Hi, On Sat, Jun 20, 2020 at 12:23 PM Gert Doering <g...@greenie.muc.de> wrote: > > If OpenVPN signals deferred authentication support (by setting the > internal environment variable "auth_control_file"), do not wait > for PAM stack to finish. Instead, the privileged PAM process > returns RESPONSE_DEFER via the control socket, which gets turned > into OPENVPN_PLUGIN_FUNC_DEFERRED towards openvpn. > > The PAM process will then fork() and handle all the PAM auth in the > new process, signalling success/failure back by means of the > auth_control_file (forking twice, to simplify wait() handling). > > With the extra fork(), multiple deferred authentications can run at > the same time - otherwise the first one would block the next auth > call (because the child would not be ready again to read from the > control socket).
I have no doubt this will work, but can we avoid the double fork? Instead, we could set a handler for SIGCHLD and reap all child pids there using a non blocking waitpid(-1, NULL, WNOHANG). Will have to also check for EINTR and retry in recv_control() and possibly recv_string(). Fork is probably not expensive, but two of them per auth event look excessive and not very elegant, somehow. > > Lightly tested on Linux. > > Signed-off-by: Gert Doering <g...@greenie.muc.de> > --- > src/plugins/auth-pam/auth-pam.c | 88 ++++++++++++++++++++++++++++++++- > 1 file changed, 86 insertions(+), 2 deletions(-) > > diff --git a/src/plugins/auth-pam/auth-pam.c b/src/plugins/auth-pam/auth-pam.c > index 9a11876d..777957fa 100644 > --- a/src/plugins/auth-pam/auth-pam.c > +++ b/src/plugins/auth-pam/auth-pam.c > @@ -62,6 +62,7 @@ > #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; > @@ -524,12 +525,21 @@ openvpn_plugin_func_v1(openvpn_plugin_handle_t handle, > const int type, const cha > const char *password = get_env("password", envp); > const char *common_name = get_env("common_name", envp) ? > get_env("common_name", envp) : ""; > > + /* get auth_control_file filename from envp string array*/ > + const char *auth_control_file = get_env("auth_control_file", envp); > + if ( auth_control_file == NULL ) > + { > + auth_control_file = ""; > + } > + plugin_log(PLOG_NOTE, MODULE, "deferred auth '%s'", > 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"); > } > @@ -540,6 +550,11 @@ openvpn_plugin_func_v1(openvpn_plugin_handle_t handle, > const int type, const cha > { > return OPENVPN_PLUGIN_FUNC_SUCCESS; > } > + if (status == RESPONSE_DEFER) > + { > + plugin_log(PLOG_NOTE, MODULE, "deferred authentication > active"); > + return OPENVPN_PLUGIN_FUNC_DEFERRED; > + } > if (status == -1) > { > plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "Error receiving > auth confirmation from background process"); > @@ -789,6 +804,7 @@ static void > 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"; > @@ -847,7 +863,8 @@ pam_server(int fd, const char *service, int verb, const > struct name_value_list * > 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); > @@ -867,6 +884,73 @@ pam_server(int fd, const char *service, int verb, const > struct name_value_list * > /* If password is of the form SCRV1:base64:base64 split it > up */ > split_scrv1_password(&up); > > + /* client wants deferred auth > + * TODO: put this into its own function > + */ TODOs seldom get attended to, it's now or never :) > + if ( strlen(ac_file_name) > 0 ) > + { > + if (send_control(fd, RESPONSE_DEFER) == -1) > + { > + plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: > write error on response socket [4]"); > + goto done; > + } > + > + /* 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"); > + goto done; > + } > + if ( p1 != 0 ) /* parent */ > + { > + waitpid(p1, NULL, 0); > + /* plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: > fork(1), parent=%d, waitpid(%d) done, loop on", (int) getpid(), (int) p1 ); */ > + break; > + } > + > + /* child */ > + pid_t p2 = fork(); > + > + if ( p2 < 0 ) > + { > + plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: > fork(2) failed"); > + goto done; > + } > + > + if ( p2 != 0 ) /* parent-child */ > + { > + /* plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: > fork(2), mid=%d, exiting", (int) getpid() ); */ > + exit(0); > + } > + > + /* grandchild */ > + plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: deferred auth > handler, pid=%d", (int) getpid() ); I think the scoket in the parent could be closed here. The child doesn't need it. > + > + /* 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: deferred auth > finished" ); > + exit(0); > + } > + > + /* 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) > -- > 2.26.2 Looks good otherwise. As auth_control_file is always generated by the core built with PLUGIN_DEF_AUTH, the patch appears to make deferred auth the default with this plugin. So PLUGIN_DEF_AUTH takes a new meaning now. Not a fault of this patch, but I noticed that the auth_control_file is removed only during the next auth cycle for the same client, or while restarting the client, not promptly after an auth success or failure. Selva _______________________________________________ Openvpn-devel mailing list Openvpn-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openvpn-devel