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