2 ** Copyright 2000-2010 Double Precision, Inc. See COPYING for
3 ** distribution information.
11 #include <sys/types.h>
16 #include "authmysql.h"
17 #include "authmysqlrc.h"
19 #include "courierauthdebug.h"
21 #define err courier_auth_err
23 static const char *read_env(const char *env
)
25 return authgetconfig(AUTHMYSQLRC
, env
);
28 static MYSQL mysql_buf
;
30 static MYSQL
*mysql
=0;
32 static char *escape_str(const char *c
, size_t n
)
34 char *buf
=malloc(n
*2+1);
42 mysql_real_escape_string(mysql
, buf
, c
, n
);
46 static void set_session_options(void)
48 * session variables can be set once for the whole session
51 /* Anton Dobkin <anton@viansib.ru>, VIAN, Ltd. */
52 #if MYSQL_VERSION_ID >= 41000
53 const char *character_set
=read_env("MYSQL_CHARACTER_SET"), *check
;
58 * This function works like the SET NAMES statement, but also sets
59 * the value of mysql->charset, and thus affects the character set
60 * used by mysql_real_escape_string()
62 * (return value apparently work the opposite of what is documented)
64 mysql_set_character_set(mysql
, character_set
);
65 check
= mysql_character_set_name(mysql
);
66 if (strcmp(character_set
, check
) != 0)
68 err("Cannot set MySQL character set \"%s\", working with \"%s\"\n",
69 character_set
, check
);
73 DPRINTF("Install of a character set for MySQL: %s", character_set
);
79 static int do_connect()
85 const char *server_socket
=0;
86 unsigned int server_port
=0;
87 unsigned int server_opt
=0;
92 const char *sslcacert
;
93 const char *sslcapath
;
94 const char *sslcipher
;
95 unsigned int use_ssl
=0;
98 ** Periodically detect dead connections.
102 static time_t last_time
=0;
107 if (t_check
< last_time
)
108 last_time
=t_check
; /* System clock changed */
110 if (t_check
< last_time
+ 60)
115 if (mysql_ping(mysql
) == 0) return (0);
117 DPRINTF("authmysqllib: mysql_ping failed, connection lost");
122 server
=read_env("MYSQL_SERVER");
123 userid
=read_env("MYSQL_USERNAME");
124 password
=read_env("MYSQL_PASSWORD");
125 database
=read_env("MYSQL_DATABASE");
127 #if MYSQL_VERSION_ID >= 32200
128 sslkey
=read_env("MYSQL_SSL_KEY");
129 sslcert
=read_env("MYSQL_SSL_CERT");
130 sslcacert
=read_env("MYSQL_SSL_CACERT");
131 sslcapath
=read_env("MYSQL_SSL_CAPATH");
132 sslcipher
=read_env("MYSQL_SSL_CIPHER");
134 if ((sslcert
!= NULL
) && ((sslcacert
!= NULL
) || (sslcapath
!= NULL
)))
140 server_socket
=(char *) read_env("MYSQL_SOCKET");
142 if ((p
=read_env("MYSQL_PORT")) != 0)
144 server_port
=(unsigned int) atoi(p
);
147 if ((p
=read_env("MYSQL_OPT")) != 0)
149 server_opt
=(unsigned int) atol(p
);
152 if (!server
&& !server_socket
)
154 err("authmysql: MYSQL_SERVER nor MYSQL_SOCKET set in"
161 err("authmysql: MYSQL_USERNAME not set in "
168 err("authmysql: MYSQL_DATABASE not set in "
173 #if MYSQL_VERSION_ID >= 32200
174 mysql_init(&mysql_buf
);
177 mysql_ssl_set(&mysql_buf
, sslkey
, sslcert
, sslcacert
,
178 sslcapath
, sslcipher
);
180 mysql
=mysql_real_connect(&mysql_buf
, server
, userid
, password
,
186 mysql
=mysql_connect(&mysql_buf
, server
, userid
, password
);
190 err("failed to connect to mysql server (server=%s, userid=%s): %s",
191 server
? server
: "<null>",
192 userid
? userid
: "<null>",
193 mysql_error(&mysql_buf
));
197 if (mysql_select_db(mysql
, database
))
199 err("authmysql: mysql_select_db(%s) error: %s",
200 database
, mysql_error(mysql
));
206 DPRINTF("authmysqllib: connected. Versions: "
210 (long)MYSQL_VERSION_ID
,
211 mysql_get_client_version(),
212 mysql_get_server_version(mysql
));
214 set_session_options();
218 void auth_mysql_cleanup()
227 static struct authmysqluserinfo ui
={0, 0, 0, 0, 0, 0, 0, 0};
248 memset(&ui
, 0, sizeof(ui
));
251 struct authmysqluserinfo
*auth_mysql_getuserinfo(const char *username
,
254 const char *defdomain
=NULL
;
261 const char *select_clause
; /* siefca@pld.org.pl */
263 #define DEFAULT_SELECT_QUERY "SELECT %s, %s, %s, %s, %s, %s, %s, %s, %s, %s FROM %s WHERE %s = '%s%s%s' %s%s%s", \
264 login_field, crypt_field, clear_field, uid_field,\
265 gid_field, home_field, maildir_field, quota_field,\
266 name_field, options_field, user_table, login_field,\
268 has_domain || !*defdomain ? "":"@", has_domain ? "":defdomain, \
269 *where_clause ? " AND (":"", where_clause,\
270 *where_clause ? ")":""
272 if (do_connect()) return (0);
276 select_clause
=read_env("MYSQL_SELECT_CLAUSE");
277 defdomain
=read_env("DEFAULT_DOMAIN");
278 if (!defdomain
) defdomain
="";
280 if (!select_clause
) /* siefca@pld.org.pl */
282 const char *user_table
,
294 char *username_escaped
;
299 user_table
=read_env("MYSQL_USER_TABLE");
303 err("authmysql: MYSQL_USER_TABLE not set in "
308 crypt_field
=read_env("MYSQL_CRYPT_PWFIELD");
309 clear_field
=read_env("MYSQL_CLEAR_PWFIELD");
310 name_field
=read_env("MYSQL_NAME_FIELD");
312 if (!crypt_field
&& !clear_field
)
314 err("authmysql: MYSQL_CRYPT_PWFIELD and "
315 "MYSQL_CLEAR_PWFIELD not set in " AUTHMYSQLRC
".");
318 if (!crypt_field
) crypt_field
="\"\"";
319 if (!clear_field
) clear_field
="\"\"";
320 if (!name_field
) name_field
="\"\"";
322 uid_field
= read_env("MYSQL_UID_FIELD");
323 if (!uid_field
) uid_field
= "uid";
325 gid_field
= read_env("MYSQL_GID_FIELD");
326 if (!gid_field
) gid_field
= "gid";
328 login_field
= read_env("MYSQL_LOGIN_FIELD");
329 if (!login_field
) login_field
= "id";
331 home_field
= read_env("MYSQL_HOME_FIELD");
332 if (!home_field
) home_field
= "home";
334 maildir_field
=read_env(service
&& strcmp(service
, "courier")==0
335 ? "MYSQL_DEFAULTDELIVERY"
336 : "MYSQL_MAILDIR_FIELD");
337 if (!maildir_field
) maildir_field
="\"\"";
339 quota_field
=read_env("MYSQL_QUOTA_FIELD");
340 if (!quota_field
) quota_field
="\"\"";
342 options_field
=read_env("MYSQL_AUXOPTIONS_FIELD");
343 if (!options_field
) options_field
="\"\"";
345 where_clause
=read_env("MYSQL_WHERE_CLAUSE");
346 if (!where_clause
) where_clause
= "";
348 username_escaped
=malloc(strlen(username
)*2+1);
350 if (!username_escaped
)
356 mysql_real_escape_string(mysql
, username_escaped
,
357 username
, strlen(username
));
359 has_domain
=strchr(username
, '@') != NULL
;
361 query_size
=snprintf(dummy_buf
, 1, DEFAULT_SELECT_QUERY
);
363 querybuf
=malloc(query_size
+1);
367 free(username_escaped
);
372 snprintf(querybuf
, query_size
+1, DEFAULT_SELECT_QUERY
);
373 free(username_escaped
);
378 /* siefca@pld.org.pl */
379 querybuf
=auth_parse_select_clause (escape_str
,
380 select_clause
, username
,
384 DPRINTF("parse_select_clause failed (DEFAULT_DOMAIN not set?)");
389 DPRINTF("SQL query: %s", querybuf
);
390 if (mysql_query (mysql
, querybuf
))
392 /* <o.blasnik@nextra.de> */
394 DPRINTF("mysql_query failed, reconnecting: %s", mysql_error(mysql
));
395 auth_mysql_cleanup();
403 if (mysql_query (mysql
, querybuf
))
405 DPRINTF("mysql_query failed second time, giving up: %s", mysql_error(mysql
));
407 auth_mysql_cleanup();
408 /* Server went down, that's OK,
409 ** try again next time.
416 result
= mysql_store_result (mysql
);
419 if (mysql_num_rows(result
))
421 row
= mysql_fetch_row (result
);
422 num_fields
= mysql_num_fields (result
);
426 DPRINTF("incomplete row, only %d fields returned",
428 mysql_free_result(result
);
432 if (row
[0] && row
[0][0])
433 ui
.username
=strdup(row
[0]);
434 if (row
[1] && row
[1][0])
435 ui
.cryptpw
=strdup(row
[1]);
436 if (row
[2] && row
[2][0])
437 ui
.clearpw
=strdup(row
[2]);
438 /* perhaps authmysql needs a glob_uid/glob_gid feature
440 if (!row
[3] || !row
[3][0] ||
441 (ui
.uid
=strtol(row
[3], &endp
, 10), endp
[0] != '\0'))
443 DPRINTF("invalid value for uid: '%s'",
444 row
[3] ? row
[3] : "<null>");
445 mysql_free_result(result
);
448 if (!row
[4] || !row
[4][0] ||
449 (ui
.gid
=strtol(row
[4], &endp
, 10), endp
[0] != '\0'))
451 DPRINTF("invalid value for gid: '%s'",
452 row
[4] ? row
[4] : "<null>");
453 mysql_free_result(result
);
456 if (row
[5] && row
[5][0])
457 ui
.home
=strdup(row
[5]);
460 DPRINTF("required value for 'home' (column 6) is missing");
461 mysql_free_result(result
);
464 if (num_fields
> 6 && row
[6] && row
[6][0])
465 ui
.maildir
=strdup(row
[6]);
466 if (num_fields
> 7 && row
[7] && row
[7][0])
467 ui
.quota
=strdup(row
[7]);
468 if (num_fields
> 8 && row
[8] && row
[8][0])
469 ui
.fullname
=strdup(row
[8]);
470 if (num_fields
> 9 && row
[9] && row
[9][0])
471 ui
.options
=strdup(row
[9]);
475 DPRINTF("zero rows returned");
476 mysql_free_result(result
);
477 return (&ui
); /* User not found */
482 DPRINTF("mysql_store_result failed");
485 mysql_free_result(result
);
489 int auth_mysql_setpass(const char *user
, const char *pass
,
499 const char *clear_field
=NULL
,
505 *chpass_clause
=NULL
; /* siefca@pld.org.pl */
507 if (do_connect()) return (-1);
509 if (!(newpass_crypt
=authcryptpasswd(pass
, oldpass
)))
512 clear_escaped
=malloc(strlen(pass
)*2+1);
521 crypt_escaped
=malloc(strlen(newpass_crypt
)*2+1);
531 mysql_real_escape_string(mysql
, clear_escaped
, pass
, strlen(pass
));
532 mysql_real_escape_string(mysql
, crypt_escaped
,
533 newpass_crypt
, strlen(newpass_crypt
));
535 /* siefca@pld.org.pl */
536 chpass_clause
=read_env("MYSQL_CHPASS_CLAUSE");
537 defdomain
=read_env("DEFAULT_DOMAIN");
538 user_table
=read_env("MYSQL_USER_TABLE");
541 int has_domain
=strchr(user
, '@') != NULL
;
542 char *username_escaped
;
546 username_escaped
=malloc(strlen(user
)*2+1);
548 if (!username_escaped
)
557 mysql_real_escape_string(mysql
, username_escaped
,
560 login_field
= read_env("MYSQL_LOGIN_FIELD");
561 if (!login_field
) login_field
= "id";
562 crypt_field
=read_env("MYSQL_CRYPT_PWFIELD");
563 clear_field
=read_env("MYSQL_CLEAR_PWFIELD");
564 where_clause
=read_env("MYSQL_WHERE_CLAUSE");
578 #define DEFAULT_SETPASS_UPDATE \
579 "UPDATE %s SET %s%s%s%s %s %s%s%s%s WHERE %s='%s%s%s' %s%s%s", \
581 *clear_field ? clear_field:"", \
582 *clear_field ? "='":"", \
583 *clear_field ? clear_escaped:"", \
584 *clear_field ? "'":"", \
586 *clear_field && *crypt_field ? ",":"", \
588 *crypt_field ? crypt_field:"", \
589 *crypt_field ? "='":"", \
590 *crypt_field ? crypt_escaped:"", \
591 *crypt_field ? "'":"", \
594 has_domain || !*defdomain ? "":"@", \
595 has_domain ? "":defdomain, \
596 *where_clause ? " AND (":"", where_clause, \
597 *where_clause ? ")":""
600 sql_buf_size
=snprintf(dummy_buf
, 1, DEFAULT_SETPASS_UPDATE
);
602 sql_buf
=malloc(sql_buf_size
+1);
605 snprintf(sql_buf
, sql_buf_size
+1,
606 DEFAULT_SETPASS_UPDATE
);
608 free(username_escaped
);
612 sql_buf
=auth_parse_chpass_clause(escape_str
,
624 if (courier_authdebug_login_level
>= 2)
626 DPRINTF("setpass SQL: %s", sql_buf
);
628 if (mysql_query (mysql
, sql_buf
))
630 DPRINTF("setpass SQL failed");
632 auth_mysql_cleanup();
638 void auth_mysql_enumerate( void(*cb_func
)(const char *name
,
647 const char *defdomain
, *select_clause
;
654 (*cb_func
)(NULL
, 0, 0, NULL
, NULL
, NULL
, void_arg
);
660 select_clause
=read_env("MYSQL_ENUMERATE_CLAUSE");
661 defdomain
=read_env("DEFAULT_DOMAIN");
662 if (!defdomain
|| !defdomain
[0])
663 defdomain
="*"; /* otherwise parse_select_clause fails */
667 const char *user_table
,
678 user_table
=read_env("MYSQL_USER_TABLE");
682 err("authmysql: MYSQL_USER_TABLE not set in "
687 uid_field
= read_env("MYSQL_UID_FIELD");
688 if (!uid_field
) uid_field
= "uid";
690 gid_field
= read_env("MYSQL_GID_FIELD");
691 if (!gid_field
) gid_field
= "gid";
693 login_field
= read_env("MYSQL_LOGIN_FIELD");
694 if (!login_field
) login_field
= "id";
696 home_field
= read_env("MYSQL_HOME_FIELD");
697 if (!home_field
) home_field
= "home";
699 maildir_field
=read_env("MYSQL_MAILDIR_FIELD");
700 if (!maildir_field
) maildir_field
="\"\"";
702 options_field
=read_env("MYSQL_AUXOPTIONS_FIELD");
703 if (!options_field
) options_field
="\"\"";
705 where_clause
=read_env("MYSQL_WHERE_CLAUSE");
706 if (!where_clause
) where_clause
= "";
709 #define DEFAULT_ENUMERATE_QUERY \
710 "SELECT %s, %s, %s, %s, %s, %s FROM %s %s%s",\
711 login_field, uid_field, gid_field, \
712 home_field, maildir_field, \
713 options_field, user_table, \
714 *where_clause ? " WHERE ":"", \
717 query_len
=snprintf(dummy_buf
, 1, DEFAULT_ENUMERATE_QUERY
);
719 querybuf
=malloc(query_len
+1);
727 snprintf(querybuf
, query_len
+1, DEFAULT_ENUMERATE_QUERY
);
731 /* siefca@pld.org.pl */
732 querybuf
=auth_parse_select_clause (escape_str
,
734 defdomain
, "enumerate");
737 DPRINTF("authmysql: parse_select_clause failed");
741 DPRINTF("authmysql: enumerate query: %s", querybuf
);
743 if (mysql_query (mysql
, querybuf
))
745 DPRINTF("mysql_query failed, reconnecting: %s", mysql_error(mysql
));
746 /* <o.blasnik@nextra.de> */
748 auth_mysql_cleanup();
756 if (mysql_query (mysql
, querybuf
))
758 DPRINTF("mysql_query failed second time, giving up: %s", mysql_error(mysql
));
760 auth_mysql_cleanup();
766 result
= mysql_use_result (mysql
);
769 const char *username
;
776 while ((row
= mysql_fetch_row (result
)) != NULL
)
778 if(!row
[0] || !row
[0][0]
779 || !row
[1] || !row
[1][0]
780 || !row
[2] || !row
[2][0]
781 || !row
[3] || !row
[3][0])
787 uid
=atol(row
[1]); /* FIXME use strtol to validate */
793 if (maildir
&& !*maildir
)
796 (*cb_func
)(username
, uid
, gid
, homedir
,
797 maildir
, options
, void_arg
);
800 /* NULL row could indicate end of result or an error */
801 if (mysql_errno(mysql
))
803 DPRINTF("mysql error during enumeration: %s", mysql_error(mysql
));
806 (*cb_func
)(NULL
, 0, 0, NULL
, NULL
, NULL
, void_arg
);
808 if (result
) mysql_free_result(result
);