Coverage Report

Created: 2025-07-03 06:49

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