d9898ee8 |
1 | /* |
2 | ** Copyright 2000-2004 Double Precision, Inc. See COPYING for |
3 | ** distribution information. |
4 | */ |
5 | |
6 | |
7 | #include <stdio.h> |
8 | #include <stdlib.h> |
9 | #include <string.h> |
10 | #include <unistd.h> |
11 | #include <ctype.h> |
12 | #include <sys/types.h> |
13 | #include <sys/stat.h> |
14 | #include <libpq-fe.h> |
15 | #include <time.h> |
16 | |
17 | #include "authpgsql.h" |
18 | #include "authpgsqlrc.h" |
19 | #include "auth.h" |
20 | #include "courierauthdebug.h" |
21 | |
22 | #define err courier_auth_err |
23 | |
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) |
30 | |
31 | static const char rcsid[]="$Id: authpgsqllib.c,v 1.18 2006/10/28 19:22:52 mrsam Exp $"; |
32 | |
33 | /* tom@minnesota.com */ |
34 | struct var_data { |
35 | const char *name; |
36 | const char *value; |
37 | const size_t size; |
d9898ee8 |
38 | } ; |
39 | |
40 | /* tom@minnesota.com */ |
41 | typedef int (*parsefunc)(const char *, size_t, void *); |
42 | |
43 | static const char *read_env(const char *env) |
44 | { |
45 | static char *pgsqlauth=0; |
46 | static size_t pgsqlauth_size=0; |
47 | size_t i; |
48 | char *p=0; |
49 | int l=strlen(env); |
50 | |
51 | if (!pgsqlauth) |
52 | { |
53 | FILE *f=fopen(AUTHPGSQLRC, "r"); |
54 | struct stat buf; |
55 | |
56 | if (!f) return (0); |
57 | if (fstat(fileno(f), &buf) || |
58 | (pgsqlauth=malloc(buf.st_size+2)) == 0) |
59 | { |
60 | fclose(f); |
61 | return (0); |
62 | } |
63 | if (fread(pgsqlauth, buf.st_size, 1, f) != 1) |
64 | { |
65 | free(pgsqlauth); |
66 | pgsqlauth=0; |
67 | fclose(f); |
68 | return (0); |
69 | } |
70 | pgsqlauth[pgsqlauth_size=buf.st_size]=0; |
71 | |
72 | for (i=0; i<pgsqlauth_size; i++) |
73 | if (pgsqlauth[i] == '\n') |
74 | { /* tom@minnesota.com */ |
75 | if (!i || pgsqlauth[i-1] != '\\') |
76 | { |
77 | pgsqlauth[i]='\0'; |
78 | } |
79 | else |
80 | { |
81 | pgsqlauth[i]=pgsqlauth[i-1]= ' '; |
82 | } |
83 | } |
84 | fclose(f); |
85 | } |
86 | |
87 | for (i=0; i<pgsqlauth_size; ) |
88 | { |
89 | p=pgsqlauth+i; |
90 | if (memcmp(p, env, l) == 0 && |
91 | isspace((int)(unsigned char)p[l])) |
92 | { |
93 | p += l; |
94 | while (*p && *p != '\n' && |
95 | isspace((int)(unsigned char)*p)) |
96 | ++p; |
97 | break; |
98 | } |
99 | |
100 | while (i < pgsqlauth_size) |
101 | if (pgsqlauth[i++] == 0) break; |
102 | } |
103 | |
104 | if (i < pgsqlauth_size) |
105 | return (p); |
106 | return (0); |
107 | } |
108 | |
109 | static PGresult *pgresult=0; |
110 | |
111 | static PGconn *pgconn=0; |
112 | |
0fde1ce3 |
113 | /* |
114 | * session variables can be set once for the whole session |
115 | */ |
116 | |
117 | static void set_session_options(void) |
118 | { |
119 | const char *character_set=read_env("PGSQL_CHARACTER_SET"), *check; |
120 | |
121 | if (character_set) |
122 | { |
123 | PQsetClientEncoding(pgconn, character_set); |
124 | check = pg_encoding_to_char(PQclientEncoding(pgconn)); |
125 | if (strcmp(character_set, check) != 0) |
126 | { |
127 | err("Cannot set Postgresql character set \"%s\", working with \"%s\"\n", |
128 | character_set, check); |
129 | } |
130 | else |
131 | { |
132 | DPRINTF("Install of a character set for Postgresql: %s", character_set); |
133 | } |
134 | } |
135 | } |
136 | |
137 | |
138 | |
d9898ee8 |
139 | /* |
140 | static FILE *DEBUG=0; |
141 | */ |
142 | |
143 | static int do_connect() |
144 | { |
145 | const char *server; |
146 | const char *userid; |
147 | const char *password; |
148 | const char *database; |
149 | const char *server_port=0; |
150 | const char *server_opt=0; |
151 | /* |
152 | if (!DEBUG) { |
153 | DEBUG=fopen("/tmp/courier.debug","a"); |
154 | } |
155 | fprintf(DEBUG,"Apro il DB!\n"); |
156 | fflush(DEBUG); |
157 | */ |
158 | |
159 | /* |
160 | ** Periodically detect dead connections. |
161 | */ |
162 | if (pgconn) |
163 | { |
164 | static time_t last_time=0; |
165 | time_t t_check; |
166 | |
167 | time(&t_check); |
168 | |
169 | if (t_check < last_time) |
170 | last_time=t_check; /* System clock changed */ |
171 | |
172 | if (t_check < last_time + 60) |
173 | return (0); |
174 | |
175 | last_time=t_check; |
176 | |
177 | if (PQstatus(pgconn) == CONNECTION_OK) return (0); |
178 | |
179 | DPRINTF("authpgsqllib: PQstatus failed, connection lost"); |
180 | PQfinish(pgconn); |
181 | pgconn=0; |
182 | } |
183 | |
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"); |
190 | |
191 | /* |
192 | fprintf(DEBUG,"Letti i parametri!\n"); |
193 | fflush(DEBUG); |
194 | */ |
195 | |
196 | if (!userid) |
197 | { |
198 | err("authpgsql: PGSQL_USERNAME not set in " |
199 | AUTHPGSQLRC "."); |
200 | return (-1); |
201 | } |
202 | |
203 | if (!database) |
204 | { |
205 | err("authpgsql: PGSQL_DATABASE not set in " |
206 | AUTHPGSQLRC "."); |
207 | return (-1); |
208 | } |
209 | |
210 | /* |
211 | fprintf(DEBUG,"Connecting to db:%s:%s\%s user=%s pass=%s\n",server,server_port,database,userid,password); |
212 | fflush(DEBUG); |
213 | */ |
214 | pgconn = PQsetdbLogin(server, server_port, server_opt, NULL , database,userid,password); |
215 | |
216 | if (PQstatus(pgconn) == CONNECTION_BAD) |
217 | { |
218 | err("Connection to server '%s' userid '%s' database '%s' failed.", |
219 | server ? server : "<null>", |
220 | userid ? userid : "<null>", |
221 | database); |
222 | err("%s", PQerrorMessage(pgconn)); |
223 | pgconn=0; |
224 | return -1; |
225 | } |
226 | /* |
227 | fprintf(DEBUG,"Connected!\n"); |
228 | fflush(DEBUG); |
229 | */ |
230 | |
0fde1ce3 |
231 | set_session_options(); |
d9898ee8 |
232 | return 0; |
233 | |
234 | } |
235 | |
236 | void auth_pgsql_cleanup() |
237 | { |
238 | if (pgconn) |
239 | { |
240 | PQfinish(pgconn); |
241 | pgconn=0; |
242 | } |
243 | } |
244 | |
245 | static struct authpgsqluserinfo ui={0, 0, 0, 0, 0, 0, 0, 0}; |
246 | |
0fde1ce3 |
247 | static char *get_username_escaped(const char *username, |
248 | const char *defdomain) |
d9898ee8 |
249 | { |
0fde1ce3 |
250 | char *username_escaped; |
251 | int *error = NULL; |
252 | |
253 | if (!defdomain) |
254 | defdomain=""; |
255 | |
256 | username_escaped=malloc(strlen(username)*2+2+strlen(defdomain)); |
257 | |
258 | if (!username_escaped) |
259 | { |
260 | perror("malloc"); |
261 | return 0; |
262 | } |
263 | |
264 | PQescapeStringConn(pgconn, username_escaped, username, strlen(username), error); |
265 | |
266 | if (strchr(username, '@') == 0 && *defdomain) |
267 | strcat(strcat(username_escaped, "@"), defdomain); |
268 | |
269 | return username_escaped; |
d9898ee8 |
270 | } |
271 | |
272 | /* tom@minnesota.com */ |
273 | static struct var_data *get_variable (const char *begin, size_t len, |
0fde1ce3 |
274 | struct var_data *vdt) |
d9898ee8 |
275 | { |
276 | struct var_data *vdp; |
277 | |
278 | if (!begin || !vdt) /* should never happend */ |
279 | { |
280 | err("authpgsql: critical error while " |
281 | "parsing substitution variable"); |
282 | return NULL; |
283 | } |
284 | if (len < 1) |
285 | { |
286 | err("authpgsql: unknown empty substitution " |
287 | "variable - aborting"); |
288 | return NULL; |
289 | } |
290 | if (len > MAX_SUBSTITUTION_LEN) |
291 | { |
292 | err("authpgsql: variable name too long " |
293 | "while parsing substitution. " |
294 | "name begins with " |
295 | SV_BEGIN_MARK |
296 | "%.*s...", MAX_SUBSTITUTION_LEN, begin); |
297 | return NULL; |
298 | } |
299 | |
300 | for (vdp=vdt; vdp->name; vdp++) |
301 | if (vdp->size == len+1 && |
302 | !strncmp(begin, vdp->name, len)) |
303 | { |
304 | if (!vdp->value) |
305 | vdp->value = ""; |
d9898ee8 |
306 | return vdp; |
307 | } |
308 | |
309 | err("authpgsql: unknown substitution variable " |
310 | SV_BEGIN_MARK |
311 | "%.*s" |
312 | SV_END_MARK |
313 | , (int)len, begin); |
314 | |
315 | return NULL; |
316 | } |
317 | |
318 | /* tom@minnesota.com */ |
319 | static int ParsePlugin_counter (const char *p, size_t length, void *vp) |
320 | { |
321 | if (!p || !vp || length < 0) |
322 | { |
323 | err("authpgsql: bad arguments while counting " |
324 | "query string"); |
325 | return -1; |
326 | } |
327 | |
328 | *((size_t *)vp) += length; |
329 | |
330 | return 0; |
331 | } |
332 | |
333 | /* tom@minnesota.com */ |
334 | static int ParsePlugin_builder (const char *p, size_t length, void *vp) |
335 | { |
336 | char **strptr = (char **) vp; |
337 | |
338 | if (!p || !vp || length < 0) |
339 | { |
340 | err("authpgsql: bad arguments while building " |
341 | "query string"); |
342 | return -1; |
343 | } |
344 | |
345 | if (!length) return 0; |
346 | memcpy ((void *) *strptr, (void *) p, length); |
347 | *strptr += length; |
348 | |
349 | return 0; |
350 | } |
351 | |
352 | /* tom@minnesota.com */ |
353 | static int parse_core (const char *source, struct var_data *vdt, |
354 | parsefunc outfn, void *result) |
355 | { |
356 | size_t v_size = 0, |
357 | t_size = 0; |
358 | const char *p, *q, *e, |
359 | *v_begin, *v_end, |
360 | *t_begin, *t_end; |
361 | struct var_data *v_ptr; |
362 | |
363 | if (!source) |
364 | source = ""; |
365 | if (!result) |
366 | { |
367 | err("authpgsql: no memory allocated for result " |
368 | "while parser core was invoked"); |
369 | return -1; |
370 | } |
371 | if (!vdt) |
372 | { |
373 | err("authpgsql: no substitution table found " |
374 | "while parser core was invoked"); |
375 | return -1; |
376 | } |
377 | |
378 | q = source; |
379 | while ( (p=strstr(q, SV_BEGIN_MARK)) ) |
380 | { |
0fde1ce3 |
381 | char *enc; |
382 | |
d9898ee8 |
383 | e = strstr (p, SV_END_MARK); |
384 | if (!e) |
385 | { |
386 | err("authpgsql: syntax error in " |
387 | "substitution " |
388 | "- no closing symbol found! " |
389 | "bad variable begins with:" |
390 | "%.*s...", MAX_SUBSTITUTION_LEN, p); |
391 | return -1; |
392 | } |
393 | |
394 | /* |
395 | ** |
396 | ** __________sometext$(variable_name)_________ |
397 | ** | | | | |
398 | ** t_begin' t_end' `v_begin `v_end |
399 | ** |
400 | */ |
401 | |
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 */ |
405 | |
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 */ |
409 | |
410 | /* work on text */ |
411 | if ( (outfn (t_begin, t_size, result)) == -1 ) |
412 | return -1; |
413 | |
414 | /* work on variable */ |
415 | v_ptr = get_variable (v_begin, v_size, vdt); |
416 | if (!v_ptr) return -1; |
0fde1ce3 |
417 | |
418 | enc=malloc(strlen(v_ptr->value)*2+1); |
419 | |
420 | if (!enc) |
d9898ee8 |
421 | return -1; |
0fde1ce3 |
422 | |
423 | PQescapeStringConn(pgconn, enc, v_ptr->value, |
424 | strlen(v_ptr->value), NULL); |
425 | |
426 | if ( (outfn (enc, strlen(enc), result)) == -1 ) |
427 | { |
428 | free(enc); |
429 | return -1; |
430 | } |
431 | free(enc); |
432 | |
d9898ee8 |
433 | q = e + 1; |
434 | } |
435 | |
436 | /* work on last part of text if any */ |
437 | if (*q != '\0') |
438 | if ( (outfn (q, strlen(q), result)) == -1 ) |
439 | return -1; |
440 | |
441 | return 0; |
442 | } |
443 | |
444 | /* tom@minnesota.com */ |
445 | static char *parse_string (const char *source, struct var_data *vdt) |
446 | { |
d9898ee8 |
447 | char *output_buf = NULL, |
448 | *pass_buf = NULL; |
449 | size_t buf_size = 2; |
450 | |
451 | if (source == NULL || *source == '\0' || |
452 | vdt == NULL || vdt[0].name == NULL) |
453 | { |
454 | err("authpgsql: source clause is empty " |
455 | "- this is critical error"); |
456 | return NULL; |
457 | } |
458 | |
d9898ee8 |
459 | /* phase 1 - count and validate string */ |
460 | if ( (parse_core (source, vdt, &ParsePlugin_counter, &buf_size)) != 0) |
461 | return NULL; |
462 | |
463 | /* phase 2 - allocate memory */ |
464 | output_buf = malloc (buf_size); |
465 | if (!output_buf) |
466 | { |
467 | perror ("malloc"); |
468 | return NULL; |
469 | } |
470 | pass_buf = output_buf; |
471 | |
472 | /* phase 3 - build the output string */ |
473 | if ( (parse_core (source, vdt, &ParsePlugin_builder, &pass_buf)) != 0) |
474 | { |
475 | free (output_buf); |
476 | return NULL; |
477 | } |
478 | *pass_buf = '\0'; |
479 | |
480 | return output_buf; |
481 | } |
482 | |
0fde1ce3 |
483 | static char *get_localpart (const char *username) |
d9898ee8 |
484 | { |
0fde1ce3 |
485 | char *p=strdup(username); |
486 | char *q; |
d9898ee8 |
487 | |
0fde1ce3 |
488 | if (!p) |
489 | return 0; |
d9898ee8 |
490 | |
0fde1ce3 |
491 | q=strchr(p, '@'); |
d9898ee8 |
492 | |
0fde1ce3 |
493 | if (q) |
494 | *q=0; |
d9898ee8 |
495 | |
0fde1ce3 |
496 | return p; |
d9898ee8 |
497 | } |
498 | |
0fde1ce3 |
499 | static const char *get_domain (const char *username, const char *defdomain) |
d9898ee8 |
500 | { |
0fde1ce3 |
501 | const char *p=strchr(username, '@'); |
d9898ee8 |
502 | |
0fde1ce3 |
503 | if (p) |
504 | return p+1; |
505 | |
506 | return defdomain; |
d9898ee8 |
507 | } |
508 | |
509 | /* tom@minnesota.com */ |
510 | static char *parse_select_clause (const char *clause, const char *username, |
511 | const char *defdomain, |
512 | const char *service) |
513 | { |
0fde1ce3 |
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")}, |
519 | {NULL, NULL, 0}}; |
d9898ee8 |
520 | |
521 | if (clause == NULL || *clause == '\0' || |
522 | !username || *username == '\0') |
523 | return NULL; |
524 | |
0fde1ce3 |
525 | localpart=get_localpart(username); |
526 | if (!localpart) |
527 | return NULL; |
528 | |
529 | vd[0].value = localpart; |
d9898ee8 |
530 | vd[1].value = get_domain (username, defdomain); |
0fde1ce3 |
531 | |
532 | if (!vd[1].value) |
533 | { |
534 | free(localpart); |
d9898ee8 |
535 | return NULL; |
0fde1ce3 |
536 | } |
d9898ee8 |
537 | vd[2].value = service; |
538 | |
0fde1ce3 |
539 | ret=parse_string (clause, vd); |
540 | free(localpart); |
541 | return ret; |
d9898ee8 |
542 | } |
543 | |
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) |
548 | { |
0fde1ce3 |
549 | char *localpart, *ret; |
550 | |
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")}, |
556 | {NULL, NULL, 0}}; |
d9898ee8 |
557 | |
558 | if (clause == NULL || *clause == '\0' || |
559 | !username || *username == '\0' || |
560 | !newpass || *newpass == '\0' || |
561 | !newpass_crypt || *newpass_crypt == '\0') return NULL; |
562 | |
0fde1ce3 |
563 | localpart=get_localpart(username); |
564 | if (!localpart) |
565 | return NULL; |
566 | |
567 | vd[0].value = localpart; |
d9898ee8 |
568 | vd[1].value = get_domain (username, defdomain); |
0fde1ce3 |
569 | vd[2].value = newpass; |
570 | vd[3].value = newpass_crypt; |
d9898ee8 |
571 | |
0fde1ce3 |
572 | if (!vd[1].value || !vd[2].value || !vd[3].value) |
573 | { |
574 | free(localpart); |
575 | return NULL; |
576 | } |
d9898ee8 |
577 | |
0fde1ce3 |
578 | ret=parse_string (clause, vd); |
579 | free(localpart); |
580 | return ret; |
d9898ee8 |
581 | } |
582 | |
583 | static void initui() |
584 | { |
585 | |
586 | if (ui.username) |
587 | free(ui.username); |
588 | if (ui.cryptpw) |
589 | free(ui.cryptpw); |
590 | if (ui.clearpw) |
591 | free(ui.clearpw); |
592 | if (ui.home) |
593 | free(ui.home); |
594 | if (ui.maildir) |
595 | free(ui.maildir); |
596 | if (ui.quota) |
597 | free(ui.quota); |
598 | if (ui.fullname) |
599 | free(ui.fullname); |
600 | if (ui.options) |
601 | free(ui.options); |
602 | memset(&ui, 0, sizeof(ui)); |
603 | } |
604 | |
605 | struct authpgsqluserinfo *auth_pgsql_getuserinfo(const char *username, |
606 | const char *service) |
607 | { |
0fde1ce3 |
608 | const char *defdomain, *select_clause; |
609 | char *querybuf; |
610 | size_t query_size; |
611 | char dummy_buf[1]; |
612 | |
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, \ |
616 | quota_field, \ |
617 | name_field, \ |
618 | options_field, \ |
619 | user_table, login_field, username_escaped, \ |
620 | where_pfix, where_clause, where_sfix |
d9898ee8 |
621 | |
d9898ee8 |
622 | |
623 | if (do_connect()) return (0); |
624 | |
625 | initui(); |
626 | |
627 | /* |
628 | fprintf(DEBUG,"1Leggo parametri\n"); |
629 | fflush(DEBUG); |
630 | */ |
631 | select_clause=read_env("PGSQL_SELECT_CLAUSE"); |
632 | defdomain=read_env("DEFAULT_DOMAIN"); |
633 | if (!defdomain) defdomain=""; |
634 | |
635 | if (!select_clause) /* tom@minnesota.com */ |
636 | { |
637 | const char *user_table, |
638 | *crypt_field, |
639 | *clear_field, |
640 | *name_field, |
641 | *uid_field, |
642 | *gid_field, |
643 | *login_field, |
644 | *home_field, |
645 | *maildir_field, |
646 | *quota_field, |
647 | *options_field, |
648 | *where_clause; |
649 | |
0fde1ce3 |
650 | const char *where_pfix, *where_sfix; |
651 | char *username_escaped; |
652 | |
d9898ee8 |
653 | user_table=read_env("PGSQL_USER_TABLE"); |
654 | |
655 | if (!user_table) |
656 | { |
657 | err("authpgsql: PGSQL_USER_TABLE not set in " |
658 | AUTHPGSQLRC "."); |
659 | return (0); |
660 | } |
661 | |
662 | crypt_field=read_env("PGSQL_CRYPT_PWFIELD"); |
663 | clear_field=read_env("PGSQL_CLEAR_PWFIELD"); |
664 | name_field=read_env("PGSQL_NAME_FIELD"); |
665 | |
666 | if (!crypt_field && !clear_field) |
667 | { |
668 | err("authpgsql: PGSQL_CRYPT_PWFIELD and " |
669 | "PGSQL_CLEAR_PWFIELD not set in " AUTHPGSQLRC "."); |
670 | return (0); |
671 | } |
672 | if (!crypt_field) crypt_field="''"; |
673 | if (!clear_field) clear_field="''"; |
674 | if (!name_field) name_field="''"; |
675 | |
676 | uid_field = read_env("PGSQL_UID_FIELD"); |
677 | if (!uid_field) uid_field = "uid"; |
678 | |
679 | gid_field = read_env("PGSQL_GID_FIELD"); |
680 | if (!gid_field) gid_field = "gid"; |
681 | |
682 | login_field = read_env("PGSQL_LOGIN_FIELD"); |
683 | if (!login_field) login_field = "id"; |
684 | |
685 | home_field = read_env("PGSQL_HOME_FIELD"); |
686 | if (!home_field) home_field = "home"; |
687 | |
688 | maildir_field=read_env(service && strcmp(service, "courier")==0 |
689 | ? "PGSQL_DEFAULTDELIVERY" |
690 | : "PGSQL_MAILDIR_FIELD"); |
691 | if (!maildir_field) maildir_field="''"; |
692 | |
693 | quota_field=read_env("PGSQL_QUOTA_FIELD"); |
694 | if (!quota_field) quota_field="''"; |
695 | |
696 | options_field=read_env("PGSQL_AUXOPTIONS_FIELD"); |
697 | if (!options_field) options_field="''"; |
698 | |
699 | where_clause=read_env("PGSQL_WHERE_CLAUSE"); |
700 | if (!where_clause) where_clause = ""; |
701 | |
0fde1ce3 |
702 | where_pfix=where_sfix=""; |
d9898ee8 |
703 | |
0fde1ce3 |
704 | if (strcmp(where_clause, "")) |
d9898ee8 |
705 | { |
0fde1ce3 |
706 | where_pfix=" AND ("; |
707 | where_sfix=")"; |
d9898ee8 |
708 | } |
709 | |
0fde1ce3 |
710 | username_escaped=get_username_escaped(username, defdomain); |
d9898ee8 |
711 | |
0fde1ce3 |
712 | if (!username_escaped) |
713 | return 0; |
714 | |
715 | query_size=snprintf(dummy_buf, 1, SELECT_QUERY); |
716 | |
717 | querybuf=malloc(query_size+1); |
718 | |
719 | if (!querybuf) |
720 | { |
721 | free(username_escaped); |
722 | perror("malloc"); |
723 | return 0; |
d9898ee8 |
724 | } |
0fde1ce3 |
725 | |
726 | snprintf(querybuf, query_size+1, SELECT_QUERY); |
727 | free(username_escaped); |
d9898ee8 |
728 | } |
729 | else |
730 | { |
731 | /* tom@minnesota.com */ |
732 | querybuf=parse_select_clause (select_clause, username, |
733 | defdomain, service); |
734 | if (!querybuf) |
735 | { |
736 | DPRINTF("authpgsql: parse_select_clause failed (DEFAULT_DOMAIN not defined?)"); |
737 | return 0; |
738 | } |
739 | } |
740 | |
741 | DPRINTF("SQL query: %s", querybuf); |
742 | pgresult = PQexec(pgconn, querybuf); |
743 | if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) |
744 | { |
745 | DPRINTF("PQexec failed, reconnecting: %s", PQerrorMessage(pgconn)); |
746 | if (pgresult) PQclear(pgresult); |
747 | |
748 | /* <o.blasnik@nextra.de> */ |
749 | |
750 | auth_pgsql_cleanup(); |
751 | |
752 | if (do_connect()) |
753 | { |
754 | free(querybuf); |
755 | return (0); |
756 | } |
757 | |
758 | pgresult = PQexec(pgconn, querybuf); |
759 | if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) |
760 | { |
761 | DPRINTF("PQexec failed second time, giving up: %s", PQerrorMessage(pgconn)); |
762 | if (pgresult) PQclear(pgresult); |
763 | free(querybuf); |
764 | auth_pgsql_cleanup(); |
765 | /* Server went down, that's OK, |
766 | ** try again next time. |
767 | */ |
768 | return (0); |
769 | } |
770 | } |
771 | free(querybuf); |
772 | |
773 | if (PQntuples(pgresult)>0) |
774 | { |
775 | char *t, *endp; |
776 | int num_fields = PQnfields(pgresult); |
777 | |
778 | if (num_fields < 6) |
779 | { |
780 | DPRINTF("incomplete row, only %d fields returned", |
781 | num_fields); |
782 | PQclear(pgresult); |
783 | return 0; |
784 | } |
785 | |
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); |
793 | if (!t || !t[0] || |
794 | (ui.uid=strtol(t, &endp, 10), endp[0] != '\0')) |
795 | { |
796 | DPRINTF("invalid value for uid: '%s'", |
797 | t ? t : "<null>"); |
798 | PQclear(pgresult); |
799 | return 0; |
800 | } |
801 | t=PQgetvalue(pgresult,0,4); |
802 | if (!t || !t[0] || |
803 | (ui.gid=strtol(t, &endp, 10), endp[0] != '\0')) |
804 | { |
805 | DPRINTF("invalid value for gid: '%s'", |
806 | t ? t : "<null>"); |
807 | PQclear(pgresult); |
808 | return 0; |
809 | } |
810 | t=PQgetvalue(pgresult,0,5); |
811 | if (t && t[0]) |
812 | ui.home=strdup(t); |
813 | else |
814 | { |
815 | DPRINTF("required value for 'home' (column 6) is missing"); |
816 | PQclear(pgresult); |
817 | return 0; |
818 | } |
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); |
827 | } |
828 | else |
829 | { |
830 | DPRINTF("zero rows returned"); |
831 | PQclear(pgresult); |
832 | return (&ui); |
833 | } |
834 | PQclear(pgresult); |
835 | |
836 | return (&ui); |
837 | } |
838 | |
839 | int auth_pgsql_setpass(const char *user, const char *pass, |
840 | const char *oldpass) |
841 | { |
842 | char *newpass_crypt; |
d9898ee8 |
843 | char *sql_buf; |
0fde1ce3 |
844 | size_t sql_buf_size; |
845 | char dummy_buf[1]; |
d9898ee8 |
846 | int rc=0; |
847 | |
0fde1ce3 |
848 | char *clear_escaped; |
849 | char *crypt_escaped; |
850 | int *error = NULL; |
851 | |
852 | char *username_escaped; |
853 | |
d9898ee8 |
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 */ |
861 | |
862 | if (!pgconn) |
863 | return (-1); |
864 | |
865 | |
866 | if (!(newpass_crypt=authcryptpasswd(pass, oldpass))) |
867 | return (-1); |
868 | |
0fde1ce3 |
869 | clear_escaped=malloc(strlen(pass)*2+1); |
870 | |
871 | if (!clear_escaped) |
872 | { |
873 | perror("malloc"); |
874 | free(newpass_crypt); |
875 | return -1; |
876 | } |
877 | |
878 | crypt_escaped=malloc(strlen(newpass_crypt)*2+1); |
879 | |
880 | if (!crypt_escaped) |
881 | { |
882 | perror("malloc"); |
883 | free(clear_escaped); |
884 | free(newpass_crypt); |
885 | return -1; |
886 | } |
887 | |
888 | PQescapeStringConn(pgconn, clear_escaped, pass, strlen(pass), error); |
889 | PQescapeStringConn(pgconn, crypt_escaped, |
890 | newpass_crypt, strlen(newpass_crypt), error); |
891 | |
892 | |
d9898ee8 |
893 | |
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"); |
898 | if (!chpass_clause) |
899 | { |
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"); |
0fde1ce3 |
905 | |
906 | username_escaped=get_username_escaped(user, defdomain); |
907 | |
908 | if (!username_escaped) |
909 | return -1; |
910 | |
911 | if (!where_clause) |
912 | where_clause=""; |
913 | |
914 | if (!crypt_field) |
915 | crypt_field=""; |
916 | |
917 | if (!clear_field) |
918 | clear_field=""; |
919 | |
920 | #define DEFAULT_SETPASS_UPDATE \ |
921 | "UPDATE %s SET %s%s%s%s %s %s%s%s%s WHERE %s='%s' %s%s%s", \ |
922 | user_table, \ |
923 | *clear_field ? clear_field:"", \ |
924 | *clear_field ? "='":"", \ |
925 | *clear_field ? clear_escaped:"", \ |
926 | *clear_field ? "'":"", \ |
927 | \ |
928 | *clear_field && *crypt_field ? ",":"", \ |
929 | \ |
930 | *crypt_field ? crypt_field:"", \ |
931 | *crypt_field ? "='":"", \ |
932 | *crypt_field ? crypt_escaped:"", \ |
933 | *crypt_field ? "'":"", \ |
934 | \ |
935 | login_field, username_escaped, \ |
936 | *where_clause ? " AND (":"", where_clause, \ |
937 | *where_clause ? ")":"" |
938 | |
939 | |
940 | sql_buf_size=snprintf(dummy_buf, 1, DEFAULT_SETPASS_UPDATE); |
941 | |
942 | sql_buf=malloc(sql_buf_size+1); |
943 | |
944 | if (sql_buf) |
945 | snprintf(sql_buf, sql_buf_size+1, |
946 | DEFAULT_SETPASS_UPDATE); |
947 | |
948 | free(username_escaped); |
d9898ee8 |
949 | } |
950 | else |
951 | { |
952 | sql_buf=parse_chpass_clause(chpass_clause, |
953 | user, |
954 | defdomain, |
955 | pass, |
956 | newpass_crypt); |
957 | } |
958 | |
959 | if (!sql_buf) |
960 | { |
0fde1ce3 |
961 | free(clear_escaped); |
d9898ee8 |
962 | free(newpass_crypt); |
963 | return (-1); |
964 | } |
d9898ee8 |
965 | if (courier_authdebug_login_level >= 2) |
966 | { |
967 | DPRINTF("setpass SQL: %s", sql_buf); |
968 | } |
969 | pgresult=PQexec (pgconn, sql_buf); |
970 | if (!pgresult || PQresultStatus(pgresult) != PGRES_COMMAND_OK) |
971 | { |
972 | DPRINTF("setpass SQL failed"); |
973 | rc= -1; |
974 | auth_pgsql_cleanup(); |
975 | } |
976 | PQclear(pgresult); |
0fde1ce3 |
977 | free(clear_escaped); |
978 | free(crypt_escaped); |
979 | free(newpass_crypt); |
d9898ee8 |
980 | free(sql_buf); |
981 | return (rc); |
982 | } |
983 | |
984 | void auth_pgsql_enumerate( void(*cb_func)(const char *name, |
985 | uid_t uid, |
986 | gid_t gid, |
987 | const char *homedir, |
988 | const char *maildir, |
989 | const char *options, |
990 | void *void_arg), |
991 | void *void_arg) |
992 | { |
993 | const char *select_clause, *defdomain; |
0fde1ce3 |
994 | char *querybuf; |
d9898ee8 |
995 | |
0fde1ce3 |
996 | int i,n; |
d9898ee8 |
997 | |
998 | if (do_connect()) return; |
999 | |
1000 | initui(); |
1001 | |
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 */ |
1006 | |
1007 | if (!select_clause) /* tom@minnesota.com */ |
1008 | { |
1009 | const char *user_table, |
1010 | *uid_field, |
1011 | *gid_field, |
1012 | *login_field, |
1013 | *home_field, |
1014 | *maildir_field, |
1015 | *options_field, |
1016 | *where_clause; |
0fde1ce3 |
1017 | char dummy_buf[1]; |
1018 | size_t query_len; |
d9898ee8 |
1019 | |
1020 | user_table=read_env("PGSQL_USER_TABLE"); |
1021 | |
1022 | if (!user_table) |
1023 | { |
1024 | err("authpgsql: PGSQL_USER_TABLE not set in " |
1025 | AUTHPGSQLRC "."); |
1026 | return; |
1027 | } |
1028 | |
1029 | uid_field = read_env("PGSQL_UID_FIELD"); |
1030 | if (!uid_field) uid_field = "uid"; |
1031 | |
1032 | gid_field = read_env("PGSQL_GID_FIELD"); |
1033 | if (!gid_field) gid_field = "gid"; |
1034 | |
1035 | login_field = read_env("PGSQL_LOGIN_FIELD"); |
1036 | if (!login_field) login_field = "id"; |
1037 | |
1038 | home_field = read_env("PGSQL_HOME_FIELD"); |
1039 | if (!home_field) home_field = "home"; |
1040 | |
1041 | maildir_field=read_env("PGSQL_MAILDIR_FIELD"); |
1042 | if (!maildir_field) maildir_field="''"; |
1043 | |
1044 | options_field=read_env("PGSQL_AUXOPTIONS_FIELD"); |
1045 | if (!options_field) options_field="''"; |
1046 | |
1047 | where_clause=read_env("PGSQL_WHERE_CLAUSE"); |
1048 | if (!where_clause) where_clause = ""; |
1049 | |
0fde1ce3 |
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 ":"", \ |
1056 | where_clause |
1057 | |
1058 | |
1059 | query_len=snprintf(dummy_buf, 1, DEFAULT_ENUMERATE_QUERY); |
1060 | |
1061 | querybuf=malloc(query_len+1); |
d9898ee8 |
1062 | |
1063 | if (!querybuf) |
1064 | { |
1065 | perror("malloc"); |
1066 | return; |
1067 | } |
1068 | |
0fde1ce3 |
1069 | snprintf(querybuf, query_len+1, DEFAULT_ENUMERATE_QUERY); |
d9898ee8 |
1070 | } |
1071 | else |
1072 | { |
1073 | /* tom@minnesota.com */ |
1074 | querybuf=parse_select_clause (select_clause, "*", |
1075 | defdomain, "enumerate"); |
1076 | if (!querybuf) |
1077 | { |
1078 | DPRINTF("authpgsql: parse_select_clause failed"); |
1079 | return; |
1080 | } |
1081 | } |
1082 | DPRINTF("authpgsql: enumerate query: %s", querybuf); |
1083 | |
1084 | if (PQsendQuery(pgconn, querybuf) == 0) |
1085 | { |
1086 | DPRINTF("PQsendQuery failed, reconnecting: %s",PQerrorMessage(pgconn)); |
1087 | |
1088 | auth_pgsql_cleanup(); |
1089 | |
1090 | if (do_connect()) |
1091 | { |
1092 | free(querybuf); |
1093 | return; |
1094 | } |
1095 | |
1096 | if (PQsendQuery(pgconn, querybuf) == 0) |
1097 | { |
1098 | DPRINTF("PQsendQuery failed second time, giving up: %s",PQerrorMessage(pgconn)); |
1099 | free(querybuf); |
1100 | auth_pgsql_cleanup(); |
1101 | return; |
1102 | } |
1103 | } |
1104 | free(querybuf); |
1105 | |
1106 | while ((pgresult = PQgetResult(pgconn)) != NULL) |
1107 | { |
1108 | if (PQresultStatus(pgresult) != PGRES_TUPLES_OK) |
1109 | { |
1110 | DPRINTF("pgsql error during enumeration: %s",PQerrorMessage(pgconn)); |
1111 | PQclear(pgresult); |
1112 | return; |
1113 | } |
1114 | |
1115 | for (n=PQntuples(pgresult), i=0; i<n; i++) |
1116 | { |
1117 | const char *username; |
1118 | uid_t uid; |
1119 | gid_t gid; |
1120 | const char *homedir; |
1121 | const char *maildir; |
1122 | const char *options; |
1123 | |
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); |
1130 | |
1131 | if (!username || !*username || !homedir || !*homedir) |
1132 | continue; |
1133 | |
1134 | if (maildir && !*maildir) |
1135 | maildir=NULL; |
1136 | |
1137 | (*cb_func)(username, uid, gid, homedir, |
1138 | maildir, options, void_arg); |
1139 | |
1140 | } |
1141 | PQclear(pgresult); |
1142 | } |
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); |
1146 | } |