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