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 | ||
d9898ee8 | 31 | |
32 | /* tom@minnesota.com */ | |
33 | struct var_data { | |
34 | const char *name; | |
35 | const char *value; | |
36 | const size_t size; | |
d9898ee8 | 37 | } ; |
38 | ||
39 | /* tom@minnesota.com */ | |
40 | typedef int (*parsefunc)(const char *, size_t, void *); | |
41 | ||
42 | static const char *read_env(const char *env) | |
43 | { | |
44 | static char *pgsqlauth=0; | |
45 | static size_t pgsqlauth_size=0; | |
46 | size_t i; | |
47 | char *p=0; | |
48 | int l=strlen(env); | |
49 | ||
50 | if (!pgsqlauth) | |
51 | { | |
52 | FILE *f=fopen(AUTHPGSQLRC, "r"); | |
53 | struct stat buf; | |
54 | ||
55 | if (!f) return (0); | |
56 | if (fstat(fileno(f), &buf) || | |
57 | (pgsqlauth=malloc(buf.st_size+2)) == 0) | |
58 | { | |
59 | fclose(f); | |
60 | return (0); | |
61 | } | |
62 | if (fread(pgsqlauth, buf.st_size, 1, f) != 1) | |
63 | { | |
64 | free(pgsqlauth); | |
65 | pgsqlauth=0; | |
66 | fclose(f); | |
67 | return (0); | |
68 | } | |
69 | pgsqlauth[pgsqlauth_size=buf.st_size]=0; | |
70 | ||
71 | for (i=0; i<pgsqlauth_size; i++) | |
72 | if (pgsqlauth[i] == '\n') | |
73 | { /* tom@minnesota.com */ | |
74 | if (!i || pgsqlauth[i-1] != '\\') | |
75 | { | |
76 | pgsqlauth[i]='\0'; | |
77 | } | |
78 | else | |
79 | { | |
80 | pgsqlauth[i]=pgsqlauth[i-1]= ' '; | |
81 | } | |
82 | } | |
83 | fclose(f); | |
84 | } | |
85 | ||
86 | for (i=0; i<pgsqlauth_size; ) | |
87 | { | |
88 | p=pgsqlauth+i; | |
89 | if (memcmp(p, env, l) == 0 && | |
90 | isspace((int)(unsigned char)p[l])) | |
91 | { | |
92 | p += l; | |
93 | while (*p && *p != '\n' && | |
94 | isspace((int)(unsigned char)*p)) | |
95 | ++p; | |
96 | break; | |
97 | } | |
98 | ||
99 | while (i < pgsqlauth_size) | |
100 | if (pgsqlauth[i++] == 0) break; | |
101 | } | |
102 | ||
103 | if (i < pgsqlauth_size) | |
104 | return (p); | |
105 | return (0); | |
106 | } | |
107 | ||
108 | static PGresult *pgresult=0; | |
109 | ||
110 | static PGconn *pgconn=0; | |
111 | ||
8d138742 CE |
112 | /* |
113 | * session variables can be set once for the whole session | |
114 | */ | |
115 | ||
116 | static void set_session_options(void) | |
117 | { | |
118 | const char *character_set=read_env("PGSQL_CHARACTER_SET"), *check; | |
119 | ||
120 | if (character_set) | |
121 | { | |
122 | PQsetClientEncoding(pgconn, character_set); | |
123 | check = pg_encoding_to_char(PQclientEncoding(pgconn)); | |
124 | if (strcmp(character_set, check) != 0) | |
125 | { | |
126 | err("Cannot set Postgresql character set \"%s\", working with \"%s\"\n", | |
127 | character_set, check); | |
128 | } | |
129 | else | |
130 | { | |
131 | DPRINTF("Install of a character set for Postgresql: %s", character_set); | |
132 | } | |
133 | } | |
134 | } | |
135 | ||
136 | ||
137 | ||
d9898ee8 | 138 | /* |
139 | static FILE *DEBUG=0; | |
140 | */ | |
141 | ||
142 | static int do_connect() | |
143 | { | |
144 | const char *server; | |
145 | const char *userid; | |
146 | const char *password; | |
147 | const char *database; | |
148 | const char *server_port=0; | |
149 | const char *server_opt=0; | |
150 | /* | |
151 | if (!DEBUG) { | |
152 | DEBUG=fopen("/tmp/courier.debug","a"); | |
153 | } | |
154 | fprintf(DEBUG,"Apro il DB!\n"); | |
155 | fflush(DEBUG); | |
156 | */ | |
157 | ||
158 | /* | |
159 | ** Periodically detect dead connections. | |
160 | */ | |
161 | if (pgconn) | |
162 | { | |
163 | static time_t last_time=0; | |
164 | time_t t_check; | |
165 | ||
166 | time(&t_check); | |
167 | ||
168 | if (t_check < last_time) | |
169 | last_time=t_check; /* System clock changed */ | |
170 | ||
171 | if (t_check < last_time + 60) | |
172 | return (0); | |
173 | ||
174 | last_time=t_check; | |
175 | ||
176 | if (PQstatus(pgconn) == CONNECTION_OK) return (0); | |
177 | ||
178 | DPRINTF("authpgsqllib: PQstatus failed, connection lost"); | |
179 | PQfinish(pgconn); | |
180 | pgconn=0; | |
181 | } | |
182 | ||
183 | server=read_env("PGSQL_HOST"); | |
184 | server_port=read_env("PGSQL_PORT"); | |
185 | userid=read_env("PGSQL_USERNAME"); | |
186 | password=read_env("PGSQL_PASSWORD"); | |
187 | database=read_env("PGSQL_DATABASE"); | |
188 | server_opt=read_env("PGSQL_OPT"); | |
189 | ||
190 | /* | |
191 | fprintf(DEBUG,"Letti i parametri!\n"); | |
192 | fflush(DEBUG); | |
193 | */ | |
194 | ||
195 | if (!userid) | |
196 | { | |
197 | err("authpgsql: PGSQL_USERNAME not set in " | |
198 | AUTHPGSQLRC "."); | |
199 | return (-1); | |
200 | } | |
201 | ||
202 | if (!database) | |
203 | { | |
204 | err("authpgsql: PGSQL_DATABASE not set in " | |
205 | AUTHPGSQLRC "."); | |
206 | return (-1); | |
207 | } | |
208 | ||
209 | /* | |
210 | fprintf(DEBUG,"Connecting to db:%s:%s\%s user=%s pass=%s\n",server,server_port,database,userid,password); | |
211 | fflush(DEBUG); | |
212 | */ | |
213 | pgconn = PQsetdbLogin(server, server_port, server_opt, NULL , database,userid,password); | |
214 | ||
215 | if (PQstatus(pgconn) == CONNECTION_BAD) | |
216 | { | |
217 | err("Connection to server '%s' userid '%s' database '%s' failed.", | |
218 | server ? server : "<null>", | |
219 | userid ? userid : "<null>", | |
220 | database); | |
221 | err("%s", PQerrorMessage(pgconn)); | |
b0322a85 | 222 | PQfinish(pgconn); |
d9898ee8 | 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 | if (do_connect()) return (0); |
623 | ||
624 | initui(); | |
625 | ||
626 | /* | |
627 | fprintf(DEBUG,"1Leggo parametri\n"); | |
628 | fflush(DEBUG); | |
629 | */ | |
630 | select_clause=read_env("PGSQL_SELECT_CLAUSE"); | |
631 | defdomain=read_env("DEFAULT_DOMAIN"); | |
632 | if (!defdomain) defdomain=""; | |
633 | ||
634 | if (!select_clause) /* tom@minnesota.com */ | |
635 | { | |
636 | const char *user_table, | |
637 | *crypt_field, | |
638 | *clear_field, | |
639 | *name_field, | |
640 | *uid_field, | |
641 | *gid_field, | |
642 | *login_field, | |
643 | *home_field, | |
644 | *maildir_field, | |
645 | *quota_field, | |
646 | *options_field, | |
647 | *where_clause; | |
648 | ||
8d138742 CE |
649 | const char *where_pfix, *where_sfix; |
650 | char *username_escaped; | |
651 | ||
d9898ee8 | 652 | user_table=read_env("PGSQL_USER_TABLE"); |
653 | ||
654 | if (!user_table) | |
655 | { | |
656 | err("authpgsql: PGSQL_USER_TABLE not set in " | |
657 | AUTHPGSQLRC "."); | |
658 | return (0); | |
659 | } | |
660 | ||
661 | crypt_field=read_env("PGSQL_CRYPT_PWFIELD"); | |
662 | clear_field=read_env("PGSQL_CLEAR_PWFIELD"); | |
663 | name_field=read_env("PGSQL_NAME_FIELD"); | |
664 | ||
665 | if (!crypt_field && !clear_field) | |
666 | { | |
667 | err("authpgsql: PGSQL_CRYPT_PWFIELD and " | |
668 | "PGSQL_CLEAR_PWFIELD not set in " AUTHPGSQLRC "."); | |
669 | return (0); | |
670 | } | |
671 | if (!crypt_field) crypt_field="''"; | |
672 | if (!clear_field) clear_field="''"; | |
673 | if (!name_field) name_field="''"; | |
674 | ||
675 | uid_field = read_env("PGSQL_UID_FIELD"); | |
676 | if (!uid_field) uid_field = "uid"; | |
677 | ||
678 | gid_field = read_env("PGSQL_GID_FIELD"); | |
679 | if (!gid_field) gid_field = "gid"; | |
680 | ||
681 | login_field = read_env("PGSQL_LOGIN_FIELD"); | |
682 | if (!login_field) login_field = "id"; | |
683 | ||
684 | home_field = read_env("PGSQL_HOME_FIELD"); | |
685 | if (!home_field) home_field = "home"; | |
686 | ||
687 | maildir_field=read_env(service && strcmp(service, "courier")==0 | |
688 | ? "PGSQL_DEFAULTDELIVERY" | |
689 | : "PGSQL_MAILDIR_FIELD"); | |
690 | if (!maildir_field) maildir_field="''"; | |
691 | ||
692 | quota_field=read_env("PGSQL_QUOTA_FIELD"); | |
693 | if (!quota_field) quota_field="''"; | |
694 | ||
695 | options_field=read_env("PGSQL_AUXOPTIONS_FIELD"); | |
696 | if (!options_field) options_field="''"; | |
697 | ||
698 | where_clause=read_env("PGSQL_WHERE_CLAUSE"); | |
699 | if (!where_clause) where_clause = ""; | |
700 | ||
8d138742 | 701 | where_pfix=where_sfix=""; |
d9898ee8 | 702 | |
8d138742 | 703 | if (strcmp(where_clause, "")) |
d9898ee8 | 704 | { |
8d138742 CE |
705 | where_pfix=" AND ("; |
706 | where_sfix=")"; | |
d9898ee8 | 707 | } |
708 | ||
8d138742 | 709 | username_escaped=get_username_escaped(username, defdomain); |
d9898ee8 | 710 | |
8d138742 CE |
711 | if (!username_escaped) |
712 | return 0; | |
713 | ||
714 | query_size=snprintf(dummy_buf, 1, SELECT_QUERY); | |
715 | ||
716 | querybuf=malloc(query_size+1); | |
717 | ||
718 | if (!querybuf) | |
719 | { | |
720 | free(username_escaped); | |
721 | perror("malloc"); | |
722 | return 0; | |
d9898ee8 | 723 | } |
8d138742 CE |
724 | |
725 | snprintf(querybuf, query_size+1, SELECT_QUERY); | |
726 | free(username_escaped); | |
d9898ee8 | 727 | } |
728 | else | |
729 | { | |
730 | /* tom@minnesota.com */ | |
731 | querybuf=parse_select_clause (select_clause, username, | |
732 | defdomain, service); | |
733 | if (!querybuf) | |
734 | { | |
735 | DPRINTF("authpgsql: parse_select_clause failed (DEFAULT_DOMAIN not defined?)"); | |
736 | return 0; | |
737 | } | |
738 | } | |
739 | ||
740 | DPRINTF("SQL query: %s", querybuf); | |
741 | pgresult = PQexec(pgconn, querybuf); | |
742 | if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) | |
743 | { | |
744 | DPRINTF("PQexec failed, reconnecting: %s", PQerrorMessage(pgconn)); | |
745 | if (pgresult) PQclear(pgresult); | |
746 | ||
747 | /* <o.blasnik@nextra.de> */ | |
748 | ||
749 | auth_pgsql_cleanup(); | |
750 | ||
751 | if (do_connect()) | |
752 | { | |
753 | free(querybuf); | |
754 | return (0); | |
755 | } | |
756 | ||
757 | pgresult = PQexec(pgconn, querybuf); | |
758 | if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) | |
759 | { | |
760 | DPRINTF("PQexec failed second time, giving up: %s", PQerrorMessage(pgconn)); | |
761 | if (pgresult) PQclear(pgresult); | |
762 | free(querybuf); | |
763 | auth_pgsql_cleanup(); | |
764 | /* Server went down, that's OK, | |
765 | ** try again next time. | |
766 | */ | |
767 | return (0); | |
768 | } | |
769 | } | |
770 | free(querybuf); | |
771 | ||
772 | if (PQntuples(pgresult)>0) | |
773 | { | |
774 | char *t, *endp; | |
775 | int num_fields = PQnfields(pgresult); | |
776 | ||
777 | if (num_fields < 6) | |
778 | { | |
779 | DPRINTF("incomplete row, only %d fields returned", | |
780 | num_fields); | |
781 | PQclear(pgresult); | |
782 | return 0; | |
783 | } | |
784 | ||
785 | t=PQgetvalue(pgresult,0,0); | |
786 | if (t && t[0]) ui.username=strdup(t); | |
787 | t=PQgetvalue(pgresult,0,1); | |
788 | if (t && t[0]) ui.cryptpw=strdup(t); | |
789 | t=PQgetvalue(pgresult,0,2); | |
790 | if (t && t[0]) ui.clearpw=strdup(t); | |
791 | t=PQgetvalue(pgresult,0,3); | |
792 | if (!t || !t[0] || | |
793 | (ui.uid=strtol(t, &endp, 10), endp[0] != '\0')) | |
794 | { | |
795 | DPRINTF("invalid value for uid: '%s'", | |
796 | t ? t : "<null>"); | |
797 | PQclear(pgresult); | |
798 | return 0; | |
799 | } | |
800 | t=PQgetvalue(pgresult,0,4); | |
801 | if (!t || !t[0] || | |
802 | (ui.gid=strtol(t, &endp, 10), endp[0] != '\0')) | |
803 | { | |
804 | DPRINTF("invalid value for gid: '%s'", | |
805 | t ? t : "<null>"); | |
806 | PQclear(pgresult); | |
807 | return 0; | |
808 | } | |
809 | t=PQgetvalue(pgresult,0,5); | |
810 | if (t && t[0]) | |
811 | ui.home=strdup(t); | |
812 | else | |
813 | { | |
814 | DPRINTF("required value for 'home' (column 6) is missing"); | |
815 | PQclear(pgresult); | |
816 | return 0; | |
817 | } | |
818 | t=num_fields > 6 ? PQgetvalue(pgresult,0,6) : 0; | |
819 | if (t && t[0]) ui.maildir=strdup(t); | |
820 | t=num_fields > 7 ? PQgetvalue(pgresult,0,7) : 0; | |
821 | if (t && t[0]) ui.quota=strdup(t); | |
822 | t=num_fields > 8 ? PQgetvalue(pgresult,0,8) : 0; | |
823 | if (t && t[0]) ui.fullname=strdup(t); | |
824 | t=num_fields > 9 ? PQgetvalue(pgresult,0,9) : 0; | |
825 | if (t && t[0]) ui.options=strdup(t); | |
826 | } | |
827 | else | |
828 | { | |
829 | DPRINTF("zero rows returned"); | |
830 | PQclear(pgresult); | |
831 | return (&ui); | |
832 | } | |
833 | PQclear(pgresult); | |
834 | ||
835 | return (&ui); | |
836 | } | |
837 | ||
838 | int auth_pgsql_setpass(const char *user, const char *pass, | |
839 | const char *oldpass) | |
840 | { | |
841 | char *newpass_crypt; | |
d9898ee8 | 842 | char *sql_buf; |
8d138742 CE |
843 | size_t sql_buf_size; |
844 | char dummy_buf[1]; | |
d9898ee8 | 845 | int rc=0; |
846 | ||
8d138742 CE |
847 | char *clear_escaped; |
848 | char *crypt_escaped; | |
849 | int *error = NULL; | |
850 | ||
851 | char *username_escaped; | |
852 | ||
d9898ee8 | 853 | const char *clear_field=NULL; |
854 | const char *crypt_field=NULL; | |
855 | const char *defdomain=NULL; | |
856 | const char *where_clause=NULL; | |
857 | const char *user_table=NULL; | |
858 | const char *login_field=NULL; | |
859 | const char *chpass_clause=NULL; /* tom@minnesota.com */ | |
860 | ||
861 | if (!pgconn) | |
862 | return (-1); | |
863 | ||
864 | ||
865 | if (!(newpass_crypt=authcryptpasswd(pass, oldpass))) | |
866 | return (-1); | |
867 | ||
8d138742 CE |
868 | clear_escaped=malloc(strlen(pass)*2+1); |
869 | ||
870 | if (!clear_escaped) | |
871 | { | |
872 | perror("malloc"); | |
873 | free(newpass_crypt); | |
874 | return -1; | |
875 | } | |
876 | ||
877 | crypt_escaped=malloc(strlen(newpass_crypt)*2+1); | |
878 | ||
879 | if (!crypt_escaped) | |
880 | { | |
881 | perror("malloc"); | |
882 | free(clear_escaped); | |
883 | free(newpass_crypt); | |
884 | return -1; | |
885 | } | |
886 | ||
887 | PQescapeStringConn(pgconn, clear_escaped, pass, strlen(pass), error); | |
888 | PQescapeStringConn(pgconn, crypt_escaped, | |
889 | newpass_crypt, strlen(newpass_crypt), error); | |
890 | ||
891 | ||
d9898ee8 | 892 | |
893 | /* tom@minnesota.com */ | |
894 | chpass_clause=read_env("PGSQL_CHPASS_CLAUSE"); | |
895 | defdomain=read_env("DEFAULT_DOMAIN"); | |
896 | user_table=read_env("PGSQL_USER_TABLE"); | |
897 | if (!chpass_clause) | |
898 | { | |
899 | login_field = read_env("PGSQL_LOGIN_FIELD"); | |
900 | if (!login_field) login_field = "id"; | |
901 | crypt_field=read_env("PGSQL_CRYPT_PWFIELD"); | |
902 | clear_field=read_env("PGSQL_CLEAR_PWFIELD"); | |
903 | where_clause=read_env("PGSQL_WHERE_CLAUSE"); | |
8d138742 CE |
904 | |
905 | username_escaped=get_username_escaped(user, defdomain); | |
906 | ||
907 | if (!username_escaped) | |
908 | return -1; | |
909 | ||
910 | if (!where_clause) | |
911 | where_clause=""; | |
912 | ||
913 | if (!crypt_field) | |
914 | crypt_field=""; | |
915 | ||
916 | if (!clear_field) | |
917 | clear_field=""; | |
918 | ||
919 | #define DEFAULT_SETPASS_UPDATE \ | |
920 | "UPDATE %s SET %s%s%s%s %s %s%s%s%s WHERE %s='%s' %s%s%s", \ | |
921 | user_table, \ | |
922 | *clear_field ? clear_field:"", \ | |
923 | *clear_field ? "='":"", \ | |
924 | *clear_field ? clear_escaped:"", \ | |
925 | *clear_field ? "'":"", \ | |
926 | \ | |
927 | *clear_field && *crypt_field ? ",":"", \ | |
928 | \ | |
929 | *crypt_field ? crypt_field:"", \ | |
930 | *crypt_field ? "='":"", \ | |
931 | *crypt_field ? crypt_escaped:"", \ | |
932 | *crypt_field ? "'":"", \ | |
933 | \ | |
934 | login_field, username_escaped, \ | |
935 | *where_clause ? " AND (":"", where_clause, \ | |
936 | *where_clause ? ")":"" | |
937 | ||
938 | ||
939 | sql_buf_size=snprintf(dummy_buf, 1, DEFAULT_SETPASS_UPDATE); | |
940 | ||
941 | sql_buf=malloc(sql_buf_size+1); | |
942 | ||
943 | if (sql_buf) | |
944 | snprintf(sql_buf, sql_buf_size+1, | |
945 | DEFAULT_SETPASS_UPDATE); | |
946 | ||
947 | free(username_escaped); | |
d9898ee8 | 948 | } |
949 | else | |
950 | { | |
951 | sql_buf=parse_chpass_clause(chpass_clause, | |
952 | user, | |
953 | defdomain, | |
954 | pass, | |
955 | newpass_crypt); | |
956 | } | |
957 | ||
958 | if (!sql_buf) | |
959 | { | |
8d138742 | 960 | free(clear_escaped); |
d9898ee8 | 961 | free(newpass_crypt); |
962 | return (-1); | |
963 | } | |
d9898ee8 | 964 | if (courier_authdebug_login_level >= 2) |
965 | { | |
966 | DPRINTF("setpass SQL: %s", sql_buf); | |
967 | } | |
968 | pgresult=PQexec (pgconn, sql_buf); | |
969 | if (!pgresult || PQresultStatus(pgresult) != PGRES_COMMAND_OK) | |
970 | { | |
971 | DPRINTF("setpass SQL failed"); | |
972 | rc= -1; | |
973 | auth_pgsql_cleanup(); | |
974 | } | |
975 | PQclear(pgresult); | |
8d138742 CE |
976 | free(clear_escaped); |
977 | free(crypt_escaped); | |
978 | free(newpass_crypt); | |
d9898ee8 | 979 | free(sql_buf); |
980 | return (rc); | |
981 | } | |
982 | ||
983 | void auth_pgsql_enumerate( void(*cb_func)(const char *name, | |
984 | uid_t uid, | |
985 | gid_t gid, | |
986 | const char *homedir, | |
987 | const char *maildir, | |
988 | const char *options, | |
989 | void *void_arg), | |
990 | void *void_arg) | |
991 | { | |
992 | const char *select_clause, *defdomain; | |
8d138742 | 993 | char *querybuf; |
d9898ee8 | 994 | |
8d138742 | 995 | int i,n; |
d9898ee8 | 996 | |
b0322a85 CE |
997 | if (do_connect()) |
998 | { | |
999 | (*cb_func)(NULL, 0, 0, NULL, NULL, NULL, void_arg); | |
1000 | return; | |
1001 | } | |
d9898ee8 | 1002 | |
1003 | initui(); | |
1004 | ||
1005 | select_clause=read_env("PGSQL_ENUMERATE_CLAUSE"); | |
1006 | defdomain=read_env("DEFAULT_DOMAIN"); | |
1007 | if (!defdomain || !defdomain[0]) | |
1008 | defdomain="*"; /* otherwise parse_select_clause fails */ | |
1009 | ||
1010 | if (!select_clause) /* tom@minnesota.com */ | |
1011 | { | |
1012 | const char *user_table, | |
1013 | *uid_field, | |
1014 | *gid_field, | |
1015 | *login_field, | |
1016 | *home_field, | |
1017 | *maildir_field, | |
1018 | *options_field, | |
1019 | *where_clause; | |
8d138742 CE |
1020 | char dummy_buf[1]; |
1021 | size_t query_len; | |
d9898ee8 | 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 | ||
8d138742 CE |
1053 | #define DEFAULT_ENUMERATE_QUERY \ |
1054 | "SELECT %s, %s, %s, %s, %s, %s FROM %s %s%s",\ | |
1055 | login_field, uid_field, gid_field, \ | |
1056 | home_field, maildir_field, \ | |
1057 | options_field, user_table, \ | |
1058 | *where_clause ? " WHERE ":"", \ | |
1059 | where_clause | |
1060 | ||
1061 | ||
1062 | query_len=snprintf(dummy_buf, 1, DEFAULT_ENUMERATE_QUERY); | |
1063 | ||
1064 | querybuf=malloc(query_len+1); | |
d9898ee8 | 1065 | |
1066 | if (!querybuf) | |
1067 | { | |
1068 | perror("malloc"); | |
1069 | return; | |
1070 | } | |
1071 | ||
8d138742 | 1072 | snprintf(querybuf, query_len+1, DEFAULT_ENUMERATE_QUERY); |
d9898ee8 | 1073 | } |
1074 | else | |
1075 | { | |
1076 | /* tom@minnesota.com */ | |
1077 | querybuf=parse_select_clause (select_clause, "*", | |
1078 | defdomain, "enumerate"); | |
1079 | if (!querybuf) | |
1080 | { | |
1081 | DPRINTF("authpgsql: parse_select_clause failed"); | |
1082 | return; | |
1083 | } | |
1084 | } | |
1085 | DPRINTF("authpgsql: enumerate query: %s", querybuf); | |
1086 | ||
1087 | if (PQsendQuery(pgconn, querybuf) == 0) | |
1088 | { | |
1089 | DPRINTF("PQsendQuery failed, reconnecting: %s",PQerrorMessage(pgconn)); | |
1090 | ||
1091 | auth_pgsql_cleanup(); | |
1092 | ||
1093 | if (do_connect()) | |
1094 | { | |
1095 | free(querybuf); | |
1096 | return; | |
1097 | } | |
1098 | ||
1099 | if (PQsendQuery(pgconn, querybuf) == 0) | |
1100 | { | |
1101 | DPRINTF("PQsendQuery failed second time, giving up: %s",PQerrorMessage(pgconn)); | |
1102 | free(querybuf); | |
1103 | auth_pgsql_cleanup(); | |
1104 | return; | |
1105 | } | |
1106 | } | |
1107 | free(querybuf); | |
1108 | ||
1109 | while ((pgresult = PQgetResult(pgconn)) != NULL) | |
1110 | { | |
1111 | if (PQresultStatus(pgresult) != PGRES_TUPLES_OK) | |
1112 | { | |
1113 | DPRINTF("pgsql error during enumeration: %s",PQerrorMessage(pgconn)); | |
1114 | PQclear(pgresult); | |
1115 | return; | |
1116 | } | |
1117 | ||
1118 | for (n=PQntuples(pgresult), i=0; i<n; i++) | |
1119 | { | |
1120 | const char *username; | |
1121 | uid_t uid; | |
1122 | gid_t gid; | |
1123 | const char *homedir; | |
1124 | const char *maildir; | |
1125 | const char *options; | |
1126 | ||
1127 | username=PQgetvalue(pgresult,i,0); | |
1128 | uid=atol(PQgetvalue(pgresult,i,1)); | |
1129 | gid=atol(PQgetvalue(pgresult,i,2)); | |
1130 | homedir=PQgetvalue(pgresult,i,3); | |
1131 | maildir=PQgetvalue(pgresult,i,4); | |
1132 | options=PQgetvalue(pgresult,i,5); | |
1133 | ||
1134 | if (!username || !*username || !homedir || !*homedir) | |
1135 | continue; | |
1136 | ||
1137 | if (maildir && !*maildir) | |
1138 | maildir=NULL; | |
1139 | ||
1140 | (*cb_func)(username, uid, gid, homedir, | |
1141 | maildir, options, void_arg); | |
1142 | ||
1143 | } | |
1144 | PQclear(pgresult); | |
1145 | } | |
1146 | /* FIXME: is it possible that a NULL result from PQgetResult could | |
1147 | indicate an error rather than EOF? The documentation is not clear */ | |
1148 | (*cb_func)(NULL, 0, 0, NULL, NULL, NULL, void_arg); | |
1149 | } |