/*------------------------------------------------------------------------- * * be-secure-common.c * * common implementation-independent SSL support code * * While be-secure.c contains the interfaces that the rest of the * communications code calls, this file contains support routines that are * used by the library-specific implementations such as be-secure-openssl.c. * * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/backend/libpq/be-secure-common.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include #include "common/string.h" #include "libpq/libpq.h" #include "storage/fd.h" /* * Run ssl_passphrase_command * * prompt will be substituted for %p. is_server_start determines the loglevel * of error messages. * * The result will be put in buffer buf, which is of size size. The return * value is the length of the actual result. */ int run_ssl_passphrase_command(const char *prompt, bool is_server_start, char *buf, int size) { int loglevel = is_server_start ? ERROR : LOG; StringInfoData command; char *p; FILE *fh; int pclose_rc; size_t len = 0; Assert(prompt); Assert(size > 0); buf[0] = '\0'; initStringInfo(&command); for (p = ssl_passphrase_command; *p; p++) { if (p[0] == '%') { switch (p[1]) { case 'p': appendStringInfoString(&command, prompt); p++; break; case '%': appendStringInfoChar(&command, '%'); p++; break; default: appendStringInfoChar(&command, p[0]); } } else appendStringInfoChar(&command, p[0]); } fh = OpenPipeStream(command.data, "r"); if (fh == NULL) { ereport(loglevel, (errcode_for_file_access(), errmsg("could not execute command \"%s\": %m", command.data))); goto error; } if (!fgets(buf, size, fh)) { if (ferror(fh)) { explicit_bzero(buf, size); ereport(loglevel, (errcode_for_file_access(), errmsg("could not read from command \"%s\": %m", command.data))); goto error; } } pclose_rc = ClosePipeStream(fh); if (pclose_rc == -1) { explicit_bzero(buf, size); ereport(loglevel, (errcode_for_file_access(), errmsg("could not close pipe to external command: %m"))); goto error; } else if (pclose_rc != 0) { explicit_bzero(buf, size); ereport(loglevel, (errcode_for_file_access(), errmsg("command \"%s\" failed", command.data), errdetail_internal("%s", wait_result_to_str(pclose_rc)))); goto error; } /* strip trailing newline and carriage return */ len = pg_strip_crlf(buf); error: pfree(command.data); return len; } /* * Check permissions for SSL key files. */ bool check_ssl_key_file_permissions(const char *ssl_key_file, bool isServerStart) { int loglevel = isServerStart ? FATAL : LOG; struct stat buf; if (stat(ssl_key_file, &buf) != 0) { ereport(loglevel, (errcode_for_file_access(), errmsg("could not access private key file \"%s\": %m", ssl_key_file))); return false; } if (!S_ISREG(buf.st_mode)) { ereport(loglevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("private key file \"%s\" is not a regular file", ssl_key_file))); return false; } /* * Refuse to load key files owned by users other than us or root. * * XXX surely we can check this on Windows somehow, too. */ #if !defined(WIN32) && !defined(__CYGWIN__) if (buf.st_uid != geteuid() && buf.st_uid != 0) { ereport(loglevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("private key file \"%s\" must be owned by the database user or root", ssl_key_file))); return false; } #endif /* * Require no public access to key file. If the file is owned by us, * require mode 0600 or less. If owned by root, require 0640 or less to * allow read access through our gid, or a supplementary gid that allows * to read system-wide certificates. * * XXX temporarily suppress check when on Windows, because there may not * be proper support for Unix-y file permissions. Need to think of a * reasonable check to apply on Windows. (See also the data directory * permission check in postmaster.c) */ #if !defined(WIN32) && !defined(__CYGWIN__) if ((buf.st_uid == geteuid() && buf.st_mode & (S_IRWXG | S_IRWXO)) || (buf.st_uid == 0 && buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO))) { ereport(loglevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("private key file \"%s\" has group or world access", ssl_key_file), 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."))); return false; } #endif return true; }