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