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