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