Merge branch 'debian'
[hcoop/debian/courier-authlib.git] / authpgsqllib.cpp
1 /*
2 ** Copyright 2000-2016 Double Precision, Inc. See COPYING for
3 ** distribution information.
4 */
5
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <ctype.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <libpq-fe.h>
15 #include <time.h>
16 #include <errno.h>
17
18 extern "C" {
19 #include "authpgsql.h"
20 #include "authpgsqlrc.h"
21 #include "auth.h"
22 #include "courierauthdebug.h"
23 };
24
25 #include "authconfigfile.h"
26 #include <string>
27
28 #define err courier_auth_err
29
30
31 class authpgsql_userinfo {
32 public:
33 std::string username;
34 std::string fullname;
35 std::string cryptpw;
36 std::string clearpw;
37 std::string home;
38 std::string maildir;
39 std::string quota;
40 std::string options;
41 uid_t uid;
42 gid_t gid;
43 } ;
44
45 class authpgsql_connection {
46
47 time_t last_time;
48 PGconn *pgconn;
49 public:
50
51 class sentquery {
52
53 int status;
54
55 public:
56 sentquery(const authpgsql_connection &conn,
57 const std::string &query)
58 : status(PQsendQuery(conn.pgconn, query.c_str()))
59 {
60 if (status == 0)
61 DPRINTF("PQsendQuery failed: %s",
62 PQerrorMessage(conn.pgconn));
63 }
64
65 operator bool() const
66 {
67 return status != 0;
68 }
69 };
70
71 class result {
72 PGresult *res;
73
74 public:
75 result(const authpgsql_connection &conn,
76 const std::string &query)
77 : res(PQexec(conn.pgconn, query.c_str()))
78 {
79 }
80
81 result(const authpgsql_connection &conn,
82 const sentquery &query)
83 : res(PQgetResult(conn.pgconn))
84 {
85 }
86
87 ~result()
88 {
89 if (res)
90 PQclear(res);
91 }
92
93 result(const result &res);
94 result &operator=(const result &res);
95
96 operator bool() const { return res != 0; }
97
98 bool query_failed() const
99 {
100 return res == 0
101 || PQresultStatus(res) != PGRES_TUPLES_OK;
102 }
103
104 bool command_failed() const
105 {
106 return res == 0
107 || PQresultStatus(res) != PGRES_COMMAND_OK;
108 }
109
110 size_t ntuples() const
111 {
112 return res == 0 ? 0: PQntuples(res);
113 }
114
115 size_t nfields() const
116 {
117 return res == 0 ? 0: PQnfields(res);
118 }
119
120 std::string value(size_t tuple, size_t field) const
121 {
122 std::string v;
123
124 if (tuple < ntuples() && field < nfields())
125 {
126 const char *p=PQgetvalue(res, tuple, field);
127
128 if (p)
129 v=p;
130 }
131 return v;
132 }
133 };
134
135 class authpgsqlrc_vars {
136
137 public:
138
139 std::string character_set;
140 std::string connection;
141 std::string select_clause;
142 std::string chpass_clause;
143 std::string enumerate_clause;
144 std::string defdomain;
145 std::string user_table;
146 std::string clear_field;
147 std::string crypt_field;
148 std::string name_field;
149 std::string uid_field;
150 std::string gid_field;
151 std::string login_field;
152 std::string home_field;
153 std::string maildir_field;
154 std::string defaultdelivery_field;
155 std::string quota_field;
156 std::string options_field;
157 std::string where_clause;
158 };
159
160 class authpgsqlrc_file : public courier::auth::config_file,
161 public authpgsqlrc_vars {
162
163 authpgsql_connection &conn;
164
165 public:
166
167 authpgsqlrc_file &operator=(const authpgsqlrc_file &o)
168 {
169 courier::auth::config_file::operator=(o);
170 authpgsqlrc_vars::operator=(o);
171 return *this;
172 }
173
174 authpgsqlrc_file(authpgsql_connection &connArg)
175 : courier::auth::config_file(AUTHPGSQLRC),
176 conn(connArg)
177 {
178 }
179
180 bool do_load()
181 {
182 character_set=config("PGSQL_CHARACTER_SET");
183
184 if (!config("PGSQL_CONNECTION", connection, true))
185 return false;
186
187 select_clause=config("PGSQL_SELECT_CLAUSE");
188 chpass_clause=config("PGSQL_CHPASS_CLAUSE");
189 enumerate_clause=config("PGSQL_ENUMERATE_CLAUSE");
190
191 defdomain=config("DEFAULT_DOMAIN");
192
193 if (select_clause.empty() || chpass_clause.empty() ||
194 enumerate_clause.empty())
195 {
196 if (!config("PGSQL_USER_TABLE", user_table, true))
197 return false;
198
199 clear_field=config("PGSQL_CLEAR_PWFIELD");
200 if (clear_field.empty())
201 {
202 if (!config("PGSQL_CRYPT_PWFIELD",
203 crypt_field,
204 true))
205 return false;
206 }
207 else
208 {
209 crypt_field=config("PGSQL_CRYPT_PWFIELD");
210 }
211
212 config("PGSQL_NAME_FIELD", name_field, false,
213 "''");
214
215 if (crypt_field.empty()) crypt_field="''";
216 if (clear_field.empty()) clear_field="''";
217
218 config("PGSQL_UID_FIELD", uid_field, false, "uid");
219 config("PGSQL_GID_FIELD", gid_field, false, "gid");
220 config("PGSQL_LOGIN_FIELD", login_field, false, "id");
221 config("PGSQL_HOME_FIELD", home_field, false, "home");
222 config("PGSQL_MAILDIR_FIELD", maildir_field, false, "''");
223 config("PGSQL_DEFAULTDELIVERY", defaultdelivery_field, false, "''");
224 config("PGSQL_QUOTA_FIELD", quota_field, false, "''");
225 config("PGSQL_AUXOPTIONS_FIELD", options_field, false, "''");
226
227 config("PGSQL_WHERE_CLAUSE", where_clause, false, "1=1");
228 }
229
230 return true;
231 }
232
233 void do_reload()
234 {
235 authpgsqlrc_file new_file(conn);
236
237 if (new_file.load(true))
238 {
239 *this=new_file;
240 DPRINTF("authpgsql: reloaded %s", filename);
241
242 // Disconnect from the server, new login
243 // parameters, perhaps.
244
245 conn.disconnect();
246 }
247 }
248 };
249
250 authpgsqlrc_file config_file;
251
252 authpgsql_connection()
253 : last_time(0), pgconn(0), config_file(*this)
254 {
255 }
256
257 ~authpgsql_connection()
258 {
259 disconnect();
260 }
261
262 void disconnect()
263 {
264 if (pgconn)
265 {
266 PQfinish(pgconn);
267 pgconn=0;
268 }
269 }
270
271 bool do_connect();
272
273 bool getuserinfo(authpgsql_userinfo &uiret,
274 const char *username,
275 const char *service);
276
277 private:
278 bool getuserinfo(authpgsql_userinfo &uiret,
279 const result &res);
280 public:
281 bool setpass(const char *user, const char *pass, const char *oldpass);
282
283 void enumerate( void(*cb_func)(const char *name,
284 uid_t uid,
285 gid_t gid,
286 const char *homedir,
287 const char *maildir,
288 const char *options,
289 void *void_arg),
290 void *void_arg);
291 void enumerate( const sentquery &sent,
292 void(*cb_func)(const char *name,
293 uid_t uid,
294 gid_t gid,
295 const char *homedir,
296 const char *maildir,
297 const char *options,
298 void *void_arg),
299 void *void_arg);
300
301 std::string escape(const std::string &s)
302 {
303 std::string buffer;
304 size_t n=s.size()*2+1;
305
306 buffer.resize(n);
307
308 n=PQescapeStringConn(pgconn, &buffer[0],
309 s.c_str(), s.size(), 0);
310
311 buffer.resize(n);
312
313 return buffer;
314 }
315
316 std::string escape_username(std::string username)
317 {
318 if (username.find('@') == username.npos &&
319 !config_file.defdomain.empty())
320 {
321 username.push_back('@');
322 username += config_file.defdomain;
323 }
324
325 return escape(username);
326 }
327
328 static authpgsql_connection *singleton;
329 };
330
331 bool authpgsql_connection::do_connect()
332 {
333 if (pgconn)
334 {
335 time_t t_check;
336
337 time(&t_check);
338
339 if (t_check < last_time)
340 last_time=t_check; /* System clock changed */
341
342 if (t_check < last_time + 60)
343 return true;
344
345 last_time=t_check;
346
347 if (PQstatus(pgconn) == CONNECTION_OK) return true;
348
349 DPRINTF("authpgsql: PQstatus failed, connection lost");
350 PQfinish(pgconn);
351 pgconn=0;
352 }
353
354 pgconn = PQconnectdb(config_file.connection.c_str());
355
356 if (PQstatus(pgconn) == CONNECTION_BAD)
357 {
358 err("PGSQL_CONNECTION could not be established");
359 err("%s", PQerrorMessage(pgconn));
360 PQfinish(pgconn);
361 pgconn=0;
362 return false;
363 }
364
365 if (!config_file.character_set.empty())
366 {
367 PQsetClientEncoding(pgconn,
368 config_file.character_set.c_str());
369 std::string real_character_set=
370 pg_encoding_to_char(PQclientEncoding(pgconn));
371
372 if (config_file.character_set != real_character_set)
373 {
374 err("Cannot set character set to \"%s\","
375 " using \"%s\"\n",
376 config_file.character_set.c_str(),
377 real_character_set.c_str());
378 }
379 else
380 {
381 DPRINTF("Using character set: %s",
382 config_file.character_set.c_str());
383 }
384 }
385
386 return true;
387 }
388
389 bool authpgsql_connection::getuserinfo(authpgsql_userinfo &uiret,
390 const char *username,
391 const char *service)
392 {
393 std::string querybuf;
394
395 if (!do_connect())
396 return false;
397
398 if (config_file.select_clause.empty())
399 {
400 std::ostringstream o;
401
402 o << "SELECT "
403 << config_file.login_field << ", "
404 << config_file.crypt_field << ", "
405 << config_file.clear_field << ", "
406 << config_file.uid_field << ", "
407 << config_file.gid_field << ", "
408 << config_file.home_field << ", "
409 << (strcmp(service, "courier") == 0 ?
410 config_file.defaultdelivery_field
411 :config_file.maildir_field) << ", "
412 << config_file.quota_field << ", "
413 << config_file.name_field << ", "
414 << config_file.options_field
415 << " FROM " << config_file.user_table
416 << " WHERE " << config_file.login_field
417 << " = '"
418 << escape_username(username)
419 << "' AND (" << config_file.where_clause << ")";
420
421 querybuf=o.str();
422 }
423 else
424 {
425 std::map<std::string, std::string> parameters;
426
427 parameters["service"]=service;
428
429 querybuf=config_file
430 .parse_custom_query(config_file.select_clause,
431 escape(username),
432 config_file.defdomain,
433 parameters);
434 }
435
436 DPRINTF("SQL query: %s", querybuf.c_str());
437
438 result res1(*this, querybuf);
439
440 if (res1)
441 return getuserinfo(uiret, res1);
442
443 disconnect();
444 if (do_connect())
445 {
446 result res2(*this, querybuf);
447
448 if (res2)
449 return getuserinfo(uiret, res2);
450 }
451 return false;
452 }
453
454 bool authpgsql_connection::getuserinfo(authpgsql_userinfo &uiret,
455 const result &res)
456 {
457 if (res.query_failed())
458 return false;
459
460 if (res.ntuples() > 0)
461 {
462 if (res.nfields() < 6)
463 {
464 DPRINTF("incomplete row, only %d fields returned",
465 res.nfields());
466 return false;
467 }
468
469 uiret.username=res.value(0, 0);
470 uiret.cryptpw=res.value(0, 1);
471 uiret.clearpw=res.value(0, 2);
472
473 {
474 std::string v=res.value(0, 3);
475
476 std::istringstream i(v);
477
478 i >> uiret.uid;
479
480 if (i.fail() || !i.eof())
481 {
482 DPRINTF("invalid value for uid: '%s'",
483 v.c_str());
484 return false;
485 }
486 }
487
488 {
489 std::string v=res.value(0, 4);
490
491 std::istringstream i(v);
492
493 i >> uiret.gid;
494
495 if (i.fail() || !i.eof())
496 {
497 DPRINTF("invalid value for gid: '%s'",
498 v.c_str());
499 return false;
500 }
501 }
502
503
504 uiret.home=res.value(0, 5);
505 uiret.maildir=res.value(0, 6);
506 uiret.quota=res.value(0, 7);
507 uiret.fullname=res.value(0, 8);
508 uiret.options=res.value(0, 9);
509 }
510 else
511 {
512 DPRINTF("zero rows returned");
513 return true;
514 }
515
516 return true;
517 }
518
519 bool authpgsql_connection::setpass(const char *user, const char *pass,
520 const char *oldpass)
521 {
522 if (!do_connect())
523 return false;
524
525 std::string newpass_crypt;
526
527 {
528 char *p;
529
530 if (!(p=authcryptpasswd(pass, oldpass)))
531 return false;
532
533 newpass_crypt=p;
534 free(p);
535 }
536
537 std::string clear_escaped=escape(pass);
538 std::string crypt_escaped=escape(newpass_crypt);
539
540 std::string sql_buf;
541
542 if (config_file.chpass_clause.empty())
543 {
544 std::ostringstream o;
545
546 o << "UPDATE " << config_file.user_table
547 << " SET ";
548 if (config_file.clear_field != "''")
549 {
550 o << config_file.clear_field << "='"
551 << clear_escaped
552 << "'";
553
554 if (config_file.crypt_field != "''")
555 o << ", ";
556 }
557
558 if (config_file.crypt_field != "''")
559 {
560 o << config_file.crypt_field << "='"
561 << crypt_escaped
562 << "'";
563 }
564
565 o << " WHERE "
566 << config_file.login_field << "='"
567 << escape_username(user)
568 << "' AND (" << config_file.where_clause << ")";
569 sql_buf=o.str();
570 }
571 else
572 {
573 std::map<std::string, std::string> parameters;
574
575 parameters["newpass"]=clear_escaped;
576 parameters["newpass_crypt"]=crypt_escaped;
577
578 sql_buf=config_file
579 .parse_custom_query(config_file.chpass_clause,
580 user,
581 config_file.defdomain,
582 parameters);
583 }
584
585 if (courier_authdebug_login_level >= 2)
586 {
587 DPRINTF("setpass SQL: %s", sql_buf.c_str());
588 }
589
590 result res(*this, sql_buf);
591
592 if (res.command_failed())
593 {
594 DPRINTF("setpass SQL failed");
595 disconnect();
596 return false;
597 }
598 return (true);
599 }
600
601 void authpgsql_connection::enumerate( void(*cb_func)(const char *name,
602 uid_t uid,
603 gid_t gid,
604 const char *homedir,
605 const char *maildir,
606 const char *options,
607 void *void_arg),
608 void *void_arg)
609 {
610 if (!do_connect())
611 {
612 (*cb_func)(NULL, 0, 0, NULL, NULL, NULL, void_arg);
613 return;
614 }
615
616 std::string sql_buf;
617
618 if (config_file.enumerate_clause.empty())
619 {
620 std::ostringstream o;
621
622 o << "SELECT "
623 << config_file.login_field << ", "
624 << config_file.uid_field << ", "
625 << config_file.gid_field << ", "
626 << config_file.home_field << ", "
627 << config_file.maildir_field << ", "
628 << config_file.options_field
629 << " FROM "
630 << config_file.user_table << " WHERE "
631 << config_file.where_clause;
632
633 sql_buf=o.str();
634 }
635 else
636 {
637 std::map<std::string, std::string> parameters;
638
639 parameters["service"]="enumerate";
640 sql_buf=config_file
641 .parse_custom_query(config_file.enumerate_clause, "*",
642 config_file.defdomain, parameters);
643 }
644
645 DPRINTF("authpgsql: enumerate query: %s", sql_buf.c_str());
646
647 sentquery query1(*this, sql_buf);
648
649 if (query1)
650 {
651 enumerate(query1, cb_func, void_arg);
652 return;
653 }
654
655 disconnect();
656
657 if (!do_connect())
658 return;
659
660 sentquery query2(*this, sql_buf);
661 if (query2)
662 {
663 enumerate(query2, cb_func, void_arg);
664 return;
665 }
666 }
667
668 void authpgsql_connection::enumerate( const sentquery &sent,
669 void(*cb_func)(const char *name,
670 uid_t uid,
671 gid_t gid,
672 const char *homedir,
673 const char *maildir,
674 const char *options,
675 void *void_arg),
676 void *void_arg)
677 {
678 while (1)
679 {
680 result res(*this, sent);
681
682 if (!res)
683 break;
684
685 if (res.query_failed())
686 continue;
687
688 size_t n=res.ntuples();
689
690 for (size_t i=0; i<n; ++i)
691 {
692 std::string username=res.value(i, 0);
693 std::string uid_s=res.value(i, 1);
694 std::string gid_s=res.value(i, 2);
695 std::string homedir=res.value(i, 3);
696 std::string maildir=res.value(i, 4);
697 std::string options=res.value(i, 5);
698
699 uid_t uid=0;
700 gid_t gid=0;
701
702 std::istringstream(uid_s) >> uid;
703 std::istringstream(gid_s) >> gid;
704
705 if (username.empty() || homedir.empty())
706 continue;
707
708 (*cb_func)(username.c_str(), uid, gid,
709 homedir.c_str(),
710 (maildir.empty() ? 0:maildir.c_str()),
711 (options.empty() ? 0:options.c_str()),
712 void_arg);
713 }
714 }
715 (*cb_func)(NULL, 0, 0, NULL, NULL, NULL, void_arg);
716 }
717
718
719 static authpgsql_connection *get_conn()
720 {
721 if (authpgsql_connection::singleton)
722 {
723 authpgsql_connection::singleton->config_file.load(true);
724 return authpgsql_connection::singleton;
725 }
726
727 authpgsql_connection *new_conn=new authpgsql_connection;
728
729 if (new_conn->config_file.load())
730 {
731 authpgsql_connection::singleton=new_conn;
732 return new_conn;
733 }
734
735 delete new_conn;
736 return NULL;
737 }
738
739 authpgsql_connection *authpgsql_connection::singleton=0;
740
741
742 static bool auth_pgsql_getuserinfo(authpgsql_userinfo &uiret,
743 const char *username,
744 const char *service)
745 {
746 authpgsql_connection *conn=get_conn();
747
748 if (!conn)
749 return false;
750
751 return conn->getuserinfo(uiret, username, service);
752 }
753
754 static bool docheckpw(authpgsql_userinfo &authinfo,
755 const char *pass)
756 {
757 if (!authinfo.cryptpw.empty())
758 {
759 if (authcheckpassword(pass,authinfo.cryptpw.c_str()))
760 {
761 errno=EPERM;
762 return false;
763 }
764 }
765 else if (!authinfo.clearpw.empty())
766 {
767 if (authinfo.clearpw != pass)
768 {
769 if (courier_authdebug_login_level >= 2)
770 {
771 DPRINTF("supplied password '%s' does not match clearpasswd '%s'",
772 pass, authinfo.clearpw.empty());
773 }
774 else
775 {
776 DPRINTF("supplied password does not match clearpasswd");
777 }
778 errno=EPERM;
779 return false;
780 }
781 }
782 else
783 {
784 DPRINTF("no password available to compare");
785 errno=EPERM;
786 return false;
787 }
788 return true;
789 }
790
791 static int do_auth_pgsql_login(const char *service, char *authdata,
792 int (*callback_func)(struct authinfo *, void *),
793 void *callback_arg)
794 {
795 char *user, *pass;
796 authpgsql_userinfo uiret;
797 struct authinfo aa;
798
799 if ((user=strtok(authdata, "\n")) == 0 ||
800 (pass=strtok(0, "\n")) == 0)
801 {
802 errno=EPERM;
803 return (-1);
804 }
805
806 if (!auth_pgsql_getuserinfo(uiret, user, service))
807 {
808 errno=EACCES; /* Fatal error - such as PgSQL being down */
809 return (-1);
810 }
811
812 if (!docheckpw(uiret, pass))
813 return -1;
814
815 memset(&aa, 0, sizeof(aa));
816
817 aa.sysuserid= &uiret.uid;
818 aa.sysgroupid= uiret.gid;
819 aa.homedir=uiret.home.c_str();
820 aa.maildir=uiret.maildir.empty() ? 0:uiret.maildir.c_str();
821 aa.address=uiret.username.c_str();
822 aa.quota=uiret.quota.empty() ? 0:uiret.quota.c_str();
823 aa.fullname=uiret.fullname.c_str();
824 aa.options=uiret.options.c_str();
825 aa.passwd=uiret.cryptpw.empty() ? 0:uiret.cryptpw.c_str();
826 aa.clearpasswd=pass;
827 courier_authdebug_authinfo("DEBUG: authpgsql: ", &aa,
828 aa.clearpasswd, aa.passwd);
829 return (*callback_func)(&aa, callback_arg);
830 }
831
832
833 static int do_auth_pgsql_changepw(const char *service, const char *user,
834 const char *pass,
835 const char *newpass)
836 {
837 authpgsql_connection *conn=get_conn();
838
839 if (!conn)
840 return false;
841
842 authpgsql_userinfo uiret;
843
844 if (conn->getuserinfo(uiret, user, service))
845 {
846 if (!docheckpw(uiret, pass))
847 {
848 errno=EPERM;
849 return -1;
850 }
851
852 if (!conn->setpass(user, newpass, uiret.cryptpw.c_str()))
853 {
854 errno=EPERM;
855 return (-1);
856 }
857 return 0;
858 }
859
860 errno=EPERM;
861 return (-1);
862 }
863
864 extern "C" {
865 #if 0
866 };
867 #endif
868
869 void auth_pgsql_cleanup()
870 {
871 if (authpgsql_connection::singleton)
872 delete authpgsql_connection::singleton;
873 authpgsql_connection::singleton=0;
874 }
875
876 void auth_pgsql_enumerate( void(*cb_func)(const char *name,
877 uid_t uid,
878 gid_t gid,
879 const char *homedir,
880 const char *maildir,
881 const char *options,
882 void *void_arg),
883 void *void_arg)
884 {
885 authpgsql_connection *conn=get_conn();
886
887 if (conn)
888 conn->enumerate(cb_func, void_arg);
889 }
890
891 int auth_pgsql_login(const char *service, char *authdata,
892 int (*callback_func)(struct authinfo *, void *),
893 void *callback_arg)
894 {
895 return do_auth_pgsql_login(service, authdata,
896 callback_func,
897 callback_arg);
898 }
899
900 int auth_pgsql_changepw(const char *service, const char *user,
901 const char *pass,
902 const char *newpass)
903 {
904 return do_auth_pgsql_changepw(service, user, pass, newpass);
905 }
906
907 int auth_pgsql_pre(const char *user, const char *service,
908 int (*callback)(struct authinfo *, void *), void *arg)
909 {
910 struct authinfo aa;
911 authpgsql_userinfo uiret;
912
913 if (!auth_pgsql_getuserinfo(uiret, user, service))
914 return 1;
915
916 if (uiret.home.empty()) /* User not found */
917 return (-1);
918
919 memset(&aa, 0, sizeof(aa));
920
921 aa.sysuserid= &uiret.uid;
922 aa.sysgroupid= uiret.gid;
923 aa.homedir=uiret.home.c_str();
924 aa.maildir=uiret.maildir.empty() ? 0:uiret.maildir.c_str();
925 aa.address=uiret.username.c_str();
926 aa.quota=uiret.quota.empty() ? 0:uiret.quota.c_str();
927 aa.fullname=uiret.fullname.c_str();
928 aa.options=uiret.options.c_str();
929 aa.passwd=uiret.cryptpw.empty() ? 0:uiret.cryptpw.c_str();
930 aa.clearpasswd=uiret.clearpw.empty() ? 0:uiret.clearpw.c_str();
931
932 return ((*callback)(&aa, arg));
933 }
934
935 #if 0
936 {
937 #endif
938 };