/*
	xcdrwrap.c
	hopefully secure wrapper to call cdrtools
	12.7.01 tn
*/

#include "largefile.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <glib.h>
#include <errno.h>
#include "xcdroast.h"

#undef DEBUG

gchar sharedir[MAXLINE];
gchar prefixdir[MAXLINE];
gchar rootconfig[MAXLINE];
gchar username[MAXLINE];
gchar hostname[MAXLINE];

gint root_users_access;
gint root_hosts_access;
GList *root_users_lists;
GList *root_hosts_lists;


/* from tools.c */
extern gint isroot();
extern gint is_directory(gchar *path);
extern gint is_file(gchar *path);
extern gchar *strip_string(gchar *str);
extern gchar *escape_parse(gchar *str);
extern gint parse_config_line(gchar *iline, gchar *id, gchar *value);

gint load_rootconfig(gchar *cnf);
void check_access(gint verbose);


/* return the defined callpath for a helper-binary */

gchar *cmdstring(gchar *cmd, gchar *ret) {

	if (strncmp(cmd,"CDRECORD",MAXLINE) == 0) {
		strcpy(ret, CDRECORD);
		return ret;
	}
	if (strncmp(cmd,"CDDA2WAV",MAXLINE) == 0) {
		strcpy(ret, CDDA2WAV);
		return ret;
	}
	if (strncmp(cmd,"READCD",MAXLINE) == 0) {
		strcpy(ret, READCD);
		return ret;
	}
	if (strncmp(cmd,"MKISOFS",MAXLINE) == 0) {
		strcpy(ret, MKISOFS);
		return ret;
	}
	if (strncmp(cmd,"WRITETEST",MAXLINE) == 0) {
		strcpy(ret,"WRITETEST");
		return ret;
	}
	if (strncmp(cmd,"-V",MAXLINE) == 0) {
		g_print("X-CD-Roast %s\n", XCDROAST_VERSION);
		g_print("sharedir: %s\n", sharedir);
		g_print("prefixdir: %s\n", prefixdir);

#if !(defined(__MACH__) && defined(__APPLE__)) 

		if (load_rootconfig(rootconfig)) {
			g_print("Warning: rootconfig unreadable\n");
		} else {
			if (!isroot()) 
				check_access(0);
		}
#endif
		exit(0);
	}
	return NULL;	
}


/* determine path for helper apps */

void get_spawn_path(gchar *app, gchar *ret) {
struct stat buf;

	/* when path is with a leading slash (absolute), do nothing */
	if (app[0] == '/') {
		strncpy(ret,app,MAXLINE);
		return;
	}

	/* otherwise its relative - add sharedir first */
	g_snprintf(ret,MAXLINE,"%s/%s", sharedir, app);

	/* now check if this file does exist */
	if (stat(ret,&buf) != 0) {
		/* it does not, so try the fallback */
		g_snprintf(ret,MAXLINE,"%s/%s", prefixdir, app);
	}

	/* paranoid check */
	if (ret[0] != '/') {
		g_print("ERROR: Invalid relative spawnpath %s\nExiting...\n", ret);
		exit(1);
	}

	return;
}


/* print warning when wrong arguments */

void usagequit() {

	g_print("This wrapper should only be called by X-CD-Roast %s\n", 
		XCDROAST_VERSION);
	exit(1);
}


/* read root-user relevant stuff from config file */

gint load_rootconfig(gchar *cnf) {
FILE *fd;
gchar line[MAXLINE];
gchar id[MAXLINE];
gchar value[MAXLINE];

	if ((fd = fopen(cnf,"r")) == NULL) { 
		/* error opening file */
		return 1;
	}

	for (;;) {
		if (fgets(line,MAXLINE,fd) == NULL)
			break;

		/* skip empty or hashed lines */
		strip_string(line);
		if (*line == '#' || *line == '\0') 
			continue;

                /* parse lines */
		if (parse_config_line(line,id,value)) {
                	g_error("syntax error in config-file\n");
		}	

		if (strcmp("ROOT_USERS_ACCESS",id) == 0) {
			root_users_access = atoi(value);
		}
		if (strcmp("ROOT_USERS_LISTS",id) == 0) {
			root_users_lists = g_list_append(root_users_lists, g_strdup(value));	
		}
		if (strcmp("ROOT_HOSTS_ACCESS",id) == 0) {
			root_hosts_access = atoi(value);
		}
		if (strcmp("ROOT_HOSTS_LISTS",id) == 0) {
			root_hosts_lists = g_list_append(root_hosts_lists, g_strdup(value));	
		}

	}

	if (fclose(fd) != 0) {
		/* error closing file */
		return 1;
	}

	return 0;
}


/* do check if the current user and host is allowed to start xcdroast */
/* return 1 if so, 0 if denied */

gint checkuserhost(gchar *username,gchar *hostname) {
gint userok, hostok;
gint match;
GList *loop;

	match = 0;
	/* user first */
	if (root_users_access == 0) {
		userok = 1;
	} else 
	if (root_users_access == 1) {
		userok = 0;
	} else {
		loop = g_list_first(root_users_lists);
		while (loop) {
			if (strcmp(username,(gchar *)loop->data) == 0) {
				/* found our login on the list */
				match = 1;
			}		
			loop = loop->next;
		}
		if ((root_users_access == 2 && match) ||
		    (root_users_access == 3 && !match)) {
			userok = 1;
		} else  {
			userok = 0;
		}
	}	

	match = 0;
	/* now check host */
	if (root_hosts_access == 0) {
		hostok = 1;
	} else 
	if (root_hosts_access == 1) {
		hostok = 0;
	} else {
		loop = g_list_first(root_hosts_lists);
		while (loop) {
			if (strcmp(hostname,(gchar *)loop->data) == 0) {
				/* found our login on the list */
				match = 1;
			}		
			loop = loop->next;
		}
		if ((root_hosts_access == 2 && match) ||
		    (root_hosts_access == 3 && !match)) {
			hostok = 1;
		} else  {
			hostok = 0;
		}
	}

	/* only when both the host and the user are allowed, allow access */
	if (userok && hostok) {
		return 1;
	} else {
		return 0;
	}
}


/* try to write a file to the given directory 
   return 0 if ok, or 1 when failed */

gint test_write_perms(gchar *dir) {
gint pidseed;
gchar tmp[MAXLINE];
gint count;
FILE *fd;

	if (!is_directory(dir))
		return 1;

	/* generate a truely unused unique filename */
	pidseed = (gint) getpid();
	count = 0;

	while (1) {
		g_snprintf(tmp,MAXLINE,"%s/xcdtmp%02d.%d", dir, count, pidseed);
		if (!is_file(tmp))
			break;
		count++;

		/* try max 100 times to find a name */
		if (count > 99) 
			return 1;
	}

	/* tmp does now contain a full filename which is unused for sure */

	/* open that file for writing */
	fd = fopen(tmp,"w");

	if (fd == NULL) {
		/* we failed */
		return 1;
	}	

	if (fclose(fd) != 0) {
		/* error closing file */
		return 1;
	}
	
	/* test finished - remove file */
	unlink(tmp);
	
	return 0;
}


/* access denied */

void check_access(gint verbose) {

	if (!username || !hostname)
		return;

	/* check if the current user on current host may run xcdroast at all */
	if (!checkuserhost(username,hostname)) {
		g_print("X-CD-Roast %s\n", "ACCESS DENIED");
		if (verbose) {
			g_warning("Unable to run wrapper - permission denied by admin.\n");
			g_warning("Aborting...\n");
		}
		exit(1);
	}
}

 
gint main(gint argc, gchar **argv) {
gint i, stat;
gchar **arglist;
gchar callpath[MAXLINE];
gchar tmp[MAXLINE];
gchar *p, *p1;

	root_users_access = 0;
	root_hosts_access = 0;
	root_users_lists = NULL;
	root_hosts_lists = NULL;

#ifdef PRE_LIBDIR 
        /* use prefix as sharedir as it came from the makefile-option */
        strncpy(sharedir, PRE_LIBDIR, MAXLINE);
#else
        /* otherwise install our default prefix */
        strncpy(sharedir, LIBDIR, MAXLINE);
#endif

#ifdef CDRTOOLS_PREFIX
        /* use prefix as it came from the makefile-option */
        strncpy(prefixdir, CDRTOOLS_PREFIX, MAXLINE);
#else
# ifdef PREFIX
        /* use prefix as it came from the makefile-option */
        strncpy(prefixdir, PRE_PREFIX, MAXLINE);
# else
        /* otherwise install our default prefix */
        strncpy(prefixdir, PREFIX, MAXLINE);
# endif
#endif  

	strncpy(rootconfig, ROOTCONFIG, MAXLINE);

	if (argc < 2) 
		usagequit();


	/* get username and host */
	if (gethostname(hostname,MAXLINE) != 0) {
		strncpy(hostname,"not_available",MAXLINE);
	}
	p1 = g_get_user_name();
	if (p1 != NULL) {
		strncpy(username,p1, MAXLINE);
	} else {
		strncpy(username,"not_available",MAXLINE);
	}

	/* check for a known first argument */
	if (cmdstring(argv[1],tmp) == NULL) 
		usagequit();

	
	/* now read rootconfig file - just the user and host allowlists */

#if !(defined(__MACH__) && defined(__APPLE__)) 

	if (!isroot()) {
		if (load_rootconfig(rootconfig)) {
			g_warning("Error reading %s - Aborting...\n", rootconfig);
			exit(1);
		}
		/* check access and quit if not allowed */
		check_access(1);
	}
#endif

	/* note: Its NOT possible to change the path of the binaries
	         via the commandline (-l switch), because this would
		 be a security risk. You have to use the compiled-in
		 paths */


	/* we should check if a directory is writeable? */
	if (strcmp(tmp,"WRITETEST") == 0) {
		if (argc != 3) 
			usagequit();
		stat = test_write_perms(argv[2]);

		if (stat == 0) {
			/* ok, dir is writeable */
			return 0;
		} else {
			return 1;
		}
	}
	
	/* make path absolute */
	get_spawn_path(tmp,callpath);

	/* build new command line */
	arglist = g_new0(gchar *, argc+1);
	for (i = 1; i < argc; i++) {
		arglist[i-1] = g_strdup(argv[i]);
	}

	/* now remove path from first argument */
	strcpy(tmp,callpath);
	p = rindex(tmp,'/');
	if (p != NULL) {
		g_free(arglist[0]);
		arglist[0] = g_strdup(p+1);
	}	

#ifdef DEBUG
	g_print(":%s:\n",callpath);
	i = 0;
	while(arglist[i]) {
		g_print(":%s:\n",arglist[i]);
		i++;
	}
	exit(1);
#endif

	/* now we recreated the original command line */ 
	if (execv(callpath,arglist) < 0) {
		g_warning("execv error while calling %s (%s)\n", 
				callpath, strerror(errno));
	}

	return 1;
}

