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 "config.h" //for BR_14400 definition
27 #include "class2.h" //for BR_14400 definition
28 #include "SendFaxClient.h"
29 #include "FaxDB.h"
30 #include "Sys.h"
31 #include "NLS.h"
32 #include "config.h"
33 #include <ctype.h> // isspace()
34
35 #if HAS_LOCALE
36 extern "C" {
37 #include <locale.h>
38 }
39 #endif
40
41 class sendFaxApp : public SendFaxClient {
42 private:
43 fxStr appName; // for error messages
44 fxStr stdinTemp; // temp file for collecting from pipe
45 FaxDB* db;
46
47 static fxStr dbName;
48
49 void addDestination(const char* cp);
50 void addDestinationsFromFile(const char* filename);
51 void copyToTemporary(int fin, fxStr& tmpl);
52 void fatal(const char* fmt ...);
53 void usage();
54 public:
55 sendFaxApp();
56 ~sendFaxApp();
57
58 bool run(int argc, char** argv);
59 };
60
61 fxStr sendFaxApp::dbName("~/.faxdb");
62
63 sendFaxApp::sendFaxApp()
64 {
65 db = NULL;
66 }
67
68 sendFaxApp::~sendFaxApp()
69 {
70 if (stdinTemp != "") Sys::unlink(stdinTemp);
71 delete db;
72 }
73
74 bool
75 sendFaxApp::run(int argc, char** argv)
76 {
77 extern int optind;
78 extern char* optarg;
79 int c;
80 char *owner = NULL;
81 bool optionsUsed = true;
82
83 appName = argv[0];
84 u_int l = appName.length();
85 appName = appName.tokenR(l, '/');
86
87 resetConfig();
88 readConfig(FAX_SYSCONF);
89 readConfig(FAX_LIBDATA "/sendfax.conf");
90 readConfig(FAX_USERCONF);
91
92 bool waitForJob = false;
93 int verbose = 0;
94 SendFaxJob& proto = getProtoJob();
95 db = new FaxDB(tildeExpand(dbName));
96 while ((c = Sys::getopt(argc, argv, "a:b:B:c:C:d:f:F:h:i:I:k:M:o:O:P:r:s:S:t:T:U:V:W:x:X:y:Y:z:Z:123lmnpvwADEGNR")) != -1) {
97 if (c != 'h')
98 optionsUsed = false;
99 switch (c) {
100 case '1': // restrict to 1D-encoded data
101 proto.setDesiredDF(0);
102 break;
103 case '2': // restrict to 2D-encoded data
104 proto.setDesiredDF(1);
105 break;
106 case '3': // restrict to MMR-encoded data
107 proto.setDesiredDF(3);
108 break;
109 case 'a': // time at which to transmit job
110 proto.setSendTime(optarg);
111 break;
112 case 'A': // archive job
113 proto.setDoneOp("archive");
114 break;
115 case 'b': // minimum transfer speed
116 proto.setMinSpeed(optarg);
117 break;
118 case 'B': // desired transmit speed
119 proto.setDesiredSpeed(optarg);
120 break;
121 case 'C': // cover page: template file
122 proto.setCoverTemplate(optarg);
123 break;
124 case 'c': // cover page: comment field
125 proto.setCoverComments(optarg);
126 break;
127 case 'D': // notify when done
128 proto.setNotification("when done");
129 break;
130 case 'd': // destination name and number
131 optionsUsed = true;
132 addDestination(optarg);
133 break;
134 case 'E': // disable use of ECM
135 proto.setDesiredEC(false);
136 break;
137 case 'F': // override tag line format string
138 proto.setTagLineFormat(optarg);
139 break;
140 case 'f': // sender's identity
141 setFromIdentity(optarg);
142 break;
143 case 'G': // extended resolutions
144 proto.setUseXVRes(true);
145 break;
146 case 'h': // server's host
147 setHost(optarg);
148 break;
149 case 'I': // fixed retry time
150 proto.setRetryTime(optarg);
151 break;
152 case 'i': // user-specified job identifier
153 proto.setJobTag(optarg);
154 break;
155 case 'k': // time to kill job
156 proto.setKillTime(optarg);
157 break;
158 case 'l': // low resolution
159 proto.setVResolution(98.);
160 break;
161 case 'M': // desired min-scanline time
162 proto.setDesiredMST(optarg);
163 break;
164 case 'm': // fine resolution
165 proto.setVResolution(196.);
166 break;
167 case 'n': // no cover sheet
168 proto.setAutoCoverPage(false);
169 break;
170 case 'N': // no notification
171 proto.setNotification("none");
172 break;
173 case 'o': // specify owner
174 owner = optarg;
175 break;
176 case 'O':
177 readConfigItem(optarg);
178 break;
179 case 'p': // submit polling request
180 addPollRequest();
181 break;
182 case 'P': // set scheduling priority
183 proto.setPriority(optarg);
184 break;
185 case 'r': // cover sheet: regarding field
186 proto.setCoverRegarding(optarg);
187 break;
188 case 'R': // notify when requeued or done
189 proto.setNotification("when requeued");
190 break;
191 case 's': // set page size
192 proto.setPageSize(optarg);
193 break;
194 case 'S': // set TSI
195 proto.setTSI(optarg);
196 break;
197 case 't': // times to retry sending
198 proto.setMaxRetries(atoi(optarg));
199 break;
200 case 'T': // times to dial telephone
201 proto.setMaxDials(atoi(optarg));
202 break;
203 case 'U': // cover page: sender's voice number
204 proto.setCoverFromVoice(optarg);
205 break;
206 case 'v': // verbose mode
207 verbose++;
208 setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
209 SendFaxClient::setVerbose(true); // type rules & basic operation
210 FaxClient::setVerbose(verbose > 1); // protocol tracing
211 break;
212 case 'V': // cover sheet: voice number field
213 proto.setCoverVoiceNumber(optarg);
214 break;
215 case 'w': // wait for job to complete
216 waitForJob = true;
217 break;
218 case 'W': // cover page: sender's fax number
219 proto.setCoverFromFax(optarg);
220 break;
221 case 'x': // cover page: to's company
222 proto.setCoverCompany(optarg);
223 break;
224 case 'X': // cover page: sender's company
225 proto.setCoverFromCompany(optarg);
226 break;
227 case 'y': // cover page: to's location
228 proto.setCoverLocation(optarg);
229 break;
230 case 'Y': // cover page: sender's location
231 proto.setCoverFromLocation(optarg);
232 break;
233 case 'z': // destinations from file
234 optionsUsed = true;
235 addDestinationsFromFile(optarg);
236 break;
237 case 'Z':
238 proto.setPageRange(optarg);
239 break;
240 case '?':
241 usage();
242 /*NOTREACHED*/
243 }
244 }
245 if (getNumberOfJobs() == 0) {
246 fprintf(stderr, _("%s: No destination specified.\n"),
247 (const char*) appName);
248 usage();
249 }
250 if (!optionsUsed) {
251 fprintf(stderr, _("%s: Unused options after last destination.\n"),
252 (const char*) appName);
253 usage();
254 }
255 if (optind < argc) {
256 for (; optind < argc; optind++) {
257 addFile(argv[optind]);
258 }
259 } else if (getNumberOfPollRequests() == 0) {
260 copyToTemporary(fileno(stdin), stdinTemp);
261 addFile(stdinTemp);
262 }
263 bool status = false;
264 fxStr emsg;
265 if (callServer(emsg)) {
266 status = login(owner, emsg)
267 && prepareForJobSubmissions(emsg)
268 && submitJobs(emsg);
269 if (status && waitForJob) {
270 if (getNumberOfJobs() > 1) {
271 printWarning(_("can only wait for one job (right now),"
272 " waiting for job %s."), (const char*) getCurrentJob());
273 }
274 jobWait(getCurrentJob());
275 }
276 hangupServer();
277 }
278 if (!status) printError("%s", (const char*) emsg);
279 return (status);
280 }
281
282 void
283 sendFaxApp::usage()
284 {
285 fxFatal(_("usage: %s [options] [files]\n"
286 "(Read the manual page; it's too complicated)"), (const char*) appName);
287 }
288
289 /*
290 * Add a destination; parse ``person@number#subaddress'' syntax.
291 * T.33 Appendix I suggests that ``#'' be used to tag a subaddress.
292 */
293 void
294 sendFaxApp::addDestination(const char* cp)
295 {
296 fxStr recipient;
297 const char* tp = strchr(cp, '@');
298 if (tp) {
299 recipient = fxStr(cp, tp-cp);
300 cp = tp+1;
301 } else {
302 recipient = "";
303 }
304 fxStr subaddress;
305 size_t sublen = 0;
306 const char* ap = strchr(cp, '#');
307 if (ap) {
308 ap = ap+1;
309 subaddress = fxStr(ap);
310 sublen = strlen(subaddress) + 1;
311 } else {
312 subaddress = "";
313 }
314 fxStr dest(cp, strlen(cp) - sublen);
315 if (db && dest.length() > 0) {
316 fxStr name;
317 FaxDBRecord* r = db->find(dest, &name);
318 if (r) {
319 if (recipient == "")
320 recipient = name;
321 dest = r->find(FaxDB::numberKey);
322 }
323 }
324 if (dest.length() == 0) {
325 fatal(_("Null destination for \"%s\""), cp);
326 }
327 SendFaxJob& job = addJob();
328 job.setDialString(dest);
329 job.setCoverName(recipient);
330 job.setSubAddress(subaddress);
331 if(job.getDesiredSpeed() > BR_14400 && job.getDesiredEC() == false) {
332 printWarning(_("ECM disabled, limiting job to 14400 bps."));
333 job.setDesiredSpeed(BR_14400);
334 }
335 }
336
337 /*
338 * Add a destinations form file
339 */
340 void
341 sendFaxApp::addDestinationsFromFile(const char* filename)
342 {
343 FILE* destfile;
344 char dest[256];
345 char *cp;
346
347 if ((destfile = fopen(filename, "r")) != NULL) {
348 while (fgets(dest, sizeof(dest), destfile)) {
349 for (cp = strchr(dest, '\0'); cp>dest && isspace(cp[-1]); cp--);
350 *cp='\0';
351 if (dest[0] != '#' && dest[0] != '\0')
352 addDestination(dest);
353 }
354 } else {
355 fatal(_("%s: no such file"), filename);
356 }
357 }
358
359 /*
360 * Copy data from fin to a temporary file.
361 */
362 void
363 sendFaxApp::copyToTemporary(int fin, fxStr& tmpl)
364 {
365 const char* templ = _PATH_TMP "/sndfaxXXXXXX";
366 char* buff = strcpy(new char[strlen(templ) + 1], templ);
367 int fd = Sys::mkstemp(buff);
368 tmpl = buff;
369 delete [] buff;
370 if (fd < 0) {
371 fatal(_("%s: Can not create temporary file"), (const char*) tmpl);
372 }
373 int cc, total = 0;
374 char buf[16*1024];
375 while ((cc = Sys::read(fin, buf, sizeof (buf))) > 0) {
376 if (Sys::write(fd, buf, cc) != cc) {
377 Sys::unlink(tmpl);
378 fatal(_("%s: write error"), (const char*) tmpl);
379 }
380 total += cc;
381 }
382 Sys::close(fd);
383 if (total == 0) {
384 Sys::unlink(tmpl);
385 tmpl = "";
386 fatal(_("No input data; transmission aborted"));
387 }
388 }
389
390 #include <signal.h>
391
392 static sendFaxApp* app = NULL;
393
394 static void
395 cleanup()
396 {
397 sendFaxApp* a = app;
398 app = NULL;
399 delete a;
400 }
401
402 static void
403 sigDone(int)
404 {
405 cleanup();
406 exit(-1);
407 }
408
409 int
410 main(int argc, char** argv)
411 {
412 #ifdef LC_CTYPE
413 setlocale(LC_CTYPE, ""); // for <ctype.h> calls
414 #endif
415 #ifdef LC_TIME
416 setlocale(LC_TIME, ""); // for strftime calls
417 #endif
418 NLS::Setup("hylafax-client");
419 signal(SIGHUP, fxSIGHANDLER(sigDone));
420 signal(SIGINT, fxSIGHANDLER(sigDone));
421 signal(SIGTERM, fxSIGHANDLER(sigDone));
422 signal(SIGCHLD, fxSIGHANDLER(SIG_DFL)); // by YC
423 app = new sendFaxApp;
424 if (!app->run(argc, argv)) sigDone(0);
425 signal(SIGHUP, fxSIGHANDLER(SIG_IGN));
426 signal(SIGINT, fxSIGHANDLER(SIG_IGN));
427 signal(SIGTERM, fxSIGHANDLER(SIG_IGN));
428 cleanup();
429 return (0);
430 }
431
432 static void
433 vfatal(FILE* fd, const char* fmt, va_list ap)
434 {
435 vfprintf(fd, fmt, ap);
436 va_end(ap);
437 fputs(".\n", fd);
438 sigDone(0);
439 }
440
441 void
442 fxFatal(const char* va_alist ...)
443 #define fmt va_alist
444 {
445 va_list ap;
446 va_start(ap, fmt);
447 vfatal(stderr, fmt, ap);
448 /*NOTTEACHED*/
449 }
450 #undef fmt
451
452 void
453 sendFaxApp::fatal(const char* va_alist ...)
454 #define fmt va_alist
455 {
456 fprintf(stderr, "%s: ", (const char*) appName);
457 va_list ap;
458 va_start(ap, fmt);
459 vfatal(stderr, fmt, ap);
460 /*NOTTEACHED*/
461 }
462 #undef fmt