out which queries a given PAM module is making, so that you can
craft the appropriate plugin directive to answer it.
+Since running OpenVPN with verb 7 is quite verbose, alternatively
+you can put
+
+ verb 3
+ setenv verb 9
+
+in the openvpn config which will only increase logging for this plugin.
+
+
+ASYNCHRONOUS OPERATION
+
+Sometimes PAM modules take very long to complete (for example, a LDAP
+or Radius query might timeout trying to connect an unreachable external
+server). Normal plugin auth operation will block the whole OpenVPN
+process in this time, that is, all forwarding for all other clients stops.
+
+The auth-pam plugin can operate asynchronously ("deferred authentication")
+to remedy this situation. To enable this, put
+
+ setenv deferred_auth_pam 1
+
+in your openvpn server config. If set, this will make the "PAM background
+process" fork() and do its job detached from OpenVPN. When finished, a
+status file is written, which OpenVPN will then pick up and read the
+success/failure result from it.
+
+While the plugin is working in the background, OpenVPN will continue to
+service other clients normally.
+
+Asynchronous operation is recommended for all PAM queries that could
+"take time" (LDAP, Radius, NIS, ...). If only local files are queried
+(passwd, pam_userdb, ...), synchronous operation has slightly lower
+overhead, so this is still the default mode of operation.
+
+
CAVEATS
This module will only work on *nix systems which support PAM,
#define RESPONSE_INIT_FAILED 11
#define RESPONSE_VERIFY_SUCCEEDED 12
#define RESPONSE_VERIFY_FAILED 13
+#define RESPONSE_DEFER 14
/* Pointers to functions exported from openvpn */
static plugin_log_t plugin_log = NULL;
static plugin_base64_decode_t plugin_base64_decode = NULL;
/* module name for plugin_log() */
-static char * MODULE = "AUTH-PAM";
+static char *MODULE = "AUTH-PAM";
/*
* Plugin state, used by foreground
const char *password = get_env("password", envp);
const char *common_name = get_env("common_name", envp) ? get_env("common_name", envp) : "";
+ /* should we do deferred auth?
+ * yes, if there is "auth_control_file" and "deferred_auth_pam" env
+ */
+ const char *auth_control_file = get_env("auth_control_file", envp);
+ const char *deferred_auth_pam = get_env("deferred_auth_pam", envp);
+ if (auth_control_file != NULL && deferred_auth_pam != NULL)
+ {
+ if (DEBUG(context->verb))
+ {
+ plugin_log(PLOG_NOTE, MODULE, "do deferred auth '%s'",
+ auth_control_file);
+ }
+ }
+ else
+ {
+ auth_control_file = "";
+ }
+
if (username && strlen(username) > 0 && password)
{
if (send_control(context->foreground_fd, COMMAND_VERIFY) == -1
|| send_string(context->foreground_fd, username) == -1
|| send_string(context->foreground_fd, password) == -1
- || send_string(context->foreground_fd, common_name) == -1)
+ || send_string(context->foreground_fd, common_name) == -1
+ || send_string(context->foreground_fd, auth_control_file) == -1)
{
plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "Error sending auth info to background process");
}
{
return OPENVPN_PLUGIN_FUNC_SUCCESS;
}
+ if (status == RESPONSE_DEFER)
+ {
+ if (DEBUG(context->verb))
+ {
+ plugin_log(PLOG_NOTE, MODULE, "deferred authentication");
+ }
+ return OPENVPN_PLUGIN_FUNC_DEFERRED;
+ }
if (status == -1)
{
plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "Error receiving auth confirmation from background process");
return ret;
}
+/*
+ * deferred auth handler
+ * - fork() (twice, to avoid the need for async wait / SIGCHLD handling)
+ * - query PAM stack via pam_auth()
+ * - send response back to OpenVPN via "ac_file_name"
+ *
+ * parent process returns "0" for "fork() and wait() succeeded",
+ * "-1" for "something went wrong, abort program"
+ */
+
+static void
+do_deferred_pam_auth(int fd, const char *ac_file_name,
+ const char *service, const struct user_pass *up)
+{
+ if (send_control(fd, RESPONSE_DEFER) == -1)
+ {
+ plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: write error on response socket [4]");
+ return;
+ }
+
+ /* double forking so we do not need to wait() for async auth kids */
+ pid_t p1 = fork();
+
+ if (p1 < 0)
+ {
+ plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: fork(1) failed");
+ return;
+ }
+ if (p1 != 0) /* parent */
+ {
+ waitpid(p1, NULL, 0);
+ return; /* parent's job succeeded */
+ }
+
+ /* child */
+ close(fd); /* socketpair no longer needed */
+
+ pid_t p2 = fork();
+ if (p2 < 0)
+ {
+ plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: fork(2) failed");
+ exit(1);
+ }
+
+ if (p2 != 0) /* new parent: exit right away */
+ {
+ exit(0);
+ }
+
+ /* grandchild */
+ plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: deferred auth for '%s', pid=%d",
+ up->username, (int) getpid() );
+
+ /* the rest is very simple: do PAM, write status byte to file, done */
+ int ac_fd = open( ac_file_name, O_WRONLY );
+ if (ac_fd < 0)
+ {
+ plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "cannot open '%s' for writing",
+ ac_file_name );
+ exit(1);
+ }
+ int pam_success = pam_auth(service, up);
+
+ if (write( ac_fd, pam_success ? "1" : "0", 1 ) != 1)
+ {
+ plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "cannot write to '%s'",
+ ac_file_name );
+ }
+ close(ac_fd);
+ plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: %s: deferred auth: PAM %s",
+ up->username, pam_success ? "succeeded" : "rejected" );
+ exit(0);
+}
+
/*
* Background process -- runs with privilege.
*/
pam_server(int fd, const char *service, int verb, const struct name_value_list *name_value_list)
{
struct user_pass up;
+ char ac_file_name[PATH_MAX];
int command;
#ifdef USE_PAM_DLOPEN
static const char pam_so[] = "libpam.so";
case COMMAND_VERIFY:
if (recv_string(fd, up.username, sizeof(up.username)) == -1
|| recv_string(fd, up.password, sizeof(up.password)) == -1
- || recv_string(fd, up.common_name, sizeof(up.common_name)) == -1)
+ || recv_string(fd, up.common_name, sizeof(up.common_name)) == -1
+ || recv_string(fd, ac_file_name, sizeof(ac_file_name)) == -1)
{
plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: read error on command channel: code=%d, exiting",
command);
/* If password is of the form SCRV1:base64:base64 split it up */
split_scrv1_password(&up);
+ /* client wants deferred auth
+ */
+ if (strlen(ac_file_name) > 0)
+ {
+ do_deferred_pam_auth(fd, ac_file_name, service, &up);
+ break;
+ }
+
+
+ /* non-deferred auth: wait for pam result and send
+ * result back via control socketpair
+ */
if (pam_auth(service, &up)) /* Succeeded */
{
if (send_control(fd, RESPONSE_VERIFY_SUCCEEDED) == -1)