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

/* Moved #defines out to config.h file - Terry Dawson VK2KTJ */
/* Added Rose support - Terry Dawson VK2KTJ */

#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>
#include <getopt.h>

#include "config.h"
#include "version.h"
#include "axcalluser.h"
#include "axconfig.h"

#define	PMS_VERSION	"2.0"

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];
static char my_callsign[20];
static char delimiter = '\n';

char * ReadLine(void)
{
	char *t;
	int size;
	
	while((t=memchr(InputBuffer,delimiter,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]=delimiter;
			break;
		}
	}
	
	memcpy(LineBuffer,InputBuffer,t-InputBuffer);
	LineBuffer[t-InputBuffer]=0;
	memcpy(InputBuffer,t+1,InputPtr-(t-InputBuffer)-1);
	InputPtr-=(t-InputBuffer)+1;
/*	printf("%c\"%s\"%c",delimiter,LineBuffer,delimiter);*/
	return LineBuffer;
}

void catfile(char *fname)
{
	FILE *f;
	int c;

	f = fopen(fname,"r");
	if (!f) {
		perror(fname);
	} else {
		while ((c = fgetc(f))!=EOF){
			if (c == '\n')
				c = delimiter;
			putchar(c);
		}
		fclose(f);
	}
}

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 Talk(int argc, char *argv[])
{
	system(MY_TALK);
}

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;
	char *to_username = NULL;
	
	*abuf=0;
	
	if(argc==1)
	{
		if(!IsBBS)
			printf("Send to whom ?%c",delimiter);
		else
			printf("NO%c",delimiter);
		return;
	}
	if(argc!=2 && !IsBBS)
	{
		printf("No extended information permitted.%c",delimiter);
		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.%c",delimiter);
			else
				printf("NO%c",delimiter);
		}
		distrib=strdup(argv[ct++]);
	}
	if(argv[ct] && strcmp(argv[ct],"<")==0)
	{
		ct++;
		if(argv[ct]==NULL)
		{
			if(!IsBBS)
				printf("Syntax error - missing from.%c",delimiter);
			else
				printf("NO%c",delimiter);
			free(to);
			if(distrib)
				free(distrib);
		}
		from=strdup(argv[ct++]);
	}
	if(argv[ct] && *argv[ct]=='$')
		bid=strdup(argv[ct++]);
	
	to_username = getusername(axcalluserid(to));
	if (!to_username)
	{
		free(to);
		free(bid);
		if (!IsBBS)
			printf("No third party traffic.%c",delimiter);
		else
			printf("NO%c",delimiter);
		return;
	}
	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.%c",delimiter);
	else
		printf("OK%c",delimiter);
	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.%c",delimiter);
	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++=delimiter;
	}
	/* We can now deliver it */
	to_username = getusername(axcalluserid(to));
	if(to_username) 
	{
		char deliver_command[80];
		FILE *mb;
		sprintf(deliver_command,MAIL_DELIVERY_AGENT,to_username);
		mb=popen(deliver_command,"w");
		if(mb==NULL)
		{
			printf("Unable to deliver.%c",delimiter);
			return;
		}
		/*
		 *	Now convert to approximate RFC822 format
		 */
#if defined(MY_GATEWAY)
		fprintf(mb,"From %s@%s %s",from?from:Caller,MY_GATEWAY,ctime(&tv));
#else
		fprintf(mb,"From %s %s",from?from:Caller,ctime(&tv));
#endif
		fprintf(mb,"To: %s\n",Lowercase(to));
#if defined(MY_GATEWAY)
		fprintf(mb,"From: %s@%s\n",from?from:Caller,MY_GATEWAY);
#else
		fprintf(mb,"From: %s\n",from?from:Caller);
#endif
		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,delimiter);
		 	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==delimiter)
				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,delimiter);
			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(DATA_PMS_LOGIN_FILE,"r+");
	User u;
	long pos=0L;
	long free= -1L;
	
	if(f==NULL)
	{
		perror(DATA_PMS_LOGIN_FILE);
		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.%c",delimiter);
			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(DATA_PMS_LOGIN_FILE,"r+");
	if(f==NULL)
	{
		perror(DATA_PMS_LOGIN_FILE);
		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(DATA_PMS_LOGIN_FILE,"r+");
	if(f==NULL)
	{
		perror(DATA_PMS_LOGIN_FILE);
		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(DATA_PMS_LOGIN_FILE,"r");
	if(f==NULL)
	{
		perror(DATA_PMS_LOGIN_FILE);
		return;
	}
	printf("User          Login          Idle%c",delimiter);
	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("%c",delimiter);
	}
	printf("%c",delimiter);
	fclose(f);
}

void show_ports(void)
{
	char *name = NULL;
	printf("PORT         BAUD DESCRIPTION%c",delimiter);
	for (name = ax25_config_get_next(NULL); name; 
	     name = ax25_config_get_next(name)) {
		printf("%-9s: %6d %s%c",
			name, 
			ax25_config_get_baud(name),
			ax25_config_get_desc(name),delimiter);
	}
}
#define SECONDS_PER_DAY (3600L*24L)

void show_heard(void)
{
    int d,h,m,s;
    time_t t;
    FILE *f;
    char iface[20];
    char *ifname;
    char callsign[16];
    char line[256];

    f = fopen(PROC_AX25_ROUTE_FILE,"r");
    if (f==NULL) {
	fprintf(stderr,"pms: AX.25 is not available%c",delimiter);
	return;
    }
    fgets(line,256,f);  /* Read and discard header line */
    while (!feof(f)) {
        if (fgets(line,256,f)<=0) 
		break;
	sscanf(line,"%s %s %d %ld", callsign, iface, &d, &t);
	ifname = ax25_config_get_name(iface);
        t = difftime(time(NULL),t);
	if (ifname) {
		printf("%9s: %-6s ",callsign,ifname);
        	if (t > SECONDS_PER_DAY) {
			d = t/SECONDS_PER_DAY;
			t = t%SECONDS_PER_DAY;
			if (d > 1) 
				printf("%d days, ",d);
			else
				printf("1 day, ");
		}
		h = t/3600L;
		t = t%3600L;
		m = t/60L;
		s = t%60L;
		printf("%d:%02d:%02d%c",h,m,s,delimiter);
	}
    }
    fclose(f);
}

int main(int argc, char *argv[])
{
	static char iobuf[1500];
	int paclen = 192;
	struct full_sockaddr_ax25 sax;
	int len=sizeof(sax);
	char *my_argv[32];
	int my_argc;
	int s;

	char *p=(char *)&sax.fsa_ax25.sax25_call;
	int ct=0;
	int stype = ' ';
	
	Caller[0] = '\0';
	while ((s = getopt(argc, argv, "banip:u:o:v")) != -1) {
		switch (s) {
			case 'b':
				IsBBS = 1;
			case 'a':
			case 'n':
			case 'r':
				delimiter = '\r';
			case 'i':
				stype = s;
				break;
			case 'p':
				paclen = atoi(optarg);
				if (paclen > 1500) {
					fprintf(stderr,"pms: max paclen is 1500\n");
					paclen = 1500;
				}
				break;
			case 'o':
				strcpy(my_callsign,optarg);
				break;
			case 'u':
				strcpy(Caller,optarg);
				break;
			case 'v':
				printf("pms: %s\n", version);
				return 0;
			case ':':
				fprintf(stderr,"pms: options -p, -o and -u need argument\n");
				return 1;
			case '?':
				fprintf(stderr,"Usage: pms [-a|-h|-i|-n|-r] -p paclen -u user -o owner -v\n");
				return 1;
		}
	}

	/* This is a useful trick to avoid stdio generating >mtu sized writes.
           Stdio doesn't really understand sock_seqpacket.... */
	   
	setbuffer(stdout,iobuf,paclen);
	
	if (strlen(Caller) == 0) {
		if (stype == 'a' || stype == 'n' || stype == 'r') {
			if(getpeername(0,(struct sockaddr *)&sax,&len)==-1)
			{
				perror("getpeername");
				fprintf(stderr,"pms: connection is not AX.25, NETROM or ROSE\n");
		                return 1; 
			}
	
			while(ct<7)
			{
				Caller[ct]=(*p>>1)&0x7F;
				ct++;
				p++;
			}
			Caller[6]=' ';
		} else {
			fprintf(stderr,"pms: arg -u required if not AX.25, NetRom or Rose\n");
			return 1;
		}
	}
	
	if (strlen(my_callsign) == 0) {
		fprintf(stderr,"pms: arg -o required: callsign of pms owner.\n");
		return 1;
	}

	if(strchr(Caller,' ')!=NULL)
		*strchr(Caller,' ')=0;
	
#ifdef notdef
	if(strncmp(Caller,"GB",2)==0)  /* TOTALLY BOGUS EXCEPT IN GB! */
		IsBBS=1;
#endif

	if (ax25_config_load_ports() == 0) {
		fprintf(stderr, "pms: no AX.25 ports configured\n");
		return 1;
	}

	LoginUser();
			
	printf("[GW4PTS-%s-HM$]%c",PMS_VERSION,delimiter);
	fflush(stdout);	/* Some people insist on getting this alone */

	printf("Hello %s%c",Caller,delimiter);
	catfile(CONF_PMS_MOTD_FILE);
	
	while(1)
	{
		if(IsBBS)
			printf(">%c",delimiter);
		else
			printf("%s PMS>%c",my_callsign,delimiter);
		p=ReadLine();
		if(p==NULL)
		{
			LogoutUser();
			return 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 %s%c",my_callsign,delimiter);
				LogoutUser();
				return 0;
			case 'c':
				printf("No outgoing connects.%c",delimiter);
				break;
			case 'f':
				if(IsBBS)
				{
					LogoutUser();
					return 0;
				}
				else
					printf("Forwarding is BBS only.%c",delimiter);
				break;
			case 'i':
				catfile(CONF_PMS_INFO_FILE);
				break;
			case 'p':
				show_ports();
				break;
			case 'n':
			case 'r':
				printf("NET/ROM not supported on this host.%c",delimiter);
				break;
			case 's':
				PostMessage(my_argc,my_argv);
				break;
			case 't':
				Talk(my_argc,my_argv);
				break;
			case 'u':
				UserList();
				break;
			case 'j':
				show_heard();
				break;
			default:
				printf("Unknown command '%s'%c",p,delimiter);
			case 'h':
			case '?':
				printf("Commands:%c",delimiter);
				printf("Bye, Info, Jheard, Ports, SB, Send, SP, Talk, Users, Quit%c",delimiter);
#ifdef notdef
				printf("Bye, Connect, Info, Jheard, Nodes, Ports, Route, SB, Send, SP, Talk, Users, Quit%c",delimiter);
#endif
				break;
		}
	}

	return 0;
}
