Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * hbafuncs.c
4 : * Support functions for SQL views of authentication files.
5 : *
6 : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/utils/adt/hbafuncs.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include "access/htup_details.h"
18 : #include "catalog/objectaddress.h"
19 : #include "common/ip.h"
20 : #include "funcapi.h"
21 : #include "libpq/hba.h"
22 : #include "utils/array.h"
23 : #include "utils/builtins.h"
24 : #include "utils/guc.h"
25 :
26 :
27 : static ArrayType *get_hba_options(HbaLine *hba);
28 : static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
29 : int rule_number, char *filename, int lineno,
30 : HbaLine *hba, const char *err_msg);
31 : static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc);
32 : static void fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
33 : int map_number, char *filename, int lineno,
34 : IdentLine *ident, const char *err_msg);
35 : static void fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc);
36 :
37 :
38 : /*
39 : * This macro specifies the maximum number of authentication options
40 : * that are possible with any given authentication method that is supported.
41 : * Currently LDAP supports 12, and there are 3 that are not dependent on
42 : * the auth method here. It may not actually be possible to set all of them
43 : * at the same time, but we'll set the macro value high enough to be
44 : * conservative and avoid warnings from static analysis tools.
45 : */
46 : #define MAX_HBA_OPTIONS 15
47 :
48 : /*
49 : * Create a text array listing the options specified in the HBA line.
50 : * Return NULL if no options are specified.
51 : */
52 : static ArrayType *
53 56 : get_hba_options(HbaLine *hba)
54 : {
55 : int noptions;
56 : Datum options[MAX_HBA_OPTIONS];
57 :
58 56 : noptions = 0;
59 :
60 56 : if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI)
61 : {
62 0 : if (hba->include_realm)
63 0 : options[noptions++] =
64 0 : CStringGetTextDatum("include_realm=true");
65 :
66 0 : if (hba->krb_realm)
67 0 : options[noptions++] =
68 0 : CStringGetTextDatum(psprintf("krb_realm=%s", hba->krb_realm));
69 : }
70 :
71 56 : if (hba->usermap)
72 0 : options[noptions++] =
73 0 : CStringGetTextDatum(psprintf("map=%s", hba->usermap));
74 :
75 56 : if (hba->clientcert != clientCertOff)
76 0 : options[noptions++] =
77 0 : CStringGetTextDatum(psprintf("clientcert=%s", (hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full"));
78 :
79 56 : if (hba->pamservice)
80 0 : options[noptions++] =
81 0 : CStringGetTextDatum(psprintf("pamservice=%s", hba->pamservice));
82 :
83 56 : if (hba->auth_method == uaLDAP)
84 : {
85 0 : if (hba->ldapserver)
86 0 : options[noptions++] =
87 0 : CStringGetTextDatum(psprintf("ldapserver=%s", hba->ldapserver));
88 :
89 0 : if (hba->ldapport)
90 0 : options[noptions++] =
91 0 : CStringGetTextDatum(psprintf("ldapport=%d", hba->ldapport));
92 :
93 0 : if (hba->ldapscheme)
94 0 : options[noptions++] =
95 0 : CStringGetTextDatum(psprintf("ldapscheme=%s", hba->ldapscheme));
96 :
97 0 : if (hba->ldaptls)
98 0 : options[noptions++] =
99 0 : CStringGetTextDatum("ldaptls=true");
100 :
101 0 : if (hba->ldapprefix)
102 0 : options[noptions++] =
103 0 : CStringGetTextDatum(psprintf("ldapprefix=%s", hba->ldapprefix));
104 :
105 0 : if (hba->ldapsuffix)
106 0 : options[noptions++] =
107 0 : CStringGetTextDatum(psprintf("ldapsuffix=%s", hba->ldapsuffix));
108 :
109 0 : if (hba->ldapbasedn)
110 0 : options[noptions++] =
111 0 : CStringGetTextDatum(psprintf("ldapbasedn=%s", hba->ldapbasedn));
112 :
113 0 : if (hba->ldapbinddn)
114 0 : options[noptions++] =
115 0 : CStringGetTextDatum(psprintf("ldapbinddn=%s", hba->ldapbinddn));
116 :
117 0 : if (hba->ldapbindpasswd)
118 0 : options[noptions++] =
119 0 : CStringGetTextDatum(psprintf("ldapbindpasswd=%s",
120 : hba->ldapbindpasswd));
121 :
122 0 : if (hba->ldapsearchattribute)
123 0 : options[noptions++] =
124 0 : CStringGetTextDatum(psprintf("ldapsearchattribute=%s",
125 : hba->ldapsearchattribute));
126 :
127 0 : if (hba->ldapsearchfilter)
128 0 : options[noptions++] =
129 0 : CStringGetTextDatum(psprintf("ldapsearchfilter=%s",
130 : hba->ldapsearchfilter));
131 :
132 0 : if (hba->ldapscope)
133 0 : options[noptions++] =
134 0 : CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
135 : }
136 :
137 56 : if (hba->auth_method == uaRADIUS)
138 : {
139 0 : if (hba->radiusservers_s)
140 0 : options[noptions++] =
141 0 : CStringGetTextDatum(psprintf("radiusservers=%s", hba->radiusservers_s));
142 :
143 0 : if (hba->radiussecrets_s)
144 0 : options[noptions++] =
145 0 : CStringGetTextDatum(psprintf("radiussecrets=%s", hba->radiussecrets_s));
146 :
147 0 : if (hba->radiusidentifiers_s)
148 0 : options[noptions++] =
149 0 : CStringGetTextDatum(psprintf("radiusidentifiers=%s", hba->radiusidentifiers_s));
150 :
151 0 : if (hba->radiusports_s)
152 0 : options[noptions++] =
153 0 : CStringGetTextDatum(psprintf("radiusports=%s", hba->radiusports_s));
154 : }
155 :
156 56 : if (hba->auth_method == uaOAuth)
157 : {
158 0 : if (hba->oauth_issuer)
159 0 : options[noptions++] =
160 0 : CStringGetTextDatum(psprintf("issuer=%s", hba->oauth_issuer));
161 :
162 0 : if (hba->oauth_scope)
163 0 : options[noptions++] =
164 0 : CStringGetTextDatum(psprintf("scope=%s", hba->oauth_scope));
165 :
166 0 : if (hba->oauth_validator)
167 0 : options[noptions++] =
168 0 : CStringGetTextDatum(psprintf("validator=%s", hba->oauth_validator));
169 :
170 0 : if (hba->oauth_skip_usermap)
171 0 : options[noptions++] =
172 0 : CStringGetTextDatum(psprintf("delegate_ident_mapping=true"));
173 : }
174 :
175 : /* If you add more options, consider increasing MAX_HBA_OPTIONS. */
176 : Assert(noptions <= MAX_HBA_OPTIONS);
177 :
178 56 : if (noptions > 0)
179 0 : return construct_array_builtin(options, noptions, TEXTOID);
180 : else
181 56 : return NULL;
182 : }
183 :
184 : /* Number of columns in pg_hba_file_rules view */
185 : #define NUM_PG_HBA_FILE_RULES_ATTS 11
186 :
187 : /*
188 : * fill_hba_line
189 : * Build one row of pg_hba_file_rules view, add it to tuplestore.
190 : *
191 : * tuple_store: where to store data
192 : * tupdesc: tuple descriptor for the view
193 : * rule_number: unique identifier among all valid rules
194 : * filename: configuration file name (must always be valid)
195 : * lineno: line number of configuration file (must always be valid)
196 : * hba: parsed line data (can be NULL, in which case err_msg should be set)
197 : * err_msg: error message (NULL if none)
198 : *
199 : * Note: leaks memory, but we don't care since this is run in a short-lived
200 : * memory context.
201 : */
202 : static void
203 56 : fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
204 : int rule_number, char *filename, int lineno, HbaLine *hba,
205 : const char *err_msg)
206 : {
207 : Datum values[NUM_PG_HBA_FILE_RULES_ATTS];
208 : bool nulls[NUM_PG_HBA_FILE_RULES_ATTS];
209 : char buffer[NI_MAXHOST];
210 : HeapTuple tuple;
211 : int index;
212 : ListCell *lc;
213 : const char *typestr;
214 : const char *addrstr;
215 : const char *maskstr;
216 : ArrayType *options;
217 :
218 : Assert(tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS);
219 :
220 56 : memset(values, 0, sizeof(values));
221 56 : memset(nulls, 0, sizeof(nulls));
222 56 : index = 0;
223 :
224 : /* rule_number, nothing on error */
225 56 : if (err_msg)
226 0 : nulls[index++] = true;
227 : else
228 56 : values[index++] = Int32GetDatum(rule_number);
229 :
230 : /* file_name */
231 56 : values[index++] = CStringGetTextDatum(filename);
232 :
233 : /* line_number */
234 56 : values[index++] = Int32GetDatum(lineno);
235 :
236 56 : if (hba != NULL)
237 : {
238 : /* type */
239 : /* Avoid a default: case so compiler will warn about missing cases */
240 56 : typestr = NULL;
241 56 : switch (hba->conntype)
242 : {
243 32 : case ctLocal:
244 32 : typestr = "local";
245 32 : break;
246 24 : case ctHost:
247 24 : typestr = "host";
248 24 : break;
249 0 : case ctHostSSL:
250 0 : typestr = "hostssl";
251 0 : break;
252 0 : case ctHostNoSSL:
253 0 : typestr = "hostnossl";
254 0 : break;
255 0 : case ctHostGSS:
256 0 : typestr = "hostgssenc";
257 0 : break;
258 0 : case ctHostNoGSS:
259 0 : typestr = "hostnogssenc";
260 0 : break;
261 : }
262 56 : if (typestr)
263 56 : values[index++] = CStringGetTextDatum(typestr);
264 : else
265 0 : nulls[index++] = true;
266 :
267 : /* database */
268 56 : if (hba->databases)
269 : {
270 : /*
271 : * Flatten AuthToken list to string list. It might seem that we
272 : * should re-quote any quoted tokens, but that has been rejected
273 : * on the grounds that it makes it harder to compare the array
274 : * elements to other system catalogs. That makes entries like
275 : * "all" or "samerole" formally ambiguous ... but users who name
276 : * databases/roles that way are inflicting their own pain.
277 : */
278 56 : List *names = NIL;
279 :
280 114 : foreach(lc, hba->databases)
281 : {
282 58 : AuthToken *tok = lfirst(lc);
283 :
284 58 : names = lappend(names, tok->string);
285 : }
286 56 : values[index++] = PointerGetDatum(strlist_to_textarray(names));
287 : }
288 : else
289 0 : nulls[index++] = true;
290 :
291 : /* user */
292 56 : if (hba->roles)
293 : {
294 : /* Flatten AuthToken list to string list; see comment above */
295 56 : List *roles = NIL;
296 :
297 112 : foreach(lc, hba->roles)
298 : {
299 56 : AuthToken *tok = lfirst(lc);
300 :
301 56 : roles = lappend(roles, tok->string);
302 : }
303 56 : values[index++] = PointerGetDatum(strlist_to_textarray(roles));
304 : }
305 : else
306 0 : nulls[index++] = true;
307 :
308 : /* address and netmask */
309 : /* Avoid a default: case so compiler will warn about missing cases */
310 56 : addrstr = maskstr = NULL;
311 56 : switch (hba->ip_cmp_method)
312 : {
313 56 : case ipCmpMask:
314 56 : if (hba->hostname)
315 : {
316 0 : addrstr = hba->hostname;
317 : }
318 : else
319 : {
320 : /*
321 : * Note: if pg_getnameinfo_all fails, it'll set buffer to
322 : * "???", which we want to return.
323 : */
324 56 : if (hba->addrlen > 0)
325 : {
326 24 : if (pg_getnameinfo_all(&hba->addr, hba->addrlen,
327 : buffer, sizeof(buffer),
328 : NULL, 0,
329 : NI_NUMERICHOST) == 0)
330 24 : clean_ipv6_addr(hba->addr.ss_family, buffer);
331 24 : addrstr = pstrdup(buffer);
332 : }
333 56 : if (hba->masklen > 0)
334 : {
335 24 : if (pg_getnameinfo_all(&hba->mask, hba->masklen,
336 : buffer, sizeof(buffer),
337 : NULL, 0,
338 : NI_NUMERICHOST) == 0)
339 24 : clean_ipv6_addr(hba->mask.ss_family, buffer);
340 24 : maskstr = pstrdup(buffer);
341 : }
342 : }
343 56 : break;
344 0 : case ipCmpAll:
345 0 : addrstr = "all";
346 0 : break;
347 0 : case ipCmpSameHost:
348 0 : addrstr = "samehost";
349 0 : break;
350 0 : case ipCmpSameNet:
351 0 : addrstr = "samenet";
352 0 : break;
353 : }
354 56 : if (addrstr)
355 24 : values[index++] = CStringGetTextDatum(addrstr);
356 : else
357 32 : nulls[index++] = true;
358 56 : if (maskstr)
359 24 : values[index++] = CStringGetTextDatum(maskstr);
360 : else
361 32 : nulls[index++] = true;
362 :
363 : /* auth_method */
364 56 : values[index++] = CStringGetTextDatum(hba_authname(hba->auth_method));
365 :
366 : /* options */
367 56 : options = get_hba_options(hba);
368 56 : if (options)
369 0 : values[index++] = PointerGetDatum(options);
370 : else
371 56 : nulls[index++] = true;
372 : }
373 : else
374 : {
375 : /* no parsing result, so set relevant fields to nulls */
376 0 : memset(&nulls[3], true, (NUM_PG_HBA_FILE_RULES_ATTS - 4) * sizeof(bool));
377 : }
378 :
379 : /* error */
380 56 : if (err_msg)
381 0 : values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(err_msg);
382 : else
383 56 : nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true;
384 :
385 56 : tuple = heap_form_tuple(tupdesc, values, nulls);
386 56 : tuplestore_puttuple(tuple_store, tuple);
387 56 : }
388 :
389 : /*
390 : * fill_hba_view
391 : * Read the pg_hba.conf file and fill the tuplestore with view records.
392 : */
393 : static void
394 8 : fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
395 : {
396 : FILE *file;
397 8 : List *hba_lines = NIL;
398 : ListCell *line;
399 8 : int rule_number = 0;
400 : MemoryContext hbacxt;
401 : MemoryContext oldcxt;
402 :
403 : /*
404 : * In the unlikely event that we can't open pg_hba.conf, we throw an
405 : * error, rather than trying to report it via some sort of view entry.
406 : * (Most other error conditions should result in a message in a view
407 : * entry.)
408 : */
409 8 : file = open_auth_file(HbaFileName, ERROR, 0, NULL);
410 :
411 8 : tokenize_auth_file(HbaFileName, file, &hba_lines, DEBUG3, 0);
412 :
413 : /* Now parse all the lines */
414 8 : hbacxt = AllocSetContextCreate(CurrentMemoryContext,
415 : "hba parser context",
416 : ALLOCSET_SMALL_SIZES);
417 8 : oldcxt = MemoryContextSwitchTo(hbacxt);
418 64 : foreach(line, hba_lines)
419 : {
420 56 : TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
421 56 : HbaLine *hbaline = NULL;
422 :
423 : /* don't parse lines that already have errors */
424 56 : if (tok_line->err_msg == NULL)
425 56 : hbaline = parse_hba_line(tok_line, DEBUG3);
426 :
427 : /* No error, set a new rule number */
428 56 : if (tok_line->err_msg == NULL)
429 56 : rule_number++;
430 :
431 56 : fill_hba_line(tuple_store, tupdesc, rule_number,
432 : tok_line->file_name, tok_line->line_num, hbaline,
433 56 : tok_line->err_msg);
434 : }
435 :
436 : /* Free tokenizer memory */
437 8 : free_auth_file(file, 0);
438 : /* Free parse_hba_line memory */
439 8 : MemoryContextSwitchTo(oldcxt);
440 8 : MemoryContextDelete(hbacxt);
441 8 : }
442 :
443 : /*
444 : * pg_hba_file_rules
445 : *
446 : * SQL-accessible set-returning function to return all the entries in the
447 : * pg_hba.conf file.
448 : */
449 : Datum
450 8 : pg_hba_file_rules(PG_FUNCTION_ARGS)
451 : {
452 : ReturnSetInfo *rsi;
453 :
454 : /*
455 : * Build tuplestore to hold the result rows. We must use the Materialize
456 : * mode to be safe against HBA file changes while the cursor is open. It's
457 : * also more efficient than having to look up our current position in the
458 : * parsed list every time.
459 : */
460 8 : InitMaterializedSRF(fcinfo, 0);
461 :
462 : /* Fill the tuplestore */
463 8 : rsi = (ReturnSetInfo *) fcinfo->resultinfo;
464 8 : fill_hba_view(rsi->setResult, rsi->setDesc);
465 :
466 8 : PG_RETURN_NULL();
467 : }
468 :
469 : /* Number of columns in pg_ident_file_mappings view */
470 : #define NUM_PG_IDENT_FILE_MAPPINGS_ATTS 7
471 :
472 : /*
473 : * fill_ident_line: build one row of pg_ident_file_mappings view, add it to
474 : * tuplestore
475 : *
476 : * tuple_store: where to store data
477 : * tupdesc: tuple descriptor for the view
478 : * map_number: unique identifier among all valid maps
479 : * filename: configuration file name (must always be valid)
480 : * lineno: line number of configuration file (must always be valid)
481 : * ident: parsed line data (can be NULL, in which case err_msg should be set)
482 : * err_msg: error message (NULL if none)
483 : *
484 : * Note: leaks memory, but we don't care since this is run in a short-lived
485 : * memory context.
486 : */
487 : static void
488 0 : fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
489 : int map_number, char *filename, int lineno, IdentLine *ident,
490 : const char *err_msg)
491 : {
492 : Datum values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
493 : bool nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
494 : HeapTuple tuple;
495 : int index;
496 :
497 : Assert(tupdesc->natts == NUM_PG_IDENT_FILE_MAPPINGS_ATTS);
498 :
499 0 : memset(values, 0, sizeof(values));
500 0 : memset(nulls, 0, sizeof(nulls));
501 0 : index = 0;
502 :
503 : /* map_number, nothing on error */
504 0 : if (err_msg)
505 0 : nulls[index++] = true;
506 : else
507 0 : values[index++] = Int32GetDatum(map_number);
508 :
509 : /* file_name */
510 0 : values[index++] = CStringGetTextDatum(filename);
511 :
512 : /* line_number */
513 0 : values[index++] = Int32GetDatum(lineno);
514 :
515 0 : if (ident != NULL)
516 : {
517 0 : values[index++] = CStringGetTextDatum(ident->usermap);
518 0 : values[index++] = CStringGetTextDatum(ident->system_user->string);
519 0 : values[index++] = CStringGetTextDatum(ident->pg_user->string);
520 : }
521 : else
522 : {
523 : /* no parsing result, so set relevant fields to nulls */
524 0 : memset(&nulls[3], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 4) * sizeof(bool));
525 : }
526 :
527 : /* error */
528 0 : if (err_msg)
529 0 : values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = CStringGetTextDatum(err_msg);
530 : else
531 0 : nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = true;
532 :
533 0 : tuple = heap_form_tuple(tupdesc, values, nulls);
534 0 : tuplestore_puttuple(tuple_store, tuple);
535 0 : }
536 :
537 : /*
538 : * Read the pg_ident.conf file and fill the tuplestore with view records.
539 : */
540 : static void
541 6 : fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
542 : {
543 : FILE *file;
544 6 : List *ident_lines = NIL;
545 : ListCell *line;
546 6 : int map_number = 0;
547 : MemoryContext identcxt;
548 : MemoryContext oldcxt;
549 :
550 : /*
551 : * In the unlikely event that we can't open pg_ident.conf, we throw an
552 : * error, rather than trying to report it via some sort of view entry.
553 : * (Most other error conditions should result in a message in a view
554 : * entry.)
555 : */
556 6 : file = open_auth_file(IdentFileName, ERROR, 0, NULL);
557 :
558 6 : tokenize_auth_file(IdentFileName, file, &ident_lines, DEBUG3, 0);
559 :
560 : /* Now parse all the lines */
561 6 : identcxt = AllocSetContextCreate(CurrentMemoryContext,
562 : "ident parser context",
563 : ALLOCSET_SMALL_SIZES);
564 6 : oldcxt = MemoryContextSwitchTo(identcxt);
565 6 : foreach(line, ident_lines)
566 : {
567 0 : TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
568 0 : IdentLine *identline = NULL;
569 :
570 : /* don't parse lines that already have errors */
571 0 : if (tok_line->err_msg == NULL)
572 0 : identline = parse_ident_line(tok_line, DEBUG3);
573 :
574 : /* no error, set a new mapping number */
575 0 : if (tok_line->err_msg == NULL)
576 0 : map_number++;
577 :
578 0 : fill_ident_line(tuple_store, tupdesc, map_number,
579 : tok_line->file_name, tok_line->line_num,
580 0 : identline, tok_line->err_msg);
581 : }
582 :
583 : /* Free tokenizer memory */
584 6 : free_auth_file(file, 0);
585 : /* Free parse_ident_line memory */
586 6 : MemoryContextSwitchTo(oldcxt);
587 6 : MemoryContextDelete(identcxt);
588 6 : }
589 :
590 : /*
591 : * SQL-accessible SRF to return all the entries in the pg_ident.conf file.
592 : */
593 : Datum
594 6 : pg_ident_file_mappings(PG_FUNCTION_ARGS)
595 : {
596 : ReturnSetInfo *rsi;
597 :
598 : /*
599 : * Build tuplestore to hold the result rows. We must use the Materialize
600 : * mode to be safe against HBA file changes while the cursor is open. It's
601 : * also more efficient than having to look up our current position in the
602 : * parsed list every time.
603 : */
604 6 : InitMaterializedSRF(fcinfo, 0);
605 :
606 : /* Fill the tuplestore */
607 6 : rsi = (ReturnSetInfo *) fcinfo->resultinfo;
608 6 : fill_ident_view(rsi->setResult, rsi->setDesc);
609 :
610 6 : PG_RETURN_NULL();
611 : }
|