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-2018, 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  */
37 int
38 run_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 
121 error:
122 	pfree(command.data);
123 	return len;
124 }
125 
126 
127 /*
128  * Check permissions for SSL key files.
129  */
130 bool
131 check_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