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