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)
32 /* tom@minnesota.com */
39 /* tom@minnesota.com */
40 typedef int (*parsefunc
)(const char *, size_t, void *);
42 static const char *read_env(const char *env
)
44 static char *pgsqlauth
=0;
45 static size_t pgsqlauth_size
=0;
52 FILE *f
=fopen(AUTHPGSQLRC
, "r");
56 if (fstat(fileno(f
), &buf
) ||
57 (pgsqlauth
=malloc(buf
.st_size
+2)) == 0)
62 if (fread(pgsqlauth
, buf
.st_size
, 1, f
) != 1)
69 pgsqlauth
[pgsqlauth_size
=buf
.st_size
]=0;
71 for (i
=0; i
<pgsqlauth_size
; i
++)
72 if (pgsqlauth
[i
] == '\n')
73 { /* tom@minnesota.com */
74 if (!i
|| pgsqlauth
[i
-1] != '\\')
80 pgsqlauth
[i
]=pgsqlauth
[i
-1]= ' ';
86 for (i
=0; i
<pgsqlauth_size
; )
89 if (memcmp(p
, env
, l
) == 0 &&
90 isspace((int)(unsigned char)p
[l
]))
93 while (*p
&& *p
!= '\n' &&
94 isspace((int)(unsigned char)*p
))
99 while (i
< pgsqlauth_size
)
100 if (pgsqlauth
[i
++] == 0) break;
103 if (i
< pgsqlauth_size
)
108 static PGresult
*pgresult
=0;
110 static PGconn
*pgconn
=0;
113 * session variables can be set once for the whole session
116 static void set_session_options(void)
118 const char *character_set
=read_env("PGSQL_CHARACTER_SET"), *check
;
122 PQsetClientEncoding(pgconn
, character_set
);
123 check
= pg_encoding_to_char(PQclientEncoding(pgconn
));
124 if (strcmp(character_set
, check
) != 0)
126 err("Cannot set Postgresql character set \"%s\", working with \"%s\"\n",
127 character_set
, check
);
131 DPRINTF("Install of a character set for Postgresql: %s", character_set
);
139 static FILE *DEBUG=0;
142 static int do_connect()
146 const char *password
;
147 const char *database
;
148 const char *server_port
=0;
149 const char *server_opt
=0;
152 DEBUG=fopen("/tmp/courier.debug","a");
154 fprintf(DEBUG,"Apro il DB!\n");
159 ** Periodically detect dead connections.
163 static time_t last_time
=0;
168 if (t_check
< last_time
)
169 last_time
=t_check
; /* System clock changed */
171 if (t_check
< last_time
+ 60)
176 if (PQstatus(pgconn
) == CONNECTION_OK
) return (0);
178 DPRINTF("authpgsqllib: PQstatus failed, connection lost");
183 server
=read_env("PGSQL_HOST");
184 server_port
=read_env("PGSQL_PORT");
185 userid
=read_env("PGSQL_USERNAME");
186 password
=read_env("PGSQL_PASSWORD");
187 database
=read_env("PGSQL_DATABASE");
188 server_opt
=read_env("PGSQL_OPT");
191 fprintf(DEBUG,"Letti i parametri!\n");
197 err("authpgsql: PGSQL_USERNAME not set in "
204 err("authpgsql: PGSQL_DATABASE not set in "
210 fprintf(DEBUG,"Connecting to db:%s:%s\%s user=%s pass=%s\n",server,server_port,database,userid,password);
213 pgconn
= PQsetdbLogin(server
, server_port
, server_opt
, NULL
, database
,userid
,password
);
215 if (PQstatus(pgconn
) == CONNECTION_BAD
)
217 err("Connection to server '%s' userid '%s' database '%s' failed.",
218 server
? server
: "<null>",
219 userid
? userid
: "<null>",
221 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
622 if (do_connect()) return (0);
627 fprintf(DEBUG,"1Leggo parametri\n");
630 select_clause
=read_env("PGSQL_SELECT_CLAUSE");
631 defdomain
=read_env("DEFAULT_DOMAIN");
632 if (!defdomain
) defdomain
="";
634 if (!select_clause
) /* tom@minnesota.com */
636 const char *user_table
,
649 const char *where_pfix
, *where_sfix
;
650 char *username_escaped
;
652 user_table
=read_env("PGSQL_USER_TABLE");
656 err("authpgsql: PGSQL_USER_TABLE not set in "
661 crypt_field
=read_env("PGSQL_CRYPT_PWFIELD");
662 clear_field
=read_env("PGSQL_CLEAR_PWFIELD");
663 name_field
=read_env("PGSQL_NAME_FIELD");
665 if (!crypt_field
&& !clear_field
)
667 err("authpgsql: PGSQL_CRYPT_PWFIELD and "
668 "PGSQL_CLEAR_PWFIELD not set in " AUTHPGSQLRC
".");
671 if (!crypt_field
) crypt_field
="''";
672 if (!clear_field
) clear_field
="''";
673 if (!name_field
) name_field
="''";
675 uid_field
= read_env("PGSQL_UID_FIELD");
676 if (!uid_field
) uid_field
= "uid";
678 gid_field
= read_env("PGSQL_GID_FIELD");
679 if (!gid_field
) gid_field
= "gid";
681 login_field
= read_env("PGSQL_LOGIN_FIELD");
682 if (!login_field
) login_field
= "id";
684 home_field
= read_env("PGSQL_HOME_FIELD");
685 if (!home_field
) home_field
= "home";
687 maildir_field
=read_env(service
&& strcmp(service
, "courier")==0
688 ? "PGSQL_DEFAULTDELIVERY"
689 : "PGSQL_MAILDIR_FIELD");
690 if (!maildir_field
) maildir_field
="''";
692 quota_field
=read_env("PGSQL_QUOTA_FIELD");
693 if (!quota_field
) quota_field
="''";
695 options_field
=read_env("PGSQL_AUXOPTIONS_FIELD");
696 if (!options_field
) options_field
="''";
698 where_clause
=read_env("PGSQL_WHERE_CLAUSE");
699 if (!where_clause
) where_clause
= "";
701 where_pfix
=where_sfix
="";
703 if (strcmp(where_clause
, ""))
709 username_escaped
=get_username_escaped(username
, defdomain
);
711 if (!username_escaped
)
714 query_size
=snprintf(dummy_buf
, 1, SELECT_QUERY
);
716 querybuf
=malloc(query_size
+1);
720 free(username_escaped
);
725 snprintf(querybuf
, query_size
+1, SELECT_QUERY
);
726 free(username_escaped
);
730 /* tom@minnesota.com */
731 querybuf
=parse_select_clause (select_clause
, username
,
735 DPRINTF("authpgsql: parse_select_clause failed (DEFAULT_DOMAIN not defined?)");
740 DPRINTF("SQL query: %s", querybuf
);
741 pgresult
= PQexec(pgconn
, querybuf
);
742 if (!pgresult
|| PQresultStatus(pgresult
) != PGRES_TUPLES_OK
)
744 DPRINTF("PQexec failed, reconnecting: %s", PQerrorMessage(pgconn
));
745 if (pgresult
) PQclear(pgresult
);
747 /* <o.blasnik@nextra.de> */
749 auth_pgsql_cleanup();
757 pgresult
= PQexec(pgconn
, querybuf
);
758 if (!pgresult
|| PQresultStatus(pgresult
) != PGRES_TUPLES_OK
)
760 DPRINTF("PQexec failed second time, giving up: %s", PQerrorMessage(pgconn
));
761 if (pgresult
) PQclear(pgresult
);
763 auth_pgsql_cleanup();
764 /* Server went down, that's OK,
765 ** try again next time.
772 if (PQntuples(pgresult
)>0)
775 int num_fields
= PQnfields(pgresult
);
779 DPRINTF("incomplete row, only %d fields returned",
785 t
=PQgetvalue(pgresult
,0,0);
786 if (t
&& t
[0]) ui
.username
=strdup(t
);
787 t
=PQgetvalue(pgresult
,0,1);
788 if (t
&& t
[0]) ui
.cryptpw
=strdup(t
);
789 t
=PQgetvalue(pgresult
,0,2);
790 if (t
&& t
[0]) ui
.clearpw
=strdup(t
);
791 t
=PQgetvalue(pgresult
,0,3);
793 (ui
.uid
=strtol(t
, &endp
, 10), endp
[0] != '\0'))
795 DPRINTF("invalid value for uid: '%s'",
800 t
=PQgetvalue(pgresult
,0,4);
802 (ui
.gid
=strtol(t
, &endp
, 10), endp
[0] != '\0'))
804 DPRINTF("invalid value for gid: '%s'",
809 t
=PQgetvalue(pgresult
,0,5);
814 DPRINTF("required value for 'home' (column 6) is missing");
818 t
=num_fields
> 6 ? PQgetvalue(pgresult
,0,6) : 0;
819 if (t
&& t
[0]) ui
.maildir
=strdup(t
);
820 t
=num_fields
> 7 ? PQgetvalue(pgresult
,0,7) : 0;
821 if (t
&& t
[0]) ui
.quota
=strdup(t
);
822 t
=num_fields
> 8 ? PQgetvalue(pgresult
,0,8) : 0;
823 if (t
&& t
[0]) ui
.fullname
=strdup(t
);
824 t
=num_fields
> 9 ? PQgetvalue(pgresult
,0,9) : 0;
825 if (t
&& t
[0]) ui
.options
=strdup(t
);
829 DPRINTF("zero rows returned");
838 int auth_pgsql_setpass(const char *user
, const char *pass
,
851 char *username_escaped
;
853 const char *clear_field
=NULL
;
854 const char *crypt_field
=NULL
;
855 const char *defdomain
=NULL
;
856 const char *where_clause
=NULL
;
857 const char *user_table
=NULL
;
858 const char *login_field
=NULL
;
859 const char *chpass_clause
=NULL
; /* tom@minnesota.com */
865 if (!(newpass_crypt
=authcryptpasswd(pass
, oldpass
)))
868 clear_escaped
=malloc(strlen(pass
)*2+1);
877 crypt_escaped
=malloc(strlen(newpass_crypt
)*2+1);
887 PQescapeStringConn(pgconn
, clear_escaped
, pass
, strlen(pass
), error
);
888 PQescapeStringConn(pgconn
, crypt_escaped
,
889 newpass_crypt
, strlen(newpass_crypt
), error
);
893 /* tom@minnesota.com */
894 chpass_clause
=read_env("PGSQL_CHPASS_CLAUSE");
895 defdomain
=read_env("DEFAULT_DOMAIN");
896 user_table
=read_env("PGSQL_USER_TABLE");
899 login_field
= read_env("PGSQL_LOGIN_FIELD");
900 if (!login_field
) login_field
= "id";
901 crypt_field
=read_env("PGSQL_CRYPT_PWFIELD");
902 clear_field
=read_env("PGSQL_CLEAR_PWFIELD");
903 where_clause
=read_env("PGSQL_WHERE_CLAUSE");
905 username_escaped
=get_username_escaped(user
, defdomain
);
907 if (!username_escaped
)
919 #define DEFAULT_SETPASS_UPDATE \
920 "UPDATE %s SET %s%s%s%s %s %s%s%s%s WHERE %s='%s' %s%s%s", \
922 *clear_field ? clear_field:"", \
923 *clear_field ? "='":"", \
924 *clear_field ? clear_escaped:"", \
925 *clear_field ? "'":"", \
927 *clear_field && *crypt_field ? ",":"", \
929 *crypt_field ? crypt_field:"", \
930 *crypt_field ? "='":"", \
931 *crypt_field ? crypt_escaped:"", \
932 *crypt_field ? "'":"", \
934 login_field, username_escaped, \
935 *where_clause ? " AND (":"", where_clause, \
936 *where_clause ? ")":""
939 sql_buf_size
=snprintf(dummy_buf
, 1, DEFAULT_SETPASS_UPDATE
);
941 sql_buf
=malloc(sql_buf_size
+1);
944 snprintf(sql_buf
, sql_buf_size
+1,
945 DEFAULT_SETPASS_UPDATE
);
947 free(username_escaped
);
951 sql_buf
=parse_chpass_clause(chpass_clause
,
964 if (courier_authdebug_login_level
>= 2)
966 DPRINTF("setpass SQL: %s", sql_buf
);
968 pgresult
=PQexec (pgconn
, sql_buf
);
969 if (!pgresult
|| PQresultStatus(pgresult
) != PGRES_COMMAND_OK
)
971 DPRINTF("setpass SQL failed");
973 auth_pgsql_cleanup();
983 void auth_pgsql_enumerate( void(*cb_func
)(const char *name
,
992 const char *select_clause
, *defdomain
;
999 (*cb_func
)(NULL
, 0, 0, NULL
, NULL
, NULL
, void_arg
);
1005 select_clause
=read_env("PGSQL_ENUMERATE_CLAUSE");
1006 defdomain
=read_env("DEFAULT_DOMAIN");
1007 if (!defdomain
|| !defdomain
[0])
1008 defdomain
="*"; /* otherwise parse_select_clause fails */
1010 if (!select_clause
) /* tom@minnesota.com */
1012 const char *user_table
,
1023 user_table
=read_env("PGSQL_USER_TABLE");
1027 err("authpgsql: PGSQL_USER_TABLE not set in "
1032 uid_field
= read_env("PGSQL_UID_FIELD");
1033 if (!uid_field
) uid_field
= "uid";
1035 gid_field
= read_env("PGSQL_GID_FIELD");
1036 if (!gid_field
) gid_field
= "gid";
1038 login_field
= read_env("PGSQL_LOGIN_FIELD");
1039 if (!login_field
) login_field
= "id";
1041 home_field
= read_env("PGSQL_HOME_FIELD");
1042 if (!home_field
) home_field
= "home";
1044 maildir_field
=read_env("PGSQL_MAILDIR_FIELD");
1045 if (!maildir_field
) maildir_field
="''";
1047 options_field
=read_env("PGSQL_AUXOPTIONS_FIELD");
1048 if (!options_field
) options_field
="''";
1050 where_clause
=read_env("PGSQL_WHERE_CLAUSE");
1051 if (!where_clause
) where_clause
= "";
1053 #define DEFAULT_ENUMERATE_QUERY \
1054 "SELECT %s, %s, %s, %s, %s, %s FROM %s %s%s",\
1055 login_field, uid_field, gid_field, \
1056 home_field, maildir_field, \
1057 options_field, user_table, \
1058 *where_clause ? " WHERE ":"", \
1062 query_len
=snprintf(dummy_buf
, 1, DEFAULT_ENUMERATE_QUERY
);
1064 querybuf
=malloc(query_len
+1);
1072 snprintf(querybuf
, query_len
+1, DEFAULT_ENUMERATE_QUERY
);
1076 /* tom@minnesota.com */
1077 querybuf
=parse_select_clause (select_clause
, "*",
1078 defdomain
, "enumerate");
1081 DPRINTF("authpgsql: parse_select_clause failed");
1085 DPRINTF("authpgsql: enumerate query: %s", querybuf
);
1087 if (PQsendQuery(pgconn
, querybuf
) == 0)
1089 DPRINTF("PQsendQuery failed, reconnecting: %s",PQerrorMessage(pgconn
));
1091 auth_pgsql_cleanup();
1099 if (PQsendQuery(pgconn
, querybuf
) == 0)
1101 DPRINTF("PQsendQuery failed second time, giving up: %s",PQerrorMessage(pgconn
));
1103 auth_pgsql_cleanup();
1109 while ((pgresult
= PQgetResult(pgconn
)) != NULL
)
1111 if (PQresultStatus(pgresult
) != PGRES_TUPLES_OK
)
1113 DPRINTF("pgsql error during enumeration: %s",PQerrorMessage(pgconn
));
1118 for (n
=PQntuples(pgresult
), i
=0; i
<n
; i
++)
1120 const char *username
;
1123 const char *homedir
;
1124 const char *maildir
;
1125 const char *options
;
1127 username
=PQgetvalue(pgresult
,i
,0);
1128 uid
=atol(PQgetvalue(pgresult
,i
,1));
1129 gid
=atol(PQgetvalue(pgresult
,i
,2));
1130 homedir
=PQgetvalue(pgresult
,i
,3);
1131 maildir
=PQgetvalue(pgresult
,i
,4);
1132 options
=PQgetvalue(pgresult
,i
,5);
1134 if (!username
|| !*username
|| !homedir
|| !*homedir
)
1137 if (maildir
&& !*maildir
)
1140 (*cb_func
)(username
, uid
, gid
, homedir
,
1141 maildir
, options
, void_arg
);
1146 /* FIXME: is it possible that a NULL result from PQgetResult could
1147 indicate an error rather than EOF? The documentation is not clear */
1148 (*cb_func
)(NULL
, 0, 0, NULL
, NULL
, NULL
, void_arg
);