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