aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManuel Novoa III <mjn3@codepoet.org>2004-02-01 10:03:05 +0000
committerManuel Novoa III <mjn3@codepoet.org>2004-02-01 10:03:05 +0000
commit31b98dd09748535a5004e948bb560c320d179a66 (patch)
tree1eb940e9065e784635878cb7abd2a17f43c2e3e2
parent083862228a3da88c6890a1a4beba5355367dfd2e (diff)
downloadbusybox-w32-31b98dd09748535a5004e948bb560c320d179a66.tar.gz
busybox-w32-31b98dd09748535a5004e948bb560c320d179a66.tar.bz2
busybox-w32-31b98dd09748535a5004e948bb560c320d179a66.zip
Rewrite parse_config_file(). Among the old version's problems:
No checking for lines that were too long. No checking that fgets returning NULL was actually due to EOF. Various whitespace handling inconsistencies. Bloat (switches and multiple identical function calls). Failure to check for trailing characters in some cases. Dynamicly allocated memory was not free()d on error. Given that this controls suid/sgid behavior, the sloppy coding was really inexcusable. :-(
-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*/