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