pgpool2.git / summary / log / commit / refs

commit    6d0b8f7c7f94f6d160d3e8015254e7bb10b2b7a4
Author:   User yamaguti <yamaguti at pgfoundry.org>
Date:     Wed May 23 01:59:28 2007 +0000

    
    new files for HBA, ps display, log_hostname & log_connections


ac_func_accept_argtypes.m4 | 85 +++ pool_hba.c | 1435 ++++++++++++++++++++++++++++++++++++++++++++ pool_hba.conf.sample | 69 +++ pool_ip.c | 565 +++++++++++++++++ pool_ip.h | 65 ++ pool_path.c | 193 ++++++ pool_path.h | 81 +++ ps_status.c | 386 ++++++++++++ strlcpy.c | 85 +++ 9 files changed, 2964 insertions(+) diff --git a/ac_func_accept_argtypes.m4 b/ac_func_accept_argtypes.m4 new file mode 100644 index 000000000..917d59ab8 --- /dev/null +++ b/ac_func_accept_argtypes.m4 @@ -0,0 +1,85 @@ +# $PostgreSQL: pgsql/config/ac_func_accept_argtypes.m4,v 1.6 2003/11/29 19:51:17 pgsql Exp $ +# This comes from the official Autoconf macro archive at +# <http://research.cys.de/autoconf-archive/> +# (I removed the $ before the Id CVS keyword below.) + + +dnl @synopsis AC_FUNC_ACCEPT_ARGTYPES +dnl +dnl Checks the data types of the three arguments to accept(). Results are +dnl placed into the symbols ACCEPT_TYPE_RETURN and ACCEPT_TYPE_ARG[123], +dnl consistent with the following example: +dnl +dnl #define ACCEPT_TYPE_RETURN int +dnl #define ACCEPT_TYPE_ARG1 int +dnl #define ACCEPT_TYPE_ARG2 struct sockaddr * +dnl #define ACCEPT_TYPE_ARG3 socklen_t +dnl +dnl This macro requires AC_CHECK_HEADERS to have already verified the +dnl presence or absence of sys/types.h and sys/socket.h. +dnl +dnl NOTE: This is just a modified version of the AC_FUNC_SELECT_ARGTYPES +dnl macro. Credit for that one goes to David MacKenzie et. al. +dnl +dnl @version Id: ac_func_accept_argtypes.m4,v 1.1 1999/12/03 11:29:29 simons Exp $ +dnl @author Daniel Richard G. <skunk@mit.edu> +dnl + +# PostgreSQL local changes: In the original version ACCEPT_TYPE_ARG3 +# is a pointer type. That's kind of useless because then you can't +# use the macro to define a corresponding variable. We also make the +# reasonable(?) assumption that you can use arg3 for getsocktype etc. +# as well (i.e., anywhere POSIX.2 has socklen_t). +# +# arg2 can also be `const' (e.g., RH 4.2). Change the order of tests +# for arg3 so that `int' is first, in case there is no prototype at all. +# +# Solaris 7 and 8 have arg3 as 'void *' (disguised as 'Psocklen_t' +# which is *not* 'socklen_t *'). If we detect that, then we assume +# 'int' as the result, because that ought to work best. +# +# On Win32, accept() returns 'unsigned int PASCAL' + +AC_DEFUN([AC_FUNC_ACCEPT_ARGTYPES], +[AC_MSG_CHECKING([types of arguments for accept()]) + AC_CACHE_VAL(ac_cv_func_accept_return,dnl + [AC_CACHE_VAL(ac_cv_func_accept_arg1,dnl + [AC_CACHE_VAL(ac_cv_func_accept_arg2,dnl + [AC_CACHE_VAL(ac_cv_func_accept_arg3,dnl + [for ac_cv_func_accept_return in 'int' 'unsigned int PASCAL'; do + for ac_cv_func_accept_arg1 in 'int' 'unsigned int'; do + for ac_cv_func_accept_arg2 in 'struct sockaddr *' 'const struct sockaddr *' 'void *'; do + for ac_cv_func_accept_arg3 in 'int' 'size_t' 'socklen_t' 'unsigned int' 'void'; do + AC_TRY_COMPILE( +[#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +extern $ac_cv_func_accept_return accept ($ac_cv_func_accept_arg1, $ac_cv_func_accept_arg2, $ac_cv_func_accept_arg3 *);], + [], [ac_not_found=no; break 4], [ac_not_found=yes]) + done + done + done + done + if test "$ac_not_found" = yes; then + AC_MSG_ERROR([could not determine argument types]) + fi + if test "$ac_cv_func_accept_arg3" = "void"; then + ac_cv_func_accept_arg3=int + fi + ])dnl AC_CACHE_VAL + ])dnl AC_CACHE_VAL + ])dnl AC_CACHE_VAL + ])dnl AC_CACHE_VAL + AC_MSG_RESULT([$ac_cv_func_accept_return, $ac_cv_func_accept_arg1, $ac_cv_func_accept_arg2, $ac_cv_func_accept_arg3 *]) + AC_DEFINE_UNQUOTED(ACCEPT_TYPE_RETURN, $ac_cv_func_accept_return, + [Define to the return type of 'accept']) + AC_DEFINE_UNQUOTED(ACCEPT_TYPE_ARG1, $ac_cv_func_accept_arg1, + [Define to the type of arg 1 of 'accept']) + AC_DEFINE_UNQUOTED(ACCEPT_TYPE_ARG2, $ac_cv_func_accept_arg2, + [Define to the type of arg 2 of 'accept']) + AC_DEFINE_UNQUOTED(ACCEPT_TYPE_ARG3, $ac_cv_func_accept_arg3, + [Define to the type of arg 3 of 'accept']) +]) diff --git a/pool_hba.c b/pool_hba.c new file mode 100644 index 000000000..6b67d9867 --- /dev/null +++ b/pool_hba.c @@ -0,0 +1,1435 @@ +/* -*-pgsql-c-*- */ +/* + * + * $Header$ + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Portions Copyright (c) 2003-2007 PgPool Global Development Group + * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of the + * author not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. The author makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * pool_hba.c.: Routines to handle host based authentication. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> + +#include "pool.h" +#include "pool_path.h" +#include "pool_ip.h" +#include "parser/pg_list.h" + +#define MULTI_VALUE_SEP "\001" /* delimiter for multi-valued column strings */ +#define MAX_TOKEN 256 + +static List *hba_lines = NIL; +static List *hba_line_nums = NIL; +static char *hbaFileName; + +static POOL_MEMORY_POOL *hba_memory_context = NULL; + +static void sendAuthRequest(POOL_CONNECTION *frontend, AuthRequest areq); +static void auth_failed(POOL_CONNECTION *frontend); +static void close_all_backend_connections(void); +static bool hba_getauthmethod(POOL_CONNECTION *frontend); +static bool check_hba(POOL_CONNECTION *frontend); +static void parse_hba(List *line, int line_num, POOL_CONNECTION *frontend, bool *found_p, bool *error_p); +static void parse_hba_auth(ListCell **line_item, UserAuth *userauth_p, char **auth_arg_p, bool *error_p); +static bool check_user(char *user, char *param_str); +static bool check_db(char *dbname, char *user, char *param_str); +static void free_lines(List **lines, List **line_nums); +static void tokenize_file(const char *filename, FILE *file, List **lines, List **line_nums); +static char *tokenize_inc_file(const char *outer_filename, const char *inc_filename); +static bool pg_isblank(const char c); +static void next_token(FILE *fp, char *buf, int bufsz); +static char * next_token_expand(const char *filename, FILE *file); + +#ifdef USE_PAM +#ifdef HAVE_PAM_PAM_APPL_H +#include <pam/pam_appl.h> +#endif +#ifdef HAVE_SECURITY_PAM_APPL_H +#include <security/pam_appl.h> +#endif + +#define PGPOOL_PAM_SERVICE "pgpool" /* Service name passed to PAM */ + +static POOL_STATUS CheckPAMAuth(POOL_CONNECTION *frontend, char *user, char *password); +static int pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, struct pam_response ** resp, void *appdata_ptr); +/* + * recv_password_packet is usually used with authentications that require a client + * password. However, pgpool's hba function only uses it for PAM authentication, + * so declare a prototype here in "#ifdef USE_PAM" to avoid compilation warning. + */ +static char *recv_password_packet(POOL_CONNECTION *frontend); + +static struct pam_conv pam_passw_conv = { + &pam_passwd_conv_proc, + NULL +}; + +static char *pam_passwd = NULL; /* Workaround for Solaris 2.6 brokenness */ +static POOL_CONNECTION *pam_frontend_kludge; /* Workaround for passing + * POOL_CONNECTION *frontend + * into pam_passwd_conv_proc */ +#endif /* USE_PAM */ + + +/* + * read in hba config file + */ +void load_hba(char *hbapath) +{ + FILE *file; + + POOL_MEMORY_POOL *old_context; + if (hba_memory_context == NULL) + { + hba_memory_context = pool_memory_create(); + if (hba_memory_context == NULL) + { + pool_error("load_hba: pool_memory_create() failed"); + exit(1); + } + } + /* switch memory context */ + old_context = pool_memory; + pool_memory = hba_memory_context; + + if (hba_lines || hba_line_nums) + free_lines(&hba_lines, &hba_line_nums); + + file = fopen(hbapath, "r"); + if (!file) + { + pool_error("could not open \"%s\". reason: %s", + hbapath, strerror(errno)); + exit(1); + } + + pool_debug("loading \"%s\" for client authentication configuration file", + hbapath); + + tokenize_file(hbapath, file, &hba_lines, &hba_line_nums); + fclose(file); + + hbaFileName = pstrdup(hbapath); + + /* switch old memory context */ + pool_memory = old_context; +} + + +/* + * do frontend <-> pgpool authentication based on pool_hba.conf + */ +void ClientAuthentication(POOL_CONNECTION *frontend) +{ + POOL_STATUS status = POOL_ERROR; + + if (! hba_getauthmethod(frontend)) + { + pool_error("missing or erroneous pool_hba.conf file"); + pool_send_error_message(frontend, frontend->protoVersion, "XX000", + "missing or erroneous pool_hba.conf file", "", + "See pgpool log for details.", __FILE__, __LINE__); + close_all_backend_connections(); + /* + * use exit(2) since this is not so fatal. other entries in + * pool_hba.conf may be valid, so treat it as reject. + */ + exit(2); + } + + switch (frontend->auth_method) + { + case uaReject: + { + /* + * This could have come from an explicit "reject" entry in + * pool_hba.conf, but more likely it means there was no matching + * entry. Take pity on the poor user and issue a helpful + * error message. NOTE: this is not a security breach, + * because all the info reported here is known at the frontend + * and must be assumed known to bad guys. We're merely helping + * out the less clueful good guys. + */ + char hostinfo[NI_MAXHOST]; + char *errmessage; + int messagelen; + + getnameinfo_all(&frontend->raddr.addr, frontend->raddr.salen, + hostinfo, sizeof(hostinfo), + NULL, 0, + NI_NUMERICHOST); + + messagelen = sizeof(hostinfo) + + strlen(frontend->username) + strlen(frontend->database) + 80; + if ((errmessage = (char *)malloc(messagelen+1)) == NULL) + { + pool_error("ClientAuthentication: malloc failed: %s", strerror(errno)); + exit(1); + } + +#ifdef USE_SSL + snprintf(errmessage, messagelen+7, /* +7 is for "SSL off" */ + "no pool_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s", + hostinfo, frontend->username, frontend->database, + frontend->ssl ? "SSL on" : "SSL off"); +#else + snprintf(errmessage, messagelen, + "no pool_hba.conf entry for host \"%s\", user \"%s\", database \"%s\"", + hostinfo, frontend->username, frontend->database); +#endif + pool_error(errmessage); + pool_send_error_message(frontend, frontend->protoVersion, "XX000", errmessage, + "", "", __FILE__, __LINE__); + + free(errmessage); + break; + } + +/* case uaKrb4: */ +/* break; */ + +/* case uaKrb5: */ +/* break; */ + +/* case uaIdent: */ +/* break; */ + +/* case uaMD5: */ +/* break; */ + +/* case uaCrypt: */ +/* break; */ + +/* case uaPassword: */ +/* break; */ + +#ifdef USE_PAM + case uaPAM: + pam_frontend_kludge = frontend; + status = CheckPAMAuth(frontend, frontend->username, ""); + break; +#endif /* USE_PAM */ + + case uaTrust: + status = POOL_CONTINUE; + break; + } + + if (status == POOL_CONTINUE) + sendAuthRequest(frontend, AUTH_REQ_OK); + else if (status != POOL_CONTINUE) + auth_failed(frontend); +} + + +static void sendAuthRequest(POOL_CONNECTION *frontend, AuthRequest areq) +{ + int wsize; /* number of bytes to write */ + int areq_nbo; /* areq in network byte order */ + + /* + * If AUTH_REQ_OK, then frontend is OK to connect __with_pgpool__. + * Do not send 'R' to the frontend, he still needs to authenticate + * himself with the backend. + */ + if (areq == AUTH_REQ_OK) + return; + + /* request a password */ + pool_write(frontend, "R", 1); + + if (frontend->protoVersion == PROTO_MAJOR_V3) + { +/* if (areq == AUTH_REQ_MD5) */ +/* wsize = htonl(sizeof(int)*2+4); */ +/* else if (areq == AUTH_REQ_CRYPT) */ +/* wsize = htonl(sizeof(int)*2+2); */ +/* else */ + wsize = htonl(sizeof(int)*2); + pool_write(frontend, &wsize, sizeof(int)); + } + + areq_nbo = htonl(areq); + pool_write(frontend, &areq_nbo, sizeof(int)); + + /* Add the salt for encrypted passwords. */ +/* if (areq == AUTH_REQ_MD5) */ +/* pq_sendbytes(&buf, port->md5Salt, 4); */ +/* else if (areq == AUTH_REQ_CRYPT) */ +/* pq_sendbytes(&buf, port->cryptSalt, 2); */ + + pool_flush(frontend); +} + + +#ifdef USE_PAM /* see the prototype comment */ + +/* + * Collect password response packet from frontend. + * + * Returns NULL if couldn't get password, else malloc'd string. + */ +static char *recv_password_packet(POOL_CONNECTION *frontend) +{ + int rsize; + char *passwd; + char *returnVal; + + if (frontend->protoVersion == PROTO_MAJOR_V3) + { + /* Expect 'p' message type */ + char kind; + + if (pool_read(frontend, &kind, 1) < 0) + return NULL; + + if (kind != 'p') + { + pool_error("expected password response, got message type %c", + kind); + return NULL; /* bad message type */ + } + } + /* pre-3.0 protocol does not send a message type */ + + if (pool_read(frontend, &rsize, sizeof(int)) < 0) + return NULL; + + rsize = ntohl(rsize) - 4; + passwd = pool_read2(frontend, rsize); /* retrieve password */ + if (passwd == NULL) + return NULL; + + /* Do not echo password to logs, for security. */ + pool_debug("received password packet from frontend for pgpool's HBA"); + + /* + * Return the received string. Note we do not attempt to do any + * character-set conversion on it; since we don't yet know the + * client's encoding, there wouldn't be much point. + */ + returnVal = strdup(passwd); + if (returnVal == NULL) + { + pool_error("recv_password_packet: strdup failed: %s", strerror(errno)); + exit(1); + } + return returnVal; +} + +#endif /* USE_PAM */ + +/* + * Tell the user the authentication failed. + */ +static void auth_failed(POOL_CONNECTION *frontend) +{ + bool send_error_to_frontend = true; + int messagelen; + char *errmessage; + + messagelen = strlen(frontend->username) + 100; + if ((errmessage = (char *)malloc(messagelen+1)) == NULL) + { + pool_error("auth_failed: malloc failed: %s", strerror(errno)); + exit(1); + } + + switch (frontend->auth_method) + { + case uaReject: + snprintf(errmessage, messagelen, + "authentication with pgpool failed for user \"%s\": host rejected", + frontend->username); + /* + * if uaReject, frontend should have received 'E' and disconnected already. + */ + send_error_to_frontend = false; + break; +/* case uaKrb4: */ +/* snprintf(errmessage, messagelen, */ +/* "Kerberos 4 authentication with pgpool failed for user \"%s\"", */ +/* frontend->username); */ +/* break; */ +/* case uaKrb5: */ +/* snprintf(errmessage, messagelen, */ +/* "Kerberos 5 authentication with pgpool failed for user \"%s\"", */ +/* frontend->username); */ +/* break; */ + case uaTrust: + snprintf(errmessage, messagelen, + "\"trust\" authentication with pgpool failed for user \"%s\"", + frontend->username); + break; +/* case uaIdent: */ +/* snprintf(errmessage, messagelen, */ +/* "Ident authentication with pgpool failed for user \"%s\"", */ +/* frontend->username); */ +/* break; */ +/* case uaMD5: */ +/* case uaCrypt: */ +/* case uaPassword: */ +/* snprintf(errmessage, messagelen, */ +/* "password authentication with pgpool failed for user \"%s\"", */ +/* frontend->username); */ +/* break; */ +#ifdef USE_PAM + case uaPAM: + snprintf(errmessage, messagelen, + "PAM authentication with pgpool failed for user \"%s\"", + frontend->username); + break; +#endif /* USE_PAM */ + default: + snprintf(errmessage, messagelen, + "authentication with pgpool failed for user \"%s\": invalid authentication method", + frontend->username); + break; + } + + pool_error(errmessage); + if (send_error_to_frontend) + pool_send_error_message(frontend, frontend->protoVersion, "XX000", errmessage, + "", "", __FILE__, __LINE__); + + /* + * don't need to free(errmessage). I will just kill myself. + */ + close_all_backend_connections(); + exit(2); +} + + +/* + * Close all of the cached backend connections. + * + * This is exactly the same as send_frontend_exits() in child.c. + */ +static void close_all_backend_connections(void) +{ + int i; + POOL_CONNECTION_POOL *p = pool_connection_pool; + +#ifdef HAVE_SIGPROCMASK + sigset_t oldmask; +#else + int oldmask; +#endif + + POOL_SETMASK2(&BlockSig, &oldmask); + + for (i=0;i<pool_config->max_pool;i++, p++) + { + if (!MASTER_CONNECTION(p)) + continue; + if (MASTER_CONNECTION(p)->sp->user == NULL) + continue; + pool_send_frontend_exits(p); + } + + POOL_SETMASK(&oldmask); +} + + +/* + * Determine what authentication method should be used when accessing database + * "database" from frontend "raddr", user "user". Return the method and + * an optional argument (stored in fields of *frontend), and true for success. + * + * Note that false indicates a problem with the hba config file. + * If the file is OK but does not contain any entry matching the request, + * we return true and method = uaReject. + */ +static bool hba_getauthmethod(POOL_CONNECTION *frontend) +{ + if (check_hba(frontend)) + return true; + else + return false; +} + + +/* + * Scan the (pre-parsed) hba file line by line, looking for a match + * to the port's connection request. + */ +static bool check_hba(POOL_CONNECTION *frontend) +{ + bool found_entry = false; + bool error = false; + ListCell *line; + ListCell *line_num; + + forboth(line, hba_lines, line_num, hba_line_nums) + { + parse_hba(lfirst(line), lfirst_int(line_num), + frontend, &found_entry, &error); + if (found_entry || error) + break; + } + + if (!error) + { + /* If no matching entry was found, synthesize 'reject' entry. */ + if (!found_entry) + frontend->auth_method = uaReject; + return true; + } + else + return false; +} + + +/* + * Process one line from the hba config file. + * + * See if it applies to a connection from a frontend with IP address + * frontend->raddr to a database named frontend->database. If so, return + * *found_p true and fill in the auth arguments into the appropriate + * frontend fields. If not, leave *found_p as it was. If the record has + * a syntax error, return *error_p true, after issuing a message to the + * log. If no error, leave *error_p as it was. + */ +static void parse_hba(List *line, int line_num, POOL_CONNECTION *frontend, + bool *found_p, bool *error_p) +{ + char *token; + char *db, *db_tmp; + char *user, *user_tmp; + struct addrinfo *gai_result; + struct addrinfo hints; + int ret; + struct sockaddr_storage addr; + struct sockaddr_storage mask; + char *cidr_slash; + ListCell *line_item; + + line_item = list_head(line); + /* Check the record type. */ + token = lfirst(line_item); + if (strcmp(token, "local") == 0) + { + /* Get the database. */ + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + db = lfirst(line_item); + + /* Get the user. */ + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + user = lfirst(line_item); + + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + + /* Read the rest of the line. */ + parse_hba_auth(&line_item, &frontend->auth_method, + &frontend->auth_arg, error_p); + if (*error_p) + goto hba_syntax; + + /* Disallow auth methods that always need TCP/IP sockets to work */ + /* + if (frontend->auth_method == uaKrb4 || + frontend->auth_method == uaKrb5) + goto hba_syntax; + */ + + /* Does not match if connection isn't AF_UNIX */ + if (!IS_AF_UNIX(frontend->raddr.addr.ss_family)) + return; + } + else if (strcmp(token, "host") == 0 + || strcmp(token, "hostssl") == 0 + || strcmp(token, "hostnossl") == 0) + { + if (token[4] == 's') /* "hostssl" */ + { +#ifdef USE_SSL + /* Record does not match if we are not on an SSL connection */ + if (!frontend->ssl) + return; + + /* Placeholder to require specific SSL level, perhaps? */ + /* Or a client certificate */ + + /* Since we were on SSL, proceed as with normal 'host' mode */ +#else + /* We don't accept this keyword at all if no SSL support */ + goto hba_syntax; +#endif + } +#ifdef USE_SSL + else if (token[4] == 'n') /* "hostnossl" */ + { + /* Record does not match if we are on an SSL connection */ + if (frontend->ssl) + return; + } +#endif + + /* Get the database. */ + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + db = lfirst(line_item); + + /* Get the user. */ + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + user = lfirst(line_item); + + /* Read the IP address field. (with or without CIDR netmask) */ + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + token = lfirst(line_item); + + /* Check if it has a CIDR suffix and if so isolate it */ + cidr_slash = strchr(token, '/'); + if (cidr_slash) + *cidr_slash = '\0'; + + /* Get the IP address either way */ + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = 0; + hints.ai_protocol = 0; + hints.ai_addrlen = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + ret = getaddrinfo_all(token, NULL, &hints, &gai_result); + if (ret || !gai_result) + { + pool_log("invalid IP address \"%s\" in file \"%s\" line %d: %s", + token, hbaFileName, line_num, gai_strerror(ret)); + if (cidr_slash) + *cidr_slash = '/'; + if (gai_result) + freeaddrinfo_all(hints.ai_family, gai_result); + goto hba_other_error; + } + + if (cidr_slash) + *cidr_slash = '/'; + + memcpy(&addr, gai_result->ai_addr, gai_result->ai_addrlen); + freeaddrinfo_all(hints.ai_family, gai_result); + + /* Get the netmask */ + if (cidr_slash) + { + if (SockAddr_cidr_mask(&mask, cidr_slash + 1, addr.ss_family) < 0) + goto hba_syntax; + } + else + { + /* Read the mask field. */ + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + token = lfirst(line_item); + + ret = getaddrinfo_all(token, NULL, &hints, &gai_result); + if (ret || !gai_result) + { + pool_log("invalid IP mask \"%s\" in file \"%s\" line %d: %s", + token, hbaFileName, line_num, gai_strerror(ret)); + if (gai_result) + freeaddrinfo_all(hints.ai_family, gai_result); + goto hba_other_error; + } + + memcpy(&mask, gai_result->ai_addr, gai_result->ai_addrlen); + freeaddrinfo_all(hints.ai_family, gai_result); + + if (addr.ss_family != mask.ss_family) + { + pool_log("IP address and mask do not match in file \"%s\" line %d", + hbaFileName, line_num); + goto hba_other_error; + } + } + + if (addr.ss_family != frontend->raddr.addr.ss_family) + { + /* + * Wrong address family. We allow only one case: if the file + * has IPv4 and the port is IPv6, promote the file address to + * IPv6 and try to match that way. + */ +#ifdef HAVE_IPV6 + if (addr.ss_family == AF_INET && frontend->raddr.addr.ss_family == AF_INET6) + { + promote_v4_to_v6_addr(&addr); + promote_v4_to_v6_mask(&mask); + } + else +#endif /* HAVE_IPV6 */ + { + /* Line doesn't match client port, so ignore it. */ + return; + } + } + + /* Ignore line if client port is not in the matching addr range. */ + if (!rangeSockAddr(&frontend->raddr.addr, &addr, &mask)) + return; + + /* Read the rest of the line. */ + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + parse_hba_auth(&line_item, &frontend->auth_method, + &frontend->auth_arg, error_p); + if (*error_p) + goto hba_syntax; + } + else + goto hba_syntax; + + /* Does the entry match database and user? */ + /* + * duplicate db and username since strtok() in check_db() and check_user() + * will override '\001' with '\0'. + */ + db_tmp = strdup(db); + if (db_tmp == NULL) + { + pool_error("parse_hba: strdup failed: %s", strerror(errno)); + exit(1); + } + user_tmp = strdup(user); + if (user_tmp == NULL) + { + pool_error("parse_hba: strdup failed: %s", strerror(errno)); + exit(1); + } + if (!check_db(frontend->database, frontend->username, db_tmp)) + return; + if (!check_user(frontend->username, user_tmp)) + return; + free(db_tmp); + free(user_tmp); + + /* Success */ + *found_p = true; + return; + + hba_syntax: + if (line_item) + pool_log("invalid entry in file \"%s\" at line %d, token \"%s\"", + hbaFileName, line_num, (char *) lfirst(line_item)); + else + pool_log("missing field in file \"%s\" at end of line %d", + hbaFileName, line_num); + + /* Come here if suitable message already logged */ + hba_other_error: + *error_p = true; +} + + +/* + * Scan the rest of a host record (after the mask field) + * and return the interpretation of it as *userauth_p, *auth_arg_p, and + * *error_p. *line_item points to the next token of the line, and is + * advanced over successfully-read tokens. + */ +static void parse_hba_auth(ListCell **line_item, UserAuth *userauth_p, + char **auth_arg_p, bool *error_p) +{ + char *token; + + *auth_arg_p = NULL; + + if (!*line_item) + { + *error_p = true; + return; + } + + token = lfirst(*line_item); + if (strcmp(token, "trust") == 0) + *userauth_p = uaTrust; + /* + else if (strcmp(token, "ident") == 0) + *userauth_p = uaIdent; + else if (strcmp(token, "password") == 0) + *userauth_p = uaPassword; + else if (strcmp(token, "krb4") == 0) + *userauth_p = uaKrb4; + else if (strcmp(token, "krb5") == 0) + *userauth_p = uaKrb5; + */ + else if (strcmp(token, "reject") == 0) + *userauth_p = uaReject; + /* + else if (strcmp(token, "md5") == 0) + *userauth_p = uaMD5; + else if (strcmp(token, "crypt") == 0) + *userauth_p = uaCrypt; + */ +#ifdef USE_PAM + else if (strcmp(token, "pam") == 0) + *userauth_p = uaPAM; +#endif /* USE_PAM */ + else + { + *error_p = true; + return; + } + *line_item = lnext(*line_item); + + /* Get the authentication argument token, if any */ + if (*line_item) + { + token = lfirst(*line_item); + *auth_arg_p = strdup(token); + if (*auth_arg_p == NULL) + { + pool_error("parse_hba_auth: strdup failed: %s", strerror(errno)); + exit(1); + } + *line_item = lnext(*line_item); + /* If there is more on the line, it is an error */ + if (*line_item) + *error_p = true; + } +} + + +/* + * Check comma user list for a specific user, handle group names. + */ +static bool check_user(char *user, char *param_str) +{ + char *tok; + + for (tok = strtok(param_str, MULTI_VALUE_SEP); + tok != NULL; tok = strtok(NULL, MULTI_VALUE_SEP)) + { + if (tok[0] == '+') + { + /* + * pgpool cannot accept groups. commented lines below are the + * original code. + */ + pool_error("group token \"+\" is not supported in pgpool"); + return false; +/* if (check_group(tok + 1, user)) */ +/* return true; */ + } + else if (strcmp(tok, user) == 0 || strcmp(tok, "all\n") == 0) + return true; + } + + return false; +} + + +/* + * Check to see if db/user combination matches param string. + */ +static bool check_db(char *dbname, char *user, char *param_str) +{ + char *tok; + + for (tok = strtok(param_str, MULTI_VALUE_SEP); + tok != NULL; tok = strtok(NULL, MULTI_VALUE_SEP)) + { + if (strcmp(tok, "all\n") == 0) + return true; + else if (strcmp(tok, "sameuser\n") == 0) + { + if (strcmp(dbname, user) == 0) + return true; + } + else if (strcmp(tok, "samegroup\n") == 0) + { + /* + * pgpool cannot accept groups. commented lines below are the + * original code. + */ + pool_error("group token \"samegroup\" is not supported in pgpool"); + return false; +/* if (check_group(dbname, user)) */ +/* return true; */ + } + else if (strcmp(tok, dbname) == 0) + return true; + } + + return false; +} + + +/* + * tokenize the given file, storing the resulting data into two lists: + * a list of sublists, each sublist containing the tokens in a line of + * the file, and a list of line numbers. + * + * filename must be the absolute path to the target file. + */ +static void tokenize_file(const char *filename, FILE *file, + List **lines, List **line_nums) +{ + List *current_line = NIL; + int line_number = 1; + char *buf; + + *lines = *line_nums = NIL; + + while (!feof(file)) + { + buf = next_token_expand(filename, file); + + /* add token to list, unless we are at EOL or comment start */ + if (buf[0]) + { + if (current_line == NIL) + { + /* make a new line List, record its line number */ + current_line = lappend(current_line, buf); + *lines = lappend(*lines, current_line); + *line_nums = lappend_int(*line_nums, line_number); + } + else + { + /* append token to current line's list */ + current_line = lappend(current_line, buf); + } + } + else + { + /* we are at real or logical EOL, so force a new line List */ + current_line = NIL; + /* Advance line number whenever we reach EOL */ + line_number++; + /* Don't forget to free the next_token_expand result */ + free(buf); + } + } +} + + +static char * tokenize_inc_file(const char *outer_filename, + const char *inc_filename) +{ + char *inc_fullname; + FILE *inc_file; + List *inc_lines; + List *inc_line_nums; + ListCell *line; + char *comma_str; + + if (is_absolute_path(inc_filename)) + { + /* absolute path is taken as-is */ + inc_fullname = strdup(inc_filename); + if (inc_fullname == NULL) + { + pool_error("tokenize_inc_file: strdup failed: %s", strerror(errno)); + exit(1); + } + } + else + { + /* relative path is relative to dir of calling file */ + inc_fullname = (char *)malloc(strlen(outer_filename) + 1 + + strlen(inc_filename) + 1); + if (inc_fullname == NULL) + { + pool_error("tokenize_inc_file: malloc failed: %s", strerror(errno)); + exit(1); + } + strcpy(inc_fullname, outer_filename); + get_parent_directory(inc_fullname); + join_path_components(inc_fullname, inc_fullname, inc_filename); + canonicalize_path(inc_fullname); + } + + inc_file = fopen(inc_fullname, "r"); + if (inc_file == NULL) + { + char *returnVal; + + pool_error("could not open secondary authentication file \"@%s\" as \"%s\": reason: %s", + inc_filename, inc_fullname, strerror(errno)); + free(inc_fullname); + + /* return single space, it matches nothing */ + returnVal = strdup(" "); + if (returnVal == NULL) + { + pool_error("tokenize_inc_file: malloc failed: %s", strerror(errno)); + exit(1); + } + return returnVal; + } + + /* There is possible recursion here if the file contains @ */ + tokenize_file(inc_fullname, inc_file, &inc_lines, &inc_line_nums); + + /*FreeFile(inc_file);*/ + fclose(inc_file); + free(inc_fullname); + + /* Create comma-separated string from List */ + comma_str = strdup(""); + if (comma_str == NULL) + { + pool_error("tokenize_inc_file: strdup failed: %s", strerror(errno)); + exit(1); + } + foreach(line, inc_lines) + { + List *token_list = (List *) lfirst(line); + ListCell *token; + + foreach(token, token_list) + { + int oldlen = strlen(comma_str); + int needed; + + needed = oldlen + strlen(lfirst(token)) + 1; + if (oldlen > 0) + needed++; + comma_str = realloc(comma_str, needed); + if (comma_str == NULL) + { + pool_error("tokenize_inc_file: realloc failed: %s", strerror(errno)); + exit(1); + } + if (oldlen > 0) + strcat(comma_str, MULTI_VALUE_SEP); + strcat(comma_str, lfirst(token)); + } + } + + free_lines(&inc_lines, &inc_line_nums); + + /* if file is empty, return single space rather than empty string */ + if (strlen(comma_str) == 0) + { + char *returnVal; + + free(comma_str); + returnVal = strdup(" "); + if (returnVal == NULL) + { + pool_error("tokenize_inc_file: strdup failed: %s", strerror(errno)); + exit(1); + } + return returnVal; + } + + return comma_str; +} + + +/* + * isblank() exists in the ISO C99 spec, but it's not very portable yet, + * so provide our own version. + */ +static bool pg_isblank(const char c) +{ + return c == ' ' || c == '\t' || c == '\r'; +} + + +/* + * Tokenize file and handle file inclusion and comma lists. We have + * to break apart the commas to expand any file names then + * reconstruct with commas. + * + * The result is always a malloc'd string. If it's zero-length then + * we have reached EOL. + */ +static char * next_token_expand(const char *filename, FILE *file) +{ + char buf[MAX_TOKEN]; + char *comma_str; + bool trailing_comma; + char *incbuf; + int needed; + + comma_str = strdup(""); + if (comma_str == NULL) + { + pool_error("next_token_expand: strdup failed: %s", strerror(errno)); + exit(1); + } + + do + { + next_token(file, buf, sizeof(buf)); + if (!buf[0]) + break; + + if (buf[strlen(buf) - 1] == ',') + { + trailing_comma = true; + buf[strlen(buf) - 1] = '\0'; + } + else + trailing_comma = false; + + /* Is this referencing a file? */ + if (buf[0] == '@') + incbuf = tokenize_inc_file(filename, buf + 1); + else + { + incbuf = strdup(buf); + if (incbuf == NULL) + { + pool_error("next_token_expand: strdup failed: %s", strerror(errno)); + exit(1); + } + } + + needed = strlen(comma_str) + strlen(incbuf) + 1; + if (trailing_comma) + needed++; + comma_str = realloc(comma_str, needed); + if (comma_str == NULL) + { + pool_error("next_token_expand: realloc failed: %s", strerror(errno)); + exit(1); + } + strcat(comma_str, incbuf); + if (trailing_comma) + strcat(comma_str, MULTI_VALUE_SEP); + free(incbuf); + } while (trailing_comma); + + return comma_str; +} + + +/* + * Grab one token out of fp. Tokens are strings of non-blank + * characters bounded by blank characters, beginning of line, and + * end of line. Blank means space or tab. Return the token as + * *buf. Leave file positioned at the character immediately after the + * token or EOF, whichever comes first. If no more tokens on line, + * return empty string as *buf and position the file to the beginning + * of the next line or EOF, whichever comes first. Allow spaces in + * quoted strings. Terminate on unquoted commas. Handle + * comments. Treat unquoted keywords that might be user names or + * database names specially, by appending a newline to them. + */ +static void next_token(FILE *fp, char *buf, int bufsz) +{ + int c; + char *start_buf = buf; + char *end_buf = buf + (bufsz - 2); + bool in_quote = false; + bool was_quote = false; + bool saw_quote = false; + + /*Assert(end_buf > start_buf);*/ + + /* Move over initial whitespace and commas */ + while ((c = getc(fp)) != EOF && (pg_isblank(c) || c == ',')) + ; + + if (c == EOF || c == '\n') + { + *buf = '\0'; + return; + } + + /* + * Build a token in buf of next characters up to EOF, EOL, unquoted + * comma, or unquoted whitespace. + */ + while (c != EOF && c != '\n' && + (!pg_isblank(c) || in_quote == true)) + { + /* skip comments to EOL */ + if (c == '#' && !in_quote) + { + while ((c = getc(fp)) != EOF && c != '\n') + ; + /* If only comment, consume EOL too; return EOL */ + if (c != EOF && buf == start_buf) + c = getc(fp); + break; + } + + if (buf >= end_buf) + { + *buf = '\0'; + pool_log("authentication file token too long, skipping: \"%s\"", start_buf); + /* Discard remainder of line */ + while ((c = getc(fp)) != EOF && c != '\n') + ; + break; + } + + if (c != '"' || (c == '"' && was_quote)) + *buf++ = c; + + /* We pass back the comma so the caller knows there is more */ + if ((pg_isblank(c) || c == ',') && !in_quote) + break; + + /* Literal double-quote is two double-quotes */ + if (in_quote && c == '"') + was_quote = !was_quote; + else + was_quote = false; + + if (c == '"') + { + in_quote = !in_quote; + saw_quote = true; + } + + c = getc(fp); + } + + /* + * Put back the char right after the token (critical in case it is + * EOL, since we need to detect end-of-line at next call). + */ + if (c != EOF) + ungetc(c, fp); + + *buf = '\0'; + + if (!saw_quote && + (strcmp(start_buf, "all") == 0 || + strcmp(start_buf, "sameuser") == 0 || + strcmp(start_buf, "samegroup") == 0)) + { + /* append newline to a magical keyword */ + *buf++ = '\n'; + *buf = '\0'; + } +} + + +/* + * free memory used by lines and tokens built by tokenize_file() + */ +static void free_lines(List **lines, List **line_nums) +{ + if (*lines) + { + ListCell *line; + + foreach(line, *lines) + { + List *ln = lfirst(line); + ListCell *token; + + foreach(token, ln) + free(lfirst(token)); + + list_free(ln); + } + + list_free(*lines); + *lines = NIL; + } + + if (*line_nums) + { + list_free(*line_nums); + *line_nums = NIL; + } +} + + +#ifdef USE_PAM + +/* + * PAM conversation function + */ +static int pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, + struct pam_response ** resp, void *appdata_ptr) +{ + if (num_msg != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF) + { + switch (msg[0]->msg_style) + { + case PAM_ERROR_MSG: + pool_log("error from underlying PAM layer: %s", + msg[0]->msg); + return PAM_CONV_ERR; + default: + pool_log("unsupported PAM conversation %d/%s", + msg[0]->msg_style, msg[0]->msg); + return PAM_CONV_ERR; + } + } + + if (!appdata_ptr) + { + /* + * Workaround for Solaris 2.6 where the PAM library is broken and + * does not pass appdata_ptr to the conversation routine + */ + appdata_ptr = pam_passwd; + } + + /* + * Password wasn't passed to PAM the first time around - let's go ask + * the client to send a password, which we then stuff into PAM. + */ + if (strlen(appdata_ptr) == 0) + { + char *passwd; + + sendAuthRequest(pam_frontend_kludge, AUTH_REQ_PASSWORD); + passwd = recv_password_packet(pam_frontend_kludge); + + if (passwd == NULL) + return PAM_CONV_ERR; /* client didn't want to send password */ + + if (strlen(passwd) == 0) + { + pool_log("empty password returned by client"); + return PAM_CONV_ERR; + } + appdata_ptr = passwd; + } + + /* + * PAM will free this memory in * pam_end() + */ + *resp = calloc(num_msg, sizeof(struct pam_response)); + if (!*resp) + { + /* originally, it was logged as LOG */ + pool_error("pam_passwd_conv_proc: calloc failed: %s", strerror(errno)); + return PAM_CONV_ERR; + } + + (*resp)[0].resp = strdup((char *) appdata_ptr); + if ((*resp)[0].resp == NULL) + { + pool_error("pam_passwd_conv_proc: strdup failed: %s", strerror(errno)); + exit(1); + } + (*resp)[0].resp_retcode = 0; + + return ((*resp)[0].resp ? PAM_SUCCESS : PAM_CONV_ERR); +} + + +/* + * Check authentication against PAM. + */ +static POOL_STATUS CheckPAMAuth(POOL_CONNECTION *frontend, char *user, char *password) +{ + int retval; + pam_handle_t *pamh = NULL; + + /* + * Apparently, Solaris 2.6 is broken, and needs ugly static variable + * workaround + */ + pam_passwd = password; + + /* + * Set the application data portion of the conversation struct This is + * later used inside the PAM conversation to pass the password to the + * authentication module. + */ + pam_passw_conv.appdata_ptr = (char *) password; /* from password above, + * not allocated */ + + /* Optionally, one can set the service name in pool_hba.conf */ + if (frontend->auth_arg && frontend->auth_arg[0] != '\0') + retval = pam_start(frontend->auth_arg, "pgpool@", + &pam_passw_conv, &pamh); + else + retval = pam_start(PGPOOL_PAM_SERVICE, "pgpool@", + &pam_passw_conv, &pamh); + + if (retval != PAM_SUCCESS) + { + pool_log("could not create PAM authenticator: %s", + pam_strerror(pamh, retval)); + pam_passwd = NULL; /* Unset pam_passwd */ + return POOL_ERROR; + } + + retval = pam_set_item(pamh, PAM_USER, user); + if (retval != PAM_SUCCESS) + { + pool_log("pam_set_item(PAM_USER) failed: %s", + pam_strerror(pamh, retval)); + pam_passwd = NULL; /* Unset pam_passwd */ + return POOL_ERROR; + } + + retval = pam_set_item(pamh, PAM_CONV, &pam_passw_conv); + if (retval != PAM_SUCCESS) + { + pool_log("pam_set_item(PAM_CONV) failed: %s", + pam_strerror(pamh, retval)); + pam_passwd = NULL; /* Unset pam_passwd */ + return POOL_ERROR; + } + + retval = pam_authenticate(pamh, 0); + if (retval != PAM_SUCCESS) /* service name does not exist */ + { + pool_log("pam_authenticate failed: %s", + pam_strerror(pamh, retval)); + pam_passwd = NULL; /* Unset pam_passwd */ + return POOL_ERROR; + } + + retval = pam_acct_mgmt(pamh, 0); + if (retval != PAM_SUCCESS) + { + pool_log("pam_acct_mgmt failed: %s", + pam_strerror(pamh, retval)); + pam_passwd = NULL; /* Unset pam_passwd */ + return POOL_ERROR; + } + + retval = pam_end(pamh, retval); + if (retval != PAM_SUCCESS) + { + pool_log("could not release PAM authenticator: %s", + pam_strerror(pamh, retval)); + } + + pam_passwd = NULL; /* Unset pam_passwd */ + + return (retval == PAM_SUCCESS ? POOL_CONTINUE : POOL_ERROR); +} + +#endif /* USE_PAM */ diff --git a/pool_hba.conf.sample b/pool_hba.conf.sample new file mode 100644 index 000000000..b2fe374d9 --- /dev/null +++ b/pool_hba.conf.sample @@ -0,0 +1,69 @@ +# pgpool Client Authentication Configuration File +# =============================================== +# +# The format rule in this file follows the rules in the PostgreSQL +# Administrator's Guide. Refer to chapter "Client Authentication" for a +# complete description. A short synopsis follows. +# +# This file controls: which hosts are allowed to connect, how clients +# are authenticated, which user names they can use, which databases they +# can access. Records take one of these forms: +# +# local DATABASE USER METHOD [OPTION] +# host DATABASE USER CIDR-ADDRESS METHOD [OPTION] +# hostnossl DATABASE USER CIDR-ADDRESS METHOD [OPTION] +# +# (The uppercase items must be replaced by actual values.) +# +# The first field is the connection type: "local" is a Unix-domain socket, +# "host" is a plain TCP/IP socket since pgpool currently doest not support +# SSL connection. "hostnossl" is also a plain TCP/IP socket. +# +# DATABASE can be "all", "sameuser", a database name, or a comma-separated +# list thereof. Note that "samegroup" like in PostgreSQL's pg_hba.conf +# file is not supported, since pgpool does not know which group a user +# belongs to. Also note that the database specified here may not exist in +# the backend PostgreSQL. pgpool will authenticate based on the database's +# name, not based on whether it exists or not. +# +# USER can be "all", a user name, or a comma-separated list thereof. In +# both the DATABASE and USER fields you can also write a file name prefixed +# with "@" to include names from a separate file. Note that a group name +# prefixed with "+" like in PostgreSQL's pg_hba.conf file is not supported +# because of the same reason as "samegroup" token. Also note that a user +# name specified here may not exist in the backend PostgreSQL. pgpool will +# authenticate based on the user's name, not based on whether he/she exists. +# +# CIDR-ADDRESS specifies the set of hosts the record matches. +# It is made up of an IP address and a CIDR mask that is an integer +# (between 0 and 32 (IPv4) that specifies the number of significant bits in +# the mask. Alternatively, you can write an IP address and netmask in +# separate columns to specify the set of hosts. +# +# METHOD can be "trust", "reject", or "pam". Note that "pam" sends passwords +# in clear text. +# +# OPTION is the name of the PAM service. Default service name is "pgpool" +# +# Database and user names containing spaces, commas, quotes and other special +# characters must be quoted. Quoting one of the keywords "all" or "sameuser" +# makes the name lose its special character, and just match a database or +# username with that name. +# +# This file is read on pgpool startup. If you edit the file on a running +# system, you have to restart the pgpool for the changes to take effect. + +# Put your actual configuration here +# ---------------------------------- +# +# If you want to allow non-local connections, you need to add more +# "host" records. In that case you will also need to make pgpool listen +# on a non-local interface via the listen_addresses configuration parameter. +# + +# TYPE DATABASE USER CIDR-ADDRESS METHOD + +# "local" is for Unix domain socket connections only +local all all trust +# IPv4 local connections: +host all all 127.0.0.1/32 trust diff --git a/pool_ip.c b/pool_ip.c new file mode 100644 index 000000000..1a11c5f14 --- /dev/null +++ b/pool_ip.c @@ -0,0 +1,565 @@ +/* -*-pgsql-c-*- */ +/* + * + * $Header$ + * + * This file was imported from PostgreSQL 8.0.8 source code. + * See below for the copyright and description. + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Portions Copyright (c) 2003-2007 PgPool Global Development Group + * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of the + * author not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. The author makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * ------------------------------ + * + * + * This file and the IPV6 implementation were initially provided by + * Nigel Kukard <nkukard@lbsd.net>, Linux Based Systems Design + * http://www.lbsd.net. + * + * pool_ip.c.: IPv6-aware network access. + * + */ + +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/un.h> +#include "pool.h" +#include "pool_ip.h" + + +static int rangeSockAddrAF_INET(const struct sockaddr_in * addr, + const struct sockaddr_in * netaddr, + const struct sockaddr_in * netmask); + +#ifdef HAVE_IPV6 +static int rangeSockAddrAF_INET6(const struct sockaddr_in6 * addr, + const struct sockaddr_in6 * netaddr, + const struct sockaddr_in6 * netmask); +#endif + +static int getaddrinfo_unix(const char *path, + const struct addrinfo * hintsp, + struct addrinfo ** result); + +static int getnameinfo_unix(const struct sockaddr_un * sa, int salen, + char *node, int nodelen, + char *service, int servicelen, + int flags); + +/* + * pool_getnameinfo_all - get name info for Unix, IPv4 and IPv6 sockets + * caller MUST allocate NI_MAXHOST, NI_MAXSERV bytes for remote_host and remote_port + */ +void pool_getnameinfo_all(SockAddr *saddr, char *remote_host, char *remote_port) +{ + remote_host[0] = '\0'; + remote_port[0] = '\0'; + + if (getnameinfo_all(&saddr->addr, saddr->salen, + remote_host, NI_MAXHOST, + remote_port, NI_MAXSERV, + (pool_config->log_hostname ? 0 : NI_NUMERICHOST) | NI_NUMERICSERV)) + { + int ret = getnameinfo_all(&saddr->addr, saddr->salen, + remote_host, NI_MAXHOST, + remote_port, NI_MAXSERV, + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret) + pool_error("getnameinfo_all() failed: %s", gai_strerror(ret)); + } +} + +/* + * getaddrinfo_all - get address info for Unix, IPv4 and IPv6 sockets + */ +int +getaddrinfo_all(const char *hostname, const char *servname, + const struct addrinfo * hintp, struct addrinfo ** result) +{ + /* not all versions of getaddrinfo() zero *result on failure */ + *result = NULL; + + if (hintp->ai_family == AF_UNIX) + return getaddrinfo_unix(servname, hintp, result); + + /* NULL has special meaning to getaddrinfo(). */ + return getaddrinfo((!hostname || hostname[0] == '\0') ? NULL : hostname, + servname, hintp, result); +} + + +/* + * freeaddrinfo_all - free addrinfo structures for IPv4, IPv6, or Unix + * + * Note: the ai_family field of the original hint structure must be passed + * so that we can tell whether the addrinfo struct was built by the system's + * getaddrinfo() routine or our own getaddrinfo_unix() routine. Some versions + * of getaddrinfo() might be willing to return AF_UNIX addresses, so it's + * not safe to look at ai_family in the addrinfo itself. + */ +void +freeaddrinfo_all(int hint_ai_family, struct addrinfo * ai) +{ + if (hint_ai_family == AF_UNIX) + { + /* struct was built by getaddrinfo_unix (see getaddrinfo_all) */ + while (ai != NULL) + { + struct addrinfo *p = ai; + + ai = ai->ai_next; + free(p->ai_addr); + free(p); + } + } + else + { + /* struct was built by getaddrinfo() */ + if (ai != NULL) + freeaddrinfo(ai); + } +} + + +/* + * getnameinfo_all - get name info for Unix, IPv4 and IPv6 sockets + * + * The API of this routine differs from the standard getnameinfo() definition + * in two ways: first, the addr parameter is declared as sockaddr_storage + * rather than struct sockaddr, and second, the node and service fields are + * guaranteed to be filled with something even on failure return. + */ +int +getnameinfo_all(const struct sockaddr_storage * addr, int salen, + char *node, int nodelen, + char *service, int servicelen, + int flags) +{ + int rc; + + if (addr && addr->ss_family == AF_UNIX) + rc = getnameinfo_unix((const struct sockaddr_un *) addr, salen, + node, nodelen, + service, servicelen, + flags); + else + rc = getnameinfo((const struct sockaddr *) addr, salen, + node, nodelen, + service, servicelen, + flags); + + if (rc != 0) + { + if (node) + strncpy(node, "???", nodelen); + if (service) + strncpy(service, "???", servicelen); + } + + return rc; +} + + +#ifndef HAVE_GAI_STRERROR +const char * +gai_strerror(int errcode) +{ +#ifdef HAVE_HSTRERROR + int hcode; + + switch (errcode) + { + case EAI_NONAME: + hcode = HOST_NOT_FOUND; + break; + case EAI_AGAIN: + hcode = TRY_AGAIN; + break; + case EAI_FAIL: + default: + hcode = NO_RECOVERY; + break; + } + + return hstrerror(hcode); +#else /* !HAVE_HSTRERROR */ + + switch (errcode) + { + case EAI_NONAME: + return "Unknown host"; + case EAI_AGAIN: + return "Host name lookup failure"; + /* Errors below are probably WIN32 only */ +#ifdef EAI_BADFLAGS + case EAI_BADFLAGS: + return "Invalid argument"; +#endif +#ifdef EAI_FAMILY + case EAI_FAMILY: + return "Address family not supported"; +#endif +#ifdef EAI_MEMORY + case EAI_MEMORY: + return "Not enough memory"; +#endif +#ifdef EAI_NODATA +#ifndef WIN32_ONLY_COMPILER /* MSVC complains because another case has the + * same value */ + case EAI_NODATA: + return "No host data of that type was found"; +#endif +#endif +#ifdef EAI_SERVICE + case EAI_SERVICE: + return "Class type not found"; +#endif +#ifdef EAI_SOCKTYPE + case EAI_SOCKTYPE: + return "Socket type not supported"; +#endif + default: + return "Unknown server error"; + } +#endif /* HAVE_HSTRERROR */ +} +#endif /* HAVE_GAI_STRERROR */ + + +/* + * getaddrinfo_unix - get unix socket info using IPv6-compatible API + * + * Bugs: only one addrinfo is set even though hintsp is NULL or + * ai_socktype is 0 + * AI_CANONNAME is not supported. + * + */ +static int +getaddrinfo_unix(const char *path, const struct addrinfo * hintsp, + struct addrinfo ** result) +{ + struct addrinfo hints; + struct addrinfo *aip; + struct sockaddr_un *unp; + + *result = NULL; + + memset(&hints, 0, sizeof(hints)); + + if (strlen(path) >= sizeof(unp->sun_path)) + return EAI_FAIL; + + if (hintsp == NULL) + { + hints.ai_family = AF_UNIX; + hints.ai_socktype = SOCK_STREAM; + } + else + memcpy(&hints, hintsp, sizeof(hints)); + + if (hints.ai_socktype == 0) + hints.ai_socktype = SOCK_STREAM; + + if (hints.ai_family != AF_UNIX) + { + /* shouldn't have been called */ + return EAI_FAIL; + } + + aip = calloc(1, sizeof(struct addrinfo)); + if (aip == NULL) + return EAI_MEMORY; + + unp = calloc(1, sizeof(struct sockaddr_un)); + if (unp == NULL) + { + free(aip); + return EAI_MEMORY; + } + + aip->ai_family = AF_UNIX; + aip->ai_socktype = hints.ai_socktype; + aip->ai_protocol = hints.ai_protocol; + aip->ai_next = NULL; + aip->ai_canonname = NULL; + *result = aip; + + unp->sun_family = AF_UNIX; + aip->ai_addr = (struct sockaddr *) unp; + aip->ai_addrlen = sizeof(struct sockaddr_un); + + strcpy(unp->sun_path, path); + +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + unp->sun_len = sizeof(struct sockaddr_un); +#endif + + return 0; +} + +/* + * Convert an address to a hostname. + */ +static int +getnameinfo_unix(const struct sockaddr_un * sa, int salen, + char *node, int nodelen, + char *service, int servicelen, + int flags) +{ + int ret = -1; + + /* Invalid arguments. */ + if (sa == NULL || sa->sun_family != AF_UNIX || + (node == NULL && service == NULL)) + return EAI_FAIL; + + /* We don't support those. */ + if ((node && !(flags & NI_NUMERICHOST)) + || (service && !(flags & NI_NUMERICSERV))) + return EAI_FAIL; + + if (node) + { + ret = snprintf(node, nodelen, "%s", "[local]"); + if (ret == -1 || ret > nodelen) + return EAI_MEMORY; + } + + if (service) + { + ret = snprintf(service, servicelen, "%s", sa->sun_path); + if (ret == -1 || ret > servicelen) + return EAI_MEMORY; + } + + return 0; +} + + +/* + * rangeSockAddr - is addr within the subnet specified by netaddr/netmask ? + * + * Note: caller must already have verified that all three addresses are + * in the same address family; and AF_UNIX addresses are not supported. + */ +int +rangeSockAddr(const struct sockaddr_storage * addr, + const struct sockaddr_storage * netaddr, + const struct sockaddr_storage * netmask) +{ + if (addr->ss_family == AF_INET) + return rangeSockAddrAF_INET((struct sockaddr_in *) addr, + (struct sockaddr_in *) netaddr, + (struct sockaddr_in *) netmask); +#ifdef HAVE_IPV6 + else if (addr->ss_family == AF_INET6) + return rangeSockAddrAF_INET6((struct sockaddr_in6 *) addr, + (struct sockaddr_in6 *) netaddr, + (struct sockaddr_in6 *) netmask); +#endif + else + return 0; +} + +static int +rangeSockAddrAF_INET(const struct sockaddr_in * addr, + const struct sockaddr_in * netaddr, + const struct sockaddr_in * netmask) +{ + if (((addr->sin_addr.s_addr ^ netaddr->sin_addr.s_addr) & + netmask->sin_addr.s_addr) == 0) + return 1; + else + return 0; +} + + +#ifdef HAVE_IPV6 +static int +rangeSockAddrAF_INET6(const struct sockaddr_in6 * addr, + const struct sockaddr_in6 * netaddr, + const struct sockaddr_in6 * netmask) +{ + int i; + + for (i = 0; i < 16; i++) + { + if (((addr->sin6_addr.s6_addr[i] ^ netaddr->sin6_addr.s6_addr[i]) & + netmask->sin6_addr.s6_addr[i]) != 0) + return 0; + } + + return 1; +} +#endif + +/* + * SockAddr_cidr_mask - make a network mask of the appropriate family + * and required number of significant bits + * + * The resulting mask is placed in *mask, which had better be big enough. + * + * Return value is 0 if okay, -1 if not. + */ +int +SockAddr_cidr_mask(struct sockaddr_storage * mask, char *numbits, int family) +{ + long bits; + char *endptr; + + bits = strtol(numbits, &endptr, 10); + + if (*numbits == '\0' || *endptr != '\0') + return -1; + + switch (family) + { + case AF_INET: + { + struct sockaddr_in mask4; + long maskl; + + if (bits < 0 || bits > 32) + return -1; + /* avoid "x << 32", which is not portable */ + if (bits > 0) + maskl = (0xffffffffUL << (32 - (int) bits)) + & 0xffffffffUL; + else + maskl = 0; + mask4.sin_addr.s_addr = htonl(maskl); + memcpy(mask, &mask4, sizeof(mask4)); + break; + } + +#ifdef HAVE_IPV6 + case AF_INET6: + { + struct sockaddr_in6 mask6; + int i; + + if (bits < 0 || bits > 128) + return -1; + for (i = 0; i < 16; i++) + { + if (bits <= 0) + mask6.sin6_addr.s6_addr[i] = 0; + else if (bits >= 8) + mask6.sin6_addr.s6_addr[i] = 0xff; + else + { + mask6.sin6_addr.s6_addr[i] = + (0xff << (8 - (int) bits)) & 0xff; + } + bits -= 8; + } + memcpy(mask, &mask6, sizeof(mask6)); + break; + } +#endif + default: + return -1; + } + + mask->ss_family = family; + return 0; +} + + +#ifdef HAVE_IPV6 + +/* + * promote_v4_to_v6_addr --- convert an AF_INET addr to AF_INET6, using + * the standard convention for IPv4 addresses mapped into IPv6 world + * + * The passed addr is modified in place; be sure it is large enough to + * hold the result! Note that we only worry about setting the fields + * that rangeSockAddr will look at. + */ +static void +promote_v4_to_v6_addr(struct sockaddr_storage * addr) +{ + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + uint32 ip4addr; + + memcpy(&addr4, addr, sizeof(addr4)); + ip4addr = ntohl(addr4.sin_addr.s_addr); + + memset(&addr6, 0, sizeof(addr6)); + + addr6.sin6_family = AF_INET6; + + addr6.sin6_addr.s6_addr[10] = 0xff; + addr6.sin6_addr.s6_addr[11] = 0xff; + addr6.sin6_addr.s6_addr[12] = (ip4addr >> 24) & 0xFF; + addr6.sin6_addr.s6_addr[13] = (ip4addr >> 16) & 0xFF; + addr6.sin6_addr.s6_addr[14] = (ip4addr >> 8) & 0xFF; + addr6.sin6_addr.s6_addr[15] = (ip4addr) & 0xFF; + + memcpy(addr, &addr6, sizeof(addr6)); +} + +/* + * promote_v4_to_v6_mask --- convert an AF_INET netmask to AF_INET6, using + * the standard convention for IPv4 addresses mapped into IPv6 world + * + * This must be different from promote_v4_to_v6_addr because we want to + * set the high-order bits to 1's not 0's. + * + * The passed addr is modified in place; be sure it is large enough to + * hold the result! Note that we only worry about setting the fields + * that rangeSockAddr will look at. + */ +static void +promote_v4_to_v6_mask(struct sockaddr_storage * addr) +{ + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + uint32 ip4addr; + int i; + + memcpy(&addr4, addr, sizeof(addr4)); + ip4addr = ntohl(addr4.sin_addr.s_addr); + + memset(&addr6, 0, sizeof(addr6)); + + addr6.sin6_family = AF_INET6; + + for (i = 0; i < 12; i++) + addr6.sin6_addr.s6_addr[i] = 0xff; + + addr6.sin6_addr.s6_addr[12] = (ip4addr >> 24) & 0xFF; + addr6.sin6_addr.s6_addr[13] = (ip4addr >> 16) & 0xFF; + addr6.sin6_addr.s6_addr[14] = (ip4addr >> 8) & 0xFF; + addr6.sin6_addr.s6_addr[15] = (ip4addr) & 0xFF; + + memcpy(addr, &addr6, sizeof(addr6)); +} + +#endif /* HAVE_IPV6 */ + diff --git a/pool_ip.h b/pool_ip.h new file mode 100644 index 000000000..159a3292a --- /dev/null +++ b/pool_ip.h @@ -0,0 +1,65 @@ +/* -*-pgsql-c-*- */ +/* + * + * $Header$ + * + * This file was imported from PostgreSQL 8.0.8 source code. + * See below for the copyright and description. + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Portions Copyright (c) 2003-2007 PgPool Global Development Group + * Portions Copyright (c) 2003-2005, PostgreSQL Global Development Group + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of the + * author not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. The author makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * pool_ip.h.: Definitions for IPv6-aware network access. + * + */ + +#ifndef POOL_IP_H +#define POOL_IP_H + +#include "pool_type.h" + +extern int getaddrinfo_all(const char *hostname, const char *servname, + const struct addrinfo * hintp, + struct addrinfo ** result); +extern void freeaddrinfo_all(int hint_ai_family, struct addrinfo * ai); + +extern int getnameinfo_all(const struct sockaddr_storage * addr, int salen, + char *node, int nodelen, + char *service, int servicelen, + int flags); + +extern int rangeSockAddr(const struct sockaddr_storage * addr, + const struct sockaddr_storage * netaddr, + const struct sockaddr_storage * netmask); + +extern int SockAddr_cidr_mask(struct sockaddr_storage * mask, + char *numbits, int family); + +/* imported from PostgreSQL getaddrinfo.c */ +#ifndef HAVE_GAI_STRERROR +extern const char * gai_strerror(int errcode); +#endif /* HAVE_GAI_STRERROR */ + +#ifdef HAVE_IPV6 +extern void promote_v4_to_v6_addr(struct sockaddr_storage * addr); +extern void promote_v4_to_v6_mask(struct sockaddr_storage * addr); +#endif + +#define IS_AF_INET(fam) ((fam) == AF_INET) +#define IS_AF_UNIX(fam) ((fam) == AF_UNIX) + +#endif /* IP_H */ diff --git a/pool_path.c b/pool_path.c new file mode 100644 index 000000000..7129f1781 --- /dev/null +++ b/pool_path.c @@ -0,0 +1,193 @@ +/* -*-pgsql-c-*- */ +/* + * + * $Header$ + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Portions Copyright (c) 2003-2007, PgPool Global Development Group + * Portions Copyright (c) 2004, PostgreSQL Global Development Group + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of the + * author not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. The author makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * pool_path.c.: small functions to manipulate paths + * + */ + +#include "pool_type.h" +#include "pool_path.h" +#include <stdio.h> +#include <string.h> + +static void trim_directory(char *path); +static void trim_trailing_separator(char *path); + +/* + * get_parent_directory + * + * Modify the given string in-place to name the parent directory of the + * named file. + */ +void get_parent_directory(char *path) +{ + trim_directory(path); +} + + +/* + * trim_directory + * + * Trim trailing directory from path, that is, remove any trailing slashes, + * the last pathname component, and the slash just ahead of it --- but never + * remove a leading slash. + */ +static void trim_directory(char *path) +{ + char *p; + + if (path[0] == '\0') + return; + + /* back up over trailing slash(es) */ + for (p = path + strlen(path) - 1; IS_DIR_SEP(*p) && p > path; p--); + + /* back up over directory name */ + for (; !IS_DIR_SEP(*p) && p > path; p--); + + /* if multiple slashes before directory name, remove 'em all */ + for (; p > path && IS_DIR_SEP(*(p - 1)); p--); + + /* don't erase a leading slash */ + if (p == path && IS_DIR_SEP(*p)) + p++; + + *p = '\0'; +} + + +/* + * join_path_components - join two path components, inserting a slash + * + * ret_path is the output area (must be of size MAXPGPATH) + * + * ret_path can be the same as head, but not the same as tail. + */ +void join_path_components(char *ret_path, const char *head, const char *tail) +{ + if (ret_path != head) + StrNCpy(ret_path, head, MAXPGPATH); + + /* + * Remove any leading "." and ".." in the tail component, + * adjusting head as needed. + */ + for (;;) + { + if (tail[0] == '.' && IS_DIR_SEP(tail[1])) + { + tail += 2; + } + else if (tail[0] == '.' && tail[1] == '\0') + { + tail += 1; + break; + } + else if (tail[0] == '.' && tail[1] == '.' && IS_DIR_SEP(tail[2])) + { + trim_directory(ret_path); + tail += 3; + } + else if (tail[0] == '.' && tail[1] == '.' && tail[2] == '\0') + { + trim_directory(ret_path); + tail += 2; + break; + } + else + break; + } + + if (*tail) + snprintf(ret_path + strlen(ret_path), MAXPGPATH - strlen(ret_path), "/%s", tail); +} + + +/* + * Clean up path by: + * o remove trailing slash + * o remove duplicate adjacent separators + * o remove trailing '.' + * o process trailing '..' ourselves + */ +void canonicalize_path(char *path) +{ + char *p, *to_p; + bool was_sep = false; + + /* + * Removing the trailing slash on a path means we never get ugly + * double trailing slashes. + */ + trim_trailing_separator(path); + + /* + * Remove duplicate adjacent separators + */ + p = path; + to_p = p; + for (; *p; p++, to_p++) + { + /* Handle many adjacent slashes, like "/a///b" */ + while (*p == '/' && was_sep) + p++; + if (to_p != p) + *to_p = *p; + was_sep = (*p == '/'); + } + *to_p = '\0'; + + /* + * Remove any trailing uses of "." and process ".." ourselves + */ + for (;;) + { + int len = strlen(path); + + if (len > 2 && strcmp(path + len - 2, "/.") == 0) + trim_directory(path); + else if (len > 3 && strcmp(path + len - 3, "/..") == 0) + { + trim_directory(path); + trim_directory(path); + /* remove directory above */ + } + else + break; + } +} + + +/* + * trim_trailing_separator + * + * trim off trailing slashes, but not a leading slash + */ +static void trim_trailing_separator(char *path) +{ + char *p; + + p = path + strlen(path); + if (p > path) + for (p--; p > path && IS_DIR_SEP(*p); p--) + *p = '\0'; +} diff --git a/pool_path.h b/pool_path.h new file mode 100644 index 000000000..a1cc9a48f --- /dev/null +++ b/pool_path.h @@ -0,0 +1,81 @@ +/* -*-pgsql-c-*- */ +/* + * + * $Header$ + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Portions Copyright (c) 2003-2007, PgPool Global Development Group + * Portions Copyright (c) 2004, PostgreSQL Global Development Group + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of the + * author not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. The author makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * pool_path.h.: interface to pool_path.c + * + */ + +#ifndef POOL_PATH_H +#define POOL_PATH_H + +/* + * MAXPGPATH: standard size of a pathname buffer in PostgreSQL (hence, + * maximum usable pathname length is one less). + * + * We'd use a standard system header symbol for this, if there weren't + * so many to choose from: MAXPATHLEN, MAX_PATH, PATH_MAX are all + * defined by different "standards", and often have different values + * on the same platform! So we just punt and use a reasonably + * generous setting here. + */ +#define MAXPGPATH 1024 + +#define IS_DIR_SEP(ch) ((ch) == '/') +#define is_absolute_path(filename) \ +( \ + ((filename)[0] == '/') \ +) + +/* + * StrNCpy + * Like standard library function strncpy(), except that result string + * is guaranteed to be null-terminated --- that is, at most N-1 bytes + * of the source string will be kept. + * Also, the macro returns no result (too hard to do that without + * evaluating the arguments multiple times, which seems worse). + * + * BTW: when you need to copy a non-null-terminated string (like a text + * datum) and add a null, do not do it with StrNCpy(..., len+1). That + * might seem to work, but it fetches one byte more than there is in the + * text object. One fine day you'll have a SIGSEGV because there isn't + * another byte before the end of memory. Don't laugh, we've had real + * live bug reports from real live users over exactly this mistake. + * Do it honestly with "memcpy(dst,src,len); dst[len] = '\0';", instead. + */ +#define StrNCpy(dst,src,len) \ + do \ + { \ + char * _dst = (dst); \ + size_t _len = (len); \ +\ + if (_len > 0) \ + { \ + strncpy(_dst, (src), _len); \ + _dst[_len-1] = '\0'; \ + } \ + } while (0) + +extern void get_parent_directory(char *path); +extern void join_path_components(char *ret_path, const char *head, const char *tail); +extern void canonicalize_path(char *path); + +#endif /* POOL_PATH_H */ diff --git a/ps_status.c b/ps_status.c new file mode 100644 index 000000000..b788aed08 --- /dev/null +++ b/ps_status.c @@ -0,0 +1,386 @@ +/* -*-pgsql-c-*- */ +/* + * + * $Header$ + * + * This file was imported from PostgreSQL source code. + * See below for the copyright and description. + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Portions Copyright (c) 2003-2007 PgPool Global Development Group + * + */ +/*-------------------------------------------------------------------- + * ps_status.c + * + * Routines to support changing the ps display of PostgreSQL backends + * to contain some useful information. Mechanism differs wildly across + * platforms. + * + * $PostgreSQL: pgsql/src/backend/utils/misc/ps_status.c,v 1.33 2006/10/04 00:30:04 momjian Exp $ + * + * Copyright (c) 2000-2006, PostgreSQL Global Development Group + * various details abducted from various places + *-------------------------------------------------------------------- + */ + +#include <unistd.h> +#ifdef HAVE_SYS_PSTAT_H +#include <sys/pstat.h> /* for HP-UX */ +#endif +#ifdef HAVE_PS_STRINGS +#include <machine/vmparam.h> /* for old BSD */ +#include <sys/exec.h> +#endif +#if defined(__darwin__) +#include <crt_externs.h> +#endif + +#include "pool.h" +#include <stdlib.h> +#include <string.h> + +extern char **environ; +bool update_process_title = true; + + +/* + * Alternative ways of updating ps display: + * + * PS_USE_SETPROCTITLE + * use the function setproctitle(const char *, ...) + * (newer BSD systems) + * PS_USE_PSTAT + * use the pstat(PSTAT_SETCMD, ) + * (HPUX) + * PS_USE_PS_STRINGS + * assign PS_STRINGS->ps_argvstr = "string" + * (some BSD systems) + * PS_USE_CHANGE_ARGV + * assign argv[0] = "string" + * (some other BSD systems) + * PS_USE_CLOBBER_ARGV + * write over the argv and environment area + * (most SysV-like systems) + * PS_USE_WIN32 + * push the string out as the name of a Windows event + * PS_USE_NONE + * don't update ps display + * (This is the default, as it is safest.) + */ +#if defined(HAVE_SETPROCTITLE) +#define PS_USE_SETPROCTITLE +#elif defined(HAVE_PSTAT) && defined(PSTAT_SETCMD) +#define PS_USE_PSTAT +#elif defined(HAVE_PS_STRINGS) +#define PS_USE_PS_STRINGS +#elif (defined(BSD) || defined(__bsdi__) || defined(__hurd__)) && !defined(__darwin__) +#define PS_USE_CHANGE_ARGV +#elif defined(__linux__) || defined(_AIX) || defined(__sgi) || (defined(sun) && !defined(BSD)) || defined(ultrix) || defined(__ksr__) || defined(__osf__) || defined(__svr4__) || defined(__svr5__) || defined(__darwin__) +#define PS_USE_CLOBBER_ARGV +#elif defined(WIN32) +#define PS_USE_WIN32 +#else +#define PS_USE_NONE +#endif + + +/* Different systems want the buffer padded differently */ +#if defined(_AIX) || defined(__linux__) || defined(__svr4__) +#define PS_PADDING '\0' +#else +#define PS_PADDING ' ' +#endif + + +#ifndef PS_USE_CLOBBER_ARGV +/* all but one options need a buffer to write their ps line in */ +#define PS_BUFFER_SIZE 256 +static char ps_buffer[PS_BUFFER_SIZE]; +static const size_t ps_buffer_size = PS_BUFFER_SIZE; +#else /* PS_USE_CLOBBER_ARGV */ +static char *ps_buffer; /* will point to argv area */ +static size_t ps_buffer_size; /* space determined at run time */ +#endif /* PS_USE_CLOBBER_ARGV */ + +static size_t ps_buffer_fixed_size; /* size of the constant prefix */ + +/* save the original argv[] location here */ +static int save_argc; +static char **save_argv; + + +/* + * Call this early in startup to save the original argc/argv values. + * If needed, we make a copy of the original argv[] array to preserve it + * from being clobbered by subsequent ps_display actions. + * + * (The original argv[] will not be overwritten by this routine, but may be + * overwritten during init_ps_display. Also, the physical location of the + * environment strings may be moved, so this should be called before any code + * that might try to hang onto a getenv() result.) + */ +char ** +save_ps_display_args(int argc, char **argv) +{ + save_argc = argc; + save_argv = argv; + +#if defined(PS_USE_CLOBBER_ARGV) + + /* + * If we're going to overwrite the argv area, count the available space. + * Also move the environment to make additional room. + */ + { + char *end_of_area = NULL; + char **new_environ; + int i; + + /* + * check for contiguous argv strings + */ + for (i = 0; i < argc; i++) + { + if (i == 0 || end_of_area + 1 == argv[i]) + end_of_area = argv[i] + strlen(argv[i]); + } + + if (end_of_area == NULL) /* probably can't happen? */ + { + ps_buffer = NULL; + ps_buffer_size = 0; + return argv; + } + + /* + * check for contiguous environ strings following argv + */ + for (i = 0; environ[i] != NULL; i++) + { + if (end_of_area + 1 == environ[i]) + end_of_area = environ[i] + strlen(environ[i]); + } + + ps_buffer = argv[0]; + ps_buffer_size = end_of_area - argv[0]; + + /* + * move the environment out of the way + */ + new_environ = (char **) malloc((i + 1) * sizeof(char *)); + for (i = 0; environ[i] != NULL; i++) + new_environ[i] = strdup(environ[i]); + new_environ[i] = NULL; + environ = new_environ; + } +#endif /* PS_USE_CLOBBER_ARGV */ + +#if defined(PS_USE_CHANGE_ARGV) || defined(PS_USE_CLOBBER_ARGV) + + /* + * If we're going to change the original argv[] then make a copy for + * argument parsing purposes. + * + * (NB: do NOT think to remove the copying of argv[], even though + * postmaster.c finishes looking at argv[] long before we ever consider + * changing the ps display. On some platforms, getopt() keeps pointers + * into the argv array, and will get horribly confused when it is + * re-called to analyze a subprocess' argument string if the argv storage + * has been clobbered meanwhile. Other platforms have other dependencies + * on argv[]. + */ + { + char **new_argv; + int i; + + new_argv = (char **) malloc((argc + 1) * sizeof(char *)); + for (i = 0; i < argc; i++) + new_argv[i] = strdup(argv[i]); + new_argv[argc] = NULL; + +#if defined(__darwin__) + + /* + * Darwin (and perhaps other NeXT-derived platforms?) has a static + * copy of the argv pointer, which we may fix like so: + */ + *_NSGetArgv() = new_argv; +#endif + + argv = new_argv; + } +#endif /* PS_USE_CHANGE_ARGV or PS_USE_CLOBBER_ARGV */ + + return argv; +} + +/* + * Call this once during subprocess startup to set the identification + * values. At this point, the original argv[] array may be overwritten. + */ +void +init_ps_display(const char *username, const char *dbname, + const char *host_info, const char *initial_str) +{ +#ifndef PS_USE_NONE + /* no ps display if you didn't call save_ps_display_args() */ + if (!save_argv) + return; +#ifdef PS_USE_CLOBBER_ARGV + /* If ps_buffer is a pointer, it might still be null */ + if (!ps_buffer) + return; +#endif + + /* + * Overwrite argv[] to point at appropriate space, if needed + */ + +#ifdef PS_USE_CHANGE_ARGV + save_argv[0] = ps_buffer; + save_argv[1] = NULL; +#endif /* PS_USE_CHANGE_ARGV */ + +#ifdef PS_USE_CLOBBER_ARGV + { + int i; + + /* make extra argv slots point at end_of_area (a NUL) */ + for (i = 1; i < save_argc; i++) + save_argv[i] = ps_buffer + ps_buffer_size; + } +#endif /* PS_USE_CLOBBER_ARGV */ + + /* + * Make fixed prefix of ps display. + */ + +#ifdef PS_USE_SETPROCTITLE + + /* + * apparently setproctitle() already adds a `progname:' prefix to the ps + * line + */ + snprintf(ps_buffer, ps_buffer_size, " "); +#else + snprintf(ps_buffer, ps_buffer_size, + "pgpool: "); +#endif + + ps_buffer_fixed_size = strlen(ps_buffer); + + set_ps_display(initial_str, true); +#endif /* not PS_USE_NONE */ +} + + + +/* + * Call this to update the ps status display to a fixed prefix plus an + * indication of what you're currently doing passed in the argument. + */ +void +set_ps_display(const char *activity, bool force) +{ + + if (!force && !update_process_title) + return; + +#ifndef PS_USE_NONE + +#ifdef PS_USE_CLOBBER_ARGV + /* If ps_buffer is a pointer, it might still be null */ + if (!ps_buffer) + return; +#endif + + /* Update ps_buffer to contain both fixed part and activity */ + strlcpy(ps_buffer + ps_buffer_fixed_size, activity, + ps_buffer_size - ps_buffer_fixed_size); + + /* Transmit new setting to kernel, if necessary */ + +#ifdef PS_USE_SETPROCTITLE + setproctitle("%s", ps_buffer); +#endif + +#ifdef PS_USE_PSTAT + { + union pstun pst; + + pst.pst_command = ps_buffer; + pstat(PSTAT_SETCMD, pst, strlen(ps_buffer), 0, 0); + } +#endif /* PS_USE_PSTAT */ + +#ifdef PS_USE_PS_STRINGS + PS_STRINGS->ps_nargvstr = 1; + PS_STRINGS->ps_argvstr = ps_buffer; +#endif /* PS_USE_PS_STRINGS */ + +#ifdef PS_USE_CLOBBER_ARGV + { + int buflen; + + /* pad unused memory */ + buflen = strlen(ps_buffer); + memset(ps_buffer + buflen, PS_PADDING, ps_buffer_size - buflen); + } +#endif /* PS_USE_CLOBBER_ARGV */ + +#ifdef PS_USE_WIN32 + { + /* + * Win32 does not support showing any changed arguments. To make it at + * all possible to track which backend is doing what, we create a + * named object that can be viewed with for example Process Explorer. + */ + static HANDLE ident_handle = INVALID_HANDLE_VALUE; + char name[PS_BUFFER_SIZE + 32]; + + if (ident_handle != INVALID_HANDLE_VALUE) + CloseHandle(ident_handle); + + sprintf(name, "pgident: %s", ps_buffer); + + ident_handle = CreateEvent(NULL, TRUE, FALSE, name); + } +#endif /* PS_USE_WIN32 */ +#endif /* not PS_USE_NONE */ +} + + +/* + * Returns what's currently in the ps display, in case someone needs + * it. Note that only the activity part is returned. On some platforms + * the string will not be null-terminated, so return the effective + * length into *displen. + */ +const char * +get_ps_display(int *displen) +{ +#ifdef PS_USE_CLOBBER_ARGV + size_t offset; + + /* If ps_buffer is a pointer, it might still be null */ + if (!ps_buffer) + { + *displen = 0; + return ""; + } + + /* Remove any trailing spaces to offset the effect of PS_PADDING */ + offset = ps_buffer_size; + while (offset > ps_buffer_fixed_size && ps_buffer[offset - 1] == PS_PADDING) + offset--; + + *displen = offset - ps_buffer_fixed_size; +#else + *displen = strlen(ps_buffer + ps_buffer_fixed_size); +#endif + + return ps_buffer + ps_buffer_fixed_size; +} diff --git a/strlcpy.c b/strlcpy.c new file mode 100644 index 000000000..011253b67 --- /dev/null +++ b/strlcpy.c @@ -0,0 +1,85 @@ +/* -*-pgsql-c-*- */ +/* + * + * $Header$ + * + * This file was imported from PostgreSQL source code. + * See below for the copyright and description. + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Portions Copyright (c) 2003-2007 PgPool Global Development Group + * + */ +/*------------------------------------------------------------------------- + * + * strlcpy.c + * strncpy done right + * + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/port/strlcpy.c,v 1.3 2006/10/04 00:30:14 momjian Exp $ + * + * This file was taken from OpenBSD and is used on platforms that don't + * provide strlcpy(). The OpenBSD copyright terms follow. + *------------------------------------------------------------------------- + */ + +/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <unistd.h> +#include "pool.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + * Function creation history: http://www.gratisoft.us/todd/papers/strlcpy.html + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) + { + while (--n != 0) + { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) + { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return (s - src - 1); /* count does not include NUL */ +} [parent: 8af298fbce50]