Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * hba.c
4 : * Routines to handle host based authentication (that's the scheme
5 : * wherein you authenticate a user by seeing what IP address the system
6 : * says he comes from and choosing authentication method based on it).
7 : *
8 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
9 : * Portions Copyright (c) 1994, Regents of the University of California
10 : *
11 : *
12 : * IDENTIFICATION
13 : * src/backend/libpq/hba.c
14 : *
15 : *-------------------------------------------------------------------------
16 : */
17 : #include "postgres.h"
18 :
19 : #include <ctype.h>
20 : #include <pwd.h>
21 : #include <fcntl.h>
22 : #include <sys/param.h>
23 : #include <sys/socket.h>
24 : #include <netdb.h>
25 : #include <netinet/in.h>
26 : #include <arpa/inet.h>
27 : #include <unistd.h>
28 :
29 : #include "access/htup_details.h"
30 : #include "catalog/pg_collation.h"
31 : #include "catalog/pg_type.h"
32 : #include "common/ip.h"
33 : #include "common/string.h"
34 : #include "funcapi.h"
35 : #include "libpq/ifaddr.h"
36 : #include "libpq/libpq.h"
37 : #include "miscadmin.h"
38 : #include "postmaster/postmaster.h"
39 : #include "regex/regex.h"
40 : #include "replication/walsender.h"
41 : #include "storage/fd.h"
42 : #include "utils/acl.h"
43 : #include "utils/builtins.h"
44 : #include "utils/conffiles.h"
45 : #include "utils/guc.h"
46 : #include "utils/lsyscache.h"
47 : #include "utils/memutils.h"
48 : #include "utils/varlena.h"
49 :
50 : #ifdef USE_LDAP
51 : #ifdef WIN32
52 : #include <winldap.h>
53 : #else
54 : #include <ldap.h>
55 : #endif
56 : #endif
57 :
58 :
59 : #define MAX_TOKEN 256
60 :
61 : /* callback data for check_network_callback */
62 : typedef struct check_network_data
63 : {
64 : IPCompareMethod method; /* test method */
65 : SockAddr *raddr; /* client's actual address */
66 : bool result; /* set to true if match */
67 : } check_network_data;
68 :
69 : typedef struct
70 : {
71 : const char *filename;
72 : int linenum;
73 : } tokenize_error_callback_arg;
74 :
75 : #define token_has_regexp(t) (t->regex != NULL)
76 : #define token_is_member_check(t) (!t->quoted && t->string[0] == '+')
77 : #define token_is_keyword(t, k) (!t->quoted && strcmp(t->string, k) == 0)
78 : #define token_matches(t, k) (strcmp(t->string, k) == 0)
79 : #define token_matches_insensitive(t,k) (pg_strcasecmp(t->string, k) == 0)
80 :
81 : /*
82 : * Memory context holding the list of TokenizedAuthLines when parsing
83 : * HBA or ident configuration files. This is created when opening the first
84 : * file (depth of CONF_FILE_START_DEPTH).
85 : */
86 : static MemoryContext tokenize_context = NULL;
87 :
88 : /*
89 : * pre-parsed content of HBA config file: list of HbaLine structs.
90 : * parsed_hba_context is the memory context where it lives.
91 : */
92 : static List *parsed_hba_lines = NIL;
93 : static MemoryContext parsed_hba_context = NULL;
94 :
95 : /*
96 : * pre-parsed content of ident mapping file: list of IdentLine structs.
97 : * parsed_ident_context is the memory context where it lives.
98 : */
99 : static List *parsed_ident_lines = NIL;
100 : static MemoryContext parsed_ident_context = NULL;
101 :
102 : /*
103 : * The following character array represents the names of the authentication
104 : * methods that are supported by PostgreSQL.
105 : *
106 : * Note: keep this in sync with the UserAuth enum in hba.h.
107 : */
108 : static const char *const UserAuthName[] =
109 : {
110 : "reject",
111 : "implicit reject", /* Not a user-visible option */
112 : "trust",
113 : "ident",
114 : "password",
115 : "md5",
116 : "scram-sha-256",
117 : "gss",
118 : "sspi",
119 : "pam",
120 : "bsd",
121 : "ldap",
122 : "cert",
123 : "radius",
124 : "peer"
125 : };
126 :
127 : /*
128 : * Make sure UserAuthName[] tracks additions to the UserAuth enum
129 : */
130 : StaticAssertDecl(lengthof(UserAuthName) == USER_AUTH_LAST + 1,
131 : "UserAuthName[] must match the UserAuth enum");
132 :
133 :
134 : static List *tokenize_expand_file(List *tokens, const char *outer_filename,
135 : const char *inc_filename, int elevel,
136 : int depth, char **err_msg);
137 : static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
138 : int elevel, char **err_msg);
139 : static int regcomp_auth_token(AuthToken *token, char *filename, int line_num,
140 : char **err_msg, int elevel);
141 : static int regexec_auth_token(const char *match, AuthToken *token,
142 : size_t nmatch, regmatch_t pmatch[]);
143 : static void tokenize_error_callback(void *arg);
144 :
145 :
146 : /*
147 : * isblank() exists in the ISO C99 spec, but it's not very portable yet,
148 : * so provide our own version.
149 : */
150 : bool
151 1111354 : pg_isblank(const char c)
152 : {
153 1111354 : return c == ' ' || c == '\t' || c == '\r';
154 : }
155 :
156 :
157 : /*
158 : * Grab one token out of the string pointed to by *lineptr.
159 : *
160 : * Tokens are strings of non-blank characters bounded by blank characters,
161 : * commas, beginning of line, and end of line. Blank means space or tab.
162 : *
163 : * Tokens can be delimited by double quotes (this allows the inclusion of
164 : * blanks or '#', but not newlines). As in SQL, write two double-quotes
165 : * to represent a double quote.
166 : *
167 : * Comments (started by an unquoted '#') are skipped, i.e. the remainder
168 : * of the line is ignored.
169 : *
170 : * (Note that line continuation processing happens before tokenization.
171 : * Thus, if a continuation occurs within quoted text or a comment, the
172 : * quoted text or comment is considered to continue to the next line.)
173 : *
174 : * The token, if any, is returned at *buf (a buffer of size bufsz), and
175 : * *lineptr is advanced past the token.
176 : *
177 : * Also, we set *initial_quote to indicate whether there was quoting before
178 : * the first character. (We use that to prevent "@x" from being treated
179 : * as a file inclusion request. Note that @"x" should be so treated;
180 : * we want to allow that to support embedded spaces in file paths.)
181 : *
182 : * We set *terminating_comma to indicate whether the token is terminated by a
183 : * comma (which is not returned).
184 : *
185 : * In event of an error, log a message at ereport level elevel, and also
186 : * set *err_msg to a string describing the error. Currently the only
187 : * possible error is token too long for buf.
188 : *
189 : * If successful: store null-terminated token at *buf and return true.
190 : * If no more tokens on line: set *buf = '\0' and return false.
191 : * If error: fill buf with truncated or misformatted token and return false.
192 : */
193 : static bool
194 276178 : next_token(char **lineptr, char *buf, int bufsz,
195 : bool *initial_quote, bool *terminating_comma,
196 : int elevel, char **err_msg)
197 : {
198 : int c;
199 276178 : char *start_buf = buf;
200 276178 : char *end_buf = buf + (bufsz - 1);
201 276178 : bool in_quote = false;
202 276178 : bool was_quote = false;
203 276178 : bool saw_quote = false;
204 :
205 : Assert(end_buf > start_buf);
206 :
207 276178 : *initial_quote = false;
208 276178 : *terminating_comma = false;
209 :
210 : /* Move over any whitespace and commas preceding the next token */
211 617246 : while ((c = (*(*lineptr)++)) != '\0' && (pg_isblank(c) || c == ','))
212 : ;
213 :
214 : /*
215 : * Build a token in buf of next characters up to EOL, unquoted comma, or
216 : * unquoted whitespace.
217 : */
218 502332 : while (c != '\0' &&
219 494108 : (!pg_isblank(c) || in_quote))
220 : {
221 : /* skip comments to EOL */
222 463816 : if (c == '#' && !in_quote)
223 : {
224 9880328 : while ((c = (*(*lineptr)++)) != '\0')
225 : ;
226 237646 : break;
227 : }
228 :
229 226170 : if (buf >= end_buf)
230 : {
231 0 : *buf = '\0';
232 0 : ereport(elevel,
233 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
234 : errmsg("authentication file token too long, skipping: \"%s\"",
235 : start_buf)));
236 0 : *err_msg = "authentication file token too long";
237 : /* Discard remainder of line */
238 0 : while ((c = (*(*lineptr)++)) != '\0')
239 : ;
240 : /* Un-eat the '\0', in case we're called again */
241 0 : (*lineptr)--;
242 0 : return false;
243 : }
244 :
245 : /* we do not pass back a terminating comma in the token */
246 226170 : if (c == ',' && !in_quote)
247 : {
248 16 : *terminating_comma = true;
249 16 : break;
250 : }
251 :
252 226154 : if (c != '"' || was_quote)
253 225840 : *buf++ = c;
254 :
255 : /* Literal double-quote is two double-quotes */
256 226154 : if (in_quote && c == '"')
257 156 : was_quote = !was_quote;
258 : else
259 225998 : was_quote = false;
260 :
261 226154 : if (c == '"')
262 : {
263 314 : in_quote = !in_quote;
264 314 : saw_quote = true;
265 314 : if (buf == start_buf)
266 94 : *initial_quote = true;
267 : }
268 :
269 226154 : c = *(*lineptr)++;
270 : }
271 :
272 : /*
273 : * Un-eat the char right after the token (critical in case it is '\0',
274 : * else next call will read past end of string).
275 : */
276 276178 : (*lineptr)--;
277 :
278 276178 : *buf = '\0';
279 :
280 276178 : return (saw_quote || buf > start_buf);
281 : }
282 :
283 : /*
284 : * Construct a palloc'd AuthToken struct, copying the given string.
285 : */
286 : static AuthToken *
287 54912 : make_auth_token(const char *token, bool quoted)
288 : {
289 : AuthToken *authtoken;
290 : int toklen;
291 :
292 54912 : toklen = strlen(token);
293 : /* we copy string into same palloc block as the struct */
294 54912 : authtoken = (AuthToken *) palloc0(sizeof(AuthToken) + toklen + 1);
295 54912 : authtoken->string = (char *) authtoken + sizeof(AuthToken);
296 54912 : authtoken->quoted = quoted;
297 54912 : authtoken->regex = NULL;
298 54912 : memcpy(authtoken->string, token, toklen + 1);
299 :
300 54912 : return authtoken;
301 : }
302 :
303 : /*
304 : * Free an AuthToken, that may include a regular expression that needs
305 : * to be cleaned up explicitly.
306 : */
307 : static void
308 4 : free_auth_token(AuthToken *token)
309 : {
310 4 : if (token_has_regexp(token))
311 0 : pg_regfree(token->regex);
312 4 : }
313 :
314 : /*
315 : * Copy a AuthToken struct into freshly palloc'd memory.
316 : */
317 : static AuthToken *
318 16380 : copy_auth_token(AuthToken *in)
319 : {
320 16380 : AuthToken *out = make_auth_token(in->string, in->quoted);
321 :
322 16380 : return out;
323 : }
324 :
325 : /*
326 : * Compile the regular expression and store it in the AuthToken given in
327 : * input. Returns the result of pg_regcomp(). On error, the details are
328 : * stored in "err_msg".
329 : */
330 : static int
331 16380 : regcomp_auth_token(AuthToken *token, char *filename, int line_num,
332 : char **err_msg, int elevel)
333 : {
334 : pg_wchar *wstr;
335 : int wlen;
336 : int rc;
337 :
338 : Assert(token->regex == NULL);
339 :
340 16380 : if (token->string[0] != '/')
341 16292 : return 0; /* nothing to compile */
342 :
343 88 : token->regex = (regex_t *) palloc0(sizeof(regex_t));
344 88 : wstr = palloc((strlen(token->string + 1) + 1) * sizeof(pg_wchar));
345 88 : wlen = pg_mb2wchar_with_len(token->string + 1,
346 88 : wstr, strlen(token->string + 1));
347 :
348 88 : rc = pg_regcomp(token->regex, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
349 :
350 88 : if (rc)
351 : {
352 : char errstr[100];
353 :
354 0 : pg_regerror(rc, token->regex, errstr, sizeof(errstr));
355 0 : ereport(elevel,
356 : (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
357 : errmsg("invalid regular expression \"%s\": %s",
358 : token->string + 1, errstr),
359 : errcontext("line %d of configuration file \"%s\"",
360 : line_num, filename)));
361 :
362 0 : *err_msg = psprintf("invalid regular expression \"%s\": %s",
363 0 : token->string + 1, errstr);
364 : }
365 :
366 88 : pfree(wstr);
367 88 : return rc;
368 : }
369 :
370 : /*
371 : * Execute a regular expression computed in an AuthToken, checking for a match
372 : * with the string specified in "match". The caller may optionally give an
373 : * array to store the matches. Returns the result of pg_regexec().
374 : */
375 : static int
376 50 : regexec_auth_token(const char *match, AuthToken *token, size_t nmatch,
377 : regmatch_t pmatch[])
378 : {
379 : pg_wchar *wmatchstr;
380 : int wmatchlen;
381 : int r;
382 :
383 : Assert(token->string[0] == '/' && token->regex);
384 :
385 50 : wmatchstr = palloc((strlen(match) + 1) * sizeof(pg_wchar));
386 50 : wmatchlen = pg_mb2wchar_with_len(match, wmatchstr, strlen(match));
387 :
388 50 : r = pg_regexec(token->regex, wmatchstr, wmatchlen, 0, NULL, nmatch, pmatch, 0);
389 :
390 50 : pfree(wmatchstr);
391 50 : return r;
392 : }
393 :
394 : /*
395 : * Tokenize one HBA field from a line, handling file inclusion and comma lists.
396 : *
397 : * filename: current file's pathname (needed to resolve relative pathnames)
398 : * *lineptr: current line pointer, which will be advanced past field
399 : *
400 : * In event of an error, log a message at ereport level elevel, and also
401 : * set *err_msg to a string describing the error. Note that the result
402 : * may be non-NIL anyway, so *err_msg must be tested to determine whether
403 : * there was an error.
404 : *
405 : * The result is a List of AuthToken structs, one for each token in the field,
406 : * or NIL if we reached EOL.
407 : */
408 : static List *
409 276162 : next_field_expand(const char *filename, char **lineptr,
410 : int elevel, int depth, char **err_msg)
411 : {
412 : char buf[MAX_TOKEN];
413 : bool trailing_comma;
414 : bool initial_quote;
415 276162 : List *tokens = NIL;
416 :
417 : do
418 : {
419 276178 : if (!next_token(lineptr, buf, sizeof(buf),
420 : &initial_quote, &trailing_comma,
421 : elevel, err_msg))
422 237646 : break;
423 :
424 : /* Is this referencing a file? */
425 38532 : if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
426 4 : tokens = tokenize_expand_file(tokens, filename, buf + 1,
427 : elevel, depth + 1, err_msg);
428 : else
429 : {
430 : MemoryContext oldcxt;
431 :
432 : /*
433 : * lappend() may do its own allocations, so move to the context
434 : * for the list of tokens.
435 : */
436 38528 : oldcxt = MemoryContextSwitchTo(tokenize_context);
437 38528 : tokens = lappend(tokens, make_auth_token(buf, initial_quote));
438 38528 : MemoryContextSwitchTo(oldcxt);
439 : }
440 38532 : } while (trailing_comma && (*err_msg == NULL));
441 :
442 276162 : return tokens;
443 : }
444 :
445 : /*
446 : * tokenize_include_file
447 : * Include a file from another file into an hba "field".
448 : *
449 : * Opens and tokenises a file included from another authentication file
450 : * with one of the include records ("include", "include_if_exists" or
451 : * "include_dir"), and assign all values found to an existing list of
452 : * list of AuthTokens.
453 : *
454 : * All new tokens are allocated in the memory context dedicated to the
455 : * tokenization, aka tokenize_context.
456 : *
457 : * If missing_ok is true, ignore a missing file.
458 : *
459 : * In event of an error, log a message at ereport level elevel, and also
460 : * set *err_msg to a string describing the error. Note that the result
461 : * may be non-NIL anyway, so *err_msg must be tested to determine whether
462 : * there was an error.
463 : */
464 : static void
465 42 : tokenize_include_file(const char *outer_filename,
466 : const char *inc_filename,
467 : List **tok_lines,
468 : int elevel,
469 : int depth,
470 : bool missing_ok,
471 : char **err_msg)
472 : {
473 : char *inc_fullname;
474 : FILE *inc_file;
475 :
476 42 : inc_fullname = AbsoluteConfigLocation(inc_filename, outer_filename);
477 42 : inc_file = open_auth_file(inc_fullname, elevel, depth, err_msg);
478 :
479 42 : if (!inc_file)
480 : {
481 6 : if (errno == ENOENT && missing_ok)
482 : {
483 6 : ereport(elevel,
484 : (errmsg("skipping missing authentication file \"%s\"",
485 : inc_fullname)));
486 6 : *err_msg = NULL;
487 6 : pfree(inc_fullname);
488 6 : return;
489 : }
490 :
491 : /* error in err_msg, so leave and report */
492 0 : pfree(inc_fullname);
493 : Assert(err_msg);
494 0 : return;
495 : }
496 :
497 36 : tokenize_auth_file(inc_fullname, inc_file, tok_lines, elevel,
498 : depth);
499 36 : free_auth_file(inc_file, depth);
500 36 : pfree(inc_fullname);
501 : }
502 :
503 : /*
504 : * tokenize_expand_file
505 : * Expand a file included from another file into an hba "field"
506 : *
507 : * Opens and tokenises a file included from another HBA config file with @,
508 : * and returns all values found therein as a flat list of AuthTokens. If a
509 : * @-token or include record is found, recursively expand it. The newly
510 : * read tokens are appended to "tokens" (so that foo,bar,@baz does what you
511 : * expect). All new tokens are allocated in the memory context dedicated
512 : * to the list of TokenizedAuthLines, aka tokenize_context.
513 : *
514 : * In event of an error, log a message at ereport level elevel, and also
515 : * set *err_msg to a string describing the error. Note that the result
516 : * may be non-NIL anyway, so *err_msg must be tested to determine whether
517 : * there was an error.
518 : */
519 : static List *
520 4 : tokenize_expand_file(List *tokens,
521 : const char *outer_filename,
522 : const char *inc_filename,
523 : int elevel,
524 : int depth,
525 : char **err_msg)
526 : {
527 : char *inc_fullname;
528 : FILE *inc_file;
529 4 : List *inc_lines = NIL;
530 : ListCell *inc_line;
531 :
532 4 : inc_fullname = AbsoluteConfigLocation(inc_filename, outer_filename);
533 4 : inc_file = open_auth_file(inc_fullname, elevel, depth, err_msg);
534 :
535 4 : if (inc_file == NULL)
536 : {
537 : /* error already logged */
538 0 : pfree(inc_fullname);
539 0 : return tokens;
540 : }
541 :
542 : /*
543 : * There is possible recursion here if the file contains @ or an include
544 : * record.
545 : */
546 4 : tokenize_auth_file(inc_fullname, inc_file, &inc_lines, elevel,
547 : depth);
548 :
549 4 : pfree(inc_fullname);
550 :
551 : /*
552 : * Move all the tokens found in the file to the tokens list. These are
553 : * already saved in tokenize_context.
554 : */
555 12 : foreach(inc_line, inc_lines)
556 : {
557 8 : TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(inc_line);
558 : ListCell *inc_field;
559 :
560 : /* If any line has an error, propagate that up to caller */
561 8 : if (tok_line->err_msg)
562 : {
563 0 : *err_msg = pstrdup(tok_line->err_msg);
564 0 : break;
565 : }
566 :
567 16 : foreach(inc_field, tok_line->fields)
568 : {
569 8 : List *inc_tokens = lfirst(inc_field);
570 : ListCell *inc_token;
571 :
572 16 : foreach(inc_token, inc_tokens)
573 : {
574 8 : AuthToken *token = lfirst(inc_token);
575 : MemoryContext oldcxt;
576 :
577 : /*
578 : * lappend() may do its own allocations, so move to the
579 : * context for the list of tokens.
580 : */
581 8 : oldcxt = MemoryContextSwitchTo(tokenize_context);
582 8 : tokens = lappend(tokens, token);
583 8 : MemoryContextSwitchTo(oldcxt);
584 : }
585 : }
586 : }
587 :
588 4 : free_auth_file(inc_file, depth);
589 4 : return tokens;
590 : }
591 :
592 : /*
593 : * free_auth_file
594 : * Free a file opened by open_auth_file().
595 : */
596 : void
597 2838 : free_auth_file(FILE *file, int depth)
598 : {
599 2838 : FreeFile(file);
600 :
601 : /* If this is the last cleanup, remove the tokenization context */
602 2838 : if (depth == CONF_FILE_START_DEPTH)
603 : {
604 2798 : MemoryContextDelete(tokenize_context);
605 2798 : tokenize_context = NULL;
606 : }
607 2838 : }
608 :
609 : /*
610 : * open_auth_file
611 : * Open the given file.
612 : *
613 : * filename: the absolute path to the target file
614 : * elevel: message logging level
615 : * depth: recursion level when opening the file
616 : * err_msg: details about the error
617 : *
618 : * Return value is the opened file. On error, returns NULL with details
619 : * about the error stored in "err_msg".
620 : */
621 : FILE *
622 2844 : open_auth_file(const char *filename, int elevel, int depth,
623 : char **err_msg)
624 : {
625 : FILE *file;
626 :
627 : /*
628 : * Reject too-deep include nesting depth. This is just a safety check to
629 : * avoid dumping core due to stack overflow if an include file loops back
630 : * to itself. The maximum nesting depth is pretty arbitrary.
631 : */
632 2844 : if (depth > CONF_FILE_MAX_DEPTH)
633 : {
634 0 : ereport(elevel,
635 : (errcode_for_file_access(),
636 : errmsg("could not open file \"%s\": maximum nesting depth exceeded",
637 : filename)));
638 0 : if (err_msg)
639 0 : *err_msg = psprintf("could not open file \"%s\": maximum nesting depth exceeded",
640 : filename);
641 0 : return NULL;
642 : }
643 :
644 2844 : file = AllocateFile(filename, "r");
645 2844 : if (file == NULL)
646 : {
647 6 : int save_errno = errno;
648 :
649 6 : ereport(elevel,
650 : (errcode_for_file_access(),
651 : errmsg("could not open file \"%s\": %m",
652 : filename)));
653 6 : if (err_msg)
654 6 : *err_msg = psprintf("could not open file \"%s\": %s",
655 : filename, strerror(save_errno));
656 : /* the caller may care about some specific errno */
657 6 : errno = save_errno;
658 6 : return NULL;
659 : }
660 :
661 : /*
662 : * When opening the top-level file, create the memory context used for the
663 : * tokenization. This will be closed with this file when coming back to
664 : * this level of cleanup.
665 : */
666 2838 : if (depth == CONF_FILE_START_DEPTH)
667 : {
668 : /*
669 : * A context may be present, but assume that it has been eliminated
670 : * already.
671 : */
672 2798 : tokenize_context = AllocSetContextCreate(CurrentMemoryContext,
673 : "tokenize_context",
674 : ALLOCSET_START_SMALL_SIZES);
675 : }
676 :
677 2838 : return file;
678 : }
679 :
680 : /*
681 : * error context callback for tokenize_auth_file()
682 : */
683 : static void
684 8 : tokenize_error_callback(void *arg)
685 : {
686 8 : tokenize_error_callback_arg *callback_arg = (tokenize_error_callback_arg *) arg;
687 :
688 8 : errcontext("line %d of configuration file \"%s\"",
689 : callback_arg->linenum, callback_arg->filename);
690 8 : }
691 :
692 : /*
693 : * tokenize_auth_file
694 : * Tokenize the given file.
695 : *
696 : * The output is a list of TokenizedAuthLine structs; see the struct definition
697 : * in libpq/hba.h. This is the central piece in charge of parsing the
698 : * authentication files. All the operations of this function happen in its own
699 : * local memory context, easing the cleanup of anything allocated here. This
700 : * matters a lot when reloading authentication files in the postmaster.
701 : *
702 : * filename: the absolute path to the target file
703 : * file: the already-opened target file
704 : * tok_lines: receives output list, saved into tokenize_context
705 : * elevel: message logging level
706 : * depth: level of recursion when tokenizing the target file
707 : *
708 : * Errors are reported by logging messages at ereport level elevel and by
709 : * adding TokenizedAuthLine structs containing non-null err_msg fields to the
710 : * output list.
711 : */
712 : void
713 2838 : tokenize_auth_file(const char *filename, FILE *file, List **tok_lines,
714 : int elevel, int depth)
715 : {
716 2838 : int line_number = 1;
717 : StringInfoData buf;
718 : MemoryContext linecxt;
719 : MemoryContext funccxt; /* context of this function's caller */
720 : ErrorContextCallback tokenerrcontext;
721 : tokenize_error_callback_arg callback_arg;
722 :
723 : Assert(tokenize_context);
724 :
725 2838 : callback_arg.filename = filename;
726 2838 : callback_arg.linenum = line_number;
727 :
728 2838 : tokenerrcontext.callback = tokenize_error_callback;
729 2838 : tokenerrcontext.arg = (void *) &callback_arg;
730 2838 : tokenerrcontext.previous = error_context_stack;
731 2838 : error_context_stack = &tokenerrcontext;
732 :
733 : /*
734 : * Do all the local tokenization in its own context, to ease the cleanup
735 : * of any memory allocated while tokenizing.
736 : */
737 2838 : linecxt = AllocSetContextCreate(CurrentMemoryContext,
738 : "tokenize_auth_file",
739 : ALLOCSET_SMALL_SIZES);
740 2838 : funccxt = MemoryContextSwitchTo(linecxt);
741 :
742 2838 : initStringInfo(&buf);
743 :
744 2838 : if (depth == CONF_FILE_START_DEPTH)
745 2798 : *tok_lines = NIL;
746 :
747 259932 : while (!feof(file) && !ferror(file))
748 : {
749 : TokenizedAuthLine *tok_line;
750 : MemoryContext oldcxt;
751 : char *lineptr;
752 257094 : List *current_line = NIL;
753 257094 : char *err_msg = NULL;
754 257094 : int last_backslash_buflen = 0;
755 257094 : int continuations = 0;
756 :
757 : /* Collect the next input line, handling backslash continuations */
758 257094 : resetStringInfo(&buf);
759 :
760 257128 : while (pg_get_line_append(file, &buf, NULL))
761 : {
762 : /* Strip trailing newline, including \r in case we're on Windows */
763 254290 : buf.len = pg_strip_crlf(buf.data);
764 :
765 : /*
766 : * Check for backslash continuation. The backslash must be after
767 : * the last place we found a continuation, else two backslashes
768 : * followed by two \n's would behave surprisingly.
769 : */
770 254290 : if (buf.len > last_backslash_buflen &&
771 245904 : buf.data[buf.len - 1] == '\\')
772 : {
773 : /* Continuation, so strip it and keep reading */
774 34 : buf.data[--buf.len] = '\0';
775 34 : last_backslash_buflen = buf.len;
776 34 : continuations++;
777 34 : continue;
778 : }
779 :
780 : /* Nope, so we have the whole line */
781 254256 : break;
782 : }
783 :
784 257094 : if (ferror(file))
785 : {
786 : /* I/O error! */
787 0 : int save_errno = errno;
788 :
789 0 : ereport(elevel,
790 : (errcode_for_file_access(),
791 : errmsg("could not read file \"%s\": %m", filename)));
792 0 : err_msg = psprintf("could not read file \"%s\": %s",
793 : filename, strerror(save_errno));
794 0 : break;
795 : }
796 :
797 : /* Parse fields */
798 257094 : lineptr = buf.data;
799 533256 : while (*lineptr && err_msg == NULL)
800 : {
801 : List *current_field;
802 :
803 276162 : current_field = next_field_expand(filename, &lineptr,
804 : elevel, depth, &err_msg);
805 : /* add field to line, unless we are at EOL or comment start */
806 276162 : if (current_field != NIL)
807 : {
808 : /*
809 : * lappend() may do its own allocations, so move to the
810 : * context for the list of tokens.
811 : */
812 38516 : oldcxt = MemoryContextSwitchTo(tokenize_context);
813 38516 : current_line = lappend(current_line, current_field);
814 38516 : MemoryContextSwitchTo(oldcxt);
815 : }
816 : }
817 :
818 : /*
819 : * Reached EOL; no need to emit line to TokenizedAuthLine list if it's
820 : * boring.
821 : */
822 257094 : if (current_line == NIL && err_msg == NULL)
823 248870 : goto next_line;
824 :
825 : /* If the line is valid, check if that's an include directive */
826 8224 : if (err_msg == NULL && list_length(current_line) == 2)
827 : {
828 : AuthToken *first,
829 : *second;
830 :
831 36 : first = linitial(linitial_node(List, current_line));
832 36 : second = linitial(lsecond_node(List, current_line));
833 :
834 36 : if (strcmp(first->string, "include") == 0)
835 : {
836 18 : tokenize_include_file(filename, second->string, tok_lines,
837 : elevel, depth + 1, false, &err_msg);
838 :
839 18 : if (err_msg)
840 0 : goto process_line;
841 :
842 : /*
843 : * tokenize_auth_file() has taken care of creating the
844 : * TokenizedAuthLines.
845 : */
846 18 : goto next_line;
847 : }
848 18 : else if (strcmp(first->string, "include_dir") == 0)
849 : {
850 : char **filenames;
851 6 : char *dir_name = second->string;
852 : int num_filenames;
853 : StringInfoData err_buf;
854 :
855 6 : filenames = GetConfFilesInDir(dir_name, filename, elevel,
856 : &num_filenames, &err_msg);
857 :
858 6 : if (!filenames)
859 : {
860 : /* the error is in err_msg, so create an entry */
861 0 : goto process_line;
862 : }
863 :
864 6 : initStringInfo(&err_buf);
865 18 : for (int i = 0; i < num_filenames; i++)
866 : {
867 12 : tokenize_include_file(filename, filenames[i], tok_lines,
868 : elevel, depth + 1, false, &err_msg);
869 : /* cumulate errors if any */
870 12 : if (err_msg)
871 : {
872 0 : if (err_buf.len > 0)
873 0 : appendStringInfoChar(&err_buf, '\n');
874 0 : appendStringInfoString(&err_buf, err_msg);
875 : }
876 : }
877 :
878 : /* clean up things */
879 18 : for (int i = 0; i < num_filenames; i++)
880 12 : pfree(filenames[i]);
881 6 : pfree(filenames);
882 :
883 : /*
884 : * If there were no errors, the line is fully processed,
885 : * bypass the general TokenizedAuthLine processing.
886 : */
887 6 : if (err_buf.len == 0)
888 6 : goto next_line;
889 :
890 : /* Otherwise, process the cumulated errors, if any. */
891 0 : err_msg = err_buf.data;
892 0 : goto process_line;
893 : }
894 12 : else if (strcmp(first->string, "include_if_exists") == 0)
895 : {
896 :
897 12 : tokenize_include_file(filename, second->string, tok_lines,
898 : elevel, depth + 1, true, &err_msg);
899 12 : if (err_msg)
900 0 : goto process_line;
901 :
902 : /*
903 : * tokenize_auth_file() has taken care of creating the
904 : * TokenizedAuthLines.
905 : */
906 12 : goto next_line;
907 : }
908 : }
909 :
910 8188 : process_line:
911 :
912 : /*
913 : * General processing: report the error if any and emit line to the
914 : * TokenizedAuthLine. This is saved in the memory context dedicated
915 : * to this list.
916 : */
917 8188 : oldcxt = MemoryContextSwitchTo(tokenize_context);
918 8188 : tok_line = (TokenizedAuthLine *) palloc0(sizeof(TokenizedAuthLine));
919 8188 : tok_line->fields = current_line;
920 8188 : tok_line->file_name = pstrdup(filename);
921 8188 : tok_line->line_num = line_number;
922 8188 : tok_line->raw_line = pstrdup(buf.data);
923 8188 : tok_line->err_msg = err_msg ? pstrdup(err_msg) : NULL;
924 8188 : *tok_lines = lappend(*tok_lines, tok_line);
925 8188 : MemoryContextSwitchTo(oldcxt);
926 :
927 257094 : next_line:
928 257094 : line_number += continuations + 1;
929 257094 : callback_arg.linenum = line_number;
930 : }
931 :
932 2838 : MemoryContextSwitchTo(funccxt);
933 2838 : MemoryContextDelete(linecxt);
934 :
935 2838 : error_context_stack = tokenerrcontext.previous;
936 2838 : }
937 :
938 :
939 : /*
940 : * Does user belong to role?
941 : *
942 : * userid is the OID of the role given as the attempted login identifier.
943 : * We check to see if it is a member of the specified role name.
944 : */
945 : static bool
946 26 : is_member(Oid userid, const char *role)
947 : {
948 : Oid roleid;
949 :
950 26 : if (!OidIsValid(userid))
951 0 : return false; /* if user not exist, say "no" */
952 :
953 26 : roleid = get_role_oid(role, true);
954 :
955 26 : if (!OidIsValid(roleid))
956 0 : return false; /* if target role not exist, say "no" */
957 :
958 : /*
959 : * See if user is directly or indirectly a member of role. For this
960 : * purpose, a superuser is not considered to be automatically a member of
961 : * the role, so group auth only applies to explicit membership.
962 : */
963 26 : return is_member_of_role_nosuper(userid, roleid);
964 : }
965 :
966 : /*
967 : * Check AuthToken list for a match to role, allowing group names.
968 : *
969 : * Each AuthToken listed is checked one-by-one. Keywords are processed
970 : * first (these cannot have regular expressions), followed by regular
971 : * expressions (if any), the case-insensitive match (if requested) and
972 : * the exact match.
973 : */
974 : static bool
975 18292 : check_role(const char *role, Oid roleid, List *tokens, bool case_insensitive)
976 : {
977 : ListCell *cell;
978 : AuthToken *tok;
979 :
980 18420 : foreach(cell, tokens)
981 : {
982 18300 : tok = lfirst(cell);
983 18300 : if (token_is_member_check(tok))
984 : {
985 16 : if (is_member(roleid, tok->string + 1))
986 18172 : return true;
987 : }
988 18284 : else if (token_is_keyword(tok, "all"))
989 18120 : return true;
990 164 : else if (token_has_regexp(tok))
991 : {
992 18 : if (regexec_auth_token(role, tok, 0, NULL) == REG_OKAY)
993 10 : return true;
994 : }
995 146 : else if (case_insensitive)
996 : {
997 0 : if (token_matches_insensitive(tok, role))
998 0 : return true;
999 : }
1000 146 : else if (token_matches(tok, role))
1001 28 : return true;
1002 : }
1003 120 : return false;
1004 : }
1005 :
1006 : /*
1007 : * Check to see if db/role combination matches AuthToken list.
1008 : *
1009 : * Each AuthToken listed is checked one-by-one. Keywords are checked
1010 : * first (these cannot have regular expressions), followed by regular
1011 : * expressions (if any) and the exact match.
1012 : */
1013 : static bool
1014 19226 : check_db(const char *dbname, const char *role, Oid roleid, List *tokens)
1015 : {
1016 : ListCell *cell;
1017 : AuthToken *tok;
1018 :
1019 20208 : foreach(cell, tokens)
1020 : {
1021 19234 : tok = lfirst(cell);
1022 19234 : if (am_walsender && !am_db_walsender)
1023 : {
1024 : /*
1025 : * physical replication walsender connections can only match
1026 : * replication keyword
1027 : */
1028 1364 : if (token_is_keyword(tok, "replication"))
1029 18252 : return true;
1030 : }
1031 17870 : else if (token_is_keyword(tok, "all"))
1032 17290 : return true;
1033 580 : else if (token_is_keyword(tok, "sameuser"))
1034 : {
1035 0 : if (strcmp(dbname, role) == 0)
1036 0 : return true;
1037 : }
1038 580 : else if (token_is_keyword(tok, "samegroup") ||
1039 576 : token_is_keyword(tok, "samerole"))
1040 : {
1041 10 : if (is_member(roleid, dbname))
1042 8 : return true;
1043 : }
1044 570 : else if (token_is_keyword(tok, "replication"))
1045 0 : continue; /* never match this if not walsender */
1046 570 : else if (token_has_regexp(tok))
1047 : {
1048 8 : if (regexec_auth_token(dbname, tok, 0, NULL) == REG_OKAY)
1049 2 : return true;
1050 : }
1051 562 : else if (token_matches(tok, dbname))
1052 270 : return true;
1053 : }
1054 974 : return false;
1055 : }
1056 :
1057 : static bool
1058 0 : ipv4eq(struct sockaddr_in *a, struct sockaddr_in *b)
1059 : {
1060 0 : return (a->sin_addr.s_addr == b->sin_addr.s_addr);
1061 : }
1062 :
1063 : static bool
1064 0 : ipv6eq(struct sockaddr_in6 *a, struct sockaddr_in6 *b)
1065 : {
1066 : int i;
1067 :
1068 0 : for (i = 0; i < 16; i++)
1069 0 : if (a->sin6_addr.s6_addr[i] != b->sin6_addr.s6_addr[i])
1070 0 : return false;
1071 :
1072 0 : return true;
1073 : }
1074 :
1075 : /*
1076 : * Check whether host name matches pattern.
1077 : */
1078 : static bool
1079 0 : hostname_match(const char *pattern, const char *actual_hostname)
1080 : {
1081 0 : if (pattern[0] == '.') /* suffix match */
1082 : {
1083 0 : size_t plen = strlen(pattern);
1084 0 : size_t hlen = strlen(actual_hostname);
1085 :
1086 0 : if (hlen < plen)
1087 0 : return false;
1088 :
1089 0 : return (pg_strcasecmp(pattern, actual_hostname + (hlen - plen)) == 0);
1090 : }
1091 : else
1092 0 : return (pg_strcasecmp(pattern, actual_hostname) == 0);
1093 : }
1094 :
1095 : /*
1096 : * Check to see if a connecting IP matches a given host name.
1097 : */
1098 : static bool
1099 0 : check_hostname(hbaPort *port, const char *hostname)
1100 : {
1101 : struct addrinfo *gai_result,
1102 : *gai;
1103 : int ret;
1104 : bool found;
1105 :
1106 : /* Quick out if remote host name already known bad */
1107 0 : if (port->remote_hostname_resolv < 0)
1108 0 : return false;
1109 :
1110 : /* Lookup remote host name if not already done */
1111 0 : if (!port->remote_hostname)
1112 : {
1113 : char remote_hostname[NI_MAXHOST];
1114 :
1115 0 : ret = pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
1116 : remote_hostname, sizeof(remote_hostname),
1117 : NULL, 0,
1118 : NI_NAMEREQD);
1119 0 : if (ret != 0)
1120 : {
1121 : /* remember failure; don't complain in the postmaster log yet */
1122 0 : port->remote_hostname_resolv = -2;
1123 0 : port->remote_hostname_errcode = ret;
1124 0 : return false;
1125 : }
1126 :
1127 0 : port->remote_hostname = pstrdup(remote_hostname);
1128 : }
1129 :
1130 : /* Now see if remote host name matches this pg_hba line */
1131 0 : if (!hostname_match(hostname, port->remote_hostname))
1132 0 : return false;
1133 :
1134 : /* If we already verified the forward lookup, we're done */
1135 0 : if (port->remote_hostname_resolv == +1)
1136 0 : return true;
1137 :
1138 : /* Lookup IP from host name and check against original IP */
1139 0 : ret = getaddrinfo(port->remote_hostname, NULL, NULL, &gai_result);
1140 0 : if (ret != 0)
1141 : {
1142 : /* remember failure; don't complain in the postmaster log yet */
1143 0 : port->remote_hostname_resolv = -2;
1144 0 : port->remote_hostname_errcode = ret;
1145 0 : return false;
1146 : }
1147 :
1148 0 : found = false;
1149 0 : for (gai = gai_result; gai; gai = gai->ai_next)
1150 : {
1151 0 : if (gai->ai_addr->sa_family == port->raddr.addr.ss_family)
1152 : {
1153 0 : if (gai->ai_addr->sa_family == AF_INET)
1154 : {
1155 0 : if (ipv4eq((struct sockaddr_in *) gai->ai_addr,
1156 0 : (struct sockaddr_in *) &port->raddr.addr))
1157 : {
1158 0 : found = true;
1159 0 : break;
1160 : }
1161 : }
1162 0 : else if (gai->ai_addr->sa_family == AF_INET6)
1163 : {
1164 0 : if (ipv6eq((struct sockaddr_in6 *) gai->ai_addr,
1165 0 : (struct sockaddr_in6 *) &port->raddr.addr))
1166 : {
1167 0 : found = true;
1168 0 : break;
1169 : }
1170 : }
1171 : }
1172 : }
1173 :
1174 0 : if (gai_result)
1175 0 : freeaddrinfo(gai_result);
1176 :
1177 0 : if (!found)
1178 0 : elog(DEBUG2, "pg_hba.conf host name \"%s\" rejected because address resolution did not return a match with IP address of client",
1179 : hostname);
1180 :
1181 0 : port->remote_hostname_resolv = found ? +1 : -1;
1182 :
1183 0 : return found;
1184 : }
1185 :
1186 : /*
1187 : * Check to see if a connecting IP matches the given address and netmask.
1188 : */
1189 : static bool
1190 1126 : check_ip(SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
1191 : {
1192 1968 : if (raddr->addr.ss_family == addr->sa_family &&
1193 842 : pg_range_sockaddr(&raddr->addr,
1194 : (struct sockaddr_storage *) addr,
1195 : (struct sockaddr_storage *) mask))
1196 842 : return true;
1197 284 : return false;
1198 : }
1199 :
1200 : /*
1201 : * pg_foreach_ifaddr callback: does client addr match this machine interface?
1202 : */
1203 : static void
1204 0 : check_network_callback(struct sockaddr *addr, struct sockaddr *netmask,
1205 : void *cb_data)
1206 : {
1207 0 : check_network_data *cn = (check_network_data *) cb_data;
1208 : struct sockaddr_storage mask;
1209 :
1210 : /* Already found a match? */
1211 0 : if (cn->result)
1212 0 : return;
1213 :
1214 0 : if (cn->method == ipCmpSameHost)
1215 : {
1216 : /* Make an all-ones netmask of appropriate length for family */
1217 0 : pg_sockaddr_cidr_mask(&mask, NULL, addr->sa_family);
1218 0 : cn->result = check_ip(cn->raddr, addr, (struct sockaddr *) &mask);
1219 : }
1220 : else
1221 : {
1222 : /* Use the netmask of the interface itself */
1223 0 : cn->result = check_ip(cn->raddr, addr, netmask);
1224 : }
1225 : }
1226 :
1227 : /*
1228 : * Use pg_foreach_ifaddr to check a samehost or samenet match
1229 : */
1230 : static bool
1231 0 : check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
1232 : {
1233 : check_network_data cn;
1234 :
1235 0 : cn.method = method;
1236 0 : cn.raddr = raddr;
1237 0 : cn.result = false;
1238 :
1239 0 : errno = 0;
1240 0 : if (pg_foreach_ifaddr(check_network_callback, &cn) < 0)
1241 : {
1242 0 : ereport(LOG,
1243 : (errmsg("error enumerating network interfaces: %m")));
1244 0 : return false;
1245 : }
1246 :
1247 0 : return cn.result;
1248 : }
1249 :
1250 :
1251 : /*
1252 : * Macros used to check and report on invalid configuration options.
1253 : * On error: log a message at level elevel, set *err_msg, and exit the function.
1254 : * These macros are not as general-purpose as they look, because they know
1255 : * what the calling function's error-exit value is.
1256 : *
1257 : * INVALID_AUTH_OPTION = reports when an option is specified for a method where it's
1258 : * not supported.
1259 : * REQUIRE_AUTH_OPTION = same as INVALID_AUTH_OPTION, except it also checks if the
1260 : * method is actually the one specified. Used as a shortcut when
1261 : * the option is only valid for one authentication method.
1262 : * MANDATORY_AUTH_ARG = check if a required option is set for an authentication method,
1263 : * reporting error if it's not.
1264 : */
1265 : #define INVALID_AUTH_OPTION(optname, validmethods) \
1266 : do { \
1267 : ereport(elevel, \
1268 : (errcode(ERRCODE_CONFIG_FILE_ERROR), \
1269 : /* translator: the second %s is a list of auth methods */ \
1270 : errmsg("authentication option \"%s\" is only valid for authentication methods %s", \
1271 : optname, _(validmethods)), \
1272 : errcontext("line %d of configuration file \"%s\"", \
1273 : line_num, file_name))); \
1274 : *err_msg = psprintf("authentication option \"%s\" is only valid for authentication methods %s", \
1275 : optname, validmethods); \
1276 : return false; \
1277 : } while (0)
1278 :
1279 : #define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) \
1280 : do { \
1281 : if (hbaline->auth_method != methodval) \
1282 : INVALID_AUTH_OPTION(optname, validmethods); \
1283 : } while (0)
1284 :
1285 : #define MANDATORY_AUTH_ARG(argvar, argname, authname) \
1286 : do { \
1287 : if (argvar == NULL) { \
1288 : ereport(elevel, \
1289 : (errcode(ERRCODE_CONFIG_FILE_ERROR), \
1290 : errmsg("authentication method \"%s\" requires argument \"%s\" to be set", \
1291 : authname, argname), \
1292 : errcontext("line %d of configuration file \"%s\"", \
1293 : line_num, file_name))); \
1294 : *err_msg = psprintf("authentication method \"%s\" requires argument \"%s\" to be set", \
1295 : authname, argname); \
1296 : return NULL; \
1297 : } \
1298 : } while (0)
1299 :
1300 : /*
1301 : * Macros for handling pg_ident problems, similar as above.
1302 : *
1303 : * IDENT_FIELD_ABSENT:
1304 : * Reports when the given ident field ListCell is not populated.
1305 : *
1306 : * IDENT_MULTI_VALUE:
1307 : * Reports when the given ident token List has more than one element.
1308 : */
1309 : #define IDENT_FIELD_ABSENT(field) \
1310 : do { \
1311 : if (!field) { \
1312 : ereport(elevel, \
1313 : (errcode(ERRCODE_CONFIG_FILE_ERROR), \
1314 : errmsg("missing entry at end of line"), \
1315 : errcontext("line %d of configuration file \"%s\"", \
1316 : line_num, file_name))); \
1317 : *err_msg = pstrdup("missing entry at end of line"); \
1318 : return NULL; \
1319 : } \
1320 : } while (0)
1321 :
1322 : #define IDENT_MULTI_VALUE(tokens) \
1323 : do { \
1324 : if (tokens->length > 1) { \
1325 : ereport(elevel, \
1326 : (errcode(ERRCODE_CONFIG_FILE_ERROR), \
1327 : errmsg("multiple values in ident field"), \
1328 : errcontext("line %d of configuration file \"%s\"", \
1329 : line_num, file_name))); \
1330 : *err_msg = pstrdup("multiple values in ident field"); \
1331 : return NULL; \
1332 : } \
1333 : } while (0)
1334 :
1335 :
1336 : /*
1337 : * Parse one tokenised line from the hba config file and store the result in a
1338 : * HbaLine structure.
1339 : *
1340 : * If parsing fails, log a message at ereport level elevel, store an error
1341 : * string in tok_line->err_msg, and return NULL. (Some non-error conditions
1342 : * can also result in such messages.)
1343 : *
1344 : * Note: this function leaks memory when an error occurs. Caller is expected
1345 : * to have set a memory context that will be reset if this function returns
1346 : * NULL.
1347 : */
1348 : HbaLine *
1349 8002 : parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
1350 : {
1351 8002 : int line_num = tok_line->line_num;
1352 8002 : char *file_name = tok_line->file_name;
1353 8002 : char **err_msg = &tok_line->err_msg;
1354 : char *str;
1355 : struct addrinfo *gai_result;
1356 : struct addrinfo hints;
1357 : int ret;
1358 : char *cidr_slash;
1359 : char *unsupauth;
1360 : ListCell *field;
1361 : List *tokens;
1362 : ListCell *tokencell;
1363 : AuthToken *token;
1364 : HbaLine *parsedline;
1365 :
1366 8002 : parsedline = palloc0(sizeof(HbaLine));
1367 8002 : parsedline->sourcefile = pstrdup(file_name);
1368 8002 : parsedline->linenumber = line_num;
1369 8002 : parsedline->rawline = pstrdup(tok_line->raw_line);
1370 :
1371 : /* Check the record type. */
1372 : Assert(tok_line->fields != NIL);
1373 8002 : field = list_head(tok_line->fields);
1374 8002 : tokens = lfirst(field);
1375 8002 : if (tokens->length > 1)
1376 : {
1377 0 : ereport(elevel,
1378 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1379 : errmsg("multiple values specified for connection type"),
1380 : errhint("Specify exactly one connection type per line."),
1381 : errcontext("line %d of configuration file \"%s\"",
1382 : line_num, file_name)));
1383 0 : *err_msg = "multiple values specified for connection type";
1384 0 : return NULL;
1385 : }
1386 8002 : token = linitial(tokens);
1387 8002 : if (strcmp(token->string, "local") == 0)
1388 : {
1389 2640 : parsedline->conntype = ctLocal;
1390 : }
1391 5362 : else if (strcmp(token->string, "host") == 0 ||
1392 378 : strcmp(token->string, "hostssl") == 0 ||
1393 0 : strcmp(token->string, "hostnossl") == 0 ||
1394 0 : strcmp(token->string, "hostgssenc") == 0 ||
1395 0 : strcmp(token->string, "hostnogssenc") == 0)
1396 : {
1397 :
1398 5362 : if (token->string[4] == 's') /* "hostssl" */
1399 : {
1400 378 : parsedline->conntype = ctHostSSL;
1401 : /* Log a warning if SSL support is not active */
1402 : #ifdef USE_SSL
1403 378 : if (!EnableSSL)
1404 : {
1405 0 : ereport(elevel,
1406 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1407 : errmsg("hostssl record cannot match because SSL is disabled"),
1408 : errhint("Set ssl = on in postgresql.conf."),
1409 : errcontext("line %d of configuration file \"%s\"",
1410 : line_num, file_name)));
1411 0 : *err_msg = "hostssl record cannot match because SSL is disabled";
1412 : }
1413 : #else
1414 : ereport(elevel,
1415 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1416 : errmsg("hostssl record cannot match because SSL is not supported by this build"),
1417 : errcontext("line %d of configuration file \"%s\"",
1418 : line_num, file_name)));
1419 : *err_msg = "hostssl record cannot match because SSL is not supported by this build";
1420 : #endif
1421 : }
1422 4984 : else if (token->string[4] == 'g') /* "hostgssenc" */
1423 : {
1424 0 : parsedline->conntype = ctHostGSS;
1425 : #ifndef ENABLE_GSS
1426 0 : ereport(elevel,
1427 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1428 : errmsg("hostgssenc record cannot match because GSSAPI is not supported by this build"),
1429 : errcontext("line %d of configuration file \"%s\"",
1430 : line_num, file_name)));
1431 0 : *err_msg = "hostgssenc record cannot match because GSSAPI is not supported by this build";
1432 : #endif
1433 : }
1434 4984 : else if (token->string[4] == 'n' && token->string[6] == 's')
1435 0 : parsedline->conntype = ctHostNoSSL;
1436 4984 : else if (token->string[4] == 'n' && token->string[6] == 'g')
1437 0 : parsedline->conntype = ctHostNoGSS;
1438 : else
1439 : {
1440 : /* "host" */
1441 4984 : parsedline->conntype = ctHost;
1442 : }
1443 : } /* record type */
1444 : else
1445 : {
1446 0 : ereport(elevel,
1447 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1448 : errmsg("invalid connection type \"%s\"",
1449 : token->string),
1450 : errcontext("line %d of configuration file \"%s\"",
1451 : line_num, file_name)));
1452 0 : *err_msg = psprintf("invalid connection type \"%s\"", token->string);
1453 0 : return NULL;
1454 : }
1455 :
1456 : /* Get the databases. */
1457 8002 : field = lnext(tok_line->fields, field);
1458 8002 : if (!field)
1459 : {
1460 0 : ereport(elevel,
1461 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1462 : errmsg("end-of-line before database specification"),
1463 : errcontext("line %d of configuration file \"%s\"",
1464 : line_num, file_name)));
1465 0 : *err_msg = "end-of-line before database specification";
1466 0 : return NULL;
1467 : }
1468 8002 : parsedline->databases = NIL;
1469 8002 : tokens = lfirst(field);
1470 16016 : foreach(tokencell, tokens)
1471 : {
1472 8014 : AuthToken *tok = copy_auth_token(lfirst(tokencell));
1473 :
1474 : /* Compile a regexp for the database token, if necessary */
1475 8014 : if (regcomp_auth_token(tok, file_name, line_num, err_msg, elevel))
1476 0 : return NULL;
1477 :
1478 8014 : parsedline->databases = lappend(parsedline->databases, tok);
1479 : }
1480 :
1481 : /* Get the roles. */
1482 8002 : field = lnext(tok_line->fields, field);
1483 8002 : if (!field)
1484 : {
1485 0 : ereport(elevel,
1486 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1487 : errmsg("end-of-line before role specification"),
1488 : errcontext("line %d of configuration file \"%s\"",
1489 : line_num, file_name)));
1490 0 : *err_msg = "end-of-line before role specification";
1491 0 : return NULL;
1492 : }
1493 8002 : parsedline->roles = NIL;
1494 8002 : tokens = lfirst(field);
1495 16012 : foreach(tokencell, tokens)
1496 : {
1497 8010 : AuthToken *tok = copy_auth_token(lfirst(tokencell));
1498 :
1499 : /* Compile a regexp from the role token, if necessary */
1500 8010 : if (regcomp_auth_token(tok, file_name, line_num, err_msg, elevel))
1501 0 : return NULL;
1502 :
1503 8010 : parsedline->roles = lappend(parsedline->roles, tok);
1504 : }
1505 :
1506 8002 : if (parsedline->conntype != ctLocal)
1507 : {
1508 : /* Read the IP address field. (with or without CIDR netmask) */
1509 5362 : field = lnext(tok_line->fields, field);
1510 5362 : if (!field)
1511 : {
1512 0 : ereport(elevel,
1513 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1514 : errmsg("end-of-line before IP address specification"),
1515 : errcontext("line %d of configuration file \"%s\"",
1516 : line_num, file_name)));
1517 0 : *err_msg = "end-of-line before IP address specification";
1518 0 : return NULL;
1519 : }
1520 5362 : tokens = lfirst(field);
1521 5362 : if (tokens->length > 1)
1522 : {
1523 0 : ereport(elevel,
1524 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1525 : errmsg("multiple values specified for host address"),
1526 : errhint("Specify one address range per line."),
1527 : errcontext("line %d of configuration file \"%s\"",
1528 : line_num, file_name)));
1529 0 : *err_msg = "multiple values specified for host address";
1530 0 : return NULL;
1531 : }
1532 5362 : token = linitial(tokens);
1533 :
1534 5362 : if (token_is_keyword(token, "all"))
1535 : {
1536 0 : parsedline->ip_cmp_method = ipCmpAll;
1537 : }
1538 5362 : else if (token_is_keyword(token, "samehost"))
1539 : {
1540 : /* Any IP on this host is allowed to connect */
1541 0 : parsedline->ip_cmp_method = ipCmpSameHost;
1542 : }
1543 5362 : else if (token_is_keyword(token, "samenet"))
1544 : {
1545 : /* Any IP on the host's subnets is allowed to connect */
1546 0 : parsedline->ip_cmp_method = ipCmpSameNet;
1547 : }
1548 : else
1549 : {
1550 : /* IP and netmask are specified */
1551 5362 : parsedline->ip_cmp_method = ipCmpMask;
1552 :
1553 : /* need a modifiable copy of token */
1554 5362 : str = pstrdup(token->string);
1555 :
1556 : /* Check if it has a CIDR suffix and if so isolate it */
1557 5362 : cidr_slash = strchr(str, '/');
1558 5362 : if (cidr_slash)
1559 5362 : *cidr_slash = '\0';
1560 :
1561 : /* Get the IP address either way */
1562 5362 : hints.ai_flags = AI_NUMERICHOST;
1563 5362 : hints.ai_family = AF_UNSPEC;
1564 5362 : hints.ai_socktype = 0;
1565 5362 : hints.ai_protocol = 0;
1566 5362 : hints.ai_addrlen = 0;
1567 5362 : hints.ai_canonname = NULL;
1568 5362 : hints.ai_addr = NULL;
1569 5362 : hints.ai_next = NULL;
1570 :
1571 5362 : ret = pg_getaddrinfo_all(str, NULL, &hints, &gai_result);
1572 5362 : if (ret == 0 && gai_result)
1573 : {
1574 5362 : memcpy(&parsedline->addr, gai_result->ai_addr,
1575 5362 : gai_result->ai_addrlen);
1576 5362 : parsedline->addrlen = gai_result->ai_addrlen;
1577 : }
1578 0 : else if (ret == EAI_NONAME)
1579 0 : parsedline->hostname = str;
1580 : else
1581 : {
1582 0 : ereport(elevel,
1583 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1584 : errmsg("invalid IP address \"%s\": %s",
1585 : str, gai_strerror(ret)),
1586 : errcontext("line %d of configuration file \"%s\"",
1587 : line_num, file_name)));
1588 0 : *err_msg = psprintf("invalid IP address \"%s\": %s",
1589 : str, gai_strerror(ret));
1590 0 : if (gai_result)
1591 0 : pg_freeaddrinfo_all(hints.ai_family, gai_result);
1592 0 : return NULL;
1593 : }
1594 :
1595 5362 : pg_freeaddrinfo_all(hints.ai_family, gai_result);
1596 :
1597 : /* Get the netmask */
1598 5362 : if (cidr_slash)
1599 : {
1600 5362 : if (parsedline->hostname)
1601 : {
1602 0 : ereport(elevel,
1603 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1604 : errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
1605 : token->string),
1606 : errcontext("line %d of configuration file \"%s\"",
1607 : line_num, file_name)));
1608 0 : *err_msg = psprintf("specifying both host name and CIDR mask is invalid: \"%s\"",
1609 : token->string);
1610 0 : return NULL;
1611 : }
1612 :
1613 5362 : if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
1614 5362 : parsedline->addr.ss_family) < 0)
1615 : {
1616 0 : ereport(elevel,
1617 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1618 : errmsg("invalid CIDR mask in address \"%s\"",
1619 : token->string),
1620 : errcontext("line %d of configuration file \"%s\"",
1621 : line_num, file_name)));
1622 0 : *err_msg = psprintf("invalid CIDR mask in address \"%s\"",
1623 : token->string);
1624 0 : return NULL;
1625 : }
1626 5362 : parsedline->masklen = parsedline->addrlen;
1627 5362 : pfree(str);
1628 : }
1629 0 : else if (!parsedline->hostname)
1630 : {
1631 : /* Read the mask field. */
1632 0 : pfree(str);
1633 0 : field = lnext(tok_line->fields, field);
1634 0 : if (!field)
1635 : {
1636 0 : ereport(elevel,
1637 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1638 : errmsg("end-of-line before netmask specification"),
1639 : errhint("Specify an address range in CIDR notation, or provide a separate netmask."),
1640 : errcontext("line %d of configuration file \"%s\"",
1641 : line_num, file_name)));
1642 0 : *err_msg = "end-of-line before netmask specification";
1643 0 : return NULL;
1644 : }
1645 0 : tokens = lfirst(field);
1646 0 : if (tokens->length > 1)
1647 : {
1648 0 : ereport(elevel,
1649 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1650 : errmsg("multiple values specified for netmask"),
1651 : errcontext("line %d of configuration file \"%s\"",
1652 : line_num, file_name)));
1653 0 : *err_msg = "multiple values specified for netmask";
1654 0 : return NULL;
1655 : }
1656 0 : token = linitial(tokens);
1657 :
1658 0 : ret = pg_getaddrinfo_all(token->string, NULL,
1659 : &hints, &gai_result);
1660 0 : if (ret || !gai_result)
1661 : {
1662 0 : ereport(elevel,
1663 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1664 : errmsg("invalid IP mask \"%s\": %s",
1665 : token->string, gai_strerror(ret)),
1666 : errcontext("line %d of configuration file \"%s\"",
1667 : line_num, file_name)));
1668 0 : *err_msg = psprintf("invalid IP mask \"%s\": %s",
1669 : token->string, gai_strerror(ret));
1670 0 : if (gai_result)
1671 0 : pg_freeaddrinfo_all(hints.ai_family, gai_result);
1672 0 : return NULL;
1673 : }
1674 :
1675 0 : memcpy(&parsedline->mask, gai_result->ai_addr,
1676 0 : gai_result->ai_addrlen);
1677 0 : parsedline->masklen = gai_result->ai_addrlen;
1678 0 : pg_freeaddrinfo_all(hints.ai_family, gai_result);
1679 :
1680 0 : if (parsedline->addr.ss_family != parsedline->mask.ss_family)
1681 : {
1682 0 : ereport(elevel,
1683 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1684 : errmsg("IP address and mask do not match"),
1685 : errcontext("line %d of configuration file \"%s\"",
1686 : line_num, file_name)));
1687 0 : *err_msg = "IP address and mask do not match";
1688 0 : return NULL;
1689 : }
1690 : }
1691 : }
1692 : } /* != ctLocal */
1693 :
1694 : /* Get the authentication method */
1695 8002 : field = lnext(tok_line->fields, field);
1696 8002 : if (!field)
1697 : {
1698 0 : ereport(elevel,
1699 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1700 : errmsg("end-of-line before authentication method"),
1701 : errcontext("line %d of configuration file \"%s\"",
1702 : line_num, file_name)));
1703 0 : *err_msg = "end-of-line before authentication method";
1704 0 : return NULL;
1705 : }
1706 8002 : tokens = lfirst(field);
1707 8002 : if (tokens->length > 1)
1708 : {
1709 0 : ereport(elevel,
1710 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1711 : errmsg("multiple values specified for authentication type"),
1712 : errhint("Specify exactly one authentication type per line."),
1713 : errcontext("line %d of configuration file \"%s\"",
1714 : line_num, file_name)));
1715 0 : *err_msg = "multiple values specified for authentication type";
1716 0 : return NULL;
1717 : }
1718 8002 : token = linitial(tokens);
1719 :
1720 8002 : unsupauth = NULL;
1721 8002 : if (strcmp(token->string, "trust") == 0)
1722 7636 : parsedline->auth_method = uaTrust;
1723 366 : else if (strcmp(token->string, "ident") == 0)
1724 0 : parsedline->auth_method = uaIdent;
1725 366 : else if (strcmp(token->string, "peer") == 0)
1726 38 : parsedline->auth_method = uaPeer;
1727 328 : else if (strcmp(token->string, "password") == 0)
1728 16 : parsedline->auth_method = uaPassword;
1729 312 : else if (strcmp(token->string, "gss") == 0)
1730 : #ifdef ENABLE_GSS
1731 : parsedline->auth_method = uaGSS;
1732 : #else
1733 0 : unsupauth = "gss";
1734 : #endif
1735 312 : else if (strcmp(token->string, "sspi") == 0)
1736 : #ifdef ENABLE_SSPI
1737 : parsedline->auth_method = uaSSPI;
1738 : #else
1739 0 : unsupauth = "sspi";
1740 : #endif
1741 312 : else if (strcmp(token->string, "reject") == 0)
1742 36 : parsedline->auth_method = uaReject;
1743 276 : else if (strcmp(token->string, "md5") == 0)
1744 : {
1745 44 : if (Db_user_namespace)
1746 : {
1747 0 : ereport(elevel,
1748 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1749 : errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
1750 : errcontext("line %d of configuration file \"%s\"",
1751 : line_num, file_name)));
1752 0 : *err_msg = "MD5 authentication is not supported when \"db_user_namespace\" is enabled";
1753 0 : return NULL;
1754 : }
1755 44 : parsedline->auth_method = uaMD5;
1756 : }
1757 232 : else if (strcmp(token->string, "scram-sha-256") == 0)
1758 30 : parsedline->auth_method = uaSCRAM;
1759 202 : else if (strcmp(token->string, "pam") == 0)
1760 : #ifdef USE_PAM
1761 0 : parsedline->auth_method = uaPAM;
1762 : #else
1763 : unsupauth = "pam";
1764 : #endif
1765 202 : else if (strcmp(token->string, "bsd") == 0)
1766 : #ifdef USE_BSD_AUTH
1767 : parsedline->auth_method = uaBSD;
1768 : #else
1769 0 : unsupauth = "bsd";
1770 : #endif
1771 202 : else if (strcmp(token->string, "ldap") == 0)
1772 : #ifdef USE_LDAP
1773 34 : parsedline->auth_method = uaLDAP;
1774 : #else
1775 : unsupauth = "ldap";
1776 : #endif
1777 168 : else if (strcmp(token->string, "cert") == 0)
1778 : #ifdef USE_SSL
1779 168 : parsedline->auth_method = uaCert;
1780 : #else
1781 : unsupauth = "cert";
1782 : #endif
1783 0 : else if (strcmp(token->string, "radius") == 0)
1784 0 : parsedline->auth_method = uaRADIUS;
1785 : else
1786 : {
1787 0 : ereport(elevel,
1788 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1789 : errmsg("invalid authentication method \"%s\"",
1790 : token->string),
1791 : errcontext("line %d of configuration file \"%s\"",
1792 : line_num, file_name)));
1793 0 : *err_msg = psprintf("invalid authentication method \"%s\"",
1794 : token->string);
1795 0 : return NULL;
1796 : }
1797 :
1798 8002 : if (unsupauth)
1799 : {
1800 0 : ereport(elevel,
1801 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1802 : errmsg("invalid authentication method \"%s\": not supported by this build",
1803 : token->string),
1804 : errcontext("line %d of configuration file \"%s\"",
1805 : line_num, file_name)));
1806 0 : *err_msg = psprintf("invalid authentication method \"%s\": not supported by this build",
1807 : token->string);
1808 0 : return NULL;
1809 : }
1810 :
1811 : /*
1812 : * XXX: When using ident on local connections, change it to peer, for
1813 : * backwards compatibility.
1814 : */
1815 8002 : if (parsedline->conntype == ctLocal &&
1816 2640 : parsedline->auth_method == uaIdent)
1817 0 : parsedline->auth_method = uaPeer;
1818 :
1819 : /* Invalid authentication combinations */
1820 8002 : if (parsedline->conntype == ctLocal &&
1821 2640 : parsedline->auth_method == uaGSS)
1822 : {
1823 0 : ereport(elevel,
1824 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1825 : errmsg("gssapi authentication is not supported on local sockets"),
1826 : errcontext("line %d of configuration file \"%s\"",
1827 : line_num, file_name)));
1828 0 : *err_msg = "gssapi authentication is not supported on local sockets";
1829 0 : return NULL;
1830 : }
1831 :
1832 8002 : if (parsedline->conntype != ctLocal &&
1833 5362 : parsedline->auth_method == uaPeer)
1834 : {
1835 0 : ereport(elevel,
1836 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1837 : errmsg("peer authentication is only supported on local sockets"),
1838 : errcontext("line %d of configuration file \"%s\"",
1839 : line_num, file_name)));
1840 0 : *err_msg = "peer authentication is only supported on local sockets";
1841 0 : return NULL;
1842 : }
1843 :
1844 : /*
1845 : * SSPI authentication can never be enabled on ctLocal connections,
1846 : * because it's only supported on Windows, where ctLocal isn't supported.
1847 : */
1848 :
1849 :
1850 8002 : if (parsedline->conntype != ctHostSSL &&
1851 7624 : parsedline->auth_method == uaCert)
1852 : {
1853 0 : ereport(elevel,
1854 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1855 : errmsg("cert authentication is only supported on hostssl connections"),
1856 : errcontext("line %d of configuration file \"%s\"",
1857 : line_num, file_name)));
1858 0 : *err_msg = "cert authentication is only supported on hostssl connections";
1859 0 : return NULL;
1860 : }
1861 :
1862 : /*
1863 : * For GSS and SSPI, set the default value of include_realm to true.
1864 : * Having include_realm set to false is dangerous in multi-realm
1865 : * situations and is generally considered bad practice. We keep the
1866 : * capability around for backwards compatibility, but we might want to
1867 : * remove it at some point in the future. Users who still need to strip
1868 : * the realm off would be better served by using an appropriate regex in a
1869 : * pg_ident.conf mapping.
1870 : */
1871 8002 : if (parsedline->auth_method == uaGSS ||
1872 8002 : parsedline->auth_method == uaSSPI)
1873 0 : parsedline->include_realm = true;
1874 :
1875 : /*
1876 : * For SSPI, include_realm defaults to the SAM-compatible domain (aka
1877 : * NetBIOS name) and user names instead of the Kerberos principal name for
1878 : * compatibility.
1879 : */
1880 8002 : if (parsedline->auth_method == uaSSPI)
1881 : {
1882 0 : parsedline->compat_realm = true;
1883 0 : parsedline->upn_username = false;
1884 : }
1885 :
1886 : /* Parse remaining arguments */
1887 8534 : while ((field = lnext(tok_line->fields, field)) != NULL)
1888 : {
1889 532 : tokens = lfirst(field);
1890 1064 : foreach(tokencell, tokens)
1891 : {
1892 : char *val;
1893 :
1894 532 : token = lfirst(tokencell);
1895 :
1896 532 : str = pstrdup(token->string);
1897 532 : val = strchr(str, '=');
1898 532 : if (val == NULL)
1899 : {
1900 : /*
1901 : * Got something that's not a name=value pair.
1902 : */
1903 0 : ereport(elevel,
1904 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1905 : errmsg("authentication option not in name=value format: %s", token->string),
1906 : errcontext("line %d of configuration file \"%s\"",
1907 : line_num, file_name)));
1908 0 : *err_msg = psprintf("authentication option not in name=value format: %s",
1909 : token->string);
1910 0 : return NULL;
1911 : }
1912 :
1913 532 : *val++ = '\0'; /* str now holds "name", val holds "value" */
1914 532 : if (!parse_hba_auth_opt(str, val, parsedline, elevel, err_msg))
1915 : /* parse_hba_auth_opt already logged the error message */
1916 0 : return NULL;
1917 532 : pfree(str);
1918 : }
1919 : }
1920 :
1921 : /*
1922 : * Check if the selected authentication method has any mandatory arguments
1923 : * that are not set.
1924 : */
1925 8002 : if (parsedline->auth_method == uaLDAP)
1926 : {
1927 : #ifndef HAVE_LDAP_INITIALIZE
1928 : /* Not mandatory for OpenLDAP, because it can use DNS SRV records */
1929 : MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");
1930 : #endif
1931 :
1932 : /*
1933 : * LDAP can operate in two modes: either with a direct bind, using
1934 : * ldapprefix and ldapsuffix, or using a search+bind, using
1935 : * ldapbasedn, ldapbinddn, ldapbindpasswd and one of
1936 : * ldapsearchattribute or ldapsearchfilter. Disallow mixing these
1937 : * parameters.
1938 : */
1939 34 : if (parsedline->ldapprefix || parsedline->ldapsuffix)
1940 : {
1941 4 : if (parsedline->ldapbasedn ||
1942 4 : parsedline->ldapbinddn ||
1943 4 : parsedline->ldapbindpasswd ||
1944 4 : parsedline->ldapsearchattribute ||
1945 4 : parsedline->ldapsearchfilter)
1946 : {
1947 0 : ereport(elevel,
1948 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1949 : errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, or ldapurl together with ldapprefix"),
1950 : errcontext("line %d of configuration file \"%s\"",
1951 : line_num, file_name)));
1952 0 : *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, or ldapurl together with ldapprefix";
1953 0 : return NULL;
1954 : }
1955 : }
1956 30 : else if (!parsedline->ldapbasedn)
1957 : {
1958 0 : ereport(elevel,
1959 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1960 : errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"),
1961 : errcontext("line %d of configuration file \"%s\"",
1962 : line_num, file_name)));
1963 0 : *err_msg = "authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set";
1964 0 : return NULL;
1965 : }
1966 :
1967 : /*
1968 : * When using search+bind, you can either use a simple attribute
1969 : * (defaulting to "uid") or a fully custom search filter. You can't
1970 : * do both.
1971 : */
1972 34 : if (parsedline->ldapsearchattribute && parsedline->ldapsearchfilter)
1973 : {
1974 0 : ereport(elevel,
1975 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1976 : errmsg("cannot use ldapsearchattribute together with ldapsearchfilter"),
1977 : errcontext("line %d of configuration file \"%s\"",
1978 : line_num, file_name)));
1979 0 : *err_msg = "cannot use ldapsearchattribute together with ldapsearchfilter";
1980 0 : return NULL;
1981 : }
1982 : }
1983 :
1984 8002 : if (parsedline->auth_method == uaRADIUS)
1985 : {
1986 0 : MANDATORY_AUTH_ARG(parsedline->radiusservers, "radiusservers", "radius");
1987 0 : MANDATORY_AUTH_ARG(parsedline->radiussecrets, "radiussecrets", "radius");
1988 :
1989 0 : if (parsedline->radiusservers == NIL)
1990 : {
1991 0 : ereport(elevel,
1992 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1993 : errmsg("list of RADIUS servers cannot be empty"),
1994 : errcontext("line %d of configuration file \"%s\"",
1995 : line_num, file_name)));
1996 0 : *err_msg = "list of RADIUS servers cannot be empty";
1997 0 : return NULL;
1998 : }
1999 :
2000 0 : if (parsedline->radiussecrets == NIL)
2001 : {
2002 0 : ereport(elevel,
2003 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2004 : errmsg("list of RADIUS secrets cannot be empty"),
2005 : errcontext("line %d of configuration file \"%s\"",
2006 : line_num, file_name)));
2007 0 : *err_msg = "list of RADIUS secrets cannot be empty";
2008 0 : return NULL;
2009 : }
2010 :
2011 : /*
2012 : * Verify length of option lists - each can be 0 (except for secrets,
2013 : * but that's already checked above), 1 (use the same value
2014 : * everywhere) or the same as the number of servers.
2015 : */
2016 0 : if (!(list_length(parsedline->radiussecrets) == 1 ||
2017 0 : list_length(parsedline->radiussecrets) == list_length(parsedline->radiusservers)))
2018 : {
2019 0 : ereport(elevel,
2020 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2021 : errmsg("the number of RADIUS secrets (%d) must be 1 or the same as the number of RADIUS servers (%d)",
2022 : list_length(parsedline->radiussecrets),
2023 : list_length(parsedline->radiusservers)),
2024 : errcontext("line %d of configuration file \"%s\"",
2025 : line_num, file_name)));
2026 0 : *err_msg = psprintf("the number of RADIUS secrets (%d) must be 1 or the same as the number of RADIUS servers (%d)",
2027 0 : list_length(parsedline->radiussecrets),
2028 0 : list_length(parsedline->radiusservers));
2029 0 : return NULL;
2030 : }
2031 0 : if (!(list_length(parsedline->radiusports) == 0 ||
2032 0 : list_length(parsedline->radiusports) == 1 ||
2033 0 : list_length(parsedline->radiusports) == list_length(parsedline->radiusservers)))
2034 : {
2035 0 : ereport(elevel,
2036 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2037 : errmsg("the number of RADIUS ports (%d) must be 1 or the same as the number of RADIUS servers (%d)",
2038 : list_length(parsedline->radiusports),
2039 : list_length(parsedline->radiusservers)),
2040 : errcontext("line %d of configuration file \"%s\"",
2041 : line_num, file_name)));
2042 0 : *err_msg = psprintf("the number of RADIUS ports (%d) must be 1 or the same as the number of RADIUS servers (%d)",
2043 0 : list_length(parsedline->radiusports),
2044 0 : list_length(parsedline->radiusservers));
2045 0 : return NULL;
2046 : }
2047 0 : if (!(list_length(parsedline->radiusidentifiers) == 0 ||
2048 0 : list_length(parsedline->radiusidentifiers) == 1 ||
2049 0 : list_length(parsedline->radiusidentifiers) == list_length(parsedline->radiusservers)))
2050 : {
2051 0 : ereport(elevel,
2052 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2053 : errmsg("the number of RADIUS identifiers (%d) must be 1 or the same as the number of RADIUS servers (%d)",
2054 : list_length(parsedline->radiusidentifiers),
2055 : list_length(parsedline->radiusservers)),
2056 : errcontext("line %d of configuration file \"%s\"",
2057 : line_num, file_name)));
2058 0 : *err_msg = psprintf("the number of RADIUS identifiers (%d) must be 1 or the same as the number of RADIUS servers (%d)",
2059 0 : list_length(parsedline->radiusidentifiers),
2060 0 : list_length(parsedline->radiusservers));
2061 0 : return NULL;
2062 : }
2063 : }
2064 :
2065 : /*
2066 : * Enforce any parameters implied by other settings.
2067 : */
2068 8002 : if (parsedline->auth_method == uaCert)
2069 : {
2070 : /*
2071 : * For auth method cert, client certificate validation is mandatory,
2072 : * and it implies the level of verify-full.
2073 : */
2074 168 : parsedline->clientcert = clientCertFull;
2075 : }
2076 :
2077 8002 : return parsedline;
2078 : }
2079 :
2080 :
2081 : /*
2082 : * Parse one name-value pair as an authentication option into the given
2083 : * HbaLine. Return true if we successfully parse the option, false if we
2084 : * encounter an error. In the event of an error, also log a message at
2085 : * ereport level elevel, and store a message string into *err_msg.
2086 : */
2087 : static bool
2088 532 : parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
2089 : int elevel, char **err_msg)
2090 : {
2091 532 : int line_num = hbaline->linenumber;
2092 532 : char *file_name = hbaline->sourcefile;
2093 :
2094 : #ifdef USE_LDAP
2095 532 : hbaline->ldapscope = LDAP_SCOPE_SUBTREE;
2096 : #endif
2097 :
2098 532 : if (strcmp(name, "map") == 0)
2099 : {
2100 162 : if (hbaline->auth_method != uaIdent &&
2101 162 : hbaline->auth_method != uaPeer &&
2102 126 : hbaline->auth_method != uaGSS &&
2103 126 : hbaline->auth_method != uaSSPI &&
2104 126 : hbaline->auth_method != uaCert)
2105 0 : INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, and cert"));
2106 162 : hbaline->usermap = pstrdup(val);
2107 : }
2108 370 : else if (strcmp(name, "clientcert") == 0)
2109 : {
2110 126 : if (hbaline->conntype != ctHostSSL)
2111 : {
2112 0 : ereport(elevel,
2113 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2114 : errmsg("clientcert can only be configured for \"hostssl\" rows"),
2115 : errcontext("line %d of configuration file \"%s\"",
2116 : line_num, file_name)));
2117 0 : *err_msg = "clientcert can only be configured for \"hostssl\" rows";
2118 0 : return false;
2119 : }
2120 :
2121 126 : if (strcmp(val, "verify-full") == 0)
2122 : {
2123 84 : hbaline->clientcert = clientCertFull;
2124 : }
2125 42 : else if (strcmp(val, "verify-ca") == 0)
2126 : {
2127 42 : if (hbaline->auth_method == uaCert)
2128 : {
2129 0 : ereport(elevel,
2130 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2131 : errmsg("clientcert only accepts \"verify-full\" when using \"cert\" authentication"),
2132 : errcontext("line %d of configuration file \"%s\"",
2133 : line_num, file_name)));
2134 0 : *err_msg = "clientcert can only be set to \"verify-full\" when using \"cert\" authentication";
2135 0 : return false;
2136 : }
2137 :
2138 42 : hbaline->clientcert = clientCertCA;
2139 : }
2140 : else
2141 : {
2142 0 : ereport(elevel,
2143 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2144 : errmsg("invalid value for clientcert: \"%s\"", val),
2145 : errcontext("line %d of configuration file \"%s\"",
2146 : line_num, file_name)));
2147 0 : return false;
2148 : }
2149 : }
2150 244 : else if (strcmp(name, "clientname") == 0)
2151 : {
2152 126 : if (hbaline->conntype != ctHostSSL)
2153 : {
2154 0 : ereport(elevel,
2155 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2156 : errmsg("clientname can only be configured for \"hostssl\" rows"),
2157 : errcontext("line %d of configuration file \"%s\"",
2158 : line_num, file_name)));
2159 0 : *err_msg = "clientname can only be configured for \"hostssl\" rows";
2160 0 : return false;
2161 : }
2162 :
2163 126 : if (strcmp(val, "CN") == 0)
2164 : {
2165 42 : hbaline->clientcertname = clientCertCN;
2166 : }
2167 84 : else if (strcmp(val, "DN") == 0)
2168 : {
2169 84 : hbaline->clientcertname = clientCertDN;
2170 : }
2171 : else
2172 : {
2173 0 : ereport(elevel,
2174 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2175 : errmsg("invalid value for clientname: \"%s\"", val),
2176 : errcontext("line %d of configuration file \"%s\"",
2177 : line_num, file_name)));
2178 0 : return false;
2179 : }
2180 : }
2181 118 : else if (strcmp(name, "pamservice") == 0)
2182 : {
2183 0 : REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
2184 0 : hbaline->pamservice = pstrdup(val);
2185 : }
2186 118 : else if (strcmp(name, "pam_use_hostname") == 0)
2187 : {
2188 0 : REQUIRE_AUTH_OPTION(uaPAM, "pam_use_hostname", "pam");
2189 0 : if (strcmp(val, "1") == 0)
2190 0 : hbaline->pam_use_hostname = true;
2191 : else
2192 0 : hbaline->pam_use_hostname = false;
2193 : }
2194 118 : else if (strcmp(name, "ldapurl") == 0)
2195 : {
2196 : #ifdef LDAP_API_FEATURE_X_OPENLDAP
2197 : LDAPURLDesc *urldata;
2198 : int rc;
2199 : #endif
2200 :
2201 10 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapurl", "ldap");
2202 : #ifdef LDAP_API_FEATURE_X_OPENLDAP
2203 10 : rc = ldap_url_parse(val, &urldata);
2204 10 : if (rc != LDAP_SUCCESS)
2205 : {
2206 0 : ereport(elevel,
2207 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2208 : errmsg("could not parse LDAP URL \"%s\": %s", val, ldap_err2string(rc))));
2209 0 : *err_msg = psprintf("could not parse LDAP URL \"%s\": %s",
2210 : val, ldap_err2string(rc));
2211 0 : return false;
2212 : }
2213 :
2214 10 : if (strcmp(urldata->lud_scheme, "ldap") != 0 &&
2215 4 : strcmp(urldata->lud_scheme, "ldaps") != 0)
2216 : {
2217 0 : ereport(elevel,
2218 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2219 : errmsg("unsupported LDAP URL scheme: %s", urldata->lud_scheme)));
2220 0 : *err_msg = psprintf("unsupported LDAP URL scheme: %s",
2221 0 : urldata->lud_scheme);
2222 0 : ldap_free_urldesc(urldata);
2223 0 : return false;
2224 : }
2225 :
2226 10 : if (urldata->lud_scheme)
2227 10 : hbaline->ldapscheme = pstrdup(urldata->lud_scheme);
2228 10 : if (urldata->lud_host)
2229 10 : hbaline->ldapserver = pstrdup(urldata->lud_host);
2230 10 : hbaline->ldapport = urldata->lud_port;
2231 10 : if (urldata->lud_dn)
2232 10 : hbaline->ldapbasedn = pstrdup(urldata->lud_dn);
2233 :
2234 10 : if (urldata->lud_attrs)
2235 2 : hbaline->ldapsearchattribute = pstrdup(urldata->lud_attrs[0]); /* only use first one */
2236 10 : hbaline->ldapscope = urldata->lud_scope;
2237 10 : if (urldata->lud_filter)
2238 6 : hbaline->ldapsearchfilter = pstrdup(urldata->lud_filter);
2239 10 : ldap_free_urldesc(urldata);
2240 : #else /* not OpenLDAP */
2241 : ereport(elevel,
2242 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2243 : errmsg("LDAP URLs not supported on this platform")));
2244 : *err_msg = "LDAP URLs not supported on this platform";
2245 : #endif /* not OpenLDAP */
2246 : }
2247 108 : else if (strcmp(name, "ldaptls") == 0)
2248 : {
2249 4 : REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");
2250 4 : if (strcmp(val, "1") == 0)
2251 4 : hbaline->ldaptls = true;
2252 : else
2253 0 : hbaline->ldaptls = false;
2254 : }
2255 104 : else if (strcmp(name, "ldapscheme") == 0)
2256 : {
2257 2 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapscheme", "ldap");
2258 2 : if (strcmp(val, "ldap") != 0 && strcmp(val, "ldaps") != 0)
2259 0 : ereport(elevel,
2260 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2261 : errmsg("invalid ldapscheme value: \"%s\"", val),
2262 : errcontext("line %d of configuration file \"%s\"",
2263 : line_num, file_name)));
2264 2 : hbaline->ldapscheme = pstrdup(val);
2265 : }
2266 102 : else if (strcmp(name, "ldapserver") == 0)
2267 : {
2268 24 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
2269 24 : hbaline->ldapserver = pstrdup(val);
2270 : }
2271 78 : else if (strcmp(name, "ldapport") == 0)
2272 : {
2273 24 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap");
2274 24 : hbaline->ldapport = atoi(val);
2275 24 : if (hbaline->ldapport == 0)
2276 : {
2277 0 : ereport(elevel,
2278 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2279 : errmsg("invalid LDAP port number: \"%s\"", val),
2280 : errcontext("line %d of configuration file \"%s\"",
2281 : line_num, file_name)));
2282 0 : *err_msg = psprintf("invalid LDAP port number: \"%s\"", val);
2283 0 : return false;
2284 : }
2285 : }
2286 54 : else if (strcmp(name, "ldapbinddn") == 0)
2287 : {
2288 10 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap");
2289 10 : hbaline->ldapbinddn = pstrdup(val);
2290 : }
2291 44 : else if (strcmp(name, "ldapbindpasswd") == 0)
2292 : {
2293 8 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap");
2294 8 : hbaline->ldapbindpasswd = pstrdup(val);
2295 : }
2296 36 : else if (strcmp(name, "ldapsearchattribute") == 0)
2297 : {
2298 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
2299 0 : hbaline->ldapsearchattribute = pstrdup(val);
2300 : }
2301 36 : else if (strcmp(name, "ldapsearchfilter") == 0)
2302 : {
2303 8 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchfilter", "ldap");
2304 8 : hbaline->ldapsearchfilter = pstrdup(val);
2305 : }
2306 28 : else if (strcmp(name, "ldapbasedn") == 0)
2307 : {
2308 20 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
2309 20 : hbaline->ldapbasedn = pstrdup(val);
2310 : }
2311 8 : else if (strcmp(name, "ldapprefix") == 0)
2312 : {
2313 4 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
2314 4 : hbaline->ldapprefix = pstrdup(val);
2315 : }
2316 4 : else if (strcmp(name, "ldapsuffix") == 0)
2317 : {
2318 4 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
2319 4 : hbaline->ldapsuffix = pstrdup(val);
2320 : }
2321 0 : else if (strcmp(name, "krb_realm") == 0)
2322 : {
2323 0 : if (hbaline->auth_method != uaGSS &&
2324 0 : hbaline->auth_method != uaSSPI)
2325 0 : INVALID_AUTH_OPTION("krb_realm", gettext_noop("gssapi and sspi"));
2326 0 : hbaline->krb_realm = pstrdup(val);
2327 : }
2328 0 : else if (strcmp(name, "include_realm") == 0)
2329 : {
2330 0 : if (hbaline->auth_method != uaGSS &&
2331 0 : hbaline->auth_method != uaSSPI)
2332 0 : INVALID_AUTH_OPTION("include_realm", gettext_noop("gssapi and sspi"));
2333 0 : if (strcmp(val, "1") == 0)
2334 0 : hbaline->include_realm = true;
2335 : else
2336 0 : hbaline->include_realm = false;
2337 : }
2338 0 : else if (strcmp(name, "compat_realm") == 0)
2339 : {
2340 0 : if (hbaline->auth_method != uaSSPI)
2341 0 : INVALID_AUTH_OPTION("compat_realm", gettext_noop("sspi"));
2342 0 : if (strcmp(val, "1") == 0)
2343 0 : hbaline->compat_realm = true;
2344 : else
2345 0 : hbaline->compat_realm = false;
2346 : }
2347 0 : else if (strcmp(name, "upn_username") == 0)
2348 : {
2349 0 : if (hbaline->auth_method != uaSSPI)
2350 0 : INVALID_AUTH_OPTION("upn_username", gettext_noop("sspi"));
2351 0 : if (strcmp(val, "1") == 0)
2352 0 : hbaline->upn_username = true;
2353 : else
2354 0 : hbaline->upn_username = false;
2355 : }
2356 0 : else if (strcmp(name, "radiusservers") == 0)
2357 : {
2358 : struct addrinfo *gai_result;
2359 : struct addrinfo hints;
2360 : int ret;
2361 : List *parsed_servers;
2362 : ListCell *l;
2363 0 : char *dupval = pstrdup(val);
2364 :
2365 0 : REQUIRE_AUTH_OPTION(uaRADIUS, "radiusservers", "radius");
2366 :
2367 0 : if (!SplitGUCList(dupval, ',', &parsed_servers))
2368 : {
2369 : /* syntax error in list */
2370 0 : ereport(elevel,
2371 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2372 : errmsg("could not parse RADIUS server list \"%s\"",
2373 : val),
2374 : errcontext("line %d of configuration file \"%s\"",
2375 : line_num, file_name)));
2376 0 : return false;
2377 : }
2378 :
2379 : /* For each entry in the list, translate it */
2380 0 : foreach(l, parsed_servers)
2381 : {
2382 0 : MemSet(&hints, 0, sizeof(hints));
2383 0 : hints.ai_socktype = SOCK_DGRAM;
2384 0 : hints.ai_family = AF_UNSPEC;
2385 :
2386 0 : ret = pg_getaddrinfo_all((char *) lfirst(l), NULL, &hints, &gai_result);
2387 0 : if (ret || !gai_result)
2388 : {
2389 0 : ereport(elevel,
2390 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2391 : errmsg("could not translate RADIUS server name \"%s\" to address: %s",
2392 : (char *) lfirst(l), gai_strerror(ret)),
2393 : errcontext("line %d of configuration file \"%s\"",
2394 : line_num, file_name)));
2395 0 : if (gai_result)
2396 0 : pg_freeaddrinfo_all(hints.ai_family, gai_result);
2397 :
2398 0 : list_free(parsed_servers);
2399 0 : return false;
2400 : }
2401 0 : pg_freeaddrinfo_all(hints.ai_family, gai_result);
2402 : }
2403 :
2404 : /* All entries are OK, so store them */
2405 0 : hbaline->radiusservers = parsed_servers;
2406 0 : hbaline->radiusservers_s = pstrdup(val);
2407 : }
2408 0 : else if (strcmp(name, "radiusports") == 0)
2409 : {
2410 : List *parsed_ports;
2411 : ListCell *l;
2412 0 : char *dupval = pstrdup(val);
2413 :
2414 0 : REQUIRE_AUTH_OPTION(uaRADIUS, "radiusports", "radius");
2415 :
2416 0 : if (!SplitGUCList(dupval, ',', &parsed_ports))
2417 : {
2418 0 : ereport(elevel,
2419 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2420 : errmsg("could not parse RADIUS port list \"%s\"",
2421 : val),
2422 : errcontext("line %d of configuration file \"%s\"",
2423 : line_num, file_name)));
2424 0 : *err_msg = psprintf("invalid RADIUS port number: \"%s\"", val);
2425 0 : return false;
2426 : }
2427 :
2428 0 : foreach(l, parsed_ports)
2429 : {
2430 0 : if (atoi(lfirst(l)) == 0)
2431 : {
2432 0 : ereport(elevel,
2433 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2434 : errmsg("invalid RADIUS port number: \"%s\"", val),
2435 : errcontext("line %d of configuration file \"%s\"",
2436 : line_num, file_name)));
2437 :
2438 0 : return false;
2439 : }
2440 : }
2441 0 : hbaline->radiusports = parsed_ports;
2442 0 : hbaline->radiusports_s = pstrdup(val);
2443 : }
2444 0 : else if (strcmp(name, "radiussecrets") == 0)
2445 : {
2446 : List *parsed_secrets;
2447 0 : char *dupval = pstrdup(val);
2448 :
2449 0 : REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecrets", "radius");
2450 :
2451 0 : if (!SplitGUCList(dupval, ',', &parsed_secrets))
2452 : {
2453 : /* syntax error in list */
2454 0 : ereport(elevel,
2455 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2456 : errmsg("could not parse RADIUS secret list \"%s\"",
2457 : val),
2458 : errcontext("line %d of configuration file \"%s\"",
2459 : line_num, file_name)));
2460 0 : return false;
2461 : }
2462 :
2463 0 : hbaline->radiussecrets = parsed_secrets;
2464 0 : hbaline->radiussecrets_s = pstrdup(val);
2465 : }
2466 0 : else if (strcmp(name, "radiusidentifiers") == 0)
2467 : {
2468 : List *parsed_identifiers;
2469 0 : char *dupval = pstrdup(val);
2470 :
2471 0 : REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifiers", "radius");
2472 :
2473 0 : if (!SplitGUCList(dupval, ',', &parsed_identifiers))
2474 : {
2475 : /* syntax error in list */
2476 0 : ereport(elevel,
2477 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2478 : errmsg("could not parse RADIUS identifiers list \"%s\"",
2479 : val),
2480 : errcontext("line %d of configuration file \"%s\"",
2481 : line_num, file_name)));
2482 0 : return false;
2483 : }
2484 :
2485 0 : hbaline->radiusidentifiers = parsed_identifiers;
2486 0 : hbaline->radiusidentifiers_s = pstrdup(val);
2487 : }
2488 : else
2489 : {
2490 0 : ereport(elevel,
2491 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2492 : errmsg("unrecognized authentication option name: \"%s\"",
2493 : name),
2494 : errcontext("line %d of configuration file \"%s\"",
2495 : line_num, file_name)));
2496 0 : *err_msg = psprintf("unrecognized authentication option name: \"%s\"",
2497 : name);
2498 0 : return false;
2499 : }
2500 532 : return true;
2501 : }
2502 :
2503 : /*
2504 : * Scan the pre-parsed hba file, looking for a match to the port's connection
2505 : * request.
2506 : */
2507 : static void
2508 18152 : check_hba(hbaPort *port)
2509 : {
2510 : Oid roleid;
2511 : ListCell *line;
2512 : HbaLine *hba;
2513 :
2514 : /* Get the target role's OID. Note we do not error out for bad role. */
2515 18152 : roleid = get_role_oid(port->user_name, true);
2516 :
2517 21190 : foreach(line, parsed_hba_lines)
2518 : {
2519 21176 : hba = (HbaLine *) lfirst(line);
2520 :
2521 : /* Check connection type */
2522 21176 : if (hba->conntype == ctLocal)
2523 : {
2524 18668 : if (port->raddr.addr.ss_family != AF_UNIX)
2525 284 : continue;
2526 : }
2527 : else
2528 : {
2529 2508 : if (port->raddr.addr.ss_family == AF_UNIX)
2530 1364 : continue;
2531 :
2532 : /* Check SSL state */
2533 1144 : if (port->ssl_in_use)
2534 : {
2535 : /* Connection is SSL, match both "host" and "hostssl" */
2536 558 : if (hba->conntype == ctHostNoSSL)
2537 0 : continue;
2538 : }
2539 : else
2540 : {
2541 : /* Connection is not SSL, match both "host" and "hostnossl" */
2542 586 : if (hba->conntype == ctHostSSL)
2543 18 : continue;
2544 : }
2545 :
2546 : /* Check GSSAPI state */
2547 : #ifdef ENABLE_GSS
2548 : if (port->gss && port->gss->enc &&
2549 : hba->conntype == ctHostNoGSS)
2550 : continue;
2551 : else if (!(port->gss && port->gss->enc) &&
2552 : hba->conntype == ctHostGSS)
2553 : continue;
2554 : #else
2555 1126 : if (hba->conntype == ctHostGSS)
2556 0 : continue;
2557 : #endif
2558 :
2559 : /* Check IP address */
2560 1126 : switch (hba->ip_cmp_method)
2561 : {
2562 1126 : case ipCmpMask:
2563 1126 : if (hba->hostname)
2564 : {
2565 0 : if (!check_hostname(port,
2566 0 : hba->hostname))
2567 0 : continue;
2568 : }
2569 : else
2570 : {
2571 1126 : if (!check_ip(&port->raddr,
2572 1126 : (struct sockaddr *) &hba->addr,
2573 1126 : (struct sockaddr *) &hba->mask))
2574 284 : continue;
2575 : }
2576 842 : break;
2577 0 : case ipCmpAll:
2578 0 : break;
2579 0 : case ipCmpSameHost:
2580 : case ipCmpSameNet:
2581 0 : if (!check_same_host_or_net(&port->raddr,
2582 : hba->ip_cmp_method))
2583 0 : continue;
2584 0 : break;
2585 0 : default:
2586 : /* shouldn't get here, but deem it no-match if so */
2587 0 : continue;
2588 : }
2589 : } /* != ctLocal */
2590 :
2591 : /* Check database and role */
2592 19226 : if (!check_db(port->database_name, port->user_name, roleid,
2593 : hba->databases))
2594 974 : continue;
2595 :
2596 18252 : if (!check_role(port->user_name, roleid, hba->roles, false))
2597 114 : continue;
2598 :
2599 : /* Found a record that matched! */
2600 18138 : port->hba = hba;
2601 18138 : return;
2602 : }
2603 :
2604 : /* If no matching entry was found, then implicitly reject. */
2605 14 : hba = palloc0(sizeof(HbaLine));
2606 14 : hba->auth_method = uaImplicitReject;
2607 14 : port->hba = hba;
2608 : }
2609 :
2610 : /*
2611 : * Read the config file and create a List of HbaLine records for the contents.
2612 : *
2613 : * The configuration is read into a temporary list, and if any parse error
2614 : * occurs the old list is kept in place and false is returned. Only if the
2615 : * whole file parses OK is the list replaced, and the function returns true.
2616 : *
2617 : * On a false result, caller will take care of reporting a FATAL error in case
2618 : * this is the initial startup. If it happens on reload, we just keep running
2619 : * with the old data.
2620 : */
2621 : bool
2622 1392 : load_hba(void)
2623 : {
2624 : FILE *file;
2625 1392 : List *hba_lines = NIL;
2626 : ListCell *line;
2627 1392 : List *new_parsed_lines = NIL;
2628 1392 : bool ok = true;
2629 : MemoryContext oldcxt;
2630 : MemoryContext hbacxt;
2631 :
2632 1392 : file = open_auth_file(HbaFileName, LOG, 0, NULL);
2633 1392 : if (file == NULL)
2634 : {
2635 : /* error already logged */
2636 0 : return false;
2637 : }
2638 :
2639 1392 : tokenize_auth_file(HbaFileName, file, &hba_lines, LOG, 0);
2640 :
2641 : /* Now parse all the lines */
2642 : Assert(PostmasterContext);
2643 1392 : hbacxt = AllocSetContextCreate(PostmasterContext,
2644 : "hba parser context",
2645 : ALLOCSET_SMALL_SIZES);
2646 1392 : oldcxt = MemoryContextSwitchTo(hbacxt);
2647 9338 : foreach(line, hba_lines)
2648 : {
2649 7946 : TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
2650 : HbaLine *newline;
2651 :
2652 : /* don't parse lines that already have errors */
2653 7946 : if (tok_line->err_msg != NULL)
2654 : {
2655 0 : ok = false;
2656 0 : continue;
2657 : }
2658 :
2659 7946 : if ((newline = parse_hba_line(tok_line, LOG)) == NULL)
2660 : {
2661 : /* Parse error; remember there's trouble */
2662 0 : ok = false;
2663 :
2664 : /*
2665 : * Keep parsing the rest of the file so we can report errors on
2666 : * more than the first line. Error has already been logged, no
2667 : * need for more chatter here.
2668 : */
2669 0 : continue;
2670 : }
2671 :
2672 7946 : new_parsed_lines = lappend(new_parsed_lines, newline);
2673 : }
2674 :
2675 : /*
2676 : * A valid HBA file must have at least one entry; else there's no way to
2677 : * connect to the postmaster. But only complain about this if we didn't
2678 : * already have parsing errors.
2679 : */
2680 1392 : if (ok && new_parsed_lines == NIL)
2681 : {
2682 0 : ereport(LOG,
2683 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2684 : errmsg("configuration file \"%s\" contains no entries",
2685 : HbaFileName)));
2686 0 : ok = false;
2687 : }
2688 :
2689 : /* Free tokenizer memory */
2690 1392 : free_auth_file(file, 0);
2691 1392 : MemoryContextSwitchTo(oldcxt);
2692 :
2693 1392 : if (!ok)
2694 : {
2695 : /*
2696 : * File contained one or more errors, so bail out.
2697 : * MemoryContextDelete is enough to clean up everything, including
2698 : * regexes.
2699 : */
2700 0 : MemoryContextDelete(hbacxt);
2701 0 : return false;
2702 : }
2703 :
2704 : /* Loaded new file successfully, replace the one we use */
2705 1392 : if (parsed_hba_context != NULL)
2706 206 : MemoryContextDelete(parsed_hba_context);
2707 1392 : parsed_hba_context = hbacxt;
2708 1392 : parsed_hba_lines = new_parsed_lines;
2709 :
2710 1392 : return true;
2711 : }
2712 :
2713 :
2714 : /*
2715 : * Parse one tokenised line from the ident config file and store the result in
2716 : * an IdentLine structure.
2717 : *
2718 : * If parsing fails, log a message at ereport level elevel, store an error
2719 : * string in tok_line->err_msg and return NULL.
2720 : *
2721 : * If ident_user is a regular expression (ie. begins with a slash), it is
2722 : * compiled and stored in IdentLine structure.
2723 : *
2724 : * Note: this function leaks memory when an error occurs. Caller is expected
2725 : * to have set a memory context that will be reset if this function returns
2726 : * NULL.
2727 : */
2728 : IdentLine *
2729 178 : parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
2730 : {
2731 178 : int line_num = tok_line->line_num;
2732 178 : char *file_name = tok_line->file_name;
2733 178 : char **err_msg = &tok_line->err_msg;
2734 : ListCell *field;
2735 : List *tokens;
2736 : AuthToken *token;
2737 : IdentLine *parsedline;
2738 :
2739 : Assert(tok_line->fields != NIL);
2740 178 : field = list_head(tok_line->fields);
2741 :
2742 178 : parsedline = palloc0(sizeof(IdentLine));
2743 178 : parsedline->linenumber = line_num;
2744 :
2745 : /* Get the map token (must exist) */
2746 178 : tokens = lfirst(field);
2747 178 : IDENT_MULTI_VALUE(tokens);
2748 178 : token = linitial(tokens);
2749 178 : parsedline->usermap = pstrdup(token->string);
2750 :
2751 : /* Get the ident user token */
2752 178 : field = lnext(tok_line->fields, field);
2753 178 : IDENT_FIELD_ABSENT(field);
2754 178 : tokens = lfirst(field);
2755 178 : IDENT_MULTI_VALUE(tokens);
2756 178 : token = linitial(tokens);
2757 :
2758 : /* Copy the ident user token */
2759 178 : parsedline->system_user = copy_auth_token(token);
2760 :
2761 : /* Get the PG rolename token */
2762 178 : field = lnext(tok_line->fields, field);
2763 178 : IDENT_FIELD_ABSENT(field);
2764 178 : tokens = lfirst(field);
2765 178 : IDENT_MULTI_VALUE(tokens);
2766 178 : token = linitial(tokens);
2767 178 : parsedline->pg_user = copy_auth_token(token);
2768 :
2769 : /*
2770 : * Now that the field validation is done, compile a regex from the user
2771 : * tokens, if necessary.
2772 : */
2773 178 : if (regcomp_auth_token(parsedline->system_user, file_name, line_num,
2774 : err_msg, elevel))
2775 : {
2776 : /* err_msg includes the error to report */
2777 0 : return NULL;
2778 : }
2779 :
2780 178 : if (regcomp_auth_token(parsedline->pg_user, file_name, line_num,
2781 : err_msg, elevel))
2782 : {
2783 : /* err_msg includes the error to report */
2784 0 : return NULL;
2785 : }
2786 :
2787 178 : return parsedline;
2788 : }
2789 :
2790 : /*
2791 : * Process one line from the parsed ident config lines.
2792 : *
2793 : * Compare input parsed ident line to the needed map, pg_user and system_user.
2794 : * *found_p and *error_p are set according to our results.
2795 : */
2796 : static void
2797 50 : check_ident_usermap(IdentLine *identLine, const char *usermap_name,
2798 : const char *pg_user, const char *system_user,
2799 : bool case_insensitive, bool *found_p, bool *error_p)
2800 : {
2801 : Oid roleid;
2802 :
2803 50 : *found_p = false;
2804 50 : *error_p = false;
2805 :
2806 50 : if (strcmp(identLine->usermap, usermap_name) != 0)
2807 : /* Line does not match the map name we're looking for, so just abort */
2808 6 : return;
2809 :
2810 : /* Get the target role's OID. Note we do not error out for bad role. */
2811 44 : roleid = get_role_oid(pg_user, true);
2812 :
2813 : /* Match? */
2814 44 : if (token_has_regexp(identLine->system_user))
2815 : {
2816 : /*
2817 : * Process the system username as a regular expression that returns
2818 : * exactly one match. This is replaced for \1 in the database username
2819 : * string, if present.
2820 : */
2821 : int r;
2822 : regmatch_t matches[2];
2823 : char *ofs;
2824 : AuthToken *expanded_pg_user_token;
2825 24 : bool created_temporary_token = false;
2826 :
2827 24 : r = regexec_auth_token(system_user, identLine->system_user, 2, matches);
2828 24 : if (r)
2829 : {
2830 : char errstr[100];
2831 :
2832 2 : if (r != REG_NOMATCH)
2833 : {
2834 : /* REG_NOMATCH is not an error, everything else is */
2835 0 : pg_regerror(r, identLine->system_user->regex, errstr, sizeof(errstr));
2836 0 : ereport(LOG,
2837 : (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
2838 : errmsg("regular expression match for \"%s\" failed: %s",
2839 : identLine->system_user->string + 1, errstr)));
2840 0 : *error_p = true;
2841 : }
2842 2 : return;
2843 : }
2844 :
2845 : /*
2846 : * Replace \1 with the first captured group unless the field already
2847 : * has some special meaning, like a group membership or a regexp-based
2848 : * check.
2849 : */
2850 22 : if (!token_is_member_check(identLine->pg_user) &&
2851 16 : !token_has_regexp(identLine->pg_user) &&
2852 12 : (ofs = strstr(identLine->pg_user->string, "\\1")) != NULL)
2853 4 : {
2854 : char *expanded_pg_user;
2855 : int offset;
2856 :
2857 : /* substitution of the first argument requested */
2858 6 : if (matches[1].rm_so < 0)
2859 : {
2860 2 : ereport(LOG,
2861 : (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
2862 : errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"",
2863 : identLine->system_user->string + 1, identLine->pg_user->string)));
2864 2 : *error_p = true;
2865 2 : return;
2866 : }
2867 :
2868 : /*
2869 : * length: original length minus length of \1 plus length of match
2870 : * plus null terminator
2871 : */
2872 4 : expanded_pg_user = palloc0(strlen(identLine->pg_user->string) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1);
2873 4 : offset = ofs - identLine->pg_user->string;
2874 4 : memcpy(expanded_pg_user, identLine->pg_user->string, offset);
2875 4 : memcpy(expanded_pg_user + offset,
2876 4 : system_user + matches[1].rm_so,
2877 4 : matches[1].rm_eo - matches[1].rm_so);
2878 4 : strcat(expanded_pg_user, ofs + 2);
2879 :
2880 : /*
2881 : * Mark the token as quoted, so it will only be compared literally
2882 : * and not for some special meaning, such as "all" or a group
2883 : * membership check.
2884 : */
2885 4 : expanded_pg_user_token = make_auth_token(expanded_pg_user, true);
2886 4 : created_temporary_token = true;
2887 4 : pfree(expanded_pg_user);
2888 : }
2889 : else
2890 : {
2891 16 : expanded_pg_user_token = identLine->pg_user;
2892 : }
2893 :
2894 : /* check the Postgres user */
2895 20 : *found_p = check_role(pg_user, roleid,
2896 20 : list_make1(expanded_pg_user_token),
2897 : case_insensitive);
2898 :
2899 20 : if (created_temporary_token)
2900 4 : free_auth_token(expanded_pg_user_token);
2901 :
2902 20 : return;
2903 : }
2904 : else
2905 : {
2906 : /*
2907 : * Not a regular expression, so make a complete match. If the system
2908 : * user does not match, just leave.
2909 : */
2910 20 : if (case_insensitive)
2911 : {
2912 0 : if (!token_matches_insensitive(identLine->system_user,
2913 : system_user))
2914 0 : return;
2915 : }
2916 : else
2917 : {
2918 20 : if (!token_matches(identLine->system_user, system_user))
2919 0 : return;
2920 : }
2921 :
2922 : /* check the Postgres user */
2923 20 : *found_p = check_role(pg_user, roleid,
2924 20 : list_make1(identLine->pg_user),
2925 : case_insensitive);
2926 : }
2927 : }
2928 :
2929 :
2930 : /*
2931 : * Scan the (pre-parsed) ident usermap file line by line, looking for a match
2932 : *
2933 : * See if the system user with ident username "system_user" is allowed to act as
2934 : * Postgres user "pg_user" according to usermap "usermap_name".
2935 : *
2936 : * Special case: Usermap NULL, equivalent to what was previously called
2937 : * "sameuser" or "samerole", means don't look in the usermap file.
2938 : * That's an implied map wherein "pg_user" must be identical to
2939 : * "system_user" in order to be authorized.
2940 : *
2941 : * Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR.
2942 : */
2943 : int
2944 108 : check_usermap(const char *usermap_name,
2945 : const char *pg_user,
2946 : const char *system_user,
2947 : bool case_insensitive)
2948 : {
2949 108 : bool found_entry = false,
2950 108 : error = false;
2951 :
2952 108 : if (usermap_name == NULL || usermap_name[0] == '\0')
2953 : {
2954 64 : if (case_insensitive)
2955 : {
2956 0 : if (pg_strcasecmp(pg_user, system_user) == 0)
2957 0 : return STATUS_OK;
2958 : }
2959 : else
2960 : {
2961 64 : if (strcmp(pg_user, system_user) == 0)
2962 58 : return STATUS_OK;
2963 : }
2964 6 : ereport(LOG,
2965 : (errmsg("provided user name (%s) and authenticated user name (%s) do not match",
2966 : pg_user, system_user)));
2967 6 : return STATUS_ERROR;
2968 : }
2969 : else
2970 : {
2971 : ListCell *line_cell;
2972 :
2973 58 : foreach(line_cell, parsed_ident_lines)
2974 : {
2975 50 : check_ident_usermap(lfirst(line_cell), usermap_name,
2976 : pg_user, system_user, case_insensitive,
2977 : &found_entry, &error);
2978 50 : if (found_entry || error)
2979 : break;
2980 : }
2981 : }
2982 44 : if (!found_entry && !error)
2983 : {
2984 8 : ereport(LOG,
2985 : (errmsg("no match in usermap \"%s\" for user \"%s\" authenticated as \"%s\"",
2986 : usermap_name, pg_user, system_user)));
2987 : }
2988 44 : return found_entry ? STATUS_OK : STATUS_ERROR;
2989 : }
2990 :
2991 :
2992 : /*
2993 : * Read the ident config file and create a List of IdentLine records for
2994 : * the contents.
2995 : *
2996 : * This works the same as load_hba(), but for the user config file.
2997 : */
2998 : bool
2999 1392 : load_ident(void)
3000 : {
3001 : FILE *file;
3002 1392 : List *ident_lines = NIL;
3003 : ListCell *line_cell;
3004 1392 : List *new_parsed_lines = NIL;
3005 1392 : bool ok = true;
3006 : MemoryContext oldcxt;
3007 : MemoryContext ident_context;
3008 : IdentLine *newline;
3009 :
3010 : /* not FATAL ... we just won't do any special ident maps */
3011 1392 : file = open_auth_file(IdentFileName, LOG, 0, NULL);
3012 1392 : if (file == NULL)
3013 : {
3014 : /* error already logged */
3015 0 : return false;
3016 : }
3017 :
3018 1392 : tokenize_auth_file(IdentFileName, file, &ident_lines, LOG, 0);
3019 :
3020 : /* Now parse all the lines */
3021 : Assert(PostmasterContext);
3022 1392 : ident_context = AllocSetContextCreate(PostmasterContext,
3023 : "ident parser context",
3024 : ALLOCSET_SMALL_SIZES);
3025 1392 : oldcxt = MemoryContextSwitchTo(ident_context);
3026 1570 : foreach(line_cell, ident_lines)
3027 : {
3028 178 : TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line_cell);
3029 :
3030 : /* don't parse lines that already have errors */
3031 178 : if (tok_line->err_msg != NULL)
3032 : {
3033 0 : ok = false;
3034 0 : continue;
3035 : }
3036 :
3037 178 : if ((newline = parse_ident_line(tok_line, LOG)) == NULL)
3038 : {
3039 : /* Parse error; remember there's trouble */
3040 0 : ok = false;
3041 :
3042 : /*
3043 : * Keep parsing the rest of the file so we can report errors on
3044 : * more than the first line. Error has already been logged, no
3045 : * need for more chatter here.
3046 : */
3047 0 : continue;
3048 : }
3049 :
3050 178 : new_parsed_lines = lappend(new_parsed_lines, newline);
3051 : }
3052 :
3053 : /* Free tokenizer memory */
3054 1392 : free_auth_file(file, 0);
3055 1392 : MemoryContextSwitchTo(oldcxt);
3056 :
3057 1392 : if (!ok)
3058 : {
3059 : /*
3060 : * File contained one or more errors, so bail out.
3061 : * MemoryContextDelete is enough to clean up everything, including
3062 : * regexes.
3063 : */
3064 0 : MemoryContextDelete(ident_context);
3065 0 : return false;
3066 : }
3067 :
3068 : /* Loaded new file successfully, replace the one we use */
3069 1392 : if (parsed_ident_context != NULL)
3070 206 : MemoryContextDelete(parsed_ident_context);
3071 :
3072 1392 : parsed_ident_context = ident_context;
3073 1392 : parsed_ident_lines = new_parsed_lines;
3074 :
3075 1392 : return true;
3076 : }
3077 :
3078 :
3079 :
3080 : /*
3081 : * Determine what authentication method should be used when accessing database
3082 : * "database" from frontend "raddr", user "user". Return the method and
3083 : * an optional argument (stored in fields of *port), and STATUS_OK.
3084 : *
3085 : * If the file does not contain any entry matching the request, we return
3086 : * method = uaImplicitReject.
3087 : */
3088 : void
3089 18152 : hba_getauthmethod(hbaPort *port)
3090 : {
3091 18152 : check_hba(port);
3092 18152 : }
3093 :
3094 :
3095 : /*
3096 : * Return the name of the auth method in use ("gss", "md5", "trust", etc.).
3097 : *
3098 : * The return value is statically allocated (see the UserAuthName array) and
3099 : * should not be freed.
3100 : */
3101 : const char *
3102 462 : hba_authname(UserAuth auth_method)
3103 : {
3104 462 : return UserAuthName[auth_method];
3105 : }
|