/
githubmirror
/
postgres
Обзор
Документация
Войти
/
githubmirror
/
postgres
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/backend/libpq/be-secure-common.c
438 строк
12 KB
Tom Lane
Declare load_hosts() as returning HostsFileLoadResult.
05 май 2026, 01:33
05 май 2026, 01:33
93da297
Код
Авторство
О чём код?
/*------------------------------------------------------------------------- * * 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-2026, 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 <sys/stat.h> #include <unistd.h> #include "common/percentrepl.h" #include "common/string.h" #include "libpq/libpq.h" #include "storage/fd.h" #include "utils/builtins.h" #include "utils/guc.h" static HostsLine *parse_hosts_line(TokenizedAuthLine *tok_line, int elevel); /* * Run ssl_passphrase_command * * prompt will be substituted for %p. is_server_start determines the loglevel * of error messages from executing the command, the loglevel for failures in * param substitution will be ERROR regardless of is_server_start. The actual * command used depends on the configuration for the current host. * * 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 *cmd, const char *prompt, bool is_server_start, char *buf, int size) { int loglevel = is_server_start ? ERROR : LOG; char *command; FILE *fh; int pclose_rc; size_t len = 0; Assert(prompt); Assert(size > 0); buf[0] = '\0'; command = replace_percent_placeholders(cmd, "ssl_passphrase_command", "p", prompt); fh = OpenPipeStream(command, "r"); if (fh == NULL) { ereport(loglevel, (errcode_for_file_access(), errmsg("could not execute command \"%s\": %m", command))); 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))); 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) { char *reason; explicit_bzero(buf, size); reason = wait_result_to_str(pclose_rc); ereport(loglevel, (errcode_for_file_access(), errmsg("command \"%s\" failed", command), errdetail_internal("%s", reason))); pfree(reason); goto error; } /* strip trailing newline and carriage return */ len = pg_strip_crlf(buf); error: pfree(command); 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; } /* Key file must be a regular file */ 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, and * require no public access to the 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 either our gid or a supplementary gid that * allows us to read system-wide certificates. * * Note that roughly similar checks are performed in * src/interfaces/libpq/fe-secure-openssl.c so any changes here may need * to be made there as well. The environment is different though; this * code can assume that we're not running as root. * * Ideally we would do similar permissions checks on Windows, but it is * not clear how that would work since Unix-style permissions may not be * available. */ #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; } 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; } /* * parse_hosts_line * * Parses a loaded line from the pg_hosts.conf configuration and pulls out the * hostname, certificate, key and CA parts in order to build an SNI config in * the TLS backend. Validation of the parsed values is left for the TLS backend * to implement. */ static HostsLine * parse_hosts_line(TokenizedAuthLine *tok_line, int elevel) { HostsLine *parsedline; List *tokens; ListCell *field; AuthToken *token; parsedline = palloc0(sizeof(HostsLine)); parsedline->sourcefile = pstrdup(tok_line->file_name); parsedline->linenumber = tok_line->line_num; parsedline->rawline = pstrdup(tok_line->raw_line); parsedline->hostnames = NIL; /* Initialize optional fields */ parsedline->ssl_passphrase_cmd = NULL; parsedline->ssl_passphrase_reload = false; /* Hostname */ field = list_head(tok_line->fields); tokens = lfirst(field); foreach_ptr(AuthToken, hostname, tokens) { if ((tokens->length > 1) && (strcmp(hostname->string, "*") == 0 || strcmp(hostname->string, "/no_sni/") == 0)) { ereport(elevel, errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("default and non-SNI entries cannot be mixed with other entries"), errcontext("line %d of configuration file \"%s\"", tok_line->line_num, tok_line->file_name)); return NULL; } parsedline->hostnames = lappend(parsedline->hostnames, pstrdup(hostname->string)); } /* SSL Certificate (Required) */ field = lnext(tok_line->fields, field); if (!field) { ereport(elevel, errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("missing entry at end of line"), errcontext("line %d of configuration file \"%s\"", tok_line->line_num, tok_line->file_name)); return NULL; } tokens = lfirst(field); if (tokens->length > 1) { ereport(elevel, errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("multiple values specified for SSL certificate"), errcontext("line %d of configuration file \"%s\"", tok_line->line_num, tok_line->file_name)); return NULL; } token = linitial(tokens); parsedline->ssl_cert = pstrdup(token->string); /* SSL key (Required) */ field = lnext(tok_line->fields, field); if (!field) { ereport(elevel, errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("missing entry at end of line"), errcontext("line %d of configuration file \"%s\"", tok_line->line_num, tok_line->file_name)); return NULL; } tokens = lfirst(field); if (tokens->length > 1) { ereport(elevel, errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("multiple values specified for SSL key"), errcontext("line %d of configuration file \"%s\"", tok_line->line_num, tok_line->file_name)); return NULL; } token = linitial(tokens); parsedline->ssl_key = pstrdup(token->string); /* SSL CA (optional) */ field = lnext(tok_line->fields, field); if (!field) return parsedline; tokens = lfirst(field); if (tokens->length > 1) { ereport(elevel, errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("multiple values specified for SSL CA"), errcontext("line %d of configuration file \"%s\"", tok_line->line_num, tok_line->file_name)); return NULL; } token = linitial(tokens); parsedline->ssl_ca = pstrdup(token->string); /* SSL Passphrase Command (optional) */ field = lnext(tok_line->fields, field); if (field) { tokens = lfirst(field); if (tokens->length > 1) { ereport(elevel, errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("multiple values specified for SSL passphrase command"), errcontext("line %d of configuration file \"%s\"", tok_line->line_num, tok_line->file_name)); return NULL; } token = linitial(tokens); parsedline->ssl_passphrase_cmd = pstrdup(token->string); /* * SSL Passphrase Command support reload (optional). This field is * only supported if there was a passphrase command parsed first, so * nest it under the previous token. */ field = lnext(tok_line->fields, field); if (field) { tokens = lfirst(field); token = linitial(tokens); /* * There should be no more tokens after this, if there are break * parsing and report error to avoid silently accepting incorrect * config. */ if (lnext(tok_line->fields, field)) { ereport(elevel, errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("extra fields at end of line"), errcontext("line %d of configuration file \"%s\"", tok_line->line_num, tok_line->file_name)); return NULL; } if (tokens->length > 1 || !parse_bool(token->string, &parsedline->ssl_passphrase_reload)) { ereport(elevel, errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("incorrect syntax for boolean value SSL_passphrase_cmd_reload"), errcontext("line %d of configuration file \"%s\"", tok_line->line_num, tok_line->file_name)); return NULL; } } } return parsedline; } /* * load_hosts * * Reads and parses the pg_hosts.conf configuration file and passes back a List * of HostsLine elements containing the parsed lines, or NIL in case of an empty * file. The list is returned in the hosts parameter. The function will return * a HostsFileLoadResult value detailing the result of the operation. When * the hosts configuration failed to load, the err_msg variable may have more * information in case it was passed as non-NULL. */ HostsFileLoadResult load_hosts(List **hosts, char **err_msg) { FILE *file; ListCell *line; List *hosts_lines = NIL; List *parsed_lines = NIL; HostsLine *newline; bool ok = true; /* * If we cannot return results then error out immediately. This implies * API misuse or a similar kind of programmer error. */ if (!hosts) { if (err_msg) *err_msg = psprintf("cannot load config from \"%s\", return variable missing", HostsFileName); return HOSTSFILE_LOAD_FAILED; } *hosts = NIL; /* * This is not an auth file per se, but it is using the same file format * as the pg_hba and pg_ident files and thus the same code infrastructure. * A future TODO might be to rename the supporting code with a more * generic name? */ file = open_auth_file(HostsFileName, LOG, 0, err_msg); if (file == NULL) { if (errno == ENOENT) return HOSTSFILE_MISSING; return HOSTSFILE_LOAD_FAILED; } tokenize_auth_file(HostsFileName, file, &hosts_lines, LOG, 0); foreach(line, hosts_lines) { TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line); /* * Mark processing as not-ok in case lines are found with errors in * tokenization (.err_msg is set) or during parsing. */ if ((tok_line->err_msg != NULL) || ((newline = parse_hosts_line(tok_line, LOG)) == NULL)) { ok = false; continue; } parsed_lines = lappend(parsed_lines, newline); } /* Free memory from tokenizer */ free_auth_file(file, 0); *hosts = parsed_lines; if (!ok) { if (err_msg) *err_msg = psprintf("loading config from \"%s\" failed due to parsing error", HostsFileName); return HOSTSFILE_LOAD_FAILED; } if (parsed_lines == NIL) return HOSTSFILE_EMPTY; return HOSTSFILE_LOAD_OK; }