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