1 #include "config.h"
2 
3 #define __USE_MISC
4 #define _SVID_SOURCE
5 #define _DEFAULT_SOURCE
6 
7 #ifdef HAVE_FEATURES_H
8 # include <features.h>
9 #endif
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 #ifdef HAVE_ENVIRON
14 # define _GNU_SOURCE 1
15 #endif
16 #include <unistd.h>
17 #include <string.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <sys/wait.h>
21 #include <pwd.h>
22 #include <grp.h>
23 #include <fnmatch.h>
24 #include <ctype.h>
25 #ifdef HAVE_ALLOCA_H
26 #include <alloca.h>
27 #endif
28 #include <Eina.h>
29 
30 #ifdef HAVE_ENVIRON
31 extern char **environ;
32 #endif
33 
34 /* local subsystem functions */
35 #ifdef HAVE_EEZE_MOUNT
36 static Eina_Bool mountopts_check(const char *opts);
37 static Eina_Bool mount_args_check(int argc, char **argv, const char *action);
38 #endif
39 static int       auth_action_ok(char *a,
40                                 gid_t gid,
41                                 gid_t *gl,
42                                 int gn,
43                                 gid_t egid);
44 static int       auth_etc_enlightenment_sysactions(char *a,
45                                                    char *u,
46                                                    char **g);
47 static void      auth_etc_enlightenment_sysactions_perm(char *path);
48 static char     *get_word(char *s,
49                           char *d);
50 /* local subsystem globals */
51 static Eina_Hash *actions = NULL;
52 static uid_t uid = -1;
53 
54 /* externally accessible functions */
55 int
main(int argc,char ** argv)56 main(int argc,
57      char **argv)
58 {
59    int i, gn;
60    int test = 0;
61    char *action = NULL, *cmd;
62 #ifdef HAVE_EEZE_MOUNT
63    Eina_Bool mnt = EINA_FALSE;
64    const char *act = NULL;
65 #endif
66    gid_t gid, gl[65536], egid;
67 
68    for (i = 1; i < argc; i++)
69      {
70         if ((!strcmp(argv[i], "-h")) ||
71             (!strcmp(argv[i], "-help")) ||
72             (!strcmp(argv[i], "--help")))
73           {
74              printf(
75                "This is an internal tool for Enlightenment.\n"
76                "do not use it.\n"
77                );
78              exit(0);
79           }
80      }
81    if (argc >= 3)
82      {
83         if ((argc == 3) && (!strcmp(argv[1], "-t")))
84           {
85              test = 1;
86              action = argv[2];
87           }
88 #ifdef HAVE_EEZE_MOUNT
89         else
90           {
91              const char *s;
92 
93              s = strrchr(argv[1], '/');
94              if ((!s) || (!s[1])) exit(1);  /* eeze always uses complete path */
95              s++;
96              if (strcmp(s, "mount") && strcmp(s, "umount") && strcmp(s, "eject")) exit(1);
97              mnt = EINA_TRUE;
98              act = s;
99              action = argv[1];
100           }
101 #endif
102      }
103    else if (argc == 2)
104      {
105         action = argv[1];
106      }
107    else
108      {
109         exit(1);
110      }
111    if (!action) exit(1);
112 
113    eina_init();
114 
115    uid = getuid();
116    gid = getgid();
117    egid = getegid();
118    gn = getgroups(65536, gl);
119    if (gn < 0)
120      {
121         printf("ERROR: MEMBER OF MORE THAN 65536 GROUPS\n");
122         exit(3);
123      }
124    if (setuid(0) != 0)
125      {
126         printf("ERROR: UNABLE TO ASSUME ROOT PRIVILEGES\n");
127         exit(5);
128      }
129    if (setgid(0) != 0)
130      {
131         printf("ERROR: UNABLE TO ASSUME ROOT GROUP PRIVILEGES\n");
132         exit(7);
133      }
134 
135    if (!auth_action_ok(action, gid, gl, gn, egid))
136      {
137         printf("ERROR: ACTION NOT ALLOWED: %s\n", action);
138         exit(10);
139      }
140    /* we can add more levels of auth here */
141 
142    /* when mounting, this will match the exact path to the exe,
143     * as required in sysactions.conf
144     * this is intentionally pedantic for security
145     */
146    cmd = eina_hash_find(actions, action);
147    if (!cmd)
148      {
149         printf("ERROR: UNDEFINED ACTION: %s\n", action);
150         exit(20);
151      }
152 
153    /* sanitize environment */
154 #ifdef HAVE_UNSETENV
155 # define NOENV(x) unsetenv(x)
156    /* pass 1 - just nuke known dangerous env vars brutally if possible via
157     * unsetenv(). if you don't have unsetenv... there's pass 2 and 3 */
158    NOENV("IFS");
159    NOENV("CDPATH");
160    NOENV("LOCALDOMAIN");
161    NOENV("RES_OPTIONS");
162    NOENV("HOSTALIASES");
163    NOENV("NLSPATH");
164    NOENV("PATH_LOCALE");
165    NOENV("COLORTERM");
166    NOENV("LANG");
167    NOENV("LANGUAGE");
168    NOENV("LINGUAS");
169    NOENV("TERM");
170    NOENV("LD_PRELOAD");
171    NOENV("LD_LIBRARY_PATH");
172    NOENV("SHLIB_PATH");
173    NOENV("LIBPATH");
174    NOENV("AUTHSTATE");
175    NOENV("DYLD_*");
176    NOENV("KRB_CONF*");
177    NOENV("KRBCONFDIR");
178    NOENV("KRBTKFILE");
179    NOENV("KRB5_CONFIG*");
180    NOENV("KRB5_KTNAME");
181    NOENV("VAR_ACE");
182    NOENV("USR_ACE");
183    NOENV("DLC_ACE");
184    NOENV("TERMINFO");
185    NOENV("TERMINFO_DIRS");
186    NOENV("TERMPATH");
187    NOENV("TERMCAP");
188    NOENV("ENV");
189    NOENV("BASH_ENV");
190    NOENV("PS4");
191    NOENV("GLOBIGNORE");
192    NOENV("SHELLOPTS");
193    NOENV("JAVA_TOOL_OPTIONS");
194    NOENV("PERLIO_DEBUG");
195    NOENV("PERLLIB");
196    NOENV("PERL5LIB");
197    NOENV("PERL5OPT");
198    NOENV("PERL5DB");
199    NOENV("FPATH");
200    NOENV("NULLCMD");
201    NOENV("READNULLCMD");
202    NOENV("ZDOTDIR");
203    NOENV("TMPPREFIX");
204    NOENV("PYTHONPATH");
205    NOENV("PYTHONHOME");
206    NOENV("PYTHONINSPECT");
207    NOENV("RUBYLIB");
208    NOENV("RUBYOPT");
209 # ifdef HAVE_ENVIRON
210    if (environ)
211      {
212         int again;
213         char *tmp, *p;
214 
215         /* go over environment array again and again... safely */
216         do
217           {
218              again = 0;
219 
220              /* walk through and find first entry that we don't like */
221              for (i = 0; environ[i]; i++)
222                {
223                   /* if it begins with any of these, it's possibly nasty */
224                   if ((!strncmp(environ[i], "LD_", 3)) ||
225                       (!strncmp(environ[i], "_RLD_", 5)) ||
226                       (!strncmp(environ[i], "LC_", 3)) ||
227                       (!strncmp(environ[i], "LDR_", 3)))
228                     {
229                        /* unset it */
230                        tmp = strdup(environ[i]);
231                        if (!tmp) abort();
232                        p = strchr(tmp, '=');
233                        if (!p) abort();
234                        *p = 0;
235                        NOENV(tmp);
236                        free(tmp);
237                        /* and mark our do to try again from the start in case
238                         * unsetenv changes environ ptr */
239                        again = 1;
240                        break;
241                     }
242                }
243           }
244         while (again);
245      }
246 # endif
247 #endif
248 
249    /* pass 2 - clear entire environment so it doesn't exist at all. if you
250     * can't do this... you're possibly in trouble... but the worst is still
251     * fixed in pass 3 */
252 #ifdef HAVE_CLEARENV
253    clearenv();
254 #else
255 # ifdef HAVE_ENVIRON
256    environ = NULL;
257 # endif
258 #endif
259 
260    /* pass 3 - set path and ifs to minimal defaults */
261    putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin");
262    putenv("IFS= \t\n");
263 
264    if ((!test)
265 #ifdef HAVE_EEZE_MOUNT
266        && (!mnt)
267 #endif
268        )
269      return system(cmd);
270 #ifdef HAVE_EEZE_MOUNT
271    if (mnt)
272      {
273         int ret = 0;
274         const char *mp = NULL;
275         Eina_Strbuf *buf = NULL;
276 
277         if (!act) exit(40);
278         if (!mount_args_check(argc, argv, act)) exit(40);
279         /* all options are deemed safe at this point, so away we go! */
280         if (!strcmp(act, "mount"))
281           {
282              struct stat s;
283 
284              mp = argv[5];
285              if (stat("/media", &s))
286                {
287                   mode_t um;
288 
289                   um = umask(0);
290                   if (mkdir("/media", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH))
291                     {
292                        printf("ERROR: COULD NOT CREATE DIRECTORY /media\n");
293                        exit(40);
294                     }
295                   umask(um);
296                }
297              else if (!S_ISDIR(s.st_mode))
298                {
299                   printf("ERROR: NOT A DIRECTORY: /media\n");
300                   exit(40);
301                }
302 
303              if (stat(mp, &s))
304                {
305                   mode_t um;
306 
307                   um = umask(0);
308                   if (mkdir(mp, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH))
309                     {
310                        printf("ERROR: COULD NOT CREATE DIRECTORY %s\n", mp);
311                        exit(40);
312                     }
313                   umask(um);
314                }
315              else if (!S_ISDIR(s.st_mode))
316                {
317                   printf("ERROR: NOT A DIRECTORY: %s\n", mp);
318                   exit(40);
319                }
320           }
321         buf = eina_strbuf_new();
322         if (!buf) exit(30);
323         for (i = 1; i < argc; i++)
324           eina_strbuf_append_printf(buf, "%s ", argv[i]);
325         ret = system(eina_strbuf_string_get(buf));
326         if ((!strcmp(act, "umount")) && (!ret))
327           {
328              Eina_Iterator *it;
329              char path[PATH_MAX];
330              const char *s;
331              struct stat st;
332              Eina_Bool rm = EINA_TRUE;
333 
334              mp = strrchr(argv[2], '/');
335              if (!mp) return ret;
336              snprintf(path, sizeof(path), "/media%s", mp);
337              if (stat(path, &st)) return ret;
338              if (!S_ISDIR(st.st_mode)) return ret;
339              it = eina_file_ls(path);
340              EINA_ITERATOR_FOREACH(it, s)
341                {
342                   /* don't delete any directories with files in them */
343                   rm = EINA_FALSE;
344                   eina_stringshare_del(s);
345                }
346              if (rm)
347                {
348                   if (rmdir(path))
349                     printf("ERROR: COULD NOT UNLINK MOUNT POINT %s\n", path);
350                }
351           }
352         return ret;
353      }
354 #endif
355    eina_shutdown();
356 
357    return 0;
358 }
359 
360 /* local subsystem functions */
361 #ifdef HAVE_EEZE_MOUNT
362 static Eina_Bool
mountopts_check(const char * opts)363 mountopts_check(const char *opts)
364 {
365    char buf[64];
366    const char *p;
367    char *end;
368    unsigned long muid;
369    Eina_Bool nosuid, nodev, noexec, nuid;
370 
371    nosuid = nodev = noexec = nuid = EINA_FALSE;
372 
373    /* these are the only possible options which can be present here; check them strictly */
374    if (eina_strlcpy(buf, opts, sizeof(buf)) >= sizeof(buf)) return EINA_FALSE;
375    for (p = buf; p && p[1]; p = strchr(p + 1, ','))
376      {
377         if (p[0] == ',') p++;
378 #define CMP(OPT) \
379   if (!strncmp(p, OPT, sizeof(OPT) - 1))
380 
381         CMP("nosuid,")
382         {
383            nosuid = EINA_TRUE;
384            continue;
385         }
386         CMP("nodev,")
387         {
388            nodev = EINA_TRUE;
389            continue;
390         }
391         CMP("noexec,")
392         {
393            noexec = EINA_TRUE;
394            continue;
395         }
396         CMP("utf8,") continue;
397         CMP("utf8=0,") continue;
398         CMP("utf8=1,") continue;
399         CMP("iocharset=utf8,") continue;
400         CMP("uid=")
401         {
402            p += 4;
403            errno = 0;
404            muid = strtoul(p, &end, 10);
405            if (muid == ULONG_MAX) return EINA_FALSE;
406            if (errno) return EINA_FALSE;
407            if (end[0] != ',') return EINA_FALSE;
408            if (muid != uid) return EINA_FALSE;
409            nuid = EINA_TRUE;
410            continue;
411         }
412         return EINA_FALSE;
413      }
414    if ((!nosuid) || (!nodev) || (!noexec) || (!nuid)) return EINA_FALSE;
415    return EINA_TRUE;
416 }
417 
418 static Eina_Bool
check_uuid(const char * uuid)419 check_uuid(const char *uuid)
420 {
421    const char *p;
422 
423    for (p = uuid; p[0]; p++)
424      if ((!isalnum(*p)) && (*p != '-')) return EINA_FALSE;
425    return EINA_TRUE;
426 }
427 
428 static Eina_Bool
mount_args_check(int argc,char ** argv,const char * action)429 mount_args_check(int argc, char **argv, const char *action)
430 {
431    Eina_Bool opts = EINA_FALSE;
432    struct stat st;
433    const char *node;
434    char buf[PATH_MAX];
435 
436    if (!strcmp(action, "mount"))
437      {
438         /* will ALWAYS be:
439            /path/to/mount -o nosuid,uid=XYZ,[utf8,] UUID=XXXX-XXXX[-XXXX-XXXX] /media/$devnode
440          */
441         if (argc != 6) return EINA_FALSE;
442         if (argv[2][0] == '-')
443           {
444              /* disallow any -options other than -o */
445              if (strcmp(argv[2], "-o")) return EINA_FALSE;
446              opts = mountopts_check(argv[3]);
447           }
448         if (!opts) return EINA_FALSE;
449         if (!strncmp(argv[4], "UUID=", sizeof("UUID=") - 1))
450           {
451              if (!check_uuid(argv[4] + 5)) return EINA_FALSE;
452           }
453         else
454           {
455              if (strncmp(argv[4], "/dev/", 5)) return EINA_FALSE;
456              if (stat(argv[4], &st)) return EINA_FALSE;
457           }
458 
459         node = strrchr(argv[5], '/');
460         if (!node) return EINA_FALSE;
461         if (!node[1]) return EINA_FALSE;
462         if (node - argv[5] != 6) return EINA_FALSE;
463         snprintf(buf, sizeof(buf), "/dev%s", node);
464         if (stat(buf, &st)) return EINA_FALSE;
465      }
466    else if (!strcmp(action, "umount"))
467      {
468         /* will ALWAYS be:
469            /path/to/umount /dev/$devnode
470          */
471         if (argc != 3) return EINA_FALSE;
472         if (strncmp(argv[2], "/dev/", 5)) return EINA_FALSE;
473         if (stat(argv[2], &st)) return EINA_FALSE;
474         node = strrchr(argv[2], '/');
475         if (!node) return EINA_FALSE;
476         if (!node[1]) return EINA_FALSE;
477         if (node - argv[2] != 4) return EINA_FALSE;
478         /* this is good, but it prevents umounting user-mounted removable media;
479          * need to figure out a better way...
480          *
481            snprintf(buf, sizeof(buf), "/media%s", node);
482            if (stat(buf, &st)) return EINA_FALSE;
483            if (!S_ISDIR(st.st_mode)) return EINA_FALSE;
484          */
485      }
486    else if (!strcmp(action, "eject"))
487      {
488         /* will ALWAYS be:
489            /path/to/eject /dev/$devnode
490          */
491         if (argc != 3) return EINA_FALSE;
492         if (strncmp(argv[2], "/dev/", 5)) return EINA_FALSE;
493         if (stat(argv[2], &st)) return EINA_FALSE;
494         node = strrchr(argv[2], '/');
495         if (!node) return EINA_FALSE;
496         if (!node[1]) return EINA_FALSE;
497         if (node - argv[2] != 4) return EINA_FALSE;
498      }
499    else return EINA_FALSE;
500    return EINA_TRUE;
501 }
502 
503 #endif
504 
505 static int
auth_action_ok(char * a,gid_t gid,gid_t * gl,int gn,gid_t egid)506 auth_action_ok(char *a,
507                gid_t gid,
508                gid_t *gl,
509                int gn,
510                gid_t egid)
511 {
512    struct passwd *pw;
513    struct group *gp;
514    char *usr = NULL, **grp, *g;
515    int ret, i, j;
516 
517    pw = getpwuid(uid);
518    if (!pw) return 0;
519    usr = pw->pw_name;
520    if (!usr) return 0;
521    grp = alloca(sizeof(char *) * (gn + 1 + 1));
522    j = 0;
523    gp = getgrgid(gid);
524    if (gp)
525      {
526         grp[j] = gp->gr_name;
527         j++;
528      }
529    for (i = 0; i < gn; i++)
530      {
531         if (gl[i] != egid)
532           {
533              gp = getgrgid(gl[i]);
534              if (gp)
535                {
536                   g = alloca(strlen(gp->gr_name) + 1);
537                   strcpy(g, gp->gr_name);
538                   grp[j] = g;
539                   j++;
540                }
541           }
542      }
543    grp[j] = NULL;
544    /* first stage - check:
545     * PREFIX/etc/enlightenment/sysactions.conf
546     */
547    ret = auth_etc_enlightenment_sysactions(a, usr, grp);
548    if (ret == 1) return 1;
549    else if (ret == -1)
550      return 0;
551    /* the DEFAULT - allow */
552    return 1;
553 }
554 
555 static int
auth_etc_enlightenment_sysactions(char * a,char * u,char ** g)556 auth_etc_enlightenment_sysactions(char *a,
557                                   char *u,
558                                   char **g)
559 {
560    FILE *f;
561    char file[4096], buf[4096], id[4096], ugname[4096], perm[4096], act[4096];
562    char *p, *pp, *s, **gp;
563    int ok = 0;
564    size_t len, line = 0;
565    int allow = 0;
566    int deny = 0;
567 
568    snprintf(file, sizeof(file), "/etc/enlightenment/sysactions.conf");
569    f = fopen(file, "r");
570    if (!f)
571      {
572         snprintf(file, sizeof(file), PACKAGE_SYSCONF_DIR "/enlightenment/sysactions.conf");
573         f = fopen(file, "r");
574         if (!f) return 0;
575      }
576 
577    auth_etc_enlightenment_sysactions_perm(file);
578 
579    while (fgets(buf, sizeof(buf), f))
580      {
581         line++;
582         len = strlen(buf);
583         if (len < 1) continue;
584         if (buf[len - 1] == '\n') buf[len - 1] = 0;
585         /* format:
586          *
587          * # comment
588          * user:  username  [allow:|deny:] halt reboot ...
589          * group: groupname [allow:|deny:] suspend ...
590          */
591         if (buf[0] == '#') continue;
592         p = buf;
593         p = get_word(p, id);
594         p = get_word(p, ugname);
595         pp = p;
596         p = get_word(p, perm);
597         allow = 0;
598         deny = 0;
599         if (!strcmp(id, "user:"))
600           {
601              if (!fnmatch(ugname, u, 0))
602                {
603                   if (!strcmp(perm, "allow:")) allow = 1;
604                   else if (!strcmp(perm, "deny:"))
605                     deny = 1;
606                   else
607                     goto malformed;
608                }
609              else
610                continue;
611           }
612         else if (!strcmp(id, "group:"))
613           {
614              Eina_Bool matched = EINA_FALSE;
615 
616              for (gp = g; *gp; gp++)
617                {
618                   if (!fnmatch(ugname, *gp, 0))
619                     {
620                        matched = EINA_TRUE;
621                        if (!strcmp(perm, "allow:")) allow = 1;
622                        else if (!strcmp(perm, "deny:"))
623                          deny = 1;
624                        else
625                          goto malformed;
626                     }
627                }
628              if (!matched) continue;
629           }
630         else if (!strcmp(id, "action:"))
631           {
632              while ((*pp) && (isspace(*pp)))
633                pp++;
634              s = eina_hash_find(actions, ugname);
635              if (s) eina_hash_del(actions, ugname, s);
636              if (!actions) actions = eina_hash_string_superfast_new(free);
637              eina_hash_add(actions, ugname, strdup(pp));
638              continue;
639           }
640         else if (id[0] == 0)
641           continue;
642         else
643           goto malformed;
644 
645         for (;; )
646           {
647              p = get_word(p, act);
648              if (act[0] == 0) break;
649              if (!fnmatch(act, a, 0))
650                {
651                   if (allow) ok = 1;
652                   else if (deny)
653                     ok = -1;
654                   goto done;
655                }
656           }
657 
658         continue;
659 malformed:
660         printf("WARNING: %s:%zu\n"
661                "LINE: '%s'\n"
662                "MALFORMED LINE. SKIPPED.\n",
663                file, line, buf);
664      }
665 done:
666    fclose(f);
667    return ok;
668 }
669 
670 static void
auth_etc_enlightenment_sysactions_perm(char * path)671 auth_etc_enlightenment_sysactions_perm(char *path)
672 {
673    struct stat st;
674    if (stat(path, &st) == -1)
675      return;
676 
677    if ((st.st_mode & S_IWGRP) || (st.st_mode & S_IXGRP) ||
678        (st.st_mode & S_IWOTH) || (st.st_mode & S_IXOTH))
679      {
680         printf("ERROR: CONFIGURATION FILE HAS BAD PERMISSIONS (writable by group and/or others)\n");
681         exit(10);
682      }
683 }
684 
685 static char *
get_word(char * s,char * d)686 get_word(char *s,
687          char *d)
688 {
689    char *p1, *p2;
690 
691    p1 = s;
692    p2 = d;
693    while (*p1)
694      {
695         if (p2 == d)
696           {
697              if (isspace(*p1))
698                {
699                   p1++;
700                   continue;
701                }
702           }
703         if (isspace(*p1)) break;
704         *p2 = *p1;
705         p1++;
706         p2++;
707      }
708    *p2 = 0;
709    return p1;
710 }
711 
712