2 ** Copyright 2000-2016 Double Precision, Inc. See COPYING for
3 ** distribution information.
12 #include <sys/types.h>
19 #include "authpgsql.h"
20 #include "authpgsqlrc.h"
22 #include "courierauthdebug.h"
25 #include "authconfigfile.h"
28 #define err courier_auth_err
31 class authpgsql_userinfo
{
45 class authpgsql_connection
{
56 sentquery(const authpgsql_connection
&conn
,
57 const std::string
&query
)
58 : status(PQsendQuery(conn
.pgconn
, query
.c_str()))
61 DPRINTF("PQsendQuery failed: %s",
62 PQerrorMessage(conn
.pgconn
));
75 result(const authpgsql_connection
&conn
,
76 const std::string
&query
)
77 : res(PQexec(conn
.pgconn
, query
.c_str()))
81 result(const authpgsql_connection
&conn
,
82 const sentquery
&query
)
83 : res(PQgetResult(conn
.pgconn
))
93 result(const result
&res
);
94 result
&operator=(const result
&res
);
96 operator bool() const { return res
!= 0; }
98 bool query_failed() const
101 || PQresultStatus(res
) != PGRES_TUPLES_OK
;
104 bool command_failed() const
107 || PQresultStatus(res
) != PGRES_COMMAND_OK
;
110 size_t ntuples() const
112 return res
== 0 ? 0: PQntuples(res
);
115 size_t nfields() const
117 return res
== 0 ? 0: PQnfields(res
);
120 std::string
value(size_t tuple
, size_t field
) const
124 if (tuple
< ntuples() && field
< nfields())
126 const char *p
=PQgetvalue(res
, tuple
, field
);
135 class authpgsqlrc_vars
{
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
;
160 class authpgsqlrc_file
: public courier::auth::config_file
,
161 public authpgsqlrc_vars
{
163 authpgsql_connection
&conn
;
167 authpgsqlrc_file
&operator=(const authpgsqlrc_file
&o
)
169 courier::auth::config_file::operator=(o
);
170 authpgsqlrc_vars::operator=(o
);
174 authpgsqlrc_file(authpgsql_connection
&connArg
)
175 : courier::auth::config_file(AUTHPGSQLRC
),
182 character_set
=config("PGSQL_CHARACTER_SET");
184 if (!config("PGSQL_CONNECTION", connection
, true))
187 select_clause
=config("PGSQL_SELECT_CLAUSE");
188 chpass_clause
=config("PGSQL_CHPASS_CLAUSE");
189 enumerate_clause
=config("PGSQL_ENUMERATE_CLAUSE");
191 defdomain
=config("DEFAULT_DOMAIN");
193 if (select_clause
.empty() || chpass_clause
.empty() ||
194 enumerate_clause
.empty())
196 if (!config("PGSQL_USER_TABLE", user_table
, true))
199 clear_field
=config("PGSQL_CLEAR_PWFIELD");
200 if (clear_field
.empty())
202 if (!config("PGSQL_CRYPT_PWFIELD",
209 crypt_field
=config("PGSQL_CRYPT_PWFIELD");
212 config("PGSQL_NAME_FIELD", name_field
, false,
215 if (crypt_field
.empty()) crypt_field
="''";
216 if (clear_field
.empty()) clear_field
="''";
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, "''");
227 config("PGSQL_WHERE_CLAUSE", where_clause
, false, "1=1");
235 authpgsqlrc_file
new_file(conn
);
237 if (new_file
.load(true))
240 DPRINTF("authpgsql: reloaded %s", filename
);
242 // Disconnect from the server, new login
243 // parameters, perhaps.
250 authpgsqlrc_file config_file
;
252 authpgsql_connection()
253 : last_time(0), pgconn(0), config_file(*this)
257 ~authpgsql_connection()
273 bool getuserinfo(authpgsql_userinfo
&uiret
,
274 const char *username
,
275 const char *service
);
278 bool getuserinfo(authpgsql_userinfo
&uiret
,
281 bool setpass(const char *user
, const char *pass
, const char *oldpass
);
283 void enumerate( void(*cb_func
)(const char *name
,
291 void enumerate( const sentquery
&sent
,
292 void(*cb_func
)(const char *name
,
301 std::string
escape(const std::string
&s
)
304 size_t n
=s
.size()*2+1;
308 n
=PQescapeStringConn(pgconn
, &buffer
[0],
309 s
.c_str(), s
.size(), 0);
316 std::string
escape_username(std::string username
)
318 if (username
.find('@') == username
.npos
&&
319 !config_file
.defdomain
.empty())
321 username
.push_back('@');
322 username
+= config_file
.defdomain
;
325 return escape(username
);
328 static authpgsql_connection
*singleton
;
331 bool authpgsql_connection::do_connect()
339 if (t_check
< last_time
)
340 last_time
=t_check
; /* System clock changed */
342 if (t_check
< last_time
+ 60)
347 if (PQstatus(pgconn
) == CONNECTION_OK
) return true;
349 DPRINTF("authpgsql: PQstatus failed, connection lost");
354 pgconn
= PQconnectdb(config_file
.connection
.c_str());
356 if (PQstatus(pgconn
) == CONNECTION_BAD
)
358 err("PGSQL_CONNECTION could not be established");
359 err("%s", PQerrorMessage(pgconn
));
365 if (!config_file
.character_set
.empty())
367 PQsetClientEncoding(pgconn
,
368 config_file
.character_set
.c_str());
369 std::string real_character_set
=
370 pg_encoding_to_char(PQclientEncoding(pgconn
));
372 if (config_file
.character_set
!= real_character_set
)
374 err("Cannot set character set to \"%s\","
376 config_file
.character_set
.c_str(),
377 real_character_set
.c_str());
381 DPRINTF("Using character set: %s",
382 config_file
.character_set
.c_str());
389 bool authpgsql_connection::getuserinfo(authpgsql_userinfo
&uiret
,
390 const char *username
,
393 std::string querybuf
;
398 if (config_file
.select_clause
.empty())
400 std::ostringstream o
;
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
418 << escape_username(username
)
419 << "' AND (" << config_file
.where_clause
<< ")";
425 std::map
<std::string
, std::string
> parameters
;
427 parameters
["service"]=service
;
430 .parse_custom_query(config_file
.select_clause
,
432 config_file
.defdomain
,
436 DPRINTF("SQL query: %s", querybuf
.c_str());
438 result
res1(*this, querybuf
);
441 return getuserinfo(uiret
, res1
);
446 result
res2(*this, querybuf
);
449 return getuserinfo(uiret
, res2
);
454 bool authpgsql_connection::getuserinfo(authpgsql_userinfo
&uiret
,
457 if (res
.query_failed())
460 if (res
.ntuples() > 0)
462 if (res
.nfields() < 6)
464 DPRINTF("incomplete row, only %d fields returned",
469 uiret
.username
=res
.value(0, 0);
470 uiret
.cryptpw
=res
.value(0, 1);
471 uiret
.clearpw
=res
.value(0, 2);
474 std::string v
=res
.value(0, 3);
476 std::istringstream
i(v
);
480 if (i
.fail() || !i
.eof())
482 DPRINTF("invalid value for uid: '%s'",
489 std::string v
=res
.value(0, 4);
491 std::istringstream
i(v
);
495 if (i
.fail() || !i
.eof())
497 DPRINTF("invalid value for gid: '%s'",
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);
512 DPRINTF("zero rows returned");
519 bool authpgsql_connection::setpass(const char *user
, const char *pass
,
525 std::string newpass_crypt
;
530 if (!(p
=authcryptpasswd(pass
, oldpass
)))
537 std::string clear_escaped
=escape(pass
);
538 std::string crypt_escaped
=escape(newpass_crypt
);
542 if (config_file
.chpass_clause
.empty())
544 std::ostringstream o
;
546 o
<< "UPDATE " << config_file
.user_table
548 if (config_file
.clear_field
!= "''")
550 o
<< config_file
.clear_field
<< "='"
554 if (config_file
.crypt_field
!= "''")
558 if (config_file
.crypt_field
!= "''")
560 o
<< config_file
.crypt_field
<< "='"
566 << config_file
.login_field
<< "='"
567 << escape_username(user
)
568 << "' AND (" << config_file
.where_clause
<< ")";
573 std::map
<std::string
, std::string
> parameters
;
575 parameters
["newpass"]=clear_escaped
;
576 parameters
["newpass_crypt"]=crypt_escaped
;
579 .parse_custom_query(config_file
.chpass_clause
,
581 config_file
.defdomain
,
585 if (courier_authdebug_login_level
>= 2)
587 DPRINTF("setpass SQL: %s", sql_buf
.c_str());
590 result
res(*this, sql_buf
);
592 if (res
.command_failed())
594 DPRINTF("setpass SQL failed");
601 void authpgsql_connection::enumerate( void(*cb_func
)(const char *name
,
612 (*cb_func
)(NULL
, 0, 0, NULL
, NULL
, NULL
, void_arg
);
618 if (config_file
.enumerate_clause
.empty())
620 std::ostringstream o
;
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
630 << config_file
.user_table
<< " WHERE "
631 << config_file
.where_clause
;
637 std::map
<std::string
, std::string
> parameters
;
639 parameters
["service"]="enumerate";
641 .parse_custom_query(config_file
.enumerate_clause
, "*",
642 config_file
.defdomain
, parameters
);
645 DPRINTF("authpgsql: enumerate query: %s", sql_buf
.c_str());
647 sentquery
query1(*this, sql_buf
);
651 enumerate(query1
, cb_func
, void_arg
);
660 sentquery
query2(*this, sql_buf
);
663 enumerate(query2
, cb_func
, void_arg
);
668 void authpgsql_connection::enumerate( const sentquery
&sent
,
669 void(*cb_func
)(const char *name
,
680 result
res(*this, sent
);
685 if (res
.query_failed())
688 size_t n
=res
.ntuples();
690 for (size_t i
=0; i
<n
; ++i
)
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);
702 std::istringstream(uid_s
) >> uid
;
703 std::istringstream(gid_s
) >> gid
;
705 if (username
.empty() || homedir
.empty())
708 (*cb_func
)(username
.c_str(), uid
, gid
,
710 (maildir
.empty() ? 0:maildir
.c_str()),
711 (options
.empty() ? 0:options
.c_str()),
715 (*cb_func
)(NULL
, 0, 0, NULL
, NULL
, NULL
, void_arg
);
719 static authpgsql_connection
*get_conn()
721 if (authpgsql_connection::singleton
)
723 authpgsql_connection::singleton
->config_file
.load(true);
724 return authpgsql_connection::singleton
;
727 authpgsql_connection
*new_conn
=new authpgsql_connection
;
729 if (new_conn
->config_file
.load())
731 authpgsql_connection::singleton
=new_conn
;
739 authpgsql_connection
*authpgsql_connection::singleton
=0;
742 static bool auth_pgsql_getuserinfo(authpgsql_userinfo
&uiret
,
743 const char *username
,
746 authpgsql_connection
*conn
=get_conn();
751 return conn
->getuserinfo(uiret
, username
, service
);
754 static bool docheckpw(authpgsql_userinfo
&authinfo
,
757 if (!authinfo
.cryptpw
.empty())
759 if (authcheckpassword(pass
,authinfo
.cryptpw
.c_str()))
765 else if (!authinfo
.clearpw
.empty())
767 if (authinfo
.clearpw
!= pass
)
769 if (courier_authdebug_login_level
>= 2)
771 DPRINTF("supplied password '%s' does not match clearpasswd '%s'",
772 pass
, authinfo
.clearpw
.empty());
776 DPRINTF("supplied password does not match clearpasswd");
784 DPRINTF("no password available to compare");
791 static int do_auth_pgsql_login(const char *service
, char *authdata
,
792 int (*callback_func
)(struct authinfo
*, void *),
796 authpgsql_userinfo uiret
;
799 if ((user
=strtok(authdata
, "\n")) == 0 ||
800 (pass
=strtok(0, "\n")) == 0)
806 if (!auth_pgsql_getuserinfo(uiret
, user
, service
))
808 errno
=EACCES
; /* Fatal error - such as PgSQL being down */
812 if (!docheckpw(uiret
, pass
))
815 memset(&aa
, 0, sizeof(aa
));
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();
827 courier_authdebug_authinfo("DEBUG: authpgsql: ", &aa
,
828 aa
.clearpasswd
, aa
.passwd
);
829 return (*callback_func
)(&aa
, callback_arg
);
833 static int do_auth_pgsql_changepw(const char *service
, const char *user
,
837 authpgsql_connection
*conn
=get_conn();
842 authpgsql_userinfo uiret
;
844 if (conn
->getuserinfo(uiret
, user
, service
))
846 if (!docheckpw(uiret
, pass
))
852 if (!conn
->setpass(user
, newpass
, uiret
.cryptpw
.c_str()))
869 void auth_pgsql_cleanup()
871 if (authpgsql_connection::singleton
)
872 delete authpgsql_connection::singleton
;
873 authpgsql_connection::singleton
=0;
876 void auth_pgsql_enumerate( void(*cb_func
)(const char *name
,
885 authpgsql_connection
*conn
=get_conn();
888 conn
->enumerate(cb_func
, void_arg
);
891 int auth_pgsql_login(const char *service
, char *authdata
,
892 int (*callback_func
)(struct authinfo
*, void *),
895 return do_auth_pgsql_login(service
, authdata
,
900 int auth_pgsql_changepw(const char *service
, const char *user
,
904 return do_auth_pgsql_changepw(service
, user
, pass
, newpass
);
907 int auth_pgsql_pre(const char *user
, const char *service
,
908 int (*callback
)(struct authinfo
*, void *), void *arg
)
911 authpgsql_userinfo uiret
;
913 if (!auth_pgsql_getuserinfo(uiret
, user
, service
))
916 if (uiret
.home
.empty()) /* User not found */
919 memset(&aa
, 0, sizeof(aa
));
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();
932 return ((*callback
)(&aa
, arg
));