diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index cfdb33a..679c40a 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -838,7 +838,7 @@ omicron bryanh guest1 unix_socket_permissions (and possibly unix_socket_group) configuration parameters as described in . Or you - could set the unix_socket_directory + could set the unix_socket_directories configuration parameter to place the socket file in a suitably restricted directory. diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 3a0b16d..67997d6 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -445,17 +445,18 @@ SET ENABLE_SEQSCAN TO OFF; - - unix_socket_directory (string) + + unix_socket_directories (string) - unix_socket_directory configuration parameter + unix_socket_directories configuration parameter - Specifies the directory of the Unix-domain socket on which the + Specifies the directories of the Unix-domain sockets on which the server is to listen for connections from client applications. The default is normally /tmp, but can be changed at build time. + Directories are separated by ','. This parameter can only be set at server start. @@ -464,7 +465,7 @@ SET ENABLE_SEQSCAN TO OFF; .s.PGSQL.nnnn where nnnn is the server's port number, an ordinary file named .s.PGSQL.nnnn.lock will be - created in the unix_socket_directory directory. Neither + created in the unix_socket_directories directories. Neither file should ever be removed manually. @@ -6551,7 +6552,7 @@ LOG: CleanUpLock: deleting: lock(0xb7acd844) id(24688,24696,0,0,0,1) - unix_socket_directory = x + unix_socket_directories = x diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 8717798..9cc9d42 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1718,7 +1718,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 The simplest way to prevent spoofing for local connections is to use a Unix domain socket directory () that has write permission only + linkend="guc-unix-socket-directories">) that has write permission only for a trusted local user. This prevents a malicious user from creating their own socket file in that directory. If you are concerned that some applications might still reference /tmp for the diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index 5272811..3e22388 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -103,8 +103,8 @@ int Unix_socket_permissions; char *Unix_socket_group; -/* Where the Unix socket file is */ -static char sock_path[MAXPGPATH]; +/* Where the Unix socket files are */ +static List *sock_paths = NIL; /* @@ -140,8 +140,9 @@ static int internal_flush(void); static void pq_set_nonblocking(bool nonblocking); #ifdef HAVE_UNIX_SOCKETS -static int Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName); -static int Setup_AF_UNIX(void); +static int Lock_AF_UNIX(unsigned short portNumber, char *unixSocketDir, + char *unixSocketPath); +static int Setup_AF_UNIX(char *sock_path); #endif /* HAVE_UNIX_SOCKETS */ @@ -234,14 +235,23 @@ pq_close(int code, Datum arg) /* StreamDoUnlink() * Shutdown routine for backend connection - * If a Unix socket is used for communication, explicitly close it. + * If any Unix sockets are used for communication, explicitly close them. */ #ifdef HAVE_UNIX_SOCKETS static void StreamDoUnlink(int code, Datum arg) { - Assert(sock_path[0]); - unlink(sock_path); + ListCell *l; + char *cursocket; + + /* Loop through all created sockets... */ + foreach(l, sock_paths) + { + cursocket = (char *) lfirst(l); + unlink(cursocket); + } + list_free(sock_paths); + sock_paths = NIL; } #endif /* HAVE_UNIX_SOCKETS */ @@ -256,7 +266,7 @@ StreamDoUnlink(int code, Datum arg) int StreamServerPort(int family, char *hostName, unsigned short portNumber, - char *unixSocketName, + char *unixSocketDir, pgsocket ListenSocket[], int MaxListen) { pgsocket fd; @@ -267,6 +277,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber, const char *familyDesc; char familyDescBuf[64]; char *service; + char unixSocketPath[MAXPGPATH]; struct addrinfo *addrs = NULL, *addr; struct addrinfo hint; @@ -286,10 +297,14 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber, #ifdef HAVE_UNIX_SOCKETS if (family == AF_UNIX) { - /* Lock_AF_UNIX will also fill in sock_path. */ - if (Lock_AF_UNIX(portNumber, unixSocketName) != STATUS_OK) + /* + * Create unixSocketPath from portNumber and unixSocketDir + * and lock that file + */ + UNIXSOCK_PATH(unixSocketPath, portNumber, unixSocketDir); + if (Lock_AF_UNIX(portNumber, unixSocketDir, unixSocketPath) != STATUS_OK) return STATUS_ERROR; - service = sock_path; + service = unixSocketPath; } else #endif /* HAVE_UNIX_SOCKETS */ @@ -432,7 +447,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber, (IS_AF_UNIX(addr->ai_family)) ? errhint("Is another postmaster already running on port %d?" " If not, remove socket file \"%s\" and retry.", - (int) portNumber, sock_path) : + (int) portNumber, service) : errhint("Is another postmaster already running on port %d?" " If not, wait a few seconds and retry.", (int) portNumber))); @@ -443,7 +458,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber, #ifdef HAVE_UNIX_SOCKETS if (addr->ai_family == AF_UNIX) { - if (Setup_AF_UNIX() != STATUS_OK) + if (Setup_AF_UNIX(service) != STATUS_OK) { closesocket(fd); break; @@ -490,10 +505,8 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber, * Lock_AF_UNIX -- configure unix socket file path */ static int -Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName) +Lock_AF_UNIX(unsigned short portNumber, char *unixSocketDir, char *unixSocketPath) { - UNIXSOCK_PATH(sock_path, portNumber, unixSocketName); - /* * Grab an interlock file associated with the socket file. * @@ -502,13 +515,19 @@ Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName) * more portable, and second, it lets us remove any pre-existing socket * file without race conditions. */ - CreateSocketLockFile(sock_path, true); + CreateSocketLockFile(unixSocketPath, true, unixSocketDir); /* * Once we have the interlock, we can safely delete any pre-existing * socket file to avoid failure at bind() time. */ - unlink(sock_path); + unlink(unixSocketPath); + + /* + * Add a new socket file to the list, so we always know which socket + * paths exist and should be removed in the end. + */ + sock_paths = lappend(sock_paths, pstrdup(unixSocketPath)); return STATUS_OK; } @@ -518,7 +537,7 @@ Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName) * Setup_AF_UNIX -- configure unix socket permissions */ static int -Setup_AF_UNIX(void) +Setup_AF_UNIX(char *sock_path) { /* Arrange to unlink the socket file at exit */ on_proc_exit(StreamDoUnlink, 0); @@ -707,17 +726,21 @@ StreamClose(pgsocket sock) * TouchSocketFile -- mark socket file as recently accessed * * This routine should be called every so often to ensure that the socket - * file has a recent mod date (ordinary operations on sockets usually won't - * change the mod date). That saves it from being removed by + * files have a recent mod date (ordinary operations on sockets usually won't + * change the mod date). That saves them from being removed by * overenthusiastic /tmp-directory-cleaner daemons. (Another reason we should - * never have put the socket file in /tmp...) + * never have put the socket files in /tmp...) */ void TouchSocketFile(void) { - /* Do nothing if we did not create a socket... */ - if (sock_path[0] != '\0') + ListCell *l; + char *cursocket; + + /* Loop through all created sockets... */ + foreach(l, sock_paths) { + cursocket = (char *) lfirst(l); /* * utime() is POSIX standard, utimes() is a common alternative. If we * have neither, there's no way to affect the mod or access time of @@ -726,10 +749,10 @@ TouchSocketFile(void) * In either path, we ignore errors; there's no point in complaining. */ #ifdef HAVE_UTIME - utime(sock_path, NULL); + utime(cursocket, NULL); #else /* !HAVE_UTIME */ #ifdef HAVE_UTIMES - utimes(sock_path, NULL); + utimes(cursocket, NULL); #endif /* HAVE_UTIMES */ #endif /* HAVE_UTIME */ } diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 45f6ac6..a89455f 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -156,7 +156,8 @@ static Backend *ShmemBackendArray; /* The socket number we are listening for connections on */ int PostPortNumber; -char *UnixSocketDir; +char *UnixSocketDirs; +List *UnixSocketDirList; char *ListenAddresses; /* @@ -609,7 +610,7 @@ PostmasterMain(int argc, char *argv[]) break; case 'k': - SetConfigOption("unix_socket_directory", optarg, PGC_POSTMASTER, PGC_S_ARGV); + SetConfigOption("unix_socket_directories", optarg, PGC_POSTMASTER, PGC_S_ARGV); break; case 'l': @@ -838,6 +839,47 @@ PostmasterMain(int argc, char *argv[]) for (i = 0; i < MAXLISTEN; i++) ListenSocket[i] = PGINVALID_SOCKET; +#ifdef HAVE_UNIX_SOCKETS + /* + * We can specify several directories for Unix sockets to listen on, + * separated with ','. Socket name itself is the same in all cases. + */ + { + int success = 0; + bool listen_socket_saved = false; + ListCell *l; + + foreach(l, UnixSocketDirList) + { + char *cursocket = (char *) lfirst(l); + + status = StreamServerPort(AF_UNIX, NULL, + (unsigned short) PostPortNumber, + cursocket, + ListenSocket, MAXLISTEN); + + if (status == STATUS_OK) + { + success++; + /* record the first successful unix socket in lockfile */ + if (!listen_socket_saved) + { + AddToDataDirLockFile(LOCK_FILE_LINE_SOCKET_DIR, cursocket); + listen_socket_saved = true; + } + } + else + ereport(WARNING, + (errmsg("could not create Unix-domain socket at \"%s\"", cursocket))); + + } + + if (!success && list_length(UnixSocketDirList)) + ereport(FATAL, + (errmsg("could not create any Unix-domain sockets"))); + } +#endif + if (ListenAddresses) { char *rawstring; @@ -864,12 +906,12 @@ PostmasterMain(int argc, char *argv[]) if (strcmp(curhost, "*") == 0) status = StreamServerPort(AF_UNSPEC, NULL, (unsigned short) PostPortNumber, - UnixSocketDir, + NULL, ListenSocket, MAXLISTEN); else status = StreamServerPort(AF_UNSPEC, curhost, (unsigned short) PostPortNumber, - UnixSocketDir, + NULL, ListenSocket, MAXLISTEN); if (status == STATUS_OK) @@ -934,16 +976,6 @@ PostmasterMain(int argc, char *argv[]) } #endif -#ifdef HAVE_UNIX_SOCKETS - status = StreamServerPort(AF_UNIX, NULL, - (unsigned short) PostPortNumber, - UnixSocketDir, - ListenSocket, MAXLISTEN); - if (status != STATUS_OK) - ereport(WARNING, - (errmsg("could not create Unix-domain socket"))); -#endif - /* * check that we have some socket to listen on */ diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 9a5438f..265fad9 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3343,7 +3343,7 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx) break; case 'k': - SetConfigOption("unix_socket_directory", optarg, ctx, gucsource); + SetConfigOption("unix_socket_directories", optarg, ctx, gucsource); break; case 'l': diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index e1b57ba..bddc972 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -2445,6 +2445,109 @@ SplitIdentifierString(char *rawstring, char separator, return true; } +/* + * SplitDirectoriesString --- parse a string containing directories + * + * Inputs: + * rawstring: the input string; must be overwritable! On return, it's + * been modified to contain the separated directories. + * separator: the separator punctuation expected between directories + * (typically ',' or ';'). Whitespace may also appear around + * directories. + * Outputs: + * namelist: filled with a palloc'd list of pointers to directories within + * rawstring. Caller should list_free() this even on error return. + * + * Returns TRUE if okay, FALSE if there is a syntax error in the string. + * + * Note that an empty string is considered okay here. + */ +bool +SplitDirectoriesString(char *rawstring, char separator, + List **namelist) +{ + char *nextp = rawstring; + bool done = false; + char *tmpname; + + *namelist = NIL; + + while (isspace((unsigned char) *nextp)) + nextp++; /* skip leading whitespace */ + + if (*nextp == '\0') + return true; /* allow empty string */ + + /* At the top of the loop, we are at start of a new directories. */ + do + { + char *curname; + char *endp; + + if (*nextp == '\"') + { + /* Quoted name --- collapse quote-quote pairs */ + curname = nextp + 1; + for (;;) + { + endp = strchr(nextp + 1, '\"'); + if (endp == NULL) + return false; /* mismatched quotes */ + if (endp[1] != '\"') + break; /* found end of quoted name */ + /* Collapse adjacent quotes into one quote, and look again */ + memmove(endp, endp + 1, strlen(endp)); + nextp = endp; + } + /* endp now points at the terminating quote */ + nextp = endp + 1; + } + else + { + /* Unquoted name --- extends to separator or whitespace */ + curname = nextp; + while (*nextp && *nextp != separator && + !isspace((unsigned char) *nextp)) + nextp++; + endp = nextp; + if (curname == nextp) + return false; /* empty unquoted name not allowed */ + } + + while (isspace((unsigned char) *nextp)) + nextp++; /* skip trailing whitespace */ + + if (*nextp == separator) + { + nextp++; + while (isspace((unsigned char) *nextp)) + nextp++; /* skip leading whitespace for next */ + /* we expect another name, so done remains false */ + } + else if (*nextp == '\0') + done = true; + else + return false; /* invalid syntax */ + + /* Now safe to overwrite separator with a null */ + *endp = '\0'; + + /* Truncate path if it's overlength */ + if (strlen(curname) >= MAXPGPATH) + curname[MAXPGPATH-1] = '\0'; + + /* + * Finished isolating current name --- add it to list + */ + tmpname = pstrdup(curname); + canonicalize_path(tmpname); + *namelist = lappend(*namelist, tmpname); + + /* Loop back if we didn't reach end of string */ + } while (!done); + + return true; +} /***************************************************************************** * Comparison Functions used for bytea diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index fb376a0..8620ee8 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -663,10 +663,11 @@ UnlinkLockFile(int status, Datum filename) * filename is the name of the lockfile to create. * amPostmaster is used to determine how to encode the output PID. * isDDLock and refName are used to determine what error message to produce. + * socketPath is the path to the Unix socket we want to lock. */ static void CreateLockFile(const char *filename, bool amPostmaster, - bool isDDLock, const char *refName) + bool isDDLock, const char *refName, const char *socketPath) { int fd; char buffer[MAXPGPATH * 2 + 256]; @@ -892,7 +893,7 @@ CreateLockFile(const char *filename, bool amPostmaster, (long) MyStartTime, PostPortNumber, #ifdef HAVE_UNIX_SOCKETS - (*UnixSocketDir != '\0') ? UnixSocketDir : DEFAULT_PGSOCKET_DIR + (socketPath) ? socketPath : "" #else "" #endif @@ -949,19 +950,20 @@ CreateLockFile(const char *filename, bool amPostmaster, void CreateDataDirLockFile(bool amPostmaster) { - CreateLockFile(DIRECTORY_LOCK_FILE, amPostmaster, true, DataDir); + CreateLockFile(DIRECTORY_LOCK_FILE, amPostmaster, true, DataDir, NULL); } /* * Create a lockfile for the specified Unix socket file. */ void -CreateSocketLockFile(const char *socketfile, bool amPostmaster) +CreateSocketLockFile(const char *socketfile, bool amPostmaster, + const char *socketDir) { char lockfile[MAXPGPATH]; snprintf(lockfile, sizeof(lockfile), "%s.lock", socketfile); - CreateLockFile(lockfile, amPostmaster, false, socketfile); + CreateLockFile(lockfile, amPostmaster, false, socketfile, socketDir); /* Save name of lockfile for TouchSocketLockFile */ strcpy(socketLockFile, lockfile); } @@ -1292,3 +1294,4 @@ pg_bindtextdomain(const char *domain) } #endif } + diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 087aaf9..5603ce4 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -207,6 +207,7 @@ static bool check_application_name(char **newval, void **extra, GucSource source static void assign_application_name(const char *newval, void *extra); static const char *show_unix_socket_permissions(void); static const char *show_log_file_mode(void); +static void assign_unix_socket_directories(const char *newval, void *extra); static char *config_enum_get_options(struct config_enum * record, const char *prefix, const char *suffix, @@ -2895,14 +2896,18 @@ static struct config_string ConfigureNamesString[] = }, { - {"unix_socket_directory", PGC_POSTMASTER, CONN_AUTH_SETTINGS, - gettext_noop("Sets the directory where the Unix-domain socket will be created."), - NULL, + {"unix_socket_directories", PGC_POSTMASTER, CONN_AUTH_SETTINGS, + gettext_noop("Sets the directories where the Unix-domain socket will be created."), + gettext_noop("Directories are separated by \",\"."), GUC_SUPERUSER_ONLY }, - &UnixSocketDir, + &UnixSocketDirs, +#ifdef HAVE_UNIX_SOCKETS + DEFAULT_PGSOCKET_DIR, +#else "", - check_canonical_path, NULL, NULL +#endif + NULL, assign_unix_socket_directories, NULL }, { @@ -8759,4 +8764,34 @@ show_log_file_mode(void) return buf; } +static void +assign_unix_socket_directories(const char *newval, void *extra) +{ +#ifdef HAVE_UNIX_SOCKETS + char *rawSocketsString; + ListCell *l; + + /* Clean previous UnixSocketDirList list if not empty */ + if (UnixSocketDirList) + { + list_free(UnixSocketDirList); + UnixSocketDirList = NIL; + } + + /* Need a modifiable copy of value */ + rawSocketsString = pstrdup(newval); + + /* Parse string into list of directories */ + if (!SplitDirectoriesString(rawSocketsString, ',', &UnixSocketDirList)) + { + /* syntax error in list */ + ereport(FATAL, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid list syntax for \"unix_socket_directories\""))); + } + + pfree(rawSocketsString); +#endif +} + #include "guc-file.c" diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index fa75d00..42b5e40 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -65,7 +65,8 @@ # Note: Increasing max_connections costs ~400 bytes of shared memory per # connection slot, plus lock space (see max_locks_per_transaction). #superuser_reserved_connections = 3 # (change requires restart) -#unix_socket_directory = '' # (change requires restart) +#unix_socket_directories = '' # comma-separated list of directories, + # (change requires restart) #unix_socket_group = '' # (change requires restart) #unix_socket_permissions = 0777 # begin with 0 to use octal notation # (change requires restart) diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c index 72fc4c1..af8d8b2 100644 --- a/src/bin/pg_ctl/pg_ctl.c +++ b/src/bin/pg_ctl/pg_ctl.c @@ -521,7 +521,7 @@ test_postmaster_connection(bool do_checkpoint) hostaddr = optlines[LOCK_FILE_LINE_LISTEN_ADDR - 1]; /* - * While unix_socket_directory can accept relative + * While unix_socket_directories can accept relative * directories, libpq's host parameter must have a * leading slash to indicate a socket directory. So, * ignore sockdir if it's relative, and try to use TCP diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 7083cd8..a79200d 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -45,7 +45,7 @@ typedef struct * prototypes for functions in pqcomm.c */ extern int StreamServerPort(int family, char *hostName, - unsigned short portNumber, char *unixSocketName, pgsocket ListenSocket[], + unsigned short portNumber, char *unixSocketDir, pgsocket ListenSocket[], int MaxListen); extern int StreamConnection(pgsocket server_fd, Port *port); extern void StreamClose(pgsocket sock); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index b186eed..edfa161 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,7 +399,8 @@ extern char *local_preload_libraries_string; #define LOCK_FILE_LINE_SHMEM_KEY 7 extern void CreateDataDirLockFile(bool amPostmaster); -extern void CreateSocketLockFile(const char *socketfile, bool amPostmaster); +extern void CreateSocketLockFile(const char *socketfile, bool amPostmaster, + const char *socketDir); extern void TouchSocketLockFile(void); extern void AddToDataDirLockFile(int target_line, const char *str); extern void ValidatePgVersion(const char *path); diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h index 7d01d3d..aa549e6 100644 --- a/src/include/postmaster/postmaster.h +++ b/src/include/postmaster/postmaster.h @@ -13,13 +13,16 @@ #ifndef _POSTMASTER_H #define _POSTMASTER_H +#include "utils/builtins.h" + /* GUC options */ extern bool EnableSSL; extern int ReservedBackends; extern int PostPortNumber; extern int Unix_socket_permissions; extern char *Unix_socket_group; -extern char *UnixSocketDir; +extern char *UnixSocketDirs; +extern List *UnixSocketDirList; extern char *ListenAddresses; extern bool ClientAuthInProgress; extern int PreAuthDelay; diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 1063403..adab4ec 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -752,6 +752,8 @@ extern int varstr_cmp(char *arg1, int len1, char *arg2, int len2, Oid collid); extern List *textToQualifiedNameList(text *textval); extern bool SplitIdentifierString(char *rawstring, char separator, List **namelist); +extern bool SplitDirectoriesString(char *rawstring, char separator, + List **namelist); extern Datum replace_text(PG_FUNCTION_ARGS); extern text *replace_text_regexp(text *src_text, void *regexp, text *replace_text, bool glob);