1 /*
2 xcdrwrap.c
3 hopefully secure wrapper to call cdrtools
4 12.7.01 tn
5
6 remove all references to glib functions to have no external
7 libraries in use - on some platforms suid mode is forbidden then
8 23.2.03 tn
9 */
10
11 #ifdef HAVE_CONFIG_H
12 # include <config.h>
13 #endif
14
15 #include "largefile.h"
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21 #include <string.h>
22 #include <strings.h>
23 #include <errno.h>
24 #include <pwd.h>
25 #include "xcdroast.h"
26
27 #undef DEBUG
28
29 /* dont allow any known exploit code to be passed through the cdrtools */
30 #define ANTI_CDRTOOLS_EXPLOIT
31
32
33 /* copy glib glist stuff we need into this code, so we dont need
34 to link with glib */
35
36 /* code ripped from glib 1.2.10 - glib.h and glist.c */
37
38 typedef struct _GList GList;
39 typedef void* gpointer;
40 struct _GList
41 {
42 gpointer data;
43 GList *next;
44 GList *prev;
45 };
46
g_list_last(GList * list)47 static GList* g_list_last (GList *list)
48 {
49 if (list)
50 {
51 while (list->next)
52 list = list->next;
53 }
54
55 return list;
56 }
57
g_list_first(GList * list)58 static GList* g_list_first (GList *list)
59 {
60 if (list)
61 {
62 while (list->prev)
63 list = list->prev;
64 }
65
66 return list;
67 }
68
g_list_append(GList * list,gpointer data)69 static GList* g_list_append (GList *list, gpointer data)
70 {
71 GList *new_list;
72 GList *last;
73
74 new_list = calloc(1, sizeof(GList));
75 new_list->data = data;
76
77 if (list)
78 {
79 last = g_list_last (list);
80 /* g_assert (last != NULL); */
81 last->next = new_list;
82 new_list->prev = last;
83
84 return list;
85 }
86 else
87 return new_list;
88 }
89
90 /* end of glib code */
91
92
93 static char sharedir[MAXLINE];
94 static char prefixdir[MAXLINE];
95 static char rootconfig[MAXLINE];
96 static char username[MAXLINE];
97 static char hostname[MAXLINE];
98
99 static int root_users_access;
100 static int root_hosts_access;
101 static GList *root_users_lists;
102 static GList *root_hosts_lists;
103
104 static int load_rootconfig(char *cnf);
105 static void check_access(int verbose);
106 static void get_spawn_path(char *app, char *ret, int debugout);
107
108
109 /* code duplicated from tools.c and cleared from glib symbols */
110
isroot()111 static int isroot() {
112
113 #ifdef DEBUG
114 printf("I am uid: %d\n", getuid());
115 #endif
116 if (getuid() == 0) {
117 return 1;
118 } else {
119 return 0;
120 }
121 }
122
is_directory(char * path)123 static int is_directory(char *path) {
124 struct stat buf;
125
126 if (stat(path,&buf) != 0) {
127 return 0;
128 }
129
130 if (S_ISDIR(buf.st_mode) == 1) {
131 return 1;
132 } else {
133 return 0;
134 }
135 }
136
is_file(char * path)137 static int is_file(char *path) {
138 struct stat buf;
139
140 if (stat(path,&buf) != 0) {
141 return 0;
142 }
143 return 1;
144 }
145
strip_string(char * str)146 static char *strip_string(char *str) {
147 int i,j;
148 int c1;
149
150 if ( str == NULL) return (NULL);
151
152 /* count how many leading chars to be whitespace */
153 for(i=0; i<strlen(str); i++) {
154 if (str[i] != ' ' && str[i] != '\t' && str[i] != '\r')
155 break;
156 }
157
158 /* count how many trailing chars to be whitespace */
159 for(j=strlen(str)-1; j >= 0; j--) {
160 if (str[j] != ' ' && str[j] != '\t' && str[j] != '\n' && str[j] != '\r')
161 break;
162 }
163
164 /* string contains only whitespace? */
165 if (j<i) {
166 str[0] = '\0';
167 return(str);
168 }
169
170 /* now move the chars to the front */
171 for(c1=i; c1 <= j; c1++) {
172 str[c1-i] = str[c1];
173 }
174 str[j+1-i] = '\0';
175
176 return(str);
177 }
178
escape_parse(char * str)179 static char *escape_parse(char *str) {
180 char tmp[MAXLINE];
181 char c;
182 int i,j;
183
184 if ( str == NULL) return (NULL);
185
186 if (strlen(str) > MAXLINE) return (NULL);
187
188 j = 0;
189 for(i=0; i<strlen(str); i++) {
190 c = str[i];
191 if (c == '\\') {
192 i++;
193 switch(str[i]) {
194
195 case 'n':
196 c = '\n';
197 break;
198
199 case 't':
200 c = '\t';
201 break;
202
203 case 'b':
204 c = '\b';
205 break;
206
207 default:
208 c = str[i];
209 }
210 }
211
212 tmp[j]=c;
213 j++;
214 }
215
216 tmp[j] = '\0';
217
218 strcpy(str,tmp);
219 return(str);
220 }
221
parse_config_line(char * iline,char * id,char * value)222 static int parse_config_line(char *iline, char *id, char *value) {
223 char *p,*p2;
224 char line[1024];
225 char tmp[1024];
226
227 strncpy(line,iline, MAXLINE);
228 strcpy(id,"");
229 p = strtok(line,"=");
230 if (p != NULL) {
231 /* got id */
232 strcpy(id,p);
233 strip_string(id);
234 } else {
235 return 1;
236 }
237
238 strcpy(tmp,"");
239 p = strtok(NULL,"");
240 if (p != NULL) {
241 /* string after = */
242 strcpy(tmp,p);
243 strip_string(tmp);
244 } else {
245 return 1;
246 }
247
248 /* now strip quotes from string */
249 p = tmp;
250 if (*p == '\"') {
251 p2 = p+1;
252 } else {
253 p2 = p;
254 }
255 if (p[strlen(p)-1] == '\"') {
256 p[strlen(p)-1] = '\0';
257 }
258 strcpy(value,p2);
259
260 /* now reconvert escape-chars */
261 escape_parse(value);
262
263 /* all ok */
264 return 0;
265 }
266
267 /* done code from tools.c */
268
269
270 /* return the defined callpath for a helper-binary */
271
cmdstring(char * cmd,char * ret)272 static char *cmdstring(char *cmd, char *ret) {
273 char tmp[MAXLINE];
274
275 if (strncmp(cmd,"CDRECORD",MAXLINE) == 0) {
276 strcpy(ret, CDRECORD);
277 return ret;
278 }
279 if (strncmp(cmd,"CDDA2WAV",MAXLINE) == 0) {
280 strcpy(ret, CDDA2WAV);
281 return ret;
282 }
283 if (strncmp(cmd,"READCD",MAXLINE) == 0) {
284 strcpy(ret, READCD);
285 return ret;
286 }
287 if (strncmp(cmd,"MKISOFS",MAXLINE) == 0) {
288 strcpy(ret, MKISOFS);
289 return ret;
290 }
291 if (strncmp(cmd,"WRITETEST",MAXLINE) == 0) {
292 strcpy(ret,"WRITETEST");
293 return ret;
294 }
295 if (strncmp(cmd,"-V",MAXLINE) == 0) {
296 printf("X-CD-Roast %s\n", XCDROAST_VERSION);
297 printf("sharedir: %s\n", sharedir);
298 printf("prefixdir: %s\n", prefixdir);
299
300 #if !(defined(__MACH__) && defined(__APPLE__)) && (USE_NONROOTMODE == 1)
301
302 if (load_rootconfig(rootconfig)) {
303 printf("Warning: rootconfig unreadable\n");
304 } else {
305 if (!isroot())
306 check_access(0);
307 }
308 #endif
309 /* show found paths of helper apps */
310 get_spawn_path(CDRECORD, tmp, 1);
311 printf("cdrecord found at: %s\n", tmp);
312 get_spawn_path(CDDA2WAV, tmp, 1);
313 printf("cdda2wav found at: %s\n", tmp);
314 get_spawn_path(READCD, tmp, 1);
315 printf("readcd found at: %s\n", tmp);
316 get_spawn_path(MKISOFS, tmp, 1);
317 printf("mkisofs found at: %s\n", tmp);
318
319 exit(0);
320 }
321 return NULL;
322 }
323
324
325 /* drop any root permissions */
326
drop_root()327 static void drop_root() {
328
329 #ifdef HAVE_SETREUID
330 if (setreuid(-1, getuid()) < 0)
331 #else
332 #ifdef HAVE_SETEUID
333 if (seteuid(getuid()) < 0)
334 #else
335 if (setuid(getuid()) < 0)
336 #endif
337 #endif
338 {
339 perror("Panic: Cannot set back effective uid.");
340 exit(1);
341 }
342 }
343
344
345 /* determine path for helper apps */
346
get_spawn_path(char * app,char * ret,int debugout)347 static void get_spawn_path(char *app, char *ret, int debugout) {
348 struct stat buf;
349
350 /* when path is with a leading slash (absolute), do nothing */
351 if (app[0] == '/') {
352 strncpy(ret,app,MAXLINE);
353 return;
354 }
355
356 /* otherwise its relative - add sharedir first */
357 snprintf(ret,MAXLINE,"%s/%s", sharedir, app);
358
359 /* now check if this file does exist */
360 if (stat(ret,&buf) != 0) {
361 /* it does not, so try the fallback */
362 snprintf(ret,MAXLINE,"%s/%s", prefixdir, app);
363 }
364
365 /* additional check if we run with -V */
366 if (debugout) {
367 /* check the fallback too */
368 if (stat(ret,&buf) != 0) {
369 strcpy(ret, "- not found -");
370 }
371 return;
372 }
373
374 /* paranoid check */
375 if (ret[0] != '/') {
376 printf("ERROR: Invalid relative spawnpath %s\nExiting...\n", ret);
377 exit(1);
378 }
379
380 return;
381 }
382
383
384 /* print warning when wrong arguments */
385
usagequit()386 static void usagequit() {
387
388 printf("This wrapper should only be called by X-CD-Roast %s\n",
389 XCDROAST_VERSION);
390 exit(1);
391 }
392
393
394 /* read root-user relevant stuff from config file */
395
load_rootconfig(char * cnf)396 static int load_rootconfig(char *cnf) {
397 FILE *fd;
398 char line[MAXLINE];
399 char id[MAXLINE];
400 char value[MAXLINE];
401
402 if ((fd = fopen(cnf,"r")) == NULL) {
403 /* error opening file */
404 return 1;
405 }
406
407 for (;;) {
408 if (fgets(line,MAXLINE,fd) == NULL)
409 break;
410
411 /* skip empty or hashed lines */
412 strip_string(line);
413 if (*line == '#' || *line == '\0')
414 continue;
415
416 /* parse lines */
417 if (parse_config_line(line,id,value) || !value) {
418 fprintf(stderr,"syntax error in config-file\n");
419 exit(1);
420 }
421
422 if (strcmp("ROOT_USERS_ACCESS",id) == 0) {
423 root_users_access = atoi(value);
424 }
425 if (strcmp("ROOT_USERS_LISTS",id) == 0) {
426 root_users_lists = g_list_append(root_users_lists, strdup(value));
427 }
428 if (strcmp("ROOT_HOSTS_ACCESS",id) == 0) {
429 root_hosts_access = atoi(value);
430 }
431 if (strcmp("ROOT_HOSTS_LISTS",id) == 0) {
432 root_hosts_lists = g_list_append(root_hosts_lists, strdup(value));
433 }
434
435 }
436
437 if (fclose(fd) != 0) {
438 /* error closing file */
439 return 1;
440 }
441
442 return 0;
443 }
444
445
446 /* do check if the current user and host is allowed to start xcdroast */
447 /* return 1 if so, 0 if denied */
448
checkuserhost(char * username,char * hostname)449 static int checkuserhost(char *username, char *hostname) {
450 int userok, hostok;
451 int match;
452 GList *loop;
453
454 match = 0;
455 /* user first */
456 if (root_users_access == 0) {
457 userok = 1;
458 } else
459 if (root_users_access == 1) {
460 userok = 0;
461 } else {
462 loop = g_list_first(root_users_lists);
463 while (loop) {
464 #ifdef DEBUG
465 printf("trying to match %s to %s\n", username,(char *)loop->data);
466 #endif
467 if (loop->data && strcmp(username,(char *)loop->data) == 0) {
468 /* found our login on the list */
469 match = 1;
470 }
471 loop = loop->next;
472 }
473 if ((root_users_access == 2 && match) ||
474 (root_users_access == 3 && !match)) {
475 userok = 1;
476 } else {
477 userok = 0;
478 }
479 }
480
481 match = 0;
482 /* now check host */
483 if (root_hosts_access == 0) {
484 hostok = 1;
485 } else
486 if (root_hosts_access == 1) {
487 hostok = 0;
488 } else {
489 loop = g_list_first(root_hosts_lists);
490 while (loop) {
491 #ifdef DEBUG
492 printf("trying to match %s to %s\n", hostname,(char *)loop->data);
493 #endif
494 if (loop->data && strcmp(hostname,(char *)loop->data) == 0) {
495 /* found our login on the list */
496 match = 1;
497 }
498 loop = loop->next;
499 }
500 if ((root_hosts_access == 2 && match) ||
501 (root_hosts_access == 3 && !match)) {
502 hostok = 1;
503 } else {
504 hostok = 0;
505 }
506 }
507
508 /* only when both the host and the user are allowed, allow access */
509 if (userok && hostok) {
510 return 1;
511 } else {
512 return 0;
513 }
514 }
515
516
517 /* try to write a file to the given directory
518 return 0 if ok, or 1 when failed */
519
test_write_perms(char * dir)520 static int test_write_perms(char *dir) {
521 int pidseed;
522 char tmp[MAXLINE];
523 int count;
524 FILE *fd;
525
526 drop_root();
527
528 if (!is_directory(dir))
529 return 1;
530
531 /* generate a truely unused unique filename */
532 pidseed = (int) getpid();
533 count = 0;
534
535 while (1) {
536 snprintf(tmp,MAXLINE,"%s/xcdtmp%02d.%d", dir, count, pidseed);
537 if (!is_file(tmp))
538 break;
539 count++;
540
541 /* try max 100 times to find a name */
542 if (count > 99)
543 return 1;
544 }
545
546 /* tmp does now contain a full filename which is unused for sure */
547
548 /* open that file for writing */
549 fd = fopen(tmp,"w");
550
551 if (fd == NULL) {
552 /* we failed */
553 return 1;
554 }
555
556 if (fclose(fd) != 0) {
557 /* error closing file */
558 return 1;
559 }
560
561 /* test finished - remove file */
562 unlink(tmp);
563
564 return 0;
565 }
566
567
568 /* access denied */
569
check_access(int verbose)570 static void check_access(int verbose) {
571
572 if (!username || !hostname)
573 return;
574
575 /* check if the current user on current host may run xcdroast at all */
576 if (!checkuserhost(username,hostname)) {
577 printf("X-CD-Roast %s\n", "ACCESS DENIED");
578 if (verbose) {
579 fprintf(stderr,"Unable to run wrapper - permission denied by admin.\n");
580 fprintf(stderr,"Aborting...\n");
581 }
582 exit(1);
583 }
584 }
585
586 /* returns the current username */
587
get_username()588 static char *get_username() {
589 struct passwd *ent;
590
591 ent = getpwuid(getuid());
592 return ent->pw_name;
593 }
594
main(int argc,char ** argv)595 int main(int argc, char **argv) {
596 int i, stat;
597 char **arglist;
598 char callpath[MAXLINE];
599 char tmp[MAXLINE];
600 char *p, *p1;
601 int seen_device_spec;
602
603 root_users_access = 0;
604 root_hosts_access = 0;
605 root_users_lists = NULL;
606 root_hosts_lists = NULL;
607 seen_device_spec = 0;
608
609 #ifdef PRE_LIBDIR
610 /* use prefix as sharedir as it came from the makefile-option */
611 strncpy(sharedir, PRE_LIBDIR, MAXLINE);
612 #else
613 /* otherwise install our default prefix */
614 strncpy(sharedir, LIBDIR, MAXLINE);
615 #endif
616
617 #ifdef CDRTOOLS_PREFIX
618 /* use prefix as it came from the makefile-option */
619 strncpy(prefixdir, CDRTOOLS_PREFIX, MAXLINE);
620 #else
621 # ifdef PREFIX
622 /* use prefix as it came from the makefile-option */
623 strncpy(prefixdir, PRE_PREFIX, MAXLINE);
624 # else
625 /* otherwise install our default prefix */
626 strncpy(prefixdir, PREFIX, MAXLINE);
627 # endif
628 #endif
629
630 snprintf(rootconfig, MAXLINE, "%s/%s", SYSCONFDIR, ROOTCONFIG);
631
632 if (argc < 2)
633 usagequit();
634
635
636 /* get username and host */
637 if (gethostname(hostname,MAXLINE) != 0) {
638 strncpy(hostname,"not_available",MAXLINE);
639 }
640 p1 = get_username();
641 if (p1 != NULL) {
642 strncpy(username,p1, MAXLINE);
643 } else {
644 strncpy(username,"not_available",MAXLINE);
645 }
646
647 #ifdef DEBUG
648 printf("User/Host: %s@%s\n", username, hostname);
649 #endif
650
651 /* check for a known first argument */
652 if (cmdstring(argv[1],tmp) == NULL)
653 usagequit();
654
655
656 /* now read rootconfig file - just the user and host allowlists */
657
658 #if !(defined(__MACH__) && defined(__APPLE__)) && (USE_NONROOTMODE == 1)
659
660 if (!isroot()) {
661 if (load_rootconfig(rootconfig)) {
662 fprintf(stderr,"Error reading %s - Aborting...\n", rootconfig);
663 exit(1);
664 }
665 /* check access and quit if not allowed */
666 check_access(1);
667 }
668 #endif
669
670 /* note: Its NOT possible to change the path of the binaries
671 via the commandline (-l switch), because this would
672 be a security risk. You have to use the compiled-in
673 paths */
674
675
676 /* we should check if a directory is writeable? */
677 if (strcmp(tmp,"WRITETEST") == 0) {
678 if (argc != 3)
679 usagequit();
680 stat = test_write_perms(argv[2]);
681
682 if (stat == 0) {
683 /* ok, dir is writeable */
684 return 0;
685 } else {
686 return 1;
687 }
688 }
689
690 /* make path absolute */
691 get_spawn_path(tmp, callpath, 0);
692
693 /* build new command line */
694 arglist = calloc(argc+1, sizeof(char *));
695 for (i = 1; i < argc; i++) {
696 arglist[i-1] = strdup(argv[i]);
697 }
698
699 /* now remove path from first argument */
700 strcpy(tmp,callpath);
701 p = rindex(tmp,'/');
702 if (p != NULL) {
703 if (arglist[0]) {
704 free(arglist[0]);
705 }
706 arglist[0] = strdup(p+1);
707 }
708
709 #ifdef ANTI_CDRTOOLS_EXPLOIT
710 i = 0;
711 while(arglist[i]) {
712
713 /* this argument is a device-spec */
714 if (strstr(arglist[i], "dev=") ||
715 strstr(arglist[i], "-D") ||
716 strstr(arglist[i], "--dev")) {
717 seen_device_spec = i;
718 }
719
720 /* this arg or the previous argument a device spec? */
721 if (i == seen_device_spec || i == seen_device_spec + 1) {
722
723 /* ok, here is no %x or %n allowed */
724 /* these tags show up in exploit code */
725 if (strstr(arglist[i], "%x") ||
726 strstr(arglist[i], "%n")) {
727 printf("Illegal input detected. Go away.\n");
728 exit(1);
729 }
730 }
731 i++;
732 }
733 #endif
734
735 #ifdef DEBUG
736 printf(":%s:\n",callpath);
737 i = 0;
738 while(arglist[i]) {
739 printf(":%s:\n",arglist[i]);
740 i++;
741 }
742 exit(1);
743 #endif
744
745 /* now we recreated the original command line */
746 if (execv(callpath,arglist) < 0) {
747 fprintf(stderr,"execv error while calling %s (%s)\n",
748 callpath, strerror(errno));
749 }
750
751 return 1;
752 }
753
754