/* signatured
 * Daemon to supply random signatures for email front ends with a static part
 * using a named pipe
 *
 * usage:
 * 	signatured [-va] [-p named_pipe_name] -i id_file.txt -f signatures.txt
 *
 * where
 * 	-v is verbose (-vv is more verbose)
 * 	-a is 'advertisement', which is appending a line about signatured
 * 	-p is the name of the named pipe (e.g. /etc/signature), 
 * 		default is 'asignature' in the current directory
 * 	-i is the text file containing the persistent part displayed first 
 * 		(mostly this is your name and your company or things like that)
 * 	-f is the signature database which is in the form:
 *
 * 		[one or more lines signature]
 * 		%	<-- this is '%\n' in a line itself
 * 		[one or more lines second signature]
 * 		....
 *
 * 	you can use the files of fortune(1) (usual in /usr/share/fortune) as 
 * 	signature files, if you don't care a fuck about RFC 1855 :)
 *
 * FX <fx@phenoelit.de>
 * $Id: sigd.c,v 1.6 2000/05/17 09:56:50 fx BETA1 fx $
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

/* syntax definitions */
#define BREAK_LINE	"%\n"
/* the usual 'plase overflow me' buffer shit */
#define BUF_SIZE	1024

#define DEFAULT_NAME 	"asignature"
#define ADVERT		"[signatured, http://www.phenoelit.de/stuff/signatured.c]\n"

typedef struct {
    char 	*text;
    void	*next;
} sigindex_t;

/* these have to be global */
sigindex_t	*anchor		=NULL;
unsigned int	sigcount	=0;
char		*pipe_name	=NULL;

/* configuration */
char	*base_file_name	=NULL;
char	*id_file_name	=NULL;
FILE	*id_file	=NULL;
int		advertisement;
int		verbose;

void sighandler(int s);
char *randomsig(void);
void index_basefile(char *name);

void usage(char *me) {
    printf("usage: %s [-va] [-p pipename] -i idfile.txt -f database.txt\n",
	    me);
    exit (1);
}


int main(int argc, char **argv) {

    /* for the named pipe */
    FILE	*npipe;
    char	*idstr;		/* the persistent part */
    int		pid;
    
    /* stuff for command line */
    char	option;
    struct stat	statbuf;
    extern char	*optarg;

    /* sleeping data ;) */
    struct timespec nanot = {0,100000};


    while ((option=getopt(argc,argv,"vap:f:i:"))!=EOF) {
	switch (option) {
	    case 'a': advertisement++;
		      break;
	    case 'v': verbose++;
		      break;
	    case 'i': if ((id_file_name=
				  (char *)malloc(strlen(optarg)+1))==NULL) {
			  fprintf(stderr,"malloc() failed\n");
			  exit(1);
		      }
		      memset(id_file_name,0,strlen(optarg)+1);
		      strcpy(id_file_name,optarg);
		      break;
	    case 'f': if (stat(optarg,&statbuf)!=0) {
			  perror("stat()");
			  fprintf(stderr,"unable to stat() base file\n");
			  exit (1);
		      }
		      if ((base_file_name=(char*)malloc(strlen(optarg)+1))
			      ==NULL) {
			  fprintf(stderr,"malloc() failed\n");
			  exit(1);
		      }
		      memset(base_file_name,0,strlen(optarg)+1);
		      strcpy(base_file_name,optarg);
		      break;
	    case 'p': if ((pipe_name=(char *)malloc(strlen(optarg)+1))==NULL) {
			  fprintf(stderr,"malloc() failed\n");
			  exit(1);
		      }
		      memset(pipe_name,0,strlen(optarg)+1);
		      strcpy(pipe_name,optarg);
		      break;
	    default: fprintf(stderr,"unknown option\n");
		     usage(argv[0]);
	}
    }

    /* check for required options */
    if (!id_file_name) {
	fprintf(stderr,"You have to supply an ID file\n"),
	usage(argv[0]);
    }
    if (!base_file_name) {
	fprintf(stderr,"You have to supply a signatures file\n"),
	usage(argv[0]);
    }

    /* check for optional commandline stuff */
    if (!pipe_name) {
	if ((pipe_name=(char *)malloc(strlen(DEFAULT_NAME)+1))==NULL) {
	      fprintf(stderr,"malloc() failed\n");
	      exit(1);
	}
	memset(pipe_name,0,strlen(DEFAULT_NAME)+1);
	strcpy(pipe_name,DEFAULT_NAME);
    }


    /* read the persistent part */
    memset(&statbuf,0,sizeof(statbuf));
    stat(id_file_name,&statbuf);
    if ((idstr=(char *)malloc(statbuf.st_size+1))==NULL) {
	fprintf(stderr,"malloc() failed for ID\n");
	exit (1);
    }
    
    if ((id_file=fopen(id_file_name,"rt"))==NULL) {
	perror("fopen()");
	fprintf(stderr,"unable to open ID file\n");
	exit(1);
    }
    if (fread(idstr,statbuf.st_size,sizeof(char),id_file)>0) {
	if (verbose) printf("Persistent signature is:\n%s\n",idstr);
    } else {
	if (verbose) printf("Persistent signature is empty\n");
	/* make sure we know later that this is empty */
	free(idstr);
	idstr=NULL;
    }
    fclose(id_file);

    /* we index the base file to get a number of available 
     * signatures */
    index_basefile(base_file_name);

    /* set the umask explicitly, you don't know where it's been */
    umask(0);

    /* create named pipe */
    if (mkfifo(pipe_name, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))
    {
	perror("mkfifo");
	exit(-1);
    }

    /* make me daemon, baby */
    if ((pid=fork())<0) {
	perror("fork()");
	exit (1);
    }
    
    /* the parent terminates here */
    if (pid) {
	if (verbose) printf("Daemon started (PID %d)\n",pid);
	exit (0);
    }

    /* register with syslog(3) */
    openlog(argv[0],LOG_PID,LOG_DAEMON);
    syslog(LOG_NOTICE,"started");

    /* assign the signal handler */
    signal(SIGINT,&sighandler);
    signal(SIGTERM,&sighandler);
    signal(SIGABRT,&sighandler);

    /* this should be random enough */
    srand((unsigned int)time(NULL));

    /* main server loop:
     * opening a named pipe for write is blocking while nobody reads from it */
    for (;;) {
	if ((npipe=fopen(pipe_name,"wt"))==NULL) {
	    perror("fopen");
	    exit(1);
	}

	/* write persitent part if any */
	if (idstr) 
	    fprintf(npipe,"%s",idstr);

	/* write random part */
	fprintf(npipe,"%s",randomsig());

	/* make the ad if desired */
	if (advertisement) 
	    fprintf(npipe,"%s",ADVERT);

	fclose(npipe);
	/* not ALL the CPU power is mine */
	nanosleep(&nanot,NULL);

	if (verbose)
	    syslog(LOG_NOTICE,"served a signature");
    }

    /* not reached but makes gcc happy */
    return 0;
}

void sighandler(int s) {
    sigindex_t	*current		=NULL;

    current=anchor;
    while(current!=NULL) {
	anchor=current;
	current=current->next;
	if (anchor->text) 
	    free(anchor->text);
	free(anchor);
    }
    syslog(LOG_NOTICE,"terminating");
    unlink(pipe_name);
    closelog();
    exit(0);
}

char *randomsig(void) {
    unsigned int sign=0;
    int		i=0;
    sigindex_t	*current=NULL;


    /* this is from the rand(3) man page */
    sign=(unsigned int)((float)sigcount*rand()/(RAND_MAX+1.0));
    if (verbose)
	syslog(LOG_NOTICE,"Use random number %d",sign);
    
    /* search this signature in list */
    current=anchor;
    while (current!=NULL) {
	if (i==sign) break;
	current=current->next;
	i++;
    }

    /* check if we are out of range */
    if (!current) {
	syslog(LOG_ERR,"fatal: random number not in list range");
	sighandler(0);
    }

    return (current->text);
}

void index_basefile(char *name) {
    FILE	*bf;
    char	*buf;
    char	*onesig		=NULL;
    int 	linec=0;
    sigindex_t	*current	=NULL;

#define APPEND_LL	if ((current->next=(sigindex_t *)\
	    malloc(sizeof(sigindex_t)))==NULL) { \
	fprintf(stderr,"malloc() failed in LL\n"); \
	exit(-1); } else { current=current->next; current->next=NULL; }


    /* prepare the linked list */
    if ((anchor=(sigindex_t *)malloc(sizeof(sigindex_t)))==NULL) {
	fprintf(stderr,"malloc() failed\n");
	exit(1);
    }
    current=anchor;
    current->text=NULL;
    current->next=NULL;

    if ((bf=fopen(name,"rt"))==NULL) {
	perror("fopen()");
	fprintf(stderr,"unable to open base file");
	exit (1);
    }

    if ((buf=(char *)malloc(BUF_SIZE+1))==NULL) {
	fprintf(stderr,"malloc() failed\n");
	exit(1);
    }
    memset(buf,0,BUF_SIZE+1);

    sigcount=0;

    while (fgets(buf,BUF_SIZE,bf)!=NULL) {
	if (!strcmp(buf,BREAK_LINE)) {
	    /* make sure the was a signature befor the first seperator */
	    if (!onesig) continue;

	    sigcount++;
	    /* if the current->text stuff is empty, use this */
	    if (current->text) APPEND_LL;
	    /* copy the actual onesig buffer to current->text */
	    if ((current->text=(char *)malloc(strlen(onesig)+1))==NULL) {
		fprintf(stderr,"malloc() failed!");
		sighandler(0);
	    }
	    memset(current->text,0,strlen(onesig)+1);
	    strcpy(current->text,onesig);
	    free(onesig); onesig=NULL;	/* seems necessary */

	    if (verbose) 
		printf("signature %d ends at line %d\n",sigcount,linec+1);
	    if (verbose>1) 
		printf("signature is:\n%s----\n",current->text);
	} else {
	    if (onesig) {
		if ((onesig=realloc(onesig,strlen(onesig)+strlen(buf)+1))
			==NULL) {
		    fprintf(stderr,"realloc() failed!");
		    sighandler(0);
		}
	    } else {
		if ((onesig=malloc(strlen(buf)+1))==NULL) {
		    fprintf(stderr,"malloc() failed!");
		    sighandler(0);
		}
		memset(onesig,0,strlen(buf)+1);
	    }
	    strcat(onesig,buf);
	    if (verbose>2) 
		printf("DEBUG: onesig is now:\n%s",onesig);
	}
	linec++;
	memset(buf,0,BUF_SIZE+1);
    }

    /* may be someone has not terminted the last signature 
     * let us be nice and add this one too*/
    if (onesig) {
	sigcount++;
	/* if the current->text stuff is empty, use this */
	if (current->text) APPEND_LL;
	/* copy the actual onesig buffer to current->text */
	if ((current->text=(char *)malloc(strlen(onesig)+1))==NULL) {
	    fprintf(stderr,"malloc() failed!");
	    sighandler(0);
	}
	strcpy(current->text,onesig);
	free(onesig);
	if (verbose) 
	    printf("signature %d ends at line %d\n",sigcount,linec+1);
	if (verbose>1) 
	    printf("signature is:\n%s----\n",current->text);
    }

    free(buf);
    fclose(bf);
}

