/* vde_cryptcab.c
 * Copyright © 2006 Daniele Lacamera <root@danielinux.net>
 * From an idea by Renzo Davoli <renzo@cs.unibo.it>
 * 
 * Released under the terms of GNU GPL v.2
 * 
 * see:
 * http://www.gnu.org/copyleft/gpl.html
 
 * This program is released under the GPL with the additional exemption that
 * compiling, linking, and/or using OpenSSL is allowed.
 */

#define _GNU_SOURCE
#include "config.h"
#include "blowfish.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include "vde.h"

#define PORTNO 7667
static char *plugname;
static char *programname;
static char *remoteusr;
static char *remotehost;
static int localport;
static int remoteport;
static int may_login=1;
static struct vde_open_args open_args={.port=0,.group=NULL,.mode=0700};


#ifndef HAVE_STRNDUP
/*
 * This could be written in a more efficient way. No time to do it now.
 */
static char *strndup(const char *s, size_t n)
{
	size_t len = MIN(n, strlen(s));
	char *new = (char *) malloc (len + 1);

	if (new == NULL)
		return NULL;

	new[len] = '\0';
	return (char *) memcpy (new, s, len);
}
#endif


/*
 * Manage dead children, avoid zombies.
 */
void zombie_carnage(int signo)
{
	int pid;
	wait(&pid);
}

/*
 * Call the generate_key() and then transmit the key to the server via 
 * OpenSSH secure copy.
 */
static struct peer *generate_and_xmit(struct peer *ret){
	char command[255];
	int res;
	struct hostent *target;

	//fprintf(stderr,"Generating new key..\n");
	ret=generate_key(ret);
	/*fprintf(stderr,"Key:");
	for(i=0;i<16;i++)
		fprintf(stderr,"%02X",ret->key[i]);
	fprintf(stderr,"\n");
	*/

	if(!ret){
		fprintf(stderr,"Couldn't create the secret key.\n");
		exit(255);
	}
	
	target=gethostbyname(remotehost);
	if (target == NULL)
	{
		fprintf(stderr,"%s not found.\n", remotehost);
		exit(2);
	}
	ret->in_a.sin_family = AF_INET;
	ret->in_a.sin_port = htons(remoteport);
	ret->in_a.sin_addr.s_addr=((struct in_addr *)(target->h_addr))->s_addr;
		
	if(remoteusr)
		sprintf(command,"scp /tmp/.blowfish.key %s@%s:/tmp/.%s.key\0", remoteusr, remotehost, ret->id);
	else
		sprintf(command,"scp /tmp/.blowfish.key %s:/tmp/.%s.key\0", remotehost, ret->id);
	//fprintf(stderr,"Contacting host: %s ",remotehost);
	res=system(command);
	
	if(res==0){
	//	fprintf(stderr,"Key successfully transferred using a secure channel.\n");
	}else{
		fprintf(stderr,"Couldn't transfer the secret key.\n");
		exit(253);
	}
	return ret;
}

/*
 * Manage dynamic address changing, client side.
 */
static void handover(struct peer *p)
{
	//fprintf(stderr,"Doing handover.\n");
	vde_close(p->plug);
	usleep(1000000);
	p=(struct peer *)generate_and_xmit(p);
	p->state=ST_OPENING;
	p->next=NULL;
	p->counter=0;
	blowfish_login(p);
	may_login=0;
}

/*
 * Send an identification packet, similar to login packet, if server
 * doesn't remind us for any reason (typically server restart or device handover)
 */
static void send_id(struct peer *p)
{
	send_udp(p->id,FILENAMESIZE,p,CMD_IDENTIFY);
}

/*
 * Request an handover to the client. We remind it, but its key is no more valid.
 */
static void send_handover(struct peer *p)
{
	send_udp(p->id,FILENAMESIZE,p,CMD_HANDOVER);
}

/*
 * Handover packet is crypted. Check its validity, i.e. it is coming from the server.
 * Avoid "handover storm" DoS attack to the client.
 */
static int valid_handover(struct datagram *pkt, struct peer *p)
{
	return (pkt->len!=16)||(strncmp(pkt->data,p->id,FILENAMESIZE))?0:1;
}

/*
 * Execute a naif vde_plug process, attach it to the two peer pipes to exchange data 
 * with cable in both directions.
void
old_vde_plug(struct peer *p){
int r;
	pipe(p->toplug);
	pipe(p->tocable);
	p->pid=fork();
	if(p->pid==0){
		close (STDIN_FILENO);
		dup(p->toplug[0]);
		close (STDOUT_FILENO);
		dup(p->tocable[1]);
		close(p->toplug[1]);
		close(p->tocable[0]);
		r=execl("/usr/bin/vde_plug","vde_plug",plugname,(char*)(0));
		if(r==-1)
			r=execl("/usr/local/bin/vde_plug","vde_plug",plugname,(char*)(0));
		if (r==-1)
		perror ("vde_plug executable not found.\n");
		exit(0);
	}	
		close(p->toplug[0]);
		close(p->tocable[1]);
}

*/


void
vde_plug(struct peer *p)
{
	p->plug=vde_open(plugname,"vde_cryptcab",&open_args);
	if(!p->plug)
	{
		perror ("libvdeplug");
		exit(1);
	}
}



/*
 * Usage implies exit.
 */
static void Usage(void)
{

	fprintf(stderr,"Usage: %s [-s socketname] [-c [remoteuser@]remotehost[:remoteport]] [-p localport] [-d] \n",programname);
	exit(1);
}


void client_maylogin(int signo)
{
	may_login=1;
}

static inline void try_to_login(struct peer *p)
{

	struct itimerval *old=NULL;
	struct itimerval nxt={
				.it_interval={.tv_sec=0, .tv_usec=0},
				.it_value={.tv_sec=5, .tv_usec=0}
	};
	if(!may_login)
		return;
	blowfish_login(p);
	may_login=0;
	setitimer(ITIMER_REAL, &nxt, old);
}

/*
 * Main.
 */
int main(int argc, char **argv)
{
	int wire;
	struct sockaddr_in myaddr;
	struct datagram *pkt;
	struct peer *p1;
	struct sigaction sa;


	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;

	programname=argv[0];		
	plugname="/tmp/vde.ctl";
	localport=PORTNO;
	sa.sa_handler = zombie_carnage;
	sigaction(SIGCHLD, &sa, NULL);
  {
	  int c;
	  while (1) {
		  int option_index = 0;
		  char *ctl_socket;
		  const char sepusr='@';
		  const char sepport=':';
		  char *pusr,*pport;

		  static struct option long_options[] = {
			  {"sock", 1, 0, 's'},
			  {"vdesock", 1, 0, 's'},
			  {"unix", 1, 0, 's'},
			  {"localport", 1, 0, 'p'},
			  {"connect",1,0,'c'},
			  {"mod",1,0,'m'},
			  {"help",0,0,'h'},
			  {0, 0, 0, 0}
		  };
		  c = GETOPT_LONG (argc, argv, "s:p:c:h",
				  long_options, &option_index);
		  if (c == -1)
			  break;
		  switch (c) {
			  case 's':
				  plugname=strdup(optarg);
				  break;
			  case 'c':
				  ctl_socket=strdup(optarg);

				  pusr=strchr(ctl_socket,sepusr);
				  pport=strchr(ctl_socket,sepport);
				  
				  if( ( pusr != strrchr(ctl_socket,sepusr)) || 
					(pport != strrchr(ctl_socket,sepport)) ||
						(pport && pusr>pport) )
					  Usage();
				  
				  if(!pusr && !pport){
					  remoteusr=NULL;
					  remoteport=PORTNO;
					  remotehost=strdup(ctl_socket);
					  break;
				  }
				  if(!pport){
				  	  remoteusr=(char *)strndup(ctl_socket,pusr-ctl_socket);
					  remotehost=(char *)strndup(pusr+1,strlen(ctl_socket)-strlen(remoteusr)-1);
					  remoteport=PORTNO;
					  break;
				  }
		  		  if(!pusr){
					  remoteusr=NULL;
				  	  remotehost=(char *)strndup(ctl_socket,pport-ctl_socket);
					  remoteport=atoi((char *)strndup(pport+1,strlen(ctl_socket)-strlen(remotehost)-1));
					  break;
				  }
				  remoteusr=(char *)strndup(ctl_socket,pusr-ctl_socket);
				  remotehost=(char *)strndup(pusr+1,pport-pusr-1);
				  remoteport=atoi((char *)strndup(pport+1,strlen(ctl_socket)-strlen(remotehost)-strlen(remoteusr)-2));
				  break;

			  case 'p':
				localport=atoi(optarg);
				break;
				
			  case 'm': 
				sscanf(optarg,"%o",&(open_args.mode));
				break;

			  case 'h':
			  default:
				  Usage();
		  }
	  }
	  if(optind < argc)
		  Usage();
  }

	memset ((char *)&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
	myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	myaddr.sin_port = htons(localport);
	
	wire=socket(PF_INET,SOCK_DGRAM,0);
	if (bind(wire,(struct sockaddr *) &myaddr, sizeof(myaddr))<0)
		        {perror("bind socket"); exit(3);}
	
	blowfish_init(wire);

	if( (remotehost) && strlen(remotehost)>0 ) {
		sa.sa_handler = client_maylogin;
		sigaction(SIGALRM, &sa, NULL);
		p1=generate_and_xmit(NULL);
		p1->state=ST_OPENING;
		p1->next=NULL;
		try_to_login(p1);
		addpeer(p1);
		vde_plug(p1);
	} else {
		sa.sa_handler = autocleaner;
		sigaction(SIGALRM, &sa, NULL);
		kill(getpid(),SIGALRM);
	}
	
	
	for(;;){
		pkt=blowfish_select(0);
//		fprintf(stderr,".");
		if(pkt!=NULL){
			p1=getpeer(pkt->orig->in_a);
			if(pkt->src==SRC_VDE){
				if(p1 && (p1->state==ST_AUTH || p1->state==ST_SERVER)){
					send_udp(pkt->data,pkt->len,p1,PKT_DATA);
				}
				if(p1 && (p1->state==ST_OPENING)){
					try_to_login(p1);
				}
				continue;
			}
			else if(pkt->src==SRC_BF){
				if(p1 && (p1->state==ST_AUTH || p1->state==ST_SERVER)){
					vde_send(p1->plug,pkt->data,pkt->len,0);	
				}else if(p1 && p1->state==ST_IDSENT){
					p1->state=ST_SERVER;
				}else{

					deny_access(pkt->orig);
				}
			}
			else if(pkt->src==SRC_CTL){
				switch(pkt->data[0]){
					case CMD_LOGIN:
						if(p1 && p1->state==ST_SERVER)
							break;
						if(!p1){
							p1=malloc(sizeof(struct peer));
							bzero(p1,sizeof(struct peer));
							memcpy(&(p1->in_a),&(pkt->orig->in_a),sizeof(struct sockaddr_in));
							addpeer(p1);
							p1->state=ST_OPENING;
						}
						p1->counter=0;
						rcv_login(pkt,p1);
						break;
						
					case CMD_RESPONSE:
						if(!p1){
							p1=(struct peer*)getpeerbynewaddr(pkt->orig->in_a);
							if(p1){
							  memcpy(&p1->in_a,&pkt->orig->in_a, sizeof(struct sockaddr_in));
							  bzero(&p1->handover_a,sizeof(struct sockaddr_in));
							}
								
						}
						if(p1){
							rcv_response(pkt, p1, vde_plug);
						}
						break;
						
					case CMD_CHALLENGE:
						if(p1 && (p1->state==ST_OPENING || p1->state==ST_IDSENT)){
							rcv_challenge(pkt, p1);
						}
						break;
						
					case CMD_AUTH_OK:
						if(p1 && p1->state==ST_WAIT_AUTH){
							p1->state=ST_SERVER;
							p1->counter=0;
							//vde_plug(p1);
						}
						break;
						
					case CMD_HANDOVER:
						if(p1 && valid_handover(pkt,p1));
							handover(p1);			
						break;
						
					case CMD_IDENTIFY:
//						fprintf(stderr,"ID received...");
						p1=(struct peer*)getpeerbyid(pkt);
						if(p1){
//							fprintf(stderr,"Client is known. Sending handover.\n");
							// case 0: client changed transport address
							memcpy(&p1->handover_a,&pkt->orig->in_a, sizeof(struct sockaddr_in));
							send_handover(p1);
						}else{
							//fprintf(stderr,"Client is not known. Sending challenge.\n");
							// case 1: server restarted
							p1=malloc(sizeof(struct peer));
							bzero(p1,sizeof(struct peer));
							memcpy(&(p1->in_a),&(pkt->orig->in_a),sizeof(struct sockaddr_in));
							addpeer(p1);
							p1->state=ST_OPENING;
							p1->counter=0;
							rcv_login(pkt,p1);
						}
						break;
						
					case CMD_DENY:
						if(p1 &&  (remotehost!=NULL) ){
							p1->state=ST_OPENING;
							send_id(p1);
						}
						break;
					default:
						deny_access(pkt->orig);
				}	
				
			}
			
		}
		
	}
	
	exit (0);
}

	
	


syntax highlighted by Code2HTML, v. 0.9.1