]> andersk Git - test.git/blob - shellinabox/launcher.c
4ef14daf85aed54f2ad9e37631bef13a9b4ffc3c
[test.git] / shellinabox / launcher.c
1 // launcher.c -- Launch services from a privileged process
2 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
3 //
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.
7 //
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.
12 //
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.
16 //
17 // In addition to these license terms, the author grants the following
18 // additional rights:
19 //
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.
28 //
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
31 //
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:
35 //
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
38 //
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
41 //
42 //
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
45
46 #define _GNU_SOURCE
47 #include "config.h"
48
49 #define pthread_once    x_pthread_once
50 #define execle          x_execle
51
52 #include <dirent.h>
53 #include <dlfcn.h>
54 #include <fcntl.h>
55 #include <grp.h>
56 #include <limits.h>
57 #include <pwd.h>
58 #include <signal.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <sys/ioctl.h>
63 #include <sys/socket.h>
64 #include <sys/stat.h>
65 #include <sys/time.h>
66 #include <sys/types.h>
67 #include <sys/wait.h>
68 #include <sys/utsname.h>
69 #include <termios.h>
70 #include <unistd.h>
71
72 #ifdef HAVE_LIBUTIL_H
73 #include <libutil.h>
74 #endif
75
76 #ifdef HAVE_PTY_H
77 #include <pty.h>
78 #endif
79
80 #ifdef HAVE_SYS_UIO_H
81 #include <sys/uio.h>
82 #endif
83
84 #ifdef HAVE_UTIL_H
85 #include <util.h>
86 #endif
87
88 #ifdef HAVE_UTMP_H
89 #include <utmp.h>
90 #endif
91
92 #ifdef HAVE_UTMPX_H
93 #include <utmpx.h>
94 #endif
95
96 #if defined(HAVE_SECURITY_PAM_APPL_H)
97 #include <security/pam_appl.h>
98
99 #if defined(HAVE_SECURITY_PAM_MISC_H)
100 #include <security/pam_misc.h>
101 #endif
102
103 #ifndef PAM_DATA_SILENT
104 #define PAM_DATA_SILENT 0
105 #endif
106 #else
107 struct pam_message;
108 struct pam_response;
109 struct pam_conv;
110 typedef struct pam_handle pam_handle_t;
111 #endif
112
113 #ifdef HAVE_STRLCAT
114 #define strncat(a,b,c) ({ char *_a = (a); strlcat(_a, (b), (c)+1); _a; })
115 #endif
116
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"
122
123 #ifdef HAVE_UNUSED
124 #defined ATTR_UNUSED __attribute__((unused))
125 #defined UNUSED(x)   do { } while (0)
126 #else
127 #define ATTR_UNUSED
128 #define UNUSED(x)    do { (void)(x); } while (0)
129 #endif
130
131 #undef pthread_once
132 #undef execle
133 int execle(const char *, const char *, ...);
134
135 #if defined(HAVE_PTHREAD_H) && defined(__linux__)
136 #include <pthread.h>
137 extern int pthread_once(pthread_once_t *, void (*)(void))__attribute__((weak));
138 #endif
139
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 *);
147 #endif
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 *,
154                           pam_handle_t **);
155 static int (*x_misc_conv)(int, const struct pam_message **,
156                           struct pam_response **, void *);
157
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
168 #endif
169
170 // MacOS X has a somewhat unusual definition of getgrouplist() which can
171 // trigger a compile warning.
172 #if defined(HAVE_GETGROUPLIST_TAKES_INTS)
173 static int x_getgrouplist(const char *user, gid_t group,
174                           gid_t *groups, int *ngroups) {
175   return getgrouplist(user, (int)group, (int *)groups, ngroups);
176 }
177 #define getgrouplist x_getgrouplist
178 #endif
179
180 static int   launcher = -1;
181 static uid_t restricted;
182
183
184 // If the PAM misc library cannot be found, we have to provide our own basic
185 // conversation function. As we know that this code is only ever called from
186 // ShellInABox, it can be kept significantly simpler than the more generic
187 // code that the PAM library implements.
188
189 static int read_string(int echo, const char *prompt, char **retstr) {
190   *retstr                = NULL;
191   struct termios term_before, term_tmp;
192   if (tcgetattr(0, &term_before) != 0) {
193     return -1;
194   }
195   memcpy(&term_tmp, &term_before, sizeof(term_tmp));
196   if (!echo) {
197     term_tmp.c_lflag    &= ~ECHO;
198   }
199   int nc;
200   for (;;) {
201     tcsetattr(0, TCSAFLUSH, &term_tmp);
202     fprintf(stderr, "%s", prompt);
203     char *line;
204     const int lineLength = 512;
205     check(line           = calloc(1, lineLength));
206     nc                   = read(0, line, lineLength - 1);
207     tcsetattr(0, TCSADRAIN, &term_before);
208     if (!echo) {
209       fprintf(stderr, "\n");
210     }
211     if (nc > 0) {
212       if (line[nc-1] == '\n') {
213         nc--;
214       } else if (echo) {
215         fprintf(stderr, "\n");
216       }
217       line[nc]           = '\000';
218       check(*retstr      = line);
219       break;
220     } else {
221       memset(line, 0, lineLength);
222       free(line);
223       if (echo) {
224         fprintf(stderr, "\n");
225       }
226       break;
227     }
228   }
229   tcsetattr(0, TCSADRAIN, &term_before);
230   return nc;
231 }
232
233 #if defined(HAVE_SECURITY_PAM_APPL_H) && defined(HAVE_DLOPEN)
234 #if defined(HAVE_SECURITY_PAM_CLIENT_H)
235 static pamc_bp_t *p(pamc_bp_t *p) {
236   // GCC is too smart for its own good, and triggers a warning in
237   // PAM_BP_RENEW, unless we pass the first argument through a function.
238   return p;
239 }
240 #endif
241
242 static int my_misc_conv(int num_msg, const struct pam_message **msgm,
243                         struct pam_response **response, void *appdata_ptr) {
244   if (num_msg <= 0) {
245     return PAM_CONV_ERR;
246   }
247   struct pam_response *reply;
248   check(reply = (struct pam_response *)calloc(num_msg,
249                                               sizeof(struct pam_response)));
250   for (int count = 0; count < num_msg; count++) {
251     char *string                 = NULL;
252     switch(msgm[count]->msg_style) {
253       case PAM_PROMPT_ECHO_OFF:
254         if (read_string(0, msgm[count]->msg, &string) < 0) {
255           goto failed_conversation;
256         }
257         break;
258       case PAM_PROMPT_ECHO_ON:
259         if (read_string(1, msgm[count]->msg, &string) < 0) {
260           goto failed_conversation;
261         }
262         break;
263       case PAM_ERROR_MSG:
264         if (fprintf(stderr, "%s\n", msgm[count]->msg) < 0) {
265           goto failed_conversation;
266         }
267         break;
268       case PAM_TEXT_INFO:
269         if (fprintf(stdout, "%s\n", msgm[count]->msg) < 0) {
270           goto failed_conversation;
271         }
272         break;
273 #if defined(HAVE_SECURITY_PAM_CLIENT_H)
274       case PAM_BINARY_PROMPT: {
275         pamc_bp_t binary_prompt = NULL;
276         if (!msgm[count]->msg || !*pam_binary_handler_fn) {
277           goto failed_conversation;
278         }
279         PAM_BP_RENEW(p(&binary_prompt), PAM_BP_RCONTROL(msgm[count]->msg),
280                      PAM_BP_LENGTH(msgm[count]->msg));
281         PAM_BP_FILL(binary_prompt, 0, PAM_BP_LENGTH(msgm[count]->msg),
282                     PAM_BP_RDATA(msgm[count]->msg));
283         if ((*pam_binary_handler_fn)(appdata_ptr, &binary_prompt) !=
284             PAM_SUCCESS || !binary_prompt) {
285           goto failed_conversation;
286         }
287         string                  = (char *)binary_prompt;
288         break; }
289 #endif
290       default:
291         goto failed_conversation;
292     }
293     if (string) {
294       reply[count].resp_retcode = 0;
295       reply[count].resp         = string;
296     }
297   }
298 failed_conversation:
299   *response                     = reply;
300   return PAM_SUCCESS;
301 }
302
303 static void *loadSymbol(const char *lib, const char *fn) {
304   void *dl = RTLD_DEFAULT;
305   void *rc = dlsym(dl, fn);
306   if (!rc) {
307 #ifdef RTLD_NOLOAD
308     dl     = dlopen(lib, RTLD_LAZY|RTLD_GLOBAL|RTLD_NOLOAD);
309 #else
310     dl     = NULL;
311 #endif
312     if (dl == NULL) {
313       dl   = dlopen(lib, RTLD_LAZY|RTLD_GLOBAL);
314     }
315     if (dl != NULL) {
316       rc   = dlsym(dl, fn);
317     }
318   }
319   return rc;
320 }
321
322 static void loadPAM(void) {
323   check(!pam_start);
324   check(!misc_conv);
325   struct {
326     union {
327       void     *avoid_gcc_warning_about_type_punning;
328       void     **var;
329     };
330     const char *lib;
331     const char *fn;
332   } symbols[] = {
333     { { &pam_acct_mgmt },         "libpam.so",      "pam_acct_mgmt"     },
334     { { &pam_authenticate },      "libpam.so",      "pam_authenticate"  },
335 #if defined(HAVE_SECURITY_PAM_CLIENT_H)
336     { { &pam_binary_handler_fn }, "libpam_misc.so", "pam_binary_handler_fn" },
337 #endif
338     { { &pam_close_session },     "libpam.so",      "pam_close_session" },
339     { { &pam_end },               "libpam.so",      "pam_end"           },
340     { { &pam_get_item },          "libpam.so",      "pam_get_item"      },
341     { { &pam_open_session },      "libpam.so",      "pam_open_session"  },
342     { { &pam_set_item },          "libpam.so",      "pam_set_item"      },
343     { { &pam_start },             "libpam.so",      "pam_start"         },
344     { { &misc_conv },             "libpam_misc.so", "misc_conv"         }
345   };
346   for (unsigned i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
347     if (!(*symbols[i].var = loadSymbol(symbols[i].lib, symbols[i].fn))) {
348 #if defined(HAVE_SECURITY_PAM_CLIENT_H)
349       if (!strcmp(symbols[i].fn, "pam_binary_handler_fn")) {
350         // Binary conversation support is optional
351         continue;
352       } else
353 #endif
354       if (!strcmp(symbols[i].fn, "misc_conv")) {
355         // PAM misc is optional
356         *symbols[i].var = (void *)my_misc_conv;
357         continue;
358       }
359       debug("Failed to load PAM support. Could not find \"%s\"",
360             symbols[i].fn);
361       for (unsigned j = 0; j < sizeof(symbols)/sizeof(symbols[0]); j++) {
362         *symbols[j].var = NULL;
363       }
364       break;
365     }
366   }
367   debug("Loaded PAM suppport");
368 }
369 #endif
370
371 int supportsPAM(void) {
372 #if defined(HAVE_SECURITY_PAM_APPL_H) && !defined(HAVE_DLOPEN)
373   return 1;
374 #else
375 #if defined(HAVE_SECURITY_PAM_APPL_H)
376
377   // We want to call loadPAM() exactly once. For single-threaded applications,
378   // this is straight-forward. For threaded applications, we need to call
379   // pthread_once(), instead. We perform run-time checks for whether we are
380   // single- or multi-threaded, so that the same code can be used.
381   // This currently only works on Linux.
382 #if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
383   if (!!&pthread_once) {
384     static pthread_once_t once = PTHREAD_ONCE_INIT;
385     pthread_once(&once, loadPAM);
386   } else
387 #endif
388   {
389     static int initialized;
390     if (!initialized) {
391       initialized = 1;
392       loadPAM();
393     }
394   }
395   return misc_conv && pam_start;
396 #else
397   return 0;
398 #endif
399 #endif
400 }
401
402 #ifndef HAVE_GETPWUID_R
403 // This is a not-thread-safe replacement for getpwuid_r()
404 #define getpwuid_r x_getpwuid_r
405 static int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen,
406                       struct passwd **result) {
407   if (result) {
408     *result        = NULL;
409   }
410   if (!pwd) {
411     return -1;
412   }
413   errno            = 0;
414   struct passwd *p = getpwuid(uid);
415   if (!p) {
416     return errno ? -1 : 0;
417   }
418   *pwd             = *p;
419   if (result) {
420     *result        = pwd;
421   }
422   return 0;
423 }
424 #endif
425
426 int launchChild(int service, struct Session *session, const char *url) {
427   if (launcher < 0) {
428     errno              = EINVAL;
429     return -1;
430   }
431
432   char *u;
433   check(u              = strdup(url));
434   for (int i; u[i = strcspn(u, "\\\"'`${};() \r\n\t\v\f")]; ) {
435     static const char hex[] = "0123456789ABCDEF";
436     check(u            = realloc(u, strlen(u) + 4));
437     memmove(u + i + 3, u + i + 1, strlen(u + i));
438     u[i + 2]           = hex[ u[i]       & 0xF];
439     u[i + 1]           = hex[(u[i] >> 4) & 0xF];
440     u[i]               = '%';
441   }
442
443   struct LaunchRequest *request;
444   ssize_t len          = sizeof(struct LaunchRequest) + strlen(u) + 1;
445   check(request        = calloc(len, 1));
446   request->service     = service;
447   request->width       = session->width;
448   request->height      = session->height;
449   strncat(request->peerName, httpGetPeerName(session->http),
450           sizeof(request->peerName) - 1);
451   request->urlLength   = strlen(u);
452   memcpy(&request->url, u, request->urlLength);
453   free(u);
454   if (NOINTR(write(launcher, request, len)) != len) {
455     free(request);
456     return -1;
457   }
458   free(request);
459   pid_t pid;
460   char cmsg_buf[CMSG_SPACE(sizeof(int))];
461   struct iovec iov     = { 0 };
462   struct msghdr msg    = { 0 };
463   iov.iov_base         = &pid;
464   iov.iov_len          = sizeof(pid);
465   msg.msg_iov          = &iov;
466   msg.msg_iovlen       = 1;
467   msg.msg_control      = &cmsg_buf;
468   msg.msg_controllen   = sizeof(cmsg_buf);
469   int bytes            = NOINTR(recvmsg(launcher, &msg, 0));
470   if (bytes < 0) {
471     return -1;
472   }
473   check(bytes == sizeof(pid));
474   struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
475   check(cmsg);
476   check(cmsg->cmsg_level == SOL_SOCKET);
477   check(cmsg->cmsg_type  == SCM_RIGHTS);
478   memcpy(&session->pty, CMSG_DATA(cmsg), sizeof(int));
479   return pid;
480 }
481
482 struct Utmp {
483   const char   pid[32];
484   int          pty;
485   int          useLogin;
486 #ifdef HAVE_UTMPX_H
487   struct utmpx utmpx;
488 #endif
489 };
490
491 static HashMap *childProcesses;
492
493 void initUtmp(struct Utmp *utmp, int useLogin, const char *ptyPath,
494               const char *peerName) {
495   memset(utmp, 0, sizeof(struct Utmp));
496   utmp->pty                 = -1;
497   utmp->useLogin            = useLogin;
498 #ifdef HAVE_UTMPX_H
499   utmp->utmpx.ut_type       = useLogin ? LOGIN_PROCESS : USER_PROCESS;
500   dcheck(!strncmp(ptyPath, "/dev/pts", 8) ||
501          !strncmp(ptyPath, "/dev/pty", 8) ||
502          !strncmp(ptyPath, "/dev/tty", 8));
503   strncat(&utmp->utmpx.ut_line[0], ptyPath + 5,   sizeof(utmp->utmpx.ut_line));
504   strncat(&utmp->utmpx.ut_id[0],   ptyPath + 8,   sizeof(utmp->utmpx.ut_id));
505   strncat(&utmp->utmpx.ut_user[0], "SHELLINABOX", sizeof(utmp->utmpx.ut_user));
506   strncat(&utmp->utmpx.ut_host[0], peerName,      sizeof(utmp->utmpx.ut_host));
507   struct timeval tv;
508   check(!gettimeofday(&tv, NULL));
509   utmp->utmpx.ut_tv.tv_sec  = tv.tv_sec;
510   utmp->utmpx.ut_tv.tv_usec = tv.tv_usec;
511 #endif
512 }
513
514 struct Utmp *newUtmp(int useLogin, const char *ptyPath,
515                      const char *peerName) {
516   struct Utmp *utmp;
517   check(utmp = malloc(sizeof(struct Utmp)));
518   initUtmp(utmp, useLogin, ptyPath, peerName);
519   return utmp;
520 }
521
522 #if defined(HAVE_UPDWTMP) && !defined(HAVE_UPDWTMPX)
523 #define min(a,b) ({ typeof(a) _a=(a); typeof(b) _b=(b); _a < _b ? _a : _b; })
524 #define updwtmpx x_updwtmpx
525
526 static void updwtmpx(const char *wtmpx_file, const struct utmpx *utx) {
527   struct utmp ut   = { 0 };
528   ut.ut_type       = utx->ut_type;
529   ut.ut_pid        = utx->ut_pid;
530   ut.ut_tv.tv_sec  = utx->ut_tv.tv_sec;
531   ut.ut_tv.tv_usec = utx->ut_tv.tv_usec;
532   memcpy(&ut.ut_line, &utx->ut_line,
533          min(sizeof(ut.ut_line), sizeof(utx->ut_line)));
534   memcpy(&ut.ut_id, &utx->ut_id,
535          min(sizeof(ut.ut_id), sizeof(utx->ut_id)));
536   memcpy(&ut.ut_user, &utx->ut_user,
537          min(sizeof(ut.ut_user), sizeof(utx->ut_user)));
538   memcpy(&ut.ut_host, &utx->ut_host,
539          min(sizeof(ut.ut_host), sizeof(utx->ut_host)));
540   updwtmp(wtmpx_file, &ut);
541 }
542 #endif
543
544 void destroyUtmp(struct Utmp *utmp) {
545   if (utmp) {
546     if (utmp->pty >= 0) {
547 #ifdef HAVE_UTMPX_H
548       utmp->utmpx.ut_type = DEAD_PROCESS;
549       memset(&utmp->utmpx.ut_user, 0, sizeof(utmp->utmpx.ut_user));
550       memset(&utmp->utmpx.ut_host, 0, sizeof(utmp->utmpx.ut_host));
551       struct timeval tv;
552       check(!gettimeofday(&tv, NULL));
553       utmp->utmpx.ut_tv.tv_sec  = tv.tv_sec;
554       utmp->utmpx.ut_tv.tv_usec = tv.tv_usec;
555
556       // Temporarily regain privileges to update the utmp database
557       uid_t r_uid, e_uid, s_uid;
558       uid_t r_gid, e_gid, s_gid;
559       check(!getresuid(&r_uid, &e_uid, &s_uid));
560       check(!getresgid(&r_gid, &e_gid, &s_gid));
561       setresuid(0, 0, 0);
562       setresgid(0, 0, 0);
563
564       setutxent();
565       pututxline(&utmp->utmpx);
566       endutxent();
567
568 #if defined(HAVE_UPDWTMP) || defined(HAVE_UPDWTMPX)
569       if (!utmp->useLogin) {
570         updwtmpx("/var/log/wtmp", &utmp->utmpx);
571       }
572 #endif
573       
574       // Switch back to the lower privileges
575       check(!setresgid(r_gid, e_gid, s_gid));
576       check(!setresuid(r_uid, e_uid, s_uid));
577 #endif
578
579       NOINTR(close(utmp->pty));
580     }
581   }
582 }
583
584 void deleteUtmp(struct Utmp *utmp) {
585   destroyUtmp(utmp);
586   free(utmp);
587 }
588
589 static void destroyUtmpHashEntry(void *arg ATTR_UNUSED, char *key ATTR_UNUSED,
590                                  char *value) {
591   UNUSED(arg);
592   UNUSED(key);
593   deleteUtmp((struct Utmp *)value);
594 }
595
596 void closeAllFds(int *exceptFds, int num) {
597   // Close all file handles. If possible, scan through "/proc/self/fd" as
598   // that is faster than calling close() on all possible file handles.
599   int nullFd  = open("/dev/null", O_RDWR);
600   DIR *dir    = opendir("/proc/self/fd");
601   if (dir == 0) {
602     for (int i = sysconf(_SC_OPEN_MAX); --i > 0; ) {
603       if (i != nullFd) {
604         for (int j = 0; j < num; j++) {
605           if (i == exceptFds[j]) {
606             goto no_close_1;
607           }
608         }
609         // Closing handles 0..2 is never a good idea. Instead, redirect them
610         // to /dev/null
611         if (i <= 2) {
612           NOINTR(dup2(nullFd, i));
613         } else {
614           NOINTR(close(i));
615         }
616       }
617     no_close_1:;
618     }
619   } else {
620     struct dirent de, *res;
621     while (!readdir_r(dir, &de, &res) && res) {
622       if (res->d_name[0] < '0')
623         continue;
624       int fd  = atoi(res->d_name);
625       if (fd != nullFd && fd != dirfd(dir)) {
626         for (int j = 0; j < num; j++) {
627           if (fd == exceptFds[j]) {
628             goto no_close_2;
629           }
630         }
631         // Closing handles 0..2 is never a good idea. Instead, redirect them
632         // to /dev/null
633         if (fd <= 2) {
634           NOINTR(dup2(nullFd, fd));
635         } else {
636           NOINTR(close(fd));
637         }
638       }
639     no_close_2:;
640     }
641     check(!closedir(dir));
642   }
643   if (nullFd > 2) {
644     check(!close(nullFd));
645   }
646 }
647
648 #if !defined(HAVE_OPENPTY) && !defined(HAVE_PTSNAME_R)
649 static int ptsname_r(int fd, char *buf, size_t buflen) {
650   // It is unfortunate that ptsname_r is not universally available.
651   // For the time being, this is not a big problem, as ShellInABox is
652   // single-threaded (and so is the launcher process). But if this
653   // code gets re-used in a multi-threaded application, that could
654   // lead to problems.
655   if (buf == NULL) {
656     errno = EINVAL;
657     return -1;
658   }
659   char *p = ptsname(fd);
660   if (p == NULL) {
661     return -1;
662   }
663   if (buflen < strlen(p) + 1) {
664     errno = ERANGE;
665     return -1;
666   }
667   strcpy(buf, p);
668   return 0;
669 }
670 #endif
671
672 static int forkPty(int *pty, int useLogin, struct Utmp **utmp,
673                    const char *peerName) {
674   int slave;
675   char ptyPath[PATH_MAX];
676   #ifdef HAVE_OPENPTY
677   if (openpty(pty, &slave, ptyPath, NULL, NULL) < 0) {
678     *pty                    = -1;
679     *utmp                   = NULL;
680     return -1;
681   }
682   #else
683   if ((*pty                 = posix_openpt(O_RDWR|O_NOCTTY))          < 0 ||
684       grantpt(*pty)                                                   < 0 ||
685       unlockpt(*pty)                                                  < 0 ||
686       ptsname_r(*pty, ptyPath, sizeof(ptyPath))                       < 0 ||
687       (slave                = NOINTR(open(ptyPath, O_RDWR|O_NOCTTY))) < 0) {
688     if (*pty >= 0) {
689       NOINTR(close(*pty));
690     }
691
692     // Try old-style pty handling
693     char fname[40]          = "/dev/ptyXX";
694     for (const char *ptr1   = "pqrstuvwxyzabcde"; *ptr1; ptr1++) {
695       fname[8]              = *ptr1;
696       for (const char *ptr2 = "0123456789abcdef"; *ptr2; ptr2++) {
697         fname[9]            = *ptr2;
698         if ((*pty           = NOINTR(open(fname, O_RDWR, 0))) < 0) {
699           if (errno == ENOENT) {
700             continue;
701           }
702         }
703         grantpt(*pty);
704         unlockpt(*pty);
705         if (ptsname_r(*pty, ptyPath, sizeof(ptyPath)) < 0) {
706           strcpy(ptyPath, fname);
707           ptyPath[5]        = 't';
708         }
709         if ((slave          = NOINTR(open(ptyPath, O_RDWR|O_NOCTTY))) >= 0) {
710           debug("Opened old-style pty: %s", ptyPath);
711           goto success;
712         }
713         NOINTR(close(*pty));
714       }
715     }
716     *pty                    = -1;
717     *utmp                   = NULL;
718     return -1;
719   }
720  success:
721   #endif
722
723   // Fill in utmp entry
724   *utmp                     = newUtmp(useLogin, ptyPath, peerName);
725
726   // Now, fork off the child process
727   pid_t pid;
728   if ((pid                  = fork()) < 0) {
729     NOINTR(close(slave));
730     NOINTR(close(*pty));
731     *pty                    = -1;
732     deleteUtmp(*utmp);
733     *utmp                   = NULL;
734     return -1;
735   } else if (pid == 0) {
736     pid                     = getpid();
737     snprintf((char *)&(*utmp)->pid[0], sizeof((*utmp)->pid), "%d", pid);
738 #ifdef HAVE_UTMPX_H
739     (*utmp)->utmpx.ut_pid   = pid;
740 #endif
741     (*utmp)->pty            = slave;
742
743     closeAllFds((int []){ slave }, 1);
744
745 #ifdef HAVE_LOGIN_TTY
746     login_tty(slave);
747 #else
748     // Become the session/process-group leader
749     setsid();
750     setpgid(0, 0);
751
752     // Redirect standard I/O to the pty
753     dup2(slave, 0);
754     dup2(slave, 1);
755     dup2(slave, 2);
756     if (slave > 2) {
757       NOINTR(close(slave));
758     }
759 #endif
760     *pty                    = 0;
761
762     // Force the pty to be our control terminal
763     NOINTR(close(NOINTR(open(ptyPath, O_RDWR))));
764
765     return 0;
766   } else {
767     snprintf((char *)&(*utmp)->pid[0], sizeof((*utmp)->pid), "%d", pid);
768 #ifdef HAVE_UTMPX_H
769     (*utmp)->utmpx.ut_pid   = pid;
770 #endif
771     (*utmp)->pty            = *pty;
772     fcntl(*pty, F_SETFL, O_NONBLOCK|O_RDWR);
773     NOINTR(close(slave));
774     return pid;
775   }
776 }
777
778 static const struct passwd *getPWEnt(uid_t uid) {
779   struct passwd pwbuf, *pw;
780   char *buf;
781   #ifdef _SC_GETPW_R_SIZE_MAX
782   int len                           = sysconf(_SC_GETPW_R_SIZE_MAX);
783   if (len <= 0) {
784     len                             = 4096;
785   }
786   #else
787   int len                           = 4096;
788   #endif
789   check(buf                         = malloc(len));
790   check(!getpwuid_r(uid, &pwbuf, buf, len, &pw) && pw);
791   if (!pw->pw_name  ) pw->pw_name   = (char *)"";
792   if (!pw->pw_passwd) pw->pw_passwd = (char *)"";
793   if (!pw->pw_gecos ) pw->pw_gecos  = (char *)"";
794   if (!pw->pw_dir   ) pw->pw_dir    = (char *)"";
795   if (!pw->pw_shell ) pw->pw_shell  = (char *)"";
796   struct passwd *passwd;
797   check(passwd                      = calloc(sizeof(struct passwd) +
798                                              strlen(pw->pw_name) +
799                                              strlen(pw->pw_passwd) +
800                                              strlen(pw->pw_gecos) +
801                                              strlen(pw->pw_dir) +
802                                              strlen(pw->pw_shell) + 5, 1));
803   passwd->pw_uid                    = pw->pw_uid;
804   passwd->pw_gid                    = pw->pw_gid;
805   strncat(passwd->pw_shell          = strrchr(
806   strncat(passwd->pw_dir            = strrchr(
807   strncat(passwd->pw_gecos          = strrchr(
808   strncat(passwd->pw_passwd         = strrchr(
809   strncat(passwd->pw_name           = (char *)(passwd + 1),
810          pw->pw_name,   strlen(pw->pw_name)),   '\000') + 1,
811          pw->pw_passwd, strlen(pw->pw_passwd)), '\000') + 1,
812          pw->pw_gecos,  strlen(pw->pw_gecos)),  '\000') + 1,
813          pw->pw_dir,    strlen(pw->pw_dir)),    '\000') + 1,
814          pw->pw_shell,  strlen(pw->pw_shell));
815   free(buf);
816   return passwd;
817 }
818
819 static void sigAlrmHandler(int sig ATTR_UNUSED, siginfo_t *info ATTR_UNUSED,
820                            void *unused ATTR_UNUSED) {
821   UNUSED(sig);
822   UNUSED(info);
823   UNUSED(unused);
824   puts("\nLogin timed out after 60 seconds.");
825   _exit(1);
826 }
827
828 static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
829                                    char ***environment) {
830   // Time out after 60 seconds
831   struct sigaction sa;
832   memset(&sa, 0, sizeof(sa));
833   sa.sa_flags                  = SA_SIGINFO;
834   sa.sa_sigaction              = sigAlrmHandler;
835   check(!sigaction(SIGALRM, &sa, NULL));
836   alarm(60);
837
838   // Change the prompt to include the host name
839   const char *hostname         = NULL;
840   if (service->authUser == 2 /* SSH */) {
841     // If connecting to a remote host, include that hostname
842     hostname                   = strrchr(service->cmdline, '@');
843     if (!hostname || !strcmp(++hostname, "localhost")) {
844       hostname                 = NULL;
845     }
846   }
847   struct utsname uts;
848   memset(&uts, 0, sizeof(uts));
849   if (!hostname) {
850     // Find our local hostname
851     check(!uname(&uts));
852     hostname                   = uts.nodename;
853   }
854   const char *fqdn;
855   check(fqdn                   = strdup(hostname));
856   check(hostname               = strdup(hostname));
857   char *dot                    = strchr(hostname, '.');
858   if (dot) {
859     *dot                       = '\000';
860   }
861
862   const struct passwd *pw;
863   pam_handle_t *pam            = NULL;
864   if (service->authUser == 2 /* SSH */) {
865     // Just ask for the user name. SSH will negotiate the password
866     char *user                 = NULL;
867     char *prompt;
868     check(prompt               = stringPrintf(NULL, "%s login: ", hostname));
869     for (;;) {
870       if (read_string(1, prompt, &user) <= 0) {
871         free(user);
872         free(prompt);
873         _exit(1);
874       }
875       if (*user) {
876         for (char *u = user; *u; u++) {
877           char ch              = *u;
878           if (!((ch >= '0' && ch <= '9') ||
879                 (ch >= 'A' && ch <= 'Z') ||
880                 (ch >= 'a' && ch <= 'z') ||
881                 ch == '-' || ch == '_' || ch == '.')) {
882             goto invalid_user_name;
883           }
884         }
885         break;
886       }
887     invalid_user_name:
888       free(user);
889       user                     = NULL;
890     }
891     free(prompt);
892     char *cmdline              = stringPrintf(NULL, service->cmdline, user);
893     free(user);
894
895     // Replace '@localhost' with the actual host name. This results in a nicer
896     // prompt when SSH asks for the password.
897     char *ptr                  = strrchr(cmdline, '@');
898     if (!strcmp(ptr + 1, "localhost")) {
899       int offset               = ptr + 1 - cmdline;
900       check(cmdline            = realloc(cmdline,
901                                          strlen(cmdline) + strlen(fqdn) -
902                                          strlen("localhost") + 1));
903       ptr                      = cmdline + offset;
904       *ptr                     = '\000';
905       strncat(ptr, fqdn, strlen(fqdn));
906     }
907
908     free((void *)service->cmdline);
909     service->cmdline           = cmdline;
910
911     // Run SSH as an unprivileged user
912     if ((service->uid          = restricted) == 0) {
913       if (runAsUser >= 0) {
914         service->uid           = runAsUser;
915       } else {
916         service->uid           = getUserId("nobody");
917       }
918       if (runAsGroup >= 0) {
919         service->gid           = runAsGroup;
920       } else {
921         service->gid           = getGroupId("nogroup");
922       }
923     }
924     pw                         = getPWEnt(service->uid);
925     if (restricted) {
926       service->gid             = pw->pw_gid;
927     }
928     service->user              = getUserName(service->uid);
929     service->group             = getGroupName(service->gid);
930   } else {
931     // Use PAM to negotiate user authentication and authorization
932 #if defined(HAVE_SECURITY_PAM_APPL_H)
933     struct pam_conv conv       = { .conv = misc_conv };
934     if (service->authUser) {
935       check(supportsPAM());
936       check(pam_start("shellinabox", NULL, &conv, &pam) == PAM_SUCCESS);
937
938       const char *origPrompt;
939       check(pam_get_item(pam, PAM_USER_PROMPT, (void *)&origPrompt) ==
940             PAM_SUCCESS);
941       char *prompt;
942       check(prompt             = stringPrintf(NULL, "%s %s", hostname,
943                                          origPrompt ? origPrompt : "login: "));
944       check(pam_set_item(pam, PAM_USER_PROMPT, prompt) == PAM_SUCCESS);
945
946       // Up to three attempts to enter the user id and password
947       for (int i = 0;;) {
948         check(pam_set_item(pam, PAM_USER, NULL) == PAM_SUCCESS);
949         int rc;
950         if ((rc                = pam_authenticate(pam, PAM_SILENT)) ==
951             PAM_SUCCESS &&
952             (geteuid() ||
953              (rc               = pam_acct_mgmt(pam, PAM_SILENT)) ==
954              PAM_SUCCESS)) {
955           break;
956         }
957         if (++i == 3) {
958           // Quit if login failed.
959           puts("\nMaximum number of tries exceeded (3)");
960           pam_end(pam, rc);
961           _exit(1);
962         } else {
963           puts("\nLogin incorrect");
964         }
965       }
966       check(pam_set_item(pam, PAM_USER_PROMPT, "login: ") == PAM_SUCCESS);
967       free(prompt);
968
969       // Retrieve user id, and group id.
970       const char *name;
971       check(pam_get_item(pam, PAM_USER, (void *)&name) == PAM_SUCCESS);
972       pw                       = getPWEnt(getUserId(name));
973       check(service->uid < 0);
974       check(service->gid < 0);
975       check(!service->user);
976       check(!service->group);
977       service->uid             = pw->pw_uid;
978       service->gid             = pw->pw_gid;
979       check(service->user      = strdup(pw->pw_name));
980       service->group           = getGroupName(pw->pw_gid);
981     } else {
982       check(service->uid >= 0);
983       check(service->gid >= 0);
984       check(service->user);
985       check(service->group);
986       if (supportsPAM()) {
987         check(pam_start("shellinabox", service->user, &conv, &pam) ==
988               PAM_SUCCESS);
989         int rc;
990
991         // PAM account management requires root access. Just skip it, if we
992         // are running with lower privileges.
993         if (!geteuid() &&
994             (rc                = pam_acct_mgmt(pam, PAM_SILENT)) !=
995             PAM_SUCCESS) {
996           pam_end(pam, rc);
997           _exit(1);
998         }
999       }
1000       pw                       = getPWEnt(service->uid);
1001     }
1002 #else
1003     check(!supportsPAM());
1004     pw                         = getPWEnt(service->uid);
1005 #endif
1006   }
1007   free((void *)fqdn);
1008   free((void *)hostname);
1009
1010   if (service->useDefaultShell) {
1011     check(!service->cmdline);
1012     service->cmdline           = strdup(*pw->pw_shell ?
1013                                         pw->pw_shell : "/bin/sh");
1014   }
1015
1016   if (restricted &&
1017       (service->uid != (int)restricted || service->gid != (int)pw->pw_gid)) {
1018     puts("\nAccess denied!");
1019 #if defined(HAVE_SECURITY_PAM_APPL_H)
1020     if (service->authUser != 2 /* SSH */) {
1021       pam_end(pam, PAM_SUCCESS);
1022     }
1023 #endif
1024     _exit(1);
1025   }
1026
1027   if (service->authUser != 2 /* SSH */) {
1028 #if defined(HAVE_SECURITY_PAM_APPL_H)
1029     if (pam) {
1030 #ifdef HAVE_UTMPX_H
1031       check(pam_set_item(pam, PAM_TTY, (const void **)utmp->utmpx.ut_line) ==
1032             PAM_SUCCESS);
1033 #endif
1034     }
1035 #else
1036     check(!pam);
1037 #endif
1038   }
1039
1040   // Retrieve supplementary group ids.
1041   int ngroups;
1042 #if defined(__linux__)
1043   // On Linux, we can query the number of supplementary groups. On all other
1044   // platforms, we play it safe and just assume a fixed upper bound.
1045   ngroups                      = 0;
1046   getgrouplist(service->user, pw->pw_gid, NULL, &ngroups);
1047 #else
1048   ngroups                      = 128;
1049 #endif
1050   check(ngroups >= 0);
1051   if (ngroups > 0) {
1052     // Set supplementary group ids
1053     gid_t *groups;
1054     check(groups               = malloc((ngroups + 1) * sizeof(gid_t)));
1055     check(getgrouplist(service->user, pw->pw_gid, groups, &ngroups) >= 0);
1056
1057     // Make sure that any group that was requested on the command line is
1058     // included, if it is not one of the normal groups for this user.
1059     for (int i = 0; ; i++) {
1060       if (i == ngroups) {
1061         groups[ngroups++]      = service->gid;
1062         break;
1063       } else if ((int)groups[i] == service->gid) {
1064         break;
1065       }
1066     }
1067     setgroups(ngroups, groups);
1068     free(groups);
1069   }
1070
1071   // Add standard environment variables
1072   int numEnvVars               = 0;
1073   for (char **e = *environment; *e; numEnvVars++, e++) {
1074   }
1075   check(*environment           = realloc(*environment,
1076                                      (numEnvVars + 6)*sizeof(char *)));
1077   (*environment)[numEnvVars++] = stringPrintf(NULL, "HOME=%s", pw->pw_dir);
1078   (*environment)[numEnvVars++] = stringPrintf(NULL, "SHELL=%s", pw->pw_shell);
1079   check(
1080   (*environment)[numEnvVars++] = strdup(
1081                               "PATH=/usr/local/bin:/usr/bin:/bin:/usr/games"));
1082   (*environment)[numEnvVars++] = stringPrintf(NULL, "LOGNAME=%s",
1083                                               service->user);
1084   (*environment)[numEnvVars++] = stringPrintf(NULL, "USER=%s", service->user);
1085   (*environment)[numEnvVars++] = NULL;
1086   free((void *)pw);
1087
1088   // Update utmp/wtmp entries
1089 #ifdef HAVE_UTMPX_H
1090   if (service->authUser != 2 /* SSH */) {
1091     memset(&utmp->utmpx.ut_user, 0, sizeof(utmp->utmpx.ut_user));
1092     strncat(&utmp->utmpx.ut_user[0], service->user,
1093             sizeof(utmp->utmpx.ut_user));
1094     setutxent();
1095     pututxline(&utmp->utmpx);
1096     endutxent();
1097
1098 #if defined(HAVE_UPDWTMP) || defined(HAVE_UPDWTMPX)
1099     updwtmpx("/var/log/wtmp", &utmp->utmpx);
1100 #endif
1101   }
1102 #endif
1103
1104   alarm(0);
1105   return pam;
1106 }
1107
1108 static void destroyVariableHashEntry(void *arg ATTR_UNUSED, char *key,
1109                                      char *value) {
1110   UNUSED(arg);
1111   free(key);
1112   free(value);
1113 }
1114
1115 static void execService(int width ATTR_UNUSED, int height ATTR_UNUSED,
1116                         struct Service *service, const char *peerName,
1117                         char **environment, const char *url) {
1118   UNUSED(width);
1119   UNUSED(height);
1120
1121   // Create a hash table with all the variables that we can expand. This
1122   // includes all environment variables being passed to the child.
1123   HashMap *vars;
1124   check(vars                  = newHashMap(destroyVariableHashEntry, NULL));
1125   for (char **e = environment; *e; e++) {
1126     char *ptr                 = strchr(*e, '=');
1127     char *key, *value;
1128     if (!ptr) {
1129       check(key               = strdup(*e));
1130       check(value             = strdup(""));
1131     } else {
1132       check(key               = malloc(ptr - *e + 1));
1133       memcpy(key, *e, ptr - *e);
1134       key[ptr - *e]           = '\000';
1135       check(value             = strdup(ptr + 1));
1136     }
1137     // All of our variables are lower-case
1138     for (ptr = key; *ptr; ptr++) {
1139       if (*ptr >= 'A' && *ptr <= 'Z') {
1140         *ptr += 'a' - 'A';
1141       }
1142     }
1143     addToHashMap(vars, key, value);
1144   }
1145   char *key, *value;
1146   check(key                   = strdup("gid"));
1147   addToHashMap(vars, key, stringPrintf(NULL, "%d", service->gid));
1148   check(key                   = strdup("group"));
1149   check(value                 = strdup(service->group));
1150   addToHashMap(vars, key, value);
1151   check(key                   = strdup("peer"));
1152   check(value                 = strdup(peerName));
1153   addToHashMap(vars, key, value);
1154   check(key                   = strdup("uid"));
1155   addToHashMap(vars, key, stringPrintf(NULL, "%d", service->uid));
1156   check(key                   = strdup("url"));
1157   addToHashMap(vars, key, strdup(url));
1158
1159   enum { ENV, ARGS } state    = ENV;
1160   enum { NONE, SINGLE, DOUBLE
1161   } quote                     = NONE;
1162   char *cmdline;
1163   check(cmdline               = strdup(service->cmdline));
1164   int argc                    = 0;
1165   char **argv;
1166   check(argv                  = malloc(sizeof(char *)));
1167   key                         = NULL;
1168   value                       = NULL;
1169   for (char *ptr = cmdline; ; ptr++) {
1170     if (!key && *ptr && *ptr != ' ') {
1171       key                     = ptr;
1172     }
1173     switch (*ptr) {
1174     case '\'':
1175       if (quote == SINGLE || quote == NONE) {
1176         memmove(ptr, ptr + 1, strlen(ptr));
1177         ptr--;
1178         quote                 = quote == SINGLE ? NONE : SINGLE;
1179       } else {
1180         dcheck(quote == DOUBLE);
1181       }
1182       break;
1183     case '\"':
1184       if (quote == DOUBLE || quote == NONE) {
1185         memmove(ptr, ptr + 1, strlen(ptr));
1186         ptr--;
1187         quote                 = quote == DOUBLE ? NONE : DOUBLE;
1188       } else {
1189         dcheck(quote == SINGLE);
1190       }
1191       break;
1192     case '$':
1193       if ((quote == NONE || quote == DOUBLE) && ptr[1] == '{') {
1194         // Always treat environment variables as if they were quoted. There
1195         // is no good reason for us to try to look for spaces within
1196         // expanded environment variables. This just leads to subtle bugs.
1197         char *end             = ptr + 2;
1198         while (*end && *end != '}') {
1199           end++;
1200         }
1201         char ch               = *end;
1202         *end                  = '\000';
1203         const char *repl      = getFromHashMap(vars, ptr + 2);
1204         int replLen           = repl ? strlen(repl) : 0;
1205         *end                  = ch;
1206         if (ch) {
1207           end++;
1208         }
1209         int incr              = replLen - (end - ptr);
1210         if (incr > 0) {
1211           char *oldCmdline    = cmdline;
1212           check(cmdline       = realloc(cmdline,
1213                                         (end - cmdline) + strlen(end) +
1214                                         incr + 1));
1215           ptr                += cmdline - oldCmdline;
1216           end                += cmdline - oldCmdline;
1217           if (key) {
1218             key              += cmdline - oldCmdline;
1219           }
1220           if (value) {
1221             value            += cmdline - oldCmdline;
1222           }
1223         }
1224         memmove(ptr + replLen, end, strlen(end) + 1);
1225         if (repl) {
1226           memcpy(ptr, repl, replLen);
1227         }
1228         ptr                  += replLen - 1;
1229       }
1230       break;
1231     case '\\':
1232       if (!ptr[1]) {
1233         *ptr--                = '\000';
1234       } else {
1235         memmove(ptr, ptr + 1, strlen(ptr));
1236       }
1237       break;
1238     case '=':
1239       // This is the seperator between keys and values of any environment
1240       // variable that we are asked to set.
1241       if (state == ENV && quote == NONE && !value) {
1242         *ptr                  = '\000';
1243         value                 = ptr + 1;
1244       }
1245       break;
1246     case ' ':
1247       // If this space character is not quoted, this is the start of a new
1248       // command line argument.
1249       if (quote != NONE) {
1250         break;
1251       }
1252       // Fall thru
1253     case '\000':;
1254       char ch                 = *ptr;
1255       if (key) {
1256         *ptr                  = '\000';
1257         if (state == ENV && value) {
1258           // Override an existing environment variable.
1259           int numEnvVars = 0;
1260           int len             = strlen(key);
1261           for (char **e = environment; *e; e++, numEnvVars++) {
1262             if (!strncmp(*e, key, len) && (*e)[len] == '=') {
1263               int s_size      = len + strlen(value) + 1;
1264               check(*e        = realloc(*e, s_size + 1));
1265               (*e)[len + 1]   = '\000';
1266               strncat(*e, value, s_size);
1267               numEnvVars      = -1;
1268               break;
1269             }
1270           }
1271           // Add a new environment variable
1272           if (numEnvVars >= 0) {
1273             check(environment = realloc(environment,
1274                                         (numEnvVars + 2)*sizeof(char *)));
1275             value[-1]         = '=';
1276             environment[numEnvVars++] = strdup(key);
1277             environment[numEnvVars]   = NULL;
1278           }
1279         } else {
1280           // Add entry to argv.
1281           state               = ARGS;
1282           argv[argc++]        = strdup(key);
1283           check(argv          = realloc(argv, (argc + 1)*sizeof(char *)));
1284         }
1285       }
1286       key                     = NULL;
1287       value                   = NULL;
1288       if (!ch) {
1289         goto done;
1290       }
1291       break;
1292     default:
1293       break;
1294     }
1295   }
1296  done:
1297   free(cmdline);
1298   argv[argc]                  = NULL;
1299   deleteHashMap(vars);
1300   check(argc);
1301
1302   extern char **environ;
1303   environ                     = environment;
1304   char *cmd                   = strdup(argv[0]);
1305   char *slash                 = strrchr(argv[0], '/');
1306   if (slash) {
1307     memmove(argv[0], slash + 1, strlen(slash));
1308   }
1309   if (service->useDefaultShell) {
1310     int len                   = strlen(argv[0]);
1311     check(argv[0]             = realloc(argv[0], len + 2));
1312     memmove(argv[0] + 1, argv[0], len);
1313     argv[0][0]                = '-';
1314     argv[0][len + 1]          = '\000';
1315   }
1316   execvp(cmd, argv);
1317 }
1318
1319 void setWindowSize(int pty, int width, int height) {
1320   if (width > 0 && height > 0) {
1321     #ifdef TIOCSSIZE
1322     {
1323       struct ttysize win;
1324       ioctl(pty, TIOCGSIZE, &win);
1325       win.ts_lines = height;
1326       win.ts_cols  = width;
1327       ioctl(pty, TIOCSSIZE, &win);
1328     }
1329     #endif
1330     #ifdef TIOCGWINSZ
1331     {
1332       struct winsize win;
1333       ioctl(pty, TIOCGWINSZ, &win);
1334       win.ws_row   = height;
1335       win.ws_col   = width;
1336       ioctl(pty, TIOCSWINSZ, &win);
1337     }
1338     #endif
1339   }
1340 }
1341
1342 static void childProcess(struct Service *service, int width, int height,
1343                          struct Utmp *utmp, const char *peerName,
1344                          const char *url) {
1345   // Set initial window size
1346   setWindowSize(0, width, height);
1347
1348   // Set up environment variables
1349   static const char *legalEnv[] = { "TZ", "HZ", NULL };
1350   char **environment;
1351   check(environment             = malloc(2*sizeof(char *)));
1352   int numEnvVars                = 1;
1353   check(environment[0]          = strdup("TERM=xterm"));
1354   if (width > 0 && height > 0) {
1355     numEnvVars                 += 2;
1356     check(environment           = realloc(environment,
1357                                           (numEnvVars + 1)*sizeof(char *)));
1358     environment[numEnvVars-2]   = stringPrintf(NULL, "COLUMNS=%d", width);
1359     environment[numEnvVars-1]   = stringPrintf(NULL, "LINES=%d", height);
1360   }
1361   for (int i = 0; legalEnv[i]; i++) {
1362     char *value                 = getenv(legalEnv[i]);
1363     if (value) {
1364       numEnvVars++;
1365       check(environment         = realloc(environment,
1366                                           (numEnvVars + 1)*sizeof(char *)));
1367       environment[numEnvVars-1] = stringPrintf(NULL, "%s=%s",
1368                                                legalEnv[i], value);
1369     }
1370   }
1371   environment[numEnvVars]       = NULL;
1372
1373   // Set initial terminal settings
1374   struct termios tt = { 0 };
1375   tcgetattr(0, &tt);
1376   cfsetispeed(&tt, 38400);
1377   cfsetospeed(&tt, 38400);
1378   tt.c_iflag                    =  TTYDEF_IFLAG & ~ISTRIP;
1379   tt.c_oflag                    =  TTYDEF_OFLAG;
1380   tt.c_lflag                    =  TTYDEF_LFLAG;
1381   tt.c_cflag                    = (TTYDEF_CFLAG & ~(CS7|PARENB|HUPCL)) | CS8;
1382   tt.c_cc[VERASE]               = '\x7F';
1383   tcsetattr(0, TCSAFLUSH, &tt);
1384
1385   // Assert root privileges in order to update utmp entry.
1386   setresuid(0, 0, 0);
1387   setresgid(0, 0, 0);
1388 #ifdef HAVE_UTMPX_H
1389   setutxent();
1390   struct utmpx utmpx            = utmp->utmpx;
1391   if (service->useLogin || service->authUser) {
1392     utmpx.ut_type               = LOGIN_PROCESS;
1393     memset(utmpx.ut_host, 0, sizeof(utmpx.ut_host));
1394   }
1395   pututxline(&utmpx);
1396   endutxent();
1397
1398 #if defined(HAVE_UPDWTMP) || defined(HAVE_UPDWTMPX)
1399   if (!utmp->useLogin) {
1400     memset(&utmpx.ut_user, 0, sizeof(utmpx.ut_user));
1401     strncat(&utmpx.ut_user[0], "LOGIN", sizeof(utmpx.ut_user));
1402     updwtmpx("/var/log/wtmp", &utmpx);
1403   }
1404 #endif
1405 #endif
1406
1407   // Create session. We might have to fork another process as PAM wants us
1408   // to close the session when the child terminates. And we must retain
1409   // permissions, as session closure could require root permissions.
1410   // None of this really applies if we are running as an unprivileged user.
1411   // In that case, we do not bother about session management.
1412   if (!service->useLogin) {
1413     pam_handle_t *pam           = internalLogin(service, utmp, &environment);
1414 #if defined(HAVE_SECURITY_PAM_APPL_H)
1415     if (pam && !geteuid()) {
1416       if (pam_open_session(pam, PAM_SILENT) != PAM_SUCCESS) {
1417         fprintf(stderr, "Access denied.\n");
1418         _exit(1);
1419       }
1420       pid_t pid                 = fork();
1421       switch (pid) {
1422       case -1:
1423         _exit(1);
1424       case 0:
1425         break;
1426       default:;
1427         // Finish all pending PAM operations.
1428         int status, rc;
1429         check(NOINTR(waitpid(pid, &status, 0)) == pid);
1430         rc = pam_close_session(pam, PAM_SILENT);
1431         pam_end(pam, rc | PAM_DATA_SILENT);
1432         _exit(WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status));
1433       }
1434     }
1435 #else
1436     check(!pam);
1437 #endif
1438   }
1439
1440   // Change user and group ids
1441   check(!setresgid(service->gid, service->gid, service->gid));
1442   check(!setresuid(service->uid, service->uid, service->uid));
1443   
1444   // Change working directory
1445   if (service->useHomeDir) {
1446     check(!service->useLogin);
1447     const struct passwd *pw     = getPWEnt(getuid());
1448     check(!service->cwd);
1449     check(service->cwd          = strdup(pw->pw_dir));
1450     free((void *)pw);
1451   }
1452   check(service->cwd);
1453   if (!*service->cwd || *service->cwd != '/' || chdir(service->cwd)) {
1454     check(service->cwd          = realloc((char *)service->cwd, 2));
1455     *(char *)service->cwd       = '\000';
1456     strncat((char *)service->cwd, "/", 1);
1457     puts("No directory, logging in with HOME=/");
1458     check(!chdir("/"));
1459     for (int i = 0; environment[i]; i++) {
1460       if (!strncmp(environment[i], "HOME=", 5)) {
1461         free(environment[i]);
1462         check(environment[i]    = strdup("HOME=/"));
1463         break;
1464       }
1465     }
1466   }
1467
1468   // Finally, launch the child process.
1469   if (service->useLogin == 1) {
1470     execle("/bin/login", "login", "-p", "-h", peerName,
1471            (void *)0, environment);
1472     execle("/usr/bin/login", "login", "-p", "-h", peerName,
1473            (void *)0, environment);
1474   } else {
1475     execService(width, height, service, peerName, environment, url);
1476   }
1477   _exit(1);
1478 }
1479
1480 static void sigChildHandler(int sig ATTR_UNUSED, siginfo_t *info ATTR_UNUSED,
1481                             void *unused ATTR_UNUSED) {
1482   UNUSED(sig);
1483   UNUSED(info);
1484   UNUSED(unused);
1485 }
1486
1487 static void launcherDaemon(int fd) {
1488   struct sigaction sa;
1489   memset(&sa, 0, sizeof(sa));
1490   sa.sa_flags                 = SA_NOCLDSTOP | SA_SIGINFO;
1491   sa.sa_sigaction             = sigChildHandler;
1492   check(!sigaction(SIGCHLD, &sa, NULL));
1493
1494   struct LaunchRequest request;
1495   for (;;) {
1496     errno                     = 0;
1497     int len                   = read(fd, &request, sizeof(request));
1498     if (len != sizeof(request) && errno != EINTR) {
1499       if (len) {
1500         debug("Failed to read launch request");
1501       }
1502       break;
1503     }
1504
1505     // Check whether our read operation got interrupted, because a child
1506     // has died.
1507     int   status;
1508     pid_t pid;
1509     while (NOINTR(pid = waitpid(-1, &status, WNOHANG)) > 0) {
1510       if (WIFEXITED(pid) || WIFSIGNALED(pid)) {
1511         char key[32];
1512         snprintf(&key[0], sizeof(key), "%d", pid);
1513         deleteFromHashMap(childProcesses, key);
1514       }
1515     }
1516     if (len != sizeof(request)) {
1517       continue;
1518     }
1519
1520     char *url;
1521     check(url                 = calloc(request.urlLength + 1, 1));
1522   readURL:
1523     len                       = read(fd, url, request.urlLength + 1);
1524     if (len != request.urlLength + 1 && errno != EINTR) {
1525       debug("Failed to read URL");
1526       free(url);
1527       break;
1528     }
1529     while (NOINTR(pid = waitpid(-1, &status, WNOHANG)) > 0) {
1530       if (WIFEXITED(pid) || WIFSIGNALED(pid)) {
1531         char key[32];
1532         snprintf(&key[0], sizeof(key), "%d", pid);
1533         deleteFromHashMap(childProcesses, key);
1534       }
1535     }
1536     if (len != request.urlLength + 1) {
1537       goto readURL;
1538     }
1539
1540     check(request.service >= 0);
1541     check(request.service < numServices);
1542
1543     // Sanitize the host name, so that we do not pass any unexpected characters
1544     // to our child process.
1545     request.peerName[sizeof(request.peerName)-1] = '\000';
1546     for (char *s = request.peerName; *s; s++) {
1547       if (!((*s >= '0' && *s <= '9') ||
1548             (*s >= 'A' && *s <= 'Z') ||
1549             (*s >= 'a' && *s <= 'z') ||
1550              *s == '.' || *s == '-')) {
1551         *s                    = '-';
1552       }
1553     }
1554
1555     // Fork and exec the child process.
1556     int pty;
1557     struct Utmp *utmp;
1558     if ((pid                  = forkPty(&pty,
1559                                         services[request.service]->useLogin,
1560                                         &utmp, request.peerName)) == 0) {
1561       childProcess(services[request.service], request.width, request.height,
1562                    utmp, request.peerName, url);
1563       free(url);
1564       _exit(1);
1565     } else {
1566       // Remember the utmp entry so that we can clean up when the child
1567       // terminates.
1568       free(url);
1569       if (pid > 0) {
1570         if (!childProcesses) {
1571           childProcesses      = newHashMap(destroyUtmpHashEntry, NULL);
1572         }
1573         addToHashMap(childProcesses, utmp->pid, (char *)utmp);
1574       } else {
1575         int fds[2];
1576         if (!pipe(fds)) {
1577           NOINTR(write(fds[1], "forkpty() failed\r\n", 18));
1578           NOINTR(close(fds[1]));
1579           pty                 = fds[0];
1580           pid                 = 0;
1581         }
1582       }
1583
1584       // Send file handle and process id back to parent
1585       char cmsg_buf[CMSG_SPACE(sizeof(int))] = { 0 };
1586       struct iovec  iov       = { 0 };
1587       struct msghdr msg       = { 0 };
1588       iov.iov_base            = &pid;
1589       iov.iov_len             = sizeof(pid);
1590       msg.msg_iov             = &iov;
1591       msg.msg_iovlen          = 1;
1592       msg.msg_control         = &cmsg_buf;
1593       msg.msg_controllen      = sizeof(cmsg_buf);
1594       struct cmsghdr *cmsg    = CMSG_FIRSTHDR(&msg);
1595       check(cmsg);
1596       cmsg->cmsg_level        = SOL_SOCKET;
1597       cmsg->cmsg_type         = SCM_RIGHTS;
1598       cmsg->cmsg_len          = CMSG_LEN(sizeof(int));
1599       memcpy(CMSG_DATA(cmsg), &pty, sizeof(int));
1600       if (NOINTR(sendmsg(fd, &msg, 0)) != sizeof(pid)) {
1601         break;
1602       }
1603       NOINTR(close(pty));
1604     }
1605   }
1606   deleteHashMap(childProcesses);
1607   _exit(0);
1608 }
1609
1610 int forkLauncher(void) {
1611   int pair[2];
1612   check(!socketpair(AF_UNIX, SOCK_STREAM, 0, pair));
1613
1614   switch (fork()) {
1615   case 0:;
1616     // If our real-uid is not "root", then we should not allow anybody to
1617     // login unauthenticated users as anyone other than their own.
1618     uid_t tmp;
1619     check(!getresuid(&restricted, &tmp, &tmp));
1620
1621     // Temporarily drop most permissions. We still retain the ability to
1622     // switch back to root, which is necessary for launching "login".
1623     lowerPrivileges();
1624     closeAllFds((int []){ pair[1], 2 }, 2);
1625     launcherDaemon(pair[1]);
1626     fatal("exit() failed!");
1627   case -1:
1628     fatal("fork() failed!");
1629   default:
1630     NOINTR(close(pair[1]));
1631     launcher = pair[0];
1632     return launcher;
1633   }
1634 }
1635
1636 void terminateLauncher(void) {
1637   if (launcher >= 0) {
1638     NOINTR(close(launcher));
1639     launcher = -1;
1640   }
1641 }
This page took 2.328997 seconds and 3 git commands to generate.