Age Owner Branch data TLA 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
8182 tgl@sss.pgh.pa.us 146 :CBC 753302 : pg_isblank(const char c)
147 : : {
8473 148 [ + + + + : 753302 : 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
772 187 : 188492 : next_token(char **lineptr, StringInfo buf,
188 : : bool *initial_quote, bool *terminating_comma)
189 : : {
190 : : int c;
8556 bruce@momjian.us 191 : 188492 : bool in_quote = false;
192 : 188492 : bool was_quote = false;
7678 193 : 188492 : bool saw_quote = false;
194 : :
195 : : /* Initialize output parameters */
772 tgl@sss.pgh.pa.us 196 : 188492 : resetStringInfo(buf);
5663 197 : 188492 : *initial_quote = false;
5192 alvherre@alvh.no-ip. 198 : 188492 : *terminating_comma = false;
199 : :
200 : : /* Move over any whitespace and commas preceding the next token */
4563 magnus@hagander.net 201 [ + + + + : 419122 : 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 : : */
3141 tgl@sss.pgh.pa.us 208 [ + + ]: 339760 : while (c != '\0' &&
6253 209 [ + + + + ]: 334182 : (!pg_isblank(c) || in_quote))
210 : : {
211 : : /* skip comments to EOL */
7887 neilc@samurai.com 212 [ + + + - ]: 313676 : if (c == '#' && !in_quote)
213 : : {
3141 tgl@sss.pgh.pa.us 214 [ + + ]: 6798214 : while ((c = (*(*lineptr)++)) != '\0')
215 : : ;
7887 neilc@samurai.com 216 : 162400 : break;
217 : : }
218 : :
219 : : /* we do not pass back a terminating comma in the token */
6253 tgl@sss.pgh.pa.us 220 [ + + + + ]: 151276 : if (c == ',' && !in_quote)
221 : : {
5192 alvherre@alvh.no-ip. 222 : 8 : *terminating_comma = true;
7887 neilc@samurai.com 223 : 8 : break;
224 : : }
225 : :
5192 alvherre@alvh.no-ip. 226 [ + + - + ]: 151268 : if (c != '"' || was_quote)
772 tgl@sss.pgh.pa.us 227 : 151062 : appendStringInfoChar(buf, c);
228 : :
229 : : /* Literal double-quote is two double-quotes */
7887 neilc@samurai.com 230 [ + + + + ]: 151268 : if (in_quote && c == '"')
231 : 103 : was_quote = !was_quote;
232 : : else
233 : 151165 : was_quote = false;
234 : :
235 [ + + ]: 151268 : if (c == '"')
236 : : {
237 : 206 : in_quote = !in_quote;
238 : 206 : saw_quote = true;
772 tgl@sss.pgh.pa.us 239 [ + + ]: 206 : if (buf->len == 0)
5663 240 : 47 : *initial_quote = true;
241 : : }
242 : :
4563 magnus@hagander.net 243 : 151268 : 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 : 188492 : (*lineptr)--;
251 : :
772 tgl@sss.pgh.pa.us 252 [ + + + + ]: 188492 : return (saw_quote || buf->len > 0);
253 : : }
254 : :
255 : : /*
256 : : * Construct a palloc'd AuthToken struct, copying the given string.
257 : : */
258 : : static AuthToken *
1262 michael@paquier.xyz 259 : 37242 : make_auth_token(const char *token, bool quoted)
260 : : {
261 : : AuthToken *authtoken;
262 : : int toklen;
263 : :
5192 alvherre@alvh.no-ip. 264 : 37242 : toklen = strlen(token);
265 : : /* we copy string into same palloc block as the struct */
1053 michael@paquier.xyz 266 : 37242 : authtoken = (AuthToken *) palloc0(sizeof(AuthToken) + toklen + 1);
1262 267 : 37242 : authtoken->string = (char *) authtoken + sizeof(AuthToken);
268 : 37242 : authtoken->quoted = quoted;
1053 269 : 37242 : authtoken->regex = NULL;
1262 270 : 37242 : memcpy(authtoken->string, token, toklen + 1);
271 : :
272 : 37242 : 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
1053 280 : 36 : free_auth_token(AuthToken *token)
281 : : {
282 [ - + ]: 36 : if (token_has_regexp(token))
1053 michael@paquier.xyz 283 :UBC 0 : pg_regfree(token->regex);
1053 michael@paquier.xyz 284 :CBC 36 : }
285 : :
286 : : /*
287 : : * Copy a AuthToken struct into freshly palloc'd memory.
288 : : */
289 : : static AuthToken *
1262 290 : 11118 : copy_auth_token(AuthToken *in)
291 : : {
292 : 11118 : AuthToken *out = make_auth_token(in->string, in->quoted);
293 : :
5192 alvherre@alvh.no-ip. 294 : 11118 : 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
1051 michael@paquier.xyz 303 : 11118 : 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 : :
1053 310 [ - + ]: 11118 : Assert(token->regex == NULL);
311 : :
312 [ + + ]: 11118 : if (token->string[0] != '/')
313 : 11070 : return 0; /* nothing to compile */
314 : :
315 : 48 : token->regex = (regex_t *) palloc0(sizeof(regex_t));
316 : 48 : wstr = palloc((strlen(token->string + 1) + 1) * sizeof(pg_wchar));
317 : 48 : wlen = pg_mb2wchar_with_len(token->string + 1,
318 : 48 : wstr, strlen(token->string + 1));
319 : :
320 : 48 : rc = pg_regcomp(token->regex, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
321 : :
1051 322 [ - + ]: 48 : if (rc)
323 : : {
324 : : char errstr[100];
325 : :
1051 michael@paquier.xyz 326 :UBC 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 : :
1053 michael@paquier.xyz 338 :CBC 48 : pfree(wstr);
339 : 48 : 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 : 57 : 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 [ + - - + ]: 57 : Assert(token->string[0] == '/' && token->regex);
356 : :
357 : 57 : wmatchstr = palloc((strlen(match) + 1) * sizeof(pg_wchar));
358 : 57 : wmatchlen = pg_mb2wchar_with_len(match, wmatchstr, strlen(match));
359 : :
360 : 57 : r = pg_regexec(token->regex, wmatchstr, wmatchlen, 0, NULL, nmatch, pmatch, 0);
361 : :
362 : 57 : pfree(wmatchstr);
363 : 57 : 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 *
3141 tgl@sss.pgh.pa.us 381 : 188484 : 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;
5192 alvherre@alvh.no-ip. 387 : 188484 : List *tokens = NIL;
388 : :
772 tgl@sss.pgh.pa.us 389 : 188484 : initStringInfo(&buf);
390 : :
391 : : do
392 : : {
393 [ + + ]: 188492 : if (!next_token(lineptr, &buf,
394 : : &initial_quote, &trailing_comma))
8556 bruce@momjian.us 395 : 162402 : break;
396 : :
397 : : /* Is this referencing a file? */
772 tgl@sss.pgh.pa.us 398 [ + + + + : 26090 : if (!initial_quote && buf.len > 1 && buf.data[0] == '@')
+ + ]
399 : 2 : 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 : : */
1017 michael@paquier.xyz 409 : 26088 : oldcxt = MemoryContextSwitchTo(tokenize_context);
772 tgl@sss.pgh.pa.us 410 : 26088 : tokens = lappend(tokens, make_auth_token(buf.data, initial_quote));
1017 michael@paquier.xyz 411 : 26088 : MemoryContextSwitchTo(oldcxt);
412 : : }
3141 tgl@sss.pgh.pa.us 413 [ + + + - ]: 26090 : } while (trailing_comma && (*err_msg == NULL));
414 : :
772 415 : 188484 : pfree(buf.data);
416 : :
5192 alvherre@alvh.no-ip. 417 : 188484 : 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
1017 michael@paquier.xyz 440 : 21 : 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 : 21 : inc_fullname = AbsoluteConfigLocation(inc_filename, outer_filename);
452 : 21 : inc_file = open_auth_file(inc_fullname, elevel, depth, err_msg);
453 : :
454 [ + + ]: 21 : if (!inc_file)
455 : : {
456 [ + - + - ]: 3 : if (errno == ENOENT && missing_ok)
457 : : {
458 [ + + ]: 3 : ereport(elevel,
459 : : (errmsg("skipping missing authentication file \"%s\"",
460 : : inc_fullname)));
461 : 3 : *err_msg = NULL;
462 : 3 : pfree(inc_fullname);
463 : 3 : return;
464 : : }
465 : :
466 : : /* error in err_msg, so leave and report */
1017 michael@paquier.xyz 467 :UBC 0 : pfree(inc_fullname);
468 [ # # ]: 0 : Assert(err_msg);
469 : 0 : return;
470 : : }
471 : :
1017 michael@paquier.xyz 472 :CBC 18 : tokenize_auth_file(inc_fullname, inc_file, tok_lines, elevel,
473 : : depth);
474 : 18 : free_auth_file(inc_file, depth);
475 : 18 : 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 : 2 : 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 : 2 : List *inc_lines = NIL;
505 : : ListCell *inc_line;
506 : :
1032 507 : 2 : inc_fullname = AbsoluteConfigLocation(inc_filename, outer_filename);
1027 508 : 2 : inc_file = open_auth_file(inc_fullname, elevel, depth, err_msg);
509 : :
7558 tgl@sss.pgh.pa.us 510 [ - + ]: 2 : if (inc_file == NULL)
511 : : {
512 : : /* error already logged */
8556 bruce@momjian.us 513 :UBC 0 : pfree(inc_fullname);
5192 alvherre@alvh.no-ip. 514 : 0 : return tokens;
515 : : }
516 : :
517 : : /*
518 : : * There is possible recursion here if the file contains @ or an include
519 : : * record.
520 : : */
1017 michael@paquier.xyz 521 :CBC 2 : tokenize_auth_file(inc_fullname, inc_file, &inc_lines, elevel,
522 : : depth);
523 : :
7558 tgl@sss.pgh.pa.us 524 : 2 : 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 : : */
5192 alvherre@alvh.no-ip. 530 [ + - + + : 6 : foreach(inc_line, inc_lines)
+ + ]
531 : : {
1262 michael@paquier.xyz 532 : 4 : TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(inc_line);
533 : : ListCell *inc_field;
534 : :
535 : : /* If any line has an error, propagate that up to caller */
3141 tgl@sss.pgh.pa.us 536 [ - + ]: 4 : if (tok_line->err_msg)
537 : : {
3141 tgl@sss.pgh.pa.us 538 :UBC 0 : *err_msg = pstrdup(tok_line->err_msg);
539 : 0 : break;
540 : : }
541 : :
3144 tgl@sss.pgh.pa.us 542 [ + - + + :CBC 8 : foreach(inc_field, tok_line->fields)
+ + ]
543 : : {
5192 alvherre@alvh.no-ip. 544 : 4 : List *inc_tokens = lfirst(inc_field);
545 : : ListCell *inc_token;
546 : :
547 [ + - + + : 8 : foreach(inc_token, inc_tokens)
+ + ]
548 : : {
1262 michael@paquier.xyz 549 : 4 : 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 : : */
1017 556 : 4 : oldcxt = MemoryContextSwitchTo(tokenize_context);
557 : 4 : tokens = lappend(tokens, token);
558 : 4 : MemoryContextSwitchTo(oldcxt);
559 : : }
560 : : }
561 : : }
562 : :
563 : 2 : free_auth_file(inc_file, depth);
5192 alvherre@alvh.no-ip. 564 : 2 : return tokens;
565 : : }
566 : :
567 : : /*
568 : : * free_auth_file
569 : : * Free a file opened by open_auth_file().
570 : : */
571 : : void
1017 michael@paquier.xyz 572 : 1909 : free_auth_file(FILE *file, int depth)
573 : : {
574 : 1909 : FreeFile(file);
575 : :
576 : : /* If this is the last cleanup, remove the tokenization context */
1016 577 [ + + ]: 1909 : if (depth == CONF_FILE_START_DEPTH)
578 : : {
1017 579 : 1889 : MemoryContextDelete(tokenize_context);
580 : 1889 : tokenize_context = NULL;
581 : : }
582 : 1909 : }
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 *
1027 597 : 1912 : 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 : : */
1016 607 [ - + ]: 1912 : if (depth > CONF_FILE_MAX_DEPTH)
608 : : {
1027 michael@paquier.xyz 609 [ # # ]:UBC 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 : :
1027 michael@paquier.xyz 619 :CBC 1912 : file = AllocateFile(filename, "r");
620 [ + + ]: 1912 : if (file == NULL)
621 : : {
622 : 3 : int save_errno = errno;
623 : :
624 [ + + ]: 3 : ereport(elevel,
625 : : (errcode_for_file_access(),
626 : : errmsg("could not open file \"%s\": %m",
627 : : filename)));
628 [ + - ]: 3 : if (err_msg)
629 : : {
367 peter@eisentraut.org 630 : 3 : errno = save_errno;
631 : 3 : *err_msg = psprintf("could not open file \"%s\": %m",
632 : : filename);
633 : : }
634 : : /* the caller may care about some specific errno */
1017 michael@paquier.xyz 635 : 3 : errno = save_errno;
1027 636 : 3 : 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 : : */
1016 644 [ + + ]: 1909 : if (depth == CONF_FILE_START_DEPTH)
645 : : {
646 : : /*
647 : : * A context may be present, but assume that it has been eliminated
648 : : * already.
649 : : */
1017 650 : 1889 : tokenize_context = AllocSetContextCreate(CurrentMemoryContext,
651 : : "tokenize_context",
652 : : ALLOCSET_START_SMALL_SIZES);
653 : : }
654 : :
1027 655 : 1909 : return file;
656 : : }
657 : :
658 : : /*
659 : : * error context callback for tokenize_auth_file()
660 : : */
661 : : static void
662 : 4 : tokenize_error_callback(void *arg)
663 : : {
664 : 4 : tokenize_error_callback_arg *callback_arg = (tokenize_error_callback_arg *) arg;
665 : :
666 : 4 : errcontext("line %d of configuration file \"%s\"",
667 : : callback_arg->linenum, callback_arg->filename);
668 : 4 : }
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
1262 691 : 1909 : tokenize_auth_file(const char *filename, FILE *file, List **tok_lines,
692 : : int elevel, int depth)
693 : : {
8803 tgl@sss.pgh.pa.us 694 : 1909 : 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 : :
1017 michael@paquier.xyz 701 [ - + ]: 1909 : Assert(tokenize_context);
702 : :
1027 703 : 1909 : callback_arg.filename = filename;
704 : 1909 : callback_arg.linenum = line_number;
705 : :
706 : 1909 : tokenerrcontext.callback = tokenize_error_callback;
282 peter@eisentraut.org 707 : 1909 : tokenerrcontext.arg = &callback_arg;
1027 michael@paquier.xyz 708 : 1909 : tokenerrcontext.previous = error_context_stack;
709 : 1909 : 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 : : */
3720 tgl@sss.pgh.pa.us 715 : 1909 : linecxt = AllocSetContextCreate(CurrentMemoryContext,
716 : : "tokenize_auth_file",
717 : : ALLOCSET_SMALL_SIZES);
1017 michael@paquier.xyz 718 : 1909 : funccxt = MemoryContextSwitchTo(linecxt);
719 : :
1829 tgl@sss.pgh.pa.us 720 : 1909 : initStringInfo(&buf);
721 : :
1016 michael@paquier.xyz 722 [ + + ]: 1909 : if (depth == CONF_FILE_START_DEPTH)
1017 723 : 1889 : *tok_lines = NIL;
724 : :
5666 tgl@sss.pgh.pa.us 725 [ + + + - ]: 177576 : while (!feof(file) && !ferror(file))
726 : : {
727 : : TokenizedAuthLine *tok_line;
728 : : MemoryContext oldcxt;
729 : : char *lineptr;
3144 730 : 175667 : List *current_line = NIL;
3141 731 : 175667 : char *err_msg = NULL;
1829 732 : 175667 : int last_backslash_buflen = 0;
733 : 175667 : int continuations = 0;
734 : :
735 : : /* Collect the next input line, handling backslash continuations */
736 : 175667 : resetStringInfo(&buf);
737 : :
1389 738 [ + + ]: 175684 : while (pg_get_line_append(file, &buf, NULL))
739 : : {
740 : : /* Strip trailing newline, including \r in case we're on Windows */
1829 741 : 173775 : 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 [ + + ]: 173775 : if (buf.len > last_backslash_buflen &&
749 [ + + ]: 167995 : buf.data[buf.len - 1] == '\\')
750 : : {
751 : : /* Continuation, so strip it and keep reading */
752 : 17 : buf.data[--buf.len] = '\0';
753 : 17 : last_backslash_buflen = buf.len;
754 : 17 : continuations++;
755 : 17 : continue;
756 : : }
757 : :
758 : : /* Nope, so we have the whole line */
759 : 173758 : break;
760 : : }
761 : :
1826 762 [ - + ]: 175667 : if (ferror(file))
763 : : {
764 : : /* I/O error! */
1826 tgl@sss.pgh.pa.us 765 :UBC 0 : int save_errno = errno;
766 : :
767 [ # # ]: 0 : ereport(elevel,
768 : : (errcode_for_file_access(),
769 : : errmsg("could not read file \"%s\": %m", filename)));
367 peter@eisentraut.org 770 : 0 : errno = save_errno;
771 : 0 : err_msg = psprintf("could not read file \"%s\": %m",
772 : : filename);
1826 tgl@sss.pgh.pa.us 773 : 0 : break;
774 : : }
775 : :
776 : : /* Parse fields */
1829 tgl@sss.pgh.pa.us 777 :CBC 175667 : lineptr = buf.data;
3141 778 [ + + + - ]: 539818 : while (*lineptr && err_msg == NULL)
779 : : {
780 : : List *current_field;
781 : :
782 : 188484 : 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 */
3144 785 [ + + ]: 188484 : 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 : : */
1017 michael@paquier.xyz 791 : 26082 : oldcxt = MemoryContextSwitchTo(tokenize_context);
3144 tgl@sss.pgh.pa.us 792 : 26082 : current_line = lappend(current_line, current_field);
1017 michael@paquier.xyz 793 : 26082 : MemoryContextSwitchTo(oldcxt);
794 : : }
795 : : }
796 : :
797 : : /*
798 : : * Reached EOL; no need to emit line to TokenizedAuthLine list if it's
799 : : * boring.
800 : : */
801 [ + + + - ]: 175667 : if (current_line == NIL && err_msg == NULL)
802 : 170091 : goto next_line;
803 : :
804 : : /* If the line is valid, check if that's an include directive */
805 [ + - + + ]: 5576 : if (err_msg == NULL && list_length(current_line) == 2)
806 : : {
807 : : AuthToken *first,
808 : : *second;
809 : :
810 : 18 : first = linitial(linitial_node(List, current_line));
811 : 18 : second = linitial(lsecond_node(List, current_line));
812 : :
813 [ + + ]: 18 : if (strcmp(first->string, "include") == 0)
814 : : {
815 : 9 : tokenize_include_file(filename, second->string, tok_lines,
816 : : elevel, depth + 1, false, &err_msg);
817 : :
818 [ - + ]: 9 : if (err_msg)
1017 michael@paquier.xyz 819 :UBC 0 : goto process_line;
820 : :
821 : : /*
822 : : * tokenize_auth_file() has taken care of creating the
823 : : * TokenizedAuthLines.
824 : : */
1017 michael@paquier.xyz 825 :CBC 9 : goto next_line;
826 : : }
827 [ + + ]: 9 : else if (strcmp(first->string, "include_dir") == 0)
828 : : {
829 : : char **filenames;
830 : 3 : char *dir_name = second->string;
831 : : int num_filenames;
832 : : StringInfoData err_buf;
833 : :
834 : 3 : filenames = GetConfFilesInDir(dir_name, filename, elevel,
835 : : &num_filenames, &err_msg);
836 : :
837 [ - + ]: 3 : if (!filenames)
838 : : {
839 : : /* the error is in err_msg, so create an entry */
1017 michael@paquier.xyz 840 :UBC 0 : goto process_line;
841 : : }
842 : :
1017 michael@paquier.xyz 843 :CBC 3 : initStringInfo(&err_buf);
844 [ + + ]: 9 : for (int i = 0; i < num_filenames; i++)
845 : : {
846 : 6 : tokenize_include_file(filename, filenames[i], tok_lines,
847 : : elevel, depth + 1, false, &err_msg);
848 : : /* cumulate errors if any */
849 [ - + ]: 6 : if (err_msg)
850 : : {
1017 michael@paquier.xyz 851 [ # # ]:UBC 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 */
1017 michael@paquier.xyz 858 [ + + ]:CBC 9 : for (int i = 0; i < num_filenames; i++)
859 : 6 : pfree(filenames[i]);
860 : 3 : pfree(filenames);
861 : :
862 : : /*
863 : : * If there were no errors, the line is fully processed,
864 : : * bypass the general TokenizedAuthLine processing.
865 : : */
866 [ + - ]: 3 : if (err_buf.len == 0)
867 : 3 : goto next_line;
868 : :
869 : : /* Otherwise, process the cumulated errors, if any. */
1017 michael@paquier.xyz 870 :UBC 0 : err_msg = err_buf.data;
871 : 0 : goto process_line;
872 : : }
1017 michael@paquier.xyz 873 [ - + ]:CBC 6 : else if (strcmp(first->string, "include_if_exists") == 0)
874 : : {
875 : :
876 : 6 : tokenize_include_file(filename, second->string, tok_lines,
877 : : elevel, depth + 1, true, &err_msg);
878 [ - + ]: 6 : if (err_msg)
1017 michael@paquier.xyz 879 :UBC 0 : goto process_line;
880 : :
881 : : /*
882 : : * tokenize_auth_file() has taken care of creating the
883 : : * TokenizedAuthLines.
884 : : */
1017 michael@paquier.xyz 885 :CBC 6 : goto next_line;
886 : : }
887 : : }
888 : :
889 : 5558 : 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 : 5558 : oldcxt = MemoryContextSwitchTo(tokenize_context);
897 : 5558 : tok_line = (TokenizedAuthLine *) palloc0(sizeof(TokenizedAuthLine));
898 : 5558 : tok_line->fields = current_line;
899 : 5558 : tok_line->file_name = pstrdup(filename);
900 : 5558 : tok_line->line_num = line_number;
901 : 5558 : tok_line->raw_line = pstrdup(buf.data);
902 [ - + ]: 5558 : tok_line->err_msg = err_msg ? pstrdup(err_msg) : NULL;
903 : 5558 : *tok_lines = lappend(*tok_lines, tok_line);
904 : 5558 : MemoryContextSwitchTo(oldcxt);
905 : :
906 : 175667 : next_line:
1829 tgl@sss.pgh.pa.us 907 : 175667 : line_number += continuations + 1;
1027 michael@paquier.xyz 908 : 175667 : callback_arg.linenum = line_number;
909 : : }
910 : :
1017 911 : 1909 : MemoryContextSwitchTo(funccxt);
912 : 1909 : MemoryContextDelete(linecxt);
913 : :
1027 914 : 1909 : error_context_stack = tokenerrcontext.previous;
8804 bruce@momjian.us 915 : 1909 : }
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
5852 tgl@sss.pgh.pa.us 925 : 13 : is_member(Oid userid, const char *role)
926 : : {
927 : : Oid roleid;
928 : :
929 [ - + ]: 13 : if (!OidIsValid(userid))
7375 tgl@sss.pgh.pa.us 930 :UBC 0 : return false; /* if user not exist, say "no" */
931 : :
5511 rhaas@postgresql.org 932 :CBC 13 : roleid = get_role_oid(role, true);
933 : :
5852 tgl@sss.pgh.pa.us 934 [ - + ]: 13 : if (!OidIsValid(roleid))
5852 tgl@sss.pgh.pa.us 935 :UBC 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 : : */
5056 andrew@dunslane.net 942 :CBC 13 : 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
960 michael@paquier.xyz 954 : 12379 : check_role(const char *role, Oid roleid, List *tokens, bool case_insensitive)
955 : : {
956 : : ListCell *cell;
957 : : AuthToken *tok;
958 : :
5192 alvherre@alvh.no-ip. 959 [ + - + + : 12809 : foreach(cell, tokens)
+ + ]
960 : : {
961 : 12383 : tok = lfirst(cell);
960 michael@paquier.xyz 962 [ + + + + ]: 12383 : if (token_is_member_check(tok))
963 : : {
5192 alvherre@alvh.no-ip. 964 [ + + ]: 7 : if (is_member(roleid, tok->string + 1))
8192 tgl@sss.pgh.pa.us 965 : 11953 : return true;
966 : : }
1048 michael@paquier.xyz 967 [ + + + + ]: 12376 : else if (token_is_keyword(tok, "all"))
968 : 11693 : return true;
969 [ + + ]: 683 : else if (token_has_regexp(tok))
970 : : {
971 [ + + ]: 8 : if (regexec_auth_token(role, tok, 0, NULL) == REG_OKAY)
972 : 4 : return true;
973 : : }
960 974 [ - + ]: 675 : else if (case_insensitive)
975 : : {
960 michael@paquier.xyz 976 [ # # ]:UBC 0 : if (token_matches_insensitive(tok, role))
977 : 0 : return true;
978 : : }
1048 michael@paquier.xyz 979 [ + + ]:CBC 675 : else if (token_matches(tok, role))
8192 tgl@sss.pgh.pa.us 980 : 250 : return true;
981 : : }
982 : 426 : 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
5192 alvherre@alvh.no-ip. 993 : 12921 : check_db(const char *dbname, const char *role, Oid roleid, List *tokens)
994 : : {
995 : : ListCell *cell;
996 : : AuthToken *tok;
997 : :
998 [ + - + + : 13522 : foreach(cell, tokens)
+ + ]
999 : : {
1000 : 12925 : tok = lfirst(cell);
3127 peter_e@gmx.net 1001 [ + + + + ]: 12925 : if (am_walsender && !am_db_walsender)
1002 : : {
1003 : : /*
1004 : : * physical replication walsender connections can only match
1005 : : * replication keyword
1006 : : */
5192 alvherre@alvh.no-ip. 1007 [ + - + + ]: 894 : if (token_is_keyword(tok, "replication"))
5617 tgl@sss.pgh.pa.us 1008 : 12324 : return true;
1009 : : }
5192 alvherre@alvh.no-ip. 1010 [ + - + + ]: 12031 : else if (token_is_keyword(tok, "all"))
8192 tgl@sss.pgh.pa.us 1011 : 11356 : return true;
5192 alvherre@alvh.no-ip. 1012 [ + - - + ]: 675 : else if (token_is_keyword(tok, "sameuser"))
1013 : : {
7375 tgl@sss.pgh.pa.us 1014 [ # # ]:UBC 0 : if (strcmp(dbname, role) == 0)
8192 1015 : 0 : return true;
1016 : : }
5192 alvherre@alvh.no-ip. 1017 [ + - + + ]:CBC 675 : else if (token_is_keyword(tok, "samegroup") ||
1018 [ + - + + ]: 672 : token_is_keyword(tok, "samerole"))
1019 : : {
5852 tgl@sss.pgh.pa.us 1020 [ + + ]: 6 : if (is_member(roleid, dbname))
8192 1021 : 4 : return true;
1022 : : }
5192 alvherre@alvh.no-ip. 1023 [ + - - + ]: 669 : else if (token_is_keyword(tok, "replication"))
5617 tgl@sss.pgh.pa.us 1024 :UBC 0 : continue; /* never match this if not walsender */
1048 michael@paquier.xyz 1025 [ + + ]:CBC 669 : else if (token_has_regexp(tok))
1026 : : {
1027 [ + + ]: 4 : if (regexec_auth_token(dbname, tok, 0, NULL) == REG_OKAY)
1028 : 1 : return true;
1029 : : }
5192 alvherre@alvh.no-ip. 1030 [ + + ]: 665 : else if (token_matches(tok, dbname))
8192 tgl@sss.pgh.pa.us 1031 : 516 : return true;
1032 : : }
1033 : 597 : return false;
1034 : : }
1035 : :
1036 : : static bool
2999 tgl@sss.pgh.pa.us 1037 :UBC 0 : ipv4eq(struct sockaddr_in *a, struct sockaddr_in *b)
1038 : : {
5440 peter_e@gmx.net 1039 : 0 : return (a->sin_addr.s_addr == b->sin_addr.s_addr);
1040 : : }
1041 : :
1042 : : static bool
2999 tgl@sss.pgh.pa.us 1043 : 0 : ipv6eq(struct sockaddr_in6 *a, struct sockaddr_in6 *b)
1044 : : {
1045 : : int i;
1046 : :
5440 peter_e@gmx.net 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
5431 1058 : 0 : hostname_match(const char *pattern, const char *actual_hostname)
1059 : : {
1060 [ # # ]: 0 : if (pattern[0] == '.') /* suffix match */
1061 : : {
5263 bruce@momjian.us 1062 : 0 : size_t plen = strlen(pattern);
1063 : 0 : size_t hlen = strlen(actual_hostname);
1064 : :
5431 peter_e@gmx.net 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
5440 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 */
4175 tgl@sss.pgh.pa.us 1086 [ # # ]: 0 : if (port->remote_hostname_resolv < 0)
1087 : 0 : return false;
1088 : :
1089 : : /* Lookup remote host name if not already done */
5440 peter_e@gmx.net 1090 [ # # ]: 0 : if (!port->remote_hostname)
1091 : : {
1092 : : char remote_hostname[NI_MAXHOST];
1093 : :
4175 tgl@sss.pgh.pa.us 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;
5440 peter_e@gmx.net 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 */
5431 1110 [ # # ]: 0 : if (!hostname_match(hostname, port->remote_hostname))
5440 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 */
4175 tgl@sss.pgh.pa.us 1122 : 0 : port->remote_hostname_resolv = -2;
1123 : 0 : port->remote_hostname_errcode = ret;
1124 : 0 : return false;
1125 : : }
1126 : :
5440 peter_e@gmx.net 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,
2999 tgl@sss.pgh.pa.us 1135 : 0 : (struct sockaddr_in *) &port->raddr.addr))
1136 : : {
5440 peter_e@gmx.net 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,
2999 tgl@sss.pgh.pa.us 1144 : 0 : (struct sockaddr_in6 *) &port->raddr.addr))
1145 : : {
5440 peter_e@gmx.net 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
2999 tgl@sss.pgh.pa.us 1169 :CBC 694 : check_ip(SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
1170 : : {
3854 1171 [ + - + - ]: 1388 : if (raddr->addr.ss_family == addr->sa_family &&
1172 : 694 : pg_range_sockaddr(&raddr->addr,
1173 : : (struct sockaddr_storage *) addr,
1174 : : (struct sockaddr_storage *) mask))
1175 : 694 : return true;
3854 tgl@sss.pgh.pa.us 1176 :UBC 0 : return false;
1177 : : }
1178 : :
1179 : : /*
1180 : : * pg_foreach_ifaddr callback: does client addr match this machine interface?
1181 : : */
1182 : : static void
2999 1183 : 0 : check_network_callback(struct sockaddr *addr, struct sockaddr *netmask,
1184 : : void *cb_data)
1185 : : {
5819 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);
2999 1197 : 0 : cn->result = check_ip(cn->raddr, addr, (struct sockaddr *) &mask);
1198 : : }
1199 : : else
1200 : : {
1201 : : /* Use the netmask of the interface itself */
5819 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 : : {
1737 peter@eisentraut.org 1221 [ # # ]: 0 : ereport(LOG,
1222 : : (errmsg("error enumerating network interfaces: %m")));
5819 tgl@sss.pgh.pa.us 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 *
1262 michael@paquier.xyz 1328 :CBC 5456 : parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
1329 : : {
3144 tgl@sss.pgh.pa.us 1330 : 5456 : int line_num = tok_line->line_num;
1046 michael@paquier.xyz 1331 : 5456 : char *file_name = tok_line->file_name;
3141 tgl@sss.pgh.pa.us 1332 : 5456 : 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 : :
5192 alvherre@alvh.no-ip. 1345 : 5456 : parsedline = palloc0(sizeof(HbaLine));
1046 michael@paquier.xyz 1346 : 5456 : parsedline->sourcefile = pstrdup(file_name);
6200 magnus@hagander.net 1347 : 5456 : parsedline->linenumber = line_num;
3144 tgl@sss.pgh.pa.us 1348 : 5456 : parsedline->rawline = pstrdup(tok_line->raw_line);
1349 : :
1350 : : /* Check the record type. */
1351 [ - + ]: 5456 : Assert(tok_line->fields != NIL);
1352 : 5456 : field = list_head(tok_line->fields);
5192 alvherre@alvh.no-ip. 1353 : 5456 : tokens = lfirst(field);
1354 [ - + ]: 5456 : if (tokens->length > 1)
1355 : : {
3141 tgl@sss.pgh.pa.us 1356 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 1363 : 0 : return NULL;
1364 : : }
5192 alvherre@alvh.no-ip. 1365 :CBC 5456 : token = linitial(tokens);
1366 [ + + ]: 5456 : if (strcmp(token->string, "local") == 0)
1367 : : {
6200 magnus@hagander.net 1368 : 1818 : parsedline->conntype = ctLocal;
1369 : : }
5192 alvherre@alvh.no-ip. 1370 [ + + ]: 3638 : else if (strcmp(token->string, "host") == 0 ||
1371 [ + + ]: 208 : strcmp(token->string, "hostssl") == 0 ||
2348 sfrost@snowman.net 1372 [ + + ]: 15 : strcmp(token->string, "hostnossl") == 0 ||
1373 [ + + ]: 11 : strcmp(token->string, "hostgssenc") == 0 ||
1374 [ + - ]: 5 : strcmp(token->string, "hostnogssenc") == 0)
1375 : : {
1376 : :
5192 alvherre@alvh.no-ip. 1377 [ + + ]: 3638 : if (token->string[4] == 's') /* "hostssl" */
1378 : : {
3169 tgl@sss.pgh.pa.us 1379 : 193 : parsedline->conntype = ctHostSSL;
1380 : : /* Log a warning if SSL support is not active */
1381 : : #ifdef USE_SSL
1382 [ + + ]: 193 : if (!EnableSSL)
1383 : : {
3141 1384 [ + - ]: 2 : 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 : 2 : *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 : : }
2348 sfrost@snowman.net 1401 [ + + ]: 3445 : else if (token->string[4] == 'g') /* "hostgssenc" */
1402 : : {
1403 : 6 : parsedline->conntype = ctHostGSS;
1404 : : #ifndef ENABLE_GSS
1405 : : 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 : : *err_msg = "hostgssenc record cannot match because GSSAPI is not supported by this build";
1411 : : #endif
1412 : : }
1413 [ + + + + ]: 3439 : else if (token->string[4] == 'n' && token->string[6] == 's')
1414 : 4 : parsedline->conntype = ctHostNoSSL;
1415 [ + + + - ]: 3435 : else if (token->string[4] == 'n' && token->string[6] == 'g')
1416 : 5 : parsedline->conntype = ctHostNoGSS;
1417 : : else
1418 : : {
1419 : : /* "host" */
6200 magnus@hagander.net 1420 : 3430 : parsedline->conntype = ctHost;
1421 : : }
1422 : : } /* record type */
1423 : : else
1424 : : {
3141 tgl@sss.pgh.pa.us 1425 [ # # ]:UBC 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);
5192 alvherre@alvh.no-ip. 1432 : 0 : return NULL;
1433 : : }
1434 : :
1435 : : /* Get the databases. */
2245 tgl@sss.pgh.pa.us 1436 :CBC 5456 : field = lnext(tok_line->fields, field);
5192 alvherre@alvh.no-ip. 1437 [ - + ]: 5456 : if (!field)
1438 : : {
3141 tgl@sss.pgh.pa.us 1439 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 1445 : 0 : return NULL;
1446 : : }
5192 alvherre@alvh.no-ip. 1447 :CBC 5456 : parsedline->databases = NIL;
1448 : 5456 : tokens = lfirst(field);
1449 [ + - + + : 10918 : foreach(tokencell, tokens)
+ + ]
1450 : : {
1048 michael@paquier.xyz 1451 : 5462 : AuthToken *tok = copy_auth_token(lfirst(tokencell));
1452 : :
1453 : : /* Compile a regexp for the database token, if necessary */
1046 1454 [ - + ]: 5462 : if (regcomp_auth_token(tok, file_name, line_num, err_msg, elevel))
1048 michael@paquier.xyz 1455 :UBC 0 : return NULL;
1456 : :
1048 michael@paquier.xyz 1457 :CBC 5462 : parsedline->databases = lappend(parsedline->databases, tok);
1458 : : }
1459 : :
1460 : : /* Get the roles. */
2245 tgl@sss.pgh.pa.us 1461 : 5456 : field = lnext(tok_line->fields, field);
5192 alvherre@alvh.no-ip. 1462 [ - + ]: 5456 : if (!field)
1463 : : {
3141 tgl@sss.pgh.pa.us 1464 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 1470 : 0 : return NULL;
1471 : : }
5192 alvherre@alvh.no-ip. 1472 :CBC 5456 : parsedline->roles = NIL;
1473 : 5456 : tokens = lfirst(field);
1474 [ + - + + : 10916 : foreach(tokencell, tokens)
+ + ]
1475 : : {
1048 michael@paquier.xyz 1476 : 5460 : AuthToken *tok = copy_auth_token(lfirst(tokencell));
1477 : :
1478 : : /* Compile a regexp from the role token, if necessary */
1046 1479 [ - + ]: 5460 : if (regcomp_auth_token(tok, file_name, line_num, err_msg, elevel))
1048 michael@paquier.xyz 1480 :UBC 0 : return NULL;
1481 : :
1048 michael@paquier.xyz 1482 :CBC 5460 : parsedline->roles = lappend(parsedline->roles, tok);
1483 : : }
1484 : :
6200 magnus@hagander.net 1485 [ + + ]: 5456 : if (parsedline->conntype != ctLocal)
1486 : : {
1487 : : /* Read the IP address field. (with or without CIDR netmask) */
2245 tgl@sss.pgh.pa.us 1488 : 3638 : field = lnext(tok_line->fields, field);
5192 alvherre@alvh.no-ip. 1489 [ - + ]: 3638 : if (!field)
1490 : : {
3141 tgl@sss.pgh.pa.us 1491 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 1497 : 0 : return NULL;
1498 : : }
5192 alvherre@alvh.no-ip. 1499 :CBC 3638 : tokens = lfirst(field);
1500 [ - + ]: 3638 : if (tokens->length > 1)
1501 : : {
3141 tgl@sss.pgh.pa.us 1502 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 1509 : 0 : return NULL;
1510 : : }
5192 alvherre@alvh.no-ip. 1511 :CBC 3638 : token = linitial(tokens);
1512 : :
1513 [ + - - + ]: 3638 : if (token_is_keyword(token, "all"))
1514 : : {
5437 peter_e@gmx.net 1515 :UBC 0 : parsedline->ip_cmp_method = ipCmpAll;
1516 : : }
5192 alvherre@alvh.no-ip. 1517 [ + - - + ]:CBC 3638 : else if (token_is_keyword(token, "samehost"))
1518 : : {
1519 : : /* Any IP on this host is allowed to connect */
5819 tgl@sss.pgh.pa.us 1520 :UBC 0 : parsedline->ip_cmp_method = ipCmpSameHost;
1521 : : }
5192 alvherre@alvh.no-ip. 1522 [ + - - + ]:CBC 3638 : else if (token_is_keyword(token, "samenet"))
1523 : : {
1524 : : /* Any IP on the host's subnets is allowed to connect */
5819 tgl@sss.pgh.pa.us 1525 :UBC 0 : parsedline->ip_cmp_method = ipCmpSameNet;
1526 : : }
1527 : : else
1528 : : {
1529 : : /* IP and netmask are specified */
5819 tgl@sss.pgh.pa.us 1530 :CBC 3638 : parsedline->ip_cmp_method = ipCmpMask;
1531 : :
1532 : : /* need a modifiable copy of token */
5192 alvherre@alvh.no-ip. 1533 : 3638 : str = pstrdup(token->string);
1534 : :
1535 : : /* Check if it has a CIDR suffix and if so isolate it */
1536 : 3638 : cidr_slash = strchr(str, '/');
5819 tgl@sss.pgh.pa.us 1537 [ + - ]: 3638 : if (cidr_slash)
1538 : 3638 : *cidr_slash = '\0';
1539 : :
1540 : : /* Get the IP address either way */
1541 : 3638 : hints.ai_flags = AI_NUMERICHOST;
4161 1542 : 3638 : hints.ai_family = AF_UNSPEC;
5819 1543 : 3638 : hints.ai_socktype = 0;
1544 : 3638 : hints.ai_protocol = 0;
1545 : 3638 : hints.ai_addrlen = 0;
1546 : 3638 : hints.ai_canonname = NULL;
1547 : 3638 : hints.ai_addr = NULL;
1548 : 3638 : hints.ai_next = NULL;
1549 : :
5192 alvherre@alvh.no-ip. 1550 : 3638 : ret = pg_getaddrinfo_all(str, NULL, &hints, &gai_result);
5440 peter_e@gmx.net 1551 [ + - + - ]: 3638 : if (ret == 0 && gai_result)
1552 : : {
1553 : 3638 : memcpy(&parsedline->addr, gai_result->ai_addr,
1554 : 3638 : gai_result->ai_addrlen);
1769 tgl@sss.pgh.pa.us 1555 : 3638 : parsedline->addrlen = gai_result->ai_addrlen;
1556 : : }
5440 peter_e@gmx.net 1557 [ # # ]:UBC 0 : else if (ret == EAI_NONAME)
5192 alvherre@alvh.no-ip. 1558 : 0 : parsedline->hostname = str;
1559 : : else
1560 : : {
3141 tgl@sss.pgh.pa.us 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));
8037 1569 [ # # ]: 0 : if (gai_result)
7264 1570 : 0 : pg_freeaddrinfo_all(hints.ai_family, gai_result);
5192 alvherre@alvh.no-ip. 1571 : 0 : return NULL;
1572 : : }
1573 : :
7264 tgl@sss.pgh.pa.us 1574 :CBC 3638 : pg_freeaddrinfo_all(hints.ai_family, gai_result);
1575 : :
1576 : : /* Get the netmask */
5819 1577 [ + - ]: 3638 : if (cidr_slash)
1578 : : {
5440 peter_e@gmx.net 1579 [ - + ]: 3638 : if (parsedline->hostname)
1580 : : {
3141 tgl@sss.pgh.pa.us 1581 [ # # ]:UBC 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);
5192 alvherre@alvh.no-ip. 1589 : 0 : return NULL;
1590 : : }
1591 : :
5819 tgl@sss.pgh.pa.us 1592 [ - + ]:CBC 3638 : if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
1593 : 3638 : parsedline->addr.ss_family) < 0)
1594 : : {
3141 tgl@sss.pgh.pa.us 1595 [ # # ]:UBC 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);
5192 alvherre@alvh.no-ip. 1603 : 0 : return NULL;
1604 : : }
1769 tgl@sss.pgh.pa.us 1605 :CBC 3638 : parsedline->masklen = parsedline->addrlen;
5192 alvherre@alvh.no-ip. 1606 : 3638 : pfree(str);
1607 : : }
5440 peter_e@gmx.net 1608 [ # # ]:UBC 0 : else if (!parsedline->hostname)
1609 : : {
1610 : : /* Read the mask field. */
5192 alvherre@alvh.no-ip. 1611 : 0 : pfree(str);
2245 tgl@sss.pgh.pa.us 1612 : 0 : field = lnext(tok_line->fields, field);
5192 alvherre@alvh.no-ip. 1613 [ # # ]: 0 : if (!field)
1614 : : {
3141 tgl@sss.pgh.pa.us 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";
5192 alvherre@alvh.no-ip. 1622 : 0 : return NULL;
1623 : : }
1624 : 0 : tokens = lfirst(field);
1625 [ # # ]: 0 : if (tokens->length > 1)
1626 : : {
3141 tgl@sss.pgh.pa.us 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";
5192 alvherre@alvh.no-ip. 1633 : 0 : return NULL;
1634 : : }
1635 : 0 : token = linitial(tokens);
1636 : :
1637 : 0 : ret = pg_getaddrinfo_all(token->string, NULL,
1638 : : &hints, &gai_result);
5819 tgl@sss.pgh.pa.us 1639 [ # # # # ]: 0 : if (ret || !gai_result)
1640 : : {
3141 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));
5819 1649 [ # # ]: 0 : if (gai_result)
1650 : 0 : pg_freeaddrinfo_all(hints.ai_family, gai_result);
5192 alvherre@alvh.no-ip. 1651 : 0 : return NULL;
1652 : : }
1653 : :
5819 tgl@sss.pgh.pa.us 1654 : 0 : memcpy(&parsedline->mask, gai_result->ai_addr,
1655 : 0 : gai_result->ai_addrlen);
1769 1656 : 0 : parsedline->masklen = gai_result->ai_addrlen;
5819 1657 : 0 : pg_freeaddrinfo_all(hints.ai_family, gai_result);
1658 : :
1659 [ # # ]: 0 : if (parsedline->addr.ss_family != parsedline->mask.ss_family)
1660 : : {
3141 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";
5192 alvherre@alvh.no-ip. 1667 : 0 : return NULL;
1668 : : }
1669 : : }
1670 : : }
1671 : : } /* != ctLocal */
1672 : :
1673 : : /* Get the authentication method */
2245 tgl@sss.pgh.pa.us 1674 :CBC 5456 : field = lnext(tok_line->fields, field);
5192 alvherre@alvh.no-ip. 1675 [ - + ]: 5456 : if (!field)
1676 : : {
3141 tgl@sss.pgh.pa.us 1677 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 1683 : 0 : return NULL;
1684 : : }
5192 alvherre@alvh.no-ip. 1685 :CBC 5456 : tokens = lfirst(field);
1686 [ - + ]: 5456 : if (tokens->length > 1)
1687 : : {
3141 tgl@sss.pgh.pa.us 1688 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 1695 : 0 : return NULL;
1696 : : }
5192 alvherre@alvh.no-ip. 1697 :CBC 5456 : token = linitial(tokens);
1698 : :
6200 magnus@hagander.net 1699 : 5456 : unsupauth = NULL;
5192 alvherre@alvh.no-ip. 1700 [ + + ]: 5456 : if (strcmp(token->string, "trust") == 0)
6200 magnus@hagander.net 1701 : 5236 : parsedline->auth_method = uaTrust;
5192 alvherre@alvh.no-ip. 1702 [ - + ]: 220 : else if (strcmp(token->string, "ident") == 0)
6200 magnus@hagander.net 1703 :UBC 0 : parsedline->auth_method = uaIdent;
5192 alvherre@alvh.no-ip. 1704 [ + + ]:CBC 220 : else if (strcmp(token->string, "peer") == 0)
5285 magnus@hagander.net 1705 : 19 : parsedline->auth_method = uaPeer;
5192 alvherre@alvh.no-ip. 1706 [ + + ]: 201 : else if (strcmp(token->string, "password") == 0)
6200 magnus@hagander.net 1707 : 10 : parsedline->auth_method = uaPassword;
5192 alvherre@alvh.no-ip. 1708 [ + + ]: 191 : else if (strcmp(token->string, "gss") == 0)
1709 : : #ifdef ENABLE_GSS
6200 magnus@hagander.net 1710 : 7 : parsedline->auth_method = uaGSS;
1711 : : #else
1712 : : unsupauth = "gss";
1713 : : #endif
5192 alvherre@alvh.no-ip. 1714 [ - + ]: 184 : else if (strcmp(token->string, "sspi") == 0)
1715 : : #ifdef ENABLE_SSPI
1716 : : parsedline->auth_method = uaSSPI;
1717 : : #else
6200 magnus@hagander.net 1718 :UBC 0 : unsupauth = "sspi";
1719 : : #endif
5192 alvherre@alvh.no-ip. 1720 [ + + ]:CBC 184 : else if (strcmp(token->string, "reject") == 0)
6200 magnus@hagander.net 1721 : 18 : parsedline->auth_method = uaReject;
5192 alvherre@alvh.no-ip. 1722 [ + + ]: 166 : else if (strcmp(token->string, "md5") == 0)
6200 magnus@hagander.net 1723 : 23 : parsedline->auth_method = uaMD5;
3063 heikki.linnakangas@i 1724 [ + + ]: 143 : else if (strcmp(token->string, "scram-sha-256") == 0)
3088 1725 : 31 : parsedline->auth_method = uaSCRAM;
5192 alvherre@alvh.no-ip. 1726 [ - + ]: 112 : else if (strcmp(token->string, "pam") == 0)
1727 : : #ifdef USE_PAM
6200 magnus@hagander.net 1728 :UBC 0 : parsedline->auth_method = uaPAM;
1729 : : #else
1730 : : unsupauth = "pam";
1731 : : #endif
3438 tgl@sss.pgh.pa.us 1732 [ - + ]:CBC 112 : else if (strcmp(token->string, "bsd") == 0)
1733 : : #ifdef USE_BSD_AUTH
1734 : : parsedline->auth_method = uaBSD;
1735 : : #else
3438 tgl@sss.pgh.pa.us 1736 :UBC 0 : unsupauth = "bsd";
1737 : : #endif
5192 alvherre@alvh.no-ip. 1738 [ - + ]:CBC 112 : else if (strcmp(token->string, "ldap") == 0)
1739 : : #ifdef USE_LDAP
6200 magnus@hagander.net 1740 :UBC 0 : parsedline->auth_method = uaLDAP;
1741 : : #else
1742 : : unsupauth = "ldap";
1743 : : #endif
5192 alvherre@alvh.no-ip. 1744 [ + + ]:CBC 112 : else if (strcmp(token->string, "cert") == 0)
1745 : : #ifdef USE_SSL
6134 magnus@hagander.net 1746 : 84 : parsedline->auth_method = uaCert;
1747 : : #else
1748 : : unsupauth = "cert";
1749 : : #endif
5192 alvherre@alvh.no-ip. 1750 [ - + ]: 28 : else if (strcmp(token->string, "radius") == 0)
5701 magnus@hagander.net 1751 :UBC 0 : parsedline->auth_method = uaRADIUS;
198 dgustafsson@postgres 1752 [ + - ]:CBC 28 : else if (strcmp(token->string, "oauth") == 0)
1753 : 28 : parsedline->auth_method = uaOAuth;
1754 : : else
1755 : : {
3141 tgl@sss.pgh.pa.us 1756 [ # # ]:UBC 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);
5192 alvherre@alvh.no-ip. 1764 : 0 : return NULL;
1765 : : }
1766 : :
6200 magnus@hagander.net 1767 [ - + ]:CBC 5456 : if (unsupauth)
1768 : : {
3141 tgl@sss.pgh.pa.us 1769 [ # # ]:UBC 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);
5192 alvherre@alvh.no-ip. 1777 : 0 : return NULL;
1778 : : }
1779 : :
1780 : : /*
1781 : : * XXX: When using ident on local connections, change it to peer, for
1782 : : * backwards compatibility.
1783 : : */
5285 magnus@hagander.net 1784 [ + + ]:CBC 5456 : if (parsedline->conntype == ctLocal &&
1785 [ - + ]: 1818 : parsedline->auth_method == uaIdent)
5285 magnus@hagander.net 1786 :UBC 0 : parsedline->auth_method = uaPeer;
1787 : :
1788 : : /* Invalid authentication combinations */
5661 magnus@hagander.net 1789 [ + + ]:CBC 5456 : if (parsedline->conntype == ctLocal &&
1790 [ - + ]: 1818 : parsedline->auth_method == uaGSS)
1791 : : {
3141 tgl@sss.pgh.pa.us 1792 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 1798 : 0 : return NULL;
1799 : : }
1800 : :
5285 magnus@hagander.net 1801 [ + + ]:CBC 5456 : if (parsedline->conntype != ctLocal &&
1802 [ - + ]: 3638 : parsedline->auth_method == uaPeer)
1803 : : {
3141 tgl@sss.pgh.pa.us 1804 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 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 : :
6134 magnus@hagander.net 1819 [ + + ]:CBC 5456 : if (parsedline->conntype != ctHostSSL &&
1820 [ - + ]: 5263 : parsedline->auth_method == uaCert)
1821 : : {
3141 tgl@sss.pgh.pa.us 1822 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 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 : : */
3592 sfrost@snowman.net 1840 [ + + ]:CBC 5456 : if (parsedline->auth_method == uaGSS ||
1841 [ - + ]: 5449 : parsedline->auth_method == uaSSPI)
1842 : 7 : 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 : : */
3438 magnus@hagander.net 1849 [ - + ]: 5456 : if (parsedline->auth_method == uaSSPI)
1850 : : {
3438 magnus@hagander.net 1851 :UBC 0 : parsedline->compat_realm = true;
1852 : 0 : parsedline->upn_username = false;
1853 : : }
1854 : :
1855 : : /* Parse remaining arguments */
2245 tgl@sss.pgh.pa.us 1856 [ + + ]:CBC 5742 : while ((field = lnext(tok_line->fields, field)) != NULL)
1857 : : {
5192 alvherre@alvh.no-ip. 1858 : 286 : tokens = lfirst(field);
1859 [ + - + + : 572 : foreach(tokencell, tokens)
+ + ]
1860 : : {
1861 : : char *val;
1862 : :
1863 : 286 : token = lfirst(tokencell);
1864 : :
1865 : 286 : str = pstrdup(token->string);
1866 : 286 : val = strchr(str, '=');
1867 [ - + ]: 286 : if (val == NULL)
1868 : : {
1869 : : /*
1870 : : * Got something that's not a name=value pair.
1871 : : */
3141 tgl@sss.pgh.pa.us 1872 [ # # ]:UBC 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);
5192 alvherre@alvh.no-ip. 1879 : 0 : return NULL;
1880 : : }
1881 : :
4836 bruce@momjian.us 1882 :CBC 286 : *val++ = '\0'; /* str now holds "name", val holds "value" */
3141 tgl@sss.pgh.pa.us 1883 [ - + ]: 286 : if (!parse_hba_auth_opt(str, val, parsedline, elevel, err_msg))
1884 : : /* parse_hba_auth_opt already logged the error message */
5192 alvherre@alvh.no-ip. 1885 :UBC 0 : return NULL;
5192 alvherre@alvh.no-ip. 1886 :CBC 286 : pfree(str);
1887 : : }
1888 : : }
1889 : :
1890 : : /*
1891 : : * Check if the selected authentication method has any mandatory arguments
1892 : : * that are not set.
1893 : : */
6162 magnus@hagander.net 1894 [ - + ]: 5456 : 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 : : */
5747 magnus@hagander.net 1908 [ # # # # ]:UBC 0 : if (parsedline->ldapprefix || parsedline->ldapsuffix)
1909 : : {
1910 [ # # ]: 0 : if (parsedline->ldapbasedn ||
1911 [ # # ]: 0 : parsedline->ldapbinddn ||
1912 [ # # ]: 0 : parsedline->ldapbindpasswd ||
2916 peter_e@gmx.net 1913 [ # # ]: 0 : parsedline->ldapsearchattribute ||
1914 [ # # ]: 0 : parsedline->ldapsearchfilter)
1915 : : {
3141 tgl@sss.pgh.pa.us 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)));
410 peter@eisentraut.org 1921 : 0 : *err_msg = "cannot mix options for simple bind and search+bind modes";
5192 alvherre@alvh.no-ip. 1922 : 0 : return NULL;
1923 : : }
1924 : : }
5747 magnus@hagander.net 1925 [ # # ]: 0 : else if (!parsedline->ldapbasedn)
1926 : : {
3141 tgl@sss.pgh.pa.us 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";
5192 alvherre@alvh.no-ip. 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 : : */
2916 peter_e@gmx.net 1941 [ # # # # ]: 0 : 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 : :
5701 magnus@hagander.net 1953 [ - + ]:CBC 5456 : if (parsedline->auth_method == uaRADIUS)
1954 : : {
3090 magnus@hagander.net 1955 [ # # # # ]:UBC 0 : MANDATORY_AUTH_ARG(parsedline->radiusservers, "radiusservers", "radius");
1956 [ # # # # ]: 0 : MANDATORY_AUTH_ARG(parsedline->radiussecrets, "radiussecrets", "radius");
1957 : :
1116 tgl@sss.pgh.pa.us 1958 [ # # ]: 0 : if (parsedline->radiusservers == NIL)
1959 : : {
1559 peter@eisentraut.org 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";
3090 magnus@hagander.net 1966 : 0 : return NULL;
1967 : : }
1968 : :
1116 tgl@sss.pgh.pa.us 1969 [ # # ]: 0 : if (parsedline->radiussecrets == NIL)
1970 : : {
1559 peter@eisentraut.org 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";
3090 magnus@hagander.net 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 : : */
1559 peter@eisentraut.org 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));
3090 magnus@hagander.net 1998 : 0 : return NULL;
1999 : : }
1559 peter@eisentraut.org 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));
3090 magnus@hagander.net 2014 : 0 : return NULL;
2015 : : }
1559 peter@eisentraut.org 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));
3090 magnus@hagander.net 2030 : 0 : return NULL;
2031 : : }
2032 : : }
2033 : :
2034 : : /*
2035 : : * Enforce any parameters implied by other settings.
2036 : : */
6134 magnus@hagander.net 2037 [ + + ]:CBC 5456 : 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 : : */
1319 2043 : 84 : parsedline->clientcert = clientCertFull;
2044 : : }
2045 : :
2046 : : /*
2047 : : * Enforce proper configuration of OAuth authentication.
2048 : : */
198 dgustafsson@postgres 2049 [ + + ]: 5456 : if (parsedline->auth_method == uaOAuth)
2050 : : {
2051 [ - + - - ]: 28 : MANDATORY_AUTH_ARG(parsedline->oauth_scope, "scope", "oauth");
2052 [ - + - - ]: 28 : MANDATORY_AUTH_ARG(parsedline->oauth_issuer, "issuer", "oauth");
2053 : :
2054 : : /* Ensure a validator library is set and permitted by the config. */
2055 [ + + ]: 28 : if (!check_oauth_validator(parsedline, elevel, err_msg))
2056 : 3 : 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 [ + + - + ]: 25 : if (parsedline->oauth_skip_usermap && parsedline->usermap != NULL)
2063 : : {
198 dgustafsson@postgres 2064 [ # # ]:UBC 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 : :
5192 alvherre@alvh.no-ip. 2076 :CBC 5453 : 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
3141 tgl@sss.pgh.pa.us 2087 : 286 : parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
2088 : : int elevel, char **err_msg)
2089 : : {
2090 : 286 : int line_num = hbaline->linenumber;
1046 michael@paquier.xyz 2091 : 286 : char *file_name = hbaline->sourcefile;
2092 : :
2093 : : #ifdef USE_LDAP
4660 peter_e@gmx.net 2094 : 286 : hbaline->ldapscope = LDAP_SCOPE_SUBTREE;
2095 : : #endif
2096 : :
5192 alvherre@alvh.no-ip. 2097 [ + + ]: 286 : if (strcmp(name, "map") == 0)
2098 : : {
2099 [ + - ]: 94 : if (hbaline->auth_method != uaIdent &&
2100 [ + + ]: 94 : hbaline->auth_method != uaPeer &&
2101 [ + + ]: 76 : hbaline->auth_method != uaGSS &&
2102 [ + - ]: 71 : hbaline->auth_method != uaSSPI &&
198 dgustafsson@postgres 2103 [ + + ]: 71 : hbaline->auth_method != uaCert &&
2104 [ - + ]: 8 : hbaline->auth_method != uaOAuth)
198 dgustafsson@postgres 2105 [ # # ]:UBC 0 : INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, cert, and oauth"));
5192 alvherre@alvh.no-ip. 2106 :CBC 94 : hbaline->usermap = pstrdup(val);
2107 : : }
2108 [ + + ]: 192 : else if (strcmp(name, "clientcert") == 0)
2109 : : {
2110 [ - + ]: 63 : if (hbaline->conntype != ctHostSSL)
2111 : : {
3141 tgl@sss.pgh.pa.us 2112 [ # # ]:UBC 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";
5192 alvherre@alvh.no-ip. 2118 : 0 : return false;
2119 : : }
2120 : :
1797 bruce@momjian.us 2121 [ + + ]:CBC 63 : if (strcmp(val, "verify-full") == 0)
2122 : : {
2373 magnus@hagander.net 2123 : 42 : hbaline->clientcert = clientCertFull;
2124 : : }
1797 bruce@momjian.us 2125 [ + - ]: 21 : else if (strcmp(val, "verify-ca") == 0)
2126 : : {
5192 alvherre@alvh.no-ip. 2127 [ - + ]: 21 : if (hbaline->auth_method == uaCert)
2128 : : {
3141 tgl@sss.pgh.pa.us 2129 [ # # ]:UBC 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)));
1797 bruce@momjian.us 2134 : 0 : *err_msg = "clientcert can only be set to \"verify-full\" when using \"cert\" authentication";
5192 alvherre@alvh.no-ip. 2135 : 0 : return false;
2136 : : }
2137 : :
1797 bruce@momjian.us 2138 :CBC 21 : hbaline->clientcert = clientCertCA;
2139 : : }
2140 : : else
2141 : : {
2373 magnus@hagander.net 2142 [ # # ]:UBC 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 : : }
1622 andrew@dunslane.net 2150 [ + + ]:CBC 129 : else if (strcmp(name, "clientname") == 0)
2151 : : {
2152 [ - + ]: 63 : if (hbaline->conntype != ctHostSSL)
2153 : : {
1622 andrew@dunslane.net 2154 [ # # ]:UBC 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 : :
1622 andrew@dunslane.net 2163 [ + + ]:CBC 63 : if (strcmp(val, "CN") == 0)
2164 : : {
2165 : 21 : hbaline->clientcertname = clientCertCN;
2166 : : }
2167 [ + - ]: 42 : else if (strcmp(val, "DN") == 0)
2168 : : {
2169 : 42 : hbaline->clientcertname = clientCertDN;
2170 : : }
2171 : : else
2172 : : {
1622 andrew@dunslane.net 2173 [ # # ]:UBC 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 : : }
5192 alvherre@alvh.no-ip. 2181 [ - + ]:CBC 66 : else if (strcmp(name, "pamservice") == 0)
2182 : : {
5192 alvherre@alvh.no-ip. 2183 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
2184 : 0 : hbaline->pamservice = pstrdup(val);
2185 : : }
3438 peter_e@gmx.net 2186 [ - + ]:CBC 66 : else if (strcmp(name, "pam_use_hostname") == 0)
2187 : : {
3438 peter_e@gmx.net 2188 [ # # # # ]:UBC 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 : : }
4660 peter_e@gmx.net 2194 [ - + ]:CBC 66 : else if (strcmp(name, "ldapurl") == 0)
2195 : : {
2196 : : #ifdef LDAP_API_FEATURE_X_OPENLDAP
2197 : : LDAPURLDesc *urldata;
2198 : : int rc;
2199 : : #endif
2200 : :
4660 peter_e@gmx.net 2201 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapurl", "ldap");
2202 : : #ifdef LDAP_API_FEATURE_X_OPENLDAP
2203 : 0 : rc = ldap_url_parse(val, &urldata);
2204 [ # # ]: 0 : if (rc != LDAP_SUCCESS)
2205 : : {
3141 tgl@sss.pgh.pa.us 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));
4660 peter_e@gmx.net 2211 : 0 : return false;
2212 : : }
2213 : :
2803 2214 [ # # ]: 0 : if (strcmp(urldata->lud_scheme, "ldap") != 0 &&
2215 [ # # ]: 0 : strcmp(urldata->lud_scheme, "ldaps") != 0)
2216 : : {
3141 tgl@sss.pgh.pa.us 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);
4660 peter_e@gmx.net 2222 : 0 : ldap_free_urldesc(urldata);
2223 : 0 : return false;
2224 : : }
2225 : :
2803 2226 [ # # ]: 0 : if (urldata->lud_scheme)
2227 : 0 : hbaline->ldapscheme = pstrdup(urldata->lud_scheme);
2857 2228 [ # # ]: 0 : if (urldata->lud_host)
2229 : 0 : hbaline->ldapserver = pstrdup(urldata->lud_host);
4660 2230 : 0 : hbaline->ldapport = urldata->lud_port;
2857 2231 [ # # ]: 0 : if (urldata->lud_dn)
2232 : 0 : hbaline->ldapbasedn = pstrdup(urldata->lud_dn);
2233 : :
4660 2234 [ # # ]: 0 : if (urldata->lud_attrs)
2999 tgl@sss.pgh.pa.us 2235 : 0 : hbaline->ldapsearchattribute = pstrdup(urldata->lud_attrs[0]); /* only use first one */
4660 peter_e@gmx.net 2236 : 0 : hbaline->ldapscope = urldata->lud_scope;
2237 [ # # ]: 0 : if (urldata->lud_filter)
2916 2238 : 0 : hbaline->ldapsearchfilter = pstrdup(urldata->lud_filter);
4660 2239 : 0 : 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 : : }
5192 alvherre@alvh.no-ip. 2247 [ - + ]:CBC 66 : else if (strcmp(name, "ldaptls") == 0)
2248 : : {
5192 alvherre@alvh.no-ip. 2249 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");
2250 [ # # ]: 0 : if (strcmp(val, "1") == 0)
2251 : 0 : hbaline->ldaptls = true;
2252 : : else
2253 : 0 : hbaline->ldaptls = false;
2254 : : }
2803 peter_e@gmx.net 2255 [ - + ]:CBC 66 : else if (strcmp(name, "ldapscheme") == 0)
2256 : : {
2803 peter_e@gmx.net 2257 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapscheme", "ldap");
2258 [ # # # # ]: 0 : 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 : 0 : hbaline->ldapscheme = pstrdup(val);
2265 : : }
5192 alvherre@alvh.no-ip. 2266 [ - + ]:CBC 66 : else if (strcmp(name, "ldapserver") == 0)
2267 : : {
5192 alvherre@alvh.no-ip. 2268 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
2269 : 0 : hbaline->ldapserver = pstrdup(val);
2270 : : }
5192 alvherre@alvh.no-ip. 2271 [ - + ]:CBC 66 : else if (strcmp(name, "ldapport") == 0)
2272 : : {
5192 alvherre@alvh.no-ip. 2273 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap");
2274 : 0 : hbaline->ldapport = atoi(val);
2275 [ # # ]: 0 : if (hbaline->ldapport == 0)
2276 : : {
3141 tgl@sss.pgh.pa.us 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);
5192 alvherre@alvh.no-ip. 2283 : 0 : return false;
2284 : : }
2285 : : }
5192 alvherre@alvh.no-ip. 2286 [ - + ]:CBC 66 : else if (strcmp(name, "ldapbinddn") == 0)
2287 : : {
5192 alvherre@alvh.no-ip. 2288 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap");
2289 : 0 : hbaline->ldapbinddn = pstrdup(val);
2290 : : }
5192 alvherre@alvh.no-ip. 2291 [ - + ]:CBC 66 : else if (strcmp(name, "ldapbindpasswd") == 0)
2292 : : {
5192 alvherre@alvh.no-ip. 2293 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap");
2294 : 0 : hbaline->ldapbindpasswd = pstrdup(val);
2295 : : }
5192 alvherre@alvh.no-ip. 2296 [ - + ]:CBC 66 : else if (strcmp(name, "ldapsearchattribute") == 0)
2297 : : {
5192 alvherre@alvh.no-ip. 2298 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
2299 : 0 : hbaline->ldapsearchattribute = pstrdup(val);
2300 : : }
2916 peter_e@gmx.net 2301 [ - + ]:CBC 66 : else if (strcmp(name, "ldapsearchfilter") == 0)
2302 : : {
2916 peter_e@gmx.net 2303 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchfilter", "ldap");
2304 : 0 : hbaline->ldapsearchfilter = pstrdup(val);
2305 : : }
5192 alvherre@alvh.no-ip. 2306 [ - + ]:CBC 66 : else if (strcmp(name, "ldapbasedn") == 0)
2307 : : {
5192 alvherre@alvh.no-ip. 2308 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
2309 : 0 : hbaline->ldapbasedn = pstrdup(val);
2310 : : }
5192 alvherre@alvh.no-ip. 2311 [ - + ]:CBC 66 : else if (strcmp(name, "ldapprefix") == 0)
2312 : : {
5192 alvherre@alvh.no-ip. 2313 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
2314 : 0 : hbaline->ldapprefix = pstrdup(val);
2315 : : }
5192 alvherre@alvh.no-ip. 2316 [ - + ]:CBC 66 : else if (strcmp(name, "ldapsuffix") == 0)
2317 : : {
5192 alvherre@alvh.no-ip. 2318 [ # # # # ]:UBC 0 : REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
2319 : 0 : hbaline->ldapsuffix = pstrdup(val);
2320 : : }
5192 alvherre@alvh.no-ip. 2321 [ + + ]:CBC 66 : else if (strcmp(name, "krb_realm") == 0)
2322 : : {
4252 magnus@hagander.net 2323 [ - + ]: 1 : if (hbaline->auth_method != uaGSS &&
5192 alvherre@alvh.no-ip. 2324 [ # # ]:UBC 0 : hbaline->auth_method != uaSSPI)
4252 magnus@hagander.net 2325 [ # # ]: 0 : INVALID_AUTH_OPTION("krb_realm", gettext_noop("gssapi and sspi"));
5192 alvherre@alvh.no-ip. 2326 :CBC 1 : hbaline->krb_realm = pstrdup(val);
2327 : : }
2328 [ + + ]: 65 : else if (strcmp(name, "include_realm") == 0)
2329 : : {
4252 magnus@hagander.net 2330 [ - + ]: 2 : if (hbaline->auth_method != uaGSS &&
5192 alvherre@alvh.no-ip. 2331 [ # # ]:UBC 0 : hbaline->auth_method != uaSSPI)
4252 magnus@hagander.net 2332 [ # # ]: 0 : INVALID_AUTH_OPTION("include_realm", gettext_noop("gssapi and sspi"));
5192 alvherre@alvh.no-ip. 2333 [ - + ]:CBC 2 : if (strcmp(val, "1") == 0)
5192 alvherre@alvh.no-ip. 2334 :UBC 0 : hbaline->include_realm = true;
2335 : : else
5192 alvherre@alvh.no-ip. 2336 :CBC 2 : hbaline->include_realm = false;
2337 : : }
3438 magnus@hagander.net 2338 [ - + ]: 63 : else if (strcmp(name, "compat_realm") == 0)
2339 : : {
3438 magnus@hagander.net 2340 [ # # ]:UBC 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 : : }
3438 magnus@hagander.net 2347 [ - + ]:CBC 63 : else if (strcmp(name, "upn_username") == 0)
2348 : : {
3438 magnus@hagander.net 2349 [ # # ]:UBC 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 : : }
3090 magnus@hagander.net 2356 [ - + ]:CBC 63 : 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;
3034 bruce@momjian.us 2363 :UBC 0 : char *dupval = pstrdup(val);
2364 : :
3090 magnus@hagander.net 2365 [ # # # # ]: 0 : REQUIRE_AUTH_OPTION(uaRADIUS, "radiusservers", "radius");
2366 : :
2124 tgl@sss.pgh.pa.us 2367 [ # # ]: 0 : if (!SplitGUCList(dupval, ',', &parsed_servers))
2368 : : {
2369 : : /* syntax error in list */
3141 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)));
5192 alvherre@alvh.no-ip. 2376 : 0 : return false;
2377 : : }
2378 : :
2379 : : /* For each entry in the list, translate it */
3090 magnus@hagander.net 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 : : }
3090 magnus@hagander.net 2408 [ - + ]:CBC 63 : else if (strcmp(name, "radiusports") == 0)
2409 : : {
2410 : : List *parsed_ports;
2411 : : ListCell *l;
3034 bruce@momjian.us 2412 :UBC 0 : char *dupval = pstrdup(val);
2413 : :
3090 magnus@hagander.net 2414 [ # # # # ]: 0 : REQUIRE_AUTH_OPTION(uaRADIUS, "radiusports", "radius");
2415 : :
2124 tgl@sss.pgh.pa.us 2416 [ # # ]: 0 : if (!SplitGUCList(dupval, ',', &parsed_ports))
2417 : : {
3141 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);
5192 alvherre@alvh.no-ip. 2425 : 0 : return false;
2426 : : }
2427 : :
3090 magnus@hagander.net 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 : : }
3090 magnus@hagander.net 2444 [ - + ]:CBC 63 : else if (strcmp(name, "radiussecrets") == 0)
2445 : : {
2446 : : List *parsed_secrets;
3034 bruce@momjian.us 2447 :UBC 0 : char *dupval = pstrdup(val);
2448 : :
3090 magnus@hagander.net 2449 [ # # # # ]: 0 : REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecrets", "radius");
2450 : :
2124 tgl@sss.pgh.pa.us 2451 [ # # ]: 0 : if (!SplitGUCList(dupval, ',', &parsed_secrets))
2452 : : {
2453 : : /* syntax error in list */
3090 magnus@hagander.net 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 : : }
3090 magnus@hagander.net 2466 [ - + ]:CBC 63 : else if (strcmp(name, "radiusidentifiers") == 0)
2467 : : {
2468 : : List *parsed_identifiers;
3034 bruce@momjian.us 2469 :UBC 0 : char *dupval = pstrdup(val);
2470 : :
3090 magnus@hagander.net 2471 [ # # # # ]: 0 : REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifiers", "radius");
2472 : :
2124 tgl@sss.pgh.pa.us 2473 [ # # ]: 0 : if (!SplitGUCList(dupval, ',', &parsed_identifiers))
2474 : : {
2475 : : /* syntax error in list */
3090 magnus@hagander.net 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 : : }
198 dgustafsson@postgres 2488 [ + + ]:CBC 63 : else if (strcmp(name, "issuer") == 0)
2489 : : {
2490 [ - + - - ]: 28 : REQUIRE_AUTH_OPTION(uaOAuth, "issuer", "oauth");
2491 : 28 : hbaline->oauth_issuer = pstrdup(val);
2492 : : }
2493 [ + + ]: 35 : else if (strcmp(name, "scope") == 0)
2494 : : {
2495 [ - + - - ]: 28 : REQUIRE_AUTH_OPTION(uaOAuth, "scope", "oauth");
2496 : 28 : hbaline->oauth_scope = pstrdup(val);
2497 : : }
2498 [ + + ]: 7 : else if (strcmp(name, "validator") == 0)
2499 : : {
2500 [ - + - - ]: 3 : REQUIRE_AUTH_OPTION(uaOAuth, "validator", "oauth");
2501 : 3 : hbaline->oauth_validator = pstrdup(val);
2502 : : }
2503 [ + - ]: 4 : else if (strcmp(name, "delegate_ident_mapping") == 0)
2504 : : {
2505 [ - + - - ]: 4 : REQUIRE_AUTH_OPTION(uaOAuth, "delegate_ident_mapping", "oauth");
2506 [ + - ]: 4 : if (strcmp(val, "1") == 0)
2507 : 4 : hbaline->oauth_skip_usermap = true;
2508 : : else
198 dgustafsson@postgres 2509 :UBC 0 : hbaline->oauth_skip_usermap = false;
2510 : : }
2511 : : else
2512 : : {
3141 tgl@sss.pgh.pa.us 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);
5192 alvherre@alvh.no-ip. 2521 : 0 : return false;
2522 : : }
5192 alvherre@alvh.no-ip. 2523 :CBC 286 : 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
8804 bruce@momjian.us 2531 : 11971 : 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. */
5511 rhaas@postgresql.org 2538 : 11971 : roleid = get_role_oid(port->user_name, true);
2539 : :
6200 magnus@hagander.net 2540 [ + - + + : 14274 : foreach(line, parsed_hba_lines)
+ + ]
2541 : : {
2542 : 14205 : hba = (HbaLine *) lfirst(line);
2543 : :
2544 : : /* Check connection type */
2545 [ + + ]: 14205 : if (hba->conntype == ctLocal)
2546 : : {
1299 peter@eisentraut.org 2547 [ + + ]: 12437 : if (port->raddr.addr.ss_family != AF_UNIX)
6200 magnus@hagander.net 2548 : 210 : continue;
2549 : : }
2550 : : else
2551 : : {
1299 peter@eisentraut.org 2552 [ + + ]: 1768 : if (port->raddr.addr.ss_family == AF_UNIX)
6200 magnus@hagander.net 2553 : 894 : continue;
2554 : :
2555 : : /* Check SSL state */
4044 heikki.linnakangas@i 2556 [ + + ]: 874 : if (port->ssl_in_use)
2557 : : {
2558 : : /* Connection is SSL, match both "host" and "hostssl" */
6200 magnus@hagander.net 2559 [ + + ]: 405 : if (hba->conntype == ctHostNoSSL)
2560 : 28 : continue;
2561 : : }
2562 : : else
2563 : : {
2564 : : /* Connection is not SSL, match both "host" and "hostnossl" */
2565 [ + + ]: 469 : if (hba->conntype == ctHostSSL)
2566 : 71 : continue;
2567 : : }
2568 : :
2569 : : /* Check GSSAPI state */
2570 : : #ifdef ENABLE_GSS
1713 tgl@sss.pgh.pa.us 2571 [ + + + - ]: 775 : if (port->gss && port->gss->enc &&
2572 [ + + ]: 281 : hba->conntype == ctHostNoGSS)
2348 sfrost@snowman.net 2573 : 58 : continue;
1713 tgl@sss.pgh.pa.us 2574 [ + + - + ]: 717 : else if (!(port->gss && port->gss->enc) &&
2575 [ + + ]: 494 : hba->conntype == ctHostGSS)
2348 sfrost@snowman.net 2576 : 23 : continue;
2577 : : #else
2578 : : if (hba->conntype == ctHostGSS)
2579 : : continue;
2580 : : #endif
2581 : :
2582 : : /* Check IP address */
5819 tgl@sss.pgh.pa.us 2583 [ + - - - ]: 694 : switch (hba->ip_cmp_method)
2584 : : {
2585 : 694 : case ipCmpMask:
5440 peter_e@gmx.net 2586 [ - + ]: 694 : if (hba->hostname)
2587 : : {
5440 peter_e@gmx.net 2588 [ # # ]:UBC 0 : if (!check_hostname(port,
2589 : 0 : hba->hostname))
2590 : 0 : continue;
2591 : : }
2592 : : else
2593 : : {
5440 peter_e@gmx.net 2594 [ - + ]:CBC 694 : if (!check_ip(&port->raddr,
2999 tgl@sss.pgh.pa.us 2595 : 694 : (struct sockaddr *) &hba->addr,
2596 : 694 : (struct sockaddr *) &hba->mask))
5440 peter_e@gmx.net 2597 :UBC 0 : continue;
2598 : : }
5819 tgl@sss.pgh.pa.us 2599 :CBC 694 : break;
5437 peter_e@gmx.net 2600 :UBC 0 : case ipCmpAll:
2601 : 0 : break;
5819 tgl@sss.pgh.pa.us 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 */
6200 magnus@hagander.net 2610 : 0 : continue;
2611 : : }
2612 : : } /* != ctLocal */
2613 : :
2614 : : /* Check database and role */
5852 tgl@sss.pgh.pa.us 2615 [ + + ]:CBC 12921 : if (!check_db(port->database_name, port->user_name, roleid,
2616 : : hba->databases))
6200 magnus@hagander.net 2617 : 597 : continue;
2618 : :
960 michael@paquier.xyz 2619 [ + + ]: 12324 : if (!check_role(port->user_name, roleid, hba->roles, false))
6200 magnus@hagander.net 2620 : 422 : continue;
2621 : :
2622 : : /* Found a record that matched! */
2623 : 11902 : port->hba = hba;
5192 alvherre@alvh.no-ip. 2624 : 11902 : return;
2625 : : }
2626 : :
2627 : : /* If no matching entry was found, then implicitly reject. */
6200 magnus@hagander.net 2628 : 69 : hba = palloc0(sizeof(HbaLine));
5619 simon@2ndQuadrant.co 2629 : 69 : hba->auth_method = uaImplicitReject;
6200 magnus@hagander.net 2630 : 69 : 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
8803 tgl@sss.pgh.pa.us 2645 : 941 : load_hba(void)
2646 : : {
2647 : : FILE *file;
5931 bruce@momjian.us 2648 : 941 : List *hba_lines = NIL;
2649 : : ListCell *line;
2650 : 941 : List *new_parsed_lines = NIL;
2651 : 941 : bool ok = true;
2652 : : MemoryContext oldcxt;
2653 : : MemoryContext hbacxt;
2654 : :
1027 michael@paquier.xyz 2655 : 941 : file = open_auth_file(HbaFileName, LOG, 0, NULL);
8556 bruce@momjian.us 2656 [ - + ]: 941 : if (file == NULL)
2657 : : {
2658 : : /* error already logged */
6030 magnus@hagander.net 2659 :UBC 0 : return false;
2660 : : }
2661 : :
1017 michael@paquier.xyz 2662 :CBC 941 : tokenize_auth_file(HbaFileName, file, &hba_lines, LOG, 0);
2663 : :
2664 : : /* Now parse all the lines */
3720 tgl@sss.pgh.pa.us 2665 [ - + ]: 941 : Assert(PostmasterContext);
2666 : 941 : hbacxt = AllocSetContextCreate(PostmasterContext,
2667 : : "hba parser context",
2668 : : ALLOCSET_SMALL_SIZES);
5192 alvherre@alvh.no-ip. 2669 : 941 : oldcxt = MemoryContextSwitchTo(hbacxt);
3144 tgl@sss.pgh.pa.us 2670 [ + - + + : 6366 : foreach(line, hba_lines)
+ + ]
2671 : : {
1262 michael@paquier.xyz 2672 : 5425 : TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
2673 : : HbaLine *newline;
2674 : :
2675 : : /* don't parse lines that already have errors */
3141 tgl@sss.pgh.pa.us 2676 [ - + ]: 5425 : if (tok_line->err_msg != NULL)
2677 : : {
3141 tgl@sss.pgh.pa.us 2678 :UBC 0 : ok = false;
2679 : 0 : continue;
2680 : : }
2681 : :
3141 tgl@sss.pgh.pa.us 2682 [ + + ]:CBC 5425 : if ((newline = parse_hba_line(tok_line, LOG)) == NULL)
2683 : : {
2684 : : /* Parse error; remember there's trouble */
5817 2685 : 3 : 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 : : */
6027 magnus@hagander.net 2692 : 3 : continue;
2693 : : }
2694 : :
6200 2695 : 5422 : 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 : : */
5072 tgl@sss.pgh.pa.us 2703 [ + + - + ]: 941 : if (ok && new_parsed_lines == NIL)
2704 : : {
5072 tgl@sss.pgh.pa.us 2705 [ # # ]:UBC 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 */
1017 michael@paquier.xyz 2713 :CBC 941 : free_auth_file(file, 0);
5192 alvherre@alvh.no-ip. 2714 : 941 : MemoryContextSwitchTo(oldcxt);
2715 : :
6027 magnus@hagander.net 2716 [ + + ]: 941 : 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 : : */
5192 alvherre@alvh.no-ip. 2722 : 1 : MemoryContextDelete(hbacxt);
6027 magnus@hagander.net 2723 : 1 : return false;
2724 : : }
2725 : :
2726 : : /* Loaded new file successfully, replace the one we use */
5192 alvherre@alvh.no-ip. 2727 [ + + ]: 940 : if (parsed_hba_context != NULL)
2728 : 135 : MemoryContextDelete(parsed_hba_context);
2729 : 940 : parsed_hba_context = hbacxt;
6200 magnus@hagander.net 2730 : 940 : parsed_hba_lines = new_parsed_lines;
2731 : :
2732 : 940 : 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 *
1257 michael@paquier.xyz 2751 : 98 : parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
2752 : : {
3144 tgl@sss.pgh.pa.us 2753 : 98 : int line_num = tok_line->line_num;
1046 michael@paquier.xyz 2754 : 98 : char *file_name = tok_line->file_name;
1257 2755 : 98 : char **err_msg = &tok_line->err_msg;
2756 : : ListCell *field;
2757 : : List *tokens;
2758 : : AuthToken *token;
2759 : : IdentLine *parsedline;
2760 : :
3144 tgl@sss.pgh.pa.us 2761 [ - + ]: 98 : Assert(tok_line->fields != NIL);
2762 : 98 : field = list_head(tok_line->fields);
2763 : :
4733 heikki.linnakangas@i 2764 : 98 : parsedline = palloc0(sizeof(IdentLine));
3144 tgl@sss.pgh.pa.us 2765 : 98 : parsedline->linenumber = line_num;
2766 : :
2767 : : /* Get the map token (must exist) */
5192 alvherre@alvh.no-ip. 2768 : 98 : tokens = lfirst(field);
2769 [ - + - - ]: 98 : IDENT_MULTI_VALUE(tokens);
2770 : 98 : token = linitial(tokens);
4733 heikki.linnakangas@i 2771 : 98 : parsedline->usermap = pstrdup(token->string);
2772 : :
2773 : : /* Get the ident user token */
2245 tgl@sss.pgh.pa.us 2774 : 98 : field = lnext(tok_line->fields, field);
5192 alvherre@alvh.no-ip. 2775 [ - + - - ]: 98 : IDENT_FIELD_ABSENT(field);
2776 : 98 : tokens = lfirst(field);
2777 [ - + - - ]: 98 : IDENT_MULTI_VALUE(tokens);
2778 : 98 : token = linitial(tokens);
2779 : :
2780 : : /* Copy the ident user token */
968 michael@paquier.xyz 2781 : 98 : parsedline->system_user = copy_auth_token(token);
2782 : :
2783 : : /* Get the PG rolename token */
2245 tgl@sss.pgh.pa.us 2784 : 98 : field = lnext(tok_line->fields, field);
5192 alvherre@alvh.no-ip. 2785 [ - + - - ]: 98 : IDENT_FIELD_ABSENT(field);
2786 : 98 : tokens = lfirst(field);
2787 [ - + - - ]: 98 : IDENT_MULTI_VALUE(tokens);
2788 : 98 : token = linitial(tokens);
964 michael@paquier.xyz 2789 : 98 : 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 : : */
968 2795 [ - + ]: 98 : if (regcomp_auth_token(parsedline->system_user, file_name, line_num,
2796 : : err_msg, elevel))
2797 : : {
2798 : : /* err_msg includes the error to report */
1053 michael@paquier.xyz 2799 :UBC 0 : return NULL;
2800 : : }
2801 : :
960 michael@paquier.xyz 2802 [ - + ]:CBC 98 : if (regcomp_auth_token(parsedline->pg_user, file_name, line_num,
2803 : : err_msg, elevel))
2804 : : {
2805 : : /* err_msg includes the error to report */
960 michael@paquier.xyz 2806 :UBC 0 : return NULL;
2807 : : }
2808 : :
4733 heikki.linnakangas@i 2809 :CBC 98 : 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 : 62 : 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 : 62 : *found_p = false;
2826 : 62 : *error_p = false;
2827 : :
2828 [ + + ]: 62 : if (strcmp(identLine->usermap, usermap_name) != 0)
2829 : : /* Line does not match the map name we're looking for, so just abort */
2830 : 3 : return;
2831 : :
2832 : : /* Get the target role's OID. Note we do not error out for bad role. */
960 michael@paquier.xyz 2833 : 59 : roleid = get_role_oid(pg_user, true);
2834 : :
2835 : : /* Match? */
968 2836 [ + + ]: 59 : 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;
960 2847 : 45 : bool created_temporary_token = false;
2848 : :
968 2849 : 45 : r = regexec_auth_token(system_user, identLine->system_user, 2, matches);
6126 magnus@hagander.net 2850 [ + + ]: 45 : if (r)
2851 : : {
2852 : : char errstr[100];
2853 : :
2854 [ - + ]: 1 : if (r != REG_NOMATCH)
2855 : : {
2856 : : /* REG_NOMATCH is not an error, everything else is */
968 michael@paquier.xyz 2857 :UBC 0 : pg_regerror(r, identLine->system_user->regex, errstr, sizeof(errstr));
5918 magnus@hagander.net 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)));
6126 2862 : 0 : *error_p = true;
2863 : : }
6126 magnus@hagander.net 2864 :CBC 1 : 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 : : */
960 michael@paquier.xyz 2872 [ + + + + ]: 44 : if (!token_is_member_check(identLine->pg_user) &&
2873 [ + + ]: 42 : !token_has_regexp(identLine->pg_user) &&
2874 [ + + ]: 41 : (ofs = strstr(identLine->pg_user->string, "\\1")) != NULL)
6126 magnus@hagander.net 2875 : 36 : {
2876 : : const char *repl_str;
2877 : : size_t repl_len;
2878 : : char *old_pg_user;
2879 : : char *expanded_pg_user;
2880 : : size_t offset;
2881 : :
2882 : : /* substitution of the first argument requested */
2883 [ + + ]: 37 : if (matches[1].rm_so < 0)
2884 : : {
5918 2885 [ + - ]: 1 : ereport(LOG,
2886 : : (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
2887 : : errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"",
2888 : : identLine->system_user->string + 1, identLine->pg_user->string)));
2889 : 1 : *error_p = true;
2890 : 1 : return;
2891 : : }
55 tgl@sss.pgh.pa.us 2892 :GNC 36 : repl_str = system_user + matches[1].rm_so;
2893 : 36 : repl_len = matches[1].rm_eo - matches[1].rm_so;
2894 : :
2895 : : /*
2896 : : * It's allowed to have more than one \1 in the string, and we'll
2897 : : * replace them all. But that's pretty unusual so we optimize on
2898 : : * the assumption of only one occurrence, which motivates doing
2899 : : * repeated replacements instead of making two passes over the
2900 : : * string to determine the final length right away.
2901 : : */
2902 : 36 : old_pg_user = identLine->pg_user->string;
2903 : : do
2904 : : {
2905 : : /*
2906 : : * length: current length minus length of \1 plus length of
2907 : : * replacement plus null terminator
2908 : : */
2909 : 38 : expanded_pg_user = palloc(strlen(old_pg_user) - 2 + repl_len + 1);
2910 : : /* ofs points into the old_pg_user string at this point */
2911 : 38 : offset = ofs - old_pg_user;
2912 : 38 : memcpy(expanded_pg_user, old_pg_user, offset);
2913 : 38 : memcpy(expanded_pg_user + offset, repl_str, repl_len);
2914 : 38 : strcpy(expanded_pg_user + offset + repl_len, ofs + 2);
2915 [ + + ]: 38 : if (old_pg_user != identLine->pg_user->string)
2916 : 2 : pfree(old_pg_user);
2917 : 38 : old_pg_user = expanded_pg_user;
2918 [ + + ]: 38 : } while ((ofs = strstr(old_pg_user + offset + repl_len, "\\1")) != NULL);
2919 : :
2920 : : /*
2921 : : * Mark the token as quoted, so it will only be compared literally
2922 : : * and not for some special meaning, such as "all" or a group
2923 : : * membership check.
2924 : : */
960 michael@paquier.xyz 2925 :CBC 36 : expanded_pg_user_token = make_auth_token(expanded_pg_user, true);
2926 : 36 : created_temporary_token = true;
2927 : 36 : pfree(expanded_pg_user);
2928 : : }
2929 : : else
2930 : : {
2931 : 7 : expanded_pg_user_token = identLine->pg_user;
2932 : : }
2933 : :
2934 : : /* check the Postgres user */
2935 : 43 : *found_p = check_role(pg_user, roleid,
2936 : 43 : list_make1(expanded_pg_user_token),
2937 : : case_insensitive);
2938 : :
2939 [ + + ]: 43 : if (created_temporary_token)
2940 : 36 : free_auth_token(expanded_pg_user_token);
2941 : :
6126 magnus@hagander.net 2942 : 43 : return;
2943 : : }
2944 : : else
2945 : : {
2946 : : /*
2947 : : * Not a regular expression, so make a complete match. If the system
2948 : : * user does not match, just leave.
2949 : : */
2950 [ - + ]: 14 : if (case_insensitive)
2951 : : {
960 michael@paquier.xyz 2952 [ # # ]:UBC 0 : if (!token_matches_insensitive(identLine->system_user,
2953 : : system_user))
960 michael@paquier.xyz 2954 :CBC 2 : return;
2955 : : }
2956 : : else
2957 : : {
2958 [ + + ]: 14 : if (!token_matches(identLine->system_user, system_user))
2959 : 2 : return;
2960 : : }
2961 : :
2962 : : /* check the Postgres user */
2963 : 12 : *found_p = check_role(pg_user, roleid,
2964 : 12 : list_make1(identLine->pg_user),
2965 : : case_insensitive);
2966 : : }
2967 : : }
2968 : :
2969 : :
2970 : : /*
2971 : : * Scan the (pre-parsed) ident usermap file line by line, looking for a match
2972 : : *
2973 : : * See if the system user with ident username "system_user" is allowed to act as
2974 : : * Postgres user "pg_user" according to usermap "usermap_name".
2975 : : *
2976 : : * Special case: Usermap NULL, equivalent to what was previously called
2977 : : * "sameuser" or "samerole", means don't look in the usermap file.
2978 : : * That's an implied map wherein "pg_user" must be identical to
2979 : : * "system_user" in order to be authorized.
2980 : : *
2981 : : * Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR.
2982 : : */
2983 : : int
6162 magnus@hagander.net 2984 : 124 : check_usermap(const char *usermap_name,
2985 : : const char *pg_user,
2986 : : const char *system_user,
2987 : : bool case_insensitive)
2988 : : {
8717 bruce@momjian.us 2989 : 124 : bool found_entry = false,
2990 : 124 : error = false;
2991 : :
8178 tgl@sss.pgh.pa.us 2992 [ + + - + ]: 124 : if (usermap_name == NULL || usermap_name[0] == '\0')
2993 : : {
6162 magnus@hagander.net 2994 [ - + ]: 63 : if (case_insensitive)
2995 : : {
968 michael@paquier.xyz 2996 [ # # ]:UBC 0 : if (pg_strcasecmp(pg_user, system_user) == 0)
6162 magnus@hagander.net 2997 : 0 : return STATUS_OK;
2998 : : }
2999 : : else
3000 : : {
968 michael@paquier.xyz 3001 [ + + ]:CBC 63 : if (strcmp(pg_user, system_user) == 0)
6162 magnus@hagander.net 3002 : 60 : return STATUS_OK;
3003 : : }
3004 [ + - ]: 3 : ereport(LOG,
3005 : : (errmsg("provided user name (%s) and authenticated user name (%s) do not match",
3006 : : pg_user, system_user)));
3007 : 3 : return STATUS_ERROR;
3008 : : }
3009 : : else
3010 : : {
3011 : : ListCell *line_cell;
3012 : :
4733 heikki.linnakangas@i 3013 [ + + + + : 71 : foreach(line_cell, parsed_ident_lines)
+ + ]
3014 : : {
3015 : 62 : check_ident_usermap(lfirst(line_cell), usermap_name,
3016 : : pg_user, system_user, case_insensitive,
3017 : : &found_entry, &error);
8804 bruce@momjian.us 3018 [ + + + + ]: 62 : if (found_entry || error)
3019 : : break;
3020 : : }
3021 : : }
6162 magnus@hagander.net 3022 [ + + + + ]: 61 : if (!found_entry && !error)
3023 : : {
3024 [ + - ]: 9 : ereport(LOG,
3025 : : (errmsg("no match in usermap \"%s\" for user \"%s\" authenticated as \"%s\"",
3026 : : usermap_name, pg_user, system_user)));
3027 : : }
5931 bruce@momjian.us 3028 [ + + ]: 61 : return found_entry ? STATUS_OK : STATUS_ERROR;
3029 : : }
3030 : :
3031 : :
3032 : : /*
3033 : : * Read the ident config file and create a List of IdentLine records for
3034 : : * the contents.
3035 : : *
3036 : : * This works the same as load_hba(), but for the user config file.
3037 : : */
3038 : : bool
8803 tgl@sss.pgh.pa.us 3039 : 940 : load_ident(void)
3040 : : {
3041 : : FILE *file;
4733 heikki.linnakangas@i 3042 : 940 : List *ident_lines = NIL;
3043 : : ListCell *line_cell;
3044 : 940 : List *new_parsed_lines = NIL;
3045 : 940 : bool ok = true;
3046 : : MemoryContext oldcxt;
3047 : : MemoryContext ident_context;
3048 : : IdentLine *newline;
3049 : :
3050 : : /* not FATAL ... we just won't do any special ident maps */
1027 michael@paquier.xyz 3051 : 940 : file = open_auth_file(IdentFileName, LOG, 0, NULL);
8804 bruce@momjian.us 3052 [ - + ]: 940 : if (file == NULL)
3053 : : {
3054 : : /* error already logged */
4733 heikki.linnakangas@i 3055 :UBC 0 : return false;
3056 : : }
3057 : :
1017 michael@paquier.xyz 3058 :CBC 940 : tokenize_auth_file(IdentFileName, file, &ident_lines, LOG, 0);
3059 : :
3060 : : /* Now parse all the lines */
3720 tgl@sss.pgh.pa.us 3061 [ - + ]: 940 : Assert(PostmasterContext);
3062 : 940 : ident_context = AllocSetContextCreate(PostmasterContext,
3063 : : "ident parser context",
3064 : : ALLOCSET_SMALL_SIZES);
4733 heikki.linnakangas@i 3065 : 940 : oldcxt = MemoryContextSwitchTo(ident_context);
3144 tgl@sss.pgh.pa.us 3066 [ + + + + : 1038 : foreach(line_cell, ident_lines)
+ + ]
3067 : : {
1262 michael@paquier.xyz 3068 : 98 : TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line_cell);
3069 : :
3070 : : /* don't parse lines that already have errors */
3141 tgl@sss.pgh.pa.us 3071 [ - + ]: 98 : if (tok_line->err_msg != NULL)
3072 : : {
3141 tgl@sss.pgh.pa.us 3073 :UBC 0 : ok = false;
3074 : 0 : continue;
3075 : : }
3076 : :
1257 michael@paquier.xyz 3077 [ - + ]:CBC 98 : if ((newline = parse_ident_line(tok_line, LOG)) == NULL)
3078 : : {
3079 : : /* Parse error; remember there's trouble */
4733 heikki.linnakangas@i 3080 :UBC 0 : ok = false;
3081 : :
3082 : : /*
3083 : : * Keep parsing the rest of the file so we can report errors on
3084 : : * more than the first line. Error has already been logged, no
3085 : : * need for more chatter here.
3086 : : */
3087 : 0 : continue;
3088 : : }
3089 : :
4733 heikki.linnakangas@i 3090 :CBC 98 : new_parsed_lines = lappend(new_parsed_lines, newline);
3091 : : }
3092 : :
3093 : : /* Free tokenizer memory */
1017 michael@paquier.xyz 3094 : 940 : free_auth_file(file, 0);
4733 heikki.linnakangas@i 3095 : 940 : MemoryContextSwitchTo(oldcxt);
3096 : :
3097 [ - + ]: 940 : if (!ok)
3098 : : {
3099 : : /*
3100 : : * File contained one or more errors, so bail out. MemoryContextDelete
3101 : : * is enough to clean up everything, including regexes.
3102 : : */
4733 heikki.linnakangas@i 3103 :UBC 0 : MemoryContextDelete(ident_context);
3104 : 0 : return false;
3105 : : }
3106 : :
3107 : : /* Loaded new file successfully, replace the one we use */
4335 heikki.linnakangas@i 3108 [ + + ]:CBC 940 : if (parsed_ident_context != NULL)
3109 : 135 : MemoryContextDelete(parsed_ident_context);
3110 : :
4733 3111 : 940 : parsed_ident_context = ident_context;
3112 : 940 : parsed_ident_lines = new_parsed_lines;
3113 : :
3114 : 940 : return true;
3115 : : }
3116 : :
3117 : :
3118 : :
3119 : : /*
3120 : : * Determine what authentication method should be used when accessing database
3121 : : * "database" from frontend "raddr", user "user". Return the method and
3122 : : * an optional argument (stored in fields of *port), and STATUS_OK.
3123 : : *
3124 : : * If the file does not contain any entry matching the request, we return
3125 : : * method = uaImplicitReject.
3126 : : */
3127 : : void
8804 bruce@momjian.us 3128 : 11971 : hba_getauthmethod(hbaPort *port)
3129 : : {
5192 alvherre@alvh.no-ip. 3130 : 11971 : check_hba(port);
8804 bruce@momjian.us 3131 : 11971 : }
3132 : :
3133 : :
3134 : : /*
3135 : : * Return the name of the auth method in use ("gss", "md5", "trust", etc.).
3136 : : *
3137 : : * The return value is statically allocated (see the UserAuthName array) and
3138 : : * should not be freed.
3139 : : */
3140 : : const char *
1613 magnus@hagander.net 3141 : 595 : hba_authname(UserAuth auth_method)
3142 : : {
michael@paquier.xyz 3143 : 595 : return UserAuthName[auth_method];
3144 : : }
|