2 ** Copyright 2000-2016 Double Precision, Inc. See COPYING for
3 ** distribution information.
11 #include <sys/types.h>
16 #include "authmysql.h"
19 #include "authmysqlrc.h"
21 #include "courierauthdebug.h"
28 #define err courier_auth_err
30 #include "authconfigfile.h"
32 class authmysql_connection
{
38 class authmysqlrc_vars
{
40 std::string server
, server_socket
, userid
, password
, database
,
42 sslkey
, sslcert
, sslcacert
,
45 defdomain
, user_table
,
49 crypt_field
, clear_field
,
53 defaultdelivery_field
,
61 unsigned int server_port
;
62 unsigned int server_opt
;
65 : server_port(0), server_opt(0) {}
68 class authmysqlrc_file
: public courier::auth::config_file
,
69 public authmysqlrc_vars
{
71 authmysql_connection
&conn
;
75 authmysqlrc_file
&operator=(const authmysqlrc_file
&o
)
77 courier::auth::config_file::operator=(o
);
78 authmysqlrc_vars::operator=(o
);
82 authmysqlrc_file(authmysql_connection
&connArg
)
83 : courier::auth::config_file(AUTHMYSQLRC
),
90 server
=config("MYSQL_SERVER");
91 userid
=config("MYSQL_USERNAME");
92 password
=config("MYSQL_PASSWORD");
93 database
=config("MYSQL_DATABASE");
94 character_set
=config("MYSQL_CHARACTER_SET");
96 sslkey
=config("MYSQL_SSL_KEY");
97 sslcert
=config("MYSQL_SSL_CERT");
98 sslcacert
=config("MYSQL_SSL_CACERT");
99 sslcapath
=config("MYSQL_SSL_CAPATH");
100 sslcipher
=config("MYSQL_SSL_CIPHER");
102 if ((std::istringstream(config("MYSQL_PORT"))
103 >> server_port
).fail())
105 err("authmysql: cannot parse the MYSQL_PORT "
110 if ((std::istringstream(config("MYSQL_OPT"))
111 >> server_opt
).fail())
113 err("authmysql: cannot parse the MYSQL_OPT "
117 server_socket
=config("MYSQL_SOCKET");
119 if (!server
.size() && !server_socket
.size())
121 err("authmysql: MYSQL_SERVER nor MYSQL_SOCKET set in"
128 err("authmysql: MYSQL_USERNAME not set in "
133 if (!database
.size())
135 err("authmysql: MYSQL_DATABASE not set in "
140 defdomain
=config("DEFAULT_DOMAIN");
141 user_table
=config("MYSQL_USER_TABLE");
143 if (!user_table
.size())
145 err("authmysql: MYSQL_USER_TABLE not set in "
150 uid_field
=config("MYSQL_UID_FIELD", "uid");
151 gid_field
=config("MYSQL_GID_FIELD", "gid");
152 name_field
=config("MYSQL_NAME_FIELD", "''");
153 login_field
=config("MYSQL_LOGIN_FIELD", "id");
154 home_field
=config("MYSQL_HOME_FIELD", "home");
155 maildir_field
=config("MYSQL_MAILDIR_FIELD",
157 defaultdelivery_field
=
158 config("MYSQL_DEFAULTDELIVERY_FIELD",
160 quota_field
=config("MYSQL_QUOTA_FIELD", "''");
162 options_field
=config("MYSQL_AUXOPTIONS_FIELD",
164 where_clause
=config("MYSQL_WHERE_CLAUSE",
166 select_clause
=config("MYSQL_SELECT_CLAUSE");
167 enumerate_clause
=config("MYSQL_ENUMERATE_CLAUSE");
169 chpass_clause
=config("MYSQL_CHPASS_CLAUSE");
170 crypt_field
=config("MYSQL_CRYPT_PWFIELD", "''");
171 clear_field
=config("MYSQL_CLEAR_PWFIELD", "''");
173 if (crypt_field
== "''" && clear_field
== "''")
175 err("authmysql: MYSQL_CRYPT_PWFIELD and "
176 "MYSQL_CLEAR_PWFIELD not set in "
186 authmysqlrc_file
new_file(conn
);
188 if (new_file
.load(true))
191 DPRINTF("authmysql: reloaded %s", filename
);
193 // Disconnect from the server, new login
194 // parameters, perhaps.
201 authmysqlrc_file config_file
;
203 authmysql_connection() : mysql(0), last_time(0), config_file(*this)
207 ~authmysql_connection()
222 static authmysql_connection
*singleton
;
224 bool try_connection()
226 bool rc
=check_connection();
233 bool check_connection();
241 unsigned long *lengths
;
243 result(const authmysql_connection
&conn
, bool use_result
=false)
245 ? mysql_use_result(conn
.mysql
)
246 : mysql_store_result(conn
.mysql
)), row(NULL
),
255 mysql_free_result(res
);
258 operator bool() const
263 size_t num_rows() const
265 return res
? mysql_num_rows(res
):0;
268 size_t num_fields() const { return num_fields_n
; }
272 if (res
&& (row
=mysql_fetch_row(res
)) != NULL
)
274 num_fields_n
=mysql_num_fields(res
);
275 lengths
=mysql_fetch_lengths(res
);
283 std::string
operator[](size_t column
) const
285 if (column
< num_fields())
287 const char *p
=reinterpret_cast<const char *>
290 return std::string(p
, p
+ lengths
[column
]);
292 return std::string();
296 bool query(const std::string
&sql
)
298 if (mysql_query(mysql
, sql
.c_str()) == 0)
301 DPRINTF("mysql_query failed: %s", mysql_error(mysql
));
304 if (!try_connection())
307 if (mysql_query (mysql
, sql
.c_str()))
309 DPRINTF("mysql_query failed second time, giving up: %s", mysql_error(mysql
));
317 std::string
escape(const std::string
&s
)
320 size_t n
=s
.size()*2+1;
324 mysql_real_escape_string(mysql
, &buffer
[0],
325 s
.c_str(), s
.size());
326 buffer
.resize(strlen(&buffer
[0]));
330 std::string
get_default_select(const char *username
,
331 const char *service
);
333 bool getuserinfo(const char *username
,
335 authmysqluserinfo
&uiret
);
337 bool setpass(const char *user
, const char *pass
,
338 const char *oldpass
);
340 void enumerate( void(*cb_func
)(const char *name
,
349 static bool connect()
352 singleton
=new authmysql_connection
;
354 if (!singleton
->config_file
.load())
357 return singleton
->try_connection();
361 authmysql_connection
*authmysql_connection::singleton
=0;
363 bool authmysql_connection::check_connection()
368 ** Periodically detect dead connections.
376 if (t_check
< last_time
)
377 last_time
=t_check
; /* System clock changed */
379 if (t_check
< last_time
+ 60)
384 if (mysql_ping(mysql
) == 0) return true;
386 DPRINTF("authmysqllib: mysql_ping failed, connection lost");
390 if (config_file
.sslcacert
.size() || config_file
.sslcapath
.size())
392 if (config_file
.sslcert
.size())
393 DPRINTF("authmysqllib: certificate file set to %s",
394 config_file
.sslcert
.c_str());
396 if (config_file
.sslcipher
.size())
397 DPRINTF("authmysqllib: ciphers set to %s",
398 config_file
.sslcipher
.c_str());
400 if (config_file
.sslcacert
.size())
401 DPRINTF("authmysqllib: certificate authority set to %s",
402 config_file
.sslcacert
.c_str());
403 if (config_file
.sslcapath
.size())
404 DPRINTF("authmysqllib: certificate authority set to %s",
405 config_file
.sslcapath
.c_str());
409 MYSQL
*conn
=new MYSQL
;
414 const char *key
=config_file
.sslkey
.c_str();
415 const char *cert
=config_file
.sslcert
.c_str();
416 const char *cacert
=config_file
.sslcacert
.c_str();
417 const char *capath
=config_file
.sslcapath
.c_str();
418 const char *cipher
=config_file
.sslcipher
.c_str();
422 if (!*cacert
) cacert
=0;
423 if (!*capath
) capath
=0;
424 if (!*cipher
) cipher
=0;
426 mysql_ssl_set(conn
, key
, cert
, cacert
,
430 mysql
=mysql_real_connect(conn
, config_file
.server
.c_str(),
431 config_file
.userid
.c_str(),
432 config_file
.password
.c_str(),
434 config_file
.server_port
,
435 (config_file
.server_socket
.size() ?
436 config_file
.server_socket
.c_str():0),
437 config_file
.server_opt
);
441 err("failed to connect to mysql server (server=%s, userid=%s): %s",
442 config_file
.server
.size() ? config_file
.server
.c_str() : "<null>",
443 config_file
.userid
.size() ? config_file
.userid
.c_str() : "<null>",
450 if (mysql_select_db(mysql
, config_file
.database
.c_str()))
452 err("authmysql: mysql_select_db(%s) error: %s",
453 config_file
.database
.c_str(), mysql_error(mysql
));
457 DPRINTF("authmysqllib: connected. Versions: "
461 (long)MYSQL_VERSION_ID
,
462 mysql_get_client_version(),
463 mysql_get_server_version(mysql
));
465 if (config_file
.character_set
.size())
467 mysql_set_character_set(mysql
,
468 config_file
.character_set
.c_str());
470 std::string real_character_set
=mysql_character_set_name(mysql
);
472 if (config_file
.character_set
!= real_character_set
)
474 err("Cannot set character set to \"%s\","
476 config_file
.character_set
.c_str(),
477 real_character_set
.c_str());
481 DPRINTF("Using character set: %s",
482 config_file
.character_set
.c_str());
489 std::string
authmysql_connection::get_default_select(const char *username
,
495 std::string maildir_field
=
496 service
&& strcmp(service
, "courier")==0
497 ? config_file
.defaultdelivery_field
498 : config_file
.maildir_field
;
500 bool has_domain
=strchr(username
, '@') != NULL
;
502 std::ostringstream o
;
505 << config_file
.login_field
<< ", "
506 << config_file
.crypt_field
<< ", "
507 << config_file
.clear_field
<< ", "
508 << config_file
.uid_field
<< ", "
509 << config_file
.gid_field
<< ", "
510 << config_file
.home_field
<< ", "
511 << maildir_field
<< ", "
512 << config_file
.quota_field
<< ", "
513 << config_file
.name_field
<< ", "
514 << config_file
.options_field
516 << config_file
.user_table
518 << config_file
.login_field
522 if (!has_domain
&& config_file
.defdomain
.size())
524 o
<< "@" << config_file
.defdomain
;
526 o
<< "' AND (" << config_file
.where_clause
<< ")";
532 bool authmysql_connection::getuserinfo(const char *username
,
534 authmysqluserinfo
&uiret
)
536 std::string querybuf
;
538 if (config_file
.select_clause
.empty())
540 querybuf
=get_default_select(username
, service
);
544 std::map
<std::string
, std::string
> parameters
;
546 parameters
["service"]=service
;
549 .parse_custom_query(config_file
.select_clause
,
551 config_file
.defdomain
,
555 DPRINTF("SQL query: %s", querybuf
.c_str());
557 if (!query(querybuf
))
564 if (res
.num_fields() < 6)
566 DPRINTF("incomplete row, only %d fields returned",
571 uiret
.username
=res
[0];
572 uiret
.cryptpw
=res
[1];
573 uiret
.clearpw
=res
[2];
575 std::string uid_s
=res
[3];
576 std::string gid_s
=res
[4];
578 std::istringstream(uid_s
) >> uiret
.uid
;
579 std::istringstream(gid_s
) >> uiret
.gid
;
583 DPRINTF("invalid value for uid: '%s'",
584 uid_s
.size() ? uid_s
.c_str() : "<null>");
590 DPRINTF("invalid value for gid: '%s'",
591 gid_s
.size() ? gid_s
.c_str() : "<null>");
596 if (uiret
.home
.size() == 0)
598 DPRINTF("required value for 'home' (column 6) is missing");
602 uiret
.maildir
=res
[6];
604 uiret
.fullname
=res
[8];
605 uiret
.options
=res
[9];
609 DPRINTF("zero rows returned");
614 bool authmysql_connection::setpass(const char *user
, const char *pass
,
617 std::string newpass_crypt
;
622 if (!(p
=authcryptpasswd(pass
, oldpass
)))
629 std::string clear_escaped
=escape(pass
);
631 std::string crypt_escaped
=escape(newpass_crypt
);
635 if (config_file
.chpass_clause
.size() == 0)
637 std::string username_escaped
=escape(user
);
639 bool has_domain
=strchr(user
, '@') != NULL
;
641 std::ostringstream o
;
643 o
<< "UPDATE " << config_file
.user_table
<< " SET ";
645 if (config_file
.clear_field
!= "''")
646 o
<< config_file
.clear_field
<< "='"
647 << clear_escaped
<< "'";
649 if (config_file
.crypt_field
!= "''")
651 if (config_file
.clear_field
!= "''") o
<< ", ";
652 o
<< config_file
.crypt_field
<< "='" << crypt_escaped
<< "'";
655 o
<< " WHERE " << config_file
.login_field
<< "='"
658 if (!has_domain
&& config_file
.defdomain
.size())
659 o
<< "@" << config_file
.defdomain
;
666 std::map
<std::string
, std::string
> parameters
;
668 parameters
["newpass"]=clear_escaped
;
669 parameters
["newpass_crypt"]=crypt_escaped
;
672 .parse_custom_query(config_file
.chpass_clause
,
674 config_file
.defdomain
,
678 if (courier_authdebug_login_level
>= 2)
680 DPRINTF("setpass SQL: %s", sql_buf
.c_str());
688 void authmysql_connection::enumerate(void(*cb_func
)(const char *name
,
697 std::string querybuf
;
699 if (config_file
.enumerate_clause
.empty())
701 std::ostringstream o
;
704 << config_file
.login_field
<< ", "
705 << config_file
.uid_field
<< ", "
706 << config_file
.gid_field
<< ", "
707 << config_file
.home_field
<< ", "
708 << config_file
.maildir_field
<< ", "
709 << config_file
.options_field
<< " FROM "
710 << config_file
.user_table
<< " WHERE "
711 << config_file
.where_clause
;
717 std::map
<std::string
, std::string
> parameters
;
719 parameters
["service"]="enumerate";
721 .parse_custom_query(config_file
.enumerate_clause
, "*",
722 config_file
.defdomain
, parameters
);
724 DPRINTF("authmysql: enumerate query: %s", querybuf
.c_str());
726 if (!query(querybuf
))
735 std::string username
=row
[0];
737 if (username
.size() == 0)
743 std::istringstream(row
[1]) >> uid
;
744 std::istringstream(row
[2]) >> gid
;
745 std::string homedir
=row
[3];
746 std::string maildir
=row
[4];
747 std::string options
=row
[5];
749 (*cb_func
)(username
.c_str(), uid
, gid
,
751 maildir
.size() ? maildir
.c_str():NULL
,
752 options
.size() ? options
.c_str():NULL
,
756 /* NULL row could indicate end of result or an error */
757 if (mysql_errno(mysql
))
759 DPRINTF("mysql error during enumeration: %s",
763 (*cb_func
)(NULL
, 0, 0, NULL
, NULL
, NULL
, void_arg
);
766 void auth_mysql_cleanup()
768 if (authmysql_connection::singleton
)
770 delete authmysql_connection::singleton
;
771 authmysql_connection::singleton
=0;
775 bool auth_mysql_setpass(const char *user
, const char *pass
,
778 if (!authmysql_connection::connect())
780 return authmysql_connection::singleton
->setpass(user
, pass
, oldpass
);
783 void auth_mysql_enumerate( void(*cb_func
)(const char *name
,
792 if (!authmysql_connection::connect())
794 (*cb_func
)(NULL
, 0, 0, NULL
, NULL
, NULL
, void_arg
);
798 authmysql_connection::singleton
->enumerate(cb_func
, void_arg
);
801 bool auth_mysql_getuserinfo(const char *username
,
803 authmysqluserinfo
&uiret
)
805 if (!authmysql_connection::connect())
808 return authmysql_connection::singleton
->getuserinfo(username
, service
,