#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <pwd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <signal.h>
#include <linux/ax25.h>

#define USERLIST	"/usr/local/pms/loggedin"
#define USERFILE	"/usr/local/pms/users"
#define TALKDIR		"/usr/local/pms/talk.pm/pipe.%d"
#define MSGHEAD		"/usr/local/pms/msg.head"
#define MSGBODY		"/usr/local/pms/msg.body"
#define MY_CALLSIGN	"GW4PTS"

/*
 *	Simple PMS service for Linux AX.25 kernel
 *
 */

extern void LogoutUser(void);

static char Caller[10];
static int  IsBBS=0;
static long CallerPos= -1L;
 
static char InputBuffer[256];
static int InputPtr=0;
static int Socket;
static char LineBuffer[256];

char * ReadLine(void)
{
	char *t;
	int size;
	
	while((t=memchr(InputBuffer,'\r',InputPtr))==NULL)
	{
		fflush(stdout);
		size=read(Socket,InputBuffer+InputPtr,256-InputPtr);
		if(size==-1 || size==0)
			return NULL;
		InputPtr+=size;
		if(size>=256)
		{
			InputBuffer[255]='\r';
			break;
		}
	}
	
	memcpy(LineBuffer,InputBuffer,t-InputBuffer);
	LineBuffer[t-InputBuffer]=0;
	memcpy(InputBuffer,t+1,InputPtr-(t-InputBuffer)-1);
	InputPtr-=(t-InputBuffer)+1;
/*	printf("\n\"%s\"\n",LineBuffer);*/
	return LineBuffer;
}

int ParseArgs(char *argv[],char *cmd)
{
	int ct=0;
	while(ct<31)
	{
		while(*cmd && isspace(*cmd))
			cmd++;
		if(*cmd==0)
		{
			argv[ct]=NULL;
			return(ct);
		}
		argv[ct++]=cmd;
		while(*cmd && !isspace(*cmd))
		{
			cmd++;
		}
		if(*cmd)
			*cmd++=0;
	}
	argv[ct]=NULL;
	return(ct);
}

static int ReadPair(char *p)
{
	if(!isdigit(*p)&&!isdigit(p[1]))
		return -1;
	return ((*p-'0')*10+p[1]-'0');
}

static char DayName[]="Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
static char MonName[]="Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec";

void AddReceivedLine(FILE *f,char *in, char *addr,char *date)
{
	char *adrp;
	char *adbuf=addr;
	char *comment=NULL;
	char *id=NULL;
	int y,m,d,hr,min;
	in+=2;	/* skip R: */
	/* We now have a data - occasionally without the trailing 'Z' or 'z'! */
	if(strlen(in)>=11)
	{
		struct tm t;
		struct tm *tp;
		long tv;
		y=ReadPair(in);
		in+=2;
		m=ReadPair(in);
		in+=2;
		d=ReadPair(in);
		in+=2;
		if(*in++!='/')
			d= -1;
		hr=ReadPair(in);
		min=ReadPair(in);
		if(y<0||m<1||m>12||d<1||d>31||hr<0||hr>23||min<0||min>59)
		{
			*addr=0;
			fprintf(f,"X-Unparseable-R-Line: %s\n",in);
			return;
		}
		t.tm_year=y;
		t.tm_mday=d;
		t.tm_mon=m-1;
		t.tm_hour=hr;
		t.tm_min=min;
		t.tm_sec=0;
		t.tm_isdst=0;
		if((tv=mktime(&t))==-1)
		{
			strcpy(date,"[Unparseable]");
		}
		else
		{
			tv-=timezone;
			tp=gmtime(&tv);
			sprintf(date,"%s, %d %s %2d:%2d:00 GMT",
				&DayName[tp->tm_wday*4],
				tp->tm_mday,
				&MonName[tp->tm_mon*4],
				tp->tm_hour,
				tp->tm_min
			);
		}
			
	}
	else
	{
		fprintf(f,"X-Short-R-Line: %s\n",in);
		*addr=0;
	}
	/* Now look for '@' */
	while(*in && isspace(*in))
		in++;
	
	/* Check for the funny NNA idents */
	
	if(isdigit(*in))
	{
		id=in;
		while(*in && isdigit(*in))
			in++;
		if(*in&&*in!='@')
			*in++=0;
	}
	/* Now look for addesses. This might be in the ID@BBS form used by NNA or the
	   @:bbs used by the rest */
	   
	adrp=strchr(in,'@');
	if(adrp==NULL)
	{
		*addr=0;
		return;
	}

	adrp++;	
	if(*adrp==':')
		adrp++;
		
	while(*adrp && !isspace(*adrp))
		*addr++=*adrp++;
	*addr=0;
	comment=strchr(adrp,'[');
	if(comment!=NULL)
	{
		adrp=strchr(comment,']');
		if(adrp!=NULL)
			*adrp=0;
	}

	/* Look for the normal #: lines. Don't look for BID: or $: lines - few use them and everyone puts
	   them in differently */
	   
	if(id==NULL && adrp!=NULL)
	{
		id=strchr(adrp,'#');
		if(id!=NULL)
		{
			adrp=id;
			while(*adrp&&!isspace(*adrp))
				adrp++;
			if(*adrp)
				*adrp=0;
		}
	}
	/* Now format up the received stuff */
	fprintf(f,"Received: by %s (%s)\n\tid %s; %s\n",adbuf,comment?comment+1:"no info",id?id:"no-ident",date);
}		
	
	
char *Lowercase(char *x)
{
	char *p=x;
	while(*p)
	{
		if(isupper(*p))
			*p=tolower(*p);
		p++;
	}
	return x;
}

void PostMessage(int argc,char *argv[])
{
	char *subj;
	char *msg,*mptr;
	int msgsize;
	int mused;
	char *p;
	char *to=NULL;
	char *distrib=NULL;
	char *from=NULL;
	char *bid=NULL;
	int len;
	char abuf[64];	/* Will hold last R: line data for Reply-To: line */
	char adate[64];
	int ct=1;
	long tv;
	struct tm *tp;
	
	*abuf=0;
	
	if(argc==1)
	{
		if(!IsBBS)
			printf("Send to whom ?\r");
		else
			printf("NO\r");
		return;
	}
	if(argc!=2 && !IsBBS)
	{
		printf("No extended information permitted.\r");
		return;
	}
	if(!IsBBS && strcasecmp(argv[1],MY_CALLSIGN)!=0)
	{
		printf("No third party traffic.\r");
		return;
	}
	to=strdup(argv[ct++]);
	if(argv[ct] && strcmp(argv[ct],"@")==0)
	{
		ct++;
		if(argv[ct]==NULL)
		{
			free(to);
			if(!IsBBS)
				printf("Syntax error - missing distribution.\r");
			else
				printf("NO\r");
		}
		distrib=strdup(argv[ct++]);
	}
	if(argv[ct] && strcmp(argv[ct],"<")==0)
	{
		ct++;
		if(argv[ct]==NULL)
		{
			if(!IsBBS)
				printf("Syntax error - missing from.\r");
			else
				printf("NO\r");
			free(to);
			if(distrib)
				free(distrib);
		}
		from=strdup(argv[ct++]);
	}
	if(*argv[ct]=='$')
		bid=strdup(argv[ct++]);
	
	time(&tv);	
	tp=gmtime(&tv);
	sprintf(adate,"%s, %d %s %2d:%2d:00 GMT",
	&DayName[tp->tm_wday*4],
		tp->tm_mday,
		&MonName[tp->tm_mon*4],
		tp->tm_hour,
		tp->tm_min);
		
	/*
	 *	Ok it parses now do the message
	 */
	 
	if(!IsBBS)
		printf("Enter the subject for the message.\r");
	else
		printf("OK\r");
	subj=ReadLine();
	if(subj==NULL)
	{
		LogoutUser();
		exit(1);
	}
	subj=strdup(subj);
	if(!IsBBS)
		printf("Enter your message. End with '/EX' on a line of its own.\r");
	msg=malloc(1024);
	msgsize=1024;
	mptr=msg;
	mused=0;
	while(1)
	{
		p=ReadLine();
		if(p==NULL)
		{
			LogoutUser();
			exit(1);
		}
		if(strcasecmp(p,"/EX")==0 || strcasecmp(p,"\032")==0)
		{
			*mptr++=0;
			break;
		}
		len=strlen(p);
		if(len+mused>=msgsize-3)
		{
			msgsize+=1024;
			msg=realloc(msg,msgsize);
			mptr=&msg[mused];
		}
		if(strncmp(mptr,"From",4)==0)
		{
			mused++;
			*mptr++='>';
		}
		strcpy(mptr,p);
		mptr+=len;
		mused+=len+1;
		*mptr++='\r';
	}
	/* We can now deliver it */
/*	if(strcasecmp(to,"GW4PTS")==0)*/
	{
		FILE *mb=fopen("/usr/spool/mail/gw4pts","a");
		if(mb==NULL)
		{
			printf("Unable to deliver.\r");
			return;
		}
		chown("/usr/spool/mail/gw4pts",103,7);
		chmod("/usr/spool/mail/gw4pts",0770);
		/*
		 *	Now convert to approximate RFC822 format
		 */
		fprintf(mb,"From %s %s",from?from:Caller,ctime(&tv));
		fprintf(mb,"To: %s\n",Lowercase(to));
		fprintf(mb,"From: %s\n",from?from:Caller);
		if(bid)
			fprintf(mb,"Message-ID: <%s>\n",bid);
		fprintf(mb,"Subject: %s\n", subj);
		/*
		 *	Now walk the message headers
		 *	generating RFC822 received lines
		 *	(and figuring the return address)
		 */
		 p=msg;
		 while(p && *p)
		 {
		 	mptr=strchr(p,'\r');
		 	if(strncmp(p,"R:",2)==0)
		 	{
		 		if(mptr!=NULL)
		 			*mptr++=0;
		 		AddReceivedLine(mb,p,abuf,adate);
		 		p=mptr;
				continue;
			}
			/* Blank line is a seperator */
			if(*p=='\r')
				break;
			if(!IsBBS)
				break;
			if(mptr!=NULL)
			{
				*mptr++=0;
			}
			fprintf(mb,"X-%s\n",p);
			p=mptr;
		}
		if(*abuf)
		{
			fprintf(mb,"Reply-To: <%s@%s>\n",from?from:Caller,abuf);
			fprintf(mb,"Return-Path: <%s@%s>\n",from?from:Caller,abuf);
		}
		fprintf(mb,"Date: %s\n",adate);

		/* Now we are onto the data part (if one exists) */
		fprintf(mb,"\n");	/* End envelope */
		/* Now dump the data part of the message */
		while(p && *p)
		{
			mptr=strchr(p,'\r');
			if(mptr!=NULL)
				*mptr++=0;
			fprintf(mb,"%s\n",p);
			p=mptr;
		}
		fclose(mb);
	}
	/*
	 *	Else its a news article
	 */
/*	else
	{
		;
	}*/
	free(to);
	free(subj);
	if(bid)
		free(bid);
	free(msg);
	if(from)
		free(from);
	if(distrib)
		free(distrib);
}

typedef struct
{
	char Call[12];
	int pid;
	int flags;
#define USER_IS_BBS 1
	long login;
	long cmdtime;
		
	char Spare[36];
} User;

void LoginUser(void)
{
	FILE *f=fopen(USERLIST,"r+");
	User u;
	long pos=0L;
	long free= -1L;
	
	if(f==NULL)
	{
		perror(USERLIST);
		return;
	}
	
	if(flock(fileno(f),LOCK_EX)==-1)
	{
		perror("flock");
		return;
	}
	
	while(fread(&u,sizeof(u),1,f)==1)
	{
		if(u.pid==-1)
			free=pos;
		if(kill(u.pid,0)==0 || errno!=ESRCH)
			free=pos;
		else if(strcmp(Caller,u.Call)==0)
		{
			fclose(f);
			printf("You are already connected.\n");
			exit(1);
		}
		pos+=sizeof(u);
	}
	if(free!= -1L)
	{
		if(fseek(f,free,0L)==-1)
		{
			perror("fseek");
			exit(1);
		}
	}
	strcpy(u.Call,Caller);
	u.pid=getpid();
	u.flags=0;
	if(IsBBS)
		u.flags|=USER_IS_BBS;
	time(&u.login);
	time(&u.cmdtime);
	fflush(f);
	CallerPos=ftell(f);	
	fwrite(&u,sizeof(u),1,f);
	fflush(f);
	flock(fileno(f),LOCK_UN);
	fclose(f);
}

void LogoutUser(void)
{
	User u;
	FILE *f=fopen(USERLIST,"r+");
	if(f==NULL)
	{
		perror(USERLIST);
		exit(1);
	}
	if(fseek(f,CallerPos,0)==-1)
	{
		perror("fseek");
		exit(1);
	}
	u.pid= -1;
	fwrite(&u,sizeof(User),1,f);
	fclose(f);
}

void UpdateUser(void)
{
	User u;
	FILE *f=fopen(USERLIST,"r+");
	if(f==NULL)
	{
		perror(USERLIST);
		exit(1);
	}
	if(fseek(f,CallerPos,0)==-1)
	{
		perror("fseek");
		exit(1);
	}
	if(fread(&u,sizeof(User),1,f)!=1)
	{
		perror("fread");
		exit(1);
	}
	time(&u.cmdtime);
	if(fseek(f,CallerPos,0)==-1)
	{
		perror("fseek");
		exit(1);
	}
	fwrite(&u,sizeof(User),1,f);
	fclose(f);
}

void UserList(void)
{
	User u;
	char *tp;
	long l;
	FILE *f=fopen(USERLIST,"r");
	if(f==NULL)
	{
		perror(USERLIST);
		return;
	}
	printf("User          Login          Idle\r");
	while(fread(&u,sizeof(User),1,f)==1)
	{
		if(u.pid==-1 || kill(u.pid,0)!=0)
			continue;
		printf("%-14s",u.Call);
		tp=ctime(&u.login);
		tp+=4;
		tp[12]=0;
		printf("%s  ",tp);
		time(&l);
		l-=u.cmdtime;
		if(l<60)
			printf("%-2lds  ",l);
		else
		{
			l/=60;
			if(l<60)
				printf("%-2ldm  ",l);
			else
			{
				l/=60;
				if(l<24)
					printf("%-2ldh  ",l);
				else
					printf("%-2ldd  ",l/24);
			}
		}
		if(u.flags&USER_IS_BBS)
			printf("[Mail forward]");
		printf("\r");
	}
	printf("\r");
	fclose(f);
}

void main(int argc,char *argv[])
{
	static char iobuf[192];
	struct full_sockaddr_ax25 sax;
	int len=sizeof(sax);
	char *my_argv[32];
	int my_argc;
	
	char *p=(char *)&sax.fsa_ax25.sax25_call;
	int ct=0;
	
	/* This is a useful trick to avoid stdio generating >mtu sized writes. Stdio
	   doesn't really understand sock_seqpacket.... */
	   
	setbuffer(stdout,iobuf,192);
	
	if(getpeername(0,(struct sockaddr *)&sax,&len)==-1)
	{
		perror("getpeername");
/*                exit(1);*/
	}
	
	while(ct<7)
	{
		Caller[ct]=(*p>>1)&0x7F;
		ct++;
		p++;
	}
	Caller[6]=' ';
	
	if(strchr(Caller,' ')!=NULL)
		*strchr(Caller,' ')=0;
	
	if(strncmp(Caller,"GB",2)==0)
		IsBBS=1;

	LoginUser();
			
	printf("[GW4PTS-1.00-HM$]\r");
	fflush(stdout);	/* Some people insist on getting this alone */

	printf("Hello %s\r",Caller);
	printf("I'm still writing this so a lot of it doesn't yet work\r");
	printf("Please report any oddities found\r");
	
	while(1)
	{
		if(IsBBS)
			printf(">\r");
		else
			printf("GW4PTS PMS>\r");
		p=ReadLine();
		if(p==NULL)
		{
			LogoutUser();
			exit(1);
		}
		UpdateUser();
		my_argc=ParseArgs(my_argv,p);
		if(my_argv[0]==NULL)
			continue;
		switch(tolower(*my_argv[0]))
		{
			case 'b':
			case 'q':
				printf("73 de GW4PTS\r");
				LogoutUser();
				exit(0);
			case 'c':
				printf("No outgoing connects.\n");
				break;
			case 'f':
				if(IsBBS)
				{
					LogoutUser();
					exit(0);
				}
				else
					printf("Forwarding is BBS only.\n");
				break;
			case 'i':
				printf("\
Software: GW4PTS PMS for Linux v1.01\r\
System:   Linux 0.99.15e, Kernel AX.25 0.12 ALPHA\r");
				printf("\
Radio:    PK88 in KISS TNC mode, a 25W Navico radio on 144.650 and a colinear\r");
				break;
			case 'p':
				printf("Port 1      144.650      25W\r");
				break;
			case 'n':
			case 'r':
				printf("NetROM not supported on this host.\n");
				break;
			case 's':
				PostMessage(my_argc,my_argv);
				break;
			case 't':
				printf("Not yet working.\r");
/*				TalkMode();*/
				break;
			case 'u':
				UserList();
				break;
			case 'h':
			case '?':
				printf("Commands:\r");
				printf("Bye, Connect, Info, Nodes, Ports, Route, SB, Send, SP, Talk, Users, Quit\r");
				break;
			default:
				printf("Unknown command '%s'\r",p);
				break;
		}
	}
}
