2 ** Copyright 2000-2004 Double Precision, Inc. See COPYING for
3 ** distribution information.
12 #include <sys/types.h>
17 #include "authpgsql.h"
18 #include "authpgsqlrc.h"
20 #include "courierauthdebug.h"
22 #define err courier_auth_err
24 /* tom@minnesota.com */
25 #define MAX_SUBSTITUTION_LEN 32
26 #define SV_BEGIN_MARK "$("
27 #define SV_END_MARK ")"
28 #define SV_BEGIN_LEN ((sizeof(SV_BEGIN_MARK))-1)
29 #define SV_END_LEN ((sizeof(SV_END_MARK))-1)
31 static const char rcsid
[]="$Id: authpgsqllib.c,v 1.18 2006/10/28 19:22:52 mrsam Exp $";
33 /* tom@minnesota.com */
40 /* tom@minnesota.com */
41 typedef int (*parsefunc
)(const char *, size_t, void *);
43 static const char *read_env(const char *env
)
45 static char *pgsqlauth
=0;
46 static size_t pgsqlauth_size
=0;
53 FILE *f
=fopen(AUTHPGSQLRC
, "r");
57 if (fstat(fileno(f
), &buf
) ||
58 (pgsqlauth
=malloc(buf
.st_size
+2)) == 0)
63 if (fread(pgsqlauth
, buf
.st_size
, 1, f
) != 1)
70 pgsqlauth
[pgsqlauth_size
=buf
.st_size
]=0;
72 for (i
=0; i
<pgsqlauth_size
; i
++)
73 if (pgsqlauth
[i
] == '\n')
74 { /* tom@minnesota.com */
75 if (!i
|| pgsqlauth
[i
-1] != '\\')
81 pgsqlauth
[i
]=pgsqlauth
[i
-1]= ' ';
87 for (i
=0; i
<pgsqlauth_size
; )
90 if (memcmp(p
, env
, l
) == 0 &&
91 isspace((int)(unsigned char)p
[l
]))
94 while (*p
&& *p
!= '\n' &&
95 isspace((int)(unsigned char)*p
))
100 while (i
< pgsqlauth_size
)
101 if (pgsqlauth
[i
++] == 0) break;
104 if (i
< pgsqlauth_size
)
109 static PGresult
*pgresult
=0;
111 static PGconn
*pgconn
=0;
114 * session variables can be set once for the whole session
117 static void set_session_options(void)
119 const char *character_set
=read_env("PGSQL_CHARACTER_SET"), *check
;
123 PQsetClientEncoding(pgconn
, character_set
);
124 check
= pg_encoding_to_char(PQclientEncoding(pgconn
));
125 if (strcmp(character_set
, check
) != 0)
127 err("Cannot set Postgresql character set \"%s\", working with \"%s\"\n",
128 character_set
, check
);
132 DPRINTF("Install of a character set for Postgresql: %s", character_set
);
140 static FILE *DEBUG=0;
143 static int do_connect()
147 const char *password
;
148 const char *database
;
149 const char *server_port
=0;
150 const char *server_opt
=0;
153 DEBUG=fopen("/tmp/courier.debug","a");
155 fprintf(DEBUG,"Apro il DB!\n");
160 ** Periodically detect dead connections.
164 static time_t last_time
=0;
169 if (t_check
< last_time
)
170 last_time
=t_check
; /* System clock changed */
172 if (t_check
< last_time
+ 60)
177 if (PQstatus(pgconn
) == CONNECTION_OK
) return (0);
179 DPRINTF("authpgsqllib: PQstatus failed, connection lost");
184 server
=read_env("PGSQL_HOST");
185 server_port
=read_env("PGSQL_PORT");
186 userid
=read_env("PGSQL_USERNAME");
187 password
=read_env("PGSQL_PASSWORD");
188 database
=read_env("PGSQL_DATABASE");
189 server_opt
=read_env("PGSQL_OPT");
192 fprintf(DEBUG,"Letti i parametri!\n");
198 err("authpgsql: PGSQL_USERNAME not set in "
205 err("authpgsql: PGSQL_DATABASE not set in "
211 fprintf(DEBUG,"Connecting to db:%s:%s\%s user=%s pass=%s\n",server,server_port,database,userid,password);
214 pgconn
= PQsetdbLogin(server
, server_port
, server_opt
, NULL
, database
,userid
,password
);
216 if (PQstatus(pgconn
) == CONNECTION_BAD
)
218 err("Connection to server '%s' userid '%s' database '%s' failed.",
219 server
? server
: "<null>",
220 userid
? userid
: "<null>",
222 err("%s", PQerrorMessage(pgconn
));
227 fprintf(DEBUG,"Connected!\n");
231 set_session_options();
236 void auth_pgsql_cleanup()
245 static struct authpgsqluserinfo ui
={0, 0, 0, 0, 0, 0, 0, 0};
247 static char *get_username_escaped(const char *username
,
248 const char *defdomain
)
250 char *username_escaped
;
256 username_escaped
=malloc(strlen(username
)*2+2+strlen(defdomain
));
258 if (!username_escaped
)
264 PQescapeStringConn(pgconn
, username_escaped
, username
, strlen(username
), error
);
266 if (strchr(username
, '@') == 0 && *defdomain
)
267 strcat(strcat(username_escaped
, "@"), defdomain
);
269 return username_escaped
;
272 /* tom@minnesota.com */
273 static struct var_data
*get_variable (const char *begin
, size_t len
,
274 struct var_data
*vdt
)
276 struct var_data
*vdp
;
278 if (!begin
|| !vdt
) /* should never happend */
280 err("authpgsql: critical error while "
281 "parsing substitution variable");
286 err("authpgsql: unknown empty substitution "
287 "variable - aborting");
290 if (len
> MAX_SUBSTITUTION_LEN
)
292 err("authpgsql: variable name too long "
293 "while parsing substitution. "
296 "%.*s...", MAX_SUBSTITUTION_LEN
, begin
);
300 for (vdp
=vdt
; vdp
->name
; vdp
++)
301 if (vdp
->size
== len
+1 &&
302 !strncmp(begin
, vdp
->name
, len
))
309 err("authpgsql: unknown substitution variable "
318 /* tom@minnesota.com */
319 static int ParsePlugin_counter (const char *p
, size_t length
, void *vp
)
321 if (!p
|| !vp
|| length
< 0)
323 err("authpgsql: bad arguments while counting "
328 *((size_t *)vp
) += length
;
333 /* tom@minnesota.com */
334 static int ParsePlugin_builder (const char *p
, size_t length
, void *vp
)
336 char **strptr
= (char **) vp
;
338 if (!p
|| !vp
|| length
< 0)
340 err("authpgsql: bad arguments while building "
345 if (!length
) return 0;
346 memcpy ((void *) *strptr
, (void *) p
, length
);
352 /* tom@minnesota.com */
353 static int parse_core (const char *source
, struct var_data
*vdt
,
354 parsefunc outfn
, void *result
)
358 const char *p
, *q
, *e
,
361 struct var_data
*v_ptr
;
367 err("authpgsql: no memory allocated for result "
368 "while parser core was invoked");
373 err("authpgsql: no substitution table found "
374 "while parser core was invoked");
379 while ( (p
=strstr(q
, SV_BEGIN_MARK
)) )
383 e
= strstr (p
, SV_END_MARK
);
386 err("authpgsql: syntax error in "
388 "- no closing symbol found! "
389 "bad variable begins with:"
390 "%.*s...", MAX_SUBSTITUTION_LEN
, p
);
396 ** __________sometext$(variable_name)_________
398 ** t_begin' t_end' `v_begin `v_end
402 v_begin
= p
+SV_BEGIN_LEN
; /* variable field ptr */
403 v_end
= e
-SV_END_LEN
; /* variable field last character */
404 v_size
= v_end
-v_begin
+1;/* variable field length */
406 t_begin
= q
; /* text field ptr */
407 t_end
= p
-1; /* text field last character */
408 t_size
= t_end
-t_begin
+1;/* text field length */
411 if ( (outfn (t_begin
, t_size
, result
)) == -1 )
414 /* work on variable */
415 v_ptr
= get_variable (v_begin
, v_size
, vdt
);
416 if (!v_ptr
) return -1;
418 enc
=malloc(strlen(v_ptr
->value
)*2+1);
423 PQescapeStringConn(pgconn
, enc
, v_ptr
->value
,
424 strlen(v_ptr
->value
), NULL
);
426 if ( (outfn (enc
, strlen(enc
), result
)) == -1 )
436 /* work on last part of text if any */
438 if ( (outfn (q
, strlen(q
), result
)) == -1 )
444 /* tom@minnesota.com */
445 static char *parse_string (const char *source
, struct var_data
*vdt
)
447 char *output_buf
= NULL
,
451 if (source
== NULL
|| *source
== '\0' ||
452 vdt
== NULL
|| vdt
[0].name
== NULL
)
454 err("authpgsql: source clause is empty "
455 "- this is critical error");
459 /* phase 1 - count and validate string */
460 if ( (parse_core (source
, vdt
, &ParsePlugin_counter
, &buf_size
)) != 0)
463 /* phase 2 - allocate memory */
464 output_buf
= malloc (buf_size
);
470 pass_buf
= output_buf
;
472 /* phase 3 - build the output string */
473 if ( (parse_core (source
, vdt
, &ParsePlugin_builder
, &pass_buf
)) != 0)
483 static char *get_localpart (const char *username
)
485 char *p
=strdup(username
);
499 static const char *get_domain (const char *username
, const char *defdomain
)
501 const char *p
=strchr(username
, '@');
509 /* tom@minnesota.com */
510 static char *parse_select_clause (const char *clause
, const char *username
,
511 const char *defdomain
,
514 char *localpart
, *ret
;
515 static struct var_data vd
[]={
516 {"local_part", NULL
, sizeof("local_part")},
517 {"domain", NULL
, sizeof("domain")},
518 {"service", NULL
, sizeof("service")},
521 if (clause
== NULL
|| *clause
== '\0' ||
522 !username
|| *username
== '\0')
525 localpart
=get_localpart(username
);
529 vd
[0].value
= localpart
;
530 vd
[1].value
= get_domain (username
, defdomain
);
537 vd
[2].value
= service
;
539 ret
=parse_string (clause
, vd
);
544 /* tom@minnesota.com */
545 static char *parse_chpass_clause (const char *clause
, const char *username
,
546 const char *defdomain
, const char *newpass
,
547 const char *newpass_crypt
)
549 char *localpart
, *ret
;
551 static struct var_data vd
[]={
552 {"local_part", NULL
, sizeof("local_part")},
553 {"domain", NULL
, sizeof("domain")},
554 {"newpass", NULL
, sizeof("newpass")},
555 {"newpass_crypt", NULL
, sizeof("newpass_crypt")},
558 if (clause
== NULL
|| *clause
== '\0' ||
559 !username
|| *username
== '\0' ||
560 !newpass
|| *newpass
== '\0' ||
561 !newpass_crypt
|| *newpass_crypt
== '\0') return NULL
;
563 localpart
=get_localpart(username
);
567 vd
[0].value
= localpart
;
568 vd
[1].value
= get_domain (username
, defdomain
);
569 vd
[2].value
= newpass
;
570 vd
[3].value
= newpass_crypt
;
572 if (!vd
[1].value
|| !vd
[2].value
|| !vd
[3].value
)
578 ret
=parse_string (clause
, vd
);
602 memset(&ui
, 0, sizeof(ui
));
605 struct authpgsqluserinfo
*auth_pgsql_getuserinfo(const char *username
,
608 const char *defdomain
, *select_clause
;
613 #define SELECT_QUERY "SELECT %s, %s, %s, %s, %s, %s, %s, %s, %s, %s FROM %s WHERE %s = '%s' %s%s%s", \
614 login_field, crypt_field, clear_field, \
615 uid_field, gid_field, home_field, maildir_field, \
619 user_table, login_field, username_escaped, \
620 where_pfix, where_clause, where_sfix
623 if (do_connect()) return (0);
628 fprintf(DEBUG,"1Leggo parametri\n");
631 select_clause
=read_env("PGSQL_SELECT_CLAUSE");
632 defdomain
=read_env("DEFAULT_DOMAIN");
633 if (!defdomain
) defdomain
="";
635 if (!select_clause
) /* tom@minnesota.com */
637 const char *user_table
,
650 const char *where_pfix
, *where_sfix
;
651 char *username_escaped
;
653 user_table
=read_env("PGSQL_USER_TABLE");
657 err("authpgsql: PGSQL_USER_TABLE not set in "
662 crypt_field
=read_env("PGSQL_CRYPT_PWFIELD");
663 clear_field
=read_env("PGSQL_CLEAR_PWFIELD");
664 name_field
=read_env("PGSQL_NAME_FIELD");
666 if (!crypt_field
&& !clear_field
)
668 err("authpgsql: PGSQL_CRYPT_PWFIELD and "
669 "PGSQL_CLEAR_PWFIELD not set in " AUTHPGSQLRC
".");
672 if (!crypt_field
) crypt_field
="''";
673 if (!clear_field
) clear_field
="''";
674 if (!name_field
) name_field
="''";
676 uid_field
= read_env("PGSQL_UID_FIELD");
677 if (!uid_field
) uid_field
= "uid";
679 gid_field
= read_env("PGSQL_GID_FIELD");
680 if (!gid_field
) gid_field
= "gid";
682 login_field
= read_env("PGSQL_LOGIN_FIELD");
683 if (!login_field
) login_field
= "id";
685 home_field
= read_env("PGSQL_HOME_FIELD");
686 if (!home_field
) home_field
= "home";
688 maildir_field
=read_env(service
&& strcmp(service
, "courier")==0
689 ? "PGSQL_DEFAULTDELIVERY"
690 : "PGSQL_MAILDIR_FIELD");
691 if (!maildir_field
) maildir_field
="''";
693 quota_field
=read_env("PGSQL_QUOTA_FIELD");
694 if (!quota_field
) quota_field
="''";
696 options_field
=read_env("PGSQL_AUXOPTIONS_FIELD");
697 if (!options_field
) options_field
="''";
699 where_clause
=read_env("PGSQL_WHERE_CLAUSE");
700 if (!where_clause
) where_clause
= "";
702 where_pfix
=where_sfix
="";
704 if (strcmp(where_clause
, ""))
710 username_escaped
=get_username_escaped(username
, defdomain
);
712 if (!username_escaped
)
715 query_size
=snprintf(dummy_buf
, 1, SELECT_QUERY
);
717 querybuf
=malloc(query_size
+1);
721 free(username_escaped
);
726 snprintf(querybuf
, query_size
+1, SELECT_QUERY
);
727 free(username_escaped
);
731 /* tom@minnesota.com */
732 querybuf
=parse_select_clause (select_clause
, username
,
736 DPRINTF("authpgsql: parse_select_clause failed (DEFAULT_DOMAIN not defined?)");
741 DPRINTF("SQL query: %s", querybuf
);
742 pgresult
= PQexec(pgconn
, querybuf
);
743 if (!pgresult
|| PQresultStatus(pgresult
) != PGRES_TUPLES_OK
)
745 DPRINTF("PQexec failed, reconnecting: %s", PQerrorMessage(pgconn
));
746 if (pgresult
) PQclear(pgresult
);
748 /* <o.blasnik@nextra.de> */
750 auth_pgsql_cleanup();
758 pgresult
= PQexec(pgconn
, querybuf
);
759 if (!pgresult
|| PQresultStatus(pgresult
) != PGRES_TUPLES_OK
)
761 DPRINTF("PQexec failed second time, giving up: %s", PQerrorMessage(pgconn
));
762 if (pgresult
) PQclear(pgresult
);
764 auth_pgsql_cleanup();
765 /* Server went down, that's OK,
766 ** try again next time.
773 if (PQntuples(pgresult
)>0)
776 int num_fields
= PQnfields(pgresult
);
780 DPRINTF("incomplete row, only %d fields returned",
786 t
=PQgetvalue(pgresult
,0,0);
787 if (t
&& t
[0]) ui
.username
=strdup(t
);
788 t
=PQgetvalue(pgresult
,0,1);
789 if (t
&& t
[0]) ui
.cryptpw
=strdup(t
);
790 t
=PQgetvalue(pgresult
,0,2);
791 if (t
&& t
[0]) ui
.clearpw
=strdup(t
);
792 t
=PQgetvalue(pgresult
,0,3);
794 (ui
.uid
=strtol(t
, &endp
, 10), endp
[0] != '\0'))
796 DPRINTF("invalid value for uid: '%s'",
801 t
=PQgetvalue(pgresult
,0,4);
803 (ui
.gid
=strtol(t
, &endp
, 10), endp
[0] != '\0'))
805 DPRINTF("invalid value for gid: '%s'",
810 t
=PQgetvalue(pgresult
,0,5);
815 DPRINTF("required value for 'home' (column 6) is missing");
819 t
=num_fields
> 6 ? PQgetvalue(pgresult
,0,6) : 0;
820 if (t
&& t
[0]) ui
.maildir
=strdup(t
);
821 t
=num_fields
> 7 ? PQgetvalue(pgresult
,0,7) : 0;
822 if (t
&& t
[0]) ui
.quota
=strdup(t
);
823 t
=num_fields
> 8 ? PQgetvalue(pgresult
,0,8) : 0;
824 if (t
&& t
[0]) ui
.fullname
=strdup(t
);
825 t
=num_fields
> 9 ? PQgetvalue(pgresult
,0,9) : 0;
826 if (t
&& t
[0]) ui
.options
=strdup(t
);
830 DPRINTF("zero rows returned");
839 int auth_pgsql_setpass(const char *user
, const char *pass
,
852 char *username_escaped
;
854 const char *clear_field
=NULL
;
855 const char *crypt_field
=NULL
;
856 const char *defdomain
=NULL
;
857 const char *where_clause
=NULL
;
858 const char *user_table
=NULL
;
859 const char *login_field
=NULL
;
860 const char *chpass_clause
=NULL
; /* tom@minnesota.com */
866 if (!(newpass_crypt
=authcryptpasswd(pass
, oldpass
)))
869 clear_escaped
=malloc(strlen(pass
)*2+1);
878 crypt_escaped
=malloc(strlen(newpass_crypt
)*2+1);
888 PQescapeStringConn(pgconn
, clear_escaped
, pass
, strlen(pass
), error
);
889 PQescapeStringConn(pgconn
, crypt_escaped
,
890 newpass_crypt
, strlen(newpass_crypt
), error
);
894 /* tom@minnesota.com */
895 chpass_clause
=read_env("PGSQL_CHPASS_CLAUSE");
896 defdomain
=read_env("DEFAULT_DOMAIN");
897 user_table
=read_env("PGSQL_USER_TABLE");
900 login_field
= read_env("PGSQL_LOGIN_FIELD");
901 if (!login_field
) login_field
= "id";
902 crypt_field
=read_env("PGSQL_CRYPT_PWFIELD");
903 clear_field
=read_env("PGSQL_CLEAR_PWFIELD");
904 where_clause
=read_env("PGSQL_WHERE_CLAUSE");
906 username_escaped
=get_username_escaped(user
, defdomain
);
908 if (!username_escaped
)
920 #define DEFAULT_SETPASS_UPDATE \
921 "UPDATE %s SET %s%s%s%s %s %s%s%s%s WHERE %s='%s' %s%s%s", \
923 *clear_field ? clear_field:"", \
924 *clear_field ? "='":"", \
925 *clear_field ? clear_escaped:"", \
926 *clear_field ? "'":"", \
928 *clear_field && *crypt_field ? ",":"", \
930 *crypt_field ? crypt_field:"", \
931 *crypt_field ? "='":"", \
932 *crypt_field ? crypt_escaped:"", \
933 *crypt_field ? "'":"", \
935 login_field, username_escaped, \
936 *where_clause ? " AND (":"", where_clause, \
937 *where_clause ? ")":""
940 sql_buf_size
=snprintf(dummy_buf
, 1, DEFAULT_SETPASS_UPDATE
);
942 sql_buf
=malloc(sql_buf_size
+1);
945 snprintf(sql_buf
, sql_buf_size
+1,
946 DEFAULT_SETPASS_UPDATE
);
948 free(username_escaped
);
952 sql_buf
=parse_chpass_clause(chpass_clause
,
965 if (courier_authdebug_login_level
>= 2)
967 DPRINTF("setpass SQL: %s", sql_buf
);
969 pgresult
=PQexec (pgconn
, sql_buf
);
970 if (!pgresult
|| PQresultStatus(pgresult
) != PGRES_COMMAND_OK
)
972 DPRINTF("setpass SQL failed");
974 auth_pgsql_cleanup();
984 void auth_pgsql_enumerate( void(*cb_func
)(const char *name
,
993 const char *select_clause
, *defdomain
;
998 if (do_connect()) return;
1002 select_clause
=read_env("PGSQL_ENUMERATE_CLAUSE");
1003 defdomain
=read_env("DEFAULT_DOMAIN");
1004 if (!defdomain
|| !defdomain
[0])
1005 defdomain
="*"; /* otherwise parse_select_clause fails */
1007 if (!select_clause
) /* tom@minnesota.com */
1009 const char *user_table
,
1020 user_table
=read_env("PGSQL_USER_TABLE");
1024 err("authpgsql: PGSQL_USER_TABLE not set in "
1029 uid_field
= read_env("PGSQL_UID_FIELD");
1030 if (!uid_field
) uid_field
= "uid";
1032 gid_field
= read_env("PGSQL_GID_FIELD");
1033 if (!gid_field
) gid_field
= "gid";
1035 login_field
= read_env("PGSQL_LOGIN_FIELD");
1036 if (!login_field
) login_field
= "id";
1038 home_field
= read_env("PGSQL_HOME_FIELD");
1039 if (!home_field
) home_field
= "home";
1041 maildir_field
=read_env("PGSQL_MAILDIR_FIELD");
1042 if (!maildir_field
) maildir_field
="''";
1044 options_field
=read_env("PGSQL_AUXOPTIONS_FIELD");
1045 if (!options_field
) options_field
="''";
1047 where_clause
=read_env("PGSQL_WHERE_CLAUSE");
1048 if (!where_clause
) where_clause
= "";
1050 #define DEFAULT_ENUMERATE_QUERY \
1051 "SELECT %s, %s, %s, %s, %s, %s FROM %s %s%s",\
1052 login_field, uid_field, gid_field, \
1053 home_field, maildir_field, \
1054 options_field, user_table, \
1055 *where_clause ? " WHERE ":"", \
1059 query_len
=snprintf(dummy_buf
, 1, DEFAULT_ENUMERATE_QUERY
);
1061 querybuf
=malloc(query_len
+1);
1069 snprintf(querybuf
, query_len
+1, DEFAULT_ENUMERATE_QUERY
);
1073 /* tom@minnesota.com */
1074 querybuf
=parse_select_clause (select_clause
, "*",
1075 defdomain
, "enumerate");
1078 DPRINTF("authpgsql: parse_select_clause failed");
1082 DPRINTF("authpgsql: enumerate query: %s", querybuf
);
1084 if (PQsendQuery(pgconn
, querybuf
) == 0)
1086 DPRINTF("PQsendQuery failed, reconnecting: %s",PQerrorMessage(pgconn
));
1088 auth_pgsql_cleanup();
1096 if (PQsendQuery(pgconn
, querybuf
) == 0)
1098 DPRINTF("PQsendQuery failed second time, giving up: %s",PQerrorMessage(pgconn
));
1100 auth_pgsql_cleanup();
1106 while ((pgresult
= PQgetResult(pgconn
)) != NULL
)
1108 if (PQresultStatus(pgresult
) != PGRES_TUPLES_OK
)
1110 DPRINTF("pgsql error during enumeration: %s",PQerrorMessage(pgconn
));
1115 for (n
=PQntuples(pgresult
), i
=0; i
<n
; i
++)
1117 const char *username
;
1120 const char *homedir
;
1121 const char *maildir
;
1122 const char *options
;
1124 username
=PQgetvalue(pgresult
,i
,0);
1125 uid
=atol(PQgetvalue(pgresult
,i
,1));
1126 gid
=atol(PQgetvalue(pgresult
,i
,2));
1127 homedir
=PQgetvalue(pgresult
,i
,3);
1128 maildir
=PQgetvalue(pgresult
,i
,4);
1129 options
=PQgetvalue(pgresult
,i
,5);
1131 if (!username
|| !*username
|| !homedir
|| !*homedir
)
1134 if (maildir
&& !*maildir
)
1137 (*cb_func
)(username
, uid
, gid
, homedir
,
1138 maildir
, options
, void_arg
);
1143 /* FIXME: is it possible that a NULL result from PQgetResult could
1144 indicate an error rather than EOF? The documentation is not clear */
1145 (*cb_func
)(NULL
, 0, 0, NULL
, NULL
, NULL
, void_arg
);