/****************************************************************
esh v1.0
eun.su.am
*****************************************************************/

/****************************************************************
  1   This program is free software; you can redistribute it and/or modify
  2   it under the terms of the GNU General Public License as published by
  3   the Free Software Foundation; version 2 of the License.
  4 
  5   This program is distributed in the hope that it will be useful,
  6   but WITHOUT ANY WARRANTY; without even the implied warranty of
  7   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  8   GNU General Public License for more details. 
*****************************************************************/

/****************************************************************
  esh is a shell that allows only the execution of specific commands

  Example use:
  Linux: 
  1. Create the folder /etc/esh/esh1
  2. Put esh to your /bin/ folder and rename it to esh1
  3. Now create symlinks in /etc/esh/esh1 which files should be allowed run
  4. For Example: ln -s /bin/cd /etc/esh/esh1
     This will allow the command cd in the esh1 shell.

  Windows: 
  1. Create the folder All Users/Application Data/esh/esh1.exe
  2. Put esh to your Applications folder (or somewhere you can run it from) and rename it to esh1
  3. Now create shortcuts in All Users/Application Data/esh/esh1.exe which files should be allowed run
  4. For Example: create to "cmd" as a shoutcut in All Users/Application Data/esh/esh1.exe
     This will allow the command cmd in the esh1 shell.



  Note: 
    * You don't need to create shourtcuts to cd in the settings folder, a simple textfile
	  is enough. The reason for this is that "cd" is handled internal.

    * make sure the users who use esh arent allowed to add new items to /etc/esh/

	* To prevent script executions ./ is not allowed

	* in windows compile with these extralibs: Ws2_32.lib Advapi32.lib userenv.lib





*****************************************************************/
#ifdef _WIN32
	#define WIN32_LEAN_AND_MEAN
#else
	#define _GNU_SOURCE
#endif


#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>

#ifdef _WIN32
	#include <windows.h>
	#include <direct.h>
	#include <winsock2.h>
    #include <userenv.h>
	#include <shlobj.h>
	#pragma warning(disable: 4996)
	#define _getch getch
	#define printw printf
	#define stdscr 0
	typedef enum {false , true}	bool;

	void getyx(int *y, int *x);
	void move(int y, int x);
	char *basename (char *path);
#else
	#include <unistd.h>
	#include <utmp.h>
	#include <sys/types.h>
	#include <dirent.h>
	#include <pwd.h>

	#include <ncurses.h> // ncurses-dev
	#define _getch(fp) getch()



	#define stricmp strcasecmp
	#define _getcwd getcwd 
	
#endif
#define MAX_PATH 260
#define MAX_CMD 1024



bool bexit;
char curhost[255] = "";
char curuser[255] = "";
char curhome[MAX_PATH] = "";
char bindir[MAX_PATH]=".";

int  maxbinlen = 0;
int  promtlen = 0;
char cmdbuf[MAX_CMD];
int  cmd;
char initcmd[MAX_CMD];
int  curYpos;
int	 curpos;
int  hist;
int  curhist;
char **cmdHist;

char *_substr(const char *pstr, int start, int numchars);
void int_exit (void);
void runcmd(char *cmd);
void writeprompt(void);
void clearline(void);
void clearchar(int pos);
void specialkey(int mode);
void MovePos(int x);
void addToHistory(char *s);
void UpdatePos(void);
void sighandler(int sig);

void getbins(void);
int listbins(char *s, int len);
//void autocomplete(char s, int len);
#ifndef _WIN32
	void GetUserInfo(char *user, size_t usize, char *home, size_t hsize);
#endif



char **ext_cmd;
int extcmdSize;

int main(int argc, char *argv[])
{
	bool bnew; // write a new promt
	char c;

	int  tab = 0; // 2 tabs = autocomplete
	int  ctrla = 0;      // ctrl+a and ctrl + d = exit

	#ifdef _WIN32
		DWORD size;
		WSADATA wsaData;
		HANDLE hToken = 0;
		WSAStartup(MAKEWORD(1, 1), &wsaData);
	#endif
	gethostname(curhost, sizeof(curhost));

	#ifdef _WIN32	   
		size = sizeof(curuser);
		GetUserName(curuser, &size);
		size = sizeof(curhome);
	    OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken );
	    GetUserProfileDirectory( hToken, curhome, &size );
	    CloseHandle( hToken );
	#else
		GetUserInfo(curuser, sizeof(curuser), curhome, sizeof(curhome));
	#endif

// set bin dir
#ifdef _WIN32	
	{		
		TCHAR szPath[MAX_PATH]; 
		if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA|CSIDL_FLAG_CREATE, NULL, 0, szPath)))
		{
			sprintf(bindir, "%s/esh/%s/",szPath, basename(argv[0]));
		} 
		else 
		{
			printf("Could not find config dir %s\n",bindir);
			return 0;
		}
	}
#else
	sprintf(bindir, "/etc/esh/%s/", basename(argv[0]));
#endif

	getbins();

	signal(SIGABRT, &sighandler);
	signal(SIGTERM, &sighandler);
#ifndef _WIN32
	signal(SIGINT, &sighandler);
	initscr();	
	raw();				
	noecho();
	//allow scrolling
	scrollok(stdscr, true);
#endif
	// current console position

	// we need to use it otherwise make triggers error
	argc = 0;
	//
	curhist = 0;
	hist = 0;
	bexit = false;
	bnew  = false;
	while (!bexit)
	{
		bnew = false;
		writeprompt();
		
		memset(cmdbuf, 0, MAX_CMD);
		// init command?
		if (initcmd[0])
		{
			strncpy(cmdbuf, initcmd, MAX_CMD);
			cmd = strlen(cmdbuf);
			printw(cmdbuf);
			memset(initcmd, 0, MAX_CMD);
		}
		else
		{
			cmd = 0;
		}
		UpdatePos();
		c = 0;
		while (!bnew && c != EOF)
		{
			c = _getch(stdin);
			//printw("%d\n", c);
			
			// check single keystrokes
			if (c == 1)               // CTRL +A
			{
				ctrla = 1;
				continue;
			}
			else                      // NO CTRL + A? RESET
			{
				ctrla = 0;
			}

			if (c == 3)               // CTRL + C
			{
				int_exit(); 
				bnew = true;
				continue;
			}
			else if (c == 4)		  // CTRL + D
			{		 
				if (ctrla == 1)       // CTRL + A & CTRL + D pressed
				{
					int_exit(); 
					bnew = true;
				}
				continue;
			}
			else if (c == 8)          //BACKSPACE
			{
				specialkey(8);
				continue;
			}
			else if (c == 9)		 // TAB
			{
				if (tab == 1)         // double TAB pressed
				{
					if (cmd == 0)
					{
						// list all bins we have
						if (listbins("", 0) == 0)
						{
							printw("\n");
						}
					}
					else 
					{
						// try to find a command, if its only one write it directly to console
						// if not put a list
						int ret = listbins(cmdbuf, cmd);
						if ( ret == 1)
						{
							// exactly one match? dont write a new prompt
							tab = 0;
							continue;
						} 
						else if (ret > 1)
						{
							// more then one?
							// keep the cmd
							memset(initcmd, 0, MAX_CMD);
							strncpy(initcmd, cmdbuf, MAX_CMD);
						}
						else 
						{
							// no match? dont write new prompt
							tab = 0;
							continue;
						}
					}
					tab = 0; 
					bnew = true;
				}
				else
				{
					// just one tab was pressed
					tab = 1;
				}
				continue;
			}
			else					// NO TAB? RESET
			{
				tab = 0;   
			}
		#ifdef _WIN32 // trigger it later on linux
			if (c == 27) // ESC
			{
				// delete currentline
				clearline();
			}
		#else
			if (c == 127) // DEL
			{
				specialkey(2);
			}
		#endif


		// check multi keystrokes
		#ifdef _WIN32
			else if (c == -32)
			{
				// read the next char instantly
				c = _getch(stdin);
				// decide now
				if (c == 71)  //POS1
				{
					specialkey(0);
					continue;
				}
				else if (c == 79) //END
				{
					specialkey(1);
					continue;
				}
				else if (c == 83) //DEL
				{
					specialkey(2);
					continue;
				}
				else if (c == 72) // UP
				{
					specialkey(3);
					continue;
				}
				else if (c == 80) // DOWN
				{
					specialkey(4);
					continue;
				}
				else if (c == 77)   //RIGHT
				{
					specialkey(6);
					continue;
				}
				else if (c == 75)   // LEFT
				{
					specialkey(7);
					continue;
				}
			}
		#else
			else if (c == 27)
			{
				// read the next chars
				int j=0;
				char inbuf[4]="";
				

				nodelay(stdscr, TRUE);
				while (1)
				{
					c = _getch(stdin);
					inbuf[j] = c;
					
					if (c == -1) break;
					if (inbuf[0] != 91 || j > 2)
					{
						// ignore
						j = -1;
						break;
					}
					j++;
				}
				nodelay(stdscr, FALSE);

				if (j == 0)		// ESC
				{
					// delete currentline
					clearline();
				}
				else if (j > 0)
				{
					// decide now
					if (inbuf[1] == 50 && inbuf[2] == 126) //POS1
					{
						specialkey(0);
						c = 0;	// set to 0 otherwise it will trigger the 27
						continue;
					}
					else if (inbuf[1] == 52 && inbuf[2] == 126) // END
					{
						specialkey(1);
						c = 0;
						continue;
					}
					else if (inbuf[1] == 65) //UP
					{
						specialkey(3);
						c = 0;
						continue;
					}
					else if (inbuf[1] == 66) //DOWN
					{
						specialkey(4);
						c = 0;
						continue;
					}
					else if (inbuf[1] == 67) //RIGHT
					{
						specialkey(6);
						c = 0;
						continue;
					}
					else if (inbuf[1] == 68) //LEFT
					{
						specialkey(7);
						c = 0;
						continue;
					}
				}
			


			}
		#endif
			// return
			else if (c == '\n' || c == '\r')
			{
				printw("\n");
				if (cmd > 0)
				{
					int i;
					bool bvalid=false;

					// exit commands
					if (!strncmp(cmdbuf, "exit", 4))
					{					
						// run command
						int_exit(); 
						bnew = true;
						continue;
					}
					
					// open commands
					for (i = 0; i < extcmdSize; i++)
					{
						if (!strncmp(cmdbuf, ext_cmd[i], strlen(ext_cmd[i])))
						{
							addToHistory(cmdbuf);
							//printw(">running %s\n",cmdbuf);
							runcmd(cmdbuf);
							bvalid = true;
							break;
						}
					}
					if(!bvalid)
					{
						addToHistory(cmdbuf);
						printw("esh: unknown command '%s'\n",cmdbuf);
					}
				}
				bnew = true;
			}
			// if its not a 'special' key: build cmdbuf
			else
			{
				if (cmd < MAX_CMD)
				{
					if (curpos == cmd) // cursor is on the end
					{
						strncat(cmdbuf, &c, 1);
						printw("%c",c);
						cmd++;
						UpdatePos();
					}
					else if (curpos < cmd)  // cursor is somewhere in the middle
					{
						char buf1[MAX_CMD]="";
						char *buf2 = "";
						char buf3[MAX_CMD]="";
						int posback = curpos;

						// copy first part
						strncpy(buf1,cmdbuf, curpos);

						// copy second part
						buf2 = _substr(cmdbuf, curpos, cmd);
						
						// put it together
						sprintf(buf3, "%s%c%s", buf1, c, buf2);
						clearline();
						
						strncpy(cmdbuf, buf3, strlen(buf3));
						cmd = strlen(buf3);
						printw(cmdbuf);

						// move cursor to curpos 
						MovePos(posback+1);
						
						free(buf2);

					}
				}
			}
		}
	}
	
	sighandler(0);
	return 0;
}

void int_exit (void)
{
	bexit = true;
}

char *_substr(const char *pstr, int start, int numchars)
{
	char *pnew = malloc(numchars+1);
	strncpy(pnew, pstr + start, numchars);
	pnew[numchars] = '\0';
	return pnew;
}

void changedir(const char *path)
{
	if (path[0] == '~')
	{
		char np[MAX_PATH] = ""; // build new path
		char *newpath = _substr(path, 1, strlen(path));
		sprintf(np,"%s/%s",curhome, newpath);
		free(newpath);
		chdir(np);
	}
	else
	{
		chdir(path);
	}
}

void runcmd(char *cmd)
{
  // cd can be disabled thats why its here
  if (!strncmp(cmd, "cd", 2))
  {
	char *pch = strtok(cmd," ");
	if (pch)
	{
		pch = strtok(NULL, " ");
		if (pch)
		{
			changedir(pch);
			return;
		}
	}
	changedir("~");
	return;
  }
  system(cmd);
}


// fill allowed commands to array
void getbins(void)
{
#ifdef _WIN32
	char sdir[MAX_PATH+1] = "";
	int   i=0;
	HANDLE hFind;
	WIN32_FIND_DATA FindData;

	extcmdSize = 0;
	ext_cmd = (char **) malloc(sizeof(char*) * 2);


	sprintf(sdir,"%s\\*", bindir);
	hFind = FindFirstFile(sdir, &FindData);
	if (hFind == INVALID_HANDLE_VALUE) return;
	
	if (strcmp(FindData.cFileName,".") && strcmp(FindData.cFileName,".."))
	{
		maxbinlen = strlen(FindData.cFileName);
		i++;
		ext_cmd[0] = (char *) malloc(sizeof(char*) * (maxbinlen+1));
		strncpy(ext_cmd[0], FindData.cFileName, maxbinlen);
	}

	while (FindNextFile(hFind, &FindData))
	{
		if (FindData.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY)
		{
			int len = strlen(FindData.cFileName);
			if (len > maxbinlen) maxbinlen = len;
			ext_cmd = (char **) realloc(ext_cmd, sizeof(char*) * (i+1));
			ext_cmd[i] = (char *) malloc(sizeof(char*) * len+1);
			strcpy(ext_cmd[i], FindData.cFileName);
			i++;
		}
	}
	FindClose(hFind);
#else
	int   i=0;
    DIR *pDir = opendir (bindir);
	struct dirent *pEntry;
    if ( !pDir) return;

   	extcmdSize = 0;
	ext_cmd = (char **) malloc(sizeof(char*) * 2);

	pEntry = readdir (pDir);
	while ( pEntry )
	{
       if (!(DT_DIR & pEntry->d_type))
	   {
			int len = strlen(pEntry->d_name);
			if (len > maxbinlen) maxbinlen = len;
			ext_cmd = (char **) realloc(ext_cmd, sizeof(char*) * (i+1));
			ext_cmd[i] = (char *) malloc(sizeof(char*) * len+1);
			strcpy(ext_cmd[i], pEntry->d_name);
			i++;
       }
	   pEntry = readdir (pDir);
	}
   closedir(pDir);
#endif
	extcmdSize = i;
}

int listbins(char *s, int len)
{
	// no commands?
	if (maxbinlen == 0) return 0;
	else
	{
		int   i;
		int   matches=0;
		int   colums, clmstep = 0;
		char  firstmatch[MAX_CMD]="";
#ifdef _WIN32
		HANDLE hFind;
		WIN32_FIND_DATA FindData;

		CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
		HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
		GetConsoleScreenBufferInfo(hStdout, &csbiInfo);
		// calculate colums
		colums = (unsigned int)csbiInfo.dwMaximumWindowSize.X / (maxbinlen + 2);
#else
		colums = (unsigned int)COLS / (maxbinlen + 2);
#endif

		if (colums == 0) colums = 1;



		if (len == 0) printw("\n");
		for (i = 0; i < extcmdSize; i++)
		{
			if (len > 0)
			{
				if (!strncmp(ext_cmd[i], s, len))
				{
					// save first match, this might be the one, if not we print it
					if (matches == 0)
					{
						strncpy(firstmatch, ext_cmd[i], MAX_CMD);
					}		
			
					if (matches == 1)
					{
						printw("\n%-*s", maxbinlen+2, firstmatch);
						clmstep++;
						if (clmstep == colums)
						{
							printw("\n");
							clmstep = 0;
						}
						printw("%-*s", maxbinlen+2, ext_cmd[i]);
						clmstep++;
						if (clmstep == colums)
						{
							printw("\n");
							clmstep = 0;
						}
					}
					else if (matches > 1)
					{
						printw("%-*s", maxbinlen+2, ext_cmd[i]);
						clmstep++;
					}
					if (clmstep == colums)
					{
						printw("\n");
						clmstep = 0;
					}
					matches++;
				}
			}
			else
			{
				printw("%-*s", maxbinlen+2, ext_cmd[i]);
				clmstep++;
				if (clmstep == colums)
				{
					printw("\n");
					clmstep = 0;
				}
			}
		}

		if (clmstep > 0 && (matches > 1 || len == 0))
			printw("\n");
		
		if (matches == 1)
		{
			// delete the old stuff and write our
			clearline();
			printw(firstmatch);
			strncpy(cmdbuf, firstmatch, MAX_CMD);
			cmd = strlen(firstmatch);
		}
		return matches;
	}
}




#ifndef _WIN32
	void GetUserInfo(char *user, size_t usize, char *home, size_t hsize)
	{
		char *c;
		struct utmp *u;
		const struct passwd *pwd;

		c=getlogin();
		setutent();
		while((u=getutent()))
		{
			if(u->ut_type==7 && strcmp(u->ut_user,c)==0)
			{
				strncpy(user,u->ut_user, usize);
				break;
			}
		}
		

		while ((pwd = getpwent ()))
		{
			if (!stricmp (user, pwd->pw_name))
			{
				strncpy(home,pwd->pw_dir, hsize);
				break;
			}
		}
	}
#endif

void writeprompt(void)
{
  char curdir[MAX_PATH] = "";
  _getcwd(curdir, MAX_PATH);
  printw("%s@%s:%s$ ", curuser, curhost, curdir);
  promtlen = strlen(curuser)+1+strlen(curhost)+1+strlen(curdir)+2;
}

void clearline(void)
{
	int i;
	memset(cmdbuf, 0, MAX_CMD);

	// move cursor to beginning and clear all chars
	specialkey(0);
	for (i = 0; i < cmd; i++)
		printw(" ");
	
	// and back to beginning
	MovePos(0);
	cmd = 0;
}

void clearchar(int pos)
{
	// abcdef
	// abcef
	// copy the first part
	char buf1[MAX_CMD]="";
	char *buf2 = "";
	char buf3[MAX_CMD]="";
	// copy first part
	strncpy(buf1,cmdbuf, pos-1);

	// copy second part
	buf2 = _substr(cmdbuf, pos, cmd);
						
	// put it together
	sprintf(buf3, "%s%s", buf1, buf2);
	clearline();
											
	strncpy(cmdbuf, buf3, strlen(buf3));
	cmd = strlen(buf3);
	printw(cmdbuf);

	free(buf2);
}

void specialkey(int mode)
{
	UpdatePos();
	if (mode == 0) // POS1 was pressed
	{
		if (curpos > 0)
		{
			MovePos(0);
		}
	}
	else if (mode == 1) // END was pressed
	{

		if (curpos < cmd)
		{
			MovePos(cmd);
		}
	}
	else if (mode == 2) // DEL was pressed
	{

		if (curpos < cmd)
		{
			// delete next char
			// save the position because clearchar calls clearline,
			// and clearline sets pos to 0
			int posbak = curpos;
			clearchar(curpos+1);
			MovePos(posbak);
		}
	}

	else if (mode == 3) // UP was pressed
	{
		if (hist > 0 && curhist < hist)
		{
			clearline();
			strncpy(cmdbuf, cmdHist[hist-curhist-1], MAX_CMD);
			printw(cmdbuf);
			cmd = strlen(cmdbuf);
			MovePos(cmd);
			curhist++;
		}
	}
	else if (mode == 4) // DOWN was pressed
	{
		if (hist > 0 && curhist > 1)
		{
			clearline();
			strncpy(cmdbuf, cmdHist[hist-curhist+1], MAX_CMD);
			printw(cmdbuf);
			cmd = strlen(cmdbuf);
			MovePos(cmd);
			curhist--;
		}
	}

	else if (mode == 6) // RIGHT was pressed
	{
		if (curpos < cmd)
		{
			MovePos(curpos+1);
		}
	}	

	else if (mode == 7) // LEFT was pressed
	{
		if (curpos > 0)
		{
			MovePos(curpos-1);
		}
	}	
	else if (mode == 8) // BACKSPACE was pressed
	{
		if (curpos > 0)
		{
			// delete previous char
			if (cmd > 0)
			{
				// we are on end
				if (cmd == curpos)
				{
					cmd--;
					cmdbuf[cmd] = 0;
					printw("\b \b");
					MovePos(curpos-1);
				}
				// we are somewhere on the middle
				else if (cmd > curpos)
				{
					// save the position because clearchar calls clearline,
					// and clearline sets pos to 0
					int posbak = curpos;
					clearchar(curpos);
					MovePos(posbak - 1);

				}

			}
		}
	}
	return;	
}

#ifdef _WIN32
	void getyx(int *y, int *x)
	{
		CONSOLE_SCREEN_BUFFER_INFO csbi;
		*x = *y = 0;
		if(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
			*x = csbi.dwCursorPosition.X;
			*y = csbi.dwCursorPosition.Y;
		}
	}


	void move( int y, int x )
	{
		HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
		COORD position = {x, y};
	   
		SetConsoleCursorPosition(hStdout, position);
	} 
#endif

void UpdatePos(void)
{
	int y,x;
	#ifdef _WIN32
		getyx(&y, &x);
	#else
		getyx(stdscr, y, x);
	#endif
	curYpos = y;
	curpos = x-promtlen;
}
void MovePos(int x)
{
	move(curYpos, promtlen + x);
	curpos = x;
}

void addToHistory(char *s)
{
	if (hist == 0)
	{
		cmdHist = (char**)malloc(sizeof(char*) * 2);
	}
	else
	{
		cmdHist = (char**)realloc(cmdHist, sizeof(char*) * (hist+1));
	}
	cmdHist[hist] = (char*)malloc(sizeof(char*) * strlen(s) + 1);
	strcpy(cmdHist[hist], s);
	hist++;
	curhist = 0;
}

#ifdef _WIN32
char *basename (char *path)
{
  char *ret = strrchr (path, '\\');
  return ret ? ++ret : (char*)path;  
}
#endif

void sighandler( int sig )
{
	int i;
	for (i = 0; i < extcmdSize; i++)
	{
		free(ext_cmd[i]);
	}
	free(ext_cmd);

	for (i = 0; i < hist; i++)
	{
		free(cmdHist[i]);
	}
	free(cmdHist);

#ifndef _WIN32
	refresh();
	endwin();
#else
	system("pause");
#endif
	exit(sig);
}


