1 // launcher.c -- Launch services from a privileged process
2 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
49 #define pthread_once x_pthread_once
50 #define execle x_execle
62 #include <sys/ioctl.h>
63 #include <sys/socket.h>
66 #include <sys/types.h>
68 #include <sys/utsname.h>
96 #if defined(HAVE_SECURITY_PAM_APPL_H)
97 #include <security/pam_appl.h>
99 #if defined(HAVE_SECURITY_PAM_MISC_H)
100 #include <security/pam_misc.h>
103 #ifndef PAM_DATA_SILENT
104 #define PAM_DATA_SILENT 0
110 typedef struct pam_handle pam_handle_t;
114 #define strncat(a,b,c) ({ char *_a = (a); strlcat(_a, (b), (c)+1); _a; })
117 #include "shellinabox/launcher.h"
118 #include "shellinabox/privileges.h"
119 #include "shellinabox/service.h"
120 #include "libhttp/hashmap.h"
121 #include "logging/logging.h"
124 #defined ATTR_UNUSED __attribute__((unused))
125 #defined UNUSED(x) do { } while (0)
128 #define UNUSED(x) do { (void)(x); } while (0)
133 int execle(const char *, const char *, ...);
135 #if defined(HAVE_PTHREAD_H) && defined(__linux__)
137 extern int pthread_once(pthread_once_t *, void (*)(void))__attribute__((weak));
140 // If PAM support is available, take advantage of it. Otherwise, silently fall
141 // back on legacy operations for session management.
142 #if defined(HAVE_SECURITY_PAM_APPL_H) && defined(HAVE_DLOPEN)
143 static int (*x_pam_acct_mgmt)(pam_handle_t *, int);
144 static int (*x_pam_authenticate)(pam_handle_t *, int);
145 #if defined(HAVE_SECURITY_PAM_CLIENT_H)
146 static int (**x_pam_binary_handler_fn)(void *, pamc_bp_t *);
148 static int (*x_pam_close_session)(pam_handle_t *, int);
149 static int (*x_pam_end)(pam_handle_t *, int);
150 static int (*x_pam_get_item)(const pam_handle_t *, int, const void **);
151 static int (*x_pam_open_session)(pam_handle_t *, int);
152 static int (*x_pam_set_item)(pam_handle_t *, int, const void *);
153 static int (*x_pam_start)(const char *, const char *, const struct pam_conv *,
155 static int (*x_misc_conv)(int, const struct pam_message **,
156 struct pam_response **, void *);
158 #define pam_acct_mgmt x_pam_acct_mgmt
159 #define pam_authenticate x_pam_authenticate
160 #define pam_binary_handler_fn x_pam_binary_handler_fn
161 #define pam_close_session x_pam_close_session
162 #define pam_end x_pam_end
163 #define pam_get_item x_pam_get_item
164 #define pam_open_session x_pam_open_session
165 #define pam_set_item x_pam_set_item
166 #define pam_start x_pam_start
167 #define misc_conv x_misc_conv
170 // MacOS X has a somewhat unusual definition of getgrouplist() which can
171 // trigger a compile warning. Unfortunately, there is no good feature test
172 // for this particular problem.
173 #if defined(__APPLE__) && defined(__MACH__)
174 static int x_getgrouplist(const char *user, gid_t group,
175 gid_t *groups, int *ngroups) {
176 return getgrouplist(user, (int)group, (int *)groups, ngroups);
178 #define getgrouplist x_getgrouplist
181 static int launcher = -1;
182 static uid_t restricted;
185 // If the PAM misc library cannot be found, we have to provide our own basic
186 // conversation function. As we know that this code is only ever called from
187 // ShellInABox, it can be kept significantly simpler than the more generic
188 // code that the PAM library implements.
190 static int read_string(int echo, const char *prompt, char **retstr) {
192 struct termios term_before, term_tmp;
193 if (tcgetattr(0, &term_before) != 0) {
196 memcpy(&term_tmp, &term_before, sizeof(term_tmp));
198 term_tmp.c_lflag &= ~ECHO;
202 tcsetattr(0, TCSAFLUSH, &term_tmp);
203 fprintf(stderr, "%s", prompt);
205 const int lineLength = 512;
206 check(line = calloc(1, lineLength));
207 nc = read(0, line, lineLength - 1);
208 tcsetattr(0, TCSADRAIN, &term_before);
210 fprintf(stderr, "\n");
213 if (line[nc-1] == '\n') {
216 fprintf(stderr, "\n");
219 check(*retstr = line);
222 memset(line, 0, lineLength);
225 fprintf(stderr, "\n");
230 tcsetattr(0, TCSADRAIN, &term_before);
234 #if defined(HAVE_SECURITY_PAM_APPL_H) && defined(HAVE_DLOPEN)
235 #if defined(HAVE_SECURITY_PAM_CLIENT_H)
236 static pamc_bp_t *p(pamc_bp_t *p) {
237 // GCC is too smart for its own good, and triggers a warning in
238 // PAM_BP_RENEW, unless we pass the first argument through a function.
243 static int my_misc_conv(int num_msg, const struct pam_message **msgm,
244 struct pam_response **response, void *appdata_ptr) {
248 struct pam_response *reply;
249 check(reply = (struct pam_response *)calloc(num_msg,
250 sizeof(struct pam_response)));
251 for (int count = 0; count < num_msg; count++) {
253 switch(msgm[count]->msg_style) {
254 case PAM_PROMPT_ECHO_OFF:
255 if (read_string(0, msgm[count]->msg, &string) < 0) {
256 goto failed_conversation;
259 case PAM_PROMPT_ECHO_ON:
260 if (read_string(1, msgm[count]->msg, &string) < 0) {
261 goto failed_conversation;
265 if (fprintf(stderr, "%s\n", msgm[count]->msg) < 0) {
266 goto failed_conversation;
270 if (fprintf(stdout, "%s\n", msgm[count]->msg) < 0) {
271 goto failed_conversation;
274 #if defined(HAVE_SECURITY_PAM_CLIENT_H)
275 case PAM_BINARY_PROMPT: {
276 pamc_bp_t binary_prompt = NULL;
277 if (!msgm[count]->msg || !*pam_binary_handler_fn) {
278 goto failed_conversation;
280 PAM_BP_RENEW(p(&binary_prompt), PAM_BP_RCONTROL(msgm[count]->msg),
281 PAM_BP_LENGTH(msgm[count]->msg));
282 PAM_BP_FILL(binary_prompt, 0, PAM_BP_LENGTH(msgm[count]->msg),
283 PAM_BP_RDATA(msgm[count]->msg));
284 if ((*pam_binary_handler_fn)(appdata_ptr, &binary_prompt) !=
285 PAM_SUCCESS || !binary_prompt) {
286 goto failed_conversation;
288 string = (char *)binary_prompt;
292 goto failed_conversation;
295 reply[count].resp_retcode = 0;
296 reply[count].resp = string;
304 static void *loadSymbol(const char *lib, const char *fn) {
305 void *dl = RTLD_DEFAULT;
306 void *rc = dlsym(dl, fn);
309 dl = dlopen(lib, RTLD_LAZY|RTLD_GLOBAL|RTLD_NOLOAD);
314 dl = dlopen(lib, RTLD_LAZY|RTLD_GLOBAL);
323 static void loadPAM(void) {
328 void *avoid_gcc_warning_about_type_punning;
334 { { &pam_acct_mgmt }, "libpam.so", "pam_acct_mgmt" },
335 { { &pam_authenticate }, "libpam.so", "pam_authenticate" },
336 #if defined(HAVE_SECURITY_PAM_CLIENT_H)
337 { { &pam_binary_handler_fn }, "libpam_misc.so", "pam_binary_handler_fn" },
339 { { &pam_close_session }, "libpam.so", "pam_close_session" },
340 { { &pam_end }, "libpam.so", "pam_end" },
341 { { &pam_get_item }, "libpam.so", "pam_get_item" },
342 { { &pam_open_session }, "libpam.so", "pam_open_session" },
343 { { &pam_set_item }, "libpam.so", "pam_set_item" },
344 { { &pam_start }, "libpam.so", "pam_start" },
345 { { &misc_conv }, "libpam_misc.so", "misc_conv" }
347 for (unsigned i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
348 if (!(*symbols[i].var = loadSymbol(symbols[i].lib, symbols[i].fn))) {
349 #if defined(HAVE_SECURITY_PAM_CLIENT_H)
350 if (!strcmp(symbols[i].fn, "pam_binary_handler_fn")) {
351 // Binary conversation support is optional
355 if (!strcmp(symbols[i].fn, "misc_conv")) {
356 // PAM misc is optional
357 *symbols[i].var = (void *)my_misc_conv;
360 debug("Failed to load PAM support. Could not find \"%s\"",
362 for (unsigned j = 0; j < sizeof(symbols)/sizeof(symbols[0]); j++) {
363 *symbols[j].var = NULL;
368 debug("Loaded PAM suppport");
372 int supportsPAM(void) {
373 #if defined(HAVE_SECURITY_PAM_APPL_H) && !defined(HAVE_DLOPEN)
376 #if defined(HAVE_SECURITY_PAM_APPL_H)
378 // We want to call loadPAM() exactly once. For single-threaded applications,
379 // this is straight-forward. For threaded applications, we need to call
380 // pthread_once(), instead. We perform run-time checks for whether we are
381 // single- or multi-threaded, so that the same code can be used.
382 // This currently only works on Linux.
383 #if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
384 if (!!&pthread_once) {
385 static pthread_once_t once = PTHREAD_ONCE_INIT;
386 pthread_once(&once, loadPAM);
390 static int initialized;
396 return misc_conv && pam_start;
403 #ifndef HAVE_GETPWUID_R
404 // This is a not-thread-safe replacement for getpwuid_r()
405 #define getpwuid_r x_getpwuid_r
406 static int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen,
407 struct passwd **result) {
415 struct passwd *p = getpwuid(uid);
417 return errno ? -1 : 0;
427 int launchChild(int service, struct Session *session, const char *url) {
434 check(u = strdup(url));
435 for (int i; u[i = strcspn(u, "\\\"'`${};() \r\n\t\v\f")]; ) {
436 static const char hex[] = "0123456789ABCDEF";
437 check(u = realloc(u, strlen(u) + 4));
438 memmove(u + i + 3, u + i + 1, strlen(u + i));
439 u[i + 2] = hex[ u[i] & 0xF];
440 u[i + 1] = hex[(u[i] >> 4) & 0xF];
444 struct LaunchRequest *request;
445 ssize_t len = sizeof(struct LaunchRequest) + strlen(u) + 1;
446 check(request = calloc(len, 1));
447 request->service = service;
448 request->width = session->width;
449 request->height = session->height;
450 strncat(request->peerName, httpGetPeerName(session->http),
451 sizeof(request->peerName) - 1);
452 request->urlLength = strlen(u);
453 memcpy(&request->url, u, request->urlLength);
455 if (NOINTR(write(launcher, request, len)) != len) {
461 char cmsg_buf[CMSG_SPACE(sizeof(int))];
462 struct iovec iov = { 0 };
463 struct msghdr msg = { 0 };
465 iov.iov_len = sizeof(pid);
468 msg.msg_control = &cmsg_buf;
469 msg.msg_controllen = sizeof(cmsg_buf);
470 int bytes = NOINTR(recvmsg(launcher, &msg, 0));
474 check(bytes == sizeof(pid));
475 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
477 check(cmsg->cmsg_level == SOL_SOCKET);
478 check(cmsg->cmsg_type == SCM_RIGHTS);
479 memcpy(&session->pty, CMSG_DATA(cmsg), sizeof(int));
492 static HashMap *childProcesses;
494 void initUtmp(struct Utmp *utmp, int useLogin, const char *ptyPath,
495 const char *peerName) {
496 memset(utmp, 0, sizeof(struct Utmp));
498 utmp->useLogin = useLogin;
500 utmp->utmpx.ut_type = useLogin ? LOGIN_PROCESS : USER_PROCESS;
501 dcheck(!strncmp(ptyPath, "/dev/pts", 8));
502 strncat(&utmp->utmpx.ut_line[0], ptyPath + 5, sizeof(utmp->utmpx.ut_line));
503 strncat(&utmp->utmpx.ut_id[0], ptyPath + 8, sizeof(utmp->utmpx.ut_id));
504 strncat(&utmp->utmpx.ut_user[0], "SHELLINABOX", sizeof(utmp->utmpx.ut_user));
505 strncat(&utmp->utmpx.ut_host[0], peerName, sizeof(utmp->utmpx.ut_host));
507 check(!gettimeofday(&tv, NULL));
508 utmp->utmpx.ut_tv.tv_sec = tv.tv_sec;
509 utmp->utmpx.ut_tv.tv_usec = tv.tv_usec;
513 struct Utmp *newUtmp(int useLogin, const char *ptyPath,
514 const char *peerName) {
516 check(utmp = malloc(sizeof(struct Utmp)));
517 initUtmp(utmp, useLogin, ptyPath, peerName);
521 #if defined(HAVE_UPDWTMP) && !defined(HAVE_UPDWTMPX)
522 #define min(a,b) ({ typeof(a) _a=(a); typeof(b) _b=(b); _a < _b ? _a : _b; })
523 #define updwtmpx x_updwtmpx
525 static void updwtmpx(const char *wtmpx_file, const struct utmpx *utx) {
526 struct utmp ut = { 0 };
527 ut.ut_type = utx->ut_type;
528 ut.ut_pid = utx->ut_pid;
529 ut.ut_tv.tv_sec = utx->ut_tv.tv_sec;
530 ut.ut_tv.tv_usec = utx->ut_tv.tv_usec;
531 memcpy(&ut.ut_line, &utx->ut_line,
532 min(sizeof(ut.ut_line), sizeof(utx->ut_line)));
533 memcpy(&ut.ut_id, &utx->ut_id,
534 min(sizeof(ut.ut_id), sizeof(utx->ut_id)));
535 memcpy(&ut.ut_user, &utx->ut_user,
536 min(sizeof(ut.ut_user), sizeof(utx->ut_user)));
537 memcpy(&ut.ut_host, &utx->ut_host,
538 min(sizeof(ut.ut_host), sizeof(utx->ut_host)));
539 updwtmp(wtmpx_file, &ut);
543 void destroyUtmp(struct Utmp *utmp) {
545 if (utmp->pty >= 0) {
547 utmp->utmpx.ut_type = DEAD_PROCESS;
548 memset(&utmp->utmpx.ut_user, 0, sizeof(utmp->utmpx.ut_user));
549 memset(&utmp->utmpx.ut_host, 0, sizeof(utmp->utmpx.ut_host));
551 check(!gettimeofday(&tv, NULL));
552 utmp->utmpx.ut_tv.tv_sec = tv.tv_sec;
553 utmp->utmpx.ut_tv.tv_usec = tv.tv_usec;
555 // Temporarily regain privileges to update the utmp database
556 uid_t r_uid, e_uid, s_uid;
557 uid_t r_gid, e_gid, s_gid;
558 check(!getresuid(&r_uid, &e_uid, &s_uid));
559 check(!getresgid(&r_gid, &e_gid, &s_gid));
564 pututxline(&utmp->utmpx);
567 #if defined(HAVE_UPDWTMP) || defined(HAVE_UPDWTMPX)
568 if (!utmp->useLogin) {
569 updwtmpx("/var/log/wtmp", &utmp->utmpx);
573 // Switch back to the lower privileges
574 check(!setresgid(r_gid, e_gid, s_gid));
575 check(!setresuid(r_uid, e_uid, s_uid));
578 NOINTR(close(utmp->pty));
583 void deleteUtmp(struct Utmp *utmp) {
588 static void destroyUtmpHashEntry(void *arg ATTR_UNUSED, char *key ATTR_UNUSED,
592 deleteUtmp((struct Utmp *)value);
595 void closeAllFds(int *exceptFds, int num) {
596 // Close all file handles. If possible, scan through "/proc/self/fd" as
597 // that is faster than calling close() on all possible file handles.
598 int nullFd = open("/dev/null", O_RDWR);
599 DIR *dir = opendir("/proc/self/fd");
601 for (int i = sysconf(_SC_OPEN_MAX); --i > 0; ) {
603 for (int j = 0; j < num; j++) {
604 if (i == exceptFds[j]) {
608 // Closing handles 0..2 is never a good idea. Instead, redirect them
611 NOINTR(dup2(nullFd, i));
619 struct dirent de, *res;
620 while (!readdir_r(dir, &de, &res) && res) {
621 if (res->d_name[0] < '0')
623 int fd = atoi(res->d_name);
624 if (fd != nullFd && fd != dirfd(dir)) {
625 for (int j = 0; j < num; j++) {
626 if (fd == exceptFds[j]) {
630 // Closing handles 0..2 is never a good idea. Instead, redirect them
633 NOINTR(dup2(nullFd, fd));
640 check(!closedir(dir));
643 check(!close(nullFd));
647 #if !defined(HAVE_OPENPTY) && !defined(HAVE_PTSNAME_R)
648 static int ptsname_r(int fd, char *buf, size_t buflen) {
649 // It is unfortunate that ptsname_r is not universally available.
650 // For the time being, this is not a big problem, as ShellInABox is
651 // single-threaded (and so is the launcher process). But if this
652 // code gets re-used in a multi-threaded application, that could
658 char *p = ptsname(fd);
662 if (buflen < strlen(p) + 1) {
671 static int forkPty(int *pty, int useLogin, struct Utmp **utmp,
672 const char *peerName) {
674 char ptyPath[PATH_MAX];
676 if (openpty(pty, &slave, ptyPath, NULL, NULL) < 0) {
682 if ((*pty = posix_openpt(O_RDWR|O_NOCTTY)) < 0 ||
684 unlockpt(*pty) < 0 ||
685 ptsname_r(*pty, ptyPath, sizeof(ptyPath)) < 0 ||
686 (slave = NOINTR(open(ptyPath, O_RDWR|O_NOCTTY))) < 0) {
691 // Try old-style pty handling
692 char fname[40] = "/dev/ptyXX";
693 for (const char *ptr1 = "pqrstuvwxyzabcde"; *ptr1; ptr1++) {
695 for (const char *ptr2 = "0123456789abcdef"; *ptr2; ptr2++) {
697 if ((*pty = NOINTR(open(fname, O_RDWR, 0))) < 0) {
698 if (errno == ENOENT) {
704 if (ptsname_r(*pty, ptyPath, sizeof(ptyPath)) < 0) {
705 strcpy(ptyPath, fname);
708 if ((slave = NOINTR(open(ptyPath, O_RDWR|O_NOCTTY))) >= 0) {
709 debug("Opened old-style pty: %s", ptyPath);
722 // Fill in utmp entry
723 *utmp = newUtmp(useLogin, ptyPath, peerName);
725 // Now, fork off the child process
727 if ((pid = fork()) < 0) {
728 NOINTR(close(slave));
734 } else if (pid == 0) {
736 snprintf((char *)&(*utmp)->pid[0], sizeof((*utmp)->pid), "%d", pid);
738 (*utmp)->utmpx.ut_pid = pid;
740 (*utmp)->pty = slave;
742 closeAllFds((int []){ slave }, 1);
744 #ifdef HAVE_LOGIN_TTY
747 // Become the session/process-group leader
751 // Redirect standard I/O to the pty
756 NOINTR(close(slave));
761 // Force the pty to be our control terminal
762 NOINTR(close(NOINTR(open(ptyPath, O_RDWR))));
766 snprintf((char *)&(*utmp)->pid[0], sizeof((*utmp)->pid), "%d", pid);
768 (*utmp)->utmpx.ut_pid = pid;
771 fcntl(*pty, F_SETFL, O_NONBLOCK|O_RDWR);
772 NOINTR(close(slave));
777 static const struct passwd *getPWEnt(uid_t uid) {
778 struct passwd pwbuf, *pw;
780 #ifdef _SC_GETPW_R_SIZE_MAX
781 int len = sysconf(_SC_GETPW_R_SIZE_MAX);
788 check(buf = malloc(len));
789 check(!getpwuid_r(uid, &pwbuf, buf, len, &pw) && pw);
790 if (!pw->pw_name ) pw->pw_name = (char *)"";
791 if (!pw->pw_passwd) pw->pw_passwd = (char *)"";
792 if (!pw->pw_gecos ) pw->pw_gecos = (char *)"";
793 if (!pw->pw_dir ) pw->pw_dir = (char *)"";
794 if (!pw->pw_shell ) pw->pw_shell = (char *)"";
795 struct passwd *passwd;
796 check(passwd = calloc(sizeof(struct passwd) +
797 strlen(pw->pw_name) +
798 strlen(pw->pw_passwd) +
799 strlen(pw->pw_gecos) +
801 strlen(pw->pw_shell) + 5, 1));
802 passwd->pw_uid = pw->pw_uid;
803 passwd->pw_gid = pw->pw_gid;
804 strncat(passwd->pw_shell = strrchr(
805 strncat(passwd->pw_dir = strrchr(
806 strncat(passwd->pw_gecos = strrchr(
807 strncat(passwd->pw_passwd = strrchr(
808 strncat(passwd->pw_name = (char *)(passwd + 1),
809 pw->pw_name, strlen(pw->pw_name)), '\000') + 1,
810 pw->pw_passwd, strlen(pw->pw_passwd)), '\000') + 1,
811 pw->pw_gecos, strlen(pw->pw_gecos)), '\000') + 1,
812 pw->pw_dir, strlen(pw->pw_dir)), '\000') + 1,
813 pw->pw_shell, strlen(pw->pw_shell));
818 static void sigAlrmHandler(int sig ATTR_UNUSED, siginfo_t *info ATTR_UNUSED,
819 void *unused ATTR_UNUSED) {
823 puts("\nLogin timed out after 60 seconds.");
827 static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
828 char ***environment) {
829 // Time out after 60 seconds
831 memset(&sa, 0, sizeof(sa));
832 sa.sa_flags = SA_SIGINFO;
833 sa.sa_sigaction = sigAlrmHandler;
834 check(!sigaction(SIGALRM, &sa, NULL));
837 // Change the prompt to include the host name
838 const char *hostname = NULL;
839 if (service->authUser == 2 /* SSH */) {
840 // If connecting to a remote host, include that hostname
841 hostname = strrchr(service->cmdline, '@');
842 if (!hostname || !strcmp(++hostname, "localhost")) {
847 memset(&uts, 0, sizeof(uts));
849 // Find our local hostname
851 hostname = uts.nodename;
854 check(fqdn = strdup(hostname));
855 check(hostname = strdup(hostname));
856 char *dot = strchr(hostname, '.');
861 const struct passwd *pw;
862 pam_handle_t *pam = NULL;
863 if (service->authUser == 2 /* SSH */) {
864 // Just ask for the user name. SSH will negotiate the password
867 check(prompt = stringPrintf(NULL, "%s login: ", hostname));
869 if (read_string(1, prompt, &user) <= 0) {
875 for (char *u = user; *u; u++) {
877 if (!((ch >= '0' && ch <= '9') ||
878 (ch >= 'A' && ch <= 'Z') ||
879 (ch >= 'a' && ch <= 'z') ||
880 ch == '-' || ch == '_' || ch == '.')) {
881 goto invalid_user_name;
891 char *cmdline = stringPrintf(NULL, service->cmdline, user);
894 // Replace '@localhost' with the actual host name. This results in a nicer
895 // prompt when SSH asks for the password.
896 char *ptr = strrchr(cmdline, '@');
897 if (!strcmp(ptr + 1, "localhost")) {
898 int offset = ptr + 1 - cmdline;
899 check(cmdline = realloc(cmdline,
900 strlen(cmdline) + strlen(fqdn) -
901 strlen("localhost") + 1));
902 ptr = cmdline + offset;
904 strncat(ptr, fqdn, strlen(fqdn));
907 free((void *)service->cmdline);
908 service->cmdline = cmdline;
910 // Run SSH as an unprivileged user
911 if ((service->uid = restricted) == 0) {
912 if (runAsUser >= 0) {
913 service->uid = runAsUser;
915 service->uid = getUserId("nobody");
917 if (runAsGroup >= 0) {
918 service->gid = runAsGroup;
920 service->gid = getGroupId("nogroup");
923 pw = getPWEnt(service->uid);
925 service->gid = pw->pw_gid;
927 service->user = getUserName(service->uid);
928 service->group = getGroupName(service->gid);
930 // Use PAM to negotiate user authentication and authorization
931 #if defined(HAVE_SECURITY_PAM_APPL_H)
932 struct pam_conv conv = { .conv = misc_conv };
933 if (service->authUser) {
934 check(supportsPAM());
935 check(pam_start("shellinabox", NULL, &conv, &pam) == PAM_SUCCESS);
937 const char *origPrompt;
938 check(pam_get_item(pam, PAM_USER_PROMPT, (void *)&origPrompt) ==
941 check(prompt = stringPrintf(NULL, "%s %s", hostname,
942 origPrompt ? origPrompt : "login: "));
943 check(pam_set_item(pam, PAM_USER_PROMPT, prompt) == PAM_SUCCESS);
945 // Up to three attempts to enter the user id and password
947 check(pam_set_item(pam, PAM_USER, NULL) == PAM_SUCCESS);
949 if ((rc = pam_authenticate(pam, PAM_SILENT)) ==
952 (rc = pam_acct_mgmt(pam, PAM_SILENT)) ==
957 // Quit if login failed.
958 puts("\nMaximum number of tries exceeded (3)");
962 puts("\nLogin incorrect");
965 check(pam_set_item(pam, PAM_USER_PROMPT, "login: ") == PAM_SUCCESS);
968 // Retrieve user id, and group id.
970 check(pam_get_item(pam, PAM_USER, (void *)&name) == PAM_SUCCESS);
971 pw = getPWEnt(getUserId(name));
972 check(service->uid < 0);
973 check(service->gid < 0);
974 check(!service->user);
975 check(!service->group);
976 service->uid = pw->pw_uid;
977 service->gid = pw->pw_gid;
978 check(service->user = strdup(pw->pw_name));
979 service->group = getGroupName(pw->pw_gid);
981 check(service->uid >= 0);
982 check(service->gid >= 0);
983 check(service->user);
984 check(service->group);
986 check(pam_start("shellinabox", service->user, &conv, &pam) ==
990 // PAM account management requires root access. Just skip it, if we
991 // are running with lower privileges.
993 (rc = pam_acct_mgmt(pam, PAM_SILENT)) !=
999 pw = getPWEnt(service->uid);
1002 check(!supportsPAM());
1003 pw = getPWEnt(service->uid);
1007 free((void *)hostname);
1009 if (service->useDefaultShell) {
1010 check(!service->cmdline);
1011 service->cmdline = strdup(*pw->pw_shell ?
1012 pw->pw_shell : "/bin/sh");
1016 (service->uid != (int)restricted || service->gid != (int)pw->pw_gid)) {
1017 puts("\nAccess denied!");
1018 #if defined(HAVE_SECURITY_PAM_APPL_H)
1019 if (service->authUser != 2 /* SSH */) {
1020 pam_end(pam, PAM_SUCCESS);
1026 if (service->authUser != 2 /* SSH */) {
1027 #if defined(HAVE_SECURITY_PAM_APPL_H)
1030 check(pam_set_item(pam, PAM_TTY, (const void **)utmp->utmpx.ut_line) ==
1039 // Retrieve supplementary group ids.
1041 #if defined(__linux__)
1042 // On Linux, we can query the number of supplementary groups. On all other
1043 // platforms, we play it safe and just assume a fixed upper bound.
1045 getgrouplist(service->user, pw->pw_gid, NULL, &ngroups);
1049 check(ngroups >= 0);
1051 // Set supplementary group ids
1053 check(groups = malloc((ngroups + 1) * sizeof(gid_t)));
1054 check(getgrouplist(service->user, pw->pw_gid, groups, &ngroups) >= 0);
1056 // Make sure that any group that was requested on the command line is
1057 // included, if it is not one of the normal groups for this user.
1058 for (int i = 0; ; i++) {
1060 groups[ngroups++] = service->gid;
1062 } else if ((int)groups[i] == service->gid) {
1066 setgroups(ngroups, groups);
1070 // Add standard environment variables
1072 for (char **e = *environment; *e; numEnvVars++, e++) {
1074 check(*environment = realloc(*environment,
1075 (numEnvVars + 6)*sizeof(char *)));
1076 (*environment)[numEnvVars++] = stringPrintf(NULL, "HOME=%s", pw->pw_dir);
1077 (*environment)[numEnvVars++] = stringPrintf(NULL, "SHELL=%s", pw->pw_shell);
1079 (*environment)[numEnvVars++] = strdup(
1080 "PATH=/usr/local/bin:/usr/bin:/bin:/usr/games"));
1081 (*environment)[numEnvVars++] = stringPrintf(NULL, "LOGNAME=%s",
1083 (*environment)[numEnvVars++] = stringPrintf(NULL, "USER=%s", service->user);
1084 (*environment)[numEnvVars++] = NULL;
1087 // Update utmp/wtmp entries
1089 if (service->authUser != 2 /* SSH */) {
1090 memset(&utmp->utmpx.ut_user, 0, sizeof(utmp->utmpx.ut_user));
1091 strncat(&utmp->utmpx.ut_user[0], service->user,
1092 sizeof(utmp->utmpx.ut_user));
1094 pututxline(&utmp->utmpx);
1097 #if defined(HAVE_UPDWTMP) || defined(HAVE_UPDWTMPX)
1098 updwtmpx("/var/log/wtmp", &utmp->utmpx);
1107 static void destroyVariableHashEntry(void *arg ATTR_UNUSED, char *key,
1114 static void execService(int width ATTR_UNUSED, int height ATTR_UNUSED,
1115 struct Service *service, const char *peerName,
1116 char **environment, const char *url) {
1120 // Create a hash table with all the variables that we can expand. This
1121 // includes all environment variables being passed to the child.
1123 check(vars = newHashMap(destroyVariableHashEntry, NULL));
1124 for (char **e = environment; *e; e++) {
1125 char *ptr = strchr(*e, '=');
1128 check(key = strdup(*e));
1129 check(value = strdup(""));
1131 check(key = malloc(ptr - *e + 1));
1132 memcpy(key, *e, ptr - *e);
1133 key[ptr - *e] = '\000';
1134 check(value = strdup(ptr + 1));
1136 // All of our variables are lower-case
1137 for (ptr = key; *ptr; ptr++) {
1138 if (*ptr >= 'A' && *ptr <= 'Z') {
1142 addToHashMap(vars, key, value);
1145 check(key = strdup("gid"));
1146 addToHashMap(vars, key, stringPrintf(NULL, "%d", service->gid));
1147 check(key = strdup("group"));
1148 check(value = strdup(service->group));
1149 addToHashMap(vars, key, value);
1150 check(key = strdup("peer"));
1151 check(value = strdup(peerName));
1152 addToHashMap(vars, key, value);
1153 check(key = strdup("uid"));
1154 addToHashMap(vars, key, stringPrintf(NULL, "%d", service->uid));
1155 check(key = strdup("url"));
1156 addToHashMap(vars, key, strdup(url));
1158 enum { ENV, ARGS } state = ENV;
1159 enum { NONE, SINGLE, DOUBLE
1162 check(cmdline = strdup(service->cmdline));
1165 check(argv = malloc(sizeof(char *)));
1168 for (char *ptr = cmdline; ; ptr++) {
1169 if (!key && *ptr && *ptr != ' ') {
1174 if (quote == SINGLE || quote == NONE) {
1175 memmove(ptr, ptr + 1, strlen(ptr));
1177 quote = quote == SINGLE ? NONE : SINGLE;
1179 dcheck(quote == DOUBLE);
1183 if (quote == DOUBLE || quote == NONE) {
1184 memmove(ptr, ptr + 1, strlen(ptr));
1186 quote = quote == DOUBLE ? NONE : DOUBLE;
1188 dcheck(quote == SINGLE);
1192 if ((quote == NONE || quote == DOUBLE) && ptr[1] == '{') {
1193 // Always treat environment variables as if they were quoted. There
1194 // is no good reason for us to try to look for spaces within
1195 // expanded environment variables. This just leads to subtle bugs.
1196 char *end = ptr + 2;
1197 while (*end && *end != '}') {
1202 const char *repl = getFromHashMap(vars, ptr + 2);
1203 int replLen = repl ? strlen(repl) : 0;
1208 int incr = replLen - (end - ptr);
1210 char *oldCmdline = cmdline;
1211 check(cmdline = realloc(cmdline,
1212 (end - cmdline) + strlen(end) +
1214 ptr += cmdline - oldCmdline;
1215 end += cmdline - oldCmdline;
1217 key += cmdline - oldCmdline;
1220 value += cmdline - oldCmdline;
1223 memmove(ptr + replLen, end, strlen(end) + 1);
1225 memcpy(ptr, repl, replLen);
1234 memmove(ptr, ptr + 1, strlen(ptr));
1238 // This is the seperator between keys and values of any environment
1239 // variable that we are asked to set.
1240 if (state == ENV && quote == NONE && !value) {
1246 // If this space character is not quoted, this is the start of a new
1247 // command line argument.
1248 if (quote != NONE) {
1256 if (state == ENV && value) {
1257 // Override an existing environment variable.
1259 int len = strlen(key);
1260 for (char **e = environment; *e; e++, numEnvVars++) {
1261 if (!strncmp(*e, key, len) && (*e)[len] == '=') {
1262 int s_size = len + strlen(value) + 1;
1263 check(*e = realloc(*e, s_size + 1));
1264 (*e)[len + 1] = '\000';
1265 strncat(*e, value, s_size);
1270 // Add a new environment variable
1271 if (numEnvVars >= 0) {
1272 check(environment = realloc(environment,
1273 (numEnvVars + 2)*sizeof(char *)));
1275 environment[numEnvVars++] = strdup(key);
1276 environment[numEnvVars] = NULL;
1279 // Add entry to argv.
1281 argv[argc++] = strdup(key);
1282 check(argv = realloc(argv, (argc + 1)*sizeof(char *)));
1298 deleteHashMap(vars);
1301 extern char **environ;
1302 environ = environment;
1303 char *cmd = strdup(argv[0]);
1304 char *slash = strrchr(argv[0], '/');
1306 memmove(argv[0], slash + 1, strlen(slash));
1308 if (service->useDefaultShell) {
1309 int len = strlen(argv[0]);
1310 check(argv[0] = realloc(argv[0], len + 2));
1311 memmove(argv[0] + 1, argv[0], len);
1313 argv[0][len + 1] = '\000';
1318 void setWindowSize(int pty, int width, int height) {
1319 if (width > 0 && height > 0) {
1323 ioctl(pty, TIOCGSIZE, &win);
1324 win.ts_lines = height;
1325 win.ts_cols = width;
1326 ioctl(pty, TIOCSSIZE, &win);
1332 ioctl(pty, TIOCGWINSZ, &win);
1333 win.ws_row = height;
1335 ioctl(pty, TIOCSWINSZ, &win);
1341 static void childProcess(struct Service *service, int width, int height,
1342 struct Utmp *utmp, const char *peerName,
1344 // Set initial window size
1345 setWindowSize(0, width, height);
1347 // Set up environment variables
1348 static const char *legalEnv[] = { "TZ", "HZ", NULL };
1350 check(environment = malloc(2*sizeof(char *)));
1352 check(environment[0] = strdup("TERM=xterm"));
1353 if (width > 0 && height > 0) {
1355 check(environment = realloc(environment,
1356 (numEnvVars + 1)*sizeof(char *)));
1357 environment[numEnvVars-2] = stringPrintf(NULL, "COLUMNS=%d", width);
1358 environment[numEnvVars-1] = stringPrintf(NULL, "LINES=%d", height);
1360 for (int i = 0; legalEnv[i]; i++) {
1361 char *value = getenv(legalEnv[i]);
1364 check(environment = realloc(environment,
1365 (numEnvVars + 1)*sizeof(char *)));
1366 environment[numEnvVars-1] = stringPrintf(NULL, "%s=%s",
1367 legalEnv[i], value);
1370 environment[numEnvVars] = NULL;
1372 // Set initial terminal settings
1373 struct termios tt = { 0 };
1375 cfsetispeed(&tt, 38400);
1376 cfsetospeed(&tt, 38400);
1377 tt.c_iflag = TTYDEF_IFLAG & ~ISTRIP;
1378 tt.c_oflag = TTYDEF_OFLAG;
1379 tt.c_lflag = TTYDEF_LFLAG;
1380 tt.c_cflag = (TTYDEF_CFLAG & ~(CS7|PARENB|HUPCL)) | CS8;
1381 tt.c_cc[VERASE] = '\x7F';
1382 tcsetattr(0, TCSAFLUSH, &tt);
1384 // Assert root privileges in order to update utmp entry.
1389 struct utmpx utmpx = utmp->utmpx;
1390 if (service->useLogin || service->authUser) {
1391 utmpx.ut_type = LOGIN_PROCESS;
1392 memset(utmpx.ut_host, 0, sizeof(utmpx.ut_host));
1397 #if defined(HAVE_UPDWTMP) || defined(HAVE_UPDWTMPX)
1398 if (!utmp->useLogin) {
1399 memset(&utmpx.ut_user, 0, sizeof(utmpx.ut_user));
1400 strncat(&utmpx.ut_user[0], "LOGIN", sizeof(utmpx.ut_user));
1401 updwtmpx("/var/log/wtmp", &utmpx);
1406 // Create session. We might have to fork another process as PAM wants us
1407 // to close the session when the child terminates. And we must retain
1408 // permissions, as session closure could require root permissions.
1409 // None of this really applies if we are running as an unprivileged user.
1410 // In that case, we do not bother about session management.
1411 if (!service->useLogin) {
1412 pam_handle_t *pam = internalLogin(service, utmp, &environment);
1413 #if defined(HAVE_SECURITY_PAM_APPL_H)
1414 if (pam && !geteuid()) {
1415 if (pam_open_session(pam, PAM_SILENT) != PAM_SUCCESS) {
1416 fprintf(stderr, "Access denied.\n");
1426 // Finish all pending PAM operations.
1428 check(NOINTR(waitpid(pid, &status, 0)) == pid);
1429 rc = pam_close_session(pam, PAM_SILENT);
1430 pam_end(pam, rc | PAM_DATA_SILENT);
1431 _exit(WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status));
1439 // Change user and group ids
1440 check(!setresgid(service->gid, service->gid, service->gid));
1441 check(!setresuid(service->uid, service->uid, service->uid));
1443 // Change working directory
1444 if (service->useHomeDir) {
1445 check(!service->useLogin);
1446 const struct passwd *pw = getPWEnt(getuid());
1447 check(!service->cwd);
1448 check(service->cwd = strdup(pw->pw_dir));
1451 check(service->cwd);
1452 if (!*service->cwd || *service->cwd != '/' || chdir(service->cwd)) {
1453 check(service->cwd = realloc((char *)service->cwd, 2));
1454 *(char *)service->cwd = '\000';
1455 strncat((char *)service->cwd, "/", 1);
1456 puts("No directory, logging in with HOME=/");
1458 for (int i = 0; environment[i]; i++) {
1459 if (!strncmp(environment[i], "HOME=", 5)) {
1460 free(environment[i]);
1461 check(environment[i] = strdup("HOME=/"));
1467 // Finally, launch the child process.
1468 if (service->useLogin == 1) {
1469 execle("/bin/login", "login", "-p", "-h", peerName,
1470 (void *)0, environment);
1471 execle("/usr/bin/login", "login", "-p", "-h", peerName,
1472 (void *)0, environment);
1474 execService(width, height, service, peerName, environment, url);
1479 static void sigChildHandler(int sig ATTR_UNUSED, siginfo_t *info ATTR_UNUSED,
1480 void *unused ATTR_UNUSED) {
1486 static void launcherDaemon(int fd) {
1487 struct sigaction sa;
1488 memset(&sa, 0, sizeof(sa));
1489 sa.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
1490 sa.sa_sigaction = sigChildHandler;
1491 check(!sigaction(SIGCHLD, &sa, NULL));
1493 struct LaunchRequest request;
1496 int len = read(fd, &request, sizeof(request));
1497 if (len != sizeof(request) && errno != EINTR) {
1499 debug("Failed to read launch request");
1504 // Check whether our read operation got interrupted, because a child
1508 while (NOINTR(pid = waitpid(-1, &status, WNOHANG)) > 0) {
1509 if (WIFEXITED(pid) || WIFSIGNALED(pid)) {
1511 snprintf(&key[0], sizeof(key), "%d", pid);
1512 deleteFromHashMap(childProcesses, key);
1515 if (len != sizeof(request)) {
1520 check(url = calloc(request.urlLength + 1, 1));
1522 len = read(fd, url, request.urlLength + 1);
1523 if (len != request.urlLength + 1 && errno != EINTR) {
1524 debug("Failed to read URL");
1528 while (NOINTR(pid = waitpid(-1, &status, WNOHANG)) > 0) {
1529 if (WIFEXITED(pid) || WIFSIGNALED(pid)) {
1531 snprintf(&key[0], sizeof(key), "%d", pid);
1532 deleteFromHashMap(childProcesses, key);
1535 if (len != request.urlLength + 1) {
1539 check(request.service >= 0);
1540 check(request.service < numServices);
1542 // Sanitize the host name, so that we do not pass any unexpected characters
1543 // to our child process.
1544 request.peerName[sizeof(request.peerName)-1] = '\000';
1545 for (char *s = request.peerName; *s; s++) {
1546 if (!((*s >= '0' && *s <= '9') ||
1547 (*s >= 'A' && *s <= 'Z') ||
1548 (*s >= 'a' && *s <= 'z') ||
1549 *s == '.' || *s == '-')) {
1554 // Fork and exec the child process.
1557 if ((pid = forkPty(&pty,
1558 services[request.service]->useLogin,
1559 &utmp, request.peerName)) == 0) {
1560 childProcess(services[request.service], request.width, request.height,
1561 utmp, request.peerName, url);
1565 // Remember the utmp entry so that we can clean up when the child
1569 if (!childProcesses) {
1570 childProcesses = newHashMap(destroyUtmpHashEntry, NULL);
1572 addToHashMap(childProcesses, utmp->pid, (char *)utmp);
1576 NOINTR(write(fds[1], "forkpty() failed\r\n", 18));
1577 NOINTR(close(fds[1]));
1583 // Send file handle and process id back to parent
1584 char cmsg_buf[CMSG_SPACE(sizeof(int))] = { 0 };
1585 struct iovec iov = { 0 };
1586 struct msghdr msg = { 0 };
1587 iov.iov_base = &pid;
1588 iov.iov_len = sizeof(pid);
1591 msg.msg_control = &cmsg_buf;
1592 msg.msg_controllen = sizeof(cmsg_buf);
1593 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
1595 cmsg->cmsg_level = SOL_SOCKET;
1596 cmsg->cmsg_type = SCM_RIGHTS;
1597 cmsg->cmsg_len = CMSG_LEN(sizeof(int));
1598 memcpy(CMSG_DATA(cmsg), &pty, sizeof(int));
1599 if (NOINTR(sendmsg(fd, &msg, 0)) != sizeof(pid)) {
1605 deleteHashMap(childProcesses);
1609 int forkLauncher(void) {
1611 check(!socketpair(AF_UNIX, SOCK_STREAM, 0, pair));
1615 // If our real-uid is not "root", then we should not allow anybody to
1616 // login unauthenticated users as anyone other than their own.
1618 check(!getresuid(&restricted, &tmp, &tmp));
1620 // Temporarily drop most permissions. We still retain the ability to
1621 // switch back to root, which is necessary for launching "login".
1623 closeAllFds((int []){ pair[1] }, 1);
1624 launcherDaemon(pair[1]);
1625 fatal("exit() failed!");
1627 fatal("fork() failed!");
1629 NOINTR(close(pair[1]));
1635 void terminateLauncher(void) {
1636 if (launcher >= 0) {
1637 NOINTR(close(launcher));