Contents of /trunk/mkinitrd-magellan/busybox/applets/applets.c
Parent Directory | Revision Log
Revision 532 -
(show annotations)
(download)
Sat Sep 1 22:45:15 2007 UTC (16 years, 8 months ago) by niro
File MIME type: text/plain
File size: 12993 byte(s)
Sat Sep 1 22:45:15 2007 UTC (16 years, 8 months ago) by niro
File MIME type: text/plain
File size: 12993 byte(s)
-import if magellan mkinitrd; it is a fork of redhats mkinitrd-5.0.8 with all magellan patches and features; deprecates magellan-src/mkinitrd
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 "busybox.h" |
16 | #include <assert.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 | #endif |
27 | |
28 | #if ENABLE_SHOW_USAGE && !ENABLE_FEATURE_COMPRESS_USAGE |
29 | static const char usage_messages[] = |
30 | #define MAKE_USAGE |
31 | #include "usage.h" |
32 | #include "applets.h" |
33 | ; |
34 | #undef MAKE_USAGE |
35 | #else |
36 | #define usage_messages 0 |
37 | #endif /* ENABLE_SHOW_USAGE */ |
38 | |
39 | #undef APPLET |
40 | #undef APPLET_NOUSAGE |
41 | #undef PROTOTYPES |
42 | #include "applets.h" |
43 | |
44 | static struct BB_applet *applet_using; |
45 | |
46 | /* The -1 arises because of the {0,NULL,0,-1} entry above. */ |
47 | const unsigned short NUM_APPLETS = (sizeof (applets) / sizeof (struct BB_applet) - 1); |
48 | |
49 | |
50 | #ifdef CONFIG_FEATURE_SUID_CONFIG |
51 | |
52 | #include <ctype.h> |
53 | |
54 | #define CONFIG_FILE "/etc/busybox.conf" |
55 | |
56 | /* applets [] is const, so we have to define this "override" structure */ |
57 | static struct BB_suid_config |
58 | { |
59 | struct BB_applet *m_applet; |
60 | |
61 | uid_t m_uid; |
62 | gid_t m_gid; |
63 | mode_t m_mode; |
64 | |
65 | struct BB_suid_config *m_next; |
66 | } *suid_config; |
67 | |
68 | static int suid_cfg_readable; |
69 | |
70 | /* check if u is member of group g */ |
71 | static int ingroup(uid_t u, gid_t g) |
72 | { |
73 | struct group *grp = getgrgid(g); |
74 | |
75 | if (grp) { |
76 | char **mem; |
77 | |
78 | for (mem = grp->gr_mem; *mem; mem++) { |
79 | struct passwd *pwd = getpwnam(*mem); |
80 | |
81 | if (pwd && (pwd->pw_uid == u)) |
82 | return 1; |
83 | } |
84 | } |
85 | return 0; |
86 | } |
87 | |
88 | /* This should probably be a libbb routine. In that case, |
89 | * I'd probably rename it to something like bb_trimmed_slice. |
90 | */ |
91 | static char *get_trimmed_slice(char *s, char *e) |
92 | { |
93 | /* First, consider the value at e to be nul and back up until we |
94 | * reach a non-space char. Set the char after that (possibly at |
95 | * the original e) to nul. */ |
96 | while (e-- > s) { |
97 | if (!isspace(*e)) { |
98 | break; |
99 | } |
100 | } |
101 | e[1] = 0; |
102 | |
103 | /* Next, advance past all leading space and return a ptr to the |
104 | * first non-space char; possibly the terminating nul. */ |
105 | return skip_whitespace(s); |
106 | } |
107 | |
108 | |
109 | #define parse_error(x) { err=x; goto pe_label; } |
110 | |
111 | /* Don't depend on the tools to combine strings. */ |
112 | static const char config_file[] = CONFIG_FILE; |
113 | |
114 | /* There are 4 chars + 1 nul for each of user/group/other. */ |
115 | static const char mode_chars[] = "Ssx-\0Ssx-\0Ttx-"; |
116 | |
117 | /* We don't supply a value for the nul, so an index adjustment is |
118 | * necessary below. Also, we use unsigned short here to save some |
119 | * space even though these are really mode_t values. */ |
120 | static const unsigned short mode_mask[] = { |
121 | /* SST sst xxx --- */ |
122 | S_ISUID, S_ISUID|S_IXUSR, S_IXUSR, 0, /* user */ |
123 | S_ISGID, S_ISGID|S_IXGRP, S_IXGRP, 0, /* group */ |
124 | 0, S_IXOTH, S_IXOTH, 0 /* other */ |
125 | }; |
126 | |
127 | static void parse_config_file(void) |
128 | { |
129 | struct BB_suid_config *sct_head; |
130 | struct BB_suid_config *sct; |
131 | struct BB_applet *applet; |
132 | FILE *f; |
133 | char *err; |
134 | char *s; |
135 | char *e; |
136 | int i, lc, section; |
137 | char buffer[256]; |
138 | struct stat st; |
139 | |
140 | assert(!suid_config); /* Should be set to NULL by bss init. */ |
141 | |
142 | if ((stat(config_file, &st) != 0) /* No config file? */ |
143 | || !S_ISREG(st.st_mode) /* Not a regular file? */ |
144 | || (st.st_uid != 0) /* Not owned by root? */ |
145 | || (st.st_mode & (S_IWGRP | S_IWOTH)) /* Writable by non-root? */ |
146 | || !(f = fopen(config_file, "r")) /* Cannot open? */ |
147 | ) { |
148 | return; |
149 | } |
150 | |
151 | suid_cfg_readable = 1; |
152 | sct_head = NULL; |
153 | section = lc = 0; |
154 | |
155 | do { |
156 | s = buffer; |
157 | |
158 | if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */ |
159 | if (ferror(f)) { /* Make sure it wasn't a read error. */ |
160 | parse_error("reading"); |
161 | } |
162 | fclose(f); |
163 | suid_config = sct_head; /* Success, so set the pointer. */ |
164 | return; |
165 | } |
166 | |
167 | lc++; /* Got a (partial) line. */ |
168 | |
169 | /* If a line is too long for our buffer, we consider it an error. |
170 | * The following test does mistreat one corner case though. |
171 | * If the final line of the file does not end with a newline and |
172 | * yet exactly fills the buffer, it will be treated as too long |
173 | * even though there isn't really a problem. But it isn't really |
174 | * worth adding code to deal with such an unlikely situation, and |
175 | * we do err on the side of caution. Besides, the line would be |
176 | * too long if it did end with a newline. */ |
177 | if (!strchr(s, '\n') && !feof(f)) { |
178 | parse_error("line too long"); |
179 | } |
180 | |
181 | /* Trim leading and trailing whitespace, ignoring comments, and |
182 | * check if the resulting string is empty. */ |
183 | if (!*(s = get_trimmed_slice(s, strchrnul(s, '#')))) { |
184 | continue; |
185 | } |
186 | |
187 | /* Check for a section header. */ |
188 | |
189 | if (*s == '[') { |
190 | /* Unlike the old code, we ignore leading and trailing |
191 | * whitespace for the section name. We also require that |
192 | * there are no stray characters after the closing bracket. */ |
193 | if (!(e = strchr(s, ']')) /* Missing right bracket? */ |
194 | || e[1] /* Trailing characters? */ |
195 | || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */ |
196 | ) { |
197 | parse_error("section header"); |
198 | } |
199 | /* Right now we only have one section so just check it. |
200 | * If more sections are added in the future, please don't |
201 | * resort to cascading ifs with multiple strcasecmp calls. |
202 | * That kind of bloated code is all too common. A loop |
203 | * and a string table would be a better choice unless the |
204 | * number of sections is very small. */ |
205 | if (strcasecmp(s, "SUID") == 0) { |
206 | section = 1; |
207 | continue; |
208 | } |
209 | section = -1; /* Unknown section so set to skip. */ |
210 | continue; |
211 | } |
212 | |
213 | /* Process sections. */ |
214 | |
215 | if (section == 1) { /* SUID */ |
216 | /* Since we trimmed leading and trailing space above, we're |
217 | * now looking for strings of the form |
218 | * <key>[::space::]*=[::space::]*<value> |
219 | * where both key and value could contain inner whitespace. */ |
220 | |
221 | /* First get the key (an applet name in our case). */ |
222 | if (!!(e = strchr(s, '='))) { |
223 | s = get_trimmed_slice(s, e); |
224 | } |
225 | if (!e || !*s) { /* Missing '=' or empty key. */ |
226 | parse_error("keyword"); |
227 | } |
228 | |
229 | /* Ok, we have an applet name. Process the rhs if this |
230 | * applet is currently built in and ignore it otherwise. |
231 | * Note: This can hide config file bugs which only pop |
232 | * up when the busybox configuration is changed. */ |
233 | if ((applet = find_applet_by_name(s))) { |
234 | /* Note: We currently don't check for duplicates! |
235 | * The last config line for each applet will be the |
236 | * one used since we insert at the head of the list. |
237 | * I suppose this could be considered a feature. */ |
238 | sct = xmalloc(sizeof(struct BB_suid_config)); |
239 | sct->m_applet = applet; |
240 | sct->m_mode = 0; |
241 | sct->m_next = sct_head; |
242 | sct_head = sct; |
243 | |
244 | /* Get the specified mode. */ |
245 | |
246 | e = skip_whitespace(e+1); |
247 | |
248 | for (i=0 ; i < 3 ; i++) { |
249 | const char *q; |
250 | if (!*(q = strchrnul(mode_chars + 5*i, *e++))) { |
251 | parse_error("mode"); |
252 | } |
253 | /* Adjust by -i to account for nul. */ |
254 | sct->m_mode |= mode_mask[(q - mode_chars) - i]; |
255 | } |
256 | |
257 | /* Now get the the user/group info. */ |
258 | |
259 | s = skip_whitespace(e); |
260 | |
261 | /* Note: We require whitespace between the mode and the |
262 | * user/group info. */ |
263 | if ((s == e) || !(e = strchr(s, '.'))) { |
264 | parse_error("<uid>.<gid>"); |
265 | } |
266 | *e++ = 0; |
267 | |
268 | /* We can't use get_ug_id here since it would exit() |
269 | * if a uid or gid was not found. Oh well... */ |
270 | { |
271 | char *e2; |
272 | |
273 | sct->m_uid = strtoul(s, &e2, 10); |
274 | if (*e2 || (s == e2)) { |
275 | struct passwd *pwd = getpwnam(s); |
276 | if (!pwd) { |
277 | parse_error("user"); |
278 | } |
279 | sct->m_uid = pwd->pw_uid; |
280 | } |
281 | |
282 | sct->m_gid = strtoul(e, &e2, 10); |
283 | if (*e2 || (e == e2)) { |
284 | struct group *grp; |
285 | if (!(grp = getgrnam(e))) { |
286 | parse_error("group"); |
287 | } |
288 | sct->m_gid = grp->gr_gid; |
289 | } |
290 | } |
291 | } |
292 | continue; |
293 | } |
294 | |
295 | /* Unknown sections are ignored. */ |
296 | |
297 | /* Encountering configuration lines prior to seeing a |
298 | * section header is treated as an error. This is how |
299 | * the old code worked, but it may not be desirable. |
300 | * We may want to simply ignore such lines in case they |
301 | * are used in some future version of busybox. */ |
302 | if (!section) { |
303 | parse_error("keyword outside section"); |
304 | } |
305 | |
306 | } while (1); |
307 | |
308 | pe_label: |
309 | fprintf(stderr, "Parse error in %s, line %d: %s\n", |
310 | config_file, lc, err); |
311 | |
312 | fclose(f); |
313 | /* Release any allocated memory before returning. */ |
314 | while (sct_head) { |
315 | sct = sct_head->m_next; |
316 | free(sct_head); |
317 | sct_head = sct; |
318 | } |
319 | return; |
320 | } |
321 | |
322 | #else |
323 | #define parse_config_file() ((void)0) |
324 | #endif /* CONFIG_FEATURE_SUID_CONFIG */ |
325 | |
326 | #ifdef CONFIG_FEATURE_SUID |
327 | static void check_suid(struct BB_applet *applet) |
328 | { |
329 | uid_t ruid = getuid(); /* real [ug]id */ |
330 | uid_t rgid = getgid(); |
331 | |
332 | #ifdef CONFIG_FEATURE_SUID_CONFIG |
333 | if (suid_cfg_readable) { |
334 | struct BB_suid_config *sct; |
335 | |
336 | for (sct = suid_config; sct; sct = sct->m_next) { |
337 | if (sct->m_applet == applet) |
338 | break; |
339 | } |
340 | if (sct) { |
341 | mode_t m = sct->m_mode; |
342 | |
343 | if (sct->m_uid == ruid) |
344 | /* same uid */ |
345 | m >>= 6; |
346 | else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid)) |
347 | /* same group / in group */ |
348 | m >>= 3; |
349 | |
350 | if (!(m & S_IXOTH)) /* is x bit not set ? */ |
351 | bb_error_msg_and_die("you have no permission to run this applet!"); |
352 | |
353 | if (sct->m_gid != 0) { |
354 | /* _both_ have to be set for sgid */ |
355 | if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { |
356 | xsetgid(sct->m_gid); |
357 | } else xsetgid(rgid); /* no sgid -> drop */ |
358 | } |
359 | if (sct->m_uid != 0) { |
360 | if (sct->m_mode & S_ISUID) xsetuid(sct->m_uid); |
361 | else xsetuid(ruid); /* no suid -> drop */ |
362 | } |
363 | } else { |
364 | /* default: drop all privileges */ |
365 | xsetgid(rgid); |
366 | xsetuid(ruid); |
367 | } |
368 | return; |
369 | } else { |
370 | #ifndef CONFIG_FEATURE_SUID_CONFIG_QUIET |
371 | static int onetime = 0; |
372 | |
373 | if (!onetime) { |
374 | onetime = 1; |
375 | fprintf(stderr, "Using fallback suid method\n"); |
376 | } |
377 | #endif |
378 | } |
379 | #endif |
380 | |
381 | if (applet->need_suid == _BB_SUID_ALWAYS) { |
382 | if (geteuid()) bb_error_msg_and_die("applet requires root privileges!"); |
383 | } else if (applet->need_suid == _BB_SUID_NEVER) { |
384 | xsetgid(rgid); /* drop all privileges */ |
385 | xsetuid(ruid); |
386 | } |
387 | } |
388 | #else |
389 | #define check_suid(x) |
390 | #endif /* CONFIG_FEATURE_SUID */ |
391 | |
392 | |
393 | |
394 | #ifdef CONFIG_FEATURE_COMPRESS_USAGE |
395 | |
396 | #include "usage_compressed.h" |
397 | #include "unarchive.h" |
398 | |
399 | static const char *unpack_usage_messages(void) |
400 | { |
401 | int input[2], output[2], pid; |
402 | char *buf; |
403 | |
404 | if(pipe(input) < 0 || pipe(output) < 0) |
405 | exit(1); |
406 | |
407 | pid = fork(); |
408 | switch (pid) { |
409 | case -1: /* error */ |
410 | exit(1); |
411 | case 0: /* child */ |
412 | close(input[1]); |
413 | close(output[0]); |
414 | uncompressStream(input[0], output[1]); |
415 | exit(0); |
416 | } |
417 | /* parent */ |
418 | |
419 | close(input[0]); |
420 | close(output[1]); |
421 | pid = fork(); |
422 | switch (pid) { |
423 | case -1: /* error */ |
424 | exit(1); |
425 | case 0: /* child */ |
426 | full_write(input[1], packed_usage, sizeof(packed_usage)); |
427 | exit(0); |
428 | } |
429 | /* parent */ |
430 | close(input[1]); |
431 | |
432 | buf = xmalloc(SIZEOF_usage_messages); |
433 | full_read(output[0], buf, SIZEOF_usage_messages); |
434 | return buf; |
435 | } |
436 | |
437 | #else |
438 | #define unpack_usage_messages() usage_messages |
439 | #endif /* ENABLE_FEATURE_COMPRESS_USAGE */ |
440 | |
441 | void bb_show_usage(void) |
442 | { |
443 | if (ENABLE_SHOW_USAGE) { |
444 | const char *format_string; |
445 | const char *usage_string = unpack_usage_messages(); |
446 | int i; |
447 | |
448 | for (i = applet_using - applets; i > 0;) |
449 | if (!*usage_string++) --i; |
450 | |
451 | format_string = "%s\n\nUsage: %s %s\n\n"; |
452 | if (*usage_string == '\b') |
453 | format_string = "%s\n\nNo help available.\n\n"; |
454 | fprintf(stderr, format_string, bb_msg_full_version, |
455 | applet_using->name, usage_string); |
456 | } |
457 | |
458 | exit(xfunc_error_retval); |
459 | } |
460 | |
461 | static int applet_name_compare(const void *name, const void *vapplet) |
462 | { |
463 | const struct BB_applet *applet = vapplet; |
464 | |
465 | return strcmp(name, applet->name); |
466 | } |
467 | |
468 | struct BB_applet *find_applet_by_name(const char *name) |
469 | { |
470 | return bsearch(name, applets, NUM_APPLETS, sizeof(struct BB_applet), |
471 | applet_name_compare); |
472 | } |
473 | |
474 | void run_applet_by_name(const char *name, int argc, char **argv) |
475 | { |
476 | if (ENABLE_FEATURE_SUID_CONFIG) |
477 | parse_config_file(); |
478 | |
479 | if (!strncmp(name, "busybox", 7)) |
480 | exit(busybox_main(argc, argv)); |
481 | /* Do a binary search to find the applet entry given the name. */ |
482 | applet_using = find_applet_by_name(name); |
483 | if (applet_using) { |
484 | applet_name = applet_using->name; |
485 | if (argc == 2 && !strcmp(argv[1], "--help")) |
486 | bb_show_usage(); |
487 | if (ENABLE_FEATURE_SUID) |
488 | check_suid(applet_using); |
489 | exit(applet_using->main(argc, argv)); |
490 | } |
491 | } |