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

传真(Fax)编程

开发平台:

C/C++

  1. /* $Id: Parser.c++,v 1.15 2009/03/02 04:54:14 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. /*
  27.  * HylaFAX Client-Server Protocol Parser.
  28.  */
  29. #include "port.h"
  30. #include "config.h"
  31. #include "Sys.h"
  32. #include "Socket.h"
  33. #include "HylaFAXServer.h"
  34. #include "Dispatcher.h"
  35. #include <ctype.h>
  36. #define N(a) (sizeof (a) / sizeof (a[0]))
  37. /*
  38.  * Standard protocol commands.
  39.  */
  40. static const tab cmdtab[] = {
  41. { "ABOR",         T_ABOR, false, true, "[modem] (abort operation)" },
  42. { "ACCT",         T_ACCT, false,false, "(specify account)" },
  43. { "ADMIN",        T_ADMIN,  true, true, "password" },
  44. { "ALLO",         T_ALLO, false,false, "(allocate disk space)" },
  45. { "ANSWER",       T_ANSWER,  true, true, "modem [DATA|VOICE|FAX]" },
  46. { "APPE",         T_APPE,  true, true, "file-name" },
  47. { "CWD",          T_CWD,  true, true, "[directory-name]" },
  48. { "CDUP",         T_CDUP,  true, true, "(change directory up one level)"},
  49. { "CHMOD",        T_CHMOD,  true, true, "file-name mode" },
  50. { "CHOWN",        T_CHOWN,  true, true, "file-name user" },
  51. { "DELE",         T_DELE,  true, true, "file-name" },
  52. { "DISABLE",      T_DISABLE,  true, true, "modem [reason]" },
  53. { "ENABLE",       T_ENABLE,  true, true, "modem" },
  54. { "EPRT",         T_EPRT,  true, true, "|f|addr|port|" },
  55. { "EPSV",         T_EPSV,  true, true, "(set server in passive mode with extended result)" },
  56. { "HELP",         T_HELP, false, true, "[<string>]" },
  57. { "FILEFMT",      T_FILEFMT,  true, true, "[format-string]" },
  58. { "FORM",         T_FORM,  true, true, "format-type" },
  59. { "IDLE",         T_IDLE,  true, true, "[max-idle-timeout]" },
  60. { "JDELE",        T_JDELE,  true, true, "[job-id]" },
  61. { "JINTR",        T_JINTR,  true, true, "[job-id]" },
  62. { "JKILL",        T_JKILL,  true, true, "[job-id]" },
  63. { "JNEW",         T_JNEW,  true, true, "" },
  64. { "JOB",          T_JOB,  true, true, "[job-id]" },
  65. { "JOBFMT",       T_JOBFMT,  true, true, "[format-string]" },
  66. { "JPARM",        T_JPARM,  true, true, "[parm-name [parm-value]]" },
  67. { "JREST",        T_JREST,  true, true, "(reset current job state)" },
  68. { "JSUBM",        T_JSUB,  true, true, "[job-id]" },
  69. { "JSUSP",        T_JSUSP,  true, true, "[job-id]" },
  70. { "JWAIT",        T_JWAIT,  true, true, "[job-id]" },
  71. { "JGDELE",       T_JGDELE,  true,false, "[jobgroup-id]" },
  72. { "JGINTR",       T_JGINTR,  true,false, "[jobgroup-id]" },
  73. { "JGKILL",       T_JGKILL,  true,false, "[jobgroup-id]" },
  74. { "JGNEW",        T_JGNEW,  true, true, "" },
  75. { "JGPARM",       T_JGPARM,  true,false, "parm-name [parm-value]" },
  76. { "JGREST",       T_JGREST,  true,false, "(reset current job group state)"},
  77. { "JGRP",         T_JGRP,  true,false, "[jobgroup-id]" },
  78. { "JGSUBM",       T_JGSUB,  true,false, "[jobgroup-id]" },
  79. { "JGSUSP",       T_JGSUSP,  true,false, "[jobgroup-id]" },
  80. { "JGWAIT",       T_JGWAIT,  true,false, "[jobgroup-id]" },
  81. { "LIST",         T_LIST,  true, true, "[path-name]" },
  82. { "MDTM",         T_MDTM,  true, true, "path-name" },
  83. { "MODE",         T_MODE, false, true, "(specify transfer mode)" },
  84. { "MDMFMT",       T_MODEMFMT,  true, true, "[format-string]" },
  85. { "NLST",         T_NLST,  true, true, "[path-name]" },
  86. { "NOOP",         T_NOOP, false, true, "" },
  87. { "PASS",         T_PASS, false, true, "password" },
  88. { "PASV",         T_PASV,  true, true, "(set server in passive mode)" },
  89. { "PORT",         T_PORT,  true, true, "a0,a1,a2,a3,p0,p1" },
  90. { "PWD",          T_PWD,  true, true, "(print working directory)" },
  91. { "QUIT",         T_QUIT, false, true, "(terminate service)", },
  92. { "RCVFMT",       T_RCVFMT,  true, true, "[format-string]" },
  93. { "REIN",         T_REIN, false, true, "(reinitialize server state)" },
  94. { "REST",         T_REST,  true, true, "restart-marker" },
  95. { "RETP",         T_RETP,  true, true, "file-name" },
  96. { "RETR",         T_RETR,  true, true, "file-name" },
  97. { "RNFR",         T_RNFR,  true,false, "file-name" },
  98. { "RNTO",         T_RNTO,  true,false, "file-name" },
  99. { "SHUT",         T_SHUT,  true, true, "NOW|HHSS|YYYYMMDDHHSS [reason]" },
  100. { "SITE",         T_SITE,  true, true, "site-cmd [arguments]" },
  101. { "SIZE",         T_SIZE,  true, true, "path-name" },
  102. { "STAT",         T_STAT, false, true, "[path-name]" },
  103. { "STOR",         T_STOR,  true, true, "file-name" },
  104. { "STOT",         T_STOT,  true, true, "(store unique temporary)" },
  105. { "STOU",         T_STOU,  true, true, "(store unique)" },
  106. { "STRU",         T_STRU, false, true, "file-structure" },
  107. { "SYST",         T_SYST,  true, true, "(return system type)" },
  108. { "TZONE",        T_TZONE,  true, true, "[GMT|LOCAL]" },
  109. { "TYPE",         T_TYPE, false, true, "transfer-type" },
  110. { "USER",         T_USER, false, true, "username" },
  111. { "VRFY",         T_VRFY, false, true, "dialstring" },
  112. };
  113. /*
  114.  * Job parameter commands/keys.
  115.  */
  116. static const tab parmtab[] = {
  117. { "ACCTINFO",     T_ACCTINFO,   false,false, "[<string>]" },
  118. { "BEGBR",        T_BEGBR,   false, true, "[ANY|bit-rate]" },
  119. { "BEGST",        T_BEGST,   false, true, "[ANY|scanline-time]" },
  120. { "CHOPTHRESHOLD",T_CHOPTHRESH,   false, true, "[inches]" },
  121. { "CLIENT",       T_CLIENT,   false, true, "[<string>]" },
  122. { "COMMENTS",     T_COMMENTS,   false, true, "[<string>]" },
  123. { "COMMID",       T_COMMID,   false, true, "(communication identifier)" },
  124. { "COVER",        T_COVER,   false, true, "path-name" },
  125. { "DATAFORMAT",   T_DATAFORMAT,   false, true, "[ANY|G31D|G32D|G4]" },
  126. { "DIALSTRING",   T_DIALSTRING,   false, true, "[<string>]" },
  127. { "DOCUMENT",     T_DOCUMENT,   false, true, "path-name" },
  128. { "DONEOP",       T_DONEOP,   false, true, "[<string>]" },
  129. { "ERRORCODE",    T_ERRORCODE,    false, true, "[<string>]" },
  130. { "EXTERNAL",     T_EXTERNAL,   false, true, "[<string>]" },
  131. { "FAXNUMBER",    T_FAXNUMBER,   false, true, "[<string>]" },
  132. { "FAXNAME",      T_FAXNAME,   false, true, "[<string>]" },
  133. { "FROMCOMPANY",  T_FROM_COMPANY, false, true, "[<string>]" },
  134. { "FROMLOCATION", T_FROM_LOCATION,false, true, "[<string>]" },
  135. { "FROMUSER",     T_FROM_USER,   false, true, "[<string>]" },
  136. { "FROMVOICE",    T_FROM_VOICE,   false, true, "[<string>]" },
  137. { "GROUPID",      T_GROUPID,   false, true, "(job group identifier)" },
  138. { "HRES",         T_HRES,   false,false, "[dots-per-inch]" },
  139. { "IGNOREMODEMBUSY", T_IGNOREMODEMBUSY, false, true, "[YES|NO]" },
  140. { "JOBID",        T_JOBID,   false, true, "(job identifier)" },
  141. { "JOBINFO",      T_JOBINFO,   false, true, "[<string>]" },
  142. { "JOBTYPE",      T_JOBTYPE,   false, true, "(job type)" },
  143. { "LASTTIME",     T_LASTTIME,   false, true, "[DDHHSS]" },
  144. { "MAXDIALS",     T_MAXDIALS,   false, true, "[<number>]" },
  145. { "MAXPAGES",     T_MAXPAGES,   false, true, "[<number>]" },
  146. { "MAXTRIES",     T_MAXTRIES,   false, true, "[<number>]" },
  147. { "MINBR",        T_MINBR,   false, true, "[ANY|bit-rate]" },
  148. { "MODEM",        T_MODEM,   false, true, "[device|class]" },
  149. { "NDIALS",       T_NDIALS,   false, true, "[<number>]" },
  150. { "NOCOUNTCOVER", T_NOCOUNTCOVER, false, true, "[<number>]" },
  151. { "SERVERDOCOVER", T_SERVERDOCOVER, false, true, "[YES|NO]" },
  152. { "NOTIFY",       T_NOTIFY,   false, true, "[NONE|DONE|REQUEUE|DONE+REQUEUE]" },
  153. { "NOTIFYADDR",   T_NOTIFYADDR,   false, true, "[email-address]" },
  154. { "NPAGES",       T_NPAGES,   false, true, "[<number>]" },
  155. { "NTRIES",       T_NTRIES,   false, true, "[<number>]" },
  156. { "OWNER",        T_OWNER,   false, true, "[<name>|<number>]" },
  157. { "PAGECHOP",     T_PAGECHOP,   false, true, "[DEFAULT|NONE|ALL|LAST]" },
  158. { "PAGELENGTH",   T_PAGELENGTH,   false, true, "[millimeters]" },
  159. { "PAGEWIDTH",    T_PAGEWIDTH,   false, true, "[millimeters]" },
  160. { "PASSWD",       T_PASSWD,   false, true, "[<string>]" },
  161. { "POLL",         T_POLL,   false, true, "selector [passwd]" },
  162. { "REGARDING",    T_REGARDING,   false, true, "[<string>]" },
  163. { "RETRYTIME",    T_RETRYTIME,   false, true, "[HHSS]" },
  164. { "SCHEDPRI",     T_SCHEDPRI,   false, true, "[<number>]" },
  165. { "SENDTIME",     T_SENDTIME,   false, true, "[NOW|YYYYMMDDHHSS]" },
  166. { "SKIPPAGES",    T_SKIPPAGES,    false, true, "[<number>]" },
  167. { "SKIPPEDPAGES", T_SKIPPEDPAGES, false, true, "[<number>]" },
  168. { "STATE",        T_STATE,   false, true, "(job state)" },
  169. { "STATUS",       T_STATUS,       false, true, "[<string>]" },
  170. { "SUBADDR",      T_SUBADDR,   false, true, "[<string>]" },
  171. { "TAGLINE",      T_TAGLINE,   false, true, "[<string>]" },
  172. { "TIMEOFDAY",    T_TIMEOFDAY,   false, true, "[<string>]" },
  173. { "TOCOMPANY",    T_TO_COMPANY,   false, true, "[<string>]" },
  174. { "TOLOCATION",   T_TO_LOCATION,  false, true, "[<string>]" },
  175. { "TOTDIALS",     T_TOTDIALS,   false, true, "[<number>]" },
  176. { "TOTPAGES",     T_TOTPAGES,   false, true, "[<number>]" },
  177. { "TOTTRIES",     T_TOTTRIES,   false, true, "[<number>]" },
  178. { "TOUSER",       T_TO_USER,   false, true, "[<string>]" },
  179. { "TOVOICE",      T_TO_VOICE,   false, true, "[<string>]" },
  180. { "TSI",          T_TSI,   false, true, "[<string>]" },
  181. { "USECONTCOVER", T_USE_CONTCOVER,false, true, "[YES|NO]" },
  182. { "USEXVRES",     T_USE_XVRES,   false, true, "[YES|NO]" },
  183. { "USEECM",       T_USE_ECM,   false, true, "[YES|NO]" },
  184. { "ECMTYPE",      T_ECMTYPE,   false, true, "[64BIT|256BIT|HALFDUPLEX|FULLDUPLEX]" },
  185. { "USETAGLINE",   T_USE_TAGLINE,  false, true, "[YES|NO]" },
  186. { "USRKEY",       T_USRKEY,   false, true, "[<string>]" },
  187. { "VRES",         T_VRES,   false, true, "[lines-per-inch]" },
  188. { "HELP",         T_HELP,   false, true, "[<string>]" },
  189. };
  190. /*
  191.  * Site-specific commands.
  192.  */
  193. static const tab sitetab[] = {
  194. { "ADDMODEM",     T_ADDMODEM,   false,false, "modem [speed]"},
  195. { "ADDUSER",      T_ADDUSER,   false, true, "user-spec [passwd [adminwd]]"},
  196. { "CONFIG",       T_CONFIG,   false, true, "[parm-name [parm-value]]" },
  197. { "DELMODEM",     T_DELMODEM,   false,false, "modem" },
  198. { "DELUSER",      T_DELUSER,   false, true, "user-spec" },
  199. { "TRIGGER",   T_TRIGGER,   false, true, "spec" },
  200. { "HELP",         T_HELP,   false, true, "[<string>]" },
  201. { "LOCKWAIT",     T_LOCKWAIT,   false, true, "max-lockwait-timeout" },
  202. };
  203. static const tab*
  204. lookup(const tab* p, u_int n, const char* cmd)
  205. {
  206.     while (n != 0) {
  207.         if (strcmp(cmd, p->name) == 0)
  208.             return (p);
  209. p++, n--;
  210.     }
  211.     return (NULL);
  212. }
  213. static const char*
  214. tokenName(const tab* p, u_int n, Token t)
  215. {
  216.     while (n != 0) {
  217. if (p->token == t)
  218.     return (p->name);
  219. p++, n--;
  220.     }
  221.     return ("???");
  222. }
  223. const char*
  224. HylaFAXServer::cmdToken(Token t)
  225. {
  226.     return tokenName(cmdtab, N(cmdtab), t);
  227. }
  228. const char*
  229. HylaFAXServer::siteToken(Token t)
  230. {
  231.     return tokenName(sitetab, N(sitetab), t);
  232. }
  233. const char*
  234. HylaFAXServer::parmToken(Token t)
  235. {
  236.     return tokenName(parmtab, N(parmtab), t);
  237. }
  238. u_int
  239. HylaFAXServer::twodigits(const char* cp, u_int range)
  240. {
  241.     return ((cp[0]-'0')*10 + (cp[1]-'0')) % range;
  242. }
  243. u_int
  244. HylaFAXServer::fourdigits(const char* cp)
  245. {
  246.     return (cp[0]-'0')*1000 +
  247.    (cp[1]-'0')*100 +
  248.    (cp[2]-'0')*10 +
  249.    (cp[3]-'0');
  250. }
  251. inline bool
  252. isLoginToken(Token t)
  253. {
  254.     return (t == T_USER || t == T_PASS || t == T_ADMIN);
  255. }
  256. /*
  257.  * Parse and process command input received on the
  258.  * control channel.  This method is invoked whenever
  259.  * data is present on the control channel.  We read
  260.  * everything and parse (and execute) as much as possible
  261.  * but do not block waiting for more data except when
  262.  * a partial line of input is received.  This is done
  263.  * to ensure other processing will be handled in a
  264.  * timely fashion (e.g. processing of messages from
  265.  * the scheduler received via the FIFO).
  266.  */
  267. int
  268. HylaFAXServer::parse()
  269. {
  270.     if (IS(WAITDATA)) { // recursive invocation
  271. state &= ~S_WAITDATA;
  272. return (0);
  273.     }
  274. // stop control channel idle timeout
  275.     Dispatcher::instance().stopTimer(this);
  276.     pushToken(T_NIL); // reset state
  277.     for (;;) {
  278. /*
  279.  * Fetch the next complete line of input received on the
  280.  * control channel.  This call will fail when no more data
  281.  * is *currently* waiting on the control channel.  Note
  282.  * that this does not mean the connection has dropped; just
  283.  * that data is not available at this instant.  Note also
  284.  * that if a partial line of input is received a complete
  285.  * line will be waited for (see below).
  286.  */
  287. if (!getCmdLine(cbuf, sizeof (cbuf)))
  288.     break;
  289. /*
  290.  * Parse the line of input read above.
  291.  */
  292. if (getToken(T_STRING, "command token")) {
  293.     tokenBody.raisecase();
  294.     const tab* p = lookup(cmdtab, N(cmdtab), tokenBody);
  295.     if (p == NULL)
  296. reply(500, "%s: Command not recognized.",
  297.     (const char*) tokenBody);
  298.     else if (!p->implemented)
  299. reply(502, "%s: Command not implemented.", p->name);
  300.     /*
  301.      * If the user is not privileged, then check for
  302.      * the service being shutdown if the command is
  303.      * not part of the login procedure (USER-PASS-ADMIN).
  304.      * This permits administrators to gain access to
  305.      * a system that is shutdown.  Other users will get
  306.      * dropped if they type something else.
  307.      */ 
  308.     else if (!IS(PRIVILEGED) && !isLoginToken(p->token) &&
  309.       isShutdown(!IS(LOGGEDIN))) {
  310. reply(221, "Server shutting down.  Goodbye.");
  311. dologout(0); // XXX
  312.     /*
  313.      * If command requires client to be logged in check
  314.      * for this.  Note that some commands have variants
  315.      * that do not require the client be logged in--these
  316.      * cannot check here and must do it specially below.
  317.      */
  318.     } else if (p->checklogin && !checklogin(p->token))
  319. ;
  320.     /*
  321.      * Command is valid, implemented, and the server
  322.      * is available to service it.  If the syntax is
  323.      * correct then reset the number of consecutive
  324.      * bad commands.  Note that since part of the syntax
  325.      * checking is login validation this logic will also
  326.      * catch people typing syntacitcally valid but
  327.      * unacceptable commands just to remain connected.
  328.      */
  329.     else if (cmd(p->token)) {
  330. if (p->token != T_REST)
  331.     restart_point = 0;
  332. consecutiveBadCmds = 0;
  333. continue;
  334.     }
  335. }
  336. /*
  337.  * If too many consecutive bad commands have been
  338.  * received disconnect.  This is to safeguard against
  339.  * a client that spews trash down the control connection.
  340.  */
  341. if (++consecutiveBadCmds >= maxConsecutiveBadCmds) {
  342.     /*
  343.      * Check for shutdown so that any shutdown message
  344.      * will get prepended to client reply.
  345.      */
  346.     (void) isShutdown(!IS(LOGGEDIN));
  347.     reply(221, "Server shutting down.  Goodbye.");
  348.     dologout(0);
  349. }
  350.     }
  351.     Dispatcher::instance().startTimer(idleTimeout, 0, this);
  352.     return (0);
  353. }
  354. /*
  355.  * Protocol command (one line).
  356.  */
  357. bool
  358. HylaFAXServer::cmd(Token t)
  359. {
  360.     fxStr s;
  361.     long n;
  362.     switch (t) {
  363.     case T_USER: // user name
  364. if (multi_string_param(s, "user name")) {
  365.     logcmd(t, "%s", (const char*) s);
  366.     userCmd(s);
  367.     return (true);
  368. }
  369. break;
  370.     case T_PASS: // user password
  371. if (multi_string_param(s, "password")) {
  372.     logcmd(t, "<password>");
  373.     passCmd(s);
  374.     return (true);
  375. }
  376. break;
  377.     case T_ADMIN: // administrator password
  378. if (opt_CRLF())
  379.     s = "";
  380. else if (!multi_string_param(s, "password"))
  381.     break;
  382. logcmd(t, "<password>");
  383. adminCmd(s);
  384. return (true);
  385.     case T_REIN: // reinitialize server
  386. logcmd(t);
  387. if (IS(LOGGEDIN))
  388.     int ignore = chdir("/"); // return to top of spooling area
  389. initServer();
  390. reply(220, "%s server (%s) ready.", (const char*) hostname, version);
  391. break;
  392.     case T_REST: // restart data transfer
  393. if (number_param(n)) {
  394.     logcmd(t, "%lu", n);
  395.     restart_point = n;
  396.     reply(350, "Data transfer will restart at %lu, "
  397. "send transfer command", n);
  398.     return (true);
  399. }
  400. break;
  401.     case T_SYST: // system identification
  402. if (CRLF()) {
  403.     logcmd(t);
  404.     // this is what we *emulate*
  405.     reply(215, "%s", (const char*) systemType);
  406.     return (true);
  407. }
  408. break;
  409.     case T_EPRT: // extended port for data transfer
  410.     case T_PORT: // port for data transfer
  411. if (SPACE() && hostPort() && CRLF()) {
  412.     portCmd(t);
  413.     return (true);
  414. }
  415. break;
  416.     case T_EPSV: // extended passive mode
  417.     case T_PASV: // enable passive mode
  418. if (CRLF()) {
  419.     logcmd(t);
  420.     passiveCmd();
  421.     return (true);
  422. }
  423. break;
  424.     case T_FORM: // document format
  425. if (string_param(s, "document format")) {
  426.     logcmd(t, "%s", (const char*) s);
  427.     formCmd(s);
  428.     return (true);
  429. }
  430. break;
  431.     case T_MODE: // data transfer mode
  432. if (string_param(s, "transfer mode")) {
  433.     logcmd(t, "%s", (const char*) s);
  434.     modeCmd(s);
  435.     return (true);
  436. }
  437. break;
  438.     case T_STRU: // data transfer file structure
  439. if (string_param(s, "file structure")) {
  440.     logcmd(t, "%s", (const char*) s);
  441.     struCmd(s);
  442.     return (true);
  443. }
  444. break;
  445.     case T_TYPE: // data transfer type
  446. if (string_param(s, "transfer type")) {
  447.     logcmd(t, "%s", (const char*) s);
  448.     typeCmd(s);
  449.     return (true);
  450. }
  451. break;
  452.     case T_RETP: // retrieve next page of document/file
  453. if (pathname_param(s)) {
  454.     logcmd(t, "%s", (const char*) s);
  455.     retrievePageCmd(s);
  456.     return (true);
  457. }
  458. break;
  459.     case T_RETR: // retrieve document/file
  460. if (pathname_param(s)) {
  461.     logcmd(t, "%s", (const char*) s);
  462.     retrieveCmd(s);
  463.     return (true);
  464. }
  465. break;
  466.     case T_STOR: // store document/file
  467. if (pathname_param(s)) {
  468.     logcmd(t, "%s", (const char*) s);
  469.     // XXX file must exist if not admin, check for bad chars in pathname
  470.     storeCmd(s, "w");
  471.     return (true);
  472. }
  473. break;
  474.     case T_APPE: // append to document/file
  475. if (pathname_param(s)) {
  476.     logcmd(t, "%s", (const char*) s);
  477.     storeCmd(s, "a");
  478.     return (true);
  479. }
  480. break;
  481.     case T_STOT: // store uniquely named document/file
  482. if (CRLF()) {
  483.     logcmd(t);
  484.     storeUniqueCmd(true);
  485.     return (true);
  486. }
  487. break;
  488.     case T_STOU: // store uniquely named document/file
  489. if (CRLF()) {
  490.     logcmd(t);
  491.     storeUniqueCmd(false);
  492.     return (true);
  493. }
  494. break;
  495.     case T_LIST: // list directory/file
  496.     case T_NLST: // list directory/file names
  497. if (opt_CRLF()) {
  498.     logcmd(t);
  499.     if (t == T_LIST)
  500. listCmd(".");
  501.     else
  502. nlstCmd(".");
  503.     return (true);
  504. } else if (pathname_param(s)) {
  505.     logcmd(t, "%s", (const char*) s);
  506.     if (t == T_LIST)
  507. listCmd(s);
  508.     else
  509. nlstCmd(s);
  510.     return (true);
  511. }
  512. break;
  513.     case T_CWD: // change working directory
  514. if (opt_CRLF()) {
  515.     logcmd(t);
  516.     cwdCmd("/");
  517.     return (true);
  518. } else if (pathname_param(s)) {
  519.     logcmd(t, "%s", (const char*) s);
  520.     cwdCmd(s);
  521.     return (true);
  522. }
  523. break;
  524.     case T_CDUP: // CWD ..
  525. logcmd(t);
  526. cwdCmd("..");
  527. return (true);
  528.     case T_PWD: // print working directory
  529. if (CRLF()) {
  530.     logcmd(t);
  531.     pwdCmd();
  532.     return (true);
  533. }
  534. break;
  535.     case T_STAT: // stat file/server status
  536. if (opt_CRLF()) {
  537.     logcmd(t);
  538.     statusCmd();
  539.     return (true);
  540. } else if (checklogin(T_STAT) && pathname_param(s)) {
  541.     logcmd(t, "%s", (const char*) s);
  542.     statFileCmd(s);
  543.     return (true);
  544. }
  545. break;
  546.     case T_SIZE: // return file size
  547. if (pathname_param(s)) {
  548.     logcmd(t, "%s", (const char*) s);
  549.     struct stat sb;
  550.     if (Sys::stat(s, sb) < 0 || (sb.st_mode&S_IFMT) != S_IFREG)
  551. reply(550, "%s: not a plain file.", (const char*) s);
  552.     else
  553. reply(213, "%lu", (u_long) sb.st_size);
  554.     return (true);
  555. }
  556. break;
  557.     case T_CHMOD: // set file protection
  558. if (SPACE() && STRING(s, "filename") && SPACE() && NUMBER(n)) {
  559.     chmodCmd(s, (u_int) n);
  560.     return (true);
  561. }
  562. break;
  563.     case T_MDTM: // return file last modification time
  564. if (pathname_param(s)) {
  565.     logcmd(t, "%s", (const char*) s);
  566.     mdtmCmd(s);
  567.     return (true);
  568. }
  569. break;
  570.     case T_DELE: // delete file/document
  571. if (pathname_param(s)) {
  572.     logcmd(t, "%s", (const char*) s);
  573.     deleCmd(s);
  574.     return (true);
  575. }
  576. break;
  577.     case T_ABOR: // abort active command/incoming call
  578. if (opt_CRLF()) {
  579.     logcmd(t);
  580.     ack(225, cmdToken(t));
  581.     return (true);
  582. } else if (checkadmin(T_ABOR) && string_param(s, "modem")) {
  583.     abortCallCmd(s);
  584.     return (true);
  585. }
  586. break;
  587.     case T_HELP: // return help
  588. if (opt_CRLF()) {
  589.     logcmd(t);
  590.     helpCmd(cmdtab, (char*) NULL);
  591.     return (true);
  592. } else if (string_param(s, "command name")) {
  593.     logcmd(t, "%s", (const char*) s);
  594.     s.raisecase();
  595.     if (s == "SITE")
  596. helpCmd(sitetab, NULL);
  597.     else if (s == "JPARM" || s == "JGPARM")
  598. helpCmd(parmtab, NULL);
  599.     else
  600. helpCmd(cmdtab, s);
  601.     return (true);
  602. }
  603. break;
  604.     case T_JNEW: // create new job
  605. if (CRLF()) {
  606.     newJobCmd();
  607.     return (true);
  608. }
  609. break;
  610.     case T_JOB: // select current job
  611. if (job_param(s)) {
  612.     setCurrentJob(s);
  613.     return (true);
  614. }
  615. break;
  616.     case T_JREST: // reset job state
  617. if (job_param(s)) {
  618.     resetJob(s);
  619.     return (true);
  620. }
  621. break;
  622.     case T_JDELE: // delete job
  623. if (job_param(s)) {
  624.     deleteJob(s);
  625.     return (true);
  626. }
  627. break;
  628.     case T_JINTR: // interrupt job
  629. if (job_param(s)) {
  630.     interruptJob(s);
  631.     return (true);
  632. }
  633. break;
  634.     case T_JKILL: // kill job
  635. if (job_param(s)) {
  636.     killJob(s);
  637.     return (true);
  638. }
  639. break;
  640.     case T_JSUSP: // suspend job
  641. if (job_param(s)) {
  642.     suspendJob(s);
  643.     return (true);
  644. }
  645. break;
  646.     case T_JSUB: // submit job
  647. if (job_param(s)) {
  648.     submitJob(s);
  649.     return (true);
  650. }
  651. break;
  652.     case T_JWAIT: // wait for job
  653. if (job_param(s)) {
  654.     waitForJob(s);
  655.     return (true);
  656. }
  657. break;
  658.     case T_JGNEW: // create job group
  659. if (CRLF()) {
  660.     curJob->groupid = curJob->jobid;
  661.     ack(200, cmdToken(t));
  662.     return (true);
  663. }
  664. break;
  665.     case T_JGREST: // reset current job group state
  666. if (CRLF()) {
  667.     return (true);
  668. }
  669. break;
  670.     case T_JGRP: // select current job group
  671. if (jgrp_param(s)) {
  672.     return (true);
  673. }
  674. break;
  675.     case T_JGDELE: // delete job group
  676. if (jgrp_param(s)) {
  677.     return (true);
  678. }
  679. break;
  680.     case T_JGINTR: // interrupt job group
  681. if (jgrp_param(s)) {
  682.     return (true);
  683. }
  684. break;
  685.     case T_JGKILL: // kill job group
  686. if (jgrp_param(s)) {
  687.     return (true);
  688. }
  689. break;
  690.     case T_JGSUSP: // suspend job group
  691. if (jgrp_param(s)) {
  692.     return (true);
  693. }
  694. break;
  695.     case T_JGSUB: // submit job group
  696. if (jgrp_param(s)) {
  697.     return (true);
  698. }
  699. break;
  700.     case T_JGWAIT: // wait for job group
  701. if (jgrp_param(s)) {
  702.     return (true);
  703. }
  704. break;
  705.     case T_NOOP: // no-op
  706. if (CRLF()) {
  707.     logcmd(t);
  708.     ack(200, cmdToken(t));
  709.     return (true);
  710. }
  711. break;
  712.     case T_IDLE: // set/query idle timeout
  713. if (opt_CRLF()) {
  714.     logcmd(t);
  715.     reply(213, "%u seconds.", idleTimeout);
  716.     return (true);
  717. } else if (number_param(n)) {
  718.     logcmd(t, "%lu", n);
  719.     if ((unsigned)n > maxIdleTimeout && !IS(PRIVILEGED)) {
  720. idleTimeout = maxIdleTimeout;
  721. reply(213, "%lu: Idle timeout too large, set to %u.",
  722.     n, maxIdleTimeout);
  723.     } else {
  724. idleTimeout = (int) n;
  725. reply(213, "Idle timeout set to %u.", idleTimeout);
  726.     }
  727.     return (true);
  728. }
  729. break;
  730.     case T_QUIT: // terminate session
  731. if (CRLF()) {
  732.     logcmd(t);
  733.     reply(221, "Goodbye.");
  734.     dologout(0);
  735.     return (true);
  736. }
  737. break;
  738.     case T_TZONE: // set/query timezone usage
  739. if (opt_CRLF()) {
  740.     reply(200, "Time values are handled in %s.", 
  741. IS(USEGMT) ? "GMT" : tzname[0]);
  742.     return (true);
  743. } else if (string_param(s, "timezone specification")) {
  744.     logcmd(t, "%s", (const char*) s);
  745.     s.raisecase();
  746.     if (s == "GMT") {
  747. state |= S_USEGMT;
  748. reply(200, "Using time values in GMT.");
  749.     } else if (s == "LOCAL") {
  750. state &= ~S_USEGMT;
  751. reply(200, "Using time values in %s.", tzname[0]);
  752.     } else {
  753. reply(503, "Unknown timezone specification %s.",
  754.     (const char*) s);
  755.     }
  756.     return (true);
  757. }
  758. break;
  759.     case T_JOBFMT: // query/specify job format string
  760.     case T_RCVFMT: // query/specify recvq format string
  761.     case T_MODEMFMT: // query/specify modem format string
  762.     case T_FILEFMT: // query/specify file format string
  763. if (opt_CRLF()) {
  764.     reply(200, "%s",
  765. t == T_JOBFMT ?   (const char*) jobFormat :
  766. t == T_RCVFMT ?   (const char*) recvFormat:
  767. t == T_MODEMFMT ? (const char*) modemFormat :
  768.   (const char*) fileFormat
  769.     );
  770.     return (true);
  771. } else if (string_param(s, "format-string")) {
  772.     if (t == T_JOBFMT)
  773. jobFormat = s;
  774.     else if (t == T_RCVFMT)
  775. recvFormat = s;
  776.     else if (t == T_MODEMFMT)
  777. modemFormat = s;
  778.     else if (t == T_FILEFMT)
  779. fileFormat = s;
  780.     ack(200, cmdToken(t));
  781.     return (true);
  782. }
  783. break;
  784.     case T_JPARM: // set/query job parameter
  785. if (opt_CRLF()) {
  786.     logcmd(T_JPARM);
  787.     if (checkJobState(curJob))
  788. jstatCmd(*curJob);
  789.     return (true);
  790. } else if (SPACE() && getToken(T_STRING, "parameter name")) {
  791.     tokenBody.raisecase();
  792.     const tab* p = lookup(parmtab, N(parmtab), tokenBody);
  793.     if (p == NULL) {
  794. reply(500, "JPARM %s: Parameter not recognized.",
  795.     (const char*) tokenBody);
  796.     } else if (!p->implemented)
  797. reply(502, "JPARM %s: Parameter not implemented.", p->name);
  798.     else
  799. return (param_cmd(p->token));
  800. }
  801. break;
  802.     case T_JGPARM: // set/query job group parameter
  803. if (SPACE() && getToken(T_STRING, "parameter name")) {
  804.     tokenBody.raisecase();
  805.     const tab* p = lookup(parmtab, N(parmtab), tokenBody);
  806.     if (p == NULL) {
  807. reply(500, "JPARM %s: Parameter not recognized.",
  808.     (const char*) tokenBody);
  809.     } else if (!p->implemented)
  810. reply(502, "JPARM %s: Parameter not implemented.", p->name);
  811.     else
  812. return (param_cmd(p->token));
  813. }
  814. break;
  815.     case T_SITE: // site-specific command
  816. if (SPACE() && getToken(T_STRING, "site command")) {
  817.     tokenBody.raisecase();
  818.     const tab* p = lookup(sitetab, N(sitetab), tokenBody);
  819.     if (p == NULL) {
  820. reply(500, "SITE %s: Command not recognized.",
  821.     (const char*) tokenBody);
  822.     } else if (!p->implemented)
  823. reply(502, "SITE %s: Command not implemented.", p->name);
  824.     else
  825. return (site_cmd(p->token));
  826. }
  827. break;
  828.     case T_ANSWER: // answer phone on specific line
  829. if (checkadmin(T_ANSWER) && SPACE() && STRING(s, "modem")) {
  830.     fxStr how;
  831.     if (opt_CRLF())
  832. how = "any";
  833.     else if (!string_param(how, "answer-how"))
  834. break;
  835.     answerCallCmd(s, how);
  836.     return (true);
  837. }
  838. break;
  839.     case T_CHOWN: // assign file ownership
  840. if (checkadmin(T_CHOWN) && SPACE() && STRING(s, "filename")) {
  841.     fxStr who;
  842.     if (string_param(who, "user")) {
  843. chownCmd(s, who);
  844. return (true);
  845.     }
  846. }
  847. break;
  848.     case T_DISABLE: // disable outbound-usage of modem
  849. if (checkadmin(T_DISABLE) && SPACE() && STRING(s, "modem")) {
  850.     fxStr reason;
  851.     if (opt_CRLF())
  852. reason = "<unspecified reason>";
  853.     else if (!SPACE() || !multi_STRING(reason))
  854. break;
  855.     disableModemCmd(s, reason);
  856.     return (true);
  857. }
  858. break;
  859.     case T_ENABLE: // enable outbound-usage of modem
  860. if (checkadmin(T_ENABLE) && string_param(s, "modem")) {
  861.     enableModemCmd(s);
  862.     return (true);
  863. }
  864. break;
  865.     case T_SHUT: // shutdown server
  866. if (checkadmin(T_SHUT) && SPACE() && STRING(s, "shutdown-time")) {
  867.     fxStr reason;
  868.     if (opt_CRLF())
  869. reason = "<unspecified reason>";
  870.     else if (!SPACE() || !multi_STRING(reason))
  871. break;
  872.     const char* cp = s;
  873.     if (s.length() == 3 && strcasecmp(cp, "NOW") == 0) {
  874. time_t t = Sys::now();
  875. shutCmd(*localtime(&t), reason);
  876. return (true);
  877.     } else if (s.length() == 4 && checkNUMBER(cp)) {
  878. time_t t = Sys::now();
  879. struct tm tm = *localtime(&t);
  880. tm.tm_hour = 60*60*twodigits(cp, 60);
  881. tm.tm_min  =    60*twodigits(cp+2, 60);
  882. shutCmd(tm, reason);
  883. return (true);
  884.     } else if (s.length() == 12 && checkNUMBER(cp)) {
  885. struct tm tm;
  886. tm.tm_sec  = 0;
  887. tm.tm_min  = twodigits(cp+10, 60);
  888. tm.tm_hour = twodigits(cp+8, 24);
  889. tm.tm_mday = twodigits(cp+6, 31);
  890. tm.tm_mon  = twodigits(cp+4, 12) -1;
  891. tm.tm_year = fourdigits(cp+0) - 1900;
  892. shutCmd(tm, reason);
  893. return (true);
  894.     } else
  895. syntaxError("bad time specification");
  896. }
  897. break;
  898.     case T_VRFY: // verify server to use for destination
  899. if (string_param(s, "dialstring")) {
  900.     // XXX at least support a static file...
  901.     reply(200, "%s (%s)"
  902. , (const char*) hostname
  903. , (const char*) hostaddr
  904.     );
  905.     return (true);
  906. }
  907. break;
  908.     default:
  909. break;
  910.     }
  911.     return (false);
  912. }
  913. /*
  914.  * Site-specific protocol commands (one line).
  915.  */
  916. bool
  917. HylaFAXServer::site_cmd(Token t)
  918. {
  919.     fxStr s;
  920.     long n;
  921.     switch (t) {
  922.     case T_ADDUSER:
  923. if (checkadmin(T_ADDUSER) && SPACE() && STRING(s, "user-spec")) {
  924.     fxStr pass;
  925.     if (opt_CRLF()) {
  926. addUserCmd(s, "", "");
  927. return (true);
  928.     } else if (SPACE() && STRING(pass, "password")) {
  929. fxStr apass;
  930. if (opt_CRLF()) {
  931.     addUserCmd(s, pass, "");
  932.     return (true);
  933. } else if (string_param(apass, "admin-password")) {
  934.     addUserCmd(s, pass, apass);
  935.     return (true);
  936. }
  937.     }
  938. }
  939. break;
  940.     case T_DELUSER:
  941. if (checkadmin(T_DELUSER) && string_param(s, "user-spec")) {
  942.     delUserCmd(s);
  943.     return (true);
  944. }
  945. break;
  946.     case T_DELMODEM:
  947. if (checkadmin(T_DELMODEM) && string_param(s, "modem")) {
  948.     delModemCmd(s);
  949.     return (true);
  950. }
  951. break;
  952.     case T_ADDMODEM:
  953. if (checkadmin(T_ADDMODEM) && string_param(s, "modem")) {
  954.     addModemCmd(s);
  955.     return (true);
  956. }
  957. break;
  958.     case T_CONFIG:
  959. if (checkadmin(T_CONFIG) && SPACE() && STRING(s, "modem")) {
  960.     fxStr config;
  961.     if (opt_CRLF()) {
  962. configQueryCmd(s);
  963. return (true);
  964.     } else if (SPACE() && multi_STRING(config)) {
  965. configCmd(s, config);
  966. return (true);
  967.     }
  968. }
  969. break;
  970.     case T_TRIGGER:
  971. if (string_param(s, "trigger-spec")) {
  972.     logcmd(t, "%s", (const char*)s);
  973.     triggerCmd("%s", (const char*) s);
  974.     return (true);
  975. }
  976. break;
  977.     case T_HELP: // return help
  978. if (opt_CRLF()) {
  979.     helpCmd(sitetab, (char*) NULL);
  980.     return (true);
  981. } else if (string_param(s, "command name")) {
  982.     logcmd(T_SITE, "HELP %s", (const char*) s);
  983.     s.raisecase();
  984.     helpCmd(sitetab, s);
  985.     return (true);
  986. }
  987. break;
  988.     case T_LOCKWAIT:
  989. if (opt_CRLF()) {
  990.     logcmd(t);
  991.     reply(213, "%u seconds.", lockTimeout);
  992.     return (true);
  993. } else if (number_param(n)) {
  994.     logcmd(t, "%lu", n);
  995.     if ((unsigned)n > maxLockTimeout && !IS(PRIVILEGED)) {
  996. lockTimeout = maxLockTimeout;
  997. reply(213, "%lu: Lock timeout too large, set to %u.",
  998.     n, maxLockTimeout);
  999.     } else {
  1000. lockTimeout = (int) n;
  1001. reply(213, "Lock timeout set to %u.", lockTimeout);
  1002.     }
  1003.     return (true);
  1004. }
  1005.     default:
  1006. break;
  1007.     }
  1008.     return (false);
  1009. }
  1010. bool
  1011. HylaFAXServer::param_cmd(Token t)
  1012. {
  1013.     fxStr s;
  1014.     long n;
  1015.     time_t ticks;
  1016.     bool b;
  1017.     switch (t) {
  1018.     case T_SENDTIME: // time to send job
  1019. if (opt_CRLF()) {
  1020.     replyJobParamValue(*curJob, 213, t);
  1021.     return (true);
  1022. } else if (SPACE() && getToken(T_STRING, "time specification")) {
  1023.     tokenBody.raisecase();
  1024.     if (tokenBody == "NOW") {
  1025. if (CRLF() && setJobParameter(*curJob, t, (time_t) 0)) {
  1026.     reply(213, "%s set to NOW.", parmToken(t));
  1027.     return (true);
  1028. }
  1029.     } else {
  1030. pushToken(T_STRING); // XXX
  1031. if (TIMESPEC(12, ticks) && CRLF() &&
  1032.   setJobParameter(*curJob, t, ticks)) {
  1033.     if (ticks != 0) {
  1034. const struct tm* tm = cvtTime(ticks);
  1035. // XXX should response include seconds?
  1036. reply(213, "%s set to %4d%02d%02d%02d%02d%02d."
  1037.     , parmToken(t)
  1038.     , tm->tm_year+1900
  1039.     , tm->tm_mon+1
  1040.     , tm->tm_mday
  1041.     , tm->tm_hour
  1042.     , tm->tm_min
  1043.     , tm->tm_sec
  1044. );
  1045.     } else
  1046. reply(213, "%s set to NOW.", parmToken(t));
  1047.     return (true);
  1048. }
  1049.     }
  1050. }
  1051. break;
  1052.     case T_LASTTIME: // time to kill job
  1053. if (opt_CRLF()) {
  1054.     replyJobParamValue(*curJob, 213, t);
  1055.     return (true);
  1056. } else if (timespec_param(6, ticks) &&
  1057.   setJobParameter(*curJob, t, ticks)) {
  1058.     reply(213, "%s set to %02d%02d%02d."
  1059. , parmToken(t)
  1060. , ticks/(24*60*60)
  1061. , (ticks/(60*60))%24
  1062. , (ticks/60)%60
  1063.     );
  1064.     return (true);
  1065. }
  1066. break;
  1067.     case T_RETRYTIME: // retry interval for job
  1068. if (opt_CRLF()) {
  1069.     replyJobParamValue(*curJob, 213, t);
  1070.     return (true);
  1071. } else if (timespec_param(4, ticks) &&
  1072.   setJobParameter(*curJob, t, ticks)) {
  1073.     reply(213, "%s set to %02d%02d."
  1074. , parmToken(t)
  1075. , ticks/60
  1076. , ticks%60
  1077.     );
  1078.     return (true);
  1079. }
  1080. break;
  1081.     case T_MAXDIALS:
  1082.     case T_MAXTRIES:
  1083.     case T_VRES:
  1084.     case T_PAGEWIDTH:
  1085.     case T_PAGELENGTH:
  1086.     case T_MINBR:
  1087.     case T_BEGBR:
  1088.     case T_BEGST:
  1089.     case T_MAXPAGES:
  1090.     case T_SCHEDPRI:
  1091.     case T_NPAGES:
  1092.     case T_SKIPPAGES:
  1093.     case T_SKIPPEDPAGES:
  1094.     case T_NOCOUNTCOVER:
  1095.     case T_TOTPAGES:
  1096.     case T_NTRIES:
  1097.     case T_TOTTRIES:
  1098.     case T_NDIALS:
  1099.     case T_TOTDIALS:
  1100. if (opt_CRLF()) {
  1101.     replyJobParamValue(*curJob, 213, t);
  1102.     return (true);
  1103. } else if (number_param(n) &&
  1104.   setJobParameter(*curJob, t, (u_short) n)) {
  1105.     reply(213, "%s set to %u.", parmToken(t), n);
  1106.     return (true);
  1107. }
  1108. break;
  1109.     case T_NOTIFYADDR:
  1110.     case T_NOTIFY:
  1111.     case T_MODEM:
  1112.     case T_DIALSTRING:
  1113.     case T_EXTERNAL:
  1114.     case T_SUBADDR:
  1115.     case T_DATAFORMAT:
  1116.     case T_TO_USER:
  1117.     case T_TO_LOCATION:
  1118.     case T_TO_COMPANY:
  1119.     case T_TO_VOICE:
  1120.     case T_FROM_USER:
  1121.     case T_FROM_LOCATION:
  1122.     case T_FROM_COMPANY:
  1123.     case T_FROM_VOICE:
  1124.     case T_USRKEY:
  1125.     case T_PASSWD:
  1126.     case T_CLIENT:
  1127.     case T_PAGECHOP:
  1128.     case T_TAGLINE:
  1129.     case T_GROUPID:
  1130.     case T_JOBID:
  1131.     case T_JOBINFO:
  1132.     case T_OWNER:
  1133.     case T_STATE:
  1134.     case T_STATUS:
  1135.     case T_ERRORCODE:
  1136.     case T_DONEOP:
  1137.     case T_COMMID:
  1138.     case T_REGARDING:
  1139.     case T_COMMENTS:
  1140.     case T_TIMEOFDAY:
  1141.     case T_FAXNUMBER:
  1142.     case T_FAXNAME:
  1143.     case T_TSI:
  1144.     case T_ECMTYPE:
  1145. if (opt_CRLF()) {
  1146.     replyJobParamValue(*curJob, 213, t);
  1147.     return (true);
  1148. } else if (SPACE() && multi_STRING(s) && CRLF() &&
  1149.   setJobParameter(*curJob, t, s)) {
  1150.     reply(213, "%s set to "%s".", parmToken(t), (const char*) s);
  1151.     return (true);
  1152. }
  1153. break;
  1154.     case T_USE_ECM:
  1155.     case T_USE_TAGLINE:
  1156.     case T_USE_CONTCOVER:
  1157.     case T_SERVERDOCOVER:
  1158.     case T_IGNOREMODEMBUSY:
  1159.     case T_USE_XVRES:
  1160. if (opt_CRLF()) {
  1161.     replyJobParamValue(*curJob, 213, t);
  1162.     return (true);
  1163. } else if (boolean_param(b) &&
  1164.   setJobParameter(*curJob, t, b)) {
  1165.     reply(213, "%s set to %s.", parmToken(t), b ? "YES" : "NO");
  1166.     return (true);
  1167. }
  1168. break;
  1169.     case T_CHOPTHRESH:
  1170. if (opt_CRLF()) {
  1171.     replyJobParamValue(*curJob, 213, t);
  1172.     return (true);
  1173. } else if (string_param(s, "floating point number") &&
  1174.   setJobParameter(*curJob, t, (float) atof(s))) {
  1175.     reply(213, "%s set to %g.", parmToken(t), (float) atof(s));
  1176.     return (true);
  1177. }
  1178. break;
  1179.      case T_COVER: // specify/query cover page document
  1180. if (opt_CRLF()) {
  1181.     replyJobParamValue(*curJob, 213, t);
  1182.     return (true);
  1183. } else if (pathname_param(s)) {
  1184.     addCoverDocument(*curJob, s);
  1185.     return (true);
  1186. }
  1187. break;
  1188.     case T_DOCUMENT: // specify/query normal document
  1189. if (opt_CRLF()) {
  1190.     replyJobParamValue(*curJob, 213, t);
  1191.     return (true);
  1192. } else if (pathname_param(s)) {
  1193.     addDocument(*curJob, s);
  1194.     return (true);
  1195. }
  1196. break;
  1197.     case T_POLL: // specify/query poll operation
  1198. if (opt_CRLF()) {
  1199.     replyJobParamValue(*curJob, 213, t);
  1200.     return (true);
  1201. } else if (SPACE() && STRING(s, "polling selector")) {
  1202.     fxStr pwd;
  1203.     if (opt_CRLF()) {
  1204. addPollOp(*curJob, s, ""); // sep but no pwd
  1205. return (true);
  1206.     } else if (SPACE() && pwd_param(pwd) && CRLF()) {
  1207. addPollOp(*curJob, s, pwd); // sep & pwd
  1208. return (true);
  1209.     }
  1210. }
  1211. break;
  1212.     case T_HELP: // return help
  1213. if (opt_CRLF()) {
  1214.     helpCmd(parmtab, (char*) NULL);
  1215.     return (true);
  1216. } else if (string_param(s, "parameter name")) {
  1217.     s.raisecase();
  1218.     logcmd(T_JPARM, "HELP %s", (const char*) s);
  1219.     helpCmd(parmtab, s);
  1220.     return (true);
  1221. }
  1222. break;
  1223.     default:
  1224. break;
  1225.     }
  1226.     return (false);
  1227. }
  1228. /*
  1229.  * Collect a password.
  1230.  */
  1231. bool
  1232. HylaFAXServer::multi_string_param(fxStr& s, const char* what)
  1233. {
  1234.     return SPACE() && multi_STRING(s, what) && CRLF();
  1235. }
  1236. /*
  1237.  * Collect a single string parameter.
  1238.  */
  1239. bool
  1240. HylaFAXServer::string_param(fxStr& s, const char* what)
  1241. {
  1242.     return SPACE() && STRING(s, what) && CRLF();
  1243. }
  1244. /*
  1245.  * Collect a single number parameter.
  1246.  */
  1247. bool
  1248. HylaFAXServer::number_param(long& n)
  1249. {
  1250.     return SPACE() && NUMBER(n) && CRLF();
  1251. }
  1252. /*
  1253.  * Collect a single boolean parameter.
  1254.  */
  1255. bool
  1256. HylaFAXServer::boolean_param(bool& b)
  1257. {
  1258.     return SPACE() && BOOLEAN(b) && CRLF();
  1259. }
  1260. /*
  1261.  * Collect a single time parameter.
  1262.  */
  1263. bool
  1264. HylaFAXServer::timespec_param(int ndigits, time_t& t)
  1265. {
  1266.     return SPACE() && TIMESPEC(ndigits, t) && CRLF();
  1267. }
  1268. /*
  1269.  * File manipulation command.
  1270.  */
  1271. bool
  1272. HylaFAXServer::pathname_param(fxStr& s)
  1273. {
  1274.     return SPACE() && pathname(s) && CRLF();
  1275. }
  1276. /*
  1277.  * Job control commands.
  1278.  */
  1279. bool
  1280. HylaFAXServer::job_param(fxStr& jid)
  1281. {
  1282.     if (opt_CRLF()) {
  1283. jid = curJob->jobid;
  1284. return (true);
  1285.     } else if (SPACE() && STRING(jid, "job identifer") && CRLF()) {
  1286. jid.lowercase();
  1287. return (true);
  1288.     }
  1289.     return (false);
  1290. }
  1291. /*
  1292.  * Job group control commands.
  1293.  */
  1294. bool
  1295. HylaFAXServer::jgrp_param(fxStr& jgid)
  1296. {
  1297.     if (opt_CRLF()) {
  1298. jgid = curJob->groupid;
  1299. return (true);
  1300.     } else if (SPACE() && STRING(jgid, "job group identifier") && CRLF()) {
  1301. jgid.lowercase();
  1302. return (true);
  1303.     }
  1304.     return (false);
  1305. }
  1306. bool
  1307. HylaFAXServer::pwd_param(fxStr& s)
  1308. {
  1309.     return STRING(s, "polling password");
  1310. }
  1311. bool
  1312. HylaFAXServer::pathname(fxStr& s)
  1313. {
  1314.     return STRING(s, "pathname");
  1315. }
  1316. bool
  1317. HylaFAXServer::TIMESPEC(u_int len, time_t& result)
  1318. {
  1319.     if (getToken(T_STRING, "time specification")) {
  1320. if (tokenBody.length() == len) {
  1321.     if (checkNUMBER(tokenBody)) {
  1322. const char* cp = tokenBody;
  1323. if (len == 12) { // YYYYMMDDHHMM
  1324.     struct tm tm;
  1325.     tm.tm_sec  = 0;
  1326.     tm.tm_min  = twodigits(cp+10, 60);
  1327.     tm.tm_hour = twodigits(cp+8, 24);
  1328.     tm.tm_mday = twodigits(cp+6, 32);
  1329.     tm.tm_mon  = twodigits(cp+4, 13) - 1;
  1330.     tm.tm_year = fourdigits(cp+0) - 1900;
  1331.     tm.tm_isdst = 0; // client gives UTC which never has DST
  1332.     /*
  1333.      * Client specifies time relative to GMT
  1334.      * and mktime returns locally adjusted
  1335.      * time so we need to adjust the result
  1336.      * here to get things in the right timezone.
  1337.      */
  1338.     result = mktime(&tm) - gmtoff;
  1339. } else if (len == 6) { // DDHHMM
  1340.     result = 24*60*60*twodigits(cp, 100)
  1341.    +    60*60*twodigits(cp+2, 24)
  1342.    +       60*twodigits(cp+4, 60);
  1343. } else { // MMSS
  1344.     result = 60*twodigits(cp, 60)
  1345.    +    twodigits(cp+2, 60);
  1346. }
  1347. return (true);
  1348.     }
  1349. } else
  1350.     syntaxError(fxStr::format(
  1351. "bad time specification (expecting %d digits)", len));
  1352.     }
  1353.     return (false);
  1354. }
  1355. bool
  1356. HylaFAXServer::BOOLEAN(bool& b)
  1357. {
  1358.     if (getToken(T_STRING, "boolean")) {
  1359. tokenBody.raisecase();
  1360. if (tokenBody == "YES") {
  1361.     b = true;
  1362.     return (true);
  1363. } else if (tokenBody == "NO") {
  1364.     b = false;
  1365.     return (true);
  1366. } else
  1367.     syntaxError("invalid boolean value, use YES or NO");
  1368.     }
  1369.     return (false);
  1370. }
  1371. bool
  1372. HylaFAXServer::checkToken(Token wanted)
  1373. {
  1374.     Token t = nextToken();
  1375.     if (t != wanted) {
  1376. pushToken(t);
  1377. return (false);
  1378.     } else
  1379. return (true);
  1380. }
  1381. bool
  1382. HylaFAXServer::getToken(Token wanted, const char* expected)
  1383. {
  1384.     Token t = nextToken();
  1385.     if (t != wanted) {
  1386. if (t == T_LEXERR)
  1387.     syntaxError("unmatched quote mark");
  1388. else
  1389.     syntaxError(fxStr::format("expecting %s", expected));
  1390. return (false);
  1391.     } else
  1392. return (true);
  1393. }
  1394. bool HylaFAXServer::SPACE() { return getToken(T_SP, "<SP>"); }
  1395. bool HylaFAXServer::COMMA() { return getToken(T_COMMA, "",""); }
  1396. bool HylaFAXServer::CRLF() { return getToken(T_CRLF, "<CRLF>"); }
  1397. bool HylaFAXServer::opt_CRLF() { return checkToken(T_CRLF); }
  1398. bool
  1399. HylaFAXServer::opt_STRING(fxStr& s)
  1400. {
  1401.     if (checkToken(T_STRING)) {
  1402. s = tokenBody;
  1403. return (true);
  1404.     } else
  1405. return (false);
  1406. }
  1407. bool
  1408. HylaFAXServer::STRING(fxStr& s, const char* what)
  1409. {
  1410.     if (getToken(T_STRING, what != NULL ? what : "<STRING>")) {
  1411. s = tokenBody;
  1412. return (true);
  1413.     } else
  1414. return (false);
  1415. }
  1416. bool
  1417. HylaFAXServer::multi_STRING(fxStr& s, const char* what)
  1418. {
  1419.     if (!STRING(s, what))
  1420. return (false);
  1421.     for (;;) {
  1422. switch (nextToken()) {
  1423. case T_CRLF:
  1424.     pushToken(T_CRLF);
  1425.     return (true);
  1426. case T_COMMA:
  1427.     s.append(',');
  1428.     break;
  1429. case T_STRING:
  1430.     s.append(tokenBody);
  1431.     break;
  1432. case T_SP:
  1433.     s.append(' ');
  1434.     break;
  1435. case T_LEXERR:
  1436.     syntaxError("unmatched quote mark");
  1437.     return (false);
  1438. default:
  1439.     break;
  1440. }
  1441.     }
  1442. }
  1443. bool
  1444. HylaFAXServer::checkNUMBER(const char* cp)
  1445. {
  1446.     if (cp[0] == '-' || cp[0] == '+')
  1447. cp++;
  1448.     for (; *cp; cp++)
  1449. if (!isdigit(*cp)) {
  1450.     syntaxError("invalid number");
  1451.     return (false);
  1452. }
  1453.     return (true);
  1454. }
  1455. bool
  1456. HylaFAXServer::NUMBER(long& n)
  1457. {
  1458.     if (!getToken(T_STRING, "decimal number")) {
  1459. return (false);
  1460.     } else if (!checkNUMBER(tokenBody)) {
  1461. return (false);
  1462.     } else {
  1463. n = strtol(tokenBody, NULL, 0);
  1464. return (true);
  1465.     }
  1466. }
  1467. void
  1468. HylaFAXServer::syntaxError(const char* msg)
  1469. {
  1470.     const char* cp = strchr(cbuf, '');
  1471.     if (cp[-1] == 'n')
  1472. cp--;
  1473.     reply(500, "'%.*s': Syntax error, %s.", cp-cbuf, cbuf, msg);
  1474. }
  1475. /*
  1476.  * Lexical Scanner.
  1477.  */
  1478. /*
  1479.  * Return the next byte of data from the control channel.
  1480.  * If no data is available, either wait for new data to
  1481.  * be received or return EOF.
  1482.  */
  1483. int
  1484. HylaFAXServer::getChar(bool waitForInput)
  1485. {
  1486.     if (recvCC <= 0) {
  1487. // enable non-blocking i/o on control channel
  1488. (void) fcntl(STDIN_FILENO, F_SETFL, ctrlFlags | O_NONBLOCK);
  1489. again:
  1490. do {
  1491.     recvCC = Sys::read(STDIN_FILENO, recvBuf, sizeof (recvBuf));
  1492. } while (recvCC < 0 && errno == EINTR);
  1493. if (recvCC < 0 && errno == EWOULDBLOCK && waitForInput) {
  1494.     Dispatcher& disp = Dispatcher::instance();
  1495.     disp.startTimer(idleTimeout, 0, this);
  1496.     for (state |= S_WAITDATA; IS(WAITDATA); disp.dispatch())
  1497. ;
  1498.     disp.stopTimer(this);
  1499.     goto again;
  1500. }
  1501. (void) fcntl(STDIN_FILENO, F_SETFL, ctrlFlags);
  1502. if (recvCC <= 0) {
  1503.     if (recvCC == 0) { // no more input coming, remove handler
  1504. Dispatcher::instance().unlink(STDIN_FILENO);
  1505. dologout(0); // XXX what about server interactions???
  1506.     }
  1507.     return (EOF);
  1508. }
  1509. recvNext = 0;
  1510.     }
  1511.     recvCC--;
  1512.     return recvBuf[recvNext++] & 0377;
  1513. }
  1514. /*
  1515.  * Push back control channel data.
  1516.  */
  1517. void
  1518. HylaFAXServer::pushCmdData(const char* data, int n)
  1519. {
  1520.     if ((unsigned) recvNext + n > sizeof (recvBuf)) {
  1521. logError("No space to push back urgent data "%.*s"", n, data);
  1522. return;
  1523.     }
  1524.     if (recvNext < n) { // not enough space, copy existing data
  1525. memmove(&recvBuf[n], &recvBuf[recvNext], recvCC);
  1526. recvNext = 0;
  1527.     } else // space available, just adjust recvNext
  1528. recvNext -= n;
  1529.     memcpy(&recvBuf[recvNext], data, n);
  1530.     recvCC += n;
  1531. }
  1532. /*
  1533.  * Return the next line of data received on the control
  1534.  * channel, ignoring any Telnet protocol command sequences.
  1535.  */
  1536. bool
  1537. HylaFAXServer::getCmdLine(char* s, int n, bool waitForInput)
  1538. {
  1539.     char* cp = s;
  1540.     cpos = 0;
  1541.     while (n > 1) {
  1542. int c = getChar(cp != s || waitForInput);
  1543. if (c == EOF)
  1544.     return (false);
  1545. if (c == IAC) { // telnet protocol command
  1546.     c = getChar(true);
  1547.     if (c == EOF)
  1548. return (false);
  1549.     switch (c&0377) {
  1550.     case WILL:
  1551.     case WONT:
  1552. c = getChar(true); // telnet option
  1553. if (c == EOF)
  1554.     return (false);
  1555. printf("%c%c%c", IAC, DONT, c&0377);
  1556. (void) fflush(stdout);
  1557. continue;
  1558.     case DO:
  1559.     case DONT:
  1560. c = getChar(true); // telnet option
  1561. if (c == EOF)
  1562.     return (false);
  1563. printf("%c%c%c", IAC, WONT, c&0377);
  1564. (void) fflush(stdout);
  1565. continue;
  1566.     case IAC: // <IAC><IAC> -> <IAC>
  1567. break;
  1568.     default:    // ignore command
  1569. continue;
  1570.     }
  1571. }
  1572. if (c == 'n') {
  1573.     // convert rn -> n
  1574.     if (cp > s && cp[-1] == 'r')
  1575. cp[-1] = 'n';
  1576.     else
  1577. *cp++ = 'n', n--;
  1578.     break;
  1579. }
  1580. *cp++ = c, n--;
  1581.     }
  1582.     *cp = '';
  1583.     if (TRACE(PROTOCOL))
  1584.         logDebug("command: %s", s);
  1585.     return (true);
  1586. }
  1587. void
  1588. HylaFAXServer::timerExpired(long, long)
  1589. {
  1590.     reply(421,
  1591.       "Timeout (%d seconds): closing control connection.", idleTimeout);
  1592.     if (TRACE(SERVER)) {
  1593. time_t now = Sys::now();
  1594. logInfo("User %s timed out after %d seconds at %.24s"
  1595.     , IS(LOGGEDIN) ? (const char*) the_user : "(noone)"
  1596.     , idleTimeout
  1597.     , asctime(cvtTime(now))
  1598. );
  1599.     }
  1600.     dologout(1);
  1601. }
  1602. Token
  1603. HylaFAXServer::nextToken(void)
  1604. {
  1605.     if (pushedToken != T_NIL) {
  1606. Token t = pushedToken;
  1607. pushedToken = T_NIL;
  1608. return (t);
  1609.     }
  1610.     switch (cbuf[cpos]) {
  1611.     case ' ':
  1612. while (cbuf[++cpos] == ' ') // compress multiple spaces
  1613.     ;
  1614. return (T_SP);
  1615.     case 'n':
  1616. cpos++;
  1617. return (T_CRLF);
  1618.     case ',':
  1619. cpos++;
  1620. return (T_COMMA);
  1621.     case '"':
  1622. /*
  1623.  * Parse quoted string and deal with  escapes.
  1624.  */
  1625. tokenBody.resize(0);
  1626. for (;;) {
  1627.     int c = cbuf[++cpos];
  1628.     if (c == '"') {
  1629. cpos++;
  1630. return (T_STRING);
  1631.     }
  1632.     if (c == 'n') // unmatched quote mark
  1633. return (T_LEXERR);
  1634.     if (c == '\') { //  escape handling
  1635. c = cbuf[++cpos];
  1636. if (isdigit(c)) { // nnn octal escape
  1637.     int v = c - '0';
  1638.     if (isdigit(c = cbuf[cpos+1])) {
  1639. cpos++, v = (v << 3) + (c - '0');
  1640. if (isdigit(c = cbuf[cpos+1]))
  1641.     cpos++, v = (v << 3) + (c - '0');
  1642.     }
  1643.     c = v;
  1644. }
  1645.     }
  1646.     tokenBody.append(c);
  1647. }
  1648. /*NOTREACHED*/
  1649.     }
  1650.     int base = cpos;
  1651.     do {
  1652. cpos++;
  1653.     } while (cbuf[cpos] != ' ' && cbuf[cpos] != 'n' && cbuf[cpos] != ',');
  1654.     tokenBody = fxStr(&cbuf[base], cpos - base);
  1655.     return (T_STRING);
  1656. }
  1657. void
  1658. HylaFAXServer::helpCmd(const tab* ctab, const char* s)
  1659. {
  1660.     const char* type;
  1661.     u_int NCMDS;
  1662.     if (ctab == sitetab) {
  1663.         type = "SITE ";
  1664. NCMDS = N(sitetab);
  1665.     } else if (ctab == parmtab) {
  1666. type = "JPARM ";
  1667. NCMDS = N(parmtab);
  1668.     } else {
  1669.         type = "";
  1670. NCMDS = N(cmdtab);
  1671.     }
  1672.     int width = 0;
  1673.     const tab* c = ctab;
  1674.     for (u_int n = NCMDS; n != 0; c++, n--) {
  1675.         int len = strlen(c->name);
  1676.         if (len > width)
  1677.             width = len;
  1678.     }
  1679.     width = (width + 8) &~ 7;
  1680.     if (s == NULL) {
  1681.         lreply(214, "The following %scommands are recognized %s.",
  1682.             type, "(* =>'s unimplemented)");
  1683.         int columns = 76 / width;
  1684.         if (columns == 0)
  1685.             columns = 1;
  1686.         int lines = (NCMDS + columns - 1) / columns;
  1687.         for (int i = 0; i < lines; i++) {
  1688.             printf("   ");
  1689.             for (int j = 0; j < columns; j++) {
  1690.                 c = &ctab[j*lines + i];
  1691.                 printf("%s%c", c->name, !c->implemented ? '*' : ' ');
  1692.                 if (c + lines >= &ctab[NCMDS])
  1693.                     break;
  1694.                 int w = strlen(c->name) + 1;
  1695.                 while (w < width) {
  1696.                     putchar(' ');
  1697.                     w++;
  1698.                 }
  1699.             }
  1700.             printf("rn");
  1701.         }
  1702.         (void) fflush(stdout);
  1703.         reply(214, "Direct comments to %s.", (const char*) faxContact);
  1704.         return;
  1705.     }
  1706.     c = lookup(ctab, NCMDS, s);
  1707.     if (c == NULL) {
  1708.         reply(502, "Unknown command %s.", s);
  1709.         return;
  1710.     }
  1711.     if (c->implemented)
  1712.         reply(214, "Syntax: %s%s %s", type, c->name, c->help);
  1713.     else
  1714.         reply(214, "%s%-*st%s; unimplemented.", type, width, c->name, c->help);
  1715. }
  1716. void
  1717. HylaFAXServer::logcmd(Token t, const char* fmt ...)
  1718. {
  1719.     if (TRACE(PROTOCOL)) {
  1720. const char* name = cmdToken(t);
  1721. if (!name)
  1722.     name = siteToken(t);
  1723. if (!name)
  1724.     name = "???";
  1725. if (fmt != NULL) {
  1726.     va_list ap;
  1727.     va_start(ap, fmt);
  1728.     vlogInfo(fxStr::format("%s %s", name, fmt), ap);
  1729.     va_end(ap);
  1730. } else
  1731.     logInfo("%s", name);
  1732.     }
  1733. }
  1734. void
  1735. HylaFAXServer::cmdFailure(Token t, const char* why)
  1736. {
  1737.     if (TRACE(SERVER)) {
  1738. const char* cp = cmdToken(t);
  1739. if (!cp)
  1740.     cp = siteToken(t);
  1741. if (cp)
  1742.     logInfo("%s cmd failure - %s", cp, why);
  1743. else
  1744.     logInfo("#%u cmd failure - %s", t, why);
  1745.     }
  1746. }
  1747. bool
  1748. HylaFAXServer::checklogin(Token t)
  1749. {
  1750.     if (!IS(LOGGEDIN)) {
  1751. cmdFailure(t, "not logged in");
  1752. reply(530, "Please login with USER and PASS.");
  1753. return (false);
  1754.     } else
  1755. return (true);
  1756. }
  1757. bool
  1758. HylaFAXServer::checkadmin(Token t)
  1759. {
  1760.     if (checklogin(t)) {
  1761. if (IS(PRIVILEGED))
  1762.     return (true);
  1763. cmdFailure(t, "user is not privileged");
  1764. reply(530, "Please use ADMIN to establish administrative privileges.");
  1765.     }
  1766.     return (false);
  1767. }