/*
	rcmdsh - a shell accepting only certain remote commands

	version 1.2 - 2004-01-29 7:40 PM
		added checking for argument options (for example /usr/bin/cvs server)

	version 1.0 - 2003-01-07 4:08 AM
		Initial version - simple config file with a command listing
		 paranoid checks of args characters
	
	Copyright (c) 2004 Benoit Bourdin (bennyben)

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>	/* basename */
#include <unistd.h>	/* execv */

#define RCMDSH_CONF_FILE_1	"/usr/local/etc/rcmdsh.conf"
#define RCMDSH_CONF_FILE_2	"/etc/rcmdsh.conf"
#define RCMDSH_CONF_BUFSIZE	1024
#define RCMDSH_ARGS_AUTH_CHARS	" ,.;;:=}+)]@_-[({"

typedef struct
{
	int argc;
	char **argv;
} Sargs, *Pargs;

void paranoid_precheck_arg(const char arg[])
{
	int i, j, b;
	
		/* check if there's only alphabetical characteres
		     (+ a little list of special characters) */
	for(i=0; arg[i]!='\0'; i++)
		if( (arg[i]<'a' || arg[i]>'z') &&
		    (arg[i]<'A' || arg[i]>'Z') &&
		    (arg[i]<'0' || arg[i]>'9') ) {
		    	b=0;
			for(j=0; j<sizeof(RCMDSH_ARGS_AUTH_CHARS); j++)
				if(arg[i]==RCMDSH_ARGS_AUTH_CHARS[j])
					b=1;
			if(!b) {
				fprintf(stderr, "unknown character in argument: '%s'.\n", arg);
				exit(3);
			}
		}
}

char *check_cmd(const int argc, char *argv[])
{
	FILE *fp;
	char *buf = (char*)malloc(RCMDSH_CONF_BUFSIZE*sizeof(char));
	char *pbuf, *pbuf2, *pbuf2s, *tmp;
	char *name=NULL;
	int i, argsok;

	fp=fopen(RCMDSH_CONF_FILE_1, "rt");
	if(!fp) {
		fp=fopen(RCMDSH_CONF_FILE_2, "rt");
		if(!fp) {
			fprintf(stderr, "unable to find configuration file.\n");
			exit(2);
		}
	}
	
	while( !feof(fp) ) {
		if( !fgets(buf, RCMDSH_CONF_BUFSIZE, fp) ) {
			if(!feof(fp)) {
				fprintf(stderr, "unable to read config size line.\n");
				fclose(fp);
				exit(7);
			} else
				break;
		} else {
			argsok=1;
				/* remove last newline */
			buf[strlen(buf)-1]='\0';
			
				/* extra arguments expected, first check args */
			if(( pbuf=strchr(buf, (int)' ') )) {
				pbuf2=pbuf+1;
				for(i=1; i<argc; i++) {
					if(!pbuf2)
							/* we are on the end of the config line,
							   there's too many arguments */
						argsok=0;
					else {
						pbuf2s=pbuf2;
						if(( pbuf2=strchr(pbuf2, (int)' ') ))
							*pbuf2='\0';
						if( strcmp(argv[i], pbuf2s) ) /* argument forbidden */
							argsok=0;
						else if(pbuf2)
							*(pbuf2++)=' ';	/* restore the space */
					}
				}
				if(pbuf2)	/* missing some arguments */
					argsok=0;
				if(argsok)
					*pbuf='\0';	/* for getting command name */
			}

			if(argsok) {
					/* compare the command name & with the full path
					  anyway, we return the full path */
				if( !strcmp(buf, argv[0]) ) {
						/* we don't restore *pbuf, to exec */
					fclose(fp);
					return buf;
				}
	
					/* if we have only the basename */
				tmp=strdup(buf);
				name=basename(tmp);
				free(tmp);
				if( !strcmp(name, argv[0]) ) {
						/* we don't restore *pbuf, to exec */
					fclose(fp);
					return buf;
				}
			}
		}
	}
	fclose(fp);
	free(buf);
	fprintf(stderr, "command forbidden: '%s'.\n", argv[0]);
	exit(3);
	
		/* yeah, only for cc for a beautiful compilation result */
	return NULL;
}

Pargs to_argv(char *str)
{
	int argvl=0;
	char *p;
	char **res;
	int i=0;
	Pargs argsres=(Pargs)malloc(sizeof(Sargs));

		/* first, determine argv length */
	if(str!=NULL)
		for(p=str; p!=NULL; p=strchr(p+1, (int)' '), argvl++);

		/* and fill the argv array */
	res=(char**)malloc((argvl+1)*sizeof(char*));
	
	if(str!=NULL) {
		for(p=str, res[i++]=p, p=strchr(p, (int)' '); p!=NULL; p=strchr(p, (int)' '))
			if(p) {
				*p='\0';
				res[i++]=++p;
			}
	}
	res[i]=NULL;
	argsres->argc=argvl;
	argsres->argv=res;
	return argsres;
}

int main(int argc, char *argv[])
{
	char *cmd;
	Pargs args;
	
	if(argc<3 || strcmp(argv[1], "-c")) {
		fprintf(stderr, "please specify a command to execute.\n");
		return 1;
	}
	if(argc>3) {
		fprintf(stderr, "too many arguments.\n");
		return 4;
	}
		/* paranoid checks for all the command line */
	paranoid_precheck_arg(argv[2]);

	args=to_argv(argv[2]);
	cmd=check_cmd(args->argc, args->argv);
		
	execv(cmd, args->argv);
	free(args->argv); free(args);
	fprintf(stderr, "unable to run command, exec failed.\n");
	return 5;
}
