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

	Original license:
	xlogin-xdmcpserver.c			Provide XDMCP server replies for a listening UDP socket
	(c) 2008 Jonathan Andrews

	It passes the MIT-XCOOKIE-1 to the X server, this is just a unique ID.  This cookie needs to be passed
	to the xlogin children, and they in turn must pass it to the xlogin shell and xauth.

	The server has no state machine, it just replies with very positive noises to all requests.  Hopefully the
	behaviour will be roughly correct for most people. 

	Any arguments to xlogin-xdmcpserver are simply passed to the xlogin processes it swpans.

	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="xlogin-xdmcpserver";
char *LASTCHANGED="19 April 2015";
#define VERSION "1.3"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <resolv.h>
#include <string.h>
#include <sys/utsname.h>

#define TRUE 1
#define FALSE 0

#define XDMCP_PORT	177
#define AUTHORIZATION_NAME	"MIT-MAGIC-COOKIE-1" // We only support this

#define XDMCP_BroadcastQuery 1
#define XDMCP_Query     2
#define XDMCP_IndirectQuery 3
#define XDMCP_ForwardQuery  4
#define XDMCP_Willing   5
#define XDMCP_Unwilling 6
#define XDMCP_Request   7
#define XDMCP_Accept    8
#define XDMCP_Decline   9
#define XDMCP_Manage    10
#define XDMCP_Refuse    11
#define XDMCP_Failed    12
#define XDMCP_KeepAlive	13
#define XDMCP_Alive     14

// The relative position of the text in the array corresponds to the opcode number, broadcastquery=1 etc
char *opcodes[15]={"","BROADCASTQUERY","QUERY","INDIRECTQUERY","FORWARDQUERY",
	 "WILLING","UNWILLING","REQUEST","ACCEPT","DECLINE","MANAGE","REFUSE","FAILED",
	 "KEEPALIVE","ALIVE"};

// 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;
}


// xdmcp socket business here
int sd;
int listen_port=XDMCP_PORT;
char *listen_addr;
struct sockaddr_in addr;
open_xdmcp_udpsocket()
{
	sd = socket(PF_INET, SOCK_DGRAM, 0);
	bzero(&addr, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(listen_port);
	if (listen_addr) 
		addr.sin_addr.s_addr = inet_addr(listen_addr);
	else
		addr.sin_addr.s_addr = INADDR_ANY;
	if ( bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
	{
		perror("Failed to bind UDP XDMCP socket.");
		exit(1);
	}
	fprintf(stderr,"%s: listening on %s port %d\n",PROGNAME,inet_ntoa(addr.sin_addr),listen_port);
}


int main (int argc, char *argv[])
{
	char rxbuffer[1024];
	char txbuffer[1024];
	char st[1024];			
	int quit=FALSE;

	long int nextsessionid;	// 32 bit number
	int x;

	// process args
	int next_option=1;
	for (x=1; x<argc; x++) {
		if (strcmp(argv[x],"-h")==0 ||
		    strcmp(argv[x],"-help")==0 ||
		    strcmp(argv[x],"--help")==0)
		{
			fprintf(stderr,"-port Specify the port to listen on (default %d)\n",PROGNAME,XDMCP_PORT);
			fprintf(stderr,"-addr Specify the addres to listen on (default all)\n",PROGNAME);
			fprintf(stderr,"All other arguments after those two are passed to instances of xlogin.\n");
			fprintf(stderr,"See \"man %s\" for details\n\n",PROGNAME);
			exit(0);			
		}
		if (strcmp(argv[x],"-p")==0 ||
		    strcmp(argv[x],"-port")==0 ||
		    strcmp(argv[x],"--port")==0)
		{
			x++; if (argv[x]) {
				listen_port=atoi(argv[x]);
			}
			next_option=x+1;
		}
		if (strcmp(argv[x],"-a")==0 ||
		    strcmp(argv[x],"-addr")==0 ||
		    strcmp(argv[x],"--addr")==0)
		{
			x++; if (argv[x]) {
				listen_addr = argv[x];
			}
			next_option=x+1;			
		}		
	}
	fprintf(stderr,"xlogin args: %d\n",next_option);

	// init
    srand(time(NULL));    // Seed random number generator
	nextsessionid=rand(); // Start handing out sessions from this number upwards
	gethostinfo();
	open_xdmcp_udpsocket();

	while (quit!=TRUE)
	{
		unsigned int xdmcp_version=0;
		unsigned int xdmcp_opcode=0;
		unsigned int xdmcp_length=0;
		unsigned int xdmcp_numberofauthnames=0;
		unsigned int xdmcp_authlength=0;
		char xdmcp_authname[512];
		
		int rxbytes, addr_len=sizeof(addr);
		int txbytes;
		char commandline[2048];

		rxbytes = recvfrom(sd, rxbuffer, sizeof(rxbuffer), 0, (struct sockaddr*)&addr, &addr_len);
		
		xdmcp_version=(rxbuffer[0]<<8)+rxbuffer[1];
		xdmcp_opcode=(rxbuffer[2]<<8)+rxbuffer[3];
		xdmcp_length=(rxbuffer[4]<<8)+rxbuffer[5];	// Length of authentication name

		fprintf(stderr,"Got packet from %s\t opcode=%d (%s)\t",inet_ntoa(addr.sin_addr),xdmcp_opcode,opcodes[xdmcp_opcode]);
		if (strlen(opcodes[xdmcp_opcode])<10)
			fprintf(stderr,"\t");
		fprintf(stderr,"| ");


		// ***********************************************************************************************************************************************
		// Received BROADCAST_QUERY or QUERY packet ? Reply with WILLING
		// ***********************************************************************************************************************************************
		if ((xdmcp_opcode==XDMCP_BroadcastQuery) || (xdmcp_opcode==XDMCP_Query))
		{
			xdmcp_numberofauthnames=rxbuffer[6]; // Number of authentication names sent
			if (xdmcp_numberofauthnames>0)		 // Only if any athnames where sent
			{
				xdmcp_authlength=(rxbuffer[8]<<8)+rxbuffer[7];	// Length of authentication name
				for (x=0;x<xdmcp_authlength;x++)
					xdmcp_authname[x]=rxbuffer[x+8];			// Copy authname string into buffer
				xdmcp_authname[x]='\0';
			}


			// Format a Willing response, least sig bits on the right
			txbuffer[0]=0;	txbuffer[1]=1;				// Version always 1
			txbuffer[2]=0;	txbuffer[3]=XDMCP_Willing;	// Opcode Willing 
			txbuffer[4]=0;	txbuffer[5]=0;				// Total data length 6+m+n+o
			txbuffer[6]=0;  txbuffer[7]=0;				// Length of authname
			txbuffer[8]=0;	txbuffer[9]=0;				// Will be length of hostname
			txbytes=10;
			// TX bytes is used an offset during the body of the code and a total length before sending the packet

			// Hostname
			for (x=0;x<strlen(hostname);x++)
				txbuffer[txbytes+x]=hostname[x]; // Now copy the hostname into the packet
			txbytes=txbytes+strlen(hostname);    
			txbuffer[8]=(strlen(hostname) >> 8); 
			txbuffer[9]=(strlen(hostname) & 0xff); 

			// Some ident string thing that doesnt seem to match the xdmcp protocol document but that xdm sends ... fuck knows ....
			txbuffer[txbytes]=0; 
			txbuffer[txbytes+1]=strlen(kernelver)-1;
			txbytes=txbytes+2;
			for (x=0;x<strlen(kernelver);x++)
				txbuffer[txbytes+x]=kernelver[x]; // Add this to the packet
			txbytes=txbytes+strlen(kernelver);

			// Lastly update the packet length field to reflect true packet length
			txbuffer[4]=0;	txbuffer[5]=txbytes-7;	// Data length field - fill it in last

			// Send the packet
			sendto(sd, txbuffer, txbytes, 0, (struct sockaddr*)&addr, sizeof(addr));
			fprintf(stderr,"Sent packet to %s\topcode=%d (%s) \n",inet_ntoa(addr.sin_addr),txbuffer[3],opcodes[txbuffer[3]]);
		}


		// ***********************************************************************************************************************************************
		// Received REQUEST packet ?    Reply with ACCEPT
		// ***********************************************************************************************************************************************
		if (xdmcp_opcode==XDMCP_Request)
		{
			int displaynumber=(rxbuffer[6]<<8)+rxbuffer[7];	// The X servers display number
			unsigned int num_contypes=rxbuffer[8];

			nextsessionid++; // We are about to start a new session

            // Format an ACCEPT response, least sig bits on the right
  			txbuffer[0]=0; 		 	txbuffer[1]=1;            // Version always 1
  			txbuffer[2]=0; 		 	txbuffer[3]=XDMCP_Accept; // Opcode ACCEPT
 			txbuffer[4]=0; 		 	txbuffer[5]=0;			  // Total data length 
			memcpy(&txbuffer[6],&nextsessionid,4);			  // Copy the session ID from long int (4 bytes) into txbuffer
			txbuffer[10]=0; 	 	txbuffer[11]=0;			  // Length of authname
			txbuffer[12]=0;  		txbuffer[13]=0;			  // Length of auth data

			txbuffer[14]=0;	txbuffer[15]=strlen(AUTHORIZATION_NAME); // Length or authorization name
			for (x=0;x<strlen(AUTHORIZATION_NAME);x++)
				txbuffer[16+x]=AUTHORIZATION_NAME[x];
			txbytes=16+x; // Last byte of authorization name

			txbuffer[txbytes++]=0;	txbuffer[txbytes++]=16; // 16 bytes to follow
			int cookiestart=txbytes;

			// Create a text version of the cookie, 16 Bytes of hex as a string
			srand(time(NULL));   // Seed random number generator
			char cookie_txt[16*2+1]; //
			cookie_txt[0]='\0';
			for (x=0;x<16;x++)
			{
				txbuffer[txbytes++]=rand();					// Random cookie, could call "mcookie" but why have the dependency
				st[0]='\0';
				sprintf(st,"%02x",(unsigned char) txbuffer[cookiestart+x]);
				strcat(cookie_txt,st);
			}

			// Create a text version of the session ID 4 Bytes long
			char session_txt[4*2+1];
			session_txt[0]='\0';
			for (x=0;x<4;x++)
			{
				st[0]='\0';
				sprintf(st,"%02X",(unsigned char) txbuffer[6+x]);
				strcat(session_txt,st);
			}

			// Lastly update the packet length field to reflect true packet length
			txbuffer[4]=0;	txbuffer[5]=txbytes-6;					// Data length field - fill it in last

			// Send the packet
			sendto(sd, txbuffer, txbytes, 0, (struct sockaddr*)&addr, sizeof(addr));
			fprintf(stderr,"Sent packet to %s\topcode=8 (%s) Display=%d \n",inet_ntoa(addr.sin_addr),opcodes[8],displaynumber);
			fprintf(stderr,"\tSessionID = %02X%02X%02X%02X\n",(unsigned char) txbuffer[9],(unsigned char) txbuffer[8], (unsigned char) txbuffer[7], (unsigned char) txbuffer[6]);
			fprintf(stderr,"\tCookie = %s\n",cookie_txt);
			
			// Simply pass the Session ID and the Magic number to the environment, the instance of xlogin will pick it up. Dont pass it on the command
			// line otherwise people can read it out of the process table.
			setenv("XSESSIONID",session_txt,1);
      		setenv("XMAGIC",cookie_txt,1);


			// ***********************************************************************************************************************************************
			// Spawn an xlogin for this display
			// If the xlogin stdin doesnt detach from the terminal then ctrl-Cing xlogin-xdmcpserver running on a console will cause the children to exit
			// As default in linux all sub-process own file desriptors from their parents, we dont want xlogin to own any stake in the udp
			// socket otherwise we have to kill all our chilren before we close/reopen the socket. Being a udp socket we dont have much state
			// held by the O/S so its simpler to close and reopen the socket as we generate the xlogin children.

			close(sd);
			// ***********************************************************************************************************************************************
			// Build command line ready to spawn sub-process

			char ipaddress[32];
			sprintf(commandline,"xlogin -xdmcp -once -display %s:%d ",inet_ntoa(addr.sin_addr),displaynumber);			

			for (x=1;x<argc;x++)
			{
				strcat(commandline,argv[x]);
				strcat(commandline," ");
			}
			// strcat(commandline," </dev/null >/dev/null &");
			strcat(commandline," &");
			
			fprintf(stderr,"\tcommandline = %s\n",commandline); 
			fprintf(stderr,"\tShould have started new instance of xlogin\n\n");
			system(commandline);

			// Take up socket listening again, did we miss much ? Probably doesnt matter as xdmcp will simply send again if we missed one
			open_xdmcp_udpsocket();	
		}


		// ***********************************************************************************************************************************************
		// Received Manage packet ?    Ignore it !
		// ***********************************************************************************************************************************************
		if (xdmcp_opcode==XDMCP_Manage)
		{
			fprintf(stderr,"Ignoring it\n");
		}


		// ***********************************************************************************************************************************************
		// Received Indirect query packet ?    Ignore it !
		// ***********************************************************************************************************************************************
		if (xdmcp_opcode==XDMCP_IndirectQuery)
		{
			fprintf(stderr,"Ignoring it\n");
		}


		// ***********************************************************************************************************************************************
		// Received KEEPAVILE		Reply with ALIVE 
		// ***********************************************************************************************************************************************
		if (xdmcp_opcode==XDMCP_KeepAlive)
		{
			// Build ALIVE packet
  			txbuffer[0]=0; 	txbuffer[1]=1; 	         // Version always 1
  			txbuffer[2]=0; 	txbuffer[3]=XDMCP_Alive; // Opcode always ALIVE
			txbuffer[4]=0;	txbuffer[5]=5;			 // Length - always 5
			txbuffer[6]=1;	                         // Tell X that session IS running
			txbuffer[7]=rxbuffer[8];	txbuffer[8]=rxbuffer[9];	// Copy session ID from received packet
			txbuffer[9]=rxbuffer[10];	txbuffer[10]=rxbuffer[11];	

			// Send it
			sendto(sd, txbuffer,12, 0, (struct sockaddr*)&addr, sizeof(addr));
			fprintf(stderr,"Sent packet to %s\topcode=%d (%s) \n",inet_ntoa(addr.sin_addr),txbuffer[3],opcodes[txbuffer[3]]);
		}
	}
	close(sd);
}
