Import Upstream version 0.69.0
[hcoop/debian/courier-authlib.git] / authsqlitelib.cpp
CommitLineData
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
15extern "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
30class authsqlite_connection {
31
32public:
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
108authsqlite_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
117authsqlite_connection::authsqliterc_file
118::authsqliterc_file(authsqlite_connection &connArg)
119 : courier::auth::config_file(AUTHSQLITERC),
120 conn(connArg)
121{
122}
123
124authsqlite_connection::authsqliterc_file::~authsqliterc_file()
125{
126}
127
128bool 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
174void 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
190authsqlite_connection *authsqlite_connection::singleton=NULL;
191
192authsqlite_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
212sqlite3 *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
235static 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
244std::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
254class select_callback_info {
255public:
256 authsqliteuserinfo &ui;
257 int ui_cnt;
258
259 select_callback_info(authsqliteuserinfo &uiArg)
260 : ui(uiArg), ui_cnt(0)
261 {
262 }
263};
264
265static 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
296bool 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
375static int dummy_callback(void *dummy, int n, char **columns, char **names)
376{
377 return 0;
378}
379
380bool 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
483struct 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
495static 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
525void 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
587int 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
597void 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
612bool 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
624void auth_sqlite_cleanup()
625{
626 do_auth_sqlite_cleanup();
627}