LCOV - code coverage report
Current view: top level - monetdb5/mal - mal_authorize.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 440 550 80.0 %
Date: 2021-10-13 02:24:04 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        6466 : AUTHfindUser(const char *username)
      66             : {
      67        6466 :         BATiter cni = bat_iterator(user);
      68             :         BUN p;
      69             : 
      70        6466 :         if (BAThash(user) == GDK_SUCCEED) {
      71        6466 :                 MT_rwlock_rdlock(&user->thashlock);
      72       12754 :                 HASHloop_str(cni, user->thash, p, username) {
      73        6025 :                         oid pos = p;
      74        6025 :                         if (BUNfnd(duser, &pos) == BUN_NONE) {
      75        5989 :                                 MT_rwlock_rdunlock(&user->thashlock);
      76        5989 :                                 bat_iterator_end(&cni);
      77        5989 :                                 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        4617 : AUTHrequireAdmin(Client cntxt) {
      92             :         oid id;
      93             : 
      94        4617 :         if (cntxt == NULL)
      95             :                 return(MAL_SUCCEED);
      96        4576 :         id = cntxt->user;
      97             : 
      98        4576 :         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         597 : AUTHcommit(void)
     137             : {
     138             :         bat blist[9];
     139             : 
     140         597 :         blist[0] = 0;
     141             : 
     142         597 :         assert(user);
     143         597 :         blist[1] = user->batCacheid;
     144         597 :         assert(pass);
     145         597 :         blist[2] = pass->batCacheid;
     146         597 :         assert(duser);
     147         597 :         blist[3] = duser->batCacheid;
     148         597 :         assert(rt_key);
     149         597 :         blist[4] = rt_key->batCacheid;
     150         597 :         assert(rt_uri);
     151         597 :         blist[5] = rt_uri->batCacheid;
     152         597 :         assert(rt_remoteuser);
     153         597 :         blist[6] = rt_remoteuser->batCacheid;
     154         597 :         assert(rt_hashedpwd);
     155         597 :         blist[7] = rt_hashedpwd->batCacheid;
     156         597 :         assert(rt_deleted);
     157         597 :         blist[8] = rt_deleted->batCacheid;
     158         597 :         TMsubcommit_list(blist, NULL, 9, getBBPlogno(), getBBPtransid());
     159         597 : }
     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        5378 : 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        5378 :         str pwd = NULL;
     430             :         str hash = NULL;
     431             :         BUN p;
     432             :         BATiter passi;
     433             : 
     434        5378 :         if (cntxt)
     435           3 :                 rethrow("checkCredentials", tmp, AUTHrequireAdminOrUser(cntxt, username));
     436        5378 :         assert(user);
     437        5378 :         assert(pass);
     438             : 
     439        5378 :         if (strNil(username))
     440           0 :                 throw(INVCRED, "checkCredentials", "invalid credentials for unknown user");
     441             : 
     442        5378 :         p = AUTHfindUser(username);
     443        5378 :         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        5371 :         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        5371 :         passi = bat_iterator(pass);
     458        5371 :         tmp = (str)BUNtvar(passi, p);
     459        5371 :         bat_iterator_end(&passi);
     460        5371 :         assert (tmp != NULL);
     461             :         /* decypher the password (we lose the original tmp here) */
     462        5371 :         rethrow("checkCredentials", tmp, AUTHdecypherValue(&pwd, tmp));
     463             : 
     464             :         /* generate the hash as the client should have done */
     465        5371 :         hash = mcrypt_hashPassword(algo, pwd, challenge);
     466        5371 :         GDKfree(pwd);
     467        5371 :         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        5371 :         if (strcmp(passwd, hash) == 0) {
     471        5367 :                 *uid = p;
     472        5367 :                 free(hash);
     473        5367 :                 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 :                 free(encrypted);
     488           0 :                 if (hash && strcmp(passwd, hash) == 0) {
     489           0 :                         *uid = p;
     490           0 :                         free(hash);
     491           0 :                         return(MAL_SUCCEED);
     492             :                 }
     493           0 :                 free(hash);
     494             :         }
     495             : 
     496             :         /* of course we DO NOT print the password here */
     497           4 :         throw(INVCRED, "checkCredentials", INVCRED_INVALID_USER " '%s'", username);
     498             : }
     499             : 
     500             : /**
     501             :  * Adds the given user with password to the administration.  The
     502             :  * return value of this function is the user id of the added user.
     503             :  */
     504             : str
     505         468 : AUTHaddUser(oid *uid, Client cntxt, const char *username, const char *passwd)
     506             : {
     507             :         BUN p;
     508             :         str tmp;
     509         468 :         str hash = NULL;
     510             : 
     511         468 :         assert(user);
     512         468 :         assert(pass);
     513         468 :         if (BATcount(user))
     514         271 :                 rethrow("addUser", tmp, AUTHrequireAdmin(cntxt));
     515             : 
     516             :         /* some pre-condition checks */
     517         468 :         if (strNil(username))
     518           0 :                 throw(ILLARG, "addUser", "username should not be nil");
     519         468 :         if (strNil(passwd))
     520           0 :                 throw(ILLARG, "addUser", "password should not be nil");
     521         468 :         rethrow("addUser", tmp, AUTHverifyPassword(passwd));
     522             : 
     523             :         /* ensure that the username is not already there */
     524         468 :         p = AUTHfindUser(username);
     525         468 :         if (p != BUN_NONE)
     526           1 :                 throw(MAL, "addUser", "user '%s' already exists", username);
     527             : 
     528             :         /* we assume the BATs are still aligned */
     529         467 :         if (!GDKembedded()) {
     530         453 :                 rethrow("addUser", tmp, AUTHcypherValue(&hash, passwd));
     531             :         } else {
     532          14 :                 if (!(hash = GDKstrdup("hash")))
     533           0 :                         throw(MAL, "addUser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     534             :         }
     535             :         /* needs force, as SQL makes a view over user */
     536         934 :         if (BUNappend(user, username, true) != GDK_SUCCEED ||
     537         467 :                 BUNappend(pass, hash, true) != GDK_SUCCEED) {
     538           0 :                 GDKfree(hash);
     539           0 :                 throw(MAL, "addUser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     540             :         }
     541         467 :         GDKfree(hash);
     542             :         /* retrieve the oid of the just inserted user */
     543         467 :         p = AUTHfindUser(username);
     544             : 
     545             :         /* make the stuff persistent */
     546         467 :         if (!GDKembedded())
     547         453 :                 AUTHcommit();
     548             : 
     549         467 :         *uid = p;
     550         467 :         return(MAL_SUCCEED);
     551             : }
     552             : 
     553             : /**
     554             :  * Removes the given user from the administration.
     555             :  */
     556             : str
     557          76 : AUTHremoveUser(Client cntxt, const char *username)
     558             : {
     559             :         BUN p;
     560             :         oid id;
     561             :         str tmp;
     562             : 
     563          76 :         rethrow("removeUser", tmp, AUTHrequireAdmin(cntxt));
     564          76 :         assert(user);
     565          76 :         assert(pass);
     566             : 
     567             :         /* pre-condition check */
     568          76 :         if (strNil(username))
     569           0 :                 throw(ILLARG, "removeUser", "username should not be nil");
     570             : 
     571             :         /* ensure that the username exists */
     572          76 :         p = AUTHfindUser(username);
     573          76 :         if (p == BUN_NONE)
     574           1 :                 throw(MAL, "removeUser", "no such user: '%s'", username);
     575          75 :         id = p;
     576             : 
     577             :         /* find the name of the administrator and see if it equals username */
     578          75 :         if (id == cntxt->user)
     579           0 :                 throw(MAL, "removeUser", "cannot remove yourself");
     580             : 
     581             :         /* now, we got the oid, start removing the related tuples */
     582          75 :         if (BUNappend(duser, &id, true) != GDK_SUCCEED)
     583           0 :                 throw(MAL, "removeUser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     584             : 
     585             :         /* make the stuff persistent */
     586          75 :         AUTHcommit();
     587          75 :         return(MAL_SUCCEED);
     588             : }
     589             : 
     590             : /**
     591             :  * Changes the username of the user indicated by olduser into newuser.
     592             :  * If the newuser is already in use, an exception is thrown and nothing
     593             :  * is modified.
     594             :  */
     595             : str
     596           2 : AUTHchangeUsername(Client cntxt, const char *olduser, const char *newuser)
     597             : {
     598             :         BUN p, q;
     599             :         str tmp;
     600             : 
     601           2 :         rethrow("addUser", tmp, AUTHrequireAdminOrUser(cntxt, olduser));
     602             : 
     603             :         /* precondition checks */
     604           2 :         if (strNil(olduser))
     605           0 :                 throw(ILLARG, "changeUsername", "old username should not be nil");
     606           2 :         if (strNil(newuser))
     607           0 :                 throw(ILLARG, "changeUsername", "new username should not be nil");
     608             : 
     609             :         /* see if the olduser is valid */
     610           2 :         p = AUTHfindUser(olduser);
     611           2 :         if (p == BUN_NONE)
     612           0 :                 throw(MAL, "changeUsername", "user '%s' does not exist", olduser);
     613             :         /* ... and if the newuser is not there yet */
     614           2 :         q = AUTHfindUser(newuser);
     615           2 :         if (q != BUN_NONE)
     616           0 :                 throw(MAL, "changeUsername", "user '%s' already exists", newuser);
     617             : 
     618             :         /* ok, just do it! (with force, because sql makes view over it) */
     619           2 :         assert(user->hseqbase == 0);
     620           2 :         if (BUNreplace(user, p, newuser, true) != GDK_SUCCEED)
     621           0 :                 throw(MAL, "changeUsername", GDK_EXCEPTION);
     622           2 :         AUTHcommit();
     623           2 :         return(MAL_SUCCEED);
     624             : }
     625             : 
     626             : /**
     627             :  * Changes the password of the current user to the given password.  The
     628             :  * old password must match the one stored before the new password is
     629             :  * set.
     630             :  */
     631             : str
     632           2 : AUTHchangePassword(Client cntxt, const char *oldpass, const char *passwd)
     633             : {
     634             :         BUN p;
     635             :         str tmp= NULL;
     636           2 :         str hash= NULL;
     637             :         oid id;
     638             :         BATiter passi;
     639             :         str msg= MAL_SUCCEED;
     640             : 
     641             :         /* precondition checks */
     642           2 :         if (strNil(oldpass))
     643           0 :                 throw(ILLARG, "changePassword", "old password should not be nil");
     644           2 :         if (strNil(passwd))
     645           0 :                 throw(ILLARG, "changePassword", "password should not be nil");
     646           2 :         rethrow("changePassword", tmp, AUTHverifyPassword(passwd));
     647             : 
     648             :         /* check the old password */
     649           2 :         id = cntxt->user;
     650             :         p = id;
     651           2 :         assert(p != BUN_NONE);
     652           2 :         passi = bat_iterator(pass);
     653           2 :         tmp = BUNtvar(passi, p);
     654           2 :         bat_iterator_end(&passi);
     655           2 :         assert (tmp != NULL);
     656             :         /* decypher the password */
     657           2 :         msg = AUTHdecypherValue(&hash, tmp);
     658           2 :         if (msg)
     659             :                 return msg;
     660           2 :         if (strcmp(hash, oldpass) != 0){
     661           1 :                 GDKfree(hash);
     662           1 :                 throw(INVCRED, "changePassword", "Access denied");
     663             :         }
     664             : 
     665           1 :         GDKfree(hash);
     666             :         /* cypher the password */
     667           1 :         msg = AUTHcypherValue(&hash, passwd);
     668           1 :         if (msg)
     669             :                 return msg;
     670             : 
     671             :         /* ok, just overwrite the password field for this user */
     672             :         assert(id == p);
     673           1 :         assert(pass->hseqbase == 0);
     674           1 :         if (BUNreplace(pass, p, hash, true) != GDK_SUCCEED) {
     675           0 :                 GDKfree(hash);
     676           0 :                 throw(INVCRED, "changePassword", GDK_EXCEPTION);
     677             :         }
     678           1 :         GDKfree(hash);
     679           1 :         AUTHcommit();
     680           1 :         return(MAL_SUCCEED);
     681             : }
     682             : 
     683             : /**
     684             :  * Changes the password of the given user to the given password.  This
     685             :  * function can be used by the administrator to reset the password for a
     686             :  * user.  Note that for the administrator to change its own password, it
     687             :  * cannot use this function for obvious reasons.
     688             :  */
     689             : str
     690           4 : AUTHsetPassword(Client cntxt, const char *username, const char *passwd)
     691             : {
     692             :         BUN p;
     693             :         str tmp;
     694           4 :         str hash = NULL;
     695             :         oid id;
     696             :         BATiter useri;
     697             : 
     698           4 :         rethrow("setPassword", tmp, AUTHrequireAdmin(cntxt));
     699             : 
     700             :         /* precondition checks */
     701           4 :         if (strNil(username))
     702           0 :                 throw(ILLARG, "setPassword", "username should not be nil");
     703           4 :         if (strNil(passwd))
     704           0 :                 throw(ILLARG, "setPassword", "password should not be nil");
     705           4 :         rethrow("setPassword", tmp, AUTHverifyPassword(passwd));
     706             : 
     707           4 :         id = cntxt->user;
     708             :         /* find the name of the administrator and see if it equals username */
     709             :         p = id;
     710           4 :         assert (p != BUN_NONE);
     711           4 :         useri = bat_iterator(user);
     712           4 :         tmp = BUNtvar(useri, p);
     713           4 :         bat_iterator_end(&useri);
     714           4 :         assert (tmp != NULL);
     715           4 :         if (strcmp(tmp, username) == 0)
     716           1 :                 throw(INVCRED, "setPassword", "The administrator cannot set its own password, use changePassword instead");
     717             : 
     718             :         /* see if the user is valid */
     719           3 :         p = AUTHfindUser(username);
     720           3 :         if (p == BUN_NONE)
     721           0 :                 throw(MAL, "setPassword", "no such user '%s'", username);
     722             :         id = p;
     723             : 
     724             :         /* cypher the password */
     725           3 :         rethrow("setPassword", tmp, AUTHcypherValue(&hash, passwd));
     726             :         /* ok, just overwrite the password field for this user */
     727             :         assert (p != BUN_NONE);
     728             :         assert(id == p);
     729           3 :         assert(pass->hseqbase == 0);
     730           3 :         if (BUNreplace(pass, p, hash, true) != GDK_SUCCEED) {
     731           0 :                 GDKfree(hash);
     732           0 :                 throw(MAL, "setPassword", GDK_EXCEPTION);
     733             :         }
     734           3 :         GDKfree(hash);
     735           3 :         AUTHcommit();
     736           3 :         return(MAL_SUCCEED);
     737             : }
     738             : 
     739             : /**
     740             :  * Resolves the given user id and returns the associated username.  If
     741             :  * the id is invalid, an exception is thrown.  The given pointer to the
     742             :  * username char buffer should be NULL if this function is supposed to
     743             :  * allocate memory for it.  If the pointer is pointing to an already
     744             :  * allocated buffer, it is supposed to be of size BUFSIZ.
     745             :  */
     746             : str
     747        5063 : AUTHresolveUser(str *username, oid uid)
     748             : {
     749             :         BUN p;
     750             :         BATiter useri;
     751             : 
     752        5063 :         if (is_oid_nil(uid) || (p = (BUN) uid) >= BATcount(user))
     753           0 :                 throw(ILLARG, "resolveUser", "userid should not be nil");
     754             : 
     755        5063 :         assert(username != NULL);
     756        5063 :         useri = bat_iterator(user);
     757        5063 :         *username = GDKstrdup((str)(BUNtvar(useri, p)));
     758        5063 :         bat_iterator_end(&useri);
     759        5063 :         if (*username == NULL)
     760           0 :                 throw(MAL, "resolveUser", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     761             :         return(MAL_SUCCEED);
     762             : }
     763             : 
     764             : /**
     765             :  * Returns the username of the given client.
     766             :  */
     767             : str
     768        7688 : AUTHgetUsername(str *username, Client cntxt)
     769             : {
     770             :         BUN p;
     771             :         BATiter useri;
     772             : 
     773        7688 :         p = (BUN) cntxt->user;
     774             : 
     775             :         /* If you ask for a username using a client struct, and that user
     776             :          * doesn't exist, you seriously screwed up somehow.  If this
     777             :          * happens, it may be a security breach/attempt, and hence
     778             :          * terminating the entire system seems like the right thing to do to
     779             :          * me. */
     780        7688 :         assert(p < BATcount(user));
     781             : 
     782        7688 :         useri = bat_iterator(user);
     783        7688 :         *username = GDKstrdup( BUNtvar(useri, p));
     784        7688 :         bat_iterator_end(&useri);
     785        7688 :         if (*username == NULL)
     786           0 :                 throw(MAL, "getUsername", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     787             :         return(MAL_SUCCEED);
     788             : }
     789             : 
     790             : /**
     791             :  * Returns a BAT with user names in the tail, and user ids in the head.
     792             :  */
     793             : str
     794        4196 : AUTHgetUsers(BAT **ret1, BAT **ret2, Client cntxt)
     795             : {
     796             :         BAT *bn;
     797             :         str tmp;
     798             : 
     799        4196 :         rethrow("getUsers", tmp, AUTHrequireAdmin(cntxt));
     800             : 
     801        4187 :         *ret1 = BATdense(user->hseqbase, user->hseqbase, BATcount(user));
     802        4187 :         if (*ret1 == NULL)
     803           0 :                 throw(MAL, "getUsers", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     804        4187 :         if (BATcount(duser)) {
     805         949 :                 bn = BATdiff(*ret1, duser, NULL, NULL, false, false, BUN_NONE);
     806         949 :                 BBPunfix((*ret1)->batCacheid);
     807         949 :                 *ret2 = BATproject(bn, user);
     808         949 :                 *ret1 = bn;
     809             :         } else {
     810        3238 :                 *ret2 = COLcopy(user, user->ttype, false, TRANSIENT);
     811             :         }
     812        4187 :         if (*ret1 == NULL || *ret2 == NULL) {
     813           0 :                 if (*ret1)
     814           0 :                         BBPunfix((*ret1)->batCacheid);
     815           0 :                 if (*ret2)
     816           0 :                         BBPunfix((*ret2)->batCacheid);
     817           0 :                 throw(MAL, "getUsers", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     818             :         }
     819             :         return(NULL);
     820             : }
     821             : 
     822             : /**
     823             :  * Returns the password hash as used by the backend for the given
     824             :  * username.  Throws an exception if called by a non-superuser.
     825             :  */
     826             : str
     827          70 : AUTHgetPasswordHash(str *ret, Client cntxt, const char *username)
     828             : {
     829             :         BUN p;
     830             :         BATiter i;
     831             :         str tmp;
     832          70 :         str passwd = NULL;
     833             : 
     834          70 :         rethrow("getPasswordHash", tmp, AUTHrequireAdmin(cntxt));
     835             : 
     836          70 :         if (strNil(username))
     837           0 :                 throw(ILLARG, "getPasswordHash", "username should not be nil");
     838             : 
     839          70 :         p = AUTHfindUser(username);
     840          70 :         if (p == BUN_NONE)
     841           0 :                 throw(MAL, "getPasswordHash", "user '%s' does not exist", username);
     842          70 :         i = bat_iterator(pass);
     843          70 :         tmp = BUNtvar(i, p);
     844          70 :         bat_iterator_end(&i);
     845          70 :         assert (tmp != NULL);
     846             :         /* decypher the password */
     847          70 :         rethrow("changePassword", tmp, AUTHdecypherValue(&passwd, tmp));
     848             : 
     849          70 :         *ret = passwd;
     850          70 :         return(NULL);
     851             : }
     852             : 
     853             : 
     854             : /*=== the vault ===*/
     855             : 
     856             : 
     857             : /**
     858             :  * Unlocks the vault with the given password.  Since the password is
     859             :  * just the decypher key, it is not possible to directly check whether
     860             :  * the given password is correct.  If incorrect, however, all decypher
     861             :  * operations will probably fail or return an incorrect decyphered
     862             :  * value.
     863             :  */
     864             : str
     865         266 : AUTHunlockVault(const char *password)
     866             : {
     867         266 :         if (strNil(password))
     868           0 :                 throw(ILLARG, "unlockVault", "password should not be nil");
     869             : 
     870             :         /* even though I think this function should be called only once, it
     871             :          * is not of real extra efforts to avoid a mem-leak if it is used
     872             :          * multiple times */
     873         266 :         if (vaultKey != NULL)
     874           0 :                 GDKfree(vaultKey);
     875             : 
     876         266 :         if ((vaultKey = GDKstrdup(password)) == NULL)
     877           0 :                 throw(MAL, "unlockVault", SQLSTATE(HY013) MAL_MALLOC_FAIL " vault key");
     878             :         return(MAL_SUCCEED);
     879             : }
     880             : 
     881             : /**
     882             :  * Decyphers a given value, using the vaultKey.  The returned value
     883             :  * might be incorrect if the vaultKey is incorrect or unset.  If the
     884             :  * cypher algorithm fails or detects an invalid password, it might throw
     885             :  * an exception.  The ret string is GDKmalloced, and should be GDKfreed
     886             :  * by the caller.
     887             :  */
     888             : static str
     889        5573 : AUTHdecypherValue(str *ret, const char *value)
     890             : {
     891             :         /* Cyphering and decyphering can be done using many algorithms.
     892             :          * Future requirements might want a stronger cypher than the XOR
     893             :          * cypher chosen here.  It is left up to the implementor how to do
     894             :          * that once those algoritms become available.  It could be
     895             :          * #ifdef-ed or on if-basis depending on whether the cypher
     896             :          * algorithm is a compile, or runtime option.  When necessary, this
     897             :          * function could be extended with an extra argument that indicates
     898             :          * the cypher algorithm.
     899             :          */
     900             : 
     901             :         /* this is the XOR decypher implementation */
     902             :         str r, w;
     903             :         const char *s = value;
     904             :         char t = '\0';
     905             :         int escaped = 0;
     906             :         /* we default to some garbage key, just to make password unreadable
     907             :          * (a space would only uppercase the password) */
     908             :         size_t keylen = 0;
     909             : 
     910        5573 :         if (vaultKey == NULL)
     911           0 :                 throw(MAL, "decypherValue", "The vault is still locked!");
     912        5573 :         w = r = GDKmalloc(sizeof(char) * (strlen(value) + 1));
     913        5573 :         if( r == NULL)
     914           0 :                 throw(MAL, "decypherValue", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     915             : 
     916        5573 :         keylen = strlen(vaultKey);
     917             : 
     918             :         /* XOR all characters.  If we encounter a 'one' char after the XOR
     919             :          * operation, it is an escape, so replace it with the next char. */
     920      758093 :         for (; (t = *s) != '\0'; s++) {
     921      752520 :                 if (t == '\1' && escaped == 0) {
     922             :                         escaped = 1;
     923       39176 :                         continue;
     924      713344 :                 } else if (escaped != 0) {
     925       39176 :                         t -= 1;
     926             :                         escaped = 0;
     927             :                 }
     928      713344 :                 *w = t ^ vaultKey[(w - r) % keylen];
     929      713344 :                 w++;
     930             :         }
     931        5573 :         *w = '\0';
     932             : 
     933        5573 :         *ret = r;
     934        5573 :         return(MAL_SUCCEED);
     935             : }
     936             : 
     937             : /**
     938             :  * Cyphers the given string using the vaultKey.  If the cypher algorithm
     939             :  * fails or detects an invalid password, it might throw an exception.
     940             :  * The ret string is GDKmalloced, and should be GDKfreed by the caller.
     941             :  */
     942             : static str
     943         509 : AUTHcypherValue(str *ret, const char *value)
     944             : {
     945             :         /* this is the XOR cypher implementation */
     946             :         str r, w;
     947             :         const char *s = value;
     948             :         /* we default to some garbage key, just to make password unreadable
     949             :          * (a space would only uppercase the password) */
     950             :         size_t keylen = 0;
     951             : 
     952         509 :         if (vaultKey == NULL)
     953           0 :                 throw(MAL, "cypherValue", "The vault is still locked!");
     954         509 :         w = r = GDKmalloc(sizeof(char) * (strlen(value) * 2 + 1));
     955         509 :         if( r == NULL)
     956           0 :                 throw(MAL, "cypherValue", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     957             : 
     958         509 :         keylen = strlen(vaultKey);
     959             : 
     960             :         /* XOR all characters.  If we encounter a 'zero' char after the XOR
     961             :          * operation, escape it with an 'one' char. */
     962       65661 :         for (; *s != '\0'; s++) {
     963       65152 :                 *w = *s ^ vaultKey[(s - value) % keylen];
     964       65152 :                 if (*w == '\0') {
     965        1984 :                         *w++ = '\1';
     966        1984 :                         *w = '\1';
     967       63168 :                 } else if (*w == '\1') {
     968         290 :                         *w++ = '\1';
     969         290 :                         *w = '\2';
     970             :                 }
     971       65152 :                 w++;
     972             :         }
     973         509 :         *w = '\0';
     974             : 
     975         509 :         *ret = r;
     976         509 :         return(MAL_SUCCEED);
     977             : }
     978             : 
     979             : /**
     980             :  * Checks if the given string is a (hex represented) hash for the
     981             :  * current backend.  This check allows to at least forbid storing
     982             :  * trivial plain text passwords by a simple check.
     983             :  */
     984             : #define concat(x,y)     x##y
     985             : #define digestlength(h) concat(h, _DIGEST_LENGTH)
     986             : static str
     987         526 : AUTHverifyPassword(const char *passwd)
     988             : {
     989             :         const char *p = passwd;
     990         526 :         size_t len = strlen(p);
     991             : 
     992         526 :         if (len != digestlength(MONETDB5_PASSWDHASH_TOKEN) * 2) {
     993           0 :                 throw(MAL, "verifyPassword",
     994             :                           "password is not %d chars long, is it a hex "
     995             :                           "representation of a %s password hash?",
     996             :                           digestlength(MONETDB5_PASSWDHASH_TOKEN), MONETDB5_PASSWDHASH);
     997             :         }
     998             :         len++; // required in case all the checks above are false
     999       67854 :         while (*p != '\0') {
    1000       67328 :                 if (!((*p >= 'a' && *p <= 'z') || isdigit((unsigned char) *p)))
    1001           0 :                         throw(MAL, "verifyPassword",
    1002             :                                         "password does contain invalid characters, is it a"
    1003             :                                         "lowercase hex representation of a hash?");
    1004       67328 :                 p++;
    1005             :         }
    1006             : 
    1007             :         return(MAL_SUCCEED);
    1008             : }
    1009             : 
    1010             : static BUN
    1011         222 : lookupRemoteTableKey(const char *key)
    1012             : {
    1013         222 :         BATiter cni = bat_iterator(rt_key);
    1014             :         BUN p = BUN_NONE;
    1015             : 
    1016         222 :         assert(rt_key);
    1017         222 :         assert(rt_deleted);
    1018             : 
    1019         222 :         if (BAThash(rt_key) == GDK_SUCCEED) {
    1020         222 :                 MT_rwlock_rdlock(&cni.b->thashlock);
    1021         295 :                 HASHloop_str(cni, cni.b->thash, p, key) {
    1022         142 :                         oid pos = p;
    1023         142 :                         if (BUNfnd(rt_deleted, &pos) == BUN_NONE) {
    1024         141 :                                 MT_rwlock_rdunlock(&cni.b->thashlock);
    1025         141 :                                 bat_iterator_end(&cni);
    1026         141 :                                 return p;
    1027             :                         }
    1028             :                 }
    1029          81 :                 MT_rwlock_rdunlock(&cni.b->thashlock);
    1030             :         }
    1031          81 :         bat_iterator_end(&cni);
    1032             : 
    1033          81 :         return BUN_NONE;
    1034             : 
    1035             : }
    1036             : 
    1037             : str
    1038         159 : AUTHgetRemoteTableCredentials(const char *local_table, str *uri, str *username, str *password)
    1039             : {
    1040             :         BUN p;
    1041             :         BATiter i;
    1042             :         str tmp;
    1043             :         str pwhash;
    1044             : 
    1045         159 :         if (strNil(local_table)) {
    1046           0 :                 throw(ILLARG, "getRemoteTableCredentials", "local table should not be nil");
    1047             :         }
    1048             : 
    1049         159 :         p = lookupRemoteTableKey(local_table);
    1050         159 :         if (p == BUN_NONE) {
    1051             :                 // No credentials for remote table with name local_table.
    1052             :                 return MAL_SUCCEED;
    1053             :         }
    1054             : 
    1055         130 :         assert(rt_key);
    1056         130 :         assert(rt_uri);
    1057         130 :         assert(rt_remoteuser);
    1058         130 :         assert(rt_hashedpwd);
    1059             : 
    1060             :         assert(p != BUN_NONE);
    1061         130 :         i = bat_iterator(rt_uri);
    1062         130 :         *uri = BUNtvar(i, p);
    1063         130 :         bat_iterator_end(&i);
    1064             : 
    1065         130 :         i = bat_iterator(rt_remoteuser);
    1066         130 :         *username = BUNtvar(i, p);
    1067         130 :         bat_iterator_end(&i);
    1068             : 
    1069         130 :         i = bat_iterator(rt_hashedpwd);
    1070         130 :         tmp = BUNtvar(i, p);
    1071         130 :         bat_iterator_end(&i);
    1072         130 :         rethrow("getRemoteTableCredentials", tmp, AUTHdecypherValue(&pwhash, tmp));
    1073             : 
    1074         130 :         *password = pwhash;
    1075             : 
    1076         130 :         return MAL_SUCCEED;
    1077             : }
    1078             : 
    1079             : str
    1080          52 : AUTHaddRemoteTableCredentials(const char *local_table, const char *local_user, const char *uri, const char *remoteuser, const char *pass, bool pw_encrypted)
    1081             : {
    1082          52 :         char *pwhash = NULL;
    1083             :         bool free_pw = false;
    1084             :         str output = MAL_SUCCEED;
    1085             :         BUN p;
    1086             :         str msg = MAL_SUCCEED;
    1087             : 
    1088          52 :         if (strNil(uri))
    1089           0 :                 throw(ILLARG, "addRemoteTableCredentials", "URI cannot be nil");
    1090          52 :         if (strNil(local_user))
    1091           0 :                 throw(ILLARG, "addRemoteTableCredentials", "local user name cannot be nil");
    1092             : 
    1093          52 :         assert(rt_key);
    1094          52 :         assert(rt_uri);
    1095          52 :         assert(rt_remoteuser);
    1096          52 :         assert(rt_hashedpwd);
    1097             : 
    1098          52 :         p = lookupRemoteTableKey(local_table);
    1099             : 
    1100          52 :         if (p != BUN_NONE) {
    1101             :         /* An entry with the given key is already in the vault (note: the
    1102             :          * key is the string "schema.table_name", which is unique in the
    1103             :          * SQL catalog, in the sense that no two tables can have the same
    1104             :          * name in the same schema). This can only mean that the entry is
    1105             :          * invalid (i.e. it does not correspond to a valid SQL table). To
    1106             :          * see this consider the following:
    1107             :          *
    1108             :          * 1. The function `AUTHaddRemoteTableCredentials` is only called
    1109             :          * from `rel_create_table` (also from the upgrade code, but this
    1110             :          * is irrelevant for our discussion since, in this case no remote
    1111             :          * table will have any credentials), i.e. when we are creating a
    1112             :          * (remote) table.
    1113             :          *
    1114             :          * 2. If a remote table with name "schema.table_name" has been
    1115             :          * defined previously (i.e there is already a SQL catalog entry
    1116             :          * for it) and we try to define it again,
    1117             :          * `AUTHaddRemoteTableCredentials` will *not* be called because we
    1118             :          * are trying to define an already existing table, and the SQL
    1119             :          * layer will not allow us to continue.
    1120             :          *
    1121             :          * 3. The only way to add an entry in the vault is calling this
    1122             :          * function.
    1123             :          *
    1124             :          * Accepting (1)-(3) above means that just before
    1125             :          * `AUTHaddRemoteTableCredentials` gets called with an argument
    1126             :          * "schema.table_name", that table does not exist in the SQL
    1127             :          * catalog.
    1128             :          *
    1129             :          * This means that if we call `AUTHaddRemoteTableCredentials` with
    1130             :          * argument "schema.table_name" and find an entry with that key
    1131             :          * already in the vault, we can safely overwrite it, because the
    1132             :          * table it refers to does not exist in the SQL catalog. We can
    1133             :          * also conclude that the previous entry was added in the vault as
    1134             :          * part of a CREATE REMOTE TABLE call (conclusion follows from (1)
    1135             :          * and (3)), that did not create a corresponding entry in the SQL
    1136             :          * catalog (conclusion follows from (2)). The only (valid) way for
    1137             :          * this to happen is if the CREATE REMOTE TABLE call was inside a
    1138             :          * transaction that did not succeed.
    1139             :          *
    1140             :          * Implementation note: we first delete the entry and then add a
    1141             :          * new entry with the same key.
    1142             :          */
    1143           0 :                 if((output = AUTHdeleteRemoteTableCredentials(local_table)) != MAL_SUCCEED)
    1144             :                         return output;
    1145             :         }
    1146             : 
    1147          52 :         if (pass == NULL) {
    1148             :                 /* NOTE: Is having the client == NULL safe? */
    1149          41 :                 if((output = AUTHgetPasswordHash(&pwhash, NULL, local_user)) != MAL_SUCCEED)
    1150             :                         return output;
    1151             :         }
    1152             :         else {
    1153             :                 free_pw = true;
    1154          11 :                 if (pw_encrypted) {
    1155           1 :                         if((pwhash = strdup(pass)) == NULL)
    1156           0 :                                 throw(MAL, "addRemoteTableCredentials", SQLSTATE(HY013) MAL_MALLOC_FAIL);
    1157             :                 }
    1158             :                 else {
    1159             :                         /* Note: the remote server might have used a different
    1160             :                          * algorithm to hash the pwhash.
    1161             :                          */
    1162          10 :                         if((pwhash = mcrypt_BackendSum(pass, strlen(pass))) == NULL)
    1163           0 :                                 throw(MAL, "addRemoteTableCredentials", SQLSTATE(42000) "Crypt backend hash not found");
    1164             :                 }
    1165             :         }
    1166          52 :         msg = AUTHverifyPassword(pwhash);
    1167          52 :         if( msg != MAL_SUCCEED){
    1168           0 :                 free(pwhash);
    1169           0 :                 return msg;
    1170             :         }
    1171             : 
    1172             :         str cypher;
    1173          52 :         msg = AUTHcypherValue(&cypher, pwhash);
    1174          52 :         if( msg != MAL_SUCCEED){
    1175           0 :                 free(pwhash);
    1176           0 :                 return msg;
    1177             :         }
    1178             : 
    1179             :         /* Add entry */
    1180         104 :         bool table_entry = (BUNappend(rt_key, local_table, true) == GDK_SUCCEED &&
    1181         104 :                                                 BUNappend(rt_uri, uri, true) == GDK_SUCCEED &&
    1182         156 :                                                 BUNappend(rt_remoteuser, remoteuser, true) == GDK_SUCCEED &&
    1183          52 :                                                 BUNappend(rt_hashedpwd, cypher, true) == GDK_SUCCEED);
    1184             : 
    1185          52 :         if (!table_entry) {
    1186           0 :                 if (free_pw) {
    1187           0 :                         free(pwhash);
    1188             :                 }
    1189             :                 else {
    1190           0 :                         GDKfree(pwhash);
    1191             :                 }
    1192           0 :                 GDKfree(cypher);
    1193           0 :                 throw(MAL, "addRemoteTableCredentials", SQLSTATE(HY013) MAL_MALLOC_FAIL);
    1194             :         }
    1195             : 
    1196          52 :         AUTHcommit();
    1197             : 
    1198          52 :         if (free_pw) {
    1199          11 :                 free(pwhash);
    1200             :         }
    1201             :         else {
    1202          41 :                 GDKfree(pwhash);
    1203             :         }
    1204          52 :         GDKfree(cypher);
    1205          52 :         return MAL_SUCCEED;
    1206             : }
    1207             : 
    1208             : str
    1209          11 : AUTHdeleteRemoteTableCredentials(const char *local_table)
    1210             : {
    1211             :         BUN p;
    1212             :         oid id;
    1213             : 
    1214          11 :         assert(rt_key);
    1215          11 :         assert(rt_uri);
    1216          11 :         assert(rt_remoteuser);
    1217          11 :         assert(rt_hashedpwd);
    1218             : 
    1219             :         /* pre-condition check */
    1220          11 :         if (strNil(local_table))
    1221           0 :                 throw(ILLARG, "deleteRemoteTableCredentials", "local table cannot be nil");
    1222             : 
    1223             :         /* ensure that the username exists */
    1224          11 :         p = lookupRemoteTableKey(local_table);
    1225          11 :         if (p == BUN_NONE)
    1226           0 :                 throw(MAL, "deleteRemoteTableCredentials", "no such table: '%s'", local_table);
    1227          11 :         id = p;
    1228             : 
    1229             :         /* now, we got the oid, start removing the related tuples */
    1230          11 :         if (BUNappend(rt_deleted, &id, true) != GDK_SUCCEED)
    1231           0 :                 throw(MAL, "deleteRemoteTableCredentials", SQLSTATE(HY013) MAL_MALLOC_FAIL);
    1232             : 
    1233             :         /* make the stuff persistent */
    1234          11 :         AUTHcommit();
    1235          11 :         return(MAL_SUCCEED);
    1236             : }

Generated by: LCOV version 1.14