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