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 | |
42 | static str AUTHdecypherValue(str *ret, const char *value); |
43 | static str AUTHcypherValue(str *ret, const char *value); |
44 | static str AUTHverifyPassword(const char *passwd); |
45 | static BUN lookupRemoteTableKey(const char *key); |
46 | |
47 | static BAT *user = NULL; |
48 | static BAT *pass = NULL; |
49 | static BAT *duser = NULL; |
50 | |
51 | /* Remote table bats */ |
52 | static BAT *rt_key = NULL; |
53 | static BAT *rt_uri = NULL; |
54 | static BAT *rt_remoteuser = NULL; |
55 | static BAT *rt_hashedpwd = NULL; |
56 | static BAT *rt_deleted = NULL; |
57 | /* yep, the vault key is just stored in memory */ |
58 | static str vaultKey = NULL; |
59 | |
60 | void 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 | |
73 | static BUN |
74 | AUTHfindUser(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 | */ |
93 | static str |
94 | AUTHrequireAdmin(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 | */ |
119 | static str |
120 | AUTHrequireAdminOrUser(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 | |
138 | static void |
139 | AUTHcommit(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 | */ |
173 | str |
174 | AUTHinitTables(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 | */ |
407 | str |
408 | AUTHcheckCredentials( |
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 | */ |
471 | str |
472 | AUTHaddUser(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 | */ |
518 | str |
519 | AUTHremoveUser(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 | */ |
557 | str |
558 | AUTHchangeUsername(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 | */ |
592 | str |
593 | AUTHchangePassword(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 | */ |
648 | str |
649 | AUTHsetPassword(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 | */ |
703 | str |
704 | AUTHresolveUser(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 | */ |
722 | str |
723 | AUTHgetUsername(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 | */ |
746 | str |
747 | AUTHgetUsers(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 | */ |
779 | str |
780 | AUTHgetPasswordHash(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 | */ |
818 | str |
819 | AUTHunlockVault(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 | */ |
842 | static str |
843 | AUTHdecypherValue(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 | */ |
896 | static str |
897 | AUTHcypherValue(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) |
940 | static str |
941 | AUTHverifyPassword(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 | |
970 | static BUN |
971 | lookupRemoteTableKey(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 | |
991 | str |
992 | AUTHgetRemoteTableCredentials(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 | |
1029 | str |
1030 | AUTHaddRemoteTableCredentials(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 | |
1149 | str |
1150 | AUTHdeleteRemoteTableCredentials(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 | |