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