aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--applets/applets.c395
1 files changed, 220 insertions, 175 deletions
diff --git a/applets/applets.c b/applets/applets.c
index 4af569de3..f24679a1a 100644
--- a/applets/applets.c
+++ b/applets/applets.c
@@ -29,6 +29,7 @@
29#include <stdio.h> 29#include <stdio.h>
30#include <stdlib.h> 30#include <stdlib.h>
31#include <string.h> 31#include <string.h>
32#include <assert.h>
32#include "busybox.h" 33#include "busybox.h"
33 34
34#undef APPLET 35#undef APPLET
@@ -53,9 +54,7 @@ static void check_suid (struct BB_applet *app);
53#include "pwd_.h" 54#include "pwd_.h"
54#include "grp_.h" 55#include "grp_.h"
55 56
56static int parse_config_file (void); 57static void parse_config_file (void);
57
58static int config_ok;
59 58
60#define CONFIG_FILE "/etc/busybox.conf" 59#define CONFIG_FILE "/etc/busybox.conf"
61 60
@@ -127,7 +126,7 @@ run_applet_by_name (const char *name, int argc, char **argv)
127 126
128#ifdef CONFIG_FEATURE_SUID_CONFIG 127#ifdef CONFIG_FEATURE_SUID_CONFIG
129 if (recurse_level == 0) 128 if (recurse_level == 0)
130 config_ok = parse_config_file (); 129 parse_config_file ();
131#endif 130#endif
132 131
133 recurse_level++; 132 recurse_level++;
@@ -193,7 +192,7 @@ check_suid (struct BB_applet *applet)
193 uid_t rgid = getgid (); 192 uid_t rgid = getgid ();
194 193
195#ifdef CONFIG_FEATURE_SUID_CONFIG 194#ifdef CONFIG_FEATURE_SUID_CONFIG
196 if (config_ok) { 195 if (suid_config) {
197 struct BB_suid_config *sct; 196 struct BB_suid_config *sct;
198 197
199 for (sct = suid_config; sct; sct = sct->m_next) { 198 for (sct = suid_config; sct; sct = sct->m_next) {
@@ -253,191 +252,237 @@ check_suid (struct BB_applet *applet)
253 252
254#ifdef CONFIG_FEATURE_SUID_CONFIG 253#ifdef CONFIG_FEATURE_SUID_CONFIG
255 254
255/* This should probably be a libbb routine. In that case,
256 * I'd probably rename it to something like bb_trimmed_slice.
257 */
258static char *get_trimmed_slice(char *s, char *e)
259{
260 /* First, consider the value at e to be nul and back up until we
261 * reach a non-space char. Set the char after that (possibly at
262 * the original e) to nul. */
263 while (e-- > s) {
264 if (!isspace(*e)) {
265 break;
266 }
267 }
268 e[1] = 0;
269
270 /* Next, advance past all leading space and return a ptr to the
271 * first non-space char; possibly the terminating nul. */
272 return (char *) bb_skip_whitespace(s);
273}
274
256 275
257#define parse_error(x) { err=x; goto pe_label; } 276#define parse_error(x) { err=x; goto pe_label; }
258 277
278/* Don't depend on the tools to combine strings. */
279static const char config_file[] = CONFIG_FILE;
280
281/* There are 4 chars + 1 nul for each of user/group/other. */
282static const char mode_chars[] = "Ssx-\0Ssx-\0Ttx-";
283
284/* We don't supply a value for the nul, so an index adjustment is
285 * necessary below. Also, we use unsigned short here to save some
286 * space even though these are really mode_t values. */
287static const unsigned short mode_mask[] = {
288 /* SST sst xxx --- */
289 S_ISUID, S_ISUID|S_IXUSR, S_IXUSR, 0, /* user */
290 S_ISGID, S_ISGID|S_IXGRP, S_IXGRP, 0, /* group */
291 0, S_IXOTH, S_IXOTH, 0 /* other */
292};
259 293
260int 294static void parse_config_file(void)
261parse_config_file (void)
262{ 295{
263 struct stat st; 296 struct BB_suid_config *sct_head;
264 char *err = 0; 297 struct BB_suid_config *sct;
265 FILE *f = 0; 298 struct BB_applet *applet;
266 int lc = 0; 299 FILE *f;
267 300 char *err;
268 suid_config = 0; 301 char *s;
269 302 char *e;
270 /* is there a config file ? */ 303 int i, lc, section;
271 if (stat (CONFIG_FILE, &st) == 0) { 304 char buffer[256];
272 /* is it owned by root with no write perm. for group and others ? */ 305 struct stat st;
273 if (S_ISREG (st.st_mode) && (st.st_uid == 0) 306
274 && (!(st.st_mode & (S_IWGRP | S_IWOTH)))) { 307 assert(!suid_config); /* Should be set to NULL by bss init. */
275 /* that's ok .. then try to open it */ 308
276 f = fopen (CONFIG_FILE, "r"); 309 if ((stat(config_file, &st) != 0) /* No config file? */
277 310 || !S_ISREG(st.st_mode) /* Not a regular file? */
278 if (f) { 311 || (st.st_uid != 0) /* Not owned by root? */
279 char buffer[256]; 312 || (st.st_mode & (S_IWGRP | S_IWOTH)) /* Writable by non-root? */
280 int section = 0; 313 || !(f = fopen(config_file, "r")) /* Can not open? */
281 314 ) {
282 while (fgets (buffer, sizeof (buffer) - 1, f)) { 315 return;
283 char c = buffer[0]; 316 }
284 char *p; 317
285 318 sct_head = NULL;
286 lc++; 319 section = lc = 0;
287 320
288 p = strchr (buffer, '#'); 321 do {
289 if (p) 322 s = buffer;
290 *p = 0;
291 p = buffer + bb_strlen (buffer);
292 while ((p > buffer) && isspace (*--p))
293 *p = 0;
294
295 if (p == buffer)
296 continue;
297 323
298 if (c == '[') { 324 if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */
299 p = strchr (buffer, ']'); 325 if (ferror(f)) { /* Make sure it wasn't a read error. */
326 parse_error("reading");
327 }
328 fclose(f);
329 suid_config = sct_head; /* Success, so set the pointer. */
330 return;
331 }
300 332
301 if (!p || (p == (buffer + 1))) /* no matching ] or empty [] */ 333 lc++; /* Got a (partial) line. */
302 parse_error ("malformed section header"); 334
335 /* If a line is too long for our buffer, we consider it an error.
336 * The following test does mistreat one corner case though.
337 * If the final line of the file does not end with a newline and
338 * yet exactly fills the buffer, it will be treated as too long
339 * even though there isn't really a problem. But it isn't really
340 * worth adding code to deal with such an unlikely situation, and
341 * we do err on the side of caution. Besides, the line would be
342 * too long if it did end with a newline. */
343 if (!strchr(s, '\n') && !feof(f)) {
344 parse_error("line too long");
345 }
346
347 /* Trim leading and trailing whitespace, ignoring comments, and
348 * check if the resulting string is empty. */
349 if (!*(s = get_trimmed_slice(s, strchrnul(s, '#')))) {
350 continue;
351 }
303 352
304 *p = 0; 353 /* Check for a section header. */
354
355 if (*s == '[') {
356 /* Unlike the old code, we ignore leading and trailing
357 * whitespace for the section name. We also require that
358 * there are no stray characters after the closing bracket. */
359 if (!(e = strchr(s, ']')) /* Missing right bracket? */
360 || e[1] /* Trailing characters? */
361 || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */
362 ) {
363 parse_error("section header");
364 }
365 /* Right now we only have one section so just check it.
366 * If more sections are added in the future, please don't
367 * resort to cascading ifs with multiple strcasecmp calls.
368 * That kind of bloated code is all too common. A loop
369 * and a string table would be a better choice unless the
370 * number of sections is very small. */
371 if (strcasecmp(s, "SUID") == 0) {
372 section = 1;
373 continue;
374 }
375 section = -1; /* Unknown section so set to skip. */
376 continue;
377 }
305 378
306 if (strcasecmp (buffer + 1, "SUID") == 0) 379 /* Process sections. */
307 section = 1;
308 else
309 section = -1; /* unknown section - just skip */
310 } else if (section) {
311 switch (section) {
312 case 1:{ /* SUID */
313 int l;
314 struct BB_applet *applet;
315 380
316 p = strchr (buffer, '='); /* <key>[::space::]*=[::space::]*<value> */ 381 if (section == 1) { /* SUID */
382 /* Since we trimmed leading and trailing space above, we're
383 * now looking for strings of the form
384 * <key>[::space::]*=[::space::]*<value>
385 * where both key and value could contain inner whitespace. */
317 386
318 if (!p || (p == (buffer + 1))) /* no = or key is empty */ 387 /* First get the key (an applet name in our case). */
319 parse_error ("malformed keyword"); 388 if (!!(e = strchr(s, '='))) {
389 s = get_trimmed_slice(s, e);
390 }
391 if (!e || !*s) { /* Missing '=' or empty key. */
392 parse_error("keyword");
393 }
320 394
321 l = p - buffer; 395 /* Ok, we have an applet name. Process the rhs if this
322 while (isspace (buffer[--l])) { 396 * applet is currently built in and ignore it otherwise.
323 /* skip whitespace */ 397 * Note: This can hide config file bugs which only pop
398 * up when the busybox configuration is changed. */
399 if ((applet = find_applet_by_name(s))) {
400 /* Note: We currently don't check for duplicates!
401 * The last config line for each applet will be the
402 * one used since we insert at the head of the list.
403 * I suppose this could be considered a feature. */
404 sct = xmalloc(sizeof(struct BB_suid_config));
405 sct->m_applet = applet;
406 sct->m_mode = 0;
407 sct->m_next = sct_head;
408 sct_head = sct;
409
410 /* Get the specified mode. */
411
412 e = (char *) bb_skip_whitespace(e+1);
413
414 for (i=0 ; i < 3 ; i++) {
415 const char *q;
416 if (!*(q = strchrnul(mode_chars + 5*i, *e++))) {
417 parse_error("mode");
418 }
419 /* Adjust by -i to account for nul. */
420 sct->m_mode |= mode_mask[(q - mode_chars) - i];
324 } 421 }
325 422
326 buffer[l + 1] = 0; 423 /* Now get the the user/group info. */
327 424
328 if ((applet = find_applet_by_name (buffer))) { 425 s = (char *) bb_skip_whitespace(e);
329 struct BB_suid_config *sct = 426
330 xmalloc (sizeof (struct BB_suid_config)); 427 /* Note: We require whitespace between the mode and the
331 428 * user/group info. */
332 sct->m_applet = applet; 429 if ((s == e) || !(e = strchr(s, '.'))) {
333 sct->m_next = suid_config; 430 parse_error("<uid>.<gid>");
334 suid_config = sct; 431 }
335 432 *e++ = 0;
336 while (isspace (*++p)) { 433
337 /* skip whitespace */ 434 /* We can't use get_ug_id here since it would exit()
338 } 435 * if a uid or gid was not found. Oh well... */
339 436 {
340 sct->m_mode = 0; 437 char *e2;
341 438
342 switch (*p++) { 439 sct->m_uid = strtoul(s, &e2, 10);
343 case 'S': 440 if (*e2 || (s == e2)) {
344 sct->m_mode |= S_ISUID; 441 struct passwd *pwd;
345 break; 442 if (!(pwd = getpwnam(s))) {
346 case 's': 443 parse_error("user");
347 sct->m_mode |= S_ISUID; 444 }
348 /* no break */ 445 sct->m_uid = pwd->pw_uid;
349 case 'x': 446 }
350 sct->m_mode |= S_IXUSR; 447
351 break; 448 sct->m_gid = strtoul(e, &e2, 10);
352 case '-': 449 if (*e2 || (e == e2)) {
353 break; 450 struct group *grp;
354 default: 451 if (!(grp = getgrnam(e))) {
355 parse_error ("invalid user mode"); 452 parse_error("group");
356 } 453 }
357 454 sct->m_gid = grp->gr_gid;
358 switch (*p++) { 455 }
359 case 's':
360 sct->m_mode |= S_ISGID;
361 /* no break */
362 case 'x':
363 sct->m_mode |= S_IXGRP;
364 break;
365 case 'S':
366 break;
367 case '-':
368 break;
369 default:
370 parse_error ("invalid group mode");
371 }
372
373 switch (*p) {
374 case 't':
375 case 'x':
376 sct->m_mode |= S_IXOTH;
377 break;
378 case 'T':
379 case '-':
380 break;
381 default:
382 parse_error ("invalid other mode");
383 }
384
385 while (isspace (*++p)) {
386 /* skip whitespace */
387 }
388
389 if (isdigit (*p)) {
390 sct->m_uid = strtol (p, &p, 10);
391 if (*p++ != '.')
392 parse_error ("parsing <uid>.<gid>");
393 } else {
394 struct passwd *pwd;
395 char *p2 = strchr (p, '.');
396
397 if (!p2)
398 parse_error ("parsing <uid>.<gid>");
399
400 *p2 = 0;
401 pwd = getpwnam (p);
402
403 if (!pwd)
404 parse_error ("invalid user name");
405
406 sct->m_uid = pwd->pw_uid;
407 p = p2 + 1;
408 }
409 if (isdigit (*p))
410 sct->m_gid = strtol (p, &p, 10);
411 else {
412 struct group *grp = getgrnam (p);
413
414 if (!grp)
415 parse_error ("invalid group name");
416
417 sct->m_gid = grp->gr_gid;
418 }
419 } 456 }
420 break;
421 }
422 default: /* unknown - skip */
423 break;
424 } 457 }
425 } else 458 continue;
426 parse_error ("keyword not within section");
427 } 459 }
428 fclose (f);
429 return 1;
430 }
431 }
432 }
433 return 0; /* no config file or not readable (not an error) */
434 460
435pe_label: 461 /* Unknown sections are ignored. */
436 fprintf (stderr, "Parse error in %s, line %d: %s\n", CONFIG_FILE, lc, err);
437 462
438 if (f) 463 /* Encountering configuration lines prior to seeing a
439 fclose (f); 464 * section header is treated as an error. This is how
440 return 0; 465 * the old code worked, but it may not be desireable.
466 * We may want to simply ignore such lines in case they
467 * are used in some future version of busybox. */
468 if (!section) {
469 parse_error("keyword outside section");
470 }
471
472 } while (1);
473
474 pe_label:
475 fprintf(stderr, "Parse error in %s, line %d: %s\n",
476 config_file, lc, err);
477
478 fclose(f);
479 /* Release any allocated memory before returning. */
480 while (sct_head) {
481 sct = sct_head->m_next;
482 free(sct_head);
483 sct_head = sct;
484 }
485 return;
441} 486}
442 487
443#endif 488#endif
@@ -446,9 +491,9 @@ pe_label:
446 491
447/* END CODE */ 492/* END CODE */
448/* 493/*
449Local Variables: 494 Local Variables:
450c-file-style: "linux" 495 c-file-style: "linux"
451c-basic-offset: 4 496 c-basic-offset: 4
452tab-width: 4 497 tab-width: 4
453End: 498End:
454*/ 499*/