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