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

传真(Fax)编程

开发平台:

C/C++

  1. /* $Id: faxmail.c++,v 1.29 2009/09/29 11:46:01 faxguy Exp $ */
  2. /*
  3.  * Copyright (c) 1990-1996 Sam Leffler
  4.  * Copyright (c) 1991-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 "MsgFmt.h"
  27. #include "MIMEState.h"
  28. #include "StackBuffer.h"
  29. #include "StrArray.h"
  30. #include "Sys.h"
  31. #include "SendFaxClient.h"
  32. #include "config.h"
  33. #include <ctype.h>
  34. #include <errno.h>
  35. #if HAS_LOCALE
  36. extern "C" {
  37. #include <locale.h>
  38. }
  39. #endif
  40. class MySendFaxClient : public SendFaxClient {
  41. public:
  42.     MySendFaxClient();
  43.     ~MySendFaxClient();
  44.     void setup(bool);
  45.     bool setConfigItem(const char* tag, const char* value);
  46.     void fatal(const char* fmt ...);
  47. };
  48. MySendFaxClient::MySendFaxClient() {}
  49. MySendFaxClient::~MySendFaxClient() {}
  50. void
  51. MySendFaxClient::setup(bool b)
  52. {
  53.     resetConfig();
  54.     readConfig(FAX_SYSCONF);
  55.     readConfig(FAX_LIBDATA "/sendfax.conf");
  56.     readConfig(FAX_LIBDATA "/faxmail.conf");
  57.     readConfig(FAX_USERCONF);
  58.     setVerbose(b);
  59.     FaxClient::setVerbose(b);
  60. }
  61. bool MySendFaxClient::setConfigItem(const char* tag, const char* value)
  62.     { return SendFaxClient::setConfigItem(tag, value); }
  63. class faxMailApp : public TextFormat, public MsgFmt {
  64. private:
  65.     bool markDiscarded; // mark MIME parts not handled
  66.     bool withinFile; // between beginFile() & endFile()
  67.     fxStr mimeConverters; // pathname to MIME converter scripts
  68.     fxStr mailProlog; // site-specific prologue definitions
  69.     fxStr clientProlog; // client-specific prologue info
  70.     fxStr pageSize; // record specified page size
  71.     fxStr msgDivider; // digest message divider
  72.     fxStrArray tmps; // temp files
  73.     MySendFaxClient* client; // for direct delivery
  74.     SendFaxJob* job; // reference to outbound job
  75.     fxStr coverTempl; // coverpage template file
  76.     fxStr mailUser; // user ID for contacting server
  77.     fxStr notify; // notification request
  78.     fxStr tsi; // user-specified TSI string
  79.     bool autoCoverPage; // make cover page for direct delivery
  80.     bool formatEnvHeaders; // format envelope headers
  81.     bool trimText; // trim text parts
  82.     void formatMIME(FILE* fd, MIMEState& mime, MsgFmt& msg);
  83.     void formatText(FILE* fd, MIMEState& mime);
  84.     void formatMultipart(FILE* fd, MIMEState& mime, MsgFmt& msg);
  85.     void formatMessage(FILE* fd, MIMEState& mime, MsgFmt& msg);
  86.     void formatApplication(FILE* fd, MIMEState& mime);
  87.     void formatDiscarded(MIMEState& mime);
  88.     bool formatWithExternal(FILE* fd, const fxStr& app, MIMEState& mime);
  89.     void emitClientPrologue(FILE*);
  90.     void discardPart(FILE* fd, MIMEState& mime);
  91.     bool copyPart(FILE* fd, MIMEState& mime, fxStr& tmpFile);
  92.     bool runConverter(const fxStr& app, const fxStr& tmp, MIMEState& mime);
  93.     void copyFile(FILE* fd, const char* filename);
  94.     void resetConfig();
  95.     void setupConfig();
  96.     bool setConfigItem(const char* tag, const char* value);
  97.     void usage(void);
  98. public:
  99.     faxMailApp();
  100.     ~faxMailApp();
  101.     void run(int argc, char** argv);
  102.     void setVerbose(bool b) { verbose = b; }
  103.     void setBoldFont(const char* cp) { boldFont = cp; }
  104.     void setItalicFont(const char* cp) { italicFont = cp; }
  105. };
  106. faxMailApp::faxMailApp()
  107. {
  108.     client = NULL;
  109.     faxMailApp::setupConfig(); // NB: virtual
  110.     setTitle("HylaFAX-Mail");
  111. }
  112. faxMailApp::~faxMailApp()
  113. {
  114.     delete client;
  115.     for (u_int i = 0, n = tmps.length(); i < n; i++)
  116. Sys::unlink(tmps[i]);
  117. }
  118. void
  119. faxMailApp::run(int argc, char** argv)
  120. {
  121.     extern int optind;
  122.     extern char* optarg;
  123.     int c;
  124.     const char* mailPass = NULL;
  125.     resetConfig();
  126.     readConfig(FAX_SYSCONF);
  127.     readConfig(FAX_LIBDATA "/faxmail.conf");
  128.     readConfig(FAX_USERCONF);
  129.     bool deliver = false;
  130.     while ((c = Sys::getopt(argc, argv, "12b:cC:df:H:i:M:nNO:p:rRs:S:t:Tu:vW:")) != -1)
  131. switch (c) {
  132. case '1': case '2': // format in 1 or 2 columns
  133.     setNumberOfColumns(c - '0');
  134.     break;
  135. case 'b': // bold font for headers
  136.     setBoldFont(optarg);
  137.     break;
  138. case 'c': // truncate lines
  139.     setLineWrapping(false);
  140.     break;
  141. case 'C': // specify cover template to use
  142.     coverTempl = optarg;
  143.     break;
  144. case 'd': // enable direct delivery
  145.     deliver = true;
  146.     break;
  147. case 'f': // default font for text body
  148.     setTextFont(optarg);
  149.     break;
  150. case 'H': // page height
  151.     setPageHeight(atof(optarg));
  152.     break;
  153. case 'i': // italic font for headers
  154.     setItalicFont(optarg);
  155.     break;
  156. case 'M': // page margins
  157.     setPageMargins(optarg);
  158.     break;
  159. case 'n': // suppress cover page
  160.     autoCoverPage = false;
  161.     break;
  162. case 'N': // suppress formatting envelope headers
  163.     formatEnvHeaders = false;
  164.     break;
  165. case 'O':
  166.     readConfigItem(optarg);
  167.     break;
  168. case 'p': // point size
  169.     setTextPointSize(TextFormat::inch(optarg));
  170.     break;
  171. case 'r': // rotate page (landscape)
  172.     setPageOrientation(TextFormat::LANDSCAPE);
  173.     break;
  174. case 'R': // don't rotate page (portrait)
  175.     setPageOrientation(TextFormat::PORTRAIT);
  176.     break;
  177. case 's': // page size
  178.     pageSize = optarg;
  179.     setPageSize(pageSize);
  180.     break;
  181. case 'S': // set TSI
  182.     tsi = optarg;
  183.     break;
  184. case 't': // job state notification request
  185.     notify = optarg;
  186.     break;
  187. case 'T': // suppress formatting MIME text parts
  188.     trimText = true;
  189.     break;
  190. case 'u': // mail/login user
  191.     {
  192. char* pp = strchr(optarg, ':');
  193. if (pp && *(pp + 1) != '') {
  194.     *pp = '';
  195.     mailPass = pp + 1;
  196. }
  197.     }
  198.     mailUser = optarg;
  199.     break;
  200. case 'W': // page width
  201.     setPageWidth(atof(optarg));
  202.     break;
  203. case 'v': // trace work
  204.     setVerbose(true);
  205.     break;
  206. case '?':
  207.     usage();
  208.     /*NOTREACHED*/
  209. }
  210.     MIMEState mime("text", "plain");
  211.     parseHeaders(stdin, mime.lineno); // collect top-level headers
  212.     if (!getPageHeaders()) // narrow top+bottom margins
  213. setPageMargins("t=0.35in,b=0.25in");
  214.     else
  215. setPageMargins("t=0.6in,b=0.25in");
  216.     if (deliver) {
  217. /*
  218.  * Direct delivery was specified on the command line.
  219.  * Setup to submit the formatted facsimile directly
  220.  * to a server.
  221.  */
  222. client = new MySendFaxClient;
  223. client->setup(verbose);
  224. /*
  225.  * Setup the destination (dialstring and
  226.  * optionally a receipient).  Dialing stuff
  227.  * given on the command line is replaced by
  228.  * information in the envelope so that strings
  229.  * that contain characters that are invalid
  230.  * email addresses can be specified.
  231.  */
  232. job = &client->addJob();
  233. if (optind < argc) { // specified on command line
  234.     fxStr dest(argv[optind]); // [person@]number
  235.     u_int l = dest.next(0, '@');
  236.     if (l != dest.length()) {
  237. job->setCoverName(dest.head(l));
  238. dest.cut(0, l+1);
  239.     }
  240.     job->setDialString(dest);
  241. }
  242. const fxStr* s;
  243. if ((s = findHeader("x-fax-dialstring")))  // dialstring in envelope
  244.     job->setDialString(*s);
  245. if (job->getDialString() == "")
  246.     fxFatal("No Destination/Dialstring specified");
  247. /*
  248.  * Establish the sender's identity.
  249.  */
  250. if (optind+1 < argc) {
  251.     client->setFromIdentity(argv[optind+1]);
  252. } else if ((s = findHeader("from"))) {
  253.     client->setFromIdentity(*s);
  254. } else {
  255.     fxFatal("No From/Sender identity specified");
  256. }
  257. if (pageSize != "") {
  258.     job->setPageSize(pageSize);
  259. }
  260. if (notify != "") {
  261.     job->setNotification((const char*) notify);
  262. }
  263. if (tsi != "") {
  264.     job->setTSI((const char*) tsi);
  265. }
  266. /*
  267.  * Scan envelope for any meta-headers that
  268.  * control how job submission is to be done.
  269.  */
  270. job->setAutoCoverPage(autoCoverPage);
  271. for (u_int i = 0, n = fields.length();  i < n; i++) {
  272.     const fxStr& field = fields[i];
  273.     if (strncasecmp(field, "x-fax-", 6) != 0)
  274. continue;
  275.     fxStr tag(field.tail(field.length() - 6));
  276.     tag.lowercase();
  277.     if (job->setConfigItem(tag, MsgFmt::headers[i]))
  278. ;
  279.     else if (client->setConfigItem(tag, MsgFmt::headers[i]))
  280. ;
  281. }
  282. /*
  283.  * If a cover page is desired fill in any info
  284.  * from the envelope that might be useful.
  285.  */
  286. if (job->getAutoCoverPage()) {
  287.     if (coverTempl.length()) job->setCoverTemplate(coverTempl);
  288.     /*
  289.      * If nothing has been specified for a
  290.      * regarding field on the cover page and
  291.      * a subject line exists, use that info.
  292.      */
  293.     if (job->getCoverRegarding() == "" && (s = findHeader("subject"))) {
  294. fxStr subj(*s);
  295. decodeRFC2047(subj);
  296. while (subj.length() > 3 && strncasecmp(subj, "Re:", 3) == 0)
  297.     subj.remove(0, subj.skip(3, " t"));
  298. job->setCoverRegarding(subj);
  299.     }
  300.     /*
  301.      * Likewise for the receipient name.
  302.      */
  303.     if (job->getCoverName() == "" && ((s = findHeader("x-fax-to")) || (s = findHeader("to")))) {
  304. /*
  305.  * Try to extract a user name from the to information.
  306.  */
  307. fxStr to(*s);
  308. u_int l = to.next(0, '<');
  309. u_int tl = to.length();
  310. if (l == tl) {
  311.     l = to.next(0, '(');
  312.     if (l != tl) // joe@foobar (Joe Schmo), no longer works due to RFC822 compliance in parseHeaders (in-parenthesis are stripped as comments)
  313. l++, to = to.token(l, ')');
  314.     else { // joe@foobar
  315. l = to.next(0, '@');
  316. if (l != tl)
  317.     to = to.head(l);
  318.     }
  319. } else { // Joe Schmo <joe@foobar>
  320.     to = to.head(l);
  321. }
  322. // strip any leading&trailing white space or double-quotes
  323. to.remove(0, to.skip(0, " t""));
  324. to.resize(to.skipR(to.length(), " t""));
  325. job->setCoverName(to);
  326.     }
  327. }
  328. /*
  329.  * Redirect formatted output to a temp
  330.  * file and setup delivery of the file.
  331.  */
  332.     const char* templ = _PATH_TMP "/faxmail.XXXXXX";
  333.     char* buff = new char[strlen(templ) + 1];
  334.     strcpy(buff, templ);
  335.     int fd = Sys::mkstemp(buff);
  336. if (fd < 0) {
  337.         fxFatal("Cannot create temp file %s", (const char*) buff);
  338.     }
  339. tmps.append(buff);
  340. client->addFile(buff);
  341.     delete[] buff;
  342. beginFormatting(fdopen(fd, "w"));
  343.     } else
  344. beginFormatting(stdout); // NB: sets up page info
  345.     const fxStr* version = findHeader("MIME-Version");
  346.     if (version && stripComments(*version) == "1.0") {
  347. if (verbose)
  348.     fprintf(stderr, "faxmail: This is a MIME messagen");
  349. beginFile();
  350. withinFile = true;
  351.         if (formatEnvHeaders) formatHeaders(*this); // format top-level headers
  352. formatMIME(stdin, mime, *this); // parse MIME format
  353.         if (withinFile) endFile();
  354. withinFile = false;
  355.     } else {
  356. if (verbose)
  357.     fprintf(stderr, "faxmail: This is not a MIME messagen");
  358. beginFile();
  359. withinFile = true;
  360. if (formatEnvHeaders) formatHeaders(*this); // format top-level headers
  361. formatText(stdin, mime); // treat body as text/plain
  362.         if (withinFile) endFile();
  363. withinFile = false;
  364.     }
  365.     endFormatting(mime.external);
  366.     if (client) { // complete direct delivery
  367. bool status = false;
  368. fxStr emsg;
  369. const char* user = mailUser;
  370. if (user[0] == '') // null user =>'s use real uid
  371.     user = NULL;
  372. if (client->callServer(emsg)) {
  373.     status = client->login(user, mailPass, emsg)
  374.   && client->prepareForJobSubmissions(emsg)
  375.   && client->submitJobs(emsg);
  376.     client->hangupServer();
  377. }
  378. if (!status)
  379.     fxFatal("%s", (const char*) emsg);
  380.     }
  381. }
  382. /*
  383.  * Emit PostScript prologue stuff defined in
  384.  * system-wide prologue file and possibly
  385.  * supplied in a MIME-encoded body part.
  386.  */
  387. void
  388. faxMailApp::emitClientPrologue(FILE* fd)
  389. {
  390.     if (mailProlog != "") // site-specific prologue
  391. copyFile(fd, mailProlog);
  392.     if (clientProlog != "") // copy client-specific prologue
  393. copyFile(fd, clientProlog);
  394. }
  395. /*
  396.  * Parse MIME headers and dispatch to the appropriate
  397.  * formatter based on the content-type information.
  398.  */
  399. void
  400. faxMailApp::formatMIME(FILE* fd, MIMEState& mime, MsgFmt& msg)
  401. {
  402.     fxStr emsg;
  403.     if (mime.parse(msg, emsg)) {
  404. if (verbose)
  405.     mime.trace(stderr);
  406. /*
  407.  * In theory each MIME part could be in a different
  408.  * character set, and each text mail header (such as
  409.  * the subject header) could also be in multiple
  410.  * character sets.  Supporting this would probably 
  411.  * require faxmail to scan the entire mail beforehand
  412.  * to add the requisite font definitions early in
  413.  * the Postscript output... which isn't how faxmail
  414.  * does things right now.
  415.  *
  416.  * Fortunately, the likelihood of a mail being written
  417.  * in more than one character set (not including ASCII)
  418.  * would be extremely rare and quite possibly only in
  419.  * contrived scenarios.  So for now we just apply a single
  420.  * character set to the entire mail message.
  421.  *
  422.  * In the past we did this:
  423.  *
  424.  * setISO8859(mime.getCharset() != CS_USASCII);
  425.  *
  426.  * However, this makes it impossible for RFC-2047-encoded
  427.  * subject lines to show properly, as well as any 
  428.  * subsequent non-ASCII mail parts.
  429.  *
  430.  * Eventually it may be best to have the character set 
  431.  * be selected by a command-line argument, however, since
  432.  * TextFormat.c++ only supports ISO-8859-1 at the moment
  433.  * there is no point.  And since ASCII is completely 
  434.  * covered by ISO-8859-1 there is no reason to prefer
  435.  * ASCII over ISO-8859-1.
  436.  */
  437. setISO8859(true);
  438. /*
  439.  * Check first for any external script/command to
  440.  * use in converting the body part to PostScript.
  441.  * If something is present, then we just decode the
  442.  * body part into a temporary file and hand it to
  443.  * the script.  Otherwise we fallback on some builtin
  444.  * body part handlers that process the most common
  445.  * content types we expect to encounter.
  446.  *
  447.  * We expect externally formatted documents to be 
  448.  * complete Postscript pages in and of themselves.
  449.  * (Otherwise either the formatter would need to know
  450.  * numerous details about the current state and settings
  451.  * of the existing Postscript formatting and would need
  452.  * to make careful use of them, or we would need to 
  453.  * implement our own Postscript interpreter to filter 
  454.  * the external formatter output to suit.)
  455.  */
  456. mime.external = false;
  457. const fxStr& type = mime.getType();
  458. fxStr app = mimeConverters | "/" | type | "/" | mime.getSubType();
  459. if (Sys::access(app, X_OK) >= 0) {
  460.     mime.external = true;
  461.     if (mime.lineno > 1) endPage(); // new page
  462.     formatWithExternal(fd, app, mime);
  463. } else if (type == "text")
  464.     formatText(fd, mime);
  465. else if (type == "application")
  466.     formatApplication(fd, mime);
  467. else if (type == "multipart")
  468.     formatMultipart(fd, mime, msg);
  469. else if (type == "message")
  470.     formatMessage(fd, mime, msg);
  471. else { // cannot handle, discard
  472.     discardPart(fd, mime);
  473.     formatDiscarded(mime);
  474. }
  475.     } else
  476. error("%s", (const char*) emsg);
  477. }
  478. /*
  479.  * Format a text part.
  480.  */
  481. void
  482. faxMailApp::formatText(FILE* fd, MIMEState& mime)
  483. {
  484.     fxStackBuffer buf;
  485.     bool trim = trimText;
  486.     while (mime.getLine(fd, buf)) {
  487. if (trim) trim = ((buf.getLength() == 0) || (buf[0] == 0xA));
  488. if (!trim) format(buf, buf.getLength()); // NB: treat as text/plain
  489.     }
  490. }
  491. /*
  492.  * Format a multi-part part.
  493.  */
  494. void
  495. faxMailApp::formatMultipart(FILE* fd, MIMEState& mime, MsgFmt& msg)
  496. {
  497.     discardPart(fd, mime); // prologue
  498.     if (!mime.isLastPart()) {
  499. bool last = false;
  500. while (!last) {
  501.     int c = getc(fd);
  502.     if (c == EOF) {
  503. error("Badly formatted MIME; premature EOF");
  504. break;
  505.     }
  506.     ungetc(c, fd); // push back read ahead
  507.     MsgFmt bodyHdrs(msg); // parse any headers
  508.     bodyHdrs.parseHeaders(fd, mime.lineno);
  509.     MIMEState bodyMime(mime); // state for sub-part
  510.     formatMIME(fd, bodyMime, bodyHdrs);
  511.     last = bodyMime.isLastPart();
  512.     mime.external = bodyMime.external;
  513. }
  514.     }
  515. }
  516. /*
  517.  * Format a message part.
  518.  */
  519. void
  520. faxMailApp::formatMessage(FILE* fd, MIMEState& mime, MsgFmt& msg)
  521. {
  522.     if (mime.getSubType() == "rfc822") {
  523. MsgFmt bodyHdrs(msg);            
  524. bodyHdrs.parseHeaders(fd, mime.lineno);
  525. /*
  526.  * Calculate the amount of space required to format
  527.  * the message headers and any required inter-message
  528.  * divider mark.  If there isn't enough space to put
  529.  * the headers and one line of text together on the
  530.  * same column then break and start a new column.
  531.  */
  532. const char* divider = NULL;
  533. if (mime.isParent("multipart", "digest") && msgDivider != "") {
  534.     /*
  535.      * XXX, hack.  Avoid inserting a digest divider when
  536.      * the sub-part is a faxmail prologue or related part.
  537.      */
  538.     const fxStr* s = bodyHdrs.findHeader("Content-Type");
  539.     if (!s || !strneq(*s, "application/x-faxmail", 21))
  540. divider = msgDivider;
  541. }
  542. u_int nl = bodyHdrs.headerCount() // header lines
  543.     + (bodyHdrs.headerCount() > 0) // blank line following header
  544.     + (divider != NULL) // digest divider
  545.     + 1; // 1st text line of message
  546. reserveVSpace(nl*getTextLineHeight());
  547. if (divider) { // emit digest divider
  548.     beginLine();
  549. fprintf(getOutputFile(), " %s ", divider);
  550.     endLine();
  551. }
  552. if (nl > 0)
  553.     bodyHdrs.formatHeaders(*this); // emit formatted headers
  554. MIMEState subMime(mime);
  555. formatMIME(fd, subMime, bodyHdrs); // format message body
  556.     } else if (mime.getSubType() == "delivery-status") {
  557. formatText(fd, mime);
  558.     } else {
  559. discardPart(fd, mime);
  560. formatDiscarded(mime);
  561.     }
  562. }
  563. /*
  564.  * Format an application part.
  565.  */
  566. void
  567. faxMailApp::formatApplication(FILE* fd, MIMEState& mime)
  568. {
  569.     if (mime.getSubType() == "postscript") { // copy PS straight thru
  570. if (withinFile) endFile();
  571. withinFile = false;
  572. FILE* fout = getOutputFile();
  573. fxStackBuffer buf;
  574. int ignore;
  575. while (mime.getLine(fd, buf))
  576.     ignore = fwrite((const char*) buf, buf.getLength(), 1, fout);
  577. if (!withinFile) beginFile();
  578. withinFile = true;
  579.     } else if (mime.getSubType() == "x-faxmail-prolog") {
  580. copyPart(fd, mime, clientProlog); // save client PS prologue
  581.     } else {
  582. discardPart(fd, mime);
  583. formatDiscarded(mime);
  584.     }
  585. }
  586. /*
  587.  * Format a MIME part using an external conversion
  588.  * script to convert the decoded body to PostScript.
  589.  */
  590. bool
  591. faxMailApp::formatWithExternal(FILE* fd, const fxStr& app, MIMEState& mime)
  592. {
  593.     bool ok = false;
  594.     if (verbose)
  595. fprintf(stderr, "CONVERT: run %sn", (const char*) app);
  596.     fxStr tmp;
  597.     if (copyPart(fd, mime, tmp)) {
  598. flush(); // flush pending stuff
  599. ok = runConverter(app, tmp, mime);
  600. Sys::unlink(tmp);
  601.     }
  602.     return (ok);
  603. }
  604. /*
  605.  * Mark a discarded part if configured.
  606.  */
  607. void
  608. faxMailApp::formatDiscarded(MIMEState& mime)
  609. {
  610.     if (markDiscarded) {
  611. fxStackBuffer buf;
  612. buf.put("-----------------------------n");
  613. if (mime.getDescription() != "")
  614.     buf.fput("DISCARDED %s (%s/%s) GOES HEREn"
  615. , (const char*) mime.getDescription()
  616. , (const char*) mime.getType()
  617. , (const char*) mime.getSubType()
  618.     );
  619. else
  620.     buf.fput("DISCARDED %s/%s GOES HEREn"
  621. , (const char*) mime.getType()
  622. , (const char*) mime.getSubType()
  623.     );
  624. format((const char*)buf, buf.getLength());
  625.     }
  626.     if (mime.getDescription() != "")
  627. fprintf(stderr, "DISCARDED: %s (%s/%s)n",
  628.     (const char*) mime.getDescription(),
  629.     (const char*) mime.getType(),
  630.     (const char*) mime.getSubType());
  631.     else
  632. fprintf(stderr, "DISCARDED: %s/%sn",
  633.     (const char*) mime.getType(),
  634.     (const char*) mime.getSubType());
  635. }
  636. /*
  637.  * Discard input data up to the next boundary marker.
  638.  */
  639. void
  640. faxMailApp::discardPart(FILE* fd, MIMEState& mime)
  641. {
  642.     fxStackBuffer buf;
  643.     while (mime.getLine(fd, buf))
  644. ; // discard input data
  645. }
  646. /*
  647.  * Copy input data up to the next boundary marker.
  648.  * The data is stored in a temporary file that is
  649.  * either created or, if passed in, appended to.
  650.  * The latter is used, for example, to accumulate
  651.  * client-specified prologue data.
  652.  */
  653. bool
  654. faxMailApp::copyPart(FILE* fd, MIMEState& mime, fxStr& tmpFile)
  655. {
  656.     int ftmp;
  657.     if (tmpFile == "") {
  658.         const char* templ = _PATH_TMP "/faxmail.XXXXXX";
  659.         char* buff = new char[strlen(templ) + 1];
  660.         strcpy(buff, templ);
  661.         ftmp = Sys::mkstemp(buff);
  662.         tmpFile = buff;
  663.         delete[] buff;
  664.         tmps.append(tmpFile);
  665.     } else {
  666.         ftmp = Sys::open(tmpFile, O_WRONLY|O_APPEND);
  667.     }
  668.     if (ftmp >= 0) {
  669.         /*
  670.         if (!Sys::isRegularFile(tmpFile)) {
  671.             error("%s: is not a regular file", (const char*) tmpFile);
  672.             return(false);
  673.         }
  674.         */
  675.         fxStackBuffer buf;
  676.         bool ok = true;
  677.         while (mime.getLine(fd, buf) && ok) {
  678.         ok = ((u_int) Sys::write(ftmp, buf, buf.getLength()) == buf.getLength());
  679.         }
  680.         if (ok) {
  681.             Sys::close(ftmp);
  682.             return (true);
  683.         }
  684.         error("%s: write error: %s", (const char*) tmpFile, strerror(errno));
  685.         Sys::close(ftmp);
  686.     } else {
  687.     error("%s: Can not create temporary file", (const char*) tmpFile);
  688.     }
  689.     discardPart(fd, mime);
  690.     return (false);
  691. }
  692. /*
  693.  * Run an external converter program.
  694.  */
  695. bool
  696. faxMailApp::runConverter(const fxStr& app, const fxStr& tmp, MIMEState& mime)
  697. {
  698.     const char* av[6];
  699.     av[0] = strrchr(app, '/');
  700.     if (av[0] == NULL)
  701. av[0] = app;
  702.     // XXX should probably pass in MIME state like charset
  703.     u_int i = 1;
  704.     av[i++] = tmp;
  705.     fxStr label;
  706.     if (mime.getDescription() != "") {
  707. label = "description:";
  708. label.append(mime.getDescription());
  709. av[i++] = (const char*) label;
  710.     }
  711.     if (mime.getContentID() != "") {
  712. label = "id:";
  713. label.append(mime.getContentID());
  714. av[i++] = (const char*) label;
  715.     }
  716.     if (mime.getDisposition() != "") {
  717. label = "disposition:";
  718. label.append(mime.getDisposition());
  719. av[i++] = (const char*) label;
  720.     }
  721.     av[i++] = NULL;
  722.     pid_t pid = fork();
  723.     switch (pid) {
  724.     case -1: // error
  725. error("Error converting %s/%s; could not fork subprocess: %s"
  726.     , (const char*) mime.getType()
  727.     , (const char*) mime.getSubType()
  728.     , strerror(errno)
  729. );
  730. break;
  731.     case 0: // child, exec command
  732. dup2(fileno(getOutputFile()), STDOUT_FILENO);
  733. Sys::execv(app, (char* const*) av);
  734. _exit(-1);
  735. /*NOTREACHED*/
  736.     default:
  737. int status;
  738. if (Sys::waitpid(pid, status) == pid && status == 0)
  739.     return (true);
  740. error("Error converting %s/%s; command was "%s %s"; exit status %x"
  741.     , (const char*) mime.getType()
  742.     , (const char*) mime.getSubType()
  743.     , (const char*) app
  744.     , (const char*) tmp
  745.     , status
  746. );
  747. break;
  748.     }
  749.     return (false);
  750. }
  751. /*
  752.  * Copy the contents of the specified file to
  753.  * the output stream.
  754.  */
  755. void
  756. faxMailApp::copyFile(FILE* fd, const char* filename)
  757. {
  758.     int fp = Sys::open(filename, O_RDONLY);
  759.     if (fp >= 0) {
  760. char buf[16*1024];
  761. u_int cc, ignore;
  762. while ((cc = Sys::read(fp, buf, sizeof (buf))) > 0)
  763.     ignore = fwrite(buf, cc, 1, fd);
  764. Sys::close(fp);
  765.     }
  766. }
  767. /*
  768.  * Configuration support.
  769.  */
  770. void
  771. faxMailApp::setupConfig()
  772. {
  773.     markDiscarded = true;
  774.     mimeConverters = FAX_LIBDATA "/faxmail";
  775.     mailProlog = FAX_LIBDATA "/faxmail.ps";
  776.     msgDivider = "";
  777.     pageSize = "";
  778.     mailUser = ""; // default to real uid
  779.     notify = "";
  780.     tsi = "";
  781.     coverTempl = "";
  782.     autoCoverPage = true; // a la sendfax
  783.     formatEnvHeaders = true; // format envelope headers by default
  784.     trimText = false; // don't trim leading CRs from text parts by default
  785.     setPageHeaders(false); // disable normal page headers
  786.     setNumberOfColumns(1); // 1 input page per output page
  787.     setLineWrapping(true);
  788.     setISO8859(true);
  789.     MsgFmt::setupConfig();
  790. }
  791. void
  792. faxMailApp::resetConfig()
  793. {
  794.     TextFormat::resetConfig();
  795.     setupConfig();
  796. }
  797. #undef streq
  798. #define streq(a,b) (strcasecmp(a,b)==0)
  799. bool
  800. faxMailApp::setConfigItem(const char* tag, const char* value)
  801. {
  802.     if (streq(tag, "markdiscarded"))
  803. markDiscarded = getBoolean(value);
  804.     else if (streq(tag, "autocoverpage"))
  805. autoCoverPage = getBoolean(value);
  806.     else if (streq(tag, "formatenvheaders"))
  807. formatEnvHeaders = getBoolean(value);
  808.     else if (streq(tag, "trimtext"))
  809. trimText = getBoolean(value);
  810.     else if (streq(tag, "mimeconverters"))
  811. mimeConverters = value;
  812.     else if (streq(tag, "prologfile"))
  813. mailProlog = value;
  814.     else if (streq(tag, "digestdivider"))
  815. msgDivider = value;
  816.     else if (streq(tag, "mailuser"))
  817. mailUser = value;
  818.     else if (MsgFmt::setConfigItem(tag, value))
  819. ;
  820.     else if (TextFormat::setConfigItem(tag, value))
  821. ;
  822.     else
  823. return (false);
  824.     return (true);
  825. }
  826. #undef streq
  827. #include <signal.h>
  828. static faxMailApp* app = NULL;
  829. static void
  830. cleanup()
  831. {
  832.     faxMailApp* a = app;
  833.     app = NULL;
  834.     delete a;
  835. }
  836. static void
  837. sigDone(int)
  838. {
  839.     cleanup();
  840.     exit(-1);
  841. }
  842. int
  843. main(int argc, char** argv)
  844. {
  845. #ifdef LC_CTYPE
  846.     setlocale(LC_CTYPE, ""); // for <ctype.h> calls
  847. #endif
  848.     app = new faxMailApp;
  849.     app->run(argc, argv);
  850.     signal(SIGHUP, fxSIGHANDLER(SIG_IGN));
  851.     signal(SIGINT, fxSIGHANDLER(SIG_IGN));
  852.     signal(SIGTERM, fxSIGHANDLER(SIG_IGN));
  853.     cleanup();
  854.     return (0);
  855. }
  856. static void
  857. vfatal(FILE* fd, const char* fmt, va_list ap)
  858. {
  859.     fprintf(stderr, "faxmail: ");
  860.     vfprintf(fd, fmt, ap);
  861.     va_end(ap);
  862.     fputs(".n", fd);
  863.     sigDone(0);
  864. }
  865. void
  866. MySendFaxClient::fatal(const char* fmt ...)
  867. {
  868.     va_list ap;
  869.     va_start(ap, fmt);
  870.     vfatal(stderr, fmt, ap);
  871.     /*NOTTEACHED*/
  872. }
  873. void
  874. fxFatal(const char* fmt ...)
  875. {
  876.     va_list ap;
  877.     va_start(ap, fmt);
  878.     vfatal(stderr, fmt, ap);
  879.     /*NOTREACHED*/
  880. }
  881. void
  882. faxMailApp::usage()
  883. {
  884.     fxFatal("usage: faxmail"
  885. " [-b boldfont]"
  886. " [-C template]"
  887. " [-H pageheight]"
  888. " [-i italicfont]"
  889. " [-f textfont]"
  890. " [-p pointsize]"
  891. " [-s pagesize]"
  892. " [-t when]"
  893. " [-W pagewidth]"
  894. " [-M margins]"
  895. " [-u user]"
  896. " [-12cdnNrRTv]"
  897.     );
  898. }