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]