1 /* util.c
2  */
3 /* This software is copyrighted as detailed in the LICENSE file. */
4 
5 
6 #include "EXTERN.h"
7 #include "common.h"
8 #include "final.h"
9 #include "term.h"
10 #include "list.h"
11 #include "hash.h"
12 #include "ngdata.h"
13 #include "nntpclient.h"
14 #include "datasrc.h"
15 #include "nntp.h"
16 #include "nntpauth.h"
17 #include "intrp.h"
18 #include "env.h"
19 #include "util2.h"
20 #include "only.h"
21 #include "search.h"
22 #ifdef I_SYS_WAIT
23 #include <sys/wait.h>
24 #endif
25 #ifdef MSDOS
26 #include <process.h>
27 #endif
28 #ifdef SCAN
29 #include "scan.h"
30 #include "smisc.h"	/* s_default_cmd */
31 #endif
32 #include "univ.h"
33 #include "INTERN.h"
34 #include "util.ih"
35 #include "util.h"
36 
37 #ifdef UNION_WAIT
38 typedef union wait WAIT_STATUS;
39 #else
40 typedef int WAIT_STATUS;
41 #endif
42 
43 #ifndef USE_DEBUGGING_MALLOC
44 static char nomem[] = "trn: out of memory!\n";
45 #endif
46 
47 static char null_export[] = "_=X";/* Just in case doshell precedes util_init */
48 
49 static char* newsactive_export = null_export + 2;
50 static char* grpdesc_export = null_export + 2;
51 static char* quotechars_export = null_export + 2;
52 #ifdef SUPPORT_NNTP
53 static char* nntpserver_export = null_export + 2;
54 static char* nntpfds_export = null_export + 2;
55 #ifdef USE_GENAUTH
56 static char* nntpauth_export = null_export + 2;
57 #endif
58 static char* nntpforce_export = null_export + 2;
59 #endif
60 
61 void
util_init()62 util_init()
63 {
64     extern char patchlevel[];
65     char* cp;
66     int i;
67     for (i = 0, cp = buf; i < 512; i++)
68 	*cp++ = 'X';
69     *cp = '\0';
70     newsactive_export = export("NEWSACTIVE", buf);
71     grpdesc_export = export("NEWSDESCRIPTIONS", buf);
72 #ifdef SUPPORT_NNTP
73     nntpserver_export = export("NNTPSERVER", buf);
74 #endif
75     buf[64] = '\0';
76     quotechars_export = export("QUOTECHARS",buf);
77 #ifdef SUPPORT_NNTP
78     nntpfds_export = export("NNTPFDS", buf);
79 #ifdef USE_GENAUTH
80     nntpauth_export = export("NNTP_AUTH_FDS", buf);
81 #endif
82     buf[3] = '\0';
83     nntpforce_export = export("NNTP_FORCE_AUTH", buf);
84 #endif
85 
86     for (cp = patchlevel; isspace(*cp); cp++) ;
87     export("TRN_VERSION", cp);
88 }
89 
90 /* fork and exec a shell command */
91 
92 int
doshell(shell,s)93 doshell(shell,s)
94 char* shell;
95 char* s;
96 {
97 #ifndef MSDOS
98     WAIT_STATUS status;
99     pid_t pid, w;
100 #endif
101     int ret;
102 
103     xmouse_off();
104 
105 #ifdef SIGTSTP
106     sigset(SIGTSTP,SIG_DFL);
107     sigset(SIGTTOU,SIG_DFL);
108     sigset(SIGTTIN,SIG_DFL);
109 #endif
110 #ifdef SUPPORT_NNTP
111     if (datasrc && (datasrc->flags & DF_REMOTE)) {
112 #ifdef USE_GENAUTH
113 	if (export_nntp_fds) {
114 	    if (!nntplink.rd_fp) {
115 		if (nntp_command("DATE") <= 0 || nntp_check() < 0)
116 		    finalize(1); /*$$*/
117 	    }
118 	    sprintf(buf,"%d.%d.%d",(int)fileno(nntplink.rd_fp),
119 		    (int)fileno(nntplink.wr_fp),nntplink.cookiefd);
120 	    re_export(nntpauth_export, buf, 512);
121 	}
122 	else
123 	    un_export(nntpauth_export);
124 #endif
125 	if (!export_nntp_fds || !nntplink.rd_fp)
126 	    un_export(nntpfds_export);
127 	else {
128 	    sprintf(buf,"%d.%d",(int)fileno(nntplink.rd_fp),
129 				(int)fileno(nntplink.wr_fp));
130 	    re_export(nntpfds_export, buf, 64);
131 	}
132 	re_export(nntpserver_export,datasrc->newsid,512);
133 	if (datasrc->nntplink.flags & NNTP_FORCE_AUTH_NEEDED)
134 	    re_export(nntpforce_export,"yes",3);
135 	else
136 	    un_export(nntpforce_export);
137 	if (datasrc->auth_user) {
138 	    int fd;
139 	    if ((fd = open(nntp_auth_file, O_WRONLY|O_CREAT, 0600)) >= 0) {
140 		write(fd, datasrc->auth_user, strlen(datasrc->auth_user));
141 		write(fd, "\n", 1);
142 		if (datasrc->auth_pass) {
143 		    write(fd, datasrc->auth_pass, strlen(datasrc->auth_pass));
144 		    write(fd, "\n", 1);
145 		}
146 		close(fd);
147 	    }
148 	}
149 	if (nntplink.port_number) {
150 	    int len = strlen(nntpserver_export);
151 	    sprintf(buf,";%d",nntplink.port_number);
152 	    if (len + (int)strlen(buf) < 511)
153 		strcpy(nntpserver_export+len, buf);
154 	}
155 	if (datasrc->act_sf.fp)
156 	    re_export(newsactive_export, datasrc->extra_name, 512);
157 	else
158 	    re_export(newsactive_export, "none", 512);
159     } else {
160 #ifdef SUPPORT_NNTP
161 	un_export(nntpfds_export);
162 #ifdef USE_GENAUTH
163 	un_export(nntpauth_export);
164 #endif
165 	un_export(nntpserver_export);
166 	un_export(nntpforce_export);
167 #endif
168 	if (datasrc)
169 	    re_export(newsactive_export, datasrc->newsid, 512);
170 	else
171 	    un_export(newsactive_export);
172     }
173 #else
174     if (datasrc)
175 	re_export(newsactive_export, datasrc->newsid, 512);
176     else
177 	un_export(newsactive_export);
178 #endif
179     if (datasrc)
180 	re_export(grpdesc_export, datasrc->grpdesc, 512);
181     else
182 	un_export(grpdesc_export);
183     interp(buf,64-1+2,"%I");
184     buf[strlen(buf)-1] = '\0';
185     re_export(quotechars_export, buf+1, 64);
186     if (shell == NULL && (shell = getval("SHELL",NULL)) == NULL)
187 	shell = PREFSHELL;
188     termlib_reset();
189 #ifdef MSDOS
190     status = spawnl(P_WAIT, shell, shell, "/c", s, (char*)NULL);
191 #else
192     if ((pid = vfork()) == 0) {
193 #ifdef SUPPORT_NNTP
194 	if (datasrc && (datasrc->flags & DF_REMOTE)) {
195 	    int i;
196 	    /* This is necessary to keep the bourne shell from puking */
197 	    for (i = 3; i < 10; ++i) {
198 		if (nntplink.rd_fp
199 		 && (i == fileno(nntplink.rd_fp)
200 		  || i == fileno(nntplink.wr_fp)))
201 		    continue;
202 #ifdef USE_GENAUTH
203 		if (i == nntplink.cookiefd)
204 		    continue;
205 #endif
206 		close(i);
207 	    }
208 	}
209 #endif /* SUPPORT_NNTP */
210 	if (nowait_fork) {
211 	    close(1);
212 	    close(2);
213 	    dup(open("/dev/null",1));
214 	}
215 
216 	if (*s)
217 	    execl(shell, shell, "-c", s, (char*)NULL);
218 	else
219 	    execl(shell, shell, (char*)NULL, (char*)NULL, (char*)NULL);
220 	_exit(127);
221     }
222     sigignore(SIGINT);
223 #ifdef SIGQUIT
224     sigignore(SIGQUIT);
225 #endif
226     waiting = TRUE;
227     while ((w = wait(&status)) != pid)
228 	if (w == -1 && errno != EINTR)
229 	    break;
230     if (w == -1)
231 	ret = -1;
232     else
233 #ifdef USE_WIFSTAT
234 	ret = WEXITSTATUS(status);
235 #else
236 #ifdef UNION_WAIT
237 	ret = status.w_status >> 8;
238 #else
239 	ret = status;
240 #endif /* UNION_WAIT */
241 #endif /* USE_WIFSTAT */
242 #endif /* !MSDOS */
243     termlib_init();
244     xmouse_check();
245     waiting = FALSE;
246     sigset(SIGINT,int_catcher);
247 #ifdef SIGQUIT
248     sigset(SIGQUIT,SIG_DFL);
249 #endif
250 #ifdef SIGTSTP
251     sigset(SIGTSTP,stop_catcher);
252     sigset(SIGTTOU,stop_catcher);
253     sigset(SIGTTIN,stop_catcher);
254 #endif
255 #ifdef SUPPORT_NNTP
256     if (datasrc && datasrc->auth_user)
257 	UNLINK(nntp_auth_file);
258 #endif
259     return ret;
260 }
261 
262 /* paranoid version of malloc */
263 
264 #ifndef USE_DEBUGGING_MALLOC
265 char*
safemalloc(size)266 safemalloc(size)
267 MEM_SIZE size;
268 {
269     char* ptr;
270 
271     ptr = malloc(size ? size : (MEM_SIZE)1);
272     if (!ptr) {
273 	fputs(nomem,stdout) FLUSH;
274 	sig_catcher(0);
275     }
276     return ptr;
277 }
278 #endif
279 
280 /* paranoid version of realloc.  If where is NULL, call malloc */
281 
282 #ifndef USE_DEBUGGING_MALLOC
283 char*
saferealloc(where,size)284 saferealloc(where,size)
285 char* where;
286 MEM_SIZE size;
287 {
288     char* ptr;
289 
290     if (!where)
291 	ptr = malloc(size ? size : (MEM_SIZE)1);
292     else
293 	ptr = realloc(where, size ? size : (MEM_SIZE)1);
294     if (!ptr) {
295 	fputs(nomem,stdout) FLUSH;
296 	sig_catcher(0);
297     }
298     return ptr;
299 }
300 #endif /* !USE_DEBUGGING_MALLOC */
301 
302 /* safe version of string concatenate, with \n deletion and space padding */
303 
304 char*
safecat(to,from,len)305 safecat(to,from,len)
306 char* to;
307 register char* from;
308 register int len;
309 {
310     register char* dest = to;
311 
312     len--;				/* leave room for null */
313     if (*dest) {
314 	while (len && *dest++) len--;
315 	if (len) {
316 	    len--;
317 	    *(dest-1) = ' ';
318 	}
319     }
320     if (from)
321 	while (len && (*dest++ = *from++)) len--;
322     if (len)
323 	dest--;
324     if (*(dest-1) == '\n')
325 	dest--;
326     *dest = '\0';
327     return to;
328 }
329 
330 /* effective access */
331 
332 #ifdef SETUIDGID
333 int
eaccess(filename,mod)334 eaccess(filename, mod)
335 char* filename;
336 int mod;
337 {
338     int protection, euid;
339 
340     mod &= 7;				/* remove extraneous garbage */
341     if (stat(filename, &filestat) < 0)
342 	return -1;
343     euid = geteuid();
344     if (euid == ROOTID)
345 	return 0;
346     protection = 7 & ( filestat.st_mode >> (filestat.st_uid == euid ?
347 			6 : (filestat.st_gid == getegid() ? 3 : 0)) );
348     if ((mod & protection) == mod)
349 	return 0;
350     errno = EACCES;
351     return -1;
352 }
353 #endif
354 
355 /*
356  * Get working directory
357  */
358 char*
trn_getwd(buf,buflen)359 trn_getwd(buf, buflen)
360 char* buf;
361 int buflen;
362 {
363     char* ret;
364 
365 #ifdef HAS_GETCWD
366     ret = getcwd(buf, buflen);
367 #else
368     ret = trn_getcwd(buf, buflen);
369 #endif
370     if (!ret) {
371 	printf("Cannot determine current working directory!\n") FLUSH;
372 	finalize(1);
373     }
374 #ifdef MSDOS
375     strlwr(buf);
376     while ((buf = index(buf,'\\')) != NULL)
377 	*buf++ = '/';
378 #endif
379     return ret;
380 }
381 
382 #ifndef HAS_GETCWD
383 static char*
trn_getcwd(buf,len)384 trn_getcwd(buf, len)
385 char* buf;
386 int len;
387 {
388     char* ret;
389 #ifdef HAS_GETWD
390     buf[len-1] = 0;
391     ret = getwd(buf);
392     if (buf[len-1]) {
393 	/* getwd() overwrote the end of the buffer */
394 	printf("getwd() buffer overrun!\n") FLUSH;
395 	finalize(1);
396     }
397 #else
398     FILE* popen();
399     FILE* pipefp;
400     char* nl;
401 
402     if ((pipefp = popen("/bin/pwd","r")) == NULL) {
403 	printf("Can't popen /bin/pwd\n") FLUSH;
404 	return NULL;
405     }
406     buf[0] = 0;
407     fgets(ret = buf, len, pipefp);
408     if (pclose(pipefp) == EOF) {
409 	printf("Failed to run /bin/pwd\n") FLUSH;
410 	return NULL;
411     }
412     if (!buf[0]) {
413 	printf("/bin/pwd didn't output anything\n") FLUSH;
414     	return NULL;
415     }
416     if ((nl = index(buf, '\n')) != NULL)
417 	*nl = '\0';
418 #endif
419     return ret;
420 }
421 #endif
422 
423 /* just like fgets but will make bigger buffer as necessary */
424 
425 char*
get_a_line(buffer,buffer_length,realloc_ok,fp)426 get_a_line(buffer,buffer_length,realloc_ok,fp)
427 char* buffer;
428 register int buffer_length;
429 bool_int realloc_ok;
430 FILE* fp;
431 {
432     register int bufix = 0;
433     register int nextch;
434 
435     do {
436 	if (bufix >= buffer_length) {
437 	    buffer_length *= 2;
438 	    if (realloc_ok) {		/* just grow in place, if possible */
439 		buffer = saferealloc(buffer,(MEM_SIZE)buffer_length+1);
440 	    }
441 	    else {
442 		char* tmp = safemalloc((MEM_SIZE)buffer_length+1);
443 		strncpy(tmp,buffer,buffer_length/2);
444 		buffer = tmp;
445 		realloc_ok = TRUE;
446 	    }
447 	}
448 	if ((nextch = getc(fp)) == EOF) {
449 	    if (!bufix)
450 		return NULL;
451 	    break;
452 	}
453 	buffer[bufix++] = (char)nextch;
454     } while (nextch && nextch != '\n');
455     buffer[bufix] = '\0';
456     len_last_line_got = bufix;
457     buflen_last_line_got = buffer_length;
458     return buffer;
459 }
460 
461 int
makedir(dirname,nametype)462 makedir(dirname,nametype)
463 register char* dirname;
464 int nametype;
465 {
466 #ifdef MAKEDIR
467     register char* end;
468     register char* s;
469 # ifdef HAS_MKDIR
470     int status = 0;
471 # else
472     char tmpbuf[1024];
473     register char* tbptr = tmpbuf+5;
474 # endif
475 
476     for (end = dirname; *end; end++) ;	/* find the end */
477     if (nametype == MD_FILE) {		/* not to create last component? */
478 	for (--end; end != dirname && *end != '/'; --end) ;
479 	if (*end != '/')
480 	    return 0;			/* nothing to make */
481 	*end = '\0';			/* isolate file name */
482     }
483 # ifndef HAS_MKDIR
484     strcpy(tmpbuf,"mkdir");
485 # endif
486 
487     s = end;
488     for (;;) {
489 	if (stat(dirname,&filestat) >= 0 && S_ISDIR(filestat.st_mode)) {
490 					/* does this much exist as a dir? */
491 	    *s = '/';			/* mark this as existing */
492 	    break;
493 	}
494 	s = rindex(dirname,'/');	/* shorten name */
495 	if (!s)				/* relative path! */
496 	    break;			/* hope they know what they are doing */
497 	*s = '\0';			/* mark as not existing */
498     }
499 
500     for (s=dirname; s <= end; s++) {	/* this is grody but efficient */
501 	if (!*s) {			/* something to make? */
502 # ifdef HAS_MKDIR
503 	    status = status || mkdir(dirname,0777);
504 # else
505 	    sprintf(tbptr," %s",dirname);
506 	    tbptr += strlen(tbptr);	/* make it, sort of */
507 # endif
508 	    *s = '/';			/* mark it made */
509 	}
510     }
511     if (nametype == MD_DIR)		/* don't need final slash unless */
512 	*end = '\0';			/*  a filename follows the dir name */
513 
514 # ifdef HAS_MKDIR
515     return status;
516 # else
517     return (tbptr==tmpbuf+5 ? 0 : doshell(sh,tmpbuf));/* exercise our faith */
518 # endif
519 #else
520     sprintf(cmd_buf,"%s %s %d", filexp(DIRMAKER), dirname, nametype);
521     return doshell(sh,cmd_buf);
522 #endif
523 }
524 
525 void
notincl(feature)526 notincl(feature)
527 char* feature;
528 {
529     printf("\nNo room for feature \"%s\" on this machine.\n",feature) FLUSH;
530 }
531 
532 /* grow a static string to at least a certain length */
533 
534 void
growstr(strptr,curlen,newlen)535 growstr(strptr,curlen,newlen)
536 char** strptr;
537 int* curlen;
538 int newlen;
539 {
540     if (newlen > *curlen) {		/* need more room? */
541 	if (*curlen)
542 	    *strptr = saferealloc(*strptr,(MEM_SIZE)newlen);
543 	else
544 	    *strptr = safemalloc((MEM_SIZE)newlen);
545 	*curlen = newlen;
546     }
547 }
548 
549 void
setdef(buffer,dflt)550 setdef(buffer,dflt)
551 char* buffer;
552 char* dflt;
553 {
554 #ifdef SCAN
555     s_default_cmd = FALSE;
556 #endif
557     univ_default_cmd = FALSE;
558     if (*buffer == ' '
559 #ifndef STRICTCR
560      || *buffer == '\n' || *buffer == '\r'
561 #endif
562     ) {
563 #ifdef SCAN
564 	s_default_cmd = TRUE;
565 #endif
566 	univ_default_cmd = TRUE;
567 	if (*dflt == '^' && isupper(dflt[1]))
568 	    pushchar(Ctl(dflt[1]));
569 	else
570 	    pushchar(*dflt);
571 	getcmd(buffer);
572     }
573 }
574 
575 #ifndef NO_FILELINKS
576 void
safelink(old,new)577 safelink(old, new)
578 char* old;
579 char* new;
580 {
581 #if 0
582     extern int sys_nerr;
583     extern char* sys_errlist[];
584 #endif
585 
586     if (link(old,new)) {
587 	printf("Can't link backup (%s) to .newsrc (%s)\n", old, new) FLUSH;
588 #if 0
589 	if (errno>0 && errno<sys_nerr)
590 	    printf("%s\n", sys_errlist[errno]);
591 #endif
592 	finalize(1);
593     }
594 }
595 #endif
596 
597 #ifndef HAS_STRSTR
598 char*
trn_strstr(s1,s2)599 trn_strstr(s1, s2)
600 char* s1;
601 char* s2;
602 {
603     register char* p = s1;
604     register int len = strlen(s2);
605 
606     for ( ; (p = index(p, *s2)) != NULL; p++)
607 	if (strnEQ(p, s2, len))
608 	    return p;
609     return NULL;
610 }
611 #endif /* !HAS_STRSTR */
612 
613 /* attempts to verify a cryptographic signature. */
614 void
verify_sig()615 verify_sig()
616 {
617     int i;
618 
619     printf("\n");
620     /* RIPEM */
621     i = doshell(sh,filexp("grep -s \"BEGIN PRIVACY-ENHANCED MESSAGE\" %A"));
622     if (!i) {	/* found RIPEM */
623 	i = doshell(sh,filexp(getval("VERIFY_RIPEM",VERIFY_RIPEM)));
624 	printf("\nReturned value: %d\n",i) FLUSH;
625 	return;
626     }
627     /* PGP */
628     i = doshell(sh,filexp("grep -s \"BEGIN PGP\" %A"));
629     if (!i) {	/* found PGP */
630 	i = doshell(sh,filexp(getval("VERIFY_PGP",VERIFY_PGP)));
631 	printf("\nReturned value: %d\n",i) FLUSH;
632 	return;
633     }
634     printf("No PGP/RIPEM signatures detected.\n") FLUSH;
635 }
636 
637 double
current_time()638 current_time()
639 {
640 #ifdef HAS_GETTIMEOFDAY
641     Timeval t;
642     (void) gettimeofday(&t, (struct timezone*)NULL);
643     return (double)t.tv_usec / 1000000. + t.tv_sec;
644 #else
645 # ifdef HAS_FTIME
646     Timeval t;
647     ftime(&t);
648     return (double)t.millitm / 1000. + t.time;
649 # else
650     return (double)time((time_t*)NULL);
651 # endif
652 #endif
653 }
654 
655 time_t
text2secs(s,defSecs)656 text2secs(s, defSecs)
657 char* s;
658 time_t defSecs;
659 {
660     time_t secs = 0;
661     time_t item;
662 
663     if (!isdigit(*s)) {
664 	if (*s == 'm' || *s == 'M')	/* "missing" */
665 	    return 2;
666 	if (*s == 'y' || *s == 'Y')	/* "yes" */
667 	    return defSecs;
668 	return secs;			/* "never" */
669     }
670     do {
671 	item = atol(s);
672 	while (isdigit(*s)) s++;
673 	while (isspace(*s)) s++;
674 	if (isalpha(*s)) {
675 	    switch (*s) {
676 	      case 'd': case 'D':
677 		item *= 24 * 60L;
678 		break;
679 	      case 'h': case 'H':
680 		item *= 60L;
681 		break;
682 	      case 'm': case 'M':
683 		break;
684 	      default:
685 		item = 0;
686 		break;
687 	    }
688 	    while (isalpha(*s)) s++;
689 	    if (*s == ',') s++;
690 	    while (isspace(*s)) s++;
691 	}
692 	secs += item;
693     } while (isdigit(*s));
694 
695     return secs * 60;
696 }
697 
698 char*
secs2text(secs)699 secs2text(secs)
700 time_t secs;
701 {
702     char* s = buf;
703     int items;
704 
705     if (!secs || (secs & 1))
706 	return "never";
707     if (secs & 2)
708 	return "missing";
709 
710     secs /= 60;
711     if (secs >= 24L * 60) {
712 	items = (int)(secs / (24*60));
713 	secs = secs % (24*60);
714 	sprintf(s, "%d day%s, ", items, PLURAL(items));
715 	s += strlen(s);
716     }
717     if (secs >= 60L) {
718 	items = (int)(secs / 60);
719 	secs = secs % 60;
720 	sprintf(s, "%d hour%s, ", items, PLURAL(items));
721 	s += strlen(s);
722     }
723     if (secs) {
724 	sprintf(s, "%d minute%s, ", (int)secs, PLURAL(items));
725 	s += strlen(s);
726     }
727     s[-2] = '\0';
728     return buf;
729 }
730 
731 /* returns a saved string representing a unique temporary filename */
732 char*
temp_filename()733 temp_filename()
734 {
735     static int tmpfile_num = 0;
736     char tmpbuf[CBUFLEN];
737     extern long our_pid;
738     sprintf(tmpbuf,"%s/trn%d.%ld",tmpdir,tmpfile_num++,our_pid);
739     return savestr(tmpbuf);
740 }
741 
742 #ifdef SUPPORT_NNTP
743 char*
get_auth_user()744 get_auth_user()
745 {
746     return datasrc->auth_user;
747 }
748 #endif
749 
750 #ifdef SUPPORT_NNTP
751 char*
get_auth_pass()752 get_auth_pass()
753 {
754     return datasrc->auth_pass;
755 }
756 #endif
757 
758 #if defined(USE_GENAUTH) && defined(SUPPORT_NNTP)
759 char*
get_auth_command()760 get_auth_command()
761 {
762     return datasrc->auth_command;
763 }
764 #endif
765 
766 char**
prep_ini_words(words)767 prep_ini_words(words)
768 INI_WORDS words[];
769 {
770     register int checksum;
771     char* cp = (char*)INI_VALUES(words);
772     if (!cp) {
773 	int i;
774 	for (i = 1; words[i].item != NULL; i++) {
775 	    if (*words[i].item == '*') {
776 		words[i].checksum = -1;
777 		continue;
778 	    }
779 	    checksum = 0;
780 	    for (cp = words[i].item; *cp; cp++)
781 		checksum += (isupper(*cp)? tolower(*cp) : *cp);
782 	    words[i].checksum = (checksum << 8) + (cp - words[i].item);
783 	}
784 	words[0].checksum = i;
785 	words[0].help_str = cp = safemalloc(i * sizeof (char*));
786     }
787     bzero(cp, INI_LEN(words) * sizeof (char*));
788     return (char**)cp;
789 }
790 
791 void
unprep_ini_words(words)792 unprep_ini_words(words)
793 INI_WORDS words[];
794 {
795     free((char*)INI_VALUES(words));
796     words[0].checksum = 0;
797     words[0].help_str = NULL;
798 }
799 
800 void
prep_ini_data(cp,filename)801 prep_ini_data(cp,filename)
802 char* cp;
803 char* filename;
804 {
805     char* t = cp;
806 
807 #ifdef DEBUG
808     if (debug & DEB_RCFILES)
809 	printf("Read %d bytes from %s\n",strlen(cp),filename);
810 #endif
811 
812     while (*cp) {
813 	while (isspace(*cp)) cp++;
814 
815 	if (*cp == '[') {
816 	    char* s = t;
817 	    do {
818 		*t++ = *cp++;
819 	    } while (*cp && *cp != ']' && *cp != '\n');
820 	    if (*cp == ']' && t != s) {
821 		*t++ = '\0';
822 		cp++;
823 		if (parse_string(&t, &cp))
824 		    cp++;
825 
826 		while (*cp) {
827 		    while (isspace(*cp)) cp++;
828 		    if (*cp == '[')
829 			break;
830 		    if (*cp == '#')
831 			s = cp;
832 		    else {
833 			s = t;
834 			while (*cp && *cp != '\n') {
835 			    if (*cp == '=')
836 				break;
837 			    if (isspace(*cp)) {
838 				if (s == t || t[-1] != ' ')
839 				    *t++ = ' ';
840 				cp++;
841 			    }
842 			    else
843 				*t++ = *cp++;
844 			}
845 			if (*cp == '=' && t != s) {
846 			    while (t != s && isspace(t[-1])) t--;
847 			    *t++ = '\0';
848 			    cp++;
849 			    if (parse_string(&t, &cp))
850 				s = NULL;
851 			    else
852 				s = cp;
853 			}
854 			else
855 			    s = cp;
856 		    }
857 		    cp++;
858 		    if (s)
859 			for (cp = s; *cp && *cp++ != '\n'; ) ;
860 		}
861 	    }
862 	    else {
863 		*t = '\0';
864 		printf("Invalid section in %s: %s\n", filename, s);
865 		t = s;
866 		while (*cp && *cp++ != '\n') ;
867 	    }
868 	}
869 	else
870 	    while (*cp && *cp++ != '\n') ;
871     }
872     *t = '\0';
873 }
874 
875 bool
parse_string(to,from)876 parse_string(to, from)
877 char** to;
878 char** from;
879 {
880     char inquote = 0;
881     char* t = *to;
882     char* f = *from;
883     char* s;
884 
885     while (isspace(*f) && *f != '\n') f++;
886 
887     for (s = t; *f; f++) {
888 	if (inquote) {
889 	    if (*f == inquote) {
890 		inquote = 0;
891 		s = t;
892 		continue;
893 	    }
894 	}
895 	else if (*f == '\n')
896 	    break;
897 	else if (*f == '\'' || *f == '"') {
898 	    inquote = *f;
899 	    continue;
900 	}
901 	else if (*f == '#') {
902 	    while (*++f && *f != '\n') ;
903 	    break;
904 	}
905 	if (*f == '\\') {
906 	    if (*++f == '\n')
907 		continue;
908 	    f = interp_backslash(t, f);
909 	    t++;
910 	}
911 	else
912 	    *t++ = *f;
913     }
914 #if 0
915     if (inquote)
916 	printf("Unbalanced quotes.\n");
917 #endif
918     inquote = (*f != '\0');
919 
920     while (t != s && isspace(t[-1])) t--;
921     *t++ = '\0';
922 
923     *to = t;
924     *from = f;
925 
926     return inquote;	/* return TRUE if the string ended with a newline */
927 }
928 
929 char*
next_ini_section(cp,section,cond)930 next_ini_section(cp,section,cond)
931 char* cp;
932 char** section;
933 char** cond;
934 {
935     while (*cp != '[') {
936 	if (!*cp)
937 	    return NULL;
938 	cp += strlen(cp) + 1;
939 	cp += strlen(cp) + 1;
940     }
941     *section = cp+1;
942     cp += strlen(cp) + 1;
943     *cond = cp;
944     cp += strlen(cp) + 1;
945 #ifdef DEBUG
946     if (debug & DEB_RCFILES)
947 	printf("Section [%s] (condition: %s)\n",*section,
948 	       **cond? *cond : "<none>");
949 #endif
950     return cp;
951 }
952 
953 char*
parse_ini_section(cp,words)954 parse_ini_section(cp, words)
955 char* cp;
956 INI_WORDS words[];
957 {
958     register int checksum;
959     register char* s;
960     char** values = prep_ini_words(words);
961     int i;
962 
963     if (!*cp)
964 	return NULL;
965 
966     while (*cp && *cp != '[') {
967 	checksum = 0;
968 	for (s = cp; *s; s++) {
969 	    if (isupper(*s))
970 		*s = tolower(*s);
971 	    checksum += *s;
972 	}
973 	checksum = (checksum << 8) + (s++ - cp);
974 	if (*s) {
975 	    for (i = 1; words[i].checksum; i++) {
976 		if (words[i].checksum == checksum
977 		 && strcaseEQ(cp,words[i].item)) {
978 		    values[i] = s;
979 		    break;
980 		}
981 	    }
982 	    if (!words[i].checksum)
983 		printf("Unknown option: `%s'.\n",cp);
984 	    cp = s + strlen(s) + 1;
985 	}
986 	else
987 	    cp = s + 1;
988     }
989 
990 #ifdef DEBUG
991     if (debug & DEB_RCFILES) {
992 	printf("Ini_words: %s\n", words[0].item);
993 	for (i = 1; words[i].checksum; i++)
994 	    if (values[i])
995 		printf("%s=%s\n",words[i].item,values[i]);
996     }
997 #endif
998 
999     return cp;
1000 }
1001 
1002 bool
check_ini_cond(cond)1003 check_ini_cond(cond)
1004 char* cond;
1005 {
1006     int not, equal, upordown, num;
1007     char* s;
1008     cond = dointerp(buf,sizeof buf,cond,"!=<>",(char*)NULL);
1009     s = buf + strlen(buf);
1010     while (s != buf && isspace(s[-1])) s--;
1011     *s = '\0';
1012     if ((not = (*cond == '!')) != 0)
1013 	cond++;
1014     if ((upordown = (*cond=='<'? -1: (*cond=='>'? 1:0))) != 0)
1015 	cond++;
1016     if ((equal = (*cond == '=')) != 0)
1017 	cond++;
1018     while (isspace(*cond)) cond++;
1019     if (upordown) {
1020 	num = atoi(cond) - atoi(buf);
1021 	if (!((equal && !num) || (upordown * num < 0)) ^ not)
1022 	    return FALSE;
1023     }
1024     else if (equal) {
1025 	COMPEX condcompex;
1026 	init_compex(&condcompex);
1027 	if ((s = compile(&condcompex,cond,TRUE,TRUE)) != NULL) {
1028 	    /*warning(s)*/;
1029 	    equal = FALSE;
1030 	}
1031 	else
1032 	    equal = execute(&condcompex,buf) != NULL;
1033 	free_compex(&condcompex);
1034 	return equal;
1035     }
1036     else
1037 	return FALSE;
1038     return TRUE;
1039 }
1040 
1041 /* $$ might get replaced soonish... */
1042 /* Ask for a single character (improve the prompt?) */
1043 char
menu_get_char()1044 menu_get_char()
1045 {
1046     printf("Enter your choice: ");
1047     fflush(stdout);
1048     eat_typeahead();
1049     getcmd(buf);
1050     printf("%c\n",*buf) FLUSH;
1051     return(*buf);
1052 }
1053 
1054 /* NOTE: kfile.c uses its own editor function */
1055 /* used in a few places, now centralized */
1056 int
edit_file(fname)1057 edit_file(fname)
1058 char* fname;
1059 {
1060     int r = -1;
1061 
1062     if (!fname || !*fname)
1063 	return r;
1064 
1065     /* XXX paranoia check on length */
1066     sprintf(cmd_buf,"%s ",
1067 	    filexp(getval("VISUAL",getval("EDITOR",defeditor))));
1068     strcat(cmd_buf, filexp(fname));
1069     termdown(3);
1070     resetty();			/* make sure tty is friendly */
1071     r = doshell(sh,cmd_buf);/* invoke the shell */
1072     noecho();			/* and make terminal */
1073     crmode();			/*   unfriendly again */
1074     return r;
1075 }
1076 
1077 /* Consider a trn_pushdir, trn_popdir pair of functions */
1078