/*	-*- mode: c; mode: fold -*-	*/
/*{{{	includes */
# include	"config.h"
# include	<stdio.h>
# include	<stdlib.h>
# include	<stdarg.h>
# include	<ctype.h>
// include	<unistd.h>
# include	<string.h>
# include	<signal.h>
# include	<time.h>
# include	<setjmp.h>
# include	<sys/types.h>
# include	<sys/stat.h>
# if		HAVE_LOCALE_H
# include	<locale.h>
# endif		/* HAVE_LOCALE_H */
# include	"pager.h"
# include	"valid.h"
/*}}}*/
/*{{{	general definitions */
# define	VERSION		"0.96 (alpha software)"

# ifndef	LIBDIR
# define	LIBDIR		NULL
# endif		/* LIBDIR */

# if		HAVE_SIGSETJMP
# undef		jmp_buf
# define	jmp_buf		sigjmp_buf
# undef		setjmp
# define	setjmp(xx)	sigsetjmp ((xx), 1)
# undef		longjmp
# define	longjmp(xx,vv)	siglongjmp ((xx), (vv))
# endif		/* HAVE_SIGSETJMP */

# define	SPLIT_TERM	" ..."
# define	SPLIT_INIT	"... "

# define	OOM		fprintf (stderr, "Out of memory in line %d, aborted\n", __LINE__)
/*}}}*/
/*{{{	configuration file entry names and default values */
# define	CONFIG_SEP	","

# define	SEC_ALIAS	"alias"

# define	CFG_SERVICES	"services"
# define	CFG_CALLID	"call-id"
# define	CFG_SIG		"signature"
# define	CFG_USESIG	"use-signature"
# define	CFG_VERBOSE	"verbose"
# define	CFG_LOGFILE	"logfile"
# define	CFG_LOGSTR	"logstring"
# define	CFG_MODEMS	"modems"
# define	CFG_FREPORT	"final-report"
# define	CFG_SPEED	"speed"
# define	CFG_BPB		"bits-per-byte"
# define	CFG_PARITY	"parity"
# define	CFG_SB		"stopbits"
# define	CFG_PHONE	"phone"
# define	CFG_PROTOCOL	"protocol"
# define	CFG_MAXSIZE	"max-size"
# define	CFG_MAYSPLIT	"may-split"
# define	CFG_MAXMSGS	"max-messages"
# define	CFG_TRUNCATE	"truncate"
# define	CFG_USECID	"use-call-id"
# define	CFG_RMCID	"rm-invalids-cid,remove-invalid-character-call-id"
# define	CFG_RMPID	"rm-invalids-pid,remove-invalid-character-pager-id"
# define	CFG_INSPID	"insert-pager-id"
# define	CFG_CHPID	"change-pid,change-pager-id"
# define	CFG_VALPID	"valid-pid,valid-pager-id"
# define	CFG_CHCID	"change-cid,change-call-id"
# define	CFG_VALCID	"valid-cid,valid-call-id"
# define	CFG_CONVTAB	"conv-table"
# define	CFG_CONVERT	"convert"
# define	CFG_COST	"cost"
# define	CFG_FORCE	"force"
# define	CFG_CANDELAY	"can-delay"
# define	CFG_CANEXP	"can-expire"
# define	CFG_CANRDS	"can-rds,can-request-delivery-status"
# define	CFG_DORDS	"rds,request-delivery-status"
# define	CFG_CHKCID	"check-call-id"
# define	CFG_CHKPID	"check-pager-id"
# define	CFG_DEVICE	"device"
# define	CFG_LCKPRE	"lock-prefix"
# define	CFG_LCKMETHOD	"lock-method"
# define	CFG_INIT	"init"
# define	CFG_LINIT	"local-init"
# define	CFG_DIAL	"dial"
# define	CFG_TIMEOUT	"timeout"
# define	CFG_RESET	"reset"

# define	DEF_SPEED	38400
# define	DEF_BPB		8
# define	DEF_PARITY	"n"
# define	DEF_SB		1
# define	DEF_USECID	False
# define	DEF_INSPID	False
# define	DEF_CONVTAB	NULL
# define	DEF_CONVERT	NULL
# define	DEF_DEVICE	"/dev/modem"
# define	DEF_LCKPRE	"/usr/spool/uucp/LCK.."
# define	DEF_LCKMETHOD	NULL
# define	DEF_INIT	"\\r !d ATZ\\r <OK ATE0V1Q0\\r <OK"
# define	DEF_LINIT	NULL
# define	DEF_DIAL	"ATD%L\\r <60CONNECT|BUSY|NO\\sCARRIER|NO\\sDIALTONE"
# define	DEF_TIMEOUT	10
# define	DEF_RESET	"ATZ\\r <OK"
/*{{{{	ASCII protocol */
/*
 * ASCII protocol specific
 */
# define	CFG_ATOUT	"asc-timeout"
# define	CFG_ALOGIN	"asc-login"
# define	CFG_ALOGOUT	"asc-logout"
# define	CFG_APID	"asc-pagerid"
# define	CFG_AMSG	"asc-message"	
# define	CFG_ANEXT	"asc-next"
# define	CFG_ASYNC	"asc-sync"

# define	DEF_ATOUT	-1
# define	DEF_ALOGIN	NULL
# define	DEF_ALOGOUT	NULL
# define	DEF_APID	"\\P\\r"
# define	DEF_AMSG	"\\M\\r"	
# define	DEF_ANEXT	NULL
# define	DEF_ASYNC	"!f"
/*}}}*/
/*{{{	Script protocol */
/*
 * Script specific
 */
# define	CFG_STYP	"script-type"
# define	CFG_SNAME	"script-name"
# define	CFG_SLOGIN	"scr-login"
# define	CFG_SLOGOUT	"scr-logout"
# define	CFG_SPID	"scr-pagerid"
# define	CFG_SMSG	"scr-message"
# define	CFG_SNEXT	"scr-next"
# define	CFG_SSYNC	"scr-sync"

# define	DEF_STYP	NULL
# define	DEF_SNAME	NULL
# define	DEF_SLOGIN	"login"
# define	DEF_SLOGOUT	"logout"
# define	DEF_SPID	"pagerid"
# define	DEF_SMSG	"message"
# define	DEF_SNEXT	"next"
# define	DEF_SSYNC	"sync"
/*}}}*/
/*{{{	TAP */
/*
 * TAP specific
 */
# define	CFG_TOLD	"tap-old"
# define	CFG_T1		"tap-t1,tap-init-timeout"
# define	CFG_T2		"tap-t2"
# define	CFG_T3		"tap-t3,tap-timeout"
# define	CFG_T4		"tap-t4"
# define	CFG_T5		"tap-t5"
# define	CFG_N1		"tap-n1,tap-init-count"
# define	CFG_N2		"tap-n2,tap-transmit-count"
# define	CFG_N3		"tap-n3"
# define	CFG_TLICNT	"tap-login-count"
# define	CFG_TLOCNT	"tap-logout-count"

# define	DEF_TOLD	False
# define	DEF_T1		-1
# define	DEF_T2		-1
# define	DEF_T3		-1
# define	DEF_T4		-1
# define	DEF_T5		-1
# define	DEF_N1		-1
# define	DEF_N2		-1
# define	DEF_N3		-1
# define	DEF_TLICNT	-1
# define	DEF_TLOCNT	-1
/*}}}*/
/*{{{	UCP */
/*
 * UCP specific
 */
# define	CFG_USTOUT	"ucp-timeout"
# define	CFG_URETRY	"ucp-retry"
# define	CFG_UXTEND	"ucp-extend"
# define	CFG_URTOUT	"ucp-ds-timeout,ucp-delivery-status-timeout"

# define	DEF_USTOUT	-1
# define	DEF_URETRY	-1
# define	DEF_UXTEND	False
# define	DEF_URTOUT	-1
/*}}}*/
/*}}}*/
/*{{{	typedefs and statics */
typedef enum {
	NotTried,
	CantDial,
	NotSend,
	CantSend
}	Reason;

typedef struct {
	Bool		success;
	Reason		reason;
	char		*rmsg;
}	status;

typedef struct _serv {
	char		*name;
	valid_t		*pchk;
	valid_t		*cchk;
	struct _serv	*next;
}	serv;

typedef struct {
	int		nr;
	char		*callid;
	char		*alias;
	char		*pid;
	char		*service;
	Protocol	prot;
	string_t	*msg;
	serv		*s;
	
	/* Status for each message */
	status		st;
}	message;

static Bool	dojump;
static Bool	jumpdone;
static jmp_buf	env, termenv;
static char	*logfile;
static char	*logstr;
/*}}}*/
/*{{{	signal handler & logging */
static void
do_log (char typ, char *fmt, ...)
{
	va_list		par;
	FILE		*fp;
	time_t		tim;
	struct tm	*tt;
	
	va_start (par, fmt);
//	omask = umask (003);
	if (logfile && ((! logstr) || strchr (logstr, typ)) && (fp = fopen (logfile, "a"))) {
		time (& tim);
		if (tt = localtime (& tim))
			fprintf (fp, "[%2d.%02d.%04d %02d:%02d:%02d] %c ",
				 tt -> tm_mday, tt -> tm_mon + 1, tt -> tm_year + 1900,
				 tt -> tm_hour, tt -> tm_min, tt -> tm_sec, typ);
		vfprintf (fp, fmt, par);
		fprintf (fp, "\n");
		fclose (fp);
	}
//	umask (omask);
	va_end (par);
}
/*}}}*/
/*{{{	dialing */
static char *
mkrealphone (char *phone, char *pagerid)
{
	char	*dst;
	int	siz, n, len;
	char	*ptr;
	
	siz = strlen (phone) + 16;
	if (dst = malloc (siz + 2)) {
		for (n = 0; *phone; )
			if (*phone == '%') {
				++phone;
				if (*phone) {
					ptr = NULL;
					switch (*phone) {
					case 'P':
						ptr = pagerid;
						break;
					}
					if (ptr) {
						len = strlen (ptr);
						if (n + len >= siz) {
							siz = n + len + 64;
							if (! (dst = Realloc (dst, siz + 2)))
								break;
						}
						strcpy (dst + n, ptr);
						n += len;
					}
					++phone;
				}
			} else {
				if (n >= siz) {
					siz += 32;
					if (! (dst = Realloc (dst, siz + 2)))
						break;
				}
				dst[n++] = *phone++;
			}
		if (dst)
			dst[n] = '\0';
	}
	return dst;
}

static void *
do_dial (void *cfg, char *service, char *pagerid, char **modem)
{
	void	*sp;
	char	*tmp, *sav, *ptr;
	char	*t2, *s2, *p2;
	char	*device, *lckpre, *lckmethod;
	int	speed, rspeed, bpb, parity, sb;
	int	tout;
	char	*minit, *linit, *dial, *reset, *phone;
	int	siz, len;
	int	n, m;

	sp = NULL;
	*modem = NULL;
	if ((! (tmp = cfg_get (cfg, service, CFG_MODEMS, NULL))) ||
	    (! (tmp = strdup (tmp))))
		return NULL;
	speed = cfg_iget (cfg, service, CFG_SPEED, DEF_SPEED);
	bpb = cfg_iget (cfg, service, CFG_BPB, DEF_BPB);
	if (ptr = cfg_get (cfg, service, CFG_PARITY, DEF_PARITY))
		parity = *ptr;
	else
		parity = '\0';
	sb = cfg_iget (cfg, service, CFG_SB, DEF_SB);
	dial = NULL;
	reset = NULL;
	tout = 0;
	for (ptr = tmp; *ptr; ) {
		sav = ptr;
		ptr = skipch (ptr, ',');
		device = cfg_get (cfg, sav, CFG_DEVICE, DEF_DEVICE);
		lckpre = cfg_get (cfg, sav, CFG_LCKPRE, DEF_LCKPRE);
		lckmethod = cfg_get (cfg, sav, CFG_LCKMETHOD, DEF_LCKMETHOD);
		minit = cfg_get (cfg, sav, CFG_INIT, DEF_INIT);
		linit = cfg_get (cfg, sav, CFG_LINIT, DEF_LINIT);
		dial = cfg_get (cfg, sav, CFG_DIAL, DEF_DIAL);
		reset = cfg_get (cfg, sav, CFG_RESET, NULL);
		tout = cfg_iget (cfg, sav, CFG_TIMEOUT, DEF_TIMEOUT);
		rspeed = cfg_block_iget (cfg, sav, CFG_SPEED, speed);
		if (device && (t2 = strdup (device))) {
			for (p2 = t2; *p2; ) {
				s2 = p2;
				p2 = skipch (p2, ',');
				V (3, ("Trying to open %s for modem %s\n", s2, sav));
				if (sp = tty_open (s2, lckpre, lckmethod)) {
					if (tty_setup (sp, True, True, rspeed, bpb, sb, parity) != -1) {
						if (! (n = setjmp (env))) {
							dojump = True;
							tty_hangup (sp, 500);
							if (((! minit) || (tty_send_expect (sp, tout, minit, NULL) != -1)) &&
							    ((! linit) || (tty_send_expect (sp, tout, linit, NULL) != -1))) {
								dojump = False;
								V (3, ("Using modem %s at %d bps, %d%c%d over %s\n", sav, rspeed, bpb, parity, sb, s2));
								*modem = strdup (sav);
								break;
							}
						} else {
							V (2, ("\n"));
						}
						dojump = False;
					}
					sp = tty_close (sp);
				}
			}
			free (t2);
		}
		if (sp)
			break;
	}
	free (tmp);
	if (sp && dial && (phone = cfg_get (cfg, service, CFG_PHONE, NULL)) &&
	    (phone = mkrealphone (phone, pagerid))) {
		if (! (n = setjmp (env))) {
			dojump = True;
			for (p2 = phone; *p2; ) {
				s2 = p2;
				p2 = skipch (p2, ',');
				siz = strlen (dial) + strlen (phone) + 64;
				if (tmp = malloc (siz + 2)) {
					for (n = 0, m = 0; dial[n]; ) {
						if (dial[n] == '%') {
							++n;
							if (dial[n]) {
								ptr = NULL;
								switch (dial[n]) {
								case 'L':
									ptr = phone;
									break;
								}
								if (ptr) {
									len = strlen (ptr);
									if (m + len >= siz) {
										siz += len + 64;
										if (! (tmp = Realloc (tmp, siz + 2)))
											break;
									}
									strcpy (tmp + m, ptr);
									m += len;
								}
								++n;
							}
						} else {
							if (m + 1 >= siz) {
								siz += 128;
								if (! (tmp = Realloc (tmp, siz + 2)))
									break;
							}
							switch (dial[n]) {
							case '-':
								tmp[m++] = ',';
								break;
							default:
								tmp[m++] = dial[n];
								break;
							}
							++n;
						}
					}
					if (tmp) {
						tmp[m] = '\0';
						V (3, ("Trying do dial %s\n", s2));
						if (tty_send_expect (sp, tout, tmp, NULL) != -1) {
							V (1, ("Dial successful\n"));
							tty_drain (sp, 1);
							while (*p2)
								++p2;
						} else {
							if (*p2)
								tty_hangup (sp, 500);
							else
								sp = tty_close (sp);
						}
						free (tmp);
					}
				}
			}
		} else {
			V (2, ("\n"));
			if (sp) {
				tty_send (sp, "x\r", 2);
				tty_hangup (sp, 500);
				if (reset)
					tty_send_expect (sp, tout, reset, NULL);
				sp = tty_close (sp);
			}
		}
		dojump = False;
		free (phone);
	}
	return sp;
}
/*}}}*/
/*{{{	send ASCII */
static int
asc_send (void *sp, void *ctab, string_t *callid, message *mg, int mcnt, void *cfg, char *service, date_t *delay, date_t *expire, Bool rds)
{
	int	ret;
	void	*ap;
	int	n;
	int	err, nerr;
	int	atout;
	char	*alin, *alout, *apid, *amsg, *anext, *async;
	char	*tmp;
	
	ret = 0;
	if (ap = asc_new (sp)) {
		atout = cfg_iget (cfg, service, CFG_ATOUT, DEF_ATOUT);
		alin = cfg_get (cfg, service, CFG_ALOGIN, DEF_ALOGIN);
		alout = cfg_get (cfg, service, CFG_ALOGOUT, DEF_ALOGOUT);
		apid = cfg_get (cfg, service, CFG_APID, DEF_APID);
		amsg = cfg_get (cfg, service, CFG_AMSG, DEF_AMSG);
		anext = cfg_get (cfg, service, CFG_ANEXT, DEF_ANEXT);
		async = cfg_get (cfg, service, CFG_ASYNC, DEF_ASYNC);
		asc_config (ap, do_log, atout, alin, alout, apid, amsg, anext, async, delay, expire, rds);
		if (ctab) {
			asc_add_convtable (ap, ctab);
			ctab = cv_free (ctab);
		}
		if ((err = asc_login (ap, callid)) == NO_ERR) {
			for (n = 0; n < mcnt; ++n) {
				tmp = schar (mg[n].msg);
				if (tmp)
					err = asc_transmit (ap, mg[n].pid, tmp);
				else
					err = ERR_FATAL;
				if (err == NO_ERR)
					mg[n].st.success = True;
				else
					mg[n].st.reason = CantSend;
				do_log ((err == NO_ERR ? LGS_SENT : LGF_SENT),
					"ASCII Message %ssent to %s via %s: %s", (err == NO_ERR ? "" : "NOT "),
					(mg[n].alias ? mg[n].alias : (mg[n].pid ? mg[n].pid : "")), service, (tmp ? tmp : ""));
				if (ESTOP (err))
					break;
				if (n + 1 < mcnt) {
					if (err == NO_ERR)
						err = asc_next (ap);
					else
						err = asc_sync (ap);
					if (ESTOP (err))
						break;
					else if (err != NO_ERR)
						if ((nerr = asc_sync (ap)) != NO_ERR) {
							if (nerr < err)
								err = nerr;
							break;
						}
				}
			}
			if (n < mcnt)
				ret = 1;
			if ((err != ERR_ABORT) && (asc_logout (ap) != NO_ERR))
				ret = 1;
		} else
			ret = 1;
		asc_free (ap);
	} else
		ret = 1;
	return ret;
}
/*}}}*/
/*{{{	send script */
static int
scr_send (void *sp, void *ctab, string_t *callid, message *mg, int mcnt, void *cfg, char *service, date_t *delay, date_t *expire, Bool rds)
{
	int	ret;
	void	*s;
	char	*typ;
	char	*name;
	char	*scr;
	char	*slogin, *slogout, *spid, *smsg, *snext, *ssync;
	char	*tmp;
	int	n, err, nerr;
	
	ret = 1;
	typ = cfg_get (cfg, service, CFG_STYP, DEF_STYP);
	if (s = scr_new (sp, typ, LIBDIR)) {
		scr_config (s, do_log, delay, expire, rds);
		name = cfg_get (cfg, service, CFG_SNAME, DEF_SNAME);
		if (name)
			if ((*name == '/') || (*name == '+')) {
				if (*name == '+')
					++name;
				if (scr_load_file (s, name) == NO_ERR)
					ret = 0;
			} else if (scr = cfg_get (cfg, service, name, NULL))
				if (scr_load_string (s, scr) == NO_ERR)
					ret = 0;
		if (! ret) {
			slogin = cfg_get (cfg, service, CFG_SLOGIN, DEF_SLOGIN);
			slogout = cfg_get (cfg, service, CFG_SLOGOUT, DEF_SLOGOUT);
			spid = cfg_get (cfg, service, CFG_SPID, DEF_SPID);
			smsg = cfg_get (cfg, service, CFG_SMSG, DEF_SMSG);
			snext = cfg_get (cfg, service, CFG_SNEXT, DEF_SNEXT);
			ssync = cfg_get (cfg, service, CFG_SSYNC, DEF_SSYNC);
			if (ctab) {
				scr_add_convtable (s, ctab);
				ctab = cv_free (ctab);
			}
			if (scr_execute (s, slogin, schar (callid)) == NO_ERR) {
				err = NO_ERR;
				for (n = 0; n < mcnt; ++n) {
					err = scr_execute (s, spid, mg[n].pid);
					do_log ((err == NO_ERR ? LGS_INF : LGF_INF),
						"SCRIPT pagerid %s %ssent via %s",
						(mg[n].alias ? mg[n].alias : (mg[n].pid ? mg[n].pid : "")),
						(err == NO_ERR ? "" : "NOT "), service);
					if (ESTOP (err))
						break;
					else if (err == NO_ERR) {
						tmp = schar (mg[n].msg);
						if (tmp)
							err = scr_execute (s, smsg, tmp);
						else
							err = ERR_FATAL;
						if (err == NO_ERR)
							mg[n].st.success = True;
						else
							mg[n].st.reason = CantSend;
						do_log ((err == NO_ERR ? LGS_INF : LGF_INF),
							"SCRIPT message %s %ssent via %s", (tmp ? tmp : ""), (err == NO_ERR ? "" : "NOT "), service);
						do_log ((err == NO_ERR ? LGS_SENT : LGF_SENT),
							"SCRIPT Message %ssent to %s via %s: %s", (err == NO_ERR ? "" : "NOT "),
							(mg[n].alias ? mg[n].alias : (mg[n].pid ? mg[n].pid : "")), service, (tmp ? tmp : ""));
						if (ESTOP (err))
							break;
					}
					if (n + 1 < mcnt) {
						if (err == NO_ERR)
							err = scr_execute (s, snext, NULL);
						else
							err = scr_execute (s, ssync, NULL);
						if (ESTOP (err))
							break;
						else if (err != NO_ERR)
							if ((nerr = scr_execute (s, ssync, NULL)) != NO_ERR) {
								if (nerr < err)
									err = nerr;
								break;
							}
					}
				}
				if (n < mcnt)
					ret = 1;
				if ((err != ERR_ABORT) && (scr_execute (s, slogout, NULL) != NO_ERR))
					ret = 1;
			} else
				ret = 1;
		}
		scr_free (s);
	}
	return ret;
}
/*}}}*/
/*{{{	send TAP */
static int
tap_send (void *sp, void *ctab, string_t *callid, message *mg, int mcnt, void *cfg, char *service, date_t *delay, date_t *expire, Bool rds)
{
	void		*tp;
	string_t	*field[4];
	int		fld;
	int		n;
	char		*tmp;
	int		err, nerr;
	
	if (! (tp = tap_new (sp)))
		return 1;
	tap_timeouts (tp,
		      cfg_iget (cfg, service, CFG_T1, DEF_T1),
		      cfg_iget (cfg, service, CFG_T2, DEF_T2),
		      cfg_iget (cfg, service, CFG_T3, DEF_T3),
		      cfg_iget (cfg, service, CFG_T4, DEF_T4),
		      cfg_iget (cfg, service, CFG_T5, DEF_T5));
	tap_retries (tp,
		     cfg_iget (cfg, service, CFG_N1, DEF_N1),
		     cfg_iget (cfg, service, CFG_N2, DEF_N2),
		     cfg_iget (cfg, service, CFG_N3, DEF_N3),
		     cfg_iget (cfg, service, CFG_TLICNT, DEF_TLICNT),
		     cfg_iget (cfg, service, CFG_TLOCNT, DEF_TLOCNT));
	tap_config (tp, do_log, cfg_bget (cfg, service, CFG_TOLD, DEF_TOLD));
	if (ctab) {
		tap_add_convtable (tp, ctab);
		ctab = cv_free (ctab);
	}
	if (tap_login (tp, NULL, '\0', NULL, callid) != NO_ERR) {
		tap_free (tp);
		return 1;
	}
	err = NO_ERR;
	for (n = 0; n < mcnt; ++n) {
		fld = 0;
		field[fld++] = snewc (mg[n].pid);
		field[fld++] = snew (mg[n].msg -> str, mg[n].msg -> len);
		field[fld] = NULL;
		nerr = tap_transmit (tp, field, (n + 1 < mcnt ? False : True));
		if (nerr == NO_ERR)
			mg[n].st.success = True;
		else
			mg[n].st.reason = CantSend;
		tmp = schar (mg[n].msg);
		do_log ((nerr == NO_ERR ? LGS_SENT : LGF_SENT),
			"TAP Message %ssent to %s via %s: %s", (nerr == NO_ERR ? "" : "NOT "),
			(mg[n].alias ? mg[n].alias : (mg[n].pid ? mg[n].pid : "")), service,
			(tmp ? tmp : ""));
		if (nerr != NO_ERR)
			err = nerr;
		for (fld = 0; field[fld]; ++fld)
			sfree (field[fld]);
		if (ESTOP (err))
			break;
	}
	if ((err != ERR_ABORT) && ((n = tap_logout (tp)) != NO_ERR))
		if (err == NO_ERR)
			err = n;
	tap_free (tp);
	return (err != NO_ERR) ? 1 : 0;
}
/*}}}*/
/*{{{	send UCP */
static int
ucp_send (void *sp, void *ctab, string_t *callid, message *mg, int mcnt, void *cfg, char *service, date_t *delay, date_t *expire, Bool rds)
{
	void		*up;
	int		n;
	int		err, nerr;
	string_t	*pid;
	char		*tmp;	

	if (! (up = ucp_new (sp)))
		return 1;
	err = ERR_FATAL;
	if (ctab) {
		ucp_add_convtable (up, ctab);
		ctab = cv_free (ctab);
	}
	ucp_config (up, do_log,
		    cfg_bget (cfg, service, CFG_UXTEND, DEF_UXTEND),
		    cfg_iget (cfg, service, CFG_USTOUT, DEF_USTOUT),
		    cfg_iget (cfg, service, CFG_URETRY, DEF_URETRY),
		    cfg_iget (cfg, service, CFG_URTOUT, DEF_URTOUT),
		    delay, expire, rds);
	if ((err = ucp_login (up, callid)) == NO_ERR) {
		for (n = 0; n < mcnt; ++n) {
			pid = snewc (mg[n].pid);
			if (pid) {
				nerr = ucp_transmit (up, pid, mg[n].msg, (n + 1 < mcnt ? False : True));
				if (nerr == NO_ERR)
					mg[n].st.success = True;
				else
					mg[n].st.reason = CantSend;
			} else
				nerr = ERR_FATAL;
			sfree (pid);
			tmp = schar (mg[n].msg);
			do_log ((nerr == NO_ERR ? LGS_SENT : LGF_SENT),
				"UCP Message %ssent to %s via %s: %s", (nerr == NO_ERR ? "" : "NOT "),
				(mg[n].alias ? mg[n].alias : (mg[n].pid ? mg[n].pid : "")), service,
				(tmp ? tmp : ""));
			if (nerr != NO_ERR)
				err = nerr;
			if (ESTOP (err))
				break;
		}
		if ((err != ERR_ABORT) && ((n = ucp_logout (up)) != NO_ERR))
			if (err == NO_ERR)
				err = n;
	}
	ucp_free (up);
	return (err != NO_ERR) ? 1 : 0;
}
/*}}}*/
/*{{{	support routines */
static Protocol
getproto (char *str)
{
	if (str)
		if (! strcmp (str, "ascii"))
			return Ascii;
		else if (! strcmp (str, "script"))
			return Script;
		else if (! strcmp (str, "tap"))
			return Tap;
		else if (! strcmp (str, "ucp"))
			return Ucp;
	return Unknown;
}

static string_t *
readfile (char *fname)
{
	FILE		*fp;
	string_t	*str;
	int		size;
	struct stat	st;
	
	str = NULL;
	if (fp = fopen (fname, "r")) {
		size = 0;
		if (fstat (fileno (fp), & st) != -1)
			size = st.st_size;
		if ((size > 0) && (str = snew (NULL, size + 1)))
			if (fread (str -> str, sizeof (char), size, fp) == size)
				str -> len = size;
			else
				str = sfree (str);
		fclose (fp);
	}
	return str;
}

static string_t *
read_stdin (void)
{
	string_t	*str;
	int		ch;
	
	if (str = snew (NULL, 256))
		while ((ch = getchar ()) != EOF) {
			if (str -> len >= str -> size)
				sexpand (str, str -> size + 64);
			str -> str[str -> len++] = (char_t) ch;
		}
	return str;
}

static void
extend_convtable (void *ctab, void *cfg, char *service)
{
	char	*str;
	char	*cv, *cvs;
	char	*ptr, *sav;
	char	*src, *dst;
	int	n;
	char	*defconv[] = {
		"no-control",
		"control",
		"no-8bit",
		"8bit",
		"numeric",
		NULL
	};
	
	if ((str = cfg_get (cfg, service, CFG_CONVERT, DEF_CONVERT)) &&
	    (str = strdup (str))) {
		for (cv = str; *cv; ) {
			cvs = cv;
			cv = skipch (cv, ',');
			if (*cvs == '*') {
				++cvs;
				for (n = 0; defconv[n]; ++n)
					if (! strcmp (cvs, defconv[n]))
						break;
				switch (n) {
				case 0:		/* no-control */
					for (n = 0; n < 32; ++n) {
						cv_invalid (ctab, (char_t) n);
						cv_invalid (ctab, (char_t) (n | 0x80));
					}
					cv_invalid (ctab, (char_t) '\x7f');
					break;
				case 1:		/* control */
					for (n = 0; n < 32; ++n) {
						cv_undefine (ctab, (char_t) n);
						cv_undefine (ctab, (char_t) (n | 0x80));
					}
					cv_undefine (ctab, (char_t) '\x7f');
					break;
				case 2:		/* no-8bit */
					for (n = 128; n < 256; ++n)
						cv_invalid (ctab, (char_t) n);
					break;
				case 3:		/* 8bit */
					for (n = 128; n < 256; ++n)
						cv_undefine (ctab, (char_t) n);
					break;
				case 4:		/* numeric */
					for (n = 0; n < 256; ++n)
						if (n && strchr ("0123456789", (char) n))
							cv_undefine (ctab, (char_t) n);
						else
							cv_invalid (ctab, (char_t) n);
					break;
				}
			} else if ((cvs = cfg_get (cfg, service, cvs, NULL)) &&
				   (cvs = strdup (cvs))) {
				for (ptr = cvs; ptr; ) {
					sav = ptr;
					if (ptr = strchr (ptr, '\n'))
						*ptr++ = '\0';
					for (src = sav; isspace (*src); ++src)
						;
					if (*src && (*src != '#')) {
						dst = skip (src);
						if (*src && *dst)
							cv_sdefine (ctab, src, dst);
					}
				}
				free (cvs);
			}
		}
		free (str);
	}
}

static int
check (char *str, char *pat)
{
	Bool	done, incs;
	char	*ptr, *sav, *val;
	int	slen;
	int	n, m, chk;
	char	*p;
	char	*chtab[] = {
		"type",
		"length",
		"minimum",
		"maximum",
		NULL
	},	*tytab[] = {
		"numeric",
		"sedecimal",
		"lower",
		"upper",
		"alpha",
		"alphanumeric",
		"print",
		"ascii",
		NULL
	};

	if (! pat)
		return 0;
	done = False;
	if (*pat == '+') {
		++pat;
		if (pat = strdup (pat)) {
			done = True;
			slen = strlen (str);
			for (ptr = pat; *ptr && done; ) {
				sav = ptr;
				ptr = skipch (ptr, ',');
				val = skipch (sav, '=');
				for (n = 0; chtab[n]; ++n)
					if (! strcmp (sav, chtab[n]))
						break;
				switch (n) {
				case 0:		/* type */
					for (m = 0; tytab[m]; ++m)
						if (! strcmp (val, tytab[m]))
							break;
					for (p = str; *p; ++p) {
						switch (m) {
						default:
							chk = 1;
							break;
						case 0:		/* numeric */
							chk = isdigit (*p);
							break;
						case 1:		/* sedecimal */
							chk = isxdigit (*p);
							break;
						case 2:		/* lower */
							chk = islower (*p);
							break;
						case 3:		/* upper */
							chk = isupper (*p);
							break;
						case 4:		/* alpha */
							chk = isalpha (*p);
							break;
						case 5:		/* alphanumeric */
							chk = isalnum (*p);
							break;
						case 6:		/* print */
							chk = isprint (*p);
							break;
						case 7:		/* ascii */
							chk = isascii (*p);
							break;
						}
						if (! chk)
							return -1;
					}
					break;
				case 1:		/* length */
					if (slen != atoi (val))
						return -1;
					break;
				case 2:		/* minimum */
					if (slen < atoi (val))
						return -1;
					break;
				case 3:		/* maximum */
					if (slen > atoi (val))
						return -1;
					break;
				}
			}
		}
		if (! done)
			return -1;
	} else {
		while (*str && *pat) {
			incs = True;
			switch (tolower (*pat)) {
			case '>':
				done = True;
				incs = False;
				break;
			case '<':
				return -1;
				break;
			case '1':
				if (! isdigit (*str))
					return -1;
				break;
			case 'h':
				if (! isxdigit (*str))
					return -1;
				break;
			case 'l':
				if (! islower (*str))
					return -1;
				break;
			case 'u':
				if (! isupper (*str))
					return -1;
				break;
			case 'a':
				if (! isalpha (*str))
					return -1;
				break;
			case 'n':
				if (! isalnum (*str))
					return 1;
				break;
			case 'p':
				if (! isprint (*str))
					return -1;
				break;
			case 'x':
				if (! isascii (*str))
					return -1;
				break;
			}
			if ((! done) || *(pat + 1))
				++pat;
			if (incs)
				++str;
		}
		if ((! done) && (*str || *pat))
			return -1;
	}
	return 0;
}
/*}}}*/
/*{{{	preparse receiver and messages */
static int
ncompare (const void *a, const void *b)
{
	return ((message *) a) -> nr - ((message *) b) -> nr;
}

static int
mcompare (const void *a, const void *b)
{
	const message	*am = (message *) a,
			*bm = (message *) b;
	int		n;

	n = strcmp (am -> service, bm -> service);
	if (! n)
		n = strcmp (am -> pid, bm -> pid);
	return n;
}

static char *
do_replace (char *str, int start, int len, char *rplc, int rlen)
{
	int	n, m;
	int	slen, diff;

	if (rlen > len) {
		slen = strlen (str);
		diff = rlen - len;
		if (! (str = Realloc (str, slen + diff + 2)))
			return NULL;
		for (n = slen; n >= start + len; --n)
			str[n + diff] = str[n];
		str[slen + diff] = '\0';
	} else if (rlen < len) {
		for (n = start + rlen, m = start + len; str[m]; ++n, ++m)
			str[n] = str[m];
		str[n] = '\0';
	}
	if (rlen > 0)
		memcpy (str + start, rplc, rlen);
	return str;
}

static void
remove_invalids (char *str, char *rmv)
{
	int	m, n;
	char	*ptr;
	
	for (n = 0, m = 0; str[n]; ++n) {
		for (ptr = rmv; *ptr; ++ptr)
			if (*ptr == str[n])
				break;
		if (! *ptr)
			if (n != m)
				str[m++] = str[n];
			else
				++m;
	}
	str[m] = '\0';
}

static message *
create_messages (void *cfg, char *service, serv *sbase, char **argv, int argc, char *mfile, int *mcnt,
		 char *callid, char *sig, Bool trunc, Bool force, date_t *delay, date_t *expire, Bool rds)
{
	char		**recv;
	int		rcnt, rsiz;
	FILE		*fp;
	message		*mg;
	int		cnt, siz;
	Bool		lforce;
	serv		*srun;
	int		n, m, len;
	char		*str, *ptr, *sav, *tstr, *tptr;
	string_t	*fstdin;
	Bool		isalias;
	Bool		doins;
	int		rsize;
	string_t	*tmp;
	char		*rmv;
	char		*rplc;
	int		start, end;
	Bool		ttrunc;
	char		*tsig;
	int		msize;
	Bool		msplit;
	char		*sr;

	rcnt = 0;
	rsiz = 0;
	recv = NULL;
	for (n = 0; n < argc; ++n) {
		if (rcnt + 2 >= rsiz) {
			rsiz += 16;
			if (! (recv = (char **) Realloc (recv, (rsiz + 2) * sizeof (char *)))) {
				OOM;
				return NULL;
			}
		}
		if (! (recv[rcnt] = strdup (argv[n]))) {
			OOM;
			return NULL;
		}
		++rcnt;
	}
	if (mfile) {
		if (! (fp = fopen (mfile, "r"))) {
			fprintf (stderr, "Unable to open message file %s for reading\n", mfile);
			return NULL;
		}
		while (ptr = getline (fp, False)) {
			sav = skip (ptr);
			if (*ptr && *sav) {
				if (rcnt + 2 >= rsiz) {
					rsiz += 16;
					if (! (recv = (char **) Realloc (recv, (rsiz + 2) * sizeof (char *)))) {
						OOM;
						return NULL;
					}
				}
				if ((! (recv[rcnt] = strdup (ptr))) ||
				    (! (recv[rcnt + 1] = strdup (sav)))) {
					OOM;
					return NULL;
				}
				rcnt += 2;
			}
		}
		fclose (fp);
	}
	if (! recv) {
		fprintf (stderr, "No receiver found\n");
		return NULL;
	}
	recv[rcnt] = NULL;

	for (n = 0; n < rcnt; n += 2) {
		ptr = recv[n];
		if (((! isdigit (*ptr)) || (*ptr == ':')) && (*ptr != '\\')) {
			if (*ptr == ':')
				++ptr;
			if (! (str = strdup (ptr))) {
				OOM;
				return NULL;
			}
			m = 0;
			tstr = 0;
			for (ptr = str; *ptr; ) {
				sav = ptr;
				ptr = skipch (ptr, ',');
				if ((! (tptr = cfg_block_get (cfg, SEC_ALIAS, sav, NULL))) ||
				    (! strchr (tptr, ',')))
					tptr = sav;
				len = strlen (tptr);
				if (tstr = Realloc (tstr, m + len + 2)) {
					if (m)
						tstr[m++] = ',';
					strcpy (tstr + m, tptr);
					m += len;
				}
			}
			free (str);
			free (recv[n]);
			if (! (recv[n] = tstr)) {
				OOM;
				return NULL;
			}
		}
	}

	mg = NULL;
	cnt = 0;
	siz = 0;
	fstdin = NULL;
	for (n = 0; n < rcnt; n += 2)
		if (str = recv[n]) {
			for (ptr = str; *ptr; ) {
				if (cnt >= siz) {
					siz += 4;
					if (! (mg = (message *) Realloc (mg, (siz + 1) * sizeof (message)))) {
						OOM;
						return NULL;
					}
				}
				mg[cnt].nr = cnt;
				sav = ptr;
				ptr = skipch (ptr, ',');
				if (*sav == '/') {
					++sav;
					isalias = False;
				} else if (*sav == ':') {
					++sav;
					isalias = True;
				} else if (! isdigit (*sav)) {
					rmv = cfg_get (cfg, NULL, CFG_RMPID, NULL);
					if (rmv && *sav && strchr (rmv, *sav))
						isalias = False;
					else
						isalias = True;
				} else
					isalias = False;
				if (isalias) {
					if (! (mg[cnt].alias = strdup (sav))) {
						OOM;
						return NULL;
					}
					if (! (tstr = cfg_block_get (cfg, SEC_ALIAS, sav, NULL))) {
						fprintf (stderr, "No alias %s found\n", sav);
						return NULL;
					} else
						sav = tstr;
				} else
					mg[cnt].alias = NULL;
				mg[cnt].pid = strdup (sav);
				sav = recv[n + 1];
				if (*sav == '+') {
					if (! (mg[cnt].msg = readfile (sav + 1))) {
						fprintf (stderr, "Unable to read %s, aborted\n", sav + 1);
						return NULL;
					}
				} else if ((*sav == '-') && (! *(sav + 1))) {
					if (! fstdin)
						if (! (fstdin = read_stdin ())) {
							fprintf (stderr, "Unable to read from stdin, aborted\n");
							return NULL;
						}
					mg[cnt].msg = snew (fstdin -> str, fstdin -> len);
				} else if ((*sav == '.') && (! *(sav + 1)))
					mg[cnt].msg = snew (NULL, 0);
				else {
					if (*sav == '\\')
						++sav;
					mg[cnt].msg = snewc (sav);
				}
				if (! (mg[cnt].pid && mg[cnt].msg)) {
					OOM;
					return NULL;
				}
				mg[cnt].callid = NULL;
				mg[cnt].service = NULL;
				mg[cnt].prot = Unknown;
				mg[cnt].s = NULL;
				memset (& mg[cnt].st, 0, sizeof (mg[cnt].st));
				rmv = cfg_get (cfg, mg[cnt].service, CFG_RMPID, NULL);
				for (srun = sbase; srun; srun = srun -> next)
					if ((! service) || (! strcmp (srun -> name, service))) {
						if (rmv)
							remove_invalids (mg[cnt].pid, rmv);
						if (v_alidate (srun -> pchk, mg[cnt].pid, & start, & end))
							break;
					}
				if (! srun) {
					fprintf (stderr, "No service for pager id %s found\n", mg[cnt].pid);
					return NULL;
				}
				if (! (mg[cnt].service = strdup (srun -> name))) {
					OOM;
					return NULL;
				}
				if (rplc = cfg_get (cfg, mg[cnt].service, CFG_CHPID, NULL)) {
					if (! strcmp (rplc, "-"))
						rplc = "";
					else if (*rplc == '\\')
						++rplc;
					if (! (mg[cnt].pid = do_replace (mg[cnt].pid, start, end - start, rplc, strlen (rplc)))) {
						OOM;
						return NULL;
					}
				}
				mg[cnt].s = srun;
				V (4, ("Found service %s for %s\n", mg[cnt].service, mg[cnt].pid));
				++cnt;
			}
			free (str);
		}
	free (recv);
	if (fstdin)
		sfree (fstdin);
	if (cnt > 1)
		qsort (mg, cnt, sizeof (message), mcompare);
	for (n = 0; n < cnt; ++n) {
		sr = mg[n].service;
		if ((mg[n].prot = getproto (cfg_get (cfg, sr, CFG_PROTOCOL, NULL))) == Unknown) {
			fprintf (stderr, "Unknown protocol for service %s\n", sr);
			return NULL;
		}
		if (! callid)
			ptr = cfg_get (cfg, sr, CFG_CALLID, NULL);
		else
			ptr = callid;
		if (ptr)
			if (! (mg[n].callid = strdup (ptr))) {
				OOM;
				return NULL;
			} else if (rmv = cfg_get (cfg, sr, CFG_RMCID, NULL))
				remove_invalids (mg[n].callid, rmv);
		if (cfg_bget (cfg, sr, CFG_USECID, DEF_USECID)) {
			if (! mg[n].callid) {
				fprintf (stderr, "Need caller id for service %s\n", sr);
				return NULL;
			}
		} else if (mg[n].callid) {
			free (mg[n].callid);
			mg[n].callid = NULL;
		}
		if (mg[n].callid) {
			if (mg[n].s && mg[n].s -> cchk)
				if (! v_alidate (mg[n].s -> cchk, mg[n].callid, & start, & end)) {
					fprintf (stderr, "Call ID %s is invalid for service %s\n", mg[n].callid, mg[n].service);
					return NULL;
				}
				if (rplc = cfg_get (cfg, mg[n].service, CFG_CHCID, NULL)) {
					if (! strcmp (rplc, "-"))
						rplc = "";
					else if (*rplc == '\\')
						++rplc;
					if (! (mg[n].callid = do_replace (mg[n].callid, start, end - start, rplc, strlen (rplc)))) {
						OOM;
						return NULL;
					}
				}
			if (check (mg[n].callid, cfg_get (cfg, sr, CFG_CHKCID, NULL)) < 0) {
				fprintf (stderr, "Invalid caller id %s for service %s\n", mg[n].callid, sr);
				return NULL;
			}
		}
		if (check (mg[n].pid, cfg_get (cfg, sr, CFG_CHKPID, NULL)) < 0) {
			fprintf (stderr, "Invalid pager id %s for service %s\n", mg[n].pid, sr);
			return NULL;
		}
		if (force)
			lforce = force;
		else
			lforce = cfg_bget (cfg, sr, CFG_FORCE, False);
		if (delay && (! cfg_bget (cfg, sr, CFG_CANDELAY, False)) && (! lforce)) {
			fprintf (stderr, "Service %s is unable to delay a message\n", sr);
			return NULL;
		}
		if (expire && (! cfg_bget (cfg, sr, CFG_CANEXP, False)) && (! lforce)) {
			fprintf (stderr, "Service %s is unable to set expiration\n", sr);
			return NULL;
		}
		if (! rds)
			rds = cfg_bget (cfg, sr, CFG_DORDS, False);
		if (rds && (! cfg_bget (cfg, sr, CFG_CANRDS, False)) && (! lforce)) {
			fprintf (stderr, "Service %s is unable to report delivery status\n", sr);
			return NULL;
		}
		if (! sig)
			tsig = cfg_get (cfg, sr, CFG_SIG, NULL);
		else
			tsig = sig;
		if (tsig && (! cfg_bget (cfg, sr, CFG_USESIG, False)))
			tsig = NULL;
		if (tsig && *tsig)
			if (! (scatc (mg[n].msg, " ") && scatc (mg[n].msg, tsig))) {
				OOM;
				return NULL;
			}
		mg[n].s = NULL;
	}
	for (n = 0; n < cnt; ++n) {
		sr = mg[n].service;
		if (! trunc)
			ttrunc = cfg_bget (cfg, sr, CFG_TRUNCATE, False);
		else
			ttrunc = trunc;
		msize = cfg_iget (cfg, sr, CFG_MAXSIZE, 0);
		msplit = cfg_bget (cfg, sr, CFG_MAYSPLIT, False);
		if ((msize > 0) && (mg[n].msg -> len > msize))
			if (trunc)
				mg[n].msg -> len = msize;
			else if (! msplit) {
				fprintf (stderr, "Unable to split message for service %s\n", sr);
				return NULL;
			} else {
				doins = (msize > 40) ? True : False;
				if (cnt >= siz) {
					siz += 4;
					if (! (mg = (message *) Realloc (mg, (siz + 1) * sizeof (message)))) {
						OOM;
						return NULL;
					}
				}
				for (m = cnt; m > n + 1; --m)
					mg[m] = mg[m - 1];
				mg[n + 1].nr = mg[n].nr;
				mg[n + 1].callid = mg[n].callid ? strdup (mg[n].callid) : NULL;
				mg[n + 1].alias = mg[n].alias ? strdup (mg[n].alias) : NULL;
				mg[n + 1].pid = strdup (mg[n].pid);
				mg[n + 1].service = strdup (mg[n].service);
				mg[n + 1].prot = mg[n].prot;
				if (doins)
					rsize = msize - (sizeof (SPLIT_INIT) - 1);
				else
					rsize = msize;
				for (m = 1; (m < 15) && (m < rsize); ++m)
					if (sisspace (mg[n].msg, rsize - m)) {
						rsize -= m;
						sdel (mg[n].msg, rsize, 1);
						break;
					}
				if (doins) {
					if (mg[n + 1].msg = snewc (SPLIT_INIT))
						if (tmp = scut (mg[n].msg, rsize, mg[n].msg -> len - rsize)) {
							if (! scat (mg[n + 1].msg, tmp))
								mg[n + 1].msg = sfree (mg[n + 1].msg);
							else {
								mg[n].msg -> len = rsize;
								if (! scatc (mg[n].msg, SPLIT_TERM))
									mg[n].msg = sfree (mg[n].msg);
							}
							sfree (tmp);
						} else
							mg[n + 1].msg = sfree (mg[n + 1].msg);
				} else {
					mg[n + 1].msg = scut (mg[n].msg, rsize, mg[n].msg -> len - rsize);
					mg[n].msg -> len = rsize;
				}
				mg[n + 1].s = NULL;
				memset (& mg[n + 1].st, 0, sizeof (mg[n + 1].st));
				srelease (mg[n].msg);
				if (! (mg[n + 1].pid && mg[n + 1].msg && mg[n + 1].service && mg[n].msg)) {
					OOM;
					return NULL;
				}
				++cnt;
			}
	}
# ifndef	NDEBUG	
	if (verbose >= 4) {
		(*verbout) ("Sending following message%s:\n", (cnt == 1 ? "" : "s"));
		for (n = 0; n < cnt; ++n)
			if (mg[n].pid && mg[n].msg)
				(*verbout) ("%-12s (%s, %s): %s\n", (mg[n].alias ? mg[n].alias : mg[n].pid), mg[n].service, mg[n].pid, schar (mg[n].msg));
	}
# endif		/* NDEBUG */
	*mcnt = cnt;
	return mg;
}
/*}}}*/
/*{{{	calculate costs */
# define	WDAY(cc1,cc2)		((((unsigned char) (cc1)) << 8) | ((unsigned char) (cc2)))
# define	DAY			(60 * 24)

typedef struct _ttable {
	int	wday;
	int	start, end;
	double	elen;
	struct _ttable
		*next;
}	ttable;

static char	*vtab[] = {
	"fixed",
	"entity-length",
	"max-entities",
	"dial-overhead",
	"cost",
	"unit",
	"timetable",
	"remainder",
	NULL
};

static int
tonum (char ch)
{
	switch (ch) {
	default:
	case '0':	return 0;
	case '1':	return 1;
	case '2':	return 2;
	case '3':	return 3;
	case '4':	return 4;
	case '5':	return 5;
	case '6':	return 6;
	case '7':	return 7;
	case '8':	return 8;
	case '9':	return 9;
	}
}

static ttable *
parse_timetable (char *str)
{
	ttable	*base, *prev, *tmp;
	char	*ptr, *sav, *cptr;
	int	n;
	
	base = NULL;
	prev = NULL;
	for (ptr = str; *ptr; ) {
		sav = ptr;
		ptr = skipch (ptr, ';');
		cptr = skipch (sav, '=');
		if (tmp = (ttable *) malloc (sizeof (ttable))) {
			tmp -> wday = 0;
			tmp -> start = 0;
			tmp -> end = DAY - 1;
			tmp -> elen = atof (cptr);
			tmp -> next = NULL;
			while (*sav)
				if (isalpha (*sav) && isalpha (*(sav + 1))) {
					*sav = tolower (*sav);
					*(sav + 1) = tolower (*(sav + 1));
					switch (WDAY (*sav, *(sav + 1))) {
					case WDAY ('s', 'o'):	tmp -> wday |= (1 << 0);	break;
					case WDAY ('m', 'o'):	tmp -> wday |= (1 << 1);	break;
					case WDAY ('t', 'u'):	tmp -> wday |= (1 << 2);	break;
					case WDAY ('w', 'e'):	tmp -> wday |= (1 << 3);	break;
					case WDAY ('t', 'h'):	tmp -> wday |= (1 << 4);	break;
					case WDAY ('f', 'r'):	tmp -> wday |= (1 << 5);	break;
					case WDAY ('s', 'a'):	tmp -> wday |= (1 << 6);	break;
					case WDAY ('w', 'k'):
						tmp -> wday |= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5);
						break;
					case WDAY ('s', 's'):
						tmp -> wday |= (1 << 0) | (1 << 6);
						break;
					case WDAY ('a', 'l'):
						tmp -> wday |= (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
						break;
					}
					sav += 2;
				} else
					break;
			while (*sav && (! isdigit (*sav)))
				if (*sav == '-')
					break;
			if (isdigit (*sav)) {
				n = 0;
				while (isdigit (*sav)) {
					n *= 10;
					n += tonum (*sav++);
				}
				tmp -> start = (n / 100) * 60 + n % 100;
			}
			while (*sav && (! isdigit (*sav)))
				++sav;
			if (isdigit (*sav)) {
				n = 0;
				while (isdigit (*sav)) {
					n *= 10;
					n += tonum (*sav++);
				}
				tmp -> end = (n / 100) * 60 + n % 100;
			}
			if (prev)
				prev -> next = tmp;
			else
				base = tmp;
			prev = tmp;
		}
	}
	return base;
}

static inline Bool
timetable_valid (ttable *t, int wday, int tim)
{
	if (t -> wday & (1 << wday))
		if (t -> end > t -> start) {
			if ((tim >= t -> start) &&
			    (tim <= t -> end))
				return True;
		} else 
			if ((tim <= t -> end) ||
			    (tim >= t -> start))
				return True;
	return False;
}

static char *
calc_cost (void *cfg, char *service, time_t start, int dur)
{
	char		*ret;
	char		*tstr;
	char		*ptr, *var, *val;
	int		n;
	Bool		fixed;
	double		elen;
	int		ment;
	int		dover;
	double		cost;
	char		*unit;
	char		*ttab;
	int		remainder;
	ttable		*tbase, *trun;
	struct tm	*tt;
	double		fdur;
	int		cur, wday;
	double		sec;
	int		ents;
	double		fcost;

	if ((! (tstr = cfg_get (cfg, service, CFG_COST, NULL))) ||
	    (! (tstr = strdup (tstr))))
		return NULL;
	ret = NULL;
	fixed = False;
	elen = 0.0;
	ment = -1;
	dover = 0;
	cost = 0.0;
	unit = NULL;
	ttab = NULL;
	remainder = 2;
	for (ptr = tstr; *ptr; ) {
		var = ptr;
		ptr = skipch (ptr, ',');
		val = skipch (var, '=');
		for (n = 0; vtab[n]; ++n)
			if (! strcmp (vtab[n], var))
				break;
		switch (n) {
		case 0:		/* fixed */
			fixed = True;
			break;
		case 1:		/* entity-length */
			elen = atof (val);
			break;
		case 2:		/* max-entities */
			ment = atoi (val);
			break;
		case 3:		/* dial-overhead */
			dover = atoi (val);
			break;
		case 4:		/* cost */
			cost = atof (val);
			break;
		case 5:		/* unit */
			unit = val;
			break;
		case 6:		/* timetable */
			ttab = val;
			break;
		case 7:		/* remainder */
			remainder = atoi (val);
			break;
		}
	}
	if (dover > 0) {
		start += dover;
		dur -= dover;
		if (dur < 1)
			dur = 1;
	}
	if (ttab && (tbase = parse_timetable (ttab))) {
		if (tt = localtime (& start)) {
			cur = tt -> tm_hour * 60 + tt -> tm_min;
			sec = (double) tt -> tm_sec;
			wday = tt -> tm_wday;
			ents = 0;
			fdur = 0;
			trun = NULL;
			while (fdur <= (double) dur) {
				if ((! trun) || (! timetable_valid (trun, wday, cur))) {
					for (trun = tbase; trun; trun = trun -> next)
						if (timetable_valid (trun, wday, cur))
							break;
					if (! trun)
						trun = tbase;
				}
				if (fixed) {
					cost = trun -> elen;
					break;
				}
				if (trun -> elen < 0.1)
					break;
				++ents;
				fdur += trun -> elen;
				sec += trun -> elen;
				while (sec >= 60.0) {
					sec -= 60.0;
					++cur;
					while (cur >= DAY) {
						cur -= DAY;
						wday++;
						if (wday > 6)
							wday = 0;
					}
				}
			}
		}
		while (tbase) {
			trun = tbase;
			tbase = tbase -> next;
			free (trun);
		}
	} else
		ents = (int) ((double) dur / elen) + 1;
	if ((ment > 0) && (ents > ment))
		ents = ment;
	if (fixed)
		fcost = cost;
	else
		fcost = cost * (double) ents;
	if (fcost > 0.01) {
		do_log (LG_COST, "Session costs aprox. %.*f %s", remainder, fcost, (unit ? unit : ""));
		if (ret = malloc (64 + (unit ? strlen (unit) : 0))) {
			sprintf (ret, "%.*f", remainder, fcost);
			if (unit) {
				for (ptr = ret; *ptr; ++ptr)
					;
				*ptr++ = ' ';
				strcpy (ptr, unit);
			}
		}
	}
	free (tstr);
	return ret;
}
/*}}}*/
/*{{{	send pages */
static int
sendit (void *cfg, int cur, message *mg, int mcnt, int *rerr,
	date_t *delay, date_t *expire, Bool rds)
{
	char		*sr;
	Bool		err;
	int		sig;
	int		n, m;
	char		*ptr;
	int		cnt;
	int		mmsgs;
	Bool		inspid;
	char		*convtab;
	void		*ctab;
	message		*send;
	int		count;
	void		*sp;
	char		*modem, *reset;
	int		tout;
	int		(*transmit) (void *, void *, string_t *, message *, int, void *, char *, date_t *, date_t *, Bool);
	time_t		start, end;
	int		diff;
	string_t	*cid;
	
	sr = mg[cur].service;
	mmsgs = cfg_iget (cfg, sr, CFG_MAXMSGS, 0);
	inspid = cfg_bget (cfg, sr, CFG_INSPID, DEF_INSPID);
	for (n = cur, cnt = 0; n < mcnt; ++n, ++cnt) {
		if (n != cur)
			if (strcmp (mg[n].service, sr))
				break;
			else if ((mmsgs > 0) && (cnt == mmsgs))
				break;
			else if (inspid && strcmp (mg[cur].pid, mg[n].pid))
				break;
		mg[n].st.success = False;
		mg[n].st.reason = NotTried;
		mg[n].st.rmsg = NULL;
	}
	ctab = NULL;
	if (ctab = cv_new ()) {
		if (convtab = cfg_get (cfg, sr, CFG_CONVTAB, DEF_CONVTAB))
			cv_read_table (ctab, convtab);
		extend_convtable (ctab, cfg, sr);
	}
	send = mg + cur;
	count = cnt;
	do_log (LG_SSESSION, "Starting up for service %s", sr);
	time (& start);
	err = 0;
	if (sp = do_dial (cfg, sr, (inspid ? mg[0].pid : NULL), & modem)) {
		for (n = 0; n < cnt; ++n)
			mg[cur + n].st.reason = NotSend;
		if (! (sig = setjmp (env))) {
			dojump = True;
			switch (mg[cur].prot) {
			default:
			case Unknown:
				transmit = NULL;
				break;
			case Ascii:
				transmit = asc_send;
				break;
			case Script:
				transmit = scr_send;
				break;
			case Tap:
				transmit = tap_send;
				break;
			case Ucp:
				transmit = ucp_send;
				break;
			}
			if (transmit) {
				cid = mg[0].callid ? snewc (mg[0].callid) : NULL;
				err = (*transmit) (sp, ctab, cid, send, count, cfg, sr, delay, expire, rds);
				sfree (cid);
			} else
				err = 1;
			tty_drain (sp, 1);
		} else
			V (2, ("\n"));
		dojump = False;
		for (n = 0, m = -1; n < cnt; ++n)
			if (mg[n + cur].st.success == True)
				m = n;
		if (m != -1)
			cnt = m + 1;
		tty_hangup (sp, 500);
		time (& end);
		diff = (int) (end - start);
		if (ptr = calc_cost (cfg, sr, start, diff))
			if (! mg[cur].st.rmsg)
				mg[cur].st.rmsg = ptr;
			else
				free (ptr);
		do_log (LG_ESESSION, "Session %s on service %s (duration %d:%02d)", (err ? "failed" : "succeeded"), sr, diff / 60, diff % 60);
		reset = cfg_get (cfg, modem, CFG_RESET, DEF_RESET);
		if (reset) {
			tout = cfg_iget (cfg, modem, CFG_TIMEOUT, DEF_TIMEOUT);
			tty_send_expect (sp, tout, reset, NULL);
		}
		tty_close (sp);
		if (modem)
			free (modem);
	} else {
		for (n = cur + 1; n < mcnt; ++n)
			if (strcmp (sr, mg[n].service))
				break;
		cnt = n - cur;
		for (n = 0; n < cnt; ++n)
			mg[cur + n].st.reason = CantDial;
		do_log (LG_ESESSION, "Unable to dial service %s", sr);
		fprintf (stderr, "Unable to dial %s\n", sr);
		err = 1;
	}
	if (err)
		*rerr = err;
	return cur + cnt;
}
/*}}}*/
/*{{{	print configuration values */
static void
print_config (void *cfg, char *service, char **vars)
{
	int	n;
	char	*val;
	
	while (*vars) {
		printf ("%s ->", *vars);
		for (n = service ? 0 : 1; n < 2; ++n) {
			if (n && service)
				printf (" (");
			else
				printf (" ");
			if (val = n ? cfg_get (cfg, NULL, *vars, NULL) : cfg_block_get (cfg, service, *vars, NULL))
				printf ("%s", val);
			else
				printf ("*none*");
			if (n && service)
				printf (")");
			if (! n)
				printf (" ->");
			else
				printf ("\n");
		}
		++vars;
	}
}
/*}}}*/
/*{{{	getopt */
static int	optind = 1;
static int	optidx = 0;
static char	*optarg = NULL;

static int
getopt (int argc, char **argv, char *what)
{
	char	ch, *ptr;
	
	optarg = NULL;
	if (optind >= argc)
		return -1;
	do {
		if (! argv[optind][optidx]) {
			optidx = 0;
			++optind;
			if (optind >= argc)
				return -1;
		}
		if (optidx == 0) 
			if (argv[optind][0] != '-')
				return -1;
			else
				optidx = 1;
	}	while (! optidx);
	if ((optidx == 1) && (argv[optind][optidx] == '-') && (! argv[optind][optidx + 1])) {
		++optind;
		return -1;
	}
	ch = argv[optind][optidx++];
	if (! (ptr = strchr (what, ch))) {
		fprintf (stderr, "%s: Unknown option -%c\n", argv[0] ? argv[0] : "", ch);
		return '?';
	}
	++ptr;
	if (*ptr == ':')
		if (argv[optind][optidx]) {
			optarg = (argv[optind]) + optidx;
			++optind;
			optidx = 0;
		} else {
			++optind;
			if (optind < argc)
				optarg = argv[optind++];
			else {
				fprintf (stderr, "%s: Missiong parameter for option -%c\n", argv[0] ? argv[0] : "", ch);
				ch = ':';
			}
			optidx = 0;
		}
	return ch;
}
/*}}}*/
/*{{{	usage/version */
static void
usage (char *pgm)
{
# if		HAVE_GETOPT_LONG
# define	PLONG(xxx)		fprintf xxx
# else		/* HAVE_GETOPT_LONG */
# define	PLONG(xxx)		putc ('\t', stderr)
# endif		/* HAVE_GETOPT_LONG */
	fprintf (stderr, "Usgae: %s [<opts>] {<pagerid> <message>}+\n", pgm);
	fprintf (stderr, "Function: sends messages to paging devices\n");
	fprintf (stderr, "Options:\n");
	PLONG ((stderr, "\t--config=<fname>,          "));
	fprintf (stderr, "-C <fname>      uses alternate global configurtion file\n");
	PLONG ((stderr, "\t--service=<service>,       "));
	fprintf (stderr, "-s <service>    use this service, if applicated\n");
	PLONG ((stderr, "\t--truncate,                "));
	fprintf (stderr, "-t              truncates too long messages\n");
	PLONG ((stderr, "\t--call-id=<callid>,        "));
	fprintf (stderr, "-c <callid>     if possible, use this caller id\n");
	PLONG ((stderr, "\t--signature=<signature>,   "));
	fprintf (stderr, "-S <sig>        add this signature to the message\n");
	PLONG ((stderr, "\t--logfile=<fname>,         "));
	fprintf (stderr, "-l <fname>      log activities to a file\n");
	PLONG ((stderr, "\t--logstring=<string>,      ")),
	fprintf (stderr, "-L <string>     log only these activities\n");
	PLONG ((stderr, "\t--force,                   "));
	fprintf (stderr, "-f              force sending, even if -d/-e/-r is not supported\n");
	PLONG ((stderr, "\t--delay=<date>,            "));
	fprintf (stderr, "-d <date>       when to send the message (if supported)\n");
	PLONG ((stderr, "\t--expire=<date>,           "));
	fprintf (stderr, "-e <date>       when should the mesage expire (if supported)\n");
	PLONG ((stderr, "\t--request-delivery-status, "));
	fprintf (stderr, "-r              request delivery report (if supported)\n");
	PLONG ((stderr, "\t--final-report=<fname>,    "));
	fprintf (stderr, "-R <fname>      store the final report to <fname>\n");
	PLONG ((stderr, "\t--message=file=<fname>,    "));
	fprintf (stderr, "-z <fname>      read pager-id/message pairs from <fname>\n");
	PLONG ((stderr, "\t--verbose[=<level>],       "));
	fprintf (stderr, "-v              increase verbosity\n");
	fprintf (stderr, "\t<pagerid>       this is the receiver of the message\n");
	fprintf (stderr, "\t<message>       the message, if it is prefixed by a plus sign (+), then the remaining part\n");
	fprintf (stderr, "\t                is taken as a filename from where the message is read\n");
	fprintf (stderr, "\n");
	PLONG ((stderr, "\t--print-config,            "));
	fprintf (stderr, "-p              print configuration variables/values\n");
	fprintf (stderr, "\t                the arguments are the variable names to be displayed\n");
	PLONG ((stderr, "\t--version,                 "));
	fprintf (stderr, "-V              show version and compile informations\n");
# ifdef		VERSION
	fprintf (stderr, "\nVersion: %s\n", VERSION);
# endif		/* VERSION */
# undef		PLONG	
}

static void
show_version (char *pgm)
{
	printf ("%s: V. %s\n", pgm ? pgm : "yaps", VERSION);
	printf ("Definitions:");
# if		POSIX_SIGNAL
	printf (" POSIX_SIGNAL");
# endif		/* POSIX_SIGNAL */
# if		BSD_SIGNAL
	printf (" BSD_SIGNAL");
# endif		/* BSD_SIGNAL */
# if		SYSV_SIGNAL
	printf (" SYSV_SIGNAL");
# endif		/* SYSV_SIGNAL */
# if		SIG_VOID_RETURN
	printf (" SIG_VOID_RETURN");
# endif		/* SIG_VOID_RETURN */
# if		SIG_INT_RETURN
	printf (" SIG_INT_RETURN");
# endif		/* SIG_INT_RETURN */
# if		HAVE_SYS_SELECT_H
	printf (" HAVE_SYS_SELECT_H");
# endif		/* HAVE_SYS_SELECT_H */
# if		HAVE_LOCALE_H
	printf (" HAVE_LOCALE_H");
# endif		/* HAVE_LOCALE_H */
# if		HAVE_REGEX_H
	printf (" HAVE_REGEX_H");
# endif		/* HAVE_REGEX_H */
# if		HAVE_SYS_SYSMACROS_H
	printf (" HAVE_SYS_SYSMACROS_H");
# endif		/* HAVE_SYS_SYSMACROS_H */
# if		HAVE_SYS_MKDEV_H
	printf (" HAVE_SYS_MKDEV_H");
# endif		/* HAVE_SYS_MKDEV_H */
# if		HAVE_GETOPT_H
	printf (" HAVE_GETOPT_H");
# endif		/* HAVE_GETOPT_H */
# if		HAVE_TZSET
	printf (" HAVE_TZSET");
# endif		/* HAVE_TZSET */
# if		HAVE_FCHMOD
	printf (" HAVE_FCHMOD");
# endif		/* HAVE_FCHMOD */
# if		HAVE_FCHOWN
	printf (" HAVE_FCHOWN");
# endif		/* HAVE_FCHOWN */
# if		HAVE_SIGSETJMP
	printf (" HAVE_SIGSETJMP");
# endif		/* HAVE_SIGSETJMP */
# if		HAVE_MEMCPY
	printf (" HAVE_MEMCPY");
# endif		/* HAVE_MEMCPY */
# if		HAVE_BCOPY
	printf (" HAVE_BCOPY");
# endif		/* HAVE_BCOPY */
# if		HAVE_MEMSET
	printf (" HAVE_MEMSET");
# endif		/* HAVE_MEMSET */
# if		HAVE_BZERO
	printf (" HAVE_BZERO");
# endif		/* HAVE_BZERO */
# if		HAVE_GETOPT
	printf (" HAVE_GETOPT");
# endif		/* HAVE_GETOPT */
# if		HAVE_GETOPT_LONG
	printf (" HAVE_GETOPT_LONG");
# endif		/* HAVE_GETOPT_LONG */
# if		NEED_OPTIND_OPTARG
	printf (" NEED_OPTIND_OPTARG");
# endif		/* NEED_OPTIND_OPTARG */
# if		BROKEN_REALLOC
	printf (" BROKEN_REALLOC");
# endif		/* BROKEN_REALLOC */
	printf ("\n");
}
/*}}}*/
/*{{{	main */
# if		HAVE_GETOPT_LONG
static struct option	opttab[] = {
	{	"config",	required_argument,	NULL,	'C'	},
	{	"service",	required_argument,	NULL,	's'	},
	{	"truncate",	no_argument,		NULL,	't'	},
	{	"call-id",	required_argument,	NULL,	'c'	},
	{	"signature",	required_argument,	NULL,	'S'	},
	{	"logfile",	required_argument,	NULL,	'l'	},
	{	"logstring",	required_argument,	NULL,	'L'	},
	{	"force",	no_argument,		NULL,	'f'	},
	{	"delay",	required_argument,	NULL,	'd'	},
	{	"expire",	required_argument,	NULL,	'e'	},
	{	"request-delivery-status", no_argument,	NULL,	'r'	},
	{	"final-report",	required_argument,	NULL,	'R'	},
	{	"message-file",	required_argument,	NULL,	'z'	},
	{	"verbose",	optional_argument,	NULL,	'v'	},
	{	"print-config",	no_argument,		NULL,	'p'	},
	{	"version",	no_argument,		NULL,	'V'	},
	{	"help",		no_argument,		NULL,	'h'	},
	{	NULL,		0,			NULL,	0	}
};
# define	GETOPT(what)		getopt_long (argc, argv, (what), opttab, NULL)
# else		/* HAVE_GETOPT_LONG */
# define	GETOPT(what)		getopt (argc, argv, (what))
# endif		/* HAVE_GETOPT_LONG */

int
main (int argc, char **argv)
{
# if		NEED_OPTIND_OPTARG
	extern int	optind;
	extern char	*optarg;
# endif		/* NEED_OPTIND_OPTARG */
	char		*cfgfile;
	void		*cfg;
	char		*home, *lfn;
	int		n, m;
	int		ret;
	char		*svcs;
	serv		*sbase, *sprev, *stmp;
	char		*chk;
	char		*service;
	Bool		trunc;
	char		*callid;
	char		*sig;
	Bool		force;
	date_t		*delay, *expire;
	Bool		rds;
	char		*freport;
	FILE		*fp;
	char		*mfile;
	Bool		pcfg;
	date_t		*cur;
	message		*mg;
	int		mcnt;
	int		onr;
	Bool		succ;
	char		*ptr, *sav;

	cfgfile = "yaps.rc";
	service = NULL;
	trunc = False;
	callid = NULL;
	sig = NULL;
	force = False;
	delay = NULL;
	expire = NULL;
	rds = False;
	freport = NULL;
	mfile = NULL;
	pcfg = False;
	logfile = NULL;
	logstr = NULL;
	verbose = -1;
# if		HAVE_TZSET	
	tzset ();
# endif		/* HAVE_TZSET */
# if		HAVE_LOCALE_H
	setlocale (LC_ALL, "");
# endif		/* HAVE_LOCALE_H */
	while ((n = GETOPT ("C:s:tc:S:l:L:fd:e:rR:z:vpVh")) != -1)
		switch (n) {
		case 'C':
			cfgfile = optarg;
			break;
		case 's':
			service = optarg;
			break;
		case 't':
			trunc = True;
			break;
		case 'c':
			callid = optarg;
			break;
		case 'S':
			sig = optarg;
			break;
		case 'l':
			logfile = optarg;
			break;
		case 'L':
			logstr = optarg;
			break;
		case 'f':
			force = True;
			break;
		case 'd':
			if (delay)
				delay = dat_free (delay);
			if (! (delay = dat_parse (optarg))) {
				fprintf (stderr, "Invalid date format for delay\n");
				return 1;
			}
			break;
		case 'e':
			if (expire)
				expire = dat_free (expire);
			if (! (expire = dat_parse (optarg))) {
				fprintf (stderr, "Invalid date format for expire\n");
				return 1;
			}
			break;
		case 'r':
			rds = True;
			break;
		case 'R':
			freport = optarg;
			break;
		case 'z':
			mfile = optarg;
			break;
		case 'v':
# if		HAVE_GETOPT_LONG
			if (optarg)
				verbose = atoi (optarg);
			else
# endif		/* HAVE_GETOPT_LONG */
			if (verbose < 0)
				verbose = 1;
			else
				++verbose;
			break;
		case 'p':
			pcfg = True;
			break;
		case 'V':
			show_version (argv[0]);
			return 0;
		case 'h':
		default:
			usage (argv[0]);
			return n == 'h' ? 0 : 1;
		}
	if (delay || expire) {
		if (cur = dat_parse (NULL)) {
			if (delay && (dat_diff (cur, delay) < 0)) {
				fprintf (stderr, "Delay is before now, invalid\n");
				return 1;
			}
			if (expire && (dat_diff (cur, expire) < 0)) {
				fprintf (stderr, "Expiration already reached\n");
				return 1;
			}
			dat_free (cur);
		}
		if (delay && expire)
			if (dat_diff (delay, expire) < 0) {
				fprintf (stderr, "Message expires before it should be send\n");
				return 1;
			}
	}
	cfg = cfg_read (cfgfile, NULL, CONFIG_SEP);
	if ((home = getenv ("HOME")) && (lfn = malloc (strlen (home) + sizeof (".yaps.rc") + 4))) {
		sprintf (lfn, "%s/%s", home, ".yaps.rc");
		cfg = cfg_read (lfn, cfg, CONFIG_SEP);
		free (lfn);
	}
	if (pcfg) {
		print_config (cfg, service, argv + optind);
		return 0;
	}
	if (((optind == argc) && (! mfile)) || ((argc - optind) & 1)) {
		if ((optind == argc) && (! mfile))
			fprintf (stderr, "No message\n");
		if ((argc - optind) & 1)
			fprintf (stderr, "Unbalanced pagerid/message\n");
		usage (argv[0]);
		return 1;
	}
	if (verbose < 0)
		verbose = cfg_iget (cfg, NULL, CFG_VERBOSE, 0);
	if (! logfile)
		logfile = cfg_get (cfg, NULL, CFG_LOGFILE, NULL);
	if (! logstr)
		logstr = cfg_get (cfg, NULL, CFG_LOGSTR, NULL);
	if (! (svcs = cfg_get (cfg, NULL, CFG_SERVICES, NULL))) {
		fprintf (stderr, "No services definied\n");
		return 1;
	}
	if (! (svcs = strdup (svcs))) {
		OOM;
		return 1;
	}
	sbase = NULL;
	sprev = NULL;
	for (ptr = svcs; *ptr; ) {
		sav = ptr;
		ptr = skipch (ptr, ',');
		if (! (chk = cfg_block_get (cfg, sav, CFG_VALPID, NULL))) {
			fprintf (stderr, "Service %s has no pattern for matching pager ids\n", sav);
			return 1;
		}
		if ((stmp = (serv *) malloc (sizeof (serv))) &&
		    (stmp -> name = strdup (sav))) {
			if (! (stmp -> pchk = v_new (chk))) {
				fprintf (stderr, "Pattern %s in service %s is not valid\n", chk, sav);
				return 1;
			}
			stmp -> cchk = NULL;
			if (chk = cfg_get (cfg, sav, CFG_VALCID, NULL))
				if (! (stmp -> cchk = v_new (chk))) {
					fprintf (stderr, "Pattern %s in service %s not valid\n", chk, sav);
					return 1;
				}
			stmp -> next = NULL;
			if (sprev)
				sprev -> next = stmp;
			else
				sbase = stmp;
			sprev = stmp;
		} else {
			OOM;
			return 1;
		}
	}
	if (! sbase) {
		fprintf (stderr, "No services found\n");
		return 1;
	}
	if (! (mg = create_messages (cfg, service, sbase, argv + optind, argc - optind, mfile, & mcnt, callid, sig, trunc, force, delay, expire, rds)))
		return 1;
	while (sbase) {
		stmp = sbase;
		sbase = sbase -> next;
		free (stmp -> name);
		v_free (stmp -> pchk);
		if (stmp -> cchk)
			v_free (stmp -> cchk);
		free (stmp);
	}
	jumpdone = False;
	if (! (ret = setjmp (termenv))) {
		dojump = False;
		for (n = 0; n < mcnt; )
			n = sendit (cfg, n, mg, mcnt, & ret, delay, expire, rds);
	}
	if (mg) {
		if (! freport)
			freport = cfg_get (cfg, NULL, CFG_FREPORT, NULL);
		fp = NULL;
		if (freport)
			if (! strcmp (freport, "-"))
				fp = stdout;
			else if (! (fp = fopen (freport, "w"))) {
				fprintf (stderr, "Unable to open %s for final report, sending it to stdout\n", freport);
				fp = stdout;
			}
		if (fp && (mcnt > 1))
			qsort (mg, mcnt, sizeof (message), ncompare);
		for (m = 0, onr = -1; m < mcnt; ++m) {
			if (fp && (onr != mg[m].nr)) {
				onr = mg[m].nr;
				succ = True;
				for (n = m; (n < mcnt) && (mg[n].nr == onr); ++n)
					if (! (mg[n].st.success)) {
						succ = False;
						break;
					}
				ptr = NULL;
				if (! succ) {
					switch (mg[n].st.reason) {
					case NotTried:
						ptr = "unable to connect";
						break;
					case CantDial:
						ptr = "could not dial service provider";
						break;
					case NotSend:
						ptr = "unable to send";
						break;
					case CantSend:
						ptr = "could not transmit the message";
						break;
					}
				}
				fprintf (fp, "%c%d %s: %s %s\n", 
					 (succ ? '+' : '-'), mg[m].nr,
					 (mg[m].alias ? mg[m].alias : (mg[m].pid ? mg[m].pid : "")),
					 (mg[m].st.rmsg ? mg[m].st.rmsg : ""),
					 (ptr ? ptr : ""));
			}
			if (mg[m].callid)
				free (mg[m].callid);
			if (mg[m].alias)
				free (mg[m].alias);
			if (mg[m].pid)
				free (mg[m].pid);
			if (mg[m].service)
				free (mg[m].service);
			if (mg[m].msg)
				sfree (mg[m].msg);
			if (mg[m].st.rmsg)
				free (mg[m].st.rmsg);
		}
		free (mg);
		if (fp && (fp != stdout))
			fclose (fp);
	}
	cfg_end (cfg);
	return ret;
}
/*}}}*/

