Tizen 2.1 release
[platform/core/uifw/e17.git] / src / bin / e_sys_main.c
1 #include "config.h"
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <unistd.h>
6 #include <string.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <pwd.h>
10 #include <grp.h>
11 #include <fnmatch.h>
12 #include <ctype.h>
13 #ifdef HAVE_ALLOCA_H
14 #include <alloca.h>
15 #endif
16 #include <Eina.h>
17
18 /* local subsystem functions */
19 #ifdef HAVE_EEZE_MOUNT
20 static Eina_Bool mountopts_check(const char *opts);
21 static Eina_Bool mount_args_check(int argc, char **argv, const char *action);
22 #endif
23 static int auth_action_ok(char *a,
24                           gid_t gid,
25                           gid_t *gl,
26                           int gn,
27                           gid_t egid);
28 static int auth_etc_enlightenment_sysactions(char *a,
29                                              char *u,
30                                              char **g);
31 static void auth_etc_enlightenment_sysactions_perm(char *path);
32 static char *get_word(char *s,
33                       char *d);
34
35 /* local subsystem globals */
36 static Eina_Hash *actions = NULL;
37 static uid_t uid = -1;
38
39 /* externally accessible functions */
40 int
41 main(int argc,
42      char **argv)
43 {
44    int i, gn;
45    int test = 0;
46    char *action = NULL, *cmd;
47 #ifdef HAVE_EEZE_MOUNT
48    Eina_Bool mnt = EINA_FALSE;
49    const char *act;
50 #endif
51    gid_t gid, gl[65536], egid;
52
53    for (i = 1; i < argc; i++)
54      {
55         if ((!strcmp(argv[i], "-h")) ||
56             (!strcmp(argv[i], "-help")) ||
57             (!strcmp(argv[i], "--help")))
58           {
59              printf(
60                "This is an internal tool for Enlightenment.\n"
61                "do not use it.\n"
62                );
63              exit(0);
64           }
65      }
66    if (argc >= 3)
67      {
68         if ((argc == 3) && (!strcmp(argv[1], "-t")))
69           {
70              test = 1;
71              action = argv[2];
72           }
73 #ifdef HAVE_EEZE_MOUNT
74         else
75           {
76              const char *s;
77
78              s = strrchr(argv[1], '/');
79              if ((!s) || (!s[1])) exit(1); /* eeze always uses complete path */
80              s++;
81              if (strcmp(s, "mount") && strcmp(s, "umount") && strcmp(s, "eject")) exit(1);
82              mnt = EINA_TRUE;
83              act = s;
84              action = argv[1];
85           }
86 #endif
87      }
88    else if (argc == 2)
89      {
90         action = argv[1];
91      }
92    else
93      {
94         exit(1);
95      }
96    if (!action) exit(1);
97
98    uid = getuid();
99    gid = getgid();
100    egid = getegid();
101    gn = getgroups(65536, gl);
102    if (gn < 0)
103      {
104         printf("ERROR: MEMBER OF MORE THAN 65536 GROUPS\n");
105         exit(3);
106      }
107    if (setuid(0) != 0)
108      {
109         printf("ERROR: UNABLE TO ASSUME ROOT PRIVILEGES\n");
110         exit(5);
111      }
112    if (setgid(0) != 0)
113      {
114         printf("ERROR: UNABLE TO ASSUME ROOT GROUP PRIVILEGES\n");
115         exit(7);
116      }
117
118    eina_init();
119
120    if (!auth_action_ok(action, gid, gl, gn, egid))
121      {
122         printf("ERROR: ACTION NOT ALLOWED: %s\n", action);
123         exit(10);
124      }
125    /* we can add more levels of auth here */
126
127    /* when mounting, this will match the exact path to the exe,
128     * as required in sysactions.conf
129     * this is intentionally pedantic for security
130     */
131    cmd = eina_hash_find(actions, action);
132    if (!cmd)
133      {
134         printf("ERROR: UNDEFINED ACTION: %s\n", action);
135         exit(20);
136      }
137    if ((!test)
138 #ifdef HAVE_EEZE_MOUNT
139     && (!mnt)
140 #endif
141       )
142      return system(cmd);
143 #ifdef HAVE_EEZE_MOUNT
144    if (mnt)
145      {
146         int ret = 0;
147         const char *mp = NULL;
148         Eina_Strbuf *buf = NULL;
149
150         if (!mount_args_check(argc, argv, act)) exit(40);
151         /* all options are deemed safe at this point, so away we go! */
152         if (!strcmp(act, "mount"))
153           {
154              struct stat s;
155
156              mp = argv[5];
157              if (stat("/media", &s))
158                {
159                   mode_t um;
160
161                   um = umask(0);
162                   if (mkdir("/media", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH))
163                     {
164                        printf("ERROR: COULD NOT CREATE DIRECTORY /media\n");
165                        exit(40);
166                     }
167                   umask(um);
168                }
169              else if (!S_ISDIR(s.st_mode))
170                {
171                   printf("ERROR: NOT A DIRECTORY: /media\n");
172                   exit(40);
173                }
174
175              if (stat(mp, &s))
176                {
177                   mode_t um;
178
179                   um = umask(0);
180                   if (mkdir(mp, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH))
181                     {
182                        printf("ERROR: COULD NOT CREATE DIRECTORY %s\n", mp);
183                        exit(40);
184                     }
185                   umask(um);
186                }
187              else if (!S_ISDIR(s.st_mode))
188                {
189                   printf("ERROR: NOT A DIRECTORY: %s\n", mp);
190                   exit(40);
191                }
192           }
193         buf = eina_strbuf_new();
194         if (!buf) exit(30);
195         for (i = 1; i < argc; i++)
196           eina_strbuf_append_printf(buf, "%s ", argv[i]);
197         ret = system(eina_strbuf_string_get(buf));
198         if ((!strcmp(act, "umount")) && (!ret))
199           {
200              Eina_Iterator *it;
201              char path[PATH_MAX];
202              const char *s;
203              struct stat st;
204              Eina_Bool rm = EINA_TRUE;
205
206              mp = strrchr(argv[2], '/');
207              if (!mp) return ret;
208              snprintf(path, sizeof(path), "/media%s", mp);
209              if (stat(path, &st)) return ret;
210              if (!S_ISDIR(st.st_mode)) return ret;
211              it = eina_file_ls(path);
212              EINA_ITERATOR_FOREACH(it, s)
213                {
214                   /* don't delete any directories with files in them */
215                   rm = EINA_FALSE;
216                   eina_stringshare_del(s);
217                }
218              if (rm)
219                {
220                   if (rmdir(path))
221                     printf("ERROR: COULD NOT UNLINK MOUNT POINT %s\n", path);
222                }
223           }
224         return ret;
225      }
226 #endif
227    eina_shutdown();
228
229    return 0;
230 }
231
232 /* local subsystem functions */
233 #ifdef HAVE_EEZE_MOUNT
234 static Eina_Bool
235 mountopts_check(const char *opts)
236 {
237    char buf[64];
238    const char *p;
239    char *end;
240    unsigned long muid;
241    Eina_Bool nosuid, nodev, noexec, nuid;
242
243    nosuid = nodev = noexec = nuid = EINA_FALSE;
244
245    /* these are the only possible options which can be present here; check them strictly */
246    if (eina_strlcpy(buf, opts, sizeof(buf)) >= sizeof(buf)) return EINA_FALSE;
247    for (p = buf; p && p[1]; p = strchr(p + 1, ','))
248      {
249         if (p[0] == ',') p++;
250 #define CMP(OPT) \
251   if (!strncmp(p, OPT, sizeof(OPT) - 1))
252
253         CMP("nosuid,")
254           {
255              nosuid = EINA_TRUE;
256              continue;
257           }
258         CMP("nodev,")
259           {
260              nodev = EINA_TRUE;
261              continue;
262           }
263         CMP("noexec,")
264           {
265              noexec = EINA_TRUE;
266              continue;
267           }
268         CMP("utf8,") continue;
269         CMP("utf8=0,") continue;
270         CMP("utf8=1,") continue;
271         CMP("iocharset=utf8,") continue;
272         CMP("uid=")
273           {
274              p += 4;
275              errno = 0;
276              muid = strtoul(p, &end, 10);
277              if (muid == ULONG_MAX) return EINA_FALSE;
278              if (errno) return EINA_FALSE;
279              if (end[0] != ',') return EINA_FALSE;
280              if (muid != uid) return EINA_FALSE;
281              nuid = EINA_TRUE;
282              continue;
283           }
284         return EINA_FALSE;
285      }
286    if ((!nosuid) || (!nodev) || (!noexec) || (!nuid)) return EINA_FALSE;
287    return EINA_TRUE;
288 }
289
290 static Eina_Bool
291 check_uuid(const char *uuid)
292 {
293    const char *p;
294
295    for (p = uuid; p[0]; p++)
296      if ((!isalnum(*p)) && (*p != '-')) return EINA_FALSE;
297    return EINA_TRUE;
298 }
299
300 static Eina_Bool
301 mount_args_check(int argc, char **argv, const char *action)
302 {
303    Eina_Bool opts = EINA_FALSE;
304    struct stat st;
305    const char *node;
306    char buf[PATH_MAX];
307
308    if (!strcmp(action, "mount"))
309      {
310         /* will ALWAYS be:
311          /path/to/mount -o nosuid,uid=XYZ,[utf8,] UUID=XXXX-XXXX[-XXXX-XXXX] /media/$devnode
312          */
313         if (argc != 6) return EINA_FALSE;
314         if (argv[2][0] == '-')
315           {
316              /* disallow any -options other than -o */
317              if (strcmp(argv[2], "-o")) return EINA_FALSE;
318              opts = mountopts_check(argv[3]);
319           }
320         if (!opts) return EINA_FALSE;
321         if (!strncmp(argv[4], "UUID=", sizeof("UUID=") - 1))
322           {
323              if (!check_uuid(argv[4] + 5)) return EINA_FALSE;
324           }
325         else
326           {
327              if (strncmp(argv[4], "/dev/", 5)) return EINA_FALSE;
328              if (stat(argv[4], &st)) return EINA_FALSE;
329           }
330         
331         node = strrchr(argv[5], '/');
332         if (!node) return EINA_FALSE;
333         if (!node[1]) return EINA_FALSE;
334         if (node - argv[5] != 6) return EINA_FALSE;
335         snprintf(buf, sizeof(buf), "/dev%s", node);
336         if (stat(buf, &st)) return EINA_FALSE;
337      }
338    else if (!strcmp(action, "umount"))
339      {
340         /* will ALWAYS be:
341          /path/to/umount /dev/$devnode
342          */
343         if (argc != 3) return EINA_FALSE;
344         if (strncmp(argv[2], "/dev/", 5)) return EINA_FALSE;
345         if (stat(argv[2], &st)) return EINA_FALSE;
346         node = strrchr(argv[2], '/');
347         if (!node) return EINA_FALSE;
348         if (!node[1]) return EINA_FALSE;
349         if (node - argv[2] != 4) return EINA_FALSE;
350         snprintf(buf, sizeof(buf), "/media%s", node);
351         if (stat(buf, &st)) return EINA_FALSE;
352         if (!S_ISDIR(st.st_mode)) return EINA_FALSE;
353      }
354    else if (!strcmp(action, "eject"))
355      {
356         /* will ALWAYS be:
357          /path/to/eject /dev/$devnode
358          */
359         if (argc != 3) return EINA_FALSE;
360         if (strncmp(argv[2], "/dev/", 5)) return EINA_FALSE;
361         if (stat(argv[2], &st)) return EINA_FALSE;
362         node = strrchr(argv[2], '/');
363         if (!node) return EINA_FALSE;
364         if (!node[1]) return EINA_FALSE;
365         if (node - argv[2] != 4) return EINA_FALSE;
366      }
367    else return EINA_FALSE;
368    return EINA_TRUE;
369 }
370 #endif
371
372 static int
373 auth_action_ok(char *a,
374                gid_t gid,
375                gid_t *gl,
376                int gn,
377                gid_t egid)
378 {
379    struct passwd *pw;
380    struct group *gp;
381    char *usr = NULL, **grp, *g;
382    int ret, i, j;
383
384    pw = getpwuid(uid);
385    if (!pw) return 0;
386    usr = pw->pw_name;
387    if (!usr) return 0;
388    grp = alloca(sizeof(char *) * (gn + 1 + 1));
389    j = 0;
390    gp = getgrgid(gid);
391    if (gp)
392      {
393         grp[j] = gp->gr_name;
394         j++;
395      }
396    for (i = 0; i < gn; i++)
397      {
398         if (gl[i] != egid)
399           {
400              gp = getgrgid(gl[i]);
401              if (gp)
402                {
403                   g = alloca(strlen(gp->gr_name) + 1);
404                   strcpy(g, gp->gr_name);
405                   grp[j] = g;
406                   j++;
407                }
408           }
409      }
410    grp[j] = NULL;
411    /* first stage - check:
412     * PREFIX/etc/enlightenment/sysactions.conf
413     */
414    ret = auth_etc_enlightenment_sysactions(a, usr, grp);
415    if (ret == 1) return 1;
416    else if (ret == -1)
417      return 0;
418    /* the DEFAULT - allow */
419    return 1;
420 }
421
422 static int
423 auth_etc_enlightenment_sysactions(char *a,
424                                   char *u,
425                                   char **g)
426 {
427    FILE *f;
428    char file[4096], buf[4096], id[4096], ugname[4096], perm[4096], act[4096];
429    char *p, *pp, *s, **gp;
430    int ok = 0;
431    size_t len, line = 0;
432    int allow = 0;
433    int deny = 0;
434
435    snprintf(file, sizeof(file), "/etc/enlightenment/sysactions.conf");
436    f = fopen(file, "r");
437    if (!f)
438      {
439         snprintf(file, sizeof(file), PACKAGE_SYSCONF_DIR "/enlightenment/sysactions.conf");
440         f = fopen(file, "r");
441         if (!f) return 0;
442      }
443
444    auth_etc_enlightenment_sysactions_perm(file);
445
446    while (fgets(buf, sizeof(buf), f))
447      {
448         line++;
449         len = strlen(buf);
450         if (len < 1) continue;
451         if (buf[len - 1] == '\n') buf[len - 1] = 0;
452         /* format:
453          *
454          * # comment
455          * user:  username  [allow:|deny:] halt reboot ...
456          * group: groupname [allow:|deny:] suspend ...
457          */
458         if (buf[0] == '#') continue;
459         p = buf;
460         p = get_word(p, id);
461         p = get_word(p, ugname);
462         pp = p;
463         p = get_word(p, perm);
464         allow = 0;
465         deny = 0;
466         if (!strcmp(id, "user:"))
467           {
468              if (!fnmatch(ugname, u, 0))
469                {
470                   if (!strcmp(perm, "allow:")) allow = 1;
471                   else if (!strcmp(perm, "deny:"))
472                     deny = 1;
473                   else
474                     goto malformed;
475                }
476              else
477                continue;
478           }
479         else if (!strcmp(id, "group:"))
480           {
481              Eina_Bool matched = EINA_FALSE;
482
483              for (gp = g; *gp; gp++)
484                {
485                   if (!fnmatch(ugname, *gp, 0))
486                     {
487                        matched = EINA_TRUE;
488                        if (!strcmp(perm, "allow:")) allow = 1;
489                        else if (!strcmp(perm, "deny:"))
490                          deny = 1;
491                        else
492                          goto malformed;
493                     }
494                }
495              if (!matched) continue;
496           }
497         else if (!strcmp(id, "action:"))
498           {
499              while ((*pp) && (isspace(*pp))) pp++;
500              s = eina_hash_find(actions, ugname);
501              if (s) eina_hash_del(actions, ugname, s);
502              if (!actions) actions = eina_hash_string_superfast_new(free);
503              eina_hash_add(actions, ugname, strdup(pp));
504              continue;
505           }
506         else if (id[0] == 0)
507           continue;
508         else
509           goto malformed;
510
511         for (;; )
512           {
513              p = get_word(p, act);
514              if (act[0] == 0) break;
515              if (!fnmatch(act, a, 0))
516                {
517                   if (allow) ok = 1;
518                   else if (deny)
519                     ok = -1;
520                   goto done;
521                }
522           }
523
524         continue;
525 malformed:
526         printf("WARNING: %s:%zu\n"
527                "LINE: '%s'\n"
528                "MALFORMED LINE. SKIPPED.\n",
529                file, line, buf);
530      }
531 done:
532    fclose(f);
533    return ok;
534 }
535
536 static void
537 auth_etc_enlightenment_sysactions_perm(char *path)
538 {
539    struct stat st;
540    if (stat(path, &st) == -1)
541      return;
542
543    if ((st.st_mode & S_IWGRP) || (st.st_mode & S_IXGRP) ||
544        (st.st_mode & S_IWOTH) || (st.st_mode & S_IXOTH))
545      {
546         printf("ERROR: CONFIGURATION FILE HAS BAD PERMISSIONS\n");
547         exit(10);
548      }
549 }
550
551 static char *
552 get_word(char *s,
553          char *d)
554 {
555    char *p1, *p2;
556
557    p1 = s;
558    p2 = d;
559    while (*p1)
560      {
561         if (p2 == d)
562           {
563              if (isspace(*p1))
564                {
565                   p1++;
566                   continue;
567                }
568           }
569         if (isspace(*p1)) break;
570         *p2 = *p1;
571         p1++;
572         p2++;
573      }
574    *p2 = 0;
575    return p1;
576 }