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