1#! @SCRIPT_SH@
2#	$Id$
3#
4# HylaFAX Facsimile Software
5#
6# Copyright (c) 1993-1996 Sam Leffler
7# Copyright (c) 1993-1996 Silicon Graphics, Inc.
8# HylaFAX is a trademark of Silicon Graphics
9#
10# Permission to use, copy, modify, distribute, and sell this software and
11# its documentation for any purpose is hereby granted without fee, provided
12# that (i) the above copyright notices and this permission notice appear in
13# all copies of the software and related documentation, and (ii) the names of
14# Sam Leffler and Silicon Graphics may not be used in any advertising or
15# publicity relating to the software without the specific, prior written
16# permission of Sam Leffler and Silicon Graphics.
17#
18# THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
19# EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
20# WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
21#
22# IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
23# ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
24# OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
25# WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
26# LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
27# OF THIS SOFTWARE.
28#
29
30#
31# Script to run periodically from cron:
32#
33# 0. Print transmit and receive statistics.
34# 1. Purge info directory of old remote machine capabilities.
35# 2. Purge old session logs from the log directory.
36# 3. Purge old files in the received facsimile queue.
37# 4. Notify about sites that currently have jobs rejected.
38#
39
40AGEINFO=30			# purge remote info after 30 days inactivity
41AGELOG=30			# keep log info for last 30 days
42AGERCV=7			# purge received facsimile after 7 days
43AGETMP=1			# purge orphaned temp files after 1 day
44FAXUSER=@FAXUID@		# owner of log files
45LOGMODE=0644			# mode for log files
46XFERLOG=etc/xferfaxlog		# HylaFAX xferfaxlog file location
47LAST=etc/lastrun		# file where time+date of last run recorded
48
49cd @SPOOL@			# NB: everything below assumes this
50. bin/common-functions
51
52test -f etc/setup.cache || {
53    SPOOL=`pwd`
54    cat<<EOF
55
56FATAL ERROR: $SPOOL/etc/setup.cache is missing!
57
58The file $SPOOL/etc/setup.cache is not present.  This
59probably means the machine has not been setup using the faxsetup(@MANNUM1_8@)
60command.  Read the documentation on setting up HylaFAX before you
61startup a server system.
62
63EOF
64    hfExit 1
65}
66. etc/setup.cache
67
68RM="$RM -f"
69TEE=tee
70UPDATE="date +'%D %H:%M' >$LAST"
71
72# security
73SetupPrivateTmp
74
75JUNK=$TMPDIR/faxjunk$$         # temp file used multiple times
76AWKTMP=$TMPDIR/faxawk$$                # temp file for awk program
77
78while [ x"$1" != x"" ] ; do
79    case $1 in
80    -n)	    RM=":" TEE=":" CP=":" MV=":" CHOWN=":" CHMOD=":" UPDATE=":";;
81    -l)	    shift; LASTRUN="$1";;
82    -info)  shift; AGEINFO="$1";;
83    -log)   shift; AGELOG="$1";;
84    -rcv)   shift; AGERCV="$1";;
85    -tmp)   shift; AGETMP="$1";;
86    -mode)  shift; LOGMODE="$1";;
87    -*)	    echo "Usage: $0 [-n] [-l lastrun] [-info days] [-log days] [-rcv days] [-tmp days] [-mode logmode]"; hfExit 1;;
88    esac
89    shift
90done
91
92test -z "$LASTRUN" && LASTRUN=`$CAT $LAST 2>/dev/null`
93
94echo "Facsimile transmitted since $LASTRUN:"
95echo ""
96$SBIN/xferfaxstats -since "$LASTRUN"
97echo ""
98
99echo "Facsimile transmitted in the last seven days:"
100echo ""
101$SBIN/xferfaxstats -age 7
102echo ""
103
104echo "Facsimile received since $LASTRUN:"
105echo ""
106$SBIN/recvstats -since "$LASTRUN"
107echo ""
108
109echo "Facsimile received in the last seven days:"
110echo ""
111$SBIN/recvstats -age 7
112echo ""
113
114echo "Report failed calls and associated session logs:"
115$CAT>$AWKTMP<<'EOF'
116#
117# Sort array a[l..r]
118#
119function qsort(a, l, r) {
120    i = l;
121    k = r+1;
122    item = a[l];
123    for (;;) {
124	while (i < r) {
125            i++;
126	    if (a[i] >= item)
127		break;
128        }
129	while (k > l) {
130            k--;
131	    if (a[k] <= item)
132		break;
133        }
134        if (i >= k)
135	    break;
136	t = a[i]; a[i] = a[k]; a[k] = t;
137    }
138    t = a[l]; a[l] = a[k]; a[k] = t;
139    if (k != 0 && l < k-1)
140	qsort(a, l, k-1);
141    if (k+1 < r)
142	qsort(a, k+1, r);
143}
144
145function cleanup(s)
146{
147    gsub("\"", "", s);
148    gsub("^ +", "", s);
149    gsub(" +$", "", s);
150    return s;
151}
152
153function setupToLower()
154{
155    upperRE = "[ABCDEFGHIJKLMNOPQRSTUVWXYZ]";
156    upper["A"] = "a"; upper["B"] = "b"; upper["C"] = "c";
157    upper["D"] = "d"; upper["E"] = "e"; upper["F"] = "f";
158    upper["G"] = "g"; upper["H"] = "h"; upper["I"] = "i";
159    upper["J"] = "j"; upper["K"] = "k"; upper["L"] = "l";
160    upper["M"] = "m"; upper["N"] = "n"; upper["O"] = "o";
161    upper["P"] = "p"; upper["Q"] = "q"; upper["R"] = "r";
162    upper["S"] = "s"; upper["T"] = "t"; upper["U"] = "u";
163    upper["V"] = "v"; upper["W"] = "w"; upper["X"] = "x";
164    upper["Y"] = "y"; upper["Z"] = "z";
165}
166
167function toLower(s)
168{
169    if (match(s, upperRE) != 0) {
170	do {
171	    c = substr(s, RSTART, 1);
172	    gsub(c, upper[c], s);
173	} while (match(s, upperRE));
174    }
175    return s;
176}
177
178#
179# Accumulate a statistics record.
180#
181function acct(dest, status, datetime, commid)
182{
183    split(datetime, a, " ");
184    split(a[1], b, "/");
185    t = b[3] b[1] b[2] a[2];
186    if (t < LASTt)
187	return;
188    status = cleanup(status);
189    if (length(status) > 11) {
190	msg = toLower(substr(status, 1, 11));
191	if (callFailed[msg])
192	    return;
193    }
194    if (status != "") {
195	dest = cleanup(dest);
196	datetime = cleanup(datetime);
197	for (i = 0; i < nerrmsg; i++)
198	    if (errmsg[i] == status)
199		break;
200	if (i == nerrmsg)
201	    errmsg[nerrmsg++] = status;
202	if (errinfo[dest] == "")
203	    errinfo[dest] = datetime "@" i "/" commid;
204	else
205	    errinfo[dest] = errinfo[dest] "|" datetime "@" i "/" commid;
206    }
207}
208
209function printOldTranscript(canon, datetime)
210{
211    gsub("[^0-9]", "", canon);
212    split(datetime, parts, " ");
213    split(parts[1], p, "/");
214    cmd = sprintf(TRANSCRIPT, canon, months[p[1]], p[2], parts[2]);
215    system(cmd);
216}
217
218function printTranscript(commid)
219{
220    printf "\n    ---- Transcript of session follows ----\n\n"
221    comFile = "log/c" commid;
222    if ((getline <comFile) > 0) {
223	do {
224	    if (index($0, "-- data") == 0)
225		print $0
226	} while ((getline <comFile) > 0);
227	close(comFile);
228    } else
229	print "No transcript available.";
230}
231
232BEGIN		{ FS="\t";
233		  callFailed["Busy signal"] = 1;
234		  callFailed["Unknown pro"] = 1;
235		  callFailed["No carrier "] = 1;
236		  callFailed["No local di"] = 1;
237		  callFailed["No answer f"] = 1;
238		  callFailed["Job aborted"] = 1;
239		  callFailed["Invalid dia"] = 1;
240		  callFailed["Can not loc"] = 1;
241		  months["01"] = "Jan"; months["02"] = "Feb";
242		  months["03"] = "Mar"; months["04"] = "Apr";
243		  months["05"] = "May"; months["06"] = "Jun";
244		  months["07"] = "Jul"; months["08"] = "Aug";
245		  months["09"] = "Sep"; months["10"] = "Oct";
246		  months["11"] = "Nov"; months["12"] = "Dec";
247
248		  split(LASTRUN, a, " ");
249		  split(a[1], b, "/");
250		  LASTt = b[3] b[1] b[2] a[2];
251		  setupToLower();
252		}
253$2 == "SEND" && NF == 9  { acct($4,  $9, $1, ""); }
254$2 == "SEND" && NF == 11 { acct($5, $11, $1, ""); }
255$2 == "SEND" && NF == 12 { acct($6, $12, $1, ""); }
256$2 == "SEND" && NF == 13 { acct($7, $13, $1, $3); }
257$2 == "SEND" && NF == 14 { acct($7, $14, $1, $3); }
258END		{ nsorted = 0;
259		  for (key in errinfo)
260		      sorted[nsorted++] = key;
261		  qsort(sorted, 0, nsorted-1);
262		  for (k = 0; k < nsorted; k++) {
263		      key = sorted[k];
264		      n = split(errinfo[key], a, "|");
265		      for (i = 1; i <= n; i++) {
266			  if (split(a[i], b, "@") != 2)
267			      continue;
268			  if (split(b[2], d, "/") != 2)
269			      continue;
270			  printf "\n"
271			  printf "To: %-16.16s  Date: %s\n", key, b[1]
272			  printf "Error: %s\n\n", errmsg[d[1]]
273
274			  if (d[2] == "")
275			      printOldTranscript(key, b[1]);
276			  else
277			      printTranscript(d[2]);
278		      }
279		  }
280		}
281EOF
282$AWK -f $AWKTMP -v LASTRUN="$LASTRUN" TRANSCRIPT="\
283    LOGFILE=log/%s;\
284    TMP=$TMPDIR/faxlog\$\$;\
285    if [ -f \$LOGFILE ]; then\
286	$SED -n -e '/%s %s %s.*SESSION BEGIN/,/SESSION END/p' \$LOGFILE |\
287	$SED -e '/start.*timer/d'\
288	     -e '/stop.*timer/d'\
289	     -e '/-- data/d'\
290	     -e 's/^/    /' >\$TMP;\
291    fi;\
292    if [ -s \$TMP ]; then\
293	$CAT \$TMP;\
294    else\
295	echo '    No transcript available.';\
296    fi;\
297    rm -f \$TMP\
298    " $XFERLOG
299echo ""
300
301#
302# Collect phone numbers that haven't been called
303# in the last $AGEINFO days.  We use this to clean
304# up the info files.
305#
306find info -type f -ctime +$AGEINFO -print >$JUNK
307
308if [ -s $JUNK ]; then
309echo "Purge remote device capabilities older than $AGEINFO days:"
310INFOTMP=info/tmp$$
311for i in `$CAT $JUNK`; do
312    if $GREP '^&' $i >/dev/null 2>&1; then
313	echo "    $i (saving locked down values)"
314	$SED '/^[^&]/d' $i > $INFOTMP
315	$MV $INFOTMP $i; $CHOWN ${FAXUSER} $i; $CHMOD ${LOGMODE} $i
316    elif [ -f $i ]; then
317	echo "    $i"
318	$RM $i
319    fi
320done
321$RM $INFOTMP			# for -n option
322else
323    echo "Nothing to purge in info directory."
324fi
325echo ""
326
327find log -type f -mtime +$AGELOG ! -name seqf -print >$JUNK
328if [ -s $JUNK ]; then
329    echo "Purge session logs older than $AGELOG days:"
330    for i in `$CAT $JUNK`; do
331	echo "    Remove $i"
332	$RM $i
333    done
334    echo ""
335fi
336
337echo "Truncate merged session logs older than $AGELOG days:"
338
339$CAT>$AWKTMP<<'EOF'
340#
341# Setup date conversion data structures.
342#
343function setupDateTimeStuff()
344{
345    Months["Jan"] =  0; Months["Feb"] =  1; Months["Mar"] =  2;
346    Months["Apr"] =  3; Months["May"] =  4; Months["Jun"] =  5;
347    Months["Jul"] =  6; Months["Aug"] =  7; Months["Sep"] =  8;
348    Months["Oct"] =  9; Months["Nov"] = 10; Months["Dec"] = 11;
349
350    daysInMonth[ 0] = 31; daysInMonth[ 1] = 28; daysInMonth[ 2] = 31;
351    daysInMonth[ 3] = 30; daysInMonth[ 4] = 31; daysInMonth[ 5] = 30;
352    daysInMonth[ 6] = 31; daysInMonth[ 7] = 31; daysInMonth[ 8] = 30;
353    daysInMonth[ 9] = 31; daysInMonth[10] = 30; daysInMonth[11] = 31;
354
355    FULLDAY = 24 * 60 * 60;
356}
357
358#
359# Convert MMM DD hh:mm:ss.ms to seconds.
360# NB: this does not deal with leap years.
361#
362function cvtTime(s)
363{
364    mon = Months[substr(s, 1, 3)];
365    yday = substr(s, 5, 2) - 1;
366    for (i = 0; i < mon; i++)
367	yday += daysInMonth[i];
368    s = substr(s, 7);
369    t = i = 0;
370    for (n = split(s, a, ":"); i++ < n; )
371	t = t*60 + a[i];
372    return yday*FULLDAY + t;
373}
374
375BEGIN			{ setupDateTimeStuff();
376			  KEEP = cvtTime(TODAY) - AGE*FULLDAY;
377			  lastRecord = "$"
378			}
379			{ if (cvtTime($1 ":" $2 ":" $3) >= KEEP) {
380			      lastRecord = NR; exit
381			  }
382			}
383END			{ print lastRecord }
384EOF
385TODAY="`date +'%h %d %T'`"
386
387for i in log/[0-9]*; do
388    if [ -f $i ]; then
389	START=`$AWK -F: -f $AWKTMP -v TODAY="$TODAY" -v AGE=$AGELOG $i` 2>/dev/null
390	if [ "$START" != 1 ]; then
391	    $SED 1,${START}d $i >$JUNK
392	    if [ -s $JUNK ]; then
393		$MV $JUNK $i; $CHOWN ${FAXUSER} $i; $CHMOD ${LOGMODE} $i
394		ls -ls $i
395	    else
396		echo "    Remove empty $i"
397		$RM $i
398	    fi
399	fi
400    fi
401done
402echo ""
403
404#
405# Purge old stuff from the receive queue.
406#
407find recvq -type f -mtime +$AGERCV ! -name seqf -print >$JUNK
408if [ -s $JUNK ]; then
409    echo "Purge received facsimile older than $AGERCV days:"
410    (for i in `$CAT $JUNK`; do
411	$SBIN/faxinfo $i
412	$RM $i >/dev/null 2>&1
413    done) | $AWK -F: '
414/recvq.*/	{ file=$1; }
415/Sender/	{ sender = $2; }
416/Pages/		{ pages = $2; }
417/Quality/	{ quality = $2; }
418/Received/	{ date = $2;
419		  for (i = 3; i <= NF; i++)
420		      date = date ":" $i;
421		  printf "    %-18.18s %21.21s %2d %8s%s\n", \
422			file, sender, pages, quality, date;
423		}
424'
425else
426    echo "Nothing to purge in receive queue."
427fi
428echo ""
429
430#
431# Purge old stuff from the temp directory.
432#
433find tmp -type f -mtime +$AGETMP -print >$JUNK
434if [ -s $JUNK ]; then
435    echo "Purge tmp files older than $AGETMP days:"
436    for i in `$CAT $JUNK`; do
437      if [ -f $i ]; then
438	echo "    Remove $i"
439	$RM $i
440      fi
441    done
442else
443    echo "Nothing to purge in the tmp directory."
444fi
445echo ""
446
447#
448# Note destinations whose jobs are currently being rejected.
449#
450find info cinfo -type f -newer $LAST -print 2>/dev/null >$JUNK
451if [ -s $JUNK ]; then
452	echo "Destinations being rejected (added since $LASTRUN):"
453	(for i in `$CAT $JUNK`; do $GREP "^rejectNotice:" $i; done) | $AWK -F: '
454		{ reason = $3;
455		  for (i = 4; i <= NF; i++)
456			reason = reason ":" $i;
457		  sub("^[ ]*", "", reason);
458		  if (reason != "") {
459		      sub(".*/", "", $1);
460		      printf "Rejecting jobs to +%s because \"%s\".\n", \
461			    $1, reason;
462		  }
463		}
464'
465fi
466
467$RM $LAST; eval $UPDATE
468
469# cleanup
470hfExit 0
471