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