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