PostgreSQL Source Code git master
Loading...
Searching...
No Matches
be-secure-common.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * be-secure-common.c
4 *
5 * common implementation-independent SSL support code
6 *
7 * While be-secure.c contains the interfaces that the rest of the
8 * communications code calls, this file contains support routines that are
9 * used by the library-specific implementations such as be-secure-openssl.c.
10 *
11 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
12 * Portions Copyright (c) 1994, Regents of the University of California
13 *
14 * IDENTIFICATION
15 * src/backend/libpq/be-secure-common.c
16 *
17 *-------------------------------------------------------------------------
18 */
19
20#include "postgres.h"
21
22#include <sys/stat.h>
23#include <unistd.h>
24
25#include "common/percentrepl.h"
26#include "common/string.h"
27#include "libpq/libpq.h"
28#include "storage/fd.h"
29#include "utils/builtins.h"
30#include "utils/guc.h"
31
33
34/*
35 * Run ssl_passphrase_command
36 *
37 * prompt will be substituted for %p. is_server_start determines the loglevel
38 * of error messages from executing the command, the loglevel for failures in
39 * param substitution will be ERROR regardless of is_server_start. The actual
40 * command used depends on the configuration for the current host.
41 *
42 * The result will be put in buffer buf, which is of size size. The return
43 * value is the length of the actual result.
44 */
45int
46run_ssl_passphrase_command(const char *cmd, const char *prompt,
47 bool is_server_start, char *buf, int size)
48{
50 char *command;
51 FILE *fh;
52 int pclose_rc;
53 size_t len = 0;
54
56 Assert(size > 0);
57 buf[0] = '\0';
58
59 command = replace_percent_placeholders(cmd, "ssl_passphrase_command", "p", prompt);
60
61 fh = OpenPipeStream(command, "r");
62 if (fh == NULL)
63 {
66 errmsg("could not execute command \"%s\": %m",
67 command)));
68 goto error;
69 }
70
71 if (!fgets(buf, size, fh))
72 {
73 if (ferror(fh))
74 {
75 explicit_bzero(buf, size);
78 errmsg("could not read from command \"%s\": %m",
79 command)));
80 goto error;
81 }
82 }
83
85 if (pclose_rc == -1)
86 {
87 explicit_bzero(buf, size);
90 errmsg("could not close pipe to external command: %m")));
91 goto error;
92 }
93 else if (pclose_rc != 0)
94 {
95 char *reason;
96
97 explicit_bzero(buf, size);
101 errmsg("command \"%s\" failed",
102 command),
103 errdetail_internal("%s", reason)));
104 pfree(reason);
105 goto error;
106 }
107
108 /* strip trailing newline and carriage return */
110
111error:
112 pfree(command);
113 return len;
114}
115
116
117/*
118 * Check permissions for SSL key files.
119 */
120bool
122{
124 struct stat buf;
125
126 if (stat(ssl_key_file, &buf) != 0)
127 {
130 errmsg("could not access private key file \"%s\": %m",
131 ssl_key_file)));
132 return false;
133 }
134
135 /* Key file must be a regular file */
136 if (!S_ISREG(buf.st_mode))
137 {
140 errmsg("private key file \"%s\" is not a regular file",
141 ssl_key_file)));
142 return false;
143 }
144
145 /*
146 * Refuse to load key files owned by users other than us or root, and
147 * require no public access to the key file. If the file is owned by us,
148 * require mode 0600 or less. If owned by root, require 0640 or less to
149 * allow read access through either our gid or a supplementary gid that
150 * allows us to read system-wide certificates.
151 *
152 * Note that roughly similar checks are performed in
153 * src/interfaces/libpq/fe-secure-openssl.c so any changes here may need
154 * to be made there as well. The environment is different though; this
155 * code can assume that we're not running as root.
156 *
157 * Ideally we would do similar permissions checks on Windows, but it is
158 * not clear how that would work since Unix-style permissions may not be
159 * available.
160 */
161#if !defined(WIN32) && !defined(__CYGWIN__)
162 if (buf.st_uid != geteuid() && buf.st_uid != 0)
163 {
166 errmsg("private key file \"%s\" must be owned by the database user or root",
167 ssl_key_file)));
168 return false;
169 }
170
171 if ((buf.st_uid == geteuid() && buf.st_mode & (S_IRWXG | S_IRWXO)) ||
172 (buf.st_uid == 0 && buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
173 {
176 errmsg("private key file \"%s\" has group or world access",
178 errdetail("File must have permissions u=rw (0600) or less if owned by the database user, or permissions u=rw,g=r (0640) or less if owned by root.")));
179 return false;
180 }
181#endif
182
183 return true;
184}
185
186/*
187 * parse_hosts_line
188 *
189 * Parses a loaded line from the pg_hosts.conf configuration and pulls out the
190 * hostname, certificate, key and CA parts in order to build an SNI config in
191 * the TLS backend. Validation of the parsed values is left for the TLS backend
192 * to implement.
193 */
194static HostsLine *
196{
198 List *tokens;
199 ListCell *field;
201
202 parsedline = palloc0(sizeof(HostsLine));
203 parsedline->sourcefile = pstrdup(tok_line->file_name);
204 parsedline->linenumber = tok_line->line_num;
205 parsedline->rawline = pstrdup(tok_line->raw_line);
206 parsedline->hostnames = NIL;
207
208 /* Initialize optional fields */
209 parsedline->ssl_passphrase_cmd = NULL;
210 parsedline->ssl_passphrase_reload = false;
211
212 /* Hostname */
213 field = list_head(tok_line->fields);
214 tokens = lfirst(field);
216 {
217 if ((tokens->length > 1) &&
218 (strcmp(hostname->string, "*") == 0 || strcmp(hostname->string, "/no_sni/") == 0))
219 {
220 ereport(elevel,
222 errmsg("default and non-SNI entries cannot be mixed with other entries"),
223 errcontext("line %d of configuration file \"%s\"",
224 tok_line->line_num, tok_line->file_name));
225 return NULL;
226 }
227
228 parsedline->hostnames = lappend(parsedline->hostnames, pstrdup(hostname->string));
229 }
230
231 /* SSL Certificate (Required) */
232 field = lnext(tok_line->fields, field);
233 if (!field)
234 {
235 ereport(elevel,
237 errmsg("missing entry at end of line"),
238 errcontext("line %d of configuration file \"%s\"",
239 tok_line->line_num, tok_line->file_name));
240 return NULL;
241 }
242 tokens = lfirst(field);
243 if (tokens->length > 1)
244 {
245 ereport(elevel,
247 errmsg("multiple values specified for SSL certificate"),
248 errcontext("line %d of configuration file \"%s\"",
249 tok_line->line_num, tok_line->file_name));
250 return NULL;
251 }
253 parsedline->ssl_cert = pstrdup(token->string);
254
255 /* SSL key (Required) */
256 field = lnext(tok_line->fields, field);
257 if (!field)
258 {
259 ereport(elevel,
261 errmsg("missing entry at end of line"),
262 errcontext("line %d of configuration file \"%s\"",
263 tok_line->line_num, tok_line->file_name));
264 return NULL;
265 }
266 tokens = lfirst(field);
267 if (tokens->length > 1)
268 {
269 ereport(elevel,
271 errmsg("multiple values specified for SSL key"),
272 errcontext("line %d of configuration file \"%s\"",
273 tok_line->line_num, tok_line->file_name));
274 return NULL;
275 }
277 parsedline->ssl_key = pstrdup(token->string);
278
279 /* SSL CA (optional) */
280 field = lnext(tok_line->fields, field);
281 if (!field)
282 return parsedline;
283 tokens = lfirst(field);
284 if (tokens->length > 1)
285 {
286 ereport(elevel,
288 errmsg("multiple values specified for SSL CA"),
289 errcontext("line %d of configuration file \"%s\"",
290 tok_line->line_num, tok_line->file_name));
291 return NULL;
292 }
294 parsedline->ssl_ca = pstrdup(token->string);
295
296 /* SSL Passphrase Command (optional) */
297 field = lnext(tok_line->fields, field);
298 if (field)
299 {
300 tokens = lfirst(field);
301 if (tokens->length > 1)
302 {
303 ereport(elevel,
305 errmsg("multiple values specified for SSL passphrase command"),
306 errcontext("line %d of configuration file \"%s\"",
307 tok_line->line_num, tok_line->file_name));
308 return NULL;
309 }
311 parsedline->ssl_passphrase_cmd = pstrdup(token->string);
312
313 /*
314 * SSL Passphrase Command support reload (optional). This field is
315 * only supported if there was a passphrase command parsed first, so
316 * nest it under the previous token.
317 */
318 field = lnext(tok_line->fields, field);
319 if (field)
320 {
321 tokens = lfirst(field);
323
324 /*
325 * There should be no more tokens after this, if there are break
326 * parsing and report error to avoid silently accepting incorrect
327 * config.
328 */
329 if (lnext(tok_line->fields, field))
330 {
331 ereport(elevel,
333 errmsg("extra fields at end of line"),
334 errcontext("line %d of configuration file \"%s\"",
335 tok_line->line_num, tok_line->file_name));
336 return NULL;
337 }
338
339 if (tokens->length > 1 || !parse_bool(token->string, &parsedline->ssl_passphrase_reload))
340 {
341 ereport(elevel,
343 errmsg("incorrect syntax for boolean value SSL_passphrase_cmd_reload"),
344 errcontext("line %d of configuration file \"%s\"",
345 tok_line->line_num, tok_line->file_name));
346 return NULL;
347 }
348 }
349 }
350
351 return parsedline;
352}
353
354/*
355 * load_hosts
356 *
357 * Reads and parses the pg_hosts.conf configuration file and passes back a List
358 * of HostsLine elements containing the parsed lines, or NIL in case of an empty
359 * file. The list is returned in the hosts parameter. The function will return
360 * a HostsFileLoadResult value detailing the result of the operation. When
361 * the hosts configuration failed to load, the err_msg variable may have more
362 * information in case it was passed as non-NULL.
363 */
364int
365load_hosts(List **hosts, char **err_msg)
366{
367 FILE *file;
368 ListCell *line;
372 bool ok = true;
373
374 /*
375 * If we cannot return results then error out immediately. This implies
376 * API misuse or a similar kind of programmer error.
377 */
378 if (!hosts)
379 {
380 if (err_msg)
381 *err_msg = psprintf("cannot load config from \"%s\", return variable missing",
384 }
385 *hosts = NIL;
386
387 /*
388 * This is not an auth file per se, but it is using the same file format
389 * as the pg_hba and pg_ident files and thus the same code infrastructure.
390 * A future TODO might be to rename the supporting code with a more
391 * generic name?
392 */
393 file = open_auth_file(HostsFileName, LOG, 0, err_msg);
394 if (file == NULL)
395 {
396 if (errno == ENOENT)
397 return HOSTSFILE_MISSING;
398
400 }
401
403
404 foreach(line, hosts_lines)
405 {
407
408 /*
409 * Mark processing as not-ok in case lines are found with errors in
410 * tokenization (.err_msg is set) or during parsing.
411 */
412 if ((tok_line->err_msg != NULL) ||
414 {
415 ok = false;
416 continue;
417 }
418
420 }
421
422 /* Free memory from tokenizer */
423 free_auth_file(file, 0);
425
426 if (!ok)
427 {
428 if (err_msg)
429 *err_msg = psprintf("loading config from \"%s\" failed due to parsing error",
432 }
433
434 if (parsed_lines == NIL)
435 return HOSTSFILE_EMPTY;
436
437 return HOSTSFILE_LOAD_OK;
438}
bool check_ssl_key_file_permissions(const char *ssl_key_file, bool isServerStart)
static HostsLine * parse_hosts_line(TokenizedAuthLine *tok_line, int elevel)
int load_hosts(List **hosts, char **err_msg)
int run_ssl_passphrase_command(const char *cmd, const char *prompt, bool is_server_start, char *buf, int size)
char * ssl_key_file
Definition be-secure.c:39
bool parse_bool(const char *value, bool *result)
Definition bool.c:31
#define Assert(condition)
Definition c.h:945
int errcode_for_file_access(void)
Definition elog.c:897
int errcode(int sqlerrcode)
Definition elog.c:874
#define LOG
Definition elog.h:31
int int errdetail_internal(const char *fmt,...) pg_attribute_printf(1
#define errcontext
Definition elog.h:198
int errdetail(const char *fmt,...) pg_attribute_printf(1
#define FATAL
Definition elog.h:41
#define ERROR
Definition elog.h:39
#define ereport(elevel,...)
Definition elog.h:150
FILE * OpenPipeStream(const char *command, const char *mode)
Definition fd.c:2731
int ClosePipeStream(FILE *file)
Definition fd.c:3039
char * HostsFileName
Definition guc_tables.c:568
void free_auth_file(FILE *file, int depth)
Definition hba.c:569
void tokenize_auth_file(const char *filename, FILE *file, List **tok_lines, int elevel, int depth)
Definition hba.c:688
FILE * open_auth_file(const char *filename, int elevel, int depth, char **err_msg)
Definition hba.c:594
@ HOSTSFILE_MISSING
Definition hba.h:180
@ HOSTSFILE_LOAD_OK
Definition hba.h:177
@ HOSTSFILE_EMPTY
Definition hba.h:179
@ HOSTSFILE_LOAD_FAILED
Definition hba.h:178
#define newline
#define token
List * lappend(List *list, void *datum)
Definition list.c:339
char * pstrdup(const char *in)
Definition mcxt.c:1781
void pfree(void *pointer)
Definition mcxt.c:1616
void * palloc0(Size size)
Definition mcxt.c:1417
static char * errmsg
char * replace_percent_placeholders(const char *instr, const char *param_name, const char *letters,...)
Definition percentrepl.c:59
const void size_t len
#define lfirst(lc)
Definition pg_list.h:172
#define NIL
Definition pg_list.h:68
#define foreach_ptr(type, var, lst)
Definition pg_list.h:469
#define linitial(l)
Definition pg_list.h:178
static ListCell * list_head(const List *l)
Definition pg_list.h:128
static ListCell * lnext(const List *l, const ListCell *c)
Definition pg_list.h:343
static char * hostname
Definition pg_regress.c:114
static char buf[DEFAULT_XLOG_SEG_SIZE]
void explicit_bzero(void *buf, size_t len)
static int fb(int x)
char * psprintf(const char *fmt,...)
Definition psprintf.c:43
static void error(void)
int pg_strip_crlf(char *str)
Definition string.c:154
Definition pg_list.h:54
char * wait_result_to_str(int exitstatus)
Definition wait_error.c:33
#define S_IXGRP
Definition win32_port.h:297
#define stat
Definition win32_port.h:74
#define S_IRWXG
Definition win32_port.h:300
#define S_IRWXO
Definition win32_port.h:312
#define S_ISREG(m)
Definition win32_port.h:318
#define S_IWGRP
Definition win32_port.h:294