Coverage Report

Created: 2026-11-09 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/utils/adt/hbafuncs.c
Line
Count
Source
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
get_hba_options(HbaLine *hba)
55
0
{
56
0
  int     noptions;
57
0
  Datum   options[MAX_HBA_OPTIONS];
58
59
0
  noptions = 0;
60
61
0
  if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI)
62
0
  {
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
0
  }
71
72
0
  if (hba->usermap)
73
0
    options[noptions++] =
74
0
      CStringGetTextDatum(psprintf("map=%s", hba->usermap));
75
76
0
  if (hba->clientcert != clientCertOff)
77
0
    options[noptions++] =
78
0
      CStringGetTextDatum(psprintf("clientcert=%s", (hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full"));
79
80
0
  if (hba->pamservice)
81
0
    options[noptions++] =
82
0
      CStringGetTextDatum(psprintf("pamservice=%s", hba->pamservice));
83
84
0
  if (hba->auth_method == uaLDAP)
85
0
  {
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
0
                       hba->ldapbindpasswd));
122
123
0
    if (hba->ldapsearchattribute)
124
0
      options[noptions++] =
125
0
        CStringGetTextDatum(psprintf("ldapsearchattribute=%s",
126
0
                       hba->ldapsearchattribute));
127
128
0
    if (hba->ldapsearchfilter)
129
0
      options[noptions++] =
130
0
        CStringGetTextDatum(psprintf("ldapsearchfilter=%s",
131
0
                       hba->ldapsearchfilter));
132
133
0
    if (hba->ldapscope)
134
0
      options[noptions++] =
135
0
        CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
136
0
  }
137
138
0
  if (hba->auth_method == uaOAuth)
139
0
  {
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
0
  }
156
157
  /* If you add more options, consider increasing MAX_HBA_OPTIONS. */
158
0
  Assert(noptions <= MAX_HBA_OPTIONS);
159
160
0
  if (noptions > 0)
161
0
    return construct_array_builtin(options, noptions, TEXTOID);
162
0
  else
163
0
    return NULL;
164
0
}
165
166
/* Number of columns in pg_hba_file_rules view */
167
0
#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
fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
186
        int rule_number, char *filename, int lineno, HbaLine *hba,
187
        const char *err_msg)
188
0
{
189
0
  Datum   values[NUM_PG_HBA_FILE_RULES_ATTS];
190
0
  bool    nulls[NUM_PG_HBA_FILE_RULES_ATTS];
191
0
  char    buffer[NI_MAXHOST];
192
0
  HeapTuple tuple;
193
0
  int     index;
194
0
  ListCell   *lc;
195
0
  const char *typestr;
196
0
  const char *addrstr;
197
0
  const char *maskstr;
198
0
  ArrayType  *options;
199
200
0
  Assert(tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS);
201
202
0
  memset(values, 0, sizeof(values));
203
0
  memset(nulls, 0, sizeof(nulls));
204
0
  index = 0;
205
206
  /* rule_number, nothing on error */
207
0
  if (err_msg)
208
0
    nulls[index++] = true;
209
0
  else
210
0
    values[index++] = Int32GetDatum(rule_number);
211
212
  /* file_name */
213
0
  values[index++] = CStringGetTextDatum(filename);
214
215
  /* line_number */
216
0
  values[index++] = Int32GetDatum(lineno);
217
218
0
  if (hba != NULL)
219
0
  {
220
    /* type */
221
    /* Avoid a default: case so compiler will warn about missing cases */
222
0
    typestr = NULL;
223
0
    switch (hba->conntype)
224
0
    {
225
0
      case ctLocal:
226
0
        typestr = "local";
227
0
        break;
228
0
      case ctHost:
229
0
        typestr = "host";
230
0
        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
0
    }
244
0
    if (typestr)
245
0
      values[index++] = CStringGetTextDatum(typestr);
246
0
    else
247
0
      nulls[index++] = true;
248
249
    /* database */
250
0
    if (hba->databases)
251
0
    {
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
0
      List     *names = NIL;
261
262
0
      foreach(lc, hba->databases)
263
0
      {
264
0
        AuthToken  *tok = lfirst(lc);
265
266
0
        names = lappend(names, tok->string);
267
0
      }
268
0
      values[index++] = PointerGetDatum(strlist_to_textarray(names));
269
0
    }
270
0
    else
271
0
      nulls[index++] = true;
272
273
    /* user */
274
0
    if (hba->roles)
275
0
    {
276
      /* Flatten AuthToken list to string list; see comment above */
277
0
      List     *roles = NIL;
278
279
0
      foreach(lc, hba->roles)
280
0
      {
281
0
        AuthToken  *tok = lfirst(lc);
282
283
0
        roles = lappend(roles, tok->string);
284
0
      }
285
0
      values[index++] = PointerGetDatum(strlist_to_textarray(roles));
286
0
    }
287
0
    else
288
0
      nulls[index++] = true;
289
290
    /* address and netmask */
291
    /* Avoid a default: case so compiler will warn about missing cases */
292
0
    addrstr = maskstr = NULL;
293
0
    switch (hba->ip_cmp_method)
294
0
    {
295
0
      case ipCmpMask:
296
0
        if (hba->hostname)
297
0
        {
298
0
          addrstr = hba->hostname;
299
0
        }
300
0
        else
301
0
        {
302
          /*
303
           * Note: if pg_getnameinfo_all fails, it'll set buffer to
304
           * "???", which we want to return.
305
           */
306
0
          if (hba->addrlen > 0)
307
0
          {
308
0
            if (pg_getnameinfo_all(&hba->addr, hba->addrlen,
309
0
                         buffer, sizeof(buffer),
310
0
                         NULL, 0,
311
0
                         NI_NUMERICHOST) == 0)
312
0
              clean_ipv6_addr(hba->addr.ss_family, buffer);
313
0
            addrstr = pstrdup(buffer);
314
0
          }
315
0
          if (hba->masklen > 0)
316
0
          {
317
0
            if (pg_getnameinfo_all(&hba->mask, hba->masklen,
318
0
                         buffer, sizeof(buffer),
319
0
                         NULL, 0,
320
0
                         NI_NUMERICHOST) == 0)
321
0
              clean_ipv6_addr(hba->mask.ss_family, buffer);
322
0
            maskstr = pstrdup(buffer);
323
0
          }
324
0
        }
325
0
        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
0
    }
336
0
    if (addrstr)
337
0
      values[index++] = CStringGetTextDatum(addrstr);
338
0
    else
339
0
      nulls[index++] = true;
340
0
    if (maskstr)
341
0
      values[index++] = CStringGetTextDatum(maskstr);
342
0
    else
343
0
      nulls[index++] = true;
344
345
    /* auth_method */
346
0
    values[index++] = CStringGetTextDatum(hba_authname(hba->auth_method));
347
348
    /* options */
349
0
    options = get_hba_options(hba);
350
0
    if (options)
351
0
      values[index++] = PointerGetDatum(options);
352
0
    else
353
0
      nulls[index++] = true;
354
0
  }
355
0
  else
356
0
  {
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
0
  }
360
361
  /* error */
362
0
  if (err_msg)
363
0
    values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(err_msg);
364
0
  else
365
0
    nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true;
366
367
0
  tuple = heap_form_tuple(tupdesc, values, nulls);
368
0
  tuplestore_puttuple(tuple_store, tuple);
369
0
}
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
fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
377
0
{
378
0
  FILE     *file;
379
0
  List     *hba_lines = NIL;
380
0
  ListCell   *line;
381
0
  int     rule_number = 0;
382
0
  MemoryContext hbacxt;
383
0
  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
0
  file = open_auth_file(HbaFileName, ERROR, 0, NULL);
392
393
0
  tokenize_auth_file(HbaFileName, file, &hba_lines, DEBUG3, 0);
394
395
  /* Now parse all the lines */
396
0
  hbacxt = AllocSetContextCreate(CurrentMemoryContext,
397
0
                   "hba parser context",
398
0
                   ALLOCSET_SMALL_SIZES);
399
0
  oldcxt = MemoryContextSwitchTo(hbacxt);
400
0
  foreach(line, hba_lines)
401
0
  {
402
0
    TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
403
0
    HbaLine    *hbaline = NULL;
404
405
    /* don't parse lines that already have errors */
406
0
    if (tok_line->err_msg == NULL)
407
0
      hbaline = parse_hba_line(tok_line, DEBUG3);
408
409
    /* No error, set a new rule number */
410
0
    if (tok_line->err_msg == NULL)
411
0
      rule_number++;
412
413
0
    fill_hba_line(tuple_store, tupdesc, rule_number,
414
0
            tok_line->file_name, tok_line->line_num, hbaline,
415
0
            tok_line->err_msg);
416
0
  }
417
418
  /* Free tokenizer memory */
419
0
  free_auth_file(file, 0);
420
  /* Free parse_hba_line memory */
421
0
  MemoryContextSwitchTo(oldcxt);
422
0
  MemoryContextDelete(hbacxt);
423
0
}
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
pg_hba_file_rules(PG_FUNCTION_ARGS)
433
0
{
434
0
  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
0
  InitMaterializedSRF(fcinfo, 0);
443
444
  /* Fill the tuplestore */
445
0
  rsi = (ReturnSetInfo *) fcinfo->resultinfo;
446
0
  fill_hba_view(rsi->setResult, rsi->setDesc);
447
448
0
  PG_RETURN_NULL();
449
0
}
450
451
/* Number of columns in pg_ident_file_mappings view */
452
0
#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
fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
471
        int map_number, char *filename, int lineno, IdentLine *ident,
472
        const char *err_msg)
473
0
{
474
0
  Datum   values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
475
0
  bool    nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
476
0
  HeapTuple tuple;
477
0
  int     index;
478
479
0
  Assert(tupdesc->natts == NUM_PG_IDENT_FILE_MAPPINGS_ATTS);
480
481
0
  memset(values, 0, sizeof(values));
482
0
  memset(nulls, 0, sizeof(nulls));
483
0
  index = 0;
484
485
  /* map_number, nothing on error */
486
0
  if (err_msg)
487
0
    nulls[index++] = true;
488
0
  else
489
0
    values[index++] = Int32GetDatum(map_number);
490
491
  /* file_name */
492
0
  values[index++] = CStringGetTextDatum(filename);
493
494
  /* line_number */
495
0
  values[index++] = Int32GetDatum(lineno);
496
497
0
  if (ident != NULL)
498
0
  {
499
0
    values[index++] = CStringGetTextDatum(ident->usermap);
500
0
    values[index++] = CStringGetTextDatum(ident->system_user->string);
501
0
    values[index++] = CStringGetTextDatum(ident->pg_user->string);
502
0
  }
503
0
  else
504
0
  {
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
0
  }
508
509
  /* error */
510
0
  if (err_msg)
511
0
    values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = CStringGetTextDatum(err_msg);
512
0
  else
513
0
    nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = true;
514
515
0
  tuple = heap_form_tuple(tupdesc, values, nulls);
516
0
  tuplestore_puttuple(tuple_store, tuple);
517
0
}
518
519
/*
520
 * Read the pg_ident.conf file and fill the tuplestore with view records.
521
 */
522
static void
523
fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
524
0
{
525
0
  FILE     *file;
526
0
  List     *ident_lines = NIL;
527
0
  ListCell   *line;
528
0
  int     map_number = 0;
529
0
  MemoryContext identcxt;
530
0
  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
0
  file = open_auth_file(IdentFileName, ERROR, 0, NULL);
539
540
0
  tokenize_auth_file(IdentFileName, file, &ident_lines, DEBUG3, 0);
541
542
  /* Now parse all the lines */
543
0
  identcxt = AllocSetContextCreate(CurrentMemoryContext,
544
0
                   "ident parser context",
545
0
                   ALLOCSET_SMALL_SIZES);
546
0
  oldcxt = MemoryContextSwitchTo(identcxt);
547
0
  foreach(line, ident_lines)
548
0
  {
549
0
    TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
550
0
    IdentLine  *identline = NULL;
551
552
    /* don't parse lines that already have errors */
553
0
    if (tok_line->err_msg == NULL)
554
0
      identline = parse_ident_line(tok_line, DEBUG3);
555
556
    /* no error, set a new mapping number */
557
0
    if (tok_line->err_msg == NULL)
558
0
      map_number++;
559
560
0
    fill_ident_line(tuple_store, tupdesc, map_number,
561
0
            tok_line->file_name, tok_line->line_num,
562
0
            identline, tok_line->err_msg);
563
0
  }
564
565
  /* Free tokenizer memory */
566
0
  free_auth_file(file, 0);
567
  /* Free parse_ident_line memory */
568
0
  MemoryContextSwitchTo(oldcxt);
569
0
  MemoryContextDelete(identcxt);
570
0
}
571
572
/*
573
 * SQL-accessible SRF to return all the entries in the pg_ident.conf file.
574
 */
575
Datum
576
pg_ident_file_mappings(PG_FUNCTION_ARGS)
577
0
{
578
0
  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
0
  InitMaterializedSRF(fcinfo, 0);
587
588
  /* Fill the tuplestore */
589
0
  rsi = (ReturnSetInfo *) fcinfo->resultinfo;
590
0
  fill_ident_view(rsi->setResult, rsi->setDesc);
591
592
  PG_RETURN_NULL();
593
0
}