src/loader.c \
src/main.c \
src/objects.c \
+ src/pam.c \
src/pktbuf.c \
src/pooler.c \
src/proto.c \
`./configure` also has flags `--enable-evdns` and `--disable-evdns` which
turn off automatic probing and force use of either `evdns` or `getaddrinfo_a()`.
+PAM authorization
+-----------------
+
+To enable PAM authorization `./configure` has a flag `--with-pam` (default value is no). When compiled with
+PAM support new global authorization type `pam` appears which can be used to validate users through PAM.
+
Building from GIT
-----------------
dnl Find libevent
AC_USUAL_LIBEVENT
+dnl Check for PAM authorization support
+pam_support=no
+AC_ARG_WITH(pam,
+ AC_HELP_STRING([--with-pam],[Enable PAM support]),
+ [ PAM=
+ if test "$withval" != no; then
+ have_pthreads=no
+ # Look for PAM header and lib
+ AC_CHECK_HEADERS(security/pam_appl.h, [have_pam_header=t])
+ AC_CHECK_HEADERS(pthread.h, [have_pthreads=yes])
+ AC_SEARCH_LIBS(pam_start, pam, [have_libpam=t])
+ AC_SEARCH_LIBS(pthread_create, pthread, [], [have_pthreads=no])
+ if test x"${have_pthreads}" != xyes; then
+ AC_MSG_ERROR([pthread library should be available for PAM support])
+ fi
+ if test x"${have_pam_header}" != x -a x"${have_libpam}" != x -a x"${have_pthreads}" = xyes; then
+ pam_support=yes
+ AC_DEFINE(HAVE_PAM, 1, [PAM support])
+ fi
+ fi
+ ], [])
+
##
## DNS backend
##
echo " evdns = $use_evdns"
echo " udns = $use_udns"
echo " tls = $tls_support"
+echo " PAM = $pam_support"
echo ""
How to authenticate users.
+pam
+ PAM is used to authenticate users, `auth_file`_ is ignored. This method is not
+ compatible with databases using `auth_user`_ option. Service name reported to
+ PAM is "pgbouncer". Also, `pam` is still not supported in HBA configuration file.
+
hba
Actual auth type is loaded from `auth_hba_file`_. This allows different
authentication methods different access paths. Example: connection
/*
* PgBouncer - Lightweight connection pooler for PostgreSQL.
- *
+ *
* Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ
- *
+ *
* Permission to use, copy, modify, and/or 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
#include "takeover.h"
#include "janitor.h"
#include "hba.h"
+#include "pam.h"
/* to avoid allocations will use static buffers */
#define MAX_DBNAME 64
#define AUTH_PEER 8
#define AUTH_HBA 9
#define AUTH_REJECT 10
+#define AUTH_PAM 11
/* type codes for weird pkts */
#define PKT_STARTUP_V2 0x20000
bool wait_for_welcome:1;/* client: no server yet in pool, cannot send welcome msg */
bool wait_for_user_conn:1;/* client: waiting for auth_conn server connection */
bool wait_for_user:1; /* client: waiting for auth_conn query results */
+ bool wait_for_auth:1; /* client: waiting for external auth (PAM) to be completed */
bool suspended:1; /* client/server: if the socket is suspended */
return container_of(slist->head.prev, PgSocket, head);
}
+bool requires_auth_file(int);
void load_config(void);
bool set_config_param(const char *key, const char *val);
void config_for_each(void (*param_cb)(void *arg, const char *name, const char *val, bool reloadable),
void *arg);
-
PgUser * add_db_user(PgDatabase *db, const char *name, const char *passwd) _MUSTCHECK;
PgUser * force_user(PgDatabase *db, const char *username, const char *passwd) _MUSTCHECK;
+PgUser * add_pam_user(const char *name, const char *passwd) _MUSTCHECK;
+
void accept_cancel_request(PgSocket *req);
void forward_cancel_request(PgSocket *server);
--- /dev/null
+/*
+ * PgBouncer - Lightweight connection pooler for PostgreSQL.
+ *
+ * Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ
+ *
+ * Permission to use, copy, modify, and/or 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.
+ */
+
+/*
+ * PAM support.
+ */
+
+/* Name of the service to be passed to PAM */
+#define PGBOUNCER_PAM_SERVICE "pgbouncer"
+
+/*
+ * Defines how many authorization requests can be placed to the waiting queue.
+ * When the queue is full calls to pam_auth_begin() will block until there is
+ * free space in the queue.
+ */
+#define PAM_REQUEST_QUEUE_SIZE 20
+
+void pam_init(void);
+void pam_auth_begin(PgSocket *client, const char *passwd);
+int pam_poll(void);
/*
* PgBouncer - Lightweight connection pooler for PostgreSQL.
- *
+ *
* Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ
- *
+ *
* Permission to use, copy, modify, and/or 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
if (sk->pool->db->auth_user && sk->auth_user && !find_user(sk->auth_user->name))
password = sk->auth_user->passwd;
+ /* PAM requires passwords as well since they are not stored externally */
+ if (cf_auth_type == AUTH_PAM && !find_user(sk->auth_user->name))
+ password = sk->auth_user->passwd;
+
return send_one_fd(admin, sbuf_socket(&sk->sbuf),
is_server_socket(sk) ? "server" : "client",
sk->auth_user ? sk->auth_user->name : NULL,
/*
* PgBouncer - Lightweight connection pooler for PostgreSQL.
- *
+ *
* Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ
- *
+ *
* Permission to use, copy, modify, and/or 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
*/
#include "bouncer.h"
+#include "pam.h"
#include <usual/pgutil.h>
int res;
int auth_type = client->client_auth_type;
+ /* Always use plain text to communicate with clients during PAM authorization */
+ if (auth_type == AUTH_PAM) {
+ auth_type = AUTH_PLAIN;
+ }
+
if (auth_type == AUTH_MD5) {
saltlen = 4;
get_random_bytes((void*)client->tmp_login_salt, saltlen);
break;
case AUTH_PLAIN:
case AUTH_MD5:
+ case AUTH_PAM:
ok = send_client_authreq(client);
break;
case AUTH_CERT:
return false;
}
client->auth_user = client->db->forced_user;
+ } else if (cf_auth_type == AUTH_PAM) {
+ if (client->db->auth_user) {
+ slog_error(client, "PAM can't be used together with database authorization");
+ disconnect_client(client, true, "bouncer config error");
+ return false;
+ }
+ /* Password will be set after successful authorization when not in takeover mode */
+ client->auth_user = add_pam_user(username, password);
+ if (!client->auth_user) {
+ slog_error(client, "set_pool(): failed to allocate new PAM user");
+ disconnect_client(client, true, "bouncer resources exhaustion");
+ return false;
+ }
} else {
/* the user clients wants to log in as */
client->auth_user = find_user(username);
return false;
}
- if (client->wait_for_welcome) {
+ if (client->wait_for_welcome || client->wait_for_auth) {
if (finish_client_login(client)) {
/* the packet was already parsed */
sbuf_prepare_skip(sbuf, pkt->len);
}
ok = mbuf_get_string(&pkt->data, &passwd);
- if (ok && check_client_passwd(client, passwd)) {
- if (!finish_client_login(client))
+
+ if (ok) {
+ if (client->client_auth_type == AUTH_PAM) {
+ if (!sbuf_pause(&client->sbuf)) {
+ disconnect_client(client, true, "pause failed");
+ return false;
+ }
+ pam_auth_begin(client, passwd);
return false;
- } else {
- disconnect_client(client, true, "Auth failed");
- return false;
+ }
+
+ if (check_client_passwd(client, passwd)) {
+ if (!finish_client_login(client))
+ return false;
+ } else {
+ disconnect_client(client, true, "Auth failed");
+ return false;
+ }
}
break;
case PKT_CANCEL:
}
return res;
}
-
return;
}
- if (cf_auth_type >= AUTH_TRUST)
+ if (requires_auth_file(cf_auth_type))
loader_users_check();
adns_zone_cache_maint(adns);
/*
* PgBouncer - Lightweight connection pooler for PostgreSQL.
- *
+ *
* Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ
- *
+ *
* Permission to use, copy, modify, and/or 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
{ "md5", AUTH_MD5 },
{ "cert", AUTH_CERT },
{ "hba", AUTH_HBA },
+#ifdef HAVE_PAM
+ { "pam", AUTH_PAM },
+#endif
{ NULL }
};
}
}
+/* Tells if the specified auth type requires data from the auth file. */
+bool requires_auth_file(int auth_type)
+{
+ /* For PAM authentication auth file is not used */
+ if (auth_type == AUTH_PAM)
+ return false;
+ return auth_type >= AUTH_TRUST;
+}
+
/* config loading, tries to be tolerant to errors */
void load_config(void)
{
ok = cf_load_file(&main_config, cf_config_file);
if (ok) {
/* load users if needed */
- if (cf_auth_type >= AUTH_TRUST)
+ if (requires_auth_file(cf_auth_type))
loader_users_check();
loaded = true;
} else if (!loaded) {
if (errno != EINTR)
log_warning("event_loop failed: %s", strerror(errno));
}
+ pam_poll();
per_loop_maint();
reuse_just_freed_objects();
rescue_timers();
check_limits();
admin_setup();
+ pam_init();
if (cf_reboot) {
if (check_old_process_unix()) {
/*
* PgBouncer - Lightweight connection pooler for PostgreSQL.
- *
+ *
* Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ
- *
+ *
* Permission to use, copy, modify, and/or 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
STATLIST(database_list);
STATLIST(pool_list);
+/* All locally defined users (in auth_file) are kept here. */
struct AATree user_tree;
+/*
+ * All PAM users are kept here. We need to differentiate two user
+ * lists to avoid user clashing for different authorization types,
+ * and because pam_user_tree is closer to PgDatabase.user_tree in
+ * logic.
+ */
+struct AATree pam_user_tree;
+
/*
* client and server objects will be pre-allocated
* they are always in either active or free lists
void init_objects(void)
{
aatree_init(&user_tree, user_node_cmp, NULL);
+ aatree_init(&pam_user_tree, user_node_cmp, NULL);
user_cache = slab_create("user_cache", sizeof(PgUser), 0, NULL, USUAL_ALLOC);
db_cache = slab_create("db_cache", sizeof(PgDatabase), 0, NULL, USUAL_ALLOC);
pool_cache = slab_create("pool_cache", sizeof(PgPool), 0, NULL, USUAL_ALLOC);
PgDatabase *db;
int len;
char *cs;
-
+
if (!cf_autodb_connstr)
return NULL;
return user;
}
+/* Add PAM user. The logic is same as in add_db_user */
+PgUser *add_pam_user(const char *name, const char *passwd)
+{
+ PgUser *user = NULL;
+ struct AANode *node;
+
+ node = aatree_search(&pam_user_tree, (uintptr_t)name);
+ user = node ? container_of(node, PgUser, tree_node) : NULL;
+
+ if (user == NULL) {
+ user = slab_alloc(user_cache);
+ if (!user)
+ return NULL;
+
+ list_init(&user->head);
+ list_init(&user->pool_list);
+ safe_strcpy(user->name, name, sizeof(user->name));
+
+ aatree_insert(&pam_user_tree, (uintptr_t)user->name, &user->tree_node);
+ user->pool_mode = POOL_INHERIT;
+ }
+ safe_strcpy(user->passwd, passwd, sizeof(user->passwd));
+ return user;
+}
+
/* create separate user object for storing server user info */
PgUser *force_user(PgDatabase *db, const char *name, const char *passwd)
{
static bool reset_on_release(PgSocket *server)
{
bool res;
-
+
Assert(server->state == SV_TESTED);
slog_debug(server, "Resetting: %s", cf_server_reset_query);
fatal("bad client state");
}
+ client->wait_for_auth = 0;
+
/* check if we know server signature */
if (!client->pool->welcome_msg_ready) {
log_debug("finish_client_login: no welcome message, pause");
PgSocket *server;
PktBuf tmp;
bool res;
-
+
/* if the database not found, it's an auto database -> registering... */
if (!db) {
db = register_auto_database(dbname);
if (db->forced_user) {
user = db->forced_user;
+ } else if (cf_auth_type == AUTH_PAM) {
+ user = add_pam_user(username, password);
} else {
user = find_user(username);
}
--- /dev/null
+/*
+ * PgBouncer - Lightweight connection pooler for PostgreSQL.
+ *
+ * Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ
+ *
+ * Permission to use, copy, modify, and/or 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.
+ */
+
+/*
+ * PAM authentication support.
+ */
+
+#include "bouncer.h"
+
+#ifdef HAVE_PAM
+
+#include <pthread.h>
+#include <security/pam_appl.h>
+
+/* The request is waiting in the queue or being authorized */
+#define PAM_STATUS_IN_PROGRESS 1
+/* The request was successfully authorized */
+#define PAM_STATUS_SUCCESS 2
+/* The request failed authorization */
+#define PAM_STATUS_FAILED 3
+
+/*
+ * How many microseconds to sleep between calls to pam_poll in
+ * pam_auth_begin when the queue is full.
+ * Default is 100 milliseconds.
+ */
+#define PAM_QUEUE_WAIT_SLEEP_MCS (100*1000)
+
+
+struct pam_auth_request {
+ /* The socket we check authorization for */
+ PgSocket *client;
+
+ /* CHECKME: The socket can be closed and reused while the request is waiting
+ * in the queue. Thus we need something to check the socket validity, and
+ * combination of its state and connect_time seems to be the good one.
+ */
+ usec_t connect_time;
+
+ /* Same as in client->remote_addr.
+ * We want to minimize synchronization between the authorization thread and
+ * the rest of pgbouncer, so the username and remote_addr are explicitly stored here.
+ */
+ PgAddr remote_addr;
+
+ /* The request status, one of the PAM_STATUS_* constants */
+ int status;
+
+ /* The username (same as in client->auth_user->name).
+ * See the comment for remote_addr.
+ */
+ char username[MAX_USERNAME];
+
+ /* password we should check for validity together with the socket's username */
+ char password[MAX_PASSWORD];
+};
+
+
+/*
+ * All incoming requests are kept in a queue which is implemented using a ring buffer.
+ * Such structure allows to avoid memory reallocation thus minimizing amount of
+ * synchronization to be done between threads.
+ *
+ * pam_first_taken_slot points to the first element in the queue;
+ * pam_first_free_slot points to the next slot after the last element in the queue.
+ *
+ * if pam_first_taken_slot == pam_first_free_slot then the queue is considered empty;
+ *
+ */
+volatile int pam_first_taken_slot;
+volatile int pam_first_free_slot;
+struct pam_auth_request pam_auth_queue[PAM_REQUEST_QUEUE_SIZE];
+
+pthread_t pam_worker_thread;
+
+/*
+ * Mutex serializes access to the queue's tail when we add new requests or
+ * check that we reach the end of the queue in the worker thread.
+ *
+ * Head and tail are modified only in the main thread. In theory, being sure that they
+ * are properly aligned we can access them directly without any risk for data races.
+ * Practically, it is better to secure them anyway to increase overall stability and
+ * provide faster notification of new requests via the condition variable.
+ */
+pthread_mutex_t pam_queue_tail_mutex;
+pthread_cond_t pam_data_available;
+
+/* Forward declarations */
+static void* pam_auth_worker(void *arg);
+static bool is_valid_socket(const struct pam_auth_request *request);
+static void pam_auth_finish(struct pam_auth_request *request);
+static bool pam_check_passwd(struct pam_auth_request *request);
+
+/*
+ * Initialize PAM subsystem.
+ */
+void pam_init(void)
+{
+ int rc;
+
+ pam_first_taken_slot = 0;
+ pam_first_free_slot = 0;
+
+ rc = pthread_mutex_init(&pam_queue_tail_mutex, NULL);
+ if (rc != 0) {
+ fatal("Failed to init a mutex");
+ }
+
+ rc = pthread_cond_init(&pam_data_available, NULL);
+ if (rc != 0) {
+ fatal("Failed to init a condition variable");
+ }
+
+ rc = pthread_create(&pam_worker_thread, NULL, &pam_auth_worker, NULL);
+ if (rc != 0) {
+ fatal("Failed to create the authentication thread");
+ }
+}
+
+/*
+ * Initiate the authentication request using PAM. The request result will be
+ * available during next calls to pam_poll(). The function might block if the
+ * request queue is full until there are free slots available.
+ * The function is called only from the main thread.
+ */
+void pam_auth_begin(PgSocket *client, const char *passwd)
+{
+ int next_free_slot = (pam_first_free_slot + 1) % PAM_REQUEST_QUEUE_SIZE;
+ struct pam_auth_request *request;
+
+ slog_debug(
+ client,
+ "pam_auth_begin(): pam_first_taken_slot=%d, pam_first_free_slot=%d",
+ pam_first_taken_slot, pam_first_free_slot);
+
+ client->wait_for_auth = 1;
+
+ /* Check that we have free slots in the queue, and if no
+ * then block until one is available.
+ */
+ if (next_free_slot == pam_first_taken_slot)
+ slog_debug(client, "PAM queue is full, waiting.");
+
+ while (next_free_slot == pam_first_taken_slot) {
+ if (pam_poll() == 0) {
+ /* Sleep a bit between consequent queue checks to avoid consuming too much CPU */
+ usleep(PAM_QUEUE_WAIT_SLEEP_MCS);
+ }
+ }
+
+ pthread_mutex_lock(&pam_queue_tail_mutex);
+
+ request = &pam_auth_queue[pam_first_free_slot];
+
+ request->client = client;
+ request->connect_time = client->connect_time;
+ request->status = PAM_STATUS_IN_PROGRESS;
+ memcpy(&request->remote_addr, &client->remote_addr, sizeof(client->remote_addr));
+ safe_strcpy(request->username, client->auth_user->name, MAX_USERNAME);
+ safe_strcpy(request->password, passwd, MAX_PASSWORD);
+
+ pam_first_free_slot = next_free_slot;
+
+ pthread_mutex_unlock(&pam_queue_tail_mutex);
+ pthread_cond_signal(&pam_data_available);
+}
+
+/*
+ * Checks for completed auth requests, returns amount of requests handled.
+ * The function is called only from the main thread.
+ */
+int pam_poll(void)
+{
+ struct pam_auth_request *request;
+ int count = 0;
+
+ while (pam_first_taken_slot != pam_first_free_slot) {
+ request = &pam_auth_queue[pam_first_taken_slot];
+
+ if (request->status == PAM_STATUS_IN_PROGRESS) {
+ /* When still-in-progress slot is found there is no need to continue
+ * the loop since all further requests will be in progress too.
+ */
+ break;
+ }
+
+ if (is_valid_socket(request)) {
+ pam_auth_finish(request);
+ }
+
+ count++;
+ pam_first_taken_slot = (pam_first_taken_slot + 1) % PAM_REQUEST_QUEUE_SIZE;
+ }
+
+ return count;
+}
+
+
+/*
+ * The authentication thread function.
+ * Performs scanning the queue for new requests and calling PAM for them.
+ */
+static void* pam_auth_worker(void *arg)
+{
+ int current_slot = pam_first_taken_slot;
+ struct pam_auth_request *request;
+
+ while (true) {
+
+ /* Wait for new data in the queue */
+ pthread_mutex_lock(&pam_queue_tail_mutex);
+
+ while (current_slot == pam_first_free_slot) {
+ pthread_cond_wait(&pam_data_available, &pam_queue_tail_mutex);
+ }
+
+ pthread_mutex_unlock(&pam_queue_tail_mutex);
+
+ log_debug("pam_auth_worker(): processing slot %d", current_slot);
+
+ /* We have at least one request in the queue */
+ request = &pam_auth_queue[current_slot];
+ current_slot = (current_slot + 1) % PAM_REQUEST_QUEUE_SIZE;
+
+ /* If the socket is already in the wrong state or reused then ignore it.
+ * This check is not safe and should not be trusted (the socket state
+ * might change exactly after it), but it helps to quickly filter out invalid
+ * sockets and thus save some time.
+ */
+ if (!is_valid_socket(request)) {
+ log_debug("pam_auth_worker(): invalid socket in slot %d", current_slot);
+ request->status = PAM_STATUS_FAILED;
+ continue;
+ }
+
+ if (pam_check_passwd(request)) {
+ request->status = PAM_STATUS_SUCCESS;
+ } else {
+ request->status = PAM_STATUS_FAILED;
+ }
+
+ log_debug("pam_auth_worker(): authorization completed, status=%d", request->status);
+ }
+
+ return NULL;
+}
+
+/*
+ * Checks that the socket is still valid to be processed.
+ * By validity we mean that it is still waiting in the login phase
+ * and was not reused for other connections.
+ */
+static bool is_valid_socket(const struct pam_auth_request *request) {
+ if (request->client->state != CL_LOGIN || request->client->connect_time != request->connect_time)
+ return false;
+ return true;
+}
+
+/*
+ * Finishes the handshake after successful or unsuccessful authorization.
+ * The function is only called from the main thread.
+ */
+static void pam_auth_finish(struct pam_auth_request *request)
+{
+ PgSocket *client = request->client;
+ bool authenticated = (request->status == PAM_STATUS_SUCCESS);
+
+ if (authenticated) {
+ safe_strcpy(client->auth_user->passwd, request->password, sizeof(client->auth_user->passwd));
+ sbuf_continue(&client->sbuf);
+ } else {
+ disconnect_client(client, true, "Auth failed");
+ }
+}
+
+static int pam_conversation(int msgc,
+ const struct pam_message **msgv,
+ struct pam_response **rspv,
+ void *authdata)
+{
+ struct pam_auth_request *request = (struct pam_auth_request *)authdata;
+ int i, rc;
+
+ if (msgc < 1 || msgv == NULL || request == NULL) {
+ log_debug(
+ "pam_conversation(): wrong input, msgc=%d, msgv=%p, authdata=%p",
+ msgc, msgv, authdata);
+ return PAM_CONV_ERR;
+ }
+
+ /* Allocate and fill with zeroes an array of responses.
+ * By filling with zeroes we automatically set resp_retcode to
+ * zero and simplify freeing resp on errors.
+ */
+ *rspv = malloc(msgc * sizeof(struct pam_response));
+ if (*rspv == NULL) {
+ log_warning("pam_conversation(): not enough memory for responses");
+ return PAM_CONV_ERR;
+ }
+
+ memset(*rspv, 0, msgc * sizeof(struct pam_response));
+
+ rc = PAM_SUCCESS;
+
+ for (i=0; i<msgc; i++) {
+ if (rc != PAM_SUCCESS)
+ break;
+
+ switch (msgv[i]->msg_style) {
+ case PAM_PROMPT_ECHO_OFF:
+ (*rspv)[i].resp = strdup(request->password);
+ if ((*rspv)[i].resp == NULL) {
+ log_warning("pam_conversation(): not enough memory for password");
+ rc = PAM_CONV_ERR;
+ }
+ break;
+
+ case PAM_ERROR_MSG:
+ log_warning(
+ "pam_conversation(): PAM error: %s",
+ msgv[i]->msg);
+ break;
+
+ default:
+ log_debug(
+ "pam_conversation(): unhandled message, msg_style=%d",
+ msgv[i]->msg_style);
+ break;
+ }
+ }
+
+ if (rc != PAM_SUCCESS) {
+ for (i=0; i<msgc; i++)
+ free((*rspv)[i].resp);
+ free(*rspv);
+ }
+
+ return rc;
+}
+
+
+static bool pam_check_passwd(struct pam_auth_request *request)
+{
+ pam_handle_t *hpam;
+ char raddr[PGADDR_BUF];
+ int rc;
+
+ struct pam_conv pam_conv = {
+ .conv = pam_conversation,
+ .appdata_ptr = request
+ };
+
+ rc = pam_start(PGBOUNCER_PAM_SERVICE, request->username, &pam_conv, &hpam);
+ if (rc != PAM_SUCCESS) {
+ log_warning("pam_start() failed: %s", pam_strerror(NULL, rc));
+ return false;
+ }
+
+ /* Set rhost too in case if some PAM modules want to take it into account (and for logging too) */
+ pga_ntop(&request->remote_addr, raddr, sizeof(raddr));
+ rc = pam_set_item(hpam, PAM_RHOST, raddr);
+ if (rc != PAM_SUCCESS) {
+ log_warning("pam_set_item(): can't set PAM_RHOST to '%s'", raddr);
+ pam_end(hpam, rc);
+ return false;
+ }
+
+ /* Here the authentication is performed */
+ rc = pam_authenticate(hpam, PAM_SILENT);
+ if (rc != PAM_SUCCESS) {
+ log_warning("pam_authenticate() failed: %s", pam_strerror(hpam, rc));
+ pam_end(hpam, rc);
+ return false;
+ }
+
+ /* And here we check that the account is not expired, verifies access hours, etc */
+ rc = pam_acct_mgmt(hpam, PAM_SILENT);
+ if (rc != PAM_SUCCESS) {
+ log_warning("pam_acct_mgmt() failed: %s", pam_strerror(hpam, rc));
+ pam_end(hpam, rc);
+ return false;
+ }
+
+ rc = pam_end(hpam, rc);
+ if (rc != PAM_SUCCESS) {
+ log_warning("pam_end() failed: %s", pam_strerror(hpam, rc));
+ }
+
+ return true;
+}
+
+#else /* !HAVE_PAM */
+
+/* If PAM is not supported then this dummy functions is used which always rejects passwords */
+
+void pam_init(void)
+{
+ /* do nothing */
+}
+
+void pam_auth_begin(PgSocket *client, const char *passwd)
+{
+ fatal("PAM authentication is not supported");
+}
+
+int pam_poll(void)
+{
+ /* do nothing */
+ return 0;
+}
+
+#endif
\ No newline at end of file