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