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