1 /*	$Id$ */
2 /*
3  * Copyright (c) 1990-1996 Sam Leffler
4  * Copyright (c) 1991-1996 Silicon Graphics, Inc.
5  * HylaFAX is a trademark of Silicon Graphics
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and
8  * its documentation for any purpose is hereby granted without fee, provided
9  * that (i) the above copyright notices and this permission notice appear in
10  * all copies of the software and related documentation, and (ii) the names of
11  * Sam Leffler and Silicon Graphics may not be used in any advertising or
12  * publicity relating to the software without the specific, prior written
13  * permission of Sam Leffler and Silicon Graphics.
14  *
15  * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
17  * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
18  *
19  * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
20  * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
21  * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
22  * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
23  * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
24  * OF THIS SOFTWARE.
25  */
26 #include "config.h"
27 #include "Sys.h"
28 #include "FaxClient.h"
29 #include "Transport.h"
30 #include "zlib.h"
31 #include <pwd.h>
32 #include <ctype.h>
33 #include <sys/types.h>
34 #include <arpa/telnet.h>
35 #include <errno.h>
36 #if HAS_MMAP
37 #include <sys/mman.h>
38 #endif
39 
40 #include "NLS.h"
41 
42 #define	N(a)	(sizeof (a) / sizeof (a[0]))
43 
FaxClient()44 FaxClient::FaxClient()
45 {
46     init();
47 }
48 
FaxClient(const fxStr & hostarg)49 FaxClient::FaxClient(const fxStr& hostarg)
50 {
51     init();
52     setupHostModem(hostarg);
53 }
54 
FaxClient(const char * hostarg)55 FaxClient::FaxClient(const char* hostarg)
56 {
57     init();
58     setupHostModem(hostarg);
59 }
60 
61 void
init()62 FaxClient::init()
63 {
64     transport = NULL;
65     fdIn = NULL;
66     fdOut = NULL;
67     fdData = -1;
68     state = 0;
69     pasv = false;
70 
71     setupConfig();
72 }
73 
74 void
initServerState(void)75 FaxClient::initServerState(void)
76 {
77     type = TYPE_A;
78     mode = MODE_S;
79     stru = STRU_F;
80     format = FORM_UNKNOWN;
81     curjob = "DEFAULT";
82     tzone = TZ_GMT;
83     jobFmt = "";
84     recvFmt = "";
85     state &= ~(FS_TZPEND|FS_JFMTPEND|FS_RFMTPEND|FS_MFMTPEND|FS_FFMTPEND);
86 }
87 
~FaxClient()88 FaxClient::~FaxClient()
89 {
90     (void) hangupServer();
91 }
92 
93 void
printError(const char * fmt...)94 FaxClient::printError(const char* fmt ...)
95 {
96     va_list ap;
97     va_start(ap, fmt);
98     vprintError(fmt, ap);
99     va_end(ap);
100 }
101 void
vprintError(const char * fmt,va_list ap)102 FaxClient::vprintError(const char* fmt, va_list ap)
103 {
104     vfprintf(stderr, fmt, ap);
105     fputs("\n", stderr);
106 }
107 
108 void
printWarning(const char * fmt...)109 FaxClient::printWarning(const char* fmt ...)
110 {
111     va_list ap;
112     va_start(ap, fmt);
113     vprintWarning(fmt, ap);
114     va_end(ap);
115 }
116 void
vprintWarning(const char * fmt,va_list ap)117 FaxClient::vprintWarning(const char* fmt, va_list ap)
118 {
119     fprintf(stderr, NLS::TEXT("Warning, "));
120     vfprintf(stderr, fmt, ap);
121     fputs("\n", stderr);
122 }
123 
124 void
traceServer(const char * fmt...)125 FaxClient::traceServer(const char* fmt ...)
126 {
127     va_list ap;
128     va_start(ap, fmt);
129     vtraceServer(fmt, ap);
130     va_end(ap);
131 }
132 void
vtraceServer(const char * fmt,va_list ap)133 FaxClient::vtraceServer(const char* fmt, va_list ap)
134 {
135     vfprintf(stdout, fmt, ap);
136     fputs("\n", stdout);
137 }
138 
139 /*
140  * Host, port, and modem can be specified using
141  *
142  *     modem@host:port
143  *
144  * e.g. ttyf2@flake.asd:9999.  Alternate forms
145  * are: modem@, modem@host, host, host:port.
146  * IPv6 IP addresses (many :) are supported in [xx:xx::x]:port
147  */
148 void
setupHostModem(const fxStr & s)149 FaxClient::setupHostModem(const fxStr& s)
150 {
151     u_int pos = s.next(0, '@');
152     if (pos != s.length()) {
153 	modem = s.head(pos);
154 	host = s.tail(s.length() - (pos+1));
155     } else
156 	host = s;
157 
158     if (host.length() && host[0] == '[')
159     {
160 	host.remove(0,1);
161 	pos = host.next(0,']');
162 	if (pos == host.length())
163 	    printWarning(NLS::TEXT("Couldn't parse IPv6 ip address string: \"%s\""), (const char*)s);
164 	else
165 	host.remove(pos,1);
166 	    pos = host.next(pos, ':');
167     } else
168 	pos = host.next(0, ':');
169     if (pos != host.length()) {
170 	port = atoi(host.tail(host.length() - (pos+1)));
171 	host.resize(pos);
172     }
173 }
setupHostModem(const char * cp)174 void FaxClient::setupHostModem(const char* cp) { setupHostModem(fxStr(cp)); }
175 
setHost(const fxStr & hostarg)176 void FaxClient::setHost(const fxStr& hostarg)	{ setupHostModem(hostarg); }
setHost(const char * hostarg)177 void FaxClient::setHost(const char* hostarg)	{ setupHostModem(hostarg); }
setPort(int p)178 void FaxClient::setPort(int p)			{ port = p; }
setProtoName(const char * s)179 void FaxClient::setProtoName(const char* s)	{ proto = s; }
180 
setModem(const fxStr & modemarg)181 void FaxClient::setModem(const fxStr& modemarg)	{ modem = modemarg; }
setModem(const char * modemarg)182 void FaxClient::setModem(const char* modemarg)	{ modem = modemarg; }
183 
184 void
setVerbose(bool v)185 FaxClient::setVerbose(bool v)
186 {
187     if (v)
188 	state |= FS_VERBOSE;
189     else
190 	state &= ~FS_VERBOSE;
191 }
192 
193 bool
setupUserIdentity(fxStr & emsg)194 FaxClient::setupUserIdentity(fxStr& emsg)
195 {
196     struct passwd* pwd = NULL;
197     const char* name = getenv("FAXUSER");
198     if (name)
199 	pwd = getpwnam(name);
200     else
201 	pwd = getpwuid(getuid());
202     if (!pwd) {
203 	if (name)
204 	    userName = name;
205 	else {
206 	    emsg = fxStr::format(NLS::TEXT("Can not locate your password entry "
207 		"(uid %lu): %s"), (u_long) getuid(), strerror(errno));
208 	    return (false);
209 	}
210     }
211     else
212 	userName = pwd->pw_name;
213     if (pwd && pwd->pw_gecos && pwd->pw_gecos[0] != '\0') {
214 	senderName = pwd->pw_gecos;
215 	senderName.resize(senderName.next(0, '('));	// strip SysV junk
216 	u_int l = senderName.next(0, '&');
217 	if (l < senderName.length()) {
218 	    /*
219 	     * Do the '&' substitution and raise the
220 	     * case of the first letter of the inserted
221 	     * string (the usual convention...)
222 	     */
223 	    senderName.remove(l);
224 	    senderName.insert(userName, l);
225 	    if (islower(senderName[l]))
226 		senderName[l] = toupper(senderName[l]);
227 	}
228 	senderName.resize(senderName.next(0,','));
229     } else
230 	senderName = userName;
231     if (senderName.length() == 0) {
232 	emsg = NLS::TEXT("Bad (null) user name; your password file entry"
233 	    " probably has bogus GECOS field information.");
234 	return (false);
235     } else
236 	return (true);
237 }
238 
239 /*
240  * Configuration file support.
241  */
242 
243 FaxClient::F_stringtag FaxClient::strings[] = {
244 { "protocol",			&FaxClient::proto,		FAX_PROTONAME },
245 { "host",			&FaxClient::host,		NULL },
246 { "modem",			&FaxClient::modem,		NULL },
247 { "jobsortfmt", 		&FaxClient::jobSFmt },
248 { "rcvsortfmt", 		&FaxClient::recvSFmt },
249 { "modemsortfmt", 		&FaxClient::modemSFmt },
250 { "filesortfmt", 		&FaxClient::fileSFmt },
251 };
252 FaxClient::F_numbertag FaxClient::numbers[] = {
253 { "port",			&FaxClient::port,		(u_int) -1 },
254 };
255 
256 void
setupConfig()257 FaxClient::setupConfig()
258 {
259     int i;
260 
261     for (i = N(strings)-1; i >= 0; i--)
262 	(*this).*strings[i].p = (strings[i].def ? strings[i].def : "");
263     for (i = N(numbers)-1; i >= 0; i--)
264 	(*this).*numbers[i].p = numbers[i].def;
265     initServerState();
266 }
267 
268 void
resetConfig()269 FaxClient::resetConfig()
270 {
271     setupConfig();
272 }
273 
274 void
configError(const char * fmt...)275 FaxClient::configError(const char* fmt ...)
276 {
277     va_list ap;
278     va_start(ap, fmt);
279     vprintError(fmt, ap);
280     va_end(ap);
281 }
282 
283 void
configTrace(const char * fmt...)284 FaxClient::configTrace(const char* fmt ...)
285 {
286     if (getVerbose()) {
287 	va_list ap;
288 	va_start(ap, fmt);
289 	vprintWarning(fmt, ap);
290 	va_end(ap);
291     }
292 }
293 
294 bool
setConfigItem(const char * tag,const char * value)295 FaxClient::setConfigItem(const char* tag, const char* value)
296 {
297     u_int ix;
298     if (findTag(tag, (const tags*)strings, N(strings), ix)) {
299 	(*this).*strings[ix].p = value;
300     } else if (findTag(tag, (const tags*)numbers, N(numbers), ix)) {
301 	(*this).*numbers[ix].p = atoi(value);
302     } else if (streq(tag, "verbose")) {
303 	if (getBoolean(value))
304 	    state |= FS_VERBOSE;
305 	else
306 	    state &= ~FS_VERBOSE;
307     } else if (streq(tag, "timezone") || streq(tag, "tzone")) {
308 	setTimeZone(streq(value, "local") ? TZ_LOCAL : TZ_GMT);
309     } else if (streq(tag, "jobfmt")) {
310 	setJobStatusFormat(value);
311     } else if (streq(tag, "rcvfmt")) {
312 	setRecvStatusFormat(value);
313     } else if (streq(tag, "modemfmt")) {
314 	setModemStatusFormat(value);
315     } else if (streq(tag, "filefmt")) {
316 	setFileStatusFormat(value);
317     } else if (streq(tag, "passivemode")) {
318 	pasv = getBoolean(value);
319     } else
320 	return (false);
321     return (true);
322 }
323 
324 bool
callServer(fxStr & emsg)325 FaxClient::callServer(fxStr& emsg)
326 {
327     if (host.length() == 0) {		// if host not specified by -h
328 	const char* cp = getenv("FAXSERVER");
329 	if (cp && *cp != '\0') {
330 	    if (modem != "") {		// don't clobber specified modem
331 		fxStr m(modem);
332 		setupHostModem(cp);
333 		modem = m;
334 	    } else
335 		setupHostModem(cp);
336 	}
337     }
338     transport = &Transport::getTransport(*this, host);
339     if (transport->callServer(emsg)) {
340 	signal(SIGPIPE, fxSIGHANDLER(SIG_IGN));
341 	/*
342 	 * Transport code is expected to call back through
343 	 * setCtrlFds so fdIn should be properly setup...
344 	 */
345 	if (fdIn == NULL)
346 	    return (false);
347 	int rep = PRELIM;
348 	for (int i = 0; rep == PRELIM && i < 100; i++)
349 	    rep = getReply(false);
350 	return (rep == COMPLETE);
351     } else
352 	return (false);
353 }
354 
355 bool
hangupServer(void)356 FaxClient::hangupServer(void)
357 {
358     if (fdIn != NULL) {
359 	if (transport) {
360 	    closeDataConn();
361 	    (void) transport->hangupServer();
362 	}
363 	fclose(fdIn), fdIn = NULL;
364     }
365     if (fdOut != NULL)
366 	fclose(fdOut), fdOut = NULL;
367     /*
368      * Reset state in case another call is placed.
369      */
370     delete transport, transport = NULL;
371     initServerState();
372     return (true);
373 }
374 
375 void
setCtrlFds(int in,int out)376 FaxClient::setCtrlFds(int in, int out)
377 {
378     if (fdIn != NULL)
379 	fclose(fdIn);
380     fdIn = fdopen(in, "r");
381     if (fdOut != NULL)
382 	fclose(fdOut);
383     fdOut = fdopen(out, "w");
384 }
385 
386 void
setDataFd(int fd)387 FaxClient::setDataFd(int fd)
388 {
389     if (fdData >= 0)
390 	Sys::close(fdData);
391     fdData = fd;
392 }
393 
394 /*
395  * Do login procedure.
396  */
397 bool
login(const char * user,fxStr & emsg)398 FaxClient::login(const char* user, fxStr& emsg)
399 {
400     if (user == NULL) {
401 	setupUserIdentity(emsg);
402 	user = userName;
403     }
404     if (*user == '\0') {
405 	emsg = NLS::TEXT("Malformed (null) username");
406 	return (false);
407     }
408     int n = command("USER %s", user);
409     if (n == CONTINUE)
410 	n = command("PASS %s", getPasswd("Password:"));
411     if (n == CONTINUE)				// XXX not used
412 	n = command("ACCT %s", getPasswd("Account:"));
413     if (n == COMPLETE)
414 	state |= FS_LOGGEDIN;
415     else
416 	state &= ~FS_LOGGEDIN;
417     if (isLoggedIn()) {
418 	if (state&FS_TZPEND) {
419 	    u_int tz = tzone;
420 	    tzone = 0;
421 	    (void) setTimeZone(tz);
422 	    state &= ~FS_TZPEND;
423 	}
424 	return (true);
425     } else {
426 	emsg = NLS::TEXT("Login failed: ") | lastResponse;
427 	return (false);
428     }
429 }
430 
431 /*
432  * Prompt for a password.
433  */
434 const char*
getPasswd(const char * prompt)435 FaxClient::getPasswd(const char* prompt)
436 {
437     return (getpass(prompt));
438 }
439 
440 /*
441  * Do admin login procedure.
442  */
443 bool
admin(const char * pass,fxStr & emsg)444 FaxClient::admin(const char* pass, fxStr& emsg)
445 {
446     if (command("ADMIN %s", pass ? pass : getpass("Password:")) != COMPLETE) {
447 	emsg = NLS::TEXT("Admin failed: ") | lastResponse;
448 	return (false);
449     } else
450 	return (true);
451 }
452 
453 bool
setCommon(FaxParam & parm,u_int v)454 FaxClient::setCommon(FaxParam& parm, u_int v)
455 {
456     if (v != this->*parm.pv) {
457 	if (0 < v && v < parm.NparmNames) {
458 	    if (command("%s %s", parm.cmd, parm.parmNames[v]) != COMPLETE) {
459 		printError("%s", (const char*) lastResponse);
460 		return (false);
461 	    }
462 	} else {
463 	    printError(NLS::TEXT("Bad %s parameter value %u."), parm.cmd, v);
464 	    return (false);
465 	}
466 	this->*parm.pv = v;
467     }
468     return (true);
469 }
470 
471 static const char* typeNames[] = { "", "A", "E", "I", "L" };
472 FaxClient::FaxParam FaxClient::typeParam =
473     { "TYPE", typeNames, N(typeNames), &FaxClient::type };
setType(u_int v)474 bool FaxClient::setType(u_int v)	{ return setCommon(typeParam, v); }
475 
476 static const char* modeNames[] = { "", "S", "B", "C", "Z" };
477 FaxClient::FaxParam FaxClient::modeParam =
478     { "MODE", modeNames, N(modeNames), &FaxClient::mode };
setMode(u_int v)479 bool FaxClient::setMode(u_int v)	{ return setCommon(modeParam, v); }
480 
481 static const char* struNames[] = { "", "F", "R", "P", "T" };
482 FaxClient::FaxParam FaxClient::struParam =
483     { "STRU", struNames, N(struNames), &FaxClient::stru };
setStruct(u_int v)484 bool FaxClient::setStruct(u_int v)	{ return setCommon(struParam, v); }
485 
486 static const char* formNames[] = { "", "PS", "PS2", "TIFF", "PCL", "PDF" };
487 FaxClient::FaxParam FaxClient::formParam =
488     { "FORM", formNames, N(formNames), &FaxClient::format };
setFormat(u_int v)489 bool FaxClient::setFormat(u_int v)	{ return setCommon(formParam, v); }
490 
491 static const char* tzoneNames[] = { "", "GMT", "LOCAL" };
492 FaxClient::FaxParam FaxClient::tzoneParam =
493     { "TZONE", tzoneNames, N(tzoneNames), &FaxClient::tzone };
494 bool
setTimeZone(u_int v)495 FaxClient::setTimeZone(u_int v)
496 {
497     if (!isLoggedIn()) {		// set and mark pending accordingly
498         if (0 < v && v < N(tzoneNames)) {
499             tzone = v;
500             if (v == TZ_GMT) state &= ~FS_TZPEND;
501             else state |= FS_TZPEND;
502         } else {
503             printError(NLS::TEXT("Bad time zone parameter value %u."), v);
504             return (false);
505         }
506         return (true);
507     } else {				// pass directly to server
508         return setCommon(tzoneParam, v);
509     }
510 }
511 
512 /*
513  * Data connection support.
514  *
515  * Separate connections are used for data transfers.
516  * The transport classes handle the work since it is
517  * inherently transport-specific.  Connections are
518  * setup in a 2-step process because of the need (in
519  * the TCP world) to setup a listening socket prior to
520  * issuing a server command that causes the data connection
521  * to be established.  Thus the expected protocol is to
522  * initialize (initDataConn), issue a server command,
523  * then open (openDataConn); after which data can be
524  * transfered over the connection.  When completed the
525  * connection should be closed (closeDataConn).
526  *
527  * Outbound connections can be terminated simply by
528  * closing the data connection.  Inbound connections
529  * must be aborted with ABOR command that is sent in
530  * a transport-specific way (e.g. for TCP the message
531  * is sent as urgent data).  The abortDataConn interface
532  * is provided for this use.
533  */
534 
535 bool
initDataConn(fxStr & emsg)536 FaxClient::initDataConn(fxStr& emsg)
537 {
538     closeDataConn();
539     if (transport) {
540         if (!transport->initDataConn(emsg)) {
541             if (emsg == "") {
542                 emsg = NLS::TEXT("Unable to initialize data connection to server");
543             }
544             return (false);
545         }
546     }
547     return (true);
548 }
549 
550 bool
openDataConn(fxStr & emsg)551 FaxClient::openDataConn(fxStr& emsg)
552 {
553     if (transport) {
554         if (!transport->openDataConn(emsg)) {
555             if (emsg == "") {
556 		emsg = NLS::TEXT("Unable to open data connection to server");
557             }
558 	        return (false);
559         }
560     }
561     return (true);
562 }
563 
564 void
closeDataConn(void)565 FaxClient::closeDataConn(void)
566 {
567     if (fdData >= 0) {
568         transport->closeDataConn(fdData);
569         fdData = -1;
570     }
571 }
572 
573 bool
abortDataConn(fxStr & emsg)574 FaxClient::abortDataConn(fxStr& emsg)
575 {
576     if (fdData >= 0 && transport) {
577         fflush(fdOut);
578         if (!transport->abortCmd(emsg)) {
579             if (emsg == "") {
580 		emsg = NLS::TEXT("Unable to abort data connection to server");
581             }
582 	        return (false);
583         }
584 #ifdef notdef
585         /*
586          * Flush data from data connection.
587          */
588         int flags = fcntl(fdData, F_GETFL, 0);
589         fcntl(fdData, F_SETFL, flags | FNONBLK);
590         while (Sys::read(fdData, buf, sizeof (buf)) > 0);
591         fcntl(fdData, F_SETFL, flags);
592 #endif
593         /*
594          * Server should send a reply that acknowledges the
595          * existing operation is aborted followed by an ack
596          * of the ABOR command itself.
597          */
598         if (getReply(false) != TRANSIENT ||	// 4xx operation aborted
599                 getReply(false) != COMPLETE) {	// 2xx abort successful
600             unexpectedResponse(emsg);
601             return (false);
602         }
603     }
604     return (true);
605 }
606 
607 void
lostServer(void)608 FaxClient::lostServer(void)
609 {
610     printError(NLS::TEXT("Service not available, remote server closed connection"));
611     hangupServer();
612 }
613 
614 void
unexpectedResponse(fxStr & emsg)615 FaxClient::unexpectedResponse(fxStr& emsg)
616 {
617     emsg = NLS::TEXT("Unexpected server response: ") | lastResponse;
618 }
619 
620 void
protocolBotch(fxStr & emsg,const char * fmt...)621 FaxClient::protocolBotch(fxStr& emsg, const char* fmt ...)
622 {
623     va_list ap;
624     va_start(ap, fmt);
625     emsg = NLS::TEXT("Protocol botch") | fxStr::vformat(fmt, ap);
626     va_end(ap);
627 }
628 
629 /*
630  * Send a command and wait for a response.
631  * The primary response code is returned.
632  */
633 int
command(const char * fmt...)634 FaxClient::command(const char* fmt ...)
635 {
636     va_list ap;
637     va_start(ap, fmt);
638     int r = vcommand(fmt, ap);
639     va_end(ap);
640     return (r);
641 }
642 
643 /*
644  * Send a command and wait for a response.
645  * The primary response code is returned.
646  */
647 int
vcommand(const char * fmt,va_list ap)648 FaxClient::vcommand(const char* fmt, va_list ap)
649 {
650     fxStr line = fxStr::vformat(fmt, ap);
651 
652     if (getVerbose()) {
653         if (strncasecmp("PASS ", fmt, 5) == 0) {
654             traceServer("-> PASS XXXX");
655         } else if (strncasecmp("ADMIN ", fmt, 6) == 0) {
656             traceServer("-> ADMIN XXXX");
657         } else {
658 	    traceServer("-> %s", (const char*)line);
659         }
660     }
661     if (fdOut == NULL) {
662         printError(NLS::TEXT("No control connection for command"));
663         code = -1;
664         return (0);
665     }
666     fputs(line, fdOut);
667     fputs("\r\n", fdOut);
668     (void) fflush(fdOut);
669     return (getReply(strncmp(fmt, "QUIT", 4) == 0));
670 }
671 
672 /*
673  * Extract a valid reply code from a string.
674  * The code must be 3 numeric digits followed
675  * by a space or ``-'' (the latter indicates
676  * the server reponse is to be continued with
677  * one or more lines).  If no valid reply code
678  * is recognized zero is returned--this is
679  * assumed to be an invalid reply code.
680  */
681 static int
getReplyCode(const char * cp)682 getReplyCode(const char* cp)
683 {
684     if (!isdigit(cp[0])) return (0);
685     int c = (cp[0] - '0');
686     if (!isdigit(cp[1])) return (0);
687     c = 10 * c + (cp[1] - '0');
688     if (!isdigit(cp[2])) return (0);
689     c = 10 * c + (cp[2] - '0');
690     return ((cp[3] == ' ' || cp[3] == '-') ? c : 0);
691 }
692 
693 /*
694  * Read from the control channel until a valid reply is
695  * received or the connection is dropped.  The last line
696  * of the received response is left in FaxClient::lastResponse
697  * and the reply code is left in FaxClient::code.  The
698  * primary response (the first digit of the reply code)
699  * is returned to the caller.  Continuation lines are
700  * collected separately.
701  */
702 int
getReply(bool expecteof)703 FaxClient::getReply(bool expecteof)
704 {
705     int firstCode = 0;
706     bool continuation = false;
707     lastContinuation.resize(0);
708     do {
709         lastResponse.resize(0);
710         int c;
711         while ((c = getc(fdIn)) != '\n') {
712             if (c == IAC) {     // handle telnet commands
713 		switch (c = getc(fdIn)) {
714 		case WILL:
715                 case WONT:
716 		    c = getc(fdIn);
717 		    fprintf(fdOut, "%c%c%c", IAC, DONT, c);
718                     (void) fflush(fdOut);
719                     break;
720                 case DO:
721                 case DONT:
722                     c = getc(fdIn);
723                     fprintf(fdOut, "%c%c%c", IAC, WONT, c);
724                     (void) fflush(fdOut);
725                     break;
726                 default:
727                     break;
728                 }
729                 continue;
730             }
731             if (c == EOF) {
732 		if (expecteof) {
733                     code = 221;
734                     return (0);
735                 } else {
736                     lostServer();
737                     code = 421;
738                     return (4);
739                 }
740             }
741             if (c != '\r') lastResponse.append(c);
742         }
743         if (getVerbose()) {
744             traceServer("%s", (const char*) lastResponse);
745         }
746         code = getReplyCode(lastResponse);
747         if (code != 0) {			// found valid reply code
748             if (lastResponse[3] == '-') {	// continuation line
749                 if (firstCode == 0)		// first line of reponse
750                     firstCode = code;
751                 continuation = true;
752             } else if (code == firstCode)	// end of continued reply
753                 continuation = false;
754         }
755 	if (continuation) {
756 	    lastContinuation.append(&lastResponse[4]);
757 	    lastContinuation.append("\n");
758 	}
759 
760     } while (continuation || code == 0);
761 
762     if (code == 421) { // server closed connection
763         lostServer();
764     }
765     return code / 100;
766 }
767 
768 /*
769  * Extract a string from a reply message.  The
770  * string that is extracted is expected to follow
771  * a pattern string.  The pattern is tried both
772  * in the initial case and then the inverse case
773  * (upper or lower depending on what the original
774  * case was).  The resulting string is checked to
775  * make sure that it is not null.
776  */
777 bool
extract(u_int & pos,const char * pattern,fxStr & result,const char * cmd,fxStr & emsg)778 FaxClient::extract(u_int& pos, const char* pattern, fxStr& result,
779     const char* cmd, fxStr& emsg)
780 {
781     fxStr pat(pattern);
782     u_int l = lastResponse.find(pos, pat);
783     if (l == lastResponse.length()) {		// try inverse-case version
784         if (isupper(pattern[0])) {
785             pat.lowercase();
786         } else {
787             pat.raisecase();
788         }
789         l = lastResponse.find(pos, pat);
790     }
791     if (l == lastResponse.length()) {
792         protocolBotch(emsg, NLS::TEXT(": No \"%s\" in %s response: %s"),
793             pattern, cmd, (const char*) lastResponse);
794         return false;
795     }
796     l = lastResponse.skip(l+pat.length(), ' ');// skip white space
797     result = lastResponse.extract(l, lastResponse.next(l, ' ')-l);
798     if (result == "") {
799         protocolBotch(emsg, NLS::TEXT(": Null %s in %s response: %s"),
800             pattern, cmd, (const char*) lastResponse);
801         return false;
802     }
803     pos = l;					// update position
804     return true;
805 }
806 
807 /*
808  * Create a new job and return its job-id
809  * and group-id, parsed from the reply.
810  */
811 bool
newJob(fxStr & jobid,fxStr & groupid,fxStr & emsg)812 FaxClient::newJob(fxStr& jobid, fxStr& groupid, fxStr& emsg)
813 {
814     if (command("JNEW") == COMPLETE) {
815         if (code == 200) {
816             /*
817              * The response should be of the form:
818              *
819              * 200 ... jobid: xxxx groupid: yyyy.
820              *
821              * where xxxx is the ID for the new job and yyyy is the
822              * ID of the new job's group.
823              */
824             u_int l = 0;
825             if (extract(l, "jobid:", jobid, "JNEW", emsg) &&
826                     extract(l, "groupid:", groupid, "JNEW", emsg)) {
827                 /*
828                  * Force job and groupd IDs to be numeric;
829                  * this deals with servers that want to append
830                  * punctuation such as ``,'' or ``.''.
831                  */
832                 jobid.resize(jobid.skip(0, "0123456789"));
833                 groupid.resize(groupid.skip(0, "0123456789"));
834                 curjob = jobid;
835                 return true;
836             }
837         } else {
838             unexpectedResponse(emsg);
839         }
840     } else {
841         emsg = lastResponse;
842     }
843     return false;
844 }
845 
846 /*
847  * Set the current job on the server.
848  */
849 bool
setCurrentJob(const char * jobid)850 FaxClient::setCurrentJob(const char* jobid)
851 {
852     if (strcasecmp(jobid, curjob) != 0) {
853         if (command("JOB %s", jobid) != COMPLETE) {
854             return false;
855         }
856         curjob = jobid;
857     }
858     return true;
859 }
860 
861 bool
jobParm(const char * name,const fxStr & value)862 FaxClient::jobParm(const char* name, const fxStr& value)
863 {
864     /*
865      * We need to quote any " marks in the string before
866      * we pass it on to the raw jobParm(... const char*)
867      */
868     if (value.next(0,'"'))
869     {
870 	fxStr tmp(value);
871 	int r = tmp.length();
872 	while (r > 0)
873 	{
874 	    if ( (r = tmp.nextR(r-1, '"') ) > 0 )
875 		tmp.insert('\\', r-1);
876 	}
877 	return jobParm(name, (const char*)tmp);
878     }
879     return jobParm(name, (const char*) value);
880 }
881 
882 bool
jobParm(const char * name,const char * value)883 FaxClient::jobParm(const char* name, const char* value)
884 {
885     /*
886      * if they're passing us a wrong char*, we expect
887      * them to have handled any quoting requried.
888      */
889     return (command("JPARM %s \"%s\"", name, value) == COMPLETE);
890 }
891 
892 bool
jobParm(const char * name,bool b)893 FaxClient::jobParm(const char* name, bool b)
894 {
895     return (command("JPARM %s %s", name, b ? "YES" : "NO") == COMPLETE);
896 }
897 
898 bool
jobParm(const char * name,u_int v)899 FaxClient::jobParm(const char* name, u_int v)
900 {
901     return (command("JPARM %s %u", name, v) == COMPLETE);
902 }
903 
904 bool
jobParm(const char * name,float v)905 FaxClient::jobParm(const char* name, float v)
906 {
907     return (command("JPARM %s %g", name, v) == COMPLETE);
908 }
909 
910 bool
jobSendTime(const struct tm tm)911 FaxClient::jobSendTime(const struct tm tm)
912 {
913     return (command("JPARM SENDTIME %d%02d%02d%02d%02d"
914         , tm.tm_year+1900
915         , tm.tm_mon+1
916         , tm.tm_mday
917         , tm.tm_hour
918         , tm.tm_min
919         ) == COMPLETE);
920 }
921 
922 bool
jobLastTime(u_long tv)923 FaxClient::jobLastTime(u_long tv)
924 {
925     return (command("JPARM LASTTIME %02d%02d%02d",
926         tv/(24*60*60), (tv/(60*60))%24, (tv/60)%60) == COMPLETE);
927 }
928 
929 bool
jobRetryTime(u_long tv)930 FaxClient::jobRetryTime(u_long tv)
931 {
932     return (command("JPARM RETRYTIME %02d%02d", tv/60, tv%60) == COMPLETE);
933 }
934 
935 bool
jobCover(const char * docname)936 FaxClient::jobCover(const char* docname)
937 {
938     return (command("JPARM COVER %s", docname) == COMPLETE);
939 }
940 
941 bool
jobDocument(const char * docname)942 FaxClient::jobDocument(const char* docname)
943 {
944     return (command("JPARM DOCUMENT %s", docname) == COMPLETE);
945 }
946 
947 bool
jobPollRequest(const char * sep,const char * pwd)948 FaxClient::jobPollRequest(const char* sep, const char* pwd)
949 {
950     return (command("JPARM POLL \"%s\" \"%s\"", sep, pwd) == COMPLETE);
951 }
952 
953 bool
jobOp(const char * op,const char * jobid)954 FaxClient::jobOp(const char* op, const char* jobid)
955 {
956     return (command(jobid == curjob ? "%s" : "%s %s", op, jobid) == COMPLETE);
957 }
jobSubmit(const char * jobid)958 bool FaxClient::jobSubmit(const char* jobid)	{ return jobOp("JSUBM",jobid); }
jobSuspend(const char * jobid)959 bool FaxClient::jobSuspend(const char* jobid)	{ return jobOp("JSUSP",jobid); }
jobKill(const char * jobid)960 bool FaxClient::jobKill(const char* jobid)	{ return jobOp("JKILL",jobid); }
jobDelete(const char * jobid)961 bool FaxClient::jobDelete(const char* jobid)	{ return jobOp("JDELE",jobid); }
jobWait(const char * jobid)962 bool FaxClient::jobWait(const char* jobid)	{ return jobOp("JWAIT",jobid); }
963 
jgrpSubmit(const char * jgrpid)964 bool FaxClient::jgrpSubmit(const char* jgrpid)
965     { return (command("JGSUBM %s", jgrpid) == COMPLETE); }
jgrpSuspend(const char * jgrpid)966 bool FaxClient::jgrpSuspend(const char* jgrpid)
967     { return (command("JGSUSP %s", jgrpid) == COMPLETE); }
jgrpKill(const char * jgrpid)968 bool FaxClient::jgrpKill(const char* jgrpid)
969     { return (command("JGKILL %s", jgrpid) == COMPLETE); }
jgrpWait(const char * jgrpid)970 bool FaxClient::jgrpWait(const char* jgrpid)
971     { return (command("JGWAIT %s", jgrpid) == COMPLETE); }
972 
973 bool
runScript(const char * filename,fxStr & emsg)974 FaxClient::runScript(const char* filename, fxStr& emsg)
975 {
976     bool ok = false;
977     FILE* fd = fopen(filename, "r");
978     if (fd != NULL) {
979 	ok = runScript(fd, filename, emsg);
980 	fclose(fd);
981     } else
982 	emsg = fxStr::format(NLS::TEXT("Unable to open script file \"%s\"."), filename);
983     return (ok);
984 }
985 
986 bool
runScript(FILE * fp,const char * filename,fxStr & emsg)987 FaxClient::runScript(FILE* fp, const char* filename, fxStr& emsg)
988 {
989     bool ok = false;
990     struct stat sb;
991     (void) Sys::fstat(fileno(fp), sb);
992     char* addr;
993 #if HAS_MMAP
994     addr = (char*) mmap(NULL, (size_t) sb.st_size, PROT_READ, MAP_SHARED, fileno(fp), 0);
995     if (addr == (char*) -1) {		// revert to file reads
996 #endif
997 	addr = new char[sb.st_size];
998 	if (Sys::read(fileno(fp), addr, (u_int) sb.st_size) == sb.st_size)
999 	    ok = runScript(addr, sb.st_size, filename, emsg);
1000 	else
1001 	    emsg = fxStr::format(NLS::TEXT("%s: Read error: %s"),
1002 		filename, strerror(errno));
1003 	delete [] addr;
1004 #if HAS_MMAP
1005     } else {				// send mmap'd file data
1006 	ok = runScript(addr, sb.st_size, filename, emsg);
1007 	munmap(addr, (size_t) sb.st_size);
1008     }
1009 #endif
1010     return (ok);
1011 }
1012 
1013 bool
runScript(const char * script,u_long scriptLen,const char * filename,fxStr & emsg)1014 FaxClient::runScript(const char* script, u_long scriptLen,
1015     const char* filename, fxStr& emsg)
1016 {
1017     u_int lineno = 0;
1018     while (scriptLen > 0) {
1019 	lineno++;
1020 	const char* ep = strchr(script, '\n');
1021 	if (!ep)
1022 	    ep = script+scriptLen;
1023 	u_int cmdLen = ep-script;
1024 	if (cmdLen > 1) {
1025 	    if (command("%.*s", cmdLen, script) != COMPLETE) {
1026 		emsg = fxStr::format(NLS::TEXT("%s: line %u: %s"),
1027 		    filename, lineno, (const char*) lastResponse);
1028 		return (false);
1029 	    }
1030 	}
1031 	if (*ep == '\n')
1032 	    ep++;
1033 	scriptLen -= ep - script;
1034 	script = ep;
1035     }
1036     return (true);
1037 }
1038 
1039 /*
1040  * Create a uniquely named document on the server
1041  * that is not removed when the server exits.
1042  */
1043 bool
storeUnique(fxStr & docname,fxStr & emsg)1044 FaxClient::storeUnique(fxStr& docname, fxStr& emsg)
1045 {
1046     return storeUnique("STOU", docname, emsg);
1047 }
1048 /*
1049  * Create a uniquely named document on the server
1050  * that is automatically removed when the server exits.
1051  */
1052 bool
storeTemp(fxStr & docname,fxStr & emsg)1053 FaxClient::storeTemp(fxStr& docname, fxStr& emsg)
1054 {
1055     return storeUnique("STOT", docname, emsg);
1056 }
1057 
1058 /*
1059  * Send a STOU/STOT command and parse the
1060  * response to get the resulting filename.
1061  */
1062 bool
storeUnique(const char * cmd,fxStr & docname,fxStr & emsg)1063 FaxClient::storeUnique(const char* cmd, fxStr& docname, fxStr& emsg)
1064 {
1065     if (command(cmd) == PRELIM) {
1066 	if (code == 150) {
1067 	    /*
1068 	     * According to RFC 1123, the response must be of the form:
1069 	     *
1070 	     * 150 FILE: pppp[ anything]
1071 	     *
1072 	     * where pppp is the unique document name chosen by the server.
1073 	     */
1074 	    u_int l = 0;
1075 	    return (extract(l, "FILE:", docname, cmd, emsg));
1076 	} else
1077 	    unexpectedResponse(emsg);
1078     } else
1079 	emsg = lastResponse;
1080     return (false);
1081 }
1082 
1083 /*
1084  * Create/overwrite a file on the server.
1085  */
1086 bool
storeFile(fxStr & docname,fxStr & emsg)1087 FaxClient::storeFile(fxStr& docname, fxStr& emsg)
1088 {
1089     if (command("STOR " | docname) != PRELIM) {
1090 	emsg = lastResponse;
1091 	return (false);
1092     }
1093     if (code != 150) {
1094 	unexpectedResponse(emsg);
1095 	return (false);
1096     }
1097     return (true);
1098 }
1099 
1100 /*
1101  * Send a block of raw data on the data
1102  * conenction, interpreting write errors.
1103  */
1104 bool
sendRawData(void * buf,int cc,fxStr & emsg)1105 FaxClient::sendRawData(void* buf, int cc, fxStr& emsg)
1106 {
1107 #ifdef __linux__
1108     /*
1109      * Linux kernel bug: can get short writes on
1110      * stream sockets when setup for blocking i/o.
1111      */
1112     u_char* bp = (u_char*) buf;
1113     for (int cnt, sent = 0; cc; sent += cnt, cc -= cnt)
1114 	if ((cnt = write(fdData, bp + sent, cc)) <= 0) {
1115 	    protocolBotch(emsg, errno == EPIPE ?
1116 		NLS::TEXT(" (server closed connection)") : NLS::TEXT(" (server write error: %s)."),
1117 		strerror(errno));
1118 	    return (false);
1119 	}
1120 #else
1121     if (write(fdData, buf, cc) != cc) {
1122 	protocolBotch(emsg, errno == EPIPE ?
1123 	    NLS::TEXT(" (server closed connection)") : NLS::TEXT(" (server write error: %s)."),
1124 	    strerror(errno));
1125 	return (false);
1126     }
1127 #endif
1128     return (true);
1129 }
1130 
1131 /*
1132  * Send a document file using stream (uncompressed) mode
1133  * and the current transfer parameters.  The server-side
1134  * document name where data gets placed is returned.
1135  */
1136 bool
sendData(int fd,bool (FaxClient::* store)(fxStr &,fxStr &),fxStr & docname,fxStr & emsg)1137 FaxClient::sendData(int fd,
1138     bool (FaxClient::*store)(fxStr&, fxStr&), fxStr& docname, fxStr& emsg)
1139 {
1140     char* addr = (char*) -1;
1141     struct stat sb;
1142     size_t cc;
1143     (void) Sys::fstat(fd, sb);
1144     if (getVerbose())
1145 	traceServer(NLS::TEXT("SEND data, %lu bytes"), (u_long) sb.st_size);
1146     if (!initDataConn(emsg))
1147 	goto bad;
1148     if (!setMode(MODE_S))
1149 	goto bad;
1150     if (!(this->*store)(docname, emsg))
1151 	goto bad;
1152     if (!openDataConn(emsg))
1153 	goto bad;
1154 #if HAS_MMAP
1155     addr = (char*) mmap(NULL, (size_t) sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
1156     if (addr == (char*) -1) {			// revert to file reads
1157 #endif
1158 	cc = (size_t) sb.st_size;
1159 	while (cc > 0) {
1160 	    char buf[32*1024];			// XXX better if page-aligned
1161 	    size_t n = fxmin(cc, sizeof (buf));
1162 	    if (read(fd, buf, n) != (ssize_t)n) {
1163 		protocolBotch(emsg, NLS::TEXT(" (data read: %s)."), strerror(errno));
1164 		goto bad;
1165 	    }
1166 	    if (!sendRawData(buf, n, emsg))
1167 		goto bad;
1168 	    cc -= n;
1169 	}
1170 #if HAS_MMAP
1171     } else if (!sendRawData(addr, (int) sb.st_size, emsg)) // send mmap'd file data
1172 	goto bad;
1173 #endif
1174     closeDataConn();
1175 #if HAS_MMAP
1176     if (addr != (char*) -1)
1177 	munmap(addr, (size_t) sb.st_size);
1178 #endif
1179     return (getReply(false) == 2);
1180 bad:
1181     closeDataConn();
1182 #if HAS_MMAP
1183     if (addr != (char*) -1)
1184 	munmap(addr, (size_t) sb.st_size);
1185 #endif
1186     return (false);
1187 }
1188 
1189 /*
1190  * Send a document file using zip-compressed mode
1191  * and the current transfer parameters.  The server-side
1192  * document name where data gets placed is returned.
1193  */
1194 bool
sendZData(int fd,bool (FaxClient::* store)(fxStr &,fxStr &),fxStr & docname,fxStr & emsg)1195 FaxClient::sendZData(int fd,
1196     bool (FaxClient::*store)(fxStr&, fxStr&), fxStr& docname, fxStr& emsg)
1197 {
1198     z_stream zstream;
1199     zstream.zalloc = NULL;
1200     zstream.zfree = NULL;
1201     zstream.opaque = NULL;
1202     zstream.data_type = Z_BINARY;
1203     if (deflateInit(&zstream, Z_DEFAULT_COMPRESSION) == Z_OK) {
1204 #if HAS_MMAP
1205 	char* addr = (char*) -1;		// mmap'd file
1206 #endif
1207 	char obuf[32*1024];			// XXX better if page-aligned
1208 	zstream.next_out = (Bytef*) obuf;
1209 	zstream.avail_out = sizeof (obuf);
1210 	struct stat sb;
1211 	size_t cc;
1212 	Sys::fstat(fd, sb);
1213 	if (getVerbose())
1214 	    traceServer(NLS::TEXT("SEND compressed data, %lu bytes"), (u_long) sb.st_size);
1215 	if (!initDataConn(emsg))
1216 	    goto bad;
1217 	if (!setMode(MODE_Z))
1218 	    goto bad;
1219 	if (!(this->*store)(docname, emsg))
1220 	    goto bad;
1221 	if (!openDataConn(emsg))
1222 	    goto bad;
1223 #if HAS_MMAP
1224 	addr = (char*) mmap(NULL, (size_t) sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
1225 	if (addr == (char*) -1) {		// revert to file reads
1226 #endif
1227 	    cc = (size_t) sb.st_size;
1228 	    while (cc > 0) {
1229 		char buf[32*1024];
1230 		int n = fxmin((size_t) cc, sizeof (buf));
1231 		if (read(fd, buf, n) != n) {
1232 		    protocolBotch(emsg, NLS::TEXT(" (data read: %s)"), strerror(errno));
1233 		    goto bad;
1234 		}
1235 		zstream.next_in = (Bytef*) buf;
1236 		zstream.avail_in = n;
1237 		do {
1238 		    if (deflate(&zstream, Z_NO_FLUSH) != Z_OK) {
1239 			emsg = fxStr::format(NLS::TEXT("zlib compressor error: %s"),
1240 			    zstream.msg);
1241 			goto bad;
1242 		    }
1243 		    if (zstream.avail_out == 0) {
1244 			if (!sendRawData(obuf, sizeof (obuf), emsg))
1245 			    goto bad2;
1246 			zstream.next_out = (Bytef*) obuf;
1247 			zstream.avail_out = sizeof (obuf);
1248 		    }
1249 		} while (zstream.avail_in > 0);
1250 		cc -= n;
1251 	    }
1252 	    zstream.avail_in = 0;
1253 #if HAS_MMAP
1254 	} else {
1255 	    zstream.next_in = (Bytef*) addr;
1256 	    zstream.avail_in = (u_int) sb.st_size;
1257 	    do {
1258 		if (deflate(&zstream, Z_NO_FLUSH) != Z_OK) {
1259 		    emsg = fxStr::format(NLS::TEXT("zlib compressor error: %s"),
1260 			zstream.msg);
1261 		    goto bad;
1262 		}
1263 		if (zstream.avail_out == 0) {
1264 		    if (!sendRawData(obuf, sizeof (obuf), emsg))
1265 			goto bad2;
1266 		    zstream.next_out = (Bytef*) obuf;
1267 		    zstream.avail_out = sizeof (obuf);
1268 		}
1269 	    } while (zstream.avail_in > 0);
1270 	}
1271 #endif
1272 	int dstate;
1273 	do {
1274 	    switch (dstate = deflate(&zstream, Z_FINISH)) {
1275 	    case Z_STREAM_END:
1276 	    case Z_OK:
1277 		if (zstream.avail_out != sizeof (obuf)) {
1278 		    if (!sendRawData(obuf, sizeof (obuf) - zstream.avail_out, emsg))
1279 			goto bad2;
1280 		    zstream.next_out = (Bytef*) obuf;
1281 		    zstream.avail_out = sizeof (obuf);
1282 		}
1283 		break;
1284 	    default:
1285 		emsg = fxStr::format(NLS::TEXT("zlib compressor error: %s"),
1286 		    zstream.msg);
1287 		goto bad;
1288 	    }
1289 	} while (dstate != Z_STREAM_END);
1290 	if (getVerbose())
1291 	    traceServer(NLS::TEXT("SEND %lu bytes transmitted (%.1fx compression)"),
1292 #define	NZ(x)	((x)?(x):1)
1293 		zstream.total_out, float(sb.st_size) / NZ(zstream.total_out));
1294 	closeDataConn();
1295 #if HAS_MMAP
1296 	if (addr != (char*) -1)
1297 	    munmap(addr, (size_t) sb.st_size);
1298 #endif
1299 	deflateEnd(&zstream);
1300 	return (getReply(false) == COMPLETE);
1301 bad2:
1302 	(void) getReply(false);
1303 	/* fall thru... */
1304 bad:
1305 	closeDataConn();
1306 #if HAS_MMAP
1307 	if (addr != (char*) -1)
1308 	    munmap(addr, (size_t) sb.st_size);
1309 #endif
1310 	deflateEnd(&zstream);
1311     } else
1312 	emsg = fxStr::format(NLS::TEXT("Can not initialize compression library: %s"),
1313 	    zstream.msg);
1314     return (false);
1315 }
1316 
1317 /*
1318  * Receive data using stream mode and the current
1319  * transfer parameters.  The supplied arguments are
1320  * passed to command to initiate the transfers once
1321  * a data connection has been setup.  These commands
1322  * can intiate a file retrieval (RETR), directory
1323  * listing (LIST), trigger event trace log (SITE TRIGGER)
1324  * or other data connection-based transfer.
1325  */
1326 bool
recvData(bool (* f)(int,const char *,int,fxStr &),int arg,fxStr & emsg,u_long restart,const char * fmt,...)1327 FaxClient::recvData(bool (*f)(int, const char*, int, fxStr&),
1328     int arg, fxStr& emsg, u_long restart, const char* fmt, ...)
1329 {
1330     if ((!setMode(MODE_S)) ||
1331 	(!initDataConn(emsg)) ||
1332 	(restart && command("REST %lu", restart) != CONTINUE)) {
1333 	// cannot "goto bad" because it is outside the scope of a va_arg
1334 	closeDataConn();
1335 	return (false);
1336     }
1337     va_list ap;
1338     va_start(ap, fmt);
1339     int r; r = vcommand(fmt, ap);
1340     va_end(ap);
1341     if (r != PRELIM)
1342 	goto bad;
1343     if (!openDataConn(emsg))
1344 	goto bad;
1345     u_long byte_count; byte_count = 0;		// XXX for __GNUC__
1346     for (;;) {
1347 	char buf[16*1024];
1348 	int cc = read(fdData, buf, sizeof (buf));
1349 	if (cc == 0) {
1350 	    closeDataConn();
1351 	    return (getReply(false) == COMPLETE);
1352 	}
1353 	if (cc < 0) {
1354 	    emsg = fxStr::format(NLS::TEXT("Data Connection: %s"), strerror(errno));
1355 	    (void) getReply(false);
1356 	    break;
1357 	}
1358 	byte_count += cc;
1359 	if (!(*f)(arg, buf, cc, emsg))
1360 	    break;
1361     }
1362 bad:
1363     closeDataConn();
1364     return (false);
1365 }
1366 
1367 /*
1368  * Receive data using zip-compressed mode and the current
1369  * transfer parameters.  The supplied arguments are
1370  * passed to command to initiate the transfers once
1371  * a data connection has been setup.  These commands
1372  * can intiate a file retrieval (RETR), directory
1373  * listing (LIST), trigger event trace log (SITE TRIGGER)
1374  * or other data connection-based transfer.
1375  */
1376 bool
recvZData(bool (* f)(void *,const char *,int,fxStr &),void * arg,fxStr & emsg,u_long restart,const char * fmt,...)1377 FaxClient::recvZData(bool (*f)(void*, const char*, int, fxStr&),
1378     void* arg, fxStr& emsg, u_long restart, const char* fmt, ...)
1379 {
1380     z_stream zstream;
1381     zstream.zalloc = NULL;
1382     zstream.zfree = NULL;
1383     zstream.opaque = NULL;
1384     zstream.data_type = Z_BINARY;
1385     if (inflateInit(&zstream) == Z_OK) {
1386 	if ((!setMode(MODE_Z)) ||
1387 	    (!initDataConn(emsg)) ||
1388 	    (restart && command("REST %lu", restart) != CONTINUE)) {
1389 	    // cannot "goto bad" because it is outside the scope of a va_arg
1390 	    closeDataConn();
1391 	    inflateEnd(&zstream);
1392 	    return (false);
1393 	}
1394 	va_list ap;
1395 	va_start(ap, fmt);
1396 	int r; r = vcommand(fmt, ap);		// XXX for __GNUC__
1397 	va_end(ap);
1398 	if (r != PRELIM)
1399 	    goto bad;
1400 	if (!openDataConn(emsg))
1401 	    goto bad;
1402 	char obuf[16*1024];
1403 	zstream.next_out = (Bytef*) obuf;
1404 	zstream.avail_out = sizeof (obuf);
1405 	for (;;) {
1406 	    char buf[16*1024];
1407 	    int cc = read(fdData, buf, sizeof (buf));
1408 	    if (cc == 0) {
1409 		size_t occ = sizeof (obuf) - zstream.avail_out;
1410 		if (occ > 0 && !(*f)(arg, obuf, occ, emsg))
1411 		    goto bad;
1412 		closeDataConn();
1413 		(void) inflateEnd(&zstream);
1414 		return (getReply(false) == COMPLETE);
1415 	    }
1416 	    if (cc < 0) {
1417 		emsg = fxStr::format(NLS::TEXT("Data Connection: %s"), strerror(errno));
1418 		(void) getReply(false);
1419 		goto bad;
1420 	    }
1421 	    zstream.next_in = (Bytef*) buf;
1422 	    zstream.avail_in = cc;
1423 	    do {
1424 		int dstate = inflate(&zstream, Z_PARTIAL_FLUSH);
1425 		if (dstate == Z_STREAM_END)
1426 		    break;
1427 		if (dstate != Z_OK) {
1428 		    emsg = fxStr::format(NLS::TEXT("Decoding error: %s"), zstream.msg);
1429 		    goto bad;
1430 		}
1431 		size_t occ = sizeof (obuf) - zstream.avail_out;
1432 		if (!(*f)(arg, obuf, occ, emsg))
1433 		    goto bad;
1434 		zstream.next_out = (Bytef*) obuf;
1435 		zstream.avail_out = sizeof (obuf);
1436 	    } while (zstream.avail_in > 0);
1437 	}
1438 bad:
1439 	closeDataConn();
1440 	inflateEnd(&zstream);
1441     } else
1442 	emsg = fxStr::format(NLS::TEXT("Can not initialize decoder: %s"), zstream.msg);
1443     return (false);
1444 }
1445 
1446 /*
1447  * Return the current value for the specified
1448  * status format string.  If we have not set a
1449  * value locally, ask the server for the default
1450  * setting.
1451  */
1452 const fxStr&
getStatusFormat(u_int flag,const char * cmd,fxStr & fmt)1453 FaxClient::getStatusFormat(u_int flag, const char* cmd, fxStr& fmt)
1454 {
1455     if (isLoggedIn()) {
1456 	if (state&flag) {		// set pending; do it
1457 	    if (command("%s \"%s\"", cmd, (const char*) fmt) == COMPLETE)
1458 		state &= ~flag;
1459 	    else
1460 		printError("%s", (const char*) lastResponse);
1461 	} else if (fmt == "") {		// must query server
1462 	    if (command(cmd) == COMPLETE)
1463 		fmt = lastResponse.tail(lastResponse.length()-4);
1464 	    else
1465 		printError("%s", (const char*) lastResponse);
1466 	}
1467     }
1468     return (fmt);
1469 }
1470 
1471 /*
1472  * Set the specified status format string
1473  *  in the client and the server.
1474  */
1475 bool
setStatusFormat(const char * cmd,u_int flag,fxStr & fmt,const char * value)1476 FaxClient::setStatusFormat(const char* cmd, u_int flag,
1477     fxStr& fmt, const char* value)
1478 {
1479     if (isLoggedIn()) {
1480 	if (command("%s \"%s\"", cmd, value) != COMPLETE) {
1481 	    printError("%s", (const char*) lastResponse);
1482 	    return (false);
1483 	}
1484 	state &= ~flag;
1485     } else
1486 	state |= flag;
1487     fmt = value;
1488     return (true);
1489 }
1490 
1491 /*
1492  * Set the job status format string in the
1493  * client and the server.
1494  */
1495 bool
setJobStatusFormat(const char * cp)1496 FaxClient::setJobStatusFormat(const char* cp)
1497 {
1498     return setStatusFormat("JOBFMT", FS_JFMTPEND, jobFmt, cp);
1499 }
1500 
1501 /*
1502  * Return the current job status format string.
1503  * If we have not set a value locally, ask the
1504  * server for the default setting.
1505  */
1506 const fxStr&
getJobStatusFormat(void)1507 FaxClient::getJobStatusFormat(void)
1508 {
1509     return getStatusFormat(FS_JFMTPEND, "JOBFMT", jobFmt);
1510 }
1511 
1512 /*
1513  * Set the receive queue status format
1514  * string in the client and the server.
1515  */
1516 bool
setRecvStatusFormat(const char * cp)1517 FaxClient::setRecvStatusFormat(const char* cp)
1518 {
1519     return setStatusFormat("RCVFMT", FS_RFMTPEND, recvFmt, cp);
1520 }
1521 
1522 /*
1523  * Return the current recv queue status format
1524  * string.  If we have not set a value locally,
1525  * ask the server for the default setting.
1526  */
1527 const fxStr&
getRecvStatusFormat(void)1528 FaxClient::getRecvStatusFormat(void)
1529 {
1530     return getStatusFormat(FS_RFMTPEND, "RCVFMT", recvFmt);
1531 }
1532 
1533 
1534 /*
1535  * Set the modem status format
1536  * string in the client and the server.
1537  */
1538 bool
setModemStatusFormat(const char * cp)1539 FaxClient::setModemStatusFormat(const char* cp)
1540 {
1541     return setStatusFormat("MDMFMT", FS_MFMTPEND, modemFmt, cp);
1542 }
1543 
1544 /*
1545  * Return the current modem status format
1546  * string.  If we have not set a value locally,
1547  * ask the server for the default setting.
1548  */
1549 const fxStr&
getModemStatusFormat(void)1550 FaxClient::getModemStatusFormat(void)
1551 {
1552     return getStatusFormat(FS_MFMTPEND, "MDMFMT", modemFmt);
1553 }
1554 
1555 /*
1556  * Set the file status format
1557  * string in the client and the server.
1558  */
1559 bool
setFileStatusFormat(const char * cp)1560 FaxClient::setFileStatusFormat(const char* cp)
1561 {
1562     return setStatusFormat("FILEFMT", FS_FFMTPEND, fileFmt, cp);
1563 }
1564 
1565 /*
1566  * Return the current file status format
1567  * string.  If we have not set a value locally,
1568  * ask the server for the default setting.
1569  */
1570 const fxStr&
getFileStatusFormat(void)1571 FaxClient::getFileStatusFormat(void)
1572 {
1573     return getStatusFormat(FS_FFMTPEND, "FILEFMT", fileFmt);
1574 }
1575 
1576 /*
1577  * Convert a format string to a header using a table
1578  * that maps format character to field header.
1579  */
1580 void
makeHeader(const char * fmt,const FaxFmtHeader fmts[],fxStr & header)1581 FaxClient::makeHeader(const char* fmt, const FaxFmtHeader fmts[], fxStr& header)
1582 {
1583     for (const char* cp = fmt; *cp; cp++) {
1584 	if (*cp == '%') {
1585 	    u_int width = 0;		// field width
1586 	    u_int prec = 0;		// field precision
1587 #define	MAXSPEC	20
1588 	    char fspec[MAXSPEC];
1589 	    char* fp = fspec;
1590 	    *fp++ = '%';
1591 	    char c = *++cp;
1592 	    if (c == '\0')
1593 		break;
1594 	    if (c == '-')
1595 		*fp++ = c, c = *++cp;
1596 	    if (isdigit(c)) {
1597 		do {
1598 		    *fp++ = c;
1599 		    width = 10*width + (c-'0');
1600 		} while (isdigit(c = *++cp) && fp < &fspec[MAXSPEC-3]);
1601 	    }
1602 	    if (c == '.') {
1603 		do {
1604 		    *fp++ = c;
1605 		    prec = 10*prec + (c-'0');
1606 		} while (isdigit(c = *++cp) && fp < &fspec[MAXSPEC-2]);
1607 	    }
1608 	    if (c == '%') {		// %% -> %
1609 		header.append(c);
1610 		continue;
1611 	    }
1612 	    const FaxFmtHeader* hp;
1613 	    for (hp = fmts; hp->fmt != '\0' && hp->fmt != c; hp++)
1614 		;
1615 	    if (hp->fmt == c) {
1616 		if (prec == 0)		// constrain header to field width
1617 		    prec = width;
1618 		if (fspec[1] == '-')	// left justify
1619 		    width = -width;
1620 		if (width == 0 && prec == 0)
1621 		    header.append(NLS::TEXT(hp->title));
1622 		else
1623 		    header.append(fxStr::format("%*.*s", width, prec, NLS::TEXT(hp->title)));
1624 	    } else {
1625 		*fp++ = c;
1626 		header.append(fxStr(fspec, fp-fspec));
1627 	    }
1628 	} else
1629 	    header.append(*cp);
1630     }
1631 }
1632 
1633 /*
1634  * Table of known format strings for the job
1635  * queue status listings returned by the server.
1636  */
1637 const FaxClient::FaxFmtHeader FaxClient::jobFormats[] = {
1638     /* translator: Column header for Recipient sub-address */				{ 'A',	N_("SUB") },
1639     /* translator: Column header for Recipient password (of sub-address) */		{ 'B',	N_("PWD") },
1640     /* translator: Column header for Recipient company name */				{ 'C',	N_("Company") },
1641     /* translator: Column header for (total dials):(maximum dials) */			{ 'D',	N_("Dials") },
1642     /* translator: Column header for signalling rate */					{ 'E',	N_("BR") },
1643     /* translator: Column header for job tagline */					{ 'F',	N_("Tagline") },
1644     /* translator: Column header for min-scanline-time */				{ 'G',	N_("ST") },
1645     /* translator: Column header for data format */					{ 'H',	N_("DF") },
1646     /* translator: Column header for user-requested scheduling priority */		{ 'I',	N_("UsrPri") },
1647     /* translator: Column header for user specified job tag */				{ 'J',	N_("JobTag") },
1648     /* translator: Column header for ECM (symbol) */					{ 'K',	N_("EC") },
1649     /* translator: Column header for recipient location */				{ 'L',	N_("Location") },
1650     /* translator: Column header for mail address */					{ 'M',	N_("MailAddr") },
1651     /* translator: Column header for tagline handling (symbol) */			{ 'N',	N_("DT") },
1652     /* translator: Column header for continuation cover page use (symbol) */		{ 'O',	N_("CC") },
1653     /* translator: Column header for (number of pages):(total pages) */			{ 'P',	N_("Pages") },
1654     /* translator: Column header for minimum speed */					{ 'Q',	N_("MinSP") },
1655     /* translator: Column header for receiver name */					{ 'R',	N_("Receiver") },
1656     /* translator: Column header for sender name */					{ 'S',	N_("Sender") },
1657     /* translator: Column header for (total tries):(maximum tries) */			{ 'T',	N_("Tries") },
1658     /* translator: Column header for minimum white space before chopping */		{ 'U',	N_("ChopThreshold") },
1659     /* translator: Column header for operation to do when job completes */		{ 'V',	N_("DoneOp") },
1660     /* translator: Column header for communication ID */				{ 'W',	N_("CommID") },
1661     /* translator: Column header for job type */					{ 'X',	N_("JobType") },
1662     /* translator: Column header for Date & Time */					{ 'Y',	N_("Date       Time") },
1663     /* translator: Column header for seconds since the UNIX epoch */			{ 'Z',	N_("UNIX Time") },
1664     /* translator: Column header for job state (symbol) */				{ 'a',	N_("State") },
1665     /* translator: Column header for number of tries */					{ 'b',	N_("NTries") },
1666     /* translator: Column header for identity of machine that submitted job */		{ 'c',	N_("Client") },
1667     /* translator: Column header for total dials */					{ 'd',	N_("TotDials") },
1668     /* translator: Column header for recipient fax number */				{ 'e',	N_("Number") },
1669     /* translator: Column header for number of dials */					{ 'f',	N_("NDials") },
1670     /* translator: Column header for group identifier */				{ 'g',	N_("GID") },
1671     /* translator: Column header for page chopping (symbol) */				{ 'h',	N_("Chop") },
1672     /* translator: Column header for current scheduling priority */			{ 'i',	N_("Priority") },
1673     /* translator: Column header for job identifier */					{ 'j',	N_("JID") },
1674     /* translator: Column header for time to kill job */				{ 'k',	N_("LastTime") },
1675     /* translator: Column header for page length */					{ 'l',	N_("PageLength") },
1676     /* translator: Column header for modem group */					{ 'm',	N_("Modem") },
1677     /* translator: Column header for email notification (symbol) */			{ 'n',	N_("Notify") },
1678     /* translator: Column header for owner name */					{ 'o',	N_("Owner") },
1679     /* translator: Column header for number of pages */					{ 'p',	N_("Pages") },
1680     /* translator: Column header for retry time */					{ 'q',	N_("RetryTime") },
1681     /* translator: Column header for fax image vertical resolution */			{ 'r',	N_("Resolution") },
1682     /* translator: Column header for request status indicator */			{ 's',	N_("Status") },
1683     /* translator: Column header for total tries */					{ 't',	N_("TotTries") },
1684     /* translator: Column header for maximum tries */					{ 'u',	N_("MaxTries") },
1685     /* translator: Column header for dial string */					{ 'v',	N_("DialString") },
1686     /* translator: Column header for page width */					{ 'w',	N_("PageWidth") },
1687     /* translator: Column header for maximum dials */					{ 'x',	N_("MaxDials") },
1688     /* translator: Column header for total of pages */					{ 'y',	N_("TotPages") },
1689     /* translator: Column header for time to send */					{ 'z',	N_("TTS") },
1690     /* translator: Column header for extended vertical resolution use (symbol) */	{ '0',	N_("UseXVres") },
1691 											{ '\0' },
1692 };
getJobStatusHeader(fxStr & header)1693 void FaxClient::getJobStatusHeader(fxStr& header)
1694 {
1695     makeHeader(getJobStatusFormat(), jobFormats, header);
1696    if (jobSFmt.length())
1697 	command("JOBSORTFMT \"%s\"", (const char*)jobSFmt);
1698 }
1699 
1700 /*
1701  * Table of known format strings for the receive
1702  * queue status listings returned by the server.
1703  */
1704 const FaxClient::FaxFmtHeader FaxClient::recvFormats[] = {
1705     /* translator: Column header for Date & Time */					{ 'Y',	N_("Date       Time") },
1706     /* translator: Column header for seconds since the UNIX epoch */			{ 'Z',	N_("UNIX Time") },
1707     /* translator: Column header for Recipient sub-address */				{ 'a',	N_("SUB") },
1708     /* translator: Column header for signalling rate */					{ 'b',	N_("BR") },
1709     /* translator: Column header for data format */					{ 'd',	N_("DF") },
1710     /* translator: Column header for error description */				{ 'e',	N_("Error") },
1711     /* translator: Column header for file name */					{ 'f',	N_("Filename") },
1712     /* translator: Column header for time spent receiving */				{ 'h',	N_("Time") },
1713     /* translator: Column header for callerid name */					{ 'i',	N_("CIDName") },
1714     /* translator: Column header for caller id number */				{ 'j',	N_("CIDNumber") },
1715     /* translator: Column header for page length */					{ 'l',	N_("Length") },
1716     /* translator: Column header for fax protection mode */				{ 'm',	N_("Protect") },
1717     /* translator: Column header for file size */					{ 'n',	N_("Size") },
1718     /* translator: Column header for job owner */					{ 'o',	N_("Owner") },
1719     /* translator: Column header for number of pages */					{ 'p',	N_("Pages") },
1720     /* translator: Column header for fax protection mode */				{ 'q',	N_("Protect") },
1721     /* translator: Column header for fax image resolution */				{ 'r',	N_("Resolution") },
1722     /* translator: Column header for sender TSI (Transmitting Station Information) */	{ 's',	N_("Sender/TSI") },
1723     /* translator: Column header for time received */					{ 't',	N_("Recvd@") },
1724     /* translator: Column header for page width */					{ 'w',	N_("Width") },
1725     /* ``*'' if being received */							{ 'z',	" " },
1726 											{ '\0' },
1727 };
getRecvStatusHeader(fxStr & header)1728 void FaxClient::getRecvStatusHeader(fxStr& header)
1729 {
1730    makeHeader(getRecvStatusFormat(), recvFormats, header);
1731    if (recvSFmt.length())
1732 	command("RCVSORTFMT \"%s\"", (const char*)recvSFmt);
1733 }
1734 
1735 /*
1736  * Table of known format strings for the modem
1737  * status listings returned by the server.
1738  */
1739 const FaxClient::FaxFmtHeader FaxClient::modemFormats[] = {
1740     /* translator: Column header for machine hostname */		{ 'h',	N_("Host") },
1741     /* translator: Column header for local identifier */		{ 'l',	N_("LocalID") },
1742     /* translator: Column header for canonical modem name */		{ 'm',	N_("Modem") },
1743     /* translator: Column header for fax phone number */		{ 'n',	N_("Number") },
1744     /* translator: Column header for maximum received pages */		{ 'r',	N_("MaxRecv") },
1745     /* translator: Column header for status information */		{ 's',	N_("Status") },
1746     /* translator: Column header for server:session tracing level */	{ 't',	N_("Tracing") },
1747     /* translator: Column header for speaker volume (symbol) */		{ 'v',	N_("Speaker") },
1748     /* ``*'' if faxgetty is running */					{ 'z',	" " },
1749 									{ '\0' },
1750 };
getModemStatusHeader(fxStr & header)1751 void FaxClient::getModemStatusHeader(fxStr& header)
1752 {
1753     makeHeader(getModemStatusFormat(), modemFormats, header);
1754     if (modemSFmt.length() )
1755 	command("MDMSORTFMT \"%s\"", (const char*)modemSFmt);
1756 
1757 }
1758 
1759 /*
1760  * Table of known format strings for the file
1761  * status listings returned by the server.
1762  */
1763 const FaxClient::FaxFmtHeader FaxClient::fileFormats[] = {
1764     /* translator: Column header for file last access time */		{ 'a',	N_("LastAcc") },
1765     /* translator: Column header for file creation time */		{ 'c',	N_("Created") },
1766     /* translator: Column header for ID of device containing file */	{ 'd',	N_("Device") },
1767     /* translator: Column header for file name */			{ 'f',	N_("Filename") },
1768     /* translator: Column header for file GID */			{ 'g',	N_("GID") },
1769     /* translator: Column header for file link count */			{ 'l',	N_("Links") },
1770     /* translator: Column header for file last modification time */	{ 'm',	N_("LastMod") },
1771     /* translator: Column header for file owner */			{ 'o',	N_("Owner") },
1772     /* translator: Column header for file protection flags */		{ 'p',	N_("Protect") },
1773     /* translator: Column header for file protection flags */		{ 'q',	N_("Protect") },
1774     /* translator: Column header for ID of device if special file */	{ 'r',	N_("RootDev") },
1775     /* translator: Column header for file size */			{ 's',	N_("Size") },
1776     /* translator: Column header for file UID */			{ 'u',	N_("UID") },
1777 									{ '\0' },
1778 };
getFileStatusHeader(fxStr & header)1779 void FaxClient::getFileStatusHeader(fxStr& header)
1780 {
1781     makeHeader(getFileStatusFormat(), fileFormats, header);
1782     if (fileSFmt.length() )
1783 	command("FILESORTFMT \"%s\"", (const char*)fileSFmt);
1784 }
1785