[comp.sources.unix] v24i039: Email fax-sending package, Part01/05

rsalz@uunet.uu.net (Rich Salz) (03/14/91)

Submitted-by: klaus u schallhorn <cnix!klaus>
Posting-number: Volume 24, Issue 39
Archive-name: faxpax/part01

This  is   faxpak   [Copyright   (C)   1991   klaus   schallhorn,
klaus@cnix.uucp]

faxpak currently runs on Suns only - not because it uses any spe-
cial  SunOs features, but because I don't have access to anything
else. I developed faxpak mainly on a 4/60, then  moved  it  to  a
3/60 that does all our spooling.

Apart from [temporary] hardware and  typeface  restrictions  [see
below]  faxpak is quite flexible. It allows faxes to be sent from
any networked machine. It allows aliases and distribution  lists.
Entries  in  such  lists needing a special "for the attention of"
string are forked out as separate jobs.  If you have more than  1
fax  modem,  faxpak  automatically  shares the load between these
modems. This includes splitting of batches to distribution  lists
into several queues.

faxpak enforces permissions. You can set faxpak up to send  local
faxes  any  time, long distance ones and international ones to be
sent at cheap rates only. You can enforce the sending of faxes to
any  given  phone  number at fixed times. And you can permit some
users to override these time restrictions.

faxpak was created when the post office  increased  our  contract
postage rates to an almost insane level. We therefore decided [as
a small publishing house] to  deliver  our  newsletter  by  other
means.  We  now  deliver  by mail(1) to subscribers' mailboxes at
various online and video text systems, we allow direct modem con-
nection to mbox [our 3/60] and we fax our newsletter.

faxpak is used for oneoff faxes [and only if I can't email],  and
has  been  used  three  times  [we  publish every other week] for
batches of ~150 faxes @ five pages each to our  subscriber  list.
Batches of this size take a good two nights to send out, although
the pure transmission time is just under 12 hours  [the  rest  is
engaged  phone lines, repeats because transmission errors, logins
sneaking in on the faxmodems, etc].

faxpak currently supports touchbase's 2496 fax modem  and  others
that  use  or emulate the sierra type fax chipset, such as zoom's
mx 2400s. Early december I found someone willing  and  supposedly
able  to ship a beta class2 modem. Unfortunately it seems to have
been sent by bicycle.  It's supposed to be here "real soon now".

For this reason faxpak does [not yet] support other  modems,  nor
does it allow incoming faxes.

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  MANIFEST faxclient faxhost faxhost/faxcleanup.c
#   faxhost/faxfonts faxhost/wiring.c
# Wrapped by rsalz@litchi.bbn.com on Wed Mar 13 14:08:00 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 1 (of 5)."'
if test -f 'MANIFEST' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'MANIFEST'\"
else
  echo shar: Extracting \"'MANIFEST'\" \(1250 characters\)
  sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
X   File Name		Archive #	Description
X----------------------------------------------------------
XMANIFEST                   1	This shipping list
XFaxConfig                  2	chmod +x or sh FaxConfig
XHowto.Install              5	basic install info
XReadme                     4	describes faxpak
Xfaxclient                  1	
Xfaxclient/fax.c            3	user interface [wot - no vindohs?]
Xfaxhost                    1	
Xfaxhost/faxcleanup.c       1	leftover disposer
Xfaxhost/faxfonts           1	kit to roll your own 8bit charset
Xfaxhost/faxfonts/diy_codep861.def  5	
Xfaxhost/faxfonts/diy_ibmpc.def  5	
Xfaxhost/faxfonts/diy_iso.def  5	
Xfaxhost/faxfonts/diykit.c  2	
Xfaxhost/faxfonts/diykit.h  4	
Xfaxhost/faxfonts/hires.uue  4	compressed font data	
Xfaxhost/faxfonts/lores.uue  4	same
Xfaxhost/faxlog.c           5	
Xfaxhost/fntwrite.c         4	
Xfaxhost/pbmtog3.c          4	REPLACE pbmplus/pbmtog3 WITH THIS ONE !
Xfaxhost/sendfax.c          2	
Xfaxhost/sierracmd.h        4	
Xfaxhost/spool.fax.c        3	spooler
Xfaxhost/texttopbm.c        5	uses the diy font kit
Xfaxhost/wiring.c           1	
Xsample.dot.faxrc           5	free samples
Xsample.fax.aliases         3	
Xsample.fax.list            4	
Xwritefax.man               3	tech specs for sierra type modems
END_OF_FILE
  if test 1250 -ne `wc -c <'MANIFEST'`; then
    echo shar: \"'MANIFEST'\" unpacked with wrong size!
  fi
  # end of 'MANIFEST'
fi
if test ! -d 'faxclient' ; then
    echo shar: Creating directory \"'faxclient'\"
    mkdir 'faxclient'
fi
if test ! -d 'faxhost' ; then
    echo shar: Creating directory \"'faxhost'\"
    mkdir 'faxhost'
fi
if test -f 'faxhost/faxcleanup.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'faxhost/faxcleanup.c'\"
else
  echo shar: Extracting \"'faxhost/faxcleanup.c'\" \(5045 characters\)
  sed "s/^X//" >'faxhost/faxcleanup.c' <<'END_OF_FILE'
X#include <stdio.h>
X#include <sys/time.h>
X#include <dirent.h>
X
X#include "../faxconfig.h"
X
Xstruct	FAX fax;
Xchar	io[512], tmp[256];
Xtime_t	now;
Xint	maxage, maxtry, blah;
X
X/*
X	faxcleanup
X
X	runs once a day to rm old garbage that might be left over.
X	all the functions here are really shortened stubs picked
X	mostly from sendfax.c [I plan to do a faxlib later].
X
X	first released version 0.99 [desperado version]
X	cleaned up Jan 28nd '91,
X	Copyright (C) 1991, klaus schallhorn, klaus@cnix.uucp
X
X	Permission to use, copy, modify, and distribute this software 
X	and its documentation for any purpose and without fee is hereby 
X	granted, provided that the above copyright notice appear in 
X	all copies and that both that copyright notice and this permission 
X	notice appear in supporting documentation. 
X
X	This software is provided "as is" without express or implied warranty.
X*/
X
Xread_config()
X{
X	FILE *cfg;
X	char a[256], b[80];
X	int i;
X
X	maxage = maxtry = 0;
X	sprintf(a, "%s/fax.config", FAXLIB);
X	if ((cfg = fopen(a, "r")) == NULL)
X	{
X		fax_log(ERROR,"faxcleanup: can't open %s/fax.config\n",FAXLIB);
X		return(ERROR);
X	}
X
X	while (fgets(io, 254, cfg) != NULL)
X	{
X		if (io[0] == '#' || (sscanf(io, "%s %s",a,b) < 1))
X			continue;
X		if (!strcmp(a, "maxage"))
X			maxage = atoi(b);
X		else if (!strcmp(a, "maxtry"))
X			maxtry = atoi(b);
X		if (maxage && maxtry)
X			break;
X	}
X	fclose(cfg);
X	return(0);
X}
Xread_job(fp, xn)
XFILE *fp;
Xchar *xn;
X{
X	int i;
X
X	if (getint(fp,&fax.spooled))	/* spool timestamp */
X		return(ERROR);
X	if (getint(fp,&fax.tries)
X		|| getstring(fp,fax.user) || getint(fp,&fax.uid)
X		|| getint(fp,&fax.now) || getint(fp,&fax.mail)
X		|| getstring(fp,tmp)	/* type, don't need to know */
X		|| getint(fp,&fax.hires)/* hires, NEEDED FOR SENDFAX */
X		|| getstring(fp,tmp)	/* font, don't need to know  */
X		|| getstring(fp,tmp))	/* retfax, don't need to know  */
X		return(ERROR);
X	if ((fax.phone_nos = getphone(fp)) <= 0)
X		return(ERROR);
X	if (getstring(fp,fax.dname))
X		return(ERROR);
X	strcpy(fax.xname, xn);
X	if (getint(fp,&fax.pages))
X		return(ERROR);
X#ifdef	JOBID
X	if (getstring(fp,fax.jobid))
X		return(ERROR);
X#endif
X	return(0);
X}
Xgetstring(fp,s)
XFILE *fp;
Xchar *s;
X{
X	char buf[80];
X
X	if (fgets(io, 80, fp) == NULL)
X		return(ERROR);
X	if (sscanf(io, "%s %s",buf,s) != 2)
X		return(ERROR);
X	return(0);
X}
Xgetint(fp,n)
XFILE *fp;
Xint *n;
X{
X	long l;
X
X	if (fgets(io, 80, fp) == NULL)
X		return(ERROR);
X	if (sscanf(io, "%s %ld",tmp,&l) != 2)
X		return(ERROR);
X	*n = l;
X	return(0);
X}
Xgetphone(fp)
XFILE *fp;
X{
X	char phone[80];
X	long oldpos;
X
X	for (;;)
X	{
X		oldpos = ftell(fp);
X		if (fgets(io, 80, fp) == NULL)
X			return(ERROR);
X		if (io[0] == '#')
X			continue;
X		if (!strncmp(io, "data", 4))
X		{
X			fseek(fp, oldpos, 0);
X			break;
X		}
X		if (sscanf(io, "%s %s",tmp,phone) != 2)
X			return(ERROR);
X	}
X	return(TRUE);
X}
Xmain()
X{
X	DIR *dirp;
X	struct dirent *dp;
X	FILE *fp, *pp;
X	time_t days;
X	char me[80];
X
X	blah = 0;
X	if (chdir(FAXSPOOL))	/* quick one, job done */
X	{
X		fax_log(ERROR,"faxcleanup: can't cd to FAXSPOOL\n");
X		exit(1);
X	}
X	if (read_config())
X		exit(1);
X
X	days = (60L * 60L * 24L);	/* about an average unix working day */
X	days *= (time_t)maxage;
X	now = time((time_t *)NULL);
X
X	if (gethostname(me, 80))
X		me[0] = '\0';
X
X	if ((dirp = opendir(FAXSPOOL)) == NULL)
X	{
X		fax_log(ERROR,"faxcleanup: can't open directory %s\n",FAXSPOOL);
X		exit(1);
X	}
X
X	while ((dp = readdir(dirp)) != NULL)
X	{
X		if (strncmp(dp->d_name, "x.f", 3))
X			continue;
X
X		if ((fp = fopen(dp->d_name, "r")) == NULL)
X		{
X			fax_log(ERROR,"faxcleanup: can't open %s\n",dp->d_name);
X			exit(1);
X		}
X		if (read_job(fp, dp->d_name))
X		{
X			fax_log(ERROR,"faxcleanup: can't read %s\n",dp->d_name);
X			exit(1);
X		}
X		if ((fax.spooled + days < now) || fax.tries >= maxtry)
X		{
X			rewind(fp);
X			sprintf(io, "mail %s",fax.user);
X			if ((pp = popen(io, "w")) == NULL)
X			{
X				fax_log(ERROR,"faxcleanup: can't mail user %s about bad job %s\n",
X					fax.user,dp->d_name);
X				exit(1);
X			}
X#ifdef	JOBID
X			fprintf(pp,"your fax job %s, queued ",fax.jobid);
X#else
X			fprintf(pp,"your fax job %s, queued ",dp->d_name);
X#endif
X			timestamp(pp, (time_t)fax.spooled);
X			fprintf(pp,"could not be sent\n\n");
X			fprintf(pp,"no of attempts and unsent phone nos:\n");
X
X			while (fgets(io, 128, fp) != NULL)
X			{
X				if ((!strncmp(io, "phone", 5))
X					|| (!strncmp(io, "tries", 4)))
X					fprintf(pp, "\t%s",io);
X			}
X			fprintf(pp,"\n");
X
X			fprintf(pp,"you can ask %s for the appropriate logs\n\n",FAXADMIN);
X			fprintf(pp,"regrettably yours\n\n\tfaxcleanup%c%s\n\n",
X				(me[0])?'@':' ',me);
X			fprintf(pp,"PS: have you considered using email ?\n");
X			fflush(pp);
X			if (ferror(pp))
X			{
X				fax_log(ERROR,"faxcleanup: can't mail user %s about bad job %s\n",
X					fax.user,dp->d_name);
X				exit(1);
X			}
X			pclose(pp);
X			fclose(fp);
X			rmjob(fax.xname, fax.dname, fax.pages);
X		}
X	}
X	closedir(dirp);
X	exit(0);
X}
Xrmjob(xname,dname,pages)
Xchar *xname,*dname;
Xint pages;
X{
X	char buf[256], *d;
X	int i;
X
X	unlink(xname);
X	for (i=0; i<pages; i++)
X	{
X		sprintf(buf,"%s.g3.%d",dname,i);
X		unlink(buf);
X	}
X}
END_OF_FILE
  if test 5045 -ne `wc -c <'faxhost/faxcleanup.c'`; then
    echo shar: \"'faxhost/faxcleanup.c'\" unpacked with wrong size!
  fi
  # end of 'faxhost/faxcleanup.c'
fi
if test ! -d 'faxhost/faxfonts' ; then
    echo shar: Creating directory \"'faxhost/faxfonts'\"
    mkdir 'faxhost/faxfonts'
fi
if test -f 'faxhost/wiring.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'faxhost/wiring.c'\"
else
  echo shar: Extracting \"'faxhost/wiring.c'\" \(41916 characters\)
  sed "s/^X//" >'faxhost/wiring.c' <<'END_OF_FILE'
X#include <stdio.h>
X#include <ctype.h>
X#include <termios.h>
X#include <fcntl.h>
X#include <errno.h>
X#include <signal.h>
X#include <sys/time.h>
X
X#include "../faxconfig.h"
X
X
Xextern	int errno;
X
X/*
X	wiring.c
X
X	first released version 0.99a [desperado version]
X	cleaned up Feb 21st, '91,
X	Copyright (C) 1991, klaus schallhorn, klaus@cnix.uucp
X
X	Permission to use, copy, modify, and distribute this software 
X	and its documentation for any purpose and without fee is hereby 
X	granted, provided that the above copyright notice appear in 
X	all copies and that both that copyright notice and this permission 
X	notice appear in supporting documentation. 
X
X	This software is provided "as is" without express or implied warranty.
X
X
X	faxpak is late by about 6 weeks. It turned out that the way
X	the faxmodem communicates with the host is extremely, well,
X	artistic. It also turned out that g3 files, as specified
X	in the ccitt specs need quite a bit of processing before
X	they can be stashed down the serial port.
X
X	The faxmodem basically makes the assumption it's running
X	on a medium to fast dos box. It assumes it has the undivided
X	attention of the host, and it assumes it can fiddle with
X	the actual wiring between the host and the modem at will
X	[like the guy in the movie trying to start a stolen car].
X
X	If, for instance, you're using hardware flow ctl, the
X	fax modem still sends tons of xon's/xoff's although you
X	specified NOT to use xonoff.
X
X	pauses of more than one fifteenth of a second during the 
X	tx [transmission] phase of a fax result in a quiet abort.
X	The faxmodem regards this as "end of page" and squirts
X	abuse to the fax machine at the other end.
X
X	Imagine news expiring a full feed, or processing an incoming
X	batch, and the standard cron tab entry finding old garbage
X	and removing it and you can see that a delay of something
X	like 60 or 70 milliseconds is quite possible.
X
X	I therefore suggest not to schedule any faxes at or
X	around the time disk intensive cron jobs are due if you cannot
X	guarantee there won't be any major swapping.
X
X	A 3/60 handles concurrent "find", or "newsrun" and faxes ok.
X	Copying a few Mb via nfs while your sending multiple faxes works
X	as well.
X
X	On a 10 mhz 286 [dos] box printf statements while transmitting 
X	can screw you up [actually not you, but the outgoing fax].
X	They take too long. And I'm not kidding.
X
X	I assume "Flow control" is done partly in rs232 fashion, 
X	more so according to what's possible and common on dos boxes. 
X	In fact, the various modem manufacturers' specs specify which 
X	bit in which register to twiddle, in the dos box hardware, 
X	rather than how to connect to a serial port.
X
X	This proves to have no effect - unless the fax modem is 
X	actually connected to a dos box.
X
X	In the course of getting this to work I have used three rolls
X	of fax paper, resorted to beating the fax modem [makes you
X	feel real good. Doesn't advance the issue though], and missed some
X	of my favourite tv pgms.
X
X	After some persistant phone calls to the vendor I even
X	discovered that my modem had beta release firmware [no hint of that
X	in the sales blurb or the tech specs]. A "pre release" prom was 
X	promised and arrived promptly after a week [I actually got two].
X	Clearly labelled "BETA RELEASE" this time. I would
X	not be surprised to find further discrepancies between specs
X	and real life [incorporated into the wiring, 26.1.90].
X
X	Well, at least it works now, on Sun 3's and Sun 4's [I run 4.1],
X	using hardware flow ctl - no chance with xon/xoff.
X
X	If you don't have a Sun, and cannot yet get your hands on one
X	of these "available real soon now" class2 modems [I'm still waiting],
X	I suggest you get a fax modem
X
X		ON LOAN, or
X		=======
X		ON APPROVAL,
X		===========
X
X	and have the supplier [remember, it's not a vendor yet] confirm
X	that the modem's command set adheres strictly to the CCITT T.30
X	specifications [dubbed sierra], as outlined in the enclosed
X	document "writefax.man" which I picked up on the net.
X
X	There are faxmodems out and available now that currently
X	support T.30 *and* some proprietary command set [often using
X	a binary language when analysed with a serial port sniffer].
X	Some faxmodems come with a non standard command set and need
X	an upgrade in firmware to make them t.30 compatible [like the
X	ones I used]. Others will be relaunched as t.30 compatible ones,
X	again, "real soon now" [honestly, I'm running beta].
X
X	You have to make sure though that the modem recognises AT LEAST
X	the sierra, or t.30, standard - class2 type modems of course
X	would be far better, but as far as I know the standard is still
X	in the making. The making of the modems thusly might take just
X	that little bit extra.
X
X	I'm not trying to discourage you.
X
X	I just want to make sure you don't run out and buy yourself
X	a fax modem you can only use with the vendors software on a dos
X	box [why don't you use email anyway?].
X
X	Only after you've aquired a fax modem on the terms outlined
X	here should you play with this module with:
X
X#define	TESTING
X#define	TEST_DEVICE=\"/dev/whatever_device_is_handy_for_testing\"
X
X	to get it working. And only if you get it to work on your system,
X	is faxpak a make, install 'n run package.
X
X	During development it's certainly helpful to turn incoming
X	getty's OFF. Otherwise you might have to reboot because
X	an aborted attempt might block the port from further use.
X
X	If the modem works to your satisfaction for several years
X	you may pay the bill for the modem.
X
X	It is possible that I have to port this within the next six to
X	eight weeks to a xenix 286 box - a system I know nothing of.
X	But I won't know this for sure until early march.
X
X*/
X
X#ifdef	SIERRA
X/*
X	sierra modems cannot digest raw group 3 format fax files. They 
X	need quite a bit of munging and processing. 
X*/
X#include "sierracmd.h"
X#endif	SIERRA	/* ***************************************************** */
X
X
X
X#ifdef	CLASS2
X	while (class2 modem != yet arrived)
X		;
X#endif
X
X
X
X
X
X
X/* *************** these apply to both types of modem types ************ */
X#define	Class2		0
X#define	Sierra		1
X
Xstruct	Modems
X{
X	char	*name;
X	int	type;
X} known_modems[]=
X{
X	"class2",	Class2,	 /* your best bet */
X	"sierra",	Sierra	 /* see above, and sierracmd.h for info */
X};
X#define	MAX_MODEM	(sizeof(known_modems)/sizeof(struct Modems))
X
Xstruct	termios	virgin, wire;
X
Xint	ser_port, cannot, dev_no, curjob, curpage, sent, failed, 
X	dial_tries;
Xstatic	int foul;
X
Xchar	wbuf[1024], biff[1024], namebuf[256];
Xchar	devnam[256], lcknam[256];
Xunsigned char rxbuf[1024], txbuf[2048];
XFILE	*logfp;
X
Xvoid	breakwrite();
Xchar	*faxtime();
X
X
X#ifndef	TESTING
X#define	SPEAKER	0
X#define	FUNCTION(funcname)	""
X	
Xextern	char **Devices, **Devlocks, **Faxtypes, **Wakeup,
X		*Dialtyp, *Topspeed;
Xextern	struct FAX fax;
Xextern	int blah;
X
X#else	/* ******************** TESTING ********************* */
X
X#include <varargs.h>
X#ifndef	SPEAKER
X#define	SPEAKER	2
X#endif
X#define	FUNCTION(funcname)	funcname
X
Xchar	tstphone[80], faxtype[80], wakeup[2], dev[80];
Xchar	*Devices[2], *Devlocks[2], *Faxtypes[2], *Wakeup[2],
X		Dialtyp[2], Topspeed[2];
Xstruct	FAX fax;
Xint	blah=9;
X
Xmain(ac,av)
Xint ac;
Xchar *av[];
X{
X	if (ac != 4)
X	{
X		fprintf(stderr,"need phoneno, no_of_pages, g3.basename\n");
X		exit(1);
X	}
X	strcpy(fax.user,  "root"); /* you need the perms for port access anyway */
X	strcpy(fax.dname, av[3]);
X	strcpy(fax.xname, "nosuch");
X	strcpy(tstphone,  av[1]);
X	fax.phone = (char **)malloc(2*sizeof(char*));
X	fax.phone[0] = tstphone;
X	fax.phone[1] = NULL;
X	fax.tries = fax.uid = 0;
X	fax.now = TRUE;
X	fax.mail = 0;
X	fax.pages = atoi(av[2]);
X	fax.phone_nos = 1;
X#ifdef	SIERRA
X	strcpy(faxtype, "sierra");
X#else
X	strcpy(faxtype, "class2");
X#endif
X	Faxtypes[0] = faxtype;
X	Faxtypes[1] = NULL;
X	wakeup[0] = '\0';
X	Wakeup[0] = wakeup;
X	Wakeup[1] = NULL;
X	Dialtyp[0] = 'T';	/* 'P' for pulse dialling */
X	Dialtyp[1] = '\0';
X	strcpy(dev, TEST_DEVICE);
X	Devices[0] = dev;
X	Devices[1] = NULL;
X	Devlocks[0] = wakeup;	/* empty */
X	Devlocks[1] = NULL;
X	Topspeed[0] = '7';	/* 9600 baud, see modem docs */
X	Topspeed[1] = '\0';
X#ifdef	BLAH
X	blah = BLAH;
X#endif
X
X	sendfax((FILE *)NULL,0,TRUE);
X	exit(0);
X}
X#ifndef	lint
Xfax_log(va_alist)
Xva_dcl
X{
X	va_list args;
X	char *fmt;
X	int db_flag;
X
X	va_start(args);
X	db_flag = va_arg(args, int);
X	fmt = va_arg(args, char *);
X
X	if (db_flag == ERROR || (blah >= db_flag))
X	{
X		fprintf(stderr,"   ");
X		vfprintf(stderr, fmt, args);
X	}
X	va_end(args);
X	return(0);
X}
Xfax_tlog(va_alist)
Xva_dcl
X{
X	va_list args;
X	int db_flag;
X	FILE *fp;
X	char *fmt;
X
X	va_start(args);
X
X	db_flag = va_arg(args, int);
X	fp = va_arg(args, FILE *);
X	fmt = va_arg(args, char *);
X
X	if (fp && (db_flag == ERROR || (blah >= db_flag)))
X	{
X		if (db_flag == ERROR)
X			fprintf(stderr,"%2d ",errno);
X		else fprintf(stderr,"   ");
X		vfprintf(stderr, fmt, args);
X	}
X	va_end(args);
X	return(0);
X}
Xfflushlog(){return(0);}		/* don't bother with these while testing */
Xnot_now(){return(0);}		/* notnow() and rmjob() are in sendfax.c */
Xrmjob(){return(0);}		/* you don't want to link that in for tests */
X
X#endif	/* lint */
X#endif	/* ******************* end of TESTING CODE ********************* */
X
X
Xchar *faxtime(t) /* dont know how portable the ctime(3) variants are */
Xtime_t t;	 /* in any case, all I need is the time */
X{
X	static char permastore[40];
X	struct tm *tml, *localtime();
X
X	tml = localtime(&t);
X	sprintf(permastore,"%02d:%02d:%02d",
X		tml->tm_hour,tml->tm_min,tml->tm_sec);
X	return(permastore);
X}
Xvoid breakwrite()
X{
X	cannot = 1;
X}
Xportunlock(name)
Xchar *name;
X{
X	if (name)
X		unlink(name);
X	return(ERROR);
X}
X
Xplug(portname,portlock)
Xchar *portname, *portlock;
X{
X	int i;
X
X	errno = 0;
X		/* lock, allow possible incoming fax, or uucp, wait 5 mins */
X		/* if no luck, simply go away */
X#ifndef	TESTING
X	if (portlock)
X		if (mklock(portlock, 30, 300))
X			return(ERROR);
X#endif
X	if ((ser_port = open(portname, (O_RDWR | O_NDELAY), 0600)) < 0)
X	{
X		if (errno == EBUSY)
X			fax_tlog(0, logfp, "sendfax: port %s BUSY\n",portname);
X		else fax_tlog(ERROR, logfp, "sendfax: can't plug into %s\n",portname);
X		return(portunlock(portlock));
X	}
X	if (ioctl(ser_port,TCGETS,&virgin) < 0)
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't ioctl on %s\n",portname);
X		return(portunlock(portlock));
X	}
X	wire = virgin;			/* working copy		*/
X
X	wire.c_iflag &= ~BRKINT;	/* c_iflag is set to 0L, but looks */
X	wire.c_iflag &= ~ICRNL;		/* cleaner when setting flags off */
X	wire.c_iflag &= ~ISTRIP;
X	wire.c_iflag &= ~IXON;	/* can't use proper xon/xoff, see set_ixon() */
X
X	wire.c_cflag &= ~CIBAUD;
X	wire.c_cflag &= ~B9600;
X	wire.c_cflag |= CSIZE;
X	wire.c_cflag |= CS8;
X	wire.c_cflag |= HUPCL;
X	wire.c_cflag |= CREAD;
X	wire.c_cflag |= B2400;
X	wire.c_cflag |= CSTOPB;
X	wire.c_cflag &= ~PARENB;
X
X	wire.c_lflag &= ~ISIG;
X/*	wire.c_lflag &= ~ICANON; I now read this a line at a time */
X	wire.c_lflag &= ~ECHO;
X	wire.c_lflag &= ~IEXTEN;
X
X	wire.c_oflag &= ~OPOST;		/* no messing with output */
X
X	wire.c_cc[VMIN] = 1;
X	wire.c_cc[VTIME] = 1;
X	wire.c_cc[VEOL] = 0x0d;	/* need this to get the lines though */
X				/* or I would have to switch to ascii asnwers */
X				/* from the modem altogether - don't like it */
X	if (ioctl(ser_port,TCSETSF,&wire) < 0)
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't set 2400 baud for %s\n",portname);
X		return(portunlock(portlock));
X	}
X
X	i = 1;			/* probably SUN SPECIFIC, raise carrier */
X	if (ioctl(ser_port,TIOCSSOFTCAR, &i) < 0)
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't raise softcar on %s\n",portname);
X		return(portunlock(portlock));
X	}
X	if (fcntl(ser_port, F_SETFL, O_RDWR) == ERROR)
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't set O_NDELAY off for %s\n",portname);
X		return(portunlock(portlock));
X	}
X	fax_tlog(5, logfp, "sendfax: 2400 baud set for %s\n",portname);
X	return(0);
X}
Xfaxmode(portname,portlock)
Xchar *portname,*portlock;
X{
X	errno = 0;
X			/* pump up the volume to 19k2 */
X	wire.c_cflag &= ~B2400;
X	wire.c_cflag |= B19200;
X
X	if (ioctl(ser_port,TCSETSW,&wire) < 0)
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't set 19200 baud for %s\n",portname);
X		return(portunlock(portlock));
X	}
X	fax_tlog(5, logfp, "sendfax: 19200 baud set for %s\n",portname);
X	return(0);
X}
Xset_ixon(on,portname,portlock)
Xint on;
Xchar *portname,*portlock;
X{
X/*
X	toggle flow control
X	there's no point trying to READ during transmit if
X	we're using CRTSCTS [hardware flow ctl], we
X	simply won't get anything even if the modem sends us some,
X	which in fact it does, namely xons and xoffs, even if we tell
X	the modem not to use xonoff flow ctl [tested on dos].
X
X	I don't know if this is standard behaviour or if this
X	is a SunOs value added feature
X
X	if you want to try your luck with xon/xoff, you have to set
X	flow ctl AFTER you've received your first XON [I know it's silly].
X	One problem you're likely to find is that the modem expects
X	the host to shut up instantly when sending an xoff. 
X
X	Testing on a dos box showed that by delaying the digesting of a 
X	received xoff by two bytes screwed up the fax. It also showed 
X	that the modem sends an xoff after fewer than 100
X	bytes were sent. At 19200 [~2000 bytes/sec] xon/xoff is thus toggled
X	more than 20 times a second. I guess this is just too much for unix
X	device drivers that are not guaranteed to stop instantly. 
X	I could not get it to work on a 3/60 using xon/xoff reliably.
X	I'm too tired to even try it again on a 4/60. I use hardware
X	flow ctl. basta.
X
X	feb 21st, '91: did not try xonoff again with icanon change
X*/
X	errno = 0;
X
X	if (on)
X	{
X#ifdef	USE_IXON
X		wire.c_iflag |= IXON;
X#else
X		wire.c_cflag |= CRTSCTS;
X#endif
X	}
X	else
X	{
X#ifdef	USE_IXON
X		wire.c_iflag &= ~IXON;
X#else
X		wire.c_cflag &= ~CRTSCTS;
X#endif
X	}
X
X	if (ioctl(ser_port,TCSETSW,&wire) < 0)
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't %s flow ctl for %s\n",
X			(on)?"set":"unset",portname);
X		return(portunlock(portlock));
X	}
X	fax_tlog(9, logfp, "sendfax: flow_ctl %s for %s\n",
X			(on)?"set":"unset",portname);
X	return(0);
X}
Xunplug(portname,portlock)
Xchar *portname,*portlock;
X{
X	int i;
X
X	wire.c_cflag = B0;
X	errno = 0;
X
X	fax_tlog(5, logfp, "sendfax: hanging up on %s\n",portname);
X	if (ioctl(ser_port,TCSETS,&wire) < 0)
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't hang up %s\n",portname);
X		return(portunlock(portlock));
X	}
X
X	i = 0;		/* software carrier OFF */
X	if (ioctl(ser_port,TIOCSSOFTCAR, &i) < 0)
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't set softcar off for %s\n",portname);
X		return(portunlock(portlock));
X	}
X	close(ser_port);
X
X	if (Wakeup[dev_no] && *(Wakeup[dev_no]))
X		resuscitate(portname);
X
X	(void)portunlock(portlock);
X	return(0);
X}
Xresuscitate(dv)		/* re enable auto answer */
Xchar *dv;
X{
X	int i;
X
X	if (!plug(dv, NULL))
X	{
X		sprintf(txbuf,"%s\r\n",Wakeup[dev_no]);
X		write(ser_port, txbuf, strlen(txbuf));
X		sleep(1);
X
X		i = 0;
X		if (ioctl(ser_port,TIOCSSOFTCAR, &i) < 0)
X			fax_tlog(ERROR, logfp, "sendfax: can't set softcar off for %s\n",dv);
X		close(ser_port);
X	}
X}
X
Xsendfax(wfp, slot, canexit)
XFILE *wfp;
Xint slot, canexit;
X{
X	int i;
X	char buf[256];
X
X	dev_no = slot;
X	sent = failed = 0;
X
X	sprintf(buf, "%s/FAX.LOG.%d", FAXLIB, getpid());
X	if ((logfp = fopen(buf, "w")) == NULL)
X	{
X		chmod(buf, FAXFMODE);
X		fax_log(ERROR, "sendfax: can't create temp log, I've had it\n");
X		if (canexit)
X			exit(1);
X		return;
X	}
X	for (i=0; i<MAX_MODEM; i++)
X		if (!strcmp(Faxtypes[slot], known_modems[i]))
X			break;
X	switch(i)
X	{
X#ifdef	CLASS2
X	case	Class2:		classyfax();	break;
X#endif
X#ifdef	SIERRA
X	case	Sierra:		sierrafax();	break;
X#endif
X	default:
X		fax_log(ERROR, "sendfax: unknown faxtype %s\n",Faxtypes[slot]);
X	}
X	if (wfp)
X	{
X		if (sent || failed)
X			xupdate(wfp);	/* update or unlink x.fax file */
X		else fclose(wfp);	/* no action, simply close file */
X	}
X
X	fclose(logfp);
X	fflushlog(buf);
X	unlink(buf);		/* get rid of tmp log */
X	if (canexit)
X		exit(0);	/* else return */
X}
Xxupdate(fp)
XFILE *fp;
X{
X	int i;
X	struct tm *tml, *localtime();
X
X	if (sent == fax.phone_nos)
X	{
X		fclose(fp);
X#ifndef	JOBID
X		fax_tlog(0, logfp, "sendfax: job %s, %d fax%s, completed\n",
X			fax.xname,fax.phone_nos,
X			(fax.phone_nos>1)?"es":"");
X#else
X		fax_tlog(0, logfp, "sendfax: %s, jobid %s, %d fax%s, completed\n",
X			fax.xname,fax.jobid,fax.phone_nos,
X			(fax.phone_nos>1)?"es":"");
X#endif
X		rmjob(fax.xname,fax.dname,fax.pages);
X		if (fax.mail)
X		{
X			tml = localtime(&fax.spooled);
X			++tml->tm_mon;		/* jan is 0 */
X#ifndef	JOBID
X			sprintf(wbuf,"echo 'your fax job %s, spooled %02d.%02d.%2d, %02d:%02d,\nhas been completed SUCCESSFUL' | mail %s",
X				fax.xname,
X				tml->tm_mday,tml->tm_mon,tml->tm_year,
X				tml->tm_hour,tml->tm_min,
X				fax.user);
X#else
X			sprintf(wbuf,"echo 'your fax job %s, spooled %02d.%02d.%2d, %02d:%02d,\nhas been completed SUCCESSFUL' | mail %s",
X				fax.jobid,
X				tml->tm_mday,tml->tm_mon,tml->tm_year,
X				tml->tm_hour,tml->tm_min,
X				fax.user);
X#endif
X			system(wbuf);
X		}
X		return;
X	}
X	else if (sent + foul == fax.phone_nos)
X	{		/* created new jobs, unlink old one */
X		rmjob(fax.xname,fax.dname,fax.pages);
X		return;
X	}
X
X	if (fseek(fp, fax.tpos, 0))	/* increment no of attempts */
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't update %s after sending %d fax%s\n",
X			fax.xname, sent, (sent>1)?"es":"");
X		return;
X	}
X	fprintf(fp, "tries %3d\n",1+fax.tries);
X	if (fseek(fp, fax.ppos, 0))	/* write out remaining phone nos */
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't update %s after sending %d fax%s\n",
X			fax.xname, sent, (sent>1)?"es":"");
X		return;
X	}
X	for (i=0; i<fax.phone_nos; i++)
X		if (fax.phone[i] != NULL)
X			fprintf(fp, "phone %s\n",fax.phone[i]);
X	fprintf(fp,"data %s\npages %d\n",fax.dname,fax.pages);
X#ifdef	JOBID
X	fprintf(fp,"jobid %s\n",fax.jobid);
X#endif
X	fflush(fp);
X	if (ferror(fp))
X		fax_tlog(ERROR, logfp, "sendfax: can't update %s after sending %d fax%s\n",
X			fax.xname, sent, (sent>1)?"es":"");
X	fclose(fp);
X}
Xfork_off()
X{
X	FILE *nj;
X	int i;
X	char dname[256];
X
X/*
X	remove foul jobs from batch on first attempt, so we don't
X	spoil a batch job because of one recipient whose fax gizzmo
X	is brain dead, switched off, run out of paper or so...
X*/
X
X	sprintf(wbuf, "%s/d.fb%05d%d",FAXSPOOL,getpid(),foul);
X	strcpy(dname, wbuf);
X
X	for (i=0; i<fax.pages; i++)
X	{
X		sprintf(namebuf, "%s.g3.%d", fax.dname, i);
X		sprintf(biff,    "%s.g3.%d", wbuf,      i);
X		if (link(namebuf, biff))
X		{
X			fax_tlog(ERROR, logfp, "sendfax: can't link %s to %s for %s, x.file %s\n",
X				namebuf, biff, fax.phone[curjob],fax.xname);
X			return(ERROR);
X		}
X	}
X	sprintf(wbuf, "%s/x.fb%05d%d",FAXSPOOL,getpid(),foul);
X
X	if ((nj = fopen(wbuf, "w")) == NULL)
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't create new x.file for %s, x.file %s\n",
X			fax.phone[curjob],fax.xname);
X		return(ERROR);
X	}
X	fprintf(nj,"spooled %ld\n",fax.spooled);
X	fprintf(nj,"tries %3d\n",1);
X	fprintf(nj,"user %s\n",fax.user);
X	fprintf(nj,"uid %d\n",fax.uid);
X	fprintf(nj,"now %d\n",fax.now);
X	fprintf(nj,"mail %d\n",fax.mail);
X	fprintf(nj,"type %d\n",fax.type);
X	fprintf(nj,"hires %d\n",fax.hires);
X	fprintf(nj,"font unknown\n");	/* that's done already */
X	fprintf(nj,"retfax unknown\n");	/* that's done as well */
X	fprintf(nj,"phone %s\n",fax.phone[curjob]);
X	fprintf(nj,"data %s\n",dname);
X	fprintf(nj,"pages %d\n",fax.pages);
X#ifdef	JOBID
X	fprintf(nj,"jobid %s\n",fax.jobid);
X#endif
X	fflush(nj);
X	if (ferror(nj))
X	{
X		fax_tlog(ERROR, logfp, "sendfax: can't write to new x.file for %s, x.file %s\n",
X			fax.phone[curjob],fax.xname);
X		return(ERROR);
X	}
X	fclose(nj);
X	++foul;
X	return(0);
X}
X
X#ifdef	SIERRA
X
Xlastframe(s, n)
Xunsigned char *s;
Xint n;
X{
X	int i;
X
X	if ((n -= 5) < 0)
X		return(ERROR);
X	for (i=0; i<n; i++)
X		if (*s++ == MARKER && (*s == 0xff) && (*(s+1) == 0xc8))
X			return(i);
X	return(ERROR);
X}
Xstrnpos(s, c, f, n)	/* similar to strchr, but works on binary'\0'strings */
Xunsigned char *s, c;	/* ignores first 'f' bytes, len is in 'n' */
Xint f, n;		/* strings are likely to contain zero bytes */
X{
X	int i;
X
X	s += f;
X	for (i=f; i<n; i++)
X		if (*s++ == c)
X			return(i);
X	return(ERROR);
X}
Xstrnrpos(s, c, f, n)	/* find last occurence of c in binary string of len n */
Xunsigned char *s, c;
Xint f, n;
X{
X	int i, last;
X
X	last = ERROR;
X	s += f;
X	for (i=f; i<n; i++)	/* I don't like counting backwards */
X		if (*s++ == c)
X			last = i;
X	return(last);
X}
Xsierrafax()
X{
X	int c, i, confirms, len, joberr, porterr, resend_cnt, tot_resends, alive;
X	FILE *fp;
X	time_t time_connect, time_hangup, *time_pstart, *time_pend;
X
X	curjob = porterr = 0;
X
X	sprintf(devnam,"%s",Devices[dev_no]);
X	sprintf(lcknam,"%s/%s",UUCPLOCKS,Devlocks[dev_no]);
X
X	signal(SIGALRM, breakwrite);
X	siginterrupt(SIGALRM, 1); /* not sure if this is avail in Sys V */
X	signal(SIGHUP, breakwrite);
X	siginterrupt(SIGHUP, 1);
X
X	while ((!porterr) && fax.phone[curjob] != NULL)
X	{
X		if (!fax.pages)		/* just in case something breaks */
X			continue;	/* cleanup will take care of it */
X
X		if (not_now(fax.uid, fax.now, fax.phone[curjob]))
X		{
X			fax_tlog(5, logfp, "sendfax: bad time for %s\n",
X				fax.phone[curjob]);
X			++curjob;
X			continue;
X		}
X
X#ifdef	TESTING
X		fprintf(stderr, "\nsendfax: STARTUP\n");
X#endif
X		i = line_minlen = have_cfr = have_connect =
X			have_dis = cannot = dial_tries = joberr = confirms =
X			tot_resends = resend_cnt = 0;
X		alive = TRUE;
X
X		if (((time_pstart = (time_t *)calloc(fax.pages, sizeof(time_t))) == NULL)
X		    || ((time_pend = (time_t *)calloc(fax.pages, sizeof(time_t))) == NULL))
X			return(fax_tlog(ERROR, logfp, "sendfax: can't allocate memory\n"));
X
X		if (plug(devnam,lcknam))
X			return;		/* errs logged already */
X
X				/* force sync with modem */
X				/* sometimes it talks gibberish */
X		strcpy(wbuf, "ATE0V0\r\n");
X		do
X		{
X			if (to_modem(wbuf))
X				break;
X			if ((c = modem_c_ans(OK,8)) == 0 && i)
X				break;
X		} while (i++ < 5);
X		if (c || cannot)
X		{
X			fax_tlog(ERROR, logfp, "sendfax: can't sync with modem\n");
X			return(unplug(devnam, lcknam));
X		}
X
X		sprintf(wbuf, "ATX4M%d#A0#B%c#E1#P%d#R%d#K%d#F1\r\n",
X			SPEAKER, Topspeed[dev_no], fax.pages, fax.hires, FLOW_CTL);
X
X		if (to_modem(wbuf) || modem_c_ans(OK,8))
X		{
X			fax_tlog(ERROR, logfp, "sendfax: %s missing after #F1\n",
X				sierra_cmds[OK].txtstring);
X			return(unplug(devnam, lcknam));
X		}
X
X		if (faxmode(devnam,lcknam))	/* errlogging done already */
X			return(unplug(devnam, lcknam));
X		sleep(2);
X
X		sprintf(wbuf, "ATD%c%s\r\n",Dialtyp[dev_no], fax.phone[curjob]);
X		if (to_modem(wbuf))
X		{
X			fax_tlog(ERROR, logfp, "sendfax: can't dial\n");
X			return(unplug(devnam, lcknam));
X		}
X/*
X	I don't know if 3*strlen(phone_no) is enough of a delay. Although tone 
X	dialling the us is often instant [like nyc], some places in the uk 
X	don't benefit from tone dial speed [yet]. When dialling germany I can 
X	make some coffee, and get a news paper before I hear ok [or busy]
X
X	Also note that "BUSY" is not recognised as such outside the US
X*/
X		if ((c = modem_c_ans(ANSWER_TONE, 15+(3*strlen(fax.phone[curjob])) ))
X				== sierra_cmds[OK].retcode)
X		{
X			c = modem_c_ans(ANSWER_TONE,15+(3*strlen(fax.phone[curjob])));
X			/* it's possible the modem sends OK after receiving phone no */
X		}
X
X		if (c == sierra_cmds[BUSY].retcode)
X		{
X			fax_tlog(5, logfp, "sendfax: %s BUSY\n",fax.phone[curjob]);
X			unplug(devnam,lcknam);
X			fflush(logfp);
X			sleep(5);
X			++curjob;
X			continue;
X		}
X		else if (c)
X		{
X			/* could be non USA BUSY */
X			/* NEED TO WATCH THIS */
X
X			for (i=0; i<LAST_SIERRA_ANS; i++)
X				if (c == sierra_cmds[i].retcode)
X					break;
X			fax_tlog(3, logfp, "sendfax: can't connect, have %s instead of ANSWER TONE for %s\n",
X				sierra_cmds[i].txtstring,fax.phone[curjob]);
X			unplug(devnam,lcknam);
X			fflush(logfp);
X			sleep(5);
X			++curjob;
X			++failed;
X			continue;
X		}
X		time_connect = time((time_t *)NULL);
X
X		if (get_frame())		/* read hldc frame */
X		{
X			fax_tlog(ERROR, logfp, "sendfax: not a legal hldc frame on %s\n",
X				fax.phone[curjob]);
X			unplug(devnam,lcknam);
X			fflush(logfp);
X
X			if ((!fax.tries) && (fax.phone_nos > 1))
X			{
X				if (!fork_off())
X				{
X					free(fax.phone[curjob]);
X					fax.phone[curjob] = NULL;
X				}
X				else break; 	/* something's seriously wrong */
X			}			/* give up to avoid desaster */
X			sleep(5);
X			++curjob;
X			++failed;
X			continue;
X		}
X
X		if (get_remote_cap())	/* read remote capabilities */
X		{			/* might include identification [ignored] */
X			fax_tlog(ERROR, logfp, "sendfax: can't get remote capabilities on %s\n",
X				fax.phone[curjob]);
X			unplug(devnam,lcknam);
X			fflush(logfp);
X
X			if ((!fax.tries) && (fax.phone_nos > 1))
X			{
X				if (!fork_off())
X				{
X					free(fax.phone[curjob]);
X					fax.phone[curjob] = NULL;
X				}
X				else break; 	/* something's seriously wrong */
X			}			/* give up to avoid desaster */
X			sleep(5);
X			++curjob;
X			++failed;
X			continue;
X		}
X
X		if (get_connect())
X		{
X			fax_tlog(ERROR, logfp, "sendfax: can't get CONNECT on %s\n",
X				fax.phone[curjob]);
X			unplug(devnam,lcknam);
X			sleep(5);
X			++curjob;
X			++failed;
X			continue;
X		}
X		set_minlen(have_connect);
X
X		for (curpage=0; curpage<fax.pages;curpage++)
X		{
X			time_pstart[curpage] = time((time_t *)NULL);
X			sprintf(biff,"%s.g3.%d",fax.dname,curpage);
X			if ((fp = fopen(biff, "r")) == NULL)
X			{
X				unplug(devnam, lcknam);
X				fax_tlog(ERROR, logfp, "sendfax: can't open %s for %s\n",
X					biff, fax.phone[curjob]);
X				return;	/* we're buggered somehow */
X			}
X
X#ifndef	USE_IXON;	/* else when using ixon/xoff, read XON before setting flow ctl */
X			if (set_ixon(TRUE,devnam,lcknam))
X			{
X				porterr = joberr = ERROR;
X				break;
X			}
X#endif
X
X		/* max tx time per full a4 page at 9k6 is < 60 secs */
X		/* alarm settings are based on 60 secs * 1.5, seems ok */
X			if (have_connect == CONNECT_24)
X				alarm(360);
X			else if (have_connect == CONNECT_48)
X				alarm(180);
X			else if (have_connect == CONNECT_72)
X				alarm(135);
X			else alarm(90);
X
X#ifdef	USE_IXON	/* you're out of luck on a sun, don't know about others */
X			fax_tlog(9, logfp, "sendfax: waiting for initial XON\n");
X			rxbuf[0] = '\0';
X			for (;;)
X			{
X				if (read(ser_port, rxbuf, 1) == 1)
X					if (rxbuf[0] == XON_CHAR)
X						break;
X			}
X			fax_tlog(9, logfp, "sendfax: GOT THAT\n");
X				/* have ixon, use flow ctl now */
X			if (set_ixon(TRUE,devnam,lcknam))
X			{
X				porterr = ERROR;
X				break;
X			}
X
X			for (i=0; i<MAX_FAX_LINES; i++)
X			{
X				if ((len = getline(fp,txbuf,line_minlen)) == EOF)
X					break;
X				for (c=0; c<len; c++)
X					if (write(ser_port, &txbuf[c], 1) != 1)
X					{
X						porterr = joberr = ERROR;
X						fax_tlog(ERROR, logfp, "sendfax: can't write %d bytes to %s in page %d\n",
X							len,devnam,curpage);
X						break;
X					}
X				if (porterr)
X					break;
X			}
X#else	/* !USE_IXON, far less wear 'n tear, use it if you can */
X			for (i=0; i<MAX_FAX_LINES; i++)
X			{
X				if ((len = getline(fp,txbuf,line_minlen)) == EOF)
X					break;
X				if (write(ser_port, txbuf, len) != len)
X				{
X					sleep(2);	/* force modem to time out, 1/10 sec is enough */
X					porterr = joberr = ERROR;
X					fax_tlog(ERROR, logfp, "sendfax: can't write %d bytes to %s in page %d\n",
X						len,devnam,curpage);
X					break;
X				}
X			}
X#endif	/* use_ixon */
X			alarm(0);
X			fclose(fp);
X			fax_tlog(9, logfp, "sendfax: %d scanlines sent\n",i);
X
X			if (set_ixon(FALSE,devnam,lcknam))
X			{
X				porterr = ERROR; /*errs logged already */
X				break;
X			}
X
X			if (!porterr)
X				c = modem_str_ans(MSG_CONFMD, 20, TRUE);
X			else c = porterr;
X
X			time_pend[curpage] = time((time_t *)NULL);
X			if (c == sierra_cmds[RETRAIN].retcode)
X			{
X				--curpage;
X				++tot_resends;
X				if (++resend_cnt == MAX_RESEND)
X				{
X					joberr = TRUE;
X					fax_tlog(ERROR, logfp, "sendfax: TOO MANY RESENDS, I've had it\n");
X					break;
X				}
X			}
X			else if (c == sierra_cmds[NO_CARRIER].retcode)
X			{
X				fax_tlog(ERROR, logfp, "sendfax: NO CARRIER after page %d of %d\n",
X					1+curpage, fax.pages);
X				joberr = TRUE;
X				alive = FALSE;
X				break;
X			}
X			else if (c != sierra_cmds[MSG_CONFMD].retcode
X				&& c != sierra_cmds[RTRN_OK].retcode)
X			{
X				for (i=0; i<LAST_SIERRA_ANS; i++)
X					if (c == sierra_cmds[i].retcode)
X						break;
X				fax_tlog(ERROR, logfp, "sendfax: unexpected response %s after page %d of %d\n",
X					sierra_cmds[i].txtstring,curpage+1,fax.pages);
X				joberr = TRUE;
X				alive = FALSE;
X				break;
X			}
X			else
X			{
X				fax_tlog(5, logfp, "sendfax: page %d of %d confirmed\n",
X					curpage+1,fax.pages);
X				confirms++;
X				resend_cnt = 0;
X			}
X		}
X
X/*
X	if we're still online, allow faxmodem to diconnect gracefully
X	at the other end, otherwise some fax machines remain in an
X	error condition barring further use. You're not gaining
X	popularity by jamming other people's fax toys
X*/
X		if (alive)
X			(void)modem_str_ans(NO_CARRIER, 20, TRUE);
X		time_hangup = time((time_t *)NULL);
X
X		unplug(devnam,lcknam);
X
X		fax_tlog(0, logfp, 
X		"sendfax: fax to %s, connect at %s\n",
X			fax.phone[curjob],faxtime(time_connect));
X		for (i=0; i<fax.pages; i++)
X		{
X			if ((!time_pstart[i]) || (!time_pend[i]))
X				break;
X				/* *faxtime() is static, stash away first use */
X			sprintf(biff,"%s, %ld secs\n",
X				faxtime(time_pend[i]),
X				(time_pend[i]-time_pstart[i]));
X			fax_tlog(0, logfp, 
X				"sendfax: fax to %s, page %d, %s to %s",
X				fax.phone[curjob], i+1,
X				faxtime(time_pstart[i]),biff);
X		}
X		if (!tot_resends)
X			fax_tlog(0, logfp, 
X				"sendfax: %s%s, connect time %ld secs\n",
X				(confirms==fax.pages)?"SUCCESSFUL: ":"",
X				fax.phone[curjob],
X				(time_hangup-time_connect));
X		else fax_tlog(0, logfp, 
X			"sendfax: %s%s, %d RESENDS, connect time %ld secs\n",
X			(confirms==fax.pages)?"SUCCESSFUL: ":"",
X			fax.phone[curjob],
X			tot_resends,
X			(time_hangup-time_connect));
X
X		if (joberr)
X		{
X			++failed;
X			if ((!fax.tries) && (fax.phone_nos > 1))
X			{
X				if (!fork_off())
X				{
X					free(fax.phone[curjob]);
X					fax.phone[curjob] = NULL;
X				}
X				else break;
X			}
X		}
X		else
X		{
X			++sent;
X			free(fax.phone[curjob]);
X			fax.phone[curjob] = NULL;
X		}
X
X		free(time_pstart);
X		free(time_pend);
X		++curjob;
X		fflush(logfp);
X		sleep(5);	/* let the dust settle */
X	}
X}
Xmodem_c_ans(want,secs)
Xint want, secs;
X{
X	int nread, rlen;
X	static int fst;
X	char fred[4];
X
X	fax_tlog(9, logfp, "sendfax: %swant %s, secs %d, attempt %d\n",
X		FUNCTION("modem_c_ans, "),sierra_cmds[want].txtstring,secs,fst);
X
X	rxbuf[0] = '\0';
X	errno = nread = rlen = 0;
X	alarm(secs);
X	nread = read(ser_port, rxbuf, 6);
X	alarm(0);
X
X	nread = max(nread,0);	/* don't set rxbuf[-1] to '\0' on error */
X	rxbuf[nread] = '\0';
X	if (asclen(rxbuf) == 0 && (fst++ < 3))
X		return(modem_c_ans(want,secs));
X	fst = 0;
X
X	/* since this is ascii directly from the modem it's quite safe */
X	if (strchr(rxbuf, sierra_cmds[want].retcode) == NULL)
X	{
X		if (blah == 9)
X		{
X			sprintf(biff, "sendfax: NOPE {%d bytes} ",nread);
X			for (rlen=0; rlen<nread; rlen++)
X			{
X				sprintf(fred,"%02x",rxbuf[rlen]);
X				strcat(biff, fred);
X			}
X			strcat(biff, "\n");
X			fax_tlog(9, logfp, biff);
X		}
X		return((rxbuf[0]) ? rxbuf[0] : ERROR);
X	}
X	fax_tlog(9, logfp, "sendfax: GOT THAT\n",errno);
X	return(0);
X}
Xmodem_str_ans(want,secs,newlot)
Xint want, secs, newlot;
X{
X	int nread, i, have, redo, left_marker, rite_marker;
X	static int fst;
X	char fred[4];
X
X	errno = nread = 0;
X	if (newlot)
X		fst = 0;
X	for (i=0; i<128; i++)
X		rxbuf[i] = '\0';
X
X	fax_tlog(9, logfp, "sendfax: %swant %s, secs %d, attempt %d\n",
X		FUNCTION("modem_str_ans, "),sierra_cmds[want].txtstring,secs,fst);
X	alarm(secs);
X	nread = read(ser_port, rxbuf, 128);
X	alarm(0);
X
X	nread = max(nread,0);
X	rxbuf[nread] = '\0';
X
X	have = left_marker = rite_marker = ERROR;
X	if (nread)
X	{			/* do we have in rxbuf what we want ? */
X		if (rxbuf[0] == sierra_cmds[want].retcode)
X			have = TRUE;
X							/* check it's outside frame data markers */
X		left_marker = strnpos(rxbuf, MARKER, 0, nread);
X		rite_marker = strnrpos(rxbuf, MARKER, max(0,left_marker), nread);
X	}
X
X	if (blah == 9)
X	{
X		sprintf(biff, "sendfax: read {%d bytes} ",nread);
X		for (i=0; i<nread; i++)
X		{
X			if (i == left_marker)
X				strcat(biff, "{");
X			sprintf(fred,"%02x",rxbuf[i]);
X			strcat(biff, fred);
X			if (i == rite_marker)
X				strcat(biff, "}");
X			if (i && (!(i % 32)))
X				strcat(biff, "\n");
X		}
X		strcat(biff, "\n");
X		fax_tlog(9, logfp, biff);
X	}
X				/* not found, or within frame data */
X	if (have == ERROR)
X	{
X		if (nread)
X		{
X			if (rxbuf[0] == sierra_cmds[NO_CARRIER].retcode)
X				return(sierra_cmds[NO_CARRIER].retcode);
X
X			redo = FALSE;
X			if (rxbuf[0] == sierra_cmds[RETRAIN].retcode)
X			{
X				redo = sierra_cmds[RETRAIN].retcode;
X				fax_tlog(5, logfp, "sendfax: REQUEST to RESEND page %d of %d\n",
X					1+curpage, fax.pages);
X			}
X			else if (rxbuf[0] == sierra_cmds[RTRN_OK].retcode)
X			{
X				fax_tlog(5, logfp, "sendfax: retrained successful after page %d of %d\n",
X					1+curpage, fax.pages);
X
X				if (1+curpage == fax.pages)
X					return(sierra_cmds[want].retcode);
X				redo = sierra_cmds[RTRN_OK].retcode;
X			}
X
X			if (redo)
X			{
X				dial_tries = 0;
X				have_cfr = have_connect = 0;
X				if ((i = get_connect()) == 0)
X					return(redo);
X				return(i);
X			}
X		}
X		if (++fst >= 8)
X			return(ERROR);
X		return(modem_str_ans(want,secs,0));
X	}
X	fax_tlog(9, logfp, "sendfax: GOT THAT\n",errno);
X	fst = 0;
X	return(sierra_cmds[want].retcode);
X}
Xget_frame()
X{
X	int i, nread, Nread, framestart, left_marker, rite_marker;
X	unsigned char timebyte;
X	char fred[4];
X
X	/*
X	here's some typical "hdlc frames" as received from two
X	fax machines, including the training and retraining session
X
X	nread = 121
X	0d0a7effc004000088005182080404040404040404040404040404040400e800
X	00748d7effc800721d0071247effffff7e0d0a620d0d0a7effc82157be7effff
X	fc7e0d0a670d7a0d0d0a7effc83275ec7efffffe7e0d0a680d0d0a7effc83275
X	ec7efffffe7e0d0a680d0d0a7effc82157be7efffffc7e0d0a
X
X	nread = 166
X	0d0a7effc00400008c00731d00e9b0bb0000bbbb0000bbbbbbbbbb8980600068
X	6224b7c6b7a5b07effc0020404040404040404040404040404040404040404a4
X	f37effc80100731d0046147e7e7fff7e0d0a6b0d202020202020202020202020
X	20202020202020200d0a620d0d0a7effc82157be7e7e787e0d0a670d7a0d0d0a
X	7effc831458f7e7e7c7e0d0a6d0d0d0a7effc831458f7e7e7c7e0d0a6d0d330d
X	8d0acf4b8d0a
X
X	Frames vary in length. What we're looking for is the first substring
X	containing 7e ff c8. if we're not answering quick enough,
X	the two gizzmos seem to exchange/repeat some garbage
X	that we don't need to concern ourselves with. These frames are
X	rather large because I sat in a loop making the connection time
X	out on purpose. Expect to see 50 to 120 bytes in real life, though.
X
X	I know of one make/model fax machine that does NOT send
X	"legal" hldc frames. I haven't found a way round this yet.
X	Ignoring it and assuming worts case seems not to help since
X	you don't get CFR and CONNECT [in a format that looks ok] either.
X
X	This is such a bad frame:
X	0d0a7e7effc00400008a00000981572e4d617965722042617520476d6248ffff
X	ffffffffffffffffffffffffff8260ffff0000ea727effc00200000000000000
X	00000000000000000000000000000000000000000000000000008bef77ef7e0d
X	0a6b0d0d0a7e7effc00400008a00000981572e4d617965722042617520476d62
X	48ffffffffffffffffffffffffffffffff8260ffff0000ea727effc002000000
X	00000000000000000000000000000000000000000000000000008bfeefde8bef
X	77ef7e0d
X
X	*/
X
X	for (i=0; i<256; i++)	/* I've seen frames THAT long */
X		rxbuf[i] = '\0';   /* I know there's bcopy, memset etc */
X				   /* can't be bothered now to ifdef portable */
X	Nread = errno = 0;
X	fax_tlog(9, logfp, "sendfax: want frame\n");
X
X	alarm(45);	/* just in case */
X	for (framestart=ERROR; Nread < 512;)	/* and again */
X	{
X		if ((nread = read(ser_port, &rxbuf[Nread], 256)) > 0)
X		{
X			Nread += nread;
X			if ((framestart = lastframe(rxbuf, Nread)) >= 0
X				&& framestart <= (Nread-6))
X				break;
X		}
X		if (cannot)	/* alarm went off */
X			break;
X	}
X	alarm(0);
X
X	left_marker = strnpos(rxbuf, MARKER, 0, Nread);
X	rite_marker = strnrpos(rxbuf, MARKER, left_marker, Nread);
X
X	if (blah == 9)
X	{
X		fax_tlog(9, logfp, "sendfax: framelen %d, start of last frame %d\n",
X			Nread,framestart);
X		strcpy(biff,"sendfax: frame data\n");
X		for (i=0; i<Nread; i++)
X		{
X			if (i == left_marker && framestart > -1)
X				strcat(biff, "{");
X			else if (i == (framestart + 6) && framestart > -1)
X				strcat(biff, "->");
X			sprintf(fred, "%02x",rxbuf[i]);
X			strcat(biff, fred);
X			if (i == rite_marker && framestart > -1)
X				strcat(biff, "}");
X			if (i && (!(i % 32)))
X				strcat(biff, "\n");
X		}
X		strcat(biff, "\n");
X		fax_tlog(9, logfp, biff);
X	}
X	if (framestart == ERROR || cannot)	/* cannot triggered by alarm */
X		return(ERROR);		/* framestart == ERROR if no MARKER */
X
X	timebyte = rxbuf[framestart+6];
X	timebyte = (timebyte >> 1) & 0x07; /* only want leftmost 3 bits in rite nibble */
X	line_minlen = not_so_fast[timebyte];
X
X	fax_tlog(9, logfp, "sendfax: min usecs per line %d\n",line_minlen);
X	return(0);
X}
Xget_remote_cap()
X{
X	int i, nread;
X	char fred[8];
X
X	for (i=0; i<48; i++)
X		rxbuf[i] = '\0';
X
X	nread = errno = 0;
X	fax_tlog(9, logfp, "sendfax: want REMOTE CAPABILITIES\n");
X
X	alarm(30);	/* just in case */
X
X	for (;;)
X	{
X		if ((nread = read(ser_port, rxbuf, 48)) <= 0)
X			return(ERROR);
X
X		if (blah == 9)
X		{
X			sprintf(biff, "sendfax: read {%d bytes} ",nread);
X			for (i=0; i<nread; i++)
X			{
X				sprintf(fred, "%02x",rxbuf[i]);
X				strcat(biff, fred);
X			}
X			strcat(biff, "\n");
X			fax_tlog(9, logfp, biff);
X		}
X
X		if (nread >= 1 && rxbuf[0] == sierra_cmds[NO_CARRIER].retcode)
X			return(sierra_cmds[NO_CARRIER].retcode);
X		else if (nread == 2 
X			&& rxbuf[0] == sierra_cmds[WHAT_I_CAN].retcode)
X		{
X				have_dis = TRUE;
X				break;
X		}
X	}
X	if (cannot)
X		return(ERROR);
X	alarm(0);
X	fax_tlog(9, logfp, "sendfax: have REMOTE CAPABILITIES\n");
X	return(0);
X}
Xget_connect()
X{
X	int nread, i, left_marker, rite_marker;
X	char fred[4];
X
X	errno = 0;
X	rxbuf[0] = '\0';
X	if (blah == 9)
X	{
X		if (!have_cfr)
X			sprintf(biff,"sendfax: %swant %s, CONNECT, attempt %d\n",
X				FUNCTION("get_connect, "),sierra_cmds[CONFIRM_TO_RX].txtstring, dial_tries);
X		else sprintf(biff,"sendfax: %swant CONNECT, attempt %d\n",
X			FUNCTION("get_connect, "),dial_tries);
X		fax_tlog(9, logfp, biff);
X	}
X
X	++dial_tries;
X	alarm(25);
X	nread = read(ser_port, rxbuf, 256);
X	alarm(0);
X
X	left_marker = strnpos(rxbuf, MARKER, 0, nread);
X	rite_marker = strnrpos(rxbuf, MARKER, max(0,left_marker), nread);
X
X	if (blah == 9)
X	{
X		sprintf(biff, "sendfax: read {%d bytes} ",nread);
X		for (i=0; i<nread; i++)
X		{
X			if (i == left_marker)
X				strcat(biff, "{");
X			sprintf(fred,"%02x",rxbuf[i]);
X			strcat(biff, fred);
X			if (i == rite_marker)
X				strcat(biff, "}");
X			if (i && (!(i % 32)))
X				strcat(biff, "\n");
X		}
X		strcat(biff, "\n");
X		fax_tlog(9, logfp, biff);
X	}
X
X	if (nread <= 0)
X		return(ERROR);
X	rxbuf[nread] = '\0';
X
X	if ((!have_cfr) && rxbuf[0] == sierra_cmds[CONFIRM_TO_RX].retcode)
X	{
X		have_cfr = TRUE;
X		fax_tlog(9, logfp, "sendfax: have %s\n",sierra_cmds[CONFIRM_TO_RX].txtstring);
X		return(get_connect());
X	}
X	else if (rxbuf[0] == sierra_cmds[CRC_IN_FRAME].retcode)
X	{
X		fax_tlog(9, logfp, "sendfax: have %s\n",sierra_cmds[CRC_IN_FRAME].txtstring);
X		return(sierra_cmds[CRC_IN_FRAME].retcode);
X	}
X	else if (rxbuf[0] == sierra_cmds[NO_CARRIER].retcode)
X	{
X		fax_tlog(9, logfp, "sendfax: have %s\n",sierra_cmds[NO_CARRIER].txtstring);
X		return(sierra_cmds[NO_CARRIER].retcode);
X	}
X	else for (i=CONNECT_MIN; i<CONNECT_MAX; i++)
X	{
X		if (rxbuf[0] == sierra_cmds[i].retcode && have_cfr)
X		{
X			have_connect = sierra_cmds[i].retcode;
X			fax_tlog(9, logfp, "sendfax: have %s\n",sierra_cmds[i].txtstring);
X			return(0);
X		}
X	}
X
X	if (dial_tries < 10)	/* none found */
X	{
X		if (rxbuf[0] == sierra_cmds[RETRAIN].retcode)
X			--dial_tries;
X		return(get_connect());
X	}
X	return(ERROR);		/* pointless, give up */
X}
Xset_minlen(c)
Xint c;
X{
X	double bits;
X	int i, j;
X
X	/* line_minlen contains minimum microsecs per line */
X	/* assume worst case, 2400 baud, we're sending 2.4 bits/usec */
X	bits = 2.4;
X
X	/* at higher speed we're sending more: */
X	for (i=CONNECT_MIN, j=1; i<CONNECT_MAX; i++, j++)
X		if (c == sierra_cmds[i].retcode)
X		{
X			bits *= (double)j;
X			break;
X		}
X
X#ifdef	TESTING
X	if (i == CONNECT_MAX)
X	{
X		/* not really necessary, never seen anything strange */
X		fax_tlog(ERROR, logfp, "sendfax: Ooops - funny connect type %d\n",c);
X		line_minlen = 80;	/* use a rubber */
X	}
X	else
X#endif
X	{
X		bits *= (double)line_minlen;
X		line_minlen = (int)(bits/8.0) + (((int)(bits)%8)!=0);
X		/* line_minlen NOW contains min bytes per line */
X	}
X	fax_tlog(5, logfp, "sendfax: min bytes per line %d\n",line_minlen);
X}
Xgetline(fp,faxbuf,minlen)
XFILE *fp;
Xunsigned char *faxbuf;
Xint minlen;
X{
X	int i, c, idx, eol;
X	static int fst;
X
X	if (eof) 
X	{
X		eof = fst = FALSE;
X		return (EOF);
X	}
X	if (!fst++)
X		last1 = cur = 0xff;
X
X	for (eol=idx=0; !eol;)
X	{
X		if ((c = getc(fp)) == EOF)	/* add eop by hand */
X		{
X			eof = TRUE;
X			return(add_eop(faxbuf, minlen, idx));
X		}
X		last1 = cur;
X		cur = c;
X
X		if ((!last1) && cur == 0x01)
X			eol = TRUE;
X		else if (eols)
X		{
X			if (((!last1) && cur == 0x10) || (last1 == 0x10 && cur == 0x01))
X				eol = TRUE;
X		}
X
X		if (eol)
X		{
X			if (++eols == 6)
X				eof = TRUE;
X			else if (eols == 1)
X			{
X				faxbuf[idx++] = '\0';
X				faxbuf[idx++] = '\0';
X				faxbuf[idx++] = '\0';
X				if ((i = (minlen - idx)) > 0)
X				{
X					memcpy(&faxbuf[idx], zeros, i);
X					idx += i;
X				}
X			}
X		}
X		else if (cur)
X			eols = 0;
X
X		faxbuf[idx++] = faxbyte[cur];
X		if (idx >= 1900)
X		{
X			fax_tlog(ERROR, logfp, "sendfax: don't think this is a fax file, scanline > %d pels\n",idx);
X			return(EOF);
X		}
X	}
X	return(idx);
X}
Xadd_eop(faxbuf, minlen, idx)
Xunsigned char *faxbuf;
Xint minlen, idx;
X{
X	int x;
X
X	fax_tlog(ERROR, logfp, "sendfax: strange fax file without EOP\n");
X
X	faxbuf[idx++] = '\0';
X	faxbuf[idx++] = '\0';
X	faxbuf[idx++] = '\0';
X	if ((x = (minlen - idx)) > 0)
X	{
X		memcpy(&faxbuf[idx], zeros, x);
X		idx += x;
X	}
X	memcpy(&faxbuf[idx], eol_pat, sizeof(eol_pat));
X	return(idx+sizeof(eol_pat));
X}
X#endif
X
Xto_modem(s)
Xchar *s;
X{
X	int len, written;
X
X	fax_tlog(9, logfp, "sendfax: sending %s",s); /* string contains \r\n */
X	len = strlen(s);
X	alarm(4);
X	written = write(ser_port, s, len);
X	alarm(0);
X	if (cannot || len != written)
X		return(ERROR);
X	sleep(1);
X	return(0);
X}
Xasclen(s)
Xunsigned char *s;
X{
X	unsigned char *d;
X	int i=0;
X
X	d = s;
X	while (*s)
X	{
X		if (*s > ' ')
X		{
X			++i;
X			*d++ = *s;
X		}
X		++s;
X	}
X	*d = '\0';
X	return(i);
X}
END_OF_FILE
  if test 41916 -ne `wc -c <'faxhost/wiring.c'`; then
    echo shar: \"'faxhost/wiring.c'\" unpacked with wrong size!
  fi
  # end of 'faxhost/wiring.c'
fi
echo shar: End of archive 1 \(of 5\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 2 3 4 5 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 5 archives.
    rm -f ark[1-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0
exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.