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