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