/*
 * Blowfish functions
 * Copyright © 2006 Daniele Lacamera
 * Released under the terms of GNU GPL v.2
 * 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.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#include "blowfish.h"
#include "crc32.h"

unsigned char *crc32(unsigned char*,int);
static unsigned long long mycounter=1;

static EVP_CIPHER_CTX ctx;
static int nfd = -1;
static struct peer *list=NULL;

static struct itimerval TIMER = {
	.it_interval={ .tv_sec=0, .tv_usec=0},
	.it_value={ .tv_sec=SESSION_TIMEOUT/2, .tv_usec=0 }
};

/*
 * Add a peer to the main list.
 * Client will have a list of one peer only,
 * server will have a peer in the list for each "connection"
 * it establishes.
 */

void addpeer(struct peer *np)
{
	np->next=list;
	list=np;

}

/*
 * Internal, recursive functions:
 */
static int _peers(struct peer *iter)
{
	if(!iter)
		return 0;
	else
		return 1+_peers(iter->next);
}

static void _populatepoll(struct pollfd *pfd, struct peer *iter,int index, struct peer *peerlist)
{
	int datafd;
	if(!iter)
		return;
	memcpy(&(peerlist[index]),iter,sizeof(struct peer));
	datafd=vde_datafd(iter->plug);
	if(datafd >= 0){
		pfd[index].fd=datafd;
		pfd[index++].events=POLLIN|POLLHUP;
	} else {
		
	}
	
	_populatepoll(pfd,iter->next,index, peerlist);

}


struct peer *_getpeer(struct sockaddr_in saddr, struct peer *sublist)
{
	if(!sublist)
		return NULL;
	if(sublist->in_a.sin_addr.s_addr==saddr.sin_addr.s_addr && sublist->in_a.sin_port==saddr.sin_port)
		return sublist;
	return _getpeer(saddr,sublist->next);
	
}

struct peer *_getpeerbna(struct sockaddr_in saddr, struct peer *sublist)
{
	if(!sublist)
		return NULL;
	if(sublist->handover_a.sin_addr.s_addr==saddr.sin_addr.s_addr && sublist->handover_a.sin_port==saddr.sin_port)
		return sublist;
	return _getpeerbna(saddr,sublist->next);
	
}

static struct peer *_getpeerbyid(struct datagram *pkt, struct peer *sublist)
{
	if(pkt->len!=FILENAMESIZE+1)
		return NULL;
	if(!sublist)
		return NULL;
	if(strncmp(pkt->data+1,sublist->id,FILENAMESIZE)==0)
		return sublist;
	return _getpeerbyid(pkt,sublist->next);
	
}


void 
set_expire(struct peer *p)
{
	gettimeofday(&(p->expire),NULL);
	p->expire.tv_sec+=SESSION_TIMEOUT;
}

/*
 * Check progressive number validity in incoming datagram
 */
int
isvalid_timestamp(unsigned char *block, int size, struct peer *p)
{
	
	
	int i;
	unsigned long long pktcounter=0;
	for(i=0;i<8;i++){
		pktcounter+=block[size-12+i]<<(i*8);
	}
	if(pktcounter>p->counter){
		p->counter=pktcounter;
		return 1;
	}else{
		//fprintf(stderr,"bad timestamp!\n");
		return 0;
	}
	
}

/*
 * Check CRC32 Checksum from incoming datagram
 */
int 
isvalid_crc32(unsigned char *block, int len)
{
	unsigned char *crc=(unsigned char *)crc32(block,len-4);
	if(strncmp((char*)block+(len-4),(char*)crc,4)==0)
		return 1;
	else{
			
		//fprintf(stderr,"bad crc32!\n");
		return 0;
	}
}


/*
 * Returns peer list length.
 */

static int numberofpeers(){
	struct peer *iter=list;
	return _peers(iter);
}



struct peer *clean_peerlist(struct peer *sublist)
{
	if(!sublist)
		return NULL;

	if(sublist->state == ST_AUTH && vde_datafd(sublist->plug) < 0){
		struct peer *nxt=sublist->next;
		free(sublist);
		return nxt;
	}

	if(sublist->state == ST_AUTH){
		struct timeval now;
		gettimeofday(&now,NULL);
		
		if(after(now,sublist->expire)){
			struct peer *nxt=sublist->next;
			vde_close(sublist->plug);
			free(sublist);
			return nxt;
		}
	}

	sublist->next=clean_peerlist(sublist->next);
	return sublist;
}


void
autocleaner(int signo)
{
	struct itimerval *old=NULL;
	list=clean_peerlist(list);
	setitimer(ITIMER_REAL, &TIMER, old);
}

/*
 * Returns a list of all the peer in the peer list, adding their 
 * network socket to pollfd.
 * This is called in blowfish_select, to populate the pollfd structure.
 */
static struct peer *populate_peerlist(struct pollfd *pfd)
{
	struct peer *iter, *peerlist;
	iter=list; //=clean_peerlist(list);

	peerlist=(struct peer *) malloc( (numberofpeers()+1)*sizeof(struct peer) );
	_populatepoll(pfd,iter,1,peerlist);

	return peerlist;
}

/*
 * Get a pointer to the peer in the list which has the given udp address.
 */
struct peer *getpeer(struct sockaddr_in saddr)
{
	struct peer *iter=list;
	return (_getpeer(saddr,iter));
			
}

struct peer *getpeerbynewaddr(struct sockaddr_in saddr)
{
	struct peer *iter=list;
	return (_getpeerbna(saddr,iter));
			
}


/*
 * Get a pointer to the peer in the list which key filename is the same of that in the login datagram. 
 */
struct peer *getpeerbyid(struct datagram *pkt)
{
	struct peer *iter=list;
	return (_getpeerbyid(pkt,iter));
			
}

/*
 * Send a plain "access denied" message to the specified peer.
 */
void
deny_access(struct peer *p)
{
	send_udp("Access Denied.\0",15,p,CMD_DENY);
}

/*
 * Main select routine.
 * A poll will wake up whenever a new packet is available to read, either from one 
 * of the vde_plug attached, or from udp socket.
 * Returns a struct datagram aware of its own source.
 * Also discriminate commands from data, by first byte.
 */
struct datagram *blowfish_select (int timeout)
{
   unsigned peerlen;
   int pollret;
   struct pollfd *pfd;
   static struct datagram *ret = NULL;
   struct peer *peerlist;
   static int i=1;
   
   pfd=malloc((1+numberofpeers())*sizeof(struct pollfd));
   
   pfd[0].fd=nfd;
   pfd[0].events=POLLIN|POLLHUP;
   peerlist = populate_peerlist(pfd);

  do{
	  pollret=poll(pfd,1+numberofpeers(),1000);
	  if(pollret<0){
		  if(errno==EINTR)
			  return NULL;
		  perror("poll");
		  exit(1);
	  }
	  if (!ret){
		  ret = malloc(sizeof(struct datagram));
		  bzero(ret,sizeof(struct datagram));
		  ret->orig = malloc(sizeof (struct peer));
		  bzero(ret->orig,sizeof(struct peer));
	  }
  } while (pollret==0);

  
for(;;){
  if (pfd[0].revents&POLLIN) {
	unsigned char inpkt[MAXPKT];
	unsigned char *inbuff=inpkt+1;
	int ilen,tlen;
	struct sockaddr_in ipaddress;
	peerlen = sizeof(struct sockaddr_in);
	ilen = recvfrom(nfd, inpkt, MAXPKT, 0,
		(struct sockaddr *) &ipaddress, &peerlen);
  
        ret->orig=getpeer(ipaddress);
	if(!ret->orig){
		ret->orig=malloc(sizeof(struct peer));
	  	bzero(ret->orig,sizeof(struct peer));
		ret->orig->in_a.sin_family = AF_INET;
		ret->orig->in_a.sin_port = ipaddress.sin_port;
		ret->orig->in_a.sin_addr.s_addr= ipaddress.sin_addr.s_addr;
		ret->orig->state=ST_CLOSED;
	}
	
		
	if((inpkt[0]==PKT_DATA)&&	  
		 (ret->orig->state==ST_AUTH || ret->orig->state==ST_SERVER))
	{
		
		ret->src = SRC_BF;
		ilen--;
		
		EVP_DecryptInit (&ctx, EVP_bf_cbc (), ret->orig->key, ret->orig->iv);
		 
		if (EVP_DecryptUpdate (&ctx, ret->data, &ret->len, inbuff, ilen) != 1)
		  {
			fprintf (stderr,"error in decrypt update\n");
			return NULL;
		  }
		if (EVP_DecryptFinal (&ctx, ret->data + ret->len, &tlen) != 1)
		  {
			fprintf (stderr,"error in decrypt final\n");
			return NULL;
		  }
		  ret->len += tlen;
		  if( isvalid_crc32(ret->data,ret->len) && isvalid_timestamp(ret->data,ret->len,ret->orig) ){
			ret->len-=12;
			if(ret->orig->state==ST_AUTH)
				set_expire(ret->orig);
			return ret;
		}else{		
//			deny_access(ret->orig);
			return NULL;
		}
	}else if((inpkt[0]==CMD_HANDOVER)){
		ret->src = SRC_CTL;
		ilen--;
		
		//fprintf (stderr,"Recived Handover datagram.:   ");
		EVP_DecryptInit (&ctx, EVP_bf_cbc (), ret->orig->key, ret->orig->iv);
		 
		if (EVP_DecryptUpdate (&ctx, ret->data+1, &ret->len, inbuff, ilen) != 1)
		  {
			fprintf (stderr,"error in decrypt update\n");
			return NULL;
		  }
		if (EVP_DecryptFinal (&ctx, ret->data + 1 + ret->len, &tlen) != 1)
		  {
			fprintf (stderr,"error in decrypt final\n");
			return NULL;
		  }
		  ret->len += tlen;
		  ret->len +=1;
		  if( isvalid_crc32(ret->data+1,ret->len-1) && isvalid_timestamp(ret->data,ret->len,ret->orig) ){
			ret->len-=12;
			ret->data[0]=CMD_HANDOVER;
			//fprintf (stderr,"Valid Handover. Resending key.\n");
			return ret;
		}else{		
			fprintf (stderr,"Invalid Handover packet. crc32?%d, timestamp?%d \n",isvalid_crc32(ret->data+1,ret->len-1), isvalid_timestamp(ret->data,ret->len,ret->orig));
			deny_access(ret->orig);
			return NULL;
		}
		
  	}else if(inpkt[0]&PKT_CTL){
		ret->src = SRC_CTL;
		memcpy(ret->data,inpkt,ilen);
		ret->len=ilen;
		return ret;
	}else{	
	deny_access(ret->orig);
	return NULL;
			
	
	}
	
  }

  // This increment comes with "static int i" def, to ensure fairness among peers.
  i++;	  
  if(i>numberofpeers())
	  i=1;
  
  if (pfd[i].revents&POLLIN) {
/*	  c=read(pfd[i].fd,ret->data,2);
	  if(c<2) 
		  return NULL;
	  vde_len=0;
	  vde_len+=((unsigned char)(ret->data[0]))<<8;
	  vde_len+=(unsigned char)(ret->data[1]);
	  
	  ret->len=2;
	  while(ret->len < (vde_len + 2)){
		ret->len += read(pfd[i].fd, ret->data+ret->len, ((vde_len+2) - ret->len));
	  }
	// fprintf(stderr,"Read %d.\n",vde_len);
	  
*/ 
	ret->len = vde_recv(peerlist[i].plug, ret->data, MAXPKT,0);
	
	if(ret->len<1)
		return NULL;
	
	ret->src = SRC_VDE;
	ret->orig = &(peerlist[i]);

	//set_expire(&(peerlist[i]));
	  
	  
	return ret;
		
  }
// list=clean_peerlist(list); 
}
  return NULL;
}


/*
 * Send a virtual frame to the vde_plug process associated 
 * with the peer
 */
void
send_vdeplug(const char *data, size_t len, struct peer *p)
{
	static unsigned int outbuf[MAXPKT];
	static int outp=0;
	static u_int16_t outlen;
	if(len<=0)
		return;
	
	if(outp==0 && (len >=2) ){
		outlen=2;
		outlen+=(unsigned char)data[1];
		outlen+=((unsigned char)(data[0]))<<8;
	}
	
	if(len>=outlen){
		vde_send(p->plug,data,outlen,0);
		send_vdeplug(data+outlen,len-outlen, p);
		return;
	}
		
	memcpy(outbuf+outp,data,len);
	outp+=len;
	if(outp>=outlen){
		vde_send(p->plug,(char *)outbuf,outlen,0);
	}			
}

/*
 * Include a progressive number into outgoing datagram,
 * to prevent packet replication/injection attack.
 * 
 */
void
set_timestamp(unsigned char *block)
{
	int i;
	for(i=0;i<8;i++){
		block[i]=(unsigned char)(mycounter>>(i*8))&(0x00000000000000FF);
	}
	mycounter++;
	
		
}


/*
 * Send an udp datagram to specified peer.
 */
void
send_udp (char *data, size_t len, struct peer *p, unsigned char flags)
{
		  
	unsigned char outpkt[MAXPKT];
	unsigned char *outbuf=outpkt+1;
	int olen,tlen;
	struct sockaddr_in *destination=&(p->in_a);
	if(flags==CMD_CHALLENGE || flags==CMD_LOGIN || flags==CMD_DENY || flags==CMD_AUTH_OK || flags==CMD_IDENTIFY){
		memcpy(outbuf,data,len);
		olen=len;
	}else{
		if(flags==PKT_DATA||flags==CMD_HANDOVER){
			set_timestamp(data+len);
			len+=8;
			
			memcpy(data+len,crc32(data,len),4);
			len+=4;
			
		}
		if(flags==CMD_HANDOVER){

			destination=&(p->handover_a);
		}
		
		EVP_EncryptInit (&ctx, EVP_bf_cbc (), p->key, p->iv);
		  if (EVP_EncryptUpdate (&ctx, outbuf, &olen, data, len) != 1)
		    {
			    fprintf (stderr,"error in encrypt update\n");
			    return;
		    }
	
		 
		  if (EVP_EncryptFinal (&ctx, outbuf + olen, &tlen) != 1)
		    {
			    fprintf (stderr,"error in encrypt final\n");
			    return;
		    }
		olen += tlen;
	}
	
	outpkt[0]=flags;
	sendto(nfd, outpkt, olen+1, 0, (struct sockaddr *) destination,
	    	sizeof(struct sockaddr_in));
}

/*
 * Generate a new blowfish key, store it in a local file and fill the fields
 * of peer structure.
 * Client only.
 */
struct peer
*generate_key (struct peer *ret)
{
	int i, fd=-1, od=-1, createnow=0;
	unsigned char key[16];
	unsigned char iv[8];
	unsigned char c;
	
	if(!ret){
		ret=malloc(sizeof(struct peer));
		bzero(ret,sizeof(struct peer));
		createnow=1;
	}
	if ( ((fd = open ("/dev/random", O_RDONLY)) == -1)||
			 ((read (fd, key, 16)) == -1) ||
			 ((read (fd, iv, 8)) == -1) )
	{

		perror ("Error Creating key.\n");
		goto failure;
	}
	
	//fprintf(stderr,"128 bit key stored.\n");

	//fprintf(stderr,"64 bit Initialization vector stored.\n");
	
	for(i=0; i<FILENAMESIZE-1;i++){
		read(fd,&c,1);
		c=(c%25);
		//fprintf(stderr,"c=%u\n",c);
		ret->id[i]=(char)('a' + c);
	}
	ret->id[FILENAMESIZE-1]='\0';
	
	close (fd);
	
	if ((od = creat ("/tmp/.blowfish.key",0600)) == -1){
		perror ("blowfish.key creat error");
		goto failure;
	}
	memcpy(ret->key,key,16);
	memcpy(ret->iv,iv,8);
	write(od,key,16);
	write(od,iv,8);
	close (od);
	return ret;
	
failure:
	if (createnow)
		free(ret);
	if (fd != -1)
		close(fd);
	if (od != -1)
		close(od);
	return NULL;
}


/*
 * Send a "Challenge" 4WHS packet.
 */
static void
send_challenge(struct peer *p)
{
	int fd;
	if ( ((fd = open ("/dev/random", O_RDONLY)) == -1)||
			 ((read (fd, p->challenge, 128)) != -1))
	{	
		send_udp(p->challenge,128,p,PKT_CTL|CMD_CHALLENGE);
	}		
	p->state=ST_CHALLENGE;
	close(fd);
}

/*
 * Send a "Auth OK" 4WHS packet.
 */
static void
send_auth_ok(struct peer *p, void (*callback)(struct peer*))
{
	send_udp(NULL,0,p,CMD_AUTH_OK);
	p->state=ST_AUTH;
	callback(p);
	set_expire(p);
}

/*
 * Receive a challenge. Try to send response encrypted with local blowfish key.
 */
void 
rcv_challenge(struct datagram *pkt, struct peer *p)
{
	send_udp(pkt->data+1,pkt->len-1,p,CMD_RESPONSE);
	p->state=ST_WAIT_AUTH;
}


/*
 * Receive a login request. Send challenge.
 */
void
rcv_login(struct datagram *pkt, struct peer *p)
{
	int fd;
	char filename[128];
	snprintf(filename,127,"/tmp/.%s.key\0",pkt->data+1);
//	fprintf(stderr,"Filename:%s\n",filename);
	if (((fd = open (filename, O_RDONLY)) == -1)||
 			((read (fd, p->key, 16)) == -1) ||
			((read (fd, p->iv, 8)) == -1) ){
		perror ("blowfish.key open error");
		deny_access(p);
		return;
	}
	close(fd);
	memcpy(p->id,pkt->data+1,FILENAMESIZE);
	send_challenge(p);

}

/*
 * Receive a response from challenge. Validate encryption and send "ok auth"
 * or "access denied"
 */
void
rcv_response(struct datagram *pkt, struct peer *p, void (*callback)(struct peer*))
{
	unsigned char response[MAXPKT];
	int rlen, tlen;
	
	EVP_DecryptInit (&ctx, EVP_bf_cbc (), p->key, p->iv);
	 
	if (EVP_DecryptUpdate (&ctx, response, &rlen, pkt->data+1, pkt->len-1) != 1)
	  {
		fprintf (stderr,"error in decrypt update\n");
		return;
	  }

	  if (EVP_DecryptFinal (&ctx, response + rlen, &tlen) != 1)
	  {
		fprintf (stderr,"error in decrypt final\n");
		return;
	  }

	  if (strncmp(response,p->challenge,128)==0){
		  p->state=ST_AUTH;
		  send_auth_ok(p, callback);
	  }
		  
	  else{
		  p->state=ST_CLOSED;
		  deny_access(p);
	  }

}

/*
 * Send a login packet. This is the first phase of 4WHS
 */
void
blowfish_login(struct peer *p)
{
	send_udp(p->id,FILENAMESIZE,p,CMD_LOGIN);
}

/*
 * Initialize blowfish module.
 * Set udp socket and initialize crypto engine & CRC32.
 */
void
blowfish_init(int socketfd)
{
	nfd=socketfd;
	EVP_CIPHER_CTX_init (&ctx);	
	chksum_crc32gentab ();
}




syntax highlighted by Code2HTML, v. 0.9.1