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