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