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-2019, 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 "libpq/libpq.h"
26#include "storage/fd.h"
27
28/*
29 * Run ssl_passphrase_command
30 *
31 * prompt will be substituted for %p. is_server_start determines the loglevel
32 * of error messages.
33 *
34 * The result will be put in buffer buf, which is of size size. The return
35 * value is the length of the actual result.
36 */
37int
38run_ssl_passphrase_command(const char *prompt, bool is_server_start, char *buf, int size)
39{
40 int loglevel = is_server_start ? ERROR : LOG;
41 StringInfoData command;
42 char *p;
43 FILE *fh;
44 int pclose_rc;
45 size_t len = 0;
46
47 Assert(prompt);
48 Assert(size > 0);
49 buf[0] = '\0';
50
51 initStringInfo(&command);
52
53 for (p = ssl_passphrase_command; *p; p++)
54 {
55 if (p[0] == '%')
56 {
57 switch (p[1])
58 {
59 case 'p':
60 appendStringInfoString(&command, prompt);
61 p++;
62 break;
63 case '%':
64 appendStringInfoChar(&command, '%');
65 p++;
66 break;
67 default:
68 appendStringInfoChar(&command, p[0]);
69 }
70 }
71 else
72 appendStringInfoChar(&command, p[0]);
73 }
74
75 fh = OpenPipeStream(command.data, "r");
76 if (fh == NULL)
77 {
78 ereport(loglevel,
79 (errcode_for_file_access(),
80 errmsg("could not execute command \"%s\": %m",
81 command.data)));
82 goto error;
83 }
84
85 if (!fgets(buf, size, fh))
86 {
87 if (ferror(fh))
88 {
89 ereport(loglevel,
90 (errcode_for_file_access(),
91 errmsg("could not read from command \"%s\": %m",
92 command.data)));
93 goto error;
94 }
95 }
96
97 pclose_rc = ClosePipeStream(fh);
98 if (pclose_rc == -1)
99 {
100 ereport(loglevel,
101 (errcode_for_file_access(),
102 errmsg("could not close pipe to external command: %m")));
103 goto error;
104 }
105 else if (pclose_rc != 0)
106 {
107 ereport(loglevel,
108 (errcode_for_file_access(),
109 errmsg("command \"%s\" failed",
110 command.data),
111 errdetail_internal("%s", wait_result_to_str(pclose_rc))));
112 goto error;
113 }
114
115 /* strip trailing newline, including \r in case we're on Windows */
116 len = strlen(buf);
117 while (len > 0 && (buf[len - 1] == '\n' ||
118 buf[len - 1] == '\r'))
119 buf[--len] = '\0';
120
121error:
122 pfree(command.data);
123 return len;
124}
125
126
127/*
128 * Check permissions for SSL key files.
129 */
130bool
131check_ssl_key_file_permissions(const char *ssl_key_file, bool isServerStart)
132{
133 int loglevel = isServerStart ? FATAL : LOG;
134 struct stat buf;
135
136 if (stat(ssl_key_file, &buf) != 0)
137 {
138 ereport(loglevel,
139 (errcode_for_file_access(),
140 errmsg("could not access private key file \"%s\": %m",
141 ssl_key_file)));
142 return false;
143 }
144
145 if (!S_ISREG(buf.st_mode))
146 {
147 ereport(loglevel,
148 (errcode(ERRCODE_CONFIG_FILE_ERROR),
149 errmsg("private key file \"%s\" is not a regular file",
150 ssl_key_file)));
151 return false;
152 }
153
154 /*
155 * Refuse to load key files owned by users other than us or root.
156 *
157 * XXX surely we can check this on Windows somehow, too.
158 */
159#if !defined(WIN32) && !defined(__CYGWIN__)
160 if (buf.st_uid != geteuid() && buf.st_uid != 0)
161 {
162 ereport(loglevel,
163 (errcode(ERRCODE_CONFIG_FILE_ERROR),
164 errmsg("private key file \"%s\" must be owned by the database user or root",
165 ssl_key_file)));
166 return false;
167 }
168#endif
169
170 /*
171 * Require no public access to key file. If the file is owned by us,
172 * require mode 0600 or less. If owned by root, require 0640 or less to
173 * allow read access through our gid, or a supplementary gid that allows
174 * to read system-wide certificates.
175 *
176 * XXX temporarily suppress check when on Windows, because there may not
177 * be proper support for Unix-y file permissions. Need to think of a
178 * reasonable check to apply on Windows. (See also the data directory
179 * permission check in postmaster.c)
180 */
181#if !defined(WIN32) && !defined(__CYGWIN__)
182 if ((buf.st_uid == geteuid() && buf.st_mode & (S_IRWXG | S_IRWXO)) ||
183 (buf.st_uid == 0 && buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
184 {
185 ereport(loglevel,
186 (errcode(ERRCODE_CONFIG_FILE_ERROR),
187 errmsg("private key file \"%s\" has group or world access",
188 ssl_key_file),
189 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.")));
190 return false;
191 }
192#endif
193
194 return true;
195}
196