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