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

传真(Fax)编程

开发平台:

C/C++

  1. /* $Id: Jobs.c++,v 1.26 2008/10/12 04:04:28 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. #include "port.h"
  27. #include "config.h"
  28. #include "Sys.h"
  29. #include "HylaFAXServer.h"
  30. #include "Dispatcher.h"
  31. #include "Timeout.h"
  32. #include "class2.h"
  33. #include <sys/file.h>
  34. #include <ctype.h>
  35. /*
  36.  * Job state query and control commands.
  37.  */
  38. Job::Job(const fxStr& qf, int f) : FaxRequest(qf,f)
  39. {
  40.     lastmod = 0;
  41.     queued = false;
  42. }
  43. Job::~Job() {}
  44. /*
  45.  * We need to override the default logic because faxq
  46.  * assumes it's working directory is at the top of the
  47.  * spooling area and we may be elsewhere.  We can also
  48.  * simplify things because we know we're chroot'd to
  49.  * the top of the spooling area--thus we don't need to
  50.  * check for ``..'' and ``/'' being used to reference
  51.  * files outside the spooling area.
  52.  */
  53. bool
  54. Job::checkDocument(const char* pathname)
  55. {
  56.     struct stat sb;
  57.     if (FileCache::lookup(fxStr::format("/%s", pathname), sb))
  58. return (true);
  59.     int fd = Sys::open(pathname, 0);
  60.     if (fd == -1) {
  61. logError("Can not access document file "%s": %s",
  62.     pathname, strerror(errno));
  63. return (false);
  64.     }
  65.     Sys::close(fd);
  66.     logError("Undetermined error with document file "%s"", pathname);
  67.     return (false);
  68. }
  69. fxIMPLEMENT_StrKeyPtrValueDictionary(JobDict, Job*)
  70. /*
  71.  * Job state parameter access controls.
  72.  *
  73.  * There are three levels of access control applied to job state
  74.  * information: administrator, owner, and other.
  75.  * Access is controlled on a read+write basis.  Write accesses
  76.  * can be further restricted to constrain written values to
  77.  * be within a range; this is used to allow users to change
  78.  * parameters within a restricted range (e.g. to up the maximum
  79.  * number of tries but still constrain it to a sane value).
  80.  */
  81. #define A_RUSR 0400 // read permission: owner
  82. #define A_WUSR 0200 // abitrary write permission: owner
  83. #define A_MUSR 0100 // restricted write permission: owner
  84. #define A_RADM 0040 // read permission: administrator
  85. #define A_WADM 0020 // abitrary write permission: administrator
  86. #define A_MADM 0010 // restricted write permission: administrator
  87. #define A_ROTH 0004 // read permission: other
  88. #define A_WOTH 0002 // abitrary write permission: other
  89. #define A_MOTH 0001 // restricted write permission: other
  90. #define A_READ  004
  91. #define A_WRITE  002
  92. #define A_MODIFY 001
  93. #define N(a) (sizeof (a) / sizeof (a[0]))
  94. static const struct {
  95.     Token t;
  96.     u_int protect; // read+write protection
  97. } params[] = {
  98.     { T_BEGBR, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  99.     { T_BEGST, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  100.     { T_CHOPTHRESH, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  101.     { T_CLIENT, A_RUSR|A_RADM|A_WADM|A_ROTH },
  102.     { T_COMMID, A_RUSR|A_RADM|A_ROTH },
  103.     { T_COMMENTS, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  104.     { T_TIMEOFDAY, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  105.     { T_COVER, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  106.     { T_DATAFORMAT, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  107.     { T_DIALSTRING, A_RUSR|A_WUSR|A_RADM|A_WADM },
  108.     { T_DOCUMENT, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  109.     { T_DONEOP, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  110.     { T_EXTERNAL, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  111.     { T_FAXNUMBER, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  112.     { T_FAXNAME, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  113.     { T_FROM_COMPANY, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  114.     { T_FROM_LOCATION, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  115.     { T_FROM_USER, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  116.     { T_FROM_VOICE, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  117.     { T_GROUPID, A_RUSR|A_RADM|A_ROTH },
  118.     { T_IGNOREMODEMBUSY,A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  119.     { T_JOBID, A_RUSR|A_RADM|A_ROTH },
  120.     { T_JOBINFO, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  121.     { T_JOBTYPE, A_RUSR|A_RADM|A_ROTH },
  122.     { T_LASTTIME, A_RUSR|A_MUSR|A_RADM|A_WADM|A_ROTH },
  123.     { T_MAXDIALS, A_RUSR|A_MUSR|A_RADM|A_WADM|A_ROTH },
  124.     { T_MAXPAGES, A_RUSR|A_MUSR|A_RADM|A_WADM|A_ROTH },
  125.     { T_MAXTRIES, A_RUSR|A_MUSR|A_RADM|A_WADM|A_ROTH },
  126.     { T_MINBR, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  127.     { T_MODEM, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  128.     { T_OWNER, A_RUSR|A_RADM|A_WADM|A_ROTH },
  129.     { T_NDIALS, A_RUSR|A_RADM|A_ROTH },
  130.     { T_NOTIFY, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  131.     { T_NOTIFYADDR, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  132.     { T_NPAGES, A_RUSR|A_RADM|A_ROTH },
  133.     { T_NTRIES, A_RUSR|A_RADM|A_ROTH },
  134.     { T_PAGECHOP, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  135.     { T_PAGELENGTH, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  136.     { T_PAGEWIDTH, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  137.     { T_PASSWD, A_RUSR|A_WUSR|A_RADM|A_WADM },
  138.     { T_POLL, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  139.     { T_REGARDING, A_RUSR|A_MUSR|A_RADM|A_WADM|A_ROTH },
  140.     { T_RETRYTIME, A_RUSR|A_MUSR|A_RADM|A_WADM|A_ROTH },
  141.     { T_SCHEDPRI, A_RUSR|A_MUSR|A_RADM|A_WADM|A_ROTH },
  142.     { T_SENDTIME, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  143.     { T_SKIPPAGES, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  144.     { T_SKIPPEDPAGES, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  145.     { T_NOCOUNTCOVER, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  146.     { T_STATE, A_RUSR|A_RADM|A_ROTH },
  147.     { T_STATUS, A_RUSR|A_RADM|A_WADM|A_ROTH },
  148.     { T_ERRORCODE, A_RUSR|A_RADM|A_WADM|A_ROTH },
  149.     { T_SERVERDOCOVER, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  150.     { T_SUBADDR, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  151.     { T_TAGLINE, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  152.     { T_TOTDIALS, A_RUSR|A_RADM|A_ROTH },
  153.     { T_TOTPAGES, A_RUSR|A_RADM|A_ROTH },
  154.     { T_TOTTRIES, A_RUSR|A_RADM|A_ROTH },
  155.     { T_TO_COMPANY, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  156.     { T_TO_LOCATION, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  157.     { T_TO_USER, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  158.     { T_TO_VOICE, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  159.     { T_TSI, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  160.     { T_USE_CONTCOVER, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  161.     { T_USE_ECM, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  162.     { T_ECMTYPE, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  163.     { T_USE_TAGLINE, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  164.     { T_USE_XVRES, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  165.     { T_USRKEY, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  166.     { T_VRES, A_RUSR|A_WUSR|A_RADM|A_WADM|A_ROTH },
  167.     { T_NIL, 0 },
  168. };
  169. /*
  170.  * Check client permission to do the specified operation
  171.  * on the specified job state parameter.  Operation can
  172.  * be a combination of A_READ+A_WRITE+A_MODIFY.  Note that
  173.  * ownership is based on login account and not fax uid;
  174.  * this may need to be rethought.
  175.  */
  176. bool
  177. HylaFAXServer::checkAccess(const Job& job, Token t, u_int op)
  178. {
  179.     u_int m = 0;
  180.     if (t == T_JOB) {
  181.      m = jobProtection;
  182.     } else {
  183. u_int n = N(params)-1;
  184. u_int i = 0;
  185. while (i < n && params[i].t != t)
  186.     i++;
  187. m = params[i].protect;
  188.     }
  189.     if (m&op) // other/public access
  190. return (true);
  191.     if (IS(PRIVILEGED) && ((m>>3)&op)) // administrative access
  192. return (true);
  193.     if (job.owner == the_user && ((m>>6)&op)) // owner access
  194. return (true);
  195.     return (false);
  196. }
  197. static struct {
  198.     Token t;
  199.     fxStr Job::* p;
  200. } strvals[] = {
  201.     { T_JOBTYPE, &Job::jobtype },
  202.     { T_EXTERNAL, &Job::external },
  203.     { T_DIALSTRING, &Job::number },
  204.     { T_NOTIFYADDR, &Job::mailaddr },
  205.     { T_USRKEY, &Job::jobtag },
  206.     { T_MODEM, &Job::modem },
  207.     { T_TO_USER, &Job::receiver },
  208.     { T_TO_COMPANY, &Job::company },
  209.     { T_TO_LOCATION, &Job::location },
  210.     { T_TO_VOICE, &Job::voice },
  211.     { T_FROM_USER, &Job::sender },
  212.     { T_FROM_COMPANY, &Job::fromcompany },
  213.     { T_FROM_LOCATION, &Job::fromlocation },
  214.     { T_FROM_VOICE, &Job::fromvoice },
  215.     { T_PASSWD, &Job::passwd },
  216.     { T_CLIENT, &Job::client },
  217.     { T_FAXNUMBER, &Job::faxnumber },
  218.     { T_FAXNAME, &Job::faxname },
  219.     { T_TSI, &Job::tsi },
  220.     { T_TAGLINE, &Job::tagline },
  221.     { T_SUBADDR, &Job::subaddr },
  222.     { T_GROUPID, &Job::groupid },
  223.     { T_JOBID, &Job::jobid },
  224.     { T_JOBINFO, &Job::jobtag },
  225.     { T_OWNER, &Job::owner },
  226.     { T_STATUS, &Job::notice },
  227.     { T_ERRORCODE, &Job::errorcode },
  228.     { T_DONEOP, &Job::doneop },
  229.     { T_COMMID, &Job::commid },
  230.     { T_REGARDING, &Job::regarding },
  231.     { T_COMMENTS, &Job::comments },
  232.     { T_TIMEOFDAY, &Job::timeofday },
  233. };
  234. static struct {
  235.     Token t;
  236.     u_short Job::* p;
  237. } shortvals[] = {
  238.     { T_TOTPAGES, &Job::totpages },
  239.     { T_NPAGES, &Job::npages },
  240.     { T_SKIPPAGES, &Job::skippages },
  241.     { T_NOCOUNTCOVER, &Job::nocountcover },
  242.     { T_NTRIES, &Job::ntries },
  243.     { T_NDIALS, &Job::ndials },
  244.     { T_TOTDIALS, &Job::totdials },
  245.     { T_MAXDIALS, &Job::maxdials },
  246.     { T_TOTTRIES, &Job::tottries },
  247.     { T_MAXTRIES, &Job::maxtries },
  248.     { T_PAGEWIDTH, &Job::pagewidth },
  249.     { T_PAGELENGTH, &Job::pagelength },
  250.     { T_VRES, &Job::resolution },
  251.     { T_SCHEDPRI, &Job::usrpri },
  252.     { T_MINBR, &Job::minbr },
  253.     { T_BEGBR, &Job::desiredbr },
  254.     { T_BEGST, &Job::desiredst },
  255. };
  256. static struct {
  257.     Token t;
  258.     int Job::* p;
  259. } intvals[] = {
  260.     { T_SKIPPEDPAGES, &Job::skippedpages },
  261. };
  262. static const char* notifyVals[4] = {
  263.     "NONE", // no_notice
  264.     "DONE", // when_done
  265.     "REQUEUE", // when_requeued
  266.     "DONE+REQUEUE" // when_done|when_requeued
  267. };
  268. static const char* chopVals[4] = {
  269.     "DEFAULT", // chop_default
  270.     "NONE", // chop_none
  271.     "ALL", // chop_all
  272.     "LAST" // chop_last
  273. };
  274. static const char* dataVals[] = {
  275.     "G31D", // Group 3, 1-D
  276.     "G32D", // Group 3, 2-D
  277.     "G32DUNC", // Group 3, 2-D (w/ uncompressed)
  278.     "G4" // Group 4
  279. };
  280. static const char* ecmVals[] = {
  281.     "NONE", // 0 = no ECM
  282.     "64BIT", // 64-bit T.30-A ECM
  283.     "256BIT", // 256-bit T.30-A ECM
  284.     "HALFDUPLEX", // Half Duplex T.30-C ECM
  285.     "FULLDUPLEX" // Full Duplex T.30-C ECM
  286. };
  287. static const char* stateVals[] = {
  288.     "UNDEFINED", // undefined state (should never be used)
  289.     "SUSPENDED", // not being scheduled
  290.     "PENDING", // waiting for time to send
  291.     "SLEEPING", // waiting for scheduled timeout
  292.     "BLOCKED", // blocked by concurrent activity
  293.     "READY", // ready to be go, waiting for resources
  294.     "ACTIVE", // actively being processed
  295.     "DONE", // processing completed with success
  296.     "FAILED", // processing completed with failure
  297. };
  298. static const char* docTypeNames[] = {
  299.     "FAX", // send_fax
  300.     "TIFF", // send_tiff
  301.     "TIFF", // send_tiff_saved
  302.     "PDF", // send_pdf
  303.     "PDF", // send_pdf_saved
  304.     "PS", // send_postscript
  305.     "PS", // send_postscript_saved
  306.     "PCL", // send_pcl
  307.     "PCL", // send_pcl_saved
  308.     "DATA", // send_data
  309.     "DATA", // send_data_saved
  310.     "POLL", // send_poll
  311.     "PAGE", // send_page
  312.     "PAGE", // send_page_saved
  313.     "UUCP", // send_uucp
  314.     "UNKNOWN", // send_unknown
  315. };
  316. static const char*
  317. boolString(bool b)
  318. {
  319.     return (b ? "YES" : "NO");
  320. }
  321. void
  322. HylaFAXServer::replyBoolean(int code, bool b)
  323. {
  324.     reply(code, "%s", boolString(b));
  325. }
  326. /*
  327.  * Check that the job's in-memory state is consistent
  328.  * with what is on disk.  If the on-disk state is newer then
  329.  * read it in.  If the job has been removed then remove our
  330.  * reference, reset the current job back to the default job,
  331.  * and send the client an error reply.  This method is used
  332.  * before each place a job's state is access.
  333.  */
  334. bool
  335. HylaFAXServer::checkJobState(Job* job)
  336. {
  337.     /*
  338.      * Verify job is still around (another process has
  339.      * not deleted it) and if the on-disk state has
  340.      * been updated, re-read the job description file.
  341.      */
  342.     struct stat sb;
  343.     if (!FileCache::update("/" | job->qfile, sb)) {
  344. jobs.remove(job->jobid);
  345. if (job == curJob) // make default job current
  346.     curJob = &defJob;
  347. delete job, job = NULL;
  348. return (false);
  349.     }
  350.     if (job->lastmod < sb.st_mtime) {
  351. if (updateJobFromDisk(*job))
  352. job->lastmod = sb.st_mtime;
  353.     }
  354.     return (true);
  355. }
  356. /*
  357.  * Check if it's ok to do the specified operation on the
  358.  * current job's state parameter.  If not, return the appropriate
  359.  * error reply.   We disallow modifications on jobs that
  360.  * do not appear to be suspended since otherwise the mods
  361.  * could be lost (in our case they will be lost due to the
  362.  * way that things work).
  363.  */
  364. bool
  365. HylaFAXServer::checkParm(Job& job, Token t, u_int op)
  366. {
  367.     if (!checkJobState(&job)) { // insure consistent state
  368. reply(500, "Cannot access job state; job deleted by another party.");
  369. return (false);
  370.     } else if (!checkAccess(job, t, op)) {
  371. reply(503, "Permission denied: no %s access to job parameter %s."
  372.     , (op == A_READ ? "read" : "write")
  373.     , parmToken(t)
  374. );
  375. return (false);
  376.     } else if ((op & (A_WRITE|A_MODIFY)) &&
  377.       job.state != FaxRequest::state_suspended && job.jobid != "default") {
  378. reply(503, "Suspend the job with JSUSP first.");
  379. return (false);
  380.     } else
  381. return (true);
  382. }
  383. /*
  384.  * Respond to a job state parameter query.
  385.  */
  386. void
  387. HylaFAXServer::replyJobParamValue(Job& job, int code, Token t)
  388. {
  389.     if (!checkParm(job, t, A_READ))
  390. return;
  391.     u_int i, n;
  392.     switch (t) {
  393.     case T_SENDTIME:
  394. if (job.tts != 0) {
  395.     const struct tm* tm = cvtTime(job.tts);
  396.     // XXX should this include seconds?
  397.     reply(code, "%4d%02d%02d%02d%02d%02d"
  398. , tm->tm_year+1900
  399. , tm->tm_mon+1
  400. , tm->tm_mday
  401. , tm->tm_hour
  402. , tm->tm_min
  403. , tm->tm_sec
  404.     );
  405. } else
  406.     reply(code, "NOW");
  407. return;
  408.     case T_LASTTIME:
  409. time_t tv; tv = job.killtime - job.tts; // XXX for __GNUC__
  410. reply(code, "%02d%02d%02d", tv/(24*60*60), (tv/(60*60))%24, (tv/60)%60);
  411. return;
  412.     case T_RETRYTIME:
  413. reply(code, "%02d%02d", job.retrytime/60, job.retrytime%60);
  414. return;
  415.     case T_STATE:
  416. reply(code, "%s", stateVals[job.state]);
  417. return;
  418.     case T_NOTIFY:
  419. reply(code, "%s", notifyVals[job.notify]);
  420. return;
  421.     case T_PAGECHOP:
  422. reply(code, "%s", chopVals[job.pagechop]);
  423. return;
  424.     case T_CHOPTHRESH:
  425. reply(code, "%g", job.chopthreshold);
  426. return;
  427.     case T_DATAFORMAT:
  428. reply(code, "%s", dataVals[job.desireddf]);
  429. return;
  430.     case T_ECMTYPE:
  431. reply(code, "%s", ecmVals[job.desiredec]);
  432. return;
  433.     case T_USE_ECM:
  434. replyBoolean(code, (job.desiredec != EC_DISABLE ? 1 : 0));
  435. return;
  436.     case T_USE_TAGLINE:
  437. replyBoolean(code, job.desiredtl);
  438. return;
  439.     case T_USE_XVRES:
  440. replyBoolean(code, job.usexvres);
  441. return;
  442.     case T_USE_CONTCOVER:
  443. replyBoolean(code, job.useccover);
  444. return;
  445.     case T_SERVERDOCOVER:
  446. replyBoolean(code, job.serverdocover);
  447. return;
  448.     case T_IGNOREMODEMBUSY:
  449. replyBoolean(code, job.ignoremodembusy);
  450. return;
  451.     case T_DOCUMENT:
  452. for (i = 0, n = job.items.length(); i < n; i++) {
  453.     const FaxItem& fitem = job.items[i];
  454.     // XXX should cover page docs not be shown?
  455.     switch (fitem.op) {
  456.     case FaxRequest::send_pdf:
  457.     case FaxRequest::send_pdf_saved:
  458.     case FaxRequest::send_tiff:
  459.     case FaxRequest::send_tiff_saved:
  460.     case FaxRequest::send_postscript:
  461.     case FaxRequest::send_postscript_saved:
  462.     case FaxRequest::send_pcl:
  463.     case FaxRequest::send_pcl_saved:
  464. lreply(code, "%s %s",
  465.     docTypeNames[fitem.op], (const char*) fitem.item);
  466. break;
  467.     }
  468. }
  469. reply(code, "End of documents.");
  470. return;
  471.     case T_COVER:
  472. for (i = 0, n = job.items.length(); i < n; i++) {
  473.     const FaxItem& fitem = job.items[i];
  474.     if (fitem.item.length() > 7 && fitem.item.tail(6) == ".cover") {
  475. switch (fitem.op) {
  476. case FaxRequest::send_tiff:
  477. case FaxRequest::send_tiff_saved:
  478. case FaxRequest::send_pdf:
  479. case FaxRequest::send_pdf_saved:
  480. case FaxRequest::send_postscript:
  481. case FaxRequest::send_postscript_saved:
  482. case FaxRequest::send_pcl:
  483. case FaxRequest::send_pcl_saved:
  484.     reply(code, "%s %s",
  485. docTypeNames[fitem.op], (const char*) fitem.item);
  486.     return;
  487. }
  488.     }
  489. }
  490. reply(code+1, "No cover page document.");
  491. return;
  492.     case T_POLL:
  493. for (i = 0, n = job.items.length(); i < n; i++) {
  494.     const FaxItem& fitem = job.items[i];
  495.     if (fitem.op == FaxRequest::send_poll)
  496. lreply(code, ""%s" "%s"",
  497.     (const char*) fitem.item, (const char*) fitem.addr);
  498. }
  499. reply(code, "End of polling items.");
  500. return;
  501.     default:
  502. break;
  503.     }
  504.     for (i = 0, n = N(strvals); i < n; i++)
  505. if (strvals[i].t == t) {
  506.     reply(code, "%s", (const char*) (job.*strvals[i].p));
  507.     return;
  508. }
  509.     for (i = 0, n = N(shortvals); i < n; i++)
  510. if (shortvals[i].t == t) {
  511.     reply(code, "%u", job.*shortvals[i].p);
  512.     return;
  513. }
  514.     for (i = 0, n = N(intvals); i < n; i++)
  515. if (intvals[i].t == t) {
  516.     reply(code, "%u", job.*intvals[i].p);
  517.     return;
  518. }
  519.     reply(500, "Botch: no support for querying parameter value.");
  520. }
  521. void
  522. HylaFAXServer::jstatLine(Token t, const char* fmt ...)
  523. {
  524.     printf("    %s: ", parmToken(t));
  525.     va_list ap;
  526.     va_start(ap, fmt);
  527.     vprintf(fmt, ap);
  528.     va_end(ap);
  529.     printf("rn");
  530. }
  531. /*
  532.  * Implement the jparm command that returns the entire
  533.  * job state as a series of parameter: value pairs.
  534.  * This command is mainly intended for debugging and
  535.  * may go away in the final spec (or become a site cmd).
  536.  */
  537. void
  538. HylaFAXServer::jstatCmd(const Job& job)
  539. {
  540.     lreply(217, "Job state: jobid %s groupid %s",
  541. (const char*) job.jobid, (const char*) job.groupid);
  542.     if (checkAccess(job, T_SENDTIME, A_READ)) {
  543. if (job.tts != 0) {
  544.     const struct tm* tm = cvtTime(job.tts);
  545.     // XXX should this include seconds? (useful for debugging)
  546.     jstatLine(T_SENDTIME, "%d%02d%02d%02d%02d%02d"
  547. , tm->tm_year+1900
  548. , tm->tm_mon+1
  549. , tm->tm_mday
  550. , tm->tm_hour
  551. , tm->tm_min
  552. , tm->tm_sec
  553.     );
  554. } else
  555.     jstatLine(T_SENDTIME, "%s", "NOW");
  556.     }
  557.     if (checkAccess(job, T_LASTTIME, A_READ)) {
  558. time_t tv = job.killtime - job.tts;
  559. jstatLine(T_LASTTIME, "%02d%02d%02d",
  560.     tv/(24*60*60), (tv/(60*60))%24, (tv/60)%60);
  561.     }
  562.     if (checkAccess(job, T_RETRYTIME, A_READ))
  563. jstatLine(T_RETRYTIME, "%02d%02d", job.retrytime/60, job.retrytime%60);
  564.     if (checkAccess(job, T_STATE, A_READ))
  565. jstatLine(T_STATE, "%s", stateVals[job.state]);
  566.     if (checkAccess(job, T_NOTIFY, A_READ))
  567. jstatLine(T_NOTIFY, "%s", notifyVals[job.notify]);
  568.     if (checkAccess(job, T_PAGECHOP, A_READ))
  569. jstatLine(T_PAGECHOP, "%s", chopVals[job.pagechop]);
  570.     if (checkAccess(job, T_CHOPTHRESH, A_READ))
  571. jstatLine(T_CHOPTHRESH, "%g", job.chopthreshold);
  572.     if (checkAccess(job, T_DATAFORMAT, A_READ))
  573. jstatLine(T_DATAFORMAT, "%s", dataVals[job.desireddf]);
  574.     if (checkAccess(job, T_ECMTYPE, A_READ))
  575. jstatLine(T_ECMTYPE, "%s", ecmVals[job.desiredec]);
  576.     if (checkAccess(job, T_USE_ECM, A_READ))
  577. jstatLine(T_USE_ECM, "%s", boolString(job.desiredec != EC_DISABLE ? 1 : 0));
  578.     if (checkAccess(job, T_USE_TAGLINE, A_READ))
  579. jstatLine(T_USE_TAGLINE,"%s", boolString(job.desiredtl));
  580.     if (checkAccess(job, T_USE_XVRES, A_READ))
  581. jstatLine(T_USE_XVRES,"%s", boolString(job.usexvres));
  582.     if (checkAccess(job, T_USE_CONTCOVER, A_READ))
  583. jstatLine(T_USE_CONTCOVER,"%s", boolString(job.useccover));
  584.     if (checkAccess(job, T_SERVERDOCOVER, A_READ))
  585. jstatLine(T_SERVERDOCOVER,"%s", boolString(job.serverdocover));
  586.     if (checkAccess(job, T_IGNOREMODEMBUSY, A_READ))
  587. jstatLine(T_IGNOREMODEMBUSY,"%s", boolString(job.ignoremodembusy));
  588.     u_int i, n;
  589.     for (i = 0, n = N(strvals); i < n; i++)
  590. if (checkAccess(job, strvals[i].t, A_READ))
  591.     jstatLine(strvals[i].t, "%s", (const char*) (job.*strvals[i].p));
  592.     for (i = 0, n = N(shortvals); i < n; i++)
  593. if (checkAccess(job, shortvals[i].t, A_READ))
  594.     jstatLine(shortvals[i].t, "%u", job.*shortvals[i].p);
  595.     for (i = 0, n = N(intvals); i < n; i++)
  596. if (checkAccess(job, intvals[i].t, A_READ))
  597.     jstatLine(intvals[i].t, "%u", job.*intvals[i].p);
  598.     /*
  599.      * NB: This assumes access to T_DOCUMENT is sufficient
  600.      *     for access to T_COVER and T_POLL also.
  601.      */
  602.     if (checkAccess(job, T_DOCUMENT, A_READ)) {
  603. for (i = 0, n = job.items.length(); i < n; i++) {
  604.     const FaxItem& fitem = job.items[i];
  605.     switch (fitem.op) {
  606.     case FaxRequest::send_fax:
  607. jstatLine(T_DOCUMENT, "%s %s %u", docTypeNames[fitem.op],
  608.     (const char*) fitem.item, fitem.dirnum);
  609. break;
  610.     case FaxRequest::send_tiff:
  611.     case FaxRequest::send_tiff_saved:
  612.     case FaxRequest::send_pdf:
  613.     case FaxRequest::send_pdf_saved:
  614.     case FaxRequest::send_postscript:
  615.     case FaxRequest::send_postscript_saved:
  616.     case FaxRequest::send_pcl:
  617.     case FaxRequest::send_pcl_saved:
  618.     case FaxRequest::send_data:
  619.     case FaxRequest::send_data_saved:
  620. jstatLine(
  621.     (fitem.item.length() > 7 && fitem.item.tail(6) == ".cover" ?
  622. T_COVER : T_DOCUMENT) 
  623.     , "%s %s"
  624.     , docTypeNames[fitem.op]
  625.     , (const char*) fitem.item
  626. );
  627. break;
  628.     case FaxRequest::send_page:
  629.     case FaxRequest::send_page_saved:
  630. jstatLine(T_DOCUMENT
  631.     , fitem.addr == "" ? "%s "%s"" : "%s "%s" "%s""
  632.     , docTypeNames[fitem.op]
  633.     , (const char*) fitem.item
  634.     , (const char*) fitem.addr
  635. );
  636. break;
  637.     case FaxRequest::send_poll:
  638. jstatLine(T_POLL
  639.     , fitem.addr == "" ? ""%s"" : ""%s" "%s""
  640.     , (const char*) fitem.item
  641.     , (const char*) fitem.addr
  642. );
  643. break;
  644.     }
  645. }
  646.     }
  647.     reply(217, "End of job %s state.", (const char*) job.jobid);
  648. }
  649. /* 
  650.  * Set a job state parameter value from an array of
  651.  * parameter names; the value is the index into the
  652.  * array.  Parameter names are case-insensitive. 
  653.  * Unknown values cause an error reply.  
  654.  */
  655. bool
  656. HylaFAXServer::setValue(u_short& v, const char* value, const char* what,
  657.     const char* valNames[], u_int nValNames)
  658. {
  659.     for (u_int i = 0; i < nValNames; i++)
  660. if (strcasecmp(value, valNames[i]) == 0) {
  661.     v = i;
  662.     return (true);
  663. }
  664.     reply(503, "Unknown %s value %s.", what, value);
  665.     return (false);
  666. }
  667. void
  668. HylaFAXServer::parmBotch(Token t)
  669. {
  670.     reply(503, "Botch, don't know how to set %s parameter.", parmToken(t));
  671. }
  672. /*
  673.  * Discard any prepared documents so that
  674.  * they will be re-prepared with revised
  675.  * parameters.
  676.  */
  677. void
  678. HylaFAXServer::flushPreparedDocuments(Job& job)
  679. {
  680.     u_int j = 0;
  681.     while (j < job.items.length()) {
  682. FaxItem& fitem = job.items[j];
  683. if (fitem.op == FaxRequest::send_fax) {
  684.     // NB: don't waste time requesting ACK
  685.     fxStr emsg;
  686.     sendQueuer(emsg, "U%s", (const char*) fitem.item);
  687.     job.items.remove(j);
  688.     continue;
  689. }
  690. if (fitem.isSavedOp())
  691.     fitem.op--; // assumes order of enum
  692. j++;
  693.     }
  694.     job.pagehandling = ""; // force recalculation
  695. }
  696. /*
  697.  * Set a job state parameter that has a string value.
  698.  */
  699. bool
  700. HylaFAXServer::setJobParameter(Job& job, Token t, const fxStr& value)
  701. {
  702.     if (checkParm(job, t, A_WRITE|A_MODIFY)) {
  703. switch (t) {
  704. case T_NOTIFY:
  705.     return setValue(job.notify, value, parmToken(t),
  706. notifyVals, N(notifyVals));
  707. case T_PAGECHOP:
  708.     if (setValue(job.pagechop, value, parmToken(t),
  709.       chopVals, N(chopVals))) {
  710. job.pagehandling = ""; // force recalculation
  711. return (true);
  712.     } else
  713. return (false);
  714. case T_DATAFORMAT:
  715.     if (setValue(job.desireddf, value, parmToken(t),
  716.       dataVals, N(dataVals))) {
  717. flushPreparedDocuments(job);
  718. return (true);
  719.     } else
  720. return (false);
  721. case T_ECMTYPE:
  722.     if (setValue(job.desiredec, value, parmToken(t),
  723.       ecmVals, N(ecmVals))) {
  724. return (true);
  725.     } else
  726. return (false);
  727. default:
  728.     break;
  729. }
  730. for (u_int i = 0, n = N(strvals); i < n; i++)
  731.     if (strvals[i].t == t) {
  732. job.*strvals[i].p = value;
  733. return (true);
  734.     }
  735. parmBotch(t);
  736.     }
  737.     return (false);
  738. }
  739. /*
  740.  * Set a job state parameter that has a integer value.
  741.  */
  742. bool
  743. HylaFAXServer::setJobParameter(Job& job, Token t, u_short value)
  744. {
  745.     if (checkParm(job, t, A_WRITE|A_MODIFY)) {
  746. for (u_int i = 0, n = N(shortvals); i < n; i++)
  747.     if (shortvals[i].t == t) {
  748. if (job.*shortvals[i].p != value) {
  749.     // XXX constrain values per A_MODIFY
  750.     job.*shortvals[i].p = value; // XXX
  751.     /*
  752.      * Handle parameters with side effects.
  753.      */
  754.     switch (t) {
  755.     case T_PAGEWIDTH:
  756.     case T_PAGELENGTH:
  757.     case T_VRES:
  758.     case T_HRES:
  759. flushPreparedDocuments(job);
  760. break;
  761.     case T_SCHEDPRI:
  762. job.pri = job.usrpri; // reload
  763. break;
  764.     default:
  765. break;
  766.     }
  767. }
  768. return (true);
  769.     }
  770. for (u_int i = 0, n = N(intvals); i < n; i++)
  771.     if (intvals[i].t == t) {
  772. if (job.*intvals[i].p != value) {
  773.     // XXX constrain values per A_MODIFY
  774.     job.*intvals[i].p = value; // XXX
  775. }
  776. return (true);
  777.     }
  778. parmBotch(t);
  779.     }
  780.     return (false);
  781. }
  782. /*
  783.  * Set a job state parameter that has a time value.
  784.  */
  785. bool
  786. HylaFAXServer::setJobParameter(Job& job, Token t, time_t value)
  787. {
  788.     if (checkParm(job, t, A_WRITE|A_MODIFY)) {
  789. time_t now = Sys::now();
  790. switch (t) {
  791. case T_SENDTIME:
  792.     if (value != 0) { // explicit time
  793. /*
  794.  * We don't complain anymore if this value is in the
  795.  * past.  Instead, we verify that the killtime is in
  796.  * the future, ensuring that a window of send-time
  797.  * opportunity still exists.
  798.  */
  799. job.tts = value;
  800.     } else // ``NOW''
  801. job.tts = now;
  802.     return (true);
  803. case T_LASTTIME:
  804.     /*
  805.      * Convert client-specified kill time (as a relative number)
  806.      * to an absolute time.  If the time-to-send has been set,
  807.      * then the killtime is relative to that; otherwise it's
  808.      * made relative to ``now''.  Note that this implies the
  809.      * order of setting SENDTIME and LASTTIME is important; if
  810.      * a client sets LASTTIME before SENDTIME then an unexpected
  811.      * value may be installed for LASTTIME.
  812.      */
  813.     job.killtime = value + (job.tts == 0 ? now : job.tts);
  814.     if (job.killtime < now) {
  815. reply(503, "Bad time to send; time window is entirely in the past.");
  816. return (false);
  817.     }
  818.     return (true);
  819. case T_RETRYTIME:
  820.     job.retrytime = value;
  821.     return (true);
  822. default:
  823.     break;
  824. }
  825. parmBotch(t);
  826.     }
  827.     return (false);
  828. }
  829. /*
  830.  * Set a job state parameter that has a boolean value.
  831.  */
  832. bool
  833. HylaFAXServer::setJobParameter(Job& job, Token t, bool b)
  834. {
  835.     if (checkParm(job, t, A_WRITE|A_MODIFY)) {
  836. switch (t) {
  837. case T_USE_ECM:
  838.     job.desiredec = (b ? (job.desiredec == EC_DISABLE ? EC_ENABLE256 : job.desiredec) : EC_DISABLE);
  839.     return (true);
  840. case T_USE_TAGLINE:
  841.     job.desiredtl = b;
  842.     return (true);
  843. case T_USE_XVRES:
  844.     job.usexvres = b;
  845.     return (true);
  846. case T_USE_CONTCOVER:
  847.     job.useccover = b;
  848.     return (true);
  849. case T_SERVERDOCOVER:
  850.     job.serverdocover = b;
  851.     return (true);
  852. case T_IGNOREMODEMBUSY:
  853.     job.ignoremodembusy = b;
  854.     return (true);
  855. default:
  856.     break;
  857. }
  858. parmBotch(t);
  859.     }
  860.     return (false);
  861. }
  862. /*
  863.  * Set a job state parameter that has a float value.
  864.  */
  865. bool
  866. HylaFAXServer::setJobParameter(Job& job, Token t, float value)
  867. {
  868.     if (checkParm(job, t, A_WRITE|A_MODIFY)) {
  869. switch (t) {
  870. case T_CHOPTHRESH:
  871.     if (job.chopthreshold != value) {
  872. job.chopthreshold = value;
  873. job.pagehandling = ""; // force recalculation
  874.     }
  875.     return (true);
  876. default:
  877.     break;
  878. }
  879. parmBotch(t);
  880.     }
  881.     return (false);
  882. }
  883. /*
  884.  * Initialize the default job state.  We
  885.  * explicitly set string values since this
  886.  * routine may be called for a JREST command
  887.  * to reset job state to the default settings.
  888.  */
  889. void
  890. HylaFAXServer::initDefaultJob(void)
  891. {
  892.     defJob.jobid = "default";
  893.     defJob.owner = the_user;
  894.     defJob.state = FaxRequest::state_undefined;
  895.     defJob.maxdials = FAX_REDIALS;
  896.     defJob.maxtries = FAX_RETRIES;
  897.     defJob.pagewidth = 0;
  898.     defJob.pagelength = 0;
  899.     defJob.resolution = FAX_DEFVRES;
  900.     defJob.usrpri = FAX_DEFPRIORITY;
  901.     defJob.minbr = BR_2400;
  902.     defJob.desiredbr = BR_33600;
  903.     defJob.desiredst = ST_0MS;
  904.     defJob.desiredec = EC_ENABLE256;
  905.     defJob.desireddf = DF_2DMMR;
  906.     defJob.desiredtl = false;
  907.     defJob.usexvres = false;
  908.     defJob.useccover = true;
  909.     defJob.serverdocover= false;
  910.     defJob.ignoremodembusy= false;
  911.     defJob.pagechop = FaxRequest::chop_default;
  912.     defJob.notify = FaxRequest::no_notice;// FAX_DEFNOTIFY
  913.     defJob.chopthreshold= 3.0;
  914.     defJob.tts = 0; // ``NOW''
  915.     defJob.killtime = 3*60*60; // FAX_TIMEOUT
  916.     defJob.retrytime = 0;
  917.     defJob.sender = the_user; // XXX usually incorrect
  918.     defJob.mailaddr = the_user | "@" | remotehost;
  919.     defJob.jobtag = "";
  920.     defJob.number = "";
  921.     defJob.subaddr = "";
  922.     defJob.passwd = "";
  923.     defJob.external = "";
  924.     defJob.modem = MODEM_ANY;
  925.     defJob.faxnumber = "";
  926.     defJob.faxname = "";
  927.     defJob.tsi = "";
  928.     defJob.receiver = "";
  929.     defJob.company = "";
  930.     defJob.location = "";
  931.     defJob.voice = "";
  932.     defJob.fromcompany = "";
  933.     defJob.fromlocation = "";
  934.     defJob.fromvoice = "";
  935.     defJob.regarding = "";
  936.     defJob.comments = "";
  937.     defJob.timeofday = "";
  938.     defJob.errorcode = "";
  939.     defJob.client = remotehost;
  940.     defJob.tagline = "";
  941.     defJob.doneop = "default";
  942.     defJob.nocountcover = 0;
  943.     defJob.skippedpages = 0;
  944.     defJob.skippages = 0;
  945. }
  946. /*
  947.  * JNEW command; create a new job and
  948.  * make it the current job.
  949.  */
  950. void
  951. HylaFAXServer::newJobCmd(void)
  952. {
  953.     fxStr emsg;
  954.     if (newJob(emsg) && updateJobOnDisk(*curJob, emsg)) {
  955. fxStr file("/" | curJob->qfile);
  956. setFileOwner(file); // force ownership
  957. FileCache::chmod(file, jobProtection); // sync cache
  958. curJob->lastmod = Sys::now(); // noone else should update
  959. reply(200, "New job created: jobid: %s groupid: %s.",
  960.     (const char*) curJob->jobid, (const char*) curJob->groupid);
  961. blankJobs[curJob->jobid] = curJob;
  962.     } else
  963. reply(503, "%s.", (const char*) emsg);
  964. }
  965. /*
  966.  * Create a new job, inheriting state from the current
  967.  * job and make the new job be the current job.  Note
  968.  * that the current job must be owned by the client;
  969.  * otherwise people could ``look inside'' other people's
  970.  * jobs by inheriting state--this would permit them to
  971.  * look at privileged information such as calling card
  972.  * information in dial strings and passwords to be
  973.  * transmitted with polling items.
  974.  */
  975. bool
  976. HylaFAXServer::newJob(fxStr& emsg)
  977. {
  978.     if (!IS(PRIVILEGED) && the_user != curJob->owner) {
  979. emsg = "Permission denied; cannot inherit from job " | curJob->jobid;
  980. return (false);
  981.     }
  982.     u_int id = getJobNumber(emsg); // allocate unique job ID
  983.     if (id == (u_int) -1)
  984. return (false);
  985.     fxStr jobid = fxStr::format("%u", id);
  986.     Job* job = new Job(FAX_SENDDIR "/" FAX_QFILEPREF | jobid);
  987.     job->jobid = jobid;
  988.     job->groupid = curJob->groupid;
  989.     if (job->groupid == "")
  990. job->groupid = jobid;
  991.     job->owner = the_user;
  992.     job->state = FaxRequest::state_suspended;
  993.     job->maxdials = curJob->maxdials;
  994.     job->maxtries = curJob->maxtries;
  995.     job->pagewidth = curJob->pagewidth;
  996.     job->pagelength = curJob->pagelength;
  997.     job->resolution = curJob->resolution;
  998.     job->usrpri = curJob->usrpri;
  999.     job->minbr = curJob->minbr;
  1000.     job->desiredbr = curJob->desiredbr;
  1001.     job->desiredst = curJob->desiredst;
  1002.     job->desiredec = curJob->desiredec;
  1003.     job->desireddf = curJob->desireddf;
  1004.     job->desiredtl = curJob->desiredtl;
  1005.     job->usexvres = curJob->usexvres;
  1006.     job->useccover = curJob->useccover;
  1007.     job->serverdocover = curJob->serverdocover;
  1008.     job->ignoremodembusy = curJob->ignoremodembusy;
  1009.     job->pagechop = curJob->pagechop;
  1010.     job->notify = curJob->notify;
  1011.     job->chopthreshold = curJob->chopthreshold;
  1012.     job->tts = curJob->tts;
  1013.     job->killtime = curJob->killtime;
  1014.     job->retrytime = curJob->retrytime;
  1015.     job->sender = curJob->sender;
  1016.     job->mailaddr = curJob->mailaddr;
  1017.     job->jobtag = curJob->jobtag; // ???
  1018.     job->number = curJob->number;
  1019.     job->external = curJob->external;
  1020.     job->modem = curJob->modem;
  1021.     job->faxnumber = curJob->faxnumber;
  1022.     job->faxname = curJob->faxname;
  1023.     job->tsi = curJob->tsi;
  1024.     job->receiver = curJob->receiver;
  1025.     job->company = curJob->company;
  1026.     job->location = curJob->location;
  1027.     job->voice = curJob->voice;
  1028.     job->fromcompany = curJob->fromcompany;
  1029.     job->fromlocation = curJob->fromlocation;
  1030.     job->fromvoice = curJob->fromvoice;
  1031.     job->regarding = curJob->regarding;
  1032.     job->comments = curJob->comments;
  1033.     job->timeofday = curJob->timeofday;
  1034.     job->jobtype = curJob->jobtype;
  1035.     job->tagline = curJob->tagline;
  1036.     job->client = remotehost;
  1037.     job->doneop = curJob->doneop;
  1038.     job->queued = curJob->queued;
  1039.     jobs[jobid] = job;
  1040.     curJob = job;
  1041.     return (true);
  1042. }
  1043. /*
  1044.  * Update the job's state on disk.
  1045.  */
  1046. bool
  1047. HylaFAXServer::updateJobOnDisk(Job& job, fxStr& emsg)
  1048. {
  1049.     if (job.fd < 0)
  1050.     {
  1051. job.fd = Sys::open("/" | job.qfile, O_RDWR|O_CREAT, jobProtection);
  1052. if (job.fd < 0)
  1053. {
  1054.     emsg = "Cannot open/create job description file /" | job.qfile;
  1055.     return false;
  1056. }
  1057.     }
  1058.     if (lockJob(job, LOCK_EX, emsg)) {
  1059. // XXX don't update in place, use temp file and rename
  1060. job.writeQFile();
  1061. unlockJob(job);
  1062. return (true);
  1063.     } else
  1064. return (false);
  1065. }
  1066. /*
  1067.  * Look for a job in the in-memory cache.
  1068.  */
  1069. Job*
  1070. HylaFAXServer::findJobInMemmory(const char* jobid)
  1071. {
  1072.     if (curJob->jobid == jobid) // fast check
  1073. return (curJob);
  1074.     Job** jpp = (Job**) jobs.find(jobid);
  1075.     if (jpp)
  1076. return (*jpp);
  1077.     return (jobid == defJob.jobid ? &defJob : (Job*) NULL);
  1078. }
  1079. /*
  1080.  * Look for a job on disk and, if found, read it
  1081.  * into memory and return it.
  1082.  */
  1083. Job*
  1084. HylaFAXServer::findJobOnDisk(const char* jid, fxStr& emsg)
  1085. {
  1086.     fxStr filename(fxStr::format("/" FAX_SENDDIR "/" FAX_QFILEPREF "%s", jid));
  1087.     struct stat sb;
  1088.     if (!FileCache::update(filename, sb)) {
  1089. /*
  1090.  * Not in sendq, look in the doneq for the job.
  1091.  */
  1092. filename = fxStr::format("/" FAX_DONEDIR "/" FAX_QFILEPREF "%s", jid);
  1093. if (!FileCache::update(filename, sb)) {
  1094.     emsg = fxStr::format("job does not exist (%s)", strerror(errno));
  1095.     return (NULL);
  1096. }
  1097.     }
  1098.     if (!S_ISREG(sb.st_mode)) {
  1099. emsg = "job description file is not a regular file";
  1100. return (NULL);
  1101.     }
  1102.     int fd = Sys::open(filename, O_RDWR);
  1103.     if (fd >= 0) {
  1104. // XXX should we lock here???
  1105. Job* req = new Job(&filename[1], fd);
  1106. bool reject;
  1107. if (req->readQFile(reject) && !reject) {
  1108.     Sys::close(req->fd), req->fd = -1;
  1109.     if (checkAccess(*req, T_JOB, A_READ) )
  1110. return (req);
  1111.     emsg = "Permission denied";
  1112.     delete req;
  1113.     return (NULL);
  1114. }
  1115. emsg = "invalid or corrupted job description file";
  1116. delete req; // NB: closes fd
  1117.     } else
  1118. emsg = fxStr::format("cannot open job description file %s (%s)",
  1119.     (const char*) filename, strerror(errno));
  1120.     return (NULL);
  1121. }
  1122. /*
  1123.  * Update a job's state from the on-disk copy.
  1124.  */
  1125. bool
  1126. HylaFAXServer::updateJobFromDisk(Job& job)
  1127. {
  1128.     bool status = false;
  1129.     if (lockJob(job, LOCK_SH)) {
  1130. bool reject;
  1131. status = (job.reReadQFile(reject) && !reject);
  1132. unlockJob(job);
  1133.     }
  1134.     return (status);
  1135. }
  1136. /*
  1137.  * Lock a job description file, creating it if
  1138.  * necessary.  Errors are not expected--if one
  1139.  * occurs a descriptive message is returned for
  1140.  * transmission to the client.
  1141.  */
  1142. bool
  1143. HylaFAXServer::lockJob(Job& job, int how, fxStr& emsg)
  1144. {
  1145.     if (job.fd < 0) {
  1146. job.fd = Sys::open("/" | job.qfile, O_RDWR, jobProtection);
  1147. if (job.fd < 0) {
  1148.     emsg = "Cannot open/create job description file /" | job.qfile;
  1149.     return (false);
  1150. }
  1151.     }
  1152.     if (flock(job.fd, how | LOCK_NB) >= 0)
  1153. return true;
  1154.     if (errno == EWOULDBLOCK && lockTimeout > 0)
  1155.     {
  1156. int r;
  1157. Timeout timer;
  1158. timer.startTimeout(lockTimeout*1000);
  1159. r = flock(job.fd, how);
  1160. timer.stopTimeout();
  1161. if (timer.wasTimeout())
  1162.     logDebug("LOCKWAIT timeout: %ds", lockTimeout);
  1163. return (r >= 0);
  1164.     }
  1165.     emsg = fxStr::format("Job file lock failed: %s", strerror(errno));
  1166.     Sys::close(job.fd);
  1167.     job.fd = -1;
  1168.     return (false);
  1169. }
  1170. /*
  1171.  * Like above, but no error message is returned.
  1172.  */
  1173. bool
  1174. HylaFAXServer::lockJob(Job& job, int how)
  1175. {
  1176.     if (job.fd < 0)
  1177.     {
  1178. job.fd = Sys::open("/" | job.qfile, O_RDWR, jobProtection);
  1179. if (job.fd < 0)
  1180.     return false;
  1181.     }
  1182.     if (flock(job.fd, how | LOCK_NB) >= 0)
  1183. return true;
  1184.     if (errno == EWOULDBLOCK && lockTimeout > 0)
  1185.     {
  1186. int r;
  1187. Timeout timer;
  1188. timer.startTimeout(lockTimeout * 1000);
  1189. r = flock(job.fd, how);
  1190. timer.stopTimeout();
  1191. if (timer.wasTimeout())
  1192.     logDebug("LOCKWAIT timeout: %ds", lockTimeout);
  1193. return (r >= 0);
  1194.     }
  1195.     return (false);
  1196. }
  1197. /*
  1198.  * Unlock a previously-locked job.
  1199.  */
  1200. void
  1201. HylaFAXServer::unlockJob(Job& job)
  1202. {
  1203.     if (job.fd >= 0)
  1204. Sys::close(job.fd), job.fd = -1; // implicit unlock
  1205. }
  1206. /*
  1207.  * Find a job either in memory (in the cache) or
  1208.  * on disk.  If a job is found in memory and the
  1209.  * on-disk state is more current, update the state
  1210.  * in the cache.
  1211.  */
  1212. Job*
  1213. HylaFAXServer::findJob(const char* jobid, fxStr& emsg)
  1214. {
  1215.     Job* job = findJobInMemmory(jobid);
  1216.     if (job) {
  1217. /*
  1218.  * Verify job is still around (another process has
  1219.  * not deleted it) and if the on-disk state has
  1220.  * been updated, re-read the job description file.
  1221.  */
  1222. if (!checkJobState(job)) {
  1223.             // We will re-check on disk in case a job was moved between queues
  1224.             job = NULL;
  1225.     emsg = "job deleted by another party";
  1226.         }
  1227.     } 
  1228.     if (!job) {
  1229. /*
  1230.  * We can only afford a certain amount of space,
  1231.  * unfortunately, there is no "bright" way to remove jobs
  1232.  * Ideally we'ld have an "aging" method, so the LRU job
  1233.  * would be the one deleted...
  1234.  */
  1235. if (jobs.size() > 10)
  1236. {
  1237.     JobDictIter iter(jobs);
  1238.     job = iter.value();
  1239.     jobs.remove(job->jobid);
  1240.     delete job;
  1241. }
  1242. job = findJobOnDisk(jobid, emsg);
  1243. if (job)
  1244.     jobs[job->jobid] = job;
  1245.     }
  1246.     return (job);
  1247. }
  1248. /*
  1249.  * Purge all in-memory job state.
  1250.  */
  1251. void
  1252. HylaFAXServer::purgeJobs(void)
  1253. {
  1254.     for (JobDictIter iter(jobs); iter.notDone(); iter++) {
  1255. Job* job = iter.value();
  1256. jobs.remove(job->jobid);
  1257. delete job;
  1258.     }
  1259. }
  1260. /*
  1261.  * Send a reply identifying the current job.
  1262.  */
  1263. void
  1264. HylaFAXServer::replyCurrentJob(const char* leader)
  1265. {
  1266.     if (curJob->jobid == "default")
  1267. reply(200, "%s (default).", leader);
  1268.     else
  1269. reply(200, "%s jobid: %s groupid: %s.", leader,
  1270.     (const char*) curJob->jobid, (const char*) curJob->groupid);
  1271. }
  1272. /*
  1273.  * Set the current job.
  1274.  */
  1275. void
  1276. HylaFAXServer::setCurrentJob(const char* jobid)
  1277. {
  1278.     fxStr emsg;
  1279.     Job* job = findJob(jobid, emsg);
  1280.     if (job) {
  1281. curJob = job;
  1282. replyCurrentJob("Current job:");
  1283.     } else
  1284. reply(500, "Cannot set job %s; %s.", jobid, (const char*) emsg);
  1285. }
  1286. /*
  1287.  * Reset a job's state to what is currently on disk.
  1288.  */
  1289. void
  1290. HylaFAXServer::resetJob(const char* jobid)
  1291. {
  1292.     fxStr emsg;
  1293.     Job* job = findJob(jobid, emsg);
  1294.     if (job) {
  1295. if (job->jobid == "default") {
  1296.     initDefaultJob();
  1297.     reply(200, "Default job reset to initial state.");
  1298. } else if (job->state != FaxRequest::state_suspended) {
  1299.     reply(504, "Job %s not reset; must be suspended.", jobid);
  1300. } else {
  1301.     updateJobFromDisk(*job);
  1302.     struct stat sb;
  1303.     if (FileCache::lookup("/" | job->qfile, sb))
  1304. job->lastmod = sb.st_mtime;
  1305.     reply(200, "Job %s reset to last state saved to disk.", jobid);
  1306. }
  1307.     } else
  1308. reply(500, "Cannot reset job %s; %s.", jobid, (const char*) emsg);
  1309. }
  1310. /*
  1311.  * Common work done for many job-related commands.
  1312.  */
  1313. Job*
  1314. HylaFAXServer::preJobCmd(const char* op, const char* jobid, fxStr& emsg)
  1315. {
  1316.     Job* job = findJob(jobid, emsg);
  1317.     if (job) {
  1318. if (job->jobid == "default") {
  1319.     reply(504, "Cannot %s default job.", op);
  1320.     job = NULL;
  1321. } else if (!IS(PRIVILEGED) && job->owner != the_user) {
  1322.     reply(504, "Cannot %s job: %s.", op, strerror(EPERM));
  1323.     job = NULL;
  1324. }
  1325.     } else
  1326. reply(500, "Cannot %s job %s; %s.", op, jobid, (const char*) emsg);
  1327.     return (job);
  1328. }
  1329. /*
  1330.  * Delete all job state (both on disk and in memory).
  1331.  */
  1332. void
  1333. HylaFAXServer::deleteJob(const char* jobid)
  1334. {
  1335.     fxStr emsg;
  1336.     Job* job = preJobCmd("delete", jobid, emsg);
  1337.     if (job) {
  1338. const char* startdir = cwd->pathname;
  1339. if (Sys::chdir("/") < 0) {
  1340.     reply(504, "Cannot change to base spool directory.");
  1341.     return;
  1342. }
  1343. if (job->state != FaxRequest::state_done &&
  1344.   job->state != FaxRequest::state_failed &&
  1345.   job->state != FaxRequest::state_suspended) {
  1346.     reply(504, "Job %s not deleted; use JSUSP first.", jobid);
  1347.     return;
  1348. }
  1349. if (!lockJob(*job, LOCK_EX, emsg)) {
  1350.     reply(504, "Cannot delete job: %s.", (const char*) emsg);
  1351.     return;
  1352. }
  1353. /*
  1354.  * Jobs in the doneq (state_done) have had their
  1355.  * documents converted to references (w/o links)
  1356.  * to the base document name; thus there is no
  1357.  * work to do to cleanup document state (a separate
  1358.  * scavenger program must deal with this since it
  1359.  * requires global knowledge of what jobs reference
  1360.  * what documents).
  1361.  *
  1362.  * Jobs that have yet to complete however hold links
  1363.  * to documents that must be removed.  We do this here
  1364.  * and also notify the scheduler about our work so that
  1365.  * it can properly expunge imaged versions of the docs.
  1366.  */
  1367. if (job->state == FaxRequest::state_suspended) {
  1368.     for (u_int i = 0, n = job->items.length(); i < n; i++) {
  1369. const FaxItem& fitem = job->items[i];
  1370. switch (fitem.op) {
  1371. case FaxRequest::send_fax:
  1372.     if (sendQueuerACK(emsg, "U%s", (const char*) fitem.item) ||
  1373.       !job->isUnreferenced(i))
  1374. break;
  1375.     /* ... fall thru */
  1376. case FaxRequest::send_tiff_saved:
  1377. case FaxRequest::send_tiff:
  1378. case FaxRequest::send_pdf_saved:
  1379. case FaxRequest::send_pdf:
  1380. case FaxRequest::send_postscript:
  1381. case FaxRequest::send_postscript_saved:
  1382. case FaxRequest::send_pcl:
  1383. case FaxRequest::send_pcl_saved:
  1384. case FaxRequest::send_data:
  1385.     Sys::unlink(fitem.item);
  1386.     break;
  1387. }
  1388.     }
  1389. } else {
  1390.     // expunge any cover page documents
  1391.     for (u_int i = 0, n = job->items.length(); i < n; i++) {
  1392. const FaxItem& fitem = job->items[i];
  1393. switch (fitem.op) {
  1394. case FaxRequest::send_tiff_saved:
  1395. case FaxRequest::send_tiff:
  1396. case FaxRequest::send_pdf_saved:
  1397. case FaxRequest::send_pdf:
  1398. case FaxRequest::send_postscript:
  1399. case FaxRequest::send_postscript_saved:
  1400. case FaxRequest::send_pcl:
  1401. case FaxRequest::send_pcl_saved:
  1402.     if (fitem.item.findR(fitem.item.length(), ".cover"))
  1403. Sys::unlink(fitem.item);
  1404.     break;
  1405. }
  1406.     }
  1407. }
  1408. if (Sys::unlink(job->qfile) < 0)
  1409.     reply(504, "Deletion of queue file %s failed.", (const char*) job->qfile);
  1410. if (Sys::chdir(startdir) < 0)
  1411.     reply(504, "Cannot change to %s spool directory.", startdir);
  1412. jobs.remove(job->jobid);
  1413. if (job == curJob) // make default job current
  1414.     curJob = &defJob;
  1415. delete job; // NB: implicit unlock
  1416. replyCurrentJob(fxStr::format("Job %s deleted; current job:", jobid));
  1417.     }
  1418. }
  1419. /*
  1420.  * Common work for doing job state manipulations.
  1421.  */
  1422. void
  1423. HylaFAXServer::operateOnJob(const char* jobid, const char* what, const char* op)
  1424. {
  1425.     fxStr emsg;
  1426.     Job* job = preJobCmd(what, jobid, emsg);
  1427.     if (job) {
  1428. if (job->state == FaxRequest::state_done ||
  1429.   job->state == FaxRequest::state_failed) {
  1430.     reply(504, "Job %s not %sed; already done.", jobid, what);
  1431.     return;
  1432. }
  1433. if (sendQueuerACK(emsg, "%s%s", op, jobid))
  1434.     reply(200, "Job %s %sed.", jobid, what);
  1435. else
  1436.     reply(460, "Failed to %s job %s: %s.",
  1437. what, jobid, (const char*) emsg);
  1438.     }
  1439. }
  1440. /*
  1441.  * Terminate a job, potentially aborting any call in progress.
  1442.  */
  1443. void
  1444. HylaFAXServer::killJob(const char* jobid)
  1445. {
  1446.     operateOnJob(jobid, "kill", "K");
  1447. }
  1448. /*
  1449.  * Suspend a job from being scheduled.
  1450.  */
  1451. void
  1452. HylaFAXServer::suspendJob(const char* jobid)
  1453. {
  1454.     operateOnJob(jobid, "suspend", "X");
  1455. }
  1456. /*
  1457.  * Interrupt a job from being scheduled.
  1458.  */
  1459. void
  1460. HylaFAXServer::interruptJob(const char* jobid)
  1461. {
  1462.     operateOnJob(jobid, "interrupt", "Y");
  1463. }
  1464. void
  1465. HylaFAXServer::replyBadJob(const Job& job, Token t)
  1466. {
  1467.     reply(504, "Cannot submit job %s; null or missing %s parameter.",
  1468. (const char*) job.jobid, parmToken(t));
  1469. }
  1470. /*
  1471.  * Submit a job for scheduling.
  1472.  */
  1473. void
  1474. HylaFAXServer::submitJob(const char* jobid)
  1475. {
  1476.     fxStr emsg;
  1477.     Job* job = preJobCmd("submit", jobid, emsg);
  1478.     if (job) {
  1479. if (job->state == FaxRequest::state_done ||
  1480.   job->state == FaxRequest::state_failed) {
  1481.     reply(504, "Job %s not submitted; already done.", jobid);
  1482.     return;
  1483. }
  1484. if (job->state != FaxRequest::state_suspended) {
  1485.     reply(504, "Job %s not submitted; use JSUSP first.", jobid);
  1486.     return;
  1487. }
  1488. if (job->number == "")
  1489.     replyBadJob(*job, T_DIALSTRING);
  1490. else if (job->mailaddr == "")
  1491.     replyBadJob(*job, T_NOTIFYADDR);
  1492. else if (job->sender == "")
  1493.     replyBadJob(*job, T_FROM_USER);
  1494. else if (job->modem == "")
  1495.     replyBadJob(*job, T_MODEM);
  1496. else if (job->client == "")
  1497.     replyBadJob(*job, T_CLIENT);
  1498. else {
  1499.     /*
  1500.      * If the client doesn't specify external then use number.  So
  1501.      * temporarily alter job->external as the job is updated on disk.
  1502.      */
  1503.     bool defaultexternal = false;
  1504.     if (job->external == "") {
  1505. job->external = job->number;
  1506. defaultexternal = true;
  1507.     }
  1508.     if (updateJobOnDisk(*job, emsg)) {
  1509. /*
  1510.  * NB: we don't mark the lastmod time for the
  1511.  * job since the scheduler should re-write the
  1512.  * queue file to reflect what it did with it
  1513.  * (e.g. what state it placed the job in).
  1514.  */
  1515. if (sendQueuerACK(emsg, "S%s", jobid)) {
  1516.     reply(200, "Job %s submitted.", jobid);
  1517.     Job** jpp = (Job**) blankJobs.find(job->jobid);
  1518.     if (jpp)
  1519. blankJobs.remove(job->jobid); // it's no longer blank
  1520. } else
  1521.     reply(460, "Failed to submit job %s: %s.",
  1522. jobid, (const char*) emsg);
  1523.     } else
  1524. reply(450, "%s.", (const char*) emsg); // XXX 550?
  1525.     if (defaultexternal)
  1526. job->external = "";
  1527. }
  1528.     }
  1529. }
  1530. /*
  1531.  * Wait for a job to complete or for the operation
  1532.  * to be aborted.  A data channel is opened and 
  1533.  * job status information is returned on it.  The
  1534.  * client can terminate this operation with an
  1535.  * ABOR command on the control channel; just like
  1536.  * a normal file transfer operation.
  1537.  */
  1538. void
  1539. HylaFAXServer::waitForJob(const char* jobid)
  1540. {
  1541.     fxStr emsg;
  1542.     Job* job = findJob(jobid, emsg);
  1543.     if (job) {
  1544. if (job->jobid == "default") {
  1545.     reply(504, "Cannot wait for default job.");
  1546.     return;
  1547. }
  1548. if (job->state == FaxRequest::state_done ||
  1549.   job->state == FaxRequest::state_failed) {
  1550.     reply(216, "Job %s done (already).", jobid);
  1551.     return;
  1552. }
  1553. state &= ~S_LOGTRIG; // just process events
  1554. if (newTrigger(emsg, "J<%s>%04x", jobid, 1<<Trigger::JOB_DEAD)) {
  1555.     // XXX is lreply the right thing?
  1556.     lreply(216, "Waiting for job %s; use ABOR command to interrupt.",
  1557. jobid);
  1558.     if (setjmp(urgcatch) == 0) {
  1559. Dispatcher& disp = Dispatcher::instance();
  1560. for (state |= S_WAITTRIG; IS(WAITTRIG); disp.dispatch()) {
  1561.     /*
  1562.      * The trigger event handlers update our notion
  1563.      * of the job state asynchronously so we can just
  1564.      * monitor the job's state variable.  Beware however
  1565.      * that the job may get removed/moved to the doneq
  1566.      * while we're monitoring its status; so we cannot
  1567.      * blindly hold a reference to the in-memory structure.
  1568.      */
  1569.     job = findJob(jobid, emsg);
  1570.     if (!job || job->state == FaxRequest::state_done ||
  1571.       job->state == FaxRequest::state_failed)
  1572. break;
  1573. }
  1574. reply(216, "Wait for job %s completed.", jobid);
  1575.     }
  1576.     state &= ~S_WAITTRIG;
  1577.     (void) cancelTrigger(emsg);
  1578. } else
  1579.     reply(504, "Cannot register trigger: %s.", (const char*) emsg);
  1580.     } else
  1581. reply(500, "Cannot wait for job %s; %s.", jobid, (const char*) emsg);
  1582. }
  1583. /*
  1584.  * Do common work used in adding a document to a
  1585.  * job's set of documents that are to be sent.
  1586.  */
  1587. bool
  1588. HylaFAXServer::checkAddDocument(Job& job, Token type,
  1589.     const char* docname, FaxSendOp& op)
  1590. {
  1591.     if (checkParm(job, type, A_WRITE)) {
  1592. struct stat sb;
  1593. if (fileAccess(docname, R_OK, sb)) {
  1594.     fxStr file(docname);
  1595.     u_int d = file.nextR(file.length(), '.');
  1596.     /*
  1597.      * We trust the client's protocol-specified (FORM) file format except 
  1598.      * in the case of Postscript because Postscript is the server-default 
  1599.      * (in the case the client did not specify the format).
  1600.      */
  1601.     if (strcmp(docname+d, "ps") != 0) {
  1602. for (u_int i = 0, n = N(formats); i < n; i++) {
  1603.     if (strcmp(docname+d, formats[i].suffix) == 0 && formats[i].supported) {
  1604. op = formats[i].op;
  1605. return (true);
  1606.     }
  1607. }
  1608.     }
  1609.     if (!docType(docname, op))
  1610. reply(550, "%s: Document type not recognized.", docname);
  1611.     else
  1612. return (true);
  1613. }
  1614.     }
  1615.     return (false);
  1616. }
  1617. /*
  1618.  * Add a cover document to the current job's
  1619.  * set of documents that are to be sent.
  1620.  */
  1621. void
  1622. HylaFAXServer::addCoverDocument(Job& job, const char* docname)
  1623. {
  1624.     FaxSendOp op;
  1625.     if (checkAddDocument(job, T_COVER, docname, op)) {
  1626. fxStr covername = "/" FAX_DOCDIR "/cover" | job.jobid | ".cover";
  1627. if (Sys::link(docname, covername) >= 0) {
  1628.     // XXX mark as cover page
  1629.     job.items.append(FaxItem(op, 0, "", &covername[1]));
  1630.     reply(200, "Added cover page document %s as %s.",
  1631. docname, &covername[1]);
  1632.     job.pagehandling = ""; // force recalculation
  1633. } else
  1634.     reply(550, "Unable to link cover page document %s to %s: %s.",
  1635. docname, (const char*) covername, strerror(errno));
  1636.     }
  1637. }
  1638. /*
  1639.  * Add a non-cover document to the current
  1640.  * job's set of documents that are to be sent.
  1641.  */
  1642. void
  1643. HylaFAXServer::addDocument(Job& job, const char* docname)
  1644. {
  1645.     FaxSendOp op;
  1646.     if (checkAddDocument(job, T_DOCUMENT, docname, op)) {
  1647. const char* cp = strrchr(docname, '/');
  1648. if (!cp) // relative name, e.g. doc123
  1649.     cp = docname;
  1650. fxStr document = fxStr::format("/" FAX_DOCDIR "%s.", cp) | job.jobid;
  1651. if (Sys::link(docname, document) >= 0) {
  1652.     job.items.append(FaxItem(op, 0, "", &document[1]));
  1653.     reply(200, "Added document %s as %s.", docname, &document[1]);
  1654.     job.pagehandling = ""; // force recalculation
  1655. } else
  1656.     reply(550, "Unable to link document %s to %s: %s.",
  1657. docname, (const char*) document, strerror(errno));
  1658.     }
  1659. }
  1660. /*
  1661.  * Add a polling operation to the current job.
  1662.  */
  1663. void
  1664. HylaFAXServer::addPollOp(Job& job, const char* sep, const char* pwd)
  1665. {
  1666.     if (checkParm(job, T_POLL, A_WRITE)) {
  1667. job.items.append(FaxItem(FaxRequest::send_poll, 0, sep, pwd));
  1668. reply(200, "Added poll operation.");
  1669.     }
  1670. }
  1671. /*
  1672.  * Directory interface support for querying job status.
  1673.  */
  1674. bool
  1675. HylaFAXServer::isVisibleSendQFile(const char* filename, const struct stat&)
  1676. {
  1677.     if (filename[0] == 'q') {
  1678.      fxStr emsg;
  1679.      Job* job = findJob(&filename[1], emsg);
  1680. if (job && checkAccess(*job, T_JOB, A_READ))
  1681.     return true;
  1682.     }
  1683.     if (strncmp(filename, FAX_SEQF, 4) == 0) return true;
  1684.     return false;
  1685. }
  1686. #ifdef roundup
  1687. #undef roundup
  1688. #endif
  1689. #define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
  1690. /*
  1691.  * Return a compact notation for the specified
  1692.  * time.  This notation is guaranteed to fit in
  1693.  * a 7-character field.  We select one of 5
  1694.  * representations based on how close the time
  1695.  * is to ``now''.
  1696.  */
  1697. const char*
  1698. HylaFAXServer::compactTime(time_t t)
  1699. {
  1700.     time_t now = Sys::now();
  1701.     if (t > now) { // already past
  1702. static char buf[15];
  1703. const struct tm* tm = cvtTime(t);
  1704. if (t < roundup(now, 24*60*60)) // today, use 19:37
  1705.     strftime(buf, sizeof (buf), "%H:%M", tm);
  1706. else if (t < now+7*24*60*60) // within a week, use Sun 6pm
  1707.     strftime(buf, sizeof (buf), "%a%I%p", tm);
  1708. else // over a week, use 25Dec95
  1709.     strftime(buf, sizeof (buf), "%d%b%y", tm);
  1710. return (buf);
  1711.     } else
  1712. return ("");
  1713. }
  1714. static const char jformat[] = {
  1715.     's', // A (subaddr)
  1716.     's', // B (passwd)
  1717.     's', // C (company)
  1718.     's', // D (totdials & maxdials)
  1719.     'u', // E (desiredbr)
  1720.     's', // F (tagline)
  1721.     'u', // G (desiredst)
  1722.     'u', // H (desireddf)
  1723.     'u', // I (usrpri)
  1724.     's', // J (jobtag)
  1725.     'c', // K (desiredec as symbol)
  1726.     's', // L (location)
  1727.     's', // M (mailaddr)
  1728.     'c', // N (desiredtl as symbol)
  1729.     'c', // O (useccover as symbol)
  1730.     's', // P (npages & total pages)
  1731.     'u', // Q (minbr)
  1732.     's', // R (receiver)
  1733.     's', // S (sender)
  1734.     's', // T (tottries & maxtries)
  1735.     's', // U (chopthreshold)
  1736.     's', // V (doneop)
  1737.     's', // W (commid)
  1738.     'c', // X (jobtype as symbol)
  1739.     's', // Y (tts in strftime %Y/%m/%d %H.%M.%S format)
  1740.     'u', // Z (tts as decimal time_t)
  1741.     '[', // [
  1742.     '\', //  (must have something after the backslash)
  1743.     ']', // ]
  1744.     '^', // ^
  1745.     '_', // _
  1746.     '`', // `
  1747.     'c', // a (state as symbol)
  1748.     'u', // b (ntries)
  1749.     's', // c (client)
  1750.     'u', // d (totdials)
  1751.     's', // e (external)
  1752.     'u', // f (ndials)
  1753.     's', // g (groupid)
  1754.     'c', // h (pagechop as symbol)
  1755.     'u', // i (pri)
  1756.     's', // j (jobid)
  1757.     's', // k (killtime)
  1758.     'u', // l (pagelength)
  1759.     's', // m (modem)
  1760.     'c', // n (notify as symbol)
  1761.     's', // o (owner)
  1762.     'u', // p (npages)
  1763.     's', // q (retrytime)
  1764.     'u', // r (resolution)
  1765.     's', // s (notice a.k.a. status)
  1766.     'u', // t (tottries)
  1767.     'u', // u (maxtries)
  1768.     's', // v (number a.k.a dialstring)
  1769.     'u', // w (pagewidth)
  1770.     'u', // x (maxdials)
  1771.     'u', // y (total pages)
  1772.     's', // z (tts)
  1773.     'c', // 0 (usexvres as symbol)
  1774. };
  1775. /*
  1776.  * Print a formatted string with fields filled in from
  1777.  * the specified job's state.  This functionality is
  1778.  * used to permit clients to get job state listings in
  1779.  * preferred formats.
  1780.  */
  1781. void
  1782. HylaFAXServer::Jprintf(FILE* fd, const char* fmt, const Job& job)
  1783. {
  1784.     /*
  1785.      * Check once to see if the client has access to
  1786.      * privileged job state.  This typically is not
  1787.      * needed but doing it here eliminates the need to
  1788.      * do more work below (and the check should be fast).
  1789.      *
  1790.      * NB: This assumes that read access to T_DIALSTRING 
  1791.      *     implies read access to anything else in the
  1792.      *     job state that is protected.
  1793.      */
  1794.     bool haveAccess = checkAccess(job, T_DIALSTRING, A_READ);
  1795.     for (const char* cp = fmt; *cp; cp++) {
  1796. if (*cp == '%') {
  1797. #define MAXSPEC 20
  1798.     char fspec[MAXSPEC];
  1799.     char* fp = fspec;
  1800.     *fp++ = '%';
  1801.     char c = *++cp;
  1802.     if (c == '-')
  1803. *fp++ = c, c = *++cp;
  1804.     if (isdigit(c)) {
  1805. do {
  1806.     *fp++ = c;
  1807. } while (isdigit(c = *++cp) && fp < &fspec[MAXSPEC-3]);
  1808.     }
  1809.     if (c == '.') {
  1810. do {
  1811.     *fp++ = c;
  1812. } while (isdigit(c = *++cp) && fp < &fspec[MAXSPEC-2]);
  1813.     }
  1814.     if (!isalpha(c)) {
  1815. if (c == '%') // %% -> %
  1816.     putc(c, fd);
  1817. else
  1818.     fprintf(fd, "%.*s%c", fp-fspec, fspec, c);
  1819. continue;
  1820.     }
  1821.     fp[0] = jformat[c-'A']; // printf format string
  1822.     fp[1] = '';
  1823.             switch (c) {
  1824.     case 'A':
  1825. fprintf(fd, fspec, (const char*) job.subaddr);
  1826. break;
  1827.     case 'B':
  1828. fprintf(fd, fspec, haveAccess ? (const char*) job.passwd : "");
  1829. break;
  1830.     case 'C':
  1831. fprintf(fd, fspec, (const char*) job.company);
  1832. break;
  1833.     case 'D':
  1834. fprintf(fd, fspec, (const char*)fxStr::format("%2u:%-2u", job.totdials, job.maxdials));
  1835. break;
  1836.     case 'E':
  1837. fprintf(fd, fspec, job.desiredbr);
  1838. break;
  1839.     case 'F':
  1840. fprintf(fd, fspec, (const char*) job.tagline);
  1841. break;
  1842.     case 'G':
  1843. fprintf(fd, fspec, job.desiredst);
  1844. break;
  1845.     case 'H':
  1846. fprintf(fd, fspec, job.desireddf);
  1847. break;
  1848.     case 'I':
  1849. fprintf(fd, fspec, job.usrpri);
  1850. break;
  1851.     case 'J':
  1852. fprintf(fd, fspec, (const char*) job.jobtag);
  1853. break;
  1854.     case 'K':
  1855. fprintf(fd, fspec, "D HF"[job.desiredec]);
  1856. break;
  1857.     case 'L':
  1858. fprintf(fd, fspec, (const char*) job.location);
  1859. break;
  1860.     case 'M':
  1861. fprintf(fd, fspec, (const char*) job.mailaddr);
  1862. break;
  1863.     case 'N':
  1864. fprintf(fd, fspec, " P"[job.desiredtl]);
  1865. break;
  1866.     case 'O':
  1867. fprintf(fd, fspec, "N "[job.useccover]);
  1868. break;
  1869.     case 'P':
  1870. fprintf(fd, fspec, (const char*)fxStr::format("%2u:%-2u", job.npages, job.totpages));
  1871. break;
  1872.     case 'Q':
  1873. fprintf(fd, fspec, job.minbr);
  1874. break;
  1875.     case 'R':
  1876. fprintf(fd, fspec, (const char*) job.receiver);
  1877. break;
  1878.     case 'S':
  1879. fprintf(fd, fspec, (const char*) job.sender);
  1880. break;
  1881.     case 'T':
  1882. fprintf(fd, fspec, (const char*)fxStr::format("%2u:%-2u", job.tottries, job.maxtries));
  1883. break;
  1884.     case 'U':
  1885. fprintf(fd, fspec, (const char*)fxStr::format("%.1f", job.chopthreshold));
  1886. break;
  1887.     case 'V':
  1888. fprintf(fd, fspec, (const char*) job.doneop);
  1889. break;
  1890.     case 'W':
  1891. fprintf(fd, fspec, (const char*) job.commid);
  1892. break;
  1893.     case 'X':
  1894. fprintf(fd, fspec, toupper(job.jobtype[0]));
  1895. break;
  1896.     case 'Y':
  1897. { char buf[30]; // XXX HP C++
  1898.   strftime(buf, sizeof (buf), "%Y/%m/%d %H.%M.%S",
  1899. IS(USEGMT) ? gmtime(&job.tts) : localtime(&job.tts));
  1900.   fprintf(fd, fspec, buf);
  1901. }
  1902. break;
  1903.     case 'Z':
  1904. fprintf(fd, fspec, job.tts);
  1905. break;
  1906.     case 'a':
  1907. fprintf(fd, fspec, "?TPSBWRDF"[job.state]);
  1908. break;
  1909.     case 'b':
  1910. fprintf(fd, fspec, job.ntries);
  1911. break;
  1912.     case 'c':
  1913. fprintf(fd, fspec, (const char*) job.client);
  1914. break;
  1915.     case 'd':
  1916. fprintf(fd, fspec, job.totdials);
  1917. break;
  1918.     case 'e':
  1919. fprintf(fd, fspec, (const char*) job.external);
  1920. break;
  1921.     case 'f':
  1922. fprintf(fd, fspec, job.ndials);
  1923. break;
  1924.     case 'g':
  1925. fprintf(fd, fspec, (const char*) job.groupid);
  1926. break;
  1927.     case 'h':
  1928. fprintf(fd, fspec, " DAL"[job.pagechop]);
  1929. break;
  1930.     case 'i':
  1931. fprintf(fd, fspec, job.pri);
  1932. break;
  1933.     case 'j':
  1934. fprintf(fd, fspec, (const char*) job.jobid);
  1935. break;
  1936.     case 'k':
  1937. fprintf(fd, fspec, compactTime(job.killtime));
  1938. break;
  1939.     case 'l':
  1940. fprintf(fd, fspec, job.pagelength);
  1941. break;
  1942.     case 'm':
  1943. fprintf(fd, fspec, (const char*) job.modem);
  1944. break;
  1945.     case 'n':
  1946. fprintf(fd, fspec, " DQA"[job.notify]);
  1947. break;
  1948.     case 'o':
  1949. fprintf(fd, fspec, (const char*) job.owner);
  1950. break;
  1951.     case 'p':
  1952. fprintf(fd, fspec, job.npages);
  1953. break;
  1954.     case 'q':
  1955. fprintf(fd, fspec,
  1956.     job.retrytime == 0 ? "" : fmtTime(job.retrytime));
  1957. break;
  1958.     case 'r':
  1959. fprintf(fd, fspec, job.resolution);
  1960. break;
  1961.     case 's':
  1962. fprintf(fd, fspec, (const char*) job.notice);
  1963. break;
  1964.     case 't':
  1965. fprintf(fd, fspec, job.tottries);
  1966. break;
  1967.     case 'u':
  1968. fprintf(fd, fspec, job.maxtries);
  1969. break;
  1970.     case 'v':
  1971. fprintf(fd, fspec, haveAccess ? (const char*) job.number : "");
  1972. break;
  1973.     case 'w':
  1974. fprintf(fd, fspec, job.pagewidth);
  1975. break;
  1976.     case 'x':
  1977. fprintf(fd, fspec, job.maxdials);
  1978. break;
  1979.     case 'y':
  1980. fprintf(fd, fspec, job.totpages);
  1981. break;
  1982.     case 'z':
  1983. fprintf(fd, fspec, compactTime(job.tts));
  1984. break;
  1985.     case '0':
  1986. fprintf(fd, fspec, "N "[job.usexvres]);
  1987. break;
  1988.     }
  1989. } else
  1990.     putc(*cp, fd);
  1991.     }
  1992. }
  1993. void
  1994. HylaFAXServer::listSendQ(FILE* fd, const SpoolDir&, DIR* dir)
  1995. {
  1996.     fxStrArray files;
  1997.     struct dirent* dp;
  1998.     while ((dp = readdir(dir))) {
  1999. files.append(dp->d_name);
  2000.     }
  2001.     files.qsort();
  2002.     for (u_int i = 0, n = files.length(); i < n; i++) {
  2003. if (files[i][0] == 'q') {
  2004.     fxStr emsg;
  2005.     Job* job = findJob(&((const char*)files[i])[1], emsg);
  2006.     if (job) {
  2007. Jprintf(fd, jobFormat, *job);
  2008. fputs("rn", fd);
  2009.     }
  2010. }
  2011.     }
  2012. }
  2013. void
  2014. HylaFAXServer::listSendQFile(FILE* fd, const SpoolDir& dir,
  2015.     const char* filename, const struct stat& sb)
  2016. {
  2017.     fxStr emsg;
  2018.     Job* job = findJob(filename, emsg);
  2019.     if (job)
  2020. Jprintf(fd, jobFormat, *job);
  2021.     else
  2022. listUnixFile(fd, dir, filename, sb);
  2023. }
  2024. void
  2025. HylaFAXServer::nlstSendQ(FILE* fd, const SpoolDir&, DIR* dir)
  2026. {
  2027.     fxStrArray files;
  2028.     struct dirent* dp;
  2029.     while ((dp = readdir(dir))) {
  2030. files.append(dp->d_name);
  2031.     }
  2032.     files.qsort();
  2033.     for (u_int i = 0, n = files.length(); i < n; i++) {
  2034. if (files[i][0] == 'q') {
  2035.     fxStr emsg;
  2036.     Job* job = findJob(&((const char*)files[i])[1], emsg);
  2037.     if (job)
  2038. Jprintf(fd, "%jrn", *job);
  2039. }
  2040.     }
  2041. }
  2042. void
  2043. HylaFAXServer::nlstSendQFile(FILE* fd, const SpoolDir&,
  2044.     const char* filename, const struct stat&)
  2045. {
  2046.     fxStr emsg;
  2047.     Job* job = findJob(filename, emsg);
  2048.     if (job)
  2049. Jprintf(fd, "%j", *job);
  2050.     else
  2051. fprintf(fd, "%s", filename);
  2052. }