SNPPServer.c++
上传用户:weiyuanprp
上传日期:2020-05-20
资源大小:1169k
文件大小:40k
源码类别:

传真(Fax)编程

开发平台:

C/C++

  1. /* $Id: SNPPServer.c++,v 1.8 2008/04/26 22:34:29 faxguy Exp $ */
  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. #ifdef SNPP_SUPPORT
  27. /*
  28.  * Simple Network Paging Protocol (SNPP) Support.
  29.  */
  30. #include "config.h"
  31. #include "Sys.h"
  32. #include "Socket.h"
  33. #include "SNPPServer.h"
  34. #include "Dispatcher.h"
  35. #include "RE.h"
  36. #include <ctype.h>
  37. #if HAS_CRYPT_H
  38. #include <crypt.h>
  39. #endif
  40. extern "C" {
  41. #include <netdb.h>
  42. #include <arpa/inet.h>
  43. #include <netinet/in_systm.h>
  44. #include <netinet/ip.h>
  45. }
  46. SNPPSuperServer::SNPPSuperServer(const char* p, int bl)
  47.     : SuperServer("SNPP", bl)
  48.     , port(p)
  49. {}
  50. SNPPSuperServer::~SNPPSuperServer() {}
  51. bool
  52. SNPPSuperServer::startServer(void)
  53. {
  54.     /*
  55.      * Switch to super-user to do the bind in case
  56.      * we are to use a port in the privileged region
  57.      * on a BSD system (ports <=1024 are reserved
  58.      * and the default SNPP port is 444).
  59.      *
  60.      * NB: We do it for both the socket+bind calls
  61.      *     to workaround a bug in Solaris 2.5.
  62.      */
  63.     uid_t ouid = geteuid();
  64.     (void) seteuid(0);
  65.     int s = socket(AF_INET, SOCK_STREAM, 0);
  66.     if (s >= 0) {
  67. struct sockaddr_in sin;
  68. memset(&sin, 0, sizeof (sin));
  69. sin.sin_family = AF_INET;
  70. const char* cp = port;
  71. struct servent* sp = getservbyname(cp, SNPP_PROTONAME);
  72. if (!sp) {
  73.     if (isdigit(cp[0]))
  74. sin.sin_port = htons(atoi(cp));
  75.     else
  76. sin.sin_port = htons(SNPP_DEFPORT);
  77. } else
  78.     sin.sin_port = sp->s_port;
  79. if (Socket::bind(s, &sin, sizeof (sin)) >= 0) {
  80.     (void) listen(s, getBacklog());
  81.     (void) seteuid(ouid);
  82.     Dispatcher::instance().link(s, Dispatcher::ReadMask, this);
  83.     return (true); // success
  84. }
  85. Sys::close(s);
  86. logError("HylaFAX %s: bind (port %u): %m",
  87.     getKind(), ntohs(sin.sin_port));
  88.     } else
  89. logError("HylaFAX %s: socket: %m", getKind());
  90.     (void) seteuid(ouid);
  91.     return (false);
  92. }
  93. HylaFAXServer* SNPPSuperServer::newChild(void) { return new SNPPServer; }
  94. SNPPServer::SNPPServer() {}
  95. SNPPServer::~SNPPServer() {}
  96. void
  97. SNPPServer::open(void)
  98. {
  99.     setupNetwork(STDIN_FILENO);
  100.     initServer(); // complete state initialization
  101.     fxStr emsg;
  102.     if (!initClientFIFO(emsg)) {
  103.         logInfo("connection refused (%s) from %s [%s]",
  104.     (const char*) emsg,
  105.     (const char*) remotehost, (const char*) remoteaddr);
  106. reply(420, "%s server cannot initialize: %s",
  107.     (const char*) hostname, (const char*) emsg);
  108. dologout(-1);
  109.     }
  110.     ctrlFlags = fcntl(STDIN_FILENO, F_GETFL); // for parser
  111.     if (isShutdown(true)) {
  112. reply(421, "%s SNPP server unavailable; try again later.",
  113.     (const char*) hostname);
  114. dologout(-1);
  115.     }
  116.     reply(220, "%s SNPP server (%s) ready.", (const char*) hostname, version);
  117.     if (TRACE(CONNECT))
  118.         logInfo("SNPP connection from %s [%s]",
  119.     (const char*) remotehost, (const char*) remoteaddr);
  120. }
  121. void
  122. SNPPServer::initServer(void)
  123. {
  124.     InetFaxServer::initServer();
  125.     state &= ~S_USEGMT; // SNPP uses local times
  126.     resetState();
  127. }
  128. void
  129. SNPPServer::initDefaultJob(void)
  130. {
  131.     InetFaxServer::initDefaultJob();
  132.     defJob.jobtype = "pager";
  133.     initSNPPJob();
  134. }
  135. void
  136. SNPPServer::initSNPPJob(void)
  137. {
  138.     defJob.queued = false; // jobs not queued--per protocol
  139.     defJob.maxdials = SNPP_DEFREDIALS; // configuration defaults...
  140.     defJob.maxtries = SNPP_DEFRETRIES;
  141.     defJob.killtime = 60*killMap[SNPP_DEFLEVEL];
  142.     defJob.retrytime = retryMap[SNPP_DEFLEVEL];
  143.     defJob.usrpri = priMap[SNPP_DEFLEVEL];
  144.     // XXX default notification
  145. }
  146. void
  147. SNPPServer::resetState(void)
  148. {
  149.     initDefaultJob(); // default job state
  150.     msgFile = "";
  151.     haveText = false; // no message text yet
  152.     msgs.resize(0); // purge any message refs
  153. }
  154. void
  155. SNPPServer::dologout(int status)
  156. {
  157.     /*
  158.      * Purge any partially constructed jobs.  If we were
  159.      * doing a SEND when the connection was dropped then
  160.      * we do not purge jobs but instead just let them run
  161.      * detached.  This may not be the best thing; we might
  162.      * instead want to kill the jobs--we'll need to think
  163.      * about this some more before changing this behaviour.
  164.      */
  165.     if (msgs.length() > 0 && !IS(SENDWAIT)) {
  166. fxStr emsg;
  167. for (u_int i = 0, n = msgs.length(); i < n; i++) {
  168.     Job* job = findJob(msgs[i], emsg);
  169.     if (job) {
  170. for (u_int j = 0, m = job->items.length(); j < m; j++)
  171.     if (job->items[j].op == FaxRequest::send_data)
  172. (void) Sys::unlink(job->items[j].item);
  173. (void) Sys::unlink(job->qfile);
  174. delete job;
  175.     }
  176. }
  177.     }
  178.     InetFaxServer::dologout(status);
  179. }
  180. static inline bool
  181. isMagic(char c)
  182. {
  183.     return (c == '[' || c == ']' || c == '*' || c == '.' || c == '^' ||
  184. c == '$' || c == '-' || c == '+' || c == '{' || c == '}' ||
  185. c == '(' || c == ')');
  186. }
  187. /*
  188.  * Handle escapes for a pager ID replacement string.
  189.  */
  190. static void
  191. subRHS(fxStr& result, const RE& re, const fxStr& match)
  192. {
  193.     /*
  194.      * Do ``&'' and ``n'' interpolations in the replacement.
  195.      */
  196.     for (u_int i = 0, n = result.length(); i < n; i++) {
  197. if (result[i] == '\') { // process <char> escapes
  198.     result.remove(i), n--;
  199.     if (isdigit(result[i])) {
  200. int mn = result[i] - '0';
  201. int ms = re.StartOfMatch(mn);
  202. int mlen = re.EndOfMatch(mn) - ms;
  203. result.remove(i); // delete n
  204. if (ms >= 0)
  205.     result.insert(match.extract(ms, mlen), i);
  206. else
  207.     logError("Invalid backreference in pagermap: \%d", mn);
  208. n = result.length(); // adjust string length ...
  209. i += mlen - 1; // ... and scan index
  210.     }
  211. } else if (result[i] == '&') { // process & replacement
  212.     int ms = re.StartOfMatch(0);
  213.     int mlen = re.EndOfMatch(0) - ms;
  214.     result.remove(i); // delete &
  215.     result.insert(match.extract(ms, mlen), i);
  216.     n = result.length(); // adjust string length ...
  217.     i += mlen - 1; // ... and scan index
  218. }
  219.     }
  220. }
  221. /*
  222.  * Given a client-supplied pager ID return the phone number
  223.  * to dial of the service provider and the PIN to supply
  224.  * to the provider when talking IXO/TAP (for aliases).
  225.  */
  226. bool
  227. SNPPServer::mapPagerID(const char* pagerID, fxStr& number, fxStr& pin, fxStr& emsg)
  228. {
  229.     if (pagerIDMapFile != "") {
  230. FILE* fd = fopen(fixPathname(pagerIDMapFile), "r");
  231. if (fd != NULL) {
  232.     char buf[1024];
  233.     while (fgets(buf, sizeof (buf), fd)) {
  234. char* cp;
  235. for (cp = buf; isspace(*cp); cp++) // leading whitespace
  236.     ;
  237. if (*cp == '#' || *cp == '')
  238.     continue;
  239. /*
  240.  * Syntax is:
  241.  *
  242.  * <pattern> <dialstring>[/<PIN>]
  243.  *
  244.  * where <pattern> can be a simple string of alpha
  245.  * numerics or a regular expression.  The first line
  246.  * that matches the client-specified pager ID is used.
  247.  * If no <PIN> is specified then the client-specified
  248.  * string is sent to the provider.  If <dialstring>
  249.  * is "reject" (verbatim) then matches are rejected.
  250.  * <PIN> is treated as the RHS of an RE-style substitution:
  251.  * n and & escapes are replaced according to the RE
  252.  * matching work.
  253.  *
  254.  * Leading white space on a line is ignored.  Lines
  255.  * that begin with '#' are ignored (i.e. comments).
  256.  */
  257. const char* pattern = cp;
  258. bool isRE = false;
  259. for (; *cp != '' && !isspace(*cp); cp++)
  260.     if (isMagic(*cp))
  261. isRE = true;
  262. if (*cp != '') // -term. <pattern>
  263.     *cp++ = '';
  264. bool match;
  265. RE* re;
  266. if (isRE) {
  267.     re = new RE(pattern);
  268.     match = re->Find(pagerID, strlen(pagerID));
  269. } else {
  270.     match = streq(pattern, pagerID);
  271.     re = NULL;
  272. }
  273. if (match) {
  274.     while (isspace(*cp)) // leading whitespace
  275. cp++;
  276.     if (*cp != 'n' && *cp != '') { // got <dialstring>
  277. char* np = strchr(cp, 'n'); // remove trailing n
  278. if (np)
  279.     *np = '';
  280. np = cp;
  281. for (; *cp && *cp != '/'; cp++)
  282.     ;
  283. if (*cp == '/') { // <dialstring>/<PIN>
  284.     *cp++ = '';
  285.     number = np;
  286.     pin = cp;
  287.     if (re) { // do substitutions
  288.     subRHS(pin, *re, pagerID);
  289.                     subRHS(number, *re, pagerID);
  290.                 }
  291. } else { // <dialstring>
  292.     number = np;
  293.     pin = pagerID;
  294. }
  295. delete re;
  296. (void) fclose(fd);
  297. fxStr s(number);
  298. s.raisecase();
  299. if (s == "REJECT") {
  300.     emsg = fxStr::format("Invalid pager ID %s",pagerID);
  301.     return (false);
  302. } else
  303.     return (true);
  304.     }
  305. }
  306. delete re;
  307.     }
  308.     (void) fclose(fd);
  309.     emsg = fxStr::format("Unknown or illegal pager ID %s", pagerID);
  310. } else {
  311.     emsg = fxStr::format("Cannot open pager ID mapping file %s (%s)", 
  312. (const char*) pagerIDMapFile, strerror(errno));
  313.     logError("%s", (const char*) emsg);
  314. }
  315.     } else {
  316. emsg = "No pager ID mapping file found";
  317. logError("%s", (const char*) emsg);
  318.     }
  319.     return (false);
  320. }
  321. /*
  322.  * SNPP Parser.
  323.  */
  324. #define N(a) (sizeof (a) / sizeof (a[0]))
  325. /*
  326.  * Standard protocol commands.
  327.  */
  328. static const tab cmdtab[] = {
  329. { "2WAY", T_2WAY,  true,false, "(preface 2-way transaction)" },
  330. { "ABOR", T_ABOR, false, true, "(abort operation)" },
  331. { "ACKR", T_ACKREAD,  true,false, "0|1" },
  332. { "ALER", T_ALERT,  true,false, "alert-level" },
  333. { "CALL", T_CALLERID,    true,false, "caller-ID" },
  334. { "COVE", T_COVERAGE,    true,false, "alternate-area" },
  335. { "DATA", T_DATA,  true, true, "(specify multi-line message)" },
  336. { "EXPT", T_EXPTAG,  true,false, "hours" },
  337. { "HELP", T_HELP, false, true, "[<string>]" },
  338. { "HOLD", T_HOLDUNTIL,   true, true, "YYYYMMDDHHMMSS [+/-GMT-diff]" },
  339. { "KTAG", T_KTAG,  true,false, "message-tag pass-code" },
  340. { "LEVE", T_LEVEL,  true, true, "service-level" },
  341. { "LOGI", T_LOGIN, false, true, "username [password]" },
  342. { "MCRE", T_MCRESPONSE,  true,false, "2-byte-code response-text" },
  343. { "MESS", T_MESSAGE,  true, true, "message" },
  344. { "MSTA", T_MSTATUS,  true,false, "message-tag pass-code" },
  345. { "NOQU", T_NOQUEUEING,  true,false, "(disable message queueing)" },
  346. { "PAGE", T_PAGER,  true, true, "pager-ID|alias [PIN]" },
  347. { "PING", T_PING,  true, true, "pager-ID|alias" },
  348. { "QUIT", T_QUIT, false, true, "(terminate service)" },
  349. { "RESE", T_REST,  true, true, "(reset state)" },
  350. { "RTYP", T_RTYPE,  true,false, "reply-type-code" },
  351. { "SEND", T_SEND,  true, true, "(send message)" },
  352. { "SITE", T_SITE,  true, true, "site-cmd [arguments]" },
  353. { "STAT", T_STAT, false, true, "(return server status)" },
  354. { "SUBJ", T_SUBJECT,  true, true, "message-subject" },
  355. };
  356. /*
  357.  * Site-specific commands.
  358.  */
  359. static const tab sitetab[] = {
  360. { "FROMUSER",   T_FROM_USER,  true, true, "[<string>]" },
  361. { "HELP",       T_HELP,      false, true, "[<string>]" },
  362. { "IDLE",       T_IDLE,       true, true, "[max-idle-timeout]" },
  363. { "JPARM",      T_JPARM,      true, true, "(print job parameter status)" },
  364. { "JQUEUE", T_JQUEUE,     true, true, "[on|off]" },
  365. { "LASTTIME",   T_LASTTIME,  false, true, "[DDHHSS]" },
  366. { "MAXDIALS",   T_MAXDIALS,   true, true, "[<number>]" },
  367. { "MAXTRIES",   T_MAXTRIES,   true, true, "[<number>]" },
  368. { "MODEM",      T_MODEM,      true, true, "[device|class]" },
  369. { "NOTIFY",     T_NOTIFY,     true, true, "[NONE|DONE|REQUEUE|DONE+REQUEUE]"},
  370. { "MAILADDR",   T_NOTIFYADDR, true, true, "[email-address]" },
  371. { "RETRYTIME",  T_RETRYTIME,  true, true, "[HHSS]" },
  372. { "SCHEDPRI",   T_SCHEDPRI,   true, true, "[<number>]" },
  373. };
  374. static const tab*
  375. lookup(const tab* p, u_int n, const char* cmd)
  376. {
  377.     while (n != 0) {
  378.         if (strneq(cmd, p->name, 4)) // NB: match on first 4 chars
  379.     return (p);
  380. p++, n--;
  381.     }
  382.     return (NULL);
  383. }
  384. static const char*
  385. tokenName(const tab* p, u_int n, Token t)
  386. {
  387.     while (n != 0) {
  388. if (p->token == t)
  389.     return (p->name);
  390. p++, n--;
  391.     }
  392.     return ("???");
  393. }
  394. const char*
  395. SNPPServer::cmdToken(Token t)
  396. {
  397.     return tokenName(cmdtab, N(cmdtab), t);
  398. }
  399. const char*
  400. SNPPServer::siteToken(Token t)
  401. {
  402.     return tokenName(sitetab, N(sitetab), t);
  403. }
  404. bool
  405. SNPPServer::checklogin(Token t)
  406. {
  407.     if (!IS(LOGGEDIN)) {
  408. cmdFailure(t, "not logged in");
  409. reply(530, "Please login with LOGIN.");
  410. return (false);
  411.     } else
  412. return (true);
  413. }
  414. /*
  415.  * Parse and process command input received on the
  416.  * control channel.  This method is invoked whenever
  417.  * data is present on the control channel.  We read
  418.  * everything and parse (and execute) as much as possible
  419.  * but do not block waiting for more data except when
  420.  * a partial line of input is received.  This is done
  421.  * to ensure other processing will be handled in a
  422.  * timely fashion (e.g. processing of messages from
  423.  * the scheduler received via the FIFO).
  424.  */
  425. int
  426. SNPPServer::parse()
  427. {
  428.     if (IS(WAITDATA)) { // recursive invocation
  429. state &= ~S_WAITDATA;
  430. return (0);
  431.     }
  432.     if (IS(SENDWAIT)) { // waiting in SEND, discard data
  433. while (getCmdLine(cbuf, sizeof (cbuf)))
  434.     ;
  435. return (0);
  436.     }
  437. // stop control channel idle timeout
  438.     Dispatcher::instance().stopTimer(this);
  439.     pushToken(T_NIL); // reset state
  440.     for (;;) {
  441. /*
  442.  * Fetch the next complete line of input received on the
  443.  * control channel.  This call will fail when no more data
  444.  * is *currently* waiting on the control channel.  Note
  445.  * that this does not mean the connection has dropped; just
  446.  * that data is not available at this instant.  Note also
  447.  * that if a partial line of input is received a complete
  448.  * line will be waited for (see below).
  449.  */
  450. if (!getCmdLine(cbuf, sizeof (cbuf)))
  451.     break;
  452. /*
  453.  * Parse the line of input read above.
  454.  */
  455. if (getToken(T_STRING, "command token")) {
  456.     tokenBody.raisecase();
  457.     const tab* p = lookup(cmdtab, N(cmdtab), tokenBody);
  458.     if (p == NULL)
  459. reply(500, "%s: Command not recognized.",
  460.     (const char*) tokenBody);
  461.     else if (!p->implemented)
  462. reply(500, "%s: Command not implemented.", p->name);
  463.     else if (isShutdown(!IS(LOGGEDIN))) {
  464. reply(421, "Server shutting down.  Goodbye.");
  465. dologout(0); // XXX
  466.     /*
  467.      * If command requires client to be logged in check
  468.      * for this.  Note that some commands have variants
  469.      * that do not require the client be logged in--these
  470.      * cannot check here and must do it specially below.
  471.      */
  472.     } else if (p->checklogin && !checklogin(p->token))
  473. ;
  474.     /*
  475.      * Command is valid, implemented, and the server
  476.      * is available to service it.  If the syntax is
  477.      * correct then reset the number of consecutive
  478.      * bad commands.  Note that since part of the syntax
  479.      * checking is login validation this logic will also
  480.      * catch people typing syntacitcally valid but
  481.      * unacceptable commands just to remain connected.
  482.      */
  483.     else if (cmd(p->token)) {
  484. consecutiveBadCmds = 0;
  485. continue;
  486.     }
  487. }
  488. /*
  489.  * If too many consecutive bad commands have been
  490.  * received disconnect.  This is to safeguard against
  491.  * a client that spews trash down the control connection.
  492.  */
  493. if (++consecutiveBadCmds >= maxConsecutiveBadCmds) {
  494.     /*
  495.      * Check for shutdown so that any shutdown message
  496.      * will get prepended to client reply.
  497.      */
  498.     (void) isShutdown(!IS(LOGGEDIN));
  499.     reply(421, "Too many errors, server shutting down.  Goodbye.");
  500.     dologout(0);
  501. }
  502.     }
  503.     Dispatcher::instance().startTimer(idleTimeout, 0, this);
  504.     return (0);
  505. }
  506. /*
  507.  * Protocol command (one line).
  508.  */
  509. bool
  510. SNPPServer::cmd(Token t)
  511. {
  512.     fxStr s;
  513.     long n;
  514.     time_t tv;
  515.     switch (t) {
  516.     case T_ABOR: // abort active command (e.g. SEND)
  517. if (CRLF()) {
  518.     logcmd(t);
  519.     ack(250, cmdToken(t));
  520.     return (true);
  521. }
  522. break;
  523.     case T_DATA: // submit multi-line message data
  524. if (CRLF()) {
  525.     logcmd(t);
  526.     dataCmd();
  527.     return (true);
  528. }
  529. break;
  530.     case T_HELP: // return help
  531. if (opt_CRLF()) {
  532.     logcmd(t);
  533.     helpCmd(cmdtab, (char*) NULL);
  534.     return (true);
  535. } else if (string_param(s, "command name")) {
  536.     logcmd(t, "%s", (const char*) s);
  537.     s.raisecase();
  538.     if (s == "SITE")
  539. helpCmd(sitetab, NULL);
  540.     else
  541. helpCmd(cmdtab, s);
  542.     return (true);
  543. }
  544. break;
  545.     case T_HOLDUNTIL: // set time to send
  546. if (SPACE() && SNPPTime(tv)) {
  547.     if (opt_CRLF()) {
  548. holdCmd(tv);
  549. return (true);
  550.     } else if (string_param(s, "GMT-difference")) {
  551. // conver GMT offset to numeric value
  552. int sign = 1;
  553. const char* cp = s;
  554. if (*cp == '-')
  555.     cp++, sign = -1;
  556. else if (*cp == '+')
  557.     cp++;
  558. time_t off = (time_t) (strtoul(cp, 0, 10)*60 / 100);
  559. holdCmd(tv + sign*off);
  560. return (true);
  561.     }
  562. }
  563. break;
  564.     case T_LEVEL: // set level of operation
  565. if (number_param(n)) {
  566.     logcmd(t, "%lu", n);
  567.     serviceLevel((u_int) n);
  568.     return (true);
  569. }
  570. break;
  571.     case T_LOGIN: // login as user
  572. if (SPACE() && STRING(s, "login-ID")) {
  573.     fxStr pwd;
  574.     if (opt_CRLF()) {
  575. logcmd(t, (const char*) s);
  576. loginCmd(s);
  577. return (true);
  578.     } else if (string_param(pwd, "password")) {
  579. logcmd(t, "%s <passwd>", (const char*) s);
  580. loginCmd(s, pwd);
  581. return (true);
  582.     }
  583. }
  584. break;
  585.     case T_MESSAGE: // specify 1-line message data
  586. if (SPACE() && multi_STRING(s) && CRLF()) {
  587.     logcmd(t, "%s", (const char*) s);
  588.     messageCmd(s);
  589.     return (true);
  590. }
  591. break;
  592.     case T_PAGER: // specify destination pager ID
  593. if (SPACE() && STRING(s, "pager-ID")) {
  594.     fxStr pwd;
  595.     if (opt_CRLF()) {
  596. logcmd(t, "%s", (const char*) s);
  597. pagerCmd(s);
  598. return (true);
  599.     } else if (string_param(pwd, "password/PIN")) {
  600. logcmd(t, "%s <passwd/PIN>", (const char*) s);
  601. pagerCmd(s, pwd);
  602. return (true);
  603.     }
  604. }
  605. break;
  606.     case T_PING: // localize/verify pager ID
  607. if (string_param(s, "pager-ID")) {
  608.     logcmd(t, "%s", (const char*) s);
  609.     pingCmd(s);
  610.     return (true);
  611. }
  612. break;
  613.     case T_QUIT: // terminate session
  614. if (CRLF()) {
  615.     logcmd(t);
  616.     reply(221, "Goodbye.");
  617.     dologout(0);
  618.     return (true);
  619. }
  620. break;
  621.     case T_REST: // reset server state
  622. logcmd(t);
  623. initServer();
  624. reply(220, "%s server (%s) ready.", (const char*) hostname, version);
  625. break;
  626.     case T_SEND: // initiate send operation
  627. if (CRLF()) {
  628.     logcmd(t);
  629.     sendCmd();
  630.     return (true);
  631. }
  632. break;
  633.     case T_SITE: // site-specific command
  634. if (SPACE() && getToken(T_STRING, "site command")) {
  635.     tokenBody.raisecase();
  636.     const tab* p = lookup(sitetab, N(sitetab), tokenBody);
  637.     if (p == NULL) {
  638. reply(500, "SITE %s: Command not recognized.",
  639.     (const char*) tokenBody);
  640.     } else if (!p->implemented)
  641. reply(500, "SITE %s: Command not implemented.", p->name);
  642.     else
  643. return (site_cmd(p->token));
  644. }
  645. break;
  646.     case T_STAT: // server status
  647. if (CRLF()) {
  648.     logcmd(t);
  649.     statusCmd();
  650.     return (true);
  651. }
  652. break;
  653.     case T_SUBJECT: // message subject
  654. if (SPACE() && multi_STRING(s) && CRLF()) {
  655.     logcmd(t, "%s", (const char*) s);
  656.     subjectCmd(s);
  657.     return (true);
  658. }
  659. break;
  660.     default:
  661. break;
  662.     }
  663.     return (false);
  664. }
  665. /*
  666.  * Site-specific protocol commands (one line).
  667.  */
  668. bool
  669. SNPPServer::site_cmd(Token t)
  670. {
  671.     fxStr s;
  672.     long n;
  673.     time_t tv;
  674.     bool b;
  675.     switch (t) {
  676.     case T_IDLE: // set/query idle timeout
  677. if (opt_CRLF()) {
  678.     logcmd(t);
  679.     reply(250, "%u seconds.", idleTimeout);
  680.     return (true);
  681. } else if (number_param(n)) {
  682.     logcmd(t, "%lu", n);
  683.     if ((unsigned)n > maxIdleTimeout && !IS(PRIVILEGED)) {
  684. idleTimeout = maxIdleTimeout;
  685. reply(250, "%lu: Idle timeout too large, set to %u.",
  686.     n, maxIdleTimeout);
  687.     } else {
  688. idleTimeout = (int) n;
  689. reply(250, "Idle timeout set to %u.", idleTimeout);
  690.     }
  691.     return (true);
  692. }
  693. break;
  694.     case T_HELP: // return help
  695. if (opt_CRLF()) {
  696.     helpCmd(sitetab, (char*) NULL);
  697.     return (true);
  698. } else if (string_param(s, "command name")) {
  699.     logcmd(T_SITE, "HELP %s", (const char*) s);
  700.     s.raisecase();
  701.     helpCmd(sitetab, s);
  702.     return (true);
  703. }
  704. break;
  705.     case T_JQUEUE:
  706. if (boolean_param(b)) {
  707.     logcmd(t, "%s", b ? "YES" : "NO");
  708.     defJob.queued = b;
  709.     reply(250, "Job will%s be queued.", b ? "" : " not");
  710.     return (true);
  711. }
  712. break;
  713.     case T_FROM_USER:
  714.     case T_MODEM:
  715.     case T_NOTIFY:
  716.     case T_NOTIFYADDR:
  717. if (SPACE() && multi_STRING(s) && CRLF() && setJobParameter(defJob, t, s)) {
  718.     logcmd(t, "%s", (const char*) s);
  719.     reply(250, "%s set to "%s".", parmToken(t), (const char*) s);
  720.     return (true);
  721. }
  722. break;
  723.     case T_MAXDIALS:
  724.     case T_MAXTRIES:
  725.     case T_SCHEDPRI:
  726. if (number_param(n) && setJobParameter(defJob, t, (u_short) n)) {
  727.     logcmd(t, "%lu", n);
  728.     reply(250, "%s set to %u.", parmToken(t), n);
  729.     return (true);
  730. }
  731. break;
  732.     case T_LASTTIME: // time to kill job
  733. if (timespec_param(6, tv) && setJobParameter(defJob, t, tv)) {
  734.     logcmd(t, "%02d%02d%02d"
  735. , tv/(24*60*60) , (tv/(60*60))%24 , (tv/60)%60);
  736.     reply(250, "%s set to %02d%02d%02d."
  737. , parmToken(t)
  738. , tv/(24*60*60) , (tv/(60*60))%24 , (tv/60)%60);
  739.     return (true);
  740. }
  741. break;
  742.     case T_RETRYTIME: // retry interval for job
  743. if (timespec_param(4, tv) && setJobParameter(defJob, t, tv)) {
  744.     logcmd(t, "%02d%02d" , tv/60, tv%60);
  745.     reply(250, "%s set to %02d%02d.", parmToken(t), tv/60, tv%60);
  746.     return (true);
  747. }
  748. break;
  749.     case T_JPARM: // query job parameters
  750. if (CRLF()) {
  751.     logcmd(t);
  752.     jstatCmd(defJob);
  753.     return (true);
  754. }
  755. break;
  756.     default:
  757. break;
  758.     }
  759.     return (false);
  760. }
  761. /*
  762.  * Parse an SNPP time specification.
  763.  */
  764. bool
  765. SNPPServer::SNPPTime(time_t& result)
  766. {
  767.     if (getToken(T_STRING, "time specification") && checkNUMBER(tokenBody)) {
  768. u_int tlen = tokenBody.length();
  769. const char* cp = tokenBody;
  770. // YYMMDDHHMM[SS]
  771. if (tlen == 12 || tlen == 10) {
  772.     struct tm tm;
  773.     tm.tm_sec  = (tlen == 12 ? twodigits(cp+10, 60) : 0);
  774.     tm.tm_min  = twodigits(cp+8, 60);
  775.     tm.tm_hour = twodigits(cp+6, 24);
  776.     tm.tm_mday = twodigits(cp+4, 32);
  777.     tm.tm_mon  = twodigits(cp+2, 13) - 1;
  778.     tm.tm_year = twodigits(cp+0, 100);
  779.     /*
  780.      * SNPP botched time specifications by not using 4 digits
  781.      * to specify a year.  This means that we have to guess at
  782.      * the intended year when the value is far in the future
  783.      * (as opposed to a year in the past that was given by
  784.      * mistake).  We arbitrarily assume years prior to 1990
  785.      * are in the next century and adjust them accordingly.
  786.      */
  787.     if (tm.tm_year < 90)
  788. tm.tm_year += 100;
  789.     tm.tm_isdst= 0; // GMT/UTC never has DST
  790.     /*
  791.      * The above time is assumed to be relative to
  792.      * GMT and mktime returns locally adjusted time
  793.      * so we need to adjust the result to get things
  794.      * in the right timezone.  Note that any additional
  795.      * timezone correction factor specified by the
  796.      * client will then be applied to this result.
  797.      */
  798.     result = mktime(&tm) - gmtoff;
  799.     return (true);
  800. }
  801. syntaxError(fxStr::format(
  802.     "bad time specification (expecting 10/12 digits, got %u)", tlen));
  803.     }
  804.     return (false);
  805. }
  806. void
  807. SNPPServer::syntaxError(const char* msg)
  808. {
  809.     const char* cp = strchr(cbuf, '');
  810.     if (cp[-1] == 'n')
  811. cp--;
  812.     reply(500, "'%.*s': Syntax error, %s.", cp-cbuf, cbuf, msg);
  813. }
  814. /*
  815.  * Command support methods.
  816.  */
  817. /*
  818.  * Process a multi-line text message.
  819.  */
  820. void
  821. SNPPServer::dataCmd(void)
  822. {
  823.     if (!haveText) {
  824. fxStr emsg;
  825. u_int seqnum = getDocumentNumber(emsg);
  826. if (seqnum == (u_int) -1) {
  827.     reply(554, "%s", (const char*)emsg);
  828.     return;
  829. }
  830. msgFile = fxStr::format("/%s/doc%u.page", FAX_TMPDIR, seqnum);
  831. FILE* fout = Sys::fopen(msgFile, "w");
  832. if (fout != NULL) {
  833.     setFileOwner(msgFile);
  834.     FileCache::chmod(msgFile, 0660); // sync cache
  835.     tempFiles.append(msgFile);
  836.     reply(354, "Begin message input; end with <CRLF>'.'<CRLF>.");
  837.     char buf[1024];
  838.     u_int len = 0;
  839.     int ignore;
  840.     for (;;) {
  841. if (getCmdLine(buf, sizeof (buf), true)) {
  842.     const char* bp = buf;
  843.     if (bp[0] == '.') {
  844. if ((bp[1] == 'n' && bp[2] == '') || bp[1] == '')
  845.     break;
  846. if (bp[1] == '.' && bp[2] == '') // .. -> .
  847.     bp++;
  848.     }
  849.     u_int blen = strlen(bp);
  850.     if ((len += blen) > maxMsgLength) {
  851. reply(550,
  852.     "Error, message too long; max %u characters.",
  853.     maxMsgLength);
  854. (void) fclose(fout);
  855. Sys::unlink(msgFile);
  856. return;
  857.     }
  858.     // if buf only contains .., we skipped the first .
  859.     // hence we must write bp, not buf. also blen is
  860.     // the length of bp, not buf.
  861.     ignore = fwrite((const char*) bp, blen, 1, fout);
  862. }
  863.     }
  864.     if (fclose(fout)) {
  865. perror_reply(554, msgFile, errno);
  866. Sys::unlink(msgFile);
  867.     } else {
  868. haveText = true;
  869. reply(250, "Message text OK.");
  870.     }
  871. } else
  872.     perror_reply(554, msgFile, errno);
  873.     } else
  874. reply(503, "Error, message already entered.");
  875. }
  876. /*
  877.  * Provide help.  We cannot share the base class
  878.  * implementation of this command because SNPP
  879.  * defines a different style for responses (sigh).
  880.  */
  881. void
  882. SNPPServer::helpCmd(const tab* ctab, const char* s)
  883. {
  884.     const char* type;
  885.     u_int NCMDS;
  886.     if (ctab == sitetab) {
  887.         type = "SITE ";
  888. NCMDS = N(sitetab);
  889.     } else {
  890.         type = "";
  891. NCMDS = N(cmdtab);
  892.     }
  893.     int width = 0;
  894.     const tab* c = ctab;
  895.     for (u_int n = NCMDS; n != 0; c++, n--) {
  896.         int len = strlen(c->name);
  897.         if (len > width)
  898.             width = len;
  899.     }
  900.     width = (width + 8) &~ 7;
  901.     if (s == NULL) {
  902.         reply(214, "The following %scommands are recognized %s.",
  903.             type, "(* =>'s unimplemented)");
  904.         int columns = 76 / width;
  905.         if (columns == 0)
  906.             columns = 1;
  907.         int lines = (NCMDS + columns - 1) / columns;
  908.         for (int i = 0; i < lines; i++) {
  909.             printf("214 ");
  910.             for (int j = 0; j < columns; j++) {
  911.                 c = &ctab[j*lines + i];
  912.                 printf("%s%c", c->name, !c->implemented ? '*' : ' ');
  913.                 if (c + lines >= &ctab[NCMDS])
  914.                     break;
  915.                 int w = strlen(c->name) + 1;
  916.                 while (w < width) {
  917.                     putchar(' ');
  918.                     w++;
  919.                 }
  920.             }
  921.             printf("rn");
  922.         }
  923.         (void) fflush(stdout);
  924.         reply(250, "Direct comments to %s.", (const char*) faxContact);
  925.     } else {
  926. c = lookup(ctab, NCMDS, s);
  927. if (c == NULL)
  928.     reply(550, "Unknown command %s.", s);
  929. else if (c->implemented)
  930.     reply(218, "Syntax: %s%s %s", type, c->name, c->help);
  931. else
  932.     reply(218, "%s%-*st%s; unimplemented.",
  933. type, width, c->name, c->help);
  934.     }
  935. }
  936. /*
  937.  * Specify the hold time (time to send) for a request.
  938.  */
  939. void
  940. SNPPServer::holdCmd(time_t when)
  941. {
  942.     time_t now = Sys::now();
  943.     if (when > now) {
  944. defJob.tts = when;
  945. const struct tm* tm = cvtTime(defJob.tts);
  946. reply(250, "Message will be processed at %02d%02d%02d%02d%02d."
  947.     , tm->tm_year
  948.     , tm->tm_mon+1
  949.     , tm->tm_mday
  950.     , tm->tm_hour
  951.     , tm->tm_min
  952. );
  953.     } else
  954. reply(550, "Invalid delivery date/time; time in the past.");
  955. }
  956. /*
  957.  * Login as the specified user.
  958.  */
  959. void
  960. SNPPServer::loginCmd(const char* loginID, const char* pass)
  961. {
  962.     if (IS(LOGGEDIN))
  963.         end_login();
  964.     the_user = loginID;
  965.     state &= ~S_PRIVILEGED;
  966.     adminWd = "*"; // make sure no admin privileges
  967.     passWd = "*"; // just in case...
  968.     if (checkUser(loginID)) {
  969. if (passWd != "") {
  970.     if (pass[0] == '' || !(streq(crypt(pass, passWd), passWd) || pamCheck(the_user, pass))) {
  971. if (++loginAttempts >= maxLoginAttempts) {
  972.     reply(421, "Login incorrect (closing connection).");
  973.     logNotice("Repeated SNPP login failures for user %s from %s [%s]"
  974. , (const char*) the_user
  975. , (const char*) remotehost
  976. , (const char*) remoteaddr
  977.     );
  978.     dologout(0);
  979. }
  980. reply(550, "Login incorrect.");
  981. logInfo("SNPP login failed from %s [%s], %s"
  982.     , (const char*) remotehost
  983.     , (const char*) remoteaddr
  984.     , (const char*) the_user
  985. );
  986. return;
  987.     }
  988. }
  989. login(250);
  990.     } else {
  991. if (++loginAttempts >= maxLoginAttempts) {
  992.     reply(421, "Login incorrect (closing connection).");
  993.     logNotice("Repeated SNPP login failures for user %s from %s [%s]"
  994. , (const char*) the_user
  995. , (const char*) remotehost
  996. , (const char*) remoteaddr
  997.     );
  998.     dologout(0);
  999. } else {
  1000.     reply(421, "User %s access denied.", (const char*) the_user);
  1001.     logNotice("SNPP LOGIN REFUSED (%s) FROM %s [%s], %s"
  1002. , "user denied"
  1003. , (const char*) remotehost
  1004. , (const char*) remoteaddr
  1005. , (const char*) the_user
  1006.     );
  1007. }
  1008.     }
  1009. }
  1010. /*
  1011.  * Specify a one-line message text.
  1012.  */
  1013. void
  1014. SNPPServer::messageCmd(const char* msg)
  1015. {
  1016.     if (!haveText) {
  1017. u_int len = strlen(msg);
  1018. if (len > maxMsgLength) {
  1019.     reply(550,
  1020. "Error, message too long; no more than %u characters accepted.",
  1021. maxMsgLength);
  1022.     return;
  1023. }
  1024. fxStr emsg;
  1025. u_int seqnum = getDocumentNumber(emsg);
  1026. if (seqnum == (u_int) -1) {
  1027.     reply(554, "%s", (const char*)emsg);
  1028.     return;
  1029. }
  1030. msgFile = fxStr::format("/%s/doc%u.page", FAX_TMPDIR, seqnum);
  1031. FILE* fout = Sys::fopen(msgFile, "w");
  1032. if (fout != NULL) {
  1033.     setFileOwner(msgFile);
  1034.     FileCache::chmod(msgFile, 0660); // sync cache
  1035.     tempFiles.append(msgFile);
  1036.     int ignore = fwrite(msg, len, 1, fout);
  1037.     if (fclose(fout)) {
  1038. perror_reply(554, msgFile, errno);
  1039. Sys::unlink(msgFile);
  1040.     } else {
  1041. haveText = true;
  1042. reply(250, "Message text OK.");
  1043.     }
  1044. } else
  1045.     perror_reply(554, msgFile, errno);
  1046.     } else
  1047. reply(503, "Error, message already entered.");
  1048. }
  1049. /*
  1050.  * Process a PAGE command.  Map the client-specified pager
  1051.  * identification string to a service provider phone number
  1052.  * and destination PIN and create a new job for this request.
  1053.  */
  1054. void
  1055. SNPPServer::pagerCmd(const char* pagerID, const char* pin)
  1056. {
  1057.     /*
  1058.      * Lookup pager ID and map to a service provider
  1059.      * and, optionally, PIN.
  1060.      */
  1061.     fxStr provider;
  1062.     fxStr PIN;
  1063.     fxStr emsg;
  1064.     if (!mapPagerID(pagerID, provider, PIN, emsg)) {
  1065. reply(550, "%s.", (const char*) emsg);
  1066. return;
  1067.     }
  1068.     /*
  1069.      * RFC 1861 says we should lock the Level, Coverage,
  1070.      * Holdtime, and Alert values for the request at this point.
  1071.      * To do this we create a job (inheriting the current state
  1072.      * from the default job) and fill in any other information.
  1073.      * However we do not submit it until later (when a SEND
  1074.      * request is made).
  1075.      */
  1076.     curJob = &defJob; // inherit from default job
  1077.     // XXX merge items to same provider (maybe?)
  1078.     if (newJob(emsg) && updateJobOnDisk(*curJob, emsg)) {
  1079. fxStr file("/" | curJob->qfile);
  1080. setFileOwner(file); // force ownership
  1081. FileCache::chmod(file, 0660); // sync cache
  1082. curJob->lastmod = Sys::now(); // noone else should update
  1083. curJob->number = provider; // destination phone number
  1084. if (!pin) // use mapped value
  1085.     pin = PIN;
  1086. curJob->items.append(FaxItem(FaxRequest::send_page, 0, "", pin));
  1087. curJob->items.append(
  1088.     FaxItem(FaxRequest::send_page_saved, 0, "", pin));
  1089. reply(250, "Pager ID accepted; provider: %s pin: %s jobid: %s."
  1090.     , (const char*) provider
  1091.     , pin
  1092.     , (const char*) curJob->jobid
  1093. );
  1094. msgs.append(curJob->jobid);
  1095. updateJobOnDisk(*curJob, emsg);
  1096.     } else
  1097. reply(554, "%s.", (const char*) emsg);
  1098.     initSNPPJob(); // reset job-related state
  1099. }
  1100. /*
  1101.  * Validate a client-specified pager ID string.
  1102.  * This is only sort of like the intended usage
  1103.  * but is the only thing that makes sense in
  1104.  * our environment (where the provider is not
  1105.  * directly accessible).
  1106.  */
  1107. void
  1108. SNPPServer::pingCmd(const char* pagerID)
  1109. {
  1110.     /*
  1111.      * Lookup pager ID and map to a service provider
  1112.      * and, optionally, PIN.
  1113.      */
  1114.     fxStr provider;
  1115.     fxStr PIN;
  1116.     fxStr emsg;
  1117.     if (mapPagerID(pagerID, provider, PIN, emsg))
  1118. reply(821, "Valid pager ID, no location information available.");
  1119.     else
  1120. reply(550, "%s.", (const char*) emsg);
  1121. }
  1122. static bool
  1123. notPresent(FaxItemArray& a, const char* name)
  1124. {
  1125.     for (u_int i = 0, n = a.length(); i < n; i++)
  1126. if (a[i].op == FaxRequest::send_data && a[i].item == name)
  1127.     return (false);
  1128.     return (true);
  1129. }
  1130. /*
  1131.  * Send (submit) a pager request.  We complete the
  1132.  * formulation of the job and submit it to the
  1133.  * scheduler.  If this job is marked for no queueing
  1134.  * then we also wait around for the job to complete.
  1135.  */
  1136. void
  1137. SNPPServer::sendCmd(void)
  1138. {
  1139.     if (msgs.length() == 0) {
  1140. reply(503, "Error, no pager ID specified with PAGE.");
  1141. return;
  1142.     }
  1143.     /*
  1144.      * If we need to wait for jobs to complete, construct
  1145.      * and register a trigger now before we submit the jobs.
  1146.      */
  1147.     fxStr emsg;
  1148.     u_int i, n = msgs.length();
  1149.     bool waitForJobs = false;
  1150.     for (i = 0; i < n; i++) {
  1151. Job* job = findJob(msgs[i], emsg);
  1152. if (!job)
  1153.     msgs.remove(i), n--;
  1154. else if (!job->queued)
  1155.     waitForJobs = true;
  1156.     }
  1157.     if (waitForJobs) {
  1158. state &= ~S_LOGTRIG; // just process events
  1159. if (!newTrigger(emsg, "J%04x", 1<<Trigger::JOB_DEAD)) {
  1160.     reply(550,
  1161. "Cannot register trigger to wait for job completion: %s.",
  1162. (const char*) emsg);
  1163.     return;
  1164. }
  1165.     }
  1166.     const char* docname = msgFile;
  1167.     const char* cp = strrchr(docname, '/');
  1168.     if (!cp) // relative name, e.g. doc123
  1169. cp = docname;
  1170.     for (i = 0; i < n; i++) {
  1171. Job* job = findJob(msgs[i], emsg);
  1172. if (!job) {
  1173.     msgs.remove(i), n--;
  1174.     continue;
  1175. }
  1176. if (*cp != '') {
  1177.     /*
  1178.      * Add a reference to the message text for
  1179.      * the current job, if not already present.
  1180.      */
  1181.     fxStr document =
  1182. fxStr::format("/" FAX_DOCDIR "%s.", cp) | job->jobid;
  1183.     if (notPresent(job->items, &document[1])) {
  1184. if (Sys::link(docname, document) < 0) {
  1185.     reply(554, "Unable to link document %s to %s: %s.",
  1186. docname, (const char*) document, strerror(errno));
  1187.     return;
  1188. }
  1189. job->items.append(
  1190.     FaxItem(FaxRequest::send_data, 0, "", &document[1]));
  1191.     }
  1192. }
  1193. /*
  1194.  * Submit the job for scheduling.
  1195.  */
  1196. if (job->mailaddr == "")
  1197.     replyBadJob(*job, T_NOTIFYADDR);
  1198. else if (job->sender == "")
  1199.     replyBadJob(*job, T_FROM_USER);
  1200. else if (job->modem == "")
  1201.     replyBadJob(*job, T_MODEM);
  1202. else if (job->client == "")
  1203.     replyBadJob(*job, T_CLIENT);
  1204. else {
  1205.     if (job->external == "")
  1206. job->external = job->number;
  1207.     if (job->tts == 0)
  1208. job->tts = Sys::now();
  1209.     job->killtime += job->tts; // adjust based on any hold time
  1210.     if (updateJobOnDisk(*job, emsg)) {
  1211. const char* jobid = job->jobid;
  1212. /*
  1213.  * NB: we don't mark the lastmod time for the
  1214.  * job since the scheduler should re-write the
  1215.  * queue file to reflect what it did with it
  1216.  * (e.g. what state it placed the job in).
  1217.  */
  1218. if (sendQueuerACK(emsg, "S%s", jobid))
  1219.     continue;
  1220. reply(554, "Failed to submit message %s: %s.",
  1221.     jobid, (const char*) emsg);
  1222.     } else
  1223. reply(554, "%s.", (const char*) emsg);
  1224. }
  1225. if (waitForJobs) { // cleanup trigger on error
  1226.     cancelTrigger(emsg);
  1227.     /*
  1228.      * Not sure what to do here.  If some jobs got
  1229.      * submitted then we want to remove them so the
  1230.      * client can just reissue SEND if the error was
  1231.      * transient (e.g. faxq was temporarily stopped).
  1232.      * However we don't track which jobs got started
  1233.      * and which did not so for now we'll just leave
  1234.      * everything so the client can resubmit things
  1235.      * with a subsequent SEND.  Unfortunately this
  1236.      * can result in duplicate pages being sent.
  1237.      */
  1238. }
  1239. return; // failure
  1240.     }
  1241.     if (waitForJobs) { // no queueing, wait for submitted jobs
  1242. if (setjmp(urgcatch) == 0) {
  1243.     Dispatcher& disp = Dispatcher::instance();
  1244.     state |= S_WAITTRIG|S_SENDWAIT;
  1245.     bool jobsPending;
  1246.     do {
  1247. disp.dispatch();
  1248. /*
  1249.  * The trigger event handlers update our notion
  1250.  * of the job state asynchronously so we can just
  1251.  * monitor the job state(s) after each event we
  1252.  * receive.
  1253.  */
  1254. jobsPending = false;
  1255. for (i = 0; i < n; i++) {
  1256.     Job* job = findJob(msgs[i], emsg);
  1257.     if (!job)
  1258. msgs.remove(i), n--;
  1259.     else if (!job->queued && 
  1260.       job->state != FaxRequest::state_done &&
  1261.       job->state != FaxRequest::state_failed)
  1262. jobsPending = true;
  1263. }
  1264.     } while (IS(WAITTRIG) && jobsPending);
  1265.     reply(250, "Message processing completed.");
  1266. }
  1267. state &= ~(S_WAITTRIG|S_SENDWAIT);
  1268. (void) cancelTrigger(emsg);
  1269.     } else // jobs queued, just acknowledge
  1270. reply(250, "Message%s succesfully queued.",
  1271.     msgs.length() > 1 ? "s" : "");
  1272.     resetState(); // reset SEND-related state
  1273. }
  1274. /*
  1275.  * Set the message service level.  We currently use
  1276.  * this just to select a scheduling priority and
  1277.  * job expiration time.
  1278.  */
  1279. void
  1280. SNPPServer::serviceLevel(u_int level)
  1281. {
  1282.     if (level <= 11) {
  1283. defJob.usrpri = priMap[level];
  1284. defJob.killtime = 60*killMap[level];
  1285. defJob.retrytime = retryMap[level];
  1286. reply(250, "OK, service level %u accepted.", level);
  1287.     } else
  1288. reply(550, "Invalid service level %u; we only handle 0-11.", level);
  1289. }
  1290. /*
  1291.  * Return server status.
  1292.  */
  1293. void
  1294. SNPPServer::statusCmd(void)
  1295. {
  1296.     reply(214, "%s SNPP server status:", (const char*) hostname);
  1297.     reply(214, "    %s", version);
  1298.     if (!isdigit(remotehost[0]))
  1299. reply(214, "    Connected to %s (%s).",
  1300.     (const char*) remotehost, (const char*) remoteaddr);
  1301.     else
  1302. reply(214, "    Connected to %s.", (const char*) remotehost);
  1303.     if (IS(LOGGEDIN)) {
  1304. reply(214, "    Logged in as user %s (uid %u)."
  1305.     , (const char*) the_user
  1306.     , uid
  1307. );
  1308.     } else
  1309. reply(214, "    Waiting for login.");
  1310.     reply(214, "    Idle timeout set to %d seconds.", idleTimeout);
  1311.     if (discTime > 0)
  1312. reply(214, "    Server scheduled to be unavailable at %.24s.",
  1313.     asctime(cvtTime(discTime)));
  1314.     else
  1315. reply(214, "    No server down time currently scheduled.");
  1316.     reply(214, "    HylaFAX scheduler reached at %s (%sconnected)."
  1317. , (const char*) faxqFIFOName
  1318. , faxqFd == -1 ? "not " : ""
  1319.     );
  1320.     if (clientFd != -1)
  1321. reply(214, "    Server FIFO is /%s (%sopen)."
  1322.     , (const char*) clientFIFOName
  1323.     , clientFd == -1 ? "not " : ""
  1324. );
  1325.     if (IS(WAITFIFO))
  1326. reply(214, "    Waiting for response from HylaFAX scheduler.");
  1327.     if (msgs.length() > 0) {
  1328. reply(214, "    %u message%s prepared for transmission.",
  1329.     msgs.length(), msgs.length() > 1 ? "s" : "");
  1330. // XXX dump status of msgs
  1331.     } else
  1332. reply(214, "    No messages prepared for transmission.");
  1333.     reply(214, "    %s message text.", haveText ? "Received" : "No received");
  1334.     reply(250, "End of status");
  1335. }
  1336. /*
  1337.  * Set the subject for outgoing messages.
  1338.  * For now we just use it to tag the jobs.
  1339.  */
  1340. void
  1341. SNPPServer::subjectCmd(const char* subj)
  1342. {
  1343.     defJob.jobtag = subj; // XXX
  1344.     reply(250, "Message subject OK.");
  1345. }
  1346. /*
  1347.  * Configuration support.
  1348.  */
  1349. void
  1350. SNPPServer::resetConfig()
  1351. {
  1352.     InetFaxServer::resetConfig();
  1353.     setupConfig();
  1354. }
  1355. void
  1356. SNPPServer::setupConfig()
  1357. {
  1358.     setConfigItem("maxmsglength",  "128");
  1359.     setConfigItem("pageridmapfile","/etc/pagermap");
  1360.     setConfigItem("prioritymap",
  1361. "63 127 127 127 127 127 127  127  127  127  127  127");
  1362.     setConfigItem("killtimemap",
  1363. " 5   5   5  15  60 240 720 1440 1440 1440 1440 1440");
  1364.     setConfigItem("retrytimemap",
  1365. "30  60  60 180   0   0   0    0    0    0    0    0");
  1366. }
  1367. static void
  1368. setShortMap(u_short map[12], const char* value)
  1369. {
  1370.     char* cp;
  1371.     for (int i = 0; i < 12; i++) {
  1372. map[i] = (u_short) strtoul(value, &cp, 0);
  1373. if (!cp && *cp == '')
  1374.     break;
  1375. value = cp;
  1376.     }
  1377. }
  1378. static void
  1379. setTimeMap(time_t map[12], const char* value)
  1380. {
  1381.     char* cp;
  1382.     for (int i = 0; i < 12; i++) {
  1383. map[i] = (time_t) strtoul(value, &cp, 0);
  1384. if (!cp && *cp == '')
  1385.     break;
  1386. value = cp;
  1387.     }
  1388. }
  1389. bool
  1390. SNPPServer::setConfigItem(const char* tag, const char* value)
  1391. {
  1392.     if (streq(tag, "maxmsglength")) {
  1393. maxMsgLength = getNumber(value);
  1394.     } else if (streq(tag, "pageridmapfile")) {
  1395. pagerIDMapFile = value;
  1396.     } else if (streq(tag, "prioritymap")) {
  1397. setShortMap(priMap, value);
  1398.     } else if (streq(tag, "killtimemap")) {
  1399. setTimeMap(killMap, value);
  1400.     } else if (streq(tag, "retrytimemap")) {
  1401. setTimeMap(retryMap, value);
  1402.     } else if (!InetFaxServer::setConfigItem(tag, value))
  1403. return (false);
  1404.     return (true);
  1405. }
  1406. #endif /* SNPP_SUPPORT */