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