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 == uaOAuth)
139 : {
140 0 : if (hba->oauth_issuer)
141 0 : options[noptions++] =
142 0 : CStringGetTextDatum(psprintf("issuer=%s", hba->oauth_issuer));
143 :
144 0 : if (hba->oauth_scope)
145 0 : options[noptions++] =
146 0 : CStringGetTextDatum(psprintf("scope=%s", hba->oauth_scope));
147 :
148 0 : if (hba->oauth_validator)
149 0 : options[noptions++] =
150 0 : CStringGetTextDatum(psprintf("validator=%s", hba->oauth_validator));
151 :
152 0 : if (hba->oauth_skip_usermap)
153 0 : options[noptions++] =
154 0 : CStringGetTextDatum(psprintf("delegate_ident_mapping=true"));
155 : }
156 :
157 : /* If you add more options, consider increasing MAX_HBA_OPTIONS. */
158 : Assert(noptions <= MAX_HBA_OPTIONS);
159 :
160 34 : if (noptions > 0)
161 0 : return construct_array_builtin(options, noptions, TEXTOID);
162 : else
163 34 : return NULL;
164 : }
165 :
166 : /* Number of columns in pg_hba_file_rules view */
167 : #define NUM_PG_HBA_FILE_RULES_ATTS 11
168 :
169 : /*
170 : * fill_hba_line
171 : * Build one row of pg_hba_file_rules view, add it to tuplestore.
172 : *
173 : * tuple_store: where to store data
174 : * tupdesc: tuple descriptor for the view
175 : * rule_number: unique identifier among all valid rules
176 : * filename: configuration file name (must always be valid)
177 : * lineno: line number of configuration file (must always be valid)
178 : * hba: parsed line data (can be NULL, in which case err_msg should be set)
179 : * err_msg: error message (NULL if none)
180 : *
181 : * Note: leaks memory, but we don't care since this is run in a short-lived
182 : * memory context.
183 : */
184 : static void
185 34 : fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
186 : int rule_number, char *filename, int lineno, HbaLine *hba,
187 : const char *err_msg)
188 : {
189 : Datum values[NUM_PG_HBA_FILE_RULES_ATTS];
190 : bool nulls[NUM_PG_HBA_FILE_RULES_ATTS];
191 : char buffer[NI_MAXHOST];
192 : HeapTuple tuple;
193 : int index;
194 : ListCell *lc;
195 : const char *typestr;
196 : const char *addrstr;
197 : const char *maskstr;
198 : ArrayType *options;
199 :
200 : Assert(tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS);
201 :
202 34 : memset(values, 0, sizeof(values));
203 34 : memset(nulls, 0, sizeof(nulls));
204 34 : index = 0;
205 :
206 : /* rule_number, nothing on error */
207 34 : if (err_msg)
208 0 : nulls[index++] = true;
209 : else
210 34 : values[index++] = Int32GetDatum(rule_number);
211 :
212 : /* file_name */
213 34 : values[index++] = CStringGetTextDatum(filename);
214 :
215 : /* line_number */
216 34 : values[index++] = Int32GetDatum(lineno);
217 :
218 34 : if (hba != NULL)
219 : {
220 : /* type */
221 : /* Avoid a default: case so compiler will warn about missing cases */
222 34 : typestr = NULL;
223 34 : switch (hba->conntype)
224 : {
225 18 : case ctLocal:
226 18 : typestr = "local";
227 18 : break;
228 16 : case ctHost:
229 16 : typestr = "host";
230 16 : break;
231 0 : case ctHostSSL:
232 0 : typestr = "hostssl";
233 0 : break;
234 0 : case ctHostNoSSL:
235 0 : typestr = "hostnossl";
236 0 : break;
237 0 : case ctHostGSS:
238 0 : typestr = "hostgssenc";
239 0 : break;
240 0 : case ctHostNoGSS:
241 0 : typestr = "hostnogssenc";
242 0 : break;
243 : }
244 34 : if (typestr)
245 34 : values[index++] = CStringGetTextDatum(typestr);
246 : else
247 0 : nulls[index++] = true;
248 :
249 : /* database */
250 34 : if (hba->databases)
251 : {
252 : /*
253 : * Flatten AuthToken list to string list. It might seem that we
254 : * should re-quote any quoted tokens, but that has been rejected
255 : * on the grounds that it makes it harder to compare the array
256 : * elements to other system catalogs. That makes entries like
257 : * "all" or "samerole" formally ambiguous ... but users who name
258 : * databases/roles that way are inflicting their own pain.
259 : */
260 34 : List *names = NIL;
261 :
262 69 : foreach(lc, hba->databases)
263 : {
264 35 : AuthToken *tok = lfirst(lc);
265 :
266 35 : names = lappend(names, tok->string);
267 : }
268 34 : values[index++] = PointerGetDatum(strlist_to_textarray(names));
269 : }
270 : else
271 0 : nulls[index++] = true;
272 :
273 : /* user */
274 34 : if (hba->roles)
275 : {
276 : /* Flatten AuthToken list to string list; see comment above */
277 34 : List *roles = NIL;
278 :
279 68 : foreach(lc, hba->roles)
280 : {
281 34 : AuthToken *tok = lfirst(lc);
282 :
283 34 : roles = lappend(roles, tok->string);
284 : }
285 34 : values[index++] = PointerGetDatum(strlist_to_textarray(roles));
286 : }
287 : else
288 0 : nulls[index++] = true;
289 :
290 : /* address and netmask */
291 : /* Avoid a default: case so compiler will warn about missing cases */
292 34 : addrstr = maskstr = NULL;
293 34 : switch (hba->ip_cmp_method)
294 : {
295 34 : case ipCmpMask:
296 34 : if (hba->hostname)
297 : {
298 0 : addrstr = hba->hostname;
299 : }
300 : else
301 : {
302 : /*
303 : * Note: if pg_getnameinfo_all fails, it'll set buffer to
304 : * "???", which we want to return.
305 : */
306 34 : if (hba->addrlen > 0)
307 : {
308 16 : if (pg_getnameinfo_all(&hba->addr, hba->addrlen,
309 : buffer, sizeof(buffer),
310 : NULL, 0,
311 : NI_NUMERICHOST) == 0)
312 16 : clean_ipv6_addr(hba->addr.ss_family, buffer);
313 16 : addrstr = pstrdup(buffer);
314 : }
315 34 : if (hba->masklen > 0)
316 : {
317 16 : if (pg_getnameinfo_all(&hba->mask, hba->masklen,
318 : buffer, sizeof(buffer),
319 : NULL, 0,
320 : NI_NUMERICHOST) == 0)
321 16 : clean_ipv6_addr(hba->mask.ss_family, buffer);
322 16 : maskstr = pstrdup(buffer);
323 : }
324 : }
325 34 : break;
326 0 : case ipCmpAll:
327 0 : addrstr = "all";
328 0 : break;
329 0 : case ipCmpSameHost:
330 0 : addrstr = "samehost";
331 0 : break;
332 0 : case ipCmpSameNet:
333 0 : addrstr = "samenet";
334 0 : break;
335 : }
336 34 : if (addrstr)
337 16 : values[index++] = CStringGetTextDatum(addrstr);
338 : else
339 18 : nulls[index++] = true;
340 34 : if (maskstr)
341 16 : values[index++] = CStringGetTextDatum(maskstr);
342 : else
343 18 : nulls[index++] = true;
344 :
345 : /* auth_method */
346 34 : values[index++] = CStringGetTextDatum(hba_authname(hba->auth_method));
347 :
348 : /* options */
349 34 : options = get_hba_options(hba);
350 34 : if (options)
351 0 : values[index++] = PointerGetDatum(options);
352 : else
353 34 : nulls[index++] = true;
354 : }
355 : else
356 : {
357 : /* no parsing result, so set relevant fields to nulls */
358 0 : memset(&nulls[3], true, (NUM_PG_HBA_FILE_RULES_ATTS - 4) * sizeof(bool));
359 : }
360 :
361 : /* error */
362 34 : if (err_msg)
363 0 : values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(err_msg);
364 : else
365 34 : nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true;
366 :
367 34 : tuple = heap_form_tuple(tupdesc, values, nulls);
368 34 : tuplestore_puttuple(tuple_store, tuple);
369 34 : }
370 :
371 : /*
372 : * fill_hba_view
373 : * Read the pg_hba.conf file and fill the tuplestore with view records.
374 : */
375 : static void
376 5 : fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
377 : {
378 : FILE *file;
379 5 : List *hba_lines = NIL;
380 : ListCell *line;
381 5 : int rule_number = 0;
382 : MemoryContext hbacxt;
383 : MemoryContext oldcxt;
384 :
385 : /*
386 : * In the unlikely event that we can't open pg_hba.conf, we throw an
387 : * error, rather than trying to report it via some sort of view entry.
388 : * (Most other error conditions should result in a message in a view
389 : * entry.)
390 : */
391 5 : file = open_auth_file(HbaFileName, ERROR, 0, NULL);
392 :
393 5 : tokenize_auth_file(HbaFileName, file, &hba_lines, DEBUG3, 0);
394 :
395 : /* Now parse all the lines */
396 5 : hbacxt = AllocSetContextCreate(CurrentMemoryContext,
397 : "hba parser context",
398 : ALLOCSET_SMALL_SIZES);
399 5 : oldcxt = MemoryContextSwitchTo(hbacxt);
400 39 : foreach(line, hba_lines)
401 : {
402 34 : TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
403 34 : HbaLine *hbaline = NULL;
404 :
405 : /* don't parse lines that already have errors */
406 34 : if (tok_line->err_msg == NULL)
407 34 : hbaline = parse_hba_line(tok_line, DEBUG3);
408 :
409 : /* No error, set a new rule number */
410 34 : if (tok_line->err_msg == NULL)
411 34 : rule_number++;
412 :
413 34 : fill_hba_line(tuple_store, tupdesc, rule_number,
414 : tok_line->file_name, tok_line->line_num, hbaline,
415 34 : tok_line->err_msg);
416 : }
417 :
418 : /* Free tokenizer memory */
419 5 : free_auth_file(file, 0);
420 : /* Free parse_hba_line memory */
421 5 : MemoryContextSwitchTo(oldcxt);
422 5 : MemoryContextDelete(hbacxt);
423 5 : }
424 :
425 : /*
426 : * pg_hba_file_rules
427 : *
428 : * SQL-accessible set-returning function to return all the entries in the
429 : * pg_hba.conf file.
430 : */
431 : Datum
432 5 : pg_hba_file_rules(PG_FUNCTION_ARGS)
433 : {
434 : ReturnSetInfo *rsi;
435 :
436 : /*
437 : * Build tuplestore to hold the result rows. We must use the Materialize
438 : * mode to be safe against HBA file changes while the cursor is open. It's
439 : * also more efficient than having to look up our current position in the
440 : * parsed list every time.
441 : */
442 5 : InitMaterializedSRF(fcinfo, 0);
443 :
444 : /* Fill the tuplestore */
445 5 : rsi = (ReturnSetInfo *) fcinfo->resultinfo;
446 5 : fill_hba_view(rsi->setResult, rsi->setDesc);
447 :
448 5 : PG_RETURN_NULL();
449 : }
450 :
451 : /* Number of columns in pg_ident_file_mappings view */
452 : #define NUM_PG_IDENT_FILE_MAPPINGS_ATTS 7
453 :
454 : /*
455 : * fill_ident_line: build one row of pg_ident_file_mappings view, add it to
456 : * tuplestore
457 : *
458 : * tuple_store: where to store data
459 : * tupdesc: tuple descriptor for the view
460 : * map_number: unique identifier among all valid maps
461 : * filename: configuration file name (must always be valid)
462 : * lineno: line number of configuration file (must always be valid)
463 : * ident: parsed line data (can be NULL, in which case err_msg should be set)
464 : * err_msg: error message (NULL if none)
465 : *
466 : * Note: leaks memory, but we don't care since this is run in a short-lived
467 : * memory context.
468 : */
469 : static void
470 8 : fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
471 : int map_number, char *filename, int lineno, IdentLine *ident,
472 : const char *err_msg)
473 : {
474 : Datum values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
475 : bool nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
476 : HeapTuple tuple;
477 : int index;
478 :
479 : Assert(tupdesc->natts == NUM_PG_IDENT_FILE_MAPPINGS_ATTS);
480 :
481 8 : memset(values, 0, sizeof(values));
482 8 : memset(nulls, 0, sizeof(nulls));
483 8 : index = 0;
484 :
485 : /* map_number, nothing on error */
486 8 : if (err_msg)
487 0 : nulls[index++] = true;
488 : else
489 8 : values[index++] = Int32GetDatum(map_number);
490 :
491 : /* file_name */
492 8 : values[index++] = CStringGetTextDatum(filename);
493 :
494 : /* line_number */
495 8 : values[index++] = Int32GetDatum(lineno);
496 :
497 8 : if (ident != NULL)
498 : {
499 8 : values[index++] = CStringGetTextDatum(ident->usermap);
500 8 : values[index++] = CStringGetTextDatum(ident->system_user->string);
501 8 : values[index++] = CStringGetTextDatum(ident->pg_user->string);
502 : }
503 : else
504 : {
505 : /* no parsing result, so set relevant fields to nulls */
506 0 : memset(&nulls[3], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 4) * sizeof(bool));
507 : }
508 :
509 : /* error */
510 8 : if (err_msg)
511 0 : values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = CStringGetTextDatum(err_msg);
512 : else
513 8 : nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = true;
514 :
515 8 : tuple = heap_form_tuple(tupdesc, values, nulls);
516 8 : tuplestore_puttuple(tuple_store, tuple);
517 8 : }
518 :
519 : /*
520 : * Read the pg_ident.conf file and fill the tuplestore with view records.
521 : */
522 : static void
523 5 : fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
524 : {
525 : FILE *file;
526 5 : List *ident_lines = NIL;
527 : ListCell *line;
528 5 : int map_number = 0;
529 : MemoryContext identcxt;
530 : MemoryContext oldcxt;
531 :
532 : /*
533 : * In the unlikely event that we can't open pg_ident.conf, we throw an
534 : * error, rather than trying to report it via some sort of view entry.
535 : * (Most other error conditions should result in a message in a view
536 : * entry.)
537 : */
538 5 : file = open_auth_file(IdentFileName, ERROR, 0, NULL);
539 :
540 5 : tokenize_auth_file(IdentFileName, file, &ident_lines, DEBUG3, 0);
541 :
542 : /* Now parse all the lines */
543 5 : identcxt = AllocSetContextCreate(CurrentMemoryContext,
544 : "ident parser context",
545 : ALLOCSET_SMALL_SIZES);
546 5 : oldcxt = MemoryContextSwitchTo(identcxt);
547 13 : foreach(line, ident_lines)
548 : {
549 8 : TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
550 8 : IdentLine *identline = NULL;
551 :
552 : /* don't parse lines that already have errors */
553 8 : if (tok_line->err_msg == NULL)
554 8 : identline = parse_ident_line(tok_line, DEBUG3);
555 :
556 : /* no error, set a new mapping number */
557 8 : if (tok_line->err_msg == NULL)
558 8 : map_number++;
559 :
560 8 : fill_ident_line(tuple_store, tupdesc, map_number,
561 : tok_line->file_name, tok_line->line_num,
562 8 : identline, tok_line->err_msg);
563 : }
564 :
565 : /* Free tokenizer memory */
566 5 : free_auth_file(file, 0);
567 : /* Free parse_ident_line memory */
568 5 : MemoryContextSwitchTo(oldcxt);
569 5 : MemoryContextDelete(identcxt);
570 5 : }
571 :
572 : /*
573 : * SQL-accessible SRF to return all the entries in the pg_ident.conf file.
574 : */
575 : Datum
576 5 : pg_ident_file_mappings(PG_FUNCTION_ARGS)
577 : {
578 : ReturnSetInfo *rsi;
579 :
580 : /*
581 : * Build tuplestore to hold the result rows. We must use the Materialize
582 : * mode to be safe against HBA file changes while the cursor is open. It's
583 : * also more efficient than having to look up our current position in the
584 : * parsed list every time.
585 : */
586 5 : InitMaterializedSRF(fcinfo, 0);
587 :
588 : /* Fill the tuplestore */
589 5 : rsi = (ReturnSetInfo *) fcinfo->resultinfo;
590 5 : fill_ident_view(rsi->setResult, rsi->setDesc);
591 :
592 5 : PG_RETURN_NULL();
593 : }
|