/* Zephyr logging program, pre-public release version July 8 1992 */
/* Author: Seth Finkelstein  sethf@athena.mit.edu */
/* This program is hereby placed into the public domain */
/* Although if by chance someone does by some miracle make a profit from it */
/* some renumeration would be nice. This is strictly optional, though, and
/* in no way a legal requirement. */
/* Do acknowledge my contribution in any derivative works.That costs nothing.*/

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <strings.h>
#include <zephyr/zephyr.h>
#include <signal.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <krb.h>
#include <sys/types.h>
/* #include <time.h> */
#include <netinet/in.h>
char *malloc();
FILE *efopen(); /* My fopen, with error handling */

#ifndef ZLOG_KEYFILE
#define ZLOG_KEYFILE "/usr/local/zlog/keyfile"
#endif

#ifndef ZLOG_SRVTAB
#define ZLOG_SRVTAB "/etc/athena/srvtab"
#endif

#define ERROR_RECOVER_TIME 21600 /* 21600 == 6 hours */
/* Time (in seconds) after which to make another call for help, if no
 * response has been received to the first.
 */

#define NOAUTH_BIT 0x80000000l


int subbing = 1;

static void renew_subs()
{
subbing = 1;
}

static void cleanup()
{
    fprintf(stderr,"Cancelling Subs.\n");
    ZCancelSubscriptions(0);
    exit(0);
}

void downcase(arg)
char *arg;
{
    while(*arg != NULL) {
	if (isupper(*arg)) *arg = tolower(*arg);
	/* Not up there - beware the jaws of the macro */
	arg++;
    }
}

int canonic(string)
char *string;
{
	int altered = 0;
	char *inchar;

	for (inchar = string; *inchar; inchar++) {
		if (*inchar == 13 || *inchar == 10) {
	/* CR & LF. It would be nice if LF -> \n, but that's too much hassle */
			altered = 1;
			*inchar = '\\';
		}
		else if (*inchar < 32) {
			*inchar = '^';
			altered = 1;
		}
		else if (*inchar > 127) {
			*inchar = '~';
			altered = 1;
		}
	}

	return altered;
}


helpme(plead)
char *plead;
{
	char command[256];
	char *people = "marthag";

	sprintf(command,"echo '%s' | /usr/ucb/mail %s", plead, people);
	system(command);
	sprintf(command,"echo '%s' | /usr/athena/zwrite -d -n -q %s", plead, people);
	system(command);
}

long criedhelp = 0;
unsigned int maxerrs = 0;

handle_error(fp)
FILE *fp;
{
#include <sys/stat.h>	
	int olderr = errno, whaterr, status;
	/* Yes, I know this is living dangerously. */
	/* Let's see if it works in practice */
	long timetemp = time(0);
	struct stat sbuf;

	clearerr(fp);
	if (++maxerrs > 50) return 0; /* Give up */

	fprintf(stderr,
		"Entering error handling routine, errno = %d  Time = %s",
		errno, ctime(&timetemp));

	status = fstat(fileno(fp), &sbuf);
	if (status == -1) {
		whaterr = errno;
		com_err("zlog", whaterr, "Got a file error %d\n", whaterr);
		/* But what if the filesystem itself goes down? */
		switch(whaterr) {
			case ESTALE: /* 70 */
			if (!criedhelp) {
				perror("Zephyr log");
				if (!criedhelp) helpme("Help. Help. I am under attack. - zlog");
			}
			break;
			case ETIMEDOUT: /* 60 */
			if (!criedhelp) {
				perror("Zephyr log");
			}
			break;
			case EDQUOT: /* 69 */
			if (!criedhelp) {
				perror("Zephyr log");
				helpme("Bitbucket filled up. Do we have quota problems ? - zlog");
			}
			break;
			default:
			perror("Zephyr log");
			fprintf(stderr,"State error = %d\n",whaterr);
		}
	}
	else {
		switch (olderr) {
			case EIO: /* 5 */
			if (!criedhelp) helpme("Bitbucket i/o error. - zlog");
			break;
			case EDQUOT: /* 69 */
			if (!criedhelp) helpme("Quota problems? - zlog");
			break;
			case ENOSPC: /* 28 */
			if (!criedhelp) helpme("Bitbucket filled up. - zlog");
			break;
			default:
			fprintf(stderr,"Weird error = %d\n",olderr);
		}
	}
	criedhelp = time(0);
	return 0; /* Saber shut-up */
}

main()
{
    ZNotice_t notice;
    ZSubscription_t sub;
    Code_t retval;
    int code;
    struct sockaddr_in from;
    char *sig, *body;
    time_t ttemp;
    char **itmp;
    char *stringtime;
    struct hostent *fromhost;
    char *hostfrom, *sender, *instance;
    u_long mess_time;
    u_short len, slen, mess_len, mess_diff;
    u_char sig_len;
    int i, weird, notmix, numnomix, numcommon, notcommon;
    FILE *out_file;
    FILE *fp;
    FILE *stats_log;


    signal(SIGHUP, cleanup);
    signal(SIGINT, cleanup);
    signal(SIGUSR1, cleanup);
    signal(SIGUSR2, renew_subs);
    signal(SIGALRM, renew_subs);

    stats_log = efopen(".stats","a");


    setenv("KRBTKFILE",ZLOG_KEYFILE,1);

    /* Subscribe this client to messages,*,* */
    sub.zsub_class = "SIPB";
    sub.zsub_classinst = "*";
    sub.zsub_recipient = "*";

    /* Initialize the library */
    if ((retval = ZInitialize()) != ZERR_NONE) {
        com_err("zlog", retval, "while initializing");
	fprintf(stderr,"Error value: %d\n",retval);
	exit(1);
    }

    /* Open a port, so that ZSubscribeTo has a port to use */
    if ((retval = ZOpenPort((int *) 0)) != ZERR_NONE) {
        com_err("zlog", retval, "while opening port");
	fprintf(stderr,"Error value: %d\n",retval);
	exit(1);
    }

    /* Loop and accept incoming messages and print them out */

    fprintf(stderr,"Now accepting messages...\n");
    ttemp = time(0);
    fprintf(stderr,"Started at time: %s\n",ctime(&ttemp));

    for (;;) {
        if (subbing) {
           code = krb_get_svc_in_tkt("rcmd","charon","ATHENA.MIT.EDU",
                  "krbtgt","ATHENA.MIT.EDU",1,ZLOG_SRVTAB);
           if (code) fprintf(stderr,"Code: %d - %s\n", code,krb_err_txt[code]);

/*           if ((retval = ZSubscribeTo(&sub,1,0)) != ZERR_NONE) {*/
            if ((retval = ZSubscribeToSansDefaults(&sub,1,0)) != ZERR_NONE) {
               com_err("zlog", retval, "while subscribing");
	       ttemp = time(0);
	       fprintf(stderr,"Time: %s\n",ctime(&ttemp));
           }
	   if (retval == ZERR_SERVNAK) alarm(90); else alarm(1200);
	   subbing = 0;
	   if (time(0) - criedhelp > ERROR_RECOVER_TIME)
	   	criedhelp = maxerrs = 0;

        }

	retval = ZReceiveNotice(&notice, &from);
	if (retval == EINTR) continue;
        if (retval != ZERR_NONE) {
                com_err("zlog", retval, "while receiving notice");
		fprintf(stderr,"Error value: %d\n",retval);
		continue;
        }
	/* fprintf(stdout,"\nCode: %s\n",notice.z_opcode);*/

	if (strlen(notice.z_opcode)>3 && !strncmp(notice.z_opcode,"PING",4)) {
		ZFreeNotice(&notice);		
		continue;
        }

#define OUT(a,b) fwrite(a,1,b,out_file)

        /* Strip sender realm */
	sender = notice.z_sender;
	if (body=index(sender,'@')) { *body = '\0'; }
	/* Just using 'body' as a tmp */
	(void) canonic(sender);

        fromhost = gethostbyaddr(&(notice.z_sender_addr),
				 sizeof(struct in_addr),AF_INET);
	hostfrom = (fromhost ? fromhost->h_name :
		    inet_ntoa(notice.z_sender_addr));

	/* Strip off .MIT.EDU from host */
	if (body = index(hostfrom,'.')) { if (body[1] == 'M') *body = '\0'; }
	(void) canonic(hostfrom);

        /*
        * Convert time & date notice was sent to ascii.  The $time
        * has the format "01:03:52" while $date has the format
        * "Sun Sep 16 1973".
        */
        stringtime = ctime(&(notice.z_time.tv_sec));

        /* printf("Message from: %s\n", notice.z_sender);*/
        /* printf("Instance: %s\n", notice.z_class_inst);*/

	body = sig = notice.z_message;
	len = (u_short) notice.z_message_len;
        mess_len = len;
	instance = notice.z_class_inst;

	if (*instance) {
		weird = canonic(instance);
		downcase(instance);
		slen = strlen(instance);
	}
	else {
		/* instance null */
		instance="null";
		weird = 1;
		slen = 4;
	}

		out_file = efopen(".plan","a");
		fprintf(out_file,"\nInstance: %s",instance);


	if (out_file == NULL) { perror("File open"); /*out_file=stderr;*/ continue; };

	fputs("\nTime: ",out_file);
        OUT(stringtime+11,8); /* time */
	fputs(" Date: ",out_file);
        OUT(stringtime,11); 	/* day */
        OUT(stringtime+20,4); /* date */
	fprintf(out_file," Host: %s\n",hostfrom);

	if (*body == NULL) { /* It's a message with null=no sig */
		if ( len > 1 && body[1] != NULL ) {
			fprintf(out_file,"From: <%s>\n\n",sender);
			while (mess_len && body[mess_len-1] == 0) mess_len--;
			OUT(++body,--mess_len);
			if (mess_len && body[mess_len-1] != 10) fputc(10,out_file);
			sig_len = 0;
		}
		else {
			fprintf(out_file,"From: <%s>\n\n",sender);
			fprintf(out_file,"NULL MESSAGE?\n\n");
			sig_len = mess_len = 0;
		}
	}
	else { /* "Standard" message */
		for(i=0;i<=len;i++) {
			if (*(++body) == NULL) {
				mess_len = len - i - 1;
				break;
			}
		}
		if (i < len) {
			++body;
			--mess_len;
			sig_len = i+1;
			if (sig_len && sig[sig_len-1]==10) sig[--sig_len]=0;
			/* Old zwrite hack for newlines */
			fprintf(out_file,"From: %s <%s>\n\n",sig,sender);
			if (!mess_len) fprintf(out_file,"NULL MESSAGE\n\n");
			else {
				/* Remove trailing nulls */
				while (mess_len && !body[mess_len-1]) mess_len--;
				OUT(body,mess_len);
				if (body[mess_len-1] != 10) fputc(10,out_file);
			}
		}
		else { /* OLC and printer case. Untested */
			fprintf(out_file,"From: %s\n\n",sender);
			if (!mess_len) fprintf(out_file,"NULL MESSAGE\n\n");
			else {
				while (mess_len && body[mess_len-1] == 0) mess_len--;
				OUT(body,mess_len);
				if (mess_len && body[mess_len-1] != 10) fputc(10,out_file);
			}				
			sig_len = 0;
		}
	}

	if (notcommon) { fclose(out_file); }
	else fflush(out_file);
	if (errno || ferror(out_file)) handle_error(out_file);

#define WRITE_SHORT(quant, filep) \
if (quant < 255) fputc(quant, filep); \
else { if (quant > 65535) quant = 65535; \
fputc(255, filep); fputc(quant >> 8, filep); fputc(quant & 0xFF, filep); } \

	mess_time = (u_long) notice.z_time.tv_sec;
	if (!notice.z_auth) mess_time |= NOAUTH_BIT;
	/* Use the top bit in the time to store whether the message is
	   authenticated or not. */
        mess_time = htonl(mess_time);
	mess_diff = len - sig_len - mess_len;/*This should always be positive*/
        fwrite((char *)&mess_time, sizeof(u_long), 1, stats_log);
	WRITE_SHORT(len, stats_log);
	WRITE_SHORT(sig_len, stats_log);
	WRITE_SHORT(mess_diff, stats_log);
        fwrite(sender, sizeof(char), strlen(sender)+1, stats_log);
        fwrite(instance, sizeof(char), slen+1, stats_log);
	fflush(stats_log);
	if (errno || ferror(stats_log)) handle_error(stats_log);
#ifdef BACKUP_STATS
        fwrite((char *)&mess_time, sizeof(u_long), 1, stats_log_private);
	WRITE_SHORT(len, stats_log_private);
	WRITE_SHORT(sig_len, stats_log_private);
	WRITE_SHORT(mess_diff, stats_log_private);
        fwrite(sender, sizeof(char), strlen(sender)+1, stats_log_private);
        fwrite(instance, sizeof(char), slen+1, stats_log_private);
	fflush(stats_log_private);
	if (errno||ferror(stats_log_private)) handle_error(stats_log_private);
#endif
        ZFreeNotice(&notice);
    }
}

FILE *efopen(name,perm)
char *name, *perm;
{
	FILE *ret;

	if ((ret = fopen(name, perm)) != NULL) return ret;
	handle_error(stderr); /* Need something in the fp slot */
	exit(1);
}

