/* BeroList 2.0.0                                                 */
/* An easy-to-use mailing list server                             */
/* (c) 1996 by Bernhard Rosenkraenzer <root@startrek.in-trier.de> */
/* Fixed by Jarno Paananen <jpaana@kalahari.ton.tut.fi>           */
/*          Stefano Torricella <stetor@ronchiato.it>              */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
#include "socket.h"
#ifndef HOST
	#include <unistd.h>
  	char *hostname;
  	#define HOST hostname
  	#define GET_HOST
#endif
#ifdef LOG
	FILE *log;
#endif
#define VALIDCHAR "\t\n\r\b\000"
char *listaddress;

/* Function prototypes... */

char *extract(char *from, const char *begin, const char end);
char *readfile(char *filename);
char *readline(FILE *file);
void send(char *list, char *to, char *from, char *subject, char *message, char *footfile);
void sendtolist(char *listname, char *from, char *subject, char *message);
void subscribe(char *listname, char *user, char *operator);
void unsubscribe(char *listname, char *user, char *operator);
char *email(char *full_address);
char *realname(char *full_address);
char *username(char *address);
char *downcase(char *s);
int scasecmp(char *s1, char *s2); /* Fix a buggy implementation of strcasecmp */

/* Functions... */

char *extract(char *from, const char *begin, const char end)
{
  char *found,*result;

  found=strstr(from,begin);

  while(found){
    if(strchr(VALIDCHAR,(int) (*(found-1))) || !(*(found-1))) {
          result=(char *) malloc(strlen(from) + 1);
          strcpy(result,(char *) found+strlen(begin));
          if((char *) strchr(result,end)!=NULL)
                  result[strchr(result,end)-result]='\0';
          if((char *) strchr(result,'\n')!=NULL)
                  result[strchr(result,'\n')-result]='\0';
          return(result);
    };
    found=strstr(found+1,begin);
  };
  return(NULL);
}

char *readfile(char *filename)
{
  FILE *in;
  char *content;
  register int size;
  register int flen;

  size=1024; flen=0;
  in=fopen(filename,"r");
  if(in!=NULL) {
	content=(char *) malloc(sizeof(char)*(size+1));
	while(!feof(in)) {
	  	content[flen]=(char) fgetc(in);
	  	if(flen==size) {
	  		content[flen+1]='\0';
  			size+=2048;
			content=(char *) realloc(content, sizeof(char)*(size+1));
		}
		flen++;
	}
	content[flen-1]='\0';
	fclose(in);
	return(content);
  } else return(NULL);
}

char *readline(FILE *file)
{
  char *line;
  register char last;
  register int len;
  
  line=(char *) malloc(sizeof(char)*1024);
  last=0; len=0;
  while(!feof(file) && last!=EOF && last!='\n') {
  	last=(char) fgetc(file);
  	if(last!='\n' && !feof(file)) {
  		line[len]=last;
  		len++;
  	}
  }
  line[len]='\0';
  return(line);
}  

void send(char *list, char *to, char *from, char *subject, char *message, char *footfile)
{
  char *footer;
  #ifdef USE_SENDMAIL
	FILE *mailer;
	char *mailer_cmd;
  #else
  	int socket;
  	char *server_reply;
  #endif
  
  printf("Sending...\n");
  #ifdef USE_SENDMAIL     /* USE_SENDMAIL: Use the 1.x.x way */
	#ifdef TRUSTED
		from=email(from);
		mailer_cmd=(char *) malloc(strlen(SENDMAIL)+strlen(from)+strlen(to)+7);
		sprintf(mailer_cmd,"%s -f%s '%s'",SENDMAIL,from,to);
		#ifdef LOG
	  		fprintf(log,"* %s\n",mailer_cmd);
	  	#endif
	  	mailer=popen(mailer_cmd,"w");
	#else
		mailer_cmd=(char *) malloc(strlen(SENDMAIL)+strlen(to)+4);
		sprintf(mailer_cmd,"%s '%s'",SENDMAIL,to); 
		#ifdef LOG
			fprintf(log,"* %s\n",mailer_cmd);
		#endif
		mailer=popen(mailer_cmd,"w");
		fprintf(mailer,"From: %s\n",from);
	#endif
	if(subject==NULL && strncasecmp(message,"subject:",8)==0) {
		/* Subject: line @ beginning of msg body... */
		subject=(char *) malloc(strlen(message)-7);
		strcpy(subject,message+8);
		while(strchr(subject,' ')==subject) subject++;
		subject[strchr(subject,' ')-subject]='\0';
		message=strchr(message,'\n')+1;
	}
	if(subject!=NULL)	fprintf(mailer,"Subject: %s\n",subject);
	fprintf(mailer,"\n%s\n",message);
	footer=readfile(footfile);
	if(footer!=NULL)	fprintf(mailer,"%s\n",footer);
	fprintf(mailer,".\n");
	pclose(mailer);
  #else
  	server_reply=(char *) malloc(4096);  /* I know, 4k might be exaggerated... */
  	socket=Socket(SMTP_SERVER,25);
  	if(socket<=0) {
  		printf("Can't connect to server %s.\n",SMTP_SERVER);
  		exit(1);
  	}
  	printf("Connected to %s.\n",SMTP_SERVER);
  	SockGets(socket,server_reply,4096);
  	puts(server_reply);
  	SockPrintf(socket,"HELO %s\r\n",HOST);
  	SockGets(socket,server_reply,4096);
  	puts(server_reply);
  	SockPrintf(socket,"MAIL FROM: %s\r\n",email(from));
  	SockGets(socket,server_reply,4096);
  	puts(server_reply);
	SockPrintf(socket,"RCPT TO: %s\r\n",email(to));
	SockGets(socket,server_reply,4096);
	puts(server_reply);
	SockPrintf(socket,"DATA\r\n");
	SockGets(socket,server_reply,4096);
	puts(server_reply);
	SockPrintf(socket,"From: %s\r\n",from);
	SockPrintf(socket,"To: %s\r\n",to);
	if(subject==NULL && strncasecmp(message,"subject:",8)==0) {
		/* Subject: line @ beginning of msg body... */
		subject=(char *) malloc(strlen(message)-7);
		strcpy(subject,message+8);
		while(strchr(subject,' ')==subject) subject++;
		subject[strchr(subject,' ')-subject]='\0';
		message=strchr(message,'\n')+1;
	}
	if(subject!=NULL)	SockPrintf(socket,"Subject: %s\r\n",subject);
	SockPrintf(socket,"\n%s\n",message);
	footer=readfile(footfile);
	if(footer!=NULL)	SockPrintf(socket,"%s\n",footer);
	SockPrintf(socket,".\n");
	SockGets(socket,server_reply,4096);
	puts(server_reply);
	if(strstr(server_reply,"accepted")==NULL && strstr(server_reply,"Accepted")==NULL && strstr(server_reply,"ACCEPTED")==NULL) {
		puts("Mail rejected by SMTP server.\n");
		SockKill(socket);
		exit(1);
	}
	SockPrintf(socket,"QUIT\r\n");
	SockGets(socket,server_reply,4096);
	puts(server_reply);
	SockKill(socket);
	free(server_reply);
  #endif  
}

void sendtolist(char *listname, char *from, char *subject, char *message)
{
  char *listfile,*footfile,*conffile,*member;
  char *oldmsg;
  char *config,*sender;
  FILE *list;
  #ifndef USE_SENDMAIL
  	int socket;
  	char *server_reply;
  	char *footer;
  	char success;
  #endif

  printf("Sending to list...\n");
  
  listfile=(char *) malloc(strlen(LISTDIR)+strlen(listname)+10);
  sprintf(listfile,"%s/%s.members",LISTDIR,listname);
  footfile=(char *) malloc(strlen(LISTDIR)+strlen(listname)+9);
  sprintf(footfile,"%s/%s.footer",LISTDIR,listname);
  conffile=(char *) malloc(strlen(LISTDIR)+strlen(listname)+9);
  sprintf(conffile,"%s/%s.config",LISTDIR,listname);
  
  config=readfile(conffile);
  sender=extract(config,"sender=",'\n');
  if((scasecmp(sender,"original")==0) || sender==NULL)
  	sender=from;
  if(scasecmp(sender,"list")==0) 
        sender=listaddress;
        
  #ifndef NO_SENDER_ADDRESS
	if(scasecmp(email(sender),email(from))) { /* sender != original sender */ 
		oldmsg=message;
		message=(char *) malloc(strlen(from)+strlen(message)+15);
  		sprintf(message,"* From: %s\n\n%s",from,oldmsg);
  	}
  #endif
  #ifndef NO_LIST_ADDRESS
  	if(scasecmp(listaddress,email(sender))) { /* sender != list */
  		oldmsg=message;
  		message=(char *) malloc(strlen(listaddress)+strlen(message)+15);
  		sprintf(message,"* List: %s\n\n%s",listaddress,oldmsg);
  	}
  #endif

  list=fopen(listfile,"r");

  #ifdef USE_SENDMAIL
	while(!feof(list)) {
		member=readline(list);
		if(strlen(member)>2)
			send(listname,member,sender,subject,message,footfile);
		free(member);
	}
  #else
  	server_reply=(char *) malloc(4096);  /* I know, 4k might be exaggerated... */
  	socket=Socket(SMTP_SERVER,25);
  	if(socket<=0) {
  		printf("Can't connect to server %s.\n",SMTP_SERVER);
  		exit(1);
  	}
  	printf("Connected to %s.\n",SMTP_SERVER);
  	SockGets(socket,server_reply,4096);
  	puts(server_reply);
  	SockPrintf(socket,"HELO %s\r\n",HOST);
  	SockGets(socket,server_reply,4096);
  	puts(server_reply);
  	SockPrintf(socket,"MAIL FROM: %s\r\n",email(sender));
  	SockGets(socket,server_reply,4096);
  	puts(server_reply);
  	while(!feof(list)) {
  		member=readline(list);
  		if(strlen(member)>2) {
  			SockPrintf(socket,"RCPT TO: %s\r\n",email(member));
  			SockGets(socket,server_reply,4096);
  			puts(server_reply);
  		}
  	}
	SockPrintf(socket,"DATA\r\n");
	SockGets(socket,server_reply,4096);
	puts(server_reply);
	SockPrintf(socket,"From: %s\r\n",sender);
	SockPrintf(socket,"To: %s\r\n",listaddress);
	if(subject==NULL && strncasecmp(message,"subject:",8)==0) {
		/* Subject: line @ beginning of msg body... */
		subject=(char *) malloc(strlen(message)-7);
		strcpy(subject,message+8);
		while(strchr(subject,' ')==subject) subject++;
		subject[strchr(subject,' ')-subject]='\0';
		message=strchr(message,'\n')+1;
	}
	if(subject!=NULL)	SockPrintf(socket,"Subject: %s\r\n",subject);
	SockPrintf(socket,"\r\n%s\r\n",message);
	footer=readfile(footfile);
	if(footer!=NULL)	SockPrintf(socket,"%s\r\n",footer);
	SockPrintf(socket,".\r\n");
	SockGets(socket,server_reply,4096);
        puts(server_reply);
	if(strstr(server_reply,"accepted")!=NULL || strstr(server_reply,"ACCEPTED")!=NULL || strstr(server_reply,"Accepted")!=NULL)
		success=1;
	SockPrintf(socket,"QUIT\r\n");
	SockGets(socket,server_reply,4096);
	puts(server_reply);
	SockKill(socket);
	free(footer);
	free(server_reply);
  #endif
  fclose(list);
  free(listfile); free(footfile); free(conffile);
}

void subscribe(char *listname, char *user, char *operator)
{
  FILE *list;
  char *mail,*from;
  char *listfile,*footfile,*welcfile;
  char *members;

  #ifdef LOG
	if(operator==NULL)
	  	fprintf(log,"* subscribing %s\n",user);
	else
		fprintf(log,"* %s subscribed by %s\n",user,operator);
  #endif

  listfile=(char *) malloc(strlen(LISTDIR)+strlen(listname)+10);
  sprintf(listfile,"%s/%s.members",LISTDIR,listname);
  welcfile=(char *) malloc(strlen(LISTDIR)+strlen(listname)+10);
  sprintf(welcfile,"%s/%s.welcome",LISTDIR,listname);
  footfile=(char *) malloc(strlen(LISTDIR)+strlen(listname)+9);
  sprintf(footfile,"%s/%s.footer",LISTDIR,listname);

  members=readfile(listfile);
  
  if(strstr(downcase(members),email(user))!=NULL) { /* User is already subscribed */
  	mail=(char *) malloc(2048);
  	if(operator==NULL) { /* User subscribed himheritself */
  		sprintf(mail,"You are already subscribed to this list.\n");
	  	send(listname,user,listname,"List subscription...",mail,footfile);	
  	} else {
  		sprintf(mail,"You tried to subscibe a user (%s) who is already subscribed to the list.\n",user);
  		send(listname,operator,listname,"List subscription...",mail,footfile);
  	}
  	free(mail);
  } else {
	list=fopen(listfile,"a");
	fprintf(list,"%s\n",user);
	fclose(list);
	mail=readfile(welcfile);
	if(mail!=NULL) {
		#ifdef LOG
			fprintf(log,"* sending welcome message\n");
		#endif
		if(operator!=NULL)
			send(listname,user,operator,NULL,mail,footfile);
		else {
			from=(char *) malloc(1024);
			sprintf(from,"%s@%s",listname,HOST);
			send(listname,user,from,NULL,mail,footfile);
			free(from);
		}
		free(mail);
	}
  }
  free(footfile);  free(welcfile);  free(listfile);
}

void unsubscribe(char *listname, char *user, char *operator)
{
  char *listfile;
  FILE *list;
  register int members,i;
  char *member[MAX_MEMBERS];
  
  listfile=(char *) malloc(strlen(LISTDIR)+strlen(listname)+10);
  sprintf(listfile,"%s/%s.members",LISTDIR,listname);
  
  #ifdef LOG
  	if(operator==NULL)
  		fprintf(log,"* %s unsubscribed\n",user);
  	else
  		fprintf(log,"* %s unsubscribed by %s\n",user,operator);
  #endif

  list=fopen(listfile,"r");
  members=0;
  while(!feof(list)) {
	member[members]=readline(list);
	if(scasecmp(member[members],user)!=0) members++;
  }
  members--;
  fclose(list);
  list=fopen(listfile,"w");
  for(i=0;i<=members;i++) {
	if(strlen(member[i])>2) fprintf(list,"%s\n",member[i]);
  }
  fclose(list); 
  free(listfile);
}

char *email(char *full_address)
{
  /* Get the plain address part from an e-mail address */
  /* (i.e. remove realname) */
  
  char *addr;
  
  addr=(char *) malloc(strlen(full_address)+1);
  strcpy(addr, full_address);
  
  /* Realname <user@host> type address */
  if(((char *) strchr(addr,'<'))!=NULL) {
  	addr=(char *) strchr(addr,'<')+1;
  	addr[strchr(addr,'>')-addr]='\0';
  }
  
  /* user@host (Realname) type address */
  if(((char *) strchr(addr,' '))!=NULL)
  	addr[strchr(addr,' ')-addr]='\0';
  
  return(addr);
  
}

char *realname(char *full_address)
{
  char *name;
  
  name=(char *) malloc(strlen(full_address)+1);
  strcpy(name,full_address);

  if(((char *) strchr(name,'<'))!=NULL)
  	/* Realname <user@host> type address */
  	name[strchr(name,'<')-1-name]='\0';
  else if(((char *) strchr(name,'('))!=NULL) {
  	/* user@host (Realname) type address) */
  	name=strchr(name,'(')+1;
  	name[strchr(name,')')-name]='\0';
  } else
  	/* Realname not specified -> use username */
  	name[strchr(name,'@')-name]='\0';
  
  return(name);
  
}

char *username(char *address)
{
  char *name;
  name=email(address);
  if ( strchr(name,'@') != NULL )
  	name[strchr(name,'@')-name]='\0';
  return(name);
}

char *downcase(char *s)
{
  register int i;
  char *n;
  
  n=(char *) malloc(strlen(s)+1);
  for(i=0;i<=strlen(s);i++)
  	if(s[i]>='A' && s[i]<='Z')
  		n[i]=s[i]-'A'+'a';
  	else
  		n[i]=s[i];
  return(n);
}

int scasecmp(char *s1, char *s2)
{
  if((s1==NULL || s2==NULL)&&(s1!=NULL || s2!=NULL))
  	return -1;
  else
  	return strcasecmp(s1,s2);
}

int main(int argc, char *argv[])
{
  int pos,flen,wsize;
  char *mail,*header,*msg,*to,*from,*errors,*subject,*recipient;
  char *conffile,*listfile,*opfile,*footfile;
  char *config;
  char *a;
  #ifdef LOG
  	char *logfile;
  #endif
  
  #ifdef GET_HOST
  	hostname=(char *) malloc(1024);
  	gethostname(hostname,1024);
  	hostname[strlen(hostname)+1]='\0';
  	hostname[strlen(hostname)]='.';
  	getdomainname(hostname+strlen(hostname),1024-strlen(hostname)-1);
  #endif
  
  printf("%s ready...\n\n",VERSION);

  flen=0; wsize=1024;
  mail=(char *) malloc(sizeof(char)*(wsize+1));
  while(!feof(stdin)) {
  	mail[flen]=(char) fgetc(stdin);
  	if(flen==wsize) {
  		mail[flen+1]='\0';
  		wsize+=1024;
  		mail=(char *) realloc(mail,sizeof(char)*(wsize+1));
  	}
  	flen++;
  }
  flen--;
  mail[flen]='\0';
  
  header=(char *) malloc(strlen(mail) + 1);
  strcpy(header,mail);
  
  pos=strstr(header,"\n\n")-header;
  header[pos]='\0';

  msg=mail+pos+2;
  
  to=downcase(email(extract(header,"To: ",'\n')));
  if(strchr(to,'@')!=NULL) to[strchr(to,'@')-to]='\0';
  
  from=extract(header,"From: ",'\n');
  subject=extract(header,"Subject: ",'\n');
  if(subject==NULL) {
    subject=(char *)malloc(13);
    sprintf(subject,"(no subject)");
  }

  if(to[0]=='"') to++;
  
  listaddress=(char *) malloc(strlen(to)+strlen(HOST)+2);
  sprintf(listaddress,"%s@%s",to,HOST);
  
  #ifdef LOG
  	logfile=(char *) malloc(strlen(LOGDIR)+strlen(to)+6);
  	sprintf(logfile,"%s/%s.log",LOGDIR,to);
	log=fopen(logfile,"a");
	if(log==NULL) {
		printf("can't access log file %s :(\nPlease contact the list operator.\n",logfile);
		return(1);
	}
	fprintf(log,"===================================================\n");
	fprintf(log,"Message '%s' from %s...\n",subject,from);
	fprintf(log,"===================================================\n");
  #endif

  /* Find filenames for list... */
  
  listfile=(char *) malloc(strlen(LISTDIR)+strlen(to)+10);
  sprintf(listfile,"%s/%s.members",LISTDIR,to); 
  opfile=(char *) malloc(strlen(LISTDIR)+strlen(to)+12);
  sprintf(opfile,"%s/%s.operators",LISTDIR,to);
  conffile=(char *) malloc(strlen(LISTDIR)+strlen(to)+9);
  sprintf(conffile,"%s/%s.config",LISTDIR,to);
  footfile=(char *) malloc(strlen(LISTDIR)+strlen(to)+9);
  sprintf(footfile,"%s/%s.footer",LISTDIR,to);
  
  /* read config file... */
  
  config=readfile(conffile);

  /* Permit "subscribe someone@somewhere" for the user - by converting it
     to a reasonable form ("subscribe"). */
     
  if(strncasecmp(subject,"subscribe ",10)==0) {
  	recipient=subject+10;
  	if(scasecmp(email(from),email(recipient))==0)
  		subject[9]='\0';
  }
  if(strncasecmp(subject,"unsubscribe ",12)==0) {
  	recipient=subject+12;
  	if(scasecmp(email(from),email(recipient))==0)
  		subject[11]='\0';
  }
  
  if(strncasecmp(subject,"subscribe ",10)==0) {
  	recipient=subject+10;
  	a=readfile(opfile);
  	if((char *) strstr(a,from)==NULL && scasecmp(email(from),email(recipient))) { /* NO OPERATOR */
  		#ifdef LOG
  			fprintf(log,"* attempt to subscribe %s by %s\n",recipient,from);
  		#endif
  		free(mail);
  		mail=(char *) malloc(1024);
  		sprintf(mail,"You must be list operator in order to add new users to the list %s@%s.",to,HOST);
  		send(to,from,listaddress,"List subscription",mail,footfile);
  	} else 
	  	subscribe(to,recipient,from);
  
  } else if(strncasecmp(subject,"unsubscribe ",12)==0) {
  	recipient=subject+12;
  	a=readfile(opfile);
  	if((char *) strstr(a,from)==NULL && scasecmp(email(from),email(recipient))) { /* NO OPERATOR */
	  	#ifdef LOG
  			fprintf(log,"* attempt to unsubscribe %s by %s\n",recipient,from);
  		#endif
  		free(mail);
  		mail=(char *) malloc(1024);
  		sprintf(mail,"You must be list operator in order to unsubscribe users.");
  		send(to,from,listaddress,"List unsubscription",mail,footfile);
  	} else {
  		unsubscribe(to,recipient,from);
  	}
  } else if(scasecmp(subject,"subscribe")==0) {
  	a=extract(config,"newusers=",'\n');
  	if(scasecmp(a,"no")==0 && a!=NULL) {  /* Do not accept new users */
  		a=extract(config,"contact=",'\n');
  		if(a==NULL) {
  			a=(char *) malloc(6+strlen(HOST));
  			sprintf(a,"root@%s",HOST);
  		}
  		#ifdef LOG
  			fprintf(log,"* %s tried to subscribe\n",from);
  		#endif
  		free(mail);
  		mail=(char *) malloc(1024);
  		sprintf(mail,"%s is a closed mailing list. If you wish to subscribe, contact\nthe list operator at %s.\n",to,a);
  		send(to,from,listaddress,"List subscribtion",mail,footfile);
  	} else {
  		subscribe(to,from,NULL);
	}
  } else if(scasecmp(subject,"unsubscribe")==0) {
  	unsubscribe(to,from,NULL);
  } else {  /* Mail for list */
  	errors=extract(config,"errors-to=",'\n');
  	if(errors==NULL) {
  		errors=extract(config,"contact=",'\n');
  		if(errors==NULL) {
  			errors=(char *) malloc(6+strlen(HOST));
  			sprintf(errors,"root@%s",HOST);
  		}
  	}
  	if(scasecmp(username(from),"mailer-daemon")==0) {
  		#ifdef LOG
  			fprintf(log,"* Error message - forwarding to %s.\n",errors);
  		#endif
  		free(mail);
  		mail=(char *) malloc(strlen(VERSION)+1+strlen(msg)+strlen(to)+28);
  		sprintf(mail,"%s\nError from mailing list %s:\n\n%s",VERSION,to,msg);
  		send(to,errors,from,subject,mail,NULL);
  	} else {
	    	a=extract(config,"closed=",'\n');
	  	if(scasecmp(a,"yes")==0) {
	  		a=readfile(listfile);
	  		if((char *) strstr(a,from)==NULL) { /* NOT member of list */
		  		#ifdef LOG
		  			fprintf(log,"* Message rejected - %s not member of list.\n",from);
		  		#endif
		  		a=extract(config,"newusers=",'\n');
		  		free(mail); mail=(char *) malloc(2048);
		  		if(scasecmp(a,"no")==0) {
		  			a=extract(config,"contact=",'\n');
		  			if(a==NULL) {
		  				a=(char *) malloc(6+strlen(HOST));
		  				sprintf(a,"root@%s",HOST);
		  			}
		  			sprintf(mail,"You may send mail to %s@%s only if you are\nsubscribed to the list.\nContact the list operator, %s.\n",to,HOST,a);
		  			send(to,from,listaddress,"Sending mail to this list",mail,footfile);
		  		} else {
		  			sprintf(mail,"You may send mail to %s@%s only if you are\nsubscribed to the list.\nTo subscribe, send a message to %s@%s\nwith the Subject set to 'subscribe'.\n",to,HOST,to,HOST);
		  			send(to,from,listaddress,"Sending mail to this list",mail,footfile);
		  		}
		  	} else
		  		sendtolist(to,from,subject,msg);
	  	} else {
  			sendtolist(to,from,subject,msg);
  		}
  	}
  }
  
  #ifdef LOG
	fclose(log);
	free(logfile);
  #endif

  free(mail); free(header);
  free(to); free(from); free(subject);
  free(listfile); free(opfile);
  free(config); free(listaddress);
  #ifdef GET_HOST
  	free(hostname);
  #endif
  return 0;
}
