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