Commit | Line | Data |
---|---|---|
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 | ||
8d138742 | 31 | static const char rcsid[]="$Id: authpgsqllib.c,v 1.19 2008/12/18 12:08:25 mrsam Exp $"; |
d9898ee8 | 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 | ||
8d138742 CE |
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 | ||
8d138742 | 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 | ||
8d138742 CE |
247 | static char *get_username_escaped(const char *username, |
248 | const char *defdomain) | |
d9898ee8 | 249 | { |
8d138742 CE |
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, | |
8d138742 | 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 | { | |
8d138742 CE |
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; | |
8d138742 CE |
417 | |
418 | enc=malloc(strlen(v_ptr->value)*2+1); | |
419 | ||
420 | if (!enc) | |
d9898ee8 | 421 | return -1; |
8d138742 CE |
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 | ||
8d138742 | 483 | static char *get_localpart (const char *username) |
d9898ee8 | 484 | { |
8d138742 CE |
485 | char *p=strdup(username); |
486 | char *q; | |
d9898ee8 | 487 | |
8d138742 CE |
488 | if (!p) |
489 | return 0; | |
d9898ee8 | 490 | |
8d138742 | 491 | q=strchr(p, '@'); |
d9898ee8 | 492 | |
8d138742 CE |
493 | if (q) |
494 | *q=0; | |
d9898ee8 | 495 | |
8d138742 | 496 | return p; |
d9898ee8 | 497 | } |
498 | ||
8d138742 | 499 | static const char *get_domain (const char *username, const char *defdomain) |
d9898ee8 | 500 | { |
8d138742 | 501 | const char *p=strchr(username, '@'); |
d9898ee8 | 502 | |
8d138742 CE |
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 | { | |
8d138742 CE |
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 | ||
8d138742 CE |
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); |
8d138742 CE |
531 | |
532 | if (!vd[1].value) | |
533 | { | |
534 | free(localpart); | |
d9898ee8 | 535 | return NULL; |
8d138742 | 536 | } |
d9898ee8 | 537 | vd[2].value = service; |
538 | ||
8d138742 CE |
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 | { | |
8d138742 CE |
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 | ||
8d138742 CE |
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); |
8d138742 CE |
569 | vd[2].value = newpass; |
570 | vd[3].value = newpass_crypt; | |
d9898ee8 | 571 | |
8d138742 CE |
572 | if (!vd[1].value || !vd[2].value || !vd[3].value) |
573 | { | |
574 | free(localpart); | |
575 | return NULL; | |
576 | } | |
d9898ee8 | 577 | |
8d138742 CE |
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 | { | |
8d138742 CE |
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 | ||
8d138742 CE |
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 | ||
8d138742 | 702 | where_pfix=where_sfix=""; |
d9898ee8 | 703 | |
8d138742 | 704 | if (strcmp(where_clause, "")) |
d9898ee8 | 705 | { |
8d138742 CE |
706 | where_pfix=" AND ("; |
707 | where_sfix=")"; | |
d9898ee8 | 708 | } |
709 | ||
8d138742 | 710 | username_escaped=get_username_escaped(username, defdomain); |
d9898ee8 | 711 | |
8d138742 CE |
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 | } |
8d138742 CE |
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; |
8d138742 CE |
844 | size_t sql_buf_size; |
845 | char dummy_buf[1]; | |
d9898ee8 | 846 | int rc=0; |
847 | ||
8d138742 CE |
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 | ||
8d138742 CE |
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"); | |
8d138742 CE |
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 | { | |
8d138742 | 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); | |
8d138742 CE |
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; | |
8d138742 | 994 | char *querybuf; |
d9898ee8 | 995 | |
8d138742 | 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; | |
8d138742 CE |
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 | ||
8d138742 CE |
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 | ||
8d138742 | 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 | } |