1 /***************************************************************/
2 /*                                                             */
3 /*  FILES.C                                                    */
4 /*                                                             */
5 /*  Controls the opening and closing of files, etc.  Also      */
6 /*  handles caching of lines and reading of lines from         */
7 /*  files.                                                     */
8 /*                                                             */
9 /*  This file is part of REMIND.                               */
10 /*  Copyright (C) 1992-2021 by Dianne Skoll                    */
11 /*                                                             */
12 /***************************************************************/
13 
14 #include "config.h"
15 
16 #include <stdio.h>
17 
18 #include <string.h>
19 #include <errno.h>
20 #include <ctype.h>
21 #include <sys/stat.h>
22 
23 #ifdef TM_IN_SYS_TIME
24 #include <sys/time.h>
25 #else
26 #include <time.h>
27 #endif
28 
29 #include <sys/types.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 
33 #ifdef HAVE_GLOB_H
34 #include <glob.h>
35 #endif
36 
37 #include "types.h"
38 #include "protos.h"
39 #include "globals.h"
40 #include "err.h"
41 
42 
43 /* Convenient macros for closing files */
44 #define FCLOSE(fp) (((fp)&&((fp)!=stdin)) ? (fclose(fp),(fp)=NULL) : ((fp)=NULL))
45 #define PCLOSE(fp) (((fp)&&((fp)!=stdin)) ? (pclose(fp),(fp)=NULL) : ((fp)=NULL))
46 
47 /* Define the structures needed by the file caching system */
48 typedef struct cache {
49     struct cache *next;
50     char const *text;
51     int LineNo;
52 } CachedLine;
53 
54 typedef struct cheader {
55     struct cheader *next;
56     char const *filename;
57     CachedLine *cache;
58     int ownedByMe;
59 } CachedFile;
60 
61 /* A linked list of filenames if we INCLUDE /some/directory/  */
62 typedef struct fname_chain {
63     struct fname_chain *next;
64     char const *filename;
65 } FilenameChain;
66 
67 /* Cache filename chains for directories */
68 typedef struct directory_fname_chain {
69     struct directory_fname_chain *next;
70     FilenameChain *chain;
71     char const *dirname;
72 } DirectoryFilenameChain;
73 
74 /* Define the structures needed by the INCLUDE file system */
75 typedef struct {
76     char const *filename;
77     FilenameChain *chain;
78     int LineNo;
79     unsigned int IfFlags;
80     int NumIfs;
81     long offset;
82     CachedLine *CLine;
83     int ownedByMe;
84 } IncludeStruct;
85 
86 static CachedFile *CachedFiles = (CachedFile *) NULL;
87 static CachedLine *CLine = (CachedLine *) NULL;
88 static DirectoryFilenameChain *CachedDirectoryChains = NULL;
89 
90 static FILE *fp;
91 
92 static IncludeStruct IStack[INCLUDE_NEST];
93 static int IStackPtr = 0;
94 
95 static int ReadLineFromFile (int use_pclose);
96 static int CacheFile (char const *fname, int use_pclose);
97 static void DestroyCache (CachedFile *cf);
98 static int CheckSafety (void);
99 static int PopFile (void);
100 static int IncludeCmd(char const *);
OpenPurgeFile(char const * fname,char const * mode)101 static void OpenPurgeFile(char const *fname, char const *mode)
102 {
103     DynamicBuffer fname_buf;
104 
105     if (PurgeFP != NULL && PurgeFP != stdout) {
106 	fclose(PurgeFP);
107     }
108     PurgeFP = NULL;
109 
110     /* Do not open a purge file if we're below purge
111        include depth */
112     if (IStackPtr-2 >= PurgeIncludeDepth) {
113 	PurgeFP = NULL;
114 	return;
115     }
116 
117     DBufInit(&fname_buf);
118     if (DBufPuts(&fname_buf, fname) != OK) return;
119     if (DBufPuts(&fname_buf, ".purged") != OK) return;
120     PurgeFP = fopen(DBufValue(&fname_buf), mode);
121     if (!PurgeFP) {
122 	fprintf(ErrFp, "Cannot open `%s' for writing: %s\n", DBufValue(&fname_buf), strerror(errno));
123     }
124     DBufFree(&fname_buf);
125 }
126 
FreeChainItem(FilenameChain * chain)127 static void FreeChainItem(FilenameChain *chain)
128 {
129 	if (chain->filename) free((void *) chain->filename);
130 	free(chain);
131 }
132 
FreeChain(FilenameChain * chain)133 static void FreeChain(FilenameChain *chain)
134 {
135     FilenameChain *next;
136     while(chain) {
137 	next = chain->next;
138 	FreeChainItem(chain);
139 	chain = next;
140     }
141 }
142 
143 /***************************************************************/
144 /*                                                             */
145 /*  ReadLine                                                   */
146 /*                                                             */
147 /*  Read a line from the file or cache.                        */
148 /*                                                             */
149 /***************************************************************/
ReadLine(void)150 int ReadLine(void)
151 {
152     int r;
153 
154 /* If we're at the end of a file, pop */
155     while (!CLine && !fp) {
156 	r = PopFile();
157 	if (r) return r;
158     }
159 
160 /* If it's cached, read line from the cache */
161     if (CLine) {
162 	CurLine = CLine->text;
163 	LineNo = CLine->LineNo;
164 	CLine = CLine->next;
165 	FreshLine = 1;
166 	if (DebugFlag & DB_ECHO_LINE) OutputLine(ErrFp);
167 	return OK;
168     }
169 
170 /* Not cached.  Read from the file. */
171     return ReadLineFromFile(0);
172 }
173 
174 /***************************************************************/
175 /*                                                             */
176 /*  ReadLineFromFile                                           */
177 /*                                                             */
178 /*  Read a line from the file pointed to by fp.                */
179 /*                                                             */
180 /***************************************************************/
ReadLineFromFile(int use_pclose)181 static int ReadLineFromFile(int use_pclose)
182 {
183     int l;
184     char copy_buffer[4096];
185     size_t n;
186 
187     DynamicBuffer buf;
188 
189     DBufInit(&buf);
190     DBufFree(&LineBuffer);
191 
192     while(fp) {
193 	if (DBufGets(&buf, fp) != OK) {
194 	    DBufFree(&LineBuffer);
195 	    return E_NO_MEM;
196 	}
197 	LineNo++;
198 	if (ferror(fp)) {
199 	    DBufFree(&buf);
200 	    DBufFree(&LineBuffer);
201 	    return E_IO_ERR;
202 	}
203 	if (feof(fp)) {
204             if (use_pclose) {
205                 PCLOSE(fp);
206             } else {
207                 FCLOSE(fp);
208             }
209 	    if ((DBufLen(&buf) == 0) &&
210 		(DBufLen(&LineBuffer) == 0) && PurgeMode) {
211 		if (PurgeFP != NULL && PurgeFP != stdout) fclose(PurgeFP);
212 		PurgeFP = NULL;
213 	    }
214 	}
215 	l = DBufLen(&buf);
216 	if (l && (DBufValue(&buf)[l-1] == '\\')) {
217 	    if (PurgeMode) {
218 		if (DBufPuts(&LineBuffer, DBufValue(&buf)) != OK) {
219 		    DBufFree(&buf);
220 		    DBufFree(&LineBuffer);
221 		    return E_NO_MEM;
222 		}
223 		if (DBufPutc(&LineBuffer, '\n') != OK) {
224 		    DBufFree(&buf);
225 		    DBufFree(&LineBuffer);
226 		    return E_NO_MEM;
227 		}
228 	    } else {
229 		DBufValue(&buf)[l-1] = '\n';
230 		if (DBufPuts(&LineBuffer, DBufValue(&buf)) != OK) {
231 		    DBufFree(&buf);
232 		    DBufFree(&LineBuffer);
233 		    return E_NO_MEM;
234 		}
235 	    }
236 	    continue;
237 	}
238 	if (DBufPuts(&LineBuffer, DBufValue(&buf)) != OK) {
239 	    DBufFree(&buf);
240 	    DBufFree(&LineBuffer);
241 	    return E_NO_MEM;
242 	}
243 	DBufFree(&buf);
244 
245 	/* If the line is: __EOF__ treat it as end-of-file */
246 	CurLine = DBufValue(&LineBuffer);
247 	if (!strcmp(CurLine, "__EOF__")) {
248 	    if (PurgeMode && PurgeFP) {
249 		PurgeEchoLine("%s\n", "__EOF__");
250 		while ((n = fread(copy_buffer, 1, sizeof(copy_buffer), fp)) != 0) {
251 		    fwrite(copy_buffer, 1, n, PurgeFP);
252 		}
253 		if (PurgeFP != stdout) fclose(PurgeFP);
254 		PurgeFP = NULL;
255 	    }
256             if (use_pclose) {
257                 PCLOSE(fp);
258             } else {
259                 FCLOSE(fp);
260             }
261 	    DBufFree(&LineBuffer);
262 	    CurLine = DBufValue(&LineBuffer);
263 	}
264 
265 	FreshLine = 1;
266 	if (DebugFlag & DB_ECHO_LINE) OutputLine(ErrFp);
267 	return OK;
268     }
269     CurLine = DBufValue(&LineBuffer);
270     return OK;
271 }
272 
273 /***************************************************************/
274 /*                                                             */
275 /*  OpenFile                                                   */
276 /*                                                             */
277 /*  Open a file for reading.  If it's in the cache, set        */
278 /*  CLine.  Otherwise, open it on disk and set fp.  If         */
279 /*  ShouldCache is 1, cache the file                           */
280 /*                                                             */
281 /***************************************************************/
OpenFile(char const * fname)282 int OpenFile(char const *fname)
283 {
284     CachedFile *h = CachedFiles;
285     int r;
286 
287     if (PurgeMode) {
288 	if (PurgeFP != NULL && PurgeFP != stdout) {
289 	    fclose(PurgeFP);
290 	}
291 	PurgeFP = NULL;
292     }
293 
294 /* If it's in the cache, get it from there. */
295 
296     while (h) {
297 	if (!strcmp(fname, h->filename)) {
298 	    if (DebugFlag & DB_TRACE_FILES) {
299 		fprintf(ErrFp, "Reading `%s': Found in cache\n", fname);
300 	    }
301 	    CLine = h->cache;
302 	    STRSET(FileName, fname);
303 	    LineNo = 0;
304 	    if (!h->ownedByMe) {
305 		RunDisabled |= RUN_NOTOWNER;
306 	    } else {
307 		RunDisabled &= ~RUN_NOTOWNER;
308             }
309 	    if (FileName) return OK; else return E_NO_MEM;
310 	}
311 	h = h->next;
312     }
313 
314 /* If it's a dash, then it's stdin */
315     if (!strcmp(fname, "-")) {
316 	fp = stdin;
317         RunDisabled &= ~RUN_NOTOWNER;
318 	if (PurgeMode) {
319 	    PurgeFP = stdout;
320 	}
321 	if (DebugFlag & DB_TRACE_FILES) {
322 	    fprintf(ErrFp, "Reading `-': Reading stdin\n");
323 	}
324     } else {
325 	fp = fopen(fname, "r");
326 	if (DebugFlag & DB_TRACE_FILES) {
327 	    fprintf(ErrFp, "Reading `%s': Opening file on disk\n", fname);
328 	}
329 	if (PurgeMode) {
330 	    OpenPurgeFile(fname, "w");
331 	}
332     }
333     if (!fp || !CheckSafety()) return E_CANT_OPEN;
334     CLine = NULL;
335     if (ShouldCache) {
336 	LineNo = 0;
337 	r = CacheFile(fname, 0);
338 	if (r == OK) {
339 	    fp = NULL;
340 	    CLine = CachedFiles->cache;
341 	} else {
342 	    if (strcmp(fname, "-")) {
343 		fp = fopen(fname, "r");
344 		if (!fp || !CheckSafety()) return E_CANT_OPEN;
345 		if (PurgeMode) OpenPurgeFile(fname, "w");
346 	    } else {
347 		fp = stdin;
348 		if (PurgeMode) PurgeFP = stdout;
349 	    }
350 	}
351     }
352     STRSET(FileName, fname);
353     LineNo = 0;
354     if (FileName) return OK; else return E_NO_MEM;
355 }
356 
357 /***************************************************************/
358 /*                                                             */
359 /*  CacheFile                                                  */
360 /*                                                             */
361 /*  Cache a file in memory.  If we fail, set ShouldCache to 0  */
362 /*  Returns an indication of success or failure.               */
363 /*                                                             */
364 /***************************************************************/
CacheFile(char const * fname,int use_pclose)365 static int CacheFile(char const *fname, int use_pclose)
366 {
367     int r;
368     CachedFile *cf;
369     CachedLine *cl;
370     char const *s;
371 
372     if (DebugFlag & DB_TRACE_FILES) {
373 	fprintf(ErrFp, "Caching file `%s' in memory\n", fname);
374     }
375     cl = NULL;
376 /* Create a file header */
377     cf = NEW(CachedFile);
378     if (!cf) {
379 	ShouldCache = 0;
380         if (use_pclose) {
381             PCLOSE(fp);
382         } else {
383             FCLOSE(fp);
384         }
385 	return E_NO_MEM;
386     }
387     cf->cache = NULL;
388     cf->filename = StrDup(fname);
389     if (!cf->filename) {
390 	ShouldCache = 0;
391         if (use_pclose) {
392             PCLOSE(fp);
393         } else {
394             FCLOSE(fp);
395         }
396 	free(cf);
397 	return E_NO_MEM;
398     }
399 
400     if (RunDisabled & RUN_NOTOWNER) {
401 	cf->ownedByMe = 0;
402     } else {
403 	cf->ownedByMe = 1;
404     }
405 
406 /* Read the file */
407     while(fp) {
408 	r = ReadLineFromFile(use_pclose);
409 	if (r) {
410 	    DestroyCache(cf);
411 	    ShouldCache = 0;
412             if (use_pclose) {
413                 PCLOSE(fp);
414             } else {
415                 FCLOSE(fp);
416             }
417 	    return r;
418 	}
419 /* Skip blank chars */
420 	s = DBufValue(&LineBuffer);
421 	while (isempty(*s)) s++;
422 	if (*s && *s!=';' && *s!='#') {
423 /* Add the line to the cache */
424 	    if (!cl) {
425 		cf->cache = NEW(CachedLine);
426 		if (!cf->cache) {
427 		    DBufFree(&LineBuffer);
428 		    DestroyCache(cf);
429 		    ShouldCache = 0;
430                     if (use_pclose) {
431                         PCLOSE(fp);
432                     } else {
433                         FCLOSE(fp);
434                     }
435 		    return E_NO_MEM;
436 		}
437 		cl = cf->cache;
438 	    } else {
439 		cl->next = NEW(CachedLine);
440 		if (!cl->next) {
441 		    DBufFree(&LineBuffer);
442 		    DestroyCache(cf);
443 		    ShouldCache = 0;
444                     if (use_pclose) {
445                         PCLOSE(fp);
446                     } else {
447                         FCLOSE(fp);
448                     }
449 		    return E_NO_MEM;
450 		}
451 		cl = cl->next;
452 	    }
453 	    cl->next = NULL;
454 	    cl->LineNo = LineNo;
455 	    cl->text = StrDup(s);
456 	    DBufFree(&LineBuffer);
457 	    if (!cl->text) {
458 		DestroyCache(cf);
459 		ShouldCache = 0;
460                 if (use_pclose) {
461                     PCLOSE(fp);
462                 } else {
463                     FCLOSE(fp);
464                 }
465 		return E_NO_MEM;
466 	    }
467 	}
468     }
469 
470 /* Put the cached file at the head of the queue */
471     cf->next = CachedFiles;
472     CachedFiles = cf;
473 
474     return OK;
475 }
476 
477 /***************************************************************/
478 /*                                                             */
479 /*  NextChainedFile - move to the next chained file in a glob  */
480 /*  list.                                                      */
481 /*                                                             */
482 /***************************************************************/
NextChainedFile(IncludeStruct * i)483 static int NextChainedFile(IncludeStruct *i)
484 {
485     while(i->chain) {
486 	FilenameChain *cur = i->chain;
487 	i->chain = i->chain->next;
488 	if (OpenFile(cur->filename) == OK) {
489 	    return OK;
490 	} else {
491 	    Eprint("%s: %s", ErrMsg[E_CANT_OPEN], cur->filename);
492 	}
493     }
494     return E_EOF;
495 }
496 
497 /***************************************************************/
498 /*                                                             */
499 /*  PopFile - we've reached the end.  Pop up to the previous   */
500 /*  file, or return E_EOF                                      */
501 /*                                                             */
502 /***************************************************************/
PopFile(void)503 static int PopFile(void)
504 {
505     IncludeStruct *i;
506 
507     if (!Hush && NumIfs) Eprint("%s", ErrMsg[E_MISS_ENDIF]);
508     if (!IStackPtr) return E_EOF;
509     i = &IStack[IStackPtr-1];
510 
511     if (i->chain) {
512 	int oldRunDisabled = RunDisabled;
513 	if (NextChainedFile(i) == OK) {
514 	    return OK;
515 	}
516 	RunDisabled = oldRunDisabled;
517     }
518 
519     if (IStackPtr <= 1) {
520 	return E_EOF;
521     }
522 
523     IStackPtr--;
524 
525     LineNo = i->LineNo;
526     IfFlags = i->IfFlags;
527     NumIfs = i->NumIfs;
528     CLine = i->CLine;
529     fp = NULL;
530     STRSET(FileName, i->filename);
531     if (!i->ownedByMe) {
532 	RunDisabled |= RUN_NOTOWNER;
533     } else {
534 	RunDisabled &= ~RUN_NOTOWNER;
535     }
536     if (!CLine && (i->offset != -1L || !strcmp(i->filename, "-"))) {
537 	/* We must open the file, then seek to specified position */
538 	if (strcmp(i->filename, "-")) {
539 	    fp = fopen(i->filename, "r");
540 	    if (!fp || !CheckSafety()) return E_CANT_OPEN;
541 	    if (PurgeMode) OpenPurgeFile(i->filename, "a");
542 	} else {
543 	    fp = stdin;
544 	    if (PurgeMode) PurgeFP = stdout;
545 	}
546 	if (fp != stdin)
547 	    (void) fseek(fp, i->offset, 0);  /* Trust that it works... */
548     }
549     free((char *) i->filename);
550     return OK;
551 }
552 
553 /***************************************************************/
554 /*                                                             */
555 /*  DoInclude                                                  */
556 /*                                                             */
557 /*  The INCLUDE command.                                       */
558 /*                                                             */
559 /***************************************************************/
DoInclude(ParsePtr p)560 int DoInclude(ParsePtr p)
561 {
562     DynamicBuffer buf;
563     int r, e;
564 
565     DBufInit(&buf);
566     if ( (r=ParseToken(p, &buf)) ) return r;
567     e = VerifyEoln(p);
568     if (e) Eprint("%s", ErrMsg[e]);
569     if ( (r=IncludeFile(DBufValue(&buf))) ) {
570 	DBufFree(&buf);
571 	return r;
572     }
573     DBufFree(&buf);
574     NumIfs = 0;
575     IfFlags = 0;
576     return OK;
577 }
578 
579 /***************************************************************/
580 /*                                                             */
581 /*  DoIncludeCmd                                               */
582 /*                                                             */
583 /*  The INCLUDECMD command.                                    */
584 /*                                                             */
585 /***************************************************************/
DoIncludeCmd(ParsePtr p)586 int DoIncludeCmd(ParsePtr p)
587 {
588     DynamicBuffer buf;
589     int r;
590     int ch;
591     char append_buf[2];
592     int seen_nonspace = 0;
593 
594     append_buf[1] = 0;
595 
596     DBufInit(&buf);
597 
598     while(1) {
599         ch = ParseChar(p, &r, 0);
600         if (r) {
601 	    DBufFree(&buf);
602 	    return r;
603 	}
604         if (!ch) {
605             break;
606         }
607         if (isspace(ch) && !seen_nonspace) {
608             continue;
609         }
610         seen_nonspace = 1;
611         /* Convert \n to ' ' to better handle line continuation */
612         if (ch == '\n') {
613             ch = ' ';
614         }
615         append_buf[0] = (char) ch;
616 	if (DBufPuts(&buf, append_buf) != OK) {
617 	    DBufFree(&buf);
618 	    return E_NO_MEM;
619 	}
620     }
621 
622     if (RunDisabled) {
623         DBufFree(&buf);
624         return E_RUN_DISABLED;
625     }
626 
627     if ( (r=IncludeCmd(DBufValue(&buf))) ) {
628 	DBufFree(&buf);
629 	return r;
630     }
631     DBufFree(&buf);
632     NumIfs = 0;
633     IfFlags = 0;
634     return OK;
635 }
636 
637 #ifdef HAVE_GLOB
SetupGlobChain(char const * dirname,IncludeStruct * i)638 static int SetupGlobChain(char const *dirname, IncludeStruct *i)
639 {
640     DynamicBuffer pattern;
641     char *dir;
642     size_t l;
643     int r;
644     glob_t glob_buf;
645     DirectoryFilenameChain *dc = CachedDirectoryChains;
646 
647     i->chain = NULL;
648     if (!*dirname) return E_CANT_OPEN;
649 
650     dir = StrDup(dirname);
651     if (!dir) return E_NO_MEM;
652 
653     /* Strip trailing slashes off directory */
654     l = strlen(dir);
655     while(l) {
656 	if (*(dir+l-1) == '/') {
657 	    l--;
658 	    *(dir+l) = 0;
659 	} else {
660 	    break;
661 	}
662     }
663 
664     /* Repair root directory :-) */
665     if (!l) {
666 	*dir = '/';
667     }
668 
669     /* Check the cache */
670     while(dc) {
671 	if (!strcmp(dc->dirname, dir)) {
672 	    if (DebugFlag & DB_TRACE_FILES) {
673 		fprintf(ErrFp, "Found cached directory listing for `%s'\n",
674 			dir);
675 	    }
676 	    free(dir);
677 	    i->chain = dc->chain;
678 	    return OK;
679 	}
680 	dc = dc->next;
681     }
682 
683     if (DebugFlag & DB_TRACE_FILES) {
684 	fprintf(ErrFp, "Scanning directory `%s' for *.rem files\n", dir);
685     }
686 
687     if (ShouldCache) {
688 	dc = malloc(sizeof(DirectoryFilenameChain));
689 	if (dc) {
690 	    dc->dirname = StrDup(dir);
691 	    if (!dc->dirname) {
692 		free(dc);
693 		dc = NULL;
694 	    }
695 	}
696 	if (dc) {
697 	    if (DebugFlag & DB_TRACE_FILES) {
698 		fprintf(ErrFp, "Caching directory `%s' listing\n", dir);
699 	    }
700 
701 	    dc->chain = NULL;
702 	    dc->next = CachedDirectoryChains;
703 	    CachedDirectoryChains = dc;
704 	}
705     }
706 
707     DBufInit(&pattern);
708     DBufPuts(&pattern, dir);
709     DBufPuts(&pattern, "/*.rem");
710     free(dir);
711 
712     r = glob(DBufValue(&pattern), 0, NULL, &glob_buf);
713     DBufFree(&pattern);
714 
715     if (r == GLOB_NOMATCH) {
716 	globfree(&glob_buf);
717 	return OK;
718     }
719 
720     if (r != 0) {
721 	globfree(&glob_buf);
722 	return -1;
723     }
724 
725     /* Add the files to the chain backwards to preserve sort order */
726     for (r=glob_buf.gl_pathc-1; r>=0; r--) {
727 	FilenameChain *ch = malloc(sizeof(FilenameChain));
728 	if (!ch) {
729 	    globfree(&glob_buf);
730 	    FreeChain(i->chain);
731 	    i->chain = NULL;
732 	    return E_NO_MEM;
733 	}
734 
735 	/* TODO: stat the file and only add if it's a plain file and
736 	   readable by us */
737 	ch->filename = StrDup(glob_buf.gl_pathv[r]);
738 	if (!ch->filename) {
739 	    globfree(&glob_buf);
740 	    FreeChain(i->chain);
741 	    i->chain = NULL;
742 	    free(ch);
743 	    return E_NO_MEM;
744 	}
745 	ch->next = i->chain;
746 	i->chain = ch;
747     }
748     if (dc) {
749 	dc->chain = i->chain;
750     }
751 
752     globfree(&glob_buf);
753     return OK;
754 }
755 #endif
756 
757 /***************************************************************/
758 /*                                                             */
759 /*  IncludeCmd                                                 */
760 /*                                                             */
761 /*  Process the INCLUDECMD command - actually do the command   */
762 /*  inclusion.                                                 */
763 /*                                                             */
764 /***************************************************************/
IncludeCmd(char const * cmd)765 static int IncludeCmd(char const *cmd)
766 {
767     IncludeStruct *i;
768     DynamicBuffer buf;
769     FILE *fp2;
770     int r;
771     CachedFile *h;
772     char const *fname;
773     int old_flag;
774 
775     FreshLine = 1;
776     if (IStackPtr+1 >= INCLUDE_NEST) return E_NESTED_INCLUDE;
777     i = &IStack[IStackPtr];
778 
779     /* Use "cmd|" as the filename */
780     DBufInit(&buf);
781     if (DBufPuts(&buf, cmd) != OK) {
782 	DBufFree(&buf);
783 	return E_NO_MEM;
784     }
785     if (DBufPuts(&buf, "|") != OK) {
786 	DBufFree(&buf);
787 	return E_NO_MEM;
788     }
789     fname = DBufValue(&buf);
790 
791     if (FileName) {
792 	i->filename = StrDup(FileName);
793 	if (!i->filename) {
794 	    DBufFree(&buf);
795 	    return E_NO_MEM;
796 	}
797     } else {
798 	i->filename = NULL;
799     }
800     i->ownedByMe = 1;
801     i->LineNo = LineNo;
802     i->NumIfs = NumIfs;
803     i->IfFlags = IfFlags;
804     i->CLine = CLine;
805     i->offset = -1L;
806     i->chain = NULL;
807     if (fp) {
808 	i->offset = ftell(fp);
809 	FCLOSE(fp);
810     }
811     IStackPtr++;
812 
813     /* If the file is cached, use it */
814     h = CachedFiles;
815     while(h) {
816         if (!strcmp(fname, h->filename)) {
817             if (DebugFlag & DB_TRACE_FILES) {
818                 fprintf(ErrFp, "Reading command `%s': Found in cache\n", fname);
819             }
820             CLine = h->cache;
821             STRSET(FileName, fname);
822             DBufFree(&buf);
823             LineNo = 0;
824             if (!h->ownedByMe) {
825                 RunDisabled |= RUN_NOTOWNER;
826             } else {
827                 RunDisabled &= ~RUN_NOTOWNER;
828             }
829             if (FileName) return OK; else return E_NO_MEM;
830         }
831         h = h->next;
832     }
833 
834     if (DebugFlag & DB_TRACE_FILES) {
835         fprintf(ErrFp, "Executing `%s' for INCLUDECMD and caching as `%s'\n",
836                 cmd, fname);
837     }
838 
839     /* Not found in cache */
840 
841     /* If cmd starts with !, then disable RUN within the cmd output */
842     if (cmd[0] == '!') {
843         fp2 = popen(cmd+1, "r");
844     } else {
845         fp2 = popen(cmd, "r");
846     }
847     if (!fp2) {
848 	DBufFree(&buf);
849 	return E_CANT_OPEN;
850     }
851     fp = fp2;
852     LineNo = 0;
853 
854     /* Temporarily turn of file tracing */
855     old_flag = DebugFlag;
856     DebugFlag &= (~DB_TRACE_FILES);
857 
858     if (cmd[0] == '!') {
859         RunDisabled |= RUN_NOTOWNER;
860     }
861     r = CacheFile(fname, 1);
862 
863     DebugFlag = old_flag;
864     if (r == OK) {
865 	fp = NULL;
866 	CLine = CachedFiles->cache;
867 	LineNo = 0;
868 	STRSET(FileName, fname);
869 	DBufFree(&buf);
870 	return OK;
871     }
872     DBufFree(&buf);
873     /* We failed */
874     PopFile();
875     return E_CANT_OPEN;
876 }
877 
878 /***************************************************************/
879 /*                                                             */
880 /*  IncludeFile                                                */
881 /*                                                             */
882 /*  Process the INCLUDE command - actually do the file         */
883 /*  inclusion.                                                 */
884 /*                                                             */
885 /***************************************************************/
IncludeFile(char const * fname)886 int IncludeFile(char const *fname)
887 {
888     IncludeStruct *i;
889     int oldRunDisabled;
890     struct stat statbuf;
891 
892     FreshLine = 1;
893     if (IStackPtr+1 >= INCLUDE_NEST) return E_NESTED_INCLUDE;
894     i = &IStack[IStackPtr];
895 
896     if (FileName) {
897 	i->filename = StrDup(FileName);
898 	if (!i->filename) return E_NO_MEM;
899     } else {
900 	i->filename = NULL;
901     }
902     i->LineNo = LineNo;
903     i->NumIfs = NumIfs;
904     i->IfFlags = IfFlags;
905     i->CLine = CLine;
906     i->offset = -1L;
907     i->chain = NULL;
908     if (RunDisabled & RUN_NOTOWNER) {
909 	i->ownedByMe = 0;
910     } else {
911 	i->ownedByMe = 1;
912     }
913     if (fp) {
914 	i->offset = ftell(fp);
915 	FCLOSE(fp);
916     }
917 
918     IStackPtr++;
919 
920 #ifdef HAVE_GLOB
921     /* If it's a directory, set up the glob chain here. */
922     if (stat(fname, &statbuf) == 0) {
923 	FilenameChain *fc;
924 	if (S_ISDIR(statbuf.st_mode)) {
925 	    if (SetupGlobChain(fname, i) == OK) { /* Glob succeeded */
926 		if (!i->chain) { /* Oops... no matching files */
927 		    if (!Hush) {
928 			Eprint("%s: %s", fname, ErrMsg[E_NO_MATCHING_REMS]);
929 		    }
930 		    PopFile();
931 		    return E_NO_MATCHING_REMS;
932 		}
933 		while(i->chain) {
934 		    fc = i->chain;
935 		    i->chain = i->chain->next;
936 
937 		    /* Munch first file */
938 		    oldRunDisabled = RunDisabled;
939 		    if (!OpenFile(fc->filename)) {
940 			return OK;
941 		    }
942 		    Eprint("%s: %s", ErrMsg[E_CANT_OPEN], fc->filename);
943 		    RunDisabled = oldRunDisabled;
944 		}
945 		/* Couldn't open anything... bail */
946 		return PopFile();
947 	    } else {
948 		if (!Hush) {
949 		    Eprint("%s: %s", fname, ErrMsg[E_NO_MATCHING_REMS]);
950 		}
951 	    }
952 	    return E_NO_MATCHING_REMS;
953 	}
954     }
955 #endif
956 
957     oldRunDisabled = RunDisabled;
958     /* Try to open the new file */
959     if (!OpenFile(fname)) {
960 	return OK;
961     }
962     RunDisabled = oldRunDisabled;
963     Eprint("%s: %s", ErrMsg[E_CANT_OPEN], fname);
964     /* Ugh!  We failed!  */
965     PopFile();
966     return E_CANT_OPEN;
967 }
968 
969 /***************************************************************/
970 /*                                                             */
971 /* GetAccessDate - get the access date of a file.              */
972 /*                                                             */
973 /***************************************************************/
GetAccessDate(char const * file)974 int GetAccessDate(char const *file)
975 {
976     struct stat statbuf;
977     struct tm *t1;
978 
979     if (stat(file, &statbuf)) return -1;
980     t1 = localtime(&(statbuf.st_atime));
981 
982     if (t1->tm_year + 1900 < BASE)
983 	return 0;
984     else
985 	return Julian(t1->tm_year+1900, t1->tm_mon, t1->tm_mday);
986 }
987 
988 /***************************************************************/
989 /*                                                             */
990 /*  DestroyCache                                               */
991 /*                                                             */
992 /*  Free all the memory used by a cached file.                 */
993 /*                                                             */
994 /***************************************************************/
DestroyCache(CachedFile * cf)995 static void DestroyCache(CachedFile *cf)
996 {
997     CachedLine *cl, *cnext;
998     CachedFile *temp;
999     if (cf->filename) free((char *) cf->filename);
1000     cl = cf->cache;
1001     while (cl) {
1002 	if (cl->text) free ((char *) cl->text);
1003 	cnext = cl->next;
1004 	free(cl);
1005 	cl = cnext;
1006     }
1007     if (CachedFiles == cf) CachedFiles = cf->next;
1008     else {
1009 	temp = CachedFiles;
1010 	while(temp) {
1011 	    if (temp->next == cf) {
1012 		temp->next = cf->next;
1013 		break;
1014 	    }
1015 	    temp = temp->next;
1016 	}
1017     }
1018     free(cf);
1019 }
1020 
1021 /***************************************************************/
1022 /*                                                             */
1023 /*  TopLevel                                                   */
1024 /*                                                             */
1025 /*  Returns 1 if current file is top level, 0 otherwise.       */
1026 /*                                                             */
1027 /***************************************************************/
TopLevel(void)1028 int TopLevel(void)
1029 {
1030     return IStackPtr <= 1;
1031 }
1032 
1033 /***************************************************************/
1034 /*                                                             */
1035 /*  CheckSafety                                                */
1036 /*                                                             */
1037 /*  Returns 1 if current file is safe to read; 0 otherwise.    */
1038 /*  Currently only meaningful for UNIX.  If we are running as  */
1039 /*  root, we refuse to open files not owned by root.           */
1040 /*  We also reject world-writable files, no matter             */
1041 /*  who we're running as.                                      */
1042 /*  As a side effect, if we don't own the file, or it's not    */
1043 /*  owned by a trusted user, we disable RUN                    */
1044 /***************************************************************/
CheckSafety(void)1045 static int CheckSafety(void)
1046 {
1047     struct stat statbuf;
1048 
1049     if (fp == stdin) {
1050 	return 1;
1051     }
1052 
1053     if (fstat(fileno(fp), &statbuf)) {
1054 	fclose(fp);
1055 	fp = NULL;
1056 	return 0;
1057     }
1058 
1059     /* Under UNIX, take extra precautions if running as root */
1060     if (!geteuid()) {
1061 	/* Reject files not owned by root or group/world writable */
1062 	if (statbuf.st_uid != 0) {
1063 	    fprintf(ErrFp, "SECURITY: Won't read non-root-owned file when running as root!\n");
1064 	    fclose(fp);
1065 	    fp = NULL;
1066 	    return 0;
1067 	}
1068     }
1069     /* Sigh... /dev/null is usually world-writable, so ignore devices,
1070        FIFOs, sockets, etc. */
1071     if (!S_ISREG(statbuf.st_mode)) {
1072 	return 1;
1073     }
1074     if ((statbuf.st_mode & S_IWOTH)) {
1075 	fprintf(ErrFp, "SECURITY: Won't read world-writable file!\n");
1076 	fclose(fp);
1077 	fp = NULL;
1078 	return 0;
1079     }
1080 
1081     /* If file is not owned by me or a trusted user, disable RUN command */
1082 
1083     /* Assume unsafe */
1084     RunDisabled |= RUN_NOTOWNER;
1085     if (statbuf.st_uid == geteuid()) {
1086         /* Owned by me... safe */
1087 	RunDisabled &= ~RUN_NOTOWNER;
1088     } else {
1089         int i;
1090         for (i=0; i<NumTrustedUsers; i++) {
1091             if (statbuf.st_uid == TrustedUsers[i]) {
1092                 /* Owned by a trusted user... safe */
1093                 RunDisabled &= ~RUN_NOTOWNER;
1094                 break;
1095             }
1096         }
1097     }
1098 
1099     return 1;
1100 }
1101