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