Commit | Line | Data |
---|---|---|
0e333c05 CE |
1 | /* |
2 | ** Copyright 2016 Double Precision, Inc. See COPYING for | |
3 | ** distribution information. | |
4 | */ | |
5 | ||
6 | #include <stdio.h> | |
7 | #include <stdlib.h> | |
8 | #include <string.h> | |
9 | #include <unistd.h> | |
10 | #include <ctype.h> | |
11 | #include <sys/types.h> | |
12 | #include <sys/stat.h> | |
13 | #include <time.h> | |
14 | ||
15 | extern "C" { | |
16 | #include "authsqliterc.h" | |
17 | #include "auth.h" | |
18 | #include "courierauthdebug.h" | |
19 | } | |
20 | ||
21 | #include <string> | |
22 | #include <sstream> | |
23 | #include "authsqlite.h" | |
24 | #include "authconfigfile.h" | |
25 | ||
26 | #define err courier_auth_err | |
27 | ||
28 | #define GET(c) ((c) < (n) && columns[(c)] ? columns[(c)]:"") | |
29 | ||
30 | class authsqlite_connection { | |
31 | ||
32 | public: | |
33 | sqlite3 *dbh; | |
34 | ||
35 | class authsqliterc_vars { | |
36 | public: | |
37 | std::string database; | |
38 | std::string select_clause, defdomain; | |
39 | std::string user_table, crypt_field, clear_field, | |
40 | name_field, uid_field, gid_field, login_field, | |
41 | home_field, maildir_field, defaultdelivery_field, | |
42 | quota_field, options_field, | |
43 | where_clause, chpass_clause, enumerate_clause; | |
44 | ||
45 | }; | |
46 | ||
47 | class authsqliterc_file : public courier::auth::config_file, | |
48 | public authsqliterc_vars { | |
49 | ||
50 | authsqlite_connection &conn; | |
51 | ||
52 | public: | |
53 | ||
54 | authsqliterc_file &operator=(const authsqliterc_file &o); | |
55 | authsqliterc_file(authsqlite_connection &connArg); | |
56 | ~authsqliterc_file(); | |
57 | ||
58 | bool do_load(); | |
59 | void do_reload(); | |
60 | }; | |
61 | ||
62 | authsqliterc_file config_file; | |
63 | ||
64 | authsqlite_connection() : dbh(NULL), config_file(*this) | |
65 | { | |
66 | } | |
67 | ||
68 | ~authsqlite_connection() | |
69 | { | |
70 | disconnect(); | |
71 | } | |
72 | ||
73 | void disconnect() | |
74 | { | |
75 | if (dbh) | |
76 | { | |
77 | sqlite3_close(dbh); | |
78 | dbh=NULL; | |
79 | } | |
80 | } | |
81 | ||
82 | std::string escape(const std::string &s); | |
83 | ||
84 | sqlite3 *do_connect(); | |
85 | ||
86 | static authsqlite_connection *singleton; | |
87 | ||
88 | static authsqlite_connection *connect(); | |
89 | ||
90 | bool getuserinfo(const char *username, | |
91 | const char *service, | |
92 | authsqliteuserinfo &ui); | |
93 | ||
94 | void enumerate( void(*cb_func)(const char *name, | |
95 | uid_t uid, | |
96 | gid_t gid, | |
97 | const char *homedir, | |
98 | const char *maildir, | |
99 | const char *options, | |
100 | void *void_arg), | |
101 | void *void_arg); | |
102 | ||
103 | bool setpass(const char *user, const char *pass, | |
104 | const char *oldpass); | |
105 | }; | |
106 | ||
107 | ||
108 | authsqlite_connection::authsqliterc_file | |
109 | &authsqlite_connection::authsqliterc_file | |
110 | ::operator=(const authsqlite_connection::authsqliterc_file &o) | |
111 | { | |
112 | courier::auth::config_file::operator=(o); | |
113 | authsqliterc_vars::operator=(o); | |
114 | return *this; | |
115 | } | |
116 | ||
117 | authsqlite_connection::authsqliterc_file | |
118 | ::authsqliterc_file(authsqlite_connection &connArg) | |
119 | : courier::auth::config_file(AUTHSQLITERC), | |
120 | conn(connArg) | |
121 | { | |
122 | } | |
123 | ||
124 | authsqlite_connection::authsqliterc_file::~authsqliterc_file() | |
125 | { | |
126 | } | |
127 | ||
128 | bool authsqlite_connection::authsqliterc_file::do_load() | |
129 | { | |
130 | if (!config("SQLITE_DATABASE", database, true)) | |
131 | return false; | |
132 | ||
133 | defdomain=config("DEFAULT_DOMAIN"); | |
134 | ||
135 | select_clause=config("SQLITE_SELECT_CLAUSE"); | |
136 | chpass_clause=config("SQLITE_CHPASS_CLAUSE"); | |
137 | enumerate_clause=config("SQLITE_ENUMERATE_CLAUSE"); | |
138 | ||
139 | if (select_clause.empty() || chpass_clause.empty() || | |
140 | enumerate_clause.empty()) | |
141 | { | |
142 | if (!config("SQLITE_USER_TABLE", user_table, | |
143 | true)) | |
144 | return false; | |
145 | ||
146 | crypt_field=config("SQLITE_CRYPT_PWFIELD", "''"); | |
147 | ||
148 | clear_field=config("SQLITE_CLEAR_PWFIELD", "''"); | |
149 | ||
150 | if (crypt_field + clear_field == "''''") | |
151 | { | |
152 | err("SQLITE_CLEAR_PWFIELD and SQLITE_CLEAR_FIELD not set in " AUTHSQLITERC); | |
153 | return false; | |
154 | } | |
155 | ||
156 | name_field=config("SQLITE_NAME_FIELD", "''"); | |
157 | ||
158 | uid_field=config("SQLITE_UID_FIELD", "uid"); | |
159 | gid_field=config("SQLITE_GID_FIELD", "gid"); | |
160 | login_field=config("SQLITE_LOGIN_FIELD", "id"); | |
161 | home_field=config("SQLITE_HOME_FIELD", "home"); | |
162 | ||
163 | maildir_field=config("SQLITE_MAILDIR_FIELD", "''"); | |
164 | defaultdelivery_field=config("SQLITE_DEFAULTDELIVERY", "''"); | |
165 | ||
166 | quota_field=config("SQLITE_QUOTA_FIELD", "''"); | |
167 | options_field=config("SQLITE_AUXOPTIONS_FIELD", "''"); | |
168 | where_clause=config("SQLITE_WHERE_CLAUSE", "1=1"); | |
169 | } | |
170 | ||
171 | return true; | |
172 | } | |
173 | ||
174 | void authsqlite_connection::authsqliterc_file::do_reload() | |
175 | { | |
176 | authsqliterc_file new_file(conn); | |
177 | ||
178 | if (new_file.load(true)) | |
179 | { | |
180 | *this=new_file; | |
181 | DPRINTF("authsqlite: reloaded %s", filename); | |
182 | ||
183 | // Disconnect from the server, new login | |
184 | // parameters, perhaps. | |
185 | ||
186 | conn.disconnect(); | |
187 | } | |
188 | } | |
189 | ||
190 | authsqlite_connection *authsqlite_connection::singleton=NULL; | |
191 | ||
192 | authsqlite_connection *authsqlite_connection::connect() | |
193 | { | |
194 | if (authsqlite_connection::singleton) | |
195 | { | |
196 | authsqlite_connection::singleton->config_file.load(true); | |
197 | return authsqlite_connection::singleton; | |
198 | } | |
199 | ||
200 | authsqlite_connection *new_conn=new authsqlite_connection; | |
201 | ||
202 | if (new_conn->config_file.load()) | |
203 | { | |
204 | authsqlite_connection::singleton=new_conn; | |
205 | return new_conn; | |
206 | } | |
207 | ||
208 | delete new_conn; | |
209 | return NULL; | |
210 | } | |
211 | ||
212 | sqlite3 *authsqlite_connection::do_connect() | |
213 | { | |
214 | const char *p; | |
215 | ||
216 | if (dbh) | |
217 | return dbh; | |
218 | ||
219 | p=config_file.database.c_str(); | |
220 | ||
221 | if (sqlite3_open_v2(p, &dbh, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK) | |
222 | { | |
223 | if (dbh) | |
224 | { | |
225 | err("sqllite3_open(%s): %s", p, sqlite3_errmsg(dbh)); | |
226 | sqlite3_close(dbh); | |
227 | dbh=0; | |
228 | } | |
229 | return 0; | |
230 | } | |
231 | ||
232 | return dbh; | |
233 | } | |
234 | ||
235 | static void do_auth_sqlite_cleanup() | |
236 | { | |
237 | if (authsqlite_connection::singleton) | |
238 | { | |
239 | delete authsqlite_connection::singleton; | |
240 | authsqlite_connection::singleton=NULL; | |
241 | } | |
242 | } | |
243 | ||
244 | std::string authsqlite_connection::escape(const std::string &s) | |
245 | { | |
246 | char *q=sqlite3_mprintf("%q", s.c_str()); | |
247 | ||
248 | std::string r(q); | |
249 | sqlite3_free(q); | |
250 | return r; | |
251 | } | |
252 | ||
253 | ||
254 | class select_callback_info { | |
255 | public: | |
256 | authsqliteuserinfo &ui; | |
257 | int ui_cnt; | |
258 | ||
259 | select_callback_info(authsqliteuserinfo &uiArg) | |
260 | : ui(uiArg), ui_cnt(0) | |
261 | { | |
262 | } | |
263 | }; | |
264 | ||
265 | static int select_callback(void *dummy, int n, char **columns, char **names) | |
266 | { | |
267 | select_callback_info *cb= | |
268 | reinterpret_cast<select_callback_info *>(dummy); | |
269 | ||
270 | if (cb->ui_cnt++) | |
271 | { | |
272 | err("Multiple rows returned"); | |
273 | return -1; | |
274 | } | |
275 | ||
276 | if (n < 6) | |
277 | { | |
278 | err("Query came back with fewer than 6 columns"); | |
279 | return -1; | |
280 | } | |
281 | ||
282 | cb->ui.username=GET(0); | |
283 | cb->ui.cryptpw=GET(1); | |
284 | cb->ui.clearpw=GET(2); | |
285 | cb->ui.home=GET(5); | |
286 | cb->ui.maildir=GET(6); | |
287 | cb->ui.quota=GET(7); | |
288 | cb->ui.fullname=GET(8); | |
289 | cb->ui.options=GET(9); | |
290 | ||
291 | std::istringstream(GET(3)) >> cb->ui.uid; | |
292 | std::istringstream(GET(4)) >> cb->ui.gid; | |
293 | return 0; | |
294 | } | |
295 | ||
296 | bool authsqlite_connection::getuserinfo(const char *username, | |
297 | const char *service, | |
298 | authsqliteuserinfo &ui) | |
299 | { | |
300 | char *errmsg; | |
301 | ||
302 | if (!do_connect()) | |
303 | return false; | |
304 | ||
305 | std::string querybuf; | |
306 | ||
307 | if (config_file.select_clause.empty()) | |
308 | { | |
309 | std::ostringstream o; | |
310 | ||
311 | o << "SELECT " | |
312 | << config_file.login_field << ", " | |
313 | << config_file.crypt_field << ", " | |
314 | << config_file.clear_field << ", " | |
315 | << config_file.uid_field << ", " | |
316 | << config_file.gid_field << ", " | |
317 | << config_file.home_field << ", " | |
318 | << (strcmp(service, "courier") == 0 ? | |
319 | config_file.defaultdelivery_field: | |
320 | config_file.maildir_field) << ", " | |
321 | << config_file.quota_field << ", " | |
322 | << config_file.name_field << ", " | |
323 | << config_file.options_field | |
324 | << " FROM " << config_file.user_table | |
325 | << " WHERE " << config_file.login_field | |
326 | << " = '" << escape(username); | |
327 | ||
328 | if (strchr(username, '@') == NULL && | |
329 | !config_file.defdomain.empty()) | |
330 | { | |
331 | o << "@" << config_file.defdomain; | |
332 | } | |
333 | ||
334 | o << "' AND (" << config_file.where_clause << ")"; | |
335 | ||
336 | querybuf=o.str(); | |
337 | } | |
338 | else | |
339 | { | |
340 | std::map<std::string, std::string> parameters; | |
341 | ||
342 | parameters["service"]=service; | |
343 | ||
344 | querybuf=config_file | |
345 | .parse_custom_query(config_file.select_clause, | |
346 | escape(username), | |
347 | config_file.defdomain, | |
348 | parameters); | |
349 | } | |
350 | ||
351 | DPRINTF("SQL query: %s", querybuf.c_str()); | |
352 | errmsg=0; | |
353 | ||
354 | select_callback_info info(ui); | |
355 | if (sqlite3_exec(dbh, querybuf.c_str(), select_callback, | |
356 | reinterpret_cast<void *>(&info), &errmsg) != SQLITE_OK) | |
357 | { | |
358 | if (errmsg) | |
359 | { | |
360 | err(errmsg); | |
361 | sqlite3_free(errmsg); | |
362 | } | |
363 | return false; | |
364 | } | |
365 | ||
366 | if (errmsg) | |
367 | { | |
368 | err(errmsg); | |
369 | sqlite3_free(errmsg); | |
370 | } | |
371 | ||
372 | return true; | |
373 | } | |
374 | ||
375 | static int dummy_callback(void *dummy, int n, char **columns, char **names) | |
376 | { | |
377 | return 0; | |
378 | } | |
379 | ||
380 | bool authsqlite_connection::setpass(const char *user, const char *pass, | |
381 | const char *oldpass) | |
382 | { | |
383 | char *errmsg; | |
384 | bool rc=true; | |
385 | ||
386 | if (!do_connect()) return false; | |
387 | ||
388 | std::string newpass_crypt; | |
389 | ||
390 | { | |
391 | char *p; | |
392 | ||
393 | if (!(p=authcryptpasswd(pass, oldpass))) | |
394 | return false; | |
395 | ||
396 | newpass_crypt=p; | |
397 | free(p); | |
398 | } | |
399 | ||
400 | std::string clear_escaped=escape(pass); | |
401 | ||
402 | std::string crypt_escaped=escape(newpass_crypt); | |
403 | ||
404 | std::string sql_buf; | |
405 | ||
406 | if (config_file.chpass_clause.size() == 0) | |
407 | { | |
408 | std::string username_escaped=escape(user); | |
409 | ||
410 | bool has_domain=strchr(user, '@') != NULL; | |
411 | ||
412 | std::ostringstream o; | |
413 | ||
414 | o << "UPDATE " << config_file.user_table << " SET "; | |
415 | ||
416 | if (config_file.clear_field != "''") | |
417 | o << config_file.clear_field << "='" | |
418 | << clear_escaped << "'"; | |
419 | ||
420 | if (config_file.crypt_field != "''") | |
421 | { | |
422 | if (config_file.clear_field != "''") o << ", "; | |
423 | o << config_file.crypt_field << "='" << crypt_escaped << "'"; | |
424 | } | |
425 | ||
426 | o << " WHERE " << config_file.login_field << "='" | |
427 | << username_escaped; | |
428 | ||
429 | if (!has_domain && config_file.defdomain.size()) | |
430 | o << "@" << config_file.defdomain; | |
431 | o << "'"; | |
432 | ||
433 | sql_buf=o.str(); | |
434 | } | |
435 | else | |
436 | { | |
437 | std::map<std::string, std::string> parameters; | |
438 | ||
439 | parameters["newpass"]=clear_escaped; | |
440 | parameters["newpass_crypt"]=crypt_escaped; | |
441 | ||
442 | sql_buf=config_file | |
443 | .parse_custom_query(config_file.chpass_clause, | |
444 | user, | |
445 | config_file.defdomain, | |
446 | parameters); | |
447 | } | |
448 | ||
449 | if (courier_authdebug_login_level >= 2) | |
450 | { | |
451 | DPRINTF("setpass SQL: %s", sql_buf.c_str()); | |
452 | } | |
453 | ||
454 | errmsg=NULL; | |
455 | ||
456 | if (sqlite3_exec(dbh, sql_buf.c_str(), dummy_callback, | |
457 | NULL, &errmsg) != SQLITE_OK) | |
458 | { | |
459 | rc=false; | |
460 | } | |
461 | else | |
462 | { | |
463 | if (sqlite3_changes(dbh) > 0) | |
464 | { | |
465 | DPRINTF("authsqllite: password updated"); | |
466 | } | |
467 | else | |
468 | { | |
469 | rc=false; | |
470 | DPRINTF("authsqllite: password not updated"); | |
471 | } | |
472 | } | |
473 | ||
474 | if (errmsg) | |
475 | { | |
476 | err(errmsg); | |
477 | sqlite3_free(errmsg); | |
478 | } | |
479 | ||
480 | return (rc); | |
481 | } | |
482 | ||
483 | struct enumerate_user_cb { | |
484 | ||
485 | void (*cb_func)(const char *name, | |
486 | uid_t uid, | |
487 | gid_t gid, | |
488 | const char *homedir, | |
489 | const char *maildir, | |
490 | const char *options, | |
491 | void *void_arg); | |
492 | void *void_arg; | |
493 | }; | |
494 | ||
495 | static int enumerate_callback(void *closure, | |
496 | int n, char **columns, char **names) | |
497 | { | |
498 | struct enumerate_user_cb *cb=(struct enumerate_user_cb *)closure; | |
499 | ||
500 | const char *username; | |
501 | uid_t uid; | |
502 | gid_t gid; | |
503 | const char *homedir; | |
504 | const char *maildir; | |
505 | const char *options; | |
506 | ||
507 | username=GET(0); | |
508 | uid=atol(GET(1)); /* FIXME use strtol to validate */ | |
509 | gid=atol(GET(2)); | |
510 | homedir=GET(3); | |
511 | maildir=GET(4); | |
512 | options=GET(5); | |
513 | ||
514 | if (maildir && !*maildir) | |
515 | maildir=NULL; | |
516 | ||
517 | if (options && !*options) | |
518 | options=NULL; | |
519 | ||
520 | (*cb->cb_func)(username, uid, gid, homedir, | |
521 | maildir, options, cb->void_arg); | |
522 | return 0; | |
523 | } | |
524 | ||
525 | void authsqlite_connection::enumerate( void(*cb_func)(const char *name, | |
526 | uid_t uid, | |
527 | gid_t gid, | |
528 | const char *homedir, | |
529 | const char *maildir, | |
530 | const char *options, | |
531 | void *void_arg), | |
532 | void *void_arg) | |
533 | { | |
534 | char *errmsg; | |
535 | struct enumerate_user_cb cb; | |
536 | std::string querybuf; | |
537 | ||
538 | cb.cb_func=cb_func; | |
539 | cb.void_arg=void_arg; | |
540 | ||
541 | if (!do_connect()) return; | |
542 | ||
543 | if (config_file.enumerate_clause.empty()) | |
544 | { | |
545 | std::ostringstream o; | |
546 | ||
547 | o << "SELECT " | |
548 | << config_file.login_field << ", " | |
549 | << config_file.uid_field << ", " | |
550 | << config_file.gid_field << ", " | |
551 | << config_file.home_field << ", " | |
552 | << config_file.maildir_field << ", " | |
553 | << config_file.options_field | |
554 | << " FROM " << config_file.user_table | |
555 | << " WHERE " << config_file.where_clause; | |
556 | ||
557 | querybuf=o.str(); | |
558 | } | |
559 | else | |
560 | { | |
561 | std::map<std::string, std::string> parameters; | |
562 | ||
563 | parameters["service"]="enumerate"; | |
564 | querybuf=config_file | |
565 | .parse_custom_query(config_file.enumerate_clause, "*", | |
566 | config_file.defdomain, parameters); | |
567 | } | |
568 | ||
569 | DPRINTF("authsqlite: enumerate query: %s", querybuf.c_str()); | |
570 | ||
571 | errmsg=NULL; | |
572 | ||
573 | sqlite3_exec(dbh, querybuf.c_str(), enumerate_callback, | |
574 | reinterpret_cast<void *>(&cb), &errmsg); | |
575 | ||
576 | if (errmsg) | |
577 | { | |
578 | err(errmsg); | |
579 | sqlite3_free(errmsg); | |
580 | } | |
581 | ||
582 | (*cb.cb_func)(NULL, 0, 0, NULL, NULL, NULL, cb.void_arg); | |
583 | } | |
584 | ||
585 | ///////////////////////////////////////////////////////////////////////////// | |
586 | ||
587 | int auth_sqlite_setpass(const char *user, const char *pass, | |
588 | const char *oldpass) | |
589 | { | |
590 | authsqlite_connection *conn=authsqlite_connection::connect(); | |
591 | ||
592 | if (conn && !conn->setpass(user, pass, oldpass)) | |
593 | return -1; | |
594 | return 0; | |
595 | } | |
596 | ||
597 | void auth_sqlite_enumerate( void(*cb_func)(const char *name, | |
598 | uid_t uid, | |
599 | gid_t gid, | |
600 | const char *homedir, | |
601 | const char *maildir, | |
602 | const char *options, | |
603 | void *void_arg), | |
604 | void *void_arg) | |
605 | { | |
606 | authsqlite_connection *conn=authsqlite_connection::connect(); | |
607 | ||
608 | if (conn) | |
609 | conn->enumerate(cb_func, void_arg); | |
610 | } | |
611 | ||
612 | bool auth_sqlite_getuserinfo(const char *username, | |
613 | const char *service, | |
614 | authsqliteuserinfo &ui) | |
615 | { | |
616 | authsqlite_connection *conn=authsqlite_connection::connect(); | |
617 | ||
618 | if (conn) | |
619 | return conn->getuserinfo(username, service, ui); | |
620 | return false; | |
621 | ||
622 | } | |
623 | ||
624 | void auth_sqlite_cleanup() | |
625 | { | |
626 | do_auth_sqlite_cleanup(); | |
627 | } |