1 /* main.c
2 * Entry point, arguments and options.
3 * This file is part of the edbrowse project, released under GPL.
4 */
5
6 #include "eb.h"
7
8 #include <pthread.h>
9 #include <pcre.h>
10 #include <signal.h>
11
12 /* Define the globals that are declared in eb.h. */
13 /* See eb.h for descriptive comments. */
14
15 const char *progname;
16 const char eol[] = "\r\n";
17 const char *version = "3.7.7";
18 char *changeFileName;
19 char *configFile, *addressFile, *cookieFile;
20 char *mailDir, *mailUnread, *mailStash, *mailReply;
21 char *recycleBin, *sigFile, *sigFileEnd;
22 char *cacheDir;
23 int cacheSize = 1000, cacheCount = 10000;
24 char *ebTempDir, *ebUserDir;
25 char *userAgents[MAXAGENT + 1];
26 char *currentAgent;
27 bool allowRedirection = true, allowJS = true, sendReferrer = true;
28 bool blockJS;
29 bool ftpActive;
30 int webTimeout = 20, mailTimeout = 0;
31 int displayLength = 500;
32 int verifyCertificates = 1;
33 char *sslCerts;
34 int localAccount, maxAccount;
35 struct MACCOUNT accounts[MAXACCOUNT];
36 int maxMime;
37 struct MIMETYPE mimetypes[MAXMIME];
38 static struct DBTABLE dbtables[MAXDBT];
39 static int numTables;
40 volatile bool intFlag;
41 time_t intStart;
42 bool curlActive;
43 bool ismc, isimap, passMail;
44 char whichproc = 'e'; // edbrowse
45 bool inInput, listNA;
46 int fileSize;
47 char *dbarea, *dblogin, *dbpw; /* to log into the database */
48 bool fetchBlobColumns;
49 bool caseInsensitive, searchStringsAll, searchWrap = true;
50 bool binaryDetect = true;
51 bool inputReadLine;
52 bool curlAuthNegotiate = false;
53 int context = 1;
54 pst linePending;
55 struct ebSession sessionList[MAXSESSION], *cs;
56 int maxSession;
57 static pthread_mutex_t share_mutex = PTHREAD_MUTEX_INITIALIZER;
58
59 /*********************************************************************
60 Redirect the incoming mail into a file, based on the subject or the sender.
61 Along with the filters in .ebrc, this routine dips into your addressbook,
62 to see if the sender (by email) is one of your established aliases.
63 If it is, we save it in a file of the same name.
64 This is saved formatted, unless you put a minus sign
65 at the start of the alias in your address book.
66 This is the same convention as the from filters in .ebrc.
67 If you don't want an alias to act as a redirect filter,
68 put a ! at the beginning of the alias name.
69 *********************************************************************/
70
71 static void *inputForever(void *ptr);
72 static pthread_t foreground_thread;
finishBrowse(void)73 static void finishBrowse(void)
74 {
75 Tag *t;
76 int j;
77 char *a, *newbuf;
78 Frame *f;
79 whichproc = 'e';
80 // tags should certainly be set
81 if (!tagList)
82 return;
83 // set all scripts to complete
84 for (t = cw->scriptlist; t; t = t->same)
85 t->step = 6;
86 // kill any timers
87 for (f = &cw->f0; f; f = f->next)
88 delTimers(f);
89 if (allowJS) {
90 allowJS = false;
91 blockJS = true;
92 i_puts(MSG_JavaOff);
93 }
94 if (cw->browseMode)
95 return;
96 // We were in the middle of the browse command; this is typical.
97 a = render(0);
98 newbuf = htmlReformat(a);
99 nzFree(a);
100 cw->rnlMode = cw->nlMode;
101 cw->nlMode = false;
102 cw->binMode = false;
103 cw->r_dot = cw->dot, cw->r_dol = cw->dol;
104 cw->dot = cw->dol = 0;
105 cw->r_map = cw->map;
106 cw->map = 0;
107 memcpy(cw->r_labels, cw->labels, sizeof(cw->labels));
108 memset(cw->labels, 0, sizeof(cw->labels));
109 j = strlen(newbuf);
110 addTextToBuffer((pst) newbuf, j, 0, false);
111 free(newbuf);
112 cw->undoable = false;
113 cw->changeMode = false;
114 if (cf->fileName) {
115 j = strlen(cf->fileName);
116 cf->fileName = reallocMem(cf->fileName, j + 8);
117 strcat(cf->fileName, ".browse");
118 }
119 cw->browseMode = true;
120 }
121
122 /* Catch interrupt and react appropriately. */
catchSig(int n)123 static void catchSig(int n)
124 {
125 time_t now;
126 pthread_t t1 = foreground_thread, t2;
127 intFlag = true;
128 /* If we were reading from a file, or socket, this signal should
129 * cause the read to fail. Check for intFlag, so we know it was
130 * interrupted, and not an io failure.
131 * Then clean up appropriately. */
132 signal(SIGINT, catchSig);
133 if (inInput) {
134 i_puts(MSG_EnterInterrupt);
135 return;
136 }
137 if (!intStart) { // first time hitting ^C
138 time(&intStart);
139 return;
140 }
141 time(&now);
142 // should 45 seconds be configurable?
143 if (now < intStart + 45)
144 return;
145 // Let's do something drastic here; start a new thread and exit the current one.
146 i_puts(MSG_IntForce);
147 if (whichproc == 'j')
148 finishBrowse();
149 if (pthread_equal(t1, pthread_self())) {
150 if (pthread_create(&t2, NULL, inputForever, NULL))
151 return; // didn't work
152 pthread_exit(NULL);
153 } else {
154 pthread_kill(t1, SIGINT);
155 }
156 } /* catchSig */
157
setDataSource(char * v)158 void setDataSource(char *v)
159 {
160 dbarea = dblogin = dbpw = 0;
161 if (!v)
162 return;
163 if (!*v)
164 return;
165 dbarea = v;
166 v = strchr(v, ',');
167 if (!v)
168 return;
169 *v++ = 0;
170 dblogin = v;
171 v = strchr(v, ',');
172 if (!v)
173 return;
174 *v++ = 0;
175 dbpw = v;
176 } /* setDataSource */
177
178 /*
179 * Libcurl allows some really fine-grained access to data. We could
180 * have multiple mutexes if we want, and that might lead to less
181 * blocking. For now, we just use one mutex.
182 */
183
lock_share(CURL * handle,curl_lock_data data,curl_lock_access access,void * userptr)184 static void lock_share(CURL * handle, curl_lock_data data,
185 curl_lock_access access, void *userptr)
186 {
187 /* TODO error handling. */
188 pthread_mutex_lock(&share_mutex);
189 } /* lock_share */
190
unlock_share(CURL * handle,curl_lock_data data,void * userptr)191 static void unlock_share(CURL * handle, curl_lock_data data, void *userptr)
192 {
193 pthread_mutex_unlock(&share_mutex);
194 } /* unlock_share */
195
eb_curl_global_init(void)196 void eb_curl_global_init(void)
197 {
198 const unsigned int major = 7;
199 const unsigned int minor = 29;
200 const unsigned int patch = 0;
201 const unsigned int least_acceptable_version =
202 (major << 16) | (minor << 8) | patch;
203 curl_version_info_data *version_data = NULL;
204 CURLcode curl_init_status = curl_global_init(CURL_GLOBAL_ALL);
205 if (curl_init_status != 0)
206 goto libcurl_init_fail;
207 version_data = curl_version_info(CURLVERSION_NOW);
208 if (version_data->version_num < least_acceptable_version)
209 i_printfExit(MSG_CurlVersion, major, minor, patch);
210
211 // Initialize the global handle, to manage the cookie space.
212 global_share_handle = curl_share_init();
213 if (global_share_handle == NULL)
214 goto libcurl_init_fail;
215
216 curl_share_setopt(global_share_handle, CURLSHOPT_LOCKFUNC, lock_share);
217 curl_share_setopt(global_share_handle, CURLSHOPT_UNLOCKFUNC,
218 unlock_share);
219 curl_share_setopt(global_share_handle, CURLSHOPT_SHARE,
220 CURL_LOCK_DATA_COOKIE);
221 curl_share_setopt(global_share_handle, CURLSHOPT_SHARE,
222 CURL_LOCK_DATA_DNS);
223 curl_share_setopt(global_share_handle, CURLSHOPT_SHARE,
224 CURL_LOCK_DATA_SSL_SESSION);
225
226 global_http_handle = curl_easy_init();
227 if (global_http_handle == NULL)
228 goto libcurl_init_fail;
229 if (cookieFile && !ismc) {
230 curl_init_status =
231 curl_easy_setopt(global_http_handle, CURLOPT_COOKIEFILE,
232 "");
233 if (curl_init_status != CURLE_OK) {
234 goto libcurl_init_fail;
235 }
236 curl_init_status =
237 curl_easy_setopt(global_http_handle, CURLOPT_COOKIEJAR,
238 cookieFile);
239 if (curl_init_status != CURLE_OK)
240 goto libcurl_init_fail;
241 }
242 curl_init_status =
243 curl_easy_setopt(global_http_handle, CURLOPT_ENCODING, "");
244 if (curl_init_status != CURLE_OK)
245 goto libcurl_init_fail;
246
247 curl_init_status =
248 curl_easy_setopt(global_http_handle, CURLOPT_SHARE,
249 global_share_handle);
250 if (curl_init_status != CURLE_OK)
251 goto libcurl_init_fail;
252 curlActive = true;
253 return;
254
255 libcurl_init_fail:
256 i_printfExit(MSG_LibcurlNoInit);
257 } /* eb_curl_global_init */
258
eb_curl_global_cleanup(void)259 static void eb_curl_global_cleanup(void)
260 {
261 curl_easy_cleanup(global_http_handle);
262 curl_global_cleanup();
263 } /* eb_curl_global_cleanup */
264
ebClose(int n)265 void ebClose(int n)
266 {
267 bg_jobs(true);
268 dbClose();
269 if (curlActive) {
270 mergeCookies();
271 eb_curl_global_cleanup();
272 }
273 exit(n);
274 } /* ebClose */
275
276 static struct ebhost {
277 // j = nojs, v = novs, p = proxy, f = function,
278 // s = subject, t = to, r = reply, a = agentsite
279 char type, filler;
280 short n;
281 // watch out, these fields are highly overloaded, depending on type
282 char *host;
283 // for proxy entry we also have
284 char *prot, *domain;
285 } *ebhosts;
286 static size_t ebhosts_avail, ebhosts_max;
287
add_ebhost(char * host,char type)288 static void add_ebhost(char *host, char type)
289 {
290 if (ebhosts_max == 0) {
291 ebhosts_max = 32;
292 ebhosts = allocZeroMem(ebhosts_max * sizeof(struct ebhost));
293 } else if (ebhosts_avail >= ebhosts_max) {
294 ebhosts_max *= 2;
295 ebhosts =
296 reallocMem(ebhosts, ebhosts_max * sizeof(struct ebhost));
297 }
298 ebhosts[ebhosts_avail].host = host;
299 ebhosts[ebhosts_avail++].type = type;
300 } /* add_ebhost */
301
delete_ebhosts(void)302 static void delete_ebhosts(void)
303 {
304 nzFree(ebhosts);
305 ebhosts = NULL;
306 ebhosts_avail = ebhosts_max = 0;
307 } /* delete_ebhosts */
308
add_proxy(char * v)309 static void add_proxy(char *v)
310 {
311 char *q;
312 char *prot = 0, *domain = 0, *proxy = 0;
313 spaceCrunch(v, true, true);
314 q = strchr(v, ' ');
315 if (q) {
316 *q = 0;
317 if (!stringEqual(v, "*"))
318 prot = v;
319 v = q + 1;
320 q = strchr(v, ' ');
321 if (q) {
322 *q = 0;
323 if (!stringEqual(v, "*"))
324 domain = v;
325 v = q + 1;
326 }
327 }
328 if (!stringEqualCI(v, "direct"))
329 proxy = v;
330 add_ebhost(proxy, 'p');
331 ebhosts[ebhosts_avail - 1].prot = prot;
332 ebhosts[ebhosts_avail - 1].domain = domain;
333 }
334
335 // Are we ok to parse and execute javascript?
javaOK(const char * url)336 bool javaOK(const char *url)
337 {
338 int j;
339 if (!allowJS)
340 return false;
341 if (isDataURI(url))
342 return true;
343 for (j = 0; j < ebhosts_avail; ++j)
344 if (ebhosts[j].type == 'j' &&
345 patternMatchURL(url, ebhosts[j].host))
346 return false;
347 return true;
348 } /* javaOK */
349
350 /* Return true if the cert for this host should be verified. */
mustVerifyHost(const char * url)351 bool mustVerifyHost(const char *url)
352 {
353 int i;
354 if (!verifyCertificates)
355 return false;
356 for (i = 0; i < ebhosts_avail; i++)
357 if (ebhosts[i].type == 'v' &&
358 patternMatchURL(url, ebhosts[i].host))
359 return false;
360 return true;
361 } /* mustVerifyHost */
362
363 /*********************************************************************
364 Given a protocol and a domain, find the proxy server
365 to mediate your request.
366 This is the C version, using entries in .ebrc.
367 There is a javascript version of the same name, that we will support later.
368 This is a beginning, and it can be used even when javascript is disabled.
369 A return of null means DIRECT, and this is the default
370 if we don't match any of the proxy entries.
371 *********************************************************************/
372
findProxyForURL(const char * url)373 const char *findProxyForURL(const char *url)
374 {
375 struct ebhost *px = ebhosts;
376 int i;
377 char prot[MAXPROTLEN], host[MAXHOSTLEN];
378
379 if (!getProtHostURL(url, prot, host)) {
380 /* this should never happen */
381 return 0;
382 }
383
384 /* first match wins */
385 for (i = 0; i < ebhosts_avail; ++i, ++px) {
386 if (px->type != 'p')
387 continue;
388
389 if (px->prot) {
390 char *s = px->prot;
391 char *t;
392 int rc;
393 while (*s) {
394 t = strchr(s, '|');
395 if (t)
396 *t = 0;
397 rc = stringEqualCI(s, prot);
398 if (t)
399 *t = '|';
400 if (rc)
401 goto domain;
402 if (!t)
403 break;
404 s = t + 1;
405 }
406 continue;
407 }
408
409 domain:
410 if (!px->domain || patternMatchURL(url, px->domain))
411 return px->host;
412 }
413
414 return 0;
415 }
416
findAgentForURL(const char * url)417 const char *findAgentForURL(const char *url)
418 {
419 struct ebhost *px = ebhosts;
420 int i;
421 for (i = 0; i < ebhosts_avail; ++i, ++px)
422 if (px->type == 'a' && patternMatchURL(url, px->host))
423 return userAgents[px->n];
424 return 0;
425 }
426
mailRedirect(const char * to,const char * from,const char * reply,const char * subj)427 const char *mailRedirect(const char *to, const char *from,
428 const char *reply, const char *subj)
429 {
430 int rlen = strlen(reply);
431 int slen = strlen(subj);
432 int tlen = strlen(to);
433 int mlen; // length of match string
434 int i, k;
435 struct ebhost *f = ebhosts;
436 const char *m, *r; // match and redirect
437
438 for (i = 0; i < ebhosts_avail; ++i, ++f) {
439 char type = f->type;
440 if (strchr("rts", type)) {
441 m = f->prot;
442 mlen = strlen(m);
443 r = f->host;
444 }
445
446 switch (type) {
447 case 'r':
448 if (stringEqualCI(m, from))
449 return r;
450 if (stringEqualCI(m, reply))
451 return r;
452 if (*m == '@' && mlen < rlen &&
453 stringEqualCI(m, reply + rlen - mlen))
454 return r;
455 break;
456
457 case 't':
458 if (stringEqualCI(m, to))
459 return r;
460 if (*m == '@' && mlen < tlen
461 && stringEqualCI(m, to + tlen - mlen))
462 return r;
463 break;
464
465 case 's':
466 if (mlen > slen)
467 break;
468 if (mlen == slen) {
469 if (stringEqualCI(m, subj))
470 return r;
471 break;
472 }
473 /* a prefix or suffix match is ok */
474 /* have to be at least half the subject line */
475 if (slen > mlen + mlen)
476 break;
477 if (memEqualCI(m, subj, mlen))
478 return r;
479 k = slen - mlen;
480 if (memEqualCI(m, subj + k, mlen))
481 return r;
482 break;
483 } /* switch */
484 } /* loop */
485
486 r = reverseAlias(reply);
487 return r;
488 } /* mailRedirect */
489
setupEdbrowseTempDirectory(void)490 static void setupEdbrowseTempDirectory(void)
491 {
492 int userid;
493 #ifdef DOSLIKE
494 int l;
495 char *a;
496 ebTempDir = getenv("TEMP");
497 if (!ebTempDir) {
498 i_printf(MSG_NoEnvVar, "TEMP");
499 nl();
500 exit(1);
501 }
502 // put /edbrowse on the end
503 l = strlen(ebTempDir);
504 a = allocString(l + 10);
505 sprintf(a, "%s/edbrowse", ebTempDir);
506 ebTempDir = a;
507 userid = 0;
508 #else
509 ebTempDir = getenv("TMPDIR");
510 if (!ebTempDir) {
511 ebTempDir="/tmp/.edbrowse";
512 }
513 userid = geteuid();
514 #endif
515
516 // On a multiuser system, mkdir /tmp/.edbrowse at startup,
517 // by root, and then chmod 1777
518
519 if (fileTypeByName(ebTempDir, false) != 'd') {
520 /* no such directory, try to make it */
521 /* this temp edbrowse directory is used by everyone system wide */
522 if (mkdir(ebTempDir, MODE_rwx)) {
523 i_printf(MSG_TempDir, ebTempDir);
524 ebTempDir = 0;
525 return;
526 }
527 #ifndef DOSLIKE
528 // yes, we called mkdir with 777 above, but that was cut by umask.
529 chmod(ebTempDir, MODE_rwx);
530 #endif
531 }
532 // make room for user ID on the end
533 ebUserDir = allocMem(strlen(ebTempDir) + 30);
534 sprintf(ebUserDir, "%s/edbrowse.%d", ebTempDir, userid);
535 if (fileTypeByName(ebUserDir, false) != 'd') {
536 /* no such directory, try to make it */
537 if (mkdir(ebUserDir, 0700)) {
538 i_printf(MSG_TempDir, ebUserDir);
539 ebUserDir = 0;
540 return;
541 }
542 chmod(ebUserDir, 0700);
543 }
544 } /* setupEdbrowseTempDirectory */
545
546 /*\ MSVC Debug: May need to provide path to 3rdParty DLLs, like
547 * set PATH=F:\Projects\software\bin;%PATH% ...
548 \*/
549
550 /* I'm not going to expand wild card arguments here.
551 * I don't need to on Unix, and on Windows there is a
552 * setargv.obj, or something like that, that performs the expansion.
553 * I'll assume you have folded that object into libc.lib.
554 * So now you can edit *.c, on any operating system,
555 * and it will do the right thing, with no work on my part. */
556
557 static void loadReplacements(void);
558
main(int argc,char ** argv)559 int main(int argc, char **argv)
560 {
561 int cx, account;
562 bool rc, doConfig = true, autobrowse = false;
563 bool dofetch = false, domail = false;
564 static char agent0[64] = "edbrowse/";
565
566 #ifndef _MSC_VER // port setlinebuf(stdout);, if required...
567 /* In case this is being piped over to a synthesizer, or whatever. */
568 if (fileTypeByHandle(fileno(stdout)) != 'f')
569 setlinebuf(stdout);
570 #endif // !_MSC_VER
571
572 selectLanguage();
573 setHTTPLanguage(eb_language);
574
575 /* Establish the home directory, and standard edbrowse files thereunder. */
576 home = getenv("HOME");
577 #ifdef _MSC_VER
578 if (!home) {
579 home = getenv("APPDATA");
580 if (home) {
581 char *ebdata = (char *)allocMem(ABSPATH);
582 sprintf(ebdata, "%s\\edbrowse", home);
583 if (fileTypeByName(ebdata, false) != 'd') {
584 FILE *fp;
585 char *cfgfil;
586 if (mkdir(ebdata, 0700)) {
587 i_printfExit(MSG_NotHome); // TODO: more appropriate exit message...
588 }
589 cfgfil = (char *)allocMem(ABSPATH);
590 sprintf(cfgfil, "%s\\.ebrc", ebdata);
591 fp = fopen(cfgfil, "w");
592 if (fp) {
593 fwrite(ebrc_string, 1,
594 strlen(ebrc_string), fp);
595 fclose(fp);
596 }
597 i_printfExit(MSG_Personalize, cfgfil);
598
599 }
600 home = ebdata;
601 }
602 }
603 #endif // !_MSC_VER
604
605 /* Empty is the same as missing. */
606 if (home && !*home)
607 home = 0;
608 /* I require this, though I'm not sure what this means for non-Unix OS's */
609 if (!home)
610 i_printfExit(MSG_NotHome);
611 if (fileTypeByName(home, false) != 'd')
612 i_printfExit(MSG_NotDir, home);
613
614 configFile = allocMem(strlen(home) + 7);
615 sprintf(configFile, "%s/.ebrc", home);
616 /* if not present then create it, as was done above */
617 if (fileTypeByName(configFile, false) == 0) {
618 int fh = creat(configFile, MODE_private);
619 if (fh >= 0) {
620 write(fh, ebrc_string, strlen(ebrc_string));
621 close(fh);
622 i_printfExit(MSG_Personalize, configFile);
623 }
624 }
625
626 /* recycle bin and .signature files are unix-like, and not adjusted for windows. */
627 recycleBin = allocMem(strlen(home) + 8);
628 sprintf(recycleBin, "%s/.Trash", home);
629 if (fileTypeByName(recycleBin, false) != 'd') {
630 if (mkdir(recycleBin, 0700)) {
631 /* Don't want to abort here; we might be on a readonly filesystem.
632 * Don't have a Trash directory and can't creat one; yet we should move on. */
633 free(recycleBin);
634 recycleBin = 0;
635 }
636 }
637
638 if (recycleBin) {
639 mailStash = allocMem(strlen(recycleBin) + 12);
640 sprintf(mailStash, "%s/rawmail", recycleBin);
641 if (fileTypeByName(mailStash, false) != 'd') {
642 if (mkdir(mailStash, 0700)) {
643 free(mailStash);
644 mailStash = 0;
645 }
646 }
647 }
648
649 sigFile = allocMem(strlen(home) + 20);
650 sprintf(sigFile, "%s/.signature", home);
651 sigFileEnd = sigFile + strlen(sigFile);
652
653 strcat(agent0, version);
654 userAgents[0] = currentAgent = agent0;
655
656 setupEdbrowseTempDirectory();
657
658 progname = argv[0];
659 ++argv, --argc;
660
661 ttySaveSettings();
662 initializeReadline();
663
664 /* Let's everybody use my malloc and free routines */
665 pcre_malloc = allocMem;
666 pcre_free = nzFree;
667
668 loadReplacements();
669
670 if (argc && stringEqual(argv[0], "-c")) {
671 if (argc == 1) {
672 *argv = configFile;
673 doConfig = false;
674 } else {
675 configFile = argv[1];
676 argv += 2, argc -= 2;
677 }
678 }
679 if (doConfig)
680 readConfigFile();
681 account = localAccount;
682
683 for (; argc && argv[0][0] == '-'; ++argv, --argc) {
684 char *s = *argv;
685 ++s;
686
687 if (stringEqual(s, "v")) {
688 puts(version);
689 exit(0);
690 }
691
692 if (stringEqual(s, "d")) {
693 debugLevel = 4;
694 continue;
695 }
696
697 if (*s == 'd' && isdigitByte(s[1]) && !s[2]) {
698 debugLevel = s[1] - '0';
699 continue;
700 }
701
702 if (stringEqual(s, "e")) {
703 errorExit = true;
704 continue;
705 }
706
707 if (stringEqual(s, "b")) {
708 autobrowse = true;
709 continue;
710 }
711
712 if (*s == 'p')
713 ++s, passMail = true;
714
715 if (*s == 'm' || *s == 'f') {
716 if (!maxAccount)
717 i_printfExit(MSG_NoMailAcc);
718 if (*s == 'f') {
719 account = 0;
720 dofetch = true;
721 ++s;
722 if (*s == 'm')
723 domail = true, ++s;
724 } else {
725 domail = true;
726 ++s;
727 }
728 if (isdigitByte(*s)) {
729 account = strtol(s, &s, 10);
730 if (account == 0 || account > maxAccount)
731 i_printfExit(MSG_BadAccNb, maxAccount);
732 }
733 if (!*s) {
734 ismc = true; /* running as a mail client */
735 allowJS = false; /* no javascript in mail client */
736 eb_curl_global_init();
737 ++argv, --argc;
738 if (!argc || !dofetch)
739 break;
740 }
741 }
742
743 i_printfExit(MSG_Usage);
744 } /* options */
745
746 srand(time(0));
747
748 if (ismc) {
749 char **reclist, **atlist;
750 char *s, *body;
751 int nat, nalt, nrec;
752
753 if (!argc) {
754 /* This is fetch / read mode */
755 if (dofetch) {
756 int nfetch = 0;
757 if (account) {
758 isimap = accounts[account - 1].imap;
759 if (isimap)
760 domail = false;
761 nfetch = fetchMail(account);
762 } else {
763 nfetch = fetchAllMail();
764 }
765 if (!domail) {
766 if (nfetch)
767 i_printf(MSG_MessagesX, nfetch);
768 else
769 i_puts(MSG_NoMail);
770 }
771 }
772
773 if (domail) {
774 scanMail();
775 }
776
777 exit(0);
778 }
779
780 /* now in sendmail mode */
781 if (argc == 1)
782 i_printfExit(MSG_MinOneRec);
783 /* I don't know that argv[argc] is 0, or that I can set it to 0,
784 * so I back everything up by 1. */
785 reclist = argv - 1;
786 for (nat = nalt = 0; nat < argc; ++nat) {
787 s = argv[argc - 1 - nat];
788 if (*s != '+' && *s != '-')
789 break;
790 if (*s == '-')
791 ++nalt;
792 strmove(s, s + 1);
793 }
794 atlist = argv + argc - nat - 1;
795 if (atlist <= argv)
796 i_printfExit(MSG_MinOneRecBefAtt);
797 body = *atlist;
798 if (nat)
799 memmove(atlist, atlist + 1, sizeof(char *) * nat);
800 atlist[nat] = 0;
801 nrec = atlist - argv;
802 memmove(reclist, reclist + 1, sizeof(char *) * nrec);
803 atlist[-1] = 0;
804 if (sendMail(account, (const char **)reclist, body, 1,
805 (const char **)atlist, 0, nalt, true))
806 exit(0);
807 showError();
808 exit(1);
809 }
810
811 signal(SIGINT, catchSig);
812
813 cx = 0;
814 while (argc) {
815 char *file = *argv;
816 char *file2 = NULL; // will be allocated
817 ++cx;
818 if (cx == MAXSESSION)
819 i_printfExit(MSG_ManyOpen, MAXSESSION);
820 cxSwitch(cx, false);
821 if (cx == 1)
822 runEbFunction("init");
823
824 // function on the command line
825 if (file[0] == '<') {
826 runEbFunction(file + 1);
827 ++argv, --argc;
828 continue;
829 }
830
831 changeFileName = 0;
832 file2 = allocMem(strlen(file) + 10);
833 // Every URL needs a protocol.
834 if (missingProtURL(file))
835 sprintf(file2 + 2, "http://%s", file);
836 else
837 strcpy(file2 + 2, file);
838 file = file2 + 2;
839
840 if (autobrowse) {
841 const struct MIMETYPE *mt;
842 uchar sxfirst = 0;
843 if (isURL(file))
844 mt = findMimeByURL(file, &sxfirst);
845 else
846 mt = findMimeByFile(file);
847 if (mt && !mt->outtype)
848 playBuffer("pb", file);
849 else {
850 file2[0] = 'b';
851 file2[1] = ' ';
852 if (runCommand(file2))
853 debugPrint(1, "%d", fileSize);
854 else
855 showError();
856 }
857
858 } else {
859
860 cf->fileName = cloneString(file);
861 cf->firstURL = cloneString(file);
862 if (isSQL(file))
863 cw->sqlMode = true;
864 rc = readFileArgv(file, 0);
865 if (fileSize >= 0)
866 debugPrint(1, "%d", fileSize);
867 fileSize = -1;
868 if (!rc) {
869 showError();
870 } else if (changeFileName) {
871 nzFree(cf->fileName);
872 cf->fileName = changeFileName;
873 changeFileName = 0;
874 }
875 cw->undoable = cw->changeMode = false;
876 /* Browse the text if it's a url */
877 if (rc && isURL(cf->fileName)
878 && ((cf->mt && cf->mt->outtype)
879 || isBrowseableURL(cf->fileName))) {
880 if (runCommand("b"))
881 debugPrint(1, "%d", fileSize);
882 else
883 showError();
884 }
885 }
886
887 nzFree(file2);
888 ++argv, --argc;
889 } /* loop over files */
890 if (!cx) { /* no files */
891 ++cx;
892 cxSwitch(cx, false);
893 runEbFunction("init");
894 i_puts(MSG_Ready);
895 }
896 if (cx > 1)
897 cxSwitch(1, false);
898
899 inputForever(NULL);
900 return 0;
901 }
902
inputForever(void * ptr)903 static void *inputForever(void *ptr)
904 {
905 foreground_thread = pthread_self();
906 while (true) {
907 pst p = inputLine();
908 pst save_p = clonePstring(p);
909 if (perl2c((char *)p)) {
910 i_puts(MSG_EnterNull);
911 nzFree(save_p);
912 } else {
913 edbrowseCommand((char *)p, false);
914 nzFree(linePending);
915 linePending = save_p;
916 }
917 } /* infinite loop */
918 return NULL;
919 }
920
921 /* Find the balancing brace in an edbrowse function */
balance(const char * ip,int direction)922 static const char *balance(const char *ip, int direction)
923 {
924 int nest = 0;
925 uchar code;
926
927 while (true) {
928 if (direction > 0) {
929 ip = strchr(ip, '\n') + 1;
930 } else {
931 for (ip -= 2; *ip != '\n'; --ip) ;
932 ++ip;
933 }
934 code = *ip;
935 if (code == 0x83) {
936 if (nest)
937 continue;
938 break;
939 }
940 if (code == 0x81)
941 nest += direction;
942 if (code == 0x82)
943 nest -= direction;
944 if (nest < 0)
945 break;
946 }
947
948 return ip;
949 } /* balance */
950
951 #define MAXNEST 20 // nested blocks
952 /* Run an edbrowse function, as defined in the config file. */
953 /* This function must be reentrant. */
runEbFunction(const char * line)954 bool runEbFunction(const char *line)
955 {
956 char *linecopy = cloneString(line);
957 char *fncopy = 0;
958 char *allargs = 0;
959 const char *args[10];
960 int argl[10]; /* lengths of args */
961 const char *s;
962 char *t, *new;
963 int j, l, nest;
964 const char *ip; /* think instruction pointer */
965 const char *endl; /* end of line to be processed */
966 bool nofail, ok;
967 uchar code;
968 char stack[MAXNEST];
969 int loopcnt[MAXNEST];
970
971 /* Separate function name and arguments */
972 spaceCrunch(linecopy, true, false);
973 if (linecopy[0] == 0) {
974 setError(MSG_NoFunction);
975 goto fail;
976 }
977 memset(args, 0, sizeof(args));
978 memset(argl, 0, sizeof(argl));
979 t = strchr(linecopy, ' ');
980 if (t)
981 *t = 0;
982 for (s = linecopy; *s; ++s)
983 if (!isalnumByte(*s)) {
984 setError(MSG_BadFunctionName);
985 goto fail;
986 }
987 for (j = 0; j < ebhosts_avail; ++j)
988 if (ebhosts[j].type == 'f' &&
989 stringEqualCI(linecopy, ebhosts[j].prot + 1))
990 break;
991 if (j == ebhosts_avail) {
992 setError(MSG_NoSuchFunction, linecopy);
993 goto fail;
994 }
995 // This or a downstream function could invoke config.
996 // Don't know why anybody would do that!
997 fncopy = cloneString(ebhosts[j].host);
998 /* skip past the leading \n */
999 ip = fncopy + 1;
1000 nofail = (ebhosts[j].prot[0] == '+');
1001 nest = 0;
1002 ok = true;
1003
1004 /* collect arguments, ~0 first */
1005 if (t) {
1006 args[0] = allargs = cloneString(t + 1);
1007 argl[0] = strlen(allargs);
1008 } else {
1009 args[0] = allargs = emptyString;
1010 argl[0] = 0;
1011 }
1012
1013 j = 0;
1014 for (s = t; s; s = t) {
1015 if (++j >= 10) {
1016 // setError(MSG_ManyArgs);
1017 // goto fail;
1018 break;
1019 }
1020 args[j] = ++s;
1021 t = strchr(s, ' ');
1022 if (t)
1023 *t = 0;
1024 argl[j] = strlen(s);
1025 }
1026
1027 while ((code = *ip)) {
1028 if (intFlag) {
1029 setError(MSG_Interrupted);
1030 goto fail;
1031 }
1032 endl = strchr(ip, '\n');
1033
1034 if (code == 0x83) {
1035 ip = balance(ip, 1) + 2;
1036 --nest;
1037 continue;
1038 }
1039
1040 if (code == 0x82) {
1041 char control = stack[nest];
1042 char ucontrol = toupper(control);
1043 const char *start = balance(ip, -1);
1044 start = strchr(start, '\n') + 1;
1045 if (ucontrol == 'L') { /* loop */
1046 if (--loopcnt[nest])
1047 ip = start;
1048 else
1049 ip = endl + 1, --nest;
1050 continue;
1051 }
1052 if (ucontrol == 'W' || ucontrol == 'U') {
1053 bool jump = ok;
1054 if (islowerByte(control))
1055 jump ^= true;
1056 if (ucontrol == 'U')
1057 jump ^= true;
1058 ok = true;
1059 if (jump)
1060 ip = start;
1061 else
1062 ip = endl + 1, --nest;
1063 continue;
1064 }
1065 /* Apparently it's the close of an if or an else, just fall through */
1066 goto nextline;
1067 }
1068
1069 if (code == 0x81) {
1070 const char *skip = balance(ip, 1);
1071 bool jump;
1072 char control = ip[1];
1073 char ucontrol = toupper(control);
1074 stack[++nest] = control;
1075 if (ucontrol == 'L') {
1076 loopcnt[nest] = j = atoi(ip + 2);
1077 if (j)
1078 goto nextline;
1079 ahead:
1080 if (*skip == (char)0x82)
1081 --nest;
1082 ip = skip + 2;
1083 continue;
1084 }
1085 if (ucontrol == 'U')
1086 goto nextline;
1087 /* if or while, test on ok */
1088 jump = ok;
1089 if (isupperByte(control))
1090 jump ^= true;
1091 ok = true;
1092 if (jump)
1093 goto ahead;
1094 goto nextline;
1095 }
1096
1097 if (!ok && nofail)
1098 goto fail;
1099
1100 /* compute length of line, then build the line */
1101 l = endl - ip;
1102 for (s = ip; s < endl; ++s)
1103 if (*s == '~' && isdigitByte(s[1]))
1104 l += argl[s[1] - '0'];
1105 t = new = allocMem(l + 1);
1106 for (s = ip; s < endl; ++s) {
1107 if (*s == '~' && isdigitByte(s[1])) {
1108 j = *++s - '0';
1109 if (!args[j]) {
1110 setError(MSG_NoArgument, j);
1111 nzFree(new);
1112 goto fail;
1113 }
1114 strcpy(t, args[j]);
1115 t += argl[j];
1116 continue;
1117 }
1118 *t++ = *s;
1119 }
1120 *t = 0;
1121
1122 /* Here we go! */
1123 debugPrint(3, "< %s", new);
1124 jClearSync();
1125 ok = edbrowseCommand(new, true);
1126 free(new);
1127
1128 nextline:
1129 ip = endl + 1;
1130 }
1131
1132 if (!ok && nofail)
1133 goto fail;
1134
1135 nzFree(linecopy);
1136 nzFree(fncopy);
1137 nzFree(allargs);
1138 return true;
1139
1140 fail:
1141 nzFree(linecopy);
1142 nzFree(fncopy);
1143 nzFree(allargs);
1144 return false;
1145 } /* runEbFunction */
1146
findTableDescriptor(const char * sn)1147 struct DBTABLE *findTableDescriptor(const char *sn)
1148 {
1149 int i;
1150 struct DBTABLE *td = dbtables;
1151 for (i = 0; i < numTables; ++i, ++td)
1152 if (stringEqual(td->shortname, sn))
1153 return td;
1154 return 0;
1155 } /* findTableDescriptor */
1156
newTableDescriptor(const char * name)1157 struct DBTABLE *newTableDescriptor(const char *name)
1158 {
1159 struct DBTABLE *td;
1160 if (numTables == MAXDBT) {
1161 setError(MSG_ManyTables, MAXDBT);
1162 return 0;
1163 }
1164 td = dbtables + numTables++;
1165 td->name = td->shortname = cloneString(name);
1166 td->ncols = 0; /* it's already 0 */
1167 return td;
1168 } /* newTableDescriptor */
1169
1170 static char *configMemory;
1171
1172 // unread the config file, so we can read it again
unreadConfigFile(void)1173 void unreadConfigFile(void)
1174 {
1175 if (!configMemory)
1176 return;
1177 nzFree(configMemory);
1178 configMemory = 0;
1179
1180 memset(accounts, 0, sizeof(accounts));
1181 maxAccount = localAccount = 0;
1182 memset(mimetypes, 0, sizeof(mimetypes));
1183 maxMime = 0;
1184 memset(dbtables, 0, sizeof(dbtables));
1185 numTables = 0;
1186 memset(userAgents + 1, 0, sizeof(userAgents) - sizeof(userAgents[0]));
1187
1188 addressFile = NULL;
1189 cookieFile = NULL;
1190 sslCerts = NULL;
1191 downDir = NULL;
1192 mailDir = NULL;
1193 nzFree(cacheDir);
1194 cacheDir = NULL;
1195 nzFree(mailUnread);
1196 mailUnread = NULL;
1197 nzFree(mailReply);
1198 mailReply = NULL;
1199
1200 webTimeout = mailTimeout = 0;
1201 displayLength = 500;
1202
1203 setDataSource(NULL);
1204 setHTTPLanguage(eb_language);
1205 delete_ebhosts();
1206 } /* unreadConfigFile */
1207
1208 /* Order is important here: mail{}, mime{}, table{}, then global keywords */
1209 #define MAILWORDS 0
1210 #define MIMEWORDS 8
1211 #define TABLEWORDS 16
1212 #define GLOBALWORDS 20
1213
1214 static const char *const keywords[] = {
1215 "inserver", "outserver", "login", "password", "from", "reply",
1216 "inport", "outport",
1217 "type", "desc", "suffix", "protocol", "program",
1218 "content", "outtype", "urlmatch",
1219 "tname", "tshort", "cols", "keycol",
1220 "downdir", "maildir", "agent",
1221 "jar", "nojs", "cachedir",
1222 "webtimer", "mailtimer", "certfile", "datasource", "proxy",
1223 "agentsite", "localizeweb", "notused33", "novs", "cachesize",
1224 "adbook", 0
1225 };
1226
1227 /* Read the config file and populate the corresponding data structures. */
1228 /* This routine succeeds, or aborts via one of these macros. */
1229 #define cfgAbort0(m) { i_printf(m); nl(); return; }
1230 #define cfgAbort1(m, arg) { i_printf(m, arg); nl(); return; }
1231 #define cfgLine0(m) { i_printf(m, ln); nl(); return; }
1232 #define cfgLine1(m, arg) { i_printf(m, ln, arg); nl(); return; }
1233 #define cfgLine1a(m, arg) { i_printf(m, arg, ln); nl(); return; }
1234
readConfigFile(void)1235 void readConfigFile(void)
1236 {
1237 char *buf, *s, *t, *v, *q;
1238 int buflen, n;
1239 char c, ftype;
1240 bool cmt = false;
1241 bool startline = true;
1242 uchar mailblock = 0;
1243 bool mimeblock = false, tabblock = false;
1244 int nest, ln, j;
1245 int sn = 0; /* script number */
1246 char stack[MAXNEST];
1247 char last[24];
1248 int lidx = 0;
1249 struct MACCOUNT *act;
1250 struct MIMETYPE *mt;
1251 struct DBTABLE *td;
1252
1253 unreadConfigFile();
1254
1255 if (!fileIntoMemory(configFile, &buf, &buflen)) {
1256 i_printf(MSG_NoConfig, configFile);
1257 return;
1258 }
1259
1260 /* An extra newline won't hurt. */
1261 if (buflen && buf[buflen - 1] != '\n')
1262 buf[buflen++] = '\n';
1263
1264 // remember this allocated pointer in case we want to reset everything.
1265 configMemory = buf;
1266
1267 /* Undos, uncomment, watch for nulls */
1268 /* Encode mail{ as hex 81 m, and other encodings. */
1269 ln = 1;
1270 for (s = t = v = buf; s < buf + buflen; ++s) {
1271 c = *s;
1272 if (c == '\0')
1273 cfgLine0(MSG_EBRC_Nulls);
1274 if (c == '\r' && s[1] == '\n')
1275 continue;
1276
1277 if (cmt) {
1278 if (c != '\n')
1279 continue;
1280 cmt = false;
1281 }
1282
1283 if (c == '#' && startline) {
1284 cmt = true;
1285 goto putc;
1286 }
1287
1288 if (c == '\n') {
1289 last[lidx] = 0;
1290 lidx = 0;
1291 if (stringEqual(last, "}")) {
1292 *v = '\x82';
1293 t = v + 1;
1294 }
1295 if (stringEqual(last, "}else{")) {
1296 *v = '\x83';
1297 t = v + 1;
1298 }
1299 if (stringEqual(last, "mail{")) {
1300 *v = '\x81';
1301 v[1] = 'm';
1302 t = v + 2;
1303 }
1304 if (stringEqual(last, "plugin{") ||
1305 stringEqual(last, "mime{")) {
1306 *v = '\x81';
1307 v[1] = 'e';
1308 t = v + 2;
1309 }
1310 if (stringEqual(last, "table{")) {
1311 *v = '\x81';
1312 v[1] = 'b';
1313 t = v + 2;
1314 }
1315 if (stringEqual(last, "fromfilter{")) {
1316 *v = '\x81';
1317 v[1] = 'r';
1318 t = v + 2;
1319 }
1320 if (stringEqual(last, "tofilter{")) {
1321 *v = '\x81';
1322 v[1] = 't';
1323 t = v + 2;
1324 }
1325 if (stringEqual(last, "subjfilter{")) {
1326 *v = '\x81';
1327 v[1] = 's';
1328 t = v + 2;
1329 }
1330 if (stringEqual(last, "if(*){")) {
1331 *v = '\x81';
1332 v[1] = 'I';
1333 t = v + 2;
1334 }
1335 if (stringEqual(last, "if(?){")) {
1336 *v = '\x81';
1337 v[1] = 'i';
1338 t = v + 2;
1339 }
1340 if (stringEqual(last, "while(*){")) {
1341 *v = '\x81';
1342 v[1] = 'W';
1343 t = v + 2;
1344 }
1345 if (stringEqual(last, "while(?){")) {
1346 *v = '\x81';
1347 v[1] = 'w';
1348 t = v + 2;
1349 }
1350 if (stringEqual(last, "until(*){")) {
1351 *v = '\x81';
1352 v[1] = 'U';
1353 t = v + 2;
1354 }
1355 if (stringEqual(last, "until(?){")) {
1356 *v = '\x81';
1357 v[1] = 'u';
1358 t = v + 2;
1359 }
1360 if (!strncmp(last, "loop(", 5) && isdigitByte(last[5])) {
1361 q = last + 6;
1362 while (isdigitByte(*q))
1363 ++q;
1364 if (stringEqual(q, "){")) {
1365 *q = 0;
1366 last[4] = 'l';
1367 last[3] = '\x81';
1368 strcpy(v, last + 3);
1369 t = v + strlen(v);
1370 }
1371 }
1372 if (!strncmp(last, "function", 8) &&
1373 (last[8] == '+' || last[8] == ':')) {
1374 q = last + 9;
1375 if (*q == 0 || *q == '{' || *q == '(')
1376 cfgLine0(MSG_EBRC_NoFnName);
1377 #if 0
1378 if (isdigitByte(*q))
1379 cfgLine0(MSG_EBRC_FnDigit);
1380 #endif
1381 while (isalnumByte(*q))
1382 ++q;
1383 if (q - last - 9 > 10)
1384 cfgLine0(MSG_EBRC_FnTooLong);
1385 if (*q != '{' || q[1])
1386 cfgLine0(MSG_EBRC_SyntaxErr);
1387 last[7] = 'f';
1388 last[6] = '\x81';
1389 strcpy(v, last + 6);
1390 t = v + strlen(v);
1391 }
1392
1393 *t++ = c;
1394 v = t;
1395 ++ln;
1396 startline = true;
1397 continue;
1398 }
1399
1400 if (c == ' ' || c == '\t') {
1401 if (startline)
1402 continue;
1403 } else {
1404 if (lidx < sizeof(last) - 1)
1405 last[lidx++] = c;
1406 startline = false;
1407 }
1408
1409 putc:
1410 *t++ = c;
1411 }
1412 *t = 0; /* now it's a string */
1413
1414 /* Go line by line */
1415 ln = 1;
1416 nest = 0;
1417 stack[0] = ' ';
1418
1419 for (s = buf; *s; s = t + 1, ++ln) {
1420 t = strchr(s, '\n');
1421 if (t == s)
1422 continue; /* empty line */
1423 if (t == s + 1 && *s == '#')
1424 continue; /* comment */
1425 *t = 0; /* I'll put it back later */
1426
1427 /* Gather the filters in a mail filter block */
1428 if (mailblock > 1 && !strchr("\x81\x82\x83", *s)) {
1429 v = strchr(s, '>');
1430 if (!v)
1431 cfgLine0(MSG_EBRC_NoCondFile);
1432 while (v > s && (v[-1] == ' ' || v[-1] == '\t'))
1433 --v;
1434 if (v == s)
1435 cfgLine0(MSG_EBRC_NoMatchStr);
1436 c = *v, *v++ = 0;
1437 if (c != '>') {
1438 while (*v != '>')
1439 ++v;
1440 ++v;
1441 }
1442 while (*v == ' ' || *v == '\t')
1443 ++v;
1444 if (!*v)
1445 cfgLine1(MSG_EBRC_MatchNowh, s);
1446 add_ebhost(v, "xxrts"[mailblock]);
1447 ebhosts[ebhosts_avail - 1].prot = s;
1448 continue;
1449 }
1450
1451 v = strchr(s, '=');
1452 if (!v)
1453 goto nokeyword;
1454
1455 while (v > s && (v[-1] == ' ' || v[-1] == '\t'))
1456 --v;
1457 if (v == s)
1458 goto nokeyword;
1459 c = *v, *v = 0;
1460 for (q = s; q < v; ++q)
1461 if (!isalphaByte(*q)) {
1462 *v = c;
1463 goto nokeyword;
1464 }
1465
1466 n = stringInList(keywords, s);
1467 if (n < 0) {
1468 if (!nest)
1469 cfgLine1a(MSG_EBRC_BadKeyword, s);
1470 *v = c; /* put it back */
1471 goto nokeyword;
1472 }
1473
1474 if (nest)
1475 cfgLine0(MSG_EBRC_KeyInFunc);
1476
1477 if (n < MIMEWORDS && mailblock != 1)
1478 cfgLine1(MSG_EBRC_MailAttrOut, s);
1479
1480 if (n >= MIMEWORDS && n < TABLEWORDS && !mimeblock)
1481 cfgLine1(MSG_EBRC_MimeAttrOut, s);
1482
1483 if (n >= TABLEWORDS && n < GLOBALWORDS && !tabblock)
1484 cfgLine1(MSG_EBRC_TableAttrOut, s);
1485
1486 if (n >= MIMEWORDS && mailblock)
1487 cfgLine1(MSG_EBRC_MailAttrIn, s);
1488
1489 if ((n < MIMEWORDS || n >= TABLEWORDS) && mimeblock)
1490 cfgLine1(MSG_EBRC_MimeAttrIn, s);
1491
1492 if ((n < TABLEWORDS || n >= GLOBALWORDS) && tabblock)
1493 cfgLine1(MSG_EBRC_TableAttrIn, s);
1494
1495 /* act upon the keywords */
1496 ++v;
1497 if (c != '=') {
1498 while (*v != '=')
1499 ++v;
1500 ++v;
1501 }
1502 while (*v == ' ' || *v == '\t')
1503 ++v;
1504 if (!*v)
1505 cfgLine1(MSG_EBRC_NoAttr, s);
1506
1507 switch (n) {
1508 case 0: /* inserver */
1509 act->inurl = v;
1510 continue;
1511
1512 case 1: /* outserver */
1513 act->outurl = v;
1514 continue;
1515
1516 case 2: /* login */
1517 act->login = v;
1518 continue;
1519
1520 case 3: /* password */
1521 act->password = v;
1522 continue;
1523
1524 case 4: /* from */
1525 act->from = v;
1526 continue;
1527
1528 case 5: /* reply */
1529 act->reply = v;
1530 continue;
1531
1532 case 6: /* inport */
1533 if (*v == '*')
1534 act->inssl = 1, ++v;
1535 act->inport = atoi(v);
1536 continue;
1537
1538 case 7: /* outport */
1539 if (*v == '+')
1540 act->outssl = 4, ++v;
1541 if (*v == '^')
1542 act->outssl = 2, ++v;
1543 if (*v == '*')
1544 act->outssl = 1, ++v;
1545 act->outport = atoi(v);
1546 continue;
1547
1548 case 8: /* type */
1549 mt->type = v;
1550 continue;
1551
1552 case 9: /* desc */
1553 mt->desc = v;
1554 continue;
1555
1556 case 10: /* suffix */
1557 mt->suffix = v;
1558 continue;
1559
1560 case 11: /* protocol */
1561 mt->prot = v;
1562 continue;
1563
1564 case 12: /* program */
1565 mt->program = v;
1566 continue;
1567
1568 case 13: /* content */
1569 mt->content = v;
1570 continue;
1571
1572 case 14: /* outtype */
1573 c = tolower(*v);
1574 if (c != 'h' && c != 't')
1575 cfgLine0(MSG_EBRC_Outtype);
1576 mt->outtype = c;
1577 continue;
1578
1579 case 15: /* urlmatch */
1580 mt->urlmatch = v;
1581 continue;
1582
1583 case 16: /* tname */
1584 td->name = v;
1585 continue;
1586
1587 case 17: /* tshort */
1588 td->shortname = v;
1589 continue;
1590
1591 case 18: /* cols */
1592 while (*v) {
1593 if (td->ncols == MAXTCOLS)
1594 cfgLine1(MSG_EBRC_ManyCols, MAXTCOLS);
1595 td->cols[td->ncols++] = v;
1596 q = strchr(v, ',');
1597 if (!q)
1598 break;
1599 *q = 0;
1600 v = q + 1;
1601 }
1602 continue;
1603
1604 case 19: /* keycol */
1605 if (!isdigitByte(*v))
1606 cfgLine0(MSG_EBRC_KeyNotNb);
1607 td->key1 = (uchar) strtol(v, &v, 10);
1608 if (*v == ',' && isdigitByte(v[1]))
1609 td->key2 = (uchar) strtol(v + 1, &v, 10);
1610 if (td->key1 > td->ncols || td->key2 > td->ncols)
1611 cfgLine1(MSG_EBRC_KeyOutRange, td->ncols);
1612 continue;
1613
1614 case 20: /* downdir */
1615 downDir = v;
1616 if (fileTypeByName(v, false) != 'd')
1617 cfgAbort1(MSG_EBRC_NotDir, v);
1618 continue;
1619
1620 case 21: /* maildir */
1621 mailDir = v;
1622 if (fileTypeByName(v, false) != 'd')
1623 cfgAbort1(MSG_EBRC_NotDir, v);
1624 mailUnread = allocMem(strlen(v) + 20);
1625 sprintf(mailUnread, "%s/unread", v);
1626 /* We need the unread directory, else we can't fetch mail. */
1627 /* Create it if it isn't there. */
1628 if (fileTypeByName(mailUnread, false) != 'd') {
1629 if (mkdir(mailUnread, 0700))
1630 cfgAbort1(MSG_EBRC_NotDir, mailUnread);
1631 }
1632 mailReply = allocMem(strlen(v) + 20);
1633 sprintf(mailReply, "%s/.reply", v);
1634 continue;
1635
1636 case 22: /* agent */
1637 for (j = 0; j < MAXAGENT; ++j)
1638 if (!userAgents[j])
1639 break;
1640 if (j == MAXAGENT)
1641 cfgLine1(MSG_EBRC_ManyAgents, MAXAGENT);
1642 userAgents[j] = v;
1643 continue;
1644
1645 case 23: /* jar */
1646 cookieFile = v;
1647 ftype = fileTypeByName(v, false);
1648 if (ftype && ftype != 'f')
1649 cfgAbort1(MSG_EBRC_JarNotFile, v);
1650 j = open(v, O_WRONLY | O_APPEND | O_CREAT,
1651 MODE_private);
1652 if (j < 0)
1653 cfgAbort1(MSG_EBRC_JarNoWrite, v);
1654 close(j);
1655 continue;
1656
1657 case 24: /* nojs */
1658 if (*v == '.')
1659 ++v;
1660 q = strchr(v, '.');
1661 if (!q || q[1] == 0)
1662 cfgLine1(MSG_EBRC_DomainDot, v);
1663 add_ebhost(v, 'j');
1664 continue;
1665
1666 case 25: /* cachedir */
1667 nzFree(cacheDir);
1668 cacheDir = cloneString(v);
1669 continue;
1670
1671 case 26: /* webtimer */
1672 webTimeout = atoi(v);
1673 continue;
1674
1675 case 27: /* mailtimer */
1676 mailTimeout = atoi(v);
1677 continue;
1678
1679 case 28: /* certfile */
1680 sslCerts = v;
1681 ftype = fileTypeByName(v, false);
1682 if (ftype && ftype != 'f')
1683 cfgAbort1(MSG_EBRC_SSLNoFile, v);
1684 j = open(v, O_RDONLY);
1685 if (j < 0)
1686 cfgAbort1(MSG_EBRC_SSLNoRead, v);
1687 close(j);
1688 continue;
1689
1690 case 29: /* datasource */
1691 setDataSource(v);
1692 continue;
1693
1694 case 30: /* proxy */
1695 add_proxy(v);
1696 continue;
1697
1698 case 31: // agentsite
1699 spaceCrunch(v, true, true);
1700 q = strchr(v, ' ');
1701 if (!q || strchr(q + 1, ' ') ||
1702 (j = stringIsNum(q + 1)) < 0)
1703 break;
1704 if (j >= MAXAGENT || !userAgents[j]) {
1705 cfgLine1(MSG_EBRC_NoAgent, j);
1706 continue;
1707 }
1708 *q = 0;
1709 add_ebhost(v, 'a');
1710 ebhosts[ebhosts_avail - 1].n = j;
1711 continue;
1712
1713 case 32: /* localizeweb */
1714 /* We should probably allow autodetection of language. */
1715 /* E.G., the keyword auto indicates that you want autodetection. */
1716 setHTTPLanguage(v);
1717 continue;
1718
1719 case 34: /* novs */
1720 if (*v == '.')
1721 ++v;
1722 q = strchr(v, '.');
1723 if (!q || q[1] == 0)
1724 cfgLine1(MSG_EBRC_DomainDot, v);
1725 add_ebhost(v, 'v');
1726 continue;
1727
1728 case 35: /* cachesize */
1729 cacheSize = atoi(v);
1730 if (cacheSize <= 0)
1731 cacheSize = 0;
1732 if (cacheSize >= 10000)
1733 cacheSize = 10000;
1734 continue;
1735
1736 case 36: /* adbook */
1737 addressFile = v;
1738 ftype = fileTypeByName(v, false);
1739 if (ftype && ftype != 'f')
1740 cfgAbort1(MSG_EBRC_AbNotFile, v);
1741 continue;
1742
1743 default:
1744 cfgLine1(MSG_EBRC_KeywordNYI, s);
1745 } /* switch */
1746
1747 nokeyword:
1748
1749 if (stringEqual(s, "default") && mailblock == 1) {
1750 if (localAccount == maxAccount + 1)
1751 continue;
1752 if (localAccount)
1753 cfgAbort0(MSG_EBRC_SevDefaults);
1754 localAccount = maxAccount + 1;
1755 continue;
1756 }
1757
1758 if (stringEqual(s, "nofetch") && mailblock == 1) {
1759 act->nofetch = true;
1760 continue;
1761 }
1762
1763 if (stringEqual(s, "secure") && mailblock == 1) {
1764 act->secure = true;
1765 continue;
1766 }
1767
1768 if (stringEqual(s, "imap") && mailblock == 1) {
1769 act->imap = act->nofetch = true;
1770 continue;
1771 }
1772
1773 if (stringEqual(s, "from_file") && mimeblock == 1) {
1774 mt->from_file = true;
1775 continue;
1776 }
1777 if (stringEqual(s, "down_url") && mimeblock == 1) {
1778 mt->down_url = true;
1779 continue;
1780 }
1781
1782 if (*s == '\x82' && s[1] == 0) {
1783 if (mailblock == 1) {
1784 ++maxAccount;
1785 mailblock = 0;
1786 if (!act->inurl)
1787 cfgLine0(MSG_EBRC_NoInserver);
1788 if (!act->outurl)
1789 cfgLine0(MSG_EBRC_NoOutserver);
1790 if (!act->login)
1791 cfgLine0(MSG_EBRC_NoLogin);
1792 if (!act->password)
1793 cfgLine0(MSG_EBRC_NPasswd);
1794 if (!act->from)
1795 cfgLine0(MSG_EBRC_NoFrom);
1796 if (!act->reply)
1797 cfgLine0(MSG_EBRC_NoReply);
1798 if (act->secure)
1799 act->inssl = act->outssl = 1;
1800 if (!act->inport) {
1801 if (act->secure) {
1802 act->inport =
1803 (act->imap ? 993 : 995);
1804 } else {
1805 act->inport =
1806 (act->imap ? 143 : 110);
1807 }
1808 }
1809 if (!act->outport)
1810 act->outport = (act->secure ? 465 : 25);
1811 continue;
1812 }
1813
1814 if (mailblock) {
1815 mailblock = 0;
1816 continue;
1817 }
1818
1819 if (mimeblock) {
1820 ++maxMime;
1821 mimeblock = false;
1822 if (!mt->type)
1823 cfgLine0(MSG_EBRC_NoType);
1824 if (!mt->desc)
1825 cfgLine0(MSG_EBRC_NDesc);
1826 if (!mt->suffix && !mt->prot)
1827 cfgLine0(MSG_EBRC_NoSuffix);
1828 if (!mt->program)
1829 cfgLine0(MSG_EBRC_NoProgram);
1830 continue;
1831 }
1832
1833 if (tabblock) {
1834 ++numTables;
1835 tabblock = false;
1836 if (!td->name)
1837 cfgLine0(MSG_EBRC_NoTblName);
1838 if (!td->shortname)
1839 cfgLine0(MSG_EBRC_NoShortName);
1840 if (!td->ncols)
1841 cfgLine0(MSG_EBRC_NColumns);
1842 continue;
1843 }
1844
1845 if (--nest < 0)
1846 cfgLine0(MSG_EBRC_UnexpBrace);
1847 if (nest)
1848 goto putback;
1849 /* This ends the function */
1850 *s = 0; /* null terminate the script */
1851 continue;
1852 }
1853
1854 if (*s == '\x83' && s[1] == 0) {
1855 /* Does else make sense here? */
1856 c = toupper(stack[nest]);
1857 if (c != 'I')
1858 cfgLine0(MSG_EBRC_UnexElse);
1859 goto putback;
1860 }
1861
1862 if (*s != '\x81') {
1863 if (!nest)
1864 cfgLine0(MSG_EBRC_GarblText);
1865 goto putback;
1866 }
1867
1868 /* Starting something */
1869 c = s[1];
1870 if ((nest || mailblock || mimeblock) && strchr("fmerts", c)) {
1871 const char *curblock = "another function";
1872 if (mailblock)
1873 curblock = "a mail descriptor";
1874 if (mailblock > 1)
1875 curblock = "a filter block";
1876 if (mimeblock)
1877 curblock = "a plugin descriptor";
1878 cfgLine1(MSG_EBRC_FnNotStart, curblock);
1879 }
1880
1881 if (!strchr("fmertsb", c) && !nest)
1882 cfgLine0(MSG_EBRC_StatNotInFn);
1883
1884 if (c == 'm') {
1885 mailblock = 1;
1886 if (maxAccount == MAXACCOUNT)
1887 cfgAbort1(MSG_EBRC_ManyAcc, MAXACCOUNT);
1888 act = accounts + maxAccount;
1889 continue;
1890 }
1891
1892 if (c == 'e') {
1893 mimeblock = true;
1894 if (maxMime == MAXMIME)
1895 cfgAbort1(MSG_EBRC_ManyTypes, MAXMIME);
1896 mt = mimetypes + maxMime;
1897 continue;
1898 }
1899
1900 if (c == 'b') {
1901 tabblock = true;
1902 if (numTables == MAXDBT)
1903 cfgAbort1(MSG_EBRC_ManyTables, MAXDBT);
1904 td = dbtables + numTables;
1905 continue;
1906 }
1907
1908 if (c == 'r') {
1909 mailblock = 2;
1910 continue;
1911 }
1912
1913 if (c == 't') {
1914 mailblock = 3;
1915 continue;
1916 }
1917
1918 if (c == 's') {
1919 mailblock = 4;
1920 continue;
1921 }
1922
1923 if (c == 'f') {
1924 stack[++nest] = c;
1925 sn = ebhosts_avail;
1926 t[-1] = 0;
1927 add_ebhost(t, 'f');
1928 ebhosts[sn].prot = s + 2;
1929 goto putback;
1930 }
1931
1932 if (++nest >= sizeof(stack))
1933 cfgLine0(MSG_EBRC_TooDeeply);
1934 stack[nest] = c;
1935
1936 putback:
1937 *t = '\n';
1938 } /* loop over lines */
1939
1940 if (nest)
1941 cfgAbort1(MSG_EBRC_FnNotClosed, ebhosts[sn].prot + 1);
1942
1943 if (mailblock | mimeblock)
1944 cfgAbort0(MSG_EBRC_MNotClosed);
1945
1946 if (maxAccount && !localAccount)
1947 localAccount = 1;
1948 } /* readConfigFile */
1949
1950 // local replacements for javascript and css
1951 struct JSR {
1952 struct JSR *next;
1953 char *url, *locf;
1954 };
1955 static struct JSR *jsr_top;
1956
fetchReplace(const char * u)1957 const char *fetchReplace(const char *u)
1958 {
1959 struct JSR *j = jsr_top;
1960 const char *s;
1961 int l;
1962 if (!j)
1963 return 0; // high runner case
1964 // This feature only works if you are browsing a local website.
1965 // Save the home page to a file called base, add a <base> tag, and browse that.
1966 if (!browseLocal)
1967 return 0;
1968 s = strchr(u, '?');
1969 l = (s ? s - u : strlen(u));
1970 while (j) {
1971 if (!strncmp(j->url, u, l) && j->url[l] == 0)
1972 return j->locf;
1973 j = j->next;
1974 }
1975 return 0;
1976 }
1977
loadReplacements(void)1978 static void loadReplacements(void)
1979 {
1980 FILE *f = fopen("jslocal", "r");
1981 struct JSR *j;
1982 char *s;
1983 int n = 0;
1984 char line[400];
1985 if (!f)
1986 return;
1987 while (fgets(line, sizeof(line), f)) {
1988 s = strchr(line, '\n');
1989 if (!s) {
1990 fprintf(stderr, "jslocal line too long\n");
1991 fclose(f);
1992 return;
1993 }
1994 *s = 0;
1995 if (s > line && s[-1] == '\r')
1996 *--s = 0;
1997 if (line[0] == '#' || line[0] == 0)
1998 continue;
1999 s = strchr(line, ':');
2000 if (!s) {
2001 fprintf(stderr, "jslocal line has no :\n");
2002 continue;
2003 }
2004 *s++ = 0;
2005 if (strchr(s, '?')) {
2006 fprintf(stderr, "jslocal line has ?\n");
2007 continue;
2008 }
2009 j = allocMem(sizeof(struct JSR));
2010 j->locf = cloneString(line);
2011 j->url = cloneString(s);
2012 j->next = jsr_top;
2013 jsr_top = j;
2014 ++n;
2015 }
2016 fclose(f);
2017 debugPrint(3, "%d js or css file replacements", n);
2018 }
2019