[comp.mail.sendmail] Tools to analyze mail log, here they are

siebeck@infoac.rmi.de (Wolfgang Siebeck ) (03/02/91)

Submitted-by: root@atreju.rmi.de (Wolfgang Siebeck)
Archive-name: Mailutil/part01

Since I received several answers to my offer of these tools within two
hours, I decided to post the stuff. Sorry about the documentation, this was
designed for local use, so the comments are mainly German, but I tried to
translate the important parts. There is a README file included, where I
explain the idea behind the package.

During the tests, I ran the package against the syslog of a SUN 3/50 and
a Intel machine running SYS V. Both gave the desired results :-).

Enjoy it and - well at least it should point to a way to interpret
the syslog.

Requests were coming from Dan Ehrlich, Kerry G. Forschler, David Asher, 
Matt Goldman, and darrah. Thank You for encouraging me to try my first
posting!

---- Cut Here and feed the following to sh ----
#!/bin/sh
# This is Mailutil, a shell archive (produced by shar 3.49)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# made 03/01/1991 16:08 UTC by root@atreju.rmi.de (Wolfgang Siebeck)
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#    946 -rwxr-xr-x Mailutil/MailRech
#    338 -rw-r--r-- Mailutil/Makefile
#   6283 -rw-r--r-- Mailutil/README
#   4565 -rw-r--r-- Mailutil/auswlog.awk
#  13260 -rw------- Mailutil/auswlog.c
#   4176 -rw-r--r-- Mailutil/justlog.c
#   1359 -rw-r--r-- Mailutil/mailstat1.awk
#   1769 -rw-r--r-- Mailutil/mailstat2.awk
#    110 -rw-r--r-- Mailutil/mailutil.h
#
# ============= Mailutil/MailRech ==============
if test ! -d 'Mailutil'; then
    echo 'x - creating directory Mailutil'
    mkdir 'Mailutil'
fi
if test -f 'Mailutil/MailRech' -a X"$1" != X"-c"; then
	echo 'x - skipping Mailutil/MailRech (File already exists)'
else
echo 'x - extracting Mailutil/MailRech (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Mailutil/MailRech' &&
X :
#
#	usage:
#
#	MailRech syslogname [hostname]
#
#	Generate invoices from syslog
#
X
HOST=`hostname`
if [ $# -gt 1 ]
then
X	HOST=$2
fi
export $HOST
X
grep sendmail $1 | justlog | sort > Sendmail.srt
X
#
X
auswlog Sendmail.srt $HOST
rm -f Sendmail.srt
if [ -f $HOST.out ]
then
X	rm -f $HOST.out
fi
mv Sendmail.out $HOST.out
echo "Input file for various statistics generated ($HOST.out)"
X
#
#	Ok, go for the money:
#
X
# cat $HOST.out | nawk -f auswlog.awk host=$HOST > $HOST.bill
X
#
#	Since the script 'auswlog.awk' makes use of functions, only nawk
#	will work here. Anyway, it is a sample. You may try
#	the other samples like
#
#		cat $HOST.out | awk -f mailstat1.awk
#
#	for a summary with fancy graphics :-)
#
#	or
#
#		cat $HOST.out | awk -f mailstat2.awk
#
#	for a sample with calculated costs. If you have nawk, You
#	might use the function round() to get real rounding instead
#	of the floating point format in printf.
#
X
#
X
echo done.
Xexit 0
SHAR_EOF
chmod 0755 Mailutil/MailRech ||
echo 'restore of Mailutil/MailRech failed'
Wc_c="`wc -c < 'Mailutil/MailRech'`"
test 946 -eq "$Wc_c" ||
	echo 'Mailutil/MailRech: original size 946, current size' "$Wc_c"
fi
# ============= Mailutil/Makefile ==============
if test -f 'Mailutil/Makefile' -a X"$1" != X"-c"; then
	echo 'x - skipping Mailutil/Makefile (File already exists)'
else
echo 'x - extracting Mailutil/Makefile (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Mailutil/Makefile' &&
#
#	Makefile for the accounting utilities for
#	sendmails log.
#
X
CC=gcc
X
CFLAGS=-O
X
LIBS=
X
.c.o:
X	$(CC) $(CFLAGS) -c $<
X
all:	auswlog justlog
X
auswlog:	auswlog.o
X	$(CC) $(CFLAGS) auswlog.o -o auswlog
X
justlog:	justlog.o
X	$(CC) $(CFLAGS) justlog.o -o justlog
X
clean:
X	rm -f justlog.o auswlog.o core
X
clobber:	clean
X	rm -f justlog auswlog
SHAR_EOF
chmod 0644 Mailutil/Makefile ||
echo 'restore of Mailutil/Makefile failed'
Wc_c="`wc -c < 'Mailutil/Makefile'`"
test 338 -eq "$Wc_c" ||
	echo 'Mailutil/Makefile: original size 338, current size' "$Wc_c"
fi
# ============= Mailutil/README ==============
if test -f 'Mailutil/README' -a X"$1" != X"-c"; then
	echo 'x - skipping Mailutil/README (File already exists)'
else
echo 'x - extracting Mailutil/README (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Mailutil/README' &&
README for the syslog accounting package.
X
(Excuse my lousy english! I hope, You get the idea, anyway...)
X
First let me express my thanks for Your interest. I am glad to
give some bytes in return to the information and help I got from
the usenet community.
X
-----------------
X
These utilities may be used to extract charging information from the
file /usr/spool/mqueue/syslog. We are using this package in a commercial
mailbox service.
X
Oh, I forgot: There are ANSI headers for gcc, the programs pass
X              gcc without warnings.
X
The provided shell script 'MailRech' is a kind of front end to the stuff.
X
This package is not a masterpiece in software engineering, but it serves
us well. The base is: Never invent the wheel again. Use the Unix
programs whereever they will do the job. So the way from the syslog to
the accounting information is:
X
Step 1. use grep to extract the lines dealing with sendmail's output.
Step 2. Reformat the lines, so they can be sorted on the queue-id.
Step 3. Perform the sort.
X
What we have now, is a new logfile, in which only the relevant parts are
contained. By sorting, multiple to= lines are grouped together and are
easy to handle.
X
Step 4. Generate one log entry for each transmission. If one message is
X        sent to multiple receivers, generate one line each.
X
Step 5. Now comes the best: use awk to generate whatever you want. Awk
X        is not the best performer, but I am a real *fan* of it. Make bar
X        charts, write bills, summarize, ... or throw it away.
X
X
Now we get the data:
X
Step 1.
-------
X
The input looks as follows (after grep sendmail ... ). 
The file is generated on a SUN 3/60.
X
-------------
X
Jan 23 05:48:10 infohh:	6890 sendmail: AA06890: message-id=<9101230448.AA06890@infohh.rmi.de>
Jan 23 05:48:10 infohh:	6890 sendmail: AA06890: from=percomp, size=1034, class=0
Jan 23 05:48:18 infohh:	6893 sendmail: AA06890: to=frisk@rhi.hi.is, delay=00:00:10, stat=Sent
Jan 23 08:18:39 infohh:	7150 sendmail: AA07150: message-id=<9101230718.AA07150@infohh.rmi.de>
Jan 23 08:18:39 infohh:	7150 sendmail: AA07150: from=news, size=107, class=0
Jan 23 08:18:41 infohh:	7153 sendmail: AA07150: to=root, delay=00:00:04, stat=Sent
Jan 23 08:33:17 infohh:	7188 sendmail: AA07188: message-id=<9101230733.AA07188@infohh.rmi.de>
Jan 23 08:33:17 infohh:	7188 sendmail: AA07188: from=natec, size=514, class=0
Jan 23 08:33:53 infohh:	7199 sendmail: AA07199: message-id=<9101230733.AA07199@infohh.rmi.de>
Jan 23 08:33:53 infohh:	7199 sendmail: AA07199: from=natec, size=506, class=0
Jan 23 08:33:24 infohh:	7191 sendmail: AA07188: to=postmaster@sh.cs.net, delay=00:00:09, stat=Sent
Jan 23 08:33:56 infohh:	7201 sendmail: AA07199: to=service@infohh.rmi.de, delay=00:00:04, stat=Sent
X
-------------
X
Step 2.
-------
X
We feed this through justlog. All irrelevant information is stripped.
(Notice the AA07188 and AA07199!)
X
--------------
AA06890:01:23:05:48:10:from=percomp, size=1034, class=0
AA06890:01:23:05:48:18:to=frisk@rhi.hi.is, delay=00:00:10, stat=Sent
AA07150:01:23:08:18:39:from=news, size=107, class=0
AA07150:01:23:08:18:41:to=root, delay=00:00:04, stat=Sent
AA07188:01:23:08:33:17:from=natec, size=514, class=0
AA07199:01:23:08:33:53:from=natec, size=506, class=0
AA07188:01:23:08:33:24:to=postmaster@sh.cs.net, delay=00:00:09, stat=Sent
AA07199:01:23:08:33:56:to=service@infohh.rmi.de, delay=00:00:04, stat=Sent
--------------
X
Step 3.
-------
X
Sort the stuff. 
X
--------------
AA06890:01:23:05:48:10:from=percomp, size=1034, class=0
AA06890:01:23:05:48:18:to=frisk@rhi.hi.is, delay=00:00:10, stat=Sent
AA07150:01:23:08:18:39:from=news, size=107, class=0
AA07150:01:23:08:18:41:to=root, delay=00:00:04, stat=Sent
AA07188:01:23:08:33:17:from=natec, size=514, class=0
AA07188:01:23:08:33:24:to=postmaster@sh.cs.net, delay=00:00:09, stat=Sent
AA07199:01:23:08:33:53:from=natec, size=506, class=0
AA07199:01:23:08:33:56:to=service@infohh.rmi.de, delay=00:00:04, stat=Sent
--------------
X
Step 4.
-------
X
Reformat the file. Generate one line per mail.
X
--------------
percomp~01~23~05~48~1034~percomp~frisk@rhi~infohh~rhi
news~01~23~08~18~107~news~root~infohh~infohh
natec~01~23~08~33~514~natec~postmaster@sh~infohh~sh
natec~01~23~08~33~506~natec~service@infohh~infohh~infohh
--------------
X
What we have now, may be the general purpose input. The fields are:
X
X 1. The user to be charged. (More on this later...)
X 2. Month
X 3. Day
X 4. Hour
X 5. Minute
X 6. Bytes
X 7. Sender
X 8. Receiver
X 9. Sending host
10. Receiving host
X
X
X
The charging algorithm:
X
We run a server for fax, telex, teletex, videotex and uucp message
handling in Germany.  Our own "network" are three Unix-based mailbox
systems using this service as well as some MESS-DOS based systems. Fax,
telex, teletex and videotex mail is charged seperately, so these
messages and the confirmations are filtered. Furthermore, if a user
sends a telex message, a copy of the message is archived in his box.
Naturally, this is not charged.
X
Generally speaking, the sender has to pay. There are two exceptions:
X
X  a) The message is from a remote host via uucp/smtp. We have to pay for
X     incoming mail, so the receiver on the local host is charged.
X
X  b) A non-user may log into the system as user 'zczc' and write a
X     message to an user. So the receiver is charged for the online time
X     of Mr. ZCZC.
X
X
If you don't need this information, just ignore the first field in the
output and You are done. 
X
X
As a sample of analysis, some awk scripts for simple statistics are
included in the package. 
X
-------------
X
Please redirect flames on the stuff to /dev/null, comments, critics or
enhancements to
X
siebeck@infoac.rmi.de or postmaster@atreju.rmi.de
X
======================================================================
X
And here comes the standard disclaimer:
X
I do in no way guarantee, that these programs serve any purpose.  I
shall not be made responsible for loss of money, sanity, free disk space
or whatever caused by these programs. All I can say is, I use them, and
they work for me.
X
If you want to distribute the stuff, feel free to do so, but keep the
stuff together.  I don't claim special Copyrights or licensing fees on
it, but please don't sell it, give it away for free. 
X
======================================================================
X
SHAR_EOF
chmod 0644 Mailutil/README ||
echo 'restore of Mailutil/README failed'
Wc_c="`wc -c < 'Mailutil/README'`"
test 6283 -eq "$Wc_c" ||
	echo 'Mailutil/README: original size 6283, current size' "$Wc_c"
fi
# ============= Mailutil/auswlog.awk ==============
if test -f 'Mailutil/auswlog.awk' -a X"$1" != X"-c"; then
	echo 'x - skipping Mailutil/auswlog.awk (File already exists)'
else
echo 'x - extracting Mailutil/auswlog.awk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Mailutil/auswlog.awk' &&
#########################################################################
#
#	Rechnungserstellung fuer Electronic Mail
#
#	Version 1.0
#
#	(c) 1990 by RMI Nachrichtentechnik GmbH
#
#########################################################################
X
function round(i) {
X	i = int((i*100) + 0.5)/100;
X	return i;
}
X
#########################################################################
X
function rechnung() {
X	if (rechnr > 0)
X		print("\n\n--------------------------------------\n");
X	rechnr++;
X	ggeb = round(localmails[uname] * lmail);
X	hostlmail += ggeb;
X	
X	k = round(localbytes[uname] / 1024);
X	if ((k == 0) && (localmails[uname] > 0))
X		k = 1;
X	mgeb = k * lkbyte;
X	hostlkbyte += mgeb;
X	
X	msteuer = 0.0;
X
X	print ("Rechnung fuer Electronic Mail");
X	printf("Username %s@%s\n\n\n", uname, host);
X	print ("                           Grund-   Mengen-");
X	print ("       Mails      Bytes   gebuehr   gebuehr");
X	print ("-------------------------------------------");
X	printf("local  %5d   %8d  %8.2f  %8.2f\n", \
X		localmails[uname], \
X		localbytes[uname], \
X		ggeb, \
X		mgeb);
X	k = round(remotebytes[uname] / 1024);
X	if ((k == 0) && (remotemails[uname] > 0))
X		k = 1;
X	printf("rem.   %5d   %8d  %8.2f  %8.2f\n", \
X		remotemails[uname], \
X		remotebytes[uname], \
X		round(remotemails[uname] * rmail), \
X		round(remotebytes[uname] / 1024 * rkbyte));
X	ggeb += round(remotemails[uname] * rmail);
X	hostrmail += round(remotemails[uname] * rmail);
X	
X	mgeb += round(k * rkbyte);
X	hostrkbyte += round(k * rkbyte);
X	
X	print ("-------------------------------------------");
X	printf("total  %5d   %8d  %8.2f  %8.2f = %8.2f\n", \
X		mails[uname], \
X		bytes[uname], \
X		ggeb, \
X		mgeb,
X		ggeb + mgeb);
X	msteuer = round((ggeb + mgeb) * mwst);
X	printf("                                 + %2d%% MwSt = %8.2f\n", \
X		mwst * 100, msteuer);
X	print ("                                              --------");
X	printf("                                              %8.2f\n", \
X		ggeb + mgeb + msteuer);
X	print ("                                              ========");
X	hostmwst += msteuer;
X
#	print ("Berechnungsgrundlage:");
#	print ("---------------------");
#	printf("Lokale Mail: %8.2f DM pro Mail\n", lmail);
#	printf("             %8.2f DM pro kByte\n", lkbyte);
#	printf("Remote Mail: %8.2f DM pro Mail\n", rmail);
#	printf("             %8.2f DM pro kByte\n", rkbyte);
}
X
#########################################################################
X
BEGIN {
X	FS="~";
X	if (host == "")
X		host = "infohh";
X	lmail = 0.0;
X	lkbyte = 0.0;
X	rmail = 0.05;
X	rkbyte = 0.20;
X	mwst = 0.14;
X	rechnr = 0;
X	hostlmail = 0.0;
X	hostlkbyte = 0.0;
X	hostrmail = 0.0;
X	hostrkbyte = 0.0;
X	hostmwst = 0.0;
}
X
#########################################################################
X
#    #    ##       #    #    #
##  ##   #  #      #    ##   #
# ## #  #    #     #    # #  #
#    #  ######     #    #  # #
#    #  #    #     #    #   ##
#    #  #    #     #    #    #
X
#########################################################################
/MAILER-DAEMON/ {
X	next
}
/\/dev\/null/ {
X	next
}
/news/ {
X	next
}
X
{
X	if (substr($0,1,1) == "~")
X		next;
X
X	mails[$1]++;
X	if ($9 == $10) {
X		localmails[$1]++;
X		localbytes[$1] += $6;
X	}
X	else {
X		remotemails[$1]++;
X		remotebytes[$1]+= $6;
X	}
X	bytes[$1] += $6;
}
X
#########################################################################
X
END {
#	i = 99;
X	for (usr in mails) {
X		uname = usr;
X		rechnung();
#		if (i > 20) {
#			print("Username              Mails    Bytes");
#			print("------------------------------------");
#			i = 0;
#		}
#		printf("%-20s %6d %8d\n", usr, mails[usr], bytes[usr]);
#		i++;
X	}
#	Jetzt noch die Summen fuer den Host ausgeben:
X	print ("\n\n\n\n================================================\n");
X	printf("Gesamtbetrag fuer den Host %s\n", host);
X	print ("           Grund-   Mengen-");
X	print ("          gebuehr   gebuehr");
X	print ("---------------------------");
X	printf("local    %8.2f  %8.2f\n", hostlmail, hostlkbyte);
X	printf("remote   %8.2f  %8.2f\n", hostrmail, hostrkbyte);
X	print ("---------------------------");
X	printf("Summe    %8.2f  %8.2f = %8.2f\n", \
X		hostlmail+hostrmail, \
X		hostlkbyte+hostrkbyte, \
X		hostlmail+hostrmail+hostlkbyte+hostrkbyte);
X	msteuer = round((hostlmail+hostrmail+hostlkbyte+hostrkbyte) * mwst);
X	printf("                 + %2d%% MwSt = %8.2f\n", \
X		mwst * 100, msteuer);
X	print ("                              --------");
X	printf("                              %8.2f\n", \
X		hostlmail+hostrmail+hostlkbyte+hostrkbyte+msteuer);
X	print ("                              ========");
X	
}
SHAR_EOF
chmod 0644 Mailutil/auswlog.awk ||
echo 'restore of Mailutil/auswlog.awk failed'
Wc_c="`wc -c < 'Mailutil/auswlog.awk'`"
test 4565 -eq "$Wc_c" ||
	echo 'Mailutil/auswlog.awk: original size 4565, current size' "$Wc_c"
fi
# ============= Mailutil/auswlog.c ==============
if test -f 'Mailutil/auswlog.c' -a X"$1" != X"-c"; then
	echo 'x - skipping Mailutil/auswlog.c (File already exists)'
else
echo 'x - extracting Mailutil/auswlog.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Mailutil/auswlog.c' &&
/*
**	Auswertung eines Log's, das bereits durch justlog
**	und sort gegangen ist.
**
**	WARNING: This program is a quick hack. It's designed for
**		 real addresses only. By saying real addresses, I
**		 talk about domain addressing, like user@host.domain.
**		 I did not try to decipher BANG's.
**
**	The input must be sorted, so all AA.. items are grouped together.
**	sort is also helpful, since the from= line will always be in front
**	of the to= line. Care is taken for multiple to= lines per message
**	and multiple receivers per to= line, as well.
**
**	In the end, there will be one or more lines per message. If user
**	joe_doe sends one message to five users, there will be five lines.
**
**	The algorithm:
**
**	Read input file, prepared by justlog and sorted.
**		Make a clean line from input.
**			No LF's or CR's, please.
**			Initialize date and user information.
**		Check, if new AA.. job.
**			If yes, write new log file and initialize new line.
**		Determine type of line (from= vs. to=)
**			If from=, extract sender and msg-size.
**			If to=, extract receiver(s). delay= and stat= are of
**				no interest. (see justlog.c)
**			Cleanup addresses (try to do so)
**			Fill new log line according to information.
**	until eof().
**	If there is still a new line to write, do so.
**
**	The writing of the new log line might be a overkill for Your
**	purpose. The underlying situation is described in the documentation.
**
**
**	Updates:
**
**	Version 1.1	added support for bang addressing. There is still
**			a problem with addresses like blah%fasel!host.domain.
**
**	Version 1.2	Since I had some problems, when compiling on a Sun
**			I changed the declaration of LZ (Logzeile,
**			means: log line) and NLZ (new log line). I had
**			forgotten about the word alignment in nested
**			structures. Now it really works.
*/
X
#include <stdio.h>
#include <string.h>
#include <memory.h>
X
#include "mailutil.h"
X
/* length of various strings */
#define FNAMELEN 80
#define MAXLINELEN 256
#define MAXFROMLEN 160
#define MAXTOLEN 160
#define MAXNAMELEN 20
#define HOSTNAMELEN 10
X
/* Types of syslog-lines we recognize */
#define UNKNOWN 0
#define FROM 1
#define TO 2
X
/* What happens to the lines ? */
#define DROPIT 1
#define KEEPIT 0
X
#ifndef DEBUG
#define DEBUG 0
#endif
X
#define VERSION "1.2"
X
FILE   *ifile,
X       *ofile;
X
#define KENNZSIZE 23
X
/* Definition for read the old lines */
typedef union {
X    struct {
X	char    kennz[8];		/* AA... von sendmail */
X	char    mon[3];			/* Monat u.s.w. */
X	char    day[3];
X	char    hour[3];
X	char    minute[3];
X	char    second[3];
X	char    rest[MAXLINELEN - 22];	/* from= und to= Zeile */
X    }       inh;
X    char    alles[MAXLINELEN + 1];
}       LZ, *LZPTR;
X
/* Definition for writing the new lines */
typedef struct {
X	char    kennz[8];		/* AA... von sendmail */
X	char    mon[3];			/* Monat u.s.w. */
X	char    day[3];
X	char    hour[3];
X	char    minute[3];
X	char    second[3];
X	char    zahler[MAXNAMELEN + 1];	/* Zahlungspflichtiger */
X	char    from[MAXFROMLEN + 1];	/* Absender der Nachricht */
X	char    to[MAXTOLEN + 1];	/* Empfaenger der Nachricht */
X	long    len;			/* Laenge der Nachricht */
}       NLZ, *NLZPTR;
X
char    defaulthost[HOSTNAMELEN + 1];
X
/*
**	Aufraeumen in der Eingabezeile
*/
X
void 
clearline(LZPTR lz)
{
X    char   *s;
X
X    if (s = strrchr(lz->alles, '\r'))	/* You never know, but this */
X	*s = '\0';			/* might run under MESS-DOS */
X    if (s = strrchr(lz->alles, '\n'))
X	*s = '\0';
X    lz->inh.kennz[7] =
X    lz->inh.mon[2] =
X    lz->inh.day[2] =
X    lz->inh.hour[2] =
X    lz->inh.minute[2] =
X    lz->inh.second[2] = '\0';
}
X
/*
**	Determine type of log line
*/
X
int 
gettype(LZPTR lz)
{
X
#if DEBUG
X    printf("Rest:%10.10s\n", lz->inh.rest);
#endif
X
X    if (!strncmp(lz->inh.rest, "to=", 3))
X	return TO;
X    if (!strncmp(lz->inh.rest, "from=", 5))
X	return FROM;
X    return UNKNOWN;
}
X
/*
**	Determine, if a log line is to be dropped
*/
X
int 
dropthis(NLZPTR nlz, char *empfhost)
{
X    /* Es fallen keine Gebuehren bei: */
X    /* 1. Qsl's, Confirmations */
X    if (!strncmp(nlz->to, "qsl@", 4))
X	return DROPIT;
X
X    /* 2. Eingehende Telexe, incoming telexes */
X    if (!strncmp(nlz->to, "ttxin@", 6))
X	return DROPIT;
X
X    /* 3. Abgehende Faxe, outgoing faxes */
X    if (!strcmp(empfhost, "fax"))
X	return DROPIT;
X
X    /* 4. "Ge-pipe-te" Sachen, stuff piped into programs */
X    if (!strncmp(nlz->to, "\"|", 2))
X	return DROPIT;
X
X    /* 5. Abgehende Telexe, outgoing telexes */
X    if (!strncmp(nlz->to, "ttx@rmi", 7))
X	return DROPIT;
X
X    /* 6. Mail an sich selbst, z.B. archivierte Telexe, archived telexes */
X    if (!strcmp(nlz->from, nlz->to))
X	return DROPIT;
X
X    return KEEPIT;
}
X
/*
**	Wegschreiben einer Zeile in das neue Log.
**
**	Es gibt pro Nachricht eine oder mehrere Zeilen, je nach
**	dem, wieviele Empfaenger existieren.
**
**	Vor dem Wegschreiben wird noch ueberprueft, ob der Absender
**	oder der Empfaenger die Gebuehren tragen soll. Als Erkennung gilt
**	dabei:
**
**	Feststellen des Absender-Hosts und des Empfaenger-Hosts.
**	Ist kein Host zu finden, so wird der default eingesetzt.
**	-------------------------
**	Write one line to the new log file. For each message, there may
**	be one or more lines according to the number of receivers.
**
**	Check for sending and receiving hosts. If no host is found,
**	insert name of default host (local mail).
*/
X
void 
writelog(NLZPTR nlz)
{
X    char    abshost[HOSTNAMELEN + 1],
X            empfhost[HOSTNAMELEN + 1],
X            zahler[MAXLINELEN + 1];
X    char   *hname,
X           *punkt;
X
X    hname = strchr(nlz->from, '@');	/* user@host.domain */
X    if (hname) {
X	hname++;
X	if ((punkt = strchr(hname, '.')) != NULL)
X	    *punkt = '\0';
X	strncpy(abshost, hname, HOSTNAMELEN);
X	abshost[HOSTNAMELEN] = '\0';
X    } else
X	strcpy(abshost, defaulthost);
X
X    hname = strchr(nlz->to, '@');
X    if (hname) {
X	hname++;
X	if ((punkt = strchr(hname, '.')) != NULL)
X	    *punkt = '\0';
X	strncpy(empfhost, hname, HOSTNAMELEN);
X	empfhost[HOSTNAMELEN] = '\0';
X    } else
X	strcpy(empfhost, defaulthost);
X
/*
**	Now: If neither the receiving nor the sending host
**	is the defaulthost, the mail was just routed and the
**	accounting will be done on another host.
*/
X
X    if (strcmp(empfhost, defaulthost) && strcmp(abshost, defaulthost))
X	return;
X
X    if (dropthis(nlz, empfhost) == DROPIT)
X	return;
X
X    /* Who should pay? By default, it is the sender! */
X    strcpy(zahler, nlz->from);
X
X    /* Except in case the receiver is on the default host ... */
X    if (!strcmp(empfhost, defaulthost)) {
X    	
X	/* but only, if not local! */
X	if (strcmp(abshost, empfhost))
X	    strcpy(zahler, nlz->to);
X    }
X    if ((punkt = strchr(zahler, '@')) != NULL)
X	*punkt = '\0';
X
/*
**	Und wieder ein Sonderfall: Falls der Absender
**	zczc ist, muss auch der Empfaenger zahlen! (Mail from Mr. ZCZC)
*/
X    if (!strcmp(nlz->from, "zczc")) {
X	strcpy(zahler, nlz->to);
X    }
X    strncpy(nlz->zahler, zahler, MAXNAMELEN);
X    nlz->zahler[MAXNAMELEN] = '\0';
X
#if DEBUG
X    printf("%s~%s~%s~%s~%s~%ld~%s~%s\n",
X	   nlz->zahler,
X	   nlz->mon,
X	   nlz->day,
X	   nlz->hour,
X	   nlz->minute,
X	   nlz->len,
X	   nlz->from,
X	   nlz->to);
#endif
X
X    fprintf(ofile, "%s~%s~%s~%s~%s~%ld~%s~%s~%s~%s\n",
X	    nlz->zahler,
X	    nlz->mon,
X	    nlz->day,
X	    nlz->hour,
X	    nlz->minute,
X	    nlz->len,
X	    nlz->from,
X	    nlz->to,
X	    abshost,
X	    empfhost);
}
X
/*
**	Try to clean up an address.
**
**	This doesn't look to good, but bang address are bad manners anyway.
*/
X
void 
clean_address(char *addr)
{
X    char    wbuff[MAXTOLEN + 1],
X           *s1,
X           *s2,
X            host[HOSTNAMELEN + 1],
X            user[MAXNAMELEN + 1];
X
X    strcpy(wbuff, addr);		/* Work on temporary buffer */
X    if ((s1 = strchr(wbuff, '@')) != NULL)
X	return;				/* assume standard format */
X
X    if ((s1 = strrchr(wbuff, '!')) != NULL) {
X	strncpy(user, &s1[1], MAXNAMELEN);
X	user[MAXNAMELEN] = '\0';
X	if ((s2 = strchr(wbuff, '!')) != NULL) {
X	    if (s1 == s2) {		/* We have host!user */
X		*s2 = '\0';
X		strncpy(host, wbuff, HOSTNAMELEN);
X		host[HOSTNAMELEN] = '\0';
X	    }
X	    		/* Here we have a blah!fasel!host!user */
X	    s2 = s1;	/*                               ^     */
X	    for (*s2 = '\0'; (s2 >= wbuff) && (*s2 != '!'); s2--);
X	    if (*s2 == '!') {
X		strncpy(host, &s2[1], HOSTNAMELEN);
X		host[HOSTNAMELEN] = '\0';
X		if ((s1 = strchr(host, '.')) != NULL)
X		    	/* blah!fasel!host.domain!user */
X		    	/*                ^            */
X		    *s1 = '\0';
X	    }
X	    sprintf(addr, "%s@%s", user, host);
X	    return;
X	}
X    }
}
X
/*
**	Zerlegen der from= Zeile aus dem Log.
**
**	Gefuellt wird das Absender/Laengenfeld
**
**	Interpret the from= line.
**	We extract the sending user and the file size
*/
X
void 
parsefrom(LZPTR lz, NLZPTR nlz)
{
X    char    delim = ',';		/* Delimiter fuer den Namen */
X    int     anfang = 5;
X    char   *src,
X           *dest;
X
X    memcpy(nlz, lz, KENNZSIZE); 	/* copy the ID-part */
X    if (lz->inh.rest[anfang] == '<') {
X	delim = '>';
X	anfang++;
X    }
X    src = &(lz->inh.rest[anfang]);
X    dest = &(nlz->from[0]);
X    while (*src != delim) {		/* copy senders name */
X	*dest = *src;
X	dest++;
X	src++;
X    }
X    *dest = '\0';
X    clean_address(&(nlz->from[0]));
/*
**	Jetzt noch die Groesse der Nachricht setzen
**	src zeigt mindestens auf das Ende des Absenders
**	Get size of message
*/
X    while (*src) {
X	if (!strncmp(src, "size=", 5)) {
X	    /* Groesse gefunden! */
X	    src += 5;
X	    nlz->len = atol(src);
X	    return;
X	}
X	src++;
X    }
X
}
X
/*
**	Zerlegen einer to= Zeile.
**
**	Es interessiert hiervon nur der echte to= Eintrag,
**	delay= und stat= werden vernachlaessigt.
**
**	Es koennen zu einer Nachricht mehrere to= Zeilen auftreten,
**	eine to= Zeile kann aber auch mehrere Empfaenger haben.
**	Sollte der to-Eintrag in nlz bereits belegt sein, so
**	muss dieser erst weggeschrieben werden, bevor der neue
**	Eintrag eingesetzt wird.
**
**	Interpret the to= line.
**
**	All we need is the to=receiver part.
*/
X
void 
parseto(LZPTR lz, NLZPTR nlz)
{
X    char    delim = ',';	/* Delimiter fuer den Namen */
X    int     anfang = 3;
X    char   *src,
X           *dest;
X
X    if (lz->inh.rest[anfang] == '<') {
X	delim = '>';
X	anfang++;
X    }
X    src = &(lz->inh.rest[anfang]);
X    dest = &(nlz->to[0]);
X    if (*dest != '\0') {		/* There is one already */
X	writelog(nlz);			/* so save that line    */
X	memset(nlz->to, 0, MAXTOLEN + 1);
X    }
X    for (; *src;) {
X	while (*src != delim) {	/* Copy receiver */
X	    *dest = *src;
X	    dest++;
X	    src++;
X	}
X	*dest = '\0';
X	clean_address(&(nlz->to[0]));
X
#if DEBUG
X	printf("Empfaenger:%s<\n", nlz->to);
#endif
X
X	/* Ok, but there might be another receiver */
X	while (*src) {
X	    if (*src == ',' || *src == ' ' || *src == delim) {
X		src++;
X		continue;
X	    }
X
#if DEBUG
X	    printf("src:%s<\n", src);
X	    getchar();
#endif
X
X	    if (!strncmp(src, "delay=", 6))	/* We're done */
X		return;
X	    writelog(nlz);			/* This line to log */
X	    memset(nlz->to, 0, MAXTOLEN + 1);
X	    dest = &(nlz->to[0]);
X	    if (*src == '<') {
X		delim = '>';
X		src++;
X	    } else
X		delim = ',';
X	    break;
X	}
X    }
X
}
X
/*
**	Parsen einer Logzeile
**
**	Jede Zeile aus dem Log geht hier hinein. Sie wird dann
**	aufgrund des Kennzeichens zugeordnet. Pro versandte Nachricht
**	entsteht so wieder eine Logzeile, die einen definierten Aufbau
**	hat.
**	Falls eine Nachricht an mehrere Empfaenger geht, entstehen auch
**	mehrere Zeilen im neuen Log. Um die Zuordnung zum Zahler zu
**	ermoeglichen, wird aus dem to= oder from= der Zahlungspflichtige
**	in den Eintrag "zahler" eingesetzt.
**	-----------------------
**	See 'algorithm' at BOF.
*/
X
void 
parseline(LZPTR lz, NLZPTR nlz)
{
X    static char okz[16];
X    char    nkz[16];
X    int     nlztype;
X
X    memcpy(nkz, lz, 15);
X    nkz[15] = '\0';
X    if (strcmp(nkz, okz)) {	/* New message-id or date */
X	if (okz[0] != '\0') {	/* but not the first one! */
X	    writelog(nlz);
X	    memset(nlz, 0, sizeof (NLZ));
X	}
X	memcpy(okz, nkz, sizeof(okz));
X
#if DEBUG
X	printf("%s\n", nkz);
#endif
X    }
X    nlztype = gettype(lz);
X
#if DEBUG
X    printf("Type: %d\n", nlztype);
#endif
X
X    switch (nlztype) {
X      case FROM:		/* Next sender */
X	parsefrom(lz, nlz);
X	break;
X      case TO:
X	parseto(lz, nlz);
X	break;
X    }
}
X
void main(int argc, char *argv[])
{
X    LZ      ibuf;
X    NLZ     nlz;
X    char   *s,
X            oname[FNAMELEN];
X
X    if (argc != 3) {
X	fprintf(stderr, "%s %s\n", argv[0], VERSION);
X	fprintf(stderr, "usage: %s infile hostname\n", argv[0]);
X	fprintf(stderr, "convert infile to usable input for any\n");
X	fprintf(stderr, "accounting purpose.\n\n");
X	fprintf(stderr, "infile must be prepared by 'justlog'.\n");
X	exit(1);
X    }
X    if ((ifile = fopen(argv[1], "r")) == NULL) {
X	printf("\"%s\" not found.\n", argv[1]);
X	exit(1);
X    }
X    strcpy(oname, argv[1]);
X    s = strrchr(oname, '.');
X    if (s)
X	*s = '\0';
X    strcat(oname, ".out");
X
X    if ((ofile = fopen(oname, "w")) == NULL) {
X	printf("Can't open output \"%s\".\n", oname);
X	fclose(ifile);
X	exit(1);
X    }
X    strcpy(defaulthost, argv[2]);
X
X    while (fgets(ibuf.alles, MAXLINELEN, ifile)) {
X	clearline(&ibuf);
X	parseline(&ibuf, &nlz);
X    }
X    if (nlz.to[0] != '\0')	/* The last one is to be written */
X	writelog(&nlz);
X    fclose(ifile);
X    fclose(ofile);
}
SHAR_EOF
chmod 0600 Mailutil/auswlog.c ||
echo 'restore of Mailutil/auswlog.c failed'
Wc_c="`wc -c < 'Mailutil/auswlog.c'`"
test 13260 -eq "$Wc_c" ||
	echo 'Mailutil/auswlog.c: original size 13260, current size' "$Wc_c"
fi
# ============= Mailutil/justlog.c ==============
if test -f 'Mailutil/justlog.c' -a X"$1" != X"-c"; then
	echo 'x - skipping Mailutil/justlog.c (File already exists)'
else
echo 'x - extracting Mailutil/justlog.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Mailutil/justlog.c' &&
/*
**	Filter zur Umformatierung von syslog's
**	zur Auswertung und Berechnung der Mailkosten
**
**	The algorithm:
**
**	Get a line from syslog
**		Convert the date from 'verbose' to numeric format.
**		Drop stuff like 'new aliases ...'
**		Get the AA.. part of the line.
**		Get the useful information (from=, to=, size=, stat=)
**		Drop lines, if the to= line does not contain stat=Sent.
**		Drop lines with message-id=
**		Move the AA.. part to the beginning of the line.
**			(We will need this later for sorting the stuff!)
**		Output the converted line.
**	until eof().
*/
X
#include <stdio.h>
#include <string.h>
X
#include "mailutil.h"
X
/* Max. Length exspected in the syslog */
#define LINELEN 256
X
#define VERSION "1.0"
X
static char *monate[] =
{
X    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
X    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
X    NULL
};
X
/*
**	Teil eins der Konvertierung.
**
**	1. Monat aus dem Klartext in lfd. Nummer uebersetzen
**	2. Zeilen wie newaliases ausfiltern
**	3. Die PID von sendmail ausfiltern
**	4. Im Datenteil (hinter AA...) alle Blanks loeschen
**	5. Blanks im Datum durch ':' ersetzen
**
**	Uebergabeparameter:
**	char *in	Zeiger auf Eingabezeile
**	char *out	Zeiger auf Ausgabezeile
**
**	Rueckgabe:
**	0		Konvertierung ok.
**	1		Zeile ist zu verwerfen.
**
**	-------------------
**	Part one of conversion
**
**	Translate verbose date to numbers
**	filter lines like 'blah new aliases' etc.
**	strip blanks from the relevant part after AA...
**	replace all blanks in date by ':'
*/
X
int 
convert_buff(char *in, char *out)
{
X    int     i;
X    char   *s,
X           *so;
X
X    for (i = 0; monate[i]; i++) {
X	if (!strncmp(in, monate[i], 3)) {
X	    sprintf(out, "%02d", i + 1);
X	    break;
X	}
X    }
X    if (monate[i] == NULL)	/* should never happen */
X	strcpy(out, "13");
X    s = in + 3;			/* start behind month */
X    so = out + 2;
X    for (i = 0; i < 12; i++) {	/* and copy date */
X	*so = *s;
X	s++;
X	so++;
X    }
X    /* next thing to get is the AA... part */
X    s = in + 15;
X    while (*s) {
X	if ((*s == ':') && (!strncmp(s, ": AA", 4)))
X	    break;
X	s++;
X    }
X    if (!*s)			/* No AA... found */
X	return 1;
X    *so++ = ':';
X    s += 2;
X    while (*s) {		/* copy rest of line */
X	*so = *s;
X	so++;
X	s++;
X    }
X    *so = '\0';
X    *(out + 14) = ':';		/* insert ':' after time */
X    if (*(out + 3) == ' ')	/* make day numeric */
X	*(out + 3) = '0';
X    for (s = out, i = 0; *s && (i < 15); i++, s++)
X	if (*s == ' ')
X	    *s = ':';
X    return 0;
}
X
/*
**	"Verdrehen" einer Zeile wg, Sortierung
**
**	1. Die AA... - Angabe von sendmail wird nach vorne gerueckt.
**	2. Zeilen mit "message-id" oder "locked" werden verworfen.
**	3. to= Zeilen, in denen nicht stat=Sent steht, werden auch
**	   verworfen.
**
**	Uebergabeparameter:
**	char buf[]	Adresse der Zeile
**
**	Rueckgabe:
**	Adresse des statischen Buffers mit gedrehter Zeile,
**	im "verwerflichen" Fall NULL
**	-------------------
**	Make lines sortable
**
**	Move AA... as key to beginning of line
**	drop lines containing 'message-id=' or 'locked'
**	drop lines containing 'to=' AND 'stat!=Sent'
**		i.e. Deferred, etc.
*/
X
char   *
drehbuf(char buf[])
{
X    static char workbuf[LINELEN + 1],
X           *s;
X
X    sprintf(workbuf, "%8.8s%15.15s%s", &buf[15], buf, &buf[24]);
X    s = &workbuf[23];
X    if ((!strncmp(s, "locked", 6)) || (!strncmp(s, "message-id=", 11)))
X	return NULL;
X    if (!strncmp(s, "to=", 3)) {
X    	/* This line should contain stat=Sent */
X	while (*s) {
X	    if (!strncmp("stat=", s, 5)) {
X		s += 5;
X		if (strncmp("Sent", s, 4))
X		    return NULL;
X	    }
X	    s++;
X	}
X    }
X    return workbuf;
X
}
X
void 
main(int argc, char *argv[])
{
X    char    inbuf[LINELEN + 1],
X            outbuf[LINELEN + 1],
X           *s;
X
X    if (isatty(fileno(stdin)) || isatty(fileno(stdout))) {
X	fprintf(stderr, "%s %s\n", argv[0], VERSION);
X	fprintf(stderr, "A filter for use on syslog for gathering\n");
X	fprintf(stderr, "information about mail costs. Use as filter!\n");
X	exit(1);
X    }
X    while (gets(inbuf)) {
X	if (strlen(inbuf) < 30)	/* useless information */
X	    continue;
X	if (!convert_buff(inbuf, outbuf)) {
X	    s = drehbuf(outbuf);
X	    if (s)
X		puts(s);
X	}
X    }
X
}
SHAR_EOF
chmod 0644 Mailutil/justlog.c ||
echo 'restore of Mailutil/justlog.c failed'
Wc_c="`wc -c < 'Mailutil/justlog.c'`"
test 4176 -eq "$Wc_c" ||
	echo 'Mailutil/justlog.c: original size 4176, current size' "$Wc_c"
fi
# ============= Mailutil/mailstat1.awk ==============
if test -f 'Mailutil/mailstat1.awk' -a X"$1" != X"-c"; then
	echo 'x - skipping Mailutil/mailstat1.awk (File already exists)'
else
echo 'x - extracting Mailutil/mailstat1.awk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Mailutil/mailstat1.awk' &&
#########################################################################
#
#	Rechnungserstellung fuer Electronic Mail
#
#	Version 1.0
#
#	(c) 1990 by RMI Nachrichtentechnik GmbH
#
#########################################################################
X
X
BEGIN {
X	FS="~";
}
X
#########################################################################
X
/MAILER/ {
X	next
}
/\/dev\/null/ {
X	next
}
/news/ {
X	next
}
X
{
X	if (substr($0,1,1) == "~")
X		next;
X	mails[$1]++;
X	if ($9 == $10) {
X		localmails[$1]++;
X		localbytes[$1] += $6;
X	}
X	else {
X		remotemails[$1]++;
X		remotebytes[$1]+= $6;
X	}
X	bytes[$1] += $6;
}
X
#########################################################################
X
END {
X	max = 0;
X	tmail = 0;
X	tbytes = 0;
X	for (usr in mails) {
X		tmail += mails[usr];
X		tbytes += bytes[usr];
X		if (bytes[usr] > max)
X			max = bytes[usr];
X	}
X	
X	for (usr in mails) {
X		end = bytes[usr] / max * 60;
X		if (end > 2) {		# don't graph the underdogs
X			printf("%-14s", usr);
X			for (i = 0; i < end; i++)
X				printf("#");
X			print("");
X		}
X	}
X	print("\n---\n");
X	print("User            Mails    Bytes");
X	print("------------------------------");
X	for (usr in mails) {
X		printf("%-14s %6d %8d\n", usr, mails[usr], bytes[usr]);
X	}
X	print("------------------------------");
X	printf("%-14s %6d %8d\n", "Total", tmail, tbytes);
X	print("==============================");
X	
}
SHAR_EOF
chmod 0644 Mailutil/mailstat1.awk ||
echo 'restore of Mailutil/mailstat1.awk failed'
Wc_c="`wc -c < 'Mailutil/mailstat1.awk'`"
test 1359 -eq "$Wc_c" ||
	echo 'Mailutil/mailstat1.awk: original size 1359, current size' "$Wc_c"
fi
# ============= Mailutil/mailstat2.awk ==============
if test -f 'Mailutil/mailstat2.awk' -a X"$1" != X"-c"; then
	echo 'x - skipping Mailutil/mailstat2.awk (File already exists)'
else
echo 'x - extracting Mailutil/mailstat2.awk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Mailutil/mailstat2.awk' &&
#########################################################################
#
#	Rechnungserstellung fuer Electronic Mail
#
#	Version 1.0
#
#	(c) 1990 by RMI Nachrichtentechnik GmbH
#
#########################################################################
X
#function round(i) {
#	i = int((i*100) + 0.5)/100;
#	return i;
#}
X
#########################################################################
X
BEGIN {
X	FS="~";
X	if (host == "")
X		host = "infohh";
X	lmail = 0.1;
X	lbyte = 0.00001;
X	rmail = 0.05;
X	rbyte = 0.0002;
X	mwst = 0.14;
X	rechnr = 0;
X	hostlmail = 0.0;
X	hostlkbyte = 0.0;
X	hostrmail = 0.0;
X	hostrkbyte = 0.0;
X	hostmwst = 0.0;
}
X
#########################################################################
X
/MAILER-DAEMON/ {
X	next
}
/\/dev\/null/ {
X	next
}
/news/ {
X	next
}
X
{
X	if (substr($0,1,1) == "~")
X		next;
X
X	mails[$1]++;
X	if ($9 == $10) {
X		localmails[$1]++;
X		localbytes[$1] += $6;
X	}
X	else {
X		remotemails[$1]++;
X		remotebytes[$1]+= $6;
X	}
X	bytes[$1] += $6;
}
X
#########################################################################
X
END {
X	i = 99;
X	for (usr in mails) {
X		if (i > 20) {
X			print("                        l o c a l      r e m o t e");
X			print("Username              Mails    Bytes  Mails    Bytes    local   remote    total");
X			print("-------------------------------------------------------------------------------");
X			i = 0;
X		}
X		lcost = localmails[usr] * lmail + localbytes[usr] * lbyte;
X		rcost = remotemails[usr] * rmail + remotebytes[usr] * rbyte;
X		printf("%-20s %6d %8d %6d %8d %8.2f %8.2f %8.2f\n", usr, \
X			localmails[usr], localbytes[usr], \
X			remotemails[usr], remotebytes[usr], \
X			lcost, rcost, lcost + rcost);
X		i++;
X	}
X	print("-------------------------------------------------------------------------------");
X	
}
SHAR_EOF
chmod 0644 Mailutil/mailstat2.awk ||
echo 'restore of Mailutil/mailstat2.awk failed'
Wc_c="`wc -c < 'Mailutil/mailstat2.awk'`"
test 1769 -eq "$Wc_c" ||
	echo 'Mailutil/mailstat2.awk: original size 1769, current size' "$Wc_c"
fi
# ============= Mailutil/mailutil.h ==============
if test -f 'Mailutil/mailutil.h' -a X"$1" != X"-c"; then
	echo 'x - skipping Mailutil/mailutil.h (File already exists)'
else
echo 'x - extracting Mailutil/mailutil.h (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Mailutil/mailutil.h' &&
/*
**	Prototypes for external functions
*/
X
void exit(int result);
long atol(char *src);
int isatty(int fh);
X
SHAR_EOF
chmod 0644 Mailutil/mailutil.h ||
echo 'restore of Mailutil/mailutil.h failed'
Wc_c="`wc -c < 'Mailutil/mailutil.h'`"
test 110 -eq "$Wc_c" ||
	echo 'Mailutil/mailutil.h: original size 110, current size' "$Wc_c"
fi
exit 0

-- 
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
| Auch: wsiebeck@rmi.de         |       Hier koennte      |
|        siebeck@infoac.rmi.de  |       Ihre Anzeige      |
|        root@atreju.rmi.de     |          stehen         |
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=