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