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