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