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