1/* vi: set sw=4 ts=4: */
2/*
3 * Utility routines.
4 *
5 * Copyright (C) tons of folks.  Tracking down who wrote what
6 * isn't something I'm going to worry about...  If you wrote something
7 * here, please feel free to acknowledge your work.
8 *
9 * Based in part on code from sash, Copyright (c) 1999 by David I. Bell
10 * Permission has been granted to redistribute this code under the GPL.
11 *
12 * Licensed under GPLv2 or later, see file License in this tarball for details.
13 */
14
15#include <assert.h>
16#include "busybox.h"
17
18/* Apparently uclibc defines __GLIBC__ (compat trick?). Oh well. */
19#if ENABLE_STATIC && defined(__GLIBC__) && !defined(__UCLIBC__)
20#warning Static linking against glibc produces buggy executables
21#warning (glibc does not cope well with ld --gc-sections).
22#warning See sources.redhat.com/bugzilla/show_bug.cgi?id=3400
23#warning Note that glibc is unsuitable for static linking anyway.
24#warning If you still want to do it, remove -Wl,--gc-sections
25#warning from top-level Makefile and remove this warning.
26#error Aborting compilation.
27#endif
28
29
30/* Declare <applet>_main() */
31#define PROTOTYPES
32#include "applets.h"
33#undef PROTOTYPES
34
35#if ENABLE_SHOW_USAGE && !ENABLE_FEATURE_COMPRESS_USAGE
36/* Define usage_messages[] */
37static const char usage_messages[] ALIGN1 = ""
38#define MAKE_USAGE
39#include "usage.h"
40#include "applets.h"
41;
42#undef MAKE_USAGE
43#else
44#define usage_messages 0
45#endif /* SHOW_USAGE */
46
47/* Define struct bb_applet applets[] */
48#include "applets.h"
49/* The -1 arises because of the {0,NULL,0,-1} entry. */
50
51#if ENABLE_FEATURE_SH_STANDALONE
52const unsigned short NUM_APPLETS = ARRAY_SIZE(applets);
53#endif
54const struct bb_applet *current_applet;
55const char *applet_name ATTRIBUTE_EXTERNALLY_VISIBLE;
56#if !BB_MMU
57bool re_execed;
58#endif
59
60USE_FEATURE_SUID(static uid_t ruid;)  /* real uid */
61
62#if ENABLE_FEATURE_SUID_CONFIG
63
64/* applets[] is const, so we have to define this "override" structure */
65static struct BB_suid_config {
66	const struct bb_applet *m_applet;
67	uid_t m_uid;
68	gid_t m_gid;
69	mode_t m_mode;
70	struct BB_suid_config *m_next;
71} *suid_config;
72
73static bool suid_cfg_readable;
74
75/* check if u is member of group g */
76static int ingroup(uid_t u, gid_t g)
77{
78	struct group *grp = getgrgid(g);
79
80	if (grp) {
81		char **mem;
82
83		for (mem = grp->gr_mem; *mem; mem++) {
84			struct passwd *pwd = getpwnam(*mem);
85
86			if (pwd && (pwd->pw_uid == u))
87				return 1;
88		}
89	}
90	return 0;
91}
92
93/* This should probably be a libbb routine.  In that case,
94 * I'd probably rename it to something like bb_trimmed_slice.
95 */
96static char *get_trimmed_slice(char *s, char *e)
97{
98	/* First, consider the value at e to be nul and back up until we
99	 * reach a non-space char.  Set the char after that (possibly at
100	 * the original e) to nul. */
101	while (e-- > s) {
102		if (!isspace(*e)) {
103			break;
104		}
105	}
106	e[1] = '\0';
107
108	/* Next, advance past all leading space and return a ptr to the
109	 * first non-space char; possibly the terminating nul. */
110	return skip_whitespace(s);
111}
112
113/* Don't depend on the tools to combine strings. */
114static const char config_file[] ALIGN1 = "/etc/busybox.conf";
115
116/* We don't supply a value for the nul, so an index adjustment is
117 * necessary below.  Also, we use unsigned short here to save some
118 * space even though these are really mode_t values. */
119static const unsigned short mode_mask[] ALIGN2 = {
120	S_ISUID,    S_ISUID|S_IXUSR,    S_IXUSR,    0,	/* user */
121	S_ISGID,    S_ISGID|S_IXGRP,    S_IXGRP,    0,	/* group */
122	0,          S_IXOTH,            S_IXOTH,    0	/* other */
123};
124
125#define parse_error(x)  do { errmsg = x; goto pe_label; } while (0)
126
127static void parse_config_file(void)
128{
129	struct BB_suid_config *sct_head;
130	struct BB_suid_config *sct;
131	const struct bb_applet *applet;
132	FILE *f;
133	const char *errmsg;
134	char *s;
135	char *e;
136	int i;
137	unsigned lc;
138	smallint section;
139	char buffer[256];
140	struct stat st;
141
142	assert(!suid_config); /* Should be set to NULL by bss init. */
143
144	ruid = getuid();
145	if (ruid == 0) /* run by root - don't need to even read config file */
146		return;
147
148	if ((stat(config_file, &st) != 0)       /* No config file? */
149	 || !S_ISREG(st.st_mode)                /* Not a regular file? */
150	 || (st.st_uid != 0)                    /* Not owned by root? */
151	 || (st.st_mode & (S_IWGRP | S_IWOTH))  /* Writable by non-root? */
152	 || !(f = fopen(config_file, "r"))      /* Cannot open? */
153	) {
154		return;
155	}
156
157	suid_cfg_readable = 1;
158	sct_head = NULL;
159	section = lc = 0;
160
161	while (1) {
162		s = buffer;
163
164		if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */
165			if (ferror(f)) {   /* Make sure it wasn't a read error. */
166				parse_error("reading");
167			}
168			fclose(f);
169			suid_config = sct_head;	/* Success, so set the pointer. */
170			return;
171		}
172
173		lc++;					/* Got a (partial) line. */
174
175		/* If a line is too long for our buffer, we consider it an error.
176		 * The following test does mistreat one corner case though.
177		 * If the final line of the file does not end with a newline and
178		 * yet exactly fills the buffer, it will be treated as too long
179		 * even though there isn't really a problem.  But it isn't really
180		 * worth adding code to deal with such an unlikely situation, and
181		 * we do err on the side of caution.  Besides, the line would be
182		 * too long if it did end with a newline. */
183		if (!strchr(s, '\n') && !feof(f)) {
184			parse_error("line too long");
185		}
186
187		/* Trim leading and trailing whitespace, ignoring comments, and
188		 * check if the resulting string is empty. */
189		s = get_trimmed_slice(s, strchrnul(s, '#'));
190		if (!*s) {
191			continue;
192		}
193
194		/* Check for a section header. */
195
196		if (*s == '[') {
197			/* Unlike the old code, we ignore leading and trailing
198			 * whitespace for the section name.  We also require that
199			 * there are no stray characters after the closing bracket. */
200			e = strchr(s, ']');
201			if (!e   /* Missing right bracket? */
202			 || e[1] /* Trailing characters? */
203			 || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */
204			) {
205				parse_error("section header");
206			}
207			/* Right now we only have one section so just check it.
208			 * If more sections are added in the future, please don't
209			 * resort to cascading ifs with multiple strcasecmp calls.
210			 * That kind of bloated code is all too common.  A loop
211			 * and a string table would be a better choice unless the
212			 * number of sections is very small. */
213			if (strcasecmp(s, "SUID") == 0) {
214				section = 1;
215				continue;
216			}
217			section = -1;	/* Unknown section so set to skip. */
218			continue;
219		}
220
221		/* Process sections. */
222
223		if (section == 1) {		/* SUID */
224			/* Since we trimmed leading and trailing space above, we're
225			 * now looking for strings of the form
226			 *    <key>[::space::]*=[::space::]*<value>
227			 * where both key and value could contain inner whitespace. */
228
229			/* First get the key (an applet name in our case). */
230			e = strchr(s, '=');
231			if (e) {
232				s = get_trimmed_slice(s, e);
233			}
234			if (!e || !*s) {	/* Missing '=' or empty key. */
235				parse_error("keyword");
236			}
237
238			/* Ok, we have an applet name.  Process the rhs if this
239			 * applet is currently built in and ignore it otherwise.
240			 * Note: this can hide config file bugs which only pop
241			 * up when the busybox configuration is changed. */
242			applet = find_applet_by_name(s);
243			if (applet) {
244				/* Note: We currently don't check for duplicates!
245				 * The last config line for each applet will be the
246				 * one used since we insert at the head of the list.
247				 * I suppose this could be considered a feature. */
248				sct = xmalloc(sizeof(struct BB_suid_config));
249				sct->m_applet = applet;
250				sct->m_mode = 0;
251				sct->m_next = sct_head;
252				sct_head = sct;
253
254				/* Get the specified mode. */
255
256				e = skip_whitespace(e+1);
257
258				for (i = 0; i < 3; i++) {
259					/* There are 4 chars + 1 nul for each of user/group/other. */
260					static const char mode_chars[] ALIGN1 = "Ssx-\0" "Ssx-\0" "Ttx-";
261
262					const char *q;
263					q = strchrnul(mode_chars + 5*i, *e++);
264					if (!*q) {
265						parse_error("mode");
266					}
267					/* Adjust by -i to account for nul. */
268					sct->m_mode |= mode_mask[(q - mode_chars) - i];
269				}
270
271				/* Now get the the user/group info. */
272
273				s = skip_whitespace(e);
274
275				/* Note: we require whitespace between the mode and the
276				 * user/group info. */
277				if ((s == e) || !(e = strchr(s, '.'))) {
278					parse_error("<uid>.<gid>");
279				}
280				*e++ = '\0';
281
282				/* We can't use get_ug_id here since it would exit()
283				 * if a uid or gid was not found.  Oh well... */
284				sct->m_uid = bb_strtoul(s, NULL, 10);
285				if (errno) {
286					struct passwd *pwd = getpwnam(s);
287					if (!pwd) {
288						parse_error("user");
289					}
290					sct->m_uid = pwd->pw_uid;
291				}
292
293				sct->m_gid = bb_strtoul(e, NULL, 10);
294				if (errno) {
295					struct group *grp;
296					grp = getgrnam(e);
297					if (!grp) {
298						parse_error("group");
299					}
300					sct->m_gid = grp->gr_gid;
301				}
302			}
303			continue;
304		}
305
306		/* Unknown sections are ignored. */
307
308		/* Encountering configuration lines prior to seeing a
309		 * section header is treated as an error.  This is how
310		 * the old code worked, but it may not be desirable.
311		 * We may want to simply ignore such lines in case they
312		 * are used in some future version of busybox. */
313		if (!section) {
314			parse_error("keyword outside section");
315		}
316
317	} /* while (1) */
318
319 pe_label:
320	fprintf(stderr, "Parse error in %s, line %d: %s\n",
321			config_file, lc, errmsg);
322
323	fclose(f);
324	/* Release any allocated memory before returning. */
325	while (sct_head) {
326		sct = sct_head->m_next;
327		free(sct_head);
328		sct_head = sct;
329	}
330}
331#else
332static inline void parse_config_file(void)
333{
334	USE_FEATURE_SUID(ruid = getuid();)
335}
336#endif /* FEATURE_SUID_CONFIG */
337
338
339#if ENABLE_FEATURE_SUID
340static void check_suid(const struct bb_applet *applet)
341{
342	gid_t rgid;  /* real gid */
343
344	if (ruid == 0) /* set by parse_config_file() */
345		return; /* run by root - no need to check more */
346	rgid = getgid();
347
348#if ENABLE_FEATURE_SUID_CONFIG
349	if (suid_cfg_readable) {
350		uid_t uid;
351		struct BB_suid_config *sct;
352		mode_t m;
353
354		for (sct = suid_config; sct; sct = sct->m_next) {
355			if (sct->m_applet == applet)
356				goto found;
357		}
358		/* default: drop all privileges */
359		xsetgid(rgid);
360		xsetuid(ruid);
361		return;
362 found:
363		m = sct->m_mode;
364		if (sct->m_uid == ruid)
365			/* same uid */
366			m >>= 6;
367		else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid))
368			/* same group / in group */
369			m >>= 3;
370
371		if (!(m & S_IXOTH))           /* is x bit not set ? */
372			bb_error_msg_and_die("you have no permission to run this applet!");
373
374		/* _both_ sgid and group_exec have to be set for setegid */
375		if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
376			rgid = sct->m_gid;
377		/* else (no setegid) we will set egid = rgid */
378
379		/* We set effective AND saved ids. If saved-id is not set
380		 * like we do below, seteiud(0) can still later succeed! */
381		if (setresgid(-1, rgid, rgid))
382			bb_perror_msg_and_die("setresgid");
383
384		/* do we have to set effective uid? */
385		uid = ruid;
386		if (sct->m_mode & S_ISUID)
387			uid = sct->m_uid;
388		/* else (no seteuid) we will set euid = ruid */
389
390		if (setresuid(-1, uid, uid))
391			bb_perror_msg_and_die("setresuid");
392		return;
393	}
394#if !ENABLE_FEATURE_SUID_CONFIG_QUIET
395	{
396		static bool onetime = 0;
397
398		if (!onetime) {
399			onetime = 1;
400			fprintf(stderr, "Using fallback suid method\n");
401		}
402	}
403#endif
404#endif
405
406	if (applet->need_suid == _BB_SUID_ALWAYS) {
407		/* Real uid is not 0. If euid isn't 0 too, suid bit
408		 * is most probably not set on our executable */
409		if (geteuid())
410			bb_error_msg_and_die("applet requires root privileges!");
411	} else if (applet->need_suid == _BB_SUID_NEVER) {
412		xsetgid(rgid);  /* drop all privileges */
413		xsetuid(ruid);
414	}
415}
416#else
417#define check_suid(x) ((void)0)
418#endif /* FEATURE_SUID */
419
420
421#if ENABLE_FEATURE_COMPRESS_USAGE
422
423#include "usage_compressed.h"
424#include "unarchive.h"
425
426static const char *unpack_usage_messages(void)
427{
428	char *outbuf = NULL;
429	bunzip_data *bd;
430	int i;
431
432	i = start_bunzip(&bd,
433			/* src_fd: */ -1,
434			/* inbuf:  */ packed_usage,
435			/* len:    */ sizeof(packed_usage));
436	/* read_bunzip can longjmp to start_bunzip, and ultimately
437	 * end up here with i != 0 on read data errors! Not trivial */
438	if (!i) {
439		/* Cannot use xmalloc: will leak bd in NOFORK case! */
440		outbuf = malloc_or_warn(SIZEOF_usage_messages);
441		if (outbuf)
442			read_bunzip(bd, outbuf, SIZEOF_usage_messages);
443	}
444	dealloc_bunzip(bd);
445	return outbuf;
446}
447#define dealloc_usage_messages(s) free(s)
448
449#else
450
451#define unpack_usage_messages() usage_messages
452#define dealloc_usage_messages(s) ((void)(s))
453
454#endif /* FEATURE_COMPRESS_USAGE */
455
456
457void bb_show_usage(void)
458{
459	if (ENABLE_SHOW_USAGE) {
460		const char *format_string;
461		const char *p;
462		const char *usage_string = p = unpack_usage_messages();
463		int i;
464
465		i = current_applet - applets;
466		while (i) {
467			while (*p++) continue;
468			i--;
469		}
470
471		fprintf(stderr, "%s multi-call binary\n", bb_banner);
472		format_string = "\nUsage: %s %s\n\n";
473		if (*p == '\b')
474			format_string = "\nNo help available.\n\n";
475		fprintf(stderr, format_string, applet_name, p);
476		dealloc_usage_messages((char*)usage_string);
477	}
478	xfunc_die();
479}
480
481
482static int applet_name_compare(const void *name, const void *vapplet)
483{
484	const struct bb_applet *applet = vapplet;
485
486	return strcmp(name, applet->name);
487}
488
489const struct bb_applet *find_applet_by_name(const char *name)
490{
491	/* Do a binary search to find the applet entry given the name. */
492	return bsearch(name, applets, ARRAY_SIZE(applets)-1, sizeof(applets[0]),
493				applet_name_compare);
494}
495
496
497#if ENABLE_FEATURE_INSTALLER
498/* create (sym)links for each applet */
499static void install_links(const char *busybox, int use_symbolic_links)
500{
501	/* directory table
502	 * this should be consistent w/ the enum,
503	 * busybox.h::bb_install_loc_t, or else... */
504	static const char usr_bin [] ALIGN1 = "/usr/bin";
505	static const char usr_sbin[] ALIGN1 = "/usr/sbin";
506	static const char *const install_dir[] = {
507		&usr_bin [8], /* "", equivalent to "/" for concat_path_file() */
508		&usr_bin [4], /* "/bin" */
509		&usr_sbin[4], /* "/sbin" */
510		usr_bin,
511		usr_sbin
512	};
513
514	int (*lf)(const char *, const char *) = link;
515	char *fpc;
516	int i;
517	int rc;
518
519	if (use_symbolic_links)
520		lf = symlink;
521
522	for (i = 0; applets[i].name != NULL; i++) {
523		fpc = concat_path_file(
524				install_dir[applets[i].install_loc],
525				applets[i].name);
526		rc = lf(busybox, fpc);
527		if (rc != 0 && errno != EEXIST) {
528			bb_perror_msg("%s", fpc);
529		}
530		free(fpc);
531	}
532}
533#else
534#define install_links(x,y) ((void)0)
535#endif /* FEATURE_INSTALLER */
536
537
538/* If we were called as "busybox..." */
539static int busybox_main(char **argv)
540{
541	if (!argv[1]) {
542		/* Called without arguments */
543		const struct bb_applet *a;
544		int col, output_width;
545 help:
546		output_width = 80;
547		if (ENABLE_FEATURE_AUTOWIDTH) {
548			/* Obtain the terminal width */
549			get_terminal_width_height(0, &output_width, NULL);
550		}
551		/* leading tab and room to wrap */
552		output_width -= sizeof("start-stop-daemon, ") + 8;
553
554		printf("%s multi-call binary\n", bb_banner); /* reuse const string... */
555		printf("Copyright (C) 1998-2006 ��Erik Andersen, Rob Landley, and others.\n"
556		       "Licensed under GPLv2. ��See source distribution for full notice.\n"
557		       "\n"
558		       "Usage: busybox [function] [arguments]...\n"
559		       "   or: [function] [arguments]...\n"
560		       "\n"
561		       "\tBusyBox is a multi-call binary that combines many common Unix\n"
562		       "\tutilities into a single executable.  Most people will create a\n"
563		       "\tlink to busybox for each function they wish to use and BusyBox\n"
564		       "\twill act like whatever it was invoked as!\n"
565		       "\nCurrently defined functions:\n");
566		col = 0;
567		a = applets;
568		while (a->name) {
569			if (col > output_width) {
570				puts(",");
571				col = 0;
572			}
573			col += printf("%s%s", (col ? ", " : "\t"), a->name);
574			a++;
575		}
576		puts("\n");
577		return 0;
578	}
579
580	if (ENABLE_FEATURE_INSTALLER && strcmp(argv[1], "--install") == 0) {
581		const char *busybox;
582		busybox = xmalloc_readlink(bb_busybox_exec_path);
583		if (!busybox)
584			busybox = bb_busybox_exec_path;
585		/* -s makes symlinks */
586		install_links(busybox, argv[2] && strcmp(argv[2], "-s") == 0);
587		return 0;
588	}
589
590	if (strcmp(argv[1], "--help") == 0) {
591		/* "busybox --help [<applet>]" */
592		if (!argv[2])
593			goto help;
594		/* convert to "<applet> --help" */
595		argv[0] = argv[2];
596		argv[2] = NULL;
597	} else {
598		/* "busybox <applet> arg1 arg2 ..." */
599		argv++;
600	}
601	/* we want "<argv[0]>: applet not found", not "busybox: ..." */
602	applet_name = argv[0];
603	run_applet_and_exit(argv[0], argv);
604	bb_error_msg_and_die("applet not found");
605}
606
607void run_current_applet_and_exit(char **argv)
608{
609	int argc = 1;
610
611	while (argv[argc])
612		argc++;
613
614	/* Reinit some shared global data */
615	optind = 1;
616	xfunc_error_retval = EXIT_FAILURE;
617
618	applet_name = current_applet->name;
619	if (argc == 2 && !strcmp(argv[1], "--help"))
620		bb_show_usage();
621	if (ENABLE_FEATURE_SUID)
622		check_suid(current_applet);
623	exit(current_applet->main(argc, argv));
624}
625
626void run_applet_and_exit(const char *name, char **argv)
627{
628	current_applet = find_applet_by_name(name);
629	if (current_applet)
630		run_current_applet_and_exit(argv);
631	if (!strncmp(name, "busybox", 7))
632		exit(busybox_main(argv));
633}
634
635
636#ifdef __GLIBC__
637/* Make it reside in R/W memory: */
638int *const bb_errno __attribute__ ((section (".data")));
639#endif
640
641int main(int argc, char **argv)
642{
643#ifdef __GLIBC__
644	(*(int **)&bb_errno) = __errno_location();
645#endif
646
647#if !BB_MMU
648	/* NOMMU re-exec trick sets high-order bit in first byte of name */
649	if (argv[0][0] & 0x80) {
650		re_execed = 1;
651		argv[0][0] &= 0x7f;
652	}
653#endif
654	applet_name = argv[0];
655	if (applet_name[0] == '-')
656		applet_name++;
657	applet_name = bb_basename(applet_name);
658
659	parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */
660
661	/* Set locale for everybody except 'init' */
662	if (ENABLE_LOCALE_SUPPORT && getpid() != 1)
663		setlocale(LC_ALL, "");
664
665	run_applet_and_exit(applet_name, argv);
666	bb_error_msg_and_die("applet not found");
667}
668