1 /*----------------------------------------------------------------------------*/
2 /* Xymon monitor library.                                                     */
3 /*                                                                            */
4 /* This is a library module, part of libxymon.                                */
5 /* It contains routines for reading configuration files with "include"s.      */
6 /*                                                                            */
7 /* Copyright (C) 2002-2011 Henrik Storner <henrik@storner.dk>                 */
8 /*                                                                            */
9 /* This program is released under the GNU General Public License (GPL),       */
10 /* version 2. See the file "COPYING" for details.                             */
11 /*                                                                            */
12 /*----------------------------------------------------------------------------*/
13 
14 static char rcsid[] = "$Id: stackio.c 8084 2019-08-30 23:01:18Z jccleaver $";
15 
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <ctype.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <limits.h>
23 #include <dirent.h>
24 
25 #include "libxymon.h"
26 
27 typedef struct filelist_t {
28 	char *filename;
29 	time_t mtime;
30 	size_t fsize;
31 	struct filelist_t *next;
32 } filelist_t;
33 
34 typedef struct fgetsbuf_t {
35 	FILE *fd;
36 	char inbuf[4096+1];
37 	char *inbufp;
38 	int moretoread;
39 	struct fgetsbuf_t *next;
40 } fgetsbuf_t;
41 static fgetsbuf_t *fgetshead = NULL;
42 
43 typedef struct stackfd_t {
44 	FILE *fd;
45 	filelist_t **listhead;
46 	struct stackfd_t *next;
47 } stackfd_t;
48 static stackfd_t *fdhead = NULL;
49 static char *stackfd_base = NULL;
50 static char *stackfd_mode = NULL;
51 static htnames_t *fnlist = NULL;
52 
53 /*
54  * initfgets() and unlimfgets() implements a fgets() style
55  * input routine that can handle arbitrarily long input lines.
56  * Buffer space for the input is dynamically allocated and
57  * expanded, until the input hits a newline character.
58  * Simultaneously, lines ending with a '\' character are
59  * merged into one line, allowing for transparent handling
60  * of very long lines.
61  *
62  * This interface is also used by the stackfgets() routine.
63  *
64  * If you open a file directly, after getting the FILE
65  * descriptor call initfgets(FILE). Then use unlimfgets()
66  * to read data one line at a time. You must read until
67  * unlimfgets() returns NULL (at which point you should not
68  * call unlimfgets() again with this fd).
69  */
initfgets(FILE * fd)70 int initfgets(FILE *fd)
71 {
72 	fgetsbuf_t *newitem;
73 
74 	newitem = (fgetsbuf_t *)malloc(sizeof(fgetsbuf_t));
75 	*(newitem->inbuf) = '\0';
76 	newitem->inbufp = newitem->inbuf;
77 	newitem->moretoread = 1;
78 	newitem->fd = fd;
79 	newitem->next = fgetshead;
80 	fgetshead = newitem;
81 	return 0;
82 }
83 
unlimfgets(strbuffer_t * buffer,FILE * fd)84 char *unlimfgets(strbuffer_t *buffer, FILE *fd)
85 {
86 	fgetsbuf_t *fg;
87 	size_t n;
88 	char *eoln = NULL;
89 
90 	for (fg = fgetshead; (fg && (fg->fd != fd)); fg = fg->next) ;
91 	if (!fg) {
92 		errprintf("umlimfgets() called with bad input FD\n");
93 		return NULL;
94 	}
95 
96 	/* End of file ? */
97 	if (!(fg->moretoread) && (*(fg->inbufp) == '\0')) {
98 		if (fg == fgetshead) {
99 			fgetshead = fgetshead->next;
100 			free(fg);
101 		}
102 		else {
103 			fgetsbuf_t *prev;
104 			for (prev = fgetshead; (prev->next != fg); prev = prev->next) ;
105 			prev->next = fg->next;
106 			free(fg);
107 		}
108 		return NULL;
109 	}
110 
111 	/* Make sure the output buffer is empty */
112 	clearstrbuffer(buffer);
113 
114 	while (!eoln && (fg->moretoread || *(fg->inbufp))) {
115 		int continued = 0;
116 
117 		if (*(fg->inbufp)) {
118 			/* Have some data in the buffer */
119 			eoln = strchr(fg->inbufp, '\n');
120 			if (eoln) {
121 				/* See if there's a continuation character just before the eoln */
122 				char *contchar = eoln-1;
123 				while ((contchar > fg->inbufp) && isspace((int)*contchar) && (*contchar != '\\')) contchar--;
124 				continued = (*contchar == '\\');
125 
126 				if (continued) {
127 					*contchar = '\0';
128 					addtobuffer(buffer, fg->inbufp);
129 					fg->inbufp = eoln+1;
130 					eoln = NULL;
131 				}
132 				else {
133 					char savech = *(eoln+1);
134 					*(eoln+1) = '\0';
135 					addtobuffer(buffer, fg->inbufp);
136 					*(eoln+1) = savech;
137 					fg->inbufp = eoln+1;
138 				}
139 			}
140 			else {
141 				/* No newline in buffer, so add all of it to the output buffer */
142 				addtobuffer(buffer, fg->inbufp);
143 
144 				/* Input buffer is now empty */
145 				*(fg->inbuf) = '\0';
146 				fg->inbufp = fg->inbuf;
147 			}
148 		}
149 
150 		if (!eoln && !continued) {
151 			/* Get data for the input buffer */
152 			char *inpos = fg->inbuf;
153 			size_t insize = sizeof(fg->inbuf);
154 
155 			/* If the last byte we read was a continuation char, we must do special stuff.
156 			 *
157 			 * Mike Romaniw discovered that if we hit an input with a newline exactly at
158 			 * the point of a buffer refill, then strlen(*buffer) is 0, and contchar then
159 			 * points before the start of the buffer. Bad. But this can only happen when
160 			 * the previous char WAS a newline, and hence it is not a continuation line.
161 			 * So the simple fix is to only do the cont-char stuff if **buffer is not NUL.
162 			 * Hence the test for both *buffer and **buffer.
163 			 */
164 			if (STRBUF(buffer) && *STRBUF(buffer)) {
165 				char *contchar = STRBUF(buffer) + STRBUFLEN(buffer) - 1;
166 				while ((contchar > STRBUF(buffer)) && isspace((int)*contchar) && (*contchar != '\\')) contchar--;
167 
168 				if (*contchar == '\\') {
169 					/*
170 					 * Remove the cont. char from the output buffer, and stuff it into
171 					 * the input buffer again - so we can check if there's a new-line coming.
172 					 */
173 					strbufferchop(buffer, 1);
174 					*(fg->inbuf) = '\\';
175 					inpos++;
176 					insize--;
177 				}
178 			}
179 
180 			n = fread(inpos, 1, insize-1, fd);
181 			*(inpos + n) = '\0';
182 			fg->inbufp = fg->inbuf;
183 			if (n < insize-1) fg->moretoread = 0;
184 		}
185 	}
186 
187 	return STRBUF(buffer);
188 }
189 
stackfopen(char * filename,char * mode,void ** v_listhead)190 FILE *stackfopen(char *filename, char *mode, void **v_listhead)
191 {
192 	FILE *newfd;
193 	stackfd_t *newitem;
194 	char stackfd_filename[PATH_MAX];
195 	filelist_t **listhead = (filelist_t **)v_listhead;
196 
197 	MEMDEFINE(stackfd_filename);
198 
199 	if (fdhead == NULL) {
200 		char *p;
201 
202 		stackfd_base = strdup(filename);
203 		p = strrchr(stackfd_base, '/'); if (p) *(p+1) = '\0';
204 
205 		stackfd_mode = strdup(mode);
206 
207 		strncpy(stackfd_filename, filename, sizeof(stackfd_filename));
208 	}
209 	else {
210 		if (*filename == '/')
211 			strncpy(stackfd_filename, filename, sizeof(stackfd_filename));
212 		else
213 			snprintf(stackfd_filename, sizeof(stackfd_filename), "%s/%s", stackfd_base, filename);
214 	}
215 
216 	dbgprintf("Opening file %s\n", stackfd_filename);
217 	newfd = fopen(stackfd_filename, stackfd_mode);
218 	if (newfd != NULL) {
219 		newitem = (stackfd_t *) malloc(sizeof(stackfd_t));
220 		newitem->fd = newfd;
221 		newitem->listhead = listhead;
222 		newitem->next = fdhead;
223 		fdhead = newitem;
224 		initfgets(newfd);
225 
226 		if (listhead) {
227 			struct filelist_t *newlistitem;
228 			struct stat st;
229 
230 			fstat(fileno(newfd), &st);
231 			newlistitem = (filelist_t *)malloc(sizeof(filelist_t));
232 			newlistitem->filename = strdup(stackfd_filename);
233 			newlistitem->mtime = st.st_mtime;
234 			newlistitem->fsize = st.st_size;
235 			newlistitem->next = *listhead;
236 			*listhead = newlistitem;
237 		}
238 	}
239 
240 	MEMUNDEFINE(stackfd_filename);
241 
242 	return newfd;
243 }
244 
245 
stackfclose(FILE * fd)246 int stackfclose(FILE *fd)
247 {
248 	int result;
249 	stackfd_t *olditem;
250 
251 	if (fd != NULL) {
252 		/* Close all */
253 		while (fdhead != NULL) {
254 			olditem = fdhead;
255 			fdhead = fdhead->next;
256 			fclose(olditem->fd);
257 			xfree(olditem);
258 		}
259 		xfree(stackfd_base);
260 		xfree(stackfd_mode);
261 		while (fnlist) {
262 			htnames_t *tmp = fnlist;
263 			fnlist = fnlist->next;
264 			xfree(tmp->name); xfree(tmp);
265 		}
266 		result = 0;
267 	}
268 	else {
269 		olditem = fdhead;
270 		fdhead = fdhead->next;
271 		result = fclose(olditem->fd);
272 		xfree(olditem);
273 	}
274 
275 	return result;
276 }
277 
stackfmodified(void * v_listhead)278 int stackfmodified(void *v_listhead)
279 {
280 	/* Walk the list of filenames, and see if any have changed */
281 	filelist_t *walk;
282 	struct stat st;
283 
284 	for (walk=(filelist_t *)v_listhead; (walk); walk = walk->next) {
285 		if (stat(walk->filename, &st) == -1) {
286 			dbgprintf("File %s no longer exists\n", walk->filename);
287 			return 1; /* File has disappeared */
288 		}
289 		if (st.st_mtime != walk->mtime) {
290 			dbgprintf("File %s new timestamp\n", walk->filename);
291 			return 1; /* Timestamp has changed */
292 		}
293 		if (S_ISREG(st.st_mode) && (st.st_size != walk->fsize)) {
294 			dbgprintf("File %s new size\n", walk->filename);
295 			return 1; /* Size has changed */
296 		}
297 	}
298 
299 	return 0;
300 }
301 
stackfclist(void ** v_listhead)302 void stackfclist(void **v_listhead)
303 {
304 	/* Free the list of filenames */
305 	filelist_t *tmp;
306 
307 	if ((v_listhead == NULL) || (*v_listhead == NULL)) return;
308 
309 	while (*v_listhead) {
310 		tmp = (filelist_t *) *v_listhead;
311 		*v_listhead = ((filelist_t *) *v_listhead)->next;
312 		xfree(tmp->filename);
313 		xfree(tmp);
314 	}
315 	*v_listhead = NULL;
316 }
317 
namecompare(const void * v1,const void * v2)318 static int namecompare(const void *v1, const void *v2)
319 {
320 	char **n1 = (char **)v1;
321 	char **n2 = (char **)v2;
322 
323 	/* Sort in reverse order, so when we add them to the list in LIFO, it will be alpha-sorted */
324 	return -strcmp(*n1, *n2);
325 }
326 
addtofnlist(char * dirname,int is_optional,void ** v_listhead)327 static void addtofnlist(char *dirname, int is_optional, void **v_listhead)
328 {
329 	filelist_t **listhead = (filelist_t **)v_listhead;
330 	DIR *dirfd;
331 	struct dirent *d;
332 	struct stat st;
333 	char dirfn[PATH_MAX], fn[PATH_MAX];
334 	char **fnames = NULL;
335 	int fnsz = 0;
336 	int i;
337 
338 	if (*dirname == '/') strncpy(dirfn, dirname, sizeof(dirfn)); else snprintf(dirfn, sizeof(dirfn), "%s/%s", stackfd_base, dirname);
339 
340 	if ((dirfd = opendir(dirfn)) == NULL) {
341 		if (!is_optional) errprintf("WARNING: Cannot open directory %s\n", dirfn);
342 		else dbgprintf("addtofnlist(): Cannot open directory %s\n", dirfn);
343 		return;
344 	}
345 
346 	/* Add the directory itself to the list of files we watch for modifications */
347 	if (listhead) {
348 		filelist_t *newlistitem;
349 
350 		stat(dirfn, &st);
351 		newlistitem = (filelist_t *)malloc(sizeof(filelist_t));
352 		newlistitem->filename = strdup(dirfn);
353 		newlistitem->mtime = st.st_mtime;
354 		newlistitem->fsize = 0; /* We don't check sizes of directories */
355 		newlistitem->next = *listhead;
356 		*listhead = newlistitem;
357 	}
358 
359 	while ((d = readdir(dirfd)) != NULL) {
360 		int fnlen = strlen(d->d_name);
361 
362 		/* Skip all dot-files */
363 		if (*(d->d_name) == '.') continue;
364 
365 		/* Skip editor backups - file ending with '~' */
366 		if (*(d->d_name + fnlen - 1) == '~') continue;
367 
368 		/* Skip RCS files - they end with ",v" */
369 		if ((fnlen >= 2) && (strcmp(d->d_name + fnlen - 2, ",v") == 0)) continue;
370 
371 		/* Skip any documentation file starting with README */
372 		if (strncmp(d->d_name, "README", 6) == 0) continue;
373 
374 		/* Skip Debian installer left-overs - they end with ".dpkg-new"  or .dpkg-orig */
375 		if ((fnlen >= 9) && (strcmp(d->d_name + fnlen - 9, ".dpkg-new") == 0)) continue;
376 		if ((fnlen >= 10) && (strcmp(d->d_name + fnlen - 10, ".dpkg-orig") == 0)) continue;
377 
378 
379 		/* Skip RPM package debris - they end with ".rpmsave", .rpmnew, or .rpmorig */
380 		if ((fnlen >= 8) && ((strcmp(d->d_name + fnlen - 8, ".rpmsave") == 0) || (strcmp(d->d_name + fnlen - 8, ".rpmorig") == 0) ) ) continue;
381 		if ((fnlen >= 7) && (strcmp(d->d_name + fnlen - 7, ".rpmnew") == 0)) continue;
382 
383 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
384 		#pragma GCC diagnostic push
385 		#pragma GCC diagnostic ignored "-Wformat-truncation"
386 #endif  // __GNUC__
387 		snprintf(fn, sizeof(fn), "%s/%s", dirfn, d->d_name);
388 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
389 		#pragma GCC diagnostic pop
390 #endif  // __GNUC__
391 		if (stat(fn, &st) == -1) continue;
392 
393 		if (S_ISDIR(st.st_mode)) {
394 			/* Skip RCS sub-directories */
395 			if (strcmp(d->d_name, "RCS") == 0) continue;
396 			addtofnlist(fn, 0, v_listhead);		/* this directory is optional, but opening up files that do exist isn't */
397 		}
398 
399 		/* Skip everything that isn't a regular file */
400 		if (!S_ISREG(st.st_mode)) continue;
401 
402 		if (fnsz == 0) fnames = (char **)malloc(2*sizeof(char **));
403 		else fnames = (char **)realloc(fnames, (fnsz+2)*sizeof(char **));
404 		fnames[fnsz] = strdup(fn);
405 
406 		fnsz++;
407 	}
408 
409 	closedir(dirfd);
410 
411 	if (fnsz) {
412 		qsort(fnames, fnsz, sizeof(char *), namecompare);
413 		for (i=0; (i<fnsz); i++) {
414 			htnames_t *newitem = malloc(sizeof(htnames_t));
415 			newitem->name = fnames[i];
416 			newitem->next = fnlist;
417 			fnlist = newitem;
418 		}
419 
420 		xfree(fnames);
421 	}
422 }
423 
stackfgets(strbuffer_t * buffer,char * extraincl)424 char *stackfgets(strbuffer_t *buffer, char *extraincl)
425 {
426 	char *result;
427 	int optional = 0;
428 
429 	result = unlimfgets(buffer, fdhead->fd);
430 
431 	if (result) {
432 		char *bufpastwhitespace = STRBUF(buffer) + strspn(STRBUF(buffer), " \t");
433 		if (strncmp(bufpastwhitespace, "optional", 8) == 0) { optional = 1; bufpastwhitespace += 8 + strspn(bufpastwhitespace+8, " \t"); }
434 
435 		if ( (strncmp(bufpastwhitespace, "include ", 8) == 0) || (strncmp(bufpastwhitespace, "include\t", 8) == 0) ||
436 		     (extraincl && (strncmp(bufpastwhitespace, extraincl, strlen(extraincl)) == 0)) ) {
437 			char *newfn, *eol, eolchar = '\0';
438 
439 			eol = bufpastwhitespace + strcspn(bufpastwhitespace, "\r\n"); if (eol) { eolchar = *eol; *eol = '\0'; }
440 			newfn = bufpastwhitespace + strcspn(bufpastwhitespace, " \t");
441 			newfn += strspn(newfn, " \t");
442 			while (*newfn && isspace(*(newfn + strlen(newfn) - 1))) *(newfn + strlen(newfn) -1) = '\0';
443 
444 			if (*newfn && (stackfopen(newfn, "r", (void **)fdhead->listhead) != NULL))
445 				return stackfgets(buffer, extraincl);
446 			else {
447 				if (!optional) errprintf("WARNING: Cannot open include file '%s', line was: %s\n", newfn, STRBUF(buffer));
448 				else dbgprintf("stackfgets(): Cannot open include file '%s', line was: %s\n", newfn, STRBUF(buffer));
449 
450 				if (eol) *eol = eolchar;
451 				return result;
452 			}
453 		}
454 		else if ((strncmp(bufpastwhitespace, "directory ", 10) == 0) || (strncmp(bufpastwhitespace, "directory\t", 10) == 0)) {
455 			char *dirfn, *eol, eolchar = '\0';
456 
457 			eol = bufpastwhitespace + strcspn(bufpastwhitespace, "\r\n"); if (eol) { eolchar = *eol; *eol = '\0'; }
458 			dirfn = bufpastwhitespace + 9;
459 			dirfn += strspn(dirfn, " \t");
460 			while (*dirfn && isspace(*(dirfn + strlen(dirfn) - 1))) *(dirfn + strlen(dirfn) -1) = '\0';
461 
462 			if (*dirfn) addtofnlist(dirfn, optional, (void **)fdhead->listhead);
463 			if (fnlist && (stackfopen(fnlist->name, "r", (void **)fdhead->listhead) != NULL)) {
464 				htnames_t *tmp = fnlist;
465 
466 				fnlist = fnlist->next;
467 				xfree(tmp->name); xfree(tmp);
468 				return stackfgets(buffer, extraincl);
469 			}
470 			else if (fnlist) {
471 				htnames_t *tmp = fnlist;
472 
473 				if (!optional) errprintf("WARNING: Cannot open include file '%s', line was: %s\n", fnlist->name, buffer);
474 				else dbgprintf("stackfgets(): Cannot open include file '%s', line was: %s\n", fnlist->name, buffer);
475 
476 				fnlist = fnlist->next;
477 				xfree(tmp->name); xfree(tmp);
478 				if (eol) *eol = eolchar;
479 				return result;
480 			}
481 			else {
482 				/* Empty directory include - return a blank line */
483 				dbgprintf("stackfgets(): Directory %s was empty\n", dirfn);
484 				*result = '\0';
485 				return result;
486 			}
487 		}
488 	}
489 	else if (result == NULL) {
490 		/* end-of-file on read */
491 		stackfclose(NULL);
492 		if (fnlist) {
493 			if (stackfopen(fnlist->name, "r", (void **)fdhead->listhead) != NULL) {
494 				htnames_t *tmp = fnlist;
495 
496 				fnlist = fnlist->next;
497 				xfree(tmp->name); xfree(tmp);
498 				return stackfgets(buffer, extraincl);
499 			}
500 			else {
501 				htnames_t *tmp = fnlist;
502 
503 				if (!optional) errprintf("WARNING: Cannot open include file '%s', line was: %s\n", fnlist->name, buffer);
504 				else dbgprintf("stackfgets(): Cannot open include file '%s', line was: %s\n", fnlist->name, buffer);
505 
506 				fnlist = fnlist->next;
507 				xfree(tmp->name); xfree(tmp);
508 				return result;
509 			}
510 		}
511 		else if (fdhead != NULL)
512 			return stackfgets(buffer, extraincl);
513 		else
514 			return NULL;
515 	}
516 
517 	return result;
518 }
519 
520 
521 #ifdef STANDALONE
main(int argc,char * argv[])522 int main(int argc, char *argv[])
523 {
524 	char *fn, *p;
525 	char cmd[1024];
526 	FILE *fd;
527 	strbuffer_t *inbuf = newstrbuffer(0);
528 	void *listhead = NULL;
529 	int done, linenum;
530 
531 	fn = strdup(argv[1]);
532 	strncpy(cmd, "!", sizeof(cmd));
533 	done = 0;
534 	while (!done) {
535 		if (*cmd == '!') {
536 			fd = stackfopen(fn, "r", &listhead);
537 			linenum = 1;
538 			if (!fd) { errprintf("Cannot open file %s\n", fn); continue; }
539 
540 			while (stackfgets(inbuf, NULL)) {
541 				linenum++;
542 				printf("%s", STRBUF(inbuf));
543 			}
544 			stackfclose(fd);
545 		}
546 		else if (*cmd == '?') {
547 			filelist_t *walk = (filelist_t *)listhead;
548 
549 			while (walk) {
550 				printf("%s %lu\n", walk->filename, (unsigned long)walk->fsize);
551 				walk = walk->next;
552 			}
553 			if (stackfmodified(listhead)) printf("File(s) have been modified\n");
554 			else printf("No changes\n");
555 		}
556 		else if (*cmd == '.') {
557 			done = 1;
558 			continue;
559 		}
560 		else {
561 			xfree(fn); fn = strdup(cmd);
562 			stackfclist(&listhead);
563 			strncpy(cmd, "!", sizeof(cmd));
564 			continue;
565 		}
566 
567 		printf("\nCmd: "); fflush(stdout);
568 		if (fgets(cmd, sizeof(cmd), stdin)) {
569 			p = strchr(cmd, '\n'); if (p) *p = '\0';
570 		}
571 		else
572 			done = 1;
573 	}
574 
575 	xfree(fn);
576 	stackfclist(&listhead);
577 
578 	return 0;
579 }
580 #endif
581 
582