diff options
author | Mike Gabriel <mike.gabriel@das-netzwerkteam.de> | 2018-05-05 16:57:41 +0200 |
---|---|---|
committer | Mike Gabriel <mike.gabriel@das-netzwerkteam.de> | 2018-05-05 17:20:12 +0200 |
commit | 589051f3ef91ab1bc3029de3952907dea81c1f88 (patch) | |
tree | c135b12c79ac38ccec2ab9997b7824eaf2dbb1f9 /src/pam-freerdp2.c | |
parent | 5b415f1d637277bcfb2328d3e0022cde48514b3a (diff) | |
download | libpam-freerdp2-589051f3ef91ab1bc3029de3952907dea81c1f88.tar.gz libpam-freerdp2-589051f3ef91ab1bc3029de3952907dea81c1f88.tar.bz2 libpam-freerdp2-589051f3ef91ab1bc3029de3952907dea81c1f88.zip |
src/: Rename files *freerdp* -> *freerdp2*.
Diffstat (limited to 'src/pam-freerdp2.c')
-rw-r--r-- | src/pam-freerdp2.c | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/src/pam-freerdp2.c b/src/pam-freerdp2.c new file mode 100644 index 0000000..35d5cf4 --- /dev/null +++ b/src/pam-freerdp2.c @@ -0,0 +1,460 @@ +/* + * Copyright © 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Ted Gould <ted@canonical.com> + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/un.h> +#include <pwd.h> +#include <grp.h> +#include <errno.h> + +#define PAM_SM_AUTH +#define PAM_SM_SESSION +#include <security/pam_modules.h> +#include <security/pam_modutil.h> +#include <security/pam_appl.h> + +#include "pam-freerdp2.h" + +#include "pam-freerdp2-children.h" +#include "auth-check-path.h" + +static int unpriveleged_kill (struct passwd * pwdent); + +static char * global_domain = NULL; +static char * global_server = NULL; +/* FIXME? This is a work around to the fact that PAM seems to be clearing + the auth token between authorize and open_session. Which then requires + us to save it. Seems like we're the wrong people to do it, but we have + no choice */ +static char * global_password = NULL; + +/* Either grab a value or prompt for it */ +static char * +get_item (pam_handle_t * pamh, int type) +{ + /* Check to see if we just have the value. If we do, great + let's dup it some we're consitently allocating memory */ + if ((type != PAM_TYPE_RDPDOMAIN) && (type != PAM_TYPE_RDPSERVER)) { + /* If it's not a domain we can use the PAM functions because the PAM + functions don't support the domain */ + char * value = NULL; + if (pam_get_item(pamh, type, (const void **)&value) == PAM_SUCCESS && value != NULL) { + return value; + } + if (type == PAM_AUTHTOK && global_password != NULL) { + /* If we're looking for a password, we didn't get one, before + prompting see if we've got a global one. */ + return global_password; + } + } else { + /* Here we only have domains, so we can see if the global domain is + useful for us, if we have it */ + if (global_server != NULL) { + return global_server; + } + if (global_domain != NULL) { + return global_domain; + } + } + /* Now we need to prompt */ + + /* Build up the message we're prompting for */ + struct pam_message message; + const struct pam_message * pmessage = &message; + + message.msg = NULL; + message.msg_style = PAM_PROMPT_ECHO_ON; + + switch (type) { + case PAM_USER: + message.msg = "login:"; + break; + case PAM_RUSER: + message.msg = "remote login:"; + break; + case PAM_TYPE_RDPSERVER: + message.msg = "remote host:"; + break; + case PAM_AUTHTOK: + message.msg = "password:"; + message.msg_style = PAM_PROMPT_ECHO_OFF; + break; + case PAM_TYPE_RDPDOMAIN: + message.msg = "domain:"; + break; + default: + return NULL; + } + + struct pam_conv * conv = NULL; + if (pam_get_item(pamh, PAM_CONV, (const void **)&conv) != PAM_SUCCESS || conv == NULL || conv->conv == NULL) { + return NULL; + } + + struct pam_response * responses = NULL; + if (conv->conv(1, &pmessage, &responses, conv->appdata_ptr) != PAM_SUCCESS || responses == NULL) { + return NULL; + } + + char * promptval = responses->resp; + free(responses); + + /* If we didn't get anything, just move on */ + if (promptval == NULL) { + return NULL; + } + + if (type == PAM_AUTHTOK) { + if (strlen(promptval) == 0){ + free(promptval); + return NULL; + } + + if (mlock(promptval, strlen(promptval) + 1) != 0) { + free(promptval); + return NULL; + } + } + + /* The way that xfreerdp does parsing means that we can't handle + spaces in the username. Let's block them as early as possible. + Though, if the xfreerdp part gets fixed, we want this to disappear + http://launchpad.net/bugs/1053102 + */ + if (type == PAM_RUSER) { + if (strstr(promptval, " ") != NULL) { + free(promptval); + return NULL; + } + } + + if (type == PAM_TYPE_RDPSERVER) { + char * subloc = strstr(promptval, "://"); + if (subloc != NULL) { + char * original = promptval; + char * newish = subloc + strlen("://"); + char * endslash = strstr(newish, "/"); + + if (endslash != NULL) { + endslash[0] = '\0'; + } + + promptval = strdup(newish); + free(original); + } + } + + char * retval = NULL; + if (promptval != NULL) { /* Can't believe it really would be at this point, but let's be sure */ + if ((type != PAM_TYPE_RDPDOMAIN) && (type != PAM_TYPE_RDPSERVER)) { + /* We can only use the PAM functions if it's neither server nor domain */ + pam_set_item(pamh, type, (const void *)promptval); + /* We're returning the value saved by PAM so we can clear promptval */ + pam_get_item(pamh, type, (const void **)&retval); + } + if (type == PAM_TYPE_RDPSERVER) { + /* The domain can be saved globally so we can use it for open */ + if (global_server != NULL) { + free(global_server); + } + global_server = strdup(promptval); + retval = global_server; + } + if (type == PAM_TYPE_RDPDOMAIN) { + /* The domain can be saved globally so we can use it for open */ + if (global_domain != NULL) { + free(global_domain); + } + global_domain = strdup(promptval); + retval = global_domain; + } + if (type == PAM_AUTHTOK) { + /* We also save the password globally if we've got one */ + if (global_password != NULL) { + memset(global_password, 0, strlen(global_password)); + munlock(global_password, strlen(global_password) + 1); + free(global_password); + } + global_password = strdup(promptval); + if (mlock(global_password, strlen(global_password) + 1) != 0) { + /* Woah, can't lock it. Can't keep it. */ + free(global_password); + global_password = NULL; + } else { + retval = global_password; + } + } + + if (type == PAM_AUTHTOK) { + memset(promptval, 0, strlen(promptval) + 1); + munlock(promptval, strlen(promptval) + 1); + } + + free(promptval); + } + + return retval; +} + +#define GET_ITEM(val, type) \ + if ((val = get_item(pamh, type)) == NULL) { \ + retval = PAM_AUTH_ERR; \ + goto done; \ + } + +/* Authenticate. We need to make sure we have a user account, that + there are remote accounts and then verify them with FreeRDP */ +PAM_EXTERN int +pam_sm_authenticate (pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + char * username = NULL; + char * password = NULL; + char * ruser = NULL; + char * rhost = NULL; + char * rdomain = NULL; + int retval = PAM_IGNORE; + + /* Get all the values, or prompt for them, or return with + an auth error */ + GET_ITEM(username, PAM_USER); + GET_ITEM(ruser, PAM_RUSER); + GET_ITEM(rhost, PAM_TYPE_RDPSERVER); + GET_ITEM(rdomain, PAM_TYPE_RDPDOMAIN); + GET_ITEM(password, PAM_AUTHTOK); + + int stdinpipe[2]; + if (pipe(stdinpipe) != 0) { + retval = PAM_SYSTEM_ERR; + goto done; + } + + /* At this point we should have the values, let's check the auth */ + pid_t pid; + switch (pid = fork()) { + case 0: { /* child */ + pam_sm_authenticate_helper (stdinpipe, username, rhost, ruser, rdomain); + break; + } + case -1: { /* fork'n error! */ + retval = PAM_SYSTEM_ERR; + break; + } + default: { + int forkret = 0; + int bytesout = 0; + + bytesout += write(stdinpipe[1], password, strlen(password)); + bytesout += write(stdinpipe[1], "\n", 1); + + close(stdinpipe[1]); + + if (waitpid(pid, &forkret, 0) < 0 || bytesout == 0) { + retval = PAM_SYSTEM_ERR; + } else if (forkret == 0) { + retval = PAM_SUCCESS; + } else { + retval = PAM_AUTH_ERR; + } + } + } + + /* Return our status */ +done: + return retval; +} + + + +pid_t session_pid = 0; +/* Open Session. Here we need to fork a little process so that we can + give the credentials to the session itself so that it can startup the + xfreerdp viewer for the login */ +PAM_EXTERN int +pam_sm_open_session (pam_handle_t *pamh, int flags, int argc, const char ** argv) +{ + char * username = NULL; + char * password = NULL; + char * ruser = NULL; + char * rhost = NULL; + char * rdomain = NULL; + int retval = PAM_SUCCESS; + + /* Get all the values, or prompt for them, or return with + an auth error */ + GET_ITEM(username, PAM_USER); + GET_ITEM(ruser, PAM_RUSER); + GET_ITEM(rhost, PAM_TYPE_RDPSERVER); + GET_ITEM(rdomain, PAM_TYPE_RDPDOMAIN); + GET_ITEM(password, PAM_AUTHTOK); + + struct passwd * pwdent = getpwnam(username); + if (pwdent == NULL) { + retval = PAM_SYSTEM_ERR; + goto done; + } + + if (session_pid != 0) { + unpriveleged_kill(pwdent); + } + + int sessionready[2]; + if (pipe(sessionready) != 0) { + retval = PAM_SYSTEM_ERR; + goto done; + } + + pid_t pid = fork(); + if (pid == 0) { + + int ret = session_socket_handler(pwdent, sessionready[1], ruser, rhost, rdomain, password); + + close(sessionready[1]); + _exit(ret); + } else if (pid < 0) { + close(sessionready[0]); + close(sessionready[1]); + + retval = PAM_SYSTEM_ERR; + } else { + char readbuffer[strlen(ALL_GOOD_SIGNAL) + 1]; + int readlen = 0; + + readlen = read(sessionready[0], readbuffer, strlen(ALL_GOOD_SIGNAL) + 1); + + close(sessionready[0]); + + if (readlen == strlen(ALL_GOOD_SIGNAL) + 1) { + session_pid = pid; + } else { + retval = PAM_SYSTEM_ERR; + } + } + +done: + return retval; +} + +/* Close Session. Make sure our little guy has died so he doesn't become + a zombie and eat things. */ +PAM_EXTERN int +pam_sm_close_session (pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + if (session_pid == 0) { + return PAM_IGNORE; + } + + char * username = NULL; + int retval = PAM_SUCCESS; + + GET_ITEM(username, PAM_USER); + + struct passwd * pwdent = getpwnam(username); + if (pwdent == NULL) { + retval = PAM_SYSTEM_ERR; + goto done; + } + + retval = unpriveleged_kill(pwdent); + +done: + return retval; +} + +/* Drop privs and try to kill the process with the PID of session_pid. + This ensures that we don't kill something important if there is PID wrap + around. */ +static int +unpriveleged_kill (struct passwd * pwdent) +{ + int retval = PAM_SUCCESS; + + pid_t pid = fork(); + if (pid == 0) { + /* Setting groups, but allowing EPERM as if we're not 100% root + we might not be able to do this */ + if (setgroups(1, &pwdent->pw_gid) != 0 && errno != EPERM) { + _exit(EXIT_FAILURE); + } + + if (setgid(pwdent->pw_gid) < 0 || setuid(pwdent->pw_uid) < 0 || + setegid(pwdent->pw_gid) < 0 || seteuid(pwdent->pw_uid) < 0) { + _exit(EXIT_FAILURE); + } + + if (clearenv() != 0) { + _exit(EXIT_FAILURE); + } + + int killval = kill(session_pid, SIGKILL); + session_pid = 0; + + if (killval != 0) { + printf("Unable to kill\n"); + } + + /* NOTE: We're ignoring whether we could kill it or not. It'd be nice to + track that but there are a lot of reason that we could fail there and + it's not a bad thing. Really we're attempting a best effort to clean up + we won't be able to guarantee it. */ + _exit(EXIT_SUCCESS); + } else if (pid < 0) { + retval = PAM_SYSTEM_ERR; + } else { + int forkret = 0; + + if (waitpid(pid, &forkret, 0) < 0) { + retval = PAM_SYSTEM_ERR; + } + } + + /* We reset this no matter. If we error'd trying to do it, we don't + want to try again. We'll just return the error for this time. */ + session_pid = 0; + + return retval; +} + +/* LightDM likes to have this function around, but we don't need it as we + don't have a token hanging around. */ +PAM_EXTERN int +pam_sm_setcred (pam_handle_t *pamh, int flags, int argc, const char ** argv) +{ + return PAM_SUCCESS; +} + +#ifdef PAM_STATIC + +struct pam_module _pam_freerdp_modstruct = { + "pam_freerdp2", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + pam_sm_open_session, + pam_sm_close_session, + NULL, +}; + +#endif |