/* xlogin.c	
	This version is (C) 2015, James Budiono
	Licensed as GNU GPL Version 2 or later.

	Original license:
	xlogin - X11 login box
	(c)2008 Jonathan Andrews
	
	This code is designed to be fast with very few dependencies

	Free Software, Use at own risk.
	This version is entirely free and may be used by anyone for anything, forked versions may replace this 
	with a license of their chosing, but remember my copywrite must still stand.

	Use entirely at your own risk, no warrenty or fitness for purpse is stated or implied.
*/

char PROGNAME[80]="xlogin\0";
char LASTCHANGED[80]="19 April 2015\0";
#define VERSION		"1.10"

#include "xlogin.h"
#include "key.h"

#define DEFWIDTH	312
#define	DEFHEIGHT	200	

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/utsname.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h> 
#include <X11/keysym.h>
#include <X11/cursorfont.h>

extern **environ;  // environment variables

// Colors
long int colorbackground=0x000000; // Defaults for monochrome displays
long int colorselectorbackground=0x000000;
long int colorhostnamebackground=0x000000;
long int colorcursor=0xffffff;

// Globals
int verbose=FALSE;
char *configpath;
char *displayname;
int displaydate=FALSE;
int allowroot=FALSE;
int once=FALSE;
int desktoplocked=FALSE; // By default any desktop can be selected
int desktopselected=0;
int disableaccesscontrol=FALSE;
char *backgroundimage=NULL;
char *identity=NULL;
int usecolor=TRUE;

int usexdmcp=FALSE;
char mcookie_txt[256]; // Magic cookie from xdmcpserver
char session_txt[256]; // Session ID handed out by xlogin-xdmcpserver
char mcookie[16]; // MIT MAGIC COOKIE originally handed out by xdmcp-server
char username[MAXUSERNAMESIZE*2];
char password[MAXPASSWORDSIZE*2];

// X management variables
Display *display;
int screen;
int xdepth,xwidth,xheight; // X Server display dimensions
XSetWindowAttributes window_attributes;
unsigned long window_mask;
Window xcanvas;
GC image_gc; // Graphics contexts - we only need one

// Login windows width and height, x and y
int width=DEFWIDTH,height=DEFHEIGHT;
int THISWINX=-1;	// Top left position of the window we create
int THISWINY=-1;
int boxheight;
int boxwidth; // width of "desktop item" in desktop chooser
int selectedline=0; //0=username, 1=password 

XFontStruct* font_info1; // Font structuret. 
char* font_name1 = "fixed"; // font name to use for drawing text,use "xfontsel -print" 

// get hostinfo
struct utsname hostinfo;
char *hostname;
char *kernelver;

void gethostinfo() {
	if (uname(&hostinfo)) {
		perror("Failed to get hostname for this machine.\n");
		exit(1);
	} 
	hostname = hostinfo.nodename;
	kernelver = hostinfo.release;
}

// ***************************************************************************************************************************
// Returns true if a file exists
int file_exists(char *fname)
{
        FILE *fp;
        if ((fp=fopen(fname,"r+"))==NULL)
                return(FALSE);
        fclose(fp);
        return(TRUE);
}

// ********************************************************************************************************************************
// Get a list of shell scripts in /etc/xlogin, each one describes a login for a different desktop
char *desktoplist[MAXDESKTOPS];	// N desktop scripts of 
getdesktops()
{
	FILE *fp;
	char buf[FILEPATHLENGTH];
	char line[1024];
	
	snprintf(buf,sizeof(buf),"ls -1 %s/desktops/",configpath);

	if (verbose==TRUE)
		fprintf(stderr,"%s: executing command line [%s]\n",PROGNAME,buf);

	fp=popen(buf,"r");
	if (fp==NULL)
	{
		fprintf(stderr,"%s: Failed to execute popen [%s]\n\n",PROGNAME,buf);
		exit(1);
	}

	//*********************************************************************************************************************************
	// Read stdout from sub process 
	int x;
	int foundconfig=FALSE;
	for (x=0;x<MAXDESKTOPS;x++)
	{
		// free previously alloc-ed memory - avoid memory leak
		if (desktoplist[x]) {
			free(desktoplist[x]);
			desktoplist[x]=NULL;
		}

		line[0]='\0';
		fgets(line, sizeof(line), fp);
		if (strlen(line)>1)
		{
			line[strlen(line)-1]='\0';
			desktoplist[x] = strdup(line);

			// Reuse line variable now
			snprintf(line,sizeof(line),"%s/desktops/%s",configpath,desktoplist[x]);
			if (file_exists(line)==TRUE)
			{
				foundconfig=TRUE;
				if (verbose==TRUE)
				{	
					fprintf(stderr,"%s: found desktop name %s in file %s\n",PROGNAME,desktoplist[x],line);
				}
			}
		}
	}
	fclose(fp);

	if (foundconfig==FALSE)
	{
		fprintf(stderr,"%s: Failed to find any scripts in %s/desktops\n",PROGNAME,configpath);
		//exit(1);
	}
}


// ********************************************************************************************************************************
display_text_loadfont()
{
	/* try to load the given font. */
	font_info1 = XLoadQueryFont(display, font_name1);
	if (!font_info1)
	{
		printf("%s: XLoadQueryFont - failed loading font '%s'\n",PROGNAME,font_name1);
		fflush(stdout);
		exit(-1);
	}
}



// ********************************************************************************************************************************
desktopselector()
{
	int x;

	// Divide the bottom of the window into N even sized sections, each of which could contain the name of a desktop
	boxwidth=width/MAXDESKTOPS;							// Each box is this wide
	boxheight=20;
    XSetFillStyle(display, image_gc, FillSolid);

	// Black bottom of window
    XSetForeground(display, image_gc, colorselectorbackground); 
	XFillRectangle(display, xcanvas, image_gc, 1, (height-boxheight)-1,width-2,boxheight); 	// Blank entire window, it may not have a desktop selector

	// Rule off the bottom of the window with a line left to right
    XSetForeground(display, image_gc, 0xffffff); 
	XDrawLine(display,xcanvas,image_gc,1,height-boxheight,width,height-boxheight);
	
	// For each possible desktop draw a box
	XSetForeground(display, image_gc, 0xffffff); 
	for (x=0;x<MAXDESKTOPS;x++)
	{	
		XDrawString(display, xcanvas, image_gc, (x*boxwidth)+4, (height-(boxheight/2))+4, 
			desktoplist[x]?desktoplist[x]:"", desktoplist[x]?strlen(desktoplist[x]):0);
		XDrawLine(display, xcanvas, image_gc, (x*boxwidth)+1, height,  (x*boxwidth)+1, height-boxheight);
	}


	// Redraw the currently selected desktop in Reverse
    XSetFillStyle(display, image_gc, FillSolid);
    XSetForeground(display, image_gc, 0xffffff); 
	XFillRectangle(display, xcanvas, image_gc, (desktopselected*boxwidth),height-boxheight,boxwidth+1,height); 

	// Redraw text again in black this time
    XSetForeground(display, image_gc, 0x000000); 
	XDrawString(display, xcanvas, image_gc, (desktopselected*boxwidth)+4, (height-(boxheight/2))+4, desktoplist[desktopselected], strlen(desktoplist[desktopselected]));
    XSetForeground(display, image_gc, 0xffffff); 
    XFlush(display); 
}


// ********************************************************************************************************************************
display_text()
{
	char textline1[80];
	int stringwidth;
	char st[1024];
	char passwordstar[512];
	int x;

	/* define the fill style for the GC. to be 'solid filling'. */
	XSetFillStyle(display, image_gc, FillSolid);

	/* assign the given font to our GC. */
	XSetFont(display, image_gc, font_info1->fid);


	// Hostname at top of window
	sprintf(st,"'%s'",hostname);
	stringwidth=XTextWidth(font_info1,st,strlen(st));
	XSetForeground(display, image_gc, 0xffffff);
	XDrawString(display, xcanvas, image_gc, (width-stringwidth)/2, 12, st, strlen(st));

	// Line under hostname
    XDrawLine(display, xcanvas, image_gc, 1, 16, width, 16);


	// Username
	sprintf(st,"UserName : %s",username);
	XDrawString(display, xcanvas, image_gc, 10, (height/2)-15,  st, strlen(st));
	stringwidth=XTextWidth(font_info1,textline1,strlen(st));

	// Add a cursor to the end of this line if selected
	if (selectedline==0)
	{
		// Cursor
  		XSetFillStyle(display, image_gc, FillSolid);
		XSetForeground(display, image_gc, colorcursor);
		XDrawLine(display, xcanvas, image_gc, 10+stringwidth, (height/2)-30, 10+stringwidth, (height/2)-10);
	}
 

	// Password
	sprintf(st,"PassWord : %s",password);

	// Create a version of the password string in stars this time.
	passwordstar[0]='\0';
	for (x=0;x<strlen(password);x++)
		passwordstar[x]='*';
	passwordstar[x]='\0';
	sprintf(st,"PassWord : %s",passwordstar);

	stringwidth=XTextWidth(font_info1,st,strlen(st));
	XDrawString(display, xcanvas, image_gc, 10, (height/2)+15,  st, strlen(st));

	// Add a cursor to the end of this line if selected
	if (selectedline==1)
	{
		// Cursor
  		XSetFillStyle(display, image_gc, FillSolid);
		XSetForeground(display, image_gc, colorcursor);
		XDrawLine(display, xcanvas, image_gc, 10+stringwidth, (height/2)+20, 10+stringwidth, (height/2));
	}
}


// ********************************************************************************************************************************
// Display date and time 
#include <time.h>

char datetimestring[128];
xprintdatetime(int displaynow)
{
	time_t      t;
	struct tm   *tm;
	int len;
	char line[128];

	time(&t);
	tm=localtime(&t);
	len=strftime(line,127,"%a %b %d %H:%M:%S UTC %Y",tm);

	if ((strcmp(line,datetimestring)!=0)|(displaynow==TRUE))				// If this date time string is not same as last
	{
		strcpy(datetimestring,line);
		
		// Blank background
		XSetFillStyle(display, image_gc, FillSolid);
		XSetForeground(display, image_gc, colorbackground);
		XFillRectangle(display, xcanvas, image_gc, 2, 30, width-4, 12);

		// Draw datetime text
		XSetForeground(display, image_gc, 0xffffffff);
		XDrawString(display, xcanvas, image_gc, (width/2)-(XTextWidth(font_info1,datetimestring,strlen(datetimestring))/2), 40,  datetimestring, strlen(datetimestring));
	}
}



// ********************************************************************************************************************************
redraw_window()
{
	// Fill the main window area with its background colour
	XSetFillStyle(display, image_gc, FillSolid);
	XSetForeground(display, image_gc, colorbackground);
	XFillRectangle(display, xcanvas, image_gc, 2, 2, width-1, height-2);

	// Hostname background
	XSetForeground(display, image_gc, colorhostnamebackground);
	XFillRectangle(display, xcanvas, image_gc, 2, 2, width-1, 15);

	// Extra box around the entire display
	XSetForeground(display, image_gc, 0xffffff);        			// Now draw in white
	XDrawRectangle(display,xcanvas,image_gc,0,0,width-1,height-1);
	display_text();
	XFlush(display);

	if (desktoplocked!=TRUE)
		desktopselector();

	if (displaydate==TRUE)	
		xprintdatetime(TRUE);
	
}


// ********************************************************************************************************************************
// Check if the user credentials are correct, if so extract the UID and GID from the passwd file. Modify this processes environment
// to add a display varaible and execute the shell script that loggs the desktop in

dologin()
{
	char st[1024];
	char cmdline[1024];
	struct userrec userrecord;

	// Initialise the structure - copy these values into the structure ready for call to auth	
	int userfound=FALSE;
	strcpy(userrecord.username,username); 
	strcpy(userrecord.password,password);
	strcpy(userrecord.homedir,"");
	userrecord.newuid=0;
	userrecord.newgid=0;
	userrecord.allowroot=allowroot;

	if (auth(&userrecord)!=TRUE) 	{
		// fail
		if (verbose==TRUE)
			fprintf(stderr,"%s: User did not auth using %s\n",PROGNAME,userrecord.authused);

		username[0]='\0';
		password[0]='\0';
		selectedline=0;
		sleep(1);
		
	} else {
		// success
		if (chdir(userrecord.homedir)==0) {		
			// Prepare to exec over this process, modify the environment for DISPLAY variable and set my UID and GID
			// This process is dammaged from this point, so whatever happens this process must die one way or another
			if (setgid(userrecord.newgid)!=0) {
				fprintf(stderr,"%s: Error, setgid failed\n",PROGNAME);
				exit(1);
			}
			
			// reinit groups or supplementary groups retains root privilegs
			if (initgroups(userrecord.username, userrecord.newgid)) {
			  perror("failed to init group");
			  exit(1);
			}

			if (setuid(userrecord.newuid)!=0) {
				fprintf(stderr,"%s: Error, setuid failed\n",PROGNAME);
				exit(1);
			}

			// Modify the environment this process has to reflect new DISPLAY, UID, HOME
			setenv("USER",username,1);
			setenv("DISPLAY",displayname,1);
			setenv("HOME",userrecord.homedir,1);
			setenv("AUTHUSED",userrecord.authused,1); // Pass this to the shell, might come in handy 
			sprintf(st,"%d",xwidth);
			setenv("XWIDTH",st,1);
			sprintf(st,"%d",xheight);
			setenv("XHEIGHT",st,1);
			unsetenv("UID");

			// Pass some of the values we may have enherited from xlogin-xdmcpserver
			unsetenv("XMAGIC"); // we don't want this hanging around
			setenv("SESSIONID",session_txt,1); // Session ID handed out by xlogin-xdmcpserver

			// tell others we want to use this Xauthority file
			snprintf(st,sizeof(st),"%s/.Xauthority",userrecord.homedir);
			setenv("XAUTHORITY",st,1);
			
			// and then make it, use xauth. I could use libXau but why another dependency?
			snprintf(cmdline,sizeof(cmdline),"touch .Xauthority && xauth add %s . %s", displayname, mcookie_txt);
			system(cmdline);

			XUnmapWindow(display,xcanvas);
			XDestroyWindow(display,xcanvas); // Remove the window - but dont close the connection
			XFlush(display);

			// Exec does not work. For one reason or another, libX11 cannot use the right cookie.
			
			//sprintf(cmdline,"%s/desktops/%s",configpath,desktoplist[desktopselected]);
			//fprintf(stderr,"execve %s\n",cmdline); 
     		//char *cmda[5] = { "-", "--login", "-c", cmdline, NULL };
			//if (execve("/bin/sh",cmda,environ)<0) {
			//	fprintf(stderr,"%s: execve failed\n\n",PROGNAME);				
			//	exit(1);
			//}

			snprintf(cmdline,sizeof(cmdline),"/bin/sh --login -c %s/desktops/%s",configpath,desktoplist[desktopselected]);
			//strcat(cmdline," </dev/null &>/dev/null");
			fprintf(stderr,"spawning %s\n",cmdline); 
			system(cmdline);
			
			// must terminate as the process cant change its UID GID next time round even if it wanted to 
			if (verbose==TRUE) fprintf(stderr,"Done, exiting now.\n");
			exit(0);								

		} else {
			// Could not change to users home directory, thats probably a bad thing !
			fprintf(stderr,"%s: Problem for user %s, Cant change to home directory %s\n",PROGNAME,username,userrecord.homedir);

			// Display an error in window
			strcpy(st,"Error : Home direcory does not exist!");
	        XDrawString(display, xcanvas, image_gc, 30, (height/2)+60,  st, strlen(st));
			XFlush(display);

			username[0]='\0';
			password[0]='\0';
			selectedline=0;
			sleep(5);
		}
	}
}

// load XDMCP cookies, this must be done before connecting to X server
void load_xdmcp_cookies()
{
	int x;
	char *env;	// Explicit pointer type	
	
	// Copy the text version of the key from the environment
	env=getenv("XMAGIC");
	if (env!=NULL) 
		strncpy(mcookie_txt,getenv("XMAGIC"),sizeof(mcookie_txt)-1);
	mcookie_txt[sizeof(mcookie_txt)-1]=0;

	// Hex version of the 32 bit session ID
	env=getenv("XSESSIONID");
	if (env!=NULL) 
		strncpy(session_txt,getenv("XSESSIONID"),sizeof(session_txt)-1);	
	session_txt[sizeof(session_txt)-1]=0;		

	if (verbose==TRUE)
		fprintf(stderr,"%s: Have MIT MAGIC COOKIE %s\n\n",PROGNAME,mcookie_txt);
	
	char twodigits[3];
	int counter=0;
	for (x=0;x<sizeof(mcookie)*2 && x<strlen(mcookie_txt);x=x+2) {
		twodigits[0]=mcookie_txt[x];
		twodigits[1]=mcookie_txt[x+1];
		twodigits[2]='\0';

		sscanf(twodigits,"%02x",&mcookie[counter]);
		counter++;
	}

	// Almost undocumented but important call ! Tell the X server its magic number is the one i've generated
	XSetAuthorization((char *)"MIT-MAGIC-COOKIE-1", (int) strlen("MIT-MAGIC-COOKIE-1"), mcookie, (int) 16);

	//for (x=0;x<16;x++)
	//	fprintf(stderr," %02x ",(unsigned char) mcookie[x]);
	//fprintf(stderr,"\n");

}


// connect to the X server
void connect_and_setup_server()
{
	char buf[FILEPATHLENGTH];
	int tries=1;

	while (display==NULL) {
		if (verbose==TRUE)
			fprintf(stderr,"%s: Trying to open display %s ",PROGNAME,displayname);

		display = XOpenDisplay (displayname);
		if (display==NULL) {
			if (verbose==TRUE)
				fprintf(stderr," [Failed attempt %d]\n",tries);
				
			if (once==TRUE) {
				if (verbose==TRUE)
					printf("\n%s: Giving up.\n",PROGNAME);				
				exit(1);
			}

			tries++;
			sleep(5);
		}
	}

	if (verbose==TRUE)
		printf(" [OK]\n");	

	// --- Connected, now setup the display
	screen = DefaultScreen (display);
	xdepth = DefaultDepth (display, screen);
	window_attributes.border_pixel = BlackPixel (display, screen);
	window_attributes.background_pixel = BlackPixel (display, screen);
	window_attributes.override_redirect = 0;
	window_mask = CWBackPixel | CWBorderPixel;

	// --- Turn pesky screen saver off
	XSetScreenSaver(display, 0, 0, DontPreferBlanking, DefaultExposures);

	// --- Calculate the window position using the X server dimensions and the window size
	xwidth=DisplayWidth(display,DefaultScreen(display));
	xheight=DisplayHeight(display,DefaultScreen(display));
	if (THISWINX<0) THISWINX=(xwidth/2)-(width/2);	//top left is dead center
	if (THISWINY<0) THISWINY=(xheight/2)-(height/2);

	// --- Make sure that window position is inside of viewable area, adjust if not
	if ((THISWINX+width)>=xwidth) // If off the right side
	{
		THISWINX=xwidth-(width+5);  // Bring it left until within X server width
		if (verbose==TRUE)
			fprintf(stderr, "%s: Window X position would have been outside of viewable area, moved it left\n",PROGNAME);
	}
	if ((THISWINY+height)>=xheight)
	{
		THISWINY=xheight-(height+5);
		if (verbose==TRUE)
			fprintf(stderr,"%s: Window Y position would have been outside of viewable area, moved it up\n",PROGNAME);
	}
	if (verbose==TRUE)
		fprintf(stderr,"%s: Xserver dimensions %dx%d,  depth %dbpp. using window position %dx%d.\n",PROGNAME,xwidth,xheight,xdepth,THISWINX,THISWINY);

	// --- Color setup
	if ((xdepth!=16) && (xdepth!=24))	// If its not a color depth we can handle 
		usecolor=FALSE;	                // then use mono instead

	if (usecolor==FALSE) {
		// Monochrome "colors"
 		colorbackground=0x000000;							
 		colorselectorbackground=0x000000;
 		colorhostnamebackground=0x000000;
 		colorcursor=0xffffff;
 	} else {
		if (xdepth==16)	{			
			// Defaults for 16bpp 565 color
			colorbackground=0x000011; 
			colorselectorbackground=0x0001e0;
			colorhostnamebackground=0x0049e8;
			colorcursor=0xffffff;
		}
		if (xdepth==24) {
			colorbackground=0x00008c; 
			colorselectorbackground=0x003c00;
			colorhostnamebackground=0x4a3c42;
			colorcursor=0xffffff;
		}		
	}
	
	if (verbose==TRUE) {
		if (usecolor==TRUE)
			fprintf(stderr,"%s: Using default color\n",PROGNAME);
		else fprintf(stderr,"%s: Using monochrome\n",PROGNAME);
	}
	
	// --- set background image
	if (backgroundimage) {
		if (strcmp(backgroundimage,"black")==0)	// special case, no path wanted here
			snprintf(buf,sizeof(buf),
			         "xlogin-rootjpeg -display %s -image black &",displayname);
		else	
			snprintf(buf,sizeof(buf),
			        "xlogin-rootjpeg -display %s -image %s/backgrounds/%s &",
			        displayname, configpath, backgroundimage);

		if (verbose==TRUE) 
			fprintf(stderr,"%s: Executing command [%s]\n",PROGNAME,buf);
			
		if (usexdmcp==TRUE)
			setenv("XMAGIC", mcookie_txt,1);
		system(buf);
	}

	// --- Access control, turn off if asked. This is equivilent in function to xhost +
	if (disableaccesscontrol==TRUE) {
		XSetAccessControl(display,DisableAccess);
		XDisableAccessControl(display);  
		XFlush(display);
	}	
}
	
// === entry point
int main (int argc, char *argv[])
{
	char *selected_desktop;
	char st[2048];
	int x,i;
	XEvent x_event;

	// == init
	session_txt[0]=mcookie_txt[0]=username[0]=password[0]=0;
	configpath=DEFAULT_CONFIG_PATH;
	displayname=DEFAULT_DISPLAY;
	selected_desktop=NULL;
	

	// == option parsing
	for (i=1; i<argc; i++) {
		if (strcmp(argv[i],"-display")==0) {
			i++; if (argv[i]) {
				displayname=argv[i];
				if (verbose==TRUE) fprintf(stderr,"%s: Display set to %s\n",PROGNAME,displayname);
			}
		}
		else if (strcmp(argv[i],"-ac")==0) 
		{
			disableaccesscontrol=TRUE;
		}
		else if (strcmp(argv[i],"-allowroot")==0) 
		{
			allowroot=TRUE;
		}
		else if (strcmp(argv[i],"-once")==0) 
		{
			once=TRUE;
		}
		else if (strcmp(argv[i],"-desktop")==0) 
		{
			i++; if (argv[i]) {
				desktoplocked=TRUE;
				selected_desktop = argv[i];
			}
		}
		else if (strcmp(argv[i],"-default")==0) 
		{
			i++; if (argv[i]) selected_desktop = argv[i];
		}
		else if (strcmp(argv[i],"-background")==0) 
		{
			i++; backgroundimage=argv[i];
		}
		else if (strcmp(argv[i],"-pos")==0) 
		{
			i++; if (argv[i]) {
				sscanf(argv[i],"%d,%d",&THISWINX,&THISWINY);
			}
		}
		else if (strcmp(argv[i],"-width")==0) 
		{
			i++; if (argv[i]) {
				width=atoi(argv[i]);
				if (verbose==TRUE) {
					if (width<80) {
						fprintf(stderr,"%s: Width %d too small, using default\n",PROGNAME,width); 	
						width=DEFWIDTH;
					} else 
						fprintf(stderr,"%s: Width set to %d\n",PROGNAME,width); 
				}			
			}
		}
		else if (strcmp(argv[i],"-height")==0) 
		{
			i++; if (argv[i]) {
				height=atoi(argv[i]);
				if (verbose==TRUE) {
					if (height<40) {
						fprintf(stderr,"%s: Height %d too small, using default\n",PROGNAME,height); 	
						height=DEFHEIGHT;
					}
					else 
						fprintf(stderr,"%s: Height set to %d\n",PROGNAME,height); 
				}			
			}
		}		
		else if (strcmp(argv[i],"-mono")==0) 
		{
			usecolor=FALSE;
		}
		else if (strcmp(argv[i],"-date")==0) 
		{
			displaydate=TRUE;
		}
		else if (strcmp(argv[i],"-v")==0 || strcmp(argv[i],"-verbose")==0) 
		{
			verbose=TRUE;
		}
		else if (strcmp(argv[i],"-ver")==0 || strcmp(argv[i],"-version")==0) 
		{
			fprintf(stderr,"%s version %s , Compiled with MAXDESKTOPS=%d\n",PROGNAME,VERSION,MAXDESKTOPS);
			exit(0);			
		}
		else if (strcmp(argv[i],"-conf")==0) 
		{
			i++; if (argv[i]) {
				configpath=argv[i];
				if (verbose==TRUE)
					fprintf(stderr,"%s: New configuration file path %s\n",PROGNAME,configpath);
			}
		}
		else if (strcmp(argv[i],"-identity")==0) 
		{
			i++; if (argv[i]) {
				identity=argv[i];
				if (verbose==TRUE)
					printf("%s: Using identity [%s]\n",PROGNAME,identity);
			}
			
		}
		else if (strcmp(argv[i],"-xdmcp")==0) 
		{
			usexdmcp=TRUE;
			load_xdmcp_cookies();
		}		
		else if (strcmp(argv[i],"-help")==0 || 
		         strcmp(argv[i],"--help")==0 || 
		         strcmp(argv[i],"-h")==0) 
		{
			fprintf(stderr,"%s: <option list>\n",PROGNAME);
			fprintf(stderr,"-display <name>\t IP or Hostname of X server to attach, default is localhost:0\n");
			fprintf(stderr,"-ac\t\t Once connected turn access controls off, same as xhost +\n");
			fprintf(stderr,"-allowroot\t By default root logins are not permitted except localhost:0\n");
			fprintf(stderr,"-once\t\t Without this it will continuously try to attach to a display\n");
			fprintf(stderr,"-desktop <name>\t Limit login to given desktop, no selection menu\n");
			fprintf(stderr,"-default <name>\t Start with this desktop selected from the list\n");
			fprintf(stderr,"-background <name> Put a jpeg image into the root window (background)\n");
			fprintf(stderr,"-height YYY \t Height of the login window\n");
			fprintf(stderr,"-width XXX \t Width of the login window\n");
			fprintf(stderr,"-pos XXX,YYY\t Position the login window here instead of centered on display\n");
			fprintf(stderr,"-mono\t\t Use mono instead of default colour\n");
			fprintf(stderr,"-date\t\t Display current date/time in login\n");
			fprintf(stderr,"-v\t\t Be verbose with messages on stdout, default is silent\n");
			fprintf(stderr,"-ver\t\t Display version information\n");
			fprintf(stderr,"-conf\t\t Path to configuration files, default is /etc/xlogin\n");
			fprintf(stderr,"-identity <name> Auto login, use filename of identity without path\n");
			fprintf(stderr,"-xdmcp \t\t Used by xlogin-xdmcpserver, do not use\n");
			fprintf(stderr,"-help or --help\t This text\n");
			exit(0);			
		}
	}

	// == enable allow root for primary local display
	if ((strcmp(displayname,"localhost")==0) || (strcmp(displayname,"localhost:0")==0))
		allowroot=TRUE;
	
	// == get machine name
	gethostinfo();	
	if (verbose==TRUE)
		printf("%s: this machines hostname is : %s\n",PROGNAME,hostname);
	
	// == process selected_desktop (-desktop) and -default)
	//    (do it here after configpath is settled)
	getdesktops();
	int founddesktop=FALSE;	
	if (selected_desktop) {
		for (x=0;x<MAXDESKTOPS;x++) {
			if (desktoplist[x] && strcmp(selected_desktop,desktoplist[x])==0) {
				desktopselected=x;
				founddesktop=TRUE;
			}
		}

		if (founddesktop!=TRUE && desktoplocked) {
			fprintf(stderr,"%s: Error failed to find a file called %s/desktops/%s\n",PROGNAME,configpath,selected_desktop);
			exit(1); // Fatel error
		}
		if (verbose==TRUE && founddesktop)
			fprintf(stderr,"%s: Selected desktop %s\n",PROGNAME,desktoplist[desktopselected]);
	}

	// == load autologin details if asked
	//    (do it here after configpath is settled)
	if (identity) {
		snprintf(st,sizeof(st),"%s/identity/%s.id", configpath, identity);
		if (verbose==TRUE)
			fprintf(stderr,"%s: Trying file [%s]\n",PROGNAME,st);
		weakdecrypt(username, password, st);
	}
	
	// == connect and setup to  x server 
	connect_and_setup_server();

	// == create login window
	xcanvas = XCreateWindow (display, DefaultRootWindow (display), THISWINX, THISWINY, \
			 width, height, 0, xdepth, InputOutput, CopyFromParent, window_mask, \
			 &window_attributes);
	image_gc = XCreateGC (display, xcanvas, 0, 0);
	XMapWindow (display,xcanvas);
	XStoreName (display,xcanvas,PROGNAME); // Tell window manager my name
	display_text_loadfont();

	// == tell x server the kind of input we are prepared to receive
	XSelectInput(display, xcanvas, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | StructureNotifyMask);

	// == set cursor
	int cursor_shape = XC_left_ptr;
	Cursor cursor;
	cursor = XCreateFontCursor(display, cursor_shape);
	XDefineCursor(display, xcanvas, cursor);

	
	// ********************************************************************************************************************************
	// Main loop - Process X11 Events

	int quit=FALSE;
	int redraw=FALSE;
	int nextdesktop;
	int count;
	char key;
	int shifted=FALSE;
	int capslock=FALSE;
		
	redraw_window();

	// If we have a username and password from the -identity option then skip the main loop and go directly for a user shell
	if (strlen(username)!=0)
	{
		sleep(2);
		dologin();

		// If we get here then the username/password was not authenticated so clear it !
		username[0]='\0';
		password[0]='\0';
		redraw=TRUE;
	}


	// Grab the keyboard so that the window need not be the current focus
	if (XGrabKeyboard(display,xcanvas,True,GrabModeAsync,GrabModeAsync,CurrentTime))
	{
		fprintf(stderr,"%s: Warning XGrabKyeyboard failed\n",PROGNAME);	
	}


	// If this sucks to read just imagine how much is sucks to write 
	while (quit!=TRUE)
	{
		if (XCheckWindowEvent(display, xcanvas, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | StructureNotifyMask, &x_event)==True)
		{
			// If its a redraw event, set the flag but dont do the action yet
			if (x_event.type==Expose) 
				redraw=TRUE;

			if (x_event.type==ButtonPress)
			{
				if (x_event.xmotion.y>height-boxheight)
				{
					nextdesktop=(x_event.xmotion.x/boxwidth);
					// If that box is populated then user can select it
					if (desktoplist[nextdesktop])	
						desktopselected=nextdesktop;
					if (verbose==TRUE)
						fprintf(stderr,"%s: Selected desktop = %s\n",PROGNAME,desktoplist[desktopselected]);
					redraw=TRUE;
				}
			}

			if (x_event.type==KeyRelease)
			{
				key=XLookupKeysym(&x_event.xkey, 0);
				if ((key==-30) || (key==-31))
					shifted=FALSE;
			}

			if (x_event.type==KeyPress)
			{
				// For the moment every time a key is pressed redraw the window having filled it with bakdground colour
				// this overwrites the cursor line and hides any graphics nasty.
				redraw=TRUE; // Redraw the window contents for each keypress

				key=XLookupKeysym(&x_event.xkey, 0);

				if ((key==-30) || (key==-31))
					shifted=TRUE;

				if (key==-27)
				{
					if (capslock==FALSE)
					{
						capslock=TRUE;
						shifted=TRUE;
					}
					else	
					{	capslock=FALSE;
						shifted=FALSE;
					}
					fprintf(stderr,"CAPS LOCK state=%d",capslock);
				}

				if (shifted==TRUE)
				{
					if (key=='1')	key='!';
					if (key=='2')	key='@';
					if (key=='3')	key='$';
					if (key=='4')	key='5';
					if (key=='5')	key='%';
					if (key=='6')	key='^';
					if (key=='7')	key='&';
					if (key=='8')	key='*';
					if (key=='9')	key='(';
					if (key=='0')	key=')';
					if (key=='-')	key='_';
					if (key=='=')	key='+';
					if (key=='[')	key='{';
					if (key==']')	key='}';
					if (key==';')	key=':';
					if (key==39)	key=34;
					if (key=='\\')	key='|';
					if (key==',')	key='<';
					if (key=='.')	key='>';
					if (key=='/')	key='?';
					if (key=='<')	key='>';
				}

				if (key==27)
					quit=TRUE;	// Escape key terminates application

				if (key>0)
				{
					if (shifted==TRUE)
						key=toupper(key); // If shift is on then upper case the key
					if (key==8)	// Delete
					{
						if (selectedline==0)
						{
							if (strlen(username)>0)	// If the string is 1 or more chars long
								username[strlen(username)-1]='\0';	// Shorten line by one character
						}
						if (selectedline==1)
						{
							if (strlen(password)>0)
								password[strlen(password)-1]='\0';
						}
					}
					else
					{
						if (selectedline==0)
						{
							if (key==13)
								selectedline=1;
							else
							{
								st[0]=key;
								st[1]='\0';
								if (strlen(username)<MAXUSERNAMESIZE)
								{
									//strcat(username,st);
									x=strlen(username);
									username[x]=key; username[x+1]='\0';
								}
									
							}
						}
						else // selected line must be password 
						{
							if (key==13)
							{
								dologin();
								// If dologin works then this process xlogin is overwritten 
								// by the instance of the shell used to start the session
								// so its safe to assume if we are running code that the credentials are not valid
							}
							else
							{
								st[0]=key;
								st[1]='\0';
								if (strlen(password)<MAXPASSWORDSIZE)
									strcat(password,st);
							}
						}
					}
				}
			} // If keypress
		} // If window event 
		else
			usleep(50000);	// No window event then zzzzz for a while

		if (redraw==TRUE)
		{
				redraw=FALSE;
				redraw_window();
		}

		if (displaydate==TRUE)
			xprintdatetime(FALSE);
	} // while
}
