1 /*****************************************************************************
2  *
3  * fetchlog.c - logfile fetcher: pick up last new messages of a logfile
4  *
5  * Copyright (c) 2002 .. 2010 Alexander Haderer (alexander.haderer@loescap.de)
6  *
7  *  Last Update:      $Author: afrika $
8  *  Update Date:      $Date: 2010/07/01 16:39:25 $
9  *  Source File:      $Source: /home/cvsroot/tools/fetchlog/fetchlog.c,v $
10  *  CVS/RCS Revision: $Revision: 1.9 $
11  *  Status:           $State: Exp $
12  *
13  *  CVS/RCS Log at end of file
14  *
15  * License:
16  *
17  * This program is free software; you can redistribute it and/or modify
18  * it under the terms of the GNU General Public License as published by
19  * the Free Software Foundation; either version 2 of the License, or
20  * (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  * GNU General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public License
28  * along with this program; if not, write to the Free Software
29  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
30  *
31  *****************************************************************************/
32 
33 
34 #include <stdio.h>	/* sprintf */
35 #include <stdlib.h>	/* atoi */
36 #include <ctype.h>	/* isalpha */
37 #include <string.h>	/* strcat, strcpy */
38 #include <fcntl.h>	/* open close */
39 #include <sys/types.h>	/* stat */
40 #include <sys/stat.h>	/* stat */
41 #include <sys/mman.h>	/* mmap, madvise */
42 #include <unistd.h>	/* access */
43 #include <errno.h>	/* errno */
44 #ifdef HAS_REGEX
45 #include <regex.h>	/* posix regex stuff */
46 #endif
47 
48 /*************************************************
49  * constants
50  *************************************************/
51 
52 #define MIN_FIRSTCOL	1	/* Min first col for fetching	*/
53 #define MAX_LASTCOL 	300 	/* Max last col for fetching	*/
54 #define MIN_COLS	20	/* Min no of cols to fetch	*/
55 #define MIN_FETCHLEN    50	/* Min length of fetched data */
56 #define MAX_FETCHLEN    20000	/* Max length of fetched data */
57 
58 #define OK_MESSAGE	"OK: no messages"
59 #define ERR_MESSAGE	"ERROR: fetchlog: "
60 
61 /* suffix for temp bookmarkfile:  mkstemp() template */
62 #define FETCH_FILE_SUFFIX "XXXXXX"
63 
64 /* conversion flags */
65 #define CONV_NONE	0
66 #define CONV_BRACKET	1
67 #define CONV_PERCENT	2
68 #define CONV_NEWLINE	4
69 #define CONV_OKMSG	8
70 #define CONV_SHELL	16
71 #define CONV_NAGIOS3	32
72 
73 /* return/exit codes */
74 #define RET_OK		0	/* ok, no messages		ok */
75 #define RET_ERROR	1	/* internal error 		warn */
76 #define RET_NEWMSG	2	/* logfile has new messages	critical */
77 #define RET_PARAM	3	/* wrong parameter, print help */
78 
79 /* version info */
80 #define FL_VERSION FETCHLOG_VERSION_NO
81 
82 /*************************************************
83  * typedefs
84  *************************************************/
85 typedef struct {
86     long last;		/* pos lastchar fetched plus 1 (=first new char) */
87     time_t mtime;	/* mtime of logfile when this bookmark was valid */
88     ino_t inode;	/* inode number of logfile */
89 } bookmark;
90 
91 /*************************************************
92  * prototypes
93  *************************************************/
94 void usage( void );
95 int fetch_logfile( char *logfilename, char *bookmarkfile, int updbm_flag );
96 int read_bookmark( char *bmfile, bookmark *bm );
97 int write_bookmark( char *bmfile, bookmark *bm );
98 int copyline( int opos, char *obuf, char *ipt, int illen );
99 int check_farg( char *farg, int *conv );
100 void perr( char *msg1, char *msg2, int err );
101 
102 /*************************************************
103  * globals
104  *************************************************/
105 
106 /* fetching */
107 int firstcol_G = MIN_FIRSTCOL;
108 int lastcol_G = MAX_LASTCOL;
109 int fetchlen_G = MAX_FETCHLEN;
110 int conv_G = CONV_NONE;
111 #ifdef HAS_REGEX
112 regex_t *rx_G = NULL;
113 int numrx_G  = 0;
114 
115 #define RXERRBUFLEN 1000
116 char rxerrbuf_G[RXERRBUFLEN];
117 
118 #endif
119 
120 /*************************************************
121  * code...
122  *************************************************/
123 
124 /************************************************
125  * perr( .. )
126  * print error message to stdout with respect to fetchlen_G
127  ************************************************
128  */
perr(char * msg1,char * msg2,int err)129 void perr( char *msg1, char *msg2, int err ) {
130     char *msg = NULL;
131     int len = 0;
132     int r;
133 
134     if( !msg1 ) return;
135 
136     len = sizeof( ERR_MESSAGE ) + strlen( msg1 ) + 1;	/* 1 == '\n' */
137     if( msg2 ) len += strlen( msg2 ) + 2;		/* 2 == ': ' */
138     if( err ) len += strlen( strerror( err ) ) + 2;	/* 2 == ': ' */
139 
140     if( (msg=malloc( len ) ) == NULL ) { exit( RET_ERROR ); }
141 
142     strcpy( msg, ERR_MESSAGE );
143     strcat( msg, msg1 );
144     if( msg2 ) {
145 	strcat( msg, ": " );
146 	strcat( msg, msg2 );
147     }
148     if( err ) {
149 	strcat( msg, ": " );
150 	strcat( msg, strerror( err ) );
151     }
152     if( len-1 <= fetchlen_G ) {
153 	strcat( msg, "\n" );
154     }else{
155 	msg[fetchlen_G-1] = '\n';
156 	msg[fetchlen_G-2] = '~';
157 	len = fetchlen_G + 1;
158     }
159     r = write( STDOUT_FILENO, msg, len-1 );
160     free( msg );
161 }
162 
163 /************************************************
164  * read_bookmark( char *bmfile, bookmark *bm );
165  * return: 0: ok    1: error
166  ************************************************
167  */
read_bookmark(char * bmfile,bookmark * bm)168 int read_bookmark( char *bmfile, bookmark *bm ) {
169     int fd = -1;
170     struct stat sb;
171 
172     fd = open( bmfile, O_RDONLY );
173     if( fd == -1 ) {
174 	if( errno == ENOENT ) {
175 	    /* no bookmarkfile --> acts like infinite old bookmarkfile */
176 	    bm->last = -1;
177 	    bm->mtime = 0;
178 	    bm->inode = 0;
179 	    return 0;
180 	}else{
181 	    perr( "open", bmfile, errno );
182 	    return 1;
183 	}
184     }
185     if( fstat( fd, &sb ) == -1 ) {
186 	perr( "stat", bmfile, errno );
187 	close( fd );
188 	return 1;
189     }
190     if( (int)sb.st_size != sizeof(bookmark) ||
191 	((sb.st_mode & S_IFMT) != S_IFREG) )
192     {
193 	perr( "no file/wrong size", bmfile, 0);
194 	close( fd );
195 	return 1;
196     }
197     if( read( fd, bm, sizeof(bookmark)) != sizeof( bookmark ) ) {
198 	perr( "file to short", bmfile, 0 );
199 	close( fd );
200 	return 1;
201     }
202     close( fd );
203 
204     return 0;  /* ok */
205 
206 }   /* read_bookmark() */
207 
208 
209 /************************************************
210  * write_bookmark( char *bmfile, bookmark *bm );
211  * return: 0: ok    1: error
212  ************************************************
213  */
write_bookmark(char * bmfile,bookmark * bm)214 int write_bookmark( char *bmfile, bookmark *bm ) {
215 
216     char *nbmfile = NULL; /* new bmfile (a tmp file to be renamed to bmfile) */
217     int nbmfd = -1;
218 
219     nbmfile = (char*)malloc( strlen(bmfile) + sizeof(FETCH_FILE_SUFFIX) );
220     if( nbmfile == NULL ) {
221 	perr("malloc", NULL, errno );
222 	return 1;
223     }
224     strcpy( nbmfile, bmfile );
225     strcat( nbmfile, FETCH_FILE_SUFFIX );
226     nbmfd = mkstemp( nbmfile );
227     if( nbmfd == -1 ) {
228 	perr( "mkstemp", nbmfile, errno );
229 	free( nbmfile );
230 	return 1;
231     }
232     if( write( nbmfd, bm, sizeof( bookmark ) ) != sizeof( bookmark )  ) {
233 	perr( "write", nbmfile, errno );
234 	close( nbmfd );
235 	unlink( nbmfile );
236 	free( nbmfile );
237 	return 1;
238     }
239     close( nbmfd );
240     if( rename( nbmfile, bmfile ) == -1 ) {
241 	perr( "rename tmpfile to", bmfile, errno );
242 	unlink( nbmfile );
243 	free( nbmfile );
244 	return 1;
245     }
246     free( nbmfile );
247     return 0;	/* ok */
248 
249 }   /* write_bookmark() */
250 
251 
252 
253 /************************************************
254  * fetch_logfile( char *logfilename, char *bookmarkfile, int updbm_flag )
255  ************************************************
256  */
fetch_logfile(char * logfile,char * bmfile,int updbm_flag)257 int fetch_logfile( char *logfile, char *bmfile, int updbm_flag ) {
258 
259     bookmark obm, nbm;		/* old, new bookmark */
260 
261     char *ibuf = NULL;		/* input buf (--> the logfile) */
262     size_t ilen = 0;
263     char *ipt  = NULL;
264     char *bmpt = NULL;		/* points to first new char if bm exists */
265 
266     char *obuf = NULL;		/* output buf (filled backwards) */
267     int opos;			/* first used char pos in obuf */
268 
269     				/* for CONV_NAGIOS3 */
270     int lastlinepos = 0;	/*   pos of beginning of lastline in obuf */
271     int lastlinelen = 0;	/*   len of lastline, excl. trailing '\' 'n' */
272 
273     int r;			/* write's returncode */
274 
275     int i;
276     int fd = -1;
277     struct stat sb;
278     char *lgfile = NULL;
279     int llen = 0;
280     int done = 0;
281 
282     if( read_bookmark( bmfile, &obm ) ) return RET_ERROR;
283 
284     if( (fd=open( logfile, O_RDONLY ))  == -1 ) {
285 	perr( "open", logfile, errno );
286 	return RET_ERROR;
287     }
288     if( fstat( fd, &sb ) == -1 ) {
289 	perr( "stat", logfile, errno );
290 	close( fd );
291 	return RET_ERROR;
292     }
293 
294     nbm.last  = (size_t) sb.st_size;
295     nbm.mtime = sb.st_mtime;
296     nbm.inode = sb.st_ino;
297 
298     /* something changed meanwhile ? */
299     if( obm.mtime==nbm.mtime && obm.inode==nbm.inode && obm.last==nbm.last ) {
300 	if( conv_G & CONV_OKMSG )
301 	    r = write( STDOUT_FILENO, OK_MESSAGE "\n", sizeof( OK_MESSAGE ) );
302 	close( fd );
303 	return RET_OK;
304     }
305 
306     /*****************/
307 
308     obuf = malloc( fetchlen_G+1 );
309     if( obuf == NULL ) {
310 	perr( "malloc", NULL,  errno );
311 	close( fd );
312 	return RET_ERROR;
313     }
314     opos = fetchlen_G;
315     *(obuf + opos) = '\0';	/* dummy: opos -> first used char in obuf */
316     if( conv_G & CONV_NEWLINE ) {
317 	/* when using CONV_NEWLINE the obuf is filled up like this:
318 	     1. init: write '\\'  'n'  '\n' to the end (3 chars)
319 	     2. fill (copyline() ) by first prepend line contents and
320 	     	then prepend '\\' 'n'
321 	     result: An additional '\\'  'n'  at the beginning
322 	   else
323 	     1. fill (copyline() ) by first prepend newline and then prepend
324 	     	line contents
325 	*/
326 	*(obuf + --opos) = '\n';
327 	*(obuf + --opos) = 'n';
328 	*(obuf + --opos) = '\\';
329     }
330 
331     lgfile = (char*)malloc( strlen(logfile) + sizeof(".X") );
332     if( lgfile == NULL ) {
333 	free( obuf );
334 	perr( "malloc", NULL, errno );
335 	close( fd );
336 	return RET_ERROR;
337     }
338 
339     /* read in all logfiles and backward fill obuf upto fetchlen_G chars */
340 
341     for( i=-1; i<10; i++ ) {
342 	/* i==-1: logfile without suffix, else i==logfile suffix */
343 	if( i==-1 ) {
344 	    /* lgfile is already open and sb contains the stat */
345 	    strcpy( lgfile, logfile );
346 	}else{
347 	    sprintf( lgfile, "%s.%1d", logfile, i );
348 
349 	    if( (fd=open( lgfile, O_RDONLY )) == -1 ) {
350 		if( errno==ENOENT && i==0 ) {
351 		    continue;           /* some logrotator start with .1 */
352 		}else if( errno==ENOENT && i>0 ) {
353 		    break;
354 		}else{
355 		    perr( "open", lgfile, errno );
356 		    free( obuf ); free( lgfile );
357 		    return RET_ERROR;
358 		}
359 	    }
360 	    if( fstat( fd, &sb ) == -1 ) {
361 		perr( "stat", lgfile, errno );
362 		free( obuf ); free( lgfile ); close( fd );
363 		return RET_ERROR;
364 	    }
365 	}
366 
367 	ilen = (size_t) sb.st_size;
368 
369 	if( ilen == 0 ) {
370 	    close( fd );
371 	    if( obm.inode == sb.st_ino ) break;
372 	    continue;
373 	}
374 
375 	ibuf = mmap( NULL, ilen, PROT_READ, MAP_SHARED, fd, (off_t)0 );
376 	if( ibuf == MAP_FAILED ) {
377 	    perr( "mmap", lgfile, errno );
378 	    free( obuf ); free( lgfile ); close( fd );
379 	    return RET_ERROR;
380 	}
381 
382 #ifdef HAS_MADVISE
383 	if( madvise( ibuf, ilen, MADV_RANDOM ) ) {
384 	    perr( "madvise", NULL, errno );
385 	    free( obuf ); free( lgfile ); close( fd );
386 	    munmap( ibuf, ilen );
387 	    return RET_ERROR;
388 	}
389 #endif
390 
391 	/* check for old bookmark */
392 	bmpt = NULL;
393 	if( obm.inode == sb.st_ino ) {
394 	    bmpt = ibuf+obm.last;
395 	}
396 
397 	/* scan backwards for lines but the first */
398 	done = 0;
399 	for( llen=1,ipt=ibuf+ilen-2; ipt>=ibuf; llen++,ipt-- ) {
400 	    if( *ipt=='\n' ) {
401 		if( ipt+1<bmpt ) { done=1; break; }
402 		opos = copyline( opos, obuf, ipt+1, llen );
403 		if( opos==0 ) { done=1; break; }
404 		llen = 0;
405 	    }
406 	}
407 	/* copy first line ? */
408 	if( ipt+1==ibuf && done==0 ) {
409 	    if( ipt+1<bmpt ) { done=1; }
410 	    else{ opos = copyline( opos, obuf, ipt+1, llen ); }
411 	    if( opos==0 ) { done=1; }
412 	}
413 
414 	munmap( ibuf, ilen );
415 	close( fd );
416 	if( done ) break;
417 	if( bmpt ) break;	/* processed a bookmarked file? --> finito */
418     }
419 
420     if( updbm_flag ) {
421 	if( write_bookmark( bmfile, &nbm ) ) return RET_ERROR;
422     }
423 
424     /* if in Nagios3 mode: prepend short message (the last line fetched) */
425     if( conv_G & CONV_NAGIOS3 ) {
426 	/* Nagios2 --> Nagios3 changed accepted format for plugin output.
427 	 * Nagios2 accepted a line containing one or more '\'+'n' as a single
428 	 * line. Nagios3 now supports multiline output, and as a result, lines
429 	 * containing '\'+'n' are now handled as multiline messages (as well as
430 	 * messages having '\n'). The new multiline format is:
431 	 *    SHORT_MESSAGE | OPTIONAL_PERFORMANCE_DATA
432 	 *    LONG_MESSAGE_LINE_1
433 	 *    LONG_MESSAGE_LINE_2
434 	 *    	    ...
435 	 *    LONG_MESSAGE_LINE_N
436 	 *
437 	 * In Nagios3 mode fetchlog copies LONG_MESSAGE_LINE_N as SHORT_MESSAGE
438 	 * and leaves OPTIONAL_PERFORMANCE_DATA empty
439 	 */
440 
441 	int oidx = fetchlen_G - 4; 	/* in obuf: last char, last line */
442 	lastlinepos = oidx + 1;		/* fallback value: empty line at end */
443 	lastlinelen = 0;		/* fallback value: empty line at end */
444 
445 	/* determine lastlinepos and lastlinelen */
446 	while( oidx > 0 ) {
447 	    if( *(obuf + oidx) == 'n' && *(obuf + oidx -1) == '\\' ) {
448 		lastlinepos = oidx + 1;
449 		lastlinelen = fetchlen_G - lastlinepos - 3;  /* 3: \ n \n */
450 		break;
451 	    }
452 	    oidx--;
453 	}
454 
455 	/* case: obuf has enough room for SHORT_MESSAGE */
456 	if( lastlinelen + 1 <= opos ) {		/* +1 = '|' */
457 	    *(obuf + --opos) = '|';
458 	    memmove( obuf+opos-lastlinelen, obuf+lastlinepos, lastlinelen );
459 	    opos -= lastlinelen;
460 
461 	/* case: obuf too small: SHORT_MESSAGE and fetched messages overlap,
462 	 *       but not LONG_MESSAGE_LINE_N */
463 	}else if( lastlinelen + 6 <= lastlinepos ) { /* +6 = '|\n...' */
464 	    memmove( obuf, obuf+lastlinepos, lastlinelen );
465 	    *(obuf + lastlinelen + 0 ) = '|';
466 	    *(obuf + lastlinelen + 1 ) = '\\';
467 	    *(obuf + lastlinelen + 2 ) = 'n';
468 	    *(obuf + lastlinelen + 3 ) = '.';
469 	    *(obuf + lastlinelen + 4 ) = '.';
470 	    if( *(obuf + lastlinelen + 5 ) != '\\' ) {
471 	       	*(obuf + lastlinelen + 5 ) = '.';
472 	    }
473 	    opos = 0;
474 
475 	/* case: obuf too small: SHORT_MESSAGE and fetched messages overlap,
476 	 *       including LONG_MESSAGE_LINE_N */
477 	}else{
478 	    memmove( obuf+lastlinepos+2, obuf+lastlinepos, lastlinelen );
479 	    opos = lastlinepos + 2;
480 	}
481     }
482 
483     /* only return a message if there is something to print */
484     if( ((conv_G & CONV_NEWLINE)==0 && fetchlen_G-opos==0 ) ||
485         ((conv_G & CONV_NEWLINE)!=0 &&
486 	    ( ((conv_G & CONV_NAGIOS3)== 0 && fetchlen_G-opos==3 ) ||
487 	      ((conv_G & CONV_NAGIOS3)!= 0 && fetchlen_G-opos==4 )    )  ) ) {
488 
489 	if( conv_G & CONV_OKMSG ) {
490 	    r = write( STDOUT_FILENO, OK_MESSAGE "\n", sizeof( OK_MESSAGE ) );
491 	}
492 	i = RET_OK;
493     }else{
494 	r = write( STDOUT_FILENO, obuf+opos,fetchlen_G-opos);
495 	i = RET_NEWMSG;
496     }
497 
498     free( obuf ); free( lgfile );
499     return i;
500 
501 }   /* fetch_logfile() */
502 
503 
504 /************************************************
505  * copyline( int opos, char *obuf, char *ipt, int illen );
506  * trim new line (ipt, illen), copy it to obuf, convert it, return new opos
507  ************************************************
508  */
copyline(int opos,char * obuf,char * ipt,int illen)509 int copyline( int opos, char *obuf, char *ipt, int illen ) {
510 
511     char *p = NULL;
512     int len = 0;
513     int i;
514 #ifdef HAS_REGEX
515     int rxerr = 0;
516     char rxbuf[MAX_LASTCOL+1];	/* +1 for extra '\0' */
517 
518     if( numrx_G > 0 ) {
519 	/* we have to copy the line to another buffer because regexec()
520 	   wants a c-string, and we only have a \n terminated line in ipt.
521 	   Some platforms (e.g. FreeBSD) have a non-portable extension in
522 	   regcomp() which allows to regex non-null terminated string slices,
523 	   but to be portable...
524 	 */
525 	if( illen <= firstcol_G ) {
526 	    rxbuf[0] = '\0';
527 	}else{
528 	    if( illen > lastcol_G ) len = lastcol_G;
529 	    else 		    len = illen - 1;
530 	    len -= firstcol_G - 1;
531 	    memcpy( rxbuf, ipt+firstcol_G-1, len );
532 	    if( illen-1 > lastcol_G ) rxbuf[len-1] = '~';
533 	    rxbuf[len] = '\0';
534 	}
535 
536 	for( i=0; i<numrx_G; i++ ) {
537 	    rxerr = regexec( &(rx_G[i]), rxbuf, (size_t)0, NULL, 0);
538 	    if( rxerr == 0 )  break;	/* match */
539 	    /* else: no match (rxerr==REG_NOMATCH) or an error (ignored) */
540 	}
541 	if( rxerr != 0 ) return opos;
542     }
543 #endif
544 
545     if( conv_G & CONV_NEWLINE ) {
546 	/* fill obuf:
547 	   prepend  concat( '\\' + 'n' + iline )   (newline sequence first) */
548 	if( opos > 2 ) {
549 	    if( illen <= firstcol_G ) {
550 		*(obuf+opos-1) = 'n'; *(obuf+opos-2) = '\\';
551 		opos -= 2;
552 	    }else{
553 		if( illen > lastcol_G ) len = lastcol_G;
554 		else 		        len = illen - 1;
555 		len -= firstcol_G - 1;
556 		if( len+2 > opos ) {
557 		    memcpy( obuf+2, ipt+firstcol_G-1+len+2-opos, opos-2 );
558 		    len = opos - 2;
559 		}else{
560 		    memcpy( obuf+opos-len, ipt+firstcol_G-1, len );
561 		}
562 		if( illen-1 > lastcol_G ) *(obuf+opos-1) = '~';
563 		opos -= len+2;
564 		*(obuf+opos+1) = 'n';
565 		*(obuf+opos+0) = '\\';
566 	    }
567 	}else{
568 	    opos = 0;
569 	}
570 	if( opos==0 ) {
571 	    p = obuf;
572 	    *p++='\\'; *p++='n'; *p++='.'; *p++='.'; *p++='.';
573 	    if( obuf[5]=='n' ) { *p++='.'; }
574 	}
575     }else{
576 	/* without newline conversion */
577 
578 	/* fill obuf:
579 	   prepend concat( iline + '\n' )  (newline char last) */
580 	if( opos > 1 ) {
581 	    if( illen <= firstcol_G ) {
582 		*(obuf+opos-1) = '\n';
583 		opos -= 1;
584 	    }else{
585 		if( illen > lastcol_G ) len = lastcol_G;
586 		else 		        len = illen - 1;
587 		len -= firstcol_G - 1;
588 		if( len+1 > opos ) {
589 		    memcpy( obuf, ipt+firstcol_G-1+len+1-opos, opos-1 );
590 		    len = opos - 1;
591 		}else{
592 		    memcpy( obuf+opos-len-1, ipt+firstcol_G-1, len );
593 		}
594 		*(obuf+opos-1) = '\n';
595 		if( illen-1 > lastcol_G ) *(obuf+opos-2) = '~';
596 		opos -= len+1;
597 	    }
598 	}else{
599 	    opos = 0;
600 	}
601 	if( opos==0 ) {
602 	    p = obuf;
603 	    *p++='.'; *p++='.'; *p++='.';
604 	}
605     }
606 
607     p = obuf+opos;
608     if( conv_G & CONV_NEWLINE )  p += 2;	/* +2: skip '\\' 'n' */
609 
610     if( conv_G & CONV_PERCENT ) {
611 	for( i=0; i<len; i++ ) {
612 	    if( *(p+i) == '%' ) *(p+i) = 'p';
613 	}
614     }
615 
616     if( conv_G & CONV_BRACKET ) {
617 	for( i=0; i<len; i++ ) {
618 	    if(      *(p+i) == '<' ) *(p+i) = '(';
619 	    else if( *(p+i) == '>' ) *(p+i) = ')';
620 	}
621     }
622 
623     if( conv_G & CONV_SHELL ) {
624 	for( i=0; i<len; i++ ) {
625 	    if(      *(p+i) == '$' )  *(p+i) = '_';
626 	    else if( *(p+i) == '\'' ) *(p+i) = '_';
627 	    else if( *(p+i) == '\"' ) *(p+i) = '_';
628 	    else if( *(p+i) == '`' )  *(p+i) = '_';
629 	    else if( *(p+i) == '^' )  *(p+i) = '_';
630 	    else if( *(p+i) == '\\' ) *(p+i) = '/';
631 	    else if( *(p+i) == '|' )  *(p+i) = '_';
632 	}
633     }
634 
635     return opos;
636 
637 }   /* copyline() */
638 
639 
640 /************************************************
641  * check_farg( char *farg )
642  * check if -f arg is in proper format for sccanf(): nnn:nnn:nnnn:XXXXX
643  ************************************************
644  */
check_farg(char * farg,int * conv)645 int check_farg( char *farg, int *conv ) {
646     char *pt = farg;
647     int numdig = 0;
648     int numc = 0;
649 
650     *conv = 0;
651 
652     for( numdig=0 ; *pt; pt++ ) {
653 	if( isdigit( (int) *pt ) ) numdig++;
654 	else if( *pt==':' ) break;
655 	else return 1;
656     }
657     if( numdig <1 || numdig > 3 ) return 1;
658 
659     for( pt++,numdig=0 ; *pt; pt++ ) {
660 	if( isdigit( (int) *pt ) ) numdig++;
661 	else if( *pt==':' ) break;
662 	else return 1;
663     }
664     if( numdig <1 || numdig > 3 ) return 1;
665 
666     for( pt++,numdig=0 ; *pt; pt++ ) {
667 	if( isdigit( (int) *pt ) ) numdig++;
668 	else if( *pt==':' ) break;
669 	else return 1;
670     }
671     if( numdig <1 || numdig > 5 ) return 1;
672 
673     for( pt++,numc=0 ; *pt; pt++ ) {
674 	if     ( *pt=='b' ) { *conv |= CONV_BRACKET; numc++; }
675 	else if( *pt=='p' ) { *conv |= CONV_PERCENT; numc++; }
676 	else if( *pt=='n' ) { *conv |= CONV_NEWLINE; numc++; }
677 	else if( *pt=='o' ) { *conv |= CONV_OKMSG;   numc++; }
678 	else if( *pt=='s' ) { *conv |= CONV_SHELL;   numc++; }
679 	else if( *pt=='3' ) { *conv |= CONV_NAGIOS3; numc++; }
680 	else return 1;
681     }
682     if( numc > 6 ) return 1;
683     return 0;	/* ok */
684 
685 }   /* check_farg() */
686 
687 /*************************************************/
usage(void)688 void usage( void ) {
689 
690     printf(
691 	"fetchlog - fetch the last new log messages - version %s\n\n"
692 	"   usage 1: fetchlog -f firstcol:lastcol:len:conv logfile bmfile [pattern ...]\n"
693 	"   usage 2: fetchlog -F firstcol:lastcol:len:conv logfile bmfile [pattern ...]\n"
694 	"   usage 3: fetchlog [ -V | -h ] \n\n"
695 
696 	"1: Read all messages of <logfile> [matching any of regex <pattern>] which are\n"
697 	"newer than the bookmark in <bmfile>. Print at most <len> bytes from column\n"
698 	"<firstcol> upto column <lastcol> to stdout. Adds '...' characters when skip-\n"
699 	"ping old lines or '~' when cutting long lines. <conv> sets output conversion:\n"
700 	"      'b': convert < and > to ( and ) for safe HTML output\n"
701 	"      'p': convert %% to p for safe printf output\n"
702 	"      's': convert $'\"`^\\| to _____/_ for safe shell parameter input\n"
703 	"      'n': convert newline to \\n for single line output\n"
704 	"      'o': show '%s' message if no new messages\n"
705 	"      '3': output in format compatible with Nagios3 plugins (enables 'no')\n"
706 	"  0  <  <firstcol>  <  <lastcol>  <  %d\n"
707 	"  <lastcol> - <firstcol> > %d \n"
708 	"  <len> valid range %d..%d\n"
709 	"  <conv> is zero or more of 'bpsno3' \n"
710 	"  <logfile> absolute path to logfile\n"
711 	"  <bmfile> absolute path to bookmarkfile\n"
712 	"2: like 1 and update <bmfile> to remember fetched messages as 'read'\n"
713 	"3: print version (-V) or print this help message (-h) \n"
714 	, FL_VERSION,
715 	OK_MESSAGE,MAX_LASTCOL+1,MIN_COLS-1,MIN_FETCHLEN, MAX_FETCHLEN
716 	);
717 
718 } /* usage() */
719 
720 /*************************************************/
721 
main(int argc,char ** argv)722 int main(int argc, char **argv)
723 {
724     int  ret=0;
725     int  i;
726     int  rxerr;
727 
728     /* check args */
729     if (argc == 2) {
730 	if( argv[1][0] == '-' && argv[1][1] == 'V' ) {
731 	    printf( "fetchlog version %s \n", FL_VERSION );
732 	    exit( RET_PARAM );
733 	}
734     }else if (argc >= 5) {
735 	if( argv[1][0] == '-' &&
736 	    argv[3][0] == '/' && isalpha((int)argv[3][1]) &&
737 	    argv[4][0] == '/' && isalpha((int)argv[4][1]) ) {
738 	    if( argv[1][1] == 'f' || argv[1][1] == 'F' ) {
739 		if( check_farg( argv[2], &conv_G ) ) {
740 		    perr( "invalid parameter", "firstcol, lastcol, len or conv", 0 );
741 		    exit( RET_PARAM );
742 		}
743 		ret = sscanf( argv[2], "%3d:%3d:%5d",
744 			      &firstcol_G, &lastcol_G, &fetchlen_G );
745 		if( ret != 3 ) {
746 		    perr( "invalid parameter", NULL, 0 );
747 		    exit( RET_PARAM );
748 		}
749 		if( conv_G & CONV_NAGIOS3 ) {
750 		    /* nagios3: auto enable conversion 'okmsg' and 'newline' */
751 		    conv_G |= (CONV_NEWLINE | CONV_OKMSG);
752 		}
753 		if( firstcol_G >= MIN_FIRSTCOL &&
754 		    lastcol_G >= firstcol_G + MIN_COLS - 1 &&
755 		    lastcol_G <= MAX_LASTCOL &&
756 		    fetchlen_G >= MIN_FETCHLEN &&
757 		    fetchlen_G <= MAX_FETCHLEN ) {
758 
759 		    /* prepare regex, if any */
760 		    if( argc > 5 ) {
761 #ifdef HAS_REGEX
762 			numrx_G = argc - 5;
763 			rx_G = (regex_t*)malloc(numrx_G*sizeof(regex_t));
764 			if( rx_G == NULL ) {
765 			    perr("malloc", NULL, errno );
766 			    exit( RET_ERROR );
767 			}
768 			for( i=5; i<argc; i++ ) {
769 			    rxerr = regcomp( &(rx_G[i-5]), argv[i],
770 					     REG_EXTENDED | REG_NOSUB );
771 			    if( rxerr ) {
772 				regerror( rxerr, &(rx_G[i-5]),
773 					  rxerrbuf_G, RXERRBUFLEN );
774 				perr("regex", rxerrbuf_G, 0 );
775 				exit( RET_ERROR );
776 			    }
777 			}
778 #else
779 			i = 0; rxerr = 0;
780 			perr("regex", "not supported on this platform", 0 );
781 			exit( RET_ERROR );
782 #endif
783 		    }
784 		    if( argv[1][1] == 'f' ) {
785 			exit( fetch_logfile( argv[3], argv[4], 0 ) );
786 		    }else{
787 			exit( fetch_logfile( argv[3], argv[4], 1 ) );
788 		    }
789 		}else{
790 		    perr( "out of range: firstcol, lastcol or len", NULL, 0 );
791 		    exit( RET_PARAM );
792 		}
793 	    }
794 	}
795     }
796     usage();
797     return RET_PARAM;
798 
799 }   /* main() */
800 
801 /*
802  * CVS/RCS Log:
803  * $Log: fetchlog.c,v $
804  * Revision 1.9  2010/07/01 16:39:25  afrika
805  * - bugfix: wrong exitcode when fetching with regex and logfile has changed
806  * - new: test-all and make file target for this
807  *
808  * Revision 1.8  2010/06/18 18:17:47  afrika
809  * - new option '3' for nagios3 compatible (multiline) output
810  * - Added sample configs for Nagios 2 + 3
811  * - Updated README.Nagios to Nagios 2 + 3
812  * - Scanning for rotated logfiles now silently skips over to .1 if
813  *   .0 is missing because some rotation tools start indexing with .1
814  * - fixed: compiler warnings on Debian Linux
815  *
816  * Revision 1.7  2008/11/21 19:58:51  afrika
817  * - update docs
818  * - now '|' is shell critical character also
819  * - added Greg Baker's files for building a .depot on hpux
820  *
821  * Revision 1.6  2004/03/26 19:46:03  afrika
822  * added regex pattern matching
823  *
824  * Revision 1.5  2003/11/19 15:24:19  afrika
825  * only return "new message" if there is at least one char to put out
826  *
827  * Revision 1.4  2003/11/18 18:44:23  afrika
828  * - removed compile option pre 0.93 exit codes
829  * - no longer use copy of last line fetched to find bookmark location
830  * - use inode to identify a (rotated) logfile instead
831  * - handling of empty (and rotated) logfiles now correct
832  *
833  * Revision 1.3  2002/12/17 18:40:05  afrika
834  * exit code now nagios compatible
835  *
836  * Revision 1.2  2002/12/17 18:04:48  afrika
837  * - inserted CVS tags
838  * - change docs: Netsaint --> Nagios(TM)
839  *
840  *
841  */
842 
843