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