LCOV - code coverage report
Current view: top level - monetdb5/mal - mal_authorize.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 441 549 80.3 %
Date: 2021-09-14 22:17:06 Functions: 24 24 100.0 %

          Line data    Source code
       1             : /*
       2             :  * This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0.  If a copy of the MPL was not distributed with this
       4             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
       5             :  *
       6             :  * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
       7             :  */
       8             : 
       9             : /*
      10             :  * (authors) M. Kersten, F. Groffen
      11             :  * Authorisation adminstration management
      12             :  * Authorisation of users is a key concept in protecting the server from
      13             :  * malicious and unauthorised users.  This file contains a number of
      14             :  * functions that administrate a set of BATs backing the authorisation
      15             :  * tables.
      16             :  *
      17             :  * The implementation is based on three persistent BATs, which keep the
      18             :  * usernames, passwords and allowed scenarios for users of the server.
      19             :  *
      20             :  */
      21             : #include "monetdb_config.h"
      22             : #include "mal_authorize.h"
      23             : #include "mal_exception.h"
      24             : #include "mal_private.h"
      25             : #include "mcrypt.h"
      26             : #include "msabaoth.h"
      27             : 
      28             : #ifdef HAVE_UNISTD_H
      29             : #include <unistd.h>
      30             : #endif
      31             : 
      32             : static str AUTHdecypherValue(str *ret, const char *value);
      33             : static str AUTHcypherValue(str *ret, const char *value);
      34             : static str AUTHverifyPassword(const char *passwd);
      35             : static BUN lookupRemoteTableKey(const char *key);
      36             : 
      37             : static BAT *user = NULL;
      38             : static BAT *pass = NULL;
      39             : static BAT *duser = NULL;
      40             : 
      41             : /* Remote table bats */
      42             : static BAT *rt_key = NULL;
      43             : static BAT *rt_uri = NULL;
      44             : static BAT *rt_remoteuser = NULL;
      45             : static BAT *rt_hashedpwd = NULL;
      46             : static BAT *rt_deleted = NULL;
      47             : /* yep, the vault key is just stored in memory */
      48             : static str vaultKey = NULL;
      49             : static str master_password = NULL;
      50             : 
      51         264 : void AUTHreset(void)
      52             : {
      53             :         //if( user) BBPunfix(user->batCacheid);
      54         264 :         user = NULL;
      55             :         //if( pass) BBPunfix(pass->batCacheid);
      56         264 :         pass = NULL;
      57             :         //if( duser) BBPunfix(duser->batCacheid);
      58         264 :         duser = NULL;
      59         264 :         if (vaultKey != NULL)
      60         264 :                 GDKfree(vaultKey);
      61         264 :         vaultKey = NULL;
      62         264 : }
      63             : 
      64             : static BUN
      65        5387 : AUTHfindUser(const char *username)
      66             : {
      67        5387 :         BATiter cni = bat_iterator(user);
      68             :         BUN p;
      69             : 
      70        5387 :         if (BAThash(user) == GDK_SUCCEED) {
      71        5387 :                 MT_rwlock_rdlock(&user->thashlock);
      72       10596 :                 HASHloop_str(cni, user->thash, p, username) {
      73        4946 :                         oid pos = p;
      74        4946 :                         if (BUNfnd(duser, &pos) == BUN_NONE) {
      75        4910 :                                 MT_rwlock_rdunlock(&user->thashlock);
      76        4910 :                                 bat_iterator_end(&cni);
      77        4910 :                                 return p;
      78             :                         }
      79             :                 }
      80         477 :                 MT_rwlock_rdunlock(&user->thashlock);
      81             :         }
      82         477 :         bat_iterator_end(&cni);
      83         477 :         return BUN_NONE;
      84             : }
      85             : 
      86             : /**
      87             :  * Requires the current client to be the admin user thread.  If not the case,
      88             :  * this function returns an InvalidCredentialsException.
      89             :  */
      90             : static str
      91        4600 : AUTHrequireAdmin(Client cntxt) {
      92             :         oid id;
      93             : 
      94        4600 :         if (cntxt == NULL)
      95             :                 return(MAL_SUCCEED);
      96        4564 :         id = cntxt->user;
      97             : 
      98        4564 :         if (id != MAL_ADMIN) {
      99           9 :                 str user = NULL;
     100             :                 str tmp;
     101             : 
     102           9 :                 rethrow("requireAdmin", tmp, AUTHresolveUser(&user, id));
     103           9 :                 tmp = createException(INVCRED, "requireAdmin", INVCRED_ACCESS_DENIED " '%s'", user);
     104           9 :                 GDKfree(user);
     105           9 :                 return tmp;
     106             :         }
     107             : 
     108             :         return(MAL_SUCCEED);
     109             : }
     110             : 
     111             : /**
     112             :  * Requires the current client to be the admin user, or the user with
     113             :  * the given username.  If not the case, this function returns an
     114             :  * InvalidCredentialsException.
     115             :  */
     116             : static str
     117           5 : AUTHrequireAdminOrUser(Client cntxt, const char *username) {
     118           5 :         oid id = cntxt->user;
     119           5 :         str user = NULL;
     120             :         str tmp = MAL_SUCCEED;
     121             : 
     122             :         /* MAL_ADMIN then all is well */
     123           5 :         if (id == MAL_ADMIN)
     124             :                 return(MAL_SUCCEED);
     125             : 
     126           0 :         rethrow("requireAdminOrUser", tmp, AUTHresolveUser(&user, id));
     127           0 :         if (username == NULL || strcmp(username, user) != 0)
     128           0 :                 tmp = createException(INVCRED, "requireAdminOrUser",
     129             :                                                           INVCRED_ACCESS_DENIED " '%s'", user);
     130             : 
     131           0 :         GDKfree(user);
     132           0 :         return tmp;
     133             : }
     134             : 
     135             : static void
     136         587 : AUTHcommit(void)
     137             : {
     138             :         bat blist[9];
     139             : 
     140         587 :         blist[0] = 0;
     141             : 
     142         587 :         assert(user);
     143         587 :         blist[1] = user->batCacheid;
     144         587 :         assert(pass);
     145         587 :         blist[2] = pass->batCacheid;
     146         587 :         assert(duser);
     147         587 :         blist[3] = duser->batCacheid;
     148         587 :         assert(rt_key);
     149         587 :         blist[4] = rt_key->batCacheid;
     150         587 :         assert(rt_uri);
     151         587 :         blist[5] = rt_uri->batCacheid;
     152         587 :         assert(rt_remoteuser);
     153         587 :         blist[6] = rt_remoteuser->batCacheid;
     154         587 :         assert(rt_hashedpwd);
     155         587 :         blist[7] = rt_hashedpwd->batCacheid;
     156         587 :         assert(rt_deleted);
     157         587 :         blist[8] = rt_deleted->batCacheid;
     158         587 :         TMsubcommit_list(blist, NULL, 9, getBBPlogno(), getBBPtransid());
     159         587 : }
     160             : 
     161             : /*
     162             :  * Localize the authorization tables in the database.  The authorization
     163             :  * tables are a set of aligned BATs that store username, password (hashed)
     164             :  * and scenario permissions.
     165             :  * If the BATs do not exist, they are created, and the monetdb
     166             :  * administrator account is added with the given password (or 'monetdb'
     167             :  * if NULL).  Initialising the authorization tables can only be done
     168             :  * after the GDK kernel has been initialized.
     169             :  */
     170             : str
     171         277 : AUTHinitTables(const char *passwd) {
     172             :         bat bid;
     173             :         int isNew = 1;
     174             :         str msg = MAL_SUCCEED;
     175             : 
     176             :         /* skip loading if already loaded */
     177         277 :         if (user != NULL && pass != NULL)
     178             :                 return(MAL_SUCCEED);
     179             : 
     180             :         /* if one is not NULL here, something is seriously screwed up */
     181         266 :         assert (user == NULL);
     182         266 :         assert (pass == NULL);
     183             : 
     184             :         /* load/create users BAT */
     185         266 :         bid = BBPindex("M5system_auth_user");
     186         266 :         if (!bid) {
     187         197 :                 user = COLnew(0, TYPE_str, 256, PERSISTENT);
     188         197 :                 if (user == NULL)
     189           0 :                         throw(MAL, "initTables.user", SQLSTATE(HY013) MAL_MALLOC_FAIL " user table");
     190             : 
     191         394 :                 if (BATkey(user, true) != GDK_SUCCEED ||
     192         394 :                         BBPrename(user->batCacheid, "M5system_auth_user") != 0 ||
     193         197 :                         BATmode(user, false) != GDK_SUCCEED) {
     194           0 :                         throw(MAL, "initTables.user", GDK_EXCEPTION);
     195             :                 }
     196             :         } else {
     197          69 :                 int dbg = GDKdebug;
     198             :                 /* don't check this bat since we'll fix it below */
     199          69 :                 GDKdebug &= ~CHECKMASK;
     200          69 :                 user = BATdescriptor(bid);
     201          69 :                 GDKdebug = dbg;
     202          69 :                 if (user == NULL)
     203           0 :                         throw(MAL, "initTables.user", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
     204             :                 isNew = 0;
     205             :         }
     206         266 :         assert(user);
     207             : 
     208             :         /* load/create password BAT */
     209         266 :         bid = BBPindex("M5system_auth_passwd_v2");
     210         266 :         if (!bid) {
     211         197 :                 pass = COLnew(0, TYPE_str, 256, PERSISTENT);
     212         197 :                 if (pass == NULL)
     213           0 :                         throw(MAL, "initTables.passwd", SQLSTATE(HY013) MAL_MALLOC_FAIL " password table");
     214             : 
     215         394 :                 if (BBPrename(pass->batCacheid, "M5system_auth_passwd_v2") != 0 ||
     216         197 :                         BATmode(pass, false) != GDK_SUCCEED) {
     217           0 :                         throw(MAL, "initTables.user", GDK_EXCEPTION);
     218             :                 }
     219             :         } else {
     220          69 :                 int dbg = GDKdebug;
     221             :                 /* don't check this bat since we'll fix it below */
     222          69 :                 GDKdebug &= ~CHECKMASK;
     223          69 :                 pass = BATdescriptor(bid);
     224          69 :                 GDKdebug = dbg;
     225          69 :                 if (pass == NULL)
     226           0 :                         throw(MAL, "initTables.passwd", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
     227             :                 isNew = 0;
     228             :         }
     229         266 :         assert(pass);
     230             : 
     231             :         /* load/create password BAT */
     232         266 :         bid = BBPindex("M5system_auth_deleted");
     233         266 :         if (!bid) {
     234         197 :                 duser = COLnew(0, TYPE_oid, 256, PERSISTENT);
     235         197 :                 if (duser == NULL)
     236           0 :                         throw(MAL, "initTables.duser", SQLSTATE(HY013) MAL_MALLOC_FAIL " deleted user table");
     237             : 
     238         394 :                 if (BBPrename(duser->batCacheid, "M5system_auth_deleted") != 0 ||
     239         197 :                         BATmode(duser, false) != GDK_SUCCEED) {
     240           0 :                         throw(MAL, "initTables.user", GDK_EXCEPTION);
     241             :                 }
     242             :         } else {
     243          69 :                 duser = BATdescriptor(bid);
     244          69 :                 if (duser == NULL)
     245           0 :                         throw(MAL, "initTables.duser", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
     246             :                 isNew = 0;
     247             :         }
     248         266 :         assert(duser);
     249             : 
     250             :         /* Remote table authorization table.
     251             :          *
     252             :          * This table holds the remote tabe authorization credentials
     253             :          * (uri, username and hashed password).
     254             :          */
     255             :         /* load/create remote table URI BAT */
     256         266 :         bid = BBPindex("M5system_auth_rt_key");
     257         266 :         if (!bid) {
     258         197 :                 rt_key = COLnew(0, TYPE_str, 256, PERSISTENT);
     259         197 :                 if (rt_key == NULL)
     260           0 :                         throw(MAL, "initTables.rt_key", SQLSTATE(HY013) MAL_MALLOC_FAIL " remote table key bat");
     261             : 
     262         394 :                 if (BBPrename(rt_key->batCacheid, "M5system_auth_rt_key") != 0 ||
     263         197 :                         BATmode(rt_key, false) != GDK_SUCCEED)
     264           0 :                         throw(MAL, "initTables.rt_key", GDK_EXCEPTION);
     265             :         }
     266             :         else {
     267          69 :                 int dbg = GDKdebug;
     268             :                 /* don't check this bat since we'll fix it below */
     269          69 :                 GDKdebug &= ~CHECKMASK;
     270          69 :                 rt_key = BATdescriptor(bid);
     271          69 :                 GDKdebug = dbg;
     272          69 :                 if (rt_key == NULL) {
     273           0 :                         throw(MAL, "initTables.rt_key", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
     274             :                 }
     275             :                 isNew = 0;
     276             :         }
     277         266 :         assert(rt_key);
     278             : 
     279             :         /* load/create remote table URI BAT */
     280         266 :         bid = BBPindex("M5system_auth_rt_uri");
     281         266 :         if (!bid) {
     282         197 :                 rt_uri = COLnew(0, TYPE_str, 256, PERSISTENT);
     283         197 :                 if (rt_uri == NULL)
     284           0 :                         throw(MAL, "initTables.rt_uri", SQLSTATE(HY013) MAL_MALLOC_FAIL " remote table uri bat");
     285             : 
     286         394 :                 if (BBPrename(rt_uri->batCacheid, "M5system_auth_rt_uri") != 0 ||
     287         197 :                         BATmode(rt_uri, false) != GDK_SUCCEED)
     288           0 :                         throw(MAL, "initTables.rt_uri", GDK_EXCEPTION);
     289             :         }
     290             :         else {
     291          69 :                 int dbg = GDKdebug;
     292             :                 /* don't check this bat since we'll fix it below */
     293          69 :                 GDKdebug &= ~CHECKMASK;
     294          69 :                 rt_uri = BATdescriptor(bid);
     295          69 :                 GDKdebug = dbg;
     296          69 :                 if (rt_uri == NULL) {
     297           0 :                         throw(MAL, "initTables.rt_uri", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
     298             :                 }
     299             :                 isNew = 0;
     300             :         }
     301         266 :         assert(rt_uri);
     302             : 
     303             :         /* load/create remote table remote user name BAT */
     304         266 :         bid = BBPindex("M5system_auth_rt_remoteuser");
     305         266 :         if (!bid) {
     306         197 :                 rt_remoteuser = COLnew(0, TYPE_str, 256, PERSISTENT);
     307         197 :                 if (rt_remoteuser == NULL)
     308           0 :                         throw(MAL, "initTables.rt_remoteuser", SQLSTATE(HY013) MAL_MALLOC_FAIL " remote table local user bat");
     309             : 
     310         394 :                 if (BBPrename(rt_remoteuser->batCacheid, "M5system_auth_rt_remoteuser") != 0 ||
     311         197 :                         BATmode(rt_remoteuser, false) != GDK_SUCCEED)
     312           0 :                         throw(MAL, "initTables.rt_remoteuser", GDK_EXCEPTION);
     313             :         }
     314             :         else {
     315          69 :                 int dbg = GDKdebug;
     316             :                 /* don't check this bat since we'll fix it below */
     317          69 :                 GDKdebug &= ~CHECKMASK;
     318          69 :                 rt_remoteuser = BATdescriptor(bid);
     319          69 :                 GDKdebug = dbg;
     320          69 :                 if (rt_remoteuser == NULL) {
     321           0 :                         throw(MAL, "initTables.rt_remoteuser", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
     322             :                 }
     323             :                 isNew = 0;
     324             :         }
     325         266 :         assert(rt_remoteuser);
     326             : 
     327             :         /* load/create remote table password BAT */
     328         266 :         bid = BBPindex("M5system_auth_rt_hashedpwd");
     329         266 :         if (!bid) {
     330         197 :                 rt_hashedpwd = COLnew(0, TYPE_str, 256, PERSISTENT);
     331         197 :                 if (rt_hashedpwd == NULL)
     332           0 :                         throw(MAL, "initTables.rt_hashedpwd", SQLSTATE(HY013) MAL_MALLOC_FAIL " remote table local user bat");
     333             : 
     334         394 :                 if (BBPrename(rt_hashedpwd->batCacheid, "M5system_auth_rt_hashedpwd") != 0 ||
     335         197 :                         BATmode(rt_hashedpwd, false) != GDK_SUCCEED)
     336           0 :                         throw(MAL, "initTables.rt_hashedpwd", GDK_EXCEPTION);
     337             :         }
     338             :         else {
     339          69 :                 int dbg = GDKdebug;
     340             :                 /* don't check this bat since we'll fix it below */
     341          69 :                 GDKdebug &= ~CHECKMASK;
     342          69 :                 rt_hashedpwd = BATdescriptor(bid);
     343          69 :                 GDKdebug = dbg;
     344          69 :                 if (rt_hashedpwd == NULL) {
     345           0 :                         throw(MAL, "initTables.rt_hashedpwd", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
     346             :                 }
     347             :                 isNew = 0;
     348             :         }
     349         266 :         assert(rt_hashedpwd);
     350             : 
     351             :         /* load/create remote table deleted entries BAT */
     352         266 :         bid = BBPindex("M5system_auth_rt_deleted");
     353         266 :         if (!bid) {
     354         197 :                 rt_deleted = COLnew(0, TYPE_oid, 256, PERSISTENT);
     355         197 :                 if (rt_deleted == NULL)
     356           0 :                         throw(MAL, "initTables.rt_deleted", SQLSTATE(HY013) MAL_MALLOC_FAIL " remote table local user bat");
     357             : 
     358         394 :                 if (BBPrename(rt_deleted->batCacheid, "M5system_auth_rt_deleted") != 0 ||
     359         197 :                         BATmode(rt_deleted, false) != GDK_SUCCEED)
     360           0 :                         throw(MAL, "initTables.rt_deleted", GDK_EXCEPTION);
     361             :                 /* If the database is not new, but we just created this BAT,
     362             :                  * write everything to disc. This needs to happen only after
     363             :                  * the last BAT of the vault has been created.
     364             :                  */
     365         197 :                 if (!isNew)
     366           0 :                         AUTHcommit();
     367             :         }
     368             :         else {
     369          69 :                 rt_deleted = BATdescriptor(bid);
     370          69 :                 if (rt_deleted == NULL) {
     371           0 :                         throw(MAL, "initTables.rt_deleted", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
     372             :                 }
     373             :                 isNew = 0;
     374             :         }
     375         266 :         assert(rt_deleted);
     376             : 
     377         266 :         if (isNew == 1) {
     378             :                 /* insert the monetdb/monetdb administrator account on a
     379             :                  * complete fresh and new auth tables system */
     380             :                 char *pw;
     381             :                 oid uid;
     382             : 
     383         197 :                 if (passwd == NULL)
     384             :                         passwd = "monetdb";   /* default password */
     385         197 :                 pw = mcrypt_BackendSum(passwd, strlen(passwd));
     386         197 :                 if(!pw) {
     387           0 :                         if (!GDKembedded())
     388           0 :                                 throw(MAL, "initTables", SQLSTATE(42000) "Crypt backend hash not found");
     389             :                         else
     390           0 :                                 pw = strdup(passwd);
     391             :                 }
     392         197 :                 msg = AUTHaddUser(&uid, NULL, "monetdb", pw);
     393         197 :                 free(pw);
     394         197 :                 if (msg)
     395             :                         return msg;
     396         197 :                 if (uid != MAL_ADMIN)
     397           0 :                         throw(MAL, "initTables", INTERNAL_AUTHORIZATION " while they were just created!");
     398             :                 /* normally, we'd commit here, but it's done already in AUTHaddUser */
     399             :         }
     400             : 
     401         266 :         if (!GDKinmemory(0) && !GDKembedded()) {
     402         254 :                 free(master_password);
     403         254 :                 master_password = NULL;
     404         254 :                 msg = msab_pickSecret(&master_password);
     405         254 :                 if (msg != NULL) {
     406           0 :                         char *nmsg = createException(MAL, "initTables", "%s", msg);
     407           0 :                         free(msg);
     408           0 :                         return nmsg;
     409             :                 }
     410             :         }
     411             : 
     412             :         return(MAL_SUCCEED);
     413             : }
     414             : 
     415             : /**
     416             :  * Checks the credentials supplied and throws an exception if invalid.
     417             :  * The user id of the authenticated user is returned upon success.
     418             :  */
     419             : str
     420        4304 : AUTHcheckCredentials(
     421             :                 oid *uid,
     422             :                 Client cntxt,
     423             :                 const char *username,
     424             :                 const char *passwd,
     425             :                 const char *challenge,
     426             :                 const char *algo)
     427             : {
     428             :         str tmp;
     429        4304 :         str pwd = NULL;
     430             :         str hash = NULL;
     431             :         BUN p;
     432             :         BATiter passi;
     433             : 
     434        4304 :         if (cntxt)
     435           3 :                 rethrow("checkCredentials", tmp, AUTHrequireAdminOrUser(cntxt, username));
     436        4304 :         assert(user);
     437        4304 :         assert(pass);
     438             : 
     439        4304 :         if (strNil(username))
     440           0 :                 throw(INVCRED, "checkCredentials", "invalid credentials for unknown user");
     441             : 
     442        4304 :         p = AUTHfindUser(username);
     443        4304 :         if (p == BUN_NONE) {
     444             :                 /* DO NOT reveal that the user doesn't exist here! */
     445           7 :                 throw(INVCRED, "checkCredentials", INVCRED_INVALID_USER " '%s'", username);
     446             :         }
     447             : 
     448             :         /* a NULL password is impossible (since we should be dealing with
     449             :          * hashes here) so we can bail out immediately
     450             :          */
     451        4297 :         if (strNil(passwd)) {
     452             :                 /* DO NOT reveal that the password is NULL here! */
     453           0 :                 throw(INVCRED, "checkCredentials", INVCRED_INVALID_USER " '%s'", username);
     454             :         }
     455             : 
     456             :         /* find the corresponding password to the user */
     457        4297 :         passi = bat_iterator(pass);
     458        4297 :         tmp = (str)BUNtvar(passi, p);
     459        4297 :         bat_iterator_end(&passi);
     460        4297 :         assert (tmp != NULL);
     461             :         /* decypher the password (we lose the original tmp here) */
     462        4297 :         rethrow("checkCredentials", tmp, AUTHdecypherValue(&pwd, tmp));
     463             : 
     464             :         /* generate the hash as the client should have done */
     465        4297 :         hash = mcrypt_hashPassword(algo, pwd, challenge);
     466        4297 :         GDKfree(pwd);
     467        4297 :         if(!hash)
     468           0 :                 throw(MAL, "checkCredentials", "hash '%s' backend not found", algo);
     469             :         /* and now we have it, compare it to what was given to us */
     470        4297 :         if (strcmp(passwd, hash) == 0) {
     471        4293 :                 *uid = p;
     472        4293 :                 free(hash);
     473        4293 :                 return(MAL_SUCCEED);
     474             :         }
     475           4 :         free(hash);
     476             : 
     477             :         /* special case: users whose name starts with '.' can authenticate using
     478             :          * the temporary master password.
     479             :          */
     480           4 :         if (username[0] == '.' && master_password != NULL && master_password[0] != '\0') {
     481             :                 // first encrypt the master password as if we've just found it
     482             :                 // in the password store
     483           0 :                 str encrypted = mcrypt_BackendSum(master_password, strlen(master_password));
     484           0 :                 if (encrypted == NULL)
     485           0 :                         throw(MAL, "checkCredentials", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     486           0 :                 hash = mcrypt_hashPassword(algo, encrypted, challenge);
     487           0 :                 if (hash && strcmp(passwd, hash) == 0) {
     488           0 :                         *uid = p;
     489           0 :                         free(hash);
     490           0 :                         return(MAL_SUCCEED);
     491             :                 }
     492           0 :                 free(hash);
     493             :         }
     494             : 
     495             :         /* of course we DO NOT print the password here */
     496           4 :         throw(INVCRED, "checkCredentials", INVCRED_INVALID_USER " '%s'", username);
     497             : }
     498             : 
     499             : /**
     500             :  * Adds the given user with password to the administration.  The
     501             :  * return value of this function is the user id of the added user.
     502             :  */
     503             : str
     504         468 : AUTHaddUser(oid *uid, Client cntxt, const char *username, const char *passwd)
     505             : {
     506             :         BUN p;
     507             :         str tmp;
     508         468 :         str hash = NULL;
     509             : 
     510         468 :         assert(user);
     511         468 :         assert(pass);
     512         468 :         if (BATcount(user))
     513         271 :                 rethrow("addUser", tmp, AUTHrequireAdmin(cntxt));
     514             : 
     515             :         /* some pre-condition checks */
     516         468 :         if (strNil(username))
     517           0 :                 throw(ILLARG, "addUser", "username should not be nil");
     518         468 :         if (strNil(passwd))
     519           0 :                 throw(ILLARG, "addUser", "password should not be nil");
     520         468 :         rethrow("addUser", tmp, AUTHverifyPassword(passwd));
     521             : 
     522             :         /* ensure that the username is not already there */
     523         468 :         p = AUTHfindUser(username);
     524         468 :         if (p != BUN_NONE)
     525           1 :                 throw(MAL, "addUser", "user '%s' already exists", username);
     526             : 
     527             :         /* we assume the BATs are still aligned */
     528         467 :         if (!GDKembedded()) {
     529         453 :                 rethrow("addUser", tmp, AUTHcypherValue(&hash, passwd));
     530             :         } else {
     531          14 :                 if (!(hash = GDKstrdup("hash")))
     532           0 :                         throw(MAL, "addUser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     533             :         }
     534             :         /* needs force, as SQL makes a view over user */
     535         934 :         if (BUNappend(user, username, true) != GDK_SUCCEED ||
     536         467 :                 BUNappend(pass, hash, true) != GDK_SUCCEED) {
     537           0 :                 GDKfree(hash);
     538           0 :                 throw(MAL, "addUser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     539             :         }
     540         467 :         GDKfree(hash);
     541             :         /* retrieve the oid of the just inserted user */
     542         467 :         p = AUTHfindUser(username);
     543             : 
     544             :         /* make the stuff persistent */
     545         467 :         if (!GDKembedded())
     546         453 :                 AUTHcommit();
     547             : 
     548         467 :         *uid = p;
     549         467 :         return(MAL_SUCCEED);
     550             : }
     551             : 
     552             : /**
     553             :  * Removes the given user from the administration.
     554             :  */
     555             : str
     556          76 : AUTHremoveUser(Client cntxt, const char *username)
     557             : {
     558             :         BUN p;
     559             :         oid id;
     560             :         str tmp;
     561             : 
     562          76 :         rethrow("removeUser", tmp, AUTHrequireAdmin(cntxt));
     563          76 :         assert(user);
     564          76 :         assert(pass);
     565             : 
     566             :         /* pre-condition check */
     567          76 :         if (strNil(username))
     568           0 :                 throw(ILLARG, "removeUser", "username should not be nil");
     569             : 
     570             :         /* ensure that the username exists */
     571          76 :         p = AUTHfindUser(username);
     572          76 :         if (p == BUN_NONE)
     573           1 :                 throw(MAL, "removeUser", "no such user: '%s'", username);
     574          75 :         id = p;
     575             : 
     576             :         /* find the name of the administrator and see if it equals username */
     577          75 :         if (id == cntxt->user)
     578           0 :                 throw(MAL, "removeUser", "cannot remove yourself");
     579             : 
     580             :         /* now, we got the oid, start removing the related tuples */
     581          75 :         if (BUNappend(duser, &id, true) != GDK_SUCCEED)
     582           0 :                 throw(MAL, "removeUser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     583             : 
     584             :         /* make the stuff persistent */
     585          75 :         AUTHcommit();
     586          75 :         return(MAL_SUCCEED);
     587             : }
     588             : 
     589             : /**
     590             :  * Changes the username of the user indicated by olduser into newuser.
     591             :  * If the newuser is already in use, an exception is thrown and nothing
     592             :  * is modified.
     593             :  */
     594             : str
     595           2 : AUTHchangeUsername(Client cntxt, const char *olduser, const char *newuser)
     596             : {
     597             :         BUN p, q;
     598             :         str tmp;
     599             : 
     600           2 :         rethrow("addUser", tmp, AUTHrequireAdminOrUser(cntxt, olduser));
     601             : 
     602             :         /* precondition checks */
     603           2 :         if (strNil(olduser))
     604           0 :                 throw(ILLARG, "changeUsername", "old username should not be nil");
     605           2 :         if (strNil(newuser))
     606           0 :                 throw(ILLARG, "changeUsername", "new username should not be nil");
     607             : 
     608             :         /* see if the olduser is valid */
     609           2 :         p = AUTHfindUser(olduser);
     610           2 :         if (p == BUN_NONE)
     611           0 :                 throw(MAL, "changeUsername", "user '%s' does not exist", olduser);
     612             :         /* ... and if the newuser is not there yet */
     613           2 :         q = AUTHfindUser(newuser);
     614           2 :         if (q != BUN_NONE)
     615           0 :                 throw(MAL, "changeUsername", "user '%s' already exists", newuser);
     616             : 
     617             :         /* ok, just do it! (with force, because sql makes view over it) */
     618           2 :         assert(user->hseqbase == 0);
     619           2 :         if (BUNreplace(user, p, newuser, true) != GDK_SUCCEED)
     620           0 :                 throw(MAL, "changeUsername", GDK_EXCEPTION);
     621           2 :         AUTHcommit();
     622           2 :         return(MAL_SUCCEED);
     623             : }
     624             : 
     625             : /**
     626             :  * Changes the password of the current user to the given password.  The
     627             :  * old password must match the one stored before the new password is
     628             :  * set.
     629             :  */
     630             : str
     631           2 : AUTHchangePassword(Client cntxt, const char *oldpass, const char *passwd)
     632             : {
     633             :         BUN p;
     634             :         str tmp= NULL;
     635           2 :         str hash= NULL;
     636             :         oid id;
     637             :         BATiter passi;
     638             :         str msg= MAL_SUCCEED;
     639             : 
     640             :         /* precondition checks */
     641           2 :         if (strNil(oldpass))
     642           0 :                 throw(ILLARG, "changePassword", "old password should not be nil");
     643           2 :         if (strNil(passwd))
     644           0 :                 throw(ILLARG, "changePassword", "password should not be nil");
     645           2 :         rethrow("changePassword", tmp, AUTHverifyPassword(passwd));
     646             : 
     647             :         /* check the old password */
     648           2 :         id = cntxt->user;
     649             :         p = id;
     650           2 :         assert(p != BUN_NONE);
     651           2 :         passi = bat_iterator(pass);
     652           2 :         tmp = BUNtvar(passi, p);
     653           2 :         bat_iterator_end(&passi);
     654           2 :         assert (tmp != NULL);
     655             :         /* decypher the password */
     656           2 :         msg = AUTHdecypherValue(&hash, tmp);
     657           2 :         if (msg)
     658             :                 return msg;
     659           2 :         if (strcmp(hash, oldpass) != 0){
     660           1 :                 GDKfree(hash);
     661           1 :                 throw(INVCRED, "changePassword", "Access denied");
     662             :         }
     663             : 
     664           1 :         GDKfree(hash);
     665             :         /* cypher the password */
     666           1 :         msg = AUTHcypherValue(&hash, passwd);
     667           1 :         if (msg)
     668             :                 return msg;
     669             : 
     670             :         /* ok, just overwrite the password field for this user */
     671             :         assert(id == p);
     672           1 :         assert(pass->hseqbase == 0);
     673           1 :         if (BUNreplace(pass, p, hash, true) != GDK_SUCCEED) {
     674           0 :                 GDKfree(hash);
     675           0 :                 throw(INVCRED, "changePassword", GDK_EXCEPTION);
     676             :         }
     677           1 :         GDKfree(hash);
     678           1 :         AUTHcommit();
     679           1 :         return(MAL_SUCCEED);
     680             : }
     681             : 
     682             : /**
     683             :  * Changes the password of the given user to the given password.  This
     684             :  * function can be used by the administrator to reset the password for a
     685             :  * user.  Note that for the administrator to change its own password, it
     686             :  * cannot use this function for obvious reasons.
     687             :  */
     688             : str
     689           4 : AUTHsetPassword(Client cntxt, const char *username, const char *passwd)
     690             : {
     691             :         BUN p;
     692             :         str tmp;
     693           4 :         str hash = NULL;
     694             :         oid id;
     695             :         BATiter useri;
     696             : 
     697           4 :         rethrow("setPassword", tmp, AUTHrequireAdmin(cntxt));
     698             : 
     699             :         /* precondition checks */
     700           4 :         if (strNil(username))
     701           0 :                 throw(ILLARG, "setPassword", "username should not be nil");
     702           4 :         if (strNil(passwd))
     703           0 :                 throw(ILLARG, "setPassword", "password should not be nil");
     704           4 :         rethrow("setPassword", tmp, AUTHverifyPassword(passwd));
     705             : 
     706           4 :         id = cntxt->user;
     707             :         /* find the name of the administrator and see if it equals username */
     708             :         p = id;
     709           4 :         assert (p != BUN_NONE);
     710           4 :         useri = bat_iterator(user);
     711           4 :         tmp = BUNtvar(useri, p);
     712           4 :         bat_iterator_end(&useri);
     713           4 :         assert (tmp != NULL);
     714           4 :         if (strcmp(tmp, username) == 0)
     715           1 :                 throw(INVCRED, "setPassword", "The administrator cannot set its own password, use changePassword instead");
     716             : 
     717             :         /* see if the user is valid */
     718           3 :         p = AUTHfindUser(username);
     719           3 :         if (p == BUN_NONE)
     720           0 :                 throw(MAL, "setPassword", "no such user '%s'", username);
     721             :         id = p;
     722             : 
     723             :         /* cypher the password */
     724           3 :         rethrow("setPassword", tmp, AUTHcypherValue(&hash, passwd));
     725             :         /* ok, just overwrite the password field for this user */
     726             :         assert (p != BUN_NONE);
     727             :         assert(id == p);
     728           3 :         assert(pass->hseqbase == 0);
     729           3 :         if (BUNreplace(pass, p, hash, true) != GDK_SUCCEED) {
     730           0 :                 GDKfree(hash);
     731           0 :                 throw(MAL, "setPassword", GDK_EXCEPTION);
     732             :         }
     733           3 :         GDKfree(hash);
     734           3 :         AUTHcommit();
     735           3 :         return(MAL_SUCCEED);
     736             : }
     737             : 
     738             : /**
     739             :  * Resolves the given user id and returns the associated username.  If
     740             :  * the id is invalid, an exception is thrown.  The given pointer to the
     741             :  * username char buffer should be NULL if this function is supposed to
     742             :  * allocate memory for it.  If the pointer is pointing to an already
     743             :  * allocated buffer, it is supposed to be of size BUFSIZ.
     744             :  */
     745             : str
     746        3990 : AUTHresolveUser(str *username, oid uid)
     747             : {
     748             :         BUN p;
     749             :         BATiter useri;
     750             : 
     751        3990 :         if (is_oid_nil(uid) || (p = (BUN) uid) >= BATcount(user))
     752           0 :                 throw(ILLARG, "resolveUser", "userid should not be nil");
     753             : 
     754        3990 :         assert(username != NULL);
     755        3990 :         useri = bat_iterator(user);
     756        3990 :         *username = GDKstrdup((str)(BUNtvar(useri, p)));
     757        3990 :         bat_iterator_end(&useri);
     758        3990 :         if (*username == NULL)
     759           0 :                 throw(MAL, "resolveUser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     760             :         return(MAL_SUCCEED);
     761             : }
     762             : 
     763             : /**
     764             :  * Returns the username of the given client.
     765             :  */
     766             : str
     767        6614 : AUTHgetUsername(str *username, Client cntxt)
     768             : {
     769             :         BUN p;
     770             :         BATiter useri;
     771             : 
     772        6614 :         p = (BUN) cntxt->user;
     773             : 
     774             :         /* If you ask for a username using a client struct, and that user
     775             :          * doesn't exist, you seriously screwed up somehow.  If this
     776             :          * happens, it may be a security breach/attempt, and hence
     777             :          * terminating the entire system seems like the right thing to do to
     778             :          * me. */
     779        6614 :         assert(p < BATcount(user));
     780             : 
     781        6614 :         useri = bat_iterator(user);
     782        6614 :         *username = GDKstrdup( BUNtvar(useri, p));
     783        6614 :         bat_iterator_end(&useri);
     784        6614 :         if (*username == NULL)
     785           0 :                 throw(MAL, "getUsername", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     786             :         return(MAL_SUCCEED);
     787             : }
     788             : 
     789             : /**
     790             :  * Returns a BAT with user names in the tail, and user ids in the head.
     791             :  */
     792             : str
     793        4184 : AUTHgetUsers(BAT **ret1, BAT **ret2, Client cntxt)
     794             : {
     795             :         BAT *bn;
     796             :         str tmp;
     797             : 
     798        4184 :         rethrow("getUsers", tmp, AUTHrequireAdmin(cntxt));
     799             : 
     800        4175 :         *ret1 = BATdense(user->hseqbase, user->hseqbase, BATcount(user));
     801        4175 :         if (*ret1 == NULL)
     802           0 :                 throw(MAL, "getUsers", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     803        4175 :         if (BATcount(duser)) {
     804         951 :                 bn = BATdiff(*ret1, duser, NULL, NULL, false, false, BUN_NONE);
     805         951 :                 BBPunfix((*ret1)->batCacheid);
     806         951 :                 *ret2 = BATproject(bn, user);
     807         951 :                 *ret1 = bn;
     808             :         } else {
     809        3224 :                 *ret2 = COLcopy(user, user->ttype, false, TRANSIENT);
     810             :         }
     811        4175 :         if (*ret1 == NULL || *ret2 == NULL) {
     812           0 :                 if (*ret1)
     813           0 :                         BBPunfix((*ret1)->batCacheid);
     814           0 :                 if (*ret2)
     815           0 :                         BBPunfix((*ret2)->batCacheid);
     816           0 :                 throw(MAL, "getUsers", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     817             :         }
     818             :         return(NULL);
     819             : }
     820             : 
     821             : /**
     822             :  * Returns the password hash as used by the backend for the given
     823             :  * username.  Throws an exception if called by a non-superuser.
     824             :  */
     825             : str
     826          65 : AUTHgetPasswordHash(str *ret, Client cntxt, const char *username)
     827             : {
     828             :         BUN p;
     829             :         BATiter i;
     830             :         str tmp;
     831          65 :         str passwd = NULL;
     832             : 
     833          65 :         rethrow("getPasswordHash", tmp, AUTHrequireAdmin(cntxt));
     834             : 
     835          65 :         if (strNil(username))
     836           0 :                 throw(ILLARG, "getPasswordHash", "username should not be nil");
     837             : 
     838          65 :         p = AUTHfindUser(username);
     839          65 :         if (p == BUN_NONE)
     840           0 :                 throw(MAL, "getPasswordHash", "user '%s' does not exist", username);
     841          65 :         i = bat_iterator(pass);
     842          65 :         tmp = BUNtvar(i, p);
     843          65 :         bat_iterator_end(&i);
     844          65 :         assert (tmp != NULL);
     845             :         /* decypher the password */
     846          65 :         rethrow("changePassword", tmp, AUTHdecypherValue(&passwd, tmp));
     847             : 
     848          65 :         *ret = passwd;
     849          65 :         return(NULL);
     850             : }
     851             : 
     852             : 
     853             : /*=== the vault ===*/
     854             : 
     855             : 
     856             : /**
     857             :  * Unlocks the vault with the given password.  Since the password is
     858             :  * just the decypher key, it is not possible to directly check whether
     859             :  * the given password is correct.  If incorrect, however, all decypher
     860             :  * operations will probably fail or return an incorrect decyphered
     861             :  * value.
     862             :  */
     863             : str
     864         266 : AUTHunlockVault(const char *password)
     865             : {
     866         266 :         if (strNil(password))
     867           0 :                 throw(ILLARG, "unlockVault", "password should not be nil");
     868             : 
     869             :         /* even though I think this function should be called only once, it
     870             :          * is not of real extra efforts to avoid a mem-leak if it is used
     871             :          * multiple times */
     872         266 :         if (vaultKey != NULL)
     873           0 :                 GDKfree(vaultKey);
     874             : 
     875         266 :         if ((vaultKey = GDKstrdup(password)) == NULL)
     876           0 :                 throw(MAL, "unlockVault", SQLSTATE(HY013) MAL_MALLOC_FAIL " vault key");
     877             :         return(MAL_SUCCEED);
     878             : }
     879             : 
     880             : /**
     881             :  * Decyphers a given value, using the vaultKey.  The returned value
     882             :  * might be incorrect if the vaultKey is incorrect or unset.  If the
     883             :  * cypher algorithm fails or detects an invalid password, it might throw
     884             :  * an exception.  The ret string is GDKmalloced, and should be GDKfreed
     885             :  * by the caller.
     886             :  */
     887             : static str
     888        4432 : AUTHdecypherValue(str *ret, const char *value)
     889             : {
     890             :         /* Cyphering and decyphering can be done using many algorithms.
     891             :          * Future requirements might want a stronger cypher than the XOR
     892             :          * cypher chosen here.  It is left up to the implementor how to do
     893             :          * that once those algoritms become available.  It could be
     894             :          * #ifdef-ed or on if-basis depending on whether the cypher
     895             :          * algorithm is a compile, or runtime option.  When necessary, this
     896             :          * function could be extended with an extra argument that indicates
     897             :          * the cypher algorithm.
     898             :          */
     899             : 
     900             :         /* this is the XOR decypher implementation */
     901             :         str r, w;
     902             :         const char *s = value;
     903             :         char t = '\0';
     904             :         int escaped = 0;
     905             :         /* we default to some garbage key, just to make password unreadable
     906             :          * (a space would only uppercase the password) */
     907             :         size_t keylen = 0;
     908             : 
     909        4432 :         if (vaultKey == NULL)
     910           0 :                 throw(MAL, "decypherValue", "The vault is still locked!");
     911        4432 :         w = r = GDKmalloc(sizeof(char) * (strlen(value) + 1));
     912        4432 :         if( r == NULL)
     913           0 :                 throw(MAL, "decypherValue", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     914             : 
     915        4432 :         keylen = strlen(vaultKey);
     916             : 
     917             :         /* XOR all characters.  If we encounter a 'one' char after the XOR
     918             :          * operation, it is an escape, so replace it with the next char. */
     919      602917 :         for (; (t = *s) != '\0'; s++) {
     920      598485 :                 if (t == '\1' && escaped == 0) {
     921             :                         escaped = 1;
     922       31189 :                         continue;
     923      567296 :                 } else if (escaped != 0) {
     924       31189 :                         t -= 1;
     925             :                         escaped = 0;
     926             :                 }
     927      567296 :                 *w = t ^ vaultKey[(w - r) % keylen];
     928      567296 :                 w++;
     929             :         }
     930        4432 :         *w = '\0';
     931             : 
     932        4432 :         *ret = r;
     933        4432 :         return(MAL_SUCCEED);
     934             : }
     935             : 
     936             : /**
     937             :  * Cyphers the given string using the vaultKey.  If the cypher algorithm
     938             :  * fails or detects an invalid password, it might throw an exception.
     939             :  * The ret string is GDKmalloced, and should be GDKfreed by the caller.
     940             :  */
     941             : static str
     942         504 : AUTHcypherValue(str *ret, const char *value)
     943             : {
     944             :         /* this is the XOR cypher implementation */
     945             :         str r, w;
     946             :         const char *s = value;
     947             :         /* we default to some garbage key, just to make password unreadable
     948             :          * (a space would only uppercase the password) */
     949             :         size_t keylen = 0;
     950             : 
     951         504 :         if (vaultKey == NULL)
     952           0 :                 throw(MAL, "cypherValue", "The vault is still locked!");
     953         504 :         w = r = GDKmalloc(sizeof(char) * (strlen(value) * 2 + 1));
     954         504 :         if( r == NULL)
     955           0 :                 throw(MAL, "cypherValue", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     956             : 
     957         504 :         keylen = strlen(vaultKey);
     958             : 
     959             :         /* XOR all characters.  If we encounter a 'zero' char after the XOR
     960             :          * operation, escape it with an 'one' char. */
     961       65016 :         for (; *s != '\0'; s++) {
     962       64512 :                 *w = *s ^ vaultKey[(s - value) % keylen];
     963       64512 :                 if (*w == '\0') {
     964        1949 :                         *w++ = '\1';
     965        1949 :                         *w = '\1';
     966       62563 :                 } else if (*w == '\1') {
     967         290 :                         *w++ = '\1';
     968         290 :                         *w = '\2';
     969             :                 }
     970       64512 :                 w++;
     971             :         }
     972         504 :         *w = '\0';
     973             : 
     974         504 :         *ret = r;
     975         504 :         return(MAL_SUCCEED);
     976             : }
     977             : 
     978             : /**
     979             :  * Checks if the given string is a (hex represented) hash for the
     980             :  * current backend.  This check allows to at least forbid storing
     981             :  * trivial plain text passwords by a simple check.
     982             :  */
     983             : #define concat(x,y)     x##y
     984             : #define digestlength(h) concat(h, _DIGEST_LENGTH)
     985             : static str
     986         521 : AUTHverifyPassword(const char *passwd)
     987             : {
     988             :         const char *p = passwd;
     989         521 :         size_t len = strlen(p);
     990             : 
     991         521 :         if (len != digestlength(MONETDB5_PASSWDHASH_TOKEN) * 2) {
     992           0 :                 throw(MAL, "verifyPassword",
     993             :                           "password is not %d chars long, is it a hex "
     994             :                           "representation of a %s password hash?",
     995             :                           digestlength(MONETDB5_PASSWDHASH_TOKEN), MONETDB5_PASSWDHASH);
     996             :         }
     997             :         len++; // required in case all the checks above are false
     998       67209 :         while (*p != '\0') {
     999       66688 :                 if (!((*p >= 'a' && *p <= 'z') || isdigit((unsigned char) *p)))
    1000           0 :                         throw(MAL, "verifyPassword",
    1001             :                                         "password does contain invalid characters, is it a"
    1002             :                                         "lowercase hex representation of a hash?");
    1003       66688 :                 p++;
    1004             :         }
    1005             : 
    1006             :         return(MAL_SUCCEED);
    1007             : }
    1008             : 
    1009             : static BUN
    1010         151 : lookupRemoteTableKey(const char *key)
    1011             : {
    1012         151 :         BATiter cni = bat_iterator(rt_key);
    1013             :         BUN p = BUN_NONE;
    1014             : 
    1015         151 :         assert(rt_key);
    1016         151 :         assert(rt_deleted);
    1017             : 
    1018         151 :         if (BAThash(rt_key) == GDK_SUCCEED) {
    1019         151 :                 MT_rwlock_rdlock(&cni.b->thashlock);
    1020         221 :                 HASHloop_str(cni, cni.b->thash, p, key) {
    1021          75 :                         oid pos = p;
    1022          75 :                         if (BUNfnd(rt_deleted, &pos) == BUN_NONE) {
    1023          74 :                                 MT_rwlock_rdunlock(&cni.b->thashlock);
    1024          74 :                                 bat_iterator_end(&cni);
    1025          74 :                                 return p;
    1026             :                         }
    1027             :                 }
    1028          77 :                 MT_rwlock_rdunlock(&cni.b->thashlock);
    1029             :         }
    1030          77 :         bat_iterator_end(&cni);
    1031             : 
    1032          77 :         return BUN_NONE;
    1033             : 
    1034             : }
    1035             : 
    1036             : str
    1037          97 : AUTHgetRemoteTableCredentials(const char *local_table, str *uri, str *username, str *password)
    1038             : {
    1039             :         BUN p;
    1040             :         BATiter i;
    1041             :         str tmp;
    1042             :         str pwhash;
    1043             : 
    1044          97 :         if (strNil(local_table)) {
    1045           0 :                 throw(ILLARG, "getRemoteTableCredentials", "local table should not be nil");
    1046             :         }
    1047             : 
    1048          97 :         p = lookupRemoteTableKey(local_table);
    1049          97 :         if (p == BUN_NONE) {
    1050             :                 // No credentials for remote table with name local_table.
    1051             :                 return MAL_SUCCEED;
    1052             :         }
    1053             : 
    1054          68 :         assert(rt_key);
    1055          68 :         assert(rt_uri);
    1056          68 :         assert(rt_remoteuser);
    1057          68 :         assert(rt_hashedpwd);
    1058             : 
    1059             :         assert(p != BUN_NONE);
    1060          68 :         i = bat_iterator(rt_uri);
    1061          68 :         *uri = BUNtvar(i, p);
    1062          68 :         bat_iterator_end(&i);
    1063             : 
    1064          68 :         i = bat_iterator(rt_remoteuser);
    1065          68 :         *username = BUNtvar(i, p);
    1066          68 :         bat_iterator_end(&i);
    1067             : 
    1068          68 :         i = bat_iterator(rt_hashedpwd);
    1069          68 :         tmp = BUNtvar(i, p);
    1070          68 :         bat_iterator_end(&i);
    1071          68 :         rethrow("getRemoteTableCredentials", tmp, AUTHdecypherValue(&pwhash, tmp));
    1072             : 
    1073          68 :         *password = pwhash;
    1074             : 
    1075          68 :         return MAL_SUCCEED;
    1076             : }
    1077             : 
    1078             : str
    1079          47 : AUTHaddRemoteTableCredentials(const char *local_table, const char *local_user, const char *uri, const char *remoteuser, const char *pass, bool pw_encrypted)
    1080             : {
    1081          47 :         char *pwhash = NULL;
    1082             :         bool free_pw = false;
    1083             :         str output = MAL_SUCCEED;
    1084             :         BUN p;
    1085             :         str msg = MAL_SUCCEED;
    1086             : 
    1087          47 :         if (strNil(uri))
    1088           0 :                 throw(ILLARG, "addRemoteTableCredentials", "URI cannot be nil");
    1089          47 :         if (strNil(local_user))
    1090           0 :                 throw(ILLARG, "addRemoteTableCredentials", "local user name cannot be nil");
    1091             : 
    1092          47 :         assert(rt_key);
    1093          47 :         assert(rt_uri);
    1094          47 :         assert(rt_remoteuser);
    1095          47 :         assert(rt_hashedpwd);
    1096             : 
    1097          47 :         p = lookupRemoteTableKey(local_table);
    1098             : 
    1099          47 :         if (p != BUN_NONE) {
    1100             :         /* An entry with the given key is already in the vault (note: the
    1101             :          * key is the string "schema.table_name", which is unique in the
    1102             :          * SQL catalog, in the sense that no two tables can have the same
    1103             :          * name in the same schema). This can only mean that the entry is
    1104             :          * invalid (i.e. it does not correspond to a valid SQL table). To
    1105             :          * see this consider the following:
    1106             :          *
    1107             :          * 1. The function `AUTHaddRemoteTableCredentials` is only called
    1108             :          * from `rel_create_table` (also from the upgrade code, but this
    1109             :          * is irrelevant for our discussion since, in this case no remote
    1110             :          * table will have any credentials), i.e. when we are creating a
    1111             :          * (remote) table.
    1112             :          *
    1113             :          * 2. If a remote table with name "schema.table_name" has been
    1114             :          * defined previously (i.e there is already a SQL catalog entry
    1115             :          * for it) and we try to define it again,
    1116             :          * `AUTHaddRemoteTableCredentials` will *not* be called because we
    1117             :          * are trying to define an already existing table, and the SQL
    1118             :          * layer will not allow us to continue.
    1119             :          *
    1120             :          * 3. The only way to add an entry in the vault is calling this
    1121             :          * function.
    1122             :          *
    1123             :          * Accepting (1)-(3) above means that just before
    1124             :          * `AUTHaddRemoteTableCredentials` gets called with an argument
    1125             :          * "schema.table_name", that table does not exist in the SQL
    1126             :          * catalog.
    1127             :          *
    1128             :          * This means that if we call `AUTHaddRemoteTableCredentials` with
    1129             :          * argument "schema.table_name" and find an entry with that key
    1130             :          * already in the vault, we can safely overwrite it, because the
    1131             :          * table it refers to does not exist in the SQL catalog. We can
    1132             :          * also conclude that the previous entry was added in the vault as
    1133             :          * part of a CREATE REMOTE TABLE call (conclusion follows from (1)
    1134             :          * and (3)), that did not create a corresponding entry in the SQL
    1135             :          * catalog (conclusion follows from (2)). The only (valid) way for
    1136             :          * this to happen is if the CREATE REMOTE TABLE call was inside a
    1137             :          * transaction that did not succeed.
    1138             :          *
    1139             :          * Implementation note: we first delete the entry and then add a
    1140             :          * new entry with the same key.
    1141             :          */
    1142           0 :                 if((output = AUTHdeleteRemoteTableCredentials(local_table)) != MAL_SUCCEED)
    1143             :                         return output;
    1144             :         }
    1145             : 
    1146          47 :         if (pass == NULL) {
    1147             :                 /* NOTE: Is having the client == NULL safe? */
    1148          36 :                 if((output = AUTHgetPasswordHash(&pwhash, NULL, local_user)) != MAL_SUCCEED)
    1149             :                         return output;
    1150             :         }
    1151             :         else {
    1152             :                 free_pw = true;
    1153          11 :                 if (pw_encrypted) {
    1154           1 :                         if((pwhash = strdup(pass)) == NULL)
    1155           0 :                                 throw(MAL, "addRemoteTableCredentials", SQLSTATE(HY013) MAL_MALLOC_FAIL);
    1156             :                 }
    1157             :                 else {
    1158             :                         /* Note: the remote server might have used a different
    1159             :                          * algorithm to hash the pwhash.
    1160             :                          */
    1161          10 :                         if((pwhash = mcrypt_BackendSum(pass, strlen(pass))) == NULL)
    1162           0 :                                 throw(MAL, "addRemoteTableCredentials", SQLSTATE(42000) "Crypt backend hash not found");
    1163             :                 }
    1164             :         }
    1165          47 :         msg = AUTHverifyPassword(pwhash);
    1166          47 :         if( msg != MAL_SUCCEED){
    1167           0 :                 free(pwhash);
    1168           0 :                 return msg;
    1169             :         }
    1170             : 
    1171             :         str cypher;
    1172          47 :         msg = AUTHcypherValue(&cypher, pwhash);
    1173          47 :         if( msg != MAL_SUCCEED){
    1174           0 :                 free(pwhash);
    1175           0 :                 return msg;
    1176             :         }
    1177             : 
    1178             :         /* Add entry */
    1179          94 :         bool table_entry = (BUNappend(rt_key, local_table, true) == GDK_SUCCEED &&
    1180          94 :                                                 BUNappend(rt_uri, uri, true) == GDK_SUCCEED &&
    1181         141 :                                                 BUNappend(rt_remoteuser, remoteuser, true) == GDK_SUCCEED &&
    1182          47 :                                                 BUNappend(rt_hashedpwd, cypher, true) == GDK_SUCCEED);
    1183             : 
    1184          47 :         if (!table_entry) {
    1185           0 :                 if (free_pw) {
    1186           0 :                         free(pwhash);
    1187             :                 }
    1188             :                 else {
    1189           0 :                         GDKfree(pwhash);
    1190             :                 }
    1191           0 :                 GDKfree(cypher);
    1192           0 :                 throw(MAL, "addRemoteTableCredentials", SQLSTATE(HY013) MAL_MALLOC_FAIL);
    1193             :         }
    1194             : 
    1195          47 :         AUTHcommit();
    1196             : 
    1197          47 :         if (free_pw) {
    1198          11 :                 free(pwhash);
    1199             :         }
    1200             :         else {
    1201          36 :                 GDKfree(pwhash);
    1202             :         }
    1203          47 :         GDKfree(cypher);
    1204          47 :         return MAL_SUCCEED;
    1205             : }
    1206             : 
    1207             : str
    1208           7 : AUTHdeleteRemoteTableCredentials(const char *local_table)
    1209             : {
    1210             :         BUN p;
    1211             :         oid id;
    1212             : 
    1213           7 :         assert(rt_key);
    1214           7 :         assert(rt_uri);
    1215           7 :         assert(rt_remoteuser);
    1216           7 :         assert(rt_hashedpwd);
    1217             : 
    1218             :         /* pre-condition check */
    1219           7 :         if (strNil(local_table))
    1220           0 :                 throw(ILLARG, "deleteRemoteTableCredentials", "local table cannot be nil");
    1221             : 
    1222             :         /* ensure that the username exists */
    1223           7 :         p = lookupRemoteTableKey(local_table);
    1224           7 :         if (p == BUN_NONE)
    1225           1 :                 throw(MAL, "deleteRemoteTableCredentials", "no such table: '%s'", local_table);
    1226           6 :         id = p;
    1227             : 
    1228             :         /* now, we got the oid, start removing the related tuples */
    1229           6 :         if (BUNappend(rt_deleted, &id, true) != GDK_SUCCEED)
    1230           0 :                 throw(MAL, "deleteRemoteTableCredentials", SQLSTATE(HY013) MAL_MALLOC_FAIL);
    1231             : 
    1232             :         /* make the stuff persistent */
    1233           6 :         AUTHcommit();
    1234           6 :         return(MAL_SUCCEED);
    1235             : }

Generated by: LCOV version 1.14