1 /*	$Id$ */
2 /*
3  * Copyright (c) 1995-1996 Sam Leffler
4  * Copyright (c) 1995-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 "SNPPClient.h"
29 #include <pwd.h>
30 #include <ctype.h>
31 #include <sys/types.h>
32 #include <errno.h>
33 
34 #define	N(a)	(sizeof (a) / sizeof (a[0]))
35 
36 #include "NLS.h"
37 
SNPPClient()38 SNPPClient::SNPPClient()
39 {
40     init();
41 }
42 
SNPPClient(const fxStr & hostarg)43 SNPPClient::SNPPClient(const fxStr& hostarg)
44 {
45     init();
46     setupHostModem(hostarg);
47 }
48 
SNPPClient(const char * hostarg)49 SNPPClient::SNPPClient(const char* hostarg)
50 {
51     init();
52     setupHostModem(hostarg);
53 }
54 
55 void
init()56 SNPPClient::init()
57 {
58     jobs = new SNPPJobArray;
59     fdIn = NULL;
60     fdOut = NULL;
61     state = 0;
62     msg = NULL;
63     pasv = false;
64 
65     setupConfig();
66 }
67 
68 void
initServerState(void)69 SNPPClient::initServerState(void)
70 {
71 }
72 
~SNPPClient()73 SNPPClient::~SNPPClient()
74 {
75     (void) hangupServer();
76     delete jobs;
77     delete msg;
78 }
79 
80 void
printError(const char * fmt...)81 SNPPClient::printError(const char* fmt ...)
82 {
83     va_list ap;
84     va_start(ap, fmt);
85     vprintError(fmt, ap);
86     va_end(ap);
87 }
88 void
vprintError(const char * fmt,va_list ap)89 SNPPClient::vprintError(const char* fmt, va_list ap)
90 {
91     vfprintf(stderr, fmt, ap);
92     fputs("\n", stderr);
93 }
94 
95 void
printWarning(const char * fmt...)96 SNPPClient::printWarning(const char* fmt ...)
97 {
98     va_list ap;
99     va_start(ap, fmt);
100     vprintWarning(fmt, ap);
101     va_end(ap);
102 }
103 void
vprintWarning(const char * fmt,va_list ap)104 SNPPClient::vprintWarning(const char* fmt, va_list ap)
105 {
106     fprintf(stderr, NLS::TEXT("Warning, "));
107     vfprintf(stderr, fmt, ap);
108     fputs("\n", stderr);
109 }
110 
111 void
traceServer(const char * fmt...)112 SNPPClient::traceServer(const char* fmt ...)
113 {
114     va_list ap;
115     va_start(ap, fmt);
116     vtraceServer(fmt, ap);
117     va_end(ap);
118 }
119 void
vtraceServer(const char * fmt,va_list ap)120 SNPPClient::vtraceServer(const char* fmt, va_list ap)
121 {
122     vfprintf(stdout, fmt, ap);
123     fputs("\n", stdout);
124 }
125 
126 /*
127  * Host, port, and modem can be specified using
128  *
129  *     modem@host:port
130  *
131  * e.g. ttyf2@flake.asd:9999.  Alternate forms
132  * are: modem@, modem@host, host, host:port.
133  */
134 void
setupHostModem(const fxStr & s)135 SNPPClient::setupHostModem(const fxStr& s)
136 {
137     u_int pos = s.next(0, '@');
138     if (pos != s.length()) {
139 	modem = s.head(pos);
140 	host = s.tail(s.length() - (pos+1));
141     } else
142 	host = s;
143     pos = host.next(0, ':');
144     if (pos != host.length()) {
145 	port = atoi(host.tail(host.length() - (pos+1)));
146 	host.resize(pos);
147     }
148 }
setupHostModem(const char * cp)149 void SNPPClient::setupHostModem(const char* cp) { setupHostModem(fxStr(cp)); }
150 
setHost(const fxStr & hostarg)151 void SNPPClient::setHost(const fxStr& hostarg)	{ setupHostModem(hostarg); }
setHost(const char * hostarg)152 void SNPPClient::setHost(const char* hostarg)	{ setupHostModem(hostarg); }
setPort(int p)153 void SNPPClient::setPort(int p)			{ port = p; }
setProtoName(const char * s)154 void SNPPClient::setProtoName(const char* s)	{ proto = s; }
155 
setModem(const fxStr & modemarg)156 void SNPPClient::setModem(const fxStr& modemarg){ modem = modemarg; }
setModem(const char * modemarg)157 void SNPPClient::setModem(const char* modemarg)	{ modem = modemarg; }
158 
159 void
setVerbose(bool v)160 SNPPClient::setVerbose(bool v)
161 {
162     if (v)
163 	state |= SS_VERBOSE;
164     else
165 	state &= ~SS_VERBOSE;
166 }
167 
168 /*
169  * Setup the sender's identity.
170  */
171 bool
setupSenderIdentity(fxStr & emsg)172 SNPPClient::setupSenderIdentity(fxStr& emsg)
173 {
174     setupUserIdentity(emsg);			// client identity
175 
176     if (from != "") {
177 	u_int l = from.next(0, '<');
178 	if (l == from.length()) {
179 	    l = from.next(0, '(');
180 	    if (l != from.length()) {		// joe@foobar (Joe Schmo)
181 		setBlankMailboxes(from.head(l));
182 		l++, senderName = from.token(l, ')');
183 	    } else {				// joe
184 		setBlankMailboxes(from);
185 		if (from != getUserName())
186 		    senderName = "";
187 	    }
188 	} else {				// Joe Schmo <joe@foobar>
189 	    senderName = from.head(l);
190 	    l++, setBlankMailboxes(from.token(l, '>'));
191 	}
192 	if (senderName == "" && getNonBlankMailbox(senderName)) {
193 	    /*
194 	     * Mail address, but no "real name"; construct one from
195 	     * the account name.  Do this by first stripping anything
196 	     * to the right of an '@' and then stripping any leading
197 	     * uucp patch (host!host!...!user).
198 	     */
199 	    senderName.resize(senderName.next(0, '@'));
200 	    senderName.remove(0, senderName.nextR(senderName.length(), '!'));
201 	}
202 
203 	// strip and leading&trailing white space
204 	senderName.remove(0, senderName.skip(0, " \t"));
205 	senderName.resize(senderName.skipR(senderName.length(), " \t"));
206     } else {
207 	setBlankMailboxes(getUserName());
208     }
209     fxStr mbox;
210     if (senderName == "" || !getNonBlankMailbox(mbox)) {
211 	emsg = NLS::TEXT("Malformed (null) sender name or mail address");
212 	return (false);
213     } else
214 	return (true);
215 }
setFromIdentity(const char * s)216 void SNPPClient::setFromIdentity(const char* s)		{ from = s; }
217 
218 /*
219  * Assign the specified string to any unspecified email
220  * addresses used for notification mail.
221  */
222 void
setBlankMailboxes(const fxStr & s)223 SNPPClient::setBlankMailboxes(const fxStr& s)
224 {
225     for (u_int i = 0, n = jobs->length(); i < n; i++) {
226 	SNPPJob& job = (*jobs)[i];
227 	if (job.getMailbox() == "")
228 	    job.setMailbox(s);
229     }
230 }
231 
232 /*
233  * Return the first non-null mailbox string
234  * in the set of jobs.
235  */
236 bool
getNonBlankMailbox(fxStr & s)237 SNPPClient::getNonBlankMailbox(fxStr& s)
238 {
239     for (u_int i = 0, n = jobs->length(); i < n; i++) {
240 	SNPPJob& job = (*jobs)[i];
241 	if (job.getMailbox() != "") {
242 	    s = job.getMailbox();
243 	    return (true);
244 	}
245     }
246     return (false);
247 }
248 
249 bool
setupUserIdentity(fxStr & emsg)250 SNPPClient::setupUserIdentity(fxStr& emsg)
251 {
252     struct passwd* pwd;
253 
254     pwd = getpwuid(getuid());
255     if (!pwd) {
256 	emsg = fxStr::format(
257 	    NLS::TEXT("Can not locate your password entry (uid %lu): %s."),
258 		(u_long) getuid(), strerror(errno));
259 	return (false);
260     }
261     userName = pwd->pw_name;
262     if (pwd->pw_gecos && pwd->pw_gecos[0] != '\0') {
263 	senderName = pwd->pw_gecos;
264 	senderName.resize(senderName.next(0, '('));	// strip SysV junk
265 	u_int l = senderName.next(0, '&');
266 	if (l < senderName.length()) {
267 	    /*
268 	     * Do the '&' substitution and raise the
269 	     * case of the first letter of the inserted
270 	     * string (the usual convention...)
271 	     */
272 	    senderName.remove(l);
273 	    senderName.insert(userName, l);
274 	    if (islower(senderName[l]))
275 		senderName[l] = toupper(senderName[l]);
276 	}
277 	senderName.resize(senderName.next(0,','));
278     } else
279 	senderName = userName;
280     if (senderName.length() == 0) {
281 	emsg = NLS::TEXT("Bad (null) user name; your password file entry"
282 	    " probably has bogus GECOS field information.");
283 	return (false);
284     } else
285 	return (true);
286 }
287 
288 void
setPagerMsg(const char * v)289 SNPPClient::setPagerMsg(const char* v)
290 {
291     delete msg;
292     msg = new fxStr(v);
293     msgFile ="";
294 }
295 
296 void
setPagerMsgFile(const char * v)297 SNPPClient::setPagerMsgFile(const char* v)
298 {
299     msgFile = v;
300     delete msg;
301 }
302 
303 /*
304  * Configuration file support.
305  */
306 
307 SNPPClient::S_stringtag SNPPClient::strings[] = {
308 { "protocol",			&SNPPClient::proto,	SNPP_PROTONAME },
309 { "host",			&SNPPClient::host,	NULL },
310 { "modem",			&SNPPClient::modem,	NULL },
311 };
312 SNPPClient::S_numbertag SNPPClient::numbers[] = {
313 { "port",			&SNPPClient::port,	(u_int) -1 },
314 };
315 
316 /*
317  * Configuration file support.
318  */
319 #define	N(a)	(sizeof (a) / sizeof (a[0]))
320 
321 void
setupConfig()322 SNPPClient::setupConfig()
323 {
324     int i;
325 
326     for (i = N(strings)-1; i >= 0; i--)
327 	(*this).*strings[i].p = (strings[i].def ? strings[i].def : "");
328     for (i = N(numbers)-1; i >= 0; i--)
329 	(*this).*numbers[i].p = numbers[i].def;
330     initServerState();
331 
332     jproto.setQueued(SNPP_DEFQUEUE);
333     jproto.setNotification(SNPP_DEFNOTIFY);
334     jproto.setHoldTime(0);		// immediate delivery
335     jproto.setRetryTime((u_int) -1);
336     jproto.setMaxTries(SNPP_DEFRETRIES);
337     jproto.setMaxDials(SNPP_DEFREDIALS);
338     jproto.setServiceLevel(SNPP_DEFLEVEL);
339     jproto.setMailbox("");
340 }
341 
342 void
resetConfig()343 SNPPClient::resetConfig()
344 {
345     setupConfig();
346 }
347 
348 void
configError(const char * fmt...)349 SNPPClient::configError(const char* fmt ...)
350 {
351     va_list ap;
352     va_start(ap, fmt);
353     vprintError(fmt, ap);
354     va_end(ap);
355 }
356 
357 void
configTrace(const char * fmt...)358 SNPPClient::configTrace(const char* fmt ...)
359 {
360     if (getVerbose()) {
361 	va_list ap;
362 	va_start(ap, fmt);
363 	vprintWarning(fmt, ap);
364 	va_end(ap);
365     }
366 }
367 
368 bool
setConfigItem(const char * tag,const char * value)369 SNPPClient::setConfigItem(const char* tag, const char* value)
370 {
371     u_int ix;
372     if (findTag(tag, (const tags*) strings, N(strings), ix)) {
373 	(*this).*strings[ix].p = value;
374     } else if (findTag(tag, (const tags*) numbers, N(numbers), ix)) {
375 	(*this).*numbers[ix].p = getNumber(value);
376     } else if (streq(tag, "verbose")) {
377 	if (getBoolean(value))
378 	    state |= SS_VERBOSE;
379 	else
380 	    state &= ~SS_VERBOSE;
381     } else if (streq(tag, "queuesend")) {
382 	jproto.setQueued(getBoolean(value));
383     } else if (streq(tag, "notify") || streq(tag, "notification")) {
384 	jproto.setNotification(value);
385     } else if (streq(tag, "holdtime")) {
386 	fxStr emsg;
387 	if (!jproto.setHoldTime(tag, emsg))
388 	    configError(NLS::TEXT("Invalid hold time \"%s\": %s"),
389 		value, (const char*) emsg);
390     } else if (streq(tag, "retrytime")) {
391 	jproto.setRetryTime(value);
392     } else if (streq(tag, "maxtries")) {
393 	jproto.setMaxTries(getNumber(value));
394     } else if (streq(tag, "maxdials")) {
395 	jproto.setMaxDials(getNumber(value));
396     } else if (streq(tag, "servicelevel")) {
397 	jproto.setServiceLevel(getNumber(value));
398     } else if (streq(tag, "mailaddr")) {
399 	jproto.setMailbox(value);
400     } else if (streq(tag, "passivemode")) {
401 	pasv = getBoolean(value);
402     } else
403 	return (false);
404     return (true);
405 }
406 
407 bool
callServer(fxStr & emsg)408 SNPPClient::callServer(fxStr& emsg)
409 {
410     if (host.length() == 0) {		// if host not specified by -h
411 	const char* cp = getenv("SNPPSERVER");
412 	if (cp && *cp != '\0') {
413 	    if (modem != "") {		// don't clobber specified modem
414 		fxStr m(modem);
415 		setupHostModem(cp);
416 		modem = m;
417 	    } else
418 		setupHostModem(cp);
419 	} else				// use default host
420 	    host = SNPP_DEFHOST;
421     }
422     if (callInetServer(emsg)) {
423 	signal(SIGPIPE, fxSIGHANDLER(SIG_IGN));
424 	/*
425 	 * Transport code is expected to call back through
426 	 * setCtrlFds so fdIn should be properly setup...
427 	 */
428 	return (fdIn != NULL && getReply(false) == COMPLETE);
429     } else
430 	return (false);
431 }
432 
433 #if CONFIG_INETTRANSPORT
434 #include "Socket.h"
435 
436 extern "C" {
437 #include <arpa/inet.h>
438 #include <arpa/telnet.h>
439 #include <netinet/in.h>
440 #include <netinet/in_systm.h>
441 #include <netinet/ip.h>
442 #include <netdb.h>
443 }
444 
445 bool
callInetServer(fxStr & emsg)446 SNPPClient::callInetServer(fxStr& emsg)
447 {
448     fxStr proto(getProtoName());
449     char* cp;
450     if ((cp = getenv("SNPPSERVICE")) && *cp != '\0') {
451 	fxStr s(cp);
452 	u_int l = s.next(0,'/');
453 	port = (u_int) (int) s.head(l);
454 	if (l < s.length())
455 	    proto = s.tail(s.length()-(l+1));
456     }
457     struct hostent* hp = Socket::gethostbyname(getHost());
458     if (!hp) {
459 	emsg = getHost() | NLS::TEXT(": Unknown host");
460 	return (false);
461     }
462     int protocol;
463     const char* cproto = proto;			// XXX for busted include files
464     struct protoent* pp = getprotobyname(cproto);
465     if (!pp) {
466 	printWarning(NLS::TEXT("%s: No protocol definition, using default."), cproto);
467 	protocol = 0;
468     } else
469 	protocol = pp->p_proto;
470     int fd = socket(hp->h_addrtype, SOCK_STREAM, protocol);
471     if (fd < 0) {
472 	emsg = NLS::TEXT("Can not create socket to connect to server.");
473 	return (false);
474     }
475     struct sockaddr_in sin;
476     memset(&sin, 0, sizeof (sin));
477     sin.sin_family = hp->h_addrtype;
478     if (port == (u_int) -1) {
479 	struct servent* sp = getservbyname(SNPP_SERVICE, cproto);
480 	if (!sp) {
481 	    if (!isdigit(cproto[0])) {
482 		printWarning(
483 		    NLS::TEXT("No \"%s\" service definition, using default %u/%s."),
484 		    SNPP_SERVICE, SNPP_DEFPORT, cproto);
485 		sin.sin_port = htons(SNPP_DEFPORT);
486 	    } else
487 		sin.sin_port = htons(atoi(cproto));
488 	} else
489 	    sin.sin_port = sp->s_port;
490     } else
491 	sin.sin_port = htons(port);
492     for (char** cpp = hp->h_addr_list; *cpp; cpp++) {
493 	memcpy(&sin.sin_addr, *cpp, hp->h_length);
494 	if (getVerbose())
495 	    traceServer(NLS::TEXT("Trying %s (%s) at port %u..."),
496 		(const char*) getHost(),
497 		inet_ntoa(sin.sin_addr),
498 		ntohs(sin.sin_port));
499 	if (Socket::connect(fd, &sin, sizeof (sin)) >= 0) {
500 	    if (getVerbose())
501 		traceServer(NLS::TEXT("Connected to %s."), hp->h_name);
502 #if defined(IP_TOS) && defined(IPTOS_LOWDELAY)
503 	    int tos = IPTOS_LOWDELAY;
504 	    if (Socket::setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)) < 0)
505 		printWarning(NLS::TEXT("setsockopt(TOS): %s (ignored)"),
506 		    strerror(errno));
507 #endif
508 #ifdef SO_OOBINLINE
509 	    int on = 1;
510 	    if (Socket::setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &on, sizeof (on)) < 0)
511 		printWarning(NLS::TEXT("setsockopt(OOBLINE): %s (ignored)"),
512 		    strerror(errno));
513 #endif
514 	    setCtrlFds(fd, fd);
515 	    return (true);
516 	}
517     }
518     emsg = fxStr::format(NLS::TEXT("Can not reach server at host \"%s\", port %u."),
519 	(const char*) getHost(), ntohs(sin.sin_port));
520     close(fd), fd = -1;
521     return (false);
522 }
523 #else
524 bool
callInetServer(fxStr & emsg)525 SNPPServer::callInetServer(fxStr& emsg)
526 {
527     emsg = NLS::TEXT("Sorry, no TCP/IP communication support was configured.");
528     return (false);
529 }
530 #endif
531 
532 bool
hangupServer(void)533 SNPPClient::hangupServer(void)
534 {
535     if (fdIn != NULL)
536 	fclose(fdIn), fdIn = NULL;
537     if (fdOut != NULL)
538 	fclose(fdOut), fdOut = NULL;
539     initServerState();
540     return (true);
541 }
542 
543 void
setCtrlFds(int in,int out)544 SNPPClient::setCtrlFds(int in, int out)
545 {
546     if (fdIn != NULL)
547 	fclose(fdIn);
548     fdIn = fdopen(in, "r");
549     if (fdOut != NULL)
550 	fclose(fdOut);
551     fdOut = fdopen(out, "w");
552 }
553 
554 /*
555  * Do login procedure.
556  */
557 bool
login(const char * user,fxStr & emsg)558 SNPPClient::login(const char* user, fxStr& emsg)
559 {
560     if (user == NULL) {
561 	setupSenderIdentity(emsg);		// invokes setupUserIdentity
562 	user = userName;
563     }
564     int n = command("LOGI %s", user);
565     if (code == 550)
566 	n = command("LOGI %s %s", user, getPasswd("Password:"));
567     if (n == COMPLETE)
568 	state |= SS_LOGGEDIN;
569     else
570 	state &= ~SS_LOGGEDIN;
571     if (isLoggedIn()) {
572 	if (command("SITE HELP NOTIFY") == COMPLETE)
573 	    state |= SS_HASSITE;
574 	else
575 	    state &= ~SS_HASSITE;
576 	return (true);
577     } else {
578 	emsg = NLS::TEXT("Login failed: ") | lastResponse;
579 	return (false);
580     }
581 }
582 
583 /*
584  * Prompt for a password.
585  */
586 const char*
getPasswd(const char * prompt)587 SNPPClient::getPasswd(const char* prompt)
588 {
589     return (getpass(prompt));
590 }
591 
592 void
lostServer(void)593 SNPPClient::lostServer(void)
594 {
595     printError(NLS::TEXT("Service not available, remote server closed connection"));
596     hangupServer();
597 }
598 
599 void
unexpectedResponse(fxStr & emsg)600 SNPPClient::unexpectedResponse(fxStr& emsg)
601 {
602     emsg = NLS::TEXT("Unexpected server response: ") | lastResponse;
603 }
604 
605 void
protocolBotch(fxStr & emsg,const char * fmt...)606 SNPPClient::protocolBotch(fxStr& emsg, const char* fmt ...)
607 {
608     va_list ap;
609     va_start(ap, fmt);
610     emsg = NLS::TEXT("Protocol botch") | fxStr::vformat(fmt, ap);
611     va_end(ap);
612 }
613 
614 /*
615  * Send a command and wait for a response.
616  * The primary response code is returned.
617  */
618 int
command(const char * fmt...)619 SNPPClient::command(const char* fmt ...)
620 {
621     va_list ap;
622     va_start(ap, fmt);
623     int r = vcommand(fmt, ap);
624     va_end(ap);
625     return (r);
626 }
627 
628 /*
629  * Send a command and wait for a response.
630  * The primary response code is returned.
631  */
632 int
vcommand(const char * fmt,va_list ap)633 SNPPClient::vcommand(const char* fmt, va_list ap)
634 {
635     if (getVerbose()) {
636 	if (strncasecmp("LOGI", fmt, 4) == 0)
637 	    traceServer("-> LOGI XXXX");
638 	else {
639         fxStr f("-> ");
640         f.append(fmt);
641 	    vtraceServer(f, ap);
642 	}
643     }
644     if (fdOut == NULL) {
645 	printError(NLS::TEXT("No control connection for command"));
646 	code = -1;
647 	return (0);
648     }
649     vfprintf(fdOut, fmt, ap);
650     fputs("\r\n", fdOut);
651     (void) fflush(fdOut);
652     int r = getReply(strncmp(fmt, "QUIT", 4) == 0);
653     return (r);
654 }
655 
656 /*
657  * Extract a valid reply code from a string.
658  * The code must be 3 numeric digits followed
659  * by a space or ``-'' (the latter indicates
660  * the server reponse is to be continued with
661  * one or more lines).  If no valid reply code
662  * is recognized zero is returned--this is
663  * assumed to be an invalid reply code.
664  */
665 static int
getReplyCode(const char * cp)666 getReplyCode(const char* cp)
667 {
668     if (!isdigit(cp[0]))
669 	return (0);
670     int c = (cp[0] - '0');
671     if (!isdigit(cp[1]))
672 	return (0);
673     c = 10*c + (cp[1]-'0');
674     if (!isdigit(cp[2]))
675 	return (0);
676     c = 10*c + (cp[2]-'0');
677     return ((cp[3] == ' ' || cp[3] == '-') ? c : 0);
678 }
679 
680 /*
681  * Read from the control channel until a valid reply is
682  * received or the connection is dropped.  The last line
683  * of the received response is left in SNPPClient::lastResponse
684  * and the reply code is left in SNPPClient::code.  The
685  * primary response (the first digit of the reply code)
686  * is returned to the caller.  Continuation lines are
687  * handled but not collected.
688  */
689 int
getReply(bool expecteof)690 SNPPClient::getReply(bool expecteof)
691 {
692     int firstCode = 0;
693     bool continuation = false;
694     do {
695 	lastResponse.resize(0);
696 	int c;
697 	while ((c = getc(fdIn)) != '\n') {
698 	    if (c == IAC) {     // handle telnet commands
699 		switch (c = getc(fdIn)) {
700 		case WILL:
701 		case WONT:
702 		    c = getc(fdIn);
703 		    fprintf(fdOut, "%c%c%c", IAC, DONT, c);
704 		    (void) fflush(fdOut);
705 		    break;
706 		case DO:
707 		case DONT:
708 		    c = getc(fdIn);
709 		    fprintf(fdOut, "%c%c%c", IAC, WONT, c);
710 		    (void) fflush(fdOut);
711 		    break;
712 		default:
713 		    break;
714 		}
715 		continue;
716 	    }
717 	    if (c == EOF) {
718 		if (expecteof) {
719 		    code = 221;
720 		    return (0);
721 		} else {
722 		    lostServer();
723 		    code = 421;
724 		    return (4);
725 		}
726 	    }
727 	    if (c != '\r')
728 		lastResponse.append(c);
729 	}
730 	if (getVerbose())
731 	    traceServer("%s", (const char*) lastResponse);
732 	code = getReplyCode(lastResponse);
733 	if (code != 0) {			// found valid reply code
734 	    if (lastResponse[3] == '-') {	// continuation line
735 		if (firstCode == 0)		// first line of reponse
736 		    firstCode = code;
737 		continuation = true;
738 	    } else if (code == firstCode)	// end of continued reply
739 		continuation = false;
740 	}
741     } while (continuation || code == 0);
742 
743     if (code == 421)				// server closed connection
744 	lostServer();
745     return (code/100);
746 }
747 
748 /*
749  * Extract a string from a reply message.  The
750  * string that is extracted is expected to follow
751  * a pattern string.  The pattern is tried both
752  * in the initial case and then the inverse case
753  * (upper or lower depending on what the original
754  * case was).  The resulting string is checked to
755  * make sure that it is not null.
756  */
757 bool
extract(u_int & pos,const char * pattern,fxStr & result)758 SNPPClient::extract(u_int& pos, const char* pattern, fxStr& result)
759 {
760     fxStr pat(pattern);
761     u_int l = lastResponse.find(pos, pat);
762     if (l == lastResponse.length()) {		// try inverse-case version
763 	if (isupper(pattern[0]))
764 	    pat.lowercase();
765 	else
766 	    pat.raisecase();
767 	l = lastResponse.find(pos, pat);
768     }
769     if (l == lastResponse.length())
770 	return (false);
771     l = lastResponse.skip(l+pat.length(), ' ');// skip white space
772     result = lastResponse.extract(l, lastResponse.next(l, ' ')-l);
773     if (result == "")
774 	return (false);
775     pos = l;					// update position
776     return (true);
777 }
778 
779 /*
780  * Create a new job and return its job-id if
781  * parsed from the reply (this is for HylaFAX,
782  * RFC 1861 says nothing about this).
783  */
784 bool
newPage(const fxStr & pin,const fxStr & passwd,fxStr & jobid,fxStr & emsg)785 SNPPClient::newPage(const fxStr& pin, const fxStr& passwd, fxStr& jobid, fxStr& emsg)
786 {
787     int result;
788     if (passwd != "")
789 	result = command("PAGE %s %s", (const char*) pin, (const char*) passwd);
790     else
791 	result = command("PAGE %s", (const char*) pin);
792     if (result == COMPLETE) {
793 	if (code == 250) {
794 	    /*
795 	     * If the server is hfaxd, then the response should be
796 	     * of the form:
797 	     *
798 	     * 250 ... jobid: xxxx.
799 	     *
800 	     * where xxxx is the ID for the new job.
801 	     */
802 	    u_int l = 0;
803 	    if (extract(l, "jobid:", jobid)) {
804 		/*
805 		 * Force job IDs to be numeric;
806 		 * this deals with servers that want to append
807 		 * punctuation such as ``,'' or ``.''.
808 		 */
809 		jobid.resize(jobid.skip(0, "0123456789"));
810 	    } else
811 		jobid = "unknown";
812 	    return (true);
813 	} else
814 	    unexpectedResponse(emsg);
815     } else
816 	emsg = lastResponse;
817     return (false);
818 }
819 
820 SNPPJob&
addJob(void)821 SNPPClient::addJob(void)
822 {
823     u_int ix = jobs->length();
824     jobs->resize(ix+1);
825     (*jobs)[ix] = jproto;
826     return ((*jobs)[ix]);
827 }
getNumberOfJobs() const828 u_int SNPPClient::getNumberOfJobs() const	{ return jobs->length(); }
829 
830 SNPPJob*
findJob(const fxStr & pin)831 SNPPClient::findJob(const fxStr& pin)
832 {
833     for (u_int i = 0, n = jobs->length(); i < n; i++) {
834 	SNPPJob& job = (*jobs)[i];
835 	if (job.getPIN() == pin)
836 	    return (&job);
837     }
838     return (NULL);
839 }
840 
841 void
removeJob(const SNPPJob & job)842 SNPPClient::removeJob(const SNPPJob& job)
843 {
844     u_int ix = jobs->find(job);
845     if (ix != fx_invalidArrayIndex)
846 	jobs->remove(ix);
847 }
848 
849 bool
prepareForJobSubmissions(fxStr &)850 SNPPClient::prepareForJobSubmissions(fxStr&)
851 {
852     // XXX nothing to do right now
853     return (true);
854 }
855 
856 bool
submitJobs(fxStr & emsg)857 SNPPClient::submitJobs(fxStr& emsg)
858 {
859     if (!isLoggedIn()) {
860 	emsg = NLS::TEXT("Not logged in to server");
861 	return (false);
862     }
863     /*
864      * Construct jobs and submit them.
865      */
866     for (u_int i = 0, n = jobs->length(); i < n; i++) {
867 	SNPPJob& job = (*jobs)[i];
868 	if (!job.createJob(*this, emsg))
869 	    return (false);
870 	notifyNewJob(job);			// notify client
871     }
872     if (msgFile != "") {
873 	if (!sendData(msgFile, emsg))
874 	    return (false);
875     } else if (msg) {
876 	if (!sendMsg(*msg, emsg))
877 	    return (false);
878     }
879     if (command("SEND") != COMPLETE) {
880 	emsg = lastResponse;
881 	return (false);
882     } else
883 	return (true);
884 }
885 
886 /*
887  * Default notification handler for when a new job is created.
888  */
889 void
notifyNewJob(const SNPPJob & job)890 SNPPClient::notifyNewJob(const SNPPJob& job)
891 {
892     printf(NLS::TEXT("destination pin %s: request id is %s for host %s\n")
893 	, (const char*) job.getPIN()
894 	, (const char*) job.getJobID()
895 	, (const char*) getHost()
896     );
897 }
898 
899 /*
900  * Send a block of raw data on the data
901  * conenction, interpreting write errors.
902  */
903 bool
sendRawData(void * buf,int cc,fxStr & emsg)904 SNPPClient::sendRawData(void* buf, int cc, fxStr& emsg)
905 {
906 #ifdef __linux__
907     /*
908      * Linux kernel bug: can get short writes on
909      * stream sockets when setup for blocking i/o.
910      */
911     u_char* bp = (u_char*) buf;
912     for (int cnt, sent = 0; cc; sent += cnt, cc -= cnt)
913 	if ((cnt = write(fileno(fdOut), bp + sent, cc)) <= 0) {
914 	    protocolBotch(emsg, errno == EPIPE ?
915 		NLS::TEXT(" (server closed connection)") : NLS::TEXT(" (server write error: %s)."),
916 		strerror(errno));
917 	    return (false);
918 	}
919 #else
920     if (write(fileno(fdOut), buf, cc) != cc) {
921 	protocolBotch(emsg, errno == EPIPE ?
922 	    NLS::TEXT(" (server closed connection)") : NLS::TEXT(" (server write error: %s)."),
923 	    strerror(errno));
924 	return (false);
925     }
926 #endif
927     return (true);
928 }
929 
930 bool
sendData(int fd,fxStr & emsg)931 SNPPClient::sendData(int fd, fxStr& emsg)
932 {
933     struct stat sb;
934     (void) Sys::fstat(fd, sb);
935     if (getVerbose())
936 	traceServer(NLS::TEXT("SEND message data, %lu bytes"), (u_long) sb.st_size);
937     if (command("DATA") == CONTINUE) {
938 	size_t cc = (size_t) sb.st_size;
939 	while (cc > 0) {
940 	    char buf[32*1024];
941 	    size_t n = fxmin(cc, sizeof (buf));
942 	    if (read(fd, buf, n) != (ssize_t) n) {
943 		protocolBotch(emsg, NLS::TEXT(" (data read: %s)."), strerror(errno));
944 		return (false);
945 	    }
946 	    if (!sendRawData(buf, n, emsg))
947 		return (false);
948 	    cc -= n;
949 	}
950 	if (command(".") == COMPLETE)
951 	    return (true);
952     }
953     emsg = getLastResponse();
954     return (false);
955 }
956 
957 bool
sendData(const fxStr & filename,fxStr & emsg)958 SNPPClient::sendData(const fxStr& filename, fxStr& emsg)
959 {
960     bool ok = false;
961     int fd = Sys::open(filename, O_RDONLY);
962     if (fd >= 0) {
963 	ok = sendData(fd, emsg);
964 	Sys::close(fd);
965     } else
966 	emsg = fxStr::format(NLS::TEXT("Unable to open message file \"%s\"."),
967 	    (const char*) filename);
968     return (ok);
969 }
970 
971 bool
sendMsg(const char * msg,fxStr & emsg)972 SNPPClient::sendMsg(const char* msg, fxStr& emsg)
973 {
974     if (command("MESS %s", msg) != COMPLETE) {
975 	emsg = getLastResponse();
976 	return (false);
977     } else
978 	return (true);
979 }
980 
981 bool
siteParm(const char * name,const fxStr & value)982 SNPPClient::siteParm(const char* name, const fxStr& value)
983 {
984     if (!hasSiteCmd()) {
985 	printWarning(NLS::TEXT("no SITE %s support; ignoring set request."), name);
986 	return (true);
987     } else
988 	return (command("SITE %s %s", name, (const char*) value) == COMPLETE);
989 }
990 
991 bool
siteParm(const char * name,u_int value)992 SNPPClient::siteParm(const char* name, u_int value)
993 {
994     if (!hasSiteCmd()) {
995 	printWarning(NLS::TEXT("no SITE %s support; ignoring set request."), name);
996 	return (true);
997     } else
998 	return (command("SITE %s %u", name, value) == COMPLETE);
999 }
1000 
1001 bool
setHoldTime(u_int t)1002 SNPPClient::setHoldTime(u_int t)
1003 {
1004     time_t tv = t;
1005     struct tm* tm = gmtime(&tv);
1006     return (command("HOLD %02d%02d%02d%02d%02d"
1007 	, (tm->tm_year) % 100
1008 	, tm->tm_mon+1
1009 	, tm->tm_mday
1010 	, tm->tm_hour
1011 	, tm->tm_min) == COMPLETE);
1012 }
1013 
1014 bool
setRetryTime(u_int t)1015 SNPPClient::setRetryTime(u_int t)
1016 {
1017     return siteParm("RETRYTIME", fxStr::format("%02d%02d", t/60, t%60));
1018 }
1019