xymon-4.3.7/0000775000175000017500000000000011671641716012235 5ustar henrikhenrikxymon-4.3.7/build/0000775000175000017500000000000011671641716013334 5ustar henrikhenrikxymon-4.3.7/build/test-pathmax.c0000664000175000017500000000033611070452713016107 0ustar henrikhenrik#include #include #include int main(int argc, char *argv[]) { long res; #ifndef PATH_MAX res = pathconf("/", _PC_PATH_MAX); printf("#define PATH_MAX %ld\n", res); #endif return 0; } xymon-4.3.7/build/lfs.sh0000775000175000017500000000163111667435061014457 0ustar henrikhenrik echo "Checking for Large File Support ..." # Solaris is br0ken when it comes to LFS tests. # See http://lists.xymon.com/archive/2011-November/033216.html if test "`uname -s`" = "SunOS"; then echo "Large File Support assumed OK on Solaris" exit 0 fi cd build OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-lfs clean OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-lfs 2>/dev/null if [ $? -ne 0 ]; then echo "ERROR: Compiler doesnt recognize the off_t C type." exit 1 fi STDRES="`./test-lfs-std 4`" if test "$STDRES" != "4:1:0" -a "$STDRES" != "8:1:0"; then echo "ERROR: LFS support check failed for standard file support" exit 1 fi LFSRES="`./test-lfs-lfs 8`" if test "$LFSRES" != "8:1:0"; then echo "ERROR: LFS support check failed for large file support" exit 1 fi echo "Large File Support OK" OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-lfs clean cd .. xymon-4.3.7/build/renametasks.c0000664000175000017500000001053511535424634016016 0ustar henrikhenrik#include #include #include #include int main(int argc, char **argv) { char buf[10240]; while (fgets(buf, sizeof(buf), stdin)) { char *dlim; dlim = (*buf == '[') ? strchr(buf, ']') : NULL; if (dlim != NULL) { *dlim = '\0'; if (strcmp(buf+1, "hobbitd") == 0) printf("[xymond]%s", dlim+1); else if (strcmp(buf+1, "bbstatus") == 0) printf("[storestatus]%s", dlim+1); else if (strcmp(buf+1, "bbhistory") == 0) printf("[history]%s", dlim+1); else if (strcmp(buf+1, "hostdata") == 0) printf("[hostdata]%s", dlim+1); else if (strcmp(buf+1, "bbdata") == 0) printf("[storedata]%s", dlim+1); else if (strcmp(buf+1, "bbnotes") == 0) printf("[storenotes]%s", dlim+1); else if (strcmp(buf+1, "bbenadis") == 0) printf("[storeenadis]%s", dlim+1); else if (strcmp(buf+1, "bbpage") == 0) printf("[alert]%s", dlim+1); else if (strcmp(buf+1, "rrdstatus") == 0) printf("[rrdstatus]%s", dlim+1); else if (strcmp(buf+1, "larrdstatus") == 0) printf("[rrdstatus]%s", dlim+1); else if (strcmp(buf+1, "rrddata") == 0) printf("[rrddata]%s", dlim+1); else if (strcmp(buf+1, "larrddata") == 0) printf("[rrddata]%s", dlim+1); else if (strcmp(buf+1, "clientdata") == 0) printf("[clientdata]%s", dlim+1); else if (strcmp(buf+1, "bbproxy") == 0) printf("[xymonproxy]%s", dlim+1); else if (strcmp(buf+1, "hobbitfetch") == 0) printf("[xymonfetch]%s", dlim+1); else if (strcmp(buf+1, "bbdisplay") == 0) printf("[xymongen]%s", dlim+1); else if (strcmp(buf+1, "bbcombotest") == 0) printf("[combostatus]%s", dlim+1); else if (strcmp(buf+1, "bbnet") == 0) printf("[xymonnet]%s", dlim+1); else if (strcmp(buf+1, "bbretest") == 0) printf("[xymonnetagain]%s", dlim+1); else if (strcmp(buf+1, "hobbitclient") == 0) printf("[xymonclient]%s", dlim+1); else { *dlim = ']'; printf("%s", buf); } continue; } dlim = buf + strspn(buf, " \t"); if (strncasecmp(dlim, "NEEDS", 5) == 0) { char *nam; char savchar; nam = dlim + 5; nam += strspn(nam, " \t"); dlim = nam + strspn(nam, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefhijklmnopqrstuvwxyz"); savchar = *dlim; *dlim = '\0'; if (strcmp(nam, "hobbitd") == 0) { *nam = '\0'; printf("%sxymond%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbstatus") == 0) { *nam = '\0'; printf("%sstorestatus%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbhistory") == 0) { *nam = '\0'; printf("%shistory%c%s", buf, savchar, dlim); } else if (strcmp(nam, "hostdata") == 0) { *nam = '\0'; printf("%shostdata%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbdata") == 0) { *nam = '\0'; printf("%sstoredata%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbnotes") == 0) { *nam = '\0'; printf("%sstorenotes%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbenadis") == 0) { *nam = '\0'; printf("%sstoreenadis%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbpage") == 0) { *nam = '\0'; printf("%salert%c%s", buf, savchar, dlim); } else if (strcmp(nam, "rrdstatus") == 0) { *nam = '\0'; printf("%srrdstatus%c%s", buf, savchar, dlim); } else if (strcmp(nam, "larrdstatus") == 0) { *nam = '\0'; printf("%srrdstatus%c%s", buf, savchar, dlim); } else if (strcmp(nam, "rrddata") == 0) { *nam = '\0'; printf("%srrddata%c%s", buf, savchar, dlim); } else if (strcmp(nam, "larrddata") == 0) { *nam = '\0'; printf("%srrddata%c%s", buf, savchar, dlim); } else if (strcmp(nam, "clientdata") == 0) { *nam = '\0'; printf("%sclientdata%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbproxy") == 0) { *nam = '\0'; printf("%sxymonproxy%c%s", buf, savchar, dlim); } else if (strcmp(nam, "hobbitfetch") == 0) { *nam = '\0'; printf("%sxymonfetch%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbdisplay") == 0) { *nam = '\0'; printf("%sxymongen%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbcombotest") == 0) { *nam = '\0'; printf("%scombostatus%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbnet") == 0) { *nam = '\0'; printf("%sxymonnet%c%s", buf, savchar, dlim); } else if (strcmp(nam, "bbretest") == 0) { *nam = '\0'; printf("%sxymonnetagain%c%s", buf, savchar, dlim); } else if (strcmp(nam, "hobbitclient") == 0) { *nam = '\0'; printf("%sxymonclient%c%s", buf, savchar, dlim); } else { *dlim = savchar; printf("%s", buf); } continue; } printf("%s", buf); } return 0; } xymon-4.3.7/build/bb-commands.sh0000775000175000017500000000243611535462534016060 0ustar henrikhenrik#!/bin/sh # $Id: bb-commands.sh 6650 2011-03-08 17:20:28Z storner $ # Script to pick up most of the commands used by BB extension scripts. # This is used during installation, to build a xymonserver.cfg that # includes these commands so that extension scripts can run from # xymonserver.cfg without having to do special setups. findbin() { MYP="`echo ${PATH} | sed -e 's/:/ /g'`" for D in $MYP do if test -x $D/$1; then echo "${D}/${1}" fi done } echo "" echo "# The following defines a bunch of commands that BB extensions expect to be present." echo "# Xymon does not use them, but they are provided here so if you use BB extension" echo "# scripts, then they will hopefully run without having to do a lot of tweaking." echo "" for CMD in uptime awk cat cp cut date egrep expr find grep head id ln ls mv rm sed sort tail top touch tr uniq who do ENVNAME=`echo $CMD | tr "[a-z]" "[A-Z]"` PGM=`findbin $CMD | head -n 1` echo "${ENVNAME}=\"${PGM}\"" done # WC is special PGM=`findbin wc | head -n 1` echo "WC=\"${PGM} -l\"" echo "WCC=\"${PGM}\"" # DFCMD is an alias for DF PGM=`findbin df | head -n 1` echo "# DF,DFCMD and PS are for compatibility only, NOT USED by the Xymon client" echo "DF=\"${PGM} -Pk\"" echo "DFCMD=\"${PGM} -Pk\"" echo "PS=\"ps ax\"" echo "" echo "MAXLINE=\"32768\"" xymon-4.3.7/build/Makefile.IRIX0000664000175000017500000000121311535462534015541 0ustar henrikhenrik# Xymon compile-time settings for a IRIX system # OSDEF = -DIRIX # NETLIBS: You may need to add -lresolv or similar to pick up network libraries NETLIBS = # Compile flags for normal build CC = cc CFLAGS = -g -O -D_REENTRANT $(OSDEF) $(LFSDEF) # Compile flags for debugging # CFLAGS = -g -DDEBUG -D_REENTRANT $(OSDEF) $(LFSDEF) # Extra environment settings for runtime stuff. # E.g. RUNTIMEDEFS="LD_LIBRARY_PATH=\"/opt/lib\"" to use # runtime libraries located in /opt/lib RUNTIMEDEFS = # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mail" xymon-4.3.7/build/Makefile.generic0000664000175000017500000000117511535462534016411 0ustar henrikhenrik# Xymon compile-time settings for a GENERIC system # OSDEF = -Dgeneric # NETLIBS: You may need to add -lresolv or similar to pick up network libraries NETLIBS = # Compile flags for normal build CC = cc CFLAGS = -g -O -D_REENTRANT $(OSDEF) # Compile flags for debugging # CFLAGS = -g -DDEBUG -D_REENTRANT $(OSDEF) # Extra environment settings for runtime stuff. # E.g. RUNTIMEDEFS="LD_LIBRARY_PATH=\"/opt/lib\"" to use # runtime libraries located in /opt/lib RUNTIMEDEFS = # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mail" xymon-4.3.7/build/Makefile.test-ldap0000664000175000017500000000033511070452713016657 0ustar henrikhenrikinclude Makefile.$(OS) test-compile: @$(CC) $(CFLAGS) $(LDAPINC) -o test-ldap.o -c test-ldap.c test-link: @$(CC) $(CFLAGS) $(LDAPLIB) -o test-ldap test-ldap.o -lldap $(LDAPLBER) clean: @rm -f test-ldap.o test-ldap xymon-4.3.7/build/test-snprintf.c0000664000175000017500000000036411070452713016311 0ustar henrikhenrik#include #include #include #include #include #include int main(int argc, char *argv[]) { char l[100]; snprintf(l, sizeof(l), "testing ... %d %d %d\n", 1, 2, 3); return 0; } xymon-4.3.7/build/makerpm.sh0000775000175000017500000000316111535462534015326 0ustar henrikhenrik#!/bin/sh REL=$1 if [ "$REL" = "" ]; then echo "Error - missing release number" exit 1 fi BASEDIR=`pwd` cd $BASEDIR rm -rf rpmbuild # Setup a temp. rpm build directory. mkdir -p rpmbuild/RPMS rpmbuild/RPMS/i386 rpmbuild/BUILD rpmbuild/SPECS rpmbuild/SRPMS rpmbuild/SOURCES cat >rpmbuild/.rpmmacros <rpmbuild/.rpmrc <rpmbuild/SPECS/xymon.spec cp rpm/xymon-init.d rpmbuild/SOURCES/ cp rpm/xymon.logrotate rpmbuild/SOURCES/ cp rpm/xymon-client.init rpmbuild/SOURCES/ cp rpm/xymon-client.default rpmbuild/SOURCES/ mkdir -p rpmbuild/xymon-$REL for f in xymongen xymonnet bbpatches xymonproxy build common contrib docs xymond web include lib client demotool do find $f/ | egrep -v "RCS|.svn" | cpio -pdvmu $BASEDIR/rpmbuild/xymon-$REL/ done cp -p Changes configure configure.server configure.client COPYING CREDITS README README.CLIENT RELEASENOTES $BASEDIR/rpmbuild/xymon-$REL/ find $BASEDIR/rpmbuild/xymon-$REL -type d|xargs chmod 755 cd rpmbuild #pushd xymon-$REL #make -f $HOME/xymon/Makefile.home distclean #popd tar zcf SOURCES/xymon-$REL.tar.gz xymon-$REL rm -rf xymon-$REL HOME=`pwd` rpmbuild -ba --clean SPECS/xymon.spec #rpm --addsign RPMS/i?86/xymon-$REL-*.i?86.rpm RPMS/i386/xymon-client-$REL-*.i?86.rpm SRPMS/xymon-$REL-*.src.rpm # mv RPMS/i?86/xymon-$REL-*.i?86.rpm RPMS/i?86/xymon-client-$REL-*.i?86.rpm SRPMS/xymon-$REL-*.src.rpm ../rpm/pkg/ xymon-4.3.7/build/Makefile.SunOS0000664000175000017500000000142411546036361015776 0ustar henrikhenrik# Xymon compile-time settings for SunOS / Solaris OSDEF = -DSunOS # Solaris need this NETLIBS = -lresolv -lsocket -lnsl # Compile flags for normal build CC = gcc CFLAGS = -g -O2 -Wall -Wno-unused -D_REENTRANT $(LFSDEF) $(OSDEF) # This guesswork doesnt work on a lot of systems. # Better have a run-time issue that can easily be fixed with "crle" # or by setting LD_LIBRARY_PATH, than a build-time problem. #LDTYPE := $(shell ld -V 2>&1|head -1|cut -d' ' -f1) #ifeq ($(LDTYPE),GNU) # RPATH=-Wl,--rpath, #else # RPATH=-Wl,-R #endif # Compile flags for debugging # CFLAGS = -g -DDEBUG -Wall -D_REENTRANT $(LFSDEF) $(OSDEF) # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mailx" xymon-4.3.7/build/Makefile.OSX0000664000175000017500000000074511535462534015450 0ustar henrikhenrik# Xymon compile-time settings for Darwin systems # Contributed by "Marc" # NETLIBS: None needed NETLIBS = # Compile flags for normal build CC = cc CFLAGS = -g -O2 -Wall -Wno-unused -D_REENTRANT -D_BSD_SOCKLEN_T_=int $(LFSDEF) $(OSDEF) # Compile flags for debugging # CFLAGS = -g -DDEBUG -Wall -D_REENTRANT $(LFSDEF) $(OSDEF) # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mail" xymon-4.3.7/build/Makefile.HP-UX0000664000175000017500000000230011535462534015625 0ustar henrikhenrik# Xymon compile-time settings for a HP-UX system OSDEF = -DHPUX # NETLIBS: You may need to add -lresolv or similar to pick up network libraries NETLIBS = -lnsl # Compile flags for normal build # NOTE: HP-UX built-in compiler is horribly broken and will not compile Xymon. # So you should use the GNU compiler, gcc. CC = gcc # NOTE: Some HP-UX 11i systems have a severely broken set of include files. This # will typically show up when compiling the "xymonnet/xymonnet.c" where it bombs with # xymonnet.c: In function 'send_rpcinfo_results': # xymonnet.c:1794: warning: assignment makes pointer from integer without a cast # xymonnet.c:1801: error: dereferencing pointer to incomplete type # xymonnet.c:1813: error: dereferencing pointer to incomplete type # xymonnet.c:1818: error: dereferencing pointer to incomplete type # If that happens, try adding -DBROKEN_HPUX_NETDB at the end of the CFLAGS line below. CFLAGS = -g -O -D_REENTRANT $(LFSDEF) $(OSDEF) # Compile flags for debugging # CFLAGS = -g -DDEBUG -D_REENTRANT $(LFSDEF) $(OSDEF) # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mailx" xymon-4.3.7/build/Makefile.NetBSD0000664000175000017500000000123211535462534016046 0ustar henrikhenrik# Xymon compile-time settings for NetBSD systems # From Emmanuel Dreyfus. # OSDEF = -DBSD # NETLIBS: None needed NETLIBS = # # Compile flags for normal build CC= gcc PKGDIR?=/usr/pkg CFLAGS = -g -O2 -Wall -Wno-unused -D_REENTRANT $(LFSDEF) $(OSDEF) \ -I${PKGDIR}/include -L${PKGDIR}/lib, -Wl,--rpath=${PKGDIR}/lib RPATH = "-Wl,--rpath," # Compile flags for debugging # CFLAGS = -g -DDEBUG -Wall -D_REENTRANT $(LFSDEF) $(OSDEF) \ -I${PKGDIR}/include -L${PKGDIR}/lib, -Wl,--rpath=${PKGDIR}/lib # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mail" xymon-4.3.7/build/makehtml.sh0000775000175000017500000000151411535462534015474 0ustar henrikhenrik#!/bin/bash export LANG=C DATE=`date +"%e %b %Y"` VERSION="$1" if [ "$VERSION" = "" ] then VERSION="Exp" fi # cd ~/xymon/trunk rm -f docs/*~ docs/manpages/index.html* docs/manpages/man1/* docs/manpages/man5/* docs/manpages/man7/* docs/manpages/man8/* for DIR in xymongen xymonnet xymonproxy common xymond web do for SECT in 1 5 7 8 do for FILE in $DIR/*.$SECT do if [ -r $FILE ] then NAME=`head -n 1 $FILE | awk '{print $2}'`; SECTION=`head -n 1 $FILE | awk '{print $3}'`; (echo ".TH $NAME $SECTION \"Version $VERSION: $DATE\" \"Xymon\""; tail -n +2 $FILE) | \ man2html -r - | tail -n +2 >docs/manpages/man$SECT/`basename $FILE`.html fi done done done # Sourceforge update # cd ~/xymon/trunk/docs && rsync -av --rsh=ssh --exclude=RCS ./ storner@shell.sourceforge.net:/home/groups/x/xy/xymon/htdocs/docs/ xymon-4.3.7/build/merge-lines.c0000664000175000017500000001447611671630131015710 0ustar henrikhenrik/* * Merge the current xymonserver.cfg file with a new template. New entries are added, * and existing ones are copied over from the current setup. */ #include #include #include #include #include #include typedef struct entry_t { char *name; char *val; int copied; struct entry_t *next; int extracount; char **extralines; } entry_t; typedef struct newname_t { char *oldname, *newname; struct newname_t *next; } newname_t; entry_t *head = NULL; entry_t *tail = NULL; newname_t *newnames = NULL; char *lastblankandcomment = NULL; int main(int argc, char *argv[]) { char *curfn, *curbckfn, *srcfn; FILE *curfd, *curbckfd, *srcfd; char delim = '='; char alldelims[10]; char l[32768]; entry_t *ewalk; struct stat st; int showit = 1; srcfn = strdup(argv[1]); curfn = strdup(argv[2]); if (argc > 3) { int i; char *p; for (i=3; (i < argc); i++) { p = strchr(argv[i], '='); if (p) { newname_t *newitem = (newname_t *)malloc(sizeof(newname_t)); *p = '\0'; newitem->oldname = strdup(argv[i]); newitem->newname = strdup(p+1); newitem->next = newnames; newnames = newitem; } } } curbckfn = (char *)malloc(strlen(curfn) + 5); sprintf(curbckfn, "%s.bak", curfn); if (strstr(srcfn, ".csv")) { delim = ';'; strcpy(alldelims, ";"); } else { sprintf(alldelims, "%c+-", delim); } if (stat(curfn, &st) == -1) { showit = 0; goto nooriginal; } curfd = fopen(curfn, "r"); unlink(curbckfn); curbckfd = fopen(curbckfn, "w"); if (curfd == NULL) { printf("Cannot open config file %s\n", curfn); return 1; } if (curbckfd == NULL) { printf("Cannot create backup file %s\n", curbckfn); return 1; } while (fgets(l, sizeof(l), curfd)) { char *bol, *p; fprintf(curbckfd, "%s", l); bol = l + strspn(l, " \t\r\n"); if ((*bol == '#') || (*bol == '\0')) { if (!lastblankandcomment) lastblankandcomment = strdup(bol); else { lastblankandcomment = (char *)realloc(lastblankandcomment, strlen(lastblankandcomment) + strlen(bol) + 1); strcat(lastblankandcomment, bol); } continue; } if ((strncmp(bol, "include ", 8) == 0) || (strncmp(bol, "directory ", 10) == 0)) { if (!tail->extralines) { tail->extracount = 1; tail->extralines = (char **)malloc(sizeof(char *)); } else { tail->extracount++; tail->extralines = (char **)realloc(tail->extralines, (tail->extracount*sizeof(char *))); } if (!lastblankandcomment) tail->extralines[tail->extracount-1] = strdup(bol); else { tail->extralines[tail->extracount-1] = (char *)malloc(1 + strlen(bol) + strlen(lastblankandcomment)); sprintf(tail->extralines[tail->extracount-1], "%s%s", lastblankandcomment, bol); } if (lastblankandcomment) { free(lastblankandcomment); lastblankandcomment = NULL; } continue; } p = bol + strcspn(bol, alldelims); if (*p) { entry_t *newent; if (*p == delim) { *p = '\0'; newent = (entry_t *)calloc(1, sizeof(entry_t)); newent->name = strdup(bol); *p = delim; newent->val = strdup(l); if (tail == NULL) { tail = head = newent; } else { tail->next = newent; tail = newent; } } else if (*(p+1) == delim) { char sav = *p; entry_t *walk; *p = '\0'; for (walk = head; (walk && (strcmp(walk->name, bol) != 0)); walk = walk->next) ; *p = sav; if (walk) { if (!walk->extralines) { walk->extracount = 1; walk->extralines = (char **)malloc(sizeof(char *)); } else { walk->extracount++; walk->extralines = (char **)realloc(walk->extralines, (walk->extracount*sizeof(char *))); } if (!lastblankandcomment) walk->extralines[walk->extracount-1] = strdup(bol); else { walk->extralines[walk->extracount-1] = (char *)malloc(1 + strlen(bol) + strlen(lastblankandcomment)); sprintf(walk->extralines[walk->extracount-1], "%s%s", lastblankandcomment, bol); } } } if (lastblankandcomment) { free(lastblankandcomment); lastblankandcomment = NULL; } } } fclose(curfd); fclose(curbckfd); if (lastblankandcomment) { /* Add this to the last entry */ if (!tail->extralines) { tail->extracount = 1; tail->extralines = (char **)malloc(sizeof(char *)); } else { tail->extracount++; tail->extralines = (char **)realloc(tail->extralines, (tail->extracount*sizeof(char *))); } tail->extralines[tail->extracount-1] = strdup(lastblankandcomment); } nooriginal: srcfd = fopen(srcfn, "r"); unlink(curfn); curfd = fopen(curfn, "w"); if (srcfd == NULL) { printf("Cannot open template file %s\n", srcfn); return 1; } if (curfd == NULL) { printf("Cannot create config file %s\n", curfn); return 1; } while (fgets(l, sizeof(l), srcfd)) { char *bol, *p; bol = l + strspn(l, " \t\r\n"); if ((*bol == '#') || (*bol == '\0') || (strncmp(bol, "include ", 8) == 0) || (strncmp(bol, "directory ", 10) == 0)) { fprintf(curfd, "%s", l); continue; } p = strchr(bol, delim); if (p) { /* Find the old value */ *p = '\0'; for (ewalk = head; (ewalk && strcmp(ewalk->name, bol)); ewalk = ewalk->next) ; if (!ewalk) { /* See if it's been renamed */ newname_t *nwalk; for (nwalk = newnames; (nwalk && strcmp(nwalk->newname, bol)); nwalk = nwalk->next) ; if (nwalk) { /* It has - find the value of the old setting */ for (ewalk = head; (ewalk && strcmp(ewalk->name, nwalk->oldname)); ewalk = ewalk->next) ; if (ewalk) { /* Merge it with the new name */ char *newval; char *oval = strchr(ewalk->val, delim); newval = (char *)malloc(strlen(nwalk->newname) + strlen(oval) + 1); sprintf(newval, "%s%s", nwalk->newname, oval); ewalk->val = newval; } } } *p = delim; if (ewalk) { fprintf(curfd, "%s", ewalk->val); if (ewalk->extralines) { int i; for (i = 0; (i < ewalk->extracount); i++) fprintf(curfd, "%s", ewalk->extralines[i]); } ewalk->copied = 1; } else { if (showit) printf("Adding new entry to %s: %s", curfn, l); fprintf(curfd, "%s", l); } } else { fprintf(curfd, "%s", l); } } /* Copy over any local settings that have been added */ for (ewalk = head; (ewalk); ewalk = ewalk->next) { if (!ewalk->copied) { fprintf(curfd, "%s", ewalk->val); } } fclose(curfd); fclose(srcfd); return 0; } xymon-4.3.7/build/convert-bbservices0000775000175000017500000000070611535462534017070 0ustar henrikhenrik#!/bin/sh # Script to convert a bbgen v3 protocols.cfg file to the # [NAME] section delimited format used in Xymon v4 (starting with RC3). FN="$1" if test ! -r "${FN}"; then exit 0; fi sed -e 's/^service \(.*\)/\[\1\]/' < "${FN}" >"${FN}.converted" cmp -s "${FN}" "${FN}.converted" if test $? -eq 0; then rm -f "${FN}.converted"; exit 0; fi if test -f "${FN}.v3"; then rm -f "${FN}.v3"; fi mv "${FN}" "${FN}.v3" mv "${FN}.converted" "${FN}" exit 0 xymon-4.3.7/build/ssl.sh0000775000175000017500000000443011535462534014473 0ustar henrikhenrik echo "Checking for OpenSSL ..." OSSLINC="" OSSLLIB="" for DIR in /opt/openssl* /opt/ssl* /usr/local/openssl* /usr/local/ssl* /usr/local /usr /usr/pkg /opt/csw /opt/sfw/ssl* do if test -d $DIR/include/openssl then OSSLINC=$DIR/include fi if test -f $DIR/lib/libcrypto.so then OSSLLIB=$DIR/lib fi if test -f $DIR/lib/libcrypto.a then OSSLLIB=$DIR/lib fi if test -f $DIR/lib64/libcrypto.so then OSSLLIB=$DIR/lib64 fi if test -f $DIR/lib64/libcrypto.a then OSSLLIB=$DIR/lib64 fi done if test "$USEROSSLINC" != ""; then OSSLINC="$USEROSSLINC" fi if test "$USEROSSLLIB" != ""; then OSSLLIB="$USEROSSLLIB" fi if test -z "$OSSLINC" -o -z "$OSSLLIB"; then echo "OpenSSL include- or library-files not found." echo "Although you can use Xymon without OpenSSL, you will not" echo "be able to run network tests of SSL-enabled services, e.g. https." echo "So installing OpenSSL is recommended." echo "OpenSSL can be found at http://www.openssl.org/" echo "" echo "If you have OpenSSL installed, use the \"--sslinclude DIR\" and \"--ssllib DIR\"" echo "options to configure to specify where they are." echo "" else # Red Hat in their wisdom ships an openssl that depends on Kerberos, # and then puts the Kerberos includes where they are not found automatically. if test "`uname -s`" = "Linux" -a -d /usr/kerberos/include then OSSLINC2="-I/usr/kerberos/include" else OSSLINC2="" fi cd build OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-ssl clean OS=`uname -s | tr '[/]' '[_]'` OSSLINC="-I$OSSLINC $OSSLINC2" $MAKE -f Makefile.test-ssl test-compile if [ $? -eq 0 ]; then echo "Found OpenSSL include files in $OSSLINC" OSSLINC="$OSSLINC $OSSLINC2" else echo "WARNING: OpenSSL include files found in $OSSLINC, but compile fails." OSSLINC="" fi OS=`uname -s | tr '[/]' '[_]'` OSSLLIB="-L$OSSLLIB" $MAKE -f Makefile.test-ssl test-link if [ $? -eq 0 ]; then echo "Found OpenSSL libraries in $OSSLLIB" else echo "WARNING: OpenSSL library files found in $OSSLLIB, but link fails." OSSLINC="" OSSLLIB="" fi OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-ssl clean cd .. fi if test -z "$OSSLINC" -o -z "$OSSLLIB"; then sleep 3 echo "Continuing with SSL support disabled." fi xymon-4.3.7/build/rrd.sh0000775000175000017500000000605711535462534014470 0ustar henrikhenrik echo "Checking for RRDtool ..." RRDDEF="" RRDINC="" RRDLIB="" PNGLIB="" ZLIB="" for DIR in /opt/rrdtool* /usr/local/rrdtool* /usr/local /usr /usr/pkg /opt/csw /opt/sfw /usr/sfw do if test -f $DIR/include/rrd.h then RRDINC=$DIR/include fi if test -f $DIR/lib/librrd.so then RRDLIB=$DIR/lib fi if test -f $DIR/lib/librrd.a then RRDLIB=$DIR/lib fi if test -f $DIR/lib64/librrd.so then RRDLIB=$DIR/lib64 fi if test -f $DIR/lib64/librrd.a then RRDLIB=$DIR/lib64 fi if test -f $DIR/lib/libpng.so then PNGLIB="-L$DIR/lib -lpng" fi if test -f $DIR/lib/libpng.a then PNGLIB="-L$DIR/lib -lpng" fi if test -f $DIR/lib64/libpng.so then PNGLIB="-L$DIR/lib64 -lpng" fi if test -f $DIR/lib64/libpng.a then PNGLIB="-L$DIR/lib64 -lpng" fi if test -f $DIR/lib/libz.so then ZLIB="-L$DIR/lib -lz" fi if test -f $DIR/lib/libz.a then ZLIB="-L$DIR/lib -lz" fi if test -f $DIR/lib64/libz.so then ZLIB="-L$DIR/lib64 -lz" fi if test -f $DIR/lib64/libz.a then ZLIB="-L$DIR/lib64 -lz" fi done if test "$USERRRDINC" != ""; then RRDINC="$USERRRDINC" fi if test "$USERRRDLIB" != ""; then RRDLIB="$USERRRDLIB" fi if test -z "$RRDINC" -o -z "$RRDLIB"; then echo "RRDtool include- or library-files not found. These are REQUIRED for Xymon" echo "RRDtool can be found at http://www.mrtg.org/rrdtool/" echo "If you have RRDtool installed, use the \"--rrdinclude DIR\" and \"--rrdlib DIR\"" echo "options to configure to specify where they are." exit 1 else cd build OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-rrd clean OS=`uname -s | tr '[/]' '[_]'` RRDDEF="$RRDDEF" RRDINC="-I$RRDINC" $MAKE -f Makefile.test-rrd test-compile if [ $? -ne 0 ]; then # See if it's the new RRDtool 1.2.x echo "Not RRDtool 1.0.x, checking for 1.2.x" RRDDEF="-DRRDTOOL12" OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-rrd clean OS=`uname -s | tr '[/]' '[_]'` RRDDEF="$RRDDEF" RRDINC="-I$RRDINC" $MAKE -f Makefile.test-rrd test-compile fi if [ $? -eq 0 ]; then echo "Found RRDtool include files in $RRDINC" else echo "ERROR: RRDtool include files found in $RRDINC, but compile fails." exit 1 fi OS=`uname -s | tr '[/]' '[_]'` RRDLIB="-L$RRDLIB" PNGLIB="$PNGLIB" $MAKE -f Makefile.test-rrd test-link 2>/dev/null if [ $? -ne 0 ]; then # Could be that we need -lz for RRD PNGLIB="$PNGLIB $ZLIB" fi OS=`uname -s | tr '[/]' '[_]'` RRDLIB="-L$RRDLIB" PNGLIB="$PNGLIB" $MAKE -f Makefile.test-rrd test-link 2>/dev/null if [ $? -ne 0 ]; then # Could be that we need -lm for RRD PNGLIB="$PNGLIB -lm" fi OS=`uname -s | tr '[/]' '[_]'` RRDLIB="-L$RRDLIB" PNGLIB="$PNGLIB" $MAKE -f Makefile.test-rrd test-link 2>/dev/null if [ $? -eq 0 ]; then echo "Found RRDtool libraries in $RRDLIB" if test "$PNGLIB" != ""; then echo "Linking RRD with PNG library: $PNGLIB" fi else echo "ERROR: RRDtool library files found in $RRDLIB, but link fails." exit 1 fi OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-rrd clean cd .. fi xymon-4.3.7/build/Makefile.AIX0000664000175000017500000000115011535462534015407 0ustar henrikhenrik# Xymon compile-time settings for a AIX system # OSDEF = -DAIX # NETLIBS: You may need to add -lresolv or similar to pick up network libraries NETLIBS = # Compile flags for normal build with gcc # CC = gcc # CFLAGS = -O -D_REENTRANT $(OSDEF) # Compile flags for normal build with IBM compiler CC = cc CFLAGS = -g -O3 -qstrict -qcpluscmt -D_REENTRANT $(LFSDEF) $(OSDEF) # Compile flags for debugging # CFLAGS = -g -DDEBUG -D_REENTRANT $(LFSDEF) $(OSDEF) # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mail" xymon-4.3.7/build/test-pcre.c0000664000175000017500000000030011535462534015375 0ustar henrikhenrik#include int main(int argc, char *argv[]) { pcre *result; const char *errmsg; int errofs; result = pcre_compile("xymon.*", PCRE_CASELESS, &errmsg, &errofs, NULL); return 0; } xymon-4.3.7/build/Makefile.OSF10000664000175000017500000000071411535462534015503 0ustar henrikhenrik# Xymon compile-time settings for a OSF/1 system # OSDEF = -DOSF # NETLIBS: You may need to add -lresolv or similar to pick up network libraries NETLIBS = # Compile flags for normal build CC = cc CFLAGS = -g -O -D_REENTRANT $(OSDEF) # Compile flags for debugging # CFLAGS = -g -DDEBUG -D_REENTRANT # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mail" xymon-4.3.7/build/test-socklent.c0000664000175000017500000000057311070452713016272 0ustar henrikhenrik#include #include #include #include #include #include #include #include int main(int argc, char *argv[]) { int connres; socklen_t connressize = sizeof(connres); int res, sockfd = 0; res = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &connres, &connressize); return 0; } xymon-4.3.7/build/test-clockgettime-librt.c0000664000175000017500000000024111070452713020224 0ustar henrikhenrik#include #include #include int main () { struct timespec ts; (void)clock_gettime(CLOCK_MONOTONIC, &ts); return 0; } xymon-4.3.7/build/test-ldap.c0000664000175000017500000000113311073116556015366 0ustar henrikhenrik#include #include #include #include LDAPURLDesc ludp; int main(int argc, char *argv[]) { LDAP *ld; if ((argc >= 1) && (strcmp(argv[1], "vendor") == 0)) { printf("%s\n", LDAP_VENDOR_NAME); } else if ((argc >= 1) && (strcmp(argv[1], "version") == 0)) { printf("%d\n", LDAP_VENDOR_VERSION); } else if ((argc >= 1) && (strcmp(argv[1], "flags") == 0)) { if ((strcasecmp(LDAP_VENDOR_NAME, "OpenLDAP") == 0) && (LDAP_VENDOR_VERSION >= 20400)) { printf("-DLDAP_DEPRECATED=1\n"); } } else ld = ldap_init(ludp.lud_host, ludp.lud_port); return 0; } xymon-4.3.7/build/test-lfs.c0000664000175000017500000000065711544715637015254 0ustar henrikhenrik#include #include #include #include #include int main(int argc, char *argv[]) { off_t fileofs; int minsize = atoi(argv[1]); memset(&fileofs, 0, sizeof(fileofs)); #ifdef _LARGEFILE_SOURCE printf("%d:%d:%lld\n", sizeof(off_t), (sizeof(off_t) >= minsize), fileofs); #else printf("%d:%d:%ld\n", sizeof(off_t), (sizeof(off_t) >= minsize), fileofs); #endif return 0; } xymon-4.3.7/build/makedeb.sh0000775000175000017500000000160311535462534015261 0ustar henrikhenrik#!/bin/bash REL=$1 if [ "$REL" = "" ]; then echo "Error - missing release number" exit 1 fi BASEDIR=`pwd` cd $BASEDIR rm -rf debbuild mkdir -p $BASEDIR/debbuild/xymon-$REL for f in xymongen xymonnet bbpatches xymonproxy build common contrib docs xymond web include lib client demotool do find $f/ | egrep -v "RCS|\.svn" | cpio -pdvmu $BASEDIR/debbuild/xymon-$REL/ done cp -p Changes configure configure.server configure.client COPYING CREDITS README README.CLIENT RELEASENOTES $BASEDIR/debbuild/xymon-$REL/ find $BASEDIR/debbuild/xymon-$REL -type d|xargs chmod 755 cd debbuild pushd xymon-$REL make -f ../../Makefile distclean popd tar zcf xymon-$REL.tar.gz xymon-$REL cd $BASEDIR find debian | egrep -v "RCS|pkg|\.svn" | cpio -pdvmu $BASEDIR/debbuild/xymon-$REL/ cd debbuild/xymon-$REL dpkg-buildpackage -rfakeroot -kA6EDAB79 #mv ../xymon*$REL*.{deb,dsc,changes} ../../debian/pkg/ xymon-4.3.7/build/genconfig.sh0000775000175000017500000000517211070452713015625 0ustar henrikhenrik#!/bin/sh # Simpler than autoconf, but it does what we need it to do right now. echo "/* This file is auto-generated */" >include/config.h echo "#ifndef __CONFIG_H__" >>include/config.h echo "#define __CONFIG_H__ 1" >>include/config.h echo "Checking for socklen_t" $CC -c -o build/testfile.o $CFLAGS build/test-socklent.c 1>/dev/null 2>&1 if test $? -eq 0; then echo "#define HAVE_SOCKLEN_T 1" >>include/config.h else echo "#undef HAVE_SOCKLEN_T" >>include/config.h fi echo "Checking for snprintf" $CC -c -o build/testfile.o $CFLAGS build/test-snprintf.c 1>/dev/null 2>&1 if test $? -eq 0; then $CC -o build/testfile $CFLAGS build/testfile.o 1>/dev/null 2>&1 if test $? -eq 0; then echo "#define HAVE_SNPRINTF 1" >>include/config.h else echo "#undef HAVE_SNPRINTF" >>include/config.h fi else echo "#undef HAVE_SNPRINTF" >>include/config.h fi echo "Checking for vsnprintf" $CC -c -o build/testfile.o $CFLAGS build/test-vsnprintf.c 1>/dev/null 2>&1 if test $? -eq 0; then $CC -o build/testfile $CFLAGS build/testfile.o 1>/dev/null 2>&1 if test $? -eq 0; then echo "#define HAVE_VSNPRINTF 1" >>include/config.h else echo "#undef HAVE_VSNPRINTF" >>include/config.h fi else echo "#undef HAVE_VSNPRINTF" >>include/config.h fi echo "Checking for rpc/rpcent.h" $CC -c -o build/testfile.o $CFLAGS build/test-rpcenth.c 1>/dev/null 2>&1 if test $? -eq 0; then echo "#define HAVE_RPCENT_H 1" >>include/config.h else echo "#undef HAVE_RPCENT_H" >>include/config.h fi echo "Checking for sys/select.h" $CC -c -o build/testfile.o $CFLAGS build/test-sysselecth.c 1>/dev/null 2>&1 if test $? -eq 0; then echo "#define HAVE_SYS_SELECT_H 1" >>include/config.h else echo "#undef HAVE_SYS_SELECT_H" >>include/config.h fi echo "Checking for u_int32_t typedef" $CC -c -o build/testfile.o $CFLAGS build/test-uint.c 1>/dev/null 2>&1 if test $? -eq 0; then echo "#define HAVE_UINT32_TYPEDEF 1" >>include/config.h else echo "#undef HAVE_UINT32_TYPEDEF" >>include/config.h fi echo "Checking for PATH_MAX definition" $CC -o build/testfile $CFLAGS build/test-pathmax.c 1>/dev/null 2>&1 if test -x build/testfile; then ./build/testfile >>include/config.h; fi echo "Checking for SHUT_RD/WR/RDWR definitions" $CC -o build/testfile $CFLAGS build/test-shutdown.c 1>/dev/null 2>&1 if test -x build/testfile; then ./build/testfile >>include/config.h; fi echo "Checking for strtoll()" $CC -c -o build/testfile.o $CFLAGS build/test-strtoll.c 1>/dev/null 2>&1 if test $? -eq 0; then echo "#define HAVE_STRTOLL_H 1" >>include/config.h else echo "#undef HAVE_STRTOLL_H" >>include/config.h fi echo "#endif" >>include/config.h echo "config.h created" rm -f testfile.o testfile exit 0 xymon-4.3.7/build/merge-sects.c0000664000175000017500000000721411070452713015710 0ustar henrikhenrik/* * Merge the current "[...]" sectioned file with a new template. New entries are added, * and existing ones are copied over from the current setup. */ #include #include #include #include #include #include typedef struct entry_t { char *name; char *val; int copied; struct entry_t *next; } entry_t; typedef struct newname_t { char *oldname, *newname; struct newname_t *next; } newname_t; entry_t *head = NULL; entry_t *tail = NULL; newname_t *newnames = NULL; int main(int argc, char *argv[]) { char *curfn, *curbckfn, *srcfn; FILE *curfd, *curbckfd, *srcfd; struct stat st; char l[32768]; entry_t *ewalk; entry_t *newent = NULL; int adding = 0; int showit = 1; if (argc < 3) { printf("Usage: %s DESTINATIONFILE TEMPLATEFILE\n", argv[0]); return 1; } srcfn = strdup(argv[1]); curfn = strdup(argv[2]); if (argc > 3) { int i; char *p; for (i=3; (i < argc); i++) { p = strchr(argv[i], '='); if (p) { newname_t *newitem = (newname_t *)malloc(sizeof(newname_t)); *p = '\0'; newitem->oldname = strdup(argv[i]); newitem->newname = strdup(p+1); newitem->next = newnames; newnames = newitem; } } } curbckfn = (char *)malloc(strlen(curfn) + 5); sprintf(curbckfn, "%s.bak", curfn); if (stat(curfn, &st) == -1) { showit = 0; goto nooriginal; } curfd = fopen(curfn, "r"); unlink(curbckfn); curbckfd = fopen(curbckfn, "w"); if (curfd == NULL) { printf("Cannot open configuration file %s\n", curfn); return 1; } if (curbckfd == NULL) { printf("Cannot open backup file %s\n", curbckfn); return 1; } while (fgets(l, sizeof(l), curfd)) { char *bol, *p; newname_t *nwalk; fprintf(curbckfd, "%s", l); bol = l + strspn(l, " \t\r\n"); if ((*bol == '#') || (*bol == '\0')) continue; if ((*bol == '[') && strchr(bol, ']')) { newent = (entry_t *)malloc(sizeof(entry_t)); p = strchr(bol, ']'); *p = '\0'; for (nwalk = newnames; (nwalk && strcmp(nwalk->oldname, bol+1)); nwalk = nwalk->next); if (nwalk) { newent->name = strdup(nwalk->newname); newent->val = (char *)malloc(strlen(nwalk->newname) + 4); sprintf(newent->val, "[%s]\n", nwalk->newname); } else { newent->name = strdup(bol+1); *p = ']'; newent->val = strdup(l); } newent->copied = 0; newent->next = NULL; if (tail == NULL) { tail = head = newent; } else { tail->next = newent; tail = newent; } } else if (newent) { newent->val = (char *)realloc(newent->val, strlen(newent->val) + strlen(l) + 1); strcat(newent->val, l); } } fclose(curfd); fclose(curbckfd); nooriginal: srcfd = fopen(srcfn, "r"); unlink(curfn); curfd = fopen(curfn, "w"); if (srcfd == NULL) { printf("Cannot open template file %s\n", srcfn); return 1; } if (curfd == NULL) { printf("Cannot open config file %s\n", curfn); return 1; } while (fgets(l, sizeof(l), srcfd)) { char *bol, *p; bol = l + strspn(l, " \t\r\n"); if ((*bol == '[') && strchr(bol, ']')) { p = strchr(bol, ']'); *p = '\0'; for (ewalk = head; (ewalk && strcmp(ewalk->name, bol+1)); ewalk = ewalk->next) ; *p = ']'; if (ewalk) { fprintf(curfd, "%s", ewalk->val); ewalk->copied = 1; adding = 0; } else { if (showit) printf("Adding new entry to %s: %s", curfn, l); fprintf(curfd, "%s", l); adding = 1; } } else if (adding || (*bol == '#') || (*bol == '\0')) { fprintf(curfd, "%s", l); } } /* Copy over any local settings that have been added */ for (ewalk = head; (ewalk); ewalk = ewalk->next) { if (!ewalk->copied) { fprintf(curfd, "%s", ewalk->val); } } fclose(curfd); fclose(srcfd); return 0; } xymon-4.3.7/build/Makefile.Linux0000664000175000017500000000127111535462534016071 0ustar henrikhenrik# Xymon compile-time settings for LINUX systems OSDEF = -DLINUX # NETLIBS: None needed on Linux NETLIBS = # Compile flags for normal build CC = gcc CFLAGS = -g -O2 -Wall -Wno-unused -D_REENTRANT $(LFSDEF) $(OSDEF) ifndef PKGBUILD RPATH = -Wl,--rpath, endif # Compile flags for debugging # CFLAGS = -g -DDEBUG -Wall -D_REENTRANT $(LFSDEF) $(OSDEF) # By default, Xymon uses a static library for common code. # To save some diskspace and run-time memory, you can use a # shared library by un-commenting this. # XYMONLIBRARY=libxymon.so # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mail" xymon-4.3.7/build/updmanver0000775000175000017500000000105711535462534015264 0ustar henrikhenrik#!/bin/bash export LANG=C DATE=`date +"%e %b %Y"` VERSION=$1 if [ "$VERSION" = "" ] then echo "Usage: $0 VERSION" exit 1 fi # cd $HOME/xymon || exit 1 for DIR in xymongen xymonnet xymonproxy common xymond web do pushd $DIR # co -l RCS/*.[1-9],v for f in *.[1-9] do NAME=`head -n 1 $f | awk '{print $2}'`; SECTION=`head -n 1 $f | awk '{print $3}'`; (echo ".TH $NAME $SECTION \"Version $VERSION: $DATE\" \"Xymon\""; tail -n +2 $f) >$f.new mv $f.new $f # ci -u -m"Version update" -q -f $f done popd done xymon-4.3.7/build/setup-newfiles.c0000664000175000017500000000706011535462534016453 0ustar henrikhenrik#include #include #include #include #include #include #include #include #include #include "libxymon.h" int main(int argc, char *argv[]) { char srcfn[PATH_MAX]; char destfn[PATH_MAX]; char *p; struct stat st; unsigned char *sumbuf = NULL; if (argc > 2) { if (stat(argv[2], &st) == 0) { FILE *sumfd; printf("Loading md5-data ... "); sumfd = fopen(argv[2], "r"); if (sumfd) { sumbuf = (char *)malloc(st.st_size + 1); if (fread(sumbuf, 1, st.st_size, sumfd) == st.st_size) { printf("OK\n"); } else { printf("failed\n"); free(sumbuf); sumbuf = NULL; } fclose(sumfd); } else { printf("failed\n"); } } } while (fgets(srcfn, sizeof(srcfn), stdin)) { FILE *fd; unsigned char buf[8192]; size_t buflen; digestctx_t *ctx; char srcmd5[40]; char *md5sum; p = strchr(srcfn, '\n'); if (p) *p = '\0'; strcpy(destfn, argv[1]); p = srcfn; if (strcmp(srcfn, ".") == 0) p = ""; else if (strncmp(p, "./", 2) == 0) p += 2; strcat(destfn, p); *srcmd5 = '\0'; if (((fd = fopen(srcfn, "r")) != NULL) && ((ctx = digest_init("md5")) != NULL)) { while ((buflen = fread(buf, 1, sizeof(buf), fd)) > 0) digest_data(ctx, buf, buflen); strcpy(srcmd5, digest_done(ctx)); fclose(fd); } if (stat(destfn, &st) == 0) { /* Destination file exists, see if it's a previous version */ if (sumbuf == NULL) continue; /* No md5-data, dont overwrite an existing file */ if (!S_ISREG(st.st_mode)) continue; fd = fopen(destfn, "r"); if (fd == NULL) continue; if ((ctx = digest_init("md5")) == NULL) continue; while ((buflen = fread(buf, 1, sizeof(buf), fd)) > 0) digest_data(ctx, buf, buflen); md5sum = digest_done(ctx); fclose(fd); if (strstr(sumbuf, md5sum) == NULL) continue; /* Not one of our known versions */ if (strcmp(srcmd5, md5sum) == 0) continue; /* Already installed */ /* We now know the destination that exists is just one of our old files */ printf("Updating old standard file %s\n", destfn); unlink(destfn); } else { printf("Installing new file %s\n", destfn); } if (lstat(srcfn, &st) != 0) { printf("Error - cannot lstat() %s\n", srcfn); return 1; } if (S_ISREG(st.st_mode)) { FILE *infd, *outfd; char buf[16384]; int n; infd = fopen(srcfn, "r"); if (infd == NULL) { /* Dont know how this can happen, but .. */ fprintf(stderr, "Cannot open input file %s: %s\n", srcfn, strerror(errno)); return 1; } outfd = fopen(destfn, "w"); if (outfd == NULL) { /* Dont know how this can happen, but .. */ fprintf(stderr, "Cannot create output file %s: %s\n", destfn, strerror(errno)); return 1; } while ( (n = fread(buf, 1, sizeof(buf), infd)) > 0) fwrite(buf, 1, n, outfd); fclose(infd); fclose(outfd); chmod(destfn, st.st_mode); } else if (S_ISDIR(st.st_mode)) { struct stat tmpst; /* Create upper-lying directories */ if (*destfn == '/') p = strchr(destfn+1, '/'); else p = strchr(destfn, '/'); while (p) { *p = '\0'; if ((stat(destfn, &tmpst) == 0) || (mkdir(destfn, st.st_mode) == 0)) { *p = '/'; p = strchr(p+1, '/'); } else p = NULL; } /* Create the directory itself */ if (stat(destfn, &tmpst) == -1) mkdir(destfn, st.st_mode); chmod(destfn, st.st_mode); } else if (S_ISLNK(st.st_mode)) { char ldest[PATH_MAX + 1]; memset(ldest, 0, sizeof(ldest)); readlink(srcfn, ldest, sizeof(ldest)-1); symlink(ldest, destfn); } } return 0; } xymon-4.3.7/build/Makefile.test-rrd0000664000175000017500000000036311535462534016537 0ustar henrikhenrikinclude Makefile.$(OS) test-compile: @$(CC) $(CFLAGS) $(RRDDEF) $(RRDINC) -o test-rrd.o -c test-rrd.c test-link: @$(CC) $(CFLAGS) $(RRDDEF) $(RRDLIB) -o test-rrd test-rrd.o -lrrd $(PNGLIB) clean: @rm -f test-rrd.o test-rrd xymongen.png xymon-4.3.7/build/Makefile.rules0000664000175000017500000004722711630431123016121 0ustar henrikhenrik# Xymon main Makefile # # This file is included from the Makefile created by the "configure" script ##################### # Build targets ##################### CFLAGS += -I. -I$(BUILDTOPDIR)/include ifeq ($(CLIENTONLY),yes) BUILDTARGETS = client CFLAGS += -DCLIENTONLY=1 ifeq ($(LOCALCLIENT),yes) CLIENTTARGETS = lib-client common-client build-build xymond-client INSTALLTARGETS = install-client install-localclient install-clientmsg CFLAGS += -DLOCALCLIENT=1 $(PCREINCDIR) else CLIENTTARGETS = lib-client common-client build-build INSTALLTARGETS = install-client install-clientmsg endif else BUILDTARGETS = lib-build common-build xymongen-build xymonnet-build xymonproxy-build docs-build build-build xymond-build web-build client CLIENTTARGETS = lib-client common-client build-build INSTALLTARGETS = install-xymongen install-xymonnet install-xymonproxy install-man install-xymond install-web install-docs install-client install-servermsg CFLAGS += $(PCREINCDIR) endif ifndef INSTALLBINDIR INSTALLBINDIR = $(XYMONHOME)/bin endif ifndef INSTALLETCDIR INSTALLETCDIR = $(XYMONHOME)/etc endif ifndef INSTALLEXTDIR INSTALLEXTDIR = $(XYMONHOME)/ext endif ifndef INSTALLTMPDIR INSTALLTMPDIR = $(XYMONHOME)/tmp endif ifndef INSTALLWEBDIR INSTALLWEBDIR = $(XYMONHOME)/web endif ifndef INSTALLWWWDIR INSTALLWWWDIR = $(XYMONHOME)/www endif ARESVER = 1.7.3 IDTOOL := $(shell if test `uname -s` = "SunOS"; then echo /usr/xpg4/bin/id; else echo id; fi) ifdef RPATH RPATHOPT := $(RPATH)$(shell echo $(RPATHVAL) | sed -e 's/ / $(RPATH)/g') endif all: include/config.h $(BUILDTARGETS) @echo "" @echo "Build complete." @echo "" @echo "#####################################################################" @echo "IMPORTANT: If upgrading from 4.2.x, see the docs/upgrade-to-430.txt" @echo " file for instructions. You must run build/upgrade430.sh" @echo " before installing the new version." @echo "#####################################################################" @echo "" @echo "Now run '${MAKE} install' as root" @echo "" client: include/config.h $(CLIENTTARGETS) CC="$(CC)" CFLAGS="$(CFLAGS)" XYMONHOME="$(XYMONTOPDIR)/client" XYMONHOSTIP="$(XYMONHOSTIP)" LOCALCLIENT="$(LOCALCLIENT)" NETLIBS="$(NETLIBS)" LIBRTDEF="$(LIBRTDEF)" $(MAKE) -C client all include/config.h: MAKE="$(MAKE)" CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" $(BUILDTOPDIR)/build/genconfig.sh build-build: CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" SSLLIBS="$(SSLLIBS)" NETLIBS="$(NETLIBS)" LIBRTDEF="$(LIBRTDEF)" XYMONHOME="$(XYMONHOME)" $(MAKE) -C build all lib-build: include/config.h CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" OSDEF="$(OSDEF)" RPATHOPT="$(RPATHOPT)" PCREINCDIR="$(PCREINCDIR)" SSLFLAGS="$(SSLFLAGS)" SSLINCDIR="$(SSLINCDIR)" SSLLIBS="$(SSLLIBS)" NETLIBS="$(NETLIBS)" LIBRTDEF="$(LIBRTDEF)" XYMONTOPDIR="$(XYMONTOPDIR)" XYMONLOGDIR="$(XYMONLOGDIR)" XYMONHOSTNAME="$(XYMONHOSTNAME)" XYMONHOSTIP="$(XYMONHOSTIP)" XYMONHOSTOS="$(XYMONHOSTOS)" $(MAKE) -C lib all lib-client: CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" OSDEF="$(OSDEF)" RPATHOPT="$(RPATHOPT)" PCREINCDIR="$(PCREINCDIR)" SSLFLAGS="$(SSLFLAGS)" SSLINCDIR="$(SSLINCDIR)" SSLLIBS="$(SSLLIBS)" NETLIBS="$(NETLIBS)" LIBRTDEF="$(LIBRTDEF)" XYMONTOPDIR="$(XYMONTOPDIR)" XYMONLOGDIR="$(XYMONLOGDIR)" XYMONHOSTNAME="$(XYMONHOSTNAME)" XYMONHOSTIP="$(XYMONHOSTIP)" XYMONHOSTOS="$(XYMONHOSTOS)" LOCALCLIENT="$(LOCALCLIENT)" $(MAKE) -C lib client common-build: lib-build CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" SSLFLAGS="$(SSLFLAGS)" SSLINCDIR="$(SSLINCDIR)" SSLLIBS="$(SSLLIBS)" NETLIBS="$(NETLIBS)" LIBRTDEF="$(LIBRTDEF)" XYMONHOME="$(XYMONHOME)" $(MAKE) -C common all common-client: lib-client CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" SSLFLAGS="$(SSLFLAGS)" SSLINCDIR="$(SSLINCDIR)" SSLLIBS="$(SSLLIBS)" NETLIBS="$(NETLIBS)" LIBRTDEF="$(LIBRTDEF)" XYMONHOME="$(XYMONHOME)" $(MAKE) -C common client xymongen-build: lib-build common-build CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" SSLFLAGS="$(SSLFLAGS)" SSLINCDIR="$(SSLINCDIR)" SSLLIBS="$(SSLLIBS)" NETLIBS="$(NETLIBS)" LIBRTDEF="$(LIBRTDEF)" XYMONHOME="$(XYMONHOME)" XYMONVAR="$(XYMONVAR)" HISTGRAPHDEF="$(HISTGRAPHDEF)" RUNTIMEDEFS="$(RUNTIMEDEFS)" PCREINCDIR="$(PCREINCDIR)" PCRELIBS="$(PCRELIBS)" $(MAKE) -C xymongen all xymonnet-build: lib-build common-build CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" SSLFLAGS="$(SSLFLAGS)" SSLINCDIR="$(SSLINCDIR)" SSLLIBS="$(SSLLIBS)" LDAPFLAGS="$(LDAPFLAGS)" LDAPINCDIR="$(LDAPINCDIR)" LDAPLIBS="$(LDAPLIBS)" DOSNMP="$(DOSNMP)" NETLIBS="$(NETLIBS)" XYMONHOME="$(XYMONHOME)" ARESVER="$(ARESVER)" RUNTIMEDEFS="$(RUNTIMEDEFS)" PCREINCDIR="$(PCREINCDIR)" PCRELIBS="$(PCRELIBS)" LIBRTDEF="$(LIBRTDEF)" $(MAKE) -C xymonnet all xymonproxy-build: lib-build common-build CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" NETLIBS="$(NETLIBS)" LIBRTDEF="$(LIBRTDEF)" XYMONHOME="$(XYMONHOME)" $(MAKE) -C xymonproxy all xymond-build: lib-build build-build common-build CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" RRDDEF="$(RRDDEF)" RRDINCDIR="$(RRDINCDIR)" PCREINCDIR="$(PCREINCDIR)" NETLIBS="$(NETLIBS)" RRDLIBS="$(RRDLIBS)" PCRELIBS="$(PCRELIBS)" LIBRTDEF="$(LIBRTDEF)" XYMONTOPDIR="$(XYMONTOPDIR)" XYMONHOME="$(XYMONHOME)" XYMONVAR="$(XYMONVAR)" XYMONLOGDIR="$(XYMONLOGDIR)" XYMONHOSTNAME="$(XYMONHOSTNAME)" XYMONHOSTIP="$(XYMONHOSTIP)" XYMONHOSTOS="$(XYMONHOSTOS)" XYMONUSER="$(XYMONUSER)" CGIDIR="$(CGIDIR)" SECURECGIDIR="$(SECURECGIDIR)" XYMONHOSTURL="$(XYMONHOSTURL)" XYMONCGIURL="$(XYMONCGIURL)" SECUREXYMONCGIURL="$(SECUREXYMONCGIURL)" MAILPROGRAM="$(MAILPROGRAM)" FPING="$(FPING)" RUNTIMEDEFS="$(RUNTIMEDEFS)" INSTALLWWWDIR="$(INSTALLWWWDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" $(MAKE) -C xymond all web-build: lib-build build-build common-build CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" RRDDEF="$(RRDDEF)" RRDINCDIR="$(RRDINCDIR)" PCREINCDIR="$(PCREINCDIR)" NETLIBS="$(NETLIBS)" RRDLIBS="$(RRDLIBS)" PCRELIBS="$(PCRELIBS)" LIBRTDEF="$(LIBRTDEF)" XYMONTOPDIR="$(XYMONTOPDIR)" XYMONHOME="$(XYMONHOME)" XYMONVAR="$(XYMONVAR)" XYMONLOGDIR="$(XYMONLOGDIR)" XYMONHOSTNAME="$(XYMONHOSTNAME)" XYMONHOSTIP="$(XYMONHOSTIP)" XYMONHOSTOS="$(XYMONHOSTOS)" XYMONUSER="$(XYMONUSER)" CGIDIR="$(CGIDIR)" SECURECGIDIR="$(SECURECGIDIR)" XYMONHOSTURL="$(XYMONHOSTURL)" XYMONCGIURL="$(XYMONCGIURL)" SECUREXYMONCGIURL="$(SECUREXYMONCGIURL)" MAILPROGRAM="$(MAILPROGRAM)" RUNTIMEDEFS="$(RUNTIMEDEFS)" INSTALLWWWDIR="$(INSTALLWWWDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" $(MAKE) -C web all xymond-client: lib-client build-build common-client CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" RRDDEF="$(RRDDEF)" RRDINCDIR="$(RRDINCDIR)" PCREINCDIR="$(PCREINCDIR)" NETLIBS="$(NETLIBS)" RRDLIBS="$(RRDLIBS)" PCRELIBS="$(PCRELIBS)" XYMONTOPDIR="$(XYMONTOPDIR)" XYMONHOME="$(XYMONHOME)" XYMONVAR="$(XYMONVAR)" XYMONLOGDIR="$(XYMONLOGDIR)" XYMONHOSTNAME="$(XYMONHOSTNAME)" XYMONHOSTIP="$(XYMONHOSTIP)" XYMONHOSTOS="$(XYMONHOSTOS)" XYMONUSER="$(XYMONUSER)" CGIDIR="$(CGIDIR)" SECURECGIDIR="$(SECURECGIDIR)" XYMONHOSTURL="$(XYMONHOSTURL)" XYMONCGIURL="$(XYMONCGIURL)" SECUREXYMONCGIURL="$(SECUREXYMONCGIURL)" MAILPROGRAM="$(MAILPROGRAM)" RUNTIMEDEFS="$(RUNTIMEDEFS)" INSTALLWWWDIR="$(INSTALLWWWDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" $(MAKE) -C xymond client docs-build: XYMONHOSTURL="$(XYMONHOSTURL)" $(MAKE) -C docs all custom-build: lib-build CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" RRDDEF="$(RRDDEF)" RRDINCDIR="$(RRDINCDIR)" PCREINCDIR="$(PCREINCDIR)" NETLIBS="$(NETLIBS)" RRDLIBS="$(RRDLIBS)" PCRELIBS="$(PCRELIBS)" XYMONTOPDIR="$(XYMONTOPDIR)" XYMONHOME="$(XYMONHOME)" XYMONVAR="$(XYMONVAR)" XYMONLOGDIR="$(XYMONLOGDIR)" XYMONHOSTNAME="$(XYMONHOSTNAME)" XYMONHOSTIP="$(XYMONHOSTIP)" XYMONHOSTOS="$(XYMONHOSTOS)" XYMONUSER="$(XYMONUSER)" CGIDIR="$(CGIDIR)" SECURECGIDIR="$(SECURECGIDIR)" XYMONHOSTURL="$(XYMONHOSTURL)" XYMONCGIURL="$(XYMONCGIURL)" SECUREXYMONCGIURL="$(SECUREXYMONCGIURL)" MAILPROGRAM="$(MAILPROGRAM)" RUNTIMEDEFS="$(RUNTIMEDEFS)" INSTALLWWWDIR="$(INSTALLWWWDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" $(MAKE) -C custom all || echo "Skipped custom modules" demo-build: CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" NETLIBS="$(NETLIBS)" $(MAKE) -C demotool all win32: include/config.h CC="$(CC)" CFLAGS="$(CFLAGS) -DXYMONWINCLIENT -DCLIENTONLY" LDFLAGS="$(LDFLAGS)" RPATHOPT="$(RPATHOPT)" SSLFLAGS="$(SSLFLAGS)" SSLINCDIR="$(SSLINCDIR)" SSLLIBS="$(SSLLIBS)" NETLIBS="$(NETLIBS)" LIBRTDEF="$(LIBRTDEF)" XYMONHOME="$(XYMONHOME)" XYMONTOPDIR="$(XYMONTOPDIR)" XYMONLOGDIR="$(XYMONLOGDIR)" XYMONHOSTNAME="$(XYMONHOSTNAME)" XYMONHOSTIP="$(XYMONHOSTIP)" XYMONHOSTOS="$(XYMONHOSTOS)" $(MAKE) -C common xymon.exe ##################### # Cleanup targets ##################### distclean: allclean rm -f Makefile rm -rf xymonnet/c-ares rm -rf debbuild rpmbuild allclean: clean (cd xymonnet/c-ares && $(MAKE) clean) || true clean: $(MAKE) -C build clean $(MAKE) -C lib clean $(MAKE) -C common clean $(MAKE) -C xymongen clean $(MAKE) DOSNMP="$(DOSNMP)" -C xymonnet clean $(MAKE) -C xymonproxy clean $(MAKE) -C xymond clean $(MAKE) -C web clean $(MAKE) -C docs clean $(MAKE) -C client clean # $(MAKE) -C demotool clean # $(MAKE) -C custom clean || echo "" rm -f ./*~ include/config.h include/*~ debian/*~ rpm/*~ contrib/*~ #################### # Install targets #################### install: $(INSTALLTARGETS) install-servermsg: @echo "" @echo "Installation complete." @echo "" @echo "You must configure your webserver for the Xymon webpages and CGI-scripts." @echo "A sample Apache configuration is in ${XYMONHOME}/etc/xymon-apache.conf" @echo "If you have your Administration CGI scripts in a separate directory," @echo "then you must also setup the password-file with the htpasswd command." @echo "" @echo "To start Xymon, as the $(XYMONUSER) user run '${XYMONHOME}/bin/xymon.sh start'" @echo "To view the Xymon webpages, go to http://${XYMONHOSTNAME}${XYMONHOSTURL}" install-dirs: mkdir -p $(INSTALLROOT)$(XYMONHOME) $(INSTALLROOT)$(XYMONHOME)/download $(INSTALLROOT)$(XYMONVAR) mkdir -p $(INSTALLROOT)$(INSTALLBINDIR) ifneq ($(INSTALLBINDIR),$(XYMONHOME)/bin) rm -f $(INSTALLROOT)$(XYMONHOME)/bin || true ln -s $(INSTALLBINDIR) $(INSTALLROOT)$(XYMONHOME)/bin || true endif ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(INSTALLBINDIR) chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(INSTALLBINDIR) endif mkdir -p $(INSTALLROOT)$(INSTALLETCDIR) ifneq ($(INSTALLETCDIR),$(XYMONHOME)/etc) rm -f $(INSTALLROOT)$(XYMONHOME)/etc || true ln -s $(INSTALLETCDIR) $(INSTALLROOT)$(XYMONHOME)/etc || true endif ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(INSTALLETCDIR) chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(INSTALLETCDIR) endif mkdir -p $(INSTALLROOT)$(INSTALLEXTDIR) ifneq ($(INSTALLEXTDIR),$(XYMONHOME)/ext) rm -f $(INSTALLROOT)$(XYMONHOME)/ext || true ln -s $(INSTALLEXTDIR) $(INSTALLROOT)$(XYMONHOME)/ext || true endif ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(INSTALLEXTDIR) chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(INSTALLEXTDIR) endif mkdir -p $(INSTALLROOT)$(INSTALLTMPDIR) ifneq ($(INSTALLTMPDIR),$(XYMONHOME)/tmp) rm -f $(INSTALLROOT)$(XYMONHOME)/tmp || true ln -s $(INSTALLTMPDIR) $(INSTALLROOT)$(XYMONHOME)/tmp || true endif ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(INSTALLTMPDIR) chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(INSTALLTMPDIR) endif mkdir -p $(INSTALLROOT)$(INSTALLWEBDIR) ifneq ($(INSTALLWEBDIR),$(XYMONHOME)/web) rm -f $(INSTALLROOT)$(XYMONHOME)/web || true ln -s $(INSTALLWEBDIR) $(INSTALLROOT)$(XYMONHOME)/web || true endif ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(INSTALLWEBDIR) chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(INSTALLWEBDIR) endif mkdir -p $(INSTALLROOT)$(INSTALLWWWDIR) $(INSTALLROOT)$(INSTALLWWWDIR)/gifs $(INSTALLROOT)$(INSTALLWWWDIR)/help $(INSTALLROOT)$(INSTALLWWWDIR)/html $(INSTALLROOT)$(INSTALLWWWDIR)/menu $(INSTALLROOT)$(INSTALLWWWDIR)/notes $(INSTALLROOT)$(INSTALLWWWDIR)/rep $(INSTALLROOT)$(INSTALLWWWDIR)/snap $(INSTALLROOT)$(INSTALLWWWDIR)/wml ifneq ($(INSTALLWWWDIR),$(XYMONHOME)/www) rm -f $(INSTALLROOT)$(XYMONHOME)/www || true ln -s $(INSTALLWWWDIR) $(INSTALLROOT)$(XYMONHOME)/www || true endif ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(INSTALLWWWDIR) $(INSTALLROOT)$(INSTALLWWWDIR)/gifs $(INSTALLROOT)$(INSTALLWWWDIR)/help $(INSTALLROOT)$(INSTALLWWWDIR)/html $(INSTALLROOT)$(INSTALLWWWDIR)/menu $(INSTALLROOT)$(INSTALLWWWDIR)/notes $(INSTALLROOT)$(INSTALLWWWDIR)/rep $(INSTALLROOT)$(INSTALLWWWDIR)/snap $(INSTALLROOT)$(INSTALLWWWDIR)/wml chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(INSTALLWWWDIR) $(INSTALLROOT)$(INSTALLWWWDIR)/gifs $(INSTALLROOT)$(INSTALLWWWDIR)/help $(INSTALLROOT)$(INSTALLWWWDIR)/html $(INSTALLROOT)$(INSTALLWWWDIR)/menu $(INSTALLROOT)$(INSTALLWWWDIR)/notes $(INSTALLROOT)$(INSTALLWWWDIR)/rep $(INSTALLROOT)$(INSTALLWWWDIR)/snap $(INSTALLROOT)$(INSTALLWWWDIR)/wml ifdef HTTPDGID # The www/rep and www/snap directories must be writable by the apache daemon chgrp $(HTTPDGID) $(INSTALLROOT)$(INSTALLWWWDIR)/rep $(INSTALLROOT)$(INSTALLWWWDIR)/snap endif chmod g+w $(INSTALLROOT)$(INSTALLWWWDIR)/rep $(INSTALLROOT)$(INSTALLWWWDIR)/snap endif mkdir -p $(INSTALLROOT)$(XYMONVAR)/acks ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(XYMONVAR)/acks chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(XYMONVAR)/acks endif mkdir -p $(INSTALLROOT)$(XYMONVAR)/data ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(XYMONVAR)/data chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(XYMONVAR)/data endif mkdir -p $(INSTALLROOT)$(XYMONVAR)/disabled ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(XYMONVAR)/disabled chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(XYMONVAR)/disabled endif mkdir -p $(INSTALLROOT)$(XYMONVAR)/hist ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(XYMONVAR)/hist chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(XYMONVAR)/hist endif mkdir -p $(INSTALLROOT)$(XYMONVAR)/histlogs ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(XYMONVAR)/histlogs || echo "Warning: Could not set owner on the histlogs directory" chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(XYMONVAR)/histlogs || echo "Warning: Could not set group on the histlogs directory" endif mkdir -p $(INSTALLROOT)$(XYMONVAR)/hostdata ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(XYMONVAR)/hostdata || echo "Warning: Could not set owner on the hostdata directory" chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(XYMONVAR)/hostdata || echo "Warning: Could not set group on the hostdata directory" endif mkdir -p $(INSTALLROOT)$(XYMONVAR)/logs ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(XYMONVAR)/logs chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(XYMONVAR)/logs endif mkdir -p $(INSTALLROOT)$(XYMONVAR)/rrd ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(XYMONVAR)/rrd chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(XYMONVAR)/rrd endif install-common: install-dirs XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" INSTALLROOT="$(INSTALLROOT)" INSTALLBINDIR="$(INSTALLBINDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" INSTALLEXTDIR="$(INSTALLEXTDIR)" INSTALLTMPDIR="$(INSTALLTMPDIR)" INSTALLWEBDIR="$(INSTALLWEBDIR)" INSTALLWWWDIR="$(INSTALLWWWDIR)" $(MAKE) -C common install install-xymongen: install-common XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" CGIDIR="$(CGIDIR)" SECURECGIDIR="$(SECURECGIDIR)" INSTALLROOT="$(INSTALLROOT)" INSTALLBINDIR="$(INSTALLBINDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" INSTALLEXTDIR="$(INSTALLEXTDIR)" INSTALLTMPDIR="$(INSTALLTMPDIR)" INSTALLWEBDIR="$(INSTALLWEBDIR)" INSTALLWWWDIR="$(INSTALLWWWDIR)" $(MAKE) -C xymongen install install-xymongen-nocgi: install-common XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" CGIDIR="$(CGIDIR)" SECURECGIDIR="$(SECURECGIDIR)" INSTALLROOT="$(INSTALLROOT)" INSTALLBINDIR="$(INSTALLBINDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" INSTALLEXTDIR="$(INSTALLEXTDIR)" INSTALLTMPDIR="$(INSTALLTMPDIR)" INSTALLWEBDIR="$(INSTALLWEBDIR)" INSTALLWWWDIR="$(INSTALLWWWDIR)" $(MAKE) -C xymongen install-nocgi install-xymonnet: install-common XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" ARESVER="$(ARESVER)" DOSNMP="$(DOSNMP)" INSTALLROOT="$(INSTALLROOT)" INSTALLBINDIR="$(INSTALLBINDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" INSTALLEXTDIR="$(INSTALLEXTDIR)" INSTALLTMPDIR="$(INSTALLTMPDIR)" INSTALLWEBDIR="$(INSTALLWEBDIR)" INSTALLWWWDIR="$(INSTALLWWWDIR)" PKGBUILD="$(PKGBUILD)" $(MAKE) -C xymonnet install install-xymonproxy: install-common XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" INSTALLROOT="$(INSTALLROOT)" INSTALLBINDIR="$(INSTALLBINDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" INSTALLEXTDIR="$(INSTALLEXTDIR)" INSTALLTMPDIR="$(INSTALLTMPDIR)" INSTALLWEBDIR="$(INSTALLWEBDIR)" INSTALLWWWDIR="$(INSTALLWWWDIR)" $(MAKE) -C xymonproxy install install-xymond: install-common MANROOT="$(MANROOT)" XYMONTOPDIR="$(XYMONTOPDIR)" XYMONHOME="$(XYMONHOME)" XYMONVAR="$(XYMONVAR)" CGIDIR="$(CGIDIR)" SECURECGIDIR="$(SECURECGIDIR)" XYMONLOGDIR="$(XYMONLOGDIR)" XYMONUSER="$(XYMONUSER)" INSTALLROOT="$(INSTALLROOT)" INSTALLBINDIR="$(INSTALLBINDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" INSTALLEXTDIR="$(INSTALLEXTDIR)" INSTALLTMPDIR="$(INSTALLTMPDIR)" INSTALLWEBDIR="$(INSTALLWEBDIR)" INSTALLWWWDIR="$(INSTALLWWWDIR)" HTTPDGID="$(HTTPDGID)" $(MAKE) -C xymond install install-web: install-common MANROOT="$(MANROOT)" XYMONTOPDIR="$(XYMONTOPDIR)" XYMONHOME="$(XYMONHOME)" XYMONVAR="$(XYMONVAR)" CGIDIR="$(CGIDIR)" SECURECGIDIR="$(SECURECGIDIR)" XYMONLOGDIR="$(XYMONLOGDIR)" XYMONUSER="$(XYMONUSER)" INSTALLROOT="$(INSTALLROOT)" INSTALLBINDIR="$(INSTALLBINDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" INSTALLEXTDIR="$(INSTALLEXTDIR)" INSTALLTMPDIR="$(INSTALLTMPDIR)" INSTALLWEBDIR="$(INSTALLWEBDIR)" INSTALLWWWDIR="$(INSTALLWWWDIR)" $(MAKE) -C web install # NOTE: This one is normally not used - man-page install is done by the sub-Makefiles during "make install" install-man: XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" INSTALLROOT="$(INSTALLROOT)" $(MAKE) -C common install-man XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" CGIDIR="$(CGIDIR)" SECURECGIDIR="$(SECURECGIDIR)" INSTALLROOT="$(INSTALLROOT)" $(MAKE) -C xymongen install-man XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" INSTALLROOT="$(INSTALLROOT)" $(MAKE) -C xymonnet install-man XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" INSTALLROOT="$(INSTALLROOT)" $(MAKE) -C xymonproxy install-man XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" INSTALLROOT="$(INSTALLROOT)" $(MAKE) -C xymond install-man XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" INSTALLROOT="$(INSTALLROOT)" $(MAKE) -C web install-man install-docs: XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" INSTALLROOT="$(INSTALLROOT)" INSTALLBINDIR="$(INSTALLBINDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" INSTALLEXTDIR="$(INSTALLEXTDIR)" INSTALLTMPDIR="$(INSTALLTMPDIR)" INSTALLWEBDIR="$(INSTALLWEBDIR)" INSTALLWWWDIR="$(INSTALLWWWDIR)" $(MAKE) -C docs install install-custom: XYMONHOME="$(XYMONHOME)" MANROOT="$(MANROOT)" INSTALLROOT="$(INSTALLROOT)" INSTALLBINDIR="$(INSTALLBINDIR)" INSTALLETCDIR="$(INSTALLETCDIR)" INSTALLEXTDIR="$(INSTALLEXTDIR)" INSTALLTMPDIR="$(INSTALLTMPDIR)" INSTALLWEBDIR="$(INSTALLWEBDIR)" INSTALLWWWDIR="$(INSTALLWWWDIR)" $(MAKE) -C custom install || echo "Skipped custom modules" client-install: install-client install-client: client XYMONHOME="$(XYMONTOPDIR)/client" INSTALLROOT="$(INSTALLROOT)" XYMONUSER="$(XYMONUSER)" LOCALCLIENT="$(LOCALCLIENT)" $(MAKE) -C client install install-clientmsg: @echo "" @echo "Installation complete." @echo "" @echo "To start the Xymon client, as the $(XYMONUSER) user run '${XYMONHOME}/runclient.sh start'" install-localclient: XYMONHOME="$(XYMONTOPDIR)/client" INSTALLROOT="$(INSTALLROOT)" XYMONUSER="$(XYMONUSER)" LOCALCLIENT="$(LOCALCLIENT)" $(MAKE) -C client install-localclient xymon-4.3.7/build/Makefile.FreeBSD0000664000175000017500000000105411535462534016203 0ustar henrikhenrik# Xymon compile-time settings for FreeBSD systems OSDEF = -DBSD # NETLIBS: None needed NETLIBS = # Compile flags for normal build CC = gcc CFLAGS = -g -O2 -Wall -Wno-unused -D_REENTRANT -I/usr/local/include -L/usr/local/lib $(LFSDEF) $(OSDEF) RPATH = "-Wl,--rpath," # Compile flags for debugging # CFLAGS = -g -DDEBUG -Wall -D_REENTRANT -I/usr/local/include -L/usr/local/lib $(LFSDEF) $(OSDEF) # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mail" xymon-4.3.7/build/Makefile.OpenBSD0000664000175000017500000000114411535462534016223 0ustar henrikhenrik# Xymon compile-time settings for OpenBSD systems OSDEF = -DBSD # NETLIBS: None needed NETLIBS = # Compile flags for normal build CC = gcc CFLAGS = -g -O2 -Wall -Wno-unused -D_REENTRANT -I/usr/local/include -L/usr/local/lib $(LFSDEF) $(OSDEF) # According to reports, this does not work on OpenBSD # RPATH = "-Wl,--rpath," # Compile flags for debugging # CFLAGS = -g -DDEBUG -Wall -D_REENTRANT -I/usr/local/include -L/usr/local/lib $(LFSDEF) $(OSDEF) # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mail" xymon-4.3.7/build/upgrade430.sh0000775000175000017500000003243511535424634015555 0ustar henrikhenrik#!/bin/sh renameandlink() { if test -f $1 then mv $1 $2 ln -s $2 $1 fi } if test "$BBHOME" = "" then echo "ERROR: This must be invoked using the bbcmd utility" echo " bbcmd $0" exit 1 fi # Get directory where we are running from SDIR=`dirname $0` # Could be a relative path SDIRFULL=`(cd $SDIR; pwd)` if test ! -f $SDIRFULL/renamevars then echo "ERROR: You must run 'make' first to build the 4.3.0 tools" exit 1 fi cd $BBHOME/etc || (echo "Cannot cd to etc directory"; exit 1) if test ! -w .; then echo "Cannot write to etc directory"; exit 1; fi renameandlink bb-hosts hosts.cfg renameandlink bbcombotest.cfg combo.cfg renameandlink hobbit-alerts.cfg alerts.cfg renameandlink hobbit-nkview.cfg critical.cfg renameandlink hobbit-nkview.cfg.bak critical.cfg.bak renameandlink hobbit-rrddefinitions.cfg rrddefinitions.cfg renameandlink hobbitgraph.cfg graphs.cfg renameandlink hobbit-holidays.cfg holidays.cfg renameandlink hobbit-clients.cfg analysis.cfg renameandlink hobbit-snmpmibs.cfg snmpmibs.cfg renameandlink hobbit-apache.conf xymon-apache.conf renameandlink bb-services protocols.cfg renameandlink hobbitcgi.cfg cgioptions.cfg if test -f hobbitlaunch.cfg then mv hobbitlaunch.cfg tasks.old $SDIRFULL/renametasks tasks.cfg ln -s tasks.cfg hobbitlaunch.cfg fi if test -f hobbitserver.cfg then mv hobbitserver.cfg xymonserver.old $SDIRFULL/renamevars >xymonserver.cfg <xymonclient.cfg < #include #include #include int main(int argc, char **argv) { char buf[1024]; char *newnam, *oldnam, *oldval, *p; while (fgets(buf, sizeof(buf), stdin)) { p = strchr(buf, '\n'); if (p) *p = '\0'; newnam = buf; oldnam = strchr(buf, '='); if (!oldnam) continue; *oldnam = '\0'; oldnam++; oldval = getenv(oldnam); if (oldval) { printf("%s=\"", newnam); for (p = oldval; (*p); p++) { if (*p == '"') printf("\\\""); else printf("%c", *p); } printf("\"\n"); } } return 0; } xymon-4.3.7/build/test-sysselecth.c0000664000175000017500000000023711070452713016633 0ustar henrikhenrik#include #include #include #include #include int main(int argc, char *argv[]) { return 0; } xymon-4.3.7/build/snmp.sh0000775000175000017500000000050111070452713014632 0ustar henrikhenrik echo "Checking for Net-SNMP ..." SNMPINC="" SNMPLIB="" VERSION=`net-snmp-config --version` if test $? -eq 0 then echo "Found Net-SNMP version $VERSION" DOSNMP=yes else sleep 3 echo "Could not find Net-SNMP (net-snmp-config command fails)" echo "Continuing with SNMP support disabled." DOSNMP=no fi xymon-4.3.7/build/Makefile.SCO_SV0000664000175000017500000000075511535462534016034 0ustar henrikhenrik# Xymon compile-time settings for SCO_SV (tested on SCO_SV 5.0.5 i386) OSDEF = -DSCO_SV # SCO_SV need this NETLIBS = -lsocket -lnsl # Compile flags for normal build CC = gcc CFLAGS = -g -O2 -Wall -Wno-unused -D_REENTRANT $(LFSDEF) $(OSDEF) # Compile flags for debugging # CFLAGS = -g -DDEBUG -Wall -D_REENTRANT $(LFSDEF) $(OSDEF) # Mail program: This must support "CMD -s SUBJECT ADDRESS" to send out a mail with a subject # Typically, this will be "mail" or "mailx" MAILPROGRAM="mailx" xymon-4.3.7/build/test-rpcenth.c0000664000175000017500000000011211070452713016100 0ustar henrikhenrik#include int main(int argc, char *argv[]) { return 0; } xymon-4.3.7/build/test-ssl.c0000664000175000017500000000054511070452713015250 0ustar henrikhenrik#include #include #include #include #include #if !defined(OPENSSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x00905000L) #error SSL-protocol testing requires OpenSSL version 0.9.5 or later #endif int main(int argc, char *argv[]) { SSL_library_init(); return 0; } xymon-4.3.7/build/test-vsnprintf.c0000664000175000017500000000067511070452713016504 0ustar henrikhenrik#include #include #include #include #include #include #include #include void errprintf(const char *fmt, ...) { char msg[4096]; va_list args; va_start(args, fmt); vsnprintf(msg, sizeof(msg), fmt, args); va_end(args); } int main(int argc, char *argv[]) { errprintf("testing ... %d %d %d\n", 1, 2, 3); return 0; } xymon-4.3.7/build/ldap.sh0000775000175000017500000000440711535462534014616 0ustar henrikhenrik echo "Checking for LDAP ..." LDAPINC="" LDAPLIB="" for DIR in /opt/openldap* /opt/ldap* /usr/local/openldap* /usr/local/ldap* /usr/local /usr /usr/pkg /opt/csw /opt/sfw do if test -f $DIR/include/ldap.h then LDAPINC=$DIR/include fi if test -f $DIR/lib/libldap.so then LDAPLIB=$DIR/lib fi if test -f $DIR/lib/libldap.a then LDAPLIB=$DIR/lib fi if test -f $DIR/lib64/libldap.so then LDAPLIB=$DIR/lib64 fi if test -f $DIR/lib64/libldap.a then LDAPLIB=$DIR/lib64 fi done if test "$USERLDAPINC" != ""; then LDAPINC="$USERLDAPINC" fi if test "$USERLDAPLIB" != ""; then LDAPLIB="$USERLDAPLIB" fi # # Some systems require liblber also # if test -f $LDAPLIB/liblber.a then LDAPLBER=-llber fi if test -f $LDAPLIB/liblber.so then LDAPLBER=-llber fi if test -z "$LDAPINC" -o -z "$LDAPLIB"; then echo "(Open)LDAP include- or library-files not found." echo "If you want to perform detailed LDAP tests (queries), you need to" echo "install LDAP an LDAP client library that Xymon can use." echo "OpenLDAP can be found at http://www.openldap.org/" echo "" echo "If you have OpenLDAP installed, use the \"--ldapinclude DIR\" and \"--ldaplib DIR\"" echo "options to configure to specify where they are." echo "" else cd build OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-ldap clean OS=`uname -s | tr '[/]' '[_]'` LDAPINC="-I$LDAPINC" $MAKE -f Makefile.test-ldap test-compile if [ $? -eq 0 ]; then echo "Found LDAP include files in $LDAPINC" else echo "WARNING: LDAP include files found in $LDAPINC, but compile fails." LDAPINC="" LDAPLIB="" fi OS=`uname -s | tr '[/]' '[_]'` LDAPLIB="-L$LDAPLIB" LDAPLBER="$LDAPLBER" $MAKE -f Makefile.test-ldap test-link if [ $? -eq 0 ]; then echo "Found LDAP libraries in $LDAPLIB" LDAPVENDOR=`./test-ldap vendor` LDAPVERSION=`./test-ldap version` LDAPCOMPILEFLAGS=`./test-ldap flags` # echo "LDAP vendor is $LDAPVENDOR, version $LDAPVERSION" else echo "WARNING: LDAP library files found in $LDAPLIB, but link fails." LDAPINC="" LDAPLIB="" fi OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-ldap clean cd .. fi if test -z "$LDAPINC" -o -z "$LDAPLIB"; then sleep 3 echo "Continuing with LDAP support disabled." fi xymon-4.3.7/build/Makefile.test-ssl0000664000175000017500000000033611070452713016541 0ustar henrikhenrikinclude Makefile.$(OS) test-compile: @$(CC) $(CFLAGS) $(OSSLINC) -o test-ssl.o -c test-ssl.c test-link: @$(CC) $(CFLAGS) $(OSSLLIB) -o test-ssl test-ssl.o -lssl -lcrypto $(NETLIBS) clean: @rm -f test-ssl.o test-ssl xymon-4.3.7/build/test-uint.c0000664000175000017500000000022611070452713015422 0ustar henrikhenrik#include #include int main(int argc, char *argv[]) { u_int32_t l; l = 1; printf("%u:%d\n", l, sizeof(l)); return 0; } xymon-4.3.7/build/md5.dat0000664000175000017500000005746611615231425014523 0ustar henrikhenrikmd5:2aef75abd6973d322c5ab1b1d52d2a4f webfiles/acknowledge_form md5:a76f9c67d39286495adf558ba7f4a412 webfiles/acknowledge_form md5:c763137d2728671f4e34af9b213cfd63 webfiles/acknowledge_form md5:205e90ad0bd8da45eaf60af92af3671c webfiles/acknowledge_header md5:43351eb1cf5732e0d03fdc79fd35c8b4 webfiles/acknowledge_header md5:497b4840642f76ac9c79ec8cde7683e3 webfiles/acknowledge_header md5:5c8e8c31770e200a2874a3065bac7b69 webfiles/acknowledge_header md5:6d850290b2b67c67d93ab11dbd33ed40 webfiles/acknowledge_header md5:8a9daf4a620fd82b40f375823a1dd4a8 webfiles/acknowledge_header md5:d79fb47e79f7d29d51a7325a9d243a7a webfiles/acknowledge_header md5:db9a4cba68249970f061f3d96ecd3eb9 webfiles/acknowledge_header md5:e81bf64853683dc48ac1daa8be2dc4c8 webfiles/acknowledge_header md5:f5fff2638b80aa0b05f7dc6f53551e44 webfiles/acknowledge_header md5:0a6a33b186d06ea3b1ce20dd2da0e28e webfiles/bb2_header md5:493c4d72368efb374e5f3009097eb922 webfiles/bb2_header md5:4dedb331a60d9c8e253e3db341722812 webfiles/bb2_header md5:57ce6cc343e9de8a42e4ad34ac348ea8 webfiles/bb2_header md5:64c4e1eff77b5d76eccf58a90fdde99e webfiles/bb2_header md5:c7dd652fac52bae36584391f66e06346 webfiles/bb2_header md5:f012d6c2991d25ca65df1cfd2dbbc920 webfiles/bb2_header md5:679ecf0460d9ee066b53703edfb45bf5 webfiles/bb_footer md5:b927ca734b83973f47762278b1015d32 webfiles/bb_footer md5:c8564c73cec7da1e297ba94ed56f693a webfiles/bb_footer md5:d507ae62602354e576423a7216e875fa webfiles/bb_footer md5:dab9ebea0665fc43eb5beb70d4a02756 webfiles/bb_footer md5:474bc71541cb592df74511a22863cc86 webfiles/bb_header md5:4ac5d2b7f12e01ac6d474a9a3ebd19ce webfiles/bb_header md5:55546fa53e7cef87d5161132376a4dd5 webfiles/bb_header md5:b4faefac3b7b3bf2687ff86b567c616f webfiles/bb_header md5:c9fc71f108007688f15b583aa36fda39 webfiles/bb_header md5:d53ac4c4d96c82c0a03e77cc0d3ae99e webfiles/bb_header md5:e39355b7be74b49b49f8cf0650f43284 webfiles/bb_header md5:1d9e995b3778cf2c40529b47a7af8739 webfiles/bbnk_header md5:5035b17fa863349fdf2ed10524f1e753 webfiles/bbnk_header md5:9094360854ab61a4d2f035a4108ae8c9 webfiles/bbnk_header md5:a3f93fea81ac47b1d78706f6dd0689fc webfiles/bbnk_header md5:b33729d38c73e2ce04514c30a0058f20 webfiles/bbnk_header md5:eb11ca199fb132916136d85ea2237068 webfiles/bbnk_header md5:ebefec59449af568839fd4c3b99c944a webfiles/bbnk_header md5:2fae67a97ac7d2dd1d029416dff48389 webfiles/bbrep_header md5:6a896754a4a7980674c15d42e1f10ba7 webfiles/bbrep_header md5:811f9be38e8554d21d368b1a1097e923 webfiles/bbrep_header md5:9ba890901af2d23bd9344f7a720a43b9 webfiles/bbrep_header md5:d03f2266e60ad6f88e5df878492133bd webfiles/bbrep_header md5:eb6fefe16aade1408f3956ef2c918d9e webfiles/bbrep_header md5:fe409202eed8f8f3f2fffbe35d14820b webfiles/bbrep_header md5:6a801ecc0819d2d5c505945f367b670c webfiles/bbsnap2_header md5:6cecb03deb4f505479e1f2cf582c3bf4 webfiles/bbsnap2_header md5:90f22e6c2933c95417a1d4a060194386 webfiles/bbsnap2_header md5:059a4404abd26025c43111e1f2d83baf webfiles/bbsnap_header md5:77ff5d11d89b6e4b125289e2de15efc6 webfiles/bbsnap_header md5:801e80f099c190002847b20def4a0ec8 webfiles/bbsnap_header md5:8174f9bc6eb7d8c4019331c356b7c83b webfiles/bbsnap_header md5:83c5980f3af0548d7aed029711a67f9d webfiles/bbsnap_header md5:abd6b4accb9cb39e3e4491a8c0905623 webfiles/bbsnap_header md5:4accea67f8fb44aeea4c58c2e70ec6a0 webfiles/bbsnapnk_header md5:d5fd55e8fd8caa675dc2bda3f2d886a6 webfiles/bbsnapnk_header md5:fe3e7e17ff71f189bf5bae2bf924fd16 webfiles/bbsnapnk_header md5:023a7ee2e6267b8bb5f84951ff2e14ef webfiles/columndoc_header md5:40c8ae5d3477449c630110677964ac3d webfiles/columndoc_header md5:488cdd7334c3eb9c193862abb37e7d3e webfiles/columndoc_header md5:4db0e94127a7a106bb9b0a7d868b0b48 webfiles/columndoc_header md5:78891f699ad3f376f67b0e61b0682ebb webfiles/columndoc_header md5:7e183c7cb5b8ce1b05733f9c5f60ecee webfiles/columndoc_header md5:b404bcecd8e569afa285a20062d94e6b webfiles/columndoc_header md5:de754f14cc93e6e1fa3bc29e32b12244 webfiles/columndoc_header md5:e8abaa53a14b000359bd8efca5efeb29 webfiles/columndoc_header md5:f087d77977baf65f2045407186460693 webfiles/columndoc_header md5:d41d8cd98f00b204e9800998ecf8427e webfiles/confreport_back md5:d921357393ccb03ffaef687bfc2e761f webfiles/confreport_footer md5:24b77246484e86b6518ab935e951f77e webfiles/confreport_front md5:49d391762e953602e903ea6dcb795c79 webfiles/confreport_front md5:704d7803ae223200faf7d88f775addac webfiles/confreport_front md5:ba20d150c6e40573658a06f42f0594dc webfiles/confreport_header md5:d212a7ad882942aa236e85ef4ed615a9 webfiles/confreport_header md5:578c97c324eab20dc9037ed1b5427468 webfiles/critack_form md5:06b49128133a91498bd2e49429ceda25 webfiles/critedit_form md5:6dd58e48fe7d18233d4cafaf7d3004f4 webfiles/critedit_header md5:c17946f11c129c5cde57fb9620ea1450 webfiles/critedit_header md5:f353bd6812cbe0b401589936a2c3bdd2 webfiles/critedit_header md5:21db9961013bc43684c4069beda7462b webfiles/critical_footer md5:73ee8e0ee307e0c7647d9cc62811f79d webfiles/critical_footer md5:0a222fda13f6e3f7b4744cb9de2da149 webfiles/critical_header md5:3b2143d0c4d641504561cb19707d652c webfiles/critical_header md5:d34d8b055b39a084bebb37cdd93b8651 webfiles/critical_header md5:2b21da4cdd5544a1efd1cb6dbbc12597 webfiles/event_form md5:2cad1811e9d8e08d6677cec8cb7a7578 webfiles/event_form md5:49006fc8f40cac94eca246b2fe5faa97 webfiles/event_form md5:5c7b30b4ab4332c421d674df90ffc4dd webfiles/event_form md5:e48b4c57298510d191d4e25cac276186 webfiles/event_form md5:49c01f3cefeec8d33f3233c216955b0e webfiles/event_header md5:61bde058b46eaed4d9bb394092982953 webfiles/event_header md5:65e553d7a63b1636e857f0901987220a webfiles/event_header md5:79a6d29ab45c7c50ea508c802718ea18 webfiles/event_header md5:7f28375fa8ded2bb7921f5e061d338cc webfiles/event_header md5:982e40879bfcbcc034dc85a9012291e6 webfiles/event_header md5:ba1f75db90350d0435a3456ca346a8cd webfiles/event_header md5:d663673082a6a047c2389e87345e8554 webfiles/event_header md5:fbe38443ac25e9fc8c8d0185a5d8012a webfiles/event_header md5:1ba14ae777f497982d377a8c8bcb4757 webfiles/findhost_form md5:b6a0e5dbd16655e0381987e3273f15a6 webfiles/findhost_form md5:d5bfa8db22b85ffae86b53bff405ba54 webfiles/findhost_form md5:dc043069fbc9d6fbd366118030b999ec webfiles/findhost_form md5:014520c12bad9d3b6813af8caadd72d5 webfiles/findhost_header md5:196ea4f94210b930f963d60e3a9a2413 webfiles/findhost_header md5:290c890f5ea1d83531d1f5d57ca6087b webfiles/findhost_header md5:5fd00142761f5a35bf551f7a76da4918 webfiles/findhost_header md5:75e229b43ad1ffce9cb82ada92010f4b webfiles/findhost_header md5:7b6271f002bd76509a828d1a1de0fbb5 webfiles/findhost_header md5:882d30d43808e3c3f565dda72b4f7379 webfiles/findhost_header md5:9d332c0401798845e73df7cf623ec4bd webfiles/findhost_header md5:b44092ed93b418463cd3a70de72a4719 webfiles/findhost_header md5:d9264b516ba58f25cd468e24830a28f4 webfiles/findhost_header md5:e6b1dc37b53a84c59f9a4db8740b1faa webfiles/findhost_header md5:2a418ce8601d6556975508e06aeee5c4 webfiles/ghosts_header md5:4f5c88a6a3bfb41e57b9801b53ad9c29 webfiles/ghosts_header md5:6c0636a026583b2b1b7f7a188a4316e7 webfiles/ghosts_header md5:6ec27117c57776b55330713fc46f69d0 webfiles/ghosts_header md5:7385028893b6dd65014960bda8587c26 webfiles/ghosts_header md5:c77887553449ea8852989b5af0e43c09 webfiles/ghosts_header md5:13335f1bd9efe02d4fbcc6afcd679a1e webfiles/graphs_header md5:4c95fdb687ff65202afb2d52ba2852e5 webfiles/graphs_header md5:6dc264cc490b1ae66f5277638bf6428f webfiles/graphs_header md5:7475d6cd57634b07d9644a23dd1b0809 webfiles/graphs_header md5:7799acee834dcdf087475295d5ca8620 webfiles/graphs_header md5:86e44561be6cd5f1034261f636ee0260 webfiles/graphs_header md5:a800f27856ac7cbab9dc18a2b5a70dc8 webfiles/graphs_header md5:ad27fb97f7a3699fdcd6e0bb1c566d23 webfiles/graphs_header md5:c0556131f57a929ddba5b6534b46c05f webfiles/graphs_header md5:df9f3d1bddb0c235fd5c79f166c64fa7 webfiles/graphs_header md5:01627dccc3a51003b39c8d8186597782 webfiles/hist_header md5:01d77cfe8ce9e630bce443c1f84b67ba webfiles/hist_header md5:0fb000cfe04a0c9e9d256cb1c46a7908 webfiles/hist_header md5:153b0f20cb7955900f9dbce9a8e96806 webfiles/hist_header md5:21633df466936d9127646e294c2da585 webfiles/hist_header md5:297866f9c07f55f6b7428388877734a4 webfiles/hist_header md5:3ef3eeeb8911b8b7f549ebf9914f853b webfiles/hist_header md5:44681e656173a378615b2d92b117215e webfiles/hist_header md5:4e9a8d280a2fe22bdc300ccf099da3ed webfiles/hist_header md5:e3f6247196a3b8d81c96c45849adb955 webfiles/hist_header md5:0743068fd065363cc4f012999fa295c6 webfiles/histlog_header md5:4310d90bd4b437a315dc3bf11ea3f086 webfiles/histlog_header md5:492374a6e3ad22db61a1adc85df30b1f webfiles/histlog_header md5:5ace7ed2cb41587f9ad9a96093d6cb8b webfiles/histlog_header md5:5bd4e7b3bf41f1f38d5d209157c1dbf8 webfiles/histlog_header md5:8850587715fcd0a1a7b9ed1c7a7f6f59 webfiles/histlog_header md5:9fbcf8fd18f69b0ac7fb2993d2dd5142 webfiles/histlog_header md5:c2bbba732ce2e66e56bd4f722607e9d6 webfiles/histlog_header md5:d75ab4808d7492c974f7db6765649ec0 webfiles/histlog_header md5:e3aee3404892a5117f79b0c8802a55b3 webfiles/histlog_header md5:43b22c9b2bbe4d19e6f1be5047dcd013 webfiles/hobbitnk_footer md5:834b0816b8ba716e35bed8bab1462a09 webfiles/hobbitnk_footer md5:c37a4c5f3fe8d412cc8002b235126f21 webfiles/hobbitnk_footer md5:d20c72f46d33284ac2006960369f9401 webfiles/hobbitnk_footer md5:1d9e995b3778cf2c40529b47a7af8739 webfiles/hobbitnk_header md5:9094360854ab61a4d2f035a4108ae8c9 webfiles/hobbitnk_header md5:eb11ca199fb132916136d85ea2237068 webfiles/hobbitnk_header md5:04370521e21a8f5e8a7f5a4bd90be710 webfiles/hostgraphs_form md5:1a89274a84bcb504ec795efe38790a29 webfiles/hostgraphs_form md5:0b93340f61b0ab8a9679ed63f8fdaf1f webfiles/hostgraphs_header md5:6fbce8b850a2b6f6c11eae3de1780a53 webfiles/hostgraphs_header md5:aedd2c25005ee3967fbcfe3d786919bb webfiles/hostgraphs_header md5:c5bb29b190d16c6ca742989e9a1de086 webfiles/hostgraphs_header md5:dcf906c6360cf53462d068b9ce23f0bd webfiles/hostgraphs_header md5:f40ffcfb250ba6cc7d2c6070bf23e8c0 webfiles/hostgraphs_header md5:742b6877f8ce89c7f10c99d28ada808b webfiles/hostlist_form md5:9f0b363a4c8519411e99e44726083b59 webfiles/hostlist_form md5:2cae7e396594a66894f134432bd83cc7 webfiles/hostlist_header md5:598bbbc38bfc7ba88543850eacf98fdb webfiles/hostlist_header md5:93b82bc3ea9af5e11c59fd8a466558c0 webfiles/hostlist_header md5:a6e156a339770dfaa002cf3ff62d9247 webfiles/hostlist_header md5:cb6a2759593b4ad6e242cfaae5a31dfa webfiles/hostlist_header md5:02e7fc8ea569046453deeb97b5acddc6 webfiles/hostsvc_header md5:1b7a69a900b93d30ef3416360bdd4d7f webfiles/hostsvc_header md5:21f51fbc2b68080bd0c9c2b2b8591fde webfiles/hostsvc_header md5:24c42b758752dd05c889ca4990990712 webfiles/hostsvc_header md5:3eae7096b99bdfe104651be21998a56a webfiles/hostsvc_header md5:560cf8fe8dc232a240cd4af8fb4b75fb webfiles/hostsvc_header md5:6dffec4e8d9b920e0635a181969871ea webfiles/hostsvc_header md5:716bff33489a38a2b56ca08b328701a7 webfiles/hostsvc_header md5:b302577baf5e5523e39653fc20224789 webfiles/hostsvc_header md5:c515695830039843ed2eab2ba71b37ac webfiles/hostsvc_header md5:cd6cdc0faa7e7c927893556dfafddc90 webfiles/hostsvc_header md5:04dfb1ba775e198224440bae14ef5d04 webfiles/info_header md5:0fa7f284384cf650788a85f9fd67e80a webfiles/info_header md5:100b94781cee0834b87f920ef9715245 webfiles/info_header md5:2338498e9800f77363e6452fe6ee08a5 webfiles/info_header md5:2afb7ea168587710bcf3a154eab5846c webfiles/info_header md5:2c9fd562e7db17cfc3efe32b4a223059 webfiles/info_header md5:553d801c12f0ecef0f41192d416ad883 webfiles/info_header md5:60b96f5f65206e650dac7727fc676f66 webfiles/info_header md5:6492e8e810dbfb46e5e7c967fddb892c webfiles/info_header md5:68b104dae211825808194a32f3ad3a05 webfiles/info_header md5:d33a2f0bbdaef3aaa9d9f4a5757978b5 webfiles/info_header md5:02c12f6ad41ac910227d69b73b28e189 webfiles/maintact_header md5:2c243255e690fe983785ba200c28616f webfiles/maintact_header md5:64bc22c385336eaacfd7a6791e330643 webfiles/maintact_header md5:75777a63aa42d7497e3e54f7c27b1af0 webfiles/maintact_header md5:97eac84720003897d2fdeb7471f20805 webfiles/maintact_header md5:c367d4fee01483db265e8e085ad0cb1f webfiles/maintact_header md5:1a5ad876e1e60e8ff8b20f14d18adf33 webfiles/maint_form md5:2228bd803ecddda0c872a55b9bcce38c webfiles/maint_form md5:352b4de71f8e5459322ffb6e84d76e4a webfiles/maint_form md5:03b90a406ad8188e0b14d01ac536f3fe webfiles/maint_header md5:31c57123b77c406f0d870eae82ccabb0 webfiles/maint_header md5:379921dfcc1a337ec2077436cebad9f0 webfiles/maint_header md5:3a6e92a359c93e3dda812f23bb56cbed webfiles/maint_header md5:966b865bbb9c67f71b26aa5dddb87d32 webfiles/maint_header md5:b6d4b7d210f57ce85aef99acab48f84d webfiles/maint_header md5:c412630b335d35f136cf81b5b93d282e webfiles/maint_header md5:c77acb7ab0ed935efbbf52974ae02262 webfiles/maint_header md5:fa4fcc8c6c4eb71ea4e76be8b0ddb881 webfiles/maint_header md5:fad36d4b7d04a94c4bb44ea9adedce9b webfiles/maint_header md5:78a9cd70a37dfa793423ffe5be770434 webfiles/nkack_form md5:f4ad6dfcfc96d8588e8a09b3ef59c90a webfiles/nkack_form md5:1df6c9d0e2a5b8253af64ee4d809ddf9 webfiles/nkedit_form md5:233b4a8fbf6cc513143001267be0adbf webfiles/nkedit_form md5:84c18fac4a57fcce6e5815c30844a86f webfiles/nkedit_form md5:bd794af473b37866b09b4ca590f4e6ed webfiles/nkedit_form md5:448fb5f498b13b7e5f89360003266127 webfiles/nkedit_header md5:a54004eaacf0575e1f7f829d45c406ee webfiles/nkedit_header md5:d2e696ae9cc8243c00e30772d67a92d2 webfiles/nkedit_header md5:ee95fdd7461d7518a41f448b51f0c85d webfiles/nkedit_header md5:679ecf0460d9ee066b53703edfb45bf5 webfiles/notify_footer md5:b927ca734b83973f47762278b1015d32 webfiles/notify_footer md5:660d6e4ae0578496e35178f5dd0382c1 webfiles/notify_form md5:c4e7cd276c4a88a9970594f102d78b18 webfiles/notify_form md5:029d369f57ca69d5fb3397a5a05dd4bc webfiles/notify_header md5:4b531c44a8d1b4d26080917ceb27bf5f webfiles/notify_header md5:64c7882aa785d49c13f77c2146ff2809 webfiles/notify_header md5:ae4db871545396b32f3b1ded6d7199d8 webfiles/notify_header md5:c1df159a27547efaa9587c6252fcef5f webfiles/notify_header md5:c61a13654bf5ad0041e572f50d909af0 webfiles/notify_header md5:62daa40456f23e6b67469018fd6851d6 webfiles/perfdata_form md5:0fc17291c4a0473908ee696df0c252fd webfiles/perfdata_header md5:88f0b2d8558257191776c7448e7d1b0d webfiles/perfdata_header md5:94f05352f7c2942833ecc1d3a70f312e webfiles/perfdata_header md5:bf74bde923d7f179d57b697b7e2d1d69 webfiles/perfdata_header md5:5a5fad279887ab62368651d01f5fa930 webfiles/replog_header md5:a3c57748c6384747d331eecf50f6cb67 webfiles/replog_header md5:b870b82a721d97f8e454d87f206931cb webfiles/replog_header md5:c67be08b9e26df6ff1c497f4148ad812 webfiles/replog_header md5:cec633be45f2dab91210dcfdd6244ebd webfiles/replog_header md5:d930c2e8041d1aa34c2e9a9c58e48666 webfiles/replog_header md5:dda39e97fe87ed5d5487c071d99707a7 webfiles/replog_header md5:de60aa05e19471556c17039bff9013b6 webfiles/replog_header md5:deb80a076dcce05567290f0e13f1aa0f webfiles/replog_header md5:f333ce96b7ba5aa8f1a8382d9b0f44da webfiles/replog_header md5:5469e970c51cdd6417f6230ca1397c24 webfiles/repnormal_header md5:5fbb435afe6127f9d8c32a2679feee9b webfiles/repnormal_header md5:b1c85b082fc7ac1f49e851d3f42626ab webfiles/repnormal_header md5:1600c0858a63f2ea16e31d68c23c1c8d webfiles/report_form md5:1ec8e1e00b792c9d4b893616600c3625 webfiles/report_form md5:22cb2a34475ef16c6ea2d70f74edf448 webfiles/report_form md5:99b1dd3b0e281a3a22a2c5821aaaa0a6 webfiles/report_form md5:c45479931e8863f29071733e267440c3 webfiles/report_form md5:08cd5e23cc5b091fbd54dd99ab970eb9 webfiles/report_form_daily md5:3c2bab1a60aeef54ff6106cabc1fd242 webfiles/report_form_daily md5:e69ae5138ab9134422b3cc96993f5ca2 webfiles/report_form_daily md5:6a91b8d2036878883e8779af9b45244c webfiles/report_form_monthly md5:c6fef48851d5cb736ba8ff4b51317464 webfiles/report_form_monthly md5:f86629e4566ad17fab4559919bd34291 webfiles/report_form_monthly md5:3af281e115975b6f139f3f79899b691d webfiles/report_form_weekly md5:7aa455d83886d52a83d7b24d5993c65d webfiles/report_form_weekly md5:f78dd68561c6d8bcc15f919db08fc1dc webfiles/report_form_weekly md5:0a2e9a98428077f2d653841917394976 webfiles/report_header md5:28cb27832f238da19fea4ce263d49e45 webfiles/report_header md5:5373ce8c0780516f53c496a6b571fc68 webfiles/report_header md5:57af05fcb91d32eb5725cf4fcdbc4595 webfiles/report_header md5:8df11e75679f001591ad99069edf176f webfiles/report_header md5:9f3e0a09a8ae034ef1ae1087e4798bdd webfiles/report_header md5:a429189b343613487385804066e52b86 webfiles/report_header md5:b162c38cdc039f78b627c389a2016e5e webfiles/report_header md5:b5f3411b293ba8d116881b59fc3a75ce webfiles/report_header md5:ba52c5aa47f656a4994bef126caf3d10 webfiles/report_header md5:007ab114780bd1339332a09c94f08607 webfiles/snapcritical_header md5:774921d90c4531dbd8665852e1e55ddf webfiles/snapcritical_header md5:cfa74bc7d493f007db5237983ba378c4 webfiles/snapcritical_header md5:18a7b1258bb1f074d4e53d7baf456338 webfiles/snapnongreen_header md5:c6dac37646074d840904d89bf958e533 webfiles/snapnongreen_header md5:d54b7e479d838d28a0bc01d9f7c72038 webfiles/snapnongreen_header md5:0121bc271e6dcb1127e21ffd37daa280 webfiles/snapnormal_header md5:472e23dada587d022290079ab981d240 webfiles/snapnormal_header md5:4aef4af069e68e41392e782504e4a453 webfiles/snapnormal_header md5:178fccce4e579115d29413721f7f920d webfiles/snapshot_form md5:51a077e0abe5299f4e96c4e67961efc1 webfiles/snapshot_form md5:817527af6850157b1471f34fb77d1a54 webfiles/snapshot_form md5:d069271f0256f14dfa2ba5d97effe217 webfiles/snapshot_form md5:07d4847d28ff996ec6a635b79fe727ca webfiles/snapshot_header md5:1afb45714ed5911dd448dcf2771ab72f webfiles/snapshot_header md5:367fc4351b0e18d432b81946332dfeaf webfiles/snapshot_header md5:68363d5ffc97b36221e898bf4e100e2a webfiles/snapshot_header md5:760a1b470f3dfcbc36e453c4c6801405 webfiles/snapshot_header md5:a331684b00910024e9887ee3ce902b8c webfiles/snapshot_header md5:b871efbc9b0a04b269dafb62c6d2286f webfiles/snapshot_header md5:e8c64a75e30ff1d201534e57da0eebb2 webfiles/snapshot_header md5:f088c56cb6da8894fd9cc6d61da06066 webfiles/snapshot_header md5:0a222fda13f6e3f7b4744cb9de2da149 webfiles/stdcritical_header md5:3b2143d0c4d641504561cb19707d652c webfiles/stdcritical_header md5:d34d8b055b39a084bebb37cdd93b8651 webfiles/stdcritical_header md5:624e3d52c93db2c6f3601d8e24203267 webfiles/stdnongreen_header md5:c682264443af81b4e2dcc02006ca69f9 webfiles/stdnongreen_header md5:fe55695a2cdb46f7caa2ece116a03eb2 webfiles/stdnongreen_header md5:d57bec4b9fe9dc8dd8f74b590d059ba3 webfiles/stdnormal_footer md5:14395d6a02ce1007374141f691be7e04 webfiles/stdnormal_header md5:725cbb67bf038f584356dcc4978ff946 webfiles/stdnormal_header md5:ef6cb4583f0a98485cc0f090098e14b1 webfiles/stdnormal_header md5:d41bb6227216667076f8e90c75512fa0 webfiles/topchanges_form md5:4e88e5fec96e1c3e927cf262456cf82c webfiles/topchanges_header md5:b7edf3431851324fb2ebdd71552c3afa webfiles/topchanges_header md5:daa843834a2a8e709b1248d78ba93693 webfiles/topchanges_header md5:e7d6bc63b04dd13aa55ff126f3a3f29e webfiles/topchanges_header md5:77a26d505caa295c0124663d50bd6dc3 webfiles/trends_form md5:78765fbe54edc0ee84dc07b3ba829152 webfiles/trends_form md5:b97f9484757a782160ae65f7578452e5 webfiles/useradm_form md5:64acc7c5c91cae0052a9abe95a75a0b1 webfiles/useradm_header md5:8ed0452ebc8934e7a8c5f78d2f37efbb webfiles/useradm_header md5:b20204911a14cf4360e0e02bed6aa1a3 webfiles/useradm_header md5:eb7962eedea531ac0437cd7ba99b2f6b webfiles/useradm_header md5:152758a0a0a028353ea10fb1824e39c1 webfiles/zoom.js md5:1781c28a551f54aba99821a0f70065ad webfiles/zoom.js md5:2e55ae03b1e9deebd4d98f1bdc7fc9fc webfiles/zoom.js md5:4cdb87332f200d1d6b1cf1cd37fcd5ff webfiles/zoom.js md5:4d2d2cd984c9046d2332d5e59c527a59 webfiles/zoom.js md5:5bf16eb30301d49011406f71f839c58b webfiles/zoom.js md5:92552cb1b50d3dccd67a7988d08dbf81 webfiles/zoom.js md5:0f7a1e430bd5f3bf75b6876dbb31346a wwwfiles/gifs/arrow.gif md5:15f209b4350be229473a5a17d9f47043 wwwfiles/gifs/bkg-blue.gif md5:550d2867f08912e067ac51dee5552003 wwwfiles/gifs/bkg-blue.gif md5:36b45d8e64a84350099f7fb06c26da0b wwwfiles/gifs/bkg-clear.gif md5:c58f1de41747454ea0beda3ac8cfc4e9 wwwfiles/gifs/bkg-clear.gif md5:30f0bc11f679d95e108da969080c5fc6 wwwfiles/gifs/bkg-green.gif md5:e414d855710fb2c8f4fb50be63b1f52d wwwfiles/gifs/bkg-green.gif md5:339f2319ad364d4ed36ddc0c06f2efc9 wwwfiles/gifs/bkg-purple.gif md5:fffed8a62895572e03fe00ebb7d4d650 wwwfiles/gifs/bkg-purple.gif md5:50920dcb02ff2ac3c317edb8dbdfe047 wwwfiles/gifs/bkg-red.gif md5:7d3c891e2c8d16a9e7529c8a41f2c6be wwwfiles/gifs/bkg-red.gif md5:09f9232bb6d3dba27604e80bacea6caa wwwfiles/gifs/bkg-yellow.gif md5:6e2d929901ca1297a6518129c40dfbf3 wwwfiles/gifs/bkg-yellow.gif md5:6edab754e263eca78cd14414e9c598eb wwwfiles/gifs/blue-ack.gif md5:db01e36f1f4f7623f730525d5a1785a9 wwwfiles/gifs/blue.gif md5:1f7e0d09d3bdcc785556e927031443e6 wwwfiles/gifs/blue-recent.gif md5:7dbeb09959873840f4e2aeddfb2c76a1 wwwfiles/gifs/clear.gif md5:9537b303a8ca9006f7132cd7d10caa74 wwwfiles/gifs/clear-recent.gif md5:b7e7717acc440e90f480fae1e2ca750c wwwfiles/gifs/favicon-blue.ico md5:40a4a21ce27ed5a127dc3182f8e6f761 wwwfiles/gifs/favicon-clear.ico md5:081a360ef5d509a41251308c2592b929 wwwfiles/gifs/favicon-green.ico md5:3a0f210c04cf0ca8112ebdb5acf6931a wwwfiles/gifs/favicon-purple.ico md5:6030caa1c3efb1f78d3371db897d4c1f wwwfiles/gifs/favicon-red.ico md5:f8096714c7bf4ba1de5bb121f37e5843 wwwfiles/gifs/favicon-unknown.ico md5:ec30822e46c1d23bcddf50e901f0841a wwwfiles/gifs/favicon-yellow.ico md5:fcd9f31b34edc95c424654eacd2ce3df wwwfiles/gifs/green.gif md5:720ee80877f25d10e18a2435df8e5515 wwwfiles/gifs/green-recent.gif md5:974f3c1e72b6cec1ff089cbcc0874340 wwwfiles/gifs/purple-ack.gif md5:69f79b571c3dc10be10c6321b37f8480 wwwfiles/gifs/purple.gif md5:59be91136046b6ce6078ff5e51b92f1f wwwfiles/gifs/purple-recent.gif md5:2900d3a7ce74c31df2c3e29081e89ec7 wwwfiles/gifs/README md5:4785eef9a563e895a949ab6fbd43f24c wwwfiles/gifs/README md5:54c9e6c5cf9f3af55c9987f126afe70c wwwfiles/gifs/README md5:71f2e080cfad6a23e2d0f241f071c5e6 wwwfiles/gifs/README md5:771a69c81b193f84a9bbf951b9533d7b wwwfiles/gifs/README md5:569c22387f3e0eae9e167a4e723c4dad wwwfiles/gifs/red-ack.gif md5:cd27207c6975c5fd95acb3edc05a663f wwwfiles/gifs/red.gif md5:3fcb4f34b6579377b91e5aac1125609b wwwfiles/gifs/red-recent.gif md5:cc7def940d5512b3db198bd4198da72f wwwfiles/gifs/unknown.gif md5:cc7def940d5512b3db198bd4198da72f wwwfiles/gifs/unknown-recent.gif md5:9d2ba1a166533c4e7275c8baeee09328 wwwfiles/gifs/xymonbody.css md5:9817d961c3a96a4b4e61f3be2eb0b5f0 wwwfiles/gifs/yellow-ack.gif md5:be7f9a896dad98a28e7fc621fc05934e wwwfiles/gifs/yellow.gif md5:c48ee1c1706fb25f84241d78c2320561 wwwfiles/gifs/yellow-recent.gif md5:2676442ce3bb26c96aa0173d462148d5 wwwfiles/gifs/zoom.gif md5:db3101fb3b347e6fd3f8fbda67e2e390 wwwfiles/menu/b2t-blue.gif md5:bfba2b5ee74c7be23107f580cbfe7d65 wwwfiles/menu/b2t-grey.gif md5:cb0fd3f28fcedcca636d9017e7cf3909 wwwfiles/menu/menu.css md5:fa0d4c587d093e953db4b21e7bacd2f8 wwwfiles/menu/menu_items.js md5:157744fbdd343c55f2866220370277a8 wwwfiles/menu/menu_items.js.DIST md5:2471b1a55835ca094a767ef84a8c923b wwwfiles/menu/menu_items.js.DIST md5:25501f4c319c14cc894fd485d1371c67 wwwfiles/menu/menu_items.js.DIST md5:33222e87f8e5ea46d187383c8ba8ee76 wwwfiles/menu/menu_items.js.DIST md5:42fc35954fb111e39fc7b13a44e0229b wwwfiles/menu/menu_items.js.DIST md5:4698934d41c97abbf79e31f2b254db24 wwwfiles/menu/menu_items.js.DIST md5:676bc52bb8183124b87c90f69afd47a2 wwwfiles/menu/menu_items.js.DIST md5:8a77e51d1da3920af481f96d8aad05ea wwwfiles/menu/menu_items.js.DIST md5:ae61aa6ecb9332a1cb7c4dc1778503e1 wwwfiles/menu/menu_items.js.DIST md5:cdfb74f05ab5f885925666a88973bfaf wwwfiles/menu/menu_items.js.DIST md5:dd15fed90607a93eeec4250c626e4f1f wwwfiles/menu/menu_items.js.DIST md5:feda8f1f05273c80b5048c43f8c38e39 wwwfiles/menu/menu_items.js.DIST md5:47fd53051fb48237501e3cc9d9410cf6 wwwfiles/menu/menu.js md5:7f1a456f123866f518220fad3d1f0137 wwwfiles/menu/menu_tpl.js md5:bc7f251fc00a431f452b9b29042cb5e7 wwwfiles/menu/menu_tpl.js md5:2420fb3d074c15362b4a6380a08e0980 wwwfiles/menu/README md5:a1a37fb8d0abfc5e48b5d00ae5a36f80 wwwfiles/menu/README md5:948d61a485df74b79f75af9406fa3be6 wwwfiles/menu/t2b-blue.gif md5:693c1595105e325af62ad73f5c766041 wwwfiles/menu/t2b-grey.gif md5:52145f5f7f3c5ca6946f18e081ee811a wwwfiles/menu/xymonmenu-blue.css md5:1b68e91383dac5118cfe9c0c9675f0c4 wwwfiles/menu/xymonmenu.css md5:2ec844f6efeaac8fdb5da8badccc3c4f wwwfiles/menu/xymonmenu-grey.css xymon-4.3.7/build/test-rrd.c0000664000175000017500000000124611535462534015245 0ustar henrikhenrik#include #include int main(int argc, char *argv[]) { char *rrdargs[] = { "rrdgraph", "xymongen.png", "-s", "e - 48d", "--title", "xymongen runtime last 48 days", "-w576", "-v", "Seconds", "-a", "PNG", "DEF:rt=xymongen.rrd:runtime:AVERAGE", "AREA:rt#00CCCC:Run Time", "COMMENT: Timestamp", NULL }; char **calcpr=NULL; int pcount, result, xsize, ysize; double ymin, ymax; for (pcount = 0; (rrdargs[pcount]); pcount++); rrd_clear_error(); #ifdef RRDTOOL12 result = rrd_graph(pcount, rrdargs, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax); #else result = rrd_graph(pcount, rrdargs, &calcpr, &xsize, &ysize); #endif return 0; } xymon-4.3.7/build/dorelease.sh0000775000175000017500000000414611535462534015641 0ustar henrikhenrik#!/bin/sh SRCDIRS="xymongen xymonnet xymonproxy build client common contrib docs xymond web include lib debian rpm demotool" case "$1" in "tag"|"untag"|"release") CMD="$1" REL="$2" RELDIR=~/xymon/release/xymon-$REL ;; "daily") CMD="daily" REL="snapshot" RELDIR=~/xymon/beta/snapshot if [ -d $RELDIR ]; then (cd $RELDIR && rm -rf *) fi ;; *) echo "$0 [tag|untag|release|daily] version" exit esac cd ~/xymon RCSTAG=`echo $REL | sed 's/\./_/g'` DIRLIST="" for D in $SRCDIRS; do DIRLIST="$DIRLIST `find $D -name RCS | sed -e 's/\/RCS//'|grep -v private|xargs echo`" done case "$CMD" in "release") make distclean for f in . $DIRLIST do # Tag all current versions a "Release" rcs -sRel ~/xymon/$f/* # Tag the current version with the release number rcs -nrel_$RCSTAG: ~/xymon/$f/RCS/* # Checkout the current version pushd ~/xymon/$f && co RCS/* && popd done ;; "untag") for f in . $DIRLIST do rcs -nrel_$RCSTAG ~/xymon/$f/RCS/* done exit 0 ;; "tag") for f in . $DIRLIST do rcs -nrel_$RCSTAG: ~/xymon/$f/RCS/* done exit 0 ;; *) ;; esac # It's a release - copy the files cd ~/xymon mkdir $RELDIR for f in $SRCDIRS do find $f/ | grep -v RCS | cpio -pdvmu $RELDIR/ done cp -p Changes configure configure.server configure.client COPYING CREDITS README README.CLIENT RELEASENOTES $RELDIR/ find $RELDIR -type d|xargs chmod 755 cd $RELDIR && make -f ~/xymon/Makefile.home distclean cd $RELDIR && rm -f {debian,rpm}/pkg/* cd $RELDIR/../ && tar zcf xymon-$REL.tar.gz `basename $RELDIR` # Change version number for snapshots if [ "$CMD" = "daily" ]; then TSTAMP=`date +"%Y%m%d%H%M%S"` if [ -f /tmp/version.h ]; then rm -f /tmp/version.h; fi cp $RELDIR/include/version.h /tmp/ rm -f $RELDIR/include/version.h cat /tmp/version.h | sed -e "s/define VERSION.*/define VERSION \"0.$TSTAMP\"/" >$RELDIR/include/version.h DAYAGO=`date +"%Y%m%d" --date=yesterday` WEEKAGO=`date +"%Y%m%d" --date="7 days ago"` ~/xymon/build/listchanges.sh $DAYAGO >$RELDIR/changelog-yesterday.txt ~/xymon/build/listchanges.sh $WEEKAGO >$RELDIR/changelog-lastweek.txt fi exit 0 xymon-4.3.7/build/revlog.c0000664000175000017500000000233011070452713014762 0ustar henrikhenrik#include int main(int argc, char *argv[]) { char *date = argv[1]; char *fn = argv[2]; FILE *logfd; char cmd[4096]; char buf[4096]; int gotrevmarker = 0; int gotlocksline = 0; int fileislocked = 0; sprintf(cmd, "rlog \"-d>%s\" %s 2>/dev/null", date, fn); logfd = popen(cmd, "r"); while (fgets(buf, sizeof(buf), logfd)) { if (gotlocksline == 0) { if (strncmp(buf, "locks:", 6) == 0) gotlocksline = 1; } else if (gotlocksline == 1) { if (isspace(*buf)) fileislocked = 1; gotlocksline = 2; } if (!gotrevmarker) { gotrevmarker = (strcmp(buf, "----------------------------\n") == 0); if (gotrevmarker) { fprintf(stdout, "%s", fn); if (fileislocked) fprintf(stdout, " (is being edited)"); fprintf(stdout, "\n"); fileislocked = 0; } } if (gotrevmarker) fprintf(stdout, "%s", buf); } pclose(logfd); if (fileislocked) { /* Locked file, but we haven't shown anything yet */ fprintf(stdout, "%s", fn); if (fileislocked) fprintf(stdout, " (is being edited)"); fprintf(stdout, "\n"); fprintf(stdout, "%s\n", "============================================================================="); } if (gotrevmarker || fileislocked) fprintf(stdout, "\n"); return 0; } xymon-4.3.7/build/generate-md5.sh0000775000175000017500000000047611535462534016155 0ustar henrikhenrik#!/bin/sh # cd ~/xymon/trunk WEBLIST=`(cd xymond; find webfiles -type f) | egrep -v "RCS|\.svn" | xargs echo` WWWLIST=`(cd xymond; find wwwfiles -type f) | egrep -v "RCS|\.svn" | xargs echo` (cat build/md5.dat; for F in $WEBLIST $WWWLIST; do echo "`./common/xymondigest md5 xymond/$F` $F"; done) | sort -k2 | uniq xymon-4.3.7/build/test-strtoll.c0000664000175000017500000000025611070452713016151 0ustar henrikhenrik#include #include int main(int argc, char **argv) { long long l; l = strtoll("1234567890123456789x", NULL, 10); printf("%lld\n", l); return 0; } xymon-4.3.7/build/clock-gettime-librt.sh0000775000175000017500000000120311070667553017530 0ustar henrikhenrik echo "Checking for clock_gettime() requiring librt ..." LIBRTDEF="" cd build OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-clockgettime-librt clean OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-clockgettime-librt test-link 1>/dev/null 2>&1 if [ $? -ne 0 ]; then OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-clockgettime-librt test-link-rt 1>/dev/null 2>&1 if [ $? -eq 0 ]; then echo "clock_gettime() requires librt" LIBRTDEF="-lrt" else echo "clock_gettime() not present, but this should be OK" fi OS=`uname -s | tr '[/]' '[_]'` $MAKE -f Makefile.test-clockgettime-librt clean fi cd .. xymon-4.3.7/build/Makefile0000664000175000017500000000072311535462534014774 0ustar henrikhenrikTOOLS = merge-lines merge-sects setup-newfiles renamevars renametasks all: $(TOOLS) merge-lines: merge-lines.c $(CC) -o $@ $(CFLAGS) $< merge-sects: merge-sects.c $(CC) -o $@ $(CFLAGS) $< setup-newfiles: setup-newfiles.c $(CC) -o $@ $(CFLAGS) $(RPATHOPT) $< ../lib/xymonclient.a $(SSLLIBS) $(NETLIBS) $(LIBRTDEF) renamevars: renamevars.c $(CC) -o $@ $(CFLAGS) $< renametasks: renametasks.c $(CC) -o $@ $(CFLAGS) $< clean: rm -f $(TOOLS) testfile *.o *~ xymon-4.3.7/build/test-shutdown.c0000664000175000017500000000051311070452713016315 0ustar henrikhenrik#include #include #include #include #include int main(int argc, char *argv[]) { #ifndef SHUT_RD printf("#define SHUT_RD 0\n"); #endif #ifndef SHUT_WR printf("#define SHUT_WR 1\n"); #endif #ifndef SHUT_RDWR printf("#define SHUT_RDWR 2\n"); #endif return 0; } xymon-4.3.7/build/fping.sh0000775000175000017500000000413711535462534015001 0ustar henrikhenrik#!/bin/sh echo "Checking for fping ..." for DIR in / /usr /usr/local /opt /usr/pkg /opt/csw do if test "$DIR" = "/"; then DIR=""; fi if test -x $DIR/bin/fping then FPING=$DIR/bin/fping elif test -x $DIR/sbin/fping then FPING=$DIR/sbin/fping fi done if test "$USERFPING" != "" then FPING="$USERFPING" fi if test "$USEXYMONPING" = "" then echo "Xymon has a built-in ping utility (xymonping)" echo "However, it is not yet fully stable and therefore it" echo "may be best to use the external fping utility instead." if test "$FPING" = "" then echo "I could not find fping on your system" echo "Do you want to use xymonping [Y/n] ?" read USEXYMONPING if test "$USEXYMONPING" = "n" then echo "What command should Xymon use to run fping ?" read FPING else USEXYMONPING="y" echo "OK, I will use xymonping." FPING="xymonping" fi else echo "I found fping in $FPING" echo "Do you want to use it [Y/n] ?" read USEFPING if test "$USEFPING" = "n" then USEXYMONPING="y" echo "OK, I will use xymonping instead." FPING="xymonping" fi fi elif test "$USEXYMONPING" = "n" then echo "OK, will use '$FPING' for ping tests" else FPING="xymonping" USEXYMONPING="y" fi if test "$USEXYMONPING" = "y" -o "$USERFPING" != "" then NOTOK=0 else NOTOK=1 fi while test $NOTOK -eq 1 do echo "Checking to see if '$FPING 127.0.0.1' works ..." $FPING 127.0.0.1 RC=$? if test $RC -eq 0; then echo "OK, will use '$FPING' for ping tests" echo "NOTE: If you are using an suid-root wrapper, make sure the 'xymond'" echo " user is also allowed to run fping without having to enter passwords." echo " For 'sudo', add something like this to your 'sudoers' file:" echo " xymon ALL=(ALL) NOPASSWD: $FPING" echo "" NOTOK=0 else echo "" echo "Failed to run fping." echo "If fping is not suid-root, you may want to use an suid-root wrapper" echo "like 'sudo' to run fping." echo "" echo "Xymon needs the fping utility. What command should it use to run fping ?" read FPING fi done xymon-4.3.7/build/Makefile.test-clockgettime-librt0000664000175000017500000000067611070452713021533 0ustar henrikhenrikinclude Makefile.$(OS) test-clockgettime-librt.o: test-clockgettime-librt.c @$(CC) $(CFLAGS) -o test-clockgettime-librt.o -c test-clockgettime-librt.c test-link: test-clockgettime-librt.o @$(CC) $(CFLAGS) -o test-clockgettime-librt test-clockgettime-librt.o test-link-rt: test-clockgettime-librt.o @$(CC) $(CFLAGS) -o test-clockgettime-librt test-clockgettime-librt.o -lrt clean: @rm -f test-clockgettime-librt.o test-clockgettime-librt xymon-4.3.7/docs/0000775000175000017500000000000011671641715013164 5ustar henrikhenrikxymon-4.3.7/docs/editor-clonemaster.jpg0000664000175000017500000023563011070452713017466 0ustar henrikhenrikJFIFHHC  !"$"$C" j  !"1QATUV2357RSqu#aBDW$46Cr%bst dv&8Ece: 1Q!24ARSqa"B3Cbr ?tSI:"'W1cv USAWUNWsړ6.X$u-'t|TئYS_nCT2x8L{g}uVꥈNB-aFGC-$_m hXC¸,³*%7l"33!*RF`v 3 a&N"%kΧ]i",KiMFFe2{-ٝ8afw~G4Vc P86Iu:{NASaի#7YOY_%|Uƣ!K &:kAȈGt Y6Q(1ܓ!VFГRdFv"3X-d4MNi)Y%DG`ln0eu$-|$/bKb8^Ka.£UU &$RГ%ȏ)qٝ8afw]Q0ؖQIJ$Jl\¤%R<ƨm3S)$)EdVbN؋HN jN/6CDL^FdziM$ XY$h5ݳ;pǖ^*^Q}S&;$ʱdF5b|C#MiTaUJLh\u֚Wv̙$CDF8Uq/nQFsa&CՑ=)"JesB9R̬w%g}nٝ8cH+"K"pʤIu $4yHJTXm|? SȫIZZiEȋ"Sdf̭ucv 3 XAWUNWsړ6.X$u-'tal0ݳ;pưsOoSVSRV2ޔ-ۏswm+hŪsyŮRp&N2ʄ55ғ#%$FFw3 JV*He+2guM8+]~54 $fJwgF4*uf Rg[iR3%f2lj3fDwє#G(ԩUMK6wIMAװeݳ;pvb:>3}nJianҒ_Ԃeyԫex UŅKfު'iE\6i\dmJ%]iӐH$).Fd] UVE*;-4ɭQ&GsY˂I-';g}nٝ8bdbF4[~IfS5ۺL r0k|q`l7a?SSNIpa+)JiI*q&|Dbo5ףcILj$ oNJiMCI8ЯtJ%'r2Wf1@GbI02k%ɤNDC!yRSTҪ/BAƊ=cӎn\\w;fD~3 j:tڞjԔH_aK\SZ(SI]֢Z䔫)ftg~qiWULm1ԘtF$ʴk%3bNl0ݳ;pŀ3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 7l1`_ݳ;pv3 yV\mFiA8f[_ahn>C9% k%ÎPLn'sR&Kq(ҕ̒fe~+ؿC+5j5;o}]XϿ?[>䓛/7{~1uVMn-*RTHG[ftzMSl}֣IbImTSRjR #3ގۘh $DDbcG3=׆#㮎˨dZ :哎ˋ:Ȕ#;D(55)iRR2R\pwcG3=0g~'F eڕ8\}aԯ =0g~'F{`N&M'T$]J'ey $#5E5*JFQJH2 w#Rۨ3@=KabɔcqA*I(⽐I"{߉чcG3YRS* T_CJe.iVFJA)ˈbGT`1.ӓ֗THi夐DnfZ iF#V߉чcG3 3EófP d";LQCiB(JBHnffc3OѢŧA7M0KTZ%F7-QtŞ=0g~'F{`N_}^ϧ-Mӟ W㍩ }5fV%JMˌͤec{h<v>y?:0 ԝ7'Xv>y?:0}wt`"uBj]gHHh۲ўZ 7GDrE'ʁ}6E<.D%),-W=0g~'F{`N#NE5MAmaɤEI--x1  ތi^2SHHYģ"ŧXa Km֣~!؈'~cEkт[C}^ϧ-Mӟ W㍩ }5fV%JMˌͤekMmF-\.>r'1SL4oe& eɻ52{[e -\.74[] s *њ슾K-bJ)S&!+=yЕ}mW n]˞F]nYn,n<.=]oSohp0ߘot`6$ayuFe,I֛k4Q IdG+=.]JrOqҏIEdBM15~cEkр,JHT$Rym!8J6j4 ̶Iv."̫qaF Lɱ ѪNlȋ#v RV+ߘotaM15N"RDNDJvԴ ))Q).Xddgr;1ԯ ~cEkцu6Ƌ| 7ڕ8\}bh,;!%ZrKCIJPJQIjB-P( % {5ҤnIq |1w.z>Uvke8ypuM15~cEkрfXPL$[%+6),ԵϺb>. e*|jL0e}H4)ݟ4dfWSohp0ߘot`'cI5X,Pb%Je !׶߆ڭ/40Xitvڧq6l6V_ȲF\F5u6Ƌ| mF{& Yv6b;5K_2˄c m6a×M,ВR"""".!74[]oSohp0C}^ϧ#5Zdyu5hzDJrCm&dHE3)GcZb;u6Ƌ| mF9 DYU&\$-q6NbYFi$+8/0bMCR{pM15~cEkрcS٠bfKHK 4lJ+Rn[l.V`!EE1BQizQ4SfFg-fv1ߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotaM15m xN>Xo+pƯߘotc1h;ULXGUiiO-hk46Vo3"خd\f@'J.}>jWsc<_3cEܪ4beCS8I[3;i.+;=ԯ xN>Xv>y?:0}wt`2J.}>jWsGcG3=0g~'F/ԯ xN>Xv>y?:0}wt`2J.}>jWsGcG3=0g~'F/ԯ xN>X¿r>b[m&[EsV!;-\.jWsR' OjmF-\.jWsR' OjmF-\.jWsR' OjmF-\.jWsR' OjmF-\.jWsR' OjmF-\.jWsR' OjmF-\.jWsR' OjmF-\.jWsR' OjmF-\.jWsFf"w鹎\Mc 9AGFJ"<7 jR}g}Txhn8$ԣ$w$FgPu\* hĕTs1,ڴkw/{\bcQBO*b-WT%˜xu)ViI4g'JN4gj."w"WSjJ7QdGr;pƻĵ v4F G}y)NXͯg|i'~y#=?:qX\w#d7JI5IJIpd@n9YDv5DE?9~>MܸoEuNSj6SK"2V%$RLǣIr3zN^ mW9.2}PJJv$fj2"""w3"pu:\y8Id)Wdgm)34>֫ز9WSlqKQG\yy&e.S[+KimjpG[iZJs(djWC!OmCWZYYmN)Tf[IlJ[i#+e#{J/J՛hhfIG?]x2쓉 I 8 ӛ3]-<-]\ENuڔj=]RY1:qq>!زYuگɊO9m Jc)ϛJd{")nAITD(24%\"=bT,IFg"%tTq)fҍ>_fݟK-QLϷ.ss}+֋Qrh}3?a*nF%B̋!B alo!Wdgȸb+M9괖ɔy32Kje^&c#[8b#DD,,Iqi:D,իJMkvQD{q\ϋgBEC\ϤԝbPFi.5')!_*UqOau4g5,'±ipq îIeFyeM8У}[ngɇ/##"2G'ȋ*nMZGh1M];]_bz+Svfcouю*grsFdmw5s~"c5HW%Q4Yi:{X+ܲ:b/We|:NacW)S:P72_I)pK+fі9 9UjGvlVC56̚Z[M4"$mQmU W(p+tD fEw"iĒ(2;hl;LN(L֑5kTGYvL r#RK'"5m!pe6|!QD/WNwD~JLgz+DM[4).>i2,6U1U~y"uZHs6Oṛ|\[8E|ѿ/!Oa{o+w_^vnNjug˺WC|r(`<1=nV LErf[Fcr"PdMTצTfО&")RX5Plfv4 ;?ᕩVl9r,=_f.,Csy0cz#uq4 4)I5R̶f2HbwVMfKnl{w-Wh9n\l?'UkQ0u:#tyMolt;)؈$4ØE!RyQ4+7$-'|͡-vD ݁@iE`:Eu'JR$E8䔩Zn9:xJAj+a@t#!T!UTwTd6R7k&+ ]5o7 2UONn2B%H^dB0Iie:ܩLσyDXȡLP T. I\rXl<"9JfmZ.ފl۟Y[iKɚǖm{aWt˩J ffF{Xeq{i];PYHհmn[O4yPԵi2|Rfq+""]a:VUj"UbPY)#9,hR,W&փUi3l6Ec@eng&,#RJ*W\.l=Ȥ##"Q(0rh*4:2Jr)M6)"0n[a4SGcޱ5NHN yLHk[nӕLdM0#%1y8V;ڸ|9Oܺ.}f]NX$g7[lww)]u%Ϭ˩D7mcҵZ K [\ZB)Ω}5F"ΆNAQ"IUD2F9\S]7#S:9F\J2&C4d"#ϥM [fKJμ#4$ (˙EbT\*܊NJB#+dG5*Ze2݉LIDjr貙Q8M-9d,BlW"m gt1AT%Xvk%Ro^KK`RI9)iVS+Hm`_Q55VAX&О^dVjHa)je{VYZ+ZxuU^S’k|JA0umMnYJp=6r*nbUfiPSG JpIJBl6,C 3k)vMJXX~&LMҢ$? &p87,Db=1֚RA8\ZQe${LhLe uT%Ic6iERIՔȸDb M 5'֨$hҾÑ2Tp($)$o;}IYe*&֒0e&$~EB%{η5mrX\ gI8yT9,¦l&$2IuM"TFxiUAhw2mKJK%ENuy aL7!lo˨I;!'dRGr̥!w|^ۗuE6DϬɭ4c|1zEn,Jh3z#g(-eR ѐՕ$/d;j 3i s/*5:"Rm!&ORLn%dDKQZ/̭l..9)苞D^eRJ)d*L4F{Q7bU?r9u9bH[ICuVRJ$zaYgsB7IZ\M38wTji-RkVT qțA ,2O[x'h^zEF`Mj[ݡVhǸaDIf[VcA%)IܧtG9qLG%6̛Z"kA+Ԓqd[H w u]e44MKQiFI#;eGrA *W&4ڈMkVwJvH%hMb̴dGkYrCȕUR4v,䔓-x%]s_1ȥL},F[qfk3'ї9[GBQ1V$@Uz. R[)!K K%3rjrQ@1\©`*NL)(~u&[R^qG!-J 5]V5˱ z_Ύ;GU\4!TBS[KQ2w;aL|qK;6̝5wb&[mͺo{qfFRr {lZz1NC432IՑdoKVC*5q5 XU,Xii>e%γ$jK(=eIQ!&ƽC(4PhO\IjKH٭W<"l&5_ַ0uqb 2ZRJ6%Ze#MIiN+hJKj֣MȈOMDi6 ӽœF]h夘I2Y;x=!zVGXOX.AU߉6!^uU)FEs"=i\s)qBzl Gf:J+atk=!V_ǘt`}h6YT#!f-Gk i*FDj|}r#J(ى;,m3sJ vpzjLOtǟz~nLOt?h΁V5#Ӓl~j=Zm)^3#1¸OJ]R%Ҩ[3sdZUs1MzYOENx3h*Z&2^IJs]Mpb1*ޕ!˭dp,oI25XȕuQb5!oZlem6XlgsͲ;cʼb} k*d9҉\MQaFN;̓TZCXµEA-“K9J$ߊRd8vJ3+if;52OH;2:.5s2ٝjOI6zb0;s%{ZfSsq :f_fk9蘁SĚj5#G_5XRJ=,v۴{=j⋓g9ysTZ M$j*N(klDiS39ii4%J+%NDFBEU]MQ~rКd)jb'X[KBNڤwmf#ig6-RNWgcVkm"2ITiaB;uPP)Ϧ]Si3pɌ+e@Y%ce/%KK̭W"RD+nԨË<d,p/0ZOz~8cØK-'lWڟc?IWLy$̖ĆK+) MGPҾdUMkUa4D_AKK}w7gMHktU8\רOuNBRVIZ]g6F?;)i{ǹ]ΟBޏx]])iǹ|]쥦smtb)uwk2зBQҿܞmJZdc]>N&WyZem~vR7ombOkT5$['%+"2%}63˜|D&y)iǷ1KM=ͷыcWn1MXKU髜Չ;ʹ%JKT8+|{woۼ}:+8]].$Yh$J#GcSY4FQk쥦e-56F+^N&׼pKM~=ͷчe-56F%tm{rGrI]::3Ѷk,2Ͻ4w6Zkm;)iǷ90oO-uwmYKaqs4hܜZ󏲖{soZkmO 8]_G7V>ߦX>Zkm;)iǷ90k}E]Be6\Xud ZKV&ly\+{wV>󏲖{soZkm8kZtXrSiQ *T٧snRyr+<=+}~KM~=ͷчe-56F5t-u}ߦX~c8)iǷ90쥦ÆE+}~o\}e-56Fۜ}pӈs~c k`|쥦ò{soqo\}Mr}ۜ}vR_nsma_N"Wk`7V>󏲖{soZkm8kZ+}>&`,VM$6HIJIw_ߦX>Zkm;)iǷ90k5_+}~KM~=ͷчe-56F5t-u}ߦX~c8)iǷ90쥦ÆE+}~o\}e-56Fۜ}pӈs~c k`|쥦ò{soq 0TBDK&RIMIZY?f!ϧmmÍ2ZZ% jZ̉Rό|쥦ò{soqo\}Mr}ۜ}vR_nsma_N"Wk`7V>󏲖{soZkm8kZ9Mr5_>qR_nsmaKM~=ͷч 8]_G7V>ߦX>Zkm;)iǷ90k5_+}~Si%JD3f,tWDJk>\}Mrte`;zϦ+}~o\}Gg.x7g:\o2ϰs5_+}~K:YΗdvy玳y}f,w>\}Mrte`;wϦ+}~o\}Gg.x7g:\o2ϰs5_+}~K:YΗdvy玳y}f,w>\}Mrte`;wϦ+}~o\}Gg.x7g:\o2ϰs5_1xԉs!^Du6(%.|vy玳y}f,y"i1Te{r{ޱᛆl2R1RIRO"It25d{8ԆAg.x7g:\o2ϰ"-Nb!ZmG}1}k`7V>#Ηvy玳y};MV>ߦX̎:\o2ϰte`;}k`7V>#Ηvy玳y};MV>ߦX̎:\o2ϰ6GS7VtxfZ7 IG5c_s'TɤlJ2k334)$[R\|9}&s|a~Ȉ &Wc2 L+ZK)m-"vb ,D]b$jB 7422DƯMMT{us*TSI-,%Z*C³\0:AR+h) ܙ EJw:n-IN;{Lũ)r6opM:H="*JE: (ikujM%rBI31aJ"%CY2Hm>i+BJpݜbA1 b.|j&G}ͥcetF\RLcKc0CSOw|Iyˎ7 :+^3\e"D`K/a,Vd))a%it%Iض;T.9 mLaN$PKc2"7 %Io S-aO:NTt0B7R J#F`cwz;.S.6!Y2ajdE#}xBvўE`j>U6d9PeL"BH"5)FfmdXIuL,-RIJiաId8(͵bVDe~!)H IsYrTbF A6Rӕ*eܝǻH^Ds%mO.UBd6c+&D/T'MVzYJϴ/V{nP|WhՊ(3Fc6/pnR`O=M +8jMs%}&TiI%SJB}&WR3++QFn*4fdjͨ9[jq)pK∌Uѣ^K*|RiJ1m:m%'$ X&!UI_Cxfi&#eG&:f.BdYkF%E )%/Woa TN3"Jh5-N|vM&Fy#8i2DŽߎͫ;dj$q(ax2jMɔ睦ǘS-MJJG!؎wWFH:3MKy=:!%&6ihq--k54\FIY(GԮ&nVw6]u5|6'mfF/f!v6c4f׮aHYiK+f#5Z\!릅~Q}ɨF˸[5|,="*M4{C1E<ǪkpsXu)BIIa(Ȓw+܄hM+X72%4)z2KYiE7˹cT)TDOpT⦅:zݍ2ɮf}ٌN*Ar';l3͖#>-oLj5S# JJjE9J찇Җy 2#lms W6K8J5@Z >sTFs#63dJIZw\:xFUn#ƥKfjoΎR%4ćY3KyN,\콬d Cscc$b?S\ʨF3JEέ2CMmU4Qۍ2&ULR.E1=` 3-rT""] k=@]B_4DNz+XQfDIU)+";X좹\fB$u{Gڡ(&wxebT[2΅Fj6RF_x78c;`UŐShzY4S:fkfyqj/g pxW<'R`=ӽ_@?IzW:HX7gf^*X!~Z1!b P7:9D(K2ux^ua-eNQ?񦡝:BV/X:BV/XPwGjKp-Oθ!|ԏ^1At1u }θ|ԏ^1HWT$ҎԏXuT'<߮9'\>fOODRfOO:2~}b"8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8i.\>fOOD@8iHeJ(dDVpX=˕\uNŸ9 %SLHhQ=i0:2ǟTѽ)<0:2ǟTѽ)}{9,}TU @B)HRymIȥ5vܸ@{¾-0|%c̪9PSM9O%Ȭ2Rc3.??F(_f,DN1z?^hʥ}c<3 R:BR|ԤMF#4q aFGVL:$4GSv\ȑ}IGZGKxq~'ʕP6J'$\ԋظ6e(֨X!KVJ$")$2TwKq𚚽'f٫=Fffcݶ'=L'JvCD;04@İieQCCMK\LU)̝BVʫ]݊'XVњ'1*=jIMa((J0BK1vS2V,i%e4{"،:0}2%D?PP,g\gMGdf;kv+]ʵVbwx;?]ݥS8Ң!jSt 4M )DgL̳w&l@bS^ӒԕrwmL>]>6qnHoKQvFJ"2ʣ+܄F>0tCI[J$n2"^d]FD;-;dΦmSL9{'wegizk)Z"546e3{G{1Ll:8K#cCmEugS=ksѧvsiRbQG LR|bJR<5X@[+#,M oFIkUec+k;\v\'1qf5Z{wI=w!IJSm2U̮FW~ Tס2[q'׻"D+Q\*c `z f!Hԙ23J-mGs7螕t{Gu,n.U2$S՝M8LfV̈fþLѾNn,7!qRSUC丕:R,NLiE4sJTwT5,ب7q5Qk$Jڅf[?Y 'LX 1;uw9Hj&ԚIFV"D~])I\cTU|*-3Rv9rTI)NSA䓡KO bitʒFHOJGV"3Ę*ӪЫv%QkT4n'VEMq˸c|, dKk HA\Rn']<~0Kztsq:u8Uu/ j+^gYe{-DwQp1#TK:EűAnNRXRoQkTjT"Zue6mi3#>TK1cUgN _ku,7ԴĕKRIVKiZTfFBw3(TX G^=Vi "ˍ̴.略ϬB\RuysfiU8uj1#d% m5Ki egt͛gc=%'˛&ǨZؙ>4-^m^S"e|WH~ xX~OCܵ R,\#;q\58|z> V/X&&,uaL;ʌ\gPyF~ tӘr%R$ܔjZ`D AZ+5t!i+"]'4Ԩ-M:0[CJ\y2+őL"h~F[QMЪ|,_MU ujs /+DbR]&WxJ\xZc_ RRZIICfi2A\K c' Fj;N/x֣GlމtXJ EFS]Yѣ5̒`9T$7|uM8KmyL2[ˌgLeOPdZ:-WA{c*cR eתN$:fSF4ȿt@ŁUwV0⚇m%y miI|dEN4sպf!b 8nAtؗ!|v4Rd{vEb1hzKa=.,JrmݹRMJ##UH#z ?fRRb~ר5fKKi"A$sL_f*0q-o%DCMy6p_0ΚD#U g^&Xbm; ß FD'1glR46 Ѻ:;N/x֣GhqØݹxHyəmma/tj;Iؾ* HfjmJ\2EV?)U ovh:[U/wn jO]^o/uųnWHXF]0&T*HLݐڒLHQԔDD{.fw?Ut3͉2%9QΩ,(ZȶT s4#)#bgߥRݺVfFd"ÜWS+4cSlF"Ifv+-G~pM 6lUT+Xw;aO^cMB2=\C].PS(ԣPoR5.jRHIld%M Q\cPGֱY=g-̻8I3ztv E GJe)"pUkAl=UV~F11AT5xRM)۷Txt64u B^.n*a&"ROS5ԕ%\R2>ROT=EMX 4%[$[j~SNq]f9JT"INfV?+4ec_@J v1ú8U1ir)ꋱud[֪Aq<t<:M#Db:r9-aYVQYqwmA+>•#z)YLlmiI%gJlVyZe ,-8sJƨF%RˉFfoFpS`ze6V,?7|#S Ddvc+ e4ǥMCG0+qc)}ɒbM $#5H|5(m3 Gp+skR!OpٓnYj;O=NE: a "3seƎaIʥ8&Ē2"ILb:=L¯ЪJJ ,y[3CF\fe/Mb}n Uk24ViM JkʔH,K,mẖTPdʙ6Cyo+ y$ѱd[-~1#`:: _ [ȇI#YRR ȋ'{Jڲ˳.!P]ST&Ĭ#5 p&iYpSJ}і6WKsN+Ư:XY.:MH!z՚dڬYeo Nzp=]j[XMעѠhDҖJI4,.Aî,%P:rc[3'4)",@hML8j)aLCi.cK*Jr#RJʛel+7Hrnk+N`VӪTJyj䖵jKZnd;i'3 RlaOJ3H,EL) %+*Vb?ZRὃd-Lj\t)3I)&;m;H/iShdUthz%Zcҍ;55[7OPǡM3!CF_x78gF_x78RFIau#puvnvKGk?R_+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=Q1#_:>+<~w#}=^BQY`יVhMҵY'9gma!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7!#X5cc|7KzlDF\iHJJI)7;(qKMTLTbaxw}SF }SFP}6N}6K{,5(Vb-O% .([P:څ P}xPbMաIEF) LR|bJR<5X@=i 7PǡM3!CF_x78gF_x78RFIFoz|sƶb PzBڅ jZX[PB 1o Pbɺ)1H#:aIOTbRbQGk(7PǡM3!F?z$"Hw}SF }SF B\;ۜbI;ۜc-WxPY>oP<([PmC P j(VB-A Vb7V&)g\L)1IR|cq*LR*1H`?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~uي+1B j-ajamB (Pž(1APc&Ф"뉅g_' Z?h;:YcVO~|uϡV5k_';:YcVO~|uϡV5k_';:YcVO~|uϡV5k_';:YcVO~|uϡV5k_';:YcVO~|uϡV5k_';:YcVO~|uϡV5k_';:YcVO~|uϡV5k_'{[V*>d)KIYKqJ#mfDdI+ڢkgbկ|nM!P4 Γ~^Frƒʒۗq$G GiGI)Udtu :XR%$Fl!4DT|k_'N=A$3MR2)It=gJIRxo?T{vEBmT/-Fil1'"w.=Ζәk_'SB1ձ# Qȷ\I֣+M;H'm`zf}ίMTyJ_5^h,QiBfƚ.~g?=33ƭ|5kiBWυLvtHdcqD%5s+#25XqKy:Knk(ͥT9E\2.[k6ҒR[ .4ժ֒?=Ntկ|m eS(ե՞Ly1!Hm-FYO*v4^VXh[ ̋Lʕ))h%[Kwկ|Oƈh)pTn"fDtqK1jlVTDlK$YYh͘Qq;it̵>|5kah3GǤtG\.s/VJl=֒W=h8^;iKuH2hljJػO3}ghZ?hjDMjQ)؂f2%6:TڔG~.RG(IKGGrbnt Ͽj4:)+IQܸZthSZjCVO&Fx Pb 7.=$՚)-)3R r hkIzFnkdͬCc;k_'u]g_' Z?hֺ+~կk} _' Z?hֺ+~կk} _' Z?hֺ+~կk} _' Z?hֺ+~կk} _' Z?hֺ+~կk} _' Z?hֺ+~կk} _' Z?hֺ+~կk} _' Z?hֺ/9DW2)~6ki3`o/CgCB%SLHD1ї^W[֧ZC*ϼm0֧Êepjy 50\oZC jy 8>W[֧ZC*ϼm0֧Êepjy 50\oZC jy 8>W[֧ZC*ϼm0֧Êepjy 50\oZC jy 8>W[֧ZC*ϼm}CAލ˾Zufɬɚ׶^Ø50֧ǟS]"br^̽u{7^8o.MgeL8r;ie4:zn5XfD[5'{X kSaO!'Zٶ.G1lGT5O(umSeN.1^h82pq:~Rj:)+ME$AKUȬy{zkSbj~sgY䣿tERG)JG2醦+f&(b+>Rov.)8yd*I?der<Z|9fqɱ:R 1pmerֽZT+ZC4j {KJێ{2ز-elC0֧[jȯ1ۿ#1í0I V+DLVS#%[F DC&cʥ5F5e'I%Q%E(ѱoRzP"4!)J<$ǐGL7tǥJ ~,tԱ=2Óݐd:3mKΞ.X!kSaO!MSN&?3֘[Z(R'Bz4ˎֺJ$I2=bGKwGrCeImQIeGq6<fi&?{9.rf>䇕_Bn(w7!j3mP*8**hFX Wi}Rd=;)W[֧ZC*ϼm0֧Êepjy 50\oZC jy 8>W[֧ZC*ϼm0֧Êepjy 50\oZC jy 8>W[֧ZC*ϼm0֧Êepjy 50\oZC jy 8>W[֧ZC*ϼm0֧Êex.RLZZr&r XKУ}u |z=$;`te׏>zSx`te׏>zS!. m1$ m1Y00N1mI$/2W΅j|FFq P? %:KmHF9 L5elҏe}: N Ś=TSѺrٛljAm;\;˰;\vx+c84r״1}nQS9*Sjm&h3 x+c;˰;\=3\Dr:>\g0g*i.*Ec;m8hyvx+cz<s,t`84vHv= ˲6cJY!K4 !jmT|Db`=w:0̱ч`=w:0̱ч`=w:0̱ч`=w:0̱ч`=w:0̱ч`=w:0̱ч`=w:0̱ч`=w:0̱ч`=w:0̱юGYң<8ɎM$DW8Gbs33 a?n_ Wa=e>zsi\<{J9=ju&J[6fi5!F23";\DU<`C?XASh~l㶧 $FfDgk!0!ѼxiWۈ%$ڌGGw`=w:0W2F@w`=w:0W2F@w`=w:0W2F@w`=w:0W2F@w`=w:0W2F@w`=w:1̽T^tCΖWdk7,ǔdq N`m8 ,-unHbKScD 2os$]Gm{%`m8 ,'&z#ao4Y#l-}ÃwY#l-};xϲÃwY#l-}]Tp1:%*I2\#LhcXfuHӵgyɆ(+|'zd B{>=2 KУ}u |z=$;`te׏>zSx`te׏>zS!. m1$ m1YTF}Jd"#WRlEgdr Vb$y9Y[y-,&VA%da[6!k+]sɕ-vQԃy%A#l3Al`YUxS)TiN2#QvOn(VuJ^TΜM)2BY?gV35u^|IZGГ;)|NTJðDtߎԇ !RH$Ɣ݄pFm;߫J&Zŭ["U"ٳ`b-SUI;*d,Q/cpʗ*^uIyKDZ Y\IM]-b0:FxyUdpT'Lq7}؋[Wu5kY<{c0jSpm:v3\jKѤjA$9<ї! $$HќI@hjPH4:S-b%ˌly8{p3fF{&Gr2lӔe<{^1=RR<m ~Ji2#Bci5mJc(Ф"+ i[b\F`5(cEqM`ڂ0ߌj"A6KKFԡ$zXGv3J) F2֚WѬS{d-'|rq&-ɥSS ȉQgPk;?N6G,l*vӔn ӳelcvGL7d_cvGL7d_cvGL7d_cvGL7d_cvGL7d_cvGL7d_cvGL7d_cvGL7d_?爿Vz9 _o@">.+?[0fzLTԉ4iyԴMIlJn˷ܐ5U3JzJct׬v"_Q2p3ip.RH94/e7#.QCn; [M!N88%sQkwL[v V+uYfm1aFVfNZYr*"&]3Fj4N"V,e:"1ީ Qe[dFX*^,@xҬz4EY5ܸh@Ãu8%UMdFHj;r|mR:,RzI%\."e} )#2=#p VU^E}R٧6Jk5sf4e{QwLfd_cvGL7d_cvGL7d_cvGL7d_Yձv?1]ZJҼ܎ٗ>/C_2JM M%:%aCo&Įn+dZ;V5-UOI8ډ+$J";FW/0uʞ()H<l\sa$:ƬnYlϬ=}wa~ X _tv]CBar4VfdփFF:޷qjzzIIY% QخW"2~3BIUn8J-J*ʾT<ȸФHQ|v2qҢ-J,W/!ʝ9 YԔBYJJ rIlEL*T%oeЅMI%ef]ܩ!e5Se%0D%(GBRFIK"#2";،DLAQ1)31B:A\fZUh%fIJK>l.R = ,ʧEj9ؖnΙ4#,LoYS0ZK!(Mդ"2%%±_itViےh7&'Mj% eW^9w,n?dsU q2fUe2BiKNşIπ$L?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~uي+1B j-ajamB (Pž(1APc&Ф"뉆G ;z&'" :@ƩUGOUrnK 6[V l|$3( EάĊNfӄ8ڢ%'G22.3ck葛L^b{1uY3CȝpТ6'+ovalD~Lii^8d)F̔()M˹dhX5a%NeX=tSFgH4Fm-5`@@d7LzE ƖSif4ܖN(GfgݸƋbπ$LOqg~ǦB}u |z=o/CgB$q:4oJp :4oJp%s_@$_@?Rx:l C SB 0Bj(b^f(1uhRbQFu#^ր NȤ@Ԙ%JJ̄("6AX6;oJ{ozijU9NHpdqkCn,̍$،Dhm ".%3OgC"Q)n(r"=G\bVȲjF[Tv֗$d)ҕDB6mDQZJI[ 9=2 KУ}u |z=$;`te׏>zSx`te׏>zS!. m1$ m1YS&R년O_xm*m$s*8J#;p{-’pWh1H0ʟTafm+"Yk*%')23!P 1m; [LjEeq 6M-Jթ*3=jHDEeq34Uu?K7i*om6FW=2!=şI %SLHhQ=i0:2ǟTѽ)<0:2ǟTѽ)}6N}6K{,5(Vb-O% .([P:څ P}xPbMաIEF) v7LND ;z&'##SZt1X ?c!Y鐁]B_4DK萉!c/yMҜ3/yMҜ) p\xW#nq$xW#nqԷ^>rι[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}DAñעbr25>5H= \YdU͕NJ5F|E6`!-^NF"-N buHTT$VY[djBngs3HW5l{O'H~ Wч`!-^NF${'1\IK!%̒fr+Sn}ه+4s &X?;)3QR꒔fv".Ѥ:,G`!-^NFty;HaiO˂U6B;Q/2Zq\s RvBͶ<^#Y'6C4,+lIϸF${ߟxX^:CZ;jw1PF֔lY-$̮BZ.%,K&s))\42}<gכN':CZRx/Uv cq -SZu)*#aɎc:ne'VjD*e:y̨A_jm ?J"sk7Vf0>Us N?!%!hJ"2;[ :/iF:qmn:KKމ ̆܎lЫqdI[^z4Y鐁 ,O@o/CgCB%SLHD1ї^%*eNZ)2IME3tGJB YvZs"1c4$f1Ǣ6dje JY]$W+ڥJ&K,no9rb+PdxIFSSƧK= FVIddmGU4h& NdTU.REeA44JʪGM"u#es[7qRq4de&|ZQֶn<"XdxH 6ӺwXش@TJ$`u yRb4DxY .הVE;_,v'kq~ULyF} i6uGcNeb;DGmo9=OQjTJM=3VK.6&Ͷh'(;xgH1T6UZvfZq)UD|Dv6OFi- 5u*ShǬ.mdwa B Bt04ԩSaVRIQlA浈wUq5ss~8tŜrlIM]+QYl/Vۑ]fNnL~#x $fLԣNr<3F|MUrLX.5d6c<b$f||#`rDkCTi2G!ƋHlq4O WV؆X٤[`@[SI+)$RW.15 mJ C8׹Fnj3B6Jg|1ԎDtWìQp5 tD[imfJQ+Id5MV* ZM{3oՙ$:m)f+c؉U>pg46Y83YEz+ģ|`'hqz>գ72R3qfyR][Hr'x@k>ȏAaS3-ReI%(IKx$EeS~.DCnffZLռ]3Dت<J^D<Ɣ5HgA:5}LO{[^閍d>=2!=şI %SLHhQ=i0:2ǟTѽ)<0:2ǟTѽ)}6N}6K{,5(Vb-O% .([P:څ P}xPbMաIEF) v7Ln6-U+#JbW)JZ3B5Z I(ñעbr1QS)qaVSs+1SCGvKVnE(4ʼ Zqꎪկ4ߺ2B=V\CyCf320Tt!Dh~%l7ՓǰFFI]fbz+5jdR6?֤_PhRirI&F&Yi֍ qãD#g#EQqRutZ ?,:RF8OuժVC,IBFѳ0:xGUb5_ZSE'RRNSK$ﰌbp;xDZ&RQeJK̕A y$$hSVաGfIȉ$,SDd|F4ԸS0mVI"u&fg)#pSrr3Q*ܚeM4:2Zd[TjKqTEb8jF$ҚW {,RKi)ZZA(eQn M KD8QЙpjͳ+IkMdl+SL3R7Հ#bRqlm0NH+-Po+VL*ɞ]Er!$-N c_Ř8N,?SRsŒGZRgsRN:FEb6W+ӺwXW4F#aڍA1uv2dnpĴ+sj3GfRf԰ Pd&o>kJՒʒ,RH|QhXÎ(1ǻdOMS5F᝜`J>=.}EJTcsϧ!EtVl!+MfGe̹@&k:X̞Αkx,smGkZ{TgV"IO{[^?>vMzQsi \8Ƨ$d$)c-iqVƼ;I-2TJJ lZ4~Kl6U%7;; $1=8Ri)TjEylĬITW+֘G Zsk+˕jO~!`}N '߿LO ޘH`郵 ;`pS imɶQqx+ "2z,'2 =2!=şI %SLHhQ=i0:2ǟTѽ)<0:2ǟTѽ)}6N}6K{,5(Vb-O% .([P:څ P}xPbMաIEF) v7LND ;z&'##SZtM;[JxVn'v%c3n$ҕMv2+eGIBˊ㈴y6;$,T)dBH%!I#2IeF;;#E>2c&`e" ][ :!G]RJĔ2Nb#=Zbl5R+.EDc6ӹ(RHy$OI+6%]glF|dlF|d ꓽ/p]ɎBD(y%=udc,s7FS#O:95V$7*bhgZd׫"IM?dA?d@! ȬɔCI? ʢ?R}R\ʋFFIY 2&1S %zg#y9:t;]H1$ .tN"2;!+";XȌW#@c]n223p; tnɳ)VSZ4Yͦ+ 6VêPdM%>ѐPk3Փ!wBlѕ[GdJ$ZD^5P=!Rw%8g9Pg8I3'u稱ˎMoMvB.iiիlvBl)=)4SLJT⡚E$: %:ݐQХTtUh0]6[1q?뵎/!roP!WG 6.*+"ҫ:e"LLɕNSI+-*;Qg=uOy/7աF_oCZnlebu9홿M򭫊w̴g½I+)"5e*-63"vRm+QYcQm Eĵ$g uM|= tjquIX}xZIQ-22>#! V,O@|'zd @7PǡM3!F?z$"Hw}SF }SF B\;ۜbI;ۜc-WxPY>oP<([PmC P j(VB-A Vb7V&)g\L28_k19l/oxn*P%&J~:XPiqIiIWԢgb>qRP|Aɨ!ѥ!N:%^QfR&rYpmQ#=Sr#Xl4εsj"D+ilJ ψmA S(Lfg s(О$IjJb#1˜BBDf!˄kHff ,dyMУ"Rs";j\m1k0*3R?Q[A)NªEW]r4܌[nu">2ZTJ+F'0#$5\%:rߙr" oG#Y C,ƒ<3#Q\~Bb1U“1$$L쓹$Ђ>u&|FFGgZD\ڃ*|KDYn%lF%Ф7UjHE/ gQ)HiON.$5$DT %cW00QT Te)Lc2䔃ȅ6Vi4ǴVfÆR&Ku7*tFS (R7R%#t  %>Lg@%!Qfh֔ФҢ,>;YIBxR"pU5%fɺ &fӑĨFJsev#$ to$flMռFΤ)DٚM*IEó]>,cNYcTd*%eyIF3#"$ψa)THS&F!l(qJs"Rk]DkIYVUbm`' @KZ3Lٲ2IY&ψ?[E;_,w=#^oH8.eq:=becQÍ;jw0?j='R)*;e]5DaqRGcJVl+;y)J!C$պ HaJm B\A5X4m'H~ Wяh/H)+/)Xn!ڋf \J֤Ithl$fFdfDFcX3L Ȍ1{~ Դ3)ɕP#%) ֭Aсp]uTLO%SgM %%IlҢetˌˎj.TĎeuI4܄-#=vD(ȓr2#+l8{4ȫi=*3$j;،xDE;_,Y O`!-^NFty;%c{4GYVq\M&3u)z%fƣ;a^}J2H"m 0#ⱥE+6k܃jw0?dӴ'~C5GIt@”m5kQ$h.32-1+P-;QlAkAԉ. $̜AȈv a'H~ Wю'H~ Wч`!-^NF;pG`!-^NFty;pǠ G߂F;S QA6kqdkqj$BԳJHƲ:hҺ&ĒSڝ6RI(O6IIm"fEdG\cSʯ1fkT"F"m8|KW:uh[ֵԥ(lόK !pGecxm,Цʧ)K+Lɾm>(Ȗ StlvEatBL))i](IqX-($VeL/GC?NJĔ Z[e#&sdUό1 ٭VViTq%%3Yk3[a4ieiH1gv[?lvIYHQwtO ydl?A]S)ZNqDmf\sd"vR;ۜbI;ۜc-WxPY>oP<([PmC P j(VB-A Vb7V&)g\L28_k1(1Sb5Qk HQmeMeNSJBc#O ;z&'#UxN\Sű_Q7H]ML:퓪+g2"u 32daf,GnJ~riLϙLq׬lԣ3-YqEbg 6BG1;_T=)GCFԧDVRVDs;73L?"t'ǐ̫JV)8c-[KhE8*l5*/1l0l˵Ĥm-Ŵ$ӟX iIuVaP6i2-J&b3lOjkAzTB͗ rڑG2Kf͔X7Qc*[rJmq41NVUøcFR3=&Mf)FN9k!_s{3>z5KB<ɗs%#)f %1f#lmj6.WJDC2iԴVRJ dgId;BӠU}JKQ]M底6Kʿ(*l.:bFJĤf=-4Jn.GM!yx ]7.#if3n!) ;b4!D[2OrQ I*#GCZقq垭(T]/LP_jդSe,d Pj+in[ZқH4;(=iғ*2D9 .ei|[ ś NyNJm ̈% DGEsJlT'3d(nuN,Q0@&owNK@1UUVGqF6A8fJRTr$Xmĭ#WnU]Mfe.di4;~I$~.ժtESF4|+3U,ۢ$%Rȳi"EoLCD򨱓KyIg RQՙ Afup`=KM 6ӺwXBq*TR2uf4LVUoY8*]sZUܛ 3%) *9,Gug}bV؎UlxhVfScYD7g1H+e6KPf##pEګGKI$~.ޑ>(rc萔X~U2io8,J[5#a,n,!*7kJԣr^mzd!m\e8,[ڻGKI$~.U^I$~.;m$xԺT{m$xԺGRuPUGRGKQ0Qi%R"e~ӱl̥)FzuAQq,Vrki&^q IȮ{.vعb;/`/{ 4o3cLShTv8}ё{T2xd:ojs1XJC Y$37a(ͤDK'#&D{?ЎFm@UM+v$%VܻfvH#t6xމXhEEiRKDufuJZTCer5-JDp09s:d^"lDd)e:!UD;c9FG6Kq}r S36GrFFDd+xR8FnL m %)Il"""""1q\]qFx)J3iEptτ!MbdmaĺҷʤmtWjZJ8빟X +)W5|f/S6X^\+,+|'zd B{>=2 }U iaz~7[O"%:HKtu? G44xW#nq$xW#nqԷ^>rι[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}DϤ6 ϕRK6PpőRеI3;XݣXac}Dݤ2.K u: ֥ fFFZ3N폪/ R*L)P_K,HeSNZ# c";qxuXԊ0)iN_d4RIi Iد;x*eTcZ!N8մӘ_i.3 ͨ;V!$N>j4SqK>5f5>Jr!PtF Ń%nGu6jAuinYmrQ0 $,}JNGyVuH횐ITRܻ22 \Q]^N:ɕo$츍ܓZ2OTlV/bffđXPWVz$Me'V2 \^fnU-u8FNךI Ss!Dɖ ePmөBZVC,"IY1̮̿Vs(re-tU̡$},K΄7Ys6s$!uyitز",<#5lFN~ZI).GqMMHQ̌Ȏ.B1Rum,UHTi*+[ h=#QVpuG&TDK[FjSR 䖭{HtGmp1y)'t mF(FJ~|q1,zGf% KnZs\`Sj<;ldOoI.T]y(KnRy9vFFJ̈teV[yzR#q}fkA$.T1dit6Lhd84=ܙ,w{Ձ\&K>7qf{L[&owNKQ;*j1C91?=(.̳$Ki ;k%& v&ز pFP2RLDdFJ-dF[H@/4Nj 2o4`N-4IsJMl#ZoBcR:t6x‡QeO8q]jAI- "3`7& UJ T9:qdʫɨ4KeO(W 3r%H"JmX7/{aelk,XW1rth!Ɓ6m[4ʅf[=Y(!Mdxj}N3اb7?+|Q&U=RiiAW^[IY$s ?KXM kxew?%6=":+]eW.c;yuJZv+Xk,aq]m"5zA>%l˄ycAJcq7 Q0ʛuHw.e(FvV#;eki)e(Bx3_? T#,SVĕ:&I9mDl)p* ±)gYR9?爿VZvSWte]V} m\$gk%D\fcyu-|SCihJcF2pNzVQ_2\ccX7/{cHImxC@0ܶW(sN$.95N).<>TF&5N&]%(sid,{9GKI_ ?v:{Sy"WΘr^[ 0I!2jՖRi$J˘#$F cu|2%`NX7/{cPb2MjHݻg?'}b8}~%á4ɳgN'|BHGbckZ|Qц 8WajpVPIIl$ēٗn{fIiⴧT8 [i7]S2,FJ=T3>331XYBM#"M/*twMTTcĆs$&,b~{Q]efI3$ӱ  [-9B $sғqf[֛銓UTFYX8DJɨ)&[R2#%2#-"Z)co:TaCU樲 ݧWȸ Ҥpr6/cu|25.{bTq|%HsgLNT;p7"Q*Ă$eسb!\ƸMѢćxշn<*yldBԅ6w6cu|25>WlSs>(Wv*4Ԃo4 Z-r${MH3NYk6U˘N5gRvV"" %ؐ6[mns41DS*la:)"BW1ܹI[Hm\c%4+MFU2ՕqdYp"lh3IQk65PM[V&d_il<+ ĥӱeJhJHDLpK,4BEy׃euOy/7աE+|'zd B{>=2 }U iaz~7[O"%:HKtu? G44xW#nq$xW#nqԷ^>rι[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}DǤk-Tyi k7Ӕ\ac}Ddj|kCվ5 =+wrs^w\UeV* –rW.绽hǀa,ĕsVv$IQ.Q$n(Wٴ[8Ug2PURLifqIR]܋e%b:m6csi$C<ær2;(Fe9\9T'+Ř)IY kM\{nE1'!>ZPj:P1$!+["32$c3=?%3*,eȚMHvJfTgr+ XUzzEjݖn'gKc. v yݪ3Iv1s 5Om[xIE𕴋|AV-C*U-&U8+6Rd-̅x@1!XV:T*:T\v!ouXڝFkuw͓6k^0jTf%ϯXDel)gN.r =/&NHD= w2tOV%oܶ b@hY4Ȑv-&$͵]J2"+m3CG::1폝 e x|Ƕ4y>vP玸wΌ{aCG::1폝 e x|Ƕ n܊iF5YXR+]YLkظk@pT钱s)VQ(s2.Az*dϦN:#VgRkv줙##;K`Ī9 $+TE-$M65't!Š$}졣pò[BLQԣ""1(}졣pòtml@Bw%*Y H3̢̤H혯kԨq֡(Q$$fdɻ" on ӷn!u*^,vHW5#:͒m LR;ebn†PcamG|)MtEP堐PmH\.GhRcŧ\yqj4ꞌWo\pZؖE`5 za= lL∈vR("NnD[BA>kt7izlBD8ZMjI+aT2ÔW٢JRwQdRBspLJ='醅b٧;>IvnӸ/Y|s^X`}N鄟"1{ ; Z*ĕ:QEU ,vLU&rKR.G(ʢ +B~uwͶ(!;kjn9_ȐdjIg;aX0~0va>'za/kb:E 2 Hv[1V}j64-Ԯ +y3` $NTb#<۱TeFQ-&TF4F;X0~0va>'za@`}N ;kwh. '߿LO ޘoX0~0va>'za@_S&ddҢ̔ę8}?h1X ?c!Y鐁=FIK k񾿪"y-"B[R9_9{¾sI'{¾s~uي+1B j-ajamB (Pž(1APc&Ф"뉆G ;z&6KPdT}%N+JY;d9XȈWUv7LNFFƴ=Ә?Jm#%!ljJˉHQi2l;Up>Y_qCϕgJIkD|#; 6!,ۘ14BLrKk+z̖ܺٗ5Yb*.)Ŋ c9EifDFVEl-.A lCVJCNYfRO5tғIXfQfJL YYKO/b[ZF Rx[9inLF% ;$TIVV=.URjiԬR22M%fPb#wRV€bM0%Cď&Viٸ(̼9a'6ۨ!`fUTAA_S~2RdnJlcZ攙tzLFGn&yTEr#" @TkU9"QȉRY";!("J giddcX뎝8~2#Vc[;(dv~ok0+O1-YZyld2;?\η5xuGC?NJoP<([PmC P j(VB-A Vb7V&)g\L28_k1iΑhֲ K*6򢶧!JJ\4I>_Qac}DՅ:RzaTdN!:M!KJ#=rbOK2oJ,|Hj,v UN$OV5pIew(0$QX$@DmlCZRNDek\ʱl=i^iz ͡J5+T^/\QIUWZ[iLF#$ԒQ[^ݸUfJbUi?-siVSQ8a+)$OxUtj̍uܾՙMw6N7T-ueRfJu+Vme=RFk yO1!i.4N$Х$WE2}c {liŽHY(JKizٔ:kUx)h%!%JFna,[T;^KTOLFO%xl 5z&I/)m$v"#WVnr&BmBH+Qj3± ŦoZT7i!s4XyT{NR)WqF'uI)VIds5+24%MxˬqS^aG3Cm Y1 N$̳YIF[G(u($–d)R7Dg|#Wn :j\:_׻͑N_۟&_qZx&2;7;6I5t@FLۛt+d;صLkMb>E&rK7SYKDRj5f+\n\Qk70=P05Ği$3 J"BQ2<+f+mNd`AWcU1,>NF!Dm$%a J ! c2Xy1MOԝq+EH'MfJII222#!u^"&BdSKKq'e!mIZ]Ԩ4o2<*Lt ӢzrZgs$􎬗kI+EnFNDȌŕ>qy-եԌTdj+VV\I""""fa*:<)hNđ rm51\Q)J52^UYDF&{f*u]))TΆGXJ*6BWeXc"x3+Nï [%25 l"B6Rgkz&u3NIf*q/™&O4(Qs!DZDf/ؕ* d IW3+3O%+"2Fec\b0?Vq.).B jaRaHn;K 8m-2$3RNТ1vj58Lƕ93N!WqHh2JJJUW+Yji%PQB-e- Q\!5Y2rk%/s" N KTj\Te$2ʔ%h%:ݒvSd>"vƘsJz-k>I{#n42KPcfehXֺNⷴ+M3 VjkI[քd-k]\#a*{8NM1'2^t'W [,nlc!զ<R4"7VM%Zjd(\xVZ*=JZi:l>-b~Wآ.7waѤu5|j/82LuB% xea8'TԪSc D\LWg5HJ .ݶiѕFgi7Q ]BR)OC7Kfi4ΥxITƒpkjL9RHk4Tk;elyirU*_JT_vp)kQ%2M4&Gr&MS¦? ֨׭I7.G'-B\GN:&PT#X 7e#専k3Sd"jRED($ .)fIUYLȕc/NHH\FG()ฃ3zi+[)wMv԰&1n:twXEZ2T&rerDun7HB̍Tk>U;I:$zKʪ?#~y{q H4hK[i ^dL gV'F(tT"b~C)7DhSNSXFtcL k><ꔩddnY kQ1?aQD~XeJYr\e#T'8֤CDejJі*:揤yRݟ5xZ!әfM6"$nm)cFm=Ԗ&ރ 7m &IKW]i;d4WAaeq/TP1LLy$N[des8b:=M}l nU{MD\[Q)vJ\$);iOzHoN)[N6NBYТ>4ːkc J[ tZ|>v7#l8=jXḧ2 KjQ vJSUC*js3 j[ Rd25Su0UD#޼]Vj1+|uiOjv5YWc;#t1D1G:&eI$2mJ+&jMV;a:H1aګnԈij)ZO- /6ҎDFv3#MR[KuF!jJdpnwq#>;pƐ&:\a HmN\Ɗ$'SW+L&ثa3> >&QM6#E_KFs*Bfjֽψ_xW^w{tn\ەϯ˛Sɪ^|ߨ]C#H\F`RjBZ %kX;gr=.qFm7vk\ݷݛQɓ߸:gٖ@H*H+FTy2sTR`[)եc\z˭7NkfDDfYNh[Ѿۻ|Y_zZl4*]F 3Ľ"V }&Pnsc&RmYr&RW^ ;؇NXrK)MQ3gTM%DKddDFA2V:ĝoE{^QsG9>iL5DbEvc:QCMuniKiRFb""dGmB6/qN^::$7Y&nu\U$ԅlޙ u \'M"bԆWjdRT7I*|aڣlSIA|9IBl$b\k$w"+\ȎE0LJ2&;6˪S%1z) 6Jx`X&uz]v1]1ũt`EvTjRQsv-vq u*&%&7RKmneQKAma[&>U6J"+ N+0$)-T'aHr:)R͒MRjJIl++e'P?Z.#BH\A o h4;{Hw;NWרI BLpiȍ&FFFFDddddFC#+Ф@ qڽJjM3I7&s-\"C#̶XhEWH|şIπ$L5_oHXn_Vȇg ng!?B2;ۜbI;ۜc-WxPY>oP<([PmC P j(VB-A Vb7V&)g\L28_k19l/o蘜Ohy Nu`%Th j$%7$Nd˟tI{m$xԺ`;m$xԺGR1GRGK:sGKI$~.sI$~.;m$xԺ`;m$xԺGR1GRGK:sGKI$~.sI$~.;m$xԺ`;m$xԺGR1GRGK:sGKI$~.sI$~.;m$xԺ`;m$xԺGR1GRGK:sGKI$~.sI$~.;m$xԺ`;m$xԺGR1GRGK:sGKI$~.sI$~.;m$xԺ`;m$xԺGR1GRGK:sGKI$~.sI$~.;m$xԺ`;m$xԺGR1GRGK:sGKI$~.sI$~.;m$xԺ`;m$xԺGR1GRGK:sGKI$~.sI$~.;m$xԺ`;m$xԺGR1GRGK:sGKI$~.s:^q{}K>=2!=şI k񾿪4=FI'?B$%:#ew+8Ēw+8[ޯ9g\񭘡B(|ޡjy(PqBچ(PB [ƒ2n LR*1Hθdpñעbr _k19-[jTꔄ SQ#ȍFI#;\ȯ=#d;c1/@FI{c1/C=#dh=#d;c1/@FI{c1/C=#dh=#d;c1/@FI{c1/C=#dh=#dZH˗ xN:גEsRb""bπ$LOqg~ǦBto$ ,7OQ@CG_ ng!H| m1$ m1YAqh Ϯȧ`Z<4&'%Ri;d#լ'ӱ&&s*Kr7;*jjIϺt]"ȮwCf؋QqG"=iUm+˚{fW[U]I"c64v ۪q(U˅b;]_DIN^Cc AhT e-&n,gdFv4Z".C ҈.Tr Q:CԸhSH6cRuc8R͔d$F5Wr_[+^95|xƙ!0lC]UE6eiUQemhjʔh:#Dx,)7Qj\M*#k&Z 2;%*"MZ_zTB[PD=2!=şI k񾿪4=FI'?B$%:#ew+8Ēw+8[ޯ9g\񭘡B(|ޡjy(PqBچ(PB [ƒ2n LR*1Hθdpñעbr _k19/QpN-իo);KjQ:ђx$v+%GsOv~|)N]B֛03U5t1icsm8&ڻWOC6Vv~|胳/D9Llugg'^;?a>OC6Vv~|胳/D9Llugg'^F1TwuO-5|2$jj9 @1X ?c!Y鐁=FIK k񾿪"y-"B[R9_9{¾sI'{¾s~uي+1B j-ajamB (Pž(1APc&Ф"뉆G ;z&'" :@Y鐁 ,O@n_V5_oHu}H֚ԙꓑ+6Y#l-};xϲ0uGU=Ņ{lRBr;"ʢ2<ч3m ] VUUTߗ som0?AԮ$\iv yd:#ao4d )@PEAR2Fu-muQlcRJ*4j5-vZJzQH&$XL\"wxuӑqR;f(f87.̌fBs(xhTE)ڌlHm$K5%Л!*U̐NC#MQڔl;6=k)/)X֛pd$4 ^Jth:$XlV#VD_*oCɢߏ4wegT٩ 4N%*M˸i3#.f{seUɈYcSuI2+ZeW.=mIin:JpilǰĻ VkGԡrY lmJ%[)47~\!H'%K'2p~k{n&'E9=ju&J[6fi5!F23";\xҮVs(re-tU̡$},K΄706/:=GV R|ݲͳK7͵3QXbͶWwxلeDF5csLJMe|\=Z;M&V6[nDq="*raFvF +4ԭDd5 ">2 Ҝs=[D-jM-h_i=Z^崬dbK \Q0ⰳҙ\stnb;$ZIݑ6MCpŚ1[ƌE:kjQuҋR=c=aDlSweܖ&{_s}_Y}·kTQSOʊgZth&"[RRԣIX!@6\rqQ!N,Y.4D>Ir1bW5'U(2Q.ٙqU$p.ۙ/G(Q¥Ȧ5m\7iEFRYz>*\dɧk +[!)m2bL*LDS%ӏeք 5ed]1#GqʫǧY-yRM4&Y#6lQ^8iw݇!.{#d`ϿKRc<qQHQDemŰk ĕ*I/GqHSfi3JIw ˺F,D &L)J~${Ypд[HzoG|FIW+wf\|F1' r* vY+-{Y|WG>[ﺷ]qFj5)F|ff|f(kjcO5MNgSB' D+XjTj%ϯXDel)gN.r~;ս;?{t/Sq{~CZԦi.Ԧ9NazƢ)-o (R>wOx0=ud-H~J̈grSjԪYHfd<@jmVgO2B,vyRKn/?hkX" )KEY8\+^RcLBr2H~de{kJ5~эj2XUP\(͕$RDEI hbπ$LOqg~ǦBzj^Γ)GȏIW 7PޜVJ,Rv""Am1Cws9֏j!<}:BqkyRW2",M"k6ӹk4iYҔ\{-G!2u/GVW-.%S>#9[?lvIYHQwta-.,.̺$2;qܧJyIK2]dSFfMbK{ϩ)hhx$athResRq[4U'kq!6cĔ-j6(af}9NY!]<+@LϏH~ygv,Im-]b$㶸jQ s;o$, 䭉%gXe>$(k륜*TVe0ݑmM.$323Jȕ\~|Ӷ{7 &0h6^yd!$)G>DDff|Bk <7Uj,(P-:Ĺ-K-2Kʕk ̌E3%EbHIPڥ%d*#qdcM-kOu_tc[v@3>?~*|n-+u7*EWp(Z74wFJRfͅ9H:LW)9ϵz*|~vS%@TώIޞu&!]<7n|tp s^(1_KTq7Lӵ P<73#V)^yoõb |tdsj+5vR!@\ώ̎akxĤJAUMe̒넖s:mKk{1Cu?ΒqטHjFQFԕ)%"s2tfUPc:TL7l`Y}oP{ Fg˟vn[6ȝeQ0Ae0'FRuiK $W".#I ̈́cѧ7bs;M~n9g>-SR4u-.CTƫu\ֵ+IE*R *Q6RJZ:v-R񙙙1Pюll=-AGxFc PюljLQ&zKsIHnc̢BX֔sj#4챕~+ u"tL-ySCm;*մe{kqCF9 040^QLܚAsi>m#5X&z1͐n(}6A 4PюlqCF9 4PюlqCF9 X ތsdz1͐`BM7>c PюliތsdM7>c n(}6A,qCF9 0!`&z1͐n(}6A 4PюlqCF9 X ތsdz1͐`BM7>c PюliތsdM7>c n(}6A,qCF9 0!`&z1͐n(}6A 4PюlqCF9 X ތsdz1͐`BM7>c PюliތsdM7>c n(}6A,qCF9 0!`&z1͐n(}6A 4PюlqCF9 X ތsdz1͐`BM7>c PюliތsdM7>c n(}6A,qCF9 0!`&z1͐n(}6A 4PюlqCF9 X ތsdz1͐`BM7>c PюliތsdM7>c n(}6A,qCF9 0!`&z1͐n(}6A 4PюlqCF9 X ތsdz1͐`BM7>c PюliތsdM7>c n(}6A,qCF9 0!`&z1͐n(}6A 4PюlqCF9 X ތsdz1͐`BM7>c PюlL`6'[Nǒ‰rxRzBg}a̻jIf/G!02"/JKvCINR J;]GMϸ."!Pюl;ipv7q39KD<#Uz"$m8n9-ktps2"3cZǸ0e>c!qYfnyREq6mSj1JmZ;ixymon-4.3.7/docs/known-issues.html0000664000175000017500000001600011535462534016513 0ustar henrikhenrik Known issues in Xymon

Known issues in Xymon

This describes some known problems you may encounter when using Xymon to monitor servers.

How to report bugs

If you think you have found a bug in Xymon, please report it to the Xymon mailing list at xymon@xymon.com. You can do a lot to help getting bugs fixed by providing detailed information about the bug:

  • Always include the version number of Xymon you're using
  • If one of the Xymon tools crashes and leaves a core-file (usually in the ~xymon/server/tmp/ directory), please use the gdb tool to pinpoint where the crash occurred:
    • Login as the Xymon user
    • $ cd ~/server
      $ gdb bin/PROGRAMFILE tmp/COREFILE
      then at the gdb> prompt, execute the command
      gdb> bt

Internet Explorer complains about Javascript errors in Enable/Disable

This happens for some, but works for most people. One workaround is to disable the Javascript validation code in the enable/disable function: Edit ~xymon/cgi-bin/enadis.sh script and add the option "--no-jsvalidation" to the hobbisvc.cgi command - this disables Javascript validation on the "info" page - and edit the file ~xymon/server/web/maint_form so you remove the text 'onClick="validateDisable(this.form)"' from the input-tag near the end of that file.

DNS error reported for network tests

The xymonnet network tester uses the built-in ARES library for DNS lookups. There have been reports that this library fails on some systems; one confirmed case is on "OSF1 V5.1". So if you suddenly get a lot of failed network tests that report "DNS error", try running xymonnet with the "--no-ares" option to use the standard DNS lookups instead.

Network tests fail sporadically, or report long reponsetimes

The xymonnet network tester runs many tests in parallel; by default it will typically run up to 256 tests concurrently. This may be more than your network test server or network infrastructure can handle; if you see sporadic timeouts of network tests or the graphs show increased responsetimes, you can lower the number of concurrent tests by adding the "--concurrency=N" option to xymonnet in the ~/server/etc/tasks.cfg file. This has been especially important for sites doing many http checks, since these typically have much more traffic going on while testing than simple TCP services such as smtp.

Network tests fail on Solaris with "Failed to find enough entropy"

OpenSSL uses random data to initialise a key that is negotiated when a new SSL-encrypted connection is being setup. Solaris 8 and earlier doesn't have a standard way of getting random data, so OpenSSL cannot get all of the randomness it needs. Solaris patch 112438 solves this by adding a /dev/random device that provides random data to applications.
Thanks to Scott Kelley for the pointer to the Solaris patch.

Asif Iqbal notes: Patch 112438 only works for Solaris 8. For Solaris 6 and 7 you need to either install SUNWski pkg or ANDIrand pkg. See http://www.cosy.sbg.ac.at/~andi/SUNrand/. I have been using ANDIrand since that did not require a reboot and easily available.

Xymon fails on FreeBSD with "Could not get sem: No space left on device"

Xymon uses some kernel ressources - semaphores and shared memory. If you get the following error message in xymonlaunch.log when trying to start Xymon:


2005-05-29 20:25:14 Setting up xymond channels
2005-05-29 20:25:14 Could not get sem: No space left on device
2005-05-29 20:25:14 Cannot setup status channel
2005-05-29 20:25:14 Task xymond terminated, status 1

then you need to increase the number of semaphore sets and individual semaphores available to applications.

The current settings for your kernel can be found with "sysctl kern.ipc.semmni" (semaphore sets) and "sysctl kern.ipc.semmns" (total number of semaphores). Xymon uses 6 semaphore sets, with a total of 18 semaphores.

To increase this, put these two lines in /boot/loader.conf on your system:


kern.ipc.semmni="40"
kern.ipc.semmns="100"

Adjust the values to something reasonable for your system - considering the current settings (from the sysctl output), and Xymon's needs (6 sets with 18 semaphores).

More information about tuning the FreeBSD kernel parameters is available in the FreeBSD Handbook

Xymon on Solaris compiles but aborts with some "ld.so" error

This info was contributed by sladewig, with a few modifications:

The system loader/linker can't find your lib.

Assuming you have the .so lib in /usr/local/lib, You can add -R to the Makefile

    PCRELIBS = -L/usr/local/lib -R/usr/local/lib -lpcre

The -R "hard code" the path to the library into the executable so env variable (LD_LIBRARY_PATH, ed.) will not be needed. The -L told it where to find it while compiling.

Or use crle to add /usr/local/lib to system wide runtime linking environment. See man crle. Be VERY CAREFUL with this or you will end up booting from cdrom to repair. Be sure to include the existing library paths!

Command line:

    crle -c /var/ld/ld.config -l /usr/lib:/usr/lib/secure:/usr/local/lib

I usally use the latter as nowadays gcc uses a .so for all its generated programs and then dragging around the LD_LIBRARY_PATH isn't needed.

Note: This information only applies if you are using the Solaris linker. The GNU linker uses the "-rpath" option which is defined differently: Add

    RPATH = -Wl,--rpath=

at the bottom of the top-level Makefile.

xymon-4.3.7/docs/xymonprocs.png0000664000175000017500000024010611535414771016115 0ustar henrikhenrikPNG  IHDRlt pHYs  ~IDATxhSپp(ī`z005=wR=`&*LS=4L0mu8im*i;0xQa$xH(MAI9݅crbzL>B̸;iڽcگZkQlJ$$ #_~EAG$@t tDD` L6<;mI$hwGD-U;WWmqaR0i{+_Ӟ]3NnR&-;WMtǶMt^ϨWLO9cXmK$r15fy(9:>=:5Z/Mぷ3|W?ouՆq{cnqNꌇۉxkX)=$#&P}t-3?frjj)J 09Ƽ3ݭw":s&?Owg+k8XCDwgKDp?ؿ|Ѵ9}<6L2LL$ UUv&«p' umxnl%߮piujaTDQ(& N.o5[)=9="^UU*>_v>vZ,S]|QI&0I)ˉ˯]{i' UAiPJ7Ll{f`r\iw~?!sޫP3Ln8_cqO/] uRl-M?&wMp {8:iH}4 ˸dکds@5B% T" g@9_3~g3x:uK-gc0;kK!ˠUwBꪣ.H:Odx%ma-nii'sao*rF),-?ZP^xwDڔlJU֎5\jpўThO;,4o/8wvgxǯ\JI#ZHhwZC8<;e.<ɅR 4#is9#iƴT6e[MI)ҍ\87 /~w9a9۠D"I>#""B#"EAG$@t tDD"QlJ$xAz;ZP\;;b]b]?P@k$y44W- یFY"MK$0ͼ}6#dól3_l&fu.˯hؠ# :UIȚejYr̿:txws=8u/Dj/uf/:Tk;77gCY&ZWVƖV [%JOHW@ˁӞ]33 mm{nSH^Y7{oM5;ũ_~05{A{"躈kȍa$~˅cwĺ9ZN9gt>ٔM6g]uK\MoMˆӚ,/8To!ݑKtDX''k~G+kVvU[_*1ưNmM wNUt$uH4\α'hzDrh~aWH|'hNlz9!stwF+&3zZc4v:Un:wfkQkBxzE) a9]>!=IC.Ʌu-t:/mKk VQj=-9?yє\fYA@7+_Ӭ`?a _p!SBcOsO[[ZIO$-[3#G#7P);Y؞/_ZnϿ5#Cu[ewA_qw=7푻V=Ń#̋x0q0=NO/t>`|)` wu| =eC[4}屆;SAGkW~F0?frIߕgnΆ3#7:,Rfgq a94a=:5J_~e^0ǘɦMLL][_T;гl n8fFR=#ulJ$ 9~{;^hW%G ?l"ܢLt[9pϿ4ih)#>QFskHpdݫkxp{̓ӟ%{/c?C#Ϳ/TR_Wi~ogh#":j2S]+_fĶb~j4^84jLd7.\xB>E)iLOi1^]x~JUw7:zљ?Mm~N6='=?ԟl{?R<3~d߅ZEGD& 뾩e>QϛrD-8\8Qe2_Ο5(Tmƃ\bF'ICr4i`^$0/UU*bkäz3jN}Mn<&XRogr1˫њoeW4-##:C]r4MtLg&ѤzFǼ\'d˟|vԯSZ|)rQQ8^h^X(m)MkZ:E9SM][Mf!e]wiқY6cM1:{G" O >Qѧ MZ~t~.3JdFZHg}nL$W\PcTw:DO 9C Ђ^Voq@5ְs˵-f*OXn` V ''_p4MZ{h{csIODE!OV-Lv8>'s1>u 1z_}VVMÿ{|BJp Z{jO{wq[WwDVƕ׎+5-M ̭̇VC )}1i~aWY[)5z6 S .INkws+arL.d[kh[:{GDWL2Ud_eH㭾H2Q:e9lJꓣI=ܲȅr0xts09yiEa-ԶI"w;\j4K_II_8373Nk9K=NmCC6Ue[BID]U]~cpl؀k}/4<4HnK$ۚ 5-˷,o6O!h6鱝xl'iJap;9є>}aS)+S0&7s's#7H~>2}n!wezet]2Jg ۞&2籌nY=0?fr瓫gO ۙZVwB-1H9;!uՑg/tX>aٽ+ OlzY/]!8gNȜ3Ýa#/65˔~)}MU4nW]ߩ$ISs t\`Φ?_4G*GvL@d/6oVkJڬxMB\;k[::~­RVٚ8xԚ~>UR&0s듻F^+/wOzG+k.̇ꗮs|_Ҟw\4Mp!{[(=?{?.v$11!1DŽ?c듯<|뷴i5PpF?=)GR_GRZO-QEw|<K^ӲMh=4\RIٔ>-_(MT=:=iSzڪ6Һj%wuru( /\^p5ȅs32Hiw4{"xW"Xx-}ᡮbZ 4w%)vXϓFݣG˗vc{/q]y];pXDUqt%7iO<{0/xN:dcI{ҽ^|s0lx6 tof7ݓ{^оsdҨ[rhBO!Wsyli |݅{lYT©U4;/>>ڿuQ}td^0ǙUb/VQEmH0p``k+#<\%qʿ"=|[왻gޭ%ەz'+LY($p1coo< ̃od)FwzJxzed's~>OCd~g K|!K\Hzbsl.~'Dt2Ayv U ُ%~SKy{x~="푗N_[/<5N^H~tI6I$rdgÏ?<(عR$~{; /Gu|"M<{D5hvb1I$>Wv+ENUGj3H:Ce)>ŰϿJ'痰Z: g޸/bC{ JJT&_6vNtƯ?&:>pį}G#QK{oLjHAF}+itz"A;A?Z>뫖nv4Uw<몣BHU;4Tԡn<}a9TӨVu[ Ϙ曓cZDRlWZ~X/xo6rFV#/'b&Nz?[m 2-{졑B>p4ZӦo4&vk2fbUߒ8|/a OϷBYlpc1c|)\>ISl ?SZSoϘmGmJiT-V?NOkpwi=9}4N|SwQ^g:1m1U&a?!sJF|^NDݽ ót6gCָ5N*%eɅf3 -(?oDd6/~ 9ZB=it@YƯ|{gJ>m ϼ_EN~Ke'g3K6%? ʪm8Q8 8h5n9󏟥?񿒰'qY.W=W\/Kؑ~tJ;K} $-KѼenfz}>JQHh3-KE;,Z)V(\ZXX׊HEcii&5DwDt~ۯ ˤ/!T+HFZ؈N+K7<9s~OH$H# :N:"c]w7Ǻjjjjjj$t囿yhYL-k~_Z$J6nf +? jbP_˩o],#մխBVyJ7gCY&>1>u 1z_/Fz8)Á;k&:cMtr;P ,x 0x뷑VrʏguoTz`){{`1~79.}..>rk& ?±[b\z4<4ߚ 5-raC/B?YY; U.tΜ9g;Z{cg-1{Yc”4?Țʚnv]׮ʅrLn>{K[TQʚ8xԚ~>R&0s|'4+ iVYd dz˛1^?l{h{BMI{l) =By{X؞Cm_jmc~;ZYs_PzD%*O }2$}s t\/#]Ev(<ro._k輳9?9}47Dx^^'_/-pxO_ *_rl,w#xKHd9߄ ЧxEG<_cڞUܜyp̓35N.uοMt?C#1;})9l/c/ؿvRhab/V>[ Z+H~6(ó\$ vXu(#Ů?wO.&FĆ|$u0ٿ~?쟋?qՑәRNj_O_/Wv,^Bo_ dׯO+.*|ai7T~(>ۿx_Ǔ]w?/fk˽}"FMX|}=/!~W_|]T}}kq~{8uDO\?:,l AQ~Lk0N19-VU;=B 8Z+R;P:<JgCp6d[Y4ǯ|+ CSm_XH”f{:.V{s%=[ds,D0(tZZ1m14.0驰 ^?,#iT _%B!_Oȏx bO*#w} g׿/嵟ib)N{kV>)B#C'勵?K?\(=BGVaCߢwi=Ι9 -zOj<޽ 5/)|jS O߈WAT{wKT+<}6Ɇ雀%w̑]]_̽K~~iR2ZD"5JկzV.m$$L6̅7Oo,8كQcQ=ݣ=O8gzR:]:M];ow'p 庅ߋ^}CAc;'6ii#EUB CvY K*- wyWIeO!pҞw \o>Nkmߊ~[_B'b/購/|A%^.SODG$!E p7-)~K~{ʐ:eP1++'+ҍROM«t-t ;ULoL詐TF{e{M{vδvciixmf:gݸj*yVy!f_xHtqA˱sGCF198sPBb2ſg픆&I.TŦ$L\M} O7h16O*;ˌ:Qb-a2: 5'~O ϙ{<_n_WmYO*x۷.%}"S_}}.ݿ_{|KSOPC Fz;Z:CSvnְvޡ>,BN@qO-/<>s>Ƥ9)5?V*sX+Ù[+wKc]b]E;"Q ̓~3LNkz֡?lRFVVF4sc T?hߢ@8G~La[{m-EC.v6ꁖr[,/j@+SId43hߢ`yAG$@t tD.#keͥpwsF"M/a_tX.ߋU6S/S;_5& ?QW|j6_y =Bv­خpG\(A]HfE^~ZL5dQKgDTDش8:uugr 8"88373q]Xx]pl8a9r"6UP{c k'<Ɇ]u}JW]7}3KV(?RFZuX._k";Ɲ/.i~ߴGZ["3/a^ă`#wӽˡKѩQ+9ƼH64lbr>fr==_Y }WɅsL.̥Z,?1ݓMUFFp{X;X9ߛgMɑd~~1o|\1y&:IB* KrkN~/vtc:_COS)W5N.Uw7:JIg?d?H:1~=?~˽ֵ=j{n//-3F^wR+_g_/'c>ÛT?c|DCvNtƯ?&:>pį{PL6ԟlR!W~[]ZԘմMUb}.;wW݉T3E;"UGd+{\M?_!+ix|5i"Bd~S fZ'-9lizmap#*W '_^ZZ1m14)ij4SZ:W3~%V=wK1j퍿~9O!|6.Cg9>_yXNTFmʨ~g LO9C[dz373}r$}$E5r(߱^\`]Bꮧ`ھUߧrտjLNkL4Q]rjm2zdyc?,Jv. )=mA]飉+0-AߐeG[Y:dn&1PZE]]&?h=z3sO5woԥcis)ަN S5`x.`grO19J٩fQ_&Amllmᳶ/m{nSs~(+6Q ԁDP0m@]\nǨDudCCz tivrM?&?wO<[<+Twn744h!KciӞ]3(b/|)=m$C|۩KNѧjS.شQB0dr\w/mlwz?tf4Q&U[b]w7Ǻt[nϯ7 ذs˵/bP]@>s>Ƥ9[&5?V*Cfˌ̭%%Ac]b]Bߘq&5=h*{CȮ* B#g'zaSie.CgmkiM哻.} M˗%FD8=I|\"Z>{_??'G57\TtDDAG$ĺnuH$םyb]b]fZfhlZ"!rqG@<_լriwUgIU7y/:.vUL}^FSטn:ӳ1_)V>&^{h{KpIϮBCLc_,=_a맡Es%9jՑm_كbMgG,ĝ\y,&, n/'ӓr2_lydg3Y!OroφL6%}b|@b\׿MմխBV/\HZDB>iHr+S9_8o>Y_W~o|/bWJp Z{[yw{lHknkʵ;W&X)díCChܽ%N8x4(}aSZ#R W;exx*ev|QrxakX2r2S3m X ܏3Ռ'_5=6+n̦%_5NH]u:,x}^_1߬m ?xh(Wtow4!?T=?": rԸժMG WS(ar? ?砛?͊4+Ww|{~dt\`o~aFSmRr//Z֬TEG_/UfE^ZL5R8boy# sZ  %g0PW?/ b&N_lF _~oK/.n|wO{G+k.̇ꗮ<{}%ii֖\h{;}~;ZY33>GUo} xtӮ~Ɠ?r,_J\#29'޽Xz3pBhzvzо!t'j)c /5TRѸs:uugr 8j,~H˗vʙ<[:gólUקtXtO:yC8'\X(Ob8:,GntX)_Տ'CkFS2ʷ=7wA-_|7B2M)|:Ќ+K|aK2>^7x{x/O~UD*B1m<1uv- ̷ .3?lܽ,wK7&_sw_\8\)%J_Jy%ssssssݿ7# =%Ӊ_O__<2edѹ? ?b";dO&{CZZSK{e{tnnslR^'~Ot U ُ%~SK^NȊ3 hm ^ާ6VK_ly[ԸQj8:=y"?Mɑd[_w~;= #}PT^b鹔_W*ьlRe?R\"~4&?bVʽ啇 {̓ӟ%{˰l|_~/m~e?B{"ޖ޸i񍯿NUGәRNjdPy_v*#ے ϰSQ>WB_o}^a9PVEl'=O럯G!񊻽 y~qGi&{;':__8ɮG#Q󥍽7cD$ >k ^Ѵl:_|~{NO}O r7;DIi*nI_hPn˞][[$qޙKSM:Z^/[~4ߜ$ѤDRlWZ~Xo6rFV#/'&NjW&nu&MjX/b)-5_Ԥ!u~l',PKB^ \*lV3&=7L1߲F.RFmʨ~k8נS)h6j! h{q$L$n<&˶s׏'s]2EW'w/- \ Ь,zј6}1V*# 7ʥlK.<3V/]٭d]_˟ߒV\kNLG荾\=&~!?|)ijPHZ:u֩?566KP=eD{keDKSuv_:{rKy#|%PI((;&Ӊ?TvruNy3kSe['3W#Lk1N19bYx&T\[.nV|tglhq{wtx6ΆlƋoѶi!j@ӊ`4$+›\7%+lb;2W=Dns>,.T_,lfErƓg/0YZ~aA->ؿZzǵ;Mʶ-/\3ޱcƛ4j߽]bSsZ_ c?ʨ%k#9ugDWW $-dsAu%CWܨUH$4ҙ'_UC SK2G|{QƞG8g3t4 Ok|[D6OzMUd|'%-V^Z]Bꮧ zʸTձg}L];owg?%k$*4(85zj(U/4"L6LcZEϥ韋^A΃W~]qg€~2z2B4;emCC6UUx0ՔiTI V%/_S)oYGnЂ/O3'hS;[_ 2iM־'~/~;=#(;SÄwKm1O~?u;Fu}'I刷_!v>֖>8^h^(gϹ2hh#&grnJZR\,պ2;{{z#iـNq`jj0|e/4Wak ;iGQ|뺻9֥taa,_;~||Is>?RjDU~Eˌr12qy˙명o4"/zv6@':"5eZAu, :"5NF,whSӹ9\?b * :"H#b̦[RбCXͱ.t7!І\2Y{c|S͒jy?<-˽ `?Q_˯ڨҎȖVƖV [f!k-)"~i\ne*˽~GxK}ԁĸ}] -g}^_=_]2ٴD~|aUn:ZZs\Ț&dhe dzRNFxzسP//#zv#躈kȍ.?Syr\8}wK0)_˿o!>S#O™;':ӽ^&Q!52{{뾆| #ɯ#I!'9DG$}lW&}/Pɕs9=6J\8ː~)}MU4W]ߩ$pa|9Q/SU?l&iRz_VlWF }#G#oVke͊4+Gleꗍw;Fߺ}C~K[{zqJi99pEퟯsw|~S>[?lmi_G:#!FgvVEJJtRhJۿ5 !<|뫨U0sNFׯ'9"~Ɠ?rp\Xx]pl8a9rRAr/4<[:Ok._ktsh9],~ߴGZ["3/a^ă`[,lqq ko{n faW]UG߮=pGQ~97pYIa94a=:5釠_1E駁d{1+v鳡g1>C gC&!:d4.k|:UD٦7~ _{K|aK2^/ ~p?茧}@igWET"dKShQew~ޞg3Ǚl.|2NJeũkeɧ/w_S[ZO"xW"XZj;_ēG(==M}ywW)a2z2B9qޮ;p{`xϗ·J޿9ߛg=:3{osEwzJ +{̓ӟ%{ oy tD)9lHjφex6 %?,$D"L`M`c~z=_H%k%A~^~'_/o\u$9 5JFvGQFUbo<)y]|? WM; =GRu{/Q_rz#7wk2ӱ0=ٳZZ6{i;MH{C<y!=%?(o8oOE˛RJ}-/.melE{;':__8).̱dO&sfbMt4NoIv|-{hmᝡXx0pA0=|4]yʨM/]} O}6E_C|CKA_ZMˆӚ%?[jٺOL?QK3'W3΅םA;A?MeO0`qD2)G-Տ-ߞ3"5ʥ/Eϼ_EN/6;ăkwp\>И6}1iZ6PyĎ?/mV.mf[rz2J"kv"g4WemV:wr=q[.x"DB/ٛxS WL]+.kh0MO># +\ tgi'_1B'x"vyoOe9U TE"/K6_N]) Ŏ\'d˟|v%ͧVYj,^Hw7φP: YC8 Dn/\\h6м}Sb`r?brýPK D!PV4TW3~digigޯ"¶].}r/nƴp\pФkr*b?_)ix|5i /Ky˧ϥpƯ|+ CSm_Xʵ{*^l'..Ň_(ƴD S~ȝhW~N2ZD"5J¾rXY>Fcٴwi=Ι9 Oi?[{7w6ʻG{{xP( faov*s.u _~W?l)|jS O?ޝ:lO_u,>k|ߡ5UyJOU##)7wYh Wʫx~]BꮧI뚹TV)\0Y84֒wؒ0?`2ÛD<߅؈Aj{>VƧ=_H,{-qWI;kțy*$k8-OIW+mM_pwē/|suOf:}ړΙ/r]xV")/;·ތj`jv9]fɌϨWsd"8u@Ԥ;_Dxx6Ӟ]31Ķ=7Ėl&c[cV~ǥ~mQ 2iM־jA˗!u@WOWON%?X}L)&G9kvjI-f-}{|./#>寞z3޿Z0v 8poLvnְv> }V>c]w7Ǻt[nC^{`7U4&;'ކ/9FcҜϏ?^e_4k2c8ske8C|kr[uzܳg'^ Ao }"Mdfr3Ǚ>P/?t#v#[_x*/ @+-:k[KKTYK],%xmSJNƈH# :NюXͱd6'S-keͯ`B#rho6-t.@ym?"ʈHm-m6emm:o/--nJͫ*$9x뷑F8ۛjo͆2S;[Um}zD:5fgz373?ZZQSC <z˅ akr#yLO׌? @SSs\8}wKhc]]t]D5|F0*U>Q(^罰xJknkʵ;W#;K6eùluȼu̓wmGmJ!;wFShʐI{c%.5$$9G~~8uD_jjUTզҸkQk"1ycWSq)ar? ?砛?zbqٜ3'dΙhyV@zYw21q@Gaʔ@JO+# #> W?զO*o5 F;*o|ҧF˯TEG&GtI^fJA%b?a{pKRh!hљ~)}}q G_Ld-y}.__S(U@{Y[BS#7:)=MӲPLvX>%N8GFSMyxٴD2p˱W $QFu*otXsvZ/E_/x0q0d^0ǘɦMLL{T(;e6,g*nlŮMߕgnΆ3#7:,Rfgq a94a)0iH<{0/Hݣ~M}CjORJ9ygDg0p`W)a2z2I=v%GtމL6Sҽ^*2Hi#J/z0oB)C™^34{#r s/P{~RN6Lt-g=/޽ϒӋyOHc_,ɦH{yɥ|kDly'X+[)u!;mg_*|kφex60%@茽75 =-lPrG8EivIHY22ܟ 6xn8.Tj̿\G~&_6QKxΉ{;W/Gcdɾ iDZlOҌՌ򝧴K0pA0=|4ѽĺr|MKr1˫iIC뤡^ uWpr i4{q$L9K\+6-WiQFmʨ~k8נS)Z4ZB&=7L1߲N/>g `[,l ^M˦KD@cEZlҵQ.vk:˅gi(+οA xpdO_t8rQUBaaw"AQ~V^ӋO.-',n&_WRҨCV849zʷwƯΦ+]_Wffo( t8-q_h/7=eCWbW?D]v/ni:gT鍿cMůo?co LRT jY iRW;jf/bé#9s<眡2^OIݦ<]/ UdK{T{wKT[,%mp\p8&毘&[{O)V^k|ޅqO5ʫx~]BꮧL@Uߧr˟Vm4ȣ1m81b?[~LO_{X>4QEݵA\/W}-_H4* uj%QΏHWny׻&s<HEH|᫟VWdŻ)\x6I 7_4gQ|$ᲁ SHhmhFC ,\@ @#RrMQP^SuDlS 6D;},;mڳw=,)镀6Wo|oOR_ 5fEzzZZi;&D?զπ⹀=<(gN_5;bmfqٓ4&gmk_+!&DAGjʠS}-@ٴŜOome7&O-"WXc\e2j2^UZ}qG.f6M@2.Twou2z3?a2: ls [)*~/0e#;cin`}0}ಁ 3xa@?x6IT_ÖMuhȦ#iGȀ6Aiˆ[5줝iN84 :(ħxΧј4YFG_]_ k2c8ske8cI;lIXXׯRhu* 455.?&lpכwm^/˦#BhECgmki?b.wɷ] C%j*''K$]@AG$@t tDNXWMMMMMD2~ '5-%keoᎯٴDBuJ7߷M7?._{~Z KtDU2z)Z^cV|7s3NvNkEM k\#G#aêp++t&& SGE~-]?ݝ7 1Ua맡Es%9F X!!gv00Yh;X.dMX[_LOT>9dI>5Zʅ?2q]~hZUH$T[4B#ʀ{(/--nJM9rTS^}/oGr1xke]Ru U=B9&2ڱƸu[[~۾FpDZ3؟2FStOr4ݓ{/qpa$}$YŖ;;EOI|?< Ǻnuc (4"躆"$-G}"bHԴli3Zib9,̭w™s@B{}&OZZFf0=WFu*/ 4ɅfaUdnUD<̱\/0a"|8a^c&q>qvX배uAWܘMK$c kp՝(2/tX>aٽ+PlzY/]pϺ(jez]xZc4V: SRza؞OW_jjUTզU4b]xzv.^9揾/,o5 F;klᇭ^>(fLG|g$۾ϟozF(Y/j~)}-OdjY*SI~3CTaiWR&;}U^?Ι2Lpg{Z y{heͅrRҳF8qr>O4K}qbMl}2{r/YX"If7%~_H177777g{ NUhI?%=}~οMtGʈ%w?%Dv̱~ѹ? ?RK7VK*?؟)a3DԸQj4MhLɦHiL{tƴ[i0(y׻&ǯPA?|~G|~ = 3TjI +ߟr/DĻ?w bb2,;M*@1"?u??}:*`Oi=OؔmJJI%=pc1r޽3OƯ|+닞Fq/;4~ Ϳ4ybS͒<#~oyl\>Sr ktځBi/sxws&~uXo|{̓TB썭IʓND-ˏh >#v\u}W^ooF,Mfc1~YMm"/輷sbBN#sl|򜯍{;"COSr0bȺO:d f@W"H$& _:..]E93 c$␽Qo(:.  1?V"nu\b+ OSH'Wz@ab/Vr=Wȃ~^__EV_7=/ VPPZK$ÏF?~TI[ y /=^׳IH|f[S3 rSJ:C{ 5ɷ5EjGW#^<[;ZDB=og?.T#ǮAA叭C+it>?e{gfig*w˷h%MfһV6]w'Ѯaʁw  v ڑ:g$%|T)M5nhZ5h mL1MRMI.vuh<E |5}pdDdI/<­d`\ ER Z+V4Nol',PKB^ |[K}̥Se>h\%f_&=7L槱51߲ʩn'<=WU)~K~I_Oզ-+$ĎWb~ x tMt]GX дl8]4ʏOy⹐_6e[M9pXMkXQQ^Ml9~K/HZ :u֩?566KV7j,brpY몀JBv>pN'B'F]{]Fg44/<¡O,g>ofnVzPEeO_tkNϟ7RsCP}HmW3~%kƱrfqc}_\^ 7+Fvi;2{w0{Cl( ِ5n9dBpWbۃJlk|G4{s՟ib Zמ!ǵ;uLXjT߹_mU7#> F<Rܡ5h\0ˑMQ2#WbDGw>mmm?#wktQ6eM4:B7?`2V%w%?qEx}||Kߕ_..M5-]w6OtI&A6IS#/oA9XZDB#iAkGZ^}@a9Rm,o/~^z_7eNKFoPe|2l^cM*n\u[ZmF^] O >Qѧ MZ~x>}P<1;Q,_8I2^S_']_~⹐5~5NL̓w!hO}jhVyG7CAgޤB@jj0|e N^ W?4ak ;0/h r򒿰ao >M;?Dy44|~H2 k2c8ske8cg IDATI;lIy7XPP4u1.?lpכwm->H/˱˦:^_]WµQZ^]xP /$hsmQRVLOTQU*Jת;Uё=#Բf&.)ÁմdSvU[_*1yc6/:e2ʹᇭ^JI 9 KӜ?6qfnfǁtoP{ThalY,4mKQ=y{4`|)` ݉u-g~Yf~гlN7iܵzܕyH0/a^ă ݷlc /5Lt5mW N,UK)ᣉaj,o\uh߽JZDbSԦ5?{OI ܜ glG ǎH[='5ʥ/BgTFmʨ~Ny>UEs% /-!u@e>Qϛ"D-8\8ipZӲ]lʶ>:4iXůmόret]2 #*61{w0{Cl( ِ5n9d[,S ]QX*OUD*B3ه,W&)&GSHi|MӢ1m81W8FR|9)?XHH@M#[P'c\"W/_ޝdjnj XK{Zsx9Cgz373O5w5.4.Uߧr'SA޽AD;"j-f$W ^ѷƬ4~ǥ~m_Sx;},;mڳw=6)˗!u@WOWOs>Ƥ91(?6~/~EX+Ù[+KbaKǺ&źbH+HRZ٨p Bj!Țʚ宕Zk쑯gt@!D6, A,fDAG$@t "뺻9USSSSS#̦!5Բ墽U^@5DGd]U/Sы½ƬpLofkj~=)%Ŧ0}DBͦ%­خpњ8xԚRfkzu2sNplUb۞by{VlW  =Bք5AML[&;sɒShJ m-m6emt:o˗6舔VƯ\3~^$M̱dS6Ɇ[;Z)M<{yЦdb՝P(yOj<"~˅cwĺ1`DG$ujZ6_ӴiJap;9hMyxhHx}_ٽ>O@M-pF-rj]L@UߧrջNUW.2r2S3m X ܏3Ռ'_5=ǨlZ"k8XUG|w^I#eʗbg]uu)VTޱuc”)Áմ5?BV=?>N^VqUQU*JWш{,GSJ&A7@ayi?Jo?S~[^2Y]ߩ$!4p@ˁVz)%i,,}]*PpF?=)GR_GRZO-hQEwI4Œ 'sᩤtU׼[Uccs'V.1TIջGNuz6W֝PK.]CíCAA yʈ#e|~%OJ L5\coBEFZfܵk[NO/?7MWz˅g3u4,G- TiFyjzFU=Hct]Qolk۾U)>Z#FAW\8O[HbWiT赼ZmssssssŦ_<`qdzi뫆fD"i1sS4GJf;=|4=L]KO/*ůʑ^ Q_߉zz /Zy>8OTb24{cϽ8Ǝ96OmJ_Qp[li6xtDR^叭C+it>?e{ԁhgJ/lh%MһV6_d$5L9PS`0Iihccȣ0u`EuGuêN4ߜP ':kbQoWL'|6vJ:2-d`\ ER䧿Z:}56iH}4jٺO/D- ;p)o%_ʨM/]۩;OѧjSpO?חe¦l)CZ mo{fpmSG$d:gT鍿Қz_~NhAшI$ Ln?\-vJOG7@ijj~\;\k=zc\ rNM8]>z)tFc|McLbQ۫AUD*B3ه,W&)&GSt ;=DKywtx6ΆlƋoW/ZM[cpZc#oIٔa,$y_4gqta?a4>54eVvcqrכl0_QH,{-qZ&mѸ6hXmSjZl;6IitnΨ^k[2躳y &UM64M/ou^Z@+n*$9!sΐ?*o]q|.ukrս#v9gIAN:{'EY;߉+~g w '7j~L#[P'c\"F4sOiI:O${̓!KtD:˱W ,ܕi_l]dټjǚUsmkYy4ڳdj} MI[5fE9iMWbkr[6@5 3ʌVf뚝T_}JW+ ^^PFVVGN_5;-}-qАMUxU.cratN&=[U~K~{ʐ:e'ZZi;i-t-t ;ULoL>BnpOwgNNi(MGvʀ⹀=<(g|?U4.lF"q՟ꩾ(qAOikQk<:w0 D f?M$%uwsQ\&V3--m g^ {Y}ۍkOsO1ikL9I`/hʌr12$oMjn I~eI*\+.c5ƪ^MʃH0E6eU=wfkV{{P:"@Dh9\ZFtZ@6, bH# :7Xͱd6"Okr[v+E{pl:첣~ߦ7x ퟷo_o=~ae[T5vUL}^FQטn:ӳ=ߩ~=% U_{h{:pXnvD??a맡Es%9F X!!gv00Yh;X.dMX[LOT>9dI>5Zʅ񖶟v+q]7vO"No#o|=T--nJM9ry)>x>U^R)wMtޛf]w]kGǕ͌^mgmVx!φܔX[~UEVVE ͆MdC䊋=rt{$I83ygDZZ}J-$Ȍˌ[}Feh Qur:'G耨 r WXc\<9 ݜVmD+8wFSh*qfnf׶szZROTl/t,6C "躈o4C~z>Ͽ ixr5iܖH$5-NkZ_KNj<6MkHWj簄3 gρFW8lC6q^mF]t^^m+^XQFu*t0a"|8c&q>qvX배>P#idc RWE7:,^9Wx%}AmϷfLG|g ۾ϟoz>[Y/[)_Z<߈~)}-?ejY*SI~3|>'͐?19sBvS $oo4PN7 !ƶ@<*Kɿ\ꋋhp>o_oxz]yyssssssݿ7,o\u\Rj=z':D'QQ~],ZMd`~~A#ti"=N'NhL՘/ sj'0wk2_ } ×y?COFiXx-7 Gl,},O]ï:L{?KNS,-v>ff?POOJ$XjzSZz{6GnRRyIX |j_ įNkU]~wylO5Iy-ˏ){@#VqU /{!c|DV=yoD'ń1#~޻G#9_{;"COSMB,^Y2B]dHP*rĕH$l"XZ!$=P`i( .  a3?V"nu\b+ _NWdSr$"}W)z /Fbe)WCKy<1?8Uj_|S:_@g"o5}W/$y1%G ?{%MB3~ŎLdC蔒1}",BMmrkqkVx!_B*cƃu֡k ^Ѵl:_|yNOkY>ٿr7;DIi*r5Ikr]G]ôvdi FuGu4-Ő=i99UL{u j7GR`ڝ`қƴ}4']/#oŋ=¿J/[ HZm:u֩?5ݦ}phzҽlY,+\KyM̱dI2Xl/W fʽ ݗ}+Fvi; ;1{w0{Cl( ِ5n9dӖ/|4s7U2W=M$dBS;$v'kX5n9<F%V_/ D/V/^%:"cgylkmk#7i{_&崈,O,A -nw*w-{-qWI(҂U9TjC_2'lz!e!L}^,EB"δ5#->o^+1^4K2Ж_FZ;Ae }%p9g(ҒиNk|OD6OzMUd|# %-V^Z]Bꮧ.=5\}*Wp}{翻G>.¿gùlJK Bǽ\+jwJ6֘c0p`!W-*v";˕MɺKKQ\"1/\LO KnoZԥH-}CAwh2^S_==DoT/İrպu\]dd[ u\'U;4֪Ufkhȣr0{AQ"4(ZѷƬ˅g3ئ6 ɿ4;UDo12Jܜ g4;\RPPI+W ^3xa@?xAY[5;mCC6UUpɅ2ſͿ4y* z/_S)EZtivr*g5GBzNOвӗ $-;},;mڳw=)v&~hUrl˽ƬpS\3 4yhiøj*y}xUs~(+6Q& @Y4=|ֶUm O| 6ɏ`LiK򥀽=}R'WSE¨ hoZ̦ -?blgPf\EݎQ_߉heyzBUKz4ēW\-_bRC Fz;Z:6+ģմ0a,@'؄?i'M͵jعZNŃ# \4/fNOsO1iJ`/Ζk2c8ske8cI;lIyW u@%@ h*%9h]{C9k9RZ4b+zvbyWkٿ$ , P_@+:k4h`E =IV  U5m :P_ߓ%.h UݼmBAG$@t8uDK-9/Iǚ:@%u*fN[ϬU،4mz :oGn [15;~˅cwĺ1BʳDGSs\]׮Rn5q5 cm6UTզlNUt$uHr20ɟK˗prnJp oi5mli%IH*P@ˁzDrn~aWxy5oRoSטn::~(̟߬06+ԲfkL#LviVPJҿ]\@1̆Ĝjm:x:f39syyHdghQvzxK;MM\ho띦UJI.R6j_׍Im0)ih%x6#Hz_H$ʨMw\Z6}|áSTx>:³\XJQJ"S_­T<{x#UR.4Ʌ͵ N+UD*"HK$&X=pq;^wKYn*:e[ҒtQy<HSLFѯLSLNZ}V*M#BAsI/4±嶹;Me{N78BUhUhxh8|L4Im˴˧q Vv`&h4~x{H*IAoRCwa; c6L?^Fw'Mwv|AvVV^kwVy2o2iz$ٕ|5_*'5 u2+Ffid̿?;04M~99f(~J9t1[vOT*U8d]_\}BrUv\EHSiLέ@ew4խF|1ͺux»f a~H&7*F͚5k^Y$M]\arggYC=Gxqi^{ޟ;C_! ]p>+w+]tMpM!&fDcwŏywl0ȓ|V&RljBHxM5i_\?0v20j j[\oa(LեS^_ȴLG¹9~eOjS~n&o4i䇧0{VѓVU_IM- ﶞѺ!;ONu w~'Cm]}KnBn T[i㔥q իtpM`\ T>6} p#mA!>7$:c#[cUW.U Ǿ NJ+5rjgfJax#Q #co msz?LG$ujLِխۘO{ϟ\:M߸[U-uD 70m`WnȷµެpI/V{bUMm 1v0pҁU:XX.Fjtvԇkf.0UrVt˜2еjve`m`,4¨R|<Yq:嬠V4w20z20Jc]z㎷53$ݫo0moHUN'2?|uiAu:Hӻ§SH&_Pp@)?PwiC]zΧӣXz /|ŢTTBʫzVkM\#S7ño:P[ oy!k׼ }0 |Ks*zSy]3^/g ݑ9Rz>t׶]}O)-a 4=TA?;cgcȠO5 4 * /MT991nQPJFVѭ)U8-- >'>O Df8*Dkޥ={?,0l_/%=o94;+h#y7HtMtZ Z i:xCAןɛd_>9ξG|#m˾dO/c?jYg,x38sr~xFFF!i{Wz/‹;_>>8r3Y}ɞf_R)S ,_x^۵^M׸ujR?FF]'#i)[jrK?X/zO)G嬠΄?0x g^g 2?????o?Gs?H6Q;r+z$rkг=D~ {c/!K1K֢kTP _Rߛ75C?CC'CPY"G?D uB3d^_/S/5-4%nq/tz|u^52jZѕDOtNw jcñZ~}y],3BRrHT( ]wO)Ra|1Ǽ'-b/z_Z\Ex}έmcFOkGBm[En_G޼>GnM|b~~eO-|=W(~#"iYà 7w][R||1r]K[@(i2 M^޹ZQ 4}踯}ւ-44Xss4Q}~{fTFbzHNnaG GL@6KTV:SOOpL8dFYX \5&ޭ&s)I-9_f,W)7kc7u 5M'.m>B :|!9߷N0k06N^WaAy*jR=W=Kgƪ=&em1GO8ҫmvQ̦]6xhVg ^7r9_nb.غ}ċpjb&Dj"OM4F,#ٷOKRW/ti-rj v TnywƯ%{ooe:"F/n]n T%P}b<Ѣ405>#?Ui2b2/Da,C,Z֑.Xƌ=덊\F lJ&l<}iI׺dWE[u%#)W2 tvt3t}ѪX}VK`%-@== mYfT.7*-mh,]юӜcZ~i\gcСIV /:@${#vĄ[檤:+{ԥfrNkG*GwUO??E-?DKoB eFL o(tS~n&14,KKn ]LG?GhzuM5}BPmcv2[gC:>R}_\oV8t?[$˃.&u&({6=.G|1MJ܄'wۅLb"7ɍɒvߎ[}T^5n~o+//^]_}kY"[,ؠU)|f=f5ş[z@8nӻ3+5۵[&nwl9tfb[ոwܬqkhԬQܦ_e~[eM0 3EKOMtvW~HE垞e9?nNK lrU<]ɍ Qfy͚q0M$8+JczcDnML}{v=*4"Wn;qݞ u&a;E|}Υ hHakNG5҅n>@dRV`Z`zau_ĻX25rqL5i/'Ɍ=\_}Õr?yo?inD8 +Gvpߓql#$0E6^( kw}[V(/!~,BCG$:" ?n/+++++cJ/%=]v!ۭ+-t>wt(/@y-'_շRLGdY])tW) f'w]YXHSӥ&d>Vn.n^lf8ӜOURU?qwΏ Yr4wܭt6q6\ sQKKc&jlg?BG865sMQH\ڒ/{^ۭDǧFkzw>yk7 Zʕ-t~F|U ._T.ےT^=V瓷;?ݭo=?5[\@W<9ogUjn`57̧ .܄g| }0?dxJMP.cTǰ?yg2`{M#r^#OvMv1.a\ c%=ɍSȍ{=j&V5A]SƩ>6CDI&80۳r?0q7xU낺:&PWُG\|$%$'ɻI5VA8:=igwh7ǸQeVφ9p>ir 7j<}ҏ,)'Ox?̈Hmo#:Yb[{K@wveЪ܁mcZwuAqkYC~ŞXUpS*HG ]/ c6t(,g,t|#h|:j_pS}:W%fWUΊnZZ-n ] e~~o=_)_pS j^XRF/XFi(z[of{{zrwzDE#֥wO]0LfS~NIhw:@?3 \ʫNi,SRttF ֥֯ϖT[Y>^(ߕ*_~퍶+FJ 6&]x~G6u7Ez1-wt ; !]h:{:y]f:ӽ@JRok8SnR^Bҏl'b3^//~!=Y)9Oޝ'CKmtnFܮ'YSTG?;cgcȠO5 4 /$=#':FM=*wSiI#Nſ* A'sy"}=r3_P} 5H=e~~l7*KIOk|yܿYyF,QV˱V Z!M|h:H?y'ٗ_㈯cbwٗ)eǾX-=S/bBdNtqj9~ &u h%r~=vm/t'rL8_:Y^'ٞOrOPC_s)R꿐dϛ?s[#[KW6P]t80?X폽Z}%r%j jkTP _Rߛ75HCIfƅj:!2ٖsY~8:=k̸ZfVk%M$:s jcc@f\y.ܳrr?/_zy--hRxs_lx C^廲ˏʂ_uRJ~mOW?BuMg 'g#SөeFY(0dاM!/Z:x>[ۅ eskυ?"_W:Rj{/h_V5z{=_S _\6#"i~?}׵.l/z#G?o\[¥حpcI4N/\[pۇNۇ(>t>DkG)hi` M -^wշQgF']$Jr0x#ix;ףҩحtN~5p=x1p{q/tz:yV|Fn^s)voԘ!M@7XdFFF d_ |% {!j~j\0sIiV f JůI-UH/l8$wDx~m?υh?y[>z>yo?y;NF;>>/ʢo"ZӶo;]D[\oO4Kl|𮵇Bϭ)V/V#A4;qk?紦ҟm>omdu2L\coz2?ҹD1}2꘎SwW{Q^g:5m5'BG:_@P}ȋM==pm,/x-tx_Y/?gNK[k&^D4bM2׿{0~=6k 9X-{*VK)Ymț&^7?QEt!Q(.l bn 4HJ~mg%~[{[}pVv˝28K~&ZJv,PQxKrM]x>Z{Xj|֞oŬ2Z7ڮX7xuiQOmCZϑNG\Q*M[L̵DDh5Œ`3*_l%&!鑂~) ?j_m]AuzVk/L]Bcmcԕ\04̦둖m6ZZ[V\]Z8h/bwQ10v~oqKT5tE8fNsjizqCW-Uu^RH?uwuΗhpUv\Aw%geZ?ݡ:F:c$\"=@J)?7oKqXJ둶GtU\)-&1_zA|3̲?Ei2bφOb>8f:n ǹKɟR#/gnK~lr˱Hؠη1rXccn/-wDJ~PR>KiυԷb>oOJ2#5}; AEmCcv2[gC:R}_\oV8t?[$˃.&u@٣nR({6=.G|1-Hqt$qNnvҬtkРh]G^ OoQOw>Zݪ>6hUg~6C`ϾYMn疞bq%Wخ]7qthd[id3ےƽf[Ff6**--%|nѢ]ӧR]Ӧ`:H;+?vrOϲLV%G~c:`V]3v7պhqݞ u&a8g&7*F͚5k^a2:tU@wsƖ ֍>5k]M8^z| \,=?kWqGiT՛M[_3!)5KBl~E?I҆UPȧ1X5;A=)/yOr +z>Ҟoxf{t4 77B^B].ypUG  ?q'&v4+'͵ڷf>IԬX],՞>=\]@^8՚=eFG$.Z\!7K0߼P='tA4Q+)=݉&X*rU4#J!?i u .LZ^ʑ+K[P^ $e=x [7c_&&'-ĸqلDy[WPW <. lx# Pp(p2K GJ//%=*Ws K W tD6zՕrMwN(]J_<~ L^.Ջ(3|hb)K0FK[':CXNN^W lj4}4`SUTUOܝ Wɡ.Cxl>P#lû sQKKc,jlg?BAm#a\'뭾g_t|htws{TNVg~=T8P8ǯ|0~TjSZƾaмad1[UGOZU%5"0zFf>91Oٟ =jKt%&.\懤. =zL%It* g__,w 1P}omS)TMpl>4igCЕmj[oSNO4Hʊ 5VuXpkp:=[#|FnF?ᎇ;QX鈤Amz:"[-1ş?t 4kqkZn`kݤo9qkYd,g'9CD鷪>鱪.ŪTA:bza%իt\󍤣X>Շy%\aF.}5Z9+ʢkVEX'iDR|Jy==tYAng+e`e`ƺҫokfȸi_\_`Zߐ!ܝO?Nd~ʥJҨ&u>E#wOU]0LfS~NIh}Oo,SRN_(}D9|Hϫ^mNh裆) 9Rz>t׶]}O)ia 5}!ᇡԱ3<=3PG}aaUY[[m]t 1G1ǿ[UG?1G;FNNuzT&TҡUtkd _ΆAKKàsϓί >Qwz2?K#k8[G^5-5rs~gRt$&V˱V !M|h:H?y'ٗ_㈯cbwٗ)eǾX-=S/b*gNmΜ=o\Vuee?W##}QMMidu4vcdc2_^M+_#|!k^k45MWc3K.74fƏUFVg -]0{>ro:kZ^Kៜ(5":"iYà 7w][Rzbo>y1r]&Qd@ʽ smIo:lJQYKQ#ӟK0Lݺ=D_l9a𣏤 ԁ9XC_JQJշQgF==5RTV:> ףҩحtN~513x1pȌ 8:=jL[MRX/Z~XxSoF obF%4'42Z!3B/!+=6O6ۻոaHar׬a2==4:{-r.AR8~c"m>om jh\+W 8I_bk2_ֈ#:FQV WQtO+&ߨk/s3њԚQoͥ:Dg]r?e9BoMЈKj v xl>~a3[x~nb.:oE855_S)JZ#r`QF@_B;Ѣ4>154VvqUE4n1~\0ȡCVF=4mШسި٦6:&5"/چZӶo1A#BQJ^Y}Tk]nd0JΎnc,΢Oۜ ?m%!h2bwQ10v~oio TA39 )zqC'g[SXlxԁҝtAA]Շ>x*eJ]Y٣gͤ4Vڥ+RW2W-&1ۏ4dz~~g7]hq gH؏ꚾkKfev/6I8f':Mӯ5_+ VZvOo0mo0v0/],J`^8՚dFG$.ʍ џ֟DXt#@qɸb#$0E6V( kw}[V(/!~,BCG$:" ?n/+++++cJ/%=]v!ۭ+-t>wtZIxA}(hPclV74+J9ҵtdWn_QV)uM'?2w v gn.n^l.V<톰!Oq9ϟzzW 6Tv;JaS?gS%ḉ074Fvl/tzcS3!-뭾Jt|htw㚷vկ\B'P}jʷ8 >y׮/*mIj*՞K+j GA}{-p?p_`:UX-{*Vs3)àyO ?<|5c2JjEhaxm 1|rc؟?j{=ԖJMt \tpNAbzbOV]zzVk}rrc@5L,-S^Xn¸n㺆?lZGmM؁p#mA!>7$:c#[cUW.U Ǿ NJ#F^ѭSkn>u7ص sp |n;hE&)P>tvҏ,)suW) 2!~S[OGduku:6f'Fy7xVUb }ms7պ4-5r:p7+\7<=7t ;^5A` -Uߞ.ŪTA:bza%իt\󍤣X>Շn K0hեF2g t-Z>jh$]+?tYAVg+e`e`Ɗҫokfȸi_\OG*wGS~\.:nRS4b]zxzW ij<:.vؿ{Ky }0ϥG-:NI1YG|g$ZZ_lxB#ӳ|}XPPs=ku6up8MrmgZ%dʕ]ޏ=3r }'t9tttE)_[׮OQ[WKy I?oũbyڟ0?????o?Gs?ҜЇjԇԹsK*DP8pZ"G?DR߿營~ɞ~Iᇞ]tFFF':$:)|VHOt&TɌ q>OhMۿӚ//CɠEd<~31=}=w'x_ }ȪeUQH:_1o?tܟ|~~w9~]zy]2P@uKM߅Ɵ[z4*v֨P ̯?Rʷt|[2jQ+i8 &%=_l,ηF3/Ql}z/rTIבǶ::tOךI͖B_j%"o)@BK\0CFzv!ғb_.[j>[L:+(#u~"sRӬry-~<ũ}x(GbvD5iawk]JO^,>Oґt4F~Ύp)v+\h%Mf ;V+!ڑO9ܿ MJii` M -B:|!9/n !EKj\0sIiV f ieu_37Ǔ[ TAUmҙ*eIC[s{^ >n:Ⱥ yo>yiY5mVk>%i뷜,fyM?_YR߄o~AB8~iM?|LdiKb>E[tt-Id8k|d++iu4^9OFQ?E}F]{Q hMojMʼn鑎~bm>9ٕ[Ef>Q/̦] x:xoB%|3/nC@S'o+5"݃X6xNM,|Ͷ&^&s3=Q(̯|^2cH&?5kdBpAZ o0ҟ+bG7geYI_23Ѻ|ӶKżޅ+kޅBׇRob7܏{?*gʵ'LG֍+֍껿u?-;-JmPC˺鈫/jWɼwɜY,C,g~Q8.Xh&g( kPAKjjKfP{i3PetO׺dWE[eXNah3-hM-ܧmn .v}Eg..-SQܨ;Em99fȡC >l᭪/v/^,u`M={#vD[檤8+9+{JIwIEo)?7o.]ֹ**"^x~]Js+ŴD?:yK5D|3e{MH\O^+Ffi/}B ?L ηbG1u>b7z.E1[AKocw\﹥t+ oߝBׇRy#+`)?iJ2#5}; LR&z-qpN&ul[GÉ5_+ g|yeu@ݤР_e%9趑/F 񑘡Ow/Ʃ'Mg&깗3  An\ivߎ[}T^5nD~o+//^]}kY"[,ؠU)ϱfSlh\$]cvc m=ls7e~ƽf[Ff6**Gز $-A+ (G^ە^==r3Z89WkYvIJ4"Wn u{6יwԟ(5k׬y]ɨob#iVͭ[.X7Tx׬!\ wew.C_{m׮zmR_H'ˍ\ηbGq7eҨfӖ%nj96jy!n` TC)/}{ WJU=܏~QO)(#{{t4 77B^B].yPhĝ H@sY}8ưX],՞j4rqL5i/ǟʌI\_uʍ џ֟DXt񕣒Ӹd܃|(*r3Yֵj>r L#R/WLZPj w#` YAxJjփ.Xu3:UmrmrBOa;M(/xY)XaBy@8#&20~ `1hVoPhCG$\֎p2K6HIm\"J#fbgů¦-@~>[['dS{^GǧFkzw>](W T}SʥY9XW%Y]`el?ᎇ;P84(Pi:~}Gݤ_J!?yDB|uiAu:({O'k~|1Mp,`L +|j!4Lau 52'O:n\])ؿ{?1).nRE)dS?gS)fk׼~TG5 H _j#VbK;f:ڂC:Y]fJi2)zVkMrNܦǾ|_K㿨*nV743۷ddcdcXzP`U6.z޸m?bv'BCH'#2~/SX}Z{uLMZ915"㙓̃[̃٦ =jyo?RdQ9+]DgŸTT 0U׮VQL'#i)K-(^ltGGwVۭeD#m;FkW6JyV^;_[tP\@LtMt/g_RG0hy}E':c#DODQk֨o I:wJy`%%-/J"6?E"Kob/ܿ G4KLdOMؼ |gy> !cXgi鯎t~f=%<%K/fN䏔^ĞC]_ĺ%K z6г/B qK }[}a|RɓL$N.)#~QjAx¯/ܿV:- j{/h_V5z{}/NjRB o:/8"|B#K:~t4f3,w<7,~i%[_R|7CC![!OgXuD:fNs: ٕr7Ѻ[U?=eW/!WeUI_hgeYLњӚh"[վƘ(U8"}!tU~)s-=B3V#S٥^^cd4 n:ϿwYN#[PIadFh\OW~Ν}k:7*7*:F:c sT+2~Ji_J^珔E)_AJ}/\ YqƭYRF}s~ImmRUpS*5}*5m:*CzmW.zm,,Q}kfBG|1ͺu 5) Mt7պ>) c\gqɼ\,N^bGlxK(\:Qw7Mt k{vs*eczck~/خ]7q_FӭYm=ls7- ?WHh::M[|CJi?Nl_El~ o!-Bܿ S-0`0k#ՋWqjtLc~/ ?QoL{Vk^Jϓ)W ȻZNJn,Wȍ併%z]rd7ۇFH&^tĊH?vD RkڎHt2  H(8tDPp{YYYYY%#b㗒ppJ)-mϸByu~ ~_J='lV74+J9ؔ f'w] Sӥo33|hpfzO074F]J;X`Os?UKU9?n% ]p>+w+]tMpM!+%6BfN)ø>>#OE[}O+|;xM5+rez @QP_| ._T.jҿگ|o~/PL욭p?pnWi뷳zo5\ nE>a@X@|75"G~-h?#q.q= q=HM: ] Nʹ& Zչe;VMlbU8eiic#1|/ ru{69Ot&j]PwSPʛJB__b/tzyEFn?݁3Zw1ndJ]0=c3B @ n xlW<6vr), 鶞Ѻ7tDVV'[lcv?y=rh4~nU ZU=%fWUΊnR njS[-n ] I])8!!t̆Tr?|rI7V˧cÊ>\T0__ v|Jy/qVN9+ƾ %~.RF/XFi+z[of4~!=BV;=~K֥QM }1ƨ01, yu7)]c/~Ic,SRS(#$׮y[ F)K-*^l~3rLP[c )YȤ^Y}5>5:ups:8s8͖~)ׯN~l3Ķ+___g1 VkDwI}G!_ۆ:w= dQ7tcHbgÜi⧩Ua'ݬS/_YI;FNNuzT&TҡUtkd _ΆAKKàsϓ!_k8ۃW|yܿYyF,T(DODW`Z̐>4qΟNˈ/q1vOѻK2Vc_|ra1S 3":9?oK*KΊ"ѕJt&WW?7 fhյkU~:SɈcZJR)J!Ć[Ŧ?kfdj9~"UR zƭWW"bφA+:ItR+mT5*>%<pvAl7_ϛ?sFnEDn-^tBzvӡgt7V}BDD-Ah%{jFF~ߝ}G}}D}hJ7K=|PZ'>Cq/zs.yȌkeFjW6?yiT9%>V]7b" $W ^_ KXxbOq))=_)uM KK: 3l䋡g_R|N!~0j?Ax¯/ܿ0m쯣uѾP[֣}[7O[tOR+4~]mS C.6F~Ύp)v+\h%M˥gkNҩحtN~5>x1pȌ Qzg.M5 i.MTFߞ t(Jgࡏ ۭחyWɼwn>u7ps)v+^J,3+ޔѵ1Co:pl jȌ Ampuu{1\rZ\ ዙ?7|)O|O|ގю|Ŭ TAUmҙ*eIC[-DŶ ]FJPEz~>>nHZqk?紦ҟm>omdu2jOC9%hMojMJ#[Io\Ʀ]xhDIg ^r7&撋-Bk74XVԢ4ypwƯ.Y1Cbkņ/)n}*K7낖[PBAkT1L$sDŸhX5FhKCg-,_3[kOZ|O~"6?Ŷo?nVe:"F/n]n T%P}b<Ѣ䝟lвG:Ui2b2KnN4Ru'i՞>}ose0+ЈPvTDutKMv]QW2Jܯ/Zm_ŻMeQܨ;HtE;fNsZڟw9th8z-Uu5bË?=e;_ZbU-sU*:+{[SkrNklWfR~cB+JmK3V#S٥S~N?F kp;Ticzk4)W0(H/OnK>HBҟ[?тì~{ k ?n_5}Pbw(eX-h.Nվ7N4Gec+ǴZRzHHME7\\!7K0rd77= `5Rݩ:"@!*Wj#@y@@G$@_|zByd:" H\p{YYYYY%V:~) O ne^)ŕ6HoCo|q5U+~>y׮R-h>rPyfuSOR鮔S)]JOv%&}aL^. 1|215 4 (]JW~ ,?굗s~<`s6+nE>?IDAT5Y[#ll $71&(]}>8θ-~Nplj44%_tt·[O|\n+[>PAw=y/*mIj*՞K+joҸ_>C[#R\@~uʸX-{*Vs3)àyO ?<|5c2JjEhaxm],d.ˍ˃.7)@j~J,-SJ7܄qݞ u O.ѕmj[o5 H[~$n H3z+AXՕcUñmñb_Wtkhͨ-+vgÜ?pG#z@ ' 7jڇnB%=A~ .ԟe:"CP[duvmn'O.oڭ: kۭZsZS~z{bUMm 1v0pҁKt\󍤣X>Շy%\aF.}5Z9+ekVEX'Jş{z Yq:60ٻpJ`mqۚ2Թ)+KATc㓍Q]SK(ar? cY8>|nSһ4c.=4 |Q??ȭ:%LgAg͟kJk]:~ PڒNaBl~秴Dh=KܦRdncc-1DOO2I꒔_ekqҧ-'ړԇg 7diΊrVC?CBB?ҹCm%FQP} (U8-["_R OIfq~yLZﴦP2FQ3o2_u*5X/ا_D6ޝxe(s/%=f>f}iZ9tkX״FF)jWmrj:}'OZ~όaM\Ë?ׇ>xYU* IK aTYoT2q<_^^/S/T۽t{a]RlAbo *YJ#|K7+3v ҟ[F _ݿ5*gE/rѾ-z7Ss7ma=?tOׂ$I7[ IDYlB?lCFut[-A-h_ڜkț'-ޝ~eO-|8]Pȉ߉4etxU` U`OScRVRUYWs/QQj:XX7j8:!4:S/7Z1/tz:!,79̜ƆcK_.!g;|ȅ{/^\zӵ!|Kwe7of~7K_j~CL:+.#~||9)iSHKN?Oևg6Et*w+ >Z)dpV8Jb9zr|ādAʼn^L[MRX/^Xx'o obF% 4'42Z 3B/!盿-! aSC?UָnV:KJ?c5kLOKyO'?h }Sٝ(ߕ-ܮSB_\no:Ⱥ yo>yi&5mVk#|[e[KĦ'ůŬBKobh{AC8~iM?|LdY*/tbWt@1їFɨc:{y"<ߨk/{ai\We#|~oos+y4֋FP}~5^M==pm,/~-tx߄J/?gN{Z:hŨDODWjE851~a5|_mp7"Xm7M%`fwã| Q_)~I1˫Yly_ O9+JI#r^#}ףUK,_/5ڟw/bu(ae:"F/n]n T%P}nMiQUd dhYw#;qUE4n1Grdy:Rj=^ʅ.:k=Ruɻɮ1J r%HdZ0Fr}VK`%KK?meT.7*-9b&Ui1C%]/41rbг~X[-6XtPpGP|iWeUI|tWrVWhS~n&i%\"sU~)sU2Ep7+\ }eU`U@٣nRhgnDcVu8?r~w[=疞bqtcNwU.U-n7({6=thd[id3MqY֬|YM}ʤQ-l2I~tMJuM pG^ە^==r3Z;9Wf{U籷~!jo>)#{{t z77B^B].yOɟ49Ql 4תjߎUؗO/ijɁՕZPyRƴW.i&Yrśf\!7K0rd7@1{|O=x7C9ra@Jtz>P0c^Yj#6=b/c[j v du)YȤ^Y}UT7rups:8s8͖~)/YӬ&8[0.7E>>|F/XF f^7x[qiҺ4N^dcTzx4Lau 5L^:?smO^ߖ>_ܿV#2KΊ"ѕJt&NU@*0x >;ZuhUPM3tL87n1jdNhdyo󮮢->5"Z!Ow,t?ywΟlj^Fq1vckvkGo?ުqոUHMDOD'qe8oDg'o˨5kT}Kx$ɻIrl1rrj?e_ؗb,3W{}eNu/=[H~nDR^_;j~~~~~~)Bm%ԖcHu,W,L?ȭ-)Y:|66/S/zl޿ylRΨ8J'#|0:d"5-/}.\*W0г/J}_]:b_HRbW ^Ο_mcj j =jz/r+==rק_S _c?j)N_WxDscpuu{1\2g[i obFFW#|B#Y' (6|)O136yo?y;NF;*jR=W=Kgƪ=&eOURUs#| ~!t|#X,|Eh W bE2c7Ӝςw ЄgnCCnv},WM%[,h#M݃鰅^O?zzvZ`,YO w?O2t?;+[-ZP<_b2LgyD?7HM$ƈXcq8%_+|iDbgN/֞aK˟\/YnKiUĶo?nVJA:"-&Ȗ}/n&1& _NH_]_\}BrUv\􅓶pVEMO?]^)ߵݿ]l3w^.E{҆$ zsNTM=*w?k{vs*eczck~/خ]7q_FӭYm=ls7- ?WHh::M[|CJi?Nl5n͗5LoL]_6UkTk6MRSx >Ino2Z0aakNG5.UvܬG;Ә# Oxi\jMK2##7ayWaɍ џ֟DXt<>+^D#R/WbEN}vD R꒵#/A@# Ppn=^VVVVV0s_Jz퓻B+ܛK•W޵Y:|?뷰@~jfuSOR鮔ӍM`Vx+y7PX;eY=]o:3w v gGx_,{s굗s~J=»f t}PV8B. M̆ƨ1JW]_3``S3a\'뭾\t|htwޕKu2](G(N//*f5b_o>ӿ7חgow(e:"7_[=Maмad1[UGOZU%5"0zF.R\b/;6POI㔥q իtpM`\ T]ަ6.g?#qC36 5VuXpkp:=[#|FnF?ᎇ;P8y&)P>tvҏ}IB~_tDR~Mm=խۘO{ϟ\:M߸[U-uDJtӿ.ŪTA:bza%իt\󍤣X>Շy%\aF.}5Z9+ʢkVEX'Jş{z g锳b`kXg20z20Jc]z㎷53d\/ D\.:nRSяO6FwO-]0LflS~NIh}{K*cyrRE)Ο'mvʟm0G R—ZUj#6/69f厙`A2SOIֳ^k}ku6up8MƤoK~ttg$}(\""6?smOև\׷/r\#ҟ;OC?҇96׹4e 3:]C;M+ G;FNNuzT&TҡUtkd _ΆAKKàs#}zr~nOTc]QQ_gl,6~)遬W|yܿYyF,T%ɻIG'orlBnfHҸDOD'qe8iا]%{}/VrO>clExċS3gÛ5M'42 j{=-~IsɔYѣrVDgŸ|us U׮VQL'#i)K-(^ltGGӓaV2jgkW6J9ӯqոUHa Jfb __Ad}~)M 4d3o;n[#[KW6P]t81jCЇ,,yş CID~nyuoP[PKKl9,I[ \52j함ooJ0'@u_BcX->ؼ.!g;_9lᅜ(==KKu,W,,5~BZcዙ?Ź V6#|0:d"5-/}.\*W0г/J}_]:hOO }/__g~B p趱no֡GBm[En_G޼>Gn}?ʞZJ{'P"FD2wk]JOy^,>9yc¥حpcI4Z/◞~hi` M Ewi~{fTFbzHNnaGpV8Jb9zr|Ėd!3*d_1n5KaHwerś2}36f35M'.m>B :|!9ݣ aSC8==θnV:K.U붜sw^:|1's3/)~}{{1t2ڑuTAU*]걍U]:3VQ7){hKCz"~HsaxKHi WHڇBׇZ/bIW4N}oS[SMʹNZsKX:tn@1Ccd1=7˜LG7_D|~oos/h>Q̦]xhDIg ^7r7&撋-Bk74XVԢ4ypwƯcbOfz _ S&Tfn-[eYI+G;\ҩ2cH&?5kkKCawm?9Z{ڇ/-sM)}W}(D}+D)t Z,90zuuc/j]['Of۠?W_ԮJyYJR/ ?jG*MTghxz(I׺dWE[u%#)W2 Di]WZOji\l%Zm_-FE+1sstӸȡCު:bRH 겝/-1얹* U=5m95D}q3)?1y UUF?)Jkk#y541u+Ffi~ׯ{#1^}.AH/Sl >In@鈴MO4"ڝn6I8f'u{6ԭ/fsHlnnWlPrZHKMkGY֬S|owy%\>}eU`U&ignDcVu8?r~nh\OnJخ]7qgYC׻FFn:m-j;nָ5k4_j֨onS߯2i2"RK)-Z5}*5m:*CzmW.zm,,QTݿ]lШm#_nnl`»f ewS_"P=L;X8ɟKl /}hCk9X' !]ڞ\2=:G5)N^_=6v.~H)?dO탔 $_@-I;z7277B^B].yũڷf>!(_@~lL{Vk^J?)W ȻZ.Cn,Wȍ併%z4 {|O=XhTjEwtG ?qHDȴʕڈP^P:  :"V@YYYYYXV  WK3Ç s_JzJ VnB_| ._T.W)ŖWt|htwz횷tpLҵt»f atJ٫ 7jڇnBp0w<W`+y78)cjc#Zg锳9 2qӾ.nYӬ&8[dZתA`nnSct!)N #d+,Vk+CG5 H_l ȫo=_)/t9f厙`ǐnN QGw*_+KΟt'Ë-/ ij1H3<8?7k;nָ鸬\!3*l0*\&@e:"M|h:p?p&:&:ٗO/#_H~xآ^۵^M UKwċpj"P}jIvm*`{cd$=r-ŦЈ<3+tq?j9~Ւ-dґt$~&Z-[-#'TnK]%{}/VrO>clExxExJMq)5-42ʙHpUYO%d?@1e툌#q=MwuVt˜:FVp7+\C'Csk䳈 s)Z/_IwVtb#K?ߝ_z\z6uFv ;Vhpgi,$MVgXNR*y߄u>—Jm/PGxW+{FeOÀyO!ʳ3Ŀ 7,tm>%?Wt1 EUAU*i:Pu~@Mߦ6Mdjػ =-͎B?f (ǾsI?i8nm3L̞Oq:c0ӟ>'>bvD)erL23Y̵|_[C[~a ahe?ZX6|00L2mY;"'0g6,+I,YS8_P83?k8<`80g//jqʢoJ0pﶞѺYڙϣLL{zտלrg%sTXZU9J)Bxk5e8^nwuyfG]go3 gw %SG^w`;&f֩46P~99fhkM|Fnn:&<]/ު:U%=XW;|pkp9q 71VuX)!٬cvc ʞ fef[F#zN#s7nZdz=I_Y9?????xX[,2t>@'wH\O@T5([uJG&f@>;bXJ4uA;lwIbwQ!nV n?AGC(ۻ,IG?tA>鈤.H='hL{ H ,mAG$ 2ٕ*~&%IP}Jk Ҵ2~ݣ~#5NrQ02 $ItJ-HM8&շjxBoNsm.f K8?X0sIs3I88qw|&O:b#Icd]-9X ׵kС7ݎst ׍߄9Gi].H}xyO dY\02If27gearn1T\M&R: 3QqDd +r %Qa42kav>bs+\_۔LdY]&+^eͽc6S_^vL:fILg'S~ϱT1;< ? 3cM"x#1IXo?t15/5kؗ)sol3pFt?pwjN:Z--VZ֎HO:U/VgOZU ShM 0md/3 j*TbKrcRfT+*Nm;ܘs_fdr@n,Wȍa8XHwxZrBS`?m˃/׸ujL5f32#{7 uAdjKdIG u<mQ6s$1FޭFEeB! @upSZ o>b ԧw7-a׬a2P}om>1|ɍ2ܨpV8ӝqǴF_KMN.7ݑ;1# 㐂aFfd2̣?M=jl]R~M:\Rr?`9^L8R3 0Nؔ.3~a mK0Ӑp$&:0'ٖ%:zRp0x X޼wKq8r?M_f\MgLoL*RU3qLE֦qc,yBeqFDѦGۢMZӎڂ|`쾾:cc0A4ptj;5cCC FʇUw+Vüm% 0,0O/3̱AKJw%Ɵ?9Z59ZE݋r#ȍ_*}<2 p3 0Ja_R`UvY7\nhU_긤.Ȏ|CI(@N]#':FV[%VǬ)FHU_W_WR[R'u\kd])Ozip:z+Gilu77NnibݫkܺS#'ǎ7]FpaQX0a.aǪ+:*x~O2 cc{rJJ'O96=(_;fu6o/i!k7Ŀ]nq1𙴉Cڇzˌ47-^ۇNۇD@>oi99>Ҭn޳Dx:ԛξhp%BahU>g'yXtb&fDŽcv2HBf\K^վ.@ͽhG}JzszT&_oәmM 7rR?QV<tf8nuX6uC Ͼb5ν;;PܼDMyNOsYL5"d:-c/~YT^f&f5IGcGr$sY-Wȴ֛I&Ypj_&|;#Ί տBHADԥۑat;Ҩ7Nr8s81yhǤ(ȌHyWq3E&\>wrTӟC?h@g锳d<ɨRlPN+wcU:eKa^jxUҝ$wʔ'e d)1 pѭZoj5izl#\ϩ+jSO  0xb j]IMmm cѪF:CmVӭZ61S^w𳋟?S64_}LV'Ӥ7!::g3#=|~x#iŽۜrn3wĦ0$8;\s~nW#"+4 ?bS0L5y0O øvA/SnrÌnX1UWCC !ysa;&{lx=:xդIœYxU\yyDD^^:@eS ٔ;D歎:V'*Z:rJѢ2odFY,ZTMո_٬f6-*8udKgFe Cc**kvg?aRgp,lՕ+du\25M4xu;9bE7 ҹέe dW#U è^MU1 Jzތ Xa, XFŠ*T|+xUya;zc۫#L23ۘ'6lm+tlхҍj jc3?1s@!,fWӥc Ĩs1ִIn,ʍ~KKX`62ѹ$)Wȏ'}+%40>ɨ=p$wֱ3&/Σ)=My"c1@7&q罜ibdrrS U}{f(ϜS~I8JZ5tMˍF%7&8̥ s&rqwbxQ]d9c`[a*OPGdt|ht<|#dj7sy>OC8*[b{ ]R|`e|57]Bѓh!Bݝr|w!t ,0c M;w0X/10&14"Rf,Wc_La4fzaf}_?( :" !И1Q{Q '0NbcT񑸾IQ2JƟ;O&U@TV:-2"Ҩ]nTP`TT($.lI{IkJ8wb&]o6U{L^uTAU*bTM*d,C[z Xml8V D Q*!~IENDB`xymon-4.3.7/docs/xymon-tips.html.DIST0000664000175000017500000003517511614526124016747 0ustar henrikhenrik Xymon Tips and Tricks

Xymon Tips and Tricks

Here you will find out how to do some common questions raised with Xymon.


What do the little red/yellow/green icons mean ?

ColorRecently changedLast change > 24 hours
Green: Status is OKGreen - recently changedGreen
Yellow: WarningYellow - recently changedYellow
Red: CriticalRed - recently changedRed
Clear: No dataClear - recently changedClear
Purple: No reportPurple - recently changedPurple
Blue: DisabledBlue - recently changedBlue

My client-side tests dont show up on the webpages

Did you install a client ? The Xymon client package is installed automatically only on the Xymon server - on other systems, you must build the client package by running Xymon's configure-script with the "--client" option and build the client package on the hosts you want to monitor.

If you did install a client, then the two most probable causes for this are:

  • The client is using another hostname than what is in the hosts.cfg file.
    Xymon only cares about the hosts that are in the hosts.cfg file, and discards status-reports from unknown hosts. If you check the "xymond" column on the webserver display for the Xymon server, you will see a report about these unknown hosts.
    Either reconfigure the client to use the same hostname as is in the hosts.cfg file, or add a CLIENT:clienthostname tag in the hosts.cfg file so Xymon knows what host matches the client hostname. The Xymon client can be started with a "--hostname=MYHOSTNAME" option to explicitly define the hostname that the client uses when reporting data to Xymon.
  • A firewall is blocking the client access to the Xymon server.
    Clients must be able to connect to the Xymon server on TCP port 1984 to send their status reports. If this port is blocked by a firewall, client status reports will not show up.
    If possible, open up the firewall to allow this access. Alternatively, you may setup a proxy using the xymonproxy tool (part of Xymon) to forward status messages from a protected network to the Xymon server.
    Other methods are also possible, e.g. bbfetch (available from the www.deadcat.net archive.

My silly clients are using a hostname different from the one in hosts.cfg

Add a CLIENT:clienthostname tag to the host entry in the hosts.cfg file, or re-configure the client to use the proper hostname.


Where are the bbrm and bbmv commands from Big Brother ?

They have been integrated into the Xymon network daemon. See the next three questions.


I accidentally added an 'ftp' check. Now I cannot get it off the webpage!

Use the command

    ~/server/bin/xymon 127.0.0.1 "drop HOSTNAME ftp"

to permanenly remove all traces of a test. Note that you need the quotes around the "drop HOSTNAME ftp".


So how do I get rid of an entire host in Xymon?

First, remove the host from the ~/server/etc/hosts.cfg file. Then use the command

    ~/server/bin/xymon 127.0.0.1 "drop HOSTNAME"

to permanenly remove all traces of a host. Note that you need the quotes around the "drop HOSTNAME".


How do I rename a host in the Xymon display?

First, change the ~/server/etc/hosts.cfg file so it has the new name. Then to move your historical data over to the new name, run

    ~/server/bin/xymon 127.0.0.1 "rename OLDHOSTNAME NEWHOSTNAME"

Getting the Apache performance graphs

Charles Jones provided this recipe on the Xymon mailing list:


From: Charles Jones
Date: Sun, 06 Feb 2005 21:28:19 -0700
Subject: Re: [hobbit] Apache tag

Okay, first you must make the indicated addition to your apache
httpd.conf (or you can make a xymon.conf in apaches conf.d directory).
[ed: See the hosts.cfg man-page for the "apache" description]

Then, you must restart apache for the change to take effect
(/etc/init.d/httpd restart).

Then, manually test the server-stats url to make sure it's working, by
using your browser and going to
http://your.server.com/server-status?auto  (you can also go to
http://your.server.com/server-status/ to get some nice extended apache
performance info).  You should get back something like this:

Total Accesses: 131577
Total kBytes: 796036
CPULoad: 1.0401
Uptime: 21595
ReqPerSec: 6.09294
BytesPerSec: 37746.7
BytesPerReq: 6195.16
BusyWorkers: 43
IdleWorkers: 13

Scoreboard: RR__RWR___RR_R_RR_RRRRRRRRR_RRRRRRR__RRR_RRRRCRRRRR_RRRR........................................................................................................................................................................................................

Now, assuming you are getting back the server-status info, time to make
sure your hosts.cfg is correctly configured to collect and graph the
data.  Heres what I have in mine:

1.2.3.4    my.server.com  # conn ssh http://1.2.3.4 apache=http://1.2.3.4/server-status?auto TRENDS:*,apache:apache|apache1|apache2|apache3

 From what you said of your setup, I'm guessing your only problem is
 using the wrong url for the apache tag (you used
 "apache=http://192.168.1.25/xymon/" which just won't work - that's the
 kind of URL you would use for the http tag).

 Hope this helped.

 -Charles

How can I add MRTG graphs to the Xymon webpages?

There is a special document for this, describing how you can configure MRTG to save data in a format that Xymon can handle natively.


I need the web-pages to update more frequently

The ~/server/etc/tasks.cfg defines the update interval for all of the Xymon programs. The default is for network tests to run every 5 minutes, and webpage updates to happen once a minute.

Note that if you run the xymonnet-again.sh tool on your network test server (this is the default for a new Xymon server), then network tests that fail will run every minute for up to 30 minutes after the initial failure, so usually there is little need to change the update interval for your network tests.


I want my temperature graphs in Fahrenheit

Edit the file server/etc/graphs.cfg, and change the [temperature] definition from the default one to the one below that shows Fahrenheit graphs.


How do I remove the HTML links from the alert messages?

Configure your alerts in server/etc/alerts.cfg to use FORMAT=PLAIN instead of TEXT.


I cannot see the man-pages on the web

A common Apache configuration mistakenly believes any filename containing ".cgi" is a CGI-script, so it refuses to present the man-pages for the CGI scripts. Stephen Beaudry found the solution:

   This occurs because by default, apache associates the cgi-script
   handler with any filename containing ".cgi".  I fixed this on my server
   by changing the following line in my httpd.conf

   AddHandler cgi-script .cgi     ->to->    AddHandler cgi-script .cgi$

My alert emails come without a subject

Xymon by default uses the system mail command to send out messages. The mail-command in Solaris and HP-UX does not understand the "-s SUBJECT" syntax that Xymon uses. So you get mails with no subject. The solution is to change the MAIL setting in etc/xymonserver.cfg to use the mailx command instead. Xymon needs to be restarted after this change.


Does Xymon support receiving SNMP traps?

Not directly, but there is other Open Source software available that can handle SNMP traps. A very elegant method of feeding traps into Xymon has been described in this article by Andy Farrior.


How can I create a custom test script?

Anything that can be automated via a script or a custom program can be added into Xymon. A lot of extension scripts are available for Big Brother at the www.deadcat.net archive, and these will typically work without modifications if you run them in Xymon. Sometimes a few minor tweaks are needed - the Xymon mailing list can help you if you dont know how to go about that.

But if you have something unique you need to test, writing an extension script is pretty simple. You need to figure out some things:

  • What name will you use for the column?
  • How will you test it?
  • What criteria should decide if the test goes red, yellow or green?
  • What extra data from the test will you include in the status message ?

A simple client-side extension script looks like this:


   #!/bin/sh

   COLUMN=mytest	# Name of the column
   COLOR=green		# By default, everything is OK
   MSG="Bad stuff status"

   # Do whatever you need to test for something
   # As an example, go red if /tmp/badstuff exists.
   if test -f /tmp/badstuff
   then
      COLOR=red
      MSG="${MSG}
 
      `cat /tmp/badstuff`
      "
   else
      MSG="${MSG}

      All is OK
      "
   fi

   # Tell Xymon about it
   $XYMON $XYMSRV "status $MACHINE.$COLUMN $COLOR `date`

   ${MSG}
   "

   exit 0

You will notice that some environment variables are pre-defined: XYMON, XYMSRV, MACHINE are all provided by Xymon when you run your script via xymonlaunch. Also note how the MSG variable is used to build the status message - it starts out with just the "Bad stuff status", then you add data to the message when we decided what the status is.

To run this, save your script in the ~xymon/client/ext/ directory (i.e. in the ext/ directory off where you installed the Xymon client), then add a new section to the ~xymon/client/etc/clientlaunch.cfg file like this:


   [myscript]
	ENVFILE $XYMONCLIENTHOME/etc/xymonclient.cfg
	CMD $XYMONCLIENTHOME/ext/myscript.sh
	LOGFILE $XYMONCLIENTHOME/logs/myscript.log
	INTERVAL 5m


Server-side scripts look almost the same, but they will typically use the xymongrep utility to pick out hosts in the hosts.cfg file that have a special tag defined, and then send one status message for each of those hosts. Like this:


   #!/bin/sh

   HOSTTAG=foo          # What we put in hosts.cfg to trigger this test
   COLUMN=$HOSTTAG	# Name of the column, often same as tag in hosts.cfg

   $XYMONHOME/bin/xymongrep $HOSTTAG | while read L
   do
      set $L	# To get one line of output from xymongrep

      HOSTIP="$1"
      MACHINEDOTS="$2"
      MACHINE=`echo $2 | $SED -e's/\./,/g'`

      COLOR=green
      MSG="$HOSTTAG status for host $MACHINEDOTS"

      #... do the test, perhaps modify COLOR and MSG

      $XYMON $XYMSRV "status $MACHINE.$COLUMN $COLOR `date`

      ${MSG}
      "
    done

    exit 0

Note that for server side tests, you need to loop over the list of hosts found in the hosts.cfg file, and send one status message for each host. Other than that, it is just like the client-side tests.


How can I make the menus work on my iPad ?

The menu system uses the CSS "hover" tag, but this is not supported on tablets and other touch-screen interfaces like the iPad. Mark Hinkle provides this solution to the problem:

In the ~xymon/server/etc/xymonmenu.cfg file, I added the '<a href="javascript:;">' anchor around the top-level menu items. Like:
  <span class="menutag"><a href="javascript:;">Views</a><span class="invis">:</span></span>


xymon-4.3.7/docs/about.html0000664000175000017500000002736311600065757015175 0ustar henrikhenrik About the Xymon

About Xymon

In this document:

What is Xymon ?

Xymon is a tool for monitoring servers, applications and networks. It collects information about the health of your computers, the applications running on them, and the network connectivity between them. All of this information is presented in a set of simple, intuitive webpages that are updated frequently to reflect changes in the status of your systems.

Xymon is capable of monitoring a vast set of network services, e.g. mail-servers, web-servers (both plain HTTP and encrypted HTTPS), local server application logs, ressource utilisation and much more.

Much of the information is processed and stored in RRD files, which then form the basis for providing trend graphs showing how e.g. webserver response-times vary over time.

Xymon was inspired by the Big Brother monitoring tool, a freely available tool from BB4 Technologies (now part of Quest Software) with some of the features that Xymon has. But Xymon is better than Big Brother in many ways:

  • Xymon can handle monitoring lots of systems.

    Big Brother is implemented mostly as shell-scripts, and performance suffers badly from this. In large networks where you need to monitor hundreds or thousands of hosts, processing of the data simply cannot keep up. Another problem with BB is that it stores all status-information in individual files; when you have lots of hosts and statuses, the amount of disk I/O triggered by this severely limits how many systems you can monitor with one BB server.
    Xymon avoids these performance bottlenecks by keeping most of the ever-changing data in memory instead of on-disk, and by being implemented in C rather than shell scripts.

  • Xymon has a centralized configuration.

    Xymon keeps all configuration data in one place: On the Xymon server. Big Brother has lots of configuration files stored on the individual servers being monitored, so to change a setting you may need to logon to several servers and change each of them individually.

  • Xymon is easy to setup and deploy.

    Big Brother has a huge number of add-ons, available from the www.deadcat.net site. This is both a blessing and a curse - you can find anything you need as an add-on, but many of the add-ons really ought to have been part of the base package. E.g. the ability to track historical performance data, simple things such as monitoring SSL-enabled services and SSL certificates, or just something as simple as a GUI for temporarily disabling monitoring of a system. Maintaining and improving all of these add-ons gets really complex.
    Xymon has all of these features built-in so you don't have to worry about getting the right add-ons and maintaining them - they come with the base package.
    Also, when it comes to deploying the client-side packages, Xymon clients require no configuration changes when you install them on multiple hosts. So you can setup a template client installation, and then blindly copy it to all of your hosts.

  • Xymon is actively being developed.

    New Xymon versions appear regularly, usually every 4-6 months. In contrast, development of Big Brother appears to have stopped - at least when it comes to the non-commercial (BTF) version.

  • Xymon is licensed as Open Source - Big Brother is not.

    Although the BB "Better-than-Free" license permits the use of BB for non-commercial use without having to buy a license, it is still a non-free package in the Open Source sense. I fully respect the decision of the people behind Big Brother to choose the licensing terms they find best - just as I can choose the licensing terms that I find best for the software I develop. It is my sincere belief that an Open Source license works best for a project such as Xymon, where community involvement is essential to get a tool capable of monitoring as many different systems as possible.

    An interesting essay appeared recently, which tries to explain why Open Source is the natural way for a software product to evolve. If you are curious as to why the trend seems to be that more and more software exist in an Open Source version, I suggest you have a look at it.

Didn't you write something called "bbgen" and "Hobbit" ?

Yes I did. The bbgen toolkit was the name I used for Xymon from 2002 until the end of 2004 (i.e. bbgen version 1.x, 2.x and 3.x). The bbgen versions relied on a Big Brother server to hold the monitoring data and status logs, and this turned out to be a real performance problem for me. So I needed to completely replace Big Brother with something more powerful. In March 2005 version 4 was ready and capable of operating without any need for a Big Brother server, so I decided to change the name to avoid any misunderstanding about whether this was an add-on to Big Brother, or a replacement for it. Xymon no longer has any relation to Big Brother.

From 2005 until November 2008 the project was called "Hobbit". However, it turned out that this is a trademarked name, and I was asked to stop using it. Therefore the project is now called Xymon.

Why did you call it Xymon ?

During the late summer and autumn of 2008 several new names for the project were discussed on the mailing list. I was looking for a name that was short, easy to pronounce, free of any legal ties, and a suitable group of domain names should be available. "Xymon" fit all of these criteria, and just sounded right to me - "XY" could be seen as meaning "anything" and "mon" is short for "monitor". So "Xymon" really just means "The Anything Monitor".

Why should I use Xymon ? My Big Brother setup works just fine.

It is your choice. I think Xymon has many improvements over BB, so I would of course say 'Yes, I think you should'. But in the end it is You who have to deal with the hassle of setting up and learning a new system, so if you are comfortable with what Big Brother is doing for you now, I am not forcing you to switch. If you want to see what some of the Xymon users think about changing to Xymon, check out this thread (continued here) from the Xymon mailing list archive. The executive summary of those messages is that You won't regret switching.

So where can I download Xymon?

The Xymon sources are available on the project page at Sourceforge.

Support

There are two mailing lists about Xymon:

  • The xymon@xymon.com mailing list is for general discussion about Xymon. To avoid spam you must be a subscriber to the list before you are allowed to post mesages. To subscribe to the list, send an e-mail to xymon-subscribe@xymon.com, or visit the list homepage.
    There is an archive of the list.
  • The xymon-announce list is an announcement-list where new versions of Xymon will be announced. You can subscribe to the list by sending an e-mail to xymon-announce-subscribe@xymon.com, or visit the list homepage.

If you have a specific problem with something that is not working, first check the list of known issues, and try to search the list archive. If you don't find the answer, post a message to the Xymon mailing list - I try to answer questions about Xymon in that forum.

Are there any other sites with Xymon stuff?

Several projects have sprung up around Xymon:

  • BBWin is a client for Microsoft Windows systems. It is available from the BBWin project page at Sourceforge. However, currently (October 2010) development seems to have stalled. A new Windows client based on Powershell is currently undergoing intense development, and the core server-side functionality is included in Xymon 4.3.0. The client itself is available from Sourceforge at the Xymon sandbox projects page.
  • DevMon is a tool to collect data from SNMP-capable devices. It is available from the DevMon project page at SourceForge.
  • hobbit-perl-cl is an add-on to Xymon for monitoring databases, BEA Weblogic servers, and NetApp boxes. It is available from the hobbit-perl-cl project page at SourceForge.
  • Xymonton is a site hosting a collection of add-ons for Xymon, including stuff like monitors for Solaris zones (the "zonestat" monitor).
  • The Xymon Wiki has some information about Xymon usage.
  • Deadcat is a repository for Big Brother extensions. Although these were written for Big Brother, most of these can be used with Xymon with little or no extra work since Xymon is compatible with the Big Brother extensions. See the Deadcat site.

Who are you ?

My name is Henrik Storner. I was born in 1964, and live in Copenhagen, the capital of Denmark which is a small country in the northern part of Europe. I have a M.Sc. in Computer Science from the University of Copenhagen, and have been working with computers and Unix systems professionally since 1984. I have been developing bits and pieces of Open Source software for the past 15 years - you'll find my name in the Linux kernel CREDITS file - and I am actively involved in the local Linux Users Group SSLUG, one of the largest LUG's world-wide, where I am a systems administrator for their Internet servers (web, e-mail, news).

I started using Big Brother around 1998, for monitoring a bunch of servers that I was administering. In late 2001 I began working for the CSC Managed Web Services division in Copenhagen, and one of my first tasks was to improve on the monitoring and SLA reporting. After looking at what the standard tools could do, I decided to setup a Big Brother system as a demonstration of what could be done. This was an immediate success. Systems were rapidly added to the Big Brother monitor, and I began to see some of the scalability problems that happen when you go from monitoring 50 servers to monitoring 500 (not to mention the 2500 hosts we are currently - 2006 - keeping tabs on). So I decided it was time to do something about it, and during the autumn and early winter 2002 bbgen was born. The rest is history.

xymon-4.3.7/docs/install.html0000664000175000017500000003160411535462534015523 0ustar henrikhenrik Installing Xymon

Installing Xymon

This describes how to setup a Xymon server for monitoring your systems. It assumes that you are setting up a full Xymon server - i.e. either you do not have a Big Brother server, or you will replace it completely with Xymon.

Note to Big Brother users: Although some of the Xymon tools have evolved from the bbgen toolkit that was used on top of a Big Brother server installation, the Xymon versions of these tools now require that you run Xymon - not Big Brother. If you are migrating from Big Brother to Xymon, then you should follow the migration guide.

Prerequisites - before you install Xymon

There are a few things you should check before you begin to install Xymon. Dont be scared of the number of items here - it is likely that you already have most or all of it in place.

A webbrowser capable of handling HTML 4, JavaScript and CSS

This includes most browsers available today - Internet Explorer 5 or later, all Mozilla/Firefox versions, Konqueror, Netscape 6 and several others. The old Netscape 4.x browsers are known NOT to work.

A Unix-like operating system

Xymon is written for Unix-based systems, e.g. Linux, FreeBSD, or Solaris. It will probably work on any Unix-like system that supports the Unix System V IPC mechanisms (shared memory, semaphores) - that should be just about anything Unix-like you are likely to have.

Sufficient SYSV IPC ressources on your system

Xymon uses 8 shared memory segments, ranging in size from 32 KB to 512 KB (2336 KB total) in the default configuration; and 8 sets of 3 semaphores. Experience shows that some systems need tuning to provide the necessary IPC ressources that Xymon uses. Specifically, when installing on Solaris you must increase the "shmseg" kernel parameter from the default 6 to at least 8. Since other programs on your system may also use shared memory, a higher value may be required. See http://www.xymon.com/archive/2005/08/msg00183.html for more information about these issues.

A webserver

Xymon is designed with a web-based front-end. So you should have a webserver such as Apache running on the server where you install Xymon.

A working C compiler, GNU make.

Xymon is written in C, so you need a working C compiler, e.g. gcc. You will also need a "make" utility - many systems have one by default, but you need to use the GNU make utility. On some systems, this is pre-installed as "gmake" or "gnumake". The configure-script checks this for you.

HP-UX users should note that the HP-supplied C compiler is known to mis-compile the lib/environ.c file, and produces an output file lib/environ.o of length 0 bytes. HP-UX users on the Xymon mailing list agree that the default C compiler shipped with HP-UX should not be used to compile Xymon - it is only for re-building the HP-UX kernel. The GNU C compiler works fine on HP-UX. More details in this e-mail from the Xymon mailing list.

PCRE, RRDtool, libpng, OpenSSL, OpenLDAP libraries.

Xymon relies on a number of Open-Source libraries - these must be installed before you start building Xymon. On many systems you already have these pre-installed - they are commonly installed by default on Linux systems, and FreeBSD has all of them in the "ports" collection.

Note: Although many systems have these libraries pre-installed, they often include only the run-time libraries and not the files that are needed to compile and build programs such as Xymon. So if you think you have all of these libraries installed but Xymon will not build, do check that you have the development files installed as well. Often these are in packages called "something-dev".

  • PCRE - Perl Compatible Regular Expression library - is a library for matching text-strings. It is available from http://www.pcre.org/
  • RRDtool is a library for handling the Round-Robin Databases used to hold the historical data Xymon gathers. It is available from http://www.mrtg.org/rrdtool/. Xymon is known to work with RRDtool 1.0.x - if you prefer to use the newer RRDtool 1.2.x, make sure you use at least version 1.2.2.
  • libpng is a library for generating images in the PNG format. It is used by RRDtool (and hence Xymon). You can find it at http://www.libpng.org/pub/png/libpng.html
  • OpenSSL is a library for communicating with network services, that use SSL encryption - e.g. secure websites. Although this library is not absolutely required for Xymon, I strongly recommend that you install it because sooner or later you will probably need it anyway. It is available from http://www.openssl.org/. Note: If you are building on Solaris, you should check that you have a random-data generator, either the prngd daemon (available on Sun Freeware) or the Solaris /dev/random driver from Solaris patch 112438.
  • OpenLDAP is used to query LDAP directory servers. If you would like to test that your directory server is up and running, you will need this library. It is available from http://www.openldap.org/

The configure-script will attempt to locate all of these libraries on your system, and complain if the required ones are missing.

A "xymon" userid on your system

A core element of Xymon is a network daemon. To keep your system secure and limit the amount of damage that can be done if someone finds a security problem in Xymon, I strongly recommend that you create a dedicated userid for the Xymon programs. This user should not be a member of any other groups on your system.

Xymon will install the xymonping tool as setuid-root(only on the Xymon server). This program requires root privileges to be able to perform network "ping" tests. It will drop root privileges immediately after obtaining the network socket needed for this, and will not run with root privileges at all while handling network traffic or doing file I/O.

Building Xymon

After unpacking Xymon from the tar-file, run the configure script. This script asks a series of questions, but all of the questions have a reasonable default response. So if you are in doubt about what to answer, use the default setting. You can see what it looks like.

When the configure script finishes, it tells you to run make to build the Xymon programs. If your default "make" tool is not GNU make, you should use the command for running GNU make instead, e.g. gmake. You will now see a lot of commands being run to build the programs, it usually takes a minute or two.

When it is finished, you finish the installation by running make install.

The first time you run make install, besides installing the Xymon programs it also creates the default directory structure used by Xymon, and installs an initial set of configuration files that you can use as the basis for setting up monitoring of your entire network.

It is safe to run make install when upgrading a Xymon server. It installs the programs, adds new template-files that were not present in your previous version, and updates your configuration files with any new sections that have been added. Any changes you have made yourself are preserved.

Configuring your webserver

Xymon uses a web-based front-end. So you need to configure your webserver so that it knows where the Xymon webpages can be found, and what CGI scripts can run as part of Xymon. This usually means adding a few lines to your webserver configuration that sets up a URL which points at the ~/server/www/ directory, and which tells your webserver that the ~/cgi-bin/ directory holds CGI scripts that the webserver should run when they are requested.

If you are using the Apache webserver, you will find the necessary additions to the Apache configuration in ~/server/etc/xymon-apache.conf - it looks like this. After changing the webserver configuration, you probably need to restart the webserver.

If you configured Xymon to put the Administration CGI scripts into a separate directory (recommended for better security), you will also need to setup the password-file that controls access to this directory. Use the htpasswd command both to create the password file and to add or delete users:


	# /usr/sbin/htpasswd -c /usr/local/xymon/server/etc/xymonpasswd admin
	New password:
	Re-type new password:
	Adding password for user admin
	#

The -c option should only be used the first time, to create the password file. See the Apache documentation for details about how to use htpasswd.

Starting Xymon

You can now login as the "xymon" user, and run the command ./server/xymon.sh start to start Xymon. After a few seconds, it should have started and you now have the following processes running:
Xymon processes

Quite a few, but all of them controlled by the master xymonlaunch process. A quick run-down of what each of them does:

  • xymond is the network daemon that receives status updates from the clients and the network test tool. It also provides the current status of all your systems to the tool that generates the webpages.
  • xymond_channel provides the communication between xymond and all of the helper modules that implement other server-based functions.
  • xymond_history takes care of recording the history of status changes for each item you monitor. This is used to track what has happened with a single status over time - when it was red, when it was green, what the error reported at 2:51 AM last Friday looked like. The history file format is compatible with the format used by the Big Brother package.
  • xymond_filestore stores files with information about the current status of the systems monitored by Xymon. There may be several of these running, but normally you will only need the one that stores information about hosts that have been disabled, which is the one you see here.
  • xymond_alert takes care of sending out alerts when your servers begin to report a critical status.
  • xymond_rrd updates the RRD database files with the numeric data collected from the status reports, to track e.g. how the disk utilization of a server changes over time. There are two of these processes, because the data can arrive in two different ways.

After a couple of minutes, you should have data available for the Xymon server itself. If you open a webbrowser with the Xymon URL - usually http://your.server/xymon/ - you should see something like this:
Xymon main window

Each of the little faces indicate an item that is being monitored for this host. Here you see the default set of items that the Xymon installation sets up for a Xymon server:

  • bbd is the availability of the Xymon network daemon.
  • conn is a simple "ping" test of the host.
  • http is the status of the HTTP-server running on the Xymon server.
  • info contains information about how the host is configured in Xymon, such as what IP-address it has, what network tests are being run against this host etc.
  • trends is a collection of the various RRD graphs available for this host.
  • xymond is the status of the Xymon daemon, with statistics about how many monitored items are being tracked.
  • xymongen is the status of the xymongen tool, which updates the webpages.
  • xymonnet is the status of the xymonnet network tester that performs all of the network tests you configure in Xymon.

You can click on each of the green icons to see a more detailed status.

Next steps

Congratulations, you now have a running Xymon server!

The next step is to configure it to monitor your servers and applications, and to set up the alerts to send you e-mail, call a pager, or send an SMS in case of trouble. For that, see the Xymon configuration guide.

xymon-4.3.7/docs/editor-main.jpg0000664000175000017500000022262611070452713016077 0ustar henrikhenrikJFIFHHC  !"$"$C" i  !1QV"ATU2357RSqu#aBDW$46r%Cbst 8dvEce: !1Q24ARSqa"B3Cbr ?tSI:"'W1cv USAWUNWsړ6.X$u-'Ϻ>ilqDglSX/!ӪYGq !4QJl1RbQʊ2Dֵ-&N)dRH6"*S5VlRZLj©2NdMJtԂ5[@6ogҒqv(Nf&xVEf"eE40nk3"ٙgdlz7H~.*F'r/ kT~,"+[bRMi0۶gnٞ8c vS)I$LƵ )R"۰ U|J&].{DIlB dwl 1j%YrSjj\[FD(д܏*Je22=5iO^T.m.|[or,Mbt¨K7T7)4ℙy4έ2KVЄ>mConٞ8afx[>S RmJmˆU֒J%M{wavs&0UdkYzJ1Xg&fZn9wؓTJ=#hJ%U笿KLeVG #J;(ia YGۑT S@Zji8dq "QSMѕ}Œznٞ8c\ʪVUޚR  Q'2ob?t2Teb~ZiN\79$kSeܪGc/TV@r*_[d}166THDFFWm3 7l 1`%v 3 sv .sXN”DA*MgQY"%Pd X%k>ۯ~,to.jɪZٲu?/._h6og\Ex(Ϲ4 ]I75j_5 ؒآ6<>#c*b5ݳ&I'E%|U\F۔fQIadnOJb%H\УNT+FjRv 3 ixUdNT.DFa^v1 Vʒ5k A6ui+^u:M6HdB[Jl23,ٕ`nٞ8afx[TR;JVz_R{fDλdџF{v 3 k1tje9Ue5%c-M(ߝN-w7~٭^G-W7MN-rTӆ2qVT$Δ(Eܑ6*UX"CєMe4lvZW;\V1uv;OӒ\6JnRF}Bb$πiTC Q;iζk~gQJ6) KFetg̉ӣ):FQS glK 46%.2#;a` fx[o5F u20}Nf )K4$ѩWR x _U,Nm̊l4FJ4 J6;yȓ6ogӚ6xҵUTgǩ"W7SRNDhMiL}[!KJHR\#;l%Zr?QA".L)HTvZi[dM瘳f3+rI-';gnٞ8bdbF4[~IfS5ۺL r0kW[Xo6ev$m0Y%K4ʅ*Ifx[CjS3ZEGirZSeRN-+4ҮII܌cbo$ꘒL*쌚Imi5fj$AHDw^T7A4Pq2X4㦛pbmdGݳ<-pƣOͩIDT9+kz%⌵4j+IJagFxJ*GuX1ȻIMjJ,A6Q8V$ v 3 X _ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pǞUe&ܜDfjUϽ?F(kY-t̒:g{wMr2_`ސDyFfd55s+^A^ɬcQn~/rW}ܹ$x/ j:kuImP咒FFJ=Bݣ73CmRdNkKjd%#RIfnvFa%.(&#h<쎼1?tv]F"mI,v\D|NJ%/DDDa!MKL0勻##;{`N;h<%jWsRg Ob;h<v>y?:04F\PIu(Hy34Hԫ!(AG)#3&܍Kn-ً&SDF$A$D[=0g~'F{`NfIL,9Q} )-]Z[Q)j#y?:0}wt`0-;RoJ5dNy씱H'\BniJ.dW;˘i0yr2k[#"R(τi=0g~'F{`NY!%JDIJ^AmJ.}>}wtaG DX [Ժ4[]oSohp0C}^3ϧ-16òb]5$N8${ʄI֛-\.74[]k YUwqT-RoR ׸]*JL4gr}ѺuWo&[[>Û5u6Ƌ| mFaaCK)2XlRSijRff>T22T!Y Чv,fW ^M15~cEkрU$`AP)4^~e[x^ixa Om&&5md)InF\5u6Ƌ| mF{& Yv6b;5K_2˺?16 0˦ƌiiZqChIY)JKa֛-\.74[] ԯ2<운="N9!SqM2$"SpȍKUVW~cEkцu6Ƌ| "Gtxqf.{j샌^vk 1,#v4LbLNM&Ha)=X븃Sohp0ߘot`'0u:4Xh1*A[Jԛ(˾c(?QiaB:j{TZi^M6Y{Ew4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]oSohp0C}^3ϧJ.}>4[]a8ŕES0ZbSj- kURFdW;̋}^3ϧJ.}>g x>VhRV̨pjui"Kfgu%bgb#2`gڕ8\}aԯ=0g~'F{`N_}^3ϧJ.}>}wtaG eڕ8\}aԯ=0g~'F{`N_}^3ϧJ.}>}wtaG eڕ8\}aԯWXa Km֣~!؈bM15m x>Xo+pƯߘotaM15m x>Xo+pƯߘotaM15m x>Xo+pƯߘotaM15m x>Xo+pƯߘotaM15m x>Xo+pƯߘotaM15m x>Xo+pƯߘotaM15m x>Xo+pƯߘotaM15m x>Xo+pƯߘotaM15m x>XʕBqʄ-bH+) %&]!4[]oSohp0У!eN4fiZIZ+wί0iy$jmd#̓xП v*GѽF{GF㋲MJ2I7{HϼDf{hN% U¬PیI[N3Xy"ͫIfz&&:5$"zB\)RnvFryԤI/vyƮ,r)%z:%9>+qNDw#kKP`)DlɔwI!EpZžK/wYq=^VDro|zE=;*= ҥMFRE{%G1+g[NyQ%!ggbmdO_'Sc.]SmڍTȌfI=#.21\rާCzsUvz~ˣPS)JKd#Bf("IE7\K'I"L9jlm&f'(j*S0T(b%A8JOVȎ$ۀ{U4LqUT۪#385'M(eźSan3dgRK)FD|J%IG欖#+8ʸleFd5PdƍQ! S&h#I'"LbM TPNQ0l4XM߻W~vpNUzf.[c{$1r.ڄo!I)Q2|$8ddG#223#h ;0jDZ-=ܙ)+yל2+gR#ev"ẍJ=)]F%YS ~ꝋ*UZ}42O9g,.IT1eK4IR#BYu#%I"̄i Q}2*BWOeO7Qޱjm(c_emH?UTtx*\1,%J[jP"iX`$f[Ufw=Y2.9Xh Nz%e+!(q̥GƳ32ڣ#|ױ &zQ=?v.pg~3T$4"djե&;(={8.gJS"\gjN(#4 im/C*inUfvG2=\C0YyjbSN!Gt(b4_i۟paKȌ%Z{tqHS8Ze7WMa}'=,$$1#3hGG7[ Зm-#ꊪS8sPANosKM- UFl\Egba ќ9TbH*#."r*n##.)⫈(۝3ZQ_w e.Ó#fӪfIA`dNWO.!iܚ9|!{WS,{9g=)`\Z|.<8!R\v)$-fDKhqYU!^Dƽ^:0yThԚeEo3QMdDDQI"3Q =E7gggꮛuMpݷe ;;p%(RW Y%WR^3>Ф'z4J9sjȋ*nMZGh1M\su?ZE˵Es3CxCqT;.Ý5ne#&[l˹{@z*Ç̌Ȯ᧔[UctGr˶+t]u8iE\ML7E@~XSgrK+fі9 9UjGvlVC56̚Z[M4"$mQmU W(p+tD fEw"iĒ(2;hl;LN(L֑5kTGYvL r#RK'"5m!pe6|!QD/WNwD~JLgz+DM[4).>i2,6@N*L}?Wۑ:̭Z9'IQ>7W诚Z7?~2\)/q~mwV߮ӿVwUju9nOB<= ZJAə\УBQpU([ hӸbt6.DJ QʌBe?6KJ͌ҕ ƃ7zg|2?Jxm>!&Tҗ5-;_WwޯS$AϬͮ6[`~:iS]kr%&A2d<$&VDDY*tEB,,3j SdFrYJФXM(fl20SfLY$FT$]f{IFFDQRa 3+3TiՌu.dR(lSD`ܶizܨޱ5NHN yLHk[nӕLdM0#%1y8V;ڸ|9Oܺ.}f]NX$g7[lww)]u%Ϭ˩DWmcҵZ K [\ZB)Ω}5F"ΆNAQ$J *P[\ZB)fjE qZI].%{I=yB٠dR27I'%֝s(P+8[B3rI4IHUDetƠaK]20̶[) (N]S*'Y~,MC-,n(4|# َdXmKґ }y lR?%-*es= K1h2+$aܬ[JݍQB 6%-LjK+Ye|+Xo.j6bs8RSt] oH&΢ͩ Y U iq+b,UVk֕4p d&ɓmNk?0.8j$%d4؜*"Ci5dM˸0ܳr4ZiKDvMErBMjJIGY2-?1 6HfP&iF٧LjnUHJ'cVS"3-)4'֠`ZȑJFvw$Q$%g3MlZKN-f0U`j~Kiڒ%>)Ek73"MnU޷i T7.ԠAɬm-c:-cpg'PSW ѲܛL&֮3hJ63̋QG \*B)uZ!sf: m&֨VI]wC ˺(rk2[tf>{η5mr'5e+j#ҔnCFQI5u${odRRX̌%en.~@S)uZ!sf: m&֨VI]E).5YXC ԩ8ir,Svjm[莤MVTꖮ rѪ+ܗN^r_dRl-SerkM+h:$Ìh8T-_Mfb*Fp=S9ƃh%1II!܈ȽޓJ5Yև0*:$9 IyM%JQ%iٗtK.뺹wtcܹw6Ѭt;)2f]{29% L*%NKObZnhPZ7q̮Rҧ#RdGÑw3^BXS j/[2`!, ok':ܳ)Eܤ7~;؛rȝ5yoױ/HʼnQ{oDvl"ReA2ۥLvbգAf|m"Vnu<ӅB'RJM:2dyJIMĬw= S9MZ%!]sԨ̰jCIB{,Ifj6lC]ފ]vԗ>.,I3Ǜ_-{eD[U6:mʉCĩ IA˨m"k&*' R`֣X/:QRH\G :lЫ%gߤĕfG$ti;%dg -Gd#>QqnNjQ) F**I6׹E!n&YW3+w*6MaP`OŇ)h,덴23.lk̛SUXXF* 4:;P/N;& :['\32%У@'1ʔIc9KІ#n)fYlfý]_5ӥLQ&@~"hlB ]Hn潃2k:nõ-B[BiEc+%:ai4%nj$MRĸβQN&᚝JtB6Pfi'&JQ$Ԩ[ ;)M h۽ZSNjZ%kD HYgQ{mNмHԶ#BяpÇ̒͢ƂKR##NQ%#L8rm6EDւW$ ȶ-7 X 0]/"-d˄hilӜFvs+X=xP(:U6PLiuw6֭b4K2Лři+ȏI0!6"Q*VhFY)'O&Zy-[6*N&4XވKn/ǘNT)SʣFyҖNS*'$cIVFQQlah)ƅUPIWNTJ)sZK%)d6;[hT4ʮz(.CjxOܖД76Fc#v)bg1STbHtZ+{N3"qd3BuEr#]iv;lEkUI*62MCEnL2ZF}fLʓEeb0xcӱզw>\Flg'||t̯a*}o)*,HY;3I v)/ܥ'w`)i$څso>w;#V*ynHi-9Ȼ,趑Ln&%u鯝@m O8.8Dmǒm%j#|&'b]Tz,?) y؄ä76riVTܲ54O_Sϡ7\4o\mm5-(ΦFM$$r\xt&F#EurkuDJ2UȌc\4%B5><'UlZ#$uW Te61%UW 3Ide iAkf"iJ^>hKij̳#'()8w]ӊnbLI~EILxo:sɜIҍ;HܬGr#,!^t%KN{p]v,uj} 46%DW &fot[U_򈑭kxP ,%$b]E\">ʥ_IuEs '*jqROÐ5nYfW!wLz%:NYpI.98mf#[G~2I.$Ri,ÃQaĄֿ}3gs1EE%~{uR46CԖ겶j?܈i\(DXݔFj ;Y4lֈZI%ck4w(߮͝h E]beZEYkRdW2#F2M'̐x6n:$"wƳeyOHf \f)meN<;Fj"Dvi&2dFO"4̆S7=Ph۳۰suou1<af~7RmLԽo&GI-~FhZٛ"ʫ*kJ7.x,"uhF8R0JSBlۆ >WFppX&p]m$H놙eK}$cW"VFUԅic%tJm+UbXevǕytXTsaT~Ia,) !֖VR$8}(-n^<(֫3%s;h#Җo6F>Y馫M1Vq㯫qQnU; JHIY$V"{)iws91KKܻ͵ь-W8?w/k˹\]"u?x]XϺۯ0ێ5}Zocv/ Ã{)iry0=)ismtc.z8];)iomtc󲖙v6F+>N6NRK) ayY\#"Wc1<)M|:gO8v6F?;)iosmbیSV>Uzj5b~Nm m$" 쥦]ͷяZjۼ}:+ݝ8].$Yh$J#GcSY4FQk쥦]ͷяZkۜ} {8^ӽpGe-5m;)insma^N&״g(Tw$ӣ;o{Ya}#vR_.òv6F-E4l48Вf" kGoLziT^q4-m2(vfȇvR_.òv6Fqe M)s0ZK$HQ:mf̜唭$}~KM|soZkۜ}qo\l}Mq}˷90쥦]ͷч 8]C/R)2ïS$j:ZM5Xc&rDeߦ>Zkۜ}vR_.ÆEE%6p5Mw;J˙rȋX\l}e-5m;)insma_N"wk`76>󏲖v6F˷90'k5_}~KM|soZkۜ}ps~c k`|쥦]ͷчe-5m8kZ9Mq5_>qR_.òv6F5d-w}ߦ~c8)insmaKM|soqO 0#Al) +RnD]6>󏲖v6F˷90'k5_}~KM|soZkۜ}ps~c k`|쥦]ͷчe-5m8kZ9Mq5_>qR_.òv6F5d-w}ߦ~c8)insmaKM|soq 0TBDK&RIMIZY?f!ϧmmÍ2ZZ% jZ̉Rτ|쥦]ͷчe-5m8kZ9Mq5_>qR_.òv6F5d-w}ߦ~c8)insmaKM|soqo\l}Mq}˷90쥦]ͷч 8]G76>ߦ>Zkۜ}vR_.ÆE}~o\l}OL:eKcԕ+)h3;Η9k7g':S]5Fa~c k`|ZYΗ9k7g'}k`76>#Η9k7g:\e`9M6>ߦ̎:\e`;#Η9k7g:\e`9M6>ߦ̎:\e`;#Η9k7g:\e`9M6>ƤKVJ#Dq)q,["35p،GsΗ9k7g:\e`9~2s~`aTeSGjٮS;%ٔۋ3$>j>*#>vy-f,K̳&_DfDuǒOYm:Z_':\e`;vy-f,"F!LbD|ZYΗ9k7gS ;{|vy-f,K̳芩QI𚏞zdžnʵJJu&AJ>d'#,RWmtY>vy-f,"D"s0oMq5_>dvy-f,K̳ s_5_}~K̳g.ro2ϰO}~o\l}Gg.ro2ϰtY>s9>\l}MqtY>vy-f,oMq5_>dvy-f,K̳35_}~K̳g.ro2ϰO}~o\l}Gg.ro2ϰtY>s9>\l}MqtY>NxIZM!ᚮk/I'1FݎR,de~.s9;uI Mlģ,33BE%8ØKM'lo8go숋i/p3®=$o9R%#)2tT{ 8'{¾ҷuPnE%TOvҗR_fU+m}3puD]6KŘJsV 9J2#RO*ȌȮ?Y6?|e5bnsD(i)m&^kJ-Ɯ# Ve3.n)PmiB(kMFfDw2.5>\ RUV4ѴۍMhʻFFT&d{8i)jP$΢Ti,:a(KOܚ'bԣI؛msz8uF\XtI9g)_Ri,܋)(SQuoX-4R{+m755RJlITQM$)jЩ qlHUH2&rd.Q)QlfU%; 13k8EQN9#`:9&7ӫc+ϫTSy:vaf]֤IW$*ē3fT"T8Z5)T撺)D $sɡ`xz**2vZhbdwٌPv?vZLeܚe}3H]6*{oSl곘b%9Qg rg\NH%(c=Gjn캵N84(U7r|]Q$O%*N ę]J+XjDFy:5qV!3#T.mEθ0SK\VgDgRjYSNGjUQmPܻm)=ȒFv3#3s`x U&} ᙤp阹SSeQHJdEGSFeam%=ѷu+\jnEs7W eP4SպqK}2m_G3;r*%(d:>fF$hڐÑm9דrFpDR $JtEٍPLiD%-dΖ33{^{MڬQ1&.QV^ d]3Nؐd-dKIiUDNeu͹vBU8^Z~m%ҥ:S֥]xօk{ ʔsBx+7l'T%Hp4'>b;&QYDF#HCFuwo?Vݤ;M-0rJ[#AIJdJogsj7SZWj3kr˖oa;<dbkWhi3Mmzz:Yrb3UE ]4,jo&MB7.]Ǹd_ʰY4b@a[maclkm 5%&"Iܬgr4btC:LKX[Ҥ3y -guiE7teP҉c"'T⦅:zݍ2d[e' VLV9 k6sG k7ifᩑ%cۥ5"ť DvXCKDKSn6mOi+l%Zi j@Qs9#}9›2%$n;vu<_*7cRs3eO7G)bCħFҮIM^2!11V ̩|WeT#S"V!^*II TFGLUSԋLO W0yz*.^Dbkhyo/CgCB[LY s=F(݌"$ƔvQ\Gr3!:mPҔ\kI;<21J*QAgB\YV#5X[)Z_/yMҜ10GZE R)=,u)َUVztJ353<︵FEfufw+0_sF?,[?3O|}o ? 1(P"E%:bC/X:R{E jw|OigN]_o tv m"4/XH\M=[i7\0jG/XH%uLtO m(H\~jOOELR|#]xKi_\>jOO~udL#V'udîx3'" #pv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpv|̟XudDpvAXPYm.DEkwܹUʷTn[@]B_4DK萉!c/yMҜ3/yMҜ) p\xW'Ñ1HP"o-䜊_3Q'mˀ w+ c NX<ʣ*5 E4C[|AC%-32Co&bN<ұNSQ!m >jR &A8 DΈB+XEufLW#H;\ddHpߣ#ieJSMcrzFeO%D5"v.-J.7u{զR8=I40u2'b}|&)ٻvj:3M31rYLGJvCD;04@İieQCCMK\LU)̝BVʫ]݊'XVњ'1*=jIMa((J0BK1vS2V,i%e4{"،:0}2%D?PP,g\gMGdf;kv+]ʵVbr9jrH(蒟Y؜^3RFm٢igJ#0tCI[J$n2"^d]FD;-;dΦmSL:?6sN9,T8{H/GYJH)-dcj;pƭ1:cq.*Po3 ELZF=gIEF)[)1IR|#q*LR*1H`mn|@4l7u&eUDMr۽r<0Z!Ř`ajs- $F%)MXW2\G4.S^ n)Ğ^iW+)DerثCgh2P0|yQ"KRg8[xH()3 OzVԱ,WȐUOVw62J%[25͇}|0ݧ(XnB֥FSq*uI-&Y)' gqsk{` Ҩ.K6* \FTtIetChIkN]arR":ZI&Q/`/ Rch(u:r Lԝf܆m9vJpӔw{8jiŤ7hZM׋1Ixܭrda ц),ULƑ)fb">v2,ΥZ)v+mx~:. *s蔤.,#hC[f !r.!gCBoFaTBD]CrB7Ig?RdiIܞ^1R1-;4C|Q֔ )4;t[vK肗Ci*KĎt2u%S4 ϼJQ%?:IT|-IUW4(J#-֨ia׊""˼c|, dKk HA\Rn'.ul %:9øڝV:*Qg/{3̬Rܖ͢\ށe(QKNؠt)YgQF_7Rt5s bz k-:6x*N%1*J/5ZbJ$ +%*J#!YDI;ߌp,s#+4ʅQefZs!.):l`i3E4ޜ:5qrtY:nfmǣIzI2=rVv&OM8qכWȮglOO4=?Rթ{!8PRe˛Dgnˆ/GkDv%9nL)vQs"+LO4x6sHROaTՓsDJRDW>v U0 ]"1\փ3M%iY8DG߱l<&n/jzRɕ^,dCFsm"2*nTSbmBkSayX"%(ȝ;Dl$2HМ3Tj#ǔ>H~*ŲN>jJ3I F2X[a8]7U[W.ޏwOtYx_Zwz%a*l:7*"#)NRufF2Jddv;p犌SP aa4n-3,QlRN#.s{3>*B9h댵]FvSvI;ܪױK$A^JE8^둘?FiGPiʁW>a5$4J;O8~miI|$EN4sպf!b 8nAtؗ!|v4Rd{vEb1hzKa=.,JrmݹRMJ##U#z ?fRRb~ר5fKKi"A$sL_f*0q-o%DCMy6< wRT)dTؤ8hd%re{)1z*TغN!^Z<7#)H;\(ntHc WGuLB-G"aAuҝmhHFzVoary]SXG$ky3 'bol5]` άT1:]b' 9ӤVliNVc;+etzV eQθ$fyfi!)WÕvTkGS+ZFh#$s"v 7Rm7 ɣ㊦&S*lS#ǐG2+ojIGkJ.!#жT$b|R.uĕHFF51vøHCr̬Ҋ==K9])2NFESÅz823p: Ȓfk\F{YU0)$cq($H#IX’"1iZޒU "Y iqiN{٨N # /຤ "VE:k`UH<'3dDIUҮn4N 7\ch$jpp^{Ee)H*Ӱʜ9ۈdBsfv͕*3IslxoL-.]ӽϺ'/;@t+0hZC-TH&Ľ$(mlgn# ~QNP\G63Wh5ĺRb.[|f-Vt%ehmnATݸ-y6Ord{88 Mb<]QGޥj\GM+kkړ2KCt #DI5j >" ơƏ9c)#SD{ \[{vpgSAw؎$RD2aւ{,plb*bc 59 jkq%ISfSn;qw`m"i\2TbLD,j%*JR2>2OT=EMX 4%[$[j~SNq]f9JT"INfV?+4ec_@J v1ú8U1ir)ꋱud[U{M:F&R9QFᰬI([L;aJ֑c=n,rB6s^4ܒ6+-i Pjz2؆a@9CcTR)e3p7#zDǸV=2R񛏾Qj"BMҲ;[V\F2cҍ&R8P1FrRJVd$Gs> 0kF9KMVu"hzA+UYgZGl. Nxհt4fe%*JRR5ZSddb-A(q+Y>".j2ddlʹܳKob=3 5UȓRdl$r#a`6*nB[MSܛ4uR̄Ue2%m;l#;:5f6/P0d-tjJݔ:zKAhJnIQE{[O6 J5|DfN&j\hNUę%)V~Ѭ 13bʤ]VBF&>՛k2mV"VQ{xfha6z镹xKZ2NjRo#ؕg~jXMUk jj iΔEg}#>N?}8oҞUUy2I*ZɾO3c LꋩIU6qPYRdd(y53CtJ%vMIH5$[r.fdjQ}8ְ2o`|Y>Kl/DJLFJIcN+n{Z꤅!3j51z8 zڥ͵F9)=H+1]>-yT.B`pEPOO%ĒEɲ^^ # 1 /Q VJ){DFKy%D2՞m1xa餫Ų1m>hTJC/1 ;Ƅ(̻2"XUigL9\;SEv:'' ]\=c4m"anScJ6;y_s3 yDW&[o~4{ 0Ð~SkT&*ĝT!+mǕYL[N[8HZSAƕL;Q.bnj'")4m(ԳUܩ^mE8JxyUOT#5 q3RDr$)Od^cI8R32+p^=i -*[ĵJҚ&>cҍ;55[7OPǡM3!CF_x78gF_x78RFIau#puvnvKGk?R_|X6=uhdrtF$IdfmXHzoP=([PmC P j(VB-A Vb7V&)g\L)1IR|#q*LR*1H`?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFu“'<7" }VPo/CgCB%SLHD1ї^oP=([PmC P j(VB-A Vb7V&)g\L)1IR|#q*LR*1H`?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFužiWHV9b&phWկ"|n}sFXկ|__k|_ Zhֻ+~կk|_ Zhֻ+~կk|_ Zhֻ+~կk|_ Zhֻ+~կk|_ Zhֻ+~կk|_ Zhֻ+~կk|_ Zhֻ+~կn=F-bV 2%٥8U2$xvwtvmQ531TMCV/7&IxڿR#WcIeIm˸]㣁E#~$ID2qG::KJڬy)R#Rlypxkf>L:38EjCV/$l#RNE1(y(̈ҦԢ={q9).ULd[(ʉfI52;Dg/uk_wh;Դbj|"ɒ$%l̃%e]֤#32s+v./:V Q%ufykZZyjIE͖+X[w:k_eҤ6'>WxK3,763mptяկ|_Mq^3\n\ {IK4S>ZRf"֓O֌f7kUY܇ w7K]jCV//ֻcV/~|íwϑV5k_<:ycV/~|íwϑV5k_<:ycV/~|íwϑV5k_<:ycV/~|íwϑV5k_<:ycV/~|íwϑV5k_<:ycV/~|íwϑV5k_<:ycV/~|íwϑV5k_<:ycV/~|íwϑ^sBeR/f, Uljg 2]B_4DK萉!c/yMҜ3/yMҜ) p\xW#np$xW#npԷ>2ι筘B(|ޡjz(PqBچ(PB [ƒ2n LR*1Hθ~ ~/"iTbx~kSb܍޵\qF2ov.)8ydIi22F9KZ# jxR-Z߾nzzv38؝OuzM8FY9qk^-*RSeOs!`h=am=MJlY\Ֆ2k!zkScvW#1í0I V+DLVS٭?F~i|b?g1RjGv\tl̔UfweN!St]'a\"ړ 5WżJHdd%w]D[n5Nl[=,mT9Hqm R))FI"#+l 1R_5,FOL0cv2d6NR󧃺+`-jx51ijicf{:Kr+EJ^#bOF}uqYI]"$&GWptNhXHuL@i-j<,հ&֧Z#6,399Ng3ܙ!WУ[5j3T %ʬ ;Q2mh%_T owc%'{kSaOjS3"9|}3pjx51Y+-SaOg6 zkSaY޵5WLUEzk ;z&'#'QT\/G~"c?~"c*̖+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETF 8UGkb"' ?c!=i 7PǡM3!CF_x78gF_x78RFIFoz<|es[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}DAñעbr25>zJTèÔU8!'34ZdI_: ժ1)Q(qCmNfi$ yt-=ɫF,eB(."4I3RNՕ3J=e\7E87hROFfmu&s̃335ۉ`=x:1@w`=x:0W2F$phW2Fy XphW2Fy XphW2Fy XphW2Fy XphW2Fy XphW2Fy XphW2Fy XphW2Fy XphW2Fy XphW2Fy XphW2Fy XphW2Fy XphW2F9OjvfJ&96\㶥eg( ;ằ~o{)]ռz'd}ҹw<;J9=ju&J[6fi5!F23";\DU<`C?XASh~l㶧 $FfDgk0!ѼxiWۈ%$ڌGGw`=x:0W2F@w`=x:0W2F@w`=x:0W2F@w`=x:0W2F@w`=x:0W2F@w`=x:1̽T^tCΖWdk7,ǔdp N`m8 i,-u9 ϲ+8pp;r yd:ig a;Ǭl-};r yd7AjUGXagD)[l.cIp- +W+9 ϲÃlnaG"ا,B\pd+ou}Ms" %/DÂ@wY#[,!v6K>ȍa;Ǭl-};r yd7AG!YG9uSC蔪%&ip0aaZUi"{Np'&lY鐁 ,O@o/CgCB%SLHD1ї^oP=([PmC P j(VB-A Vb7V&)g\L28_k19l/o蘜O<ҩT F]MgKqD$I-+-SM_OoITJgd8$Fuo,┥ftjUT#E>2[ϩie6"ʂ3JIX~ͫUg1<ڜ,NHb+ 쒲S\BD5ʖkq(JjAՠinwv$8UE^%"jU{SL-TiJ>YRjYӉe&B_1ft&Ϋϕza+[hgb%8xjXv莛ڐᶤ7*IdҖҒ۰} {tWW٫Xu۾VJ[6lU٥Jj9{uLJ>3EyRKn/?hkX" )KELFvu-|SCyhmAz&H' ANNql?bE7d 1" ?vG ,n9 ?`/#L7d 0ݑs& ?vG ,n9 ?`/#L7d 0ݑs& ?vG ,n9 ?`/#L7d 0ݑs&>#L7d ,n9 ?`/#L7d 0ݑs& ?vG :>6`N#LqVDw#6eϾjLR$BI|ΉXxI+Y%MCU~;N6J(Rr rkJ-m}R:O152>;)Wk!ob'(I4-Ar!XSX茳cZjԆH(D1U"CյOz%ƸC}l4HV&E:"cK48EɃMm^LzƷGż;Hޛ;[y2B#F ;U)K}nIS (dv U$UX \F(R!(15@K7zjTzYl)/V촰elTe#Ͼ\c$>.+?[0c(1:*!;;;;NKjdd䏺22.k葛L^b{1uY3CȝpТ6nW4*â"؈!:KjpR(Qw)M˽dhX5a%NeX=tSFgLФr#Z 2S=Nv̆K\J)KlnK'gr#r11X ?c!Y鐁]B_4DK萉!c/yMҜ3/yMҜ) p\xW#np$xW#npԷ>2ι筘B(|ޡjz(PqBچ(PB [ƒ2n LR*1Hθdpñעbr _k19=hyQq t,K ]IQ!jJ#m_{ncVFS I6/ZЂMa|FF ؝2(Ra<(^i8t8r%KQYW"#D{E/UjyL&eGmirMKiM֔%72 3m' 5U2Ja<-JYK6KZ̔i5+4JEMRmN6JIBҤT|*2?c ؠPeQaÐR ,%:QgJ+RLhbO =J3B*%:,N8[L ,ȕhR:VQP0b$GnC3aīGƦ2JB3*Dztg=iS#MFɦTHr:wii5lr"Qgcv'L3~ 4|9=7D;VUȈLQ!M7 A(^$"3Q&DdȬg@)yq="TܙY:UhFnȔ3.ẫ:*1czy͓:v*{kr2ι筘B(|ޡjz(PqBچ(PB [ƒ2n LR*1Hθdpñעcf0!TMPa+4)رu$"3IH2;~ ;z&76n& (T=THYm /rJ\Y_=O6hZ~=? ~t!EoY"Ci\2v"-cz$jCpKM$A.eGt3tn&*EVEYTH 3mIYȳ]IQ)9H.BHiSiecUGLr+,im jVIQREҩTע,ݦԨR9\e,y㝳*3V.W<7imLEܡ$*aD204U)$RH Ȕ2f.2$?Z}P BtڋurHZY%N4wiQ,bB;oadNz ܐJ#<é+D\M}FD*s-*]23=orf ӗ)eS!0Ҋcm,&܈f}U!:ڮјī- ?*]DʣIp7z#ke ڨS?ZԷI)I<{gFєvmoz+hLƘC@Ujme-dG{ $P?#;Y,.Etn5ʻUʕJn:ok,o<$4aoX:IǴ(hwné`qdJyj&dӴԒ,jiҏt"R`@1X ?c!Y鐁]B_4DK萉!c/yMҜ3/yMҜ) p\xW#np$xW#npԷ>2ι筘B(|ޡjz(PqBچ(PB [ƒ2n LR*1HθdpñעcfƬbe{!RTnkDMH-?v|I^FkZ 7Ur^Xf,y/By/4gNԙROa{\ܮGWSMqq*==Ey3)$YOkc] 86TɎE:U=e{lhQ,+ݝȊD,Y%S) Mq.!YZ J ˸Rg EfME==gSHu&rK RHԣ;le]9OeA\1R҄ȉD\FŒ@ @˟ RkG=Ue0ԥQ"]w4܌IX-bo^7V.=~<`>>r MM p"YaBOb.oi/5Z ?)&6DjcQ6b=2 KУ}u |z=$;`te׏>zSx`te׏>zS!. m$ mY<(Vb-OE .([P:څ P}xPbMաIEF) v7LND ;z&'##S:@Y鐁 ,O@o/CgCB%SLHD1ї^oP=([PmC P j(VB-A Vb7V&)g\L28_k19l/o蘜O<KZ3Lٲ2IY&π?[E;_,N)j~QĊ+~L^}ShMܮfvI!͟ 'H~+Wч`!^NF=V\BԑҒ͑K?te3> YSe䥉de 2Bs+fF['r\v a'H~+Wъ@O8ʮ:CZbn!akNBз%%Dv224l21ԌgM AȐcCYBVo9+Q~DNrfʹy~#Y6)նԇ㔄$5IDFGt+aE-(GSN-ѧIi{!mn "k|k=2!=şI %SLHhQ=i0:2ǟTѽ)<0:2ǟTѽ)}6N}6K{,zي+1B j-ajamB (Pž(1APc&Ф"뉆G ;z&'" {vx^SbOg/q%YtdN*^Պq ÖPXZdyɼJܮf%6~:hP&A5A)5ek̋ǟ0<=ҪHlڝQ')dEt\g?jz(:.tx5cYA&aO?CNG/05Z# &V咓=q&v<[ UT]ԛ-9QVO(+*4ԎQ;ϸn3!ziõ y)JBYԢh̬dJ>0 | -fI-qZT\Ȭj˶6yK=#1bgS+q$[WXJ"= P)I%f~Wu9-HJڈĠIm$`c{U7R~1N+#rV‰+ec= k`;>&|ZQֶn"XdxH 6ۺwXش@TJ$`u yRb4D{ Dfkgr٫Miu;k5ø᪎ n>SFѴ:2m Ԣ#طrz׌K⨵ TKaiJ&♎lW%VTؓf4Xq3gy S{-;b3-q">;\t'WkRRf)΂JIjYlʲ;h:\JjTﰈn)$n ZF;ƪ8Κ}:QZ{OU}HCi6[KGm+m̮W7CѦ?U]M~StU\*5 Y%7 )mزIb>\!豚i2:aLjQqR""3>!M=Uݡ16!6i0Ve$zJTQ$JJ0ơMTYz(`3(SfhFIX&:Ț.u.U&n9hKx-J5%i,RIB2CQS uFmУ3":$vէXbدa1=H5Sq'rF}'rF}n6uwq(2j^h2LT2LYTWb;\gydGh)a2m$D"DEͩb"!ʼn33-Mj.lUnjtHJrMZZ"AcJd$f}3 Z>oçLaK=խfRFşIπ$L?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFu# GJR{Ѕ!;MVq?_+l/o蘜}TfpdyUjݥ%dzmFgF 23DBzmk$%xgrjkx6Ѵ(l]t5&Uru=MoVY<;jjf*&؇.dabehз:9I"=z4[ U.[EP3!nYZd2dD(m3 è7IV#U0i5:Rq%+.4N'JJ]Ee*T!dY/Xے GbLπB@6:n1)MZyfl$KB,4FGjI MK3ϩ4U?CН~QGVelsQy&7[2PS.%CУ#آ#a46iX3aRnye2?ڢ"3#Qlnۭ 8%Z֣$ԙD£)M˺N"\$eNb#lW"!8 x!q49"d-{#[Ij\ō^FDrkҍ4뺸k)mQ.miQ`'#hXUJi\)6-J-σajIj#-Dah3i4,E-GBe6x%5O3J|CVJ&Ųizc–9 CAY2ʬ&yvȄ\Z#Hhk%& bV8N!IK 9iII8Y+\Ú-n c=_ij5ǩʛYڒIeMiFW\4g]'HͩaAZL|!j%)II!%$Y#б%NPcC9Qvy(%)iZpWrI4cC%UҀuMvGI,ʹ3 !u.c<;;Oc i -%QarJFG|t)%c#.Ǎ`)U\eUb m"̊u3S? M!g̜V$M]Ռ0L|qK;6̝5wb&[mͺo{pO҅JpԸiÔd)ofT kQgg3R3#'ϡ Q*ny$(Εjm%iL2^dx4cWk9-rse0rmHxQYjU0iS}RcOuk|k'G5鸏J8m!{˙d̄"|m.ׁ3|)%@JCVT@mKF[mjR5c^c4 M6*5T-i3Jr;<:dTO^Q!J3;xL̋mn"&:hÐ0zrKs֣fṿs @)-YE}my[d,V'npz!fƘ"jB3BYV 5lpjగva? 'z`l{X00va? 'z`l{X00va? 'z`lu9ΛnͲG^ HJQ-|+؝pNXZkh2tOoP=([PmC P j(VB-A Vb7V&)g\L28_k19l/o蘜O<E;_,vOK-CVfI*e)DV#ʏ/ hl*v1˨ILXRǔ(JBFd5\ˀvwlFyI#'ZԊDul3V膳k#)uI+PI9kUmJ w0k` |GG\ڂN$I#}>Ju$Ėew)';b4SO= q z_Ύ;GU\4!TBS[KQ2w;tjU2>.TAOmUbKOC~R)fuI=z,Ijydݱ)';b4SO=v²+8e ivReOr56}k_Fg2QǒqBz̲T hީY'Nbmi)5 I'r"KȌHJȎ2#+Fd=4|!W[3Ie[$VJH[m#E<lFyI#'F1K NDn(mf)o+JҢ|dW.7lFyI#'_S13c[N8D%KQ'<%((DEӱX!Wʭ[J}mM'1nW8VƼ;CY薱IJJ-2+Ֆ8aiul7?vn:6ZHU }*+Ha+#B/eٕ}A ̫R~*8Ry.eIDԓ"Rde2fbjYuR(T ;M7$cN&A"SYxrDk+-].kt9TZ-6lg}|D8$d\r)ZlCqJyDNgEFi^;%h=>KM7Pkۊi"#[jZLU;Rd{ tS\j,Ym0insRMm3>iShb,S1-Rua2PI p3xԵM~*<71Pr:SJղHQXm+II% Vb,进uѢxd'tl3BGty0^;YV㈎G9jeڌFw4ӿG:Cb5DFҪhEb2=ΥJ!hN,4&c+]4ӝ:4Ǣ0(ٺKLZ&DkltWҞE>$gSJq6a.\`%6=ثmȞӑ>8kKΧ\ZD AS¸b\TI"nfuJ,)f[\yR6<Ɣj|,8XpފRitθ9%Nv3A% Q&Q$$vn!ɑW"C,҃ZTEu)(^sJ]VDZ椬7T!RPr8V2*$w$!ԑ!յH"}0. $lIR-T[ ;pyqEʃXl 1曐gȔnFDeb#-"Su"Vfm!eSdMG{qhk%!)?ðNV'`f4WR86˙\i~C9OX:Xgb;?Kغq"ңOQUQ)M$|4fr#{q_`!^NFty;vdf2nRymPFj"$fEf%bJej-ڨ3Mq(#Z%A$>"#:CZ;jw1;jw0?{?ðNV'8;8[a7_jc5H&mn,mn-D(A|IIX'MWSDUJzS I% R)-WQȸL딹p,jyUf ԭcq؛$\dVZm}V*j5)J73333>m{u2/K4)Rē3o-GVGĔ Z[e#&sdUτ1 ٭VViTq%%3Yk3[a4ieixg{ x+OWX6;uPjѬ(!y9 <Dz6pP~]6γ.|^;_) | m$ mY<(Vb-OE .([P:څ P}xPbMաIEF) v7Lm zCXTZRdtSYSҦdXc^Fk^+DZlWMR$SS*-{βd̈BL̳"YuK9ۭҟS3Sz#v5$-(Vd7QXyeN/!Oq{ѵ)ǻhJȓgsQ&b)C5sTiJҵe'\eiqm %[MSQ7 B-veXd:s~-5I.z6BV&&EQ$vRlFw" ^uM{H2گC6ZA[R2$iIp%:4q4:k4He2p+]E ÛID^kɪZL{-O}њ\$\ŘIIԺ^*&|i [/)Rd!YI(5&Y[cd N U).BEvY7VD/*H4Ű@ tFTIz[2iqiЕ1&\eB ]7.#if3n!) ;b4!D[2OzQ I*#GCZقq垭(T]tD&mUܚui{C?Y}@Ti)[֧&t &yN"#E>ttʧ̑B}ˬ:hY}GpDŇaf9ӞSқB"2IH%(\ȮiV͂ =5*R&lř}*31$-n c*Jp2w&' JBʎDYAX#݄jбJU)̥Y&"Ucci#Kj-/TQ jlvK67 lj,dnH[#b|Pq!(,dqY6kB5fFyPY]G"?z@9ԺGhʻM4ZR(ܩWg=ȍrs =IX||=C7O^wܘ`S%!U,cnFP"%vF"=RGGKE6*UIJUeUϸ-.ٝ9ԺSoDD4xvn""qW%:Bn3C-*Aڃ h|vm2f/6HQ"S2eղXz*ߢSI]vHGRoaJMb=5fi 1̈BΥcm$r#t.JN#hu-NL:[WiFDdddF084hMHp3m2H$!&䔥%hgǃ}ruֵ'ԥ(ffg>5[}JO*w#c]]k+|hϮb42X\n}N|ڽbysXk+hY鐁 ,O@n_V5_oH=vp!-)S/ӽ_@$_@?Rx:瞶b PzBڅ jZX[PB 1o Pbɺ)1H#:a^Il*Zlfᓋ"#4-j;fvf-F^Izd\,&tMq KŘʴf_#IaU/ R*L)P_K,HeSNZ# c";pxuXԊ0)iN_d4RIݴWv:tFtJ}U'ֵfn6m"%. Wc˺D 3j}էH}55}S.i)RυYO’܈cT1E3B`[MPzZx[\e7 &~>ӑqR;f(f87.̌fC-WWSeEj;.#n7$ֆL $gV-T#4պމ:)df.1nIDC:WۭUf]N=Qӧ8BFHQ2e5l*ymPZu2sHSJe$I473Q[ٕN~1Lj;O8 yЕf3ݢ+.t}nd9?r.DECsg|LDU20uszL6Ir;ljB&dfDvqZ[ibJEBOT,IZ݌3D]=Ғ$gFYµ1R-n7eMH7Z"64*zcl4SNAڍy 6QI_&bXBbJCܴIkX/y;ldOoI.T]y(KnRy9vFFJ̈teV[yzR#q}fkA$.T1dit6Lhd84=ܙ,7{Ձ\&K>7qf{L[&owNKQ;*j1C91?=(.̳$Ki k%& v&ز pFP2RLDdFJ-dF[H@/4Nj 2o4`N-4IsJMl#ZoBcR:t6x‡QeO8p]jAI- "rؓ**F[8eUuTC܉D8k ?v:'{1*kܝ,HqG[yV.겡gVJ$-HSoY'{S)عkNJIOTjAnAH4u܉+$nv:'{)xK !Molۺ.yDǤSEvk*vSu3KV;\b+ s4+MFU2ՕqdYp"lh3IQVS n1&ScYI̥HDg{lr!4 3ae,c%Ok'u@||Ejؒ'P3':MMe.A\{Nŕ)ӝSxڴ֣0(7CKmZ&[;Y*"3P;˩kBPF1:sdΠMJM'2++:'{BKkJ9(5嶒Cq%wBȯcJQH\QQM*\-J&QDۼn!I3RI%o^ñ ?؉VjUF3ejreӦֱMtԹY\Ҧ[A\"1:}QP98R]vȔQͥزee-$F`m~X7/{aelbt=Ouju^:cVۓQyl0u'ІɫVYI+.b+܍NX7/{aelJ0"o^Ǟe:>ԑ˻v!3:15[O?n;Hp4KKBig ΜO-<nckZ|Qц 8WajpVPIIl$ēٗn{fIiⴧT8 [i7]S2,FJ=T3>31XYBM#"M/*twMTTcĆs$&,b~{Q]efI3$ӱ  [-9B $sғqf[֛銓UTFYX8DJɨ)&[R2#%2#-"Z)co:TaCU樲 ݧWȸ. Ҥw9L ?v:'{1Ph8o9ճ&U^MAZ[*xEJ8ȔJ *z,XW1rth!Ɓ6m[4ʅf[=Y(!Mdl el;`̽GO`\vz$݊M5 [ KUuDO})i]-fʹsFRմ3XDAm0xƛ(jeM:]e$HJ;2I#;+i˄dtiZ4l.vRsmv2ಉ*-qv)bJԝC$̜6q6mgsJY;T4TH4] I!<[x6WTǞ" j.1X ?c!Y鐁=FIK k񾿪"z-"B[R:_9{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFu#YRRjtq-NS+qև|j{V%:ksq/*[UTʬTQ-,\]w{яY!z&1*2I(\'R2HQ+eȯi\p[dSrD1N$ɹ˒K.E:tlӦH)yMdvQ\1rZrNV* 1~S*C֛J܋cNB}n>Bu^*c$IjCVDfdIQf{8~KfTY˫ϑ5J앩'r4cVF!fYQջ-2'NKc.; X23czB֖#>YRjYӉe&B_12jߊBuI[/kgkدn"Fo\ݩnW|3f[}ƪFiORZLnK&\VHS>;L$pm:Q"U2Bk}8ZXӎi>1K$߽m4Ā $ѓi!ZMKhIj"dDW2f;˲9k|Ƕ>tݔ4y\;F=졣Z:1폝 e rяl;(hwΌ{cH-CGþtc 7nEZCkvHfs)Es\τ`u~uCXFe칔^+(VpнQu]2Wg'Fϫ~3q;DvRLde|}F0bUqMv&NԚOa>vP-pò9k|Ƕ>tݔ4y\;F=졣Z:1폝 e rяl;(hwΌ{cH-CGþtc<>vP-pǃ+?G/aELRL)5vR캍*3^"k\K쮼dEv?haj#TxH}12xVD[`M:8暹e}Զ+FDEs-b6P-CGþtc<>vP-pò9k|Ƕ>tݔ4y\;F=졣Z:1폝 ݹiӱۈjE"̤Vױp^>Z?L4h^˙M좲Ek qDN[.JdӢ;ou.6GhI1Y鐁 ,O@n_V5_oH=vp!-)S/ӽ_@$_@?Rx:瞶b PzBڅ jZX[PB 1o Pbɺ)1H#:a^ȃac}Ddj|H>=2!=şI k񾿪4=FI'?$%:#ew+8Dw+8G[ޏg\PY>oP=([PmC P j(VB-A Vb7V&)g\L28_k1hjVQteaɩGj#RJ-'V>U[ñעcrnj&Dc} W*_QU3aN }ͻ1sFQiro\8Pn&V4alj}.i`4 /CPZU5$:NPT#*RّfQwWktT*~[,yر鴹I›I%85se2"3=C˸k] QaShǩj'Fcq7)$cF̶0g-4 =Fv>!ڋ.?!pۑ^[dg#$ WM 2d|؀9&!*JTA(gEI#41^!PAs5CNPH&HɓvDݽɬӷn!u*^,vHW5#:͒m LR;ebn†PcamG|)MtEP堐PmH]x#S)sbL.<5ZuOF+I8H-rȈKN\ĢnFWrwNX&OqDDte)YԔ'7ukĐO m*%2;У>;%O6,豨Y >&XIKܬ seAK-+0ıV)Y=w@(КKTѓ"թe(QlBaa;ળrW83܍lgd+N"#EǴGd7V&캇T)l +2̋k#"2ح;tXU3 agcQh4՜YK6IBn"Yܮgcq:CPjKSi ȊcId:nkϷVZA ;vh62YPɑGX00нL_4;Cč7郵 o; F`~N7 o;kwh. 'ῄLO ޘoX00va? 'za@`~N7 o;kwh. 'ῄLO ޘoX00va? 'za@_S&ddҢ̔ę8}?1X ?c!Y鐁=FIK k񾿪"z-"B[R:_9{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFu#%(2hm>^u',֝2pӜFdDFEe+* ;z&'##Stz)қquH[(ZRdjA챚L[=qUv\'=-WDf{RZtgq%sל&SIQ{q2Ebo[Y2浶p 4EV8^aLg7܈>,ȈԂq*ȭ6=1@jTIHiӫ 5r#,ٽjI沮#R|)#+ ժ,_7)I!Լ6+)i;W+x@1+HJ\sg9-ɈԄGb5$J*[Jǰ MBB!ڕZCfID=jbDv.SJP @ ĴtX*?7r;˳sm1!Q$OE)-,K2[#Q-g[~uC:(GC?yld2;?\η5xuQ-YZ(dv~ok0+O[;IJMH+$X(kN]d"3 9KhZ̖Y_q&eN2=2!=şI k񾿪4=FI'?$%:#ew+8Dw+8G[ޏg\PY>oP=([PmC P j(VB-A Vb7V&)g\L28_k19l/o蘜O<ѧT:VAg YIԉu'bKeϾ$9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:sG"?z@9Ժ`;m$r#ti#K:iΫAx>RG2bπ$LOqg~ǦBto$ ,7OQ@CG_0 ngaH| m$ mY<(Vb-OE .([P:څ P}xPbMաIEF) v7LND ;z&'##S:@JFNHB 5:Y d3̊x@I{cDb_5?c{ȌK^O!#dh=F%Sv>Ǽľj%}y|"1/@FI{cDb_5?c{ȌK^O!#dh=F%Sȵ/X]/Bui% DEs3şIπ$L5_oHXn_VЇg nga?‘2;ۜ"I;ۜ#-Gyf(P 7Z-\Pu +PC+1ABR3&/o蘜6v7LNFFZtvuGc*?LnΣO:^9y)UjW&iwIM Bk/y@JPq}()۳IΤEO(ՆkxUzt:;rc';kI)*er2;^s(4z]n-ay G„jB-'lzv$]o?R)jXHqcqbVWAݜR,7XB29|LMzki^\+5bzڬ2bM!9%滮TP͗ w,mrH!iU&V9;J$yV[C+LY1&(1hE]چT]N75u쇩pЦy&"N^95|xƌm QުX`߇RTSk#N[ik29RgrIPW"c3i#b.="K#T,Ye32' d]'zyϼa{4cT irgRt315! ]te jl!I+wVBeMrj ʩKqqME)fJE m9_/-{yϼcLj6pN!몢GD˲(e5eJO 4l <Ngڛ(5.Teȵo-m& BSWdq"}i[3测/s{F4"Gc:5[N%ivQjv>=GD,b 0u"Tef㊜B(̕&ŖlF"ŵC-E@şIπ$L5_oHXn_VЇg nga?‘2;ۜ"I;ۜ#-Gyf(P 7Z-\Pu +PC+1ABR3&/o蘜6v7LNFFZte;c . ŵZm6ӴcImJ5Z2OrGbTw>.؏Zg'^r -i5XWC&;Qw;m)!6جo8w"{}z!``˫;?a>/A  /Dz!``˫;?a>/A  /Dz!``˫;?a>/B7/aM&ңym9\<6Ԓ-"-p %bπ$LOqg~ǦBto$ ,7OQ@CG_0 ngaH| m$ mY<(Vb-OE .([P:څ P}xPbMաIEF) v7LND ;z&'##S:@Y鐁 ,O@n_V5_oH=vp!-)S/ӽ_@$_@?Rx:瞶b PzBڅ jZX[PB 1o Pbɺ)1H#:a^ȃac}Ddj|H;V >Ӂ[G:+Ra;r yd:iggCWשfWRy Y?O"QȎe~#sf?Dt-d:(Q#Ua`,ìTu]}7fde͹XGOa*ZU KCӖ%~6FHsEt? 0aH $S[yDZnJBQ1HqJhMĕkE8`Ibg#Qgq3&,{,WLg MH%&GDgdn!թe8Z.ܧfjz l=9IR zՔmJVu5T.Dh[/uɱXNjEIXi8ΩRi3JJTxfF]3!9(xhTE)ڌlHm$K5%Л!*U̐NC#MQڔl;6=k)/)X֛wEnfNѥi}Vt4ZM,H[ddeg++"/Ǖ7!cVr;2GlԅLҧ&4|3<seUɈYcSuI2+ZeW.mIin:JpilðĻ VkGԡrY lTLM}e%8Kus^ۉ"r:%MR\34I\DH%BUvVreӗby)tԾnf٥ڍwfX1ʉ1B**$jEJZ!zvLm܈zE}wJTq4IpVh=ZJ/jfD|d8zQͶ&7;jM-h_i=Z^崬dbK \Q0ⰳҙ\stnb;$ZIݑ6MCwfvbMQF}x"f5M:E)±m0"6~Fo;jKo}/pm03FTr5Ym֝4 ȅԤԵ(V#=#tP TgHSgbG PB&GKjq)32"Zeb_աDbπ$LOqg~ǦBto$ ,7OQ@CG_0 ngaH| m$ mY<(Vb-OE .([P:څ P}xPbMաIEF) v7LND ;z&'##S:@uMCU~;N6J(Rr$h)ͨ~5e΃qC5l9k-n+CGD&,%$I%m!#jrCV̓FT:\ĩ-1sL[.Y:}Q.‰0bMI=-8N8itĘVl7L̘eZBJiYYD*E(ؗl̸*d8tim̗vRS"A)[ UZR.sp̎2dӵ\C6wJ1&D&"C)hiNJ2kB Fȍk2.eqǑeU,Ԗ鼩hyNTY6U{F]L˻0=Rn? 2b0gߋ%1qY8ۍҤ(2FG BA5 ĕ*I/GqHSfi3JIw˾F,D &L)J~${Ypд[HzoG|FIW+wf\<1' r* vY+-{Ye{ztٍͧLSwkM22coLz&nKm2$RRQ:tٍͧLSwkM22c1![:DuyrLڒ8J#{v#2d1r[pCYҲ;En- XUzzEjݖn'gr1r}V0! ^+ֲ̕ $v?F!nQQ"K-u"""Rs s;!ujZ֣Rgfgb m6V4TڤMVu1)pI2ƪFORZLnK&\VHS>;L<`V]нO jRR9lQ|}Ҷwόx0=ud-H~J̈gr3jԪYHfd<@jmVgO2B,vyRKn/?hkX" )KEY8\+^RcLBr2H~de{kJܪdEz+['.ZQ*I RDEܤaw4bg~ǦB' ?c!zc5/MgIdGܒUd|CE ,7⒄&4ԥ[LD}>ڮHx2Ʈ%:NyZTÜ)HZʹDZƽVt~h FKeՕKmˁGπNtVOèuRhRFG%ŔEٗDsGn;O))fKbɻIpy3>?>6v>/Wd. LC.|N1kuftN$"\Нr-Fl,O|1׏u}՚)Rƴ4dJf~|gjRđl*N;k5Sy%iemEfw%lI(>2ų)x!EP_],)R){l:it')VD>V[SN{5ަT(ơ\q0ڝyiВ P|g@LωVزvCܴjCgW .HdI/'*U3225Kĕ+U]"&=BV'jėVC$Œ7$)>QO[}S6f`:f|~R?T5MZ?Vob(U9TCP!nh(} mrurSsj;T5M\xʁ橿=kLCW)1yoj345M'0hLPbةocޙkǻ~;y_I#Bv/t3Kv/t3K`#2x_fx_f3#Bv/t3Kv/t3K`;^x;^xo Н < <7dhNׅiׅi24'kw@4kw@4 ~;y_烵~;y_̍ :@29b |;V)^0yoHfG7Ra@\χj+5 ՊW(kXx湿  >+InNE%G% ,2u-2+$b~%5<(1Ԍ+"Y+"SnKq$y#͇6^um*KAM!߂6DJZlnvRJҟUREsxub(3,~>"s2tfUPc:T,7l`Y}oP{ Fg˟vn-ÛdNoQSuΣ):݆{F_~iM؜R樂7i8-Sϋ}mU"[JBE\h Z\9WqT늹j;pV+`U26"=6)\Tx5Q9b%Qc"?e." ~L"f3\)˯y5elԵԳխI[?c PljLQ&zKsIHnc̢BX֔sj#4챕~+ u"tL-ySCm;*մe{kqCF9 040^QLܚAsi>mMKQGa,M7?c n(~6A#n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A0hw-RKBcROD<)=J3uf]$#nRMDYOT!e$VO)ʄ&In(~6C4Xqb%p]EsZ67Zw2v\̈V %sd.2,Tm߇*H&ͪmF)MTڧm/xymon-4.3.7/docs/man-index.html0000664000175000017500000002312411667507553015742 0ustar henrikhenrik Xymon man-pages

Xymon man-pages

Overview Introduction to Xymon

Server Configuration Files
The hosts.cfg configuration file
The Xymon client configuration (analysis.cfg)
The local Xymon client configuration (client-local.cfg)
Xymon environment variables (xymonserver.cfg)
Xymon server task configuration (tasks.cfg)
Xymon alert configuration (alerts.cfg)
Xymon Critical Systems configuration (critical.cfg)
Xymon CGI configuration (cgioptions.cfg)

Web page generation
Generating web pages (xymongen)
Viewing current status (svcstatus.cgi)
Viewing trend graphs (showgraph.cgi)
Trend graph definitions (graphs.cfg)
Viewing multiple trend graphs (hostgraphs.cgi)
Viewing critical systems (criticalview.cgi)
Viewing ghost clients (ghostlist.cgi)
Viewing historical logs (history.cgi)
Viewing the eventlog (eventlog.cgi)
Viewing information from a CSV file (csvinfo.cgi)
Linking to prebuilt reports by date (datepage.cgi)
Custom Xymon webpages (xymonpage.cgi)
Xymon webpage headers, footers and forms (xymonweb)
Xymon webpage access control

Report generation
Generating reports (report.cgi)
Viewing report details (reportlog.cgi)
Generating snapshots (snapshot.cgi)
Configuration summary report (confreport.cgi)
Single status summary report (statusreport.cgi)

Administrative web pages
Editing critical systems (criticaleditor.cgi)
Finding hosts (findhost.cgi)
Acknowledging alerts (compatibility mode) (acknowledge.cgi)
Acknowledging critical alerts (ackinfo.cgi)
Enabling/disabling tests (enadis)
Acknowledging alerts via e-mail (xymon-mailack)

Network service testing
Network service test engine (xymonnet)
Xymon ping utility (xymonping)
Re-testing failed network services (xymonnet-again.sh)
Network services definitions (protocols.cfg)

Combination tests
Combining status results (combostatus)
Combo-test definitions (combo.cfg)

Xymon server programs
The Xymon network daemon (xymond)
xymond communication module (xymond_channel)
xymond history module (xymond_history)
xymond RRD module (xymond_rrd)
xymond alert module (xymond_alert)
xymond file-storage module (xymond_filestore)
xymond client-data module (xymond_client)
xymond hostdata module (xymond_hostdata)
xymond admin command distribution module (xymond_distribute)
xymond data capture module (xymond_capture)
xymond sample module (xymond_sample)
Xymon proxy server (xymonproxy)
Xymon client HTTP gateway (xymoncgimsg.cgi)
Trimming Xymon history-logs (trimhistory)
Client data collector (xymonfetch)
Application status feed (appfeed.cgi)

Xymon Client
Xymon client filedata tool (logfetch)
Xymon client update utility (clientupdate)
Xymon client ORCA data utility (orcaxymon)
Xymon client task configuration (clientlaunch.cfg)
Xymon client settings (xymonclient.cfg)
Xymon client message cache (msgcache)

Miscellaneous programs
Running Xymon tasks (xymonlaunch)
Xymon communiations program (xymon)
Digest calculation tool (xymondigest)
hosts.cfg utility for extension scripts (xymongrep)
hosts.cfg file combiner (xymoncfg)
Xymon command-line tool (xymoncmd)
List of XMH-field names
xymon-4.3.7/docs/editor-diskchanged.jpg0000664000175000017500000023617011070452713017416 0ustar henrikhenrikJFIFHHC  !"$"$C" j  !1QV"ATU2357RSqu#aBDW$46rt%Cbs dv&8Ece: !1Q24ARSqa"B3Cbr ?tSI:"'W1cv USAWUNWsړ6.X$u-'Ϻ>ilqDglSX/!ӪYGq !4QJl1RbQʊ2Dֵ-&N)dRH6"*S5VlRZLj©2NdMJtԂ5[@6ogҒqv(Nf&xVEf"eE40nk3"ٙgdlz7H~.*F'r/ kT~,"+[bRMi0۶gnٞ8c vS)I$LƵ )R"۰ U|J&].{DIlBF dwl 1j%YrSjj\[FD(д܏*Je22=5iO^T.m.|[or,Mbt¨K7T7)4ℙy4έ2KVЄ>mConٞ8afx[>S RmJmˆU֒J% {wavs&0UdkYzJ1Xg&fZn9wؓTJ=#hJ%U笿KLeVG #J;(ia YGۑT S@Zji8dq "QSMѕ}Œznٞ8c\ʪVUޚR  Q'2ob?t2Teb~ZiN\79$kSeܪGc/TV@r*_[d}166THDFFWm3 7l 1`%v 3 sv .sXN”DA*MgQY"%Pd X%k>ۯ~,to.jɪZٲu?/._h6og\Ex(Ϲ4 ]I75j_5 ؒآ6<>#c*b5ݳ&I'E%|U\F۔fQIadnOJb%H\УNT+FjRv 3 ixUdNT.DFa^v1 Vʒ5k A6ui+^u:M6HdB[Jl23,ٕ`nٞ8afx[TR;JVz_R{fDλdџF{v 3 k1tje9Ue5%c-M(ߝN-w7~٭^G-W7MN-rTӆ2qVT$Δ(Eܑ6*UX"CєMe4lvZW;\V1uv;OӒ\6JnRF}Bb$πiTC Q;iζk~gQJ6) KFetg̉ӣ):FQS glK 46%.2#;a` fx[o5F u20}Nf )K4$ѩWR x _U,Nm̊l4FJ4 J6;yȓ6ogӚ6xҵUTgǩ"W7SRNDhMiL}[!KJHR\#;l%Zr?QA".L)HTvZi[dM瘳f3+rI-';gnٞ8bdbF4[~IfS5ۺL r0kW[Xo6ev$m0Y%K4ʅ*Ifx[CjS3ZEGirZSeRN-+4ҮII܌cbo$ꘒL*쌚Imi5fj$AHDw^T7A4Pq2X4㦛pbmdGݳ<-pƣOͩIDT9+kz%⌵4j+IJagFxJ*GuX1ȻIMjJ,A6Q8V$ v 3 X _ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pǞUe&ܜDfjUϽ?F =1Pֲ[8% tw/!dX!)Z&jjWཋD:XƣS,6W{_h厯sI9_3wG\jtr K%%O${hFo|gLڥ?v7j:)d$E5*JF)YP3; J\QoMG-v>y?:1xb;~:캍vEJY8팸3JJ"3J^ȉNQPBa%%*#%%vFFw#?{h<v>y?:0K/ԯ x>Xv>y?:0}Wt`12h:&6P=<,7Kfi$)VBP5*RFgaMAI[ L#9RIGIx ݏ{`N;h< ZUXrS)v[L2RFyL\sb8tjCO-$#u2w-LV2Z=O0g~'F{`N3P0ΤMB7Qr&43G b$(m "3W>>F74Zm,8iR h"ؕVܶF]{h<v>y?:0}x>X6N~5\b#6%hљXY)7.F[6}WtaG `[L7RvޔjȜii)bNҕ+r\Ȯv#=15J"!aaɑ%e%ըַ &FD)JQ {`N;h< uJChJQ$"."?wڕ8\}cG ñ_рְ -uy"!n˃Fyh/jfBH=uC*<ړ1;\JSmYR[s={`N;h<!bFbkҚ$#!mIl) [ $[-k[`GNbj#1)m,d[$X"5'FE{O=ÏIGC\'~cEkт[C}^3ϧ-Mӟ W㍩ }5fV%JM˄ͤekMmF-\.>r'1SL4oe& eɻ52{[e -\.74[] s *њ슾K-bJ)S&!+=yЕ}mW n]˞F]nYn 7nh[-\.74[] 1X^fgFKu$iBF,1vilRe,b*lRQk$YSohp0ߘot`6K![HeN!m3-Rk]*&,XQBSEh2lC4j2"ݽlb.!74[]oSohp075-0JJTFJK.쌌G{f7ڕ8\}cWSohp0ߘot`6Rg OZbmd:ĺkNIp}Hq 7VIJ J2G Mϼ.!74[]oSohp0װ겪ܩ-HZ5!DqTmi#.8φ7.GtMv[}gw7uôkmF-\.2†ReE4%R/Yԥ)NfEF}pe;)SP"eC(AN&#2~cEkцu6Ƌ| ?@Ib/bS(ij,AajmMLk -YRʌ kmF-\.4Lj4l>Q2vkmܿetc m6a×M,ВR"""".74[]oSohp0C}^3ϧ#5Zdyu5hzDJrCm&dHE3)GcZb;u6Ƌ| mF9 DYU&\$-q6NbYFi$+8/0bMCtR{wqߘotaM15O`thb1 v3L%%TR%F7-Q|+P0~Ӣru!HL(m)̳#3ۖ3;qSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp1q*a?4Z45M֫7ܤȮv+ Rg O7ڕ8\}c'}b7*+ ;%P%DZKŴFdo+p}^3ϧ1{`N;h< Rg O7ڕ8\}cG ñ_р+p}^3ϧ1{`N;h< Rg O7ڕ8\}cG ñ_р+p}^3ϧ0=ÏIGC\'~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>*!dZđVRJM巄C[Sohp0ߘot`6FCZʜ'hҵ>W71˝_2#ɬa'#>(HDG?UUz摒 ddn$xН]JXa- Nf66EVnkLLuCj?IWLEjS*;i&I٦^\YRJtJr|W ICj4Gcx7S5&a(ﲒCo%)؋Y൏=_޳o6㗮{2ߝz,w.;UzͥK$$$J2bIVηN,;RJBuۈ""ȟxNn\7RͩNX+A{v)&F\$dcѤzN'/NErY(%%;j35m;΍pk:.8kpдxˀ;Cm9L5ϸjRߵ\D_gQ]F%YS ~ꝋ*UZ}42O9g,.IT1eK4IR#BYu#%I"̄i Q}2*BWOeO7Qޱjm(c_emH?UTtx*\1,%J[jP"iX`$f[Ufw=Y2.9Xh Nz%e+!(q̥GƳ32ڣ#|ױ &zQ=?v.pg~3T$4"djե&;(={8.gJS"\gjN(#4 im/C*inUfvG2=\C0YyjbSN!Gt(b4_i۟paKȌ%Z{tqHS8Ze7WMa}'=,$$1#3hGG7[ Зm-#ꊪS8sPANosKM- UFl\Egba ќ9TbH*#."r*n##.)⫈(۝3ZQ_w e.Ó#fӪfIA`dNWO.!iܚ9|!{WS,{9z&1S=L&!\xq Bښ_e@ɂ͒TvRES",6K+iq^I45r1>UlO%ju.~ 1S \v_Ym.wVf#o32k"SmWaұ.\`Z&)v^'í1r53s%bIM˺A,elKFXZ'OƸ(U8ٳhJy[ JDHt2ijim6tӜGۦT\*܊NJB#+dG=#Ѱ3E:(3ZDZ Qe2ۣ'6+KI,JLմQE0]8uQ)1i5njLҦDȲGt۹8b 3}l^nD2jl'G\;hih]sOq{o+w_^vnNjug˺WC|r`<1=nV LErf[Fcr"PdMTצTfО&")RX5Plfv4 ;?ᕩVl9r,=_4f. Csy0cz#uq4 4)I5R̶f2HbwVMfKnl{-Wh9n\l?'UkQ0u:#tyMoCoi:|DDvښcaCC{ "UPdF<2tȵzBp0d,fRbCZvc&i)ΦFG±5~oIps2rđ#=ygX؆|9Oܺ.}f]NX$g7[lwZ]o=rNuNFoc홪1t2r j%te&PYUo`=rMt܍O3Tb,dJq(L7쇜iDGKΚ "yFiI=(˙EbT\*܊NJB#+dG5*Ze2݉LIDjr貙Q8M-9d,BlW"m gt1AT%Xvk%Ro^KK`RI9)iVS+Hm`_Q55VAX&О^dVjHa)je{VYZ+ZxuU^S’k|JA0umMnRAոN\71b]n)8l$[!6Lms\W!tg;T%T ,O? Ɠ&yQvI"n]ȐG:JZ#h'2+kRRJ>̤imiaC4n0Lb5&?6;\#Nԑ)J+Xnt 62HbwVMfKnl{-WkK?8G? "ET֍dކI6NqBTId_j,88_VWG1Nv:A 1H[i6DjJ\P Ռnn]խ@Yۦ[1ukY98)[Qu4I%$f{$dTda,T/+qp 1Mv:A 1H[i6DjJW2-Iq^INW0ӑ`*OӳSmrDu%*mV2Tp]FTXs[U+"gXyl(Zov\{F&fAl-Bjk3R0!4E!)JI~FENOQƴ8ц)Q!jLU'i*R+Nf̻-Yw]&뻣U0˸)neHɖ2k|m1(XaQ*u]j}%8/#u+Dѻewږ9bK">7Y˜nCQzٔP a}CY.Re12DdFdn EȮ[v5\)լPSp5չjvM4fh"+\Řei)ԜX IQ+pQ$?2l5v%uu)7 VfE\bڃ0O>0ĭPpՔ&ޥFjVYMnWD&w Tե)-\r&jB:+orv4YjF LU3ϾuRQ> LҲ2ҋf SAN4,Jp QMԂY.L%EF2lUv#W\ArUc}䎄3ð1Hx9#C]sq-$Kk$۪+rK7b,XBH?!QJm=rvR+rdLyj׬R32d^T.+Fƭ34{e #?;彶^+F&e{ W!SxIV!bFݙLxLYI~)<LKM&(3ySt(CIiͦEeEz?PܻUR&=4罋j[;\#F;E$Fz[ 7[ĮJcLM}q)ͭvUeQ ౕIō0Z#nOr>U;^ jsW;gYT\xf&[ˈfw_w.m9)W^ͮ5)>}* 0Z]TFJ9.ܺs u)fFYGr#ᘈ+H@ l:TuG\J2"RVU|;@t( 1pg'PSW ѲܛL&֮3hJ63̋QMøՊ@2XLQij۪T dJN+A$C#3 SSbLTNIņ=cˏnEb.*bmdGMWj zN3SQG2Ԉw)pܥ>:Ι2V7r%)Hm(ַ0uqb 2ZRJ6%Ze#MIiN+hJKj֣ ȈOMDi6 ӽF]h夘I2Y;xe@Y%ce/%KK̭W"RD+nԨË<d8cØKOz~K1 % 'lW֟S?IWLy$̖ĆK+) MGPҾdUMkUa4D_AKK}w7gMHktU8]qרO*؄$$+=˹]쥥]t}?+ZKK͵яZaܮmR|z?Zjۜ}쥦]ͷъ𗽓;vR_.òv6F%dm{NrGrI]::3Ѷk,2Ͻ4wZkۜ}vR_.Å>ݵf~-ZWdC!rs h ɏBvM* 4ㆅRnv.Zkۜ}vR_.4N"w~L`i8fkIuSQvW$݉ 'Mٓ6;ܲ~c8)insmaKM|so_N"wk`76>󏲖v6F˷90'k}E]Be6\Xud ZKV&ly]H}~KM|soZkۜ}p>"7ңU&NiYs6ݛRyr+r"k`|쥦]ͷчe-5m8kZ9Mq5_>qR_.òv6F5d-w}ߦ~c8)insmaKM|soqo\l}Mq}˷90쥦]ͷч 8]G76>ߦ>Zkۜ}vR_.ÆE}~o\l}e-5m;)insma_N"wX!3h1bm%!EbJRM؈B5_>qR_.òv6F5d-w}ߦ~c8)insmaKM|soqo\l}Mq}˷90쥦]ͷч 8]G76>ߦ>Zkۜ}vR_.ÆE}~o\l}e-5m;)insma_N"w~FÓJHid~| CI32ii+Vܐ' 9MqB[KDZKY7JQ𙙙]˷90쥦]ͷч 8]G76>ߦ>Zkۜ}vR_.ÆE}~o\l}e-5m;)insma_N"wk`76>󏲖v6F˷90'k5_}~KM|soZkۜ}ps~c k`|٩Lyre"CM}Pvy-f,tWDJk>\l}MqtY>vy-f,oMq5_>dvy-f,K̳35_}~K̳g.ro2ϰO}~o\l}Gg.ro2ϰtY>s9>\l}MqtY>vy-f,oMq5_>dvy-f,K̳35_}~K̳g.ro2ϰO}~o\l}Gg.ro2ϰtY>s9>\l}MqtY>vy-f,oMq5_>dvy-f,K̳35_1xԉs!^Du6(%.vy-f,vr$og.ro2ϰtY>m/f~-ZWdCQbS0&՜iʅ 8C%3KM̪BRfSI=Eώ:\e`;QXX6}^VRN)T̑$:DeV^=*CjMΗ9k7g:\e`D[BۢtM6>ߦ̎:\e`;#Η9k7g:\e`9M6>ߦ̎:\e`;#Η9k7g#+ID<3U-b$f#۱Eg'tN3!IؔeffhRHs I#Y""(K5\=0A"<ĎwHLEs>µ#dDgi1ݴԼٕjhmJq_@ຜ*]k5Q2Mñf%ոNdԓʲ#2+GM:o0TҦM.ce ҥ6k}SI]岕Ӕaj,eQrM%4 (Q%MiHȎV%#Ƨ+j]J܆6qciWtҙHʕ$y g 31-U*Xʐ5EL55% iCQ^DZ{I;mw\GQ˘+77q.I6c>2 L+JQ%ۑe;x1} b"T .mq!}JRot^mcW&ƦIM=ʕ*)e-\!_aY- թiVDRnLj";j7MЃʤaf=by)$lG0u~LzErT9jtQO'Yl,ԛi=zJXfb?LUDJ Ce*|WQ6(=8Dc4,E\F\aM L1JT钌RLcKc0CSOw|Iyˎ7 aȯ{2Ds0F%K0w+s2MFU$Yl[okoT\zdc&0[q[K%g1$؎)갧͝Vs D*:lNRQVVZlgzh S1ؽ ՝VǗRqKqp^52INdgsf!;h9E`j>U6d9PeL"BH"5)FfmdXIuL,-RIJiաId8(͵bVDe~)H IsYrTbF A6Rӕ*eܝǻH^Ds%mO.UBd6c+&D/T'MVzYJϴ/VznP|WhՊ(3Fc6/pnRtB0'Gp&5 9O>K4ީIա>+EkޕX7Fn*4fdjͨ9[jq)pK∌Uѣ^K*|RiJ1m:m%'YHd{b᪤ү3423! qJp,#IV"̞t̲ 5Mtz6Υw^+2MȮvr_alqfzC5M2*$fR$Gd+(dg Ĭa[tSh})hjm";fͶ1i { dQM!4ST 5|5Do28Sc=DuθNje]:j\TNfl"SLHu4U)B7618BF*ᙕ5ϊ쪄jz#4\$4ݥYi3I!}ʈi6cr)?/EB"%඿P]wm@# %SLHhQi+:az?tgҵ[dD[XҒ#+Fd"GQW}Rbi'qGZ)EJ53,Y1fܓke+Ke׏>zS3u_TȻjE6sN01JN)Ffgȶp\ΰ }u& Ƚc;ۜ#|sz'fq21U|Mzգz #{?OD'W_{EY_oȽcmC.tW5 "tc ^PC6=m[pBtb6b 駢cKm&HqBtb0b䮩-qu>IOyk\tO m+Iι|̟Xyj݄v|̟XudDqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN]<S|̟XqN;+-%QȈc.{*Vۋqs\KУ}u |z=$;`te׏>zSx`te׏>zS!. @8r&#X:S|Kj$pN}[aIKǙTrF(hs:KoYH#df\?~RQz$Xݺc>=gI&X{GV30)s$-!JAh23AgqThTzkâHIu:ǩnt{ut7zU iLn[H̳i䨒}"\ų)EBbZUG&NY O ۾];7nYTfif<[bs8Nو~hGf ,4hirK4QJUr؋B8CaZ3D4%GI)2%% FvUt?~"YC*qն]jŔ$tdW>U1S{geVլ,mb~"˹VOc1^gQOxn%IEF) xW&$̵WȉW;wGD8P-_e֤)*W#+?ժpݙA8kݑm"Qe([rqlP=J3j$IjLG o e#dOJú=⺖7e*™ &ID+wFF""ٰt4oӛE \|:ԨuPy.%N:Ťԋ%=Dl.zcMxb,UѩfAKԹ]I"V,2m :bMu@۫5JDGKT7vJ2#E%JLmBSUYi59ېM.INrVgNw.35(b:M5XIf4/r{[ !z0ѥ3E%2lהX,D\EԠR.muѴEeN}Œtmq+lWAd=nE,HX 7(U4˨nHFV),JL);{tVܦ1 ]F%fho":ҐwAe<Ɲbnði}Ru%Pxn"FWfJ$C*4*DeZ ,:[ՑdSd\=љwsEItԩw.2R&Gri\ǰt/I'=q-a2#33JM%3έ^G8wSSWQ 6EfyjVW{ٴK,q #0UIt[\[+,5(>!NFa,AOR!:eV_a֓23I۾D3f=VtP]}KLIT$a%dIVdd+('s;AюpuefP",۝LByZ\|%'W9&h[ӇZ&;6NNPT6V~Mٻ;i/I8&\6=GJn0j I^#I&]a>rD'\"JJLYshWpxE5X bij-֣)0*3ndWer;CFijSL*qNcUȖiJHrQY=Z`D AZ+5t!i+"]'4Ԩ-M:0[CJ\y2+őL"h~F[QMЪ|,_MU ujs /+DbR]&WxJ\xZc_ RRZIICfi2A\K c' Fj;/{QoDCt,%MTDDe"@ӊ[.њIRnQpa>l:%e -IeCpA3ou&~2U(G2-qt=rZ1q2 Tȧ ҝr3tѨ(b2/89P*`U]Ռ8FGi|ݶ)#3.zL5A\g .=OƂJLa؎;F7cѣMRt0ӥNR"47 84T\^ȧRq q QVglV##J5:U-ƘXc ~&<:km$V. a_BLB3)ZvS>qNcٲFi"2##NmO 酱٣uUu˺w9V^֣GhqØݹxHyəmma/tj;Iؾ* HfjmJ\2E+aŪ΄LMuͭH*V'.Yxsٷ+,cJ.jG*$Ug&SmI&l(JL ""=3;NRZEfęTht-d[KMJOa őUjn3Nko[Thq )n+3#2^*DWriwG+[)ef1ȇ)MQ$3;#&}IT6*٪R{;0Y/1PgiaG4sKT7T5(ԸKԒ%mm{Rf[?YhnAhi&A$A85e$jhyOao{.zz]1C~.ĒJH#FUl4[en ,_QCaP~!mMn:$*lԓJv"n>", M3CW[lIŔD%IWrjR^G@)ꇣc5H4Wᙆ$QbD+m]uôi+#U)JI)485'Ef+i_an8wGJf-.B=Qv2Σ7 o{ɠ|gCè>JV#*#h6i%KiW~)Z>gMŐNTQFvKƔ{Vtoŭ0j OFX034ljTbZ%,fnoH 1VSjUb^3q"52 DHIVGkjv2ˈSLzQ43 R]G=Rܙ*F(єRJIJ̒3Q$g_UpfX-h4iΤM/H%jk,XtHm;OPl9 IJJBf\jlEXch8%=k3$EZfB 썞Tcpmbl]u4a|b\,sNCl#3/1@%O#SHԝhuCTiosq`::_ # -D$ZNm%echIXW B*NN\T܇ &I&$F*{{.zΐьʱ6ZxHb2 MȐj =Ziأ\DZV|V$Suh2'%m򒭆M%2xstى#bY<|LPiJQ)v{IMȭȈaDjf!PqM ĥRbDUSMqsBYRSdbVT+a\FkړW?nje> ۛ55avN'uŲ,%iK%&i#%$|um'iǃOuN'jCujFl6w*"2eOY_}kF.m5,ݧ%1I2AXوJ hv KJ*z}/$-MmVV}*zTL&2[!*$m!hM%_N-i(EJPqyYV4!FeQ¬u3H8/ `"ڞ.[88mvQiSp2 T9S'½2~ T"Z6iV$= [mḕ&)xk}u |z=o/CgB$q:4oJp :4oJp%s_@$_@?Rx:瞶b PzBڅ jZX[PB 1o Pbɺ)1H#:aIOTbRbQG(7PǡM3!F?z$"Hw}SF }SF B\;ۜ"I;ۜ#-Gyf(P 7Z-\Pu +PC+1ABR3&JEB΋܌3B|__sϒ7ƭ|_5k]oZhjE>FXկ|__]oZhjE>FXկ|__]oZhjE>FXկ|__]oZhjE>FXկ|__]oZhjE>FXկ|__]oZhjE>FXկ|__]oZhjE>FXկ|__wG8GR1m[ԪT!-%f/)(M$ðsj~~ɊZoV/~ѹ4@,,:N yK*Kn]Ēg5-\<&&PbJ%V9\$VciJcͳ/]S1g-7_ Zh2tO/U'T)tJl 0!JK=RHDy45/F{6*gmgyj7LKdy9Gsp6tUM=_ ZhښьLglxbEHֵ_"oVRDG}=+h0$|mfuzjnw#RR3AeB̮Mw64s9e5k_M:Ǵ?|/5cG#'&-Ӝa_aw_S֪Xp\Fm,ҡz*ɖwVYipVVc֜MZhjFZ\"Ɩjۿ՞Mg_ Zh cj#f J$R(¤[.WMЎ!9t\ƇZYe%wm˄K__ 5|_5kkg rS2J]Yғ5 xwf N6fK^ 8]63ZV/~|_>H+~կk|_ Zhֻ+~կk|_ Zhֻ+~կk|_ Zhֻ+~կk|_ Zhֻ+~կk|_ Zhֻ+~կk|_ Zhֻ+~կk|_ Zhֻ+~կk|Es-}{1`gfS9y?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFuHV(9~SM<BkcZ#Mm0֧Êepjx50F\oZ# jx8>ѶW[֧Z#*ϴm0֧Êepjx50F\oZ# jx8>ѶW[֧Z#*ϴm0֧Êepjx50F\oZ# jx8>ѶW[֧Z#*ϴm0֧Êepjx50F\7;;Ѥܻ娉7VmN,̽LֽO >(,4ZEJ! =%YPTy/6QJz!h&coJYò1DW[4޵E֯PӖHSsȟ)T; m.TCju8)=hs"e qkSc6vE?1=C8ÝM1:DxԷs{d&fl?r|">q;et+R0u&RVI(##3+XZ5[0֧5nH>G>mK` ETz댦&U*U˦8EѼcغCO4xTVU&"A-jx51Hk~sbu>6cc4f"̇Hi բ @e5+K;cɱer3V[ ٯSaOۑ_c?cT]&`<%Xm4}1[NFKfQq':LǕJk4 jdOѪqKCmJ2PyUݗZ:XMtpGjL4^S )"QTEw$fDJQmZE6ߺD9w<:d >ѱoRzP"4!)J<$ǐGL7tǥJ ~,tԱ=2Óݐd:3mKΞgm0֧ŦթsՏQLi-|a)xj\!=uek]e%wlf\DV#%;գc9!2٨VVG8Z# jxDس4o<9Ά.rf>䇕_Bn(w7!j3smP*8**hFX Wi}Rd=݌#I=OzSx`te׏>zS!. m$ mY<(Vb-OE .([P:څ P}xPbMաIEF)e;203+^3 VR</o蘜ESrZ η 2X [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQPzXuGO_e@+?~"a?~ [ETbޣ'/oQP4jlZ;#rVu.π$L?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFu#^@ΐe+TcSSQTdIjA!%|Z{W323VLҦF9 9ԃ%BJд&pgdg= sSԊc$I:VV(_a^0Y=haCMbTEmo2FٕR[;mv3-Ge@Gex:ur1.Ib*UU}2lBoW؈HFƋܗNnKTl-Se4Gܸ~dcGe.z<s,taGe.z<s,taGe.z<s,taGe.z<s,taGe.z<s,taGe.z<s,taGe.z<s,taGe.z<s,taGe.z<s,taGe.z<s,taGe.z<s,taGe.z<s,tcFa,Vh2chI;jQض\qOaBL=[ǬvGNm+sôS֧Rd KSffRi3#2#ˈEQ#=:sLe6n;jp̒JZ$fdFvǏ/@)ez͸RNͨ{8Hxrt1<6c\Ts2e-N8߀s A2ʞrZK2I-2o%iRLHd{H Vt1竒RÇ̌ .htDҙm. 413KJ$YiM>=2!=şI %SLHhQ=i0:2ǟTѽ)<0:2ǟTѽ)}6N}6K{,zي+1B j-ajamB (Pž(1APc&Ф"뉆G ;z&'" ր U*aa(Ԝ9Kn(䔩%ew%u _iI*QLDζ4ERΒ6V JhԦKb"5qy-,YPFvIY)+qٵj"GS%iRlEdVJv ShFYµ1R-n7eMH7Z"64ʨKİDMYJO`wEJ6#xt)GZ]+5zZMR:q4Vl[ f3[΄yL=%kmBL_DU;O SU+]~;R6ԆI#̒;RR[vR]!~*5knwӘWxf͂U4)MU'7OpiGhC*\z%-Mk5dAp%7Iw ePp")KNUl0dg[h-(ٷi(xdJXF]yeЍJSv+)+2DΦXѝ.tk$wM-3*M&[ + to JK#qBC:|dIf=Ĉ?U ӏ lje>6J& DQ$RȌ%K]YU+^’[U\dh^QbgrS= `P'S,hu5M=7$τI&{fTY3eFzsm͗ ֎IpTfec$k,6%a1\LV=B_v2K4kunddN#I3ӈo\r']gW{>ln&ٖFܔ-Ǹ-ͺw^sM~]kg7~,o>뇮-L㸑rusZ-pR 9I:8YҜ=Y[CBPf}-%6jBmC3DM`WRQa:ҖKs$6ZjЋ6;/iGޒEaH񨔦*Bd8\Jp1z}%21C,TDJqD+Ȋ=bcvGn9|?a#L7dɆ0Xݑs&?d_cvGn9|?a#L7dɆ0Xݑs&?d_cvGn9|?a#L}SxMhw1]SxMhVgz3oIM.OjWUAMHLKD ʹFl}UZ4-T7N}zb%.+gt_)r]J e܌U#r6&,FO˨"3,L`䤥%qV3N'#,ʒtT*yq mL?!HZoփ;)GsժTΦNksfg%3>G-L:d.ld}T ˀě]iq*(fw<:iMԔ/iS4ȸ#E;_11 ~Dj+72KR-jq222JEc>1Cn; [M!N88%sQkw[uF2Vk٪Lťi-4C$UJ,j_|bOF,I0juMMM0&zPu5hZ6ӕf]ٞm,=eַV?Ug]ڒXiܩ)2۰F\?*{a" SgEnCPĚ{n6Z$&D)JQi G;TKH,Iԑ!=iqڊIܒMYShQWR(mn%$Fȳ(˾v"+"!~}2R: 1$+KwH-cI,ٴǭGa$4i$!FT$V"""D]7dɆ0Xݑs&?d_cvGn9|qgVٛ?duk+JGr:3f\: U*2I4*4蕇 MXkObQ[8W=$j$(W+\`*xا#S #㲐eqֲ"rO(ʕ5k>65}HdțA$KZ!aQ13UQ**Kt=[T2Plk*$8qZwVJ/ei2j8S&==4NCP 4k,ktq[ýmý{W/"4lQ,YO\D؆0kMg`%X]>"MLX0b"RT m4yFF8ӪD#v@M^״LSjZ/sU2f#Uf1OP4`gŚ.0C]z6!{qJtΥM?[r:חYY,aSgc~fܿ蛟tnVVMv[[gwysw\;G=sQ/gGe?!P7#H+}fmMh;ZndcxwmDPr#+=/H4]&ᓁ$ԡj*PȌtVM>]fKpKp3[)fOi/=7VtȖDuc2fUa'$oM;ZN/JAs/rUYn?dsU q2fUe2BiKNşIπ$L?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFu#^@ΐ{U3Vv1sյ-L(q[{Ll#7a.Ք#{7ɦзdte")n$iܯr3#Yz MiȦOLƐfɛQ-%etQrGm2@AL1b8s iLWʣRVjZ.!w7,IXDU"2~;܆\efIZM&iRIIUi22>&zKB~{1ȮNYIiHd%:h+W3;0hrhR!`5&3浒Ve""Q$ꯘmE\0`$A2:odf[i%/g7F]:0`XyY]M LN4$!mm6c0CݳFr#+ȌFGΡ4-"]KfΡ,V%$ړ]V4^ Ci1i.O" i`Adn:v$߀ɻX~,cIeefۍJ;Li f.ŦVwʟOCQqM*.INsaZ3Kj:0 dIfCqZJum$aƷPf(r$vTږzĕ{Kq <**D\e0+J9k$ f̛38VreGZ4n'Dg{&Ri5%NLJ+ƛZZlCFQ滉24[8-TZ ^*;yO%|V.mvl=~7 z=n㑸uڍӪ=Vٲf[m{mfG6zSRw5dwgyn5"\\zl$ j[B z!}dw3l`J}b%%5!dZ2˕W?te7".KwӈiklRFB 0ff9 D9M;/m7"2LdA L)TS Rf .iK&Q,Y1c+ &' IM~GW9t[-e ęf)Wgr"w1ғR)#5؈iF]D:!⭴+3"#Zd4ڔZ'uR^RVUI;d[`aj-@*.3yf{d+B3d.300(ip:D6=MEbqE.Q8Ժ|ϒsqSFvu2AM5L*|'"dDCX[]*RK=Tgq4\&Ħ&$"%Qe>6ԓ%k[]VX%)3{91/Eq:pֆ+IQ)/67L8ɱou /WP& TUXDU M-)}hwT=2'IW$XZ7rDH1_!òeZm0\ K:+d6v[O6hZ~=$BSCaK5^KI(e2S "oP=([PmC P j(VB-A Vb7V&)g\L28_k19l/o蘜O<%a Z<3Ggy>IȲ=[.V33; 5)M@8)uLɼkp}b\VZa%,HSPp#εʹuslFEy+Xu**L!CKNDJ:kbM= nL(ҩPuNլSmRڣBl$-"C7<2lH!&mٙSde%&FFFFEc1AQY9LZa98HJ%FfJ^͇m7. DjDUa=YRK$SV5̌(#l 7*5=3)T JR5p4-&dg*Wi/ΗZr"KZAn5K&e6A$ԒWrob-3ETXQpcƒN1:JA)Ju-Y DFE_Am.I9Oe22V|QUWBI);#Z8n~"Sש[R '$%;(Zr-YvHqa)-hR$dFyTIw#lP#Ա܁N.]Ocqj2"3Tدc¶Cυ ѫGV-ًL-jPDY)gkXld~u_un!Y60CZ%InB!M$,b%[R~lXrF&w4(P0l4DԚԤԷ ȈZ"5|*E2ԃI8s3NJ7&<\f@ @+;u;E= %7͓6RIiJ4Q#W#珈[fR'I)RRH2vA8f%_7ulHՓc Lt5T*Jr(I"['ω67pOV)S݌lHAkj$s= Ee]G!TV]B% ͫQ!YOaqUT$aL>w22Lt)Ѷf3.ī"@#?NRbFi,iJv9\DVQ%V33,U^zR#ɇCL%M½JBVV ֎FU&1 I')5؋{kτ4Hs[[2┥Ȯv+cYScǗCz$ͷSr2S%L/Nz\vBD;=SIj5\=3UB`CSilNKw"Qj2"=Sbͅo &8&yۍq +) .2byQ12SbBNR 4i<)JQ)3""\SBjICR)oKA!KZUD(#<8 G6SfɧObt75rY-X\d{ Q1̀1tdw\'_L4,+kRvJl $ U9&JRJ)3bƳBMDgsJRgs1&(_;ujOrwn3X†.PaȪj&kYn)W =b.3KU> b^(m$lϫ5$h3[vf>|frk3UF9=x^ Tz}AǙ[$rȅ$GJ̌[-cp MbpCu yrpNW%6#;fD`H V,O@|'zd @7PǡM3!F?z$"Hw}SF }SF B\;ۜ"I;ۜ#-Gyf(P 7Z-\Pu +PC+1ABR3&/oؐi涧܄)0ə؏a8/o蘜O2{QT;DCx?*h(*ʦy'JQT;DF0dv҆T~U3:Q=gtULNcOx?*h(oGS<*ʦy'JQT;DF0dv҆T~U3:Q=gtULNcOx?*h(oGS<*ʦy'JQT;DF0dv҆T~U3:Q=gtULNcOx?*h(oGS<*ʦy'JQT;DF0dv҆T~U3:Q=gtULNcOx?*h(oGS<*ʦy'JQT;DF0dv҆T~U3:Q=gtULNcOx?*h(oGS<*ʦy'JQT;DF0dv҆T~U3:Q=gtULNcOx?*h(oGS<*ʦy'JQT;DF0dv҆T~U3:Q=gtULNcOx?*h(oGS<*ʦy'JQT;DF0dv҆T~U3:Q=gtULNcOx?*h(oGS<*ʦy'JQT;DF0dv҆T~U3:Q=gtULNcOx?*h(oGS<*ʦy'JQT;DF0dv҆T~U3:Q=gtULNcOx?*h(oGS<*ʦy'JQT;DF0dv҆T~U3:Q,yPR))iF'FJ; ;\GƺY鐁]B_4DK萉!c/yMҜ3/yMҜ) p\xW#np$xW#npԷ>2ι筘B(|ޡjz(PqBچ(PB [ƒ2n LR*1Hθdpñעcf0!TMPa+4)رu$"3IH2;~ ;z&76n& (T=THYm /rJ\Y_=O6hZ~=? ~t!EoY"Ci\2v"-cz$jCpKM$A.eGt3tn&*EVEYTH 3mIYȳ]IQ)9H.BHiSiecUGLr+,im jVIQREҩTע,ݦԨR9\e,y㝳*3V.W<7imLEܡ$*aD204U)$RH Ȕ2f.2$?Z}P BtڋurHZY%N4wiQ,bB;oadNz ܐJ#<é+D\M}FD*s-*]23=orf ӗ)eS!0Ҋcm,&܈f}U!:ڮјī- ?*]DʣIp72Dt] rJڐ^[zĶq̪4ܳ+ѝx>SEKz$Ǻ7UKI0؊[җW" l5dܺȖvY|IwD`g6)W*WS*U)[ PWmc#+'tݻ3;UDeI)PzKN#RHgAɦ_'J?"D,bg~ǦB' ?c!=i 7PǡM3!CF_x78gF_x78RFIFoz<|es[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}D͍Y60CZ%InB!M$,b%[R~meִ2n X^^idΝ3ʤñm\LTz{Jf%S ZH ge*ƻ_lpl%_8tt{ iТY8V#;bXK^S\C,fkIp<u4̚4z{}(ڦM $pFv;ʺ>"rMDiTʃm>ck^ť Q*6%s0)>'9F֎zaK lEG(i6ذ[޽ gtnW]^{ôx"}ьrq̓;ٸˆ QmJ |4PDIʄ&\}#pE2jQDeaꊙ-ԅ-K-lFFȝ=5LJDfs Jf[i;xqg}+r|n;;[_tbMumdk(T"U4uGwz/)y{ HƪnU~ьn(ΐ15 E_t9u6*i2G"0,bg~ǦB' ?c!=i 7PǡM3!CF_x78gF_x78RFIFoz<|es[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}DAñעbr25>zşIπ$L?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFu#^@ΐ{4ȫi=*3$j;، l:CZE[ӿ c<.%CJ&S0B7[I$쬩"̵*dGc'?ðNV'vx:9RԸU::ҕ))*tTʌ˹Re$fV;ˀ̏%ܚ0#LBˠě:J-ָ[2"$f{P2?ðNV'] Iz)wj59 SfބM>Jy 3o+igrIެ7^+MTm80݌k"#ʦJV{HFFjw0?{?)[ʦj4- qRTGc##F!0jcO ƕ4vS9oB T|lDf"RNUs N?!%!hJ"2;[ :/iF:qmn:KKމ ̆܎lЫpdI[^DP V,O@|'zd @7PǡM3!F?z$"Hw}SF }SF B\;ۜ"I;ۜ#-Gyf(P 7Z-\Pu +PC+1ABR3&/o蘜6v7Lm;ڭzM> B!r4Jm;3\Ie˗mDfgU8xFgV),SYAajB]m&(GsrԔcRT/EBS$S7M{ ՗eW2.3|3J="Lviz#jvKhaFPtErQԢhꔹI鎦f"GjٸσI'ל)* fcR2*2 00@=(~&|\{sc-iSsd#".phͅ2 ›Di(2%2fϼi='" c0JSz%HQIgJupS,OM7Pɭ L.Jn[QNĒul=]a(IBZV'g [%3+F},<=_á!+[+j"֗&EfDf\~TbIlơ8[ &"Q+EMtb3}kGZEs( $-n,ŴUiR[RfdDs [*B)k:Jۮ]a_t[ӿ cnAЪxƱj*8 ;%eb+mZLLELE6[jD ˘Q)ѝƻ]fz.ed|h.CQE"1nRekEȌ2prr12a1-1P֨+2Y7 O`ʭi6,ձ{2(͹"JT lIx}lXtyB s}q$}]-Vc˃ Xj.d<<]2zfbi$F(ҽZ II.NTq)2B!feoʐ8\i3==tJ1ӥ-B+2ʨK15}LFşIπ$L?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFu# GJR{Ѕ!;MVq?_+l/o蘜}TfpdyUjݥ%dzmFgF 23DBzmk$%xgrjkx6Ѵ(l]t5&Uru=MoVY<;jjf*&؇.dabehз:9I"=z4[ U.[EP3!nYZd2dD(m3 è7IV#U0i5:Rq%+.4N'JJ]Ee*T!dY/Xے GbLπB@6:n1)MZyfl$KB,4FGjI MK3ϩ4U?CН~QGVelsQy&7[2PS.%CУ#آ#a46iX3aRnye2?ڢ"3#Qlnۭ 8%Z֣$ԙD£)M˺N"\$eNb#lW"!8 x!q49"d-{#[Ij\ō^FDrkҍ4뺸k)mQ.miQ`'#hXUJi\)6-J-σajIj#-Dah3i4,E-GBe6x%5O3J|CVJ&Ųizc–9 CAY2ʬ&yvȄ\Z#HhKI&Jo7ˆRgkXԤ˄[8 k=tX욾#ѰF:޻{TfOjI&tJʛ{z,]^8_Ux}M/Sr"1Uŧ7?ѣFrb#3Sg{\gn_(,;QjN&]-fRHjFdQ~)V4<bU](Q8&Y9-(5e#̤[Gkmz7 :_i~dXl'%9V-,>k2u)DtI22;(wrS+05…K"\kj"IڐKRJFǙhg~]#oF#Y8Ѽ|qK=ifٓF-^v*2eϷ6Vwi۟T4#|V VOג_:rm[dKn9!D pXcTqr liU2Yi\GRvѿ'?ލ9GHDnO ߓdtoF#((+D;/l;z7 jiB"CESC$qֳԵ+yuWinfeLaK=խf|O]kqq\C4221 #E im2\"խf|yiz@JCVT@mKF[mjR5c^c4 M6*5T-i3Jr;<:dTO^Q!J3;xL̋mn"&:hÐ0zrKs֣fṿs @)-YE}my[d,V'npz!fƘ"jB3BYV 5lpjగva? 'z`l{X00va? 'z`l{X00va? 'z`lu9ΛnͲG^ HJQ-|+؝pNXZkh2tOoP=([PmC P j(VB-A Vb7V&)g\L28_k19l/o蘜O<E;_;[JxVn'v%c3n$ҕMv2+eGIB˂㈴y6;$,T)dBH%!I#2IeF;;#E<'ZԊDul3V膳k#)uI+PI9kUmJ w0k` |GG\ڂN$I#}>Ju$Ėew)'=vhd@)8%~C::K!WircЇ'Q "sO]o-G9vѩTSNq=pMU-= qJ!֙$Ȳ7fj%!vhdARq`l+"&R=e&['*sPcaJ7VfqIs*/ey']g'L(LU"}/?`qv"PĒw,$Q::̈N4c"2\fCOuc4YeI-%dUb4SO2z O)?m ARSaj+j6u[Ҵ˄Rq }]z/ߘΙ-[ "Y֥wR}Ҍ""KˈSV,^Vȥ>C Rvm&al+?[^k:%ReҪzpǒKeL5eEc/.!i[ ݱ!q8NM>'mD@B_@ʁJj ЋYxe_jg굳*_Aʣ1KYuFQ-h5$Ȕẹ+0]xԬ'%CM'&ӉHi%^W}F,KE2Mq__;)O6,豨Y 6qUT:dƨАMBK1%+J23;Gs/SMr eHfqI5-q]FlS,:tSq 1ɓ eI#&Qܪ#DGz*("ɠEz&AӷVd.ِ^̄dw%A"Jy/JڜRL4+\b+*~{6ɩ˓.M^_q^.e(w*2QSKN%In7ZJCJ]#5ڭ:%I䚣8r;dNfɸ ٤#I$fW=iy+mv2Q fX[ :Xe[C&!Hi4ujնW;!6Iq5qJRH2ͽ!'CRYSXHt2%sbu-zU"U/ Ús}r;mr鳝LS4:S4Z[b#97Mgu(ԣ5(JQ~vp$d\r)ZlCqJyDNgEFi^;%h=>KM7Pkۊi"#[jZLU;Rd{ tS\j,Ym0insRMm3>iShb,S1-Rua2PI p3xԵMпEqPMι^RrLk)j$(m6ו1t_Ժh<2Rғ6#:<RK/PK+qG^#sRg52mFf;Oi߇h~SB4qݮUe,r:5ZfuKi-BMGgu/VQv iMէ:tiESaQu& ZL3L+4dԵk2f1Vg5-)f9ZS~`%6=ثmȞӑ>8kKΧ\ZD AS¸b\TI"nfuJ,)f[\yR6<Ɣj|,8XpފRitθ9%Nv3A% Q&Q$$vn!ɑW"C,҃ZTEu)(^sJ]VDZ椬7T!RPr8V;<2L[pm%Z#Apm݂ty;V􂒺"l?ю╉(qkj5ĠjDI̒FdfN dDf=u4p̌̌8Gn?wя=KC8LQ ;]RH jڤ^>MUDU6z[KQ) RT*-WIHȼAHQ^MBR3mdJB7#"2)ÍGKZ3Lٲ2IY&πI[ӿ bJ{jw0?e+=٢:Ԏ#Wi1NS)(6V5؎.EHTitJAhiI*)Y\HW'H~+Wч`!^NF;&>;<2L[pm%Z#ApmXn!ڋf \J֤Ithl$fFdfDF`8{NV':CZw:CZ;jw1;jw0?{=iN2?v .1ژfmrR [&%$vJFRFgDf5 Fƕ0$R鶈2IBxT)JKies2.":.vǥ U|Y#5+Xd"%6&,4lAǼľu_}պ늪-kZJRLϾ4Gv^1]~ lr$hQ-oP߾?{ۍFhZLT$’fّ5Ҋė"҉ 2M1%f^ȠrYZ(uQ-쎟J+t4Ԫ~"ݫT)aُq%<–Aɜd$c3<|q$CvkUiU|Im1LZVm>6ErrYZ( r/XX),?d&rB lղEl#rz_juVsh:[?$DEu)3D<utp [du1_y)hQ$̮dGk*v6c:6at3:հ {*RkI(id^;FRz:M:4Rr3ĕ\hQV>\yJ8빟F kڅעEe9ZeB J/AǼľu%샏yo|q-/_hI{ [_:d{|KW^8ίAhFX&]f:!& yd32I3;~Y鐁 ,O@oe8!4Y(DDcC ԕNմ>,nG\mv[fWJdG;}^3ϧ1z+y͎a:ک4k) ##wD`Ca4썃;;T*e+_n(˟.lW๊B_>g{¾sI'{¾s~u=l C SB 0Bj(b^f(1uhRbQFu#b9/#Uԅ)6TT4)iY+=V24Xñעbr1Qׄ1[5~ԉ0%ʋ^:s"'P3,Hvh]ndvt-ݍz mJ32ՙ-MV-pmd.Ds`jiKSbt4mJq"$+Ia;M\gā><.eZRYIĥW+m"\[F)V`Tqyaf]%&f{nYlV-}'NߨmMRK޻ ɳIjTfI5ȣ`'{WD^ ͪlkԌ>-O6Rec4Em)XE8-j[KdmRh\f{pMR3YD2 QlBRgQ#W}-2j*y/4KvFStf %1f#lmj6.WJDC2iԴVRJ dgId;BӠU}JKQ]M底6Kʿ(*l.Ph1QU%bRr̚\Zt%Li7I#j&s3k"+2W`7D˩LjrYۈuFCN؍QL"޹|hbJaHPֶ`ygx*%n%#ɛjk&ZE:^P@P*UJV嵩ɝ)ISH O6)2$Cr_A&'űa~,Yt4мȌR dJ4wW2+U`MJ>(Ԧɛ!EcvCqf_Jyb 6ۺw,}TIUZR)HTQȑb;;=+;]#Uv*6•5dJ{xlcm$r#t6WE*Vj1YzMfݞ -BEIR+zd`O0:$%E[:K9ҖhFo* 6[=PHGRG"?z@h Wi\T>*lC' jWl)V2&l;l1=9ԺuQNz?-ϧ+Eh5|H|WW# nJ4^lDכ%*;V̟v={)]iP}OS+Lu4: F9 N;.yTݎcppГhKdep]#Uv*6•5dJ{xlb}TIUZR)HTQȑb;;=+pgKU8cG³0Zͺ!=qA[)Z,1,V]9Ժȏޥ0V'LwʢM-%iK}F#Vdl7-2qMT@}J7*UgOȮ&SdL1bvݶ9Ժȏޥi#KI]ʽ9Ժȏޥi#KI]ЄB4JVHą. u*+fe-^J3 d"[I4zJHlEssC{%?a{Iߋ}͍2'WiMSoxFDn㓟\hQRHƓX@E}c) *YfL݆p26,4R8B:Z-/-U7dLR#-Z}mwȏޥژz$:%aóq9K/5wQ)iR WP+Eð΀i1xB,tЄYVXvHGRG"?z@|h Tk'ȕ}٨0HadGTu.׽`/i#Kg Ft 7./Rq6CAju:ffaآ[HȌV22###"1qBo CјiZA! 7$)-DDDD\C?<>(ֵ>)Fw334m3>)L[8ZVyTK\i[G}w3ub⚵sQk9j5bkخ1bg~ǦB' ?c!z~7[}U D![DG_0uLsN}6N}6K{,zي+1B j-ajamB (Pž(1APc&Ф"뉆G ;z&6}&N| \=iN,ҔJIŘ ;z&6%p^PU56-HS227*wIu~'mfGl}W-Jsة3 A}/ި!MM:Huj2;\+M[m0aWcR(RPSPó:i}2JI'7v_;x*eTcZ!N8մӘ3_i.3 ͨ;V!$N>j4SqK>f5> Jr!PtF Ń%nGu6jAuinYmrQ0 $,}JNGyVuH횐ITRܻƓ22 \Q]^N:ɕo$츍ܓZ2OTlV/bffđXPWVz$Me'V2 \^fnU-u8FNךI Ss!DɖղCAi!N-+g!D$ҬEn_CfW+u9H92]PSM>H%BUv[u:ʴdlYYtf31T)S2T߅%)3I 4C6jUn) >%Rr`%kv2d^8UtJJH-eV!f=$5Ru.IYH=',ҕ*2# k ٕZoGU9uHujIgfP iA.A۩3o_rfhmVrKʒ/8,qjRgsQ33ql\E;_;twMTTcĆs$&,b~{Q]efI3$ӱowN%& v&ز pFP2RLDdFJ-dF[H@/4Nj 2o4`N-4IsJMl#ZoBc/{c]Uk8umRW#P3vq_"ԃJZE3`7& UJ T9:qdʫɨ4KeO(WvV$%Bp6c/{aוYVc\&bCMjM).MJQL(lYDs2#I06c/{azftǫ&aO VI"V\WI'0"oc/{bVu|Oly?FSIa1C#_uCXifipM2lYӉ㇟""Qح{mk[oJ:0*"l9 N*"TJi);-ؒ{2l2sٍ#Bu^*c$IjCVDfdIQf{8~KfTY˫ϑ5J앩'r4cVF!fYQջ-2'NKc.; X23czB֖#>YRjYӉe&B_12jߊBuI[/kgkدn"Fo\ݩnW|3f[}ƪFiORZLnK&\VHS>;L$pm:Q"U2Bk}8ZXӎi>1K$߽m4Ā $ѓi!ZMKhIj"dDW2f;˲9k|Ƕ>tݔ4y\;F=졣Z:1폝 e rяl;(hwΌ{cH-CGþtc 7nEZCkvHfs)Es\τ`u~uCXFe칔^+(VpнQu]2Wg'Fϫ~3q;DvRLde|}F0bUqMv&NԚOa>vP-pò9k|Ƕ>tݔ4y\;F=졣Z:1폝 e rяl;(hwΌ{cH-CGþtc<>vP-pǃ+?G/aELRL)5vR캍*3^"k\K쮼dEv?haj#TxH}12xVD[`M:8暹e}Զ+FDEs-b6P-CGþtc<>vP-pò9k|Ƕ>tݔ4y\;F=졣Z:1폝 ݹiӱۈjE"̤Vױp^>Z?L4h^˙M좲Ek qDN[.JdӢ;ou.6GhI1Y鐁 ,O@n_V5_oH=vp!-)S/ӽ_@$_@?Rx:瞶b PzBڅ jZX[PB 1o Pbɺ)1H#:a^ȃac}Ddj|H>=2!=şI k񾿪4=FI'$%:?#ew+8Dw+8G[ޏg\PY>oP=([PmC P j(VB-A Vb7V&)g\L28_k1hjVQteaɩGj#RJ-'V>U[ñעcrnj&Dc} W*_QU3aN }ͻ1sFQiro\8Pn&V4alj}.i`4 /CPZU5$:NPT#*RّfQwWktT*~[,yر鴹I›I%85se2"3=C˸k] QaShǩj'Fcq7)$cF̶0g-4 =Fv>!ڋ.?!pۑ^[dg#$ WM 2d|؀9&!*JTA(gEI#41^!PAs5CNPH&HɓvDݽɬӷn!u*^,vHW5#:͒m LR;ebn†PcamG|)MtEP堐PmH]x#S)sbL.<5ZuOF+I8H-rȈKN\ĢnFWrwNX&OqDDte)YԔ'7ukĐO m*%2;У>;%O6,豨Y >&XIKܬ seAK- 0ıV)Y=w@(КKTѓ"թe(QlBaa;ળrW83܍lgd+N"#EǴGd7V&캇T)l +2̋k#"2ح;tXU3 agcQh4՜YK6IBn"Yܮgcq:CPjKSi ȊcId:nkϷVZA ;vh62YPɑGX00нL_4ӻS>aknV;ͬϷ5 'ῄLO ޘIN3Ң׳*;TynN[WTIVU#IXW5+JJLCfKK ug^%FRM,ADFDvȫ2˲6cq*yd,Ђ7dBbd 'ῄL2kWSGEEVzl(q2c%Tm.-K4fc$*Wp3҄=m!QBw Z(=jr2ё Ԓv5va? 'z``~N_t%zeF/rb(":3P Zm8iZ[;]ʜ+[<0g'1e Lxm؎* ңqn(K*#NTME*f 'ῄLO ޘoX00va? 'za@`~N7 o;kwh. 'ῄLO ޘoWɃ"u*q%)q&e}G;뱎7O/C V,O@|'zd @7OQ@to$;8Ktu?iFIFoz<|es[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}Db= jdIiK5bL4+YJJ^C9Cl>]R{,f+ùlU] Kk#<^qԤGay fu).bcb^k\LXd̹m#UqN,WSķ"+O 2"5 J+amMaq P ecbtURt4M\K6oty H5j4WR`Hu/:͊Zx}v;  @1Rv:kKrb5!(Q؍I'Jұ.!v*“PHvbْn+5ZXiq-:$y5 Oܢgƣ;2}nᰅLdHOUVQQY}NmLIne*5kRgV곩2qBKRFȎGx1!QTflFOG"&5HKIdD(+-q La1 R|tfJ̢$F2YD{HgqzNc4fdޔnqӴu٧7"1]eixg{!VGIUWZ[iLF#$ԒQ[^ݸUfJbUi?-siVSݨN\yG[Vf'Utj̍uܾՙMw6N7T-ueRfJu+Vme=RFk yO1!i.4N$Х$WE2}c {liŽHY(%4&EA lNM5O"GԔF B֕VZI2m{nGhUis{w1q)*[ {2R*[L*4I:)yDgQI]>_׻ՑN_۟&_pZxn&2;A8U'Ft*E"UZIF#P[j;\s*(IJi".))jcTT13Re!eCBTEi(UeOne#aK9sZ̘A RVfZQ+*ɕܶS|UYUVݜ:R I!&(,uh.:Պ(iJm&^R 'MVqMaz]U>|r 1))+MD[FlB4qچP۔@(s]ĕ) Q+e&PVeydfQXՕ)etuXƘ^^/yY7>C-JI'1n8P\)3Rlw"DR5ȴG>\g0Ewm2kEc;mMR[KuF!jJdw7;O$$gb<25.(-RJdT$5!qkKm%%Qc%SN#oԤf2yp&d(m(8v=HEtQ*&5V)p6N28GHGi}m"GFW7)%>DGmjB\+IP:=IøUl 1R$˒Rio]!q(#ې7b l%T=`ڞ-VTe))K2i;(/a$ P5 0r[ܱ]DZ,Jmm/*bF6”!Pfo ͐iȩ^W4q&o9t(vv"aZ(j:KJQFudոh\Vwb5زa?J'c SN@VklrCtQ(Cm64k^v1jPb* 2(K2 im&Tn<)]Ջa; H4S$ʾ)q)I6%į[dG%r*ٰ7Lj[I}Nrm$ΔSYy4g#A##R@JW0UHTge$4(;%Fhr"3 KfOq#=)YTiRT)*####"22;p֏j EŤIRuDa,PYt"RPE˴sm+|# ^oAp{\l8!NdADe{L`XҖNnӳʣƫYj'#; YfdVJL;gcV-ï4([͸*NMJJHҶM\Es4k[KJ>.MlN*o^74gMD3"bI*sTj)DSindjo4w2_Q"<s1j$:uJ:wO HSj:K̬(KDlXIҰi2CpP[ql$jJBdjQrWQ1T]u=OC%P ĒQlgd^KG')U0KqrHiRȋfGZ"A\̶sG2zDqcoM5-Q!lREjv+IaYtJa5%NjL^){s4f#,u}04øtyRU2.K& ]a.ɼy Qfլk؈Fp5 ^'x=2tceFe4)qfwVTlEmOC(!OAe4JkeRִB5-z ̒DY<)3o>T_ԇY鐁 ,O@n_V5_oH=vp!-)S/ӽ_@$_@?Rx:瞶b PzBڅ jZX[PB 1o Pbɺ)1H#:a^ȃac}Ddj|H7:ՂZ 8MR͢NwH%ܓ;[.}%ȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0ӝ9Ժȏޥc9i#KI]0NuX IB9+|'zd B{>=2 }U iaz~7[OB%:?HKtu? GT4xW#np$xW#npԷ>2ι筘B(|ޡjz(PqBچ(PB [ƒ2n LR*1Hθdpñעbr _k19=hyuWTJ5*uJBn)dF$dWdK#d;cDb_5?쀍>ǼľjO #@$"1/C=F%S /c{ȌK}y|4K#d;cDb_5?쀍>ǼľjO #@$"1/ExĊx?ǎ㮻My(mW5(6""+ V,O@|'zd @7OQ@to$;8Ktu?iFIFoz<|es[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}DAñעbr25>z̴V2;PLcvu|d{fH={<1V\yHz2&2q6;p- +W+5SϠWM*BT]LyD_nΡ&ΒO:c#mYsэP+euɞFK ưԇT-u'ɔ5$Y Ur |m5/*- 4q))H&j||Vsk>3C`88.m.ҫ#3єՕ)?s,Ѱt*FY;Sjn#pԹRLUG[":MdvJTDl6 N^Ʒynh?,}܉h׻m89EY,N,8TIzl yQ[*s! "42TD[eLO?p=iq>=2!=şI k񾿪4=FI'$%:?#ew+8Dw+8G[ޏg\PY>oP=([PmC P j(VB-A Vb7V&)g\L28_k19l/o蘜O<v\jjmidڔkuNdd|]Y g'/D9LvrفIM;鶔␛jV7;\m޾O^r2O^;?a>/z!``˫;?a>/z )m.胳 O^r2O^F1TwuO-5}ˇFڒEE\7vY鐁 ,O@n_V5_oH=vp!-)S/ӽ_@$_@?Rx:瞶b PzBڅ jZX[PB 1o Pbɺ)1H#:a^ȃac}Ddj|H>=2!=şI k񾿪4=FI'$%:?#ew+8Dw+8G[ޏg\PY>oP=([PmC P j(VB-A Vb7V&)g\L28_k19l/o蘜O<{JXp>2+pYtpH'P\_ЅjL#g`Cao4Y#[,";tJ:"TZ!+'J"Q̯ônl臉)H֒) Ó"jZ'TLVnS1^ccu9 ϲg`Cao4숝]5_RiѓQ:Mk[ VH&&FHJUgu_ib}H֚ԙꓑ+lG!YCl-}:*v.e;VeʒܦUWx6>1hRT*ʬLS}m-YI ֥wZ,esLsNG!YCl-} \5V0}T*TSזΥ-"͗Vv֪UURƍR@fێ“9kSRXJ"iTd鋺-sG!YGuGe@+afݨH8SiM7$Y]͸LRqN'yxIÔ#f.IږE$jz֌k,LRVȔN"]٥$6לf/s;r ydq͓P‡ Sf"0llJRV" 5/HIFE͡-4BIS-R5XvL:G\}n߇vn-FI&\۟/{xظtz $d}%a#+:vHQ*q)Rn]Iw̃3a٧>UY*l%I5:T"fU}oOY5{r/W)S2T߅%)3I 4C6rSaC)kڮe%4!d^t%YLha&Q9}q):bMKm\Qmٚ{ms 0"ҢFntTI ˙'Y'iKmȎ']tN\WSL(܎ѴAfڕF1VdGASaǫhbcq\c֤Bօլ[JF$E+ =)'7F#I多]d;vgm,}1ݕ4g׈)c[TڍX̲+ gdn֮.6g !S4iEM>**UiKQmJMKR%b3b7HqFt:̳g0%hԒ5Yo/cQm [ɪCVqz[8W=$j$(W+\`@18FxI|ڍM[6\;_*on3V Ӛ&28tA2i BhRRITQp<6.ݭT0l:juKs;ojƜ;C= TŲՓ(.n a $ԓ{ﳍ6wLIfz Ɋji](& A85BDQpˀ&C@6vz>Gj..E1jN-d2UU!R7 &M;XLZ H<8itaTLrb$2.x, hI-ֳ̈"fW|y;VU^=>InʖnWI5eZe1L=sm/H#>Y-I>mF!Dw%2=J1,1$P7bIz;B54ITFJM˼dfF]1db _2d HS$#:ˆ2CտMc~j;2Lҽq\˾n218kw~Po|oYnͽ+_ӧMnm:drGtZnFGe̾p xdfD15kr[i&BVZWE~ӧMnm:drGtZnFGe̾pu)'CTc˒foԕLjQs۴y%̇[ۄB̖ܔJ-wqhexfF/Vpq;;.3Y Pj^d]wI#8@7 u7jת]n{xcy[Rֵ>3>`{i1'Bm򳩏!MĢI~5U5JzJct׬v"_Q2wJE)e.!gz}xo/o(vTz%ڔ)/XE>em#|c['PW.c$IjCVDfdD;qXU$7&T5rD83!uVWj֫:~鐷2f3ʗ*^uIyKDZ Y\ M]- \EWdhrb.):DW#+#+܈m^VW֠%+\M]\Ju 9ujҌRHOr".$[ 1>=2!=şI ;zn6^ϫ:LC">䒮#(oe8!4Y(DDb$nm/suC8y5q)t-kdDYHE׾mr"5hҳ([CEd5_#,[n\ <|sLCF2>w|a-.,.̺$2;qܧJyIK2]dSFfMbK{ϩ)hhx$athResRq[4U'kq!6c-j6(af~;9NLX5ygk$?P<3;WҖ$6.g1Vq\l5Tyœ,K+j+6H3+bIFY1-Olj ,zg&JYL7dkaK 9ĽҲ%m%9z߯ꟺvٯf6ҡC~d;L6ԥ(؈π]b:f|JVœ?RP7#8%IuŵC&RIy9RaFz^$ZR1;T$²̕<%Dn, i%ܭqIzݫꟹ;5k<72yobS{BqʤUw sGxd(Fo[mss[2CW2yoj;T5MM\2bڹMS>?;V1yoG޹??ɇ;EbN{;\ߞ=ݯ <7 MUOY~;y_烵~;y_'kw@4kw@4 ~;y_烵~;y_̍ :@29b |;V)^0yoHfG7Ra@\χj+5 Nf\JKtt-eTQ*<.Ia3i_'3=S.GyDaYjIYr[#̄l>CiRZLbi&LzsdDŦ[fGa$)X])DW;\kLJ["s2j(3(=7FeQJ :h;-=#H̓x -ѩgjf|kfx2\9D* =;)X=2J]g$jpHnl%g=]3tx$KiTTKK'1GnblJFGe+Mεj)F|&ff{Lfw?c!KPGqۦ4ZB>DQY,dG,C!O´8)L}k1SYuoilqDglSX/!ӪYGq !4QJl1RbQʊ2Dֵ-&N)dRH6"*S5VlRZLj©2NdMJtԂ5[@6ogҒqv(Nf&xVEf"eE40nk3"ٙgdlz7H~.*F'r/ kT~,"+[bRMi0۶gnٞ8c vS)I$LƵ )R"۰ U|J&].{DIlBF dwl 1j%YrSjj\[FD(д܏*Je22=5iO^T.m.|[or,Mbt¨K7T7)4ℙy4έ2KVЄ>mConٞ8afx[>S RmJmˆU֒J% {wavs&0UdkYzJ1Xg&fZn9wؓTJ=#hJ%U笿KLeVG #J;(ia YGۑT S@Zji8dq "QSMѕ}Œznٞ8c\ʪVUޚR  Q'2ob?t2Teb~ZiN\79$kSeܪGc/TV@r*_[d}166THDFFWm3 7l 1`%v 3 sv .sXN”DA*MgQY"%Pd X%k>ۯ~,to.jɪZٲu?/._h6og\Ex(Ϲ4 ]I75j_5 ؒآ6<>#c*b5ݳ&I'E%|U\F۔fQIadnOJb%H\УNT+FjRv 3 ixUdNT.DFa^v1 Vʒ5k A6ui+^u:M6HdB[Jl23,ٕ`nٞ8afx[TR;JVz_R{fDλdџF{v 3 k1tje9Ue5%c-M(ߝN-w7~٭^G-W7MN-rTӆ2qVT$Δ(Eܑ6*UX"CєMe4lvZW;\V1uv;OӒ\6JnRF}Bb$πiTC Q;iζk~gQJ6) KFetg̉ӣ):FQS glK 46%.2#;a` fx[o5F u20}Nf )K4$ѩWR x _U,Nm̊l4FJ4 J6;yȓ6ogӚ6xҵUTgǩ"W7SRNDhMiL}[!KJHR\#;l%Zr?QA".L)HTvZi[dM瘳f3+rI-';gnٞ8bdbF4[~IfS5ۺL r0kW[Xo6ev$m0Y%K4ʅ*Ifx[CjS3ZEGirZSeRN-+4ҮII܌cbo$ꘒL*쌚Imi5fj$AHDw^T7A4Pq2X4㦛pbmdGݳ<-pƣOͩIDT9+kz%⌵4j+IJagFxJ*GuX1ȻIMjJ,A6Q8V$ v 3 X _ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pv3 7l 1`_ݳ<-pǞUe&ܜDfjUϽ?F =1Pֲ[8% tw/!dX!)Z&jjWཋD:XƣS,6W{_h厯sI9_3w\jtr K%%O${hFo|gLڥ?v7j:)d$E5*JF)YP3; J\QoMG-v>y?:1xb;~:캍vEJY8팸3JJ"3J^ȉNQPBa%%*#%%vFFw#?{h<v>y?:0K/ԯ x>Xv>y?:0}Wt`12h:&6P=<,7Kfi$)VBP5*RFgaMAI[ L#9RIGIx ݏ{`N;h< ZUXrS)v[L2RFyL\sb8tjCO-$#u2w-LV2Z=O0g~'F{`N3P0ΤMB7Qr&43G b$(m "3W>>F74Zm,8iR h"ؕVܶF]{h<v>y?:0}x>X6N~5\b#6%hљXY)7.F[6}WtaG `[L7RvޔjȜii)bNҕ+r\Ȯv#=15J"!aaɑ%e%ըַ &FD)JQ {`N;h< uJChJQ$"."?wڕ8\}cG ñ_рְ -uy"!n˃Fyh/jfBH=uC*<ړ1;\JSmYR[s={`N;h<!bFbkҚ$#!mIl) [ $[-k[`GNbj#1)m,d[$X"5'FE{O=ÏIGC\'~cEkт[C}^3ϧ-Mӟ W㍩ }5fV%JM˄ͤekMmF-\.>r'1SL4oe& eɻ52{[e -\.74[] s *њ슾K-bJ)S&!+=yЕ}mW n]˞F]nYn 7nh[-\.74[] 1X^fgFKu$iBF,1vilRe,b*lRQk$YSohp0ߘot`6K![HeN!m3-Rk]*&,XQBSEh2lC4j2"ݽlb.!74[]oSohp075-0JJTFJK.쌌G{f7ڕ8\}cWSohp0ߘot`6Rg OZbmd:ĺkNIp}Hq 7VIJ J2G Mϼ.!74[]oSohp0װ겪ܩ-HZ5!DqTmi#.8φ7.GtMv[}gw7uôkmF-\.2†ReE4%R/Yԥ)NfEF}pe;)SP"eC(AN&#2~cEkцu6Ƌ| ?@Ib/bS(ij,AajmMLk -YRʌ kmF-\.4Lj4l>Q2vkmܿetc m6a×M,ВR"""".74[]oSohp0C}^3ϧ#5Zdyu5hzDJrCm&dHE3)GcZb;u6Ƌ| mF9 DYU&\$-q6NbYFi$+8/0bMCtR{wqߘotaM15O`thb1 v3L%%TR%F7-Q|+P0~Ӣru!HL(m)̳#3ۖ3;qSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp0ߘot`6Rg O7ڕ8\}cWSohp1q*a?4Z45M֫7ܤȮv+ Rg O7ڕ8\}c'}b7*+ ;%P%DZKŴFdo+p}^3ϧ1{`N;h< Rg O7ڕ8\}cG ñ_р+p}^3ϧ1{`N;h< Rg O7ڕ8\}cG ñ_р+p}^3ϧ0=ÏIGC\'~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>jWs_M15~cEkрJ.}>*!dZđVRJM巄C[Sohp0ߘot`6FCZʜ'hҵ>W71˝_2#ɬa'#>(HDG?UUz摒 ddn$xН]JXa- Nf66EVnkLLsCj?IWLEjS*;i&I٦^\YRJtJr|W ICj4Gcx7S5&a(ﲒCo%)؋Y൏=_޳o6=^Dw{gT=n"KdliR&I)I" Rhӧ<(ԒF6'/ۗ 让ԶjmFӪidFV3Jd݊I i.^FoSӡ~;gm+uw7x@RU G&4YRThЖ]pRH!'{wFBTqLvSTwdJ6X}v}/4~UDU3>ޜcϝZ-Eɣ<Q5 2, ;% Fe^gsՑ".3f 4Z&RTy,k33-2={IljqGkh"ONf{󜰲N%)$V)5)Ge#s> >fU r>RuAT\F[Ki~tU?%SKuPҭԳ4Ouc>\ȌSʚq ;GkHoZ^FFDey*ۧKBq-'an{ >`,'$G!Fr<*9hV(imTUTbĦ&#X:B9)-6L%Wg"s5Q%+Fr4R-KI#$ܸȫHc, ntkME~m$6n6EwJ^;ܸGLUN$N].qMn'g;]?C?rk=BoSrYjsLc/zpMjCHyq$L̵-eV[ {9z&50Iԉ̿*F*(ylX%"%Ii])8f3=#=_m4FgXV:1ªJBvA&('I\md]IxBDD|Ҍ}(`ukKJBJCD85|T}zq ny Z>ښ_e@ɂ͒TvRES",6K+iq^I45wcu?罉Nڢ!!wF8a˗pSa7C-e׽ } =\DmFfSdWpu-L:V#eku D:^uô"S&undsI)wH%mh SigR#6m O+uHfM-M-Ns6(6tЪ+8[B3rI4IHUDetǴsdz6hEY&kH]a5#&[tyDB9Eti%I82 >F"?B&3=z&ITH4Vw 'LUAoퟫxȝfVR͓ܤ(rgtW -ܟ.b)/q~mwV߮ӿVwUju9nOB<= ZJAə\УBQpU([ hӸbt6.DJ QʌBe?6KJ͌ҕ ƃ7zg|2?Jxm>!&Tҗ5-;_WwޯS$AϬͮ6[`~:iS]kr%&A2d<$&VDDY*tEB,,3j SdFrYJФXM(fl20SfLY$FT$]f{IFFDQRa 3+3TiՌu.dR(lSD`ܶizܨޱ5NHN yLHk[nӕLdM0#%1y8V;ڸ|9Oܺ.}f]NX$g7[lww)]u%Ϭ˩DWmcҵZ K [\ZB)Ω}5F"ΆNAQ$J *P[\ZB)fjE qZI].%{I=yB٠dR27I'%֝s(P+8[B3rI4IHUDetƠaK]20̶[) (N]S*'Y~,MC-,n(4|# َdXmKґ }y lR?%-*es= K1h2+$aܬ[JݍQB 6%-LjK+Ye|+Xo.j6bs8RSt] oH&΢ͩ Y U iq+b,UVk֕4p d&ɓmNk?0.8j$%d4؜*"Ci5dM˸0ܳr4ZiKDvMErBMjJIGY2-?1 6HfP&iF٧LjnUHJ'cVS"3-)4'֠`ZȑJFvw$Q$%g3MlZKN-f0U`j~Kiڒ%>)Ek73"MnU޷i T7.ԠAɬm-c:-cpg'PSW ѲܛL&֮3hJ63̋QG \*B)uZ!sf: m&֨VI]wC ˺(rk2[tf>{η5mr'5e+j#ҔnCFQI5u${odRRX̌%en.~@S)uZ!sf: m&֨VI]E).5YXC ԩ8ir,Svjm[莤MVTꖮ rѪ+ܗN^r_dRl-SerkM+h:$Ìh8T-_Mfb*Fp=S9ƃh%1II!܈ȽޓJ5Yև0*:$9 IyM%JQ%iٗtK.뺹wtcܹw6Ѭt;)2f]{29% L*%NKObZnhPZ7q̮Rҧ#RdGÑw3^BXS j/[2`!, ok':ܳ)Eܤ7~;؛rȝ5yoױ/HʼnQ{oDvl"ReA2ۥLvbգAf|m"Vnu<ӅB'RJM:2dyJIMĬw= S9MZ%!]sԨ̰jCIB{,Ifj6lC]ފ]vԗ>.,I3Ǜ_-{eD[U6:mʉCĩ IA˨m"k&*' R`֣X/:QRH\G :lЫ%gߤĕfG$ti;%dg -Gd#>QqnNjQ) F**I6׹E!n&YW3+w*6MaP`OŇ)h,덴23.lk̛SUXXF* 4:;P/N;& :['\32%У@'1ʔIc9KІ#n)fYlfý]_5ӥLQ&@~"hlB ]Hn潃2k:nõ-B[BiEc+%:ai4%nj$MRĸβQN&᚝JtB6Pfi'&JQ$Ԩ[ ;)M h۽ZSNjZ%kD HYgQ{mNмHԶ#BяpÇ̒͢ƂKR##NQ%#L8rm6EDւW$ ȶ-7 X 0]/"-d˄hilӜFvs+X=xP(:U6PLiuw6֭b4K2Лři+ȏI0!6"Q*VhFY)'O&Zy-[6*N&4XވKn/ǘNT)SʣFyҖNS*'$cIVFQQlah)ƅUPIWNTJ)sZK%)d6;[hT4ʮz(.CjxOܖД76Fc#v)bg1STbHtZ+{N3"qd3BuEr#]iv;lEkUI*62MCEnL2ZF}fLʓEeb0xcӱզw>\Flg'||t̯a*}o)*,HY;3I v)/ܥ'w`)i$څso>w;#V*ynHi-9Ȼ,趑\TrSg|{VkwZkdhhԕb/RaxPLv_鞩28ٵʬ"֡\24W8Up} G>JkA-Njl+*> o yq N.~te;5!e;jy{۹ƿҥ4ϥAF K Br%TۗYnt9 Z՘7Xu3EaRnFDJJJGs1.z3<*}JSZ6[iwz$:m Q&y}#?20 wB(FSt ;-RuJ!,iI%h$Hrfw0u |lIꞩ8ӑGtqMȬ_e\]͸LCpIp|*j(Z8ЧS5J6D6i U##Z8YAKJIFĺLD|J#OU * !/6j)ܳ$̮BKt V زẓn\r"pF$e{|];ZHӨX/ &$4- )gb60J ZiOmI-)em ImZ2^P 8PI(1aTwhK<&K;^o/Wjh3:];A(3&7Rεq2"4(ȮdG7+e8.4(OM!luTIBEsQ""g*"R9F*yDv$ Ec32!0M:eSȍOnDiE1't% n{;ѷgu`\Vb{yFO^jPb=9/?}AɦɎFխҕ#>af~7RmLԽo&GI-~FhZٛ"ʫ*kJ7.x,"uhF8R0JSBlۆ >WFppX&p]m$H놙eK}$cW"VFUԅic%tJm+UbXevǕytXTso's=;樵3O?NItUDPٺҧfrӴ-hJV2J+\YLΪ^4S2NW I$I.GlZ[A?H.prDd#"ӖX5LwXzXSLSg!Ɍ+e@Y%ce/%KK̭W"RD+nԨË<d8cØKOz~K1 % 'lWڟc?IWLy$̖ĆK+) MGPҾdUMkUa4D_AKK}w7gMHktU8\רOuNBRVIZ]m~vR.smtc:} z?uwx쥥]e-0W6F)>O*Wv. q)HUm+\KL|͵сKLw#kw KLk˷1YuU*tYLK䕑qL>gm"k Փ<~ݔ˷1KM{oƮb:W9whKh$!$pWe-4m~vRW. ^ q"A"TvIȖ;"q4ҏЛ\pe-5rm~vR_.xK;)insmaKM|soq6s9B$䮝OhgޚB;pge-5m;)insmaޟaZڳN?JI- +i2!ѹ9yǡ;&IqB)B7li;\pe-5m;)insma[k&P4BG3 a5۩+KnąlʛYJAMq}˷90쥦]ͷщk5_}~KM|soZkۜ}pӈ>.2.c,:2IʆstqU6?Ѽlw.$F^k`|쥦]ͷчe-5m8kZtXrSiQ *T٧sn)<On5_>qR_.òv6F5t-u}ߦ~c8)insmaKM|soqo\l}Mq}˷90쥦]ͷч 8]_G76>ߦ>Zkۜ}vR_.ÆE}~o\l}e-5m;)insma_N"Wk`76>󏲖v6F˷90k 4XA6 i"%)&DE!~c8)insmaKM|soqo\l}Mq}˷90쥦]ͷч 8]_G76>ߦ>Zkۜ}vR_.ÆE}~o\l}e-5m;)insma_N"Wk`76>󏲖v6F˷90kp# ͥD$I}n?>L!4޴lHEb|&8c!n奢R N-Fțڥ(LήZkۜ}vR_.ÆE}~o\l}e-5m;)insma_N"Wk`76>󏲖v6F˷90k5_}~KM|soZkۜ}pӈs~c k`|쥦]ͷчe-5m8kZ9Mq5_>læZ|t9}IR!c>cߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ƤKVJ#Dq)q,["35p،GsΗ9k7g:\e`;muߌzߘlU/b磚5k~hme6JϽaݞtY>vy-f,6ɗ!DYq8V[NI1óΗ9k7g:\e`68Кm:LiL)`62J=#og.ro2ϰtY>DDS ;{vy-f,y"i1Te{r|&ޱᛆl2R1RIRO"It25${8TAg.ro2ϰtY>9iEM6>ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`;ߦ̎:\e`lµ#dDgi1ݴԼٕjhmJq_@ຜ*]k5Q2Mñf%ոNdԓʲ#2+GM:o0TҦM.ce ҥ6k}SI]岕Ӕaj,eQrM%4 (Q%MiHȎV%#Ƨ+j]J܆6qciWtҙHʕ$y g 31-U*Xʐ5EL55% iCQ^DZ{I;mw\'Qh˘+77q.I6c>2 L+JQ%ۑe;x1} b"T .mq!}JRot^mcW&ƦIM=ʕ*)e-\!_aY- թiVDRnLj";j7MЃʤaf=byU7&_S\}Z"Ss 5&O^!V$0jB鬙J64MJ%8en ØM CW1Q>kXzC#f҃:d.ԓ+!@R?Tn/CugeժqԢfd"k2&W3pLS(oN3(L Qձ :l\g*]fF$hڐÑm9דrFpDR $JtEٍPLiD%-dΖ33{^{MڬQ1&.QV^ d]3Nؐd-dKIiUDNeu͹vBU8^Z~m%ҥ:S֥]xօk{ ʔsBx+7l'T%Hp4'>b;&QYDF#CFuwo?Vݤ;M-0rJ[#AIJdJogsj7SZWj3kr˖oa;<dbkWhi3Mmzz:Yrb3UE ]4,jo&MB7.]Ǹd_°i4b@a[maclkm 5%&"Iܬgr4btC:LKX[Ҥ3y -guiE7teP҉c"'u@*cktA{mOnƙskYecJoEFPrrh59eσh4ZpimҚNjb҅;,!^C% 6\ŧ6M-E4ҵMP (ւMV::⎯/ѕv걩qR9ڧ󣔉M1!LbS#iW$/k fT>+ґsLo[ve$*##qDڪEȦ'^DKm痢Z?GKУwu"Vt6~Qk FGG(bfN?zBچ]DjӮD/zbtb>Bmy-H?K?:R>zl<-O$ƖM /K?:R>z`#]S[J:R>}a,SS~W4Ss?u>1Hխ %s?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜x3's?u>㮜wV#T!![y*K5Z]c<.Ur;۷7PǡM3!F?z$"Hw}SF }SF B\; pLGuR!T*%!Km'" Ir.! CX“92 BQM6u<"GKE̸P~u8}zwg{l *>$?Jr mHiRi5 w\B%tDUZ0+4bNAs#"E['vi-|sM*UBlS3,y*$tH3pqlQqPCؖ2QIDSH$e$;H=~55zN۳V{4ǃlN{N혇0$v`i:`ʣN)SO:W+#ѻ13T98}z`Az>VAH M& k'v{sQۀ}5iTpqUR}DqhmȮ,g nb4q=LR*1H!IOTbRbQGk(ku;~^ xɣa;3-jer"mgk2,u# Woni'5)JmJ9}Ouj:7fPkqN$d[HErYJ#+\,r[;A,R5Z=FiEHv}Ұheʽ0Dz鴇 Q,ёȈl;4?Br*5:ET8^KSNi5"Ot}8[;^/؋hFtjYPoR5.jRH +̶~BN]P,bv?Fs ok=Mݩ4E~ xR@ƩCЫUVZfMNs65n˴S䓡KO bitʒFHOJGV"3Ę*SD!qd.FJ5fYfۑq :z5 j%M2U-JK9#JN^wF2]%`EU)BQh٦n:YO1iػ۰_DkIT:^$tȑ!(}R)qJjM: GbQmeCKVudYtf]ŃQahq5u*BK-FGcIܯrW2!Kvm&'b\KXLDz :v q=we@i,WUl8{ٞefڕ^m -aEˆČR],u9J=bJ2|HzEQKESԈNYi՗q̌yRv/>iU:T1~!hRU-I'XIY-iRU "I`=Ptcac)xYT*.6S2лV> qI;eF{cI)Vֶ Ď󍓓5-2͕s6n{n=KN 7MQ뒵G2}ei[6ڼEs>e|H~ xX~OCܵ R,\#;pv\58|z> V/X&&,uaL;ʌ$\gPyF~ tӘr%R$ܔjVOs֘4PbM}&%qi+JȗvI,%*">a6pDΩ|/#VҗLdp$3#i`!St*" j|6]Z )FDؔ$a&IFW1{?. Sa1aQH*t˫404fT##ۄbih983 %֔kGrEr3~ }/b=O)&[ɘH$e$;}Sfy"[Vubi՚I8](7Q֝%%f#JrG{\,5&M$ãʱ-LҎui#3ȣ3M̶ HҾK*S_İ*:ZH43A${ðh:ifMU15Sb<9[{VL/.r;\Wq wBi#th3TtƦGC$$B61)GjefQ\^TIv2-,#ĩ =?fD5]7آ04`c̚,1蕉L'sEQ%EJnJФ@®WX]6*0kKCJsEbvfQπgx iW)ԜC\~UE9"%նv|2HҮ Np q>p_0ΚD#U˂3D/WГ,PLF6VTφE"k3lQHӛ`zal}vhUlrGtN}՗=yxQ\Ap7n^/Ealt)Ki{(#0Zl{  2J8.#R:mu$[[^ԙ@2Zi IPif54ykI%Sfz˳ĞbxXT+'R"5R#K$j() ̴k!wYlXJZ>fgU*ө37㮚53Ĥ%tgMN<լIPF* * qTM.ͨ$za u2z$:%EA8+m-הl4 i-Y\Cߣ+]ǺYJu)#fN>i-Yl2-ZREY&pSF1)TT\\TY)FFX6KzyYu D?Kγp׫]|TjYOzJc]ʕL۽StT%Nl "3Pڈ5.4M'*bLm{h~6MU4nAJZJ35)(3"KآuKT)iS=(##XUt |z=$;`te׏>zSx`te׏>zS!. m$  .R7 N7WmfaNd|vC%Vub lOɺǐO MIOTxczǑdcK<~N|7!#^kYǒ#_;+]kgOTxczǑdcK<~w#}=Q1%FPuy/X5|GHzoP<([PmC P j(VB-A Vb7V&)g\L)1IR|#q*LR*1H`?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~uي+1B j-ajamB (Pž(1APc&Ф"뉅&)>QOxn%IEF)]B_4DK萉!c/yMҜ3/yMҜ) p\xW#np$xW#npԷ^>rι[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0'*1I ĩ1H#_5KУ}u |z=$;`te׏>zSx`te׏>zS!. m$ mYXկ|__]goZhjEε>Xկ|__]goZhjEε>Xկ|__]goZhjEε>Xկ|__]goZhjEε>Xկ|__]goZhjEε>Xկ|__]goZhjEε>Xկ|__wG8GR1m[ԪT!-%f/)(M$ðsj~~ɊZoV/~ѹ4@,,:N yK*Kn]Ēg5-\<&&PbJ%V9\$VciJcͳ/]S13կ|_n:'KēR%6IJgHKĥ%p)$]I"UP=  3Ͷ˳Shm<Ĝ#ܸv:ZwNgfկ|_mM h&3VxT<1G"r$kZ7V+\|)"#̞i>63:5R7;)|{FdfWI; hy 2~կ cM^>1#TYdigb;/U,mYSأ6iP=rdTTqmJIKkil4[HҫVZJ{59V/~Ѵ14UNLVVz1d\[k!w9$WQe<|U{EZBaul/2-3*TT#5kAm/tD:SUsnV/~?#6%EeSFmnްaR[;c3E.OS(dz[US%,ggI6b/E[8nM#s2~կh crI̽ZM)Hf3ZKo\g4q4zu.K#7 UֆU iZQv*bV>L9ek_|c6Db Zɘ_<fDiSjQ8fA\2e-IeDj3$KȋFGIi3kZhjFŵ-'Dؚ*rdmIa3 YWuL̳cCN)TIsY"Z֧!VZyQ|se)4VFjk=ZhjDhP17V(ĨC0h}jQ$ F$rbnt Ͽj4:)+$͇ n\$d-:]4cn5k_\WXկ|__]goZhjEε>Xկ|__]goZhjEε>Xկ|__]goZhjEε>Xկ|__]goZhjEε>Xկ|__]goZhjEε>Xկ|__]goZhjEε>Xկ|__]goТ+lً?Uf54Z̰7PǡM3!F?z$"Hw}SF }SF B\;ۜ"I;ۜ#-WxPY>oP<([PmC P j(VB-A Vb7V&)g\L? b?sj4(X#mG#lOi/a]}`7>׺˓Yz)^!6x~=r2[LOTM ȋfkjx51 [6::U.lؚR(RiDf[o%Du9b";*k~zkScm[+D'3ọLET2UGK)eatF0+rkV05IîF2㵮DI3RLX"+rƱj[lyRYaa}+QMO5WLUEzk ;z&'#'QT]C#?~?~K?~zXʀ W[Eí?~"c*1]oQ uGO_:ޣ'/2?~zXʀ W[Eí?~"c*1]oQ uGO_:ޣ'/2?~zXʀ W[Eí?~"c*1]oQ uGO_:ޣ'/2?~zXʀ W[Eí?~"c*1]oQ uGO_:ޣ'/2?~zXʀ W[Eí?~"c*1]oQ uGO_:ޣ'/2?~zXʀ W[Eí?~"c*1]oQ uGO_:ޣ'/2?~zXʀ W[Eí?~"c*1]oQ uGO_:ޣ'/2?~zXʀ W[Eí?~"c*1]oQ uGO_:ޣ'/2?~zXʀ W[Eí?~"c*1]oQ uGO_:ޣ'/2?~zXʀ W[Eí?~"c*1]oQ uGO_:ޣ'/2?~zXʀ W[Eí?~"c*#xMG~DxN\ΣE1Y鐁]B_4DK萉!c/yMҜ3/yMҜ) p\xW#np$xW#npԷ^>rι[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}DAñעbr25>5H21)Q(qCmNfi$ yt-=ɫTcSSQTdIjA!%|Z{W323X ʅ9Q)]SjEB1hgtq+gf{//О!d&*"i7VDbqfl-6;W2F phW2FӁ[Y#[,"G!YCl-}zigG!YCtppP4yU00fyHz2&2q64Тr_Y#[,! 88f}"-}.M(a I"L1XJXp>2+pYtNL8$x9 ϲg`Cao4zigG!YCtpp;r ydsU4:\ ANJRiL]f,Ǵ~ braJşIπ$L?z$4(]B_4DI|uϪhޔ|uϪhޔHK{¾sI'{¾s~uي+1B j-ajamB (Pž(1APc&Ф"뉆G ;z&'" :@4U 8Q8rSYQ;))RKvKTmlUY"9*%Qm~i8)Y$l3;AgjcOLDj6ZYM쒲RV."jYD66K0Ӓ+o>؊#;$+p$:bkb92"Zn3Ro$h3Dmh- 5Q~ak0F) r8lGd&RƻVjTVthI2ga:Y 3EgޘzJ>ؿ6N wDV#v8m ʒG$v4$B5_Ci]U6j-jG1͛vinRNnqS!dҏ{TR[KZ&8jȂJol+ʠEr R*`ZHZQn"/ Q 8W5o3ա=VRVe~]Lᷣ:\Q[)DHﭛ[LgJUkRMȶW%mAj-.F℆uJȒ{OiUINKjِ[J}m:#"M.-IԥKQnnVU*$@)ivн£jŨ%+l{zNôXj{hnI M7q53 fʍI..5ǴHXmKBb8{. #3&ehRdFg޸:NNίS6|u_Mɳ-([q[tN,v[ϭ&nX|+\[K߬7q#t嵲r浶Z 0+z@rYuHq곧å9{вT&r[J3+ZJlԅ&Hgۢ2u,Il(աBlv5^Ӻ$F‘kWQ)LU'+ۄp'1bJd cRT*YK$WQ{N,0ݑs& ?dvG ,n9 ?`/#L7dɀ0ݑs& ?dvG ,n9 ?`/#L7dɀ0ݑs& ?dvG <j]?c<jf \^I& /:i5iR;L?)u:*Aɥ%|)6q]&F+4lMaG/TXYPDfXIJKDfZO%-KGqLY$_MrTsژ~C*(#33Yw2R ;L٫UXMJQ:f|'Z>t:\2%Fn3v%6W(UPmyJtқ(^,iQqGۺw,c$b$W*oLe&.Zdddd܊|c+wHBqjq)JJ6b"eT!TJ; Zh:IYYu%|r*ğ CcX`&ꛫښa@L3,j(Jm *̻=^ۙYx{˭oܬ=F~vvOmOݵ%MӛRRea~U0 DΊ܇$4m/-fIRLjRW v0XK"4C&z1p!z%n?Уܥ[)P[JHőfQ|DW?DC,>dtS.2bH%I'>VrZ,Yճi[Bl0HiHBJHDDE=@,n9 ?`/#L7dɀ0ݑs&έ?S;7dɎ22V(tf̹u CqBI5VTdhU"i/+}_6%wSv] "֟ĢqjzzIIY% QخW"2~T-v)EOGI&@7Ge 㟵d-D &.Q+*k}lkMQ6%HCVbf0UdUT[zTdTHpڭ_)dp"(DLzzif0iɖXXx{i|ۇ{ s&^DhأYGj6%8:ƬnYlϬpv_]{:>2_WΎ˨/C:nFV*یvp[5-UOI8ډ+$J";FW/&y^i2*M'IEC%@yWʇ"Y ) /Qڮ4CTERbEe5yS4+:K5IAI;IW1Ҥm㌳ZI$"3B ˿bgKGTR2I^) Dzn$,(.e~+YڪæOnILv4_!͖k_` xޫ wa~ .)q9ez̪e{!=̖t>=2!=şI %SLHhQ=i0:2ǟTѽ)<0:2ǟTѽ)}6N}6K{,5(Vb-O% .([P:څ P}xPbMաIEF) v7LND ;z&'##SZt=RR5M9ݖlʗl}vˌxĂgz32s1"Y!N$FFIH#"><@&,]4M5'U1.!: #k#v-щT6hU'f_֯{갦;%٬ffBl; .:ˍkRdN'12OYw˼-(B,%)єInH6e2JLZܗ3#Q&WQb,F eJdiےqR֓i8+)VUg=0ѡj I%/1R H2͖i&GOi+NĆ&ΦLr$Ҟۊ&.""ϳ9/m ߁fnr#nR:Vԝ[ J'Ri]ςeUZC8Ldγ&ԕ6_ GLiRaa!(Ȧ7NRث([N0!7!Y.VITRUcLFF&8,]4M5'U1.!: #k#v-يUht/OIi3\[dftѕ$-fw3nz)fԦ7 DN_#,4n-V#3HFA JK\:eNj;;SX8h#Q%%ȲMe޹>1?Hnf|"hUMDD1Cuzᓤ2PR{`Ѡ&jJr zqL2[ܙH4Fm-5`@@d7LzE ƖSif4ܖN(G33cEbg~ǦB' ?c!=i 7PǡM3!CF_x78gF_x78RFIFoz|sƶb PzBڅ jZX[PB 1o Pbɺ)1H#:a^ȃac}Ddj|k@ΐr[QdR_RLH TH%fBTnDsbX45*'$8LO8IzfFlF[ 46NBFcFO'(grZʹ#.W1|ʫStdY5#-*;kKl_tFJnG)mo8TVRVG)֧YmjR̒Y_2dIXTR*mT"jqRM.'2$ҤiQ @N"*Ia/fi:T ZdED{FsbxQP7iQ)bq|eQe^DBYТҁ3A";r%Z<579RV={9̥KM:lw"6M2CkKIgw3;$:dPјxQqqJ%-ܖDGebi75d BFFI$)2$$%$fEc=|Kˍ%1r̎tDFB3pDap? \סVKОK6lӵ&yTv;^-+1=3i(bK"Z FmiqԙEcQ}M8 zυIQ5~jR(}Q.;nFM(bo^7V.=~<`@bg~ǦB' ?c!=i 7PǡM3!CF_x78gF_x78RFIFoz|sƶb PzBڅ jZX[PB 1o Pbɺ)1H#:a^.kjqIJs Imᵸx^ֆOx?*h(oGS<t{QT;DCx?*h( T~U3:P*ʦy'J1'ULN7vҌ`GS< gt2{QT;DCx?*h( T~U3:P*ʦy'J1'ULN7vҌ`GS< gt2{QT;DCx?*h( T~U3:P*ʦy'J1'ULN7vҌ`GS< gt2{QT;DCx?*h( T~U3:P*ʦy'J1'ULN7vҌ`GS< gt2{QT;DCx?*h( T~U3:P*ʦy'J1'ULN7vҌ`GS< gt2{QT;DCx?*h( T~U3:P*ʦy'J1'ULN7vҌ`GS< gt2{QT;DCx?*h( T~U3:P*ʦy'J1'ULN7vҌ`GS< gt2{QT;DCx?*h( T~U3:P*ʦy'J1'ULN7vҌ`GS< gt2{QT;DCx?*h( T~U3:P*ʦy'J1'ULN7vҌ`GS< gt2{QT;DCx?*h( T~U3:P*ʦy'J1'ULN7vҌ`GS< gt2{QT;DCx?*h( T~U3:P*ʦy'J1ŏ"*Tu%-(äiU'a^kB{>=2 KУ}u |z=$;`te׏>zSx`te׏>zS!. m$ mY3RVD,RTJNR#EdfC˻cTvn6QS/lCjZRTfzԑwDEep4Uu?K7i*om6 #+xlʭgLՁ )Z[q+w(AG)3aJ}1%LL86 'JI$Ԓ>2%$ً̻#֟T"86n\fR#)֖ISe%MTK>aPhƎ~5GSq$qO?pJQcSpQ&jJr zqL2[ܙH4Fm-'1cj,n44K4ɦqFw">)~dcGHaΦf**Cʗd(wF\$}覌%CjF%ޱ-s*7,Gcd,4gj9OjGRޡ$+I$Rd,"VǧHo!;+Mk27.%ݖy]#;Y,.Etn5ʻUʕJn:ok,o<$4aoX:IǴ(hwné`qdJyj&dӴԒ,jiҏ)r Y鐁 ,O@o/CgCB%SLHD1ї^9K|8uIleCtE}\>u MM p"YaBOb.oi۸w"\^20ELejB%Otmf#QN{izT"39\J3-EaF|8U7Fʯ1^65֔**:lkY{<=D|$cUvwҷ*w7igH"\|w͕DIAwI#}NPp@1X ?c!Y鐁]B_4DK萉!c/yMҜ3/yMҜ) p\xW#np$xW#npԷ^>rι[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}DAñעbr25>5H>=2!=şI %SLHhQ=i0:2ǟTѽ)<0:2ǟTѽ)}6N}6K{,5(Vb-O% .([P:څ P}xPbMաIEF) v7LND ;z&'##SZt@ȭUE[HyT$Qg\C` b-/K@q!w *#HZ>ow$hbg~ǦB' ?c!=i 7PǡM3!CF_x78gF_x78RFIFoz|sƶb PzBڅ jZX[PB 1o Pbɺ)1H#:a^ȃac}Dø]Tبຄ"#Kss5D]%*eNZ)2IME3tGJB YvZs"1c4$f1Ǣ6dje JY]$W+ڥJ&K,no9qb+0dxIFSSƧK= FVIddmG U4h& NdTU.2Ee44JʪGM"u#es-8{x^{py2JRЖjy65(3+)Y/π3Y0RgeǷ:f2֕;6B2+ c[ZV"կ~0v㹽:Q11mSL7.cFFw3vYJS堸z:KmETjJUI"3+G9ʦ[(5\CgTZ4dWK)=*ز"ƓV?,ȣ6)SfDD3fEv'q⣣}z#Mb+ F5 D7κ)R$VFw<.Y.%aHSTt陉T=ʼJJjND%'u$]9QH4 ,噕*Cdq0+XƣNPH dh4ʗ*,-qI"&iWxIrqqap WW: Go'G)2^qݎq߱6&e3 }j.lUnjtHJrMZZ"AcJd$f}3 Z>owĦ0Dƞׁ38R-+|'zd B{>=2 KУ}u |z=$;`te׏>zSx`te׏>zS!. m$ mYRHHej\)}IE%&:+gs:4aْ4q.l줞 NiKJ L,t)Hgt[vm aq-V*ֵ$N5Dr ?JnN]qg1$ū*sb N^İ Im<&nnGGBH̬U,nZ4e %@^lg]KYLjItnJ C19G­HĚSJOqjTim%56| RKQ%l# SAIb)h :. Ycu)-ijIFjV2lJQ4-M)e jɕUeW3˴D"@@E;_6f7DI4W+}!GPԔ:;ZN&\&JcYKd&ij4ǩʤ2{RI7;VTؖte{خcb4f*ixُLv-?123k#;;p>d!T"RvA2nv5g5FkVb3$eNʱ@:)2iAכ)e$6;[hѿ'?iZ#Oss$Jg'=)ʴIiaYJ$jIMD YA&*\IC[Q 2MԂZR5"ҫ:e"LLɕNSI+-*; Qg=uOy/7աF_oCZnlebu9홿M򭫊w̴g^$씑~KE2Mq__;)O6,豨Y 6qZ6i qqM8dDkmKIj'jLaqkmE-C7&-.wIgܙm;mlQf%Sc8nc6;Ji#r!333333f`/)谎>xU +ӪNIe4[$򴔒Pf"΋\m'JZRwF;4$xGGC Ieen8sj^l\}MgsI;2hF1;ʬ>E1nGFҋVҌΩm%I(\"nX j.)N1y >"6n+S I;];zpf fLq5楥,g1իJo mr?L&RS7Z"Dq+dJ4(3)IS2IBII. `H`a)dU=2'-3Fօ%&gADJUE9+6M-T6y36%D2U#/sn&FVK#5S`n5u!H2&iRLGdq(dž&e;r&Q+.SJ5$|f't 8 s 9JEZ24 f1F[SZR#ZJʲ{m9= \YdU͕NJ5F|6`!^NF"-nޚJ`hEz5#lƚLg8SJ#͕Fv#4?ðNV'.EHTitJAhiI*)Y\Hi+Oj! +V!)&u qkh֢-bH\&d[@q`!^NF? b[cbJej-ڨ3Mq(#Z%A$>"#]`023#"3#/]/cR8&TeBB#ĎT:$6ZWOt~6FuQ1>$M62JB%Jagc#.2/.9Pk;qׁ&3,,W_,BB*,I3;&;$}eixg{"[7O^yѱ&ie 0,dMt%bȴBLsIY2(VG=2 YhoN+q%MiJ; o%Sm(?OA>u䴗]ٕҢ2;$B'jWs^i3cXuMB>ǘc,{#`w Juۊ#l2˛!s.bϙFIFoz|sƶb PzBڅ jZX[PB 1o Pbɺ)1H#:a^AHrXjBn*k*rT+x,w_k19krz-jDjeEy_lQ[9Ic$^; 4.ig2;uSJf|cDnƽcdj̖+8`6ѲX95I4ڥ)1Or:68rmYIYrj$E0xҦs3@Bߊ2)ZVRm.-dpiR`ت8ÈCe.3=,+Ӿ̓NQ`o6妩%[o]CJd٤ȵ*3$MQ=΢Ii[UfQ T6_H5jFRrd')f2nHU ib,xw-6)4d')63>NVUøcFR3=&Mf)FNk!_s{3>z5KB<ɗs%#)3K˘6Q65zC%W"DO!ke4ZL+)%3֤2Kab삡Ua%\K&Jț%_FXtb(NѪ)9YKfM.-:4ˑH^^9apbfzM9,m:ye!lF(c&WQ]I\>I1ӥS0hk[0N<ռEJ]͵ Vn"\c(qg OUQ^[M%+rΔA)D\$wΛNT!OuM /Hذ`L?,g:stSh^dFI)%;+*ٰDPSrSd͐!8/Ffb$w Ύ)jWrlp̔* H՝[ob?F ĪRaJ]՚i2%YV=619ԺVN+5=Vdnn\cpVlK"FFᤋ)20V'LwʢM-%iK}F#Vdl7-{m$r#ti#KpSEU*PRʕysꡓąr+I{ Xmg?I]V:V=Ngӕ"mWK$v>++7%o kuUFI6]"Aɀ͒+fO;BRe.4i') \:f#G{RHߧȗSc$w Ύ)jWrlp̔* H՝[ob8UWE*Vj1YzMfݞ -BEIR+jI]vHGRm+&; EQc&AZ26ʂ͖`a Wi\T>*lC' jWl)V2&l;jI]vHGRuPUȏޥmD~.U^I]vHGRuPUȏޥmD~.DboB!Dt%FQRbBWRgdHpٕNų2t|MEIJZ[孤[y%$|6J["b!콂0 $žOfƙ+ 4Щ7qh"7qϮ4($ecI "uP߾?{ۍrbLWSIfnqQCHNGQiL)I-ۗ VV&I)VKW>ܻfvHGRmL?ٸҥ\u ꔴjk+(Zar@uɘD!DLɖSVEcuB,~NwǬ;m$r#ti#K4M*xR5jJlyi0c2#Ϫi W{:k0{ޗWȏޥ곐ddx#aT)8Q :33olQ^w-ddFJ+ѡ7!ÎhV- ЛR"""".}EukZ׈R;6|WLB&FnK+:hd]WqMZ5zzŚ^׵Wd1X ?c!Y鐁=FIK k񾿪"y-"B[R9_9{¾sI'{¾s~uي+1B j-ajamB (Pž(1APc&Ф"뉆G ;z&6}&N| \=iN,ҔJIŘ ;z&6%p^PU56-HS227*wIu~'mfGl}WxZRfJ_QbC.udvVܶۄ`ìƤPTgIKJt'eBNn-'bpv#ӧ3Uʩ=\$6&>C3tqi--1pf2]!6fPw:CI|iuIJ|*j}>C*/V阍rOJ܎l7Rՠ-aAI4X5!F4ĥIw&de#2hRu*-N#PIqq&2edج_( #:mRIH#5IquO$߼e~:j6Zq&94,2B-ɫePmөBZVC,"IY1쿬̮Vs(re-tU̡$},K΄7 Ys6s$!uyitز",[;gb"S֧Rd KSffRi3#2#ˈm*Ԫ[Kf*R*|JbJd$q$'[8x5ʖkq(JjAՠinwvP awB kijM77ǪvkPԺ&5N Z9v}eC R{zIvj:B\uF{NY5*2UdF@%*ޏH~s58ԓZi$ͤ6 u%6L\RgsC$9%e& Hߕ%2^qY(ԥ3fgظ 6ۺw,wUGlj HLX׼3Kh2̒fI-c+/K*LWR*Meb*/&dmJȌ[HȌ^he2io4pZi%攛#2F߄X7^ƺqKyڥ  G5Ef?Кd³}>E!$D[;ֶta1N-U:Er$UD<8Rv[ %emñ$e۞ْdGn!x)6MT̋1k3Rf(τVqГhKdI[ӿ b򫺪87GtEMF+[5qyUڪVUbl)g%z{x6Pq 1 IW9gbID8yF[.E}J峄ZFs%U*Ɩg$u,%Mȶ\_Ap ` z)Ӧf762D9M#:m7##f_ACӐBrP\YTpִ̌UȎ\Cp2u S"KR#3"JV33c]n2F]^|TԇdNi;Fw+Ұ@5 5WW_Fiem#|cAV-C?J^TΜM)2BY< W+UVV*M[J{_.c;^{quՊ7|nNt5ɛ5mkp @5U3JzJct׬v"_Q2wJE)e.!'iډ["NsIYbY&l $H &esM\ Go2n>[BLQԣ""1]4y\;F=}졣Z:1e rяl|E(hwΌ{aCGþtc:@n<؀W1r*c]fEc3Hue2+b཮|'#íRJǚ4C/e̦QYD˄ꋨ@钻>:4}[K#fGc#/k.|oz1hp(SP4vt{ [9k|Ƕ4y\;F=}졣Z:1e rяl|E(hwΌ{aCGþtc:@n<vP-pΐ[9k|Ƕ<IX1eu':/C TiSƘ"Cቐ#ǧU$Ԓ$fgr|['' hY4Ȑv-&$͵]J2"+m3n<vP-pΐ[9k|Ƕ4y\;F=}졣Z:1e rяl|\ȫHvtnCU)e"ՔȮv\:!dyD2\neJ+\̸Kr r=Qu]2Wg'Fϫ~3q;DvRLde|%bπ$LOqg~ǦBto$ ,7OQ@CG_0 nGaH| m$ mYjYeJ.i[.DbPXl8*6g 7#z%YFY 򓰈r2/ Ļ {.Ű- [#J̳"ZȌG+N8LXYZ.M5g"CVodi##MPw+Arr"zۄդ֤EC!l9Nxݚ-M̾'uE)'7rdgbQ> 'ῄL4/S?htTO}&oXZۺ7N}ek3{bDs o;kw|Sc=8t5ʎg&kh[%UUd{V3U7 JRR"+*CY׉O2BPID{X00va? 'za0Zm{ ĮDFPAIu&Y,kc3Ms\1*&D >249ÏkŨK++grQ-#Fz32*̲MpJY!K4 YX*>1{ o; Z*ĕ:QEU ,vLU&rKR.G(ʢu{??`:hfHiDµrVJ7OZdH25$@0ݬO ޘ;X005"^DQܤ;-J>VNVWr >x| YĉêYBSgv#촨[%ʈӕ&QwJc o;kwh. 'ῄLO ޘoX00va? 'za@`~N7 o;kwh2`HvJm*,J\I_iNco>=2!=şI k񾿪4=FI'$%:?#ew+8Dw+8G[ޯ9g\񭘡B(|ޡjy(PqBچ(PB [ƒ2n LR*1HθdpñעcdYM G2^KΤⴥӱ&Ns̈Ȭ|_[ac}Ddj|kC9Cl>]R{,f+ùlU] Kk#<^qԤGay fu).bcb^k\LXd̹m#UqN,WSķ"+O 2"5 J+amMaq P ecbtURt4M\K6oty H5j4WR`Hu/:͊Zx}v;  @1Rv:kKrb5!(Q؍I'Jұ.!v*“PHvbْn+5ZXiq-:$y5 Oܢgƣ;2}nᰅLdHOUVQQY}NmLIne*5kRgV곩2qBKRFȎGx1!QTflFOG"&5HKIdD(+-q La1 R|tfJ̢$F2YD{HgqzNc4fdޔnqӴu٧7"1]eixg{!VGoP<([PmC P j(VB-A Vb7V&)g\L28_k1iΑhֲ K*6򢶧!JJ\4I>_Qac}DՅ:RzaTdN!:M!KJ#=rbOq~%wQ%>$}ɵc*RfH[htQȻYptJ D*I"9-Q[VFnwiț,kV-'y#Ы?TrODw46"Fejw5[5;SLi*)KnI 22Q𬻳#y? ~M (i`$*TIILefr"K;첼4 4j\Y k1+'XC$,mwI3E4%U\BrYim1KCo$RIDZO l;{vTa)U$E Z]Ovq9sVRImYP Q) E9k iy}3%%M-[a(b+ (mBn&S[9!nJ̔V ${?P&Q1bB]ZhIJI:ee:#0'})AӋ{2QJKiLj lNM5O"GԔF B֕VZI2m{nGhUis{w1q)*[ {2R*[L*4I:)yDgQI]>_׻ՑN_۟&_pZxn&2;A8U'Ft*E"UZIF#P[j;\s*(IJi".))jcTT13Re!eCBTEi(UeOne#aK9sZ̘A RVfZQ+*ɕܶS|UYUVݜ:R I!&(,uh.:Պ(iJm&^R 'MVqMaz]U>|r 1))+MD[FlB4qچP۔@(s]ĕ) Q+e&PVeydfQXՕ)etuXƘ^^/yY7>C-JI'1n8P\)3Rlw"DR5ȴG>\g0Ewm2kEc;mMR[KuF!jJdw7;O$$gb<25.(-RJdT$5!qkKm%%Qc%SN#oԤf2yp&d(m(8v=HEtQ*&5V)p6N28GHGi}m"GFW7)%>DGmjB\+IP:=IøUl 1R$˒Rio]!q(#ې7b l%T=`ڞ-VTe))K2i;(/a$ P5 0r[ܱ]DZ,Jmm/*bF6”!Pfo ͐iȩ^W4q&o9t(vv"aZ(j:KJQFudոh\Vwb5زa?J'c SN@VklrCtQ(Cm64k^v1jPb* 2(K2 im&Tn<)]Ջa; H4S$ʾ)q)I6%į[dG%r*ٰ7Lj[I}Nrm$ΔSYy4g#A##R@JW0UHTge$4(;%Fhr"3 KfOq#=)YTiRT)*####"22;p֏j EŤIRuDa,PYt"RPE˴sm+|# ^oAp{\l8!NdADe{L`XҖNnӳʣƫYj'#; YfdVJL;gcV-ï4([͸*NMJJHҶM\Es4k[KJ>.MlN*o^74gMD3"bI*sTj)DSindjo4w2_Q"<s1j$:uJ:wO HSj:K̬(KDlXIҰi2CpP[ql$jJBdjQrWQ1T]u=OC%P ĒQlgd^KG')U0KqrHiRȋfGZ"A\̶sG2zDqcoM5-Q!lREjv+IaYtJa5%NjL^){s4f#,u}04øtyRU2.K& ]a.ɼy Qfլk؈Fp5 ^'x=2tceFe4)qfwVTlEmOC(!OAe4JkeRִB5-z ̒DY<)3o>T_ԇY鐁 ,O@n_V5_oHǼľjO #@$"1/C=F%S /c{ȌK}y|4K#dZH˗ xN:גEsRb""bπ$LOqg~ǦBto$ ,7OQ@CG_0 nGaH| m$ mY"hИPMHB퐏V4WNě+MT# K)n,v,Uԓt*#8E]wo6(@+Q72ܯkqfQ-<dFDV݈M=*]bm ut}Uf##oRR2pf$ڶ46QhulaH-A7l8b$ŚL\lhҎƋ\Xea:QEJs[nAJ'^z ij"$!F{[mhdJMkok8Y}vٻ[TChksͭ>lImmg /ֶ[7kjɨ[@wlV7pԆU0Ӗy̫NTܒDȧ ZHçXKHk:; "VFYL̉$!sr +hksͭ>lImmg /ֶ[7kj~ny͒)6ͭefmSy5T irgRt315! ]te jl!I+wVBeMrj ʩKqqME)fJE m9_/)hksͭ>lImmg /ֶ[7kj~ny͒)6ͭefmSy5~iFag/DqL+J,ϛkFSVTFЩ#dyMbRJm1VYlX6fّ)QmHԉl*\81 kl?i"=ܴj=2 }U iaz~7[O"%:?HKtu? G44xW#np$xW#npԷ^>rι[1Bf(PBPmBⅵ -C-PZ ׅ(1Y dZTbq0c}DAñעbr25>5H;V >Ӂ[G:+Ra;r yd:iggCWשfWRy Y?O"QȎe~#sf?Du9 ϲ"uwן}IFMD#E7%nG-Y hA!)UM} >-,*ZңfU#Zj7RgND~Nx=g`Cao4Y#[,#[uST\w˕$'!+sM,##ʮm|c?ХШ UkYUI}p6fZ $JY'NG!YCl-} \5V0}T*TSזΥ-"͗Vv֪UURƍR@fێ“9kSRXJ"iTd鋺-wWY#[,#:2F0n$hvX4㦛vw ,v.&Dq)Vĸuڼ<$MU{3[$mKq Q"LVRNj5ZkF5ʖ&)JTdJU'RS{.lҒ_kj3QigmsdT8h٥%#mw= iKEQr3hzKM8qеT넣KdgGV.gF-Q[wݛQggɗ6^b6.61>jT*Í-OkNZLGO)"ix*$#T$MnTfhmj$8v) Dn|"e(P "j7WVƧa%eE ̛̲U1]2Y756&"PɺldVtMjb}%a#+:vHQ*q)Rn]Iw̄PШ1"S4ؑNHkKs16BT!gb$̇,G*s(vl{R^Su6$$4 ^Jth:$XlV#D_*oCɢߏ4wegT٩ 4N%*M˼i3#.f{seUɈYcSuI2+ZeW.mIin:JpilðĻ VkGԡrY lTLM}e%8Kus^ۉzN~ZI).GqMMHQ̌Ȏ."1$c K].s(w )q $p*g}F+ 92뎱OEQռj_7llmFV;سmU @6aQ18(EQDXSrk_$52OVNIۑOHJ>Qi.J+Q)EBbȏtÏV*9uI 9M'Y{ܶIaT+&VzS+NnGwI4{&ɣw.LYc*m{]v]om﷿7zCZhҊ}NTU= ӦA7YڔJgn 5c ugaq! K-ѩ$j_ ZƢ@Uf1*y{1ܪj>77ܢs,[ƴ>fnuYUڑU\75nY(i6̈ZƔw)إ} {tWW٫Xu۾VJ[6lwʆ(oe3TrrgJW"$e߰NV'/q#v-Tp~E"dt3"%+O -[(laDyttSH^&El'}l˺fbL+6ЦHfLWSO2e!Ee%I4̌XMj J"lKnf\I2:˶K;Tipr)[W vvn єV^Ej 9fG2i`JA.!ĀL c!u4dBMYndF{2czjKtTSr *IH͛*W.&ewa~ m)u@jLgaVN6j4 #(Puya<0%JKR٩LҢ2Rn]#22 # JD!\4-?AkQ$fndv]pp 3\35{}ݖJu^m^Ǟ:m6csi$C<ær2;(Fe`ex#7^![L 2Բj-g{ǎ:m6csi$C<ær2;(Fe@CN:b\3}:fw3Rݣ̙,LDd:ܖ'PdQm#ۋ@'(#,^5^7Q}e[ܩweܧaxnƒWE%hBﳺIǀbQG[TnTDppDE}ouԥMU<6:oLy l.%LQSԖS>cR/ϹN.q~;ս;?{t/Sq{~CZԦi.Ԧ9NazƢ)-ot_)t]0 |f=s"KR#3"%ܬfg5:Z!5*鯶VC$)'5 {[UYEfL1TR[KZ&8jȂJol@NW *&CqL"Y^G{lwҷ*wƵ,Q^jrVIˮ{VfʒB{w)"]Y鐁 ,O@o^Kq}Ye($pC| - n8 5)Gb"$$;w{i73O-h)̱NӤ!k^g(s""D,Vm;hF)EDz߄r,'Qdue~rQ3<ӝfnmT5s+ h%vqeuve \ێ:SJY$f32n\#}LO@Dύϋ}mU# B/˟*d8Y;]5S!'s)lQDF[ 0Ibu5}^jϱ eZf~|~vC3?>3})bHih6si'UG)4d;$ecbٔO;}mmt4>jXPr]‡ hC%)J2QDz6\#u3_̧>wjڹNS>:4'Szy?̘9vS|c@TϏպghj+7OOaz}/S!ǽ3N+wwkw@4 USFx_fx_fFe- P<73#V)^0yoõb |tdsj+5vRa@\ώ̎oXx湿ՊW(kP<7ڱJs>:@29/S|W*YU5J2KXeZeW-I A#:KjxQ^cQ!VEERVDܖH!&Gb=l+!PT#SC)(fqiٺQuI+J}V"+}JQ;1뱈̳ڴ)MљTRCEOcR8$#/!49faB tj5.}ٻ Wm:ʣaFOqNa"O:8җvID\ ǣOznwKݦs-Sϋ}mU"[JBE\h Z\9WqT늹j;pV+`U26"=6)\Tx5Q9b%Qc"?e." ~L"f3\)˯y5elԵԳխI[?c PljLQ&zKsIHnc̢BX֔sj#4챕~+ u"tL-ySCm;*մe{kqCF9 040^QLܚAsi>mMKQGa,M7?c n(~6A#n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A 4PlqCF9 X sd1͐`BM7?c Pli#sdM7?c n(~6A#,qCF9 0!`&1͐n(~6A0hw-RKBcROD<)=J3uf]$#nRMDYOT!e$VO)ʄ&In(~6C48qbp]EsZ67Zw2v\̈V %gm\eYۿT\MTڌRVN_xymon-4.3.7/docs/configure.txt0000664000175000017500000001063311535462534015710 0ustar henrikhenrikConfiguration script for Xymon This script asks a few questions and builds a Makefile to compile Xymon Checking your make-utility Checking pre-requisites for building Xymon Checking for fping ... Found fping in /usr/bin/fping Checking to see if '/usr/bin/fping 127.0.0.1' works ... 127.0.0.1 is alive OK, will use '/usr/bin/fping' for ping tests NOTE: If you are using an suid-root wrapper, make sure the 'xymon' user is also allowed to run fping without having to enter passwords. For 'sudo', add something like this to your 'sudoers' file: xymon: ALL=(ALL) NOPASSWD: /usr/local/sbin/fping Checking for RRDtool ... Found RRDtool include files in /usr/include Found RRDtool libraries in /usr/lib Linking RRD with PNG library: -L/usr/lib -lpng Checking for PCRE ... Found PCRE include files in /usr/include Found PCRE libraries in /usr/lib Checking for OpenSSL ... Found OpenSSL include files in /usr/include Found OpenSSL libraries in /usr/lib Xymon can use the OpenSSL library to test SSL-enabled services like POP3S, IMAPS, NNTPS and TELNETS. If you have the OpenSSL library installed, I recommend that you enable this. Do you want to be able to test SSL-enabled services (y) ? y Checking for LDAP ... Found LDAP include files in /usr/include Found LDAP libraries in /usr/lib Xymon can use your OpenLDAP LDAP client library to test LDAP servers. Do you want to be able to test LDAP servers (y) ? y Enable experimental support for LDAP/SSL (OpenLDAP 2.x only) (y) ? y Setting up for a Xymon server What userid will be running Xymon [xymon] ? Found passwd entry for user xymon:x:1000:100:Xymon user:/usr/lib/xymon: Where do you want the Xymon installation [/usr/lib/xymon] ? OK, will configure to use /usr/lib/xymon as the Xymon toplevel directory What URL will you use for the Xymon webpages [/xymon] ? Where to put the Xymon CGI scripts [/usr/lib/xymon/cgi-bin] ? What is the URL for the Xymon CGI directory [/xymon-cgi] ? ********************** SECURITY NOTICE **************************** If your Xymon server is accessible by outsiders, then you should restrict access to the CGI scripts that handle enable/disable of hosts, and acknowledging of alerts. The easiest way to do this is to put these in a separate CGI directory and require a password to access them. If your Xymon server is on a secure network (Intranet) and you trust your users, then you can keep all the CGI scripts in one directory. Where to put the Xymon Administration CGI scripts [/usr/lib/xymon/cgi-secure] ? What is the URL for the Xymon Administration CGI directory [/xymon-seccgi] ? ** Note that you may need to modify your webserver configuration. ** After installing, see /usr/lib/xymon/server/etc/xymon-apache.conf for an example configuration. To generate Xymon availability reports, your webserver must have write-access to a directory below the Xymon top-level directory. I can set this up if you tell me what group-ID your webserver runs with. This is typically 'nobody' or 'apache' or 'www-data' If you dont know, just hit ENTER and we will handle it later. What group-ID does your webserver use ? www-data Where to put the Xymon logfiles [/var/log/xymon] ? What is the name of this host [osiris] ? osiris.hswn.dk What is the IP-address of this host [127.0.0.1] ? 172.16.10.100 Where should I install the Xymon man-pages (/usr/local/man) ? The Xymon history webpage by default displays a 1-day graph of the history. It can also show a 1-week, 4-weeks and 1-year graphs, or any combination of these. Which graphs to show by default (1d/1w/4w/1y/all) [all] The Xymon history webpage can use a new method to create the summary graphs on the history page. This method gives a more accurate view (more detailed), but uses a fixed-width graph instead of the standard Big Brother graph that automatically resizes to fit your browser window. Use the new detailed Xymon history graph (y/n) [y] ? Tell me the display width (in pixels) to use for the history graph. This could be anything, but to eliminate as many rounding errors as possible, it is best to use a multiple of 24. The default value (960) is good on 1024x768 displays What width should I use for the graph [960] ? Using Linux Makefile settings Created Makefile with the necessary information to build Xymon Some defaults are used, so do look at the Makefile before continuing. Configuration complete - now run make (GNU make) to build the tools xymon-4.3.7/docs/critview-detail-acked.jpg0000664000175000017500000027265011070452713020032 0ustar henrikhenrikJFIFHHC  !"$"$CA" j !1A"QTV2SUWu#3746Baq$%5vFRbs&'8CDrt 9EGcd?!1AQRSa3q"2BC4br#c ?=>\ΐy)'TDD&<9l TUCJUN­:':O{Lغ`BWם9ԟ#3?YMb܇KNde4j.8L86g RJZ9u:wvo 5J:o "hNzO|G]w;V⨼KBmYHٔIih6h)#Q<-(afx[Pƞ߃P۩dUz4#$E m)̵jWʏ6 Ifx[PÖu՘Oz$>r N0k*ݑDry-'w +o5P\j2\draJC ]$JlATyI-xyzE!AZ6'&x"3bݐ5:g ҇K4drzV`Gd)j}6w1RbQʊ2Dֵ-&N)hRHW\UbHjuIji2٪ 3$:i5)RJp<-[3g Rn :ĚȢ:+1,r2-ᡃsy_ K<'fǬ^Qiԥ%Hܧ.KzZ=K&(wRHӅL {-oCpqle8iBtKtȰkQ I".<SU2s'Kel<FF_Q&ǖ(eiNũCiqmQУBӒ>*IQ!VLO}NJRRХ[ ۫xM]_PnZnRi 3MidM }Aorٞ0<-(cOknmRmJmˆuޒJ%sugb<-(cvšUY^Ld{ѽuZ;pNx$cG\,sZ%~U'KLiV #J;(ωim%@4u(V~%i4W(]` Ӗ}rD&URƆRdpYR:$IJS)ÑQ9NI<k%w*ɤg#TRjUN3E~KImq(ڌBZR"Ig&Y[g 9l g 9l eQLsXMjQ6IE舖yޙ\-dw]׹_=@Z;(^b5uʷitgV?$-oC[3)qa5҆GwE~L`*I[Ys:H_F;}iQ.Y{ UT$qKBLFK"=$FFy3<-(afx[P]Q-Z]F5"SdŚ!*!7DmI!J,$ʵx,\Bv[I͸%S4>WQ).SI"AA%I @_g bѫX«Ӫ/ tvwRӭaX2 n.{5R1oҪR`DwܓFҼd$"4['{r3ð{7'1IR̲hQJRٖ$5w)<-(afx[P W]ո/?RЛVeR$6eZA Fqz=F%cJHz-{~ BnYVSd%##2թ\O*>qfx[PÖTRJu$ϠS<*5UGmU+OFu[#:Qn!HJZ3,=&dHy;2SleQZ1}F40CiR(23-[3g Ue^"ϩ̸ zn})JianҒ_ԂezԬezU ^zW;M2*M;%+lЂQ+,I3"O -[3g S6JVQ}\)MJU961n-*A!Ipj3$i:v}F YujdYJBL%my=EQdIpwJu$Ϡzٞ1P5ZkS3ZEF|䴦CI8ЯJ%'2Y3V/~&T$®Ѿ8t[&Q9:N6fJKGJW T_mڄ:{ 4;y<s"<[3*:tڝ}(}-qNJk#x-M%yZR%x㸥S*F*hS"fLu&27I(F`-oC[3<A'-oC[3<fx[PÖ{ٞ0<-(cg 9l -oC[3<fx[PÖ{ٞ0<-(cg 9l -oC[3<fx[PÖ{ٞ0<-(cg 9l -oC[3<fx[PÖ{ٞ0<-(cg 9l -oC[3<fx[PÖ{ٞ0<-(cg 9l -oC[3<fx[PÖ{ٞ0<-(cg 9l -oCRb՚NR[nT"!iyd䑑[ft{MSl}֣IՂIqTSRjR #3ۘhݘIK".`ǗkYc&duCGeo-TpeѭdJWeJ]mGMA njZa0]9Ȟ==õ~o,Gz$+8^]?s+8^]?vOV{yfz?Հ52h:&6P=<,7Kfi$)XBP5JRFgMr5-4 Զf,LF7$t."=_l~'==Yc֤UbQ} )-]Z[Q)j#=&Fy.ɏAQj0@iKLv4H"7[3-rQE,?==õ~o,GzР[4[vu"lA1Gi=m(CQ% ChILnithia z3L%Æ ւ. QjNKecǵ~o,Gz}ߛ3jWp6N~5\b#6%hљ`Y)9.F\8}ߛ3;_l~'h[nIՑ9ӲR q ɥ*W$ɑdF|2cQ(QnL!,.!.FI22%)JRL==õ~o,Gzٺ!%JDIJ^AxjWp}ߛ3;_l~'ru [Ժ"!n˃Fyh/5C3!7GM*y<ړ1;JSZR\2;_l~'===SX~& lKdZII"cxJ4O^N~-2ӉNDd;Ogs>(dYI_kY` ;RS;RSGkYag7g=Xoԯyt!ԯyt#Qg7g=XvOV+8^]?s+8^]?vOV{yfz?Հ6JNOJNO5{yfz?Վ~釱YX͞Ҝ}&JU{#-R;~v| ˧v| ˧?{~#ekßͿ5` C_)C_)E_߈ozsolpXjWpjWpW6[^9-\/V9ڕ/.9ڕ/.Um~6F| Հ-v| ˧v| ˧?{~#ekßͿ5` C_)C_)E_߈ozsolpXjWpjWpW6[^9-\/V9ڕ/.9ڕ/.Um~6F| Հ-v| ˧!I4I2#/Vm~6F| Հ;妅amUb6YdL>T%љ͍l2Ä%'3?{~#ekAmbzAy˪)~"YQxQe%20`S%G!'u_rzB\)n*;&dIᦒ_  ]Z$]%z:%9>+qODy#Zv4F G}y)O` F0})IyӯZ;[m&KoS[}%Z'TIJRHzplt2#$,QM"- lvz+u-ڛQY Ҵ)'ǂdeFF4h+TFzܫ #x˒F iA))QȈ̈s;5]ɺnD s-6JL)OQՓ<}^bzN6Ij2#U>xk6j2)@8R#YA%:E25q,q!fkj6!K,2}$8* i-Qt5M$e$yo2ITe[m  9t}gQuu˰7<'LnSϲN'\4-'de8 ӛ3]..<-]xɞ EYΠJ5.,̘ʝ^:L ϠWT벥YuگɊO9m Jc)ϋJdx|2E]nAITD(24%\"=T-HICFg"%tTq)fҎ>V>2\R8i< S_}%4$5ʩ.Ԇ-JIJYHIede}*ZZ=Iю)VYX%)$ݑH5-RQDج)PnPf`;!qrRPHA*؆Г+MdL:-0,N6IuЍJR[ZTe3ҶA){jNM2ͯV;iZJu>Nr34Զ)C6$d."rnsW.,ed%DFj4-R令 JK1{;۔hܱZm|=u::(*%)rwHѧ4l'}9 =\DmSFfShWpu-8L:X#i㌖x:ŋDv]sۭLb*2jgy*;ĒUZcR#6m O+HfM-M-Nu8H)M C[ T#7*+N$aDFYIȏ3GkvQgY3ZDZ Qe2ۣ'6,KI,JLąfSaG*(kntD~JLgz+DMۛ4).>i2--8@R9ߖ~SDN#':#)\47dQU* iN2{ByR׌hJ4z":nJR$E8䔩[,[n9:{ 5t\tF%jִ &$BOm!3\t&A0ZXZZ~{g*d=rV eJȅVa礒˓6S.süRXW]"lJb Rw2%%saTi,Fb=z伯)'r}/FpxA]zO仞d"k O838*Wng+uk6_ΧmyPܵi2|Rfq+""^ta;VEBg9pu|71Me+B6I`YlU6lŒDjIEJKNg$ddJ%Q+[rh*4.dR(lSD`i{ܨVjv" fByLHk[nӕLdM0#%1z8X<-[{rwR\{;1$H׌q<0k{rwR\{;1$H׌q<0tVC~ͭg\SSfjE q[Ie.%{IUDem=Ť"͏fZ74RQ$쇜iDGKΚ BzFiI=(өEB@PVoʊ'xӉ%!XQRdx2#n*ZljvJd*J#SEʉV_io!dDDlf> хOَdVė":7TNJZU&|R~NN2{bT؋j @q%$Iid<4JȻ8NViuZڻ BUY0\ mՒZuh{KāVާ:ZqNmf|YULEbW{(77%M;5%z5`cV3:V|IR. dg*}nmTd̏+L2Auwa4`Gt2@nSi5inQl #Q#@U]Ǡܗs6sJ&:nfV4M*m5"M-Ց7͕!$j6҃#$cHK^]zʧ^p>hTɄu y4IVˏt'LW&ۚ i1!mNU1Q4KSh##`d1mJƕISҹ]n/&$6ih& "R5ȴG>\g0օwm2ⳌE<4QvMgwS0j06hND%Z҃A`k-jNڱkÇ /L.&kj4!+( ,Ԓ֬pk6!%仪:7ɥ^p_ uZNښEȬLQfrO/Im+v5E $02+Q,|"XD$mW돦YG_*LG+' 'D¢J2.ң_^@lKڕr:ʠnd'ˮ6Fn6A6IQR2Ҭ=лח99^w[ktWV3kM*6iTjMazƘ=Jdi-TX3(r./>[F9rrM< U[٦ijG%5LLrRt6)Z֒##3"2,zG8U',SVƭbdTk2H'DTm),xAO7D{NߗXvѪ~2mkRЇIiխ$l Tvgk~ZNtn% V~ɖH\_X63!9"<Bi]3H\UuW9cQ;͜ʢa"4n)I)5RzHՁm6Xs4f{Ntc"N8gQ7Ww{sr^W>FtҗVNt8:p]>lg?օDefKQ&XAZK&}"3"19_QCxÓ:ymo22g%Se;úѧ|s)F ]إYJ0Iғ\_ЗIðy pҕk|zVO =y/QY7!RḦIW I222<xȌ,Sfi:r3EɛorfO$:!Dm8"5ee"el \.䧫vlg"LhI/!KRٵ&wJ.'Wfp܊dmI%ZG56$%YJY;TfDJ3ldVܗL׺޶Ղ5c8,㠆WP|$dddd}҉]+QBPCLԪٔ'Nb-27WiԮ I%J<f^ZLVߩ.tgRĸ"IГ2iZ+qG-v-rFRPn[zG3TêoQ/RMXOq3 x`ܴffeP2CJ|g*2t4K4OjQcYկ{ N'f o.`6v`mtv]m꫰'dZm3HiI(9ɒTdFIChViRi:q%d&D67'-[vǡ;&IqB)B9li[k&߫[k&߫ h)u>^&See^I9Pվtn.2j'7N%g7>[k&߫[k&߫ h)u>]'#T~Bʣ6i+N}OB2ES!Mwkm-oՇm-oՆ_mG9m9m|mm٫S=5c=5c}۞MV۞MV5~ѴR}离}@离}A󏶖{sɷö{sɷf6]O}}>q_ny6Xv_ny6XlFKs>[k&߫[k&߫ h)u>O 0#Al) ,RoD]D=m|mm٫S=5c=5c}۞MV۞MV5~ѴR}离}@离}A󏶖{sɷö{sɷf6]O}4̻LBv{N%6 ԧB]cu$zuq?v_ny6Xv_ny6XlFKU3ƂRi,%oMFy4FEF离}A󏶖{sɷö{sɷf6]O}}>q_ny6Xv_ny6XlFKs>[k&߫[k&߫ h)u>s]> s]> Km~=aKm~=aWE.zk_zk_8imǷ<~;imǷ<~6jh9MwkMwkm-oՇm-oՆ_mG9m9m|٩ly%JD3  8N:Fq>]> s]> k;M,@f ۏ}}>dv|?;|siE=5c=5cv";\ogoMwkMwkGox7ȳ]> s]> k;M,@f oޗ_WD:EYFk!۪OJ]y+gRSďe=/e=th{|sYEv"vU ®j"VF}\Y7c6&מdvoox7ȳL+9N3|siEv" ,\5BW5A!۬JP,Tҩ5,`\G8|sYEv"trQwQYG}gݥj]"]1IhJqYR)JRXJHsMwkGox7ȳdv玳|?;|sYE=5c=5cv";\ogoMwkMwkGox7ȳ]> s]> k:,@f o}}>dv|?;|siE=5c=5cv";\ogoMwkMwkGox7ȳ]> /P&j=9Վ$]m{sMI[,È $dFh#Hk\ō[]94.  JL3gA O?}v[*n;M6˨Zr09-:4|6ڗ<4M1_IcneRZl1,$u$dFe$?K?VB6,Ma؜\98A!Jm[I׫;Ӆ+QIkȵlYDu7XӨ6ҴFJBi5)#3"RN):mwzm'IdLsjRPhtLRCK*&Х2GLJH-M ̇oE\F\aM L1Jt钌RLĆ2Ma㮆 cO>}o!Fڗdnw;,g9ΩA"K0y+s4MF$Zp\s?;!4=!)uD zfDF$uXSΫ9lD*:lNRQvXZpgdj{w8QN3in25n+fI)Ԕ`b1H0r,j}ZTcGM+B2Vq]3,G "lԥEjKjnUejI6M:)8ԇYJȌ2uJR,*Di]]#u Y)iҕoj2O#;hKڞ\l}V^FN4•]9b"G <COg+hՊ/(3F֣6KOpǎtC~mM +6*9>KTM.ҒK{'v&YR3&oZ _y[mpљb6g\Tymĥe.+S"3)W捜Բ&Ԭ&+C2R{$fGo˪ɇpUI_Cv8LGʎLt\))в($J%`RzFvSe8[TKYJ{rjWu,'b- eQh0$e('M(fx$RTJQj'gI<60gqo7$Q=E%zIШi46cT%cQ Km5: g?qV~bTj*ޫ KFiTRi2M*(G՝uG)Oq#UpؗE<ǪksXuIBII(Ȓy,4;JNp2˽iRSud4᫺2\X&I$@:- (68sX.|]֍:KV bmҩQQ!x#Mjh=Zq3;zfkP/[jd{bݥJjE9J찇Җz 2#ƭ\qOm+6K8J5@Z ?'sTFu#83dJIcqTu:w~̫ێ.ә-y:9Hd-%86Js8x6߾S\ʨF3JFN2CMZV4ܨE2&"{0Gy/PG]y/PG{" D1=B $ =B0000"wþ2VKFAd#m⍿[H5/V֜(ˡYZ{ԮY~櫲߬]GqD}p͗oAG^.%l6=b[Y*z]3^DjI[i%=K&fgǖ٬Fk S) R)r8=Y4*A]= ###.`$@w6Jӌ˼ +WOwKT-k_Ym3ٔ4P_߭ uv%]2`Q:?,['~ʟrG?'^מ}~tQcW'n^W'ʹLฮ#c}?.-dg4ekVFR1|l4dq(.'+ 1]1RulW](w/ D.%;ރ#:4UR>~#ϲh'ӿ?X:9Md!<SH mHwd>&OO?e?䏨@mFH&2~)G"U#)Cx'Tltx'ɠ|Lr uQ:ɠ|L;'2~ȀmFH'2~)G"U#)Ch' Tlth'ɠ|Lr uQ:|L;&2~ȀmFH&2~)G%uQ:ɠ|L;&2~ȀmFH'2~)G"U#)Cx'Tltx'ɠ|Lr AU#)Cx'Tlth'|Lr]Bn6:G]4Sd>&OO9 wd>&OO?e?n6:G]4Sd>&OO9"uQ:ɠ|L;'2~ȀmFH&2~)G$AuQ7b5BeJ3Y0e4@=J,R;Ӧ"D 9'A`$ ! '@ $@$þ;C7K{~^D&=6 o<>LsX@X@wƞkMfrDδTi^0gq eJ=>-eHS/4j$2d>%(w~LSz[ S%STu/B&H^L@@ګ0\zb4FdgCgFZzE~12]Rʍ+#"J{\LCr-(#&ɬ$ > zGh@N$I6m_TzL%+伹^c]x|3 Jyd.wdfY"<9-&իj{SRjoy4%YeGL(%R*9Hm%RԒBX#IdƪrPaŵhIM7.Lc 3<'J>EFn}^k'[{E`DܕT 23(}cpm$|K #3K8P|.zMDY&\Z).wOaIQKNRyx6u6ݐ*r%O-)Z(F]HxHr8;))NOV6Ltiude O>G%m绊k-A^.ETd~qbaGzjV mu jrB*qMJM2eX՜*[[+p"c|*ޱwwF#ރNWz mF#?Xwb$ >' ' O|@^6f÷ZEhSiv"Kti'%8'):?:5OݩQWA*d)s$LgFxɖ}趂rsQq%u2#S",mGS"S.iTʒFHOJGX"3ׅdP6=;gUZUwJu\hKj(RC*5UX tnl񫎱]`"ˆ""kQq9qϟ VKM c6An[GKUw4i:ߧZ4-: E -E hIaub-Y32,gþ?F[G]ԶsR+0gi錤jIFMJ$@ U"+&LF-ɫy .&k[PvV֌$x_Zډ9*RT1)N2It<ͼG]JQԩU>ҵGuϼ^IbM* LBR+ N V@u5gTtS*erBPb:eZfY>Hcm|ZT!6ᥙKcrnRq;/mv>`[QD9Qʢ9&BzS(#ќ~q'rbQDH7rfJ"2 gNx^6GM:-W"ae)/uAi4+36?aTTˍuai#Rx@VcǙb$FiҢ>FFddbFiiirbcKnh)ݻi-*I7l6)"jϸU և A.q:VAɡ*'\pgKkkVJi˶#1#pT[el'ҽ]e@E/eijK[ny6]oy_wt==<8]X u)'hSSZ +#WI3|l`ܣR% lL!ה"2VFI#5`ˆH]"^$PR4Jp5ȋe[SYf)Sq)cg6-mL=f*}L)_qNzGrVA-&;7Rݨ̗Ybnj'")4m(ԳUܩ_ώ:36mh-۶^㩜5p0qI2Ig2?5KINŒҚ}n!EJdF.=UOiɤ^oWRinFޢCl%$IOtgݟYtc'T\EJiƉϘ $qf" ?TvyGc4bۿSjtWu9smUvsOMaUt^:DԬ\2H$c do);*w9Nyo~_ߜugvOo\yt t jb$j$%<%==@r5ɶKN![tزnԲ6ҦZ?q3::l"UޓHMj)% Zk%(zIg$EcT[wO[ MWSAj$5$l{5~QyHGoP>]t"4vx3#GтZv}bUhrJNɰM KV 2Cޚu2JR$q^O?pgQL?85 &]Zi'OtDgI_Qڴ3grJKPςi%!#򋊔ZϤdWPcS3`VgUj:V$F8i$GYu{k.PS(ԳPor7.nRHŵIpvULtlgS!5=4r,J- =?O?i 5(QKʄO 8y-D33iwe'cv=n9C5Fi#DK)3.+:kQbdǐ4ɤ)zKx.'M~:K&Fh97);-DgI|xm+>ΧQwmԭjeM S&oSa/$_J8,:39vq\a * G$927=% `J3FڝT;.qT&Ĭ$FjqL%M$ҲSܥ?ˎz5E@l WߠNDDHy)F3ޗ<:Lo_c<_9Q] l?7;Bߢz2V O#.~oa?m/ר.lB[c'l?76ŎkaBec'l?76ŎkaB Wh]> O~50ˡZB O~50ˡZ_h]>vX Co|XFt+n,лa}!l?76ŎkaBec'l?76ŎkaB!- O~50ˡ[u_h]>vX Co|XFt+PWh]> O~50ˡZBh]>_9Q] l?7;Bߢz2V,лa}";Bߢz2V,лa}";Bߢz2Vl?7Gh]>_9Q] l?7;Bߢz2V,лa}!l?76ŎkaBevc'лa}!,s^  +.~oa?vc' EceЭ@Y}vX C.~oa?m/ר.h OvX Co|XFt+cBh]>_9Q] l?7;Bߢz2V;cKkB}: (TKiLKI`|sFeӪSk܆})/.XR%1_]3ıإKa̒bW|pU_,?FFG@)['{L l=ci#ƮOXhfk%tl)]#[#ǫD3\|f>0347F#c-шXGF+2G~ ~15ǁŃ$ I}BO|A O|@  OP' $ I$@AH" 0B  aҝo%=~/_C7K{~^D&9,],T;オcATRnLRPߔ %(i,#3Ү>E6r:O@jF_hzFES2C?Z?AFy*C1;'*^kYc]>zSF)=N.WH1ɲ|&[놽"fϤ^=J;8zŭ]kON uM7׏FS48F#cv_jzULϫǟOg zdqWz wl-@sgW-K~6B:߂cwl2HRxvs1{Hɦq vG{;WopgzcJmm>g}Bd{7~Q1{H6w4i m>嘳8k{;Wk{;WrYh˵+Dv~Q1{Hki,ř{[?(ޘҽOk{;WrYh˵+Dv~Q1{Hki,ř{[?(ޘҽ;[?(ޘҽ5bO|v=lLi^lLi^}1fq;.6w4i6w4i m>嘳8k{;WopgzcJY3k{;Wk{;WrYh˵+C+C[Of,4cFƕ!Fƕ!ܳgopgzcJopgzcJY3k{;WopgzcJY3v~Q1{H6w4i m>嘳8op#gzcJlLi^}1fq;.6w4i6w4i m>嘳8vxlLi^#+C[Of,<ȶopgzcJlLi^}1fq;.6w4iFƕ!ܳgA;ٽ+CÏ+C[Of,4cFƕ"{[?(ޘҽ5b˵+Dv~Q1{Hki,ř1vq1{Hv~Q1{Hki,řƀ[?(ޘҽGk{;WrYylFƕ";[?(ޘҽ5b˵Ï+Dv~Q1{Hki,ř{[?(ޘҽ;[?(ޘҽ5b v]lLi^lLi^}1fq;6w4i6w4i m>嘳8k{;WopgzcJY3v~Q1{H6w4i m>嘳8opgzcJlLi^}1fq;6w4i6w4i m>嘳8k{;Wk{;WrYpǵ+C+C[Of,8cFƕ!Fƕ!ܳgopgzcJopgzcJY3~Q1{Hv~Q1{Hki,řǘwb{7~Q1{Hv~Q1{Hki,řƀ{[?(ޘҽ;[?(ޘҽ5bo+FA]?NZs̉Dë4䒬jpxh2Jv o<>Lzlc} ,y)x}2J9?:1?7|s2u_b>}fEПC_!t7 ['{tR2ԄzopܳQ t{߾3e1=J0bWCMBA~~PG3Q{i({FsA8>I]#ػhvUӏ g%NJ4FmG]<ϼF=m9I&W8n*:Ҋ_)T5#Lfainc MD]uT/˘m<ڲ)zrzI<:(m}֭}ˬۮOxa*JғQ(ҲVO%#"/J5.6Y\Xj\>wi|Rx<p+K?7vͦK >-b܉*B%ICFiN)ɑwyJ-2ڼ[0-(ԷfKiIۤFfp40e5GFڲ99 y7RTsdErdcV[jtX7K>UeNS).L]Kz)peLWs$Xwi緫JytO}%GS%%k4)i%%JL҂3=0,M%VpJ?8R L&L} 27̫:\F .얣o3DbBVa!vvϥB6Գ'nFxQylnKrZScˠU})F6.2' m 4K.'$- $rmܵy'-GfƲk-ڌVqKK  %&R]q3>#uhv,;o3Db;%5=XӟPhv,dy'+vԩjGBۦҷN8l,F2zK+e+uFm.:te(E'E7ZQՇdy'`vZ$W7=|h;%5=Xl;HbfZQՇdy'` b LKQj7z얣o3DcLY!j> F4OV'ZQՍ) Cd&ttX5N&xθfx"3F6jn)6d?*>mVBJdko/6}ܑȵVDf^'enBH5_KHY"%5=XvKQj7zbNf`_1,{5e]L 4;+I$)J—#iۜ[ UiVTIF}Ro*ZTiY+QDdE Y"ndy'j> F4OV-:f6KTu3C2L $$$,%HVHij(vuvSؓX1#©%R.Y {N3,%\O$.Z?QՈ얣o3Dc3e2a"76˹NN72E[G4e}[<ѳH[mӥK Rʡ$0o hF#Ҽ$X"j(v,!wԦ%5=XvKQj7zvRNtjx:tעX%4j ;Hu-G5=Xj> F4OV4Y!j> F4OV#ZQՍ9 b LKQj7z얣o3DcLY!j> F4OVqhi:eزC, ՝^Хi90WaG4BTA $% "?M n<>z^mv/OyliEj (U#k^R,=|hGdy'qFCdfZ?QՈ얣o3DcO eزC7=|h;%5=Xl;HbfZQՈ얣o3DcNBeزC7]qhGdy'~Y!A͝TMJ"/9 -RZq 4 %$ˤ;tF{fGy]EK4T/2\UL=[5{$KFQn(ϥjm&DxtsQJ $MJvKQj7z얣o3DcVL[l$UHb[xZjRUHF8vIZ[Ltj$HFo)ZZdZR$K}'QC"KQj7z얣o3DcPZGpE tYpMJWQ_Rsޏo6[+#sjom#s)Yջ4qQO NdS얣o3Da-Gfqػ8gi{C<[rUf;T_7mˆv'BQp}\-e*mZ?xH~[uYimC$wR$jdp|h;%5=X'Xo 2-DE91Vݻ9yQ!o2r M%IՃ"iE sThv,quK%,CRY332"" )COƒÊi]BZO JmdZA;wl=1 ïGUy8Fqt!ZgȖF|Hj,/X{TZd*S99$jqnĶzPDZ<PزB;%5=X˓Rd nI!d#A^XVxXiOم_9oQ%L[[o:mit$Ւ)E>BgܶR"=]q$LFj>dJS܆(,VF*YZ;ˡFJxVXV?dy'E~ۅZX@^6 ʞu48ZX"˥ZmkMCSrpIBxM[BRQCdVvKQj7z얣o3DcS(^38LܧJK%y*P&EGNsjmܣ[vM*E۰SZ(F4f|D"KQj7z얣o3DcL-Œ얣o3Da-GfƘeزC7=|h;%5=Xl;HbfZQՇdy'cl;HbfZQՇdy'` b LvcKcEK- n; i㔍$$jZq33oj9%_2>c} ,y)x}/.XR%1ߘ.*P~H㙕S#-޿($t l=w]_J8_F וEf?uM=X|SIl=cz ?Or계0_.]_?rC3m[Eu>OLk*e %iƮ>`ˎzt'Om#^pЩ(wjC2Zu,5"e,v>h]ܣcN4|18ͫ] =Xsk7Ag=X |79I#CZ ڵ >AKTXqO$do7ZxYQt;JWk%~U) uj>iiDiNg]_z^=\6鋞hJ]J ,txᴯL\B;o]8m+=P鯭<]J ,txᴯL\B;o]8m+=PkxO4,]J!zi^ɪRڕRJSKm<A)H8%+ԩ:RST@,GJT#ҽ1seԭ@Y]6鋞Om zb }o 慗Re޺""-m zbGJT!լ^˩}ʆRh4W(u7!;xKyBsт/J^4ۆTEԆҍJmdkֳRUԳ>%DCozi^޺tЇ8HpВI(k^I%< .ɟIzi^޺rS"*$HY-*%)*RRxyGJT#ҽ1sUhYu+/*Kd:^ufYJQLy/ҽ1sm zbhYu+^e޺enjTB`KmEB> mE("i'Ezi^ꃶҽ1s WkYu4 NvTKQ(gWN'$VAQ!I#:8]N*E2TQ+|ғE9츧c#VAhq)ZHd dY1no]8m+=Pv޺\S|NN&jY ,%6Y\ٵf)4H쭙U8.ӥj"5HR$jB|Ozi^ꃶҽ1s^˩WkJy_O(Ɲyh_qjhwә]NF;1+z*3y3m zbGJT'[[ERev޺NSvbhW",R)J2-DsIp)Iu 4NHS_]3ıإKc`fco߱K~`@X@w3'UF'9#fONOrGz?_/Tq:O@H=ܢgkdtI5rzǫ@3Y+kdtJ&=Z&91a1n֨JFi 9s*ܑJ8x%II%mezwUUrD2hCm; _tHk4d9Wz mF#?Xwb$ >]J"^˿R[DdӿZZJSt6 2Kj6KQAF]dpݐ9lt5CIQ*n&fR mByIu*I-qefG4b;.sUni2L'c3KKΥƒїKAi>Ji©ץVqM\rN&S*%J#liQc4$5^1]V?͠fIԥ():Ij%vJO))H0 .-R1ܜ".*Vqɖ Ȍ[6 O[t˙UO\S)ȚhuxרJY$&D$:]M*Bf,*4ˈ$#ZTFhWsў赩ˣ]GiLG]nԗ7h^)3#28#Z;mDҠWe6Cг(C ziݒ]lsx1oI3S_<#̖Oexj#liEIzؐ8+V-*ߨT[B)Z^q?LE%4zVYR9Bqn\~E;mfcF%4 OP {$ !$:mv/OyliEj mv/OyliEj =~?ċ.@YRzH@zv*T=YODw]Җ&h[BӒRTt1;.K-"X$zLOAIu-*Czau<$̌eӓQ%{dT׭#k)'kMPK7"q$f)I=j"=gf8t:K'o;QUљj,􇐔8<75ωcCe-.dߖ4ʊfDŽnT׭n2<'QeIdS"XmP귝:W2 Mȩ."D-6 ԔJeBL!\ME~[;'+dӃ~Fa' rESS 4mNdQI%HxIVfImF@ B^BStQҖֳlĖs$J ɖZ_;Η5.eKa6NO*鬖fI\-$J'O*n(BngUy$뼅:R%$I$s^dg>kuܷUEaq?n'nҪ'LrGi40֒#s*)ڵwJn'ǟ$Y4k5[ܭHxf}nkgzHl֧*EV[SJRpzXoX+nEBSInME%!8tݞaF4(;e4{kIqۛ+n'gXw]HrUFU'#ϑ!eF]SJh%gCJNDGY6>ǨKqUezMj̇Sm[-R7 ޴D V5ofdJve6sj*.-i[QB\Ɩ۪ޝ,DDFFhɫIȅaX6zTM1&5JX&㠋zJ32KV0^ wO{IDpFafֻ&$Gz~ϩRm-ˊ|wݯ._$Rir<@ze 0|oj,E{[!Z' |WbZGҝo%=~/_C7K{~^D&;J9?:1?7|s2u_b>~5zG@=C[#ƮOXH1=Z]#[#WH11>07ǥL# ѻaV<,&nRa+L"GDjRUl&IᨏI2g#ޱ ]}G[ nk&i x,҂3]J"^˿R[DdӿZ("K̹:O ;,$jX_d_gˮmgu[;7rZ9FKyqJo<B50t-6x#a'{&fk$$dE~0gWHEbJhO~15%*RrGghnΚ;-&\3GiI$#~&ρ%ƙqO:ȵ;j@SGߤTФh2[5dE˪h*Щ[GKܦOO8#7KA'3-: :f[rEA2صy2`6am|F e[jN6QDӒt[R?rD4(Ղ2uaWrTmʍ2΄ʪyé-p-'yDt'iJuvW)*pSޘJe-fJTMFY#DG )z $Ox@$ uPoX-=>_܁oX-=>_܁eӈ k*OP IB OPNit**VS DuF(4tdD' _OLUJ"-9 A7Kp$]:N:5I'fmţݘRUL;=IҧLAA3Ki2Q4tdD'oY4TEHz\" NDFYJrećs;kU*]oh'Ъ8fGunBy{I 3u]%LHt]}WDGIU%׉I8FII3Qn1㞑Z_K"MUqTjw0jqfdHZRRgZ[i?y *d9aemW+GZ) Y[UַB4NĴ;C7K{~^D&=6 o<>Lw |s2u_b:ndTHg$| A3~5C̿Hl:2K\#G@)y/'6ORms&/-sg L=cңS.~WĮmz۵7?sҺFGIN&&Svֵ[k_}~Ml ܲB?֋~1ܳmsv?u߶)шXVY}N2;,\ݡ5mYhoGwƨS޲c]kLG-pͥz߷ ߂cj-#_rmt, W p W p@Huвõ3i^7;3i^7¶>T~_P\/;_6C~#;_6C~+Pi"Quв3i^7{3i^7´i"Quв3i^7;3i^7µi"Quв3i^7J!nHJ!n W pO|5ZGo]t, W pvkfҽoۅh,"#1ѭ.=RoӇ9hL?WV_ޚҜiM'x m3i^7J!n}F=GXN.#L4zI}=)N e? pSlU'Ъ HSCNgiYj~ߨv݁Bͥz߷&pL=XCȦ춢-lK"_—n\5XʦPvG%mq {NHקӎ=)QrnYV}V^4Ns"uZGo]t; W p W pZ7iP*MG)0LDiV$K}ۂLb>TF72s%ҳ,".`vm+ІG`vm+Іr%5ySN[q Cqmn+JA-F\ &DfX<:%\q(9 NjuO #<HyJ!nJ!n7.l2yvuɷJ4sdl*VMfSj imr" J fQg#_.`vm+І;3i^7!{'rU"q\+зYeWeKZjIHp豞fIIS ȧis^rFӣ'Bw~wCͥz߷ͥz߷'ۃ9󘪜՜rHƞ upTԩ*1OIb#481:#Y}EC+ͥz߷ͥz߷ ؆l՛˦÷ȑrgF Dr&ne fJQr牆H[J!nJ!n=j]Zԩ!&y\U.ud@ZM%d".EoB5m*_4ԣv7iΜgAThttL.9 Lԓy Z%#&1ÆEL24JLioKmRֲ"R2ɞmB %CҜJDxk ƒTQ'Y$*Vm7Q]y||Cͥz߷ͥz߷f淩*6A5)\BdTdj% ItXQO1TBD#Oq 9%fX?bڽ#_.`vm+І;3i^7Èn [H*GxzE1ڍ:OO72FHYJFGBuzGo]t, W p W p@5ZGo]t, W p W p@j~ߨY}BB V/] +;_6C~BZj~ߨY}B᷷)emr.QN y>Ntbsȇ:ܿ:yDP!yϘ#tO›ʋ!4/[ZB Ju21F/gYac, W pvkfҽoۅjNHuв3i^7J!nHJ!n W p@5ZGo]t, W pvkfҽoۅhԤ4UJV&C(TihSDh%5j>Ic} ,y)x}/.XR%1ߘ.*P~H㙕S#-޿Wѿ_d:/mR@ȡ 42qQfJ_i=c~J<ݭ3}k[,NaQF)\cnQ߶k߶g|de$ľ0bG z֡̋X/d9#4:1 */=O3-2Tl89ݪ1mmU=lE8]&1pocgګCð{?[;_bQ[Lo|u[;_b'E<{VW؄vo|ukc8aέZaέZ>y?Acvo|u[;_b l|~>ճU!=:wj4e}g}֫B;ul}5 pǰ{VW؃{VW؃[<{ul};ul}5 h˰{VW؃{VW؃[<ƀ{ul}OaέZ>y?Ac])SNNy,GT%<3#8xㄛ1]jzm1TjԺEIx3HjDFf={ul};ul}Fyō !A6Z$>[^\undI'N"ǹ3m{;3K*۰f-D{l<[ A!])#3JH̸n[;_b[;_bļ`6]mn+ kkp545LZV %-iZ75Q34ENEetY6Fn;J(ւ?Yvo|uvo|ub^y0{. UfR1o)$&lNea2e%H>!ٴʅ{gw K): o-ۘ(.*ҧ#˰{VW؃{VW؄VӨ*=$N\v(bBޢ5'&dGJi2.:ڙEIMe6*/-rsiG'8$"*m]>l9)%EĞP*=*±g%=|R~cϵ"+n8V I)5R]q3>"1<<6%LE&Kh5!kV )JgDf|ghꅀDOrZW)R ΦQ6#ܚ8>ճU!:wj%_LX ˚NJ{/K7ż圧V^YC=ORnj&*Pn%Odp^PY7Rja83"΃@ġ*M7w,Ipx2Ȍl+(bipݝiH\x1+FNI4ԧbfdJQjjV Ƽ`7m\p{>t }0nNO&.;*`quݑH2Kԕ.2LiYw&:LAqul};ul}N5tۗLRvYݸnȴYEhF6uˊxjvu&gTiO+ /(4 3QIڒ].{l}GaέZ [Ly ꇶfQWjŌBb8NIȱ~o|u[;_bsOLXɦë؅=Q'HPmM6f̤z M9GԮ䨴¿GgF鬿yo|ujڤ mm~Di Je,sԞE2xFOמLX1h1&U \mȍ*ˉh~G!ieܴ vD[Q)SIɤDxMDjuIt,hϷul};UU Ƽ`Wѷg_R+$Ki# sx:f$พjԗ Uɭv/R%J.O:A1w .jVG=[}g}֫Cݫj7ᵵQ) yW$8iΓRy Q=>J`ag6OcKGVl`q2|Ydug\՛2u:bE嶢m/ཇgh;ul}GaέZ Ƽ`ϥ]5ifLAml,Ɋsm% _C&o=X(6]ٶi豝W=g\mhI]ֈC3J`h6P>nIuOճU!ml|~BdV}g}֫B;ul}5 Pvocg}֫B;ul}5 pǰ{VW؃{VW؃[<ul};ul}5 hǰ{VW؃{VW؃[<ul}GaέZ>y?Acvo|u[;_b l|~ v]:wj#{VW؃[<{ul};ul}5 pǰ{VW؃{VW؃[<{ul};ul}5 pǰ{VW؃{VW؃[<ul};ul}5 hǰ{VW؃{VW؃[<ǘwbv}g}֫A}g}֫AOX@v=:wj:wjubE+ԙDݿ2ۈmǦ) $R:Qp+Dܥ_2>c} ,y)x}/.XR%1ߘ.*P~H㙓S#-޿}J N}T ԥGcs8JOnuZ܏֍﹣77?4-G~"jmb|J_e\?:0o{׋/(c8?c?~ ?׋Z7KA)*ڑ?s\CuO#v=Y"1˩@:_J?b R~}?s,/+D{xYĿiNCdϞ8:~/CZ%HY!=p{xYĿi{k?x#Cd&~|ֳ~8:~/G0 b Lcg=p{xYĿi{k?x#Cd&~|ֳ~8:~/G0 b Lcg=p{xYĿi{k?x#Cd&~|ֳ~8:~/G0 b LcgP^*FɑkDkST}g9NɍIS%Çw?(l;Hbg"k?~w ]YU')Tr0ޭ*CfzIIKS T>Dk)Roeբ˗(3JXq_XԅqIˆrPj)~Ef=]Y'*=*9IoVɕ!=$ K)R]*IZߝìɵoШ2Jɩɵf PDGl:r_6Ժ5GđO&W)CJ]27mIƌvz=$.ewO;T6,1k)2 -f%`ǻuAsOGIRBjL{\6eQBR6֖}g RADDөC2ʈ4# ˨Q(KQtaF)Ig $MWtYGewO;Ǎ[:>fȞeOa$eK (%rDjG<ฝqQRlvdT6=7&4Gԩ;#s[yZM%"{+|e6 _+SpNjZ! ZH׬҅ZtrFCeV߶dAQ.XvTOܭzd0dz+(үtci[ڈO}l%iY"PiLju:HFჇ_вD+rk?~w >&"Y7TrR02#BY2ʲ1c{mXh,fxUg *w*YDPVQI2>>d{??l hv,+~k?~w ܳE]qVK:7OrY)q9΂.'PcM_CeزC/{k?x!cgXVKCmMXQ5rj]6Tv*I%NqnJSȈg^mESgկh;=V|j (aieR7$J2G[4~eزC1g ȝ:ʼn˄BҴ)&FFF\ kjwh˝sTiۨWYE1Y[\0Z׼Y> ;RZϤ\0nRJ[O-bCn*D0nK$I')^26:nRѺ˱dRM-LEAFT٨,=ѲcM*KM+kBj$ӿD}zU1ڗg"}&F'zyH%JM8"|1'Rd5H̕, Mq&3И-<\wܔ2r;ѬiQI>٨,6I~1i%댕P5 5Ti ">*Kk>T}pUX(*tBPD\v S&4ڜB${%R 2{]xƩU)C]>#޹2CN/ RܣR̲j"&GdٺKk>T}p^;:ڙSbVuRDԥH"38gtn:nEN%Z+Q 4魾Nk =֤8(i<ڧj٨,? qd"Sﺷ]rj5)J:1XE_}kؤ+Q] YKD;C7K{~^D&=6 o<>Lh |s2u_b:ndTHg$| A3EjZSb%)$|I&o5Q rgCLZOGfcH..Mpa QISuorK?7W3ro?ioaoïޗgexkd_g_R{ QS33l7b~7ϩ7G*~a/3r 0%,).gM7b~qm/b=mEt_ތ3vO1?Xv]c9q ?8촴tZ}A7x'-vKhWod~q6Dx%uUQM7b~ͩm8Ѯ=G|'ܩx' |=m?ܟ6xOAx' |=m?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?ܟ6xO}e7x'&jm?{r8v=?x' |{r8v=?x' |Ǽ|'mM>{Ywb~7ɢڦq=Si8p]~0/273##/GE{jm?ܟ4-t\ܟKZڷ9XէZNt8wZ%SS *"ccy&^R#IJA%YAJ)2u6KF @TDǪ( LG8R;J#ԔJReIZU=P48F]z6L.\LϸRxH@E: p)¢)MJ̔"#qx"-I.<5&*q!39L7% J%CRW+#D}$5"MڕItHJ)$$RI"""""""!G]ä'[ҘuiQ\vCjAHyjlM&ddᨿyY0o(lnODea7iG\xtVcPPG!̖ ,<#Z |2Ƭak,ajΔ3۝Zr#i-؂l%HI+DE~ojnJL8%2!qM䔕JJO8` Wߟ?mG:m]!ǟ?un"v-$(YɟmI3".bH6qO_ѝy$WcԴF,z^-kbਝQN#R &BRZ5j4P\O2x,6Өbq9.&>[JtռR&yYQ9򻶦q=Si8pՊ>\ϢULEӼiĚV)3,E}Q̿%oB4SJuQ(o2yWSi8p{r8 ój;xy2c;C\Oi`}%_唷*UZS8oh0Jq4'-]6 KUZɷ"T%Xԓ[+B'9I\Λ6#DaIm%)""""(jm?ܟ6xO}e7x'&jm?ܟ6xO}'Iaj䖖IG(YtdAdc*u ճ.ҹ4r%GAwڛOǼ|'mM>{pr/ QP٨Ie9꒴*RdfFFYc˂2M2RYK$`ឭIҜrX,tnʮ}s jh)*Դ˜h䙯BtDYX,I}7gP> jT'cՔèydD!mJF.$"<\1>ʮ}s.} gȏ"M!B92I!Ni²dyXu]fFZM5>F#TkFm6䌏zLsvUsX럘;*c}EϢ5vnt6I5CCOEj rquiOw&ZNrXUc~`?0a>>݂+'ќLL} Jh6ѧV IᑖO4Z~ϣ'STy&-iNt[)DIə`%;*c}\1> "T{Zf[*Pĕ*I[-_rn2Q0}q(V7.UXD֜F;Jqk3u]E.u\1>ʮ}ssݽ]~tc[I%V)s:qJ$|kTyvTjy{-! QęCeW?u:\\춫oj,E{[!Z' |WbL;C7K{~^D&=6 o<>Lw |s2u_b:naFݛ'(CGZ~н Tލet l=XcC'm[;~K0ԫVq: jm[kPm[WRң$3 WH1`?X[CTzngf~j޸zt%̓]0#kղ?{\1][vݬG3ɕˣޱd9Xz[ [ũ6{\6CH]&qZ:1]1fXUv;C^T4,H&1Šŝ{\:ś|sś|k]Ɍ%j7][O@ != eVѳ^ƖyRy>QH8i4?4ml6Jpilj2IE{ujTQ5:‰-ˍLbq.j3YV|OtP OKJ((rSWK}ծp)EWii4jAZV#M$xzVY\9rkMxQ:mТYZKHf]Dgj ߷St5F@Iu"rVRkJ3$X"pˉϿ,٪:BTñ0\IMn8"VӣIJ\hi*tpIݭխ%x2Uy'^J[rc >灞|`F֤՗IG=Ql̗S2?Z.T*MJ}2nuQ]ye!{TꍬkQ+Ipg^Vטn=$rg1ΒqZt*,gHGk?E4&MO f>ʛ{Lv_QS )$J2Y 2##<ぐ6mV赔Dr&q㨍Di%(Ԓw$8\T%%+il(|F]DOT(aGE)15j5Q)$gxd:q;Ku?g-Z~*|1ij8ܯ'wq_v$~DRݝWvKf\};^6ҥDH5'f^lJK#%犈2.%]r]Kj+vraS% RRtdӍFfFDJ3`6FػJtrޘm"Kk#J4o0dJr eihm=4/j鐌!8~'~1woK+qwSv#vvoz鬛G wR:O1)T:ZCt,hqH$~uukhʺn$/u#F>v=HLCUfNDwZQCZ ӝX )Zx>\[s?WOnJw$iÎ1 ť-dX%wIĻiiX=*=Q.Le^2DI3ޫ3ùyQҞ~Ū\ K)uQSDDI/&IԼH#Б֪yv۴3^[M#'wQU.z;q%,ХGң#,YpN1i=KZ85k;5|ŷuf$@ bz !H@z ````D  0|Nav7Ǟ߿b/ Mo%=~/_.*PU\Urhht l5zDP16: jVfWH15:LzLs5ό7c S3Htb;2]NEٝTI}Or uef՝JtczT)9ROO?IcFtxݗ6r߿}JъA.ٌSSӛEUS%ItY,]xf#GDuϘIƚzMJjV*Ӥ̸PFDiuTϠT4iJ8;wx_+^ՙYglGlʠR'+n*Ek8hZVy`9NLQ'*ҥ]JB^MP'BJQ;YppDME_Ӑu!$fsIɹ[.\JfQ*4n%tJT&_>fyTK[+3Jլ [ ԣɸ [qB:&&2zxFdX-]|xKG/-?kh;qgK Ɣ;:lHͮ{Y= x"GGP̦v;r[miRpHpiQX>'JT;hm)7EM@-ԶKFgOd:ѯ^ВRX2u֞Uyŏ*ISe4WҥY9JYxW"336ZȽ*jkt{5g~Kdc2WiC\!6rJ97]ɹ(̉J"6ϛnQO[D\i<hmsffy3_I6iZ7ETp6;-J.٣vbӢ5HuUZڢ|!Me)3#,q.5j+`s\ 74#3-&EY+ΗFwxy7? +d͛P٧Rk  t-4agm(Gdg'2Om2o&-LD @z *PKd0eSn#$dxRLFebj:ZY̪%ϒdI7Vd]Ff-rlɆTC[sVܗu:+KI_rEJHcdC6.b"CDˎOiI(FHBI>:efsk>'-MKu{_?ʀ Jʍ=nl%ESWҹ'Yp{։XʰGɩd\uf͹6 RiԺ_$ң5qVu^דti VٴZmLȨ\ ?()ܷoM-68*-8ʰCVjԸd!s0U.5+OtXQ䌅vj>ɹiW. Pgɶ5&z[C4ȔJZМ`8UP(5J^:&J#9Tmj4,{j",=)<`-{n%{_CsPfݬ[.kKW8"OP IB OP HI| ;Jv o<>Lzlc} ,y)x}̱vR 8*?_xϣG#G#kd =& GA\cW'z 35FGIcբc|a3oJFz~ClQ&D!iT%84KÄ݆s1Գ#Y$ZrFdFiVK=CU.EE%1DmH>dI#IV{ϻLc1;ZEePrwǮ{W(7|=[ZSdFhO=ޖޓKҬɞB,kMμMΐG,pa ݸjdi,qΤSUAr8-LEFi#Fu!XV4,䌆qtzq?ſc{khۍm>ZRTm7IRvlmjE@sջ|McIe$E] Ө_R)4"dIZqFN4e$\G99]:Ut2IQřN3tSL(qRQtg?5^hPl%ϒm|y\ӶR6[MIZJ:T^j,oI&F2=jpc*OKu82aIG3!6ǫZRgNtܵY2̴)9Jozvѡ;ood`Vm26^k&N4Q$DE?.PL:dSvT̋ I;4wjr5$[1@l['i()3QVQ"~3D9(lѶLCJ%U5suFmz*х`mFmz*х`B7kIoǙzEZRJu))w̒Fx{yJ0XY3CM9&dqaKtKAxk2*Ǥz2TDFZ#.4j*qu53[\%d Foc9*N RQLiM*"0rUH7d PQk,R=I9rdϼK2XB(SSe%?n<|MpʥӪYL*tYʜYt)#1OjQ`[yr"(%!8gȋ)3d!:|RJfĔYL:ĆViVҢ>$de#IMDfcDgԤ2Cj$̵'8wX%˨@LIGs3.*|=,̐jdFxxTr L+,iN-_qI6\]*3U,gU51TkR&cS2SkODF@" ;&oПјIBѤ%n%JR'p$2 $Y a6PJ$eU炨~r;sj;QUaʨC5ȮMR ,)'Iw魩3lz-D=V7ÓU~ضk*[%d%k4e ̺ cK; 0 bHrJimX[iMEfJDx2,@k?gh$ÆӇNE.6DB&Xli7֧ ܙ8=|1R6P9v$IơNZΏbR ',j=8%d5 m:Nk(%u7x^T&sڸ9Gv4٬wEr=|l70+:(3>&hJsǨ̇6CK/*:%(RP '*Q̒^"u2tgPk{wo~χ$@ήRQjKT[in~:KJФiQT##>*;%;S!n +BiQ22i̬g<.;%=S!ZqDx4`u{@3ҧM*1iqC%k$%D2RRFi#Ƣ2CKM6H@ =B01(5*SLVY2q(ڸ,{Ǐ*՞8huĶ9pڌxYOzL8 `cIIIY󿝿/:VKZYNTTX:Gyim(ҕV#4$#####~G 4i f\zr䬼 Z Q ̔ԤH񨳌LwIaӎ$DJL̐"}fUN.>_1Fu>% 6ћM h'R⒔TeȺ&ݐNS*9SZ< ȔJJȍ*J̔$D3%շ2c%%uΠҧW+rdC!Z:dAxLȆ;\Qr}~uV:&6Q>*eSo![F'9A 4\JQ]$þ;C7K{~^D&=6 o<>LsX@X@wUr{W/gѣ5zG@C[#ƮOXH1=Z]#[#WH11>07ǥL# шXtb;6#]py;*Oj\$$۫u3 -($XWff8F+= 3š`h:4ݽ[12&1ƈ.jΫ3s[4ʚ5n$Ȓi4i3#u8GZųNV:|J4͏pHeQ@j`.i %(".<3IRMpEfέIMT//$ﵞyvU%ĚMBNRyAɑ%% %(t*ғĄߛiB%>MvLBI'v$Hp4ԓiI. REx}BMn_{=(9Nnwcr\PTjZdMUl$m?NˆpY鷬UFXE3!$NK9 $*VSШukZ}r/vZWdi6a}AMY 5q$Uԛ{B+Zd h!GNKiEB=qN U ygJr\F۞5YjM=!2D'yJBวA2VJUBQj2P:ܧkte3VHYikvNз_x|5wבEnilNtDsv4!Roo[Chqey=' QMTgUrn P7l!d҉B*##IgL>#]Gzu}՗'o{ä2vRjtRmo"#㧸Ru*i'/YY,^-Id!ⵦ@4QA)HQ<`R5>&s{=ۛmKWj4XS[5QqRPiZJBQF b΍2i3NV}K NzS 4x'=C!=@tGm䩵m۬AhzOJ4DGӁT:̘{1[[~xo\,NjQ:<#1PDU4t"_IXTS5T2*|:2$k&Gݯ.|dWFԥ8WJ[*sȳѫSfp:T(* iQ8y$fdX<{Ԧ[[ tv\CM`IJ-OH쒒gÚIuệqp#z2<Ϊ:o;%JBɥ҅D#BTFFZHeTsZۥ*DGMOp8TO^0-Y.<@e%kJxui/eWRnxi%$;-F} Rl:$X1rRת-PQ WnLFj) --nҒQ m ~^V{w^GwGe]Yju9mibC̤޶QVzOԔz]:-Z$jy!Kn!sIhk˭%]h=$d|qMu_t}չn୿#u{Mç,-(TZfgH7T[ ."LMKMFS0ʈI$,XWBff+U-OTo6=׿rNn${ȊcU:ٖN#I[&Ӆp=KW-3t[="7X`AF&$JX>8:@:pRXf|=Qqɭ7  JfP jMJUܺiKҥ6L;OE.SIF|3Yd[x̣Nڋs9Q)mkidLG| FFDb%UI[Z*2SM;_uwk <)Rl&':i^)s(]ʜ$$U-S~'&(7rPQQN+N1FzukQj. õ)QcCb+4hĭ-RZԥT(*1U_ }JoWmw;=;RO;)v|$,c83iVs۔(i*ә%4%Hl[6ԖFK)jOQFCm]VGt1 6RkSkֵuw$eI"FTd*5*tf4]pmZ,#mCXk:6ͨ^ݢrHۄUԗ՜>\(W)$4LRO)|AH"OSf٬M)`ߗ٭;.7}n/BHn$U.j9)%Iܼoӣj&ӫ8'N5$bTw)ܑөV'c9|#KjIi-VN zBneTӓO?r L.LmJ%TźKdKO k4$$R%P-ӕr&4S\9tvJJJ#IKRII?1eT7q;}m6)-F_5e2N0LFSܒd) Uwޯ$/L>|Ꝿ=}f Tj ܃HTɘ6 Y#kIFi.Č4z2 vסңy3,m YmHT,I%dh"pq@檤fSbk}}x߁xm-˪ƞqU& Rb#rDʴ:Mj°W CCi5wjhoMC0؇j[qukЕݠdj""g**@{eOonkp:*6 1+$6FBHQK#!O ˫ڔya&)m'o'l}&ujW= RP_%tw_ubjj֢-')'hqIIxJ׌.XIW6]rގ!P\M)1KQTJKsQ(ʉ+`bH'q QM~oz{Ķ+IV㧱mIDT<**6NZ.2ғ"F] =N=EI`楶]=^JM <4J4geeYljK&BQtWWnVUۙ2I$tiwIoffFڈ 4jRV9Վw Cxݮ)r[hfO$xGrf#B6*T4Ue&hNpi[jB$J ˃nj>j򃻻RIY,Ʃ]S[}M5mkT'k$|VI<2ˎ-.F1-4~'`èmWf#NU~RWPz%$ӥJRIJ]IJHD0|>^#=<+_z|^y*k9)Fi ;tԟ#YQKJI$-e&F]hUbjٶݔԫ~-.v*K"3&};4Ң!YF)[[NGVYs_͗jTԸpNjG&g%`7>PH"fB ,pJOz).CǗ%3h,#s({)c;)4+R =%>['"QSVV݇^|ylov@k~̓ &Il`OZu3n9TɎȣ$֓YQO=I,Ȑ-5"XEj@ J uk£KgSg+ ׭۾//woi2Qk6vid%SMokCdsNK`I"QM->E I_v~r6I| ;Jv o<>Lzlc} ,y)x}̱vR 8*?_xϣG#G#kd =& GA\cW'z 35FGIcբc|a3oJFޱwmF#ރNWz mF#?Xwb$ >' ' O|@ '@ $ @$@ bz !H@z ````D  0|Nav7Ǟ߿b/ Mo%=~/_.*PU\Urhht l5zDP16: jVfWH15:LzLs5ό7c S3Htb;28Htbc)ъA8wc\xX1L@P'  =B={BA@ =B OP$ IOP=B H"&0 })/.XR%1_]3ıإKc2J8*;િX}>t6R:O@*5rzGA\0J&6RFGIVf|a=*finGz[ޱ Wz e:1]1c!߂ck I $@=@'@' Ox@H@H:D'A`$ ! '@ $@$þ;C7K{~^D&9màl̥Vn,љzZ D)3Icmi=lo*$)![S4E&}ϙc (િX{オcE4}4r:O@H=ߢ`kdtI5rzǫ@3Y+kdtJ&=Z&91a1nGz$b:1]1wdb;Lc~ ~<,& XO@B{O|{z= !H@ D 'A$'@$@w>?CjqڿF3# (િX{オcE4}4r:O@H=ߢ`kdtI5rzǫ@3Y+kdtJ&=Z&91a1nGz$b:1]1wdb;Lc~ ~<,& XO@B{O|{?W'd4UٟQHa-'w!e|r;_zF[?u=;(n_P{}/ l~ޓoEI?wޢ?wܞ|{}/zF[бpܞ|{}//a:͂^:TRL1̈́xIhg"2H:D'A`$ ! '@ $@$þڿF3#nd6ь"ŶJ8*;િX}>t6R:O@*5rzGA\0J&6RFGIVf|a=*finGz[ޱ Wz e:1]1c!߂ck I $@=@lwçmfMk"[R*t6SNMaRTZD5qxslwý)p)nFFёm>Gx2>#I̾tڔJ+q:Rm TT8aJpK};VzIjmZ VrǪ4jB&8q$ӻhQil+i"mV>1)S-Al:G8qHlY\IQ2= ޶t֗ZZ5IJL~~IQQZ^2i?=KItI)z F 7Z1nڋӛ.U.Ju䶂-)I2἞Qď&{Z[1m9,GJu۔F\ 1lUTUQLl oZ3ɡIq B' ' O|@&)ŷLĎl4KqN/IFfgǠٷ+?q;f-@{³! vͽ[Y6og<l#m x@--AZ5mJ| %s uP"OP IB OP HI| ;Av!~gGd8݇m_Elʔ|pU_,wU\}>9['lt l0T5: j=cՠa5:Lltls |zT0F#cl1tbcl21&1CI?K:3quLIlFGJG#S?P^ֶo=S{zJYUKj udFd,J}m#܋jR*r)yJS4"tTlN~m* dG JɇDiADi,Eg9ۮZh 5|>##2Q|l]j67aFTÒ,BqdIu*$ I‹',}7[~b$&qcFc4J5:Ij>*Ҟ*ÕMٝKEq8KdhKʍdrx[[3nKnѩ-IRSsbJ Ƶ% IS3u=2B]SyqOhY.RKc "Y֕&/dr'Mw%9oJrA̐Km&H-7lPSbnh5HIIE9f}Wv"B{3j1UtlH)n2\HKNq>`S6Sy$;)Fr+q)JMM= Gä9 (;rPإ\ТׯxRD8LmSyZY)*Qғ=Ӷݚ:dZE>c6MӺ[˂ n 5ئv딪fʎѡw {$g-HQg#r-}Q[WݠxtYbN#4?:w e2./= Eʦ:k'mթSA+2'Qt䏫֧os: ȥ dGjK*54 D$\Ʈ lE\n%Lg*rTi4\,d.PZ&!Z4|5fÒm!Q:S$4ܟON\􆪦ϓKDK pi"xə۾ l:"J#NQ}5RDFx",q`QunS$ STb"^R$MJm)#JL DvKƓRc"%OOiH^`D׻ ʒڻ"iorc ;kޫ"ؗPq@:6 ś"SCK9N$)ZUфddGdܷ 154崹e -ynjzPɬ_&DD3"J9:g]l9D%3J<$*)D'RmFEq<e;nQhuIfۗY4f4)fa DzF$Ú@ܳ.J2ERNmٲ:Vxw(ĸgI{Q)D+>< 4c[IjtmOh}-\5-` uK[qD2 ܔٓX*yi%Y1Rj4:Ժ7agN JIyL  QM\mJqeʨ3*ĥ#}n$ԃsܚu`mf Z!m[;>s{U(ZVFJlρj#/쨳iY|{;N-"ڛ0WɴSNt#WEF- mzmU(r4d8v[iu 66:֕/DEvp] 5r엍imd21{A\moSTt>R M!1QtiJFx 灋oYBC\\DJ}mE2i$ 2*殁TmӪ$Z#NbQ ̍*6J22q~ w>?CjqڿF3# (િX{オcE4}4r:O@H=ߢ`kdtI5rzǫ@3Y+kdtJ&=Z&91a1nGz$b:1]1wdb;LT{JPc9]ΕJffyq% ]fY<q2!LcqqeD }.Ǭ[W>2[ \y4fW} 22J%ij/mz{W**E91ZTadsi iV=ɖr?7WM4.^V Jb'ileŐ+%%V褡FH2ef]$xZY 6pUj- DCm&t5#'Q`g@t;Ъ7M_MLYN2s6#s: TY,tѩ5zfUB#g4Q(4q3"#Eӵίcٸz J7՚b@R侤LQԮpvTcE PDTsp݌JqN`8 ZZҬ.JJ̭$TdEG ܮmvm]6kjA՘ɫnt):6y#<]mVFNmTq-ʴrm:ޅk򄙞> fD?/u.~==R9H* 5 =&n`өamEM3Uea>hN&+Q%zJXgm}ԷڒTD$Hi'*ZdG^ =:ET},s5O6V7Mp:Ң^ ) 8== fjU:DGfA.Kqn%IQ3m%gQ$'!2n^ʽackUrgǙ1e970d㆔+We&r tSq2"Rgz\a&e?(y Ӹn{2Q C60eV n D5' =Mo݃Bb;ɒZ24II2ʔ*G܊(`݆ܼޭ;I~-MUJ'+Y4~ )gܶzZAzx }s[UlA Fe;$[3Ax$ρp)8rmRm9Xѣ!䦴h6~)%o Y%e8.bGW,W ~WnLX=djOh. /,t7X\Ө~k[X2zTI=86'J%IJEu\rޱ.[~o)i uqk1\pߺzIsx$Q 8ݦ@nݰ뒝X¸L >1*58饵$Kᨌ\$wpvwn(zlJ+d|kGO(AdI5Tz]=lm${g[1R\ T[ 7nc'NTLˇ!zw֏-Lo5:+fqQmDFRԑ#df VmJeu;PiUXEWb&CdDM:?/ J̛Yq`m_Tm;}^Pb>7BtŒ#K8vQcS)#T*rFzdm8jiJ$*‹%,: aW ΠAj]^RHQ%Mř%9ɟx胵b44];ftku:*O8JOZPf| *1J7 eWhHWm*ytG\' ZւIpY:+B f!ijŋOԽ \JZRFzˣRrn]w}bCcS!UiPvnA94:EGX[Zv6 K*SfTRq), z F Z/2׍I5QQmoCE|J-e_xԣnb#?QW=]u*޷.I1Q̷j/nsP'H$F9GfrhA(k4ޗ}AKF'-e}OfX)N˂Ҩ Uknk5=[B戗Ė8x">zLb.vGDI{M ? XmM[ϭ/[h$eP}BV.~ƴ r'ݖt9tFZ0Q8(_Hw%#<Ӫ] ܑz[3kkr*%|NNF֤8ztEdDiJN?Kϧ\+e[NN.J]ubT,Azy8z^#J'sU8i̦6P]I46(KdDG DE[qsH$TFTcTd4RN:$$ynöv,]UKBs5Zd6Œ&iZI(iă {ϺvMV%Y4蔽WtĎRTGTsܨE}WWn'΋xZ\x촤4gŒ%t dW!Bj5&ݷmC)ɃS4iBTI$g5MnSvdmW5jtdÈPfՂ,@7$HUpl(tJKr1e J3hWrȻȁ26G̳:mJ[lAHMN6jFOt(ϻtB,Xԝk3-z*|ƫIKTddBn$֞%$tD`(&Mk>ҹF5CRʅ=ՠܸ( ,dȉX3."EH 0|.d6ь c?;!ȱmR 8*?_xϣG#G#kd =& GA\cW'z 35FGIcբc|a3oJFޱwmF#ރNWz mF#?Xwi ֖֤IO<ŮA' I!='={OP=BOx@$ uP"OP IB OP HI| ;Av!~gGd8݇m_Elʔ|pU_,wU\}>9['lt l0T5: j=cՠa5:Lltls |zT0F#cl1tbcl21&1C?X~->Ţ#,CH#("Cd"'xz`e>UGNeZ 'X.9Nէ:s#*P4-V\oF6N8 UJ2ɹk;2ZI͵gJdfFGdg,UL-dF.xXo2!?UWkpoSrb%Sx)rMAM-=E!(yNu`n ',J1VZy+JJӼa"RHT6?껲|!k5e4'B:2|ã7qڲjT5R-c3 zuon+lqiѩ(R kij[^ۦT7p)m&Hҭ%EI+.]29)Lo)4+RLZOQ2^&R49q83ky-&E`ZxnvJ0ۨx1 \QYQ#:̹[=.W:}Q5MN^6f$jA(ˆD|\@daaD~ZŅPTI p*[*#Qi2J &V[Ud;gi"YEԆAZMO-FdAB1T{Ҭ^;ۥOET4φ٥TESIdRV?X4g!f*mz z[)Vj3̮rˆ%EB#etCqӬMͳpՄiH:a1kq5 5eMYZRoN:M pȱ,Ȓb%rmu+hC#3=fdX^xƼYUs\oV)JT㎥kR\Ix3m+ZLX32W΍])ˡj]S}SL4kiR{=F\) &Mjs⭤8I23" yeyL׋ƫ&;1*db-8Lm$Nq[FzBJVDxҤX:pJw`-Jn҆rqSuD JVHBg !tSnʵZjT|b:JSRP}d-zL)2E.l]PTs&Eՙ#vBtXDi}k4Ҝ+d!%H6,Q*)TcAyθhSEiqܤ$iQlSۨ\0\U%nKʬSu- !R=S3IlZ3d\eMUceF.n6ˣz]fB\AƳ92EbL*~) #"ȧX5B,'[ފ9F)/+ZhnoU6:Ou*xeH7)*RD̒%[pZ$KݷvU-(]~9YSRlaB)kSi2&Wq NIDdfF,QU)5 /{EQ- iALW!dxR٤Ptj`BE,{ߍzح$fm ZTk% lZwʕ4"Ec$ҥK H=˜K%,6R:"iQ6Y4RMM-DJ-EK-HiҘLBD)vKE">BW*HquDɰ[iVM:O N'{2 eo*MA,թ$ÈFOkq CjI!jFrX3 6^vn"L"U2 68'oRI֒əg'w{'RV|Q.)6 Rp5ZX2,nx7Wk̙d(љ~D)y)A_æ(XA)DcDx~™L)p$>N%=Xi3";ԗYAcB  a]m_7aWvCbrW|pU_,?FFG@)['{L l=ci#ƮOXhfk%tl)]#[#ǫD3\|f>0347F#c-шXGF+2G~ ~15ǁŃ$ I}BO|A O|@  OP' $ I$@AH" 0B  a]m_7aWvCbrW|pU_,?FFG@)['{L l=ci#ƮOXhfk%tl)]#[#ǫD3\|f>0347F#c-шXGF+2G~ E_9Q(5fMT]=#Z1 v[.v*DuGƕH̲x!^ێ,Rm=zR9 ˎ[H[ &qrJV",$;EHP9%L.!4^!M%i. F8>{zWoۚFLnldrhudNlH3Or}ZnAk̊3%CSˈhhRrt̋yi.) *^C~]. <'Oٸ& FFzJV:үHqX(Fێ\P뭠̖4JM*ʒ"}m}S-Y)APL*)/-W&WPzRfy%``,k5PT( |we F[ggJK4$<dKivXF $ڋ3Z{ʋHu[**:*SҪ|k?8=d6\7"exaUXg!w҄ÿ-zj;G:U9Ixͧa$(ҝhRu(zI2ӳc&;̦$ Q6%;"Z:xyw+Q ۓ5)2 Sn9)"A 1yQ+*&TDeMHz5)Iw)%/KIdQsA]zT1Œ 15d2mimwJ3Z1yXj:ʤ~O-IҲ’JIfFFҭXbdT:ۦ@t6YY3jmF_e#4 B{ Nڕڗ9jI\LʟaﷄSӻ2~KvɉSIDUiQ7{Jwk-޷' _%]tނVj Nc)4KJjI]DfF,NVlW8ZijDk[Ŷ (QO!(Seʉ&I`ǽAl&ݸܩEf4&ĕ>6ִM(I<-͢R`ƦnK#5S"}˪޷.6!I$ՓɤᶁUEn:,>JL m-HJiJ 2"sFCY%M4rCO6Ip "WvBi_8W[HoQm1a*ը{ӤD]ʪ&ٍ![t xʥƌMKߠS2fZ$Iͳ+ЪqiM"IXm AehqsCk.'"Lu ȗ\ڊěTgS8o&lſ&Qq9-׃X¨dWW PSqrYBx< fd:x`UZKu¬ĸa+iqd"&gܖ87&٬2cJ(nˇ1(%FmTҔK 2#x>_S&RN&+eiY%i3J2>$|H̅T%bSڝQSI22X4JCv)-)ZJ7$j3Ru ^q3Rfg%6,Gdnj]Ïh}m'WtZ;udr:KyR% On% 3$-M 2<)I">1ZG]EfKXg'qR~Ztdc&$|bծ$jf᪏07ǥL# шXtb;6#ъAF+6#߂cd;Ld[S#bH N#3"3, qqgAb\p{07N:e+4d)f%gQ;i744]::o̒ar$CixJިi"#>hWZ[N]M+fO Q)tWHGU̧Q费Ҡ%!~4}oSJwZ˹$zԬ$l6i%)N5)N񬒂=Hk*ʒZZ ׇ)vr;MKx7'桴xMZ)+3z̗' Z[YFYQdM43-TgӪu6IkyɸRjkIDko&i2W@="-\5QLICu)hDV Kk.#._ovWS%I[ vB^CDMowE SYS#o2䩢 Z4#KԄ iI$X8ʿ2hB{ZٽmQŗG,ڔq_K[B 6SZrAR;$X^@4RZrSd-)ܶjB \5gEmRpJ*u(,J_[ImR$)+%i'xS*oЪTJUmb0YYq$6Gl9Yt ;h VL: ԫk%/hI!(%D,j.4 Mn}btTadkeF]6 0J՜涑&¦Qk*p*lTIS&KiHm .v U;*O"TGXlF5=:k-n(-IO$CU٬Nl9_>sΌ7?կH9 㩺ӧ,hևۑT֐%)eȈͯD}r;.\:j4y9F 9IHҼItN!-D:.Hi I$2fJAԬ^+4F]=5Y"$/47KZM4hRRj=hjO+ WaȮ 3?G6z.ׯ9g_8qo *aʝVK**iY Q4FdzIXQ<$`yT)Xnf]sνsNND~ n]I!~+URaMvҦMfiQkW|fy-5FgGE&<Điiqn_iɥ;W8՜y@i8oL-T9JM<Җ&4JY8siɦ1k·H~o.5T--ɓmxfd#WA)>-F.y*o?-KQq(#qz[AK<%%X_Z#E6NGKܙ%MAD\r\qV/ZDڹ\j5}y 4jStN%"RWO&<Ǭ0MP0 źfI)FVy$p_y"e\5Piuh,%,8-HYHg%ӭkqtb[Ȧqu9KX'vPTڍqfh3A7RgFr"V 6^K)&i?#1۷kVЙX6Q=Q%94o7m)$kB}x8/Zd4%8AhSŞ%N+JQ hRcTKQ[RW*b2%$̌KqHA"1jњYCu'*Ȉ4')%-Y0347F#c-шXGF+2G~ ~1nOb]RtZPyNYK9-qqgVKK2J.L 7Jq4kJMH7ZAS)̞Cv\yrt]*]"NZQ;A*49?)\k5:VP}Nqmmbn"RVY4)*δYJcQc H2d>\)5$06:igYFCM8]j#KWdžͧC{+fw\sqF}w,2>\OG 2"b\1` kRPL#QZ"2$uƻ^JL\U,7Zy-86bjkLid`$楚O;DgĈҰ?E5Z69O1jp\g)ԣ6 #Q2M¦RsۦS'6 C YqX"AՁU^s'nPYL)]SnBKn)JRLQ)&| :˒ritM>qq$Z֥6\@O|A dխ~U5.Z}^uy 7!Nr5% O&5F$uu^KbmV*8Se9dQnxAjQ5*mVI)tZV9vu6P9&Z[&an8UdDimzebeIifAQ)fRHHV+9vrrqDFMjڛ,'P%L]ٜr:L3ONpfyNXj[JtS ͝nzn6Y' 2:dT+Fە{ųbs{h3]'N%DG4fyҔ tM-w>ML*NR.4 D٩ emZ eN[)tJ4VSC(㧫^OxdFU]s[~Hʑ3fɶ IB;X%UlONyM- iե={c㈛Htƨ4<:2j6g0fDdFYb DҧpLJ2JbSfFFFFDd}FFG##"1pw]:@fTiR=tWd=*l^V+Yj6OjRU ʧT+ \5n)(mJT ɥTaȃk[F;{A-}+_rEDe;s|{ly)J{ZՃ"^e9,vd5CwGjN1On߄ۍ$n2[I*4Fj2]*q&UNqgvE`[%)ۦfENmJ.}oTUe6MM>JqW _m렵Ȗʔ5TiPµDIpZg5FX. 6WUF̢#7 $M'ޚ|Q4X"Q k\:USʼn*$ߓ5j}֕≕-ym ‰eݑ]bL !Q!8&qN' ,`]y3kJ~||-Kz>l:L5K.GY-;,q&fg% [p)§ JP35UNiƴ,׾Q6#ZT*GDrJeD:[R%*QI'JKQfR̪S*TxwY%Ȯ1 hZRyZZTX.mbh=j̶mGQLt4-=ozY*JG) Jg!&N]5~L0aEvۇMԈ 0vɱ=^K&)ʍg[5 Ri5Ob V# #D2]6CƐkNSJ;RwPi !*owl57Lh󬌷i045u9$(#h"D )ԥ(FjQ}Ԁ lS';~K&,'Y䓒lQ9+bP(v츧S-ilD4ɡ6"2TF3QMƵCRM+B*J. mbFPأeJ[xN-JR7udK#-q!I'}9tEfzJqMǓ!&4Գ2ԖJIRNzWLfoFYZq]%ĥED7|TSqSS.h !|' =[LկVVfFGj.zԫV[l4m!6[BHBK&fg:ͧC{+fw\sqF}w,2>\OG 24T>DvSChIhjMg":x<jMc]6\.ktJ B8q⥞{ꥴkbfHy z,դHCM2rޖ\z Ƭ4iVUtLIR\2&ը.QO(bPmiӫ\xUEҷhqA[FkV%#Li4nҡpTYiM%2sћZ\ i']ԣ-XR YZ1S]VQS:s%*&u4 rg"zҰ1.Lz]T)t%om $ X"3#KzU֧kiCΠ_+2L,L^,0001 $@w>?CjqڿF3# (િX{オcE4}4r:O@H=ߢ`kdtI5rzǫ@3Y+kdtJ&=Z&91a1nGz$b:1]1wdb;Lc~ ~<,& XO@B{O|{z= !H@ D 'A$'@$@w>?CjqڿF3# (િX{オcE4}4r:O@H=ߢ`kdtI5rzǫ@3Y+kdtJ&=Z&91a1nGz$b:1]1wdb;Lc~ ~<,& XO@B{O|{z= !H@ D 'A$'@$@w>?CjqڿF3# (િX{オcE4}4r:O@H=ߢ`kdtI5rzǫ@3Y+kdtJ&=Z&91a1nGz$b:1]1wdb;Lc~ ~<,& XO@B{O|{z= !H@ D 'A$'@$@w>lz}B RȦAa[D3f"kJs ղeThsi-4-It(Hˀ;f=kF)Znd2KJTjQ9IҢ>)21.ٖeDVioQ1 vCʔZ.@X6RTg2LEL2i3-E:PH+uO?lv!^"=x9 (w9 *S2SqMw$#\UV*t۷[-c2L&R5 d.E>Ցu0a3+g0?U}7 `4d6*U9]PU­.p6ݔX4vDQ-Œ9E.N ȌrTkpkcR'KhkSc T'\k5?7RASԼK1o+e>~Lj6ҽ'zO?͔c )#ҚO8M-$Fֵ`ԥ)F}ffgǸ4Pl&rXm&4 )=w#"N5>ޥDbf%TR%F9.8QY5E"%2S)v\FZ[Q)j#=&Fy.ɏJS!ɤ@[3C2A~FK[Jrdg4"=)PiRIBݡJJRCh",uq3>#ux;_d</H#vg1 @_3rĀY9|oax;_d</H#vg1 @_3rĀY9|oax;_d<hReɚsi%9 s rf%Z]Cpm)IR%R;ƉH3’x##z-?ՍGq$Q6D/R qV.×Ӿ2V<^Z\L8 o3."Ї "tFm(w 33OSv\*GS,k'}u&[RJRINTfjQXM}-LG<\D[#ZINT,d%OϦ).x[sa1G~@<t˲)+2J1e NK%5d EYڍaY\ȓ IayNҬGFCՏ_,sZ@y|oax;_d</H#vg1 @_3rĀY9|oax;_d</H#vg1 @_3rĀY9|oax;_d</H#vg1 @_3SQec cUzV'd?#m T*yF4a.w;~_3rĀdzr×v$x;__3 G/9|ob@,g0<r×v$x;__3 G/9|ob@,g0<r×v$x;__3 G/9|ob@,g0<r×v$x;__3 G/9|ob@,g0<r×v$x;__3 jXAڛn=F-EȔnGtAsܙAɒoH}N(ͲlK9<sC,#Ft|QxT9)E٣QJ5M̩GHRZ:Rgqg\m^yOhiA?jʸ犏^8ѴgvK[ TRw6V<'ӑxymon-4.3.7/docs/xymon-alerts.html0000664000175000017500000003545511535462534016527 0ustar henrikhenrik Configuring Xymon Alerts

Configuring Xymon Alerts

When something breaks, you want to know about it. Since you probably dont have the Xymon webpages in view all of the time, Xymon can generate alerts to draw your attention to problems. Alerts can go out as e-mail, or Xymon can run a script that takes care of activating a pager, sending an SMS, or however you prefer to get alerted.

A simple alert configuration

The configuration file for the Xymon alert module is ~/server/etc/alerts.cfg. This file consists of a number of rules that are matched against the name of the host that has a problem, the name of the service, the time of day and a number of other criteria. Each rule then has a number of recipients that receive the alert. For each recipient you can further refine the rules that need to be matched. An example:

	HOST=www.foo.com
		MAIL webmaster@foo.com SERVICE=http REPEAT=1h
		MAIL unixsupport@foo.com SERVICE=cpu,disk,memory

The first line defines a rule for alerting when something breaks on the host "www.foo.com".
There are two recipients: webmaster@foo.com is notified if it is the "http" service that fails, and the notification is repeated once an hour until the problem is resolved.
unixsupport@foo.com is notified if it is the "cpu", "disk" or "memory" tests that report a failure. Since there is no "REPEAT" setting for this recipient, the default is used which is to repeat the alert every 30 minutes.

OK, suppose now that the webmaster complains about getting e-mails at 4 AM in the morning. The webserver is not supposed to be running between 9 PM and 8 AM, so even though there is a problem, he doesn't want to hear about it until 7:30 - that gives him just enough time to fix the problem. So you must modify the rule so that it doesn't send out alerts until 7:30 AM:

	HOST=www.foo.com
		MAIL webmaster@foo.com SERVICE=http REPEAT=1h TIME=*:0730:2100
		MAIL unixsupport@foo.com SERVICE=cpu,disk,memory

Adding the TIME setting on the recipient causes the alerts for this recipient to be suppressed, unless the time of day is within the interval. So with this setup, the webmaster gets his sleep.

What would have happened if you put the TIME setting on the rule instead of on the recipient ? Like this:

	HOST=www.foo.com TIME=*:0730:2100
		MAIL webmaster@foo.com SERVICE=http REPEAT=1h
		MAIL unixsupport@foo.com SERVICE=cpu,disk,memory

Well, the webmaster would still have his nights to himself - but the TIME setting would then also apply to the alerts that go out when there is a problem with the "cpu", "disk" or "memory" services. So there would not be any mails going to unixsupport@foo.com when a disk fills up during the night.

Keywords in rules and recipients

These are the keywords for setting up rules:

PAGErule matching an alert by the name of the page the host is displayed on. This is the name following the "page", "subpage" or "subparent" keyword in the hosts.cfg file.
EXPAGErule excluding an alert if the pagename matches.
HOSTrule matching an alert by the hostname.
EXHOSTrule excluding an alert by matching the hostname.
SERVICErule matching an alert by the service name.
EXSERVICErule excluding an alert by matching the hostname.
COLORrule matching an alert by color. Can be "red", "yellow", or "purple".
TIMErule matching an alert by the time-of-day. This is specified as the DOWNTIME timespecification in the hosts.cfg file (see hosts.cfg(5)).
DURATIONRule matching an alert if the event has lasted longer/shorter than the given duration. E.g. DURATION>10m (lasted longer than 10 minutes) or DURATION<2h (only sends alerts the first 2 hours). Unless explicitly stated, this is in minutes - you can use 'm', 'h', 'd' for 'minutes', 'hours' and 'days' respectively.
UNMATCHEDThis keyword on a recipient means that he will only get an alert, if no other alerts have been sent. So you can use it e.g. when setting up alerts to specific people for some services, then after those you add a recipient with the UNMATCHED keyword who will only get those alerts that were not sent anyone else. You can also use it to setup a "catch-all" alert recipient, use the UNMATHED keyword on a recipient at the end of the alerts.cfg file.
RECOVEREDRule matches if the alert has recovered from an alert state.
NOTICERule matches if the message is a "notify" message. This type of message is sent when a host or test is disabled or enabled.

These are the keywords for specifying a recipient:

MAILRecipient who receives an e-mail alert. This takes one parameter, the e-mail address.
SCRIPTRecipient that invokes a script. This takes two parameters: The script filename, and the recipient that gets passed to the script.
IGNORERecipient that does NOT send an alert, and will cause Xymon to stop looking for any more recipients. See the example below.
FORMATformat of the text message with the alert. Default is "TEXT" (suitable for e-mail alerts). "PLAIN" is the same as TEXT, except it does not include the URL linking to the status webpage. "SMS" is a short message with no subject for SMS alerts. "SCRIPT" is a brief message template for scripts.
REPEATHow often an alert gets repeated. As with the DURATION setting, this is in minutes unless explicitly modified with 'm', 'h', 'd'.
STOPBy default, xymond_alert looks at all the possible recipients in the alerts.cfg file when handling an alert. If you would like it stop after a specific recipient gets an alert, add the STOP keyword to this recipient. This terminates the search for more recipients.

Wildcards - regular expressions

So now we can setup an alert. But using explicit hostnames is bothersome, if you have many hosts. There is a smarter way:

	HOST=%(www|intranet|support|mail).foo.com
		MAIL webmaster@foo.com SERVICE=http REPEAT=1h
		MAIL unixsupport@foo.com SERVICE=cpu,disk,memory

The percent-sign indicates that the hostname should not be taken literally - instead, (www|intranet|support|mail).foo.com is a Perl-compatible regular expression. This particular expression matches "www.foo.com", "intranet.foo.com", "support.foo.com" and "mail.foo.com". You can use regular expressions to match hostnames, service-names and page-names.

If you want to test how your alert configuration handles a specific host, you can run xymond_alert in test mode - you give it a hostname and servicename as input, and it will go through the configuration and tell you which rules match and who gets an alert.


	osiris:~ $ cd server/
	osiris:~/server $ ./bin/xymoncmd xymond_alert --test osiris.hswn.dk cpu
	Matching host:service:page 'osiris.hswn.dk:cpu:' against rule line 109:Matched
	    *** Match with 'HOST=*' ***
	Matching host:service:page 'osiris.hswn.dk:cpu:' against rule line 110:Matched
	    *** Match with 'MAIL henrik@sample.com REPEAT=2 RECOVERED COLOR=red' ***
	Mail alert with command 'mail -s "XYmon [12345] osiris.hswn.dk:cpu is RED" henrik@sample.com'

If e-mail is not enough

The MAIL keyword means that the alert is sent in an e-mail. Sometimes this ends up being an SMS to your cell-phone - there are several "e-mail to SMS" gateways that perform this service - but that may not be what you want to do. And also, for an e-mail to actually be delivered requires that the mail-server is working. So if you need full control over how alerts are handled, you can use the SCRIPT method instead. Here's how:

	HOST=%(www|intranet|support|mail).foo.com SERVICE=http
		SCRIPT /usr/local/bin/smsalert 4538761925 FORMAT=sms

This alert doesn't go out as e-mail. Instead, when an alert needs to be delivered, Xymon will run the script /usr/local/bin/smsalert. The script can use data from a series of environment variables to build the information it sends in the alert, depending on what the recipient can handle. E.g. for pagers you will typically just send a sequence of numbers - Xymon provides things like the IP-address of the server that has a problem and a numeric code for the service to the script. So a simple script to send an SMS alert with the "sendsms" tool could look like this:

	#!/bin/sh

	/usr/local/bin/sendsms $RCPT "$BBALPHAMSG"

Here you can see the script use two environment variables that Xymon sets up for the script: The $RCPT is the recipient, i.e. the phone-number "4538761925" that is in the alerts.cfg file. The $BBALPHAMSG is text of the status that triggers the alert.

Although $BBALPHAMSG is nice to have, not all recipients can handle the large messages that may be sent in the status message. The FORMAT=sms tells Xymon to change the BBALPHAMSG into a form that is suitable for an SMS message - which has a maximum size of 160 bytes. So Xymon picks out the most important bits of the status message, and puts as much of that as possible into the BBALPHSMSG variable for the script.

The full list of environment variables provided to scripts are as follows:

BBCOLORLEVELThe current color of the status
BBALPHAMSGThe full text of the status log triggering the alert
ACKCODEThe "cookie" that can be used to acknowledge the alert
RCPTThe recipient, from the SCRIPT entry
BBHOSTNAMEThe name of the host that the alert is about
MACHIPThe IP-address of the host that has a problem
BBSVCNAMEThe name of the service that the alert is about
BBSVCNUMThe numeric code for the service. From SVCCODES definition.
BBHOSTSVCHOSTNAME.SERVICE that the alert is about.
BBHOSTSVCCOMMAS As BBHOSTSVC, but dots in the hostname replaced with commas
BBNUMERICA 22-digit number made by BBSVCNUM, MACHIP and ACKCODE.
RECOVEREDIs "1" if the service has recovered.
DOWNSECSNumber of seconds the service has been down.
DOWNSECSMSGWhen recovered, holds the text "Event duration : N" where N is the DOWNSECS value.

This set of environment variables are the same as those provided by Big Brother to custom paging scripts, so you should be able to re-use any paging scripts written for Big Brother with Xymon.

Save on the typing - use macros

Say you have a long list of hosts or e-mail adresses that you want to use several times throughout the alerts.cfg file. Do you have to write the full list every time ? No:

	$WEBHOSTS=%(www|intranet|support|mail).foo.com 
	
	HOST=$WEBHOSTS SERVICE=http
		SCRIPT /usr/local/bin/smsalert 4538761925 FORMAT=sms

	HOST=$WEBHOSTS SERVICE=cpu,disk,memory
		MAIL unixsupport@foo.com

The first line defines $WEBHOSTS as a macro. So everywhere else in the file, "$WEBHOSTS" is automatically replaced with "%(www|intranet|support|mail).foo.com" before the rule is processed. The same method can be used for recipients, e.g. e-mail adresses. In fact, you can put an entire line into a macro:
	$UNIXSUPPORT=MAIL unixsupport@foo.com TIME=*:0800:1600 SERVICE=cpu,disk,memory

	HOST=%(www|intranet|support|mail).foo.com 
		$UNIXSUPPORT

	HOST=dns.bar.com
		$UNIXSUPPORT

would be a perfectly valid way of specifying that unixsupport@foo.com gets e-mailed about cpu-, disk- or memory-problems on the foo.com web-servers, and the bar.com dns-servers.

Note: Nesting macros is possible, except that you must define a macro before you use it in a subsequent macro definition.

There are rules ... and exceptions: IGNORE

A common scenario is where you handle most of the alerts with a wildcard rule, but there is just that one exception where you dont want any cpu alerts from the marketing server on Thursday afternoon. Then it is time for the IGNORE recipient:

	HOST=* COLOR=red
		IGNORE HOST=marketing.foo.com SERVICE=cpu TIME=4:1500:1800
		MAIL admin@foo.com

What this does is it defines a general catch-all alert: All red alerts go off to the admin@foo.com mailbox. There is just one exception: When the marketing.foo.com alerts on the "cpu" status on Thursdays between 3PM and 6PM, that alert is ignored. The IGNORE recipient implicitly has a STOP flag associated, so when the IGNORE recipient is matched, Xymon will stop looking for more recipients - so the next line with the MAIL recipient is never looked at when handling that busy marketing server on Thursdays.

xymon-4.3.7/docs/editor-showclone.jpg0000664000175000017500000012156211070452713017151 0ustar henrikhenrikJFIFHHC  !"$"$C" `  !1U"#5AQVs27Bar346RSTtu$qv%b&CDWXc%1!QA"2a ?㲼%v_E@ڙ w8G,4?S=Z7?E3Og )q)НZ7?E3b($n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g'VwL$n~g/dvxT27ZFBƳ#9ێsA[>Vw"VUjϷ+I^JW\ţmR_*lxj馭Cٽ<qx"f}3z]we2F~Ӄegnp2{XZ;aCF,|PST ӚPc-xc[`[D5M5CLjx2陰rk vGqgZ4Gjq5/ c{ր}hɯhzͰ fO=holm bj+[/:V&e#brEPfhᰞ`̤r%5 xo,E[wdThb:Lێ`+CT -qͶn{ç3Js9^{ F6>Zʍ($̝I0x%k 3J^uEmҦq7*O ə^ч}*vEE]SjkNԴvJsFL!anH w!FشUv/r kr$EB˚yyGy`0zW{?7:OiA*,{LZڮ6<݆]-<Fܲ&cIwNz=k@O=Zc62A]srJzo EGG޻,S$U.^KCJF[:}Y+kpii{`8#RS[BԔP ZkT015.k^4Q{zuJ Lq1cpJʎ] ֑iD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDDA[>Vw"VUjϷ+I^JW\œ+mʊ:nɠ8p G}gZydI\縜yO<OT`Rei![|W[`.44x4TGmn֓H%u[S vR4{`n8h[c} t3ձ6u F*j&x^lRͫ+u}fmN0R] _|" a`"tg޵I4"*|YEb՟nV*" "" %vU'3m?%R}?YKD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDjϷ+e^>Vw"PdEy+X6>,i*B\l""" """ """ """ """ "" VP]uC5aHh9 xr9Y:;uʱPXa|[)ƴdܵuژlNT6h sܱ7r}qϜ tױ߸m.{цrvmgG_SG_TΐR:8H&qc =D fٞG 8A$sL[mn-T"#vvnWyL_ax8{*x8{*hf&3E̡&/6m#'U|/rOdJ8{*x8{*E ;|u6T^%3,x"&I(pʔx%]*O+a6|WF:R:piԮ~9Გѷ <1ʪ [-ѡu.9Wbgwm%G!stbs n""" """ """ """ """ """ """ """ """ """ """ "" gۿ+V}X2JWf,OT`R覚Ri uuB͐k9z lPIx ˎD\xsr~`{W++kM[fMQZto{LTMݵ;p1 r_Iu] eAtp{H'g/jMKm  КK]]K-WIkq7S[8qw<Q'$ #ʍ=~}u|⪳jka\cpWVR:ᒮhcshpܴg|j6(_tη fnsہ18|0uknqTEk9hp9`֒CA8NNa?7FWZiB붤 UIoŰYksp p/- `5@e _Ŀuf'YQ6oʻi~rDkZ\._*&T\Nc yY,˲@o^"cjh_ƥQK͟|Uæ3!/kU3UB%6Gpm">$~9ygOkk[𭋘T7DDUD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDDA[>Vw"VUjϷ+I^JW\ŽMM )*)&{[kvr#8"Bߨhl2 ^E\6JÀ8(9+OT`R7kMշ[MҲUSA__Ԍm:,?." lc1%f^/:{O/<_{npwd_5ޕޅr|UC-Hik p=G7?2Żt-WEuvU[}DR:H# 3m-9 HYөOm[ZQkG@T.HipCG, I++DxW* =plGpݜ`=5tkz?TGuPQYj*) M9+ekd#w.G+ak/eqXO$t33hf0 !?p= ͣu}qtkw_~8}^Zݞ{1WFHym븳p7#QgO݀tscm4YD3SO%=DRC4n-|r4#ytlqudkwp Psz/Ћw׾IrM?V$_}Mt]w>i}tGK v/l$C,:SKXAu.,W'71d} xFsl cJ>P~z"O{ʤ=1Zz LT&U #sVw"PdEy+X6>,i*B\l""" """ """ """ """ ""U}79C$1mS@1.  \WCw5vYhiᩔ :y) k 爢>W& ۹Ot;VäVb 11oaOζ_JzP:'T\&֫Tce:EL64IcX[.c{6,An /ҕ>u= }R"qCn@|m*>}=BxT}HzH#Z\Ѧ]G)Şp-tgR*͕[{$[#s^jK\ӐA.cVv碬4%|A4l=F!`ss>*X'+MGO <])-cKkdk4O*WD5E=>]GiY2USin2 8{Z2)fۦ]tC5EMvS]#]$Q77on "=Ow^)Y -. Q$Nk^;[w4*D8SUɭmꑕn7lD<XhX=(xEƖymtp71wT<x8vdIuoӮ^]ۜ׻UY.qw uOSGkuMogq9N*:^蛩pScSfypUCϮwҐ~t];H?:>WY]p,7K^## t2[ u}KUm 8ze٨ņX-ȓ&; ];H?:xP뮝wGUH:i,UEU+>-p^Doi#?lڧ }uӾw[if֚ H]_w8 '$紑vŊOtQ|=}Y[= kKI%q0 A k~ :zNn5:+U{gA 'w/6(tvI`Yb`|hIqVnx)bVRvTIL H~ulUTZrm5Ӿ)OCN 7֝gm͢ɭj+/Z t k\_WU 弌Ҵ:;u=9Q]iK 0pJ48l@GM5qtVm֫-QhF} ΒHI s_3Gd6q e[‡G]t CϮwҐ~t%:`=~}6e8s!wH.VR.~ _@-MN~̬Q)v'>JA‡G]t 5 swΒ-U%[(4̴晸l3SX$Ʋ>Sw{?8P^:<})Pڿm+Ui1X5efcks|O,cnsUVڇ˥/#-5.[`F\6 pt?Ű1ZgJWKk1Tj%pz ql<)4߳ϖ{ DZAoxZYW>TD@DD%y+^Jr3 Og )ftwWECUlbbA#c]j/jw]MiRg5v湡-$v\iJ.qVeIk$aN,m'pgzE7:kEAr!U5Ҳ<4 ́o |{yMwHo6GƩe5LAЖ,N{ Ãdcq^DZ]a;(0F*A.g.ۈ̂4S"l&8E T햝7E Ӽamh97h7" ji (#sp[Ym9嫡"dӴ67jfWubIQ)t9=݀cFMOW4y|Fv 98f%39vh4HH4#g[zu*pwQ+LffDH`h1ӂ57)GWY%SOQ[,~A=߳7dKlTZ~SQQMKt]SX%eDƵD]#FH8~l569mq-YGdtմԱymsY((G $YWc]^X' |RDZ-s\ ~ejdlLELLd8pi>Pn9/E5&]eie3$l2), !hCsmm>ڨEڹd͑=kAi3=2mM))*D& /ho s9&N\|a"""" "" n"֚NMr|"F0H' my^WҶ_I;s*-$g"P.ue?@Uۓ_ߢ64Sa7# v 7RNoxI-W6 YN6Ǧv{ZfN [d21ہnKcQK]Q|ӱ3-0 E''|Eb? Zmp&u.uNk], o yIf݌d,^/ 5mN鯄t^%0(?{#ek~KܽΖu պin9's'TJ^=.n*6cI|, bZ c~] \5H:W婌ߍ-c-d7iu9\-+V۠S\6I|^pG wk&?U$觊9XAՔWnM~~Pڮb6qOņ h-ۂw8 T_hG]c;=uw X '-KeB gRNoxI-Wgt[ʎJZ~ƾ9p0׉i>gT0dDWbkM/<)3l|۱?I;s)'|Eu* I;s)'|Eu* I;s+7dT7=k ꕇy>_|Y/<C~m[UwjUEj#HؘH$pv2>|go(4~Ym}KmN|Itr4DDdicK73Z:S(: R[\ZpK[%W)kjZKeSd3oc1Gh:ٺ-_=iߊ9T[?Ζ:]'I롥^ 6:㡫ฐK^) }oHGvcLޮwJGD43QD18||IZ ?>PT~*w|SRNokzGaM"Nq7N;M$rʒpH3Z?nrUVϱ71ϖf*)?I;s+[j,Σ)Do0,6=FcKugzXtE[앖밸s.k9s;1NUwIXj۝єr!YStKD JH-vC8;>SΟ~RQL"+{՟nV*ʼ|YEb""" +^Wk4U|{MJicpݐ~bU hlu2UW lZӓ˷j֟>,KZmGT6uꍴeDeFh9-8ǧKi/1SeRd[,r1snCHpZ,rw,Vz*{me=]Z#I%q#ii/8h^si(VQRKK8,tHr5Ҹ,̃07Z+5Z䣨R #s?8k݁A:kLZ[k +fV [(cZc;أÉvA Sѵ\5MuU=g[/5=jjCV8˹C0/ . K~꠴LeCH*Iw5۟)f %% kUE:9i)572M]h4V_6dn-MMNff2wOƖ , :+ +} S\e}CZ@ͮc\79.ƶk}$UM-O;- nĝ݊ kjʋvh+*]QWNֽs.xc`4`eib6uP04WLOycy8nFI!SE:?OZ(i*`.WԵ.x`G x;=H۷a{te=^YP>h_ӆcx8nۅY"\Awk&?_t{SQq5dRqx۹1N{tӧ4c` $m$;y W׺ ?w}wJn׊J;ku4ъkƆ.cnf.UKpE$A0i1v˘%#"{zʟ]M̵P[-8tԐX?45 ԚLP*nBȞ22֑Gr;W=:Aw?>{zʗVv餫h(.{zʛ{zʟ]ME_]O^.އjU[InVN)a/{!ȁy'̒\}ޟTuO**y*յ-zvTqf'pZ:Aw?bݵ_O[]]IY> wd4O0rٺ-_=>/̰J:>t#[$lAAT~^l[I-մ2\SNC[wX9 猃]ޝ/}QlM3'd-xl08 q~}MIOtS3$n#-# =Q-ԦKP>{sluX`~7l7sܸxӽr-UJH4ƤFs$8`||rïtz~CS׺ ?wlvѺEԶ;%u<; F{!g8'+{zʟ]Ko75ޟUqw~c{sؤ-"oj_r: ^+aqvC\"oxZYW>TD@DD%y+^Jr32-IfҼscZ-T#Qi}[5feC*rܶ6pi.vCJhj5ՖiÙVF\̑͠5qV9^5Eq"?{&/"Ig)n#,nX -,K)H#{{9 \tG-UU$aT`F́lU&CeϮMAG U1H@ Ȝqh裱\Wmm4uPAd:hƓ;ق7~,LQV z6SVB&K;($4/Y@ (:y*$ØpA1[-Ji'ArWY>ڟ,q1=֕چYi%0SG #tE浍SSSTCGmNܐC.x- uk]ud2?!c q,v?G%Y!=K%iZ*n0b6Y{O2@t~z;/+tP׹ۺXI'j0> z\l|~7om=q}Aou{ *6O-t0=^P! 94>{mu[gnwunwݸsP7egZ_jtkk胷LRs#7.bX Wmil.hlN;Iks4+2'5N6Yc#l%k/~G38lF_ܬN}q}88q~5oh(b)$Q*䊘58ɁǗg0jv\(⪥̛&>&K>78>N`$`զKt 8 u,3&JขFƜ4FsMG|]ioV*hh`Vt1"2w%`w}9Y2u}Šz fV_bjb9؍v89'$^}7sZr$#}K tN>fDenӜpKt@kywYkҎF3620>s+42ܭ;Qv`t1blZ11XpUYe}Qi#&g'{i2@0fdsv ZO[.Eq89! .@څntB*E%n~p݅ZA=& W? j횐@-S,dϪ00B]CC.J6S5>]/4C)3WN󁴎88. =<5tZ6%wc]9qgUw([#7' p r9;h4YK_Z|禞q$/nk~,5CaE)+5t1UV_m-&VM_<Ť䘝o00 ME41\H~_t+k!fy0s{{ִ;$@vTqUWZ2vΑku(|c$ocdx 9÷Fk}K]Rtdl5@I%sj~k9.xZWǸC?:ꚍkh=%`%b&`2y]]-k57H%hs8з_k%Q\,wՑQ@! VrXu/H4Mxzp6Y-qLD ,'i#v#ȑ.jO.z_7a} T;Ig 8 \-+bUjܵQYpj/rMGf%NXP׎kBo_SG,Lm\q8tEoal-GZKMk֜RTUǐ]ϗ UA}VSCo[ZL Dm9.پFi^4jTaKWlԂjpd{&}TN9F p\&jʋ ^/X3ɹv;'i_ k3[;y݌s;xWM. x9NŲ:w qpι)`KWoTB3DoŖh{Z4-~ӗ{}Wr:Z8+#|lrp\w#k΢H$ ?J=r:JֺMɫ|XRѕ3SS<bӽ= kZI ;UE\+-h#M k@ԓJ-}hgHwO1wV7<ۃ^e0s"-+{՟nV*ʼ|YEb""" +^Wkm?%R}?YKU'3{ZA!tun00bh.Iˉ9sk1J|YVw"VUjϷ+I^JW\ūݮ fΆLDx i\܍g!3qW zcYO^8K k\gZ 5&x.!s\`|~` [:ʫ46LDq9N;[?'iˀ<;{VPٮQ)ګC2jwɰNp-w;Smچ]<ӆVC@*Ŧ5+[[84i_s [NhU-ĭ7+".csG!gcEX+{p8g]Wj\aWKĴJO75-8!noWv= ^ lqD-#;=eޏGdUQtcw4q^Qo,^Ȍik(k-he+;~ܹ!'8,: 5[${x~hV~7Yii|s].Hgl3;x%olMS%gVlHGG# xG2]+H^h۪yi$.8#''T5 6;4178 'QIjGSGM%ֱRFPל1˃ Fs/6KϪd6U5&Zy..y0%MԴ2SUNts`#]:K:B:KK],5& s8;phKS-tGӇpwZt?'}ŒSTcF3mkavv(m|:;V6q))m $s'i*˱}myt"3TF*$k,pO `;+aкji!ZYD5 G:5K=4/m%|uSl JD4#; "NH'^K(jmk Skcմi1^v;v4EQLSD$2c}Cc0NksF?3~uG[SjqiAuz o5 JdnӒUI;l9ķcrDEG]iUPY-H>%wn] ֍fF?3~utUhSmlmU=#qGOr㘸Dw{fk[sSWh Z*)]VƑ[ CfԺVӨlsGIuMQ a8$g*O֍f&X-wEECGƜ{ujF6iE`2[.L>IO@: ]QQ TwmUsO0QUeB5(43b&d 9!w>mpDos7UEٖx:T5OUIx~ct[1htOttYW)xշ %UL|@8vd/Z7y󧃭։x:ѽߝ<hoζM S֍fF?3~uhjos7OZ7yCTu{:x:ѽߝmhAYK[iOm ;A$g'?S l+4?x:V>UxZ@DDDAW#1`~J~1C[rQVUIvCFG @$ S2T$1ഃyaq"S2T$1ഃya|NfT=ѲBӵhisA$4nH^)gYʘ̰fc?E-J} |rFc|5q4A 4SH=8%}/w.qӶq;c {NIj)DVBo5O;vّwX JYo&oыuP y$ɺ&k3wvFI-OI;s*_ȺRNoxI-TZ}*G[3sndH |-‡G]t T[?*w|T>JA‡G]t T[?*w|P^:<})O }uӾ+RNoxI-AzP뮝<(tyNRΨI;s)'|ECϮwҐ~t];H?:U$觊9>JA‡G]t T[?*w|P^:<})O }uӾ+RNoxI-AzP뮝<(tyNRΨI;s)'|ECϮwҐ~t];H?:U$觊9>JA‡G]t T[?*w|Ph^՞emCri*+Z4i-$?*mu9T[?u9T[?]C'|EU%nt-{k3daҴ4jϷ+e^>Vw"PdEy+\]Z;aCF)>,hʾ u]-Ɩ(`ser<5- FݮrZc!E4QϻfZN48829d/Z,7M/>i K3j.LYyC.ZV(i-V\93n/{ St ږ;-)쁰Ӵa&fBZv e!)$WTT\xp7vL9V7@JY^Sz}]wXapAJ[sc).Tes>59%$տCk eTQ`$oi H}9IWn4M+Siᕸ *9`T>k־"lm%L9݀\>% kH{I.b-Wo4飞]479̉X@#su.^D445D`V=sJF\5j:&6U+NeK}8&j)Z`cG5Ižԗ]Mhk-5I(ˣdKw<ś]TV sCIRZ4]R@hbW5-{` ftW=EgGt[mbX^lJ,2 ![WZ U_,6F~Mis.8IQz(8x1ek 2v tdo _b4,u/p۸woݏ+nkqj9X{wnvg3s/**[CE-a Y&s1Zl%'!҅WZo.KQ*sMWmk(m Ls`Ak[=!W+/X~]]–,aF={;p7-cpWmu<.,2p%cAYCkQ:]_U-IGtn1Mp5w րrE_zGnzFiiJsLF<2cdsKېA$~GgRX <5O4(y Hٜ)w՚ڮto7hmuk3(i#"@Z# s vS(uq'w#{潭p?1hA\^(YpܮBAC}<:b99lqOœ hhaQ˨%Лl0J(y|f&߰$3uZ!(+5WRdUO0nX -1CݐKKV>;U7i/UH-]Ke.kp" c1CY3TQ۵³B;l,s$ ys8+#S}Ϭjtt5]#( ]DrC \"^msI!%EZ}ck'_sHxYnyDUϬmsTV]c8 -Z|(ـ>Xߣ>~lQt; tM)76E(33>_%A]'VԻuke8l:`73M0<%'i }Bө+ EԹWY$Va2pw0]Ʒ7DkcjKt5V5ӴCKSlp8O``>gfq١W{mΧ1#xl h4',=IqN"pY\4X7Sm^xl#vno0JoXlp]eÅ,N1vb'L,ڞPϩu 沖-\g̎   | ]>]/M>hӔ}K^k\'d<9kck%`^ 4hl."|Eݯf إqnWOtR:utqetB@O#cpr9(9ݮtxq=Toiim`y7k!5;N:*lV[ um 04u4M5 bl Gn5 gҺ.:R{qT& dKOXu6JYU| (NF3fc dFhj|{+eY-Fقq;ÆyHzDm~"ltچ S=@wWpN֍2&,Xu7U~hzP7`g*F>m= {g'IYIpJ]q1#*WUg[Oqp8۶߷v?69 ڶ\oE$"-M(|g d.ktvH,q7]QVux: g5k2nSC]fPqM]otbV@$dJDZv O64QhӶQ&lufֹV82q$ sñn @!zARWZ(O_ !u*BÆ.샆ǕA @顳PP 4O|G9^ysûli-MtN"7&_bv6LSS9DoidA `tmliM7Ab[mQ^-em|VT2$c 8eisv6s2QIacm:sJ*<>wȷi+6ղ] {dw_=dѹTАg ñ4>;kTuh-%K)bs pb{Ȁ'ګe)[kNcfdf4@ܵ[8֜B{Uۍ|ZKEB*s%p ۾tR;sv7 1(657l-;@$0}YwZ{K_e]D#eMDQI39nn9;%S:K4SE_hT#^pk#p^n<2ޔ/z}tZT۝jjk]NChjel{E ln ^տ*˛QF]#7ZQNON{G솞jsX鸵6pUje )"$ XcFpPWNW)zRS.Ke:ۍK<Ym4A |7q49I2MM%bDFTɊjg4ȍ<̓!n7=jYjë}{CG!e O{0U](5 hYkC_lk[71ky5OE>Yt\*u,b 1psy`8q$S{VC-ek u&HG 9gKrpK&碭W >QZuwht{<,i򃼢OfRϪ5UDG w13 ` `$qgcYSy?vC1oxZYW>TD@DD%y+^Jr3 Og )b~J~/kB"( {Fk E%ӕV)N{8v3cAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTAexw[Wޭ<;oVTA95nWHzת@81LjÜ0Ɓsy4DjϷ+e^>Vw"PdEy+X6>,i*B\l""" """ """ """ """ """ """ """ "+pӵ׽.Tީ$joT-Z߿4ؤ]-Z߿4ū[RlRhoߚ[IխKyީ6)4WgV-szjoT+ū[Rxk~w7MMխKyީTD@DD%y+^Jr3 Og )b~J~/kB"( N/'prX5}OmvWYߍ )ٷn9F6vk[Uv8QPh*_XK '֒0[sx 7/ֽE`PAQJjSC9ƵŎ2 dcVڋKTGMO5< 5~_;[}+#yzr_t* ּdl+6VD\&m[b bj\[h4{]$Pݫ}SK$)Qc9hpp3w]WYU:}-#(jv;k1{f-5nざ/%ب,fKαܨ'6ڨ!3# sw8)kƴ֘i$SD}CdX|4dd7h=|,uj:yuP>|5h9eҺF^}Gq:kdTUQ:|q{Ù-;AfW= 6Fqm U[lud nKўY̢m\ Eqy4mԸ48({fӝiy:!vO4 -tS1Ә۟s;^eh\]Qmdo}͖9d}7 M! jP {秸)KM-D !F<8ͦ*Ik+-Q)&9|klq>cv #{SX+tp)k =¡S |m;ӻ/܀3.}]0TVZaYc'^cǗ3IPA-Q47kS5EKI4+J]KẔy 7$ ׶_+if׆Y1 h#.69D9Ԯ `&f0O0 XtіX͗J!ROO,, cs9 $Y+R?/1w*ᮔû LWD[AV>UxZ@DDDAW#1`~J~-Og )q""+6W_5D6vFy6 dǒ.Fh]>04~04~U:oݷkC{p>F"hPDDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDjϷ+e^>Vw"PdEy+X6>,i*B\l""" """ """ """ """ "}M{u]NTyQI33?,FTe}~j_EOS}?A"|kߩST}~j_EOPkH_FT<kߩST-}?OFTe}~j_EOS}?A"i}Mdm]NxI $43N?D@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDDA[>Vw"VUjϷ+I^JW\Ńi*BX>,Ј" """ """ """ """ ""!9X /7lz۽ƲH(i䩨v; 8J}ȿ5{/![K5j::yjjgVE 10=<5hI$jYwe\bGYr<n'M'c78nN+bZO/_Z[=VWZ碎8&ls p2c .4 \꧶"ںv|E$ %ig* 7i?)ǛB} zOQ>ϧ.V/Qq(|Ӌ}aGsII޺0ߧYjrVS ZYF Kxy \ƖvŎ%;@-w9o螚%GYO-5LJ8V>75<ADU馒6V9s.HӁ.q]_Lnx[E-.xx,& HsFk`8MYdt7ګRpmgI-cAg mpۍRNw2`vy>2Z{~c vy־H؝RbW@tca#iSiUUHU-@Kzt5 c1=n67-#m#,}]M5dUEX]k4)KNG"vym-O]m]IVʍca-y2wegZ_jtkk胷LRs#77~Xnh_K1I8 g/u>42vLs158LG0V%Q0۴cTکkjRϜ0 \8 ?HjҮcpUxZ@DDDAW#1b 85L[rË\<|U3ӽy^Π?%R}?YKN}Q{:q4w}%PI4w}%N}Q{:D|M;IEӽy^ΣN}Q{:q4w}%A'ӽy^ΜM;IE1I4w}%N}Q{:D|M;IEӽy^ΣN}Q{:q4w}%A'ӽy^ΜM;IE1I4w}%N}Q{:D|M;IEӽy^ΣN}Q{:q4w}%A'ӽy^ΜM;IE1I4w}%N}Q{:D|M;IEӽy^ΣN}Q{:q4w}%A'ӽy^ΜM;IE1I4w}%N}Q{:D|M;IEӽy^ΣN}Q{:q4w}%A'ӽy^ΜM;IE1I4w}%N}Q{:D|M;IEӽy^ΣN}Q{:q4w}%A'ӽy^ΜM;IE1I4w}%N}Q{:D|M;IEӽy^ΣN}Q{:q4w}%A'ӽy^ΜM;IE1I4w}%N}Q{:D|M;IEӽy^ΣN}Q{:q4w}%A'ӽy^ΜM;IE1I4w}%N}Q{:D|M;IEӽy^ΣN}Q{:q4w}%A'ӽy^ΜM;IE1I4w}%N}Q{:D|M;IEӽy^ΣN}Q{:q4w}%A'ӽy^ΜM;IE1I4w}%N}Q{:D|M;IEY5јmdk hkc`H99]`" gۿ+V}X2JWf,OT`RŴI !e.6^քDPETZ(4̒O Icҗ+dGA=%i*dEyےqYg>v|\̕`{ZZ朂{TQ" "",ERz1I$79! d́]eInȪckVHݯ`{Hs i0JSS.Լpnrڢ 4hdd7k'8,;eee 2Jƹs nB@DDYQTmK2yvyQ`eYg>v|\̕`{ZZ朂{WŔeB8r2>;pqѳSc∳,֞odrUԿ 3"w!=gA" |tp;##o#>@(1EŠvWGCn,6kq3Ϛ tD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDDA[>Vw"VUjϷ+I^JW\Ńi*BX>,Ј" "",UƤcMn%gi\xotFA+FAD=)TԊcE_vۿ7־sOMSfS {$`84H_ó,7kHW:AGSo:`+iis 6-ǙryUy(bӶݡ\68 7wUUWϿP۠Pܡ(㝯d i$ólli l4qHsH6@71n  tiiĆGHI9i9 vj*@-n1쨑QF\2@qZi-hi*,o _cqcp!wхn^%ԎK=\HŶX˛ÞKc'ǴXAӚz[u;^[KP84I5܇1} Ej&v6wW>Nd2B2pq' ֍k]G-ߘwMa̳[lz-Q-':ٙC@ p/}.Z?[kdtAsDC` gZ,}!ZM--AK;X.`vrۃ} ZFU4Ql7h5T]$n1k]ɦW7kIh"HXdlPBGm-#-i5h)㎶*v|]'b".(ΒG5l{1=u֪z9lT:bSDZ=t7="me:7SҲ9"=f -qbqEe5`N*X]0 _|c?$A)/*H62[z$nYcv ۂ֑-ĘlGVۢ|oL\ F0 `y<akjYf.V1ԁc6=vѓU|KTC]᥊727gs\Z^g~r^gz]BHvB ()6gFִf7{D΃mUTvKGσ!fMKҕQK_xU-%; koV!ŌÎ` ek6}GHq  H)Kp`pMd{,Ξh#^&@8VcaQtUYmTJcs o~KCp#mnZ' &Rp+'qdřrێkSŽZZ&즉σ'ɪ[n}ci0V!>.&rۻ؝hl 7:ɞO3å9^|8k@*u޿w_?ز캖g`htR|n{I !WtP*'IAhٽ۝ dDEYIoW̛(S^)+:6텛۷~9cxn3SJϩ3t茂V7'-!ǘ 9SwkHW:AGSo:`+iis 6-Ǚryz.ETvTsI. *.䡷ApC  Q;^vIgUy(bӶݡ\68 7u.kL[H$02:F@dLMg8N@REg^ךMi@."|#\Ǔ8 ,;M=ݥ14%U{%m<+ n nD;.0zGfFcʉl! w,] =-/lu-kp $C>: sZRQJműcF%q .-ިַIiz -sh&8hr̒~53ZVMYYA$C]mTnӺn?qK;[&&Pߧu ¢zكwBZ(`::Jʹ*l-CadL14` ~u_u\luZ,i⑂Jj}H7 0Qw]#WW[ɀqh :4l>Î"[՛+lc[ݭ. m@V͌::KUUEM Lp)7=0. Yzu.|_g??b.6.rRFY6gVd$n'0O1W]QAգ6ovs''%Y4[7Y?E"[Ff;2|՝_65[7Y?E"ɱپ/̟ugtMeVwO~d;2lk(o:x'Y?EcYE|՝_>/̛-"VwO~dQlugtO:x&Ʋf;2|՝_65[7Y?E"ɱپ/̟ugtMeVwO~d;2lk(>Ȣ:IֵKnV|YEb՟nV*" "" %vU'3m?%R}?YKD@DD>ONyuoӠt)S~wKE7sfm<$SOO# O>g8r]E6[.eM\q-CNGg>jzWh;Lz7MDۡL]q|TH`imEitݝm7^5>P&\`0|nG"%Uk$Tu-n:뢄OcasٞժMwpj+L:6fa;$F:(X*,8P7APݚf7o 92yG/`3?QOcdbc \U U\CdWn:^G>;MJ3{)0|3ŵˆ d2OMWI4(+"vZ =hz+jbc/wg~Xv׊~4һKdM{]ph'!ꉝ5V52Qj%w/t$ {SMsTttbG'c^2Ʒ{Ӑr8ёuoӮ\tP|+O|YEb՟nV*" "" %vU'3m?%R}?YKD@DD>ONyuoӠTD@DDD@ZI_}GE/kdZI_}GE/j_ДB6(59PG?_Ȉ""" """ "" Ҟ:)n\#m,]Kn`ƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAؾ5z#CƯDw6h}rAS{<:Ϩ?E4>q"" """ "" gۿ+V}X2JWf,OT`RŴI !e.6^քDPP?7:>ONQk}%k}%|#BS/ sؠB6,E~"" +{՟nV*ʼ|YEb""" +^Wkm?%R}?YKU'3{Z@DDD@]Ct뗗P?7:DDD@DDRFERF Nh*?bS/ sر" """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ +V}X*gۿ$%z+]FbI !e,[OT`RehDEuoӮ^]Ct:WQK1F*JQZG9lXWi5MoQ-Q Ӏhaa2z:~EU,fnYCFyq,HDEAV>UxZ@DDDAW#1`~J~-Og )q""V\zXQ4umUCn$Tҽ^f'!Ϛ:뾥< ߮ҳeH""" """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ "" gۿ+V}X2JQkm?%R}?YHD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDjϷ+xymon-4.3.7/docs/criticalsystems.html0000664000175000017500000002624411535462534017303 0ustar henrikhenrik Critical Systems

Critical Systems

If you are monitoring lots of hosts, getting an overview of which hosts need attention can be difficult. Most likely you've split the hosts among several pages, and the "All non-green" view is just cramped full with systems where a logfile is showing some errors, a filesystem needs cleaning up etc.

The "Critical Systems" view lets you define exactly which tests on what hosts need attention. In other words, this is the view your Operations Center will be using to decide whether to call out people in the middle of the night. It might look like this:

This document describes how you configure the Critical Systems view, and how it works for your operators. By "operators" I mean the people who are doing the 24x7 monitoring. Where I work, these people normally do not resolve the issues - they just raise the trouble-tickets and assign them to the "engineer" on duty. It may be different in your organization.

The Critical Systems editor

To configure what goes on the Critical Systems view, you use a dedicated editor.

The default Xymon setup has nothing on the critical systems view. So to use it, you must configure some of your systems and tests to be included on this view. From the Administration menu, pick the Edit Critical Systems item. This is usually in the password-protected area of Xymon, so you will need to authenticate yourself before you are allowed access. If you haven't set this up yet, look at the installation guide to see how you do that.

After authenticating, you are presented with the editor page.

The editor form

Let me explain what the various fields are for:
  • The Host and Test fields are text entry fields. This is where you enter the name of the host and test you want to configure. If you would rather not type too much, you can enter just the beginning of the hostname and use the Search and Next buttons to walk through the currently configured tests.
  • The Priority field defines how important this test is. By default you have three priorities: 1, 2 and 3. Priority 3 is the lowest - things you must fix, but it can wait until you've had lunch or finished the department meeting. Priority 2 is for more important things, like one of your RAID systems running in degraded mode. Priority 1 is the highest priority - the kind of problem where you want to get a phonecall at 3 AM in the morning.
  • Then there is a group of time-related settings. The Monitoring time defines when this test should show up on the Critical Systems view. By default, that will be 24 hours a day, 7 days a week. But you probably have some systems that don't need attention during week-ends, or perhaps you only want to support a server during normal work-hours. Then you can use this setting to make sure it will only show up on the Critical Systems view during those periods. If you are migrating from the old "NK" settings in the hosts.cfg file, this is the equivalent of the "NKTIME" setting.
    The Start monitoring and Stop monitoring settings are used if you have systems that go into production at a certain date, or which are de-commisioned at a certain date. Instead of having to update your Critical Systems configuration exactly when that happens, you can configure the dates when monitoring of the systems should begin or end.
  • The Resolver group is a text field. You can use it for your operations people to see which group of engineers the should call about this problem. If you have multiple groups handling different parts of your IT systems, use this to let the operations staff know whether to call the Unix admins, the DBA's or one of your Webmasters.
  • The Instruction is a text entry field, where you can place a brief instruction to the operators handling the problem: If there is a simple thing that the operations people can try to fix the problem before calling the on-duty engineer, then you can place instructions here - e.g. perhaps the issue is with an external partner, so they just need to call them and let them know there is an issue. You can use HTML tags in this field, so if it's a long story then just put in an HTML link to another document.
  • The Clone fields at the bottom of the form (not visible in the screenshot) are described later

Setting up a disk status

Right now, there is a yellow disk status on my system.

But it is not on the Critical Systems view, and I want it to be. It is a priority 3 event, and I only want it monitored between 7AM and 8PM on weekdays. Most likely it is just some logfiles that are filling up, so the operators can try and clean out the /var/log/ directory - if that doesn't solve the problem, then they must escalate it to the Unix admins.

So on the Critical Systems editor, I enter the hostname localhost and the test disk, then hit the Search button. I get this warning:


telling me that there is nothing configured yet for this host+test combination. If there had been any previous configuration, it would have shown up on the form.

So I fill out the fields of the form and hit the Update button. The form changes to look like this: As you can see, there is now a Last update text showing who has changed this configuration, and when it was last done.

If I now go back to the Critical Systems view - from the menu, pick Views and Critical Systems view - you will see that the status is now showing up:

Template definitions - cloning records

If you have many hosts that share a common setup on the Critical Systems view, then editing all of them can be tiresome. Instead, you should define a template and then clone it to all of the hosts.

NOTE: A cloned definition is not a copy of the original definition. It is in fact a pointer back to the original definition, so if you change the original definition after you performed the cloning, then the clone definition will also change.

Defining a template is just like defining the Critical Systems view for a host. Just call the host something that looks like a template - "Standard Unix", for instance. So here is a definition for a Unix cpu template.

Now we have created the template (if you haven't pushed the Update button to save the template, do it now). To apply this template to a host, scroll down to the bottom of the editor form, and enter the hostname that you want to apply the template to, then hit the Add/remove clones button:

After it has updated, you can see that "localhost" is now listed in the scrollbox showing the clones.

NOTE: Cloning happens at the host level, so even though we did the cloning from a cpu test definition, it will also affect all the other definitions we have for the Standard Unix host.

The Critical Systems view

The critical systems view lets the operators filter active alerts in several ways. It might look like this:

Filtering the Critical Systems view

The drop-down boxes lets the operators filter the alerts that show up on the page.

  • The Priority limits alerts so that only those with a matching priority get displayed.
  • The Color removes those alerts that have an unwanted color
  • The Age limit can be used to only see the most recent events.
  • The Acked selection can be used to toggle the view of events that have been acknowledged by the operators.

Tip: If you have a preferred default setting for these, then you can bookmark it in your browser - the settings are part of the URL, so your bookmark will include the current settings.

The detailed status view

When looking at the status of one of the items shown on the Critical Systems view, a number of additional items show up. On the example Critical Systems view above, you will notice that the instructions we entered about what to do with the disk status is shown here, so they are available to the operators. There are links to the host documentation and host information. There is also an acknowledge function, so that the operators can acknowledge an alert right away.

Critical Systems acknowledgment

From the detailed status view, the operator can acknowledge an alert, after he has assigned the problem to an engineer or has handled it in some other way. This serves two purposes: First, it removes the status from the Critical Systems view, so the operator can concentrate on the new problems that appear. And second, it lets everyone else see that the problem has been noticed and is being handled by someone.

When acknowledging an alert, the operator can add information about what the problem is, or who is handling it, and when it is expected to be resolved. E.g. like this:

The Host-ack checkbox lets the operator acknowledge all current alerts for a given host, e.g. a full disk could easily trigger alerts for both the disk-, msgs- and procs-statuses - a Host ack lets him handle all of those.

After the operator has acknowledged the status, the acknowledgment will be visible on the Critical systems status view:

(If you are wondering why this image says it is a "Level 1" acknowledgement, then the answer is that a future release of Xymon will allow multiple acknowledgments by different groups. Level 1 is the operator who sees the alert on the Critical Systems view. Level 2 could be the engineer who gets paged by a Xymon alert going out).

How acknowledgements are visible to everyone

The acknowledgments that the operator enters from the status page will show up on the status visible to everyone. E.g. here is how the overview page will appear to a normal user: Note that the "disk" status has a yellow checkmark, indicating that it has been acknowledged:

And the detailed status page also includes the acknowledgment information:

xymon-4.3.7/docs/mainview-acked.jpg0000664000175000017500000015015011070452713016543 0ustar henrikhenrikJFIFHHC  !"$"$C" ^  !1"AQ2SUWTV#3Babq$%457RsCEFu&Ddet6r>Q!1SA3Raq"B2C#bcr ?r:BS)䤝Qb+pٟk0$WLT4Ҿ*z=b _O]2hqfK9Q+*zKqFQCOb摡IröÆ[]xTԜ_E((e$[Boh+"K"pʤIu,$4yhJTXwٟk0>xcO A6ui+^u:M6HdB[Jl23,ٕm>6g 8lϵ8?.ZZǩK)*r &YDNrŻ+W5P\j2\draJC ]$Jl!wI fTz<ڽFrLs-YBMJUs;؈}X-d4MNi)Y%DG$X֢A%7=甈"-XīReODyHQɱ>xcm)ص5Hm.-SO"ZhZnG*Jedd{HU>mzGSRu7)pR?[ UQ\_PnZnRi 3MiZdM ngq>xaf}Ɵ nu6peIh% {t^bٟk1!18?*#ZWR952tX̣ĝqQ(B#U+%VK\DDa/1Y6Rldp()Ff{LMjb0dٟk0>xc*f a8; Q6IEfCw֙\-b앬ָ#p;zQļEk&j3f˭lFL 3o)"VMmZP{^'  ŵw/ąIlQ/qiQi,]=GK MVI8&J#%R##;qٟk0>xbmKKYsQ%6LYAaRRcTIA6ٙM˫1'o"&¡ړ͸%S4>Q).SI"AA%cI @_6g bѫX«Ӫ/ tvw2ӝYV2jZU*#~R#$>b5ݳ&I'Eϒ>*#E3(a$ðz7'1IR̮hQ*Rٕ5sRf}Æ[@^8qEYfU"Kqe%DggBUGj u:̊:u$\!-6AlwQ3o6g |QJ+⪧XN6.X$u-'opٟk0>xbsOoSVSRV2-9ks}-W7MN-rTӆ2qVT$Δ(E"23*UX"CєMe4lvܴ(rXv;OӒ\6JnRF|ʅ*Г=bNQ0UñVm6FuDlBfWMFyLȑENEEjTƪo-ٚRKL:u,]*z;;R6ȫM4k씭B Dc'̉;lϵpٟk1NhJVQ}\)MJU961n-*A!Ipj3$e:v~WEV]ZR&GA1ffW.i$;3o6g |}6F$cKGTj3]êNDP˗#:k~54 $f8Jt$qٟk1Pᚭz64֢@Q|xa:m iW$F[Jj 4z$ #&[ilMDD88ٚ).j/ו*^(2FQ}j4TY&trigssmȏ+[኎>6Z%"biR䭮(72WuV%*YFxJ*GuX1ȼIMjJ,A6Q8V$ f}Æ[Ꮐ >6g 8lϵf}Æ[Ꮐ>6g 8lϵf}Æ[Ꮐ>6g 8lϵf}Æ[Ꮐ>6g 8lϵf}Æ[Ꮐ>6g 8lϵf}Æ[Ꮐ>6g 8lϵf}Æ[Ꮐ>6g 8lϵf}Æ[Ꮐ>6g 8lϵf}Æ[Ꮐ>6g 8lϵf}Æ[Ꮐ>6g 8lϵf}Æ[Ꮐ>6g 8lϵf}Æ[Ꮐ>6g |d܍_Pud4?~'h ?jdruBLm%ԡ"{yXnI"3TSRk5T`>r5-4 Զf,LF7$rײ $]E}>~'h ?MfIL,9Q} )-]Z[Q)j#Sj4U"8jB_imi%rYf25h ?O{~4-;RoJ5dNy씱H'\BniJ.dW;˘i0yr2k[#"R(y~`}J4OcKE?Di}Dd;u6˓dkem84[^Xqѷhp깅|hvE_1%D թJJf~uWo&[[v}g?6EuFaߕÏE+,I8O :4X6׮i&JY$kY̮h=v)N]>2";J=J-d"_}}~W >6 X HC*q lmhAmʓZ[2łŒ**+GbT ّF&IXV"}}~W >6 "RDNDJvԴ ))Q).Xdgr;qԯy*>6 }m-\/,hq+ph,;!%ZrKCIJPJQyPEV}m-\/,84[^X5Az*KRjE7HQ)ky%&[vHp3 s?:]->~nvooyaFaߕ4*)*~;)Ju=K5-fj-j31O@R ϩ;i5o23+p<}m-\/,84[^X@Ib/"S(im1ixa Om&&5mRn1]qѷhpooy`4Lj4l>Q2vkmܾ.qiSh0lh6e6n}}~W >6 8ڕ8]|G3UG]VIԧ$6Jn"iD_*s;njv5#x4[^Xqѷhp1MŘ7-H[2%ym8dyfydg%vi5C I?}}~W >6 `thb1 v3L%%TR%F7-QIV`!EE1BQizQ4SfFg-fv1ѷhpooy` C^ߧ8ڕ8]|E_FaߕÏE+-6{~jWw}}~W >6 8ڕ8]|C^ߧU}m-\/,84[^XjWw6{~Wѷhpooy` C^ߧ8ڕ8]|E_FaߕÏE+-6{~jWw}}~W >6 8ڕ8]|C^ߧU}m-\/,84[^XjWw6{~Wѷhpooy` C^ߧ8ڕ8]|E_Faߕ9"ooyaFaߕR Oq+pE+}}~W ZmJ.>!ԯy*>6 }m-\/,hq+pR O4[^XqѷhpԯymJ.>"ooyaFaߕR Oq+pE+}}~W ZmJ.>!ԯy*>6 }m-\/,hq+pR O4[^Xqѷhpԯy>2fP΀Cnv߻ ׏E+}}~W wѓBo>M'XvߴϧŰQO qehZ;V*evw:<4J%Բ VPwR̈s дDr!jN;wii=6D}3`UGa'*cժL<:3Υ'fIz瘬j[1܋GD'pԔ%MðI6Xd;줐Jv"Vm{7Zǽ=)._׭FޯCؚz[P[^z|zE=;*= ҥMFRE{%GlIVηN,;RJBυۈ""ȟi}pފKo6m:Dec4JIؤo##0+TF jiW˿ E92KB9RԥRDI#33RHk]qC$I8M^rSmқ$"#Zgbʔ1L)U 29 }N(ғպ-r#6bzQqZR9J 'o>Rz{Tۍ\[66HFu$ddGiXl5(Ւ}d~;gV(#-V23!_~eLhr2m>o&4r$9ZF:l1E5x:a Ƣlѹ;H'TJ31؉B-wӀJSuKjS%&G%##">=FFdzpH^W&}JJu YԬbAFdJJ5.,̘ʝ]n/{uNŕ*˭>~LRy[hfSN{4O9gY[%P`zRcE,%J& eX$2w@jѤ%GȪa ]=wqOTG /:%Qĵ(UYd4Qa,0m3-*3;>q,ӞIk,JJ's3)Q䱬̶_5CSVJ$|NTwo}nT$4"djե&;(={7\vԪ52*}&3IoRrn2[K :T⟒)(iVjYNvGd{qq îIeFyeM8У}[nټmKμiV>2\R8i; 簿 { DOg#Ɍr-zЗm-#JJvfSK{4uڅJ 2sSZm)hJ23dEj+#;WWViͥ[FIQ/"v22C_Qe4Uq[skZj+n!q,RxÓ#fӪfIA`dȮ]B[ E}֍IDl3m^tG؁8s$7 &J!9} NgRRCMd>LsuE΋+tM~OO\F[58*3HƤ7!nRJR͢BJ#-U*ԝMEi;/Ś)I&RF3(JeE&bTCDCSћ!솬S#"KҐB V΄4!a4M0ꌴVI8%֟B3)ImiQ*uT(Mz"8i)9RL :dy3ss :"G\JiVsC еHZr%JR\I7x?Ũa+YmZ6vw]/46_1S \w_Ym/wϸ1|H}4KjwSYv~hKxSXvUdγT ̗Jl\YZ21=~5G*YԈ͛BShbR&CٓKSKiD>M C[ T#7*+cN$YDFWIȏf6GaftQEfZ:˲eGOm#WIY8i SaGHtqDGTqODչ3Ji"nhb 3l_nD2jlO2!Sun+-?d)??c]ս7sU|+>^lNkۓy0cz#uq4 4)I5ʥam7M"jf26y1Oͪ҄3c4H3Cx!v~'+Sيsh"YY/z1nʹ IC%kd̮hQJIU([ hc**kjP d |kT7.6r:L<7Coi:|DDN֦sȣd T<*&euF2ȕ!W۰ 5L{އHNTD3H\+_؜m'[O9H;XE},(bVD** =ʌGpqЙ–eakf繸?T}Zst)Z9*B"YKN.^t;%.&xE Jb RsdJJked!V33nuE={p^>&Tҗ5-;_q_.*HY]$yb}Kmӵ[N K\)6ɓ')&i7"%(͡VlEVR*\!aeUnb"3V"rmh5YF6+_H,#M91dQR!weE')GJ Xyg&YLrNcs$"Cb2#K4v>jX槆NHN yLHk[nӕLdM0%1y8V;-\C]⊾]wԗ>.,I3Ǜ-zx)]u%Ϭ˩DmJh-.D7mmrqi:#S:9F\J2&PYUo`=rMt܍O3Tb,dJq($쇜iDGKΚ "yFiIeR洓YY,w%mE VcNfKwiM>eR洓YY,w%.C]⊾]wԗ>.,I3Ǜ-zeF2crN,4Q&\xr+qW;6̈]#ә51H)TioQ0|Q%dlSm5XdO_8ut& ~e"hj`=DI/R[RZLM$^̀_M뾋z&/)'p}fMn/&k[v>:qt1WOJ:\nT[IZ9ڥ#)-҆fEm暪`* uK"Q"3qe5]jx>) _P b(ԗ]IN ZHr%C!bWeYio%҃4cwдDu1 #r3YN."2'ۉp11=nZϥGV""qv z|M9/P.2j;(]$WQ"3.p:LCМLz02$6 JT؜s+ (:|J^!Ztp8øG;j#0WK uT""3}޲OGD֕4Ң=FW4O S#)[y">cjg{ڬd8Ғ*܋8ljw߿hZS8)pĩḙUR [qV"&^#F}?_M )ԣMwT}sD.BSyP;u&wEU׏Z ZX[NTCӨީM)FmTHII9:: =v ENS=cӎnEby^rfO]5MJyn$ :ԑ6쬧{#W7 -"' TXm4꫊塅Đ% RndD{R?Vŕ.QxWnY5>jk9f# IsR5i/-IQw{RH6\8 ꦇp2;ڗ,@n\"ud3pl˅q {*xҿ xi.Җw3c\[Ba3SQ̊W\ҘQΦ̐*F7q6S)"22uGn=)&0(BD鵩e&HKJ̏3KJ%V>#FP1>=G*2[RRFvCGԫ\Gk,& %$w~.+Ɵros^5VLv k:gS=$8{̬dder23يo>v{q՗$gs{]ҕS19Ffc7CĜ2ͯe˘Nw HD$xZR;_?,|,M8kqJҖXR} y#<6)i3q_[c´uOm,o:nԩjIe3!,:O4K+VDdJqL>gn/"k&{xnwPzRF;pV"MVgB[A! $x@w/[kKL}Oז#VYb{\HHGr%>q)b,#M(k юʖݷ卲^pKM~Õ-5so otfi{ǹQܒrWNk4mk,2B:6g*Zkvߖi۝~Xek?AڳN?JI- +i2!ѹ9yǡ;&IqB)B7li;\x -5soT,јɴ, 7'8bMi.j.B۱!d鵛2r{Rq]lv*Zkvߖi۝~XZ3?G8鮶;kMu_|i۝~Xrnwma-OԺl'*ΖeMV$fܹDeMu_|i۝~Xrnwma-O|JYTx<evoȋX!Mu_|i۝~Xrnwma-O:k q]lv*Zkvߖi۝~Xe1KSsct[Aʖݷ*ZkvߖjR㦺5m~q򥦿nwmaʖݷZ3?G8鮶;kMu_|i۝~Xrnwma-O:k q]lv*Zkvߖi۝~Xe1KSV|L X eIHmXv""!㦺>T,9R_߷;WFb5m8鮶;k-5soT,2ј9Mu_'ܚ#A\}DqkJU }?:R_߷;KM~-_Zt[@㦺>T,9R_߷;WFb5m8鮶;k-5soT,2ј9Mu_:k KM~Õ-5so tf)j~q]lvc?8R_߷;KM~-_Zt[@㦺>T,9R_߷;WFb5m8鮶;k-5soT,2ј9Mu_:k KM~Õ-5so tf)j~q]lvc?6jza->:^{YHFwKkyi?E8N$%t~]lvc?29yi?9yY?kqoMu_:k K},@K},@7t[AP6uJK%QS:jd'9!32-C.}ܳ.}ܳx{ke:w?98chc*y*RL/k"Ͼ{ rϾ{ ?D(HR #J(K},@K}f,@cq MRiqtM:+-Qb6ɯ$W󷗝.}ܳ.}ܳ-U?D~vϾ{ rϾ{ 3sHo/:\7g/:\7g?DG8l9&-Fl[~LBHDX̯ecK},@K}f,@f$=i?jw 1[imc4oRZKQ)*$9tefԩh;jIyϾ{ rϾ{ SKzH4*7㦺5m~drϾ{ rϾ{ yߎct[AΗ>YΗ>Yn?M鮶;kMu_ssߎct[AΗ>YΗ>Yn?M鮶;kMu_ssߎct[AΗ>MYΗ>MYn?M鮶;kMu_ssߎct[AΗ>MYΗ>MYn?M0% [)I-*3%,Ģ=?oP?oJǟyyY?hqq33OQ{\iʻ\)#f`7XM6èZr,9-:2z)z/5eZ+[n}mPu8Tjd.%bK %9q%'dFdWI7.?lPM4;K'CH$4Mi5_TW|lf4,Z.>quG\uM:m+Jd(FZn32#xrz-KU[Fn6b69*S)RO!VSgQc*Bf0Ԕ% G'bԣI؛mM|n'2 Kh/\F>)4v]U6u Zp˶ْYHd{b᪤ү3423! qJp,#IV"̝QQŔ<{ejIjO5:c+܊m6б-bFYÏ[!lН7l>љؓIQ)E'b3C#\y4mHa6rk̹H8y"J)%t5!Qgi46cT%cQ Km3: ^ק?Mڬb*5oUȆLQ4 )VBDNi&XD_U6oc;4cHZ? kOͤT[SzԸ˯Эo?JIFeeD0+7l'T%Hp4'>b;&QYDF#=gzKNf4|y™hoa՝ZR]SY57(cx =}ze=Z);M1M6\ZSO+V(?.CvفFuwo?Vݤ;M-05-fF˔(+%Jqgj8SZWf\{ٯHE]b7}RfdZRوV8h)eTx8jr>}_;&O[0?PCUp&(GR puRnke#[hY)5l%Nc;iZŔ鸇Hu.INfZvcJ-]KAɯ4I75 PpۍS.z5YekJqEFPrrh59ykifᩑ%cۥ5"ť DvXCKDKSn6mA&gBiZkAGjNdoMV;U4%50~GUKi͖<ߝJifYJ%7{XpB8HX32]PODfZd}z۴-&i$/2=)I1QE=`N_r#⶿ӗ㽩"D *bz !H@z ````D  0OyF(ҹt2qzWpU_,x*uY :{~6Sto> 4E#p>ʟˋ],k}Jx^z7r-c~ƮOHK%ŮYx&z>*2B73q?[ƶFeŮyxFz>di>Ku)F>ό7x-2 ^cIޕ ޗ?^#шHĿUЊ^EU;« {oKi1b>']F4V"E?I{=iT?HufFthOe>!<c'z#OzOrG 3FNzM=&dS9 Quޓ@2{)I{=D=Tdw=|ChOe>#U:G]4c'O@3FNzO='dS9 Quޓ@2{)I{=Dz:I{==|G"2txOe>!4c'Ȁg#OzMr]b=Tdw=|ChOe>#U:G]<c'O@3FNzO='dS9 Quޓ2{)I{=D3FNzM='dS9 Quޓ@2{)I{=K@g#OzMr 'H&dS2{)PU:G]4c'O@g#OzOr 'H&dS@2{)@g#y+-%QȈc.cD+ԩ*ڑ=:j$@3rzH@zDL:ìzE7yI9FiQvTP2كI*Jc-LʔqM|O)GJï S%STu/"&H]̌c98@@ګ0\yb4F\_glZU4FK[Qdd[=C\LCr-(#&ɬ$嵌z}*Ӧ4ݫo5G}}b :cbuV<>kaj ˓)lJ=/ӎʍ{;^믊m.ǘ%OVΉP̡Ri +$b#Wƺ^ zMDY&\Z).sky+I',Iߛ/n(9ȈQٍDsF[#a!ʳq4w))NOV6Ltiu\eN>êJ뻊UVOFѱe4$e6iؼ5lӴV Mu k]U8&Gm Bfn-/f7W0F#"xtbS.F#~c[ @60I}OX=b0&te+GUHJ%um5*+% U=N(t}*i*!QCJrA|"2I\33Ѷ2 i52Ms$N5b"I*2ؔ_##->-Pφ*N4 ;kbDvN6nLyIWo 3+]ᚽW}Go'ht}Gu,n.U2$S՝M8LfV̈fþEn%M)rh8grSu(VRz _*QRԳbޥj\GM+jWIldiGCpKSکVJ946[gRRE>vұZ.u4ʟ?SattFZl";%;(&搨U0[fN=*4e\q7Zl4iL "W~=:\iGZZM&{Oi\guDtٚ3c8EQCTUqҫ#DFfg`C4:x{N\eBȧTW4ӗ!澲=3υ|jzIp6jdy9Fg$5cWq3k\nIeb-Gl04t:˩U[tjڛ(;w#.aL'ARKh3Ȉ$Uq%(YfYmT+e$Z\*䨴ZթLb3<ڝ-CZ>zN@b\))%Rߐi.K%rvu :;Iu-T,BmhNef$%K"Me-j.m6[ FDcSZbm ʗi*tavXgJX2u/`bب d\hԤ"+1pfvm2re?vm RMd+e2#eEbI4_6:38: .:LR,68go$8salm })z2L4{_fUZIqHċji؏=^Y2G[*% J3f->lM`:# t:X[S:i[jK\+t1֕HX+ޘCoT޵MIf;zKCVz|YlmMTɾ .: Q)\Ⱥ:YHxZ^"q)lQS)M|qYT[3R2iN.!A\!:EcjZ*i5e6i2+3+^ T: $@zH@~tjEsI5TtQq-K̑(De{7˥F&.(s r:im-JrmJÂ/b=1%pn܎5.9w ֔Ed$rU7-)Pʗg5+3Lϩs{fOH:vR#Ǔ-If23j!a~>.2e7 qJlkVc$eZji3(ZMIΥ"p,|6OVHLƔR5o6IE7>xJ4 G&8WM*BJ+6a4kkԊcȊx3dm DY$Ȉ=s?Go i M JNe!*4Ee!7.Ptt$:Z[UYl'\#Q$ʶvV*aJ.V `i2[Fvp˝lKc!LØ+ait Tu2̝RɲJY۷S8u.:?Db:)CZHhjW4IXajD]RS ja" #tYf'ۺFnJۍ"t:Khկ*Z2R)5$)9ڕgGxKՙSX~>RД,լšj5(қN.p2_1Jt&* Bi6c$+_Kn#GzЗJ3dK &ɱ#>c~ݡ5pV_Sj tʧ1ҷ i#;S~jwvӴ݇@w F%5*Jm5;^m$VM}cI\<=A 4HZȪXzVhZR DκV>O[49_qR)Ƽcu&_)l#j{Kx 0JEb56Sݞe3sQiA%[Usݰ[yuiVD./A.V2m'L,|e-T֎HGXri2Kiu-7ߴcioa΋M~IxWKBMJoEJ3p3K0AoK)n8IdȵnWqҶ?|pɪ=+L[Vc|f)=*w8.Yk}\}N?瀨XkUeRun2LJQ幤-[Oe4N"* $t\e+fI-<:-bv1k3 f"9a>!cw2Э@Y|cL? ͏UlBe |Dr |C?Ǫ6eZa>"ya>!cw2ЭY|ccy껍hV,AtOr |C?Ǫ6eZer |C]0?v/6=Wq- .~; AtOg]̴+@W aX'O aX'3^lzfZXe |C]0?v/6=Wq- .~; AtOg]̴+@W aX'9a>!cw2Э@Y\cL? ͏UlBW aX'O aX'3^lzfZ /]0?v#]0?v/6=Wq- ۠@a>"9a>!cw2Э@Y|cL? ͏UlB,AtOAtOg]̴+b,.~; .~; ٖjL?L? ͏UlB W aX'9a>!cw2ЭY\c!cw2ЭL Y|cL? ͏UlBer |C]0?v/6=Wq- .~; .~; ٖjL?L? ͏UlB,AtOAtOg]̴+PW aX'9a>!cw2Э@Y\cL? ͏UlBer |C]0?v/6=Wq- .~; AtOg]̴+P_ aX'9a>!cw2ЭY\cL? ͏UlB0W aX'9a>!cw2Эo "RIRe$mւQۜڶe3JQwLWXિXh$hn;F᭓w*j􍤍5rzGVFfV񭑼V񭑼VNf|a:T0F#"$b:1]c)ъ`C#T?Hw1-ǁ  I$ >'A @zzz$ !$: $@zH@zDL:ìWXિXh$hn;F᭓w*j􍤍5rzGVFfV񭑼V񭑼VNf|a:T0F#"$b:1]c)ъ`C#T?Hw1-ǁ  I$ >'A @zzz$ !$: $@zH@zDL:ìWXિXh$hn;F᭓w*j􍤍5rzGVFfV񭑼V񭑼VNf|a:T0F#"$b:1]c)ъ`C#T?Hw1-ǁ  I$ >'A @zzz$ !$: $@zH@zDL:ìWXિXh$hn;F᭓w*j􍤍5rzGVFfV񭑼V񭑼VNf|a:T0F#"$b:1]c)ъ`C#T?Hw1-ǁ  I鎎O8DJTdN'PeQtd})#R5 ]'-hW' ZO3c=&3IԇkVl5$Д*A&\eb<JXʦ?2Xdm;e)RF3Un2*CiiGR)dLRJз NE8$H͐i){֥~ qiG*ݪ:ŽCi5gJ%$H"I[ao+ЩFN'EWa>H2)*$XiEĹ_[$r?; puFQksj'b%]dTEZ_={XUe+\VtaATHU?GkDvF_q m̶8?=>riS&v]V\:lώ*JF{i[ncIXrGU1*Uǩ!8ִtDJ]-I3hՌ_zAAs L[4[.R%[qK"3ݘt!Ťi hrgǜ")i6PRT$ +O߹lw(- !ԕ\KA{I cX$+/!ߜ#]9.a*.#(e]>Sk^VhBA$DĂ#33-Iש9\Z0p&]2&_t^35! "K*dK%Yz f7t%hONʊ;$q$l8sNLX&K#JVp|L:E&(TU`fY-IMGn"QnŘΛIF)n()IV.NW\VWf@XƱEvN[T֩ d)Bё ggڏp^5ڦϯDwdo4:vf{})rW_w#ub ;FJ]Ui,^}! 5Ba،@GXv*MTKfr]mhy2HY(IH_ٿمZ;8Xz+VlQM9CK5zZ"Eia7do)( * øWnYDO1ISFdi3Qg#4Eo[/JTע^ӕA)o,i[+7iD];KhJbDRR%:ڝ5$$dDȐpp7j҉NIxD+usj7y-6[Q8YLKddFifE/K"c/DBVٲW1dRM4(W-l,3ٻvo6*NŘ&pØLT5^I6_X jKQBxX- ȜNjʢ,IRG 2j[1rЮz :UnF:Tّć-/Kk(FI$[c1,'Ԋ6ėҧTj%!) RT.\Jo֏@q&v84i5(UJw3[!dJk*IHRVWޭ}/F">8&9.qClFLYYܭn +6WW[w(EwK *gbnhKHVgWu8y}*=}bUn@ ֕DpN(5U-q_m2kT1(ԒKiI$];,9p~RGqʏ*Rhs75'mEj1^4o_Nߙz~r X.cC"\z;f"i<J7lg ~BYut|*>'AĠwXL΍r&F}7.Bʴ$3kJУ2Qve]SY1+3j) ]GζңIors\C *8E*-ԩ@pi3W)IDqLyHAe֌m3%wkf|~U+;EpbH*Vg-mXGs-1ma(%} [nkV(҃Qe$̈FDFvUq[Yj%%$V_Ȧ2JY."gI+3I6GV͡55tYxzU'XD#ĖFbp3M|#`U| x_hGFF᭓ln;~BƮOHHcW'uhfk%olouhkoL# шHtb;.F#21v21Lc}S"xc&k ՜<$dwAG|aҹȳQh !7 )G*qIH*elNtߊ CGʥQ?iV\jdC%)f:kSpe}3E+%յiBƪi-z9S\򟌄Nt(Y }9Ȏ!]jiBǛjtcKU^bT^irU$%Hm|ʺU#V*FCJKVKJa$jdiVE&U UWcɆuKӨT!.Ur %Q23ʭK\"2+g['(;QRVe P1#rTkJmNg~,/Tfm%)%*#mdIiW],Ds^q\RTڙ2Vpd6 ^*I_ SabKQ1Ju+eI);s,@!7.$;3PpDž[5Eq20hlq+v3{HVc9HU*$RVأ2r2>qZ3QsMTbɩ" I/&].}$ࠤ&& G˹!w]RS1je6,Hq:$Ff̈ ՗@oxǓO@:mfYʪc\FHyDw%JV+S"[IF9nRIEIY@-:2i[rLEF#JԼD"4G#˰"!ӕ> ,%)DIiUYBkdfFDw @3WSO&-OHtTۨrQLy>Rw%:E.ܭoIQk(*9)DRێ6 hJ˰+3&D$BVՕO6ު OɲݭȄDi"3"I܊b+H1]m:lBSYY5< Wljw f tBChUR*]P΂VےL}LŽCE䈔 kv$wR )\ԺR̕[ aSV.Vt`bzJuFѧ$y6_[OF$-^+r摍 &ł!t gD$lgAv>sJ<׾cDb DJ.\}cNtG8NDY ܣCԛA`jC._c<yumj9jK~|'̒ZFh;!$YLbjxmNT˾zts>r2;mcu5i31c Ŵ;M~2`U]rbI3 3ceQv+FۙV6q&n~*[z1Ԫ:Rn >s^y_b*%[򓍨riiCmH|YR5oMT]#ANj:jyTPkC[(7k˙v V&ãX:qɨH:elQt [GʕI2%>$&KT4&zk16IdD^+؊ՙXja}=>9NᲑkd% F=#i#q\ՠQkdo1kdo1բS|a3o27F##-шH Wwtbw12LcqB0DYsMuSޢql+&4dc1J?o0,6~7QAQz _T|[@Z6]#вѯ?*>Pz _T|Zese?Hвѯ?*>PAk +P NlGZW5GG6~7QAQh͗H"BF B=ѯ?*>P@29]hY^hu(=ѯ?*>P@29]hY~hu(=ѯ?*>POXese?Hвѯ?*>Pz _T|Zese?Hвѯ?*>PAk +@ NlGZ_5GGAk +^͗H"BF A6~7QAQh͗H"BF A6~7QAQj͗H"BF A6~7QAQj͗H"BF B}ѯ?*>P-S.Eօ6~7QAQz _T|Zese?Hвѯ?*>PAk +n͗H"BF A6~7QAQh͗H"BF B=ѯ?*>PZ6]# ,6~7QAQz _T|[Tˤ~ue _T|hu(Vjt.,Ak hu(V͗H"BF B}ѯ?*>P29]hY~hu(G5GGTˤ~ue _T|F Ba͗H"BF B=ѯ?*>P@29]hYek #mn ؄Z6]# /mnF Bjt.,Ak hu(Vjt.,Ak hu(Vjt.,Ak hu(Vjt.,Ak #mn -S.Eօ6~7QAQz _T|Zese?Hвѯ?*>PAk +a͗H"BF A6~7QAQj͗H"BF A6~7QAQj͗H"BF A6~7QAQj͗H"BF A6~7QAQj͗H"BF A6~7QAQh͗H"BF A6~7QAQj͗H"BF A6~7QAQjajt.6"_^E~[6+ꍴdHsDs"[دaO>U| _hFFF᭓uXAdc:y !]C$&Ht gΫb*(ZSUivtҔN*͠qX[}&cYd5rzEގq4Q/^־D$m ۿföf7QE{afN*d#>K%mUtҚSJV񭑼}F>W)5DJ[ʉ$×iV]>xEW`X[Ǖ6R"j%씑n)QۖգJuc{||a,~dj1t+tG9dV,;##++1s2g !4 JET#2#}i e$UGzGWHnjK)_Mb[SMHR2X:gst5MW ,"Rr)V+,˩I)7b ;21zR1Lc}S"xbO@48 -VJ(eod} CRWpʎj9qmmȶjKaw~-C7tDݿN{*+W*t=aj&+?K&H[*} D4҃#Zv䝮S:F¥iM#2:nQ\;ܭjʓ;Ks%GRP;_\PHtML#jP6Xdio-+4̈22i#.&D=R2%D{M-p{nk9.xpGfqwr>{ ==Q@ώq'?՜uM浯c2i#%%td=bFEZJO5ʟB etyֲݹ'k\*Xɤ ԥS.{vI}qi Q6ʌ\JDVVEEr۸r.\]R5" Nq(`=P+4R$FRu- !IgcYIJJ6&S~%̨l%.:gI{ljZ#:~}9|JjO_FP/I&\}Mft7{.WomK25URnU<֭:=b?C 0:r7RdNg%Itˠ. |5Q|&Z^\}Mft7{.WomK4Ԩ5^K[W#J~Mdcp#Na6y*i2##WkF!zvYwTy^}~u {|7ԃi@NhOP=BBɢXL23Ν)fFVRDڌRvFGb2tj'Ԣ4`2E'ywԑm՛q?G2N׾4>wP6lJvnZb> @9Ք*s = >ǑФҾ$c-݋X ^ qoDUc):8~mS",MIԢ1bMS3K3k>kN$+FO@ ĠYz[QUSOܫh%pV[nX2vW*x.% i:8"!ns+]teG5L6~{d[ DE%isjT0xb*Gc &w| '@X4IqQbKs[KG{!=Um#3ĪoL.HCLIPI.jcijk {]E߹ϫTi|>GJT:hzuIxFF!rKd/Y;œ΢! kղ7jUmo(JIk\.[ƴ*'WMFsO6-4Ĕf0'>|iG@Wu:U\i\ql:4UwҵW T*cVaՠϓ([fnEϧn΃!QLS,fO!K%Ԩ_Dgw4y V=kFWNV%妾 ծimhQ/\[alpxNTƱ%!lɕGhԢ$)V+/s"EjtfDՌGF+e:1]bb;1E!` 1c rRvb}2>Fd{ o\ylCK@5f4ķXat[>'F~eyq۶Sp Wiu\De62g=GunVQTIAx(r#N0mqЦ:Yym։7 [y\m㤕M-^39NT! 3#,ǫIvD82)P=_0؊eHݒ┵֣RwRf}c ;\+]Zr[hG"2led(ziZWXZP$i:qWʪN\Ɏfq[At$a IY2GLO$SeJlkeFG73_]$zLEZ[r$Vri>keOXX(фfiq M{z T[jO6}k9-6oΏXWOSacbLB'yljJ%$Bv- :(\^.R]?hYRN}תdɕ7BE=q24SnyʎdDV={V|\vH^]]l׳~mkߝ~ 1F z:+:sʔLaiUts=g݃ɮ%I8$)%I/{̎ǰܙP~u$A\Y+ 4=~݂=秩_l>.h)Ƥ\dveBrUm욾+wYHAYI%dO@ @,C;TTSO]O]̏LY35Tرuǖ4VcJKp~u_nXIN4"z 76ϡUQ*mπGQiJ27]eb.Dmdj3e?aQm#L E*0ލ8m[x)lpZEB9f4\D;i+iRjAjMj)62㍓emF'X/wU׹"|Mz\fu@ö\P¶F̀gʬ!!ɧKj[)tХZIDFFer+.9DW uN.)D.*t7ʔY*S̒YМL?tO߇) ^[i+Z3N(u35iJs;l2=ZzR#̬dF:77/Qb8nYԵw,Eֿ@N$ͦe[iu> BKF5$;_ZSޑ}U'RUL1XRlTwZ{ڮDґl{R te bSm"@&pʜ&nRHVI7vnHLc=!cw*u/7WXu[7IYI*PDIUH !ri(N Ӻ4I&}6k[5'b)E:YE'$+Yҝc(FDW3"Xc<tXR+6Ls;]M26b=r= KFzf=O*t]]]qSiJM+BsHt"+JU{az-R lI:o"3I^$InGL@ͮʇ:*]>*#˫q '$ ԫu[hI}i)PiiUfl)Zͯe;e㽶\ PPi*S>[5PR&[qWg؏kM1ԦqHKI%UcYn# CmRAH9KJ!ܒy󛌥|ʵm=- HnAk[ "gb-{lb;OYVV7o)ۧ1ڽ~[/7(60)94 zcUF>L6v/vz\C"JVcHY)9}尬 Ӛ`R&r99E6Hnl(4)berFm-}HQ܋)-3D[mG1ؘ IjSm%DHĤ*,Gi|@tM5MJ%14IuMb3+HZ[bOiW2@˫EbAرQ*m"eEKmˑ؝BR[Hr '@H@ˤEbmEjQ).JKm\B״{LbUbƩDe..Dgbu^;mIm#`bH:6Jѩ4K%yiڣDDW33DFgb!/ TJ\KbܥJfeb=[npҩFr\S*a9҆ԽHiiU)eq#Čde`k:=| Ue(wQ9l*1c VUiMYaN'Ab0H?*KQbRmIkQ$Ea~5Fɑ*±ܢ8뤔Ye>y{-O*~oR.QHGiO+\IBaʫ%Ö\У%Y)&der3F[ư^k:J*ܲ* pϣ[(C^z*Uciz %Zi)U= lCJLp,KJ3g-Nֶm{--j@ mT1i1iS=<O1t󛌥|ʵm=-R An%Q5yrp[{.7XmPm0w 1 ::EsI۔W+좹\5C S1e ptqK(vu}w,(u:l+yM_-b\6/ZqǯcUҼ-o{+zyqTNuo>/B`0eqJ%YFəUt{ sҷl:dNQH鴛 =&]EĤd#\M$IJ{'p?L;=T&֣ӨPiH%ԜeTmgPi#A/Y}T І򼢋15KV?P`SN$Ҩ[#5j32b;I8xaKUGQKzɱPGqΌGzEEI~{xa_5K.lޮ[y.ҵ3#'**ѢȐxe) S[VQX|yWwDEޞho(xhXFYUI*]mgcdD_HZܚP)"D `O@ ㌺qM&[H ˪Tff*d }FDV."_1VFO@=L H"mVQT%%M Y/Oa\ii n,rL:ìdWXિXh$hn;F᭓w*j􍤍5rzGVFfV񭑼V񭑼VNf|a:T0F#"$b:1]c)ъ`C#T?Hw1-ǁ  I$ >'A @zzz$ !$: $@zH@zDL:ìWXિXh$hn;F᭓w*j􍤍5rzGVFfV񭑼V񭑼VNf|a:T0F#"$b:1]c)ъ`C#T?Hw1-ǁ  I$ >'A @zzz$ !$: $@zH@zDL:ìWXિXh$hn;F᭓w*j􍤍5rzGVFfV񭑼V񭑼VNf|a:T0F#"$b:1]c)ъ`C#T?Hw1-ǁ  I$ >'A @zzz$ !$: $@zH@zDL:ìWXિXh$hn;F᭓w*j􍤍5rzGVFfV񭑼V񭑼VNf|a:T0F#"$b:1]c)ъ`C#T?Hw1-ǁ  I$ >'A @zzz$ !$: $@zH@zDL:ìj/>l5 f_CiZUZCW6#:)I\=j-Ɖ i%n_,?(`5w&R%{W{H~PH7ʪ9pƮOHQ|QNt7o'r_/$a=^>HotҦt*NҮ4N4AF+Z .H_o<5+#sKMw4ǏG=Ow\"Úh nQS4WFvъ?I^4EG;NǗ=%_iG9NzcZ oeYG=%{ҏrI^y1ljYg=%{ҏrI^y>vRԲtW()@r餯zQSZd0]Ե,]4J?w#M%{ҏr !G6er餯zQS"]ҞUHҔϘޣ'aﴂṊ!h/NjPS/&dSim5Q侖a-%Me&˫*qoFn:gm[v91DUiu*ky Ru Ju$M*8mDF_*=H_bkJ E%;(lmI哵(6G׺[_]+kCnx^ &5.\ڔe%ףSFA#1f(]f[OfҷCW´?GT"diOj(K#H%˕4IFaѩ5( WW7i5Zx*Xk1ltxynK9(5zI (dK6|-jK*kC#E";8&W>F]4J :V— jg5jMRӡ,UuܚUjR"q wPsFF~"I:t 绷;^/vօ͢3P&jC1$*]79EeNK`\$iIZ+jllEƫT6R"%\5 I6#:jxl7/37nֵSIp=J7!b5J4BC캷a Lco"5z(Yv)iF*|W+Rt%4yƊYYܔkȤ\lGb$<MM5m߆z+{ǘ= apu`.M &Y5PJTIM( Qԑ|4S&Q$6֢%cp~ %mne6ڌFDgHub骷_6Z\,6և]c4}/blf!ǎfQ0Em4ѺB3Jl"4l4LȹӡMӧ7vWѻOpU_,wccўd['pH5wC[#q\ƮOH(J57J57)>07JZFޑw]GF+e:1]bdb;1E!` 1c@' !=` OX  O@ OP$ t@"O@ IB O@ HIXu }ccWpўd['pH5wC[#q\ƮOH(J57J57)>07JZFޑw]GF+e:1]bdb;1E!` 1c@' !=` OX  O@ OP$ t@"O@ IB O@ HIXu 1Z;>d=2 qLdN>[2[H T2}jWU&??j5/.4wiטV%Sʪ!QеT^mkeddBNɹZp;Ɨw:ͱil.|K~c91u~X(F$<hMA2KJ qr*)ZOffdjev(*ePPZCHym^ 6T} Oz z# gE=)[m6ff{+I]IM֢IX.Ϧ-ȥafh%1qqqdO9FQv$ .ϫN2e~>8ݕ(N6_v3qL:i5fW4(jMq3- h)8ڟP*tJ+\VCH(GYF+s:S`է{S^VeqNx=KH.'ea.<}g`J[wZӬLl26]B%ؐuNr&bB(&xbZKqa/vWwӌ_)߯ƸJ’D "LRT%<%L=ꖤ.$EG{)&[ugZ~l7) UBC~ԶN% #Ȳ6ִ)*ʢ#%ԨAvZq+;i^׏TU14Fk0ۤWkL !1dRY=|qvq$&u+!X.͌}MK0ݥS*ҕNneOemE_ţֲQU.oN2e~>8ݕ멘!Jڤکf̑Q!-iR\vҾ-/J8ݕn-r9K4:.&STBܤ$ғUZԒ%8d%4s1rMJɡTrFD3)Kq\RynI yӌ_)߯N2e~z#aؓQ%ՇjUY;l$s!{96W22k.~z7T 6ͮgɟW.N2e~>8ݕb@Jn3r.WTdY$W%ғ-yӌ_)߯N2e~ztf^$Mfٚhjn\f5"'Iۖ% z=0tɍGb5^1CTV7 [[ N%d;8ݕ/vWwǹ6<1i^ׇէ{S^=ᏫN2e~>8ݕp  }Zq+;i^׏sm0xcӌ_)߯N2e~{iVeqNx}Zq+;L/vWwӌ_)߯`է{S^VeqNx8>8ݕ/vWwǹ6<1i^ׇէ{S^=ᏫN2e~>8ݕp  }Zq+;i^׏sm0xZgʺ}G ś-$Y 7%լ&dܶi^׏[Rˎ2(>̧dޚ/z-DimO&zU#xd0R|?N2e~np7WNC~\"PɺOϋMY Q)f;jHG#~!D؜=̤Egapo$<U{YV: 'FH;f89?[7kJ:e+S؎ťrjŤ̗!>+$Eb+踂^kJQ:RdvUjr=Ni؈2MgrC2R5Kô%)N1!zi#p-dFIJ^IV>oyn"#4sҌ^H(i.Ruiձ+8&U%$4iȳuIɷokAڿ/~x X]3 ֊{*%n9k.OK$4)n-*Z hO3+tj!us PLSTW%/ܚ+eʥIۛc8^ 0Uއq:G3ګf[F;!XGO%hRI%մIGt m _XL?58 r}_;=F2S&K.!{]~Ghxw:!T'q5:2e|&KT4aJ3N+!WEAڿ/)1:M4U%ʍŏ6]rTow҇tm7/r˷apb*l| 並2=N{*i?jh!WEsCicFWIΨJO/!fg*A-dZ:6ivޢֺ|"p8ΰ8Sfw+!WEAڿ/(qS?jZdқˏ$!ouYs-IŚ◇ڧixYLOn17bi1!Dy"ֱ[&!WEAڿ/ FҘRТe;\O*&BuոF k.~; ,UT{ bv1CE&_&Ƈ ZY;bJrUC8^`=* 1;tȡâ/IQCbl坱\%c*oVYDEƥ"'#Iqrsi#Ieؓ۷e _pPi`tLA92U$JN%jQmK;w3='K)Lk9M0o“n2Y2Jvm<Ǵ[Aڿ/~x XtAĘҭQVODžNT NS$FHZ^&Ԥ6vI&1:7ΩW#L 1`ٱ^A{ ӹl4!WEAڿ/S<UnTq;|u=zlj~ÒO41u)G{V:vD՟ӱD0.HL+SZk8^Hñx]RgiBDeRrc'$"N FҘRТe;\O*&BuոF k.~; !WEAڿ/)OFj]i-BaR).2[,wv\%FD[oj1kx\j ­8YbI0pוHO!klWC8^f jfRq )S 6uFZU41m4>ٔJ5iaX&JhnE^Q]S%䥥hYޡ$IDb~xC,F[K\mǣ["B%iI͢JڈV3#)sNhijr !1z"$d"AYK+]Aڿ/~x8ITm~/bS}{כ5eg$\TUUI֡*%0ȓ"3 ;̎~xC,F:o qEAs2dͭ2յܹ@\S迤N8綪˳5!WEAڿ/+LIE5YFEśG)&e"2Bii^>lD1c Nuz1rv;嵶ܬ _q?jhj>U>LBۮ2b њ+YJw3f`` 4ijPrbJSu2i7]Yas5XI""n _q?jh0KEfUy3)7DL=[ݥ2k̜ւQsJ>Òeq1SMTRrFs%IQw=~xC, .j(nTp"@LB2~Jqi՞U6̻sulx:J_]ٹgޟZ;G!WEAڿ/1&TZf-y0/T=cYSțWc"Vq*7K2QRÑ23idd̏2M 3%(Dekg!WEAڿ/0T%9@P^JO[PKTΥ-Iu$镋*ydyrgGWLlETđ\}MHz,*aNj-H%$N+QXқ$Es?jh!WE36*XEG*5J'U$ShQ+oĨ5gjø T(85Zb%Wd-3,%22`sb~xCb䛍唢4qTQXa&_INFes8%2vFjlhs!naү8ZAڿ/~x+*XVfIi@Hȯ>o%Gr5IX15k (uU jT8ԝNAZZ$GsٺֿC8^`W4v)wuLO[Y)<*ŘK4TFddߔ,E1MMqIMQudw"REjTFGY|Aڿ/~xN\UaRq;t8EA'5 c;j$F$W$4fkٯMfKLSkNr$dfEj?jh!WE`[\ġ1.F50֩}^KTMj G8+)+9lvjS1$!5)q_3+-c~xC,hK _X!WEAڿ/9/C8^`s@:^ _q?jhtAڿ/~xx^ ?jh!WE4~xC,hK _X!WEAڿ/9/C8^`s@:^ _q?jhtAڿ/~xx^ ?jh!WE4~xC, \qDd:>L6%U 2jAM.+$˦ȣailI^7ӝ9 ΃"|r}؛m!WE!AU4Wad٣W&'uUKQ4o%?b2ve#ݘӹ%}}1xo3^"L urkC4(*Wv {xymon-4.3.7/docs/critview-green.jpg0000664000175000017500000013572711070452713016626 0ustar henrikhenrikJFIFHHC  !"$"$C" e  !A1Q"2TUV3SWa#Bq $%457RbstCr6DEdu'cfw9 !1RbAQ3a"2qBCc ?r:BS)䤝Q&>l;0$W8AuU>EZu]=b _Nμr?GgG.NfMk=%8ZuK#(!1xhqĤg8 3 p:uRPN;Y Hu'R N0kj"u8%{wwJ[T,.rRPB:Ij4""g\׊GW_bIe#hIJ8DgV[ir3CRJ #21ZV}*LJ=9QQRѦZ֥4IEWXY-l"# xɌᲓc#iG3-5Vb#D6"q*D,Q~"Λl;1]ʪVUޚR  (8G$L^W?-NWEF\6rIkSeI#Kr*tV@r*"%8bmFm! ,yđ$C gl;0fyYTE,gZDA*MgQY"%P:2K^ ZzG^odQȼk&jf͗[lw P}gm<Un+ۻJf ކ RMbs`H^vzң] t,*I5`qKBLFK":3)T QJ1Ȼ3 :NդYVmdQfp$ ŶC gl;0fyC g3 6ٞvhc>l;0fyC g3 6ٞvhc>l;0fyC g3 6ٞvhc>l;0fyC g3 6ٞvhc>l;0fyC g3 6ٞvhc>l;0fyC.GY6#43R_y4{wO@C>5:fIB3$}\?$:2_Y!)Z&jk,z1C5fv8cuc?,YܹI9cp1ۚNR[nT"!iyd䑑qhT ZY'6ި`jR #3nfGm}h$ DD˛Y@̎b;~:캍vEJY8]D#Oz9awGb[m&bb Q.PjWvGmF7jGmHK<ѭ2Y)8If#-pN^m\/v-^֏;R9SŦQq8#!z\E۲5e 5Vn@ߨoua Qnz3]WRrDJjɥHAώ:q݆l7 ^{ces}ibMv[ã>ӛ8뗼~E ݇/x6F{ȓkvHKu$iBF,1vilR˧YUG`iGҤ yoh{ߨou`9%V![HeN!m3-Rk^YIj0J`MfRvf̈7,roh{ߨou`/)vDNDJu5)iRR2R\r23ďqVzNnq/x6F{^m\/v9Z8]Qbmd:ĺkNIp}Hq 7VIJ J2*OHroh{ߨou`3^/UWv}ʒԅZQMRJA<إIIƒ2˲FӪżnYF}g7Ӽq/x6F{^m\/v̭CK)2Xlm)JSYk3Qo3Q*|[22T!`hS|fWIcoh{ߨou`/c*jXŨKCoYoVfl2]lM$j0Bx21\-^9{4[p h\Z˵ѭ*[m=9^13ЦaCf9tјm-2N!m ,t4ߨoua Qr+R' O+5Zdyu5hzDJrCm&dHF9SpȍKV QkV*-^9{4[p +r${U~*Yrڄ .2Wi#K3IR&9,SP$0?爣oh{ߨou`/,thb1 v3L%%TR%F8eƪY#Ƨ EEeDiNefxuoh{ߨou`9C^ۧVzNnq/x6F{^m\/v9Z8]PjWvGoh{ߨou`9C^ۧVzNnq/x6F{^m\/v9Z8]PjWvGoh{ߨou`9C^ۧVzNnq/x6F{^m\/v9Z8]PjWvGoh{ߨou`9C^ۧVzNnq/x6F{^m\/v9Z8]PjWvGoh{ߨou`9C^ۧVzNnq/x6F{N6ʢտ4Z45NkV #2,OȺL^9Z8]PjWvF; x>Z1nUJDZXi,lʇ f^q-6$fxi. yFdr+ptԯIG7='Qpg33!O>85hZOA!U6W&Ioq5)j&xQg"k(z<2b+*uxtt}eJO_sVSVX!+`dJ7`|1ݎK-TES3˄Lc9}WJZF# EQȹjP"iX`$f[V8'#=E]gkAZiULd%9`k33-2=1-M樷{{DD\NfxZYS'LtYVהQ={13ӌJS"\gjN(#4Jyo/åJ),<[sRx`xo#d{n\ȌSʚq YBOqH_sfmcKyZT-13LLGh d+,;Zm)hJg#Egl ќ9TIi$d.#deC_Qe4Un ,LִW!q0Q㸔x]#*ܙ6S7bH ] 8%bn'wxbmBYɮo ަ_Gm9c]j,K&!q'I IN8RYI(VUelk9c^r$$wMҩuyܒq),q̔>d#5vkSo8ʺUW)\ڌՉ4~TioSyȍRYLLe%."_ffh4]6Qk]GbvFi4$LD3t)'d}v qZ)‰䔄kgR\V+5(՘̋?{ Z(j9cGqvI&A8ZY$K#4q}"]٦<9r-'Lzi3Tqqs×/_Kge˰Sa56e#&\7eٱ?Aa1|H}yNct#IJƇXhN{uiE\ML6XSgx%YԴekS[(U65";fД1)Q!lɥډNr"F2?VRrKhPܨRu8RIdG3G[#ѭftQEdik3_Dudn(pG(IY8yA)H]"?B&3=z&ITH4VN( R9_mxȝ+Vf(Uѻ,wCwO_y.b)Fxcr++3Voi͉W%YXd>IC%kḏ4(Х$]J,Kqo;lQȉA5Rj3^QBzVVS-e_l1tf$!Cu֒xn2fXhRj.ʥ%̷e];HbZڔ950e>8͇bX\:ۗȰ?lN֢YghC~2}Ibx)DDOT\47dQU* iN2zBxm k R 8j`޽t4M*Hi[KRk [n9:|e E hĭZ֒!T!UTwTd6R67  L)i< + ^slLGSӛLСR0+3TiՋ\)ȥ6PاLizTo`xl2-^nkd+ͧĆ9TMD /2SM#dn$U~ˮ庒e"F|0

.,I3ˆ/w`|+U-߳koW.-!Tjw&>ٚgC' VXģ/I5Rʨ3z̭g\S]7#S1:9F.%|bI>yƞDyklI`)YיDfRIIGӻ.e…T\ T#7*+cN$`#,Rdx [d[2ڵe2 e2q[s,Bp,HC4Y Ph*}Xvk%R$)Зא(rRҬXHr-~NN2{bT؋j @q%$Ied<4Jȼ$R.m:5Y-jjm*)pmmNlIi͓v`.FdU[zN(,Ԓtlț5xܢ$LE+*,z Ͳ윯M;ggkZJfa p,p!|IR. dg*}nm2ZfGҕJI X[t ԩʙeiU*B&e/rTHIasxXߕ=vNWǝ3국%y3`Y͆88tmQ]pFųV5,jqbÉV/2s6FS^DYGMlI%M\ȤKudDeHIeV%wEA]lkI^Ll3a>ԪYӗLSoD3o|nvK-zu*xr}.Wtc9S&I)|Spғ3Q%Y҃-5B.xﮇbv;i LUho/:M-Y./3,>(yNv9y-!-2 ѫpj#A+6d#׵B.[oJR߷SuDMR6*N-Rˁ7 =DHZgP AKJIFĺLD}J#_IuErՠOU HA?B^lSij$̱!m'FUv,nۗ6Ǥ\HZHӨV]GDn%l$$f{32r0ܕKҟl)RZS޵w"-echPvQcM¨4F]h=&K<1k4|LGʇ߮͝j~c3rQW~$؆Yy֢3VfDZƔ&D{ӉbXbex.4(OM!luTIBEqg*|:C0d>4YHk,qHAyၙi&2hȍO>܈Ҋ6bNЖd6xA~~ӯUs^&jbxO,_m#[Z4{΁V5#ӒM6Lt?VJWS=f5uOꔺfG^o&GI-~FhZٛ"ʬL*kJ@]բmKDKֺ)Nk 8GI= 3y%nH}3oeq^2DθiZķLXbĉ]<:kv jBle6қJՎe'v88W]>52D'ghT|GH5ʋHsKzmIiGo\e)IBNW̜<Y+RxOHSRRu]̷gARH̱ XutUr]vLEo!-n!\^3/nk9bNEF`u :AqI*jb{lw߼\T]\S9p?}.jS4ቺDME^I m*|fg-;? R$%e22+>uU5- B!u(I$$1^kK6\ŪQ)մYMx#$UF jMR|V(T鮩trc*481t[j5c33型MS3|eΠ>0$6 yIu y%!dJBJq -{'F?դ=o?:1/'&>w _Ē2XSC,RHf:[;5ѱ$3"NzÆf}jǨU:5U9g8RZmus?o "H\mc)\}͸~u"oOUxo};ҕۉZRv?q}"W־:. q)H*=8"~_mc)ion?ŻNiV_ǻiKJ?}l{Y,_.ԩjIe3!,:O4K,I+"2% ƸK|O"k Փ=9?0=)i[Qm>o4#C]Wtj5DO޶ЖHBI).!=)i/׻Җ_rm{v-ͯ{%ċ-Qy$x-$xħA\h44' GEJZg}oݏ:ZkͿv2/tys^aΖ{so݆{mu; 䓒tukڳN?JI- ,p&D67'-[vǡ;&IqB)B8GO H@_nvmKM~ٷ4[x6M2Ѷ$֒o&.I- 'Mٓ88RàX9i?:ZkͿvi׷;6؝IZz9Mu-5_t۝~9_nvmݯůET}GlӳoTDbEx!Mu8_nvmKM~ٷvo^r]l~5Mu8_nvmKM~ٷvo^r]l~5Mu8_nvmKM~ٷvo^r]l~5Mu8_nvmKM~ٷvo^r]l~5Mu8_nvmKM~ٷvo^r]l~5Mu8_nvmKM~ٷvo^O 0#Al) , )I7~Zkƿ`yΖ{so݇:ZkͿvxscZkƿ`yΖ{so݇:ZkͿvxscZkƿ`yΖ{so݇:ZkͿvxsc tf]&!;= SS.0k 8/.b"-@_nvmKM~ٷvo^}=3J[%DbI҃VId]j>~Zkƿ`yΖ{so݇:ZkͿvxscZkƿ`yΖ{so݇:ZkͿvxscZkƿ`yΖ{so݇:ZkͿvxscZkƿ`yΖ{so݇:ZkͿvxscZkƿ`yΖ{so݇:ZkͿvxscZkƿ`yΖ{so݇:ZkͿvxscZkƿ`ySiJ 4g|[.z7g'6S]5Fa-5_[~#.z7g~tş`cŗ[~r]l~5̎~tş`9{}pzoMu ]^\jDUd:jGR%Oh".3WAgsi>s]f,잿tk]tO=魳\vK+Cm)fH|~*UF|0>KMY.z7g&^ĆYq8X-PKBI1?:\obϰsY>j84u:ҙS`mg2J$b2GsY>s]f,"F!Lb󷟝.z7g~tş`N/DyΗ=uس?:\obϰeEYKaq8bf"fQbS,KNVq**3tH-62 IMM$|s],Η=uسETTb햓5՛>LTLTRd{Hq"2̍a+/I!yM?:\obϰsY>94ۢ4]l~5Mu29{}KY\YzoMu-5_E[e8̺#IĦA 2RdI8̈2xPtPO\u*rhmٌQ4e^)Q2*I▪ rL,eHL֚æ!)"w-J=\'oct77b]!l}-gNe&AXQ%H} b"T .mq!M>[JRq&FXXo9^55RJlITQM$)kЩ թiVDRnLj";j7MЃʤq9313^x*q[wlu~LzErT9jtQO'Yl,ԛi=zKB$-J"%CY2Hm>i,TMJ%8ei"n_hVd;z**2vZhbdwٌPxZLe⚒eH]6*{'}&B.:2uXc:fkQ*"K0vVd))a%it%I[݇t&$31:ێ]A/39Ȉ$fv>V[)ʎ8S8zpjA)DhՖ N{Mz;.S.6!Y2ajdFgׯN*KVU6d9PeL!$MZH6-]mV-2 MI&)V' q(Qk,ŊVDe@ˡ)H+v;)tmd*U1_$G(@82\T&Cf;dBJ2tѕgV V}X&l{1W*x ` =hV*yET6I]6,e~B/֍M4(PoÜ/MS4JI-JTZ2J,03 ~TDgF<۪3#T.mEθ0SK\VgDg[p4h\F>)4vbdl:F-d"IfGo˪ɇpUI_Cv8LGʎLt\))Ȳ($J%`E=#QQ)yw#2˔-m%Ru+es,Ozq"Zn F2Ш4Sմ0$e('MGfxr*%(d'G#\y4mHa6rk̹H8y%yI5!Qghlƨ&KƢ2gKTt ioV_ŴUW"2eFӸTRBDNi&`J"p Ƶ\:󆐴UMY?6RalN)R.kB?JIFeeТ +o[ƩfER3$!lj[N8QsYzi2DŽߎͫ;dj$t(cwwצSU2juũ4iRH|QÕ3gS[cLk,qݎbmҩQQ!x#&g4tl`F}ޙ ڙXimҚNjb҅;,!^C% ͛~=[d,SHM+Th(>c7ә)y)%u|6⏖ߣ*6ƥKfjoΎR%4ćY3KyN,bIN;!Bx6߾S\ʨF3JF'V!^fBUpțUU1r)඿ӗ*nm/T*#}W[CI$@ O$ !'< $@ 0= )Iх}W2f]N/)_G'i~zhW9?BU{Q0?w,ʢ:_dr#S+}UcçMnZt)_r1.1J5rx_Uc}5̪HGJyฮ#kdq =6s錵I|SF>O(.yxot'+q,'.QxY blUW;GUȉm_:1i+5W$#>)c2 QZw9C}:ul)C<SoS[S@??|2 QR>~nv>OO)}LŸT@7inMd)ED~nv>OO)}LŸT@7inMd)ED~nv>OO)}LŸT@7inMd)EDt-)}LŸ>S@??oM)C4St-)}LŸ>S@??u [S@??|2 QQ߮<SxS' [S??|2 QQ߮<ShS' ߮4SxS' [S@??|2 QR 7inMd)ED~nv>OO)}LŸTt-)}LŸ>S??߮4SxS' [S@??|2 QR s\HeJ(dDX`e\Z7۷D lA $ '0000"&`a1Hls$FK> OҘAǃS/: H5`02d<*9fc]#Gq?|cۓѷLkUi%)T[gDe'OhbVG\ڣiD41ˎH' 7ci?-6WB^Nie5QT4q֏;CTM"򾷕'*&óa,)y];~o*|][k?Uɭy}qkdtj BSʔSʓ/&d`eyQ[`67jd˓70V(#&i#PENY2n(ΌMdd꼲#ĕsEkޑe! 9^R,qy㼰#vN.~ݰLvYY1OFHN&I7ȷ6m:سV܋qr T*PY2$%D̏2AG< --:Jc#M#9*Y椭>[$B2*e2{@NTφE"5*Tf#"24,ǥ;u49ׅVH3(TmaH=[L5|X"3=eZpڪ>R<9 1֣ӽ`4[iObk u3QrFJ4g+;[ђfZ`8bDMı<8bG+ϻtCfǍ\u1N\7\fPk|"&4 ]Z ٔS=-J"ı,2ķ+x^:!,:f֯?nO[kRR%beb5w>Vh]Je*™ &ID,U;ASSͲT-$)[}:abng-MrR":ZwzM$,_xeQyXS%U~ ♫zLG y:ةqT4z eI#m$fdX=Df50ѥ2S/Hɳ^S1`s< ;A8/@_v/54bFc-&ŘҤ^*RJZ~8#hHV-X0ުLVT.C7Ig?BRdiIe}-÷beN}Œtmq+lX̲u(.ZUr:MH+JO\ v=#C*-j\z k-:6<<8/W,/ -KNP[)X,5(|x0R:efP",:yZ\|%'W˜ ZeJҡw5ZbJ$ ,V%X!`$&xVd˛dMQIZؙ>4-^m^S"݊Oc3E4ޜ>KY5qrtYY73fz 3߈0GUw[WTKij-֣)0$^JN&E?,H#IMO˿u=!8PRe f&XS $@XRh1tXM}&%qi+Jȗ$Fޕی>|;5摡:*©'45bDJRDIFO>. ^ * ujs /+D)DIVNudLռ84Ǔ+ex8iDY cGk_֜.z6.ӟjПj*x(tt .z4sKT6jQǨ7q5Qӆ*I3-yhnAhMZOH1qkXH,3wtgo؃7I)4eVI`:7 VU8yxTAn[S[*J5$ҝHw_Go+Hf:^-TbLD,j%*JSR ~|!XR*j4W2dFrD׆<#ݣNxudj-RJs2 (I,?:WnlmZU1ir)ꋱud[U.zv.k~}蔫KipVUi-X88 ^V;LKqie.Ф$m&EmRtwP~kmUTBRp[wbh5dV֋KT?T^r)Sdٸ+ye$E^~޷U oE[IJRh#Yo&RÂ8f[ Z/tgR& Zָ֩MU:fBVq*[H Fƻ.d-tjJݔ=BTI4%8Txp>Vtc5S*s`#5 q3RDr$)Oxoa"z.ʤ]VBF&>՛k2mX+V](=A~ V겣T6%|hMhJqYEAAn3- T-7ɭ+2x$):RF7 ue-Ca)sR㘸n,>~n9`*eFԖ {&7<hAa gߢ(,Qi5f&p8jt w.8NV2oY[fԸz"Rf2RMXV_k Uy&R֒JgfdXtc67EPw-ROlFf?#\OҘ[)?Jb[jC3y)'{*TfDfhIbiWOPG]"i&g;yL#e!RZ]dr=cY?cHtOthWn߃TV9hf 5y4*GR͋ΙD¿',ok-Y?0^nUt[.#j-Q]?*FENWEYvλUlV$/M>aWxb<-NY7Zɹ-}gk*o[*ޑprϢԬQsGZU/G7E]QMw9B/k?W>[UB7G-uGT@oT+?PsiY[{}Q;Ş -[ T_V*&g<¤ͥgm/T+?PmGxaRmBU6ʨ6ɼY0iYK T{}Q,ǘT[yPUAͥg oz*@-_V*BUTy7=q"-_V*BUTy7=q m/T+?PsiYoo<Ş o6ʨ9PUAM\yO@iYK T{}Q,ǘT[yPUAͥg oz*@-_V*BUTy7=q m/T+?PsiYoo<Ş iYK T{}Q,ǘT[yPUAͥg oz*| o6ʨ9PUAM\yHK T_V*&g<§ [yPUAͥg oz*d-yG͏]y%h[m +QI5s#<7>K T_V*&g<¤ͥgm/T+?PmGxaS -_V*BUTy7=q m/T+?PsiYoo<Ş mBU6ʨ6ɼY0bͥgm/T+?PmGxaRmBU6ʨ6ɼY0m-_V*BUTy7=q>K T_V*&g<¤ͥgm/T+?PmGxaR01mBU6ʨ6ɼY0iYK T{}Q,ǘT[yPUAͥg oz*@-_V*BUTy7=q1ͥgm/T+?PmGxaRmBU6ʨ6ɼY0iYK T{}Q,ǘT[yPUAͥg oz*@-_V*BUTy7=q m/T+?PsiYoo<Ş o6ʨ9PUAM\yLì[9PUAͥg oz*@3Ԋ .q&)VeӎDxtAclmOҘ[)?JbWe寯?h|FWjeq \#W'J5rx{/6L#aѡjI\FOװCW'6Rxl#en# #5# #mfx^KшHtb;.[nAsC1n 1$| Bz@ '@'@$ !H8$@'A !H@ O````D L:ìCi?JbWel)_GvwsW\F_w:45r\#i+:jFOu׿a?fa?txb<2 )b1nGzEmԱ1~Pw9B-& dO!=bOX=b @ $ I  bx0$ I< H"IXum'LTL-1S2ןnj#S+K52FWm%q!=`  Ox OPB$ HA $ '0000"&`a1S2R~N#_^;ѹL#m/;\FOj{ T5rxl#e':Vk03_0:Via1o Gk]F#"X?(d;cn @2JO'@'#3""33DCo]ȭRL(ngF"d .bY\|l719wգMIߖ&JRU.m#v4=؅*ѦC5Jj6kI4 :sGFXN!5Fp)=b}Nr_ZY6SQӇN+Y$K{Gh3`9L!jK&ITDq gƊmˎ̝s^3QHU1KB#k}Я*1, .dF"A]b SJuސ$"6#7A(М$0〨jlY~[,0GꈛqČ_TH;Gkb7.thɿ1-:%)J-ifQGZIթqӖQRĘF\K:yEQ"B$@$ì :@6*u_OҘ|kz75~_m\GzãCW+6\#ajOlGZ {FFkFGJ,7#-mb+ޑw\Kރ z c-bII$ KJ NzdӡKFBXԵn"ijOAS-U/2[4j.:Q)`HpzV]6ɴr&Q){x R?".55ӹE0}~:NS񲫠> USTRhK?꣱hqU8'҇B:zcR+VYYW#!S;V3흸Sޟ&N6*O&R˕;O4xcT[rlXt Ei8fʜq33.:׃U15M8[-8E$-GВe t\hET2T^DD73Qg=hVTNdJHn;lzHntQ)V_ƥ"k"B[6҇NrsIZV`EtKFůOQ9!ܲ]#yIW SFZ4BSuSuʭB1}xժl-J#MOFF\I\JL`8$JjTgT.6OJ#/,Mͥp=WUkgƩ=QG~  [4&uyZUWT j䠑!ImE*{>K)*~m\8ZIA5bJeD6 fi&nм,We3 K\W-~Y%S=aǣ iVDթӭ}2j)t65,J2,U8άgޜ Q\s-KN)eע6-#=bgҥpǣ_[͊KRROmKLW3&4$8$̺w%wJIWo^ٍHL8.SS5{9 Ǡ-`t]4sLjSkKyQ`Nr"ȟ#ϫӬ IŶ P&@S℣:#Ȣ١DJ.%C'H}ƛU[6[]>tk^ɷҕYIĺ j[hcN{ڝYeIYrwIvCfEdYujIexxäZ&\\Uh!5+ UEGr%8ӫE&gӹpA^Fub"KM- 2Cdx+N8rҥNfh)+ŏUKm4Tx'VeDEX}}"Unэ*smQ3u,dɑ%`g|KphrQVYs4xK{Q&̣<#32Ic!a1.Pzh9(Y%W߫ Ǝ2ԦLϤ@m'$$k'QoB",K#-G,!MTa8i⥭G$ƅ {ZM-c$cRfTf?Tx i6M '%F VT"96rC=&Je*^gLf^,:w*KUȼ : E9YoQN%:[qoîcof5R\T2L:z] I3IboJ:uTjG#ĘD5oQ"#"2Y$ '0000"&`a1S2R~N#_^;ѹL#m/;\FOj{ T5rxl#e':Vk03_0:Via1o Gk]F#"X?(d;cn @2JO'@'z O>Y*U:!mKv$ ̌f#23"qkq8┵ԥ&f|LGPB$ HA $ '0000"&`a1S2R~N#_^;ѹL#m/;\FOj{ T5rxl#e':Vk03_0:Via1o Gk]F#"X?(d;cn @2JO'n:,kҧ;VTCZ1,Ɯ|mʼnFYFݵ{6ܼrӞ!Υl[iļce*=y(fţ_t3h<y>2pROļb!U4,;>KSUȥfIJ+^$g%h64k^GUf5Wz3T8Ef,mL]R@̀G%4 ֒2%.nn*BtJIj~^)jaO\u|R`jW8 S\uKd5Zq6' ~6;f]tã5ZmVcJ`fJİ"PT ]T)RV:f[+R ̉D$e}C^$OXOXqf΅O)붴%)p#EIyStkm(F;Xa|;N EѮTںmٔί:lDGT`ƴ bٹ(W`M=*!J"#CsxYvvE;1Fl98ˎĖjR<̈f<Mrƣ&ݫLYcK #2Y]~ZWT'¶kRapڊ0][ob(/qcQ`i@e=AuZ.552F$J,R#,H2@'=B$ I 6F UVd5Lt3m%cD8 nεn8R`ISd5G⨋Ed2/m쫑n8ٺK|H#"5eX\ 6{fve"߫Tc2x:Xn:$It$@'A !H_m9WUn$̼l%2kUIlh<Ŕҕ7]F"f#&uNcp͔S+%$fboYs_kRTJn*V=Z)dBkR1NcN', 3*--\uW -bd§$yqm{TvP:hN:̺IF2/01MT*sTnʜqF]8%$fb_̪.ڼaN*$ɥM iFʝ'^*I҄3Df0$@$ì :@6*u_OҘ|kz75~_m\GzãCW+6\#ajOlGZ {FFkFGJ,7#-mb+ޑw\Kރ z c-bII F/LVsQct FHCZՁpJR"3=*@M'UkKJ:5HݩKmu$|歧QRQBYJHX2]%^x5HE׌д#:F}jAH,L-ㅀcs,թUGISʧ=--fV#t=ݎ"VVxAۗO&ǎTPmxC 'Z̰t 2hB58kVTII".6  < OP=BIPBo"Tcd[oţ&B4S4EJSlRMqaf="yLri+Bd44ħH*pYHҒ­zFWh&ҩ)jfI,yԥѫPk d7&:$KQ)'=[YXj '"N#SqiBTdX $xa5l8'.ODžZ>ڶnt3M8 U ԭFꐳQi%V'źf1Vmr(PvJmBuNRJ<2,1Q`gP 3J1"< }F6c0vLKL kqj<tn\&ZVy^Өͪї4L.˰i^v߃Tʬ2v5mdxbHY[䦦 **5&حHtЖu$)&٫Zջ+Ɉ]v[huXd3$'*Ib$՛/{cb=FUЫz"*zIʣ1~S!J͐FLM&R1yG˖]j/!mj/jɛe&c.9g86òb2]<"irV+Y fIEXFI#2Bl9p]q--$hZ ~RV$ˤ&G+Sشe] n/§>l2ԡ :djʥ#cY4iҋJP]AgΑN]K[M:ۮ+"d0j̋Xj'jRs}elk#z.qI#LzzE9%S%s2Sy2ze|KZ4. IH#&h[jpϧ*IMpݖ_;j&墱=%J"9ߑLS- XJLҜ1Q-Ȁ.tَ&1pZ/b]m":Ց.Ϊ))]Dkp=$¿Tl.G.}JcVEr+vB;5y)`K^&j"ޜ0F<$N `pffiQn',T[w8rͣQVm*] sY̜pĺF| |O(Xظ$izn۩R>OVMNT)ڑSqI!4dx<ٌ#,%xV%6#S!Sٓy- ITK}}elpʍRi[EʏIVn7_EB Tv^GFY,ޢvRopQڪ*!kIej#d3^'VQc8Kj԰gCMN D YHr:̰Ye,Hq}NѹQ٬pJqCs ZL8K[W,Avӹ)̴uҝY;-$u99dȮV$xhuʶmRTmʅAJu҅l-+6O\0 9 TN8L4o}aFI5ώR3"ǣ!rږ7i!$< A+/LG:S*ch]BTã*Zs6B5QYPAQ&A(Q<C[JKmT.IEl+p9CV`#H=W'))$mZ~t7i(+X~ezU2c܂;K%HIRdR[MRԹr6qf)N2#V$|0`r7VoU^˴a<:Xt+u;5Rc%ǚ%Z=e!of^Q4ac-EuN[j6SK"4h2RO~$ˤc< ɋO?GR|3v j@\MjY%5(",L̈\S״s\pᢓ]I!oՕ In˻>Dx]}ot:WFwS8܃-d}$C7mRhͼf> f\cR0_ԑm$ʤo}Pmk_e.8k7e1%ffY1NC5G/WtxͩU:|i KZSL-+'7a盫7^Rby6"ǕoѤ;2@I8#mң̜L<~ I7eRm3PBT:'m) J,lp9+7^Û7^zC5ST45.\ͳDYADS(̈թI /njIjpjr;DR2ٙyDwk՛/{bBXKoCyS"yd88g8woN&yOp\ʩ'ۚ=5AG_- uJJ+2x*ɣ/E 7Vo7VoXS7^Û7^Ŭ՛/{a՛/{bT??ksufsufص`U9}elc4gN$gp:=a&-?@[wM+ٶ/*dJL6mFqGRؚMņԜIdnļI'}elk= )Rh̋ ;~S~LZ~:) L KPN=)2qn)Hn+J3߆#-W)Q~'HdiW(uF`F=I=gxYW<1bkh<\9#l!xyl"tRh)לWB3QDFs!jڛԶW=R Я%JGI3-+m8*.ZdH'.UNBjN*QAB[[&%:2CR/(~(WQĻ*[Z6 PgT J'0TI($R~̊nIdGagƦebD{ +-.vU?,%E'l%Qq. Na$Q`Ip;R U׫Tשp[U%e&x6f\#=ͻl!/ǭ<'$({N{o'I_YZ&S(!g>rRk!j3Լ(fIOBRDE:ԯ&9 0= )_E*u_=yލ_jeqi|FWް5rx#W'Zkdq)->}Z}(@i,X& HV5c6[JqQ0ӎ;?.\1N/hqޞ. .Tn%"T`sv={LHOX'.aARa:TLHaTefE Q'xtYiox<(z>->}Z}(@sâϴKO{XsâϴKO{Eh}F<:,D7xtYiox<sâϴKO{StlMDYJLTyG$+"ç#.pH'~:yQ=ihH5*=RޱTM'bX9O` N]SpWiT -(:kQE}>.uciXsâϴKO{ CxtYiol9g%Q򋀀sâϴKO{Eh}F<:,D7yE sâϴKO{<:,D7xtYiox<sâϴKO{a>->(8w}Z}<:,D7PWg%QEh}F!=^Eh}FåH/HVJ)&M lbJ,q~b]%.pH'~+oj^ .LF}>4jeemF$fg`[̋$=b5@ b2jat8i&JI'TXw(z>-><:,D7QpxtYiox+,E=]TDDDG$2w`.pH'~8gžAB^n&*HFx"%`Xo-T Fe /KbΥUTJU=1~B[Ɠ)(Ib|1.&X=8cPLGx54_D84U2ݝ;rq)}X2Ƌ抶ܙ&Y}YnYn%8/31:OѕBWx!@#Tx;|Bql^UKKGtT^{q|0okbAB4Ji-"Cث(xX㬢82L:ìCi?JbWel)_GvwsW\F_w:45r\#i+:jFOu׿a?fa?txb<2 )b1nGzEmԱ1~Pw9B-& dO!=bOX=b @ $ I  bx0$ I< H"IXum'LTL-1S2ןnj#S+K52FWm%q!=`  Ox OPB$ HA $ '0000"&`a1S2R~**FYsESR<E\:xqG_HGwډ|FW *Q@Ȏ TQѦ\FO~G./Jg'MT􋃑>5R}q]ʔ tW*k17#bT>F.7>eS1qm鋳@l6Q eeS>guGm鋷@m;dvʤ۳ouGm鋷@m;dvʤ۳ouGm鋷@m;dvʤ۳ouGm鋷@m;dvʤ۳ouGm鋷@m;dvʤ۳ouGm鋷@m;dvʤBzųgѷ.ouGvɶHgѷ.ouGvɶHgѷ.ouGvɶO@6Q 6}zbN6ݲ6Q 6}zbN6ݲ6Q 6}zbN6ݲ6Q 6}zbN6ݲ nϣoL]gѷ.m* nϣoL]gѷ.m*۳ouGm鋷@m;dvʤ۳ouGm鋷@m;dvʧ [v}zb>1vT lnT@}zb>1vT lnT[v}zb>1vT lnT8 fϣoL]gѷ.m* nϣoL]gѷ.m*mm鋷@l6Q 6meS ŷgѷ.ouGvɶHgѷ.ouGvɶL iGѷ.ouGvɶOmm鋷@l6Q 6meRmm鋷@l6Q 6meR01mm鋷@l6Q 6meRmm鋷@l6Q 6meRmm鋷@l6Q 6meRmm鋷@l6Q 6meS->1vT Fޘ{?ӶMl@->1vT Fޘ{?ӶMl@->1vT Fޘ{?ӶMl@->1vT Fޘ{?ӶMl@->1vT Fޘ{?ӶMl@->1vT Fޘ{?ӶMl@->1vT Fޘ{?ӶMlfbٳouGm鋷@m;dvʤ:T8T%]s͋6'C̥/%)d8$|^<88N/Mݵ )$Hqr*SSL-Knbӊ$DKY% 4וi֊]`?%Ƕzwe*Q!*q,l))I-"Bi4zVg{jDmtݹ6c7C83- vAx hI̋z#p\qɊ&\d):(MQ<Β4 ,έ Yi.Ri5JR]y(ȥ,:F$̉GaXnI֗&ִwVF~&H}6ɫfI=7mF[ޖXa[#Q㳵D\#am"wVIM'A(Q#kMb""c0k&Q+_ȑ3jzCgp[q<(~$xQ)2Qy*RSluM8)f^3o<&Vxbx A.FkRFfMH{FWcf@QgG#KB.>U&kuftN$"\Нt%)KdZ"2Y.='@c4F+qK-ƿ*r*GƏ_JW$iAv9[UG)4A$%oؼٔ&|xDcP;gǎ€RN?$3P;gǏ0@>xX Bq4j4XzBs~kHP;oǎfG[kHP;oLJb u b uR!@; oR!@5W(7d3#5W(7JxduJx++XzBs~kHP;oǎfG\>kHP;oLjR!@; oR!@5W(7d3#5W(7JxduJx|)^w\ߏ̎)^w\ߏ++XzBs~kHP;oǎǀfG[kHP;oLJb u b u5W(7d3#5W(7|)^w\ߏ̎)^w\ߏ++>kHP;oǎfG\>kHP;oLjR!@; oR!@5W(7d3#5W(7JxduJx|)^w\ߏ̎)^w\ߏ++XzBs~( p1GM뱈32imZ>"s2JoUQJo:h<J#2Ny2K BmLeRbdf!a ʣw =;)X=2J]g$jĈ $9,hq%F7i.)gG"JBE\h Z\9Xubf}+Z!f)Tօ)Jl%$goR(Ϥynˈ#яqۦ4ZB>DQ/AuSZ&c>˜s^VɭKZK=ZԟvE'Pю̃bvd'. չf-bR(#"!+4yMDf`BnVEQў:[S$І%vUi)%#ǖ(~hfACF;2 хKJXnMJ4ćCj6lZi&F[bvd?4c lPю̃bvd#CF;2 1ِ`R]6(~hfACF;2 X 6(~hfAKtء0)`.?4c ء,bvd?4c lPю̃bvdM1ِlPю̃CF;2 1ِ`R]6(~hfACF;2 X 6(~hfAKtء0)`.?4c ء,bvd?4c lPю̃bvdM1ِlPю̃CF;2 1ِ`R]6(~hfACF;2 X 6(~hfAKtء0)`.?4c ء,bvd?4c lPю̃bvdM1ِlPю̃CF;2 1ِ`R]6(~hfACF;2 X 6(~hfAKtء0)`.?4c ء,bvd?4c lPю̃bvdM1ِlPю̃CF;2 1ِ`R]6(~hfACF;2 X 6(~hfAKtء0)`.?4c ء,bvd?4c lPю̃bvdM1ِlPю̃CF;2 1ِ`R]6(~hfACF;2 X 6(~hfAhv[Nݏ%I>#ԞPXjA3.jIf/àrL,KҪRݐ2S'R TyP|ED7?4c!nbg4ݱMLRNѪ]Er$m8n9-kt2;3"#=%Cء,:o"MTڌRVN/xymon-4.3.7/docs/critview-detail-ackform.jpg0000664000175000017500000011053111070452713020372 0ustar henrikhenrikJFIFHHC  !"$"$C'" e  !1VW"AT2QU#467au$35BFSq%DEv&'RbCr 8Gcds;Q!"Ra1ASq3B24$br ? 7yMLI{KES02 ?{0𷉜DcEeDA&is6ϱʶzq]nMNAci1w*{w~=V8IͻU1Ѭ(S)E)C~Ш6lH0xEH ($aQxn?f۸ͥ_֕m*s&jUV뒙}9)$qbC8dB] z=#QR4QEEP윶9.#Y 隋ې/RLfRB9nr"?.ex6.D"# 9KYMLYAx\wS[j%w@њ2]L(:3b8G$pgȥA" o/}Z ZXeY 6R,t69Q3CQ3 F͵d0t2j1)ɹ@t'4(f)w`ܜ]lDKos'v %)cD1W]ق-YOϦamnDD@d9|SyBRO1&m(iˎ4ݑjc!9AE"b%? h,?+Jnmffw@BH ?|)H&[~(}((ua)l丣ZbErJD1j/ cl.KB@v"f 'r"<4kFY$v2|dlA_:e6!12'@QE Oj|QE@Q@Pԅ@ԅQEQ^ fgC~U'{ALU m2Pɻ?->JH9o ]gY"E)5 GPd;s 4Sn$nP.n !٨ʙj03ݗ//r(yrWQT0O0ZDr6yn@TPl2Jٲ4VM-& !9)tEz.ø{ F :;z݉꜍WQQ88Gp0Q>1.mxfo6y?|oͼӥƌuGǍ3EHeC2|^Baë Yͼdm&1@6CəLMaYtt6rVdד.=pҁV(&~Rn83 Qb ۏw)Lm1Dϓx9h?Q[e'!!06KfF("sڳ˒Io[_kOJqn8 D0 b9@yڊ}c18}fg)$Ͷݏ~9A9jLyG}g1ݵ {Z%lJI\l@+F&PS `r.j 3=lKVm

ػ&XĴVPH9yp>MPwM~t6"[dbkΙnԁ0(~S(s, n.e:2 B l@@6Gd;2񁿓02tWf,;:ϭqNm6<Y[DSN߿xCTY~qebQQԔ@,éA6Bo+Is ITփyIXHQ*իtJP6B0ӟ԰C+qIȷ)vV KHSyNn!ݫ]%HLC245 [l"= 9n/5_U+c废oe^*N RV㶣s>]۹Ø# $:M0xokx3qjj')Ŝ9 o p4uUn+eoƸTZ_Pb\]=pfLX&daSJJ&P(wrYC.:BQDas23cD)_O?ϟj=^j8);%+Z[fUk_=u\홢#^ZS]j"zfj?ƨ?ƨ(EO@EQ@EEQE@Q@PQ@QE?%EOQ@EET*|TEHEPQ@9av$C'~,% B*tz">W+XowNtDX4NFf&@)D>L!wb.9PGhuʹ0"}py#xW .5[d*( %ۋXMG1Cqr")JRuxa+%oy][nmmdXJ mf "!3~Y9s g:@ˠp>.EHau!@?qZڎSdcEkiʠDNC%)NS2D3/.\@_ZfdcTAFgDp>1iμ@P\C(r#'T^DTMY.Zڗ [&BIki)Yw"Z1ș s 豈T 萲W,Z-Vx^̎Jc&:10(|W~(_p!54ًw5+RR s@0VN= ~aݻKOb, duJ )O!یQ16Vk'7WSvFU(VJ&`FG0f7ɾE%; Ҏg){0qN%@Od՘Lar`m /pF..Q cQ }xw#Eǩ+Fv}JMt!us9G~Kښ(j|S"( *((&bb3l)ZtdT7\;1eqIIvR0[镪*TۮM _ 5j0ל(=a`t=Q#tٝb4L]d|vJUɣWcȄ[s"sM H"@(O<3r ^滭^m!q9kP4&&r :ĺn893Ff-\CdVPY-G.d)9w2`]<.Y6< ^x[Cv:znH&B/"f021~Au鿭7K,Ӄ^ ꇃh]\jn|Ywqvf&(&Cy&jM@.)&l$6P%8y%J_!?6Zۇ۪NrNxY;v>)D!x| ؄yh#ѹ.*Ry_XS|_@w|K3:mN\Ls~NzGRD@>mlW^߸|\Ų+gDTQ .e.>.]U9c~hHTQe!0(5VHE2() `ypP XˆY]D".eXD \ /C[.״ rL^0pդ1R7"̒uv ޒs7VdC& D# >I;lld- :1_9`9;0 ?w|:(w[ ECDB8lbL Dutg"~0mcaLFȷT;$ut")WES˛èe"3.ˢ9pBJ"9l5 h&]SK6Kuta_? dsE R ]@~Y&;~qN|Am6|j״˖~\|>| j"׳-K/ڭ̀@P!߻hl/,k,1$I";?}?ƨ&Yd =}x'@ZTrk+.01 z?Ҋ\ce =}z?(ɮx|8FW/!q_}-hɮx|8F_/!q_}-( \ceq =}z?֊\ceq =}z?֊\ce =}z?䨥ɮx|8F_/\acKP&X\BG@!q?/>z\cd =}x>}\ceq =}8>}&X\BG@8>}&X\BG@8>}&X\BG@!q_}-*irk+.01 >}Z\cd =}x>})rk+.01 \acK@R<>W#+\acG/.Ms~2>}BG@ZK\_p!q_}BG@Z 5z?q =}mQK\_p!q_}BG@ZQK\_p!q_}BG@Z O&X\BG@8>}&X\BG@!q_}-( \ce =} >}ɮx|8FO/!q_}-hɮx|8FW/\acKJ)rk+.01 z?֊\ceq =}x>})rk+.01 \acKZ)rk+.01 \acKAɮx|8FO/!q_}-hɮx|8FW/!q_}-hɮx|8FW/!q_}-hɮx|8FW/!q_}-hɮx|8FW/\acKJ)rk+.01 z?֊\ceq =}x>}ɮx|8FW/\acKo&X\BG@ƲKT s@OyG 5 !EE[1=u~oe\WWVU)d1L_U*=V8݉ʹ+U[JuUn+bQsUW/箭V3nJVV%jZ#ZW]j"zfhWZȧY9O*O*5S権PEPQ@Q@QEPPEPQ@OQSTPQ@Q@>z=#QR4QEEPQ@54T-EHr@Q@PT@EE@QE  TPQ@S5QEPP5!P5!@EQ@QEQE@Q@PQ@SP54QEEPQ@QEQEEPMEM|?%EQE--&F!1ZLRQLawԈ Nִ1E+9e+$T*MG޸aн&?P~gr@yk\`r5'Ot>lawomUlɁ -q0F~s ~}ظ q:Y#{mu?Yux޿-xFQ?^mո 7#L;Pz+spo(Yo5m'8W(.vuPϳL= 1_>)2n/M;m:zZuߧ fO dr;:Vkszߋ{;1]構{;1]橸K#iYF3NΕރWy{;1]0DϘ,L*8*&`1S)@sg4h$=Lfɘ&2b""c#>bV?iŽG6w\bSGѡӳ> fgY!8K-lw8~qj> f%hoc4Y!8K-lw8~qj> f%hoc4Y!8K-lw8~qj> f%hoc4Y!8K-lw8~qj> fG d};:Vksz{;1]棋{;1]TqM" a܁PLk=0VaKM3iSD SBU )oA[qopgu+opgu+7 d};:47vu5ރWy7~qj> fX9UL+u)@R$"g•ރWyWy~47vtpGѡӳf7.8~qhWy~47vtpGѡӳf7.8~qhWy~47vtpGѡӳf7.8~qjx~qj> fO d};:Vkszߋ{;1]0FWyn47vu!r47vt%6w\bO6w\bSpGѡӳ> f+59 sŽG_6w\bSpGѡӳ,_C{gJaoA\qopgu+ŽT%hoc4,C{gJaoA\opgu+qopgu+? d};:8K#iҳXCp<[?8q4qopgu+7 d};:8K#iҳXCp<[?8q5[?8q5QYF3NΣ> f+59 sŽG6w\b^bU( t-@- % JQ91W d};: YJKu1?E6w\b[9(q5MYF3NΧ9~ fgY!8K-lw8~qj> f%hoc4Y!8K-lw-lw,C{gG d};:Vksz{;1]{;1]橸K#iԅ#iҳXCp[?8q4qopgu+? d};:DFb,rR16B f᭨!F*"48N~-lw-lw◐"9hVQ@3]@6HHK#iҳXCp<[?8q5[?8q5OYF3NΎ47vt%6w\bO6w\bSpGѡӳ> f+59 qŽG6w\bSGѡӳ> f+59 sŽG6w\bSpGѡӳ,C{gJaoA[opgu+Ž\R2Wox? nW- "lL D#ʹ8K#iҳXCp[?8q4qopgu+i+{WdGNotY,wyf Ew|JaoAXqopgu+ŽT%hoc4,C{gJaoA\qopgu+ŽT%hoc4,C{gJaoA\qopgu+opgu+7 d};:8K#iҳXCp<[?8q5[?8q5OYF3NΎ47vt%6w\bO6w\bSpGѡӳ,C{gJaoA\qopgu+qopgu+G d};:47vt%6w\b|_aђpD2#K9ʺJJKDt5VGѡӳ3gmY$1$kCٔj]^$Őr@rWOsQn?G=J_?}1}JXcX UV뒭U[Jؔc;pm~k8VběaY*1pа Ds6ݝ.ee0fVfVI5hQ̢:LQ9/~aU}(酛7lŌFo0wcyf; 6HkkjᥗpjFPNRPP @d>Hsy9d>C Vn>,nz*B SHc RkHΒK0ڱq7#ڎsSV|C7W"s/xxF-:hFZr~ZEv8\SWP lV|)R.R T 65kQ~&v9GjHdDH2]YWxMhSl*MEC8 4DP#;}UEmK4ds9q1RbQ*PDG1ը7U M3Un2^`J,Y;˦?UC&}`"Gvbbচm"YXtdD٘qv%ǹݶ0٨Ld\xC~|7˸MkU4DA߄P|E~ YE%?vC~j 53J sY[^gF2J:*($.y~0(V'kr1b* eT6F wgżѓ #AXdcKa٨A쀄/Q _R@L>j5l (`krs)S^?~c5sߍuMx+!5Gї)bQEEPTTX&n&ޚωM0.s>F!M aɝ4ͼү㔎îU]6¹eDt&>G1DsZY_HS43`d6S3d`GP#+9lW_i]%UE$SbUs̡ "D7E|?Q^j= ݜ]ē~J| Aa۳KL4M$ݯkծttvӫ6L Dӿpf;R`q ؔ]&\ :Se P0@3 0J 돵)I.BBNr00=ԸbWTy`n,-nr a9"9L\,m>ԑp. Yۂ̗17B;5핺#;pټUC$ l2DCj[j)5\Q94(d A fr|-<.^\py&1SzN<UDP.[1a5!a'|YbZHpbJj0q&j5ϡzMo[Kh%RAV R@:Bweɘ7bJ? ўAȕ"Nr9:D)j@ & "QèLVͣdC$ZVn8U;rR9쌘$P8&0r1l.Z\BD925.(& !*DwG؎鵵r6l-:٢b"CA^f "'du`[2BFF}B2\.t$1 '#ɖaͧ ؙiF*$bB&r[!Sd 0 @|l[)HHI.TTlMZPrD <7WESw$o63V@QSPP6E1ⅷ=+#CeQ%->JMÐ_ qacN?2і[VR5@QE (*j*h䨩*(PƖʅ[n5Rw=9h9hf'cq9VMyre7(g$^.c9bn?G=J_&Ռ4>jOصtI( ׵kK(\W^ɷ(Gc-WJ.2\+_M*U䮝 )g|EUy~@tWf.j=taxBbt~6__SW2tW:brWz^6b]*}Snbr+pJ`zbft7b/[TtާgX5ykO=ZN<ަ? J낝GG<1+ vT?ƨ 7޲k(Ӊm1.D..%5篟G<1+*vUX3{j^(NŠA /$tOHv8db2`"2wQpv{e&Ô|At DQe0& yCggDno8qtseGG<1+*vUebLnd-ƧAdbItdp]eDb\$ָfdTQ:MQ0LdL (&۵BblۇuVQnU{kNv7xbW\Tz%uNʱפ)mxԜ$\6XITAdF:DHDC1s Ʃ휤*8S1q42޺9]qS룞;*ZY68<ޢ\o]ĮQ J늝-hc/1 J늝OG<1+*vT 7_G<1+*vUo]ĮR֊l68<ޢ\o]ĮTtseKJl68<ޢ\o]ĮTqtseKo 7WG<1+*vUa)(a.9MU >3T>J]a\@ܹ/JZeBJ7?[1-gef+)Űr@r]#1E)~itRF2Oa~LpR=V85X%;BUGUV뒺%5\\rzؕc8%knJVٕ-IR0#CDxDjKfdJzIq@XYHĦeV,PK!KF8+Sݖ5,_4QMeL=dRd4S$cj.@!2]rjFCUuò*)(E5xsߕ]4EeUE<֯-r)竖fSjjDT*|QE^NVCkm{N;kYѬtF?/挿)MEU((( (䨠3pGHDT*R*5%0nSV8rH鰛]&b[%!v ]"`%[وxKSD)t=A&Eeu)qP Bُf_mAcuqx(@;YTQm@@[&쭴&bNtuzQ-9oòk.i9 Qc*nP:[1T6[Q) _y\M~HN5jESڙnYDԉK 9GYqŁT޶r蕂fLT"'9AGHDNfTIq;i ὚p=tFIJ0" ,QCH1236 .U&^Ɋy&ʳ* Q΢}f .>VM3vĹ۾ gdTseؤB4"֡X (q<Yihʤ2"\ΈCfSP! IP+z6tm7s/Ih  LCo$|LѳŬ G4t,ޮ;s}g!SH a٨;PȂ9[7HN>5v[rA!$DubLĪ r ڗo,2pɄkdeTk.` lҮvG#zᒲ)(xT VfЩ9ELP2 Yl;c+rF2JdL1R XԐNQ C3n8j;)şoFO%\.̒Y%ȁLH y'.@*.{Q$s6DT1)'B 2@ ud7|m]f&mVIxU#Dt %d52 Wjڅ.;qc߷l` Y5LC$;B>h2Ho#=j10DfΝDf9/@&TS6lG@R#n7'y*220LT6D0808P̳|0UG3:C`95Ia ـ@%' ic#9jD_4U & 1w~ ^VE͇-d">jP@Q@PQ@EE55/[u ^* [)iU%;"e Oj|lĊ(( ( "( (2E7PnZ&!,$9 ) 9LCaf\"r12$tMɀfDɘeylԏP;VYT(*g93SL J`ߐVRhIXxU X$8 )DL!0eҷQHi:RUC$ )gbHȱ-By5$M"$rz <Q5c@vrQM$ђtOoE)L. nPe.(h\=]A2 d)yj)s06uy!hbjajRS1I0Hi6b se٭ѵNN1fx ,vnǨMۙWz)@*_dCfȹt~.D,9̭kxb/TQ)$E19H1](BI.^19'DnUL)M HL\qbV˺ؤ4iY;2(`(Gm !r Օ`uukzF:t!Yw<{xqY"*I"VWM}F(TKp |af>ÕYpl L‹S&u,{@n(3p±7Ag(@09Vaokkӧ-YKgrU`Š(Q@QEQESQS@%EOQ@ʅ[n4T/#s̡EE[1=tRF 1E+&xde\sDc~+Gv^`ּ6IV}JvS0VfT2cqJܟ 1^ʋf@Cu[s.o?^7NVcpS~櫗 {rw\jPU{ȭH]\JKRPU|e-\h*͒V)K:YR˵U}pM#LHY\y{"7z}sָ3~PN/̍Q&+)kJB}vbWRcnFZT;_1+ ~]HOSnBS+vbWR<3~/΅S`krs)S^?~c4ķ``m/ Z$A35L;8*"rJRԆIB Ѻ #d"yC; F{ynw(,hWLĮ'ߨ阕ԄXۥX);_1+ 8kf%u!>M_ ZQL]HOQ;_1+ m]b֊epJB}]HOSnB%E2kf%u!>GLĮ'ߩKu@|3~阕Ԅ6~t.kSOLĮ'ߪxkf%u!>M_ ZT2xkf%u!>S;_1+ m]bҊeJB}vbWRۥкŭ阕Ԅ3~/΅-hWLĮ'ߨ阕Ԅ6~t.kSL]HOTJB}t:XZdJB},;_1+ m]bҀWLĮ'ߪxkf%u!>M_ ZO\3~vbWRۥкťL]HOQ;_1+ m]bҊeJB}vbWRۥкŨL]HOQ;_1+ m]b֢|3~阕Ԅ6~t.qWUGZKJp\,Oʾl6e(rUWP̘SkH\f >[ϺvbWR^^n ]D[SWLĮ'ߨ阕ԄXۥX|3~vbWRۥкťL]HOTJB}t:XWLĮ'ߨ JB}t:X\3~vbWRۥкŭ阕ԄOLĮ'ߩKuMxLYb$@@&9 @߼ @b 2eo QQtUvD 1&6" k%' ;ѯd奜T&`%&+@rAn^1Jk5mekk |1$ߋG)S^e-xbmjEv2(+Jl (() G#Uo ԶBArCL9GA}cR xC}Fܽ(cϰ5/CJbnݯS#)7l잂Tw/Pղa"g4Ƥq]mݲrMd]s(U B@%*M"B!҆< O5zPǟa)QVY}Z}2A;PYƳc,$M%RD )3LQ7&Wv{־s+srEA+iCpbDT%#`.dUn^1Jh҆< OaktqCw{_M=z%n0aKJιK;q0GȤs+)@$0kɖyX^-nK"W.&01nS )&02xC}Fܽ(cϰ_{wNXBsF+kLGx4MXT?0Oj>5zPǟa)Q6޽N(eVYk,:0c1#V$m౽~5S݂DD+WLĮ'ߨ阕Ԅ_ۥэ-hWLĮ'ߨ阕Ԅ6~t.kE2kf%u!>S;_1+ m]bҊeJB}vbWRۥкŭM2xkf%u!>S;_1+ m]b䨦_LĮ'ߪ8kf%u!>M_ Z*Uo;_1+ 갔n*لot I}ػ,yT2f &-wo6Vn"2Ri`墀墺Fcq9R(?7/rxd>bzqVkv,JvcuV%ulJ1j=Xձ*q-\Jjܕĭ_+Dr\ZWWl"zW3TG)5EI5EYF*|>j( ( (( (( **~J ( (QS5em㑥q9 s 0ST6B P>Y;@>m+k2l̑Jf8 . tyNQo-$kfܴl* j:5D?GV\` #6&6$6j "pYÚ(`TA`(gXW21!| :AI<1p |&\$D.ͱ1K@)ŲRĵ䠭q#ԛdHa8 S>0g@*. \]445 u"ȜNO0\GNyT_$[4vf/.#4n#JQG =~'`ԏ:;@K0zQ"q!ikzs @$b @Q $7!Ћ]ԩ\6h婅et)E2@.@co vVq/'ΉĿ.F1+{U׶Tj@4fEJA6^P7)|l˺!)l1n;7I1zASfP?S7R4y@5hj[B˼W#3i*W 9S&2/䀎h9񕪅>n& w*"W zDON9t$} !D8ehm8KtKM 3y1zx͒]Ҷfd`揁2ň4rSrJQ!a>Y r0r^ouI8i-sA=,#p=>bQ]~g( M&Ð2b!I osBɂ͡هm 9~lpjo|A+|# x??}Igk3ۙ!%"˜AEuL)l@\!(3OL!Ld6:(J9W+YQprѣHfxz*Wm#"G.E 䞝g(R< /Ǥu*1]r+t )p&aߙ"9ԨA\0oOu*TQxPR pPNSg@ΡsZE0ْ *B&6 o `xwqHlsQ&D!H P*PE)v[a@[/HzD†0 ^$H k.[r"9,$^(9|6'2QbBu2ТFeWIC疂 YsZ% ~_LJ 5Lxr.3z62 sZfvwnL74fdEdb\C寓z}0`ɮbk49P o`ACsH[FeJ  TN 9mEUaH~~L<ci܌Ao pUMbA8 u0f/o& ݧts&ڙnŪ*T"p>)%Ȣr=ܵy!cc$ Ʉ)adc3)WyN@&_7<&bݒ<gHv*A>C"zwrW*.$/qGƻY`rP1A DvQ8jfBQyݸ$#a(|ΐhO-BBN:3HHn ]b6X_"Tsu?f[dE'则AkYfDclpD5ҬY&"CeBd aYM$R JD?b(DrS&m*Da)\jĔ ,&nl*1r|9RC%JVI(*@ HT HPEPQ@QEPPEP MQEQEEPQ@QEQESQS@%EOQ@ʅ[n4T/#s̡EE[1=tRIF2pZfsg+&N]oБֵ?7UaŤ7+"d6_4霯9i~i ғ3džotgzr}ҭ경ʽ1Ϭ;4}l93)1?hxsJw*8otgzrql93szþJLt7}ҭ경ʎ*ޫ+ܫܟszþGÞ>ғ3LJ8otgzrJw*~vѧ \Iv2CL$x@#iZ3 YeW߾ڴl]u"ic PJLt7!}ҭ경ʎ*ޫ+ܫܟszþGÞ>ғ3džotgzrJw*'Þ>1Ϭ;4Lq*ޫ+ܨ}ҭ경ʽ fȸUҢr(L8.|h!͐oȦ@}l93)1?hxsJw**ޫ+ܫܟszþ_ ,sH_M AQex䉦P1.riIF\UVWT>gzrn7C?bMG)dE㓦 09 5sgRc~ѸlUU#g*ޫ+ܫszþ_gP|tEYvCe1Lr1D7 )1?hxJw**ޫ+ܫܟszþGÞ>ғ3džotgzrJw*t3YV.&2'C9M6C#y+1Ϭ;4Lq*ޫ+ܨ}ҭ경ʽ1Ϭ;4}l93)1?hxiTd }d dlH$E0M1c`B嘈/lUU?=f}aycaYXwj_ >VY^R}VY^^sg>6}榓3Ǒ+ 觯:MCp)3ِCpWJw*Þ>1Ϭ;5 ѸlUTp>gzrql93szþSIFÜUVWQ{[;e{{caYXwhsgRc~ѸlUUUVWW>6}揹=f}ay&:g2X,q~jF\1D y D@~P >VY^^B8{[qg^9 rb#cv\ rj韴n.xotgzr}ҭ경ʾp4e|@Ij s!u(r1"W!-t%mGOYH6i*&l2(0s33Sj%rFLPQe`J`gDlUUXکLqs{[;e{[۱VJNs{t ~g2 sHmH>0`ݘAjjg?2׮ٺ^09usJ\b »/Y^WTRa៴n5wLVY^S{[;e{sA_3JJ:QFxO,)" m%@5_»>ڥ&:g*ޫ+ܨ >VY^U+5fT]…I"xz3dZcL`)1?hS>VY^GlUUʶjܜDB q1s 'w`N<~)1?h\p>VY^S{[;e{u%=2UDbs#7<폹=f}ay&:gp>VY^Q{[;e{{caYXwk ,rUQ'NP:g 1D9@J ;”韴nVY^^sg>6}攘韴n<7{[;e{UVWW>6}(V 韴ne4mO3ǁotgzrJw*q,Y{j^^6Q%) S2N1ĥ_$11Dyi[#UVWT>VY^^>e4mGq,Y{jVߦ~Dx#Jw**ޫ+ܫR̳=fz IxDrOtU8(t<m(>VY^S{[;e{{ߊ\8g=|W/AV/(T)R<4E*ޫ+ܪ8otgzr|qK2G\8g=+o?h<{[;e{h hF5s;n"艤Eۑ0]L" }{c\8g=y_ntZ&2JF+fUȢA61y;,r c~dTr@r]31E*aU1E*`-L zyo3>|ow3d8]p\p`g W<[ *Am_/HQ oez [ƁZz0ffbR6u7qUP<T9PE1%!@dљS ѶmQMŹ:GJ,u1PP0J3rY[M4fQچUU^*9cL$c0`Qd#eϫrL)nǠtUJQL82 g GvyV~גY\8!+pD8?rQ&U4@`TNef+Z܁RrH)p]a]jcɕ|%Vlel-Q3E Ad'B^ZCNyn+1d_ܚ<#"ZAe($Hb v:c!̡s/c_G7\Ȫ3"yY oFᅈ<ӓ=q89hELbt,4ӖcRvUˈ<F@7 _cH  ۦr}̳)k2N=I)4~^ 7L`990y^ahfohP褹̙p*`R,L) Ho{b܄< cٔqU0=%ֱ`(j6E3Ҷ\i\+P$H)R"˜昪TJ;ˬ͕XN#voWnSC/(!(d:g*߷{>_#b먡dŒ*b!LQ VY%fHLx7# @p&3gs v&bpNSu"!۹wed3eL\A"t$RNMyIJc9V^7+7lJܛ]p*Y&@O-l-rfQƐUW+T\Z9 wo9"њ &@M1=E8&PbxEǤLtx`3i}G(@d.| IO"Z~VfTC8v 1@SRuJHN٦Trh#TM6B9LA"#S#fZvcJ$W)2a2E8Rh@l Et]-ikk>Vat+(*@v˔n 6y4aVW}%vFQpvٻE]ETMY;L$!SP'B-Um[ k,93jgO0|m~W ;3,aImQMƟXT٨d#KQ\pP؏5pE#sS?H&`p) Qa̅rX]2Mt\2ZPy*#1NMD &1 \LB25ɴe0) b)L<҈]U*g+9pUa]u1$Ps;"e 'l^Fv霏_iԜc5HNPM1)#15–(][w<\ɖmIG6AtH @]@ b @Ki?IzAvpJMG@M!KN!"uݳ]P<#RD)V.DDB^Ek g^2MEi@M9lԠ2l-j޷v^'hV(ֱdϓVM~QT0#eZX z!ȌDؠR'1U<A0107u'W49  &W:ASbuC*^AC# TGxb7ռ>rЧmq6܍ͼ 3I2˓>Q+O6X_@~? h'am;D/h+O6X_@~? h'am;D/h[զүgp7`9!_:W7 Z\){am2T\0EEgP>ܚDeY39knL[Ӎ&I'[t9 B(@ MЕfI"\pd":S.B9@db/aNF}|s2YI$N &gM@G[Z&MӔm89$-8]2sb, SdM)&E RBDa Tx,"٪gc$݂ &L6Tc s(#frrU+?UTʁS"G B3.E(y"`0󽺦^ -b1'@nEaPJ@PDr 6< ]u>vG _[QkXFUXp(yy +.&1b݋XE6*I30>xxXߌN/ @ܬaڌVa (M[]Z>yl˖Yr--h ~R=KQ6jԹXdx1 ]&v Q8LaG?)Q?䞿TTi @LSs$g(SB1D>Z= 1Rn.H]y*$ PE(C'0d&ΙUeЊ[_/PWgVCj ]**Fs)s9Nڄ#F>!-&P(e:*1΢1aDw_ [(OeMx_m3՟^Ӻl5OxO < oף+g纻 Ǜ^hmgcgZ嗓WxK >Nmrh|jz_ӒE[*\6@x`"}ӈ{fm$GǷd1P3o9VP‰Jq(i9s3upAp'[;3u 9@6"G \#D[vx6\TBlpsme68 D4Zr -V:dYa]utVP@ 'QULcw;D/h\3"S8" !1@Yf'ambdX=n02BbX2 nۃ1wߖa>PlidPT4j٢ ߜJS c* %0ɖ>/gתNgG^\e4tp6\7֒`;p z $+gGrY  ngB )@9&5q+ l$^3j,H"yn].I8QS/zçVB9Yf"c/f^6Йb>A̢"rU2L{%L-9I*}e?m(O=̇9d"9N% 52ű%.؇pY-r:"mZDF\! ƾ LfD,ݙ's.u(cg1r0d 8nH_]&IFI\@Mwi J)":<]8]n@b($LC( yoo`ølkj̈:!DRLbȺr.@#v#B2wYZE+Ȏ*96ʲwddz9˒b%CEhr)`_!$ٙḌ T |H7+i~k g$2jhW hׂT8J : M& ialIf9Κ~79c2h u1)@w_0jmg.9s&P!i  "`p@cvd-Թh&Bw N%9f]C] .I s|fe2.P0 i]"`\p׌-QniUcU"r䩄1FEDc ? 6RLX9O3t|MPS f%(h xs?kFg/׌13FnX7OLv`墀墮O翺)XұfWŒoX2x:O^\,?7/rxtqAc ӷ}*)S{Kl?{Kt? _U+c1ke+K80GkK[Usy\Ak}uUn+cmi)G8N/9/k?ޫ[+j\rzYZGV8Vڸ@?Wq9/۫>Q-\J8įFLK`l8uʦ&Hrb n"zOLI51?wbnX-?nj"z4i۟QƦ'xm۬5EXFaƦ'xmۣLOxm۬uOq#>sSG}:cƦ'xmۣLO۟Xࢀ#>sSG}:cƦ'xmۣLO۟X#>sSG}:cƦ'xmۣLO۟X( =?n51?{n~cJcƦ'xmۣLO۟Xࢀ#>sS>sS6jbH1?{n~cFq#>sSG}:cƦ'xmۣLO۟X( =?n51?{n~c6s@P51?{n~jb~_{n~c@l8mtq#>s@P51?{n~jbHQ@ifn5h`[VY%4 cB!r f( (P5>j(((jBjB(鋐 [,^mhlQ3|1Dj8mu#>sSG}:cƦ'xmۣLO۟X#>sSG}:cƦ'xmۣLO۟X᩠6jbH8mu#>s庮IrLMx6(e-Zut礹ː|OEpU \sD"IÔȄ!@()@(U9]SU= ~qzj\>S@\pǮU9]SU= ~qzj\>S@\pǮxUscTUMS@\pǮ8UscTUQTP*1}1tA:zg+F֪E1Gx W*UoIfP`墀墭p*fNRG3P(1ARZ-˞k,9*RVWVU)bjzՒ7YڴWՀji=md;j_U+dhs#U!<Ւj^bj_YOWUV뒺v0ЧMճZݵq1moղZf\rzYYZ(Mb[[3W2հZ]c%jհ?Zs1iy\=jՈVո%;4n1h_[=W9,W>ȧA,Hm Q1i7?\5ۃś\5ۃ5EX_kC]'o5pnڧn_kmKJ56WE^2|qfW ~|qfW ~iE6WE^2qfW ~ś\5ۃQMf Y_;j8y[pv>8y[pvԴ),ւA7kmG,j-Mf Y_;j8y[pvԴ),ւA7km@LY_;jZ HSe~$Y'o5pnڏY_;jZMf Y_;jY_;jZQMf Y_;jY_;jZMf Y_;jY_;jZP_kA{ś\5ۃo5pnږSe~$Y/o5pnڏY_;jZ M6WE^2|qfW ~ś\5ۃ_kA{ś\5ۃo5pnږSe~$Y+o5pnڏY_;jZMf Y_;jzZQQG_BCbxymon-4.3.7/docs/clonewarn.jpg0000664000175000017500000001046511070452713015653 0ustar henrikhenrikJFIFHHC  !"$"$Cq"6!"123#AQBaq$7Su$!A1BQq ?"D2{l 4c*M+?ƪ3TJ#oJQ$%%jQIffDEdԔ7io!JKJ#NTDIA&uz_ĸ$Sr5EǮ_+HIEg9>)~B=ҥՒUhJHc㷽҉61?GfX:"Zr"\?п22θ?Ԕ?+O2(CJBȌCwJw 6}-Z3j_ļⳑCsnJKjQȍD-r2:kg9ظN)bVjFm/ Mx=;7ij\S9,}6lx,m$DWDf,2CGD7!jKk5cI)$Ix3MDeq#L)r~-(ND/=#.Dʊ󹬺aJ%J<42?_sNA s09~V26)(} Yԃ2##_p?[B.C(}%8KuJy庻UW3-?]sY||c4RɑN"l̻1)FEY)'DFuC$E^2bx DTp7Љ RY"2#?&Ug%f> dC1q B&H' Z$G6+~Z|i"=}k"<@ɉ \Y%4`䥥G_v6ڔFFW#=v%e63䵏.3f%rTCD:27c<ER[dd ܍'ת gIR[Z̊\w\jqdͼnE#5uM/ǖѣ:C^NPMs!bpq3d͑39u |aJR͵UII#ڂ-J*?8d"b\ԻLc Jj"pҔ=||7ⲱ1ߖc#8O"KI/D7{I8㱑d`;>4P:LdIJeA|Au)'گ$II/&}KV5Ȫt6ć;QwugVyOv+d<,rK0JZ\S |"C}mkU$gCIfg1)]dڤ{U/]jLqq\WKNiuG_q¥|c؜l/ P&V] BeFK!H#&[*3*z X3.CexWNZ[?[idLh2v\20#, )Tyh%I4䴫ʌj{0?!j\dҟ7zPɕ)lil"?$ªX<6«̕(IȌֆ%Z KV +G़9YbO0Oآk_gmv +R+إHPI^}2kBL$)Hg.pܔsƲeǍ[Vh7RL켝r,!=pqM 8u#[;d Qٮʵ2R9nNoakT*i)% '6{vLQKIn&If鑞/^ N(YN},7g2鑠;QDFI42qWq|jX+0@Z%BSlo'gJ:1z(c䬶w" O)pm$ZJ:1'[f4y,k7DEYmBT2*fUwXxlw74M.Sԭ6Ԅ'ܕ+ul4VGeqp2Mdxe9 6Cۛ$ihuM咐^Q.W(,L~-^V<2eLCImjdԳ҄33`HW%2LR%mw+K̷HBPvD3^lJ\MPMeJ'XQ6ЪR̔T[*V/_ A@ 33 n #Z 5]a`" rCX.l̲#q 3"q*ȜA&gJբ5ڔnfgL.J_!s-.+JlZc9Z:_CUh`k4ć\U<\o?0-+ScDJiۊjmHH$J"2$Ԓ#!yhIJS۵ܷ\/j8~ܠW,1qzTIҧIĒE(-JⱝV2s[3{u]ԃ{WuaʻoAc;)έdmg껩Õva:ᓚMy۪ڻU}N:ᓚMy۪ڻU}3߆Nk}6/nOj9WmXs~9wA=]c;)έdmg껩Õv`N:ᓚMy۪ڻU}3߆Nk}6/nOj9Wm#ʤr.sem[x\TȌ!H^rTRfdҵ#FD 5Xꭩ'eubkѽvpUi,tuV~u;8Y@V[;Iڱ fKChqkCHKhZ Fi35$^Pd}G{ Q#:}x|%`Df'JL?F{?0KJ5 ZMKEUc—Yyi)9Wy3&r#э!)Khɞ¢HLH;hh:u^ƕ,fI"$U n)=(h_/A_mnl}(ȕUfw"(9ݜg2mw_}8sJy8]e;p x3K$\sjSitB4FI8IQlUKq4IqdbMgLG>5ߗzU'En]NG1ck=Zb8i(ת=.krbMgLG>5ߗzU'En]N1ck=Zb8i(ת=.krbMgLG>5ߗzU'En]NŌWiզ^d˾7151p~_UWlaww8s1^զ#Vz".\+zq]~QWU]{}]k\o$yX>.Qۇ~b9jqǛJ̝O5xymon-4.3.7/docs/TODO0000664000175000017500000001702111535462534013654 0ustar henrikhenrikBugfixes -------- * From: Date: Mon, 10 Jan 2005 15:06:36 -0500 Subject: {bb} Bbgen depends not working for conn tests 10.0.0.1 host1.domain.com # depends=(conn:host2.domain.com/conn) 10.0.0.2 host2.domain.com # Both hosts have red connectivity. My understanding is that since host2 can't be pinged, host1's conn test should be clear, not red. Is this right? Analysis: "depends" is not evaluated for "conn" tests, only the "router" setting is. Simple fix would be to change "conn" dependencies into router tags on the fly, or implement "depends" throughout and treat "route" as a special-case of depends. * SMTP network check violates the SMTP protocol by sending commands before the banner has appeared. Some servers recognize this as a spam-client, and refuses with a status 554, causing a red status. The correct fix would be to implement a full expect-send engine for the TCP tests (would help with other things also). * Make a common vmstat RRD layout, to allow for systems that grow more advanced. Use this to add Solaris I/O wait data. AIX also needs it. Will break compatibility with existing RRD files, unless we look at what datasets exist and drop data that cannot be stored. For AIX (bug-report Nov.10-14-16 2006 from Andy France): > There's a mix of "cpu_w" and "cpu_wait" definitions in the Xymon RRD > module, depending on what operating system the vmstat data comes from. > But all of the graph definitions in graphs.cfg use the cpu_wait > definition. Also, postponed from the 4.2 release: o vmstat columns on HP-UX 11.0 are different from 11.11i. Marco Avvissano March 10. o vmstat columns differ between various Red Hat Enterprise versions. o sar data parsing for IRIX client instead of vmstat data. * A host cannot be configured to appear on multiple pages of an alternate pageset. Configuring this will cause it to appear twice on each page. Fixing this requires a complete re-design of how alternate pagesets are built, and probably also quite a bit of work on the internal datastructures in xymongen. Things I must remember to look at --------------------------------- * IIS6Check: Log performance data in graphs. * Scott Walters suggest larger RRA's for graphs: "I think 3 RRAs is good. A month of 5m samples, 2 years of 1 hour samples, and 7 years of one day samples. This doesn't address keeping the MAXs, but is worth considering as a blanket change for all RRDs. You could then generate *real* 9AM-5PM Load avareage reports for the last year Monday - Friday." It will require re-generating all of the RRD's. * configuration file for NCV. - filter out unwanted lines - more flexible DS configuration than the env settings * Create a new xymond worker module off the stachg channel. This will dynamically receive status updates, and therefore it can have the full status of each PAGE without having to load the xymond board (should do so regularly just in case). This can be used to switch the overview pages to a CGI tool instead of generating the static pages. NB: Must be able to handle multiple page setups - or should we just have one worker per setup with different config files ? * "cpu" status determined by the non-idle time reported by vmstat, instead of the rather meaningless load average. * xymond_client process/disk alarms to different people depending on *which* process/disk is in error. * process checks that relate to a group of host (process "foo" must exist on at least X of these Y nodes: HostA, HostB, HostC. * Configuration of which graph(s) to show by default, including limiting it to e.g. one of the 7 disk graphs. Ref. mail from Charles Jones 15-feb-2005. What we really want to do is customize on a per host/test basis which graphs appear for which tests. So this means some way of customizing svcstatus.cgi to include specific graphs. * Something similar to larrd-graphs.cgi for picking out a bunch of graphs to show on one page. * Move all of the xymonnet "badFOO" etc. stuff away from xymonnet and into xymond. * On Fri, Aug 05, 2005 at 09:39:15AM +0200, Thomas Bergauer wrote: 2. the NOPROP(RED|YELLOW|..) command in the hosts.cfg file works as announced, but I am looking for a possibility to tell NOPROP a "level" of propagation. This means that an alarm should propagate to its sub-page, but not further up to the main page. * Dialog-style network tests. Currently when we connect, we immediately blast all of the SEND string to the remote end, which in many cases is a protocol violation (e.g. SMTP servers may refuse us because we send data before seeing their "220" greeting). Should do this right and also cater for multiple http exchanges to follow redirects. * Better dependencies between tests. If you have multiple http tests for one host, be able to make them depend on each other - also such that one http test depends on another on the same host. And direct alerts for one URL to one group, and for another URL to a different group (like GROUP in client handling). See http://www.xymon.com/archive/2006/06/msg00210.html * Better selection of which graphs go with what statuses. * Easy way of grouping hosts for multi-graphs. Improvements ------------ * showgraph.cgi change to make zoom work in two dimensions. Requires RRDtool 1.2.x. * More reports: Check out bb-reports on deadcat * Multi-line macros in alerts.cfg * Allow for regex's in the TCP response match code. * Merging of alerts based on some criteria, e.g. merge all purple alerts for a host into one message. * Implement "--follow" in the new HTTP tester. * https proxying (proxy CONNECT protocol) * Optionally hide the URL and content output from HTTP/content checks for "security reasons". Marco Avvisano, 20-sep-2004. * Set a "BASE" URL in the content status message, so the web page we show will link back to the original page for images etc. * Provide a way of sending http status-messages with individual test (column) names for each URL - apparently, Big Sister does this. Suggested by Darshan Purandare. Repeated by Scott Walker. * Provide a way for a "cont" check to NOT be included in the "http" column. Suggested by Kim Scarborough. * Allow for enable/disable of TCP response check per host/service. * Use the "acked" gif for subpage/page/etc. links when there are only acked tests on the page. Marco Avvisano. * Handle "summary" pages for alternate pagesets. Need to find a way of detecting what color a page has when it was NOT generated by the current pageset (allowing for summaries across pagesets). * Improve the history colorbars in cases where there are many shortlived statuses. They should not automatically be given 1 pixel each, as that will cause the history graph to be *very* wide. * Display-only tags should work on duplicate host-entries in hosts.cfg, e.g. you should be able to put a "NAME:foo" tag on a host and have it show up with different names for the same host. Various ideas that have appeared on the mailing lists ----------------------------------------------------- * A report generater capable of displaying for a certain time frame: 1) List of the top XX "host.service" state changes. This is to help us understand what is barking the most in our environment and focus efforts on fixing chronic issues rather than band aiding. 2) List of lowest XX "Availablity" for host.service. And since I am throwing things out, how about embedding a 13 week rolling availability into the status/history page? Scott Walters, "Reporting based on history", Sep 9 2004 xymon-4.3.7/docs/xymonmain.png0000664000175000017500000013477411535414771015730 0ustar henrikhenrikPNG  IHDR9m԰ pHYs  ~IDATx}l7ή3 G-RlU3LJq&)IUeUf8:nHDsG*ѲrС#_-(T{Hd?.8N@/_s\xǏ tx)Ȭ22!XY,,@ ցM%ͪXI,0~tSZ5oT::q Sl~q5|%qk w[w~rsHSҔ)y" ug!3ݾcG7H={94wVzεOJ 9;-ck/f֖X=awWۭ~]lz8"+];\άUF*g'xNT<~ډc/9p~ ohA_RVp˕v4хQhj=!!8Pq !K$/6Jr}^sSn< ÅB gu|(ṵeޗ_h?'S5l ւ솰6cBj:$T?Q^Df5c½Θekez`OrҟU.;mr?l+IMbt:_q8-LL B!/Ņطsػ=e{ȞƳ4su ;^ǎ-orubAf]^Hv~??lfIe&T2\uu 9[ ϙ=s=/{{vEK9/.Ë;R=c@Sf~fMj-]Grw񸻿u wkwvԚo26n25.3)ڔ3 MTk{=uʃ?*,]Gg v,l yhjl>hr8.ٻzʟ^fnU*X9scyw}v}''Ud?[ʑbB^.l/6gi<`%jv22R΋|cqX8{`=Ǐ?~+ .GE}T:|jy,4f ).Dž˙ͤ-oXa Mاm^ej W"W6{Ve}{ a1jWWsD!!!y{]x~:v]:`hNoͯZݵ%G>.lK~ zj*ς( o ٳĸ0=Xci'sMv:rˡIHr6=ז^ ?/Zp[,ϖ7v]:N~|9:bfm%*8 Xęw#kJ 䆇~]]/oJW,g&`ϕK{^ow{||ISyZG ;v=_2fLm}Wֿzڝ?DDSk 鑎Goޝy<.uZV&ȇ= ֍n /]\2}C<o: (^ LyG#Ǟ[~]]Xz>D}\{^o8+w^<ۑh ֺu͟ >֋/h+hϜ6,s?gkݷݩ'` b7ݾ.G  ~3 ^J|kиpo {u_UvvOn`dR.uyq =DV|L=܎.3A0 os\iYw?uwndX7t~A:x@y02<`]*2 ~@75K]sWU@dTckvζtXY=/{cӕ8/r`=z1 `iؠD:H? \ujm'"=a,|6ԑTfդC~?,L 2gDSh 4Ej Ѭ8N̾!xr\dDNJ[xd Zŭ{w[Z7X~,`Y$?Naf )[X"Q-@!"2ASp{)hv~fޭgOSi?L^<>Yǰt5e+,"M)Jb[J3aUZԸ .X"dzZ{^6TdMaB7 \̕r"z9}s-}qk\&J{-cKߘoX^pbTG5 _] '&dk]om_xm+˟Mm0hĥK:æ`񝏚;lJ͜\z(|NY/} ?3KZGsnkwܯgn6l@ Ej)X:$=ͪzeE6^Ю+R#W"r?Len-q软f3Ù9h&)lFqɽΘ uo_k캒[%s-81#Qm%0B&E;E[e/W~4)p>kb Vmms!pQ1Cca=qaQ5n1-1:FA˾Л"\+¡cB>ɿ̄5%$)ԐS _6Q:- ^͆f}q=y !+L LgpO9?vQ~rˠՖV8\T\9JUfy{ID2XSSITEMѦK3CI85J$K*wD`yLY ?XvϮ!rP#qz{Ƅߥjx:~_:Go|}@5&'ΜTXfOX^1rj9> ol ڍޚѻnXk[vj=he#Akپ}n˾Xbd6z}ѣ+{j<&d2 %HBzS|NJJnQd4c$nacGQ A/0_}6_LĆkJpiֳ:h.h?U[TiگJ;I%PLe%=x 8D !eĞ gs8qKA6s;>3;AD?LpqᇩhjPCNJ )hl4DCS irD1nk/xMy56CEC.URh Z+﷎ĨvkW$w}f ^zQo֋prfI"N̜o[rp[S}*:z2g:pzk8vnc͞7)I ׉oѨ)z:j^z5hLM&a rn>Pt]uS*QRѢ~*HSMi qDĥPω9?hli y:*n:x%#@UO.<sOբ'eڮg X_t$Ifl&Gdޥ;|LjTGOF0:dZ ~3? 3<'aG:L` 2+ì3䣨y^d]5ua0wtTF@*gs`Yr9,^޲/zzYRF RvG{F.}ԷCDDݾnߓD)?DDڝglݼrkU?~`zϓH N$9C> u8g3O Nv+];ĭ{^n:G`#fzNԔ'uR!qo2>Qq^]drpp.%ʭ&#nΥuK_;! h4Dki~@W""dOĉVaE \$ 'Z)ꈦ*-=56"W ӋMDY@*76{Zk9 <="S?d$"c8A|90'{1ɳY7avc驦k+$B>OڇvGw;O0.dOjv=Ɖ 2_m\i1HBƀ|lV'/2Sl>ec,'&%'ǿahP[B ");NȭJÉ "'i|ֺ5J:eg-saoܑܷtf钖z@ي'ze2K"Ua7KNQoA; ",vY˂Qc*'VDk_cڐ;\r'6o䷙'Dm[oĄXL0Mo^^+q-vͥH 5M75dˬ7TBlYj@WrkW$@Dnn #<K*>1*;ah/Rz7߹Y*& )!%sҧ>LM(rfNdmH]Rm A+3l $srChGu4Ij)!Ϣ\ MMdƿ.|}"ڶӎ&ݼzy,705n ǪnEEy6V=\v">yr,z{5ߒeJ7K @#u9G?__R 6Z䝘 Į')t?K*DOĉ(ɦWfä{D۫s}qkqBGW/竗f7kٚ>!FGS7/L0{d|їVǎ.c$D?Ez263Ћ:'&HBSNSO*YXwv4Yd6`\V'%rIJ b93X~jzH_oϔ f'#_+/xֺ>DdՔHԴ$VsgTk0%$9F<ߥn<Hy߉qOik..ˬDHTiJ}d7k v55eVMHVAd+wryKy;ݻQkݷǔi {>dW3ֺ}u速C-ׇZv>>DNjqX -WW:{aܲnkK_[ڌ,u9}OZ91tˡϖ3\QGfYiws߁5Nrx:X?ή[?Zkuҷ e/~/>ho$MIC˿\hfOm\_%ǁܽm^gwgwq,G ӞU@%AfI&ȼ\Oi!8^Jb"{?KB 5/ sv+WbYPuqq{J@Nz89|>U_}n}[눈4w]#Ơo'&EZ',LƯ6 k*ϩ;NL~ARqo*ubv`҈H4A)o6$oçr|֎H NujwDa#!7⦳Ԕ*tI*D7_1siEm2E~.u{lx{[G{kLal &"2n{ۨ;kkp»,$hQϤ/{^Сcz[" 3yfn)R\{]TM%h6T8QgЋĄiTc., ֔Ĉ%K{dRѦQ-=\?YQ\&HoXZ㿞-bٝ RA F*ܻ ~8R:[ _WoX2j;iOՖA6L0kQ\ cu&brjG4jj4 -UcW|fv(W:y"ݵ Z.ˠe_j_mu=+^foVZIJwzClbSg5p7,m7)0SCMԢ5^/9\űC3N^__<~,WpU8.{uqkJf M귟5g1&cCO˷&XZwﱈ]qW "{Fvp[l^U^$ҋאZąxg\ i&L̄5evcdXSsD̪D2"I=HXEEjJ'}K|V7]XkS7yZkJ][YU/f.ag&JwؤSf3fIHDn}T2L -iYїeqݷ)yv}eۤTes.A6Mɜl{It|v !&)3aRyv4Osg6:`Gno2d:,畫 ۹9[ޱqY{}wJῃ-]8jl4ne+Gx^[OfP~MsMwϜb~* dl,4x8*Xڝ d.1;+btiT_2;zBQO7}ݎW;z+oﷀM(GӦ >`5e>ϼ?WZd>67gٛ_Aƞi\v$6 =/[c½X3'lMj<94|=1f鰿,}L&s̈́4=Xʙ[?1xe_&`bpLj:{R>If>cc)۶5oyf [a+۳*Q:,}lgU"(_mUǑ'e(Ew5c?s9>GsuK94e{͖{}]/֒Nj5[g_ez,wO r ,VuTyI[Ŏޝ:Y0]6uzӿD /]}٥kNǁ5L8wU哭S?q|n5˼z!|-XYn>̱͜4MMFwtgi49~ٚ3Fw5-MֽZ:V>nmy:)h|1}hֳ)X\c V+nTm/lUV%N\`k 2[ &\Ξogmu$&~lMW`ӘZ.f)2_ˣQ[:2NPg63⚶!|u:?ٚ,̜Oea*' eݰ3ݳ L<+}wűpĖwٚfng˭g r ,yuusvaz(VN@>`b)h:l ._94[n3ltk-)fT3MKLc'F1JHj X~&Z4}eOбwtYk)rv-sMI_ܓ¿VVǮ+VvsVn>S(syLzDУÚr:K˕~KU!\f魕:k{j^K#rW |'{W:9Feԭͣ`A!\ Gؓ {_+N\|z+}d 2r#tN@Cj&pnʙ)dlno[:h#ec9.-.Ȟƛ=Tk*ML dN('G `#Xk^˅P if,!flI_3}gkwtWn rd_Y3 Bd*>(bC!pC4?MYKUxYջGIgx?&S,YvqϽƙ} sfL,d^OfK>Kd]3<^wvبuf{Mmm`7KF7B?!ri$SPu4)hdDn S|2w^gF'z.8EOF5w.z2הKO&RǁCDŽԓ5)ؚlH^.gA]A;ۺ0VyOhThFZ,6 ?Mhô`a{*~8&ULiL`{7fZ d:R#Ugdg|LNF43JGiőۢ$̌%Hާ@jn+fͰ9^P)Ξx<}}qf}{=_Th%{^y6Ʈ6Cˎ;륟%r|Shjm l׶׶J'5i27aol~ssqxȬ =9:C}k/?~|5W$pH$E~.&&Q n 2`u^Ϩ{ϟBykE65v};j߲gkO)$ߚ02dG07?ӟ\͆ ^'om4g^ڰ;m7응#DF&EUT ɟ2]l8%,X/u ˋpz{EܽĂ| \e_QEj(m:hF>#L؄|KBYF9x- KeATss gxgYJlDd%Ɛ:Lzb&(z}ZQ5EjJ]"`c`tΪl en̂Dݾ'}ukSuOڂll1!~>& `:*n:0xfZjsXEacGš7HQE# H N$|uTU`V8` 8{Ψ=D~OiQdM|gS4yV@spI 4eVD&"I .vӨf d-d_ޝH*Ѩ=NR I/8JۉDrA/"ZĠ] ڃyE&މj}kGִ=IH )]XT̙͜mmfH#y;! hLXSRa0h%4tVFvSMq8_)B]SYU9cIH&B2L8lG}3y^F؜6CkuKVO9Mj6fnGcW IIH :8owWD3ᤂ\է{^B4tʭ;0~(pDWH&] "HwQO3Ņ{1AYHO/Q)f1>h>chNH3c IO&eAԨ{U""zHD@Y;c'$ML$}^#:٭źwkDoW7Nĕ6Vs:1&#DCkwG:`Z|ﳶC!7!7ϏˉD1gIH3ߘ0=LNY_jeûGD3&إqk_|_%^IR 'OJ\ -E6N VH퉟jm[~|LHHg,~]*oqkRFDS'vw_/nu A~M4DД"4IW !w}:ąaMIͪDqaԑ 9=*Q{$ph;nEZ͜?Tfj GAX9Ip_lK_|eP k̚OR?aPAj|TR?y~N,/ Æe됨EܽL݈z4%|2&][k^<~QiZD{پ1Ot^| #:|K9 }^?D\Q%l s[4ᘦ$pUSIR '$E"njrw>2Iez8.bͳv5p=l@?!)ܠyxMSK6K9 r-^_Xi&M^A-A{L6A.ke.6e {^{1q`855Y$nek@rN6* v%NNo!c<0e ߢ^|M RR9>,J`A_⹾8ԔԤ)*'jwAu| W vv9Ms$ńGIGflR!3hʨtG涤j~m jlߊ8\'ɨƉRz<1ss[NWG6acaܷ{v߶{"fs<6!TPxɨ<Iy`XmMiJn ls&K!sM~q&{4u򿥩ֺzZ^Y;,ܭVX l.WGlBR0E6)3c2eam0; )ѓQhdVQqJH QRa3ǎ.#h+hg.}>ɨ)vޟݐkt{Rrڕ0MHDv)Fė{6) )z2'ҦbhZ4p%Jy9!MDŽç}VP1ENTyqgIeP$@4OHWDUEZ VG_ch FGZ:ç1!ulȮeek69{N&`?r5uyDo/~rSNH'|5J0&tG5"m*%mgUUjViz%1)|5/k g6s+pY"N,1s^ URaZ͆f. gſ|_HSʭ] ?같{"WÑ?F oM%~T–6-w5jwVv7/dXy,`!go:hfy&)&e.%$E W%pURO^NF#S63Fɤ$[I⹾xL8om :'z{N `w]jQ%mj7'R\TNeQ-\8Ź]\^~֔H`zs-1a&X!;A1`yT̼#@BrLV8Ϲw>'/6 {B U!yO// o9+&DɨmɥKs Zٚ5[&lc77#^.[*<7l˥/-LD`r&JOsn_YlE6" [FavcF7C6ljGえ1YwE4 vSx47ѓ΃mI1&𲾔$pLD'̚9BnBvO ñS"QǓc7#0`K_yk4vEv-6 /!oGF.7 ]ɃRT~h&)F'G" Rs Е??itL-{V5~&DtݫxPok*k'bAQI#nj#:V86' rK-dDxb㉬jy"zT _f ]IjN.>{^E QLm0e""*y>ň#uPŌX.U`}aC"pw,p8;="udc머auTKZ>r'X5+vBD]DMiJ,L2D>rAsc@uƈ#L3flf,L.,jwD5d|g}$#~{5{8jl4"`a `}! ٰ0nWDa6X4 `X]֋- pYTlذ 0f,  Fd1 a~.%e[t`߁=/;?UW] ' DEݾ.GOS~R5-WUTYTYd&WIM"UcB|Lhmh0hxnЊبP9k|w``Sx| ]Ϧs *iEY,ɨ&n󒸕Nܧ97f~cKОRD{n5ٶ<͝fsN'b½Θ8o؞ly,3c5mo3so:-o*yk~W}kWȽ)LMa8k\%ߪngw]oW?\αWûK\)ڔl}wG~fz/ZdT{|= q?_vl[웲oxPG{FϵtG߿ >4ރޕ+dZiS\Qy`q8\SSfUMyl7lz(ֺ/)F~Rxz\ m7[&ۖ39g/M륩_x~f鿙Oޭg{^|'At1wgck \z(*ts-gOݮ2sMw{0%[z[mmfk+*lej;/bkðp[l{J|l}iXDtϖ0AGKGwsZZK:gtp8Y}M=}o+AwٓZVmr cZCK6O-crs| Y>-hu$/yk5{jn؄[GD(sZݵn-mH i&^Ůɉ:3en=1!}W9pyog7w٭*R=K"֏+&3wzk8 ZX VGeoVFenq_8Qŷ,XʞE/k:{Bg7{?/{wm)NokiEea֚5|RH}^ҹ_| z9ܶ5mksU\. X̕&E}'⬻r `Z`A֔K9Zo}kW?6\֮X_8w=_=Oj?tF53>+kMP?αq^ݾ>6J'kw(r~Y@}l(g[sa7lɠҥAݳ{'w5\ׇ\,(3S'ZZn,^wq%ؐLVuCHo Z~0FgE5 ; 2[_2pW|rZ/r}:jwLKN2D=DM/6yhBlg˛_l269`()PR qqʼnsWݑ 磋}Dβn뚫N.k21xԚ4M5RNbKwy`L&TʖX/eqys̕>DHkw/BqI>6:HzȦ謭'"mTADkVѝFY/|[1'6\?e"j7IѤDIԹ6 .x{EܽMbQ>Uf( f]f7Q9XD"`#EX_U5=2ܙQQKVg{7ӤogDC2,Si*OG]W0TY 72pl:Ä$$xɥ'-z_A"]!  ;ۛlN,L@ ?~12 I2GQ|qgI #S' 8HSt<^DșRMD)2u̘iQ"lhVY5HSKvLKĤ2NLf:)rV 5ƉVS2Oo%kNL*:^S%J*DDzh>}(gէyȉ%jn>-oseɷ{iOޣ٧}N:兿+yΖkS{0N$DMO6{Of϶ER텬/ͽh󭳼{PjK)zyψ 2Xn[+ERv]e Z`dA XY,,@ 2 ϔKMƷ:r"WʉWG~,7s?4?40Zc 9qCo_Vb-]fnm9'nDOc[ɈXeZ.-s!}z>EMar8`҅S̖;X`vGQw9uvUEQOc'~X3a[{g ow8k[uXݻSB&V3[Տ)~~%$!%Fҳٕ>/򥿔o |-s ࿱%h8JMASt|nbw5_,}*pr){2岖 ƹ6ث7~5// !ۿZlI[Hw^,|6?No^-~~/Bj\nU٠^ۻCpy~Q-KyYO^B@suT޾xva!;dtx)E+n oޢZguzbTr] x{g$ωZ-?qk!w䭨FcVje(&29=&""_v.5i팾k`/c~/[뤲>ycKmUK?lkT:^Eѓ LDԷuZCm_<'/ּڐڦȕoVBhm#!1:ç~zO[*}Oӄ颡,XqnH׺}>~+&ljq Mvȧ5!"eB_-ymIRchg,͋B%ߊjbv\FaR_X-)煁 |giHI"$4u c赭HS sYrccOٸBn)6JI'Q3S~Fn%^c;]gS,٠C{qV|z"g}]Ua&;ݕDMMV:[M2QCT ߹Rؖ Nw\66uȏ;bBn0сK);s?d'^ HS]Ak$pHTC& ռ Mk9N M-ܲE2jF_.4Ea;j\''mk3B4>tݐu.ȋѓXMѦP7TFLD!iE V\(x>HUx+qJU[ o`K"{\!7#%mƳn(Y~r~3]QV| ӋI]Xl8,̜j6W7\51 ;ǟ%76Tװp ھғυJEޠA݊ ["\KN2s{wuN*"wO&Cǎk1MYx+j`MzgU-n=Ϛš*¬K}|e־}[|)i.l:%=[.-ڧM' [t-,_JNnN^3ʪ;i@ưS' 3CSPQo CwDǔ7ʴ~+&Տvçjzkx{  43E4USp at*MrOCǧԏ-:=fE{dfd|Ծ$d4ΉOn^9΋S~aQxw^,ɉ\)a땡Γ_9Xl¥{_~~y֠J [aY_u lJ8QS؝,峰Z,!O1X~_bm7kS{:rR49QsbTiTSӊ^ͪϒK*.4.\>}u<_^[W, &DŽڞުY_`@!x=FrHj LD2 Ddd^s[KoR- TEpGx{kon}v I 'Y@V\%%nf}=7E3̜ۮ3sbcQl 6z5(2MӔYظH!" a"S^"=ċUF;~#}vt9ct̼q]=_mm>KKD(U[i'Y7eott=_.$N2s[Z FIGҷԭ/=g,fVϻ{Eܽgܻ מ~nW䱈4i no3 ϫ3ngv4C=D[|qMݸW1ș1!UҢq!V??h%M\ԗ¸u(B܀] H$0h~/G clu6POBuNjÑ:2jt:,Xpˑoik|wx,pZȘj: JO7.OHDn"ri__.=)?S+y=gkt.|D?O,v5YC}snѕvJên s nϛJ}T?nTܰyRkGͿt4k6)5._LtdV^2D"X܈:?5" D{PM}1T^D$U0ƶ.ٳfB*Mj?]ź*] {U7u춊WYwֈ7W,dn랣6zCgOw/d]g[61^gLPwj%}RHd }aJRjiW9-/,vru`ݠK^c;nvܓWncXPlRla5eV])R%ǹ"`K ZG7/;YK94}L|.#X!{^P^9eSvN*XN,Xd,mX[l:b!gKzz>j2Zp@_7] OֺWy/zBFODv:3^Tٵ;1af,ܿ;_أIlc";zib~ZURE"'ayEEC|aMM/՘S@?tFɏc'&Y3H m evГTtRmP* Bب֋ 74)4dÌA&l$uG䟽KSeND.Ka=SK" ᪐M7uM]_YH46 d Fv2m}uWg70ri\_I DoB[!ts(@Y:ju؃g]5 (#7~4~F≈ PeP'5.I̕S%/;y:TYs?eTr,}[>GHH.am.T@D|s #B)E6.+^Y\uei`tXy M+ɗj ;HbkulSOKbB2ռȡ/r$= ҧ!>l2.NY=7J<7GH:s./N'~*}yD.VkCTq8ћ>?Nֆ.|ٖֆֆ3XWS%aހK5 cw-<\F b""b7ҭ \km`X[tjæ`c?[ƞ{=cs%3q#{7:7r5Ŕ&Ǥɫ\Z.s'밼{塧ɺ4gCL\Uw$ ϶3\.;c˂[d@8(D7e'eVӨ}t[~5rՊ,ZkSg: Vϲ/6Y(sߑ`>}{@fDF] -] Kiv12Ą0(‰\):Wbo\ߚlO꩎GO"־x\՜9mN^7siRT]TZ(XDjK0FXdTYp廈+ &Y[Re"J=Lq3gjt}Z}O:\P}=穯g{e{!6 Y\;ym2Yl߻y=j6F6rrܻܸz\PKJAkb$!i^U91Dd|)`2;ĄpvME473dOxNL*?QahLHD5s[̼L b Q-sxO8zĚϱA.0*Mkh?cB?&K̥zq9Gq™O m7!CnfDR~4h4h/XEK9B.W,Xg5'n_YWqY?ND4N/2vf-']J}}%ˉ?'. &Pcll pc)ҼN/ZM6TNP⩁ٖVM'hQ-t2o3w>oMWs'5Mh}Onw_x-1џIYus˓Wܶ\t1{(ͷ߭sn!$$JjTKHjTccQl TF?=^wnT `3bQ3<'-abgxlOCX?~yiКX{nhkG{3s5]Nj. +-tqS}z`Ì p]ە%ġ258H 85 R:_oSpBlAB,1=C^ͪ&,{+jl4Ⱦ4lٽLA4{̄Ƅ" :^nBԑ袛[= ꟢2@iwՏǴ{{YX%?k>sG3f{>4ΓA_\K_<%Iٚf*ư&6:|r?K3<͝!+Йϩ9&!VW眙Yi\\⓮LO,%ٴ}h[de.ʹ!~cו;c#SYo]WMçX\_|M V}Yp\1sE6ֻho:a|Sd2sjb3sv6wz/dOI̜0k\ertzf,~p5kGc ƠA6Tk%+]Q- wlK}@M27n2_rcgWH@AG{Er KVWaH?Ac1ppz;+=˻',vvj[0vD/,gWae(p47Mj{fJ3cG<= ϘkcYr`JP˗_̾K$pH O#1w}llvcgz;Ս?~́Wـ۳<\Z1v';vckR~pztzZ ,/hduk5g)0]eИ}8^~1w¶W;e񝏚,t>ZnOOn=ʓ<=*X\Zt BnOv 7XGOc竞FYVؠlnsϯ[7ڧ~Xfn1f3 Z{ }z2_x@{a]uow4Hid2:~;IzqG#Q9Ց&23V— D.滇wEKw[S#U9#'*cG;Ϝh4sv3FvrrODHDFӸ][TZn_DtI'5х?]%5Iq&>NFWnO%Ef_Ԁ8 T_6*끟_N:(59k𙣻Jvs9IDATQO+jjԚ"J*?TH$ĥr(HteK刵eNh)?fF5͞uQen=e횧,w鼜LXnj#7eϵ%vs$,%s\f-}=,}r3ou['/XrM(5- uiYװ@õrjk"4.S%M蠮;))!ou,`;O |HDDEBȭ({sg? CQZ"2Ԕل*S ͑~"}꿽jϥEUIQ'?>}+y~mW(/!N<fS#_ONJݼv37{6nvT1z3=0kι[ώwg &N}b:UF~PBr(H?O#D ͦK=%P=!n8H"RkmfkW=yִ46ِNE55dP+%C^Wݾs->]:6eۖ*#]L.=IO]m[UXG !TPY;>KF_ɥ}%.G:ܙ , wDVg|֨6pNoͯ^)CSu<'F{F5E>>K>+ѬtK%R.jg.ƪK[|F v+رIq$R;]=Dz|9UQ񾫡j3Mo'&9_/^^"@D2VIVb퍻wm7KIGp%]JMRezT]|F7XgOjR_8o*yֆo6"SA7ֺzZrSy^mn(&84q~LJBo !ss1|p-;gm'7\G}|,uCfªjhj苟kG"]a3Se[t=ٷ֋GaXU?\GgwtwadĢ NK9<_^f>#XF3GrM*Z4YP~6AH43)0J'4y<&Mæ`>=h~qg: d.V^\cB撸0=Zb 2'r0NNW͈< Ms$&Ms"gξQ'MrB@õ˕Z`app>^jB|\`%_<7؟o^xnh*kn,o{6A?G}5=A-}<"yG?@U$R>cCȂ`dA XY,,@ Ȃ`dA XY,,@_<~SDDHD)D:H/x"NDD\pM:ɉ ifLS() ;Oc[wR,/底9$kقuJD "Y5qʉB)%ZtEܚiT~L'72RJ!fz8qvjJV|{͉IEk^ԢDIH/}ԧ~V}[?8YB6ɷ{iOޣ٧}N:兿k풛l6' D"NtZ)-ΟkZ(0șm(( Y_{sϳI!)[gyjԖRb7kd XY,,@ Ȃ`dA XY,,@ Ȃ`dA XY,,@ Ȃ`dA XY,,@ Ȃ`dA XY,,@ Ȃ`dA XY,,@ Ȃ`dA XY,,@ Ȃ`dA XY,Xr_^K}gr9g}gKa3JZ[[f\L Ͽ&\lnܷ/<&2hKd߇X֝7y2֯7`u:BF*AnlT)dԖtkO{˙␫kok*M>:ED - ^tzyؓ] Vdq&'UrGr^=t;^n269L {5DnOǿN˫yyۇf.svDl'&Ͼ?ms=SybЋ%1i2rr=ea3+ϓh/ NLMcy[Ԅ].\-_ǏgŞA_=r{ M TA }Ypo\ƅ밾$e& ڃ}qll4stzKߋ|=}yh6hv]B,97R_]_<'M_=->|3E?x.@"_mS16l"i27s_!]WCLH Jy\*oUc TISRsz4Z9kw/[]ZX;eIgЋw{:>G=Y|n,O̷- m7nM/{oWy |몟Bs}3h{R ?Q-\v6o\:l2 D,vSeleձI:nn٧6S>n|!`T&992Kr6A$c4 clAWrX$05sMє[!hqJc#@L{$ηUl~ZTɨvS,Ks_$g-k,,>7||%j=Ԅ?]\~s}z|RsmOrqgϲB~6V{֙{;ϸ ,#wSM)bfٜF"7e#7nne,U4QRhm Ɓɍ4ef-VːN сDUcW_z<=Hk1* u DrrO;k@T97͡7Lkg-Dz=cDz]D*am'|z>-rba oہ]rH9YbS9DfkQun=Y[ />m5F{Wl-xYU NWFc_PuL9z`c;k/W?덹sH[nHl}cqOَ?uy˯ᤲJI}ߵ|F&3Ҿ]ѩ~fi}YdTrе|SGQuȦ'xj.]o˧7}z4$=b_Gy>hy/k5wΥ鯲w3 1o"=)gel?5<^F{sY_cٺ=eɡit;wg^R.3u)NvhGkGɺ72c)_rw-"Ozb^˾ۮӽ]ڬѽN UO&`1)u'"pd+wh/~TȿʍlE/nB}9Sٷe+Rέ}#3n>hlxA5{x15?2{FE_zz/żzYzfB1[}3BE{Iɦ""'R'.a""FcX5~037KIdD򧌪Soս ӱD+IMowzͼ2U ۙƒ2kfzX[G\frtR "c`|sQ0sYK9׷fȱN vfn}|Kٙθޓa:=s^-Plzd5u5~u7O%ݫ5Uf)s{>˻GXdhz2us]Տ|-fƮ}e_gXѸpQ/O-;G-T/]«ٸVd.=soyӞIWQBDs=NʜD(k\O?9$ pfjoŌq>*1,X TY!P,@Ch( X4 bP,@Ch(W;wX9z &6~#buM(D"OIy""9I~ zx. g"\Vst=)yXoX*\h( X4 bP,@Ch( X4 bP,@Cl2= jDx;}px4O%R]Ɏ ?9SfH +bUD_sq~٫TN.ڱ^cӷ7w$"|]Dd/ED6D;HHHHH +EXNMTөAمr*y:5HX .\ W""fsTDa#1#1#1#1r,'gGBkaŵ,#nDD"y/EDyz͍L/Q;? 1#1#1#1#1,/{;u]Xou˿;ų@\m> =87{̡b}Q繦ѝ;df({{ᱦxݵՌKt\Yu|k 9;9^.s(c]w\ptW sHtERg]o~[Xq}W47 n-q{DGliO_ @9[NNfŌjgߋTdD.oٱx]Ozx/ֿ;w}]A#/j_Ԫ^ z@b$Fb$Fb$Fb$G`]3züzz:l诽pe=:Q90~ʉkcOccmHšPO{7nI5R@ v0`rכ#ٶe/wKz?6RqmhPe_lx@mڗޫBM6|+ 9 8VG u?ob|=؇f¾D7hakg;R_խZR/pƟtswʱw/ci)}3~ez󏥸eTfcOz:wl1] ׆ތ&B_nO*>,ͮT ]b٭4Ѫ:(""yA|-wÎDb$Fb$Fb$Fb$5 ֵPOOeJWo6=lJȸ.Հt;9[^r,i X/GץZ}}^8,o}:ol l}ΗHW&{sYdkn38x/~Y4xWDDgedMɘ?n'=tmc;M%y*^ztfC= QwJl=!hTKY\"ꫡkND>^N\DuQDd xKaepfܲtj{2>靽Nj [m^Ȅ[7ݑ#_ů꣆9-nK9Q;{!tm6_|W5L}hPߕƳg e!ϰpGܛ s9G^}gi4^NPƊשoλ#Z^93>}=hts!}ܰ:/@_FpEL `{U߸e7ju)ȷpt̂6b//$csϮ7(\wۮsHi}i>O[: g:_F-)\nw&#1#1#1#1<XFBS]ޓ`2<ڱ`kyy{O~\sv[Ǎݭ`rAz2ϧFB|g}rʱC/p>lАPUJ ^n""lK TvȄ/]`/܆*/nWa/fx`RjuDD}KԵ/[SF$Fb$Fb$Fb$FbXy\`]KeyWiO|c+Oˢ)0?K|76Sw>*TOH[mk5wwPD䣷DDV<ıN}k@b$Fb$Fb$Fb$`PβOV\%?0=Xl;"iT}J.)wξPݫ+ux\ 7˾VG;Zl^b_'.SJP+5KQ?H][qDg{WxtoP>PN]Uת?Vgk֏-{?z+қt02a._ڔsO=V},㪌a{ȱ^l}-nG{W/iϬĕʼnZރHHH $FbX~499999ٔ?RvTDD Xb%-bFQXr;c1L _H#;'"ˈ/1}5̜m%X%NjzkM1̬5~R)\37=t}[*܊>tV0X%Q'"c`|sQ0sYK9׷fȱ/h%fd};3W˝q'3Lt,{Z*-><ա'ɬ1.lR|wv[ɞZj3c¿>k2YWsX4 bP,@Ch( X4 bP,@Ch( X4 bP,@Ch( X4 bP,@Ch( X4 bP,@Ch( X4 bP,@Ch( X4 bP,@Ch( X4 bP,@Ch( X4 bP,@Ch( X4 bP,@Ch( X4 bP,@Ch( X4 bP,@Ch( dSrHQq,[tE Ө1*0]nr# ߿X"NJd8X x9Pb>^k):Ǔ؊ѷDVVB}"HoKD0sa:c83̛j!cYkz^-<(hYk!Sfo{U}ṱaf-۱JL'%DDJL?gfgzfZbiMufȱ\zr3<#ɷ<.3W˝q'3Lt,{Z*->˻GXdhzRLsx{5_kk?`|W,@Ch( X4 bP,@Ch( X4 "k?W}Q]LˏwGmAGw--@[p1~\)-ghG )QiT8l:ÌT?"$%mVfO#W}[=wܑ ;ܑG]d᱃ZRoQnQ̧Y+Z=qӽL޲t˽Q~*5{{k?MG}8w{ͧ~mzzp\igO&lei7Ҿ/;_"n$MnM[Dݻ8|PcQ :fwW\IV4Z%yo;U}Pi<|G{W^S֚._ZuygxOβ۲/v_s}@̹5 O3%sIgҾXXs@gΝMjʳ;K̒S&Z(cCc9㎥◕Fpt5'[uxfO߽` %,-?x-齽ýi_/|듶\Ifia&B#'<ޛDo}7g_,)Nʹ}*<'-^! @'jխ:Sja{[gůYhų^c9)*\2?u¼:DTjGOo}|M7rwĽY?=jc2s0'6x`&|FF;}Y.߲;A 1PO{p@{PSis3:4un޿L).w>/4Sp+FC>c]u9*>ţX` SͪS MNǥrg\E ӱ\jWbӣ$Ƹ-2KY=Mo%{?GKדbZk۫Z[̌] d"ogpCX4 bP,@Ch( X4 bP,@Ch( X4 bP,@WP\5. XO]?nHY6+tovn/}f· .dXoOIk5";e9{pQa0K b \ '{!TTEܛ"kQu:[A}dͅXd멆[\܆i7̄3/{MLw*1~;?|O'J9߼r<xֲHHYRQbl=Qsto6pfO t Cv,]v,tg;O{Wqa)>3 3 X3u|/{g\F-u,z`ZUv83"m}-m>^m>[3Л>[ٛn)f3湉̰v,g\v%"Rbdθc9~{!O^:|Xm7ۂ#/_  "w#e9V&|U{JozZd ֗3#bپ2a'䤜ImC5 xBQiYZ_Z9[DX(UeV?PNX.;v)8V_@}1!$_{'|߼eP'q 3(-RblǪW4""Y^,/1 UV\\5cg9VƱ+Vֺ?Xx5-cIHrNiU8+ XCI93/ HAZ'<ҩD*l4zÔy]PTG?tvCtHbTu2yIBֳtC/ݠ졜e &IXףg^>aicdd'oIENDB`xymon-4.3.7/docs/xymon-clients.png0000664000175000017500000010354011535414771016505 0ustar henrikhenrikPNG  IHDR'S= pHYs  ~IDATx{@T?~AUQ#T25A9BB[FXJ 5uJL2/xPB/`Q;?=g0 2~љf͞ރMQSWWWWWNJ """"DU@DDDDDC"""""QP: :FN%s=sG=z4J5PD+һw޽{Ǐ?)gꫯuT~ DDR:ԙ3gΜ9sАBs̙3g/[lٲeݻw=ͽ<$ ͛7oJ:.\pŒ3f̘ѭ[nݺYݷo߾}߿>z#93K333333<LOOOOOoYNs;wܹsgɓ'OoJgׯ_~_|_7۟322222裏>ܖݻw޽ݿIQZ 4h?Ҝ]zի]{キcǎ;vH駟~'U߇m [nݺueW)۶m۶m5AZZZZZСC* '֭[n]~~~~~HG' L'&&&&&fҥK.mw)a˗/]vuرcǎꫯ+i9_~'L0a\ê\#񞑾VoN*~'Ҳ-'DDKPgǏ?h&bUΝ;w,RH|0//ĜhVZjժUeeeeee|'|'-)~'4_~ubVVVVVߋ߶۷o߾};zZVȉ+R wŋ/^&iNh|&'M4iҤӧO>}]WUUUUUaf}> m~~aů2oܹsΝ[f͚5k0d)h<^{ +)))))/ei{ߥ?HE]XyaUG!Po"UNnZ7ҲHsi)QhWϟpX[TTTTT$}8.A>ek{Ȑ!C Axv, ccccc7o޼yN̩xy-c>Pf͚5kL+VXbzP>lΝ;w\eů2ʀIhnnnnnk4]j}>|0μzիW3{rQsYj׮]vB/S.]tR!xׯ_>|pÇíuw"cUwrB7"E""ͥt8]vڵ,l}w}^zKE0 Fl 20-sJk|AiQ6 0-[lٲE rmC PTJmBBBBBƍ7nز_ ׄ/?"~-[PD\gGSRG:pdkҥzֺF7V*{'*W'DDKGgO1Vxʕ+W/:2JIIIIIIx._IR(1w޽{&&&&&&"iiiiiia۷o߾ y&6vxghhhhh"A]yfj[<-X?믿iϪr?H,_e.mYѯ>чk@0 5]xȑ#Go.1CYQ~uau_#QNnW'DDK^Lh޴iӦMͦ 6lذAFHcp [?qƍ'tuuuuuh Ր5TLRcƌ3f̡C:8a$: ĉ'Nw޽{L)R]V<]3gΜ9sfXXXXXT0 _~[v+~U' K`c-˫4444440ag1e知Z)~~p|G}E""ͥt?x.17o޼ySĜl~! 0@B9۷o_!k r5PZeky8pIEW%'''''SIq8y@VS܇KWM:ůP^FyD0N Q[059Z.o믿믗,Ydܥ:qn50CI\wN{XkڲNVFO4G*vܹsNIWR{]䝬,`e?^ = tX,|4B[>Q/!I!7nܸq;<<4777777  &HYd"""""zV0!""""u@ u]055555m1!""""W^zk333333ՏP10!""""D~޽{ݻeaQ۫w۩!""""u@ u\"""""R>|-QPGKKKKKտgfffffȲ*+=-FDDDDDjTRRRRR2f̘1cƴSSSSSS>|p uH:wܹsfY;WG|ѣGFV`ӒtҥKGGGGGG9Ϟ={ّ#G9k׮]vڹsΝ;-,,,,,G}hʙ>;We!"""RBE*uzzzzzzddddddlllllluuuuum۶m۶lٲe˖:uԩSW_}WyҥK.!@BRɏ;6M9S_""""uP˾:щZ//+W\r/^x{뭷z-t4 <={ٳdظhѢEY[[[[[8p ,X`ACCCCCxe… .\ˣcǎ;vo߾}] A E=~puuuuuE"E7nܸTVyz6{)Zҡ"ͯ|4!C222222(DGU$ݻw޽'2]D (xŋ/^loooooʕ+W\zիH l~_YYYYYrСCIWQErrrrrrAAAAA={/ h%K,Y<Zݻwb`tK?t.++++++-----Ue){3bVXbŊSN:Ґ@t45hРAzΝ;w?s1EVHV ]1Ç>tСC:Yxxxxx3gΜ9Jy0(NV]1qƍWWWWWWwBC/M5LQXoҤI&M°:[Ð &L0A)߿)*9xe6ϟ?y8eDDDDDDx{{{{{ggggggY~NNNNN!@'N8qڵk׮]F}ۇھj"H زe˖-[t,ɀwUf͚5kֈbbbbbbھ<U0gȐ!C 0@ |*"{fot)BŦ 888881`˗/}}}}}3gΜ9sÆ 6l͛7o޼~-0 JD4zѣGKs^z՘򜠰朗f\J7}ӧK]fPKuӧO`HY4Q#`^4Q<ʞK](?_1)K BCCCCC>>>>>>u-ѳ 0IuĨ1avSDDDDD`CDDDDDC"""""QP: :DDDDD1ZʦwҥK.ݥ=K.]4mڴiӦu֭[n(BBBBB4c&,5(OQǦ*h8ҕI;)~ݨ;\5~Է~ߊkִiӦM֭[nݺ)A}IIIIIIG o@ܡSSSSSS??PV[mJ?+݂[v~W_}Uuuuuu5Oh E~y:.....nƌ3fhݾ7nܸq ,S=j4K=zTJ^klIcC^q|(8p޽{Zϼ\Ν;wqqqqqqAɍfϞ={욚1߫W^zÇ>|=]F4جMOkkkkk8ECCCCC8}ӧH7QEIS>ţ!(255555Wl+[~!(((((utttttp… dѣG=HH{-p_Z2ojjjjjƯz;s̙3g~~1bĈ#/⋪-ϣG=z$NDDm333333CCD㠏(#####H4b͕Y|jڵk׮ݵk׮]js\f͚5k>裏>HV .\pӦM6mBCw;wܹs7o޼yk׮]6|ÇKnS|~[/^xW\rիW^0YG'L0ab*"S"{#:qĉ'0ȑ#G;vرc<#cU|/_|e4@UOe)[ʖ3 R5 }۷/9'{Ì~Vd|C1ܹsΝ;k֬YfO)߿?JQ<ץ Tూy""ҫf"~Ŝ1E{ѣG$=.cbwI??H~Vr{ݻ7_|b~ !ð+1)eeeeeeӧO>ׄỈ_HSV݊aE7nܸqЫJ}*te__H\Yd-U \#1t+SK/7`F4NNNNN++gR+tzZ{P}gY\DDD.<>øM%}-]F;'33333[ =----- mu*Ϥv)tTM xg} :D{84QP: :DDDDD1! }WٳXDDDD u4>߫KH<{ٳgWvDڹsΝ;-,,,,,G~@!69rOu\,%%%%%) *Ğ?kiyoo۲}t裏>g… .ܴiӦM0-/////?Ν;w͛7o޼yڵk׮ >|Ҧˁ422222}kbŋ/^ʕ+W\zիW&( &L&Jt_aUS\PPPPP~ihJK?999999E뫬-[lٲwywM6m4\SO0+UxG=z-CDD0s~ԩSNEjgd]E??wsssssu#F1""""""B:FyyyyyysʅJJJJJJT믿zqqqqq:Dj/!466666F/xW5U>#ʞSZv}~3ԏO>O9R̤qpEDՖ!""Rs޽{U ******wx`#߅1)qث0v_l0 7r?~㪗aիWVaذaÆ Ӓ5ֽ{ݻwܹsʖS:+Ivi~Yb]w)RNUDˮ~Q:LWC0,?Cݖ!""\ u4aRT9%a~ž}ۇd}ь[nݺu?~1ׯ_(͜9s̙۷o߾};>"{#:qĉ'9rȑcǎ;VaQb:f1]q˗/_FXTW,KOWWWWWh?z xΝ;wYf͚5-CDD8^54_0È֧.tL??QG=zte*1wL[vMgE5Vݻw޽1oҾ/B̏!U%#E>}89k])nE0'J7nܸqUS>eAVȑŔ]_eI!,R^z饗^z N_!""R߻k|||||<2AexJ}ӧO]t0[~>فGox1bJZxvq)HOKKKKK;uԩSt*ev)tTM xg} :D{84QP: :DDDDD1!""""В7"P֧{5q gϞ={,%VVVVVV;wܹsOWSNDXP񫠈Mz"g`!""" l+8qĉ>ظSԧK$-D 6lذjAz\\\\\\jjjjjm۶mۦYLMMMMMTHHHHH6R=Fiiiii)6̙3g޳gϞ={"=#####+KkIqUX޺u֭[$K """"`FzW_}U1/^x߿_OOOOOﭷz뭷f8jccccccUA>!C 2;~*Gl߾}b bQ<KzGDDDDb̚T9aGcQ͕Y|jڵk׮ݵk׮]Zño̙3gΜݻw޽[& 9s̙3w_~u]-[lٲwywM6mڴ??1cƌ3dvjF"""gC4pnݺu֭7nܸqիW^|/볡#F1bľ}ZܹsΝǎ;vQF5jrʕ+WZ0o߾}}^x]u?ůꫯZf͚5kN:u)Dవz4C _Y91h K Ǐ?z,X`|_u/fC??Az@_"߿U#"""h$ sqqqqqr +wM8qĉЀ^HYKKKKKK裏># ohݵפ0ȑ#G$iݻwu.cJǏ bz]]]]]]]]]]]uЗHq6 ]Y9ҵҥK.]z_|E 4|qDDDDD;V/Pa 41UYm`Aɓ'O}t 4zzzzzzddddddlllll,ܶm۶mۖ-[lٲSN:u ׯ_~+W\r%77777ׯ_ Axwϟ;wܹs/^x"#ݥT-Ê': 4YYYYYYꆇKGGGGGG'&&&&&:;;;;;W=<tʕ+WD͛7o޼yҥK.555555E$Uٜ9s̙chhhhhhaaaaa}۷ׯ_~b~I|eeeee%9??cE-Zz\` dƍ7nYT9_L}wܹs王ٳgϞ]SSSSSӖ#֧t"""j- u4N$%%%%%!8ݻw=nܸqƉAO}}}}}}'L0a4=<<<<</^x=^zU:~YYYYYwٳgϞ='wɓ'Oc1cƌѣGE~^nڴiӦMS^^^^^k=e[v"޽HG03aŊ+V:uԩSikkkkk%??UTTTTT9A xԤ33`, `˖-[lQ~˻E<>|u 0`1Ǐu֭[b J޲8p)~ ""R:@:eOř65<˗/_|ȑ#GD~?~~~~~~lLLLLLL1+t̰5^zWTo+*r]EsyZw˃ᑕ03 Cڰ T/; 7??dCDD>ZxBxQ{ѷx:!""x06cakH_1!33333⑞RtXD% uu1!DDDn uMa*^2dȐ!x]PPPPPVwk,=VU6]:DDDDDԦ6ӱӭPpfa{tҥK.cǎ;vliiiii::::::آQRSSSSSp|GGGGGG91[|ZZZZZZbߛI&M4IܱK߱cǎ;At>tҥKp' tչ|˗t.v C ]Mv… .>}篫Ö鑑۶m۶m6vȿ~!!!!!!(W>H͛7o޼y={ٳ_?Ν;wŋ/^곽GʽuꈈcS\NB~ԩSNEjg0u֭[nU7|rqnݺu֭ɓ'O<)XL}2Wȑ#G):tСCÇ>|k׮]ќ 600000 GKVO }_BKy۷oYf͚S~]Nx_~嶹oAѣG="[XXXXXR744444||~ 0`ϟ_,+Hu{ݻwÇ>|6rm͛7o޼#uرcǎ!aF\$ggggggڽ{ݻOn~~~~~h!HVZj*t_VUUUUU-Ydɒ%޲j a>?P㇆镔}/芕޹M6mڴ W?l______4Xv>xPɓ'O[z#8J?pN:{ٳgׯ_?%"%I:bث#W%ij䌌 (CzX8阾)w\6JaՓn1!"`CDDn t06+>i<6ĻH\.K@DDDDD{uMaC`uQ[ثCDDDDDO!VKX"""zZ8WD.]t1::::::QRSSSSSp|Kt1'&pcKMKKKKK˾}wҤI&Mžj6utҥKpeYm#?׆څ .\pӧOnԨQFOOOOOO눰Ie˖-[ɑׯLj\ϟ?sΝ;gkkkkk{ŋ/}A~lfϞ={욚E-Z8p ,XР}.ADD,`0!))))) ?v޽{nS+]iŠEdnnnnn.h[TTTTTjnyyyyy%K,Y"3r(q&>MWFpIIIII j@______ZjժU/_|rUUUUU-VVVVVV]{ٳg?yŝ|Z1)G9r*3~@'zxxxxx @BߗK3eizpb=zCVжn766666Fp"ޕ~ zpgϞ={IDDO޽{݋GrXZRpWj͛7ođ:vرcWG3z-,.^b1K X%)%.,oa|ÇcVV 0`ĜJnݺuV,5Vlٲe1/+8pHS.q5""g C jܲxLj@` œ˗/_|ȑ#GD~?~~~~~~l0 OG#ӧO:AfH?wԑ_q>|yZ{u { .]t%Y @DD0kkkkkk.\pӧO>]~:l(͟٫m۶m6lɑׯfc>$$$$$eS|QBGZZ//ATߕRG~Um1wu\AGS?Ν;wŋ/^H)i ""h}}}}}ԩSN'H 6`֭[nɏ,oܺu֭['O>0`ϟ?>jUzk׮]v_~嗻w޽{iӦM6Ç>T%?9rȑ#oY֙ ͏@w̙3gg_z饗^z Mx+{8F1bĈW~Zv?ݻw޽/^xG=zhJJJJJnaaaaa-oGS>o߾}Ç>Κ5k֬YunW~ȽMD>{ݻw/Z5ݻw^஠\pGp[p͛7ow;vرcqث0=)))))Y~~{ݻwcZ|SSSSSSgRTTTTT@5Y0)zf4Ǐ?ngggggAw%'''''_hϞ={1joooooW^z)aaaaaa#رcǎׯ_~z~cUUUUUƍ4? 4>>>>|֧,1b:H+=ȏ>U:@Ϟ={)Me^kkkkkk T~꣓B?*)qqqqqqҩ-K%K[G?}q[~[v!Ξ={Y<@yBDثC %ijƌ !4)<Kz T:tСCbccccc#>>ì,30cƌ3f`NQ~lPY&]E ӯ#ct iWĀƠ ^?-PV[/PQQQQQQo|m_*sذaÆ UrS U߿?R0snQb <#"""""Oxxxxx8#JF <@KV]@lٲe1w»xꀧhZ_Yv._|5k֬YF̉oD8,&&&&&ܵk׮]b!C 2Dep3F<=====c>Ǐ?~[nݺCIq?<~8p)xUlde^?>|[>aah̎u.QJ}Jǯc{ ӂϢ dtqZ..&qeq|L'sk>'Q>lW|1s-]=P~ZvDܸqƍmQ>O?Oqe0믿:VzTW~8~xnݺu_U"j{Ok'44444χ0/Y =-IwMUc})1"ƌw:2333331 iiiiiiT1n+ɦ9듈Pa:듈Te zիW/%Q aZ!끈'QP:3gΜ9s@2Hz ""b3tҥKcǎ;Ϗ=e4?:99999cbNLݰaÆ o h.A6ثԯ믿>>`ڭGvڵk^~_~O6mڴi'Xpȑ#GeYgj````` /4?ݙ3gΜ9}饗^z%4U ~;E~w}]}}}}}ŋ/^X.~voEk]G=z4%%%%%EL@~`=|Ç]CCCCCYf͚5 Wu/?~߈"""e޽{EU ******w7o޼y&,~ױcǎ;: c񓒒Gbݻw>Ϛ!Hj` nS 4Ǐ?ngggggAw|X-X 0msWvUSV:~c[ ,~g&ӋӲͯs}Y5+cf:I#9O?]v_ױRb[~Ex胣u֭[*)i `E +CA""M#"\c.XCZx t,RtXDD$txC""j~~""D\Z:k)W^zbQ[b}bu@ ub3tҥKcǎ;ϯ-!55555 ;Hsb 6l`iiiiiٷo߾}N4iҤIسBS=ԁ&^[[[[[p… bDF5j(iH m+Ie˖-[ɑׯfؖ{lR8DDDD u4zi<===== 揎NLLLLLtvvvvvFzxʕ+W\7o޼yҥK.]jjjjjjt//////I]BBBBBʀ>======WWWWWW++++++U򋰟.zpMSSSSSSdhhhhh .\pw#QPG#awؤ$'#ؽ{ݻOn~~~~~RTTTTTjnyyyyy%K,Y_\PPPPPgϞ={y2UVVVVV:tСC'.6ak=bĈ#FϏvvvvvvǏ?~GفXȏp K{;wܹs/obbbbbYE§L2eʔ&1BDX{;aaZ5[:޽{ݻҭʆ:tPؠu466666bo &L0AE@&xOO 2^oooooll`XbŊH3*ٳgϞ=Q]vsi 2d3Wߚ5k֬Y#}EDDD>1 4E6#δqwwwww ƴ~́Y|G9rHG aa~eӧO h0LGANaaaaa!|kqi"""F OCCCCCC㹐.+bq,n".^cxfffff&#=----- W: :DDDDD1!""""u@ ubCDDDDDC %ѥK.]`wRutttttx7xCRSSSSSp|GGGGGG9߆ 6l`iiiii-;'M4i$q| u4t5څ .\PRVQF5J?======22222266666z۶m۶m[lٲe˰69_~W\rJnnnnnׯ_x?:DDDD C^OOOOOϬ,uuuuuuåѫlrʕ+W"͛7o޼tҥK" a*gWSSSSSdhhhhh .\pA_ظqƍQ*EBBBBB)xsuuuuu#P3gΜ9sP ۷o߾_~jܹsε޽{G=zG=zTñcǎ;^5Y{Ν;wLfϞ={l6SDDD0H6))))) ?v޽{nS+/-"sssss󼼼<oZXXXXXBʔC:tH_(+++++ ={ٳ_ /Ydɒ%jժUV|˗p|ooooooՏ6mڴiϟu@+VXbE}}}}}ԩSN'ΈWM[[[[[ ;;;;; 1' >hРA5zBq={ٳ'b:_E" X[[[[[c} vE`Ы%-KzѣG=d@@޽{݋ݽ{ݻW)+@K\pGp[p͛7owa bh\B<@"??ylmmmmmq{I~C:)% '466666 30dn„ &LL2eʔǏ?4 W" egذaÆ G~b @81="""""Wy>ȏ()gK`N*[f͚5kf111111zp]vڵ+ 2dȐ!ҹ+琂i$0 0 9Hˣ񕅠k֭[n?~1DDDԱ1Š,1,+?^;888883mݣaWӲ|˗9rȑȏ  CtZXV[\;N9ӧO>}. x˃Y.133333ChOW^zooP{Xf>Qʞ/R[م!mXRWFDDDcb4b)cU8숱wS233333 iiiiii}^"""""QP: :DDDDD1!""""u@ u4D.]tfJKKKKKy7x i~HMMMMMurrrrr &ŜX[^ZZZZZZbI&M4IܱG}Hz{ԍ&^[[[[[p… k_WWWWW-#######ccccccm۶m۶e˖-[ k#ׯ_ʕ+W^~!!!!!!(A|Hh$xzzzzzfeeeeeϯ.^`˧+W\7o޼yK.]^^^^^^T9 CCCCCC/]p… ?{رcǎ g A7gΜ9s۷o߾}{~'{c>|awqqqqq믿*?V&IIIIIINGw޽{bZ|1h!H}¬*+++++߿}2lrrrrrrAAAAA#G9؈덈H1Hʒ_WWWWW7<<<<<\?::::::11111:O+W\r%o޼yK.]t)ҽ&֙;vرc5B<-Zh` 444444H__#F1bŒwywޑU~e=zѣ"Ip…  """: &%%%%%!8ݻw޽ԊA>>>>aݥkkkkkk!.....nРA ?~^z / /P\\\\\,[@ߎ/|oAxٳgϞ7nܸq8 w޽{#ܾ}bbl~Y"7H011111))))))G:`ddddd/% k5gfffff =----- /t*Tm96iPݻwӡlСCE ywR@Ǐ?~:5Tl B/흝Dɘ D fKy7r˖-[lQ+E_ |0gO|*DBu˗/Yf͚5bN|#\<&&&&&90P4E#rwwwww  \|G9rHG7 <u^^z՘󜠰J?E@6 ꫯjY~YH, 30, *HCG98;<DDDDCDDDDDjW: :DDDDD1!""""u@WÇ>lodCDDDDDjԿc4uYֻ$tҥKRZZZZZ*?ooHCjjjjjt1'۰aÆ L߾}=g{T?_d[D}ܨϿDDDhРA ( GÑea@h^c=|f `5k֬YF##Xzz€1,/Hynݺu- |oٶm۶m0(â,i|e?{ wG@3 zw}]x,GUT9]vէO>}t֭[n'N8q"hӵk׮]b>B_ůtt^0A*7 `ˮ6-w|||||<2$"""""U*bk,?!ؖON:u)`#""""@~ DDDDD`W#ESZv|QP: :DDDDDqnodCDDDDDjԿcahuYֻFDDDDD{uHJJJJJJƌ3f̘=rjjjjjÇ.}]Ν;w,kꈯ=z#lDDDDD1!""""u@CDDDDDO"9ܹse^"""""z *+++++iY u詹{ݻw=ebz u]h1!""""u@ u:DDDDDF߿#G91333333qdYy 4hРAxEc",:O ޜ;wܹs""""jN:u)GGGGGGqbhnjjjjjz_~e^ +++++̙3gΜamQ{ 1ֽ{ݻ 7|7 x.1y} 72C?ξp…Kڲ@vv/MWBZWWWWWKؖ0\  r=?V`ٳimwx-+!""""mQG g֬Y>i˪||_i 17nܸq{9)^-D}w'OvqcRe ں~qSSc'HMM 5666>yЀ 7444wܹs~'|ĉ'N1}ӧFDDDDMFFFFFFZ_%MRֻ- su~S_ IDATF͑O|MDē'w77ϙO=lMMAj033y>|æƦ&1SW 9rȑ#?OmڴiӦM8UQ&SL2eJ׮]v:uԩS޿ "=/9dddddd$9b:.YXXXXX`0`r{u]:aBi_ݯ4sg-㋊ 8|xsΜA="[@K  NC`^ 76ݫhp֮]vZ |BX@TNNNNNsΝ;w۷o_[^^^^^#`\^^^^^ZDDDDz9/ /0J1Jz4??'""""jV=:0B#MG:B˹SQ0 aMcYePb,=1g `E5CehnnjjnF؃we90N4͛7o,본?|g}),c=lذaÆI,=i{-p{iA7&q\vڵkr B^Ǐ<%Kdژ(g~l>nPU`ook;dȀ=z< x#-B;(_XR&sbe6G#""""jK;w$b#+ݻw޽13J/K ^Rb_;;(^)))-u''g熆fggЈk=z?Ŕ=9Ott|QǕo>k)"Ķ=BՈK J5aHAWDɓ')*****J4zeCoq嗂Ggg01MLDDDD$ WW"@x:A#.K B~U/ncook#""""jb^ i7nܸq[cPu0B>s̙3g:.^tʕ ui\cPӧO>3tEUn!m,!""""1HX(r#ԱKH嗟~^5 vv/ٹ;~Cx4E6ߛoP]\kmYAr2Q7hРA5^9ⷃl6r nIt4׮]vZ''''''LmܹsΝU`mQ{a8n^¶ -G^ֽ{ݻNJ """"DU@DDDDDC"""""b|ՏmIENDB`xymon-4.3.7/docs/critview-disk.jpg0000664000175000017500000013560511070452713016453 0ustar henrikhenrikJFIFHHC  !"$"$C" e  !1AQU"2S37TVWu#Ba45q $%Rbtrsv6CDdwEHĆ91Q!R3ASa"q2BCbc ?r:BS)䤝Qb+qٟT$Ϋ*U*{u^/_t'o?GgG+9Q+*zKqFQCOHI[aaf|mHb^#U,@ru'vp d~hMxUhNT.ɗDFa^Q VҒ5ύ 8ύ i&N"%k֧]i"-KiMFFeR{-vgvg<(ˬf-Y-B|%ה9NjqWWFnܴn~/ ĕT,.rRPB:Ij4""guD-xyzE!AZ6&v"3V[ir3CRJ=+J|#d)g|l1RbQʊ Fhv"VkZ_4)$Jli cWXr{n)-M&[5CaTd'RM&:jA[]-b !!RN.ix $o"CZxȸCC ^K;'@͏XQiԥ%HS%=-q %q;$EklJTi&!!8M86XvKmOI%dV5IMy"-`V)*ԙt%AFF_0Kcf|mHcm)ص5Hm.-SO"ZhZnG*Jedd{HU>mz/'j)pnnRХ[ U\_P7T7)4ℙy4%!6_w!oqٟqٟy0.Ԯ6ܸl.%K{ !gC򂪲8V^,d| ]VOԣĝqQ(B#U+%VK\^Da/1Z6Rlhp(3=e5f1nFP.6"q*D-&JE+f|mHc_\ʪVUޚRH u(7d+iQju:r*8Is䓱/Y+U'c;snENj+Rq/%Ilj3iIiK$$Dde{\f|mHaf|mHcٟqٟv .氜(}tUf$΢ 7~̒pVq{q[@Z=r/!p hV}ZtB>60>61OUq"km҆Gآ?`lq0eu$ܾ%EQLJlbG] t,*I5Y$▄(DzHf!![a-.eDH1fHJHym3S)$)EdVNEMN jN/6CDL2R]>SI"AA% Qύ bѫX«Ӫ/ tvwRӭYV2jZU*#~R#$>)wlɒI=DiQm3䲟W3(a$ð|ҘR$fW4(ӥ)lrQTf|mHaf|mHb,W!\EQyYHtIih6hm)#Q갵&N"%k֧]i"-KiMFFeR{,;3oC;3oCTR<5USVz_͋%~<]w}I"ý^`ϳ;0KoԮ+RO5}ه{gv`52h:&6eԡ"{yXnI"3TSRk5`>r5-4 Զf,LF7$tײ $\E}e ?{y>MfIL,9Q} )-]Z[Q)j#=&Fw-s#AQj0@iKLv4H"7[3.m(dv"{gva/~o0g fa.H2Pn Lif*BJIBPDf&ff74:,Z{4lCtތ iH5bTiZresߛ~'f}ـ+zt#6N~5\b#6%hљXY)7-娌m#-[/~o0gwߛ~'fBa&F6)M)R̊b3s?T"F.BY}\B]ZkpddJR333b;e ?{y>mRBP$Dtý^`ϳ;0C=ur)?Jj lM%-$Il$lm < sPɎKie%:$ą;dW?}ܾm~O/w6\.9Zp:Q? 1RO4kh̬KI,F[6ZrsoU×|VfG<)F`Th(8ﲓuw.rAm:8ƛZ'kl;c+}W ^m[] s *њ슾K-bJ)S&!+]=hJjW~1x+x^۵>mEu;c+}W ^m[] b3΍#$fj4wF-J1vإ9I˧YUG`iGIEC1ه/w6\.Ĭ<ąHbE-S[dmB nbZm>,aPQZ8 xfDZ%%bXW2p01ـ"RDNDJvԴ ))Q).Xr23qjWYE_;c+}W ^m[] C]g ӧ&&XvCK ԇud/()$q9{eova;c+}W VU]x% T@Ƥ(5=WJ- $ew_q ^|'~~]rsoU×|Vf2†ReE4%R/Yԥ)NfEF|>T24T,hS?\fV#2;c+}W ^m[] |& ZCHu[U٥XK>IdA\~'eUn1]rsoU×|VfŬZTxl/R?c: (lÇ.3 Yi! %d)-DDDD[i;c+}W ^m[] C]g ӧV]vMZbN9!SqM2$"SpȍKUV+;c+}W ^m[] ,9>#dcT1sn[PdeJ[p7cI$[)x K4jI 0E'/p2p01ـ`thb1 v3L%%TR%F7-Q9V`!EE1BQizQ4SfFgMfv1soU×|VfjWYC]g ӧ/w6\.9{eov`-Vu/N9Zp:WsoU×|VfjWYC]g ӧ/w6\.9{eov`-Vu/N9Zp:WsoU×|VfjWYC]g ӧ/w6\.9{eov`-Vu/N9Zp:WsoU×|VfjWYC]g ӧ/w6\.n0lŕES0˪4Z45M֫7⤌Ȯv+ +ROJ8^?_7 ଱nUKVvK1[2YלKM-ִ;}Ԯ+RO5}ه{gv`6J8^?r+zt#Q/~o0gwߛ~'foԮ+RO5}ه{gv`6J8^?r+zt#Q/~o0gwߛ~'foԮ+RO4 қ$eW3iJw̦)8RqKQ\ kIKJ[Z)VJ"m!fkj6!K,eCRO0̶ټ|i#+i#[J/Jʹ4V3$1g33!O>8phZOqw!U6W&Imq5)j]_]F%YS"-OpزYuگɊO9a1Jdyequ JLh$Dё,RH!'{sFBTqLvSTwdJ6X6_h?"|S\1FbZ*ȲD(ﰖ6{σ#=EOfVAZiULh%9Xff[Td{/!َGh"'psoOĜZYS'LtYJMkvQD{nT p2*2>RxD[ԝ$[ *inUf5Hn.!u,51lv/mo3S niV>2\R8i; 簿 { DOg#Ɍr-zЗm-#ꊪS8yG]T'9Ye9ۥҖL#V\f3luuhFXEIi$d."t*n#-5SAˊ nt67I;%/a;ܷ92=Vm:nđ9tq 6KʹI܊+p<{]ywoSrjg>鏎_b%4:TH1 GlfHJu8⒕mV!Q6s)t9Y[zk }2z2٩U=QE܅II)K6 +]ܮ:fb:D?9cDEUDLgSX)H]R7*7C XG[DFXRIN):dF l&4v?FsS'LW' sBm<&$5iʦ2j&I~jmdz+Z|9O7-ԗ_ DvٚkC' Wi+ģOI5Rʨ3{V+kj&>ٚkC' Wi+ģOI0d'=6"A>6D-1̞'FiY#iѪ.b\6 Ufqh6ѳP ]n$Ȫ?N(,Ԓtlț5xآd/Mx+c& i+ѪŪڭ{oJh4 `GmeTd̏zV+q'e0r?Ra2:#U*B&e/bTHI[aƛW}`z&Sq~_´bmVB*+q60Zpjq]$ qMz/pdMN +piQ*m53\QI&ȉʐ5iA-Ձ+0=q^) ZJjj^{n!VΜbz%Q1ʂA)H56j˩= Q}&t~DY.I]5inQl #Q"FnJaj}ayy<ǖQ%. L6|"j$}$b$U?ܷR\|&LI5ǫ>l}>/azuNXF%qgO;:#2a$Bw7 )35U(2rbxﮇbCj7qBE$uS~)!PK"4&e<;<h5DJթ:H"c3I3# k v;Ԛ%IaW"ZTF Y;`N$S]&OIsFS*FVf'dӨϴ*^W&6SJa)4fƔeuk[0 $-Cj.0oSRZ0Y64+qKTޖH iwxD"W $yb#MGN>#JF|+ Fפ-|<ҋJYج`V*3 +忇u"'/JvHwb `U__4{"Ek[Ƙ:8Ve-)%-2=*QrXW0}U!9 yQNf&er\\3Ѡ`.6"' Dkh,oOis"N`/GDn V$$3=w32X×ĕKҟl)RZSڵVD[L~p8PI(1aTwdѲZ#$ƲN%c]YDH7gU=c38rQW~$؆Yy֢3VfD\#JRD{HrZ]qBzl Gf:J+a8 <ä3C.3EQҧQ#5m";ẌvNT2#SۑQFI fCn(4mm5ukU1>S?W&5j'z>3U AM؍Gҕ#?af~7Rn-L/q7[ѡK_Q[3sd\*MzYOENx3h*Z&2_ $9p nl[K`0+ѓ% t I͏s'?K6S{ a'u_1%d$4ZYYHZnG* Nw2qէ$gs{]*96c7cĦi8#sW8 Dg8$$+ 7ViWW/>|Xq.R&WeI܎frK~;?+4Iۋٌ]K^{Iuh[!JEi_}xYW{q1Vif/n?f3w9쏋kpڳK3?W{lvcVif㺧meoz%תݩSԒgEnBXthW$ȕ3˜|ܞDט'VL;xK5Cf?,s{lc}T5W<5s/uBIIn"oc1ig6;k׽2qkde*;o$KI|SY4FQ4&<~lvm=۞eFשp諾.maK:]i{nmzrGrI]::3Ѷ XeziC۞;g_˷=}moOZmYKaqs4e9 ɏBvM* 4ㆅRnv},voYFfޏ]^Bqq#$֒o&.I-NZ'Jlwi+n-5_}۞;g_˷=}I9Mtc>YFf۞6&W}E]Be6\Xud \3lɪěq6;}FYK]~qοnz60諾.ma7C.Jm*?eQpMxJөߊЋXT妺X󏾖usѷه},vo I9Mtc>YFf۞6&W-5_Zk?8g_˷=}wοnz60_]_K]9i> 諾.maK:]mn-u~r],}u@妺X󏾖usѷه},vo I9Mtc>YFf۞6&W 4XA6 i"%)&DECMt},voYFfkqksc-5_}۞;g_˷=}mMŮZkK]~qοnz60諾.ma7G9i> KaxG&!;= SS.BMQ݃]۞;g_˷=}mMŮTϧs Kqȕ)(5pw4FEGcMt},voYFfkqksc-5_}۞;g_˷=}mMŮZkK]~qοnz60諾.ma7G9i> r],}uAK:]þusѷنZ妺XMt},voYFfkqksc-5_}۞;g_˷=}mMŮZkK]~l9i%JD3]7>[M,A+qSe5TfK]j5yqCUmQJ\yK4[M,@-f ͗^=thlU/b5zkl)B lmř5n#>kwϖ} ?;i#L~C,ȎYi+-PKB- wϖ} ?48M6N4i0YH GIW#n|Y7>Z,AF#1 ssB9og )Hok7гn|YLB*8*[ 'S0$kȄ`,JftZ9PQsFiiHJLji'- wϖ} ?4$UM5F*G'5]F 5gj)LOz|N3FZ#ٽHm^Sh4wϖ} ?;Y"-Nb!6Θߖc-5_ssB9og ?Mi> r],}uA7>[M,@-f yO~ZkK]~dwϖ} ?;Ygߖc-5_ssB9og7妺XMtGk7гn|Yy?Mi> r],}uA7>Z,@-f yO~ZkK]~dwϖ} ?;Ygߖc-5_ssBGs7̬35\Ŭ@c$sYv;YH9{uI Mlģ-&33BE%quG\uM:m+Jd(FZn32# ZR!mlsF*4R4I4#ٿ)jP$΢Ti,:a(KO'bԣI؛mMW3˘+77.I6c>R L+Q%ۑi;`b ,D^m8ڐM>[JRot^m'+ƦIM=ʕ*)e-\*C³]49VV*ѤRYqI2 6Z3BJwѨ)ǓN)HJ>RN):mwIW$*ē33 VQ*T-ɔChqI]DS_ټtsɡ`xz**2v412;m(;\J2MI2?]6*{}&B.:2xB+^3\e"rs0F%K0xs4MF$Zl[ok)qM5OeǦHf;l"c uQ%^uI-u1OU>l곘b%9Qg rg' FF `ɣ5Lb7Vv]Z]J)m-B&-e|!jdIE#]xBtџ9r05EV*LhXeqFJN2ew!$MZH6-^$U:QWa$%4ФRJfQ]+"2CR+v;)tmdJUEOwُ(@82\T&Cf;hB%:hҳUe+^+&X1N\x `5b*ѵ:MGr6\;ۤ}q7 RhQYPoÜ/MS4JI-O%*OLg|+ϛ-p5Md2*$fR$Gd+(dge'Wզ/X5ZU3 *8.9 kVFզ#=GoL͚ kG;KnԊsS-a--MdGmZ>Of W6K8J@Q9#}:63dJIZu\ۊ:~YWi.ә-y:9Hd-%86rJo͌q8feMs*): 6[-&i$/QۊdMd\b|Z3TL^F<]> n-؍NYDdg$V+s ZRuDn|ԕ$ҥOIݳ`Y.׏)ǟO5*QiϜ8[ƶF>?&QzO"IQGb"[ŤDV-UuI+qMk4'Q9)lƱoHZn\1SNr_o6T\&]9jx҄wMm"Mi4QTfnGy[qjWwtbjb;&1źy4b|0!="OH>WEðC)ǟZnL)V".s2ݼ0cQV9CJ\Rf%RDF{5DfW#aXM?Es4A+Y{lG w] Ws~#c8N^z|eƑ*\Vʑ3Rg2KCnkzMw3> ~)x6)ev~OKgQ]$5h#2='>rBTb$Q Vaַ9EJĨb-H}[jѥZMZIFMXIhsO$/Rq8y+uki(ICi"Qu&-{Bɼ{0+c=\8+h"#2+i"#>ka2.uM1 '(JDf[D`)`!?4HYRW$i4Zw#&Zn͇kBpjْq%Թ* u\BeJI%Imgs#24H=je"(D棩ƥDT'nFdrc,1W;Xfb\ O]_Y7}fmmq[2ÔmfB\;0[CJ\y2šL"h~F[a/0ĪET eVLiRv2#++{}xrܥx-\&_ӻ8¬=jҰlJQ'Є\҂؝!<3WH*,Z&Y'JfIYˤpU_~XFf"L؋/Ù?ĺm~esl#<L gC#NU)ZM2CIb :-el8)N|n☻#9jOȹSLΉǟO 6!KNhuڴM].FwQQIixPqU2K%r_[cW'_⚩W,~Jp~^_02+bLIT"rrZR ;/JA#(˧Y[x<|kGbJIeS+xcOs׬gs;Nlه*i(m*Gr*c k뚣i ш8tb;,SъNWwCMLG|!$?8O& @2BO@'A bŔ# ʬ>r^ĴjR("3`Pj=VjK]9fKvAEV#=ť&wШ1rQd97䡤o\5(eb$f;s# S|ݨNQRItz|^7(N=6RlUMfX)/-ul)t 3-HġMԉT\ tS"U9ƐqTHmթE_;3@&R;%Ke(KQ$3VIbKsOX̶I4M),MuN)$3ԅض[m.>j:V7UnP%đ3'L̇;`zgb|b*TX*/G~{W;L 2CRCKNQKIFQ+.2FamH$k_5i.Tv<>а,9ܣv <֒dfF[RfGq3iMVc!0|:J:s6fF"?)LysC%Ĉr,Ԫ#P'n:fI8dIQ9]eYiHC6fV%>21a:$9j' MLŕ25${Dg}ו82]R͚aEQĩ2ѵ%g̭'tVkCdDHu&j{2Tp N9JhM1"}-:ڐ)K#Zvn/uظ3ʧ0 b܍"R YijIKa$fF+p̊]7ub(ÎՎLHKj}}("+[F䍌3i2_TxAN&iRyTZG I^׹[x9luLTh+7>cJN9%Jy 6Q$u3c)RK'(=Gw;~ 9r4noaL0l"ZCIJJ)i3++H^V9Tk"]8EM jDdW׋[m;ͣ,$9mpnYÒ\eLѫv{x[N2bCj6 fdoQI(oozS-R*raT-Eo?(Pp? U[R? q(qZ3-7܎/6.PB+BƓ-F\/~/Cg!M(OSquf&Z={9!j3SKI_KHD^<& ^<& !.OpU_~XzGU~d ['pH5w l5ryFV_Ƭc?Kkdo1ӵn'=RN|j7Ը1]X_"|_uT;#=c\OhG}f:1(=妧p|f|zb\/h#-QǪTȫmfG{@|ez $?8O TԲ>Cuez ^+c2GK,7\fW{@/q^>a]]ԲCuezn̯UZm#Wy5,/q^8Cuez iɩeq|\/h3+pVHMK/7\fW{@/q^GjY\_!2W 7\fW{@@6뫼_n̯U ^+BHMK+7\fW{@q|\/h=uwR ^'7\fW{@6뫼_n̯U ^+^]]ԲCuezn̯UZm#Wy5,/q^3+pVHMK+7\fW{@q|\/h=uwR ^'7\fW{@iɩe|\/h3+pVHMK/7\fW{@/q^HMK/7\fW{@q|\/h=uwR ^#7\fW{@=uwR( ^#7\fW{@@m#Wy5,/q^8Cuez iɩe|\/h/q^ z&3+pOn̯UZm#Wy5,/q^8Cuez @m#Wy5,/q^3+pV 6뫼_n̯U_!2W j]]Բ>Cuez ^+bHMK/7\fW{@/q^HMK+7\fW{@q|\/h=uwR ^_!2W h`a]]ԲCuezn̯UZm#Wy5,/q^8Cuez iɩe|\/h3+pVHMK/7\fW{@/q^HMK+7\fW{@q|\/h=uwR ^_!2W j]]ԲCuezn̯UZm#Wy5,/q^3+pVHMM.,8U .1%aUD6*WM^nfw'pá 3E%CL,b0Ƈ,4o8o,4o8cWߖ;ޑU}cx_/ECG#p6R7 lÿeB[#q\qƮO8Qkdo1kdo1ղ[\|f>0+j0F# 11wq4w12Lcti 1$$ >aBzDzD @'@az H@$9@Da'A !H@ '@D KHPt?}$=49e׏>}|49e׏>}KWߖ;ޑU}cx_/ECG#p6R7 lÿeB[#q\qƮO8Qkdo1kdo1ղ[\|f>0+j0F# 11wq4w12Lcti 1$$ >aBzDzD @'@az H@$t8 Ո=EIDggGo,)5mI\{FgqUsq;zc'&M=fBjpvQucɌLN ah4J BEZRgF#3"\mPu^LbmmDŽ!+h&%f g2D 5O0 $  H"%CL(Q}w |:>kǟDѾkǟDѾ% Hા+5Ũѐ-Q 'ɶ$lBtjxF(ySD4tGyd PYO0 $  H"%CL(Q}w |:>kǟDѾkǟDѾ% HાkǟDѾkǟDѾ% HાkǟDѾkǟDѾ% HાA3drw4n8%\cXвqGҧV8 & ڵXf\||aRA.6巒\ALr#\XXRLgq'Bq&ŌD:ə/}H%m$ene2Lʏr>`Ǭ)ZkNɹ+p[33#2/߉VsK5O^BSިԥvJ]j?I#3>b#N?TUq^O8:Jq(R.ٔE*HJTu "RM*+LH̅)B3_/XA*8B|1ӬȓscUS1D(!bT\-%Ȇ&ZB45lmÙxr^|aU[:vIL[m{sm"χ#/%6{3ڥIT(& ֋տvٽZMՌ¹U)fE&*MCDZ3!9c++q,a{3S Yu̗4 +IF\J]imPum#lYgb+XոzaW Eb=T6?oG"35JZ̜"YMDgcaZS>UFŏ1J%!Uƥ5vIF[Ma5qS8_dxza|{W:%+;E ĸ肹v4BQ{vmO fe®$*u.1 $ T;\ȷnynqNJ}|49e׏>}KWߖ;ޑU}cx_/ECG#p6R7 lÿeB[#q\qƮO8Qkdo1kdo1ղ[\|f>0+j0F# 11wq4w12Lcti 1$$ >agR)(-\f͵-J"23MW֥ۜbLSVSeuIM} i[5Ffds39a GkZQQ:.Ǧp*-.4'GաKԥjQ2Vlk֏Zku*9I).Oġ.4fo{$j;\( DL'.$XV¢ >"]5)fl3؄2I܋ep̬Ę&C&KFF᠌c2{.vp 'ѝX2`KP&! Qu[<ˉZҴ#J_uXaa7ES!TqDnKnm3Q^mj@MFAxa<Ԥ(,l]^mKxerN>Ӫ]@xtE|̯w[ѣc 3-3N.ѡ ]]&f[Hcȭه+r#UԸ=]"7Q}L8[56ض{Eۇkr[>pB=q:kRSr"efO$TWNӺOe| +U@DVx=tQ̵z2;=1|;0pbmA)1# 1%,$W:oNvƹɆ6PZBj"~c&j;ZyyLئg>^ӆjzJnLuF)}W*b!cՌc&O-Kf%:}CXmĨLpI$6za50))'@܀O@=IBȦϝLl07~ rI22 U1TLLy&&bs ʽZXUzʌN}NFfI3Lah4 s2D a'A !H@ '@D KHPt?}$=49e׏>}|49e׏>}KWߖ;ޑU}cx_/ECG#p6R7 lÿeB[#q\qƮO8Qkdo1kdo1ղ[\|f>0+j0F# 11wq4w12Lcti 1$$ >aBzDzD @'@az H@$9@Da'A !H@ '@D KHPt?}$=49e׏>}|49e׏>}KWߖ;ޑU}cx_/ECG#p6R7 lÿeB[#q\qƮO8Qkdo1kdo1ղ[\|f>0+j0F# 11wq4w12Lcti 1$$ >aBzDzD @'@az H@$9@Da'A !H@ '@D KHPt?}$=49e׏>}|49e׏>}KWߖ;ޑU}cx_/ECG#p6R7 lÿeB[#q\qƮO8Qkdo1kdo1ղ[\|f>0+j0F# 11wq4w12Lcti˒e20ܥaunNr019)/ImJ#B763"#31MF)Ô^TXsb#ke,ẝj7RFi;$j񯤢!}&IS ƕz@[4hqV-T)m\);438\UvVqj4."x 7%TD4IgrPRBYR6{3((I$I/Es=fv;XEUbtMf"4Weu(-V#3MQ18]>ys3v$aǦDLҢ8ѣDfZ)f^QGG}T0ldWr*irbܨ%8[k"'JfF##b|ؠJz/ê?EĔRQd4/CJ+ZL˃B׹mGʿp/+XG *4#>]B3q6M=J[a &|aX C0hZ܎۷27k32Q[ac}ϹUW#Pcg_}.Cvbګc|S*M09ʩ԰F˜euZN^"WIj#Ijs `6NNi1ZZ#% M^1ZmK;Dv?Xn*D9R'!5I$A%Zf#Im2s fn8[5],E\\H< FeKm7 FI]-s;k8R ѫfonj65jR3ЃZLc"0r!="OHY@r`+XTQ#Q!DF-K;Dv"oiQ0f)DFJrM6|&Kp)$(ȕ$H1}&W!c ; /NgUeiۊ +I7Ch;lӕeb bMpPhM=EDZIxH hm`kzQEMʥE&C4A% 龍jaG2g_r_b-h_BijS-k2Oݴc9Ia UMEbp1D`) NIK-j3rHy_.(ຮ(T,W9 T\f]y$uJ2Ro?u8s~\|[޸ _ h0K<%WJveV#ʿ>=Z8dGBRƲSi=3O-D4r1Ѫ8[ի,'!g\*%m+'BI(<(A"'䚴w*D*QFk#U*2vyEw<2 LAņ㶶SJu*ֳI'ƾFT33 <<*"11!9md#IևoXJڮWIjV܄s0 H;U*i)1[0)-ے2i=ؤpþ^3=MEMl= CjnJR-)QZK`ms,=ML*UM^IJ^B]6@jw Kȣc>;.TRKfq,ܶUH=$gm,J~r&?𻎠dxEpYhٞV;MB;K)eP`VeDFg2Ɲ\+C1pD K e9F, ZS--)ʹ֫$;wX[+X2"PnWYmfJi9rd `4[-v+ A|3N7CSf崄KdRŴNڲ3!bG<+WƔJÙ(jd7t’ȞhHkA%r;\QNW PJd6v!F]ZTޕֱrR<:|BxVsLmM";DWW35 v-:#Aquk\; I!%Ѱj33Vye\DØW販mG$nnɵkΩQڀ _]_4ϺBpá 3!1/yMN/yMNB\Hા z-?*9NᲑkd*j󍤍5ryVmd[#y[#y:1[Vwe1hh`CvR!.y fI%)DE{smӣ3?$]Sk3%GmuRKmG&W##&de?ӿ2|+ dB[ҡ1'.KGIB)#tvJŸ6%YkB'\iKmuLB$ZlG{Ȍ4oe 'ٚ}LdC&69,SU$T&d!q|)$̖l%Wguwkc)P_ayhCDVq$8;fRǃfi2l>_{!M,c!֖-kRLF٨|jSDD(si-lfKDF[ 8ٸ.&JaI;)&D~ Od Od?IC"DmU&!H&tԍH%Ao䙕/.9i۝6ْrJJv:.jIFRv=?:3O,<3O,~a+?_{ l>_{!9kWÔ+rIpux1$Hkm˽XNq8xv㚍̨FY"mřH2N[. Od?o;$Csb3n8.4n.4^ŧRiQyIaWa3+U)2cn[, ȷl"3;_'_{ l>_{!IǘZ&4u٩IwSJq Z IIT"=FfdC`$" [y#Y-53"tk#3\{E>U!EB<3?Oy=%*R Jqi8/PUMH(sTQri(FCU2?a\/?V !|OHuemhIZv>c'15Wњt-mJOHmDW4*y{d>R>igS 3OuƢGrGu-q%:+6)C^ Od Od?D+ kEGTԢSLWf?vou(6{E&>&$q٨;=shY ȔDfdMWdW#223e 'e 'PytklBʔf/ ̌q13gL[2eB$6u-JN=_{ l>_{!j?23O,<3O,~̕w7Nʤ(Ɠp?C{-mBԓv #<0 [i.j]K3ԕ-lzV$c2 (qq 41 r&iq 7KBnVmW2_y |S'2ԃe!m][bI+Wy-qRu-Fb:DW>b""/C#~Y Bl>_{ l>_{!Fh#.I%1`7"k.7kdJ̼"ىEnY/[j6E.>.@ԶAm6j"iعdzt`\]UITm.k)D+riQl2Lde[*Ld (.4܄-#ZNmdJB7#"2)97ҰL&4%Z纴7hA.kXh3. :G`_љ3Kc?Hx0|;r<5CqLhD\V7+y BJI:qmV򚎷:HJ'cR#"RaoKc?Hgc) *&%y%Zj:J#A(MJ"33+܌*W17qZcfՋBiZ5XS/;^Ld%"3ImDFer#o#"{~ Q0&$2BD //Cg!BKHD^<& ^<& !.OpU_~XzGU~d ['pH5w l5ryFxr=kG#6KďGcZkUjkr#NRZ OS/R\4ZPZJ"QzvxΜ2̊%7q!?ôy\Y'uvNJiǏIjw#6 xr=i*Sp_T~RZQΛMvRTiIIQm.µUaJ ;eR9,5)S{MzQގD~6N&C|WؚT|1*fNc*T֋;%8VKBo8r=i<9x^x?(]*xI*z:٥NBB"AOApj'uv_Q13>x8T(o“ RZBς"2-)|9z^|Zͦ"IvBN-UU99$MQ?G2qHRHeBlV5w-mpTw5iD<o۱cՙ{Y]Dez\|2<9t"buE Kح&y$Ȏ\6&JmhdZRjIƥJJZXa;GW!AoI\^C| 2Rk^RB j@lff1cf&5GSt%U.JED䮓ldnHm)BM)Ij3ѷ >FLyt˥N.EMi2-DN)%6Rm$O<-ژ9~1F\Qf]{ FBBY6n6L6+#]KSd%Yp^ZQe o𴢞M[D% hQZOIiLq1ڼ9|?XNYeFUYm8FSͷ=iy(BDJe=YPS0Q$Yi"ZDy2ɫKqlI]&s9O鉚Ø~XU/HՊq}>Ku&)efD&Qu4R Efx8m&K[:D\e*y|eT2wTsǟYyF0>DQpGfPY SiTɩqGF!DFp̈CU+ƍA()VUĊQi)RwI艡tfdT=MqCh֢q*u1J&InT&r&%eNZ+ZK2-&.v`SݩTۈVyN[i[ҒImY%DfVNxMqLULrzO1~Yelo%(fdFdI2cD(vt,~Zŧ@b/%m\\dD\dEfZ̙Mt-{npKY(u+n܏Xm2!d`Aaq֣3333$ {u*@q,s/a+p6x'I+RwW$62>EęT :3QFtQ*uf[vCKN~)ëJTg2LEL2i3-E:;{n7 Fw^7?<(g>ʪresV8oHx}QZ8Kʈ {'e<_{ j 4z$ #G p-M&r"tlD5ҕt1JKR13$nH"Y7CDVlI嶵Џ__TaEm^ƒ2]thm 5)V(fgb#;휛]8°&J#m,ҕTE=+J|#d52 Hc!C2P'!JuFJqN)zJA?$tUE\UbHjؤ4l SdI4k%mt ~쏉WVu Jl*g+CX?ƙ샦 NkتJUy<h> Y\MDJ;o+vB~WUJy }z/y }r_p觡z.{5?WRA?SWzb:c3*|0}[J1s=I{N ʜ>{SWJ17&b߃ {x;`ްWS~7&azo^.@ oUҽ<oXM]+M;`ްW {2)l}WJ0va7tc E7zo^7&bȦoXM]+Uҽ\߃ {x;`ްW"va7tcl}WJ1rdS~7&azo^.@ oUҽ<oXM]+M;`ްW {2)l}WJ0va7tc E7zo^7&bȦoXM]+Uҽ\߃ {x;`ްW"va7tcl}WJ1rdS~7&azo^.@ oUҽ<oXM]+M;`ްW {2)l}WJ0va7tc E7zo^7&bȦoXM]+Uҽ\߃ {x;`ްW"va7tcl}WJ1rdS~7&azo^.@ oUҽ<oXM]+M;`ްW {2)l}WJ0va7tc E7zo^7&bȦoXM]+Uҽ\߃ {x;`ްW"va7tcl}WJ1rdS~7&azo^.@ oUҽ<oXM]+M;`ްW {2)l}WJ0va7tc E7zo^7&bȡ31-ƍ-Ңҫˁ4*]<І;m7cfzVW@Q򶍅g7 Lxj})"\)d/(R~ ?,pfNqߪ3y* Integrating MRTG data into Xymon

Integrating MRTG data into Xymon

This document describes one way of integrating MRTG graphs into Xymon. It's simple, doesn't require any additional scripts, and provides all of your MRTG graphs as part of the "trends" column that is already present for all hosts in Xymon.

Another way of doing this is the bb-mrtg.pl script. This is an extension script that gives you some more options for controlling where the graphs show up, and also lets you generate alerts based on data collected by MRTG.

Simple Xymon-MRTG support

MRTG by default uses its own fileformat for the data files, and continuously generates PNG- or GIF-images of the data. This is a waste of ressources - most of the time, these images are never seen. This was in fact one of the reasons that RRDtool was developed, to separate the data-collection from the graph generation.

Xymon uses the RRDtool format for all of its data. You can configure MRTG to save data using the RRDtool data format, instead of the default MRTG log-file format. This lets your MRTG save the data directly into the Xymon RRD directory, in the same format that all of the other Xymon RRD files use. You can then use the normal Xymon graph tools to view the graphs.

To configure MRTG to use the RRDtool format, you must setup the mrtg.cfg file like this at the top of the file:

# For Xymon integration
WorkDir: /usr/local/xymon/data/rrd
LogFormat: rrdtool

Note that the WorkDir setting points to the top-level RRD directory, i.e. the one defined via the XYMONRRDS setting in xymonserver.cfg. The Logformat: rrdtool makes MRTG save data using the RRDtool data format.

Each of the network interfaces you monitor have a target-definition in the mrtg.cfg file. You need to modify this slightly, to make it save the RRD data file in a subdirectory matching the hostname you have in the hosts.cfg file, and with a filename that begins with "mrtg.". Like this:

Target[mrtg.myrouter.eth0]: /10.0.0.1:public@myrouter.sample.com:
Directory[mrtg.myrouter.eth0]: myrouter.sample.com

This defines an MRTG target, where it monitors the interface on myrouter.sample.com that has the IP-address 10.0.0.1. It uses the community name public to query the SNMP daemon on the router.

The Directory[mrtg.myrouter.eth0]: myrouter.sample.com instructs MRTG to save the data file in this directory relative to the WorkDir directory, i.e. the final directory for the RRD datafile will be /usr/local/xymon/data/rrd/myrouter.sample.com which is where Xymon expects all of the RRD-files for the myrouter.sample.com host to be. The name of the RRD data-file will be mrtg.myrouter.eth0.rrd - i.e. the name of the target.

The reason for naming the data file mrtg.*.rrd is that the showgraph tool has a built-in definition for generating graphs from this type of files. So if you stick to this naming convention, the graphs will automatically show up on the Xymon "trends" page. If you have more than one device that you collect data from, you'll need to modify this; you can use any name for the target as long as it is of the form mrtg.*.DEVICE - i.e. first "mrtg.", then some random text (e.g. the hostname), then a dot and the device-name. The device-name is used as a legend on the graphs, so you probably want to make this something recognizable, like the name of the network interface, or some sensible description like "DSL", "LAN", "T1" or whatever you know your devices as. Note the MRTG converts this to lower-case.

Here is the full mrtg.cfg configuration used to track traffic on my Internet gateway (currently a 4 Mbit/512 Kbit ADSL). Note that even though MRTG does not use the Title and MaxBytes settings, they are required - MRTG will not run without them:

# For Xymon integration
WorkDir: /var/lib/xymon/rrd
LogFormat: rrdtool

# The external interface on my router
Directory[mrtg.fenris.dsl]: fenris.hswn.dk
Target[mrtg.fenris.dsl]: /80.62.63.88:public@fenris:
Title[mrtg.fenris.dsl]: Traffic Analysis for External DSL
MaxBytes1[mrtg.fenris.dsl]: 500000
MaxBytes2[mrtg.fenris.dsl]: 62500

# The internal interface on my router
Directory[mrtg.fenris.lan]: fenris.hswn.dk
Target[mrtg.fenris.lan]: /10.0.0.1:public@fenris:
Title[mrtg.fenris.lan]: Traffic Analysis for internal LAN
MaxBytes[mrtg.fenris.lan]: 1250000

With this setup, I have the MRTG graphs readily available on the "trends" page, together with all of the other Xymon graphs.

Running the MRTG data collector from xymonlaunch

Normally there is a cron job that runs the mrtg command every 5 minutes to collect the MRTG data. But you can run it from xymonlaunch - this also has the benefit that the RRD files will be owned by the xymon user.

All that is needed is to add a section for MRTG to Xymon's tasks.cfg file. Mine looks like this:

[mrtg]
	CMD /usr/bin/mrtg --lock-file $XYMONSERVERLOGS/mrtg.lock /etc/mrtg.cfg
	INTERVAL 5m
	LOGFILE $XYMONSERVERLOGS/mrtg.log

Some Linux distributions setup MRTG with the expectation that it will always be run by the root user. So you may have to change permissions on some files and directories e.g. to permit the xymon user to read the mrtg.cfg file. Check the mrtg.log file for errors.

xymon-4.3.7/docs/howtograph.html0000664000175000017500000003302311535462534016234 0ustar henrikhenrik How to setup custom graphs

How to setup custom graphs

This document walks you through the setup of custom graphs in your Xymon installation. Although Xymon comes with pre-defined setups for a lot of common types of graphs, it is also extensible allowing you to add your own tests. For many kinds of tests, it is nice to view them over a period of time in a graph - this document tells you how to do that.

The NCV (Name-Colon-Value) and SPLITNCV methods

In many cases it is quite trivial to collect some data and send them to Xymon in format like this:

  Name: Value

e.g. if you have a device that collects weather information, it might look like

  Temperature: 19.2
  Humidity: 53
  Wind: 9.6

In Xymon, this is known as NCV - Name, Colon, Value - formatted data, and Xymon has built-in support to pick up data formatted this way, and collect the data for graphs.

Make a script to collect the data

First create your test data. Typically, this is an extension script that sends in some data to Xymon, using a status or data command. If you use status, it will show up as a separate column on the display, with a green/yellow/red color that can trigger alerts. If you use data, Xymon just collects the data into a graph - you must go to the trends column to see the graph. For this example, we'll use status.

So we create an extension script. Here is an example script; it picks two numbers out of the Linux kernel's memory statistics, and reports these to Xymon.


	#!/bin/sh

	cat /proc/slabinfo | \
	   egrep "^dentry_cache|^inode_cache" | \
	      awk '{print $1 " : " $3*$4}' >/tmp/slab.txt

	$XYMON $XYMSRV "status $MACHINE.slab green `date`

	`cat /tmp/slab.txt`
	"

	exit 0

Get xymonlaunch to run the script

Save this script in ~xymon/client/ext/slab, and add a section to the ~xymon/client/etc/clientlaunch.cfg to run it every 5 minutes:


	[slabinfo]
        	ENVFILE /usr/lib/xymon/client/etc/xymonclient.cfg
	        CMD /usr/lib/xymon/client/ext/slab
		INTERVAL 5m
(On the Xymon server itself, you must add this to the file ~xymon/server/etc/tasks.cfg)

Check that the script data arrives in Xymon

After a few minutes, a slab column should appear on your Xymon view of this host, with the data it reports. The output looks like this:


	Sun Nov 20 09:03:44 CET 2005

	inode_cache : 330624
	dentry_cache : 40891068

Arrange for the data to be collected into an RRD file

This is obviously a name-colon-value formatted report, so we'll use the NCV module in Xymon to handle it. Xymon will find two datasets here: The first will be called inodecache, and the second dentrycache (note that Xymon strips off any part of the name that is not a letter or a number; Xymon also limits the length of the dataset name to 19 letters max. since RRD will not handle longer names). To enable this, on the Xymon server edit the ~xymon/server/etc/xymonserver.cfg file. The TEST2RRD setting defines how Xymon tests (status columns) map to RRD datafiles. So you add the new test to this setting, by adding slab=ncv at the end:


TEST2RRD="cpu=la,disk,<...lots more stuff...>,xymond,mysql=ncv,slab=ncv"

slab is the status column name, and =ncv is a token that tells Xymon to send these data through the built-in NCV module.

By default, the Xymon NCV module expects data to be some sort of counter, e.g. number of bytes sent over a network - it uses the RRD DERIVE datatype by default, which is for data that is continuously increasing in value. Some data are not like that - the data in our test script is not - and for those data you'll have to make an extra setting to tell Xymon what RRD data type to use. The RRDtool rrdcreate(1) man-page has a detailed description of the various RRD datatypes. It is available online at http://www.mrtg.org/rrdtool/doc/rrdcreate.en.html

Our test script provides data that goes up and down in value (it is the number of bytes of memory used for a Linux kernel bufffer), and for that kind of data we'll use the RRD GAUGE datatype. So we add an extra setting to xymonserver.cfg:


	NCV_slab="inodecache:GAUGE,dentrycache:GAUGE"

This tells the xymond_rrd module that it should create an RRD file with two datasets of type GAUGE instead of the default (DERIVE). The setting must be named NCV_<columnname>.

In some cases it can be useful to use multiple RRD files - one for each dataset - instead of putting all of the datasets into one RRD file. E.g. if you want to add or remove datasets over time - if they are all stored in one RRD file then you have to dump, modify and reload the data from the RRD file. If you store each dataset in a separate file, then you can just delete the file. Xymon supports this if you call this setting SPLITNCV instead of NCV. Then Xymon will store each dataset in an RRD file column,dataset.rrd, e.g. slab,inodecache.rrd, and the name of the dataset will be "lambda". Apart from this, the setup is identical for the NCV- and SPLITNCV-methods.

The xymonserver.cfg file is not reloaded automatically, so you must restart Xymon after making these changes. Or at least, kill the xymond_rrd processes (there are usually two) - xymonlaunch will automatically restart them, and they will then pick up the new settings.

Check that the RRD collects data

The next time the slab status is updated, Xymon will begin to collect the data. You can check this by looking for the slab.rrd file in the ~xymon/data/rrd/HOSTNAME/ directory. If you want to check the data it collects, the rrdtool dump ~xymon/data/rrd/HOSTNAME/slab.rrd will tell you what it got:


	<!-- Round Robin Database Dump -->
	<rrd>
		<version> 0001 </version>
		<step> 300 </step> <!-- Seconds -->
		<lastupdate> 1132474725 </lastupdate> <!-- 2005-11-20 09:18:45 CET -->

		<ds>
			<name> inodecache </name>
RRD datatype------>	<type> GAUGE </type>
			<minimal_heartbeat> 600 </minimal_heartbeat>
			<min> 0.0000000000e+00 </min>
			<max> NaN </max>

			<!-- PDP Status -->
current value----->	<last_ds> 330624 </last_ds>
			<value> 0.0000000000e+00 </value>
			<unknown_sec> 0 </unknown_sec>
		</ds>

If you go and look at the status page for the slab column, you should not see any graph yet, but a link to xymon graph ncv:slab. One final step is missing.

Setup a graph definition

The final step is to tell Xymon how to create a graph from the data in the RRD file. This is done in the ~xymon/server/etc/graphs.cfg file.


	[slab]
		TITLE Slab info
		YAXIS Bytes
		DEF:inode=slab.rrd:inodecache:AVERAGE
		DEF:dentry=slab.rrd:dentrycache:AVERAGE
		LINE2:inode#00CCCC:Inode cache
		LINE2:dentry#FF0000:Dentry cache
		COMMENT:\n
		GPRINT:inode:LAST:Inode cache \: %5.1lf%s (cur)
		GPRINT:inode:MAX: \: %5.1lf%s (max)
		GPRINT:inode:MIN: \: %5.1lf%s (min)
		GPRINT:inode:AVERAGE: \: %5.1lf%s (avg)\n
		GPRINT:dentry:LAST:Dentry cache\: %5.1lf%s (cur)
		GPRINT:dentry:MAX: \: %5.1lf%s (max)
		GPRINT:dentry:MIN: \: %5.1lf%s (min)
		GPRINT:dentry:AVERAGE: \: %5.1lf%s (avg)\n

[slab] is the name of this graph, and it must match the name of your status column if you want the graph to appear together with the status. The TITLE and YAXIS settings define the graph title and the legend on the Y-axis. The rest are definitions for the rrdgraph(1) tool - you should read the RRDtool docs if you want to know in detail how it works. For now, all you need to know is that you must pick out the data you want from the RRD file with a DEF line, like


		DEF:inode=slab.rrd:inodecache:AVERAGE
which gives you an "inode" definition that has the value from the inodecache dataset in the slab.rrd file. This is then used to draw a line on the graph:

		LINE2:inode#00CCCC:Inode cache
The line gets the color #00CCCC (red-green-blue), which is a light greenish-blue color. Note that you can have several lines in one graph, if it makes sense to compare them. You can also use other types of visual effects, e.g. stack values on top of each other (like the vmstat graphs do) - this is described in the rrdgraph man-page. An online version is at http://www.mrtg.org/rrdtool/doc/rrdgraph.en.html.

The GPRINT lines at the end of the graph definition also uses the inode value to print a summary line showing the current, maximum, minimum and average values from the data that has been collected.

Once you have added this section to graphs.cfg, refresh the status page in your browser, and the graph should show up.

Add the graph to the collection of graphs on the trends column

If you want the graph included with the other graphs on the trends column, you must add it to the GRAPHS setting in the ~xymon/server/etc/xymonserver.cfg file.


	GRAPHS="la,disk,<... lots more ...>,xymonproxy,xymond,slab"
Save the file, and when you click on the trends column you should see the slab graph at the bottom of the page.

Common problems and pitfalls

If your graph nearly always shows 0

You probably used the wrong RRD datatype for your data - see step 4. By default, the RRD file expects data that is increasing constantly; if you are tracking some data that just varies up and down, you must use the RRD GAUGE datatype. Note that when you change the RRD datatype, you must delete any existing RRD files - the RRD datatype is defined when the RRD file is created, and cannot be changed on the fly.

No graph on the status page, but OK on the trends page

Make sure you have ncv listed in the GRAPHS setting in xymonserver.cfg. (Don't ask why - just take my word that it must be there).

More advanced: Sending a "trends" message

The NCV method works fine in many cases, but you may run into a situation where it isn't really suitable. One possible problem with this method is that you can only store data in a single RRD file using the NCV method, and there may be situations where you want to use multiple RRD files for flexibility - e.g. if you are reporting performance for a number of applications, then it is useful to have one RRD file for each application. And since there are different performance metrics for each application, you cannot use the SPLITNCV method.

Xymon supports a different method for collecting data in these cases - you can send a "trends" message to Xymon with the data you want to put into a graph. All hosts appearing on the Xymon server already have a "trends" status column, but if you send a "trends" status message to Xymon, it will not show up in the "trends" column - instead, it will be used to create/update an RRD file with data. And inside the "trends" message, you can define exactly what RRD files you want to use, how they are formatted, what data goes into the RRD file and so on. Here is an example of a "trends" message, using the same data that we used in the example above for collecting some data about the current weather:

data berlin.trends
[weather.rrd]
DS:temperature:GAUGE:600:U:U 19.2
DS:humidity:GAUGE:600:0:U 72
DS:wind:GAUGE:600:0:U 9.6

This creates an RRD file for the host "berlin", called "weather.rrd". The RRD file has three datasets: temperature, humidity and wind, all of which are of the GAUGE datatype. For more information about the DS definitions, see the .I rrdcreate(1) manpage. The current values for each of the datasets is specified after the DS definition.

If the RRD file already exists, then it just updates the data.

You still need to create graph definitions on the Xymon server, and if you want the trend graphs displayed on the "trends" status page then you must also add the graph name to the "TEST2RRD" and "GRAPHS" settings in .I xymonserver.cfg(5) but it is not necessary to restart the xymond_rrd program.

If you want to create more than one RRD file, you just add more data in the "trends" status message. E.g. if you have three applications with performance metrics:

data appserver.trends
[salaryapp.rrd]
DS:usercount:GAUGE:600:0:U 210
DS:payrollgeneration:GAUGE:600:0:U 29
[bulkmailapp.rrd]
DS:customercount:GAUGE:600:0:U 1922
DS:mailsize:GAUGE:600:0:U 61293
DS:transmissiontime:GAUGE:600:0:U 88

This will create 2 RRD files, salaryapp.rrd with performance metrics from the Salary application, and bulkmailapp.rrd with performance metrics from the bulkmail application. Note that the metrics collected are quite different - you have total control over which RRD files get created, what datasets they contain etc.

To view the graphs, you still need to setup a definition for generating the graphs in the graphs.cfg configuration file, and the graph definition must be listed in both the TEST2RRD- and GRAPHS-settings in xymonserver.cfg - then the graph will appear on the "trends" page.

xymon-4.3.7/docs/editor-makeclone.jpg0000664000175000017500000002304511070452713017103 0ustar henrikhenrikJFIFHHC  !"$"$C" T  !1"2QUV37A#5t$6BRau%4CrsSWbcqv1! ?HPiQX̪DI9#"W+IIȮstL-S$u!JL҃J[I#+^">\¯GgkN!?UN,QRd'sD ZR┩L3&yM>,̒mJ\|=}JˊmpdLh%I,w2.Č:FDU46'~Cl4;؍n))#;;7k ;KIin:6Xn(YWܬ|6EzrinO4iq.!I\ˑQ%ii?!AFv+aͦtکtZ&nj짉rZhԄ4n!ke6qͲu s [4M$fq;F#(J].Mm3bq6H3⒒3دsS.5p0_EfGr2r22222dw!<ΓzN]&]~TC8 MrvD5-V[dhShSh&q™9f_u%V_m*QdDf|CT @KKnEQgDFI5PEoJW*$)9*AHox"[[XaL%`FY!JM(Ȏߍc lպ&IV*|\FiKhc+XspiڵR)ʊ c3m.H ZȈ(7̹&̼@%`r*Yr"rDtTWJf(54)1ɐ;%|dGS 4 h8YJ\#vK$qGG+W_ӅShKfl",ƥ8[Q kG`eNM*#3%*+9+-(3R %!ȏd[eG=2i#X12 ]IHIm% "ɢ.yo "hvʅ:C[b;1&7 WxH*I JJqJ7_ME2YeF,FFۅkX"ď)OHx~eBT)ovbRrt͖i QmV27bQ|qzq eļéd37+ܹ܋rr.G:NG!@[Z_mb%E%gb6\>d^IY7kDBj*,g%F"J 2%RLȒW.N++ P"d )Q"S'%.It渮6%8kIZs#2S%pҰg&TuHn!Jm/)(nBVjȍ~'UZ4;Dži ?kѶom?zUPܨT)+kSz-Bg, .]9EMR<'Ð)ShnJVd{yMIdS*ZY8M}$i%my?Q3eeu&C8|̚Q8P"m2#3p~UBJm l̈́Eԧ_Sj3Ayv_\ q".G}8(IO/G܎V!Ts)21_N^zTDڨ82PR҃5 R]˽&EQ@ӵT)ى1⼇ ̒DH4VW7~ʎze>COd$3**aǎ(}5ieInsb<Ddi2Iw)4B$݉sc5F 畹 3+%i[$$q-u()/3"/teG=2i!QL~Hb2GI^MJlHn]E^aԲIp׉\E &hCSNe$3.cqynȔFK$7W7~ʎze>CO9VHص)Kg-JMHCZ ##2#23'Oc05"DzUl="VhCFDTv,RG6ʎze>COs]f\Gr\.)Z$SZn;f\˝E=RSKP3S4u1d!hK&C|jKq2vS?$;*9O u´Οѿ76{s]R6|) xK.zFշmkᏋ{ʎze>CO\{*9Oʎze>F:OS?$/ʎze>CO{*9Oʎze>@rS?$;*9O:OS?$/ʎze>CO{*9Oʎze>@rS?$;*9O:OS?$/ʎze>CO{*9Oʎze>@rS?$;*9O:}Ee QSb+9XۡTtC+ɷ3Ӱп /oB7wFTķe1=[܈ao6n6&jMr%$̿ )uRjqSC&j%I[l#Ik#+w;QIEӊϹdGoy\ݞ(z@j u"zQ87#߂qijbu5˽k|8Ӊtd? cO&W߇:pNuײNˈZwز2[EԵޜĪvdȜJ! 4*;\Gú]n>SjHuM S5DT3U3um:$F)'̉*V2+45*b.4q]l[DIB% GRH)"UCHm1pjM۬&?KL5n)*q74fEsĒd u҂t Djۓ4|<ֲBEȊB.⹕7u8Tx]2R,F/y&DN#Ȍ+nTzZiVo `IBb%{$q$䛑|ljS4oVk4fϘ4o#[gSjl֢)e+gPsۤAؐPDWv]er nb%w+nzơũ(TٸG'󙒒ipԶ[[JfkR M9I5;1RVWZn'MyJRJ5bJ#ResZ.P4څRTzJMQ\5K ՙ3?"??P?]V.=s[﹉'7RDEs3Eaп /oB7wo_E!d%3~ľB/hbsE9_} ߒ/w~K;8VϑD_A[W>GlK!9XHƁ'4_ 1п /oB7w\24ݗ 5w\\ߪSAE+?̋N>O̗J~jM6 5s3xDGreV""0J%ڶEsRn33~/û 1(SIi)J]?y: Z2j;ul&  uYl[iW?ŸY?2~j:Ivk0V!mɍqd$ A)$vRLc@O}kfk=O[>-I\lq Hmi[j#g{2.|R2.*l۶0uM[{~^=ژUq.fhW%&_G{;٧1:;XknzFRmկu?ni&np[cekEۢ6rse&mZQc퍜0e&mZQc퍜ma*Ż9I[VX/{cg/m0i ma*Ż9I[VX/{cg/m01:;XknzFRmկu?nic:;XknzFRmկu?ni p[cekEۢ6rsnp[cekEۢ6rse&mZQc퍜0e&mZQc퍜ma*Ż9I[VX/{cg/m0i ma*Ż9I[VX/{cg/m01:;XknzFRmկu?nicT>^KBKR4+BV^+$D|(,J.XUY)d.RM$r,{~<*(;i^;CtO׋HުOOGIyݬrB"3u_mMVn+FjoCNGnY%ƍdHyr3ḧD>]6ޡ6V @zDƕ5{ff2Jwٷ /52C2uN=>lA )&DiQ؍E+*=l@ 2>ORm.[ZN%Yi%Ȳj5n'R#Lbx)l$!"m!j+²Ys=;uhˡ̢ S1R [5cNbMڏE,Ȝj8(f b.BUu-#b2ZEYl(3bDm$K*qN8G!5]GғI}Z;tڤxYSj8ГQfRiNII\HRTM2K2)&M&:(+HVüʩxYMҵr1SemPn~*JIrQ(V%w[3ܯ;pgAoJjF'KZ3fٸMi%=PeyjShI!%effոMI40Q!q`RI}1GlwJ(3R㑑zS \OTUbY IBPYm-MffUsRTz D""EeUzb'9RuHJJwy*ߧj+v*Դ&Q 4- /IRT/9f ut4 [NORo`ұq%f줚2曑\\4=bL$+$jZdu+&fffuhZhViTWҩ}hA;y"sA!^TEJdg)Y1ixN|vqDܺgd*<Rdga2}iЫš:TG2ToffnCgTgԚmRUiäj |/Pr%;y \cV>)ջM\5/Yʷs|*#TH:؂JI(2Y(% ^)23XGVQ ֙)4l[JJ#Y8efI+2ڿYMjyմ1VGlR!-qj,\ΐy)'TDD&<9lTU!Py~YVW'l]0!+ܓם9ԟtGlg}$Mb܇KNde4j.ԍ8L86g} quuVp-PN[R[HU'#qT^~6ʤIueZA Fqz=F~JƔ`lrٝ1 A6ui+^:M6HhB[Jp23-Zፈ{ٝ0;(cD~]chf-j.OT:Me[3pNO%[wwJ[T,.rRPB:Ij4""gRBY6Q(1ܓ!KVГRFx"3Ub\4Pif#OJҬB-F>Sgs&%h-JkRkF$Muq!\UV*tOmۭKSIPU1&IԓINFWK9lrٝ1$Du^55EjtVb!X-$`֢A%9>sD\xXīReODyQ}[3{6wˤE~B^麥iI$6qwi[6' c8"fwPÖ=9K+./zKA(s8-oChLZ ޲c#ܰ΍Դ)#u('zE>EWXY-rzdƕhpI4R1qۑT s@Zji8hq "QNQ}g} kDj$92Fn47Fꔽ$F넂ʔIԜ$̇>*2c-NWEF\99$kSeګ& X;[SڵJU8/%Ilj3iIiK$$Ddedlrٝ1 =lrٝ1ʢ.el氛:Ԣ% Ul:-3$[+YȽu^}[뀴u^nѺ[Zw?3N$_-oCzq\XMmQzɘ4 eI77k.gV _oqw*;K0OaQªVN)hIdG&`v>[3g} rnmr7,&, T1$lI Qa&UƢʹ'o ԛۉ Z%3C{I4$\-j4;(c^*^Q}S&;m$ HdF9P:$֕JſJI KrNJٓ$|HҢgR=\F۔fQ&Cݑ=)"JeB:R̰y%Hߖ-oC@qT^~6ʤIueZA Fqz=F~JƔ`u{~ BnYVSd%##2թ\O*>qfwPÖ)T_{UUIiLJ$uNu'|`u[3g} s.٩Uԕyu%9Kdg;֜snjmqjct޹޹y\4LeU $j$o5&FJ%vFFy3ҫU0*/$=JԴ]SN'eEcK%Ֆñ~Vud,Ғ3JBLNQ-;njZz3ߩeq BRљe853"Gٔ#g(ԩUMKl%INAWelrٝ1,ˎ&ESpSK43JI~+֥eK/{=IRr#F2mkJc9 ZTBfHӁj_$Ueթe) M2klz FeI%y;(afwPǀd\m~*P@ouIȚ?*taY8s v;OӒ\6JnRF}BI1;(c5ZkS3ZEF|䴦CI8Я|J%'2Yf|^&MrI]|p-M&r"tlD(5W*NP#J۵ *u,N:isv,x.Dy\g} r:tڝ}(}-qNJDQEI)V"ΙiJ-otR:I.*jV JR$fx;sm; )qDE,yv>3s$̎b;~:캍JY8 J#<‰K"r"S!MKL0 ##<9g/,7z0}g=mJNOV| ˧=yfyчc<߉4F\Piu(Hy34HԬ!(AG)#3&cAI[ L#:RIG6pIApݏ{~'=yfyр&ǭIL+rS)v[L2RFzL\ǓT`1.ӓ֗THi夐Dnf[մX,]{~'=yfyр5٢۳fP d";LQCiBʉ(JBHrffcsOgSEOf nњa-64pJ+Rr\p.={~'=yfyр6V| ˧Sj4U"8jB_im i%-DeÉp=yfyчc<߉;Roj5dNy씱H'\BriJ2dY< i5J"!a[#!K/ˈKQnLJR>s33>&#|^Yoag/,7z0ٺ!%JDIJ^Ap+8^]?v>3;lOF֬*n[R7䊔 . 堿 ̅=H $RnP3ԙdBRp"ҒEg/,7z0}g=g"bkҚ$#!mIlI \ $\1cǏOM9VPɎKie%:$<2,y?a}ݟYm}o<ORzm с'P+8^]?Q? 1RO4kh̰KI,9j#.Hˀ^6F~ë߈op֏;R9S:L4sυ& 'tr r87ጊWߺz06[\/FW-z%^f"n1%D թJ;hJ jklr^KG)eym{W>۟]^6F~ë߈opLU3Hl;m{j4y#Z̈wLkݢ[)6n1Q6ZQ(D)}^6F~ë߈op*bB1"i -QQeJMkscNjj0J`Mfy3fDZǼN`E߈opu{-^"R(j:jSsR $Dd$yFԯyt#{-{~#eu`jWp ut֜:neJBH46[\/F^6F~XTʫ~>IjB-P( % |VRˏIs ƻc]t~O9N-owskvvG:6[\/F^6F~ 4*)*{}JR{5-fj.&j31OfS>5&J[2>4dfY{-{~#eu` t (1jJe !׸YqWfl2]M$ ݗa RxcQ1ucol^:m р/Yqk.֣Fت3+8^]?uZ/.j;lOF{~'mJNOV| ˧=yfyчc<߉~RSU_)F|^Yobt؍,\viN>[%* .V| ˧RS_col^:m р:U_)Cԯyt#{-{~#eu`jWp+8^]?^6F~ë߈opuZ/.:JNO9Wߺz06[\/FV| ˧RS_col^:m р:U_)Cԯyt#{-{~#eu`jWp+8^]?^6F~ë߈opuZ/.:JNO9Wߺz06[\/FV| ˧!I4I2#/s{~#euacol^~Zh[V#mOY>cpB] &-L8IB_Iۓ1m ыkseU˱DS.3u* ̊qI;Ġ\N.n_0R~K8묡cYfi<xGd|qn\ȌSʚq I"?kxͣ9Lcio4\B\JEĸsM-j(0Ϋ,;R0gjЌLY<ЮӛKH)-$?z3BxsYMg[-75|ٸMQ)|VYꙻDb$/+&x{S E9wZ5%#e63cgmcŁ%l4:TH1 GlfHJu8⒕mV!^!9&+uEc+tM~OO\F[58*3HcRI)%)f!%ih'F8\Ne`vERE)Qjʊ$MfJt*7C `G[D;RB ]gbBNЭ4M0ꌴXI8%֟B5)ImiQ Ju94+Sk6Z8i)9RL :d򘑒z3sIz1qS%\X4+JK RhZ- vi.cw)CKP+Yb۽_CThb|΅/t l9ܣy(vR4i <9_1cc5HW%QTZ4Kj0ySZx%bŢ];^˹p ݦ1r53sF{]IMKA,eKfV9˞>UZcR#6m O+HfM-M-Nu8H)杦T\* NJB#,dGdz5LN("-uk֨mzRFN%&DjCٔQ $ZtۮRƞ=v*iJKLKdyN;PuPiW}G܉ej!Z=ORAʹvŝѿ71 a|cLcu޻[~9OW9zyV4nug_CIC%kd̲hQJIJY.eqbtݶ.DJ QʌBe?VKJ ҕ [znϹ합UmFO ux""GT\47dQU* iN2{ByR׌hJAƮ+& =އHNےT(fN5%*VK)9ێNH<`E,(6щZ$BBI S~9v>6[YnuUq+3TiՋ\)Х6PاLMQά퓦Emr̅yԘֶݧ*ɨa%Sh##`d w{rw[.Θ$kW}85]E^ܧ]VKys$N8{h-.D7z/ˋHE993Tb-hdK)q(MT% ޳+oW.-!M>ٚkC' VYKFؒ`w8ѐ>4-m ,+Z(Ӕ{RQiR…T\* NJB#,dGn*ZljvJd*J#SEʉV^SBȈ'!}7c= iV!T/JDt%%nJ$)iV,HX-~NN2{bT؋j @q%$Iid<4JȻz)}6Q˵wSY.JJ`N4i`os$PKāVާ:ZqNmf|YULEbWz77%Wǝ[֒Zg9T@ [uAU-E3#J%n$F] 0#M[V7DcoI4VDO(6TJ bWz77%Wǝ[֒Zg w{rw[.Θ$kW}8>+ 0#M[V7DcoI4VDO(6TJ "zZU:}.Wtc9S&I){Spғ3Q%Z҃.=4'LW&ۚ i1!mNU1Q4K)֦FGcͶڕٍ*Vۥsr=1 ^LH4?O,:l)MMER5ȴG>\g0օv2ⳌE<4pzަͳ )5M4'(o˄JRdLoRxjD{j[oU"դT:yux[,(M#Rx-So6fFyi$ Lq!GȚ$Qp#yxX. ZSZ0K!%du-F%xY%ZՎtakqRr^KOy<[Zѫ[F99&IFtҗVNt8r2f; pq2e FEO=ӂd=6+ Vj14j2eդg".>b.=6+ 0ηn[ ̦{hǺ9ՔRI]ʼғ\_ЗIðy pҕk|zVy/QY7!RḦIW I222<pȌIX۵Y*t8g(6̞IujB2/zy2ɉՅiheǧ7n$iOtF\0g:UJ⺻V_G*i+Kzf ;1M应ԆP%&de"#j!myr$8Sߔq"^v.Kff|d0zܹ.uN\K~NN\ձͲm *J7 Z B_EvuAn;W&UVGvH>Kv[ųKFc?Bu(Tu(|s[rLmZ.)$jFztgMƪS)uJZfBHimqIYX>8"5ee"eX$o칊]OWlDГ^Ch6kAMK-8\N+Pm]§r))$iK\lIM"KN<4,BwŔ̈gl\Vܗt&nuo[JgqC]+B{'&-(s#KHm<˨4-2222>aD.?! E_BUUJK1X 4W 3/jBQoԗ:3)Ob\i$qhI4Qp8ӌ~E[gfZ*ʥDܶ%1*g}4Uޢ^ g+h&h͠ʡdjnTdC^ ,=$jqGIW:fV[o1 7ZNͫv`mnH_߫}kqVԶoыuDkiujږ?VcVԶSǣdku#JS:+räD%dFDoM|A:gG!n[gяwmF;ipV$rѦ+3[hKh$!$1ږ/j[cc[)u%ċ-Qy$y"ZHc%: EDi98({R?EmF?[k&ߣ+tR?{Km~aKm~aVM.3*;NJMf9"?iCge-voчe-voцY 3mYKaqq4lܜymۓU&,i [L 4<! ;sɷò;sɷhE.gɴ+měelI%ME\[x$,6jNX5c}۞MF۞MF'f6\ϣZk_5c}۞MF۞MF5~R}KL˘Lr|n6\dՂNq8 Km~aKm~aWE.gέ5cV>[k&ߣ[k&ߣ H)s>ui]:u>qR_ny6vR_ny6lFKsMwXզ}uA󏲖;sɷò;sɷf6\Ϣ'Bf ba,2JCmA~5c}۞MF۞MF5~R}]> ui]|쥶m0쥶m0٫􍢗3VMwXe-voчe-voц_mG:u4̻LBv{N%6 ԧB]cu$zuq?vR_ny6vR_ny6lFKU3ƂRi,%oMFy4FEF]> Km~aKm~aWE.gέ5cV>[k&ߣ[k&ߣ H)s>ui]:u>qR_ny6vR_ny6lFKsMwXզ}uA󏲖;sɷò;sɷf6\ϣZk_5c}۞MF۞MF5~R}]> ui]|쥶m0쥶m0٫􍢗3VMwXOl;eK_/*V$4ўpg wgxm7ȳ)pv3߫MwXզ}uA#<6Yk,A]MuZk_2;qvy熳|?;YZϥKԺD'd)bЕ:ⲥcG2RMwXGgxk7ȳ<5YλZk_5cv";\ogoզ}u@]> k,@ f oZk_5cv";\ogoզ}u@]> k,@ f oZk_5cv";\ogoզ}u@]> k,@ f oZk_5cv";\ogoզ}uAX]CԙBүWfkV8wHr mӳ~曵ۦ+H(YhAHȌG1 ָ-pri]Ɏ4Ɣ Zg>cwd~푽\TwGm+Q*arZuLh){/VxiZ,crB˩¥ֵU!t,;bXI)ۈ4I(ȍI=+"3"H~/Lo}.UP6,Ma؜\98A!Jm[I׫;Ӆ+QIkȵlYDu7XӨ6ҴFJBi5)#3"▪ rL,eHL֚æ!Bx-J>$ b~- q+.YWCsyf3kZu)2 ΒlZKqEB[{ĜymHA&~Ҕ&FYcxI)'W9RE4^[B?,5 ZXFHedM&\&*S֣th=*JxFf-;DIFۊ0f4"s),NYw6{I $~mVQ*T-ɔChqIeDS_|xs~ɡY諘˟5ڬ=顉f3iAn2QjjIx[@RV[)ʎ8S8{pjA)Dhݖ4{ڦ^]n캵Npm1ar+Q8ˮ\#D6DjRi"ȵw%XT*25$DZjCBYj,dFYt:)erR\\"4خe.rMJi{^Ds%mO.UBd6c+F/t'MV{aJċ.[Qrg+hՊ/(3F֣6KO%؅`_mRhQYT߇9^iuF[<;'2ʔXi7+~Jn̍c:0mN%.)qZQJt4l\F>)4veU6u Zp̶ْ۬$g2>n]VL;ڪM*fb)O<[VљIQ)EG\fF$hڐÑm9ߓtFpPDR $JvEcI.J[hѭ-Qfg89oi(&-Fɔ*4f!uE+ZȖ)$Ҭ# *obpӽ!l^Z~m%ҥ:Sޥ]xօo{~ ҔJӓB`.̯qoHnβqH̒T Anu#sDi23n;^3m>2(X&J?5HٵuW.f]^ULn~ʝE5{?m{2JWbU&_ h./0!-2e2U1AUΗ>F+ޑ>_z/gQFUx·~GBm_Ӻ1]1>ӮރI\P#<2~Zwޘ#Vg7-s@?U?udT"bltw\>&OOs?U?@۪nCh' |L:2~Q۪nCh' |L:2~Q۪nCx' |L:2~Q!=۪mCh' |L:2~Q۪nCh'.۪nCh' |L:2~Q۪nCx' |L:2~Q |L:2~Q۪nCx'.U"4S\>&OO* uQ-s@?U?udT"uQ-s@?U?udT"Bn6:Eh'|LT@6c[2~EH6co. j$2o%Ipf",`ˠhz%RXwMSVD s.O@ IB O@ HIt ;Jv c|,yR1_fco/ }NzE7ɚy:FiQx7 th)(9!L8`Jɨ`˛GON_?1Mvn \vNdMQԽ 4!y22W*7Y6j7&G<>:5}(֣j/TiY>9jdIoDGQ6Md'N0eS?cF:Zt{&mjvsƨJWܼ~&-ne:)RA(3KH%ǁ tյ=U5 ̷ܚHxYB2?pBL(%R*9Hm%RԒBX#9dƪrPaŵhIM7.Lc 3<'|37ӎ{;}^k'[{E`DܕT 23(}cpm$|K #38P|.zԦN.-r;gJI)G%m[5f–WFѯYi*2I?m10z{cy5p9m@]WhUeN)I[{B,>Þ" W0F##r1tbcl211FC?H @,A' I!='={6-KBL_6#I<)%Y> O~Ї}MJ S%uM([$g#83L ˢ%FfQĖQʘtRfyNL9dd#o'b<LsJT6FfJxJU>rH."=ҫV HQ \M^>mۻe:M5XIj4/j㜖2\ ^6iL"T%2lהX,D\N[)x,q g6>SD!qd.FJ5PfZwb{5 j%M2U-JK&dғɗ?F6]XmnS ]EG7#qDu zHJWaYc*nƺ,Bt%LUJLGVLQ'ңL~xOV=ʄ&4)lnMԗhԭ$}s0'K]OXQ.Tsr2{ !<툔NK??8qn 蓹}1HФg3%8K = O@$ IO@=7a^Ci}J$aDC:ڔFYF=Gꊦ٬^̎USmR#̕iR Y,V0F~KJgeTm;~WJ'mC*JDFzO')HiPi2UNe%<(X.r2CڣGa6;sUJ.!K\γNɸc3ũlޕwWvHT%תDn5 pN{/sG10|=l3ft[|EèS!*^8iVVfm "#33xvasZLB QaT+Z3[m&4uq}-Z5[i*dIS.hčSImZJtKR){(%tuw-mO'yu}7Yӄgw ŷOK@M8+BlҜըXL gtg/7Li[Q0^SY$Ճ.#vl~{[CJDxҹ+ǭLb2H\[_".@iRʷuR(AOSQsxScg6-mL=f*}L)_q>yrVA-&}mN: Tj3%o; Ȋl J5,UvW3㎎ ͛Z kd6vWg"c\ %\i'x|LY㌏RSdefۈQ`Ң<DZ94q=M-HqM=iIS\򋚨S84n,d_ݐvoV;uM.nNuyύ]ncmj|mE+ %XemjA gk#xwNW[HW%Vy:8]=cpU%+ioSK 4ڔ5$$-Z4 'Fiϣ!XR*j4W!m2IC,%(<0XgK2.]9*|U*DuhUgHtjN̋Cm{cnҨYKOT]5$*qsuiCP?Reա K&tDIZ{qϺ{$v"ܫR(IH}&A>=^z6sKT9T5,ܸ˛T%qm|Rf\?jմa7q/uVu잷Z=*2Acv3g?ÆSB'r o NxgzGXf9Rvs&JFo7N:3~jf&J(yyAA 'x~݁wIJa[jK ?rBFzОs/mg6LyA*d },%kGp6yck5W,vJCQä"$d̍OIeG&X#R󑰱b˪\U +6tIE8S mI4TO2㞎 mj,*H5f8m QǣOs;"W2`'IJgu.<k)&myڗU-|ḅ{NnG/d?]fJZ{V8?d%p lf YJޕ3Ϲ)"YpV[3مF")nv2ɩNPIWd@; fo<)x|/]3ıK>mbW覮S|Mk)93"nH&8Šrv+9O0=g݊.`N~"Qv`k)E(jW2̦1/X7REߊΛJU~ң$Ú(RƶG9v{gʶfVcңV%s30ݑ-\Uc Wv=sY"= zMh&rwt65#Gw6+S,Zǒ/lCf29c5͈dJǓ/cݮqaV<~#T4r99{?H N̸X5x~Q??Ek9, G`]}D"~5Fr9, G`]}D!,s^ K ;c' Ece:_`]}D";c' Ece:W`]}D"{c' Ece:_`]}D";c' Ece:W`]}D!l>/Q?m/ר.G5!=һc'aXCo|XFr9l>/Q?vO~50ˑtaXDO~50ˑ{K ;c' Ece:_`]}D!l>/Q?m/ר.G4һc'aXCo|XFr9l>/Q?vO~50ˑ@J O`]}D!,s^ K G`]}D!,s^ ۠@}v 6Ŏka#}v`]}D!,s^ ׸ taXDvO~50ˑ͈@e]}D";c' Ece:_`]}D";c' EcevOvߢz2s@+.|_>=vߢz2sQO#.|_>_9Q\m K ;c' Ece:_`]}D";c' Ece t.|_>vߢz2snO#.|_>_9Q\jvOvߢz2sS/.|_> 6Ŏka#]v`]}D!,s^ K G`]}D!,s^ K G`]}D!,s^ @}v 6Ŏka#]v`]}D!,s^ J ;c' Ece:W`]}D!l>/Q?m/ר.G5һc'aXCo|XFr9l>/Q?vO~50ˑtaXC.|_>_9Q\la+.|_> 6Ŏka#]v`]}D!,s^ zПNBJ"C愥[&fY>9#2ӪSk܆})/]3ıKǦ~o%=| ^4&䝴(U_,_{U23쑣5yG0C[#ƮOHH1=Z\[#W811>07ǥL# шHtb;6#ъ1F+6#zcd;Lcqq` 1}@B{'   IBI@t  H1= $ =0000"w@ú`v7Ǟ߾/_ fo<)x|X,TBbR&yKѹ2ZeIC~SL$Q∌J"nH"U5yfe&OJ5-jߐ(KԩeFG1\r~ҪB]"ϫpԢ1NqRslsY6]`τsp=0׿dVL6ǩGqrE%=b>zq\?ǣNKMF#"]?͟W?X!88Ȥ:1]1xsgXl2?u/T4qEwޘ6qpl<)^<;9(xҽdjZvdri.G{;WopgyJmm>e< Fƕ";?(xҽd5b̧v7~Qzv7~Qzki,řMropgyJl).=l).]l).=l)˱+Dv7~Qzki,řNqopgyJopgyJY2Fƕ!Fƕ!Գe8DZ+C+C[Of,p c{;Wc{;WRY.6w4Y6w4Y m>).=l95vEq1F{zMGDBYTu8RVBRQ$(,33 BDniG" >TdУ#|ʳjkAx޹j9F's Z̈́TW>! [oRp>Er5GKճٹ-˞jAM.UTtod'M$i,cLhP,ɶr3j=F'-GsDcY5ߖFj+n8F̒YX.bf|8::HLQj7=j=F's-떣oz1unq6]H[tVFR|Iw1/eԩ_魿3itiӣ)B)?$Z-Szh:hiiht,\LQj7=uQj7=l:HbfZ{QîZ{QƘeвC7=r{ڍ4OFr{ڍ4OF4-떣oz1=r{ڍ4OF4'-cT*Au&5ŠFuG31SvH[!QޓjS$D0v;fٴԭvE7j%0;-pBArY:bd?:uQj7=uQj7=lʃ+f L*URJjŇS"dn-juYIJVGEbvvzNXRutUpk-KE=I--fF*pFвBr{ڍ4OF#Z{QYY"r>-̉M2O%njDJ$QY#/f6KTu3C2L $$$,%HVIj(t,9w\}ч\}ю*ҷͦޔ^u|VLBɴw[qTyRINK"2!eX4d 6jrPn'D!mx\xPjt~g#떣oz0떣oz1_[RKXIDMynJu Qj34+Ri2fv"ex^5Pb܏ځSi}kR 832I$,z _vVh:htI,%Ofא4wƔ'}%i$)XRdzM>saYJ-:]ڊ4);Ϲ*C4KJ+%jFM FKr4Qi>@J~.!}&!(g:ykK)$FIM.V >]6J6I%FRd%&4DkQK%'W2R5;B_ף5U9rwB= 61~j=F'"m­lc,CBHJO:TRdKn-IAMj$i#Q,eҭK6ʡ۩rd8uXgu&ѡF)BpJaвBr{ڍ4OFr{ڍ4OF/uu}iIqr6q8DxɑhSڵn(VݓCQk:7TcV%J3Q9,*dVzh:hi[eвC7=r{ڍ4OFr{ڍ4OF4-떣oz0떣oz1ht,ܵ}ч\}э1t6Z $13q-GsDa-GsDcLY!j f4M4Tې㰖n9H8JFJ<31JگWnRs8K޿$S_fco/ M37K{~>iLwmb̟W7tVeT8W$| A_gMH|{˺N)'XQ,'Id3Ƈa"ywT9WrQ:;Lh8Wl,mZ/hsьw6t~eV^)$hk[AV4)r`Rʕ)$-Q 4j.r2>#gtqi[*uDv!mGM-("258Z)Ԓ4l׵ˠߴ;CDzџҼs WyKL@g yzi^xM}o 戲sc:Yzi^xv^<0W.z hYs9{.JMmutxa<\Cdb|J]wDmPfnw$IWpjT)) tɊW9zi^xv^<0W.z!_[y,Jtxa<\B{/]m+=kxO4,Ktxa<\B;/]m+=kxO4,Jtxa<\Azi^x_[ye':We yGJD .e*Ю;m\ۂ<8$SLZvn$%GXV32,GZ?jN$q4f,ԣ2φqzi^x胲Ҽsuk?<רh_rT MHz8-oP'Ĝ<`ilLyIRJ3#",R6yNnj>tЇ8HpВI(k^I%< .ɝҼs txa<\BJO4,YDUg4I - JdK&(Wk_Z5GW]BSRP'JHZVJ%(ŏҼstxa<\A|רk.[]o[B@<ÖO6겲Yڙ!K UlU7Eݫ/veÒT&JB iQ)IRdžYtxa<\B;/]m+=e_ 慗3}R]!պ5*RfgfctGJD 6狞__[ye^<0W.z! 6狞5'\lBK-]m+=GJD .g5/]m+=GJD .g6 +Ҽse y }o 慗3K߆#4"rM;}jT np]u(5\LY%YTJuzj[ l.a 8ɲ3<3'Rdu)zi^xv^<0W.z!_[y,۠Azi^x胲ҼsB˙tGJD#ҼsB˙͈@e yzi^x_[yeKtxa<\B;/]m+=kxO4,uut*v!t SiQ΄i:UGDf}&cK>enjTB`KmEB=mE("i'ŋҼse yUj|רi@6 DPή:NIFF8BMDFilGsp@Te:DVE9츧c#VAhq)ZHd dY1n/]m+=v^<0W.z kYs4Y}XpV4!dq+3mHQ(fddW6e6A[@ykPymjԶWxXI Hgtxa<\Azi^x[V|ר{HMbMp\-ki3u4$8"'I)m&d|7i jVNжֵ7A)jf,iY!txa<\B;/]m+=*WkYs5PRr~Tl: FrNt&WcFr_WcN4~ƌc5vzi^x臢w+rt<6NwBI${-&eѨګ_5,{i5Zj٬7IR,1ixȚaDRuy3zc+]AU\Ǝ!եiY(qf^R$fyH/]m+=GJDʾz.f v6n´Tہ-nIVڍHZoFFI34p.\S|NN&jY ,%6Y\ٵf)4H쭙U8.ӥj"5HR$jB|Ozi^x胲Ҽs^˙WcFr_WcN4~ƌc5v;U~WFS܇:?Fo\^tX>z&|1txa<\Azi^xkq<רsP+Ҽse y<вsP/Ҽse y }o 慗3] 6狞;/]m+=kxO4,ú:We yGJD .g4һ/]m+=v^<0W.z hYsUַJC$:ET}Z&ERYɒ?LSƕ8dZ&R4N`v7Ǟ߾/_ fo<)x|N?:1?n~qHڿw)9O0H<ܢgkdsI5rzG@3Y+kdsJ=Z&91a1neP6,*kGrU")Jp8JK}5tzwūi1ü!DdЇ$- Kwؑi#U]1#!zck I $+5G4/eԩ_魿2iZZJSt6 2Kj6KQA\dpݐ*70-%DHH% Q%ԩ$dyA1pmUnT0-/:KK#F^I-3A+8l*zUi ۍPe·)de2 IRT6Qj-FpbRz}fDFf"K,:'I-DniI#%%1aqƥîKET,r7'7zde2#< BS2vUisDr&7l57f"Qӭ;THu;&U jX%kBTh7HF%RЮכ6֦V.Yuz$A1u[R\Vhݡz\V̌˴xH^NѵJ\rDCBΐ\H5o}vIuK3틵qŽ&M~2Xn2\jR=ᨍ' z&Fib@.ҵbҭJ5\"N)^݈dJ*Qg(N0u[&_fh4N{A:YX̑M0at H= !H@ a v/GOiliEj ~_?s*v,qD eIbz !H@z b밉Rz$6i3BJB$˘̏Q~eҩ{EW+ďI).^uHoS,>g`ҕrrѪ$tsZٸm mkeVCK/)䓮=J2$DDD9ΥzQZqoJI~}NݥUiOz&嚏ha%GT*S;=jYݬO>/.I6i0k`gZ [-R%3vmR٭O.T^5c8<'=JBޱV܋ބ)WJܚJBKHq 釻=1K"=fi2QvIXi7ui^֒{+n'gXw]HrUFU'#ϑ!eF]SJh%gCJNDGY6>ǨKqUezMj̇Sm[-R7 ޴D V5ofdJve6sj*.-i[QB\Ɩ۪ޝ,DDFFhɫIȅaX6zTM1&5JfqEzJ32KV0^ wO{I$pFY^ֺ=O'.΍FyiQGmꅙnz꼛cO'~{iN819~;2N),ZR#-I#">}JmMIn\Syp"K$úmW+7Z)j:VuR&^-#N~o%=| ^4&=6 c|,yR1߼+2u_b,YS\-~ٟ/4S$s la5yDP169jVfW8159zLs5ό7c S3HtnU& [ԘJ%ȑ%yZan2Ixj#GHwmFeWj_v{cZɶCH5 *4b1F+6#zcd;Lcqq` 1}@tR惥+5FM;yGk*B{nlt*6YJ)^im8VhZx<%NͷبKȯT*2? ͆v%n(ԇVnKIpGFBEwfz+.[\T&>eIp5'IAgxeԍUKo[=IԶwrZ9F}:1(WUoSi 8r ȝ$FmIa@ί&ĔП7Vck)i8. JT%>8 8.hnΚ;-&\3GiI$#~&ρ%nj8ȵ;j@SGߤTФh2[5dEUU\%סSL&upFo OkL̴,-nTmʫsbʚiڛC/֒d3Yӕn=:EEkhnmgNIVmJ2wR%4M^IF AnTit&&USljH=7IyGdMU)\Y©Ozce)w5M)R5d 8* H O@ {$ !$: 5ؿ?ͫk7 k:KcOp-ϛW dGHi 5'A`$ ! '@Nit**VS DuF(4sdD'V1{b1}=2%V*CjTP<'gC-K޸0y/}fYt8$O}kvaKV.)0'J1Z#\xH"5-Dґ͒5zt-gWNtQxm!s\"#46o-;e)ɖKZR{@=O~Tik7;r4ߺHQ7/}fZCڽ$Z$32n#(RD}XQqHUZkWL-zqi#S!ER %e -IeFN9n+μsYv\VD.絭֗"=)zSim+yGꑙ,s ENկ1)Ӕɤ"B$i#im)3,@תU [kTt?WT:3tjk럲ueV?eF a=R_?SHOl(M0_Z-5rzGGuS.f~WeC n3(l^59ztd|;B~1]si낢|o*,y"k~E\Y;hoE)шHVY}N2/YVCk~1ܲ-sne߮#cNZr[\fR} ne?4x`aH\Xv~m+̆x:õi^d7“ݸ&RDSX[Q[>i/aK.fS(UI;#x8^$dki`".n~W ukҼoׇ>9 LmbCJm?]M#OQɹf[[xӯO98i"~ͥy߯ ͥy߯ KVB 9Uɵ&tpܒɑm8d`"}pSU'Ъ HSCNdZVe1#Y}E"ߛJ!^ߛJ!^ n-!֌N{n"3,XJ8JLY:Y'WxE;_6C~#;_6C~9SKO.n6[yƝsьJӺzmV -RAuiAԒJ22,`WxEǬ;_6C~a2ᐽKn UnDwMn8T[Dm$$(豞fIIS ȧis^rFӣ'BwFEì;_6C~#;_6C~)=nDPJ9w$sqϏcO?:ݸ*TTRd'oOR^,w+ͥy߯ͥy߯lC{TjeaH 3I~9H7\ZHJ3%(RsWxE;_6C~#;_6C~(vLjRqVE\k$\%6BSqiLzl.XKcx;h 8tVdT8W$Y+U'#Zj߳?Y_FtS$s lal~M2Kj_:aƆ_3=\{`ْeFG1\r~J<ZqoO?/bwR1NH.m9/b {lCGD6R_oŵQAC?r-bԽPz4e)шHjмeZGrԠe_jGF+/ZT٪1m4mU=LlE8ȢLcÖ;?_RgUU!5g&q#wj#{ƭZ kc"< [;_R}V-Wԃ[g}A}V-Wԃ[g}B:Ϸjekc).=g5l}HOY[;_R l|~4ˬ{ƭZ>g}AOXoxճU >g}AOXoxճU >g}AOXuoxճU >g}AOXoxճU!=g5l}H5 OOrsb<ҡ-Q $%8l#SiSPF%D,KŔFW"31}V-Wԃ{ƭZ/g}Ayﴸ5V-uJJMCϴM8Qmɗ|Ci *5 U): o-ۘ(.*ҧ#ˬ{ƭZY[;_RZb֝EQi"tUZ㵯F h'<3"< < UIqn*Ll;-QymJ>#3C>FT,%:{WҹJMu4]AYFY[;_R}V-WԄŋaTrSZ\y-,:ZNG8ssʥ&lBb\eu/v)V2,:RKE>[;_R}V-Wԅnߓl}HGY[;_R l|~8Ǭ{ƭZY[;_R l|~8ˬ{ƭZY[;_R l|~4Ǭ{ƭZY[;_R l|~8ˬ{ƭZ>g}AOXuoxճU!g5l}H5 }V-WԄuoxճU ',S\zϷjeuoxճU ',S\zϷjeuoxճU ',S\zϷjeuoxճU ',S\zϷjeuoxճU ',S\ϷjeuoxճU ',S@\zϷjeuoxճU ',S;v}V-Wԃ{ƭZ>y?Ab}V-Wԃ{ƭZ>y?Acs_|kԤ9KMMIHS-\zbpN%+ӭjIg&/zX#N~o%=| ^4&=6 c|,yR1߼+2u_b,YS\-~ٟ/4mk3(/:Jp+9֟\c>~o}I9G4oghn ^kTSkp?ȿJ?=|L;'z:c.aу{XxkYDYk k?h#Cd&~|ֳ~8~/G0 B Lcgd=p<5_`-Z%{XxkYDY6Z $13絎Kֳ~?Al:Hbgk k?h!cgd~ht,Ϟ8~/CZ%Y!=p<5_K eвC?>{XxkYDYk k?h#Cd&~|ֳ~8~/G0 B Lcgd=p<5_`-Z%{XxkYDY6Z $13絎Kֳ~?Al:Hbgk k?h!cgd~ht,Ϟ8~/CZ%Y!=p<5_쾲ltK5L7s$lD8\^O͵.Cn1$SI`!禭җL5eRq<1]B e[Z۝* j|ˍS㔙JvٺX5qA`|sFey$թP\5&=.2 g(Pѡ)ekKKNMJ>Q`@Jڢ"Qij!SDbrbjL(I%JTeɳvRzqe#Z۝ƭƝZqPi JQ@O22ݥ "5#p\OƣGTmzԛ4,9MOx ɍu*NV{G"Ip!/G"vs^ k?nw jW.5ESn,kCB6"4xV'EG"շhzV1S7+^.2L'Y$J49gi[ڈO}l%iY"PiLju:HFჇ_dsr k?nw >&"Y7T)IT.u7D*4xi 8">i ,seYQ㉘ÿ_?, lzHK;} \;UvX"Li(Z\ +BV(Ҥp}p2=K]?6:H]߮Z۝ñhvuKn*gF_#G}\Ns ~!Y!Z%{XxkYDYjhm*&MK*\=i֮$ʖ%$Dff|{{XxkYDY+l: eDkOypۨTUV$ˁmNsj=u (0+k;kZߣ "G<Cd&Qp<5_KlokԤzlIz0mȩF]%8q9y-&DKSm{-3T!=kUХe|tҢ~Ex,qeвC9絎Kֳ~.mh*ju%i:d%)ovʙi놣7L-)"Ae~NNo*LR Z3h<0-½p<5_K eвC?5z4Y%9r0+()sru(REfd\p\x;X]lKtjk6$ŲRV >Q%V65P &.Vq`Cn<^ⰵp=Di"c45&yd[~ۯW$QJk>FLst$^0y"2#s"nVOJ5G?mBH?^GdFKk>sT}0v%ȵ9>iYN OWiggȕI*K%Ht)D;g‰$3U{ ;( 9 㩶d%!F:'J zH]~1i%댕P5 5Ti ">*Kk>sT}0U*#kOҘ 땊)tPu.R)]:,)x"%(]Χ ڋ#qw}1 ėHNhZL}eԩ٨,7vq-gjĻ85G sԩҺkiHmH!dۉ$+Ԣ2&FН\m*Q7SYv,ZS)aɟT3ʖ|45g6_riEIi:%t hBDw"3BȏJ=ZϜL)ѯI^R R"0N(ȟ= e Ե%D33%`$CsC9*oɌ& z2%VUX(*tBPD\v S&4ڜB${b%R 2{]xƩU)C]>#޹2CN/ RڣR̲j"&GdٺKk>sT}0^;:ڙSbVuRDԥH"38gsn:nEN%Z+Q 4魾Nk =8(i<ڧ5QY!v~Dun ]kZjRtb3333>GJگWnRWCVR?"Gҝ37K{~>iLzl.XKcGx;h 8tVdT8W$Y'U'#Zj߳?__h>ƥ6,RG79hu ~6fk8#:fZЄx"<3EvpkOwh8V$u}7&#O1?H:xqvWEzqx\'0*r%ޛ1?Ho#ԛ#yeG0hK{f).O1?H6\_Y16ЋKƈF/ތyx' |wi;E.kGm/hmGqGEO1?HhӶ\B~3PYhwR?8'QM7b~ͩm8ѮqSiG~TT˼oO>M6OʛOx' ˼oO>M6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6OʛOx'>!o]6{?;*m?=ܟ˼oO>MvT{?;*m?=ܟ˼oO>MvTc?}Si1p]7b~hr8vT{?x'?Ǜ=6Ok.nO%Xrm[NrFV5i֣ӝ)9]։p Xa= RjGlIVPdzJLD QH14z/)m$N Ԏؒ%ΔfokOf)%(9^3=˫A3&g+" p)¢)MJ̔"#qx"-I.<5&*q!39L7% J%CRW+#D|5"MڕItHJ)$$RI"""""""!dŇIOͥ0ҢԃB٩LQ~!OadSe=͇khJ FqC-YCEC2Z&H'|pF+ MҾhOΩwX|RZ-HlAg&|s· ~ԟ32"y$#kn\ەגHup&9KIHh2gUkָ. 15" rd%%VNu'1cm:Hjȶ'ra*-Ÿ*ADM[(gs+*m?=ܟ6O}XS%QȁPYMo ;ƜIiL2pyϤQLY$)j3N4PiΕϵc'}6OʛOx'>Ь;:ơӷS&;+nZ58O /D>K!JJ}G  N:fedCc?}Si1pԫڡ\`TrY6J$kehQ)32<Kc@ӣS4hZC,2 -$$]DDCeMǼ~òc?}1?Ho#eMǼ~òc?}7,5m\ډ;k2"Γ3,rY> UJBl˦Et<*rIaiQg];c?}Si1q\$;_55 ,Y6RVJAL< |pW\ fVR+)qSPvR$3թ:SKaUc~a-\\M}6ZsÜ5N`< 333ϦjgmJzu,- HEĒdG3#31󿮫:\Uc~aEϢlTyI8܈QGmss/z]̄Щ4mu``,cE=2u8PGnO-.R֔JuD X1󻮫:\Uc~`.}Mg)`L" I]BYR5#%cڧ"nr~>eZeMim4o7UZI("wUc~`?0a>ewO5rUarQ':SIj< 'am&GeOrI<>xusGXk:#}E˖uR棥mW+7Z)j3h%_d})/]3ıKǦ~o%=| ^4&;SwEfONOrE*se0~ K͎!Q盁?O]~TލeЩHiLwmbWUL4{$hs la5yDP169jVfW8159zLs5ό7c S3Htb;28Htbc)ъ18wޘ#\xX1L@@' DD =={BA = O@$ IO@= H"&0 })/]3ıKǦ~o%=| ^4&9;h 8tPXE egѣ#G#kd a=& G1\cW'z 35ƶG9qcբc|a3oJFޑwmF#cNWymF#?Hwޘb$ >' @' Ot@ '@ $ @$@ bz !H@z ````D  0tN~o%=| ^4&=6 c|,yR1ϼ@X;U2(U_,?轣>9['ls la0T59j􍤎c=#ՠa59lslss |zT0F##l1tbcl211FC?H @,A' I!='={O@=Op@$ t@"O@ IB O@ HIt ;Jv c|,yR1_fco/ }N*/BcEhHO@ != @z {$ !$: $@zH@zDL;a@S_fco/ M37K{~>iLs,v*pUL}W/hϣGFG0)['{L lc=#i#ƮOHhfk%sls)\[#ǫD3\|f>0347F##-шHGF+2G}~15ǁŃ$ I}OtA Ot@ cŶWtl>۹.KK5Yܫ~TdDPK JK0^E6>xtD=[J׭g HX臵a~C?|L <;k3҇a~C>xC?|L{)a 'f˵@%)i$d$-I#gpd?% D 'A$'@$@w@>.XKc`v7Ǟ߾/_(U_,_{U23쑣5yG0C[#ƮOHH1=Z\[#W811>07ǥL# шHtb;6#ъ1F+6#zcd;Lcqq` 1}@B{' C؝MNlxXv>m2neϜ +:bkTׯ)ƵVetl>ӈmhI$Z5RȌnHDǭW#R)6JD}#^Il,{$mP8FO2eRK@:I@t  H1= $ =0000"w@ú`v7Ǟ߾/_ fo<)x|X,TBb*?^џFda6R9O0*5rzFG1\0J6RƶG9Vf|a=*finGzF[ޑ Wye:1]1#!zck I $@=@ k qVcGI[PMF$F)X.ﮫ:\N ]W>?uusGXkj:7u\>ms 87=t?c~au\>䮫:\m~62ͭTkiKZd|Hk@ D 'A$'@$@w@>.XKc`v7Ǟ߾/_(U_,_{U23쑣5yG0C[#ƮOHH1=Z\[#W811>07ǥL# шHtb;6#ъ1F+6#zcd;LTFQjIKg;^SN#$i<)&FY#21<,1҈擂v\EnE'Kq&KH?aMЮsiJ2ozc7hZ>:ষacJvm5{mLf)jJ8R/ ZՂ""I(<-VlI9ϧf-op v2Uzmyv zN$҅$;dFK#Μxf"v)M%j3"ROxq"<|Yv]JMꄸ|4o|ʤ7R$#Udԛ8뛖",jr' OSV8@Wn]MTW.,"2NѠdڝB#2"%tY^9ť;QV1ZM+gxdI:]p?9DvUq+z|M+Lnk2J'8d–ԓgm=e|U^ݭv촻ɉEⱼm\]X+qkf75`ZB]kG'&5֍i RHρ:p|]^ oD kL4FIldWf3ə`e~~^r3&MDrD͹dY*)tΪT➉RRԌ%hQHȻa.Vip!vm&VcS*n*MZMЫMd"֢Ԥ,Ւ4t޶>LidQ ;r7O\: ɬlj\jI~OSeuKl{_jwS`B2+'̍ N- B *dx5eEE^vnmUw*%I[vUoju9&DzNBLe+$$X}y:McNq8ȫM BzKٞŶs֤ǩSj3b}ʐ,m 'q8dXn쎢V&UYMqXN4X{RMNKOhUmGnI 9mXQ -;Eۺdm pKhZeI7f7 j$ӢM2yĒx6iRaJ2O5 HpkڻjĬےɔe;qL&LM>ѝYIC4;Nx&I%ڹV`}D{j;vҮ.mSj*RJԨ0'[J}8}^nOͥӉ8ԸHD}fGSor?m꾭Ȃ8%Ɛ2h24j- 2.5wF񻮶=v]uZMmƟPKVh]ܝc( EmUƌî)jZ6XmjRԢlf|f6V][i:eăG:5C =)JQ>%,[*-M$ujֳCnZ\BwK=L^v`;>HZ(s6")nu)X>>sIc0 IF(]hGvJ̍9qjRF#,P$ú; fo<)x|/]3ıK>mbWUL4{$hs la5yDP169jVfW8159zLs5ό7c S3Htb;28Htbc)ъ18wޘ1XQj,JcKΩRofY&2,eLcqqgal[vlKW-XdގKiVu4'FdA8ޢ6~,*z1QKVI6ҐM E%N0cjd.f˻eZtS}pqcnhZT]$外tp2ݷv7f+ L˖:m%R()R.*E[Ϻ,MШu>LD 8Єa#Si6U)O1}u|_T[ju%ɓI8XVHg*i[ț-Ö,W]mɭCu)"u I5$) ,JL ^ZD[J>6N2ʉXR_ڙ{nmDJ!kkv9Yt733eW*%'?>|dʐάԷgQanWdn~ֶѢrJ7s0Ot&\1,]V=셾/z)N$)Q!tM*ՎN?:R6rYŋnƓ_|YkB->x,K_e%<<YFͲ tԦQ7vDQ#&D9.>O 'ɡ+Bj%()8ՌD&6:3@HkBTjQ6Tp`s ; /lhkXq+iqf65K< IJӌpEgM7Ezq]T 9t!XJ[CX5i+wϘHfȼ]P{@3&ۦI8ffYi\rBӸ:L:Lsj%C~;S3)Ơem-E24$lfl-jD+SPИM#K7BPDDdzud|"\~ſlFtu蔸T88[-j.Ԝ4>+[~7hڵ >DaK-DA"23x3㊯@8@f"OWTdrPZdDH9<p;m͓.lm.Q^bߘe!2S=PY%;3Mr.2q58ҾDHYsX?*Av[v[zϻby#GI,BQ`s97p@nvPovbl8Ę$t(Zi,pxz er$mX_F! u;Nyd\tjAs'.&BVj nqsX" 0}+#mFi&x$8V]ze,5jm'AYvFd5cYmjM2~-ryNKg\09@t QKzt+d_c=~{ң֫3ڜ*$6sFۖeڒ`̸kګ/]_vն֕!NPBrIdd!B达md^uI+^GɷZ7Z:[Wj0ͧZѶQoF5FPFBVV8!\v:VZ:+Ԫi+TYKuIKL'O*&0FDfC}WxVԆ9tFP %$GpBrctBki{:%E*C1`7 0$ӹ᧓q$-Ϧ{ 뺞nSTY60Z"5 4'F߶9] d)Ө@S:NYQ4L2 pY.¦t!Lre/FSLG;01* ;rᲳ1X2qS2tFKRnD7IrIRjՎ|cMC(yvK͒RGz8I2V8,D]^趧6EfS-Mc gTdeBIdFF6X`y* g'ӯ _x()NkQvIB s-EjthoBPF須ּp#2JK !pdc={ҤVݜ&$Զs%DԨ۞Sȸn\_f][2U,\JNmA'Oû 5punn)4WjM-nnL譙% &e|x۽Fn昝I C3>3dmVمPZ)1&ӎ/",I3<qv<<>ce@,TBb*?^џFda6R9O0*5rzFG1\0J6RƶG9Vf|a=*finGzF[ޑ Wye:1]1#!zck lPksaҦHwʔ`y2X.#X"5#&]ė$\I>O@P != @z {$ !$: $@zH@zDL;a@ W\7a6͌EN*/BcEhH/u{>_G»w>[*+Q)8JJU)HJɵ mf38eLr5*|&)V$8ZSÉb;ד*SXJD6MdۮyiItpS"?6-ہd QXD DGZZu!řj$DeĂn\5XKU'FoFNI*IZL9GsjVg:eY0w./E6CQh#IRɷۚSGHU¨8R)9 M\=RѣS1˚6%f`¸Jm5g>2K%&;O($DF ̲eќ D^. .NY& ˆRQr&-ER"O=TG*$&;j343$32"JL' \StbG&ɨJЃJh<$!DW qAw[wJg2?BkI.Z9!,aDfE#"E)M.;;=f2MrT7FNlL[<϶mD:E[%^N˫MPδV* ߓ]tsL eAfddDf(F\ fkhD(nU#HOKI,00]}鵢1i$Fl4K#'Io)Tɷt.Ɉ[Nn^:m:s"bjMOw$T{[׭ʋhVy{@En-Y0ZOzl6myfS- gT^}3T҉II|)ɫ$eİfW(SzMZ("EfJI6F܂3i(AM9Y(3 <5b#NhCeYz6En>"+߄:m":uj)]? rXqrDȇ)(kV[uOl8d >ԒI{U2]o멪춪}huDHʞJHmj>b=\qIuQݠ\3i3P yIE3J_*DiD)BO8<܅\y>1ήrLؓoGɉ}<&; N+w[ntfujtЍDki=Eõ[ #.c50huz ))-lZ2.Ը>9]FzRz;1S qK"_d+Ӫ?^2j 47I2iNȕ|^ZUI(U}ȫu8.?.<[fSsqqIJ\Aʔy ZHsI.ɠF%9IJzJGne*#.Fq.l}}J25% [DD&xIȗ*;TɵR*1)/2餛ݢ:F$hL2{b3@VU-fMcN([$"4kRzyZ:6AfۄWށnKnQvB$&&o6ddm'8.l.VvPF2_U!l%KyAYVA•,-J3REiB%h#,ZGƳ&'oaU0i?$#$CX%k2L*m>-^A˓W\y.vQ#ۦi4Ȝ'VdI`̌$Ui L U`J-oʛq,R#.kHkuKU \t'e{ 3(yŞpXо334c8nju:kP=1Ptƥ%"qRk<pIFv ߰lVjTFb-i3#j%q&AL>r%k ?g 7idU3qDJmL6׋pO6RdM&Wi;N:!'dJnJ[K"#pÛ"ţuzFcHTdG|Bքo7Zdh-*>&Գ~RS-zM9sbȇ2RiLйN2'eI%DDg,![ȒPrkEQˈqFiU)MtIvRx=m od\o0iV'Vn0J ԓ.*$q^Vjt)&i%2`$JJ\Vȋ,#_mGeQMJ5oE0A*t#R $]&\CUE_\(~ IsN+S}ĺwZ#F2i>lSpإn5zWCN!]%"4Gq;5Y/&䖩R*R 0FL?zkBf%rFdʡx9Bbӡѩ*]VlI3~l( cS.;ZBRTդDJaYt:uyujrb4ۈfMX#$cGʑMΚ4o%9:KI$ g\ʔD ДK&DGXJ̒FI)3b糛oNzUrug.cܔBM#'0f؎[hѺeI4֗XrCfTd[ÊSyW1i<ݣڣYv9c6ژ&RKƓ>\h=&6ɪ4YWЧ"AN̹,2OV0ZM'y>P|pwunώ,#3-Ls̛3U31ͣ5dIjVWnde)B1>@^T:uiT*CLg0i4% <\WMYة3iȂ'xʒ:Jӻ4agqV:.'b*n |ZS~cOqֵٲDZԕ"+xP-4͗QܔRɽY|1N{%&gE1dXc=U{VV9ț簓yIqq624Qi>(IJ @z3trTtꓓqmKQrRlZ.+ͫ:0H6uJqqe2ztjYmDZXfQ "DSoM9'IYㅑ$iy' @' Ot@ '@ $ @$@ bz !H@z ````D  0t.m_r݇ڿ63"9: 8tPXE egѣ#G#kd a=& G1\cW'z 35ƶG9qcբc|a3oJFޑwmF#cNWymF#ft%&TZc2[1]y M>\ #D1kV:Tzo'!ק3j5cg%pqe62_4궋nzB٨m)aV֜0xfR̪S*TxwY%Ȯ1 hZRyZZTX.guwS 2]K&&>^mĭ&nef]nU)ԙK\w[˥KI-FN,{2JKZͨ\0&<|S~AĂԗ͐I%MiI)TiNVLBl)cԸhӯC'd!)*4 #ԅ%KJYZ;FzڦJ+5892#pk}M9Qķ/趄#Pj;.+#+,#- đDiI̸5-ZS7_a8KV'ե 2s$x/r^ YȹmTJ))1J*-R@g%1RmiA)J파қG!^Ep.^IROLfɠ)IX3ΨV$Y6i\6v:Mۭ)&dGB|HưmnW*lw=giY{ެtgH(AʌF~j' O9ҳ(8(3 …9AG4M&di%'j,G ըUZXb[2O2%Q]4Inޱ)i+DTyԙe/v:oA48-8R8p"Gtf4g?=[hջ6J^Jf')[1MnT=_ ؄i3K2֢ƭ|O vƏmmV!)OZ(dOjQI,+JWz)91\V$FFm)J7T?&fThTɤtڋShCy֥$ QpFY!T[TJ*MJ?CyllҦKIe2{ŒSg;`ڶH::*mSpջl%GZ|"I MCDUj4!D?=ՖK~J)ʕELmkSmM@yH X @-*tۋgt^m-4:P~1^I>:c>J[vMf&] ukw%LoZݨ>hVWhIPͫR!E?S thYVII3JVj"35=V;R NČȒ#42ujj"4({={U5) 9EQOEhZWeG8N Z̰{XS*r:53Lò!̷1̈I*2. 4HWyZ(6Mc9rڐq4 JpiS$%I=Gbvөrqn[Sl n$urR* m@vrw(S},$:NeB4F$KvXS70M*Kme=9W6Rn*#*4)7.+ݞbJcJo<\N1VLRM.=rSQ5pb0 902ҧ4G2-'jr]m\nѢ/(n8ђ!n͸H"ǹsxF64vOةTJ|"AK$DkVIDxI[*Jvje*N' \E-2 %NY-.e3j.U~6+VO9%,$z0 -B֬}RJh->BluDi6ԍhWjzO)252LpӍ55zh2vdVEO_ Q2IWL ܕR{vN+V8i$̉kg7ԅ)&uVx;Z}<&X$n.Tv75jML6VfFdj2ϬPڳW"U*eMLSx*KhҌxN {6TZ "?\Ry;|ЗBKQ'#:. H8y3mJ$̈'Ht ^温m7,ۍuA) `ф4<\# JHy25sq)X{)\1iT'7jB6)SzxF| >˨÷學ˑ,ǎudiѥZfZӒ,.Pe57"QSDE4IXzpQR#˷2̧ŦLr6hٶIkIi_2FFGLG">+O\ShIO)0(7伷썅)O1Y Td\2[2ì&k$k5*I##Lڅ=5"jc4+zQO<6ݖ 7eFecQc H2d>\)5$06:igYFCM8]j#KGdžͧC{;e.9fI>VYl'#Ѕsxi1.Z5M(&pM- Z:KJC Mc]6.L5:ee!R= w|g!?QT}G*$;#jY8JVYS𸛹&PKZܐg: V8y%9<1*B{Ot3gGMa-|).Tb-nmDh{yҒF49LZ%^o.'%@SZqޘe0ЭIKhF o/ɵpűjC1hbSV̳r$qJe5x.ƺG=UݭP鲪Q:7]Ky)q%vROI"&Z,x %2;yTuL>h58kqNLȰӓ43̦m }nPlPӑN$ m=ZۭIcbdIv)s)* ՚RnI3QI&#,]3iS ̚[t" 2ʊKe :ueVZIzQj<"MS _R-5㎣Z=[BRS7Y:RFeQ3K*=qԉGKn6K/$I`"B]VSǤI"INOT{dԜJoy9')ID%z^(Q$z]*lE':)O)tK5Z3s $FI#3<Nv҇kH&k*s G6K%R h%HcE.$ "7jLH[JF fIA('LUӮELRSۅ&Rq.$שf=ДRH!]ȹЪc"TZM1oy/-j4:I-&I" XkEn AE&J'-6cxkV0F%:RZYx܂ ,21M:6&OT(djSe ZrF˳i1$N*5 blUJ|LkZT$jq/9ZIudqF2ԪuZ5mIsA޲BM JYpV y#"$.NUoKEumЄ*BR,i;I޵K6eԘTYW\]nTM' $*J^Ѣu:uuI}S;_5{cwApmjUJ\rbgfB fY2NKgLtQPS:smGhէ-"KVض.}Ů:L4j<>LQ-Z zDx= W޺ I )M<܃l3Rj$ԕ $d]1EGHyܑ*Ӊqmiqz֤hBR\8oh–ݯm%9\ӏ.AN:s=4&3#Y~M%7Bb/ڔqZn֣N[I,TKM*e>+P*̍Ladۆj`ԓԮ .'2e_=[vݵEehu ُ& yFIƊȮjCDi4X4IFj5Ffg1LTD22PjTlJHΨ[s$dY63,G){~/.M$+2(ᓘMJiG5L?bARi*m#:Sm̑dZ<\H #u֥.Q7Zy^{1կW :xNY& m hԙv mIdFw^1#P)R*i 6N6zƟruzHBI!mˏmN'+qNaIRzFX,b\Wiڬiim2Chm'QL33 ;:ͧC{;e.9fI>VYl'#Ѕs IkPc4%y9iA`HG.9['ls la0T59j􍤎c=#ՠa59lslss |zT0F##l1tbcl21<-ġ 5)FDDE3w}k~{I䚒vdxQtd$e=qqe S)3qqZYɪ*܉rM-uIQP(Zc:ͥD\d ]; 9TOu{sF)甡*3,`ehʝ>BRSnCu*Z%%d}HlZWz{2%KedL:өi CzZ 4 c9əqdGOGeŢUf&+6݄]qx2ʹ;JʈѣC\?5JC%I4H= 5v*+ b ٷQR{gس1 yui2(phj,7t27s3zR0DI.330t+BB)Js\ZAq #NQoZO1e^ܢAkv-5G)٫9%5$c*5!J<=,`1NH-qO'g`O{O~Id)x\+7ZZhlk33ݧ$9񑧝~]c':[fvmO%)wЂA _q\Uj'ME7QD-Y2, 9%$qaFcT=R_jK.ƒP%yWY&rIQ'KnM^CQELV ;&PmIZLR43;bNT |YQQC~"'BYF-HhIB1c\+4RjeiѧI:;]8ӧ1g~KjBKiKQI+4Dqɽғ34)IzMSU/JBR鏵o0i$:rPq*(3#a`tN53DmɩM322#4 ,H]"2Pj|I gJ Y`diQ(t uLmϡ훒Q\&FIEqi6M 2BM&/'j,ZJ9T&LdRۈ"4HQ-i3IL16wD&liR#;  Kre:/%!#x!QBiQMneX^XF\UNll:s _'lDH5+>32.'sX[FӟFla%lmytI7./-OvzcVTↈnЍFax/<^7^LJܝ.ͨЙ7&kjVOcB&*ȂǒV:|Ҩ'W`ж`Hq6ђȏ(4ĸ-ؖ4/ƧUUPyˑkC6KC)m*[n6HR3$%_ bԺjò=.HlqRiJB%Dj#䇽.9Oo}%R\e.S$̙>=8c8 6`8M }¤R%9-ʖr^"24DFF\ޛbMr%F\sUTU=kDV\m <놓Dxҡ@|\31!q\o zR dԣ=*.*>A̅|hnڨG(ғx:I92۴`c̎0NyuT(48R1؍ q8JTdDZ5:ejN ]PUgZԖaڤ+R-2ȍI7BJiF0xY -~w#Y:4Yj;0n1!~&PM+pCW_TmM[KHKm!By (Iq"}1L\D,/SjwV+BNjТb;U4Dd\pg#SRDwK܀|yA}"kCP"qF$zp\~>e2B)m/>7ʿn^Դ͌LvY'(&J2IRy41 J-uҦ*[4A@]JLuEQP)VbiFKhJ7i8uIr^Kp#dӃu(#ZTRU)i2QT2223z]#U963[4Fi-6ђm$ldNԬƺTZR?R A%$)IRIHL.4hW5B;OƧ6MGeIDM'D%d|wViSSn_w^DPޗ(Q:WI{' Ui2(pj,7t27s3zR0DI.33ʕv\RG4Ijuh,'>Zwj}WNNUDIq nKCǽh4$f@(?kJUY3[򚜦IP%eGڿvWXUeD$jqdI-!$nIcRGYrp.fjUUʞ#9J\i-Iqe.->%I޾2ZDd%u*ʓ5󌲆I 3"ʰX"3b H $@$ú!~lgESvCjX`.TBb*?^џFda6R9O0*5rzFG1\0J6RƶG9Vf|a=*finGzF[ޑ Wye:1]1#!zck I $@=@'@' Op@H@H:D'A`$ ! '@ $@$ú!~lgESvCjX`.TBb*?^џFda6R9O0*5rzFG1\0J6RƶG9Vf|a=*finGzF[ޑ Wye:1]1#!zck I $@=@'@' Op@H@H:D'A`$ ! '@ $@$ú!~lgESvCjX`.TBb*?^џFda6R9O0*5rzFG1\0J6RƶG9Vf|a=*finGzF[ޑ Wye:1]1#!zck I $@=@'@' Op@H@H:D'A`$ ! '@ $@$ú!~lgESvCjX`.TBb*?^џFda6R9O0*5rzFG1\0J6RƶG9Vf|a=*finGzF[ޑ Wye:1]1#!zck I $@r$i i4R%Sy$zQ'n)jQ)yQ%]ttOϺ tZ"ݩ]Q!S(K*Z>9 T6FfCfkZ N)FFj5iɒGP-,Z˳'0R;I} VSEee=6wwrVo$LSiʊRhY!S4Z>OpFZ>* @p6[ˎjK+mi%8'$Ifj%pVmb 6-JEZK3\܄.o(Dh3Jନo{Î}kRrV^MT2IʷYgϘ'#I@SδuXLe1C* :Q*#8IgXzJey%;o%A'&DFj2ԟ{beĉ:2<:fT20j%!Z2̔J#!VWN>&zۑJEUn:&JTr57dfsd9.+{1 c3)u<0օ8nTI8`vrY SM}hw %mη)Ddx0P[&4$$gR{R"=܋+Eꋕ4[2ތ"3(J#KfIyˁJvl9\ ] [%ԩvTSf:۔PQ[JIi5(R8-Z@jOOD:$)ϓH!9['ls la0T59j􍤎c=#ՠa59lslss |zT0F##l1tbcl211FC?H @,A' I!='uqNaMikٶd:,V Qp<+hG=HQ2*ITfɝ)hRT5k#ԾNc-2q!Mo#$I) Z X3$Y֬abi5ם3M0G[l4ԒuĚ˵HfJ2,5)/|\яRgOlڷ3Z 5ڙq#]ZH*[ۤ2fQ'1 pIN&fE,7nYۢg莭eI&%),J=;F4Wsf>DhoIN"C$('x%dJ2Ry?AJ|=nHjKPBIޥl3NtdzTY<(eWzW-.S'U.1^GQ乏ZGY*Kyu%!SJskɩZ 43Ns"3fXeH/\nU%01I85bUZ^2pOzüL.LW>. AE ѥ+$Ffffy0y1JTU. a`0 ԥʌJD=]C_$Ž7$˙X.'<#2%\Oa(IJA(Z Fk%%8Ңɞ3l5{ޟ_`œ0]OgFfMӨ' |8Kνrg)rMO/(\š"Мl%YM7qB5.|7[vHA!9%%į&JKuYMT%Mܔ5ܑ 70DZddya2oO͵Xt"M[|i+Q1 f&>Ԕ$|x**RXRBj6ԨM\ԓSfBb=4$ӝNu #CMm Bə"ceE[)ȡ TO-Y5dZ5ܷ#NId;O"42ƽn( -]OLf6Mi;jyr:i%:[^6Hᚰfj#'g&13ܰqZ2q)A)w)R$z4:v|T +Ff3- nԳAFDLF\T\x`pU6}=}feD)$gSFe 8(mǢ/+/* \&IihhNjƔ5+cI.C.SySʖLf7TY4*23Q +QhۊD$ s,O@= H"&0 } c?.BW\"[rWUL4{$hs la5yDP169jVfW8159zLs5ό7c S3Htb;28Htbc)ъ18wޘ#\xX1L@@' DMƨt~JS*!R[DFۊJRJZ$'$fe  FN<i>%qIm*j%-QʘBdҒQPNjI9{Șȡ"ٛ$8C4D%XIOReU{ӵWy73zEW-C)qhqJqDyTD%$dx"~S[7Gkn46.zpFI2ANSȪtxӮ)/#ř9nlj%!I> 툈 6U?\ݣ'u'*1rOOn)N\䜙z!UpL1slKQ&Gm9"VV I)FdNLq15"ѩ,J9KJJj6ϊF93:/PuG\nwiq8mQ⾞eɷfdDd8Vm[ȕM3JbMSZ\ۧ 6YdlUH#ϓ4h; Ti1!=&^ҵ-)%)J%d9,bզ\;LB*S!Ja.k5HSdQ CKCZd9iҢɨ\ 8R'QzPh,*AkR5)&E m]߅wo2.;:*0Z3T8Χ2#HI$J2sI)iŒFh%sfzj2 BHC14$Y5GǝFg"С#4 Gv|qM}$JhKopr%i= Jj͈.VA!l4 <FJYI4JӫSV~ۿN^aW5L=it. 4HotIQ8 HfbԄ-)^Nr#2qJY|R.ܴEƱsD4$>0~<ѥ34 )J3Bjg?QΧEr_&~ZiiK6Yn,ȐhNJKQ$I_vo|q⛜t*9By6Y5i^5Ƴ<(p̫Oj4RDMI7mɴJ%%nl3ffg+%E2Rw$U%r& M[[!;hJu8d$ӯ$*E \sLRbLk65HԝiJ Bd QYG ⮮׿b4e^m}܅-0LiFJEӯR]ZGL%EQD6VinBi3%K7ReۖIcG+uٔZN.($AޣZ:SJJ%K$FXlEAu 3Ih)_CЖ,±UڵyOǧ0e>i쩆y*\,n)djZՃ=G|F樮nюEG%uG'+ҵZ}G \p^ۮFUI2ZeFviJTGZHȔfQ 0#j4b!O1]=&x$H}fGk/qS+2&d&f&K'Өȏ^pfFg-Rijlm]Jb2 &.%fkfe$j#~u2L-"4wTcJR=FD4n]TCȺqjKmTPkX#5IդU{sm]QͻFkX/A6ѻ4tq^UZ;~:-.FJ˘ˉo+i5߆}|T)4'fEG+5h“oJB%iQJM&FG2=dewLpdm;4q6%6M.N#VD,]&j3dX#2p}U4t7LZrVvڔff49%fĨrz\<ܯwt(՞]d<7#au0lɥ632K&gՓt1:sq7"׏A*)GZnJ#|N('FTM $Iˉ)%$ O@= H"&0 } c?.BW\"[rWUL4{$hs la5yDP169jVfW816Vu\\Cl|adDfXR_HhJnW1Ti+|tv;es[lH1ҕm iЪ+)Ǚ|] w6-7!N|R3:1]1^ w6s-_} w]1GV^eҏk3RҍQrg/1k ~D{_X@Ox=\K_J:k ~Cx^{_=\K_J:k ~Cx^{_=\K_J:k ~Cx^B{_=\K_J:k ~Cx^{_=\K_J{_=\K_J:k ~Cx^{_=\K_JYnD#)P  j$%J2.sғ<3; R_\v|ۑ59!*jX7t5,϶2 \k<҇k ~FM'DZCMɫrRftVzhR[kJqj-"JMaDqu(̉&vhF(y4+N8 2R=;џ7""{_=\K_J1T-*9;⹷YThgP7E![hT[ni'Fzɞo4W|k*L#V4r-ѓ CEF3Ii4?k ~CxT!=djJ;\ݑwVdT#*->;W12WQ*J.Ju'd[ZmAƥI`*R\˃ ULIZ7&+ NHFI. +x_bQևiQ)I["V57Օ",j)4n;9j5Y]6ۑ aI(3IԜ9Č wǰ=h7; M̃&RIiJJ"Qg)<T\&]4eò`52̺(-2&!NtyR2*~i-9Mԫ̓S m yk{'G#p0{GS|#jc$h6UALui?l{OoyFUP\ GmuN{|Tj|}jw ZM=R #F[ilMDD88ٚ).Pj/t"G(Y+$'jzV`Gd5YY~$G!C2P'!JuFJqN)$~XqU!SvbeT6LfIu$jSx"Ҳ#Y.f:|%l3΋STm]֣R0%&dǁ}6;0TuBW T'tvQc,ҭMDJԨIazu$+F0>ѧJpMw)%(;P85_P |uOI>df8ݩ1R)$jydjyESEÛ>#ҚO8M-$Fֵ`T)F}&fgǸ4Pl&rXm&4 )=>nFE>>,j}J7M1K 4pJ+Rr\p. j+4EjJeV-z-E4R츍2RFzL\Ǔ810CHg:f!QZd)$BQ/)m)ɑ"#,6`@ZtCmK9KA%Bv(J )JI /?H#vg{1 @_3rĀY9|oawOd;ݿ/?H#vg{1 @_3rĀY9|oawOd;ݿ/?H#v=I&kͦNdy GVz?Z>JdێS蒥J4Zwg$Gx27?HƪN/!_oWtt5R߁{ӡ]|oawOwO_3 G/?9|ob@,g{0;ݿr×v$wO_3 G/?9|ob@,g{0;ݿr×v$wO_3 G/?9|ob@,g{0;ݿr×v$wO_3 G/?9|ob@,g{0;ݿr×v$wO_3 G/?9|ob@,b*OcjmK"Qqyjf\KC&I!:6ɲN ,I tMҫERTfE*62*"f{yKIh#$Iy36r Iy=%j$a**>{?Fў*0I/nK-RJmzwO0 P #xymon-4.3.7/docs/xymon-config.html0000664000175000017500000004103711535462534016473 0ustar henrikhenrik Configuring Xymon Monitoring

Configuring Xymon Monitoring

The Xymon configuration is kept in the files in the ~/server/etc/ directory. If you look at this directory, you will see these files:

  • hosts.cfg is the one you will change the most. This file contains a list of all the hosts you are monitoring, including information such as their IP-address, what network services you are monitoring on the host, what URL's you are checking, what subpage in the Xymon web-pages this host is shown on etc. The name of the file - "hosts.cfg" - was chosen so it is compatible with the Big Brother system which uses the same filename and file format.
  • analysis.cfg is the configuration file for data reported by the Xymon clients installed on the hosts you are monitoring. This defines the color of the cpu-, disk-, memory- and procs-columns, based on the information that is sent to Xymon by the clients.
  • alerts.cfg holds the alerting configuration. In this file, you setup the rules for sending out alerts about services going down: Who gets the alert, how is it sent, how often, whether to send alerts 24x7 or only between 10 AM and 4 PM on weekdays etc.
  • xymonserver.cfg is the configuration file for the Xymon server. This file defines a lot of environment variables that are made available to all of the Xymon programs when they run. Some environment variables that are defined in the Big Brother system are also setup by Xymon, so that Big Brother extension scripts will work.
    The initial configuration of xymonserver.cfg is setup by the configure script when you install Xymon, and in most cases you will not need to change it.
  • tasks.cfg is the configuration file for the xymonlaunch tool. xymonlaunch is the master program in Xymon, it is the only program you start to run the Xymon server. xymonlaunch reads the tasks.cfg file, and starts the programs listed here to run the server. Some of the programs may run as daemons, some of the programs may run at regular intervals. If you want to use some of the advanced options for the xymongen or xymonnet programs, you change the tasks.cfg file to add these options to the command line.
  • graphs.cfg is a configuration file for the showgraph CGI. It defines how the graphs are generated from the data in the RRD files.
  • protocols.cfg is a configuration file for the xymonnet program. It defines how network services are checked.

Setting up monitoring of hosts

The hosts.cfg file defines which hosts Xymon monitors. When you install Xymon, a simple configuration is setup that just lists the Xymon server:
Simple Xymon hosts.cfg file

There are a few things to notice here:

  • Lines that begin with a # are comments.
  • Each host you monitor is on a line by itself, with the IP-address and the hostname of the host.
  • You can add extra tags to each host definition, by putting in a #-mark and then some keywords. These keywords define how Xymon handles the host.

The hosts.cfg file shown in the example has only one host defined: www.hswn.dk which is the server running Xymon. There are a few extra keywords thrown in:

  • BBDISPLAY, BBPAGER, BBNET are compatibility settings for extensions written for Big Brother. Xymon doesn't use these, but puts them in the hosts.cfg file to avoid problems if you mix Xymon and Big Brother modules.
  • bbd is the name of a network test. This keyword causes the Xymon network-tester xymonnet to check if the bbd network service is running on this host, and send a status report to the Xymon server with the information about this service. So you'll get a (hopefully!) green icon on the Xymon webpage for this host, showing the status of the bbd network service.
    Network services are defined in the protocols.cfg file, so this file must have an entry for bbd defining what TCP port to check, and possibly also what data to send to the service and what to expect as a response.
  • http://www.hswn.dk/ is a URL, obviously. This also triggers a network test, the Xymon network tester will try to request this URL, and send in a status report showing if the URL was accessible or not.

By default, Xymon will always check if the host is up and running by trying to "ping" it. This results in a conn column on the Xymon webpage for this host, showing if the ping-test succeeded. If you have a host that does not respond to ping - e.g. because there is a firewall that filters out such requests - then you can disable the ping-test by putting a "noconn" keyword on the line in hosts.cfg.

As you can see, the syntax is pretty straight-forward. Need to monitor an extra URL for this server ? Just add the URL to the line. Need to check if ssh (Secure Shell) is running ? Just add ssh to the line. The full set of keywords you can use is described in the hosts.cfg man-page. Many of the keywords relate to the way Xymon displays the information about the host on the web-pages, other keywords deal with how the uptime percentage is calculated for availability reports, and some keywords - like the bbd and http://... mentioned above - describe the network services that are tested for this host.

Monitoring network services

As shown in the example above, adding a network test for a host is as simple as putting the right keyword into the hosts.cfg file. The default set of network tests configured in Xymon 4.0 is as follows:

connSimple ping test. Enabled by default, you can disable it by putting "noconn" into hosts.cfg.
httpWeb-server test. Enter the URL to request from the webserver.
ftpFTP server test.
sshSSH (Secure Shell) server test. Supports ssh1 and ssh2.
telnetTelnet server test.
smtpSMTP (Mail server) test.
pop3POP-3 test.
imapIMAP test. IMAP version 2 and 4 are supported, for version 3 use "imap3".
nntpNNTP (News) server test.
ldapLDAP (Directory server) test. Enter the full LDAP URI if Xymon is configured with LDAP support.
rsyncrsync server test
bbdXymon network daemon test (historically named after the Big Brother daemon, bbd).
clamdCLAM anti-virus daemon test.
spamdSpamAssassin anti-spam daemon test.
oratnsOracle TNS listener test. Will attempt to do an oratns "ping".
qmtpQMTP server test. For qmail's qmtpd service.
qmqpQMQP server test. For qmail's qmqpd service.

If Xymon is built with OpenSSL support, the following SSL-enabled services can also be checked:

httpsWeb-server test. Enter the URL to request from the webserver.
ftpsSecure FTP server test.
telnetsSecure Telnet server test.
smtpsSecure SMTP server test.
pop3sSecure POP-3 server test.
imapsSecure IMAP server test.
nntpsSecure NNTP (News) server test.
ldapsSecure LDAP (Directory) server test. Enter the full LDAP URI if Xymon is configured with LDAP support. Note that this is only possible when Xymon is built with the OpenLDAP v2.x client library, and only for LDAP servers that support LDAP version 3 and the "starttls" command. LDAP server that use the older non-standard method of tunnelling LDAP through SSL on port 636 will not work.

There are a few network tests that Xymon can run for you, by using external programs. This is not a very effective way of testing, so it is only done this way for a few very specialised tests:

ntpNTP (Network Time protocol) server test, using the "ntpdate" command.
rpcRPC service test. This queries the portmapper service on the server, using the "rpcinfo" command. See the hosts.cfg(5) man-page for details on how to test for specific RPC services.

Monitoring host-specific data with clients

You can install a client on each of the hosts you monitor, to check host-specific data such as CPU utilisation, disk usage, if certain processes and services are running etc. Xymon includes clients for most Unix-like operating systems. A client for Windows is planned but the programming has not yet started.

First, make sure you have installed the Xymon client on all of the hosts you want to monitor, and you have these hosts listed in your hosts.cfg file. The Xymon client will pick up the hostname of the box it is running on automatically, but it is not uncommon for the name it finds to be different from what you've put into hosts.cfg. So if you know that the client is running but no data appears, check that the hostname used by the Xymon client is the one you expect. See this FAQ item for details.

With the Xymon client running and reporting data into Xymon, you should see the cpu-, disk-, memory- and procs-columns appear. The color of these status columns is determined by settings in the analysis.cfg configuration file. Here is an example of how to setup a host:

As you can see, there's first a definition of what hosts the following criteria applies to. Here, it is only a single host: voodoo.hswn.dk - but you can use various filters on hostnames, pagenames and time of day to determine what the thresholds should be for each of the criteria monitored with the client data. The analysis.cfg man-page describes this in detail.

After the host filter comes the criteria used to determine the color of each of the status columns.

UP Sets the cpu column color, based on how long the host has been up. After the UP keyword you put two time limits: The first one (30m in the example) defines how long after a reboot the cpu column is yellow. The second (optional) value causes the cpu column to go yellow after the host has been up for this long - it may be useful, if you need to reboot your servers regularly.
LOAD Sets the cpu column color, based on how much load is on the system. After the LOAD keyword you put two limits: The first number is the limit where the cpu column goes yellow; the second is the limit where the cpu column goes red.
For Unix systems, this threshold is matched against the 5-minute load average value, as reported by the "uptime" command - it is therefore a positive number.
For Windows systems, this threshold is matched against the CPU utilisation - this is a percentage between 0 and 100.
DISK Sets the disk column color based on how full the filesystem is. This takes three parameters: The name of the filesystems; the threshold where it goes yellow; and the thresholds where it goes red.
The name of the filesystem is the mount point. You can specify this either with the full path, or you can use * meaning "all filesystems". You can also use regular expressions by prefixing the expression with a percent sign, e.g. "%^/ora.*" would match all filesystems that are mounted on a path beginning with "/ora" - "/ora/db/vol1" for instance. As shown in the example, you can have multiple specifications with different thresholds - these are evaluated from top to bottom, so it is best to put the most specific ones first, and the general ones last.
The yellow and red thresholds are percentages - they trigger when the filesystem has filled up to the percentage you specify.
PROC Sets the procs column color based on what processes are running. This takes at least one parameter: A string that is (part of) the command line that the process runs. You can have a simple string here or a regular expression - Xymon will scan the "ps" output for the string or expression, and count how many times it appeared in the ps listing.
The process count is then matched against the thresholds that are the second and third parameter - the second parameter is the minimum count (by default: 1), and the third parameter is the maximum count (default: -1, meaning unlimited). Note: If you want to set a maximum count, then you must also set a minimum count - even if it is 1.
The last parameter defines the color used for the procs column, if the process count does not fall within the thresholds. By default it will go red - you can put "yellow" as the last parameter.
You can have several PROC entries for the same host, if you need to monitor multiple processes.
MEMPHYS
MEMACT
MEMSWAP
Set the memory column color based on the thresholds for memory utilisation. Each of these keywords takes two parameters: The first is the warning (yellow) threshold - in percent - of memory used. The second is the panic (red) threshold - in percent - of memory used.
By using one of the three keywords, you can set thresholds for the physical memory (RAM), the swap space, and - on platforms supporting this, e.g. Linux - the actual amount of memory used for applications.
LOG Set the msgs column color. This takes at least two parameters: The first is the name of the logfile, the second is a pattern defining which logentries trigger a change of color.
Optionally, this can be followed by a third parameter defining which color this LOG entry causes, and fourth parameter which is an "ignore" pattern you can use to filter out lines which do match the first pattern of lines that trigger a change in color, but that you really do not want to trigger a color change.

More about logfile monitoring

Configuring the LOG entries in the analysis.cfg file is only one half of the configuration - you also need to tell the Xymon client running on the monitored system that it must send in some data from that logfile in the first place. For that, you must configure the client-local.cfg file with the name of the logfile.

xymon-4.3.7/docs/bb-to-xymon.html0000664000175000017500000001313311535462534016225 0ustar henrikhenrik Upgrading from Big Brother to Xymon

Upgrading from Big Brother to Xymon

First, you should realize that this is not a fully automated proces. You will need to do some work yourself - especially with the handling of alerts.

First step: Install Xymon

To begin, install Xymon as described in the Xymon installation guide. I recommend that you configure Xymon to use the same user-ID as your current Big Brother installation, but have it use a different directory for the server- and data-files. The default is to use ~/server and ~/data respectively, which is unlikely to clash with the directories where you have Big Brother installed. If you do need to change the directories, you must edit the top-level Makefile and change the XYMONHOME and XYMONVAR settings near the top of the file.

Step two: Move the configuration files

A couple of configuration files can be copied directly from Big Brother to Xymon:

  • The bb-hosts file, must be renamed to hosts.cfg
  • The bb-services file. You need only copy this if you have used bbgen before, and added custom network tests to the bb-services file. You must rename it to protocols.cfg.
  • The cookies file. You may not have this file - it is only present if you have used bbgen before and have setup HTTP tests that require cookies.
  • The bbcombotests.cfg file. You may not have this file - it is only present if you have used bbgen before and have setup the bbcombotest tool. Copy it to combo.cfg.

The bbwarnrules.cfg and bbwarnsetup.cfg files cannot be copied over. Xymon uses a very different configuration file for the alert configuration, so you will have to re-write your alert configuration for Xymon. See the Xymon alert configuration to learn how Xymon alerts are configured.

Any server-side extension-scripts can be copied from the $XYMONHOME/ext/ directory to the ~/server/ext/ directory. You must also add entries for them to the Xymon tasks.cfg file. Beware that many scripts rely on environment variables that Big Brother defines, but which Xymon does not define - in that case, you need to setup those environment variables in the xymonserver.cfg file. It is probably easiest to save this until you start running Xymon, and can look at any error-output from the scripts.

If you have modified the webpage header- and footer-files in $XYMONHOME/web/ then you can copy the modified files over directly to the ~/server/web/ directory. Note that Xymon has a number of header- and footer-files for the various CGI scripts that are not present in Big Brother, so you may need to setup a few extra files to get a consistent look across your new Xymon installation.

Step three: Stop Big Brother

You are now going to move over the data files. To avoid confusion about files being updated by Big Brother while they are being moved over to Xymon, I recommend that you stop Big Brother now.

Step four: Move the history logs

You may want to save the historical logfiles and the history of your status changes. To do that, move all of the files or directories in the $XYMONVAR/hist/ to the ~/data/hist/ directory, and all of the files or directories in $XYMONVAR/histlogs/ to the ~/data/histlogs/ directory. If you prefer to keep them in the Big Brother directory, you can copy them over with "cp -r" or "tar" instead of moving them.

Step five: Move the RRD files

The RRD files are used to generate the graphs, if you have installed the LARRD add-on to Big Brother. Xymon has RRD support built-in, and it is obviously nice to keep the historical data that has been collected over time.

The filesystem layout of the RRD files is different from Big Brother+LARRD to Xymon. Instead of having all of the RRD files in one big directory, there is a subdirectory for each host holding only the RRD files for data from that host. This is easier to manage, and also speeds up the graph generation when you have many hosts. Unfortunately, it makes migrating from Big Brother to Xymon slightly more complicated.

In the Xymon source-tree, you will find a script xymond/moverrd.sh. This script moves or copies the RRD files from the Big Brother+LARRD structure into the Xymon structure. You must edit a couple of settings at the beginning of the file, especially to set the correct directory where Big Brother stores your current RRD files (the SRCDIR setting). By default the script copies the files over to the new structure, if you would rather just move them then change to "OP" setting to "mv".

After setting up the script, run it and it should copy all of the RRD-files that relate to a host currently in the hosts.cfg file to the new directory structure.

Step 6: Start Xymon

Start Xymon with the ~/server/xymon.sh start command. Look at the logfiles in the /var/log/xymon directory (or elsewhere, if you did not choose the default logfile directory when configuring Xymon) and fix any problems that show up.

Look at the webpages generated. For the first few minutes, there will be some missing columns and icons for each host, since it takes some time for all of the tests to report a status to the new Xymon daemon. After 5-10 minutes all of the standard tests should appear.

xymon-4.3.7/docs/xymon-apacheconf.txt0000664000175000017500000000256211535462534017170 0ustar henrikhenrik# This file is for Apache 1.3.x and Apache 2.0.x # # Add this to your Apache configuration, it makes # the Xymon webpages and cgi-scripts available in the # "/xymon" and "/xymon-cgi" URLs. # NB: The "Alias" line below must NOT be used if you have # the Xymon webfiles as the root URL. In that case, # you should instead set this: # # DocumentRoot /usr/local/xymon/server/www/ Alias /xymon/ "/usr/local/xymon/server/www/" Options Indexes FollowSymLinks Includes MultiViews Order allow,deny Allow from all ScriptAlias /xymon-cgi/ "/usr/local/xymon/cgi-bin/" AllowOverride None Options ExecCGI Includes Order allow,deny Allow from all ScriptAlias /xymon-seccgi/ "/usr/local/xymon/cgi-secure/" AllowOverride None Options ExecCGI Includes Order allow,deny Allow from all # Password file where users with access to these scripts are kept. # Create it with "htpasswd -c /usr/local/xymon/server/etc/xymonpasswd USERNAME" # Add more users / change passwords with "htpasswd /usr/local/xymon/server/etc/xymonpasswd USERNAME" AuthUserFile /usr/local/xymon/server/etc/xymonpasswd AuthType Basic AuthName "Xymon Administration" Require valid-user xymon-4.3.7/docs/xymon-hosts.png0000664000175000017500000014503311535414771016207 0ustar henrikhenrikPNG  IHDR,Y pHYs  ~IDATxhSi?͝#$LL05t\hDi.ێ˴՛u.iN[a5)6~M|qIʭ$r;4}Oۜ/&9IOLuN\u9Wsu`af0q`@& dA,0hM F:ͤs^7̧x;4 n3nܻٸѸq?7hu`޿W\V8Eݻٸ1gioV^tδ':g )SH1KG@| `DЉ֡~fiR^7};z׋k]Goz6nB']@)[ƫrPޥw1N#vN#CX b'cs%!9u9l>kZ+/ .M%o7>uNa#6fK3>ƍ_ƍ?^G /ͧ @ |~xҡأt$;SѶbOQQJQ] m9z"r-R6xf0!z ]rzuP=s$t4i6ڦ,&}MgB`;;A)8w3EKYOW}Qk֬*ʛg]]nIt6\ Ǹ=1.Xf8*N7s*wqK7Ч0c{l n> Qą8+.Hvjd[/Kn#UÙۅ{\}2qk::RNf#po.nGiKh;7-W,kZ~Ѧ-\`v пT-"ͽ6J-( K?R=hҠAe̸ܸpejmC퉶f/7[iVEs^jtF::v]p.fjj?mCIIJO”\`>42i,~Ι)MrhiŊAUՠsf9s9lmoji͙{~(:ECzzטSczk.%wt9g'L?lHK>]Tx:c gaW}Uu]+W7;\G߭ww/.붡S|n/eH?/|m5Xl]=iΆһv׻Q_ffErKm-^km"3KfD+os\VJ*.\J@9~߁++5jd[*h/j?QU0'%9IvFZi-tG-uv ˿9~~uQV.7I״DGQK_ֱRi "N v_cr.XQpK:"<:>8̿=?txJxBM/}]K~{ҼZyc\"(44I| ̵D|u/nřuV# h2c7u$Ӛwe@*eʕFV~Z#a7UD/Xfބ%By{xlzn[̲˽s\kcmgxr/ex9e1]&i&ͤW߳'3Ǯ' 0L Ӳ(+?OAkhkяx~ooD(ZܔN_b¼Ǵ _>> 3ՠXS.cd̊`|liOt4{];]pɒZ -ִac(2\fduߵ]̳'35Q34`e|)\\>r-Pt\H<zÕ##ÕtǛ w곮UY]NJBxt4/&/C^G ti͆gٰv7[[ A~kd[Zi{ ̽D r5 &ů0=&e讧K覕RK [A.=nmrۭپriDQ6BͥD њzz=9'},۽貪б1m2m'n&W}pWRzrUcEG1jLPH}3Ev}s>7-|mԧpKȯC%4L-\MXWs/3Nvxv{>n5 K>i{HݲfBkU~TON!AگЀEq}M%{M+$FFS~K 5M`5e CϞLoLW }1f}GEP4Ջ~Mun: FT /?!2(7[Mn6mla(44XfȄA,0hM @e, Gdnaaaaa*C8mi4GZ'#e7[^;o{qrIP |V va Wϧ&O/$qi_ 0hŊ DZi]nݺu넓^ޔRO)$>[>3M rT]uT]!לMrܤtn(=Ց(d6mQk-*7m':c”aʉmDm6xʼP~H,Rjkꜚ?D:w~F}XK*kX_0hRcS1vݱi:ǫb'U7[PM %d{|( z]yr0}`<ᴟCoBrB@#'lSӧҝӴ=^W9Oh0+m [h@LJ-\`6"mvFڤC}(V8IM3ZFR׫C4?=r\ S& {XfR*=J'͌0)?lR?3p_ԡ-`B{A}4'^ͣ7ͅ) Fhk޻-2;:nmrۭԝ TO2p3Õ2G9YҢ 5[lsos8.ww[ʽJ)STSoO5=JR3efjzc/9sZ96j,>#ķڗpΊ8)'#P{-N~g'-giR?P.XWЉ$&I_l.v26zqqmC'چ)?.<ّH'vUP*XwgjErPxېi䊷fR KwJun:OɎf̜@| eo̩X)^DϲԹvѹ,U$U9rq6rQlyidid4f01>>Ū뷾,qYCfTT--ٖͯ }/} 5:`_tjB؍fkffĸS1.6;K?&۩~pp%Ň>s$?cH׽뎮[Tիpu`u8/bCu]UH(R%BN,Zz[ p az.vrzN.sSXxU|0^E~jlI{+ tz|`q@.w4m4i#?j K_^KvG:5R;5)6d [Q 6]nx7"?E EOf]6YËڢ5wMk.R^K:::On|Q^  63ua&i۾]tqg"#\@Wfb2ʴ~ɴkX^%Gi{wUѠ*^o.^gZlXdW \k~wZ~-Ӎ9ImahF%O,,?_Gl,>@dMΙS\ -HO]qF:ڠ:eeh.¡A.4Z3K:OadF,b`0 w9Kk\Jd.XalM?}}ϯ@KTpOjg6Eyy=\`>bO$t\E\{7mCm_e>LO79+N˜QѥvT9QkwѥW[il|eT+hMZ?MnZ6ysۢLYH~M ;3+~WH2hBOWѬ|Yڢ4kRi_) 5#lSG`an6n4[~?Uvm(<+#;-Y5O!9E߳'[{1nc8c轤 nU 'i?%9ԟ7)esv"iڸwi#ݒ|W86Yj+/En֠2zkEq+irD1㥧M{'b?naN)-6LqdɍrnzYv":ma`h$ۆzdӭ^YKg~>6+~zR?(-hz2>zk- ,Z8 v4ǧ*?=< -ogJgk`Ų։Hkj i{YF4͜JN @j4AK4p&3bt`>~8Wy q6&lb4&Y` db&u֭[N&Rj)5-<&ReP84)Qu}Qu\sB.75rҹ٢tTGvsEܴA3֞ S /)'׶Ѷ<2m6+~Ba#ճld\GǦGݻd`I4)±Vzմ?KUtI-{ ٿPK~l%ۣ#D+/wWƿogU']Jt7MC C ui zYCn۷\WO1^K/V.O,&˫c~ibH4h5-5E[d[kdΧa|kw}6bxF+6&Ə72|jU!U*ir䮼R٥WvUh/V<7< QXw xc硹wU["ێX ;ҡأtL~qIDSfB(DVGoz텗݈4heԒ.q6q!)?8?K\\)_z38';Ms!~|vZ1Gi ]QP>?cWzw*8ak'dڟ II۔6UxP/=S6ux&8aph5ͥBwL:C!}~yPU|~fNe[FS++OOQ\nKΙS\ -IO]qF:ڠ:eeh.¡ARt1Z3K0RXadFկ7w.7K[cEAK*S2mΙ1n>c>lmo%T Y'lxRHyѢ.0o Y:uy1As 6į2gW:  _ I/?RM8`,=RkdЄYZ^=E=^iL vRIr qfFe)qT;*]-.+ʈNKE3|CM~nN~ .Ÿ@#W{/z/jUA1AoO Ԕ29;4mܻٴnQQkV+, OCKKy-Btы]-ʮl4\OϞ& /=̷0 RtǴy_ӓY8ҡL JV^sZ^#~N#sU_x{jY@ yzvڂR"ދ{vgag R@Zk`5JXp׵OO\}q6~!&P}HDuoC@ae-HK`m  @4&Y` dA,D آF[v牴{/FgkAΐ^}_ ٰF`i$ՑHX: U>5gû`֢ߖ6&O1 5 F.0;]^#cbLiHڏ&4l=k^{x,6Q#w1\kONw6lz ӿ Yn5kssafX5r Ȧ)93zS` =K4O{hd[kdK`zB^MNQa"Z`_\'CժzOՊZAP@62L.A`mB &Y` dA,0hMH춦v$MTHDuu4oImݺu֭xߎ:|Hke$6=J|:] b6ڢ>ϵ>}Y6 Ol#'lQổ5;UּS蘾{}7H= C;k; ܒciRuݻ躗Oo=>ԇ2%P; Z[ H[]pb pO߭aR ɿPJAΐ^}_ ٰ.<Q}Q.0l ЧG팴W٧qvN[mhkI< ٲob:hBQ֪MR۩t^ C[z6J ac6ҍo <ћ;d!XfR*=J'^dR~٤<KgUwYCw:W(:E_h SһD ~=Tc?G]G|~6&;5]os\sBN{ӺB"xMu~('ѶYSg^S'LOG$̿0 g JfIћ4<l7^0$*}a oKybH[bI]~>˫8XbO3Z_Ӌ?bc~ɥȯ=R}zck,m5oGq??_}7K}(5&W׫lB-ʋgܕ.m)$f˾͖1_y}?f۲ tQޥr';d ٮ >S_NbچZۆM#WMRzuKwGZ_2'ۦMŸS1.6;K?&S۩Nܥzrdd2r=~~B"Yo}d{|(ٞxMvճFEȼ_TpPcs&}#=7[?o ʖrxfK<" ۔6 nU&DԯlSɥOMl|._b~8KDtOW\dl.^'^~㖪u[ĶGAd =Z6Z5-529V]ҞO2ˌrqu_l[o;RJ`[XXXXX~hnjruҹ^WUY5[[>1}+=su.b?w~~e)}y8}+-'d;u gtԞ'GM^_X!2 L=OωI;8Iaȇ?+$bRT|Y-JwbKDg]2c oR/f}+\f =W>׎ϋW~7v~ ?w_'^Ex};+}ѫYg3> }t{Æg059/NbLe I#FfTݔmNehو1N75nxq#̧2~cOsGDG,]ۙZBm4=NǮU>?^YjZ?I'LII -/eVT}ҿU*EZKϜʶ]>=:藨Ҭ?2Mv>Hl!3&E|J| *>m]nk=.RVJaʠ xm+/ﮔ/]?KX4UO{t?9m)NFn&ů:ՠQg2gE_ѻ4_NP #34dV7'!ݽ!]fsEX7r}ܓ0Rjʼnͩy:mCmS95^z9s뜡Q~uM8`,͊K7?xnwδ'1=Qazy9]X^M ,_̽[R9f]1/AuUŒ=?e iipGEQHJapFV~: b~_Vjjs蒲ú؃}1cESbfn9qTmSG`ݍn6n\i;t8-wT(6[]WUhK*1}21m޿WzA%QV.7joVzD7u8ۚ4ieVnRҏ\4zizl9UW}1/iT hUv).}*S2Nfrw\Tlz)OPYjxGû5b?%^zwuW~׻v^ӻ45_k֫Ƿ+ڿTiBWrorm|qtOt~B+\Z$>3vuWeϋ)[io:ZमϲkV@@&s>,ɭ|ޭF6Z4 0 XN|-/o)~8glb(>i{u:!Vn| ?&!;+T:+,vτRJM`w[[ bw`%D ?TE+Lg&هN4a @4&Y` &yy"ދY6 #b$3׻v׻;z6lxĆDX{r4Iun:.BzU(m 4l :aS uͧ.qm NpqtȘS<#6f 4[zz/id] ړ9A A=eoCzրs`Za5Vb\k2lJΌޔ3ikϒ3Mh(>i8`-ٖYl.v26X^>zkS~X?-W86IPS"|Vfd >> ˄k8X~d  @4&Y` djM"ދ[nݺu 3ՑVIJ춦vj$_J4OXJ `m1hbZmQ]NH,+‹{]wڟqOD>,_dJu]oO%&DǦGݻ軥?3tD wwzzWPwgPφ p~L-i0}^٧qvN[xZf#\ vog4g~Rr4Iun:.BzU(m o;k߰ҹ٢tҫy93ܴA'~}wYCw:Woա-`B` cveKϩ/T]uTt*((b\AV4ĤhIIGQ!ל{)]ጶ!_[mSZU"~_zPgCPR'ĉq?~0J‚CKpPR*>-p ץI?$i{pW_ BOL0? }5SpŠ=?ҥRZDplU8;F> 0ssw=MS0x¹_J0㳱F^~q8a>*V~VHo?k۾ȵ~5{ׂ^;ݘc&|iqKk͸}dZ2%Hޞ$Ml j#j>~V ʒ3Mh(G^BSb'csko.Q~YJ)S0(ӼUtii͔VKVefKC/= -_7gC熰YAOTK(f~_o￐&&{^77m,tIh.¡AGJpV|-sV^ nU!ݽ!]!9Ӟ蜡C:cp]3:`_t,s`Rsh}]outjpl(tJ]')qT;*]-.J_ii?0Nf1wkx~"ճlDFYܨY[8ND}SBm|qs'i?1*Q^܉ϿM? U aUhKKAy*WmlSgƽ͖}͖6FtǴy_ӓe赔3Րt4*FeGwGSc1oyO<|3-Lf @YG Yyvڂ@@@VY{ ϻFkf c8⟰r˟|_Q1/JOim(Eۄt[[gřJgwnkkM Z)ϿkxWomQ+= Gߥ}%XD+Lg&ه3M @4&Y4Esi^Tϲ&!ݝ!޵Գa#6 ړӠI#u;Ցpt@RիBA}hkPφw? Ee˿-7mM 3bk>uɍnˍ\`v xFTŘ瑴53Mh(zlS{)6XlN#+FFb֞n lp,ճܘk֒70 8zk\#M)dSrfLX{iBC!&yOK/mȶbs9 Z`ћ^{:Eh±O U5&ldp]&\ M @4&Y` U9hb5]ۺP{/Һnݺu1|։H$Mk5kxg(W-'F7Q{U~POXZHAĠ-jmE}kw}DH,)<u}u}JrҙK9}*98-sTp/<nQxw uﺣNӱ(\tlptL߽)~~J>' {>w>w%Ae2%P; B[?++?<*&!ݝ!޵Գa#6\x&=eFEߥ}rI U!6lx̆+y8/ en6u6R.i3V c ?oV}:gښm5h#Z\]/@.r4Iun:.ЀEPԳVbФvFMk~Nk =]Xj,QԑO]8TDՒ-2ǃ|'h I*¯JPʅOB$bKJFŶTRKlU`:IzNZhG-ثxʔoroOMo_A\ڗy2O [*?œR{(f} {a3ҿI{ڣt%'H&9ݞԳY6L6g +pcN!ҁtfI*>q{bMlPw5+GF+ip+F*E*$'.MN/4nxq#̧q6q]#r\hFF&"6}!N*S3S=Wc~ROGStO7Je^UYXYxsSj1˗e'trS~G5b*g!s?S?Sg'QH~_Yf*SLgn)qe-b䠉 Ƿ[}SY=mM@S nmrۭ߻+jk޻њӚћ^`/g ϺT|.A{@Km ěHG;vO;duP:HK?>ln^j1?KEZjZ?IpII۔u˔b&~b>?GګxrTWQϳ[ɽ_?ױd ?N;R?%vgUjP4n>iiFFі3Ar?K/H|HYzX$LÕYlKG}B>7fmMSO_˜ݓ8-sVA7Ay+$ԄiOtPibhCvΜ1?h]Gm&ECN h= կ:j/ΐn\rO~.?QK(>}4 &C-͇d8G)?-R;s蔱ú؃}1cE.f2!P^!0kn(H0m mOܤW׫9cWGx{+Jck镙dYAsp4Uo:mܸwq#=9Ky5]>mq8: 'i? O/ަ+ަt4*FeGwGC0Soށi䊧)&.$XefK^sZ^#~N#sU/S 8-wTPz~eccdcڼ߯I Z=?#m~ķt2 02;:ixW;mTϲ>ierfo5_~,;A  _֚{{Tzc(6K]?;?)MC?-/3m'u}u}^-rvuC+zxgOQwn)XɛOI|R@ N͟TNM{Eĥo3y=h0.0\`:V)IS+O-%/JOib5w`p ƿo+R:p^CҍotҤJgřJgwn)obtG TnQ8)?ۇBL Aw&71ӌ.bRKa'bP%o8vu}i? -v-R1PN&TG:UHU A=`-85gN:W :=r\{1_}7tn(J53%矍p~tYjAP 6q!w3JT}mLG&}\QO*="Ƶ j4}[7Eg zg97ȼ_Tp$۟xbstDmC-|o~瑴?/r?nzXXcET؍fJ|aMu 7T3MpQQܨPB'O~&y܄8g8K -,x JxU|(^8#}Ro6ښm5pCf,S#%a~؉YvUjNnof?ڰ䠉"ӥE]kލ9$~|6^{t6mښikmf}>˙D곮ޘ>S8+SJ+Gi#/@[vJwAxUdJ8(ɿ@)XrЄ.zk-XpSOϞ&rf1)̩X*?'@@VX[{[klF!\aqt`>~R᫟0|_0/JOim7 ƕwv).)MyiVp =&ߢB78+T:+,v[VÃV na5~j 1L:5>E=p((}]BVJBP De&3c CD&Y` d*M춦v[wYCw0{EZ׭[n:cKO^ Eu[c_Vmo}=da\m b6ڢ>ϵ>ualzG]Gr 9u=JfIq9}*98-sTpK ;eaWw uﺣNӱնkԎm?0yd,Xu}USg^SGRԷoOvx _v_rW=[3Boo;_=R~۾Hw־R^+/5eaaaaa[WȌeFY0e -]2ŠrG!\^*p(~pᖻn#zmru"/~~@3e? ߣGypпwіtuI:0!sCAAE9L?$)'ұZ.TcxǴFFWFŞ2*?Nґ&ۓd;Մ?qe-^^b)2[o4[n.!G*MTKiu2T:oD}(b#,5K^9wY\x^ՇW,^&ϯ}Q_[Ǜ{)|Ηkh/ԟ| 'bJl(wпǫxUG*jC;:y=zh:R-4!4p{oz/kd\PȦZ`,B:>PRל-2ǃ&*$)EYʅOB$Ħi /T*B#a)o}Ʈ+z0 y+l5k+T`)}y8}k{TD}( & ^B|H oogq~$u{D{Ym2J`JM1Mr='!gճlmW 1'sSo]]]츸H:@+?4WŇU1qOI}” pp%M~pQQܨPB]BƏ72|Og`5-5id[kd-bBTQ82?SI8KT˗:VLuWM;Q=NSs7䦚jӽB7z\4+/~Tv]5ڋ5c)K?t4[)>_tIij[./>/Ur?|% R|`jAaҏo 6{##^;[x&nsԑ?ئnW!RD6Ҧw~wI iwݕwi5y9k&rf0ѠAU]Թ OɎy$׻cWIQU߱㳋Jq8VjY\OG89TWzXy$_6`k'$ h&&mSVel|>|͉R_fPJC._\oԟR,UŭoRŐR1#+}m_?Oe['E@i//bAj̞&yO N[4-5\dl}nxrû]u@ӃO)KﺿOg=ƃ]jGE7u8꣈ {O˔|Zߔ[gi:TSoVHz 9Ӟ蜡"Į=7H7OhxfҨػ٨XxMŽZƛ M cǻG˘6$2щm h;j,)37Z/ }> _>&M^r?Ōg.1[Q7$².f},f{|%S1/E*fuϛF@)) `x9Ԅzk?덎=S86YjPJun:={0/3u4A}hKPo<|Ltrz\]]x+Jck镙dYAsp4UOsp(ƍ{77҃Wަl~Z RmmJOGKiTv{tw= #>Uǹ#NQ/K=U}V/ bʍrQ~/Kd/ p3\_0bGiBٵ٢دx=JGOrw\~eFgH.wwik֬WomQW4;ʻTrԇҩR+`ikNkkdidov oԟbS\/u}%4i*kz g1{Kih/K~ɽ}f jxGû5S50l/ҕ*@ ,lU*\+]~,(R~(%•Bw`pP`)g0}`\xgaTly9D!~M/tVtVXzV,/ŞiR($CԇX{Kh/QBPbpS8rlw c]D=fGߥ}'0Nt޺>}N[(5nn5zŹpŏO1L:0J|eι|y_ 61`i=:Qi]H &Y` FO_b~z{u֭[NxZ%x V2]=e">P?R/ou@q4EsCe#b'asgsӓt,_GǦGݻWKL^5tݻs/WMy{MS'SSxy ݺ;n:mZH]_jۛ%o)|&!ݝ!޵Գa#6 ٧qvN[.i3҆qW>%o8vu}i?$-v-R?/UmӠI#u;Ցpt@RիBA}hkPφw?X[&4g~g+=JQu}Q5bܢv50_[so٠2I>wqOԺو1-TŶ|b_m^3hB [=W'ąK[ܕϸ+mS])ڲ6a_y±VH?&cs&}#!az5;/-~.19-]fo.Ǜ;z֨CճP{~Ag Uy9ue5Kt`>8ʻTdGvj*L?B㥓lmm^|w]Ƈp.rϖ~lqZ OƏ>JE+_/WoRgKb߿t:W36\yJFd!b/6RGiRKGϲG.}Ku:_{ ୴`+":d@sƫ4?jd kE`z.vrzN:̿ p trٿ0M'NۃB SN?ϖ:^y%3f Hl"}!+u}ގ+[2J.ǛK/CTPET'i}US媧f2tb_H{cW}5vUPZL_6)R[+/S 3W?o=oڷgf|b+ayB[wo>gOK(?/ra-MJOoѷR]l< (–R?JwxO;^E}h_Wg}2A%sh~!ճ5ucNM\%ȷ,m=::KXN,S<ճ?sNnof?ݫ\x~d _-.nB/uRC 7m'm+ұ?^WŸ=1&J׾l_Cqʮ;t;'mn> hrFV~Z#S87YB/6ЦZUnUS-%-sNō?n0|s~K7z\4+/~Tv]5ڋ5>ύ>O1{SH}X_j/N}zOKũɃ['}kw}^5kk;5kkv\A8ɯLe I3rPj'lAaҏo k6{##^Z1GT}ҿcgq4&rWxU*Z K厊fVhvknuW^])]/]~4z0q}0Ѡ:e7?^H?ax:ʯ`gYi8Mb(auZ!CSNƮU>|R|}.x-}{-}d`ޟAۤs$M.NjV꛸M\)E[oV#ך߻5{ztcmhOS [!知UKPgir4t>Bem~)3Z~5rF:jFh7٠*<})tsD4(rGQt?\IR Ι?}~ 6w $;>D}.T]OkmlSƽ(2,>??دx陹 ׍ZYNG,oަlVD7'FŞ*pC!]l/|ꛘ-N[;?!oBzC!L%X_l<U(l-pRg[絏i BP:bSt';U~B+@@&Ǯkl }\h'o>;5R95DP@QK WϻFkfq!OOz<\.0zRBMK6/ކvK}ҍopQgřJgwn^T]+q!>_H &P$tpû.¡أp]? gچ#a @4&Y`$OHu֭[N"H+=@}CBA{b1hbZmQ]N)H,珎Mw?w+>];?Mgu(?)~ԙ95R95 Ы[wGM"+)Û1h]]AݝA=65gûA5;;o٠%[jsw־asEWoMosfJ?G]GJrIOSEE9?mA'7eS!ל ' {XfRk&&e,Xz<2Os[.cS~wZ[ELx\K6o g ꐺ^C[[ԡx~=t[Bm_ܰ[Bm֩=0v[eRi Cb(aX*+dh`f)wRWqڻxҋOΙΙPۀNX#Q_ߊkR_YjbW|漻ጶ#u[j_PL4^=`,mqW^<㮴Mw٦hu]W-59ՙLv<&;7[ۆNL eM9aq3b\l.v*6lMچZƳU.hJ:W}?w+/?\i-QᖻnץI?$i{pW_/`azJC--w~o3:+$(8`-[-:I=4y=eG!j <.񡣦cW}5vUPZLMˬҕ={~ 9F]2c iR/$>R3Ҕ]SV-$b?4>ͅOm)͆wzkךwEk츨e}و1N75nxq#̧,mͶsVa|4-5n#ȶROGS| \ir䮼R٥WvUh/V<7k 䠉+!WE]kҍ9)Wz3uW /ZBk۾ȵi^^;ݘc6ݘCw;ӗ=݁Ok)'cWzw*K K?>l_r/dkN-}{-}d`ޟzۤs$Mf/t{ML\hL4|٠>ͧ>!lx5Rr+'qfRr)R=_?Ŧ/rT;*w7[RۭMn]y{wttYҕzRŰ } x=M4hd[kdt9mY{AQ7_!ݽK/;_`{li>2|H8} ,Ju[{ݴmẌ́ILɟ|8+9+sMni9uвj%oКӚi⫰hfRBGG˶ SadFuw/ aʭ6ŨSfTϙ{ݴIa4٠L?i0AxfҨػI~!9Ӟ\G Rt%E~nOK| --Iӝ'@ pJVH~K3.EJ/u[/Z)s~x^s{]~XK]±ɢpWZ *E>6+~Z^ 3 BɍrQ~/oe'ٞ)  U a:"aFN+nئ6E91nܻٸ YxNĮNii%]-.zizl9UW='STz*#6R=F^0_iBר~nT(_o5r$~iSk5K^fz|kzҬKL|IvҼQ_ߥKX}:`]JKߣgd^'x-/{!KG|k{,~>HOzV-~IUReό]Gl:ũ=wϥ٩1nc P:'u}u}^- 4=Q߳?=/-M'$M^mKϜ+?yzbZ!wmC^>ZTSo 0 X|-/|_*/JONkSm(Ht[)4iYqYaݷ&ZBl.A]P+&'Ͽkx~E=p((}. ~ !kf[O'3c CV_/3M @4bUmMa'Fnk&z]nݺum*eՕ7{tʄ%OTGZOSѭ~vo#אbR`Jk4EsC] e#|<= O߭:O;e ?+|㢜(=J'm<KUL0i:wepW,:6u8:@]:Z= C;k; BTz];B6BF<ʗ}T3gz6x"6Ԟ^(>&!ݝ!޵Գa#6\ki;#is7hP-CWF4PNo%gHq4hHNu\,ԇlxMMSҢ )ΟJMRkTdTxy"EKu>Mu6[n ;C!BUwUӜ/Ch RlM-/:ulS~~b)'C!yGQ~_崝bNRxX g)ϯ|t/}3W=W_Mp>BPͤw_cjfB7W_dUtQ#J^W1K[^Wҝ?%6bOROo[{o¿KAT} ҵY8IIDAT-gew^#+ca_KN;A:Y*QG@]N]aлAl]APwL'UZ*|®M7UKb+I+'Uh4̔yGFŰT-z/>WK2 4O?_xPHB\_H=J]>^ۋ'm_l/?ʯ*A\/Z{,Dl.6^L^H $ۅ}2]ҕu+@9h9A A=eoCzPЍ9KQ8{N]ty$˕tm͎ښa_ 񡸁t((u>맔q6qe'trS~G5^Dsyyi>zk5nsZ֗N4nxq#̧5Ixoȶ橕cl2?IxkFq⿲Y*}~N./5܏n~4]irW^]R+*k+k|}?fKG)Os{ϯ}?tUqg3b3.]%+=PK)DxB*8Ot|ޤ^(%M]dH?5X﵏x[=ෑ6kK PhtQmWUīb'U/34mjZ{״f]}ÕIkmR2.aH % 6jHv<&;hcWzwR8u{ ASxK?>^ll J)S/.i)vϟ i2&&mSFJ**V.?Z"E|R^ZP??mGl}^dJ幔ۋ g6&E++Ԗ4n>izqy$ȶbs9R qptmԜR;+̖}͖64{-D'L|~RSMn괄{;7.-EKe~ua~kk0A?Hm7٠*N?_I^/}+Jl,\xehsϿSr IBzkUU&^|񶥯^)=]~XKC &A]FbbMvsۿլW?OKu A}hKPo<|b%Nwtr#=\OhNkӤ5|WEJr\n\z3Sh$ۆ>?fи{\#MўWLdC_ hxw_*R=F )OOSM\Hʮe_#~N#sUWizL5NGt)=>hu'i?5sb_c>0rG_ؽM#WM?{ƒ&[AjzkzfkzxYJK(/f?_I^O~?UėW>k.pA+;B\Ŷ~{_lFu0qDdsv"iڸwi#a|ڳ])ޔuI]eo]>zk-iFSP `-qjrjb/b@|} Hl ԅk 5y=h(.0\`)IS闫W+R/JOif * ZDnf^dU6=+am M @4&Y4Esi^Tϲ&!ݝ!޵Գa#6 ړӠI#u;Ցpt@RիBA}hkPφw?X+&4wfݺu֭:I[BmlЖqO`RLJSG4&GMXcth^ͣ7+&7mM Cb(aϧO % .TCm'm 3s?:A'7٢GNآq~4B(M4K\^=`,mqW^<㮴Mw٦hZ Q{`g}M%:cp|*pwɎdG=HT?b\ ěh~lm:16RW՟iHگ5o?5fٿۦ)UpS*H)^l3o:r6fNŸ\Tl.ělOun:ۆZWVkM»zlS{)6XlN#+FFZ ּ/ZsYJ3klyizlx$Lvkcie{w]c51nߠAEM6xf=4$R4B)NmDm@TSok嵻{ zJI:p:0\922\F)sheoCzְfW32XBhY\yGxqۍf>m4|bϋ!F|UM[\:S+ՙ2ąˡ-5r~452azvz=k~G5l @i*[ M7\`v ] p\@6UMəћrf4ͼ֌{=MZ(S2@ҴϦK .OD8ax:03qhUlT4*6uP:0&~|6Y*lc#~/_gxUdJdᇊJߒ3Mh(t^ڢm9b'csopɋtb(_+ S#_˜o*oʋg+BOC0 ΙD -J y!4Db{li>2|Rһyd$ѱcr 9()=k~XK &AoshUo:8cܸwqٲo|Iv\Q_ߥK4r۔`&XeҨ{j,m5/ӗ)Ѡ(+5YOB7D}SOrc̿ǸgB]eo]-I[Յ9-,/H|HJ%o#[{[klF!$\auESc_ny<O+l8`C I#Ց_ B7t[B:B78+T:+,v[z%X0h"J( ~]ûtz|kz\PQ8.\|U n>VBL1 q 3M @4&Y4Esi^Tϲ&!ݝ!޵Գa#6 ړӠI#u;Ցpt@RիBA}hkPφw? Ee˿-7mM 3bk>uɍnˍ\`v xFTŘ瑴53Mh(zlS{)6XlN#+FFb֞n lp,ճܘk֒70 8zk\#M)dSrfLX{iBC!&yOK/mȶbs9 Z`ћ^{:Eh±O U5&ldp]&\ M @4&Y` "ދ[nݺu 3g5]&Fnץ?}:Һ|x$^?Bj,Bܿ@Ġ-jmE}kw} d#ճlD'B ywYCwC:r:RMw?w Ǜ02^:q%R.i36jgYf9m \J->?|W 24hBKT!U*ԇlx|MaʽJbOQ9s뜡͇7nwNk5r_o 5u5urܤt*(G]G՜q/~Yʕ S3sEWoMos_qR(hf0m6)iJm\sB^xu}g'-IW~xkxxg.ũ?XfRk&p,X |KKh~:W(:E_h3>o;kp)Էĩ9ujz}sO{{/f{ɽ~-_am6SKRu}|JeNn7'U򕮼OrˏtPj Wz\]O +/qWڦlS%tK kS`$ց/k?2W86*'d{lܤ/zd626T r.OɎfֶSrb:r6fNŸ\Tl"ՙ̿~lq,N-w 7~y ?)=s%xsӡ?70J]*Gy#y;ّlO3U R]".i?ަ+&:zUF)7O\dl.^'^~:ӋmR>sd4Kzu07pu`u8ɷ}I[,R8O.7(i --B_N^:K`J#+FBQN>v_cW3R9Ở&&:-ᖻng|ʥ%O'NۃBʟpSN ?τ>~ԹPly%xSM/uYYit"[Rmxl|:^֕)fৡ~JzN*vf#O8rAd|/ݜiW}v7zMϖMUox.Fd %4^'R/7.?N:>1vxzڣ;=Q8ymk)PKo\5Vb\k2lJΌޔ*uFAg 0cXXZI{t6)S8^2~'LLO12&^;R87YSLL|hrkxƳH!10Ԍ[Zk=M&K_t)@| ihgFmaOq~L)3BWg髝ѻN͟NmjM%"u{,&E{_?:})P.8嫑oV#ך_ ztcm:kd&VBWz}Yjk@1, =pK[h߻8%~ѹ޿s±L-R:u6y=ZsZ3M| h>zk/~+l|eAto3-]_z!sCYqZ欠ӯTSo~K_cQ7ժoal )C+6FL:@)_+tM ΊeΊBC!myj'w}}~@ )S0(73>RK!RTlPKl,f&&{^7_j+W.g>؃}ѱe`m{B4P[a]/}U(, ?+&ZVٵ٢Ҭל֬ȶ\pSJRʼTϲ7nerfo5_?N 6]>mq8Z^oO=dTQB[ZTi-OW~x%1?NmSG`=7lhm^CmyxW;mGѢsO;bGi3~ec/u{һv^ӻ45_k֫Ƿ+ڿTiBޥgqʗHBm G0E[ʽ,5RׇO Ǹqxd8c-pRg[絏i Bƭ|ޭF6Zt`\r|b-/_f0 8`C I#Ց_ 0hM @4&Y` dA,0hM @4&Y` dA,0hM @4&Y` H"֭[n'l@`yb)VPFQAn|KHF و$MrTS5u̩XZF8-jmE)ҹ٢tv|a/nmJrIhf tOTG1Cw)>+0[HnĖPyy䃍Tϲ&<}{Fn+:Qi]s22$Iv|B[bRK:zSWh>0VȻ(jo.->h0}`F{>Eڢu7[*??|p'{\$> Mg&&:==|:vMj~zL|;L&|~bSIץ+Oߕ[B|Z׳ȰzͶ}V>82g+41{(4auܳ_Uݪ.7.Z@6hBaSaSGӉX{d8λ=ff@*`ZdRwKs! Q 7zo41(Vީmn5v0_0ݻn~~ur:@O}.npla:{c͎.7;jǚ4d"]l{KʓrX9}OyT/;z+>'? ?]M;9sefr׶&P/26xostrup~z.x}gO;y(Ju]%cMΏ5ѻ[ۂ--z}ԏvOM_tb6]5kjqxZF-'];'s;=r,TsW gxaVR/3)~,־ǤX;Z9cf\r=E=PMQUwbW-aT,̶&+{߱SI}^IWe\Y}3/vklۇlT-̙zSﵕk˭')|*V7q)}VHU/ʯ2tN) Em̥K.vog~JW8?0ASӑMy){jx۩mAG)VyYwM5ˮ!߭}!_neoRzۈ)ݗ+TR[߿i^P2kJ4+éʶZݏolӕgӻ[׺|>SqQr@mlʒtmZ۰yRϢz,aYʤ>ٲm8HV>}L6t46D奇SI!ߵwQꂢc^-jZixJffiI!-cl2Tt4]/ʭ.yẀp̀omXC2n񅟄 ?鍔GroQ| ̝2}~z?e:m0Z[=L󑰜y& a3m̧<q~a GAYiB(CQLay?;%FHRGGs^?$UHEz\}='R*Zbn2__;}8`%t u{G\^C+J$>2)鮊g[9KwtByWS$ibuz:{I^v}+TrW6:3YTX MA?Dn5Q_(GɇJ鎷t݌%,߿g]~MH:$]}ԏ]<꒯狾;( cLL'OARnsZ32:PpJV燅o:y)2M(\rK&tP/N G#ϩ4Xֺ=Z\gsӼG7bbo<:w{.bh:Hs[]ipf7zoQcՎU/>]QkA_ٗ_[kaԆxkZl S_J uJ/pSkiRC걹On\=Ͷ nn5||tVu7_}[D[w?.ܕdLR[F#Ñ|7,:+TLT~掎#3GKd8b/KKG8QPiebw{b̠y<[4[eOA2O{T(İMf[ +޿gD}_qs瑥d\WLg~bnZ|C\ zj?.5UK&y/y/CW;GBLSU>`3[dew4Y9 m]4r(b6_mu)=Z4\>I/l˯5SOUS%.*>AGmu`;߆01/?;Pn٨%:T{m&˴zp Y>>xJ})-lyEӖg:nsGC\w\f/cN6lvÆ 4%6а11WoڏzRzZfrwrbq,οZoyGJe"}PHnZK'.T=X)ӤPwtj\%4sJ&LoH41pZ AzӸ|΋SMCQTyEC_͈qJҨ^KOc@f4n EZ38f\%ǍZ?%fP_"#'kG6kJWaFY|D}BU򍾫fϭ>zIFw2}O1b@=G9*ΰJ{nV&K@hCWI'UB[*Y]"pj;B-X]kkTZBic<~n쵸).z閽- 9µL2Տေ+|c?cLJ4@#;ɋZ>t]Fo v?zտTLj8i]o-/}s-s3`igTC?R_FJrK33HS L<q~O߯wٍ&}cNcNMfM߆N_wwV\SI7~=i2Р_^ 𼉦(vok)td#;y]̽ AS~\,lֻCN}:t/drۿkMw?xkJަ@Mgh.m^2DʩPs0椩2^oӹS&-ԌCjѧˀCs&h̚ѩ l]tRw=tRgr-4{(!y-`a7GrѤ(K㭡%F+^(;5br䏎+}2Xy嵶7K}hjv4y4ԔrpKm3ZHev _}Vzx%)<{Ek[k[Jm36WqN'*,]`[.@w [~}@_cc`L>-O{r)M&0ll@6'tsGi^]֟ {wٴ­&Y΋,d} )(IS^$%dz5r:bZQ+@Ehn1|ԙـlWh4x @ MR@ MR@ MR@ MR@ MR@ MR@ MR@ MR@ "4 6lذaÆ/,,,,,`  MR(X${VeE27e&A&yiw7K4BcThص]<w~yqP 9ׅ4oVT2&Ƕ|Ⴗٶomi D^'6/ks5]<:3V6VQ~dj?Vc쒒]y%<Q96hu>eu Q\UGӉIƊR ~tn=[s&dp_|5/Fɚdtb2z}#B ɓrXTZF-ZBvQfD[*ϟ+𭥧AYƠCCGN:G('Wo`zpM\q:շxN)/#NULk͎#SM_ވUyY2w^4- A[E6LbIdh~*$R~7Ro mGgۆ%R2\:Wq#6눵ǤXίXS̀<Yچ[ {.6%&dRwKfc^3bf'͎5),)>oZs[C81, bk+kY|>U x\vwN8ʬEi<_3kM&Jfg*.~6x468RzRuIӠZ^tp:m7vۖWg|(wm]iv|ivU߯埏v)vs7r^n뉋og[e,vC5˴. ,$wnR̥%2.r'泿YSt[pRnCFA^ޠAl s/c7voܵ9o4¦ KƗYcMϏ5yJ8)vݥUJ|coVև?'Ho2 %QKt8joC@ IL> &&'FĨ_6Օɘ%&cRbnILݬpl=ef?sݬJչ3 CK.moo&+J mÚ wokoXӅckgMuu4 l`w{>\Xf6lذaCKLS#T{1Ę,-5Wlɳ5r:bZQ+[oMhP(,- 4HA2%A @  @  @  @  @  @  @  @  @ EhxR2.K)EFxXGc_E|֏ b;"+<1 bɓ^b'~]Nr}ͅ]v|iK>=g-,]ZZ3Ƙ㻾oҚu1ʮ|Mڶ:X封Ӻ6W]qZb(-W7w[.cےGe)|Sb`\6OHo$$Yl\||#_\1OHunsm=6w9,K}m-A*ixW'o{&k_ުfWnه 9toV Q4l BB>N M?]_j3)EMdRZ'knrf1ƘiOgcofd^vҖ앺C_tήc:n-𕛗uFFz%v]Lź5>=נc[z1+wO3O^9NAWINYk^#P&8<+ker*{^ c(Ȣ^n ӫ~ݰD˛~z`@ue@? P|ȫQqƤR|t}B!1E~*ey"ch  >ٰu#…dpI_ KYw}]/p%mGgۆskjP'ɸ,ܦ&}a&t=܂[J-ǠR42}8e1W c'k=O)c3u?鯽 V.a34+r'br5玆Ĺވ>Ppl:< i+qXB;5۳5}v^c+cN&[%hxQ˾]rNֆeƂZCKVNJ;cdmM)ښދqhhЍ5yN5Y߿i~ 7nƭ4:򬁨5z:jmɆoB_L1#FP)R}%Z_ÿ \q-~WA+>>giP\/=̿WSr\m4#On&474R0N(,7S֙&ᣳÍ]]ێZZ\ lwP׃^y9zjrzЍɽ_Ln"^orƚz^^* S ktJFLTfY d<&0Z l7_}{yۆ|B>k=U`<&ݍgzcLJ#o}GQޯ{oA~`Lj#<2brHC(8NTn{Iy*~7->}dƧoNU;&sqx\9&RFvT9N wHh)DB=2b)yNۇtw!aӞ_ ([loyt?{bo`ۭm}G~qKm`/l"l'pK-wt{M[ me6kJV7{u{}k^oE ty׌_~خgcc^EÌ1c̡y|0V ,ӱxc,B=eԌwOmȨ$:m-VJYHsT1g2ΜKcx-7eq/|,&S<3;*fcXb^+znwRd!狌>+Kq2M#gFcQKBb,xk&1:4ө4BbD%qxl=EawTHd^y>ޘ,1Xp'0.2Q$41{(4_33[Z-Ӗez7xsѩfjG-kQ-hJJq_Wk]=Zcc(X_)2M~OSb+~$XimY!Ke6hBa MwFS>w0.tLW|aL=eԃvՆHyd(RP`H:QyIENDB`xymon-4.3.7/docs/upgrade-to-430.txt0000664000175000017500000001512511535424634016302 0ustar henrikhenrikUpgrading from Hobbit 4.0 / 4.2 / 4.3.0 beta 1 / 4.3.0 beta 2 ============================================================= Xymon was renamed from "Hobbit" to "Xymon" during the 4.3.0 development cycle. That meant there were quite a few names being used for Xymon for historical reasons: - "Big Brother" or "BB" (really the original software from Quest) - "bbgen" (the precursor to Xymon, which used the Big Brother server) - "Hobbit" and finally "Xymon". This was evident in the names of configuration files, programs, tools, commands, webpages, status columns etc. etc. The forced change of name from Hobbit to Xymon was seen as a good time to clean up this spaghetti of various names. So with the 4.3.0-beta3 release, all references to the old names have been eliminated from Xymon. However, that means upgrading involves a bit of renaming stuff. A script has been provided to help with this task. For it to work, it is essential that you perform the upgrade like this: 1) Make sure you have a backup. It is not necessary to backup the data in the hist/, histlogs/, hostdata/ and rrd/ directories (if you use the default directory layout, then that means the entire data/ directory need not be backed up). 2) Configure Xymon 4.3.0 using a directory layout that is identical to your previous version. Make sure you keep the directory names exactly as they were in the version you are currently running, even though that may include "hobbit" in the directory name. 3) Build Xymon 4.3.0 (run "make") 4) Stop Hobbit (the current version). 5) Using the "bbcmd" utility from your current version, run then "./build/upgrade430.sh" script. I.e. run a command like ~hobbit/server/bin/bbcmd ./build/upgrade430.sh This will perform almost all of the file renaming and configuration file updates that are needed when going to version 4.3. A few updates cannot be done automatically, you will be notified what else is needed after the script completes. 6) Install Xymon 4.3.0 (run "make install"). Your installation should now be upgraded to version 4.3.0. Run your normal startup-script to launch Xymon, or the ~hobbit/server/xymon.sh start command. If you don't feel comfortable doing the upgrade using the script, the rest of this document describes what must be done. It assumes that you perform a clean installation of Xymon 4.3.0, and then move your old configuration and data over manually. The ~xymon/server/etc/ directory ================================ Copy over the existing configuration files, then rename them as follows: Old name New name -------- -------- mv bb-hosts hosts.cfg mv bbcombotest.cfg combo.cfg mv hobbit-alerts.cfg alerts.cfg mv hobbit-nkview.cfg critical.cfg mv hobbit-rrddefinitions.cfg rrddefinitions.cfg mv hobbitgraph.cfg graphs.cfg mv hobbit-holidays.cfg holidays.cfg mv hobbit-clients.cfg analysis.cfg mv hobbit-snmpmibs.cfg snmpmibs.cfg mv bb-services protocols.cfg Four files will require special attention: "hobbitcgi.cfg" is renamed to "cgioptions.cfg". Three settings in that file have been changed also: CGI_HOBBITCOLUMN_OPTS is now CGI_COLUMNDOC_OPTS, CGI_HOBBITGRAPH_OPTS is now CGI_SHOWGRAPH_OPTS, and CGI_HOBBITCONFREPORT_OPTS is now CGI_CONFREPORT_OPTS. "hobbitlaunch.cfg" is renamed to "tasks.cfg". However, most of the programs have also been renamed, so it is probably easiest if you use the standard tasks.cfg file as the starting point, and the merge your local modifications into that file. If you have too many local modifications, see the "docs/Renaming-430.txt" file for a list of all the new program names. "hobbitserver.cfg" is renamed to "xymonserver.cfg". However most of the settings inside that file have also been renamed. As with hobbitlaunch.cfg, I would recommend that you start with the default xymonserver.cfg file and use that as the basis for merging in your local modifications. If that is not possible, then you can use the xymond/etcfiles/xymonserver-migration.cfg file together with your current hobbitserver.cfg file. "hobbitclient.cfg" is renamed to "xymonclient.cfg". As with xymonserver.cfg, a lot of settings have been renamed, but since this file has very few it is probably easiest to setup this file from the standard file. Finally, if you use the "nobb2" tag in your hosts.cfg, then this has been deprecated. Please change it to "nonongreen". The ~xymon/server/bin/ directory ================================ Most of the programs have changed names, so if you have custom scripts that use some of the programs then these will probably break. I recommend that you setup symlinks for the following programs in ~xymon/server/bin : ln -s xymon bb ln -s xymoncmd bbcmd ln -s xymongrep bbhostgrep ln -s xymoncfg bbhostshow ln -s xymondigest bbdigest The ~xymon/server/web/ directory ================================ Some of the web template files have been renamed, and since the menu system has also been completely changed it is not possible to automatically migrate your local changes to the templates over. In the docs/Renaming-430.txt file you can see what the new templates are called. The ~xymon/data/rrd/ directory ============================== Some of the Xymon-servers' own status-messages have changed names, and therefore the RRD graphs have also been renamed. So if you would like to keep the history of your Xymon server before the upgrade, you must renamed some of the RRD files. In your ~xymon/data/rrd/XYMONSERVERNAME/ directory: mv bbgen.rrd xymongen.rrd mv bbtest.rrd xymonnet.rrd mv hobbit.rrd xymon.rrd mv hobbit2.rrd xymon2.rrd mv hobbitd.rrd xymond.rrd mv bbproxy.rrd xymonproxy.rrd If you have multiple servers running Xymon tasks, do this in each of these servers' RRD-directory. The Xymon internal statuses =========================== If you have alerts defined for the internal Xymon statuses, then you will have to change them since the names of the internal statuses have changed. So in alerts.cfg, check if you have any alerts for the "bbgen", "bbtest", "hobbitd" or "bbproxy" statuses. If you do, then change them: bbgen is now xymongen bbtest is now xymonnet bbproxy is now xymonproxy hobbitd is now xymond The Xymon webpages ================== The three top-level Xymon webpages have also been renamed, so if you use links directly to the "bb.html", "bb2.html" or "bbnk.html" webpage, then these will have to be changed: bb.html is now xymon.html bb2.html is now nongreen.html bbnk.html is now critical.html xymon-4.3.7/docs/Makefile0000664000175000017500000000074211535462534014626 0ustar henrikhenrikall: cat xymon-tips.html.DIST | sed -e 's!@XYMONHOSTURL@!$(XYMONHOSTURL)!g' >xymon-tips.html clean: rm -f xymon-tips.html *~ install: mkdir -p $(INSTALLROOT)$(INSTALLWWWDIR)/help/manpages cd manpages; tar cf - . | (cd $(INSTALLROOT)$(INSTALLWWWDIR)/help/manpages; tar xf -) cp -f *html *txt *png *jpg $(INSTALLROOT)$(INSTALLWWWDIR)/help/; rm -f $(INSTALLROOT)$(INSTALLWWWDIR)/help/man-index.html cp -f man-index.html $(INSTALLROOT)$(INSTALLWWWDIR)/help/manpages/index.html xymon-4.3.7/docs/stdview-detail-acked.jpg0000664000175000017500000030022511070452713017651 0ustar henrikhenrikJFIFHHC  !"$"$CA" `  !"1A2Q#TW37BSVu$CUaq%&456Rtbs8vErd@!1AQ3Ra"Sq2s#BCbrc ?z|i!)RP8pMxvٟ .?6o⾪{w^ԞٱvOg^vr~}_X$\ضyS_nCTDwpJJIr1öHkQ[@zBuӫ;[(e$m ω9=kjqJ+ܶ'J̺DpfQ%#8 V6ؠ;mӶHjO@[d]{4F@Ҝ Hݹ]NTzbƄgCN3o!őw"ݘ'{!w:}%n@C*qX%W`IyY%ۓJV+ЂTPl'*9HuYnB2ܵlm *R ׫e솘#8}(qie)XJ=ӵiV"n7Y_ML-1,EDKE2DjZJqM#b9UQ_xz=u[[*­!;RTJ º:@u3o!mDu77Ȳ:+1,v s .s7#+g QYTk-Dr' #q; cR(׻lύ􆝶gCXpqle8iBt$- נy^-.֙vKel

-BOV=Z|_pnznRmfqyi9jmNǹ$A׻lύ􆝶gCT^swM5k+./W/YXgCZιCAWY^Akܴ)#w(N iQ,BΣu/%K]Da/lpJ;Q$$SjO#DMlE0V~Ҝj%];m}TFC*yTfCzk/Tx'Ź;~XJD_>b0mD$mK$#9#4_>6vٟ xR߶Hif|m5v.٢sxNҖD@U$Ph7i!.b޽߻_ = .C[9]vo7g۷q֤ٟ ;lύQj,&ޭY}٘(,v0FT1meB$tP_cljGTif*:XUPU$;mӶHk\ow6u,&, T1 @mJHB0U'Ʀړq!KAL_1D% ).`R gCXkY]߅:;rciaX# nPzU#~r#$>b9+d2v\'WpDi$ð|\J%K#% )ڔFBWu w~3o!m\CҺ-ҳ.%=Ii/{Gq?Gv+7Kw;̋zubS +ʏLqf|m4>6Qj+ꫧ'uImlJ$unw''{Hif|m5ѩՔܕϦyq4;c5t=mo܉ -%-2EEETƮKl%C (m8J\e _>6vٟ rdh@'S{MSo wRj@e{Ԭc= uՂûSuۙpi;%+lAYg*);=ٟ ;lύ ^]lWHSRnDh61n ZT%N ۊڸ:~_E]R%mnHf|m4>6}6FcsMu'"lp۷c{9یv[irKV;P`y$k׶Hk雭6֢@q|x-)R[hWJJNAFsݵƱ/$L+썜Kd9:lvjTz~e@*n$SgiJr<;,wq@9]gC\>6Z%"jiR[^^C J=G[EZ1"fLu&2.#h (np`';mӶHk•gCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCN3o! PgCP򒒥Ly)$H~;i 켽ߍfW\簆qs|0:pHz6LK VT HI5k^O+;努+ov8wR/{h~'&du볲9TrÎ JĠ ɬ(ݦ75-0DspF?O{h~'IokW/OOKZ}:~=<џO2l:&7P=<,7kIHH)RJA'`>r56 ֶf,LF.6;R  y:W~O4gt zWZrS)v[LB ;H'#Xn[%rz <]lu '3_:{pF?@Vi ։,W2 ";LQ؆҄1(m z$W6:,[{6lCtia• tJVuˆ5}ߓ/={y?à--jӅm7jGmHK<[D,)9#p#PGJ=<џO-;ro7dNy씱h*Wd'5qkDCӓ#!K/ˈKQZ )JQ>$OSQ}ߓ/={y?à-ZBP / =O_p?]T{pF?O{h~' NWmK) 66gP7gDEPʁ6Eb[m%j>2'so8[.I=-jӅm7jGmHK<[D,)9#p#PGJ枞!pOO}Ϳo_à=l3Җiȟh-[e)0v}N,>۳lkm1h!pOO}Ϳo_à.^w֌d]YnCT5jdHAAZܯil=vũN]2ˌETv -(( =6C!p@t%i$*C-m<ʜB -RVX5Ҍ*VK9I͐' H瞞!pOO}Ϳo_à7ݦ75-0Dsǥ_p?]rO}Ϳo_çp\/C֯8_Naa.Ӓ\8V(XBS<iﹷ- t˅:5az쫻.KRje/!AH%}vR Ao~yYog;c3w{ǭsO}Ϳo_çp\/ (RH%RR:bʖT:|y[vo`RBFR O(%%^$3}=6C!p@ocI7X,Xb%%2sXꮾ^/L2]M-XL!JOLwTGuﹷ- t˅:}EżzOtxe/YЦaCf9vјm-2N!m JR 枞!pOO}Ϳo_à:_p?]k7[dyw7h|DjrCm&@B3;KVc_O}Ϳo_çp\/Hurn,qmBBݐc.2Wiv7xůA0bݦMILsZ?p\/so8[. ƎF͆#.i tJVuˆ5U,?HYmab7!HL(4Sdz 8}=6C!p@uKZ}:~zZ /˅:z{m e=-jӅkW/O\so8[.ﹷ- tPNӧ맥_p?]rO}Ϳo_çp\/C֯8_Nitu=6C!p@uKZ}:~zZ /˅:z{m e=-jӅkW/O\so8[.ﹷ- tPNӧ맥_p?]rO}Ϳo_çp\/C֯8_Nitu=6C!p@uKZ}:~zZ /˅:z{m e=-jӅkW/O\so8[.ﹷ- tPNӧ맥_p?]rO}Ϳo_çp\/C֯8_NituOk<8- 9m%$$[WT9@H#d{/ إakW/OOKZ}:~aiΗpBݷIeX"ۼ;<= 8 {h~' KZ}:~zZ G~O4gtNӧ맥_p?]T{pF?O{h~' KZ}:~zZ G~O4gtNӧ맥_p?]T{pF?Z,,j=9S* `o?c@nittNӧ_ﹷ- t˅:zZ ֯8_Np\/so8[.kW/OOKZ}:~!pOO}Ϳo_à:_p?]=-jӅ{m e==6CittNӧ_ﹷ- t˅:zZ ֯8_Np\/so8[.kW/OOKZ}:~!pOO}Ϳo_à:_p?]=-jӅ{m e==6Citu Z\`$`H#0˅:z{m e-6.{ j[g\%͍ٙl1PJI\so8[.`8[xү_,:/C̈]TyLme)<($y0~oҔOK'zk.çRvvĒRHႯ7_ST<\mE'p|sKp`)@$G}6Rm}<14#֣N֜_#lMl=,Zq||{E=;*zͥK *) R@VuZl{NyQ"Bj+8 ؟gʷۗ 让ԶjmET#h!I=z)$5 RH^li֯Vxwz׌,`PIQ%DNItu\y:PId)w`[iI%)Oy@d M+-f6hqUq+YěqMv-":JՔr χQVmEq֖0#|EiS3-^J|KE% m=W~ .2 \$'93yy.xO݌d8spi>DExX*nn+$tuq*R猓5nQywId"W|+[eO<-3 )?&soAIT*PhbT7!'8FBTc>X!+`,zg^FOY()6ݢj׿wqOTG //4#Rܡ]fE!G}l#rBNO,ԁ=k3Vsi-m)[9Y$ V^GIdd%eÂI=lǬwo[]YS'L rҒ8QO &EÜϤ<ĠGQâ|SXy4]e +ʖJNq"\]C0$#<ŲBPOxRIN) tȌ14SGw`Ld[:nk2i1!mnU Șm(oezn;y;bH;qz` }9oMܗ3o'lI7w_n:zL+u-ookˋhE9%QކLE#)q( +Y,e{G\\[B-#[d0ɐ\W(e.%{$!z2 [e, VP%9HOt(irc{ \#7*+cN$) €#) },qR`3-(Kd*J#[eʃ|K}Na *ۃ6Y XlFI^!ȹi^KuJPOÅ-*FI:+m'wzL\zvj i6!׸zVխ#WmѮ.񥮎j\-*)q mwd-;t@wiw"3"8Ae-N8Km*pBU Zl,æ:2`Sm5X*BAQm( cmڴ%Fn=/m;7%{7`ng87D]忲7r\ͼ$Hߌ}=0xl,æ:2`Sm5X*BAQm( cmnKohv~a}[&P RIPJui\FM-Н75 Yֶ֘ݷ*PidL^6A7M+UoӣSv= ^PaŸ-- J8H/] 3|dqzS lW}T\Vq>9;gޠ;a3 ͉DŽ7˄•*61I*OZ:R#T@{MtvpMdFa'IaEA2R;+m:6fFk<6ɒ[Lq!Gn|PqhtK^nzkB$2]fL[BP. $oV:ѪF}Zv^ʹO=[nǖd'j1cM]d^&I$˱k^"exK3u]1 L[Q*HNk8evVե3vjy|qF!8ppiNH)@u(ޕ;+m'wzL\zvj i6!׸zVխPB/`i.sgVG ֚B3P72kTYJVmڐuC Gjv}pnўg3wg5ozΨNLumM+c or;r7l^Ddĺ؊P( vrC{ON:bS >2<(B~Cm^Rj}nSn֚ndձ#qJRI ,ɏ<.Jmi JA|AUxc]5MPq2DPxAh]yK>ۜ@BQ+lcmr.2C, [FI<֧shW%l:}ϐˬm,y}zwmue()FqWi3W)+Zk'B\)Jk|-)J'+=!~W<ث i*ܐH)*$A k֝rſNc3mfӔ2ͷeI9ݹ rGLxX"42c6\Qq#.|\*JY]]~GT]6w6 f ;1M{4(/u($!)II UV7iHw"nD\{v^픺B[))*$I.KsRjuN|K~n\ձͲ[(B)RTpoAAy_}-K3}n;]lym `bwT {'ZI2org?$ݻ =wtZ;6ʹ<̄q)+)Vu8+n#X$o^D&?iy / (u:AҼ3or--$=ؒDyd,<ᔨ@tyE,v_Iؙ߿m+ۻ8݌g"_Q!W I%dwcIb u miaIROPA|+.dZ5 w]/-,Ob40uWD%J8'k^[,׷K1.4b8$ҷGmcZ[+RXu.%x0%XOEu'"MlͰʱ-ݜچYOyXCn($׫8gS ^K[)uںfh5ހBR$a ` \R._n\Rߋw[:KK/3W▮]wOג]OuuIuh[!JFv>8J\Rߋn7\R/[:Y~DuڸwW[duԻvnrK) aAc!+5\t'V<_)q[]??ïK^Zw[gu hG)4f}+Ŷ_ܟ[ݒEQy neq4ҏBqPKZ_۟F%nE.+}.5}m\ks6JѴs9b$]:'%esf,#ˠ=ѷ:{qϣotk>hӏڥw8 BFqR@44ޜ'dڭ1`N8PP I@ѷ:{qϣotXZhmCMIs4zK!m!aڜmM{XƿϷ>Kn}é٫FK9馽|zik=b8ѷ:{qϣot٫FKqzpm1z$ʆseJo8|@#7M{XƿϷ>Kn}æ_6\}jYTx{;J۹u'b2u>Yk_د?}.5}m\ks6jѴR~zik=bk_د?}.5}m\ks6jѴR~zik=bk_د?}.5}m\ks6jѴR~zik=bk_د?}.5}m\ks6jѴR~zik=bk_د?}.5}m\ks6jѴR~zik=bk_د?}.5}m\ks6jѴR~o 0#Aa6@JRy ^>z~qqϣot_۟FW3sM{X^>z~qqϣot_۟FW3sM{X^>z~qqϣot_۟FW3sM{XA.Ѧef8>I`:Kr8{w^>6O}.5}m5~)s?ES>ޙOLh"[%m%E(* ׿+Kn}ç>6Mtmk_ا+Kn}ç>6Mtmk_ا+Kn}ç>6Mtmk_ا+Kn}ç>6Mtmk_ا+Kn}ç>6Mtmk_ا+Kn}ç>6Mtmk_ا+f2J49>muW{soг+8;HJ7^>z=4׵_<\,{soг*7^>z=4׵_<\,{soг)7^>z=4׵_<\,{soг)7^>z=4׵_<\,{soг)7^>z=4׵_<\,{soг)Y[!4 ߥ}^Z+?)J@>k_د̏.~z~=x7Y}Nk_ا+#狟~O.~z~5k_ا+#狟~O.~z~5k_ا+#狟~O.~{M~5k_ا+#狟~O.~z~5k_ا+#狟~O.~{M~5k_ا+#狟~O.~z~5k_ا+#狟~O.~{M~5k_حcX3T_^'np +V:8knxbt+eq@% |BڃLhkޡwZrm]ٌ(hUwdRo үrǶIڻupnF%T]/MSCcCEZ[l1,$)[)ܐRNՀHH԰k9؃4Mi*+ݞSI^v•h_#K^ef]e$ĦAm+JBRV@9#kn5| RWV4if069F*)LmJRvzx35ŭW*Xʐ5el*jJ GOEGOD3T BeؗhxI,}-oN&A@ghyJ+H:iϱLDK[|Ęڐ)IRAZnDkj'w2JRe-dT {:Ph]H2!pNZB%= ǩ5hTDmҔ1JRR()JwѺ&[|}"[ìiVԖyVI54BXe*|eA)AN^5i{&ţ!諘˟5۬>iC#f҃S(RHQW]}[TRO%*O- Hʔ1sI[-]7Uj329LZY늜-8YK'JyW8sb4veW-:.eR{ JӚD:j4_ޙ &#eFLt\))cREV[wc2Ǥ4նRѻ+꜁fm <5B,ޮÏ !o[m(';T(n'ڌ\h!#8ۍsmgR @V}~cf52]5)mfgA$3n[&Vk*޻ 2eFӸ]J'4`(LjobrӽAtKKKanqO5.2hW7Bv!Eے ^ƹ8̋fBT eKp'~pC no:K [s1~F<&[yol-)i.,*BYg-nܙnylyi:ښy\AH859$޶%v{v&7jihq-k+y@(*rBzcٹ\흩+ݟ{vq[fFf!vm#PiͯGR\c wMoq=9{cw/gبRVКcH 5]Km:sh+m *JJr0NEkU^eneKiRSdg{qyu]t^I$\)@mLio,:O8?~eWw{g;#vJҭQ; Šp\ nQwm>kwqfZ驑cN۵5"ť DvXCKA{Zpqw\dדA j5`q_Nx )#㚧#2u5*]36[VD$N,q+Bͬufes*ވ)7imrҰT=s\fפEE+R()JJR[_Fgekv쉨| OA( "**JRJRR( ()JJRR()JM=4JRR?C4|4 lۄnzARlӅ)hViƪ:֧G M?^<0:uŇ&p{F\v [i#{$ԓ^\f:FmUqo Z)根8vH)WҨXNIS{k6˷=/WҾЮ~]|_?ӸEkҵє1ԡ_ѽ;|: wG?=fh_k>?I{^G? nOoKGGG>u%#, ?3Ur!+kUฮ#ħk}?6[3ˢ0˃+V |kn> ׳lNbqFP\TƓ_®,'.~+iK_rGQECWȃŷhwֈw4|EH?t?#'iT?h:9#mM2~j~{?5?]jFU#cm~FOOOt?#'HTSn6:FdtM2~j~iMwhS<Q6cm~FOOOt?#'F۪{?5?]=Sn6:FdtO2~j~iMwhS4Q>mFH}@~FOOZ)U#nO2~j~{?5?]j4Tlt4du{j)U#nM2~j~{?5?]j4Tlt<duқuQ6t?#'맺xS֣JmFH۽~FOOZ6cm~FOOOt?#'F۪{?5?]=/*mFH۽@~FOOZ)U#nM2~j~{?5?]j^ʊmFH۽@~FOOZTlt4duқuQ6t?#'맺hS֤)M R\ %`JRK4jȚ\*SPh)JEEH'ʢʢRR()Jj*j()JJRR()JJROm =R(Ҟ7fyu//C~o^|~ k/|KX=Ob跦3O?E+ 늸$)g d%XHeJ8~;wSx;C}Gjӯ°[ QԽ)! gµ:@J V`<қ*.,zggf[{~1Dd?>^5pTl֗Kf=m#^h=I{6KfFer 6z$rpjŦ^z覢N\Z-.wky+ʒTmN{omY"Tvcve+PJ;DxwRpzg"Vuu'SWM-+!FpkzXuQ[[zfS*ZƿNj5ͬ]f)7M&oox^t\\kX/ZjR^h[|!eXݜ¹-ۯ*b;Yn#u9+W| mF#:w5Z5b >U'ʢR()JJRRP*}R( Tj()JJRW|pz)-;OY%ti<⒀2z%?:W73|lnVUE[GZҋs!+ 'FqN2F(_XݗH+WiQSYbKyeL::䁜2H9y<VȬo];̩ I xV=H׶պ"=WiWK\{dNS eHP@ UHztouw 5񁫵曬[A푘IqḥIxwRF2:@aKfS5Kdٯ)Xnu(#^:VOٸee}ԅŒtZ*!ĭ6rx?- kI+Ke;$"V),%$T5ouVUۋr)un! #)(N➧:8|6җ+gҒ6.7nCSwuݕ-] ǚx3y t-䴕6wA= %'8$cN&Nhes=voKGtvmw{/6⦋_u%MՐ. G^#ؠ94=ռ(lrmgRQ9J+%=s}Ud UݳJn!!ZzɎbS jSHpzW֚9xy8Lsi՝'J oZ/]PVp>7{ae9S $;+yތwv ޾Zv+m8j]^v&9^IN2N3#ǡ_e^v(ܭ>`wOʔb iH Oxޤޜh4ʈ{zs6oIz[/y[8to}ӚjÛ|n ®fnmJBlj'Y#u3WDz+o7s}]8JxO;<:iAw``f*L-Ad/iϒSړnՐƞ;T9jX-M'~2GRp:Jc][pp"؋PM#r”0v&Ýy]A{~)Uo.)77$-ge"mC u$}c@ux>ЗMnH[d% K=('`>`tGJiAir'vZ岠I:9'W<>dHM}Z7o1{zV]WÝ;q~!oMr: RJBUק5y?*GYl?87 &]R<Hs>+hҖE:^P JPkZN-+K @-Ѓz65=mvkjf.#*:q$um}RHVI]pdӷ$MѠˈ) +)$t v֣?qB$gB|4@R'D>0iOOHB 9 AhnpMܴ*Tɲ{XK LxgƵ,xq|0@jCQhEI3$%$hʏLTsSЂǢ6%GDMS`[AwSJ}b:˭[4FH7i@!Хyޞ&L2`'ОɀwcDZvg5 ҉u>q Z Sp7,Uz+~τnҀ+3Zfԩ X-=+tgR ;Vvq[LOpHܤ^#[e9-S9*S( W_UÑP<oϏ߁Ma%5_^ ._\;m)JS{kBukM]sۦM-gaNmq8?MiDQNNƱ#«dVՊ4N}U*/x?Zޏ6zOdx[[+8җ/sJU}UQ[َuak? *WzhzTj[̓y~##Z4U+q=^"5S_^h&rb;]! qHh{c^*B>}u29x|4%Xp78W]j7 GMcP_xGMx9>b~~ݬ|9x OQ OV>kFYr9򨮖x OQ OM cc,֕xx OM cc,ҕxx=xx}l|e#ҺW/~O1?]O/~O1?]6/sJWK''߅_1\kJ^a?|t'߅_1\j*}ҽxx OM cc,ҕxx OM cc,ҕҽxx}xx}l|e#j+?'~|Yr9+?'~|Yr9+{?'~|Yr9+{?'~|Yr9tx^0>b~x^0>b~m_[5e攮 OQ OM cc,ʢ_/~O1?]G/~O1?]6/sZWK'맼/~O1?]6/s_eEtx^0>b~x^0>b~m_[5e¢X/~O1?]G/~O1?]6/sZWK''߅_1\l)]+?'a?|t>k2ˑ)]+?'?'~|Yr9Etx^0>b~x^0>b~m_[5eU_/~O1?]=xx}l|e#R_/~O1?]G/~O1?]6/saQ],p?'?'~|Yr9Etx^0>b~x^0>b~m_[5e洮 OOx^0>b~m_[5e榆_/~O1?]=xx}l|e#RW/~O1?]=xx}l|e#Һ_/~O1?]G/~O1?]6/sZWK''߅_1\mQ]/?'?'~|Yr9+{?'~|Yr9+{?'~|Yr9+{?'~|Yr9+{?'~|Yr9+?'~|Yr9+{?'~|Yr9W/~O1?]=xx}l|e#RW/~O1?]=xx}l|e#ҷI%HO[!% !N%6I*ZFO\F]էUf$׃ 5)//C~o^|~ k/N7fyuq$*N /hϣGE VR<*O{L դUr|ՠaY+ƫdxUUR$TT|*|()Jhhhh)JJRR&R()JJRR( 4@E)JJR)//C~o^|~ k/N7fyuıiJUZ˭Z˯^џFd9['¬xUl *< UI׫@2WV5e+ƫdxhW?XoczT0ݬG|-wζ#wS8w5Y\wX5&A'ʢTP R)@)JP RTjOR>ڊmE)@)JP R>U>U>ʊeE"TP RyPSʀRTPPj| )@HTTTP R R)@MEME)@)JP R)@)JPiRS_^ ._^oϏ߁Ma%5ωcҔ8+ߗ[ﶴ+ߗ_>r<*OYH>0T+dxV< UVedjWV51̮~߬ aX[mF#k[`qkβMcָ8j*MEXOEI()JJRR( m()J}>ڊJRR/E^kb}=hQ qIRsXoKe7JVE-pTBAQǠ' v.jTQJmaA9.71sq+;JB'|ܽXEB\]zn8|붛5?}TXt#JAjh"r?1סmxPtV J6m$ M塞3&ө Y.]uG}H)5ސ0+lGeWЮk/Ɯ֙q%nZkNyN3:ӣIJ\9}!sSڵkIkulբwWZ[^mwVa'НcI.pz!q<Yܾ&J:v(S͌) (a$ :SLuk~yDHͷ"k[1={WcBfZWr59i@Aly+A>`;=.:WAۍ9]LKw$il4!҇z zxڴZSYW-7"re4.O W]8N\4P[5*#lB]Ts ܼk>.k[MQmτiq<- (yEY|Wy>JQ&Y>v$~Ò[ƥJKܖ ` UlĒYL:ĆVi * kplvGfolNw={Qɏ3PܥvSߖ9)eo- Y GDs\*S`zZypj*vjse})JzDTPPj| )@HTTTP R R)@MEME)@)JP R)@)JPiRS_^ ._^oϏ߁Ma%5ωcҔ8+ߗ[ﶴ+ߗ_>r<*OYH>0T+dxV< UVedjWV51̮~߬ aX[Ku/ }ڤ%TWp60,n`xPI?¯KgS1mn{Xnȿڠ\̹YnǷͥO%)Bd $^03^7B ηAnn+J#6&:K=| PMFVV]/u:n2kq\9»_q`OW Pݸ\piZͣ')qOA^V%jշKA/&e\p'b9ek' . GԬ<\%a:k#zqIɹ[\7TVoіK{jlO:C)kd ݼ +X+awy:LƝޙ11smy `ndEX]JM[~ߪvg}2t)aҜLSƤ02wpq+u ic%֕) E$+":R#Go*W^ZSMGMCn3.m܎!)m8u„nZk UǕk]$}2+K)dRꔌ+kS6UMa5Vwyt{5g~ 2t[ϛ3/5D5bfHc@Rb]pz}էNۭCl0 $|MVĮ8OK6:YqrZo95+6{t-#VYY[TYO<8S`e)$u$m;SswNyrNEͻ$m dXY+oΗMQo9%z/sWLk6MQqzpHeЌ2-(v_qqIrHuƥ)SF62)7I qkZ0*}\DR( Tj()JJRR( ( e}sSdyouM$57k,̺%ϒ@IzKudD04Ԝ2aUkvK%m>ےb֠TN+ p܈vղ}qփ( BTճTˣi>ªZ]>? TWRproe-)qbc\YpZ U n|5N}lݙ&ڹ1@BԧZu./RD=Z#WU9|M?a)]hMlȸj$)/F[ShmեⲐRV{J œ!|O`#=]4Ta=*5^_lɹkG^9&h悞UЇ-t7=r!rPRV0}\'oUNXnTpuLGe[78ʏ5|RqGkBWK3ٷk4ֺ.+KsR􉨩'ʠTJRQR*( (()JJRJRR()JJRR$COm()J ]y)?D8 ߡןKkm)V*pmhW_.mhW_.?{F}=(xUl U|+ߢ`V5W'έ$x^ ^5[#Քkբc\aYS3Hv :H1dԅ71R\!N(<(g<+_vFIBxq8juêի;[k6KqCFkXDg^68RUj optN0bL/BPz U&,5-";RZuH!pI$ndžG½VsX M.Z.4[MvfR+ -9JU*e-)KM,_w5ZcVv Axߎ~zoyXu|>閭V[;SsrҗFeCrN`Thm_'IzWZ) p$vwF<=H0G\ԋTX\%&"FiVi"jdgqWSZV^}O6 &M%%EURT OPB_動k֛-pPd’nfCJmi( ;&_1ПDͱFThcn%JR)i$D# !v<͙w1ޭvh[bÕq·j9]~:C;H†Ԏ3lxZK*zVzu\9>n6f)_jRVBҵ(@FH&]POM$$ܶuO1(m %!*H хE:$vϖ\5r"r[,ClIpIJpw)hEPͦ9k:U>U45i,7+sLVYP,1\hKBW_|},WUG7VҽePikK)꒒B''22yNz>,!ҊRR$  _J΃j6pmǷ!JJЕ!*$)Cr H83LwIav%J )I>mvLBCO-b\/*MsQG]5C%')^[ƝR5&]rUmg6JBqݭ %m?N;qfwYZ|fS?$%=1㑜EjTYN6ޥ:kZ5ږѯ!Azv뫲n4 aeKe!JNE-ɽO <4Bw[JARpuNÐ0kҎxywkYUZާBZd h!G;- )HQg:a5YVA H_5PeJJBֹiHP ԩ}}+VrW{‹3Nm6M:B9rX)/s[Chqqs[*9*u_5KvlJsòT,4QBT)+Ң5xΣwkue5v;W)7;\֭i-o"#O!JqԩVFG[[zSxii%$4"4!Eꝇ `)G\Y$N|Mn6W6\{+Oip-ךrP$kXR`h.:OPZ:,0 uP海N|Q>ډURk}G*t:MoVݴLq;iU l&YԵAE+PiA) W^q4i z C9 ʔgSҹ*k}}N41¦u>7/ViWO-V/TGm䩵 KQnL7@OQ45l}G#rTڅx&Bm 'i(E(|}$ãrKG}ݗEݪdsVŘ!"%:Qq;zӦ jAg"j,ȹhtAR}x@=OD:I5toJSQzZ[Oj>p^"5@]nCmPTe>90qsrRQ:;G|C%+ ;ׯ\fd{=nvsSfUjؕ:1mdHXiABR3$Vv;W)7;\֭i-o"#O!JqԩVFG]"bz9Ҟu?9]]ܛPCKL-$(Gy) ,T9+"Tū $[x2%\!ck\(uMqU~_|ny+nkM;Ҫߧ=[kM7+k]kxL;u%Km*qߍeHlM;p$AiifYP8 Y ¼$\EQzZo4K '<ËWoF/Nn$}[x"[l J`6+5]h۞zD30lIY %jTUtGN K=w{7٫UWroM²Gn$ҙo [ ܫNཪXu)>}v%hQ=].+9=+O4F[aÚĶFA=  URVx)ww~; T2f(ۯ s(QU[կlqm&(7rP#yiZyɎN7cTX؊4`[-R7J9RԢI'*5YVU^"lڮlwqV xږ|whbVS='p3kcBzmG8dnPNMD H---ITF+ gmb TS.u%jpZwwB}r<@FTy :JWwvܚ|.6mO-Chx7;nme3n+%hmyWR gc![I/H?қ-3%)4uRˣZwR[kuZt'V?oАJzVĕ'xÛmݜsX=.I>mۭv8g1_!@TtݓE<yaU reNYa bRU/ZB. q->8P*VКZԋtԉ; XJ9JJ$dnRR5GoGG}KZo7~+m]D/vǙmLPLFS .6'vsַme#Qk:z2ddr!\.HKAz֕-dBuU_Y;oy])V;(!g* ^8aF|}t]J)A[=5w_ɟvaZMqrrP**-hu R@t⭴ǰRlm,mom4J)AUgg^<ΕKxKBQHoWWk̙qۤڒO)M3ݠI SW׫%-=+/3v\Qn1FC 8Gtqjwj܆LmyANpRԅ9X=2&5W JϟӦG޷\~ޅmkT'Vyd )OLQŵެ֝3rܳO\GaCum=- rM^k) m;P!)BGR+ */rD*%{.eX?=߅VMv9cCiݡ7'Y'ZP 7 6Kݪ:mMJl!v&-b[[)q ĕ$(Z+(+kӾ#VYG_5}w#˼ݭq cm.FqrfvVІ #PթAҢK1L (S)MI8* }Dd^Mx: Q612Cl<PBv rSճp2cA,rݩwKm"@Rz2z$Juw>FNU[+hKv d3FZu3uddY]㑽%eI'y;X( kE oMJ@}(_g}-%V*:Cil)+Nޕ=zOs.εwIeN22ꭥooCd-6s5ǩJJii X\,JMmn7/JR)JM=4JRR?Jx ߡןKkӀ ]y)?D,vRbօu}օugѣ"GV)['½& lUr|G>u0̬UU)@)JP R>ځS")@OQ@)JP R)@OEOE)@OQ@H"T"55T*@)JP**EE>U>U4444)@)JPQSQ@)JP R)@)JP R{hi")@~C7˯>?~5tקz3SX~MsX*N /hϣGE VR<*O{L դUr|ՠaY+ƫdxUUR$TT|*|()Jhhhh)JJRR&R()JJRR( 4@E)JJR)//C~o^|~ k/N7fyuıiJUZ˭Z˯^џFd9['¬xUl *< UI׫@2WV5e+ƫdxhW?XoczT0ݬG|-wζ#wS8w5Y\wX5&A'ʢTP R)@)JP RTjOR>ڊmE)@)JP R>U>U>ʊeE"TP RyPSʀRTPPj| )@HTTTP R R)@MEME)@)JP R)@)JPiRS_^ ._^oϏ߁Ma%5ωcҔ8+ߗ[ﶴ+ߗ_>r<*OYH>0T+dxV< UVedjWV51̮~߬ aX[mF#k[`qkβMcָ8j*MEXOEI()JJRR( m()J}>ڊJRR((()J|*|()J}>ʊEEH( 򠧕( 'ʠTJRQR*( (()JJRJRR()JJRR$COm()J ]y)?D8 ߡןKkm)V*pmhW_.mhW_.?{F}=(xUl U|+ߢ`V5W'έ$x^ ^5[#Քkբc\aYS3Hvk:GkYN+#T?d;qq`T*Q@)JP R)@)JPS>)JP Rj*})@)JP PP W x8Caۮf}Ǵs!\ iDt@=s[8OEEбE}DmoQO$po[ ]cWܟI?޶5O б粢O ?8OE.E}DmoQ\gs]jWE)!2LrSBVTQ8_@tEJTE<)@E)Jj*j( 5>UR$TT|*|()Jhhhh)JJRR&R()JJRR( 4@E)JJR)//C~o^|~ k/N7fyuıiJUZ˭Z˯^џFd9['¬xUl *< UI׫@2WV5e+ƫdxhW?XoczT0ݬG|-wζ#wS8w5Y\wX5&A'ʢTP R)@)JP RTjOR>ڊmE)@)JP RܝFǫgmNȺEqԋ QSNMaRJ*mkH ]{qZܝFǫ}׸ CPs#KM'$kf)k3n,MJL)NScm ;庵:TZRs@!Lzf"cLq$4-lPrWM#\&>Rb@ YaVALu2 +Z-6>6m0km'jJKFx)I?kӺ>~qⴽ:"8pOԗB)8PHR:oPIcHշo76]Q\>mr)JC+:n-h1t#Gap)_nRe-Du9WAc?EZXFܬ&Z, pNJ(gI8#'"M/cBL\T*6h&D,9mwՐ8ڲ6[&$eM.)pćJT$^ߑm %9"(6 6[˜nI.He/+6 $mi mcR,MCKH %ʜFRpOO=Cv/jZ$Mqr[%&#*9mIF*~?\?Y-t O* yPJR| OA( "**JRJRR( ()JJRR()JM=4JRR?Jx ߡןKkӀ ]y)?D,vRbօu}օugѣ"GV)['½& lUr|G>u0̬UU)@)JP R>ځS")@OQ@)JP R[ u%m-q1#RZ qN/I$(O_׾nYҀ=uJاnYyTPnY+?bPnYogV쨠7oҳ+Ut{o1sH> V*()JE<)@E)Jj*j( 5>UR$TT|*|()Jhhhh)JJRR&R()JJRR( 4@E)JJR)//C~o^|~ k/N7fyuıiJUZ˭Z˯^џFd9['¬xUl *< UI׫@2WV5e+ƫdxhW?XoczT0ݬG|-wζ#wS8w5YM]ũћgjeDjKg JZSQ_P_Kig=U;KMվlՀH@a*mx_JG[6jvNhM pPUSYn,||+f5>͛ml;-؅:R2$׎ѺO[\v|164qC!֔g5qJ.!m͵Ab5 JsvR3pkY՚n/Nٵ |Rs BESJ8.Q9 v86 2P}TH8P57\~t atXѢ [YJ7 Qꭩr8+pczzEl8KQXJmI'84 wm;f&hqM͊gGmmXޱ ' e9=̹OmxqZ'l1-m +l0y֥D+ &Vvȝ63Krޔ2BR-5aOCiw\CӶ=<ol`5! )F'ªw`j"]7gT$u͔TR]J R8x/\VaAZ-γg" <0 w. mÂgEkxRD8LMheaIR/x8t[ѮöEj t?|m;XT]GJgYM1Jei ѼhroY *($(qWUmbxtvybi PJi+|/lgV[mbSa[ujl) V@y8 x/[߹GE ݖإ dGjK**iz4ή7Jد'UY4ųS]lŴ]1䨤̍à9N@$n df (ZV$ A HڭݚIh=- "T^vN@R>|Yd˟hjPw# p'FN2HhX(aحTeuLŒPE)QҰ8})]Vk_mN[ڒlι.1/aRCM6SiH)_RNZ9YFkܴ`昴Dm>a ۬Q8ق2j⽖ovМ?Lt 7`8c*P(#%Nt;4'Q-V{PC6{r$= cW<+p TL:gMƸA?c㎸:Z[.uV{Zu#1VXl)!LK4HC-LIJ?W]9ks:U~R".W9wX N@<|Ev>\Aҷa5 [eIJ'!)ʔ}jK*8gw xGLm{F-،rܥR$xGC,ssjC۝U Q)qKihi)J֕FOC&ڵ} xEpVNۓJAj^Ƃ[RBJ䁸W8j;n&3 KIu^s(`uwJ >ҾV+!]mV>igGeUJe :xA0GFYT)_HpׯsPFvp7ȑOe<õn+$jmڵ[-CZ5 Cge0WZ,ԦR7Z\cTW?vxutEvwU'ʉq JT THi>$xԂjM45oHQCcHjLw?~5tקz3SX~MsX*N /hϣGE VR<*O{L դUr|ՠaY+ƫdxU[ڐndrv);6r GHۭNqtz~%GkKr6>Robvi譤_/ҫik @OAu BnM*;CjBN\NQ3碎>~*6mt-|4.m3uea>PzKk)ZWnī#IVu.)*`_:HMD!-E`3㶹U)[t﹛\XԦ.RVR!9\g¶ζp骵Drlv-Ÿ%E-[H *} s[.{CoVv2gǙ1bsDp S} zc :)˴+Ψ禇u=C4LY2ʔ{©QL&sO^toŲ7}dk 2> )g$3s%9и{]lA FeG?Ҭvo0^-p8 tf7qJ8KjI*_MĩGszoN,{lK-W2I}kFJ9RHXH\16h1-Cە<:x(*uNQ4yn"fkuPVͬƹGuv:u BVH=;DKe_D+L$xs-ṰDHJO,7A'ʍ|JFAUƑHЋ]kQQաs"e.\LlM*L٭k}<4# BpcryכF x%- qJ  Z1`t^lwnC673qY?,gs =РXHAAO*@D r^n7ͩ~b+*8myJPۂ3\Äw'ξѐe"FoJ< dDtt'H VJ=+c线tz]vKG=3[mX:V{78h,:R5AN% ywRHRHA)P*R&hڀ[x)t/{]ŧ[,26)wdOS]ѺLhɬAo.xj4 >%넞|qPh&.w߹SX CPh=둤5J߅/29+A)>yR}[@ɧ,1W%S崥0_3r7pB7u^s5jm=c242$,dGs V (%iRA$㩭u]uj9*Q9$mNMX>Z9$Pk2ݸO1%O1(Q֨>Ɛ,o6ޗ}KERNP0w'ࢢC5 nsN]n6[ۯ!-OdݏA^6)ÀBvżjvGDI}&Xea[PmR}ix# ɮQN\I 9Nmj4ző;Gg F:`Ze0ifߴor.K3NNFޤݞK uXpRs*/_skt~Ytݤ5M..w mYyθv U)Pޤu?tR$2!V[ԂD-scCVP.n ۄ6Sqq2i);$2Us[ot,/WHNqq vIJ|;㴩qRA }}sNɺ$㥓nkEx?1#)*R>ꇍs⻨6]aSҐ9Q_ ?Bͪ$;\rEL[4Pd+j8mgC[n3ڿ駯v&Cj šVSp0fz2hI5ٸUlr8trܸoLt2ԄMu]ԟV7;\fQ oV}-23vۖĶ؃) qd0OkB5[/p\O(v2C 帒'+QL4IjgcYhyjT9P w\JJ2HX$x4R-H()JJROm =R(Ҟ7fyu//C~o^|~ k/|KX=]~}]~hHU|*GV~[#\:jOz 3+%xlVRjGVesf?XoץL# wβݬG|l1W| e;XG}SXuk4wIDx8(% (MjM%vqg@ӥ%AzTC-4:o'sKR NHO#5lYgʢTT)JP R)@)JPS>)JP Rj*})@)JP PP RTTTP R**}Q@)JP)AO*)JPQSQ@OA4"TPQSQ@)JPCCC@)JP R55)@)JP R)@I)JP ROz3SX~MzpC7˯>?~5t>%JRTЮ~]oЮ~]~4{$Q>e#«dWDPjOZH5W'νZjG)^5[#׫D2~7Ҧfb;Yn#u +W| mF# _k}SW<=.I}>QSj 'Ңs4&Z}J EČ/@<{ʾtskǀ63[=i;$9󞘮}zz"HmRQ&BhX;Z;BJH_{>[kԣ9f_Ue{ZD*a[Hw忑*ʹ[[7mgdLT -;T@9N@Kk=KnK8,7@J-#κmjŹ&"U.ێe)OQl*;6F9濱ri]nJffv"J\3lvBA{ v$i8MX]t m07)wJi[Q v&Qdg5R[D,#c6P::}u>#ޭrhv u7J;l@Bⴴ+k/s7*br6T4dK!J{iYBIN07$g#9An4E.5rZJ6K$ nOV[ :g܄AT2#@x%a^`խ[@1v^r&-FtݎR _s)R[hjL@_ ZٙmNQ1%;JVyJz$xJu5ե_zսZ~,i90̅ -iF]P_1)r~!c=deEjJ%E6 IKjQR7 n@AҺƚU%ݡJm}̇pShKd;0NSuF%^^Zy+JO1@ Z RT:6­Sd뻲|!k*Ji,)kX =HGOEt`ڏJɹXmHc3 2VJ㠧f8w cvTָ&unru.8Rn&d}bm@m鴤9.zhL7˛$5G{NMmLjI;CI /=|\iZS &"ucm+mG>yiߗg}ŵM x) GL,X3wkH} %i5^adXIm]Œ*q! p) TT m$%%YNթi3WvmX, Z܈+ISQ!)uJAA 9?]JunOzEV4φK-Ɗ\ JO0QdU 售~¸M2f-{R4a;aiUBM̙vdd!GAu)PiX$̓UKkW^~3>riqܤwJgCS۸j.a*7%^ZÎd4!KP9$ zͦ47rSWXd.޶g{r)Bi<75KP{.RVNOu"3xj<MuqIRβA֩|la#HM.|DfJxeP69ZYfoC_naVHoj/9Ȋۅ) ˭OU4}]e*-9 rC1[J29@cOEnwfYc3``]Ɠ} IIk))OF]Y[%ءZD[<ʥ%ƐZ[mB%{S3$nTR.w JGBocnW3f}eY-}O5QB6 dg6{^2$Q-q21nS!) {R]E# P5"J|$=^jG ʠT@)@HTTTP R R)@MEME)@)JP R)@)JPiRS_^ ._^oϏ߁Ma%5ωcҔ8+ߗ[ﶴ+ߗ_>r<*OYH>0T+dxV< UVedjWV51̮~߬ aX[mF#k[`qkβMcָ8j*MEXOEI()JJRR( m()J}>ڊJRR((()J|*|()J}>ʊEEH( 򠧕( 'ʠTJRQR*( (()JJRJRR()JJRR$COm()J ]y)?D8 ߡןKkm)V*pmhW_.mhW_.?{F}=(xUl U|+ߢ`V5W'έ$x^ ^5[#Քkբc\aYS3Hvk:GkYN+#TՖƿ2sc"$o>p҈}A(+@Q!T<|jT5)%laتo=֛X9ڷr@ ԯm,=QۭzNm|{h[zeq*q[%C86-E͋-%~46+)iu|_uԷ(I͌9ehuu‚:w+ YC2.S,D O."DWZA').3uNz5 °i[pC̲vl[ P./ V<,>n+mQc(uHZҖZRR) m>[nV9k6Jmn-ܖve| ԼrkǪ챴#kptDxO[.8TPrI泵J (H!J' / kO- /^) uIZ{*rc.뢵 Qp:*/zq[6=:R.n2_xnmn"㴙ڔ =>vK||vi4Z%Rw(XI:mTe,w$cGyě8n(6%<Մ)ޞ}zܙDLͭLaHq-(Jp/cZҐI$uVhzũkjnqFTaS{EJR]B$dwqsa]٭XH]tFgj3tbjXd [su ^V$WMtvtVX’A*| `޴F"(:%lCe*mDqJݩH( mcۮzRrV{Tˬ;;yda>^wDĹMQ.gb=jm<7^' _QyiRPDܯqݡD+cR";¥lK%;q >e:a말(1$HT@K* %{Sꑚo*mzfuv}gV-4]qRհFA)-\6kWI%(rᇐm+=7uVpFm D>\ m ;@ta)OQi+;VA'A}2v!}>%!;v}2B 4p'>iIISuӕt[iǎ˖ sJ^KU8a+LSxQLoF͂Z-Ѣ=BJHS*=p<zڙ&ݥ8lBZzHnR䨔JʜR@ܬ:kM^u7hۼݐIQS%;z' Cth @gwn?u$t=_V&U$q&G6'{ tlS|Ґ},jUjvHmo/1n2voe{ԝrʻ4\$ٮvƝu/\\Klm!pCHI*R9aWV4^daiKmlqLnNR:{?ZBËVĔ[m!]Jrs[|'*H z/ ι\4v#v-;%$\AJTڒBSRJR((mvz"_Wrswv/9[Sc'T5Vt.ɺi\vXmlK)(}ғ/ `e~&++o2bU4{c%68BB' )z;R[iIJuP-QSk  z/ ι\hF2[RwJI渂$hE(/48_M=LDCum/9ZS'!DE WWh:gJZg܀EmluM>mF)!M7nuĨn iZS۬|F$%S2{Du]k+Jl:uwwS6cmC茔! W$zg ̺Ka{J[+yBҰZꓜ\ikCi[,)*Jչr(V&lчQ.;NF\,(T; u%\RmB+p#*lBo.ʗ.ɔU m%x˵Oҍper2[B4[72ВUx-R8vՎJqq~dN[ [/ JV e!5mnAi`ZN[uW"][- - %*B*-;Az`{M\+Uz ZA29:*䔕 Dѱ_2.4Xxb,Ǐ1 !ctwh:e~k} Xd[B.>I@ RJR;D> ɽI=/BB84 ݲ)9% eO@2dg-m' VH8  GJ#\ؙ&DR$HT7]CNMWOD irCڐMج%-L`2OS1omzȫީ&$~ IyRxAZJBђUp\BlQ.1mCn>RʔSc* !8 ^KNNn\kbJRTCSkZ4 9iiljLmrrJ]q>eJQ淵.6!BJrRa]^Y[N&SKZ[ym-HJҕ0w_G;K,;6RxGiZ#!6X ztӈW{- {C}6ҭۊ))wNp2I1nҶUPG\hʹt u\|2H';JD,~smlyKAr-HZpՔ1+:Ţ%8ՙ76s \a )!EMCϪ vxa\[.;jqr]v.:FP <z# oLΪ5ֵݝW)p^]b\d0KŅ0$F:'LTcJec2P%4e$z_s&rn&+eiXJJTH=A xQ*djŽ=fFKi /Tŭ*Z*Q9$:`:GuNymp6>7o(9YB^߂q(^eġd-MoBA”5in##FGi٨)GCh&O"K)ʲ@ bݵeNj雍 KKm3M,a,+rH9TrJFfv"ҋnfƻey{}َġeoR232^2YlC!2-֞j ԪEbkkn0KIeR-w?Qۑy6 j=ڢ\qd 5 9秚z-u/{qQqv#bCsX ZuA–w%(3EYWYL{HvKM%^ RTW$mg\XX6YxQ*K!VJTFUOD b;[eؓ v]()m)( ڞ$55]_P둭91S $88*b;<)Tҵ) -ihzВpT<ȨhuCƸe.8ꖐ ի,>{WLۭCh1nI.e[.wBNv KmzfdĒS)_JTӅ!AiNsҵݮZ$_B=!mw%=ףvW{esd2q@)R)Jj*j()JJRR()JJROm =R(Ҟ7fyu//C~o^|~ k/|KX=]~}]~hHU|*GV~[#\:jOz 3+%xlVRjGVesf?XoץL# wβݬG|l1W| e;XG}SXuk#N=l}"ò`2q/F 1Z̓b\pan;DxL,N.8]u46]_%H%+Q/hGjjM:w6/m>ZC{TCl8[D(wkU|X mWŐه0ĤîR CAA A_%JJyGmRV9#j\9?69R\VV|Uʘ a(BI-!vp@5sgmFB VRTu|ĩʜQiI9<̓zŎ K2.,),8=R'St:b%2eΕ/8 GvSYt'rI6JR; jRiⰔ eYRF ôcVgٻwvOW9'o[HR5'˿DVk2^d=kk/(DeC$(fiVgӮw6IkyгIIS_ RF_ֱ"CWl缇JZ(ԭ(@RTZK}DRV syCb`:lԗ4\aFAKԄ[aR## u~$)J()J*}@E)J]Y|7M75lU,<رfܥ ]~bV{ ȮS[ˈh~ݒ, `ZzrSa-+06RO{k+FȾZpJ.w(Y2%K`!IXVbzׅIXĭ?bXoq-WYìj㏄! N! sXz ^l: ܯk%) ! @Z@AQݸtǮlޟ}1p6\ayK+eEnX!HNġkiR+e"ɒRCKڋEF:>WzKjk$oO}>oq %KjKO6Vgv:ROr"3މM4k,SnB sQyS`L;m6RMC$8=dmCVvpSȸ\)dSn)$IJ$X&ӣ,ʶ.E۲aHi*f&9FF:TBRHۀM{Gna_.'e^dJ}ok;V7h@IU'9WHvNy;3]Qihrݢ˗gY)at8ԅN@#r1+.幫KE.1RÊq TGLt i;4ޞ.vQXmhm)wJ[[H'o/VM)aGvR=/ 1!]eqM+*C0Wmoh/ִ[H/\N%C)RࣩP8v^ X`SRaL8-²!kZ޶8o؝ yDR攵tԘz9mb<.1mr u ZRFxO -uȑ-0nFqkmrTKKW-a;{e ]QL_ęgЖ%-*)!>xYXߙbujma<_Hk\Z􁗨t$.oT8ohkj[}  '67976̙mόIt$QR 8w҆۩,vci ^%9qKZY W Y?D4Ţ-GsuOrX}>܍Ͳδ,t = xn:^r=xv\wfSa曌-D=ttuNj=+ plB$N\ы40I9j$翹;jUxڐn|7u [-ܨ|UдKY^rPqR'.'dD"\Ź!8aVJ-'f2Qi=CiYֹ3u}oey k~,`Ё[^%}ڮ;# M!7%!$0drOѤtzok:DI^ibrTBҔQތ 8jNŨ9>Ѽ^˷fw3V]yav6%O^d"pxj2VB8R$XQs{b/7?'9<ݟfww֨!S_7oj5; m mmGm*doW`Mqpj7-1&$_NK{NrJP7Gt)%+nHm8[' y҄CKHؒi)g z4lcK·h~on7T]u(Z[Kn)m, % gQ$qCU枔^~ZhyZ@./kh%KF%#rkD{- #ܙ )0\ɸk[Dm|5`տP%JAr敭Jqy-:N JSp(u= Al1#?p#IZ)En%#tY)J&˴nje˄3f,ajB' 9KiqNumUVx-^o2Iud ) NO*幫KE.1RÊq TGLn4s7_;vvw~.ͻUe~4='s5-46ҁ[ŒP@ont5Ό9nEulm(]FI%'A5Z֜ esnΕ\BZ(sRSIQZk5&y7< lSŝJ7-x֭ RhpZB*\joSd %I n*۴fk68(u.$ʸpJBeNM%JTTTTJRJRR( ()JJRR()JM=4JRR?Jx ߡןKkӀ ]y)?D,vRbօu}օugѣ"GV)['½& lUr|G>u0̬U k`yZbħb.WvmH)1n]?Jjodf%}(A7tQ_z-Er&9r'Zs0A@%Ԓr Т0:%dK(mH a`mAkiuXm|X%DfOZڲʖAKI`d-HN:C`ZzBF0H;MOϓoƔɷIc[k}M (9$楦 %i0hLn"3蛪4[WQqۀ`e@65Pj- Er{J(^J*bZm|7b>erě̴Ȯy.Eqo#bғ9 JLdBҭ3-(w|vSy!FRZO VN&HZaS ruRuP$䎅;OZVo[:GOK ĆWe'BHt`a.~I97+8nL9ԈdoNyMZӻj{lZӎ%m֛6Eļ[;y׮TPZVCjRJH@#{ >Ӗ :tpvu2֖1VH8 o9N s[81\oLY-3 [۞Ӑj Ie&Am#$IWּfq#Uܭm$)oK ]v@yN6r0A`u86kXb*loqz;-Е}EhBԬ! m+ob # #D2]CƐO1§ JW) ']O"4k-:ZmlVTtZhx#cMdqȑ!GF*Ta+jw)J9R֢TIQS*iJU&ظqdu|Kdyq9O|CymV'!7qV LX2%Nˊ`JrܥC-Ȓ.2P TTg.]$ٮb[C܇ iRT&Dq[A1#X,lY-c[kZ:]T'!`Zq*N9Jcf=L%8ɐvZT#&JiT-K$nKeJI)|=hɶH$e2Ӎmq)P?W\\Sxlf 4ΈYp:JbSޥj ӷYmӎ! 6KhBwpII'" n/fy\r㍕Wur=u8 B4T>";)haqvRVdGOS\8*EkRoK x܆}rd&CmA#^Y{mKpЬ2mm4C ;Y[H'8t2pMc&tU6˫* IR\ 6ǘwx`[EBXqׇ5%+xF"\8 孢VKGBBJJip0DtR2sћZ\ $: _DG5殹Yݵ_**g]Lu6 $ť`bjL{V)tKSmhVNv$d`]ާ Pj-:BP/UI5d)@)JPQSQ@)JP R)@)JP R{hi")@~C7˯>?~5tקz3SX~MsX*N /hϣGE VR<*O{L դUr|ՠaY+ƫdxUUR$TT|*|()Jhhhh)JJRR&R()JJRR( 4@E)JJR)//C~o^|~ k/'^=½&6۠5ha 0o:d/$TOFR 6ZO Ri˅,:ӪJT0H=eJUZ˭Z˯^џFd9['¬xUl *< UI׫@2WV5e+ƫdxhW?XoczT0ݬG|-wζ#wS8w5Y\wX5&A'ʢTP R)@)JP RTjOR>ڊmE)@)JP R>U>U>ʊeE"TP RyPSʀRTPPj| )@HTTTP R R)@MEME)@)JP R)@)JPiR Zo:Wc?[r,uRrօu}օugѣ"GV)['½& lUr|G>u0̬UU)@)JP R>ځS")@OQ@)JP R)@OEOE)@OQ@H"T"55T*@)JP**EE>U>U4444)@)JPQSQ@)JP R)@)JP R{hi")@~?CF3V\n\=]~}]~hHU|*GV~[#\:jOz 3+%xlVRjGVesf?XoץL# wβݬG|l1W| e;XG}SXukεqŃQRj*|*OE)@)JP R)@OTh)@)J-ew%[-[̇=}$[̸ wc~H'g)̬~ڊ:_O-69}Qt!JZ@vJ/)/^#ڟٲCIT2-:+9HI$9 <QO2dPPͤR()[np.nϽD4"5zpJ;zϱe}t[nҥRTC$$`olj8S%}Eo4O*ߴ,M ~\Cv\m T5K$цאgIۣ]'K!ڛKki wr tε)[z4T'ؾm\!)5aÏ, '>{L{l#(PQuGP΂qzIΊ_eEl<v(O?͸V(H'O_!w6oi S0~I  DEJqi)m`+j;xTm"W4QQ[\-) ڝD]HSH[J7 K&as~=xs꜠ué"j+d]A.c"*aJSk #_ XaIW{3 *3P!Y8!GJQWaM7bSʭ44G?x.Ec6'’'yNI: NO gAW8ҷ<)[IY%I;FЗ4M+k~]*>LH0ܔ/1SܡRV½4gӝ2Vܯ2dPS;GXEnVm-jyIj7k)kF[Ms}फ09{m__.9sL!ISz<T:QD| mZ{K{uǦO[0y[A'N 7hZU]Vyý nJZ)JRĞDhN[|éht|7!#}]iTڐ}^`ӯ]ĕmql)dJTuJ$d0LN$Lgn0EHe(()JJRJRR()JJRR$COm()J+1ݭʴtv*Xt*N /hϣGE VR<*O{L դUr|ՠaY+ƫdxUnBg[)s)Ҕ(,ќݳ:~ٚ*_*\\N{U7IEgFi!M\#ۃËZ4Z p 3N0[57B[iMTFRRsj+HRPT\E̸%M%63 !(˫#(g`UK6lQ!!֗Ce*HPݳ9JIPHQRrsgtt[mR5kՕՕ!lH Ii(*IZ7`S^ܩzjj$F⸩/rV$k7e>jHtJ_Ἤ_™6<6⛒ݱP7`V-f{V+җ&2pdtJFڻ=랫cN"t?++aN8SaY VPu uX\^vU6sdnX_xwaֵereg3Z?6:\Ss%i^400 f~)+3.N\qHy;^(q( ROPV-KK<΀7v-a{IIلNH fӣ'HEۦ:ڂ_) nHB$2H8=~hn=mDmηc\f-#'q=W]}oQmj+2}rr&Ië7TcX?ēC78[ c*%hJӸNҿ%XMa! e}QۻwLR~ZͤuޛU[eJ5 p4a{ڐ:w$DdRߨ܋of#]Qz[JZ Pܥ+ JIk.KFb99On؎ AŒ{gH$ ݵImĖh<Ć -HܝA"Dխ*-Vi&ZS[Dۈ*~B9MZH`(7cUѱ\\t9-.m u #)I鸟q3BaXfcŕ%ZWq|$6 Q=S#'9*u sȷa/v 1'zqѪW Y/l ko2~*üyuztFKLlSO0Jyx@q Ah4/m Td v(('Z˷hsƐnQn,sqƗm@̓j?8PPdx+÷DdD$d10PmTr)Dz[Bji3̟iʰkF+DmFhNǺ[r\K ,\u{ 8> SWqJ 6 8+@̝sDu/#V&uۄV1+m|Rc %#*RI=v]e&,uZ-S{,w!(wys(q=2# FB4D̛Pub}bC0%0SWMiT.•aUP6$HT.)P6Tw)DwphױM+ݍmdٚ 4B}<¥ũ*Բ@c"nra-1cÈ̃,B@}0Ja*;r1jNZO1W'cKuiyŶ”4s2ڎGZђfwKLŊmt4\A9 Z)8Vz_xb+n$,9 ?6-$(y|@#^Y\bK+Qs?Y.)NN  κs$*l5!Xqef;Rs;ᔊ}հ-Wō%e>I>'8guO71M76{uYC/bm q~P#Mf{VZ]zuA=WВ-iʷ` djm1bDZZ.ݽe!a!8OLd5jM[s-c^o1I6T:TT#~9u_c5[t>9q$nNư<~ 9'+ܶ5[t>9q$~SNIVk~з;)R⊒0R+*v:秣c/  @@HR@^h3SvmLKfqŴZ"MC\%L|A,k9Q4{=\e KZێF\X'TAQۗkyNCLc8uwJ@KK$>cGł)7Tp ڒ0 mc Fj76v5+)#LX4^C*ܤ\.e#,`d (MZ[K% !)Jz% dN:jhDV[ ʓ- ܍Hmxs$cr^&)oDQ\˓QSQ@)JPCCC@)JP R55)@)JP R)@I)JP R/:Wc?[i_ьnUȱJUʜZ˭Z˯^џFd9['¬xUl *< UI׫@2WV5e+ƫdxhW?XoczT0ݬG|-wζ#wS8w5Y\wX5&A'ʢTP R)@)JP RTjOR-7K#vqnW-HەPy) QmaHr:U'5sosXB32,b1MTfakTRTOPEXݸu?vaw&,gBd$cnIH 5{j+TIn]18SyC w[6- J(OLz7Mɛ=" ,bb]JXAmiJ2%G)衒+G:_Yk`SlɇB %*B`R OuD6RRklR7Õ= Tb#m3}늃Qgs9;Ԃ6H傝')Qk%PΨ[^Sjr uva/Ts چǣI^omnSCe2 v)ýIHk>wk9Aarm Ҕ*DzR.;qkC#ct͙N{1J.?\RAI;1ҵma}sP\ۖ=AR ˖8yHHV&A'ҚZ܎ ֔%µl(V􁹼5Bђ#"M1b [gJ%ZRVR %`'U*ӽkK.!^ƄBۓw v|!P2<ɹk=̦..2E/*[kRda]1{* e *> L\m)<۫aAN))ڐJ @ƌԏ顨oSKyhڜJ2J0F6yCɋZӖSf3OK.\τg~NVKE#:$-'1"%yOg‰_tt^ӯ^MqTۋVu{ٓ,'kC[AJ %Dw:cVU$qL~M:l n8em:y5wۮlS G+(mjZ0eJ$0I  5=do ا^lhFmFy e.e;p7rUZ)UdEEHOEOE)@ )@)JP RTP R)@)JP R&{h)@)JP_ьnU?CF3W"[)W*pmhW_.mhW_.?{F}=(xUl U|+ߢ`V5W'έ$x^ ^5[#ՔC_n*ErTP#E[ 8% +֠ckWb=^8KR\Mwζt.>;Q~{3\Fj?o}ˑMφԟ5#S~ָ&q4}SXu~GM dk9Kbqɚ?Kb?KbA*O?d*=?d( ^{qT~S?Kb[G15G=?d( ^{qT~S?Kb[G15G=?d( ^{qT~S?KbO{ bj/{ bj/Vm\A_&ؠ5zVuMQ%O{ bj/XVuMQ%QuMQ%@j?Kb\A_J=?d)uMQ%@kc92kS)qi-d%U(R@s[Vk~-k*ԝUgLG%K2[RQ@o \?Kb\A_U e㿊&eq[{V^6ʴ) Jb6 )HkJ yD2_q^~CB9x15G=?d+NKwʥeY2%+q ^,V܆'O%=0uA>/iVdi"9(PTYm VAW15G=?d+> b6{iX6MW{'ڇ쮲mꄇU9]݄Bz9^=jbzHLn48u؎0)}YZ)݁ƽ?d*=?d+&sM:ܵp~!TBifrrVV|ʍq29,3lςaD4=.iU. "ނa`hp7#%)B%Dj\A_&خ:6Tƣk^vCՋ5l?.[% YqiO&B9RlF CuRtKJc3Rԙ8eEr o^\Aj/{ bj/i5cypz]%>n; YZ^iԡOARy{Js>;{}]]}+aㄞ̎$sӸ7qT~T{qT~T{޿2$ W޲^~WA5fiyR>]I唕(;T|1nL\=,ZkimȘuD8냻ʫ}?d)uMQ%G$Ӭ,-L+T;{j~U,Ȭrz2Ԃ ;8ݧ˚/.smJi)BF0O:&اqq5GVu5fL`Z{qT~S?Kb5}?d*=?d( cʠuMQ%QuMQ%@j?Kb\A_*+i:&ب&ؠ5*}?d*=?d( ^{qT~S?Kb m\A_&ؠ5zVuMQ%O{ bj/Wm\A_&ؠ5zVuMQ%O{ bj/X?Kb?Kb[G15G=?d( ^{qT~S?Kb[G15G=?d( ^+^JFI6ֱ@)JP R{hi")@~?CF3V\n\=]~}]~hHU|*GV~[#\:jOz 3+%xq,nHy8=-m|+ƾ<1_?nII&Iޏ(D(Di@cz>ģ}~z>ģ}~ɥII&7|J7'ꧣ|J7'꬚Pޏ(D(Di@cz>ģ}~z>ģ}~ɥII&7|J7'ꧣ|J7'꬚Pޏ(D(Di@cz>ģ}~z>ģ}~ɭRt]Qrx&'"|!mn)TS$IIW]z 6dnq8b߆ne8yM!@'rHkTƼNL[KW)emYm^uA^mBSyHb|F$T|F$U%ZtSvk3iiĴkd,Ruwn%; fNrc{j.Od݄[obD4Э2 p@ؽQ?U=Q?Us|)CںZ3V;K~-HSRYKAR76q5;<mlvpſ %JKˈp^BNII]kTƼNL[KW)emYm^uA^mBSyH_5ca[˰ʶːiuĶ$ҎέoJ( >%S>%VMrR6oQ\=#$"nKR\]ڰP@tGoOOGoOTr etƣ=e3o!1!h~23Bz6(?F+S:3lq-\@me:R[Symxe N >%S>%Vfծ'WK,Ž:B/öas MB֥(RBz>%S>%W?ԺHqz|i${>Di F\Zǀ/WlV22,rZ]q- *tj,% t|F$T|F$UG.!PX7Lj3Y6S#<-竳bݔbgN1='m{-1"PVf IE8 A l^(D(D$#Fz2fCJbBdgvlP;Vͨjږ܉-ʼn"Je:khrJ;@YQ()mk=ĕPG|J7'ꧣ|J7'G^q*Ӣ[]ɛKN%\f#!e”C#p))ܴ|qan.*.B˗%۰J;:r[ Y*R @7OGoOOGoOTr etƣ=e3o!1!h~23Bz6(?F+PkvywQ+uٹ~KޕW6 >%S>%VxEqG6 uې]*[S Ôw߻ޏ(D(D}tCKͶXؑhS2~ǀV%ZtSvk3iiĴkd,Ruwn%; z>ģ}~z>ģ}~nZ1A7d[q[C%:Pi׆[p^R+oWlV22,rZ]q- *tj,% t|F$T|F$U\ԤKe-L ķޥ2i'2:A JP)Lz>ģ}~z>ģ}~Aշ}U;y&eBkCK :{ck! Uب oGoOOGoO\\.eHϤlC="KqFʚykY櫠#vuguͷY?훜n᤾[u{YqSkP ܠR=Q?U=Q?UQܵWbb|ޔ!%NlԳʰ~ ԎOx̢wY(b&$VL4Zw8qިe.8 HoxG|J7'ꧣ|J7'vS:3lq-\@me:R[Symxe N#k1Q?U=Q?UdҀ|F$T|F$UJ>%S>%VM( oGoOOGoOY41Q?U=Q?UdҀ|F$T|F$UJ>%S>%VM( oGoOOGoOY41Q?U=Q?UdҀ|F$U2$6"+mOѓ4.z*Kr䍊[EJHIm`AH>d$J,d(o\]M=4Ո")@~?CF3V\n\=]~}]~hHU|*GV~[#\:jOz 3+%x?q׹+i^5euquajߌW;;%6m/rBJku+`BVZD=t)q$)Hp)Y#j}z^>$iT銗HS*t /-ce ڙ?s:J^[mPvFc-mJQ o ZTmZwPuM\Hڊ1.Yq0^R7mW/ 9Jל7sTW.r,z8S IˈRCRF'"<ˑiТ'#(GyW+ZwUo=Grܨ$D1ٝuJ)^S!ѻҔKɪs=sB[-#n( \j7:{^DωD:bʝ-@Klj @nmyi@iM7%q"ߪ#j+30 v gqTˎxIHLa\$(+itzW-63 ga wuLn1(JJ)iR:M(y8n ] [iiXp%!‡TdMsuzLAS*_!L$ vב4[.F}ʛXP BT0AD^}ڧil9/zϼijiK (mZ9$wjmt/R!IJjazO~3q\H۽0;)@iםzSM1[Y8zA.԰`q,>ajߌW;;%6m/rBJk^/queժ5Sk7g>J]TTF' h $nڊjzj9{y;bG9;:dWi\O -m69a@<#ʀ5 :w˰!+m-"\@ĸ’r8Pꔬ t)@)JP RDy-#> [eM(E*NGP "JJRR()^qGr3ÑhZż-mZ&:Z#Ii+pJz|}ΊTIc4yu* w#i})pMFwcQuEb;A2]IID纆H>pI' H˪Vrtӷ,!ϊ: RTR$$U}( =IKsW6^Ґ3*I%dKmM=4ԐE)JJRJgkr7+1ݭʹ:)JS{kBukBu 3G#«dU ^BG>ui#\:hfVJ~/s;WҼk韸/CyxRyB;g$;7fsN_'w8qY8i>㭌@ט;'ZܱÎXe#H *G);S ^/MsȼZ^O<2Hr RA(*R#%J$[N}w82܌6#\ iFh..Iqh.rX'RGD{pV+ZZ[JIA}$ U i,ٷLll@,=`A*pwDYSVLiCnK͟$n+os8:vå /O{2Q`ChdQ^T78vKFʚfsnVq5c9N;,$ۦu$Ҷ.$:Wr'<T?.A=OUCsoD ۛ\nۜvg8_gV3òΐRMgYK+oCw(NZJ{3dS<T?.@Ujj'Jr 'r[%V#vMws9+8u^H~ڵҒT_i͘Aw(ӔuW.O#WrmJ*m|s(lQHYs,@*9;9NI \Q9ش7%fZgYvh6s$`CŢH :7cr/J!>1+nmsnrٜ~[~MXSlg3PӶ(a|{ٔE#/'h C'aҊg'R\= NSBFNe!ؤtβZV߅WP RS'HVmHևD@tn_ޔB}URg2<]KV];`sl:P+)F^Nф>A=OUCsoD ۛ\nۜvg8_gV3òΐRMgYK+oCw(NZJ{+!\rmJ*m|s(lQHYs,@*9;9NI \Q9ش7%fZgYvh6s$`CŢH :7cr/J!>1+nmsnrٜ~[~MXSd3˙hm=gZXϐs)drIܢ8dT?.竹@w[vh6s$`CŢH :7cr/J!>V/RoT͋J_.r^vly%vy[0x9Ӷ(a|{ٔE#/'h C'\mͮb7mTw;3/rɫwagHv)&3A%֕pA!-@%=ԙ)\rmJ*m|s(lQHYs,@*9;9NI \Q9ش7%fZgYvh6s$`CŢH :7cr/J!>1+nmsnrٜ~[~MXSd3˙i0 ={aFaOyEyPMEMe ) .eO'sG)!x#k'cΐRMgYK+oCw(NZJ{)vh6s$`CŢH :7cr/J!>d3˙T͋J_.r^vly%vy[0x9{I"δ *5HR5%R 9 m EyX qI+nmsnrٜ~[~MXSJ$?mZ I*/H Cz PGu+U'_I6å6˾O96,{?$(lZPwg-[w\kyyͬ;Utƹ0Au$DUUr6ݷ9S]p-?&g)6E3˙i0 ={aFaOyEyPMEMe ) .eO'sG)!x#k'cΐRMgYK+oCw(NZJ{)vh6s$`CŢH :7cr/J!>d3˙T͋J_.r^vly%vy[0x9Ӷ(a|{ٔE#/'h C'\mͮb7mTw;3/rɫwagHv)&3A%֕pA!-@%=ԕ.f6å6˾O96,{?$(lZPwg-[w\kyyͬ;Utƹ0Au$DUUr6ݷ9S]p-?&g)ܲ46H,_ RD TB~B9[C$Q^VRk2mJ*m|s(lQHYs,@*9;9NI \Q7zV!j`LVgJIQ}6bAsܠ@WNRB;D;Utƹ0Au$DUU +RnmĪfť/y/;6ջ;N;()+ Ht(sp@<{6aI0!rM%wv?\yKG"xHp Qnnè ?Ԍg<š\sDh\}P ); 0e'opm.v"g>8s8ܟuDj ^3BByhߟ8;{U쇕ʺ6ysǙ;YEQI\nCD0;/3ܑ; J`U.t>(Is}W^2UX'EIjA݇P3^jy42AwH@3R1'^wo|Cw1|j؊S8Cxr|vzWn[V!ҒVA6Fl-C W*Z}oYg0O5zӤ% $$$g؍庎%|9Vй0Rw$\ axNUf!۾\5cnEPϩ|!p;NY*g|J,> %#ѿ?q~vʫ-+uli-[,3ϘwIQe5E%sŽpx8wrF))Vй0Rw$\ axNUbX9%;Cҋsv@H5yc?CDʵ!!ҋ<ςIH4oayߝ wb*}L31waEQI\nCD0;/3ܑ; I+uli-[,3ϘwbX9%;Cҋsv@H5yc?ssa@0"H3@񔝽ªCw1|j؊S8Cxr|v&U5x Y|JF9~~ V[ W*Z}oYg0JMjԻ7:RTҕ {CH؍Ș.hm # '@UUf~h!(Wq!!#<3Ho-Dj ^3BByhߟ8;{U qi&[۾\5cnEPϩ|!p;N;()+ Ht(sp@<{6aI0!rM%wv?\yKG"xHp Qnnè ?Ԍg<š\sDh\}P ); 0e'opm.v"g>8s8ܟuDj ^3BByhߟ8;{U쇕ʺ6ysǙ;YEQI\nCD0;/3ܑ; J`U.t>(Is}W^2UX'EIjA݇P3^jy42AwH@3R1'^wo|Cw1|j؊S8Cxr|vzWn[V!ҒVA6Fl-C W*Z}oYg0O5zӤ% $$$g؍庎%|9Vй0Rw$\ axNUf!۾\5cnEPϩ|!p;NY*g|J,> %#ѿ?q~vʫ-+uli-[,3ϘwIQe5E%sŽpx8wrF))Vй0Rw$\ axNUbX9%;Cҋsv@H5yc?CDʵ!!ҋ<ςIH4oayߝ wb*}L31waEQI\nCD0;/3ܑ; I+uli-[,3ϘwbX9%;Cҋsv@H5yc?ssa@0"H3@񔝽ªCw1|j؊S8Cxr|v&U5x Y|JF9~~ V[ W*Z}oYg0JMjԻ7:RTҕ {CH؍Ș.hm # '@UUf~h!(Wq!!#<3Ho-Dj ^3BByhߟ8;{U qi&[۾\5cnEPϩ|!p;N;()+ Ht(sp@<{6aI0!rM%wv?\yKG"xHp Qnnè ?Ԍg<š\sDh\}P ); 0e'opm.v"g>8s8ܟuDj ^3BByhߟ8;{U쇕ʺ6ysǙ;YEQI\nCD0;/3ܑ; J`U.t>(Is}W^2UX'EIjA݇P3^jy42AwH@3R1'^wo|Cw1|j؊S8Cxr|vzWn[V!ҒVA6Fl-C W*Z}oYg0O5zӤ% $$$g؍庎%|9Vй0Rw$\ axNUf!۾\5cnEPϩ|!p;NY*g|J,> %#ѿ?q~vʫ-+uli-[,3ϘwIQe5E%sŽpx8wrF))Vй0Rw$\ axNUbX9%;Cҋsv@H5yc?CDʵ!!ҋ<ςIH4oayߝ wb*}L31waEQI\nCD0;/3ܑ; I+uli-[,3ϘwbX9%;Cҋsv@H5yc?ssa@0"H3@񔝽ªCw1|j؊S8Cxr|v&U5x Y|JF9~~ V[ W*Z}oYg0JMjԻ7:RTҕ {CH؍Ș.hm # '@UUf~h!(Wq!!#<3Ho-Dj ^3BByhߟ8;{U qi&[۾\5cnEPϩ|!p;N;()+ Ht(sp@<{6aI0!rM%wv?\yKG"xHp Qnnè ?Ԍg<š\sDh\}P ); 0e'opm.v"g>8s8ܟuDj ^3BByhߟ8;{U쇕ʺ6ysǙ;YEQI\nCD0;/3ܑ; J`U.t>(Is}W^2UX'EIjA݇P3^jy42AwH@3R1'^wo|Cw1|j؊S8Cxr|vzWn[V!ҒVA6Fl-C W*Z}oYg0O5zӤ% $$$g؍庎%|9Vй0Rw$\ axNUf!۾\5cnEPϩ|!p;NY*g|J,> %#ѿ?q~vʫ-+uli-[,3ϘwIS&͗%s 10zsӣ7 e# Rjؒ8}KތG-Y+>B`nϏ^]9}㠻:$8rz SXZ,[-lH\ZޠTHǀ1s"/vڸh*aI$)_' i6T-H^d䵭㶠0VIw+h)JJROm =R(^tv*x!ҿܫcҔ8+ߗ[ﶴ+ߗ_>r<*OYH>0T+dxV< UVedg2?|+ƾ<1_?nϠ)UPXt$NWm* !jY,U*N˧]*y|In6 XۻT3gq9nOӻ.jJCw !9xp䍩Ri^}D_=Ǟ5q!зȄI@NW:9A^ 7Iݷ9w;3-?&g)ݾL4J·ޑ“ pϪ Rv 1 wb*}L31w2;PU# 9FIs6K}_ O?@x6+!<硭!D'pJr)x/ܶKC%J )^9mp[Vй0Rw$\ axNUB#:wKV?hh 7Iݷ9w;3-?&g)nd"Uׇa;)']6=;== wb*}L31waEQI\nCD0;/3ܑ; Lw:aSc'<{<;yknpCo 6t s ꃁ*+nnns vg8[~MXS|.hm # '@UVbV6U gcewG?V?s wxv!7VV; ܄aI>*y|I=Ǟ5q!зȄI@NW:9A].jJCw !9xp䍩RSsa@0"H3@񔝽ªew:aRՏ8Emb7ma9rɫw`i=0TW;kTQJTSt _Wj("V6U gcҾrڵ. N(4z 1R6#an."{/F^§M<硭!D'pJr)x/GhLF';ٜynVq5c9N`U.t>(Is}W^2UYn6 XۻT3gq9nOӺr;QU_8Щ[öq2N%k IWMC`I<硭!D'pJr)x/vQsTRW:Q (yǃ w$mN’.hm # '@US.#M +nnns vg8[~MXSǞDկw!+XROm^z+wzzV6U gcòćBaG8w^<g#jv\Gy{'?V?sǸ÷7:i( G@1ྨ8~ᢶ10fsYՌ;ɂVй0Rw$\ axNUf!۾\5cnEPϩ|!p;N\Gy{ DqSc'({`2w֩ob:QEd=Ǟ5q!зȄI@NW:9A[ޕթwotA+ՐA17wr u0\*:zF N$9> /I*Qwg_nЩM ~ᢶ10fsYՌ;-ylJZZ'r$Bq'۾\5cnEPϩ|!p;N;()+ Ht(sp@<{6aI_G_*~qo= mnt-!;PNc}Pp=EW8Emb7ma9rɫwosa@0"H3@񔝽ªCw1|j؊S8Cxr|v̸_~BNand"Uׇa;)']6=;'÷7:i( G@1ྨ8EQI\nCD0;/3ܑ; J`U.t>(Is}W^2UL_G_6*_CGhLF';ٜynVq5c9N =Ǿ *pmj#)J΁+ m@%WWCw1|j؊S8Cxr|vzWn[V!ҒVA6Fl-e_/uETX =Ǟ5q!зȄI@NW:9A^ 7Iݷ9w;3-?&g)ݾL4J·ޑ“ pϪ Rv 1 wb*}L31wN\Gy{*G?V?s wxv!7VV; ܄aI>*y|I=Ǟ5q!зȄI@NW:9A].jJCw !9xp䍩RSsa@0"H3@񔝽ªew:aRՏ8Emb7ma9rɫwx[öq2N%k IWMC`OOCw1|j؊S8Cxr|vvQsTRW:Q (yǃ w$mN’ˈ/uD~§NxxvB"m%9^<U{4V&#vp<+8v0\*:zF N$9> /I*7swˆm݈38ǎ7'iˈ/uAW4*~{|;L&U-|,GTR?;WڀJ;÷7:i( G@1ྨ8{Ҿrڵ. N(4z 1R6#an& %[BCH€aID8gp);{U 8_LM/X 4V&#vp<+8w<;mW+[^BVtڼ>V$7swˆm݈38ǎ7'i݇e5E%sŽpx8wrF)3^"OՏ<硭!D'pJr)x/GhLF';ٜynVq5c9N`U.t>(Is}W^2UYn6 XۻT3gq9nOӹ^hTX -ylJZZ'r$Bq$xvB"m%9^<Ut;()+ Ht(sp@<{6aIL4J·ޑ“ pϪ Rv ^&݅KV?hh 7Iݷ9w;3-?&g)݁ôRe\ RWuE)SO%~^ n6 XۻT3gq9nOӺJMjԻ7:RTҕ {CH؍%_{ 4G÷7:i( G@1ྨ8~ᢶ10fsYՌ;ɂVй0Rw$\ axNUf!۾\5cnEPϩ|!p;Nˈ/uEU~BNand"Uׇa;)']6=;'÷7:i( G@1ྨ8EQI\nCD0;/3ܑ; J`U.t>(Is}W^2UL_G_6*_CGhLF';ٜynVq5c9N wxv!7VV; ܄aI>*y|In6 XۻT3gq9nOӻ.jJCw !9xp䍩RYq/TX z۸[Bw '+ǂztqۛnۜsgV3& %[BCH€aID8gp);{UcnpՍC>qØ;q*ƅOՏOqiʹZRsJP GqxvB"m%9^<UozWn[V!ҒVA6Fl-DsDh\}P ); 0e'opG^~B44qۛnۜsgV3qm*pekukðJ⮛W ĞcnpՍC>qØ;樤t7q!ТQHڝ&r;QD_=Ǟ5q!зȄI@NW:9A^ 7Iݷ9w;3-?&g)ݾL4J·ޑ“ pϪ Rv 1 wb*}L31w2;PU# 9<;mW+[^BVtڼ>V$z۸[Bw '+ǂze5E%sŽpx8wrF))Vй0Rw$\ axNU2;QD۰M ~ᢶ10fsYՌ;4v*L[X*)w:+w]] wb*}L31wU_ImZp'JJRYhsww)QĽ#/aSc&xvB"m%9^<U{4V&#vp<+8v0\*:zF N$9> /I*7swˆm݈38ǎ7'i9qhTX bq,2޷ҡK N2U};a#jNbyRvq5%H$t#J&͗%s 10zsӣ7 e# RiK}CqM.S%IiԱP PV02:ºSU߻1b Gi)_hFyk)V~&>V[^^6h=Ucޠ_7G7N߇'zAc()JM=4JRR?E=?MXW,9kh.Len%@ iNp OZ69܈S݄xAPGJԴᏼ>:QSͰ2e$-)QRPRpRz#TPO4f+6(;!ғ(2+cRb:I:uwZRCqˌCS 4H|u+B|TzFroyZgT*&_=i?~벑Cާͬ AnGnyGώԇ[j|u~?5JnXlMI&FqimRT6JR;AP@W*ټkYQ3-jg-I'(#q9 cJpmjDu3?hͻܵPc&Kܭ%JV2N'_<]: >G(%@DJ|[ř_"=Ŏa174NvSiHRCxW^.vޏmVťʡc2HN䔕)ҤTȎ\#ԗ0U4@֢""`4N2HLQנBxar{UB_ZynmhBUЃVUϵwMe<ګw͌0i,/;\iWWL?c(}T|]>.ZR|]>.ZR|]>.ZR|]>.ZR|]>.ZR|]>.ZR|]>.ZR|]>.ZR|]>.ZR|]>.ZR|]>.ZR|])YJǎU?p~M5 T*y{C-qo;|ϋ֦>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d>.ZvoM)d KW|h3SmǸŸ\q=GQ*/L-ӌm,R|RsZҫ(Fk,g5J蠻Zd] F6ubAuNq緯LV}d3mreKQ9R|$)Q0EaJwԵ]xO~*xymon-4.3.7/docs/manpages/0000775000175000017500000000000011671641715014757 5ustar henrikhenrikxymon-4.3.7/docs/manpages/man5/0000775000175000017500000000000011671641715015617 5ustar henrikhenrikxymon-4.3.7/docs/manpages/man5/xymonwebaccess.5.html0000664000175000017500000000627111671641417021707 0ustar henrikhenrik Man page of XYMON-WEBACCESS

XYMON-WEBACCESS

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymon-webaccess - Web-based access controls in Xymon

 

DESCRIPTION

Xymon does not provide any built-in authentication (login) mechanism. Instead, it relies on the access controls available in your web server, e.g. the Apache mod_auth modules.

This provides a simple way of controlling access to the physical directories that make up the pages and subpages with the hosts defined in your Xymon hosts.cfg(5) setup - you can use the Apache "require" setting to allow or deny access to information on any page, usually through the use of a "Require group ..." setting. The group name then refers to one or more groups in an Apache AuthGroupFile file.

However, this does not work for the Xymon CGI programs since they are used to fetch information about all hosts in Xymon, but there is only a single directory holding all of the CGI's. So here you can only require that the user is logged-in (the Apache "Require valid-user" directive). A user with a login can - if he knows the hostname - manipulate the request sent to the webserver and fetch information about any status by use of the Xymon CGI programs, even though he cannot see the overview webpages.

To alleviate this situation, the following Xymon CGI's support a "--access=FILENAME" option, where FILENAME is an Apache compatible group-definitions file:
svcstatus.cgi(1)
acknowledge.cgi(1)
enadis.cgi(1)
appfeed.cgi(1)

When invoked with this option the CGI will read the Apache group-definitions file, and assume that an Apache group maps to a Xymon page, and then - based on the logged-in userid - determine which pages and hosts the user is allowed access to. Only information about those hosts will be made available by the CGI tool.

Members of the group root has access to all hosts.

Access will also be granted, if the user is a member of a group with the same name as the host being requested, or as the statuscolumn being requested.

 

SEE ALSO

The Apache "Authentication, Authorization and Access Control" documentation, http://httpd.apache.org/docs/2.2/howto/auth.html


 

Index

NAME
DESCRIPTION
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/protocols.cfg.5.html0000664000175000017500000001010511671641417021426 0ustar henrikhenrik Man page of PROTOCOLS.CFG

PROTOCOLS.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

protocols.cfg - Configuration of TCP network services

 

SYNOPSIS

$XYMONHOME/etc/protocols.cfg

 

DESCRIPTION

protocols.cfg contains definitions of how xymonnet(1) should test a TCP-based network service (i.e. all common network services except HTTP and DNS). For each service, a simple dialogue can be defined to check that the service is functioning normally, and optional flags determine if the service has e.g. a banner or requires SSL- or telnet-style handshaking to be tested.

 

FILE FORMAT

protocols.cfg is a text file. A simple service definition for the SMTP service would be this:


   [smtp]

      send "mail\r\nquit\r\n"

      expect "220"

      options banner

This defines a service called "smtp". When the connection is first established, xymonnet will send the string "mail\r\nquit\r\n" to the service. It will then expect a response beginning with "220". Any data returned by the service (a so-called "banner") will be recorded and included in the status message.

The full set of commands available for the protocols.cfg file are:

[NAME]
Define the name of the TCP service, which will also be the column-name in the resulting display on the test status. If multiple tests share a common definition (e.g. ssh, ssh1 and ssh2 are tested identically), you may list these in a single "[ssh|ssh1|ssh2]" definition, separating each service-name with a pipe-sign.

send STRING
expect STRING
Defines the strings to send to the service after a connection is established, and the response that is expected. Either of these may be omitted, in which case xymonnet(1) will simply not send any data, or match a response against anything.

The send- and expect-strings use standard escaping for non-printable characters. "\r" represents a carriage-return (ASCII 13), "\n" represents a line-feed (ASCII 10), "\t" represents a TAB (ASCII 8). Binary data is input as "\xNN" with NN being the hexadecimal value of the byte.

port NUMBER
Define the default TCP port-number for this service. If no portnumber is defined, xymonnet(1) will attempt to lookup the portnumber in the standard /etc/services file.

options option1[,option2][,option3]
Defines test options. The possible options are

   banner - include received data in the status message

   ssl - service uses SSL so perform an SSL handshake

   telnet - service is telnet, so exchange telnet options

 

FILES

$XYMONHOME/etc/protocols.cfg

 

SEE ALSO

xymonnet(1)


 

Index

NAME
SYNOPSIS
DESCRIPTION
FILE FORMAT
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/combo.cfg.5.html0000664000175000017500000001314111671641417020504 0ustar henrikhenrik Man page of COMBO.CFG

COMBO.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

combo.cfg - Configuration of combostatus tool

 

SYNOPSIS

$XYMONHOME/etc/combo.cfg

 

DESCRIPTION

combostatus(1) uses it's own configuration file, $XYMONHOME/etc/combo.cfg Each line in this file defines a combined test.

 

FILE FORMAT

Each line of the file defines a new combined test. Blank lines and lines starting with a hash mark (#) are treated as comments and ignored.

The configuration file uses the hostnames and testnames that are already used in your Xymon hosts.cfg file. These are then combined using normal logical operators - "||" for "or", "&&" for "and" etc.

A simple test - e.g. "Web1.http" - results in the value "1" if the "http" test for server "Web1" is green, yellow or clear. It yields the value "0" if it is red, purple or blue.

Apart from the logical operations, you can also do integer arithmetic and comparisons. E.g. the following is valid:

WebCluster.http = (Web1.http + Web2.http + Web3.http) >= 2

This test is green if two or more of the http tests for Web1, Web2 and Web3 are green.

The full range of operators are:


        +      Add
        -      Subtract
        *      Multiply
        /      Divide
        %      Modulo
        |      Bit-wise "or"
        &      Bit-wise "and"
        ||     Logical "or"
        &&     Logical "and"
        >      Greater than
        <      Less than
        >=     Greater than or equal
        <=     Less than or equal
        ==     Equal

There is currently no support for a "not" operator. If you need it, use the transcription "(host.test == 0)" instead of "!host.test".

NB: All operators have EQUAL PRECEDENCE. If you need something evaluated in a specific order, use parentheses to group the expressions together.

If the expression comes out as "0", the combined test goes red. If it comes out as non-zero, the combined test is green.

Note: If the expression involves hostnames with a character that is also an operator - e.g. if you have a host "t1-router-newyork.foo.com" with a dash in the hostname - then the operator-character must be escaped with a backslash '\' in the expression, or it will be interpreted as an operator. E.g. like this:


 nyc.conn = (t1\-router\-nyc.conn || backup\-router\-nyc.conn)

 

EXAMPLE

WebCluster.http = (Web1.http || Web2.http)
AppSrvCluster.procs = (AppSrv1.conn && AppSrv1.procs) || (AppSrv2.conn && AppSrv2.procs)
Customer.cluster = WebCluster.http && AppSrvCluster.procs

The first line defines a new test, with hostname "WebCluster" and the columnname "http". It will be green if the http test on either the "Web1" or the "Web2" server is green.

The second line defines a "procs" test for the "AppSrvCluster" host. Each of the AppSrv1 and AppSrv2 hosts is checked for "conn" (ping) and their "procs" test. On each host, both of these must be green, but the combined test is green if that condition is fulfilled on just one of the hosts.

The third line uses the two first tests to build a "double combined" test, defining a test that shows the overall health of the system.

 

FILES

$XYMONHOME/etc/combo.cfg

 

SEE ALSO

combostatus(1)


 

Index

NAME
SYNOPSIS
DESCRIPTION
FILE FORMAT
EXAMPLE
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/cgioptions.cfg.5.html0000664000175000017500000001030111671641417021556 0ustar henrikhenrik Man page of CGIOPTIONS.CFG

CGIOPTIONS.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

cgioptions.cfg - Command-line parameters for the Xymon CGI tools

 

SYNOPSIS

$XYMONHOME/etc/cgioptions.cfg

 

DESCRIPTION

cgioptions.cfg(1) controls the command-line options passed to all of the Xymon CGI tools through their respective shell-script wrappers. Typically the options listed here are used for system-wide configuration of the CGI utilities, e.g. to define where they read configuration files.

The exact set of command-line options available are described in the man-page for each of the CGI utilities.

The file is "sourced" into the shell script wrapper, so assignments to the CGI-specific variables must follow standard shell-script syntax.

 

SETTINGS

CGI_ACKINFO_OPTS
Options for the ackinfo.cgi(1) utility.

CGI_ACK_OPTS
Options for the acknowledge.cgi(1) utility.

CGI_CSVINFO_OPTS
Options for the csvinfo.cgi(1) utility.

CGI_DATEPAGE_OPTS
Options for the datepage.cgi(1) utility.

CGI_ENADIS_OPTS
Options for the enadis.cgi(8) utility.

CGI_EVENTLOG_OPTS
Options for the eventlog.cgi(1) utility.

CGI_FINDHOST_OPTS
Options for the findhost.cgi(1) utility.

CGI_HIST_OPTS
Options for the history.cgi(1) utility.

CGI_COLUMNDOC_OPTS
Xymon-specific options for column documentation. This uses the csvinfo.cgi(1) utility with the server/etc/columndoc.cfg configuration file.

CGI_CONFREPORT_OPTS
Options for the confreport.cgi(1) utility.

CGI_SHOWGRAPH_OPTS
Options for the showgraph.cgi(1) utility.

CGI_HOSTGRAPHS_OPTS
Options for the hostgraphs.cgi(1) utility.

CGI_CRITEDIT_OPTS
Options for the criticaleditor.cgi(1) utility.

CGI_CRITVIEW_OPTS
Options for the criticalview.cgi(1) utility.

CGI_REPLOG_OPTS
Options for the reportlog.cgi(1) utility.

CGI_REP_OPTS
Options for the report.cgi(1) utility.

CGI_SNAPSHOT_OPTS
Options for the snapshot.cgi(1) utility.

CGI_SVCHIST_OPTS
Options for the svcstatus.cgi(1) utility when used to view historical logs. Note that the "--historical" option must be included in this setting.

CGI_SVC_OPTS
Options for the svcstatus.cgi(1) utility.

 

SEE ALSO

xymon(7), the individual CGI utility man-pages.


 

Index

NAME
SYNOPSIS
DESCRIPTION
SETTINGS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/tasks.cfg.5.html0000664000175000017500000001637211671641417020543 0ustar henrikhenrik Man page of TASKS.CFG

TASKS.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

tasks.cfg - Task definitions for the xymonlaunch utility

 

SYNOPSIS

~xymon/server/etc/tasks.cfg

 

DESCRIPTION

The tasks.cfg file holds the list of tasks that xymonlaunch runs to perform all of the tasks needed by the Xymon monitor.

 

FILE FORMAT

A task is defined by a key, a command, and optionally also interval, environment, and logfile.

Blank lines and lines starting with a hash mark (#) are treated as comments and ignored. Long lines can be broken up by putting a backslash at the end of the line and continuing the entry on the next line.

An entry looks like this:


    [xymond]

          ENVFILE /usr/local/xymon/server/etc/xymonserver.cfg

          CMD /usr/local/xymon/server/bin/xymond


    [updateweb]

          ENVFILE /usr/local/xymon/server/etc/xymonserver.cfg

          CMD /usr/local/xymon/server/bin/xymongen

          NEEDS xymond

          GROUP webupdates

          INTERVAL 5m

          ONHOST localhost

          MAXTIME 10m

          LOGFILE /var/log/xymon/updateweb.log


    [monthlyreport]

          ENVFILE /usr/local/xymon/server/etc/xymonserver.cfg

          CMD /usr/local/xymon/server/ext/monthlyreport.sh

          CRONDATE 30 4 1 * *

The key is enclosed in angle brackets, and must be unique for each task. You can choose your key-names as you like, they are only used internally in xymonlaunch to identify each task.

The command is defined by the CMD keyword. This is the full command including any options you want to use for this task. This is required for all tasks.

The DISABLED keyword means that this command is disabled. xymonlaunch will not start this task. It is recommended that you use this to disable standard tasks, instead of removing them or commenting them out. Upgrades to Xymon will add standard tasks back into the file, so unless you have them listed as DISABLED then tasks may re-appear unexpectedly after an upgrade. There is also a corresponding ENABLED keyword, to explicitly enable a task.

The ONHOST keyword tells xymonlaunch that this task should only run on specific hosts. After the ONHOST keyword, you must provide a "regular expression"; if the hostname where xymonlaunch runs matches this expression, then the task will run. If it doesn't match, then the task is treated as if it were DISABLED.

The MAXTIME keyword sets a maximum time that the task may run; if exceeded, xymonlaunch will kill the task. The time is in seconds by default, you can specify minutes, hours or days by adding an "m", "h" or "d" after the number. By default there is no upper limit on how long a taskmay run.

The NEEDS instructs xymonlaunch not to run this task unless the task defined by the NEEDS keyword is already running. This is used e.g. to delay the start of some application until the needed daemons have been started. The task that must be running is defined by its key.

The GROUP keyword can be used to limit the number of tasks that may run simultaneously. E.g. if you are generating multiple pagesets of webpages, you dont want them to run at the same time. Putting them into a GROUP will cause xymonlaunch to delay the start of new tasks, so that only one task will run per group. You can change the limit by defining the group before the tasks, with a "GROUP groupname maxtasks" line.

The INTERVAL keyword defines how often this command is executed. The example shows a command that runs every 5 minutes. If no interval is given, the task is only run once - this is useful for tasks that run continually as daemons - although if the task stops for some reason, then xymonlaunch will attempt to restart it. Intervals can be specified in seconds (if you just put a number there), or in minutes (5m), hours (2h), or days (1d).

The CRONDATE keyword is used for tasks that must run at regular intervals or at a specific time. The time specification is identical to the one used by cron in crontab(5) entries, i.e. a sequence of numbers for minute, hour, day-of-month, month and day-of-week. Three-letter abbreviations in english can be used for the month and day-of-week fields. An asterisk is a wildcard. So in the example above, this job would run once a month, at 4:30 AM on the 1st day of the month.

The ENVFILE setting points to a file with definitions of environment variables. Before running the task, xymonlaunch will setup all of the environment variables listed in this file. Since this is a per-task setting, you can use the same xymonlaunch instance to run e.g. both the server- and client-side Xymon tasks. If this option is not present, then the environment defined to xymonlaunch is used.

The ENVAREA setting modifies which environment variables are loaded, by picking up the ones that are defined for this specific "area". See xymonserver.cfg(5) for information about environment areas.

The LOGFILE setting defines a logfile for the task. xymonlaunch will start the task with stdout and stderr redirected to this file. If this option is not present, then the output goes to the same location as the xymonlaunch output.

 

SEE ALSO

xymonlaunch(8), xymond(8), crontab(5), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
FILE FORMAT
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/alerts.cfg.5.html0000664000175000017500000002352211671641417020703 0ustar henrikhenrik Man page of ALERTS.CFG

ALERTS.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

alerts.cfg - Configuration for for xymond_alert module

 

SYNOPSIS

~xymon/server/etc/alerts.cfg

 

DESCRIPTION

The alerts.cfg file controls the sending of alerts by Xymon when monitoring detects a failure.

 

FILE FORMAT

The configuration file consists of rules, that may have one or more recipients associated. A recipient specification may include additional rules that limit the circumstances when this recipient is eligible for receiving an alert.

Blank lines and lines starting with a hash mark (#) are treated as comments and ignored. Long lines can be broken up by putting a backslash at the end of the line and continuing the entry on the next line.

 

RULES

A rule consists of one of more filters using these keywords:

PAGE=targetstring Rule matching an alert by the name of the page in Xymon. This is the path of the page as defined in the hosts.cfg file. E.g. if you have this setup:

page servers All Servers
subpage web Webservers
10.0.0.1 www1.foo.com
subpage db Database servers
10.0.0.2 db1.foo.com

Then the "All servers" page is found with PAGE=servers, the "Webservers" page is PAGE=servers/web and the "Database servers" page is PAGE=servers/db. Note that you can also use regular expressions to specify the page name, e.g. PAGE=%.*/db would find the "Database servers" page regardless of where this page was placed in the hierarchy.

The PAGE name of top-level page is an empty string. To match this, use PAGE=%^$ to match the empty string.

EXPAGE=targetstring Rule excluding an alert if the pagename matches.

DISPLAYGROUP=groupstring Rule matching an alert by the text of the display-group (text following the group, group-only, group-except heading) in the hosts.cfg file. "groupstring" is the text for the group, stripped of any HTML tags. E.g. if you have this setup:

group Web
10.0.0.1 www1.foo.com
10.0.0.2 www2.foo.com
group Production databases
10.0.1.1 db1.foo.com

Then the hosts in the Web-group can be matched with DISPLAYGROUP=Web, and the database servers can be matched with DISPLAYGROUP="Production databases". Note that you can also use regular expressions, e.g. DISPLAYGROUP=%database. If there is no group-setting for the host, use "DISPLAYGROUP=NONE".

EXDISPLAYGROUP=groupstring Rule excluding a group by matching the display-group string.

HOST=targetstring Rule matching an alert by the hostname.

EXHOST=targetstring Rule excluding an alert by matching the hostname.

SERVICE=targetstring Rule matching an alert by the service name.

EXSERVICE=targetstring Rule excluding an alert by matching the service name.

GROUP=groupname Rule matching an alert by the group name. Groupnames are assigned to a status via the GROUP setting in the analysis.cfg file.

EXGROUP=groupname Rule excluding an alert by the group name. Groupnames are assigned to a status via the GROUP setting in the analysis.cfg file.

COLOR=color[,color] Rule matching an alert by color. Can be "red", "yellow", or "purple". The forms "!red", "!yellow" and "!purple" can also be used to NOT send an alert if the color is the specified one.

TIME=timespecification Rule matching an alert by the time-of-day. This is specified as the DOWNTIME timespecification in the hosts.cfg file.

DURATION>time, DURATION<time Rule matcing an alert if the event has lasted longer/shorter than the given duration. E.g. DURATION>1h (lasted longer than 1 hour) or DURATION<30 (only sends alerts the first 30 minutes). The duration is specified as a number, optionally followed by 'm' (minutes, default), 'h' (hours) or 'd' (days).

RECOVERED Rule matches if the alert has recovered from an alert state.

NOTICE Rule matches if the message is a "notify" message. This type of message is sent when a host or test is disabled or enabled.

The "targetstring" is either a simple pagename, hostname or servicename, OR a '%' followed by a Perl-compatible regular expression. E.g. "HOST=%www(.*)" will match any hostname that begins with "www". The same for the "groupname" setting.

 

RECIPIENTS

The recipients are listed after the initial rule. The following keywords can be used to define recipients:

MAIL address[,address] Recipient who receives an e-mail alert. This takes one parameter, the e-mail address. The strings "&host&", "&service&" and "&color&" in an address will be replaced with the hostname, service and color of the alert, respectively.

SCRIPT /path/to/script recipientID Recipient that invokes a script. This takes two parameters: The script filename, and the recipient that gets passed to the script. The strings "&host&", "&service&" and "&color&" in the recipientID will be replaced with the hostname, service and color of the alert, respectively.

IGNORE This is used to define a recipient that does NOT trigger any alerts, and also terminates the search for more recipients. It is useful if you have a rule that handles most alerts, but there is just that one particular server where you dont want cpu alerts on Monday morning. Note that the IGNORE recipient always has the STOP flag defined, so when the IGNORE recipient is matched, no more recipients will be considered. So the location of this recipient in your set of recipients is important.

FORMAT=formatstring Format of the text message with the alert. Default is "TEXT" (suitable for e-mail alerts). "PLAIN" is the same as text, but without the URL link to the status webpage. "SMS" is a short message with no subject for SMS alerts. "SCRIPT" is a brief message template for scripts.

REPEAT=time How often an alert gets repeated. As with DURATION, time is a number optionally followed by 'm', 'h' or 'd'.

UNMATCHED The alert is sent to this recipient ONLY if no other recipients received an alert for this event.

STOP Stop looking for more recipients after this one matches. This is implicit on IGNORE recipients.

Rules You can specify rules for a recipient also. This limits the alerts sent to this particular recipient.

 

MACROS

It is possible to use macros in the configuration file. To define a macro:

       $MYMACRO=text extending to end of line

After the definition of a macro, it can be used throughout the file. Wherever the text $MYMACRO appears, it will be substituted with the text of the macro before any processing of rules and recipients.

It is possible to nest macros, as long as the macro is defined before it is used.

 

ALERT SCRIPTS

Alerts can go out via custom scripts, by using the SCRIPT keyword for a recipient. Such scritps have access to the following environment variables:

BBALPHAMSG The full text of the status log triggering the alert

ACKCODE The "cookie" that can be used to acknowledge the alert

RCPT The recipientID from the SCRIPT entry

BBHOSTNAME The name of the host that the alert is about

MACHIP The IP-address of the host that has a problem

BBSVCNAME The name of the service that the alert is about

BBSVCNUM The numeric code for the service. From the SVCCODES definition.

BBHOSTSVC HOSTNAME.SERVICE that the alert is about.

BBHOSTSVCCOMMAS As BBHOSTSVC, but dots in the hostname replaced with commas

BBNUMERIC A 22-digit number made by BBSVCNUM, MACHIP and ACKCODE.

RECOVERED Is "0" if the service is alerting, "1" if the service has recovered, "2" if the service was disabled.

EVENTSTART Timestamp when the current status (color) began.

SECS Number of seconds the service has been down.

DOWNSECSMSG When recovered, holds the text "Event duration : N" where N is the DOWNSECS value.

CFID Line-number in the alerts.cfg file that caused the script to be invoked. Can be useful when troubleshooting alert configuration rules.

 

SEE ALSO

xymond_alert(8), xymond(8), xymon(7), the "Configuring Xymon Alerts" guide in the Online documentation.


 

Index

NAME
SYNOPSIS
DESCRIPTION
FILE FORMAT
RULES
RECIPIENTS
MACROS
ALERT SCRIPTS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/hosts.cfg.5.html0000664000175000017500000020500111671641417020543 0ustar henrikhenrik Man page of HOSTS.CFG

HOSTS.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

hosts.cfg - Main Xymon configuration file

 

SYNOPSIS

hosts.cfg

 

DESCRIPTION

The hosts.cfg(5) file is the most important configuration file for all of the Xymon programs. This file contains the full list of all the systems monitored by Xymon, including the set of tests and other configuration items stored for each host.

 

FILE FORMAT

Each line of the file defines a host. Blank lines and lines starting with a hash mark (#) are treated as comments and ignored. Long lines can be broken up by putting a backslash at the end of the line and continuing the entry on the next line.

The format of an entry in the hosts.cfg file is as follows:

   IP-address hostname # tag1 tag2 ...

The IP-address and hostname are mandatory; all of the tags are optional. Listing a host with only IP-address and hostname will cause a network test to be executed for the host - the connectivity test is enabled by default, but no other tests.

The optional tags are then used to define which tests are relevant for the host, and also to set e.g. the time-interval used for availability reporting by xymongen(1)

An example of setting up the hosts.cfg file is in the Xymon on-line documentation (from the Help menu, choose "Configuring Monitoring"). The following describes the possible settings in a hosts.cfg file supported by Xymon.

 

TAGS RECOGNIZED BY ALL TOOLS

include filename
This tag is used to include another file into the hosts.cfg file at run-time, allowing for a large hosts.cfg file to be split up into more manageable pieces.

The "filename" argument should point to a file that uses the same syntax as hosts.cfg. The filename can be an absolute filename (if it begins with a '/'), or a relative filename - relative file names are prefixed with the directory where the main hosts.cfg file is located (usually $XYMONHOME/etc/).

You can nest include tags, i.e. a file that is included from the main hosts.cfg file can itself include other files.

dispinclude filename
Acts like the "include" tag, but only for the xymongen tool. Can be used e.g. to put a group of hosts on multiple sub-pages, without having to repeat the host definitions.

netinclude filename
Acts like the "include" tag, but only for the xymonnet tool.

directory directoryname
This tag is used to include all files in the named directory. Files are included in alphabetical order. If there are sub- directories, these are recursively included also. The following files are ignored: Files that begin with a dot, files that end with a tilde, RCS files that end with ",v", RPM package manager files ending in ".rpmsave" or ".rpmnew", DPKG package manager files ending in ".dpkg-new" or ".dpkg-orig", and all special files (devices, sockets, pipes etc).

 

GENERAL PER-HOST OPTIONS

noclear
Controls whether stale status messages go purple or clear when a host is down. Normally, when a host is down the client statuses ("cpu", "disk", "memory" etc) will stop updating - this would usually make them go "purple" which can trigger alerts. To avoid that, Xymon checks if the "conn" test has failed, and if that is true then the other tests will go "clear" instead of purple so you only get alerts for the "conn" test. If you do want the stale statuses to go purple, you can use the "noclear" tag to override this behaviour.

Note that "noclear" also affects the behaviour of network tests; see below.

prefer
When a single host is defined multiple time in the hosts.cfg file, xymongen tries to guess which definition is the best to use for the information used on the "info" column, or for the NOPROPRED and other xymongen-specific settings. Host definitions that have a "noconn" tag or an IP of 0.0.0.0 get lower priority.

By using the "prefer" tag you tell xymongen that this host definition should be used.

Note: This only applies to hosts that are defined multiple times in the hosts.cfg file, although it will not hurt to add it on other hosts as well.

multihomed
Tell Xymon that data from the host can arrive from multiple IP-adresses. By default, Xymon will warn if it sees data for one host coming from different IP-adresses, because this usually indicates a mis-configuration of the hostname on at least one of the servers involved. Some hosts with multiple IP-adresses may use different IP's for sending data to Xymon, however. This tag disables the check of source IP when receiving data.

delayred=STATUSCOLUMN:DELAY[,STATUSCOLUMN:DELAY...]
Usually, status changes happen immediately. This tag is used to defer an update to red for the STATUSCOLUMN status for DELAY minutes. E.g. with delayred=disk:10,cpu:30, a red disk-status will not appear on the Xymon webpages until it has been red for at least 10 minutes. Note: Since most tests only execute once every 5 minutes, it will usually not make sense to set N to anything but a multiple of 5. The exception is network tests, since xymonnet-again.sh(1) will re-run failed network tests once a minute for up to 30 minutes.

delayyellow=STATUSCOLUMN:DELAY[,STATUSCOLUMN:DELAY...]
Same as delayred, but defers the change to a yellow status.

 

XYMONGEN DISPLAY OPTIONS

These tags are processed by the xymongen(1) tool when generating the Xymon webpages or reports.

page NAME [Page-title]
This defines a page at the level below the entry page. All hosts following the "page" directive appear on this page, until a new "page", "subpage" or "subparent" line is found.

subpage NAME [Page-title]
This defines a sub-page in the second level below the entry page. You must have a previous "page" line to hook this sub-page to.

subparent parentpage newpage [Page-title]
This is used to define sub-pages in whatever levels you may wish. Just like the standard "subpage" tag, "subparent" defines a new Xymon web page; however with "subparent" you explicitly list which page it should go as a sub-page to. You can pick any page as the parent - pages, sub-pages or even other subparent pages. So this allows you to define any tree structure of pages that you like.

E.g. with this in hosts.cfg:


   page USA United States
   subpage NY New York
   subparent NY manhattan Manhattan data centers
   subparent manhattan wallstreet Wall Street center

you get this hierarchy of pages:


   USA (United States)
     NY (New York)
       manhattan (Manhattan data centers)
          wallstreet (Wall Street center)

Note: The parent page must be defined before you define the subparent. If not, the page will not be generated, and you get a message in the log file.

Note: xymongen is case-sensitive, when trying to match the name of the parent page.

The inspiration for this came from Craig Cook's mkbb.pl script, and I am grateful to Craig for suggesting that I implement it in xymongen. The idea to explicitly list the parent page in the "subparent" tag was what made it easy to implement.

vpage
These are page-definitions similar to the "page", "subpage" and "subparent" definitions. However, on these pages the rows are the tests, and the columns are the hosts (normal pages have it the other way around). This is useful if you have a very large number of tests for a few hosts, and prefer to have them listed on a page that can be scrolled vertically.
Note that the "group" directives have no effect on these types of pages.

group [group-title]
group-compress [group-title]
Defines a group of hosts, that appear together on the web page, with a single header-line listing all of the columns. Hosts following the "group" line appear inside the group, until a new "group" or page-line is found. The two group-directives are handled identically by Xymon and xymongen, but both forms are allowed for backwards compatibility.

group-sorted [group-title]
Same as the "group" line, but will sort the hosts inside the group so they appear in strict lexicographic order.

group-only COLUMN1|COLUMN2|COLUMN3 [group-title]
Same as the "group" and "group-compress" lines, but includes only the columns explicitly listed in the group. Any columns not listed will be ignored for these hosts.

group-except COLUMN1|COLUMN2|COLUMN3 [group-title]
Same as the "group-only" lines, but includes all columns EXCEPT those explicitly listed in the group. Any columns listed will be ignored for these hosts - all other columns are shown.

title Page, group or host title text
The "title" tag is used to put custom headings into the pages generated by xymongen, in front of page/subpage links, groups or hosts.

The title tag operates on the next item in the hosts.cfg file following the title tag.

If a title tag precedes a host entry, the title is shown just before the host is listed on the status page. The column headings present for the host will be repeated just after the heading.

If a title tag precedes a group entry, the title is show just before the group on the status page.

If a title tag precedes a page/subpage/subparent entry, the title text replaces the normal "Pages hosted locally" heading normally inserted by Xymon. This appears on the page that links to the sub-pages, not on the sub-page itself. To get a custom heading on the sub-page, you may want to use the "--pagetext-heading" when running xymongen(1)

NAME:hostname
Overrides the default hostname used on the overview web pages. If "hostname" contains spaces, it must be enclosed in double quotes, e.g. NAME:"R&D Oracle Server"

CLIENT:hostname
Defines an alias for a host, which will be used when identifying status messages. This is typically used to accommodate a local client that sends in status reports with a different hostname, e.g. if you use hostnames with domains in your Xymon configuration, but the client is a silly Window box that does not include the hostname. Or vice-versa. Whatever the reason, this can be used to match status reports with the hosts you define in your hosts.cfg file. It causes incoming status reports with the specified hostname to be filed using the hostname defined in hosts.cfg.

NOCOLUMNS:column[,column]
Used to drop certain of the status columns generated by the Xymon client. column is one of cpu, disk, files, memory, msgs, ports, procs. This setting stops these columns from being updated for the host. Note: If the columns already exist, you must use the xymon(1) utility to drop them, or they will go purple.

COMMENT:Host comment
Adds a small text after the hostname on the web page. This can be used to describe the host, without completely changing its display-name as the NAME: tag does. If the comment includes whitespace, it must be in double-quotes, e.g. COMMENT:"Sun web server"

DESCR:Hosttype:Description
Define some informational text about the host. The "Hosttype" is a text describing the type of this device - "router", "switch", "hub", "server" etc. The "Description" is an informational text that will be shown on the "Info" column page; this can e.g. be used to store information about the physical location of the device, contact persons etc. If the text contain whitespace, you must enclose it in double-quotes, e.g. DESCR:"switch:4th floor Marketing switch"

CLASS:Classname
Force the host to belong to a specific class. Class-names are used when configuring log-file monitoring (they can be used as references in client-local.cfg(5) and analysis.cfg(5) to group log file checks). Normally, class-names are controlled on the client by starting the Xymon client with the "--class=Classname" option. If you specify it in the hosts.cfg file on the Xymon server, it overrides any class name that the client reports.

dialup
The keyword "dialup" for a host means that it is OK for it to be off-line - this should not trigger an alert. All network tests will go "clear" upon failure, and any missing reports from e.g. cpu- and disk-status will not go purple when they are not updated.

nonongreen
Ignore this host on the "All non-green" page. Even if it has an active alert, it will not be included in the "All non-green" page. This also removes the host from the event-log display.

nodisp
Ignore this host completely when generating the Xymon webpages. Can be useful for monitoring a host without having it show up on the webpages, e.g. because it is not yet in production use. Or for hiding a host that is shown only on a second pageset.

TRENDS:[*,][![graph,...]]
Defines the RRD graphs to include in the "trends" column generated by xymongen. This option syntax is complex.
If this option is not present, xymongen provides graphs matching the standard set of RRD files: la, disk, memory, users, vmstat, iostat, netstat, tcp, bind, apache, sendmail
* If this option is specified, the list of graphs to include start out as being empty (no graphs).
* To include all default graphs, use an asterisk. E.g. "TRENDS:*"
* To exclude a certain graph, specify it prefixed with '!'. E.g. to see all graphs except users: "TRENDS:*,!users"
* The netstat, vmstat and tcp graphs have many "subgraphs". Which of these are shown can be specified like this: "TRENDS:*,netstat:netstat2|netstat3,tcp:http|smtp|conn" This will show all graphs, but instead of the normal netstat graph, there will be two: The netstat2 and netstat3 graphs. Instead of the combined tcp graphs showing all services, there will be three: One for each of the http, conn and smtp services.
COMPACT:COLUMN=COLUMN1,COLUMN2,COLUMN3[;ditto]
Collapses a series of statuses into a single column on the overview web page.
INTERFACES:REGEXP
On systems with multiple network interfaces, the operating system may report a number of network interface where the statistics are of no interest. By default Xymon tracks and graphs the traffic on all network interfaces. This option defines a regular expression, and only those interfaces whose name matches the expression are tracked.

 

XYMON TAGS FOR THE CRITICAL SYSTEMS OVERVIEW PAGE

NOTE: The "NK" set of tags is deprecated. They will be supported for Xymon 4.x, but will be dropped in version 5. It is recommended that you move your critical systems view to the criticalview.cgi(1) viewer, which has a separate configuration tool, criticaleditor.cgi(1) with more facilities than the NK tags in hosts.cfg.

xymongen will create three sets of pages: The main page xymon.html, the all-non-green-statuses page (nongreen.html), and a specially reduced version of nongreen.html with only selected tests (critical.html). This page includes selected tests that currently have a red or yellow status.

NK:testname[,testname]
NOTE: This has been deprecated, you should use criticalview.cgi(1) instead of the NK tag.

Define the tests that you want included on the critical page. E.g. if you have a host where you only want to see the http tests on critical.html, you specify it as


  12.34.56.78  www.acme.com  # http://www.acme.com/ NK:http

If you want multiple tests for a host to show up on the critical.html page, specify all the tests separated by commas. The test names correspond to the column names (e.g. https tests are covered by an "NK:http" tag).

NKTIME=day:starttime:endtime[,day:starttime:endtime]
This tag limits the time when an active alert is presented on the NK web page.

By default, tests with a red or yellow status that are listed in the "NK:testname" tag will appear on the NK page. However, you may not want the test to be shown outside of normal working hours - if, for example, the host is not being serviced during week-ends.

You can then use the NKTIME tag to define the time periods where the alert will show up on the NK page.

The time specification consists of

day-of-week: W means Mon-Fri ("weekdays"), * means all days, 0 .. 6 = Sunday .. Saturday. Listing multiple days is possible, e.g. "60" is valid meaning "Saturday and Sunday".

starttime: Time to start showing errors, must be in 24-hour clock format as HHMM hours/minutes. E.g. for 8 am enter "0800", for 9.30 pm enter "2130"

endtime: Time to stop showing errors.

If necessary, multiple periods can be specified. E.g. to monitor a site 24x7, except between noon and 1 pm, use NKTIME=*:0000:1159,*:1300:2359

The interval between start time and end time may cross midnight, e.g. *:2330:0200 would be valid and have the same effect as *:2330:2400,*:0000:0200.

 

XYMON TAGS FOR THE WML (WAP) CARDS

If xymongen is run with the "--wml" option, it will generate a set of WAP-format output "cards" that can be viewed with a WAP-capable device, e.g. a PDA or cell-phone.

WML:[+|-]testname[,[+|-]testname]
This tag determines which tests for this hosts are included in the WML (WAP) page. Syntax is identical to the NK: tag.

The default set of WML tests are taken from the --wml command line option. If no "WML:" tag is specified, the "NK:" tag is used if present.

 

XYMON STATUS PROPAGATION OPTIONS

These tags affect how a status propagates upwards from a single test to the page and higher. This can also be done with the command-line options --nopropyellow and --nopropred, but the tags apply to individual hosts, whereas the command line options are global.

NOPROPRED:[+|-]testname[,[+|-]testname]
This tag is used to inhibit a yellow or red status from propagating upwards - i.e. from a test status color to the (sub)page status color, and further on to xymon.html or nongreen.html

If a host-specific tag begins with a '-' or a '+', the host-specific tags are removed/added to the default setting from the command-line option. If the host-specific tag does not begin with a '+' or a '-', the default setting is ignored for this host and the NOPROPRED applies to the tests given with this tag.

E.g.: xymongen runs with "--nopropred=ftp,smtp". "NOPROPRED:+dns,-smtp" gives a NOPROPRED setting of "ftp,dns" (dns is added to the default, smtp is removed). "NOPROPRED:dns" gives a setting of "dns" only (the default is ignored).

Note: If you set use the "--nopropred=*" command line option to disable propagation of all alerts, you cannot use the "+" and "-" methods to add or remove from the wildcard setting. In that case, do not use the "+" or "-" setting, but simply list the required tests that you want to keep from propagating.

NOPROPYELLOW:[+|-]testname[,[+|-]testname]
Similar to NOPROPRED: tag, but applies to propagating a yellow status upwards.

NOPROPPURPLE:[+|-]testname[,[+|-]testname]
Similar to NOPROPRED: tag, but applies to propagating a purple status upwards.

NOPROPACK:[+|-]testname[,[+|-]testname]
Similar to NOPROPRED: tag, but applies to propagating an acknowledged status upwards.

 

XYMON AVAILABILITY REPORT OPTIONS

These options affect the way the Xymon availability reports are processed (see report.cgi(1) for details about availability reports).

REPORTTIME=day:starttime:endtime[,day:starttime:endtime]
This tag defines the time interval where you measure uptime of a service for reporting purposes.

When xymongen generates a report, it computes the availability of each service - i.e. the percentage of time that the service is reported as available (meaning: not red).

By default, this calculation is done on a 24x7 basis, so no matter when an outage occurs, it counts as downtime.

The REPORTTIME tag allows you to specify a period of time other than 24x7 for the service availability calculation. If you have systems where you only guarantee availability from e.g. 7 AM to 8 PM on weekdays, you can use

  REPORTTIME=W:0700:2000
and the availability calculation will only be performed for the service with measurements from this time interval.

The syntax for REPORTTIME is the same as the one used by the NKTIME parameter.

When REPORTTIME is specified, the availability calculation happens like this:

* Only measurements done during the given time period is used for the calculation.
* "blue" time reduces the length of the report interval, so if you are generating a report for a 10-hour period and there are 20 minutes of "blue" time, then the availability calculation will consider the reporting period to be 580 minutes (10 hours minus 20 minutes). This allows you to have scheduled downtime during the REPORTTIME interval without hurting your availability; this is (I believe) the whole idea of the downtime being "planned".
* "red" and "clear" status counts as downtime; "yellow" and "green" count as uptime. "purple" time is ignored.

The availability calculation correctly handles status changes that cross into/out of a REPORTTIME interval.

If no REPORTTIME is given, the standard 24x7 calculation is used.

WARNPCT:percentage
Xymon's reporting facility uses a computed availability threshold to color services green (100% available), yellow (above threshold, but less than 100%), or red (below threshold) in the reports.

This option allows you to set the threshold value on a host-by-host basis, instead of using a global setting for all hosts. The threshold is defined as the percentage of the time that the host must be available, e.g. "WARNPCT:98.5" if you want the threshold to be at 98.5%

 

NETWORK TEST SETTINGS

testip
By default, Xymon will perform a name lookup of the hostname to get the IP address it will use for network tests. This tag causes Xymon to use the IP listed in the hosts.cfg file.

NET:location
This tag defines the host as being tested from a specific location. If xymonnet sees that the environment variable XYMONNETWORK is set, it will only test the hosts that have a matching "NET:location" tag in the hosts.cfg file. So this tag is useful if you have more than one system running network tests, but you still want to keep a consolidated hosts.cfg file for all your systems.

Note: The "--test-untagged" option modifies this behaviour, see xymonnet(1)

noclear
Some network tests depend on others. E.g. if the host does not respond to ping, then there's a good chance that the entire host is down and all network tests will fail. Or if the http server is down, then any web content checks are also likely to fail. To avoid floods of alerts, the default behaviour is for xymonnet to change the status of these tests that fail because of another problem to "clear" instead of "red". The "noclear" tag disables this behaviour and causes all failing tests to be reported with their true color.

This behaviour can also be implemented on a per-test basis by putting the "~" flag on any network test.

Note that "noclear" also affects whether stale status messages from e.g. a client on the host go purple or clear when the host is down; see the "noclear" description in the "GENERAL PER-HOST OPTIONS" section above.

nosslcert
Disables the standard check of any SSL certificates for this host. By default, if an SSL-enabled service is tested, a second test result is generated with information about the SSL certificate - this tag disables the SSL certificate checks for the host.

ssldays=WARNDAYS:ALARMDAYS
Define the number of days before an SSL certificate expires, in which the sslcert status shows a warning (yellow) or alarm (red) status. These default to the values from the "--sslwarndays" and "--sslalarmdays" options for the xymonnet(1) tool; the values specified in the "ssldays" tag overrides the default.

sslbits=MINIMUMKEYBITS
Enable checking of the encryption strength of the SSL protocol offered by the server. If the server offers encryption using a key with fewer than MINIMUMKEYBITS bits, the "sslcert" test will go red. E.g. to check that your server only uses strong encryption (128 bits or better), use "sslbits=128".

DOWNTIME=day:starttime:endtime[,day:starttime:endtime]
DOWNTIME=columns:day:starttime:endtime:cause[,columns:day:starttime:endtime:cause]
This tag can be used to ignore failed checks during specific times of the day - e.g. if you run services that are only monitored e.g. Mon-Fri 8am-5pm, or you always reboot a server every Monday between 5 and 6 pm.

What happens is that if a test fails during the specified time, it is reported with status BLUE instead of yellow or red. Thus you can still see when the service was unavailable, but alarms will not be triggered and the downtime is not counted in the availability calculations generated by the Xymon reports.

The "columns" and "cause" settings are optional, but both or neither must be specified. "columns" may be a comma-separated list of status columns to which DOWNTIME will apply. The "cause" string will be displayed on the status web page to explain why the system is down.

The syntax for DOWNTIME is the same as the one used by the NKTIME parameter.

SLA=day:starttime:endtime[,day:starttime:endtime]
This tag is now deprecated. Use the DOWNTIME tag instead.

This tag works the opposite of the DOWNTIME tag - you use it to specify the periods of the day that the service should be green. Failures OUTSIDE the SLA interval are reported as blue.

depends=(testA:host1/test1,host2/test2),(testB:host3/test3),[...]
This tag allows you to define dependencies between tests. If "testA" for the current host depends on "test1" for host "host1" and test "test2" for "host2", this can be defined with


   depends=(testA:host1/test1,host2/test2)

When deciding the color to report for testA, if either host1/test1 failed or host2/test2 failed, if testA has failed also then the color of testA will be "clear" instead of red or yellow.

Since all tests are actually run before the dependencies are evaluated, you can use any host/test in the dependency - regardless of the actual sequence that the hosts are listed, or the tests run. It is also valid to use tests from the same host that the dependency is for. E.g.


   1.2.3.4  foo # http://foo/ webmin depends=(webmin:foo/http)

is valid; if both the http and the webmin tests fail, then webmin will be reported as clear.

Note: The "depends" tag is evaluated by xymonnet while running the network tests. It can therefore only refer to other network tests that are handled by the same server - there is currently no way to use the e.g. the status of locally run tests (disk, cpu, msgs) or network tests from other servers in a dependency definition. Such dependencies are silently ignored.

badTEST[-weekdays-starttime-endtime]:x:y:z
NOTE: This has been deprecated, use the delayred and delayyellow settings instead.

Normally when a network test fails, the status changes to red immediately. With a "badTEST:x:y:z" tag this behaviour changes:
* While "z" or more successive tests fail, the column goes RED.
* While "y" or more successive tests fail, but fewer than "z", the column goes YELLOW.
* While "x" or more successive tests fail, but fewer than "y", the column goes CLEAR.
* While fewer than "x" successive tests fail, the column stays GREEN.

The optional time specification can be used to limit this "badTEST" setting to a particular time of day, e.g. to require a longer period of downtime before raising an alarm during out-of-office hours. The time-specification uses:
* Weekdays: The weekdays this badTEST tag applies, from 0 (Sunday) through 6 (Saturday). Putting "W" here counts as "12345", i.e. all working days. Putting "*" here counts as all days of the week, equivalent to "0123456".
* start time and end time are specified using 24-hour clocks, e.g. "badTEST-W-0900-2000" is valid for working days between 9 AM (09:00) and 8 PM (20:00).

When using multiple badTEST tags, the LAST one specified with a matching time-spec is used.

Note: The "TEST" is replaced by the name of the test, e.g.


 12.34.56.78  www.foo.com  # http://www.foo.com/ badhttp:1:2:4

defines a http test that goes "clear" after the first failure, "yellow" after two successive failures, and "red" after four successive failures.

For LDAP tests using URL's, use the option "badldapurl". For the other network tests, use "badftp", "badssh" etc.

 

CONNECTIVITY (PING) TEST

These tags affect the behaviour of the xymonnet connectivity test.

noping
Disables the ping-test, but will keep the "conn" column on the web display with a notice that it has been disabled.

noconn
Disables the ping-test, and does not put a "conn" column on the web display.

conn
The "conn" test (which does a ping of the host) is enabled for all hosts by default, and normally you just want to disable it using "noconn" or "noping". However, on the rare occasion where you may want to check that a host is NOT up, you can specify it as an explicit test, and use the normal test modifiers, e.g. "!conn" will be green when the host is NOT up, and red if it does appear on the network.

The actual name of the tag - "conn" by default - depends on the "--ping=TESTNAME" option for xymonnet, as that decides the testname for the connectivity test.

conn={best,|worst,}IP1[,IP2...]
This adds additional IP-adresses that are pinged during the normal "conn" test. So the normal "conn" test must be enabled (the default) before this tag has any effect. The IP-adresses listed here are pinged in addition to the main IP-address.

When multiple IP's are pinged, you can choose if ALL IP's must respond (the "worst" method), or AT LEAST one IP must respond (the "best" setting). All of the IP's are reported in a single "conn" status, whose color is determined from the result of pinging the IP's and the best/worst setting. The default method is "best" - so it will report green if just one of the IP's respond to ping.

badconn[-weekdays-starttime-endtime]:x:y:z
This is taken directly from the "fping.sh" connectivity- testing script, and is used by xymonnet when it runs with ping testing enabled (the default). See the description of the "badTEST" tag.

route:router1,router2,....
This tag is taken from the "fping.sh" script, and is used by xymonnet when run with the "--ping" option to enable ping testing.

The router1,router2,... is a comma-separated list of hosts elsewhere in the hosts.cfg file. You cannot have any spaces in the list - separate hosts with commas.

This tag changes the color reported for a ping check that fails, when one or more of the hosts in the "route" list is also down. A "red" status becomes "yellow" - other colors are unchanged. The status message will include information about the hosts in the router-list that are down, to aid tracking down which router is the root cause of the problem.

Note: Internally, the ping test will still be handled as "failed", and therefore any other tests run for this host will report a status of "clear".

route_LOCATION:router1,router2,...
If the XYMONNETWORK environment variable is defined, a tag of "route_XYMONNETWORK:" is recognized by xymonnet with the same effect as the normal "route:" tag (see above). This allows you to have different route: tags for each server running xymonnet. The actual text for the tag then must match the value you have for the XYMONNETWORK setting. E.g. with XYMONNETWORK=dmz, the tag becomes "route_dmz:"

trace
If the connectivity test fails, run a "traceroute" and include the output from this in the status message from the failed connectivity test. Note: For this to work, you may have to define the TRACEROUTE environment variable, see xymonserver.cfg(5)

notrace
Similar to the "trace" option, this disables the running of a traceroute for the host after a failed connectivity test. It is only used if running traceroute is made the default via the --trace option.

 

SIMPLE NETWORK TESTS

These tests perform a simple network test of a service by connecting to the port and possibly checking that a banner is shown by the server.

How these tests operate are configured in the protocols.cfg(5) configuration file, which controls which port to use for the service, whether to send any data to the service, whether to check for a response from the service etc.

You can modify the behaviour of these tests on a per-test basis by adding one or more modifiers to the test: :NUMBER changes the port number from the default to the one you specify for this test. E.g. to test ssh running on port 8022, specify the test as ssh:8022.

:s makes the test silent, i.e. it does not send any data to the service. E.g. to do a silent test of an smtp server, enter smtp:s.

You can combine these two: ftp:8021:s is valid.

If you must test a service from a multi-homed host (i.e. using a specific source IP-address instead of the one your operating system provides), you can use the modifier "@IPADDRESS" at the end of the test specification, after any other modifiers or port number. "IPADDRESS" must be a valid dotted IP-address (not hostname) which is assigned to the host running the network tests.

The name of the test also determines the column name that the test result will appear with in the Xymon webpages.

By prefixing a test with "!" it becomes a reverse test: Xymon will expect the service NOT to be available, and send a green status if it does NOT respond. If a connection to the service succeeds, the status will go red.

By prefixing a test with "?" errors will be reported with a "clear" status instead of red. This is known as a test for a "dialup" service, and allows you to run tests of hosts that are not always online, without getting alarms while they are off-line.

ftp ssh telnet smtp pop3 imap nntp rsync clamd oratns qmtp qmqp
These tags are for testing services offering the FTP, Secure Shell (ssh), SMTP, POP3, IMAP, NNTP, rsync, CLAM anti-virus daemon (clamd), Oracle TNS listener (oratns), qmail QMTP and QMQP protocols.

ftps telnets smtps pop3s imaps nntps
These tags are for testing of the SSL-tunneled versions of the standard ftp, telnet, smtp, pop3, imap and nntp protocols. If Xymon was configured with support for SSL, you can test these services like any other network service - xymonnet will setup an SSL-encrypted session while testing the service. The server certificate is validated and information about it sent in the "sslcert" column. Note that smtps does not have a standard port number assignment, so you will need to enter this into the protocols.cfg file or your /etc/services file.

bbd
Test that a Big Brother compatible daemon is running. This check works both for the Xymon xymond(8) daemon, and the original Big Brother bbd daemon.

 

DNS SERVER TESTS

These tags are used to setup monitoring of DNS servers.

dns
Simple DNS test. It will attempt to lookup the A record for the hostname of the DNS server.

dig
This is an alias for the "dns" test. In xymonnet, the "dns" and "dig" tests are handled identically, so all of the facilities for testing described for the "dns" test are also available for the "dig" test.

dns=hostname
dns=TYPE:lookup[,TYPE:lookup...]
The default DNS tests will attempt a DNS lookup of the DNS' servers own hostname. You can specify the hostname to lookup on a DNS server by listing it on each test.

The second form of the test allows you to perform multiple queries of the DNS server, requesting different types of DNS records. The TYPE defines the type of DNS data: A (IP-address), MX (Mail eXchanger), PTR (reverse), CNAME (alias), SOA (Start-Of-Authority), NS (Name Server) are among the more common ones used. The "lookup" is the query. E.g. to lookup the MX records for the "foo.com" domain, you would use "dns=mx:foo.com". Or to lookup the nameservers for the "bar.org" domain, "dns=ns:bar.org". You can list multiple lookups, separated by commas. For the test to end up with a green status, all lookups must succeed.

 

OTHER NETWORK TESTS

ntp
Check for a running NTP (Network Time Protocol) server on this host. This test uses the "ntpdate" utility to check for a NTP server - you should either have ntpdate in your PATH, or set the location of the ntpdate program in $XYMONHOME/etc/xymonserver.cfg

rpc[=rpcservice1,rpcservice2,...]
Check for one or more available RPC services. This check is indirect in that it only queries the RPC Portmapper on the host, not the actual service.

If only "rpc" is given, the test only verifies that the port mapper is available on the remote host. If you want to check that one or more RPC services are registered with the port mapper, list the names of the desired RPC services after the equals-sign. E.g. for a working NFS server the "mount", "nlockmgr" and "nfs" services must be available; this can be checked with "rpc=mount,nlockmgr,nfs".

This test uses the rpcinfo tool for the actual test; if this tool is not available in the PATH of xymonnet, you must define the RPCINFO environment variable to point at this tool. See xymonserver.cfg(5)

 

HTTP TESTS

Simple testing of a http URL is done simply by putting the URL into the hosts.cfg file. Note that this only applies to URL's that begin with "http:" or "https:".

The following items describe more advanced forms of http URL's.

Basic Authentication with username/password
If the URL requires authentication in the form of a username and password, it is most likely using the HTTP "Basic" authentication. xymonnet support this, and you can provide the username and password either by embedding them in the URL e.g.

    http://USERNAME:PASSWORD@www.sample.com/
or by putting the username and password into the ~/.netrc file (see ftp(1) for details).

Authentication with SSL client certificates
An SSL client certificate can be used for authentication. To use this, the client certificate must be stored in a PEM-formatted file together with the client certificate key, in the $XYMONHOME/certs/ directory. The URL is then given as

    http://CERT:FILENAME@www.sample.com/
The "CERT:" part is literal - i.e. you write C-E-R-T-colon and then the filename of the PEM-formatted certificate.
A PEM-formatted certificate file can be generated based on certificates stored in Microsoft Internet Explorer and OpenSSL. Do as follows:
From the MSIE Tools-Options menu, pick the Content tab, click on Certificates, choose the Personal tab, select the certificate and click Export. Make sure you export the private key also. In the Export File Format, choose PKCS 12 (.PFX), check the "Include all certificates" checkbox and uncheck the "Enable strong protection". Provide a temporary password for the exported file, and select a filename for the PFX-file.
Now run "openssl pkcs12 -in file.pfx -out file.pem". When prompted for the "Import Password", provide the temporary password you gave when exporting the certificate. Then provide a "PEM pass phrase" (twice) when prompted for one.
The file.pem file is the one you should use in the FILENAME field in the URL - this file must be kept in $XYMONHOME/certs/. The PEM pass phrase must be put into a file named the same as the certificate, but with extension ".pass". E.g. if you have the PEM certificate in $XYMONHOME/certs/client.pem, you must put the pass phrase into the $XYMONHOME/certs/client.pass file. Make sure to protect this file with Unix permissions, so that only the user running Xymon can read it.

Forcing an HTTP or SSL version
Some SSL sites will only allow you to connect, if you use specific "dialects" of HTTP or SSL. Normally this is auto-negotiated, but experience shows that this fails on some systems.

xymonnet can be told to use specific dialects, by adding one or more "dialect names" to the URL scheme, i.e. the "http" or "https" in the URL:

* "2", e.g. https2://www.sample.com/ : use only SSLv2
* "3", e.g. https3://www.sample.com/ : use only SSLv3
* "m", e.g. httpsm://www.sample.com/ : use only 128-bit ciphers
* "h", e.g. httpsh://www.sample.com/ : use only >128-bit ciphers
* "10", e.g. http10://www.sample.com/ : use HTTP 1.0
* "11", e.g. http11://www.sample.com/ : use HTTP 1.1

These can be combined where it makes sense, e.g to force SSLv2 and HTTP 1.0 you would use "https210".

Testing sites by IP-address
xymonnet ignores the "testip" tag normally used to force a test to use the IP-address from the hosts.cfg file instead of the hostname, when it performs http and https tests.

The reason for this is that it interacts badly with virtual hosts, especially if these are IP-based as is common with https-websites.

Instead the IP-address to connect to can be overridden by specifying it as:

        http://www.sample.com=1.2.3.4/index.html

The "=1.2.3.4" will case xymonnet to run the test against the IP-address "1.2.3.4", but still trying to access a virtual website with the name "www.sample.com".

The "=ip.address.of.host" must be the last part of the hostname, so if you need to combine this with e.g. an explicit port number, it should be done as

        http://www.sample.com:3128=1.2.3.4/index.html

HTTP Testing via proxy
NOTE: This is not enabled by default. You must add the "--bb-proxy-syntax" option when running xymonnet(1) if you want to use this.

xymonnet supports the Big Brother syntax for specifying an HTTP proxy to use when performing http tests. This syntax just joins the proxy- and the target-URL into one, e.g.

    http://webproxy.sample.com:3128/http://www.foo.com/
would be the syntax for testing the www.foo.com website via the proxy running on "webproxy.sample.com" port 3128.

If the proxy port number is not specified, the default HTTP port number (80) is used.

If your proxy requires authentication, you can specify the username and password inside the proxy-part of the URL, e.g.

    http://fred:Wilma1@webproxy.sample.com:3128/http://www.foo.com/
will authenticate to the proxy using a username of "fred" and a password of "Wilma1", before requesting the proxy to fetch the www.foo.com homepage.

Note that it is not possible to test https-sites via a proxy, nor is it possible to use https for connecting to the proxy itself.

cont[=COLUMN];URL;[expected_data_regexp|#digesttype:digest]
This tag is used to specify a http/https check, where it is also checked that specific content is present in the server response.

If the URL itself includes a semi-colon, this must be escaped as '%3B' to avoid confusion over which semicolon is part of the URL, and which semicolon acts as a delimiter.

The data that must be returned can be specified either as a regular expression (except that <space> is not allowed) or as a message digest (typically using an MD5 sum or SHA-1 hash).

The regex is pre-processed for backslash "\" escape sequences. So you can really put any character in this string by escaping it first:

   \n     Newline (LF, ASCII 10 decimal)

   \r     Carriage return (CR, ASCII 13 decimal)

   \t     TAB (ASCII 8 decimal)

   \\    Backslash (ASCII 92 decimal)

   \XX    The character with ASCII hex-value XX

If you must have whitespace in the regex, use the [[:space:]] syntax, e.g. if you want to test for the string "All is OK", use "All[[:space:]]is[[:space:]]OK". Note that this may depend on your particular implementation of the regex functions found in your C library. Thanks to Charles Goyard for this tip.

Note: If you are migrating from the "cont2.sh" script, you must change the '_' used as wildcards by cont2.sh into '.' which is the regular-expression wildcard character.

Message digests can use whatever digest algorithms your libcrypto implementation (usually OpenSSL) supports. Common message digests are "md5" and "sha1". The digest is calculated on the data portion of the response from the server, i.e. HTTP headers are not included in the digest (as they change from one request to the next).

The expected digest value can be computed with the xymondigest(1) utility.

"cont" tags in hosts.cfg result in two status reports: One status with the "http" check, and another with the "content" check.

As with normal URL's, the extended syntax described above can be used e.g. when testing SSL sites that require the use of SSLv2 or strong ciphers.

The column name for the result of the content check is by default called "content" - you can change the default with the "--content=NAME" option to xymonnet. See xymonnet(1) for a description of this option.

If more than one content check is present for a host, the first content check is reported in the column "content", the second is reported in the column "content1", the third in "content2" etc.

You can also specify the column name directly in the test specification, by writing it as "cont=COLUMN;http://...". Column-names cannot include whitespace or semi-colon.

The content-check status by default includes the full URL that was requested, and the HTML data returned by the server. You can hide the HTML data on a per-host (not per-test) basis by adding the HIDEHTTP tag to the host entry.

content=URL
This syntax is deprecated. You should use the "cont" tag instead, see above.

post[=COLUMN];URL;form-data;[expected_data_regexp|#digesttype:digest]
This tag can be used to test web pages, that use an input form. Data can be posted to the form by specifying them in the form-data field, and the result can be checked as if it was a normal content check (see above for a description of the cont-tag and the restrictions on how the URL must be writen).

The form-data field must be entered in "application/x-www-form-urlencoded" format, which is the most commonly used format for web forms.

E.g. if you have a web form defined like this:


   <form action="/cgi-bin/form.cgi" method="post">

     <p>Given name<input type="text" name="givenname"></p>

     <p>Surname<input type="text" name="surname"></p>

     <input type="submit" value="Send">

   </form>

and you want to post the value "John" to the first field and "Doe Jr." to the second field, then the form data field would be


    givenname=John&surname=Doe+Jr.

Note that any spaces in the input value is replaced with '+'.

If your form-data requires a different content-type, you can specify it by beginning the form-data with (content-type=TYPE), e.g. "(content-type=text/xml)" followed by the POST data. Note that as with normal forms, the POST data should be specified using escape-sequences for reserved characters: "space" should be entered as "\x20", double quote as "\x22", newline as "\n", carriage-return as "\r", TAB as "\t", backslash as "\\". Any byte value can be entered using "\xNN" with NN being the hexadecimal value, e.g. "\x20" is the space character.

The [expected_data_regexp|#digesttype:digest] is the expected data returned from the server in response to the POST. See the "cont;" tag above for details. If you are only interested in knowing if it is possible to submit the form (but don't care about the data), this can be an empty string - but the ';' at the end is required.

nocont[=COLUMN];URL;forbidden_data_regexp
This tag works just like "cont" tag, but reverses the test. It is green when the "forbidden_data_regexp" is NOT found in the response, and red when it IS found. So it can be used to watch for data that should NOT be present in the response, e.g. a server error message.

nopost[=COLUMN];URL;form-data;expected_data_regexp
This tag works just like "post" tag, but reverses the test. It is green when the "forbidden_data_regexp" is NOT found in the response, and red when it IS found. So it can be used to watch for data that should NOT be present in the response, e.g. a server error message.

type[=COLUMN];URL;expected_content_type
This is a variant of the content check - instead of checking the content data, it checks the type of the data as given by the HTTP Content-Type: header. This can used to check if a URL returns e.g. a PDF file, regardless of what is inside the PDF file.

soap[=COLUMN];URL;SOAPMESSAGE;[expected_data_regexp|#digesttype:digest]
Send SOAP message over HTTP. This is identical to the "cont" test, except that the request sent to the server uses a Content-type of "application/soap+xml", and it also sends a "SOAPAction" header with the URL. SOAPMESSAGE is the SOAP message sent to the server. Since SOAP messages are usually XML documents, you can store this in a separate file by specifying "file:FILENAME" as the SOAPMESSAGE parameter. E.g. a test specification of
    soap=echo;http://soap.foo.bar/baz?wsdl;file:/home/foo/msg.xml;. will read the SOAP message from the file /home/foo/msg.xml and post it to the URL http://soap.foo.bar/bas?wsdl

Note that SOAP XML documents usually must begin with the XML version line, <?xml version="1.0">

nosoap[=COLUMN];URL;SOAPMESSAGE;[forbidden_data_regexp|#digesttype:digest]
This tag works just like "soap" tag, but reverses the test. It is green when the "forbidden_data_regexp" is NOT found in the response, and red when it IS found. So it can be used to watch for data that should NOT be present in the response, e.g. a server error message.

httpstatus[=COLUMN];URL;okstatusexpr;notokstatusexpr
This is used to explicitly test for certain HTTP statuscodes returned when the URL is requested. The okstatusexpr and nokokstatusexpr expressions are Perl-compatible regular expressions, e.g. "2..|302" will match all OK codes and the redirect (302) status code. If the URL cannot be retrieved, the status is "999".

HIDEHTTP
The status display for HTTP checks usually includes the URL, and for content checks also the actual data from the web page. If you would like to hide these from view, then the HIDEHTTP tag will keep this information from showing up on the status webpages.

browser=BROWSERNAME
By default, Xymon sends an HTTP "User-Agent" header identifying it a "Xymon". Some websites require that you use a specific browser, typically Internet Explorer. To cater for testing of such sites, this tag can be used to modify the data sent in the User-Agent header.
E.g. to perform an HTTP test with Xymon masquerading as an Internet Explorer 6.0 browser, use browser="Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)". If you do not know what the User-Agent header should be, open up the browser that works with this particular site, and open the URL "javascript:document.writeln(navigator.userAgent)" (just copy this into the "Open URL" dialog. The text that shows up is what the browser sends as the User-Agent header.

 

LDAP (DIRECTORY SERVER) TESTS

ldap
ldaps
Simple check for an LDAP service. This check merely looks for any service running on the ldap/ldaps service port, but does not perform any actual LDAP transaction.

ldap://hostport/dn[?attrs[?scope[?filter[?exts]]]]
Check for an LDAP service by performing an LDAP request. This tag is in the form of an LDAP URI (cf. RFC 2255). This type of LDAP test requires that xymonnet(1) was built with support for LDAP, e.g. via the OpenLDAP library. The components of the LDAP URI are:
  hostport is a host name with an optional ":portnumber"
  dn is the search base
  attrs is a comma separated list of attributes to request
  scope is one of these three strings:
    base one sub (default=base)
  filter is filter
  exts are recognized set of LDAP and/or API extensions.

ldaps://hostport/dn[?attrs[?scope[?filter[?exts]]]]
LDAP service check using LDAPv3 and STARTTLS for talking to an LDAP server that requires TLS encryption. See xymonnet(1) for a discussion of the different ways of running LDAP servers with SSL/TLS, and which of these are supported by xymonnet.

ldaplogin=username:password
Define a username and password to use when binding to the LDAP server for ldap URI tests. If not specified, xymonnet will attempt an anonymous bind.

ldapyellowfail
Used with an LDAP URL test. If the LDAP query fails during the search of the directory, the ldap status is normally reported as "red" (alarm). This tag reduces a search failure to a "yellow" (warning) status.

 

PERFORMANCE MONITORING TESTS

apache[=URL]
If you are running an Apache web server, adding this tag makes xymonnet(1) collect performance statistics from the Apache web server by querying the URL http://IP.ADDRESS.OF.HOST/server-status?auto. The response is sent as a data-report and processed by the Xymon xymond_rrd module into an RRD file and an "apache" graph. If your web server requires e.g. authentication, or runs on a different URL for the server-status, you can provide the full URL needed to fetch the server-status page, e.g. apache=http://LOGIN:PASSWORD@10.0.0.1/server-status?auto for a password protected server-status page, or apache=http://10.0.0.1:8080/apache/server-status?auto for a server listening on port 8080 and with a different path to the server-status page.

Note that you need to enable the server-status URL in your Apache configuration. The following configuration is needed:


    <Location /server-status>

        SetHandler server-status

        Order deny,allow

        Deny from all

        allow from 127.0.0.1

    </Location>

    ExtendedStatus On

Change "127.0.0.1" to the IP-address of the server that runs your network tests.

 

DEFAULT HOST

If you have certain tags that you want to apply to all hosts, you can define a host name ".default." and put the tags on that host. Note that per-host definitions will override the default ones.

NOTE: The ".default." host entry will only accept the following tags - others are silently ignored: NOCOLUMNS, COMMENT, DESCR, CLASS, dialup, testip, nonongreen, nodisp, noinfo, notrends, TRENDS, NOPROPRED, NOPROPYELLOW, NOPROPPURPLE, NOPROPACK, REPORTTIME, WARNPCT, NET, noclear, nosslcert, ssldays, DOWNTIME, depends, noping, noconn, trace, notrace, HIDEHTTP, browser, pulldata. Specifically, note that network tests, "badTEST" settings, and alternate pageset relations cannot be listed on the ".default." host.

 

SENDING SUMMARIES TO REMOTE XYMON SERVERS

summary ROW.COLUMN IP URL
If you have multiple Xymon servers, the "summary" directive lets you form a hierarchy of servers by sending the overall status of this server to a remote Xymon server, which then displays this in a special summary section. E.g. if your offices are spread over three locations, you can have a Xymon server at each office. These branch-office Xymon have a "summary" definition in their hosts.cfg file that makes them report the overall status of their branch Xymon to the central Xymon server you maintain at the corporate headquarters.

Multiple "summary" definitions are allowed.

The ROW.COLUMN setting defines how this summary is presented on the server that receives the summary. The ROW text will be used as the heading for a summary line, and the COLUMN defines the name of the column where this summary is shown - like the hostname and testname used in the normal displays. The IP is the IP-address of the remote (upstream) Xymon server, where this summary is sent). The URL is the URL of your local Xymon server.

The URL need not be that of your Xymon server's main page - it could be the URL of a sub-page on the local Xymon server. Xymon will report the summary using the color of the page found at the URL you specify. E.g. on your corporate Xymon server you want a summary from the Las Vegas office - but you would like to know both what the overall status is, and what is the status of the servers on the critical Sales department back-office servers in Las Vegas. So you configure the Las Vegas Xymon server to send two summaries:


    summary Vegas.All 10.0.1.1 http://vegas.foo.com/xymon/

    summary Vegas.Sales 10.0.1.1 http://vegas.foo.com/xymon/sales/

This gives you one summary line for Baltimore, with two columns: An "All" column showing the overall status, and a "Sales" column showing the status of the "sales" page on the Baltimore Xymon server.

Note: Pages defined using alternate pageset definitions cannot be used, the URL must point to a web page from the default set of Xymon webpages.

 

OTHER TAGS

pulldata[=[IP][:port]]
This option is recognized by the xymonfetch(8) utility, and causes it to poll the host for client data. The optional IP-address and port-number can be used if the client-side msgcache(8) daemon is listening on a non-standard IP-address or port-number.

 

FILES

~xymon/server/etc/hosts.cfg

 

SEE ALSO

xymongen(1), xymonnet(1), xymondigest(1), xymonserver.cfg(5), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
FILE FORMAT
TAGS RECOGNIZED BY ALL TOOLS
GENERAL PER-HOST OPTIONS
XYMONGEN DISPLAY OPTIONS
XYMON TAGS FOR THE CRITICAL SYSTEMS OVERVIEW PAGE
XYMON TAGS FOR THE WML (WAP) CARDS
XYMON STATUS PROPAGATION OPTIONS
XYMON AVAILABILITY REPORT OPTIONS
NETWORK TEST SETTINGS
CONNECTIVITY (PING) TEST
SIMPLE NETWORK TESTS
DNS SERVER TESTS
OTHER NETWORK TESTS
HTTP TESTS
LDAP (DIRECTORY SERVER) TESTS
PERFORMANCE MONITORING TESTS
DEFAULT HOST
SENDING SUMMARIES TO REMOTE XYMON SERVERS
OTHER TAGS
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/clientlaunch.cfg.5.html0000664000175000017500000000273011671641417022060 0ustar henrikhenrik Man page of CLIENTLAUNCH.CFG

CLIENTLAUNCH.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

clientlaunch.cfg - Task definitions for the xymonlaunch utility

 

SYNOPSIS

~xymon/client/etc/clientlaunch.cfg

 

DESCRIPTION

The clientlaunch.cfg file holds the list of tasks that xymonlaunch runs on a Xymon client. This is typically just the Xymon client itself, but you can add custom test scripts here and have them executed regularly by the Xymon scheduler.

 

FILE FORMAT

See the tasks.cfg(5) description.

 

SEE ALSO

xymonlaunch(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
FILE FORMAT
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/graphs.cfg.5.html0000664000175000017500000001464711671641417020705 0ustar henrikhenrik Man page of GRAPHS.CFG

GRAPHS.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

graphs.cfg - Configuration of the showgraph CGI

 

SYNOPSIS

$XYMONHOME/etc/graphs.cfg

 

DESCRIPTION

showgraph.cgi(1) uses the configuration file $XYMONHOME/etc/graphs.cfg to build graphs from the RRD files collected by Xymon.

 

FILE FORMAT

Each definition of a graph type begins with a "[SERVICE]" indicator, this is the name passed as the "service" parameter to showgraph.cgi(1). If the service name passed to showgraph.cgi is not found, it will attempt to match the service name to a graph via the TEST2RRD environment variable. So calling showgraph.cgi with "service=cpu" or "service=la" will end up producing the same graph.

A graph definition needs to have a TITLE and a YAXIS setting. These are texts shown as the title of the graph, and the YAXIS heading respectively. (The X-axis is always time-based).

If a fixed set of RRD files are used for the graph, you just write those in the RRDtool definitions. Note that Xymon keeps all RRD files for a host in a separate directory per host, so you need not worry about the hostname being part of the RRD filename.

For graphs that use multiple RRD files as input, you specify a filename pattern in the FNPATTERN statement, and optionally a pattern of files to exclude from the graph with EXFNPATTERN (see "[tcp]" for an example). When FNPATTERN is used, you can use "@RRDFN@" in the RRDtool definitions to pick up each filename. "@RRDIDX@" is an index (starting at 0) for each file in the set. "@RRDPARAM@" contains the first word extracted from the pattern of files (see e.g. "[memory]" how this is used). "@COLOR@" picks a new color for each graph automatically.

The remainder of the lines in each definition are passed directly to the RRDtool rrd_graph() routine.

The following is an example of how the "la" (cpu) graph is defined. This is a simple definition that uses a single RRD-file, la.rrd:

[la]

        TITLE CPU Load

        YAXIS Load

        DEF:avg=la.rrd:la:AVERAGE

        CDEF:la=avg,100,/

        AREA:la#00CC00:CPU Load Average

        GPRINT:la:LAST: : %5.1lf (cur)

        GPRINT:la:MAX: : %5.1lf (max)

        GPRINT:la:MIN: : %5.1lf (min)

        GPRINT:la:AVERAGE: : %5.1lf (avg)

Here is an example of a graph that uses multiple RRD-files, determined automatically at run-time via the FNPATTERN setting. Note how it uses the @RRDIDX@ to define a unique RRD parameter per input-file, and the @COLOR@ and @RRDPARAM@ items to pick unique colors and a matching text for the graph legend:

[disk]

        FNPATTERN disk(.*).rrd

        TITLE Disk Utilization

        YAXIS % Full

        DEF:p@RRDIDX@=@RRDFN@:pct:AVERAGE

        LINE2:p@RRDIDX@#@COLOR@:@RRDPARAM@

        -u 100

        -l 0

        GPRINT:p@RRDIDX@:LAST: : %5.1lf (cur)

        GPRINT:p@RRDIDX@:MAX: : %5.1lf (max)

        GPRINT:p@RRDIDX@:MIN: : %5.1lf (min)

        GPRINT:p@RRDIDX@:AVERAGE: : %5.1lf (avg)

 

ADVANCED GRAPH TITLES

Normally the title of a graph is a static text defined in the graphs.cfg file. However, there may be situations where you want to use different titles for the same type of graph, e.g. if you are incorporating RRD files from MRTG into Xymon. In that case you can setup the TITLE definition so that it runs a custom script to determine the graph title. Like this:

       TITLE exec:/usr/local/bin/graphitle

The /usr/local/bin/graphtitle command is then called with the hostname, the graphtype, the period string, and all of the RRD files used as parameters. The script must generate one line of output, which is then used as the title of the graph.

 

ENVIRONMENT

TEST2RRD Maps service names to graph definitions.

 

NOTES

Most of the RRD graph definitions shipped with Xymon have been ported from the definitions in the larrd-grapher.cgi CGI from LARRD 0.43c.

 

SEE ALSO

xymonserver.cfg(5), rrdtool(1), rrdgraph(1)


 

Index

NAME
SYNOPSIS
DESCRIPTION
FILE FORMAT
ADVANCED GRAPH TITLES
ENVIRONMENT
NOTES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/xymonclient.cfg.5.html0000664000175000017500000000602711671641417021763 0ustar henrikhenrik Man page of XYMONCLIENT.CFG

XYMONCLIENT.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymonclient.cfg - Xymon client environment variables

 

DESCRIPTION

Xymon programs use multiple environment variables beside the normal set of variables. For the Xymon client, the environment definitions are stored in the ~xymon/client/etc/xymonclient.cfg file. Each line in this file is of the form NAME=VALUE and defines one environment variable NAME with the value VALUE.

 

SETTINGS

XYMSRV
The IP-address used to contact the Xymon server. Default: Chosen when the Xymon client was compiled.

XYMSERVERS
List of IP-adresses of Xymon servers. Data will be sent to all of the servers listed here. This setting is only used if XYMSRV=0.0.0.0.

XYMONDPORT
The port number for used to contact the Xymon server. Default: 1984.

XYMONHOME
The Xymon client top-level directory. Default: The $XYMONCLIENTHOME setting inherited from the "runclient.sh" script which starts the Xymon client.

XYMONCLIENTLOGS
The directory for the Xymon clients' own log files. Default: $XYMONHOME/logs

XYMONTMP
Directory used for temporary files. Default: $XYMONHOME/tmp/

XYMON
Full path to the xymon(1) client program. Default: $XYMONHOME/bin/xymon.

Commands
Many extension scripts expect a series of environment variables to point at various system utilities. These are included in the file when the client is built.

 

INHERITED SETTINGS

Some environment variables are inherited from the "runclient.sh" script which launches the Xymon client:

MACHINEDOTS
The hostname of the local system. Default: Taken from "uname -n".

MACHINE
The hostname of the local system, with dots replaced by commas. For compatibility with Big Brother extension scripts.

SERVEROSTYPE
The operating system of the local system, in lowercase. Default: taken from "uname -s".

XYMONCLIENTHOME
The top-level directory for the Xymon client. Default: The location of the "runclient.sh" script.

 

SEE ALSO

xymon(7)


 

Index

NAME
DESCRIPTION
SETTINGS
INHERITED SETTINGS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/analysis.cfg.5.html0000664000175000017500000007477111671641417021250 0ustar henrikhenrik Man page of ANALYSIS.CFG

ANALYSIS.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

analysis.cfg - Configuration file for the xymond_client module

 

SYNOPSIS

~Xymon/server/etc/analysis.cfg

 

DESCRIPTION

The analysis.cfg file controls what color is assigned to the status-messages that are generated from the Xymon client data - typically the cpu, disk, memory, procs- and msgs-columns. Color is decided on the basis of some settings defined in this file; settings apply to specific hosts through a set of rules.

Note: This file is only used on the Xymon server - it is not used by the Xymon client, so there is no need to distribute it to your client systems.

 

FILE FORMAT

Blank lines and lines starting with a hash mark (#) are treated as comments and ignored.

 

CPU STATUS COLUMN SETTINGS

LOAD warnlevel paniclevel

If the system load exceeds "warnlevel" or "paniclevel", the "cpu" status will go yellow or red, respectively. These are decimal numbers.

Defaults: warnlevel=5.0, paniclevel=10.0

UP bootlimit toolonglimit [color]

The cpu status goes yellow/red if the system has been up for less than "bootlimit" time, or longer than "toolonglimit". The time is in minutes, or you can add h/d/w for hours/days/weeks - eg. "2h" for two hours, or "4w" for 4 weeks.

Defaults: bootlimit=1h, toolonglimit=-1 (infinite), color=yellow.

CLOCK max.offset [color]

The cpu status goes yellow/red if the system clock on the client differs more than "max.offset" seconds from that of the Xymon server. Note that this is not a particularly accurate test, since it is affected by network delays between the client and the server, and the load on both systems. You should therefore not rely on this being accurate to more than +/- 5 seconds, but it will let you catch a client clock that goes completely wrong. The default is NOT to check the system clock.
NOTE: Correct operation of this test obviously requires that the system clock of the Xymon server is correct. You should therefore make sure that the Xymon server is synchronized to the real clock, e.g. by using NTP.

Example: Go yellow if the load average exceeds 5, and red if it exceeds 10. Also, go yellow for 10 minutes after a reboot, and after 4 weeks uptime. Finally, check that the system clock is at most 15 seconds offset from the clock of the Xymon system and go red if that is exceeded.

LOAD 5 10
UP 10m 4w yellow
CLOCK 15 red

 

DISK STATUS COLUMN SETTINGS

DISK filesystem warnlevel paniclevel
DISK filesystem IGNORE
INODE filesystem warnlevel paniclevel
INODE filesystem IGNORE

If the utilization of "filesystem" is reported to exceed "warnlevel" or "paniclevel", the "disk" status will go yellow or red, respectively. "warnlevel" and "paniclevel" are either the percentage used, or the space available as reported by the local "df" command on the host. For the latter type of check, the "warnlevel" must be followed by the letter "U", e.g. "1024U".

The special keyword "IGNORE" causes this filesystem to be ignored completely, i.e. it will not appear in the "disk" status column and it will not be tracked in a graph. This is useful for e.g. removable devices, backup-disks and similar hardware.

"filesystem" is the mount-point where the filesystem is mounted, e.g. "/usr" or "/home". A filesystem-name that begins with "%" is interpreted as a Perl-compatible regular expression; e.g. "%^/oracle.*/" will match any filesystem whose mountpoint begins with "/oracle".

"INODE" works identical to "DISK", but uses the count of i-nodes in the filesystem instead of the amount of disk space. This requires a 4.3.x or later Xymon client.

Defaults DISK: warnlevel=90%, paniclevel=95% Defaults INODE: warnlevel=70%, paniclevel=90%

 

MEMORY STATUS COLUMN SETTINGS

MEMPHYS warnlevel paniclevel
MEMACT warnlevel paniclevel
MEMSWAP warnlevel paniclevel

If the memory utilization exceeds the "warnlevel" or "paniclevel", the "memory" status will change to yellow or red, respectively. Note: The words "PHYS", "ACT" and "SWAP" are also recognized.

Example: Go yellow if more than 20% swap is used, and red if more than 40% swap is used or the actual memory utilisation exceeds 90%. Dont alert on physical memory usage.

MEMSWAP 20 40
MEMACT 90 90
MEMPHYS 101 101

Defaults:

MEMPHYS warnlevel=100 paniclevel=101 (i.e. it will never go red).
MEMSWAP warnlevel=50 paniclevel=80
MEMACT  warnlevel=90 paniclevel=97

 

PROCS STATUS COLUMN SETTINGS

PROC processname minimumcount maximumcount color [TRACK=id] [TEXT=text]

The "ps" listing sent by the client will be scanned for how many processes containing "processname" are running, and this is then matched against the min/max settings defined here. If the running count is outside the thresholds, the color of the "procs" status changes to "color".

To check for a process that must NOT be running: Set minimum and maximum to 0.

"processname" can be a simple string, in which case this string must show up in the "ps" listing as a command. The scanner will find a ps-listing of e.g. "/usr/sbin/cron" if you only specify "processname" as "cron". "processname" can also be a Perl-compatiable regular expression, e.g. "%java.*inst[0123]" can be used to find entries in the ps-listing for "java -Xmx512m inst2" and "java -Xmx256 inst3". In that case, "processname" must begin with "%" followed by the regular expression. Note that Xymon defaults to case-insensitive pattern matching; if that is not what you want, put "(?-i)" between the "%" and the regular expression to turn this off. E.g. "%(?-i)HTTPD" will match the word HTTPD only when it is upper-case.
If "processname" contains whitespace (blanks or TAB), you must enclose the full string in double quotes - including the "%" if you use regular expression matching. E.g.


    PROC "%xymond_channel --channel=data.*xymond_rrd" 1 1 yellow

or


    PROC "java -DCLASSPATH=/opt/java/lib" 2 5

You can have multiple "PROC" entries for the same host, all of the checks are merged into the "procs" status and the most severe check defines the color of the status.

The optional TRACK=id setting causes Xymon to track the number of processes found in an RRD file, and put this into a graph which is shown on the "procs" status display. The id setting is a simple text string which will be used as the legend for the graph, and also as part of the RRD filename. It is recommended that you use only letters and digits for the ID.
Note that the process counts which are tracked are only performed once when the client does a poll cycle - i.e. the counts represent snapshots of the system state, not an average value over the client poll cycle. Therefore there may be peaks or dips in the actual process counts which will not show up in the graphs, because they happen while the Xymon client is not doing any polling.

The optional TEXT=text setting is used in the summary of the "procs" status. Normally, the summary will show the "processname" to identify the process and the related count and limits. But this may be a regular expression which is not easily recognizable, so if defined, the text setting string will be used instead. This only affects the "procs" status display - it has no effect on how the rule counts or recognizes processes in the "ps" output.

Example: Check that "cron" is running:
       PROC cron

Example: Check that at least 5 "httpd" processes are running, but not more than 20:
       PROC httpd 5 20

Defaults:
       mincount=1, maxcount=-1 (unlimited), color="red".

       Note that no processes are checked by default.

 

MSGS STATUS COLUMN SETTINGS

LOG logfilename pattern [COLOR=color] [IGNORE=excludepattern]

The Xymon client extracts interesting lines from one or more logfiles - see the client-local.cfg(5) man-page for information about how to configure which logs a client should look at.

The LOG setting determine how these extracts of log entries are processed, and what warnings or alerts trigger as a result.

"logfilename" is the name of the logfile. Only logentries from this filename will be matched against this rule. Note that "logfilename" can be a regular expression (if prefixed with a '%' character).

"pattern" is a string or regular expression. If the logfile data matches "pattern", it will trigger the "msgs" column to change color. If no "color" parameter is present, the default is to go "red" when the pattern is matched. To match against a regular expression, "pattern" must begin with a '%' sign - e.g "%WARNING|NOTICE" will match any lines containing either of these two words. Note that Xymon defaults to case-insensitive pattern matching; if that is not what you want, put "(?-i)" between the "%" and the regular expression to turn this off. E.g. "%(?-i)WARNING" will match the word WARNING only when it is upper-case.

"excludepattern" is a string or regular expression that can be used to filter out any unwanted strings that happen to match "pattern".

Example: Trigger a red alert when the string "ERROR" appears in the "/var/adm/syslog" file:
       LOG /var/adm/syslog ERROR

Example: Trigger a yellow warning on all occurrences of the word "WARNING" or "NOTICE" in the "daemon.log" file, except those from the "lpr" system:
       LOG /var/log/daemon.log %WARNING|NOTICE COLOR=yellow IGNORE=lpr

Defaults:
       color="red", no "excludepattern".

Note that no logfiles are checked by default. Any log data reported by a client will just show up on the "msgs" column with status OK (green).

 

FILES STATUS COLUMN SETTINGS

FILE filename [color] [things to check] [TRACK]

DIR directoryname [color] [size<MAXSIZE] [size>MINSIZE] [TRACK]

These entries control the status of the "files" column. They allow you to check on various data for files and directories.

filename and directoryname are names of files or directories, with a full path. You can use a regular expression to match the names of files and directories reported by the client, if you prefix the expression with a '%' character.

color is the color that triggers when one or more of the checks fail.

The TRACK keyword causes the size of the file or directory to be tracked in an RRD file, and presented in a graph on the "files" status display.

For files, you can check one or more of the following:

noexist
triggers a warning if the file exists. By default, a warning is triggered for files that have a FILE entry, but which do not exist.
type=TYPE
where TYPE is one of "file", "dir", "char", "block", "fifo", or "socket". Triggers warning if the file is not of the specified type.
ownerid=OWNER
triggers a warning if the owner does not match what is listed here. OWNER is specified either with the numeric uid, or the user name.
groupid=GROUP
triggers a warning if the group does not match what is listed here. GROUP is specified either with the numeric gid, or the group name.
mode=MODE
triggers a warning if the file permissions are not as listed. MODE is written in the standard octal notation, e.g. "644" for the rw-r--r-- permissions.
size<MAX.SIZE and size>MIN.SIZE
triggers a warning it the file size is greater than MAX.SIZE or less than MIN.SIZE, respectively. For filesizes, you can use the letters "K", "M", "G" or "T" to indicate that the filesize is in Kilobytes, Megabytes, Gigabytes or Terabytes, respectively. If there is no such modifier, Kilobytes is assumed. E.g. to warn if a file grows larger than 1MB, use size<1024M.
mtime>MIN.MTIME mtime<MAX.MTIME
checks how long ago the file was last modified (in seconds). E.g. to check if a file was updated within the past 10 minutes (600 seconds): mtime<600. Or to check that a file has NOT been updated in the past 24 hours: mtime>86400.
mtime=TIMESTAMP
checks if a file was last modified at TIMESTAMP. TIMESTAMP is a unix epoch time (seconds since midnight Jan 1 1970 UTC).
ctime>MIN.CTIME, ctime<MAX.CTIME, ctime=TIMESTAMP
acts as the mtime checks, but for the ctime timestamp (when the directory entry of the file was last changed, eg. by chown, chgrp or chmod).
md5=MD5SUM, sha1=SHA1SUM, rmd160=RMD160SUM
trigger a warning if the file checksum using the MD5, SHA1 or RMD160 message digest algorithms do not match the one configured here. Note: The "file" entry in the client-local.cfg(5) file must specify which algorithm to use.

For directories, you can check one or more of the following:

size<MAX.SIZE and size>MIN.SIZE
triggers a warning it the directory size is greater than MAX.SIZE or less than MIN.SIZE, respectively. Directory sizes are reported in whatever unit the du command on the client uses - often KB or diskblocks - so MAX.SIZE and MIN.SIZE must be given in the same unit.

Experience shows that it can be difficult to get these rules right. Especially when defining minimum/maximum values for file sizes, when they were last modified etc. The one thing you must remember when setting up these checks is that the rules describe criteria that must be met - only when they are met will the status be green.

So "mtime<600" means "the difference between current time and the mtime of the file must be less than 600 seconds - if not, the file status will go red".

 

PORTS STATUS COLUMN SETTINGS

PORT criteria [MIN=mincount] [MAX=maxcount] [COLOR=color] [TRACK=id] [TEXT=displaytext]

The "netstat" listing sent by the client will be scanned for how many sockets match the criteria listed. The criteria you can use are:

LOCAL=addr
"addr" is a (partial) local address specification in the format used on the output from netstat.
EXLOCAL=addr
Exclude certain local adresses from the rule.
REMOTE=addr
"addr" is a (partial) remote address specification in the format used on the output from netstat.
EXREMOTE=addr
Exclude certain remote adresses from the rule.
STATE=state
Causes only the sockets in the specified state to be included, "state" is usually LISTEN or ESTABLISHED but can be any socket state reported by the clients "netstat" command.
EXSTATE=state
Exclude certain states from the rule.

"addr" is typically "10.0.0.1:80" for the IP 10.0.0.1, port 80. Or "*:80" for any local address, port 80. Note that the Xymon clients normally report only the numeric data for IP-adresses and port-numbers, so you must specify the port number (e.g. "80") instead of the service name ("www").
"addr" and "state" can also be a Perl-compatiable regular expression, e.g. "LOCAL=%[.:](80|443)" can be used to find entries in the netstat local port for both http (port 80) and https (port 443). In that case, portname or state must begin with "%" followed by the reg.expression.

The socket count found is then matched against the min/max settings defined here. If the count is outside the thresholds, the color of the "ports" status changes to "color". To check for a socket that must NOT exist: Set minimum and maximum to 0.

The optional TRACK=id setting causes Xymon to track the number of sockets found in an RRD file, and put this into a graph which is shown on the "ports" status display. The id setting is a simple text string which will be used as the legend for the graph, and also as part of the RRD filename. It is recommended that you use only letters and digits for the ID.
Note that the sockets counts which are tracked are only performed once when the client does a poll cycle - i.e. the counts represent snapshots of the system state, not an average value over the client poll cycle. Therefore there may be peaks or dips in the actual sockets counts which will not show up in the graphs, because they happen while the Xymon client is not doing any polling.

The TEXT=displaytext option affects how the port appears on the "ports" status page. By default, the port is listed with the local/remote/state rules as identification, but this may be somewhat difficult to understand. You can then use e.g. "TEXT=Secure Shell" to make these ports appear with the name "Secure Shell" instead.

Defaults: mincount=1, maxcount=-1 (unlimited), color="red". Note: No ports are checked by default.

Example: Check that the SSH daemon is listening on port 22. Track the number of active SSH connections, and warn if there are more than 5.

        PORT LOCAL=%[.:]22$ STATE=LISTEN "TEXT=SSH listener"

        PORT LOCAL=%[.:]22$ STATE=ESTABLISHED MAX=5 TRACK=ssh TEXT=SSH

 

SVCS status (Microsoft Windows clients)

SVC servicename status=(started|stopped) [startup=automatic|disabled|manual]

 

DS - RRD based status override

DS column filename:dataset rules COLOR=colorname TEXT=explanation

"column" is the statuscolumn that will be modified. "filename" is the name of the RRD file holding the data you use for comparison. "dataset" is the name of the dataset in the RRD file - the "rrdtool info" command is useful when determining these. "rules" determine when to apply the override. You can use ">", ">=", "<" or "<=" to compare the current measurement value against one or more thresholds. "explanation" is a text that will be shown to explain the override - you can use some placeholders in the text: "&N" is replaced with the name of the dataset, "&V" is replaced with the current value, "&L" is replaced by the low threshold, "&U" is replaced with the upper threshold.

NOTE: This rule uses the raw data value from a client to examine the rules. So this type of test is only really suitable for datasets that are of the "GAUGE" type. It cannot be used meaningfully for datasets that use "COUNTER" or "DERIVE" - e.g. the datasets that are used to capture network packet traffic - because the data stored in the RRD for COUNTER-based datasets undergo a transformation (calculation) when going into the RRD. Xymon does not have direct access to the calculated data.

Example: Flag "conn" status a yellow if responsetime exceeds 100 msec.
       DS conn tcp.conn.rrd:sec >0.1 COLOR=yellow TEXT="Response time &V exceeds &U seconds"

 

MQ Series SETTINGS

MQ_QUEUE queuename [age-warning=N] [age-critical=N] [depth-warning=N] [depth-critical=N]
MQ_CHANNEL channelname [warning=PATTERN] [alert=PATTERN]

This is a set of checks for checking the health of IBM MQ message-queues. It requires the "mq.sh" or similar collector module to run on a node with access to the MQ "Queue Manager" so it can report the status of queues and channels.

The MQ_QUEUE setting checks the health of a single queue: You can warn (yellow) or alert (red) based on the depth of the queue, and/or the age of the oldest entry in the queue. These values are taken directly from the output generated by the "runmqsc" utility.

The MQ_CHANNEL setting checks the health of a single MQ channel: You can warn or alert based on the reported status of the channel. The PATTERN is a normal pattern, i.e. either a list of status keywords, or a regular expression pattern.

 

CHANGING THE DEFAULT SETTINGS

If you would like to use different defaults for the settings described above, then you can define the new defaults after a DEFAULT line. E.g. this would explicitly define all of the default settings:
DEFAULT
        UP      1h
        LOAD    5.0 10.0
        DISK    * 90 95
        MEMPHYS 100 101
        MEMSWAP 50 80
        MEMACT  90 97

 

RULES TO SELECT HOSTS

All of the settings can be applied to a group of hosts, by preceding them with rules. A rule defines of one of more filters using these keywords (note that this is identical to the rule definitions used in the alerts.cfg(5) file).

PAGE=targetstring Rule matching an alert by the name of the page in Xymon. "targetstring" is the path of the page as defined in the hosts.cfg file. E.g. if you have this setup:

page servers All Servers
subpage web Webservers
10.0.0.1 www1.foo.com
subpage db Database servers
10.0.0.2 db1.foo.com

Then the "All servers" page is found with PAGE=servers, the "Webservers" page is PAGE=servers/web and the "Database servers" page is PAGE=servers/db. Note that you can also use regular expressions to specify the page name, e.g. PAGE=%.*/db would find the "Database servers" page regardless of where this page was placed in the hierarchy.

The top-level page has a the fixed name /, e.g. PAGE=/ would match all hosts on the Xymon frontpage. If you need it in a regular expression, use PAGE=%^/ to avoid matching the forward-slash present in subpage-names.

EXPAGE=targetstring Rule excluding a host if the pagename matches.

HOST=targetstring Rule matching a host by the hostname. "targetstring" is either a comma-separated list of hostnames (from the hosts.cfg file), "*" to indicate "all hosts", or a Perl-compatible regular expression. E.g. "HOST=dns.foo.com,www.foo.com" identifies two specific hosts; "HOST=%www.*.foo.com EXHOST=www-test.foo.com" matches all hosts with a name beginning with "www", except the "www-test" host.

EXHOST=targetstring Rule excluding a host by matching the hostname.

CLASS=classname Rule match by the client class-name. You specify the class-name for a host when starting the client through the "--class=NAME" option to the runclient.sh script. If no class is specified, the host by default goes into a class named by the operating system.

EXCLASS=classname Exclude all hosts belonging to "classname" from this rule.

DISPLAYGROUP=groupstring Rule matching an alert by the text of the display-group (text following the group, group-only, group-except heading) in the hosts.cfg file. "groupstring" is the text for the group, stripped of any HTML tags. E.g. if you have this setup:

group Web
10.0.0.1 www1.foo.com
10.0.0.2 www2.foo.com
group Production databases
10.0.1.1 db1.foo.com

Then the hosts in the Web-group can be matched with DISPLAYGROUP=Web, and the database servers can be matched with DISPLAYGROUP="Production databases". Note that you can also use regular expressions, e.g. DISPLAYGROUP=%database. If there is no group-setting for the host, use "DISPLAYGROUP=NONE".

EXDISPLAYGROUP=groupstring Rule excluding a group by matching the display-group string.

TIME=timespecification Rule matching by the time-of-day. This is specified as the DOWNTIME time specification in the hosts.cfg file. E.g. "TIME=W:0800:2200" applied to a rule will make this rule active only on week-days between 8AM and 10PM.

 

DIRECTING ALERTS TO GROUPS

For some tests - e.g. "procs" or "msgs" - the right group of people to alert in case of a failure may be different, depending on which of the client rules actually detected a problem. E.g. if you have PROCS rules for a host checking both "httpd" and "sshd" processes, then the Web admins should handle httpd-failures, whereas "sshd" failures are handled by the Unix admins.

To handle this, all rules can have a "GROUP=groupname" setting. When a rule with this setting triggers a yellow or red status, the groupname is passed on to the Xymon alerts module, so you can use it in the alert rule definitions in alerts.cfg(5) to direct alerts to the correct group of people.

 

RULES: APPLYING SETTINGS TO SELECTED HOSTS

Rules must be placed after the settings, e.g.
LOAD 8.0 12.0  HOST=db.foo.com TIME=*:0800:1600

If you have multiple settings that you want to apply the same rules to, you can write the rules *only* on one line, followed by the settings. E.g.

HOST=%db.*.foo.com TIME=W:0800:1600
        LOAD 8.0 12.0
        DISK /db  98 100
        PROC mysqld 1

will apply the three settings to all of the "db" hosts on week-days between 8AM and 4PM. This can be combined with per-settings rule, in which case the per-settings rule overrides the general rule; e.g.

HOST=%.*.foo.com
        LOAD 7.0 12.0 HOST=bax.foo.com
        LOAD 3.0 8.0

will result in the load-limits being 7.0/12.0 for the "bax.foo.com" host, and 3.0/8.0 for all other foo.com hosts.

The entire file is evaluated from the top to bottom, and the first match found is used. So you should put the specific settings first, and the generic ones last.

 

NOTES

For the LOG, FILE and DIR checks, it is necessary also to configure the actual file- and directory-names in the client-local.cfg(5) file. If the filenames are not listed there, the clients will not collect any data about these files/directories, and the settings in the analysis.cfg file will be silently ignored.

The ability to compute file checksums with MD5, SHA1 or RMD160 should not be used for general-purpose file integrity checking, since the overhead of calculating these on a large number of files can be significant. If you need this, look at tools designed for this purpose - e.g. Tripwire or AIDE.

At the time of writing (april 2006), the SHA-1 and RMD160 algorithms are considered cryptographically safe. The MD5 algorithm has been shown to have some weaknesses, and is not considered strong enough when a high level of security is required.

 

SEE ALSO

xymond_client(8), client-local.cfg(5), xymond(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
FILE FORMAT
CPU STATUS COLUMN SETTINGS
DISK STATUS COLUMN SETTINGS
MEMORY STATUS COLUMN SETTINGS
PROCS STATUS COLUMN SETTINGS
MSGS STATUS COLUMN SETTINGS
FILES STATUS COLUMN SETTINGS
PORTS STATUS COLUMN SETTINGS
SVCS status (Microsoft Windows clients)
DS - RRD based status override
MQ Series SETTINGS
CHANGING THE DEFAULT SETTINGS
RULES TO SELECT HOSTS
DIRECTING ALERTS TO GROUPS
RULES: APPLYING SETTINGS TO SELECTED HOSTS
NOTES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/xymonweb.5.html0000664000175000017500000001557111671641417020530 0ustar henrikhenrik Man page of XYMONWEB

XYMONWEB

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

Xymon web page headers, footers and forms.

 

DESCRIPTION

The Xymon webpages are somewhat customizable, by modifying the header- and footer-templates found in the ~xymon/server/web/ directory. There are usually two or more files for a webpage: A template_header file which is the header for this webpage, and a template_footer file which is the footer. Webpages where entry forms are used have a template_form file which is the data-entry form.

With the exception of the bulletin files, the header files are inserted into the HTML code at the very beginning and the footer files are inserted at the bottom.

The following templates are available:

bulletin
A bulletin_header and bulletin_footer is not shipped with Xymon, but if they exist then the content of these files will be inserted in all HTML documents generated by Xymon. The "bulletin_header" contents will appear after the normal header for the webpage, and the "bulletin_footer" will appear just before the normal footer for the webpage. These files can be used to post important information about the Xymon system, e.g. to notify users of current operational or monitoring problems.

acknowledge
Header, footer and form template for the Xymon acknowledge alert webpage generated by acknowledge.cgi(1)

stdnormal
Header and footer for the Xymon Main view webpages, generated by xymongen(1)

stdnongreen
Header and footer for the Xymon All non-green view webpage, generated by xymongen(1)

stdcritical
Header and footer for the now deprecated old critical webpage, generated by xymongen. You should use the newer criticalview.cgi(1) utility instead, which uses the critical templates.

repnormal
Header and footer for the Xymon Main view availability report webpages, generated by xymongen(1) when running in availability report mode.

snapnormal
Header and footer for the Xymon Main view snapshot webpages, generated by xymongen(1) when running in snapshot report mode.

snapnongreen
Header and footer for the Xymon All non-green view snapshot webpage, generated by xymongen(1) when running in snapshot report mode.

columndoc
Header and footer for the Xymon Column documentation webpages, generated by the csvinfo.cgi(1) utility in the default Xymon configuration.

confreport
Header and footer for the Xymon Configuration report webpage, generated by the confreport.cgi(1) utility. Note that there are also "confreport_front" and "confreport_back" templates, these are inserted into the generated report before the hostlist, and before the column documentation, respectively.

event
Header, footer and form for the Xymon Eventlog report, generated by eventlog.cgi(1)

findhost
Header, footer and form for the Xymon Find host webpage, generated by findhost.cgi(1)

graphs
Header and footer for the Xymon Graph details webpages, generated by showgraph.cgi(1)

hist
Header and footer for the Xymon History webpage, generated by history.cgi(1)

histlog
Header and footer for the Xymon Historical status-log webpage, generated by svcstatus.cgi(1) utility when used to show a historical (non-current) status log.

critical
Header and footer for the Xymon Critical Systems view webpage, generated by criticalview.cgi(1)

hostsvc
Header and footer for the Xymon Status-log webpage, generated by svcstatus.cgi(1) utility when used to show a current status log.

info
Header and footer for the Xymon Info column webpage, generated by svcstatus.cgi(1) utility when used to show the host configuration page.

maintact
Header and footer for the Xymon webpage, generated by enadis.cgi(1) utility when using the Enable/Disable "preview" mode.

maint
Header, footer and form for the Xymon Enable/disable webpage, generated by enadis.cgi(1)

critack
Form show on the status-log webpage when viewed from the "Critical Systems" overview. This form is used to acknowledge a critical status by the operators monitoring the Critical Systems view.

critedit
Header, footer and form for the Critical Systems Editor, the criticaleditor.cgi(1) utility.

replog
Header and footer for the Xymon Report status-log webpage, generated by svcstatus.cgi(1) utility when used to show a status log for an availability report.

report
Header, footer and forms for selecting a pre-generated Availability Report. Handled by the datepage.cgi(1) utility.

snapshot
Header and footer for the Xymon Snapshot report selection webpage, generated by snapshot.cgi(1)

 

SEE ALSO

xymongen(1), svcstatus.cgi(1), xymon(7)


 

Index

NAME
DESCRIPTION
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/client-local.cfg.5.html0000664000175000017500000002123211671641417021753 0ustar henrikhenrik Man page of CLIENT-LOCAL.CFG

CLIENT-LOCAL.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

client-local.cfg - Local configuration settings for Xymon clients

 

SYNOPSIS

~xymon/server/etc/client-local.cfg

 

DESCRIPTION

The client-local.cfg file contains settings that are used by each Xymon client when it runs on a monitored host. It provides a convenient way of configuring clients from a central location without having to setup special configuration maintenance tools on all clients.

The client-local.cfg file is currently used to configure what logfiles the client should fetch data from, to be used as the basis for the "msgs" status column; and to configure which files and directories are being monitored in the "files" status column.

Note that there is a dependency between the client-local.cfg file and the anaysis.cfg(5) file. When monitoring e.g. a logfile, you must first enter it into the client-local.cfg file, to trigger the Xymon client into reporting any data about the logfile. Next, you must configure analysis.cfg so the Xymon server knows what to look for in the file data sent by the client. So: client-local.cfg defines what raw data is collected by the client, and analysis.cfg defines how to analyze them.

 

PROPAGATION TO CLIENTS

The client-local.cfg file resides on the Xymon server.

When clients connect to the Xymon server to send in their client data, they will receive part of this file back from the Xymon server. The configuration received by the client is then used the next time the client runs.

This method of propagating the configuration means that there is a delay of up to two poll cycles (i.e. 5-10 minutes) from a configuration change is entered into the client-local.cfg file, and until you see the result in the status messages reported by the client.

 

FILE FORMAT

The file is divided into sections, delimited by "[name]" lines. A section name can be either an operating system identifier - linux, solaris, hp-ux, aix, freebsd, openbsd, netbsd, darwin - or a hostname. When deciding which section to send to a client, Xymon will first look for a section named after the hostname of the client; if such a section does not exist, it will look for a section named by the operating system of the client. So you can configure special configurations for individual hosts, and have a default configuration for all other hosts of a certain type.

Apart from the section delimiter, the file format is free-form, or rather it is defined by the tools that make use of the configuration.

 

LOGFILE CONFIGURATION ENTRIES

A logfile configuration entry looks like this:


    log:/var/log/messages:10240

    ignore MARK

    trigger Oops

The log:FILENAME:SIZE line defines the filename of the log, and the maximum amount of data (in bytes) to send to the Xymon server. FILENAME is usually an explicit full-path filename on the client. If it is enclosed in backticks, it is a command which the Xymon client runs and each line of output from this command is then used as a filename. This allows scripting which files to monitor, e.g. if you have logfiles that are named with some sort of timestamp.

The ignore PATTERN line (optional) defines lines in the logfile which are ignored entirely, i.e. they are stripped from the logfile data before sending it to the Xymon server. It is used to remove completely unwanted "noise" entries from the logdata processed by Xymon. "PATTERN" is a regular expression.

The trigger PATTERN line (optional) is used only when there is more data in the log than the maximum size set in the "log:FILENAME:SIZE" line. The "trigger" pattern is then used to find particularly interesting lines in the logfile - these will always be sent to the Xymon server. After picking out the "trigger" lines, any remaining space up to the maximum size is filled in with the most recent entries from the logfile. "PATTERN" is a regular expression.

 

COUNTING LOGENTRIES

A special type of log-handling is possible, where the number of lines matching a regular expressions are merely counted. This is linecount:FILENAME, followed by a number of lines of the form ID:PATTERN. E.g.


    linecount:/var/log/messages

    diskerrors:I/O error.*device.*hd

    badlogins:Failed login

 

FILE CONFIGURATION ENTRIES

A file monitoring entry is used to watch the meta-data of a file: Owner, group, size, permissions, checksum etc. It looks like this:


    file:/var/log/messages[:HASH]

The file:FILENAME line defines the filename of the file to monitor. As with the "log:" entries, a filename enclosed in backticks means a command which will generate the filenames dynamically. The optional [:HASH] setting defines what type of hash to compute for the file: md5, sha1 or rmd160. By default, no hash is calculated.
NOTE: If you want to check multiple files using a wildcard, you must use a command to generate the filenames. Putting wildcards directly into the file: entry will not work.

 

DIRECTORY CONFIGURATION ENTRIES

A directory monitoring entry is used to watch the size of a directory and any sub-directories. It looks like this:


    dir:DIRECTORYNAME

The dir:DIRECTORYNAME line defines the filename of the file to monitor. As with the "log:" entries, a filename enclosed in backticks means a command which will generate the filenames dynamically. The Xymon client will run the du(1) command with the directoryname as parameter, and send the output back to the Xymon server.
NOTE: If you want to check multiple directories using a wildcard, you must use a command to generate the directory names. Putting wildcards directly into the dir: entry will not work. E.g. use something like
       dir:`find /var/log -maxdepth 1 -type d`

The "du" command used can be configured through the DU environment variable. On some systems, by default du reports data in disk blocks instead of KB (e.g. Solaris). So you may want to configure the Xymon client to use a du command which reports data in KB, e.g. by setting

    DU="du -k"
in the xymonclient.cfg file.

 

NOTES

The ability of the Xymon client to calculate file hashes and monitor those can be used for file integrity validation on a small scale. However, there is a significant processing overhead in calculating these every time the Xymon client runs, so this should not be considered a replacement for host-based intrusion detection systems such as Tripwire or AIDE.

Use of the directory monitoring on directory structures with a large number of files and/or sub-directories can be quite ressource-intensive.

 

SEE ALSO

analysis.cfg(5), xymond_client(8), xymond(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
PROPAGATION TO CLIENTS
FILE FORMAT
LOGFILE CONFIGURATION ENTRIES
COUNTING LOGENTRIES
FILE CONFIGURATION ENTRIES
DIRECTORY CONFIGURATION ENTRIES
NOTES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/xymonserver.cfg.5.html0000664000175000017500000007515311671641417022021 0ustar henrikhenrik Man page of XYMONSERVER.CFG

XYMONSERVER.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymonserver.cfg - Xymon environment variables

 

DESCRIPTION

Xymon programs use multiple environment variables beside the normal set of variables. The environment definitions are stored in the ~Xymon/server/etc/xymonserver.cfg file. Each line in this file is of the form NAME=VALUE and defines one environment variable NAME with the value VALUE.

You can also append data to existing variables, using the syntax NAME+=VALUE. VALUE is then appended to the existing value of the NAME variable. If NAME has not been defined, then this acts as if it were a normal definition.

 

ENVIRONMENT AREAS

In some cases it may be useful to have different values for an environment variable, depending on where it is used. This is possible by defining variables with an associated "area". Such definitions have the form AREA/NAME=VALUE.

E.g. to define a special setup of the XYMSERVERS variable when it is used by an application in the "management" area, you would do this:

  XYMSERVERS="127.0.0.1"            # Default definition
  management/XYMSERVERS="10.1.0.5"  # Definition in the "management" area

Areas are invoked by using the "--area" option for all tools, or via the ENVAREA setting in the tasks.cfg(5) file.

 

GENERAL SETTINGS

XYMONSERVERHOSTNAME
The fully-qualified hostname of the server that is running Xymon.

XYMONSERVERWWWNAME
The hostname used to access this servers' web-pages, used to construct URL's in the Xymon webpages. Default is the XYMONSERVERHOSTNAME.

XYMONSERVERIP
The public IP-address of the server that is running Xymon.

XYMONSERVEROS
A name identifying the operating system of the Xymon server. The known names are currently "linux", "freebsd", "solaris", "hpux", "aix" and "osf".

FQDN
If set to TRUE, Xymon will use fully-qualified hostnames throughout. If set to FALSE, hostnames are stripped of their domain-part before being processed. It is highly recommended that you keep this set to TRUE. Default: TRUE.

XYMONLOGSTATUS
Controls how the HTML page for a status log is generated. If set to DYNAMIC, the HTML logs are generated on-demand by the svcstatus.cgi(1) script. If set to STATIC, you must activate the xymond_filestore(8) module (through an entry in the tasks.cfg(5) file) to create and store the HTML logs whenever a status update is received. Setting "XYMONLOGSTATUS=STATIC" is discouraged since the I/O load on the Xymon server will increase significantly.

PINGCOLUMN
Defines the name of the column for "ping" test status. The data from the "ping" test is used internally by Xymon, so it must be defined here so all of the Xymon tools know which column to watch for this data. The default setting is PINGCOLUMN=conn.

INFOCOLUMN
Defines the name of the column for the "info" pages.

TRENDSCOLUMN
Defines the name of the column for the RRD graph pages.

RRDHEIGHT
The default height (in pixels) of the RRD graph images. Default: 120 pixels.

RRDWIDTH
The default width (in pixels) of the RRD graph images. Default: 576 pixels.

TRENDSECONDS
The graphs on the "trends" page show data for the past TRENDSECONDS seconds. Default: 172800 seconds, i.e. 48 hours.

HTMLCONTENTTYPE
The Content-type reported by the CGI scripts that generate web pages. By default, this it "text/html". If you have on-line help texts in character sets other than the ISO-8859-1 (western european) character set, it may be necessary to modify this to include a character set. E.g.

   HTMLCONTENTTYPE="text/html; charset=euc-jp"
for a Japanese character sets. Note: Some webservers will automatically add this, if configured to do so.

HOLIDAYS
Defines the default set of holidays used if there is no "holidays" tag for a host in the hosts.cfg file. Holiday sets are defined in the holidays.cfg(5) file. If not defined, only the default holidays (those defined outside a named holiday set) will be considered as holidays.

WEEKSTART
Defines which day is the first day of the week. Set to "0" for Sunday, "1" for Monday. Default: 1 (Monday).

XYMONBODYHEADER
Text that is included in the Xymon web pages at the location specified by ~xymon/server/web/*_header templates. If this is set to a value beginning with "file:", then the contents of the specified file is included. Default: "file:$XYMONHOME/etc/xymonmenu.cfg"

XYMONBODYFOOTER
Text that is included in the Xymon web pages at the location specified by ~xymon/server/web/*_footer templates. If this is set to a value beginning with "file:", then the contents of the specified file is included. Default: Empty.

XYMONBODYMENUCSS
URL for the Xymon menu CSS file. Default: "$XYMONMENUSKIN/xymonmenu.css"

HOSTPOPUP
Determines what is used as the host comment on the webpages. The comment by default appears as a pop-up when the mouse hovers over the hostname, and is also shown on the "info" status page. This setting must be one or more of the letters "C" (COMMENT), "D" (DESCRIPTION) or "I" (IP-address). Including "C" uses the COMMENT setting for the host, including "D" uses the DESCR setting for the host, and "I" uses the IP-address of the host. If more than one of these is set, then COMMENT takes precedence over DESCR which again has precence over IP. Note that DESCR and IP only appear in pop-up windows (if enabled), whereas the COMMENT is always used - if pop-up's have been disabled, then the COMMENT value is displayed next to the hostname on the webpages. Default: CDI

STATUSLIFETIME
The number of minutes that a status is considered valid after an update. After this time elapses, the status will go purple. Default: 30 minutes

 

DIRECTORIES

XYMONSERVERROOT
The top-level directory for the Xymon installation. The default is the home-directory for the user running Xymon.

XYMONSERVERLOGS
The directory for the Xymon's own logfiles (NOT the status-logs from the monitored hosts).

XYMONHOME
The Xymon server directory, where programs and configurations are kept. Default: $XYMONSERVERROOT/server/ .

XYMONTMP
Directory used for temporary files. Default: $XYMONHOME/tmp/

XYMONWWWDIR
Directory for Xymon webfiles. The $XYMONWEB URL must map to this directory. Default: $XYMONHOME/www/

XYMONNOTESDIR
Directory for Xymon notes-files. The $XYMONNOTESSKIN URL must map to this directory. Default: $XYMONHOME/www/notes/

XYMONREPDIR
Directory for Xymon availability reports. The $XYMONREPURL URL must map to this directory. Note also that your webserver must have write-access to this directory, if you want to use the report.cgi(1) CGI script to generate reports on-demand. Default: $XYMONHOME/www/rep/

XYMONSNAPDIR
Directory for Xymon snapshots. The $XYMONSNAPURL URL must map to this directory. Note also that your webserver must have write-access to this directory, if you want to use the snapshot.cgi(1) CGI script to generate snapshots on-demand. Default: $XYMONHOME/www/snap/

XYMONVAR
Directory for all data stored about the monitored items. Default: $XYMONSERVERROOT/data/

XYMONRAWSTATUSDIR
Directory for storing the raw status-logs. Not used unless "xymond_filestore --status" is running, which is discouraged since it increases the load on the Xymon server significantly. Default: $XYMONVAR/logs/

XYMONHTMLSTATUSDIR
Directory for storing HTML status-logs. Not used unless "xymond_filestore --status --html" is running, which is discouraged since it increases the load on the Xymon server significantly. Default: $XYMONHOME/www/html/

XYMONHISTDIR
Directory for storing the history of monitored items. Default: $XYMONVAR/hist/

XYMONHISTLOGS
Directory for storing the detailed status-log of historical events. Default: $XYMONVAR/histlogs/

XYMONACKDIR
Directory for storing information about alerts that have been acknowledged. Default: $XYMONVAR/acks/

XYMONDISABLEDDIR
Directory for storing information about tests that have been disabled. Default: $XYMONVAR/disabled/

XYMONDATADIR
Directory for storing incoming "data" messages. Default: $XYMONVAR/data/

XYMONRRDS
Top-level directory for storing RRD files (the databases with trend-information used to generate graphs). Default: $XYMONVAR/rrd/

CLIENTLOGS
Directory for storing the data sent by a Xymon client around the time a status changes to a warning (yellow) or critical (red) state. Used by the xymond_hostdata(8) module. Default: $XYMONVAR/hostdata/

XYMONCGILOGDIR
Directory where debug output from CGI applications are stored. If not specified, it defaults to $XYMONSERVERLOGS, but this is often a directory that is not writable by the userid running the CGI applications. It is therefore recommended when using "--debug" on CGI applications that you create a separate directory owned by the user running your webserver, and point XYMONCGILOGDIR to this directory.

 

SYSTEM FILES

HOSTSCFG
Full path to the Xymon hosts.cfg(5) configuration file. Default: $XYMONHOME/etc/hosts.cfg.

XYMON
Full path to the xymon(1) client program. Default: $XYMONHOME/bin/xymon.

XYMONGEN
Full path to the xymongen(1) webpage generator program. Default: $XYMONHOME/bin/xymongen.

 

URLS

XYMONSERVERWWWURL
The root URL for the Xymon webpages, without the hostname. This URL must be mapped to the ~/server/www/ directory in your webserver configuration. See the sample Apache configuration in ~/server/etc/xymon-apache.conf.

XYMONSERVERCGIURL
The root URL for the Xymon CGI-scripts, without the hostname. This directory must be mapped to the ~/cgi-bin/ directory in your webserver configuration, and must be flagged as holding executable scripts. See the sample Apache configuration in ~/server/etc/xymon-apache.conf.

XYMONWEBHOST
Initial part of the Xymon URL, including just the protocol and the hostname, e.g. "http://www.foo.com"

XYMONWEBHOSTURL
Prefix for all of the static Xymon webpages, e.g. "http://www.foo.com/xymon"

XYMONWEBHTMLLOGS
URL prefix for the static HTML status-logs generated when XYMONLOGSTATUS=STATIC. Note that this setting is discouraged so this setting should not be used.

XYMONWEB
URL prefix (without hostname) of the Xymon webpages. E.g. "/xymon".

XYMONSKIN
URL prefix (without hostname) of the Xymon graphics. E.g. "/xymon/gifs".

XYMONHELPSKIN
URL prefix (without hostname) of the Xymon on-line help files. E.g "/xymon/help".

XYMONMENUSKIN
URL prefix (without hostname) of the Xymon menu files. E.g "/xymon/menu".

XYMONNOTESSKIN
URL prefix (without hostname) of the Xymon on-line notes files. E.g "/xymon/notes".

XYMONREPURL
URL prefix (without hostname) of the Xymon availability reports. E.g. "/xymon/rep".

XYMONSNAPURL
URL prefix (without hostname) of the Xymon snapshots. E.g. "/xymon/snap".

XYMONWAP
URL prefix (without hostname) of the Xymon WAP/WML files. E.g. "/xymon/wml".

CGIBINURL
URL prefix (without hostname) of the Xymon CGI-scripts. Default: $XYMONSERVERCGIURL .

COLUMNDOCURL
Format string used to build a link to the documentation for a column heading. Default: "$CGIBINURL/columndoc.sh?%s", which causes links to use the columndoc.sh(1) script to document a column.

HOSTDOCURL
Format string used to build a link to the documentation for a host. If not set, then Xymon falls back to scanning the XYMONNOTES directory for files matching the hostname, or the hostname together with a common filename extension (.php, .html, .doc and so on). If set, this string becomes a formatting string for the documentation URL. E.g. for the host "myhost", a setting of HOSTDOCURL="/docs/%s.php" will generate a link to "/docs/myhost.php". Default: Not set, so host documentation will be retrieved from the XYMONNOTES directory.

 

SETTINGS FOR SENDING MESSAGES TO XYMON

XYMSRV
The IP-address used to contact the xymond(8) service. Used by clients and the tools that perform network tests. Default: $XYMONSERVERIP

XYMSERVERS
List of IP-adresses. Clients and network test tools will try to send status reports to a Xymon server running on each of these adresses. This setting is only used if XYMSRV=0.0.0.0.

XYMONDPORT
The portnumber for used to contact the xymond(8) service. Used by clients and the tools that perform network tests. Default: 1984.

MAXMSGSPERCOMBO
The maximum number of status messages to combine into one combo message. Default: 100.

SLEEPBETWEENMSGS
Length of a pause introduced between each successive transmission of a combo-message by xymonnet, in microseconds. Default: 0 (send messages as quickly as possible).

 

XYMOND SETTINGS

ALERTCOLORS
Comma-separated list of the colors that may trigger an alert-message. The default is "red,yellow,purple". Note that alerts may further be generated or suppresed based on the configuration in the alerts.cfg(5) file.

OKCOLORS
Comma-separated list of the colors that may trigger a recovery-message. The default is "green,clear,blue".

ALERTREPEAT
How often alerts get repeated while a status is in an alert state. This is the default setting, which may be changed in the alerts.cfg(5) file.

MAXMSG_STATUS
The maximum size of a "status" message in kB, default: 256. Status messages are the ones that end up as columns on the web display. The default size should be adequate in most cases, but some extension scripts can generate very large status messages - close to 1024 kB. You should only change this if you see messages in the xymond log file about status messages being truncated.

MAXMSG_CLIENT
The maximum size of a "client" message in kB, default: 512. "client" messages are generated by the Xymon client, and often include large process-listings. You should only change this if you see messages in the xymond log file about client messages being truncated.

MAXMSG_DATA
The maximum size of a "data" message in kB, default: 256. "data" messages are typically used for client reports of e.g. netstat or vmstat data. You should only change this setting if you see messages in the xymond log file about data messages being truncated.

MAXMSG_NOTES
The maximum size of a "notes" message in kB, default: 256. "notes" messages provide a way for uploading documentation about a host to Xymon; it is not enabled by default. If you want to upload large documents, you may need to change this setting.

MAXMSG_STACHG
The maximum size of a "status change" message in kB, default: Current value of the MAXMSG_STATUS setting. Status-change messages occur when a status changes color. There is no reason to change this setting.

MAXMSG_PAGE
The maximum size of a "page" message in kB, default: Current value of the MAXMSG_STATUS setting. "page" messages are alerts, and include the status message that triggers the alert. There is no reason to change this setting.

MAXMSG_ENADIS
The maximum size of an "enadis" message in kB, default: 32. "enadis" are small messages used when enabling or disabling hosts and tests, so the default size should be adequate.

MAXMSG_CLICHG
The maximum size of a "client change" message in kB, default: Current value of the MAXMSG_CLIENT setting. Client-change messages occur when a status changes color to one of the alert-colors, usually red, yellow and purple. There is no reason to change this setting.

MAXMSG_USER
The maximum size of a "user" message in kB, default: 128. "user" messages are for communication between custom Xymon modules you have installed, it is not used directly by Xymon.

 

XYMOND_HISTORY SETTINGS

XYMONALLHISTLOG
If set to TRUE, xymond_history(8) will update the $XYMONHISTDIR/allevents file logging all changes to a status. The allevents file is used by the eventlog.cgi(1) tool to show the list of recent events on the "All non-green" webpage.

XYMONHOSTHISTLOG
If set to TRUE, xymond_history(8) will update the host-specific eventlog that keeps record of all status changes for a host. This logfile is not used by any Xymon tool.

SAVESTATUSLOG
If set to TRUE, xymond_history(8) will save historical detailed status-logs to the $XYMONHISTLOGS directory.

 

XYMOND_ALERT SETTINGS

MAIL
Command used to send alerts via e-mail, including a "Subject:" header in the mail. Default: "mail -s"

MAILC
Command used to send alerts via e-mail in a form that does not have a "Subject" in the mail. Default: "mail"

SVCCODES
Maps status-columns to numeric service-codes. The numeric codes are used when sending an alert using a script, where the numeric code of the service is provided in the BBSVCNUM variable.

 

XYMOND_RRD SETTINGS

TEST2RRD
List of "COLUMNNAME[=RRDSERVICE]" settings, that define which status- and data-messages have a corresponding RRD graph. You will normally not need to modify this, unless you have added a custom TCP-based test to the protocols.cfg file, and want to collect data about the response-time, OR if you are using the xymond_rrd(8) external script mechanism to collect data from custom tests. Note: All TCP tests are automatically added.

This is also used by the svcstatus.cgi(1) script to determine if the detailed status view of a test should include a graph.

GRAPHS
List of the RRD databases, that should be shown as a graph on the "trends" column.

NORRDDISKS
This is used to disable the tracking of certain filesystems. By default all filesystems reported by a client are tracked. In some cases you may want to disable this for certain filesystems, e.g. database filesystems since they are always completely full. This setting is a regular expression that is matched against the filesystem name (the Unix mount-point, or the Windows disk-letter) - if the filesystem name matches this expression, then it will not be tracked by Xymon.
Note: Setting this does not affect filesystems that are already being tracked by Xymon - to remove them, you must remove the RRD files for the unwanted filesystems from the ~xymon/data/rrd/HOSTNAME/ directory.

RRDDISKS
This is used to enable tracking of only selected filesystems (see the NORRDDISKS setting above). By default all filesystems are being tracked, setting this changes that default so that only those filesystems that match this pattern will be tracked.

 

XYMONNET NETWORK TEST SETTINGS

XYMONNETWORK
If this variable is defined, then only the hosts that have been tagged with "NET:$XYMONNETWORK" will be tested by the xymonnet tool.

CONNTEST
If set to TRUE, the connectivity (ping) test will be performed.

IPTEST_2_CLEAR_ON_FAILED_CONN
If set to TRUE, then failing network tests go CLEAR if the conn-test fails.

NONETPAGE
List of network services (separated with <space>) that should go yellow upon failure instead of red.

XYMONROUTERTEXT
When using the "router" or "depends" tags for a host, a failure status will include text that an "Intermediate router is down". With todays network topologies, the router could be a switch or another network device; if you define this environment variable the word "router" will be replaced with whatever you put into the variable. So to inform the users that an intermediate switch or router is down, use XYMONROUTERTEXT="switch or router". This can also be set on a per-host basis using the "DESCR:hosttype:description" tag in the hosts.cfg(5) file.

NETFAILTEXT
When a network test fails, the status message reports "SERVICENAME not OK". The "not OK" message can be changed via this variable, e.g. you can change it to "FAILED" or customize it as you like.

FPING
The command used to run the xymonping(1) tool for the connectivity test. (The name FPING is due to the fact that the "fping" utility was used until Xymon version 4.2). This may include suid-root wrappers and xymonping options. Default: "xymonping"

TRACEROUTE
Defines the location of the "traceroute" tool and any options needed to run it. traceroute it used by the connectivity test when the ping test fails; if requested via the "trace" tag, the TRACEROUTE command is executed to try to determine the point in the network that is causing the problem. By default the command executed is "traceroute -n -q 2 -w 2 -m 15" (no DNS lookup, max. 2 probes, wait 2 seconds per hop, max 15 hops).

If you have the mtr(8) tool installed - available from http://www.bitwizard.nl/mtr/ - I strongly recommend using this instead. The recommended setting for mtr is "/usr/sbin/mtr -c 2 -n --report" (the exact path to the mtr utility may be different on your system). Note that mtr needs to be installed suid-root on most systems.

NTPDATE
Defines the ntpdate(1) program used for the "ntp" test. Default: "ntpdate"

RPCINFO
Defines the rpcinfo(8) program used for "rpc" tests. Default: "rpcinfo"

 

XYMONGEN WEBPAGE GENERATOR SETTINGS

XYMONLOGO
HTML code that is inserted on all standard headers. The default is to add the text "Xymon" in the upper-left corner of the page, but you can easily replace this with e.g. a company logo. If you do, I suggest that you keep it at about 30-35 pixels high, and 100-150 pixels wide.

XYMONPAGELOCAL
The string "Pages hosted locally" that appears above all of the pages linked from the main Xymon webpage.

XYMONPAGESUBLOCAL
The string "Subpages hosted locally" that appears above all of the sub-pages linked from pages below the main Xymon webpage.

XYMONPAGEREMOTE
The string "Remote status display" that appears about the summary statuses displayed on the min Xymon webpage.

XYMONPAGETITLE
HTML tags designed to go in a <FONT> tag, to choose the font for titles of the webpages.

XYMONPAGEROWFONT
HTML tags designed to go in a <FONT> tag, to choose the font for row headings (hostnames) on the webpages.

XYMONPAGECOLFONT
HTML tags designed to go in a <FONT> tag, to chose the font for column headings (test names) on the webpages.

XYMONPAGEACKFONT
HTML tags designed to go in a <FONT> tag, to chose the font for the acknowledgement text displayed on the status-log HTML page for an acknowledged status.

ACKUNTILMSG
When displaying the detailed status of an acknowledged test, Xymon will include the time that the acknowledge expires using the print-format defined in this setting. You can define the timeformat using the controls in your systems strftime(3) routine, and add the text suitable for your setup.

XYMONDATEFORMAT
On webpages generated by xymongen, the default header includes the current date and time. Normally this looks like "Tue Aug 24 21:59:47 2004". The XYMONDATEFORMAT controls the format of this timestamp - you can define the format using the controls in the strftime(3) routine. E.g. to have it show up as "2004-08-24 21:59:47 +0200" you would set XYMONDATEFORMAT="%Y-%m-%d %H:%M:%S %z"

HOLIDAYFORMAT
How holiday dates are displayed. The default is "%d/%m" which show the day and month. American users may want to change this to "%m/%d" to suit their preferred date-display style. This is a formatting string for the system strftime(3) routine, so any controls available for this routine may be used.

XYMONPAGECOLREPEAT
Inspired by Jeff Stoner's col_repeat_patch.tgz patch, this defines the maximum number of rows before repeating the column headings on a webpage. This sets the default value for the xymongen(1) "--maxrows" option; if the command-line option is also specifed, then it overrides this environment variable. Note that unlike Jeff's patch, xymongen implements this for both the "All non-green" page and all other pages (xymon.html, subpages, critical.html).

SUMMARY_SET_BKG
If set to TRUE, then summaries will affect the color of the main Xymon webpage. Default: FALSE.

DOTHEIGHT
The height (in pixels) of the icons showing the color of a status. Default: 16, which matches the default icons.

DOTWIDTH
The width (in pixels) of the icons showing the color of a status. Default: 16, which matches the default icons.

CLIENTSVCS
List of the status logs fed by data from the Xymon client. These status logs will - if there are Xymon client data available for the host - include a link to the raw data sent by the client. Default: cpu,disk,memory,procs,svcs.

XYMONRSSTITLE
If defined, this is the title of the RSS/RDF documents generated when xymongen(1) is invoked with the "--rss" option. The default value is "Xymon Alerts".

WMLMAXCHARS
Maximum size of a WAP/WML output "card" when generating these. Default: 1500.

XYMONNONGREENEXT
List of scripts to run as extensions to the "All non-green" page. Note that two scripts, "eventlog.sh" and "acklog.sh" are handled specially: They are handled internally by xymongen, but the script names must be listed in this variable for this function to be enabled.

XYMONHISTEXT
List of scripts to run as extensions to a history page.

XYMONREPWARN
Default threshold for listing the availability as "critical" (red) when generating the availability report. This can be set on a per-host basis with the WARNPCT setting in hosts.cfg(5). Default: 97 (percent)

XYMONGENREPOPTS
Default xymongen options used for reports. This will typically include such options as "--subpagecolumns", and also "--ignorecolumns" if you wish to exclude certain tests from reports by default.

XYMONGENSNAPOPTS
Default xymongen options used by snapshots. This should be identical to the options you normally used when building Xymon webpages.

 

FILES

~xymon/server/etc/xymonserver.cfg

 

SEE ALSO

xymon(7)


 

Index

NAME
DESCRIPTION
ENVIRONMENT AREAS
GENERAL SETTINGS
DIRECTORIES
SYSTEM FILES
URLS
SETTINGS FOR SENDING MESSAGES TO XYMON
XYMOND SETTINGS
XYMOND_HISTORY SETTINGS
XYMOND_ALERT SETTINGS
XYMOND_RRD SETTINGS
XYMONNET NETWORK TEST SETTINGS
XYMONGEN WEBPAGE GENERATOR SETTINGS
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/critical.cfg.5.html0000664000175000017500000000373411671641417021206 0ustar henrikhenrik Man page of CRITICAL.CFG

CRITICAL.CFG

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

critical.cfg - Configuration of the showgraph CGI

 

SYNOPSIS

$XYMONHOME/etc/critical.cfg

 

DESCRIPTION

critical.cgi(1) uses the configuration file $XYMONHOME/etc/critical.cfg to determine which of the statuses currently in a red or yellow state should appear on the Critical Systems view.

This file should not be edited manually. It is maintained by the criticaleditor.cgi(1) utility via a web-based frontend.

 

FILE PERMISSIONS

Since the file is maintained by a web front-end tool, the userid running Web CGI scripts must have write-permission to the file. Typically, this means it must have a group-ownership matching your webserver userid, and group-write permissions (mode 664).

When editing the file with the web front-end, a backup file is created. Therefore the critical.cfg.bak file should have identical permissions.

 

SEE ALSO

criticalview.cgi(1), criticaleditor.cgi(1)


 

Index

NAME
SYNOPSIS
DESCRIPTION
FILE PERMISSIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man5/xymon-xmh.5.html0000664000175000017500000001416011671641417020615 0ustar henrikhenrik Man page of XYMON-XMH

XYMON-XMH

Section: File Formats (5)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

Xymon XMH variables - Configuration items available online

 

DESCRIPTION

The hosts.cfg(5) file is the most important configuration file for all of the Xymon programs. This file contains the full list of all the systems monitored by Xymon, including the set of tests and other configuration items stored for each host.

Although the file is in text format and can be searched using tools like xymongrep(1) it can be difficult to pick out specific fields from the configuration due to quoting and other text parsing issues. So Xymon allows for querying this information directly from the xymond daemon via the "xymondboard" command. So the information can be provided together with the current status of a host and/or test. This is done by adding a "fields" definition to the "xymondboard" command.

 

XMH items

Except where specified below, all items return the text of a particular setting from the hosts.cfg file, or an empty string if the setting has not been set for the host that is queried.

XMH_ALLPAGEPATHS
List of all pages where the host is found, using the filename hierarchy page path.

XMH_BROWSER
Value of the "browser" tag.

XMH_CLASS
The host "class" (if reported by a Xymon client), or the value of the CLASS tag.

XMH_CLIENTALIAS
Value of the CLIENT tag.

XMH_COMMENT
Value of the COMMENT tag.

XMH_COMPACT
Value of the COMPACT tag.

XMH_DEPENDS
Value of the DEPENDS tag.

XMH_DESCRIPTION
Value of the DESCR tag.

XMH_DGNAME
The text string from the hosts.cfg "group" definition (group, group-only, group-except) in which the host is defined.

XMH_DISPLAYNAME
Value of the NAME tag.

XMH_DOCURL
Value of the DOC tag.

XMH_DOWNTIME
Value of the DOWNTIME tag.

XMH_FLAG_DIALUP
Value of the "dialup" tag.

XMH_FLAG_HIDEHTTP
Value of the HIDEHTTP tag.

XMH_FLAG_LDAPFAILYELLOW
Value of the "ldapyellowfail" tag.

XMH_FLAG_MULTIHOMED
Value of the MULTIHOMED tag.

XMH_FLAG_NOBB2
Value of the "nobb2" tag (deprecated, use NONONGREEN instead).

XMH_FLAG_NOCLEAR
Value of the NOCLEAR tag.

XMH_FLAG_NOCONN
Value of the "noconn" tag.

XMH_FLAG_NODISP
Value of the "nodisp" tag.

XMH_FLAG_NOINFO
Value of the "noinfo" atg.

XMH_FLAG_NONONGREEN
Value of the "nonongreen" tag.

XMH_FLAG_NOPING
Value of the "noping" tag.

XMH_FLAG_NOSSLCERT
Value of the "nosslcert" tag.

XMH_FLAG_NOTRACE
Value of the "notrace" tag.

XMH_FLAG_NOTRENDS
Value of the "notrends" tag.

XMH_FLAG_PREFER
Value of the "prefer" tag.

XMH_FLAG_PULLDATA
Value of the PULLDATA tag.

XMH_FLAG_TESTIP
Value of the "testip" tag.

XMH_FLAG_TRACE
Value of the "trace" tag.

XMH_GROUPID
Number of the group where the host is listed - first group is 0. If a host is present on multiple pages, this is the number of the group for the first page where the host is found.

XMH_HOLIDAYS
Value of the "holidays" tag.

XMH_HOSTNAME
The name of the host.

XMH_IP
The IP-address of the host (as specified in hosts.cfg).

XMH_LDAPLOGIN
Value of the "ldaplogin" tag.

XMH_NET
Value of the NET tag.

XMH_NK
Value of the NK tag (deprecated).

XMH_NKTIME
Value of the NKTIME tag (deprecated).

XMH_NOCOLUMNS
Value of the NOCOLUMNS tag.

XMH_NOPROP
Value of the NOPROP tag.

XMH_NOPROPACK
Value of the NOPROPACK tag.

XMH_NOPROPPURPLE
Value of the NOPROPPURPLE tag.

XMH_NOPROPRED
Value of the NOPROPRED tag.

XMH_NOPROPYELLOW
Value of the NOPROPYELLOW tag.

XMH_NOTAFTER
Value of the NOTAFTER tag.

XMH_NOTBEFORE
Value of the NOTBEFORE tag.

XMH_OS
The host operating system (if reported by a Xymon client), or the value of the OS tag.

XMH_PAGEINDEX
Index of the host on the page where it is shown, first host has index 0.

XMH_PAGENAME
Name of the page where the host is shown (see also XMH_PAGEPATH).

XMH_PAGEPATH
File path to the page where the host is shown.

XMH_PAGEPATHTITLE
Title of the full path to the page where the host is shown.

XMH_PAGETITLE
Title of the page where the host is shown.

XMH_RAW
All configuration settings for the host. Settings are separated by a pipe-sign.

XMH_REPORTTIME
Value of the REPORTTIME tag.

XMH_SSLDAYS
Value of the "ssldays" tag.

XMH_SSLMINBITS
Value of the "sslbits" tag.

XMH_TRENDS
Value of the TRENDS tag.

XMH_WARNPCT
Value of the WARNPCT tag.

XMH_WARNSTOPS
Value of the WARNSTOPS tag.

XMH_WML
Value of the WML tag.

 

SEE ALSO

xymon(1), hosts.cfg(5), xymongrep(1)


 

Index

NAME
DESCRIPTION
XMH items
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man7/0000775000175000017500000000000011671641715015621 5ustar henrikhenrikxymon-4.3.7/docs/manpages/man7/xymon.7.html0000664000175000017500000007032311671641417020032 0ustar henrikhenrik Man page of XYMON

XYMON

Section: Environments, Tables, and Troff Macros (7)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

Xymon - Introduction to Xymon

 

OVERVIEW

Xymon is a tool for monitoring the health of your networked servers and the applications running on them. It provides a simple, intuitive way of checking the health of your systems from a web browser, and can also alert you to any problems that arise through alarms sent as e-mail, SMS messages, via a pager or by other means.

Xymon is Open Source software, licensed under the GNU GPL. This means that you are free to use Xymon as much as you like, and you are free to re-distribute it and change it to suit your specific needs. However, if you change it then you must make your changes available to others on the same terms that you received Xymon originally. See the file COPYING in the Xymon source-archive for details.

Xymon was called "Hobbit" until November 2008, when it was renamed to Xymon. This was done because the name "Hobbit" is trademarked.

Xymon initially began life as an enhancement to Big Brother called "bbgen". Over a period of 5 years, Xymon has evolved from a small add-on to a full-fledged monitoring system with capabilities far exceeding what was in the original Big Brother package. Xymon does still maintain some compatibility with Big Brother, so it is possible to migrate from Big Brother to Xymon without too much trouble.

Migrating to Xymon will give you a significant performance boost, and provide you with much more advanced monitoring. The Xymon tools are designed for installations that need to monitor a large number of hosts, with very little overhead on the monitoring server. Monitoring of thousands of hosts with a single Xymon server is possible - it was developed to handle just this task.

 

FEATURES

These are some of the core features in Xymon:

Monitoring of hosts and networks
Xymon collects information about your systems in two ways: From querying network services (Web, LDAP, DNS, Mail etc.), or from scripts that run either on the Xymon server or on the systems you monitor. The Xymon package includes a Xymon client which you can install on the servers you monitor; it collects data about the CPU-load, disk- and memory-utilization, log files, network ports in use, file- and directory-information and more. All of the information is stored inside Xymon, and you can define conditions that result in alerts, e.g. if a network service stops responding, or a disk fills up.

Centralized configuration
All configuration of Xymon is done on the Xymon server. Even when monitoring hundreds or thousands of hosts, you can control their configuration centrally on the Xymon server - so there is no need for you to login to a system just to change e.g. which processes are monitored.

Works on all major platforms
The Xymon server works on all Unix-like systems, including Linux, Solaris, FreeBSD, AIX, HP-UX and others. The Xymon client supports all major Unix platforms, and there are other Open Source projects - e.g. BBWin, see http://bbwin.sourceforge.net/ - providing support for Microsoft Windows based systems.

A simple, intuitive web-based front-end
"Green is good, red is bad". Using the Xymon web pages is as simple as that. The hosts you monitor can be grouped together in a way that makes sense in your organisation and presented in a tree-structure. The web pages use many techniques to convey information about the monitored systems, e.g. different icons can be used for recently changed statuses; links to sub-pages can be listed in multiple columns; different icons can be used for dial-up-tests or reverse-tests; selected columns can be dropped or unconditionally included on the web pages to eliminate unwanted information, or always include certain information; user-friendly names can be shown for hosts regardless of their true hostname. You can also have automatic links to on-line documentation, so information about your critical systems is just a click away.

Integrated trend analysis, historical data and SLA reporting
Xymon stores trend- and availability-information about everything it monitors. So if you need to look at how your systems behave over time, Xymon has all of the information you need: Whether it is response times of your web pages during peak hours, the CPU utilization over the past 4 weeks, or what the availability of a site was compared to the SLA - it's all there inside Xymon. All measurements are tracked and made available in time-based graphs.

When you need to drill down into events that have occurred, Xymon provides a powerful tool for viewing the event history for each status log, with overviews of when problems have occurred during the past and easy-to-use zoom-in on the event.

For SLA reporting, You can configure planned downtime, agreed service availability level, service availability time and have Xymon generate availability reports directly showing the actual availability measured against the agreed SLA. Such reports of service availability can be generated on-the-fly, or pre-generated e.g. for monthly reporting.

Role-based views
You can have multiple different views of the same hosts for different parts of the organisation, e.g. one view for the hardware group, and another view for the webmasters - all of them fed by the same test tools.

If you have a dedicated Network Operations Center, you can configure precisely which alerts will appear on their monitors - e.g. a simple anomaly in the system log file need not trigger a call to 3rd-level support at 2 AM, but if the on-line shop goes down you do want someone to respond immediately. So you put the web-check for the on-line shop on the NOC monitor page, and leave out the log-file check.

Also for the techies
The Xymon user-interface is simple, but engineers will also find lots of relevant information. E.g. the data that clients report to Xymon contain the raw output from a number of system commands. That information is available directly in Xymon, so an administrator no longer needs to login to a server to get an overview of how it is behaving - the very commands they would normally run have already been performed, and the results are on-line in Xymon.

Easy to adapt to your needs
Xymon includes a lot of tests in the core package, but there will always be something specific to your setup that you would like to watch. Xymon allows you to write test scripts in your favorite scripting language and have the results show up as regular status columns in Xymon. You can trigger alerts from these, and even track trends in graphs just by a simple configuration setting.

Real network service tests
The network test tool knows how to test most commonly used protocols, including HTTP, SMTP (e-mail), DNS, LDAP (directory services), and many more. When checking websites, it is possible to not only check that the web server is responding, but also that the response looks correct by matching the response against a pre-defined pattern or a check-sum. So you can test that a network service is really working and supplying the data you expect - not just that the service is running.

Protocols that use SSL encryption such as https web sites are fully supported, and while checking such services the network tester will automatically run a check of the validity of the SSL server certificate, and warn about certificates that are about to expire.

Highly configurable alerts
You want to know when something breaks. But you don't want to get flooded with alerts all the time. Xymon lets you define several criteria for when to send out an alert, so you only get alerts when there is really something that needs your attention right away. While you are handling an incident, you can tell Xymon about it so it stops sending more alerts, and so that everyone else can check with Xymon and know that the problem is being taken care of.

Combined super-tests and test inter-dependencies
If a single test is not enough, combination tests can be defined that combine the result of several tests to a single status-report. So if you need to monitor that at least 3 out of 5 servers are running at any time, Xymon can do that for you and generate the necessary availability report.

Tests can also be configured to depend on each other, so that when a critical router goes down you will get alerts only for the router - and not from the 200 hosts behind the router.

 

SECURITY

All of the Xymon server tools run under an unprivileged user account. A single program - the xymonping(1) network connectivity tester - must be installed setuid-root, but has been written so that it drops all root privileges immediately after performing the operation that requires root privileges.

It is recommended that you setup a dedicated account for Xymon.

Communications between the Xymon server and Xymon clients use the Big Brother TCP port 1984. If the Xymon server is located behind a firewall, it must allow for inbound connections to the Xymon server on tcp port 1984. Normally, Xymon clients - i.e. the servers you are monitoring - must be permitted to connect to the Xymon server on this port. However, if that is not possible due to firewall policies, then Xymon includes the xymonfetch(8) and msgcache(8) tools to allows for a pull-style way of collecting data, where it is the Xymon server that initiates connections to the clients.

The Xymon web pages are dynamically generated through CGI programs.

Access to the Xymon web pages is controlled through your web server access controls, e.g. you can require a login through some form of HTTP authentication.

 

DEMONSTRATION SITE

A site running this software can be seen at http://www.xymon.com/

 

PREREQUISITES AND INSTALLATION

You will need a Unix-like system (Linux, Solaris, HP-UX, AIX, FreeBSD, Mac OS X or similar) with a web server installed. You will also need a C compiler and some additional libraries, but many systems come with the required development tools and libraries pre-installed. The required libraries are:

RRDtool This library is used to store and present trend-data. It is required.

libpcre This library is used for advanced pattern-matching of text strings in configuration files. This library is required.

OpenSSL This library is used for communication with SSL-enabled network services. Although optional, it is recommended that you install this for Xymon since many network tests do use SSL.

OpenLDAP This library is used for testing LDAP servers. Use of this is optional.

For more detailed information about Xymon system requirements and how to install Xymon, refer to the on-line documentation "Installing Xymon" available from the Xymon web server (via the "Help" menu), or from the "docs/install.html" file in the Xymon source archive.

 

SUPPORT and MAILING LISTS

xymon@xymon.com is an open mailing list for discussions about Xymon. If you would like to participate, send an e-mail to xymon-subscribe@xymon.com to join the list, or visit http://lists.xymon.com/mailman/listinfo/xymon .

An archive of the mailing list is available at http://lists.xymon.com/archive/

If you just want to be notified of new releases of Xymon, please subscribe to the xymon-announce mailing list. This is a moderated list, used only for announcing new Xymon releases. To be added to the list, send an e-mail to xymon-announce-subscribe@xymon.com or visit http://lists.xymon.com/mailman/listinfo/xymon-announce .

 

XYMON SERVER TOOLS

These tools implement the core functionality of the Xymon server:

xymond(8) is the core daemon that collects all reports about the status of your hosts. It uses a number of helper modules to implement certain tasks such as updating log files and sending out alerts: xymond_client, xymond_history, xymond_alert and xymond_rrd. There is also a xymond_filestore module for compatibility with Big Brother.

xymond_channel(8) Implements the communication between the Xymon daemon and the other Xymon server modules.

xymond_history(8) Stores historical data about the things that Xymon monitors.

xymond_rrd(8) Stores trend data, which is used to generate graphs of the data monitored by Xymon.

xymond_alert(8) handles alerts. When a status changes to a critical state, this module decides if an alert should be sent out, and to whom.

xymond_client(8) handles data collected by the Xymon clients, analyzes the data and feeds back several status updates to Xymon to build the view of the client status.

xymond_hostdata(8) stores historical client data when something breaks. E.g. when a web page stops responding xymond_hostdata will save the latest client data, so that you can use this to view a snapshot of how the system state was just prior to it failing.

 

XYMON NETWORK TEST TOOLS

These tools are used on servers that execute tests of network services.

xymonping(1) performs network connectivity (ping) tests.

xymonnet(1) runs the network service tests.

xymonnet-again.sh(1) is an extension script for re-doing failed network tests with a higher frequency than the normal network tests. This allows Xymon to pick up the recovery of a network service as soon as it happens, resulting in less downtime being recorded.

 

XYMON TOOLS HANDLING THE WEB USER-INTERFACE

These tools take care of generating and updating the various Xymon web-pages.

xymongen(1) takes care of updating the Xymon web pages.

svcstatus.cgi(1) This CGI program generates an HTML view of a single status log. It is used to present the Xymon status-logs.

showgraph.cgi(1) This CGI program generates graphs of the trend-data collected by Xymon.

hostgraphs.cgi(1) When you want to combine multiple graphs into one, this CGI lets you combine graphs so you can e.g. compare the load on all of the nodes in your server farm.

criticalview.cgi(1) Generates the Critical Systems view, based on the currently critical systems and the configuration of what systems and services you want to monitor when.

history.cgi(1) This CGI program generates a web page with the most recent history of a particular host+service combination.

eventlog.cgi(1) This CGI lets you view a log of events that have happened over a period of time, for a single host or test, or for multiple systems.

ack.cgi(1) This CGI program allows a user to acknowledge an alert he received from Xymon about a host that is in a critical state. Acknowledging an alert serves two purposes: First, it stops more alerts from being sent so the technicians are not bothered wit more alerts, and secondly it provides feedback to those looking at the Xymon web pages that the problem is being handled.

xymon-mailack(8) is a tool for processing acknowledgments sent via e-mail, e.g. as a response to an e-mail alert.

enadis.cgi(8) is a CGI program to disable or re-enable hosts or individual tests. When disabling a host or test, you stop alarms from being sent and also any outages do not affect the SLA calculations. So this tool is useful when systems are being brought down for maintenance.

findhost.cgi(1) is a CGI program that finds a given host in the Xymon web pages. As your Xymon installation grows, it can become difficult to remember exactly which page a host is on; this CGI script lets you find hosts easily.

report.cgi(1) This CGI program triggers the generation of Xymon availability reports, using xymongen(1) as the reporting back-end engine.

reportlog.cgi(1) This CGI program generates the detailed availability report for a particular host+service combination.

snapshot.cgi(1) is a CGI program to build the Xymon web pages in a "snapshot" mode, showing the look of the web pages at a particular point in time. It uses xymongen(1) as the back-end engine.

statusreport.cgi(1) is a CGI program reporting test results for a single status but for several hosts. It is used to e.g. see which SSL certificates are about to expire, across all of the Xymon web pages.

csvinfo.cgi(1) is a CGI program to present information about a host. The information is pulled from a CSV (Comma Separated Values) file, which is easily exported from any spreadsheet or database program.

 

CLIENT-SIDE TOOLS

logfetch(1) is a utility used by the Xymon Unix client to collect information from log files on the client. It can also monitor various other file-related data, e.g. file meta-data or directory sizes.

clientupdate(1) Is used on Xymon clients, to automatically update the client software with new versions. Through this tool, updates of the client software can happen without an administrator having to logon to the server.

msgcache(8) This tool acts as a mini Xymon server to the client. It stores client data internally, so that the xymonfetch(8) utility can pick it up later and send it to the Xymon server. It is typically used on hosts that cannot contact the Xymon server directly due to network- or firewall-restrictions.

 

XYMON COMMUNICATION TOOLS

These tools are used for communications between the Xymon server and the Xymon clients. If there are no firewalls then they are not needed, but it may be necessary due to network or firewall issues to make use of them.

xymonproxy(8) is a proxy-server that forwards Xymon messages between clients and the Xymon server. The clients must be able to talk to the proxy, and the proxy must be able to talk to the Xymon server.

xymonfetch(8) is used when the client is not able to make outbound connections to neither xymonproxy nor the Xymon server (typically, for clients located in a DMZ network zone). Together with the msgcache(8) utility running on the client, the Xymon server can contact the clients and pick up their data.

 

OTHER TOOLS

xymonlaunch(8) is a program scheduler for Xymon. It acts as a master program for running all of the Xymon tools on a system. On the Xymon server, it controls running all of the server tasks. On a Xymon client, it periodically launches the client to collect data and send them to the Xymon server.

xymon(1) is the tool used to communicate with the Xymon server. It is used to send status reports to the Xymon server, through the custom Xymon/BB protocol, or via HTTP. It can be used to query the state of tests on the central Xymon server and retrieve Xymon configuration files. The server-side script xymoncgimsg.cgi(1) used to receive messages sent via HTTP is also included.

xymoncmd(1) is a wrapper for the other Xymon tools which sets up all of the environment variables used by Xymon tools.

xymongrep(1) is a utility for use by Xymon extension scripts. It allows an extension script to easily pick out the hosts that are relevant to a script, so it need not parse a huge hosts.cfg file with lots of unwanted test-specifications.

xymoncfg(1) is a utility to dump the full hosts.cfg(5) file following any "include" statements.

xymondigest(1) is a utility to compute message digest values for use in content checks that use digests.

combostatus(1) is an extension script for the Xymon server, allowing you to build complicated tests from simpler Xymon test results. E.g. you can define a test that uses the results from testing your web server, database server and router to have a single test showing the availability of your enterprise web application.

trimhistory(8) is a tool to trim the Xymon history logs. It will remove all log entries and optionally also the individual status-logs for events that happened before a given time.

 

VERSIONS

Version 1 of bbgen was released in November 2002, and optimized the web page generation on Big Brother servers.

Version 2 of bbgen was released in April 2003, and added a tool for performing network tests.

Version 3 of bbgen was released in September 2004, and eliminated the use of several external libraries for network tests, resulting in a significant performance improvement.

With version 4.0 released on March 30 2005, the project was de-coupled from Big Brother, and the name changed to Hobbit. This version was the first full implementation of the Hobbit server, but it still used the data collected by Big Brother clients for monitoring host metrics.

Version 4.1 was released in July 2005 included a simple client for Unix. Log file monitoring was not implemented.

Version 4.2 was released in July 2006, and includes a fully functional client for Unix.

Version 4.3 was released in November 2010, and implemented the renaming of the project to Xymon. This name was already introduced in 2008 with a patch version of 4.2, but with version 4.3.0 this change of names was fully implemented.

 

COPYRIGHT

Xymon is

  Copyright (C) 2002-2011 Henrik Storner <henrik@storner.dk
Parts of the Xymon sources are from public-domain or other freely available sources. These are the the Red-Black tree implementation, and the MD5-, SHA1- and RIPEMD160-implementations. Details of the license for these is in the README file included with the Xymon sources. All other files are released under the GNU General Public License version 2, with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. See the file COPYING for details.

 

SEE ALSO

xymond(8), xymond_channel(8), xymond_history(8), xymond_rrd(8), xymond_alert(8), xymond_client(8), xymond_hostdata(8), xymonping(1), xymonnet(1), xymonnet-again.sh(1), xymongen(1), svcstatus.cgi(1), showgraph.cgi(1), hostgraphs.cgi(1), criticalview.cgi(1), history.cgi(1), eventlog.cgi(1), ack.cgi(1), xymon-mailack(8), enadis.cgi(8), findhost.cgi(1), report.cgi(1), reportlog.cgi(1), snapshot.cgi(1), statusreport.cgi(1), csvinfo.cgi(1), logfetch(1), clientupdate(1), msgcache(8), xymonproxy(8), xymonfetch(8), xymonlaunch(8), xymon(1), xymoncgimsg.cgi(1), xymoncmd(1), xymongrep(1), xymoncfg(1), xymondigest(1), combostatus(1), trimhistory(8), hosts.cfg(5), tasks.cfg(5), xymonserver.cfg(5), alerts.cfg(5), analysis.cfg(5), client-local.cfg(5)


 

Index

NAME
OVERVIEW
FEATURES
SECURITY
DEMONSTRATION SITE
PREREQUISITES AND INSTALLATION
SUPPORT and MAILING LISTS
XYMON SERVER TOOLS
XYMON NETWORK TEST TOOLS
XYMON TOOLS HANDLING THE WEB USER-INTERFACE
CLIENT-SIDE TOOLS
XYMON COMMUNICATION TOOLS
OTHER TOOLS
VERSIONS
COPYRIGHT
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/0000775000175000017500000000000011671641715015622 5ustar henrikhenrikxymon-4.3.7/docs/manpages/man8/xymond_filestore.8.html0000664000175000017500000001072611671641417022255 0ustar henrikhenrik Man page of XYMOND_FILESTORE

XYMOND_FILESTORE

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond_filestore - xymond worker module for storing Xymon data  

SYNOPSIS

xymond_channel --channel=status xymond_filestore --status [options]
xymond_channel --channel=data xymond_filestore --data [options]
xymond_channel --channel=notes xymond_filestore --notes [options]
xymond_channel --channel=enadis xymond_filestore --enadis [options]

 

DESCRIPTION

xymond_filestore is a worker module for xymond, and as such it is normally run via the xymond_channel(8) program. It receives xymond messages from a xymond channel via stdin, and stores these in the filesystem in a manner that is compatible with the Big Brother daemon, bbd.

This program can be started multiple times, if you want to store messages for more than one channel.

 

OPTIONS

--status
Incoming messages are "status" messages, they will be stored in the $XYMONRAWSTATUSDIR/ directory. If you are using xymon(7) throughout your Xymon server, you will not need to run this module to save status messages, unless you have a third-party add-on that reads the status-logs directly. This module is NOT needed to get trend graphs, you should run the xymond_rrd(8) module instead.

--data
Incoming messages are "data" messages, they will be stored in the $XYMONDATADIR directory. This module is not needed, unless you have a third-party module that processes the data-files. This module is NOT needed to get trend graphs, you should run the xymond_rrd(8) module instead.

--notes
Incoming messages are "notes" messages, they will be stored in the $XYMONNOTESDIR directory. This modules is only needed if you want to allow people to remotely update the notes-files available on the Xymon webpages.

--enadis
Incoming messages are enable/disable messages, they will update files in the $XYMONDISABLEDDIR directory. This is only needed if you have third-party add-ons that use these files.

--dir=DIRECTORY
Overrides the default output directory.

--html
Used together with "--status". Tells xymond_filestore to also save an HTML version of the status-log. Should not be used unless you must run with "XYMONLOGSTATUS=static".

--htmldir=DIRECTORY
The directory where HTML-versions of the status logs are stored. Default: $XYMONHTMLSTATUSDIR

--htmlext=.EXT
Set the filename extension for generated HTML files. By default, HTML files are saved with a ".html" extension.

--multigraphs=TEST1[,TEST2]
This causes xymond_filestore to generate HTML status pages with links to service graphs that are split up into multiple images, with at most 5 graphs per image. If not specified, only the "disk" status is split up this way.

--only=test[,test,test]
Save status messages only for the listed set of tests. This can be useful if you have an external script that needs to parse some of the status logs, but you do not want to save all status logs.

--debug
Enable debugging output.

 

FILES

This module does not rely on any configuration files.

 

SEE ALSO

xymond_channel(8), xymond_rrd(8), xymond(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/msgcache.8.html0000664000175000017500000000752711671641417020442 0ustar henrikhenrik Man page of MSGCACHE

MSGCACHE

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

msgcache - Cache client messages for later pickup by xymonfetch

 

SYNOPSIS

msgcache [options]

 

DESCRIPTION

msgcache implements a Xymon message cache. It is intended for use with clients which cannot deliver their data to the Xymon server in the normal way. Instead of having the client tools connect to the Xymon server, msgcache runs locally and the client tools then deliver their data to the msgcache daemon. The msgcache daemon is then polled regularly by the xymonfetch(8) utility, which collects the client messages stored by msgcache and forwards them to the Xymon server.

NOTE: When using msgcache, the XYMSRV setting for the clients should be XYMSRV=127.0.0.1 instead of pointing at the real Xymon server.

 

RESTRICTIONS

Clients delivering their data to msgcache instead of the real Xymon server will in general not notice this. Specifically, the client configuration data provided by the Xymon server when a client delivers its data is forwarded through the xymonfetch / msgcache chain, so the normal centralized client configuration works.

However, other commands which rely on clients communicating directly with the Xymon server will not work. This includes the config and query commands which clients may use to fetch configuration files and query the Xymon server for a current status.

The download command also does not work with msgcache. This means that the automatic client update facility will not work for clients communicating via msgcache.

 

OPTIONS

--listen=IPADDRESS[:PORT]
Defines the IP-address and portnumber where msgcache listens for incoming connections. By default, msgcache listens for connections on all network interfaces, port 1984.

--server=IPADDRESS[,IPADDRESS]
Restricts which servers are allowed to pick up the cached messages. By default anyone can contact the msgcache utility and request all of the cached messages. This option allows only the listed servers to request the cached messages.

--max-age=N
Defines how long cached messages are kept. If the message has not been picked up with N seconds after being delivered to msgcache, it is silently discarded. Default: N=600 seconds (10 minutes).

--daemon
Run as a daemon, i.e. msgcache will detach from the terminal and run as a background task

--no-daemon
Run as a foreground task. This option must be used when msgcache is started by xymonlaunch(8) which is the normal way of running msgcache.

--pidfile=FILENAME
Store the process ID of the msgcache task in FILENAME.

--logfile=FILENAME
Log msgcache output to FILENAME.

--debug
Enable debugging output.

 

SEE ALSO

xymonfetch(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
RESTRICTIONS
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymonproxy.8.html0000664000175000017500000002260711671641417021140 0ustar henrikhenrik Man page of XYMONPROXY

XYMONPROXY

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents

 

NAME

xymonproxy - Xymon message proxy  

SYNOPSIS

xymonproxy [options] --server=$XYMSRV

 

DESCRIPTION

xymonproxy(8) is a proxy for forwarding Xymon messages from one server to another. It will typically be needed if you have clients behind a firewall, so they cannot send status messages to the Xymon server directly.

xymonproxy serves three purposes. First, it acts as a regular proxy server, allowing clients that cannot connect directly to the Xymon servers to send data. Although xymonproxy is optimized for handling status messages, it will forward all types of messages, including notes- and data-messages.

Second, it acts as a buffer, smoothing out peak loads if many clients try to send status messages simultaneously. xymonproxy can absorb messages very quickly, but will queue them up internally and forward them to the Xymon server at a reasonable pace.

Third, xymonproxy merges small "status" messages into larger "combo" messages. This can dramatically decrease the number of connections that need to go from xymonproxy to the Xymon server. The merging of messages causes "status" messages to be delayed for up to 0.25 seconds before being sent off to the Xymon server.

 

OPTIONS

--server=SERVERIP[:PORT][,SERVER2IP[:PORT]]
Specifies the IP-address and optional portnumber where incoming messages are forwarded to. The default portnumber is 1984, the standard Xymon port number. If you have setup the normal Xymon environment, you can use "--server=$XYMSRV". Up to 3 servers can be specified; incoming messages are sent to all of them (except "config", "query" and "download" messages, which go to the LAST server only). If you have Xymon clients sending their data via this proxy, note that the clients will receive their configuration data from the LAST of the servers listed here. This option is required.

--listen=LOCALIP[:PORT]
Specifies the IP-adress where xymonproxy listens for incoming connections. By default, xymonproxy listens on all IP-adresses assigned to the host. If no portnumber is given, port 1984 will be used.

--timeout=N
Specifies the number of seconds after which a connection is aborted due to a timeout. Default: 10 seconds.

--report=[PROXYHOSTNAME.]SERVICE
If given, this option causes xymonproxy to send a status report every 5 minutes to the Xymon server about itself. If you have set the standard Xymon environment, you can use "--report=xymonproxy" to have xymonproxy report its status to a "xymonproxy" column in Xymon. The default for PROXYHOSTNAME is the $MACHINE environment variable, i.e. the hostname of the server running xymonproxy. See REPORT OUTPUT below for an explanation of the report contents.

--lqueue=N
Size of the listen-queue where incoming connections can queue up before being processed. This should be large to accomodate bursts of activity from clients. Default: 512.

--daemon
Run in daemon mode, i.e. detach and run as a background proces. This is the default.

--no-daemon
Runs xymonproxy as a foreground proces.

--pidfile=FILENAME
Specifies the location of a file containing the proces-ID of the xymonproxy daemon proces. Default: /var/run/xymonproxy.pid.

--logfile=FILENAME
Sends all logging output to the specified file instead of stderr.

--log-details
Log details (IP-address, message type and hostname) to the logfile. This can also be enabled and disabled at run-time by sending the xymonproxy proces a SIGUSR1 signal.

--debug
Enable debugging output.

 

REPORT OUTPUT

If enabled via the "--report" option, xymonproxy will send a status message about itself to the Xymon server once every 5 minutes.

The status message includes the following information:

Incoming messages
The total number of connections accepted from clients since the proxy started. The "(N msgs/second)" is the average number of messages per second over the past 5 minutes.

Outbound messages
The total number of messages sent to the Xymon server. Note that this is probably smaller than the number of incoming messages, since xymonproxy merges messages before sending them.

Incoming - Combo messages
The number of "combo" messages received from a client.

Incoming - Status messages
The number of "status" messages received from a client. xymonproxy attempts to merge these into "combo" messages. The "Messages merged" is the number of "status" messages that were merged into a combo message, the "Resulting combos" is the number of "combo" messages that resulted from the merging.

Incoming - Other messages
The number of other messages (data, notes, ack, query, ...) messages received from a client.

Proxy ressources - Connection table size
This is the number of connection table slots in the proxy. This measures the number of simultaneously active requests that the proxy has handled, and so gives an idea about the peak number of clients that the proxy has handled simultaneously.

Proxy ressources - Buffer space
This is the number of KB memory allocated for network buffers.

Timeout details - reading from client
The number of messages dropped because reading the message from the client timed out.

Timeout details - connecting to server
The number of messages dropped, because a connection to the Xymon server could not be established.

Timeout details - sending to server
The number of messages dropped because the communication to the Xymon server timed out after a connection was established.

Timeout details - recovered
When a timeout happens while sending the status message to the server, xymonproxy will attempt to recover the message and retry sending it to the server after waiting a few seconds. This number is the number of messages that were recovered, and so were not lost.

Timeout details - reading from server
The number of response messages that timed out while attempting to read them from the server. Note that this applies to the "config" and "query" messages only, since all other message types do not get any response from the servers.

Timeout details - sending to client
The number of response messages that timed out while attempting to send them to the client. Note that this applies to the "config" and "query" messages only, since all other message types do not get any response from the servers.

Average queue time
The average time it took the proxy to process a message, calculated from the messages that have passed through the proxy during the past 5 minutes. This number is computed from the messages that actually end up establishing a connection to the Xymon server, i.e. status messages that were combined into combo-messages do not go into the calculation - if they did, it would reduce the average time, since it is faster to merge messages than send them out over the network.

 

If you think the numbers do not add up, here is how they relate.

The "Incoming messages" should be equal to the sum of the "Incoming Combo/Status/Page/Other messages", or slightly more because messages in transit are not included in the per-type message counts.

The "Outbound messages" should be equal to sum of the "Incoming Combo/Page/Other messages", plus the "Resulting combos" count, plus "Incoming Status messages" minus "Messages merged" (this latter number is the number of status messages that were NOT merged into combos, but sent directly). The "Outbound messages" may be slightly lower than that, because messages in transit are not included in the "Outbound messages" count until they have been fully sent.

 

SIGNALS

SIGHUP
Re-opens the logfile, e.g. after it has been rotated.

SIGTERM
Shut down the proxy.

SIGUSR1
Toggles logging of individual messages.

 

SEE ALSO

xymon(1), xymond(1), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
REPORT OUTPUT
SIGNALS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymon-mailack.8.html0000664000175000017500000000674711671641417021444 0ustar henrikhenrik Man page of XYMON-MAILACK

XYMON-MAILACK

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymon-mailack - permit acknowledging alerts via e-mail  

SYNOPSIS

xymon-mailack --env=FILENAME [--debug]

 

DESCRIPTION

xymon-mailack normally runs as an input mail-filter for the xymon user, e.g. by being called from the xymon users' procmailrc(5) file. xymon-mailack recognizes e-mails that are replies to xymond_alert(8) mail alerts, and converts the reply mail into an acknowledge message that is sent to the Xymon system. This permits an administrator to acknowledge an alert via e-mail.

 

ADDING INFORMATION TO THE REPLY MAIL

By default, an acknowledgment is valid for 1 hour. If you know in advance that solving the problem is going to take longer, you can change this by adding delay=DURATION to the subject of your mail reply or on a line in the reply message. Duration is in minutes, unless you add a trailing 'h' (for 'hours'), 'd' (for 'days') or 'w' (for 'weeks').

You can also include a message that will show up on the status-page together with the acknowledgment, e.g. to provide an explanation for the issue or some other information to the users. You can either put it at the end of the subject line as msg=Some random text, or you can just enter it in the e-mail as the first non-blank line of text in the mail (a "delay=N" line is ignored when looking for the message text).

 

USE WITH PROCMAIL

To setup xymon-mailack, create a .procmailrc file in the xymon-users home-directory with the following contents:
DEFAULT=$HOME/Mailbox
LOGFILE=$HOME/procmail.log
:0
| $HOME/server/bin/xymon-mailack --env=$HOME/server/etc/xymonserver.cfg

 

USE WITH QMAIL

If you are using Qmail to deliver mail locally, you can run xymon-mailack directly from a .qmail file. Setup the xymon-users .qmail file like this:
| $HOME/server/bin/xymon-mailack --env=$HOME/server/etc/xymonserver.cfg

 

OPTIONS

--env=FILENAME
Load environment from FILENAME, usually xymonserver.cfg.

--debug
Dont send a message to xymond, but dump the message to stdout.

 

SEE ALSO

xymond_alert(8), xymond(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
ADDING INFORMATION TO THE REPLY MAIL
USE WITH PROCMAIL
USE WITH QMAIL
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymond_hostdata.8.html0000664000175000017500000000465711671641417022076 0ustar henrikhenrik Man page of XYMOND_HOSTDATA

XYMOND_HOSTDATA

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond_hostdata - xymond worker module for storing historical client data  

SYNOPSIS

xymond_channel --channel=clichg xymond_hostdata

 

DESCRIPTION

xymond_hostdata is a worker module for xymond, and as such it is normally run via the xymond_channel(8) program. Whenever a status column in Xymon changes to an alert state (usually red, yellow or purple), this module receives a copy of the latest Xymon client data sent by the host, and stores it on disk. This allows you to review all of the data collected by the Xymon client on the server around the time that a problem occurred. This can make troubleshooting incidents easier by providing a snapshot of the host status shortly before a problem became apparent.

Note: This module requires that xymond(8) is launched with the "--store-clientlogs" option enabled.

 

OPTIONS

--minimum-free=N
Sets the minimum percentage of free filesystem space on the $CLIENTLOGS directory. If there is less than N% free space, xymond_hostdata will not save the data. Default: 5

--debug
Enable debugging output.

 

FILES

All of the host data are stored in the $CLIENTLOGS directory, by default this is the $XYMONVAR/hostdata/ directory.

 

SEE ALSO

xymond(8), xymond_channel(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymond_sample.8.html0000664000175000017500000000352711671641417021543 0ustar henrikhenrik Man page of XYMOND_SAMPLE

XYMOND_SAMPLE

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond_sample - example of a xymond worker module  

SYNOPSIS

xymond_channel --channel=status xymond_sample [options]

 

DESCRIPTION

xymond_sample is a worker module for xymond, and as such it is normally run via the xymond_channel(8) program. It receives messages from xymond via stdin, and simply displays these on stdout. It can be used with all types of xymond channels.

xymond_sample is not designed to actually run, except as a demonstration. The purpose of this tool is to show how xymond worker modules can be implemented to handle different tasks that need to hook into the xymond processing.

 

OPTIONS

--timeout=N
Read messages with a timeout of N seconds.

--debug
Enable debugging output.

 

SEE ALSO

xymond_channel(8), xymond(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymond.8.html0000664000175000017500000003547711671641417020213 0ustar henrikhenrik Man page of XYMOND

XYMOND

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond - Master network daemon for a Xymon server  

SYNOPSIS

xymond [options]

 

DESCRIPTION

xymond is the core daemon in the Xymon Monitor. It is designed to handle monitoring of a large number of hosts, with a strong focus on being a high-speed, low-overhead implementation of a Big Brother compatible server.

To achieve this, xymond stores all information about the state of the monitored systems in memory, instead of storing it in the host filesystem. A number of plug-ins can be enabled to enhance the basic operation; e.g. a set of plugins are provided to implement persistent storage in a way that is compatible with the Big Brother daemon. However, even with these plugins enabled, xymond still performs much faster than the standard bbd daemon.

xymond is normally started and controlled by the xymonlaunch(8) tool, and the command used to invoke xymond should therefore be in the tasks.cfg file.

 

OPTIONS

--hosts=FILENAME
Specifies the path to the Xymon hosts.cfg file. This is used to check if incoming status messages refer to known hosts; depending on the "--ghosts" option, messages for unknown hosts may be dropped. If this option is omitted, the default path used is set by the HOSTSCFG environment variable.

--checkpoint-file=FILENAME
With regular intervals, xymond will dump all of its internal state to this check-point file. It is also dumped when xymond terminates, or when it receives a SIGUSR1 signal.

--checkpoint-interval=N
Specifies the interval (in seconds) between dumps to the check-point file. The default is 900 seconds (15 minutes).

--restart=FILENAME
Specifies an existing file containing a previously generated xymond checkpoint. When starting up, xymond will restore its internal state from the information in this file. You can use the same filename for "--checkpoint-file" and "--restart".

--ghosts={allow|drop|log|match}
How to handle status messages from unknown hosts. The "allow" setting accepts all status messages, regardless of whether the host is known in the hosts.cfg file or not. "drop" silently ignores reports from unknown hosts. "log" works like drop, but logs the event in the xymond output file. "match" will try to match the name of the unknown host reporting with the known names by ignoring any domain-names - if a match is found, then a temporary client alias is automatically generated. The default is "log".

--no-purple
Prevent status messages from going purple when they are no longer valid. Unlike the standard bbd daemon, purple-handling is done by xymond.

--listen=IP[:PORT]
Specifies the IP-address and port where xymond will listen for incoming connections. By default, xymond listens on IP 0.0.0.0 (i.e. all IP- adresses available on the host) and port 1984.

--daemon
xymond is normally started by xymonlaunch(8) it will then detach from the terminal and continue running as a background task.

--timeout=N
Set the timeout used for incoming connections. If a status has not been received more than N seconds after the connection was accepted, then the connection is dropped and any status message is discarded. Default: 10 seconds.

--flap-count=N
Track the N latest status-changes for flap-detection. See the --flap-seconds option also. To disable flap-checks, set N to zero. Default: 5

--flap-seconds=N
If a status changes more than flap-count times in N seconds or less, then it is considered to be flapping. In that case, the status is locked at the most severe level until the flapping stops. The history information is not updated after the flapping is detected. NOTE: If this is set higher than the default value, you should also use the --flap-count option to ensure that enough status-changes are stored for flap detection to work. The flap-count setting should be at least (N/300)-1, e.g. if you set flap-seconds to 3600 (1 hour), then flap-count should be at least (3600/300)-1, i.e. 11. Default: 1800 seconds (30 minutes).

--delay-red=N
Sets the delay before a red/yellow status causes a change in the web page display. Is usually controlled on a per-host basis via the delayred and delayyellow settings in hosts.cfg(5) but these options allow you to set a default value for the delays. The value N is in minutes. Default: 0 minutes (no delay). Note: Since most tests only execute once every 5 minutes, it will usually not make sense to set N to anything but a multiple of 5.

--env=FILENAME
Loads the content of FILENAME as environment settings before starting xymond. This is mostly used when running as a stand-alone daemon; if xymond is started by xymonlaunch, the environment settings are controlled by the xymonlaunch tasks.cfg file.

--pidfile=FILENAME
xymond writes the process-ID it is running with to this file. This is for use in automated startup scripts. The default file is $XYMONSERVERLOGS/xymond.pid.

--log=FILENAME
Redirect all output from xymond to FILENAME.

--store-clientlogs[=[!]COLUMN]
Determines which status columns can cause a client message to be broadcast to the CLICHG channel. By default, no client messages are pushed to the CLICHG channel. If this option is specified with no parameter list, all status columns that go into an alert state will trigger the client data to be sent to the CLICHG channel. If a paramater list is added to this option, only those status columns listed in the list will cause the client data to be sent to the CLICHG channel. Several column names can be listed, separated by commas. If all columns are given as "!COLUMNNAME", then all status columns except those listed will cause the client data to be sent.

--status-senders=IP[/MASK][,IP/MASK]
Controls which hosts may send "status", "combo", "config" and "query" commands to xymond.

By default, any host can send status-updates. If this option is used, then status-updates are accepted only if they are sent by one of the IP-adresses listed here, or if they are sent from the IP-address of the host that the updates pertains to (this is to allow Xymon clients to send in their own status updates, without having to list all clients here). So typically you will need to list your servers running network tests here.

The format of this option is a list of IP-adresses, optionally with a network mask in the form of the number of bits. E.g. if you want to accept status-updates from the host 172.16.10.2, you would use

    --status-senders=172.16.10.2
whereas if you want to accept status updates from both 172.16.10.2 and from all of the hosts on the 10.0.2.* network (a 24-bit IP network), you would use

    --status-senders=172.16.10.2,10.0.2.0/24

--maint-senders=IP[/MASK][,IP/MASK]
Controls which hosts may send maintenance commands to xymond. Maintenance commands are the "enable", "disable", "ack" and "notes" commands. Format of this option is as for the --status-senders option. It is strongly recommended that you use this to restrict access to these commands, so that monitoring of a host cannot be disabled by a rogue user - e.g. to hide a system compromise from the monitoring system.

Note: If messages are sent through a proxy, the IP-address restrictions are of little use, since the messages will appear to originate from the proxy server address. It is therefore strongly recommended that you do NOT include the address of a server running xymonproxy in the list of allowed addresses.

--www-senders=IP[/MASK][,IP/MASK]
Controls which hosts may send commands to retrieve the state of xymond. These are the "xymondlog", "xymondboard" and "xymondxboard" commands, which are used by xymongen(1) and combostatus(1) to retrieve the state of the Xymon system so they can generate the Xymon webpages.

Note: If messages are sent through a proxy, the IP-address restrictions are of little use, since the messages will appear to originate from the proxy server address. It is therefore strongly recommended that you do NOT include the address of a server running xymonproxy in the list of allowed addresses.

--admin-senders=IP[/MASK][,IP/MASK]
Controls which hosts may send administrative commands to xymond. These commands are the "drop" and "rename" commands. Access to these should be restricted, since they provide an un-authenticated means of completely disabling monitoring of a host, and can be used to remove all traces of e.g. a system compromise from the Xymon monitor.

Note: If messages are sent through a proxy, the IP-address restrictions are of little use, since the messages will appear to originate from the proxy server address. It is therefore strongly recommended that you do NOT include the address of a server running xymonproxy in the list of allowed addresses.

--no-download
Disable the "download" and "config" commands which can be used by clients to pull files from the Xymon server. The use of these may be seen as a security risk since they allow file downloads.

--debug
Enable debugging output.

--dbghost=HOSTNAME
For troubleshooting problems with a specific host, it may be useful to track the exact communications from a single host. This option causes xymond to dump all traffic from a single host to the file "/tmp/xymond.dbg".

 

HOW ALERTS TRIGGER

When a status arrives, xymond matches the old and new color against the "alert" colors (from the "ALERTCOLORS" setting) and the "OK" colors (from the "OKCOLORS" setting). The old and new color falls into one of three categories:

OK: The color is one of the "OK" colors (e.g. "green").

ALERT: The color is one of the "alert" colors (e.g. "red").

UNDECIDED: The color is neither an "alert" color nor an "OK" color (e.g. "yellow").

If the new status shows an ALERT state, then a message to the xymond_alert(8) module is triggered. This may be a repeat of a previous alert, but xymond_alert(8) will handle that internally, and only send alert messages with the interval configured in alerts.cfg(5).

If the status goes from a not-OK state (ALERT or UNDECIDED) to OK, and there is a record of having been in a ALERT state previously, then a recovery message is triggered.

The use of the OK, ALERT and UNDECIDED states make it possible to avoid being flooded with alerts when a status flip-flops between e.g yellow and red, or green and yellow.

 

CHANNELS

A lot of functionality in the Xymon server is delegated to "worker modules" that are fed various events from xymond via a "channel". Programs access a channel using IPC mechanisms - specifically, shared memory and semaphores - or by using an instance of the xymond_channel(8) intermediate program. xymond_channel enables access to a channel via a simple file I/O interface.

A skeleton program for hooking into a xymond channel is provided as part of Xymon in the xymond_sample(8) program.

The following channels are provided by xymond:

status This channel is fed the contents of all incoming "status" and "summary" messages.

stachg This channel is fed information about tests that change status, i.e. the color of the status-log changes.

page This channel is fed information about tests where the color changes between an alert color and a non-alert color. It also receives information about "ack" messages.

data This channel is fed information about all "data" messages.

notes This channel is fed information about all "notes" messages.

enadis This channel is fed information about hosts or tests that are being disabled or enabled.

client This channel is fed the contents of the client messages sent by Xymon clients installed on the monitored servers.

clichg This channel is fed the contents of a host client messages, whenever a status for that host goes red, yellow or purple.

Information about the data stream passed on these channels is in the Xymon source-tree, see the "xymond/new-daemon.txt" file.

 

SIGNALS

SIGHUP
Re-read the hosts.cfg configuration file.

SIGUSR1
Force an immediate dump of the checkpoint file.

 

BUGS

Timeout of incoming connections are not strictly enforced. The check for a timeout only triggers during the normal network handling loop, so a connection that should timeout after N seconds may persist until some activity happens on another (unrelated) connection.

 

FILES

If ghost-handling is enabled via the "--ghosts" option, the hosts.cfg file is read to determine the names of all known hosts.

 

SEE ALSO

xymon(7), xymonserver.cfg(5).


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
HOW ALERTS TRIGGER
CHANNELS
SIGNALS
BUGS
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymond_client.8.html0000664000175000017500000001073111671641417021533 0ustar henrikhenrik Man page of XYMOND_CLIENT

XYMOND_CLIENT

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond_client - xymond worker module for client data  

SYNOPSIS

xymond_channel --channel=client xymond_client [options]

 

DESCRIPTION

xymond_client is a worker module for xymond, and as such it is normally run via the xymond_channel(8) program. It receives xymond client messages sent from systems that have the the Xymon client installed, and use the client data to generate the Xymon status messages for the cpu-, disk-, memory- and procs-columns. It also feeds Xymon data messages with the netstat- and vmstat-data collected by the client.

When generating these status messages from the client data, xymond_client will use the configuration rules defined in the analysis.cfg(5) file to determine the color of each status message.

 

OPTIONS

--clear-color=COLOR
Define the color used when sending "msgs", "files" or "ports" reports and there are no rules to check for these statuses. The default is to show a "clear" status, but some people prefer to have it "green". If you would rather prefer not to see these status columns at all, then you can use the "--no-clear-msgs", "--no-clear-files" and "--no-clear-ports" options instead.

--no-clear-msgs
If there are no logfile checks, the "msgs" column will show a "clear" status. If you would rather avoid having a "msgs" column, this option causes xymond_client to not send in a clear "msgs" status.

--no-clear-files
If there are no file checks, the "files" column will show a "clear" status. If you would rather avoid having a "files" column, this option causes xymond_client to not send in a clear "files" status.

--no-clear-ports
If there are no port checks, the "ports" column will show a "clear" status. If you would rather avoid having a "ports" column, this option causes xymond_client to not send in a clear "ports" status.

--no-ps-listing
Normally the "procs" status message includes the full process-listing received from the client. If you prefer to just have the monitored processes shown, this option will turn off the full ps-listing.

--no-port-listing
Normally the "ports" status message includes the full netstat-listing received from the client. If you prefer to just have the monitored ports shown, this option will turn off the full netstat-listing.

--config=FILENAME
Sets the filename for the analysis.cfg file. The default value is "etc/analysis.cfg" below the Xymon server directory.

--dump-config
Dumps the configuration after parsing it. May be useful to track down problems with configuration file errors.

--test
Starts an interactive session where you can test the analysis.cfg configuration.

--collectors=COLLECTOR1[,COLLECTOR2,...]
Limit the set of collector modules that xymond_client will handle. This is not normally used except for running experimental versions of the program.

--debug
Enable debugging output.

 

FILES

~xymon/server/etc/analysis.cfg

 

SEE ALSO

analysis.cfg(5), xymond(8), xymond_channel(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymoncgimsg.cgi.8.html0000664000175000017500000000520311671641417021762 0ustar henrikhenrik Man page of XYMONCGIMSG.CGI

XYMONCGIMSG.CGI

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents

 

NAME

xymoncgimsg.cgi - CGI utility used for proxying Xymon data over HTTP  

SYNOPSIS

xymoncgimsg.cgi

 

DESCRIPTION

xymoncgimsg.cgi(8) is the server-side utility receiving Xymon messages sent by the xymon(1) utility over an HTTP transport. The xymon utility normally sends data over a dedicated TCP protocol, but it may use HTTP to go through proxies or through restrictive firewalls. In that case, the webserver must have this CGI utility installed, which takes care of receiving the message via HTTP, and forwards it to a local Xymon server through the normal Xymon transport.

The CGI expects to be invoked from an HTTP "POST" request, with the POST-data being the status-message. xymoncgimsg.cgi simply collects all of the POST data, and send it off as a message to the Xymon daemon running on IP 127.0.0.1. This destination IP currently cannot be changed.

The CGI will return any output provided by the Xymon daemon back to the requestor as the response to the HTTP POST, so this allows for all normal Xymon commands to work.

 

SECURITY

xymoncgimsg.cgi will only send data to a Xymon server through the loopback interface, i.e. IP-address 127.0.0.1.

Access to the CGI should be restricted through webserver access controls, since the CGI provides no authentication at all to validate incoming messages.

If possible, consider using the xymonproxy(8) utility instead for native proxying of Xymon data between networks.

 

SEE ALSO

xymon(1), xymonproxy(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
SECURITY
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymond_rrd.8.html0000664000175000017500000004304411671641417021047 0ustar henrikhenrik Man page of XYMOND_RRD

XYMOND_RRD

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond_rrd - xymond worker module for updating Xymon RRD files  

SYNOPSIS

xymond_channel --channel=status xymond_rrd [options]
xymond_channel --channel=data xymond_rrd [options]

 

DESCRIPTION

xymond_rrd is a worker module for xymond, and as such it is normally run via the xymond_channel(8) program. It receives "status" and "data" messages from xymond via stdin, and updates the RRD databases used to generate trend-graphs.

Clients can send data to Xymon using both status- and data- messages. So you will normally run two instances of this module, once for the "status" channel and once for the "data" channel.

xymond_rrd understands data sent by the LARRD 0.43c client-side scripts (the so-called "bottom-feeder" scripts). So you still want to install the LARRD bottom-feeders on the clients you monitor.

Note: For certain types of data, the RRD files used by Xymon are imcompatible with those generated by the Big Brother LARRD add-on. See the COMPATIBILITY section below.

 

OPTIONS

--debug
Enable debugging output.

--rrddir=DIRECTORY
Defines the directory where the RRD-files are stored. xymond_rrd will use the location pointed to by the XYMONRRDS environment if this option is not present.

--no-cache
xymond_rrd by default caches updates to the RRD files, to reduce the disk I/O needed for storing the RRD data. Data is collected for a 30 minute period before being committed to disk in one update. This option disables caching of the data, so that data is stored on disk immediately.

--extra-script=FILENAME
Defines the script that is run to get the RRD data for tests that are not built into xymond_rrd. You must also specify which tests are handled by the external script in the --extra-tests option. This option can only be given once, so the script must handle all of the external test-data. See the CUSTOM RRD DATA section below. Note that this is NOT needed if your custom graphs are generated by the NCV (Name Colon Value) module described below, it is only required for data where you have a custom script to parse the status message and extract the data that is put into the graph.

--extra-tests=TEST[,TEST]
List of testnames that are handled by the external script. See the CUSTOM RRD DATA section below. Note that NCV graphs should NOT be listed here, but in the TEST2RRD environment variable - see below.

 

ENVIRONMENT

TEST2RRD
Defines the mapping between a status-log columnname and the corresponding RRD database format. This is normally defined in the xymonserver.cfg(5) file.

XYMONRRDS
Default directory where RRD files are stored.

NCV_testname
Defines the types of data collected by the "ncv" module in xymond_rrd. See below for more information.

SPLITNCV_testname
The same as NCV_testname, but keeps the data into separate files. That is, it creates one rrd file per "NAME : value" line found in the status message. It is useful when the list of NCV lines is varying.

TRACKMAX
Comma-separated list of columnname for which you want to keep the maximum values along with the default average values. This only works
 for the NCV backend.

 

COLLECTED DATA

The following RRD-file datasets are generated by xymond_rrd:

la
Records the CPU load average. Data is collected from the "cpu" status report. Requires that a Xymon client is running on the monitored server.

disk
Records the disk utilization. Data is collected from the "disk" status report. Requires that a Xymon-compatible client is running on the monitored server.

memory
Records memory- and swap-utilization. Data is collected from the "memory" status report. If no "memory" status is reported, it will use the data from the Win32 client "cpu" status report to generate this dataset. Requires that a Xymon-compatible client is running on the monitored server.

netstat
Records TCP and UDP statistics. Data is collected from the "netstat" status report; however, this data is often sent via the Xymon "data" protocol, so there need not be a "netstat" column visible on the Xymon display. To get these data, the LARRD netstat bottom-feeder script must be running on the monitored server.

vmstat
Records system performance metrics from the "vmstat" command. Data is collected from the "vmstat" status report; however, this data is often sent via the Xymon "data" protocol, so there need not be a "vmstat" column visible on the Xymon display. To get these data, the LARRD vmstat bottom-feeder script must be running on the monitored server.

tcp
Response-time metrics from all of the Xymon network tests are recorded in the "tcp" RRD.

apache
Apache server performance metrics, taken from the "apache" data report. See the description of the apache keyword in hosts.cfg(5) for details.

sendmail
Sendmail server performance metrics, taken from the "mailstats" output. To get these data, the LARRD sendmail bottom-feeder script must be running on the monitored server.

mailq
Mail queue size. To get these data, the LARRD nmailq bottom-feeder script must be running on the monitored server.

bea
BEA Weblogic performance data. This is an experimental set of data collected from BEA Weblogic servers via SNMP, by the "beastats" tool included with Xymon.

iishealth
IIS webserver performance data, collected by the "iishealth" script. This script is a client-side add-on available from the www.deadcat.net archive.

temperature
Temperature data, collected with the temperature script from www.deadcat.net. To get these data, the temperature script must be running on the monitored server.

ntpstat
Tracks the deviation between the local system time and an NTP server, using the output from the "ntpq -c rv" command. A simple script to collect these data is included in the Xymon contrib/ directory.

citrix
Tracks the number of active sessions on a Citrix server using the "query session" command. An extension for the BBNT client that generates data for this graph is in the Xymon contrib/ directory.

 

CUSTOM RRD DATA IN NAME-COLON-VALUE (NCV) FORMAT

Many data-collection scripts report data in the form "NAME : value" or "NAME = value". So a generic module in xymond_rrd allows for easy tracking of this type of data.

The "ncv" module will automatically detect all occurrences of a "NAME : value" or "NAME = value" string in a status message, and generate an RRD file holding all of the name/value data found in the message (unless you use SPLITNCV, see above). The colon- or equal-sign must be present - if there is only whitespace, this module will fail.

Only the valid letters (A-Z, a-z) and digits (0-9) are used in the dataset names; whitespace and other characters are stripped off automatically. Only the first 19 characters of a dataset name are used (this is an RRD limitation). Underscore '_' is not allowed, even though RRDtool permits this, and will be stripped from the name.

When using the alternative SPLITNCV_testname, the dataset name is not limited in length, and non-valid characters are changed to underscores instead of being stripped off. The dataset inside the resulting rrd file is always "lambda".

Note that each "NAME : value" must be on a line by itself. If you have a custom script generating the status- or data-message that is fed into the NCV handler, make sure it inserts a newline before each of the data-items you want to track.

To enable the ncv module for a status, add a "COLUMNNAME=ncv" to the TEST2RRD setting and the COLUMNNAME to the GRAPHS setting in xymonserver.cfg(5) , then restart Xymon. Xymon will now send all status-messages for the column COLUMNNAME through the xymond_rrd ncv-handler.

The name of the RRD file will be COLUMNNAME.rrd. When using SPLITNCV, the name of the RRD file will be COLUMNAME,DATASETNAME.rrd.

By default, all of the datasets are generated as the RRD type "DERIVE" which works for all types of monotonically increasing counters. If you have data that are of the type GAUGE, you can override the default via an environment variable NCV_COLUMNNAME (or SPLITNCV_COLUMNAME).

E.g. if you are using the bb-mysqlstatus script from www.deadcat.net to collect data about your MySQL server, it generates a report in the column called "mysql". One data item is the average number of queries/second, which must be logged in the RRD file as type "GAUGE". To do that, add the following to xymonserver.cfg:

    NCV_mysql="Queriespersecondavg:GAUGE" 
If you have multiple datasets that you myst define, add them to the environment variable separated by commas, e.g.

    NCV_mysql="Uptime:NONE,Queriespersecondavg:GAUGE" 

The dataset type "NONE" used above causes xymond_rrd to ignore this data, it is not included in the RRD file.

You can use "*" as the dataset name to match all datasets not listed. E.g.

    NCV_weather="Rain:DERIVE,*:GAUGE"
will cause the "Rainfall" dataset to be of type DERIVE, and all others of type GAUGE. If you want to track only a few of the variables in your data, you can use "*:NONE" to drop any dataset not explicitly listed.

For a more detailed "how to" description, see the on-line HTML documentation of "How to create graph custom data" available in the Help menu section on your Xymon server.

 

CUSTOM RRD DATA VIA SCRIPTS

xymond_rrd provides a simple mechanism for adding custom graphs to the set of data collected on your Xymon server. By adding the "--extra-script" and "--extra-tests" options, data reported to Xymon from selected tests are passed to an external script, which can define the RRD data-sets to store in an RRD file.

NOTE: For performance reasons, you should not use this mechanism for large amounts of data. The overhead involved in storing the received message to disk and launching the script is significantly larger than the normal xymond_rrd overhead. So if you have a large number of reports for a given test, you should consider implementing it in C and including it in the xymond_rrd tool.

Apart from writing the script, You must also add a section to graphs.cfg(5) so that showgraph.cgi(1) knows how to generate the graph from the data stored in the RRD file. To make the graphs actually show up on the status-page and/or the "trends" page, add the name of the new graph to the TEST2RRD and/or GRAPHS setting in xymonserver.cfg(5).

The script is invoked for each message that arrives, where the test-name matches one of the testnames given in the "--extra-tests" option. The script receives three command-line parameters:

Hostname
The name of the host reporting the data.
Testname
The name of the test being reported.
Filename
File containing the data that was reported. This file is generated for you by xymond_rrd, and is also deleted automatically after your script is finished with it.

The script must process the data that is reported, and generate the following output:

RRD data-set definitions
For each dataset that the RRD file holds, a line beginning with "DS:" must be output. If multiple data-sets are used, print one line for each dataset.
Data-set definitions are described in the rrdcreate(1) documentation, but a common definition for e.g. tracking the number of users logged on would be "DS:users:GAUGE:600:0:U". "users" is the name of the dataset, "GAUGE" is the datatype, "600" is the longest time allowed between updates for the data to be valid, "0" is the minimum value, and "U" is the maximum value (a "U" means "unknown").
RRD filename
The name of the RRD file where the data is stored. Note that Xymon stores all RRD files in host-specific directories, so unlike LARRD you should not include the hostname in the name of the RRD file.
RRD values
One line, with all of the data values collected by the script. Data-items are colon-delimited and must appear in the same sequence as your data-set definitions, e.g. if your RRD has two datasets with the values "5" and "0.4" respectively, then the script must output "5:0.4" as the RRD values.
In some cases it may be useful to define a dataset even though you will not always have data for it. In that case, use "U" (unknown) for the value.

If you want to store the data in multiple RRD files, the script can just print out more sequences of data-set definitions, RRD filenames and RRD values. If the data-set definitions are identical to the previous definition, you need not print the data-set definitions again - just print a new RRD filename and value.

The following sample script for tracking weather data shows how to use this mechanism. It assumes the status message include lines like these:

green Weather in Copenhagen is FAIR

Temperature: 21 degrees Celsius
Wind: 4 m/s
Humidity: 72 %
Rainfall: 5 mm since 6:00 AM

A shell-script to track all of these variables could be written like this:

#!/bin/sh

# Input parameters: Hostname, testname (column), and messagefile
HOSTNAME="$1"
TESTNAME="$2"
FNAME="$3"

if [ "$TESTNAME" = "weather" ]
then
        # Analyze the message we got
        TEMP=`grep "^Temperature:" $FNAME | awk '{print $2}'`
        WIND=`grep "^Wind:" $FNAME | awk '{print $2}'`
        HMTY=`grep "^Humidity:" $FNAME | awk '{print $2}'`
        RAIN=`grep "^Rainfall:" $FNAME | awk '{print $2}'`

        # The RRD dataset definitions
        echo "DS:temperature:GAUGE:600:-30:50"
        echo "DS:wind:GAUGE:600:0:U"
        echo "DS:humidity:GAUGE:600:0:100"
        echo "DS:rainfall:DERIVE:600:0:100"

        # The filename
        echo "weather.rrd"

        # The data
        echo "$TEMP:$WIND:$HMTY:$RAIN"
fi

exit 0

 

COMPATIBILITY

Some of the RRD files generated by xymond_rrd are incompatible with the files generated by the Big Brother LARRD add-on:

vmstat
The vmstat files with data from Linux based systems are incompatible due to the addition of a number of new data-items that LARRD 0.43 do not collect, but xymond_rrd does. This is due to changes in the output from the Linux vmstat command, and changes in the way e.g. system load metrics are reported.

netstat
All netstat files from LARRD 0.43 are incompatible with xymond_rrd. The netstat data collected by LARRD is quite confusing: For some types of systems LARRD collects packet-counts, for others it collects byte- counts. xymond_rrd uses a different RRD file-format with separate counters for packets and bytes and tracks whatever data the system is reporting.

 

SEE ALSO

xymond_channel(8), xymond(8), xymonserver.cfg(5), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
ENVIRONMENT
COLLECTED DATA
CUSTOM RRD DATA IN NAME-COLON-VALUE (NCV) FORMAT
CUSTOM RRD DATA VIA SCRIPTS
COMPATIBILITY
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymonfetch.8.html0000664000175000017500000000734011671641417021045 0ustar henrikhenrik Man page of XYMONFETCH

XYMONFETCH

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymonfetch - fetch client data from passive clients  

SYNOPSIS

xymonfetch [--server=XYMON.SERVER.IP] [options]

 

DESCRIPTION

This utility is used to collect data from Xymon clients.

Normally, Xymon clients will themselves take care of sending all of their data directly to the Xymon server. In that case, you do not need this utility at all. However, in some network setups clients may be prohibited from establishing a connection to an external server such as the Xymon server, due to firewall policies. In such a setup you can configure the client to store all of the client data locally by enabling the msgcache(8) utility on the client, and using xymonfetch on the Xymon server to collect data from the clients.

xymonfetch will only collect data from clients that have the pulldata tag listed in the hosts.cfg(5) file. The IP-address listed in the hosts.cfg file must be correct, since this is the IP-address where xymonfetch will attempt to contact the client. If the msgcache daemon is running on a non-standard IP-address or portnumber, you can specify the portnumber as in pulldata=192.168.1.2:8084 for contacting the msgcache daemon using IP 192.168.1.2 port 8084. If the IP-address is omitted, the default IP in the hosts.cfg file is used. If the port number is omitted, the portnumber from the XYMONDPORT setting in xymonserver.cfg(5) is used (normally, this is port 1984).

 

OPTIONS

--server=XYMON.SERVER.IP
Defines the IP address of the Xymon server where the collected client messages are forwarded to. By default, messages are sent to the loopback address 127.0.0.1, i.e. to a Xymon server running on the same host as xymonfetch.

--interval=N
Sets the interval (in seconds) between polls of a client. Default: 60 seconds.

--id=N
Used when you have a setup with multiple Xymon servers. In that case, you must run xymonfetch on each of the Xymon servers, with xymonfetch instance using a different value of N. This allows several Xymon servers to pick up data from the clients running msgcache, and msgcache can distinguish between which messages have already been forwarded to which server.
N is a number in the range 1-31.

--log-interval=N
Limit how often xymonfetch will log problems with fetching data from a host, in seconds. Default: 900 seconds (15 minutes). This is to prevent a host that is down or where msgcache has not been started from flooding the xymonfetch logs. Note that this is ignored when debugging is enabled.

--debug
Enable debugging output.

 

SEE ALSO

msgcache(8), xymond(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymond_channel.8.html0000664000175000017500000001135411671641417021667 0ustar henrikhenrik Man page of XYMOND_CHANNEL

XYMOND_CHANNEL

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond_channel - Feed a xymond channel to a worker module  

SYNOPSIS

xymond_channel --channel=CHANNEL [options] workerprogram [worker-options]

 

DESCRIPTION

xymond_channel hooks into one of the xymond(8) channels that provide information about events occurring in the Xymon system. It retrieves messages from the xymond daemon, and passes them on to the workerprogram on the STDIN (file descripter 1) of the worker program. Worker programs can then handle messages as they like.

A number of worker programs are shipped with xymond, e.g. xymond_filestore(8) xymond_history(8) xymond_alert(8) xymond_rrd(8)

If you want to write your own worker module, a sample worker module is provided as part of the xymond distribution in the xymond_sample.c file. This illustrates how to easily fetch and parse messages.

 

OPTIONS

xymond_channel accepts a few options.

--channel=CHANNELNAME
Specifies the channel to receive messages from, only one channel can be used. This option is required. The following channels are available:
"status" receives all Xymon status- and summary-messages
"stachg" receives information about status changes
"page" receives information about statuses triggering alerts
"data" receives all Xymon "data" messages
"notes" receives all Xymon "notes" messages
"enadis" receives information about hosts being disabled or enabled.

--filter=EXPRESSION
EXPRESSION is a Perl-compatible regular expression. xymond_channel will match the first line of each message against this expression, and silently drops any message that does not match the expression. Especially useful for custom worker modules and during testing, to limit the amount of data that the module must process.
Note that messages for "logrotate", "shutdown", "drophost", "renamehost", "droptest" and "renametest" are always forwarded by xymond_channel, whether they match the filter or not.

--daemon
xymond_channel is normally started by xymonlaunch(8) as a task defined in the tasks.cfg(5) file. If you are not using xymonlaunch, then starting xymond_channel with this option causes it to run as a stand-alone background task.

--pidfile=FILENAME
If running as a stand-alone daemon, xymond_channel will save the proces-ID of the daemon in FILENAME. This is useful for automated startup- and shutdown- scripts.

--env=FILENAME
Loads the environment variables defined in FILENAME before starting xymond_channel. This is normally used only when running as a stand-alone daemon; if xymond_channel is started by xymonlaunch, then the environment is controlled by the task definition in the tasks.cfg(5) file.

--log=FILENAME
Redirect output to this log-file.

--md5 / --no-md5
Enable/disable checksumming of messages passed from xymond_channel to the worker module. This may be useful if you suspect that data may be corrupted, e.g. when sent to a remote worker module. Note that enabling this may break communication with old versions of Xymon worker modules. Default: Disabled.

--debug
Enable debugging output.

 

FILES

This program does not use any configuration files.

 

SEE ALSO

xymond(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymonlaunch.8.html0000664000175000017500000000760311671641417021230 0ustar henrikhenrik Man page of XYMONLAUNCH

XYMONLAUNCH

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymonlaunch - Master program to launch other Xymon programs

 

SYNOPSIS

xymonlaunch [options]

 

DESCRIPTION

xymonlaunch(8) is the main program that controls the execution and scheduling of all of the components in the Xymon system.

xymonlaunch allows the administrator to add, remove or change the set of Xymon applications and extensions without restarting Xymon - xymonlaunch will automatically notice any changes in the set of tasks, and change the scheduling of activities accordingly.

xymonlaunch also allows the administrator to setup specific logfiles for each component of the Xymon system, instead of getting output from all components logged to a single file.

 

OPTIONS

--env=FILENAME
Loads the environment from FILENAME before starting other tools. The environment defined by FILENAME is the default, it can be overridden by the ENVFILE option in tasks.cfg(5)

--config=FILENAME
This option defines the file that xymonlaunch scans for tasks it must launch. A description of this file is in tasks.cfg(5) The default tasklist is /etc/tasks.cfg

--log=FILENAME
Defines the logfile where xymonlaunch logs information about failures to launch tasks and other data about the operation of xymonlaunch. Logs from individual tasks are defined in the tasks.cfg file. By default this is logged to stdout.

--pidfile=FILENAME
Filename which xymonlaunch saves its own process-ID to. Commonly used by automated start/stop scripts.

--verbose
Logs the launch of all tasks to the logfile. Note that the logfile may become quite large if you enable this.

--dump
Just dump the contents of the tasks.cfg file after parsing it. Used for debugging.

--debug
Enable debugging output while running.

--no-daemon
xymonlaunch normally detaches from the controlling tty and runs as a background task. This option keeps it running in the foreground.

 

STARTING TASKS

xymonlaunch will read the configuration file and start all of the tasks listed there.

If a task completes abnormally (i.e. terminated by a signal or with a non-zero exit status), then xymonlaunch will attempt to restart it 5 times. If it still will not run, then the task is disabled for 10 minutes. This will be logged to the xymonlaunch logfile.

If the configuration file changes, xymonlaunch will re-read it and notice any changes. If a running task was removed from the configuration, then the task is stopped. If a new task was added, it will be started. If the command used for a task changed, or it was given a new environment definition file, or the logfile was changed, then the task is stopped and restarted with the new definition.

 

SEE ALSO

tasks.cfg(5), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
STARTING TASKS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymond_history.8.html0000664000175000017500000000772411671641417021766 0ustar henrikhenrik Man page of XYMOND_HISTORY

XYMOND_HISTORY

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond_history - xymond worker module for logging status changes  

SYNOPSIS

xymond_channel --channel=stachg xymond_history [options]

 

DESCRIPTION

xymond_history is a worker module for xymond, and as such it is normally run via the xymond_channel(8) program. It receives xymond status-change messages from the "stachg" channel via stdin, and uses these to update the history logfiles in a manner that is compatible with the standard Big Brother daemon, bbd.

 

OPTIONS

--histdir=DIRECTORY
The directory for the history files. If not specified, the directory given by the XYMONHISTDIR environment is used.

--histlogdir=DIRECTORY
The directory for the historical status-logs. If not specified, the directory given by the XYMONHISTLOGS environment is used.

--minimum-free=N
Sets the minimum percentage of free filesystem space on the $XYMONHISTLOGS directory. If there is less than N% free space, xymond_history will not save the detailed status-logs. Default: 5

--pidfile=FILENAME
xymond_history writes the process-ID it is running with to this file. This is for use in automated startup scripts. The default file is $XYMONSERVERLOGS/xymond_history.pid.

--debug
Enable debugging output.

 

ENVIRONMENT

XYMONALLHISTLOG
This environment variable controls if the $XYMONHISTDIR/allevents logfile is updated. This file is used by the event-log display on the nongreen html page and the eventlog-webpage, among other things. You can disable it by setting XYMONALLHISTLOGS=FALSE, but this is not recommended.

XYMONHOSTHISTLOG
This environment variable controls if the $XYMONHISTDIR/HOSTNAME logfile is updated. This file holds a list of all status changes seen for a single host, but is not used by any of the standard Xymon tools. If you do not want to save this, you can disable it by setting XYMONHOSTHISTLOG=FALSE.

SAVESTATUSLOG
This environment variable controls if the historical status-logs are saved whenever a status change occurs. These logfiles are stored in the $XYMONHISTLOGS directory, and are used for the detailed log-display of a status from the Xymon "History" page. If you do not want to save these, you can disable it by setting SAVESTATUSLOG=FALSE. If you want to save all except some specific logs, use SAVESTATUSLOG=TRUE,!TEST1[,!TEST2...] If you want to save none except some specific logs, use SAVESTATUSLOG=FALSE,TEST1[,TEST2...]
NOTE: Status logs will not be saved if there is less than 5% free space on the filesystem hosting the $XYMONHISTLOGS directory. The threshold can be tuned via the "--minimum-free" option.

 

FILES

This module does not rely on any configuration files.

 

SEE ALSO

xymond_channel(8), xymond(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
ENVIRONMENT
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymond_alert.8.html0000664000175000017500000001513211671641417021364 0ustar henrikhenrik Man page of XYMOND_ALERT

XYMOND_ALERT

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond_alert - xymond worker module for sending out alerts  

SYNOPSIS

xymond_channel --channel=page xymond_alert [options]

 

DESCRIPTION

xymond_alert is a worker module for xymond, and as such it is normally run via the xymond_channel(8) program. It receives xymond page- and ack-messages from the "page" channel via stdin, and uses these to send out alerts about failed and recovered hosts and services.

The operation of this module is controlled by the alerts.cfg(5) file. This file holds the definition of rules and recipients, that determine who gets alerts, how often, for what servers etc.

 

OPTIONS

--config=FILENAME
Sets the filename for the alerts.cfg file. The default value is "etc/alerts.cfg" below the Xymon server directory.

--dump-config
Dumps the configuration after parsing it. May be useful to track down problems with configuration file errors.

--checkpoint-file=FILENAME
File where the current state of the xymond_alert module is saved. When starting up, xymond_alert will also read this file to restore the previous state.

--checkpoint-interval=N
Defines how often (in seconds) the checkpoint-file is saved.

--cfid
If this option is present, alert messages will include a line with "cfid:N" where N is the linenumber in the alerts.cfg file that caused this message to be sent. This can be useful to track down problems with duplicate alerts.

--test HOST SERVICE [options]
Shows which alert rules matches the given HOST/SERVICE combination. Useful to debug configuration problems, and see what rules are used for an alert.

The possible options are:
--color=COLORNAME The COLORNAME parameter is the color of the alert: red, yellow or purple.
--duration=SECONDS The SECONDS parameter is the duration of the alert in seconds.
--group=GROUPNAME The GROUPNAME paramater is a groupid string from the analysis.cfg file.
--time=TIMESTRING The TIMESTRING parameter is the time-of-day for the alert, expressed as an absolute time in the epoch format (seconds since Jan 1 1970). This is easily obtained with the GNU date utility using the "+%s" output format.

--trace=FILENAME
Send trace output to FILENAME, This allows for more detailed analysis of how alerts trigger, without having the full debugging enabled.

--debug
Enable debugging output.

 

HOW XYMON DECIDES WHEN TO SEND ALERTS

The xymond_alert module is responsible for sending out all alerts. When a status first goes to one of the ALERTCOLORS, xymond_alert is notified of this change. It notes that the status is now in an alert state, and records the timestamp when this event started, and adds the alert to the list statuses that may potentially trigger one or more alert messages.

This list is then matched against the alerts.cfg configuration. This happens at least once a minute, but may happen more often. E.g. when status first goes into an alert state, this will always trigger the matching to happen.

When scanning the configuration, xymond_alert looks at all of the configuration rules. It also checks the DURATION setting against how long time has elapsed since the event started - i.e. against the timestamp logged when xymond_alert first heard of this event.

When an alert recipient is found, the alert is sent and it is recorded when this recipient is due for his next alert message, based on the REPEAT setting defined for this recipient. The next time xymond_alert scans the configuration for what alerts to send, it will still find this recipient because all of the configuration rules are fulfilled, but an alert message will not be generated until the repeat interval has elapsed.

It can happen that a status first goes yellow and triggers an alert, and later it goes red - e.g. a disk filling up. In that case, xymond_alert clears the internal timer for when the next (repeat) alert is due for all recipients. You generally want to be told when something that has been in a warning state becomes critical, so in that case the REPEAT setting is ignored and the alert is sent. This only happens the first time such a change occurs - if the status switches between yellow and red multiple times, only the first transition from yellow->red causes this override.

When an status recovers, a recovery message may be sent - depending on the configuration - and then xymond_alert forgets everything about this status. So the next time it goes into an alert state, the entire process starts all over again.

 

ENVIRONMENT

MAIL
The first part of a command line used to send out an e-mail with a subject, typically set to "/usr/bin/mail -s" . xymond_alert will add the subject and the mail recipients to form the command line used for sending out email alerts.

MAILC
The first part of a command line used to send out an e-mail without a subject. Typically this will be "/usr/bin/mail". xymond_alert will add the mail recipients to form the command line used for sending out email alerts.

 

FILES

~xymon/server/etc/alerts.cfg

 

SEE ALSO

alerts.cfg(5), xymond(8), xymond_channel(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
HOW XYMON DECIDES WHEN TO SEND ALERTS
ENVIRONMENT
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/trimhistory.8.html0000664000175000017500000001065711671641417021263 0ustar henrikhenrik Man page of TRIMHISTORY

TRIMHISTORY

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

trimhistory - Remove old Xymon history-log entries  

SYNOPSIS

trimhistory --cutoff=TIME [options]

 

DESCRIPTION

The trimhistory tool is used to purge old entries from the Xymon history logs. These logfiles accumulate information about all status changes that have occurred for any given service, host, or the entire Xymon system, and is used to generate the event- and history-log webpages.

Purging old entries can be done while Xymon is running, since the tool takes care not to commit updates to a file if it changes mid-way through the operation. In that case, the update is aborted and the existing logfile is left untouched.

Optionally, this tool will also remove logfiles from hosts that are no longer defined in the Xymon hosts.cfg(5) file. As an extension, even logfiles from services can be removed, if the service no longer has a valid status-report logged in the current Xymon status.

 

OPTIONS

--cutoff=TIME
This defines the cutoff-time when processing the history logs. Entries dated before this time are discarded. TIME is specified as the number of seconds since the beginning of the Epoch. This is easily generated by the GNU date(1) utility, e.g. the following command will trim history logs of all entries prior to Oct. 1st 2004:


    trimhistory --cutoff=`date +%s --date="1 Oct 2004"`

--outdir=DIRECTORY
Normally, files in the XYMONHISTDIR directory are replaced. This option causes trimhistory to save the shortened history logfiles to another directory, so you can verify that the operation works as intended. The output directory must exist.

--drop
Causes trimhistory to delete files from hosts that are not listed in the hosts.cfg(5) file.

--dropsvcs
Causes trimhistory to delete files from services that are not currently tracked by Xymon. Normally these files would be left untouched if only the host exists.

--droplogs
Process the XYMONHISTLOGS directory also, and delete status-logs from events prior to the cut-off time. Note that this can dramatically increase the processing time, since there are often lots and lots of files to process.

--progress[=N]
This will cause trimhistory to output a status line for every N history logs or status-log collections it processes, to indicate how far it has progressed. The default setting for N is 100.

--env=FILENAME
Loads the environment from FILENAME before executing trimhistory.

--debug
Enable debugging output.

 

FILES

$XYMONHISTDIR/allevents
The eventlog of all events that have happened in Xymon.

$XYMONHISTDIR/HOSTNAME
The per-host eventlogs.

$XYMONHISTDIR/HOSTNAME.SERVICE
The per-service eventlogs.

$XYMONHISTLOGS/*/*
The historical status-logs.

 

ENVIRONMENT VARIABLES

XYMONHISTDIR
The directory holding all history logs.

XYMONHISTLOGS
The top-level directory for the historical status-log collections.

HOSTSCFG
The location of the hosts.cfg file, holding the list of currently known hosts in Xymon.

 

SEE ALSO

xymon(7), hosts.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
ENVIRONMENT VARIABLES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymond_distribute.8.html0000664000175000017500000000430311671641417022431 0ustar henrikhenrik Man page of XYMOND_DISTRIBUTE

XYMOND_DISTRIBUTE

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond_distribute - xymond worker module for distributing commands  

SYNOPSIS

xymond_channel --channel=enadis xymond_distribute [options]

 

DESCRIPTION

xymond_distribute is a worker module for xymond, and as such it is normally run via the xymond_channel(8) program. It is used if you have multiple Xymon servers running in a master/slave configuration. xymond_distribute runs on the master server and receives "drop", "rename", "enable" and "disable" messages from xymond. xymond_distribute then forwards these to the other Xymon servers as standard xymon messages. So if a user on the master Xymon server disables a red status, xymond_distribute will forward this to the other Xymon servers so that the change happens everywhere.

NOTE: xymond_distribute does not check to see if a message has already been forwarded, so you can easily create forwarding loops.

 

OPTIONS

--peer=HOSTNAME
The peer that messages are forwarded to. If you have multiple peers, repeat this option.

--debug
Enable debugging output.

 

SEE ALSO

xymond_channel(8), xymond(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/enadis.cgi.8.html0000664000175000017500000000551511671641417020667 0ustar henrikhenrik Man page of ENADIS.CGI

ENADIS.CGI

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

enadis.cgi - CGI program to enable/disable Xymon tests  

SYNOPSIS

enadis.cgi (invoked via CGI from webserver)

 

DESCRIPTION

enadis.cgi is a CGI tool for disabling and enabling hosts and tests monitored by Xymon. You can disable monitoring of a single test, all tests for a host, or multiple hosts - immediately or at a future point in time.

enadis.cgi runs as a CGI program, invoked by your webserver. It is normally run via a wrapper shell-script in the secured CGI directory for Xymon.

enadis.cgi is the back-end script for the enable/disable form present on the "info" status-pages. It can also run in "stand-alone" mode, in which case it displays a web form allowing users to select what to enable or disable.

 

OPTIONS

--no-cookies
Normally, enadis.cgi uses a cookie sent by the browser to initially filter the list of hosts presented. If this is not desired, you can turn off this behaviour by calling acknowledge.cgi with the --no-cookies option. This would normally be placed in the CGI_ENADIS_OPTS setting in cgioptions.cfg(5)

--env=FILENAME
Load the environment from FILENAME before executing the CGI.

--area=NAME
Load environment variables for a specific area. NB: if used, this option must appear before any --env=FILENAME option.

 

FILES

$XYMONHOME/web/maint_{header,form,footer}
HTML template header

 

BUGS

When using alternate pagesets, hosts will only show up on the Enable/Disable page if this is accessed from the primary page in which they are defined. So if you have hosts on multiple pages, they will only be visible for disabling from their main page which is not what you would expect.

 

SEE ALSO

xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
BUGS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man8/xymond_capture.8.html0000664000175000017500000000574011671641417021724 0ustar henrikhenrik Man page of XYMOND_CAPTURE

XYMOND_CAPTURE

Section: Maintenance Commands (8)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymond_capture - catch selected messages from a xymond channel  

SYNOPSIS

xymond_channel --channel=status xymond_capture [options]

 

DESCRIPTION

xymond_capture is a worker module for xymond, and as such it is normally run via the xymond_channel(8) program. It receives messages from xymond via stdin and filters them to select messages based on the hostname, testname or color of the status. By default the resulting messages are printed on stdout, but they can also be fed into a command for further processing.

xymond_capture supports the status, data, client and hostdata channels.

 

OPTIONS

--hosts=PATTERN
Select messages only from hosts matching PATTERN (regular expression).

--exhosts=PATTERN
Exclude messages from hosts matching PATTERN. If used with the --hosts option, then the hostname must match the --hosts pattern, but NOT the --exhosts pattern.

--tests=PATTERN
Select messages only from tests matching PATTERN (regular expression).

--extests=PATTERN
Exclude messages from tests matching PATTERN. If used with the --tests option, then the testname must match the --tests pattern, but NOT the --extests pattern.

--colors=COLOR[,color]
Select messages based on the color of the status message. Multiple colors can be listed, separated by comma. Default: Accept all colors.

--batch-command=COMMAND
Instead of printing the messages to stdout, feed them to COMMAND on stdin. COMMAND can be any command which accepts the mssage on standard input.

--batch-timeout=SECONDS
Collect messages until no messages have arrived in SECONDS seconds, before sending them to the --batch-command COMMAND.

--client
Capture data from the "client" channel instead of the default "status" channel.

--debug
Enable debugging output.

 

SEE ALSO

xymond_channel(8), xymond(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/0000775000175000017500000000000011671641715015613 5ustar henrikhenrikxymon-4.3.7/docs/manpages/man1/xymongen.1.html0000664000175000017500000010271511671641417020511 0ustar henrikhenrik Man page of XYMONGEN

XYMONGEN

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymongen - Xymon webpage generator  

SYNOPSIS

xymongen -?
xymongen --help
xymongen --version
xymongen [options] [output-directory]
(See the OPTIONS section for a description of the available command-line options).

 

DESCRIPTION

xymongen generates the overview webpages for the Xymon monitor. These are the webpages that show the overall status of your hosts, not the detailed status pages for each test.

Note: The data for the webpages is retrieved from the xymond(8) daemon, and xymongen uses the values of the XYMSRV / XYMSERVERS environment variables to determine the network address where xymond can be reached. If you have more than one server listed in XYMSERVERS, make sure the first one is the local Xymon server - this is the one that xymongen will query for data.

 

OPTIONS

xymongen has a large number of command-line options. The options can be used to change the behaviour of xymongen and affect the web pages generated by it.

 

GENERAL OPTIONS

--help or -?
Provide a summary of available command-line options.

--version
Prints the version number of xymongen

--docurl=URL
This option is deprecated, use the HOSTDOCURL setting in xymonserver.cfg(5) instead.

--doccgi=URL
This option is deprecated, use the HOSTDOCURL setting in xymonserver.cfg(5) instead.

--doc-window
Causes links to documentation for hosts and services to open in a new window. The default is to show documentation in the same browser window as the Xymon status.

--htmlextension=.EXTENSION
Sets the filename extension used for the webpages generated by xymongen. By default, an extension of ".html" is used. Note that you need to specify the "dot".

--report[=COLUMNNAME]
With this option, xymongen will send a status message with details of how many hosts were processed, how many pages were generated, any errors that occurred during the run, and some timing statistics. The default columnname is "xymongen".

--htaccess[=htaccess-filename]
Create .htaccess files when new web page directories are created. The content of the .htaccess files are determined by the XYMONHTACCESS environment variable (for the top-level directory with xymon.html and nongreen.html); by the XYMONPAGEHTACCESS variable (for the page-level directories); and by the XYMONSUBPAGEHTACCESS variable for subpage- and subparent-level directories. The filename of the .htaccess files default to ".htaccess" if no filename is given with this option. The XYMONHTACCESS variable is copied verbatim into the top-level .htaccess file. The XYMONPAGEHTACCESS variable may contain a "%s" where the name of the page is inserted. The XYMONSUBPAGEHTACCESS variable may contain two "%s" instances: The first is replaced with the pagename, the second with the subpagename.

--max-eventcount=N
Limit the eventlog on the "All non-green" page to only N events. Default: 100.

--max-eventtime=N
Limit the eventlog on the "All non-green" page to events that happened within the past N minutes. Default: 240.

--no-eventlog
Disable the eventlog normally displayed on the "All non-green" page

--max-ackcount=N
Limit the acknowledgment log on the "All non-green" page to only N events. Default: 25.

--max-acktime=N
Limit the acknowledgment log on the "All non-green" page to acks that happened within the past N minutes. Default: 240.

--no-acklog
Disable the acknowledgement log normally displayed on the "All non-green" page.

--cricitcallog[=Critical log column]
This generates a text-based log of what is shown on the critical.html status page, and sends a status message for the Xymon server itself reflecting the color of the Critical status page. This allows you to track when problems have appeared on the critical status page. The logfile is stored in $XYMONSERVERLOGS/criticalstatus.log

--loadhostsfromxymond
Instead of reading the hosts.cfg file, xymongen will load the hosts.cfg configuration from the xymond daemon. This eliminates the need for reading the hosts.cfg, and if you have xymond and xymongen running on different hosts, it also eliminates the need for copying the hosts.cfg file between systems. Note that the "dispinclude" option in hosts.cfg is ignored when this option is enabled.

 

PAGE LAYOUT OPTIONS

These options affect how the webpages generated by xymongen appear in the browser.

--pages-last
Put page- and subpage-links after hosts.
--pages-first
Put page- and subpage-links before hosts (default).

These two options decide whether a page with links to subpages and hosts have the hosts or the subpages first.

--subpagecolumns=N
Determines the number of columns used for links to pages and subpages. The default is N=1.

--maxrows=N
Column headings on a page are by default only shown at the beginning of a page, subpage or group of hosts. This options causes the column headings to repeat for every N hosts shown.

--pagetitle-links
Normally, only the colored "dots" next to a page or subpage act as links to the page itself. With this option, the page title will link to the page also.

--pagetext-headings
Use the description text from the "page" or "subpage" tags as a heading for the page, instead of the "Pages hosted locally" or other standard heading.

--no-underline-headings
Normally, page headings are underlined using an HTML "horizontal ruler" tag. This option disables the underlining of headings.

--recentgifs[=MINUTES]
Use images named COLOR-recent.gif for tests, where the test status has changed within the past 24 hours. These GIF files need to be installed in the $XYMONHOME/www/gifs/ directory. By default, the threshold is set to 24 hours - if you want it differently, you can specify the time limit also. E.g. "--recentgifs=3h" will show the recent GIFs for only 3 hours after a status change.

--sort-group-only-items
In a normal "group-only" directive, you can specify the order in which the tests are displayed, from left to right. If you prefer to have the tests listed in alphabetical order, use this option - the page will then generate "group-only" groups like it generates normal groups, and sort the tests alphabetically.

--dialupskin=URL
If you want to visually show that a test is a dialup-test, you can use an alternate set of icons for the green/red/yellow>/etc. images by specifying this option. The URL parameter specified here overrides the normal setting from the XYMONSKIN environment variable, but only for dialup tests.

--reverseskin=URL
Same as "--dialupskin", but for reverse tests (tests with '!' in front).

--tooltips=[always,never,main]
Determines which pages use tooltips to show the description of the host (from the COMMENT entry in the hosts.cfg(5) file). If set to always, tooltips are used on all pages. If set to never, tooltips are never used. If set to main, tooltips are used on the main pages, but not on the "All non-green" or "Critical systems" pages.

 

COLUMN SELECTION OPTIONS

These options affect which columns (tests) are included in the webpages generated by xymongen.

--ignorecolumns=test[,test]
The given columns will be completely ignored by xymongen when generating webpages. Can be used to generate reports where you eliminate some of the more noisy tests, like "msgs".

--critical-reds-only
Only red status columns will be included on the Critical page. By default, the Critical page will contain hosts with red, yellow and clear status.

--nongreen-colors=COLOR[,COLOR]
Defines which colors cause a test to appear on the "All non-green" status page. COLOR is red, yellow or purple. The default is to include all three.

--nongreen-ignorecolumns=test[,test]
Same as the --ignorecolumns, but applies to hosts on the "All non-green" page only.

--nongreen-ignorepurples
Deprecated, use "--nongreen-colors" instead.

--nongreen-ignoredialups
Ignore all dialup hosts on the "All non-green" page, including the eventlog.

--no-nongreen
Do not generate the "All non-green" page.

--includecolumns=test[,test]
Always include these columns on "All non-green" page Will include certain columns on the nongreen.html page, regardless of its color. Normally, nongreen.html drops a test-column, if all tests are green. This can be used e.g. to always have a link to the trends column (with the RRD graphs) from your nongreen.html page.

--eventignore=test[,test]
Ignore these tests in the "All non-green" event log display.

 

STATUS PROPAGATION OPTIONS

These options suppress the normal propagation of a status upwards in the page hierarchy. Thus, you can have a test with status yellow or red, but still have the entire page green. It is useful for tests that need not cause an alarm, but where you still want to know the actual status. These options set global defaults for all hosts; you can use the NOPROPRED and NOPROPYELLOW tags in the hosts.cfg(5) file to apply similar limits on a per-host basis.

--nopropyellow=test[,test] or --noprop=test[,test]
Disable upwards status propagation when YELLOW. The "--noprop" option is deprecated and should not be used.

--noproppurple=test[,test]
Disable upwards status propagation when PURPLE.

--nopropred=test[,test]
Disable upwards status propagation when RED or YELLOW.

--nopropack=test[,test]
Disable upwards status propagation when status has been acknowledged. If you want to disable all acked tests from being propageted, use "--nopropack=*".

 

PURPLE STATUS OPTIONS

Purple statuses occur when reporting of a test status stops. A test status is valid for a limited amount of time - normally 30 minutes - and after this time, the test becomes purple.

--purplelog=FILENAME
Generate a logfile of all purple status messages.

 

ALTERNATE PAGESET OPTIONS

--pageset=PAGESETNAME
Build webpages for an alternate pageset than the default. See the PAGESETS section below.

--template=TEMPLATE
Use an alternate template for header and footer files. Typically used together the the "--pageset" option; see the PAGESETS section below.

 

ALTERNATE OUTPUT FORMATS

--wml[=test1,test2,...]
This option causes xymongen to generate a set of WML "card" files that can be accessed by a WAP device (cell phone, PDA etc.) The generated files contain the hosts that have a RED or YELLOW status on tests specified. This option can define the default tests to include - the defaults can be overridden or amended using the "WML:" or "NK:" tags in the hosts.cfg(5) file. If no tests are specified, all tests will be included.

--nstab=FILENAME
Generate an HTML file suitable for a Netscape 6/Mozilla sidebar entry. To actually enable your users to obtain such a sidebar entry, you need this Javascript code in a webpage (e.g. you can include it in the $XYMONHOME/web/stdnormal_header file):

<SCRIPT TYPE="text/javascript">
<!--
function addNetscapePanel() {

   if ((typeof window.sidebar == "object") && 
       (typeof window.sidebar.addPanel == "function"))

      window.sidebar.addPanel ("Xymon", 

            "http://your.server.com/nstab.html","");

   else

      alert("Sidebar only for Mozilla or Netscape 6+");
}
//-->
</SCRIPT>

and then you can include a "Add this to sidebar" link using this as a template:


   <A HREF="javascript:addNetscapePanel();">Add to Sidebar</A>

or if you prefer to have the standard Netscape "Add tab" button, you would do it with


   <A HREF="javascript:addNetscapePanel();">

      <IMG SRC="/gifs/add-button.gif" HEIGHT=45 WIDTH=100

           ALT="[Add Sidebar]" STYLE="border:0">

   </A>

The "add-button.gif" is available from Netscape at http://developer.netscape.com/docs/manuals/browser/sidebar/add-button.gif.

If FILENAME does not begin with a slash, the Netscape sidebar file is placed in the $XYMONHOME/www/ directory.

--nslimit=COLOR
The minimum color to include in the Netscape Sidebar - default is "red", meaning only critical alerts are included. If you want to include warnings also, use "--nslimit=yellow".

--rss
Generate RSS/RDF content delivery stream of your Xymon alerts. This output format can be dynamically embedded in other web pages, much like the live newsfeeds often seen on web sites. Two RSS files will be generated, one reflects the "All non-green" page, the other reflects the "Critical" page. They will be in the "nongreen.rss" and "critical.rss" files, respectively. In addition, an RSS file will be generated for each page and/or subpage listing the hosts present on that page or subpage.
The FILENAME parameter previously allowed on the --rss option is now obsolete.
For more information about RSS/RDF content feeds, please see http://www.syndic8.com/.

--rssextension=.EXTENSION
Sets the filename extension used for the RSS files generated by xymongen. By default, an extension of ".rss" is used. Note that you need to specify the "dot".

--rssversion={0.91|0.92|1.0|2.0}
The desired output format of the RSS/RDF feed. Version 0.91 appears to be the most commonly used format, and is the default if this option is omitted.

--rsslimit=COLOR
The minimum color to include in the RSS feed - default is "red", meaning only critical alerts are included. If you want to include warnings also, use "--rsslimit=yellow".

 

OPTIONS USED BY CGI FRONT-ENDS

--reportopts=START:END:DYNAMIC:STYLE
Invoke xymongen in report-generation mode. This is normally used by the report.cgi(1) CGI script, but may also be used directly when pre-generating reports. The START parameter is the start-time for the report in Unix time_t format (seconds since Jan 1st 1970 00:00 UTC); END is the end-time for the report; DYNAMIC is 0 for a pre-built report and 1 for a dynamic (on-line) report; STYLE is "crit" to include only critical (red) events, "nongr" to include all non-green events, and "all" to include all events.

--csv=FILENAME
Used together with --reportopts, this causes xymongen to generate an availability report in the form of a comma-separated values (CSV) file. This format is commonly used for importing into spreadsheets for further processing.
The CSV file includes Unix timestamps. To display these as human readable times in Excel, the formula =C2/86400+DATEVALUE(1-jan-1970) (if you have the Unix timestamp in the cell C2) can be used. The result cell should be formatted as a date/time field. Note that the timestamps are in UTC, so you may also need to handle local timezone and DST issues yourself.

--csvdelim=DELIMITER
By default, a comma is used to delimit fields in the CSV output. Some non-english spreadsheets use a different delimiter, typically semi-colon. To generate a CSV file with the proper delimiter, you can use this option to set the character used as delimiter. E.g. "--csvdelim=;" - note that this normally should be in double quotes, to prevent the Unix shell from interpreting the delimiter character as a command-line delimiter.

--snapshot=TIME
Generate a snapshot of the Xymon pages, as they appeared at TIME. TIME is given as seconds since Jan 1st 1970 00:00 UTC. Normally used via the snapshot.cgi(1) CGI script.

 

DEBUGGING OPTIONS

--debug
Causes xymongen to dump large amounts of debugging output to stdout, if it was compiled with the -DDEBUG enabled. When reporting a problem with xymongen, please try to reproduce the problem and provide the output from running xymongen with this option.

--timing
Dump information about the time spent by various parts of xymongen to stdout. This is useful to see what part of the processing is responsible for the run-time of xymongen.
Note: This information is also provided in the output sent to the Xymon display when using the "--report" option.

 

BUILDING ALTERNATE PAGESETS

With version 1.4 of xymongen comes the possibility to generate multiple sets of pages from the same data.
Suppose you have two groups of people looking at the Xymon webpages. Group A wants to have the hosts grouped by the client, they belong to. This is how you have Xymon set up - the default pageset. Now group B wants to have the hosts grouped by operating system - let us call it the "os" set. Then you would add the page layout to hosts.cfg like this:

ospage win Microsoft Windows
ossubpage win-nt4 MS Windows NT 4
osgroup NT4 File servers
osgroup NT4 Mail servers
ossubpage win-xp MS Windows XP
ospage unix Unix
ossubpage unix-sun Solaris
ossubpage unix-linux Linux

This defines a set of pages with one top-level page (the xymon.html page), two pages linked from xymon.html (win.html and unix.html), and from e.g. the win.html page there are subpages win-nt4.html and win-xp.html
The syntax is identical to the normal "page" and "subpage" directives in hosts.cfg, but the directive is prefixed with the pageset name. Dont put any hosts in-between the page and subpage directives - just add all the directives at the top of the hosts.cfg file.
How do you add hosts to the pages, then ? Simple - just put a tag "OS:win-xp" on the host definition line. The "OS" must be the same as prefix used for the pageset names, but in uppercase. The "win-xp" must match one of the pages or subpages defined within this pageset. E.g.

207.46.249.190 www.microsoft.com # OS:win-xp http://www.microsoft.com/
64.124.140.181 www.sun.com # OS:unix-sun http://www.sun.com/

If you want the host to appear inside a group defined on that page, you must identify the group by number, starting at 1. E.g. to put a host inside the "NT4 Mail servers" group in the example above, use "OS:win-nt4,2" (the second group on the "win-nt4" page).
If you want the host to show up on the frontpage instead of a subpage, use "OS:*" .

All of this just defines the layout of the new pageset. To generate it, you must run xymongen once for each pageset you define - i.e. create an extension script like this:

#!/bin/sh

XYMONWEB="/xymon/os" $XYMONHOME/bin/xymongen \
        --pageset=os --template=os \
        $XYMONHOME/www/os/

Save this to $XYMONHOME/ext/os-display.sh, and set this up to run as a Xymon extension; this means addng an extra section to tasks.cfg to run it.

This generates the pages. There are some important options used here:
* XYMONWEB="/xymon/os" environment variable, and the
  "$XYMONHOME/www/os/" option work together, and places the 
  new pageset HTML files in a subdirectory off the normal 
  Xymon webroot. If you normally access the Xymon pages as 
  "http://xymon.acme.com/xymon/", you will then access 
  the new pageset as "http://xymon.acme.com/xymon/os/"
  NB: The directory given as XYMONWEB must contain a symbolic 
  link to the $XYMONHOME/www/html/ directory, or links to 
  individual status messages will not work. Similar links 
  should be made for the gifs/, help/ and notes/ 
  directories.
* "--pageset=os" tells xymongen to structure the webpages
  using the "os" layout, instead of the default layout.
* "--template=os" tells xymongen to use a different set of
  header- and footer-templates. Normally xymongen uses the 
  standard template in $XYMONHOME/web/stdnormal_header and 
  .../stdnormal_footer - with this option, it will instead use 
  the files "os_header" and "os_footer" from the 
  $XYMONHOME/web/ directory. This allows you to customize 
  headers and footers for each pageset. If you just want 
  to use the normal template, you can omit this option.

 

USING XYMONGEN FOR REPORTS

xymongen reporting is implemented via drop-in replacements for the standard Xymon reporting scripts (report.sh and reportlog.sh) installed in your webservers cgi-bin directory.

These two shell script have been replaced with two very small shell-scripts, that merely setup the Xymon environment variables, and invoke the report.cgi(1) or reportlog.cgi(1) scripts in $XYMONHOME/bin/

You can use xymongen command-line options when generating reports, e.g. to exclude certain types of tests (e.g. "--ignorecolumns=msgs") from the reports, to specify the name of the trends- and info- columns that should not be in the report, or to format the report differently (e.g. "--subpagecolumns=2"). If you want certain options to be used when a report is generated from the web interface, put these options into your $XYMONHOME/etc/xymonserver.cfg file in the XYMONGENREPOPTS environment variable.

The report files generated by xymongen are stored in individual directories (one per report) below the $XYMONHOME/www/rep/ directory. These should be automatically cleaned up - as new reports are generated, the old ones get removed.

After installing, try generating a report. You will probably see that the links in the upper left corner (to ack.html, nongreen.html etc.) no longer works. To fix these, change your $XYMONHOME/web/repnormal_header file so these links do not refer to "&XYMONWEB" but to the normal URL prefix for your Xymon pages.

 

SLA REPORTING

xymongen reporting allows for the generation of true SLA (Service Level Agreement) reports, also for service periods that are not 24x7. This is enabled by defining a "REPORTTIME:timespec" tag for the hosts to define the service period, and optionally a "WARNPCT:level" tag to define the agreed availability.

Note: See hosts.cfg(5) for the exact syntax of these options.

"REPORTTIME:timespec" specifies the time of day when the service is expected to be up and running. By default this is 24 hours a day, all days of the week. If your SLA only covers Mon-Fri 7am - 8pm, you define this as "REPORTTIME=W:0700:2000", and the report generator will then compute both the normal 24x7 availability but also a "SLA availability" which only takes the status of the host during the SLA period into account.

The DOWNTIME:timespec parameter affects the SLA availability calculation. If an outage occurs during the time defined as possible "DOWNTIME", then the failure is reported with a status of "blue". (The same color is used if you "disable" then host using the Xymon "disable" function). The time when the test status is "blue" is not included in the SLA calculation, neither in the amount of time where the host is considered down, nor in the total amount of time that the report covers. So "blue" time is effectively ignored by the SLA availability calculation, allowing you to have planned downtime without affecting the reported SLA availability.

Example: A host has "DOWNTIME:*:0700:0730 REPORTTIME=W:0600:2200" because it is rebooted every day between 7am and 7.30am, but the service must be available from 6am to 10pm. For the day of the report, it was down from 7:10am to 7:15am (the planned reboot), but also from 9:53pm to 10:15pm. So the events for the day are:


   0700 : green for 10 minutes (600 seconds)
   0710 : blue for 5 minutes (300 seconds)
   0715 : green for 14 hours 38 minutes (52680 seconds)
   2153 : red for 22 minutes (1320 seconds)
   2215 : green

The service is available for 600+52680 = 53280 seconds. It is down (red) for 420 seconds (the time from 21:53 until 22:00 when the SLA period ends). The total time included in the report is 15 hours (7am - 10pm) except the 5 minutes blue = 53700 seconds. So the SLA availability is 53280/53700 = 99,22%

The "WARNPCT:level" tag is supported in the hosts.cfg file, to set the availability threshold on a host-by-host basis. This threshold determines whether a test is reported as green, yellow or red in the reports. A default value can be set for all hosts with the via the XYMONREPWARN environment variable, but overridden by this tag. The level is given as a percentage, e.g. "WARNPCT:98.5"

 

PRE-GENERATED REPORTS

Normally, xymongen produce reports that link to dynamically generated webpages with the detailed status of a test (via the reportlog.sh CGI script).

It is possible to have xymongen produce a report without these dynamic links, so the report can be exported to another server. It may also be useful to pre-generate the reports, to lower the load by having multiple users generate the same reports.

To do this, you must run xymongen with the "--reportopts" option to select the time interval that the report covers, the reporting style (critical, non-green, or all events), and to request that no dynamic pages are to be generated.

The syntax is:


   xymongen --reportopts=starttime:endtime:nodynamic:style

"starttime" and "endtime" are specified as Unix time_t values, i.e. seconds since Jan 1st 1970 00:00 GMT. Fortunately, this can easily be computed with the GNU date utility if you use the "+%s" output option. If you don't have the GNU date utility, either pick that up from www.gnu.org; or you can use the "etime" utility for the same purpose, which is available from the archive at www.deadcat.net.

"nodynamic" is either 0 (for dynamic pages, the default) or 1 (for no dynamic, i.e. pre-generated, pages).

"style" is either "crit" (include critical i.e. red events only), "nongr" (include all non-green events), or "all" (include all events).

Other xymongen options can be used, e.g. "--ignorecolumns" if you want to exclude certain tests from the report.

You will normally also need to specify the XYMONWEB environment variable (it must match the base URL for where the report will be made accessible from), and an output directory where the report files are saved. If you specify XYMONWEB, you should probably also define the XYMONHELPSKIN and XYMONNOTESSKIN environment variables. These should point to the URL where your Xymon help- and notes-files are located; if they are not defined, the links to help- and notes-files will point inside the report directory and will probably not work.

So a typical invocation of xymongen for a static report would be:


  START=`date +%s --date="22 Jun 2003 00:00:00"`
  END=`date +%s --date="22 Jun 2003 23:59:59"`
  XYMONWEB=/reports/bigbrother/daily/2003/06/22 \
  XYMONHELPSKIN=/xymon/help \
  XYMONNOTESSKIN=/xymon/notes \
  xymongen --reportopts=$START:$END:1:crit \
        --subpagecolumns=2 \
        /var/www/docroot/reports/xymon/daily/2003/06/22

The "XYMONWEB" setting means that the report will be available with a URL of "http://www.server.com/reports/xymon/daily/2003/06/22". The report contains internal links that use this URL, so it cannot be easily moved to another location.

The last parameter is the corresponding physical directory on your webserver matching the XYMONWEB URL. You can of course create the report files anywhere you like - perhaps on another machine - and then move them to the webserver later on.

Note how the date(1) utility is used to calculate the start- and end-time parameters.

 

SEE ALSO

hosts.cfg(5), xymonserver.cfg(5), tasks.cfg(5), report.cgi(1), snapshot.cgi(1), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
GENERAL OPTIONS
PAGE LAYOUT OPTIONS
COLUMN SELECTION OPTIONS
STATUS PROPAGATION OPTIONS
PURPLE STATUS OPTIONS
ALTERNATE PAGESET OPTIONS
ALTERNATE OUTPUT FORMATS
OPTIONS USED BY CGI FRONT-ENDS
DEBUGGING OPTIONS
BUILDING ALTERNATE PAGESETS
USING XYMONGEN FOR REPORTS
SLA REPORTING
PRE-GENERATED REPORTS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/xymonpage.cgi.1.html0000664000175000017500000000341211671641417021407 0ustar henrikhenrik Man page of XYMONPAGE

XYMONPAGE

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymonpage - Utility to show a webpage using header and footer  

SYNOPSIS

xymonpage [options]

 

DESCRIPTION

xymonpage is a tool to generate a webpage in the Xymon style, with a standard header- and footer as well as a Xymon background. The data to present on the webpage, apart from the header and footer, are passed to xymonpage in stdin. The generated webpage is printed to stdout.

 

OPTIONS

--env=FILENAME
Loads the environment defined in FILENAME before executing the CGI script.

--hffile=PREFIX
Use the header- and footer-files in $XYMONHOME/web/PREFIX_header and PREFIX_footer. If not specified, stdnormal_header and stdnormal_footer are used.

--color=COLOR
Set the background color of the generated webpage to COLOR. Default: Blue

--debug
Enable debugging output.

 

SEE ALSO

xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/combostatus.1.html0000664000175000017500000000601011671641417021177 0ustar henrikhenrik Man page of COMBOSTATUS

COMBOSTATUS

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

combostatus - Xymon combination test tool  

SYNOPSIS

combostatus --help
combostatus --version
combostatus [--debug] [--quiet]

 

DESCRIPTION

combostatus is a Xymon extension script that runs on the Xymon server. It combines the results of one or more of the normal Xymon test results into a combined test result, using standard arithmetic og logical operators.

The resulting tests are sent to the Xymon display server as any normal test - so all of the standard Xymon functions (history, statistics etc.) are available for the combined tests.

The tool was born from the need to monitor systems with built-in redundancy and automatic failover - e.g. load-balanced web servers. But other uses are possible.

 

OPTIONS

--error-colors=COLOR[,COLOR]
Specify which colors trigger an error status. By default only a "red" status counts as an error color - all other colors, including yellow, will count as "green" when evaluating the combined status. COLOR is "red", "yellow", "blue", "purple" or "clear".

--quiet
Normally, the test status sent by combostatus includes information about the underlying test results used to determine the current value of the combined test. "--quiet" eliminates this information from the test status page.

--debug
Provide debugging output for use in troubleshooting problems with combostatus.

--no-update
Dont send any status messages - instead, the result of the combotests is simply dumped to stdout. Useful for debugging.

 

FILES

$XYMONHOME/etc/combo.cfg
Configuration file for combostatus, where the combined tests are defined
$XYMONHOME/etc/tasks.cfg
Configuration file controlling when combostatus is run.

 

SEE ALSO

combo.cfg(5), hosts.cfg(5), xymonserver.cfg(5), tasks.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/statusreport.cgi.1.html0000664000175000017500000001004011671641417022152 0ustar henrikhenrik Man page of STATUSREPORT.CGI

STATUSREPORT.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

statusreport.cgi - CGI program to report a status for a group of servers  

SYNOPSIS

statusreport.cgi --column=COLUMNNAME [options]

 

DESCRIPTION

statusreport.cgi is a CGI tool to generate a simple HTML report showing the current status of a single column for a group of Xymon hosts.

E.g. You can use this report to get an overview of all of the SSL certificates that are about to expire.

The generated webpage is a simple HTML table, suitable for copying into other documents or e-mail.

statusreport.cgi runs as a CGI program, invoked by your webserver. It is normally run via a wrapper shell-script in the CGI directory for Xymon.

 

EXAMPLES

The Xymon installation includes two web report scripts using this CGI tool: The certreport.sh script generates a list of SSL server certificates that are yellow or red (i.e. they will expire soon); and the nongreen.sh script generates a report of all statuses that are currently non-green. These can be accessed from a web browser through a URL referencing the script in the Xymon CGI directory (e.g. "/xymon-cgi/xymon-nongreen.sh").

 

OPTIONS

--column=COLUMNNAME
Report the status of the COLUMNNAME column.

--all
Report the status for all hosts known to Xymon. By default, this tool reports only on the hosts found on the current page from where the CGI was invoked (by looking at the "pagepath" cookie).

--filter=CRITERIA
Only report on statuses that match the CRITERIA setting. See the xymon(1) man-page - in the "xymondboard" command description - for details about specifying filters.

--heading=HTML
Defines the webpage heading - i.e. the "title" tag in the generated HTML code.

--show-column
Include the column name in the display.

--show-colors
Show the status color on the generated webpage. The default is to not show the status color.

--no-colors
Do not include text showing the current color of each status in the report. This is the default.

--show-summary
Show only a summary of the important lines in the status message. By default, the entire status message appears in the generated HTML code. This option causes the first non-blank line of the status message to be shown, and also any lines beginning with "&COLOR" which is used by many status messages to point out lines of interest (non-green lines only, though).

--show-message
Show the entire message on the webpage. This is the default.

--link
Include HTML links to the host "info" page, and the status page.

--embedded
Only generate the HTML table, not a full webpage. This can be used to embed the status report into an external webpage.

--env=FILENAME
Load the environment from FILENAME before executing the CGI.

--area=NAME
Load environment variables for a specific area. NB: if used, this option must appear before any --env=FILENAME option.

 

SEE ALSO

xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
EXAMPLES
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/xymongrep.1.html0000664000175000017500000001370711671641417020677 0ustar henrikhenrik Man page of XYMONGREP

XYMONGREP

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymongrep - pick out lines in hosts.cfg  

SYNOPSIS

xymongrep --help
xymongrep --version
xymongrep [--noextras] [--test-untagged] [--web] [--net] TAG [TAG...]

 

DESCRIPTION

xymongrep(1) is for use by extension scripts that need to pick out the entries in a hosts.cfg file that are relevant to the script.

The utility accepts test names as parameters, and will then parse the hosts.cfg file and print out the host entries that have at least one of the wanted tests specified. Tags may be given with a trailing asterisk '*', e.g. "xymongrep http*" is needed to find all http and https tags.

The xymongrep utility supports the use of "include" directives inside the hosts.cfg file, and will find matching tags in all included files.

If the DOWNTIME or SLA tags are used in the hosts.cfg(5) file, these are interpreted relative to the current time. xymongrep then outputs a "INSIDESLA" or "OUTSIDESLA" tag for easier use by scripts that want to check if the current time is inside or outside the expected uptime window.

 

OPTIONS

--noextras
Remove the "testip", "dialup", "INSIDESLA" and "OUTSIDESLA" tags from the output.

--test-untagged
When using the XYMONNETWORK environment variable to test only hosts on a particular network segment, xymonnet will ignore hosts that do not have any "NET:x" tag. So only hosts that have a NET:$XYMONNETWORK tag will be tested.
With this option, hosts with no NET: tag are included in the test, so that all hosts that either have a matching NET: tag, or no NET: tag at all are tested.

--no-down[=TESTNAME]
xymongrep will query the Xymon server for the current status of the "conn" test, and if TESTNAME is specified also for the current state of the specified test. If the status of the "conn" test for a host is non-green, or the status of the TESTNAME test is disabled, then this host is ignored and will not be included in the output. This can be used to ignore hosts that are down, or hosts where the custom test is disabled.

--web
Search the hosts.cfg file following include statements as a Xymon web-server would.

--net
Search the hosts.cfg file following include statements as when running xymonnet.

 

EXAMPLE

If your hosts.cfg file looks like this


   192.168.1.1   www.test.com  # ftp telnet !oracle
   192.168.1.2   db1.test.com  # oracle
   192.168.1.3   mail.test.com # smtp

and you have a custom Xymon extension script that performs the "oracle" test, then running "xymongrep oracle" would yield


   192.168.1.1   www.test.com  # !oracle
   192.168.1.2   db1.test.com  # oracle

so the script can quickly find the hosts that are of interest.

Note that the reverse-test modifier - "!oracle" - is included in the output; this also applies to the other test modifiers defined by Xymon (the dial-up and always-true modifiers).

If your extension scripts use more than one tag, just list all of the interesting tags on the command line.

xymongrep also supports the "NET:location" tag used by xymonnet, so if your script performs network checks then it will see only the hosts that are relevant for the test location that the script currently executes on.

 

USE IN EXTENSION SCRIPTS

To integrate xymongrep into an existing script, look for the line in the script that grep's in the $HOSTSCFG file. Typically it will look somewhat like this:


   $GREP -i "^[0-9].*#.*TESTNAME" $HOSTSCFG | ... code to handle test

Instead of the grep, we will use xymongrep. It then becomes


   $XYMONHOME/bin/xymongrep TESTNAME | ... code to handle test

which is simpler, less error-prone and more efficient.

 

ENVIRONMENT VARIABLES

XYMONNETWORK
If set, xymongrep outputs only lines from hosts.cfg that have a matching NET:$XYMONNETWORK setting.

HOSTSCFG
Filename for the Xymon hosts.cfg(5) file.

 

FILES

$HOSTSCFG
The Xymon hosts.cfg file

 

SEE ALSO

hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
EXAMPLE
USE IN EXTENSION SCRIPTS
ENVIRONMENT VARIABLES
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/logfetch.1.html0000664000175000017500000000657611671641417020450 0ustar henrikhenrik Man page of LOGFETCH

LOGFETCH

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

logfetch - Xymon client data collector  

SYNOPSIS

logfetch CONFIGFILE STATUSFILE

 

DESCRIPTION

logfetch is part of the Xymon client. It is responsible for collecting data from logfiles, and other file-related data, which is then sent to the Xymon server for analysis.

logfetch uses a configuration file, which is automatically retrieved from the Xymon server. There is no configuration done locally. The configuration file is usually stored in the $XYMONHOME/tmp/logfetch.cfg file, but editing this file has no effect since it is re-written with data from the Xymon server each time the client runs.

logfetch stores information about what parts of the monitored logfiles have been processed already in the $XYMONHOME/tmp/logfetch.status file. This file is an internal file used by logfetch, and should not be edited. If deleted, it will be re-created automatically.

 

SECURITY

logfetch needs read access to the logfiles it should monitor. If you configure monitoring of files or directories through the "file:" and "dir:" entries in client-local.cfg(5) then logfetch will require at least read-acces to the directory where the file is located. If you request checksum calculation for a file, then it must be readable by the Xymon client user.

Do NOT install logfetch as suid-root. There is no way that logfetch can check whether the configuration file it uses has been tampered with, so installing logfetch with suid-root privileges could allow an attacker to read any file on the system by using a hand-crafted configuration file. In fact, logfetch will attempt to remove its own suid-root setup if it detects that it has been installed suid-root.

 

ENVIRONMENT VARIABLES

DU
Command used to collect information about the size of directories. By default, this is the command du -k. If the local du-command on the client does not recognize the "-k" option, you should set the DU environment variable in the $XYMONHOME/etc/xymonclient.cfg file to a command that does report directory sizes in kilobytes.

 

FILES

$XYMONHOME/tmp/logfetch.cfg
$XYMONHOME/tmp/logfetch.status

 

SEE ALSO

xymon(7), analysis.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
SECURITY
ENVIRONMENT VARIABLES
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/confreport.cgi.1.html0000664000175000017500000000563311671641417021570 0ustar henrikhenrik Man page of CONFREPORT.CGI

CONFREPORT.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

confreport.cgi - Xymon Configuration report  

SYNOPSIS

confreport.cgi

 

DESCRIPTION

confreport.cgi is invoked as a CGI script via the confreport.sh CGI wrapper.

confreport.cgi provides a plain HTML (Web) report of the Xymon configuration for a group of hosts; which hosts are included is determined by the hosts available on the webpage from where the CGI script is invoked.

The configuration report include the hostnames, a list of the statuses monitored for each host, and if applicable any configuration settings affecting these. Alerts that may be triggered by status changes are also included.

The report is plain HTML without any images included, and therefore suitable for inclusion into e-mails or other documents that may be accessed outside the Xymon system.

 

OPTIONS

--critical
Report only on the statuses that are configured to show up on the Critical Systems view.

--old-nk-config
Use the deprecated NK tag in hosts.cfg to determine if tests appear on the Critical Systems view.

--env=FILENAME
Loads the environment defined in FILENAME before executing the CGI script.

--area=NAME
Load environment variables for a specific area. NB: if used, this option must appear before any --env=FILENAME option.

--debug
Enables debugging output.

--nkconfig=FILENAME
Use FILENAME as the configuration file for the Critical Systems information. The default is to load this from $XYMONHOME/etc/critical.cfg

 

BUGS

Client-side configuration done in the analysis.cfg(5) is not currently reflected in the report.

Critical Systems view configuration is not reflected in the report.

 

SEE ALSO

hosts.cfg(5), alerts.cfg(5), analysis.cfg(5), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
BUGS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/xymoncfg.1.html0000664000175000017500000000376111671641417020500 0ustar henrikhenrik Man page of XYMONCFG

XYMONCFG

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymoncfg - output the full hosts.cfg file  

SYNOPSIS

xymoncfg [--web] [--net] [filename]

 

DESCRIPTION

xymoncfg(1) dumps the full hosts.cfg file to stdout. It follows "include" tags in the hosts.cfg files, and prints the full contents as seen by the xymongen(1) and xymonnet(1) utilities.

If no filename is given, xymoncfg displays the file pointed to by the HOSTSCFG environment variable.

 

OPTIONS

--web
Show the hosts.cfg file following include statements as a Xymon web-server would.

--net
Show the hosts.cfg file following include statements as done when running xymonnet.

 

ENVIRONMENT VARIABLES

HOSTSCFG
Filename for the hosts.cfg(5) file.

 

SEE ALSO

hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
ENVIRONMENT VARIABLES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/xymonping.1.html0000664000175000017500000001152411671641417020672 0ustar henrikhenrik Man page of XYMONPING

XYMONPING

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymonping - Xymon ping tool  

SYNOPSIS

xymonping [--retries=N] [--timeout=N] [IP-adresses]

 

DESCRIPTION

xymonping(1) is used for ping testing of the hosts monitored by the xymon(7) monitoring system. It reads a list of IP adresses from stdin, and performs a "ping" check to see if these hosts are alive. It is normally invoked by the xymonnet(1) utility, which performs all of the Xymon network tests.

Optionally, if a list of IP-adresses is passed as command-line arguments, it will ping those IP's instead of reading them from stdin.

xymonping only handles IP-adresses, not hostnames.

xymonping was inspired by the fping(1) tool, but has been written from scratch to implement a fast ping tester without much of the overhead found in other such utilities. The output from xymonping is similar to that of "fping -Ae".

xymonping probes multiple systems in parallel, and the runtime is therefore mostly dependant on the timeout-setting and the number of retries. With the default options, xymonping takes approximately 18 seconds to ping all hosts (tested with an input set of 1500 IP adresses).

 

SUID-ROOT INSTALLATION REQUIRED

xymonping needs to be installed with suid-root privileges, since it requires a "raw socket" to send and receive ICMP Echo (ping) packets.

xymonping is implemented such that it immediately drops the root privileges, and only regains them to perform two operations: Obtaining the raw socket, and optionally binding it to a specific source address. These operations are performed as root, the rest of the time xymonping runs with normal user privileges. Specifically, no user-supplied data or network data is used while running with root privileges. Therefore it should be safe to provide xymonping with the necessary suid-root privileges.

 

OPTIONS

--retries=N
Sets the number of retries for hosts that fail to respond to the initial ping, i.e. the number of ping probes sent in addition to the initial probe. The default is --retries=2, to ping a host 3 times before concluding that it is not responding.

--timeout=N
Determines the timeout (in seconds) for ping probes. If a host does not respond within N seconds, it is regarded as unavailable, unless it responds to one of the retries. The default is --timeout=5.

--responses=N
xymonping normally stops pinging a host after receiving a single response, and uses that to determine the round-trip time. If the first response takes longer to arrive - e.g. because of additional network overhead when first determining the route to the target host - it may skew the round-trip-time reports. You can then use this option to require N responses, and xymonping will calculate the round-trip time as the average of all of responsetimes.

--max-pps=N
Maximum number of packets per second. This limits the number of ICMP packets xymonping will send per second, by enforcing a brief delay after each packet is sent. The default setting is to send a maximum of 50 packets per second. Note that increasing this may cause flooding of the network, and since ICMP packets can be discarded by routers and other network equipment, this can cause erratic behaviour with hosts recorded as not responding when they are in fact OK.

--source=ADDRESS
Use ADDRESS as the source IP address of the ping packets sent. On multi-homed systems, allows you to select the source IP of the hosts going out, which might be necessary for ping to work.

--debug
Enable debug output. This prints out all packets sent and received.

 

SEE ALSO

xymon(7), xymonnet(1), fping(1)


 

Index

NAME
SYNOPSIS
DESCRIPTION
SUID-ROOT INSTALLATION REQUIRED
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/clientupdate.1.html0000664000175000017500000001376111671641417021330 0ustar henrikhenrik Man page of CLIENTUPDATE

CLIENTUPDATE

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

clientupdate - Xymon client update utility  

SYNOPSIS

clientupdate [options]

 

DESCRIPTION

clientupdate is part of the Xymon client. It is responsible for updating an existing client installation from a central repository of client packages stored on the Xymon server.

When the Xymon client sends a normal client report to the Xymon server, the server responds with the section of the client-local.cfg(5) file that is relevant to this client. Included in this may be a "clientversion" value. The clientversion received from the server is compared against the current clientversion installed on the client, as determined by the contents of the $XYMONHOME/etc/clientversion.cfg file. If the two versions are not identical, clientupdate is launched to update the client installation.

 

OPTIONS

--level
Report the current clientversion.

--update=NEWVERSION
Attempt to update the client to NEWVERSION by fetching this version of the client software from the Xymon server.

--reexec
Used internally during the update process, see OPERATION below.

--remove-self
Used internally during the update process. This option causes the running clientupdate utility to delete itself - it is used during the update to purge a temporary copy of the clientupdate utility that is installed in $XYMONTMP.

 

USING CLIENTUPDATE IN XYMON

To manage updating clients without having to logon to each server, you can use the clientupdate utility. This is how you setup the release of a new client version.

Create the new client
Setup the new client $XYMONHOME directory, e.g. by copying an existing client installation to an empty directory and modifying it for your needs. It is a good idea to delete all files in the tmp/ and logs/ directories, since there is no need to copy these over to all of the clients. Pay attention to the etc/ files, and make sure that they are suitable for the systems where you want to deploy this new client. You can add files - e.g. extension scripts in the ext/ directory - but the clientupdate utility cannot delete or rename files.

Package the client
When your new client software is ready, create a tar-file of the new client. All files in the tar archive must have file names relative to the clients' $XYMONHOME (usually, ~xymon/client/). Save the tar file on the Xymon server in ~xymon/server/download/somefile.tar. Don't compress it. It is recommended that you use some sort of operating-system and version-numbering scheme for the filename, but you can choose whatever filename suits you - the only requirement is that it must end with ".tar". The part of the filename preceding ".tar" is what Xymon will use as the "clientversion" ID.

Configure which hosts receive the new client
In the client-local.cfg(5) file, you must now setup a clientversion:ID line where the ID matches the filename you used for the tar-file. So if you have packaged the new client into the file linux.v2.tar, then the corresponding entry in client-local.cfg would be clientversion:linux.v2.

Wait for xymond to reload client-local.cfg
xymond will automatically reload the client-local.cfg file after at most 10 minutes. If you want to force an immediate reload, send a SIGHUP signal to the xymond process.

Wait for the client to update
The next time the client contacts the Xymon server to send the client data, it will notice the new clientversion setting in client-local.cfg, and will run clientupdate to install the new client software. So when the client runs the next time, it will use the new client software.

 

OPERATION

clientupdate runs in two steps:

Re-exec step
The first step is when clientupdate is first invoked from the xymonclient.sh script with the "--re-exec" option. This step copies the clientupdate program from $XYMONHOME/bin/ to a temporary file in the $XYMONTMP directory. This is to avoid conflicts when the update procedure installs a new version of the clientupdate utility itself. Upon completion of this step, the clientupdate utility automatically launches the next step by running the program from the file in $XYMONTMP.

Update step
The second step downloads the new client software from the Xymon server. The new software must be packed into a tar file, which clientupdate then unpacks into the $XYMONHOME directory.

 

ENVIRONMENT VARIABLES

clientupdate uses several of the standard Xymon environment variables, including XYMONHOME and XYMONTMP.

 

SEE ALSO

xymon(7), xymon(1), client-local.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
USING CLIENTUPDATE IN XYMON
OPERATION
ENVIRONMENT VARIABLES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/hostgraphs.cgi.1.html0000664000175000017500000000534111671641417021565 0ustar henrikhenrik Man page of HOSTGRAPHS.CGI

HOSTGRAPHS.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

hostgraphs.cgi - CGI program to show multiple graphs  

SYNOPSIS

hostgraph.cgi

 

DESCRIPTION

hostgraph.cgi is invoked as a CGI script via the hostgraph.sh CGI wrapper.

If no parameters are provided when invoked, it will present a form where the user can select a time period, one or more hosts, and a set of graphs.

The parameters selected by the user are passed to a second invocation of hostgraph.cgi, and result in a webpage showing a list of graph images based on the trend data stored about the hosts.

If multiple graph-types are selected, hostgraph.cgi will display a list of graphs, with one graph per type.

If multiple hosts are selected, hostgraph.cgi will attempt to display a multi-host graph for each type where the graphs for all hosts are overlayed in a single image, allowing for easy comparison of the hosts.

The hostlist uses the PAGEPATH cookie provided by Xymon webpages to select the list of hosts to present. Only the hosts visible on the page where hostgraph.cgi is invoked from will be visible.

The resulting graph page can be bookmarked, but the bookmark also fixates the time period shown.

 

OPTIONS

--env=FILENAME
Loads the environment defined in FILENAME before executing the CGI script.

 

BUGS

This utility is experimental. It may change in a future release of Xymon.

It is possible for the user to select graphs which do not exist. This results in broken image links.

The set of graph-types is fixed in the server/web/hostgraphs_form template and does not adjust to which graphs are available.

If the tool is invoked directly, all hosts defined in Xymon will be listed.

 

SEE ALSO

hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
BUGS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/history.cgi.1.html0000664000175000017500000001140111671641417021076 0ustar henrikhenrik Man page of HISTORY.CGI

HISTORY.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

history.cgi - CGI program to display service history  

SYNOPSIS

history.cgi

 

DESCRIPTION

history.cgi is invoked as a CGI script via the history.sh CGI wrapper. It is passed a QUERY_STRING environment variable with the following parameters:


   HISTFILE (a Xymon service history file)
   ENTRIES (the number of entries to show)
  The following non-standard parameters are handled by the Xymon version of history.cgi:


   IP (IP address of host - for display purposes only)
   PIXELS (width of colorbar when in pixel-mode)
   ENDTIME (when the colorbar begins, a time_t value)
   BARSUMS (which colorbars and summaries to show)

history.cgi analyses the service history file for changes that have occurred within the past 24 hours, and build a colorbar showing the status of the service over this period of time. A statistics summary is also produced, listing the amount of time for each status (green, yellow, red, purple, blue, clear).

Finally, a summary of the last N events is given, with links to the actual event logs.

Unlike the standard history.sh script, history.cgi provides a colorbar and statistics summaries also covering the past 1 week, 4 weeks and 1 year of data. Via links it is possible to browse the entire history of the service at the requested interval.

Note that since the resolution of the display is limited, events may be too short to show up on a colorbar; also, the exact placement of an event may not fully match up with the time-markers.

The graphs should correctly handle the display of months with different number of days, as well as the display of periods that involve beginning and end of Daylight Savings Time, if this occurs in your timezone.

All dates and times shown are in local time for the timezone defined on the Xymon server.

 

PARAMETERS

HISTFILE
Defines the host and service whose history is presented.
ENTRIES
The number of log-entries to show in the event log table. Default is 50; to view all log entries set this to "ALL".
IP
The IP-address of the host. This is only used for the title of the document.
PIXELS
The width of the colorbar graph in pixels. If this is set to 0, a percentage-based graph will be shown, similar to the one provided by the standard history.sh script. Pixel-based graphs can have a higher resolution, but do not resize automatically to suit the size of a browser window. The default value for this parameter is defined at compile-time; 960 is a good value for displays with a 1024x768 resolution.
BARSUMS
Defines which colorbars and summaries to show. This is a number made up from a bitmask. The 1-day graph uses the value "1"; the 1-week graph uses the value "2"; the 4-week graph uses the value "4" and the 1-year graph the value "8". To show multiple graph, add the values - e.g. "6" will show the 1-week and 4-weeks graphs, whereas "15" will show all the graphs. The default is defined at compile-time.
ENDTIME
The history display by default ends with the current time. Setting the ENDTIME parameter causes it to end at the time specified - this is given as a Unix "time_t" value, i.e. as the number of seconds elapsed since Jan 1 1970 00:00 UTC.

 

OPTIONS

--env=FILENAME
Load the environment from FILENAME before executing the CGI.

 

SEE ALSO

hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
PARAMETERS
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/xymondigest.1.html0000664000175000017500000000455111671641417021216 0ustar henrikhenrik Man page of XYMONDIGEST

XYMONDIGEST

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymondigest - calculate message digests  

SYNOPSIS

xymondigest md5|sha1|rmd160 [filename]

 

DESCRIPTION

xymondigest(1) is a utility to calculate message digests for a file or document. It is used when defining HTTP- or FTP-based content checks, where xymonnet(1) checks that a URL returns a specific document; instead of having to compare the entire document, the comparison is done against a pre-computed message digest value using the MD5, RIPEMD160, SHA1 or any of the SHA2 (SHA-512, SHA-256, SHA-384, SHA-224) message digest algorithms.

The optional filename parameter is the input file whose message digest should be calculated; if no filename is given, the data is read from standard input.

xymondigest outputs a string containing the digest algorithm and the computed message digest. This is in a format suitable for use in the hosts.cfg(5) definition of a content check.

 

EXAMPLE


   $ xymondigest md5 index.html
   md5:88b81b110a85c83db56a939caa2e2cf6


   $ curl -s http://www.foo.com/ | xymondigest sha1
   sha1:e5c69784cb971680e2c7380138e04021a20a45a2

 

SEE ALSO

xymonnet(1), hosts.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
EXAMPLE
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/findhost.cgi.1.html0000664000175000017500000000560111671641417021220 0ustar henrikhenrik Man page of FINDHOST.CGI

FINDHOST.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

findhost.cgi - Xymon CGI script to find hosts  

SYNOPSIS

findhost.cgi?host=REGEX

 

DESCRIPTION

findhost.cgi is invoked as a CGI script via the findhost.sh CGI wrapper.

findhost.cgi is passed a QUERY_STRING environment variable with the "host=REGEX" parameter. The REGEX is a Posix regular expression (see regex(7) ) describing the hostnames to look for. A trailing wildcard is assumed on all hostnames - e.g. requesting the hostname "www" will match any host whose name begins with "www".

It then produces a single web page, listing all of the hosts that matched any of the hostnames, with links to the Xymon webpages where they are located.

The output page lists hosts in the order they appear in the hosts.cfg(5) file.

A sample web page implementing the search facility is included with xymongen, you access it via the URL /xymon/help/findhost.html.

 

OPTIONS

--env=FILENAME
Loads the environment from FILENAME before executing the CGI.

 

FILES

$XYMONHOME/web/findhost_header
HTML header file for the generated web page

$XYMONHOME/web/findhost_footer
HTML footer file for the generated web page

$XYMONHOME/web/findhost_form
Query form displayed when findhost.cgi is called with no parameters.

 

ENVIRONMENT VARIABLES

HOSTSCFG
findhost.cgi uses the HOSTSCFG environment variable to find the hosts.cfg file listing all known hosts and their page locations.

XYMONHOME
Used to locate the template files for the generated web pages.

 

SEE ALSO

xymongen(1), hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
ENVIRONMENT VARIABLES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/svcstatus.cgi.1.html0000664000175000017500000001232311671641417021440 0ustar henrikhenrik Man page of SVCSTATUS.CGI

SVCSTATUS.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

svcstatus.cgi - CGI program to view Xymon status logs  

SYNOPSIS

svcstatus.cgi [--historical] [--history={top|bottom}]

 

DESCRIPTION

svcstatus.cgi is a CGI program to present a Xymon status log in HTML form (ie, as a web page). It can be used both for the logs showing the current status, and for historical logs from the "histlogs" directory. It is normally invoked as a CGI program, and therefore receives most of the input parameters via the CGI QUERY_STRING environment variable.

Unless the "--historical" option is present, the current status log is used. This assumes a QUERY_STRING environment variable of the form

   HOSTSVC=hostname.servicename
where "hostname" is the name of the host with commas instead of dots, and "servicename" is the name of the service (the column name in Xymon). Such links are automatically generated by the xymongen(1) tool when the environment contains "XYMONLOGSTATUS=dynamic".

With the "--historical" option present, a historical logfile is used. This assumes a QUERY_STRING environment variable of the form

   HOST=hostname&SERVICE=servicename&TIMEBUF=timestamp
where "hostname" is the name of the host with commas instead of dots, "servicename" is the name of the service, and "timestamp" is the time of the log. This is automatically generated by the history.cgi(1) tool.

 

OPTIONS

--historical
Use a historical logfile instead of the current logfile.

--history={top|bottom|none}
When showing the current logfile, provide a "HISTORY" button at the top or the bottom of the webpage, or not at all. The default is to put the HISTORY button at the bottom of the page.

--env=FILENAME
Load the environment from FILENAME before executing the CGI.

--templates=DIRECTORY
Where to look for the HTML header- and footer-templates used when generating the webpages. Default: $XYMONHOME/web/

--no-svcid
Do not include the HTML tags to identify the hostname/service on the generated web page. Useful is this already happens in the hostsvc_header template file, for instance.

--multigraphs=TEST1[,TEST2]
This causes svcstatus.cgi to generate links to service graphs that are split up into multiple images, with at most 5 graphs per image. This option only works in Xymon mode. If not specified, only the "disk" status is split up this way.

--no-disable
By default, the info-column page includes a form allowing users to disable and re-enable tests. If your setup uses the default separation of administration tools into a separate, password- protected area, then use of the disable- and enable-functions requires access to the administration tools. If you prefer to do this only via the dedicated administration page, this option will remove the disable-function from the info page.

--no-jsvalidation
The disable-function on the info-column page by default uses JavaScript to validate the form before submitting the input to the Xymon server. However, some browsers cannot handle the Javascript code correctly so the form does not work. This option disables the use of Javascript for form-validation, allowing these browsers to use the disable-function.

--nkconfig=FILENAME
Use FILENAME as the configuration file for the Critical Systems information. The default is to load this from $XYMONHOME/etc/critical.cfg

 

FILES

$XYMONHOME/web/hostsvc_header
HTML template header

$XYMONHOME/web/hostsvc_footer
HTML template footer

 

ENVIRONMENT

NONHISTS=info,trends,graphs
A comma-separated list of services that does not have meaningful history, e.g. the "info" and "trends" columns. Services listed here do not get a "History" button.

TEST2RRD=test,test
A comma-separated list of the tests that have an RRD graph.

 

SEE ALSO

xymon(7), xymond(1)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
ENVIRONMENT
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/xymonnet-again.sh.1.html0000664000175000017500000000464011671641417022212 0ustar henrikhenrik Man page of XYMONNET-AGAIN.SH

XYMONNET-AGAIN.SH

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymonnet-again.sh - Xymon network re-test tool  

SYNOPSIS

xymonnet-again.sh

 

DESCRIPTION

xymonnet-again.sh is an extension script for Xymon that runs on the network test server. It picks up the failing network tests executed by the xymonnet(1) program, and repeats these tests with a faster test cycle than the normal xymonnet schedule. This means that when the server recovers and the network service becomes available again, this is detected quicker resulting in less reported downtime.

Only tests whose first failure occurred within 30 minutes are included in the tests that are run by xymonnet-again.sh. The 30 minute limit is there to avoid hosts that are down for longer periods of time to bog down xymonnet-again.sh. You can change this limit with the "--frequenttestlimit=SECONDS" when you run xyxmonnet.

 

INSTALLATION

This script runs by default from your tasks.cfg(5) file.

 

FILES

$XYMONTMP/TESTNAME.LOCATION.status
Temporary status file managed by xyxmonnet with status of tests that have currently failed.
$XYMONTMP/frequenttests.LOCATION
Temporary file managed by xymonnet with the hostnames that xymonnet-again.sh should test.

 

SEE ALSO

xymonnet(1), xymon(7), tasks.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
INSTALLATION
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/snapshot.cgi.1.html0000664000175000017500000000670311671641417021245 0ustar henrikhenrik Man page of SNAPSHOT.CGI

SNAPSHOT.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

snapshot.cgi - CGI program to rebuild the Xymon webpages for a specific point in time.  

SYNOPSIS

snapshot.cgi

 

DESCRIPTION

snapshot.cgi is invoked as a CGI script via the snapshot.sh CGI wrapper. It rebuilds the Xymon web pages to the look they had at a particular point in time, based upon the historical information logged about events.

snapshot.cgi is passed a QUERY_STRING environment variable with the following parameters:


   mon (Start month of the snapshot)
   day (Start day-of-month of the snapshot)
   yr  (Start year of the snapshot)
   hour (Start hour of the snapshot)
   min  (Start minute of the snapshot)
   sec  (Start second of the snapshot)

The "month" parameters must be specified as the three-letter english month name abbreviation: Jan, Feb, Mar ...

"day" must be in the range 1..31; "yr" must be specified including century (e.g. "2003"). "hour" must be specified using a 24-hour clock.

All of the processing involved in generating the report is done by invoking xymongen(1) with the proper "--snapshot" option.

 

OPTIONS

--env=FILENAME
Load environment from FILENAME before executing the CGI.

xymongen-options
All options except "--env" are passed on to the xymongen(1) program building the snapshot files.

 

ENVIRONMENT VARIABLES

XYMONGENSNAPOPTS
xymongen options passed by default to the snapshot.cgi script. This happens in the snapshot.sh CGI wrapper script.
XYMONHOME
Home directory of the Xymon server files
XYMONSNAPDIR
Directory where generated snapshots are stored. This directory must be writable by the userid executing the CGI script, typically "www", "apache" or "nobody". Default: $XYMONHOME/www/snap/
XYMONSNAPURL
The URL prefix to use when accessing the reports via a browser. Default: $XYMONWEB/snap

 

SEE ALSO

xymongen(1), hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
ENVIRONMENT VARIABLES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/ackinfo.cgi.1.html0000664000175000017500000000554011671641417021016 0ustar henrikhenrik Man page of ACKINFO.CGI

ACKINFO.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

ackinfo.cgi - Xymon CGI script to acknowledge alerts  

SYNOPSIS

ackinfo.cgi

 

DESCRIPTION

ackinfo.cgi is invoked as a CGI script via the ackinfo.sh CGI wrapper.

ackinfo.cgi is used to acknowledge an alert on the Xymon "Critical Systems" view, generated by the criticalview.cgi(1) utility. This allows the staff viewing the Critical Systems view to acknowledge alerts with a "Level 1" alert, thereby removing the alert from the Critical Systems view.

Note that the Level 1 alert generated by the ackinfo.cgi utility does NOT stop alerts from being sent.

In a future version of Xymon (after Xymon 4.2), this utility will also be used for acknowledging alerts at other levels.

 

OPTIONS

--level=NUMBER
Sets the acknowledgment level. This is typically used to force a specific level of the acknowledgment, e.g. a level 1 acknowledge when called from the Critical Systems view.

--validity=TIME
Sets the validity of the acknowledgment. By default this is taken from the CGI parameters supplied by the user.

--sender=STRING
Logs STRING as the sender of the acknowledgment. By default, this is taken from the loginname of the webuser sending the acknowledgment.

--env=FILENAME
Loads the environment defined in FILENAME before executing the CGI script.

--area=NAME
Load environment variables for a specific area. NB: if used, this option must appear before any --env=FILENAME option.

--debug
Enables debugging output.

 

ENVIRONMENT VARIABLES

XYMONHOME
Used to locate the template files for the generated web pages.

QUERY_STRING
Contains the parameters for the CGI script.

 

SEE ALSO

criticalview.cgi(1), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
ENVIRONMENT VARIABLES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/report.cgi.1.html0000664000175000017500000001130111671641417020707 0ustar henrikhenrik Man page of REPORT.CGI

REPORT.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

report.cgi - CGI front-end to xymongen reporting  

SYNOPSIS

report.cgi [--noclean] [xymongen-options]

 

DESCRIPTION

report.cgi is invoked as a CGI script via the report.sh CGI wrapper. It triggers the generation of a Xymon availability report for the timeperiod specified by the CGI paramaters.

report.cgi is passed a QUERY_STRING environment variable with the following parameters:


   start-mon (Start month of the report)
   start-day (Start day-of-month of the report)
   start-yr  (Start year of the report)
   end-mon   (End month of the report)
   end-day   (End day-of-month of the report)
   end-yr    (End year of the report)
   style     (Report style)
  The following non-standard parameters are handled by the xymongen version of report.cgi:


   suburl    (Page in report to go to, if not the top page)

The "month" parameters must be specified as the three-letter english month name abbreviation: Jan, Feb, Mar ...

Start- and end-days are in the range 1..31; the start- and end-year must be specified including century (e.g. "2003").

End-times beyond the current time are silently replaced with the current time.

The generated report will include data for the start- and end-days, i.e. the report will begin at 00:00:00 of the start-day, and end at 23:59:59 of the end-day.

The "style" parameter is passed directly to xymongen(1) and should be "crit", "non-crit" or "all". Other values result in undefined behaviour.

All of the processing involved in generating the report is done by invoking xymongen(1) with the proper "--reportopts" option.

 

OPTIONS

--noclean
Do not clean the XYMONREPDIR directory of old reports. Makes the report-tool go a bit faster - instead, you can clean up the XYMONREPDIR directory e.g. via a cron-job.

--env=FILENAME
Load the environment from FILENAME before executing the CGI.

xymongen-options
All other options passed to report.cgi are passed on to the xymongen(1) program building the report files.

 

FILES

$XYMONHOME/web/report_header
HTML template header for the report request form

$XYMONHOME/web/report_footer
HTML template footer for the report request form

$XYMONHOME/web/report_form
HTML template report request form

 

ENVIRONMENT VARIABLES

XYMONGENREPOPTS
xymongen options passed by default to the report.cgi. This happens in the report.sh wrapper.
XYMONHOME
Home directory of the Xymon server installation
XYMONREPDIR
Directory where generated reports are stored. This directory must be writable by the userid executing the CGI script, typically "www", "apache" or "nobody". Default: $XYMONHOME/www/rep/
XYMONREPURL
The URL prefix to use when accessing the reports via a browser. Default: $XYMONWEB/rep

 

SEE ALSO

xymongen(1), hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
ENVIRONMENT VARIABLES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/ghostlist.cgi.1.html0000664000175000017500000000406511671641417021425 0ustar henrikhenrik Man page of GHOSTLIST.CGI

GHOSTLIST.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

ghostlist.cgi - CGI program to view ghost clients  

SYNOPSIS

ghostlist.cgi

 

DESCRIPTION

ghostlist.cgi is invoked as a CGI script via the ghostlist.sh CGI wrapper.

It generates a listing of the Xymon clients that have reported data to the Xymon server, but are not listed in the hosts.cfg(5) file. Data from these clients - called "ghosts" - are ignored, since Xymon does not know which webpage to present the data on.

The listing includes the hostname that the client reports with, the IP-address where the report came from, and how long ago the report arrived.

By far the most common reason for hosts showing up here is that the client uses a hostname without a DNS domain, but the hosts.cfg file uses the hostname with the DNS domain. Or vice versa. You can then use a CLIENT setting in the hosts.cfg file to match the two hostnames together.

 

OPTIONS

--env=FILENAME
Loads the environment defined in FILENAME before executing the CGI script.

 

SEE ALSO

hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/criticalview.cgi.1.html0000664000175000017500000000703511671641417022072 0ustar henrikhenrik Man page of CRITICALVIEW.CGI

CRITICALVIEW.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

criticalview.cgi - Xymon Critical Systems view CGI  

SYNOPSIS

criticalview.cgi

 

DESCRIPTION

criticalview.cgi is invoked as a CGI script via the criticalview.sh CGI wrapper.

criticalview.cgi matches the current critical statuses against the critical.cfg(5) file, and generates the "Critical Systems" view.

 

RELATION TO OLD CRITICAL PAGE

This view is a replacement for the statically generated "critical" page provided in versions of Xymon prior to version 4.2. Although the "critical" pages are supported throughout Xymon 4.x, it is recommended that You switch to the newer Critical Systems view provided by this CGI.

 

OPTIONS

--acklevel=NUMBER
Sets the acknowledgment level for acknowledgments sent via the ackinfo.cgi(1) page. Note that this may be overridden by the configuration of the ackinfo.cgi utility.

--tooltips
Hide the host description in a "tooltip", i.e. it will be shown when your mouse hovers over the hostname on the webpage. This saves space on the display so there is more room for the status columns.

--hffile=PREFIX
Define the header/footer files used when building the webpage. The actual files used will be PREFIX_header and PREFIX_footer found in the ~xymon/server/web/ directory. Default: critical.

--env=FILENAME
Loads the environment defined in FILENAME before executing the CGI script.

--area=NAME
Load environment variables for a specific area. NB: if used, this option must appear before any --env=FILENAME option.

--debug
Enables debugging output.

--config=FILENAME
Use FILENAME as the configuration file for the Critical Systems information. The default is to load this from $XYMONHOME/etc/critical.cfg

--config=ID:FILENAME
Allows the use of multiple Critical Systems configuration files on a single webpage. "ID" is a text that will be shown on the web page prior to the critical systems from FILENAME. This option can be repeated to include critical systems from multiple configurations.

 

ENVIRONMENT VARIABLES

XYMONHOME
Used to locate the template files for the generated web pages.

QUERY_STRING
Contains the parameters for the CGI script.

 

SEE ALSO

ackinfo.cgi(1), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
RELATION TO OLD CRITICAL PAGE
OPTIONS
ENVIRONMENT VARIABLES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/datepage.cgi.1.html0000664000175000017500000000766411671641417021167 0ustar henrikhenrik Man page of DATEPAGE.CGI

DATEPAGE.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

datepage.cgi - Xymon CGI script to view pre-built reports by date  

SYNOPSIS

datepage.cgi?type={day,week,month} --url=URLPREFIX [options]

 

DESCRIPTION

datepage.cgi is invoked as a CGI script via the datepage.sh CGI wrapper.

datepage.cgi is passed a QUERY_STRING environment variable with the type of time-selection that is desired: Either "day", "week" or "month" can be requested. It will then generate a web form with appropriate day/week/month selection boxes, and based on the users' selection a resulting url is built from the URLPREFIX and the time selection. The browser is then redirected to this URL.

The URL is constructed from the URLPREFIX, the type-parameter, the value of the "pagepath" or "host" cookie, and the users' selection as follows:

type=day
The final URL is URLPREFIX/daily/YEAR/MONTH/DAY/PAGEPATH.

type=week
The final URL is URLPREFIX/weekly/YEAR/WEEK/PAGEPATH.

type=month
The final URL is URLPREFIX/monthly/YEAR/MONTH/PAGEPATH.

YEAR is the full year (4 digits, including century). MONTH is the two-digit number of the month (01..12). DAY is the number of the day in the month (01..31). WEEK is the ISO 8601:1988 week-number (01..53). PAGEPATH is the current value of the "pagepath" cookie if set; if it is not set but the "host" cookie is set, then this host is looked up in the hosts.cfg file and the page where this host is found is used for PAGEPATH. These two cookies are set by the default web-header templates supplied with Xymon.

 

OPTIONS

--url=URLPREFIX
This specifies the initial part of the final URL. This option is required.

--hffile=FILENAME
Specifies the template files (from $XYMONHOME/web/) to use. The default is "--hffile=report".

--color=COLOR
Sets the background color of the generated webpage. The default is blue.

--env=FILENAME
Loads the environment defined in FILENAME before executing the CGI script.

--debug
Enables debugging output.

$XYMONHOME/web/report_form_daily
HTML form template for the date selection form when type=daily.

$XYMONHOME/web/report_form_weekly
HTML form template for the date selection form when type=weekly.

$XYMONHOME/web/report_form_monthly
HTML form template for the date selection form when type=monthly.

$XYMONHOME/web/report_header
HTML header file for the generated web page

$XYMONHOME/web/report_footer
HTML footer file for the generated web page

 

ENVIRONMENT VARIABLES

XYMONHOME
Used to locate the template files for the generated web pages.

QUERY_STRING
Contains the parameters for the CGI script.

 

SEE ALSO

xymongen(1), hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
ENVIRONMENT VARIABLES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/reportlog.cgi.1.html0000664000175000017500000000630711671641417021423 0ustar henrikhenrik Man page of REPORTLOG.CGI

REPORTLOG.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

reportlog.cgi - CGI program to report service availability log  

SYNOPSIS

reportlog.cgi

 

DESCRIPTION

reportlog.cgi is invoked as a CGI script via the reportlog.sh CGI wrapper. Based on the parameters it receives, it generates an availability report for a specific host-service combination for the requested time-period. The availability report includes a calculation of the availability percentage (split out on percent green, yellow, red time), and an eventlog for the period listing the status changes that have occurred to allow for drill-down to the test reports that indicate a problem. Access to the individual historical status logs go via the svcstatus.cgi(1) CGI script.

reportlog.cgi is passed a QUERY_STRING environment variable with the following parameters:


   HOSTSVC (the host and service to report on)
   STYLE (report style: "crit", "non-crit", "all")
   ST (starttime in seconds since 1-1-1970 00:00 UTC)
   END (endtime in seconds since 1-1-1970 00:00 UTC)

The following non-standard parameters are handled by the Xymon version of history.cgi:


   IP (IP address of host - for display purposes only)
   REPORTTIME (the REPORTTIME: setting for this host)
   WARNPCT (the WARNPCT: setting for this host)

The REPORTTIME and WARNPCT options are taken from the hosts.cfg(5) definition for the host, or the defaults are used. These modify the availability calculation to handle reporting against agreed Service Level Agreements re. the time of day when the service must be available, and the agreed availability level.

 

OPTIONS

--env=FILENAME
Loads environment from FILENAME before executing the CGI.

 

SEE ALSO

hosts.cfg(5), xymonserver.cfg(5), svcstatus.cgi(1)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/orcaxymon.1.html0000664000175000017500000000450711671641417020664 0ustar henrikhenrik Man page of ORCAXYMON

ORCAXYMON

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

orcaxymon - Xymon client utility to grab data from ORCA  

SYNOPSIS

orcaxymon --orca=PREFIX [options]

 

NOTICE

This utility is included in the client distribution for Xymon 4.2. However, the backend module to parse the data it sends it NOT included in Xymon 4.2. It is possible to use the generic Xymon NCV data handler in xymond_rrd(8) to process ORCA data, if you have an urgent need to do so.

 

DESCRIPTION

orcaxymon is an add-on tool for the Xymon client. It is used to grab data collected by the ORCA data collection tool (orcallator.se), and send it to the Xymon server in NCV format.

orcaxymon should run from the client xymonlaunch(8) utility, i.e. there must be an entry in the clientlaunch.cfg(5) file for orcaxymon.

 

OPTIONS

--orca=PREFIX
The filename prefix for the ORCA data log. Typically this is the directory for the ORCA logs, followed by "orcallator". The actual filename for the ORCA logs include a timestamp and sequence number, e.g. "orcallator-2006-06-20-000". This option is required.

--debug
Enable debugging output.

 

SEE ALSO

xymon(7), clientlaunch.cfg(5)


 

Index

NAME
SYNOPSIS
NOTICE
DESCRIPTION
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/acknowledge.cgi.1.html0000664000175000017500000000761311671641417021672 0ustar henrikhenrik Man page of ACKNOWLEDGE.CGI

ACKNOWLEDGE.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

acknowledge.cgi - Xymon CGI script to acknowledge alerts  

SYNOPSIS

acknowledge.cgi?ACTION=action&NUMBER=acknum&DELAY=validity&MESSAGE=text

 

DESCRIPTION

acknowledge.cgi is invoked as a CGI script via the acknowledge.sh CGI wrapper.

acknowledge.cgi is passed a QUERY_STRING environment variable with the ACTION, NUMBER, DELAY and MESSAGE parameters.

 

PARAMETERS

ACTION is the action to perform. The only supported action is "Ack" to acknowledge an alert.

NUMBER is the number identifying the host/service to be acknowledged. It is included in all alert-messages sent out by Xymon.

DELAY is the time (in minutes) that the acknowledge is valid.

MESSAGE is an optional text which will be shown on the status page while the acknowledgment is active. You can use it to e.g. tell users not to contact you about the problem, or inform them when the problem is expected to be resolved.

 

OPTIONS

--no-pin
acknowledge.cgi normally requires the user to enter the acknowledgment code received in an alert message. If you run it with this option, the user will instead get a list of the current non-green statuses, and he may send an acknowledge without knowing the code.

--no-cookies
Normally, acknowledge.cgi uses a cookie sent by the browser to initially filter the list of hosts presented. If this is not desired, you can turn off this behaviour by calling acknowledge.cgi with the --no-cookies option. This would normally be placed in the CGI_ACK_OPTS setting in cgioptions.cfg(5)

--env=FILENAME
Loads the environment defined in FILENAME before executing the CGI script.

--debug
Enables debugging output.

 

FILES

$XYMONHOME/web/acknowledge_header
HTML header file for the generated web page

$XYMONHOME/web/acknowledge_footer
HTML footer file for the generated web page

$XYMONHOME/web/acknowledge_form
Query form displayed when acknowledge.cgi is called with no parameters.

 

ENVIRONMENT VARIABLES

XYMONHOME
Used to locate the template files for the generated web pages.

QUERY_STRING
Contains the parameters for the CGI script.

 

BUGS

When using alternate pagesets, hosts will only show up on the acknowledgment page if this is accessed from the primary page in which they are defined. So if you have hosts on multiple pages, they will only be visible for acknowledging from their main page which is not what you would expect.

 

SEE ALSO

xymongen(1), hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
PARAMETERS
OPTIONS
FILES
ENVIRONMENT VARIABLES
BUGS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/xymoncmd.1.html0000664000175000017500000000376111671641417020504 0ustar henrikhenrik Man page of XYMONCMD

XYMONCMD

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymoncmd - Run a Xymon command with environment set  

SYNOPSIS

xymoncmd [--env=ENVFILE COMMAND|--version|--debug]

 

DESCRIPTION

xymoncmd(1) is a utility that can setup the Xymon environment variables as defined in a xymonlaunch(8) compatible environment definition file, and then execute a command with this environment in place. It is mostly used for testing extension scripts or in other situations where you need to run a single command with the environment in place.

The "--env=ENVFILE" option points xymoncmd to the file where the environment definitions are loaded from.

COMMAND is the command to execute after setting up the environment.

If you want to run multiple commands, it is often easiest to just use "sh" as the COMMAND - this gives you a sub-shell with the environment defined globally.

The "--debug" option print out more detail steps of xymoncmd execution.

The "--version" option print out version number of xymoncmd.  

SEE ALSO

xymonlaunch(8), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/xymon.1.html0000664000175000017500000005241411671641417020017 0ustar henrikhenrik Man page of XYMON

XYMON

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymon - Xymon client communication program  

SYNOPSIS

xymon [options] RECIPIENT message

 

DESCRIPTION

xymon(1) is the client program used to communicate with a Xymon server. It is frequently used by Xymon client systems to send in status messages and pager alerts on local tests.

In Xymon, the xymon program is also used for administrative purposes, e.g. to rename or delete hosts, or to disable hosts that are down for longer periods of time.

 

OPTIONS AND PARAMETERS

--debug
Enable debugging. This prints out details about how the connection to the Xymon server is being established.

--proxy=http://PROXYSERVER:PROXYPORT/
When sending the status messages via HTTP, use this server as an HTTP proxy instead of connecting directly to the Xymon server.

--timeout=N
Specifies the timeout for connecting to the Xymon server, in seconds. The default is 5 seconds.

--response
The xymon utility normally knows when to expect a response from the server, so this option is not required. However, it will cause any response from the server to be displayed.

--merge
Merge the command line message text with the data provided on standard input, and send the result to the Xymon server. The message text provided on the command line becomes the first line of the merged message.

RECIPIENT
The RECIPIENT parameter defines which server receives the message. If RECIPIENT is given as "0.0.0.0", then the message is sent to all of the servers listed in the XYMSERVERS environment variable.

Usually, a client will use "$XYMSRV" for the RECIPIENT parameter, as this is defined for the client scripts to automatically contain the correct value.

The RECIPIENT parameter may be a URL for a webserver that has the xymoncgimsg.cgi or similar script installed. This tunnels the Xymon messages to the Xymon server using standard HTTP protocol. The xymoncgimsg.cgi(8) CGI tool (included in Xymon) must be installed on the webserver for the HTTP transport to work.

MESSAGE
The message parameter is the message to be sent across to the Xymon server. Messages must be enclosed in quotes, but by doing so they can span multiple lines. The maximum size of a message is defined by the maximum allowed length of your shell's command-line, and is typically 8-32 KB.

If you need to send longer status messages, you can specify "@" as the message: xymon will then read the status message from its stdin.

 

XYMON MESSAGE SYNTAX

This section lists the most commonly used messages in the Xymon protocol.

Each message must begin with one of the Xymon commands. Where a HOSTNAME is specified, it must have any dots in the hostname changed to commas if the Xymon FQDN setting is enabled (which is the default). So the host "www.foo.com", for example, would report as "www,foo,com".

status[+LIFETIME][/group:GROUP] HOSTNAME.TESTNAME COLOR <additional text>
This sends in a status message for a single test (column) on a single host. TESTNAME is the name of the column where this test will show up; any name is valid except that using dots in the testname will not work. COLOR must be one of the valid colors: "green", "yellow", "red" or "clear". The colors "blue" and "purple" - although valid colors - should not be sent in a status message, as these are handled specially by the Xymon server. As a special case (for supporting older clients), "client" can be used as the name of the color. This causes the status message to be handled by Xymon as a "client" data message, and the TESTNAME parameter is used as the "collector id".
The "additional text" normally includes a local timestamp and a summary of the test result on the first line. Any lines following the first one are free-form, and can include any information that may be useful to diagnose the problem being reported.
The LIFETIME defines how long this status is valid after being received by the Xymon server. The default is 30 minutes, but you can set any period you like. E.g. for a custom test that runs once an hour, you will want to set this to at least 60 minutes - otherwise the status will go purple after 30 minutes. It is a good idea to set the LIFETIME to slightly longer than the interval between your tests, to allow for variations in the time it takes your test to complete. The LIFETIME is in minutes, unless you add an "h" (hours), "d" (days) or "w" (weeks) immediately after the number, e.g. "status+5h" for a status that is valid for 5 hours.
The GROUP option is used to direct alerts from the status to a specific group. It is currently used for status generated from the Xymon clients' data, e.g. to direct alerts for a "procs" status to different people, depending on exactly which process is down.

notify HOSTNAME.TESTNAME <message text>
This triggers an informational message to be sent to those who receive alerts for this HOSTNAME+TESTNAME combination, according to the rules defined in alerts.cfg(5) This is used by the enadis.cgi(1) tool to notify people about hosts being disabled or enabled, but can also serve as a general way of notifying server administrators.

data HOSTNAME.DATANAME<newline><additional text>
The "data" message allows tools to send data about a host, without it appearing as a column on the Xymon webpages. This is used, for example, to report statistics about a host, e.g. vmstat data, which does not in itself represent something that has a red, yellow or green identity. It is used by RRD bottom-feeder modules, among others. In Xymon, data messages are by default processed only by the xymond_rrd(8) module. If you want to handle data-messages using an external application, you may want to enable the xymond_filestore(8) module for data-messages, to store data-messages in a format compatible with how the Big Brother daemon does.

disable HOSTNAME.TESTNAME DURATION <additional text>
Disables a specific test for DURATION minutes. This will cause the status of this test to be listed as "blue" on the Xymon server, and no alerts for this host/test will be generated. If DURATION is given as a number followed by s/m/h/d, it is interpreted as being in seconds/minutes/hours/days respectively. To disable a test until it becomes OK, use "-1" as the DURATION. To disable all tests for a host, use an asterisk "*" for TESTNAME.

enable HOSTNAME.TESTNAME
Re-enables a test that had been disabled.

query HOSTNAME.TESTNAME
Query the Xymon server for the latest status reported for this particular test. If the host/test status is known, the response is the first line of the status report - the current color will be the first word on the line. Additional lines of text that might be present on the status message cannot be retrieved.
This allows any Xymon client to determine the status of a particular test, whether it is one pertaining to the host where the client is running, some other host, or perhaps the result of a combined test from multiple hosts managed by combostatus(1) This will typically be useful to Xymon client extension scripts, that need to determine the status of other hosts, for example, to decide if an automatic recovery action should be initiated.

config FILENAME
Retrieve one of the Xymon configuration files from the server. This command allows a client to pull files from the $XYMONHOME/etc/ directory on the server, allowing for semi-automatic updates of the client configuration. Since the configuration files are designed to have a common file for the configuration of all hosts in the system - and this is in fact the recommended way of configuring your clients - this makes it easier to keep the configuration files synchronized.

drop HOSTNAME
Removes all data stored about the host HOSTNAME. It is assumed that you have already deleted the host from the hosts.cfg configuration file.

drop HOSTNAME TESTNAME
Remove data about a single test (column).

rename OLDHOSTNAME NEWHOSTNAME
Rename all data for a host that has had its name changed. You should do this after changing the hostname in the hosts.cfg configuration file.

rename HOSTNAME OLDTESTNAME NEWTESTNAME
Rename data about a single test (column).

xymondlog HOSTNAME.TESTNAME
Retrieve the Xymon status-log for a single test. The first line of the response contains a series of fields separated by a pipe-sign:

hostname The name of the host

testname The name of the test

color Status color (green, yellow, red, blue, clear, purple)

testflags For network tests, the flags indicating details about the test (used by xymongen).

lastchange Unix timestamp when the status color last changed.

logtime Unix timestamp when the log message was received.

validtime Unix timestamp when the log message is no longer valid (it goes purple at this time).

acktime Either -1 or Unix timestamp when an active acknowledgement expires.

disabletime Either -1 or Unix timestamp when the status is no longer disabled.

sender IP address where the status was received from.

cookie Either -1 or the cookie value used to acknowledge an alert.

ackmsg Empty or the acknowledgment message sent when the status was acknowledged. Newline, pipe-signs and backslashes are escaped with a backslash, C-style.

dismsg Empty or the message sent when the status was disabled. Newline, pipe-signs and backslashes are escaped with a backslash, C-style.

After the first line comes the full status log in plain text format.

xymondxlog HOSTNAME.TESTNAME
Retrieves an XML string containing the status log as with the "xymondlog" command.

xymondboard [CRITERIA] [fields=FIELDLIST]
Retrieves a summary of the status of all known tests available to the Xymon daemon.

By default - if no CRITERIA is provided - it returns one line for all status messages that are found in Xymon. You can filter the response by selecting a page, a host, a test or a color - the PAGEPATH, HOSTNAME and TESTNAME parameters are interpreted as regular expressions, the COLOR parameter accepts multiple colors separated by comma.

page=PAGEPATH Include only tests from hosts found on the PAGEPATH page in the hosts.cfg file.

host=HOSTNAME Include only tests from the host HOSTNAME

test=TESTNAME Include only tests with the testname TESTNAME

color=COLORNAME Include only tests where the status color is COLORNAME

You can filter on, for example, both a hostname and a testname.

The response is one line for each status that matches the CRITERIA, or all statuses if no criteria is specified. The line is composed of a number of fields, separated by a pipe-sign. You can select which fields to retrieve by listing them in the FIELDLIST. The following fields are available:

hostname The name of the host

testname The name of the test

color Status color (green, yellow, red, blue, clear, purple)

flags For network tests, the flags indicating details about the test (used by xymongen).

lastchange Unix timestamp when the status color last changed.

logtime Unix timestamp when the log message was received.

validtime Unix timestamp when the log message is no longer valid (it goes purple at this time).

acktime Either -1 or Unix timestamp when an active acknowledgement expires.

disabletime Either -1 or Unix timestamp when the status is no longer disabled.

sender IP address where the status was received from.

cookie Either -1 or the cookie value used to acknowledge an alert.

line1 First line of status log.

ackmsg Empty (if no acknowledgement is active), or the text of the acknowledge message.

dismsg Empty (if the status is currently enabled), or the text of the disable message.

msg The full text of the current status message.

client Shows "Y" if there is client data available, "N" if not.

clntstamp Timestamp when the last client message was received, in Unix "epoch" format.

acklist List of the current acknowledgements for a test. This is a text string with multiple fields, delimited by a colon character. There are 5 fields: Timestamp for when the ack was generated and when it expires; the the "ack level"; the user who sent the ack; and the acknowledgement text.

flapinfo Tells if the status is flapping. 5 fields, delimited by "/": A "0" if the status is not flapping and "1" if it is flapping; timestamp when the latest status change was recorded and when the first statuschange was recorded; and the two colors that the status is flapping between.

stats Number of status-changes that have been recorded for this status since xymond was started.

modifiers Lists all active modifiers for this status (i.e. updates sent using a "modify" command).

XMH_* The XMH-tags refer to the Xymon hosts.cfg(5) configuration settings. A full list of these can be found in the xymon-xmh(5) man-page.

The ackmsg, dismsg and msg fields have certain characters encoded: Newline is "\n", TAB is "\t", carriage return is "\r", a pipe-sign is "\p", and a backslash is "\\".

If the "fields" parameter is omitted, a default set of hostname,testname,color,flags,lastchange,logtime,validtime,acktime,disabletime,sender,cookie,line1 is used.

xymondxboard
Retrieves an XML string with the summary of all status logs as for the "xymondboard" command.

download FILENAME
Download a file from the Xymon server's download directory.

client[/COLLECTORID] HOSTNAME.OSTYPE [HOSTCLASS]
Used to send a "client" message to the Xymon server. Client messages are generated by the Xymon client; when sent to the Xymon server they are matched against the rules in the analysis.cfg(5) configuration file, and status messages are generated for the client-side tests. The COLLECTORID is used when sending client-data that are additions to the standard client data. The data will be concatenated with the normal client data.

clientlog HOSTNAME [section=SECTIONNAME[,SECTIONNAME...]]
Retrieves the current raw client message last sent by HOSTNAME. The optional "section" filter is used to select specific sections of the client data.

ping
Attempts to contact the Xymon server. If successful, the Xymon server version ID is reported.

pullclient
This message is used when fetching client data via the "pull" mechanism implemented by xymonfetch(8) and msgcache(8) for clients that cannot connect directly to the Xymon server.

ghostlist
Report a list of ghost clients seen by the Xymon server. Ghosts are systems that report data to the Xymon server, but are not listed in the hosts.cfg file.

schedule [TIMESTAMP COMMAND]
Schedules a command sent to the Xymon server for execution at a later time. E.g. used to schedule disabling of a host or service at sometime in the future. COMMAND is a complete Xymon command such as the ones listed above. TIMESTAMP is the Unix epoch time when the command will be executed.
If no parameters are given, the currently scheduled tasks are listed in the response. The response is one line per scheduled command, with the job-id, the time when the command will be executed, the IP address from which this was sent, and the full command string.
To cancel a previously scheduled command, "schedule cancel JOBID" can be used. JOBID is a number provided as the first item in the output from the schedule list.

notes FILENAME
The message text will be stored in $XYMONHOME/notes/FILENAME which is then used as hyperlinks from hostnames or column names. This requires that the "storenotes" task is enabled in tasks.cfg (it is disabled by default). FILENAME cannot contain any directory path - these are stripped automatically.

usermsg ID
These messages will be relayed directly to modules listening on the "user" channel of the Xymon daemon. This is intended for custom communication between client-side modules and the Xymon server.

modify HOSTNAME.TESTNAME COLOR SOURCE CAUSE
Modify the color of a specific status, without generating a complete status message. This is for backend processors (e.g. RRD graphs) that can override the color of a status based on some criteria determined outside the normal flow of a status. E.g. the normal "conn" status may appear to be green since it merely checks on whether a host can be ping'ed or not; the RRD handler can then use a "modify" command to override this is the actual ping responsetime exceeds a given threshold. (See the "DS" configuration setting in analysis.cfg(5) for how to do this). SOURCE is some identification of the module that generates the "modify" message - future modifications must use the same source. There may be several sources that modify the same status (the most severe status then becomes the actual color of the status). CAUSE is a one-line text string explaining the reason for overriding the normal status color - it will be displayed on the status webpage.

 

EXAMPLE

Send a normal status message to the Xymon server, using the standard Xymon protocol on TCP port 1984:

   $ $XYMON $XYMSRV "status www,foo,com.http green `date` Web OK"

Send the same status message, but using HTTP protocol via the webserver's xymoncgimsg.cgi script:

   $ $XYMON http://bb.foo.com/cgi-bin/xymoncgimsg.cgi "status www,foo,com.http green `date` Web OK"

Use "query" message to determine the color of the "www" test, and restart Apache if it is red:


   $ WWW=`$XYMON $XYMSRV "query www,foo,com.www" | awk '{print $1}'`
   $ if [ "$WWW" = "red" ]; then /etc/init.d/apache restart; fi

Use "config" message to update a local mytest.cfg file (but only if we get a response):


   $ $XYMON $XYMSRV "config mytest.cfg" >/tmp/mytest.cfg.new
   $ if [ -s /tmp/mytest.cfg.new ]; then 
       mv /tmp/mytest.cfg.new $XYMONHOME/etc/mytest.cfg
     fi

Send a very large status message that has been built in the file "statusmsg.txt". Instead of providing it on the command-line, pass it via stdin to the xymon command:


   $ cat statusmsg.txt | $XYMON $XYMSRV "@"

 

SEE ALSO

combostatus(1), hosts.cfg(5), xymonserver.cfg(5), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS AND PARAMETERS
XYMON MESSAGE SYNTAX
EXAMPLE
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/csvinfo.cgi.1.html0000664000175000017500000000563111671641417021054 0ustar henrikhenrik Man page of CSVINFO.CGI

CSVINFO.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

csvinfo.cgi - CGI program to show host information from a CSV file  

SYNOPSIS

csvinfo.cgi

 

DESCRIPTION

csvinfo.cgi is invoked as a CGI script via the csvinfo.sh CGI wrapper. Based on the parameters it receives, it searches a comma- separated file for the matching host, and presents the information found as a table.

csvinfo.cgi is passed a QUERY_STRING environment variable with the following parameters:


   key (string to search for, typically hostname)
   column (columnnumber to search - default 0)
   db  (name of the CSV database file in $XYMONHOME/etc/, default hostinfo.csv)
   delimiter (delimiter character for columns, default semi-colon)

CSV files are easily created from e.g. spreadsheets, by exporting them in CSV format. You should have one host per line, with the first line containing the column headings. Despite their name, the default delimiter for CSV files is the semi-colon - if you need a different delimiter, invoke csvinfo.cgi with the "delimiter=<character>" in the query string.

 

Example usage

This example shows how you can use the csvinfo CGI. It assumes you have a CSV-formatted file with information about the hosts stored as $XYMONHOME/etc/hostinfo.csv, and the hostname is in the first column of the file.

Use with the xymongen --docurl
The --docurl option to xymongen(1) sets up all of the hostnames on your Xymon webpages to act as links to a CGI script. To invoke the csvinfo CGI script, run xymongen with the option


   --docurl=/cgi-bin/csvinfo.sh?db=hostinfo.csv&key=%s

 

SEE ALSO

hosts.cfg(5), xymonserver.cfg(5), xymongen(1)


 

Index

NAME
SYNOPSIS
DESCRIPTION
Example usage
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/showgraph.cgi.1.html0000664000175000017500000001021111671641417021375 0ustar henrikhenrik Man page of SHOWGRAPH.CGI

SHOWGRAPH.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

showgraph.cgi - CGI to generate Xymon trend graphs  

SYNOPSIS

showgraph [options]

 

DESCRIPTION

showgraph.cgi is invoked as a CGI script via the showgraph.sh CGI wrapper.

showgraph.cgi is passed a QUERY_STRING environment variable with the following parameters:

host Name of the host to generate a graph for

service Name of the service to generate a graph for

disp Display-name of the host, used on the generated graphs instead of hostname.

graph Can be "hourly", "daily", "weekly" or "monthly" to select the time period that the graph covers.

first Used to split multi-graphs into multiple graphs. This causes showgraph.cgi to generate only the graphs starting with the "first'th" graph and continuing for "count".

count Number of graphs in a multi-graph.

upper Set the upper limit of the graph. See rrdgraph(1) for a description of the "-u" option.

lower Set the lower limit of the graph. See rrdgraph(1) for a description of the "-l" option.

graph_start Set the starttime of the graph. This is used in zoom-mode.

graph_end Set the end-time of the graph. This is used in zoom-mode.

action=menu Generate an HTML page with links to 4 graphs, representing the hourly, weekly, monthly and yearly graphs. Doesn't actually generate any graphs, only the HTML that links to the graphs.

action=selzoom Generate an HTML page with link to single graph, and with JavaScript code that lets the user select part of the graph for a zoom-operation. The JavaScript invokes showgraph.cgi with "action=showzoom" to generate the zoomed graph webpage.

action=showzoom Generate HTML with a link to the zoomed graph image. This link goes to an "action=view" invocation of showgraph.cgi.

action=view Generate a single graph image.

 

OPTIONS

--config=FILENAME
Loads the graph configuration file from FILENAME. If not specified, the file $XYMONHOME/etc/graphs.cfg is used. See the graphs.cfg(5) for details about this file.

--env=FILENAME
Loads the environment settings defined in FILENAME before executing the CGI.

--rrddir=DIRECTORY
The top-level directory for the RRD files. If not specified, the directory given by the XYMONRRDS environment is used.

--save=FILENAME
Instead of returning the image via the CGI interface (i.e. on stdout), save the generated image to FILENAME.

--debug
Enable debugging output.

 

ENVIRONMENT

QUERY_STRING Provided by the webserver CGI interface, this decides what graph to generate.

 

FILES

graphs.cfg: The configuration file determining how graphs are generated from RRD files.

 

SEE ALSO

graphs.cfg(5), xymon(7), rrdtool(1)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
ENVIRONMENT
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/appfeed.cgi.1.html0000664000175000017500000000633611671641417021014 0ustar henrikhenrik Man page of APPFEED.CGI

APPFEED.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

appfeed.cgi - Xymon CGI feeder for Smartphone apps  

SYNOPSIS

appfeed.cgi [options]

 

DESCRIPTION

appfeed.cgi is invoked as a CGI script via the appfeed.sh CGI wrapper.

appfeed.cgi is optionally passed a QUERY_STRING environment variable with the "filter=FILTER" parameter. FILTER is a filter for the "xymondboard" command sent to xymond(8) daemon. These filters are described in detail in the xymon(1) manual. Typically, the filter will specify hosts found on a specific (sub)page to be returned, e.g. "filter=page=webservers" will cause appfeed.cgi to only return hosts that are present on the "webservers" page in Xymon.

If no filter is specified, appfeed.cgi returns data for all red, yellow or purple statuses (equivalent to the data present on the "All non-green" page), or if invoked with the "--critical" option it returns data from the "Critical systems" page.

The output is an XML document with the current status of the selected hosts/services.

 

OPTIONS

--env=FILENAME
Loads the environment from FILENAME before executing the CGI.

--critical[=FILENAME]
FILENAME specifies the "Critical Systems" configuration file (default: critical.cfg). appfeed.cgi will only return the statuses currently on the "Critical Systems" view.

--access=FILENAME
In addition to the filtering done by the "filter" parameter or the "--critical" option, this option limits the output to hosts that the user has access to as defined in the Apache-compatible group-definitions in FILENAME. See xymon-webaccess(5) for more details of this. Note: Use of this option requires that access to the appfeed.cgi tool is password-protected by whatever means your webserver software provides, and that the login userid is available via the REMOTE_USER environment variable (this is standard CGI behaviour, so all webservers should provide it if you use the webserver's built-in authentication mechanisms).

 

SEE ALSO

xymon(1), critical.cfg(5), xymon-webaccess(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/xymonnet.1.html0000664000175000017500000007004011671641417020521 0ustar henrikhenrik Man page of XYMONNET

XYMONNET

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

xymonnet - Xymon network test tool  

SYNOPSIS

xymonnet [--ping|--noping] [--timeout=N] [options] [hostname] [hostname]
(See the OPTIONS section for a description of the available command-line options).

 

DESCRIPTION

xymonnet(1) handles the network tests of hosts defined in the Xymon configuration file, hosts.cfg. It is normally run at regular intervals by xymonlaunch(8) via an entry in the tasks.cfg(5) file.

xymonnet does all of the normal tests of TCP-based network services (telnet, ftp, ssh, smtp, pop, imap ....) - i.e. all of the services listed in protocols.cfg. For these tests, a completely new and very speedy service- checker has been implemented.

xymonnet has built-in support for testing SSL-enabled protocols, e.g. imaps, pop3s, nntps, telnets, if SSL-support was enabled when configuring xymonnet. The full list of known tests is found in the protocols.cfg(5) file in $XYMONHOME/etc/protocols.cfg.

In addition, it implements the "dns" and "dig" tests for testing DNS servers.

xymonnet also implements a check for NTP servers - this test is called "ntp". If you want to use it, you must define the NTPDATE environment variable to point at the location of your ntpdate(1) program.

Note: xymonnet performs the connectivity test (ping) based on the hostname, unless the host is tagged with "testip" or the "--dns=ip" option is used. So the target of the connectivity test can be determined by your /etc/hosts file or DNS.

By default, all servers are tested - if XYMONNETWORK is set via xymonserver.cfg(5) then only the hosts marked as belonging to this network are tested. If the command-line includes one or more hostnames, then only those servers are tested.

 

GENERAL OPTIONS

--timeout=N
Determines the timeout (in seconds) for each service that is tested. For TCP tests (those from XYMONNETSVCS), if the connection to the service does not succeed within N seconds, the service is reported as being down. For HTTP tests, this is the absolute limit for the entire request to the webserver (the time needed to connect to the server, plus the time it takes the server to respond to the request). Default: 10 seconds

--conntimeout=N
This option is deprecated, and will be ignored. Use the --timeout option instead.

--cmdtimeout=N
This option sets a timeout for the external commands used for testing of NTP and RPC services, and to perform traceroute.

--concurrency=N
Determines the number of network tests that run in parallel. Default is operating system dependent, but will usually be 256. If xymonnet begins to complain about not being able to get a "socket", try running xymonnet with a lower value like 50 or 100.

--dns-timeout=N (default: 30 seconds)
xymonnet will timeout all DNS lookups after N seconds. Any pending DNS lookups are regarded as failed, i.e. the network tests that depend on this DNS lookup will report an error.
Note: If you use the --no-ares option, timeout of DNS lookups cannot be controlled by xymonnet.

--dns-max-all=N
Same as "--dns-timeout=N". The "--dns-max-all" option is deprecated and should not be used.

--dns=[ip|only|standard]
Determines how xymonnet finds the IP adresses of the hosts to test. By default (the "standard"), xymonnet does a DNS lookup of the hostname to determine the IP address, unless the host has the "testip" tag, or the DNS lookup fails.
With "--dns=only" xymonnet will ONLY do the DNS lookup; it it fails, then all services on that host will be reported as being down.
With "--dns=ip" xymonnet will never do a DNS lookup; it will use the IP adresse specified in hosts.cfg for the tests. Thus, this setting is equivalent to having the "testip" tag on all hosts. Note that http tests will ignore this setting and still perform a DNS lookup for the hostname given in the URL; see the "xymonnet tags for HTTP tests" section in hosts.cfg(5)

--no-ares
Disable the ARES resolver built into xymonnet. This makes xymonnet resolve hostnames using your system resolver function. You should only use this as a last resort if xymonnet cannot resolve the hostnames you use in the normal way (via DNS or /etc/hosts). One reason for using this would be if you need to resolve hostnames via NIS/NIS+ (a.k.a. Yellow Pages).
The system resolver function does not provide a mechanism for controlling timeouts of the hostname lookups, so if your DNS or NIS server is down, xymonnet can take a very long time to run. The --dns-timeout option is effectively disabled when using this option.

--dnslog=FILENAME
Log failed hostname lookups to the file FILENAME. FILENAME should be a full pathname.

--report[=COLUMNNAME]
With this option, xymonnet will send a status message with details of how many hosts were processed, how many tests were generated, any errors that occurred during the run, and some timing statistics. The default columnname is "xymonnet".

--test-untagged
When using the XYMONNETWORK environment variable to test only hosts on a particular network segment, xymonnet will ignore hosts that do not have any "NET:x" tag. So only hosts that have a NET:$XYMONNETWORK tag will be tested.
With this option, hosts with no NET: tag are included in the test, so that all hosts that either have a matching NET: tag, or no NET: tag at all are tested.

--frequenttestlimit=N
Used with the xymonnet-again.sh(1) Xymon extension. This option determines how long failed tests remain in the frequent-test queue. The default is 1800 seconds (30 minutes).

--timelimit=N
Causes xymonnet to generate a warning if the run-time of xymonnet exceeds N seconds. By default N is set to the value of TASKSLEEP, so a warning triggers if the network tests cannot complete in the time given for one cycle of the xymonnet task. Apart from the warning, this option has no effect, i.e. it will not terminate xymonnet prematurely. So to eliminate any such warnings, use this option with a very high value of N.

--huge=N
Warn if the response from a TCP test is more than N bytes. If you see from the xymonnet status report that you are transferring large amounts of data for your tests, you can enable this option to see which tests have large replies.
Default: 0 (disabled).

--validity=N
Make the test results valid for N minutes before they go purple. By default test results are valid for 30 minutes; if you run xymonnet less often than that, the results will go purple before the next run of xymonnet. This option lets you change how long the status is valid.

--source-ip=IPADDRESS
On multi-homed hosts, this option can be used to explicitly select the source IP address used for the network tests. "IPADDRESS" must be a valid IP-address on the host running xymonnet.

--loadhostsfromxymond
Instead of reading the hosts.cfg file, xymonnet will load the hosts.cfg configuration from the xymond daemon. This eliminates the need for reading the hosts.cfg, and if you have xymond and xymonnet running on different hosts, it also eliminates the need for copying the hosts.cfg file between systems. Note that the "netinclude" option in hosts.cfg is ignored when this option is enabled.

 

OPTIONS FOR TESTS OF THE SIMPLE TCP SERVICES

--checkresponse[=COLOR]
When testing well-known services (e.g. FTP, SSH, SMTP, POP-2, POP-3, IMAP, NNTP and rsync), xymonnet will look for a valid service-specific "OK" response. If another reponse is seen, this will cause the test to report a warning (yellow) status. Without this option, the response from the service is ignored.
The optional color-name is used to select a color other than yellow for the status message when the response is wrong. E.g. "--checkresponse=red" will cause a "red" status message to be sent when the service does not respond as expected.

--no-flags
By default, xymonnet sends some extra information in the status messages, called "flags". These are used by xymongen e.g. to pick different icons for reversed tests when generating the Xymon webpages. This option makes xymonnet omit these flags from the status messages.

--shuffle
By default, TCP tests run roughly in the order that the hosts are listed in the hosts.cfg file. If you have many tests for one server, this may result in an exceptionally large load when Xymon is testing it because Xymon will perform a lot of tests at the same time. To avoid this, the --shuffle option reorders the sequence of tests so they are spread randomly across all of the servers tested.

 

OPTIONS FOR THE PING TEST

Note: xymonnet uses the program defined by the FPING environment to execute ping-tests - by default, that is the xymonping(1) utility. See xymonserver.cfg(5) for a description of how to customize this, e.g. if you need to run it with "sudo" or a similar tool.

--ping
Enables xymonnet's ping test. The column name used for ping test results is defined by the PINGCOLUMN environment variable in xymonserver.cfg(5).
If not specifed, xymonnet uses the CONNTEST environment variable to determine if it should perform the ping test or not. So if you prefer to use another tool to implement ping checks, either set the CONNTEST environment variable to false, or run xymonnet with the "--noping".

--noping
Disable the connectivity test.

--trace
--notrace
Enable/disable the use of traceroute when a ping-test fails. Performing a traceroute for failed ping tests is a slow operation, so the default is not to do any traceroute, unless it is requested on a per-host basis via the "trace" tag in the hosts.cfg(5) entry for each host. The "--trace" option changes this, so the default becomes to run traceroute on all hosts where the ping test fails; you can then disable it on specific hosts by putting a "notrace" tag on the host-entry.

--ping-tasks=N
Spread the task of pinging the hosts over N processes. If you have a very large number of hosts the time it takes to ping all of them can be substantial, even with the use of tools like fping or xymonping that ping many hosts in parallel. This option causes xymonnet to start N separate ping processes, the IP's that are being ping'ed will be divided evenly between these processes.

 

OPTIONS FOR HTTP (WEB) TESTS

--content=CONTENTTESTNAME
Determines the name of the column Xymon displays for content checks. The default is "content". If you have used the "cont.sh" or "cont2.sh" scripts earlier, you may want to use "--content=cont" to report content checks using the same test name as these scripts do.
--bb-proxy-syntax
Adhere to the Big Brother syntax for a URL, which allows specifying a HTTP proxy as part of a URL. See "HTTP Testing via proxy" in the hosts.cfg(5) file for details. Beginning with Xymon 4.3.0, this behaviour is disabled by default since URL's that include other URL's are now much more common. This option restores the old Big Brother-compatible behaviour.

 

OPTIONS FOR SSL CERTIFICATE TESTS

--ssl=SSLCERTTESTNAME
Determines the name of the column Xymon displays for the SSL certificate checks. The default is "sslcert".
--no-ssl
Disables reporting of the SSL certificate check.

--sslwarn=N
--sslalarm=N
Determines the number of days before an SSL certificate expires, where xymonnet will generate a warning or alarm status for the SSL certificate column.

--sslbits=N
Enables checking that the encryption supported by the SSL protocol uses an encryption key of at least N bits. E.g. to trigger an alert if your SSL-enabled website supports less than 128 bits of encryption, use "--sslbits=128". Note: This can be enabled on a per-host basis using the "sslbits=N" setting in hosts.cfg(5)

 

DEBUGGING OPTIONS

--no-update
Don't send any status updates to the Xymon server. Instead, all messages are dumped to stdout.

--timing
Causes xymonnet to collect information about the time spent in different parts of the program. The information is printed on stdout just before the program ends. Note that this information is also included in the status report sent with the "--report" option.

--debug
Dumps a bunch of status about the tests as they progress to stdout.

--dump[=before|=after|=both]
Dumps internal memory structures before and/or after the tests have executed.

 

INFORMATIONAL OPTIONS

--help or -?
Provide a summary of available command-line options.

--version
Prints the version number of xymonnet

--services
Dump the list of defined TCP services xymonnet knows how to test. Do not run any tests.

 

USING COOKIES IN WEB TESTS

If the file $XYMONHOME/etc/cookies exist, cookies will be read from this file and sent along with the HTTP requests when checking websites. This file is in the Netscape Cookie format, see http://www.netscape.com/newsref/std/cookie_spec.html for details on this format. The curl(1) utility can output a file in this format if run with the "--cookie-jar FILENAME" option.

 

ABOUT SSL CERTIFICATE CHECKS

When xymonnet tests services that use SSL- or TLS-based protocols, it will check that the server certificate has not expired. This check happens automatically for https (secure web), pop3s, imaps, nntps and all other SSL-enabled services (except ldap, see LDAP TESTS below).

All certificates found for a host are reported in one status message.

Note: On most systems, the end-date of the certificate is limited to Jan 19th, 2038. If your certificate is valid after this date, xymonnet will report it as valid only until Jan 19, 2038. This is due to limitations in your operating system C library. See http://en.wikipedia.org/wiki/2038_problem .

 

LDAP TESTS

ldap testing can be done in two ways. If you just put an "ldap" or "ldaps" tag in hosts.cfg, a simple test is performed that just verifies that it is possible to establish a connection to the port running the ldap service (389 for ldap, 636 for ldaps).

Instead you can put an LDAP URI in hosts.cfg. This will cause xymonnet to initiate a full-blown LDAP session with the server, and do an LDAP search for the objects defined by the URI. This requires that xymonnet was built with LDAP support, and relies on an existing LDAP library to be installed. It has been tested with OpenLDAP 2.0.26 (from Red Hat 9) and 2.1.22. The Solaris 8 system ldap library has also been confirmed to work for un-encrypted (plain ldap) access.

The format of LDAP URI's is defined in RFC 2255. LDAP URLs look like this:


  ldap://hostport/dn[?attrs[?scope[?filter[?exts]]]]

where:
  hostport is a host name with an optional ":portnumber"
  dn is the search base
  attrs is a comma separated list of attributes to request
  scope is one of these three strings:
    base one sub (default=base)
  filter is filter
  exts are recognized set of LDAP and/or API extensions.

Example:
  ldap://ldap.example.net/dc=example,dc=net?cn,sn?sub?(cn=*)

All "bind" operations to LDAP servers use simple authentication. Kerberos and SASL are not supported. If your LDAP server requires a username/password, use the "ldaplogin" tag to specify this, cf. hosts.cfg(5) If no username/password information is provided, an anonymous bind will be attempted.

SSL support requires both a client library and an LDAP server that support LDAPv3; it uses the LDAP "STARTTLS" protocol request after establishing a connection to the standard (non-encrypted) LDAP port (usually port 389). It has only been tested with OpenSSL 2.x, and probably will not work with any other LDAP library.

The older LDAPv2 experimental method of tunnelling normal LDAP traffic through an SSL connection - ldaps, running on port 636 - is not supported, unless someone can explain how to get the OpenLDAP library to support it. This method was never formally described in an RFC, and implementations of it are non-standard.

For a discussion of the various ways of running encrypted ldap, see
http://www.openldap.org/lists/openldap-software/200305/msg00079.html
http://www.openldap.org/lists/openldap-software/200305/msg00084.html
http://www.openldap.org/lists/openldap-software/200201/msg00042.html
http://www.openldap.org/lists/openldap-software/200206/msg00387.html

When testing LDAP URI's, all of the communications are handled by the ldap library. Therefore, it is not possible to obtain the SSL certificate used by the LDAP server, and it will not show up in the "sslcert" column.

 

USING MULTIPLE NETWORK TEST SYSTEMS

If you have more than one system running network tests - e.g. if your network is separated by firewalls - then is is problematic to maintain multiple hosts.cfg files for each of the systems. xymonnet supports the NET:location tag in hosts.cfg(5) to distinguish between hosts that should be tested from different network locations. If you set the environment variable XYMONNETWORK e.g. to "dmz" before running xymonnet, then it will only test hosts that have a "NET:dmz" tag in hosts.cfg. This allows you to keep all of your hosts in the same hosts.cfg file, but test different sets of hosts by different systems running xymonnet.

 

XYMONNET INTERNALS

xymonnet first reads the protocols.cfg file to see which network tests are defined. It then scans the hosts.cfg file, and collects information about the TCP service tests that need to be tested. It picks out only the tests that were listed in the protocols.cfg file, plus the "dns", "dig" and "ntp" tests.

It then runs two tasks in parallel: First, a separate process is started to run the "xymonping" tool for the connectivity tests. While xymonping is busy doing the "ping" checks, xymonnet runs all of the TCP-based network tests.

All of the TCP-based service checks are handled by a connection tester written specifically for this purpose. It uses only standard Unix-style network programming, but relies on the Unix "select(2)" system-call to handle many simultaneous connections happening in parallel. Exactly how many parallel connections are being used depends on your operating system - the default is FD_SETSIZE/4, which amounts to 256 on many Unix systems.

You can choose the number of concurrent connections with the "--concurrency=N" option to xymonnet.

Connection attempts timeout after 10 seconds - this can be changed with the "--timeout=N" option.

Both of these settings play a part in deciding how long the testing takes. A conservative estimate for doing N TCP tests is:


   (1 + (N / concurrency)) * timeout

In real life it will probably be less, as the above formula is for every test to require a timeout. Since the most normal use of Xymon is to check for services that are active, you should have a lot less timeouts.

The "ntp" and "rpcinfo" checks rely on external programs to do each test.

 

ENVIRONMENT VARIABLES

XYMONNETWORK
Defines the network segment where xymonnet is currently running. This is used to filter out only the entries in the hosts.cfg(5) file that have a matching "NET:LOCATION" tag, and execute the tests for only those hosts.

MAXMSGSPERCOMBO
Defines the maximum number of status messages that can be sent in one combo message. Default is 0 - no limit.
In practice, the maximum size of a single Xymon message sets a limit - the default value for the maximum message size is 32 KB, but that will easily accomodate 100 status messages per transmission. So if you want to experiment with this setting, I suggest starting with a value of 10.

SLEEPBETWEENMSGS
Defines a a delay (in microseconds) after each message is transmitted to the Xymon server. The default is 0, i.e. send the messages as fast as possible. This gives your Xymon server some time to process the message before the next message comes in. Depending on the speed of your Xymon server, it may be necessary to set this value to half a second or even 1 or 2 seconds. Note that the value is specified in MICROseconds, so to define a delay of half a second, this must be set to the value "500000"; a delay of 1 second is achieved by setting this to "1000000" (one million).

FPING
Command used to run the xymonping(1) utility. Used by xymonnet for connectivity (ping) testing. See xymonserver.cfg(5) for more information about how to customize the program that is executed to do ping tests.

TRACEROUTE
Location of the traceroute(8) utility, or an equivalent tool e.g. mtr(8). Optionally used when a connectivity test fails to pinpoint the network location that is causing the failure.

NTPDATE
Location of the ntpdate(1) utility. Used by xymonnet when checking the "ntp" service.

RPCINFO
Location of the rpcinfo(8) utility. Used by xymonnet for the "rpc" service checks.

 

FILES

~/server/etc/protocols.cfg
This file contains definitions of TCP services that xymonnet can test. Definitions for a default set of common services is built into xymonnet, but these can be overridden or supplemented by defining services in the protocols.cfg file. See protocols.cfg(5) for details on this file.

$XYMONHOME/etc/netrc - authentication data for password-protected webs
If you have password-protected sites, you can put the usernames and passwords for these here. They will then get picked up automatically when running your network tests. This works for web-sites that use the "Basic" authentication scheme in HTTP. See ftp(1) for details - a sample entry would look like this

   machine www.acme.com login fred password Wilma1
Note that the machine-name must be the name you use in the http://machinename/ URL setting - it need not be the one you use for the system-name in Xymon.

$XYMONHOME/etc/cookies
This file may contain website cookies, in the Netscape HTTP Cookie format. If a website requires a static cookie to be present in order for the check to complete, then you can add this cookie to this file, and it will be sent along with the HTTP request. To get the cookies into this file, you can use the "curl --cookie-jar FILE" to request the URL that sets the cookie.

$XYMONTMP/*.status - test status summary
Each time xymonnet runs, if any tests fail (i.e. they result in a red status) then they will be listed in a file name TESTNAME.[LOCATION].status. The LOCATION part may be null. This file is used to determine how long the failure has lasted, which in turn decides if this test should be included in the tests done by xymonnet-again.sh(1)
It is also used internally by xymonnet when determining the color for tests that use the "badconn" or "badTESTNAME" tags.

$XYMONTMP/frequenttests.[LOCATION]
This file contains the hostnames of those hosts that should be retested by the xymonnet-again.sh(1) test tool. It is updated only by xymonnet during the normal runs, and read by xymonnet-again.sh.

 

SEE ALSO

hosts.cfg(5), protocols.cfg(5), xymonserver.cfg(5), xymonping(1), curl(1), ftp(1), fping(1), ntpdate(1), rpcinfo(8)


 

Index

NAME
SYNOPSIS
DESCRIPTION
GENERAL OPTIONS
OPTIONS FOR TESTS OF THE SIMPLE TCP SERVICES
OPTIONS FOR THE PING TEST
OPTIONS FOR HTTP (WEB) TESTS
OPTIONS FOR SSL CERTIFICATE TESTS
DEBUGGING OPTIONS
INFORMATIONAL OPTIONS
USING COOKIES IN WEB TESTS
ABOUT SSL CERTIFICATE CHECKS
LDAP TESTS
USING MULTIPLE NETWORK TEST SYSTEMS
XYMONNET INTERNALS
ENVIRONMENT VARIABLES
FILES
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/criticaleditor.cgi.1.html0000664000175000017500000000456611671641417022414 0ustar henrikhenrik Man page of CRITICALEDITOR.CGI

CRITICALEDITOR.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

criticaleditor.cgi - Xymon Critical Systems View Editor CGI  

SYNOPSIS

criticaleditor.cgi

 

DESCRIPTION

criticaleditor.cgi is invoked as a CGI script via the criticaleditor.sh CGI wrapper.

criticaleditor.cgi is a web-based editor for the critical.cfg(5) file, which is used to configure the Xymon "Critical Systems" view.

A detailed description of how to use the editor is provided in the Xymon Web documentation, available from the "Help" -> "Critical Systems" link on the Xymon website.

 

SECURITY

Access to this CGI script should be restricted through access controls in your webserver. Editing the Critical Systems View configuration will impact the monitoring of your site.

 

OPTIONS

--config=FILENAME
Name of the Critical Systems View configuration file. The default is critical.cfg in the $XYMONHOME/etc/ directory.

--env=FILENAME
Loads the environment defined in FILENAME before executing the CGI script.

--area=NAME
Load environment variables for a specific area. NB: if used, this option must appear before any --env=FILENAME option.

--debug
Enables debugging output.

 

SEE ALSO

criticalview.cgi(1), critical.cfg(5), xymon(7)


 

Index

NAME
SYNOPSIS
DESCRIPTION
SECURITY
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/manpages/man1/eventlog.cgi.1.html0000664000175000017500000000342611671641417021230 0ustar henrikhenrik Man page of EVENTLOG.CGI

EVENTLOG.CGI

Section: User Commands (1)
Updated: Version Exp: 13 Dec 2011
Index Return to Main Contents
 

NAME

eventlog.cgi - CGI program to report the Xymon eventlog  

SYNOPSIS

eventlog.cgi

 

DESCRIPTION

eventlog.cgi is invoked as a CGI script via the eventlog.sh CGI wrapper. Based on the parameters it receives, it generates the Xymon event log for a period. This log shows all status changes that have occurred for all hosts and services.

eventlog.cgi is passed a QUERY_STRING environment variable with the following parameters:


   MAXTIME (maximum minutes to go back in the log)
   MAXCOUNT (maximum number of events to report)

 

OPTIONS

--env=FILENAME
Loads the environment defined in FILENAME before executing the CGI script.

 

SEE ALSO

hosts.cfg(5), xymonserver.cfg(5)


 

Index

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
SEE ALSO

This document was created by man2html, using the manual pages.
Time: 12:13:03 GMT, December 13, 2011 xymon-4.3.7/docs/mainview.jpg0000664000175000017500000013671411070452713015510 0ustar henrikhenrikJFIFHHC  !"$"$C" ] !1A"Q2TUWSV#3Baq$%45Rbst 7CDdu&Er<Q!1SA3Raq"2BC#bcr ?r:BS)䤝Qb+rٞxaQIJT4/Ҿ*y꼞=b _O']2hfK9Q+*zKqFQCObꑡIröÖUb'S8XQQvIy;,W!\EQyDYHtIih6hЕl#Q=-Ö|? SȫIZZiEȋ"Sdf̭ul@=-Ö~G4Vc P86Iu:|NASaի#7YOxbJa FC NL9)[(t!rK5 Md3!ܬ׊GW_bIe#hIJngb#;e솘#8M>?Y+$iU cL/&%h-JkRk4I;CyXUV*t۸KSIPU1&I̓INFVKa؈hrٞxafz[RN.ix $o"CZxȵ fd]{3,56=cʍE?RH#N\ۄ*{?hMPd)Q4-ogÄӌCed)J[EcZR"۰bIK=[+a!Dde&ǖ(eiNũCiqm1УBr=RT.FGZSש0ty=;U)K[sr>E-NVGmsˤE~B^iI$6quiZ6&#m {[3 ijSjW+n\6_K^Q/' l@~[3 s8HLaO ֲc#԰Mk̴)#bs('cTJ=#hJ%U'KLeVG #J:Q2b#D6"q*D,*3WXfz[}sFC*YTfCzknK2DnH+Dɽ!_cJOe֔Ȩ%sNƸ6Y]U\|lgc#TV@r*_[d}166THDFFW-ogfz[-o<݂`/Jd_jh7}i%.Zϭk:+( 'ZjV6lnO˗mdkٞxafz[z[o2<^3c+&m]!{[_lx}F/ZTxK0OaQªUN)hIdG`\|grٞxbmKKYsQ%6LYAaRRcTIA6ٙM˫1'o"&¡ړ͸%S4>WQ).SI"AA%cI @_gţWGWT_~Ɏe;kI)*er2;1>!&TF0*& 4Gy.H}:kM+fLO!"#Ji%|U\F۔fQIadnOJb%H\УNT+Fj ߖ[3 R<+/?RȜ+2]C.- #8c?$j<P۩dUy4"E m)̳fVӺerٞxafz[Ꮐ)T_|UT+ y=~/{fdNdc?70-[3 9lK1X9S)ʫ)+sJnF3}o)JianҒ_Ԃeyԫe< UŅKfު'iE\6i\dmJ%]iӐH$).FdV\TjH˫S"Rd(h#,ٌ$sfz[-o>#1ߓMC5'"deˑs_{嵋}aV[irK [MՒTJHϬyPX$qٞxb5Zli)D"4_9-)u)'4ҮII܌ձ17hLI&vFMqä4Ȉqq5R\_";*T:Pe@*n$h̳8M8廨byW-o*:tڞjԔH_aK\SZ(SI]֢Z䔫)fٞxafz[Ꮐ>grٞxc-ogfz[-o>ٞxafz[Ꮐ>grٞxc-ogfz[-o>ٞxafz[Ꮐ>grٞxc-ogfz[-o>ٞxafz[Ꮐ>grٞxc-ogfz[-o>ٞxafz[Ꮐ>grٞxc-ogfz[-o>ٞxafz[Ꮐ>gǕYr:ɷ'?Qᚕnχh'{`ρaL(Sw>rR"L=:Q+Y$M\bqPWkjyue}^yc0~.~I'6]WzSTەZ^Y)*y$dd{Gq2j[ u襒sX[Uԫ!(AeBH73tw>4w )qDE61G0g3# G}]QȴiRu'"RQaD(55)iRR2R\uw}g3O :>~'M;Rg O9ڕ8]~cQ}`<0G0gS&˓ci. S2tfId%#Y$fv;mT1db1 ӕ$nI"D[ۣ>F74Zm,8iR h"ؕVܶF\L|z>~'h ?mڕ8]~c6N~5\b#6%hљXY)7-嘌m#-[}`<0G0gBa&VDKNKu&攩\Es칍FQ L!,.!.FI22%)JR1h ?{~6) (EF P"JR hvz~t}g3O :>~'X [Ժb[m&Esَ'a߅hs+pmF7jGmHK<ѭ2-$Rn[1lF[iF߰oxaF߰ox`FxR9|ŦQq7e&|Emkk<[e8~6~ ~6~ n^WƌdUYnCTHMZ49Y΄.[kl^|1y.z?'UvkeguoߴW\m .m .&+ <9[)bN^(]䑭fD{ 2Lkݢ[)8Yt7-((̈q|m .m .X HC*q lmhAmʓZ[2łŒ**+GbTLّF&IXV"?}~E ?}~E ;75-0JJTFJK.܎s+p_F߰oxaF߰ox` C^ߧ>LMXMi.!&)A)F^qBSs-Bohp0ohp0k YUwqT-RoR ]*JL4g~QuWo&[[v}g_6mEuF߰oxaF߰ox` YIĕKdefR:Ś551O@R ϩ;i5o23+p~'+pÝ^ߧ4=ÏIGC\cohp0ԯYvz~-\/ 9-\/ hs+pÝ^ߧ*~6~ ~6~ ZJ.?0jWwa߅ßa߅;Rg O9ڕ8]~bohp0ohp0ԯYvz~-\/ 9-\/ hs+pÝ^ߧ*~6~ ~6~ ZJ.?0jWwa߅ßa߅;Rg O9ڕ8]~bohp0ohp0ԯYP,r X2JBIIۼv ߟa߅ßa߅FCZʜ'hҵ>_w.}>dGXNF}Q䑩3{6 ƄkV)TZ>3߾8 ӛ3]-<-\ms;a's=A5ڔj=]RY1:o2"_ ꝋ*UZ}424O9gYn.H JƋ*YJMˮ*Id$nFg"%tTq)fҍ>}v}/aaoNV+q+~r8Y~W?a*nF%B̋!B alo!WdgȷicdZKYdVBQ<㙙J%ffeFGbxfJV^'u ^>c{w4N%)$V)5)Ge#ٺ{onPP.35'XKzwa_aҥWqM-YCJsRw'c>G:^DgTӈQ ;XE7bבm#1*ۧKBq-'aYBOaH_36y1TsqoZT-b9)IZ.iofPANssKM- UFl\Egba ќ9TbH*#\EU݆F[k, ntkME~m$6n6EwJ^woXrdzuL݉"r1yclsq;<ӹVa>|Zѩ//Ws՜G؁8s$7-&J!9} NgRRCMd>g?1R:,=5>=ql쪞"܅II)K6 +TKRtc5դ6k$9ILSX)H]R6U OFo<#L.uJB %[:t6Ldê2 Y$o4Z}̥%FV3jJ6i#3)+u-L:V#ek:b/We|:NacW)S:QP72_I)uK+fh SigR#6m O+HfM-M-Ns6(S]4* n(PܨRu8Re]&Gc">њ<i)Ev "XfMj.ɖQ>᷐Q]$jZIdRdF,MTQ# Ӈ]Rƞ=V*iJKL+dwM⩊4w|ժCy<r'5\ʳVɫ}?y<= ZJAə\УBQn!cb4U?9u9bHٚgC' VWKF^$J *P[\ZB)fjE qZI].%zē=yB٠dR27I'J;;2Qj17"sru+ZIHQ#.{(f*&T## k ;e2 e2q[rY<خD10-2F:qt1WOJ:\nT[IZ9ڥ#)-҆fEm暪`* uK"Q"3qe5]jx0*_T{aLCp+XLupU(9}%)+@DAMIiN+hJKj֣m3+'B&4ƛUPi &i&3w,{xiҥxjz-J#^thGUwquJ 䢮I ԲE\g̈)J2+#Mkweƅ $53 (Bj3=D\Eg*"R9F*yDv$ Ec32!i&2dFO"4̆S7=tѷg[nVM>g[[[QOs^Pj G%941zRg3FcYq*ꔺS#/r7[ɑK_Qֶfȵyrc5gEWFpp4X&p]m$H놙eK}rIǘDuQb5!oZlem6XlgsͲ;kʼb} k*d9҉\MQaFN;̓*-!,aZO IiGo\e)IBNW̜;%52OH;2:.5s2ٝjOI#2;` *ѹHG#2[KGa%77ao3/75g?1SĚj5#G_5XRJ=lv۴\P]B]E|[x17H騫‰8uOi\Е(d8VΪ^4S2NW I$I.;oc15;3 صJ%:"~]][;k#I崈&GrӖX5LwXzXSLSg!VVy)3Iv4q8ZdᲮžX[wnۮn -̓c)y->]B^elD%![vDFGȌ}$[Odž?a/i?6=[Odž?a/i?653{ 0_Ē2XSC,-7#n'~C\kqFeI+E*tTzwm3p|1NK Kfײ{eF XxZI^ӏ{ JHIY$V" h")4f{%JKqJZ_VzR w'׆#VYb{\HHGr%>q)b,#M(k [oJZkv߆6VLf){ǽx#-5so 2љrGrI]::3Ѷ"?iᰇ:R_߷;0KM~+Y -Ov՚qTNgahI^3I 57&= 4LX/8ӎJidv3IC])i۝~tnwm`-O~M`i8fkIuQv$݉ 'Mٓ6;ܲc_})i۝~tnwmc9jR离Xc_})i۝~tnwma-OԺl'*ΖeMV$fܺDeMv> :R_߷;0WFb|Ebr>IMG,k KM~å-5so tf)j~s]{Mv> :R_߷;0WFb=5Ǿ>O5FL!mFiY֔Wr%r-J~utnwmaҖݷZ3?G9=@离XJZkv߆)i۝~e1KSsc_zk}~qnwmaҖݷZ3?G9=@离XJZkv߆)i۝~e1KSsc_zk}~qnwmaҖݷZ3?G9=@离XJZkv߆)i۝~e1KSsc_zk}~qnwmaҖݷZ3?G9=@离XJZkv߆)i۝~e1KSsc_zk}~læZ|t9}IR!c>-!K},A8;H3oMv=5Ǿ?2:yi?:yY?kqoMv=5Ǿ?2:yi?:yi?7=5Ǿ5xԉs!^Du6(%.Fˏf]e$bSNJ҅) 2Q֛̈em5>\ RUV4ѴۍMhʻFFT&d{731-U*Xʐ5EL55% iCQص(v&}Sj!h[̹Csyf3k:s)2 RYJ5r,l AEB[zĜymHA=)7Lr񩪒SbOnrJi%YKW?= fjԊZ4C+"n)7&B5RεFhARS31\ u~LzErT9jtQO'Yl,ԛi=zJXfcaJ"%CY2Hm>i+BJp=7c4,E\F\aM L1J疩%uMI2? e.M] N='}&B.:2FE{J%/_YSXSÈJ4K7 T5emvx!4=!)uD yfDF$>VaGC T)Sq=q8j5 4jM >?Tn/CugeժqԢfd"k2&W3pLS(6kgfZ&؅Si6K 3Z]t̮r$$#RkIfEĘjT*25$DZfCBYf+dFW2uJR0erR\\"4خe.rMJqf2vKڞ\l}VL^FN2injvnq$#~^!V* YSۮdw,eþݹJbﰎ=M +8jMs%}&TiI%SJBx+EkI[A+0tkCFfF\ڋ1qSa嶧 (J>h/\F>)4v]U6u Zp˶ْYHd{b᪤ү3423! qJp,#IV"̝QQŔVљؓIQ)E'b3C#\y4mHa6rk̹H8y"J)%t5!Qgi46cT%cQ Km3: ^ק?ٵY&=Tj*ޫ KFiTRi2M*(;l6lviƐUN֟Iitl0q^5Z޿U ʔsB`/Woa TN3"Jh5-N|vM&F{#8i2DŽߎͫ;dj$n"Q {{٪7&SvbmN6V*Q)~\b;O1Si~;ONHv IʚZaKy*Zo()%*QVJdJ&krf?.[mvx פ"A1 f>u) 3p-)blf\4~Tвժ<̜5y#s澯'VRq8P#z m5]y,Ԕ'rsCbtC:LKX[Ҥ3y -g[gQc.KAɯi$nj15@>q;gN\\suke' VLV9 k6sF͖#=GoLj5S# JJjE9J찇Җy 2#lms'6M-E4ҵMP (ւޛ2%$hvhKhjaWi.ә-y:9Hd-%86rJo͌q8feMs*): 6iVZLH_UDd{n)R_EOyMdD=Tdw>OyMdD=Tdw>OyMdK@g#S)}u?1Ȁg#S)}u?1Ȁg#S)}u?1Ȁg#S)}u?1ȁz:)}u?0Sz:)}u?0S2thc'O<2}"2thc'O<2}% 3FNyMd>O@g#S)}u?1Ȁg#S)}u?1p Qo1b5BeJ3Ye *JON$@ܞ OH@````D  0OiP[&i$LIF{n;FROYC3f $*m2!;*Qǧ[l/_}1M7_?V~Z/yi2Bde{*7Y6j7̏e΍_J(Z8cDduVFE?47*މ"2m,N[XwGю:j>Ivm_ƨ0z^L\!ZLlYNLJc0J&L\[a}$:1&9I RU}Ww!1eDU"[B[5-I$- v2dW1bJ 8VOtڃr[*E3weo Fn~믊m.ǘ%OVΉP̡Ri +$b#Wƺ^ zMDY&\Z).uky+I',I߫/n(9ȈQٍɖDuF[#a!ʳq4w))NOV6Ltiu\eN>?/UwnЭse-AcYi*2I?m1,yzDZyjٻZ,-5(ZvVT⚔e4!"3ٸWT+pe\tb;e1'GF+e:1]bdb;2Lc$ | >Oh=0&te+GUHJ%um5*+% U=N(t}*i*!QCJrA|"2I\33Ѷ2 i52Ms$N5b"I*2ؔ_##->-Pφ*N4 ;kbDvN6nLyIWo s+]嚽嫾;m'aQ]K˕zaLTgqi$YF""ٰﳻF3m"#bSfb\<5!$ܔJ$1E4qJTyT5,ب7q5Qk$Jڅf[?>Q,c6UNM=M ԔlϭV˨hms&2TE6] Ȏt=byɽB4*cL8{YSJW6h֤8[-${;mmn2Hr2y6G{)}֖IfW; ]@6fꯎ"ae)/UgAe436dv:)*ůî*=)DЧr)$h4yyĸL3|b\,sNC6F- :bM} X~%"#{Ri%Xi1L*M>&tgrUG$j3N0)%MdDC*˒Ԕ,,̶*Ix-.rTZ-TDÎMұEmNۖ-c= QاV W1.thv4 J%wY;; цѝzD!6Gj K~ѲSƒQK%&5h-#g"k1OrCﶅKδ^:UYvXgJX2u/`bبJd\hԤ"+ c\ 5eJ`~%=Fj;n(AFVeFѶ02iؾl7tf Pqu0\uI+j23 %E1 Ip]|Rfe#i#,Ͷ8IH/#z eHTJAg[,}9؛G] uG:ugd)ZtӔ ԗLV2ƨh kZW!b6dszc Sz6i$՘I^w5 Σcjm}:^MiqWRiOTEôtZu(1”ښqIu5#*$r25 Q!SiX;ƩU3[VQf"NS2H8D  OH@>3&*LLAȭI%/.r;\WavB;R:4JtcS#~9*#+ꐍ)FjefQ7qz5Rk&I6ȶ#;L8YGS*,> {8G̉&k=oE`)3_& az%bS.IQgImmF[zHZޒU "Y iqiN~[v'fm#^?uHEꬊu'5nfy*Nfȉ{um6HҮ Np q>p_0ΚD#UaI(Xm#@N*pn"ё w56T$FDdiͰS/L/.9U[W/)wOYw'}j<ہж 8s7/Ѣi2CQ 322p23#Mm  Ѩ}iT* A q.e}>U svh:[ΐU/r jO]^ow/;vͻ]!cQtCP" ơƏ9c)#SD{ \[xeټOWў؃7I)4eVIYVݰqZ"U8Z<PjrJI4nR-wq&^-L6$JIfZjR^G@GjSVphC3 2ICMbD+m]>v:1udj0)RX%9[FY+_?+-юYKOT]3$*U>3bl?z%+ӕm4n ʴ%+w@;le#xFxLKxi~t84uhRH^jMN"F}?p'GuX^%WS44H9Q I˲Y'hUtLCL?bmzȧMfP|a꼪mUC[V`[m)[Wa֌sND"V6̵?`wjwV\!)IHBk Mv\V@ÏaZIsV$F{#gm-4aF/T3 W"L]KT#iF3=чGq.]MPUiu KNIQ]jNR' GRb4Y$3jљi; -Kl/DJLFJIcܝq}(tV L42t6m ' NdGc ogr׫]|ШԳv%' g|GbWwţ\?B& U /$-6[k˻ml7´ eTH4Ւf6ђq Q'qjowle4ŴEQ(Gq8YV4!FeFDW+i 0 ˜&sv΋u0N8N)hA%[w;voyp~t)ZL{*})޽2}'3a!`-֩MU:fBVq*o#;iOU0Fd13PѴRϪ 0?3`$̓WFjQfƉQlIR:ohϵɪƓ )CIFqf3ePȮe1Uj4M*|ǥdw$kj?DGU{8u962il9?DVqs7 lî{3X?!*.!sjWu8j*•/0_~b:Ǹg]̴+P_@aXt'/6=Wq- .}=t gq?0y껍hV,t'']0?{ ͏UlBe gq?1cO3^lzfZ +]0?{@aX ٖjB{E.}=à]0?{ ͏UlBe gq?0L>~acw2ЭY]cOO@aX ٖk Y}cO:Ǹg]̴+@_@aXt'/6=Wq- .}=à]0?{ ͏UlBet gq?0L>~acw2Э@.}= gq?0y껍hV,t'#]0?{ ͏UlB Y}cOG@aX ٖjL>~a.}=?Ǫ6eZ_@aX.}=?Ǫ6e[et'#]0?{ ͏UlBe gq?1cO3^lzfZet gq?0L>~acw2ЭY]cOO@aX ٖj Y}cOG@aX ٖmA/]0?{@aX ٖhL>~b:Ǹg]̴+b,.}=t gq?0y껍hV /]0?{L>~acw2Э@Y]cO:Ǹg]̴+S_@aXt'/6=Wq- .}=à]0?{ ͏UlBe gq?1cO3^lzfZ /]0?{L>~acw2Э /]0?{L>~acw2Э@Y]cO:Ǹg]̴+PW@aXt'/6=Wq- .}=à]0?{ ͏UlBet gq?0L>~acw2Э@Y}cO:Ǹg]̴+@W@aXt'/6=Wq- ôY]cO:Ǹg]̴+@l]+XN".N:[S֤H'KS2i;o+dd5"d}cWўd['pH5wC[#q\#i#q\#@3Y+xce+xcD3\|f>0*einGxF#EHtbS.F#i~#!4?n<X1L@'@ != Oh @O`=BA8  Hbx0B' HIhv }cWpўd['pH5wC[#q\#i#q\#@3Y+xce+xcD3\|f>0*einGxF#EHtbS.F#i~#!4?n<X1L@'@ != Oh @O`=BA8  Hbx0B' HIhv }cWpўd['pH5wC[#q\#i#q\#@3Y+xce+xcD3\|f>0*einGxF#EHtbS.F#i~#!4?n<X1L@'@ != Oh @O`=BA8  Hbx0B' HIhv }cWpўd['pH5wC[#q\#i#q\#@3Y+xce+xcD3\|f>0*einGxF#EHtbS.F#i~#!4?n<X1LXFgLtvy$6R"q:*fJN$|ԡMCWcIf.Z-\DǙ1RGjC56UhJl ԓI}d2 bGeS|,HNJҶ2)I#NUp3Un2\Iٕ NN2d&\)oh['"BR$fDe=KRvxOnиCRrsaGyF!Y$$LhT Jb'jQ$ed֕SsZEυĹ_[D?;8Z:a(y12*" =^dJd{Hl+:0X$g*#5EF;Iu#/8HIFf[DXT[ϟ˹O XURLL. 6Ttҕ%j#=4-b19MEԐ JkZJ I"%[.vjNYz|X -W)D}:}b4k93فcK_x(Y)*RRZ D[RGOɬlw(- !ԕ\KA{I cX$+/!߬#]9.a*.#(e]>Sk^VhBA$DĂ#33-Iש9\Z0p&]2&_t^35! "K*dK%YxXn8KkSÕ)#"vIIQ+jq-Z朙\LF\&uLQ.3n4ͤ8Z#QHnwc:n%ST&Xvbb%Œ4=05t+rܨgNfWNk% Jfs<#<>|j56}|#%|,5&kLӒĭX5 Uf ꮳOdyI2Mem 4uhqRM4&g%և$eL{+qxY;70Gwu RSc ͐*)3ifoXkRԕ$[> %eCXAXwVS :SI)*h̍&j,few@)UTrK:r%7ӥ2ڜ vݓef}dT2FiRmIFW%t6iE(帅&Q-7e&3V#34_b5{@y UbI.Eu(SjB3̅em %ŒJJ)r -7+[,%Yd33RgZ=C]juTe8Ʒ `,-CV:Vd<9qmn)/us4D[ey>.ΏٕO`L+. T57;R6%.جe꽷mΎeNHgTI[c$KH2-qF+ =sq4 j1g%.X2oGta> n362ԦS:[RwT"Mxi%vտ {DL TNJmC˚ېέ+q"hEV2ڽ痭]St.&𚚺-'+\OVcĬI4r1Zڲ3J'սsշł*%8"KAܔI""Yض1.VvߧtiF@hƒD5h#&Uu3ؔܶY$e(%Fj<Ĥ-q /SLj:TlDP-)ʍ9JͦK9o˷a*.?|;U;})a8xY4ҍ {'+Zs)%M8wڬĤmqt#%%tZ_Y2 b/W)C!Ե"6i5^?;fy; LYUgWKim5gFfѲ/ Yrӵ_ftJMxYp^QT~%0ITV61.[lC)eRa$e3EmҥSI`Ǐu)wB)6 \Ԟj˷}#<{N4qQirfU| Ϙ_hFFF᭓ln;~BƮOƮOՠQkdo1kdo1բS|a3o27F#F["$b:1]c)ъ`C#4?c,t{ug*1sI#m?Xtebe2,Ŵc1HMJQk(+2 Ri [%S7⠧&pȲ|sW{_H4.52!쒔Uʝx䵩dj2˙ۅ+ Kk\ɆT[shjyB} ݓ iudGk")Dv<ĢVW JgS|ͣÁeDdH)cӫ-ǦI^ROܖ{3;:~ii(1L4I9zgBw;,j۽Fv[qZ&jL4-9yVTR0%Js'XI6e'oX\U~/&h%-Hi58aҔ$đV; cɅX4O*\ԪW.1Du*hmIBP334籶&kƤj<ƑLZ9O0ꉤI%6J2M5o;Z~dTv[eRwH.J"T&r &;wdgTW;TWꑍM%CYWO.#)|,{ɶĎ3q"Ot'vn8;=#%VRnG!)w"FUbJl2SU8F嵆&%fcQW+3ab3*[/֨TqL l%)DIiO{1+\]M}"+CqƐ5e}[l]#SӚƤUXr'$^rp܌f͙zn\5Un $߆iuvmK`-23-V1i Ъر'ΛyCinRR dRL{ 9An_Km ERKJ8yJrIp &x#` oBSb*q66qTb)BDW$J\k"o&P +(yKv+RJAen-Ti7Jj[N 3Uv\oy0 b)a_Oa9}mEj1R}OEqiV2UV3iMIDsE"I84iMRL[mkV*ɑPnCQgii#hIj|LD;XȌeqŸ݊%ێV'F47IZF\qD a*4aR't{ug*1sI#m?Xtebe2,Ŵc G IJrT-0DFVOW+ [ZɆR66\uHq'PDT/LQ]MSRBDA-D[ {ȈTeRvAz|:K=*ki-ov/q'Tuٴb.bj:ueѳyl63һ!%k1Gs"1GD)qU%.&y\R-zIJQ|ǴdF+p 6'3 džӍ5fB44 vGkb!^n[F4⛢J"䈌)E#cS%Ʋ[SXZhǑ 68b$@mc3 +Cl i#amndA9ˉ,b}ZH@PTJ "CtkR]S.dbT6v3λi)rTڪ1 jxȳ5wl ~j1* @-8nKtYMᙶI3R$zn [:㐔ȪEVq5rlc?ϕ h9:C/F6N)J^WkQr4YOa;3*"id9jcIH$ֶKi]J\eBP))ZFPٵ:L"Qޫ;boʍb)qiMQh*qtmB5 .bj:ueѳyl63л!%k1Gs"1Zb ãX!өJaS+m!g-jK#&bX}Ѥ舐ҪfYɀfAms+31ZvU[29]hY~ChuG5GGZ6]# +mn5GGZ6]# /mn5GG NlGZW5GG!k +@ NlGZW5GG6}QQh͗H"BFۨA6}QQk 29]hY~Chu<ѯ?*>29]hY^Chu<ѯ?*>@29]hY^Chu<ѯ?*>@29]hY^ChuO5GGese?Hвѯ?*>!k +P NlGZ_5GG6}QQm@ese?Hвѯ?*>y _mT|!Zese?Hвѯ?*>!k +n͗H"B, _mT|!ChuV 29]hY~ChuG5GGZ6]# /mn5GGTˤ~ud _mT|!>ChuVjt.,!k #mn @ese?Hвѯ?*>y _mT|!Za͗H"BFۨB<ѯ?*>@29]hYek #mn ؄Z6]# /mnFۨB 29]hY^Chu<ѯ?*>@29]hY~Chu<ѯ?*> 29]hY^Chu<ѯ?*>@29]hY~ChuG5GGZ6]# /mnFۨBTˤ~ue _mT|!ChuV-S.Eօ6}QQmn -S.Eօ6}QQmn -S.Eօ6}QQmn -S.Eօ6}QQmn -S.Eօ6}QQmn -S.Eօ6}QQmn -S.Eօ6}QQmn ô29]he#[Q8 q5*g!̊ڌDc`5?DGU{8*??a}=>9NaYq4v6###dem8u[WGGҚHóvqVmD{ź8єZc+2ƮOa7M#Kf>0e?DXmjM]#n|Ꜳf+JV{qrt93s%Ondp*|ٴd2*F#GWHnjK)_Mb[SMHR2X:gst5MW),"Rr)V+,˱I)7b ;21zR1Lc1qBb >ŵU1hʶBPeE+~'m;6Z2ݧFTsQȄˈCkm7ED[R[ u[j's߉QXE)PC Q0tX]F5BSZ!̮;V[$u*6,-X&Mjl Ը,trqm\PT)_o? TqU%_suD9( ceK:ƖҳIȎ-c#.F1bdHc#"\~TIJ״'VrÈf8|CtGz=qNmm}8jlZױ\|e42d{Gt-Dҥbu։ eOh2PdYnܓԮx,bvIqR=$84K V ZFeFu.%K"+]+"ܢmw[i..өSZ8Ԋx0spО Еqld#)p`:q3$e{|M)y?JT6Ov3{n؎V=5r-C_~}9|JjO_FPa=tS 8zS"QTd+$d#P%Z7J_ R6ԯk[nn$M<@{ |EpL@ΤT9Ty9.8)IACVSQ"+ugqs|wz4mZZ_`{$ FsHjCoH~YguiBg6k6Ȍg~JԦމ2RIu)>CdfLGB#ٲnpρ?a[Z-m{zxDpm@fSd%HKOu- !IgcYI#4ʗwܒSiB֕lL;53~%yP"+;^~6_;uӱ.ikv![J:S:H3-4v2;RȌ ܄g"5m3"2j>:45Z"ڄGyԳVZnifEs3`rv`ڊ|hH[G!(vSDztYmp- K]ӣ*9Be!"J"--[w- W}vĨN_}_hv }cWpўdy·"Ce1Q%(I833=D\GiʋL4A9fKO>k'( F᭓}6-tV"ԔfvpeU~ Ǐh-9-#De62g=un+@(UV48ZH̫eU'.d38E核$x7dvƏЙr5.H-*ʔʌjoyRGUU"Am7&#_>"4aFc'$BSp/=i^´=U]YlCD ľef-! ۗYTq.Q9%x_bctȚyѕ[Zb cAF.̭I'I.BI"CJ4] ;SQT܅J'EQ~X^u0 =v{ NNl-*Q̺Ci2.;bR^)BmRTƳXYgpb,"JH_}6B8(FB>o9Gq |A.YRQMqďZQ-Ɠf#gRsDnM4Gl},E o#dA L\Y+ ʢSUw#GZ{eҥ8ԋY3̸Y_Jfjg&?v脐AYI%dOx&2&Wr=nd:yeX.9ǠUK>77/Qb8nTYԵw,Eο'IMˬ/ |o,qk6H#~~iOSˮGxꯄ8ږa 8Xsf8꣺ߵ6r'f#ڕXg;,˜=SiA5NKNT4wFHȺpDRg1YCSLax ì2ٺJHԧ WE҂"Mb@a8K+Lu!Bu0nHq:I3l tE#V0h9Dk:SuPȊdW3" g^ ^#rfɗ:CNgv+ fY\G[G)tL?GZNr:#um8[)IhNtc=;0`H ұe2^^gT*RN6[Ȍy #>&f[?#h)PUK٥DuS0un!I)5*ݦ{.| >i)PiEiUfl)Zͯe;e㽶\ PPi*S>[5PR&[qWg؏kM1ԦqHKI%UcYn# CmRAH9KJ!ܒy󛌥}Sʵm=- HѶnAk[ "gb-{lb;OYVV7o)ۧ1ڽ~^QLm|,aSri|oOP|m6_=/2;*E*'&8SjraY{p3BH)5k))`CpaAĩL[+3ilOCBYHm_i"{ob&iؒD[LĔ*KB\9mFJ##JI(\1e+jU"U;bDqI)CYzڲ}s\[3sAC䢫M+X⠭ uvvbkq81# qoE~ kWJ^_:(ô ;G`>U| ϸ_hFFN-Pj4 %$ˁ1ԻѤ_+oS:;/~vNF᭓}skӌk5.),*PɚV騔*+gdÊBgUiD5۳JᬑƮOԡRVU!jp3vL]M\Vəd[O019g>>/B`0esJ%YFəUt{ sҷl:dNQH鴛 =&]EĤd#\M$IJ{'p?L;=T&֣ӨPiH%ԜeTmgPi#A/Y| І򼢋15KV?P`SN$Ҩ[#5j32b;I8xaKUGQKzɱPG!A$tzOƞY3<ŇQ3,ymm7XJGgjx"(F"B]獖D-MmZuFebIJ-[1]b!)KzyT|AUcR5eU'ד6RYuNŞk)F}!krkj@<R9YۖB_(S ND]CydBw1X z6:,Xt)JsyJסYd$f;XbYXԓ I2'A @љKi܎$\ Ǵai5fa2Ox=IBI INJT aÏ'a">MYi=ZqN8-k3R3ȑ&I `ȧ̕OO) +2.[8 4 _s>$2S:^p+IJ````D eu2q %!i;L1UͪT+]E+ŕIhv }cWpўd['pH5wC[#q\#i#q\#@3Y+xce+xcD3\|f>0*einGxF#EHtbS.F#i~#!4?n<X1L@'@ != Oh @O`=BA8  Hbx0B' HIhv }cWpўd['pH5wC[#q\#i#q\#@3Y+xce+xcD3\|f>0*einGxF#EHtbS.F#i~#!4?n<X1L@'@ != Oh @O`=BA8  Hbx0B' HIhv }cWpўd['pH5wC[#q\#i#q\#@3Y+xce+xcD3\|f>0*einGxF#EHtbS.F#i~#!4?n<X1L@'@ != Oh @O`=BA8  Hbx0B' HIhv }cWpўd['pH5wC[#q\#i#q\#@3Y+xce+xcD3\|f>0*einGxF#EHtbS.F#i~#!4?n<X1L@'@ != Oh @O`=BA8  Hbx0B' HIhv }]E|͆km?k_v*!v FV5DhƉ i%n_X~kdAJMg6 KWCd8*9!C bT*Å L5rxJ#vR|(…'{9[ƶF*41] t:wtM|wK9O EH\;-hx#1⽧QS:1.C C] 4iT(sMD+ڣG;OwqPaq舤ޯNG"SxӞ4xw1Z oeYG=%zҏrI^x1ljYg=%zҏrI^x>!G6e餯ZQSI^xayQ jY}:i+֔ÔGNJ;C ʏEmKRI^xztW()2^Tz.jZ_NJ;ӦiG9NjvRԲtW()t餯ZQSZd0]Ե,4J?w:i+֔ÔV'2^Tz.jZWNJ;4J?w+@ ږӦiG9N餯ZQSZd0]Ե,4J?w:i+֔ÔV!G6e餯ZQSI^xayQ jY]:i+֔Ô:tW()@2^Tz.jZWNJ;4J?w+P ږӦiG9N餯ZQSZC ʏEmKRI^x:tW()@2^Tz.jZ_NJ;ӦiG9Nm@d0]Ե,4J?w:i+֔ÔV /*=a-K/=%zҏrI^x /*=a-K,餯ZQS:i+֔ÔV 2^Tz.jZ_NJ;ӦiG9NjvRԲtW()t餯ZQSZp ږӦiG9N餯ZQSZd0]Ե,4J?w#M%zҏr @d0]Ե,4J?w:i+֔ÔVd0]Ե,4J?w#M%zҏr !G6e4J?w#M%zҏr ؄C ʏEmKRI^x:tW() ږӦiG9NM%zҏr !G6e餯ZQSI^x00ayQ jY]:i+֔Ô:tW()@2^Tz.jZ_NJ;ӦiG9NjvRԲtW()4J?w+P ږӦiG9Nt餯ZQS[ ږӦiG9NM%zҏr !G6et餯ZQSI^xayQ jY]:i+֔Ô:tW()@2^Tz.jZWNJ;4J?w+P ږӦiG9NM%zҏr !G6et餯ZQSI^xayQ jY}:i+֔Ô:tW()L;C!G6؊+(PaSR! -[ZTUSOpU_,wc3ѣ쑣kd)NߢPkdn1m$n1uhfk%olouhkoL# шw Wwtbw1d;-ǁ  I$|'{D @x@ '@H@H8D  OH@````tA,,Sc3*,&e IROa#Aԧ('kf\Lx*1+jn]YKpQ-SI1#$t㶲ȹlɡŦ:hfEV:#6p>Z -n$ȍ/7|5-o= F*s MU&HKaM4GT >mm-IgYW23OF H̆Cʛ%rDЫYDI#[IޓZa)I9T|,ոorO¸FN85YÉLST2M24U6ѹF~BN1yJQvJrI-9d^hY$FWci_$vօ Kح&y$Ȏ\6&JmAsieI&)*k05 wڏLB"<4hJVnyH"4Y2γ$>ۍV6ևQJM*O"rWI6k_Bз$De6&Qm5iCŧ).tȩ-6&V:JMĕ:Y-Kj{յm~/SK~艙u) sQ Qf[ٺT3ږphw\!*țiF) ʊ{6nJ'(mBe& Uoŵ|-n??Tgmhz,QV[y"90gmZ`>e&Jq ̢A|tYOVfKu&)efČQu4R Efx8m&K[9D\*yz5]N7d<S'97Z:Tܭb^(v#S(,ƇOadĸCmj"#A8fDFFiJ p+ bEKǨm DȌtfdUU7|nW=F~!´VkQ9:%$CDqU9EBoϗ2P-N-V%~M.*jU61U:bSVVtGK6̢3+]'HvCf* ?]V>}cWpўd['pH5wC[#q\#i#q\#@3Y+xce+xcD3\|f>0*einGxF#EHtbS.F#i~#!4?n<X1L@'@ != Oh @O`=BA8  Hbx0B' HIhv }cWpўd['pH5wC[#q\#i#q\#@3Y+xce+xcD3\|f>0*einGxF#EHtbS.F#i~#!4?n<X1L@'@ != Oh @O`=BA8  Hbx0B' HIhv |ihˬH *t ǔ%He%$fd2]n)uFnb?ګd. LC.|N1kuftN$"\ОLr%)KdZ"2Y-xNoT!Zb-o1fEUPGZ+aHhU#M Uv Uj7QNgmY$ܕ$-̧ą=Bm}tPJӬ5 9ĽҲ%m%ð(Ugo9'b73 מv!m +JQα*eE^v"OFJlY;cQޡZ5!ur3r[\[_2e$*xKĕ+U]"&=BV'jėVC3%O>IQ$%H+nIm7+\nXuœz#}_y8hZز\>j>~Y?Vob(Un9T!CP!rh(} m}Ha]=YA+j[|_𣀊U@<@Qt1-O>ѱW|"gFǾ@D׏Bˎ6 ( т>7`3d]r?4h V)^|.oǣmpyJs^#Js^= v8}V)^|.o}V)^|.oǤ.+x}V)^|.oǤ.+x}V)^|.oǤ.+x}V)^|.oǤ.+x}V)^|.oǤ.+x}V)^|.oǤ.+x+xx7Xz𹿯XP>7Xz𹿯XP>7Xz𹿯XP>7Xz𹿯XP>7Xz𹿯>XP>7>XP>7`b >XP>7`b Gb z@pRa@\׈Ra@\׏H]UW( Ra@\׏H]UW( Ra@\׏G]UW( Ra@\׏H]UW( UW( yJs^#Js^= v7V)^|.oJs^= v8}V)^|.o}V)^|.oǤ.+x+x7Xz𹿯XP>7Xz𹿯XP>7Xz𹿯XP>7Xz𹿯XP>7Xz𹿯XP>7Xz𹿯bo⸔T4ZʩTy]p.gR-mbOL6a؎)j5-%u%5AOgpCiRZLbi&1OC.6DJZlnvRJҟUJQ;1G (+saRߣVjXΚO5NJٙ'<}0 d5;.qI͛uneSG6#SR4u-.CTƫu\j;n+جV!`U26"=6)\T˜s^VɭKZK=ZԞvE9?Dc! gY:d<$ ȵmiJ7<3NX+ u"tL-ySCm;*մ;E9?Dc )ag.)a5*<} m-kw+I5-DFj5X;NE9?Dc 8"wd1ݐŁŀ9?Dc Pv1ݐr(~vA`q`;NE9?Dc 8"wd1ݐXXӑCF;E,,iȡ#"wdP샑CF; r(~vAȡ#ŀ9?Dc Pv1ݐr(~vA`q`;NE9?Dc 8"wd1ݐXXӑCF;E,,iȡ#"wdP샑CF; r(~vAȡ#ŀ9?Dc Pv1ݐr(~vA`q`;NE9?Dc 8"wd1ݐXXӑCF;E,,iȡ#"wdP샑CF; r(~vAȡ#ŀ9?Dc Pv1ݐr(~vA`q`;NE9?Dc 8"wd1ݐXXӑCF;E,,iȡ#"wdP샑CF; r(~vAȡ#ŀ9?Dc Pv1ݐr(~vA`q`;NE9?Dc 8"wd1ݐXXӑCF;E,,iȡ#"wdP샑CF; r(~vAȡ#ŀ9?Dc Pv1ݐr(~vA`q`;NE9?Dc )KiԒмXTdp\FoIU u7PFe9-I,Qp- )L,KҪRݐ2S'RQBs-Cqȡ#QK7աo' W1%iuk[~WN˙; ??6܊1ݐ2,Tm~T\f(VTJ;1?xymon-4.3.7/docs/Renaming-430.txt0000664000175000017500000001261611615610703015766 0ustar henrikhenrikThis documents all of the renaming that has happened between the 4.3.0-beta2 and 4.3.0-beta3 releases of Xymon. Configuration files =================== bb-hosts hosts.cfg bbcombotest.cfg combo.cfg hobbit-alerts.cfg alerts.cfg hobbitcgi.cfg cgioptions.cfg hobbit-nkview.cfg critical.cfg hobbit-rrddefinitions.cfg rrddefinitions.cfg hobbitgraph.cfg graphs.cfg hobbit-holidays.cfg holidays.cfg hobbit-clients.cfg analysis.cfg hobbit-snmpmibs.cfg snmpmibs.cfg hobbitlaunch.cfg tasks.cfg hobbitserver.cfg xymonserver.cfg hobbitclient.cfg xymonclient.cfg bb-services protocols.cfg Common programs =============== bb xymon bbcmd xymoncmd hobbitlaunch xymonlaunch bbhostgrep xymongrep bbhostshow xymoncfg bbdigest xymondigest Client programs =============== hobbitclient* xymonclient* orcahobbit orcaxymon Xymon server programs ===================== hobbitd xymond hobbitd_alert xymond_alert hobbitd_capture xymond_capture hobbitd_channel xymond_channel hobbitd_client xymond_client hobbitd_filestore xymond_filestore hobbitd_history xymond_history hobbitd_hostdata xymond_hostdata hobbitd_locator xymond_locator hobbitd_rrd xymond_rrd hobbitd_sample xymond_sample hobbitfetch xymonfetch hobbit-mailack xymon-mailack bbcombotest combostatus Net test tools ============== bbtest-net xymonnet bbretest-net.sh xymonnet-again.sh hobbitping xymonping hobbit-snmpcollect xymon-snmpcollect Proxy tools =========== bbproxy xymonproxy bbmessage.cgi xymoncgimsg.cgi Web tools ========= bbgen xymongen bb-ack.cgi acknowledge.cgi bb-csvinfo.cgi csvinfo.cgi bb-datepage.cgi datepage.cgi bb-eventlog.cgi eventlog.cgi bb-findhost.cgi findhost.cgi bb-hist.cgi history.cgi bb-histlog.cgi historylog.cgi bb-hostsvc.sh svcstatus.sh bb-message.cgi xymoncgimsg.cgi bb-rep.cgi report.cgi bb-replog.cgi reportlog.cgi bb-snapshot.cgi snapshot.cgi bb-webpage xymonpage hobbit-ackinfo.cgi ackinfo.cgi hobbit-certreport.sh certreport.sh hobbit-confreport.cgi confreport.cgi hobbit-enadis.cgi enadis.cgi hobbit-ghosts.cgi ghostlist.cgi hobbit-hostgraphs.cgi hostgraphs.cgi hobbit-hostlist.cgi hostlist.cgi hobbit-nkedit.cgi criticaleditor.cgi hobbit-nkview.cgi criticalview.cgi hobbit-nongreen.sh nongreen.sh hobbit-notifylog.cgi notifications.cgi hobbit-perfdata.cgi perfdata.cgi hobbit-statusreport.cgi statusreport.cgi hobbit-topchanges.sh topchanges.sh hobbit-useradm.cgi useradm.cgi hobbitcolumn.sh columndoc.sh hobbitgraph.cgi showgraph.cgi hobbitsvc.cgi svcstatus.cgi hobbitreports.sh xymonreports.sh Templates ========= bb stdnormal bb2 stdnongreen bbnk stdcritical bbsnap snapnormal bbsnap2 snapnongreen bbsnapnk snapcritical bbrep repnormal hobbitnk critical nkack critack nkedit critedit Web pages ========= bb.html xymon.html bb2.html nongreen.html bbnk.html critical.html CGI option variables ==================== CGI_HOBBITCOLUMN_OPTS CGI_COLUMNDOC_OPTS CGI_HOBBITGRAPH_OPTS CGI_SHOWGRAPH_OPTS CGI_HOBBITCONFREPORT_OPTS CGI_CONFREPORT_OPTS Configuration settings (in xymonserver.cfg) =========================================== BB XYMON BBACKS XYMONACKDIR BBALLHISTLOG XYMONALLHISTLOG BBDATA XYMONDATADIR BBDATEFORMAT XYMONDATEFORMAT BBDISABLED XYMONDISABLEDDIR BBDISPLAYS XYMSERVERS BBDISP XYMSRV BBGENOPTS XYMONGENOPTS BBGENREPOPTS XYMONGENREPOPTS BBGENSNAPOPTS XYMONGENSNAPOPTS BBGEN XYMONGEN BBHELPSKIN XYMONHELPSKIN BBHISTEXT XYMONHISTEXT BBHISTLOGS XYMONHISTLOGS BBHIST XYMONHISTDIR BBHOME XYMONHOME BBHOSTHISTLOG XYMONHOSTHISTLOG BBHOSTS HOSTSCFG BBHTML XYMONHTMLSTATUSDIR BBLOCATION XYMONNETWORK BBLOGSTATUS XYMONLOGSTATUS BBLOGS XYMONRAWSTATUSDIR BBMAXMSGSPERCOMBO MAXMSGSPERCOMBO BBMENUSKIN XYMONMENUSKIN BBNOTESSKIN XYMONNOTESSKIN BBNOTES XYMONNOTESDIR BBOSTYPE SERVEROSTYPE BBREPEXT XYMONREPEXT BBREPPANIC XYMONREPGREEN BBREPURL XYMONREPURL BBREPWARN XYMONREPWARN BBREP XYMONREPDIR BBROUTERTEXT XYMONROUTERTEXT BBRRDS XYMONRRDS BBRSSTITLE XYMONRSSTITLE BBSERVERCGIURL XYMONSERVERCGIURL BBSERVERHOSTNAME XYMONSERVERHOSTNAME BBSERVERIP XYMONSERVERIP BBSERVERLOGS XYMONSERVERLOGS BBSERVEROS XYMONSERVEROS BBSERVERROOT XYMONSERVERROOT BBSERVERSECURECGIURL XYMONSERVERSECURECGIURL BBSERVERWWWNAME XYMONSERVERWWWNAME BBSERVERWWWURL XYMONSERVERWWWURL BBSKIN XYMONSKIN BBSLEEPBETWEENMSGS SLEEPBETWEENMSGS BBSNAPURL XYMONSNAPURL BBSNAP XYMONSNAPDIR BBTMP XYMONTMP BBVAR XYMONVAR BBWAP XYMONWAP BBWEBHOSTURL XYMONWEBHOSTURL BBWEBHOST XYMONWEBHOST BBWEBHTMLLOGS XYMONWEBHTMLLOGS BBWEB XYMONWEB BBWWW XYMONWWWDIR MKBBACKFONT XYMONPAGEACKFONT MKBBCOLFONT XYMONPAGECOLFONT MKBBLOCAL XYMONPAGELOCAL MKBBREMOTE XYMONPAGEREMOTE MKBBROWFONT XYMONPAGEROWFONT MKBBSUBLOCAL XYMONPAGESUBLOCAL MKBBTITLE XYMONPAGETITLE hosts.cfg tags ============== The "nobb2" tag has been deprecated. Use "nonongreen" instead. Xymon internal statuses ======================= bbgen xymongen bbtest xymonnet hobbitd xymond bbproxy xymonproxy Xymon internal RRD files ======================== bbgen.rrd xymongen.rrd bbtest.rrd xymonnet.rrd hobbit.rrd xymon.rrd hobbit2.rrd xymon2.rrd hobbitd.rrd xymond.rrd bbproxy.rrd xymonproxy.rrd Xymon network-daemon commands ============================= hobbitdboard xymondboard hobbitdxboard xymondxboard hobbitdlog xymondlog hobbitdxlog xymondxlog hobbitdack xymondack xymon-4.3.7/docs/editor-nohost.jpg0000664000175000017500000001026211070452713016454 0ustar henrikhenrikJFIFHHC  !"$"$Cq"3!1"#23Aa$BQSq$!A1BQq ?"D2}6dQvQ&Yƪ3TJ#oJQ$%%jQIffDEf!#1Y8/5%*[HR咈ӹPiI^b\)QI/$R3Wx!CL,|udZ?d|vQ7fx'#l٫\g^KYDC"Kg/%FF_ݼv'iU!m>iD,zNϺ%SfmU8V=73J ܁0A∍5FA*"CBiĦJLZjlԕ}4dx|n.^i~o-S:nKkC'4ihYڑEf.y7UVI+q6kS`FFEF( fIs'#Guv$eV'H] Dl[mdG Si#td~@a_S#r<9ȓlZq7\VRjFviyW '`ӕ)gNDŒMnOҭVROTTVB=Q'+3(M[IBR[*d{ƙ7Kj"a="4I]723QӪ֪QX G&枋L?32sFVcԞk6>W#-ʕM>Ť$&c.I23;IIp\v_#2Kg;(I$-)=DGbo i9%24j94DdCI3;5y" /# q*.;qjwR ZU'Uȶ/G=w/;kr)J|i-U -!'4'MK8C: eF=JClm,ʐmDl35Ưe/&de-k-u"""?pϱ&͑sܶqvJYtmjO[kleэF._ &n 9E-*-HNM}I'^ˆ;ǿ3Cw<ѹ3KKfȬ&jY)FhM<,Jd)yi-M2IDpM!)Y$5{ \.Z̦/êI@*Cq+S-8Xe%dJ{mKUJ%U YLKG9,I]dT$dq>|:w|kZޝ~jo7_H"Ӯr.-l&NˎvDM`Гegge_N\Ζ*qj dOfYQvz gVSqW9q|;*RT RЏZ{;$J93!LjLBC+yi2*3}wOeeeY)K*h&iF6n'^+K_*+!+kM' ed#}.ޭ3hR\zjA7 b)BD!.2)TebF{peqdCDq\oTH=Х'ǍiQyT{=yqaq9n;蘥8 $gI&8j5J4))% ^f CBdM̎A0 ],iq'TL5h9O ~l~G򬴖-MQ.<&sHά˸|~HʢHebcQAə Y*%!Fs-4*ςZ&Tel%3I7QS+-'# ;NsP]m(iI$Х!ZSf,XedtT;FQ0VRtw"3ܻ"9.MacU"C3TRe֤#K&S3|`2߄s67YR&t;^Y^G' %;RMQelIUzjYnu,U> |)iQ]3G%xleT(%0D)Ͽ)#dQJVt:X8xIlgگP3Otcd]F Wkx[SO]kף{leYlW='jz<$M.{ } ŭ !-h5LDIx;A$xFuSe+q*?vKF~[9܉'>N4?:3|1DahjA)+dU=;.,ӔRrdg":M3G5BRD=Eٙؑw/hju:*Y$jSqLD7qCB|B ekscFD5@}(w8vvoi{o}_cC4wwg{L~v{r_}8Ys̼_Rrs%xsWIݮI9)N#$$*$TF1^զ#Vz".\ߧ51p~_UWlߡww1^զ#Vz".\ߧ51p~_UWlߡww1^զ#Vz".\ߧb+zq]~QWU]{~]kzV8}Zk/*꫶OoЋܻs~9SYMwE^Uvw{[}nck=Zb8i(ת=B.krpzqy.7fs<[(ȿ1Uߵ8ͥFfNɚ̌xymon-4.3.7/common/0000775000175000017500000000000011671641715013524 5ustar henrikhenrikxymon-4.3.7/common/msgcache.80000664000175000017500000000533411671641417015373 0ustar henrikhenrik.TH MSGCACHE 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME msgcache \- Cache client messages for later pickup by xymonfetch .SH SYNOPSIS .B "msgcache [options]" .SH DESCRIPTION \fBmsgcache\fR implements a Xymon message cache. It is intended for use with clients which cannot deliver their data to the Xymon server in the normal way. Instead of having the client tools connect to the Xymon server, msgcache runs locally and the client tools then deliver their data to the msgcache daemon. The msgcache daemon is then polled regularly by the .I xymonfetch(8) utility, which collects the client messages stored by msgcache and forwards them to the Xymon server. \fBNOTE:\fR When using msgcache, the \fBXYMSRV\fR setting for the clients should be \fBXYMSRV=127.0.0.1\fR instead of pointing at the real Xymon server. .SH RESTRICTIONS Clients delivering their data to msgcache instead of the real Xymon server will in general not notice this. Specifically, the client configuration data provided by the Xymon server when a client delivers its data is forwarded through the xymonfetch / msgcache chain, so the normal centralized client configuration works. However, other commands which rely on clients communicating directly with the Xymon server will not work. This includes the \fBconfig\fR and \fBquery\fR commands which clients may use to fetch configuration files and query the Xymon server for a current status. The \fBdownload\fR command also does not work with msgcache. This means that the automatic client update facility will not work for clients communicating via msgcache. .SH OPTIONS .IP "--listen=IPADDRESS[:PORT]" Defines the IP-address and portnumber where msgcache listens for incoming connections. By default, msgcache listens for connections on all network interfaces, port 1984. .IP "--server=IPADDRESS[,IPADDRESS]" Restricts which servers are allowed to pick up the cached messages. By default anyone can contact the msgcache utility and request all of the cached messages. This option allows only the listed servers to request the cached messages. .IP "--max-age=N" Defines how long cached messages are kept. If the message has not been picked up with N seconds after being delivered to msgcache, it is silently discarded. Default: N=600 seconds (10 minutes). .IP "--daemon" Run as a daemon, i.e. msgcache will detach from the terminal and run as a background task .IP "--no-daemon" Run as a foreground task. This option must be used when msgcache is started by .I xymonlaunch(8) which is the normal way of running msgcache. .IP "--pidfile=FILENAME" Store the process ID of the msgcache task in FILENAME. .IP "--logfile=FILENAME" Log msgcache output to FILENAME. .IP "--debug" Enable debugging output. .SH "SEE ALSO" xymonfetch(8), xymon(7) xymon-4.3.7/common/xymonclient.cfg.50000664000175000017500000000373211671641417016725 0ustar henrikhenrik.TH XYMONCLIENT.CFG 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymonclient.cfg \- Xymon client environment variables .SH DESCRIPTION Xymon programs use multiple environment variables beside the normal set of variables. For the Xymon client, the environment definitions are stored in the ~xymon/client/etc/xymonclient.cfg file. Each line in this file is of the form \fBNAME=VALUE\fR and defines one environment variable NAME with the value VALUE. .SH SETTINGS .IP XYMSRV The IP-address used to contact the Xymon server. Default: Chosen when the Xymon client was compiled. .IP XYMSERVERS List of IP-adresses of Xymon servers. Data will be sent to all of the servers listed here. This setting is only used if XYMSRV=0.0.0.0. .IP XYMONDPORT The port number for used to contact the Xymon server. Default: 1984. .IP XYMONHOME The Xymon client top-level directory. Default: The $XYMONCLIENTHOME setting inherited from the "runclient.sh" script which starts the Xymon client. .IP XYMONCLIENTLOGS The directory for the Xymon clients' own log files. Default: $XYMONHOME/logs .IP XYMONTMP Directory used for temporary files. Default: $XYMONHOME/tmp/ .IP XYMON Full path to the .I xymon(1) client program. Default: $XYMONHOME/bin/xymon. .IP Commands Many extension scripts expect a series of environment variables to point at various system utilities. These are included in the file when the client is built. .SH INHERITED SETTINGS Some environment variables are inherited from the "runclient.sh" script which launches the Xymon client: .IP MACHINEDOTS The hostname of the local system. Default: Taken from "uname -n". .IP MACHINE The hostname of the local system, with dots replaced by commas. For compatibility with Big Brother extension scripts. .IP SERVEROSTYPE The operating system of the local system, in lowercase. Default: taken from "uname -s". .IP XYMONCLIENTHOME The top-level directory for the Xymon client. Default: The location of the "runclient.sh" script. .SH "SEE ALSO" xymon(7) xymon-4.3.7/common/xymon.c0000664000175000017500000001301311615341300015021 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon communications tool. */ /* */ /* This is used to send a single message using the Xymon/BB protocol to the */ /* Xymon server. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymon.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include "libxymon.h" int main(int argc, char *argv[]) { int timeout = XYMON_TIMEOUT; int result = 1; int argi; int showhelp = 0; char *recipient = NULL; strbuffer_t *msg = newstrbuffer(0); FILE *respfd = stdout; char *envarea = NULL; sendreturn_t *sres; int wantresponse = 0, mergeinput = 0; for (argi=1; (argi < argc); argi++) { if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (strncmp(argv[argi], "--proxy=", 8) == 0) { char *p = strchr(argv[argi], '='); setproxy(p+1); } else if (strcmp(argv[argi], "--help") == 0) { showhelp = 1; } else if (strcmp(argv[argi], "--version") == 0) { fprintf(stdout, "Xymon version %s\n", VERSION); return 0; } else if (strcmp(argv[argi], "--str") == 0) { respfd = NULL; } else if (strncmp(argv[argi], "--out=", 6) == 0) { char *fn = argv[argi]+6; respfd = fopen(fn, "wb"); } else if (strncmp(argv[argi], "--timeout=", 10) == 0) { char *p = strchr(argv[argi], '='); timeout = atoi(p+1); } else if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--merge") == 0) { mergeinput = 1; } else if (strcmp(argv[argi], "--response") == 0) { wantresponse = 1; } else if (strcmp(argv[argi], "-?") == 0) { showhelp = 1; } else if ((*(argv[argi]) == '-') && (strlen(argv[argi]) > 1)) { fprintf(stderr, "Unknown option %s\n", argv[argi]); } else { /* No more options - pickup recipient and msg */ if (recipient == NULL) { recipient = argv[argi]; } else if (STRBUFLEN(msg) == 0) { msg = dupstrbuffer(argv[argi]); } else { showhelp=1; } } } if ((recipient == NULL) || (STRBUFLEN(msg) == 0) || showhelp) { fprintf(stderr, "Xymon version %s\n", VERSION); fprintf(stderr, "Usage: %s [--debug] [--merge] [--proxy=http://ip.of.the.proxy:port/] RECIPIENT DATA\n", argv[0]); fprintf(stderr, " RECIPIENT: IP-address, hostname or URL\n"); fprintf(stderr, " DATA: Message to send, or \"-\" to read from stdin\n"); return 1; } if (strcmp(STRBUF(msg), "-") == 0) { strbuffer_t *inpline = newstrbuffer(0); sres = newsendreturnbuf(0, NULL); initfgets(stdin); while (unlimfgets(inpline, stdin)) { result = sendmessage(STRBUF(inpline), recipient, timeout, sres); clearstrbuffer(inpline); } return result; } if (mergeinput || (strcmp(STRBUF(msg), "@") == 0)) { strbuffer_t *inpline = newstrbuffer(0); if (mergeinput) /* Must add a new-line before the rest of the message */ addtobuffer(msg, "\n"); else /* Clear input buffer, we'll read it all from stdin */ clearstrbuffer(msg); initfgets(stdin); while (unlimfgets(inpline, stdin)) addtostrbuffer(msg, inpline); freestrbuffer(inpline); } if (strncmp(STRBUF(msg), "query ", 6) == 0) wantresponse = 1; else if (strncmp(STRBUF(msg), "client ", 7) == 0) wantresponse = 1; else if (strncmp(STRBUF(msg), "config ", 7) == 0) wantresponse = 1; else if (strncmp(STRBUF(msg), "download ", 9) == 0) wantresponse = 1; else if ((strncmp(STRBUF(msg), "xymondlog ", 10) == 0) || (strncmp(STRBUF(msg), "hobbitdlog ", 11) == 0)) wantresponse = 1; else if ((strncmp(STRBUF(msg), "xymondxlog ", 11) == 0) || (strncmp(STRBUF(msg), "hobbitdxlog ", 12) == 0)) wantresponse = 1; else if ((strncmp(STRBUF(msg), "xymondboard", 11) == 0) || (strncmp(STRBUF(msg), "hobbitdboard", 12) == 0)) wantresponse = 1; else if ((strncmp(STRBUF(msg), "xymondxboard", 12) == 0) || (strncmp(STRBUF(msg), "hobbitdxboard", 13) == 0)) wantresponse = 1; else if (strncmp(STRBUF(msg), "schedule", 8) == 0) wantresponse = 1; else if (strncmp(STRBUF(msg), "clientlog ", 10) == 0) wantresponse = 1; else if (strncmp(STRBUF(msg), "hostinfo", 8) == 0) wantresponse = 1; else if (strncmp(STRBUF(msg), "ping", 4) == 0) wantresponse = 1; else if (strncmp(STRBUF(msg), "pullclient", 10) == 0) wantresponse = 1; else if (strncmp(STRBUF(msg), "ghostlist", 9) == 0) wantresponse = 1; else if (strncmp(STRBUF(msg), "multisrclist", 12) == 0) wantresponse = 1; sres = newsendreturnbuf(wantresponse, respfd); result = sendmessage(STRBUF(msg), recipient, timeout, sres); if (sres->respstr) printf("Buffered response is '%s'\n", STRBUF(sres->respstr)); return result; } xymon-4.3.7/common/xymoncmd.10000664000175000017500000000204711671641417015446 0ustar henrikhenrik.TH XYMONCMD 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymoncmd \- Run a Xymon command with environment set .SH SYNOPSIS .B "xymoncmd [--env=ENVFILE COMMAND|--version|--debug]" .SH DESCRIPTION .I xymoncmd(1) is a utility that can setup the Xymon environment variables as defined in a .I xymonlaunch(8) compatible environment definition file, and then execute a command with this environment in place. It is mostly used for testing extension scripts or in other situations where you need to run a single command with the environment in place. The "--env=ENVFILE" option points xymoncmd to the file where the environment definitions are loaded from. COMMAND is the command to execute after setting up the environment. If you want to run multiple commands, it is often easiest to just use "sh" as the COMMAND - this gives you a sub-shell with the environment defined globally. The "--debug" option print out more detail steps of xymoncmd execution. The "--version" option print out version number of xymoncmd. .SH "SEE ALSO" xymonlaunch(8), xymon(7) xymon-4.3.7/common/xymon.10000664000175000017500000004373011671641417014766 0ustar henrikhenrik.TH XYMON 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymon \- Xymon client communication program .SH SYNOPSIS .B "xymon [options] RECIPIENT message" .SH DESCRIPTION .I xymon(1) is the client program used to communicate with a Xymon server. It is frequently used by Xymon client systems to send in status messages and pager alerts on local tests. In Xymon, the xymon program is also used for administrative purposes, e.g. to rename or delete hosts, or to disable hosts that are down for longer periods of time. .SH OPTIONS AND PARAMETERS .IP "--debug" Enable debugging. This prints out details about how the connection to the Xymon server is being established. .IP "--proxy=http://PROXYSERVER:PROXYPORT/" When sending the status messages via HTTP, use this server as an HTTP proxy instead of connecting directly to the Xymon server. .IP "--timeout=N" Specifies the timeout for connecting to the Xymon server, in seconds. The default is 5 seconds. .IP "--response" The xymon utility normally knows when to expect a response from the server, so this option is not required. However, it will cause any response from the server to be displayed. .IP "--merge" Merge the command line message text with the data provided on standard input, and send the result to the Xymon server. The message text provided on the command line becomes the first line of the merged message. .IP "RECIPIENT" The \fBRECIPIENT\fR parameter defines which server receives the message. If RECIPIENT is given as "0.0.0.0", then the message is sent to all of the servers listed in the XYMSERVERS environment variable. Usually, a client will use "$XYMSRV" for the \fBRECIPIENT\fR parameter, as this is defined for the client scripts to automatically contain the correct value. The \fBRECIPIENT\fR parameter may be a URL for a webserver that has the xymoncgimsg.cgi or similar script installed. This tunnels the Xymon messages to the Xymon server using standard HTTP protocol. The .I xymoncgimsg.cgi(8) CGI tool (included in Xymon) must be installed on the webserver for the HTTP transport to work. .br .IP MESSAGE The \fBmessage\fR parameter is the message to be sent across to the Xymon server. Messages must be enclosed in quotes, but by doing so they can span multiple lines. The maximum size of a message is defined by the maximum allowed length of your shell\(aqs command-line, and is typically 8-32 KB. If you need to send longer status messages, you can specify "@" as the message: xymon will then read the status message from its stdin. .SH XYMON MESSAGE SYNTAX This section lists the most commonly used messages in the Xymon protocol. Each message must begin with one of the Xymon commands. Where a HOSTNAME is specified, it must have any dots in the hostname changed to commas if the Xymon FQDN setting is enabled (which is the default). So the host "www.foo.com", for example, would report as "www,foo,com". .IP "status[+LIFETIME][/group:GROUP] HOSTNAME.TESTNAME COLOR " This sends in a status message for a single test (column) on a single host. TESTNAME is the name of the column where this test will show up; any name is valid except that using dots in the testname will not work. COLOR must be one of the valid colors: "green", "yellow", "red" or "clear". The colors "blue" and "purple" - although valid colors - should not be sent in a status message, as these are handled specially by the Xymon server. As a special case (for supporting older clients), "client" can be used as the name of the color. This causes the status message to be handled by Xymon as a "client" data message, and the TESTNAME parameter is used as the "collector id". .br The "additional text" normally includes a local timestamp and a summary of the test result on the first line. Any lines following the first one are free-form, and can include any information that may be useful to diagnose the problem being reported. .br The LIFETIME defines how long this status is valid after being received by the Xymon server. The default is 30 minutes, but you can set any period you like. E.g. for a custom test that runs once an hour, you will want to set this to at least 60 minutes - otherwise the status will go purple after 30 minutes. It is a good idea to set the LIFETIME to slightly longer than the interval between your tests, to allow for variations in the time it takes your test to complete. The LIFETIME is in minutes, unless you add an "h" (hours), "d" (days) or "w" (weeks) immediately after the number, e.g. "status+5h" for a status that is valid for 5 hours. .br The GROUP option is used to direct alerts from the status to a specific group. It is currently used for status generated from the Xymon clients\(aq data, e.g. to direct alerts for a "procs" status to different people, depending on exactly which process is down. .IP "notify HOSTNAME.TESTNAME " This triggers an informational message to be sent to those who receive alerts for this HOSTNAME+TESTNAME combination, according to the rules defined in .I alerts.cfg(5) This is used by the .I enadis.cgi(1) tool to notify people about hosts being disabled or enabled, but can also serve as a general way of notifying server administrators. .IP "data HOSTNAME.DATANAME" The "data" message allows tools to send data about a host, without it appearing as a column on the Xymon webpages. This is used, for example, to report statistics about a host, e.g. vmstat data, which does not in itself represent something that has a red, yellow or green identity. It is used by RRD bottom-feeder modules, among others. In Xymon, data messages are by default processed only by the .I xymond_rrd(8) module. If you want to handle data-messages using an external application, you may want to enable the .I xymond_filestore(8) module for data-messages, to store data-messages in a format compatible with how the Big Brother daemon does. .IP "disable HOSTNAME.TESTNAME DURATION " Disables a specific test for DURATION minutes. This will cause the status of this test to be listed as "blue" on the Xymon server, and no alerts for this host/test will be generated. If DURATION is given as a number followed by s/m/h/d, it is interpreted as being in seconds/minutes/hours/days respectively. .BR To disable a test until it becomes OK, use "-1" as the DURATION. .BR To disable all tests for a host, use an asterisk "*" for TESTNAME. .IP "enable HOSTNAME.TESTNAME" Re-enables a test that had been disabled. .IP "query HOSTNAME.TESTNAME" Query the Xymon server for the latest status reported for this particular test. If the host/test status is known, the response is the first line of the status report - the current color will be the first word on the line. Additional lines of text that might be present on the status message cannot be retrieved. .br This allows any Xymon client to determine the status of a particular test, whether it is one pertaining to the host where the client is running, some other host, or perhaps the result of a combined test from multiple hosts managed by .I combostatus(1) This will typically be useful to Xymon client extension scripts, that need to determine the status of other hosts, for example, to decide if an automatic recovery action should be initiated. .IP "config FILENAME" Retrieve one of the Xymon configuration files from the server. This command allows a client to pull files from the $XYMONHOME/etc/ directory on the server, allowing for semi-automatic updates of the client configuration. Since the configuration files are designed to have a common file for the configuration of all hosts in the system - and this is in fact the recommended way of configuring your clients - this makes it easier to keep the configuration files synchronized. .IP "drop HOSTNAME" Removes all data stored about the host HOSTNAME. It is assumed that you have already deleted the host from the hosts.cfg configuration file. .IP "drop HOSTNAME TESTNAME" Remove data about a single test (column). .IP "rename OLDHOSTNAME NEWHOSTNAME" Rename all data for a host that has had its name changed. You should do this after changing the hostname in the hosts.cfg configuration file. .IP "rename HOSTNAME OLDTESTNAME NEWTESTNAME" Rename data about a single test (column). .IP "xymondlog HOSTNAME.TESTNAME" Retrieve the Xymon status-log for a single test. The first line of the response contains a series of fields separated by a pipe-sign: .sp .BR hostname The name of the host .sp .BR testname The name of the test .sp .BR color Status color (green, yellow, red, blue, clear, purple) .sp .BR testflags For network tests, the flags indicating details about the test (used by xymongen). .sp .BR lastchange Unix timestamp when the status color last changed. .sp .BR logtime Unix timestamp when the log message was received. .sp .BR validtime Unix timestamp when the log message is no longer valid (it goes purple at this time). .sp .BR acktime Either -1 or Unix timestamp when an active acknowledgement expires. .sp .BR disabletime Either -1 or Unix timestamp when the status is no longer disabled. .sp .BR sender IP address where the status was received from. .sp .BR cookie Either -1 or the cookie value used to acknowledge an alert. .sp .BR ackmsg Empty or the acknowledgment message sent when the status was acknowledged. Newline, pipe-signs and backslashes are escaped with a backslash, C-style. .sp .BR dismsg Empty or the message sent when the status was disabled. Newline, pipe-signs and backslashes are escaped with a backslash, C-style. .sp After the first line comes the full status log in plain text format. .IP "xymondxlog HOSTNAME.TESTNAME" Retrieves an XML string containing the status log as with the "xymondlog" command. .IP "xymondboard [CRITERIA] [fields=FIELDLIST]" Retrieves a summary of the status of all known tests available to the Xymon daemon. By default - if no CRITERIA is provided - it returns one line for all status messages that are found in Xymon. You can filter the response by selecting a page, a host, a test or a color - the PAGEPATH, HOSTNAME and TESTNAME parameters are interpreted as regular expressions, the COLOR parameter accepts multiple colors separated by comma. .sp .BR page=PAGEPATH Include only tests from hosts found on the PAGEPATH page in the hosts.cfg file. .sp .BR host=HOSTNAME Include only tests from the host HOSTNAME .sp .BR test=TESTNAME Include only tests with the testname TESTNAME .sp .BR color=COLORNAME Include only tests where the status color is COLORNAME .sp You can filter on, for example, both a hostname and a testname. The response is one line for each status that matches the CRITERIA, or all statuses if no criteria is specified. The line is composed of a number of fields, separated by a pipe-sign. You can select which fields to retrieve by listing them in the FIELDLIST. The following fields are available: .sp .BR hostname The name of the host .sp .BR testname The name of the test .sp .BR color Status color (green, yellow, red, blue, clear, purple) .sp .BR flags For network tests, the flags indicating details about the test (used by xymongen). .sp .BR lastchange Unix timestamp when the status color last changed. .sp .BR logtime Unix timestamp when the log message was received. .sp .BR validtime Unix timestamp when the log message is no longer valid (it goes purple at this time). .sp .BR acktime Either -1 or Unix timestamp when an active acknowledgement expires. .sp .BR disabletime Either -1 or Unix timestamp when the status is no longer disabled. .sp .BR sender IP address where the status was received from. .sp .BR cookie Either -1 or the cookie value used to acknowledge an alert. .sp .BR line1 First line of status log. .sp .BR ackmsg Empty (if no acknowledgement is active), or the text of the acknowledge message. .sp .BR dismsg Empty (if the status is currently enabled), or the text of the disable message. .sp .BR msg The full text of the current status message. .sp .BR client Shows "Y" if there is client data available, "N" if not. .sp .BR clntstamp Timestamp when the last client message was received, in Unix "epoch" format. .sp .BR acklist List of the current acknowledgements for a test. This is a text string with multiple fields, delimited by a colon character. There are 5 fields: Timestamp for when the ack was generated and when it expires; the the "ack level"; the user who sent the ack; and the acknowledgement text. .sp .BR flapinfo Tells if the status is flapping. 5 fields, delimited by "/": A "0" if the status is not flapping and "1" if it is flapping; timestamp when the latest status change was recorded and when the first statuschange was recorded; and the two colors that the status is flapping between. .sp .BR stats Number of status-changes that have been recorded for this status since xymond was started. .sp .BR modifiers Lists all active modifiers for this status (i.e. updates sent using a "modify" command). .sp .BR XMH_* The XMH-tags refer to the Xymon .I hosts.cfg(5) configuration settings. A full list of these can be found in the .I xymon-xmh(5) man-page. The ackmsg, dismsg and msg fields have certain characters encoded: Newline is "\\n", TAB is "\\t", carriage return is "\\r", a pipe-sign is "\\p", and a backslash is "\\\\". If the "fields" parameter is omitted, a default set of hostname,testname,color,flags,lastchange,logtime,validtime,acktime,disabletime,sender,cookie,line1 is used. .IP "xymondxboard" Retrieves an XML string with the summary of all status logs as for the "xymondboard" command. .IP "download FILENAME" Download a file from the Xymon server\(aqs download directory. .IP "client[/COLLECTORID] HOSTNAME.OSTYPE [HOSTCLASS]" Used to send a "client" message to the Xymon server. Client messages are generated by the Xymon client; when sent to the Xymon server they are matched against the rules in the .I analysis.cfg(5) configuration file, and status messages are generated for the client-side tests. The COLLECTORID is used when sending client-data that are additions to the standard client data. The data will be concatenated with the normal client data. .IP "clientlog HOSTNAME [section=SECTIONNAME[,SECTIONNAME...]]" Retrieves the current raw client message last sent by HOSTNAME. The optional "section" filter is used to select specific sections of the client data. .IP "ping" Attempts to contact the Xymon server. If successful, the Xymon server version ID is reported. .IP "pullclient" This message is used when fetching client data via the "pull" mechanism implemented by .I xymonfetch(8) and .I msgcache(8) for clients that cannot connect directly to the Xymon server. .IP "ghostlist" Report a list of \fBghost\fR clients seen by the Xymon server. Ghosts are systems that report data to the Xymon server, but are not listed in the hosts.cfg file. .IP "schedule [TIMESTAMP COMMAND]" Schedules a command sent to the Xymon server for execution at a later time. E.g. used to schedule disabling of a host or service at sometime in the future. COMMAND is a complete Xymon command such as the ones listed above. TIMESTAMP is the Unix epoch time when the command will be executed. .br If no parameters are given, the currently scheduled tasks are listed in the response. The response is one line per scheduled command, with the job-id, the time when the command will be executed, the IP address from which this was sent, and the full command string. .br To cancel a previously scheduled command, \fB"schedule cancel JOBID"\fR can be used. JOBID is a number provided as the first item in the output from the schedule list. .IP "notes FILENAME" The message text will be stored in $XYMONHOME/notes/FILENAME which is then used as hyperlinks from hostnames or column names. This requires that the "storenotes" task is enabled in tasks.cfg (it is disabled by default). FILENAME cannot contain any directory path - these are stripped automatically. .IP "usermsg ID" These messages will be relayed directly to modules listening on the "user" channel of the Xymon daemon. This is intended for custom communication between client-side modules and the Xymon server. .IP "modify HOSTNAME.TESTNAME COLOR SOURCE CAUSE" Modify the color of a specific status, without generating a complete status message. This is for backend processors (e.g. RRD graphs) that can override the color of a status based on some criteria determined outside the normal flow of a status. E.g. the normal "conn" status may appear to be green since it merely checks on whether a host can be ping'ed or not; the RRD handler can then use a "modify" command to override this is the actual ping responsetime exceeds a given threshold. (See the "DS" configuration setting in .I analysis.cfg(5) for how to do this). SOURCE is some identification of the module that generates the "modify" message - future modifications must use the same source. There may be several sources that modify the same status (the most severe status then becomes the actual color of the status). CAUSE is a one-line text string explaining the reason for overriding the normal status color - it will be displayed on the status webpage. .SH EXAMPLE Send a normal status message to the Xymon server, using the standard Xymon protocol on TCP port 1984: .br $ $XYMON $XYMSRV "status www,foo,com.http green \(gadate\(ga Web OK" Send the same status message, but using HTTP protocol via the webserver\(aqs xymoncgimsg.cgi script: .br $ $XYMON http://bb.foo.com/cgi-bin/xymoncgimsg.cgi "status www,foo,com.http green \(gadate\(ga Web OK" Use "query" message to determine the color of the "www" test, and restart Apache if it is red: .br $ WWW=\(ga$XYMON $XYMSRV "query www,foo,com.www" | awk \(aq{print $1}\(aq\(ga $ if [ "$WWW" = "red" ]; then /etc/init.d/apache restart; fi Use "config" message to update a local mytest.cfg file (but only if we get a response): .br $ $XYMON $XYMSRV "config mytest.cfg" >/tmp/mytest.cfg.new $ if [ -s /tmp/mytest.cfg.new ]; then mv /tmp/mytest.cfg.new $XYMONHOME/etc/mytest.cfg fi Send a very large status message that has been built in the file "statusmsg.txt". Instead of providing it on the command-line, pass it via stdin to the xymon command: $ cat statusmsg.txt | $XYMON $XYMSRV "@" .SH "SEE ALSO" combostatus(1), hosts.cfg(5), xymonserver.cfg(5), xymon(7) xymon-4.3.7/common/xymongrep.10000664000175000017500000000763311671641417015646 0ustar henrikhenrik.TH XYMONGREP 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymongrep \- pick out lines in hosts.cfg .SH SYNOPSIS .B "xymongrep --help" .br .B "xymongrep --version" .br .B "xymongrep [--noextras] [--test-untagged] [--web] [--net] TAG [TAG...]" .SH DESCRIPTION .I xymongrep(1) is for use by extension scripts that need to pick out the entries in a hosts.cfg file that are relevant to the script. The utility accepts test names as parameters, and will then parse the hosts.cfg file and print out the host entries that have at least one of the wanted tests specified. Tags may be given with a trailing asterisk '*', e.g. "xymongrep http*" is needed to find all http and https tags. The xymongrep utility supports the use of "include" directives inside the hosts.cfg file, and will find matching tags in all included files. If the DOWNTIME or SLA tags are used in the .I hosts.cfg(5) file, these are interpreted relative to the current time. xymongrep then outputs a "INSIDESLA" or "OUTSIDESLA" tag for easier use by scripts that want to check if the current time is inside or outside the expected uptime window. .SH OPTIONS .IP "--noextras" Remove the "testip", "dialup", "INSIDESLA" and "OUTSIDESLA" tags from the output. .IP "--test-untagged" When using the XYMONNETWORK environment variable to test only hosts on a particular network segment, xymonnet will ignore hosts that do not have any "NET:x" tag. So only hosts that have a NET:$XYMONNETWORK tag will be tested. .br With this option, hosts with no NET: tag are included in the test, so that all hosts that either have a matching NET: tag, or no NET: tag at all are tested. .IP "--no-down[=TESTNAME]" xymongrep will query the Xymon server for the current status of the "conn" test, and if TESTNAME is specified also for the current state of the specified test. If the status of the "conn" test for a host is non-green, or the status of the TESTNAME test is disabled, then this host is ignored and will not be included in the output. This can be used to ignore hosts that are down, or hosts where the custom test is disabled. .IP "--web" Search the hosts.cfg file following include statements as a Xymon web-server would. .IP "--net" Search the hosts.cfg file following include statements as when running xymonnet. .SH EXAMPLE If your hosts.cfg file looks like this 192.168.1.1 www.test.com # ftp telnet !oracle 192.168.1.2 db1.test.com # oracle 192.168.1.3 mail.test.com # smtp and you have a custom Xymon extension script that performs the "oracle" test, then running "xymongrep oracle" would yield 192.168.1.1 www.test.com # !oracle 192.168.1.2 db1.test.com # oracle so the script can quickly find the hosts that are of interest. Note that the reverse-test modifier - "!oracle" - is included in the output; this also applies to the other test modifiers defined by Xymon (the dial-up and always-true modifiers). If your extension scripts use more than one tag, just list all of the interesting tags on the command line. xymongrep also supports the "NET:location" tag used by xymonnet, so if your script performs network checks then it will see only the hosts that are relevant for the test location that the script currently executes on. .SH USE IN EXTENSION SCRIPTS To integrate xymongrep into an existing script, look for the line in the script that grep's in the $HOSTSCFG file. Typically it will look somewhat like this: $GREP -i "^[0-9].*#.*TESTNAME" $HOSTSCFG | ... code to handle test Instead of the grep, we will use xymongrep. It then becomes $XYMONHOME/bin/xymongrep TESTNAME | ... code to handle test which is simpler, less error-prone and more efficient. .SH ENVIRONMENT VARIABLES .IP XYMONNETWORK If set, xymongrep outputs only lines from hosts.cfg that have a matching NET:$XYMONNETWORK setting. .sp .IP HOSTSCFG Filename for the Xymon .I hosts.cfg(5) file. .SH FILES .IP $HOSTSCFG The Xymon hosts.cfg file .SH "SEE ALSO" hosts.cfg(5), xymonserver.cfg(5) xymon-4.3.7/common/clientupdate.10000664000175000017500000001107011671641417016265 0ustar henrikhenrik.TH CLIENTUPDATE 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME clientupdate \- Xymon client update utility .SH SYNOPSIS .B "clientupdate [options]" .SH DESCRIPTION \fBclientupdate\fR is part of the Xymon client. It is responsible for updating an existing client installation from a central repository of client packages stored on the Xymon server. When the Xymon client sends a normal client report to the Xymon server, the server responds with the section of the .I client-local.cfg(5) file that is relevant to this client. Included in this may be a "clientversion" value. The clientversion received from the server is compared against the current clientversion installed on the client, as determined by the contents of the $XYMONHOME/etc/clientversion.cfg file. If the two versions are not identical, clientupdate is launched to update the client installation. .SH OPTIONS .IP "--level" Report the current clientversion. .IP "--update=NEWVERSION" Attempt to update the client to NEWVERSION by fetching this version of the client software from the Xymon server. .IP "--reexec" Used internally during the update process, see \fBOPERATION\fR below. .IP "--remove-self" Used internally during the update process. This option causes the running clientupdate utility to delete itself - it is used during the update to purge a temporary copy of the clientupdate utility that is installed in $XYMONTMP. .SH USING CLIENTUPDATE IN XYMON To manage updating clients without having to logon to each server, you can use the clientupdate utility. This is how you setup the release of a new client version. .IP "Create the new client" Setup the new client $XYMONHOME directory, e.g. by copying an existing client installation to an empty directory and modifying it for your needs. It is a good idea to delete all files in the tmp/ and logs/ directories, since there is no need to copy these over to all of the clients. Pay attention to the etc/ files, and make sure that they are suitable for the systems where you want to deploy this new client. You can add files - e.g. extension scripts in the ext/ directory - but the clientupdate utility cannot delete or rename files. .IP "Package the client" When your new client software is ready, create a tar-file of the new client. All files in the tar archive must have file names relative to the clients' $XYMONHOME (usually, ~xymon/client/). Save the tar file on the Xymon server in ~xymon/server/download/somefile.tar. Don't compress it. It is recommended that you use some sort of operating-system and version-numbering scheme for the filename, but you can choose whatever filename suits you - the only requirement is that it must end with ".tar". The part of the filename preceding ".tar" is what Xymon will use as the "clientversion" ID. .IP "Configure which hosts receive the new client" In the .I client-local.cfg(5) file, you must now setup a \fBclientversion:ID\fR line where the \fBID\fR matches the filename you used for the tar-file. So if you have packaged the new client into the file \fBlinux.v2.tar\fR, then the corresponding entry in client-local.cfg would be \fBclientversion:linux.v2\fR. .IP "Wait for xymond to reload client-local.cfg" xymond will automatically reload the client-local.cfg file after at most 10 minutes. If you want to force an immediate reload, send a SIGHUP signal to the xymond process. .IP "Wait for the client to update" The next time the client contacts the Xymon server to send the client data, it will notice the new clientversion setting in client-local.cfg, and will run \fBclientupdate\fR to install the new client software. So when the client runs the next time, it will use the new client software. .SH OPERATION \fBclientupdate\fR runs in two steps: .IP "Re-exec step" The first step is when clientupdate is first invoked from the xymonclient.sh script with the "--re-exec" option. This step copies the clientupdate program from $XYMONHOME/bin/ to a temporary file in the $XYMONTMP directory. This is to avoid conflicts when the update procedure installs a new version of the clientupdate utility itself. Upon completion of this step, the clientupdate utility automatically launches the next step by running the program from the file in $XYMONTMP. .IP "Update step" The second step downloads the new client software from the Xymon server. The new software must be packed into a tar file, which clientupdate then unpacks into the $XYMONHOME directory. .SH "ENVIRONMENT VARIABLES" clientupdate uses several of the standard Xymon environment variables, including \fBXYMONHOME\fR and \fBXYMONTMP\fR. .SH "SEE ALSO" xymon(7), xymon(1), client-local.cfg(5) xymon-4.3.7/common/xymoncfg.c0000664000175000017500000000403011615341300015500 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon config file viewer */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymoncfg.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include "version.h" #include "libxymon.h" int main(int argc, char *argv[]) { FILE *cfgfile; char *fn = NULL; strbuffer_t *inbuf; int argi; char *include2 = NULL; for (argi=1; (argi < argc); argi++) { if (strcmp(argv[argi], "--version") == 0) { printf("xymoncfg version %s\n", VERSION); exit(0); } else if (strcmp(argv[argi], "--help") == 0) { printf("Usage:\n%s [filename]\n", argv[0]); exit(0); } else if ((strcmp(argv[argi], "--net") == 0) || (strcmp(argv[argi], "--bbnet") == 0)) { include2 = "netinclude"; } else if ((strcmp(argv[argi], "--web") == 0) || (strcmp(argv[argi], "--bbdisp") == 0)) { include2 = "dispinclude"; } else if (*argv[argi] != '-') { fn = strdup(argv[argi]); } } if (!fn || (strlen(fn) == 0)) { fn = getenv("HOSTSCFG"); if (!fn) { errprintf("Environment variable HOSTSCFG is not set - aborting\n"); exit(2); } } cfgfile = stackfopen(fn, "r", NULL); if (cfgfile == NULL) { printf("Cannot open file '%s'\n", fn); exit(1); } inbuf = newstrbuffer(0); while (stackfgets(inbuf, include2)) { printf("%s", STRBUF(inbuf)); } stackfclose(cfgfile); freestrbuffer(inbuf); return 0; } xymon-4.3.7/common/xymon-xmh.50000664000175000017500000001100611671641417015553 0ustar henrikhenrik.TH XYMON-XMH 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME Xymon XMH variables \- Configuration items available online .SH DESCRIPTION The .I hosts.cfg(5) file is the most important configuration file for all of the Xymon programs. This file contains the full list of all the systems monitored by Xymon, including the set of tests and other configuration items stored for each host. Although the file is in text format and can be searched using tools like .I xymongrep(1) it can be difficult to pick out specific fields from the configuration due to quoting and other text parsing issues. So Xymon allows for querying this information directly from the .I xymond daemon via the "xymondboard" command. So the information can be provided together with the current status of a host and/or test. This is done by adding a "fields" definition to the "xymondboard" command. .SH XMH items Except where specified below, all items return the text of a particular setting from the hosts.cfg file, or an empty string if the setting has not been set for the host that is queried. .IP XMH_ALLPAGEPATHS List of all pages where the host is found, using the filename hierarchy page path. .IP XMH_BROWSER Value of the "browser" tag. .IP XMH_CLASS The host "class" (if reported by a Xymon client), or the value of the CLASS tag. .IP XMH_CLIENTALIAS Value of the CLIENT tag. .IP XMH_COMMENT Value of the COMMENT tag. .IP XMH_COMPACT Value of the COMPACT tag. .IP XMH_DEPENDS Value of the DEPENDS tag. .IP XMH_DESCRIPTION Value of the DESCR tag. .IP XMH_DGNAME The text string from the hosts.cfg "group" definition (group, group-only, group-except) in which the host is defined. .IP XMH_DISPLAYNAME Value of the NAME tag. .IP XMH_DOCURL Value of the DOC tag. .IP XMH_DOWNTIME Value of the DOWNTIME tag. .IP XMH_FLAG_DIALUP Value of the "dialup" tag. .IP XMH_FLAG_HIDEHTTP Value of the HIDEHTTP tag. .IP XMH_FLAG_LDAPFAILYELLOW Value of the "ldapyellowfail" tag. .IP XMH_FLAG_MULTIHOMED Value of the MULTIHOMED tag. .IP XMH_FLAG_NOBB2 Value of the "nobb2" tag (deprecated, use NONONGREEN instead). .IP XMH_FLAG_NOCLEAR Value of the NOCLEAR tag. .IP XMH_FLAG_NOCONN Value of the "noconn" tag. .IP XMH_FLAG_NODISP Value of the "nodisp" tag. .IP XMH_FLAG_NOINFO Value of the "noinfo" atg. .IP XMH_FLAG_NONONGREEN Value of the "nonongreen" tag. .IP XMH_FLAG_NOPING Value of the "noping" tag. .IP XMH_FLAG_NOSSLCERT Value of the "nosslcert" tag. .IP XMH_FLAG_NOTRACE Value of the "notrace" tag. .IP XMH_FLAG_NOTRENDS Value of the "notrends" tag. .IP XMH_FLAG_PREFER Value of the "prefer" tag. .IP XMH_FLAG_PULLDATA Value of the PULLDATA tag. .IP XMH_FLAG_TESTIP Value of the "testip" tag. .IP XMH_FLAG_TRACE Value of the "trace" tag. .IP XMH_GROUPID Number of the group where the host is listed - first group is 0. If a host is present on multiple pages, this is the number of the group for the first page where the host is found. .IP XMH_HOLIDAYS Value of the "holidays" tag. .IP XMH_HOSTNAME The name of the host. .IP XMH_IP The IP-address of the host (as specified in hosts.cfg). .IP XMH_LDAPLOGIN Value of the "ldaplogin" tag. .IP XMH_NET Value of the NET tag. .IP XMH_NK Value of the NK tag (deprecated). .IP XMH_NKTIME Value of the NKTIME tag (deprecated). .IP XMH_NOCOLUMNS Value of the NOCOLUMNS tag. .IP XMH_NOPROP Value of the NOPROP tag. .IP XMH_NOPROPACK Value of the NOPROPACK tag. .IP XMH_NOPROPPURPLE Value of the NOPROPPURPLE tag. .IP XMH_NOPROPRED Value of the NOPROPRED tag. .IP XMH_NOPROPYELLOW Value of the NOPROPYELLOW tag. .IP XMH_NOTAFTER Value of the NOTAFTER tag. .IP XMH_NOTBEFORE Value of the NOTBEFORE tag. .IP XMH_OS The host operating system (if reported by a Xymon client), or the value of the OS tag. .IP XMH_PAGEINDEX Index of the host on the page where it is shown, first host has index 0. .IP XMH_PAGENAME Name of the page where the host is shown (see also XMH_PAGEPATH). .IP XMH_PAGEPATH File path to the page where the host is shown. .IP XMH_PAGEPATHTITLE Title of the full path to the page where the host is shown. .IP XMH_PAGETITLE Title of the page where the host is shown. .IP XMH_RAW All configuration settings for the host. Settings are separated by a pipe-sign. .IP XMH_REPORTTIME Value of the REPORTTIME tag. .IP XMH_SSLDAYS Value of the "ssldays" tag. .IP XMH_SSLMINBITS Value of the "sslbits" tag. .IP XMH_TRENDS Value of the TRENDS tag. .IP XMH_WARNPCT Value of the WARNPCT tag. .IP XMH_WARNSTOPS Value of the WARNSTOPS tag. .IP XMH_WML Value of the WML tag. .SH "SEE ALSO" xymon(1), hosts.cfg(5), xymongrep(1) xymon-4.3.7/common/xymonlaunch.80000664000175000017500000000533511671641417016167 0ustar henrikhenrik.TH XYMONLAUNCH 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymonlaunch \- Master program to launch other Xymon programs .SH SYNOPSIS .B "xymonlaunch [options]" .SH DESCRIPTION .I xymonlaunch(8) is the main program that controls the execution and scheduling of all of the components in the Xymon system. xymonlaunch allows the administrator to add, remove or change the set of Xymon applications and extensions without restarting Xymon - xymonlaunch will automatically notice any changes in the set of tasks, and change the scheduling of activities accordingly. xymonlaunch also allows the administrator to setup specific logfiles for each component of the Xymon system, instead of getting output from all components logged to a single file. .SH OPTIONS .IP "--env=FILENAME" Loads the environment from FILENAME before starting other tools. The environment defined by FILENAME is the default, it can be overridden by the ENVFILE option in .I tasks.cfg(5) .IP "--config=FILENAME" This option defines the file that xymonlaunch scans for tasks it must launch. A description of this file is in .I tasks.cfg(5) The default tasklist is /etc/tasks.cfg .IP "--log=FILENAME" Defines the logfile where xymonlaunch logs information about failures to launch tasks and other data about the operation of xymonlaunch. Logs from individual tasks are defined in the tasks.cfg file. By default this is logged to stdout. .IP "--pidfile=FILENAME" Filename which xymonlaunch saves its own process-ID to. Commonly used by automated start/stop scripts. .IP "--verbose" Logs the launch of all tasks to the logfile. Note that the logfile may become quite large if you enable this. .IP "--dump" Just dump the contents of the tasks.cfg file after parsing it. Used for debugging. .IP "--debug" Enable debugging output while running. .IP "--no-daemon" xymonlaunch normally detaches from the controlling tty and runs as a background task. This option keeps it running in the foreground. .SH STARTING TASKS xymonlaunch will read the configuration file and start all of the tasks listed there. If a task completes abnormally (i.e. terminated by a signal or with a non-zero exit status), then xymonlaunch will attempt to restart it 5 times. If it still will not run, then the task is disabled for 10 minutes. This will be logged to the xymonlaunch logfile. If the configuration file changes, xymonlaunch will re-read it and notice any changes. If a running task was removed from the configuration, then the task is stopped. If a new task was added, it will be started. If the command used for a task changed, or it was given a new environment definition file, or the logfile was changed, then the task is stopped and restarted with the new definition. .SH "SEE ALSO" tasks.cfg(5), xymon(7) xymon-4.3.7/common/hosts.cfg.50000664000175000017500000015654111671641417015523 0ustar henrikhenrik.TH HOSTS.CFG 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME hosts.cfg \- Main Xymon configuration file .SH SYNOPSIS .IP hosts.cfg .SH DESCRIPTION The .I hosts.cfg(5) file is the most important configuration file for all of the Xymon programs. This file contains the full list of all the systems monitored by Xymon, including the set of tests and other configuration items stored for each host. .SH FILE FORMAT Each line of the file defines a host. Blank lines and lines starting with a hash mark (#) are treated as comments and ignored. Long lines can be broken up by putting a backslash at the end of the line and continuing the entry on the next line. .sp The format of an entry in the hosts.cfg file is as follows: .br IP-address hostname # tag1 tag2 ... .sp The IP-address and hostname are mandatory; all of the tags are optional. Listing a host with only IP-address and hostname will cause a network test to be executed for the host - the connectivity test is enabled by default, but no other tests. The optional tags are then used to define which tests are relevant for the host, and also to set e.g. the time-interval used for availability reporting by .I xymongen(1) An example of setting up the hosts.cfg file is in the Xymon on-line documentation (from the Help menu, choose "Configuring Monitoring"). The following describes the possible settings in a hosts.cfg file supported by Xymon. .SH TAGS RECOGNIZED BY ALL TOOLS .IP "include filename" This tag is used to include another file into the hosts.cfg file at run-time, allowing for a large hosts.cfg file to be split up into more manageable pieces. The "filename" argument should point to a file that uses the same syntax as hosts.cfg. The filename can be an absolute filename (if it begins with a '/'), or a relative filename - relative file names are prefixed with the directory where the main hosts.cfg file is located (usually $XYMONHOME/etc/). You can nest include tags, i.e. a file that is included from the main hosts.cfg file can itself include other files. .IP "dispinclude filename" Acts like the "include" tag, but only for the xymongen tool. Can be used e.g. to put a group of hosts on multiple sub-pages, without having to repeat the host definitions. .IP "netinclude filename" Acts like the "include" tag, but only for the xymonnet tool. .IP "directory directoryname" This tag is used to include all files in the named directory. Files are included in alphabetical order. If there are sub- directories, these are recursively included also. The following files are ignored: Files that begin with a dot, files that end with a tilde, RCS files that end with ",v", RPM package manager files ending in ".rpmsave" or ".rpmnew", DPKG package manager files ending in ".dpkg-new" or ".dpkg-orig", and all special files (devices, sockets, pipes etc). .SH GENERAL PER-HOST OPTIONS .IP noclear Controls whether stale status messages go purple or clear when a host is down. Normally, when a host is down the client statuses ("cpu", "disk", "memory" etc) will stop updating - this would usually make them go "purple" which can trigger alerts. To avoid that, Xymon checks if the "conn" test has failed, and if that is true then the other tests will go "clear" instead of purple so you only get alerts for the "conn" test. If you do want the stale statuses to go purple, you can use the "noclear" tag to override this behaviour. Note that "noclear" also affects the behaviour of network tests; see below. .IP prefer When a single host is defined multiple time in the hosts.cfg file, xymongen tries to guess which definition is the best to use for the information used on the "info" column, or for the NOPROPRED and other xymongen-specific settings. Host definitions that have a "noconn" tag or an IP of 0.0.0.0 get lower priority. By using the "prefer" tag you tell xymongen that this host definition should be used. Note: This only applies to hosts that are defined multiple times in the hosts.cfg file, although it will not hurt to add it on other hosts as well. .IP multihomed Tell Xymon that data from the host can arrive from multiple IP-adresses. By default, Xymon will warn if it sees data for one host coming from different IP-adresses, because this usually indicates a mis-configuration of the hostname on at least one of the servers involved. Some hosts with multiple IP-adresses may use different IP's for sending data to Xymon, however. This tag disables the check of source IP when receiving data. .IP delayred=STATUSCOLUMN:DELAY[,STATUSCOLUMN:DELAY...] Usually, status changes happen immediately. This tag is used to defer an update to red for the STATUSCOLUMN status for DELAY minutes. E.g. with \fBdelayred=disk:10,cpu:30\fR, a red disk-status will not appear on the Xymon webpages until it has been red for at least 10 minutes. Note: Since most tests only execute once every 5 minutes, it will usually not make sense to set N to anything but a multiple of 5. The exception is network tests, since .I xymonnet-again.sh(1) will re-run failed network tests once a minute for up to 30 minutes. .IP delayyellow=STATUSCOLUMN:DELAY[,STATUSCOLUMN:DELAY...] Same as \fBdelayred\fR, but defers the change to a yellow status. .SH XYMONGEN DISPLAY OPTIONS These tags are processed by the .I xymongen(1) tool when generating the Xymon webpages or reports. .IP "page NAME [Page-title]" This defines a page at the level below the entry page. All hosts following the "page" directive appear on this page, until a new "page", "subpage" or "subparent" line is found. .IP "subpage NAME [Page-title]" This defines a sub-page in the second level below the entry page. You must have a previous "page" line to hook this sub-page to. .IP "subparent parentpage newpage [Page-title]" This is used to define sub-pages in whatever levels you may wish. Just like the standard "subpage" tag, "subparent" defines a new Xymon web page; however with "subparent" you explicitly list which page it should go as a sub-page to. You can pick any page as the parent - pages, sub-pages or even other subparent pages. So this allows you to define any tree structure of pages that you like. E.g. with this in hosts.cfg: page USA United States subpage NY New York subparent NY manhattan Manhattan data centers subparent manhattan wallstreet Wall Street center you get this hierarchy of pages: USA (United States) NY (New York) manhattan (Manhattan data centers) wallstreet (Wall Street center) Note: The parent page must be defined before you define the subparent. If not, the page will not be generated, and you get a message in the log file. Note: xymongen is case-sensitive, when trying to match the name of the parent page. The inspiration for this came from Craig Cook's mkbb.pl script, and I am grateful to Craig for suggesting that I implement it in xymongen. The idea to explicitly list the parent page in the "subparent" tag was what made it easy to implement. .IP "vpage" "vsubpage" "vsubparent" These are page-definitions similar to the "page", "subpage" and "subparent" definitions. However, on these pages the rows are the tests, and the columns are the hosts (normal pages have it the other way around). This is useful if you have a very large number of tests for a few hosts, and prefer to have them listed on a page that can be scrolled vertically. .br Note that the "group" directives have no effect on these types of pages. .IP "group [group-title]" .IP "group-compress [group-title]" Defines a group of hosts, that appear together on the web page, with a single header-line listing all of the columns. Hosts following the "group" line appear inside the group, until a new "group" or page-line is found. The two group-directives are handled identically by Xymon and xymongen, but both forms are allowed for backwards compatibility. .IP "group-sorted [group-title]" Same as the "group" line, but will sort the hosts inside the group so they appear in strict lexicographic order. .IP "group-only COLUMN1|COLUMN2|COLUMN3 [group-title]" Same as the "group" and "group-compress" lines, but includes only the columns explicitly listed in the group. Any columns not listed will be ignored for these hosts. .IP "group-except COLUMN1|COLUMN2|COLUMN3 [group-title]" Same as the "group-only" lines, but includes all columns EXCEPT those explicitly listed in the group. Any columns listed will be ignored for these hosts - all other columns are shown. .IP "title Page, group or host title text" The "title" tag is used to put custom headings into the pages generated by xymongen, in front of page/subpage links, groups or hosts. The title tag operates on the next item in the hosts.cfg file following the title tag. If a title tag precedes a host entry, the title is shown just before the host is listed on the status page. The column headings present for the host will be repeated just after the heading. If a title tag precedes a group entry, the title is show just before the group on the status page. If a title tag precedes a page/subpage/subparent entry, the title text replaces the normal "Pages hosted locally" heading normally inserted by Xymon. This appears on the page that links to the sub-pages, not on the sub-page itself. To get a custom heading on the sub-page, you may want to use the "--pagetext-heading" when running .I xymongen(1) .IP NAME:hostname Overrides the default hostname used on the overview web pages. If "hostname" contains spaces, it must be enclosed in double quotes, e.g. NAME:"R&D Oracle Server" .IP CLIENT:hostname Defines an alias for a host, which will be used when identifying status messages. This is typically used to accommodate a local client that sends in status reports with a different hostname, e.g. if you use hostnames with domains in your Xymon configuration, but the client is a silly Window box that does not include the hostname. Or vice-versa. Whatever the reason, this can be used to match status reports with the hosts you define in your hosts.cfg file. It causes incoming status reports with the specified hostname to be filed using the hostname defined in hosts.cfg. .IP NOCOLUMNS:column[,column] Used to drop certain of the status columns generated by the Xymon client. \fBcolumn\fR is one of \fBcpu\fR, \fBdisk\fR, \fBfiles\fR, \fBmemory\fR, \fBmsgs\fR, \fBports\fR, \fBprocs\fR. This setting stops these columns from being updated for the host. Note: If the columns already exist, you must use the .I xymon(1) utility to \fBdrop\fR them, or they will go purple. .IP "COMMENT:Host comment" Adds a small text after the hostname on the web page. This can be used to describe the host, without completely changing its display-name as the NAME: tag does. If the comment includes whitespace, it must be in double-quotes, e.g. COMMENT:"Sun web server" .IP "DESCR:Hosttype:Description" Define some informational text about the host. The "Hosttype" is a text describing the type of this device - "router", "switch", "hub", "server" etc. The "Description" is an informational text that will be shown on the "Info" column page; this can e.g. be used to store information about the physical location of the device, contact persons etc. If the text contain whitespace, you must enclose it in double-quotes, e.g. DESCR:"switch:4th floor Marketing switch" .IP "CLASS:Classname" Force the host to belong to a specific class. Class-names are used when configuring log-file monitoring (they can be used as references in .I client-local.cfg(5) and .I analysis.cfg(5) to group log file checks). Normally, class-names are controlled on the client by starting the Xymon client with the "--class=Classname" option. If you specify it in the hosts.cfg file on the Xymon server, it overrides any class name that the client reports. .IP dialup The keyword "dialup" for a host means that it is OK for it to be off-line - this should not trigger an alert. All network tests will go "clear" upon failure, and any missing reports from e.g. cpu- and disk-status will not go purple when they are not updated. .IP nonongreen Ignore this host on the "All non-green" page. Even if it has an active alert, it will not be included in the "All non-green" page. This also removes the host from the event-log display. .IP nodisp Ignore this host completely when generating the Xymon webpages. Can be useful for monitoring a host without having it show up on the webpages, e.g. because it is not yet in production use. Or for hiding a host that is shown only on a second pageset. .IP TRENDS:[*,][![graph,...]] Defines the RRD graphs to include in the "trends" column generated by xymongen. This option syntax is complex. .br If this option is not present, xymongen provides graphs matching the standard set of RRD files: la, disk, memory, users, vmstat, iostat, netstat, tcp, bind, apache, sendmail .br * If this option is specified, the list of graphs to include start out as being empty (no graphs). .br * To include all default graphs, use an asterisk. E.g. "TRENDS:*" .br * To exclude a certain graph, specify it prefixed with '!'. E.g. to see all graphs except users: "TRENDS:*,!users" .br * The netstat, vmstat and tcp graphs have many "subgraphs". Which of these are shown can be specified like this: "TRENDS:*,netstat:netstat2|netstat3,tcp:http|smtp|conn" This will show all graphs, but instead of the normal netstat graph, there will be two: The netstat2 and netstat3 graphs. Instead of the combined tcp graphs showing all services, there will be three: One for each of the http, conn and smtp services. .br .IP "COMPACT:COLUMN=COLUMN1,COLUMN2,COLUMN3[;ditto]" Collapses a series of statuses into a single column on the overview web page. .br .IP "INTERFACES:REGEXP" On systems with multiple network interfaces, the operating system may report a number of network interface where the statistics are of no interest. By default Xymon tracks and graphs the traffic on all network interfaces. This option defines a regular expression, and only those interfaces whose name matches the expression are tracked. .SH XYMON TAGS FOR THE CRITICAL SYSTEMS OVERVIEW PAGE \fBNOTE:\fR The "NK" set of tags is deprecated. They will be supported for Xymon 4.x, but will be dropped in version 5. It is recommended that you move your critical systems view to the .I criticalview.cgi(1) viewer, which has a separate configuration tool, .I criticaleditor.cgi(1) with more facilities than the NK tags in hosts.cfg. xymongen will create three sets of pages: The main page xymon.html, the all-non-green-statuses page (nongreen.html), and a specially reduced version of nongreen.html with only selected tests (critical.html). This page includes selected tests that currently have a red or yellow status. .IP NK:testname[,testname] NOTE: This has been deprecated, you should use .I criticalview.cgi(1) instead of the NK tag. Define the tests that you want included on the critical page. E.g. if you have a host where you only want to see the http tests on critical.html, you specify it as 12.34.56.78 www.acme.com # http://www.acme.com/ NK:http If you want multiple tests for a host to show up on the critical.html page, specify all the tests separated by commas. The test names correspond to the column names (e.g. https tests are covered by an "NK:http" tag). .IP NKTIME=day:starttime:endtime[,day:starttime:endtime] This tag limits the time when an active alert is presented on the NK web page. By default, tests with a red or yellow status that are listed in the "NK:testname" tag will appear on the NK page. However, you may not want the test to be shown outside of normal working hours - if, for example, the host is not being serviced during week-ends. You can then use the NKTIME tag to define the time periods where the alert will show up on the NK page. The time specification consists of .sp .BR day-of-week: \fBW\fR means Mon-Fri ("weekdays"), \fB*\fR means all days, \fB0\fR .. \fB6\fR = Sunday .. Saturday. Listing multiple days is possible, e.g. "60" is valid meaning "Saturday and Sunday". .sp .BR starttime: Time to start showing errors, must be in 24-hour clock format as HHMM hours/minutes. E.g. for 8 am enter "0800", for 9.30 pm enter "2130" .sp .BR endtime: Time to stop showing errors. If necessary, multiple periods can be specified. E.g. to monitor a site 24x7, except between noon and 1 pm, use NKTIME=*:0000:1159,*:1300:2359 The interval between start time and end time may cross midnight, e.g. \fB*:2330:0200\fR would be valid and have the same effect as \fB*:2330:2400,*:0000:0200\fR. .SH XYMON TAGS FOR THE WML (WAP) CARDS If xymongen is run with the "--wml" option, it will generate a set of WAP-format output "cards" that can be viewed with a WAP-capable device, e.g. a PDA or cell-phone. .IP WML:[+|-]testname[,[+|-]testname] This tag determines which tests for this hosts are included in the WML (WAP) page. Syntax is identical to the NK: tag. The default set of WML tests are taken from the --wml command line option. If no "WML:" tag is specified, the "NK:" tag is used if present. .SH XYMON STATUS PROPAGATION OPTIONS These tags affect how a status propagates upwards from a single test to the page and higher. This can also be done with the command-line options --nopropyellow and --nopropred, but the tags apply to individual hosts, whereas the command line options are global. .IP NOPROPRED:[+|-]testname[,[+|-]testname] This tag is used to inhibit a yellow or red status from propagating upwards - i.e. from a test status color to the (sub)page status color, and further on to xymon.html or nongreen.html If a host-specific tag begins with a '-' or a '+', the host-specific tags are removed/added to the default setting from the command-line option. If the host-specific tag does not begin with a '+' or a '-', the default setting is ignored for this host and the NOPROPRED applies to the tests given with this tag. E.g.: xymongen runs with "--nopropred=ftp,smtp". "NOPROPRED:+dns,-smtp" gives a NOPROPRED setting of "ftp,dns" (dns is added to the default, smtp is removed). "NOPROPRED:dns" gives a setting of "dns" only (the default is ignored). Note: If you set use the "--nopropred=*" command line option to disable propagation of all alerts, you cannot use the "+" and "-" methods to add or remove from the wildcard setting. In that case, do not use the "+" or "-" setting, but simply list the required tests that you want to keep from propagating. .IP NOPROPYELLOW:[+|-]testname[,[+|-]testname] Similar to NOPROPRED: tag, but applies to propagating a yellow status upwards. .IP NOPROPPURPLE:[+|-]testname[,[+|-]testname] Similar to NOPROPRED: tag, but applies to propagating a purple status upwards. .IP NOPROPACK:[+|-]testname[,[+|-]testname] Similar to NOPROPRED: tag, but applies to propagating an acknowledged status upwards. .SH XYMON AVAILABILITY REPORT OPTIONS These options affect the way the Xymon availability reports are processed (see .I report.cgi(1) for details about availability reports). .IP REPORTTIME=day:starttime:endtime[,day:starttime:endtime] This tag defines the time interval where you measure uptime of a service for reporting purposes. When xymongen generates a report, it computes the availability of each service - i.e. the percentage of time that the service is reported as available (meaning: not red). By default, this calculation is done on a 24x7 basis, so no matter when an outage occurs, it counts as downtime. The REPORTTIME tag allows you to specify a period of time other than 24x7 for the service availability calculation. If you have systems where you only guarantee availability from e.g. 7 AM to 8 PM on weekdays, you can use .br REPORTTIME=W:0700:2000 .br and the availability calculation will only be performed for the service with measurements from this time interval. The syntax for REPORTTIME is the same as the one used by the NKTIME parameter. When REPORTTIME is specified, the availability calculation happens like this: * Only measurements done during the given time period is used for the calculation. .br * "blue" time reduces the length of the report interval, so if you are generating a report for a 10-hour period and there are 20 minutes of "blue" time, then the availability calculation will consider the reporting period to be 580 minutes (10 hours minus 20 minutes). This allows you to have scheduled downtime during the REPORTTIME interval without hurting your availability; this is (I believe) the whole idea of the downtime being "planned". .br * "red" and "clear" status counts as downtime; "yellow" and "green" count as uptime. "purple" time is ignored. The availability calculation correctly handles status changes that cross into/out of a REPORTTIME interval. If no REPORTTIME is given, the standard 24x7 calculation is used. .IP WARNPCT:percentage Xymon's reporting facility uses a computed availability threshold to color services green (100% available), yellow (above threshold, but less than 100%), or red (below threshold) in the reports. This option allows you to set the threshold value on a host-by-host basis, instead of using a global setting for all hosts. The threshold is defined as the percentage of the time that the host must be available, e.g. "WARNPCT:98.5" if you want the threshold to be at 98.5% .SH NETWORK TEST SETTINGS .IP testip By default, Xymon will perform a name lookup of the hostname to get the IP address it will use for network tests. This tag causes Xymon to use the IP listed in the hosts.cfg file. .IP NET:location This tag defines the host as being tested from a specific location. If xymonnet sees that the environment variable XYMONNETWORK is set, it will only test the hosts that have a matching "NET:location" tag in the hosts.cfg file. So this tag is useful if you have more than one system running network tests, but you still want to keep a consolidated hosts.cfg file for all your systems. Note: The "--test-untagged" option modifies this behaviour, see .I xymonnet(1) .IP noclear Some network tests depend on others. E.g. if the host does not respond to ping, then there's a good chance that the entire host is down and all network tests will fail. Or if the http server is down, then any web content checks are also likely to fail. To avoid floods of alerts, the default behaviour is for xymonnet to change the status of these tests that fail because of another problem to "clear" instead of "red". The "noclear" tag disables this behaviour and causes all failing tests to be reported with their true color. This behaviour can also be implemented on a per-test basis by putting the "~" flag on any network test. Note that "noclear" also affects whether stale status messages from e.g. a client on the host go purple or clear when the host is down; see the "noclear" description in the "GENERAL PER-HOST OPTIONS" section above. .IP nosslcert Disables the standard check of any SSL certificates for this host. By default, if an SSL-enabled service is tested, a second test result is generated with information about the SSL certificate - this tag disables the SSL certificate checks for the host. .IP "ssldays=WARNDAYS:ALARMDAYS" Define the number of days before an SSL certificate expires, in which the sslcert status shows a warning (yellow) or alarm (red) status. These default to the values from the "--sslwarndays" and "--sslalarmdays" options for the .I xymonnet(1) tool; the values specified in the "ssldays" tag overrides the default. .IP "sslbits=MINIMUMKEYBITS" Enable checking of the encryption strength of the SSL protocol offered by the server. If the server offers encryption using a key with fewer than MINIMUMKEYBITS bits, the "sslcert" test will go red. E.g. to check that your server only uses strong encryption (128 bits or better), use "sslbits=128". .IP DOWNTIME=day:starttime:endtime[,day:starttime:endtime] .IP DOWNTIME=columns:day:starttime:endtime:cause[,columns:day:starttime:endtime:cause] This tag can be used to ignore failed checks during specific times of the day - e.g. if you run services that are only monitored e.g. Mon-Fri 8am-5pm, or you always reboot a server every Monday between 5 and 6 pm. What happens is that if a test fails during the specified time, it is reported with status BLUE instead of yellow or red. Thus you can still see when the service was unavailable, but alarms will not be triggered and the downtime is not counted in the availability calculations generated by the Xymon reports. The "columns" and "cause" settings are optional, but both or neither must be specified. "columns" may be a comma-separated list of status columns to which DOWNTIME will apply. The "cause" string will be displayed on the status web page to explain why the system is down. The syntax for DOWNTIME is the same as the one used by the NKTIME parameter. .IP SLA=day:starttime:endtime[,day:starttime:endtime] This tag is now deprecated. Use the DOWNTIME tag instead. This tag works the opposite of the DOWNTIME tag - you use it to specify the periods of the day that the service should be green. Failures OUTSIDE the SLA interval are reported as blue. .IP depends=(testA:host1/test1,host2/test2),(testB:host3/test3),[...] This tag allows you to define dependencies between tests. If "testA" for the current host depends on "test1" for host "host1" and test "test2" for "host2", this can be defined with depends=(testA:host1/test1,host2/test2) When deciding the color to report for testA, if either host1/test1 failed or host2/test2 failed, if testA has failed also then the color of testA will be "clear" instead of red or yellow. Since all tests are actually run before the dependencies are evaluated, you can use any host/test in the dependency - regardless of the actual sequence that the hosts are listed, or the tests run. It is also valid to use tests from the same host that the dependency is for. E.g. 1.2.3.4 foo # http://foo/ webmin depends=(webmin:foo/http) is valid; if both the http and the webmin tests fail, then webmin will be reported as clear. Note: The "depends" tag is evaluated by xymonnet while running the network tests. It can therefore only refer to other network tests that are handled by the same server - there is currently no way to use the e.g. the status of locally run tests (disk, cpu, msgs) or network tests from other servers in a dependency definition. Such dependencies are silently ignored. .IP badTEST[-weekdays-starttime-endtime]:x:y:z NOTE: This has been deprecated, use the \fBdelayred\fR and \fBdelayyellow\fR settings instead. Normally when a network test fails, the status changes to red immediately. With a "badTEST:x:y:z" tag this behaviour changes: .br * While "z" or more successive tests fail, the column goes RED. .br * While "y" or more successive tests fail, but fewer than "z", the column goes YELLOW. .br * While "x" or more successive tests fail, but fewer than "y", the column goes CLEAR. .br * While fewer than "x" successive tests fail, the column stays GREEN. The optional time specification can be used to limit this "badTEST" setting to a particular time of day, e.g. to require a longer period of downtime before raising an alarm during out-of-office hours. The time-specification uses: .br * Weekdays: The weekdays this badTEST tag applies, from 0 (Sunday) through 6 (Saturday). Putting "W" here counts as "12345", i.e. all working days. Putting "*" here counts as all days of the week, equivalent to "0123456". .br * start time and end time are specified using 24-hour clocks, e.g. "badTEST-W-0900-2000" is valid for working days between 9 AM (09:00) and 8 PM (20:00). When using multiple badTEST tags, the LAST one specified with a matching time-spec is used. Note: The "TEST" is replaced by the name of the test, e.g. 12.34.56.78 www.foo.com # http://www.foo.com/ badhttp:1:2:4 defines a http test that goes "clear" after the first failure, "yellow" after two successive failures, and "red" after four successive failures. For LDAP tests using URL's, use the option "badldapurl". For the other network tests, use "badftp", "badssh" etc. .SH CONNECTIVITY (PING) TEST These tags affect the behaviour of the xymonnet connectivity test. .IP noping Disables the ping-test, but will keep the "conn" column on the web display with a notice that it has been disabled. .IP noconn Disables the ping-test, and does not put a "conn" column on the web display. .IP conn The "conn" test (which does a ping of the host) is enabled for all hosts by default, and normally you just want to disable it using "noconn" or "noping". However, on the rare occasion where you may want to check that a host is NOT up, you can specify it as an explicit test, and use the normal test modifiers, e.g. "!conn" will be green when the host is NOT up, and red if it does appear on the network. The actual name of the tag - "conn" by default - depends on the "--ping=TESTNAME" option for xymonnet, as that decides the testname for the connectivity test. .IP "conn={best,|worst,}IP1[,IP2...]" This adds additional IP-adresses that are pinged during the normal "conn" test. So the normal "conn" test must be enabled (the default) before this tag has any effect. The IP-adresses listed here are pinged in addition to the main IP-address. When multiple IP's are pinged, you can choose if ALL IP's must respond (the "worst" method), or AT LEAST one IP must respond (the "best" setting). All of the IP's are reported in a single "conn" status, whose color is determined from the result of pinging the IP's and the best/worst setting. The default method is "best" - so it will report green if just one of the IP's respond to ping. .IP badconn[-weekdays-starttime-endtime]:x:y:z This is taken directly from the "fping.sh" connectivity- testing script, and is used by xymonnet when it runs with ping testing enabled (the default). See the description of the "badTEST" tag. .IP route:router1,router2,.... This tag is taken from the "fping.sh" script, and is used by xymonnet when run with the "--ping" option to enable ping testing. The router1,router2,... is a comma-separated list of hosts elsewhere in the hosts.cfg file. You cannot have any spaces in the list - separate hosts with commas. This tag changes the color reported for a ping check that fails, when one or more of the hosts in the "route" list is also down. A "red" status becomes "yellow" - other colors are unchanged. The status message will include information about the hosts in the router-list that are down, to aid tracking down which router is the root cause of the problem. Note: Internally, the ping test will still be handled as "failed", and therefore any other tests run for this host will report a status of "clear". .IP route_LOCATION:router1,router2,... If the XYMONNETWORK environment variable is defined, a tag of "route_XYMONNETWORK:" is recognized by xymonnet with the same effect as the normal "route:" tag (see above). This allows you to have different route: tags for each server running xymonnet. The actual text for the tag then must match the value you have for the XYMONNETWORK setting. E.g. with XYMONNETWORK=dmz, the tag becomes "route_dmz:" .IP "trace" If the connectivity test fails, run a "traceroute" and include the output from this in the status message from the failed connectivity test. Note: For this to work, you may have to define the TRACEROUTE environment variable, see .I xymonserver.cfg(5) .IP "notrace" Similar to the "trace" option, this disables the running of a traceroute for the host after a failed connectivity test. It is only used if running traceroute is made the default via the --trace option. .SH SIMPLE NETWORK TESTS These tests perform a simple network test of a service by connecting to the port and possibly checking that a banner is shown by the server. How these tests operate are configured in the .I protocols.cfg(5) configuration file, which controls which port to use for the service, whether to send any data to the service, whether to check for a response from the service etc. You can modify the behaviour of these tests on a per-test basis by adding one or more modifiers to the test: \fB:NUMBER\fR changes the port number from the default to the one you specify for this test. E.g. to test ssh running on port 8022, specify the test as \fBssh:8022\fR. \fB:s\fR makes the test silent, i.e. it does not send any data to the service. E.g. to do a silent test of an smtp server, enter \fBsmtp:s\fR. You can combine these two: \fBftp:8021:s\fR is valid. If you must test a service from a multi-homed host (i.e. using a specific source IP-address instead of the one your operating system provides), you can use the modifier "@IPADDRESS" at the end of the test specification, \fBafter\fR any other modifiers or port number. "IPADDRESS" must be a valid dotted IP-address (not hostname) which is assigned to the host running the network tests. The name of the test also determines the column name that the test result will appear with in the Xymon webpages. By prefixing a test with "!" it becomes a reverse test: Xymon will expect the service NOT to be available, and send a green status if it does NOT respond. If a connection to the service succeeds, the status will go red. By prefixing a test with "?" errors will be reported with a "clear" status instead of red. This is known as a test for a "dialup" service, and allows you to run tests of hosts that are not always online, without getting alarms while they are off-line. .IP "ftp ssh telnet smtp pop3 imap nntp rsync clamd oratns qmtp qmqp" These tags are for testing services offering the FTP, Secure Shell (ssh), SMTP, POP3, IMAP, NNTP, rsync, CLAM anti-virus daemon (clamd), Oracle TNS listener (oratns), qmail QMTP and QMQP protocols. .IP "ftps telnets smtps pop3s imaps nntps" These tags are for testing of the SSL-tunneled versions of the standard ftp, telnet, smtp, pop3, imap and nntp protocols. If Xymon was configured with support for SSL, you can test these services like any other network service - xymonnet will setup an SSL-encrypted session while testing the service. The server certificate is validated and information about it sent in the "sslcert" column. Note that smtps does not have a standard port number assignment, so you will need to enter this into the protocols.cfg file or your /etc/services file. .IP bbd Test that a Big Brother compatible daemon is running. This check works both for the Xymon .I xymond(8) daemon, and the original Big Brother bbd daemon. .SH DNS SERVER TESTS These tags are used to setup monitoring of DNS servers. .IP dns Simple DNS test. It will attempt to lookup the A record for the hostname of the DNS server. .IP dig This is an alias for the "dns" test. In xymonnet, the "dns" and "dig" tests are handled identically, so all of the facilities for testing described for the "dns" test are also available for the "dig" test. .IP "dns=hostname" .IP "dns=TYPE:lookup[,TYPE:lookup...] The default DNS tests will attempt a DNS lookup of the DNS' servers own hostname. You can specify the hostname to lookup on a DNS server by listing it on each test. The second form of the test allows you to perform multiple queries of the DNS server, requesting different types of DNS records. The TYPE defines the type of DNS data: A (IP-address), MX (Mail eXchanger), PTR (reverse), CNAME (alias), SOA (Start-Of-Authority), NS (Name Server) are among the more common ones used. The "lookup" is the query. E.g. to lookup the MX records for the "foo.com" domain, you would use "dns=mx:foo.com". Or to lookup the nameservers for the "bar.org" domain, "dns=ns:bar.org". You can list multiple lookups, separated by commas. For the test to end up with a green status, all lookups must succeed. .SH OTHER NETWORK TESTS .IP ntp Check for a running NTP (Network Time Protocol) server on this host. This test uses the "ntpdate" utility to check for a NTP server - you should either have ntpdate in your PATH, or set the location of the ntpdate program in $XYMONHOME/etc/xymonserver.cfg .IP rpc[=rpcservice1,rpcservice2,...] Check for one or more available RPC services. This check is indirect in that it only queries the RPC Portmapper on the host, not the actual service. If only "rpc" is given, the test only verifies that the port mapper is available on the remote host. If you want to check that one or more RPC services are registered with the port mapper, list the names of the desired RPC services after the equals-sign. E.g. for a working NFS server the "mount", "nlockmgr" and "nfs" services must be available; this can be checked with "rpc=mount,nlockmgr,nfs". This test uses the rpcinfo tool for the actual test; if this tool is not available in the PATH of xymonnet, you must define the RPCINFO environment variable to point at this tool. See .I xymonserver.cfg(5) .SH HTTP TESTS Simple testing of a http URL is done simply by putting the URL into the hosts.cfg file. Note that this only applies to URL's that begin with "http:" or "https:". The following items describe more advanced forms of http URL's. .IP "Basic Authentication with username/password" If the URL requires authentication in the form of a username and password, it is most likely using the HTTP "Basic" authentication. xymonnet support this, and you can provide the username and password either by embedding them in the URL e.g. .br http://USERNAME:PASSWORD@www.sample.com/ .br or by putting the username and password into the ~/.netrc file (see .I ftp(1) for details). .IP "Authentication with SSL client certificates" An SSL client certificate can be used for authentication. To use this, the client certificate must be stored in a PEM-formatted file together with the client certificate key, in the $XYMONHOME/certs/ directory. The URL is then given as .br http://CERT:FILENAME@www.sample.com/ .br The "CERT:" part is literal - i.e. you write C-E-R-T-colon and then the filename of the PEM-formatted certificate. .br A PEM-formatted certificate file can be generated based on certificates stored in Microsoft Internet Explorer and OpenSSL. Do as follows: .br From the MSIE Tools-Options menu, pick the Content tab, click on Certificates, choose the Personal tab, select the certificate and click Export. Make sure you export the private key also. In the Export File Format, choose PKCS 12 (.PFX), check the "Include all certificates" checkbox and uncheck the "Enable strong protection". Provide a temporary password for the exported file, and select a filename for the PFX-file. .br Now run "openssl pkcs12 -in file.pfx -out file.pem". When prompted for the "Import Password", provide the temporary password you gave when exporting the certificate. Then provide a "PEM pass phrase" (twice) when prompted for one. .br The file.pem file is the one you should use in the FILENAME field in the URL - this file must be kept in $XYMONHOME/certs/. The PEM pass phrase must be put into a file named the same as the certificate, but with extension ".pass". E.g. if you have the PEM certificate in $XYMONHOME/certs/client.pem, you must put the pass phrase into the $XYMONHOME/certs/client.pass file. Make sure to protect this file with Unix permissions, so that only the user running Xymon can read it. .IP "Forcing an HTTP or SSL version" Some SSL sites will only allow you to connect, if you use specific "dialects" of HTTP or SSL. Normally this is auto-negotiated, but experience shows that this fails on some systems. xymonnet can be told to use specific dialects, by adding one or more "dialect names" to the URL scheme, i.e. the "http" or "https" in the URL: * "2", e.g. https2://www.sample.com/ : use only SSLv2 .br * "3", e.g. https3://www.sample.com/ : use only SSLv3 .br * "m", e.g. httpsm://www.sample.com/ : use only 128-bit ciphers .br * "h", e.g. httpsh://www.sample.com/ : use only >128-bit ciphers .br * "10", e.g. http10://www.sample.com/ : use HTTP 1.0 .br * "11", e.g. http11://www.sample.com/ : use HTTP 1.1 These can be combined where it makes sense, e.g to force SSLv2 and HTTP 1.0 you would use "https210". .IP "Testing sites by IP-address" xymonnet ignores the "testip" tag normally used to force a test to use the IP-address from the hosts.cfg file instead of the hostname, when it performs http and https tests. The reason for this is that it interacts badly with virtual hosts, especially if these are IP-based as is common with https-websites. Instead the IP-address to connect to can be overridden by specifying it as: http://www.sample.com=1.2.3.4/index.html The "=1.2.3.4" will case xymonnet to run the test against the IP-address "1.2.3.4", but still trying to access a virtual website with the name "www.sample.com". The "=ip.address.of.host" must be the last part of the hostname, so if you need to combine this with e.g. an explicit port number, it should be done as http://www.sample.com:3128=1.2.3.4/index.html .IP "HTTP Testing via proxy" \fBNOTE:\fR This is not enabled by default. You must add the "--bb-proxy-syntax" option when running .I xymonnet(1) if you want to use this. xymonnet supports the Big Brother syntax for specifying an HTTP proxy to use when performing http tests. This syntax just joins the proxy- and the target-URL into one, e.g. .br http://webproxy.sample.com:3128/http://www.foo.com/ .br would be the syntax for testing the www.foo.com website via the proxy running on "webproxy.sample.com" port 3128. If the proxy port number is not specified, the default HTTP port number (80) is used. If your proxy requires authentication, you can specify the username and password inside the proxy-part of the URL, e.g. .br http://fred:Wilma1@webproxy.sample.com:3128/http://www.foo.com/ .br will authenticate to the proxy using a username of "fred" and a password of "Wilma1", before requesting the proxy to fetch the www.foo.com homepage. Note that it is not possible to test https-sites via a proxy, nor is it possible to use https for connecting to the proxy itself. .IP cont[=COLUMN];URL;[expected_data_regexp|#digesttype:digest] This tag is used to specify a http/https check, where it is also checked that specific content is present in the server response. If the URL itself includes a semi-colon, this must be escaped as '%3B' to avoid confusion over which semicolon is part of the URL, and which semicolon acts as a delimiter. The data that must be returned can be specified either as a regular expression (except that is not allowed) or as a message digest (typically using an MD5 sum or SHA-1 hash). The regex is pre-processed for backslash "\\" escape sequences. So you can really put any character in this string by escaping it first: .br \\n Newline (LF, ASCII 10 decimal) .br \\r Carriage return (CR, ASCII 13 decimal) .br \\t TAB (ASCII 8 decimal) .br \\\\ Backslash (ASCII 92 decimal) .br \\XX The character with ASCII hex-value XX .br If you must have whitespace in the regex, use the [[:space:]] syntax, e.g. if you want to test for the string "All is OK", use "All[[:space:]]is[[:space:]]OK". Note that this may depend on your particular implementation of the regex functions found in your C library. Thanks to Charles Goyard for this tip. Note: If you are migrating from the "cont2.sh" script, you must change the '_' used as wildcards by cont2.sh into '.' which is the regular-expression wildcard character. Message digests can use whatever digest algorithms your libcrypto implementation (usually OpenSSL) supports. Common message digests are "md5" and "sha1". The digest is calculated on the data portion of the response from the server, i.e. HTTP headers are not included in the digest (as they change from one request to the next). The expected digest value can be computed with the .I xymondigest(1) utility. "cont" tags in hosts.cfg result in two status reports: One status with the "http" check, and another with the "content" check. As with normal URL's, the extended syntax described above can be used e.g. when testing SSL sites that require the use of SSLv2 or strong ciphers. The column name for the result of the content check is by default called "content" - you can change the default with the "--content=NAME" option to xymonnet. See .I xymonnet(1) for a description of this option. If more than one content check is present for a host, the first content check is reported in the column "content", the second is reported in the column "content1", the third in "content2" etc. You can also specify the column name directly in the test specification, by writing it as "cont=COLUMN;http://...". Column-names cannot include whitespace or semi-colon. The content-check status by default includes the full URL that was requested, and the HTML data returned by the server. You can hide the HTML data on a per-host (not per-test) basis by adding the \fBHIDEHTTP\fR tag to the host entry. .IP content=URL This syntax is deprecated. You should use the "cont" tag instead, see above. .IP post[=COLUMN];URL;form-data;[expected_data_regexp|#digesttype:digest] This tag can be used to test web pages, that use an input form. Data can be posted to the form by specifying them in the form-data field, and the result can be checked as if it was a normal content check (see above for a description of the cont-tag and the restrictions on how the URL must be writen). The form-data field must be entered in "application/x-www-form-urlencoded" format, which is the most commonly used format for web forms. E.g. if you have a web form defined like this:
.br

Given name

.br

Surname

.br .br
and you want to post the value "John" to the first field and "Doe Jr." to the second field, then the form data field would be givenname=John&surname=Doe+Jr. Note that any spaces in the input value is replaced with '+'. If your form-data requires a different content-type, you can specify it by beginning the form-data with \fB(content-type=TYPE)\fR, e.g. "(content-type=text/xml)" followed by the POST data. Note that as with normal forms, the POST data should be specified using escape-sequences for reserved characters: "space" should be entered as "\\x20", double quote as "\\x22", newline as "\\n", carriage-return as "\\r", TAB as "\\t", backslash as "\\\\". Any byte value can be entered using "\\xNN" with NN being the hexadecimal value, e.g. "\\x20" is the space character. The [expected_data_regexp|#digesttype:digest] is the expected data returned from the server in response to the POST. See the "cont;" tag above for details. If you are only interested in knowing if it is possible to submit the form (but don't care about the data), this can be an empty string - but the ';' at the end is required. .IP nocont[=COLUMN];URL;forbidden_data_regexp This tag works just like "cont" tag, but reverses the test. It is green when the "forbidden_data_regexp" is NOT found in the response, and red when it IS found. So it can be used to watch for data that should NOT be present in the response, e.g. a server error message. .IP nopost[=COLUMN];URL;form-data;expected_data_regexp This tag works just like "post" tag, but reverses the test. It is green when the "forbidden_data_regexp" is NOT found in the response, and red when it IS found. So it can be used to watch for data that should NOT be present in the response, e.g. a server error message. .IP type[=COLUMN];URL;expected_content_type This is a variant of the content check - instead of checking the content data, it checks the type of the data as given by the HTTP Content-Type: header. This can used to check if a URL returns e.g. a PDF file, regardless of what is inside the PDF file. .IP soap[=COLUMN];URL;SOAPMESSAGE;[expected_data_regexp|#digesttype:digest] Send SOAP message over HTTP. This is identical to the "cont" test, except that the request sent to the server uses a Content-type of "application/soap+xml", and it also sends a "SOAPAction" header with the URL. SOAPMESSAGE is the SOAP message sent to the server. Since SOAP messages are usually XML documents, you can store this in a separate file by specifying "file:FILENAME" as the SOAPMESSAGE parameter. E.g. a test specification of soap=echo;http://soap.foo.bar/baz?wsdl;file:/home/foo/msg.xml;. will read the SOAP message from the file /home/foo/msg.xml and post it to the URL http://soap.foo.bar/bas?wsdl Note that SOAP XML documents usually must begin with the XML version line, \fB\fR .IP nosoap[=COLUMN];URL;SOAPMESSAGE;[forbidden_data_regexp|#digesttype:digest] This tag works just like "soap" tag, but reverses the test. It is green when the "forbidden_data_regexp" is NOT found in the response, and red when it IS found. So it can be used to watch for data that should NOT be present in the response, e.g. a server error message. .IP httpstatus[=COLUMN];URL;okstatusexpr;notokstatusexpr This is used to explicitly test for certain HTTP statuscodes returned when the URL is requested. The \fBokstatusexpr\fR and \fBnokokstatusexpr\fR expressions are Perl-compatible regular expressions, e.g. "2..|302" will match all OK codes and the redirect (302) status code. If the URL cannot be retrieved, the status is "999". .IP HIDEHTTP The status display for HTTP checks usually includes the URL, and for content checks also the actual data from the web page. If you would like to hide these from view, then the HIDEHTTP tag will keep this information from showing up on the status webpages. .IP browser=BROWSERNAME By default, Xymon sends an HTTP "User-Agent" header identifying it a "Xymon". Some websites require that you use a specific browser, typically Internet Explorer. To cater for testing of such sites, this tag can be used to modify the data sent in the User-Agent header. .br E.g. to perform an HTTP test with Xymon masquerading as an Internet Explorer 6.0 browser, use \fBbrowser="Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"\fR. If you do not know what the User-Agent header should be, open up the browser that works with this particular site, and open the URL "javascript:document.writeln(navigator.userAgent)" (just copy this into the "Open URL" dialog. The text that shows up is what the browser sends as the User-Agent header. .SH LDAP (DIRECTORY SERVER) TESTS .IP ldap .IP ldaps Simple check for an LDAP service. This check merely looks for any service running on the ldap/ldaps service port, but does not perform any actual LDAP transaction. .IP ldap://hostport/dn[?attrs[?scope[?filter[?exts]]]] Check for an LDAP service by performing an LDAP request. This tag is in the form of an LDAP URI (cf. RFC 2255). This type of LDAP test requires that .I xymonnet(1) was built with support for LDAP, e.g. via the OpenLDAP library. The components of the LDAP URI are: .nf \fIhostport\fP is a host name with an optional ":portnumber" \fIdn\fP is the search base \fIattrs\fP is a comma separated list of attributes to request \fIscope\fP is one of these three strings: base one sub (default=base) \fIfilter\fP is filter \fIexts\fP are recognized set of LDAP and/or API extensions. .fi .IP ldaps://hostport/dn[?attrs[?scope[?filter[?exts]]]] LDAP service check using LDAPv3 and STARTTLS for talking to an LDAP server that requires TLS encryption. See .I xymonnet(1) for a discussion of the different ways of running LDAP servers with SSL/TLS, and which of these are supported by xymonnet. .IP ldaplogin=username:password Define a username and password to use when binding to the LDAP server for ldap URI tests. If not specified, xymonnet will attempt an anonymous bind. .IP ldapyellowfail Used with an LDAP URL test. If the LDAP query fails during the search of the directory, the ldap status is normally reported as "red" (alarm). This tag reduces a search failure to a "yellow" (warning) status. .SH PERFORMANCE MONITORING TESTS .IP apache[=URL] If you are running an Apache web server, adding this tag makes .I xymonnet(1) collect performance statistics from the Apache web server by querying the URL \fBhttp://IP.ADDRESS.OF.HOST/server-status?auto\fR. The response is sent as a data-report and processed by the Xymon xymond_rrd module into an RRD file and an "apache" graph. If your web server requires e.g. authentication, or runs on a different URL for the server-status, you can provide the full URL needed to fetch the server-status page, e.g. \fBapache=http://LOGIN:PASSWORD@10.0.0.1/server-status?auto\fR for a password protected server-status page, or \fBapache=http://10.0.0.1:8080/apache/server-status?auto\fR for a server listening on port 8080 and with a different path to the server-status page. Note that you need to enable the server-status URL in your Apache configuration. The following configuration is needed: .sp .br SetHandler server-status .br Order deny,allow .br Deny from all .br allow from 127.0.0.1 .br .br ExtendedStatus On .sp Change "127.0.0.1" to the IP-address of the server that runs your network tests. .SH DEFAULT HOST If you have certain tags that you want to apply to all hosts, you can define a host name ".default." and put the tags on that host. Note that per-host definitions will override the default ones. \fBNOTE:\fR The ".default." host entry will only accept the following tags - others are silently ignored: NOCOLUMNS, COMMENT, DESCR, CLASS, dialup, testip, nonongreen, nodisp, noinfo, notrends, TRENDS, NOPROPRED, NOPROPYELLOW, NOPROPPURPLE, NOPROPACK, REPORTTIME, WARNPCT, NET, noclear, nosslcert, ssldays, DOWNTIME, depends, noping, noconn, trace, notrace, HIDEHTTP, browser, pulldata. Specifically, note that network tests, "badTEST" settings, and alternate pageset relations cannot be listed on the ".default." host. .SH SENDING SUMMARIES TO REMOTE XYMON SERVERS .IP "summary ROW.COLUMN IP URL" If you have multiple Xymon servers, the "summary" directive lets you form a hierarchy of servers by sending the overall status of this server to a remote Xymon server, which then displays this in a special summary section. E.g. if your offices are spread over three locations, you can have a Xymon server at each office. These branch-office Xymon have a "summary" definition in their hosts.cfg file that makes them report the overall status of their branch Xymon to the central Xymon server you maintain at the corporate headquarters. Multiple "summary" definitions are allowed. The ROW.COLUMN setting defines how this summary is presented on the server that receives the summary. The ROW text will be used as the heading for a summary line, and the COLUMN defines the name of the column where this summary is shown - like the hostname and testname used in the normal displays. The IP is the IP-address of the \fBremote\fR (upstream) Xymon server, where this summary is sent). The URL is the URL of your \fBlocal\fR Xymon server. The URL need not be that of your Xymon server's main page - it could be the URL of a sub-page on the local Xymon server. Xymon will report the summary using the color of the page found at the URL you specify. E.g. on your corporate Xymon server you want a summary from the Las Vegas office - but you would like to know both what the overall status is, and what is the status of the servers on the critical Sales department back-office servers in Las Vegas. So you configure the Las Vegas Xymon server to send \fBtwo\fR summaries: .sp summary Vegas.All 10.0.1.1 http://vegas.foo.com/xymon/ .br summary Vegas.Sales 10.0.1.1 http://vegas.foo.com/xymon/sales/ .sp This gives you one summary line for Baltimore, with two columns: An "All" column showing the overall status, and a "Sales" column showing the status of the "sales" page on the Baltimore Xymon server. Note: Pages defined using alternate pageset definitions cannot be used, the URL must point to a web page from the default set of Xymon webpages. .SH OTHER TAGS .IP pulldata[=[IP][:port]] This option is recognized by the .I xymonfetch(8) utility, and causes it to poll the host for client data. The optional IP-address and port-number can be used if the client-side .I msgcache(8) daemon is listening on a non-standard IP-address or port-number. .SH FILES .BR ~xymon/server/etc/hosts.cfg .SH "SEE ALSO" xymongen(1), xymonnet(1), xymondigest(1), xymonserver.cfg(5), xymon(7) xymon-4.3.7/common/clientlaunch.cfg.50000664000175000017500000000103311671641417017015 0ustar henrikhenrik.TH CLIENTLAUNCH.CFG 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME clientlaunch.cfg \- Task definitions for the xymonlaunch utility .SH SYNOPSIS .B ~xymon/client/etc/clientlaunch.cfg .SH DESCRIPTION The clientlaunch.cfg file holds the list of tasks that xymonlaunch runs on a Xymon client. This is typically just the Xymon client itself, but you can add custom test scripts here and have them executed regularly by the Xymon scheduler. .SH "FILE FORMAT" See the .I tasks.cfg(5) description. .SH "SEE ALSO" xymonlaunch(8), xymon(7) xymon-4.3.7/common/xymonlaunch.c0000664000175000017500000005315411615613541016237 0ustar henrikhenrik/* -*- mode:C; tab-width:8; intent-tabs-mode:1; c-basic-offset:8 -*- */ /*----------------------------------------------------------------------------*/ /* Xymon application launcher. */ /* */ /* This is used to launch various parts of the Xymon system. Some programs */ /* start up once and keep running, other must run at various intervals. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* "CMD +/-" code and enable/disable enhancement by Martin Sperl 2010-2011 */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymonlaunch.c 6717 2011-08-01 21:15:13Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include /* * config file format: * * [xymond] * CMD xymond --no-daemon * LOGFILE /var/log/xymond.log * * [xymongen] * CMD xymongen * INTERVAL 5m */ #define MAX_FAILS 5 typedef struct grouplist_t { char *groupname; int currentuse, maxuse; struct grouplist_t *next; } grouplist_t; typedef struct tasklist_t { char *key; int disabled; grouplist_t *group; char *cmd; int interval, maxruntime; char *logfile; char *envfile, *envarea, *onhostptn; pid_t pid; time_t laststart; int exitcode; int failcount; int cfload; /* Used while reloading a configuration */ int beingkilled; char *cronstr; /* pointer to cron string */ void *crondate; /* pointer to cron date-time structure */ struct tasklist_t *depends; struct tasklist_t *next; struct tasklist_t *copy; } tasklist_t; tasklist_t *taskhead = NULL; tasklist_t *tasktail = NULL; grouplist_t *grouphead = NULL; volatile time_t nextcfgload = 0; volatile int running = 1; volatile int dologswitch = 0; volatile int forcereload=0; # define xfreenull(k) { if (k) { xfree(k); k=NULL;} } # define xfreeassign(k,p) { if (k) { xfree(k); } k=p; } # define xfreedup(k,p) { if (k) { xfree(k); } k=strdup(p); } void load_config(char *conffn) { static void *configfiles = NULL; tasklist_t *twalk, *curtask = NULL; FILE *fd; strbuffer_t *inbuf; char *p; char myhostname[256]; /* First check if there were no modifications at all */ if (configfiles) { if (!stackfmodified(configfiles) && (!forcereload)) { dbgprintf("No files modified, skipping reload of %s\n", conffn); return; } else { stackfclist(&configfiles); configfiles = NULL; } } errprintf("Loading tasklist configuration from %s\n", conffn); if (gethostname(myhostname, sizeof(myhostname)) != 0) { errprintf("Cannot get the local hostname, using 'localhost' (error: %s)\n", strerror(errno)); strcpy(myhostname, "localhost"); } /* The cfload flag: -1=delete task, 0=old task unchanged, 1=new/changed task */ for (twalk = taskhead; (twalk); twalk = twalk->next) { twalk->cfload = -1; twalk->group = NULL; /* Create a copy, but retain the settings and pointers are the same */ twalk->copy = xmalloc(sizeof(tasklist_t)); memcpy(twalk->copy,twalk,sizeof(tasklist_t)); /* These should get cleared */ twalk->copy->next = NULL; twalk->copy->copy = NULL; /* And clean the values of all others, so that we really can detect a difference */ twalk->disabled = 0; twalk->cmd = NULL; twalk->interval = 0; twalk->maxruntime = 0; twalk->group = NULL; twalk->logfile = NULL; twalk->envfile = NULL; twalk->envarea = NULL; twalk->onhostptn = NULL; twalk->cronstr = NULL; twalk->crondate = NULL; twalk->depends = NULL; } fd = stackfopen(conffn, "r", &configfiles); if (fd == NULL) { errprintf("Cannot open configuration file %s: %s\n", conffn, strerror(errno)); return; } inbuf = newstrbuffer(0); while (stackfgets(inbuf, NULL)) { sanitize_input(inbuf, 1, 0); if (STRBUFLEN(inbuf) == 0) continue; p = STRBUF(inbuf); if (*p == '[') { /* New task */ char *endp; /* get name */ p++; endp = strchr(p, ']'); if (endp == NULL) continue; *endp = '\0'; /* try to find the task */ for (twalk = taskhead; (twalk && (strcmp(twalk->key, p))); twalk = twalk->next); if (twalk) { curtask=twalk; } else { /* New task, just create it */ curtask = (tasklist_t *)calloc(1, sizeof(tasklist_t)); curtask->key = strdup(p); /* add it to the list */ if (taskhead == NULL) taskhead = curtask; else tasktail->next = curtask; tasktail = curtask; } /* mark task as configured */ curtask->cfload = 0; } else if (curtask && (strncasecmp(p, "CMD ", 4) == 0)) { p += 3; p += strspn(p, " \t"); /* Handle + - options as well */ if (*p == '+') { /* append to command */ if (curtask->cmd) { int l1 = strlen(curtask->cmd); int l2 = strlen(p); char *newcmd = xcalloc(1, l1+l2+1); strncpy(newcmd,curtask->cmd,l1); strncpy(newcmd+l1,p,l2); newcmd[l1]=' '; /* this also overwrites the + */ /* free and assign new */ xfreeassign(curtask->cmd,newcmd); } } else if (*p == '-') { /* remove from command */ if (curtask->cmd) { int l = strlen(p)-1; if (l > 0) { char *found; while((found = strstr(curtask->cmd,p+1)) != NULL) { /* doing a copy - can not use strcpy as we are overlapping */ char *s = found + l; while (*s) { *found=*s; found++; s++; } *found=0; } } else { errprintf("Configuration error, empty command removal (CMD -) for task %s\n", curtask->key); } } } else { xfreedup(curtask->cmd,p); } } else if (strncasecmp(p, "GROUP ", 6) == 0) { /* Note: GROUP can be used by itself to define a group, or inside a task definition */ char *groupname; int maxuse; grouplist_t *gwalk; p += 6; p += strspn(p, " \t"); groupname = p; p += strcspn(p, " \t"); if (isdigit((int) *p)) maxuse = atoi(p); else maxuse = 1; /* Find or create the grouplist entry */ for (gwalk = grouphead; (gwalk && (strcmp(gwalk->groupname, groupname))); gwalk = gwalk->next); if (gwalk == NULL) { gwalk = (grouplist_t *)malloc(sizeof(grouplist_t)); gwalk->groupname = strdup(groupname); gwalk->maxuse = maxuse; gwalk->currentuse = 0; gwalk->next = grouphead; grouphead = gwalk; } if (curtask) curtask->group = gwalk; } else if (curtask && (strncasecmp(p, "INTERVAL ", 9) == 0)) { char *tspec; p += 9; curtask->interval = atoi(p); tspec = p + strspn(p, "0123456789"); switch (*tspec) { case 'm': curtask->interval *= 60; break; /* Minutes */ case 'h': curtask->interval *= 3600; break; /* Hours */ case 'd': curtask->interval *= 86400; break; /* Days */ } } else if (curtask && (strncasecmp(p, "CRONDATE ", 9) == 0)) { p+= 9; xfreedup(curtask->cronstr,p); if (curtask->crondate) { crondatefree(curtask->crondate);curtask->crondate=parse_cron_time(curtask->cronstr); } if (!curtask->crondate) { errprintf("Can't parse cron date: %s->%s", curtask->key, curtask->cronstr); curtask->disabled = 1; } curtask->interval = -1; /* disable interval */ } else if (curtask && (strncasecmp(p, "MAXTIME ", 8) == 0)) { char *tspec; p += 8; curtask->maxruntime = atoi(p); tspec = p + strspn(p, "0123456789"); switch (*tspec) { case 'm': curtask->maxruntime *= 60; break; /* Minutes */ case 'h': curtask->maxruntime *= 3600; break; /* Hours */ case 'd': curtask->maxruntime *= 86400; break; /* Days */ } } else if (curtask && (strncasecmp(p, "LOGFILE ", 8) == 0)) { p += 7; p += strspn(p, " \t"); xfreedup(curtask->logfile,p); } else if (curtask && (strncasecmp(p, "NEEDS ", 6) == 0)) { p += 6; p += strspn(p, " \t"); for (twalk = taskhead; (twalk && strcmp(twalk->key, p)); twalk = twalk->next); if (twalk) { curtask->depends = twalk; } else { errprintf("Configuration error, unknown dependency %s->%s", curtask->key, p); } } else if (curtask && (strncasecmp(p, "ENVFILE ", 8) == 0)) { p += 7; p += strspn(p, " \t"); xfreedup(curtask->envfile,p); } else if (curtask && (strncasecmp(p, "ENVAREA ", 8) == 0)) { p += 7; p += strspn(p, " \t"); xfreedup(curtask->envarea,p); } else if (curtask && (strcasecmp(p, "DISABLED") == 0)) { curtask->disabled = 1; } else if (curtask && (strcasecmp(p, "ENABLED") == 0)) { curtask->disabled = 0; } else if (curtask && (strncasecmp(p, "ONHOST ", 7) == 0)) { regex_t cpattern; int status; p += 7; p += strspn(p, " \t"); xfreedup(curtask->onhostptn,p); /* Match the hostname against the pattern; if it doesnt match then disable the task */ status = regcomp(&cpattern, curtask->onhostptn, REG_EXTENDED|REG_ICASE|REG_NOSUB); if (status == 0) { status = regexec(&cpattern, myhostname, 0, NULL, 0); if (status == REG_NOMATCH) curtask->disabled = 1; } else { errprintf("ONHOST pattern '%s' is invalid\n", p); } } } stackfclose(fd); freestrbuffer(inbuf); /* Running tasks that have been deleted or changed are killed off now. */ for (twalk = taskhead; (twalk); twalk = twalk->next) { /* compare the current settings with the copy - if we have one */ if (twalk->cfload == 0) { if (twalk->copy) { /* compare the current version with the new version and decide if we have changed */ int changed=0; int reload=0; /* first the nummeric ones */ if (twalk->disabled!=twalk->copy->disabled) { changed++; } if (twalk->interval!=twalk->copy->interval) { changed++; } if (twalk->maxruntime!=twalk->copy->maxruntime) { changed++; } if (twalk->group!=twalk->copy->group) { changed++; reload++;} /* then the string versions */ #define twalkstrcmp(k,doreload) { \ if (twalk->k!=twalk->copy->k) { \ if (twalk->copy->k) { \ if (twalk->k) { \ if (strcmp(twalk->k,twalk->copy->k)) { \ changed++;reload+=doreload; \ } \ } else { \ changed++;reload+=doreload; \ } \ /* we can always delete the copy*/ \ xfree(twalk->copy->k); \ twalk->copy->k=NULL; \ } else { \ changed++;reload+=doreload; \ } \ } \ } twalkstrcmp(cmd,1); twalkstrcmp(logfile,1); twalkstrcmp(envfile,1); twalkstrcmp(envarea,1); twalkstrcmp(onhostptn,0); twalkstrcmp(cronstr,0); if ((twalk->copy->cronstr == NULL) && twalk->copy->crondate) { crondatefree(twalk->copy->crondate); twalk->copy->crondate = NULL; } /* we can release the copy now - not using xfree, as this releases it from the list making a mess...*/ xfreenull(twalk->copy); /* now make the decision for reloading - if we have changed, then we may assign cfload, - otherwise the entry does not exist any longer */ if (reload) { reload=1;} if (changed) { twalk->cfload=reload; } } else { /* new object, so we need to do this */ twalk->cfload=1; } } /* and based on this decide what to do */ switch (twalk->cfload) { case -1: /* Kill the task, if active */ if (twalk->pid) { dbgprintf("Killing task %s PID %d\n", twalk->key, (int)twalk->pid); twalk->beingkilled = 1; kill(twalk->pid, SIGTERM); } /* And prepare to free this tasklist entry */ xfreenull(twalk->key); xfreenull(twalk->cmd); xfreenull(twalk->logfile); xfreenull(twalk->envfile); xfreenull(twalk->envarea); xfreenull(twalk->onhostptn); xfreenull(twalk->cronstr); if (twalk->crondate) crondatefree(twalk->crondate); break; case 0: /* Do nothing */ break; case 1: /* Bounce the task, if it is active */ if (twalk->pid) { dbgprintf("Killing task %s PID %d\n", twalk->key, (int)twalk->pid); twalk->beingkilled = 1; kill(twalk->pid, SIGTERM); } break; } } /* First clean out dead tasks at the start of the list */ while (taskhead && (taskhead->cfload == -1)) { tasklist_t *tmp; tmp = taskhead; taskhead = taskhead->next; xfree(tmp); } /* Then unlink and free those inside the list */ twalk = taskhead; while (twalk && twalk->next) { tasklist_t *tmp; if (twalk->next->cfload == -1) { tmp = twalk->next; twalk->next = tmp->next; xfree(tmp); } else twalk = twalk->next; } if (taskhead == NULL) tasktail = NULL; else { tasktail = taskhead; while (tasktail->next) tasktail = tasktail->next; } /* Make sure group usage counts are correct (groups can change) */ for (twalk = taskhead; (twalk); twalk = twalk->next) { if (twalk->group) twalk->group->currentuse = 0; } for (twalk = taskhead; (twalk); twalk = twalk->next) { if (twalk->group && twalk->pid) twalk->group->currentuse++; } } void sig_handler(int signum) { switch (signum) { case SIGCHLD: break; case SIGHUP: nextcfgload = 0; dologswitch = 1; break; case SIGTERM: running = 0; break; } } int main(int argc, char *argv[]) { tasklist_t *twalk, *dwalk; grouplist_t *gwalk; int argi; int daemonize = 1; int verbose = 0; char *config = "/etc/tasks.cfg"; char *logfn = NULL; char *pidfn = NULL; pid_t cpid; int status; struct sigaction sa; char *envarea = NULL; for (argi=1; (argi < argc); argi++) { if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (strcmp(argv[argi], "--no-daemon") == 0) { daemonize = 0; } else if (strcmp(argv[argi], "--verbose") == 0) { verbose = 1; } else if (argnmatch(argv[argi], "--config=")) { char *p = strchr(argv[argi], '='); config = strdup(expand_env(p+1)); } else if (argnmatch(argv[argi], "--log=")) { char *p = strchr(argv[argi], '='); logfn = strdup(expand_env(p+1)); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--pidfile=")) { char *p = strchr(argv[argi], '='); pidfn = strdup(expand_env(p+1)); } else if (strcmp(argv[argi], "--dump") == 0) { /* Dump configuration */ forcereload=1; load_config(config); forcereload=0; for (gwalk = grouphead; (gwalk); gwalk = gwalk->next) { if (gwalk->maxuse > 1) printf("GROUP %s %d\n", gwalk->groupname, gwalk->maxuse); } printf("\n"); for (twalk = taskhead; (twalk); twalk = twalk->next) { printf("[%s]\n", twalk->key); printf("\tCMD %s\n", twalk->cmd); if (twalk->disabled) printf("\tDISABLED\n"); if (twalk->group) printf("\tGROUP %s\n", twalk->group->groupname); if (twalk->depends) printf("\tNEEDS %s\n", twalk->depends->key); if (twalk->interval > 0) printf("\tINTERVAL %d\n", twalk->interval); if (twalk->cronstr) printf("\tCRONDATE %s\n", twalk->cronstr); if (twalk->maxruntime) printf("\tMAXTIME %d\n", twalk->maxruntime); if (twalk->logfile) printf("\tLOGFILE %s\n", twalk->logfile); if (twalk->envfile) printf("\tENVFILE %s\n", twalk->envfile); if (twalk->envarea) printf("\tENVAREA %s\n", twalk->envarea); if (twalk->onhostptn) printf("\tONHOST %s\n", twalk->onhostptn); printf("\n"); } fflush(stdout); return 0; } else { fprintf(stderr,"%s: Unsupported argument: %s\n",argv[0],argv[argi]); fflush(stderr); return 1; } } /* Go daemon */ if (daemonize) { pid_t childpid; /* Become a daemon */ childpid = fork(); if (childpid < 0) { /* Fork failed */ errprintf("Could not fork child\n"); exit(1); } else if (childpid > 0) { /* Parent exits */ if (pidfn) { FILE *pidfd = fopen(pidfn, "w"); if (pidfd) { fprintf(pidfd, "%d\n", (int)childpid); fclose(pidfd); } } exit(0); } /* Child (daemon) continues here */ setsid(); } /* If using a logfile, switch stdout and stderr to go there */ if (logfn) { /* Should we close stdin here ? No ... */ freopen("/dev/null", "r", stdin); freopen(logfn, "a", stdout); freopen(logfn, "a", stderr); } save_errbuf = 0; setup_signalhandler("xymonlaunch"); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; sigaction(SIGHUP, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGCHLD, &sa, NULL); errprintf("xymonlaunch starting\n"); while (running) { time_t now = gettimer(); if (now >= nextcfgload) { load_config(config); nextcfgload = (now + 30); } if (logfn && dologswitch) { freopen(logfn, "a", stdout); freopen(logfn, "a", stderr); dologswitch = 0; } /* Pick up children that have terminated */ while ((cpid = wait3(&status, WNOHANG, NULL)) > 0) { for (twalk = taskhead; (twalk && (twalk->pid != cpid)); twalk = twalk->next); if (twalk) { twalk->pid = 0; twalk->beingkilled = 0; if (WIFEXITED(status)) { twalk->exitcode = WEXITSTATUS(status); if (twalk->exitcode) { errprintf("Task %s terminated, status %d\n", twalk->key, twalk->exitcode); twalk->failcount++; } else { twalk->failcount = 0; } } else if (WIFSIGNALED(status)) { twalk->exitcode = -WTERMSIG(status); twalk->failcount++; errprintf("Task %s terminated by signal %d\n", twalk->key, abs(twalk->exitcode)); } if (twalk->group) twalk->group->currentuse--; /* Tasks that depend on this task should be killed ... */ for (dwalk = taskhead; (dwalk); dwalk = dwalk->next) { if ((dwalk->depends == twalk) && (dwalk->pid > 0)) { dwalk->beingkilled = 1; kill(dwalk->pid, SIGTERM); } } } } /* See what new tasks need to get going */ dbgprintf("\n"); dbgprintf("Starting tasklist scan\n"); crongettime(); for (twalk = taskhead; (twalk); twalk = twalk->next) { if ( (twalk->pid == 0) && !twalk->disabled && ( ((twalk->interval >= 0) && (now >= (twalk->laststart + twalk->interval))) || /* xymon interval condition */ (twalk->crondate && ((twalk->laststart + 55) < now) && cronmatch(twalk->crondate)) /* cron date */ ) ) { if (twalk->depends && ((twalk->depends->pid == 0) || (twalk->depends->laststart > (now - 5)))) { dbgprintf("Postponing start of %s due to %s not yet running\n", twalk->key, twalk->depends->key); continue; } if (twalk->group && (twalk->group->currentuse >= twalk->group->maxuse)) { dbgprintf("Postponing start of %s due to group %s being busy\n", twalk->key, twalk->group->groupname); continue; } if ((twalk->failcount > MAX_FAILS) && ((twalk->laststart + 600) < now)) { dbgprintf("Releasing %s from failure hold\n", twalk->key); twalk->failcount = 0; } if (twalk->failcount > MAX_FAILS) { dbgprintf("Postponing start of %s due to multiple failures\n", twalk->key); continue; } if (twalk->laststart > (now - 5)) { dbgprintf("Postponing start of %s, will not try more than once in 5 seconds\n", twalk->key); continue; } dbgprintf("About to start task %s\n", twalk->key); twalk->laststart = now; twalk->pid = fork(); if (twalk->pid == 0) { /* Exec the task */ char *cmd; char **cmdargs = NULL; static char tasksleepenv[20],bbsleepenv[20]; /* Setup environment */ if (twalk->envfile) { dbgprintf("%s -> Loading environment from %s area %s\n", twalk->key, expand_env(twalk->envfile), (twalk->envarea ? twalk->envarea : "")); loadenv(expand_env(twalk->envfile), twalk->envarea); } /* Setup TASKSLEEP to match the interval */ sprintf(tasksleepenv, "TASKSLEEP=%d", twalk->interval); sprintf(bbsleepenv, "BBSLEEP=%d", twalk->interval); /* For compatibility */ putenv(tasksleepenv); putenv(bbsleepenv); /* Setup command line and arguments */ cmdargs = setup_commandargs(twalk->cmd, &cmd); /* Point stdout/stderr to a logfile, if requested */ if (twalk->logfile) { char *logfn = expand_env(twalk->logfile); dbgprintf("%s -> Assigning stdout/stderr to log '%s'\n", twalk->key, logfn); freopen(logfn, "a", stdout); freopen(logfn, "a", stderr); } /* Go! */ dbgprintf("%s -> Running '%s', XYMONHOME=%s\n", twalk->key, cmd, xgetenv("XYMONHOME")); execvp(cmd, cmdargs); /* Should never go here */ errprintf("Could not start task %s using command '%s': %s\n", twalk->key, cmd, strerror(errno)); exit(0); } else if (twalk->pid == -1) { /* Fork failed */ errprintf("Fork failed!\n"); twalk->pid = 0; } else { if (twalk->group) twalk->group->currentuse++; if (verbose) errprintf("Task %s started with PID %d\n", twalk->key, (int)twalk->pid); } } else if (twalk->pid > 0) { dbgprintf("Task %s active with PID %d\n", twalk->key, (int)twalk->pid); if (twalk->maxruntime && ((now - twalk->laststart) > twalk->maxruntime)) { errprintf("Killing hung task %s (PID %d) after %d seconds\n", twalk->key, (int)twalk->pid, (now - twalk->laststart)); kill(twalk->pid, (twalk->beingkilled ? SIGKILL : SIGTERM)); twalk->beingkilled = 1; /* Next time it's a real kill */ } } } sleep(5); } /* Shutdown running tasks */ for (twalk = taskhead; (twalk); twalk = twalk->next) { if (twalk->pid) kill(twalk->pid, SIGTERM); } if (pidfn) unlink(pidfn); return 0; } xymon-4.3.7/common/xymondigest.10000664000175000017500000000226411671641417016163 0ustar henrikhenrik.TH XYMONDIGEST 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymondigest \- calculate message digests .SH SYNOPSIS .B "xymondigest md5|sha1|rmd160 [filename]" .SH DESCRIPTION .I xymondigest(1) is a utility to calculate message digests for a file or document. It is used when defining HTTP- or FTP-based content checks, where .I xymonnet(1) checks that a URL returns a specific document; instead of having to compare the entire document, the comparison is done against a pre-computed message digest value using the MD5, RIPEMD160, SHA1 or any of the SHA2 (SHA-512, SHA-256, SHA-384, SHA-224) message digest algorithms. The optional \fBfilename\fR parameter is the input file whose message digest should be calculated; if no filename is given, the data is read from standard input. xymondigest outputs a string containing the digest algorithm and the computed message digest. This is in a format suitable for use in the .I hosts.cfg(5) definition of a content check. .SH EXAMPLE $ xymondigest md5 index.html md5:88b81b110a85c83db56a939caa2e2cf6 $ curl -s http://www.foo.com/ | xymondigest sha1 sha1:e5c69784cb971680e2c7380138e04021a20a45a2 .SH "SEE ALSO" xymonnet(1), hosts.cfg(5) xymon-4.3.7/common/xymonserver.cfg.50000664000175000017500000006203411671641417016755 0ustar henrikhenrik.TH XYMONSERVER.CFG 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymonserver.cfg \- Xymon environment variables .SH DESCRIPTION Xymon programs use multiple environment variables beside the normal set of variables. The environment definitions are stored in the ~Xymon/server/etc/xymonserver.cfg file. Each line in this file is of the form \fBNAME=VALUE\fR and defines one environment variable NAME with the value VALUE. You can also append data to existing variables, using the syntax \fBNAME+=VALUE\fR. VALUE is then appended to the existing value of the NAME variable. If NAME has not been defined, then this acts as if it were a normal definition. .SH ENVIRONMENT AREAS In some cases it may be useful to have different values for an environment variable, depending on where it is used. This is possible by defining variables with an associated "area". Such definitions have the form \fBAREA/NAME=VALUE\fR. E.g. to define a special setup of the XYMSERVERS variable when it is used by an application in the "management" area, you would do this: .IP .nf XYMSERVERS="127.0.0.1" # Default definition management/XYMSERVERS="10.1.0.5" # Definition in the "management" area .fi .LP Areas are invoked by using the "--area" option for all tools, or via the ENVAREA setting in the .I tasks.cfg(5) file. .SH GENERAL SETTINGS .IP XYMONSERVERHOSTNAME The fully-qualified hostname of the server that is running Xymon. .IP XYMONSERVERWWWNAME The hostname used to access this servers' web-pages, used to construct URL's in the Xymon webpages. Default is the XYMONSERVERHOSTNAME. .IP XYMONSERVERIP The public IP-address of the server that is running Xymon. .IP XYMONSERVEROS A name identifying the operating system of the Xymon server. The known names are currently "linux", "freebsd", "solaris", "hpux", "aix" and "osf". .IP FQDN If set to TRUE, Xymon will use fully-qualified hostnames throughout. If set to FALSE, hostnames are stripped of their domain-part before being processed. It is \fBhighly recommended\fR that you keep this set to TRUE. Default: TRUE. .IP XYMONLOGSTATUS Controls how the HTML page for a status log is generated. If set to DYNAMIC, the HTML logs are generated on-demand by the .I svcstatus.cgi(1) script. If set to STATIC, you must activate the .I xymond_filestore(8) module (through an entry in the .I tasks.cfg(5) file) to create and store the HTML logs whenever a status update is received. Setting "XYMONLOGSTATUS=STATIC" is \fBdiscouraged\fR since the I/O load on the Xymon server will increase significantly. .IP PINGCOLUMN Defines the name of the column for "ping" test status. The data from the "ping" test is used internally by Xymon, so it must be defined here so all of the Xymon tools know which column to watch for this data. The default setting is PINGCOLUMN=conn. .IP INFOCOLUMN Defines the name of the column for the "info" pages. .IP TRENDSCOLUMN Defines the name of the column for the RRD graph pages. .IP RRDHEIGHT The default height (in pixels) of the RRD graph images. Default: 120 pixels. .IP RRDWIDTH The default width (in pixels) of the RRD graph images. Default: 576 pixels. .IP TRENDSECONDS The graphs on the "trends" page show data for the past TRENDSECONDS seconds. Default: 172800 seconds, i.e. 48 hours. .IP HTMLCONTENTTYPE The Content-type reported by the CGI scripts that generate web pages. By default, this it "text/html". If you have on-line help texts in character sets other than the ISO-8859-1 (western european) character set, it may be necessary to modify this to include a character set. E.g. .br HTMLCONTENTTYPE="text/html; charset=euc-jp" .br for a Japanese character sets. Note: Some webservers will automatically add this, if configured to do so. .IP HOLIDAYS Defines the default set of holidays used if there is no "holidays" tag for a host in the hosts.cfg file. Holiday sets are defined in the .I holidays.cfg(5) file. If not defined, only the default holidays (those defined outside a named holiday set) will be considered as holidays. .IP WEEKSTART Defines which day is the first day of the week. Set to "0" for Sunday, "1" for Monday. Default: 1 (Monday). .IP XYMONBODYHEADER Text that is included in the Xymon web pages at the location specified by ~xymon/server/web/*_header templates. If this is set to a value beginning with "file:", then the contents of the specified file is included. Default: "file:$XYMONHOME/etc/xymonmenu.cfg" .IP XYMONBODYFOOTER Text that is included in the Xymon web pages at the location specified by ~xymon/server/web/*_footer templates. If this is set to a value beginning with "file:", then the contents of the specified file is included. Default: Empty. .IP XYMONBODYMENUCSS URL for the Xymon menu CSS file. Default: "$XYMONMENUSKIN/xymonmenu.css" .IP HOSTPOPUP Determines what is used as the host comment on the webpages. The comment by default appears as a pop-up when the mouse hovers over the hostname, and is also shown on the "info" status page. This setting must be one or more of the letters "C" (COMMENT), "D" (DESCRIPTION) or "I" (IP-address). Including "C" uses the COMMENT setting for the host, including "D" uses the DESCR setting for the host, and "I" uses the IP-address of the host. If more than one of these is set, then COMMENT takes precedence over DESCR which again has precence over IP. Note that DESCR and IP only appear in pop-up windows (if enabled), whereas the COMMENT is always used - if pop-up's have been disabled, then the COMMENT value is displayed next to the hostname on the webpages. Default: CDI .IP STATUSLIFETIME The number of minutes that a status is considered valid after an update. After this time elapses, the status will go purple. Default: 30 minutes .SH DIRECTORIES .IP XYMONSERVERROOT The top-level directory for the Xymon installation. The default is the home-directory for the user running Xymon. .IP XYMONSERVERLOGS The directory for the Xymon's own logfiles (NOT the status-logs from the monitored hosts). .IP XYMONHOME The Xymon server directory, where programs and configurations are kept. Default: $XYMONSERVERROOT/server/ . .IP XYMONTMP Directory used for temporary files. Default: $XYMONHOME/tmp/ .IP XYMONWWWDIR Directory for Xymon webfiles. The $XYMONWEB URL must map to this directory. Default: $XYMONHOME/www/ .IP XYMONNOTESDIR Directory for Xymon notes-files. The $XYMONNOTESSKIN URL must map to this directory. Default: $XYMONHOME/www/notes/ .IP XYMONREPDIR Directory for Xymon availability reports. The $XYMONREPURL URL must map to this directory. Note also that your webserver must have write-access to this directory, if you want to use the .I report.cgi(1) CGI script to generate reports on-demand. Default: $XYMONHOME/www/rep/ .IP XYMONSNAPDIR Directory for Xymon snapshots. The $XYMONSNAPURL URL must map to this directory. Note also that your webserver must have write-access to this directory, if you want to use the .I snapshot.cgi(1) CGI script to generate snapshots on-demand. Default: $XYMONHOME/www/snap/ .IP XYMONVAR Directory for all data stored about the monitored items. Default: $XYMONSERVERROOT/data/ .IP XYMONRAWSTATUSDIR Directory for storing the raw status-logs. Not used unless "xymond_filestore --status" is running, which is \fBdiscouraged\fR since it increases the load on the Xymon server significantly. Default: $XYMONVAR/logs/ .IP XYMONHTMLSTATUSDIR Directory for storing HTML status-logs. Not used unless "xymond_filestore --status --html" is running, which is \fBdiscouraged\fR since it increases the load on the Xymon server significantly. Default: $XYMONHOME/www/html/ .IP XYMONHISTDIR Directory for storing the history of monitored items. Default: $XYMONVAR/hist/ .IP XYMONHISTLOGS Directory for storing the detailed status-log of historical events. Default: $XYMONVAR/histlogs/ .IP XYMONACKDIR Directory for storing information about alerts that have been acknowledged. Default: $XYMONVAR/acks/ .IP XYMONDISABLEDDIR Directory for storing information about tests that have been disabled. Default: $XYMONVAR/disabled/ .IP XYMONDATADIR Directory for storing incoming "data" messages. Default: $XYMONVAR/data/ .IP XYMONRRDS Top-level directory for storing RRD files (the databases with trend-information used to generate graphs). Default: $XYMONVAR/rrd/ .IP CLIENTLOGS Directory for storing the data sent by a Xymon client around the time a status changes to a warning (yellow) or critical (red) state. Used by the .I xymond_hostdata(8) module. Default: $XYMONVAR/hostdata/ .IP XYMONCGILOGDIR Directory where debug output from CGI applications are stored. If not specified, it defaults to $XYMONSERVERLOGS, but this is often a directory that is not writable by the userid running the CGI applications. It is therefore recommended when using "--debug" on CGI applications that you create a separate directory owned by the user running your webserver, and point XYMONCGILOGDIR to this directory. .SH SYSTEM FILES .IP HOSTSCFG Full path to the Xymon .I hosts.cfg(5) configuration file. Default: $XYMONHOME/etc/hosts.cfg. .IP XYMON Full path to the .I xymon(1) client program. Default: $XYMONHOME/bin/xymon. .IP XYMONGEN Full path to the .I xymongen(1) webpage generator program. Default: $XYMONHOME/bin/xymongen. .SH URLS .IP XYMONSERVERWWWURL The root URL for the Xymon webpages, without the hostname. This URL must be mapped to the ~/server/www/ directory in your webserver configuration. See the sample Apache configuration in ~/server/etc/xymon-apache.conf. .IP XYMONSERVERCGIURL The root URL for the Xymon CGI-scripts, without the hostname. This directory must be mapped to the ~/cgi-bin/ directory in your webserver configuration, and must be flagged as holding executable scripts. See the sample Apache configuration in ~/server/etc/xymon-apache.conf. .IP XYMONWEBHOST Initial part of the Xymon URL, including just the protocol and the hostname, e.g. "http://www.foo.com" .IP XYMONWEBHOSTURL Prefix for all of the static Xymon webpages, e.g. "http://www.foo.com/xymon" .IP XYMONWEBHTMLLOGS URL prefix for the static HTML status-logs generated when XYMONLOGSTATUS=STATIC. Note that this setting is \fBdiscouraged\fR so this setting should not be used. .IP XYMONWEB URL prefix (without hostname) of the Xymon webpages. E.g. "/xymon". .IP XYMONSKIN URL prefix (without hostname) of the Xymon graphics. E.g. "/xymon/gifs". .IP XYMONHELPSKIN URL prefix (without hostname) of the Xymon on-line help files. E.g "/xymon/help". .IP XYMONMENUSKIN URL prefix (without hostname) of the Xymon menu files. E.g "/xymon/menu". .IP XYMONNOTESSKIN URL prefix (without hostname) of the Xymon on-line notes files. E.g "/xymon/notes". .IP XYMONREPURL URL prefix (without hostname) of the Xymon availability reports. E.g. "/xymon/rep". .IP XYMONSNAPURL URL prefix (without hostname) of the Xymon snapshots. E.g. "/xymon/snap". .IP XYMONWAP URL prefix (without hostname) of the Xymon WAP/WML files. E.g. "/xymon/wml". .IP CGIBINURL URL prefix (without hostname) of the Xymon CGI-scripts. Default: $XYMONSERVERCGIURL . .IP COLUMNDOCURL Format string used to build a link to the documentation for a column heading. Default: "$CGIBINURL/columndoc.sh?%s", which causes links to use the .I columndoc.sh(1) script to document a column. .IP HOSTDOCURL Format string used to build a link to the documentation for a host. If not set, then Xymon falls back to scanning the XYMONNOTES directory for files matching the hostname, or the hostname together with a common filename extension (.php, .html, .doc and so on). If set, this string becomes a formatting string for the documentation URL. E.g. for the host "myhost", a setting of HOSTDOCURL="/docs/%s.php" will generate a link to "/docs/myhost.php". Default: Not set, so host documentation will be retrieved from the XYMONNOTES directory. .SH SETTINGS FOR SENDING MESSAGES TO XYMON .IP XYMSRV The IP-address used to contact the .I xymond(8) service. Used by clients and the tools that perform network tests. Default: $XYMONSERVERIP .IP XYMSERVERS List of IP-adresses. Clients and network test tools will try to send status reports to a Xymon server running on each of these adresses. This setting is only used if XYMSRV=0.0.0.0. .IP XYMONDPORT The portnumber for used to contact the .I xymond(8) service. Used by clients and the tools that perform network tests. Default: 1984. .IP MAXMSGSPERCOMBO The maximum number of status messages to combine into one combo message. Default: 100. .IP SLEEPBETWEENMSGS Length of a pause introduced between each successive transmission of a combo-message by xymonnet, in microseconds. Default: 0 (send messages as quickly as possible). .SH XYMOND SETTINGS .IP ALERTCOLORS Comma-separated list of the colors that may trigger an alert-message. The default is "red,yellow,purple". Note that alerts may further be generated or suppresed based on the configuration in the .I alerts.cfg(5) file. .IP OKCOLORS Comma-separated list of the colors that may trigger a recovery-message. The default is "green,clear,blue". .IP ALERTREPEAT How often alerts get repeated while a status is in an alert state. This is the default setting, which may be changed in the .I alerts.cfg(5) file. .IP MAXMSG_STATUS The maximum size of a "status" message in kB, default: 256. Status messages are the ones that end up as columns on the web display. The default size should be adequate in most cases, but some extension scripts can generate very large status messages - close to 1024 kB. You should only change this if you see messages in the xymond log file about status messages being truncated. .IP MAXMSG_CLIENT The maximum size of a "client" message in kB, default: 512. "client" messages are generated by the Xymon client, and often include large process-listings. You should only change this if you see messages in the xymond log file about client messages being truncated. .IP MAXMSG_DATA The maximum size of a "data" message in kB, default: 256. "data" messages are typically used for client reports of e.g. netstat or vmstat data. You should only change this setting if you see messages in the xymond log file about data messages being truncated. .IP MAXMSG_NOTES The maximum size of a "notes" message in kB, default: 256. "notes" messages provide a way for uploading documentation about a host to Xymon; it is not enabled by default. If you want to upload large documents, you may need to change this setting. .IP MAXMSG_STACHG The maximum size of a "status change" message in kB, default: Current value of the MAXMSG_STATUS setting. Status-change messages occur when a status changes color. There is no reason to change this setting. .IP MAXMSG_PAGE The maximum size of a "page" message in kB, default: Current value of the MAXMSG_STATUS setting. "page" messages are alerts, and include the status message that triggers the alert. There is no reason to change this setting. .IP MAXMSG_ENADIS The maximum size of an "enadis" message in kB, default: 32. "enadis" are small messages used when enabling or disabling hosts and tests, so the default size should be adequate. .IP MAXMSG_CLICHG The maximum size of a "client change" message in kB, default: Current value of the MAXMSG_CLIENT setting. Client-change messages occur when a status changes color to one of the alert-colors, usually red, yellow and purple. There is no reason to change this setting. .IP MAXMSG_USER The maximum size of a "user" message in kB, default: 128. "user" messages are for communication between custom Xymon modules you have installed, it is not used directly by Xymon. .SH XYMOND_HISTORY SETTINGS .IP XYMONALLHISTLOG If set to TRUE, .I xymond_history(8) will update the $XYMONHISTDIR/allevents file logging all changes to a status. The allevents file is used by the .I eventlog.cgi(1) tool to show the list of recent events on the "All non-green" webpage. .IP XYMONHOSTHISTLOG If set to TRUE, .I xymond_history(8) will update the host-specific eventlog that keeps record of all status changes for a host. This logfile is not used by any Xymon tool. .IP SAVESTATUSLOG If set to TRUE, .I xymond_history(8) will save historical detailed status-logs to the $XYMONHISTLOGS directory. .SH XYMOND_ALERT SETTINGS .IP MAIL Command used to send alerts via e-mail, including a "Subject:" header in the mail. Default: "mail -s" .IP MAILC Command used to send alerts via e-mail in a form that does not have a "Subject" in the mail. Default: "mail" .IP SVCCODES Maps status-columns to numeric service-codes. The numeric codes are used when sending an alert using a script, where the numeric code of the service is provided in the BBSVCNUM variable. .SH XYMOND_RRD SETTINGS .IP TEST2RRD List of "COLUMNNAME[=RRDSERVICE]" settings, that define which status- and data-messages have a corresponding RRD graph. You will normally not need to modify this, unless you have added a custom TCP-based test to the protocols.cfg file, and want to collect data about the response-time, OR if you are using the .I xymond_rrd(8) external script mechanism to collect data from custom tests. Note: All TCP tests are automatically added. This is also used by the .I svcstatus.cgi(1) script to determine if the detailed status view of a test should include a graph. .IP GRAPHS List of the RRD databases, that should be shown as a graph on the "trends" column. .IP NORRDDISKS This is used to disable the tracking of certain filesystems. By default all filesystems reported by a client are tracked. In some cases you may want to disable this for certain filesystems, e.g. database filesystems since they are always completely full. This setting is a regular expression that is matched against the filesystem name (the Unix mount-point, or the Windows disk-letter) - if the filesystem name matches this expression, then it will not be tracked by Xymon. .br Note: Setting this does not affect filesystems that are already being tracked by Xymon - to remove them, you must remove the RRD files for the unwanted filesystems from the ~xymon/data/rrd/HOSTNAME/ directory. .IP RRDDISKS This is used to enable tracking of only selected filesystems (see the NORRDDISKS setting above). By default all filesystems are being tracked, setting this changes that default so that only those filesystems that match this pattern will be tracked. .SH XYMONNET NETWORK TEST SETTINGS .IP XYMONNETWORK If this variable is defined, then only the hosts that have been tagged with "NET:$XYMONNETWORK" will be tested by the xymonnet tool. .IP CONNTEST If set to TRUE, the connectivity (ping) test will be performed. .IP IPTEST_2_CLEAR_ON_FAILED_CONN If set to TRUE, then failing network tests go CLEAR if the conn-test fails. .IP NONETPAGE List of network services (separated with ) that should go yellow upon failure instead of red. .IP XYMONROUTERTEXT When using the "router" or "depends" tags for a host, a failure status will include text that an "Intermediate router is down". With todays network topologies, the router could be a switch or another network device; if you define this environment variable the word "router" will be replaced with whatever you put into the variable. So to inform the users that an intermediate switch or router is down, use XYMONROUTERTEXT="switch or router". This can also be set on a per-host basis using the "DESCR:hosttype:description" tag in the .I hosts.cfg(5) file. .IP NETFAILTEXT When a network test fails, the status message reports "SERVICENAME not OK". The "not OK" message can be changed via this variable, e.g. you can change it to "FAILED" or customize it as you like. .IP FPING The command used to run the .I xymonping(1) tool for the connectivity test. (The name FPING is due to the fact that the "fping" utility was used until Xymon version 4.2). This may include suid-root wrappers and xymonping options. Default: "xymonping" .IP TRACEROUTE Defines the location of the "traceroute" tool and any options needed to run it. traceroute it used by the connectivity test when the ping test fails; if requested via the "trace" tag, the TRACEROUTE command is executed to try to determine the point in the network that is causing the problem. By default the command executed is "traceroute -n -q 2 -w 2 -m 15" (no DNS lookup, max. 2 probes, wait 2 seconds per hop, max 15 hops). .sp If you have the .I mtr(8) tool installed - available from http://www.bitwizard.nl/mtr/ - I strongly recommend using this instead. The recommended setting for mtr is "/usr/sbin/mtr -c 2 -n --report" (the exact path to the mtr utility may be different on your system). Note that mtr needs to be installed suid-root on most systems. .IP NTPDATE Defines the .I ntpdate(1) program used for the "ntp" test. Default: "ntpdate" .IP RPCINFO Defines the .I rpcinfo(8) program used for "rpc" tests. Default: "rpcinfo" .SH XYMONGEN WEBPAGE GENERATOR SETTINGS .IP XYMONLOGO HTML code that is inserted on all standard headers. The default is to add the text "Xymon" in the upper-left corner of the page, but you can easily replace this with e.g. a company logo. If you do, I suggest that you keep it at about 30-35 pixels high, and 100-150 pixels wide. .IP XYMONPAGELOCAL The string "Pages hosted locally" that appears above all of the pages linked from the main Xymon webpage. .IP XYMONPAGESUBLOCAL The string "Subpages hosted locally" that appears above all of the sub-pages linked from pages below the main Xymon webpage. .IP XYMONPAGEREMOTE The string "Remote status display" that appears about the summary statuses displayed on the min Xymon webpage. .IP XYMONPAGETITLE HTML tags designed to go in a tag, to choose the font for titles of the webpages. .IP XYMONPAGEROWFONT HTML tags designed to go in a tag, to choose the font for row headings (hostnames) on the webpages. .IP XYMONPAGECOLFONT HTML tags designed to go in a tag, to chose the font for column headings (test names) on the webpages. .IP XYMONPAGEACKFONT HTML tags designed to go in a tag, to chose the font for the acknowledgement text displayed on the status-log HTML page for an acknowledged status. .IP ACKUNTILMSG When displaying the detailed status of an acknowledged test, Xymon will include the time that the acknowledge expires using the print-format defined in this setting. You can define the timeformat using the controls in your systems .I strftime(3) routine, and add the text suitable for your setup. .IP XYMONDATEFORMAT On webpages generated by xymongen, the default header includes the current date and time. Normally this looks like "Tue Aug 24 21:59:47 2004". The XYMONDATEFORMAT controls the format of this timestamp - you can define the format using the controls in the .I strftime(3) routine. E.g. to have it show up as "2004-08-24 21:59:47 +0200" you would set XYMONDATEFORMAT="%Y-%m-%d %H:%M:%S %z" .IP HOLIDAYFORMAT How holiday dates are displayed. The default is "%d/%m" which show the day and month. American users may want to change this to "%m/%d" to suit their preferred date-display style. This is a formatting string for the system .I strftime(3) routine, so any controls available for this routine may be used. .IP XYMONPAGECOLREPEAT Inspired by Jeff Stoner's col_repeat_patch.tgz patch, this defines the maximum number of rows before repeating the column headings on a webpage. This sets the default value for the .I xymongen(1) "--maxrows" option; if the command-line option is also specifed, then it overrides this environment variable. Note that unlike Jeff's patch, xymongen implements this for both the "All non-green" page and all other pages (xymon.html, subpages, critical.html). .IP SUMMARY_SET_BKG If set to TRUE, then summaries will affect the color of the main Xymon webpage. Default: FALSE. .IP DOTHEIGHT The height (in pixels) of the icons showing the color of a status. Default: 16, which matches the default icons. .IP DOTWIDTH The width (in pixels) of the icons showing the color of a status. Default: 16, which matches the default icons. .IP CLIENTSVCS List of the status logs fed by data from the Xymon client. These status logs will - if there are Xymon client data available for the host - include a link to the raw data sent by the client. Default: cpu,disk,memory,procs,svcs. .IP XYMONRSSTITLE If defined, this is the title of the RSS/RDF documents generated when .I xymongen(1) is invoked with the "--rss" option. The default value is "Xymon Alerts". .IP WMLMAXCHARS Maximum size of a WAP/WML output "card" when generating these. Default: 1500. .IP XYMONNONGREENEXT List of scripts to run as extensions to the "All non-green" page. Note that two scripts, "eventlog.sh" and "acklog.sh" are handled specially: They are handled internally by xymongen, but the script names must be listed in this variable for this function to be enabled. .IP XYMONHISTEXT List of scripts to run as extensions to a history page. .IP XYMONREPWARN Default threshold for listing the availability as "critical" (red) when generating the availability report. This can be set on a per-host basis with the WARNPCT setting in .I hosts.cfg(5). Default: 97 (percent) .IP XYMONGENREPOPTS Default xymongen options used for reports. This will typically include such options as "--subpagecolumns", and also "--ignorecolumns" if you wish to exclude certain tests from reports by default. .IP XYMONGENSNAPOPTS Default xymongen options used by snapshots. This should be identical to the options you normally used when building Xymon webpages. .SH FILES .BR "~xymon/server/etc/xymonserver.cfg" .SH "SEE ALSO" xymon(7) xymon-4.3.7/common/tasks.cfg.50000664000175000017500000001234411671641417015500 0ustar henrikhenrik.TH TASKS.CFG 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME tasks.cfg \- Task definitions for the xymonlaunch utility .SH SYNOPSIS .B ~xymon/server/etc/tasks.cfg .SH DESCRIPTION The tasks.cfg file holds the list of tasks that xymonlaunch runs to perform all of the tasks needed by the Xymon monitor. .SH FILE FORMAT A task is defined by a \fBkey\fR, a \fBcommand\fR, and optionally also \fBinterval\fR, \fBenvironment\fR, and \fBlogfile\fR. Blank lines and lines starting with a hash mark (#) are treated as comments and ignored. Long lines can be broken up by putting a backslash at the end of the line and continuing the entry on the next line. An entry looks like this: .sp [xymond] .br ENVFILE /usr/local/xymon/server/etc/xymonserver.cfg .br CMD /usr/local/xymon/server/bin/xymond .sp [updateweb] .br ENVFILE /usr/local/xymon/server/etc/xymonserver.cfg .br CMD /usr/local/xymon/server/bin/xymongen .br NEEDS xymond .br GROUP webupdates .br INTERVAL 5m .br ONHOST localhost .br MAXTIME 10m .br LOGFILE /var/log/xymon/updateweb.log .sp [monthlyreport] .br ENVFILE /usr/local/xymon/server/etc/xymonserver.cfg .br CMD /usr/local/xymon/server/ext/monthlyreport.sh .br CRONDATE 30 4 1 * * .sp The \fBkey\fR is enclosed in angle brackets, and must be unique for each task. You can choose your key-names as you like, they are only used internally in xymonlaunch to identify each task. The \fBcommand\fR is defined by the \fbCMD\fR keyword. This is the full command including any options you want to use for this task. This is required for all tasks. The \fBDISABLED\fR keyword means that this command is disabled. xymonlaunch will not start this task. It is recommended that you use this to disable standard tasks, instead of removing them or commenting them out. Upgrades to Xymon will add standard tasks back into the file, so unless you have them listed as DISABLED then tasks may re-appear unexpectedly after an upgrade. There is also a corresponding \fBENABLED\fR keyword, to explicitly enable a task. The \fBONHOST\fR keyword tells xymonlaunch that this task should only run on specific hosts. After the ONHOST keyword, you must provide a "regular expression"; if the hostname where xymonlaunch runs matches this expression, then the task will run. If it doesn't match, then the task is treated as if it were DISABLED. The \fBMAXTIME\fR keyword sets a maximum time that the task may run; if exceeded, xymonlaunch will kill the task. The time is in seconds by default, you can specify minutes, hours or days by adding an "m", "h" or "d" after the number. By default there is no upper limit on how long a taskmay run. The \fBNEEDS\fR instructs xymonlaunch not to run this task unless the task defined by the NEEDS keyword is already running. This is used e.g. to delay the start of some application until the needed daemons have been started. The task that must be running is defined by its \fBkey\fR. The \fBGROUP\fR keyword can be used to limit the number of tasks that may run simultaneously. E.g. if you are generating multiple pagesets of webpages, you dont want them to run at the same time. Putting them into a GROUP will cause xymonlaunch to delay the start of new tasks, so that only one task will run per group. You can change the limit by defining the group before the tasks, with a "GROUP groupname maxtasks" line. The \fBINTERVAL\fR keyword defines how often this command is executed. The example shows a command that runs every 5 minutes. If no interval is given, the task is only run once - this is useful for tasks that run continually as daemons - although if the task stops for some reason, then xymonlaunch will attempt to restart it. Intervals can be specified in seconds (if you just put a number there), or in minutes (5m), hours (2h), or days (1d). The \fBCRONDATE\fR keyword is used for tasks that must run at regular intervals or at a specific time. The time specification is identical to the one used by cron in .I crontab(5) entries, i.e. a sequence of numbers for minute, hour, day-of-month, month and day-of-week. Three-letter abbreviations in english can be used for the month and day-of-week fields. An asterisk is a wildcard. So in the example above, this job would run once a month, at 4:30 AM on the 1st day of the month. The \fBENVFILE\fR setting points to a file with definitions of environment variables. Before running the task, xymonlaunch will setup all of the environment variables listed in this file. Since this is a per-task setting, you can use the same xymonlaunch instance to run e.g. both the server- and client-side Xymon tasks. If this option is not present, then the environment defined to xymonlaunch is used. The \fBENVAREA\fR setting modifies which environment variables are loaded, by picking up the ones that are defined for this specific "area". See .I xymonserver.cfg(5) for information about environment areas. The \fBLOGFILE\fR setting defines a logfile for the task. xymonlaunch will start the task with stdout and stderr redirected to this file. If this option is not present, then the output goes to the same location as the xymonlaunch output. .SH "SEE ALSO" xymonlaunch(8), xymond(8), crontab(5), xymon(7) xymon-4.3.7/common/xymoncfg.10000664000175000017500000000144511671641417015443 0ustar henrikhenrik.TH XYMONCFG 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymoncfg \- output the full hosts.cfg file .SH SYNOPSIS .B "xymoncfg [--web] [--net] [filename]" .SH DESCRIPTION .I xymoncfg(1) dumps the full hosts.cfg file to stdout. It follows "include" tags in the hosts.cfg files, and prints the full contents as seen by the .I xymongen(1) and .I xymonnet(1) utilities. If no filename is given, xymoncfg displays the file pointed to by the HOSTSCFG environment variable. .SH OPTIONS .IP "--web" Show the hosts.cfg file following include statements as a Xymon web-server would. .IP "--net" Show the hosts.cfg file following include statements as done when running xymonnet. .SH ENVIRONMENT VARIABLES .IP HOSTSCFG Filename for the .I hosts.cfg(5) file. .SH "SEE ALSO" hosts.cfg(5), xymonserver.cfg(5) xymon-4.3.7/common/xymongrep.c0000664000175000017500000001706011615341300015705 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon hosts.cfg file grep'er */ /* */ /* This tool will pick out the hosts from a hosts.cfg file that has one of */ /* the tags given on the command line. This allows an extension script to */ /* deal with only the relevant parts of the hosts.cfg file, instead of */ /* having to parse the entire file. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymongrep.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include "version.h" #include "libxymon.h" static char *connstatus = NULL; static char *teststatus = NULL; static char *conncolumn = "conn"; static char *testcolumn = NULL; static void load_hoststatus() { int res; char msg[1024]; sendreturn_t *sres; sprintf(msg, "xymondboard fields=hostname,testname,color test=%s", conncolumn); sres = newsendreturnbuf(1, NULL); res = sendmessage(msg, NULL, XYMON_TIMEOUT, sres); if (res == XYMONSEND_OK) connstatus = getsendreturnstr(sres, 1); if ((res == XYMONSEND_OK) && testcolumn) { sprintf(msg, "xymondboard fields=hostname,testname,color test=%s", testcolumn); res = sendmessage(msg, NULL, XYMON_TIMEOUT, sres); if (res == XYMONSEND_OK) teststatus = getsendreturnstr(sres, 1); } if (res != XYMONSEND_OK) { errprintf("Cannot fetch Xymon status, ignoring --no-down\n"); connstatus = NULL; teststatus = NULL; } freesendreturnbuf(sres); } static int netok(char *netstring, char *curnet, int testuntagged) { return ( (netstring == NULL) || (curnet && netstring && (strcmp(curnet, netstring) == 0)) || (testuntagged && (curnet == NULL)) ); } static int downok(char *hostname, int nodownhosts) { char *mark, *colorstr; int color; if (!nodownhosts) return 1; /* Check if the host is down (i.e. "conn" test is non-green) */ if (!connstatus) return 1; mark = (char *)malloc(strlen(hostname) + strlen(conncolumn) + 4); sprintf(mark, "\n%s|%s|", hostname, conncolumn); colorstr = strstr(connstatus, mark); if (colorstr) { colorstr += strlen(mark); /* Skip to the color data */ } else if (strncmp(connstatus, mark+1, strlen(mark+1)) == 0) { colorstr = connstatus + strlen(mark+1); /* First entry we get */ } xfree(mark); color = (colorstr ? parse_color(colorstr) : COL_GREEN); if ((color == COL_RED) || (color == COL_BLUE)) return 0; /* Check if the test is currently disabled */ if (!teststatus) return 1; mark = (char *)malloc(strlen(hostname) + strlen(testcolumn) + 4); sprintf(mark, "\n%s|%s|", hostname, testcolumn); colorstr = strstr(teststatus, mark); if (colorstr) { colorstr += strlen(mark); /* Skip to the color data */ } else if (strncmp(teststatus, mark+1, strlen(mark+1)) == 0) { colorstr = teststatus + strlen(mark+1); /* First entry we get */ } xfree(mark); color = (colorstr ? parse_color(colorstr) : COL_GREEN); if ((color == COL_RED) || (color == COL_BLUE)) return 0; return 1; } int main(int argc, char *argv[]) { void *hwalk; char *hostsfn = NULL; char *netstring = NULL; char *include2 = NULL; int extras = 1; int testuntagged = 0; int nodownhosts = 0; int onlypreferredentry = 0; char *p; char **lookv; int argi, lookc; strbuffer_t *wantedtags; if ((argc <= 1) || (strcmp(argv[1], "--help") == 0)) { printf("Usage:\n%s test1 [test1] [test2] ... \n", argv[0]); exit(1); } lookv = (char **)malloc(argc*sizeof(char *)); lookc = 0; hostsfn = xgetenv("HOSTSCFG"); conncolumn = xgetenv("PINGCOLUMN"); for (argi=1; (argi < argc); argi++) { if (strcmp(argv[argi], "--noextras") == 0) { extras = 0; } else if (strcmp(argv[argi], "--test-untagged") == 0) { testuntagged = 1; } else if (argnmatch(argv[argi], "--no-down")) { char *p; nodownhosts = 1; p = strchr(argv[argi], '='); if (p) testcolumn = strdup(p+1); } else if (strcmp(argv[argi], "--version") == 0) { printf("xymongrep version %s\n", VERSION); exit(0); } else if ((strcmp(argv[argi], "--net") == 0) || (strcmp(argv[argi], "--bbnet") == 0)) { include2 = "netinclude"; onlypreferredentry = 0; } else if ((strcmp(argv[argi], "--web") == 0) || (strcmp(argv[argi], "--bbdisp") == 0)) { include2 = "dispinclude"; onlypreferredentry = 1; } else if (argnmatch(argv[argi], "--hosts=")) { hostsfn = strchr(argv[argi], '=') + 1; } else { lookv[lookc] = strdup(argv[argi]); lookc++; } } lookv[lookc] = NULL; if ((hostsfn == NULL) || (strlen(hostsfn) == 0)) { errprintf("Environment variable HOSTSCFG is not set - aborting\n"); exit(2); } load_hostnames(hostsfn, include2, get_fqdn()); if (first_host() == NULL) { errprintf("Cannot load %s, or file is empty\n", hostsfn); exit(3); } /* If we must avoid downed or disabled hosts, let's find out what those are */ if (nodownhosts) load_hoststatus(); /* Each network test tagged with NET:locationname */ p = xgetenv("XYMONNETWORK"); if ((p == NULL) || (strlen(p) == 0)) p = xgetenv("BBLOCATION"); if (p && strlen(p)) netstring = strdup(p); hwalk = first_host(); wantedtags = newstrbuffer(0); while (hwalk) { char hostip[IP_ADDR_STRLEN]; char *curnet = xmh_item(hwalk, XMH_NET); char *curname = xmh_item(hwalk, XMH_HOSTNAME); /* * Only look at the hosts whose NET: definition matches the wanted one. * Must also check if the host is currently down (not responding to ping). * And if the host is OK with knownhost(), because it may be time-limited. */ if (netok(netstring, curnet, testuntagged) && downok(curname, nodownhosts) && knownhost(curname, hostip, GH_IGNORE)) { char *item; clearstrbuffer(wantedtags); for (item = xmh_item_walk(hwalk); (item); item = xmh_item_walk(NULL)) { int i; char *realitem = item + strspn(item, "!~?"); for (i=0; lookv[i]; i++) { char *outitem = NULL; if (lookv[i][strlen(lookv[i])-1] == '*') { if (strncasecmp(realitem, lookv[i], strlen(lookv[i])-1) == 0) { outitem = (extras ? item : realitem); } } else if (strcasecmp(realitem, lookv[i]) == 0) { outitem = (extras ? item : realitem); } if (outitem) { int needquotes = ((strchr(outitem, ' ') != NULL) || (strchr(outitem, '\t') != NULL)); addtobuffer(wantedtags, " "); if (needquotes) addtobuffer(wantedtags, "\""); addtobuffer(wantedtags, outitem); if (needquotes) addtobuffer(wantedtags, "\""); } } } if (STRBUF(wantedtags) && (*STRBUF(wantedtags) != '\0') && extras) { if (xmh_item(hwalk, XMH_FLAG_DIALUP)) addtobuffer(wantedtags, " dialup"); if (xmh_item(hwalk, XMH_FLAG_TESTIP)) addtobuffer(wantedtags, " testip"); } if (STRBUF(wantedtags) && *STRBUF(wantedtags)) { printf("%s %s #%s\n", xmh_item(hwalk, XMH_IP), xmh_item(hwalk, XMH_HOSTNAME), STRBUF(wantedtags)); } } do { hwalk = next_host(hwalk, 1); } while (hwalk && onlypreferredentry && (strcmp(curname, xmh_item(hwalk, XMH_HOSTNAME)) == 0)); } return 0; } xymon-4.3.7/common/xymondigest.c0000664000175000017500000000333511615341300016227 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor message digest tool. */ /* */ /* This is used to implement message digest functions (MD5, SHA1 etc.) */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymondigest.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include "libxymon.h" int main(int argc, char *argv[]) { FILE *fd; char buf[8192]; int buflen; digestctx_t *ctx; if (argc < 2) { printf("Usage: %s digestmethod [filename]\n", argv[0]); printf("\"digestmethod\" is \"md5\", \"sha1\", \"sha256\", \"sha512\" or \"rmd160\"\n"); return 1; } if ((ctx = digest_init(argv[1])) == NULL) { printf("Unknown message digest method %s\n", argv[1]); return 1; } if (argc > 2) fd = fopen(argv[2], "r"); else fd = stdin; if (fd == NULL) { printf("Cannot open file %s\n", argv[2]); return 1; } while ((buflen = fread(buf, 1, sizeof(buf), fd)) > 0) { digest_data(ctx, buf, buflen); } printf("%s\n", digest_done(ctx)); return 0; } xymon-4.3.7/common/Makefile0000664000175000017500000000742511636272271015172 0ustar henrikhenrik# Xymon - common tools # PROGRAMS = xymongrep xymondigest xymon xymoncmd xymonlaunch xymoncfg CLIENTPROGRAMS = ../client/xymon ../client/xymonlaunch ../client/xymoncmd ../client/xymongrep ../client/xymoncfg ../client/xymondigest HOSTGREPOBJS = xymongrep.o HOSTSHOWOBJS = xymoncfg.o DIGESTOBJS = xymondigest.o XYMONOBJS = xymon.o LAUNCHOBJS = xymonlaunch.o CMDOBJS = xymoncmd.o all: $(PROGRAMS) client: $(CLIENTPROGRAMS) xymongrep: $(HOSTGREPOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) -o $@ $(HOSTGREPOBJS) ../lib/libxymon.a $(NETLIBS) $(LIBRTDEF) ../client/xymongrep: $(HOSTGREPOBJS) ../lib/xymonclient.a $(CC) $(CFLAGS) -o $@ $(HOSTGREPOBJS) ../lib/xymonclient.a $(NETLIBS) $(LIBRTDEF) xymoncfg: $(HOSTSHOWOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) -o $@ $(HOSTSHOWOBJS) ../lib/libxymon.a $(NETLIBS) $(LIBRTDEF) ../client/xymoncfg: $(HOSTSHOWOBJS) ../lib/xymonclient.a $(CC) $(CFLAGS) -o $@ $(HOSTSHOWOBJS) ../lib/xymonclient.a $(NETLIBS) $(LIBRTDEF) xymon: $(XYMONOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) -o $@ $(XYMONOBJS) ../lib/libxymon.a $(NETLIBS) $(LIBRTDEF) ../client/xymon: $(XYMONOBJS) ../lib/xymonclient.a $(CC) $(CFLAGS) -o $@ $(XYMONOBJS) ../lib/xymonclient.a $(NETLIBS) $(LIBRTDEF) xymonlaunch: $(LAUNCHOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) -o $@ $(LAUNCHOBJS) ../lib/libxymon.a $(NETLIBS) $(LIBRTDEF) ../client/xymonlaunch: $(LAUNCHOBJS) ../lib/xymonclient.a $(CC) $(CFLAGS) -o $@ $(LAUNCHOBJS) ../lib/xymonclient.a $(NETLIBS) $(LIBRTDEF) xymoncmd: $(CMDOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) -o $@ $(CMDOBJS) ../lib/libxymon.a $(NETLIBS) $(LIBRTDEF) ../client/xymoncmd: $(CMDOBJS) ../lib/xymonclient.a $(CC) $(CFLAGS) -o $@ $(CMDOBJS) ../lib/xymonclient.a $(NETLIBS) $(LIBRTDEF) xymondigest: $(DIGESTOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) -o $@ $(DIGESTOBJS) ../lib/libxymon.a $(NETLIBS) $(LIBRTDEF) ../client/xymondigest: $(DIGESTOBJS) ../lib/xymonclient.a $(CC) $(CFLAGS) -o $@ $(DIGESTOBJS) ../lib/xymonclient.a $(NETLIBS) $(LIBRTDEF) xymon.exe: xymon.c ../lib/strfunc.c ../lib/errormsg.c ../lib/environ.c ../lib/stackio.c ../lib/timefunc.c ../lib/memory.c ../lib/sendmsg.c ../lib/holidays.c ../lib/rbtr.c ../lib/msort.c $(CC) $(CFLAGS) -c xymon.c $(CC) $(CFLAGS) -DXYMONTOPDIR=\"$(XYMONTOPDIR)\" -DXYMONLOGDIR=\"$(XYMONLOGDIR)\" -DXYMONHOSTNAME=\"$(XYMONHOSTNAME)\" -DXYMONHOSTIP=\"$(XYMONHOSTIP)\" -DXYMONHOSTOS=\"$(XYMONHOSTOS)\" -DBUILD_HOME=\"$(XYMONTOPDIR)/client\" -c ../lib/environ.c $(CC) $(CFLAGS) -c ../lib/strfunc.c $(CC) $(CFLAGS) -c ../lib/errormsg.c $(CC) $(CFLAGS) -c ../lib/stackio.c $(CC) $(CFLAGS) -c ../lib/timefunc.c $(CC) $(CFLAGS) -c ../lib/memory.c $(CC) $(CFLAGS) -c ../lib/sendmsg.c $(CC) $(CFLAGS) -c ../lib/holidays.c $(CC) $(CFLAGS) -c ../lib/rbtr.c $(CC) $(CFLAGS) -c ../lib/msort.c $(CC) $(CFLAGS) -c ../lib/misc.c ar cr xymonwin32.a environ.o strfunc.o errormsg.o stackio.o timefunc.o memory.o sendmsg.o holidays.o rbtr.o msort.o misc.o ranlib xymonwin32.a || echo "" $(CC) -o $@ xymon.o xymonwin32.a ################################################ # Default compilation rules ################################################ %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< clean: rm -f *.o *.a *~ $(PROGRAMS) $(CLIENTPROGRAMS) install: install-bin install-man install-bin: $(PROGRAMS) cp -fp $(PROGRAMS) $(INSTALLROOT)$(INSTALLBINDIR)/ cd $(INSTALLROOT)$(INSTALLBINDIR)/; rm -f bb bbcmd bbhostgrep bbhostshow; ln -s xymon bb; ln -s xymoncmd bbcmd; ln -s xymongrep bbhostgrep; ln -s xymondigest bbdigest; ln -s xymoncfg bbhostshow install-man: mkdir -p $(INSTALLROOT)$(MANROOT)/man1 $(INSTALLROOT)$(MANROOT)/man5 $(INSTALLROOT)$(MANROOT)/man7 $(INSTALLROOT)$(MANROOT)/man8 cp -fp *.1 $(INSTALLROOT)$(MANROOT)/man1/ cp -fp *.5 $(INSTALLROOT)$(MANROOT)/man5/ cp -fp *.7 $(INSTALLROOT)$(MANROOT)/man7/ cp -fp *.8 $(INSTALLROOT)$(MANROOT)/man8/ xymon-4.3.7/common/logfetch.10000664000175000017500000000431611671641417015404 0ustar henrikhenrik.TH LOGFETCH 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME logfetch \- Xymon client data collector .SH SYNOPSIS .B "logfetch CONFIGFILE STATUSFILE" .SH DESCRIPTION \fBlogfetch\fR is part of the Xymon client. It is responsible for collecting data from logfiles, and other file-related data, which is then sent to the Xymon server for analysis. logfetch uses a configuration file, which is automatically retrieved from the Xymon server. There is no configuration done locally. The configuration file is usually stored in the \fB$XYMONHOME/tmp/logfetch.cfg\fR file, but editing this file has no effect since it is re-written with data from the Xymon server each time the client runs. logfetch stores information about what parts of the monitored logfiles have been processed already in the \fB$XYMONHOME/tmp/logfetch.status\fR file. This file is an internal file used by logfetch, and should not be edited. If deleted, it will be re-created automatically. .SH SECURITY logfetch needs read access to the logfiles it should monitor. If you configure monitoring of files or directories through the "file:" and "dir:" entries in .I client-local.cfg(5) then logfetch will require at least read-acces to the directory where the file is located. If you request checksum calculation for a file, then it must be readable by the Xymon client user. Do \fBNOT\fR install logfetch as suid-root. There is no way that logfetch can check whether the configuration file it uses has been tampered with, so installing logfetch with suid-root privileges could allow an attacker to read any file on the system by using a hand-crafted configuration file. In fact, logfetch will attempt to remove its own suid-root setup if it detects that it has been installed suid-root. .SH "ENVIRONMENT VARIABLES" .IP DU Command used to collect information about the size of directories. By default, this is the command \fBdu -k\fR. If the local du-command on the client does not recognize the "-k" option, you should set the DU environment variable in the \fB$XYMONHOME/etc/xymonclient.cfg\fR file to a command that does report directory sizes in kilobytes. .SH FILES .IP $XYMONHOME/tmp/logfetch.cfg .IP $XYMONHOME/tmp/logfetch.status .SH "SEE ALSO" xymon(7), analysis.cfg(5) xymon-4.3.7/common/orcaxymon.10000664000175000017500000000226611671641417015632 0ustar henrikhenrik.TH ORCAXYMON 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME orcaxymon \- Xymon client utility to grab data from ORCA .SH SYNOPSIS .B "orcaxymon --orca=PREFIX [options]" .SH NOTICE This utility is included in the client distribution for Xymon 4.2. However, the backend module to parse the data it sends it \fBNOT\fR included in Xymon 4.2. It is possible to use the generic Xymon NCV data handler in .I xymond_rrd(8) to process ORCA data, if you have an urgent need to do so. .SH DESCRIPTION \fBorcaxymon\fR is an add-on tool for the Xymon client. It is used to grab data collected by the ORCA data collection tool (orcallator.se), and send it to the Xymon server in NCV format. orcaxymon should run from the client .I xymonlaunch(8) utility, i.e. there must be an entry in the .I clientlaunch.cfg(5) file for orcaxymon. .SH OPTIONS .IP "--orca=PREFIX" The filename prefix for the ORCA data log. Typically this is the directory for the ORCA logs, followed by "orcallator". The actual filename for the ORCA logs include a timestamp and sequence number, e.g. "orcallator-2006-06-20-000". This option is required. .IP "--debug" Enable debugging output. .SH "SEE ALSO" xymon(7), clientlaunch.cfg(5) xymon-4.3.7/common/xymon.70000664000175000017500000005313611671641417014775 0ustar henrikhenrik.TH XYMON 7 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME Xymon \- Introduction to Xymon .SH OVERVIEW Xymon is a tool for monitoring the health of your networked servers and the applications running on them. It provides a simple, intuitive way of checking the health of your systems from a web browser, and can also alert you to any problems that arise through alarms sent as e-mail, SMS messages, via a pager or by other means. Xymon is Open Source software, licensed under the GNU GPL. This means that you are free to use Xymon as much as you like, and you are free to re-distribute it and change it to suit your specific needs. However, if you change it then you must make your changes available to others on the same terms that you received Xymon originally. See the file COPYING in the Xymon source-archive for details. Xymon was called "Hobbit" until November 2008, when it was renamed to Xymon. This was done because the name "Hobbit" is trademarked. Xymon initially began life as an enhancement to Big Brother called "bbgen". Over a period of 5 years, Xymon has evolved from a small add-on to a full-fledged monitoring system with capabilities far exceeding what was in the original Big Brother package. Xymon does still maintain some compatibility with Big Brother, so it is possible to migrate from Big Brother to Xymon without too much trouble. Migrating to Xymon will give you a significant performance boost, and provide you with much more advanced monitoring. The Xymon tools are designed for installations that need to monitor a large number of hosts, with very little overhead on the monitoring server. Monitoring of thousands of hosts with a single Xymon server is possible - it was developed to handle just this task. .SH FEATURES These are some of the core features in Xymon: .IP "Monitoring of hosts and networks" Xymon collects information about your systems in two ways: From querying network services (Web, LDAP, DNS, Mail etc.), or from scripts that run either on the Xymon server or on the systems you monitor. The Xymon package includes a \fbXymon client\fR which you can install on the servers you monitor; it collects data about the CPU-load, disk- and memory-utilization, log files, network ports in use, file- and directory-information and more. All of the information is stored inside Xymon, and you can define conditions that result in alerts, e.g. if a network service stops responding, or a disk fills up. .IP "Centralized configuration" All configuration of Xymon is done on the Xymon server. Even when monitoring hundreds or thousands of hosts, you can control their configuration centrally on the Xymon server - so there is no need for you to login to a system just to change e.g. which processes are monitored. .IP "Works on all major platforms" The Xymon server works on all Unix-like systems, including Linux, Solaris, FreeBSD, AIX, HP-UX and others. The Xymon client supports all major Unix platforms, and there are other Open Source projects - e.g. BBWin, see http://bbwin.sourceforge.net/ - providing support for Microsoft Windows based systems. .IP "A simple, intuitive web-based front-end" "Green is good, red is bad". Using the Xymon web pages is as simple as that. The hosts you monitor can be grouped together in a way that makes sense in your organisation and presented in a tree-structure. The web pages use many techniques to convey information about the monitored systems, e.g. different icons can be used for recently changed statuses; links to sub-pages can be listed in multiple columns; different icons can be used for dial-up-tests or reverse-tests; selected columns can be dropped or unconditionally included on the web pages to eliminate unwanted information, or always include certain information; user-friendly names can be shown for hosts regardless of their true hostname. You can also have automatic links to on-line documentation, so information about your critical systems is just a click away. .IP "Integrated trend analysis, historical data and SLA reporting" Xymon stores trend- and availability-information about everything it monitors. So if you need to look at how your systems behave over time, Xymon has all of the information you need: Whether it is response times of your web pages during peak hours, the CPU utilization over the past 4 weeks, or what the availability of a site was compared to the SLA - it's all there inside Xymon. All measurements are tracked and made available in time-based graphs. When you need to drill down into events that have occurred, Xymon provides a powerful tool for viewing the event history for each status log, with overviews of when problems have occurred during the past and easy-to-use zoom-in on the event. For SLA reporting, You can configure planned downtime, agreed service availability level, service availability time and have Xymon generate availability reports directly showing the actual availability measured against the agreed SLA. Such reports of service availability can be generated on-the-fly, or pre-generated e.g. for monthly reporting. .IP "Role-based views" You can have multiple different views of the same hosts for different parts of the organisation, e.g. one view for the hardware group, and another view for the webmasters - all of them fed by the same test tools. If you have a dedicated Network Operations Center, you can configure precisely which alerts will appear on their monitors - e.g. a simple anomaly in the system log file need not trigger a call to 3rd-level support at 2 AM, but if the on-line shop goes down you do want someone to respond immediately. So you put the web-check for the on-line shop on the NOC monitor page, and leave out the log-file check. .IP "Also for the techies" The Xymon user-interface is simple, but engineers will also find lots of relevant information. E.g. the data that clients report to Xymon contain the raw output from a number of system commands. That information is available directly in Xymon, so an administrator no longer needs to login to a server to get an overview of how it is behaving - the very commands they would normally run have already been performed, and the results are on-line in Xymon. .IP "Easy to adapt to your needs" Xymon includes a lot of tests in the core package, but there will always be something specific to your setup that you would like to watch. Xymon allows you to write test scripts in your favorite scripting language and have the results show up as regular status columns in Xymon. You can trigger alerts from these, and even track trends in graphs just by a simple configuration setting. .IP "Real network service tests" The network test tool knows how to test most commonly used protocols, including HTTP, SMTP (e-mail), DNS, LDAP (directory services), and many more. When checking websites, it is possible to not only check that the web server is responding, but also that the response looks correct by matching the response against a pre-defined pattern or a check-sum. So you can test that a network service is really working and supplying the data you expect - not just that the service is running. Protocols that use SSL encryption such as https web sites are fully supported, and while checking such services the network tester will automatically run a check of the validity of the SSL server certificate, and warn about certificates that are about to expire. .IP "Highly configurable alerts" You want to know when something breaks. But you don't want to get flooded with alerts all the time. Xymon lets you define several criteria for when to send out an alert, so you only get alerts when there is really something that needs your attention right away. While you are handling an incident, you can tell Xymon about it so it stops sending more alerts, and so that everyone else can check with Xymon and know that the problem is being taken care of. .IP "Combined super-tests and test inter-dependencies" If a single test is not enough, combination tests can be defined that combine the result of several tests to a single status-report. So if you need to monitor that at least 3 out of 5 servers are running at any time, Xymon can do that for you and generate the necessary availability report. Tests can also be configured to depend on each other, so that when a critical router goes down you will get alerts only for the router - and not from the 200 hosts behind the router. .SH SECURITY All of the Xymon server tools run under an unprivileged user account. A single program - the .I xymonping(1) network connectivity tester - must be installed setuid-root, but has been written so that it drops all root privileges immediately after performing the operation that requires root privileges. It is recommended that you setup a dedicated account for Xymon. Communications between the Xymon server and Xymon clients use the Big Brother TCP port 1984. If the Xymon server is located behind a firewall, it must allow for inbound connections to the Xymon server on tcp port 1984. Normally, Xymon clients - i.e. the servers you are monitoring - must be permitted to connect to the Xymon server on this port. However, if that is not possible due to firewall policies, then Xymon includes the .I xymonfetch(8) and .I msgcache(8) tools to allows for a pull-style way of collecting data, where it is the Xymon server that initiates connections to the clients. The Xymon web pages are dynamically generated through CGI programs. Access to the Xymon web pages is controlled through your web server access controls, e.g. you can require a login through some form of HTTP authentication. .SH DEMONSTRATION SITE A site running this software can be seen at http://www.xymon.com/ .SH PREREQUISITES AND INSTALLATION You will need a Unix-like system (Linux, Solaris, HP-UX, AIX, FreeBSD, Mac OS X or similar) with a web server installed. You will also need a C compiler and some additional libraries, but many systems come with the required development tools and libraries pre-installed. The required libraries are: .sp .BR RRDtool This library is used to store and present trend-data. It is required. .sp .BR libpcre This library is used for advanced pattern-matching of text strings in configuration files. This library is required. .sp .BR OpenSSL This library is used for communication with SSL-enabled network services. Although optional, it is recommended that you install this for Xymon since many network tests do use SSL. .sp .BR OpenLDAP This library is used for testing LDAP servers. Use of this is optional. For more detailed information about Xymon system requirements and how to install Xymon, refer to the on-line documentation "Installing Xymon" available from the Xymon web server (via the "Help" menu), or from the "docs/install.html" file in the Xymon source archive. .SH "SUPPORT and MAILING LISTS" xymon@xymon.com is an open mailing list for discussions about Xymon. If you would like to participate, send an e-mail to \fBxymon-subscribe@xymon.com\fR to join the list, or visit http://lists.xymon.com/mailman/listinfo/xymon . An archive of the mailing list is available at http://lists.xymon.com/archive/ If you just want to be notified of new releases of Xymon, please subscribe to the xymon-announce mailing list. This is a moderated list, used only for announcing new Xymon releases. To be added to the list, send an e-mail to \fBxymon-announce-subscribe@xymon.com\fR or visit http://lists.xymon.com/mailman/listinfo/xymon-announce . .SH XYMON SERVER TOOLS These tools implement the core functionality of the Xymon server: .I xymond(8) is the core daemon that collects all reports about the status of your hosts. It uses a number of helper modules to implement certain tasks such as updating log files and sending out alerts: xymond_client, xymond_history, xymond_alert and xymond_rrd. There is also a xymond_filestore module for compatibility with Big Brother. .I xymond_channel(8) Implements the communication between the Xymon daemon and the other Xymon server modules. .I xymond_history(8) Stores historical data about the things that Xymon monitors. .I xymond_rrd(8) Stores trend data, which is used to generate graphs of the data monitored by Xymon. .I xymond_alert(8) handles alerts. When a status changes to a critical state, this module decides if an alert should be sent out, and to whom. .I xymond_client(8) handles data collected by the Xymon clients, analyzes the data and feeds back several status updates to Xymon to build the view of the client status. .I xymond_hostdata(8) stores historical client data when something breaks. E.g. when a web page stops responding xymond_hostdata will save the latest client data, so that you can use this to view a snapshot of how the system state was just prior to it failing. .SH XYMON NETWORK TEST TOOLS These tools are used on servers that execute tests of network services. .I xymonping(1) performs network connectivity (ping) tests. .I xymonnet(1) runs the network service tests. .I xymonnet-again.sh(1) is an extension script for re-doing failed network tests with a higher frequency than the normal network tests. This allows Xymon to pick up the recovery of a network service as soon as it happens, resulting in less downtime being recorded. .SH XYMON TOOLS HANDLING THE WEB USER-INTERFACE These tools take care of generating and updating the various Xymon web-pages. .I xymongen(1) takes care of updating the Xymon web pages. .I svcstatus.cgi(1) This CGI program generates an HTML view of a single status log. It is used to present the Xymon status-logs. .I showgraph.cgi(1) This CGI program generates graphs of the trend-data collected by Xymon. .I hostgraphs.cgi(1) When you want to combine multiple graphs into one, this CGI lets you combine graphs so you can e.g. compare the load on all of the nodes in your server farm. .I criticalview.cgi(1) Generates the Critical Systems view, based on the currently critical systems and the configuration of what systems and services you want to monitor when. .I history.cgi(1) This CGI program generates a web page with the most recent history of a particular host+service combination. .I eventlog.cgi(1) This CGI lets you view a log of events that have happened over a period of time, for a single host or test, or for multiple systems. .I ack.cgi(1) This CGI program allows a user to acknowledge an alert he received from Xymon about a host that is in a critical state. Acknowledging an alert serves two purposes: First, it stops more alerts from being sent so the technicians are not bothered wit more alerts, and secondly it provides feedback to those looking at the Xymon web pages that the problem is being handled. .I xymon-mailack(8) is a tool for processing acknowledgments sent via e-mail, e.g. as a response to an e-mail alert. .I enadis.cgi(8) is a CGI program to disable or re-enable hosts or individual tests. When disabling a host or test, you stop alarms from being sent and also any outages do not affect the SLA calculations. So this tool is useful when systems are being brought down for maintenance. .I findhost.cgi(1) is a CGI program that finds a given host in the Xymon web pages. As your Xymon installation grows, it can become difficult to remember exactly which page a host is on; this CGI script lets you find hosts easily. .I report.cgi(1) This CGI program triggers the generation of Xymon availability reports, using .I xymongen(1) as the reporting back-end engine. .I reportlog.cgi(1) This CGI program generates the detailed availability report for a particular host+service combination. .I snapshot.cgi(1) is a CGI program to build the Xymon web pages in a "snapshot" mode, showing the look of the web pages at a particular point in time. It uses .I xymongen(1) as the back-end engine. .I statusreport.cgi(1) is a CGI program reporting test results for a single status but for several hosts. It is used to e.g. see which SSL certificates are about to expire, across all of the Xymon web pages. .I csvinfo.cgi(1) is a CGI program to present information about a host. The information is pulled from a CSV (Comma Separated Values) file, which is easily exported from any spreadsheet or database program. .SH CLIENT-SIDE TOOLS .I logfetch(1) is a utility used by the Xymon Unix client to collect information from log files on the client. It can also monitor various other file-related data, e.g. file meta-data or directory sizes. .I clientupdate(1) Is used on Xymon clients, to automatically update the client software with new versions. Through this tool, updates of the client software can happen without an administrator having to logon to the server. .I msgcache(8) This tool acts as a mini Xymon server to the client. It stores client data internally, so that the .I xymonfetch(8) utility can pick it up later and send it to the Xymon server. It is typically used on hosts that cannot contact the Xymon server directly due to network- or firewall-restrictions. .SH XYMON COMMUNICATION TOOLS These tools are used for communications between the Xymon server and the Xymon clients. If there are no firewalls then they are not needed, but it may be necessary due to network or firewall issues to make use of them. .I xymonproxy(8) is a proxy-server that forwards Xymon messages between clients and the Xymon server. The clients must be able to talk to the proxy, and the proxy must be able to talk to the Xymon server. .I xymonfetch(8) is used when the client is not able to make outbound connections to neither xymonproxy nor the Xymon server (typically, for clients located in a DMZ network zone). Together with the .I msgcache(8) utility running on the client, the Xymon server can contact the clients and pick up their data. .SH OTHER TOOLS .I xymonlaunch(8) is a program scheduler for Xymon. It acts as a master program for running all of the Xymon tools on a system. On the Xymon server, it controls running all of the server tasks. On a Xymon client, it periodically launches the client to collect data and send them to the Xymon server. .I xymon(1) is the tool used to communicate with the Xymon server. It is used to send status reports to the Xymon server, through the custom Xymon/BB protocol, or via HTTP. It can be used to query the state of tests on the central Xymon server and retrieve Xymon configuration files. The server-side script .I xymoncgimsg.cgi(1) used to receive messages sent via HTTP is also included. .I xymoncmd(1) is a wrapper for the other Xymon tools which sets up all of the environment variables used by Xymon tools. .I xymongrep(1) is a utility for use by Xymon extension scripts. It allows an extension script to easily pick out the hosts that are relevant to a script, so it need not parse a huge hosts.cfg file with lots of unwanted test-specifications. .I xymoncfg(1) is a utility to dump the full .I hosts.cfg(5) file following any "include" statements. .I xymondigest(1) is a utility to compute message digest values for use in content checks that use digests. .I combostatus(1) is an extension script for the Xymon server, allowing you to build complicated tests from simpler Xymon test results. E.g. you can define a test that uses the results from testing your web server, database server and router to have a single test showing the availability of your enterprise web application. .I trimhistory(8) is a tool to trim the Xymon history logs. It will remove all log entries and optionally also the individual status-logs for events that happened before a given time. .SH VERSIONS Version 1 of bbgen was released in November 2002, and optimized the web page generation on Big Brother servers. Version 2 of bbgen was released in April 2003, and added a tool for performing network tests. Version 3 of bbgen was released in September 2004, and eliminated the use of several external libraries for network tests, resulting in a significant performance improvement. With version 4.0 released on March 30 2005, the project was de-coupled from Big Brother, and the name changed to Hobbit. This version was the first full implementation of the Hobbit server, but it still used the data collected by Big Brother clients for monitoring host metrics. Version 4.1 was released in July 2005 included a simple client for Unix. Log file monitoring was not implemented. Version 4.2 was released in July 2006, and includes a fully functional client for Unix. Version 4.3 was released in November 2010, and implemented the renaming of the project to Xymon. This name was already introduced in 2008 with a patch version of 4.2, but with version 4.3.0 this change of names was fully implemented. .SH COPYRIGHT Xymon is .br Copyright (C) 2002-2011 Henrik Storner .br Parts of the Xymon sources are from public-domain or other freely available sources. These are the the Red-Black tree implementation, and the MD5-, SHA1- and RIPEMD160-implementations. Details of the license for these is in the README file included with the Xymon sources. All other files are released under the GNU General Public License version 2, with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. See the file COPYING for details. .SH "SEE ALSO" xymond(8), xymond_channel(8), xymond_history(8), xymond_rrd(8), xymond_alert(8), xymond_client(8), xymond_hostdata(8), xymonping(1), xymonnet(1), xymonnet-again.sh(1), xymongen(1), svcstatus.cgi(1), showgraph.cgi(1), hostgraphs.cgi(1), criticalview.cgi(1), history.cgi(1), eventlog.cgi(1), ack.cgi(1), xymon-mailack(8), enadis.cgi(8), findhost.cgi(1), report.cgi(1), reportlog.cgi(1), snapshot.cgi(1), statusreport.cgi(1), csvinfo.cgi(1), logfetch(1), clientupdate(1), msgcache(8), xymonproxy(8), xymonfetch(8), xymonlaunch(8), xymon(1), xymoncgimsg.cgi(1), xymoncmd(1), xymongrep(1), xymoncfg(1), xymondigest(1), combostatus(1), trimhistory(8), hosts.cfg(5), tasks.cfg(5), xymonserver.cfg(5), alerts.cfg(5), analysis.cfg(5), client-local.cfg(5) xymon-4.3.7/common/xymoncmd.c0000664000175000017500000001000511615341300015503 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon application launcher. */ /* */ /* This is used to launch a single Xymon application, with the environment */ /* that would normally be established by xymonlaunch. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymoncmd.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include "libxymon.h" static void xymon_default_envs(char *envfn) { FILE *fd; char buf[1024]; char *evar; char *homedir, *p; if (getenv("MACHINEDOTS") == NULL) { fd = popen("uname -n", "r"); if (fd) { char *p; fgets(buf, sizeof(buf), fd); p = strchr(buf, '\n'); if (p) *p = '\0'; pclose(fd); } else strcpy(buf, "localhost"); evar = (char *)malloc(strlen(buf) + 13); sprintf(evar, "MACHINEDOTS=%s", buf); putenv(evar); } xgetenv("MACHINE"); if (getenv("SERVEROSTYPE") == NULL) { char *p; fd = popen("uname -s", "r"); if (fd) { fgets(buf, sizeof(buf), fd); pclose(fd); } else strcpy(buf, "unix"); for (p=buf; (*p); p++) *p = (char) tolower((int)*p); evar = (char *)malloc(strlen(buf) + 10); sprintf(evar, "SERVEROSTYPE=%s", buf); putenv(evar); } if (getenv("XYMONCLIENTHOME") == NULL) { homedir = strdup(envfn); p = strrchr(homedir, '/'); if (p) { *p = '\0'; if (strlen(homedir) > 4) { p = homedir + strlen(homedir) - 4; if (strcmp(p, "/etc") == 0) { *p = '\0'; evar = (char *)malloc(20 + strlen(homedir)); sprintf(evar, "XYMONCLIENTHOME=%s", homedir); putenv(evar); } } } } } int main(int argc, char *argv[]) { int argi; char *cmd = NULL; char **cmdargs = NULL; int argcount = 0; char *envfile = NULL; char *envarea = NULL; char envfn[PATH_MAX]; cmdargs = (char **) calloc(argc+2, sizeof(char *)); for (argi=1; (argi < argc); argi++) { if ((argcount == 0) && (strcmp(argv[argi], "--debug") == 0)) { debug = 1; } else if ((argcount == 0) && (argnmatch(argv[argi], "--env="))) { char *p = strchr(argv[argi], '='); envfile = strdup(p+1); } else if ((argcount == 0) && (argnmatch(argv[argi], "--area="))) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if ((argcount == 0) && (strcmp(argv[argi], "--version") == 0)) { fprintf(stdout, "Xymon version %s\n", VERSION); return 0; } else { if (argcount == 0) { cmdargs[0] = cmd = strdup(expand_env(argv[argi])); argcount = 1; } else cmdargs[argcount++] = strdup(expand_env(argv[argi])); } } if (!envfile) { struct stat st; sprintf(envfn, "%s/etc/xymonserver.cfg", xgetenv("XYMONHOME")); if (stat(envfn, &st) == -1) sprintf(envfn, "%s/etc/xymonclient.cfg", xgetenv("XYMONHOME")); errprintf("Using default environment file %s\n", envfn); /* Make sure SERVEROSTYPE, MACHINEDOTS and MACHINE are setup for our child */ xymon_default_envs(envfn); loadenv(envfn, envarea); } else { /* Make sure SERVEROSTYPE, MACHINEDOTS and MACHINE are setup for our child */ xymon_default_envs(envfile); loadenv(envfile, envarea); } /* Go! */ if (cmd == NULL) cmd = cmdargs[0] = "/bin/sh"; execvp(cmd, cmdargs); /* Should never go here */ errprintf("execvp() failed: %s\n", strerror(errno)); return 0; } xymon-4.3.7/README.CLIENT0000664000175000017500000001164411535462534014076 0ustar henrikhenrikUnix client for Xymon ====================== As of version 4.1, Xymon ships with a native client for most Unix-based systems. The Xymon client will generate status columns for: * cpu : CPU utilisation * disk : Filesystem (disk) utilisation * files : File- and directory attributes and sizes * memory : Memory and Swap utilisation * msgs : Log file messages * ports : TCP/IP Network connections * procs : Processes It will also feed data to generate some graphs: * ifstat : Raw traffic data for network interfaces * netstat: TCP/IP statistics * vmstat : Various performance counters (OS-specific) In the default setup, all configuration of disk thresholds, load limits, which processes to monitor etc. is done on the Xymon server, NOT on the client. This is to allow centralized configuration of the monitored systems. If you prefer to have the client configuration done locally on each of the systems you monitor, this is an option when building the client. It is possible to have a mix of systems, with some systems configured locally, and others that use the central configuration. Note: The locally-configured client requires the PCRE libraries to be installed on the client host. The Xymon client is released under the GNU GPL, version 2 or later. See the file COPYING for details. Installation ============ Building the client package requires a working C compiler and GNU make on the target platform. None of the extra libraries needed for building Xymon are used by the client - so a plain C compiler installation with GNU make is all that is needed. To build the client: - create a "xymon" userid on the system (not required, but recommended). - extract the Xymon source archive - cd to the xymon-X.X directory - run "./configure --client; make" - as root, run "make install" The client installation is kept entirely within the "xymon" users' home-directory. All client-related files are in the ~xymon/client/ directory. If convenient, this directory can be copied directly to other systems of the same type, so you need not build the client from source on all systems. Running it ========== To start the client, su to the "xymon" user, then run $HOME/client/runclient.sh start You should arrange for your boot-time scripts to run this command at startup. Client configuration ==================== All of the normal configuration is done on the Xymon SERVER. See the analysis.cfg(5) and client-local.cfg(5) man- pages on the Xymon server. Hostname detection ------------------ The client reports to Xymon using the hostname taken from the "uname -n" command output. Whether this provides a fully qualified DNS name (myserver.foo.com) or a simple hostname (myserver) varies a lot. If your client gets it wrong, you can override the default with the "--hostname=CLIENT.HOST.NAME" option to the runclient.sh startup command. OS detection on Linux systems ----------------------------- The client normally determines the operating system auto- matically from the "uname -s" output. This results in all Linux systems reporting as "Linux" - which causes the vmstat graphs to fail for certain types of systems: - Systems running Linux kernels 2.2.x (e.g. Debian Woody) Must report "linux22" instead. - Red Hat Enterprise Linux 2.1 Must report "linux22" instead. - Red Hat Enterprise Linux 3 update 1 The default is adequate - Red Hat Enterprise Linux 3 update 2 or later Must report "rhel3" instead. - Red Hat Enterprise Linux 4 update 2 and later The default is adequate To override the default, start the client using the "--os=OSNAME" option to the runclient.sh startup command. (Thanks to Thomas Seglard Enata for providing the about the various Red Hat versions). Extension scripts ----------------- The file client/etc/clientlaunch.cfg configures the "xymonlaunch" used to run the client scripts. If you need to run extension scripts for your client, you can add them to this file. The environment variables commonly used by Big Brother-based extensions are made available by xymonlaunch for the scripts, so in most cases extensions written for the Big Brother client will work without modifications on Xymon. Each script should have a separate section in the file, like the default one that runs the core client: [myextension] ENVFILE /usr/lib/xymon/client/etc/xymonclient.cfg CMD /usr/lib/xymon/client/ext/myscript.sh INTERVAL 5m LOGFILE /usr/lib/xymon/client/logs/myscript.log See the tasks.cfg(5) man-page for a full description of this file. Installing on multiple systems ============================== After building and installing the client on one system, you can copy the client installation to other systems. You need only copy the directory structure with the "client" directory. The client does not have any hardcoded file- or directory-paths embedded in it, so you can put the client files anywhere you like, as long as you preserve the directory structure below the "client" directory. Henrik Stoerner, 2006-Apr-23 xymon-4.3.7/xymonproxy/0000775000175000017500000000000011671641716014511 5ustar henrikhenrikxymon-4.3.7/xymonproxy/xymonproxy.80000664000175000017500000001731411671641417017062 0ustar henrikhenrik.TH XYMONPROXY 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymonproxy \- Xymon message proxy .SH SYNOPSIS .B "xymonproxy [options] --server=$XYMSRV" .SH DESCRIPTION .I xymonproxy(8) is a proxy for forwarding Xymon messages from one server to another. It will typically be needed if you have clients behind a firewall, so they cannot send status messages to the Xymon server directly. xymonproxy serves three purposes. First, it acts as a regular proxy server, allowing clients that cannot connect directly to the Xymon servers to send data. Although xymonproxy is optimized for handling status messages, it will forward all types of messages, including notes- and data-messages. .br Second, it acts as a buffer, smoothing out peak loads if many clients try to send status messages simultaneously. xymonproxy can absorb messages very quickly, but will queue them up internally and forward them to the Xymon server at a reasonable pace. .br Third, xymonproxy merges small "status" messages into larger "combo" messages. This can dramatically decrease the number of connections that need to go from xymonproxy to the Xymon server. The merging of messages causes "status" messages to be delayed for up to 0.25 seconds before being sent off to the Xymon server. .SH OPTIONS .IP "--server=SERVERIP[:PORT][,SERVER2IP[:PORT]]" Specifies the IP-address and optional portnumber where incoming messages are forwarded to. The default portnumber is 1984, the standard Xymon port number. If you have setup the normal Xymon environment, you can use "--server=$XYMSRV". Up to 3 servers can be specified; incoming messages are sent to all of them (except "config", "query" and "download" messages, which go to the LAST server only). If you have Xymon clients sending their data via this proxy, note that the clients will receive their configuration data from the LAST of the servers listed here. This option is required. .IP "--listen=LOCALIP[:PORT]" Specifies the IP-adress where xymonproxy listens for incoming connections. By default, xymonproxy listens on all IP-adresses assigned to the host. If no portnumber is given, port 1984 will be used. .IP "--timeout=N" Specifies the number of seconds after which a connection is aborted due to a timeout. Default: 10 seconds. .IP "--report=[PROXYHOSTNAME.]SERVICE" If given, this option causes xymonproxy to send a status report every 5 minutes to the Xymon server about itself. If you have set the standard Xymon environment, you can use "--report=xymonproxy" to have xymonproxy report its status to a "xymonproxy" column in Xymon. The default for PROXYHOSTNAME is the $MACHINE environment variable, i.e. the hostname of the server running xymonproxy. See REPORT OUTPUT below for an explanation of the report contents. .IP "--lqueue=N" Size of the listen-queue where incoming connections can queue up before being processed. This should be large to accomodate bursts of activity from clients. Default: 512. .IP "--daemon" Run in daemon mode, i.e. detach and run as a background proces. This is the default. .IP "--no-daemon" Runs xymonproxy as a foreground proces. .IP "--pidfile=FILENAME" Specifies the location of a file containing the proces-ID of the xymonproxy daemon proces. Default: /var/run/xymonproxy.pid. .IP "--logfile=FILENAME" Sends all logging output to the specified file instead of stderr. .IP "--log-details" Log details (IP-address, message type and hostname) to the logfile. This can also be enabled and disabled at run-time by sending the xymonproxy proces a SIGUSR1 signal. .IP "--debug" Enable debugging output. .SH "REPORT OUTPUT" If enabled via the "--report" option, xymonproxy will send a status message about itself to the Xymon server once every 5 minutes. The status message includes the following information: .IP "Incoming messages" The total number of connections accepted from clients since the proxy started. The "(N msgs/second)" is the average number of messages per second over the past 5 minutes. .IP "Outbound messages" The total number of messages sent to the Xymon server. Note that this is probably smaller than the number of incoming messages, since xymonproxy merges messages before sending them. .IP "Incoming - Combo messages" The number of "combo" messages received from a client. .IP "Incoming - Status messages" The number of "status" messages received from a client. xymonproxy attempts to merge these into "combo" messages. The "Messages merged" is the number of "status" messages that were merged into a combo message, the "Resulting combos" is the number of "combo" messages that resulted from the merging. .IP "Incoming - Other messages" The number of other messages (data, notes, ack, query, ...) messages received from a client. .IP "Proxy ressources - Connection table size" This is the number of connection table slots in the proxy. This measures the number of simultaneously active requests that the proxy has handled, and so gives an idea about the peak number of clients that the proxy has handled simultaneously. .IP "Proxy ressources - Buffer space" This is the number of KB memory allocated for network buffers. .IP "Timeout details - reading from client" The number of messages dropped because reading the message from the client timed out. .IP "Timeout details - connecting to server" The number of messages dropped, because a connection to the Xymon server could not be established. .IP "Timeout details - sending to server" The number of messages dropped because the communication to the Xymon server timed out after a connection was established. .IP "Timeout details - recovered" When a timeout happens while sending the status message to the server, xymonproxy will attempt to recover the message and retry sending it to the server after waiting a few seconds. This number is the number of messages that were recovered, and so were not lost. .IP "Timeout details - reading from server" The number of response messages that timed out while attempting to read them from the server. Note that this applies to the "config" and "query" messages only, since all other message types do not get any response from the servers. .IP "Timeout details - sending to client" The number of response messages that timed out while attempting to send them to the client. Note that this applies to the "config" and "query" messages only, since all other message types do not get any response from the servers. .IP "Average queue time" The average time it took the proxy to process a message, calculated from the messages that have passed through the proxy during the past 5 minutes. This number is computed from the messages that actually end up establishing a connection to the Xymon server, i.e. status messages that were combined into combo-messages do not go into the calculation - if they did, it would reduce the average time, since it is faster to merge messages than send them out over the network. .SH "" If you think the numbers do not add up, here is how they relate. The "Incoming messages" should be equal to the sum of the "Incoming Combo/Status/Page/Other messages", or slightly more because messages in transit are not included in the per-type message counts. The "Outbound messages" should be equal to sum of the "Incoming Combo/Page/Other messages", plus the "Resulting combos" count, plus "Incoming Status messages" minus "Messages merged" (this latter number is the number of status messages that were NOT merged into combos, but sent directly). The "Outbound messages" may be slightly lower than that, because messages in transit are not included in the "Outbound messages" count until they have been fully sent. .SH SIGNALS .IP SIGHUP Re-opens the logfile, e.g. after it has been rotated. .IP SIGTERM Shut down the proxy. .IP SIGUSR1 Toggles logging of individual messages. .SH "SEE ALSO" xymon(1), xymond(1), xymon(7) xymon-4.3.7/xymonproxy/xymoncgimsg.c0000664000175000017500000000260411615341300017203 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon CGI proxy. */ /* */ /* This CGI can gateway a Xymon message sent via HTTP PORT to a Xymon */ /* server running on the local host. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymoncgimsg.c 6712 2011-07-31 21:01:52Z storner $"; #include "libxymon.h" int main(int argc, char *argv[]) { int result = 1; cgidata_t *cgidata = NULL; sendreturn_t *sres; cgidata = cgi_request(); if (cgidata) { printf("Content-Type: application/octet-stream\n\n"); sres = newsendreturnbuf(1, stdout); result = sendmessage(cgidata->value, "127.0.0.1", XYMON_TIMEOUT, sres); } return result; } xymon-4.3.7/xymonproxy/xymonproxy.c0000664000175000017500000010230411615341300017111 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message proxy. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymonproxy.c 6712 2011-07-31 21:01:52Z storner $"; #include "config.h" #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include /* Someday I'll move to GNU Autoconf for this ... */ #endif #include #include #include #include #include #include #include #include #include #include #include #include "version.h" #include "libxymon.h" enum phase_t { P_IDLE, P_REQ_READING, /* Reading request data */ P_REQ_READY, /* Done reading request from client */ P_REQ_COMBINING, P_REQ_CONNECTING, /* Connecting to server */ P_REQ_SENDING, /* Sending request data */ P_REQ_DONE, /* Done sending request data to server */ P_RESP_READING, P_RESP_READY, P_RESP_SENDING, P_RESP_DONE, P_CLEANUP }; char *statename[P_CLEANUP+1] = { "idle", "reading from client", "request from client OK", "request combining", "connecting to server", "sending to server", "request sent", "reading from server", "response from server OK", "sending to client", "response sent", "cleanup" }; typedef struct conn_t { enum phase_t state; int csocket; struct sockaddr_in caddr; struct in_addr *clientip, *serverip; int snum; int ssocket; int conntries, sendtries; int connectpending; time_t conntime; int madetocombo; struct timespec arrival; struct timespec timelimit; unsigned char *buf, *bufp, *bufpsave; unsigned int bufsize, buflen, buflensave; struct conn_t *next; } conn_t; #define MAX_SERVERS 3 #define CONNECT_TRIES 3 /* How many connect-attempts against the server */ #define CONNECT_INTERVAL 8 /* Seconds between each connection attempt */ #define SEND_TRIES 2 /* How many times to try sending a message */ #define BUFSZ_READ 2048 /* Minimum #bytes that must be free when read'ing into a buffer */ #define BUFSZ_INC 8192 /* How much to grow the buffer when it is too small */ #define MAX_OPEN_SOCKS 256 #define MINIMUM_FOR_COMBO 2048 /* To start merging messages, at least have 2 KB free */ #define MAXIMUM_FOR_COMBO 32768 /* Max. size of a combined message */ #define COMBO_DELAY 250000000 /* Delay before sending a combo message (in nanoseconds) */ int keeprunning = 1; time_t laststatus = 0; char *logfile = NULL; int logdetails = 0; unsigned long msgs_timeout_from[P_CLEANUP+1] = { 0, }; void sigmisc_handler(int signum) { switch (signum) { case SIGTERM: errprintf("Caught TERM signal, terminating\n"); keeprunning = 0; break; case SIGHUP: if (logfile) { freopen(logfile, "a", stdout); freopen(logfile, "a", stderr); errprintf("Caught SIGHUP, reopening logfile\n"); } break; case SIGUSR1: /* Toggle logging of details */ logdetails = (1 - logdetails); errprintf("Log details is %sabled\n", (logdetails ? "en" : "dis")); break; } } int overdue(struct timespec *now, struct timespec *limit) { if (now->tv_sec < limit->tv_sec) return 0; else if (now->tv_sec > limit->tv_sec) return 1; else return (now->tv_nsec >= limit->tv_nsec); } static int do_read(int sockfd, struct in_addr *addr, conn_t *conn, enum phase_t completedstate) { int n; if ((conn->buflen + BUFSZ_READ + 1) > conn->bufsize) { conn->bufsize += BUFSZ_INC; conn->buf = realloc(conn->buf, conn->bufsize); conn->bufp = conn->buf + conn->buflen; } n = read(sockfd, conn->bufp, (conn->bufsize - conn->buflen - 1)); if (n == -1) { /* Error - abort */ errprintf("READ error from %s: %s\n", inet_ntoa(*addr), strerror(errno)); msgs_timeout_from[conn->state]++; conn->state = P_CLEANUP; return -1; } else if (n == 0) { /* EOF - request is complete */ conn->state = completedstate; } else { conn->buflen += n; conn->bufp += n; *conn->bufp = '\0'; } return 0; } static int do_write(int sockfd, struct in_addr *addr, conn_t *conn, enum phase_t completedstate) { int n; n = write(sockfd, conn->bufp, conn->buflen); if (n == -1) { /* Error - abort */ errprintf("WRITE error to %s: %s\n", inet_ntoa(*addr), strerror(errno)); msgs_timeout_from[conn->state]++; conn->state = P_CLEANUP; return -1; } else if (n >= 0) { conn->buflen -= n; conn->bufp += n; if (conn->buflen == 0) { conn->state = completedstate; } } return 0; } void do_log(conn_t *conn) { char *rq, *eol, *delim; char savechar; rq = conn->buf+6; if (strncmp(rq, "combo\n", 6) == 0) rq += 6; eol = strchr(rq, '\n'); if (eol) *eol = '\0'; for (delim = rq; (*delim && isalpha((unsigned char) *delim)); delim++); for (; (*delim && isspace((unsigned char) *delim)); delim++); for (; (*delim && !isspace((unsigned char) *delim)); delim++); savechar = *delim; *delim = '\0'; errprintf("%s : %s\n", inet_ntoa(*conn->clientip), rq); *delim = savechar; if (eol) *eol = '\n'; } int main(int argc, char *argv[]) { int daemonize = 1; int timeout = 10; int listenq = 512; char *pidfile = "/var/run/xymonproxy.pid"; char *proxyname = NULL; char *proxynamesvc = "xymonproxy"; int sockcount = 0; int lsocket; struct sockaddr_in laddr; struct sockaddr_in xymonserveraddr[MAX_SERVERS]; int xymonservercount = 0; int opt; conn_t *chead = NULL; struct sigaction sa; /* Statistics info */ time_t startuptime = gettimer(); unsigned long msgs_total = 0; unsigned long msgs_total_last = 0; unsigned long msgs_combined = 0; unsigned long msgs_merged = 0; unsigned long msgs_delivered = 0; unsigned long msgs_status = 0; unsigned long msgs_combo = 0; unsigned long msgs_other = 0; unsigned long msgs_recovered = 0; struct timespec timeinqueue = { 0, 0 }; /* Dont save the output from errprintf() */ save_errbuf = 0; memset(&laddr, 0, sizeof(laddr)); inet_aton("0.0.0.0", (struct in_addr *) &laddr.sin_addr.s_addr); laddr.sin_port = htons(1984); laddr.sin_family = AF_INET; for (opt=1; (opt < argc); opt++) { if (argnmatch(argv[opt], "--listen=")) { char *locaddr, *p; int locport; locaddr = strchr(argv[opt], '=')+1; p = strchr(locaddr, ':'); if (p) { locport = atoi(p+1); *p = '\0'; } else locport = 1984; memset(&laddr, 0, sizeof(laddr)); laddr.sin_port = htons(locport); laddr.sin_family = AF_INET; if (inet_aton(locaddr, (struct in_addr *) &laddr.sin_addr.s_addr) == 0) { errprintf("Invalid listen address %s\n", locaddr); return 1; } } else if (argnmatch(argv[opt], "--server=") || argnmatch(argv[opt], "--bbdisplay=")) { char *ips, *ip1; int port1; ips = strdup(strchr(argv[opt], '=')+1); ip1 = strtok(ips, ","); while (ip1) { char *p; p = strchr(ip1, ':'); if (p) { port1 = atoi(p+1); *p = '\0'; } else port1 = 1984; memset(&xymonserveraddr[xymonservercount], 0, sizeof(xymonserveraddr[xymonservercount])); xymonserveraddr[xymonservercount].sin_port = htons(port1); xymonserveraddr[xymonservercount].sin_family = AF_INET; if (inet_aton(ip1, (struct in_addr *) &xymonserveraddr[xymonservercount].sin_addr.s_addr) == 0) { errprintf("Invalid remote address %s\n", ip1); } else { xymonservercount++; } if (p) *p = ':'; ip1 = strtok(NULL, ","); } xfree(ips); } else if (argnmatch(argv[opt], "--timeout=")) { char *p = strchr(argv[opt], '='); timeout = atoi(p+1); } else if (argnmatch(argv[opt], "--lqueue=")) { char *p = strchr(argv[opt], '='); listenq = atoi(p+1); } else if (strcmp(argv[opt], "--daemon") == 0) { daemonize = 1; } else if (strcmp(argv[opt], "--no-daemon") == 0) { daemonize = 0; } else if (argnmatch(argv[opt], "--pidfile=")) { char *p = strchr(argv[opt], '='); pidfile = strdup(p+1); } else if (argnmatch(argv[opt], "--logfile=")) { char *p = strchr(argv[opt], '='); logfile = strdup(p+1); } else if (strcmp(argv[opt], "--log-details") == 0) { logdetails = 1; } else if (argnmatch(argv[opt], "--report=")) { char *p1 = strchr(argv[opt], '=')+1; if (strchr(p1, '.') == NULL) { if (xgetenv("MACHINE") == NULL) { errprintf("Environment variable MACHINE is undefined\n"); return 1; } proxyname = strdup(xgetenv("MACHINE")); proxyname = (char *)realloc(proxyname, strlen(proxyname) + strlen(p1) + 1); strcat(proxyname, "."); strcat(proxyname, p1); proxynamesvc = strdup(p1); } else { proxyname = strdup(p1); proxynamesvc = strchr(proxyname, '.')+1; } } else if (strcmp(argv[opt], "--debug") == 0) { debug = 1; } else if (strcmp(argv[opt], "--version") == 0) { printf("xymonproxy version %s\n", VERSION); return 0; } else if (strcmp(argv[opt], "--help") == 0) { printf("xymonproxy version %s\n", VERSION); printf("\nOptions:\n"); printf("\t--listen=IP[:port] : Listen address and portnumber\n"); printf("\t--server=IP[:port] : Xymon server address and portnumber\n"); printf("\t--report=[HOST.]SERVICE : Sends a status message about proxy activity\n"); printf("\t--timeout=N : Communications timeout (seconds)\n"); printf("\t--lqueue=N : Listen-queue size\n"); printf("\t--daemon : Run as a daemon\n"); printf("\t--no-daemon : Do not run as a daemon\n"); printf("\t--pidfile=FILENAME : Save proces-ID of daemon to FILENAME\n"); printf("\t--logfile=FILENAME : Log to FILENAME instead of stderr\n"); printf("\t--debug : Enable debugging output\n"); printf("\n"); return 0; } } if (xymonservercount == 0) { errprintf("No Xymon server address given - aborting\n"); return 1; } /* Set up a socket to listen for new connections */ lsocket = socket(AF_INET, SOCK_STREAM, 0); if (lsocket == -1) { errprintf("Cannot create listen socket (%s)\n", strerror(errno)); return 1; } opt = 1; setsockopt(lsocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); fcntl(lsocket, F_SETFL, O_NONBLOCK); if (bind(lsocket, (struct sockaddr *)&laddr, sizeof(laddr)) == -1) { errprintf("Cannot bind to listen socket (%s)\n", strerror(errno)); return 1; } if (listen(lsocket, listenq) == -1) { errprintf("Cannot listen (%s)\n", strerror(errno)); return 1; } /* Redirect logging to the logfile, if requested */ if (logfile) { freopen(logfile, "a", stdout); freopen(logfile, "a", stderr); } errprintf("xymonproxy version %s starting\n", VERSION); errprintf("Listening on %s:%d\n", inet_ntoa(laddr.sin_addr), ntohs(laddr.sin_port)); { int i; char *p; char srvrs[500]; for (i=0, srvrs[0] = '\0', p=srvrs; (i 0) { /* Parent - save PID and exit */ FILE *fd = fopen(pidfile, "w"); if (fd) { fprintf(fd, "%d\n", (int)childpid); fclose(fd); } exit(0); } /* Child (daemon) continues here */ setsid(); } setup_signalhandler(proxynamesvc); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigmisc_handler; sigaction(SIGHUP, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGUSR1, &sa, NULL); do { fd_set fdread, fdwrite; int maxfd; struct timespec tmo; struct timeval selecttmo; int n, idx; conn_t *cwalk, *ctmp; time_t ctime; time_t now; int combining = 0; /* See if it is time for a status report */ if (proxyname && ((now = gettimer()) >= (laststatus+300))) { conn_t *stentry; int ccount = 0; unsigned long bufspace = 0; unsigned long avgtime; /* In millisecs */ char runtime_s[30]; unsigned long runt = (unsigned long) (now-startuptime); char *p; unsigned long msgs_sent = msgs_total - msgs_total_last; /* Setup a conn_t struct for the status message */ stentry = (conn_t *)calloc(1, sizeof(conn_t)); stentry->state = P_REQ_READY; stentry->csocket = stentry->ssocket = -1; stentry->clientip = &stentry->caddr.sin_addr; getntimer(&stentry->arrival); stentry->timelimit.tv_sec = stentry->arrival.tv_sec + timeout; stentry->timelimit.tv_nsec = stentry->arrival.tv_nsec; stentry->bufsize = BUFSZ_INC; stentry->buf = (char *)malloc(stentry->bufsize); stentry->next = chead; chead = stentry; sprintf(runtime_s, "%lu days, %02lu:%02lu:%02lu", (runt/86400), ((runt % 86400) / 3600), ((runt % 3600) / 60), (runt % 60)); init_timestamp(); for (cwalk = chead; (cwalk); cwalk = cwalk->next) { ccount++; bufspace += cwalk->bufsize; } if (msgs_sent == 0) { avgtime = 0; } else { avgtime = (timeinqueue.tv_sec*1000 + timeinqueue.tv_nsec/1000) / msgs_sent; } p = stentry->buf; p += sprintf(p, "combo\nstatus %s green %s Proxy up %s\n\nxymonproxy for Xymon version %s\n\nProxy statistics\n\nIncoming messages : %10lu (%lu msgs/second)\nOutbound messages : %10lu\n\nIncoming message distribution\n- Combo messages : %10lu\n- Status messages : %10lu\n Messages merged : %10lu\n Resulting combos : %10lu\n- Other messages : %10lu\n\nProxy ressources\n- Connection table size : %10d\n- Buffer space : %10lu kByte\n", proxyname, timestamp, runtime_s, VERSION, msgs_total, (msgs_total - msgs_total_last) / (now - laststatus), msgs_delivered, msgs_combo, msgs_status, msgs_merged, msgs_combined, msgs_other, ccount, bufspace / 1024); p += sprintf(p, "\nTimeout/failure details\n"); p += sprintf(p, "- %-22s : %10lu\n", statename[P_REQ_READING], msgs_timeout_from[P_REQ_READING]); p += sprintf(p, "- %-22s : %10lu\n", statename[P_REQ_CONNECTING], msgs_timeout_from[P_REQ_CONNECTING]); p += sprintf(p, "- %-22s : %10lu\n", statename[P_REQ_SENDING], msgs_timeout_from[P_REQ_SENDING]); p += sprintf(p, "- %-22s : %10lu\n", "recovered", msgs_recovered); p += sprintf(p, "- %-22s : %10lu\n", statename[P_RESP_READING], msgs_timeout_from[P_RESP_READING]); p += sprintf(p, "- %-22s : %10lu\n", statename[P_RESP_SENDING], msgs_timeout_from[P_RESP_SENDING]); p += sprintf(p, "\n%-24s : %10lu.%03lu\n", "Average queue time", (avgtime / 1000), (avgtime % 1000)); /* Clear the summary collection totals */ laststatus = now; msgs_total_last = msgs_total; timeinqueue.tv_sec = timeinqueue.tv_nsec = 0; stentry->buflen = strlen(stentry->buf); stentry->bufp = stentry->buf + stentry->buflen; stentry->state = P_REQ_READY; } FD_ZERO(&fdread); FD_ZERO(&fdwrite); maxfd = -1; combining = 0; for (cwalk = chead, idx=0; (cwalk); cwalk = cwalk->next, idx++) { dbgprintf("state %d: %s\n", idx, statename[cwalk->state]); /* First, handle any state transitions and setup the FD sets for select() */ switch (cwalk->state) { case P_REQ_READING: FD_SET(cwalk->csocket, &fdread); if (cwalk->csocket > maxfd) maxfd = cwalk->csocket; break; case P_REQ_READY: if (cwalk->buflen <= 6) { /* Got an empty request - just drop it */ dbgprintf("Dropping empty request from %s\n", inet_ntoa(*cwalk->clientip)); cwalk->state = P_CLEANUP; break; } if (logdetails) do_log(cwalk); cwalk->conntries = CONNECT_TRIES; cwalk->sendtries = SEND_TRIES; cwalk->conntime = 0; /* * We now want to find out what kind of message we've got. * If it's NOT a "status" message, just pass it along. * For "status" messages, we want to try and merge many small * messages into a "combo" message - so send those off the the * P_REQ_COMBINING state for a while. * If we are not going to send back a response to the client, we * also close the client socket since it is no longer needed. * Note that since we started out as optimists and put a "combo\n" * at the front of the buffer, we need to skip that when looking at * what type of message it is. Hence the "cwalk->buf+6". */ if (strncmp(cwalk->buf+6, "client", 6) == 0) { /* * "client" messages go to all Xymon servers, but * we will only pass back the response from one of them * (the last one). */ shutdown(cwalk->csocket, SHUT_RD); msgs_other++; cwalk->snum = xymonservercount; if ((cwalk->buflen + 40 ) < cwalk->bufsize) { int n = sprintf(cwalk->bufp, "\n[proxy]\nClientIP:%s\n", inet_ntoa(*cwalk->clientip)); cwalk->bufp += n; cwalk->buflen += n; } } else if ((strncmp(cwalk->buf+6, "query", 5) == 0) || (strncmp(cwalk->buf+6, "config", 6) == 0) || (strncmp(cwalk->buf+6, "ping", 4) == 0) || (strncmp(cwalk->buf+6, "download", 8) == 0)) { /* * These requests get a response back, but send no data. * Send these to the last of the Xymon servers only. */ shutdown(cwalk->csocket, SHUT_RD); msgs_other++; cwalk->snum = 1; } else { /* It's a request that doesn't take a response. */ if (cwalk->csocket >= 0) { shutdown(cwalk->csocket, SHUT_RDWR); close(cwalk->csocket); sockcount--; cwalk->csocket = -1; } cwalk->snum = xymonservercount; if (strncmp(cwalk->buf+6, "status", 6) == 0) { msgs_status++; getntimer(&cwalk->timelimit); cwalk->timelimit.tv_nsec += COMBO_DELAY; if (cwalk->timelimit.tv_nsec >= 1000000000) { cwalk->timelimit.tv_sec++; cwalk->timelimit.tv_nsec -= 1000000000; } /* * Some clients (bbnt) send a trailing \0, so we cannot * rely on buflen being what we want it to be. */ cwalk->buflen = strlen(cwalk->buf); cwalk->bufp = cwalk->buf + cwalk->buflen; if ((cwalk->buflen + 50 ) < cwalk->bufsize) { int n = sprintf(cwalk->bufp, "\nStatus message received from %s\n", inet_ntoa(*cwalk->clientip)); cwalk->bufp += n; cwalk->buflen += n; } cwalk->state = P_REQ_COMBINING; break; } else if (strncmp(cwalk->buf+6, "combo\n", 6) == 0) { char *currmsg, *nextmsg; msgs_combo++; /* * Some clients (bbnt) send a trailing \0, so we cannot * rely on buflen being what we want it to be. */ cwalk->buflen = strlen(cwalk->buf); cwalk->bufp = cwalk->buf + cwalk->buflen; getntimer(&cwalk->timelimit); cwalk->timelimit.tv_nsec += COMBO_DELAY; if (cwalk->timelimit.tv_nsec >= 1000000000) { cwalk->timelimit.tv_sec++; cwalk->timelimit.tv_nsec -= 1000000000; } currmsg = cwalk->buf+12; /* Skip pre-def. "combo\n" and message "combo\n" */ do { nextmsg = strstr(currmsg, "\n\nstatus"); if (nextmsg) { *(nextmsg+1) = '\0'; nextmsg += 2; } /* Create a duplicate conn_t record for all embedded messages */ ctmp = (conn_t *)malloc(sizeof(conn_t)); memcpy(ctmp, cwalk, sizeof(conn_t)); ctmp->bufsize = BUFSZ_INC*(((6 + strlen(currmsg) + 50) / BUFSZ_INC) + 1); ctmp->buf = (char *)malloc(ctmp->bufsize); ctmp->buflen = sprintf(ctmp->buf, "combo\n%s\nStatus message received from %s\n", currmsg, inet_ntoa(*cwalk->clientip)); ctmp->bufp = ctmp->buf + ctmp->buflen; ctmp->state = P_REQ_COMBINING; ctmp->next = chead; chead = ctmp; currmsg = nextmsg; } while (currmsg); /* We dont do anymore with this conn_t */ cwalk->state = P_CLEANUP; break; } else if (strncmp(cwalk->buf+6, "page", 4) == 0) { /* xymond has no use for page requests */ cwalk->state = P_CLEANUP; break; } else { msgs_other++; } } /* * This wont be made into a combo message, so skip the "combo\n" * and go off to send the message to the server. */ cwalk->bufp = cwalk->buf+6; cwalk->buflen -= 6; cwalk->bufpsave = cwalk->bufp; cwalk->buflensave = cwalk->buflen; cwalk->state = P_REQ_CONNECTING; /* Fall through for non-status messages */ case P_REQ_CONNECTING: /* Need to restore the bufp and buflen, as we may get here many times for one message */ cwalk->bufp = cwalk->bufpsave; cwalk->buflen = cwalk->buflensave; ctime = gettimer(); if (ctime < (cwalk->conntime + CONNECT_INTERVAL)) { dbgprintf("Delaying retry of connection\n"); break; } cwalk->conntries--; cwalk->conntime = ctime; if (cwalk->conntries < 0) { errprintf("Server not responding, message lost\n"); cwalk->state = P_REQ_DONE; /* Not CLENAUP - might be more servers */ msgs_timeout_from[P_REQ_CONNECTING]++; break; } cwalk->ssocket = socket(AF_INET, SOCK_STREAM, 0); if (cwalk->ssocket == -1) { dbgprintf("Could not get a socket - will try again\n"); break; /* Retry the next time around */ } sockcount++; fcntl(cwalk->ssocket, F_SETFL, O_NONBLOCK); { int idx = (xymonservercount - cwalk->snum); n = connect(cwalk->ssocket, (struct sockaddr *)&xymonserveraddr[idx], sizeof(xymonserveraddr[idx])); cwalk->serverip = &xymonserveraddr[idx].sin_addr; dbgprintf("Connecting to Xymon server at %s\n", inet_ntoa(*cwalk->serverip)); } if ((n == 0) || ((n == -1) && (errno == EINPROGRESS))) { cwalk->state = P_REQ_SENDING; cwalk->connectpending = 1; getntimer(&cwalk->timelimit); cwalk->timelimit.tv_sec += timeout; /* Fallthrough */ } else { /* Could not connect! Invoke retries */ dbgprintf("Connect to server failed: %s\n", strerror(errno)); close(cwalk->ssocket); sockcount--; cwalk->ssocket = -1; break; } /* No "break" here! */ case P_REQ_SENDING: FD_SET(cwalk->ssocket, &fdwrite); if (cwalk->ssocket > maxfd) maxfd = cwalk->ssocket; break; case P_REQ_DONE: /* Request has been sent to the server - we're done writing data */ shutdown(cwalk->ssocket, SHUT_WR); cwalk->snum--; if (cwalk->snum) { /* More servers to do */ close(cwalk->ssocket); cwalk->ssocket = -1; sockcount--; cwalk->conntries = CONNECT_TRIES; cwalk->sendtries = SEND_TRIES; cwalk->conntime = 0; cwalk->state = P_REQ_CONNECTING; break; } else { /* Have sent to all servers, grab the response from the last one. */ cwalk->bufp = cwalk->buf; cwalk->buflen = 0; memset(cwalk->buf, 0, cwalk->bufsize); } msgs_delivered++; if (cwalk->sendtries < SEND_TRIES) { errprintf("Recovered from write error after %d retries\n", (SEND_TRIES - cwalk->sendtries)); msgs_recovered++; } if (cwalk->arrival.tv_sec > 0) { struct timespec departure; getntimer(&departure); timeinqueue.tv_sec += (departure.tv_sec - cwalk->arrival.tv_sec); if (departure.tv_nsec >= cwalk->arrival.tv_nsec) { timeinqueue.tv_nsec += (departure.tv_nsec - cwalk->arrival.tv_nsec); } else { timeinqueue.tv_sec--; timeinqueue.tv_nsec += (1000000000 + departure.tv_nsec - cwalk->arrival.tv_nsec); } if (timeinqueue.tv_nsec > 1000000000) { timeinqueue.tv_sec++; timeinqueue.tv_nsec -= 1000000000; } } else { errprintf("Odd ... this message was not timestamped\n"); } if (cwalk->csocket < 0) { cwalk->state = P_CLEANUP; break; } else { cwalk->state = P_RESP_READING; getntimer(&cwalk->timelimit); cwalk->timelimit.tv_sec += timeout; } /* Fallthrough */ case P_RESP_READING: FD_SET(cwalk->ssocket, &fdread); if (cwalk->ssocket > maxfd) maxfd = cwalk->ssocket; break; case P_RESP_READY: shutdown(cwalk->ssocket, SHUT_RD); close(cwalk->ssocket); sockcount--; cwalk->ssocket = -1; cwalk->bufp = cwalk->buf; cwalk->state = P_RESP_SENDING; getntimer(&cwalk->timelimit); cwalk->timelimit.tv_sec += timeout; /* Fall through */ case P_RESP_SENDING: if (cwalk->buflen && (cwalk->csocket >= 0)) { FD_SET(cwalk->csocket, &fdwrite); if (cwalk->csocket > maxfd) maxfd = cwalk->csocket; break; } else { cwalk->state = P_RESP_DONE; } /* Fall through */ case P_RESP_DONE: if (cwalk->csocket >= 0) { shutdown(cwalk->csocket, SHUT_WR); close(cwalk->csocket); sockcount--; } cwalk->csocket = -1; cwalk->state = P_CLEANUP; /* Fall through */ case P_CLEANUP: if (cwalk->csocket >= 0) { close(cwalk->csocket); sockcount--; cwalk->csocket = -1; } if (cwalk->ssocket >= 0) { close(cwalk->ssocket); sockcount--; cwalk->ssocket = -1; } cwalk->arrival.tv_sec = cwalk->arrival.tv_nsec = 0; cwalk->bufp = cwalk->bufp; cwalk->buflen = 0; memset(cwalk->buf, 0, cwalk->bufsize); memset(&cwalk->caddr, 0, sizeof(cwalk->caddr)); cwalk->madetocombo = 0; cwalk->state = P_IDLE; break; case P_IDLE: break; case P_REQ_COMBINING: /* See if we can combine some "status" messages into a "combo" */ combining++; getntimer(&tmo); if ((cwalk->buflen < MINIMUM_FOR_COMBO) && !overdue(&tmo, &cwalk->timelimit)) { conn_t *cextra; /* Are there any other messages in P_COMBINING state ? */ cextra = cwalk->next; while (cextra && (cextra->state != P_REQ_COMBINING)) cextra = cextra->next; if (cextra) { /* * Yep. It might be worthwhile to go for a combo. */ while (cextra && (cwalk->buflen < (MAXIMUM_FOR_COMBO-20))) { if (strncmp(cextra->buf+6, "status", 6) == 0) { int newsize; /* * Size of the new message - if the cextra one * is merged - is the cwalk buffer, plus the * two newlines separating messages in combo's, * plus the cextra buffer except the leading * "combo\n" of 6 bytes. */ newsize = cwalk->buflen + 2 + (cextra->buflen - 6); if ((newsize < cwalk->bufsize) && (newsize < MAXIMUM_FOR_COMBO)) { /* * There's room for it. Add it to the * cwalk buffer, but without the leading * "combo\n" (we already have one of those). */ cwalk->madetocombo++; strcat(cwalk->buf, "\n\n"); strcat(cwalk->buf, cextra->buf+6); cwalk->buflen = newsize; cextra->state = P_CLEANUP; dbgprintf("Merged combo\n"); msgs_merged++; } } /* Go to the next connection in the right state */ do { cextra = cextra->next; } while (cextra && (cextra->state != P_REQ_COMBINING)); } } } else { combining--; cwalk->state = P_REQ_CONNECTING; if (cwalk->madetocombo) { /* * Point the outgoing buffer pointer to the full * message, including the "combo\n" */ cwalk->bufp = cwalk->buf; cwalk->madetocombo++; msgs_merged++; /* Count the proginal message also */ msgs_combined++; dbgprintf("Now going to send combo from %d messages\n%s\n", cwalk->madetocombo, cwalk->buf); } else { /* * Skip sending the "combo\n" at start of buffer. */ cwalk->bufp = cwalk->buf+6; cwalk->buflen -= 6; dbgprintf("No messages to combine - sending unchanged\n"); } } cwalk->bufpsave = cwalk->bufp; cwalk->buflensave = cwalk->buflen; break; default: break; } } /* Add the listen-socket to the select() list, but only if we have room */ if (sockcount < MAX_OPEN_SOCKS) { FD_SET(lsocket, &fdread); if (lsocket > maxfd) maxfd = lsocket; } else { static time_t lastlog = 0; if ((now = gettimer()) < (lastlog+30)) { lastlog = now; errprintf("Squelching incoming connections, sockcount=%d\n", sockcount); } } if (combining) { selecttmo.tv_sec = 0; selecttmo.tv_usec = COMBO_DELAY; } else { selecttmo.tv_sec = 1; selecttmo.tv_usec = 0; } n = select(maxfd+1, &fdread, &fdwrite, NULL, &selecttmo); if (n <= 0) { getntimer(&tmo); for (cwalk = chead; (cwalk); cwalk = cwalk->next) { switch (cwalk->state) { case P_REQ_READING: case P_REQ_SENDING: case P_RESP_READING: case P_RESP_SENDING: if (overdue(&tmo, &cwalk->timelimit)) { cwalk->state = P_CLEANUP; msgs_timeout_from[cwalk->state]++; } break; default: break; } } } else { for (cwalk = chead; (cwalk); cwalk = cwalk->next) { switch (cwalk->state) { case P_REQ_READING: if (FD_ISSET(cwalk->csocket, &fdread)) { do_read(cwalk->csocket, cwalk->clientip, cwalk, P_REQ_READY); } break; case P_REQ_SENDING: if (FD_ISSET(cwalk->ssocket, &fdwrite)) { if (cwalk->connectpending) { int connres, connressize; /* First time ready for write - check connect status */ cwalk->connectpending = 0; connressize = sizeof(connres); n = getsockopt(cwalk->ssocket, SOL_SOCKET, SO_ERROR, &connres, &connressize); if (connres != 0) { /* Connect failed! Invoke retries. */ dbgprintf("Connect to server failed: %s - retrying\n", strerror(errno)); close(cwalk->ssocket); sockcount--; cwalk->ssocket = -1; cwalk->state = P_REQ_CONNECTING; break; } } if ( (do_write(cwalk->ssocket, cwalk->serverip, cwalk, P_REQ_DONE) == -1) && (cwalk->sendtries > 0) ) { /* * Got a "write" error after connecting. * Try saving the situation by retrying the send later. */ dbgprintf("Attempting recovery from write error\n"); close(cwalk->ssocket); sockcount--; cwalk->ssocket = -1; cwalk->sendtries--; cwalk->state = P_REQ_CONNECTING; cwalk->conntries = CONNECT_TRIES; cwalk->conntime = gettimer(); } } break; case P_RESP_READING: if (FD_ISSET(cwalk->ssocket, &fdread)) { do_read(cwalk->ssocket, cwalk->serverip, cwalk, P_RESP_READY); } break; case P_RESP_SENDING: if (FD_ISSET(cwalk->csocket, &fdwrite)) { do_write(cwalk->csocket, cwalk->clientip, cwalk, P_RESP_DONE); } break; default: break; } } if (FD_ISSET(lsocket, &fdread)) { /* New incoming connection */ conn_t *newconn; int caddrsize; dbgprintf("New connection\n"); for (cwalk = chead; (cwalk && (cwalk->state != P_IDLE)); cwalk = cwalk->next); if (cwalk) { newconn = cwalk; } else { newconn = malloc(sizeof(conn_t)); newconn->next = chead; chead = newconn; newconn->bufsize = BUFSZ_INC; newconn->buf = newconn->bufp = malloc(newconn->bufsize); } newconn->connectpending = 0; newconn->madetocombo = 0; newconn->snum = 0; newconn->ssocket = -1; newconn->serverip = NULL; newconn->conntries = 0; newconn->sendtries = 0; newconn->timelimit.tv_sec = newconn->timelimit.tv_nsec = 0; /* * Why this ? Because we like to merge small status messages * into larger combo messages. So put a "combo\n" at the start * of the buffer, and then don't send it if we decide it won't * be a combo-message after all. */ strcpy(newconn->buf, "combo\n"); newconn->buflen = 6; newconn->bufp = newconn->buf+6; caddrsize = sizeof(newconn->caddr); newconn->csocket = accept(lsocket, (struct sockaddr *)&newconn->caddr, &caddrsize); if (newconn->csocket == -1) { /* accept() failure. Yes, it does happen! */ dbgprintf("accept failure, ignoring connection (%s), sockcount=%d\n", strerror(errno), sockcount); newconn->state = P_IDLE; } else { msgs_total++; newconn->clientip = &newconn->caddr.sin_addr; sockcount++; fcntl(newconn->csocket, F_SETFL, O_NONBLOCK); newconn->state = P_REQ_READING; getntimer(&newconn->arrival); newconn->timelimit.tv_sec = newconn->arrival.tv_sec + timeout; newconn->timelimit.tv_nsec = newconn->arrival.tv_nsec; } } } /* Clean up unused conn_t's */ { conn_t *tmp, *khead; khead = NULL; cwalk = chead; while (cwalk) { if ((cwalk == chead) && (cwalk->state == P_IDLE)) { /* head of chain is dead */ tmp = chead; chead = chead->next; tmp->next = khead; khead = tmp; cwalk = chead; } else if (cwalk->next && (cwalk->next->state == P_IDLE)) { tmp = cwalk->next; cwalk->next = tmp->next; tmp->next = khead; khead = tmp; /* cwalk is unchanged */ } else { cwalk = cwalk->next; } } /* khead now holds a list of P_IDLE conn_t structs */ while (khead) { tmp = khead; khead = khead->next; if (tmp->buf) xfree(tmp->buf); xfree(tmp); } } } while (keeprunning); if (pidfile) unlink(pidfile); return 0; } xymon-4.3.7/xymonproxy/xymoncgimsg.cgi.80000664000175000017500000000305411671641417017707 0ustar henrikhenrik.TH XYMONCGIMSG.CGI 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymoncgimsg.cgi \- CGI utility used for proxying Xymon data over HTTP .SH SYNOPSIS .B "xymoncgimsg.cgi" .SH DESCRIPTION .I xymoncgimsg.cgi(8) is the server-side utility receiving Xymon messages sent by the .I xymon(1) utility over an HTTP transport. The \fBxymon\fR utility normally sends data over a dedicated TCP protocol, but it may use HTTP to go through proxies or through restrictive firewalls. In that case, the webserver must have this CGI utility installed, which takes care of receiving the message via HTTP, and forwards it to a local Xymon server through the normal Xymon transport. The CGI expects to be invoked from an HTTP "POST" request, with the POST-data being the status-message. \fBxymoncgimsg.cgi\fR simply collects all of the POST data, and send it off as a message to the Xymon daemon running on IP 127.0.0.1. This destination IP currently cannot be changed. The CGI will return any output provided by the Xymon daemon back to the requestor as the response to the HTTP POST, so this allows for all normal Xymon commands to work. .SH SECURITY \fBxymoncgimsg.cgi\fR will only send data to a Xymon server through the loopback interface, i.e. IP-address 127.0.0.1. Access to the CGI should be restricted through webserver access controls, since the CGI provides no authentication at all to validate incoming messages. If possible, consider using the .I xymonproxy(8) utility instead for native proxying of Xymon data between networks. .SH "SEE ALSO" xymon(1), xymonproxy(8), xymon(7) xymon-4.3.7/xymonproxy/Makefile0000664000175000017500000000137411535462534016154 0ustar henrikhenrik# xymonproxy Makefile PROGRAMS = xymonproxy xymoncgimsg.cgi PROXYOBJS = xymonproxy.o MSGCGIOBJS = xymoncgimsg.o all: $(PROGRAMS) xymonproxy: $(PROXYOBJS) $(CC) $(CFLAGS) -o $@ $(PROXYOBJS) ../lib/libxymon.a $(NETLIBS) $(LIBRTDEF) xymoncgimsg.cgi: $(MSGCGIOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) -o $@ $(MSGCGIOBJS) ../lib/libxymon.a $(NETLIBS) $(LIBRTDEF) ################################################ # Default compilation rules ################################################ %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< clean: rm -f *.o *.a *~ $(PROGRAMS) install: install-bin install-man install-bin: cp -fp $(PROGRAMS) $(INSTALLROOT)$(INSTALLBINDIR)/ install-man: mkdir -p $(INSTALLROOT)$(MANROOT)/man8 cp -fp *.8 $(INSTALLROOT)$(MANROOT)/man8/ xymon-4.3.7/RELEASENOTES0000664000175000017500000004061411665425247014060 0ustar henrikhenrik <<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>> * * * Release notes for Xymon 4.3.6 * * * <<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>> Changes for 4.3.6, 4.3.5, 4.3.4, 4.3.3, 4.3.2 ============================================= See the Changes file for a list of significant changes. These releases are mostly to fix bugs. Changes for 4.3.1 ================== This is a SECURITY BUG FIX release, resolving a number of potential cross-site scripting vulnerabilities in the Xymon web interface. Thanks to David Ferrest who reported these to me. Changes for 4.3.0 ================== IMPORTANT: Most files and internals in Xymon have been renamed with the 4.3.0-beta3 release. If you are upgrading from a version prior to 4.3.0-beta3, you MUST read the file docs/upgrade-to-430.txt and make sure you follow the steps outlined here. That also applies if you are upgrading from 4.3.0-beta2. Xymon 4.3.0 is the first release with new features after the 4.2.0 release. Several large changes have been made throughout the entire codebase. Some highlights (see the Changes file for a longer list): * Data going into graphs can now be used to alter a status, e.g. to trigger an alert from the response time of a network service. * Tasks in xymonlaunch can be be configured to run at specific time of day using a cron-style syntax for when they must run. * Worker modules (RRD, client-data parsers etc) can operate on remote hosts from the xymond daemon, for load-sharing. * Support for defining holidays as non-working days in alerts and SLA calculations. * Hosts which appear on multiple pages in the web display can use any page they are on in the alerting rules and elsewhere. * Various new network tests: SOAP-over-HTTP, HTTP tests with session cookies, SSL minimum encryption strength test. * New "compact" status display can group multiple statuses into one on the webpage display. * Configurable periods for graphs on the trends page. * RRD files can be configured to maintain more data and/or different data (e.g. MAX/MIN values) * SLA calculations now include the number of outages in addition to the down-time. * Solaris client now uses "swap -l" to determine swap-space usage, which gives very different results from the previous "swap -s" data. So your Solaris hosts will appear to change swap-utilisation when the Xymon client is upgraded. TRACKMAX feature removed ------------------------ For users of the TRACKMAX feature present in 4.2.2 and later 4.2.x releases: This feature has been dropped. Instead, you should add a definition for the tests that you want to track max-values for to the rrddefinitions.cfg file. E.g. to track max-values for the "mytest" status column: [mytest] RRA:MAX:0.5:1:576 RRA:MAX:0.5:6:576 RRA:MAX:0.5:24:576 RRA:MAX:0.5:288:576 Changed behaviour of http testing via proxy ------------------------------------------- The Big Brother behaviour of allowing you to specify an http (web) proxy as part of the URL in a test has been disabled by default, since it interferes with URL's that contain another URL as part of the URL path. If you need the Big Brother behaviour, add "--bb-proxy-syntax" to your invocations of bbtest-net. BBGHOSTS setting removed ------------------------ Setting BBGHOSTS in the xymonserver.cfg file no longer has any effect. Instead, you must use the "--ghosts" option for hobbitd. The default ghost handling has also been changed from "ignore" to "log". xymonproxy: Alert support for Big Brother servers removed --------------------------------------------------------- The xymonproxy tool no longer supports forwarding "page" messages. This means that if you are forwarding messages from an old Big Brother client to a Big Brother server that is sending out alerts, then these alerts will be dropped. Status updates will be forwarded, only alerts triggered from the Big Brother server are affected. Webpage menu: New menu code --------------------------- The Javascript-based menu code ("Tigra") has been replaced with a menu based on CSS/HTML4. This means that any menu customization will have to be manually transferred to the new menu definition file, ~xymon/server/etc/xymonmenu.cfg. The new menu-system is known NOT to work with Internet Explorer 6 (IE 7 and newer are fine). If you must have a menu system that works with the ancient browsers, then the old Tigra menu can be downloaded from http://xymon.svn.sourceforge.net/viewvc/xymon/sandbox/tigramenu/?view=tar together with instructions for using it with Xymon 4.3.0. Changes from 4.2.2 -> 4.2.3 =========================== See the "Changes" file for a list of the changes in 4.2.3. The 4.2.3 release primarily touches the network test module, where a number of DNS bugs were fixed - these could cause network tests (especially tests of DNS servers) to crash in 4.2.2. This release also marks the beginning of a series of releases leading up the the 5.0 release. New features will be added in coming 4.3 / 4.4 etc. versions releases throughout 2009. Changes from 4.2.0 -> 4.2.2 =========================== The 4.2.2 release is an interim release. It contains the original 4.2.0 release plus the following changes: *) Due to the Hobbit project being renamed to Xymon (for legal reasons), the documentation has been updated to refer to this as the name of the project. Note that the 4.2.2 release does not change any filenames for configuration files or programs. *) All patches from the "all-in-one" patch have been applied *) Patches from Debian and Mandriva source archives and elsewhere have been merged, fixing: - The "Critical Systems" configuration report would crash when hosts were in NKview.cfg, but no NK tags were in bb-hosts (from Mandriva) - SSL certificates with 4-digit expiration years would crash the network tester (from Debian) - Newer OpenLDAP versions have a different API, so the LDAP test code would not build correctly (from Debian) - Old Big Brother clients report disk output with no header line (from Debian) - The bb-hosts syntax for HTTP testing via a proxy could not support https target-URL's (from Debian) - Certain SuSE versions would not be identified by the Linux client as such (only affected OS name displayed in the "info" status). - hobbitping could loop indefinitely in case of network errors. - Installing on newer Darwin versions failed because "nireport" was used to find user-info. Changed to use "dscl". - Alert-scripts might fail because of size limitations on how large environment variables could be passed. - 64-bit systems might put odd numbers into the acknowledgment-log - Fixed display of alert duration on the "info" page. - NetBSD systems with >2 GB RAM would report strange memory data. *) Support for sending custom graph data in a "trends" data message (from http://www.hswn.dk/hobbiton/2007/01/msg00236.html) *) Split-NCV and TRACKMAX support for custom graphs (from http://www.hobbitmon.com/hobbiton/2007/03/msg00368.htm). *) Support for the "BBWin" client http://bbwin.sf.net/ in centralized (server-side) configuration mode. Based on BBWin v. 0.12. *) Support for the "hobbit-perl-client" add-on http://hobbit-perl-client.sf.net/ . Based on perl-http-client v1.15 *) Support for the "Devmon" SNMP data collector http://devmon.sf.net/ based on Devmon 0.3.0. A detailed changelog can be found in the Changes document. Note: There was never an official 4.2.1 release. Below are the release notes for the 4.2.0 version * * * * * This release contains several new programs, enhanced functionality and changes to the configuration files. New Critical Systems View - "NK" tags now deprecated ==================================================== The "Critical Systems" view is now built dynamically by a new CGI tool, hobbit-nkview. It uses a new configuration file separate from the bb-hosts file, which is maintained by the hobbit-nkedit CGI. *** The "NK" tags in bb-hosts are being deprecated *** The NK tags in the bb-hosts file will only be handled in Xymon 4.x. It is recommended that you begin to move your critical systems view to the new hobbit-nkview configuration. Modem-bank testing ("dialup" host definition) does not work =========================================================== Previous versions of Xymon supported the Big Brother "dialup" host definition for pinging a range of IP-adresses, e.g. a modem-bank or a pool of IP-adresses handed out by a DHCP server. This feature is not supported in Xymon 4.2, but may re-appear in a different form in a later version. (Note: This has nothing to do with the "dialup" directive which can be applied to individual hosts, to make them go clear when they are offline instead of red or purple). Installation ============ Follow the normal procedure for building and installing Xymon, i.e. "./configure; make; make install". Note that Xymon now installs hobbitping suid-root, so root privileges are needed for a server installation. Xymon 4.2 uses one more IPC communication channel than the previous release (for a total of 8 shared memory segments totalling 2336 KB of shared memory, and 8 semaphore sets of 3 semaphores each). If your system has a limited number of IPC shared-memory segments and/or semaphores, you may need to increase this. See the installation document at docs/install.html for some information about this, or refer to your operating system documentation on System V IPC. bbproxy upgrade =============== If you have a multi-host setup using the bbproxy utility, then you must upgrade bbproxy for the new Xymon clients to be fully functional. Configuration file changes ========================== If you are upgrading from a previous version of Xymon, you will need to merge several new configuration items into your Xymon configuration files, in order to make full use of these enhance- ments. These notes describe what changes to perform. xymonserver.cfg ---------------- Several new status columns are being reported by the Xymon client, and these have associated graphs. In order for them to show up correctly, you must change the two graph-settings: - TEST2RRD: Add ",files,procs=processes,ports,clock,lines" to the current setting. - GRAPHS: Add ",files,processes,ports,ifstat,clock,lines" to the current setting. Xymon now includes a ping utility, which is faster than "fping" which was used in previous Xymon versions. To use this utility, change the FPING setting to FPING="hobbitping". However, the hobbitping utility is still somewhat experimental, so if you run into problems with the connectivity tests, it is advised that you use fping instead. The following settings in xymonserver.cfg are no longer used and may be deleted: BBPAGE, BBPAGERS, USEHOBBITD, PAGELEVELS, PURPLEDELAY, BBREL, BBRELDATE, CLIENTSVCS. hobbitcgi.cfg ------------- The bb-ack.cgi tool has a new option, "--no-pin", which allows you to acknowledge alerts without having to bother with the acknowledgment code. To use this, add the "--no-pin" option to the CGI_ACK_OPTS setting. Five new CGI programs have been added in Xymon 4.2. To ensure they are invoked correctly, add these lines to your hobbitcgi.cfg file: # hobbitgraph.cgi options CGI_HOSTGRAPHS_OPTS="--env=/home/hobbit/server/etc/xymonserver.cfg" # hobbit-nkview.cgi options CGI_NKVIEW_OPTS="--env=/home/hobbit/server/etc/xymonserver.cfg" # hobbit-nkedit.cgi options CGI_NKEDIT_OPTS="--env=/home/hobbit/server/etc/xymonserver.cfg" # hobbit-ackinfo.cgi options CGI_ACKINFO_OPTS="--env=/home/hobbit/server/etc/xymonserver.cfg" #hobbit-ghosts.cgi options CGI_GHOSTS_OPT="--env=/home/hobbit/etc/xymonserver.cfg" (Replace "/home/hobbit" with the top-level directory of your Xymon server installation). hobbitgraph.cfg --------------- Several new graphs require additions to the hobbitgraph.cfg file. If you haven't added any custom graphs, it is recommended that you install the new version of the file located in the hobbit-4.2/hobbitd/etcfiles/ directory. If you have added custom graphs, copy the following sections from the 4.2 version of the file to your hobbitgraph.cfg file: apache3-multi, conn-multi, ifstat, files, processes, ports, clock, lines. hobbitlaunch.cfg ---------------- The path for the [bbcombotest] module was incorrect. To use the bbcombotest feature, make sure that it has CMD BBHOME/bin/bbcombotest A new Xymon module has been implemented, which saves a snapshot of the client data reported by a Xymon client prior to a status going into a critical or warning state. This aids in diagnosing problems where the critical status appearing in Xymon is only a symptom of the real problem, and you need to look at other types of data to determine the root cause. To enable this module, two changes are needed: * The [hobbitd] command must have the option "--store-clientlogs=!msgs" added. See the hobbitd(8) man-page for further information. * A new [hostdata] section must be added: # "hostdata" stores the Xymon client messages on disk when some status for a host # changes. This lets you access a lot of data collected from a host around the time # when a problem occurred. However, it may use a significant amount of disk space # if you have lots of Xymon clients. # Note: The --store-clientlogs option for the [hobbitd] provides control over # which status-changes will cause a client message to be stored. [hostdata] ENVFILE $BBHOME/etc/xymonserver.cfg NEEDS hobbitd CMD hobbitd_channel --channel=clichg --log=$BBSERVERLOGS/hostdata.log hobbitd_hostdata If you want to use the new "hobbitfetch" utility to collect data from clients, you must add a [hobbitfetch] section to run the data collector. Add this to your hobbitlaunch.cfg file: # "hobbitfetch" is used when you have clients that cannot connect to your Xymon server, # but the Xymon server can connect to the client. Normally the clients will initiate # a connection to the Xymon server to deliver the data they collect, but this is # forbidden in some firewall setups. By enabling the hobbitfetch task, hosts that have # the "pulldata" tag in the bb-hosts file will be polled by hobbitfetch for their data. # # NOTE: On the clients you must enable the "msgcache" task, since this is what # hobbitfetch is talking to. [hobbitfetch] ENVFILE $BBHOME/etc/xymonserver.cfg CMD $BBHOME/bin/hobbitfetch --server=YOUR.HOBBIT.SERVER.IP --no-daemon --pidfile=$BBSERVERLOGS/hobbitfetch.pid LOGFILE $BBSERVERLOGS/hobbitfetch.log ~hobbit/server/www/menu/menu_items.js ----------------------------------- Two new menu items have been added, and the "NK view" has been replaced by the new "Critical systems" view. To make this visible, change this: - Replace the "NK view" section with ['Critical systems', '/hobbit-cgi/hobbit-nkview.sh'], Note that this requires you to migrate your current "NK" tags in the bb-hosts file to the new Critical Systems configuration file. See the "Critical Systems" menu item in the "Help" menu. - In the "Reports" menu, after the "Config report" line, add ['Metrics Report', '/hobbit-cgi/hobbit-hostgraphs.sh'], ['Ghost Clients', '/hobbit-cgi/hobbit-ghosts.sh'], - In the "Administration" menu, after the "Enable/Disable" line, add ['Edit critical systems', '/hobbit-seccgi/hobbit-nkedit.sh'], - In the "Help" menu, after the "Tips and Tricks" line, add ['Custom graphs', '/hobbit/help/howtograph.html'], - In the "Help" menu, after the "Configuring Alerts" line, add ['Critical systems', '/hobbit/help/criticalsystems.html'], ['Custom graphs', '/hobbit/help/howtograph.html'], The "/hobbit-cgi" part of the URL may be different, depending on your setup. ~hobbit/server/www/menu/menu_tpl.js --------------------------------- The width of menu items need to be increased slightly. Change line 25 of this file from "'width': 150" to "'width': 160". bb-services ----------- The [spamd] definition had an erroneous "expect" string. It should be changed to expect "SPAMD" A "cupsd" definition has been added to test the CUPS printing system. # CUPS print server. It answers to HTTP requests. [cupsd] send "GET /printers\r\n" expect "HTTP/1.1 200 OK" port 631 And an "AJP13" definition has been added for the Apache JServer protocol version 1.3. # AJP (Apache JServ Protocol) 1.3 - sends an AJP "ping" request. # Ref: http://tomcat.apache.org/connectors-doc/common/ajpv13a.html # From Charles Goyard [ajp13] send "\x12\x34\x00\x01\x0a" expect "\x41\x42\x00\x01\x09" port 8009 xymon-4.3.7/web/0000775000175000017500000000000011671641716013012 5ustar henrikhenrikxymon-4.3.7/web/report.sh.DIST0000664000175000017500000000032211535462534015416 0ustar henrikhenrik#!/bin/sh # This it the Xymon wrapper for the report.cgi tool . @XYMONHOME@/etc/xymonserver.cfg . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/report.cgi $CGI_REP_OPTS $XYMONGENREPOPTS xymon-4.3.7/web/history.c0000664000175000017500000006422511615341300014650 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon history viewer. */ /* */ /* This is a CGI tool used to view the history of a status log. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: history.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include "libxymon.h" static char selfurl[PATH_MAX]; static time_t req_endtime = 0; static char *displayname = NULL; static int wantserviceid = 1; static int len1d = 24; static char *bartitle1d = "1 day summary"; static int len1w = 7; static char *bartitle1w = "1 week summary"; static int len4w = 28; static char *bartitle4w = "4 week summary"; static int len1y = 12; static char *bartitle1y = "1 year summary"; #define DEFPIXELS 960 /* The pixel setup */ static int usepct = 0; static int pixels = DEFPIXELS; /* What colorbars and summaries to show */ #define BARSUM_1D 0x0001 /* 1-day bar */ #define BARSUM_1W 0x0002 /* 1-week bar */ #define BARSUM_4W 0x0004 /* 4-week bar */ #define BARSUM_1Y 0x0008 /* 1-year bar */ static unsigned int barsums = (BARSUM_1D|BARSUM_1W|BARSUM_4W|BARSUM_1Y); static char *barbkgcolor = "\"#000033\""; static char *tagcolors[COL_COUNT] = { "#3AF03A", /* A bright green */ "white", "blue", "purple", "yellow", "red" }; #define ALIGN_HOUR 0 #define ALIGN_DAY 1 #define ALIGN_MONTH 2 #define DAY_BAR 0 #define WEEK_BAR 1 #define MONTH_BAR 2 #define YEAR_BAR 3 #define END_START 0 #define END_END 1 #define END_UNCHANGED 2 static void generate_pct_summary( FILE *htmlrep, /* output file */ char *hostname, char *service, char *caption, reportinfo_t *repinfo, /* Percent summaries for period */ time_t secsperpixel) { fprintf(htmlrep, "\n", barbkgcolor); fprintf(htmlrep, "\n", caption); fprintf(htmlrep, "\n", durationstr(secsperpixel / 2)); fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n", xgetenv("XYMONSKIN"), dotgiffilename(COL_GREEN, 0, 1), colorname(COL_GREEN), colorname(COL_GREEN), xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); fprintf(htmlrep, "\n", xgetenv("XYMONSKIN"), dotgiffilename(COL_YELLOW, 0, 1), colorname(COL_YELLOW), colorname(COL_YELLOW), xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); fprintf(htmlrep, "\n", xgetenv("XYMONSKIN"), dotgiffilename(COL_RED, 0, 1), colorname(COL_RED), colorname(COL_RED), xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); fprintf(htmlrep, "\n", xgetenv("XYMONSKIN"), dotgiffilename(COL_PURPLE, 0, 1), colorname(COL_PURPLE), colorname(COL_PURPLE), xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); fprintf(htmlrep, "\n", xgetenv("XYMONSKIN"), dotgiffilename(COL_CLEAR, 0, 1), colorname(COL_CLEAR), colorname(COL_CLEAR), xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); fprintf(htmlrep, "\n", xgetenv("XYMONSKIN"), dotgiffilename(COL_BLUE, 0, 1), colorname(COL_BLUE), colorname(COL_BLUE), xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n", repinfo->fullpct[COL_GREEN]); fprintf(htmlrep, "\n", repinfo->fullpct[COL_YELLOW]); fprintf(htmlrep, "\n", repinfo->fullpct[COL_RED]); fprintf(htmlrep, "\n", repinfo->fullpct[COL_PURPLE]); fprintf(htmlrep, "\n", repinfo->fullpct[COL_CLEAR]); fprintf(htmlrep, "\n", repinfo->fullpct[COL_BLUE]); fprintf(htmlrep, "\n"); fprintf(htmlrep, "
%s
Min. duration shown: %s
\"%s\"\"%s\"\"%s\"\"%s\"\"%s\"\"%s\"
%.2f%%%.2f%%%.2f%%%.2f%%%.2f%%%.2f%%
\n"); } static unsigned int calc_time(time_t endtime, int change, int alignment, int endofperiod) { int daysinmonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; struct tm *tmbuf; time_t result, now; int dstsetting = -1; again: tmbuf = localtime(&endtime); switch (alignment) { case ALIGN_HOUR: tmbuf->tm_hour += change; if (endofperiod == END_END) { tmbuf->tm_min = tmbuf->tm_sec = 59; } else if (endofperiod == END_START) { tmbuf->tm_min = tmbuf->tm_sec = 0; } break; case ALIGN_DAY: tmbuf->tm_mday += change; if (endofperiod == END_END) { tmbuf->tm_hour = 23; tmbuf->tm_min = 59; tmbuf->tm_sec = 59; } else if (endofperiod == END_START) { tmbuf->tm_hour = tmbuf->tm_min = tmbuf->tm_sec = 0; } break; case ALIGN_MONTH: tmbuf->tm_mon += change; if (endofperiod == END_END) { /* Need to find the last day of the month */ tmbuf->tm_mday = daysinmonth[tmbuf->tm_mon]; if (tmbuf->tm_mon == 1) { if (((tmbuf->tm_year + 1900) % 4) == 0) { tmbuf->tm_mday = 29; if (((tmbuf->tm_year + 1900) % 100) == 0) tmbuf->tm_mday = 28; if (((tmbuf->tm_year + 1900) % 400) == 0) tmbuf->tm_mday = 29; } } tmbuf->tm_hour = 23; tmbuf->tm_min = 59; tmbuf->tm_sec = 59; } else if (endofperiod == END_START) { tmbuf->tm_mday = 1; tmbuf->tm_hour = tmbuf->tm_min = tmbuf->tm_sec = 0; } break; } tmbuf->tm_isdst = dstsetting; result = mktime(tmbuf); if ((dstsetting == -1) && (endofperiod == END_END) && (result < endtime)) { /* DST->normaltime switchover - redo with forced DST setting */ dbgprintf("DST rollover with endtime/change/alignment/endodperiod = %u/%d/%d/%d\n", (unsigned int)endtime, change, alignment, endofperiod); dstsetting = 0; goto again; } /* Dont try to foresee the future */ now = getcurrenttime(NULL); if (result > now) result = now; return (unsigned int)result; } static int maxcolor(replog_t *periodlog, time_t begintime, time_t endtime) { int result = COL_GREEN; replog_t *walk = periodlog; while (walk) { if (walk->color > result) { /* * We want this event, IF: * - it starts sometime during begintime -> endtime, or * - it starts before begintime, but lasts into after begintime. */ if ( ((walk->starttime >= begintime) && (walk->starttime < endtime)) || ((walk->starttime < begintime) && ((walk->starttime + walk->duration) >= begintime)) ) { result = walk->color; } } walk = walk->next; } return result; } static void generate_colorbar( FILE *htmlrep, /* Output file */ time_t begintime, time_t endtime, int alignment, /* Align by hour/day/month */ int bartype, /* Day/Week/Month/Year bar */ char *hostname, char *service, char *caption, /* Title */ replog_t *periodlog, /* Log entries for period */ reportinfo_t *repinfo) /* Info for the percent summary */ { int secsperpixel; char *pctstr = ""; replog_t *colorlog, *walk; int changeval = 0; int changealign = 0; /* * Pixel-based charts are better, but for backwards * compatibility allow for a graph that has 100 "pixels" * and adds a "%" to the width specs. */ if (usepct) { pixels = 100; pctstr = "%"; } /* How many seconds required for 1 pixel */ secsperpixel = ((endtime - begintime) / pixels); /* Need to re-sort the period-log to chronological order */ colorlog = NULL; { replog_t *tmp; for (walk = periodlog; (walk); walk = tmp) { tmp = walk->next; walk->next = colorlog; colorlog = walk; walk = tmp; } } /* Determine the back/forward link times */ switch (bartype) { case DAY_BAR : changeval = len1d; changealign = ALIGN_HOUR; break; case WEEK_BAR : changeval = len1w; changealign = ALIGN_DAY; break; case MONTH_BAR : changeval = len4w; changealign = ALIGN_DAY; break; case YEAR_BAR : changeval = len1y; changealign = ALIGN_MONTH; break; } /* Beginning of page */ fprintf(htmlrep, "\n", pixels, pctstr); fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n"); fprintf(htmlrep, "
\n"); /* The date stamps, percent summaries and zoom/reset links */ fprintf(htmlrep, "\n", caption); fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n", barbkgcolor); fprintf(htmlrep, "
\n", barbkgcolor); fprintf(htmlrep, " \n"); if (usepct) { fprintf(htmlrep, " \n", selfurl, (usepct ? 0 : pixels)); } else { fprintf(htmlrep, " \n", selfurl, (unsigned int)endtime, pixels+200); if (pixels > 200) { fprintf(htmlrep, " \n", selfurl, (unsigned int)endtime, pixels-200); } } fprintf(htmlrep, " \n"); fprintf(htmlrep, "
Time reset
Zoom +
Zoom -

\n"); if (colorlog && colorlog->starttime <= begintime) { fprintf(htmlrep, "", selfurl, calc_time(endtime, -changeval, changealign, END_UNCHANGED), (usepct ? 0 : pixels)); } fprintf(htmlrep, "%s", ctime(&begintime)); if (colorlog && colorlog->starttime <= begintime) fprintf(htmlrep, ""); fprintf(htmlrep, "\n
\n"); fprintf(htmlrep, "
\n"); generate_pct_summary(htmlrep, hostname, service, caption, repinfo, secsperpixel); fprintf(htmlrep, "\n"); fprintf(htmlrep, " \n"); fprintf(htmlrep, " \n", selfurl, (usepct ? 0 : pixels)); if (!usepct) { fprintf(htmlrep, " \n", selfurl, (unsigned int)endtime, DEFPIXELS); } fprintf(htmlrep, " \n"); fprintf(htmlrep, "
Time reset
Zoom reset

\n"); fprintf(htmlrep, " ", selfurl, calc_time(endtime, +changeval, changealign, END_UNCHANGED), (usepct ? 0 : pixels)); fprintf(htmlrep, "%s", ctime(&endtime)); fprintf(htmlrep, "\n"); fprintf(htmlrep, "
\n"); fprintf(htmlrep, "

\n"); /* The period marker line */ fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n"); { time_t begininterval = begintime; time_t endofinterval; char tag[20]; char *bgcols[2] = { "\"#000000\"", "\"#555555\"" }; int curbg = 0; int intervalpixels, tagcolor; time_t minduration = 1800; struct tm *tmbuf; do { endofinterval = calc_time(begininterval, 0, alignment, END_END); dbgprintf("Period starts %u ends %u - %s", (unsigned int)begininterval, (unsigned int)endofinterval, ctime(&endofinterval)); tmbuf = localtime(&begininterval); switch (bartype) { case DAY_BAR : minduration = 1800; strftime(tag, sizeof(tag), "%H", tmbuf); break; case WEEK_BAR : minduration = 14400; strftime(tag, sizeof(tag), "%a", tmbuf); break; case MONTH_BAR : minduration = 43200; strftime(tag, sizeof(tag), "%d", tmbuf); break; case YEAR_BAR : minduration = 10*86400; strftime(tag, sizeof(tag), "%b", tmbuf); break; } intervalpixels = ((endofinterval - begininterval) / secsperpixel); tagcolor = maxcolor(colorlog, begininterval, endofinterval); fprintf(htmlrep, "\n"); curbg = (1 - curbg); if ((endofinterval + 1) <= begininterval) { /* * This should not happen! */ fprintf(htmlrep, "Time moves backwards! begintime=%u, alignment=%d, begininterval=%u\n", (unsigned int)begintime, alignment, (unsigned int)begininterval); begininterval = endtime; } begininterval = endofinterval + 1; } while (begininterval < endtime); } fprintf(htmlrep, "\n"); fprintf(htmlrep, "
", intervalpixels, pctstr, bgcols[curbg]); if ((endofinterval - begininterval) > minduration) { int dolink = (colorlog && endofinterval >= colorlog->starttime); if (dolink) fprintf(htmlrep, "", selfurl, (unsigned int)endofinterval, (usepct ? 0 : pixels)); fprintf(htmlrep, "%s", tagcolors[tagcolor], tag); if (dolink) fprintf(htmlrep, ""); } fprintf(htmlrep, "
\n"); /* The actual color bar */ fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n"); /* First entry may not start at our report-start time */ if (colorlog == NULL) { /* No data for period - all white */ fprintf(htmlrep, "\n"); } else if (colorlog->starttime > begintime) { /* Data starts after the bar does - so a white period in front */ int pixels = ((colorlog->starttime - begintime) / secsperpixel); if (((colorlog->starttime - begintime) >= (secsperpixel/2)) && (pixels == 0)) pixels = 1; if (pixels > 0) { fprintf(htmlrep, "\n", pixels, pctstr, "white"); } } for (walk = colorlog; (walk); walk = walk->next) { /* Show each interval we have data for */ int pixels = (walk->duration / secsperpixel); /* Intervals that give between 0.5 and 1 pixel are enlarged */ if ((walk->duration >= (secsperpixel/2)) && (pixels == 0)) pixels = 1; if (pixels > 0) { fprintf(htmlrep, "\n", pixels, pctstr, ((walk->color == COL_CLEAR) ? "white" : colorname(walk->color))); } } fprintf(htmlrep, "\n"); fprintf(htmlrep, "
   
\n"); fprintf(htmlrep, "
\n"); fprintf(htmlrep, "

\n"); } static void generate_histlog_table(FILE *htmlrep, char *hostname, char *service, int entrycount, replog_t *loghead) { char *bgcols[2] = { "\"#000000\"", "\"#000033\"" }; int curbg = 0; replog_t *walk; fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n"); if (entrycount) { fprintf(htmlrep, "\n", selfurl, (unsigned int)req_endtime, (usepct ? 0 : pixels)); } else { fprintf(htmlrep, "\n"); } fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n", xgetenv("XYMONPAGECOLFONT")); fprintf(htmlrep, "\n", xgetenv("XYMONPAGECOLFONT")); fprintf(htmlrep, "\n", xgetenv("XYMONPAGECOLFONT")); fprintf(htmlrep, "\n"); for (walk = loghead; (walk); walk = walk->next) { char start[30]; strftime(start, sizeof(start), "%a %b %d %H:%M:%S %Y", localtime(&walk->starttime)); fprintf(htmlrep, "\n", bgcols[curbg]); curbg = (1-curbg); fprintf(htmlrep, "\n", start); fprintf(htmlrep, "\n"); fprintf(htmlrep, "\n", durationstr(walk->duration)); fprintf(htmlrep, "\n\n"); } fprintf(htmlrep, "
Last %d log entries ", entrycount); fprintf(htmlrep, "(Full HTML log)All log entries
DateStatusDuration
%s"); fprintf(htmlrep, "", histlogurl(hostname, service, 0, walk->timespec)); fprintf(htmlrep, "\"%s\"", xgetenv("XYMONSKIN"), dotgiffilename(walk->color, 0, 1), colorname(walk->color), colorname(walk->color), xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); fprintf(htmlrep, "%s
\n"); } void generate_history(FILE *htmlrep, /* output file */ char *hostname, char *service, /* Host and service we report on */ char *ip, /* IP - for the header only */ time_t endtime, /* End time of color-bar graphs */ time_t start1d, /* Start time of 1-day period */ reportinfo_t *repinfo1d, /* Percent summaries for 1-day period */ replog_t *log1d, /* Events during past 1 day */ time_t start1w, /* Start time of 1-week period */ reportinfo_t *repinfo1w, /* Percent summaries for 1-week period */ replog_t *log1w, /* Events during past 1 week */ time_t start4w, /* Start time of 4-week period */ reportinfo_t *repinfo4w, /* Percent summaries for 4-week period */ replog_t *log4w, /* Events during past 4 weeks */ time_t start1y, /* Start time of 1-year period */ reportinfo_t *repinfo1y, /* Percent summaries for 1-year period */ replog_t *log1y, /* Events during past 1 yeary */ int entrycount, /* Log entry maxcount */ replog_t *loghead) /* Eventlog for entrycount events back */ { sethostenv(displayname, ip, service, colorname(COL_GREEN), hostname); headfoot(htmlrep, "hist", "", "header", COL_GREEN); fprintf(htmlrep, "\n"); fprintf(htmlrep, "
\n"); if (wantserviceid) { fprintf(htmlrep, "
%s", xgetenv("XYMONPAGEROWFONT"), htmlquoted(displayname)); fprintf(htmlrep, " - %s
\n", htmlquoted(service)); } /* Create the color-bars */ if (log1d) { /* 1-day bar */ generate_colorbar(htmlrep, start1d, endtime, ALIGN_HOUR, DAY_BAR, hostname, service, bartitle1d, log1d, repinfo1d); } if (log1w) { /* 1-week bar */ generate_colorbar(htmlrep, start1w, endtime, ALIGN_DAY, WEEK_BAR, hostname, service, bartitle1w, log1w, repinfo1w); } if (log4w) { /* 4-week bar */ generate_colorbar(htmlrep, start4w, endtime, ALIGN_DAY, MONTH_BAR, hostname, service, bartitle4w, log4w, repinfo4w); } if (log1y) { /* 1-year bar */ generate_colorbar(htmlrep, start1y, endtime, ALIGN_MONTH, YEAR_BAR, hostname, service, bartitle1y, log1y, repinfo1y); } /* Last N histlog entries */ fprintf(htmlrep, "
\n"); generate_histlog_table(htmlrep, hostname, service, entrycount, loghead); fprintf(htmlrep, "
\n"); fprintf(htmlrep, "

\n"); /* XYMONHISTEXT extensions */ do_extensions(htmlrep, "XYMONHISTEXT", "hist"); fprintf(htmlrep, "
\n"); headfoot(htmlrep, "hist", "", "footer", COL_GREEN); } double reportgreenlevel = 99.995; double reportwarnlevel = 98.0; int reportwarnstops = -1; char *hostname = ""; char *service = ""; char *ip = ""; int entrycount = 50; cgidata_t *cgidata = NULL; char *reqenv[] = { "XYMONHISTDIR", "XYMONHISTLOGS", "XYMONREPDIR", "XYMONREPURL", "XYMONSKIN", "CGIBINURL", "DOTWIDTH", "DOTHEIGHT", "XYMONPAGECOLFONT", "XYMONPAGEROWFONT", NULL }; static void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } static void parse_query(void) { cgidata_t *cwalk; cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ if (strcasecmp(cwalk->name, "HISTFILE") == 0) { char *p = strrchr(cwalk->value, '.'); if (p) { *p = '\0'; service = strdup(p+1); } hostname = strdup(basename(cwalk->value)); while ((p = strchr(hostname, ','))) *p = '.'; } else if (strcasecmp(cwalk->name, "IP") == 0) { ip = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "ENTRIES") == 0) { if (strcmp(cwalk->value, "all") == 0) entrycount = 0; else entrycount = atoi(cwalk->value); if (entrycount < 0) errormsg("Invalid parameter"); } else if (strcasecmp(cwalk->name, "PIXELS") == 0) { pixels = atoi(cwalk->value); if (pixels > 0) usepct = 0; else usepct = 1; } else if (strcasecmp(cwalk->name, "ENDTIME") == 0) { req_endtime = atol(cwalk->value); if (req_endtime < 0) errormsg("Invalid parameter"); } else if (strcasecmp(cwalk->name, "BARSUMS") == 0) { barsums = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "DISPLAYNAME") == 0) { displayname = strdup(cwalk->value); } cwalk = cwalk->next; } if (!displayname) displayname = strdup(hostname); } int main(int argc, char *argv[]) { char histlogfn[PATH_MAX]; char tailcmd[PATH_MAX]; FILE *fd; time_t start1d, start1w, start4w, start1y; reportinfo_t repinfo1d, repinfo1w, repinfo4w, repinfo1y, dummyrep; replog_t *log1d, *log1w, *log4w, *log1y; char *p; int argi; char *envarea = NULL; for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--no-svcid") == 0) { wantserviceid = 0; } } redirect_cgilog("history"); envcheck(reqenv); cgidata = cgi_request(); parse_query(); /* Build our own URL */ sprintf(selfurl, "%s", histcgiurl(hostname, service)); p = selfurl + strlen(selfurl); sprintf(p, "&BARSUMS=%d", barsums); if (strlen(ip)) { p = selfurl + strlen(selfurl); sprintf(p, "&IP=%s", htmlquoted(ip)); } if (entrycount) { p = selfurl + strlen(selfurl); sprintf(p, "&ENTRIES=%d", entrycount); } else strcat(selfurl, "&ENTRIES=ALL"); if (usepct) { /* Must modify 4-week charts to be 5-weeks, or the last day is 19% of the bar */ /* * Percent-based charts look awful with 24 hours / 7 days / 28 days / 12 months as basis * because these numbers dont divide into 100 neatly. So the last item becomes * too large (worst with the 28-day char: 100/28 = 3, last becomes (100-27*3) = 19% wide). * So adjust the periods to something that matches percent-based calculations better. */ len1d = 25; bartitle1d = "25 hour summary"; len1w = 10; bartitle1w = "10 day summary"; len4w = 33; bartitle4w = "33 day summary"; len1y = 10; bartitle1y = "10 month summary"; } sprintf(histlogfn, "%s/%s.%s", xgetenv("XYMONHISTDIR"), commafy(hostname), service); fd = fopen(histlogfn, "r"); if (fd == NULL) { errormsg("Cannot open history file"); } log1d = log1w = log4w = log1y = NULL; if (req_endtime == 0) req_endtime = getcurrenttime(NULL); /* * Calculate the beginning time of each colorbar. We go back the specified length * of time, except 1 second - so days are from midnight -> 23:59:59 etc. */ start1d = calc_time(req_endtime, -len1d, ALIGN_HOUR, END_UNCHANGED) + 1; start1w = calc_time(req_endtime, -len1w, ALIGN_DAY, END_UNCHANGED) + 1; start4w = calc_time(req_endtime, -len4w, ALIGN_DAY, END_UNCHANGED) + 1; start1y = calc_time(req_endtime, -len1y, ALIGN_MONTH, END_UNCHANGED) + 1; /* * Collect data for the color-bars and summaries. Multiple scans over the history file, * but doing it all in one go would be hideously complex. */ if (barsums & BARSUM_1D) { parse_historyfile(fd, &repinfo1d, NULL, NULL, start1d, req_endtime, 1, reportwarnlevel, reportgreenlevel, reportwarnstops, NULL); log1d = save_replogs(); } if (barsums & BARSUM_1W) { parse_historyfile(fd, &repinfo1w, NULL, NULL, start1w, req_endtime, 1, reportwarnlevel, reportgreenlevel, reportwarnstops, NULL); log1w = save_replogs(); } if (barsums & BARSUM_4W) { parse_historyfile(fd, &repinfo4w, NULL, NULL, start4w, req_endtime, 1, reportwarnlevel, reportgreenlevel, reportwarnstops, NULL); log4w = save_replogs(); } if (barsums & BARSUM_1Y) { parse_historyfile(fd, &repinfo1y, NULL, NULL, start1y, req_endtime, 1, reportwarnlevel, reportgreenlevel, reportwarnstops, NULL); log1y = save_replogs(); } if (entrycount == 0) { /* All entries - just rewind the history file and do all of them */ rewind(fd); parse_historyfile(fd, &dummyrep, NULL, NULL, 0, getcurrenttime(NULL), 1, reportwarnlevel, reportgreenlevel, reportwarnstops, NULL); fclose(fd); } else { /* Last 50 entries - we cheat and use "tail" in a pipe to pick the entries */ fclose(fd); sprintf(tailcmd, "tail -%d %s", entrycount, histlogfn); fd = popen(tailcmd, "r"); if (fd == NULL) errormsg("Cannot run tail on the histfile"); parse_historyfile(fd, &dummyrep, NULL, NULL, 0, getcurrenttime(NULL), 1, reportwarnlevel, reportgreenlevel, reportwarnstops, NULL); pclose(fd); } /* Now generate the webpage */ printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); generate_history(stdout, hostname, service, ip, req_endtime, start1d, &repinfo1d, log1d, start1w, &repinfo1w, log1w, start4w, &repinfo4w, log4w, start1y, &repinfo1y, log1y, entrycount, reploghead); return 0; } xymon-4.3.7/web/report.c0000664000175000017500000002464011615341300014457 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon report generation front-end. */ /* */ /* This is a front-end CGI that lets the user select reporting parameters, */ /* and then invokes xymongen to generate the report. When the report is */ /* ready, the user's browser is sent off to view the report. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: report.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" char *reqenv[] = { "XYMONHOME", "XYMONREPDIR", "XYMONREPURL", NULL }; char *style = ""; time_t starttime = 0; time_t endtime = 0; char *suburl = ""; int csvoutput = 0; char csvdelim = ','; cgidata_t *cgidata = NULL; char *monthnames[13] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } void parse_query(void) { cgidata_t *cwalk; int startday, startmon, startyear; int endday, endmon, endyear; struct tm tmbuf; startday = startmon = startyear = endday = endmon = endyear = -1; cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ if (strcasecmp(cwalk->name, "start-day") == 0) { startday = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "start-mon") == 0) { char *errptr; startmon = strtol(cwalk->value, &errptr, 10) - 1; if (errptr == cwalk->value) { for (startmon=0; (monthnames[startmon] && strcmp(cwalk->value, monthnames[startmon])); startmon++) ; if (startmon >= 12) startmon = -1; } } else if (strcasecmp(cwalk->name, "start-yr") == 0) { startyear = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "end-day") == 0) { endday = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "end-mon") == 0) { char *errptr; endmon = strtol(cwalk->value, &errptr, 10) - 1; if (errptr == cwalk->value) { for (endmon=0; (monthnames[endmon] && strcmp(cwalk->value, monthnames[endmon])); endmon++) ; if (endmon > 12) endmon = -1; } } else if (strcasecmp(cwalk->name, "end-yr") == 0) { endyear = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "style") == 0) { style = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "suburl") == 0) { suburl = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "DoReport") == 0) { csvoutput = 0; } else if (strcasecmp(cwalk->name, "DoCSV") == 0) { csvoutput = 1; } else if (strcasecmp(cwalk->name, "csvdelim") == 0) { csvdelim = *cwalk->value; } cwalk = cwalk->next; } memset(&tmbuf, 0, sizeof(tmbuf)); tmbuf.tm_mday = startday; tmbuf.tm_mon = startmon; tmbuf.tm_year = startyear - 1900; tmbuf.tm_hour = 0; tmbuf.tm_min = 0; tmbuf.tm_sec = 0; tmbuf.tm_isdst = -1; /* Important! Or we mishandle DST periods */ starttime = mktime(&tmbuf); memset(&tmbuf, 0, sizeof(tmbuf)); tmbuf.tm_mday = endday; tmbuf.tm_mon = endmon; tmbuf.tm_year = endyear - 1900; tmbuf.tm_hour = 23; tmbuf.tm_min = 59; tmbuf.tm_sec = 59; tmbuf.tm_isdst = -1; /* Important! Or we mishandle DST periods */ endtime = mktime(&tmbuf); if ((starttime == -1) || (endtime == -1) || (starttime > getcurrenttime(NULL))) errormsg("Invalid parameters"); if (endtime > getcurrenttime(NULL)) endtime = getcurrenttime(NULL); if (starttime > endtime) { /* Swap start and end times */ time_t tmp; tmp = endtime; endtime = starttime; starttime = tmp; } } void cleandir(char *dirname) { DIR *dir; struct dirent *d; struct stat st; char fn[PATH_MAX]; time_t killtime = getcurrenttime(NULL)-86400; dir = opendir(dirname); if (dir == NULL) return; while ((d = readdir(dir))) { if (d->d_name[0] != '.') { sprintf(fn, "%s/%s", dirname, d->d_name); if ((stat(fn, &st) == 0) && (st.st_mtime < killtime)) { if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { dbgprintf("rm %s\n", fn); unlink(fn); } else if (S_ISDIR(st.st_mode)) { dbgprintf("Cleaning directory %s\n", fn); cleandir(fn); dbgprintf("rmdir %s\n", fn); rmdir(fn); } else { /* Ignore file */ }; } } } } int main(int argc, char *argv[]) { char dirid[PATH_MAX]; char outdir[PATH_MAX]; char xymonwebenv[PATH_MAX]; char xymongencmd[PATH_MAX]; char xymongentimeopt[100]; char csvdelimopt[100]; char *xymongen_argv[20]; pid_t childpid; int childstat; char htmldelim[100]; char startstr[30], endstr[30]; int cleanupoldreps = 1; int argi, newargi; char *envarea = NULL; char *useragent = NULL; int usemultipart = 1; newargi = 0; xymongen_argv[newargi++] = xymongencmd; xymongen_argv[newargi++] = xymongentimeopt; for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[1], "--noclean") == 0) { cleanupoldreps = 0; } else { xymongen_argv[newargi++] = argv[argi]; } } redirect_cgilog("report"); cgidata = cgi_request(); if (cgidata == NULL) { /* Present the query form */ sethostenv("", "", "", colorname(COL_BLUE), NULL); printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); showform(stdout, "report", "report_form", COL_BLUE, getcurrenttime(NULL)-86400, NULL, NULL); return 0; } useragent = getenv("HTTP_USER_AGENT"); if (useragent && strstr(useragent, "KHTML")) { /* KHTML (Konqueror, Safari) cannot handle multipart documents. */ usemultipart = 0; } envcheck(reqenv); parse_query(); /* * We need to set these variables up AFTER we have put them into the xymongen_argv[] array. * We cannot do it before, because we need the environment that the command-line options * might provide. */ if (xgetenv("XYMONGEN")) sprintf(xymongencmd, "%s", xgetenv("XYMONGEN")); else sprintf(xymongencmd, "%s/bin/xymongen", xgetenv("XYMONHOME")); snprintf(xymongentimeopt, sizeof(xymongentimeopt)-1,"--reportopts=%u:%u:1:%s", (unsigned int)starttime, (unsigned int)endtime, style); sprintf(dirid, "%u-%u", (unsigned int)getpid(), (unsigned int)getcurrenttime(NULL)); if (!csvoutput) { sprintf(outdir, "%s/%s", xgetenv("XYMONREPDIR"), dirid); mkdir(outdir, 0755); xymongen_argv[newargi++] = outdir; sprintf(xymonwebenv, "XYMONWEB=%s/%s", xgetenv("XYMONREPURL"), dirid); putenv(xymonwebenv); } else { sprintf(outdir, "--csv=%s/%s.csv", xgetenv("XYMONREPDIR"), dirid); xymongen_argv[newargi++] = outdir; sprintf(csvdelimopt, "--csvdelim=%c", csvdelim); xymongen_argv[newargi++] = csvdelimopt; } xymongen_argv[newargi++] = NULL; if (usemultipart) { /* Output the "please wait for report ... " thing */ snprintf(htmldelim, sizeof(htmldelim)-1, "xymonrep-%u-%u", (int)getpid(), (unsigned int)getcurrenttime(NULL)); printf("Content-type: multipart/mixed;boundary=%s\n", htmldelim); printf("\n"); printf("--%s\n", htmldelim); printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); /* It's ok with these hardcoded values, as they are not used for this page */ sethostenv("", "", "", colorname(COL_BLUE), NULL); sethostenv_report(starttime, endtime, 97.0, 99.995); headfoot(stdout, "repnormal", "", "header", COL_BLUE); strftime(startstr, sizeof(startstr), "%b %d %Y", localtime(&starttime)); strftime(endstr, sizeof(endstr), "%b %d %Y", localtime(&endtime)); printf("
 \n"); printf("



\n"); printf("

Generating report for the period: %s", htmlquoted(startstr)); printf(" - %s ", htmlquoted(endstr)); printf("(%s)
\n", htmlquoted(style)); printf("

\n"); fflush(stdout); } /* Go do the report */ childpid = fork(); if (childpid == 0) { execv(xymongencmd, xymongen_argv); } else if (childpid > 0) { wait(&childstat); /* Ignore SIGHUP so we dont get killed during cleanup of XYMONREPDIR */ signal(SIGHUP, SIG_IGN); if (WIFEXITED(childstat) && (WEXITSTATUS(childstat) != 0) ) { char msg[4096]; if (usemultipart) printf("--%s\n\n", htmldelim); sprintf(msg, "Could not generate report.
\nCheck that the %s/www/rep/ directory has permissions '-rwxrwxr-x' (775)
\n and that is is set to group %d", xgetenv("XYMONHOME"), (int)getgid()); errormsg(msg); } else { /* Send the browser off to the report */ if (usemultipart) { printf("Done...Report is here.

\n", xgetenv("XYMONREPURL"), dirid, suburl); fflush(stdout); printf("--%s\n\n", htmldelim); } printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("\n"); if (!csvoutput) { printf("Report is available here\n", xgetenv("XYMONREPURL"), dirid, suburl); } else { printf("Report is available here\n", xgetenv("XYMONREPURL"), dirid); } if (usemultipart) printf("\n--%s\n", htmldelim); fflush(stdout); } if (cleanupoldreps) cleandir(xgetenv("XYMONREPDIR")); } else { if (usemultipart) printf("--%s\n\n", htmldelim); printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); errormsg("Fork failed"); } return 0; } xymon-4.3.7/web/eventlog.sh.DIST0000664000175000017500000000025111535462534015727 0ustar henrikhenrik#!/bin/sh # This is the Xymon CGI script to display the eventlog . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/eventlog.cgi $CGI_EVENTLOG_OPTS xymon-4.3.7/web/ghostlist.cgi.10000664000175000017500000000211611671641417015653 0ustar henrikhenrik.TH GHOSTLIST.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME ghostlist.cgi \- CGI program to view ghost clients .SH SYNOPSIS .B "ghostlist.cgi" .SH DESCRIPTION \fBghostlist.cgi\fR is invoked as a CGI script via the ghostlist.sh CGI wrapper. It generates a listing of the Xymon clients that have reported data to the Xymon server, but are not listed in the .I hosts.cfg(5) file. Data from these clients - called "ghosts" - are ignored, since Xymon does not know which webpage to present the data on. The listing includes the hostname that the client reports with, the IP-address where the report came from, and how long ago the report arrived. By far the most common reason for hosts showing up here is that the client uses a hostname without a DNS domain, but the hosts.cfg file uses the hostname with the DNS domain. Or vice versa. You can then use a \fBCLIENT\fR setting in the hosts.cfg file to match the two hostnames together. .SH OPTIONS .IP "--env=FILENAME" Loads the environment defined in FILENAME before executing the CGI script. .SH "SEE ALSO" hosts.cfg(5), xymonserver.cfg(5) xymon-4.3.7/web/xymonpage.cgi.10000664000175000017500000000161111671641417015641 0ustar henrikhenrik.TH XYMONPAGE 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymonpage \- Utility to show a webpage using header and footer .SH SYNOPSIS .B "xymonpage [options]" .SH DESCRIPTION \fBxymonpage\fR is a tool to generate a webpage in the Xymon style, with a standard header- and footer as well as a Xymon background. The data to present on the webpage, apart from the header and footer, are passed to xymonpage in stdin. The generated webpage is printed to stdout. .SH OPTIONS .IP "--env=FILENAME" Loads the environment defined in FILENAME before executing the CGI script. .IP "--hffile=PREFIX" Use the header- and footer-files in $XYMONHOME/web/PREFIX_header and PREFIX_footer. If not specified, stdnormal_header and stdnormal_footer are used. .IP "--color=COLOR" Set the background color of the generated webpage to COLOR. Default: Blue .IP "--debug" Enable debugging output. .SH "SEE ALSO" xymon(7) xymon-4.3.7/web/confreport.sh.DIST0000664000175000017500000000035611535462534016273 0ustar henrikhenrik#!/bin/sh # This is the Xymon CGI script interface to confreport.cgi # # Install this script in your webservers' cgi-bin directory . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/confreport.cgi $CGI_CONFREPORT_OPTS xymon-4.3.7/web/history.sh.DIST0000664000175000017500000000024211535462534015605 0ustar henrikhenrik#!/bin/sh # This is the Xymon wrapper for the history.cgi tool . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/history.cgi $CGI_HIST_OPTS xymon-4.3.7/web/datepage.c0000664000175000017500000001241011630732307014716 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon webpage generator tool. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: datepage.c 6749 2011-09-04 17:26:31Z storner $"; #include #include #include #include #include #include #include #include #include "libxymon.h" #include "version.h" static enum { FRM_NONE, FRM_MONTH, FRM_WEEK, FRM_DAY } frmtype = FRM_NONE; static int year = -1; static int month = -1; static int day = -1; static int week = -1; static void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } static void parse_query(void) { cgidata_t *cgidata = cgi_request(); cgidata_t *cwalk; if (cgidata == NULL) { errormsg(cgi_error()); } cwalk = cgidata; while (cwalk) { if (strcasecmp(cwalk->name, "YEAR") == 0) { year = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "MONTH") == 0) { month = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "DAY") == 0) { day = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "WEEK") == 0) { week = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "TYPE") == 0) { if (strcasecmp(cwalk->value, "month") == 0) frmtype = FRM_MONTH; else if (strcasecmp(cwalk->value, "week") == 0) frmtype = FRM_WEEK; else if (strcasecmp(cwalk->value, "day") == 0) frmtype = FRM_DAY; else errormsg("Bad type parameter\n"); } cwalk = cwalk->next; } } int main(int argc, char *argv[]) { int argi; char *hffile = "report"; char *urlprefix = ""; int bgcolor = COL_BLUE; char *envarea = NULL; for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--hffile=")) { char *p = strchr(argv[argi], '='); hffile = strdup(p+1); } else if (argnmatch(argv[argi], "--color=")) { char *p = strchr(argv[argi], '='); bgcolor = parse_color(p+1); } else if (argnmatch(argv[argi], "--url=")) { char *p = strchr(argv[argi], '='); urlprefix = strdup(p+1); } } redirect_cgilog("datepage"); parse_query(); if (cgi_method == CGI_POST) { char *pagepath, *cookie, *endurl; cookie = get_cookie("pagepath"); if (cookie && *cookie) { pagepath = strdup(cookie); } else { cookie = get_cookie("host"); if (cookie && *cookie) { void *hinfo; load_hostinfo(cookie); hinfo = hostinfo(cookie); if (hinfo) { pagepath = xmh_item(hinfo, XMH_PAGEPATH); } else { pagepath = ""; } } else { pagepath = ""; } } endurl = (char *)malloc(strlen(urlprefix) + strlen(pagepath) + 1024); switch (frmtype) { case FRM_DAY: if ((year == -1) || (month == -1) || (day == -1)) errormsg("Invalid day-request"); sprintf(endurl, "%s/daily/%d/%02d/%02d/%s", urlprefix, year, month, day, pagepath); break; case FRM_WEEK: if ((year == -1) || (week == -1)) errormsg("Invalid week-request"); sprintf(endurl, "%s/weekly/%d/%02d/%s", urlprefix, year, week, pagepath); break; case FRM_MONTH: if ((year == -1) || (month == -1)) errormsg("Invalid month-request"); sprintf(endurl, "%s/monthly/%d/%02d/%s", urlprefix, year, month, pagepath); break; case FRM_NONE: break; } if (*pagepath) strcat(endurl, "/"); fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); fprintf(stdout, "\n", endurl); } else if (cgi_method == CGI_GET) { char formfn[PATH_MAX]; time_t seltime; struct tm *seltm; seltime = getcurrenttime(NULL); seltm = localtime(&seltime); /* Present the query form */ switch (frmtype) { case FRM_DAY: seltm->tm_mday -= 1; seltime = mktime(seltm); sprintf(formfn, "%s_form_daily", hffile); break; case FRM_WEEK: seltm->tm_mday -= 7; seltime = mktime(seltm); sprintf(formfn, "%s_form_weekly", hffile); break; case FRM_MONTH: seltm->tm_mon -= 1; seltime = mktime(seltm); sprintf(formfn, "%s_form_monthly", hffile); break; case FRM_NONE: errormsg("No report type defined"); } sethostenv("", "", "", colorname(bgcolor), NULL); fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); showform(stdout, hffile, formfn, COL_BLUE, seltime, NULL, NULL); } return 0; } xymon-4.3.7/web/notifications.c0000664000175000017500000001031611615341300016010 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon notification log viewer */ /* */ /* Copyright (C) 2007-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: notifications.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" int maxcount = 100; /* Default: Include last 100 events */ int maxminutes = 1440; /* Default: for the past 24 hours */ char *totime = NULL; char *fromtime = NULL; char *hostregex = NULL; char *exhostregex = NULL; char *testregex = NULL; char *extestregex = NULL; char *pageregex = NULL; char *expageregex = NULL; char *rcptregex = NULL; char *exrcptregex = NULL; cgidata_t *cgidata = NULL; static void parse_query(void) { cgidata_t *cwalk; cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ if (strcasecmp(cwalk->name, "MAXCOUNT") == 0) { maxcount = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "MAXTIME") == 0) { maxminutes = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "FROMTIME") == 0) { if (*(cwalk->value)) fromtime = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "TOTIME") == 0) { if (*(cwalk->value)) totime = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "HOSTMATCH") == 0) { if (*(cwalk->value)) hostregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "EXHOSTMATCH") == 0) { if (*(cwalk->value)) exhostregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "TESTMATCH") == 0) { if (*(cwalk->value)) testregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "EXTESTMATCH") == 0) { if (*(cwalk->value)) extestregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "PAGEMATCH") == 0) { if (*(cwalk->value)) pageregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "EXPAGEMATCH") == 0) { if (*(cwalk->value)) expageregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "RCPTMATCH") == 0) { if (*(cwalk->value)) rcptregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "EXRCPTMATCH") == 0) { if (*(cwalk->value)) exrcptregex = strdup(cwalk->value); } cwalk = cwalk->next; } } int main(int argc, char *argv[]) { int argi; char *envarea = NULL; for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } } redirect_cgilog("notifications"); load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); cgidata = cgi_request(); if (cgidata == NULL) { /* Present the query form */ sethostenv("", "", "", colorname(COL_BLUE), NULL); showform(stdout, "notify", "notify_form", COL_BLUE, getcurrenttime(NULL), NULL, NULL); return 0; } parse_query(); /* Now generate the webpage */ headfoot(stdout, "notify", "", "header", COL_GREEN); fprintf(stdout, "
\n"); do_notifylog(stdout, maxcount, maxminutes, fromtime, totime, pageregex, expageregex, hostregex, exhostregex, testregex, extestregex, rcptregex, exrcptregex); fprintf(stdout, "
\n"); headfoot(stdout, "notify", "", "footer", COL_GREEN); return 0; } xymon-4.3.7/web/ackinfo.sh.DIST0000664000175000017500000000024411535462534015520 0ustar henrikhenrik#!/bin/sh # This is a wrapper for the Xymon ackinfo.cgi script . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/ackinfo.cgi $CGI_ACKINFO_OPTS xymon-4.3.7/web/confreport.cgi.10000664000175000017500000000342011671641417016013 0ustar henrikhenrik.TH CONFREPORT.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME confreport.cgi \- Xymon Configuration report .SH SYNOPSIS .B "confreport.cgi" .SH DESCRIPTION \fBconfreport.cgi\fR is invoked as a CGI script via the confreport.sh CGI wrapper. \fBconfreport.cgi\fR provides a plain HTML (Web) report of the Xymon configuration for a group of hosts; which hosts are included is determined by the hosts available on the webpage from where the CGI script is invoked. The configuration report include the hostnames, a list of the statuses monitored for each host, and if applicable any configuration settings affecting these. Alerts that may be triggered by status changes are also included. The report is plain HTML without any images included, and therefore suitable for inclusion into e-mails or other documents that may be accessed outside the Xymon system. .SH OPTIONS .IP "--critical" Report only on the statuses that are configured to show up on the \fBCritical Systems\fR view. .IP "--old-nk-config" Use the deprecated \fBNK\fR tag in hosts.cfg to determine if tests appear on the Critical Systems view. .IP "--env=FILENAME" Loads the environment defined in FILENAME before executing the CGI script. .IP "--area=NAME" Load environment variables for a specific area. NB: if used, this option must appear before any --env=FILENAME option. .IP "--debug" Enables debugging output. .IP "--nkconfig=FILENAME" Use FILENAME as the configuration file for the Critical Systems information. The default is to load this from $XYMONHOME/etc/critical.cfg .SH BUGS Client-side configuration done in the .I analysis.cfg(5) is not currently reflected in the report. Critical Systems view configuration is not reflected in the report. .SH "SEE ALSO" hosts.cfg(5), alerts.cfg(5), analysis.cfg(5), xymon(7) xymon-4.3.7/web/cgioptions.cfg.50000664000175000017500000000436111671641417016016 0ustar henrikhenrik.TH CGIOPTIONS.CFG 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME cgioptions.cfg \- Command-line parameters for the Xymon CGI tools .SH SYNOPSIS .B $XYMONHOME/etc/cgioptions.cfg .SH DESCRIPTION .I cgioptions.cfg(1) controls the command-line options passed to all of the Xymon CGI tools through their respective shell-script wrappers. Typically the options listed here are used for system-wide configuration of the CGI utilities, e.g. to define where they read configuration files. The exact set of command-line options available are described in the man-page for each of the CGI utilities. The file is "sourced" into the shell script wrapper, so assignments to the CGI-specific variables must follow standard shell-script syntax. .SH SETTINGS .IP CGI_ACKINFO_OPTS Options for the .I ackinfo.cgi(1) utility. .IP CGI_ACK_OPTS Options for the .I acknowledge.cgi(1) utility. .IP CGI_CSVINFO_OPTS Options for the .I csvinfo.cgi(1) utility. .IP CGI_DATEPAGE_OPTS Options for the .I datepage.cgi(1) utility. .IP CGI_ENADIS_OPTS Options for the .I enadis.cgi(8) utility. .IP CGI_EVENTLOG_OPTS Options for the .I eventlog.cgi(1) utility. .IP CGI_FINDHOST_OPTS Options for the .I findhost.cgi(1) utility. .IP CGI_HIST_OPTS Options for the .I history.cgi(1) utility. .IP CGI_COLUMNDOC_OPTS Xymon-specific options for column documentation. This uses the .I csvinfo.cgi(1) utility with the \fBserver/etc/columndoc.cfg\fR configuration file. .IP CGI_CONFREPORT_OPTS Options for the .I confreport.cgi(1) utility. .IP CGI_SHOWGRAPH_OPTS Options for the .I showgraph.cgi(1) utility. .IP CGI_HOSTGRAPHS_OPTS Options for the .I hostgraphs.cgi(1) utility. .IP CGI_CRITEDIT_OPTS Options for the .I criticaleditor.cgi(1) utility. .IP CGI_CRITVIEW_OPTS Options for the .I criticalview.cgi(1) utility. .IP CGI_REPLOG_OPTS Options for the .I reportlog.cgi(1) utility. .IP CGI_REP_OPTS Options for the .I report.cgi(1) utility. .IP CGI_SNAPSHOT_OPTS Options for the .I snapshot.cgi(1) utility. .IP CGI_SVCHIST_OPTS Options for the .I svcstatus.cgi(1) utility when used to view historical logs. Note that the "--historical" option must be included in this setting. .IP CGI_SVC_OPTS Options for the .I svcstatus.cgi(1) utility. .SH "SEE ALSO" xymon(7), the individual CGI utility man-pages. xymon-4.3.7/web/hostlist.c0000664000175000017500000000754711645550676015051 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon hostlist report generator. */ /* */ /* Copyright (C) 2007-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: hostlist.c 6766 2011-10-13 11:55:42Z storner $"; #include #include #include #include "libxymon.h" cgidata_t *cgidata = NULL; char *pagefilter = NULL; char *testfilter = "cpu"; enum { SORT_IP, SORT_HOSTNAME } sortkey = SORT_HOSTNAME; char *fields = NULL; char csvchar = ','; void parse_query(void) { cgidata_t *cwalk; fields = strdup("XMH_HOSTNAME,XMH_IP"); cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ if (strcasecmp(cwalk->name, "page") == 0) { pagefilter = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "filter") == 0) { testfilter = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "sort") == 0) { if (strcasecmp(cwalk->value, "hostname") == 0) sortkey = SORT_HOSTNAME; else if (strcasecmp(cwalk->value, "ip") == 0) sortkey = SORT_IP; } else if (strncasecmp(cwalk->name, "XMH_", 4) == 0) { if (strcasecmp(cwalk->value, "on") == 0) { fields = (char *)realloc(fields, strlen(fields) + strlen(cwalk->name) + 2); strcat(fields, ","); strcat(fields, cwalk->name); } } else if (strcasecmp(cwalk->name, "csvdelim") == 0) { csvchar = *(cwalk->value); } cwalk = cwalk->next; } } int main(int argc, char *argv[]) { char *envarea = NULL; char *req, *board, *l; int argi, res; sendreturn_t *sres; char *cookie; pcre *dummy; init_timestamp(); for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } } cookie = get_cookie("pagepath"); if (cookie) sethostenv_pagepath(cookie); cgidata = cgi_request(); if (cgidata == NULL) { /* Present the query form */ sethostenv("", "", "", colorname(COL_BLUE), NULL); printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); showform(stdout, "hostlist", "hostlist_form", COL_BLUE, getcurrenttime(NULL), NULL, NULL); return 0; } parse_query(); dummy = (testfilter ? compileregex(testfilter) : NULL); if (dummy == NULL) return 1; else freeregex(dummy); dummy = (pagefilter ? compileregex(pagefilter) : NULL); if (dummy == NULL) return 1; else freeregex(dummy); sres = newsendreturnbuf(1, NULL); req = malloc(1024 + strlen(fields) + strlen(testfilter) + strlen(pagefilter)); sprintf(req, "xymondboard fields=%s test=%s page=%s", fields, testfilter, pagefilter); res = sendmessage(req, NULL, XYMON_TIMEOUT, sres); if (res != XYMONSEND_OK) return 1; board = getsendreturnstr(sres, 1); freesendreturnbuf(sres); printf("Content-type: text/csv\n\n"); l = strtok(fields, ","); while (l) { printf("%s;", l); l = strtok(NULL, ",\n"); } printf("\n"); l = board; while (l && *l) { char *p; char *eoln = strchr(l, '\n'); if (eoln) *eoln = '\0'; do { p = strchr(l, '|'); if (p) *p = csvchar; } while (p); printf("%s\n", l); if (eoln) l = eoln+1; else l = NULL; } return 0; } xymon-4.3.7/web/svcstatus.cgi.10000664000175000017500000000745411671641417015704 0ustar henrikhenrik.TH SVCSTATUS.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME svcstatus.cgi \- CGI program to view Xymon status logs .SH SYNOPSIS .B "svcstatus.cgi [--historical] [--history={top|bottom}]" .SH DESCRIPTION \fBsvcstatus.cgi\fR is a CGI program to present a Xymon status log in HTML form (ie, as a web page). It can be used both for the logs showing the current status, and for historical logs from the "histlogs" directory. It is normally invoked as a CGI program, and therefore receives most of the input parameters via the CGI QUERY_STRING environment variable. Unless the "--historical" option is present, the current status log is used. This assumes a QUERY_STRING environment variable of the form .br HOSTSVC=hostname.servicename .br where "hostname" is the name of the host with commas instead of dots, and "servicename" is the name of the service (the column name in Xymon). Such links are automatically generated by the .I xymongen(1) tool when the environment contains "XYMONLOGSTATUS=dynamic". With the "--historical" option present, a historical logfile is used. This assumes a QUERY_STRING environment variable of the form .br HOST=hostname&SERVICE=servicename&TIMEBUF=timestamp .br where "hostname" is the name of the host with commas instead of dots, "servicename" is the name of the service, and "timestamp" is the time of the log. This is automatically generated by the .I history.cgi(1) tool. .SH OPTIONS .IP "--historical" Use a historical logfile instead of the current logfile. .IP "--history={top|bottom|none}" When showing the current logfile, provide a "HISTORY" button at the top or the bottom of the webpage, or not at all. The default is to put the HISTORY button at the bottom of the page. .IP "--env=FILENAME" Load the environment from FILENAME before executing the CGI. .IP "--templates=DIRECTORY" Where to look for the HTML header- and footer-templates used when generating the webpages. Default: $XYMONHOME/web/ .IP "--no-svcid" Do not include the HTML tags to identify the hostname/service on the generated web page. Useful is this already happens in the hostsvc_header template file, for instance. .IP "--multigraphs=TEST1[,TEST2]" This causes svcstatus.cgi to generate links to service graphs that are split up into multiple images, with at most 5 graphs per image. This option only works in Xymon mode. If not specified, only the "disk" status is split up this way. .IP "--no-disable" By default, the info-column page includes a form allowing users to disable and re-enable tests. If your setup uses the default separation of administration tools into a separate, password- protected area, then use of the disable- and enable-functions requires access to the administration tools. If you prefer to do this only via the dedicated administration page, this option will remove the disable-function from the info page. .IP "--no-jsvalidation" The disable-function on the info-column page by default uses JavaScript to validate the form before submitting the input to the Xymon server. However, some browsers cannot handle the Javascript code correctly so the form does not work. This option disables the use of Javascript for form-validation, allowing these browsers to use the disable-function. .IP "--nkconfig=FILENAME" Use FILENAME as the configuration file for the Critical Systems information. The default is to load this from $XYMONHOME/etc/critical.cfg .SH FILES .IP "$XYMONHOME/web/hostsvc_header" HTML template header .IP "$XYMONHOME/web/hostsvc_footer" HTML template footer .SH ENVIRONMENT .IP "NONHISTS=info,trends,graphs" A comma-separated list of services that does not have meaningful history, e.g. the "info" and "trends" columns. Services listed here do not get a "History" button. .IP "TEST2RRD=test,test" A comma-separated list of the tests that have an RRD graph. .SH "SEE ALSO" xymon(7), xymond(1) xymon-4.3.7/web/hostgraphs.cgi.10000664000175000017500000000333211671641417016016 0ustar henrikhenrik.TH HOSTGRAPHS.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME hostgraphs.cgi \- CGI program to show multiple graphs .SH SYNOPSIS .B "hostgraph.cgi" .SH DESCRIPTION \fBhostgraph.cgi\fR is invoked as a CGI script via the hostgraph.sh CGI wrapper. If no parameters are provided when invoked, it will present a form where the user can select a time period, one or more hosts, and a set of graphs. The parameters selected by the user are passed to a second invocation of hostgraph.cgi, and result in a webpage showing a list of graph images based on the trend data stored about the hosts. If multiple graph-types are selected, hostgraph.cgi will display a list of graphs, with one graph per type. If multiple hosts are selected, hostgraph.cgi will attempt to display a multi-host graph for each type where the graphs for all hosts are overlayed in a single image, allowing for easy comparison of the hosts. The hostlist uses the PAGEPATH cookie provided by Xymon webpages to select the list of hosts to present. Only the hosts visible on the page where hostgraph.cgi is invoked from will be visible. The resulting graph page can be bookmarked, but the bookmark also fixates the time period shown. .SH OPTIONS .IP "--env=FILENAME" Loads the environment defined in FILENAME before executing the CGI script. .SH BUGS This utility is experimental. It may change in a future release of Xymon. It is possible for the user to select graphs which do not exist. This results in broken image links. The set of graph-types is fixed in the server/web/hostgraphs_form template and does not adjust to which graphs are available. If the tool is invoked directly, all hosts defined in Xymon will be listed. .SH "SEE ALSO" hosts.cfg(5), xymonserver.cfg(5) xymon-4.3.7/web/reportlog.c0000664000175000017500000001241711630732307015170 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon report-mode statuslog viewer. */ /* */ /* This tool generates the report status log for a single status, with the */ /* availability percentages etc needed for a report-mode view. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: reportlog.c 6749 2011-09-04 17:26:31Z storner $"; #include #include #include #include #include #include #include "libxymon.h" char *hostname = NULL; char *displayname = NULL; char *ip = NULL; char *reporttime = NULL; char *service = NULL; time_t st, end; int style; int color; double reportgreenlevel = 99.995; double reportwarnlevel = 98.0; int reportwarnstops = -1; cgidata_t *cgidata = NULL; static void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } static void parse_query(void) { cgidata_t *cwalk; cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ if (strcasecmp(cwalk->name, "HOSTSVC") == 0) { char *p = strrchr(cwalk->value, '.'); if (p) { *p = '\0'; service = strdup(p+1); } hostname = strdup(basename(cwalk->value)); while ((p = strchr(hostname, ','))) *p = '.'; } else if (strcasecmp(cwalk->name, "HOST") == 0) { hostname = strdup(basename(cwalk->value)); } else if (strcasecmp(cwalk->name, "SERVICE") == 0) { service = strdup(basename(cwalk->value)); } else if (strcasecmp(cwalk->name, "REPORTTIME") == 0) { reporttime = (char *) malloc(strlen(cwalk->value)+strlen("REPORTTIME=")+1); sprintf(reporttime, "REPORTTIME=%s", cwalk->value); } else if (strcasecmp(cwalk->name, "WARNPCT") == 0) { reportwarnlevel = atof(cwalk->value); } else if (strcasecmp(cwalk->name, "STYLE") == 0) { if (strcmp(cwalk->value, "crit") == 0) style = STYLE_CRIT; else if (strcmp(cwalk->value, "nongr") == 0) style = STYLE_NONGR; else style = STYLE_OTHER; } else if (strcasecmp(cwalk->name, "ST") == 0) { /* Must be after "STYLE" */ st = atol(cwalk->value); } else if (strcasecmp(cwalk->name, "END") == 0) { end = atol(cwalk->value); } else if (strcasecmp(cwalk->name, "COLOR") == 0) { char *colstr = (char *) malloc(strlen(cwalk->value)+2); sprintf(colstr, "%s ", cwalk->value); color = parse_color(colstr); xfree(colstr); } else if (strcasecmp(cwalk->name, "RECENTGIFS") == 0) { use_recentgifs = atoi(cwalk->value); } cwalk = cwalk->next; } } int main(int argc, char *argv[]) { char histlogfn[PATH_MAX]; FILE *fd; char *textrepfn = NULL, *textrepfullfn = NULL, *textrepurl = NULL; FILE *textrep; reportinfo_t repinfo; int argi; char *envarea = NULL; void *hinfo; for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } } redirect_cgilog("reportlog"); cgidata = cgi_request(); parse_query(); load_hostinfo(hostname); if ((hinfo = hostinfo(hostname)) == NULL) { errormsg("No such host"); return 1; } ip = xmh_item(hinfo, XMH_IP); displayname = xmh_item(hinfo, XMH_DISPLAYNAME); if (!displayname) displayname = hostname; sprintf(histlogfn, "%s/%s.%s", xgetenv("XYMONHISTDIR"), commafy(hostname), service); fd = fopen(histlogfn, "r"); if (fd == NULL) { errormsg("Cannot open history file"); } color = parse_historyfile(fd, &repinfo, hostname, service, st, end, 0, reportwarnlevel, reportgreenlevel, reportwarnstops, reporttime); fclose(fd); textrepfn = (char *)malloc(1024 + strlen(hostname) + strlen(service)); sprintf(textrepfn, "avail-%s-%s-%u-%u.txt", hostname, service, (unsigned int)getcurrenttime(NULL), (int)getpid()); textrepfullfn = (char *)malloc(1024 + strlen(xgetenv("XYMONREPDIR")) + strlen(textrepfn)); sprintf(textrepfullfn, "%s/%s", xgetenv("XYMONREPDIR"), textrepfn); textrepurl = (char *)malloc(1024 + strlen(xgetenv("XYMONREPURL")) + strlen(textrepfn)); sprintf(textrepurl, "%s/%s", xgetenv("XYMONREPURL"), textrepfn); textrep = fopen(textrepfullfn, "w"); /* Now generate the webpage */ printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); generate_replog(stdout, textrep, textrepurl, hostname, service, color, style, ip, displayname, st, end, reportwarnlevel, reportgreenlevel, reportwarnstops, &repinfo); if (textrep) fclose(textrep); return 0; } xymon-4.3.7/web/notifications.sh.DIST0000664000175000017500000000025511535462534016761 0ustar henrikhenrik#!/bin/sh # This is the Xymon wrapper for the notifications CGI . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/notifications.cgi $CGI_NOTIFYLOG_OPTS xymon-4.3.7/web/appfeed-critical.sh.DIST0000775000175000017500000000031411655027463017304 0ustar henrikhenrik#!/bin/sh # This is a wrapper for the Xymon appfeed.cgi script . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/appfeed.cgi --critical=@XYMONHOME@/etc/critical.cfg $CGI_APPFEED_OPTS xymon-4.3.7/web/hostlist.sh.DIST0000664000175000017500000000024211535462534015755 0ustar henrikhenrik#!/bin/sh # This is the Xymon wrapper for the hostlist CGI . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/hostlist.cgi $CGI_HOSTLIST_OPTS xymon-4.3.7/web/useradm.sh.DIST0000775000175000017500000000024411535462534015551 0ustar henrikhenrik#!/bin/sh # This is a wrapper for the Xymon useradm.cgi script . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/useradm.cgi $CGI_USERADM_OPTS xymon-4.3.7/web/csvinfo.c0000664000175000017500000001234611615341300014613 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon general CSV info viewer. */ /* */ /* This is a CGI script for a generic presentation of information stored in */ /* a comma-separated file (CSV), e.g. via an export from a spreadsheet or DB. */ /* It is also used for the Xymon column-name links, to provide information */ /* about what each column header means and what kind of test is run. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: csvinfo.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include "libxymon.h" #define MAXCOLUMNS 80 char *srcdb = "hostinfo.csv"; char *wantedname = ""; int keycolumn = 0; char delimiter = ';'; cgidata_t *cgidata = NULL; void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } void parse_query(void) { cgidata_t *cwalk; cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ if (strcasecmp(cwalk->name, "key") == 0) { wantedname = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "db") == 0) { char *val, *p; /* Dont allow any slashes in the db-name */ val = cwalk->value; p = strrchr(val, '/'); if (p) val = (p+1); srcdb = strdup(val); } else if (strcasecmp(cwalk->name, "column") == 0) { keycolumn = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "delimiter") == 0) { delimiter = *(cwalk->value); } cwalk = cwalk->next; } } int main(int argc, char *argv[]) { FILE *db; char dbfn[PATH_MAX]; strbuffer_t *inbuf; char *hffile = "info"; int bgcolor = COL_BLUE; char *envarea = NULL; char *headers[MAXCOLUMNS]; char *items[MAXCOLUMNS]; int i, found; int argi; for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--hffile=")) { char *p = strchr(argv[argi], '='); hffile = strdup(p+1); } else if (argnmatch(argv[argi], "--color=")) { char *p = strchr(argv[argi], '='); bgcolor = parse_color(p+1); } } redirect_cgilog("csvinfo"); cgidata = cgi_request(); parse_query(); if (strlen(wantedname) == 0) { errormsg("Invalid request"); return 1; } sprintf(dbfn, "%s/etc/%s", xgetenv("XYMONHOME"), srcdb); db = fopen(dbfn, "r"); if (db == NULL) { char msg[PATH_MAX]; sprintf(msg, "Cannot open sourcedb %s\n", dbfn); errormsg(msg); return 1; } /* First, load the headers from line 1 of the sourcedb */ memset(headers, 0, sizeof(headers)); initfgets(db); inbuf = newstrbuffer(0); if (unlimfgets(inbuf, db)) { char *p1, *p2; for (i=0, p1=STRBUF(inbuf), p2=strchr(STRBUF(inbuf), delimiter); (p1 && p2 && strlen(p1)); i++,p1=p2+1,p2=strchr(p1, delimiter)) { *p2 = '\0'; headers[i] = strdup(p1); } } /* * Pre-allocate the buffer space for the items - we weill be stuffing data * into these while scanning for the right item. */ for (i=0; i\n"); for (i=0; (headers[i]); i++) { printf("\n"); printf(" %s%s\n", headers[i], items[i]); printf("\n"); } printf("\n"); headfoot(stdout, hffile, "", "footer", bgcolor); return 0; } xymon-4.3.7/web/enadis.c0000664000175000017500000003141111664526142014416 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon backend script for disabling/enabling tests. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: enadis.c 6776 2011-11-27 21:32:18Z storner $"; #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" enum { ACT_NONE, ACT_FILTER, ACT_ENABLE, ACT_DISABLE, ACT_SCHED_DISABLE, ACT_SCHED_CANCEL } action = ACT_NONE; int hostcount = 0; char **hostnames = NULL; int disablecount = 0; char **disabletest = NULL; int enablecount = 0; char **enabletest = NULL; int duration = 0; int scale = 1; char *disablemsg = "No reason given"; time_t schedtime = 0; int cancelid = 0; int preview = 0; char *hostpattern = NULL; char *pagepattern = NULL; char *ippattern = NULL; void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } void parse_cgi(void) { cgidata_t *postdata, *pwalk; struct tm schedtm; memset(&schedtm, 0, sizeof(schedtm)); postdata = cgi_request(); if (cgi_method == CGI_GET) return; if (!postdata) { errormsg(cgi_error()); } pwalk = postdata; while (pwalk) { /* * When handling the "go", the "Disable now" and "Schedule disable" * radio buttons mess things up. So ignore the "go" if we have seen a * "filter" request already. */ if ((strcmp(pwalk->name, "go") == 0) && (action != ACT_FILTER)) { if (strcasecmp(pwalk->value, "enable") == 0) action = ACT_ENABLE; else if (strcasecmp(pwalk->value, "disable now") == 0) action = ACT_DISABLE; else if (strcasecmp(pwalk->value, "schedule disable") == 0) action = ACT_SCHED_DISABLE; else if (strcasecmp(pwalk->value, "cancel") == 0) action = ACT_SCHED_CANCEL; else if (strcasecmp(pwalk->value, "apply filters") == 0) action = ACT_FILTER; } else if (strcmp(pwalk->name, "duration") == 0) { duration = atoi(pwalk->value); } else if (strcmp(pwalk->name, "untilok") == 0) { if (strcasecmp(pwalk->value, "on") == 0) { duration = -1; scale = 1; } } else if (strcmp(pwalk->name, "scale") == 0) { scale = atoi(pwalk->value); } else if (strcmp(pwalk->name, "cause") == 0) { disablemsg = strdup(pwalk->value); } else if (strcmp(pwalk->name, "hostname") == 0) { if (hostnames == NULL) { hostnames = (char **)malloc(2 * sizeof(char *)); hostnames[0] = strdup(pwalk->value); hostnames[1] = NULL; hostcount = 1; } else { hostnames = (char **)realloc(hostnames, (hostcount + 2) * sizeof(char *)); hostnames[hostcount] = strdup(pwalk->value); hostnames[hostcount+1] = NULL; hostcount++; } } else if (strcmp(pwalk->name, "enabletest") == 0) { char *val = pwalk->value; if (strcmp(val, "ALL") == 0) val = "*"; if (enabletest == NULL) { enabletest = (char **)malloc(2 * sizeof(char *)); enabletest[0] = strdup(val); enabletest[1] = NULL; enablecount = 1; } else { enabletest = (char **)realloc(enabletest, (enablecount + 2) * sizeof(char *)); enabletest[enablecount] = strdup(val); enabletest[enablecount+1] = NULL; enablecount++; } } else if (strcmp(pwalk->name, "disabletest") == 0) { char *val = pwalk->value; if (strcmp(val, "ALL") == 0) val = "*"; if (disabletest == NULL) { disabletest = (char **)malloc(2 * sizeof(char *)); disabletest[0] = strdup(val); disabletest[1] = NULL; disablecount = 1; } else { disabletest = (char **)realloc(disabletest, (disablecount + 2) * sizeof(char *)); disabletest[disablecount] = strdup(val); disabletest[disablecount+1] = NULL; disablecount++; } } else if (strcmp(pwalk->name, "year") == 0) { schedtm.tm_year = atoi(pwalk->value) - 1900; } else if (strcmp(pwalk->name, "month") == 0) { schedtm.tm_mon = atoi(pwalk->value) - 1; } else if (strcmp(pwalk->name, "day") == 0) { schedtm.tm_mday = atoi(pwalk->value); } else if (strcmp(pwalk->name, "hour") == 0) { schedtm.tm_hour = atoi(pwalk->value); } else if (strcmp(pwalk->name, "minute") == 0) { schedtm.tm_min = atoi(pwalk->value); } else if (strcmp(pwalk->name, "canceljob") == 0) { cancelid = atoi(pwalk->value); } else if (strcmp(pwalk->name, "preview") == 0) { preview = (strcasecmp(pwalk->value, "on") == 0); } else if ((strcmp(pwalk->name, "hostpattern") == 0) && pwalk->value && strlen(pwalk->value)) { hostpattern = strdup(pwalk->value); } else if ((strcmp(pwalk->name, "pagepattern") == 0) && pwalk->value && strlen(pwalk->value)) { pagepattern = strdup(pwalk->value); } else if ((strcmp(pwalk->name, "ippattern") == 0) && pwalk->value && strlen(pwalk->value)) { ippattern = strdup(pwalk->value); } pwalk = pwalk->next; } schedtm.tm_isdst = -1; schedtime = mktime(&schedtm); } void do_one_host(char *hostname, char *fullmsg, char *username) { char *xymoncmd = (char *)malloc(1024); int i, result; switch (action) { case ACT_ENABLE: for (i=0; (i < enablecount); i++) { if (preview) result = 0; else { xymoncmd = (char *)realloc(xymoncmd, 1024 + 2*strlen(hostname) + 2*strlen(enabletest[i]) + strlen(username)); sprintf(xymoncmd, "enable %s.%s", commafy(hostname), enabletest[i]); result = sendmessage(xymoncmd, NULL, XYMON_TIMEOUT, NULL); sprintf(xymoncmd, "notify %s.%s\nMonitoring of %s:%s has been ENABLED by %s\n", commafy(hostname), enabletest[i], hostname, enabletest[i], username); sendmessage(xymoncmd, NULL, XYMON_TIMEOUT, NULL); } if (preview) { printf("Enabling host %s", htmlquoted(hostname)); printf(" test %s", htmlquoted(enabletest[i])); printf(": %s\n", ((result == XYMONSEND_OK) ? "OK" : "Failed")); } } break; case ACT_DISABLE: for (i=0; (i < disablecount); i++) { if (preview) result = 0; else { xymoncmd = (char *)realloc(xymoncmd, 1024 + 2*strlen(hostname) + 2*strlen(disabletest[i]) + strlen(fullmsg) + strlen(username)); sprintf(xymoncmd, "disable %s.%s %d %s", commafy(hostname), disabletest[i], duration*scale, fullmsg); result = sendmessage(xymoncmd, NULL, XYMON_TIMEOUT, NULL); sprintf(xymoncmd, "notify %s.%s\nMonitoring of %s:%s has been DISABLED by %s for %d minutes\n%s", commafy(hostname), disabletest[i], hostname, disabletest[i], username, duration*scale, fullmsg); result = sendmessage(xymoncmd, NULL, XYMON_TIMEOUT, NULL); } if (preview) { printf("Disabling host %s", htmlquoted(hostname)); printf(" test %s", htmlquoted(disabletest[i])); printf(": %s\n", ((result == XYMONSEND_OK) ? "OK" : "Failed")); } } break; case ACT_SCHED_DISABLE: for (i=0; (i < disablecount); i++) { xymoncmd = (char *)realloc(xymoncmd, 1024 + 2*strlen(hostname) + strlen(disabletest[i]) + strlen(fullmsg)); sprintf(xymoncmd, "schedule %d disable %s.%s %d %s", (int) schedtime, commafy(hostname), disabletest[i], duration*scale, fullmsg); result = (preview ? 0 : sendmessage(xymoncmd, NULL, XYMON_TIMEOUT, NULL)); if (preview) { printf("Scheduling disable of host %s", htmlquoted(hostname)); printf("test %s", htmlquoted(disabletest[i])); printf(" at %s: %s\n", ctime(&schedtime), ((result == XYMONSEND_OK) ? "OK" : "Failed")); } } break; case ACT_SCHED_CANCEL: sprintf(xymoncmd, "schedule cancel %d", cancelid); result = (preview ? 0 : sendmessage(xymoncmd, NULL, XYMON_TIMEOUT, NULL)); if (preview) { printf("Canceling job %d : %s\n", cancelid, ((result == XYMONSEND_OK) ? "OK" : "Failed")); } break; default: errprintf("No action\n"); break; } xfree(xymoncmd); } int main(int argc, char *argv[]) { int argi, i; char *username = getenv("REMOTE_USER"); char *userhost = getenv("REMOTE_HOST"); char *userip = getenv("REMOTE_ADDR"); char *fullmsg = "No cause specified"; char *envarea = NULL; int obeycookies = 1; char *accessfn = NULL; if ((username == NULL) || (strlen(username) == 0)) username = "unknown"; if ((userhost == NULL) || (strlen(userhost) == 0)) userhost = userip; for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--no-cookies") == 0) { obeycookies = 0; } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--access=")) { char *p = strchr(argv[argi], '='); accessfn = strdup(p+1); } } redirect_cgilog("enadis"); parse_cgi(); if (debug) preview = 1; if (cgi_method == CGI_GET) { /* * It's a GET, so the initial request. * If we have a pagepath cookie, use that as the initial * host-name filter. */ char *pagepath; action = ACT_FILTER; pagepath = get_cookie("pagepath"); if (obeycookies && pagepath && *pagepath) pagepattern = strdup(pagepath); } if (action == ACT_FILTER) { /* Present the query form */ load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); sethostenv("", "", "", colorname(COL_BLUE), NULL); sethostenv_filter(hostpattern, pagepattern, ippattern); printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); showform(stdout, "maint", "maint_form", COL_BLUE, getcurrenttime(NULL), NULL, NULL); return 0; } fullmsg = (char *)malloc(1024 + strlen(username) + strlen(userhost) + strlen(disablemsg)); sprintf(fullmsg, "\nDisabled by: %s @ %s\nReason: %s\n", username, userhost, disablemsg); /* * Ready ... go build the webpage. */ printf("Content-Type: %s\n", xgetenv("HTMLCONTENTTYPE")); if (!preview) { printf("Location: %s\n\n", xgetenv("HTTP_REFERER")); } else { printf("\n"); } /* It's ok with these hardcoded values, as they are not used for this page */ sethostenv("", "", "", colorname(COL_BLUE), NULL); if (preview) headfoot(stdout, "maintact", "", "header", COL_BLUE); if (debug) { printf("
\n");
		switch (action) {
		  case ACT_NONE   : dbgprintf("Action = none\n"); break;

		  case ACT_FILTER : dbgprintf("Action = filter\n"); break;

		  case ACT_ENABLE : dbgprintf("Action = enable\n"); 
				    dbgprintf("Tests = ");
				    for (i=0; (i < enablecount); i++) printf("%s ", enabletest[i]);
				    printf("\n");
				    break;

		  case ACT_DISABLE: dbgprintf("Action = disable\n"); 
				    dbgprintf("Tests = ");
				    for (i=0; (i < disablecount); i++) printf("%s ", disabletest[i]);
				    printf("\n");
				    dbgprintf("Duration = %d, scale = %d\n", duration, scale);
				    dbgprintf("Cause = %s\n", textornull(disablemsg));
				    break;

		  case ACT_SCHED_DISABLE:
				    dbgprintf("Action = schedule\n");
				    dbgprintf("Time = %s\n", ctime(&schedtime));
				    dbgprintf("Tests = ");
				    for (i=0; (i < disablecount); i++) printf("%s ", disabletest[i]);
				    printf("\n");
				    dbgprintf("Duration = %d, scale = %d\n", duration, scale);
				    dbgprintf("Cause = %s\n", textornull(disablemsg));
				    break;

		  case ACT_SCHED_CANCEL:
				    dbgprintf("Action = cancel\n");
				    dbgprintf("ID = %d\n", cancelid);
				    break;
		}
		printf("
\n"); } if (preview) printf("\n"); if (action == ACT_SCHED_CANCEL) { do_one_host(NULL, NULL, username); } else { /* Load the host data (for access control) */ if (accessfn) { load_web_access_config(accessfn); for (i = 0; (i < hostcount); i++) { if (web_access_allowed(getenv("REMOTE_USER"), hostnames[i], NULL, WEB_ACCESS_CONTROL)) { do_one_host(hostnames[i], fullmsg, username); } } } else { for (i = 0; (i < hostcount); i++) do_one_host(hostnames[i], fullmsg, username); } } if (preview) { printf("\n", xgetenv("HTTP_REFERER")); printf("


\n"); headfoot(stdout, "maintact", "", "footer", COL_BLUE); } return 0; } xymon-4.3.7/web/ackinfo.c0000664000175000017500000000761011615341300014554 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon CGI for sending in an "ackinfo" message. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: ackinfo.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include "libxymon.h" static char *hostname = NULL; static char *testname = NULL; static int level = -1; static int validity = -1; static char *ackedby = NULL; static char *ackmsg = NULL; static void parse_query(void) { cgidata_t *cgidata = cgi_request(); cgidata_t *cwalk; cwalk = cgidata; while (cwalk) { if (strcasecmp(cwalk->name, "HOST") == 0) { hostname = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "SERVICE") == 0) { testname = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "ALLTESTS") == 0) { if (strcasecmp(cwalk->value, "on") == 0) testname = strdup("*"); } else if (strcasecmp(cwalk->name, "NOTE") == 0) { ackmsg = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "LEVEL") == 0) { /* Command line may override this */ if (level == -1) level = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "VALIDITY") == 0) { /* Command line may override this */ if (validity == -1) validity = atoi(cwalk->value); } cwalk = cwalk->next; } } int main(int argc, char *argv[]) { int argi; char *envarea = NULL; char *xymonmsg; int res; for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--level=")) { char *p = strchr(argv[argi], '='); level = atoi(p+1); } else if (argnmatch(argv[argi], "--validity=")) { char *p = strchr(argv[argi], '='); validity = atoi(p+1); } else if (argnmatch(argv[argi], "--sender=")) { char *p = strchr(argv[argi], '='); ackedby = strdup(p+1); } } redirect_cgilog("ackinfo"); parse_query(); if (hostname && *hostname && testname && *testname && ((level == 0) || (validity>0)) && ackmsg && *ackmsg) { char *p; /* Get the login username */ if (!ackedby) ackedby = getenv("REMOTE_USER"); if (!ackedby) ackedby = "UnknownUser"; if (validity == -1) validity = 30; /* 30 minutes */ validity = validity*60; p = strchr(ackmsg, '\n'); if (p) *p = '\0'; /* ackinfo HOST.TEST\nlevel\nvaliduntil\nackedby\nmsg */ xymonmsg = (char *)malloc(1024 + strlen(hostname) + strlen(testname) + strlen(ackedby) + strlen(ackmsg)); sprintf(xymonmsg, "ackinfo %s.%s\n%d\n%d\n%s\n%s\n", hostname, testname, level, validity, ackedby, ackmsg); res = sendmessage(xymonmsg, NULL, XYMON_TIMEOUT, NULL); } else { xymonmsg = (char *)malloc(1024 + strlen(hostname) + strlen(testname) + strlen(ackmsg)); sprintf(xymonmsg, "error in input params: hostname=%s, testname=%s, ackmsg=%s, validity=%d\n", hostname, testname, ackmsg, validity); } fprintf(stdout, "Content-type: %s\n", xgetenv("HTMLCONTENTTYPE")); fprintf(stdout, "Location: %s\n", getenv("HTTP_REFERER")); fprintf(stdout, "\n"); fprintf(stdout, "Sent to xymond:\n%s\n", htmlquoted(xymonmsg)); return 0; } xymon-4.3.7/web/enadis.cgi.80000664000175000017500000000333611671641417015112 0ustar henrikhenrik.TH ENADIS.CGI 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME enadis.cgi \- CGI program to enable/disable Xymon tests .SH SYNOPSIS .B "enadis.cgi (invoked via CGI from webserver)" .SH DESCRIPTION \fBenadis.cgi\fR is a CGI tool for disabling and enabling hosts and tests monitored by Xymon. You can disable monitoring of a single test, all tests for a host, or multiple hosts - immediately or at a future point in time. enadis.cgi runs as a CGI program, invoked by your webserver. It is normally run via a wrapper shell-script in the secured CGI directory for Xymon. enadis.cgi is the back-end script for the enable/disable form present on the "info" status-pages. It can also run in "stand-alone" mode, in which case it displays a web form allowing users to select what to enable or disable. .SH OPTIONS .IP "--no-cookies" Normally, enadis.cgi uses a cookie sent by the browser to initially filter the list of hosts presented. If this is not desired, you can turn off this behaviour by calling acknowledge.cgi with the --no-cookies option. This would normally be placed in the CGI_ENADIS_OPTS setting in .I cgioptions.cfg(5) .IP "--env=FILENAME" Load the environment from FILENAME before executing the CGI. .IP "--area=NAME" Load environment variables for a specific area. NB: if used, this option must appear before any --env=FILENAME option. .SH FILES .IP "$XYMONHOME/web/maint_{header,form,footer}" HTML template header .SH BUGS When using alternate pagesets, hosts will only show up on the Enable/Disable page if this is accessed from the primary page in which they are defined. So if you have hosts on multiple pages, they will only be visible for disabling from their main page which is not what you would expect. .SH "SEE ALSO" xymon(7) xymon-4.3.7/web/showgraph.sh.DIST0000664000175000017500000000035311535462534016111 0ustar henrikhenrik#!/bin/sh # This is the Xymon CGI script interface to showgraph.cgi # # Install this script in your webservers' cgi-bin directory . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/showgraph.cgi $CGI_SHOWGRAPH_OPTS xymon-4.3.7/web/boilerplate.c0000664000175000017500000000423711615341300015446 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon webpage generator tool. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: boilerplate.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include "libxymon.h" static void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } void parse_query(void) { cgidata_t *cgidata, *cwalk; cgidata = cgi_request(); if (cgidata == NULL) errormsg(cgi_error()); cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwakl->value points to the value (may be an empty string). */ cwalk = cwalk->next; } } int main(int argc, char *argv[]) { int argi; char *envarea = NULL; char *hffile = "boilerplate"; int bgcolor = COL_BLUE; for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--hffile=")) { char *p = strchr(argv[argi], '='); hffile = strdup(p+1); } } parse_query(); fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); headfoot(stdout, hffile, "", "header", bgcolor); headfoot(stdout, hffile, "", "footer", bgcolor); return 0; } xymon-4.3.7/web/snapshot.c0000664000175000017500000001746011615341300015005 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon "snapshot" CGI front-end. */ /* */ /* This is a CGI front-end that lets the user pick which snapshot view is */ /* generated. A snapshot is a view of the Xymon webpages from any time in */ /* the past, generated from the history logs. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: snapshot.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" time_t starttime = 0; cgidata_t *cgidata = NULL; char *monthnames[13] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } void parse_query(void) { int day, mon, year, hour, min, sec; struct tm tmbuf; cgidata_t *cwalk; day = mon = year = hour = min = sec = -1; cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ if (strcasecmp(cwalk->name, "day") == 0) { day = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "mon") == 0) { char *errptr; mon = strtol(cwalk->value, &errptr, 10) - 1; if (errptr == cwalk->value) { for (mon=0; (monthnames[mon] && strcmp(cwalk->value, monthnames[mon])); mon++) ; if (mon >= 12) mon = -1; } } else if (strcasecmp(cwalk->name, "yr") == 0) { year = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "hour") == 0) { hour = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "min") == 0) { min = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "sec") == 0) { sec = atoi(cwalk->value); } cwalk = cwalk->next; } memset(&tmbuf, 0, sizeof(tmbuf)); tmbuf.tm_mday = day; tmbuf.tm_mon = mon; tmbuf.tm_year = year - 1900; tmbuf.tm_hour = hour; tmbuf.tm_min = min; tmbuf.tm_sec = sec; tmbuf.tm_isdst = -1; /* Important! Or we mishandle DST periods */ starttime = mktime(&tmbuf); if ((starttime == -1) || (starttime > getcurrenttime(NULL))) errormsg("Invalid parameters"); } void cleandir(char *dirname) { DIR *dir; struct dirent *d; struct stat st; char fn[PATH_MAX]; time_t killtime = getcurrenttime(NULL)-86400; dir = opendir(dirname); if (dir == NULL) return; while ((d = readdir(dir))) { if (d->d_name[0] != '.') { sprintf(fn, "%s/%s", dirname, d->d_name); if ((stat(fn, &st) == 0) && (st.st_mtime < killtime)) { if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { dbgprintf("rm %s\n", fn); unlink(fn); } else if (S_ISDIR(st.st_mode)) { dbgprintf("Cleaning directory %s\n", fn); cleandir(fn); dbgprintf("rmdir %s\n", fn); rmdir(fn); } else { /* Ignore file */ }; } } } } int main(int argc, char *argv[]) { char dirid[PATH_MAX]; char outdir[PATH_MAX]; char xymongencmd[PATH_MAX]; char xymonwebenv[PATH_MAX]; char xymongentimeopt[100]; char *xymongen_argv[20]; pid_t childpid; int childstat; char htmldelim[100]; char startstr[20]; int argi, newargi; char *envarea = NULL; char *useragent; int usemultipart = 1; newargi = 0; xymongen_argv[newargi++] = xymongencmd; xymongen_argv[newargi++] = xymongentimeopt; for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else { xymongen_argv[newargi++] = argv[argi]; } } xymongen_argv[newargi++] = outdir; xymongen_argv[newargi++] = NULL; redirect_cgilog("snapshot"); cgidata = cgi_request(); if (cgidata == NULL) { /* Present the query form */ sethostenv("", "", "", colorname(COL_BLUE), NULL); printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); showform(stdout, "snapshot", "snapshot_form", COL_BLUE, getcurrenttime(NULL), NULL, NULL); return 0; } parse_query(); useragent = getenv("HTTP_USER_AGENT"); if (useragent && strstr(useragent, "KHTML")) { /* KHTML (Konqueror, Safari) cannot handle multipart documents. */ usemultipart = 0; } /* * Need to set these up AFTER putting them into xymongen_argv, since we * need to have option parsing done first. */ if (xgetenv("XYMONGEN")) sprintf(xymongencmd, "%s", xgetenv("XYMONGEN")); else sprintf(xymongencmd, "%s/bin/xymongen", xgetenv("XYMONHOME")); snprintf(xymongentimeopt, sizeof(xymongentimeopt), "--snapshot=%u", (unsigned int)starttime); sprintf(dirid, "%u-%u", (unsigned int)getpid(), (unsigned int)getcurrenttime(NULL)); sprintf(outdir, "%s/%s", xgetenv("XYMONSNAPDIR"), dirid); if (mkdir(outdir, 0755) == -1) errormsg("Cannot create output directory"); sprintf(xymonwebenv, "XYMONWEB=%s/%s", xgetenv("XYMONSNAPURL"), dirid); putenv(xymonwebenv); if (usemultipart) { /* Output the "please wait for report ... " thing */ snprintf(htmldelim, sizeof(htmldelim)-1, "xymonrep-%u-%u", (int)getpid(), (unsigned int)getcurrenttime(NULL)); printf("Content-type: multipart/mixed;boundary=%s\n", htmldelim); printf("\n"); printf("%s\n", htmldelim); printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); /* It's ok with these hardcoded values, as they are not used for this page */ sethostenv("", "", "", colorname(COL_BLUE), NULL); sethostenv_report(starttime, starttime, 97.0, 99.995); headfoot(stdout, "snapshot", "", "header", COL_BLUE); strftime(startstr, sizeof(startstr), "%b %d %Y", localtime(&starttime)); printf("
 \n"); printf("



\n"); printf("

Generating snapshot: %s
\n", htmlquoted(startstr)); printf("

\n"); fflush(stdout); } /* Go do the report */ childpid = fork(); if (childpid == 0) { execv(xymongencmd, xymongen_argv); } else if (childpid > 0) { wait(&childstat); /* Ignore SIGHUP so we dont get killed during cleanup of XYMONSNAPDIR */ signal(SIGHUP, SIG_IGN); if (WIFEXITED(childstat) && (WEXITSTATUS(childstat) != 0) ) { if (usemultipart) printf("%s\n\n", htmldelim); printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); errormsg("Could not generate report"); } else { /* Send the browser off to the report */ if (usemultipart) { printf("Done...

\n"); fflush(stdout); printf("%s\n\n", htmldelim); } printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("\n"); printf("\n"); if (usemultipart) printf("\n%s\n", htmldelim); fflush(stdout); } cleandir(xgetenv("XYMONSNAPDIR")); } else { if (usemultipart) printf("%s\n\n", htmldelim); printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); errormsg("Fork failed"); } return 0; } xymon-4.3.7/web/topchanges.sh.DIST0000664000175000017500000000027611535462534016246 0ustar henrikhenrik#!/bin/sh # This is the Xymon CGI script to display the top-N changing hosts/statuses . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/eventlog.cgi $CGI_TOPCHANGE_OPTS xymon-4.3.7/web/criticalview.c0000664000175000017500000004076211636073731015651 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon CGI for generating the Xymon Critical Systems page */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: criticalview.c 6760 2011-09-20 11:24:09Z storner $"; #include #include #include #include #include #include #include "libxymon.h" typedef struct hstatus_t { char *hostname; char *testname; char *key; int color; time_t lastchange, logtime, validtime, acktime; char *ackedby, *ackmsg; critconf_t *config; } hstatus_t; static void * *rbstate = NULL; static void * *hostsonpage = NULL; static int treecount = 0; static time_t oldlimit = 3600; static int critacklevel = 1; static int usetooltips = 0; static time_t maxage = INT_MAX; static time_t pagecolor = COL_GREEN; void errormsg(char *s) { fprintf(stderr, "%s\n", s); } static char *boardmaster = NULL; static int getboard(int mincolor) { char msg[1024]; int i; sendreturn_t *sres; int xymondresult; if (!boardmaster) { sprintf(msg, "xymondboard acklevel=%d fields=hostname,testname,color,lastchange,logtime,validtime,acklist color=%s", critacklevel,colorname(mincolor)); for (i=mincolor+1; (i < COL_COUNT); i++) sprintf(msg+strlen(msg), ",%s", colorname(i)); sres = newsendreturnbuf(1, NULL); xymondresult = sendmessage(msg, NULL, XYMON_TIMEOUT, sres); if (xymondresult != XYMONSEND_OK) { boardmaster = ""; freesendreturnbuf(sres); errormsg("Unable to fetch current status\n"); return 1; } else { boardmaster = getsendreturnstr(sres, 1); freesendreturnbuf(sres); } } return 0; } int loadstatus(int maxprio, time_t maxage, int mincolor, int wantacked) { char *board, *bol, *eol; time_t now; /* * We leak memory by dup'ing this and not freeing it. * But we cannot free it, because the tree holding the data * for later printing contains pointers into this string buffer. */ board = strdup(boardmaster); now = getcurrenttime(NULL); treecount++; if (treecount == 1) { rbstate = malloc(sizeof(void *)); hostsonpage = malloc(sizeof(void *)); } else { rbstate = realloc(rbstate, (treecount) * sizeof(void *)); hostsonpage = realloc(hostsonpage, (treecount) * sizeof(void *)); } rbstate[treecount-1] = xtreeNew(strcasecmp); hostsonpage[treecount-1] = xtreeNew(strcasecmp); bol = board; while (bol && (*bol)) { char *endkey; xtreeStatus_t status; eol = strchr(bol, '\n'); if (eol) *eol = '\0'; /* Find the config entry */ endkey = strchr(bol, '|'); if (endkey) endkey = strchr(endkey+1, '|'); if (endkey) { critconf_t *cfg; char *ackstr, *ackrtimestr, *ackvtimestr, *acklevelstr, *ackbystr, *ackmsgstr; *endkey = '\0'; cfg = get_critconfig(bol, CRITCONF_TIMEFILTER, NULL); *endkey = '|'; if (cfg) { hstatus_t *newitem = (hstatus_t *)calloc(1, sizeof(hstatus_t)); newitem->config = cfg; newitem->hostname = gettok(bol, "|"); newitem->testname = gettok(NULL, "|"); newitem->color = parse_color(gettok(NULL, "|")); newitem->lastchange = atoi(gettok(NULL, "|")); newitem->logtime = atoi(gettok(NULL, "|")); newitem->validtime = atoi(gettok(NULL, "|")); ackstr = gettok(NULL, "|"); ackrtimestr = ackvtimestr = acklevelstr = ackbystr = ackmsgstr = NULL; if (ackstr) { nldecode(ackstr); ackrtimestr = strtok(ackstr, ":"); if (ackrtimestr) ackvtimestr = strtok(NULL, ":"); if (ackvtimestr) acklevelstr = strtok(NULL, ":"); if (acklevelstr) ackbystr = strtok(NULL, ":"); if (ackbystr) ackmsgstr = strtok(NULL, ":"); } if ( (hostinfo(newitem->hostname) == NULL) || ((newitem->config->priority > maxprio) && (newitem->config->priority != 99)) || ((now - newitem->lastchange) > maxage) || (newitem->color < mincolor) || (ackmsgstr && !wantacked) ) { xfree(newitem); } else { if (ackvtimestr && ackbystr && ackmsgstr) { newitem->acktime = atoi(ackvtimestr); newitem->ackedby = strdup(ackbystr); newitem->ackmsg = strdup(ackmsgstr); } newitem->key = (char *)malloc(strlen(newitem->hostname) + strlen(newitem->testname) + 2); sprintf(newitem->key, "%s|%s", newitem->hostname, newitem->testname); status = xtreeAdd(rbstate[treecount-1], newitem->key, newitem); } } } bol = (eol ? (eol+1) : NULL); } return 0; } void * columnlist(void * statetree) { void * rbcolumns; xtreePos_t hhandle; rbcolumns = xtreeNew(strcasecmp); for (hhandle = xtreeFirst(statetree); (hhandle != xtreeEnd(statetree)); hhandle = xtreeNext(statetree, hhandle)) { hstatus_t *itm; xtreeStatus_t status; itm = (hstatus_t *)xtreeData(statetree, hhandle); status = xtreeAdd(rbcolumns, itm->testname, NULL); } return rbcolumns; } void print_colheaders(FILE *output, void * rbcolumns) { int colcount; xtreePos_t colhandle; colcount = 1; /* Remember the hostname column */ /* Group column headings */ fprintf(output, ""); fprintf(output, " \n"); /* For the prio column - in both row headers+dash rows */ fprintf(output, " \n"); /* For the host column - in both row headers+dash rows */ for (colhandle = xtreeFirst(rbcolumns); (colhandle != xtreeEnd(rbcolumns)); colhandle = xtreeNext(rbcolumns, colhandle)) { char *colname; colname = (char *)xtreeKey(rbcolumns, colhandle); colcount++; fprintf(output, " \n"); fprintf(output, " %s \n", columnlink(colname), xgetenv("XYMONPAGECOLFONT"), colname); } fprintf(output, "\n"); fprintf(output, "


\n\n", colcount); } void print_hoststatus(FILE *output, hstatus_t *itm, void * statetree, void * columns, int prio, int firsthost, int firsthostever) { void *hinfo; char *dispname, *ip, *key; time_t now; xtreePos_t colhandle; now = getcurrenttime(NULL); hinfo = hostinfo(itm->hostname); dispname = xmh_item(hinfo, XMH_DISPLAYNAME); ip = xmh_item(hinfo, XMH_IP); fprintf(output, "\n"); /* Print the priority */ fprintf(output, ""); if (firsthost) if (prio == 99) { if (firsthostever) /* Only non-prioritised hosts, so just drop that text */ fprintf(output, " "); else fprintf(output, "No priority", xgetenv("XYMONPAGEROWFONT")); } else { fprintf(output, "PRIO %d", xgetenv("XYMONPAGEROWFONT"), prio); } else fprintf(output, " "); fprintf(output, "\n"); /* Print the hostname with a link to the critical systems info page */ fprintf(output, "%s\n", hostnamehtml(itm->hostname, NULL, usetooltips)); key = (char *)malloc(1024); for (colhandle = xtreeFirst(columns); (colhandle != xtreeEnd(columns)); colhandle = xtreeNext(columns, colhandle)) { char *colname; xtreePos_t sthandle; fprintf(output, ""); colname = (char *)xtreeKey(columns, colhandle); key = (char *)realloc(key, 2 + strlen(itm->hostname) + strlen(colname)); sprintf(key, "%s|%s", itm->hostname, colname); sthandle = xtreeFind(statetree, key); if (sthandle == xtreeEnd(statetree)) { fprintf(output, "-"); } else { hstatus_t *column; char *htmlalttag; char *htmlackstr; column = (hstatus_t *)xtreeData(statetree, sthandle); if (column->config->priority != prio) fprintf(output, "-"); else { time_t age = now - column->lastchange; char *htmlgroupstr; char *htmlextrastr; htmlalttag = alttag(colname, column->color, 0, 1, agestring(age)); htmlackstr = (column->ackmsg ? column->ackmsg : ""); htmlgroupstr = strdup(urlencode(column->config->ttgroup ? column->config->ttgroup : "")); htmlextrastr = strdup(urlencode(column->config->ttextra ? column->config->ttextra : "")); fprintf(output, "", hostsvcurl(itm->hostname, colname, 1), prio, htmlgroupstr, htmlextrastr); fprintf(output, "\"%s\"", xgetenv("XYMONSKIN"), dotgiffilename(column->color, (column->acktime > 0), (age > oldlimit)), colorname(column->color), htmlalttag, htmlackstr, xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); xfree(htmlgroupstr); xfree(htmlextrastr); } } fprintf(output, "\n"); } xfree(key); fprintf(output, "\n"); } void print_oneprio(FILE *output, void * statetree, void * hoptree, void * rbcolumns, int prio) { xtreePos_t hhandle; static int firsthostever = 1; int firsthostthisprio = 1; char *curhost = ""; /* Then output each host and their column status */ for (hhandle = xtreeFirst(statetree); (hhandle != xtreeEnd(statetree)); hhandle = xtreeNext(statetree, hhandle)) { hstatus_t *itm; itm = (hstatus_t *)xtreeData(statetree, hhandle); if (itm->config->priority != prio) continue; if (strcmp(curhost, itm->hostname) == 0) continue; /* New host */ curhost = itm->hostname; print_hoststatus(output, itm, statetree, rbcolumns, prio, firsthostthisprio, firsthostever); xtreeAdd(hoptree, itm->hostname, itm); firsthostthisprio = 0; } /* If we did output any hosts, make some room for the next priority */ if (!firsthostthisprio) fprintf(output, " \n"); } static int evcount = 0; static void * evhopfilter; static int ev_included(char *hostname) { /* Callback function for filtering eventlog-hosts */ return (xtreeFind(evhopfilter, hostname) == xtreeEnd(evhopfilter)) ? 0 : 1; } void generate_critpage(void * statetree, void * hoptree, FILE *output, char *header, char *footer, int color, int maxprio) { xtreePos_t hhandle; headfoot(output, header, "", "header", pagecolor); /* Use PAGE color here, not the part color */ fprintf(output, "
\n"); if (color != COL_GREEN) { void * rbcolumns; int prio; rbcolumns = columnlist(statetree); fprintf(output, "\n"); print_colheaders(output, rbcolumns); for (prio = 1; (prio <= maxprio); prio++) { print_oneprio(output, statetree, hoptree, rbcolumns, prio); } fprintf(output, "
\n"); xtreeDestroy(rbcolumns); } else { /* "All Monitored Systems OK */ fprintf(output, "%s", xgetenv("XYMONALLOKTEXT")); } if (evcount > 0) { /* Include the eventlog */ evhopfilter = hoptree; do_eventlog(output, evcount, maxage/60, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, ev_included, NULL, NULL, NULL, XYMON_COUNT_NONE, XYMON_S_NONE, NULL); fprintf(output, "


\n"); } fprintf(output, "
\n"); headfoot(output, footer, "", "footer", color); } static int maxprio = 3; static int mincolor = COL_YELLOW; static int wantacked = 0; static void selectenv(char *name, char *val) { char *env; char *p; int envlen; envlen = 20; envlen += strlen(htmlquoted(name)); envlen += strlen(htmlquoted(val)); env = (char *)malloc(envlen); sprintf(env, "SELECT_%s", htmlquoted(name)); sprintf(env+strlen(env), "_%s=SELECTED", htmlquoted(val)); for (p=env; (*p); p++) *p = toupper((int)*p); putenv(env); } static void parse_query(void) { cgidata_t *cgidata = cgi_request(); cgidata_t *cwalk; int havemaxprio=0, havemaxage=0, havemincolor=0, havewantacked=0, haveevcount=0; cwalk = cgidata; while (cwalk) { if (strcasecmp(cwalk->name, "MAXPRIO") == 0) { selectenv(cwalk->name, cwalk->value); maxprio = atoi(cwalk->value); havemaxprio = 1; } else if (strcasecmp(cwalk->name, "MAXAGE") == 0) { selectenv(cwalk->name, cwalk->value); maxage = 60*atoi(cwalk->value); havemaxage = 1; } else if (strcasecmp(cwalk->name, "MINCOLOR") == 0) { selectenv(cwalk->name, cwalk->value); mincolor = parse_color(cwalk->value); havemincolor = 1; } else if (strcasecmp(cwalk->name, "OLDLIMIT") == 0) { selectenv(cwalk->name, cwalk->value); oldlimit = 60*atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "WANTACKED") == 0) { selectenv(cwalk->name, cwalk->value); wantacked = (strcasecmp(cwalk->value, "yes") == 0); havewantacked = 1; } else if (strcasecmp(cwalk->name, "EVCOUNT") == 0) { selectenv(cwalk->name, cwalk->value); evcount = atoi(cwalk->value); haveevcount = 1; } cwalk = cwalk->next; } if (!havemaxprio) selectenv("MAXPRIO", "3"); if (!havemaxage) selectenv("MAXAGE", "525600"); if (!havemincolor) selectenv("MINCOLOR", "yellow"); if (!havewantacked) selectenv("WANTACKED", "no"); if (!haveevcount) selectenv("EVCOUNT", "0"); } int main(int argc, char *argv[]) { int argi; char *envarea = NULL; char **critconfig = NULL; int cccount = 0; char *hffile = "critical"; critconfig = (char **)calloc(1, sizeof(char *)); for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (strcmp(argv[argi], "--tooltips") == 0) { usetooltips = 1; } else if (argnmatch(argv[argi], "--acklevel=")) { char *p = strchr(argv[argi], '='); critacklevel = atoi(p+1); } else if (argnmatch(argv[argi], "--config=")) { char *p = strchr(argv[argi], '='); critconfig[cccount] = strdup(p+1); cccount++; critconfig = (char **)realloc(critconfig, (1 + cccount)*sizeof(char *)); critconfig[cccount] = NULL; } else if (argnmatch(argv[argi], "--hffile=")) { char *p = strchr(argv[argi], '='); hffile = strdup(p+1); } } if (!critconfig[0]) { critconfig = (char **)realloc(critconfig, 2*sizeof(char *)); critconfig[0] = (char *)malloc(strlen(xgetenv("XYMONHOME")) + strlen(DEFAULT_CRITCONFIGFN) + 2); sprintf(critconfig[0], "%s/%s", xgetenv("XYMONHOME"), DEFAULT_CRITCONFIGFN); critconfig[1] = NULL; } redirect_cgilog("criticalview"); setdocurl(hostsvcurl("%s", xgetenv("INFOCOLUMN"), 1)); parse_query(); load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); load_all_links(); fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); use_recentgifs = 1; if (getboard(mincolor) == 0) { int i; char *oneconfig, *onename; int *partcolor, *partprio; xtreePos_t hhandle; for (i=0; (critconfig[i]); i++) { oneconfig = strchr(critconfig[i], ':'); load_critconfig(oneconfig ? oneconfig+1 : critconfig[i]); loadstatus(maxprio, maxage, mincolor, wantacked); /* Determine background color and max. priority */ if (i == 0) { partcolor = (int *)malloc(sizeof(int)); partprio = (int *)malloc(sizeof(int)); } else { partcolor = (int *)realloc(partcolor, (i+1)*sizeof(int)); partprio = (int *)realloc(partprio, (i+1)*sizeof(int)); } partcolor[i] = COL_GREEN; partprio[i] = 0; for (hhandle = xtreeFirst(rbstate[i]); (hhandle != xtreeEnd(rbstate[i])); hhandle = xtreeNext(rbstate[i], hhandle)) { hstatus_t *itm; itm = (hstatus_t *)xtreeData(rbstate[i], hhandle); if (itm->color > partcolor[i]) partcolor[i] = itm->color; if (itm->config->priority > partprio[i]) partprio[i] = itm->config->priority; } if (partcolor[i] > pagecolor) pagecolor = partcolor[i]; } for (i=0; (critconfig[i]); i++) { oneconfig = strchr(critconfig[i], ':'); if (oneconfig) { *oneconfig = '\0'; oneconfig++; onename = (char *)malloc(strlen("DIVIDERTEXT=") + strlen(critconfig[i]) + 1); sprintf(onename, "DIVIDERTEXT=%s", critconfig[i]); putenv(onename); } else { oneconfig = critconfig[i]; putenv("DIVIDERTEXT="); } generate_critpage(rbstate[i], hostsonpage[i], stdout, (i == 0) ? (critconfig[1] ? "critmulti" : "critical") : "divider", (critconfig[i+1] == NULL) ? "critical" : "divider", partcolor[i], partprio[i]); } } else { fprintf(stdout, "Cannot load Xymon status\n"); } return 0; } xymon-4.3.7/web/perfdata.sh.DIST0000775000175000017500000000024711535462534015702 0ustar henrikhenrik#!/bin/sh # This is a wrapper for the Xymon perfdata.cgi script . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/perfdata.cgi $CGI_PERFDATA_OPTS xymon-4.3.7/web/criticaleditor.c0000664000175000017500000003033111615341300016137 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon CGI for administering the critical.cfg file */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: criticaleditor.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" static char *operator = NULL; static enum { CRITEDIT_FIND, CRITEDIT_NEXT, CRITEDIT_UPDATE, CRITEDIT_DELETE, CRITEDIT_ADDCLONE, CRITEDIT_DROPCLONE } editaction = CRITEDIT_FIND; static char *rq_hostname = NULL; static char *rq_service = NULL; static int rq_priority = 0; static char *rq_group = NULL; static char *rq_extra = NULL; static char *rq_crittime = NULL; static time_t rq_start = 0; static time_t rq_end = 0; static char *rq_clonestoadd = NULL; static char *rq_clonestodrop = NULL; static int rq_dropevenifcloned = 0; static void parse_query(void) { cgidata_t *cgidata = cgi_request(); cgidata_t *cwalk; char *rq_critwkdays = NULL; char *rq_critslastart = NULL; char *rq_critslaend = NULL; int rq_startday = 0; int rq_startmon = 0; int rq_startyear = 0; int rq_endday = 0; int rq_endmon = 0; int rq_endyear = 0; cwalk = cgidata; while (cwalk) { if (strcasecmp(cwalk->name, "Find") == 0) { editaction = CRITEDIT_FIND; } else if (strcasecmp(cwalk->name, "Next") == 0) { editaction = CRITEDIT_NEXT; } else if (strcasecmp(cwalk->name, "Update") == 0) { editaction = CRITEDIT_UPDATE; } else if (strcasecmp(cwalk->name, "Drop") == 0) { editaction = CRITEDIT_DELETE; } else if (strcasecmp(cwalk->name, "Clone") == 0) { /* The "clone" button does both things */ editaction = CRITEDIT_ADDCLONE; } else if (strcasecmp(cwalk->name, "HOSTNAME") == 0) { if (*cwalk->value) rq_hostname = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "SERVICE") == 0) { if (*cwalk->value) rq_service = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "PRIORITY") == 0) { rq_priority = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "GROUP") == 0) { if (*cwalk->value) rq_group = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "CRITWKDAYS") == 0) { if (*cwalk->value) { if (!rq_critwkdays) rq_critwkdays = strdup(cwalk->value); else { rq_critwkdays = (char *)realloc(rq_critwkdays, strlen(rq_critwkdays) + strlen(cwalk->value) + 1); strcat(rq_critwkdays, cwalk->value); } } } else if (strcasecmp(cwalk->name, "CRITSTARTHOUR") == 0) { if (*cwalk->value) rq_critslastart = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "CRITENDHOUR") == 0) { if (*cwalk->value) rq_critslaend = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "start-day") == 0) { rq_startday = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "start-mon") == 0) { rq_startmon = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "start-yr") == 0) { rq_startyear = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "end-day") == 0) { rq_endday = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "end-mon") == 0) { rq_endmon = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "end-yr") == 0) { rq_endyear = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "EXTRA") == 0) { if (*cwalk->value) rq_extra = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "DROPEVENIFCLONED") == 0) { rq_dropevenifcloned = 1; } else if (strcasecmp(cwalk->name, "CRITEDITADDCLONES") == 0) { if (*cwalk->value) rq_clonestoadd = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "CRITEDITCLONELIST") == 0) { if (rq_clonestodrop) { rq_clonestodrop = (char *)realloc(rq_clonestodrop, strlen(rq_clonestodrop) + strlen(cwalk->value) + 2); strcat(rq_clonestodrop, " "); strcat(rq_clonestodrop, cwalk->value); } else { if (*cwalk->value) rq_clonestodrop = strdup(cwalk->value); } } cwalk = cwalk->next; } if (editaction == CRITEDIT_UPDATE) { struct tm tm; if ((rq_startday == 0) || (rq_startmon == 0) || (rq_startyear == 0)) rq_start = -1; else { memset(&tm, 0, sizeof(tm)); tm.tm_mday = rq_startday; tm.tm_mon = rq_startmon - 1; tm.tm_year = rq_startyear - 1900; tm.tm_isdst = -1; rq_start = mktime(&tm); } if ((rq_endday == 0) || (rq_endmon == 0) || (rq_endyear == 0)) rq_end = -1; else { memset(&tm, 0, sizeof(tm)); tm.tm_mday = rq_endday; tm.tm_mon = rq_endmon - 1; tm.tm_year = rq_endyear - 1900; tm.tm_isdst = -1; rq_end = mktime(&tm); } rq_crittime = (char *)malloc(strlen(rq_critwkdays) + strlen(rq_critslastart) + strlen(rq_critslaend) + 3); sprintf(rq_crittime, "%s:%s:%s", rq_critwkdays, rq_critslastart, rq_critslaend); } else if (editaction == CRITEDIT_ADDCLONE) { if (!rq_clonestoadd && rq_clonestodrop) editaction = CRITEDIT_DROPCLONE; } } void findrecord(char *hostname, char *service, char *nodatawarning, char *isclonewarning, char *hascloneswarning) { critconf_t *rec = NULL; int isaclone = 0; int hasclones = 0; char warnmsg[4096]; /* Setup the list of cloned records */ sethostenv_critclonelist_clear(); if (hostname && *hostname) { char *key, *realkey, *clonekey; critconf_t *clonerec; if (service && *service) { /* First check if the host+service is really a clone of something else */ key = (char *)malloc(strlen(hostname) + strlen(service) + 2); sprintf(key, "%s|%s", hostname, service); rec = get_critconfig(key, CRITCONF_FIRSTMATCH, &realkey); } else { key = strdup(hostname); rec = get_critconfig(key, CRITCONF_FIRSTHOSTMATCH, &realkey); } if (rec && realkey && (strcmp(key, realkey) != 0)) { char *p; xfree(key); key = strdup(realkey); hostname = realkey; p = strchr(realkey, '|'); if (p) { *p = '\0'; service = p+1; } isaclone = 1; } xfree(key); /* Next, see what hosts are clones of this one */ clonerec = get_critconfig(NULL, CRITCONF_RAW_FIRST, &clonekey); while (clonerec) { if ((*(clonekey + strlen(clonekey) -1) == '=') && (strcmp(hostname, (char *)clonerec) == 0)) { sethostenv_critclonelist_add(clonekey); hasclones = 1; } clonerec = get_critconfig(NULL, CRITCONF_RAW_NEXT, &clonekey); } } else { hostname = ""; } if (!service || !(*service)) service=""; if (rec) sethostenv_critedit(rec->updinfo, rec->priority, rec->ttgroup, rec->starttime, rec->endtime, rec->crittime, rec->ttextra); else sethostenv_critedit("", 0, NULL, 0, 0, NULL, NULL); sethostenv(hostname, "", service, colorname(COL_BLUE), NULL); *warnmsg = '\0'; if (!rec && nodatawarning) sprintf(warnmsg, "\n", nodatawarning); if (isaclone && isclonewarning) sprintf(warnmsg, "\n", isclonewarning); if (hasclones && hascloneswarning) sprintf(warnmsg, "\n", hascloneswarning); printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); showform(stdout, "critedit", "critedit_form", COL_BLUE, getcurrenttime(NULL), warnmsg, NULL); } void nextrecord(char *hostname, char *service, char *isclonewarning, char *hascloneswarning) { critconf_t *rec; char *nexthost, *nextservice; /* First check if the host+service is really a clone of something else */ if (hostname && service) { char *key; key = (char *)malloc(strlen(hostname) + strlen(service) + 2); sprintf(key, "%s|%s", hostname, service); rec = get_critconfig(key, CRITCONF_FIRSTMATCH, NULL); if (rec) rec = get_critconfig(NULL, CRITCONF_NEXT, NULL); xfree(key); } else { rec = get_critconfig(NULL, CRITCONF_FIRST, NULL); } if (rec) { nexthost = strdup(rec->key); nextservice = strchr(nexthost, '|'); if (nextservice) { *nextservice = '\0'; nextservice++; } } else { nexthost = strdup(""); nextservice = ""; } findrecord(nexthost, nextservice, NULL, isclonewarning, hascloneswarning); xfree(nexthost); } void updaterecord(char *hostname, char *service) { critconf_t *rec = NULL; if (hostname && service) { char *key = (char *)malloc(strlen(hostname) + strlen(service) + 2); char *realkey; char datestr[20]; time_t now = getcurrenttime(NULL); strftime(datestr, sizeof(datestr), "%Y-%m-%d %H:%M:%S", localtime(&now)); sprintf(key, "%s|%s", hostname, service); rec = get_critconfig(key, CRITCONF_FIRSTMATCH, &realkey); if (rec == NULL) { rec = (critconf_t *)calloc(1, sizeof(critconf_t)); rec->key = strdup(key); } rec->priority = rq_priority; rec->starttime = (rq_start > 0) ? rq_start : 0; rec->endtime = (rq_end > 0) ? rq_end : 0; if (rec->crittime) { xfree(rec->crittime); rec->crittime = NULL; } if (rq_crittime) { rec->crittime = (strcmp(rq_crittime, "*:0000:2400") == 0) ? NULL : strdup(rq_crittime); } if (rec->ttgroup) xfree(rec->ttgroup); rec->ttgroup = (rq_group ? strdup(rq_group) : NULL); if (rec->ttextra) xfree(rec->ttextra); rec->ttextra = (rq_extra ? strdup(rq_extra) : NULL); if (rec->updinfo) xfree(rec->updinfo); rec->updinfo = (char *)malloc(strlen(operator) + strlen(datestr) + 2); sprintf(rec->updinfo, "%s %s", operator, datestr); update_critconfig(rec); xfree(key); } findrecord(hostname, service, NULL, NULL, NULL); } void addclone(char *origin, char *newhosts, char *service) { char *newclone; newclone = strtok(newhosts, " "); while (newclone) { addclone_critconfig(origin, newclone); newclone = strtok(NULL, " "); } update_critconfig(NULL); findrecord(origin, service, NULL, NULL, NULL); } void dropclone(char *origin, char *drops, char *service) { char *drop; drop = strtok(drops, " "); while (drop) { dropclone_critconfig(drop); drop = strtok(NULL, " "); } update_critconfig(NULL); findrecord(origin, service, NULL, NULL, NULL); } void deleterecord(char *hostname, char *service, int evenifcloned) { char *key; key = (char *)malloc(strlen(hostname) + strlen(service) + 2); sprintf(key, "%s|%s", hostname, service); if (delete_critconfig(key, evenifcloned) == 0) { update_critconfig(NULL); } findrecord(hostname, service, NULL, NULL, (evenifcloned ? "Warning: Orphans will be ignored" : "Will not delete record that is cloned")); } int main(int argc, char *argv[]) { int argi; char *envarea = NULL; char *configfn = NULL; operator = getenv("REMOTE_USER"); if (!operator) operator = "Anonymous"; for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (argnmatch(argv[argi], "--config=")) { char *p = strchr(argv[argi], '='); configfn = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } } redirect_cgilog("criticaleditor"); parse_query(); load_critconfig(configfn); switch (editaction) { case CRITEDIT_FIND: findrecord(rq_hostname, rq_service, ((rq_hostname && rq_service) ? "No record for this host/service" : NULL), "Cloned - showing master record", NULL); break; case CRITEDIT_NEXT: nextrecord(rq_hostname, rq_service, "Cloned - showing master record", NULL); break; case CRITEDIT_UPDATE: updaterecord(rq_hostname, rq_service); break; case CRITEDIT_DELETE: deleterecord(rq_hostname, rq_service, rq_dropevenifcloned); break; case CRITEDIT_ADDCLONE: addclone(rq_hostname, rq_clonestoadd, rq_service); break; case CRITEDIT_DROPCLONE: dropclone(rq_hostname, rq_clonestodrop, rq_service); break; } return 0; } xymon-4.3.7/web/graphs.cfg.50000664000175000017500000000765611671641417015136 0ustar henrikhenrik.TH GRAPHS.CFG 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME graphs.cfg \- Configuration of the showgraph CGI .SH SYNOPSIS .B $XYMONHOME/etc/graphs.cfg .SH DESCRIPTION .I showgraph.cgi(1) uses the configuration file $XYMONHOME/etc/graphs.cfg to build graphs from the RRD files collected by Xymon. .SH FILE FORMAT Each definition of a graph type begins with a "[SERVICE]" indicator, this is the name passed as the "service" parameter to .I showgraph.cgi(1). If the service name passed to showgraph.cgi is not found, it will attempt to match the service name to a graph via the TEST2RRD environment variable. So calling showgraph.cgi with "service=cpu" or "service=la" will end up producing the same graph. A graph definition needs to have a TITLE and a YAXIS setting. These are texts shown as the title of the graph, and the YAXIS heading respectively. (The X-axis is always time-based). If a fixed set of RRD files are used for the graph, you just write those in the RRDtool definitions. Note that Xymon keeps all RRD files for a host in a separate directory per host, so you need not worry about the hostname being part of the RRD filename. For graphs that use multiple RRD files as input, you specify a filename pattern in the FNPATTERN statement, and optionally a pattern of files to exclude from the graph with EXFNPATTERN (see "[tcp]" for an example). When FNPATTERN is used, you can use "@RRDFN@" in the RRDtool definitions to pick up each filename. "@RRDIDX@" is an index (starting at 0) for each file in the set. "@RRDPARAM@" contains the first word extracted from the pattern of files (see e.g. "[memory]" how this is used). "@COLOR@" picks a new color for each graph automatically. The remainder of the lines in each definition are passed directly to the RRDtool rrd_graph() routine. The following is an example of how the "la" (cpu) graph is defined. This is a simple definition that uses a single RRD-file, la.rrd: .sp [la] .br TITLE CPU Load .br YAXIS Load .br DEF:avg=la.rrd:la:AVERAGE .br CDEF:la=avg,100,/ .br AREA:la#00CC00:CPU Load Average .br GPRINT:la:LAST: \: %5.1lf (cur) .br GPRINT:la:MAX: \: %5.1lf (max) .br GPRINT:la:MIN: \: %5.1lf (min) .br GPRINT:la:AVERAGE: \: %5.1lf (avg)\n .sp Here is an example of a graph that uses multiple RRD-files, determined automatically at run-time via the FNPATTERN setting. Note how it uses the @RRDIDX@ to define a unique RRD parameter per input-file, and the @COLOR@ and @RRDPARAM@ items to pick unique colors and a matching text for the graph legend: .sp [disk] .br FNPATTERN disk(.*).rrd .br TITLE Disk Utilization .br YAXIS % Full .br DEF:p@RRDIDX@=@RRDFN@:pct:AVERAGE .br LINE2:p@RRDIDX@#@COLOR@:@RRDPARAM@ .br -u 100 .br -l 0 .br GPRINT:p@RRDIDX@:LAST: \: %5.1lf (cur) .br GPRINT:p@RRDIDX@:MAX: \: %5.1lf (max) .br GPRINT:p@RRDIDX@:MIN: \: %5.1lf (min) .br GPRINT:p@RRDIDX@:AVERAGE: \: %5.1lf (avg)\n .SH ADVANCED GRAPH TITLES Normally the title of a graph is a static text defined in the graphs.cfg file. However, there may be situations where you want to use different titles for the same type of graph, e.g. if you are incorporating RRD files from MRTG into Xymon. In that case you can setup the TITLE definition so that it runs a custom script to determine the graph title. Like this: .sp TITLE exec:/usr/local/bin/graphitle .sp The \fB/usr/local/bin/graphtitle\fR command is then called with the hostname, the graphtype, the period string, and all of the RRD files used as parameters. The script must generate one line of output, which is then used as the title of the graph. .SH ENVIRONMENT .BR TEST2RRD Maps service names to graph definitions. .SH NOTES Most of the RRD graph definitions shipped with Xymon have been ported from the definitions in the \fBlarrd-grapher.cgi\fR CGI from LARRD 0.43c. .SH "SEE ALSO" xymonserver.cfg(5), rrdtool(1), rrdgraph(1) xymon-4.3.7/web/reportlog.cgi.10000664000175000017500000000337411671641417015657 0ustar henrikhenrik.TH REPORTLOG.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME reportlog.cgi \- CGI program to report service availability log .SH SYNOPSIS .B "reportlog.cgi" .SH DESCRIPTION \fBreportlog.cgi\fR is invoked as a CGI script via the reportlog.sh CGI wrapper. Based on the parameters it receives, it generates an availability report for a specific host-service combination for the requested time-period. The availability report includes a calculation of the availability percentage (split out on percent green, yellow, red time), and an eventlog for the period listing the status changes that have occurred to allow for drill-down to the test reports that indicate a problem. Access to the individual historical status logs go via the .I svcstatus.cgi(1) CGI script. reportlog.cgi is passed a QUERY_STRING environment variable with the following parameters: HOSTSVC (the host and service to report on) STYLE (report style: "crit", "non-crit", "all") ST (starttime in seconds since 1-1-1970 00:00 UTC) END (endtime in seconds since 1-1-1970 00:00 UTC) The following non-standard parameters are handled by the Xymon version of history.cgi: IP (IP address of host - for display purposes only) REPORTTIME (the REPORTTIME: setting for this host) WARNPCT (the WARNPCT: setting for this host) The REPORTTIME and WARNPCT options are taken from the .I hosts.cfg(5) definition for the host, or the defaults are used. These modify the availability calculation to handle reporting against agreed Service Level Agreements re. the time of day when the service must be available, and the agreed availability level. .SH OPTIONS .IP "--env=FILENAME" Loads environment from FILENAME before executing the CGI. .SH "SEE ALSO" hosts.cfg(5), xymonserver.cfg(5), svcstatus.cgi(1) xymon-4.3.7/web/acknowledge.sh.DIST0000664000175000017500000000024311535462534016370 0ustar henrikhenrik#!/bin/sh # This is the Xymon wrapper for the acknowledge CGI . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/acknowledge.cgi $CGI_ACK_OPTS xymon-4.3.7/web/xymonpage.c0000664000175000017500000000443711615341300015155 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon webpage generator tool. */ /* */ /* This is a generic webpage generator, that allows scripts to output a */ /* standard Xymon-like webpage without having to deal with headers and */ /* footers. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymonpage.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include "libxymon.h" #include "version.h" char *reqenv[] = { "XYMONHOME", NULL }; int main(int argc, char *argv[]) { int argi; char *hffile = "stdnormal"; int bgcolor = COL_BLUE; char inbuf[8192]; int n; char *envarea = NULL; for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--hffile=")) { char *p = strchr(argv[argi], '='); hffile = strdup(p+1); } else if (argnmatch(argv[argi], "--color=")) { char *p = strchr(argv[argi], '='); bgcolor = parse_color(p+1); } } envcheck(reqenv); fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); headfoot(stdout, hffile, "", "header", bgcolor); do { n = fread(inbuf, 1, sizeof(inbuf), stdin); if (n > 0) fwrite(inbuf, 1, n, stdout); } while (n == sizeof(inbuf)); headfoot(stdout, hffile, "", "footer", bgcolor); return 0; } xymon-4.3.7/web/ghostlist.sh.DIST0000664000175000017500000000024211535462534016124 0ustar henrikhenrik#!/bin/sh # This is the Xymon wrapper for the ghostlist CGI . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/ghostlist.cgi $CGI_GHOSTS_OPTS xymon-4.3.7/web/showgraph.c0000664000175000017500000010721611615341300015147 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD graph generator. */ /* */ /* This is a CGI script for generating graphs from the data stored in the */ /* RRD databases. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: showgraph.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #define HOUR_GRAPH "e-48h" #define DAY_GRAPH "e-12d" #define WEEK_GRAPH "e-48d" #define MONTH_GRAPH "e-576d" /* RRDtool 1.0.x handles graphs with no DS definitions just fine. 1.2.x does not. */ #ifdef RRDTOOL12 #ifndef HIDE_EMPTYGRAPH #define HIDE_EMPTYGRAPH 1 #endif #endif #ifdef HIDE_EMPTYGRAPH unsigned char blankimg[] = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x12\x00\x00\x0b\x12\x01\xd2\xdd\x7e\xfc\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd1\x01\x14\x12\x21\x14\x7e\x4a\x3a\xd2\x00\x00\x00\x0d\x49\x44\x41\x54\x78\xda\x63\x60\x60\x60\x60\x00\x00\x00\x05\x00\x01\x7a\xa8\x57\x50\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; #endif char *hostname = NULL; char **hostlist = NULL; int hostlistsize = 0; char *displayname = NULL; char *service = NULL; char *period = NULL; time_t persecs = 0; char *gtype = NULL; char *glegend = NULL; enum {ACT_MENU, ACT_SELZOOM, ACT_VIEW} action = ACT_VIEW; time_t graphstart = 0; time_t graphend = 0; double upperlimit = 0.0; int haveupperlimit = 0; double lowerlimit = 0.0; int havelowerlimit = 0; int graphwidth = 0; int graphheight = 0; int ignorestalerrds = 0; int bgcolor = COL_GREEN; int coloridx = 0; char *colorlist[] = { "0000FF", "FF0000", "00CC00", "FF00FF", "555555", "880000", "000088", "008800", "008888", "888888", "880088", "FFFF00", "888800", "00FFFF", "00FF00", "AA8800", "AAAAAA", "DD8833", "DDCC33", "8888FF", "5555AA", "B428D3", "FF5555", "DDDDDD", "AAFFAA", "AAFFFF", "FFAAFF", "FFAA55", "55AAFF", "AA55FF", NULL }; typedef struct gdef_t { char *name; char *fnpat; char *exfnpat; char *title; char *yaxis; int novzoom; char **defs; struct gdef_t *next; } gdef_t; gdef_t *gdefs = NULL; typedef struct rrddb_t { char *key; char *rrdfn; char *rrdparam; } rrddb_t; rrddb_t *rrddbs = NULL; int rrddbcount = 0; int rrddbsize = 0; int rrdidx = 0; int paramlen = 0; int firstidx = -1; int idxcount = -1; int lastidx = 0; void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } void request_cacheflush(char *hostname) { /* Build a cache-flush request, and send it to all of the $XYMONTMP/rrdctl.* sockets */ char *req, *bufp; int bytesleft; DIR *dir; struct dirent *d; int ctlsocket = -1; req = (char *)malloc(strlen(hostname)+3); sprintf(req, "/%s/", hostname); ctlsocket = socket(AF_UNIX, SOCK_DGRAM, 0); if (ctlsocket == -1) { errprintf("Cannot get socket: %s\n", strerror(errno)); return; } fcntl(ctlsocket, F_SETFL, O_NONBLOCK); dir = opendir(xgetenv("XYMONTMP")); while ((d = readdir(dir)) != NULL) { if (strncmp(d->d_name, "rrdctl.", 7) == 0) { struct sockaddr_un myaddr; socklen_t myaddrsz = 0; int n, sendfailed = 0; memset(&myaddr, 0, sizeof(myaddr)); myaddr.sun_family = AF_UNIX; sprintf(myaddr.sun_path, "%s/%s", xgetenv("XYMONTMP"), d->d_name); myaddrsz = sizeof(myaddr); bufp = req; bytesleft = strlen(req); do { n = sendto(ctlsocket, bufp, bytesleft, 0, (struct sockaddr *)&myaddr, myaddrsz); if (n == -1) { if (errno != EAGAIN) { errprintf("Sendto failed: %s\n", strerror(errno)); } sendfailed = 1; } else { bytesleft -= n; bufp += n; } } while ((!sendfailed) && (bytesleft > 0)); } } closedir(dir); xfree(req); /* * Sleep 0.3 secs to allow the cache flush to happen. * Note: It isn't guaranteed to happen in this time, but * there's a good chance that it will. */ usleep(300000); } void parse_query(void) { cgidata_t *cgidata = NULL, *cwalk; char *stp; cgidata = cgi_request(); cwalk = cgidata; while (cwalk) { if (strcmp(cwalk->name, "host") == 0) { char *hnames = strdup(cwalk->value); hostname = strtok_r(cwalk->value, ",", &stp); while (hostname) { if (hostlist == NULL) { hostlistsize = 1; hostlist = (char **)malloc(sizeof(char *)); hostlist[0] = strdup(hostname); } else { hostlistsize++; hostlist = (char **)realloc(hostlist, (hostlistsize * sizeof(char *))); hostlist[hostlistsize-1] = strdup(hostname); } hostname = strtok_r(NULL, ",", &stp); } xfree(hnames); if (hostlist) hostname = hostlist[0]; } else if (strcmp(cwalk->name, "service") == 0) { service = strdup(cwalk->value); } else if (strcmp(cwalk->name, "disp") == 0) { displayname = strdup(cwalk->value); } else if (strcmp(cwalk->name, "graph") == 0) { if (strcmp(cwalk->value, "hourly") == 0) { period = HOUR_GRAPH; persecs = 48*60*60; gtype = strdup(cwalk->value); glegend = "Last 48 Hours"; } else if (strcmp(cwalk->value, "daily") == 0) { period = DAY_GRAPH; persecs = 12*24*60*60; gtype = strdup(cwalk->value); glegend = "Last 12 Days"; } else if (strcmp(cwalk->value, "weekly") == 0) { period = WEEK_GRAPH; persecs = 48*24*60*60; gtype = strdup(cwalk->value); glegend = "Last 48 Days"; } else if (strcmp(cwalk->value, "monthly") == 0) { period = MONTH_GRAPH; persecs = 576*24*60*60; gtype = strdup(cwalk->value); glegend = "Last 576 Days"; } else if (strcmp(cwalk->value, "custom") == 0) { period = NULL; persecs = 0; gtype = strdup(cwalk->value); glegend = ""; } } else if (strcmp(cwalk->name, "first") == 0) { firstidx = atoi(cwalk->value) - 1; } else if (strcmp(cwalk->name, "count") == 0) { idxcount = atoi(cwalk->value); lastidx = firstidx + idxcount - 1; } else if (strcmp(cwalk->name, "action") == 0) { if (cwalk->value) { if (strcmp(cwalk->value, "menu") == 0) action = ACT_MENU; else if (strcmp(cwalk->value, "selzoom") == 0) action = ACT_SELZOOM; else if (strcmp(cwalk->value, "view") == 0) action = ACT_VIEW; } } else if (strcmp(cwalk->name, "graph_start") == 0) { if (cwalk->value) graphstart = atoi(cwalk->value); } else if (strcmp(cwalk->name, "graph_end") == 0) { if (cwalk->value) graphend = atoi(cwalk->value); } else if (strcmp(cwalk->name, "upper") == 0) { if (cwalk->value) { upperlimit = atof(cwalk->value); haveupperlimit = 1; } } else if (strcmp(cwalk->name, "lower") == 0) { if (cwalk->value) { lowerlimit = atof(cwalk->value); havelowerlimit = 1; } } else if (strcmp(cwalk->name, "graph_width") == 0) { if (cwalk->value) graphwidth = atoi(cwalk->value); } else if (strcmp(cwalk->name, "graph_height") == 0) { if (cwalk->value) graphheight = atoi(cwalk->value); } else if (strcmp(cwalk->name, "nostale") == 0) { ignorestalerrds = 1; } else if (strcmp(cwalk->name, "color") == 0) { int color = parse_color(cwalk->value); if (color != -1) bgcolor = color; } cwalk = cwalk->next; } if (hostlistsize == 1) { xfree(hostlist); hostlist = NULL; } else { displayname = hostname = strdup(""); } if ((hostname == NULL) || (service == NULL)) errormsg("Invalid request - no host or service"); if (displayname == NULL) displayname = hostname; if (graphstart && graphend) { char t1[15], t2[15]; persecs = (graphend - graphstart); strftime(t1, sizeof(t1), "%d/%b/%Y", localtime(&graphstart)); strftime(t2, sizeof(t2), "%d/%b/%Y", localtime(&graphend)); glegend = (char *)malloc(40); sprintf(glegend, "%s - %s", t1, t2); } } void load_gdefs(char *fn) { FILE *fd; strbuffer_t *inbuf; char *p; gdef_t *newitem = NULL; char **alldefs = NULL; int alldefcount = 0, alldefidx = 0; inbuf = newstrbuffer(0); fd = stackfopen(fn, "r", NULL); if (fd == NULL) errormsg("Cannot load graph definitions"); while (stackfgets(inbuf, NULL)) { p = strchr(STRBUF(inbuf), '\n'); if (p) *p = '\0'; p = STRBUF(inbuf); p += strspn(p, " \t"); if ((strlen(p) == 0) || (*p == '#')) continue; if (*p == '[') { char *delim; if (newitem) { /* Save the current one, and start on the next item */ alldefs[alldefidx] = NULL; newitem->defs = alldefs; newitem->next = gdefs; gdefs = newitem; } newitem = calloc(1, sizeof(gdef_t)); delim = strchr(p, ']'); if (delim) *delim = '\0'; newitem->name = strdup(p+1); alldefcount = 10; alldefs = (char **)malloc((alldefcount+1) * sizeof(char *)); alldefidx = 0; } else if (strncasecmp(p, "FNPATTERN", 9) == 0) { p += 9; p += strspn(p, " \t"); newitem->fnpat = strdup(p); } else if (strncasecmp(p, "EXFNPATTERN", 11) == 0) { p += 11; p += strspn(p, " \t"); newitem->exfnpat = strdup(p); } else if (strncasecmp(p, "TITLE", 5) == 0) { p += 5; p += strspn(p, " \t"); newitem->title = strdup(p); } else if (strncasecmp(p, "YAXIS", 5) == 0) { p += 5; p += strspn(p, " \t"); newitem->yaxis = strdup(p); } else if (strncasecmp(p, "NOVZOOM", 7) == 0) { newitem->novzoom = 1; } else if (haveupperlimit && (strncmp(p, "-u ", 3) == 0)) { continue; } else if (haveupperlimit && (strncmp(p, "-upper ", 7) == 0)) { continue; } else if (havelowerlimit && (strncmp(p, "-l ", 3) == 0)) { continue; } else if (havelowerlimit && (strncmp(p, "-lower ", 7) == 0)) { continue; } else { if (alldefidx == alldefcount) { /* Must expand alldefs */ alldefcount += 5; alldefs = (char **)realloc(alldefs, (alldefcount+1) * sizeof(char *)); } alldefs[alldefidx++] = strdup(p); } } /* Pick up the last item */ if (newitem) { /* Save the current one, and start on the next item */ alldefs[alldefidx] = NULL; newitem->defs = alldefs; newitem->next = gdefs; gdefs = newitem; } stackfclose(fd); freestrbuffer(inbuf); } char *lookup_meta(char *keybuf, char *rrdfn) { FILE *fd; char *metafn, *p; int servicelen = strlen(service); int keylen = strlen(keybuf); int found; static char buf[1024]; /* Must be static since it is returned to caller */ p = strrchr(rrdfn, '/'); if (!p) { metafn = strdup("rrd.meta"); } else { metafn = (char *)malloc(strlen(rrdfn) + 10); *p = '\0'; sprintf(metafn, "%s/rrd.meta", rrdfn); *p = '/'; } fd = fopen(metafn, "r"); xfree(metafn); if (!fd) return NULL; /* Find the first line that has our key and then whitespace */ found = 0; while (!found && fgets(buf, sizeof(buf), fd)) { found = ( (strncmp(buf, service, servicelen) == 0) && (*(buf+servicelen) == ':') && (strncmp(buf+servicelen+1, keybuf, keylen) == 0) && isspace(*(buf+servicelen+1+keylen)) ); } fclose(fd); if (found) { char *eoln, *val; val = buf + servicelen + 1 + keylen; val += strspn(val, " \t"); eoln = strchr(val, '\n'); if (eoln) *eoln = '\0'; if (strlen(val) > 0) return val; } return NULL; } char *colon_escape(char *buf) { static char *result = NULL; int count = 0; char *p, *inp, *outp; p = buf; while ((p = strchr(p, ':')) != NULL) { count++; p++; } if (count == 0) return buf; if (result) xfree(result); result = (char *) malloc(strlen(buf) + count + 1); *result = '\0'; inp = buf; outp = result; while (*inp) { p = strchr(inp, ':'); if (p == NULL) { strcat(outp, inp); inp += strlen(inp); outp += strlen(outp); } else { *p = '\0'; strcat(outp, inp); strcat(outp, "\\:"); *p = ':'; inp = p+1; outp = outp + strlen(outp); } } *outp = '\0'; return result; } char *expand_tokens(char *tpl) { static strbuffer_t *result = NULL; char *inp, *p; if (strchr(tpl, '@') == NULL) return tpl; if (!result) result = newstrbuffer(2048); else clearstrbuffer(result); inp = tpl; while (*inp) { p = strchr(inp, '@'); if (p == NULL) { addtobuffer(result, inp); inp += strlen(inp); continue; } *p = '\0'; if (strlen(inp)) { addtobuffer(result, inp); inp = p; } *p = '@'; if (strncmp(inp, "@RRDFN@", 7) == 0) { addtobuffer(result, colon_escape(rrddbs[rrdidx].rrdfn)); inp += 7; } else if (strncmp(inp, "@RRDPARAM@", 10) == 0) { /* * We do a colon-escape first, then change all commas to slashes as * this is a common mangling used by multiple backends (disk, http, iostat...) */ if (rrddbs[rrdidx].rrdparam) { char *val, *p; int vallen; char *resultstr; val = colon_escape(rrddbs[rrdidx].rrdparam); p = val; while ((p = strchr(p, ',')) != NULL) *p = '/'; /* rrdparam strings may be very long. */ if (strlen(val) > 100) *(val+100) = '\0'; /* * "paramlen" holds the longest string of the any of the matching files' rrdparam. * However, because this goes through colon_escape(), the actual string length * passed to librrd functions may be longer (since ":" must be escaped as "\:"). */ vallen = strlen(val); if (vallen < paramlen) vallen = paramlen; resultstr = (char *)malloc(vallen + 1); sprintf(resultstr, "%-*s", paramlen, val); addtobuffer(result, resultstr); xfree(resultstr); } inp += 10; } else if (strncmp(inp, "@RRDMETA@", 9) == 0) { /* * We do a colon-escape first, then change all commas to slashes as * this is a common mangling used by multiple backends (disk, http, iostat...) */ if (rrddbs[rrdidx].rrdparam) { char *val, *p, *metaval; val = colon_escape(rrddbs[rrdidx].rrdparam); p = val; while ((p = strchr(p, ',')) != NULL) *p = '/'; metaval = lookup_meta(val, rrddbs[rrdidx].rrdfn); if (metaval) addtobuffer(result, metaval); } inp += 9; } else if (strncmp(inp, "@RRDIDX@", 8) == 0) { char numstr[10]; sprintf(numstr, "%d", rrdidx); addtobuffer(result, numstr); inp += 8; } else if (strncmp(inp, "@STACKIT@", 9) == 0) { /* Contributed by Gildas Le Nadan */ /* the STACK behavior changed between rrdtool 1.0.x * and 1.2.x, hence the ifdef: * - in 1.0.x, you replace the graph type (AREA|LINE) * for the graph you want to stack with the STACK * keyword * - in 1.2.x, you add the STACK keyword at the end * of the definition * * Please note that in both cases the first entry * mustn't contain the keyword STACK at all, so * we need a different treatment for the first rrdidx * * examples of graphs.cfg entries: * * - rrdtool 1.0.x * @STACKIT@:la@RRDIDX@#@COLOR@:@RRDPARAM@ * * - rrdtool 1.2.x * AREA::la@RRDIDX@#@COLOR@:@RRDPARAM@:@STACKIT@ */ char numstr[10]; if (rrdidx == 0) { #ifdef RRDTOOL12 strcpy(numstr, ""); #else sprintf(numstr, "AREA"); #endif } else { sprintf(numstr, "STACK"); } addtobuffer(result, numstr); inp += 9; } else if (strncmp(inp, "@SERVICE@", 9) == 0) { addtobuffer(result, service); inp += 9; } else if (strncmp(inp, "@COLOR@", 7) == 0) { addtobuffer(result, colorlist[coloridx]); inp += 7; coloridx++; if (colorlist[coloridx] == NULL) coloridx = 0; } else { addtobuffer(result, "@"); inp += 1; } } return STRBUF(result); } int rrd_name_compare(const void *v1, const void *v2) { rrddb_t *r1 = (rrddb_t *)v1; rrddb_t *r2 = (rrddb_t *)v2; char *endptr; long numkey1, numkey2; int key1isnumber, key2isnumber; /* See if the keys are all numeric; if yes, then do a numeric sort */ numkey1 = strtol(r1->key, &endptr, 10); key1isnumber = (*endptr == '\0'); numkey2 = strtol(r2->key, &endptr, 10); key2isnumber = (*endptr == '\0'); if (key1isnumber && key2isnumber) { if (numkey1 < numkey2) return -1; else if (numkey1 > numkey2) return 1; else return 0; } return strcmp(r1->key, r2->key); } void graph_link(FILE *output, char *uri, char *grtype, time_t seconds) { time_t gstart, gend; char *grtype_s; fprintf(output, "\n"); grtype_s = htmlquoted(grtype); switch (action) { case ACT_MENU: fprintf(output, " \"%s\n", uri, grtype_s, grtype_s); fprintf(output, " \"Zoom \n", uri, grtype_s, colorname(bgcolor), getenv("XYMONSKIN")); break; case ACT_SELZOOM: if (graphend == 0) gend = getcurrenttime(NULL); else gend = graphend; if (graphstart == 0) gstart = gend - persecs; else gstart = graphstart; fprintf(output, " \"Zoom\n"); break; case ACT_VIEW: break; } fprintf(output, "\n"); } char *build_selfURI(void) { strbuffer_t *result = newstrbuffer(2048); char *p; char numbuf[40]; addtobuffer(result, xgetenv("SCRIPT_NAME")); addtobuffer(result, "?host="); if (hostlist) { int i; addtobuffer(result, urlencode(hostlist[0])); for (i = 1; (i < hostlistsize); i++) { addtobuffer(result, ","); addtobuffer(result, urlencode(hostlist[i])); } } else { addtobuffer(result, urlencode(hostname)); } addtobuffer(result, "&color="); addtobuffer(result, colorname(bgcolor)); if (service) { addtobuffer(result, "&service="); addtobuffer(result, urlencode(service)); } if (graphheight) { snprintf(numbuf, sizeof(numbuf)-1, "%d", graphheight); addtobuffer(result, "&graph_height="); addtobuffer(result, urlencode(numbuf)); } if (graphwidth) { snprintf(numbuf, sizeof(numbuf)-1, "%d", graphwidth); addtobuffer(result, "&graph_width="); addtobuffer(result, urlencode(numbuf)); } if (displayname && (displayname != hostname)) { addtobuffer(result, "&disp="); addtobuffer(result, urlencode(displayname)); } if (firstidx != -1) { snprintf(numbuf, sizeof(numbuf)-1, "&first=%d", firstidx+1); addtobuffer(result, numbuf); } if (idxcount != -1) { snprintf(numbuf, sizeof(numbuf)-1, "&count=%d", idxcount); addtobuffer(result, numbuf); } if (ignorestalerrds) addtobuffer(result, "&nostale"); return STRBUF(result); } void build_menu_page(char *selfURI, int backsecs) { /* This is special-handled, because we just want to generate an HTML link page */ fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); sethostenv(displayname, "", service, colorname(bgcolor), hostname); sethostenv_backsecs(backsecs); headfoot(stdout, "graphs", "", "header", bgcolor); fprintf(stdout, "\n"); graph_link(stdout, selfURI, "hourly", 48*60*60); graph_link(stdout, selfURI, "daily", 12*24*60*60); graph_link(stdout, selfURI, "weekly", 48*24*60*60); graph_link(stdout, selfURI, "monthly", 576*24*60*60); fprintf(stdout, "
\n"); headfoot(stdout, "graphs", "", "footer", bgcolor); } void generate_graph(char *gdeffn, char *rrddir, char *graphfn) { gdef_t *gdef = NULL, *gdefuser = NULL; int wantsingle = 0; DIR *dir; time_t now = getcurrenttime(NULL); int argi, pcount; /* Options for rrd_graph() */ int rrdargcount; char **rrdargs = NULL; /* The full argv[] table of string pointers to arguments */ char heightopt[30]; /* -h HEIGHT */ char widthopt[30]; /* -w WIDTH */ char upperopt[30]; /* -u MAX */ char loweropt[30]; /* -l MIN */ char startopt[30]; /* -s STARTTIME */ char endopt[30]; /* -e ENDTIME */ char graphtitle[1024]; /* --title TEXT */ char timestamp[50]; /* COMMENT with timestamp graph was generated */ /* Return variables from rrd_graph() */ int result; char **calcpr = NULL; int xsize, ysize; double ymin, ymax; /* Find the graphs.cfg file and load it */ if (gdeffn == NULL) { char fnam[PATH_MAX]; sprintf(fnam, "%s/etc/graphs.cfg", xgetenv("XYMONHOME")); gdeffn = strdup(fnam); } load_gdefs(gdeffn); /* Determine the real service name. It might be a multi-service graph */ if (strchr(service, ':') || strchr(service, '.')) { /* * service is "tcp:foo" - so use the "tcp" graph definition, but for a * single service (as if service was set to just "foo"). */ char *delim = service + strcspn(service, ":."); char *realservice; *delim = '\0'; realservice = strdup(delim+1); /* The requested gdef only acts as a fall-back solution so dont set gdef here. */ for (gdefuser = gdefs; (gdefuser && strcmp(service, gdefuser->name)); gdefuser = gdefuser->next) ; strcpy(service, realservice); wantsingle = 1; xfree(realservice); } /* * Lookup which RRD file corresponds to the service-name, and how we handle this graph. * We first lookup the service name in the graph definition list. * If that fails, then we try mapping it via the servicename -> RRD map. */ for (gdef = gdefs; (gdef && strcmp(service, gdef->name)); gdef = gdef->next) ; if (gdef == NULL) { if (gdefuser) { gdef = gdefuser; } else { xymonrrd_t *ldef = find_xymon_rrd(service, NULL); if (ldef) { for (gdef = gdefs; (gdef && strcmp(ldef->xymonrrdname, gdef->name)); gdef = gdef->next) ; wantsingle = 1; } } } if (gdef == NULL) errormsg("Unknown graph requested"); if (hostlist && (gdef->fnpat == NULL)) { char *multiname = (char *)malloc(strlen(gdef->name) + 7); sprintf(multiname, "%s-multi", gdef->name); for (gdef = gdefs; (gdef && strcmp(multiname, gdef->name)); gdef = gdef->next) ; if (gdef == NULL) errormsg("Unknown multi-graph requested"); xfree(multiname); } /* * If we're here only to collect the min/max values for the graph but it doesn't * allow vertical zoom, then there's no reason to waste anymore time. */ if ((action == ACT_SELZOOM) && gdef->novzoom) { haveupperlimit = havelowerlimit = 0; return; } /* Determine the directory with the host RRD files, and go there. */ if (rrddir == NULL) { char dnam[PATH_MAX]; if (hostlist) sprintf(dnam, "%s", xgetenv("XYMONRRDS")); else sprintf(dnam, "%s/%s", xgetenv("XYMONRRDS"), hostname); rrddir = strdup(dnam); } if (chdir(rrddir)) errormsg("Cannot access RRD directory"); /* Request an RRD cache flush from the xymond_rrd update daemon */ if (hostlist) { int i; for (i=0; (i < hostlistsize); i++) request_cacheflush(hostlist[i]); } else if (hostname) request_cacheflush(hostname); /* What RRD files do we have matching this request? */ if (hostlist || (gdef->fnpat == NULL)) { /* * No pattern, just a single file. It doesnt matter if it exists, because * these types of graphs usually have a hard-coded value for the RRD filename * in the graph definition. */ rrddbcount = rrddbsize = (hostlist ? hostlistsize : 1); rrddbs = (rrddb_t *)malloc((rrddbsize + 1) * sizeof(rrddb_t)); if (!hostlist) { rrddbs[0].key = strdup(service); rrddbs[0].rrdfn = (char *)malloc(strlen(gdef->name) + strlen(".rrd") + 1); sprintf(rrddbs[0].rrdfn, "%s.rrd", gdef->name); rrddbs[0].rrdparam = NULL; } else { int i, maxlen; char paramfmt[10]; for (i=0, maxlen=0; (i < hostlistsize); i++) { if (strlen(hostlist[i]) > maxlen) maxlen = strlen(hostlist[i]); } sprintf(paramfmt, "%%-%ds", maxlen+1); for (i=0; (i < hostlistsize); i++) { rrddbs[i].key = strdup(service); rrddbs[i].rrdfn = (char *)malloc(strlen(hostlist[i]) + strlen(gdef->fnpat) + 2); sprintf(rrddbs[i].rrdfn, "%s/%s", hostlist[i], gdef->fnpat); rrddbs[i].rrdparam = (char *)malloc(maxlen + 2); sprintf(rrddbs[i].rrdparam, paramfmt, hostlist[i]); } } } else { struct dirent *d; pcre *pat, *expat = NULL; const char *errmsg; int errofs, result; int ovector[30]; struct stat st; time_t now = getcurrenttime(NULL); /* Scan the directory to see what RRD files are there that match */ dir = opendir("."); if (dir == NULL) errormsg("Unexpected error while accessing RRD directory"); /* Setup the pattern to match filenames against */ pat = pcre_compile(gdef->fnpat, PCRE_CASELESS, &errmsg, &errofs, NULL); if (!pat) { char msg[8192]; snprintf(msg, sizeof(msg), "graphs.cfg error, PCRE pattern %s invalid: %s, offset %d\n", htmlquoted(gdef->fnpat), errmsg, errofs); errormsg(msg); } if (gdef->exfnpat) { expat = pcre_compile(gdef->exfnpat, PCRE_CASELESS, &errmsg, &errofs, NULL); if (!expat) { char msg[8192]; snprintf(msg, sizeof(msg), "graphs.cfg error, PCRE pattern %s invalid: %s, offset %d\n", htmlquoted(gdef->exfnpat), errmsg, errofs); errormsg(msg); } } /* Allocate an initial filename table */ rrddbsize = 5; rrddbs = (rrddb_t *) malloc((rrddbsize+1) * sizeof(rrddb_t)); while ((d = readdir(dir)) != NULL) { char *ext; char param[PATH_MAX]; /* Ignore dot-files and files with names shorter than ".rrd" */ if (*(d->d_name) == '.') continue; ext = d->d_name + strlen(d->d_name) - strlen(".rrd"); if ((ext <= d->d_name) || (strcmp(ext, ".rrd") != 0)) continue; /* First check the exclude pattern. */ if (expat) { result = pcre_exec(expat, NULL, d->d_name, strlen(d->d_name), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (result >= 0) continue; } /* Then see if the include pattern matches. */ result = pcre_exec(pat, NULL, d->d_name, strlen(d->d_name), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (result < 0) continue; if (wantsingle) { /* "Single" graph, i.e. a graph for a service normally included in a bundle (tcp) */ if (strstr(d->d_name, service) == NULL) continue; } /* * Has it been updated recently (within the past 24 hours) ? * We dont want old graphs to mess up multi-displays. */ if (ignorestalerrds && (stat(d->d_name, &st) == 0) && ((now - st.st_mtime) > 86400)) { continue; } /* We have a matching file! */ rrddbs[rrddbcount].rrdfn = strdup(d->d_name); if (pcre_copy_substring(d->d_name, ovector, result, 1, param, sizeof(param)) > 0) { /* * This is ugly, but I cannot find a pretty way of un-mangling * the disk- and http-data that has been molested by the back-end. */ if ((strcmp(param, ",root") == 0) && ((strncmp(gdef->name, "disk", 4) == 0) || (strncmp(gdef->name, "inode", 5) == 0)) ) { rrddbs[rrddbcount].rrdparam = strdup(","); } else if ((strcmp(gdef->name, "http") == 0) && (strncmp(param, "http", 4) != 0)) { rrddbs[rrddbcount].rrdparam = (char *)malloc(strlen("http://")+strlen(param)+1); sprintf(rrddbs[rrddbcount].rrdparam, "http://%s", param); } else { rrddbs[rrddbcount].rrdparam = strdup(param); } if (strlen(rrddbs[rrddbcount].rrdparam) > paramlen) { /* * "paramlen" holds the longest string of the any of the matching files' rrdparam. */ paramlen = strlen(rrddbs[rrddbcount].rrdparam); } rrddbs[rrddbcount].key = strdup(rrddbs[rrddbcount].rrdparam); } else { rrddbs[rrddbcount].key = strdup(d->d_name); rrddbs[rrddbcount].rrdparam = NULL; } rrddbcount++; if (rrddbcount == rrddbsize) { rrddbsize += 5; rrddbs = (rrddb_t *)realloc(rrddbs, (rrddbsize+1) * sizeof(rrddb_t)); } } pcre_free(pat); if (expat) pcre_free(expat); closedir(dir); } rrddbs[rrddbcount].key = rrddbs[rrddbcount].rrdfn = rrddbs[rrddbcount].rrdparam = NULL; /* Sort them so the display looks prettier */ qsort(&rrddbs[0], rrddbcount, sizeof(rrddb_t), rrd_name_compare); /* Setup the title */ if (!gdef->title) gdef->title = strdup(""); if (strncmp(gdef->title, "exec:", 5) == 0) { char *pcmd; int i, pcmdlen = 0; FILE *pfd; char *p; pcmdlen = strlen(gdef->title+5) + strlen(displayname) + strlen(service) + strlen(glegend) + 5; for (i=0; (ititle+5, displayname, service, glegend); for (i=0; (i= firstidx) && (i <= lastidx))) { p += sprintf(p, " \"%s\"", rrddbs[i].rrdfn); } } pfd = popen(pcmd, "r"); if (pfd) { fgets(graphtitle, sizeof(graphtitle), pfd); pclose(pfd); } /* Drop any newline at end of the title */ p = strchr(graphtitle, '\n'); if (p) *p = '\0'; } else { sprintf(graphtitle, "%s %s %s", displayname, gdef->title, glegend); } sprintf(heightopt, "-h%d", graphheight); sprintf(widthopt, "-w%d", graphwidth); /* * Setup the arguments for calling rrd_graph. * There's up to 16 standard arguments, plus the * graph-specific ones (which may be repeated if * there are multiple RRD-files to handle). */ for (pcount = 0; (gdef->defs[pcount]); pcount++) ; rrdargs = (char **) calloc(16 + pcount*rrddbcount + 1, sizeof(char *)); argi = 0; rrdargs[argi++] = "rrdgraph"; rrdargs[argi++] = (action == ACT_VIEW) ? graphfn : "/dev/null"; rrdargs[argi++] = "--title"; rrdargs[argi++] = graphtitle; rrdargs[argi++] = widthopt; rrdargs[argi++] = heightopt; rrdargs[argi++] = "-v"; rrdargs[argi++] = gdef->yaxis; rrdargs[argi++] = "-a"; rrdargs[argi++] = "PNG"; if (haveupperlimit) { sprintf(upperopt, "-u %f", upperlimit); rrdargs[argi++] = upperopt; } if (havelowerlimit) { sprintf(loweropt, "-l %f", lowerlimit); rrdargs[argi++] = loweropt; } if (haveupperlimit || havelowerlimit) rrdargs[argi++] = "--rigid"; if (graphstart) sprintf(startopt, "-s %u", (unsigned int) graphstart); else sprintf(startopt, "-s %s", period); rrdargs[argi++] = startopt; if (graphend) { sprintf(endopt, "-e %u", (unsigned int) graphend); rrdargs[argi++] = endopt; } for (rrdidx=0; (rrdidx < rrddbcount); rrdidx++) { if ((firstidx == -1) || ((rrdidx >= firstidx) && (rrdidx <= lastidx))) { int i; for (i=0; (gdef->defs[i]); i++) { rrdargs[argi++] = strdup(expand_tokens(gdef->defs[i])); } } } #ifdef RRDTOOL12 strftime(timestamp, sizeof(timestamp), "COMMENT:Updated\\: %d-%b-%Y %H\\:%M\\:%S", localtime(&now)); #else strftime(timestamp, sizeof(timestamp), "COMMENT:Updated: %d-%b-%Y %H:%M:%S", localtime(&now)); #endif rrdargs[argi++] = strdup(timestamp); rrdargcount = argi; rrdargs[argi++] = NULL; if (debug) { for (argi=0; (argi < rrdargcount); argi++) dbgprintf("%s\n", rrdargs[argi]); } /* If sending to stdout, print the HTTP header first. */ if ((action == ACT_VIEW) && (strcmp(graphfn, "-") == 0)) { time_t expiretime = now + 300; char expirehdr[100]; printf("Content-type: image/png\n"); strftime(expirehdr, sizeof(expirehdr), "Expires: %a, %d %b %Y %H:%M:%S GMT", gmtime(&expiretime)); printf("%s\n", expirehdr); printf("\n"); #ifdef HIDE_EMPTYGRAPH /* It works, but we still get the "zoom" magnifying glass which looks odd */ if (rrddbcount == 0) { /* No graph */ fwrite(blankimg, 1, sizeof(blankimg), stdout); return; } #endif } /* All set - generate the graph */ rrd_clear_error(); #ifdef RRDTOOL12 result = rrd_graph(rrdargcount, rrdargs, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax); /* * If we have neither the upper- nor lower-limits of the graph, AND we allow vertical * zooming of this graph, then save the upper/lower limit values and flag that we have * them. The values are then used for the zoom URL we construct later on. */ if (!haveupperlimit && !havelowerlimit) { upperlimit = ymax; haveupperlimit = 1; lowerlimit = ymin; havelowerlimit = 1; } #else result = rrd_graph(rrdargcount, rrdargs, &calcpr, &xsize, &ysize); #endif /* Was it OK ? */ if (rrd_test_error() || (result != 0)) { if (calcpr) { int i; for (i=0; (calcpr[i]); i++) xfree(calcpr[i]); calcpr = NULL; } errormsg(rrd_get_error()); } } void generate_zoompage(char *selfURI) { fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); sethostenv(displayname, "", service, colorname(bgcolor), hostname); headfoot(stdout, "graphs", "", "header", bgcolor); fprintf(stdout, "
\n"); fprintf(stdout, "
\n"); fprintf(stdout, "\n"); graph_link(stdout, selfURI, gtype, 0); fprintf(stdout, "
\n"); { char zoomjsfn[PATH_MAX]; struct stat st; sprintf(zoomjsfn, "%s/web/zoom.js", xgetenv("XYMONHOME")); if (stat(zoomjsfn, &st) == 0) { FILE *fd; char *buf; size_t n; char *zoomrightoffsetmarker = "var cZoomBoxRightOffset = -"; char *zoomrightoffsetp; fd = fopen(zoomjsfn, "r"); if (fd) { buf = (char *)malloc(st.st_size+1); n = fread(buf, 1, st.st_size, fd); fclose(fd); #ifdef RRDTOOL12 zoomrightoffsetp = strstr(buf, zoomrightoffsetmarker); if (zoomrightoffsetp) { zoomrightoffsetp += strlen(zoomrightoffsetmarker); memcpy(zoomrightoffsetp, "30", 2); } #endif fwrite(buf, 1, n, stdout); } } } headfoot(stdout, "graphs", "", "footer", bgcolor); } int main(int argc, char *argv[]) { /* Command line settings */ int argi; char *envarea = NULL; char *rrddir = NULL; /* RRD files top-level directory */ char *gdeffn = NULL; /* graphs.cfg file */ char *graphfn = "-"; /* Output filename, default is stdout */ char *selfURI; /* Setup defaults */ graphwidth = atoi(xgetenv("RRDWIDTH")); graphheight = atoi(xgetenv("RRDHEIGHT")); /* See what we want to do - i.e. get hostname, service and graph-type */ parse_query(); /* Handle any command-line args */ for (argi=1; (argi < argc); argi++) { if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (argnmatch(argv[argi], "--rrddir=")) { char *p = strchr(argv[argi], '='); rrddir = strdup(p+1); } else if (argnmatch(argv[argi], "--config=")) { char *p = strchr(argv[argi], '='); gdeffn = strdup(p+1); } else if (strcmp(argv[argi], "--save=") == 0) { char *p = strchr(argv[argi], '='); graphfn = strdup(p+1); } } redirect_cgilog("showgraph"); selfURI = build_selfURI(); if (action == ACT_MENU) { build_menu_page(selfURI, graphend-graphstart); return 0; } if ((action == ACT_VIEW) || !(haveupperlimit && havelowerlimit)) { generate_graph(gdeffn, rrddir, graphfn); } if (action == ACT_SELZOOM) { generate_zoompage(selfURI); } return 0; } xymon-4.3.7/web/eventlog.c0000664000175000017500000003375711615341300015000 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon eventlog generator tool. */ /* */ /* This displays the "eventlog" found on the "All non-green status" page. */ /* It also implements a CGI tool to show an eventlog for a given period of */ /* time, as a reporting function. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* Host/test/color/start/end filtering code by Eric Schwimmer 2005 */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: eventlog.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" int maxcount = 100; /* Default: Include last 100 events */ int maxminutes = 240; /* Default: for the past 4 hours */ char *totime = NULL; char *fromtime = NULL; char *hostregex = NULL; char *exhostregex = NULL; char *testregex = NULL; char *extestregex = NULL; char *pageregex = NULL; char *expageregex = NULL; char *colorregex = NULL; int ignoredialups = 0; int topcount = 0; eventsummary_t summarybar = XYMON_S_NONE; countsummary_t counttype = XYMON_COUNT_NONE; char *webfile_hf = "event"; char *webfile_form = "event_form"; cgidata_t *cgidata = NULL; char *periodstring = NULL; static void parse_query(void) { cgidata_t *cwalk; cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ if (strcasecmp(cwalk->name, "MAXCOUNT") == 0) { maxcount = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "MAXTIME") == 0) { maxminutes = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "FROMTIME") == 0) { if (*(cwalk->value)) fromtime = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "TOTIME") == 0) { if (*(cwalk->value)) totime = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "HOSTMATCH") == 0) { if (*(cwalk->value)) hostregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "EXHOSTMATCH") == 0) { if (*(cwalk->value)) exhostregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "TESTMATCH") == 0) { if (*(cwalk->value)) testregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "EXTESTMATCH") == 0) { if (*(cwalk->value)) extestregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "PAGEMATCH") == 0) { if (*(cwalk->value)) pageregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "EXPAGEMATCH") == 0) { if (*(cwalk->value)) expageregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "COLORMATCH") == 0) { if (*(cwalk->value)) colorregex = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "NODIALUPS") == 0) { ignoredialups = 1; } else if (strcasecmp(cwalk->name, "TOP") == 0) { if (*(cwalk->value)) topcount = atoi(cwalk->value); } else if (strcasecmp(cwalk->name, "SUMMARY") == 0) { if (strcasecmp(cwalk->value, "hosts") == 0) summarybar = XYMON_S_HOST_BREAKDOWN; else if (strcasecmp(cwalk->value, "services") == 0) summarybar = XYMON_S_SERVICE_BREAKDOWN; else summarybar = XYMON_S_NONE; } else if (strcasecmp(cwalk->name, "COUNTTYPE") == 0) { if (strcasecmp(cwalk->value, "events") == 0) counttype = XYMON_COUNT_EVENTS; else if (strcasecmp(cwalk->value, "duration") == 0) counttype = XYMON_COUNT_DURATION; else counttype = XYMON_COUNT_NONE; } else if (strcasecmp(cwalk->name, "TIMETXT") == 0) { if (*(cwalk->value)) periodstring = strdup(cwalk->value); } cwalk = cwalk->next; } } void show_topchanges(FILE *output, countlist_t *hostcounthead, countlist_t *svccounthead, event_t *eventhead, int topcount, time_t firstevent, time_t lastevent) { fprintf(output, "

%s

\n", (periodstring ? periodstring : "")); fprintf(output, "\n"); fprintf(output, "\n"); if (hostcounthead && (output != NULL)) { countlist_t *cwalk; int i; unsigned long others = 0, totalcount = 0; strbuffer_t *s = newstrbuffer(0); strbuffer_t *othercriteria = newstrbuffer(0); if (hostregex) { addtobuffer(othercriteria, "&HOSTMATCH="); addtobuffer(othercriteria, hostregex); } if (exhostregex) addtobuffer(s, exhostregex); if (testregex) { addtobuffer(othercriteria, "&TESTMATCH="); addtobuffer(othercriteria, testregex); } if (extestregex) { addtobuffer(othercriteria, "&EXTESTMATCH="); addtobuffer(othercriteria, extestregex); } if (pageregex) { addtobuffer(othercriteria, "&PAGEMATCH="); addtobuffer(othercriteria, pageregex); } if (expageregex) { addtobuffer(othercriteria, "&EXPAGEMATCH="); addtobuffer(othercriteria, expageregex); } if (colorregex) { addtobuffer(othercriteria, "&COLORMATCH="); addtobuffer(othercriteria, colorregex); } if (ignoredialups) { addtobuffer(othercriteria, "&NODIALUPS=on"); } addtobuffer(othercriteria, "&SUMMARY=services"); addtobuffer(othercriteria, "&TIMETXT="); addtobuffer(othercriteria, periodstring); if (counttype == XYMON_COUNT_EVENTS) addtobuffer(othercriteria, "&COUNTTYPE=events"); else if (counttype == XYMON_COUNT_DURATION) addtobuffer(othercriteria, "&COUNTTYPE=duration"); fprintf(output, "\n"); freestrbuffer(s); freestrbuffer(othercriteria); } if (svccounthead && (output != NULL)) { countlist_t *cwalk; int i; unsigned long others = 0, totalcount = 0; strbuffer_t *s = newstrbuffer(0); strbuffer_t *othercriteria = newstrbuffer(0); if (hostregex) { addtobuffer(othercriteria, "&HOSTMATCH="); addtobuffer(othercriteria, hostregex); } if (exhostregex) { addtobuffer(othercriteria, "&EXHOSTMATCH="); addtobuffer(othercriteria, exhostregex); } if (testregex) { addtobuffer(othercriteria, "&TESTMATCH="); addtobuffer(othercriteria, testregex); } if (extestregex) addtobuffer(s, extestregex); if (pageregex) { addtobuffer(othercriteria, "&PAGEMATCH="); addtobuffer(othercriteria, pageregex); } if (expageregex) { addtobuffer(othercriteria, "&EXPAGEMATCH="); addtobuffer(othercriteria, expageregex); } if (colorregex) { addtobuffer(othercriteria, "&COLORMATCH="); addtobuffer(othercriteria, colorregex); } if (ignoredialups) { addtobuffer(othercriteria, "&NODIALUPS=on"); } addtobuffer(othercriteria, "&SUMMARY=hosts"); addtobuffer(othercriteria, "&TIMETXT="); addtobuffer(othercriteria, periodstring); if (counttype == XYMON_COUNT_EVENTS) addtobuffer(othercriteria, "&COUNTTYPE=events"); else if (counttype == XYMON_COUNT_DURATION) addtobuffer(othercriteria, "&COUNTTYPE=duration"); fprintf(output, "\n"); freestrbuffer(s); freestrbuffer(othercriteria); } fprintf(output, "\n"); fprintf(output, "
\n"); fprintf(output, " \n", topcount); fprintf(output, " \n", topcount); fprintf(output, " \n", (counttype == XYMON_COUNT_EVENTS) ? "State changes" : "Seconds red/yellow"); /* Compute the total count */ for (i=0, cwalk=hostcounthead; (cwalk); i++, cwalk=cwalk->next) totalcount += cwalk->total; for (i=0, cwalk=hostcounthead; (cwalk && (cwalk->total > 0)); i++, cwalk=cwalk->next) { if (i < topcount) { fprintf(output, " \n", xmh_item(cwalk->src, XMH_HOSTNAME), (unsigned long)firstevent, (unsigned long)lastevent, STRBUF(othercriteria), xmh_item(cwalk->src, XMH_HOSTNAME), cwalk->total, ((100.0 * cwalk->total) / totalcount)); if (STRBUFLEN(s) > 0) addtobuffer(s, "|"); addtobuffer(s, "^"); addtobuffer(s, xmh_item(cwalk->src, XMH_HOSTNAME)); addtobuffer(s, "$"); } else { others += cwalk->total; } } fprintf(output, " \n", STRBUF(s), (unsigned long)firstevent, (unsigned long)lastevent, STRBUF(othercriteria), "Other hosts", others, ((100.0 * others) / totalcount)); fprintf(output, " \n"); fprintf(output, " \n", totalcount); fprintf(output, "
Top %d hosts
Host%s
%s%lu(%6.2f %%)
%s%lu(%6.2f %%)

Total%lu 
\n"); fprintf(output, "
\n"); fprintf(output, " \n", topcount); fprintf(output, " \n", topcount); fprintf(output, " \n", (counttype == XYMON_COUNT_EVENTS) ? "State changes" : "Seconds red/yellow"); /* Compute the total count */ for (i=0, cwalk=svccounthead; (cwalk); i++, cwalk=cwalk->next) totalcount += cwalk->total; for (i=0, cwalk=svccounthead; (cwalk && (cwalk->total > 0)); i++, cwalk=cwalk->next) { if (i < topcount) { fprintf(output, " \n", ((htnames_t *)cwalk->src)->name, (unsigned long)firstevent, (unsigned long)lastevent, STRBUF(othercriteria), ((htnames_t *)cwalk->src)->name, cwalk->total, ((100.0 * cwalk->total) / totalcount)); if (STRBUFLEN(s) > 0) addtobuffer(s, "|"); addtobuffer(s, "^"); addtobuffer(s, ((htnames_t *)cwalk->src)->name); addtobuffer(s, "$"); } else { others += cwalk->total; } } fprintf(output, " \n", STRBUF(s), (unsigned long)firstevent, (unsigned long)lastevent, STRBUF(othercriteria), "Other services", others, ((100.0 * others) / totalcount)); fprintf(output, " \n"); fprintf(output, " \n", totalcount); fprintf(output, "
Top %d services
Service%s
%s%lu(%6.2f %%)
%s%lu(%6.2f %%)

Total%lu 
\n"); fprintf(output, "
\n"); } int main(int argc, char *argv[]) { int argi; char *envarea = NULL; for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (argnmatch(argv[argi], "--top")) { topcount = 10; webfile_hf = "topchanges"; webfile_form = "topchanges_form"; maxminutes = -1; maxcount = -1; } else if (strcmp(argv[argi], "--debug=")) { debug = 1; } } redirect_cgilog("eventlog"); load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); cgidata = cgi_request(); if (cgidata == NULL) { /* Present the query form */ sethostenv("", "", "", colorname(COL_BLUE), NULL); showform(stdout, webfile_hf, webfile_form, COL_BLUE, getcurrenttime(NULL), NULL, NULL); return 0; } parse_query(); if (periodstring && (fromtime || totime)) { strbuffer_t *pstr = newstrbuffer(1024); if (fromtime && totime) { addtobuffer(pstr, "Events between "); addtobuffer(pstr, htmlquoted(fromtime)); addtobuffer(pstr, "- "); addtobuffer(pstr, htmlquoted(totime)); } else if (fromtime) { addtobuffer(pstr, "Events since "); addtobuffer(pstr, htmlquoted(fromtime)); } else if (totime) { addtobuffer(pstr, "Events until "); addtobuffer(pstr, htmlquoted(totime)); } xfree(periodstring); periodstring = grabstrbuffer(pstr); } /* Now generate the webpage */ headfoot(stdout, webfile_hf, "", "header", COL_GREEN); fprintf(stdout, "
\n"); if (topcount == 0) { do_eventlog(stdout, maxcount, maxminutes, fromtime, totime, pageregex, expageregex, hostregex, exhostregex, testregex, extestregex, colorregex, ignoredialups, NULL, NULL, NULL, NULL, counttype, summarybar, periodstring); } else { countlist_t *hcounts, *scounts; event_t *events; time_t firstevent, lastevent; do_eventlog(NULL, -1, -1, fromtime, totime, pageregex, expageregex, hostregex, exhostregex, testregex, extestregex, colorregex, ignoredialups, NULL, &events, &hcounts, &scounts, counttype, XYMON_S_NONE, NULL); lastevent = (totime ? eventreport_time(totime) : getcurrenttime(NULL)); if (fromtime) { firstevent = eventreport_time(fromtime); } else if (events) { event_t *ewalk; ewalk = events; while (ewalk->next) ewalk = ewalk->next; firstevent = ewalk->eventtime; } else firstevent = 0; show_topchanges(stdout, hcounts, scounts, events, topcount, firstevent, lastevent); } fprintf(stdout, "
\n"); headfoot(stdout, webfile_hf, "", "footer", COL_GREEN); return 0; } xymon-4.3.7/web/statusreport.cgi.10000664000175000017500000000566011671641417016421 0ustar henrikhenrik.TH STATUSREPORT.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME statusreport.cgi \- CGI program to report a status for a group of servers .SH SYNOPSIS .B "statusreport.cgi --column=COLUMNNAME [options]" .SH DESCRIPTION \fBstatusreport.cgi\fR is a CGI tool to generate a simple HTML report showing the current status of a single column for a group of Xymon hosts. E.g. You can use this report to get an overview of all of the SSL certificates that are about to expire. The generated webpage is a simple HTML table, suitable for copying into other documents or e-mail. statusreport.cgi runs as a CGI program, invoked by your webserver. It is normally run via a wrapper shell-script in the CGI directory for Xymon. .SH EXAMPLES The Xymon installation includes two web report scripts using this CGI tool: The \fBcertreport.sh\fR script generates a list of SSL server certificates that are yellow or red (i.e. they will expire soon); and the \fBnongreen.sh\fR script generates a report of all statuses that are currently non-green. These can be accessed from a web browser through a URL referencing the script in the Xymon CGI directory (e.g. "/xymon-cgi/xymon-nongreen.sh"). .SH OPTIONS .IP "--column=COLUMNNAME" Report the status of the COLUMNNAME column. .IP "--all" Report the status for all hosts known to Xymon. By default, this tool reports only on the hosts found on the current page from where the CGI was invoked (by looking at the "pagepath" cookie). .IP "--filter=CRITERIA" Only report on statuses that match the CRITERIA setting. See the .I xymon(1) man-page - in the "xymondboard" command description - for details about specifying filters. .IP "--heading=HTML" Defines the webpage heading - i.e. the "title" tag in the generated HTML code. .IP "--show-column" Include the column name in the display. .IP "--show-colors" Show the status color on the generated webpage. The default is to not show the status color. .IP "--no-colors" Do not include text showing the current color of each status in the report. This is the default. .IP "--show-summary" Show only a summary of the important lines in the status message. By default, the entire status message appears in the generated HTML code. This option causes the first non-blank line of the status message to be shown, and also any lines beginning with "&COLOR" which is used by many status messages to point out lines of interest (non-green lines only, though). .IP "--show-message" Show the entire message on the webpage. This is the default. .IP "--link" Include HTML links to the host "info" page, and the status page. .IP "--embedded" Only generate the HTML table, not a full webpage. This can be used to embed the status report into an external webpage. .IP "--env=FILENAME" Load the environment from FILENAME before executing the CGI. .IP "--area=NAME" Load environment variables for a specific area. NB: if used, this option must appear before any --env=FILENAME option. .SH "SEE ALSO" xymon(7) xymon-4.3.7/web/findhost.cgi.10000664000175000017500000000314111671641417015450 0ustar henrikhenrik.TH FINDHOST.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME findhost.cgi \- Xymon CGI script to find hosts .SH SYNOPSIS .B "findhost.cgi?host=REGEX" .SH DESCRIPTION \fBfindhost.cgi\fR is invoked as a CGI script via the findhost.sh CGI wrapper. findhost.cgi is passed a QUERY_STRING environment variable with the "host=REGEX" parameter. The REGEX is a Posix regular expression (see .I regex(7) ) describing the hostnames to look for. A trailing wildcard is assumed on all hostnames - e.g. requesting the hostname "www" will match any host whose name begins with "www". It then produces a single web page, listing all of the hosts that matched any of the hostnames, with links to the Xymon webpages where they are located. The output page lists hosts in the order they appear in the .I hosts.cfg(5) file. A sample web page implementing the search facility is included with xymongen, you access it via the URL /xymon/help/findhost.html. .SH OPTIONS .IP "--env=FILENAME Loads the environment from FILENAME before executing the CGI. .SH FILES .IP "$XYMONHOME/web/findhost_header" HTML header file for the generated web page .IP "$XYMONHOME/web/findhost_footer" HTML footer file for the generated web page .IP "$XYMONHOME/web/findhost_form" Query form displayed when findhost.cgi is called with no parameters. .SH "ENVIRONMENT VARIABLES" .IP HOSTSCFG findhost.cgi uses the HOSTSCFG environment variable to find the hosts.cfg file listing all known hosts and their page locations. .IP XYMONHOME Used to locate the template files for the generated web pages. .SH "SEE ALSO" xymongen(1), hosts.cfg(5), xymonserver.cfg(5) xymon-4.3.7/web/acknowledge.c0000664000175000017500000003404511667410370015442 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon alert acknowledgment CGI tool. */ /* */ /* This is a CGI script for handling acknowledgments of alerts. */ /* If called with no CGI query, it will present the acknowledgment form; */ /* if called with a proper CGI query string it will send an ack-message to */ /* the Xymon daemon. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* Minute/Hour/Day options for duration by Heather Keen 2011. */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: acknowledge.c 6790 2011-12-06 12:56:24Z storner $"; #include #include #include #include #include #include #include #include #include "libxymon.h" #include "version.h" static cgidata_t *cgidata = NULL; static int nopin = 0; typedef struct acklist_t { int id, checked; int acknum; int validity; char *period; char *hostname; char *testname; char *ackmsg; struct acklist_t *next; } acklist_t; acklist_t *ackhead = NULL; acklist_t *acktail = NULL; char *validityall = NULL; char *periodall = NULL; char *ackmsgall = NULL; enum { ACK_UNKNOWN, ACK_OLDSTYLE, ACK_ONE, ACK_MANY } reqtype = ACK_UNKNOWN; int sendnum = 0; static void parse_query(void) { cgidata_t *cwalk; /* See what kind of request this is */ for (cwalk=cgidata; (cwalk); cwalk = cwalk->next) { if (nopin && (strcmp(cwalk->name, "Send_all") == 0)) { /* User pushed the "Send all" button */ reqtype = ACK_MANY; } else if (nopin && (strncmp(cwalk->name, "Send_", 5) == 0)) { /* User pushed a specific "Send" button */ sendnum = atoi(cwalk->name+5); reqtype = ACK_ONE; } else if (!nopin && (strcmp(cwalk->name, "Send") == 0)) { /* Old style request */ reqtype = ACK_OLDSTYLE; } } for (cwalk=cgidata; (cwalk); cwalk = cwalk->next) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ int id = 0; char *acknum = NULL, *validity = NULL, *ackmsg = NULL, *period = NULL; char *hostname = NULL, *testname = NULL, *checked = NULL; char *delim; if (strncasecmp(cwalk->name, "NUMBER", 6) == 0) { if (*cwalk->value) acknum = cwalk->value; delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1); } else if (strcasecmp(cwalk->name, "DELAY_all") == 0) { if (*cwalk->value) validityall = cwalk->value; } else if (strcasecmp(cwalk->name, "PERIOD_all") == 0) { if (*cwalk->value) periodall = cwalk->value; } else if (strcasecmp(cwalk->name, "MESSAGE_all") == 0) { if (*cwalk->value) ackmsgall = cwalk->value; } else if (strncasecmp(cwalk->name, "DELAY", 5) == 0) { if (*cwalk->value) validity = cwalk->value; delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1); } else if (strncasecmp(cwalk->name, "PERIOD", 5) == 0) { if (*cwalk->value) period = cwalk->value; delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1); } else if (strncasecmp(cwalk->name, "MESSAGE", 7) == 0) { if (*cwalk->value) ackmsg = cwalk->value; delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1); } else if (strncasecmp(cwalk->name, "HOSTNAME", 8) == 0) { if (*cwalk->value) hostname = cwalk->value; delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1); } else if (strncasecmp(cwalk->name, "TESTNAME", 8) == 0) { if (*cwalk->value) testname = cwalk->value; delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1); } else if (strncasecmp(cwalk->name, "CHECKED", 7) == 0) { if (*cwalk->value) checked = cwalk->value; delim = strchr(cwalk->name, '_'); if (delim) id = atoi(delim+1); } switch (reqtype) { case ACK_UNKNOWN: break; case ACK_OLDSTYLE: id = 1; /* Fall through */ case ACK_ONE: if ((id == sendnum) || (reqtype == ACK_OLDSTYLE)) checked = "checked"; /* Fall through */ case ACK_MANY: if (id) { acklist_t *awalk; awalk = ackhead; while (awalk && (awalk->id != id)) awalk = awalk->next; if (!awalk) { awalk = (acklist_t *)calloc(1, sizeof(acklist_t)); awalk->id = id; awalk->next = NULL; if (!ackhead) ackhead = acktail = awalk; else { acktail->next = awalk; acktail = awalk; } } if (acknum) awalk->acknum = atoi(acknum); if (validity) awalk->validity = durationvalue(validity); if (ackmsg) awalk->ackmsg = strdup(ackmsg); if (hostname) awalk->hostname = strdup(hostname); if (testname) awalk->testname = strdup(testname); if (period) awalk->period = strdup(period); if (checked) awalk->checked = 1; } break; } } } void generate_ackline(FILE *output, char *hname, char *tname, char *ackcode) { static int num = 0; char numstr[10]; num++; if (ackcode) { sprintf(numstr, "%d", num); } else { strcpy(numstr, "all"); } fprintf(output, "\n"); fprintf(output, " %s\n", (hname ? htmlquoted(hname) : " ")); fprintf(output, " %s\n", (tname ? htmlquoted(tname) : " ")); fprintf(output, " \n", numstr, numstr); fprintf(output, " \n", numstr); fprintf(output, " \n"); if (ackcode && hname && tname) { fprintf(output, " \n", num, htmlquoted(ackcode)); fprintf(output, " \n", num, htmlquoted(hname)); fprintf(output, " \n", num, htmlquoted(tname)); fprintf(output, " \n", num); } else { fprintf(output, "  \n"); } fprintf(output, " \n"); fprintf(output, " \n"); if (ackcode) fprintf(output, " \n", num); else fprintf(output, " \n"); fprintf(output, " \n"); fprintf(output, "\n"); } int main(int argc, char *argv[]) { int argi; char *envarea = NULL; int obeycookies = 1; char *accessfn = NULL; for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (strcmp(argv[argi], "--no-pin") == 0) { nopin = 1; } else if (strcmp(argv[argi], "--no-cookies") == 0) { obeycookies = 0; } else if (argnmatch(argv[argi], "--access=")) { char *p = strchr(argv[argi], '='); accessfn = strdup(p+1); } } redirect_cgilog("ack"); cgidata = cgi_request(); if ( (nopin && (cgi_method == CGI_GET)) || (!nopin && (cgidata == NULL)) ) { /* Present the query form */ sethostenv("", "", "", colorname(COL_RED), NULL); printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); if (!nopin) { showform(stdout, "acknowledge", "acknowledge_form", COL_RED, getcurrenttime(NULL), NULL, NULL); } else { char *cmd; char *respbuf = NULL; char *hostname, *pagename; int gotfilter = 0, filtererror = 0; sendreturn_t *sres = NULL; headfoot(stdout, "acknowledge", "", "header", COL_RED); cmd = (char *)malloc(1024); strcpy(cmd, "xymondboard color=red,yellow fields=hostname,testname,cookie"); if (obeycookies && !gotfilter && ((hostname = get_cookie("host")) != NULL)) { if (*hostname) { pcre *dummy; char *re; re = (char *)malloc(3+strlen(hostname)); sprintf(re, "^%s$", hostname); dummy = compileregex(re); if (dummy) { /* Valid expression */ freeregex(dummy); cmd = (char *)realloc(cmd, 1024 + strlen(cmd) + strlen(re)); sprintf(cmd + strlen(cmd), " host=%s", re); gotfilter = 1; } else { filtererror = 1; printf("

Invalid hostname filter

\n"); } } } if (obeycookies && !gotfilter && ((pagename = get_cookie("pagepath")) != NULL)) { if (*pagename) { pcre *dummy; char *re; re = (char *)malloc(8 + strlen(pagename)); sprintf(re, "%s$|^%s/.+", pagename, pagename); dummy = compileregex(re); if (dummy) { /* Valid expression */ freeregex(dummy); cmd = (char *)realloc(cmd, 1024 + strlen(cmd) + strlen(re)); sprintf(cmd + strlen(cmd), " page=%s", re); gotfilter = 1; } else { filtererror = 1; printf("

Invalid pagename filter

\n"); } } } sres = newsendreturnbuf(1, NULL); if (!filtererror && (sendmessage(cmd, NULL, XYMON_TIMEOUT, sres) == XYMONSEND_OK)) { char *bol, *eoln; int first = 1; respbuf = getsendreturnstr(sres, 1); bol = respbuf; while (bol) { char *hname, *tname, *ackcode; eoln = strchr(bol, '\n'); if (eoln) *eoln = '\0'; hname = tname = ackcode = NULL; hname = strtok(bol, "|"); if (hname) tname = strtok(NULL, "|"); if (tname) ackcode = strtok(NULL, "|"); if (hname && tname && ackcode && (strcmp(hname, "summary") != 0)) { if (first) { fprintf(stdout, "
\n", getenv("SCRIPT_NAME")); fprintf(stdout, "
\n"); fprintf(stdout, "\n"); first = 0; } generate_ackline(stdout, hname, tname, ackcode); } if (eoln) bol = eoln+1; else bol = NULL; } if (first) { fprintf(stdout, "
No active alerts
\n"); fprintf(stdout, "\n"); } } freesendreturnbuf(sres); headfoot(stdout, "acknowledge", "", "footer", COL_RED); } } else if ( (nopin && (cgi_method == CGI_POST)) || (!nopin && (cgidata != NULL)) ) { char *xymonmsg; char *acking_user = ""; acklist_t *awalk; strbuffer_t *response = newstrbuffer(0); int count = 0; parse_query(); if (getenv("REMOTE_USER")) { char *remaddr = getenv("REMOTE_ADDR"); acking_user = (char *)malloc(1024 + strlen(getenv("REMOTE_USER")) + (remaddr ? strlen(remaddr) : 0)); sprintf(acking_user, "\nAcked by: %s", getenv("REMOTE_USER")); if (remaddr) sprintf(acking_user + strlen(acking_user), " (%s)", remaddr); } /* Load the host data (for access control) */ if (accessfn) { load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); load_web_access_config(accessfn); } addtobuffer(response, "
\n"); for (awalk = ackhead; (awalk); awalk = awalk->next) { char *msgline = (char *)malloc(1024 + (awalk->hostname ? strlen(awalk->hostname) : 0) + (awalk->testname ? strlen(awalk->testname) : 0)); if (!awalk->checked) continue; if (accessfn && (!web_access_allowed(getenv("REMOTE_USER"), awalk->hostname, awalk->testname, WEB_ACCESS_CONTROL))) continue; if ((reqtype == ACK_ONE) && (awalk->id != sendnum)) continue; if (reqtype == ACK_MANY) { if (!awalk->ackmsg) awalk->ackmsg = ackmsgall; if (!awalk->validity && validityall) awalk->validity = durationvalue(validityall); if (periodall) awalk->period = periodall; } if (strncmp(awalk->period, "hour", 4) == 0) awalk->validity *= 60; else if (strncmp(awalk->period, "day", 4) == 0) awalk->validity *= 60*24; count++; if (!awalk->ackmsg || !awalk->validity || !awalk->acknum) { if (awalk->hostname && awalk->testname) { sprintf(msgline, "NO ACK sent for host %s / test %s", awalk->hostname, awalk->testname); } else { sprintf(msgline, "NO ACK sent for item %d", awalk->id); } addtobuffer(response, msgline); addtobuffer(response, ": Duration or message not set
\n"); continue; } xymonmsg = (char *)malloc(1024 + strlen(awalk->ackmsg) + strlen(acking_user)); sprintf(xymonmsg, "xymondack %d %d %s %s", awalk->acknum, awalk->validity, awalk->ackmsg, acking_user); if (sendmessage(xymonmsg, NULL, XYMON_TIMEOUT, NULL) == XYMONSEND_OK) { if (awalk->hostname && awalk->testname) { sprintf(msgline, "Acknowledge sent for host %s / test %s
\n", awalk->hostname, awalk->testname); } else { sprintf(msgline, "Acknowledge sent for code %d
\n", awalk->acknum); } } else { if (awalk->hostname && awalk->testname) { sprintf(msgline, "Failed to send acknowledge for host %s / test %s
\n", awalk->hostname, awalk->testname); } else { sprintf(msgline, "Failed to send acknowledge for code %d
\n", awalk->acknum); } } addtobuffer(response, msgline); xfree(xymonmsg); } if (count == 0) addtobuffer(response, "No acks requested\n"); addtobuffer(response, "
\n"); fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); headfoot(stdout, "acknowledge", "", "header", COL_RED); fprintf(stdout, "%s", STRBUF(response)); headfoot(stdout, "acknowledge", "", "footer", COL_RED); } return 0; } xymon-4.3.7/web/report.cgi.10000664000175000017500000000550311671641417015151 0ustar henrikhenrik.TH REPORT.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME report.cgi \- CGI front-end to xymongen reporting .SH SYNOPSIS .B "report.cgi [--noclean] [xymongen-options]" .SH DESCRIPTION \fBreport.cgi\fR is invoked as a CGI script via the report.sh CGI wrapper. It triggers the generation of a Xymon availability report for the timeperiod specified by the CGI paramaters. report.cgi is passed a QUERY_STRING environment variable with the following parameters: start-mon (Start month of the report) start-day (Start day-of-month of the report) start-yr (Start year of the report) end-mon (End month of the report) end-day (End day-of-month of the report) end-yr (End year of the report) style (Report style) The following non-standard parameters are handled by the xymongen version of report.cgi: suburl (Page in report to go to, if not the top page) The "month" parameters must be specified as the three-letter english month name abbreviation: Jan, Feb, Mar ... Start- and end-days are in the range 1..31; the start- and end-year must be specified including century (e.g. "2003"). End-times beyond the current time are silently replaced with the current time. The generated report will include data for the start- and end-days, i.e. the report will begin at 00:00:00 of the start-day, and end at 23:59:59 of the end-day. The "style" parameter is passed directly to .I xymongen(1) and should be "crit", "non-crit" or "all". Other values result in undefined behaviour. All of the processing involved in generating the report is done by invoking .I xymongen(1) with the proper "--reportopts" option. .SH OPTIONS .IP --noclean Do not clean the XYMONREPDIR directory of old reports. Makes the report-tool go a bit faster - instead, you can clean up the XYMONREPDIR directory e.g. via a cron-job. .IP "--env=FILENAME" Load the environment from FILENAME before executing the CGI. .IP xymongen-options All other options passed to report.cgi are passed on to the .I xymongen(1) program building the report files. .SH FILES .IP $XYMONHOME/web/report_header HTML template header for the report request form .IP $XYMONHOME/web/report_footer HTML template footer for the report request form .IP $XYMONHOME/web/report_form HTML template report request form .SH "ENVIRONMENT VARIABLES" .IP XYMONGENREPOPTS xymongen options passed by default to the report.cgi. This happens in the report.sh wrapper. .IP XYMONHOME Home directory of the Xymon server installation .IP XYMONREPDIR Directory where generated reports are stored. This directory must be writable by the userid executing the CGI script, typically "www", "apache" or "nobody". Default: $XYMONHOME/www/rep/ .IP XYMONREPURL The URL prefix to use when accessing the reports via a browser. Default: $XYMONWEB/rep .SH "SEE ALSO" xymongen(1), hosts.cfg(5), xymonserver.cfg(5) xymon-4.3.7/web/criticalview.sh.DIST0000664000175000017500000000025711535462534016577 0ustar henrikhenrik#!/bin/sh # This is a wrapper for the Xymon criticalview.cgi script . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/criticalview.cgi $CGI_CRITVIEW_OPTS xymon-4.3.7/web/showgraph.cgi.10000664000175000017500000000537011671641417015642 0ustar henrikhenrik.TH SHOWGRAPH.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME showgraph.cgi \- CGI to generate Xymon trend graphs .SH SYNOPSIS .B "showgraph [options]" .SH DESCRIPTION \fBshowgraph.cgi\fR is invoked as a CGI script via the showgraph.sh CGI wrapper. showgraph.cgi is passed a QUERY_STRING environment variable with the following parameters: .sp .BR host Name of the host to generate a graph for .sp .BR service Name of the service to generate a graph for .sp .BR disp Display-name of the host, used on the generated graphs instead of hostname. .sp .BR graph Can be "hourly", "daily", "weekly" or "monthly" to select the time period that the graph covers. .sp .BR first Used to split multi-graphs into multiple graphs. This causes showgraph.cgi to generate only the graphs starting with the "first'th" graph and continuing for "count". .sp .BR count Number of graphs in a multi-graph. .sp .BR upper Set the upper limit of the graph. See .I rrdgraph(1) for a description of the "-u" option. .sp .BR lower Set the lower limit of the graph. See .I rrdgraph(1) for a description of the "-l" option. .sp .BR graph_start Set the starttime of the graph. This is used in zoom-mode. .sp .BR graph_end Set the end-time of the graph. This is used in zoom-mode. .sp .BR action=menu Generate an HTML page with links to 4 graphs, representing the hourly, weekly, monthly and yearly graphs. Doesn't actually generate any graphs, only the HTML that links to the graphs. .sp .BR action=selzoom Generate an HTML page with link to single graph, and with JavaScript code that lets the user select part of the graph for a zoom-operation. The JavaScript invokes showgraph.cgi with "action=showzoom" to generate the zoomed graph webpage. .sp .BR action=showzoom Generate HTML with a link to the zoomed graph image. This link goes to an "action=view" invocation of showgraph.cgi. .sp .BR action=view Generate a single graph image. .SH OPTIONS .IP "--config=FILENAME" Loads the graph configuration file from FILENAME. If not specified, the file $XYMONHOME/etc/graphs.cfg is used. See the .I graphs.cfg(5) for details about this file. .IP "--env=FILENAME" Loads the environment settings defined in FILENAME before executing the CGI. .IP "--rrddir=DIRECTORY" The top-level directory for the RRD files. If not specified, the directory given by the XYMONRRDS environment is used. .IP "--save=FILENAME" Instead of returning the image via the CGI interface (i.e. on stdout), save the generated image to FILENAME. .IP "--debug" Enable debugging output. .SH ENVIRONMENT .sp .BR QUERY_STRING Provided by the webserver CGI interface, this decides what graph to generate. .SH FILES .sp .BR graphs.cfg: The configuration file determining how graphs are generated from RRD files. .SH "SEE ALSO" graphs.cfg(5), xymon(7), rrdtool(1) xymon-4.3.7/web/enadis.sh.DIST0000664000175000017500000000023511535462534015351 0ustar henrikhenrik#!/bin/sh # This is a wrapper for the Xymon enadis script . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/enadis.cgi $CGI_ENADIS_OPTS xymon-4.3.7/web/certreport.sh.DIST0000664000175000017500000000050011535462534016272 0ustar henrikhenrik#!/bin/sh # This is an example of using the Xymon statusreport.cgi tool. # Through the options, it generates a report of the currently # about-to-expire SSL certificates. . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/statusreport.cgi --column=sslcert --filter=color=red,yellow --all --no-colors xymon-4.3.7/web/confreport.c0000664000175000017500000006357211615341300015334 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon CGI tool to generate a report of the Xymon configuration */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: confreport.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" typedef struct hostlist_t { char *hostname; int testcount; htnames_t *tests; htnames_t *disks, *svcs, *procs; struct hostlist_t *next; } hostlist_t; typedef struct coltext_t { char *colname; char *coldescr; int used; struct coltext_t *next; } coltext_t; hostlist_t *hosthead = NULL; static char *pingcolumn = "conn"; static char *pingplus = "conn="; static char *coldelim = ";"; static coltext_t *chead = NULL; static int ccount = 0; static int criticalonly = 0; static int newcritconfig = 1; void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } static int host_compare(const void *v1, const void *v2) { hostlist_t **h1 = (hostlist_t **)v1; hostlist_t **h2 = (hostlist_t **)v2; return strcmp((*h1)->hostname, (*h2)->hostname); } static int test_compare(const void *v1, const void *v2) { htnames_t **t1 = (htnames_t **)v1; htnames_t **t2 = (htnames_t **)v2; return strcmp((*t1)->name, (*t2)->name); } static int is_net_test(char *tname) { char *miscnet[] = { NULL, "http", "dns", "dig", "rpc", "ntp", "ldap", "content", "sslcert", NULL }; int i; miscnet[0] = pingcolumn; /* Cannot be computed in advance */ if (find_tcp_service(tname)) return 1; for (i=0; (miscnet[i]); i++) if (strcmp(tname, miscnet[i]) == 0) return 1; return 0; } void use_columndoc(char *column) { coltext_t *cwalk; for (cwalk = chead; (cwalk && strcasecmp(cwalk->colname, column)); cwalk = cwalk->next); if (cwalk) cwalk->used = 1; } typedef struct tag_t { char *columnname; char *visualdata; /* The URL or other end-user visible test spec. */ char *expdata; int b1, b2, b3; /* "badFOO" values, if any */ struct tag_t *next; } tag_t; static void print_disklist(char *hostname) { /* * We get the list of monitored disks/filesystems by looking at the * set of disk RRD files for this host. That way we do not have to * parse the disk status reports that come in many different flavours. */ char dirname[PATH_MAX]; char fn[PATH_MAX]; DIR *d; struct dirent *de; char *p; sprintf(dirname, "%s/%s", xgetenv("XYMONRRDS"), hostname); d = opendir(dirname); if (!d) return; while ((de = readdir(d)) != NULL) { if (strncmp(de->d_name, "disk,", 5) == 0) { strcpy(fn, de->d_name + 4); p = strstr(fn, ".rrd"); if (!p) continue; *p = '\0'; p = fn; while ((p = strchr(p, ',')) != NULL) *p = '/'; fprintf(stdout, "%s
\n", fn); } } closedir(d); } char *criticalval(char *hname, char *tname, char *alerts) { static char *result = NULL; if (result) xfree(result); if (newcritconfig) { char *key; critconf_t *critrec; key = (char *)malloc(strlen(hname) + strlen(tname) + 2); sprintf(key, "%s|%s", hname, tname); critrec = get_critconfig(key, CRITCONF_FIRSTMATCH, NULL); if (!critrec) { result = strdup("No"); } else { char *tspec; tspec = (critrec->crittime ? timespec_text(critrec->crittime) : "24x7"); result = (char *)malloc(strlen(tspec) + 30); sprintf(result, "%s prio %d", tspec, critrec->priority); } xfree(key); } else { result = strdup((checkalert(alerts, tname) ? "Yes" : "No")); } return result; } static void print_host(hostlist_t *host, htnames_t *testnames[], int testcount) { int testi, rowcount, netcount; void *hinfo = hostinfo(host->hostname); char *dispname = NULL, *clientalias = NULL, *comment = NULL, *description = NULL, *pagepathtitle = NULL; char *net = NULL, *alerts = NULL; char *crittime = NULL, *downtime = NULL, *reporttime = NULL; char *itm; tag_t *taghead = NULL; int contidx = 0, haveping = 0; char contcol[1024]; activealerts_t *alert; strbuffer_t *buf = newstrbuffer(0); fprintf(stdout, "

\n"); fprintf(stdout, "

HostTestDurationCauseAckAck Multiple
\n", host->hostname); pagepathtitle = xmh_item(hinfo, XMH_PAGEPATHTITLE); if (!pagepathtitle || (strlen(pagepathtitle) == 0)) pagepathtitle = "Top page"; dispname = xmh_item(hinfo, XMH_DISPLAYNAME); if (dispname && (strcmp(dispname, host->hostname) == 0)) dispname = NULL; clientalias = xmh_item(hinfo, XMH_CLIENTALIAS); if (clientalias && (strcmp(clientalias, host->hostname) == 0)) clientalias = NULL; comment = xmh_item(hinfo, XMH_COMMENT); description = xmh_item(hinfo, XMH_DESCRIPTION); net = xmh_item(hinfo, XMH_NET); alerts = xmh_item(hinfo, XMH_NK); crittime = xmh_item(hinfo, XMH_NKTIME); if (!crittime) crittime = "24x7"; else crittime = strdup(timespec_text(crittime)); downtime = xmh_item(hinfo, XMH_DOWNTIME); if (downtime) downtime = strdup(timespec_text(downtime)); reporttime = xmh_item(hinfo, XMH_REPORTTIME); if (!reporttime) reporttime = "24x7"; else reporttime = strdup(timespec_text(reporttime)); rowcount = 1; if (pagepathtitle) rowcount++; if (dispname || clientalias) rowcount++; if (comment) rowcount++; if (description) rowcount++; if (!newcritconfig && crittime) rowcount++; if (downtime) rowcount++; if (reporttime) rowcount++; fprintf(stdout, "\n"); fprintf(stdout, "\n", rowcount); fprintf(stdout, "\n", (dispname ? dispname : host->hostname), xmh_item(hinfo, XMH_IP)); fprintf(stdout, "\n"); if (dispname || clientalias) { fprintf(stdout, "\n"); } if (pagepathtitle) fprintf(stdout, "\n", pagepathtitle); if (comment) fprintf(stdout, "\n", comment); if (description) fprintf(stdout, "\n", description); if (!newcritconfig && crittime) fprintf(stdout, "\n", crittime); if (downtime) fprintf(stdout, "\n", downtime); if (reporttime) fprintf(stdout, "\n", reporttime); /* Build a list of the network tests */ itm = xmh_item_walk(hinfo); while (itm) { char *visdata = NULL, *colname = NULL, *expdata = NULL; weburl_t bu; int dialuptest = 0, reversetest = 0, alwaystruetest = 0, httpextra = 0; if (*itm == '?') { dialuptest=1; itm++; } if (*itm == '!') { reversetest=1; itm++; } if (*itm == '~') { alwaystruetest=1; itm++; } if ( argnmatch(itm, "http") || argnmatch(itm, "content=http") || argnmatch(itm, "cont;http") || argnmatch(itm, "cont=") || argnmatch(itm, "nocont;http") || argnmatch(itm, "nocont=") || argnmatch(itm, "post;http") || argnmatch(itm, "post=") || argnmatch(itm, "nopost;http") || argnmatch(itm, "nopost=") || argnmatch(itm, "type;http") || argnmatch(itm, "type=") ) { visdata = decode_url(itm, &bu); colname = bu.columnname; if (!colname) { if (bu.expdata) { httpextra = 1; if (contidx == 0) { colname = "content"; contidx++; } else { sprintf(contcol, "content%d", contidx); colname = contcol; contidx++; } } else { colname = "http"; } } expdata = bu.expdata; } else if (strncmp(itm, "rpc=", 4) == 0) { colname = "rpc"; visdata = strdup(itm+4); } else if (strncmp(itm, "dns=", 4) == 0) { colname = "dns"; visdata = strdup(itm+4); } else if (strncmp(itm, "dig=", 4) == 0) { colname = "dns"; visdata = strdup(itm+4); } else if (strncmp(itm, pingplus, strlen(pingplus)) == 0) { haveping = 1; colname = pingcolumn; visdata = strdup(itm+strlen(pingplus)); } else if (is_net_test(itm)) { colname = strdup(itm); } if (colname) { tag_t *newitem; addtolist: for (newitem = taghead; (newitem && strcmp(newitem->columnname, colname)); newitem = newitem->next); if (!newitem) { newitem = (tag_t *)calloc(1, sizeof(tag_t)); newitem->columnname = strdup(colname); newitem->visualdata = (visdata ? strdup(visdata) : NULL); newitem->expdata = (expdata ? strdup(expdata) : NULL); newitem->next = taghead; taghead = newitem; } else { /* Multiple tags for one column - must be http */ newitem->visualdata = (char *)realloc(newitem->visualdata, strlen(newitem->visualdata) + strlen(visdata) + 5); strcat(newitem->visualdata, "
"); strcat(newitem->visualdata, visdata); } if (httpextra) { httpextra = 0; colname = "http"; expdata = NULL; goto addtolist; } } itm = xmh_item_walk(NULL); } if (!haveping && !xmh_item(hinfo, XMH_FLAG_NOCONN)) { for (testi = 0; (testi < testcount); testi++) { if (strcmp(testnames[testi]->name, pingcolumn) == 0) { tag_t *newitem = (tag_t *)calloc(1, sizeof(tag_t)); newitem->columnname = strdup(pingcolumn); newitem->next = taghead; taghead = newitem; } } } /* Add the "badFOO" settings */ itm = xmh_item_walk(hinfo); while (itm) { if (strncmp(itm, "bad", 3) == 0) { char *tname, *p; int b1, b2, b3, n = -1; tag_t *tag = NULL; tname = itm+3; p = strchr(tname, ':'); if (p) { *p = '\0'; n = sscanf(p+1, "%d:%d:%d", &b1, &b2, &b3); for (tag = taghead; (tag && strcmp(tag->columnname, tname)); tag = tag->next); *p = ':'; } if (tag && (n == 3)) { tag->b1 = b1; tag->b2 = b2; tag->b3 = b3; } } itm = xmh_item_walk(NULL); } if (taghead) { fprintf(stdout, "\n"); fprintf(stdout, "\n"); fprintf(stdout, "\n"); fprintf(stdout, "\n"); } if (netcount != testcount) { fprintf(stdout, "\n"); fprintf(stdout, "\n"); fprintf(stdout, "\n"); fprintf(stdout, "\n"); } /* Do the alerts */ alert = (activealerts_t *)calloc(1, sizeof(activealerts_t)); alert->hostname = host->hostname; alert->location = xmh_item(hinfo, XMH_ALLPAGEPATHS); strcpy(alert->ip, "127.0.0.1"); alert->color = COL_RED; alert->pagemessage = ""; alert->state = A_PAGING; alert->cookie = 12345; alert_printmode(2); for (testi = 0; (testi < testcount); testi++) { alert->testname = testnames[testi]->name; if (have_recipient(alert, NULL)) print_alert_recipients(alert, buf); } xfree(alert); if (STRBUFLEN(buf) > 0) { fprintf(stdout, "\n"); fprintf(stdout, "\n"); fprintf(stdout, "\n"); fprintf(stdout, "\n"); } /* Finish off this host */ fprintf(stdout, "
Basics%s (%s)
Aliases:"); if (dispname) fprintf(stdout, " %s", dispname); if (clientalias) fprintf(stdout, " %s", clientalias); fprintf(stdout, "
Monitoring location: %s
Comment: %s
Description: %s
Critical monitoring period: %s
Planned downtime: %s
SLA Reporting Period: %s
Network tests"); if (net) fprintf(stdout, "
(from %s)", net); fprintf(stdout, "
\n", host->hostname); fprintf(stdout, "\n"); } for (testi = 0, netcount = 0; (testi < testcount); testi++) { tag_t *twalk; for (twalk = taghead; (twalk && strcasecmp(twalk->columnname, testnames[testi]->name)); twalk = twalk->next); if (!twalk) continue; use_columndoc(testnames[testi]->name); fprintf(stdout, ""); fprintf(stdout, "", testnames[testi]->name); fprintf(stdout, "", criticalval(host->hostname, testnames[testi]->name, alerts)); fprintf(stdout, ""); fprintf(stdout, ""); fprintf(stdout, ""); netcount++; } if (taghead) { fprintf(stdout, "
ServiceCriticalC/Y/R limitsSpecifics
%s%s"); if (twalk->b1 || twalk->b2 || twalk->b3) { fprintf(stdout, "%d/%d/%d", twalk->b1, twalk->b2, twalk->b3); } else { fprintf(stdout, "-/-/-"); } fprintf(stdout, ""); fprintf(stdout, "%s", (twalk->visualdata ? twalk->visualdata : " ")); if (twalk->expdata) fprintf(stdout, " must return '%s'", twalk->expdata); fprintf(stdout, "
Local tests\n", host->hostname); fprintf(stdout, "\n"); } for (testi = 0; (testi < testcount); testi++) { tag_t *twalk; for (twalk = taghead; (twalk && strcasecmp(twalk->columnname, testnames[testi]->name)); twalk = twalk->next); if (twalk) continue; use_columndoc(testnames[testi]->name); fprintf(stdout, ""); fprintf(stdout, "", testnames[testi]->name); fprintf(stdout, "", criticalval(host->hostname, testnames[testi]->name, alerts)); fprintf(stdout, ""); /* Make up some default configuration data */ fprintf(stdout, ""); fprintf(stdout, ""); } if (netcount != testcount) { fprintf(stdout, "
ServiceCriticalC/Y/R limitsConfiguration (NB: Thresholds on client may differ)
%s%s-/-/-"); if (strcmp(testnames[testi]->name, "cpu") == 0) { fprintf(stdout, "UNIX - Yellow: Load average > 1.5, Red: Load average > 3.0
"); fprintf(stdout, "Windows - Yellow: CPU utilisation > 80%%, Red: CPU utilisation > 95%%"); } else if (strcmp(testnames[testi]->name, "disk") == 0) { fprintf(stdout, "Default limits: Yellow 90%% full, Red 95%% full
\n"); print_disklist(host->hostname); } else if (strcmp(testnames[testi]->name, "memory") == 0) { fprintf(stdout, "Yellow: swap/pagefile use > 80%%, Red: swap/pagefile use > 90%%"); } else if (strcmp(testnames[testi]->name, "procs") == 0) { htnames_t *walk; if (!host->procs) fprintf(stdout, "No processes monitored
\n"); for (walk = host->procs; (walk); walk = walk->next) { fprintf(stdout, "%s
\n", walk->name); } } else if (strcmp(testnames[testi]->name, "svcs") == 0) { htnames_t *walk; if (!host->svcs) fprintf(stdout, "No services monitored
\n"); for (walk = host->svcs; (walk); walk = walk->next) { fprintf(stdout, "%s
\n", walk->name); } } else { fprintf(stdout, " "); } fprintf(stdout, "
Alerts\n", host->hostname); fprintf(stdout, "\n"); fprintf(stdout, "%s", STRBUF(buf)); fprintf(stdout, "
ServiceRecipient1st DelayStop afterRepeatTime of DayColors
\n"); freestrbuffer(buf); } static int coltext_compare(const void *v1, const void *v2) { coltext_t **t1 = (coltext_t **)v1; coltext_t **t2 = (coltext_t **)v2; return strcmp((*t1)->colname, (*t2)->colname); } void load_columndocs(void) { char fn[PATH_MAX]; FILE *fd; strbuffer_t *inbuf; sprintf(fn, "%s/etc/columndoc.csv", xgetenv("XYMONHOME")); fd = fopen(fn, "r"); if (!fd) return; inbuf = newstrbuffer(0); initfgets(fd); /* Skip the header line */ if (!unlimfgets(inbuf, fd)) { fclose(fd); freestrbuffer(inbuf); return; } while (unlimfgets(inbuf, fd)) { char *s1 = NULL, *s2 = NULL; s1 = strtok(STRBUF(inbuf), coldelim); if (s1) s2 = strtok(NULL, coldelim); if (s1 && s2) { coltext_t *newitem = (coltext_t *)calloc(1, sizeof(coltext_t)); newitem->colname = strdup(s1); newitem->coldescr = strdup(s2); newitem->next = chead; chead = newitem; ccount++; } } fclose(fd); freestrbuffer(inbuf); } void print_columndocs(void) { coltext_t **clist; coltext_t *cwalk; int i; clist = (coltext_t **)malloc(ccount * sizeof(coltext_t *)); for (i=0, cwalk=chead; (cwalk); cwalk=cwalk->next,i++) clist[i] = cwalk; qsort(&clist[0], ccount, sizeof(coltext_t **), coltext_compare); fprintf(stdout, "

\n"); fprintf(stdout, "\n"); fprintf(stdout, "\n"); for (i=0; (iused) { fprintf(stdout, "\n", clist[i]->colname, clist[i]->coldescr); } } fprintf(stdout, "
Xymon column descriptions
%s%s
\n"); } htnames_t *get_proclist(char *hostname, char *statusbuf) { char *bol, *eol; char *marker; htnames_t *head = NULL, *tail = NULL; if (!statusbuf) return NULL; marker = (char *)malloc(strlen(hostname) + 3); sprintf(marker, "\n%s|", hostname); if (strncmp(statusbuf, marker+1, strlen(marker)-1) == 0) { /* Found at start of buffer */ bol = statusbuf; } else { bol = strstr(statusbuf, marker); if (bol) bol++; } xfree(marker); if (!bol) return NULL; bol += strlen(hostname) + 1; /* Skip hostname and delimiter */ marker = bol; eol = strchr(bol, '\n'); if (eol) *eol = '\0'; marker = strstr(marker, "\\n&"); while (marker) { char *p; htnames_t *newitem; marker += strlen("\\n&"); if (strncmp(marker, "green", 5) == 0) marker += 5; else if (strncmp(marker, "yellow", 6) == 0) marker += 6; else if (strncmp(marker, "red", 3) == 0) marker += 3; else marker = NULL; if (marker) { marker += strspn(marker, " \t"); p = strstr(marker, " - "); if (p) *p = '\0'; newitem = (htnames_t *)malloc(sizeof(htnames_t)); newitem->name = strdup(marker); newitem->next = NULL; if (!tail) { head = tail = newitem; } else { tail->next = newitem; tail = newitem; } if (p) { *p = ' '; marker = p; } marker = strstr(marker, "\\n&"); } } if (eol) *eol = '\n'; return head; } int main(int argc, char *argv[]) { int argi, hosti, testi; char *pagepattern = NULL, *hostpattern = NULL; char *envarea = NULL, *cookie = NULL, *nexthost; char *xymoncmd, *procscmd, *svcscmd; int alertcolors, alertinterval; char configfn[PATH_MAX]; char *respbuf = NULL, *procsbuf = NULL, *svcsbuf = NULL; hostlist_t *hwalk; htnames_t *twalk; hostlist_t **allhosts = NULL; htnames_t **alltests = NULL; int hostcount = 0, maxtests = 0; time_t now = getcurrenttime(NULL); sendreturn_t *sres; char *critconfigfn = NULL; int patternerror = 0; for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--delimiter=")) { char *p = strchr(argv[argi], '='); coldelim = strdup(p+1); } else if (strcmp(argv[argi], "--critical") == 0) { criticalonly = 1; } else if (strcmp(argv[argi], "--old-critical-config") == 0) { newcritconfig = 0; } else if (argnmatch(argv[argi], "--critical-config=")) { char *p = strchr(argv[argi], '='); critconfigfn = strdup(p+1); } } redirect_cgilog("confreport"); load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); load_critconfig(critconfigfn); /* Setup the filter we use for the report */ cookie = get_cookie("pagepath"); if (cookie && *cookie) pagepattern = strdup(cookie); cookie = get_cookie("host"); if (cookie && *cookie) hostpattern = strdup(cookie); /* Fetch the list of host+test statuses we currently know about */ if (pagepattern) { pcre *dummy; char *re; re = (char *)malloc(8 + 2*strlen(pagepattern)); sprintf(re, "^%s$|^%s/.+", pagepattern, pagepattern); dummy = compileregex(re); if (dummy) { freeregex(dummy); xymoncmd = (char *)malloc(2*strlen(pagepattern) + 1024); procscmd = (char *)malloc(2*strlen(pagepattern) + 1024); svcscmd = (char *)malloc(2*strlen(pagepattern) + 1024); sprintf(xymoncmd, "xymondboard page=%s fields=hostname,testname", re); sprintf(procscmd, "xymondboard page=%s test=procs fields=hostname,msg", re); sprintf(svcscmd, "xymondboard page=%s test=svcs fields=hostname,msg", re); } else patternerror = 1; xfree(re); } else if (hostpattern) { pcre *dummy; char *re; re = (char *)malloc(3 + strlen(hostpattern)); sprintf(re, "^%s$", hostpattern); dummy = compileregex(re); if (dummy) { freeregex(dummy); xymoncmd = (char *)malloc(strlen(hostpattern) + 1024); procscmd = (char *)malloc(strlen(hostpattern) + 1024); svcscmd = (char *)malloc(strlen(hostpattern) + 1024); sprintf(xymoncmd, "xymondboard host=^%s$ fields=hostname,testname", hostpattern); sprintf(procscmd, "xymondboard host=^%s$ test=procs fields=hostname,msg", hostpattern); sprintf(svcscmd, "xymondboard host=^%s$ test=svcs fields=hostname,msg", hostpattern); } else patternerror = 1; xfree(re); } else { xymoncmd = (char *)malloc(1024); procscmd = (char *)malloc(1024); svcscmd = (char *)malloc(1024); sprintf(xymoncmd, "xymondboard fields=hostname,testname"); sprintf(procscmd, "xymondboard test=procs fields=hostname,msg"); sprintf(svcscmd, "xymondboard test=svcs fields=hostname,msg"); } if (patternerror) { errormsg("Invalid host/page filter\n"); return 1; } sres = newsendreturnbuf(1, NULL); if (sendmessage(xymoncmd, NULL, XYMON_TIMEOUT, sres) != XYMONSEND_OK) { errormsg("Cannot contact the Xymon server\n"); return 1; } respbuf = getsendreturnstr(sres, 1); if (sendmessage(procscmd, NULL, XYMON_TIMEOUT, sres) != XYMONSEND_OK) { errormsg("Cannot contact the Xymon server\n"); return 1; } procsbuf = getsendreturnstr(sres, 1); if (sendmessage(svcscmd, NULL, XYMON_TIMEOUT, sres) != XYMONSEND_OK) { errormsg("Cannot contact the Xymon server\n"); return 1; } svcsbuf = getsendreturnstr(sres, 1); freesendreturnbuf(sres); if (!respbuf) { errormsg("Unable to find host information\n"); return 1; } /* Parse it into a usable list */ nexthost = respbuf; do { char *hname, *tname, *eoln; int wanted = 1; eoln = strchr(nexthost, '\n'); if (eoln) *eoln = '\0'; hname = nexthost; tname = strchr(nexthost, '|'); if (tname) { *tname = '\0'; tname++; } if (criticalonly) { void *hinfo = hostinfo(hname); char *alerts = xmh_item(hinfo, XMH_NK); if (newcritconfig) { if (strcmp(criticalval(hname, tname, alerts), "No") == 0 ) wanted = 0; } else { if (!alerts) wanted = 0; } } if (wanted && hname && tname && strcmp(hname, "summary") && strcmp(tname, xgetenv("INFOCOLUMN")) && strcmp(tname, xgetenv("TRENDSCOLUMN"))) { htnames_t *newitem = (htnames_t *)malloc(sizeof(htnames_t)); for (hwalk = hosthead; (hwalk && strcmp(hwalk->hostname, hname)); hwalk = hwalk->next); if (!hwalk) { hwalk = (hostlist_t *)calloc(1, sizeof(hostlist_t)); hwalk->hostname = strdup(hname); hwalk->procs = get_proclist(hname, procsbuf); hwalk->svcs = get_proclist(hname, svcsbuf); hwalk->next = hosthead; hosthead = hwalk; hostcount++; } newitem->name = strdup(tname); newitem->next = hwalk->tests; hwalk->tests = newitem; hwalk->testcount++; } if (eoln) { nexthost = eoln+1; if (*nexthost == '\0') nexthost = NULL; } } while (nexthost); allhosts = (hostlist_t **) malloc(hostcount * sizeof(hostlist_t *)); for (hwalk = hosthead, hosti=0; (hwalk); hwalk = hwalk->next, hosti++) { allhosts[hosti] = hwalk; if (hwalk->testcount > maxtests) maxtests = hwalk->testcount; } alltests = (htnames_t **) malloc(maxtests * sizeof(htnames_t *)); qsort(&allhosts[0], hostcount, sizeof(hostlist_t **), host_compare); /* Get the static info */ load_all_links(); init_tcp_services(); pingcolumn = xgetenv("PINGCOLUMN"); pingplus = (char *)malloc(strlen(pingcolumn) + 2); sprintf(pingplus, "%s=", pingcolumn); /* Load alert config */ alertcolors = colorset(xgetenv("ALERTCOLORS"), ((1 << COL_GREEN) | (1 << COL_BLUE))); alertinterval = 60*atoi(xgetenv("ALERTREPEAT")); sprintf(configfn, "%s/etc/alerts.cfg", xgetenv("XYMONHOME")); load_alertconfig(configfn, alertcolors, alertinterval); load_columndocs(); printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); sethostenv("", "", "", colorname(COL_BLUE), NULL); headfoot(stdout, "confreport", "", "header", COL_BLUE); fprintf(stdout, "\n"); fprintf(stdout, "\n"); fprintf(stdout, "\n", ctime(&now)); fprintf(stdout, "\n"); if (criticalonly) { fprintf(stdout, "\n"); } fprintf(stdout, "
Xymon configuration Report
Date%s
%d hosts included\n", hostcount); for (hosti=0; (hosti < hostcount); hosti++) { fprintf(stdout, "%s ", allhosts[hosti]->hostname); } fprintf(stdout, "
FilterOnly data for the "Critical Systems" view reported
\n"); headfoot(stdout, "confreport", "", "front", COL_BLUE); for (hosti=0; (hosti < hostcount); hosti++) { for (twalk = allhosts[hosti]->tests, testi = 0; (twalk); twalk = twalk->next, testi++) { alltests[testi] = twalk; } qsort(&alltests[0], allhosts[hosti]->testcount, sizeof(htnames_t **), test_compare); print_host(allhosts[hosti], alltests, allhosts[hosti]->testcount); } headfoot(stdout, "confreport", "", "back", COL_BLUE); print_columndocs(); headfoot(stdout, "confreport", "", "footer", COL_BLUE); return 0; } xymon-4.3.7/web/hostgraphs.c0000664000175000017500000001736411615341300015333 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon webpage generator tool. */ /* */ /* This tool creates an overview page of several graphs. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: hostgraphs.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include "libxymon.h" enum { A_SELECT, A_GENERATE } action = A_SELECT; char *hostpattern = NULL; char *pagepattern = NULL; char *ippattern = NULL; char **hosts = NULL; char **tests = NULL; time_t starttime = 0; time_t endtime = 0; void parse_query(void) { cgidata_t *cgidata, *cwalk; int sday = 0, smon = 0, syear = 0, eday = 0, emon = 0, eyear = 0; int smin = 0, shour = 0, ssec = 0, emin = -1, ehour = -1, esec = -1; int hostcount = 0, testcount = 0, alltests = 0; cgidata = cgi_request(); if (cgidata == NULL) return; cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwakl->value points to the value (may be an empty string). */ if ((strcmp(cwalk->name, "hostpattern") == 0) && cwalk->value && strlen(cwalk->value)) { hostpattern = strdup(cwalk->value); } else if ((strcmp(cwalk->name, "pagepattern") == 0) && cwalk->value && strlen(cwalk->value)) { pagepattern = strdup(cwalk->value); } else if ((strcmp(cwalk->name, "ippattern") == 0) && cwalk->value && strlen(cwalk->value)) { ippattern = strdup(cwalk->value); } else if (strcmp(cwalk->name, "DoReport") == 0) { action = A_GENERATE; } else if ((strcmp(cwalk->name, "hostname") == 0) && cwalk->value && strlen(cwalk->value)) { if (!hosts) hosts = (char **) malloc(sizeof(char *)); hosts = (char **)realloc(hosts, (hostcount+2) * sizeof(char *)); hosts[hostcount] = strdup(cwalk->value); hostcount++; hosts[hostcount] = NULL; } else if ((strcmp(cwalk->name, "testname") == 0) && cwalk->value && strlen(cwalk->value)) { if (!tests) tests = (char **) malloc(sizeof(char *)); if (strcmp(cwalk->value, "ALL") == 0) { alltests = 1; } else { tests = (char **)realloc(tests, (testcount+2) * sizeof(char *)); tests[testcount] = strdup(cwalk->value); testcount++; } tests[testcount] = NULL; } else if ((strcmp(cwalk->name, "start-day") == 0) && cwalk->value && strlen(cwalk->value)) { sday = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "start-mon") == 0) && cwalk->value && strlen(cwalk->value)) { smon = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "start-yr") == 0) && cwalk->value && strlen(cwalk->value)) { syear = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "start-hour") == 0) && cwalk->value && strlen(cwalk->value)) { shour = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "start-min") == 0) && cwalk->value && strlen(cwalk->value)) { smin = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "start-sec") == 0) && cwalk->value && strlen(cwalk->value)) { ssec = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "end-day") == 0) && cwalk->value && strlen(cwalk->value)) { eday = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "end-mon") == 0) && cwalk->value && strlen(cwalk->value)) { emon = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "end-yr") == 0) && cwalk->value && strlen(cwalk->value)) { eyear = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "end-hour") == 0) && cwalk->value && strlen(cwalk->value)) { ehour = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "end-min") == 0) && cwalk->value && strlen(cwalk->value)) { emin = atoi(cwalk->value); } else if ((strcmp(cwalk->name, "end-sec") == 0) && cwalk->value && strlen(cwalk->value)) { esec = atoi(cwalk->value); } cwalk = cwalk->next; } if (action == A_GENERATE) { struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_mday = sday; tm.tm_mon = smon - 1; tm.tm_year = syear - 1900; tm.tm_hour = shour; tm.tm_min = smin; tm.tm_sec = ssec; tm.tm_isdst = -1; starttime = mktime(&tm); if (ehour == -1) ehour = 23; if (emin == -1) emin = 59; if (esec == -1) esec = 59; memset(&tm, 0, sizeof(tm)); tm.tm_mday = eday; tm.tm_mon = emon - 1; tm.tm_year = eyear - 1900; tm.tm_hour = ehour; tm.tm_min = emin; tm.tm_sec = esec; tm.tm_isdst = -1; endtime = mktime(&tm); } if (alltests) { if (tests) xfree(tests); testcount = 0; tests = (char **) malloc(5 * sizeof(char *)); if (hostcount == 1) { tests[testcount] = strdup("cpu"); testcount++; tests[testcount] = strdup("disk"); testcount++; tests[testcount] = strdup("memory"); testcount++; tests[testcount] = strdup("conn"); testcount++; } else { tests[testcount] = strdup("cpu"); testcount++; tests[testcount] = strdup("mem"); testcount++; tests[testcount] = strdup("swap"); testcount++; tests[testcount] = strdup("conn-multi"); testcount++; } tests[testcount] = NULL; } if (hostcount > 1) { int i; for (i = 0; (i < testcount); i++) { if (strcmp(tests[i], "conn") == 0) tests[i] = strdup("conn-multi"); } } } int main(int argc, char *argv[]) { int argi; char *envarea = NULL; char *hffile = "hostgraphs"; char *formfile = "hostgraphs_form"; for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--hffile=")) { char *p = strchr(argv[argi], '='); hffile = strdup(p+1); formfile = (char *)malloc(strlen(hffile) + 6); sprintf(formfile, "%s_form", hffile); } } parse_query(); fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); if (action == A_SELECT) { char *cookie; cookie = get_cookie("pagepath"); if (!pagepattern && cookie && *cookie) { /* Match the exact pagename and sub-pages */ pagepattern = (char *)malloc(10 + 2*strlen(cookie)); sprintf(pagepattern, "^%s$|^%s/.+", cookie, cookie); } if (hostpattern || pagepattern || ippattern) sethostenv_filter(hostpattern, pagepattern, ippattern); showform(stdout, hffile, formfile, COL_BLUE, getcurrenttime(NULL), NULL, NULL); } else if ((action == A_GENERATE) && hosts && hosts[0] && tests && tests[0]) { int hosti, testi; headfoot(stdout, hffile, "", "header", COL_GREEN); fprintf(stdout, "\n"); for (testi=0; (tests[testi]); testi++) { fprintf(stdout, "\n", htmlquoted(tests[testi]), (long int)starttime, (long int)endtime); } fprintf(stdout, "


\n"); headfoot(stdout, hffile, "", "footer", COL_GREEN); } return 0; } xymon-4.3.7/web/findhost.sh.DIST0000664000175000017500000000025311535462534015724 0ustar henrikhenrik#!/bin/sh # This is the Xymon CGI script interface to findhost.CGI . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/findhost.cgi $CGI_FINDHOST_OPTS xymon-4.3.7/web/nongreen.sh.DIST0000775000175000017500000000052011535462534015721 0ustar henrikhenrik#!/bin/sh # This uses the statusreport.cgi to provide a list # of all non-green statuses with a brief summary of what is wrong. . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/statusreport.cgi \ --filter="color=red,yellow" --all \ --heading="All non-green systems" \ --show-column \ --show-summary \ --link xymon-4.3.7/web/hostgraphs.sh.DIST0000775000175000017500000000025011535462534016270 0ustar henrikhenrik#!/bin/sh # This is the Xymon wrapper for the hostgraphs CGI . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/hostgraphs.cgi $CGI_HOSTGRAPHS_OPTS xymon-4.3.7/web/svcstatus.sh.DIST0000664000175000017500000000024411535462534016145 0ustar henrikhenrik#!/bin/sh # This is a wrapper for the Xymon svcstatus.cgi script . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/svcstatus.cgi $CGI_SVC_OPTS xymon-4.3.7/web/criticaleditor.sh.DIST0000664000175000017500000000026311535462534017110 0ustar henrikhenrik#!/bin/sh # This is a wrapper for the Xymon criticaleditor.cgi script . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/criticaleditor.cgi $CGI_CRITEDIT_OPTS xymon-4.3.7/web/findhost.c0000664000175000017500000002137711615341300014766 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon host finder. */ /* */ /* This is a CGI script to find hosts in the Xymon webpages without knowing */ /* their full name. When you have 1200+ hosts split on 60+ pages, it can be */ /* tiresome to do a manual search to find a host ... */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /* 2004/09/08 - Werner Michels [wm] */ /* Added support regular expression on the host search. */ /* Minor changes on errormsg() and error messagess. */ /* The parse_query was rewriten to meet the new needs. */ /* */ /*----------------------------------------------------------------------------*/ /* * [wm] - Functionality change * Now the search is done using only Extended POSIX pattern match. * If you don't know how Regex works, look at "man 7 regex". * If you want search for multiple hosts use "name1|name2|name3" insted * of separating them by spaces. You can now search for host (displayname) * with spaces. * Emtpy search string will list all the hosts. * * * * [wm] - TODO * - Move the new global vars to local vars and use function parameters * - Verify the security implication of removing the urlvalidate() call * - Move to POST instead of GET to receive the FORM data * - Add the posibility to choose where to search (hostname, description * host comment, host displayname, host clientname...) * */ static char rcsid[] = "$Id: findhost.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include /*[wm] For the POSIX regex support*/ #include #include "libxymon.h" /* Global vars */ /* * [wm] To support regex searching */ char *pSearchPat = NULL; /* What're searching for (now its regex, not a hostlist) */ int re_flag = REG_EXTENDED|REG_NOSUB|REG_ICASE; /* default regcomp flags see man 3 regcomp */ /* You must remove REG_ICASE for case sensitive */ cgidata_t *cgidata = NULL; int dojump = 0; /* If set and there is only one page, go directly to it */ void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Xymon FindHost Error\n"); printf("


%s\n", msg); exit(1); } void parse_query(void) { cgidata_t *cwalk; cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ if (strcasecmp(cwalk->name, "host") == 0) { pSearchPat = (char *)strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "case_sensitive") == 0 ) { /* remove the ignore case flag */ re_flag ^= REG_ICASE; } else if (strcasecmp(cwalk->name, "jump") == 0 ) { dojump = 1; } cwalk = cwalk->next; } } void print_header(void) { /* It's ok with these hardcoded values, as they are not used for this page */ sethostenv("", "", "", colorname(COL_BLUE), NULL); printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); headfoot(stdout, "findhost", "", "header", COL_BLUE); printf("

\n"); printf("\n"); } void print_footer(void) { printf("
Hostname (DisplayName)Location (Group Name)
\n"); headfoot(stdout, "findhost", "", "footer", COL_BLUE); } int main(int argc, char *argv[]) { void *hostwalk, *clonewalk; int argi; char *envarea = NULL; strbuffer_t *outbuf; char *oneurl = NULL; int gotany = 0; enum { OP_INITIAL, OP_YES, OP_NO } gotonepage = OP_INITIAL; /* Tracks if all matches are on one page */ char *onepage = NULL; /* If gotonepage==OP_YES, then this is the page */ /*[wm] regex support */ #define BUFSIZE 256 regex_t re; char re_errstr[BUFSIZE]; int re_status; for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } } redirect_cgilog("findhost"); cgidata = cgi_request(); if (cgidata == NULL) { /* Present the query form */ sethostenv("", "", "", colorname(COL_BLUE), NULL); printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); showform(stdout, "findhost", "findhost_form", COL_BLUE, getcurrenttime(NULL), NULL, NULL); return 0; } parse_query(); if ( (re_status = regcomp(&re, pSearchPat, re_flag)) != 0 ) { regerror(re_status, &re, re_errstr, BUFSIZE); print_header(); printf("%s\n", htmlquoted(pSearchPat)); printf("%s\n", re_errstr); print_footer(); return 0; } outbuf = newstrbuffer(0); load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); hostwalk = first_host(); while (hostwalk) { /* * [wm] - Allow the search to be done on the hostname * also on the "displayname" and the host comment * Maybe this should be implemented by changing the HTML form, but until than.. * we're supposing that hostname will NEVER be null */ char *hostname, *displayname, *comment, *ip; hostname = xmh_item(hostwalk, XMH_HOSTNAME); displayname = xmh_item(hostwalk, XMH_DISPLAYNAME); comment = xmh_item(hostwalk, XMH_COMMENT); ip = xmh_item(hostwalk, XMH_IP); if ( regexec (&re, hostname, (size_t)0, NULL, 0) == 0 || (regexec(&re, ip, (size_t)0, NULL, 0) == 0) || (displayname && regexec (&re, displayname, (size_t)0, NULL, 0) == 0) || (comment && regexec (&re, comment, (size_t)0, NULL, 0) == 0) ) { /* match */ addtobuffer(outbuf, "\n"); addtobuffer(outbuf, " "); addtobuffer(outbuf, displayname ? displayname : hostname); addtobuffer(outbuf, " \n"); oneurl = (char *)malloc(4 + strlen(xgetenv("XYMONWEB")) + strlen(xmh_item(hostwalk, XMH_PAGEPATH)) + strlen(hostname)); sprintf(oneurl, "%s/%s/#%s", xgetenv("XYMONWEB"), xmh_item(hostwalk, XMH_PAGEPATH), hostname); addtobuffer(outbuf, " "); addtobuffer(outbuf, xmh_item(hostwalk, XMH_PAGEPATHTITLE)); addtobuffer(outbuf, "\n"); gotany++; /* See if all of the matches so far are on one page */ switch (gotonepage) { case OP_INITIAL: gotonepage = OP_YES; onepage = xmh_item(hostwalk, XMH_PAGEPATH); break; case OP_YES: if (strcmp(onepage, xmh_item(hostwalk, XMH_PAGEPATH)) != 0) gotonepage = OP_NO; break; case OP_NO: break; } clonewalk = next_host(hostwalk, 1); while (clonewalk && (strcmp(xmh_item(hostwalk, XMH_HOSTNAME), xmh_item(clonewalk, XMH_HOSTNAME)) == 0)) { addtobuffer(outbuf, "
"); addtobuffer(outbuf, xmh_item(clonewalk, XMH_PAGEPATHTITLE)); addtobuffer(outbuf, "\n"); clonewalk = next_host(clonewalk, 1); gotany++; } addtobuffer(outbuf, "\n\n"); hostwalk = clonewalk; } else { hostwalk = next_host(hostwalk, 0); } } regfree (&re); /*[wm] - free regex compiled patern */ if (dojump) { if (gotany == 1) { printf("Location: %s%s\n\n", xgetenv("XYMONWEBHOST"), oneurl); return 0; } else if ((gotany > 1) && (gotonepage == OP_YES)) { printf("Location: %s%s/%s/\n\n", xgetenv("XYMONWEBHOST"), xgetenv("XYMONWEB"), onepage); return 0; } } print_header(); if (!gotany) { printf("%sNot found\n", htmlquoted(pSearchPat)); } else { printf("%s", grabstrbuffer(outbuf)); } print_footer(); /* [wm] - Free the strdup allocated memory */ if (pSearchPat) xfree(pSearchPat); return 0; } xymon-4.3.7/web/confreport-critical.sh.DIST0000664000175000017500000000046311535462534020062 0ustar henrikhenrik#!/bin/sh # This is the Xymon CGI script interface to confreport.cgi # It shows only the statuses on the Critical systems view # # Install this script in your webservers' cgi-bin directory . @XYMONHOME@/etc/cgioptions.cfg @RUNTIMEDEFS@ exec @XYMONHOME@/bin/confreport.cgi $CGI_CONFREPORT_OPTS --critical xymon-4.3.7/web/useradm.c0000664000175000017500000001230211635415000014575 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon webpage generator tool. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: useradm.c 6588 2010-11-14 17:21:19Z storner $"; #include #include #include #include #include "libxymon.h" static void errormsg(char *msg) { printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); printf("Invalid request\n"); printf("%s\n", msg); exit(1); } static int idcompare(const void *p1, const void *p2) { return strcmp(* (char * const *) p1, * (char * const *) p2); } #define ACT_NONE 0 #define ACT_CREATE 1 #define ACT_DELETE 2 char *adduser_name = NULL; char *adduser_password = NULL; char *deluser_name = NULL; int parse_query(void) { cgidata_t *cgidata, *cwalk; int returnval = ACT_NONE; cgidata = cgi_request(); if (cgi_method != CGI_POST) return ACT_NONE; if (cgidata == NULL) errormsg(cgi_error()); cwalk = cgidata; while (cwalk) { /* * cwalk->name points to the name of the setting. * cwalk->value points to the value (may be an empty string). */ if (strcmp(cwalk->name, "USERNAME") == 0) { adduser_name = cwalk->value; } else if (strcmp(cwalk->name, "PASSWORD") == 0) { adduser_password = cwalk->value; } else if (strcmp(cwalk->name, "USERLIST") == 0) { deluser_name = cwalk->value; } else if (strcmp(cwalk->name, "SendCreate") == 0) { returnval = ACT_CREATE; } else if (strcmp(cwalk->name, "SendDelete") == 0) { returnval = ACT_DELETE; } cwalk = cwalk->next; } return returnval; } int main(int argc, char *argv[]) { int argi; char *envarea = NULL; char *hffile = "useradm"; int bgcolor = COL_BLUE; char *passfile = NULL; FILE *fd; char *infomsg = NULL; for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--passwdfile=")) { char *p = strchr(argv[argi], '='); passfile = strdup(p+1); } } if (passfile == NULL) { passfile = (char *)malloc(strlen(xgetenv("XYMONHOME")) + 20); sprintf(passfile, "%s/etc/xymonpasswd", xgetenv("XYMONHOME")); } switch (parse_query()) { case ACT_NONE: /* Show the form */ break; case ACT_CREATE: /* Add a user */ { char *cmd; int n, ret; cmd = (char *)malloc(1024 + strlen(passfile) + strlen(adduser_name) + strlen(adduser_password)); sprintf(cmd, "htpasswd -b '%s' '%s' '%s'", passfile, adduser_name, adduser_password); n = system(cmd); n = system(cmd); ret = WEXITSTATUS(n); if ((n == -1) || (ret != 0)) { infomsg = "\n"; } else { infomsg = "\n"; } xfree(cmd); } break; case ACT_DELETE: /* Delete a user */ { char *cmd; int n, ret; cmd = (char *)malloc(1024 + strlen(passfile) + strlen(deluser_name)); snprintf(cmd, sizeof(cmd), "htpasswd -D '%s' '%s'", passfile, deluser_name); n = system(cmd); ret = WEXITSTATUS(n); if ((n == -1) || (ret != 0)) { infomsg = "\n"; } else { infomsg = "\n"; } xfree(cmd); } break; } sethostenv_clearlist(NULL); sethostenv_addtolist(NULL, "", "", NULL, 1); /* Have a blank entry first so we won't delete one by accident */ fd = fopen(passfile, "r"); if (fd != NULL) { char l[1024]; char *id, *delim; int usercount; char **userlist; int i; usercount = 0; userlist = (char **)calloc(usercount+1, sizeof(char *)); while (fgets(l, sizeof(l), fd)) { id = l; delim = strchr(l, ':'); if (delim) { *delim = '\0'; usercount++; userlist = (char **)realloc(userlist, (usercount+1)*sizeof(char *)); userlist[usercount-1] = strdup(id); userlist[usercount] = NULL; } } fclose(fd); qsort(&userlist[0], usercount, sizeof(char *), idcompare); for (i=0; (userlist[i]); i++) sethostenv_addtolist(NULL, userlist[i], userlist[i], NULL, 0); } fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE")); showform(stdout, hffile, "useradm_form", COL_BLUE, getcurrenttime(NULL), infomsg, NULL); return 0; } xymon-4.3.7/web/history.cgi.10000664000175000017500000000647611671641417015351 0ustar henrikhenrik.TH HISTORY.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME history.cgi \- CGI program to display service history .SH SYNOPSIS .B "history.cgi" .SH DESCRIPTION \fBhistory.cgi\fR is invoked as a CGI script via the history.sh CGI wrapper. It is passed a QUERY_STRING environment variable with the following parameters: HISTFILE (a Xymon service history file) ENTRIES (the number of entries to show) The following non-standard parameters are handled by the Xymon version of history.cgi: IP (IP address of host - for display purposes only) PIXELS (width of colorbar when in pixel-mode) ENDTIME (when the colorbar begins, a time_t value) BARSUMS (which colorbars and summaries to show) history.cgi analyses the service history file for changes that have occurred within the past 24 hours, and build a colorbar showing the status of the service over this period of time. A statistics summary is also produced, listing the amount of time for each status (green, yellow, red, purple, blue, clear). Finally, a summary of the last N events is given, with links to the actual event logs. Unlike the standard history.sh script, history.cgi provides a colorbar and statistics summaries also covering the past 1 week, 4 weeks and 1 year of data. Via links it is possible to browse the entire history of the service at the requested interval. Note that since the resolution of the display is limited, events may be too short to show up on a colorbar; also, the exact placement of an event may not fully match up with the time-markers. The graphs should correctly handle the display of months with different number of days, as well as the display of periods that involve beginning and end of Daylight Savings Time, if this occurs in your timezone. All dates and times shown are in local time for the timezone defined on the Xymon server. .SH PARAMETERS .IP HISTFILE Defines the host and service whose history is presented. .IP ENTRIES The number of log-entries to show in the event log table. Default is 50; to view all log entries set this to "ALL". .IP IP The IP-address of the host. This is only used for the title of the document. .IP PIXELS The width of the colorbar graph in pixels. If this is set to 0, a percentage-based graph will be shown, similar to the one provided by the standard history.sh script. Pixel-based graphs can have a higher resolution, but do not resize automatically to suit the size of a browser window. The default value for this parameter is defined at compile-time; 960 is a good value for displays with a 1024x768 resolution. .IP BARSUMS Defines which colorbars and summaries to show. This is a number made up from a bitmask. The 1-day graph uses the value "1"; the 1-week graph uses the value "2"; the 4-week graph uses the value "4" and the 1-year graph the value "8". To show multiple graph, add the values - e.g. "6" will show the 1-week and 4-weeks graphs, whereas "15" will show all the graphs. The default is defined at compile-time. .IP ENDTIME The history display by default ends with the current time. Setting the ENDTIME parameter causes it to end at the time specified - this is given as a Unix "time_t" value, i.e. as the number of seconds elapsed since Jan 1 1970 00:00 UTC. .SH OPTIONS .IP "--env=FILENAME" Load the environment from FILENAME before executing the CGI. .SH "SEE ALSO" hosts.cfg(5), xymonserver.cfg(5) xymon-4.3.7/web/snapshot.cgi.10000664000175000017500000000362711671641417015502 0ustar henrikhenrik.TH SNAPSHOT.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME snapshot.cgi \- CGI program to rebuild the Xymon webpages for a specific point in time. .SH SYNOPSIS .B "snapshot.cgi" .SH DESCRIPTION \fBsnapshot.cgi\fR is invoked as a CGI script via the snapshot.sh CGI wrapper. It rebuilds the Xymon web pages to the look they had at a particular point in time, based upon the historical information logged about events. snapshot.cgi is passed a QUERY_STRING environment variable with the following parameters: mon (Start month of the snapshot) day (Start day-of-month of the snapshot) yr (Start year of the snapshot) hour (Start hour of the snapshot) min (Start minute of the snapshot) sec (Start second of the snapshot) The "month" parameters must be specified as the three-letter english month name abbreviation: Jan, Feb, Mar ... "day" must be in the range 1..31; "yr" must be specified including century (e.g. "2003"). "hour" must be specified using a 24-hour clock. All of the processing involved in generating the report is done by invoking .I xymongen(1) with the proper "--snapshot" option. .SH OPTIONS .IP "--env=FILENAME" Load environment from FILENAME before executing the CGI. .IP "xymongen-options" All options except "--env" are passed on to the .I xymongen(1) program building the snapshot files. .SH ENVIRONMENT VARIABLES .IP XYMONGENSNAPOPTS xymongen options passed by default to the snapshot.cgi script. This happens in the snapshot.sh CGI wrapper script. .IP XYMONHOME Home directory of the Xymon server files .IP XYMONSNAPDIR Directory where generated snapshots are stored. This directory must be writable by the userid executing the CGI script, typically "www", "apache" or "nobody". Default: $XYMONHOME/www/snap/ .IP XYMONSNAPURL The URL prefix to use when accessing the reports via a browser. Default: $XYMONWEB/snap .SH "SEE ALSO" xymongen(1), hosts.cfg(5), xymonserver.cfg(5) xymon-4.3.7/web/xymonwebaccess.50000664000175000017500000000422011671641417016126 0ustar henrikhenrik.TH XYMON-WEBACCESS 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymon-webaccess \- Web-based access controls in Xymon .SH DESCRIPTION Xymon does not provide any built-in authentication (login) mechanism. Instead, it relies on the access controls available in your web server, e.g. the Apache \fBmod_auth\fR modules. This provides a simple way of controlling access to the physical directories that make up the pages and subpages with the hosts defined in your Xymon .I hosts.cfg(5) setup - you can use the Apache "require" setting to allow or deny access to information on any page, usually through the use of a "Require group ..." setting. The group name then refers to one or more groups in an Apache \fBAuthGroupFile\fR file. However, this does not work for the Xymon CGI programs since they are used to fetch information about all hosts in Xymon, but there is only a single directory holding all of the CGI's. So here you can only require that the user is logged-in (the Apache "Require valid-user" directive). A user with a login can - if he knows the hostname - manipulate the request sent to the webserver and fetch information about any status by use of the Xymon CGI programs, even though he cannot see the overview webpages. To alleviate this situation, the following Xymon CGI's support a "--access=FILENAME" option, where FILENAME is an Apache compatible group-definitions file: .br .I svcstatus.cgi(1) .br .I acknowledge.cgi(1) .br .I enadis.cgi(1) .br .I appfeed.cgi(1) When invoked with this option the CGI will read the Apache group-definitions file, and assume that an Apache \fBgroup\fR maps to a Xymon \fBpage\fR, and then - based on the logged-in userid - determine which pages and hosts the user is allowed access to. Only information about those hosts will be made available by the CGI tool. Members of the group \fBroot\fR has access to all hosts. Access will also be granted, if the user is a member of a group with the same name as the \fBhost\fR being requested, or as the \fBstatuscolumn\fR being requested. .SH "SEE ALSO" The Apache "Authentication, Authorization and Access Control" documentation, http://httpd.apache.org/docs/2.2/howto/auth.html xymon-4.3.7/web/ackinfo.cgi.10000664000175000017500000000334311671641417015250 0ustar henrikhenrik.TH ACKINFO.CGI 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME ackinfo.cgi \- Xymon CGI script to acknowledge alerts .SH SYNOPSIS .B "ackinfo.cgi" .SH DESCRIPTION \fBackinfo.cgi\fR is invoked as a CGI script via the ackinfo.sh CGI wrapper. ackinfo.cgi is used to acknowledge an alert on the Xymon "Critical Systems" view, generated by the .I criticalview.cgi(1) utility. This allows the staff viewing the Critical Systems view to acknowledge alerts with a "Level 1" alert, thereby removing the alert from the Critical Systems view. Note that the Level 1 alert generated by the ackinfo.cgi utility does \fBNOT\fR stop alerts from being sent. In a future version of Xymon (after Xymon 4.2), this utility will also be used for acknowledging alerts at other levels. .SH OPTIONS .IP "--level=NUMBER" Sets the acknowledgment level. This is typically used to force a specific level of the acknowledgment, e.g. a level 1 acknowledge when called from the Critical Systems view. .IP "--validity=TIME" Sets the validity of the acknowledgment. By default this is taken from the CGI parameters supplied by the user. .IP "--sender=STRING" Logs STRING as the sender of the acknowledgment. By default, this is taken from the loginname of the webuser sending the acknowledgment. .IP "--env=FILENAME" Loads the environment defined in FILENAME before executing the CGI script. .IP "--area=NAME" Load environment variables for a specific area. NB: if used, this option must appear before any --env=FILENAME option. .IP "--debug" Enables debugging output. .SH "ENVIRONMENT VARIABLES" .IP XYMONHOME Used to locate the template files for the generated web pages. .IP QUERY_STRING Contains the parameters for the CGI script. .SH "SEE ALSO" criticalview.cgi(1), xymon(7) xymon-4.3.7/web/svcstatus.c0000664000175000017500000005256111664526142015223 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon status-log viewer CGI. */ /* */ /* This CGI tool shows an HTML version of a status log. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: svcstatus.c 6776 2011-11-27 21:32:18Z storner $"; #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "version.h" #include "svcstatus-info.h" #include "svcstatus-trends.h" /* Command-line params */ static enum { SRC_XYMOND, SRC_HISTLOGS, SRC_CLIENTLOGS } source = SRC_XYMOND; static int wantserviceid = 1; static char *multigraphs = ",disk,inode,qtree,quotas,snapshot,TblSpace,if_load,"; static int locatorbased = 0; static char *critconfigfn = NULL; static char *accessfn = NULL; /* CGI params */ static char *hostname = NULL; static char *service = NULL; static char *tstamp = NULL; static char *nkprio = NULL, *nkttgroup = NULL, *nkttextra = NULL; static enum { FRM_STATUS, FRM_CLIENT } outform = FRM_STATUS; static char *clienturi = NULL; static int backsecs = 0; static time_t fromtime = 0, endtime = 0; static char errortxt[1000]; static char *hostdatadir = NULL; static void errormsg(char *msg) { snprintf(errortxt, sizeof(errortxt), "Content-type: %s\n\nInvalid request\n%s\n", xgetenv("HTMLCONTENTTYPE"), msg); errortxt[sizeof(errortxt)-1] = '\0'; } static int parse_query(void) { cgidata_t *cgidata = cgi_request(); cgidata_t *cwalk; cwalk = cgidata; while (cwalk) { if (strcasecmp(cwalk->name, "HOST") == 0) { hostname = strdup(basename(cwalk->value)); } else if (strcasecmp(cwalk->name, "SERVICE") == 0) { service = strdup(basename(cwalk->value)); } else if (strcasecmp(cwalk->name, "HOSTSVC") == 0) { /* For backwards compatibility */ char *p = strrchr(cwalk->value, '.'); if (p) { *p = '\0'; hostname = strdup(basename(cwalk->value)); service = strdup(p+1); for (p=strchr(hostname, ','); (p); p = strchr(p, ',')) *p = '.'; } } else if (strcasecmp(cwalk->name, "TIMEBUF") == 0) { /* Only for the historical logs */ tstamp = strdup(basename(cwalk->value)); } else if (strcasecmp(cwalk->name, "CLIENT") == 0) { char *p; hostname = strdup(cwalk->value); p = hostname; while ((p = strchr(p, ',')) != NULL) *p = '.'; service = strdup(""); outform = FRM_CLIENT; } else if (strcasecmp(cwalk->name, "SECTION") == 0) { service = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "NKPRIO") == 0) { nkprio = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "NKTTGROUP") == 0) { nkttgroup = strdup(cwalk->value); } else if (strcasecmp(cwalk->name, "NKTTEXTRA") == 0) { nkttextra = strdup(cwalk->value); } else if ((strcmp(cwalk->name, "backsecs") == 0) && cwalk->value && strlen(cwalk->value)) { backsecs += atoi(cwalk->value); } else if ((strcmp(cwalk->name, "backmins") == 0) && cwalk->value && strlen(cwalk->value)) { backsecs += 60*atoi(cwalk->value); } else if ((strcmp(cwalk->name, "backhours") == 0) && cwalk->value && strlen(cwalk->value)) { backsecs += 60*60*atoi(cwalk->value); } else if ((strcmp(cwalk->name, "backdays") == 0) && cwalk->value && strlen(cwalk->value)) { backsecs += 24*60*60*atoi(cwalk->value); } else if ((strcmp(cwalk->name, "FROMTIME") == 0) && cwalk->value && strlen(cwalk->value)) { fromtime = eventreport_time(cwalk->value); } else if ((strcmp(cwalk->name, "TOTIME") == 0) && cwalk->value && strlen(cwalk->value)) { endtime = eventreport_time(cwalk->value); } cwalk = cwalk->next; } if (backsecs == 0) { if (getenv("TRENDSECONDS")) backsecs = atoi(getenv("TRENDSECONDS")); else backsecs = 48*60*60; } if (!hostname || !service || ((source == SRC_HISTLOGS) && !tstamp) ) { errormsg("Invalid request"); return 1; } if (outform == FRM_STATUS) { char *p, *req; req = getenv("SCRIPT_NAME"); clienturi = (char *)malloc(strlen(req) + 10 + strlen(htmlquoted(hostname))); strcpy(clienturi, req); p = strchr(clienturi, '?'); if (p) *p = '\0'; else p = clienturi + strlen(clienturi); sprintf(p, "?CLIENT=%s", htmlquoted(hostname)); } return 0; } int loadhostdata(char *hostname, char **ip, char **displayname, char **compacts, int full) { void *hinfo = NULL; int loadres; if (full) { loadres = load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); } else { loadres = load_hostinfo(hostname); } if ((loadres != 0) && (loadres != -2)) { errormsg("Cannot load host configuration"); return 1; } if ((loadres == -2) || (hinfo = hostinfo(hostname)) == NULL) { errormsg("No such host"); return 1; } *ip = xmh_item(hinfo, XMH_IP); *displayname = xmh_item(hinfo, XMH_DISPLAYNAME); if (!(*displayname)) *displayname = hostname; *compacts = xmh_item(hinfo, XMH_COMPACT); return 0; } int do_request(void) { int color = 0, flapping = 0; char timesincechange[100]; time_t logtime = 0, acktime = 0, disabletime = 0; char *log = NULL, *firstline = NULL, *sender = NULL, *clientid = NULL, *flags = NULL; /* These are free'd */ char *restofmsg = NULL, *ackmsg = NULL, *dismsg = NULL, *acklist=NULL, *modifiers = NULL; /* These are just used */ int ishtmlformatted = 0; int clientavail = 0; char *ip, *displayname, *compacts; if (parse_query() != 0) return 1; /* Load the host data (for access control) */ if (accessfn) { load_hostinfo(hostname); load_web_access_config(accessfn); if (!web_access_allowed(getenv("REMOTE_USER"), hostname, service, WEB_ACCESS_VIEW)) { errormsg("Not available (restricted)."); return 1; } } { char *s; s = xgetenv("CLIENTLOGS"); if (s) { hostdatadir = (char *)malloc(strlen(s) + strlen(hostname) + 12); sprintf(hostdatadir, "%s/%s", s, hostname); } else { s = xgetenv("XYMONVAR"); hostdatadir = (char *)malloc(strlen(s) + strlen(hostname) + 12); sprintf(hostdatadir, "%s/hostdata/%s", s, hostname); } } if (outform == FRM_CLIENT) { if (source == SRC_XYMOND) { char *xymondreq; int xymondresult; sendreturn_t *sres = newsendreturnbuf(1, NULL); xymondreq = (char *)malloc(1024 + strlen(hostname) + (service ? strlen(service) : 0)); sprintf(xymondreq, "clientlog %s", hostname); if (service && *service) sprintf(xymondreq + strlen(xymondreq), " section=%s", service); xymondresult = sendmessage(xymondreq, NULL, XYMON_TIMEOUT, sres); if (xymondresult != XYMONSEND_OK) { char *errtxt = (char *)malloc(1024 + strlen(xymondreq)); sprintf(errtxt, "Status not available: Req=%s, result=%d\n", htmlquoted(xymondreq), xymondresult); errormsg(errtxt); return 1; } else { log = getsendreturnstr(sres, 1); } freesendreturnbuf(sres); } else if (source == SRC_HISTLOGS) { char logfn[PATH_MAX]; FILE *fd; sprintf(logfn, "%s/%s", hostdatadir, tstamp); fd = fopen(logfn, "r"); if (fd) { struct stat st; int n; fstat(fileno(fd), &st); if (S_ISREG(st.st_mode)) { log = (char *)malloc(st.st_size + 1); n = fread(log, 1, st.st_size, fd); if (n >= 0) *(log+n) = '\0'; else *log = '\0'; } fclose(fd); } } restofmsg = (log ? log : strdup("\n")); } else if ((strcmp(service, xgetenv("TRENDSCOLUMN")) == 0) || (strcmp(service, xgetenv("INFOCOLUMN")) == 0)) { int fullload = (strcmp(service, xgetenv("INFOCOLUMN")) == 0); if (loadhostdata(hostname, &ip, &displayname, &compacts, fullload) != 0) return 1; ishtmlformatted = 1; sethostenv(displayname, ip, service, colorname(COL_GREEN), hostname); sethostenv_refresh(600); color = COL_GREEN; logtime = getcurrenttime(NULL); strcpy(timesincechange, "0 minutes"); if (strcmp(service, xgetenv("TRENDSCOLUMN")) == 0) { if (locatorbased) { char *cgiurl, *qres; qres = locator_query(hostname, ST_RRD, &cgiurl); if (!qres) { errprintf("Cannot find RRD files for host %s\n", hostname); } else { /* Redirect browser to the real server */ fprintf(stdout, "Location: %s/svcstatus.sh?HOST=%s&SERVICE=%s\n\n", cgiurl, hostname, service); return 0; } } else { if (endtime == 0) endtime = getcurrenttime(NULL); if (fromtime == 0) { fromtime = endtime - backsecs; sethostenv_backsecs(backsecs); } else { sethostenv_eventtime(fromtime, endtime); } log = restofmsg = generate_trends(hostname, fromtime, endtime); } } else if (strcmp(service, xgetenv("INFOCOLUMN")) == 0) { log = restofmsg = generate_info(hostname, critconfigfn); } } else if (source == SRC_XYMOND) { char *xymondreq; int xymondresult; char *items[25]; int icount; time_t logage, clntstamp; char *sumline, *msg, *p, *compitem, *complist; sendreturn_t *sres; if (loadhostdata(hostname, &ip, &displayname, &compacts, 0) != 0) return 1; complist = NULL; if (compacts && *compacts) { compitem = strtok(compacts, ","); while (compitem && !complist) { p = strchr(compitem, '='); if (p) *p = '\0'; if (strcmp(service, compitem) == 0) complist = p+1; compitem = strtok(NULL, ","); } } /* We need not check that hostname is valid, has already been done with loadhostdata() */ if (!complist) { pcre *dummy = NULL; /* Check service as a pcre pattern. And no spaces in servicenames */ if (strchr(service, ' ') == NULL) dummy = compileregex(service); if (dummy == NULL) { errormsg("Invalid testname pattern"); return 1; } freeregex(dummy); xymondreq = (char *)malloc(1024 + strlen(hostname) + strlen(service)); sprintf(xymondreq, "xymondlog host=%s test=%s fields=hostname,testname,color,flags,lastchange,logtime,validtime,acktime,disabletime,sender,cookie,ackmsg,dismsg,client,acklist,XMH_IP,XMH_DISPLAYNAME,clntstamp,flapinfo,modifiers", hostname, service); } else { pcre *dummy = NULL; char *re; re = (char *)malloc(5 + strlen(complist)); sprintf(re, "^(%s)$", complist); dummy = compileregex(re); if (dummy == NULL) { errormsg("Invalid testname pattern"); return 1; } freeregex(dummy); xymondreq = (char *)malloc(1024 + strlen(hostname) + strlen(re)); sprintf(xymondreq, "xymondboard host=^%s$ test=%s fields=testname,color,lastchange", hostname, re); } sres = newsendreturnbuf(1, NULL); xymondresult = sendmessage(xymondreq, NULL, XYMON_TIMEOUT, sres); if (xymondresult == XYMONSEND_OK) log = getsendreturnstr(sres, 1); freesendreturnbuf(sres); if ((xymondresult != XYMONSEND_OK) || (log == NULL) || (strlen(log) == 0)) { errormsg("Status not available\n"); return 1; } if (!complist) { sumline = log; p = strchr(log, '\n'); *p = '\0'; msg = (p+1); p = strchr(msg, '\n'); if (!p) { firstline = strdup(msg); restofmsg = NULL; } else { *p = '\0'; firstline = strdup(msg); restofmsg = (p+1); *p = '\n'; } memset(items, 0, sizeof(items)); p = gettok(sumline, "|"); icount = 0; while (p && (icount < 20)) { items[icount++] = p; p = gettok(NULL, "|"); } /* * hostname, [0] * testname, [1] * color, [2] * flags, [3] * lastchange, [4] * logtime, [5] * validtime, [6] * acktime, [7] * disabletime, [8] * sender, [9] * cookie, [10] * ackmsg, [11] * dismsg, [12] * client, [13] * acklist [14] * XMH_IP [15] * XMH_DISPLAYNAME [16] * clienttstamp [17] * flapping [18] * modifiers [19] */ color = parse_color(items[2]); flags = strdup(items[3]); logage = getcurrenttime(NULL) - atoi(items[4]); timesincechange[0] = '\0'; p = timesincechange; if (logage > 86400) p += sprintf(p, "%d days,", (int) (logage / 86400)); p += sprintf(p, "%d hours, %d minutes", (int) ((logage % 86400) / 3600), (int) ((logage % 3600) / 60)); logtime = atoi(items[5]); if (items[7] && strlen(items[7])) acktime = atoi(items[7]); if (items[8] && strlen(items[8])) disabletime = atoi(items[8]); sender = strdup(items[9]); if (items[11] && strlen(items[11])) ackmsg = items[11]; if (ackmsg) nldecode(ackmsg); if (items[12] && strlen(items[12])) dismsg = items[12]; if (dismsg) nldecode(dismsg); if (items[13]) clientavail = (*items[13] == 'Y'); acklist = ((items[14] && *items[14]) ? strdup(items[14]) : NULL); ip = (items[15] ? items[15] : ""); displayname = ((items[16] && *items[16]) ? items[16] : hostname); clntstamp = ((items[17] && *items[17]) ? atol(items[17]) : 0); flapping = (items[18] ? (*items[18] == '1') : 0); modifiers = (items[19] && *(items[19])) ? items[19] : NULL; sethostenv(displayname, ip, service, colorname(COL_GREEN), hostname); sethostenv_refresh(60); } else { /* Compressed status display */ strbuffer_t *cmsg; char *row, *p_row, *p_fld; char *nonhistenv; color = COL_GREEN; cmsg = newstrbuffer(0); addtobuffer(cmsg, "\n"); row = strtok_r(log, "\n", &p_row); while (row) { /* testname,color,lastchange */ char *testname, *itmcolor, *chgs; time_t lastchange; int icolor; testname = strtok_r(row, "|", &p_fld); itmcolor = strtok_r(NULL, "|", &p_fld); chgs = strtok_r(NULL, "|", &p_fld); lastchange = atoi(chgs); icolor = parse_color(itmcolor); if (icolor > color) color = icolor; addtobuffer(cmsg, "\n"); row = strtok_r(NULL, "\n", &p_row); } addtobuffer(cmsg, "
&"); addtobuffer(cmsg, itmcolor); addtobuffer(cmsg, " "); addtobuffer(cmsg, htmlquoted(testname)); addtobuffer(cmsg, "
\n"); ishtmlformatted = 1; sethostenv(displayname, ip, service, colorname(color), hostname); sethostenv_refresh(60); logtime = getcurrenttime(NULL); strcpy(timesincechange, "0 minutes"); log = restofmsg = grabstrbuffer(cmsg); firstline = (char *)malloc(1024); sprintf(firstline, "%s Compressed status display\n", colorname(color)); nonhistenv = (char *)malloc(10 + strlen(service)); sprintf(nonhistenv, "NONHISTS=%s", service); putenv(nonhistenv); } } else if (source == SRC_HISTLOGS) { char logfn[PATH_MAX]; struct stat st; FILE *fd; /* * Some clients (Unix disk reports) dont have a newline before the * "Status unchanged in ..." text. Most do, but at least Solaris and * AIX do not. So just look for the text, not the newline. */ char *statusunchangedtext = "Status unchanged in "; char *receivedfromtext = "Message received from "; char *clientidtext = "Client data ID "; char *p, *unchangedstr, *receivedfromstr, *clientidstr, *hostnamedash; int n; if (!tstamp) { errormsg("Invalid request"); return 1; } if (loadhostdata(hostname, &ip, &displayname, &compacts, 0) != 0) return 1; hostnamedash = strdup(hostname); p = hostnamedash; while ((p = strchr(p, '.')) != NULL) *p = '_'; p = hostnamedash; while ((p = strchr(p, ',')) != NULL) *p = '_'; sprintf(logfn, "%s/%s/%s/%s", xgetenv("XYMONHISTLOGS"), hostnamedash, service, tstamp); xfree(hostnamedash); p = tstamp; while ((p = strchr(p, '_')) != NULL) *p = ' '; sethostenv_histlog(tstamp); if ((stat(logfn, &st) == -1) || (st.st_size < 10) || (!S_ISREG(st.st_mode))) { errormsg("Historical status log not available\n"); return 1; } fd = fopen(logfn, "r"); if (!fd) { errormsg("Unable to access historical logfile\n"); return 1; } log = (char *)malloc(st.st_size+1); n = fread(log, 1, st.st_size, fd); if (n >= 0) *(log+n) = '\0'; else *log = '\0'; fclose(fd); p = strchr(log, '\n'); if (!p) { firstline = strdup(log); restofmsg = NULL; } else { *p = '\0'; firstline = strdup(log); restofmsg = (p+1); *p = '\n'; } color = parse_color(log); p = strstr(log, "\n", linecount); fprintf(output, "%s\n", xymon_graph_data(hostname, displayname, service, color, graph, linecount, HG_WITHOUT_STALE_RRDS, HG_PLAIN_LINK, locatorbased, now-graphtime, now)); } } if (histlocation == HIST_BOTTOM) { historybutton(cgibinurl, hostname, service, ip, displayname, (is_history ? "Full History" : "HISTORY"), output); } fprintf(output,"
\n"); headfoot(output, tplfile, "", "footer", color); } char *alttag(char *columnname, int color, int acked, int propagate, char *age) { static char tag[1024]; size_t remain; remain = sizeof(tag) - 1; remain -= snprintf(tag, remain, "%s:%s:", columnname, colorname(color)); if (remain > 20) { if (acked) { strncat(tag, "acked:", remain); remain -= 6; } if (!propagate) { strncat(tag, "nopropagate:", remain); remain -= 12; } strncat(tag, age, remain); } tag[sizeof(tag)-1] = '\0'; return tag; } static char *nameandcomment(void *host, char *hostname, int usetooltip) { static char *result = NULL; char *cmt, *disp, *hname; if (result) xfree(result); hostpopup_setup(); /* For summary "hosts", we have no hinfo record. */ if (!host) return hostname; hname = xmh_item(host, XMH_HOSTNAME); disp = xmh_item(host, XMH_DISPLAYNAME); cmt = NULL; if (!cmt && (hostpopup & HOSTPOPUP_COMMENT)) cmt = xmh_item(host, XMH_COMMENT); if (!cmt && usetooltip && (hostpopup & HOSTPOPUP_DESCR)) cmt = xmh_item(host, XMH_DESCRIPTION); if (!cmt && usetooltip && (hostpopup & HOSTPOPUP_IP)) cmt = xmh_item(host, XMH_IP); if (disp == NULL) disp = hname; if (cmt) { if (usetooltip) { /* Thanks to Marco Schoemaker for suggesting the use of */ result = (char *)malloc(strlen(disp) + strlen(cmt) + 30); sprintf(result, "%s", cmt, disp); } else { result = (char *)malloc(strlen(disp) + strlen(cmt) + 4); sprintf(result, "%s (%s)", disp, cmt); } return result; } else return disp; } static char *urldoclink(const char *docurl, const char *hostname) { /* * docurl is a user defined text string to build * a documentation url. It is expanded with the * hostname. */ static char linkurl[PATH_MAX]; if (docurl) { sprintf(linkurl, docurl, hostname); } else { linkurl[0] = '\0'; } return linkurl; } void setdocurl(char *url) { if (documentationurl) xfree(documentationurl); documentationurl = strdup(url); } void setdoctarget(char *target) { if (doctarget) xfree(doctarget); doctarget = strdup(target); } char *hostnamehtml(char *hostname, char *defaultlink, int usetooltip) { static char result[4096]; void *hinfo = hostinfo(hostname); char *hostlinkurl; if (!doctarget) doctarget = strdup(""); /* First the hostname and a notes-link. * * If a documentation CGI is defined, use that. * * else if a host has a direct notes-link, use that. * * else if no direct link and we are doing a nongreen/critical page, * provide a link to the main page with this host (there * may be links to documentation in some page-title). * * else just put the hostname there. */ if (documentationurl) { snprintf(result, sizeof(result), "%s", urldoclink(documentationurl, hostname), doctarget, xgetenv("XYMONPAGEROWFONT"), nameandcomment(hinfo, hostname, usetooltip)); } else if ((hostlinkurl = hostlink(hostname)) != NULL) { snprintf(result, sizeof(result), "%s", hostlinkurl, doctarget, xgetenv("XYMONPAGEROWFONT"), nameandcomment(hinfo, hostname, usetooltip)); } else if (defaultlink) { /* Provide a link to the page where this host lives */ snprintf(result, sizeof(result), "%s", xgetenv("XYMONWEB"), defaultlink, doctarget, xgetenv("XYMONPAGEROWFONT"), nameandcomment(hinfo, hostname, usetooltip)); } else { snprintf(result, sizeof(result), "%s", xgetenv("XYMONPAGEROWFONT"), nameandcomment(hinfo, hostname, usetooltip)); } return result; } xymon-4.3.7/lib/sha1.c0000664000175000017500000002004411535462534014001 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This file is part of the Xymon monitor library, but was taken from the */ /* "mutt" source archive and adapted for use in Xymon. According to the */ /* copyright notice in the "mutt" version, this file is public domain. */ /*----------------------------------------------------------------------------*/ /* SHA-1 in C By Steve Reid , with small changes to make it fit into mutt by Thomas Roessler . 100% Public Domain. Test Vectors (from FIPS PUB 180-1) "abc" A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 A million repetitions of "a" 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F */ #include typedef unsigned int uint32_t; typedef struct { uint32_t state[5]; uint32_t count[2]; unsigned char buffer[64]; } SHA1_CTX; #if !defined(XYMON_BIG_ENDIAN) && !defined(XYMON_LITTLE_ENDIAN) #error "Endianness is UNDEFINED" #endif #define SHA1HANDSOFF #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) /* blk0() and blk() perform the initial expand. */ /* I got the idea of expanding during the round function from SSLeay */ #ifdef XYMON_BIG_ENDIAN # define blk0(i) block->l[i] #else # define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ |(rol(block->l[i],8)&0x00FF00FF)) #endif #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ ^block->l[(i+2)&15]^block->l[i&15],1)) /* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); #define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); #define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); #define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); #define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); /* Hash a single 512-bit block. This is the core of the algorithm. */ static void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) { uint32_t a, b, c, d, e; typedef union { unsigned char c[64]; uint32_t l[16]; } CHAR64LONG16; #ifdef SHA1HANDSOFF CHAR64LONG16 block[1]; /* use array to appear as a pointer */ memcpy(block, buffer, 64); #else /* The following had better never be used because it causes the * pointer-to-const buffer to be cast into a pointer to non-const. * And the result is written through. I threw a "const" in, hoping * this will cause a diagnostic. */ CHAR64LONG16* block = (const CHAR64LONG16*)buffer; #endif /* Copy context->state[] to working vars */ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; /* 4 rounds of 20 operations each. Loop unrolled. */ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); /* Add the working vars back into context.state[] */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; /* Wipe variables */ a = b = c = d = e = 0; #ifdef SHA1HANDSOFF memset(block, '\0', sizeof(block)); #endif } /* SHA1Init - Initialize new context */ static void SHA1Init(SHA1_CTX* context) { /* SHA1 initialization constants */ context->state[0] = 0x67452301; context->state[1] = 0xEFCDAB89; context->state[2] = 0x98BADCFE; context->state[3] = 0x10325476; context->state[4] = 0xC3D2E1F0; context->count[0] = context->count[1] = 0; } /* Run your data through this. */ static void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len) { uint32_t i; uint32_t j; j = context->count[0]; if ((context->count[0] += len << 3) < j) context->count[1]++; context->count[1] += (len>>29); j = (j >> 3) & 63; if ((j + len) > 63) { memcpy(&context->buffer[j], data, (i = 64-j)); SHA1Transform(context->state, context->buffer); for ( ; i + 63 < len; i += 64) { SHA1Transform(context->state, &data[i]); } j = 0; } else i = 0; memcpy(&context->buffer[j], &data[i], len - i); } /* Add padding and return the message digest. */ static void SHA1Final(unsigned char digest[20], SHA1_CTX* context) { unsigned i; unsigned char finalcount[8]; unsigned char c; #if 0 /* untested "improvement" by DHR */ /* Convert context->count to a sequence of bytes * in finalcount. Second element first, but * big-endian order within element. * But we do it all backwards. */ unsigned char *fcp = &finalcount[8]; for (i = 0; i < 2; i++) { uint32_t t = context->count[i]; int j; for (j = 0; j < 4; t >>= 8, j++) *--fcp = (unsigned char) t } #else for (i = 0; i < 8; i++) { finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ } #endif c = 0200; SHA1Update(context, &c, 1); while ((context->count[0] & 504) != 448) { c = 0000; SHA1Update(context, &c, 1); } SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ for (i = 0; i < 20; i++) { digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); } /* Wipe variables */ memset(context, '0', sizeof(*context)); memset(&finalcount, '\0', sizeof(finalcount)); } /* * Not part of the original file. Added for use with Xymon, * to avoid namespace-pollution when compiled with OpenSSL. */ int mySHA1_Size(void) { return sizeof(SHA1_CTX); } void mySHA1_Init(void* context) { SHA1Init((SHA1_CTX *)context); } void mySHA1_Update(void* context, const unsigned char* data, uint32_t len) { SHA1Update((SHA1_CTX *)context, data, len); } void mySHA1_Final(unsigned char digest[20], void* context) { SHA1Final(digest, (SHA1_CTX *)context); } #ifdef STANDALONE #include #include #include int main(int argc, char *argv[]) { FILE *fd; int n; unsigned char buf[8192]; void *context; unsigned char digest[20]; int i; fd = fopen(argv[1], "r"); if (fd == NULL) return 1; context = (void *)malloc(mySHA1_Size()); mySHA1_Init(context); while ((n = fread(buf, 1, sizeof(buf), fd)) > 0) mySHA1_Update(context, buf, n); fclose(fd); mySHA1_Final(digest, context); for (i=0; (i < sizeof(digest)); i++) { printf("%02x", digest[i]); } printf("\n"); return 0; } #endif xymon-4.3.7/lib/test-endianness.c0000664000175000017500000000342011615341300016232 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Utility program to define endian-ness of the target system. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: test-endianness.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include int main(int argc, char **argv) { unsigned int c; unsigned char cbuf[sizeof(c)]; int i; int outform = 1; if ((argc > 1) && (strcmp(argv[1], "--configh") == 0)) outform = 0; for (i=0; (i < sizeof(c)); i++) { cbuf[i] = (i % 2); } memcpy(&c, cbuf, sizeof(c)); if (c == 65537) { /* Big endian */ if (outform == 0) printf("#ifndef XYMON_BIG_ENDIAN\n#define XYMON_BIG_ENDIAN\n#endif\n"); else printf(" -DXYMON_BIG_ENDIAN"); } else if (c == 16777472) { /* Little endian */ if (outform == 0) printf("#ifndef XYMON_LITTLE_ENDIAN\n#define XYMON_LITTLE_ENDIAN\n#endif\n"); else printf(" -DXYMON_LITTLE_ENDIAN"); } else { fprintf(stderr, "UNKNOWN ENDIANNESS! testvalue is %u\n", c); } fflush(stdout); return 0; } xymon-4.3.7/lib/netservices.h0000664000175000017500000000242611615341300015472 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __NETSERVICES_H__ #define __NETSERVICES_H__ /* * Flag bits for known TCP services */ #define TCP_GET_BANNER 0x0001 #define TCP_TELNET 0x0002 #define TCP_SSL 0x0004 #define TCP_HTTP 0x0008 typedef struct svcinfo_t { char *svcname; unsigned char *sendtxt; int sendlen; unsigned char *exptext; int expofs, explen; unsigned int flags; int port; } svcinfo_t; extern char *init_tcp_services(void); extern void dump_tcp_services(void); extern int default_tcp_port(char *svcname); extern svcinfo_t *find_tcp_service(char *svcname); #endif xymon-4.3.7/lib/encoding.h0000664000175000017500000000203011615341300014715 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __ENCODING_H__ #define __ENCODING_H__ extern char *base64encode(unsigned char *buf); extern char *base64decode(unsigned char *buf); extern void getescapestring(char *msg, unsigned char **buf, int *buflen); extern unsigned char *nlencode(unsigned char *msg); extern void nldecode(unsigned char *msg); #endif xymon-4.3.7/lib/timefunc.h0000664000175000017500000000307111615341300014747 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __TIMEFUNC_H__ #define __TIMEFUNC_H__ extern time_t fakestarttime; extern char *timestamp; extern time_t getcurrenttime(time_t *retparm); #define time(X) getcurrenttime(X) extern time_t gettimer(void); extern void getntimer(struct timespec *tp); extern void init_timestamp(void); extern char *timespec_text(char *spec); extern struct timespec *tvdiff(struct timespec *tstart, struct timespec *tend, struct timespec *result); extern int within_sla(char *holidaykey, char *timespec, int defresult); extern char *check_downtime(char *hostname, char *testname); extern int periodcoversnow(char *tag); extern char *histlogtime(time_t histtime); extern int durationvalue(char *dur); extern char *durationstring(time_t secs); extern char *agestring(time_t secs); extern time_t timestr2timet(char *s); extern time_t eventreport_time(char *timestamp); #endif xymon-4.3.7/lib/color.h0000664000175000017500000000230611615341300014253 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __COLOR_H__ #define __COLOR_H__ #define COL_GREEN 0 #define COL_CLEAR 1 #define COL_BLUE 2 #define COL_PURPLE 3 #define COL_YELLOW 4 #define COL_RED 5 #define COL_CLIENT 99 #define COL_COUNT (COL_RED+1) extern int use_recentgifs; extern char *colorname(int color); extern int parse_color(char *colortext); extern int eventcolor(char *colortext); extern char *dotgiffilename(int color, int acked, int oldage); extern int colorset(char *colspec, int excludeset); #endif xymon-4.3.7/lib/cgiurls.h0000664000175000017500000000242511615341300014607 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __CGIURLS_H__ #define __CGIURLS_H__ #include #include "availability.h" extern char *hostsvcurl(char *hostname, char *service, int htmlformat); extern char *hostsvcclienturl(char *hostname, char *section); extern char *histcgiurl(char *hostname, char *service); extern char *histlogurl(char *hostname, char *service, time_t histtime, char *histtime_txt); extern char *replogurl(char *hostname, char *service, int color, char *style, int recentgifs, reportinfo_t *repinfo, char *reportstart, time_t reportend, float reportwarnlevel); #endif xymon-4.3.7/lib/links.c0000664000175000017500000001247011630612042014254 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module for Xymon, responsible for loading the host-, */ /* page-, and column-links present in www/notes and www/help. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: links.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #include #include #include #include #include #include "libxymon.h" /* Info-link definitions. */ typedef struct link_t { char *key; char *filename; char *urlprefix; /* "/help", "/notes" etc. */ } link_t; static int linksloaded = 0; static void * linkstree; static char *notesskin = NULL; static char *helpskin = NULL; static char *columndocurl = NULL; static char *hostdocurl = NULL; char *link_docext(char *fn) { char *p = strrchr(fn, '.'); if (p == NULL) return NULL; if ( (strcmp(p, ".php") == 0) || (strcmp(p, ".php3") == 0) || (strcmp(p, ".asp") == 0) || (strcmp(p, ".doc") == 0) || (strcmp(p, ".shtml") == 0) || (strcmp(p, ".phtml") == 0) || (strcmp(p, ".html") == 0) || (strcmp(p, ".htm") == 0)) { return p; } return NULL; } static link_t *init_link(char *filename, char *urlprefix) { char *p; link_t *newlink = NULL; dbgprintf("init_link(%s, %s)\n", textornull(filename), textornull(urlprefix)); newlink = (link_t *) malloc(sizeof(link_t)); newlink->filename = strdup(filename); newlink->urlprefix = urlprefix; /* Without extension, this time */ p = link_docext(filename); if (p) *p = '\0'; newlink->key = strdup(filename); return newlink; } static void load_links(char *directory, char *urlprefix) { DIR *linkdir; struct dirent *d; char fn[PATH_MAX]; dbgprintf("load_links(%s, %s)\n", textornull(directory), textornull(urlprefix)); linkdir = opendir(directory); if (!linkdir) { errprintf("Cannot read links in directory %s\n", directory); return; } MEMDEFINE(fn); while ((d = readdir(linkdir))) { link_t *newlink; if (*(d->d_name) == '.') continue; strcpy(fn, d->d_name); newlink = init_link(fn, urlprefix); xtreeAdd(linkstree, newlink->key, newlink); } closedir(linkdir); MEMUNDEFINE(fn); } void load_all_links(void) { char dirname[PATH_MAX]; char *p; MEMDEFINE(dirname); dbgprintf("load_all_links()\n"); linkstree = xtreeNew(strcasecmp); if (notesskin) { xfree(notesskin); notesskin = NULL; } if (helpskin) { xfree(helpskin); helpskin = NULL; } if (columndocurl) { xfree(columndocurl); columndocurl = NULL; } if (hostdocurl) { xfree(hostdocurl); hostdocurl = NULL; } if (xgetenv("XYMONNOTESSKIN")) notesskin = strdup(xgetenv("XYMONNOTESSKIN")); else { notesskin = (char *) malloc(strlen(xgetenv("XYMONWEB")) + strlen("/notes") + 1); sprintf(notesskin, "%s/notes", xgetenv("XYMONWEB")); } if (xgetenv("XYMONHELPSKIN")) helpskin = strdup(xgetenv("XYMONHELPSKIN")); else { helpskin = (char *) malloc(strlen(xgetenv("XYMONWEB")) + strlen("/help") + 1); sprintf(helpskin, "%s/help", xgetenv("XYMONWEB")); } if (xgetenv("COLUMNDOCURL")) columndocurl = strdup(xgetenv("COLUMNDOCURL")); if (xgetenv("HOSTDOCURL")) hostdocurl = strdup(xgetenv("HOSTDOCURL")); if (!hostdocurl || (strlen(hostdocurl) == 0)) { strcpy(dirname, xgetenv("XYMONNOTESDIR")); load_links(dirname, notesskin); } /* Change xxx/xxx/xxx/notes into xxx/xxx/xxx/help */ strcpy(dirname, xgetenv("XYMONNOTESDIR")); p = strrchr(dirname, '/'); *p = '\0'; strcat(dirname, "/help"); load_links(dirname, helpskin); linksloaded = 1; MEMUNDEFINE(dirname); } static link_t *find_link(char *key) { link_t *l = NULL; xtreePos_t handle; handle = xtreeFind(linkstree, key); if (handle != xtreeEnd(linkstree)) l = (link_t *)xtreeData(linkstree, handle); return l; } char *columnlink(char *colname) { static char *linkurl = NULL; link_t *link; if (linkurl == NULL) linkurl = (char *)malloc(PATH_MAX); if (!linksloaded) load_all_links(); link = find_link(colname); if (link) { sprintf(linkurl, "%s/%s", link->urlprefix, link->filename); } else if (columndocurl) { sprintf(linkurl, columndocurl, colname); } else { *linkurl = '\0'; } return linkurl; } char *hostlink(char *hostname) { static char *linkurl = NULL; link_t *link; if (linkurl == NULL) linkurl = (char *)malloc(PATH_MAX); if (!linksloaded) load_all_links(); if (hostdocurl && *hostdocurl) { snprintf(linkurl, PATH_MAX-1, hostdocurl, hostname); return linkurl; } else { link = find_link(hostname); if (link) { sprintf(linkurl, "%s/%s", link->urlprefix, link->filename); return linkurl; } } return NULL; } xymon-4.3.7/lib/locator.c0000664000175000017500000003036411630612042014601 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for communicating with the Xymon locator service. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: locator.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include const char *servicetype_names[] = { "rrd", "client", "alert", "history", "hostdata" }; static struct sockaddr_in myaddr; static socklen_t myaddrsz = 0; static int locatorsocket = -1; #define DEFAULT_CACHETIMEOUT (15*60) /* 15 minutes */ static void * locatorcache[ST_MAX]; static int havecache[ST_MAX] = {0,}; static int cachetimeout[ST_MAX] = {0,}; typedef struct cacheitm_t { char *key, *resp; time_t tstamp; } cacheitm_t; enum locator_servicetype_t get_servicetype(char *typestr) { enum locator_servicetype_t res = 0; while ((res < ST_MAX) && (strcmp(typestr, servicetype_names[res]))) res++; return res; } static int call_locator(char *buf, size_t bufsz) { int n, bytesleft; fd_set fds; struct timeval tmo; char *bufp; /* First, send the request */ bufp = buf; bytesleft = strlen(buf)+1; do { FD_ZERO(&fds); FD_SET(locatorsocket, &fds); tmo.tv_sec = 5; tmo.tv_usec = 0; n = select(locatorsocket+1, NULL, &fds, NULL, &tmo); if (n == 0) { errprintf("Send to locator failed: Timeout\n"); return -1; } else if (n == -1) { errprintf("Send to locator failed: %s\n", strerror(errno)); return -1; } n = send(locatorsocket, bufp, bytesleft, 0); if (n == -1) { errprintf("Send to locator failed: %s\n", strerror(errno)); return -1; } else { bytesleft -= n; bufp += n; } } while (bytesleft > 0); /* Then read the response */ FD_ZERO(&fds); FD_SET(locatorsocket, &fds); tmo.tv_sec = 5; tmo.tv_usec = 0; n = select(locatorsocket+1, &fds, NULL, NULL, &tmo); if (n > 0) { n = recv(locatorsocket, buf, bufsz-1, 0); if (n == -1) { errprintf("call_locator() recv() failed: %s\n", strerror(errno)); return -1; } buf[n] = '\0'; } else { errprintf("call_locator() comm failure: %s\n", (n == 0) ? "Timeout" : strerror(errno)); return -1; } return 0; } static char *locator_querycache(enum locator_servicetype_t svc, char *key) { xtreePos_t handle; cacheitm_t *itm; if (!havecache[svc]) return NULL; handle = xtreeFind(locatorcache[svc], key); if (handle == xtreeEnd(locatorcache[svc])) return NULL; itm = (cacheitm_t *)xtreeData(locatorcache[svc], handle); return (itm->tstamp + cachetimeout[svc]) > getcurrenttime(NULL) ? itm->resp : NULL; } static void locator_updatecache(enum locator_servicetype_t svc, char *key, char *resp) { xtreePos_t handle; cacheitm_t *newitm; if (!havecache[svc]) return; handle = xtreeFind(locatorcache[svc], key); if (handle == xtreeEnd(locatorcache[svc])) { newitm = (cacheitm_t *)calloc(1, sizeof(cacheitm_t)); newitm->key = strdup(key); newitm->resp = strdup(resp); if (xtreeAdd(locatorcache[svc], newitm->key, newitm) != XTREE_STATUS_OK) { xfree(newitm->key); xfree(newitm->resp); xfree(newitm); } } else { newitm = (cacheitm_t *)xtreeData(locatorcache[svc], handle); if (newitm->resp) xfree(newitm->resp); newitm->resp = strdup(resp); newitm->tstamp = getcurrenttime(NULL); } } void locator_flushcache(enum locator_servicetype_t svc, char *key) { xtreePos_t handle; if (!havecache[svc]) return; if (key) { handle = xtreeFind(locatorcache[svc], key); if (handle != xtreeEnd(locatorcache[svc])) { cacheitm_t *itm = (cacheitm_t *)xtreeData(locatorcache[svc], handle); itm->tstamp = 0; } } else { for (handle = xtreeFirst(locatorcache[svc]); (handle != xtreeEnd(locatorcache[svc])); handle = xtreeNext(locatorcache[svc], handle)) { cacheitm_t *itm = (cacheitm_t *)xtreeData(locatorcache[svc], handle); itm->tstamp = 0; } } } void locator_prepcache(enum locator_servicetype_t svc, int timeout) { if (!havecache[svc]) { locatorcache[svc] = xtreeNew(strcasecmp); havecache[svc] = 1; } else { locator_flushcache(svc, NULL); } cachetimeout[svc] = ((timeout>0) ? timeout : DEFAULT_CACHETIMEOUT); } char *locator_cmd(char *cmd) { static char pingbuf[512]; int res; strcpy(pingbuf, cmd); res = call_locator(pingbuf, sizeof(pingbuf)); return (res == 0) ? pingbuf : NULL; } char *locator_ping(void) { return locator_cmd("p"); } int locator_init(char *ipport) { char *ip, *p; int portnum; if (locatorsocket >= 0) { close(locatorsocket); locatorsocket = -1; } locatorsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (locatorsocket == -1) { errprintf("Cannot get socket: %s\n", strerror(errno)); return -1; } ip = strdup(ipport); p = strchr(ip, ':'); if (p) { *p = '\0'; portnum = atoi(p+1); } else { portnum = atoi(xgetenv("XYMONDPORT")); } memset(&myaddr, 0, sizeof(myaddr)); inet_aton(ip, (struct in_addr *) &myaddr.sin_addr.s_addr); myaddr.sin_port = htons(portnum); myaddr.sin_family = AF_INET; myaddrsz = sizeof(myaddr); if (connect(locatorsocket, (struct sockaddr *)&myaddr, myaddrsz) == -1) { errprintf("Cannot set target address for locator: %s:%d (%s)\n", ip, portnum, strerror(errno)); close(locatorsocket); locatorsocket = -1; return -1; } fcntl(locatorsocket, F_SETFL, O_NONBLOCK); return (locator_ping() ? 0 : -1); } int locator_register_server(char *servername, enum locator_servicetype_t svctype, int weight, enum locator_sticky_t sticky, char *extras) { char *buf; int bufsz; int res; bufsz = strlen(servername) + 100; if (extras) bufsz += (strlen(extras) + 1); buf = (char *)malloc(bufsz); if (sticky == LOC_SINGLESERVER) weight = -1; sprintf(buf, "S|%s|%s|%d|%d|%s", servername, servicetype_names[svctype], weight, ((sticky == LOC_STICKY) ? 1 : 0), (extras ? extras : "")); res = call_locator(buf, bufsz); xfree(buf); return res; } int locator_register_host(char *hostname, enum locator_servicetype_t svctype, char *servername) { char *buf; int bufsz; int res; bufsz = strlen(servername) + strlen(hostname) + 100; buf = (char *)malloc(bufsz); sprintf(buf, "H|%s|%s|%s", hostname, servicetype_names[svctype], servername); res = call_locator(buf, bufsz); xfree(buf); return res; } int locator_rename_host(char *oldhostname, char *newhostname, enum locator_servicetype_t svctype) { char *buf; int bufsz; int res; bufsz = strlen(oldhostname) + strlen(newhostname) + 100; buf = (char *)malloc(bufsz); sprintf(buf, "M|%s|%s|%s", oldhostname, servicetype_names[svctype], newhostname); res = call_locator(buf, bufsz); xfree(buf); return res; } char *locator_query(char *hostname, enum locator_servicetype_t svctype, char **extras) { static char *buf = NULL; static int bufsz = 0; int res, bufneeded; bufneeded = strlen(hostname) + 100; if (extras) bufneeded += 1024; if (!buf) { bufsz = bufneeded; buf = (char *)malloc(bufsz); } else if (bufneeded > bufsz) { bufsz = bufneeded; buf = (char *)realloc(buf, bufsz); } if (havecache[svctype] && !extras) { char *cachedata = locator_querycache(svctype, hostname); if (cachedata) return cachedata; } sprintf(buf, "Q|%s|%s", servicetype_names[svctype], hostname); if (extras) buf[0] = 'X'; res = call_locator(buf, bufsz); if (res == -1) return NULL; switch (*buf) { case '!': /* This host is fixed on an available server */ case '*': /* Roaming host */ if (extras) { *extras = strchr(buf+2, '|'); if (**extras == '|') { **extras = '\0'; (*extras)++; } } if (havecache[svctype] && !extras) locator_updatecache(svctype, hostname, buf+2); return ((strlen(buf) > 2) ? buf+2 : NULL); case '?': /* No available server to handle the request */ locator_flushcache(svctype, hostname); return NULL; } return NULL; } int locator_serverdown(char *servername, enum locator_servicetype_t svctype) { char *buf; int bufsz; int res; bufsz = strlen(servername) + 100; buf = (char *)malloc(bufsz); sprintf(buf, "D|%s|%s", servername, servicetype_names[svctype]); res = call_locator(buf, bufsz); locator_flushcache(svctype, NULL); xfree(buf); return res; } int locator_serverup(char *servername, enum locator_servicetype_t svctype) { char *buf; int bufsz; int res; bufsz = strlen(servername) + 100; buf = (char *)malloc(bufsz); sprintf(buf, "U|%s|%s", servername, servicetype_names[svctype]); res = call_locator(buf, bufsz); xfree(buf); return res; } int locator_serverforget(char *servername, enum locator_servicetype_t svctype) { char *buf; int bufsz; int res; bufsz = strlen(servername) + 100; buf = (char *)malloc(bufsz); sprintf(buf, "F|%s|%s", servername, servicetype_names[svctype]); res = call_locator(buf, bufsz); locator_flushcache(svctype, NULL); xfree(buf); return res; } #ifdef STANDALONE int main(int argc, char *argv[]) { char buf[1024]; int done = 0; char *res; if (argc < 2) { printf("Usage: %s IP:PORT\n", argv[0]); return 1; } if (locator_init(argv[1]) == -1) { printf("Locator ping failed\n"); return 1; } else { printf("Locator is available\n"); } while (!done) { char *p, *p1, *p2, *p3, *p4, *p5, *p6, *p7; char *extras; printf("Commands:\n"); printf(" r(egister) s servername type weight sticky\n"); printf(" r(egister) h servername type hostname\n"); printf(" d(own) servername type\n"); printf(" u(p) servername type\n"); printf(" f(orget) servername type\n"); printf(" q(uery) hostname type\n"); printf(" x(query) hostname type\n"); printf(" p(ing)\n"); printf(" s(ave state)\n"); printf(">"); fflush(stdout); done = (fgets(buf, sizeof(buf), stdin) == NULL); if (done) continue; p = strchr(buf, '\n'); if (p) *p = '\0'; p1 = p2 = p3 = p4 = p5 = p6 = p7 = NULL; p1 = strtok(buf, " "); if (p1) p2 = strtok(NULL, " "); if (p2) p3 = strtok(NULL, " "); if (p3) p4 = strtok(NULL, " "); if (p4) p5 = strtok(NULL, " "); if (p5) p6 = strtok(NULL, " "); if (p6) p7 = strtok(NULL, "\r\n"); switch (*p1) { case 'R': case 'r': if (*p2 == 's') { enum locator_servicetype_t svc; enum locator_sticky_t sticky; int weight; svc = get_servicetype(p4); weight = (p5 ? atoi(p5) : 1); sticky = ((p6 && (atoi(p6) == 1)) ? LOC_STICKY : LOC_ROAMING); printf("%s\n", locator_register_server(p3, svc, weight, sticky, p7) ? "Failed" : "OK"); } else if (*p2 == 'h') { printf("%s\n", locator_register_host(p5, get_servicetype(p4), p3) ? "Failed" : "OK"); } break; case 'D': case 'd': printf("%s\n", locator_serverdown(p2, get_servicetype(p3)) ? "Failed" : "OK"); break; case 'U': case 'u': printf("%s\n", locator_serverup(p2, get_servicetype(p3)) ? "Failed" : "OK"); break; case 'F': case 'f': printf("%s\n", locator_serverforget(p2, get_servicetype(p3)) ? "Failed" : "OK"); break; case 'Q': case 'q': case 'X': case 'x': extras = NULL; res = locator_query(p2, get_servicetype(p3), (*p1 == 'x') ? &extras : NULL); if (res) { printf("Result: %s\n", res); if (extras) printf(" Extras gave: %s\n", extras); } else { printf("Failed\n"); } break; case 'P': case 'p': p = locator_cmd("p"); if (p == NULL) printf("Failed\n"); else printf("OK: %s\n", p); break; case 'S': case 's': p = locator_cmd("@"); if (p == NULL) printf("Failed\n"); else printf("OK: %s\n", p); break; } } return 0; } #endif xymon-4.3.7/lib/osdefs.c0000664000175000017500000000227511615341300014420 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* Compatibility definitions for various OS's */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #include #include #include #include "osdefs.h" #ifndef HAVE_SNPRINTF int snprintf(char *str, size_t size, const char *format, ...) { va_list args; va_start(args, format); return vsprintf(str, format, args); } #endif #ifndef HAVE_VSNPRINTF int vsnprintf(char *str, size_t size, const char *format, va_list args) { return vsprintf(str, format, args); } #endif xymon-4.3.7/lib/msort.h0000664000175000017500000000202011615341300014272 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __MSORT_H__ #define __MSORT_H__ typedef int (msortcompare_fn_t)(void **, void **); typedef void * (msortgetnext_fn_t)(void *); typedef void (msortsetnext_fn_t)(void *, void *); extern void *msort(void *head, msortcompare_fn_t comparefn, msortgetnext_fn_t getnext, msortsetnext_fn_t setnext); #endif xymon-4.3.7/lib/ripemd.h0000664000175000017500000001165411535462534014441 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This file is part of the Xymon monitor library, but was taken from the */ /* FreeBSD sources. It was originally written by Eric Young, and is NOT */ /* licensed under the GPL. Please adhere the original copyright notice below. */ /*----------------------------------------------------------------------------*/ /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * * This package is an SSL implementation written * by Eric Young (eay@cryptsoft.com). * The implementation was written so as to conform with Netscapes SSL. * * This library is free for commercial and non-commercial use as long as * the following conditions are aheared to. The following conditions * apply to all code found in this distribution, be it the RC4, RSA, * lhash, DES, etc., code; not just the SSL code. The SSL documentation * included with this distribution is covered by the same copyright terms * except that the holder is Tim Hudson (tjh@cryptsoft.com). * * Copyright remains Eric Young's, and as such any Copyright notices in * the code are not to be removed. * If this package is used in a product, Eric Young should be given attribution * as the author of the parts of the library used. * This can be in the form of a textual message at program startup or * in documentation (online or textual) provided with the package. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * "This product includes cryptographic software written by * Eric Young (eay@cryptsoft.com)" * The word 'cryptographic' can be left out if the rouines from the library * being used are not cryptographic related :-). * 4. If you include any Windows specific code (or a derivative thereof) from * the apps directory (application code) you must include an acknowledgement: * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" * * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * The licence and distribution terms for any publically available version or * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] */ #ifndef HEADER_RIPEMD_H #define HEADER_RIPEMD_H /******************** Modifications for Xymon ***************************/ #include "config.h" #ifndef HAVE_UINT32_TYPEDEF typedef unsigned int u_int32_t; #endif #ifndef BYTE_ORDER #ifndef LITTLE_ENDIAN #define LITTLE_ENDIAN 1234 #endif #ifndef BIG_ENDIAN #define BIG_ENDIAN 4321 #endif #ifdef XYMON_LITTLE_ENDIAN #define BYTE_ORDER LITTLE_ENDIAN #else #define BYTE_ORDER BIG_ENDIAN #endif #endif /******************** End of modifications for Xymon ***************************/ #define RIPEMD160_CBLOCK 64 #define RIPEMD160_LBLOCK 16 #define RIPEMD160_BLOCK 16 #define RIPEMD160_LAST_BLOCK 56 #define RIPEMD160_LENGTH_BLOCK 8 #define RIPEMD160_DIGEST_LENGTH 20 typedef struct RIPEMD160state_st { u_int32_t A,B,C,D,E; u_int32_t Nl,Nh; u_int32_t data[RIPEMD160_LBLOCK]; int num; } RIPEMD160_CTX; #if 0 __BEGIN_DECLS void RIPEMD160_Init(RIPEMD160_CTX *c); void RIPEMD160_Update(RIPEMD160_CTX *c, const void *data, size_t len); void RIPEMD160_Final(unsigned char *md, RIPEMD160_CTX *c); char *RIPEMD160_End(RIPEMD160_CTX *, char *); char *RIPEMD160_File(const char *, char *); char *RIPEMD160_FileChunk(const char *, char *, off_t, off_t); char *RIPEMD160_Data(const void *, unsigned int, char *); __END_DECLS #endif #endif xymon-4.3.7/lib/files.h0000664000175000017500000000150411615341300014236 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __FILES_H__ #define __FILES_H__ extern void dropdirectory(char *dirfn, int background); #endif xymon-4.3.7/lib/environ.h0000664000175000017500000000177311615341300014624 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __ENVIRON_H__ #define __ENVIRON_H__ extern char *xgetenv(const char *name); extern void envcheck(char *envvars[]); extern void loadenv(char *envfile, char *area); extern char *getenv_default(char *envname, char *envdefault, char **buf); extern char *expand_env(char *s); #endif xymon-4.3.7/lib/osdefs.h0000664000175000017500000000230111615341300014413 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* Compatibility definitions for various OS's */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __LIBXYMON_OSDEFS_H__ #define __LIBXYMON_OSDEFS_H__ #include "config.h" #include #include #ifndef HAVE_SOCKLEN_T typedef unsigned int socklen_t; #endif #ifndef HAVE_SNPRINTF extern int snprintf(char *str, size_t size, const char *format, ...); #endif #ifndef HAVE_VSNPRINTF extern int vsnprintf(char *str, size_t size, const char *format, va_list ap); #endif #endif xymon-4.3.7/lib/xymonrrd.c0000664000175000017500000002564311630612042015024 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for working with RRD graphs. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymonrrd.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #include #include #include #include #include "libxymon.h" #include "version.h" /* This is for mapping a status-name -> RRD file */ xymonrrd_t *xymonrrds = NULL; void * xymonrrdtree; /* This is the information needed to generate links on the trends column page */ xymongraph_t *xymongraphs = NULL; static const char *xymonlinkfmt = "
\"xymongraph \"Zoom
\n"; static const char *metafmt = "\n %s\n \n \n\n"; /* * Define the mapping between Xymon columns and RRD graphs. * Normally they are identical, but some RRD's use different names. */ static void rrd_setup(void) { static int setup_done = 0; char *lenv, *ldef, *p, *tcptests, *services; int count; xymonrrd_t *lrec; xymongraph_t *grec; /* Do nothing if we have been called within the past 5 minutes */ if ((setup_done + 300) >= getcurrenttime(NULL)) return; /* * Must free any old data first. * NB: These lists are NOT null-terminated ! * Stop when svcname becomes a NULL. */ lrec = xymonrrds; while (lrec && lrec->svcname) { if (lrec->xymonrrdname != lrec->svcname) xfree(lrec->xymonrrdname); xfree(lrec->svcname); lrec++; } if (xymonrrds) { xfree(xymonrrds); xtreeDestroy(xymonrrdtree); } grec = xymongraphs; while (grec && grec->xymonrrdname) { if (grec->xymonpartname) xfree(grec->xymonpartname); xfree(grec->xymonrrdname); grec++; } if (xymongraphs) xfree(xymongraphs); /* Get the tcp services, and count how many there are */ services = strdup(init_tcp_services()); tcptests = strdup(services); count = 0; p = strtok(tcptests, " "); while (p) { count++; p = strtok(NULL, " "); } strcpy(tcptests, services); /* Setup the xymonrrds table, mapping test-names to RRD files */ lenv = (char *)malloc(strlen(xgetenv("TEST2RRD")) + strlen(tcptests) + count*strlen(",=tcp") + 1); strcpy(lenv, xgetenv("TEST2RRD")); p = lenv+strlen(lenv)-1; if (*p == ',') *p = '\0'; /* Drop a trailing comma */ p = strtok(tcptests, " "); while (p) { sprintf(lenv+strlen(lenv), ",%s=tcp", p); p = strtok(NULL, " "); } xfree(tcptests); xfree(services); count = 0; p = lenv; do { count++; p = strchr(p+1, ','); } while (p); xymonrrds = (xymonrrd_t *)calloc(sizeof(xymonrrd_t), (count+1)); xymonrrdtree = xtreeNew(strcasecmp); lrec = xymonrrds; ldef = strtok(lenv, ","); while (ldef) { p = strchr(ldef, '='); if (p) { *p = '\0'; lrec->svcname = strdup(ldef); lrec->xymonrrdname = strdup(p+1); } else { lrec->svcname = lrec->xymonrrdname = strdup(ldef); } xtreeAdd(xymonrrdtree, lrec->svcname, lrec); ldef = strtok(NULL, ","); lrec++; } xfree(lenv); /* Setup the xymongraphs table, describing how to make graphs from an RRD */ lenv = strdup(xgetenv("GRAPHS")); p = lenv+strlen(lenv)-1; if (*p == ',') *p = '\0'; /* Drop a trailing comma */ count = 0; p = lenv; do { count++; p = strchr(p+1, ','); } while (p); xymongraphs = (xymongraph_t *)calloc(sizeof(xymongraph_t), (count+1)); grec = xymongraphs; ldef = strtok(lenv, ","); while (ldef) { p = strchr(ldef, ':'); if (p) { *p = '\0'; grec->xymonrrdname = strdup(ldef); grec->xymonpartname = strdup(p+1); p = strchr(grec->xymonpartname, ':'); if (p) { *p = '\0'; grec->maxgraphs = atoi(p+1); if (strlen(grec->xymonpartname) == 0) { xfree(grec->xymonpartname); grec->xymonpartname = NULL; } } } else { grec->xymonrrdname = strdup(ldef); } ldef = strtok(NULL, ","); grec++; } xfree(lenv); setup_done = getcurrenttime(NULL); } xymonrrd_t *find_xymon_rrd(char *service, char *flags) { /* Lookup an entry in the xymonrrds table */ xtreePos_t handle; rrd_setup(); if (flags && (strchr(flags, 'R') != NULL)) { /* Dont do RRD's for reverse tests, since they have no data */ return NULL; } handle = xtreeFind(xymonrrdtree, service); if (handle == xtreeEnd(xymonrrdtree)) return NULL; else { return (xymonrrd_t *)xtreeData(xymonrrdtree, handle); } } xymongraph_t *find_xymon_graph(char *rrdname) { /* Lookup an entry in the xymongraphs table */ xymongraph_t *grec; int found = 0; char *dchar; rrd_setup(); grec = xymongraphs; while (!found && (grec->xymonrrdname != NULL)) { found = (strncmp(grec->xymonrrdname, rrdname, strlen(grec->xymonrrdname)) == 0); if (found) { /* Check that it's not a partial match, e.g. "ftp" matches "ftps" */ dchar = rrdname + strlen(grec->xymonrrdname); if ( (*dchar != '.') && (*dchar != ',') && (*dchar != '\0') ) found = 0; } if (!found) grec++; } return (found ? grec : NULL); } static char *xymon_graph_text(char *hostname, char *dispname, char *service, int bgcolor, xymongraph_t *graphdef, int itemcount, hg_stale_rrds_t nostale, const char *fmt, int locatorbased, time_t starttime, time_t endtime) { static char *rrdurl = NULL; static int rrdurlsize = 0; static int gwidth = 0, gheight = 0; char *svcurl; int svcurllen, rrdparturlsize; char rrdservicename[100]; char *cgiurl = xgetenv("CGIBINURL"); MEMDEFINE(rrdservicename); if (locatorbased) { char *qres = locator_query(hostname, ST_RRD, &cgiurl); if (!qres) { errprintf("Cannot find RRD files for host %s\n", hostname); return ""; } } if (!gwidth) { gwidth = atoi(xgetenv("RRDWIDTH")); gheight = atoi(xgetenv("RRDHEIGHT")); } dbgprintf("rrdlink_url: host %s, rrd %s (partname:%s, maxgraphs:%d, count=%d)\n", hostname, graphdef->xymonrrdname, textornull(graphdef->xymonpartname), graphdef->maxgraphs, itemcount); if ((service != NULL) && (strcmp(graphdef->xymonrrdname, "tcp") == 0)) { sprintf(rrdservicename, "tcp:%s", service); } else if ((service != NULL) && (strcmp(graphdef->xymonrrdname, "ncv") == 0)) { sprintf(rrdservicename, "ncv:%s", service); } else if ((service != NULL) && (strcmp(graphdef->xymonrrdname, "devmon") == 0)) { sprintf(rrdservicename, "devmon:%s", service); } else { strcpy(rrdservicename, graphdef->xymonrrdname); } svcurllen = 2048 + strlen(cgiurl) + strlen(hostname) + strlen(rrdservicename) + strlen(urlencode(dispname ? dispname : hostname)); svcurl = (char *) malloc(svcurllen); rrdparturlsize = 2048 + strlen(fmt) + 3*svcurllen + strlen(rrdservicename) + strlen(xgetenv("XYMONSKIN")); if (rrdurl == NULL) { rrdurlsize = rrdparturlsize; rrdurl = (char *) malloc(rrdurlsize); } *rrdurl = '\0'; { char *rrdparturl; int first = 1; int step; step = (graphdef->maxgraphs ? graphdef->maxgraphs : 5); if (itemcount) { int gcount = (itemcount / step); if ((gcount*step) != itemcount) gcount++; step = (itemcount / gcount); } rrdparturl = (char *) malloc(rrdparturlsize); do { if (itemcount > 0) { sprintf(svcurl, "%s/showgraph.sh?host=%s&service=%s&graph_width=%d&graph_height=%d&first=%d&count=%d", cgiurl, hostname, rrdservicename, gwidth, gheight, first, step); } else { sprintf(svcurl, "%s/showgraph.sh?host=%s&service=%s&graph_width=%d&graph_height=%d", cgiurl, hostname, rrdservicename, gwidth, gheight); } strcat(svcurl, "&disp="); strcat(svcurl, urlencode(dispname ? dispname : hostname)); if (nostale == HG_WITHOUT_STALE_RRDS) strcat(svcurl, "&nostale"); if (bgcolor != -1) sprintf(svcurl+strlen(svcurl), "&color=%s", colorname(bgcolor)); sprintf(svcurl+strlen(svcurl), "&graph_start=%d&graph_end=%d", (int)starttime, (int)endtime); sprintf(rrdparturl, fmt, rrdservicename, svcurl, svcurl, rrdservicename, svcurl, xgetenv("XYMONSKIN")); if ((strlen(rrdparturl) + strlen(rrdurl) + 1) >= rrdurlsize) { rrdurlsize += (4096 + rrdparturlsize); rrdurl = (char *) realloc(rrdurl, rrdurlsize); } strcat(rrdurl, rrdparturl); first += step; } while (first <= itemcount); xfree(rrdparturl); } dbgprintf("URLtext: %s\n", rrdurl); xfree(svcurl); MEMUNDEFINE(rrdservicename); return rrdurl; } char *xymon_graph_data(char *hostname, char *dispname, char *service, int bgcolor, xymongraph_t *graphdef, int itemcount, hg_stale_rrds_t nostale, hg_link_t wantmeta, int locatorbased, time_t starttime, time_t endtime) { return xymon_graph_text(hostname, dispname, service, bgcolor, graphdef, itemcount, nostale, ((wantmeta == HG_META_LINK) ? metafmt : xymonlinkfmt), locatorbased, starttime, endtime); } rrdtpldata_t *setup_template(char *params[]) { int i; rrdtpldata_t *result; rrdtplnames_t *nam; int dsindex = 1; result = (rrdtpldata_t *)calloc(1, sizeof(rrdtpldata_t)); result->dsnames = xtreeNew(strcmp); for (i = 0; (params[i]); i++) { if (strncasecmp(params[i], "DS:", 3) == 0) { char *pname, *pend; pname = params[i] + 3; pend = strchr(pname, ':'); if (pend) { int plen = (pend - pname); nam = (rrdtplnames_t *)calloc(1, sizeof(rrdtplnames_t)); nam->idx = dsindex++; if (result->template == NULL) { result->template = (char *)malloc(plen + 1); *result->template = '\0'; nam->dsnam = (char *)malloc(plen+1); strncpy(nam->dsnam, pname, plen); nam->dsnam[plen] = '\0'; } else { /* Hackish way of getting the colon delimiter */ pname--; plen++; result->template = (char *)realloc(result->template, strlen(result->template) + plen + 1); nam->dsnam = (char *)malloc(plen); strncpy(nam->dsnam, pname+1, plen-1); nam->dsnam[plen-1] = '\0'; } strncat(result->template, pname, plen); xtreeAdd(result->dsnames, nam->dsnam, nam); } } } return result; } xymon-4.3.7/lib/tree.h0000664000175000017500000000334311630612042014077 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for tree-based record storage. */ /* */ /* Copyright (C) 2011-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __TREE_H__ #define __TREE_H__ typedef enum { XTREE_STATUS_OK, XTREE_STATUS_MEM_EXHAUSTED, XTREE_STATUS_DUPLICATE_KEY, XTREE_STATUS_KEY_NOT_FOUND, XTREE_STATUS_NOTREE } xtreeStatus_t; #define xtreeEnd(X) (-1) typedef int xtreePos_t; extern void *xtreeNew(int(*xtreeCompare)(const char *a, const char *b)); extern void xtreeDestroy(void *treehandle); extern xtreeStatus_t xtreeAdd(void *treehandle, char *key, void *userdata); extern void *xtreeDelete(void *treehandle, char *key); extern xtreePos_t xtreeFind(void *treehandle, char *key); extern xtreePos_t xtreeFirst(void *treehandle); extern xtreePos_t xtreeNext(void *treehandle, xtreePos_t pos); extern char *xtreeKey(void *treehandle, xtreePos_t pos); extern void *xtreeData(void *treehandle, xtreePos_t pos); #endif xymon-4.3.7/lib/loadhosts_file.c0000664000175000017500000003145111630773353016150 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module for Xymon, responsible for loading the hosts.cfg */ /* file and keeping track of what hosts are known, their aliases and planned */ /* downtime settings etc. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid_file[] = "$Id: loadhosts_file.c 6751 2011-09-04 22:08:43Z storner $"; static int get_page_name_title(char *buf, char *key, char **name, char **title) { *name = *title = NULL; *name = buf + strlen(key); *name += strspn(*name, " \t\r\n"); if (strlen(*name) > 0) { /* (*name) now points at the start of the name. Find end of name */ *title = *name; *title += strcspn(*title, " \t\r\n"); /* Null-terminate the name */ **title = '\0'; (*title)++; *title += strspn(*title, " \t\r\n"); return 0; } return 1; } static int pagematch(pagelist_t *pg, char *name) { char *p = strrchr(pg->pagepath, '/'); if (p) { return (strcmp(p+1, name) == 0); } else { return (strcmp(pg->pagepath, name) == 0); } } static strbuffer_t *contentbuffer = NULL; static int prepare_fromfile(char *hostsfn, char *extrainclude) { static void *hostfiles = NULL; FILE *hosts; strbuffer_t *inbuf; /* First check if there were no modifications at all */ if (hostfiles) { if (!stackfmodified(hostfiles)){ return 1; } else { stackfclist(&hostfiles); hostfiles = NULL; } } if (!contentbuffer) contentbuffer = newstrbuffer(0); clearstrbuffer(contentbuffer); hosts = stackfopen(hostsfn, "r", &hostfiles); if (hosts == NULL) return -1; inbuf = newstrbuffer(0); while (stackfgets(inbuf, extrainclude)) { sanitize_input(inbuf, 0, 0); addtostrbuffer(contentbuffer, inbuf); addtobuffer(contentbuffer, "\n"); } stackfclose(hosts); freestrbuffer(inbuf); return 0; } static int prepare_fromnet(void) { static char contentmd5[33] = { '\0', }; sendreturn_t *sres; sendresult_t sendstat; char *fdata, *fhash; sres = newsendreturnbuf(1, NULL); sendstat = sendmessage("config hosts.cfg", NULL, XYMON_TIMEOUT, sres); if (sendstat != XYMONSEND_OK) { errprintf("Cannot load hosts.cfg from xymond, code %d\n", sendstat); return -1; } fdata = getsendreturnstr(sres, 1); fhash = md5hash(fdata); if (strcmp(contentmd5, fhash) == 0) { /* No changes */ xfree(fdata); return 1; } if (contentbuffer) freestrbuffer(contentbuffer); contentbuffer = convertstrbuffer(fdata, 0); strcpy(contentmd5, fhash); return 0; } char *hostscfg_content(void) { return strdup(STRBUF(contentbuffer)); } int load_hostnames(char *hostsfn, char *extrainclude, int fqdn) { /* Return value: 0 for load OK, 1 for "No files changed since last load", -1 for error (file not found) */ int prepresult; int ip1, ip2, ip3, ip4, groupid, pageidx; char hostname[4096], *dgname; pagelist_t *curtoppage, *curpage, *pgtail; namelist_t *nametail = NULL; void * htree; char *cfgdata, *inbol, *ineol, insavchar; load_hostinfo(NULL); if (*hostsfn == '!') prepresult = prepare_fromfile(hostsfn+1, extrainclude); else if (extrainclude) prepresult = prepare_fromfile(hostsfn, extrainclude); else if ((*hostsfn == '@') || (strcmp(hostsfn, xgetenv("HOSTSCFG")) == 0)) { prepresult = prepare_fromnet(); if (prepresult == -1) { errprintf("Failed to load from xymond, reverting to file-load\n"); prepresult = prepare_fromfile(xgetenv("HOSTSCFG"), extrainclude); } } else prepresult = prepare_fromfile(hostsfn, extrainclude); /* Did we get the data ? */ if (prepresult == -1) { errprintf("Cannot load host data\n"); return -1; } /* Any modifications at all ? */ if (prepresult == 1) { dbgprintf("No files modified, skipping reload of %s\n", hostsfn); return 1; } MEMDEFINE(hostname); MEMDEFINE(l); configloaded = 1; initialize_hostlist(); curpage = curtoppage = pgtail = pghead; pageidx = groupid = 0; dgname = NULL; htree = xtreeNew(strcasecmp); inbol = cfgdata = hostscfg_content(); while (inbol && *inbol) { inbol += strspn(inbol, " \t"); ineol = strchr(inbol, '\n'); if (ineol) { while ((ineol > inbol) && (isspace(*ineol) || (*ineol == '\n'))) ineol--; if (*ineol != '\n') ineol++; insavchar = *ineol; *ineol = '\0'; } if (strncmp(inbol, "page", 4) == 0) { pagelist_t *newp; char *name, *title; pageidx = groupid = 0; if (dgname) xfree(dgname); dgname = NULL; if (get_page_name_title(inbol, "page", &name, &title) == 0) { newp = (pagelist_t *)malloc(sizeof(pagelist_t)); newp->pagepath = strdup(name); newp->pagetitle = (title ? strdup(title) : NULL); newp->next = NULL; pgtail->next = newp; pgtail = newp; curpage = curtoppage = newp; } } else if (strncmp(inbol, "subpage", 7) == 0) { pagelist_t *newp; char *name, *title; pageidx = groupid = 0; if (dgname) xfree(dgname); dgname = NULL; if (get_page_name_title(inbol, "subpage", &name, &title) == 0) { newp = (pagelist_t *)malloc(sizeof(pagelist_t)); newp->pagepath = malloc(strlen(curtoppage->pagepath) + strlen(name) + 2); sprintf(newp->pagepath, "%s/%s", curtoppage->pagepath, name); newp->pagetitle = malloc(strlen(curtoppage->pagetitle) + strlen(title) + 2); sprintf(newp->pagetitle, "%s/%s", curtoppage->pagetitle, title); newp->next = NULL; pgtail->next = newp; pgtail = newp; curpage = newp; } } else if (strncmp(inbol, "subparent", 9) == 0) { pagelist_t *newp, *parent; char *pname, *name, *title; pageidx = groupid = 0; if (dgname) xfree(dgname); dgname = NULL; parent = NULL; if (get_page_name_title(inbol, "subparent", &pname, &title) == 0) { for (parent = pghead; (parent && !pagematch(parent, pname)); parent = parent->next); } if (parent && (get_page_name_title(title, "", &name, &title) == 0)) { newp = (pagelist_t *)malloc(sizeof(pagelist_t)); newp->pagepath = malloc(strlen(parent->pagepath) + strlen(name) + 2); sprintf(newp->pagepath, "%s/%s", parent->pagepath, name); newp->pagetitle = malloc(strlen(parent->pagetitle) + strlen(title) + 2); sprintf(newp->pagetitle, "%s/%s", parent->pagetitle, title); newp->next = NULL; pgtail->next = newp; pgtail = newp; curpage = newp; } } else if (strncmp(inbol, "group", 5) == 0) { char *tok, *inp; groupid++; if (dgname) xfree(dgname); dgname = NULL; tok = strtok(inbol, " \t"); if ((strcmp(tok, "group-only") == 0) || (strcmp(tok, "group-except") == 0)) { tok = strtok(NULL, " \t"); } if (tok) tok = strtok(NULL, "\r\n"); if (tok) { char *inp; /* Strip HTML tags from the string */ dgname = (char *)malloc(strlen(tok) + 1); *dgname = '\0'; inp = tok; while (*inp) { char *tagstart, *tagend; tagstart = strchr(inp, '<'); if (tagstart) { tagend = strchr(tagstart, '>'); *tagstart = '\0'; if (*inp) strcat(dgname, inp); if (tagend) { inp = tagend+1; } else { /* Unmatched '<', keep all of the string */ *tagstart = '<'; strcat(dgname, tagstart); inp += strlen(inp); } } else { strcat(dgname, inp); inp += strlen(inp); } } } } else if (sscanf(inbol, "%d.%d.%d.%d %s", &ip1, &ip2, &ip3, &ip4, hostname) == 5) { char *startoftags, *tag, *delim; int elemidx, elemsize; char clientname[4096]; char downtime[4096]; char groupidstr[10]; xtreePos_t handle; if ( (ip1 < 0) || (ip1 > 255) || (ip2 < 0) || (ip2 > 255) || (ip3 < 0) || (ip3 > 255) || (ip4 < 0) || (ip4 > 255)) { errprintf("Invalid IPv4-address for host %s (nibble outside 0-255 range): %d.%d.%d.%d\n", hostname, ip1, ip2, ip3, ip4); goto nextline; } namelist_t *newitem = calloc(1, sizeof(namelist_t)); namelist_t *iwalk, *iprev; MEMDEFINE(clientname); MEMDEFINE(downtime); /* Hostname beginning with '@' are "no-display" hosts. But we still want them. */ if (*hostname == '@') memmove(hostname, hostname+1, strlen(hostname)); if (!fqdn) { /* Strip any domain from the hostname */ char *p = strchr(hostname, '.'); if (p) *p = '\0'; } sprintf(newitem->ip, "%d.%d.%d.%d", ip1, ip2, ip3, ip4); sprintf(groupidstr, "%d", groupid); newitem->groupid = strdup(groupidstr); newitem->dgname = (dgname ? strdup(dgname) : strdup("NONE")); newitem->pageindex = pageidx++; newitem->hostname = strdup(hostname); if (ip1 || ip2 || ip3 || ip4) newitem->preference = 1; else newitem->preference = 0; newitem->logname = strdup(newitem->hostname); { char *p = newitem->logname; while ((p = strchr(p, '.')) != NULL) { *p = '_'; } } newitem->page = curpage; newitem->defaulthost = defaulthost; clientname[0] = downtime[0] = '\0'; startoftags = strchr(inbol, '#'); if (startoftags == NULL) startoftags = ""; else startoftags++; startoftags += strspn(startoftags, " \t\r\n"); newitem->allelems = strdup(startoftags); elemsize = 5; newitem->elems = (char **)malloc((elemsize+1)*sizeof(char *)); tag = newitem->allelems; elemidx = 0; while (tag && *tag) { if (elemidx == elemsize) { elemsize += 5; newitem->elems = (char **)realloc(newitem->elems, (elemsize+1)*sizeof(char *)); } newitem->elems[elemidx] = tag; /* Skip until we hit a whitespace or a quote */ tag += strcspn(tag, " \t\r\n\""); if (*tag == '"') { delim = tag; /* Hit a quote - skip until the next matching quote */ tag = strchr(tag+1, '"'); if (tag != NULL) { /* Found end-quote, NULL the item here and move on */ *tag = '\0'; tag++; } /* Now move quoted data one byte down (including the NUL) to kill quotechar */ memmove(delim, delim+1, strlen(delim)); } else if (*tag) { /* Normal end of item, NULL it and move on */ *tag = '\0'; tag++; } else { /* End of line - no more to do. */ tag = NULL; } /* * If we find a "noconn", drop preference value to 0. * If we find a "prefer", up reference value to 2. */ if ((newitem->preference == 1) && (strcmp(newitem->elems[elemidx], "noconn") == 0)) newitem->preference = 0; else if (strcmp(newitem->elems[elemidx], "prefer") == 0) newitem->preference = 2; /* Skip whitespace until start of next tag */ if (tag) tag += strspn(tag, " \t\r\n"); elemidx++; } newitem->elems[elemidx] = NULL; /* See if this host is defined before */ handle = xtreeFind(htree, newitem->hostname); if (strcasecmp(newitem->hostname, ".default.") == 0) { /* The pseudo DEFAULT host */ newitem->next = NULL; defaulthost = newitem; } else if (handle == xtreeEnd(htree)) { /* New item, so add to end of list */ newitem->next = NULL; if (namehead == NULL) namehead = nametail = newitem; else { nametail->next = newitem; nametail = newitem; } xtreeAdd(htree, newitem->hostname, newitem); } else { /* Find the existing record - compare the record pointer instead of the name */ namelist_t *existingrec = (namelist_t *)xtreeData(htree, handle); for (iwalk = namehead, iprev = NULL; ((iwalk != existingrec) && iwalk); iprev = iwalk, iwalk = iwalk->next) ; if (newitem->preference <= iwalk->preference) { /* Add after the existing (more preferred) entry */ newitem->next = iwalk->next; iwalk->next = newitem; } else { /* New item has higher preference, so add before the iwalk item (i.e. after iprev) */ if (iprev == NULL) { newitem->next = namehead; namehead = newitem; } else { newitem->next = iprev->next; iprev->next = newitem; } } } newitem->clientname = xmh_find_item(newitem, XMH_CLIENTALIAS); if (newitem->clientname == NULL) newitem->clientname = newitem->hostname; newitem->downtime = xmh_find_item(newitem, XMH_DOWNTIME); MEMUNDEFINE(clientname); MEMUNDEFINE(downtime); } nextline: if (ineol) { *ineol = insavchar; if (*ineol != '\n') ineol = strchr(ineol, '\n'); inbol = (ineol ? ineol+1 : NULL); } else inbol = NULL; } xfree(cfgdata); if (dgname) xfree(dgname); xtreeDestroy(htree); MEMUNDEFINE(hostname); MEMUNDEFINE(l); build_hosttree(); return 0; } xymon-4.3.7/lib/acklog.c0000664000175000017500000001622311630450173014401 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This file contains code to build the acknowledgement log shown on the */ /* "all non-green" page. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: acklog.c 6744 2011-09-03 16:06:19Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" int havedoneacklog = 0; void do_acklog(FILE *output, int maxcount, int maxminutes) { FILE *acklog; char acklogfilename[PATH_MAX]; time_t cutoff; struct stat st; char l[MAX_LINE_LEN]; char title[200]; ack_t *acks; int num, ackintime_count; havedoneacklog = 1; cutoff = ( (maxminutes) ? (getcurrenttime(NULL) - maxminutes*60) : 0); if ((!maxcount) || (maxcount > 100)) maxcount = 100; sprintf(acklogfilename, "%s/acknowledge.log", xgetenv("XYMONSERVERLOGS")); acklog = fopen(acklogfilename, "r"); if (!acklog) { /* BB compatible naming */ sprintf(acklogfilename, "%s/acklog", xgetenv("XYMONACKDIR")); acklog = fopen(acklogfilename, "r"); } if (!acklog) { /* If no acklog, that is OK - some people dont use acks */ dbgprintf("Cannot open acklog\n"); return; } /* HACK ALERT! */ if (stat(acklogfilename, &st) == 0) { if (st.st_size != 0) { /* Assume a log entry is max 150 bytes */ if (150*maxcount < st.st_size) { fseeko(acklog, -150*maxcount, SEEK_END); fgets(l, sizeof(l), acklog); if (strchr(l, '\n') == NULL) { errprintf("Oops - couldnt find a newline in acklog\n"); } } } } acks = (ack_t *) calloc(maxcount, sizeof(ack_t)); ackintime_count = num = 0; while (fgets(l, sizeof(l), acklog)) { char ackedby[MAX_LINE_LEN], hosttest[MAX_LINE_LEN], color[10], ackmsg[MAX_LINE_LEN]; char ackfn[PATH_MAX]; char *testname; void *hinfo; int ok; if (atol(l) >= cutoff) { int c_used; char *p, *p1, *xymondacker = NULL; unsigned int atim; sscanf(l, "%u\t%d\t%d\t%d\t%s\t%s\t%s\t%n", &atim, &acks[num].acknum, &acks[num].duration, &acks[num].acknum2, ackedby, hosttest, color, &c_used); acks[num].acktime = atim; p1 = ackmsg; for (p=l+c_used, p1=ackmsg; (*p); ) { /* * Need to de-code the ackmsg - it may have been entered * via a web page that did "%asciival" encoding. */ if ((*p == '%') && (strlen(p) >= 3) && isxdigit((int)*(p+1)) && isxdigit((int)*(p+2))) { char hexnum[3]; hexnum[0] = *(p+1); hexnum[1] = *(p+2); hexnum[2] = '\0'; *p1 = (char) strtol(hexnum, NULL, 16); p1++; p += 3; } else { *p1 = *p; p1++; p++; } } *p1 = '\0'; /* Xymon uses \n in the ack message, for the "acked by" data. Cut it off. */ nldecode(ackmsg); p = strchr(ackmsg, '\n'); if (p) { if (strncmp(p, "\nAcked by:", 10) == 0) xymondacker = p+10; *p = '\0'; } /* Show only the first 30 characters in message */ if (strlen(ackmsg) > 30) ackmsg[30] = '\0'; sprintf(ackfn, "%s/ack.%s", xgetenv("XYMONACKDIR"), hosttest); testname = strrchr(hosttest, '.'); if (testname) { *testname = '\0'; testname++; } else testname = "unknown"; ok = 1; /* Ack occurred within wanted timerange ? */ if (ok && (acks[num].acktime < cutoff)) ok = 0; /* Unknown host ? */ hinfo = hostinfo(hosttest); if (!hinfo) ok = 0; if (hinfo && xmh_item(hinfo, XMH_FLAG_NONONGREEN)) ok = 0; if (ok) { char *ackerp; /* If ack has expired or tag file is gone, the ack is no longer valid */ acks[num].ackvalid = 1; if ((acks[num].acktime + 60*acks[num].duration) < getcurrenttime(NULL)) acks[num].ackvalid = 0; if (acks[num].ackvalid && (stat(ackfn, &st) != 0)) acks[num].ackvalid = 0; if (strcmp(ackedby, "np_filename_not_used") != 0) { ackerp = ackedby; if (strncmp(ackerp, "np_", 3) == 0) ackerp += 3; p = strrchr(ackerp, '_'); if (p > ackerp) *p = '\0'; acks[num].ackedby = strdup(ackerp); } else if (xymondacker) { acks[num].ackedby = strdup(xymondacker); } else { acks[num].ackedby = ""; } acks[num].hostname = strdup(hosttest); acks[num].testname = strdup(testname); strcat(color, " "); acks[num].color = parse_color(color); acks[num].ackmsg = strdup(ackmsg); ackintime_count++; num = (num + 1) % maxcount; } } } if (ackintime_count > 0) { int firstack, lastack; int period = maxminutes; if (ackintime_count <= maxcount) { firstack = 0; lastack = ackintime_count-1; period = maxminutes; } else { firstack = num; lastack = ( (num == 0) ? maxcount : (num-1)); ackintime_count = maxcount; period = ((getcurrenttime(NULL)-acks[firstack].acktime) / 60); } sprintf(title, "%d events acknowledged in the past %u minutes", ackintime_count, period); fprintf(output, "

\n"); fprintf(output, "\n", title); fprintf(output, "\n"); fprintf(output, "\n", title); for (num = lastack; (ackintime_count); ackintime_count--, num = ((num == 0) ? (maxcount-1) : (num - 1)) ) { fprintf(output, "\n"); fprintf(output, "\n", ctime(&acks[num].acktime)); fprintf(output, "\n", colorname(acks[num].color), acks[num].hostname); fprintf(output, "\n", acks[num].testname); if (acks[num].color != -1) { fprintf(output, "\n", xgetenv("XYMONSKIN"), dotgiffilename(acks[num].color, acks[num].ackvalid, 1)); } else fprintf(output, "\n"); fprintf(output, "\n", acks[num].ackedby); fprintf(output, "\n", acks[num].ackmsg); } } else { sprintf(title, "No events acknowledged in the last %u minutes", maxminutes); fprintf(output, "

\n"); fprintf(output, "
%s
%s%s%s %s%s
\n", title); fprintf(output, "\n"); fprintf(output, "\n", title); } fprintf(output, "
%s
\n"); fclose(acklog); } xymon-4.3.7/lib/crondate.c0000664000175000017500000003767511615341300014750 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for time-specs in CRON format. */ /* */ /* Copyright (C) 2010-2011 Henrik Storner */ /* Copyright (C) 2010 Milan Kocian */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2 (see the file "COPYING" for details), except as listed below. */ /* */ /*----------------------------------------------------------------------------*/ /* * A large part of this file was adapted from the "cron" sources by Paul * Vixie. It contains this copyright notice: * ------------------------------------------------------------------------ * Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * ------------------------------------------------------------------------ * Adjusted by Milan Kocian so don't tease original autor * for my bugs. * * Major change is that functions operate on string instead on file. * And it's used only time part of cronline (no user, cmd, etc.) * Also, the file "bitstring.h" was used from the cron sources. This file * carries the following copyright notice: * ------------------------------------------------------------------------ * Copyright (c) 1989 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Paul Vixie. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of California, Berkeley. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. * * @(#)bitstring.h 5.2 (Berkeley) 4/4/90 * ------------------------------------------------------------------------ * */ static char rcsid[] = "$Id: crondate.c 6712 2011-07-31 21:01:52Z storner $"; /* -------------------- bitstring.h begins -----------------------------*/ #include #include #include #include #include typedef unsigned char bitstr_t; /* internal macros */ /* byte of the bitstring bit is in */ #define _bit_byte(bit) \ ((bit) >> 3) /* mask for the bit within its byte */ #define _bit_mask(bit) \ (1 << ((bit)&0x7)) /* external macros */ /* bytes in a bitstring of nbits bits */ #define bitstr_size(nbits) \ ((((nbits) - 1) >> 3) + 1) /* allocate a bitstring */ #define bit_alloc(nbits) \ (bitstr_t *)malloc(1, \ (unsigned int)bitstr_size(nbits) * sizeof(bitstr_t)) /* allocate a bitstring on the stack */ #define bit_decl(name, nbits) \ (name)[bitstr_size(nbits)] /* is bit N of bitstring name set? */ #define bit_test(name, bit) \ ((name)[_bit_byte(bit)] & _bit_mask(bit)) /* set bit N of bitstring name */ #define bit_set(name, bit) \ (name)[_bit_byte(bit)] |= _bit_mask(bit) /* clear bit N of bitstring name */ #define bit_clear(name, bit) \ (name)[_bit_byte(bit)] &= ~_bit_mask(bit) /* clear bits start ... stop in bitstring */ #define bit_nclear(name, start, stop) { \ register bitstr_t *_name = name; \ register int _start = start, _stop = stop; \ register int _startbyte = _bit_byte(_start); \ register int _stopbyte = _bit_byte(_stop); \ if (_startbyte == _stopbyte) { \ _name[_startbyte] &= ((0xff >> (8 - (_start&0x7))) | \ (0xff << ((_stop&0x7) + 1))); \ } else { \ _name[_startbyte] &= 0xff >> (8 - (_start&0x7)); \ while (++_startbyte < _stopbyte) \ _name[_startbyte] = 0; \ _name[_stopbyte] &= 0xff << ((_stop&0x7) + 1); \ } \ } /* set bits start ... stop in bitstring */ #define bit_nset(name, start, stop) { \ register bitstr_t *_name = name; \ register int _start = start, _stop = stop; \ register int _startbyte = _bit_byte(_start); \ register int _stopbyte = _bit_byte(_stop); \ if (_startbyte == _stopbyte) { \ _name[_startbyte] |= ((0xff << (_start&0x7)) & \ (0xff >> (7 - (_stop&0x7)))); \ } else { \ _name[_startbyte] |= 0xff << ((_start)&0x7); \ while (++_startbyte < _stopbyte) \ _name[_startbyte] = 0xff; \ _name[_stopbyte] |= 0xff >> (7 - (_stop&0x7)); \ } \ } /* find first bit clear in name */ #define bit_ffc(name, nbits, value) { \ register bitstr_t *_name = name; \ register int _byte, _nbits = nbits; \ register int _stopbyte = _bit_byte(_nbits), _value = -1; \ for (_byte = 0; _byte <= _stopbyte; ++_byte) \ if (_name[_byte] != 0xff) { \ _value = _byte << 3; \ for (_stopbyte = _name[_byte]; (_stopbyte&0x1); \ ++_value, _stopbyte >>= 1); \ break; \ } \ *(value) = _value; \ } /* find first bit set in name */ #define bit_ffs(name, nbits, value) { \ register bitstr_t *_name = name; \ register int _byte, _nbits = nbits; \ register int _stopbyte = _bit_byte(_nbits), _value = -1; \ for (_byte = 0; _byte <= _stopbyte; ++_byte) \ if (_name[_byte]) { \ _value = _byte << 3; \ for (_stopbyte = _name[_byte]; !(_stopbyte&0x1); \ ++_value, _stopbyte >>= 1); \ break; \ } \ *(value) = _value; \ } /* -------------------- end of bitstring.h ------------------------------- */ /* --------------------- crondate from Paul Vixie cron ------------------ */ #define TRUE 1 #define FALSE 0 #define MAX_TEMPSTR 1000 #define Skip_Blanks(c) \ while (*c == '\t' || *c == ' ') \ c++; #define Skip_Nonblanks(c) \ while (*c!='\t' && *c!=' ' && *c!='\n' && *c!='\0') \ c++; #define SECONDS_PER_MINUTE 60 #define FIRST_MINUTE 0 #define LAST_MINUTE 59 #define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1) #define FIRST_HOUR 0 #define LAST_HOUR 23 #define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1) #define FIRST_DOM 1 #define LAST_DOM 31 #define DOM_COUNT (LAST_DOM - FIRST_DOM + 1) #define FIRST_MONTH 1 #define LAST_MONTH 12 #define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1) /* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */ #define FIRST_DOW 0 #define LAST_DOW 7 #define DOW_COUNT (LAST_DOW - FIRST_DOW + 1) typedef struct { bitstr_t bit_decl(minute, MINUTE_COUNT); bitstr_t bit_decl(hour, HOUR_COUNT); bitstr_t bit_decl(dom, DOM_COUNT); bitstr_t bit_decl(month, MONTH_COUNT); bitstr_t bit_decl(dow, DOW_COUNT); int flags; #define DOM_STAR 0x01 #define DOW_STAR 0x02 #define WHEN_REBOOT 0x04 #define MIN_STAR 0x08 #define HR_STAR 0x10 } c_bits_t; #define PPC_NULL ((char **)NULL) static char *MonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; static char *DowNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", NULL }; static char * get_number(numptr, low, names, ch) int *numptr; /* where does the result go? */ int low; /* offset applied to result if symbolic enum used */ char *names[]; /* symbolic names, if any, for enums */ char *ch; /* current character */ { char temp[MAX_TEMPSTR], *pc; int len, i, all_digits; /* collect alphanumerics into our fixed-size temp array */ pc = temp; len = 0; all_digits = TRUE; while (isalnum((int)*ch)) { if (++len >= MAX_TEMPSTR) return(NULL); *pc++ = *ch; if (!isdigit((int)*ch)) all_digits = FALSE; ch++; } *pc = '\0'; if (len == 0) { return(NULL); } /* try to find the name in the name list */ if (names) { for (i = 0; names[i] != NULL; i++) { if (!strcasecmp(names[i], temp)) { *numptr = i+low; return ch; } } } /* no name list specified, or there is one and our string isn't * in it. either way: if it's all digits, use its magnitude. * otherwise, it's an error. */ if (all_digits) { *numptr = atoi(temp); return ch; } return(NULL) ; } static int set_element(bits, low, high, number) bitstr_t *bits; /* one bit per flag, default=FALSE */ int low; int high; int number; { if (number < low || number > high) return(-1); bit_set(bits, (number-low)); return(0); } static char * get_range(bits, low, high, names, ch, last) bitstr_t *bits; /* one bit per flag, default=FALSE */ int low, high; /* bounds, impl. offset for bitstr */ char *names[]; /* NULL or names of elements */ char *ch; /* current character being processed */ int last; /* processing last value */ { /* range = number | number "-" number [ "/" number ] */ register int i; auto int num1, num2, num3; if (*ch == '*') { /* '*' means "first-last" but can still be modified by /step */ num1 = low; num2 = high; ch++; if (!*ch) { if (!last) /* string is too short (if not last)*/ return(NULL); } } else { ch = get_number(&num1, low, names, ch); if (!ch) return (NULL); if (*ch != '-') { /* not a range, it's a single number. */ if (set_element(bits, low, high, num1)) return(NULL); return ch; } else { /* eat the dash */ ch++; if (!*ch) return(NULL); /* get the number following the dash */ ch = get_number(&num2, low, names, ch); if (!ch) return(NULL); } } /* check for step size */ if (*ch == '/') { /* eat the slash */ ch++; if (!*ch) return(NULL); /* get the step size -- note: we don't pass the * names here, because the number is not an * element id, it's a step size. 'low' is * sent as a 0 since there is no offset either. */ ch = get_number(&num3, 0, PPC_NULL, ch); if (!ch || num3 <= 0) return(NULL) ; } else { /* no step. default==1. */ num3 = 1; } /* Explicitly check for sane values. Certain combinations of ranges and * steps which should return EOF don't get picked up by the code below, * eg: * 5-64/30 * * * * touch /dev/null * * Code adapted from set_elements() where this error was probably intended * to be catched. */ if (num1 < low || num1 > high || num2 < low || num2 > high) return(NULL); /* range. set all elements from num1 to num2, stepping * by num3. (the step is a downward-compatible extension * proposed conceptually by bob@acornrc, syntactically * designed then implmented by paul vixie). */ for (i = num1; i <= num2; i += num3) if (set_element(bits, low, high, i)) return(NULL); return ch; } static char * get_list(bits, low, high, names, ch, last) bitstr_t *bits; /* one bit per flag, default=FALSE */ int low, high; /* bounds, impl. offset for bitstr */ char *names[]; /* NULL or *[] of names for these elements */ char *ch; /* current character being processed */ int last; /* processing last value */ { register int done; /* we know that we point to a non-blank character here; * must do a Skip_Blanks before we exit, so that the * next call (or the code that picks up the cmd) can * assume the same thing. */ /* clear the bit string, since the default is 'off'. */ bit_nclear(bits, 0, (high-low+1)); /* process all ranges */ done = FALSE; while (!done) { ch = get_range(bits, low, high, names, ch, last); if (ch && *ch == ',') ch++; else done = TRUE; } /* exiting. skip to some blanks, then skip over the blanks. */ if (ch) { Skip_Nonblanks(ch) Skip_Blanks(ch) } return ch; } /* parse cron time */ void * parse_cron_time(char * ch) { c_bits_t *e; e = (c_bits_t *) calloc(1, sizeof(c_bits_t)); if (!e) return(NULL); if (ch[0] == '@') { if (!strcmp("yearly", ch + 1) || !strcmp("annually", ch + 1)) { bit_set(e->minute, 0); bit_set(e->hour, 0); bit_set(e->dom, 0); bit_set(e->month, 0); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); e->flags |= DOW_STAR; } else if (!strcmp("monthly", ch + 1)) { bit_set(e->minute, 0); bit_set(e->hour, 0); bit_set(e->dom, 0); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); e->flags |= DOW_STAR; } else if (!strcmp("weekly", ch + 1)) { bit_set(e->minute, 0); bit_set(e->hour, 0); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); e->flags |= DOM_STAR; bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0,0); } else if (!strcmp("daily", ch + 1) || !strcmp("midnight", ch + 1)) { bit_set(e->minute, 0); bit_set(e->hour, 0); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); } else if (!strcmp("hourly", ch + 1)) { bit_set(e->minute, 0); bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); e->flags |= HR_STAR; } else { free(e); return(NULL); } } else { /* end of '@' and begin for * * .. */ if (*ch == '*') e->flags |= MIN_STAR; ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, PPC_NULL, ch, 0); if (!ch) { free(e); return(NULL); } /* hours */ if (*ch == '*') e->flags |= HR_STAR; ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, PPC_NULL, ch, 0); if (!ch) { free(e); return(NULL); } /* DOM (days of month) */ if (*ch == '*') e->flags |= DOM_STAR; ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, 0); if (!ch) { free(e); return(NULL); } /* month */ ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, MonthNames, ch, 0); if (!ch) { free(e); return(NULL); } /* DOW (days of week) */ if (*ch == '*') e->flags |= DOW_STAR; ch = get_list(e->dow, FIRST_DOW, LAST_DOW, DowNames, ch, 1); if (!ch) { free(e); return(NULL); } } /* make sundays equivilent */ if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { bit_set(e->dow, 0); bit_set(e->dow, 7); } /* end of * * ... parse */ return e; } /* END of cron date-time parser */ /*----------------- End of code from Paul Vixie's cron sources ----------------*/ void crondatefree(void *vcdate) { c_bits_t *cdate = (c_bits_t *)vcdate; free(cdate); } static int minute=-1, hour=-1, dom=-1, month=-1, dow=-1; void crongettime(void) { time_t now; struct tm tt; now = time(NULL); /* we need real clock, not monotonic from gettimer */ localtime_r(&now, &tt); minute = tt.tm_min -FIRST_MINUTE; hour = tt.tm_hour -FIRST_HOUR; dom = tt.tm_mday -FIRST_DOM; month = tt.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; dow = tt.tm_wday -FIRST_DOW; } int cronmatch(void *vcdate) { c_bits_t *cdate = (c_bits_t *)vcdate; if (minute == -1) crongettime(); return cdate && bit_test(cdate->minute, minute) && bit_test(cdate->hour, hour) && bit_test(cdate->month, month) && ( ((cdate->flags & DOM_STAR) || (cdate->flags & DOW_STAR)) ? (bit_test(cdate->dow,dow) && bit_test(cdate->dom,dom)) : (bit_test(cdate->dow,dow) || bit_test(cdate->dom,dom))); } xymon-4.3.7/lib/loadcriticalconf.c0000664000175000017500000003067211630612042016440 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module for Xymon, responsible for loading the */ /* critical.cfg file. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: loadcriticalconf.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #include #include #include #include #include #include #include #include "libxymon.h" static void * rbconf; static char *defaultfn = NULL; static char *configfn = NULL; static void flushrec(void *k1, void *k2) { char *key; key = (char *)k1; if (*(key + strlen(key) - 1) == '=') { /* Clone record just holds a char string pointing to the origin record */ char *pointsto = (char *)k2; xfree(pointsto); } else { /* Full record */ critconf_t *rec = (critconf_t *)k2; if (rec->crittime) xfree(rec->crittime); if (rec->ttgroup) xfree(rec->ttgroup); if (rec->ttextra) xfree(rec->ttextra); } xfree(key); } int load_critconfig(char *fn) { static void *configfiles = NULL; static int firsttime = 1; FILE *fd; strbuffer_t *inbuf; /* Setup the default configuration filename */ if (!fn) { if (!defaultfn) { char *xymonhome = xgetenv("XYMONHOME"); defaultfn = (char *)malloc(strlen(xymonhome) + strlen(DEFAULT_CRITCONFIGFN) + 2); sprintf(defaultfn, "%s/%s", xymonhome, DEFAULT_CRITCONFIGFN); } fn = defaultfn; } if (configfn && (strcmp(fn, configfn) != 0)) { /* Force full reload - it's a different config file */ if (configfiles) { stackfclist(&configfiles); configfiles = NULL; } } if (configfn) xfree(configfn); configfn = strdup(fn); /* First check if there were no modifications at all */ if (configfiles) { if (!stackfmodified(configfiles)){ dbgprintf("No files modified, skipping reload of %s\n", fn); return 0; } else { stackfclist(&configfiles); configfiles = NULL; } } if (!firsttime) { /* Clean up existing datatree */ xtreePos_t handle; for (handle = xtreeFirst(rbconf); (handle != xtreeEnd(rbconf)); handle = xtreeNext(rbconf, handle)) { flushrec(xtreeKey(rbconf, handle), xtreeData(rbconf, handle)); } xtreeDestroy(rbconf); } firsttime = 0; rbconf = xtreeNew(strcasecmp); fd = stackfopen(fn, "r", &configfiles); if (fd == NULL) return 1; inbuf = newstrbuffer(0); while (stackfgets(inbuf, NULL)) { /* Full record : Host service START END TIMESPEC TTPrio TTGroup TTExtra */ /* Clone record: Host =HOST */ char *ehost, *eservice, *estart, *eend, *etime, *ttgroup, *ttextra, *updinfo; int ttprio = 0; critconf_t *newitem; xtreeStatus_t status; int idx = 0; ehost = gettok(STRBUF(inbuf), "|\n"); if (!ehost) continue; eservice = gettok(NULL, "|\n"); if (!eservice) continue; if (*eservice == '=') { char *key = (char *)malloc(strlen(ehost) + 2); char *pointsto = strdup(eservice+1); sprintf(key, "%s=", ehost); status = xtreeAdd(rbconf, key, pointsto); } else { estart = gettok(NULL, "|\n"); if (!estart) continue; eend = gettok(NULL, "|\n"); if (!eend) continue; etime = gettok(NULL, "|\n"); if (!etime) continue; ttprio = atoi(gettok(NULL, "|\n")); if (ttprio == 0) continue; ttgroup = gettok(NULL, "|\n"); ttextra = gettok(NULL, "|\n"); updinfo = gettok(NULL, "|\n"); newitem = (critconf_t *)malloc(sizeof(critconf_t)); newitem->key = (char *)malloc(strlen(ehost) + strlen(eservice) + 15); sprintf(newitem->key, "%s|%s", ehost, eservice); newitem->starttime= ((estart && *estart) ? atoi(estart) : 0); newitem->endtime = ((eend && *eend) ? atoi(eend) : 0); newitem->crittime = ((etime && *etime) ? strdup(etime) : NULL); newitem->priority = ttprio; newitem->ttgroup = strdup(ttgroup); newitem->ttextra = strdup(ttextra); newitem->updinfo = strdup(updinfo); status = xtreeAdd(rbconf, newitem->key, newitem); while (status == XTREE_STATUS_DUPLICATE_KEY) { idx++; sprintf(newitem->key, "%s|%s|%d", ehost, eservice, idx); status = xtreeAdd(rbconf, newitem->key, newitem); } } } stackfclose(fd); freestrbuffer(inbuf); if (debug) { xtreePos_t handle; handle = xtreeFirst(rbconf); while (handle != xtreeEnd(rbconf)) { printf("%s\n", (char *)xtreeKey(rbconf, handle)); handle = xtreeNext(rbconf, handle); } } return 0; } static xtreePos_t findrec(char *key) { xtreePos_t handle; handle = xtreeFind(rbconf, key); if (handle == xtreeEnd(rbconf)) { /* Check if there's a clone pointer record */ char *clonekey, *p; clonekey = strdup(key); p = strchr(clonekey, '|'); if (p && *(p+1)) { *p = '='; *(p+1) = '\0'; } handle = xtreeFind(rbconf, clonekey); xfree(clonekey); if (handle != xtreeEnd(rbconf)) { char *pointsto; char *service; /* Get the origin record for this cloned record, using the same service name */ pointsto = (char *)xtreeData(rbconf, handle); service = strchr(key, '|'); if (service) service++; clonekey = (char *)malloc(strlen(pointsto) + strlen(service) + 2); sprintf(clonekey, "%s|%s", pointsto, service); handle = xtreeFind(rbconf, clonekey); xfree(clonekey); } } return handle; } static int timecheck(time_t starttime, time_t endtime, char *crittime) { time_t now = getcurrenttime(NULL); if (starttime && (now < starttime)) return 0; if (endtime && (now > endtime)) return 0; if ((crittime == NULL) || within_sla(NULL, crittime, 0)) return 1; /* FIXME */ return 0; } critconf_t *get_critconfig(char *key, int flags, char **resultkey) { static xtreePos_t handle; static char *realkey = NULL; critconf_t *result = NULL; int isclone; if (resultkey) *resultkey = NULL; switch (flags) { case CRITCONF_TIMEFILTER: handle = findrec(key); /* We may have hit a cloned record, so use the real key for further searches */ if (handle != xtreeEnd(rbconf)) { realkey = (char *)xtreeKey(rbconf, handle); } while (handle != xtreeEnd(rbconf)) { result = (critconf_t *)xtreeData(rbconf, handle); if (timecheck(result->starttime, result->endtime, result->crittime)) return result; /* Go to the next */ handle = xtreeNext(rbconf, handle); if (handle != xtreeEnd(rbconf)) { critconf_t *rec = (critconf_t *)xtreeData(rbconf, handle); if (strncmp(realkey, rec->key, strlen(realkey)) != 0) handle=xtreeEnd(rbconf); } } realkey = NULL; break; case CRITCONF_FIRSTMATCH: handle = findrec(key); realkey = NULL; if (handle != xtreeEnd(rbconf)) { realkey = (char *)xtreeKey(rbconf, handle); } break; case CRITCONF_FIRST: realkey = NULL; handle = xtreeFirst(rbconf); if (handle == xtreeEnd(rbconf)) return NULL; do { realkey = (char *)xtreeKey(rbconf, handle); isclone = (*(realkey + strlen(realkey) - 1) == '='); if (isclone) handle = xtreeNext(rbconf, handle); } while (isclone && (handle != xtreeEnd(rbconf))); break; case CRITCONF_NEXT: if (!realkey || (handle == xtreeEnd(rbconf))) return NULL; isclone = 1; while (isclone && (handle != xtreeEnd(rbconf))) { handle = xtreeNext(rbconf, handle); if (handle) { realkey = (char *)xtreeKey(rbconf, handle); isclone = (*(realkey + strlen(realkey) - 1) == '='); } } break; case CRITCONF_RAW_FIRST: handle = xtreeFirst(rbconf); realkey = NULL; break; case CRITCONF_RAW_NEXT: handle = xtreeNext(rbconf, handle); realkey = NULL; break; case CRITCONF_FIRSTHOSTMATCH: do { int found = 0; char *delim; realkey = NULL; handle = xtreeFirst(rbconf); while (!found && (handle != xtreeEnd(rbconf))) { realkey = (char *)xtreeKey(rbconf, handle); delim = realkey + strlen(key); /* OK even if past end of realkey */ found = ((strncmp(realkey, key, strlen(key)) == 0) && ((*delim == '|') || (*delim == '='))); if (!found) { handle = xtreeNext(rbconf, handle); realkey = NULL; } } if ((handle != xtreeEnd(rbconf)) && (*(realkey + strlen(realkey) - 1) == '=')) { key = (char *)xtreeData(rbconf, handle); isclone = 1; } else isclone = 0; } while (isclone && (handle != xtreeEnd(rbconf))); break; } if (handle == xtreeEnd(rbconf)) { realkey = NULL; return NULL; } if (resultkey) *resultkey = (char *)xtreeKey(rbconf, handle); result = (critconf_t *)xtreeData(rbconf, handle); return result; } int update_critconfig(critconf_t *rec) { char *bakfn; FILE *bakfd; unsigned char buf[8192]; int n; struct stat st; struct utimbuf ut; xtreePos_t handle; FILE *fd; int result = 0; /* First, copy the old file */ bakfn = (char *)malloc(strlen(configfn) + 5); sprintf(bakfn, "%s.bak", configfn); if (stat(configfn, &st) == 0) { ut.actime = st.st_atime; ut.modtime = st.st_mtime; } else ut.actime = ut.modtime = getcurrenttime(NULL); fd = fopen(configfn, "r"); if (fd) { bakfd = fopen(bakfn, "w"); if (bakfd) { while ((n = fread(buf, 1, sizeof(buf), fd)) > 0) fwrite(buf, 1, n, bakfd); fclose(bakfd); utime(bakfn, &ut); } fclose(fd); } xfree(bakfn); fd = fopen(configfn, "w"); if (fd == NULL) { errprintf("Cannot open output file %s\n", configfn); return 1; } if (rec) { handle = xtreeFind(rbconf, rec->key); if (handle == xtreeEnd(rbconf)) xtreeAdd(rbconf, rec->key, rec); } handle = xtreeFirst(rbconf); while (handle != xtreeEnd(rbconf)) { char *onekey; onekey = (char *)xtreeKey(rbconf, handle); if (*(onekey + strlen(onekey) - 1) == '=') { char *pointsto = (char *)xtreeData(rbconf, handle); char *hostname; hostname = strdup(onekey); *(hostname + strlen(hostname) - 1) = '\0'; fprintf(fd, "%s|=%s\n", hostname, pointsto); } else { critconf_t *onerec = (critconf_t *)xtreeData(rbconf, handle); char startstr[20], endstr[20]; *startstr = *endstr = '\0'; if (onerec->starttime > 0) sprintf(startstr, "%d", (int)onerec->starttime); if (onerec->endtime > 0) sprintf(endstr, "%d", (int)onerec->endtime); fprintf(fd, "%s|%s|%s|%s|%d|%s|%s|%s\n", onekey, startstr, endstr, (onerec->crittime ? onerec->crittime : ""), onerec->priority, (onerec->ttgroup ? onerec->ttgroup : ""), (onerec->ttextra ? onerec->ttextra : ""), (onerec->updinfo ? onerec->updinfo : "")); } handle = xtreeNext(rbconf, handle); } fclose(fd); return result; } void addclone_critconfig(char *origin, char *newclone) { char *newkey; xtreePos_t handle; newkey = (char *)malloc(strlen(newclone) + 2); sprintf(newkey, "%s=", newclone); handle = xtreeFind(rbconf, newkey); if (handle != xtreeEnd(rbconf)) dropclone_critconfig(newclone); xtreeAdd(rbconf, newkey, strdup(origin)); } void dropclone_critconfig(char *drop) { xtreePos_t handle; char *key; char *dropkey, *dropsrc; key = (char *)malloc(strlen(drop) + 2); sprintf(key, "%s=", drop); handle = xtreeFind(rbconf, key); if (handle == xtreeEnd(rbconf)) return; dropkey = (char *)xtreeKey(rbconf, handle); dropsrc = (char *)xtreeDelete(rbconf, key); xfree(dropkey); xfree(dropsrc); xfree(key); } int delete_critconfig(char *dropkey, int evenifcloned) { xtreePos_t handle; handle = xtreeFind(rbconf, dropkey); if (handle == xtreeEnd(rbconf)) return 0; if (!evenifcloned) { /* Check if this record has any clones attached to it */ char *hostname, *p; hostname = strdup(dropkey); p = strchr(hostname, '|'); if (p) *p = '\0'; handle = xtreeFirst(rbconf); while (handle != xtreeEnd(rbconf)) { char *key, *ptr; key = (char *)xtreeKey(rbconf, handle); ptr = (char *)xtreeData(rbconf, handle); if ((*(key + strlen(key) - 1) == '=') && (strcmp(hostname, ptr) == 0)) { xfree(hostname); return 1; } handle = xtreeNext(rbconf, handle); } xfree(hostname); } handle = xtreeFind(rbconf, dropkey); if (handle != xtreeEnd(rbconf)) { void *k1, *k2; k1 = xtreeKey(rbconf, handle); k2 = xtreeDelete(rbconf, dropkey); flushrec(k1, k2); } return 0; } xymon-4.3.7/lib/misc.c0000664000175000017500000004721311615341300014071 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains miscellaneous routines. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: misc.c 6712 2011-07-31 21:01:52Z storner $"; #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include /* Someday I'll move to GNU Autoconf for this ... */ #endif #include #include #include "libxymon.h" #include "version.h" enum ostype_t get_ostype(char *osname) { char *nam; enum ostype_t result = OS_UNKNOWN; int n; if (!osname || (*osname == '\0')) return OS_UNKNOWN; n = strspn(osname, "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-/_"); nam = (char *)malloc(n+1); strncpy(nam, osname, n); *(nam+n) = '\0'; if (strcasecmp(nam, "solaris") == 0) result = OS_SOLARIS; else if (strcasecmp(nam, "sunos") == 0) result = OS_SOLARIS; else if (strcasecmp(nam, "hpux") == 0) result = OS_HPUX; else if (strcasecmp(nam, "hp-ux") == 0) result = OS_HPUX; else if (strcasecmp(nam, "aix") == 0) result = OS_AIX; else if (strcasecmp(nam, "osf") == 0) result = OS_OSF; else if (strcasecmp(nam, "osf1") == 0) result = OS_OSF; else if (strcasecmp(nam, "win32") == 0) result = OS_WIN32; else if (strcasecmp(nam, "hmdc") == 0) result = OS_WIN32_HMDC; else if (strcasecmp(nam, "bbwin") == 0) result = OS_WIN32_BBWIN; else if (strcasecmp(nam, "powershell") == 0) result = OS_WIN_POWERSHELL; else if (strcasecmp(nam, "freebsd") == 0) result = OS_FREEBSD; else if (strcasecmp(nam, "netbsd") == 0) result = OS_NETBSD; else if (strcasecmp(nam, "openbsd") == 0) result = OS_OPENBSD; else if (strcasecmp(nam, "debian3") == 0) result = OS_LINUX22; else if (strcasecmp(nam, "linux22") == 0) result = OS_LINUX22; else if (strcasecmp(nam, "linux") == 0) result = OS_LINUX; else if (strcasecmp(nam, "redhat") == 0) result = OS_LINUX; else if (strcasecmp(nam, "debian") == 0) result = OS_LINUX; else if (strcasecmp(nam, "suse") == 0) result = OS_LINUX; else if (strcasecmp(nam, "mandrake") == 0) result = OS_LINUX; else if (strcasecmp(nam, "redhatAS") == 0) result = OS_LINUX; else if (strcasecmp(nam, "redhatES") == 0) result = OS_RHEL3; else if (strcasecmp(nam, "rhel3") == 0) result = OS_RHEL3; else if (strcasecmp(nam, "snmp") == 0) result = OS_SNMP; else if (strcasecmp(nam, "snmpnetstat") == 0) result = OS_SNMP; else if (strncasecmp(nam, "irix", 4) == 0) result = OS_IRIX; else if (strcasecmp(nam, "macosx") == 0) result = OS_DARWIN; else if (strcasecmp(nam, "darwin") == 0) result = OS_DARWIN; else if (strcasecmp(nam, "sco_sv") == 0) result = OS_SCO_SV; else if (strcasecmp(nam, "unixware") == 0) result = OS_SCO_SV; else if (strcasecmp(nam, "netware_snmp") == 0) result = OS_NETWARE_SNMP; else if (strcasecmp(nam, "zvm") == 0) result = OS_ZVM; else if (strcasecmp(nam, "zvse") == 0) result = OS_ZVSE; else if (strcasecmp(nam, "zos") == 0) result = OS_ZOS; else if (strcasecmp(nam, "snmpcollect") == 0) result = OS_SNMPCOLLECT; else if (strcasecmp(nam, "mqcollect") == 0) result = OS_MQCOLLECT; else if (strcasecmp(nam, "gnu/kfreebsd") == 0) result = OS_GNUKFREEBSD; if (result == OS_UNKNOWN) dbgprintf("Unknown OS: '%s'\n", osname); xfree(nam); return result; } char *osname(enum ostype_t os) { switch (os) { case OS_SOLARIS: return "solaris"; case OS_HPUX: return "hpux"; case OS_AIX: return "aix"; case OS_OSF: return "osf"; case OS_WIN32: return "win32"; case OS_WIN32_HMDC: return "hmdc"; case OS_WIN32_BBWIN: return "bbwin"; case OS_WIN_POWERSHELL: return "powershell"; case OS_FREEBSD: return "freebsd"; case OS_NETBSD: return "netbsd"; case OS_OPENBSD: return "openbsd"; case OS_LINUX22: return "linux22"; case OS_LINUX: return "linux"; case OS_RHEL3: return "rhel3"; case OS_SNMP: return "snmp"; case OS_IRIX: return "irix"; case OS_DARWIN: return "darwin"; case OS_SCO_SV: return "sco_sv"; case OS_NETWARE_SNMP: return "netware_snmp"; case OS_ZVM: return "zvm"; case OS_ZVSE: return "zvse"; case OS_ZOS: return "zos"; case OS_SNMPCOLLECT: return "snmpcollect"; case OS_MQCOLLECT: return "mqcollect"; case OS_GNUKFREEBSD: return "gnu/kfreebsd"; case OS_UNKNOWN: return "unknown"; } return "unknown"; } int hexvalue(unsigned char c) { switch (c) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': return 10; case 'A': return 10; case 'b': return 11; case 'B': return 11; case 'c': return 12; case 'C': return 12; case 'd': return 13; case 'D': return 13; case 'e': return 14; case 'E': return 14; case 'f': return 15; case 'F': return 15; } return -1; } char *commafy(char *hostname) { static char *s = NULL; char *p; if (s == NULL) { s = strdup(hostname); } else if (strlen(hostname) > strlen(s)) { xfree(s); s = strdup(hostname); } else { strcpy(s, hostname); } for (p = strchr(s, '.'); (p); p = strchr(s, '.')) *p = ','; return s; } void uncommafy(char *hostname) { char *p; p = hostname; while ((p = strchr(p, ',')) != NULL) *p = '.'; } char *skipword(char *l) { return l + strcspn(l, " \t"); } char *skipwhitespace(char *l) { return l + strspn(l, " \t"); } int argnmatch(char *arg, char *match) { return (strncmp(arg, match, strlen(match)) == 0); } char *msg_data(char *msg) { /* Find the start position of the data following the "status host.test " message */ char *result; if (!msg || (*msg == '\0')) return msg; result = strchr(msg, '.'); /* Hits the '.' in "host.test" */ if (!result) { dbgprintf("Msg was not what I expected: '%s'\n", msg); return msg; } result += strcspn(result, " \t\n"); /* Skip anything until we see a space, TAB or NL */ result += strspn(result, " \t"); /* Skip all whitespace */ return result; } char *gettok(char *s, char *delims) { /* * This works like strtok(), but can handle empty fields. */ static char *source = NULL; static char *whereat = NULL; int n; char *result; if ((delims == NULL) || (*delims == '\0')) return NULL; /* Sanity check */ if ((source == NULL) && (s == NULL)) return NULL; /* Programmer goofed and called us first time with NULL */ if (s) source = whereat = s; /* First call */ if (*whereat == '\0') { /* End of string ... clear local state and return NULL */ source = whereat = NULL; return NULL; } n = strcspn(whereat, delims); if (n == 0) { /* An empty token */ whereat++; result = ""; } else if (n == strlen(whereat)) { /* Last token */ result = whereat; whereat += n; } else { /* Mid-string token - null-teminate the token */ *(whereat + n) = '\0'; result = whereat; /* Move past this token and the delimiter */ whereat += (n+1); } return result; } char *wstok(char *s) { /* * This works like strtok(s, " \t"), but can handle quoted fields. */ static char *source = NULL; static char *whereat = NULL; int n; char *result; if ((source == NULL) && (s == NULL)) return NULL; if (s) source = whereat = s + strspn(s, " \t"); /* First call */ if (*whereat == '\0') { /* End of string ... clear local state and return NULL */ source = whereat = NULL; return NULL; } n = 0; do { n += strcspn(whereat+n, " \t\""); if (*(whereat+n) == '"') { char *p = strchr(whereat+n+1, '"'); if (!p) n = strlen(whereat); else n = (p - whereat) + 1; } } while (*(whereat+n) && (*(whereat+n) != ' ') && (*(whereat+n) != '\t')); if (n == strlen(whereat)) { /* Last token */ result = whereat; whereat += n; } else { /* Mid-string token - null-teminate the token */ *(whereat + n) = '\0'; result = whereat; /* Move past this token and the delimiter */ whereat += (n+1); whereat += strspn(whereat, " \t"); } /* Strip leading/trailing quote */ { char *p; if (*result == '"') result++; p = result + strlen(result) - 1; if (*p == '"') *p = '\0'; } return result; } void sanitize_input(strbuffer_t *l, int stripcomment, int unescape) { int i; /* * This routine sanitizes an input line, stripping off leading/trailing whitespace. * If requested, it also strips comments. * If requested, it also un-escapes \-escaped charactes. */ /* Kill comments */ if (stripcomment || unescape) { char *p, *commentstart = NULL; char *noquotemarkers = (unescape ? "\"'#\\" : "\"'#"); char *inquotemarkers = (unescape ? "\"'\\" : "\"'"); int inquote = 0; p = STRBUF(l) + strcspn(STRBUF(l), noquotemarkers); while (*p && (commentstart == NULL)) { switch (*p) { case '\\': if (inquote) p += 2+strcspn(p+2, inquotemarkers); else p += 2+strcspn(p+2, noquotemarkers); break; case '"': case '\'': inquote = (1 - inquote); if (inquote) p += 1+strcspn(p+1, inquotemarkers); else p += 1+strcspn(p+1, noquotemarkers); break; case '#': if (!inquote) commentstart = p; break; } } if (commentstart) strbufferchop(l, STRBUFLEN(l) - (commentstart - STRBUF(l))); } /* Kill a trailing CR/NL */ i = strcspn(STRBUF(l), "\r\n"); if (i != STRBUFLEN(l)) strbufferchop(l, STRBUFLEN(l)-i); /* Kill trailing whitespace */ i = STRBUFLEN(l); while ((i > 0) && isspace((int)(*(STRBUF(l)+i-1)))) i--; if (i != STRBUFLEN(l)) strbufferchop(l, STRBUFLEN(l)-i); /* Kill leading whitespace */ i = strspn(STRBUF(l), " \t"); if (i > 0) { memmove(STRBUF(l), STRBUF(l)+i, STRBUFLEN(l)-i); strbufferchop(l, i); } if (unescape) { char *p; p = STRBUF(l) + strcspn(STRBUF(l), "\\"); while (*p) { memmove(p, p+1, STRBUFLEN(l)-(p-STRBUF(l))); strbufferchop(l, 1); p = p + 1 + strcspn(p+1, "\\"); } } } unsigned int IPtou32(int ip1, int ip2, int ip3, int ip4) { return ((ip1 << 24) | (ip2 << 16) | (ip3 << 8) | (ip4)); } char *u32toIP(unsigned int ip32) { int ip1, ip2, ip3, ip4; static char *result = NULL; if (result == NULL) result = (char *)malloc(16); ip1 = ((ip32 >> 24) & 0xFF); ip2 = ((ip32 >> 16) & 0xFF); ip3 = ((ip32 >> 8) & 0xFF); ip4 = (ip32 & 0xFF); sprintf(result, "%d.%d.%d.%d", ip1, ip2, ip3, ip4); return result; } const char *textornull(const char *text) { return (text ? text : "(NULL)"); } int get_fqdn(void) { /* Get FQDN setting */ getenv_default("FQDN", "TRUE", NULL); return (strcmp(xgetenv("FQDN"), "TRUE") == 0); } int generate_static(void) { getenv_default("XYMONLOGSTATUS", "STATIC", NULL); return (strcmp(xgetenv("XYMONLOGSTATUS"), "STATIC") == 0); } int run_command(char *cmd, char *errortext, strbuffer_t *banner, int showcmd, int timeout) { int result; char l[1024]; int pfd[2]; pid_t childpid; MEMDEFINE(l); result = 0; if (banner && showcmd) { sprintf(l, "Command: %s\n\n", cmd); addtobuffer(banner, l); } /* Adapted from Stevens' popen()/pclose() example */ if (pipe(pfd) > 0) { errprintf("Could not create pipe: %s\n", strerror(errno)); MEMUNDEFINE(l); return -1; } if ((childpid = fork()) < 0) { errprintf("Could not fork child process: %s\n", strerror(errno)); MEMUNDEFINE(l); return -1; } if (childpid == 0) { /* The child runs here */ close(pfd[0]); if (pfd[1] != STDOUT_FILENO) { dup2(pfd[1], STDOUT_FILENO); dup2(pfd[1], STDERR_FILENO); close(pfd[1]); } execl("/bin/sh", "sh", "-c", cmd, NULL); exit(127); } else { /* The parent runs here */ int done = 0, didterm = 0, n; struct timespec tmo, timestamp, cutoff; close(pfd[1]); /* Make our reads non-blocking */ if (fcntl(pfd[0], F_SETFL, O_NONBLOCK) == -1) { /* Failed .. but lets try and run this anyway */ errprintf("Could not set non-blocking reads on pipe: %s\n", strerror(errno)); } getntimer(&cutoff); cutoff.tv_sec += timeout; while (!done) { fd_set readfds; getntimer(×tamp); tvdiff(×tamp, &cutoff, &tmo); if ((tmo.tv_sec < 0) || (tmo.tv_nsec < 0)) { /* Timeout already happened */ n = 0; } else { struct timeval selecttmo; selecttmo.tv_sec = tmo.tv_sec; selecttmo.tv_usec = tmo.tv_nsec / 1000; FD_ZERO(&readfds); FD_SET(pfd[0], &readfds); n = select(pfd[0]+1, &readfds, NULL, NULL, &selecttmo); } if (n == -1) { errprintf("select() error: %s\n", strerror(errno)); result = -1; done = 1; } else if (n == 0) { /* Timeout */ errprintf("Timeout waiting for data from child, killing it\n"); kill(childpid, (didterm ? SIGKILL : SIGTERM)); if (!didterm) didterm = 1; else { done = 1; result = -1; } } else if (FD_ISSET(pfd[0], &readfds)) { n = read(pfd[0], l, sizeof(l)-1); l[n] = '\0'; if (n == 0) { done = 1; } else { if (banner && *l) addtobuffer(banner, l); if (errortext && (strstr(l, errortext) != NULL)) result = 1; } } } close(pfd[0]); result = 0; while ((result == 0) && (waitpid(childpid, &result, 0) < 0)) { if (errno != EINTR) { errprintf("Error picking up child exit status: %s\n", strerror(errno)); result = -1; } } if (WIFEXITED(result)) { result = WEXITSTATUS(result); } else if (WIFSIGNALED(result)) { errprintf("Child process terminated with signal %d\n", WTERMSIG(result)); result = -1; } } MEMUNDEFINE(l); return result; } void do_extensions(FILE *output, char *extenv, char *family) { /* * Extension scripts. These are ad-hoc, and implemented as a * simple pipe. So we do a fork here ... */ char *exts, *p; FILE *inpipe; char extfn[PATH_MAX]; strbuffer_t *inbuf; p = xgetenv(extenv); if (p == NULL) { /* No extension */ return; } MEMDEFINE(extfn); exts = strdup(p); p = strtok(exts, "\t "); inbuf = newstrbuffer(0); while (p) { /* Dont redo the eventlog or acklog things */ if ((strcmp(p, "eventlog.sh") != 0) && (strcmp(p, "acklog.sh") != 0)) { sprintf(extfn, "%s/ext/%s/%s", xgetenv("XYMONHOME"), family, p); inpipe = popen(extfn, "r"); if (inpipe) { initfgets(inpipe); while (unlimfgets(inbuf, inpipe)) fputs(STRBUF(inbuf), output); pclose(inpipe); freestrbuffer(inbuf); } } p = strtok(NULL, "\t "); } xfree(exts); MEMUNDEFINE(extfn); MEMUNDEFINE(buf); } static void clean_cmdarg(char *l) { /* * This routine sanitizes command-line argument, stripping off whitespace, * removing comments and un-escaping \-escapes and quotes. */ char *p, *outp; int inquote, inhyphen; /* Remove quotes, comments and leading whitespace */ p = l + strspn(l, " \t"); outp = l; inquote = inhyphen = 0; while (*p) { if (*p == '\\') { *outp = *(p+1); outp++; p += 2; } else if (*p == '"') { inquote = (1 - inquote); p++; } else if (*p == '\'') { inhyphen = (1 - inhyphen); p++; } else if ((*p == '#') && !inquote && !inhyphen) { *p = '\0'; } else { if (outp != p) *outp = *p; outp++; p++; } } /* Remove trailing whitespace */ while ((outp > l) && (isspace((int) *(outp-1)))) outp--; *outp = '\0'; } char **setup_commandargs(char *cmdline, char **cmd) { /* * Good grief - argument parsing is complex! * * This routine takes a command-line, picks out any environment settings * that are in the command line, and splits up the remainder into the * actual command to run, and the arguments. * * It handles quotes, hyphens and escapes. */ char **cmdargs; char *cmdcp, *barg, *earg, *eqchar, *envsetting; int argi, argsz; int argdone, inquote, inhyphen; char savech; argsz = 1; cmdargs = (char **) malloc((1+argsz)*sizeof(char *)); argi = 0; cmdcp = strdup(expand_env(cmdline)); /* Kill a trailing CR/NL */ barg = cmdcp + strcspn(cmdcp, "\r\n"); *barg = '\0'; barg = cmdcp; do { earg = barg; argdone = 0; inquote = inhyphen = 0; while (*earg && !argdone) { if (!inquote && !inhyphen) { argdone = isspace((int)*earg); } if ((*earg == '"') && !inhyphen) inquote = (1 - inquote); if ((*earg == '\'') && !inquote) inhyphen = (1 - inhyphen); if (!argdone) earg++; } savech = *earg; *earg = '\0'; clean_cmdarg(barg); eqchar = strchr(barg, '='); if (eqchar && (eqchar == (barg + strspn(barg, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")))) { /* It's an environment definition */ dbgprintf("Setting environment: %s\n", barg); envsetting = strdup(barg); putenv(envsetting); } else { if (argi == argsz) { argsz++; cmdargs = (char **) realloc(cmdargs, (1+argsz)*sizeof(char *)); } cmdargs[argi++] = strdup(barg); } *earg = savech; barg = earg + strspn(earg, " \t\n"); } while (*barg); cmdargs[argi] = NULL; xfree(cmdcp); *cmd = cmdargs[0]; return cmdargs; } long long str2ll(char *s, char **errptr) { #ifdef HAVE_STRTOLL return strtoll(s, errptr, 10); #else long long result = 0; int negative = 0; char *inp; inp = s + strspn(s, " \t"); if (*inp == '-') { negative = 1; inp++; } while (isdigit((int)*inp)) { result = 10*result + (*inp - '0'); inp++; } if (errptr && (*inp != '\0') && (!isspace((int)*inp))) *errptr = inp; if (negative) result = -result; return result; #endif } int checkalert(char *alertlist, char *testname) { char *alist, *aname; int result; if (!alertlist) return 0; alist = (char *) malloc(strlen(alertlist) + 3); sprintf(alist, ",%s,", alertlist); aname = (char *) malloc(strlen(testname) + 3); sprintf(aname, ",%s,", testname); result = (strstr(alist, aname) != NULL); xfree(aname); xfree(alist); return result; } char *nextcolumn(char *s) { static char *ofs = NULL; char *result; if (s) ofs = s + strspn(s, " \t"); if (!s && !ofs) return NULL; result = ofs; ofs += strcspn(ofs, " \t"); if (*ofs) { *ofs = '\0'; ofs += 1 + strspn(ofs+1, " \t"); } else ofs = NULL; return result; } int selectcolumn(char *heading, char *wanted) { char *hdr; int result = 0; hdr = nextcolumn(heading); while (hdr && strcasecmp(hdr, wanted)) { result++; hdr = nextcolumn(NULL); } if (hdr) return result; else return -1; } char *getcolumn(char *s, int wanted) { char *result; int i; for (i=0, result=nextcolumn(s); (i < wanted); i++, result = nextcolumn(NULL)); return result; } int chkfreespace(char *path, int minblks, int mininodes) { /* Check there is least 'minblks' % free space on filesystem 'path' */ struct statvfs fs; int n; int avlblk, avlnod; n = statvfs(path, &fs); if (n == -1) { errprintf("Cannot stat filesystem %s: %s", path, strerror(errno)); return 0; } /* Not all filesystems report i-node data, so play it safe */ avlblk = ((fs.f_bavail > 0) && (fs.f_blocks > 0)) ? fs.f_bavail / (fs.f_blocks / 100) : 100; avlnod = ((fs.f_favail > 0) && (fs.f_files > 0)) ? fs.f_favail / (fs.f_files / 100) : 100; if ((avlblk >= minblks) && (avlnod >= mininodes)) return 0; return 1; } xymon-4.3.7/lib/msort.c0000664000175000017500000001354711615341300014305 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains a "mergesort" implementation for sorting linked lists. */ /* */ /* Based on http://en.wikipedia.org/wiki/Merge_sort pseudo code, adapted for */ /* use in a generic library routine. */ /* */ /* Copyright (C) 2009-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: msort.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include "libxymon.h" #if 0 static void *merge(void *left, void *right, msortcompare_fn_t comparefn, msortgetnext_fn_t getnext, msortsetnext_fn_t setnext) { void *head, *tail; head = tail = NULL; while (left && right) { if (comparefn(left, right) < 0) { /* Add the left item to the resultlist */ if (tail) { setnext(tail, left); } else { head = left; } tail = left; left = getnext(left); } else { /* Add the right item to the resultlist */ if (tail) { setnext(tail, right); } else { head = right; } tail = right; right = getnext(right); } } /* One or both lists have ended. Add whatever elements may remain */ if (left) { if (tail) setnext(tail, left); else head = tail = left; } if (right) { if (tail) setnext(tail, right); else head = tail = right; } return head; } void *msort(void *head, msortcompare_fn_t comparefn, msortgetnext_fn_t getnext, msortsetnext_fn_t setnext) { void *left, *right, *middle, *walk, *walknext; /* First check if list is empty or has only one element */ if ((head == NULL) || (getnext(head) == NULL)) return head; /* * Find the middle element of the list. * We do this by walking the list until we reach the end. * "middle" takes one step at a time, whereas "walk" takes two. */ middle = head; walk = getnext(middle); /* "walk" must be ahead of "middle" */ while (walk && (walknext = getnext(walk))) { middle = getnext(middle); walk = getnext(walknext); } /* Split the list in two halves, and sort each of them. */ left = head; right = getnext(middle); setnext(middle, NULL); left = msort(left, comparefn, getnext, setnext); right = msort(right, comparefn, getnext, setnext); /* We have sorted the two halves, now we must merge them together */ return merge(left, right, comparefn, getnext, setnext); } #endif void *msort(void *head, msortcompare_fn_t comparefn, msortgetnext_fn_t getnext, msortsetnext_fn_t setnext) { void *walk; int len, i; void **plist; for (walk = head, len=0; (walk); walk = getnext(walk)) len++; plist = malloc((len+1) * sizeof(void *)); for (walk = head, i=0; (walk); walk = getnext(walk)) plist[i++] = walk; plist[len] = NULL; qsort(plist, len, sizeof(plist[0]), comparefn); for (i=0, head = plist[0]; (i < len); i++) setnext(plist[i], plist[i+1]); xfree(plist); return head; } #ifdef STANDALONE typedef struct rec_t { struct rec_t *next; char *key; char *val; } rec_t; void dumplist(rec_t *head) { rec_t *walk; walk = head; while (walk) { printf("%p : %-15s:%3s\n", walk, walk->key, walk->val); walk = walk->next; } printf("\n"); printf("\n"); } int record_compare(void **a, void **b) { rec_t **reca = (rec_t **)a; rec_t **recb = (rec_t **)b; return strcasecmp((*reca)->key, (*recb)->key); } void * record_getnext(void *a) { return ((rec_t *)a)->next; } void record_setnext(void *a, void *newval) { ((rec_t *)a)->next = (rec_t *)newval; } /* 50 most popular US babynames in 2006: http://www.ssa.gov/OACT/babynames/ */ char *tdata[] = { "Jacob", "Emily", "Michael", "Emma", "Joshua", "Madison", "Ethan", "Isabella", "Matthew", "Ava", "Daniel", "Abigail", "Christopher", "Olivia", "Andrew", "Hannah", "Anthony", "Sophia", "William", "Samantha", "Joseph", "Elizabeth", "Alexander", "Ashley", "David", "Mia", "Ryan", "Alexis", "Noah", "Sarah", "James", "Natalie", "Nicholas", "Grace", "Tyler", "Chloe", "Logan", "Alyssa", "John", "Brianna", "Christian", "Ella", "Jonathan", "Taylor", "Nathan", "Anna", "Benjamin", "Lauren", "Samuel", "Hailey", "Dylan", "Kayla", "Brandon", "Addison", "Gabriel", "Victoria", "Elijah", "Jasmine", "Aiden", "Savannah", "Angel", "Julia", "Jose", "Jessica", "Zachary", "Lily", "Caleb", "Sydney", "Jack", "Morgan", "Jackson", "Katherine", "Kevin", "Destiny", "Gavin", "Lillian", "Mason", "Alexa", "Isaiah", "Alexandra", "Austin", "Kaitlyn", "Evan", "Kaylee", "Luke", "Nevaeh", "Aidan", "Brooke", "Justin", "Makayla", "Jordan", "Allison", "Robert", "Maria", "Isaac", "Angelina", "Landon", "Rachel", "Jayden", "Gabriella", NULL }; int main(int argc, char *argv[]) { int i; rec_t *head, *newrec, *tail; head = tail = NULL; for (i=0; (tdata[i]); i++) { char numstr[10]; newrec = (rec_t *)calloc(1, sizeof(rec_t)); newrec->key = strdup(tdata[i]); sprintf(numstr, "%d", i+1); newrec->val = strdup(numstr); if (tail) { tail->next = newrec; tail = newrec; } else { head = tail = newrec; } } dumplist(head); head = msort(head, record_compare, record_getnext, record_setnext); dumplist(head); return 0; } #endif xymon-4.3.7/lib/webaccess.c0000664000175000017500000000632111665414371015106 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for web access control. */ /* */ /* Copyright (C) 2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: misc.c 6712 2011-07-31 21:01:52Z storner $"; #include #include "config.h" #include "libxymon.h" void *acctree = NULL; void *load_web_access_config(char *accessfn) { FILE *fd; strbuffer_t *buf; if (acctree) return 0; acctree = xtreeNew(strcasecmp); fd = stackfopen(accessfn, "r", NULL); if (fd == NULL) return NULL; buf = newstrbuffer(0); while (stackfgets(buf, NULL)) { char *group, *member; char *key; group = strtok(STRBUF(buf), ": \n"); if (!group) continue; member = strtok(NULL, ", \n"); while (member) { key = (char *)malloc(strlen(group) + strlen(member) + 2); sprintf(key, "%s %s", group, member); xtreeAdd(acctree, key, NULL); member = strtok(NULL, ", \n"); } } stackfclose(fd); return acctree; } int web_access_allowed(char *username, char *hostname, char *testname, web_access_type_t acc) { void *hinfo; char *pages, *onepg, *key; hinfo = hostinfo(hostname); if (!hinfo || !acctree || !username) return 0; /* Check for "root" access first */ key = (char *)malloc(strlen(username) + 6); sprintf(key, "root %s", username); if (xtreeFind(acctree, key) != xtreeEnd(acctree)) { xfree(key); return 1; } xfree(key); pages = strdup(xmh_item(hinfo, XMH_ALLPAGEPATHS)); onepg = strtok(pages, ","); while (onepg) { char *p; p = strchr(onepg, '/'); if (p) *p = '\0'; /* Will only look at the top-level path element */ key = (char *)malloc(strlen(onepg) + strlen(username) + 2); sprintf(key, "%s %s", onepg, username); if (xtreeFind(acctree, key) != xtreeEnd(acctree)) { xfree(key); xfree(pages); return 1; } xfree(key); onepg = strtok(NULL, ","); } xfree(pages); if (hostname) { /* See if user is a member of a group named by the hostname */ key = (char *)malloc(strlen(hostname) + strlen(username) + 2); sprintf(key, "%s %s", hostname, username); if (xtreeFind(acctree, key) != xtreeEnd(acctree)) { xfree(key); return 1; } xfree(key); } if (testname) { /* See if user is a member of a group named by the testname */ key = (char *)malloc(strlen(testname) + strlen(username) + 2); sprintf(key, "%s %s", testname, username); if (xtreeFind(acctree, key) != xtreeEnd(acctree)) { xfree(key); return 1; } xfree(key); } return 0; } xymon-4.3.7/lib/holidays.c0000664000175000017500000004421311630612042014750 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for handling holidays. */ /* */ /* Copyright (C) 2006-2008 Michael Nagel */ /* Modifications for Xymon (C) 2007-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: holidays.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #include #include #include #include #include #include "libxymon.h" static int holidays_like_weekday = -1; typedef struct holidayset_t { char *key; holiday_t *head; holiday_t *tail; } holidayset_t; static void * holidays; static int current_year = 0; static time_t mkday(int year, int month, int day) { struct tm t; memset(&t, 0, sizeof(t)); t.tm_year = year; t.tm_mon = month - 1; t.tm_mday = day; t.tm_isdst = -1; return mktime(&t); } /* * Algorithm for calculating the date of Easter Sunday * (Meeus/Jones/Butcher Gregorian algorithm) * For reference, see http://en.wikipedia.org/wiki/Computus */ static time_t getEasterDate(int year) { time_t day; int Y = year+1900; int a = Y % 19; int b = Y / 100; int c = Y % 100; int d = b / 4; int e = b % 4; int f = (b + 8) / 25; int g = (b - f + 1) / 3; int h = (19 * a + b - d - g + 15) % 30; int i = c / 4; int k = c % 4; int L = (32 + 2 * e + 2 * i - h - k) % 7; int m = (a + 11 * h + 22 * L) / 451; day = mkday(year, (h + L - 7 * m + 114) / 31, ((h + L - 7 * m + 114) % 31) + 1); return day; } /* Algorithm to compute the 4th Sunday in Advent (ie the last Sunday before Christmas Day) */ static time_t get4AdventDate(int year) { time_t day; struct tm *t; day = mkday(year, 12, 24); t = localtime(&day); day -= t->tm_wday * 86400; return day; } static int getnumberedweekday(int wkday, int daynum, int month, int year) { struct tm tm; time_t t; /* First see what weekday the 1st of this month is */ memset(&tm, 0, sizeof(tm)); tm.tm_mon = (month - 1); tm.tm_year = year; tm.tm_mday = 1; tm.tm_isdst = -1; t = mktime(&tm); if (tm.tm_wday != wkday) { /* Skip forward so we reach the first of the wanted weekdays */ tm.tm_mday += (wkday - tm.tm_wday); if (tm.tm_mday < 1) tm.tm_mday += 7; tm.tm_isdst = -1; t = mktime(&tm); } /* t and tm now has the 1st wkday in this month. So skip to the one we want */ tm.tm_mday += 7*(daynum - 1); /* Check if we overflowed into next month (if daynum == 5) */ t = mktime(&tm); tm.tm_isdst = -1; if ((daynum == 5) && (tm.tm_mon != (month - 1))) { /* We drifted into the next month. Go back one week to return the last wkday of the month */ tm.tm_mday -= 7; tm.tm_isdst = -1; t = mktime(&tm); } return tm.tm_yday; } static int getweekdayafter(int wkday, int daynum, int month, int year) { struct tm tm; time_t t; /* First see what weekday this date is */ memset(&tm, 0, sizeof(tm)); tm.tm_mon = (month - 1); tm.tm_year = year; tm.tm_mday = daynum; tm.tm_isdst = -1; t = mktime(&tm); if (tm.tm_wday != wkday) { /* Skip forward so we reach the wanted weekday */ tm.tm_mday += (wkday - tm.tm_wday); if (tm.tm_mday < daynum) tm.tm_mday += 7; tm.tm_isdst = -1; t = mktime(&tm); } return tm.tm_yday; } static void reset_holidays(void) { static int firsttime = 1; xtreePos_t handle; holidayset_t *hset; holiday_t *walk, *zombie; if (!firsttime) { for (handle = xtreeFirst(holidays); (handle != xtreeEnd(holidays)); handle = xtreeNext(holidays, handle)) { hset = (holidayset_t *)xtreeData(holidays, handle); xfree(hset->key); walk = hset->head; while (walk) { zombie = walk; walk = walk->next; xfree(zombie->desc); xfree(zombie); } } xtreeDestroy(holidays); } holidays_like_weekday = -1; firsttime = 0; holidays = xtreeNew(strcasecmp); } static void add_holiday(char *key, int year, holiday_t *newhol) { int isOK = 0; struct tm *t; time_t day; holiday_t *newitem; xtreePos_t handle; holidayset_t *hset; switch (newhol->holtype) { case HOL_ABSOLUTE: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <=31) && (!newhol->year || (newhol->year == year)) ); if (!isOK) break; day = mkday(year, newhol->month, newhol->day); t = localtime(&day); newhol->yday = t->tm_yday; break; case HOL_EASTER: isOK = (newhol->month == 0); if (!isOK) break; day = getEasterDate(year); t = localtime(&day); newhol->yday = t->tm_yday + newhol->day; break; case HOL_ADVENT: isOK = (newhol->month == 0); if (!isOK) break; day = get4AdventDate(year); t = localtime(&day); newhol->yday = t->tm_yday + newhol->day; break; case HOL_MON: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 5) ); if (!isOK) break; newhol->yday = getnumberedweekday(1, newhol->day, newhol->month, year); break; case HOL_TUE: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 5) ); if (!isOK) break; newhol->yday = getnumberedweekday(2, newhol->day, newhol->month, year); break; case HOL_WED: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 5) ); if (!isOK) break; newhol->yday = getnumberedweekday(3, newhol->day, newhol->month, year); break; case HOL_THU: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 5) ); if (!isOK) break; newhol->yday = getnumberedweekday(4, newhol->day, newhol->month, year); break; case HOL_FRI: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 5) ); if (!isOK) break; newhol->yday = getnumberedweekday(5, newhol->day, newhol->month, year); break; case HOL_SAT: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 5) ); if (!isOK) break; newhol->yday = getnumberedweekday(6, newhol->day, newhol->month, year); break; case HOL_SUN: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 5) ); if (!isOK) break; newhol->yday = getnumberedweekday(0, newhol->day, newhol->month, year); break; case HOL_MON_AFTER: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 31) ); if (!isOK) break; newhol->yday = getweekdayafter(1, newhol->day, newhol->month, year); break; case HOL_TUE_AFTER: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 31) ); if (!isOK) break; newhol->yday = getweekdayafter(2, newhol->day, newhol->month, year); break; case HOL_WED_AFTER: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 31) ); if (!isOK) break; newhol->yday = getweekdayafter(3, newhol->day, newhol->month, year); break; case HOL_THU_AFTER: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 31) ); if (!isOK) break; newhol->yday = getweekdayafter(4, newhol->day, newhol->month, year); break; case HOL_FRI_AFTER: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 31) ); if (!isOK) break; newhol->yday = getweekdayafter(5, newhol->day, newhol->month, year); break; case HOL_SAT_AFTER: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 31) ); if (!isOK) break; newhol->yday = getweekdayafter(6, newhol->day, newhol->month, year); break; case HOL_SUN_AFTER: isOK = ( (newhol->month >= 1 && newhol->month <=12) && (newhol->day >=1 && newhol->day <= 31) ); if (!isOK) break; newhol->yday = getweekdayafter(0, newhol->day, newhol->month, year); break; } if (!isOK) { errprintf("Error in holiday definition %s\n", newhol->desc); return; } newitem = (holiday_t *)calloc(1, sizeof(holiday_t)); newitem->holtype = newhol->holtype; newitem->day = newhol->day; newitem->month = newhol->month; newitem->desc = strdup(newhol->desc); newitem->yday = newhol->yday; handle = xtreeFind(holidays, key); if (handle == xtreeEnd(holidays)) { hset = (holidayset_t *)calloc(1, sizeof(holidayset_t)); hset->key = strdup(key); xtreeAdd(holidays, hset->key, hset); } else { hset = (holidayset_t *)xtreeData(holidays, handle); } if (hset->head == NULL) { hset->head = hset->tail = newitem; } else { hset->tail->next = newitem; hset->tail = hset->tail->next; } } static int record_compare(void **a, void **b) { holiday_t **reca = (holiday_t **)a; holiday_t **recb = (holiday_t **)b; return ((*reca)->yday < (*recb)->yday); } static void * record_getnext(void *a) { return ((holiday_t *)a)->next; } static void record_setnext(void *a, void *newval) { ((holiday_t *)a)->next = (holiday_t *)newval; } int load_holidays(int year) { static void *configholidays = NULL; char fn[PATH_MAX]; FILE *fd; strbuffer_t *inbuf; holiday_t newholiday; xtreePos_t handle, commonhandle; char *setname = NULL; holidayset_t *commonhols; MEMDEFINE(fn); if (year == 0) { time_t tnow; struct tm *now; tnow = getcurrenttime(NULL); now = localtime(&tnow); year = now->tm_year; } else if (year > 1000) { year -= 1900; } sprintf(fn, "%s/etc/holidays.cfg", xgetenv("XYMONHOME")); /* First check if there were no modifications at all */ if (configholidays) { /* if the new year begins, the holidays have to be recalculated */ if (!stackfmodified(configholidays) && (year == current_year)){ dbgprintf("No files modified, skipping reload of %s\n", fn); MEMUNDEFINE(fn); return 0; } else { stackfclist(&configholidays); configholidays = NULL; } } reset_holidays(); fd = stackfopen(fn, "r", &configholidays); if (!fd) { errprintf("Cannot open configuration file %s\n", fn); MEMUNDEFINE(fn); return 0; } memset(&newholiday,0,sizeof(holiday_t)); inbuf = newstrbuffer(0); while (stackfgets(inbuf, NULL)) { char *p, *delim, *arg1, *arg2; sanitize_input(inbuf, 1, 0); if (STRBUFLEN(inbuf) == 0) continue; p = STRBUF(inbuf); if (strncasecmp(p, "HOLIDAYLIKEWEEKDAY=", 19) == 0) { p+=19; holidays_like_weekday = atoi(p); if (holidays_like_weekday < -1 || holidays_like_weekday > 6) { holidays_like_weekday = -1; errprintf("Invalid HOLIDAYLIKEWEEKDAY in %s\n", fn); } continue; } if (*p == '[') { /* New set of holidays */ if (setname) xfree(setname); delim = strchr(p, ']'); if (delim) *delim = '\0'; setname = strdup(p+1); continue; } delim = strchr(p, ':'); if (delim) { memset(&newholiday,0,sizeof(holiday_t)); if (delim == p) { newholiday.desc = "untitled"; } else { *delim = '\0'; newholiday.desc = p; p=delim+1; } } arg1 = strtok(p, "="); while (arg1) { arg2=strtok(NULL," ,;\t\n\r"); if (!arg2) break; if (strncasecmp(arg1, "TYPE", 4) == 0) { if (strncasecmp(arg2, "STATIC", 6) == 0) newholiday.holtype = HOL_ABSOLUTE; else if (strncasecmp(arg2, "EASTER", 6) == 0) newholiday.holtype = HOL_EASTER; else if (strncasecmp(arg2, "4ADVENT", 7) == 0) newholiday.holtype = HOL_ADVENT; else if (strncasecmp(arg2, "MON", 3) == 0) newholiday.holtype = HOL_MON; else if (strncasecmp(arg2, "TUE", 3) == 0) newholiday.holtype = HOL_TUE; else if (strncasecmp(arg2, "WED", 3) == 0) newholiday.holtype = HOL_WED; else if (strncasecmp(arg2, "THU", 3) == 0) newholiday.holtype = HOL_THU; else if (strncasecmp(arg2, "FRI", 3) == 0) newholiday.holtype = HOL_FRI; else if (strncasecmp(arg2, "SAT", 3) == 0) newholiday.holtype = HOL_SAT; else if (strncasecmp(arg2, "SUN", 3) == 0) newholiday.holtype = HOL_SUN; else if (strncasecmp(arg2, "+MON", 4) == 0) newholiday.holtype = HOL_MON_AFTER; else if (strncasecmp(arg2, "+TUE", 4) == 0) newholiday.holtype = HOL_TUE_AFTER; else if (strncasecmp(arg2, "+WED", 4) == 0) newholiday.holtype = HOL_WED_AFTER; else if (strncasecmp(arg2, "+THU", 4) == 0) newholiday.holtype = HOL_THU_AFTER; else if (strncasecmp(arg2, "+FRI", 4) == 0) newholiday.holtype = HOL_FRI_AFTER; else if (strncasecmp(arg2, "+SAT", 4) == 0) newholiday.holtype = HOL_SAT_AFTER; else if (strncasecmp(arg2, "+SUN", 4) == 0) newholiday.holtype = HOL_SUN_AFTER; } else if (strncasecmp(arg1, "MONTH", 5) == 0) { newholiday.month=atoi(arg2); } else if (strncasecmp(arg1, "DAY", 3) == 0) { newholiday.day=atoi(arg2); } else if (strncasecmp(arg1, "OFFSET", 6) == 0) { newholiday.day=atoi(arg2); } else if (strncasecmp(arg1, "YEAR", 4) == 0) { newholiday.year=atoi(arg2); if (newholiday.year > 1000) { newholiday.year -= 1900; } } arg1 = strtok(NULL,"="); } add_holiday((setname ? setname : ""), year, &newholiday); } stackfclose(fd); freestrbuffer(inbuf); commonhandle = xtreeFind(holidays, ""); commonhols = (commonhandle != xtreeEnd(holidays)) ? (holidayset_t *)xtreeData(holidays, commonhandle) : NULL; for (handle = xtreeFirst(holidays); (handle != xtreeEnd(holidays)); handle = xtreeNext(holidays, handle)) { holidayset_t *oneset = (holidayset_t *)xtreeData(holidays, handle); if (commonhols && (oneset != commonhols)) { /* Add the common holidays to this set */ holiday_t *walk; for (walk = commonhols->head; (walk); walk = walk->next) add_holiday(oneset->key, year, walk); } oneset->head = msort(oneset->head, record_compare, record_getnext, record_setnext); } MEMUNDEFINE(fn); current_year = year; return 0; } static holiday_t *findholiday(char *key, int dayinyear) { xtreePos_t handle; holidayset_t *hset; holiday_t *p; if (key && *key) { handle = xtreeFind(holidays, key); if (handle == xtreeEnd(holidays)) { key = NULL; handle = xtreeFind(holidays, ""); } } else { key = NULL; handle = xtreeFind(holidays, ""); } if (handle != xtreeEnd(holidays)) hset = (holidayset_t *)xtreeData(holidays, handle); else return NULL; p = hset->head; while (p) { if (dayinyear == p->yday) { return p; } p = p->next; } return NULL; } int getweekdayorholiday(char *key, struct tm *t) { holiday_t *rec; if (holidays_like_weekday == -1) return t->tm_wday; rec = findholiday(key, t->tm_yday); if (rec) return holidays_like_weekday; return t->tm_wday; } char *isholiday(char *key, int dayinyear) { holiday_t *rec; rec = findholiday(key, dayinyear); if (rec) return rec->desc; return NULL; } void printholidays(char *key, strbuffer_t *buf, int mfirst, int mlast) { int day; char *fmt; char oneh[1024]; char dstr[1024]; fmt = xgetenv("HOLIDAYFORMAT"); for (day = 0; (day < 366); day++) { char *desc = isholiday(key, day); if (desc) { struct tm tm; time_t t; /* * mktime() ignores the tm_yday parameter, so to get the * actual date for the "yday'th" day of the year we just set * tm_mday to the yday value, and month to January. mktime() * will figure out that the 56th of January is really Feb 25. * * Note: tm_yday is zero-based, but tm_mday is 1-based! */ tm.tm_mon = 0; tm.tm_mday = day+1; tm.tm_year = current_year; tm.tm_hour = 12; tm.tm_min = 0; tm.tm_sec = 0; tm.tm_isdst = -1; t = mktime(&tm); if ((tm.tm_mon >= mfirst) && (tm.tm_mon <= mlast)) { strftime(dstr, sizeof(dstr), fmt, localtime(&t)); sprintf(oneh, "%s%s\n", desc, dstr); addtobuffer(buf, oneh); } } } } #ifdef STANDALONE int main(int argc, char *argv[]) { char l[1024]; char *hset = NULL; char *p; strbuffer_t *sbuf = newstrbuffer(0); char *dayname[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; load_holidays(0); do { printf("$E year, $4 year, $W daynum wkday month year, Setname\n? "); fflush(stdout); if (!fgets(l, sizeof(l), stdin)) return 0; p = strchr(l, '\n'); if (p) *p = '\0'; if (hset) xfree(hset); hset = strdup(l); if (*hset == '$') { time_t t; struct tm *tm; int i; char *tok, *arg[5]; i = 0; tok = strtok(hset, " "); while (tok) { arg[i] = tok; i++; tok = strtok(NULL, " "); } if (arg[0][1] == 'E') { t = getEasterDate(atoi(arg[1]) - 1900); tm = localtime(&t); printf("Easter Sunday %04d is %02d/%02d/%04d\n", atoi(arg[1]), tm->tm_mday, tm->tm_mon+1, tm->tm_year+1900); } else if (arg[0][1] == '4') { t = get4AdventDate(atoi(arg[1]) - 1900); tm = localtime(&t); printf("4Advent %04d is %02d/%02d/%04d\n", atoi(arg[1]), tm->tm_mday, tm->tm_mon+1, tm->tm_year+1900); } else if (arg[0][1] == 'W') { struct tm wtm; memset(&wtm, 0, sizeof(wtm)); wtm.tm_mday = getnumberedweekday(atoi(arg[2]), atoi(arg[1]), atoi(arg[3]), atoi(arg[4])-1900) + 1; wtm.tm_mon = 0; wtm.tm_year = atoi(arg[4]) - 1900; wtm.tm_isdst = -1; mktime(&wtm); printf("The %d. %s in %02d/%04d is %02d/%02d/%04d\n", atoi(arg[1]), dayname[atoi(arg[2])], atoi(arg[3]), atoi(arg[4]), wtm.tm_mday, wtm.tm_mon+1, wtm.tm_year+1900); } else if (arg[0][1] == 'A') { struct tm wtm; memset(&wtm, 0, sizeof(wtm)); wtm.tm_mday = getweekdayafter(atoi(arg[2]), atoi(arg[1]), atoi(arg[3]), atoi(arg[4])-1900) + 1; wtm.tm_mon = 0; wtm.tm_year = atoi(arg[4]) - 1900; wtm.tm_isdst = -1; mktime(&wtm); printf("The %d. %s on or after %02d/%04d is %02d/%02d/%04d\n", atoi(arg[1]), dayname[atoi(arg[2])], atoi(arg[3]), atoi(arg[4]), wtm.tm_mday, wtm.tm_mon+1, wtm.tm_year+1900); } } else { int year = atoi(hset); load_holidays(year); printholidays(hset, sbuf); printf("Holidays in set: %s\n", STRBUF(sbuf)); clearstrbuffer(sbuf); } } while (1); return 0; } #endif xymon-4.3.7/lib/eventlog.c0000664000175000017500000006572211615341300014766 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This displays the "eventlog" found on the "All non-green status" page. */ /* It also implements a CGI tool to show an eventlog for a given period of */ /* time, as a reporting function. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* Host/test/color/start/end filtering code by Eric Schwimmer 2005 */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: eventlog.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" char *eventignorecolumns = NULL; int havedoneeventlog = 0; static int wanted_eventcolumn(char *service) { char svc[100]; int result; if (!eventignorecolumns || (strlen(service) > (sizeof(svc)-3))) return 1; sprintf(svc, ",%s,", service); result = (strstr(eventignorecolumns, svc) == NULL); return result; } static char *string_time(time_t timestamp) { static char result[20]; strftime(result, sizeof(result), "%Y/%m/%d@%H:%M:%S", localtime(×tamp)); return result; } int record_compare(void **a, void **b) { countlist_t **reca = (countlist_t **)a, **recb = (countlist_t **)b; /* Sort the countlist_t records in reverse */ if ( (*reca)->total > (*recb)->total ) return -1; else if ( (*reca)->total < (*recb)->total ) return 1; else return 0; } void * record_getnext(void *a) { return ((countlist_t *)a)->next; } void record_setnext(void *a, void *newval) { ((countlist_t *)a)->next = (countlist_t *)newval; } static htnames_t *namehead = NULL; static htnames_t *getname(char *name, int createit) { htnames_t *walk; for (walk = namehead; (walk && strcmp(walk->name, name)); walk = walk->next) ; if (walk || (!createit)) return walk; walk = (htnames_t *)malloc(sizeof(htnames_t)); walk->name = strdup(name); walk->next = namehead; namehead = walk; return walk; } static void count_events(countlist_t **hostcounthead, countlist_t **svccounthead) { void *hostwalk; for (hostwalk = first_host(); (hostwalk); hostwalk = next_host(hostwalk, 0)) { eventcount_t *swalk; countlist_t *hrec, *srec; swalk = (eventcount_t *)xmh_item(hostwalk, XMH_DATA); if (!swalk) continue; hrec = (countlist_t *)malloc(sizeof(countlist_t)); hrec->src = hostwalk; hrec->total = 0; hrec->next = *hostcounthead; *hostcounthead = hrec; for (swalk = (eventcount_t *)xmh_item(hostwalk, XMH_DATA); (swalk); swalk = swalk->next) { hrec->total += swalk->count; for (srec = *svccounthead; (srec && (srec->src != (void *)swalk->service)); srec = srec->next) ; if (!srec) { srec = (countlist_t *)malloc(sizeof(countlist_t)); srec->src = (void *)swalk->service; srec->total = 0; srec->next = *svccounthead; *svccounthead = srec; } srec->total += swalk->count; } } } typedef struct ed_t { event_t *event; struct ed_t *next; } ed_t; typedef struct elist_t { htnames_t *svc; ed_t *head, *tail; struct elist_t *next; } elist_t; static void dump_eventtree(void) { void *hwalk; elist_t *lwalk; ed_t *ewalk; for (hwalk = first_host(); (hwalk); hwalk = next_host(hwalk, 0)) { printf("%s\n", xmh_item(hwalk, XMH_HOSTNAME)); lwalk = (elist_t *)xmh_item(hwalk, XMH_DATA); while (lwalk) { printf("\t%s\n", lwalk->svc->name); ewalk = lwalk->head; while (ewalk) { printf("\t\t%ld->%ld = %6ld %s\n", (long) ewalk->event->changetime, (long) ewalk->event->eventtime, (long) ewalk->event->duration, colorname(ewalk->event->oldcolor)); ewalk = ewalk->next; } lwalk = lwalk->next; } } } void dump_countlists(countlist_t *hosthead, countlist_t *svchead) { countlist_t *cwalk; printf("Hosts\n"); for (cwalk = hosthead; (cwalk); cwalk = cwalk->next) { printf("\t%20s : %lu\n", xmh_item(cwalk->src, XMH_HOSTNAME), cwalk->total); } printf("\n"); printf("Services\n"); for (cwalk = svchead; (cwalk); cwalk = cwalk->next) { printf("\t%20s : %lu\n", ((htnames_t *)cwalk->src)->name, cwalk->total); } printf("\n"); } static int eventfilter(void *hinfo, char *testname, pcre *pageregexp, pcre *expageregexp, pcre *hostregexp, pcre *exhostregexp, pcre *testregexp, pcre *extestregexp, int ignoredialups, f_hostcheck hostcheck) { int pagematch, hostmatch, testmatch; char *hostname = xmh_item(hinfo, XMH_HOSTNAME); int ovector[30]; if (ignoredialups && xmh_item(hinfo, XMH_FLAG_DIALUP)) return 0; if (hostcheck && (hostcheck(hostname) == 0)) return 0; if (pageregexp) { char *pagename; pagename = xmh_item_multi(hinfo, XMH_PAGEPATH); pagematch = 0; while (!pagematch && pagename) { pagematch = (pcre_exec(pageregexp, NULL, pagename, strlen(pagename), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); pagename = xmh_item_multi(NULL, XMH_PAGEPATH); } } else pagematch = 1; if (!pagematch) return 0; if (expageregexp) { char *pagename; pagename = xmh_item_multi(hinfo, XMH_PAGEPATH); pagematch = 0; while (!pagematch && pagename) { pagematch = (pcre_exec(expageregexp, NULL, pagename, strlen(pagename), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); pagename = xmh_item_multi(NULL, XMH_PAGEPATH); } } else pagematch = 0; if (pagematch) return 0; if (hostregexp) hostmatch = (pcre_exec(hostregexp, NULL, hostname, strlen(hostname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); else hostmatch = 1; if (!hostmatch) return 0; if (exhostregexp) hostmatch = (pcre_exec(exhostregexp, NULL, hostname, strlen(hostname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); else hostmatch = 0; if (hostmatch) return 0; if (testregexp) testmatch = (pcre_exec(testregexp, NULL, testname, strlen(testname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); else testmatch = 1; if (!testmatch) return 0; if (extestregexp) testmatch = (pcre_exec(extestregexp, NULL, testname, strlen(testname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); else testmatch = 0; if (testmatch) return 0; return 1; } static void count_duration(time_t fromtime, time_t totime, pcre *pageregexp, pcre *expageregexp, pcre *hostregexp, pcre *exhostregexp, pcre *testregexp, pcre *extestregexp, int ignoredialups, f_hostcheck hostcheck, event_t *eventhead, countlist_t **hostcounthead, countlist_t **svccounthead) { void *hwalk; elist_t *lwalk; event_t *ewalk; ed_t *ed; sendreturn_t *bdata; /* * Restructure the event-list so we have a tree instead: * * HostRecord * | *Data ----> EventList * | | *Service * | | *EventHead --> Event --> Event --> Event * | | *EventTail --------------------------^ * | | * | v * | * v * */ for (ewalk = eventhead; (ewalk); ewalk = ewalk->next) { lwalk = (elist_t *)xmh_item(ewalk->host, XMH_DATA); while (lwalk && (lwalk->svc != ewalk->service)) lwalk = lwalk->next; if (lwalk == NULL) { lwalk = (elist_t *)calloc(1, sizeof(elist_t)); lwalk->svc = ewalk->service; lwalk->next = (elist_t *)xmh_item(ewalk->host, XMH_DATA); xmh_set_item(ewalk->host, XMH_DATA, (void *)lwalk); } ed = (ed_t *)calloc(1, sizeof(ed_t)); ed->event = ewalk; ed->next = lwalk->head; if (lwalk->head == NULL) lwalk->tail = ed; lwalk->head = ed; } if (debug) { printf("\n\nEventtree before fixups\n\n"); dump_eventtree(); } /* * Next, we must add a pseudo record for the current state. * This is for those statuses that haven't changed since the * start of our data-collection period - they won't have any events * so we cannot tell what color they are. By grabbing the current * color we can add a pseudo-event that lets us determine what the * color has been since the start of the event-period. */ bdata = newsendreturnbuf(1, NULL); if (sendmessage("xymondboard fields=hostname,testname,color,lastchange", NULL, XYMON_TIMEOUT, bdata) == XYMONSEND_OK) { char *bol, *eol; char *hname, *tname; int color; time_t lastchange; void *hrec; htnames_t *srec; char *icname = xgetenv("INFOCOLUMN"); char *tcname = xgetenv("TRENDSCOLUMN"); bol = getsendreturnstr(bdata, 0); while (bol) { eol = strchr(bol, '\n'); if (eol) *eol = '\0'; hname = strtok(bol, "|"); tname = (hname ? strtok(NULL, "|") : NULL); color = (tname ? parse_color(strtok(NULL, "|")) : -1); lastchange = ((color != -1) ? atol(strtok(NULL, "\n")) : totime+1); if (hname && tname && (color != -1) && (strcmp(tname, icname) != 0) && (strcmp(tname, tcname) != 0)) { int addrec = 1; hrec = hostinfo(hname); srec = getname(tname, 1); if (eventfilter(hrec, tname, pageregexp, expageregexp, hostregexp, exhostregexp, testregexp, extestregexp, ignoredialups, hostcheck) == 0) goto nextrecord; lwalk = (elist_t *)xmh_item(hrec, XMH_DATA); while (lwalk && (lwalk->svc != srec)) lwalk = lwalk->next; if (lwalk == NULL) { lwalk = (elist_t *)calloc(1, sizeof(elist_t)); lwalk->svc = srec; lwalk->next = (elist_t *)xmh_item(hrec, XMH_DATA); xmh_set_item(hrec, XMH_DATA, (void *)lwalk); } /* See if we already have an event past the "totime" value */ if (lwalk->head) { addrec = 0; ed = lwalk->head; while (ed && (ed->event->eventtime < totime)) ed = ed->next; if (ed) { ed->next = NULL; lwalk->tail = ed; } else { ed = (ed_t *)calloc(1, sizeof(ed_t)); ed->event = (event_t *)calloc(1, sizeof(event_t)); lwalk->tail->next = ed; ed->event->host = hrec; ed->event->service = srec; ed->event->eventtime = totime; ed->event->changetime = lwalk->tail->event->eventtime; ed->event->duration = (totime - lwalk->tail->event->eventtime); ed->event->newcolor = -1; ed->event->oldcolor = lwalk->tail->event->newcolor; ed->event->next = NULL; ed->next = NULL; lwalk->tail = ed; } } else if (lastchange < totime) { ed = (ed_t *)calloc(1, sizeof(ed_t)); ed->event = (event_t *)calloc(1, sizeof(event_t)); ed->event->host = hrec; ed->event->service = srec; ed->event->eventtime = totime; ed->event->changetime = (lwalk->tail ? lwalk->tail->event->eventtime : fromtime); ed->event->duration = (totime - ed->event->changetime); ed->event->newcolor = color; ed->event->oldcolor = (lwalk->tail ? lwalk->tail->event->newcolor : color); ed->event->next = NULL; ed->next = NULL; lwalk->head = lwalk->tail = ed; } } nextrecord: bol = (eol ? eol+1 : NULL); } freesendreturnbuf(bdata); } else { errprintf("Cannot get the current state\n"); freesendreturnbuf(bdata); return; } if (debug) { printf("\n\nEventtree after pseudo-events\n\n"); dump_eventtree(); } /* * Fixup the beginning-time (and duration) of the first events recorded. * This is to handle events that begin BEFORE our event-logging period. * Fixup the end-time (and duration) of the last events recorded. * This is to handle events that end AFTER our event-logging period. */ for (hwalk = first_host(); (hwalk); hwalk = next_host(hwalk, 0)) { elist_t *lwalk; event_t *erec; ed_t *ewalk; lwalk = (elist_t *)xmh_item(hwalk, XMH_DATA); while (lwalk) { if (lwalk->head) { erec = lwalk->head->event; if (erec->changetime > totime) { /* First event is after our start-time. Drop the events */ lwalk->head = lwalk->tail = NULL; } else if (erec->changetime < fromtime) { /* First event is before our start-time. Adjust to starttime. */ erec->changetime = fromtime; erec->duration = (erec->eventtime - fromtime); } ewalk = lwalk->head; while (ewalk && (ewalk->event->eventtime < totime)) ewalk = ewalk->next; if (ewalk) { lwalk->tail = ewalk; lwalk->tail->next = 0; } if (lwalk->tail) { erec = lwalk->tail->event; if (erec->eventtime > totime) { /* Last event is after our end-time. Adjust to end-time */ erec->eventtime = totime; erec->duration = (totime - erec->changetime); } } } lwalk = lwalk->next; } } if (debug) { printf("\n\nEventtree after fixups\n\n"); dump_eventtree(); } for (hwalk = first_host(); (hwalk); hwalk = next_host(hwalk, 0)) { countlist_t *hrec, *srec; hrec = (countlist_t *)malloc(sizeof(countlist_t)); hrec->src = hwalk; hrec->total = 0; hrec->next = *hostcounthead; *hostcounthead = hrec; lwalk = (elist_t *)xmh_item(hwalk, XMH_DATA); while (lwalk) { for (srec = *svccounthead; (srec && (srec->src != (void *)lwalk->svc)); srec = srec->next) ; if (!srec) { srec = (countlist_t *)malloc(sizeof(countlist_t)); srec->src = (void *)lwalk->svc; srec->total = 0; srec->next = *svccounthead; *svccounthead = srec; } if (lwalk->head) { ed_t *ewalk = lwalk->head; while (ewalk) { if (ewalk->event->oldcolor >= COL_YELLOW) { hrec->total += ewalk->event->duration; srec->total += ewalk->event->duration; } ewalk = ewalk->next; } } lwalk = lwalk->next; } } if (debug) dump_countlists(*hostcounthead, *svccounthead); } void do_eventlog(FILE *output, int maxcount, int maxminutes, char *fromtime, char *totime, char *pageregex, char *expageregex, char *hostregex, char *exhostregex, char *testregex, char *extestregex, char *colrregex, int ignoredialups, f_hostcheck hostcheck, event_t **eventlist, countlist_t **hostcounts, countlist_t **servicecounts, countsummary_t counttype, eventsummary_t sumtype, char *periodstring) { FILE *eventlog; char eventlogfilename[PATH_MAX]; time_t firstevent = 0; time_t lastevent = getcurrenttime(NULL); event_t *eventhead = NULL; struct stat st; char l[MAX_LINE_LEN]; char title[200]; /* For the PCRE matching */ const char *errmsg = NULL; int errofs = 0; pcre *pageregexp = NULL; pcre *expageregexp = NULL; pcre *hostregexp = NULL; pcre *exhostregexp = NULL; pcre *testregexp = NULL; pcre *extestregexp = NULL; pcre *colrregexp = NULL; countlist_t *hostcounthead = NULL, *svccounthead = NULL; if (eventlist) *eventlist = NULL; if (hostcounts) *hostcounts = NULL; if (servicecounts) *servicecounts = NULL; havedoneeventlog = 1; if ((maxminutes > 0) && (fromtime || totime)) { fprintf(output, "Only one time interval type is allowed!"); return; } if (fromtime) { firstevent = eventreport_time(fromtime); if(firstevent < 0) { if (output) fprintf(output,"Invalid 'from' time: %s", htmlquoted(fromtime)); return; } } else if (maxminutes == -1) { /* Unlimited number of minutes */ firstevent = 0; } else if (maxminutes > 0) { firstevent = getcurrenttime(NULL) - maxminutes*60; } else { firstevent = getcurrenttime(NULL) - 86400; } if (totime) { lastevent = eventreport_time(totime); if (lastevent < 0) { if (output) fprintf(output,"Invalid 'to' time: %s", htmlquoted(totime)); return; } if (lastevent < firstevent) { if (output) fprintf(output,"'to' time must be after 'from' time."); return; } } if (!maxcount) maxcount = 100; if (pageregex && *pageregex) pageregexp = pcre_compile(pageregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (expageregex && *expageregex) expageregexp = pcre_compile(expageregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (hostregex && *hostregex) hostregexp = pcre_compile(hostregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (exhostregex && *exhostregex) exhostregexp = pcre_compile(exhostregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (testregex && *testregex) testregexp = pcre_compile(testregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (extestregex && *extestregex) extestregexp = pcre_compile(extestregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (colrregex && *colrregex) colrregexp = pcre_compile(colrregex, PCRE_CASELESS, &errmsg, &errofs, NULL); sprintf(eventlogfilename, "%s/allevents", xgetenv("XYMONHISTDIR")); eventlog = fopen(eventlogfilename, "r"); if (eventlog && (stat(eventlogfilename, &st) == 0)) { time_t curtime; int done = 0; int unlimited = (maxcount == -1); if (unlimited) maxcount = 1000; do { /* Find a spot in the eventlog file close to where the firstevent time is */ fseeko(eventlog, 0, SEEK_END); do { /* Go back maxcount*80 bytes - one entry is ~80 bytes */ if (ftello(eventlog) > maxcount*80) { unsigned int uicurtime; fseeko(eventlog, -maxcount*80, SEEK_CUR); fgets(l, sizeof(l), eventlog); /* Skip to start of line */ fgets(l, sizeof(l), eventlog); sscanf(l, "%*s %*s %u %*u %*u %*s %*s %*d", &uicurtime); curtime = uicurtime; done = (curtime < firstevent); if (unlimited && !done) maxcount += 1000; } else { off_t ofs; rewind(eventlog); curtime = 0; ofs = ftello(eventlog); done = 1; } } while (!done); if (unlimited) unlimited = ((curtime > firstevent) && (ftello(eventlog) > 0)); } while (unlimited); } eventhead = NULL; while (eventlog && (fgets(l, sizeof(l), eventlog))) { time_t eventtime, changetime, duration; unsigned int uievt, uicht, uidur; char hostname[MAX_LINE_LEN], svcname[MAX_LINE_LEN], newcol[MAX_LINE_LEN], oldcol[MAX_LINE_LEN]; char *newcolname, *oldcolname; int state, itemsfound, pagematch, hostmatch, testmatch, colrmatch; event_t *newevent; void *eventhost; struct htnames_t *eventcolumn; int ovector[30]; eventcount_t *countrec; itemsfound = sscanf(l, "%s %s %u %u %u %s %s %d", hostname, svcname, &uievt, &uicht, &uidur, newcol, oldcol, &state); eventtime = uievt; changetime = uicht; duration = uidur; oldcolname = colorname(eventcolor(oldcol)); newcolname = colorname(eventcolor(newcol)); /* For DURATION counts, we must parse all events until now */ if ((counttype != XYMON_COUNT_DURATION) && (eventtime > lastevent)) break; eventhost = hostinfo(hostname); eventcolumn = getname(svcname, 1); if ( (itemsfound == 8) && (eventtime >= firstevent) && (eventhost && !xmh_item(eventhost, XMH_FLAG_NONONGREEN)) && (wanted_eventcolumn(svcname)) ) { if (eventfilter(eventhost, svcname, pageregexp, expageregexp, hostregexp, exhostregexp, testregexp, extestregexp, ignoredialups, hostcheck) == 0) continue; /* For duration counts, record all events. We'll filter out the colors later. */ if (colrregexp && (counttype != XYMON_COUNT_DURATION)) { colrmatch = ( (pcre_exec(colrregexp, NULL, newcolname, strlen(newcolname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0) || (pcre_exec(colrregexp, NULL, oldcolname, strlen(oldcolname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0) ); } else colrmatch = 1; if (!colrmatch) continue; newevent = (event_t *) malloc(sizeof(event_t)); newevent->host = eventhost; newevent->service = eventcolumn; newevent->eventtime = eventtime; newevent->changetime = changetime; newevent->duration = duration; newevent->newcolor = eventcolor(newcol); newevent->oldcolor = eventcolor(oldcol); newevent->next = eventhead; eventhead = newevent; if (counttype != XYMON_COUNT_DURATION) { countrec = (eventcount_t *)xmh_item(eventhost, XMH_DATA); while (countrec && (countrec->service != eventcolumn)) countrec = countrec->next; if (countrec == NULL) { countrec = (eventcount_t *)calloc(1, sizeof(eventcount_t)); countrec->service = eventcolumn; countrec->next = (eventcount_t *)xmh_item(eventhost, XMH_DATA); xmh_set_item(eventhost, XMH_DATA, (void *)countrec); } countrec->count++; } } } /* Count the state changes per host */ svccounthead = hostcounthead = NULL; switch (counttype) { case XYMON_COUNT_EVENTS: count_events(&hostcounthead, &svccounthead); break; case XYMON_COUNT_DURATION: count_duration(firstevent, lastevent, pageregexp, expageregexp, hostregexp, exhostregexp, testregexp, extestregexp, ignoredialups, hostcheck, eventhead, &hostcounthead, &svccounthead); break; default: break; } if (hostcounthead) hostcounthead = msort(hostcounthead, record_compare, record_getnext, record_setnext); if (svccounthead) svccounthead = msort(svccounthead, record_compare, record_getnext, record_setnext); if (eventhead && (output != NULL)) { char *bgcolors[2] = { "#000000", "#000033" }; int bgcolor = 0; struct event_t *ewalk, *lasttoshow = eventhead; countlist_t *cwalk; unsigned long totalcount = 0; if (periodstring) fprintf(output, "

%s

\n", htmlquoted(periodstring)); switch (sumtype) { case XYMON_S_HOST_BREAKDOWN: /* Request for a specific service, show breakdown by host */ for (cwalk = hostcounthead; (cwalk); cwalk = cwalk->next) totalcount += cwalk->total; fprintf(output, "\n"); fprintf(output, "\n", (counttype == XYMON_COUNT_EVENTS) ? "State changes" : "Seconds red/yellow"); fprintf(output, "\n"); for (cwalk = hostcounthead; (cwalk && (cwalk->total > 0)); cwalk = cwalk->next) { fprintf(output, "\n", xmh_item(cwalk->src, XMH_HOSTNAME), cwalk->total, ((100.0 * cwalk->total) / totalcount)); } fprintf(output, "
Host%s

%s%lu(%6.2f %%)
\n"); break; case XYMON_S_SERVICE_BREAKDOWN: /* Request for a specific host, show breakdown by service */ for (cwalk = svccounthead; (cwalk); cwalk = cwalk->next) totalcount += cwalk->total; fprintf(output, "\n"); fprintf(output, "\n", (counttype == XYMON_COUNT_EVENTS) ? "State changes" : "Seconds red/yellow"); fprintf(output, "\n"); for (cwalk = svccounthead; (cwalk && (cwalk->total > 0)); cwalk = cwalk->next) { fprintf(output, "\n", ((htnames_t *)cwalk->src)->name, cwalk->total, ((100.0 * cwalk->total) / totalcount)); } fprintf(output, "
Service%s

%s%lu(%6.2f %%)
\n"); break; case XYMON_S_NONE: break; } if (sumtype == XYMON_S_NONE) { int count; count=0; ewalk=eventhead; do { count++; lasttoshow = ewalk; ewalk = ewalk->next; } while (ewalk && (countnext = NULL; /* Terminate list if any items left */ if (maxminutes > 0) { sprintf(title, "%d events received in the past %u minutes", count, (unsigned int)((getcurrenttime(NULL) - lasttoshow->eventtime) / 60)); } else { sprintf(title, "%d events received.", count); } } else { strcpy(title, "Events in summary"); } fprintf(output, "

\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n", htmlquoted(title)); for (ewalk=eventhead; (ewalk); ewalk=ewalk->next) { char *hostname = xmh_item(ewalk->host, XMH_HOSTNAME); if ( (counttype == XYMON_COUNT_DURATION) && (ewalk->oldcolor < COL_YELLOW) && (ewalk->newcolor < COL_YELLOW) ) continue; if ( (counttype == XYMON_COUNT_DURATION) && (ewalk->eventtime >= lastevent) ) continue; fprintf(output, "\n", bgcolors[bgcolor]); bgcolor = ((bgcolor + 1) % 2); fprintf(output, "\n", ctime(&ewalk->eventtime)); if (ewalk->newcolor == COL_CLEAR) { fprintf(output, "\n", hostname); } else { fprintf(output, "\n", colorname(ewalk->newcolor), hostname); } fprintf(output, "\n", ewalk->service->name); fprintf(output, "\n", xgetenv("XYMONSKIN"), dotgiffilename(ewalk->newcolor, 0, 0), xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH"), colorname(ewalk->newcolor), colorname(ewalk->newcolor)); fprintf(output, "\n"); } fprintf(output, "
%s
%s%s%s%s\n", histlogurl(hostname, ewalk->service->name, ewalk->changetime, NULL)); fprintf(output, "\"%s\"\n", xgetenv("XYMONSKIN"), dotgiffilename(ewalk->oldcolor, 0, 0), xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH"), colorname(ewalk->oldcolor), colorname(ewalk->oldcolor)); fprintf(output, "\"From\n", xgetenv("XYMONSKIN")); fprintf(output, "\n", histlogurl(hostname, ewalk->service->name, ewalk->eventtime, NULL)); fprintf(output, "\"%s\"
\n"); } else if (output != NULL) { /* No events during the past maxminutes */ if (eventlog) sprintf(title, "No events received in the last %d minutes", maxminutes); else strcpy(title, "No events logged"); fprintf(output, "

\n"); fprintf(output, "\n", title); fprintf(output, "\n"); fprintf(output, "\n", htmlquoted(title)); fprintf(output, "\n"); fprintf(output, "
%s
\n"); fprintf(output, "
\n"); } if (eventlog) fclose(eventlog); if (pageregexp) pcre_free(pageregexp); if (hostregexp) pcre_free(hostregexp); if (testregexp) pcre_free(testregexp); if (colrregexp) pcre_free(colrregexp); /* Return the event- and count-lists, if wanted - or clean them up */ if (eventlist) { *eventlist = eventhead; } else { event_t *zombie, *ewalk = eventhead; while (ewalk) { zombie = ewalk; ewalk = ewalk->next; xfree(zombie); } } if (hostcounts) { *hostcounts = hostcounthead; } else { countlist_t *zombie, *hwalk = hostcounthead; while (hwalk) { zombie = hwalk; hwalk = hwalk->next; xfree(zombie); } } if (servicecounts) { *servicecounts = svccounthead; } else { countlist_t *zombie, *swalk = svccounthead; while (swalk) { zombie = swalk; swalk = swalk->next; xfree(zombie); } } } xymon-4.3.7/lib/errormsg.h0000664000175000017500000000227111615341300014776 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __ERRORMSG_H__ #define __ERRORMSG_H__ #include extern char *errbuf; extern int save_errbuf; extern int debug; extern void errprintf(const char *fmt, ...); extern void dbgprintf(const char *fmt, ...); extern void flush_errbuf(void); extern void set_debugfile(char *fn, int appendtofile); extern void starttrace(const char *fn); extern void stoptrace(void); extern void traceprintf(const char *fmt, ...); extern void redirect_cgilog(char *cginame); #endif xymon-4.3.7/lib/holidays.h0000664000175000017500000000307511615341300014755 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __HOLIDAYS_H__ #define __HOLIDAYS_H__ typedef struct holiday_t { enum { HOL_ABSOLUTE, HOL_EASTER, HOL_ADVENT, HOL_MON_AFTER, HOL_TUE_AFTER, HOL_WED_AFTER, HOL_THU_AFTER, HOL_FRI_AFTER, HOL_SAT_AFTER, HOL_SUN_AFTER, HOL_MON, HOL_TUE, HOL_WED, HOL_THU, HOL_FRI, HOL_SAT, HOL_SUN } holtype; char *desc; /* description */ int month; /* month for absolute date */ int day; /* day for absolute date or offset for type 2 and 3 */ int yday; /* day of the year this holiday occurs in current year */ int year; /* year for absolute date */ struct holiday_t *next; } holiday_t; extern int load_holidays(int year); extern int getweekdayorholiday(char *key, struct tm *t); extern char *isholiday(char *key, int dayinyear); extern void printholidays(char *key, strbuffer_t *buf, int mfirst, int mlast); #endif xymon-4.3.7/lib/notifylog.c0000664000175000017500000002636511630450173015163 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This displays the "notification" log. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* Host/test/color/start/end filtering code by Eric Schwimmer 2005 */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: notifylog.c 6744 2011-09-03 16:06:19Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" typedef struct notification_t { void *host; struct htnames_t *service; time_t eventtime; char *recipient; struct notification_t *next; } notification_t; static time_t convert_time(char *timestamp) { time_t event = 0; unsigned int year,month,day,hour,min,sec,count; struct tm timeinfo; count = sscanf(timestamp, "%u/%u/%u@%u:%u:%u", &year, &month, &day, &hour, &min, &sec); if(count != 6) { return -1; } if(year < 1970) { return 0; } else { memset(&timeinfo, 0, sizeof(timeinfo)); timeinfo.tm_year = year - 1900; timeinfo.tm_mon = month - 1; timeinfo.tm_mday = day; timeinfo.tm_hour = hour; timeinfo.tm_min = min; timeinfo.tm_sec = sec; timeinfo.tm_isdst = -1; event = mktime(&timeinfo); } return event; } static htnames_t *namehead = NULL; static htnames_t *getname(char *name, int createit) { htnames_t *walk; for (walk = namehead; (walk && strcmp(walk->name, name)); walk = walk->next) ; if (walk || (!createit)) return walk; walk = (htnames_t *)malloc(sizeof(htnames_t)); walk->name = strdup(name); walk->next = namehead; namehead = walk; return walk; } void do_notifylog(FILE *output, int maxcount, int maxminutes, char *fromtime, char *totime, char *pageregex, char *expageregex, char *hostregex, char *exhostregex, char *testregex, char *extestregex, char *rcptregex, char *exrcptregex) { FILE *notifylog; char notifylogfilename[PATH_MAX]; time_t firstevent = 0; time_t lastevent = getcurrenttime(NULL); notification_t *head, *walk; struct stat st; char l[MAX_LINE_LEN]; char title[200]; /* For the PCRE matching */ const char *errmsg = NULL; int errofs = 0; pcre *pageregexp = NULL; pcre *expageregexp = NULL; pcre *hostregexp = NULL; pcre *exhostregexp = NULL; pcre *testregexp = NULL; pcre *extestregexp = NULL; pcre *rcptregexp = NULL; pcre *exrcptregexp = NULL; if (maxminutes && (fromtime || totime)) { fprintf(output, "Only one time interval type is allowed!"); return; } if (fromtime) { firstevent = convert_time(fromtime); if(firstevent < 0) { fprintf(output,"Invalid 'from' time: %s", htmlquoted(fromtime)); return; } } else if (maxminutes) { firstevent = getcurrenttime(NULL) - maxminutes*60; } else { firstevent = getcurrenttime(NULL) - 86400; } if (totime) { lastevent = convert_time(totime); if (lastevent < 0) { fprintf(output,"Invalid 'to' time: %s", htmlquoted(totime)); return; } if (lastevent < firstevent) { fprintf(output,"'to' time must be after 'from' time."); return; } } if (!maxcount) maxcount = 100; if (pageregex && *pageregex) pageregexp = pcre_compile(pageregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (expageregex && *expageregex) expageregexp = pcre_compile(expageregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (hostregex && *hostregex) hostregexp = pcre_compile(hostregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (exhostregex && *exhostregex) exhostregexp = pcre_compile(exhostregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (testregex && *testregex) testregexp = pcre_compile(testregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (extestregex && *extestregex) extestregexp = pcre_compile(extestregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (rcptregex && *rcptregex) rcptregexp = pcre_compile(rcptregex, PCRE_CASELESS, &errmsg, &errofs, NULL); if (exrcptregex && *exrcptregex) exrcptregexp = pcre_compile(exrcptregex, PCRE_CASELESS, &errmsg, &errofs, NULL); sprintf(notifylogfilename, "%s/notifications.log", xgetenv("XYMONSERVERLOGS")); notifylog = fopen(notifylogfilename, "r"); if (notifylog && (stat(notifylogfilename, &st) == 0)) { time_t curtime; int done = 0; /* Find a spot in the notification log file close to where the firstevent time is */ fseeko(notifylog, 0, SEEK_END); do { /* Go back maxcount*80 bytes - one entry is ~80 bytes */ if (ftello(notifylog) > maxcount*80) { unsigned int uicurtime; fseeko(notifylog, -maxcount*80, SEEK_CUR); fgets(l, sizeof(l), notifylog); /* Skip to start of line */ fgets(l, sizeof(l), notifylog); /* Sun Jan 7 10:29:08 2007 myhost.disk (130.225.226.90) foo@test.com 1168162147 100 */ sscanf(l, "%*s %*s %*u %*u:%*u:%*u %*u %*s %*s %*s %u %*d", &uicurtime); curtime = uicurtime; done = (curtime < firstevent); } else { rewind(notifylog); done = 1; } } while (!done); } head = NULL; while (notifylog && (fgets(l, sizeof(l), notifylog))) { unsigned int etim; time_t eventtime; char hostsvc[MAX_LINE_LEN]; char recipient[MAX_LINE_LEN]; char *hostname, *svcname, *p; int itemsfound, pagematch, hostmatch, testmatch, rcptmatch; notification_t *newrec; void *eventhost; struct htnames_t *eventcolumn; int ovector[30]; itemsfound = sscanf(l, "%*s %*s %*u %*u:%*u:%*u %*u %s %*s %s %u %*d", hostsvc, recipient, &etim); eventtime = etim; if (itemsfound != 3) continue; if (eventtime < firstevent) continue; if (eventtime > lastevent) break; hostname = hostsvc; svcname = strrchr(hostsvc, '.'); if (svcname) { *svcname = '\0'; svcname++; } else svcname = ""; eventhost = hostinfo(hostname); if (!eventhost) continue; /* Dont report hosts that no longer exist */ eventcolumn = getname(svcname, 1); p = strchr(recipient, '['); if (p) *p = '\0'; if (pageregexp) { char *pagename; pagename = xmh_item_multi(eventhost, XMH_PAGEPATH); pagematch = 0; while (!pagematch && pagename) { pagematch = (pcre_exec(pageregexp, NULL, pagename, strlen(pagename), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); pagename = xmh_item_multi(NULL, XMH_PAGEPATH); } } else pagematch = 1; if (!pagematch) continue; if (expageregexp) { char *pagename; pagename = xmh_item_multi(eventhost, XMH_PAGEPATH); pagematch = 0; while (!pagematch && pagename) { pagematch = (pcre_exec(expageregexp, NULL, pagename, strlen(pagename), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); pagename = xmh_item_multi(NULL, XMH_PAGEPATH); } } else pagematch = 0; if (pagematch) continue; if (hostregexp) hostmatch = (pcre_exec(hostregexp, NULL, hostname, strlen(hostname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); else hostmatch = 1; if (!hostmatch) continue; if (exhostregexp) hostmatch = (pcre_exec(exhostregexp, NULL, hostname, strlen(hostname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); else hostmatch = 0; if (hostmatch) continue; if (testregexp) testmatch = (pcre_exec(testregexp, NULL, svcname, strlen(svcname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); else testmatch = 1; if (!testmatch) continue; if (extestregexp) testmatch = (pcre_exec(extestregexp, NULL, svcname, strlen(svcname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); else testmatch = 0; if (testmatch) continue; if (rcptregexp) rcptmatch = (pcre_exec(rcptregexp, NULL, recipient, strlen(recipient), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); else rcptmatch = 1; if (!rcptmatch) continue; if (exrcptregexp) rcptmatch = (pcre_exec(exrcptregexp, NULL, recipient, strlen(recipient), 0, 0, ovector, (sizeof(ovector)/sizeof(int))) >= 0); else rcptmatch = 0; if (rcptmatch) continue; newrec = (notification_t *) malloc(sizeof(notification_t)); newrec->host = eventhost; newrec->service = eventcolumn; newrec->eventtime = eventtime; newrec->recipient = strdup(recipient); newrec->next = head; head = newrec; } if (head) { char *bgcolors[2] = { "#000000", "#000066" }; int bgcolor = 0; int count; struct notification_t *lasttoshow = head; count=0; walk=head; do { count++; lasttoshow = walk; walk = walk->next; } while (walk && (counteventtime) / 60)); } else { sprintf(title, "%d notifications sent.", count); } fprintf(output, "

\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n", htmlquoted(title)); fprintf(output, "\n"); for (walk=head; (walk != lasttoshow->next); walk=walk->next) { char *hostname = xmh_item(walk->host, XMH_HOSTNAME); fprintf(output, "\n", bgcolors[bgcolor]); bgcolor = ((bgcolor + 1) % 2); fprintf(output, "\n", ctime(&walk->eventtime)); fprintf(output, "\n", hostname); fprintf(output, "\n", walk->service->name); fprintf(output, "\n", walk->recipient); } fprintf(output, "
%s
TimeHostServiceRecipient
%s%s%s%s
\n"); /* Clean up */ walk = head; do { struct notification_t *tmp = walk; walk = walk->next; xfree(tmp->recipient); xfree(tmp); } while (walk); } else { /* No notifications during the past maxminutes */ if (notifylog) sprintf(title, "No notifications sent in the last %d minutes", maxminutes); else strcpy(title, "No notifications logged"); fprintf(output, "

\n"); fprintf(output, "\n", title); fprintf(output, "\n"); fprintf(output, "\n", htmlquoted(title)); fprintf(output, "\n"); fprintf(output, "
%s
\n"); fprintf(output, "
\n"); } if (notifylog) fclose(notifylog); if (pageregexp) pcre_free(pageregexp); if (expageregexp) pcre_free(expageregexp); if (hostregexp) pcre_free(hostregexp); if (exhostregexp) pcre_free(exhostregexp); if (testregexp) pcre_free(testregexp); if (extestregexp) pcre_free(extestregexp); if (rcptregexp) pcre_free(rcptregexp); if (exrcptregexp) pcre_free(exrcptregexp); } xymon-4.3.7/lib/loadalerts.h0000664000175000017500000000660011617473104015302 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __LOADALERTS_H__ #define __LOADALERTS_H__ #include #include /* The clients probably dont have the pcre headers */ #if defined(LOCALCLIENT) || !defined(CLIENTONLY) #include typedef enum { A_PAGING, A_NORECIP, A_ACKED, A_RECOVERED, A_DISABLED, A_NOTIFY, A_DEAD } astate_t; typedef struct activealerts_t { /* Identification of the alert */ char *hostname; char *testname; char *location; char *osname; char *classname; char *groups; char ip[IP_ADDR_STRLEN]; /* Alert status */ int color, maxcolor; unsigned char *pagemessage; unsigned char *ackmessage; time_t eventstart; time_t nextalerttime; astate_t state; int cookie; struct activealerts_t *next; } activealerts_t; /* These are the criteria we use when matching an alert. Used both generally for a rule, and for recipients */ enum method_t { M_MAIL, M_SCRIPT, M_IGNORE }; enum msgformat_t { ALERTFORM_TEXT, ALERTFORM_PLAIN, ALERTFORM_SMS, ALERTFORM_PAGER, ALERTFORM_SCRIPT, ALERTFORM_NONE }; enum recovermsg_t { SR_UNKNOWN, SR_NOTWANTED, SR_WANTED }; typedef struct criteria_t { int cfid; char *cfline; char *pagespec; /* Pages to include */ pcre *pagespecre; char *expagespec; /* Pages to exclude */ pcre *expagespecre; char *dgspec; /* Display groups to include */ pcre *dgspecre; char *exdgspec; /* Display groups to exclude */ pcre *exdgspecre; char *hostspec; /* Hosts to include */ pcre *hostspecre; char *exhostspec; /* Hosts to exclude */ pcre *exhostspecre; char *svcspec; /* Services to include */ pcre *svcspecre; char *exsvcspec; /* Services to exclude */ pcre *exsvcspecre; char *classspec; pcre *classspecre; char *exclassspec; pcre *exclassspecre; char *groupspec; pcre *groupspecre; char *exgroupspec; pcre *exgroupspecre; int colors; char *timespec; int minduration, maxduration; /* In seconds */ enum recovermsg_t sendrecovered, sendnotice; } criteria_t; /* This defines a recipient. There may be some criteria, and then how we send alerts to him */ typedef struct recip_t { int cfid; criteria_t *criteria; enum method_t method; char *recipient; char *scriptname; enum msgformat_t format; time_t interval; /* In seconds */ int stoprule, unmatchedonly, noalerts; struct recip_t *next; } recip_t; extern int load_alertconfig(char *configfn, int alertcolors, int alertinterval); extern void dump_alertconfig(int showlinenumbers); extern int stoprulefound; extern recip_t *next_recipient(activealerts_t *alert, int *first, int *anymatch, time_t *nexttime); extern int have_recipient(activealerts_t *alert, int *anymatch); extern void alert_printmode(int on); extern void print_alert_recipients(activealerts_t *alert, strbuffer_t *buf); #endif #endif xymon-4.3.7/lib/url.h0000664000175000017500000000330711615341300013741 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __URL_H__ #define __URL_H__ extern int obeybbproxysyntax; typedef struct urlelem_t { char *origform; char *scheme; char *schemeopts; char *host; char *ip; int port; char *auth; char *relurl; int parseerror; } urlelem_t; enum webtesttype_t { WEBTEST_PLAIN, WEBTEST_CONTENT, WEBTEST_CONT, WEBTEST_NOCONT, WEBTEST_POST, WEBTEST_NOPOST, WEBTEST_TYPE, WEBTEST_STATUS, WEBTEST_SOAP, WEBTEST_NOSOAP, }; typedef struct weburl_t { int testtype; char *columnname; struct urlelem_t *desturl; struct urlelem_t *proxyurl; unsigned char *postcontenttype; unsigned char *postdata; unsigned char *expdata; unsigned char *okcodes; unsigned char *badcodes; } weburl_t; extern char *urlunescape(char *url); extern char *urldecode(char *envvar); extern char *urlencode(char *s); extern int urlvalidate(char *query, char *validchars); extern char *cleanurl(char *url); extern void parse_url(char *inputurl, urlelem_t *url); extern char *decode_url(char *testspec, weburl_t *weburl); #endif xymon-4.3.7/lib/sig.h0000664000175000017500000000147411615341300013724 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __SIG_H__ #define __SIG_H__ extern void setup_signalhandler(char *programname); #endif xymon-4.3.7/lib/memory.h0000664000175000017500000000652011615341300014447 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __MEMORY_H__ #define __MEMORY_H__ #undef XYMON_MEMORY_WRAPPERS /* If defined, will wrap mem-alloc routines */ #undef MEMORY_DEBUG /* If defined, use debugging code */ typedef struct xmemory_t { char *sdata; size_t ssize; struct xmemory_t *next; } xmemory_t; extern const char *xfreenullstr; extern void add_to_memlist(void *ptr, size_t memsize); extern void remove_from_memlist(void *ptr); extern void *xcalloc(size_t nmemb, size_t size); extern void *xmalloc(size_t size); extern void *xrealloc(void *ptr, size_t size); extern char *xstrdup(const char *s); extern char *xstrcat(char *dest, const char *src); extern char *xstrcpy(char *dest, const char *src); extern char *xstrncat(char *dest, const char *src, size_t maxlen); extern char *xstrncpy(char *dest, const char *src, size_t maxlen); extern int xsprintf(char *dest, const char *fmt, ...); #ifdef XYMON_MEMORY_WRAPPERS #ifndef LIB_MEMORY_C_COMPILE #undef calloc #undef malloc #undef realloc #undef strdup /* * This arranges for all memory-allocation routines to * go via a wrapper that checks for bogus input data * and malloc() returning NULL when running out of memory. * Errors caught here are fatal. * Overhead is small, so this is turned on always. */ #define calloc(N,S) xcalloc((N), (S)) #define malloc(N) xmalloc((N)) #define realloc(P,S) xrealloc((P), (S)) #define strdup(P) xstrdup((P)) #endif /* LIB_MEMORY_C_COMPILE */ #endif /* XYMON_MEMORY_WRAPPERS */ #ifdef MEMORY_DEBUG /* * This arranges for all calls to potentially memory-overwriting routines * to do strict allocation checks and overwrite checks. The performance * overhead is significant, so it should only be turned on in debugging * situations. */ #ifndef LIB_MEMORY_C_COMPILE #define MEMDEFINE(P) { add_to_memlist((P), sizeof((P))); } #define MEMUNDEFINE(P) { remove_from_memlist((P)); } #define xfree(P) { remove_from_memlist((P)); free((P)); (P) = NULL; } #undef strcat #undef strncat #undef strcpy #undef strncpy #undef sprintf #define strcat(D,S) xstrcat((D), (S)) #define strncat(D,S,L) xstrncat((D), (S), (L)) #define strcpy(D,S) xstrcpy((D), (S)) #define strncpy(D,S,L) xstrncpy((D), (S), (L)) #define sprintf xsprintf #endif #else /* * This defines an "xfree()" macro, which checks for freeing of * a NULL ptr and complains if that happens. It does not check if * the pointer is valid. * After being freed, the pointer is set to NULL to catch double-free's. */ #define xfree(P) { if ((P) == NULL) { errprintf(xfreenullstr); abort(); } free((P)); (P) = NULL; } #define MEMDEFINE(P) do { } while (0); #define MEMUNDEFINE(P) do { } while (0); #endif /* MEMORY_DEBUG */ #endif xymon-4.3.7/lib/readmib.h0000664000175000017500000000726311615341300014547 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2007-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __READMIB_H__ #define __READMIB_H__ /* This holds one OID and our corresponding short-name */ typedef struct oidds_t { char *dsname; /* Our short-name for the data item in the mib definition */ char *oid; /* The OID for the data in the mib definition */ enum { RRD_NOTRACK, RRD_TRACK_GAUGE, RRD_TRACK_COUNTER, RRD_TRACK_DERIVE, RRD_TRACK_ABSOLUTE } rrdtype; /* How to store this in an RRD file */ enum { OID_CONVERT_NONE, /* No conversion */ OID_CONVERT_U32 /* Convert signed -> unsigned 32 bit integer */ } conversion; } oidds_t; /* This holds a list of OID's and their shortnames */ typedef struct oidset_t { int oidsz, oidcount; oidds_t *oids; struct oidset_t *next; } oidset_t; /* This describes the ways we can index into this MIB */ enum mibidxtype_t { MIB_INDEX_IN_OID, /* * The index is part of the key-table OID's; by scanning the key-table * values we find the one matching the wanted key, and can extract the * index from the matching rows' OID. * E.g. interfaces table looking for ifDescr or ifPhysAddress, or * interfaces table looking for the extension-object ifName. * IF-MIB::ifDescr.1 = STRING: lo * IF-MIB::ifDescr.2 = STRING: eth1 * IF-MIB::ifPhysAddress.1 = STRING: * IF-MIB::ifPhysAddress.2 = STRING: 0:e:a6:ce:cf:7f * IF-MIB::ifName.1 = STRING: lo * IF-MIB::ifName.2 = STRING: eth1 * The key table has an entry with the value = key-value. The index * is then the part of the key-OID beyond the base key table OID. */ MIB_INDEX_IN_VALUE /* * Index can be found by adding the key value as a part-OID to the * base OID of the key (e.g. interfaces by IP-address). * IP-MIB::ipAdEntIfIndex.127.0.0.1 = INTEGER: 1 * IP-MIB::ipAdEntIfIndex.172.16.10.100 = INTEGER: 3 */ }; typedef struct mibidx_t { char marker; /* Marker character for key OID */ enum mibidxtype_t idxtype; /* How to interpret the key */ char *keyoid; /* Key OID */ void *rootoid; /* Binary representation of keyoid */ unsigned int rootoidlen; /* Length of binary keyoid */ struct mibidx_t *next; } mibidx_t; typedef struct mibdef_t { enum { MIB_STATUS_NOTLOADED, /* Not loaded */ MIB_STATUS_LOADED, /* Loaded OK, can be used */ MIB_STATUS_LOADFAILED /* Load failed (e.g. missing MIB file) */ } loadstatus; char *mibfn; /* Filename of the MIB file (for non-standard MIB's) */ char *mibname; /* MIB definition name */ int tabular; /* Does the MIB contain a table ? Or just simple data */ oidset_t *oidlisthead, *oidlisttail; /* The list of OID's in the MIB set */ mibidx_t *idxlist; /* List of the possible indices used for the MIB */ int haveresult; /* Used while building result messages */ strbuffer_t *resultbuf; /* Used while building result messages */ } mibdef_t; extern int readmibs(char *cfgfn, int verbose); extern mibdef_t *first_mib(void); extern mibdef_t *next_mib(void); extern mibdef_t *find_mib(char *mibname); #endif xymon-4.3.7/lib/strfunc.h0000664000175000017500000000276611615341300014633 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __STRFUNC_H__ #define __STRFUNC_H__ extern strbuffer_t *newstrbuffer(int initialsize); extern strbuffer_t *convertstrbuffer(char *buffer, int bufsz); extern void addtobuffer(strbuffer_t *buf, char *newtext); extern void addtostrbuffer(strbuffer_t *buf, strbuffer_t *newtext); extern void addtobufferraw(strbuffer_t *buf, char *newdata, int bytes); extern void clearstrbuffer(strbuffer_t *buf); extern void freestrbuffer(strbuffer_t *buf); extern char *grabstrbuffer(strbuffer_t *buf); extern strbuffer_t *dupstrbuffer(char *src); extern void strbufferchop(strbuffer_t *buf, int count); extern void strbufferrecalc(strbuffer_t *buf); extern void strbuffergrow(strbuffer_t *buf, int bytes); extern void strbufferuse(strbuffer_t *buf, int bytes); extern char *htmlquoted(char *s); #endif xymon-4.3.7/lib/url.c0000664000175000017500000004036611615341300013742 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for URL parsing and mangling. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: url.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include "libxymon.h" int obeybbproxysyntax = 0; /* Big Brother can put proxy-spec in a URL, with "http://proxy/bla;http://target/foo" */ /* This is used for loading a .netrc file with hostnames and authentication tokens. */ typedef struct loginlist_t { char *host; char *auth; struct loginlist_t *next; } loginlist_t; static loginlist_t *loginhead = NULL; /* * Convert a URL with "%XX" hexadecimal escaped style bytes into normal form. * Result length will always be <= source length. */ char *urlunescape(char *url) { static char *result = NULL; char *pin, *pout; pin = url; if (result) xfree(result); pout = result = (char *) malloc(strlen(url) + 1); while (*pin) { if (*pin == '+') { *pout = ' '; pin++; } else if (*pin == '%') { pin++; if ((strlen(pin) >= 2) && isxdigit((int)*pin) && isxdigit((int)*(pin+1))) { *pout = 16*hexvalue(*pin) + hexvalue(*(pin+1)); pin += 2; } else { *pout = '%'; pin++; } } else { *pout = *pin; pin++; } pout++; } *pout = '\0'; return result; } /* * Get an environment variable (eg: QUERY_STRING) and do CGI decoding of it. */ char *urldecode(char *envvar) { if (xgetenv(envvar) == NULL) return NULL; return urlunescape(xgetenv(envvar)); } /* * Do a CGI encoding of a URL, i.e. unusual chars are converted to %XX. */ char *urlencode(char *s) { static char *result = NULL; static int resbufsz = 0; char *inp, *outp; if (result == NULL) { result = (char *)malloc(1024); resbufsz = 1024; } outp = result; for (inp = s; (*inp); inp++) { if ((outp - result) > (resbufsz - 5)) { int offset = (outp - result); resbufsz += 1024; result = (char *)realloc(result, resbufsz); outp = result + offset; } if ( ( (*inp >= 'a') && (*inp <= 'z') ) || ( (*inp >= 'A') && (*inp <= 'Z') ) || ( (*inp >= '0') && (*inp <= '9') ) ) { *outp = *inp; outp++; } else { sprintf(outp, "%%%0x", *inp); outp += 3; } } *outp = '\0'; return result; } /* * Check if a URL contains only safe characters. * This is not really needed any more, since there are no more CGI * shell-scripts that directly process the QUERY_STRING parameter. */ int urlvalidate(char *query, char *validchars) { #if 0 static int valid; char *p; if (validchars == NULL) validchars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.-:&_%=*+/ "; for (p=query, valid=1; (valid && *p); p++) { valid = (strchr(validchars, *p) != NULL); } return valid; #else return 1; #endif } /* * Load the $HOME/.netrc file with authentication tokens for HTTP tests. */ static void load_netrc(void) { #define WANT_TOKEN 0 #define MACHINEVAL 1 #define LOGINVAL 2 #define PASSVAL 3 #define OTHERVAL 4 static int loaded = 0; char netrcfn[MAXPATHLEN]; FILE *fd; strbuffer_t *inbuf; char *host, *login, *password, *p; int state = WANT_TOKEN; if (loaded) return; loaded = 1; MEMDEFINE(netrcfn); /* Look for $XYMONHOME/etc/netrc first, then the default ~/.netrc */ sprintf(netrcfn, "%s/etc/netrc", xgetenv("XYMONHOME")); fd = fopen(netrcfn, "r"); /* Can HOME be undefined ? Yes, on Solaris when started during boot */ if ((fd == NULL) && getenv("HOME")) { sprintf(netrcfn, "%s/.netrc", xgetenv("HOME")); fd = fopen(netrcfn, "r"); } if (fd == NULL) { MEMUNDEFINE(netrcfn); return; } host = login = password = NULL; initfgets(fd); inbuf = newstrbuffer(0); while (unlimfgets(inbuf, fd)) { sanitize_input(inbuf, 0, 0); if (STRBUFLEN(inbuf) != 0) { p = strtok(STRBUF(inbuf), " \t"); while (p) { switch (state) { case WANT_TOKEN: if (strcmp(p, "machine") == 0) state = MACHINEVAL; else if (strcmp(p, "login") == 0) state = LOGINVAL; else if (strcmp(p, "password") == 0) state = PASSVAL; else if (strcmp(p, "account") == 0) state = OTHERVAL; else if (strcmp(p, "macdef") == 0) state = OTHERVAL; else if (strcmp(p, "default") == 0) { host = ""; state = WANT_TOKEN; } else state = WANT_TOKEN; break; case MACHINEVAL: host = strdup(p); state = WANT_TOKEN; break; case LOGINVAL: login = strdup(p); state = WANT_TOKEN; break; case PASSVAL: password = strdup(p); state = WANT_TOKEN; break; case OTHERVAL: state = WANT_TOKEN; break; } if (host && login && password) { loginlist_t *item = (loginlist_t *) malloc(sizeof(loginlist_t)); item->host = host; item->auth = (char *) malloc(strlen(login) + strlen(password) + 2); sprintf(item->auth, "%s:%s", login, password); item->next = loginhead; loginhead = item; host = login = password = NULL; } p = strtok(NULL, " \t"); } } } fclose(fd); freestrbuffer(inbuf); MEMUNDEFINE(netrcfn); } /* * Clean a URL of double-slashes. */ char *cleanurl(char *url) { static char *cleaned = NULL; char *pin, *pout; int lastwasslash = 0; if (cleaned == NULL) cleaned = (char *)malloc(strlen(url)+1); else { cleaned = (char *)realloc(cleaned, strlen(url)+1); } for (pin=url, pout=cleaned, lastwasslash=0; (*pin); pin++) { if (*pin == '/') { if (!lastwasslash) { *pout = *pin; pout++; } lastwasslash = 1; } else { *pout = *pin; pout++; lastwasslash = 0; } } *pout = '\0'; return cleaned; } /* * Parse a URL into components, following the guidelines in RFC 1808. * This fills out a urlelem_t struct with the elements, and also * constructs a canonical form of the URL. */ void parse_url(char *inputurl, urlelem_t *url) { char *tempurl; char *fragment = NULL; char *netloc; char *startp, *p; int haveportspec = 0; char *canonurl; int canonurllen; memset(url, 0, sizeof(urlelem_t)); url->scheme = url->host = url->relurl = ""; /* Get a temp. buffer we can molest */ tempurl = strdup(inputurl); /* First cut off any fragment specifier */ fragment = strchr(tempurl, '#'); if (fragment) *fragment = '\0'; /* Get the scheme (protocol) */ startp = tempurl; p = strchr(startp, ':'); if (p) { *p = '\0'; if (strncmp(startp, "https", 5) == 0) { url->scheme = "https"; url->port = 443; if (strlen(startp) > 5) url->schemeopts = strdup(startp+5); } else if (strncmp(startp, "http", 4) == 0) { url->scheme = "http"; url->port = 80; if (strlen(startp) > 4) url->schemeopts = strdup(startp+4); } else if (strcmp(startp, "ftp") == 0) { url->scheme = "ftp"; url->port = 21; } else if (strcmp(startp, "ldap") == 0) { url->scheme = "ldap"; url->port = 389; } else if (strcmp(startp, "ldaps") == 0) { url->scheme = "ldaps"; url->port = 389; /* ldaps:// URL's are non-standard, and must use port 389+STARTTLS */ } else { /* Unknown scheme! */ errprintf("Unknown URL scheme '%s' in URL '%s'\n", startp, inputurl); url->scheme = strdup(startp); url->port = 0; } startp = (p+1); } else { errprintf("Malformed URL - no 'scheme:' in '%s'\n", inputurl); url->parseerror = 1; return; } if (strncmp(startp, "//", 2) == 0) { startp += 2; netloc = startp; p = strchr(startp, '/'); if (p) { *p = '\0'; startp = (p+1); } else startp += strlen(startp); } else { errprintf("Malformed URL missing '//' in '%s'\n", inputurl); url->parseerror = 2; return; } /* netloc is [username:password@]hostname[:port][=forcedIP] */ p = strchr(netloc, '@'); if (p) { *p = '\0'; url->auth = strdup(urlunescape(netloc)); netloc = (p+1); } p = strchr(netloc, '='); if (p) { url->ip = strdup(p+1); *p = '\0'; } p = strchr(netloc, ':'); if (p) { haveportspec = 1; *p = '\0'; url->port = atoi(p+1); } url->host = strdup(netloc); if (url->port == 0) { struct servent *svc = getservbyname(url->scheme, NULL); if (svc) url->port = ntohs(svc->s_port); else { errprintf("Unknown scheme (no port) '%s'\n", url->scheme); url->parseerror = 3; return; } } if (fragment) *fragment = '#'; url->relurl = malloc(strlen(startp) + 2); sprintf(url->relurl, "/%s", startp); if (url->auth == NULL) { /* See if we have it in the .netrc list */ loginlist_t *walk; load_netrc(); for (walk = loginhead; (walk && (strcmp(walk->host, url->host) != 0)); walk = walk->next) ; if (walk) url->auth = walk->auth; } /* Build the canonical form of this URL, free from all config artefacts */ canonurllen = 1; canonurllen += strlen(url->scheme)+3; /* Add room for the "://" */ canonurllen += strlen(url->host); canonurllen += 6; /* Max. length of a port spec. */ canonurllen += strlen(url->relurl); p = canonurl = (char *)malloc(canonurllen); p += sprintf(p, "%s://", url->scheme); /* * Dont include authentication here, since it * may show up in clear text on the info page. * And it is not used in URLs to access the site. * if (url->auth) p += sprintf(p, "%s@", url->auth); */ p += sprintf(p, "%s", url->host); if (haveportspec) p += sprintf(p, ":%d", url->port); p += sprintf(p, "%s", url->relurl); url->origform = canonurl; xfree(tempurl); return; } /* * If a column name is column=NAME, pick out NAME. */ static char *gethttpcolumn(char *inp, char **name) { char *nstart, *nend; nstart = inp; nend = strchr(nstart, ';'); if (nend == NULL) { *name = NULL; return inp; } *nend = '\0'; *name = strdup(nstart); *nend = ';'; return nend+1; } /* * Split a test-specification with a URL and optional * post-data/expect-data/expect-type data into the URL itself * and the other elements. * Un-escape data in the post- and expect-data. * Parse the URL. */ char *decode_url(char *testspec, weburl_t *weburl) { static weburl_t weburlbuf; static urlelem_t desturlbuf, proxyurlbuf; char *inp, *p; char *urlstart, *poststart, *postcontenttype, *expstart, *proxystart, *okstart, *notokstart; urlstart = poststart = postcontenttype = expstart = proxystart = okstart = notokstart = NULL; /* If called with no buffer, use our own static one */ if (weburl == NULL) { memset(&weburlbuf, 0, sizeof(weburl_t)); memset(&desturlbuf, 0, sizeof(urlelem_t)); memset(&proxyurlbuf, 0, sizeof(urlelem_t)); weburl = &weburlbuf; weburl->desturl = &desturlbuf; weburl->proxyurl = NULL; } else { memset(weburl, 0, sizeof(weburl_t)); weburl->desturl = (urlelem_t*) calloc(1, sizeof(urlelem_t)); weburl->proxyurl = NULL; } inp = strdup(testspec); if (strncmp(inp, "content=", 8) == 0) { weburl->testtype = WEBTEST_CONTENT; urlstart = inp+8; } else if (strncmp(inp, "cont;", 5) == 0) { weburl->testtype = WEBTEST_CONT; urlstart = inp+5; } else if (strncmp(inp, "cont=", 5) == 0) { weburl->testtype = WEBTEST_CONT; urlstart = gethttpcolumn(inp+5, &weburl->columnname); } else if (strncmp(inp, "nocont;", 7) == 0) { weburl->testtype = WEBTEST_NOCONT; urlstart = inp+7; } else if (strncmp(inp, "nocont=", 7) == 0) { weburl->testtype = WEBTEST_NOCONT; urlstart = gethttpcolumn(inp+7, &weburl->columnname); } else if (strncmp(inp, "post;", 5) == 0) { weburl->testtype = WEBTEST_POST; urlstart = inp+5; } else if (strncmp(inp, "post=", 5) == 0) { weburl->testtype = WEBTEST_POST; urlstart = gethttpcolumn(inp+5, &weburl->columnname); } else if (strncmp(inp, "nopost;", 7) == 0) { weburl->testtype = WEBTEST_NOPOST; urlstart = inp+7; } else if (strncmp(inp, "nopost=", 7) == 0) { weburl->testtype = WEBTEST_NOPOST; urlstart = gethttpcolumn(inp+7, &weburl->columnname); } else if (strncmp(inp, "soap;", 5) == 0) { weburl->testtype = WEBTEST_SOAP; urlstart = inp+5; } else if (strncmp(inp, "soap=", 5) == 0) { weburl->testtype = WEBTEST_SOAP; urlstart = gethttpcolumn(inp+5, &weburl->columnname); } else if (strncmp(inp, "nosoap;", 7) == 0) { weburl->testtype = WEBTEST_NOSOAP; urlstart = inp+7; } else if (strncmp(inp, "nosoap=", 7) == 0) { weburl->testtype = WEBTEST_NOSOAP; urlstart = gethttpcolumn(inp+7, &weburl->columnname); } else if (strncmp(inp, "type;", 5) == 0) { weburl->testtype = WEBTEST_TYPE; urlstart = inp+5; } else if (strncmp(inp, "type=", 5) == 0) { weburl->testtype = WEBTEST_TYPE; urlstart = gethttpcolumn(inp+5, &weburl->columnname); } else if (strncmp(inp, "httpstatus;", 11) == 0) { weburl->testtype = WEBTEST_STATUS; urlstart = strchr(inp, ';') + 1; } else if (strncmp(inp, "httpstatus=", 11) == 0) { weburl->testtype = WEBTEST_STATUS; urlstart = gethttpcolumn(inp+11, &weburl->columnname); } else if (strncmp(inp, "http=", 5) == 0) { /* Plain URL test, but in separate column */ weburl->testtype = WEBTEST_PLAIN; urlstart = gethttpcolumn(inp+5, &weburl->columnname); } else { /* Plain URL test */ weburl->testtype = WEBTEST_PLAIN; urlstart = inp; } switch (weburl->testtype) { case WEBTEST_PLAIN: break; case WEBTEST_CONT: case WEBTEST_NOCONT: case WEBTEST_TYPE: expstart = strchr(urlstart, ';'); if (expstart) { *expstart = '\0'; expstart++; } else { errprintf("content-check, but no content-data in '%s'\n", testspec); weburl->testtype = WEBTEST_PLAIN; } break; case WEBTEST_POST: case WEBTEST_NOPOST: case WEBTEST_SOAP: poststart = strchr(urlstart, ';'); if (poststart) { *poststart = '\0'; poststart++; /* See if "poststart" points to a content-type */ if (strncasecmp(poststart, "(content-type=", 14) == 0) { postcontenttype = poststart+14; poststart = strchr(postcontenttype, ')'); if (poststart) { *poststart = '\0'; poststart++; } } if (poststart) { expstart = strchr(poststart, ';'); if (expstart) { *expstart = '\0'; expstart++; } } if ((weburl->testtype == WEBTEST_NOPOST) && (!expstart)) { errprintf("content-check, but no content-data in '%s'\n", testspec); weburl->testtype = WEBTEST_PLAIN; } } else { errprintf("post-check, but no post-data in '%s'\n", testspec); weburl->testtype = WEBTEST_PLAIN; } break; case WEBTEST_STATUS: okstart = strchr(urlstart, ';'); if (okstart) { *okstart = '\0'; okstart++; notokstart = strchr(okstart, ';'); if (notokstart) { *notokstart = '\0'; notokstart++; } } if (okstart && (strlen(okstart) == 0)) okstart = NULL; if (notokstart && (strlen(notokstart) == 0)) notokstart = NULL; if (!okstart && !notokstart) { errprintf("HTTP status check, but no OK/not-OK status codes in '%s'\n", testspec); weburl->testtype = WEBTEST_PLAIN; } if (okstart) weburl->okcodes = strdup(okstart); if (notokstart) weburl->badcodes = strdup(notokstart); } if (poststart) getescapestring(poststart, &weburl->postdata, NULL); if (postcontenttype) getescapestring(postcontenttype, &weburl->postcontenttype, NULL); if (expstart) getescapestring(expstart, &weburl->expdata, NULL); if (obeybbproxysyntax) { /* * Ye olde Big Brother syntax for using a proxy on per-URL basis. */ p = strstr(urlstart, "/http://"); if (!p) p = strstr(urlstart, "/https://"); if (p) { proxystart = urlstart; urlstart = (p+1); *p = '\0'; } } parse_url(urlstart, weburl->desturl); if (proxystart) { if (weburl == &weburlbuf) { /* We use our own static buffers */ weburl->proxyurl = &proxyurlbuf; } else { /* User allocated buffers */ weburl->proxyurl = (urlelem_t *)malloc(sizeof(urlelem_t)); } parse_url(proxystart, weburl->proxyurl); } xfree(inp); return weburl->desturl->origform; } xymon-4.3.7/lib/rmdconst.h0000664000175000017500000002162511535462534015011 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This file is part of the Xymon monitor library, but was taken from the */ /* FreeBSD sources. It was originally written by Eric Young, and is NOT */ /* licensed under the GPL. Please adhere the original copyright notice below. */ /*----------------------------------------------------------------------------*/ /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * * This package is an SSL implementation written * by Eric Young (eay@cryptsoft.com). * The implementation was written so as to conform with Netscapes SSL. * * This library is free for commercial and non-commercial use as long as * the following conditions are aheared to. The following conditions * apply to all code found in this distribution, be it the RC4, RSA, * lhash, DES, etc., code; not just the SSL code. The SSL documentation * included with this distribution is covered by the same copyright terms * except that the holder is Tim Hudson (tjh@cryptsoft.com). * * Copyright remains Eric Young's, and as such any Copyright notices in * the code are not to be removed. * If this package is used in a product, Eric Young should be given attribution * as the author of the parts of the library used. * This can be in the form of a textual message at program startup or * in documentation (online or textual) provided with the package. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * "This product includes cryptographic software written by * Eric Young (eay@cryptsoft.com)" * The word 'cryptographic' can be left out if the rouines from the library * being used are not cryptographic related :-). * 4. If you include any Windows specific code (or a derivative thereof) from * the apps directory (application code) you must include an acknowledgement: * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" * * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * The licence and distribution terms for any publically available version or * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] */ #define KL0 0x00000000L #define KL1 0x5A827999L #define KL2 0x6ED9EBA1L #define KL3 0x8F1BBCDCL #define KL4 0xA953FD4EL #define KR0 0x50A28BE6L #define KR1 0x5C4DD124L #define KR2 0x6D703EF3L #define KR3 0x7A6D76E9L #define KR4 0x00000000L #define WL00 0 #define SL00 11 #define WL01 1 #define SL01 14 #define WL02 2 #define SL02 15 #define WL03 3 #define SL03 12 #define WL04 4 #define SL04 5 #define WL05 5 #define SL05 8 #define WL06 6 #define SL06 7 #define WL07 7 #define SL07 9 #define WL08 8 #define SL08 11 #define WL09 9 #define SL09 13 #define WL10 10 #define SL10 14 #define WL11 11 #define SL11 15 #define WL12 12 #define SL12 6 #define WL13 13 #define SL13 7 #define WL14 14 #define SL14 9 #define WL15 15 #define SL15 8 #define WL16 7 #define SL16 7 #define WL17 4 #define SL17 6 #define WL18 13 #define SL18 8 #define WL19 1 #define SL19 13 #define WL20 10 #define SL20 11 #define WL21 6 #define SL21 9 #define WL22 15 #define SL22 7 #define WL23 3 #define SL23 15 #define WL24 12 #define SL24 7 #define WL25 0 #define SL25 12 #define WL26 9 #define SL26 15 #define WL27 5 #define SL27 9 #define WL28 2 #define SL28 11 #define WL29 14 #define SL29 7 #define WL30 11 #define SL30 13 #define WL31 8 #define SL31 12 #define WL32 3 #define SL32 11 #define WL33 10 #define SL33 13 #define WL34 14 #define SL34 6 #define WL35 4 #define SL35 7 #define WL36 9 #define SL36 14 #define WL37 15 #define SL37 9 #define WL38 8 #define SL38 13 #define WL39 1 #define SL39 15 #define WL40 2 #define SL40 14 #define WL41 7 #define SL41 8 #define WL42 0 #define SL42 13 #define WL43 6 #define SL43 6 #define WL44 13 #define SL44 5 #define WL45 11 #define SL45 12 #define WL46 5 #define SL46 7 #define WL47 12 #define SL47 5 #define WL48 1 #define SL48 11 #define WL49 9 #define SL49 12 #define WL50 11 #define SL50 14 #define WL51 10 #define SL51 15 #define WL52 0 #define SL52 14 #define WL53 8 #define SL53 15 #define WL54 12 #define SL54 9 #define WL55 4 #define SL55 8 #define WL56 13 #define SL56 9 #define WL57 3 #define SL57 14 #define WL58 7 #define SL58 5 #define WL59 15 #define SL59 6 #define WL60 14 #define SL60 8 #define WL61 5 #define SL61 6 #define WL62 6 #define SL62 5 #define WL63 2 #define SL63 12 #define WL64 4 #define SL64 9 #define WL65 0 #define SL65 15 #define WL66 5 #define SL66 5 #define WL67 9 #define SL67 11 #define WL68 7 #define SL68 6 #define WL69 12 #define SL69 8 #define WL70 2 #define SL70 13 #define WL71 10 #define SL71 12 #define WL72 14 #define SL72 5 #define WL73 1 #define SL73 12 #define WL74 3 #define SL74 13 #define WL75 8 #define SL75 14 #define WL76 11 #define SL76 11 #define WL77 6 #define SL77 8 #define WL78 15 #define SL78 5 #define WL79 13 #define SL79 6 #define WR00 5 #define SR00 8 #define WR01 14 #define SR01 9 #define WR02 7 #define SR02 9 #define WR03 0 #define SR03 11 #define WR04 9 #define SR04 13 #define WR05 2 #define SR05 15 #define WR06 11 #define SR06 15 #define WR07 4 #define SR07 5 #define WR08 13 #define SR08 7 #define WR09 6 #define SR09 7 #define WR10 15 #define SR10 8 #define WR11 8 #define SR11 11 #define WR12 1 #define SR12 14 #define WR13 10 #define SR13 14 #define WR14 3 #define SR14 12 #define WR15 12 #define SR15 6 #define WR16 6 #define SR16 9 #define WR17 11 #define SR17 13 #define WR18 3 #define SR18 15 #define WR19 7 #define SR19 7 #define WR20 0 #define SR20 12 #define WR21 13 #define SR21 8 #define WR22 5 #define SR22 9 #define WR23 10 #define SR23 11 #define WR24 14 #define SR24 7 #define WR25 15 #define SR25 7 #define WR26 8 #define SR26 12 #define WR27 12 #define SR27 7 #define WR28 4 #define SR28 6 #define WR29 9 #define SR29 15 #define WR30 1 #define SR30 13 #define WR31 2 #define SR31 11 #define WR32 15 #define SR32 9 #define WR33 5 #define SR33 7 #define WR34 1 #define SR34 15 #define WR35 3 #define SR35 11 #define WR36 7 #define SR36 8 #define WR37 14 #define SR37 6 #define WR38 6 #define SR38 6 #define WR39 9 #define SR39 14 #define WR40 11 #define SR40 12 #define WR41 8 #define SR41 13 #define WR42 12 #define SR42 5 #define WR43 2 #define SR43 14 #define WR44 10 #define SR44 13 #define WR45 0 #define SR45 13 #define WR46 4 #define SR46 7 #define WR47 13 #define SR47 5 #define WR48 8 #define SR48 15 #define WR49 6 #define SR49 5 #define WR50 4 #define SR50 8 #define WR51 1 #define SR51 11 #define WR52 3 #define SR52 14 #define WR53 11 #define SR53 14 #define WR54 15 #define SR54 6 #define WR55 0 #define SR55 14 #define WR56 5 #define SR56 6 #define WR57 12 #define SR57 9 #define WR58 2 #define SR58 12 #define WR59 13 #define SR59 9 #define WR60 9 #define SR60 12 #define WR61 7 #define SR61 5 #define WR62 10 #define SR62 15 #define WR63 14 #define SR63 8 #define WR64 12 #define SR64 8 #define WR65 15 #define SR65 5 #define WR66 10 #define SR66 12 #define WR67 4 #define SR67 9 #define WR68 1 #define SR68 12 #define WR69 5 #define SR69 5 #define WR70 8 #define SR70 14 #define WR71 7 #define SR71 6 #define WR72 6 #define SR72 8 #define WR73 2 #define SR73 13 #define WR74 13 #define SR74 6 #define WR75 14 #define SR75 5 #define WR76 0 #define SR76 15 #define WR77 3 #define SR77 13 #define WR78 9 #define SR78 11 #define WR79 11 #define SR79 11 xymon-4.3.7/lib/timing.h0000664000175000017500000000163311615341300014426 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __TIMING_H__ #define __TIMING_H__ extern int timing; extern void add_timestamp(const char *msg); extern void show_timestamps(char **buffer); extern long total_runtime(void); #endif xymon-4.3.7/lib/headfoot.c0000664000175000017500000013444011630612042014727 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for handling header- and footer-files. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: headfoot.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "version.h" /* Stuff for headfoot - variables we can set dynamically */ static char *hostenv_hikey = NULL; static char *hostenv_host = NULL; static char *hostenv_ip = NULL; static char *hostenv_svc = NULL; static char *hostenv_color = NULL; static char *hostenv_pagepath = NULL; static time_t hostenv_reportstart = 0; static time_t hostenv_reportend = 0; static char *hostenv_repwarn = NULL; static char *hostenv_reppanic = NULL; static time_t hostenv_snapshot = 0; static char *hostenv_logtime = NULL; static char *hostenv_templatedir = NULL; static int hostenv_refresh = 60; static char *statusboard = NULL; static char *scheduleboard = NULL; static char *hostpattern_text = NULL; static pcre *hostpattern = NULL; static char *pagepattern_text = NULL; static pcre *pagepattern = NULL; static char *ippattern_text = NULL; static pcre *ippattern = NULL; static void * hostnames; static void * testnames; typedef struct treerec_t { char *name; int flag; } treerec_t; static int backdays = 0, backhours = 0, backmins = 0, backsecs = 0; static char hostenv_eventtimestart[20]; static char hostenv_eventtimeend[20]; typedef struct listrec_t { char *name, *val, *extra; int selected; struct listrec_t *next; } listrec_t; typedef struct listpool_t { char *name; struct listrec_t *listhead, *listtail; struct listpool_t *next; } listpool_t; static listpool_t *listpoolhead = NULL; typedef struct bodystorage_t { char *id; strbuffer_t *txt; } bodystorage_t; static void clearflags(void * tree) { xtreePos_t handle; treerec_t *rec; if (!tree) return; for (handle = xtreeFirst(tree); (handle != xtreeEnd(tree)); handle = xtreeNext(tree, handle)) { rec = (treerec_t *)xtreeData(tree, handle); rec->flag = 0; } } void sethostenv(char *host, char *ip, char *svc, char *color, char *hikey) { if (hostenv_hikey) xfree(hostenv_hikey); if (hostenv_host) xfree(hostenv_host); if (hostenv_ip) xfree(hostenv_ip); if (hostenv_svc) xfree(hostenv_svc); if (hostenv_color) xfree(hostenv_color); hostenv_hikey = (hikey ? strdup(htmlquoted(hikey)) : NULL); hostenv_host = strdup(htmlquoted(host)); hostenv_ip = strdup(htmlquoted(ip)); hostenv_svc = strdup(htmlquoted(svc)); hostenv_color = strdup(color); } void sethostenv_report(time_t reportstart, time_t reportend, double repwarn, double reppanic) { if (hostenv_repwarn == NULL) hostenv_repwarn = malloc(10); if (hostenv_reppanic == NULL) hostenv_reppanic = malloc(10); hostenv_reportstart = reportstart; hostenv_reportend = reportend; sprintf(hostenv_repwarn, "%.2f", repwarn); sprintf(hostenv_reppanic, "%.2f", reppanic); } void sethostenv_snapshot(time_t snapshot) { hostenv_snapshot = snapshot; } void sethostenv_histlog(char *histtime) { if (hostenv_logtime) xfree(hostenv_logtime); hostenv_logtime = strdup(histtime); } void sethostenv_template(char *dir) { if (hostenv_templatedir) xfree(hostenv_templatedir); hostenv_templatedir = strdup(dir); } void sethostenv_refresh(int n) { hostenv_refresh = n; } void sethostenv_pagepath(char *s) { if (!s) return; if (hostenv_pagepath) xfree(hostenv_pagepath); hostenv_pagepath = strdup(s); } void sethostenv_filter(char *hostptn, char *pageptn, char *ipptn) { const char *errmsg; int errofs; if (hostpattern_text) xfree(hostpattern_text); if (hostpattern) { pcre_free(hostpattern); hostpattern = NULL; } if (pagepattern_text) xfree(pagepattern_text); if (pagepattern) { pcre_free(pagepattern); pagepattern = NULL; } if (ippattern_text) xfree(ippattern_text); if (ippattern) { pcre_free(ippattern); ippattern = NULL; } /* Setup the pattern to match names against */ if (hostptn) { hostpattern_text = strdup(hostptn); hostpattern = pcre_compile(hostptn, PCRE_CASELESS, &errmsg, &errofs, NULL); } if (pageptn) { pagepattern_text = strdup(pageptn); pagepattern = pcre_compile(pageptn, PCRE_CASELESS, &errmsg, &errofs, NULL); } if (ipptn) { ippattern_text = strdup(ipptn); ippattern = pcre_compile(ipptn, PCRE_CASELESS, &errmsg, &errofs, NULL); } } static listpool_t *find_listpool(char *listname) { listpool_t *pool = NULL; listrec_t *zombie; if (!listname) listname = ""; for (pool = listpoolhead; (pool && strcmp(pool->name, listname)); pool = pool->next); if (!pool) { pool = (listpool_t *)calloc(1, sizeof(listpool_t)); pool->name = strdup(listname); pool->next = listpoolhead; listpoolhead = pool; } return pool; } void sethostenv_clearlist(char *listname) { listpool_t *pool = NULL; listrec_t *zombie; pool = find_listpool(listname); while (pool->listhead) { zombie = pool->listhead; pool->listhead = pool->listhead->next; xfree(zombie->name); xfree(zombie->val); xfree(zombie); } } void sethostenv_addtolist(char *listname, char *name, char *val, char *extra, int selected) { listpool_t *pool = NULL; listrec_t *newitem = (listrec_t *)calloc(1, sizeof(listrec_t)); pool = find_listpool(listname); newitem->name = strdup(name); newitem->val = strdup(val); newitem->extra = (extra ? strdup(extra) : NULL); newitem->selected = selected; if (pool->listtail) { pool->listtail->next = newitem; pool->listtail = newitem; } else { pool->listhead = pool->listtail = newitem; } } static int critackttprio = 0; static char *critackttgroup = NULL; static char *critackttextra = NULL; static char *ackinfourl = NULL; static char *critackdocurl = NULL; void sethostenv_critack(int prio, char *ttgroup, char *ttextra, char *infourl, char *docurl) { critackttprio = prio; if (critackttgroup) xfree(critackttgroup); critackttgroup = strdup((ttgroup && *ttgroup) ? ttgroup : " "); if (critackttextra) xfree(critackttextra); critackttextra = strdup((ttextra && *ttextra) ? ttextra : " "); if (ackinfourl) xfree(ackinfourl); ackinfourl = strdup(infourl); if (critackdocurl) xfree(critackdocurl); critackdocurl = strdup((docurl && *docurl) ? docurl : ""); } static char *criteditupdinfo = NULL; static int criteditprio = -1; static char *criteditgroup = NULL; static time_t criteditstarttime = 0; static time_t criteditendtime = 0; static char *criteditextra = NULL; static char *criteditslawkdays = NULL; static char *criteditslastart = NULL; static char *criteditslaend = NULL; static char **criteditclonelist = NULL; static int criteditclonesize = 0; void sethostenv_critedit(char *updinfo, int prio, char *group, time_t starttime, time_t endtime, char *crittime, char *extra) { char *p; if (criteditupdinfo) xfree(criteditupdinfo); criteditupdinfo = strdup(updinfo); criteditprio = prio; criteditstarttime = starttime; criteditendtime = endtime; if (criteditgroup) xfree(criteditgroup); criteditgroup = strdup(group ? group : ""); if (criteditextra) xfree(criteditextra); criteditextra = strdup(extra ? extra : ""); if (criteditslawkdays) xfree(criteditslawkdays); criteditslawkdays = criteditslastart = criteditslaend = NULL; if (crittime) { criteditslawkdays = strdup(crittime); p = strchr(criteditslawkdays, ':'); if (p) { *p = '\0'; criteditslastart = p+1; p = strchr(criteditslastart, ':'); if (p) { *p = '\0'; criteditslaend = p+1; } } if (criteditslawkdays && (!criteditslastart || !criteditslaend)) { xfree(criteditslawkdays); criteditslawkdays = criteditslastart = criteditslaend = NULL; } } } void sethostenv_critclonelist_clear(void) { int i; if (criteditclonelist) { for (i=0; (criteditclonelist[i]); i++) xfree(criteditclonelist[i]); xfree(criteditclonelist); } criteditclonelist = malloc(sizeof(char *)); criteditclonelist[0] = NULL; criteditclonesize = 0; } void sethostenv_critclonelist_add(char *hostname) { char *p; criteditclonelist = (char **)realloc(criteditclonelist, (criteditclonesize + 2)*sizeof(char *)); criteditclonelist[criteditclonesize] = strdup(hostname); p = criteditclonelist[criteditclonesize]; criteditclonelist[++criteditclonesize] = NULL; p += (strlen(p) - 1); if (*p == '=') *p = '\0'; } void sethostenv_backsecs(int seconds) { backdays = seconds / 86400; seconds -= backdays*86400; backhours = seconds / 3600; seconds -= backhours*3600; backmins = seconds / 60; seconds -= backmins*60; backsecs = seconds; } void sethostenv_eventtime(time_t starttime, time_t endtime) { *hostenv_eventtimestart = *hostenv_eventtimeend = '\0'; if (starttime) strftime(hostenv_eventtimestart, sizeof(hostenv_eventtimestart), "%Y/%m/%d@%H:%M:%S", localtime(&starttime)); if (endtime) strftime(hostenv_eventtimeend, sizeof(hostenv_eventtimeend), "%Y/%m/%d@%H:%M:%S", localtime(&endtime)); } char *wkdayselect(char wkday, char *valtxt, int isdefault) { static char result[100]; char *selstr; if (!criteditslawkdays) { if (isdefault) selstr = "SELECTED"; else selstr = ""; } else { if (strchr(criteditslawkdays, wkday)) selstr = "SELECTED"; else selstr = ""; } sprintf(result, "\n", wkday, selstr, valtxt); return result; } static void *wanted_host(char *hostname) { void *hinfo = hostinfo(hostname); int result, ovector[30]; if (!hinfo) return NULL; if (hostpattern) { result = pcre_exec(hostpattern, NULL, hostname, strlen(hostname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (result < 0) return NULL; } if (pagepattern && hinfo) { char *pname = xmh_item(hinfo, XMH_PAGEPATH); result = pcre_exec(pagepattern, NULL, pname, strlen(pname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (result < 0) return NULL; } if (ippattern && hinfo) { char *hostip = xmh_item(hinfo, XMH_IP); result = pcre_exec(ippattern, NULL, hostip, strlen(hostip), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (result < 0) return NULL; } return hinfo; } static void fetch_board(void) { static int haveboard = 0; char *walk, *eoln; sendreturn_t *sres; if (haveboard) return; sres = newsendreturnbuf(1, NULL); if (sendmessage("xymondboard fields=hostname,testname,disabletime,dismsg", NULL, XYMON_TIMEOUT, sres) != XYMONSEND_OK) { freesendreturnbuf(sres); return; } haveboard = 1; statusboard = getsendreturnstr(sres, 1); freesendreturnbuf(sres); hostnames = xtreeNew(strcasecmp); testnames = xtreeNew(strcasecmp); walk = statusboard; while (walk) { eoln = strchr(walk, '\n'); if (eoln) *eoln = '\0'; if (strlen(walk) && (strncmp(walk, "summary|", 8) != 0)) { char *buf, *hname = NULL, *tname = NULL; treerec_t *newrec; buf = strdup(walk); hname = gettok(buf, "|"); if (hname && wanted_host(hname) && hostinfo(hname)) { newrec = (treerec_t *)malloc(sizeof(treerec_t)); newrec->name = strdup(hname); newrec->flag = 0; xtreeAdd(hostnames, newrec->name, newrec); tname = gettok(NULL, "|"); if (tname) { newrec = (treerec_t *)malloc(sizeof(treerec_t)); newrec->name = strdup(tname); newrec->flag = 0; xtreeAdd(testnames, strdup(tname), newrec); } } xfree(buf); } if (eoln) { *eoln = '\n'; walk = eoln + 1; } else walk = NULL; } sres = newsendreturnbuf(1, NULL); if (sendmessage("schedule", NULL, XYMON_TIMEOUT, sres) != XYMONSEND_OK) { freesendreturnbuf(sres); return; } scheduleboard = getsendreturnstr(sres, 1); freesendreturnbuf(sres); } static char *eventreport_timestring(time_t timestamp) { static char result[20]; strftime(result, sizeof(result), "%Y/%m/%d@%H:%M:%S", localtime(×tamp)); return result; } static void build_pagepath_dropdown(FILE *output) { void * ptree; void *hwalk; xtreePos_t handle; ptree = xtreeNew(strcmp); for (hwalk = first_host(); (hwalk); hwalk = next_host(hwalk, 0)) { char *path = xmh_item(hwalk, XMH_PAGEPATH); char *ptext; handle = xtreeFind(ptree, path); if (handle != xtreeEnd(ptree)) continue; ptext = xmh_item(hwalk, XMH_PAGEPATHTITLE); xtreeAdd(ptree, ptext, path); } for (handle = xtreeFirst(ptree); (handle != xtreeEnd(ptree)); handle = xtreeNext(ptree, handle)) { fprintf(output, "\n", (char *)xtreeData(ptree, handle), xtreeKey(ptree, handle)); } xtreeDestroy(ptree); } char *xymonbody(char *id) { static void * bodystorage; static int firsttime = 1; xtreePos_t handle; bodystorage_t *bodyelement; strbuffer_t *rawdata, *parseddata; char *envstart, *envend, *outpos; char *idtag, *idval; int idtaglen; if (firsttime) { bodystorage = xtreeNew(strcmp); firsttime = 0; } idtaglen = strspn(id, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); idtag = (char *)malloc(idtaglen + 1); strncpy(idtag, id, idtaglen); *(idtag+idtaglen) = '\0'; handle = xtreeFind(bodystorage, idtag); if (handle != xtreeEnd(bodystorage)) { bodyelement = (bodystorage_t *)xtreeData(bodystorage, handle); xfree(idtag); return STRBUF(bodyelement->txt); } rawdata = newstrbuffer(0); idval = xgetenv(idtag); if (idval == NULL) return ""; if (strncmp(idval, "file:", 5) == 0) { FILE *fd; strbuffer_t *inbuf = newstrbuffer(0); fd = stackfopen(idval+5, "r", NULL); if (fd != NULL) { while (stackfgets(inbuf, NULL)) addtostrbuffer(rawdata, inbuf); stackfclose(fd); } freestrbuffer(inbuf); } else { addtobuffer(rawdata, idval); } /* Output the body data, but expand any environment variables along the way */ parseddata = newstrbuffer(0); outpos = STRBUF(rawdata); while (*outpos) { envstart = strchr(outpos, '$'); if (envstart) { char savechar; char *envval = NULL; *envstart = '\0'; addtobuffer(parseddata, outpos); envstart++; envend = envstart + strspn(envstart, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); savechar = *envend; *envend = '\0'; if (*envstart) envval = xgetenv(envstart); *envend = savechar; outpos = envend; if (envval) { addtobuffer(parseddata, envval); } else { addtobuffer(parseddata, "$"); addtobuffer(parseddata, envstart); } } else { addtobuffer(parseddata, outpos); outpos += strlen(outpos); } } freestrbuffer(rawdata); bodyelement = (bodystorage_t *)calloc(1, sizeof(bodystorage_t)); bodyelement->id = idtag; bodyelement->txt = parseddata; xtreeAdd(bodystorage, bodyelement->id, bodyelement); return STRBUF(bodyelement->txt); } typedef struct distest_t { char *name; char *cause; time_t until; struct distest_t *next; } distest_t; typedef struct dishost_t { char *name; struct distest_t *tests; struct dishost_t *next; } dishost_t; void output_parsed(FILE *output, char *templatedata, int bgcolor, time_t selectedtime) { char *t_start, *t_next; char savechar; time_t now = getcurrenttime(NULL); time_t yesterday = getcurrenttime(NULL) - 86400; struct tm *nowtm; for (t_start = templatedata, t_next = strchr(t_start, '&'); (t_next); ) { /* Copy from t_start to t_next unchanged */ *t_next = '\0'; t_next++; fprintf(output, "%s", t_start); /* Find token */ t_start = t_next; /* Dont include lower-case letters - reserve those for eg " " */ t_next += strspn(t_next, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); savechar = *t_next; *t_next = '\0'; if ((strcmp(t_start, "XYMWEBDATE") == 0) || (strcmp(t_start, "BBDATE") == 0)) { char *datefmt = xgetenv("XYMONDATEFORMAT"); char datestr[100]; MEMDEFINE(datestr); /* * If no XYMONDATEFORMAT setting, use a format string that * produces output similar to that from ctime() */ if (datefmt == NULL) datefmt = "%a %b %d %H:%M:%S %Y\n"; if (hostenv_reportstart != 0) { char starttime[20], endtime[20]; MEMDEFINE(starttime); MEMDEFINE(endtime); strftime(starttime, sizeof(starttime), "%b %d %Y", localtime(&hostenv_reportstart)); strftime(endtime, sizeof(endtime), "%b %d %Y", localtime(&hostenv_reportend)); if (strcmp(starttime, endtime) == 0) fprintf(output, "%s", starttime); else fprintf(output, "%s - %s", starttime, endtime); MEMUNDEFINE(starttime); MEMUNDEFINE(endtime); } else if (hostenv_snapshot != 0) { strftime(datestr, sizeof(datestr), datefmt, localtime(&hostenv_snapshot)); fprintf(output, "%s", datestr); } else { strftime(datestr, sizeof(datestr), datefmt, localtime(&now)); fprintf(output, "%s", datestr); } MEMUNDEFINE(datestr); } else if ((strcmp(t_start, "XYMWEBBACKGROUND") == 0) || (strcmp(t_start, "BBBACKGROUND") == 0)) { fprintf(output, "%s", colorname(bgcolor)); } else if ((strcmp(t_start, "XYMWEBCOLOR") == 0) || (strcmp(t_start, "BBCOLOR") == 0)) fprintf(output, "%s", hostenv_color); else if ((strcmp(t_start, "XYMWEBSVC") == 0) || (strcmp(t_start, "BBSVC") == 0)) fprintf(output, "%s", hostenv_svc); else if ((strcmp(t_start, "XYMWEBHOST") == 0) || (strcmp(t_start, "BBHOST") == 0)) fprintf(output, "%s", hostenv_host); else if ((strcmp(t_start, "XYMWEBHIKEY") == 0) || (strcmp(t_start, "BBHIKEY") == 0)) fprintf(output, "%s", (hostenv_hikey ? hostenv_hikey : hostenv_host)); else if ((strcmp(t_start, "XYMWEBIP") == 0) || (strcmp(t_start, "BBIP") == 0)) fprintf(output, "%s", hostenv_ip); else if ((strcmp(t_start, "XYMWEBIPNAME") == 0) || (strcmp(t_start, "BBIPNAME") == 0)) { if (strcmp(hostenv_ip, "0.0.0.0") == 0) fprintf(output, "%s", hostenv_host); else fprintf(output, "%s", hostenv_ip); } else if ((strcmp(t_start, "XYMONREPWARN") == 0) || (strcmp(t_start, "BBREPWARN") == 0)) fprintf(output, "%s", hostenv_repwarn); else if ((strcmp(t_start, "XYMONREPPANIC") == 0) || (strcmp(t_start, "BBREPPANIC") == 0)) fprintf(output, "%s", hostenv_reppanic); else if (strcmp(t_start, "LOGTIME") == 0) fprintf(output, "%s", (hostenv_logtime ? hostenv_logtime : "")); else if ((strcmp(t_start, "XYMWEBREFRESH") == 0) || (strcmp(t_start, "BBREFRESH") == 0)) fprintf(output, "%d", hostenv_refresh); else if ((strcmp(t_start, "XYMWEBPAGEPATH") == 0) || (strcmp(t_start, "BBPAGEPATH") == 0)) fprintf(output, "%s", (hostenv_pagepath ? hostenv_pagepath : "")); else if (strcmp(t_start, "REPMONLIST") == 0) { int i; struct tm monthtm; char mname[20]; char *selstr; MEMDEFINE(mname); nowtm = localtime(&selectedtime); for (i=1; (i <= 12); i++) { if (i == (nowtm->tm_mon + 1)) selstr = "SELECTED"; else selstr = ""; monthtm.tm_mon = (i-1); monthtm.tm_mday = 1; monthtm.tm_year = nowtm->tm_year; monthtm.tm_hour = monthtm.tm_min = monthtm.tm_sec = monthtm.tm_isdst = 0; strftime(mname, sizeof(mname)-1, "%B", &monthtm); fprintf(output, "\n", rec->name, rec->name); } } } else if (strcmp(t_start, "JSHOSTLIST") == 0) { xtreePos_t handle; fetch_board(); clearflags(testnames); fprintf(output, "var hosts = new Array();\n"); fprintf(output, "hosts[\"ALL\"] = [ \"ALL\""); for (handle = xtreeFirst(testnames); (handle != xtreeEnd(testnames)); handle = xtreeNext(testnames, handle)) { treerec_t *rec = xtreeData(testnames, handle); fprintf(output, ", \"%s\"", rec->name); } fprintf(output, " ];\n"); for (handle = xtreeFirst(hostnames); (handle != xtreeEnd(hostnames)); handle = xtreeNext(hostnames, handle)) { treerec_t *hrec = xtreeData(hostnames, handle); if (wanted_host(hrec->name)) { xtreePos_t thandle; treerec_t *trec; char *bwalk, *tname, *p; char *key = (char *)malloc(strlen(hrec->name) + 3); /* Setup the search key and find the first occurrence. */ sprintf(key, "\n%s|", hrec->name); if (strncmp(statusboard, (key+1), strlen(key+1)) == 0) bwalk = statusboard; else { bwalk = strstr(statusboard, key); if (bwalk) bwalk++; } while (bwalk) { tname = bwalk + strlen(key+1); p = strchr(tname, '|'); if (p) *p = '\0'; if ( (strcmp(tname, xgetenv("INFOCOLUMN")) != 0) && (strcmp(tname, xgetenv("TRENDSCOLUMN")) != 0) ) { thandle = xtreeFind(testnames, tname); if (thandle != xtreeEnd(testnames)) { trec = (treerec_t *)xtreeData(testnames, thandle); trec->flag = 1; } } if (p) *p = '|'; bwalk = strstr(tname, key); if (bwalk) bwalk++; } fprintf(output, "hosts[\"%s\"] = [ \"ALL\"", hrec->name); for (thandle = xtreeFirst(testnames); (thandle != xtreeEnd(testnames)); thandle = xtreeNext(testnames, thandle)) { trec = (treerec_t *)xtreeData(testnames, thandle); if (trec->flag == 0) continue; trec->flag = 0; fprintf(output, ", \"%s\"", trec->name); } fprintf(output, " ];\n"); } } } else if (strcmp(t_start, "TESTLIST") == 0) { xtreePos_t handle; treerec_t *rec; fetch_board(); for (handle = xtreeFirst(testnames); (handle != xtreeEnd(testnames)); handle = xtreeNext(testnames, handle)) { rec = (treerec_t *)xtreeData(testnames, handle); fprintf(output, "\n", rec->name, rec->name); } } else if (strcmp(t_start, "DISABLELIST") == 0) { char *walk, *eoln; dishost_t *dhosts = NULL, *hwalk, *hprev; distest_t *twalk; fetch_board(); clearflags(testnames); walk = statusboard; while (walk) { eoln = strchr(walk, '\n'); if (eoln) *eoln = '\0'; if (*walk) { char *buf, *hname, *tname, *dismsg, *p; time_t distime; xtreePos_t thandle; treerec_t *rec; buf = strdup(walk); hname = tname = dismsg = NULL; distime = 0; hname = gettok(buf, "|"); if (hname) tname = gettok(NULL, "|"); if (tname) { p = gettok(NULL, "|"); if (p) distime = atol(p); } if (distime) dismsg = gettok(NULL, "|\n"); if (hname && tname && (distime != 0) && dismsg && wanted_host(hname)) { nldecode(dismsg); hwalk = dhosts; hprev = NULL; while (hwalk && (strcasecmp(hname, hwalk->name) > 0)) { hprev = hwalk; hwalk = hwalk->next; } if (!hwalk || (strcasecmp(hname, hwalk->name) != 0)) { dishost_t *newitem = (dishost_t *) malloc(sizeof(dishost_t)); newitem->name = strdup(hname); newitem->tests = NULL; newitem->next = hwalk; if (!hprev) dhosts = newitem; else hprev->next = newitem; hwalk = newitem; } twalk = (distest_t *) malloc(sizeof(distest_t)); twalk->name = strdup(tname); twalk->cause = strdup(dismsg); twalk->until = distime; twalk->next = hwalk->tests; hwalk->tests = twalk; thandle = xtreeFind(testnames, tname); if (thandle != xtreeEnd(testnames)) { rec = xtreeData(testnames, thandle); rec->flag = 1; } } xfree(buf); } if (eoln) { *eoln = '\n'; walk = eoln+1; } else { walk = NULL; } } if (dhosts) { /* Insert the "All hosts" record first. */ hwalk = (dishost_t *)calloc(1, sizeof(dishost_t)); hwalk->next = dhosts; dhosts = hwalk; for (hwalk = dhosts; (hwalk); hwalk = hwalk->next) { fprintf(output, ""); fprintf(output, ""); fprintf(output,"
\n", xgetenv("SECURECGIBINURL")); fprintf(output, "\n", (hwalk->name ? hwalk->name : "")); fprintf(output, "\n"); fprintf(output, "", (hwalk->name ? hwalk->name : "All hosts")); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "
%s
\n"); if (hwalk->name) { fprintf(output, "\n", hwalk->name); fprintf(output, "\n"); } else { dishost_t *hw2; fprintf(output, "\n"); } fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "
\n"); fprintf(output, "
\n"); fprintf(output, "\n"); fprintf(output, "\n"); } } else { fprintf(output, "No tests disabled\n"); } } else if (strcmp(t_start, "SCHEDULELIST") == 0) { char *walk, *eoln; int gotany = 0; fetch_board(); walk = scheduleboard; while (walk) { eoln = strchr(walk, '\n'); if (eoln) *eoln = '\0'; if (*walk) { int id = 0; time_t executiontime = 0; char *sender = NULL, *cmd = NULL, *buf, *p, *eoln; buf = strdup(walk); p = gettok(buf, "|"); if (p) { id = atoi(p); p = gettok(NULL, "|"); } if (p) { executiontime = atoi(p); p = gettok(NULL, "|"); } if (p) { sender = p; p = gettok(NULL, "|"); } if (p) { cmd = p; } if (id && executiontime && sender && cmd) { gotany = 1; nldecode(cmd); fprintf(output, "\n"); fprintf(output, "%s\n", ctime(&executiontime)); fprintf(output, ""); p = cmd; while ((eoln = strchr(p, '\n')) != NULL) { *eoln = '\0'; fprintf(output, "%s
", p); p = (eoln + 1); } fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "
\n", xgetenv("SECURECGIBINURL")); fprintf(output, "\n", id); fprintf(output, "\n"); fprintf(output, "
\n"); fprintf(output, "\n"); } xfree(buf); } if (eoln) { *eoln = '\n'; walk = eoln+1; } else { walk = NULL; } } if (!gotany) { fprintf(output, "No tasks scheduled\n"); } } else if (strncmp(t_start, "GENERICLIST", strlen("GENERICLIST")) == 0) { listpool_t *pool = find_listpool(t_start + strlen("GENERICLIST")); listrec_t *walk; for (walk = pool->listhead; (walk); walk = walk->next) fprintf(output, "\n", walk->val, (walk->selected ? "SELECTED" : ""), (walk->extra ? walk->extra : ""), walk->name); } else if (strcmp(t_start, "CRITACKTTPRIO") == 0) fprintf(output, "%d", critackttprio); else if (strcmp(t_start, "CRITACKTTGROUP") == 0) fprintf(output, "%s", critackttgroup); else if (strcmp(t_start, "CRITACKTTEXTRA") == 0) fprintf(output, "%s", critackttextra); else if (strcmp(t_start, "CRITACKINFOURL") == 0) fprintf(output, "%s", ackinfourl); else if (strcmp(t_start, "CRITACKDOCURL") == 0) fprintf(output, "%s", critackdocurl); else if (strcmp(t_start, "CRITEDITUPDINFO") == 0) { fprintf(output, "%s", criteditupdinfo); } else if (strcmp(t_start, "CRITEDITPRIOLIST") == 0) { int i; char *selstr; for (i=1; (i <= 3); i++) { selstr = ((i == criteditprio) ? "SELECTED" : ""); fprintf(output, "\n", i, selstr, i); } } else if (strcmp(t_start, "CRITEDITCLONELIST") == 0) { int i; for (i=0; (criteditclonelist[i]); i++) fprintf(output, "\n", criteditclonelist[i], criteditclonelist[i]); } else if (strcmp(t_start, "CRITEDITGROUP") == 0) { fprintf(output, "%s", criteditgroup); } else if (strcmp(t_start, "CRITEDITEXTRA") == 0) { fprintf(output, "%s", criteditextra); } else if (strcmp(t_start, "CRITEDITWKDAYS") == 0) { fprintf(output, "%s", wkdayselect('*', "All days", 1)); fprintf(output, "%s", wkdayselect('W', "Mon-Fri", 0)); fprintf(output, "%s", wkdayselect('1', "Monday", 0)); fprintf(output, "%s", wkdayselect('2', "Tuesday", 0)); fprintf(output, "%s", wkdayselect('3', "Wednesday", 0)); fprintf(output, "%s", wkdayselect('4', "Thursday", 0)); fprintf(output, "%s", wkdayselect('5', "Friday", 0)); fprintf(output, "%s", wkdayselect('6', "Saturday", 0)); fprintf(output, "%s", wkdayselect('0', "Sunday", 0)); } else if (strcmp(t_start, "CRITEDITSTART") == 0) { int i, curr; char *selstr; curr = (criteditslastart ? (atoi(criteditslastart) / 100) : 0); for (i=0; (i <= 23); i++) { selstr = ((i == curr) ? "SELECTED" : ""); fprintf(output, "\n", i, selstr, i); } } else if (strcmp(t_start, "CRITEDITEND") == 0) { int i, curr; char *selstr; curr = (criteditslaend ? (atoi(criteditslaend) / 100) : 24); for (i=1; (i <= 24); i++) { selstr = ((i == curr) ? "SELECTED" : ""); fprintf(output, "\n", i, selstr, i); } } else if (strncmp(t_start, "CRITEDITDAYLIST", 13) == 0) { time_t t = ((*(t_start+13) == '1') ? criteditstarttime : criteditendtime); char *defstr = ((*(t_start+13) == '1') ? "Now" : "Never"); int i; char *selstr; struct tm *tm; tm = localtime(&t); selstr = ((t == 0) ? "SELECTED" : ""); fprintf(output, "\n", selstr, defstr); for (i=1; (i <= 31); i++) { selstr = ( (t && (tm->tm_mday == i)) ? "SELECTED" : ""); fprintf(output, "\n", i, selstr, i); } } else if (strncmp(t_start, "CRITEDITMONLIST", 13) == 0) { time_t t = ((*(t_start+13) == '1') ? criteditstarttime : criteditendtime); char *defstr = ((*(t_start+13) == '1') ? "Now" : "Never"); int i; char *selstr; struct tm tm; time_t now; struct tm nowtm; struct tm monthtm; char mname[20]; memcpy(&tm, localtime(&t), sizeof(tm)); now = getcurrenttime(NULL); memcpy(&nowtm, localtime(&now), sizeof(tm)); selstr = ((t == 0) ? "SELECTED" : ""); fprintf(output, "\n", selstr, defstr); for (i=1; (i <= 12); i++) { selstr = ( (t && (tm.tm_mon == (i -1))) ? "SELECTED" : ""); monthtm.tm_mon = (i-1); monthtm.tm_mday = 1; monthtm.tm_year = nowtm.tm_year; monthtm.tm_hour = monthtm.tm_min = monthtm.tm_sec = monthtm.tm_isdst = 0; strftime(mname, sizeof(mname)-1, "%B", &monthtm); fprintf(output, "\n", i, selstr, mname); } } else if (strncmp(t_start, "CRITEDITYEARLIST", 14) == 0) { time_t t = ((*(t_start+14) == '1') ? criteditstarttime : criteditendtime); char *defstr = ((*(t_start+14) == '1') ? "Now" : "Never"); int i; char *selstr; struct tm tm; time_t now; struct tm nowtm; int beginyear, endyear; memcpy(&tm, localtime(&t), sizeof(tm)); now = getcurrenttime(NULL); memcpy(&nowtm, localtime(&now), sizeof(tm)); beginyear = nowtm.tm_year + 1900; endyear = nowtm.tm_year + 1900 + 5; selstr = ((t == 0) ? "SELECTED" : ""); fprintf(output, "\n", selstr, defstr); for (i=beginyear; (i <= endyear); i++) { selstr = ( (t && (tm.tm_year == (i - 1900))) ? "SELECTED" : ""); fprintf(output, "\n", i, selstr, i); } } else if (hostenv_hikey && ( (strncmp(t_start, "XMH_", 4) == 0) || (strncmp(t_start, "BBH_", 4) == 0) )) { void *hinfo = hostinfo(hostenv_hikey); if (hinfo) { char *s; if (strncmp(t_start, "BBH_", 4) == 0) memmove(t_start, "XMH_", 4); /* For compatibility */ s = xmh_item_byname(hinfo, t_start); if (!s) { fprintf(output, "&%s", t_start); } else { fprintf(output, "%s", s); } } } else if (strncmp(t_start, "BACKDAYS", 8) == 0) { fprintf(output, "%d", backdays); } else if (strncmp(t_start, "BACKHOURS", 9) == 0) { fprintf(output, "%d", backhours); } else if (strncmp(t_start, "BACKMINS", 8) == 0) { fprintf(output, "%d", backmins); } else if (strncmp(t_start, "BACKSECS", 8) == 0) { fprintf(output, "%d", backsecs); } else if (strncmp(t_start, "EVENTLASTMONTHBEGIN", 19) == 0) { time_t t = getcurrenttime(NULL); struct tm *tm = localtime(&t); tm->tm_mon -= 1; tm->tm_mday = 1; tm->tm_hour = tm->tm_min = tm->tm_sec = 0; tm->tm_isdst = -1; t = mktime(tm); fprintf(output, "%s", eventreport_timestring(t)); } else if (strncmp(t_start, "EVENTCURRMONTHBEGIN", 19) == 0) { time_t t = getcurrenttime(NULL); struct tm *tm = localtime(&t); tm->tm_mday = 1; tm->tm_hour = tm->tm_min = tm->tm_sec = 0; tm->tm_isdst = -1; t = mktime(tm); fprintf(output, "%s", eventreport_timestring(t)); } else if (strncmp(t_start, "EVENTLASTWEEKBEGIN", 18) == 0) { time_t t = getcurrenttime(NULL); struct tm *tm = localtime(&t); int weekstart = atoi(xgetenv("WEEKSTART")); if (tm->tm_wday == weekstart) { /* Do nothing */ } else if (tm->tm_wday > weekstart) tm->tm_mday -= (tm->tm_wday - weekstart); else tm->tm_mday += (weekstart - tm->tm_wday) - 7; tm->tm_mday -= 7; tm->tm_hour = tm->tm_min = tm->tm_sec = 0; tm->tm_isdst = -1; t = mktime(tm); fprintf(output, "%s", eventreport_timestring(t)); } else if (strncmp(t_start, "EVENTCURRWEEKBEGIN", 18) == 0) { time_t t = getcurrenttime(NULL); struct tm *tm = localtime(&t); int weekstart = atoi(xgetenv("WEEKSTART")); if (tm->tm_wday == weekstart) { /* Do nothing */ } else if (tm->tm_wday > weekstart) tm->tm_mday -= (tm->tm_wday - weekstart); else tm->tm_mday += (weekstart - tm->tm_wday) - 7; tm->tm_hour = tm->tm_min = tm->tm_sec = 0; tm->tm_isdst = -1; t = mktime(tm); fprintf(output, "%s", eventreport_timestring(t)); } else if (strncmp(t_start, "EVENTLASTYEARBEGIN", 18) == 0) { time_t t = getcurrenttime(NULL); struct tm *tm = localtime(&t); tm->tm_year -= 1; tm->tm_mon = 0; tm->tm_mday = 1; tm->tm_hour = tm->tm_min = tm->tm_sec = 0; tm->tm_isdst = -1; t = mktime(tm); fprintf(output, "%s", eventreport_timestring(t)); } else if (strncmp(t_start, "EVENTCURRYEARBEGIN", 18) == 0) { time_t t = getcurrenttime(NULL); struct tm *tm = localtime(&t); tm->tm_mon = 0; tm->tm_mday = 1; tm->tm_hour = tm->tm_min = tm->tm_sec = 0; tm->tm_isdst = -1; t = mktime(tm); fprintf(output, "%s", eventreport_timestring(t)); } else if (strncmp(t_start, "EVENTYESTERDAY", 14) == 0) { time_t t = getcurrenttime(NULL); struct tm *tm = localtime(&t); tm->tm_mday -= 1; tm->tm_hour = tm->tm_min = tm->tm_sec = 0; tm->tm_isdst = -1; t = mktime(tm); fprintf(output, "%s", eventreport_timestring(t)); } else if (strncmp(t_start, "EVENTTODAY", 10) == 0) { time_t t = getcurrenttime(NULL); struct tm *tm = localtime(&t); tm->tm_hour = tm->tm_min = tm->tm_sec = 0; tm->tm_isdst = -1; t = mktime(tm); fprintf(output, "%s", eventreport_timestring(t)); } else if (strncmp(t_start, "EVENTNOW", 8) == 0) { time_t t = getcurrenttime(NULL); fprintf(output, "%s", eventreport_timestring(t)); } else if (strncmp(t_start, "PAGEPATH_DROPDOWN", 17) == 0) { build_pagepath_dropdown(output); } else if (strncmp(t_start, "EVENTSTARTTIME", 8) == 0) { fprintf(output, "%s", hostenv_eventtimestart); } else if (strncmp(t_start, "EVENTENDTIME", 8) == 0) { fprintf(output, "%s", hostenv_eventtimeend); } else if (strncmp(t_start, "XYMONBODY", 9) == 0) { char *bodytext = xymonbody(t_start); fprintf(output, "%s", bodytext); } else if (*t_start && (savechar == ';')) { /* A "&xxx;" is probably an HTML escape - output unchanged. */ fprintf(output, "&%s", t_start); } else if (*t_start && (strncmp(t_start, "SELECT_", 7) == 0)) { /* * Special for getting the SELECTED tag into list boxes. * Cannot use xgetenv because it complains for undefined * environment variables. */ char *val = getenv(t_start); fprintf(output, "%s", (val ? val : "")); } else if (strlen(t_start) && xgetenv(t_start)) { fprintf(output, "%s", xgetenv(t_start)); } else fprintf(output, "&%s", t_start); /* No substitution - copy all unchanged. */ *t_next = savechar; t_start = t_next; t_next = strchr(t_start, '&'); } /* Remainder of file */ fprintf(output, "%s", t_start); } void headfoot(FILE *output, char *template, char *pagepath, char *head_or_foot, int bgcolor) { int fd; char filename[PATH_MAX]; char *bulletinfile; struct stat st; char *templatedata; char *hfpath; int have_pagepath = (hostenv_pagepath != NULL); MEMDEFINE(filename); if (xgetenv("XYMONDREL") == NULL) { char *xymondrel = (char *)malloc(12+strlen(VERSION)); sprintf(xymondrel, "XYMONDREL=%s", VERSION); putenv(xymondrel); } /* * "pagepath" is the relative path for this page, e.g. * - for the top-level page it is "" * - for a page, it is "pagename/" * - for a subpage, it is "pagename/subpagename/" * * We allow header/footer files named template_PAGE_header or template_PAGE_SUBPAGE_header * so we need to scan for an existing file - starting with the * most detailed one, and working up towards the standard "web/template_TYPE" file. */ hfpath = strdup(pagepath); /* Trim off excess trailing slashes */ if (*hfpath) { while (*(hfpath + strlen(hfpath) - 1) == '/') *(hfpath + strlen(hfpath) - 1) = '\0'; } fd = -1; if (!have_pagepath) hostenv_pagepath = strdup(hfpath); while ((fd == -1) && strlen(hfpath)) { char *p; char *elemstart; if (hostenv_templatedir) { sprintf(filename, "%s/", hostenv_templatedir); } else { sprintf(filename, "%s/web/", xgetenv("XYMONHOME")); } p = strchr(hfpath, '/'); elemstart = hfpath; while (p) { *p = '\0'; strcat(filename, elemstart); strcat(filename, "_"); *p = '/'; p++; elemstart = p; p = strchr(elemstart, '/'); } strcat(filename, elemstart); strcat(filename, "_"); strcat(filename, head_or_foot); dbgprintf("Trying header/footer file '%s'\n", filename); fd = open(filename, O_RDONLY); if (fd == -1) { p = strrchr(hfpath, '/'); if (p == NULL) p = hfpath; *p = '\0'; } } xfree(hfpath); if (fd == -1) { /* Fall back to default head/foot file. */ if (hostenv_templatedir) { sprintf(filename, "%s/%s_%s", hostenv_templatedir, template, head_or_foot); } else { sprintf(filename, "%s/web/%s_%s", xgetenv("XYMONHOME"), template, head_or_foot); } dbgprintf("Trying header/footer file '%s'\n", filename); fd = open(filename, O_RDONLY); } if (fd != -1) { fstat(fd, &st); templatedata = (char *) malloc(st.st_size + 1); read(fd, templatedata, st.st_size); templatedata[st.st_size] = '\0'; close(fd); output_parsed(output, templatedata, bgcolor, getcurrenttime(NULL)); xfree(templatedata); } else { fprintf(output, " \n
\n
%s is either missing or invalid, please create this file with your custom header
\n
", htmlquoted(filename)); } /* Check for bulletin files */ bulletinfile = (char *)malloc(strlen(xgetenv("XYMONHOME")) + strlen("/web/bulletin_") + strlen(head_or_foot)+1); sprintf(bulletinfile, "%s/web/bulletin_%s", xgetenv("XYMONHOME"), head_or_foot); fd = open(bulletinfile, O_RDONLY); if (fd != -1) { fstat(fd, &st); templatedata = (char *) malloc(st.st_size + 1); read(fd, templatedata, st.st_size); templatedata[st.st_size] = '\0'; close(fd); output_parsed(output, templatedata, bgcolor, getcurrenttime(NULL)); xfree(templatedata); } if (!have_pagepath) { xfree(hostenv_pagepath); hostenv_pagepath = NULL; } xfree(bulletinfile); MEMUNDEFINE(filename); } void showform(FILE *output, char *headertemplate, char *formtemplate, int color, time_t seltime, char *pretext, char *posttext) { /* Present the query form */ int formfile; char formfn[PATH_MAX]; sprintf(formfn, "%s/web/%s", xgetenv("XYMONHOME"), formtemplate); formfile = open(formfn, O_RDONLY); if (formfile >= 0) { char *inbuf; struct stat st; fstat(formfile, &st); inbuf = (char *) malloc(st.st_size + 1); read(formfile, inbuf, st.st_size); inbuf[st.st_size] = '\0'; close(formfile); if (headertemplate) headfoot(output, headertemplate, (hostenv_pagepath ? hostenv_pagepath : ""), "header", color); if (pretext) fprintf(output, "%s", pretext); output_parsed(output, inbuf, color, seltime); if (posttext) fprintf(output, "%s", posttext); if (headertemplate) headfoot(output, headertemplate, (hostenv_pagepath ? hostenv_pagepath : ""), "footer", color); xfree(inbuf); } } xymon-4.3.7/lib/eventlog.h0000664000175000017500000000413411615341300014761 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __EVENTLOG_H_ #define __EVENTLOG_H_ /* Format of records in the $XYMONHISTDIR/allevents file */ typedef struct event_t { void *host; struct htnames_t *service; time_t eventtime; time_t changetime; time_t duration; int newcolor; /* stored as "re", "ye", "gr" etc. */ int oldcolor; int state; /* 2=escalated, 1=recovered, 0=no change */ struct event_t *next; } event_t; typedef struct eventcount_t { struct htnames_t *service; unsigned long count; struct eventcount_t *next; } eventcount_t; typedef struct countlist_t { void *src; /* May be a pointer to a host or a service */ unsigned long total; struct countlist_t *next; } countlist_t; typedef enum { XYMON_S_NONE, XYMON_S_HOST_BREAKDOWN, XYMON_S_SERVICE_BREAKDOWN } eventsummary_t; typedef enum { XYMON_COUNT_NONE, XYMON_COUNT_EVENTS, XYMON_COUNT_DURATION } countsummary_t; typedef int (*f_hostcheck)(char *hostname); extern char *eventignorecolumns; extern int havedoneeventlog; extern void do_eventlog(FILE *output, int maxcount, int maxminutes, char *fromtime, char *totime, char *pagematch, char *expagematch, char *hostmatch, char *exhostmatch, char *testmatch, char *extestmatch, char *colormatch, int ignoredialups, f_hostcheck hostcheck, event_t **eventlist, countlist_t **hostcounts, countlist_t **servicecounts, countsummary_t counttype, eventsummary_t sumtype, char *periodstring); #endif xymon-4.3.7/lib/md5.h0000664000175000017500000000215711615341300013626 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* API for the MD5 digest routines. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __MD5_H__ #define __MD5_H__ extern int myMD5_Size(void); extern void myMD5_Init(void *pms); extern void myMD5_Update(void *pms, unsigned char *data, int nbytes); extern void myMD5_Final(unsigned char digest[16], void *pms); #endif xymon-4.3.7/lib/availability.h0000664000175000017500000000345711615341300015617 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __AVAILABILITY_H__ #define __AVAILABILITY_H__ #include "color.h" typedef struct reportinfo_t { char *fstate; time_t reportstart; int count[COL_COUNT]; double fullavailability; int fullstops; double fullpct[COL_COUNT]; unsigned long fullduration[COL_COUNT]; int withreport; double reportavailability; int reportstops; double reportpct[COL_COUNT]; unsigned long reportduration[COL_COUNT]; } reportinfo_t; typedef struct replog_t { time_t starttime; time_t duration; int color; int affectssla; char *cause; char *timespec; struct replog_t *next; } replog_t; extern replog_t *reploghead; extern char *durationstr(time_t duration); extern int parse_historyfile(FILE *fd, reportinfo_t *repinfo, char *hostname, char *servicename, time_t fromtime, time_t totime, int for_history, double warnlevel, double greenlevel, int warnstops, char *reporttime); extern replog_t *save_replogs(void); extern void restore_replogs(replog_t *head); extern int history_color(FILE *fd, time_t snapshot, time_t *starttime, char **histlogname); #endif xymon-4.3.7/lib/digest.h0000664000175000017500000000247511615341300014423 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is used to implement the message digest functions. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __DIGEST_H_ #define __DIGEST_H_ typedef enum { D_MD5, D_SHA1, D_RMD160, D_SHA256, D_SHA512, D_SHA224, D_SHA384 } digesttype_t; typedef struct digestctx_t { char *digestname; digesttype_t digesttype; void *mdctx; } digestctx_t; extern char *md5hash(char *input); extern digestctx_t *digest_init(char *digest); extern int digest_data(digestctx_t *ctx, unsigned char *buf, int buflen); extern char *digest_done(digestctx_t *ctx); #endif xymon-4.3.7/lib/sha2.c0000664000175000017500000010700111535462534014001 0ustar henrikhenrik/*- * FIPS 180-2 SHA-224/256/384/512 implementation * Last update: 05/23/2005 * Issue date: 04/30/2005 * * Copyright (C) 2005 Olivier Gay * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 #define UNROLL_LOOPS /* Enable loops unrolling */ #endif #include /* For Xymon: These definitions were copied in here from sha2.h */ #define SHA224_DIGEST_SIZE (224 / 8) #define SHA256_DIGEST_SIZE (256 / 8) #define SHA384_DIGEST_SIZE (384 / 8) #define SHA512_DIGEST_SIZE (512 / 8) #define SHA256_BLOCK_SIZE ( 512 / 8) #define SHA512_BLOCK_SIZE (1024 / 8) #define SHA384_BLOCK_SIZE SHA512_BLOCK_SIZE #define SHA224_BLOCK_SIZE SHA256_BLOCK_SIZE #ifdef HAVE_STDINT_H #include #else typedef unsigned char uint8_t; typedef unsigned int uint32_t; typedef unsigned long long uint64_t; #endif typedef struct { unsigned int tot_len; unsigned int len; unsigned char block[2 * SHA256_BLOCK_SIZE]; uint32_t h[8]; } sha256_ctx; typedef struct { unsigned int tot_len; unsigned int len; unsigned char block[2 * SHA512_BLOCK_SIZE]; uint64_t h[8]; } sha512_ctx; typedef sha512_ctx sha384_ctx; typedef sha256_ctx sha224_ctx; /* end of definitions copied from sha2.h */ #define SHFR(x, n) (x >> n) #define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) #define ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) #define CH(x, y, z) ((x & y) ^ (~x & z)) #define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) #define SHA256_F1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) #define SHA256_F2(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) #define SHA256_F3(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3)) #define SHA256_F4(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10)) #define SHA512_F1(x) (ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39)) #define SHA512_F2(x) (ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41)) #define SHA512_F3(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHFR(x, 7)) #define SHA512_F4(x) (ROTR(x, 19) ^ ROTR(x, 61) ^ SHFR(x, 6)) #define UNPACK32(x, str) \ { \ *((str) + 3) = (uint8_t) ((x) ); \ *((str) + 2) = (uint8_t) ((x) >> 8); \ *((str) + 1) = (uint8_t) ((x) >> 16); \ *((str) + 0) = (uint8_t) ((x) >> 24); \ } #define PACK32(str, x) \ { \ *(x) = ((uint32_t) *((str) + 3) ) \ | ((uint32_t) *((str) + 2) << 8) \ | ((uint32_t) *((str) + 1) << 16) \ | ((uint32_t) *((str) + 0) << 24); \ } #define UNPACK64(x, str) \ { \ *((str) + 7) = (uint8_t) ((x) ); \ *((str) + 6) = (uint8_t) ((x) >> 8); \ *((str) + 5) = (uint8_t) ((x) >> 16); \ *((str) + 4) = (uint8_t) ((x) >> 24); \ *((str) + 3) = (uint8_t) ((x) >> 32); \ *((str) + 2) = (uint8_t) ((x) >> 40); \ *((str) + 1) = (uint8_t) ((x) >> 48); \ *((str) + 0) = (uint8_t) ((x) >> 56); \ } #define PACK64(str, x) \ { \ *(x) = ((uint64_t) *((str) + 7) ) \ | ((uint64_t) *((str) + 6) << 8) \ | ((uint64_t) *((str) + 5) << 16) \ | ((uint64_t) *((str) + 4) << 24) \ | ((uint64_t) *((str) + 3) << 32) \ | ((uint64_t) *((str) + 2) << 40) \ | ((uint64_t) *((str) + 1) << 48) \ | ((uint64_t) *((str) + 0) << 56); \ } /* Macros used for loops unrolling */ #define SHA256_SCR(i) \ { \ w[i] = SHA256_F4(w[i - 2]) + w[i - 7] \ + SHA256_F3(w[i - 15]) + w[i - 16]; \ } #define SHA512_SCR(i) \ { \ w[i] = SHA512_F4(w[i - 2]) + w[i - 7] \ + SHA512_F3(w[i - 15]) + w[i - 16]; \ } #define SHA256_EXP(a, b, c, d, e, f, g, h, j) \ { \ t1 = wv[h] + SHA256_F2(wv[e]) + CH(wv[e], wv[f], wv[g]) \ + sha256_k[j] + w[j]; \ t2 = SHA256_F1(wv[a]) + MAJ(wv[a], wv[b], wv[c]); \ wv[d] += t1; \ wv[h] = t1 + t2; \ } #define SHA512_EXP(a, b, c, d, e, f, g ,h, j) \ { \ t1 = wv[h] + SHA512_F2(wv[e]) + CH(wv[e], wv[f], wv[g]) \ + sha512_k[j] + w[j]; \ t2 = SHA512_F1(wv[a]) + MAJ(wv[a], wv[b], wv[c]); \ wv[d] += t1; \ wv[h] = t1 + t2; \ } uint32_t sha224_h0[8] = {0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4}; uint32_t sha256_h0[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; uint64_t sha384_h0[8] = {0xcbbb9d5dc1059ed8ULL, 0x629a292a367cd507ULL, 0x9159015a3070dd17ULL, 0x152fecd8f70e5939ULL, 0x67332667ffc00b31ULL, 0x8eb44a8768581511ULL, 0xdb0c2e0d64f98fa7ULL, 0x47b5481dbefa4fa4ULL}; uint64_t sha512_h0[8] = {0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL}; uint32_t sha256_k[64] = {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; uint64_t sha512_k[80] = {0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL}; /* SHA-256 functions */ static void sha256_transf(sha256_ctx *ctx, unsigned char *message, unsigned int block_nb) { uint32_t w[64]; uint32_t wv[8]; uint32_t t1, t2; unsigned char *sub_block; int i; #ifndef UNROLL_LOOPS int j; #endif for (i = 1; i <= block_nb; i++) { sub_block = message + ((i - 1) << 6); #ifndef UNROLL_LOOPS for (j = 0; j < 16; j++) { PACK32(&sub_block[j << 2], &w[j]); } for (j = 16; j < 64; j++) { SHA256_SCR(j); } for (j = 0; j < 8; j++) { wv[j] = ctx->h[j]; } for (j = 0; j < 64; j++) { t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha256_k[j] + w[j]; t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); wv[7] = wv[6]; wv[6] = wv[5]; wv[5] = wv[4]; wv[4] = wv[3] + t1; wv[3] = wv[2]; wv[2] = wv[1]; wv[1] = wv[0]; wv[0] = t1 + t2; } for (j = 0; j < 8; j++) { ctx->h[j] += wv[j]; } #else PACK32(&sub_block[ 0], &w[ 0]); PACK32(&sub_block[ 4], &w[ 1]); PACK32(&sub_block[ 8], &w[ 2]); PACK32(&sub_block[12], &w[ 3]); PACK32(&sub_block[16], &w[ 4]); PACK32(&sub_block[20], &w[ 5]); PACK32(&sub_block[24], &w[ 6]); PACK32(&sub_block[28], &w[ 7]); PACK32(&sub_block[32], &w[ 8]); PACK32(&sub_block[36], &w[ 9]); PACK32(&sub_block[40], &w[10]); PACK32(&sub_block[44], &w[11]); PACK32(&sub_block[48], &w[12]); PACK32(&sub_block[52], &w[13]); PACK32(&sub_block[56], &w[14]); PACK32(&sub_block[60], &w[15]); SHA256_SCR(16); SHA256_SCR(17); SHA256_SCR(18); SHA256_SCR(19); SHA256_SCR(20); SHA256_SCR(21); SHA256_SCR(22); SHA256_SCR(23); SHA256_SCR(24); SHA256_SCR(25); SHA256_SCR(26); SHA256_SCR(27); SHA256_SCR(28); SHA256_SCR(29); SHA256_SCR(30); SHA256_SCR(31); SHA256_SCR(32); SHA256_SCR(33); SHA256_SCR(34); SHA256_SCR(35); SHA256_SCR(36); SHA256_SCR(37); SHA256_SCR(38); SHA256_SCR(39); SHA256_SCR(40); SHA256_SCR(41); SHA256_SCR(42); SHA256_SCR(43); SHA256_SCR(44); SHA256_SCR(45); SHA256_SCR(46); SHA256_SCR(47); SHA256_SCR(48); SHA256_SCR(49); SHA256_SCR(50); SHA256_SCR(51); SHA256_SCR(52); SHA256_SCR(53); SHA256_SCR(54); SHA256_SCR(55); SHA256_SCR(56); SHA256_SCR(57); SHA256_SCR(58); SHA256_SCR(59); SHA256_SCR(60); SHA256_SCR(61); SHA256_SCR(62); SHA256_SCR(63); wv[0] = ctx->h[0]; wv[1] = ctx->h[1]; wv[2] = ctx->h[2]; wv[3] = ctx->h[3]; wv[4] = ctx->h[4]; wv[5] = ctx->h[5]; wv[6] = ctx->h[6]; wv[7] = ctx->h[7]; SHA256_EXP(0,1,2,3,4,5,6,7, 0); SHA256_EXP(7,0,1,2,3,4,5,6, 1); SHA256_EXP(6,7,0,1,2,3,4,5, 2); SHA256_EXP(5,6,7,0,1,2,3,4, 3); SHA256_EXP(4,5,6,7,0,1,2,3, 4); SHA256_EXP(3,4,5,6,7,0,1,2, 5); SHA256_EXP(2,3,4,5,6,7,0,1, 6); SHA256_EXP(1,2,3,4,5,6,7,0, 7); SHA256_EXP(0,1,2,3,4,5,6,7, 8); SHA256_EXP(7,0,1,2,3,4,5,6, 9); SHA256_EXP(6,7,0,1,2,3,4,5,10); SHA256_EXP(5,6,7,0,1,2,3,4,11); SHA256_EXP(4,5,6,7,0,1,2,3,12); SHA256_EXP(3,4,5,6,7,0,1,2,13); SHA256_EXP(2,3,4,5,6,7,0,1,14); SHA256_EXP(1,2,3,4,5,6,7,0,15); SHA256_EXP(0,1,2,3,4,5,6,7,16); SHA256_EXP(7,0,1,2,3,4,5,6,17); SHA256_EXP(6,7,0,1,2,3,4,5,18); SHA256_EXP(5,6,7,0,1,2,3,4,19); SHA256_EXP(4,5,6,7,0,1,2,3,20); SHA256_EXP(3,4,5,6,7,0,1,2,21); SHA256_EXP(2,3,4,5,6,7,0,1,22); SHA256_EXP(1,2,3,4,5,6,7,0,23); SHA256_EXP(0,1,2,3,4,5,6,7,24); SHA256_EXP(7,0,1,2,3,4,5,6,25); SHA256_EXP(6,7,0,1,2,3,4,5,26); SHA256_EXP(5,6,7,0,1,2,3,4,27); SHA256_EXP(4,5,6,7,0,1,2,3,28); SHA256_EXP(3,4,5,6,7,0,1,2,29); SHA256_EXP(2,3,4,5,6,7,0,1,30); SHA256_EXP(1,2,3,4,5,6,7,0,31); SHA256_EXP(0,1,2,3,4,5,6,7,32); SHA256_EXP(7,0,1,2,3,4,5,6,33); SHA256_EXP(6,7,0,1,2,3,4,5,34); SHA256_EXP(5,6,7,0,1,2,3,4,35); SHA256_EXP(4,5,6,7,0,1,2,3,36); SHA256_EXP(3,4,5,6,7,0,1,2,37); SHA256_EXP(2,3,4,5,6,7,0,1,38); SHA256_EXP(1,2,3,4,5,6,7,0,39); SHA256_EXP(0,1,2,3,4,5,6,7,40); SHA256_EXP(7,0,1,2,3,4,5,6,41); SHA256_EXP(6,7,0,1,2,3,4,5,42); SHA256_EXP(5,6,7,0,1,2,3,4,43); SHA256_EXP(4,5,6,7,0,1,2,3,44); SHA256_EXP(3,4,5,6,7,0,1,2,45); SHA256_EXP(2,3,4,5,6,7,0,1,46); SHA256_EXP(1,2,3,4,5,6,7,0,47); SHA256_EXP(0,1,2,3,4,5,6,7,48); SHA256_EXP(7,0,1,2,3,4,5,6,49); SHA256_EXP(6,7,0,1,2,3,4,5,50); SHA256_EXP(5,6,7,0,1,2,3,4,51); SHA256_EXP(4,5,6,7,0,1,2,3,52); SHA256_EXP(3,4,5,6,7,0,1,2,53); SHA256_EXP(2,3,4,5,6,7,0,1,54); SHA256_EXP(1,2,3,4,5,6,7,0,55); SHA256_EXP(0,1,2,3,4,5,6,7,56); SHA256_EXP(7,0,1,2,3,4,5,6,57); SHA256_EXP(6,7,0,1,2,3,4,5,58); SHA256_EXP(5,6,7,0,1,2,3,4,59); SHA256_EXP(4,5,6,7,0,1,2,3,60); SHA256_EXP(3,4,5,6,7,0,1,2,61); SHA256_EXP(2,3,4,5,6,7,0,1,62); SHA256_EXP(1,2,3,4,5,6,7,0,63); ctx->h[0] += wv[0]; ctx->h[1] += wv[1]; ctx->h[2] += wv[2]; ctx->h[3] += wv[3]; ctx->h[4] += wv[4]; ctx->h[5] += wv[5]; ctx->h[6] += wv[6]; ctx->h[7] += wv[7]; #endif /* !UNROLL_LOOPS */ } } static void sha256_init(sha256_ctx *ctx) { #ifndef UNROLL_LOOPS int i; for (i = 0; i < 8; i++) { ctx->h[i] = sha256_h0[i]; } #else ctx->h[0] = sha256_h0[0]; ctx->h[1] = sha256_h0[1]; ctx->h[2] = sha256_h0[2]; ctx->h[3] = sha256_h0[3]; ctx->h[4] = sha256_h0[4]; ctx->h[5] = sha256_h0[5]; ctx->h[6] = sha256_h0[6]; ctx->h[7] = sha256_h0[7]; #endif /* !UNROLL_LOOPS */ ctx->len = 0; ctx->tot_len = 0; } static void sha256_update(sha256_ctx *ctx, unsigned char *message, unsigned int len) { unsigned int block_nb; unsigned int new_len, rem_len; unsigned char *shifted_message; rem_len = SHA256_BLOCK_SIZE - ctx->len; memcpy(&ctx->block[ctx->len], message, rem_len); if (ctx->len + len < SHA256_BLOCK_SIZE) { ctx->len += len; return; } new_len = len - rem_len; block_nb = new_len / SHA256_BLOCK_SIZE; shifted_message = message + rem_len; sha256_transf(ctx, ctx->block, 1); sha256_transf(ctx, shifted_message, block_nb); rem_len = new_len % SHA256_BLOCK_SIZE; memcpy(ctx->block, &shifted_message[block_nb << 6], rem_len); ctx->len = rem_len; ctx->tot_len += (block_nb + 1) << 6; } static void sha256_final(sha256_ctx *ctx, unsigned char *digest) { unsigned int block_nb; unsigned int pm_len; unsigned int len_b; #ifndef UNROLL_LOOPS int i; #endif block_nb = (1 + ((SHA256_BLOCK_SIZE - 9) < (ctx->len % SHA256_BLOCK_SIZE))); len_b = (ctx->tot_len + ctx->len) << 3; pm_len = block_nb << 6; memset(ctx->block + ctx->len, 0, pm_len - ctx->len); ctx->block[ctx->len] = 0x80; UNPACK32(len_b, ctx->block + pm_len - 4); sha256_transf(ctx, ctx->block, block_nb); #ifndef UNROLL_LOOPS for (i = 0 ; i < 8; i++) { UNPACK32(ctx->h[i], &digest[i << 2]); } #else UNPACK32(ctx->h[0], &digest[ 0]); UNPACK32(ctx->h[1], &digest[ 4]); UNPACK32(ctx->h[2], &digest[ 8]); UNPACK32(ctx->h[3], &digest[12]); UNPACK32(ctx->h[4], &digest[16]); UNPACK32(ctx->h[5], &digest[20]); UNPACK32(ctx->h[6], &digest[24]); UNPACK32(ctx->h[7], &digest[28]); #endif /* !UNROLL_LOOPS */ } /* SHA 512 functions*/ static void sha512_transf(sha512_ctx *ctx, unsigned char *message, unsigned int block_nb) { uint64_t w[80]; uint64_t wv[8]; uint64_t t1, t2; unsigned char *sub_block; int i; #ifndef UNROLL_LOOPS int j; #endif for (i = 1; i <= block_nb; i++) { sub_block = message + ((i - 1) << 7); #ifndef UNROLL_LOOPS for (j = 0; j < 16; j++) { PACK64(&sub_block[j << 3], &w[j]); } for (j = 16; j < 80; j++) { SHA512_SCR(j); } for (j = 0; j < 8; j++) { wv[j] = ctx->h[j]; } for (j = 0; j < 80; j++) { t1 = wv[7] + SHA512_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha512_k[j] + w[j]; t2 = SHA512_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); wv[7] = wv[6]; wv[6] = wv[5]; wv[5] = wv[4]; wv[4] = wv[3] + t1; wv[3] = wv[2]; wv[2] = wv[1]; wv[1] = wv[0]; wv[0] = t1 + t2; } for (j = 0; j < 8; j++) { ctx->h[j] += wv[j]; } #else PACK64(&sub_block[ 0], &w[ 0]); PACK64(&sub_block[ 8], &w[ 1]); PACK64(&sub_block[ 16], &w[ 2]); PACK64(&sub_block[ 24], &w[ 3]); PACK64(&sub_block[ 32], &w[ 4]); PACK64(&sub_block[ 40], &w[ 5]); PACK64(&sub_block[ 48], &w[ 6]); PACK64(&sub_block[ 56], &w[ 7]); PACK64(&sub_block[ 64], &w[ 8]); PACK64(&sub_block[ 72], &w[ 9]); PACK64(&sub_block[ 80], &w[10]); PACK64(&sub_block[ 88], &w[11]); PACK64(&sub_block[ 96], &w[12]); PACK64(&sub_block[104], &w[13]); PACK64(&sub_block[112], &w[14]); PACK64(&sub_block[120], &w[15]); SHA512_SCR(16); SHA512_SCR(17); SHA512_SCR(18); SHA512_SCR(19); SHA512_SCR(20); SHA512_SCR(21); SHA512_SCR(22); SHA512_SCR(23); SHA512_SCR(24); SHA512_SCR(25); SHA512_SCR(26); SHA512_SCR(27); SHA512_SCR(28); SHA512_SCR(29); SHA512_SCR(30); SHA512_SCR(31); SHA512_SCR(32); SHA512_SCR(33); SHA512_SCR(34); SHA512_SCR(35); SHA512_SCR(36); SHA512_SCR(37); SHA512_SCR(38); SHA512_SCR(39); SHA512_SCR(40); SHA512_SCR(41); SHA512_SCR(42); SHA512_SCR(43); SHA512_SCR(44); SHA512_SCR(45); SHA512_SCR(46); SHA512_SCR(47); SHA512_SCR(48); SHA512_SCR(49); SHA512_SCR(50); SHA512_SCR(51); SHA512_SCR(52); SHA512_SCR(53); SHA512_SCR(54); SHA512_SCR(55); SHA512_SCR(56); SHA512_SCR(57); SHA512_SCR(58); SHA512_SCR(59); SHA512_SCR(60); SHA512_SCR(61); SHA512_SCR(62); SHA512_SCR(63); SHA512_SCR(64); SHA512_SCR(65); SHA512_SCR(66); SHA512_SCR(67); SHA512_SCR(68); SHA512_SCR(69); SHA512_SCR(70); SHA512_SCR(71); SHA512_SCR(72); SHA512_SCR(73); SHA512_SCR(74); SHA512_SCR(75); SHA512_SCR(76); SHA512_SCR(77); SHA512_SCR(78); SHA512_SCR(79); wv[0] = ctx->h[0]; wv[1] = ctx->h[1]; wv[2] = ctx->h[2]; wv[3] = ctx->h[3]; wv[4] = ctx->h[4]; wv[5] = ctx->h[5]; wv[6] = ctx->h[6]; wv[7] = ctx->h[7]; SHA512_EXP(0,1,2,3,4,5,6,7, 0); SHA512_EXP(7,0,1,2,3,4,5,6, 1); SHA512_EXP(6,7,0,1,2,3,4,5, 2); SHA512_EXP(5,6,7,0,1,2,3,4, 3); SHA512_EXP(4,5,6,7,0,1,2,3, 4); SHA512_EXP(3,4,5,6,7,0,1,2, 5); SHA512_EXP(2,3,4,5,6,7,0,1, 6); SHA512_EXP(1,2,3,4,5,6,7,0, 7); SHA512_EXP(0,1,2,3,4,5,6,7, 8); SHA512_EXP(7,0,1,2,3,4,5,6, 9); SHA512_EXP(6,7,0,1,2,3,4,5,10); SHA512_EXP(5,6,7,0,1,2,3,4,11); SHA512_EXP(4,5,6,7,0,1,2,3,12); SHA512_EXP(3,4,5,6,7,0,1,2,13); SHA512_EXP(2,3,4,5,6,7,0,1,14); SHA512_EXP(1,2,3,4,5,6,7,0,15); SHA512_EXP(0,1,2,3,4,5,6,7,16); SHA512_EXP(7,0,1,2,3,4,5,6,17); SHA512_EXP(6,7,0,1,2,3,4,5,18); SHA512_EXP(5,6,7,0,1,2,3,4,19); SHA512_EXP(4,5,6,7,0,1,2,3,20); SHA512_EXP(3,4,5,6,7,0,1,2,21); SHA512_EXP(2,3,4,5,6,7,0,1,22); SHA512_EXP(1,2,3,4,5,6,7,0,23); SHA512_EXP(0,1,2,3,4,5,6,7,24); SHA512_EXP(7,0,1,2,3,4,5,6,25); SHA512_EXP(6,7,0,1,2,3,4,5,26); SHA512_EXP(5,6,7,0,1,2,3,4,27); SHA512_EXP(4,5,6,7,0,1,2,3,28); SHA512_EXP(3,4,5,6,7,0,1,2,29); SHA512_EXP(2,3,4,5,6,7,0,1,30); SHA512_EXP(1,2,3,4,5,6,7,0,31); SHA512_EXP(0,1,2,3,4,5,6,7,32); SHA512_EXP(7,0,1,2,3,4,5,6,33); SHA512_EXP(6,7,0,1,2,3,4,5,34); SHA512_EXP(5,6,7,0,1,2,3,4,35); SHA512_EXP(4,5,6,7,0,1,2,3,36); SHA512_EXP(3,4,5,6,7,0,1,2,37); SHA512_EXP(2,3,4,5,6,7,0,1,38); SHA512_EXP(1,2,3,4,5,6,7,0,39); SHA512_EXP(0,1,2,3,4,5,6,7,40); SHA512_EXP(7,0,1,2,3,4,5,6,41); SHA512_EXP(6,7,0,1,2,3,4,5,42); SHA512_EXP(5,6,7,0,1,2,3,4,43); SHA512_EXP(4,5,6,7,0,1,2,3,44); SHA512_EXP(3,4,5,6,7,0,1,2,45); SHA512_EXP(2,3,4,5,6,7,0,1,46); SHA512_EXP(1,2,3,4,5,6,7,0,47); SHA512_EXP(0,1,2,3,4,5,6,7,48); SHA512_EXP(7,0,1,2,3,4,5,6,49); SHA512_EXP(6,7,0,1,2,3,4,5,50); SHA512_EXP(5,6,7,0,1,2,3,4,51); SHA512_EXP(4,5,6,7,0,1,2,3,52); SHA512_EXP(3,4,5,6,7,0,1,2,53); SHA512_EXP(2,3,4,5,6,7,0,1,54); SHA512_EXP(1,2,3,4,5,6,7,0,55); SHA512_EXP(0,1,2,3,4,5,6,7,56); SHA512_EXP(7,0,1,2,3,4,5,6,57); SHA512_EXP(6,7,0,1,2,3,4,5,58); SHA512_EXP(5,6,7,0,1,2,3,4,59); SHA512_EXP(4,5,6,7,0,1,2,3,60); SHA512_EXP(3,4,5,6,7,0,1,2,61); SHA512_EXP(2,3,4,5,6,7,0,1,62); SHA512_EXP(1,2,3,4,5,6,7,0,63); SHA512_EXP(0,1,2,3,4,5,6,7,64); SHA512_EXP(7,0,1,2,3,4,5,6,65); SHA512_EXP(6,7,0,1,2,3,4,5,66); SHA512_EXP(5,6,7,0,1,2,3,4,67); SHA512_EXP(4,5,6,7,0,1,2,3,68); SHA512_EXP(3,4,5,6,7,0,1,2,69); SHA512_EXP(2,3,4,5,6,7,0,1,70); SHA512_EXP(1,2,3,4,5,6,7,0,71); SHA512_EXP(0,1,2,3,4,5,6,7,72); SHA512_EXP(7,0,1,2,3,4,5,6,73); SHA512_EXP(6,7,0,1,2,3,4,5,74); SHA512_EXP(5,6,7,0,1,2,3,4,75); SHA512_EXP(4,5,6,7,0,1,2,3,76); SHA512_EXP(3,4,5,6,7,0,1,2,77); SHA512_EXP(2,3,4,5,6,7,0,1,78); SHA512_EXP(1,2,3,4,5,6,7,0,79); ctx->h[0] += wv[0]; ctx->h[1] += wv[1]; ctx->h[2] += wv[2]; ctx->h[3] += wv[3]; ctx->h[4] += wv[4]; ctx->h[5] += wv[5]; ctx->h[6] += wv[6]; ctx->h[7] += wv[7]; #endif /* !UNROLL_LOOPS */ } } static void sha512_init(sha512_ctx *ctx) { #ifndef UNROLL_LOOPS int i; for (i = 0; i < 8; i++) { ctx->h[i] = sha512_h0[i]; } #else ctx->h[0] = sha512_h0[0]; ctx->h[1] = sha512_h0[1]; ctx->h[2] = sha512_h0[2]; ctx->h[3] = sha512_h0[3]; ctx->h[4] = sha512_h0[4]; ctx->h[5] = sha512_h0[5]; ctx->h[6] = sha512_h0[6]; ctx->h[7] = sha512_h0[7]; #endif /* !UNROLL_LOOPS */ ctx->len = 0; ctx->tot_len = 0; } static void sha512_update(sha512_ctx *ctx, unsigned char *message, unsigned int len) { unsigned int block_nb; unsigned int new_len, rem_len; unsigned char *shifted_message; rem_len = SHA512_BLOCK_SIZE - ctx->len; memcpy(&ctx->block[ctx->len], message, rem_len); if (ctx->len + len < SHA512_BLOCK_SIZE) { ctx->len += len; return; } new_len = len - rem_len; block_nb = new_len / SHA512_BLOCK_SIZE; shifted_message = message + rem_len; sha512_transf(ctx, ctx->block, 1); sha512_transf(ctx, shifted_message, block_nb); rem_len = new_len % SHA512_BLOCK_SIZE; memcpy(ctx->block, &shifted_message[block_nb << 7], rem_len); ctx->len = rem_len; ctx->tot_len += (block_nb + 1) << 7; } static void sha512_final(sha512_ctx *ctx, unsigned char *digest) { unsigned int block_nb; unsigned int pm_len; unsigned int len_b; #ifndef UNROLL_LOOPS int i; #endif block_nb = 1 + ((SHA512_BLOCK_SIZE - 17) < (ctx->len % SHA512_BLOCK_SIZE)) ; len_b = (ctx->tot_len + ctx->len) << 3; pm_len = block_nb << 7; memset(ctx->block + ctx->len, 0, pm_len - ctx->len); ctx->block[ctx->len] = 0x80; UNPACK32(len_b, ctx->block + pm_len - 4); sha512_transf(ctx, ctx->block, block_nb); #ifndef UNROLL_LOOPS for (i = 0 ; i < 8; i++) { UNPACK64(ctx->h[i], &digest[i << 3]); } #else UNPACK64(ctx->h[0], &digest[ 0]); UNPACK64(ctx->h[1], &digest[ 8]); UNPACK64(ctx->h[2], &digest[16]); UNPACK64(ctx->h[3], &digest[24]); UNPACK64(ctx->h[4], &digest[32]); UNPACK64(ctx->h[5], &digest[40]); UNPACK64(ctx->h[6], &digest[48]); UNPACK64(ctx->h[7], &digest[56]); #endif /* !UNROLL_LOOPS */ } /* SHA-384 functions */ static void sha384_init(sha384_ctx *ctx) { #ifndef UNROLL_LOOPS int i; for (i = 0; i < 8; i++) { ctx->h[i] = sha384_h0[i]; } #else ctx->h[0] = sha384_h0[0]; ctx->h[1] = sha384_h0[1]; ctx->h[2] = sha384_h0[2]; ctx->h[3] = sha384_h0[3]; ctx->h[4] = sha384_h0[4]; ctx->h[5] = sha384_h0[5]; ctx->h[6] = sha384_h0[6]; ctx->h[7] = sha384_h0[7]; #endif /* !UNROLL_LOOPS */ ctx->len = 0; ctx->tot_len = 0; } static void sha384_update(sha384_ctx *ctx, unsigned char *message, unsigned int len) { unsigned int block_nb; unsigned int new_len, rem_len; unsigned char *shifted_message; rem_len = SHA384_BLOCK_SIZE - ctx->len; memcpy(&ctx->block[ctx->len], message, rem_len); if (ctx->len + len < SHA384_BLOCK_SIZE) { ctx->len += len; return; } new_len = len - rem_len; block_nb = new_len / SHA384_BLOCK_SIZE; shifted_message = message + rem_len; sha512_transf(ctx, ctx->block, 1); sha512_transf(ctx, shifted_message, block_nb); rem_len = new_len % SHA384_BLOCK_SIZE; memcpy(ctx->block, &shifted_message[block_nb << 7], rem_len); ctx->len = rem_len; ctx->tot_len += (block_nb + 1) << 7; } static void sha384_final(sha384_ctx *ctx, unsigned char *digest) { unsigned int block_nb; unsigned int pm_len; unsigned int len_b; #ifndef UNROLL_LOOPS int i; #endif block_nb = (1 + ((SHA384_BLOCK_SIZE - 17) < (ctx->len % SHA384_BLOCK_SIZE))); len_b = (ctx->tot_len + ctx->len) << 3; pm_len = block_nb << 7; memset(ctx->block + ctx->len, 0, pm_len - ctx->len); ctx->block[ctx->len] = 0x80; UNPACK32(len_b, ctx->block + pm_len - 4); sha512_transf(ctx, ctx->block, block_nb); #ifndef UNROLL_LOOPS for (i = 0 ; i < 6; i++) { UNPACK64(ctx->h[i], &digest[i << 3]); } #else UNPACK64(ctx->h[0], &digest[ 0]); UNPACK64(ctx->h[1], &digest[ 8]); UNPACK64(ctx->h[2], &digest[16]); UNPACK64(ctx->h[3], &digest[24]); UNPACK64(ctx->h[4], &digest[32]); UNPACK64(ctx->h[5], &digest[40]); #endif /* !UNROLL_LOOPS */ } /* SHA-224 functions */ static void sha224_init(sha224_ctx *ctx) { #ifndef UNROLL_LOOPS int i; for (i = 0; i < 8; i++) { ctx->h[i] = sha224_h0[i]; } #else ctx->h[0] = sha224_h0[0]; ctx->h[1] = sha224_h0[1]; ctx->h[2] = sha224_h0[2]; ctx->h[3] = sha224_h0[3]; ctx->h[4] = sha224_h0[4]; ctx->h[5] = sha224_h0[5]; ctx->h[6] = sha224_h0[6]; ctx->h[7] = sha224_h0[7]; #endif /* !UNROLL_LOOPS */ ctx->len = 0; ctx->tot_len = 0; } static void sha224_update(sha224_ctx *ctx, unsigned char *message, unsigned int len) { unsigned int block_nb; unsigned int new_len, rem_len; unsigned char *shifted_message; rem_len = SHA224_BLOCK_SIZE - ctx->len; memcpy(&ctx->block[ctx->len], message, rem_len); if (ctx->len + len < SHA224_BLOCK_SIZE) { ctx->len += len; return; } new_len = len - rem_len; block_nb = new_len / SHA224_BLOCK_SIZE; shifted_message = message + rem_len; sha256_transf(ctx, ctx->block, 1); sha256_transf(ctx, shifted_message, block_nb); rem_len = new_len % SHA224_BLOCK_SIZE; memcpy(ctx->block, &shifted_message[block_nb << 6], rem_len); ctx->len = rem_len; ctx->tot_len += (block_nb + 1) << 6; } static void sha224_final(sha224_ctx *ctx, unsigned char *digest) { unsigned int block_nb; unsigned int pm_len; unsigned int len_b; #ifndef UNROLL_LOOPS int i; #endif block_nb = (1 + ((SHA224_BLOCK_SIZE - 9) < (ctx->len % SHA224_BLOCK_SIZE))); len_b = (ctx->tot_len + ctx->len) << 3; pm_len = block_nb << 6; memset(ctx->block + ctx->len, 0, pm_len - ctx->len); ctx->block[ctx->len] = 0x80; UNPACK32(len_b, ctx->block + pm_len - 4); sha256_transf(ctx, ctx->block, block_nb); #ifndef UNROLL_LOOPS for (i = 0 ; i < 7; i++) { UNPACK32(ctx->h[i], &digest[i << 2]); } #else UNPACK32(ctx->h[0], &digest[ 0]); UNPACK32(ctx->h[1], &digest[ 4]); UNPACK32(ctx->h[2], &digest[ 8]); UNPACK32(ctx->h[3], &digest[12]); UNPACK32(ctx->h[4], &digest[16]); UNPACK32(ctx->h[5], &digest[20]); UNPACK32(ctx->h[6], &digest[24]); #endif /* !UNROLL_LOOPS */ } #ifdef TEST_VECTORS /* FIPS 180-2 Validation tests */ #include #include void test(unsigned char *vector, unsigned char *digest, unsigned int digest_size) { unsigned char output[2 * SHA512_DIGEST_SIZE + 1]; int i; output[2 * digest_size] = '\0'; for (i = 0; i < digest_size ; i++) { sprintf((char *) output + 2*i, "%02x", digest[i]); } printf("H: %s\n", output); if (strcmp((char *) vector, (char *) output)) { fprintf(stderr, "Test failed.\n"); exit(1); } } int main() { static unsigned char *vectors[] = { /* SHA-224 */ "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", "75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525", "20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67", /* SHA-256 */ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0", /* SHA-384 */ "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed" "8086072ba1e7cc2358baeca134c825a7", "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712" "fcc7c71a557e2db966c3e9fa91746039", "9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b" "07b8b3dc38ecc4ebae97ddd87f3d8985", /* SHA-512 */ "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018" "501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909", "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973eb" "de0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b" }; static unsigned char message1[] = "abc"; static unsigned char message2[] = "abcdbcdecdefdefgefghfghighijhi" "jkijkljklmklmnlmnomnopnopq"; static unsigned char message2b[] = "abcdefghbcdefghicdefghijdefghijkefghij" "klfghijklmghijklmnhijklmnoijklmnopjklm" "nopqklmnopqrlmnopqrsmnopqrstnopqrstu"; unsigned char *message3; unsigned int message3_len = 1000000; unsigned char digest[SHA512_DIGEST_SIZE]; message3 = malloc(message3_len); if (message3 == NULL) { fprintf(stderr, "Can't allocate memory\n"); return -1; } memset(message3, 'a', message3_len); printf("SHA-2 FIPS 180-2 Validation tests\n\n"); printf("SHA-224 Test vectors\n"); sha224(message1, strlen((char *) message1), digest); test(vectors[0], digest, SHA224_DIGEST_SIZE); sha224(message2, strlen((char *) message2), digest); test(vectors[1], digest, SHA224_DIGEST_SIZE); sha224(message3, message3_len, digest); test(vectors[2], digest, SHA224_DIGEST_SIZE); printf("\n"); printf("SHA-256 Test vectors\n"); sha256(message1, strlen((char *) message1), digest); test(vectors[3], digest, SHA256_DIGEST_SIZE); sha256(message2, strlen((char *) message2), digest); test(vectors[4], digest, SHA256_DIGEST_SIZE); sha256(message3, message3_len, digest); test(vectors[5], digest, SHA256_DIGEST_SIZE); printf("\n"); printf("SHA-384 Test vectors\n"); sha384(message1, strlen((char *) message1), digest); test(vectors[6], digest, SHA384_DIGEST_SIZE); sha384(message2b, strlen((char *) message2b), digest); test(vectors[7], digest, SHA384_DIGEST_SIZE); sha384(message3, message3_len, digest); test(vectors[8], digest, SHA384_DIGEST_SIZE); printf("\n"); printf("SHA-512 Test vectors\n"); sha512(message1, strlen((char *) message1), digest); test(vectors[9], digest, SHA512_DIGEST_SIZE); sha512(message2b, strlen((char *) message2b), digest); test(vectors[10], digest, SHA512_DIGEST_SIZE); sha512(message3, message3_len, digest); test(vectors[11], digest, SHA512_DIGEST_SIZE); printf("\n"); printf("All tests passed.\n"); return 0; } #endif /* TEST_VECTORS */ /* Added for Xymon - not part of the original file */ int mySHA224_Size(void) { return sizeof(sha224_ctx); } void mySHA224_Init(void *c) { sha224_init((sha224_ctx *)c); } void mySHA224_Update(void *c, unsigned char *in, int len) {sha224_update((sha224_ctx *)c, in, len); } void mySHA224_Final(char md[20], void *c) { sha224_final((sha224_ctx *)c, md); } int mySHA256_Size(void) { return sizeof(sha256_ctx); } void mySHA256_Init(void *c) { sha256_init((sha256_ctx *)c); } void mySHA256_Update(void *c, unsigned char *in, int len) {sha256_update((sha256_ctx *)c, in, len); } void mySHA256_Final(char md[20], void *c) { sha256_final((sha256_ctx *)c, md); } int mySHA384_Size(void) { return sizeof(sha384_ctx); } void mySHA384_Init(void *c) { sha384_init((sha384_ctx *)c); } void mySHA384_Update(void *c, unsigned char *in, int len) {sha384_update((sha384_ctx *)c, in, len); } void mySHA384_Final(char md[20], void *c) { sha384_final((sha384_ctx *)c, md); } int mySHA512_Size(void) { return sizeof(sha512_ctx); } void mySHA512_Init(void *c) { sha512_init((sha512_ctx *)c); } void mySHA512_Update(void *c, unsigned char *in, int len) {sha512_update((sha512_ctx *)c, in, len); } void mySHA512_Final(char md[20], void *c) { sha512_final((sha512_ctx *)c, md); } xymon-4.3.7/lib/reportlog.h0000664000175000017500000000231611615341300015153 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __REPORTLOG_H__ #define __REPORTLOG_H__ #include #include "availability.h" #define STYLE_CRIT 0 #define STYLE_NONGR 1 #define STYLE_OTHER 2 extern char *stylenames[]; extern void generate_replog(FILE *htmlrep, FILE *textrep, char *textrepurl, char *hostname, char *service, int color, int style, char *ip, char *displayname, time_t st, time_t end, double reportwarnlevel, double reportgreenlevel, int reportwarnstops, reportinfo_t *repinfo); #endif xymon-4.3.7/lib/acklog.h0000664000175000017500000000216111615341300014374 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef _ACKLOG_H_ #define _ACKLOG_H_ /* Format of records in $XYMONACKDIR/acklog file (TAB separated) */ typedef struct ack_t { time_t acktime; int acknum; int duration; /* Minutes */ int acknum2; char *ackedby; char *hostname; char *testname; int color; char *ackmsg; int ackvalid; } ack_t; extern int havedoneacklog; extern void do_acklog(FILE *output, int maxcount, int maxminutes); #endif xymon-4.3.7/lib/sha1.h0000664000175000017500000000220411615341300013766 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* API for the SHA1 digest routines. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __SHA1_H__ #define __SHA1_H__ extern int mySHA1_Size(void); extern void mySHA1_Init(void *context); extern void mySHA1_Update(void *context, const unsigned char *data, int len); extern void mySHA1_Final(unsigned char digest[20], void *context); #endif xymon-4.3.7/lib/htmllog.h0000664000175000017500000000331111615341300014600 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __HTMLLOG_H__ #define __HTMLLOG_H__ #include enum histbutton_t { HIST_TOP, HIST_BOTTOM, HIST_NONE }; extern enum histbutton_t histlocation; extern void generate_html_log(char *hostname, char *displayname, char *service, char *ip, int color, int flapping, char *sender, char *flags, time_t logtime, char *timesincechange, char *firstline, char *restofmsg, char *modifiers, time_t acktime, char *ackmsg, char *acklist, time_t disabletime, char *dismsg, int is_history, int wantserviceid, int htmlfmt, int locatorbased, char *multigraphs, char *linktoclient, char *nkprio, char *nkttgroup, char *nkttextra, int graphtime, FILE *output); extern char *alttag(char *columnname, int color, int acked, int propagate, char *age); extern void setdocurl(char *url); extern void setdoctarget(char *target); extern char *hostnamehtml(char *hostname, char *defaultlink, int usetooltip); #endif xymon-4.3.7/lib/errormsg.c0000664000175000017500000001011511615341300014765 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for error- and debug-message display. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: errormsg.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include "libxymon.h" char *errbuf = NULL; int save_errbuf = 1; static unsigned int errbufsize = 0; static char *errappname = NULL; int debug = 0; static FILE *tracefd = NULL; static FILE *debugfd = NULL; void errprintf(const char *fmt, ...) { char timestr[30]; char msg[4096]; va_list args; time_t now = getcurrenttime(NULL); MEMDEFINE(timestr); MEMDEFINE(msg); strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&now)); fprintf(stderr, "%s ", timestr); if (errappname) fprintf(stderr, "%s ", errappname); va_start(args, fmt); vsnprintf(msg, sizeof(msg), fmt, args); va_end(args); fprintf(stderr, "%s", msg); fflush(stderr); if (save_errbuf) { if (errbuf == NULL) { errbufsize = 8192; errbuf = (char *) malloc(errbufsize); *errbuf = '\0'; } else if ((strlen(errbuf) + strlen(msg)) > errbufsize) { errbufsize += 8192; errbuf = (char *) realloc(errbuf, errbufsize); } strcat(errbuf, msg); } MEMUNDEFINE(timestr); MEMUNDEFINE(msg); } void dbgprintf(const char *fmt, ...) { if (debug) { va_list args; char timestr[30]; time_t now = getcurrenttime(NULL); MEMDEFINE(timestr); if (!debugfd) debugfd = stdout; strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&now)); fprintf(debugfd, "%d %s ", (int)getpid(), timestr); va_start(args, fmt); vfprintf(debugfd, fmt, args); va_end(args); fflush(debugfd); MEMUNDEFINE(timestr); } } void flush_errbuf(void) { if (errbuf) xfree(errbuf); errbuf = NULL; } void set_debugfile(char *fn, int appendtofile) { if (debugfd && (debugfd != stdout)) fclose(debugfd); if (fn) { debugfd = fopen(fn, (appendtofile ? "a" : "w")); if (debugfd == NULL) errprintf("Cannot open debug log %s\n", fn); } if (!debugfd) debugfd = stdout; } void starttrace(const char *fn) { if (tracefd) fclose(tracefd); if (fn) { tracefd = fopen(fn, "a"); if (tracefd == NULL) errprintf("Cannot open tracefile %s\n", fn); } else tracefd = stdout; } void stoptrace(void) { if (tracefd) fclose(tracefd); tracefd = NULL; } void traceprintf(const char *fmt, ...) { va_list args; if (tracefd) { char timestr[40]; time_t now = getcurrenttime(NULL); MEMDEFINE(timestr); strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&now)); fprintf(tracefd, "%08u %s ", (unsigned int)getpid(), timestr); va_start(args, fmt); vfprintf(tracefd, fmt, args); va_end(args); fflush(tracefd); MEMUNDEFINE(timestr); } } void redirect_cgilog(char *cginame) { char logfn[PATH_MAX]; char *cgilogdir; cgilogdir = getenv("XYMONCGILOGDIR"); if (cgilogdir == NULL) cgilogdir = xgetenv("XYMONSERVERLOGS"); if (cginame) errappname = strdup(cginame); sprintf(logfn, "%s/cgierror.err", cgilogdir); freopen(logfn, "a", stderr); /* If debugging, setup the debug logfile */ if (debug) { sprintf(logfn, "%s/%s.dbg", cginame, (errappname ? errappname : "cgi")); set_debugfile(logfn, 1); } } xymon-4.3.7/lib/encoding.c0000664000175000017500000001357711615341300014732 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for Base64 encoding and decoding. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: encoding.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include "libxymon.h" static char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; char *base64encode(unsigned char *buf) { unsigned char c0, c1, c2; unsigned int n0, n1, n2, n3; unsigned char *inp, *outp; unsigned char *result; result = malloc(4*(strlen(buf)/3 + 1) + 1); inp = buf; outp=result; while (strlen(inp) >= 3) { c0 = *inp; c1 = *(inp+1); c2 = *(inp+2); n0 = (c0 >> 2); /* 6 bits from c0 */ n1 = ((c0 & 3) << 4) + (c1 >> 4); /* 2 bits from c0, 4 bits from c1 */ n2 = ((c1 & 15) << 2) + (c2 >> 6); /* 4 bits from c1, 2 bits from c2 */ n3 = (c2 & 63); /* 6 bits from c2 */ *outp = b64chars[n0]; outp++; *outp = b64chars[n1]; outp++; *outp = b64chars[n2]; outp++; *outp = b64chars[n3]; outp++; inp += 3; } if (strlen(inp) == 1) { c0 = *inp; c1 = 0; n0 = (c0 >> 2); /* 6 bits from c0 */ n1 = ((c0 & 3) << 4) + (c1 >> 4); /* 2 bits from c0, 4 bits from c1 */ *outp = b64chars[n0]; outp++; *outp = b64chars[n1]; outp++; *outp = '='; outp++; *outp = '='; outp++; } else if (strlen(inp) == 2) { c0 = *inp; c1 = *(inp+1); c2 = 0; n0 = (c0 >> 2); /* 6 bits from c0 */ n1 = ((c0 & 3) << 4) + (c1 >> 4); /* 2 bits from c0, 4 bits from c1 */ n2 = ((c1 & 15) << 2) + (c2 >> 6); /* 4 bits from c1, 2 bits from c2 */ *outp = b64chars[n0]; outp++; *outp = b64chars[n1]; outp++; *outp = b64chars[n2]; outp++; *outp = '='; outp++; } *outp = '\0'; return result; } char *base64decode(unsigned char *buf) { static short bval[128] = { 0, }; static short bvalinit = 0; int n0, n1, n2, n3; unsigned char *inp, *outp; unsigned char *result; int bytesleft = strlen(buf); if (!bvalinit) { int i; bvalinit = 1; for (i=0; (i < strlen(b64chars)); i++) bval[(int)b64chars[i]] = i; } result = malloc(3*(bytesleft/4 + 1) + 1); inp = buf; outp=result; while (bytesleft >= 4) { n0 = bval[*(inp+0)]; n1 = bval[*(inp+1)]; n2 = bval[*(inp+2)]; n3 = bval[*(inp+3)]; *(outp+0) = (n0 << 2) + (n1 >> 4); /* 6 bits from n0, 2 from n1 */ *(outp+1) = ((n1 & 0x0F) << 4) + (n2 >> 2); /* 4 bits from n1, 4 from n2 */ *(outp+2) = ((n2 & 0x03) << 6) + (n3); /* 2 bits from n2, 6 from n3 */ inp += 4; bytesleft -= 4; outp += 3; } *outp = '\0'; return result; } void getescapestring(char *msg, unsigned char **buf, int *buflen) { char *inp, *outp; int outlen = 0; inp = msg; if (*inp == '\"') inp++; /* Skip the quote */ outp = *buf = malloc(strlen(msg)+1); while (*inp && (*inp != '\"')) { if (*inp == '\\') { inp++; if (*inp == 'r') { *outp = '\r'; outlen++; inp++; outp++; } else if (*inp == 'n') { *outp = '\n'; outlen++; inp++; outp++; } else if (*inp == 't') { *outp = '\t'; outlen++; inp++; outp++; } else if (*inp == '\\') { *outp = '\\'; outlen++; inp++; outp++; } else if (*inp == 'x') { inp++; if (isxdigit((int) *inp)) { *outp = hexvalue(*inp); inp++; if (isxdigit((int) *inp)) { *outp *= 16; *outp += hexvalue(*inp); inp++; } } else { errprintf("Invalid hex escape in '%s'\n", msg); } outlen++; outp++; } else { errprintf("Unknown escape sequence \\%c in '%s'\n", *inp, msg); } } else { *outp = *inp; outlen++; inp++; outp++; } } *outp = '\0'; if (buflen) *buflen = outlen; } unsigned char *nlencode(unsigned char *msg) { static unsigned char *buf = NULL; static int bufsz = 0; int maxneeded; unsigned char *inp, *outp; int n; if (msg == NULL) msg = ""; maxneeded = 2*strlen(msg)+1; if (buf == NULL) { bufsz = maxneeded; buf = (char *)malloc(bufsz); } else if (bufsz < maxneeded) { bufsz = maxneeded; buf = (char *)realloc(buf, bufsz); } inp = msg; outp = buf; while (*inp) { n = strcspn(inp, "|\n\r\t\\"); if (n > 0) { memcpy(outp, inp, n); outp += n; inp += n; } if (*inp) { *outp = '\\'; outp++; switch (*inp) { case '|' : *outp = 'p'; outp++; break; case '\n': *outp = 'n'; outp++; break; case '\r': *outp = 'r'; outp++; break; case '\t': *outp = 't'; outp++; break; case '\\': *outp = '\\'; outp++; break; } inp++; } } *outp = '\0'; return buf; } void nldecode(unsigned char *msg) { unsigned char *inp = msg; unsigned char *outp = msg; int n; if ((msg == NULL) || (*msg == '\0')) return; while (*inp) { n = strcspn(inp, "\\"); if (n > 0) { if (inp != outp) memmove(outp, inp, n); inp += n; outp += n; } /* *inp is either a backslash or a \0 */ if (*inp == '\\') { inp++; switch (*inp) { case 'p': *outp = '|'; outp++; inp++; break; case 'r': *outp = '\r'; outp++; inp++; break; case 'n': *outp = '\n'; outp++; inp++; break; case 't': *outp = '\t'; outp++; inp++; break; case '\\': *outp = '\\'; outp++; inp++; break; } } } *outp = '\0'; } xymon-4.3.7/lib/md5.c0000664000175000017500000003411011535462534013631 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This file is part of the Xymon monitor library, but was written by */ /* Peter Deutsch and released under the GNU GPL. */ /*----------------------------------------------------------------------------*/ /* Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. L. Peter Deutsch ghost@aladdin.com */ /* $Id: md5.c 6650 2011-03-08 17:20:28Z storner $ */ /* Independent implementation of MD5 (RFC 1321). This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being copyrighted. The original and principal author of md5.c is L. Peter Deutsch . Other authors are noted in the change history that follows (in reverse chronological order): 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order either statically or dynamically; added missing #include in library. 2002-03-11 lpd Corrected argument list for main(), and added int return type, in test program and T value program. 2002-02-21 lpd Added missing #include in test program. 2000-07-03 lpd Patched to eliminate warnings about "constant is unsigned in ANSI C, signed in traditional"; made test program self-checking. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). 1999-05-03 lpd Original version. */ /************ XYMON SPECIFIC MODIFICATION *****************/ /* For Xymon: Moved these definitions from md5.h into here */ typedef unsigned char md5_byte_t; /* 8-bit byte */ typedef unsigned int md5_word_t; /* 32-bit word */ /* Define the state of the MD5 Algorithm. */ typedef struct md5_state_s { md5_word_t count[2]; /* message length in bits, lsw first */ md5_word_t abcd[4]; /* digest buffer */ md5_byte_t buf[64]; /* accumulate block */ } md5_state_t; /************ END XYMON SPECIFIC MODIFICATION *****************/ #include #undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ #ifdef ARCH_IS_BIG_ENDIAN # define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) #else # define BYTE_ORDER 0 #endif #define T_MASK ((md5_word_t)~0) #define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) #define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) #define T3 0x242070db #define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) #define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) #define T6 0x4787c62a #define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) #define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) #define T9 0x698098d8 #define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) #define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) #define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) #define T13 0x6b901122 #define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) #define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) #define T16 0x49b40821 #define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) #define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) #define T19 0x265e5a51 #define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) #define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) #define T22 0x02441453 #define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) #define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) #define T25 0x21e1cde6 #define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) #define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) #define T28 0x455a14ed #define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) #define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) #define T31 0x676f02d9 #define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) #define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) #define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) #define T35 0x6d9d6122 #define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) #define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) #define T38 0x4bdecfa9 #define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) #define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) #define T41 0x289b7ec6 #define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) #define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) #define T44 0x04881d05 #define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) #define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) #define T47 0x1fa27cf8 #define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) #define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) #define T50 0x432aff97 #define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) #define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) #define T53 0x655b59c3 #define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) #define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) #define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) #define T57 0x6fa87e4f #define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) #define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) #define T60 0x4e0811a1 #define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) #define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) #define T63 0x2ad7d2bb #define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) static void md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) { md5_word_t a = pms->abcd[0], b = pms->abcd[1], c = pms->abcd[2], d = pms->abcd[3]; md5_word_t t; #if BYTE_ORDER > 0 /* Define storage only for big-endian CPUs. */ md5_word_t X[16]; #else /* Define storage for little-endian or both types of CPUs. */ md5_word_t xbuf[16]; const md5_word_t *X; #endif { #if BYTE_ORDER == 0 /* * Determine dynamically whether this is a big-endian or * little-endian machine, since we can use a more efficient * algorithm on the latter. */ static const int w = 1; if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ #endif #if BYTE_ORDER <= 0 /* little-endian */ { /* * On little-endian machines, we can process properly aligned * data without copying it. */ if (!((data - (const md5_byte_t *)0) & 3)) { /* data are properly aligned */ X = (const md5_word_t *)data; } else { /* not aligned */ memcpy(xbuf, data, 64); X = xbuf; } } #endif #if BYTE_ORDER == 0 else /* dynamic big-endian */ #endif #if BYTE_ORDER >= 0 /* big-endian */ { /* * On big-endian machines, we must arrange the bytes in the * right order. */ const md5_byte_t *xp = data; int i; # if BYTE_ORDER == 0 X = xbuf; /* (dynamic only) */ # else # define xbuf X /* (static only) */ # endif for (i = 0; i < 16; ++i, xp += 4) xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); } #endif } #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) /* Round 1. */ /* Let [abcd k s i] denote the operation a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ #define F(x, y, z) (((x) & (y)) | (~(x) & (z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + F(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 7, T1); SET(d, a, b, c, 1, 12, T2); SET(c, d, a, b, 2, 17, T3); SET(b, c, d, a, 3, 22, T4); SET(a, b, c, d, 4, 7, T5); SET(d, a, b, c, 5, 12, T6); SET(c, d, a, b, 6, 17, T7); SET(b, c, d, a, 7, 22, T8); SET(a, b, c, d, 8, 7, T9); SET(d, a, b, c, 9, 12, T10); SET(c, d, a, b, 10, 17, T11); SET(b, c, d, a, 11, 22, T12); SET(a, b, c, d, 12, 7, T13); SET(d, a, b, c, 13, 12, T14); SET(c, d, a, b, 14, 17, T15); SET(b, c, d, a, 15, 22, T16); #undef SET /* Round 2. */ /* Let [abcd k s i] denote the operation a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ #define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + G(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 1, 5, T17); SET(d, a, b, c, 6, 9, T18); SET(c, d, a, b, 11, 14, T19); SET(b, c, d, a, 0, 20, T20); SET(a, b, c, d, 5, 5, T21); SET(d, a, b, c, 10, 9, T22); SET(c, d, a, b, 15, 14, T23); SET(b, c, d, a, 4, 20, T24); SET(a, b, c, d, 9, 5, T25); SET(d, a, b, c, 14, 9, T26); SET(c, d, a, b, 3, 14, T27); SET(b, c, d, a, 8, 20, T28); SET(a, b, c, d, 13, 5, T29); SET(d, a, b, c, 2, 9, T30); SET(c, d, a, b, 7, 14, T31); SET(b, c, d, a, 12, 20, T32); #undef SET /* Round 3. */ /* Let [abcd k s t] denote the operation a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ #define H(x, y, z) ((x) ^ (y) ^ (z)) #define SET(a, b, c, d, k, s, Ti)\ t = a + H(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 5, 4, T33); SET(d, a, b, c, 8, 11, T34); SET(c, d, a, b, 11, 16, T35); SET(b, c, d, a, 14, 23, T36); SET(a, b, c, d, 1, 4, T37); SET(d, a, b, c, 4, 11, T38); SET(c, d, a, b, 7, 16, T39); SET(b, c, d, a, 10, 23, T40); SET(a, b, c, d, 13, 4, T41); SET(d, a, b, c, 0, 11, T42); SET(c, d, a, b, 3, 16, T43); SET(b, c, d, a, 6, 23, T44); SET(a, b, c, d, 9, 4, T45); SET(d, a, b, c, 12, 11, T46); SET(c, d, a, b, 15, 16, T47); SET(b, c, d, a, 2, 23, T48); #undef SET /* Round 4. */ /* Let [abcd k s t] denote the operation a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ #define I(x, y, z) ((y) ^ ((x) | ~(z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + I(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 6, T49); SET(d, a, b, c, 7, 10, T50); SET(c, d, a, b, 14, 15, T51); SET(b, c, d, a, 5, 21, T52); SET(a, b, c, d, 12, 6, T53); SET(d, a, b, c, 3, 10, T54); SET(c, d, a, b, 10, 15, T55); SET(b, c, d, a, 1, 21, T56); SET(a, b, c, d, 8, 6, T57); SET(d, a, b, c, 15, 10, T58); SET(c, d, a, b, 6, 15, T59); SET(b, c, d, a, 13, 21, T60); SET(a, b, c, d, 4, 6, T61); SET(d, a, b, c, 11, 10, T62); SET(c, d, a, b, 2, 15, T63); SET(b, c, d, a, 9, 21, T64); #undef SET /* Then perform the following additions. (That is increment each of the four registers by the value it had before this block was started.) */ pms->abcd[0] += a; pms->abcd[1] += b; pms->abcd[2] += c; pms->abcd[3] += d; } static void md5_init(md5_state_t *pms) { pms->count[0] = pms->count[1] = 0; pms->abcd[0] = 0x67452301; pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; pms->abcd[3] = 0x10325476; } static void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) { const md5_byte_t *p = data; int left = nbytes; int offset = (pms->count[0] >> 3) & 63; md5_word_t nbits = (md5_word_t)(nbytes << 3); if (nbytes <= 0) return; /* Update the message length. */ pms->count[1] += nbytes >> 29; pms->count[0] += nbits; if (pms->count[0] < nbits) pms->count[1]++; /* Process an initial partial block. */ if (offset) { int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); memcpy(pms->buf + offset, p, copy); if (offset + copy < 64) return; p += copy; left -= copy; md5_process(pms, pms->buf); } /* Process full blocks. */ for (; left >= 64; p += 64, left -= 64) md5_process(pms, p); /* Process a final partial block. */ if (left) memcpy(pms->buf, p, left); } static void md5_finish(md5_state_t *pms, md5_byte_t digest[16]) { static const md5_byte_t pad[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; md5_byte_t data[8]; int i; /* Save the length before padding. */ for (i = 0; i < 8; ++i) data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); /* Pad to 56 bytes mod 64. */ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); /* Append the length. */ md5_append(pms, data, 8); for (i = 0; i < 16; ++i) digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); } /* Added for use with Xymon */ int myMD5_Size(void) { return sizeof(md5_state_t); } void myMD5_Init(void *pms) { md5_init((md5_state_t *)pms); } void myMD5_Update(void *pms, unsigned char *data, int nbytes) { md5_append((md5_state_t *)pms, (md5_byte_t *)data, nbytes); } void myMD5_Final(unsigned char digest[16], void *pms) { md5_finish((md5_state_t *)pms, (md5_byte_t *)digest); } #ifdef STANDALONE #include #include #include int main(int argc, char *argv[]) { FILE *fd; int n; unsigned char buf[8192]; void *context; unsigned char digest[16]; int i; fd = fopen(argv[1], "r"); if (fd == NULL) return 1; context = (void *)malloc(myMD5_Size()); myMD5_Init(context); while ((n = fread(buf, 1, sizeof(buf), fd)) > 0) myMD5_Update(context, buf, n); fclose(fd); myMD5_Final(digest, context); for (i=0; (i < sizeof(digest)); i++) printf("%02x", digest[i]); printf("\n"); return 0; } #endif xymon-4.3.7/lib/cgi.c0000664000175000017500000001531311615341300013674 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for handling CGI requests. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: cgi.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include "libxymon.h" #define MAX_REQ_SIZE (1024*1024) enum cgi_method_t cgi_method = CGI_OTHER; static char *cgi_error_text = NULL; static void lcgi_error(char *msg) { cgi_error_text = msg; } char *cgi_error(void) { char *result = cgi_error_text; cgi_error_text = NULL; return result; } cgidata_t *cgi_request(void) { char *method = NULL; char *reqdata = NULL; char *conttype = NULL; char *token; cgidata_t *head = NULL, *tail = NULL; cgi_error_text = NULL; cgi_method = CGI_OTHER; method = getenv("REQUEST_METHOD"); if (!method) { lcgi_error("CGI violation - no REQUEST_METHOD\n"); return NULL; } conttype = getenv("CONTENT_TYPE"); if (strcasecmp(method, "POST") == 0) { char *contlen = getenv("CONTENT_LENGTH"); int postsize = 0; cgi_method = CGI_POST; if (contlen) { postsize = atoi(contlen); } else { lcgi_error("CGI violation - no CONTENT_LENGTH\n"); return NULL; } if (postsize < MAX_REQ_SIZE) { size_t n; reqdata = (char *)malloc(postsize+1); n = fread(reqdata, 1, postsize, stdin); if (n < postsize) { lcgi_error("Error reading POST data\n"); return NULL; } reqdata[n] = '\0'; } else { lcgi_error("Request too large\n"); return NULL; } } else if (strcasecmp(method, "GET") == 0) { char *q = getenv("QUERY_STRING"); cgi_method = CGI_GET; if (q) { if (strlen(q) < MAX_REQ_SIZE) { reqdata = strdup(q); } else { lcgi_error("Request too large\n"); return NULL; } } else { /* This is OK - we may not have any query */ return NULL; } } dbgprintf("CGI: Request method='%s', data='%s'\n", method, textornull(reqdata)); if ((cgi_method == CGI_GET) || (conttype && (strcasecmp(conttype, "application/x-www-form-urlencoded") == 0))) { token = strtok(reqdata, "&"); while (token) { cgidata_t *newitem = (cgidata_t *)calloc(1, sizeof(cgidata_t)); char *val; val = strchr(token, '='); if (val) { *val = '\0'; val = urlunescape(val+1); } newitem->name = strdup(token); newitem->value = strdup(val ? val : ""); if (!tail) { head = newitem; } else { tail->next = newitem; } tail = newitem; token = strtok(NULL, "&"); } } else if ((cgi_method == CGI_POST) && (conttype && (strcasecmp(conttype, "multipart/form-data") == 0))) { char *bol, *eoln, *delim; char eolnchar = '\n'; char *currelembegin = NULL, *currelemend = NULL; cgidata_t *newitem = NULL; delim = reqdata; eoln = strchr(delim, '\n'); if (!eoln) return NULL; *eoln = '\0'; delim = strdup(reqdata); *eoln = '\n'; if (*(delim + strlen(delim) - 1) == '\r') { eolnchar = '\r'; *(delim + strlen(delim) - 1) = '\0'; } bol = reqdata; do { eoln = strchr(bol, eolnchar); if (eoln) *eoln = '\0'; if (strncmp(bol, delim, strlen(delim)) == 0) { if (newitem && currelembegin && (currelemend >= currelembegin)) { /* Finish off the current item */ char savech; savech = *currelemend; *currelemend = '\0'; newitem->value = strdup(currelembegin); *currelemend = savech; currelembegin = currelemend = NULL; } if (strcmp(bol+strlen(delim), "--") != 0) { /* New element */ newitem = (cgidata_t *)calloc(1, sizeof(cgidata_t)); if (!tail) head = newitem; else tail->next = newitem; tail = newitem; } else { /* No more elements, end of input */ newitem = NULL; bol = NULL; continue; } } else if (newitem && (strncasecmp(bol, "Content-Disposition:", 20) == 0)) { char *tok; tok = strtok(bol, ";\t "); while (tok) { if (strncasecmp(tok, "name=", 5) == 0) { char *name; name = tok+5; if (*name == '\"') { name++; *(name + strlen(name) - 1) = '\0'; } newitem->name = strdup(name); } else if (strncasecmp(tok, "filename=", 9) == 0) { char *filename; filename = tok+9; if (*filename == '\"') { filename++; *(filename + strlen(filename) - 1) = '\0'; } newitem->filename = strdup(filename); } tok = strtok(NULL, ";\t "); } } else if (newitem && (strncasecmp(bol, "Content-Type:", 12) == 0)) { } else if (newitem && !currelembegin && (*bol == '\0')) { /* End of headers for one element */ if (eoln) { currelembegin = eoln+1; if ((eolnchar == '\r') && (*currelembegin == '\n')) currelembegin++; } currelemend = currelembegin; } else if (newitem && currelembegin) { currelemend = (eoln ? eoln+1 : bol + strlen(bol)); } if (eoln) { bol = eoln+1; if ((eolnchar == '\r') && (*bol == '\n')) bol++; } else { bol = NULL; } } while (bol && (*bol)); if (newitem) { if (!newitem->name) newitem->name = ""; if (!newitem->value) newitem->value = ""; } } else { /* Raw data - return a single record to caller */ head = (cgidata_t *)calloc(1, sizeof(cgidata_t)); head->name = strdup(""); head->value = reqdata ? strdup(reqdata) : NULL; } if (reqdata) xfree(reqdata); return head; } char *get_cookie(char *cookiename) { static char *ckdata = NULL; char *tok, *p; int n; /* If no cookie, just return NULL */ p = getenv("HTTP_COOKIE"); if (!p) return NULL; if (ckdata) xfree(ckdata); n = strlen(cookiename); /* Split the cookie variable into elements, separated by ";" and possible space. */ ckdata = strdup(p); tok = strtok(ckdata, "; "); while (tok) { if ((strncmp(cookiename, tok, n) == 0) && (*(tok+n) == '=')) { /* Got it */ return (tok+n+1); } tok = strtok(NULL, "; "); } xfree(ckdata); ckdata = NULL; return NULL; } xymon-4.3.7/lib/color.c0000664000175000017500000000732111615341300014250 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for color <-> string conversion */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: color.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include "libxymon.h" int use_recentgifs = 0; char *colorname(int color) { static char *cs = ""; switch (color) { case COL_CLEAR: cs = "clear"; break; case COL_BLUE: cs = "blue"; break; case COL_PURPLE: cs = "purple"; break; case COL_GREEN: cs = "green"; break; case COL_YELLOW: cs = "yellow"; break; case COL_RED: cs = "red"; break; default: cs = "unknown"; break; } return cs; } int parse_color(char *colortext) { char inpcolor[10]; int n; if (!colortext) return -1; MEMDEFINE(inpcolor); strncpy(inpcolor, colortext, 7); inpcolor[7] = '\0'; n = strspn(inpcolor, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); inpcolor[n] = '\0'; strcat(inpcolor, " "); if (strncasecmp(inpcolor, "green ", 6) == 0) { MEMUNDEFINE(inpcolor); return COL_GREEN; } else if (strncasecmp(inpcolor, "yellow ", 7) == 0) { MEMUNDEFINE(inpcolor); return COL_YELLOW; } else if (strncasecmp(inpcolor, "red ", 4) == 0) { MEMUNDEFINE(inpcolor); return COL_RED; } else if (strncasecmp(inpcolor, "blue ", 5) == 0) { MEMUNDEFINE(inpcolor); return COL_BLUE; } else if (strncasecmp(inpcolor, "clear ", 6) == 0) { MEMUNDEFINE(inpcolor); return COL_CLEAR; } else if (strncasecmp(inpcolor, "purple ", 7) == 0) { MEMUNDEFINE(inpcolor); return COL_PURPLE; } else if (strncasecmp(inpcolor, "client ", 7) == 0) { MEMUNDEFINE(inpcolor); return COL_CLIENT; } MEMUNDEFINE(inpcolor); return -1; } int eventcolor(char *colortext) { if (!colortext) return -1; if (strcmp(colortext, "cl") == 0) return COL_CLEAR; else if (strcmp(colortext, "bl") == 0) return COL_BLUE; else if (strcmp(colortext, "pu") == 0) return COL_PURPLE; else if (strcmp(colortext, "gr") == 0) return COL_GREEN; else if (strcmp(colortext, "ye") == 0) return COL_YELLOW; else if (strcmp(colortext, "re") == 0) return COL_RED; else return -1; } char *dotgiffilename(int color, int acked, int oldage) { static char *filename = NULL; /* yellow-recent.gif */ /* Allocate the first time, never free */ if (filename == NULL) filename = (char *)malloc(20); strcpy(filename, colorname(color)); if (acked) { strcat(filename, "-ack"); } else if (use_recentgifs) { strcat(filename, (oldage ? "" : "-recent")); } strcat(filename, ".gif"); return filename; } #ifndef CLIENTONLY int colorset(char *colspec, int excludeset) { char *cspeccopy = strdup(colspec); int c, ac; char *p; char *pp; p = strtok_r(cspeccopy, ",", &pp); ac = 0; while (p) { c = parse_color(p); if (c != -1) ac = (ac | (1 << c)); p = strtok_r(NULL, ",", &pp); } xfree(cspeccopy); /* Some color may be forbidden */ ac = (ac & ~excludeset); return ac; } #endif xymon-4.3.7/lib/timing.c0000664000175000017500000000655111615341300014425 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for timing program execution. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: timing.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include "libxymon.h" int timing = 0; typedef struct timestamp_t { char *eventtext; struct timespec eventtime; struct timestamp_t *prev; struct timestamp_t *next; } timestamp_t; static timestamp_t *stamphead = NULL; static timestamp_t *stamptail = NULL; void add_timestamp(const char *msg) { if (timing) { timestamp_t *newstamp = (timestamp_t *) malloc(sizeof(timestamp_t)); getntimer(&newstamp->eventtime); newstamp->eventtext = strdup(msg); if (stamphead == NULL) { newstamp->next = newstamp->prev = NULL; stamphead = newstamp; } else { newstamp->prev = stamptail; newstamp->next = NULL; stamptail->next = newstamp; } stamptail = newstamp; } } void show_timestamps(char **buffer) { timestamp_t *s; struct timespec dif; char *outbuf = (char *) malloc(4096); int outbuflen = 4096; char buf1[80]; if (!timing || (stamphead == NULL)) return; MEMDEFINE(buf1); strcpy(outbuf, "\n\nTIME SPENT\n"); strcat(outbuf, "Event "); strcat(outbuf, " Start time"); strcat(outbuf, " Duration\n"); for (s=stamphead; (s); s=s->next) { sprintf(buf1, "%-40s ", s->eventtext); strcat(outbuf, buf1); sprintf(buf1, "%10u.%06u ", (unsigned int)s->eventtime.tv_sec, (unsigned int)s->eventtime.tv_nsec / 1000); strcat(outbuf, buf1); if (s->prev) { tvdiff(&((timestamp_t *)s->prev)->eventtime, &s->eventtime, &dif); sprintf(buf1, "%10u.%06u ", (unsigned int)dif.tv_sec, (unsigned int)dif.tv_nsec / 1000); strcat(outbuf, buf1); } else strcat(outbuf, " -"); strcat(outbuf, "\n"); if ((outbuflen - strlen(outbuf)) < 200) { outbuflen += 4096; outbuf = (char *) realloc(outbuf, outbuflen); } } tvdiff(&stamphead->eventtime, &stamptail->eventtime, &dif); sprintf(buf1, "%-40s ", "TIME TOTAL"); strcat(outbuf, buf1); sprintf(buf1, "%-18s", ""); strcat(outbuf, buf1); sprintf(buf1, "%10u.%06u ", (unsigned int)dif.tv_sec, (unsigned int)dif.tv_nsec / 1000); strcat(outbuf, buf1); strcat(outbuf, "\n"); if (buffer == NULL) { printf("%s", outbuf); xfree(outbuf); } else *buffer = outbuf; MEMUNDEFINE(buf1); } long total_runtime(void) { if (timing) return (stamptail->eventtime.tv_sec - stamphead->eventtime.tv_sec); else return 0; } xymon-4.3.7/lib/loadalerts.c0000664000175000017500000011640111667656103015305 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module for Xymon, responsible for loading the */ /* alerts.cfg file which holds information about the Xymon alert */ /* configuration. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: loadalerts.c 6795 2011-12-07 12:29:55Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" /* token's are the pre-processor macros we expand while parsing the config file */ typedef struct token_t { char *name; char *value; struct token_t *next; } token_t; static token_t *tokhead = NULL; /* This defines a rule. Some general criteria, and a list of recipients. */ typedef struct rule_t { int cfid; criteria_t *criteria; recip_t *recipients; struct rule_t *next; } rule_t; static rule_t *rulehead = NULL; static rule_t *ruletail = NULL; static int cfid = 0; static char cfline[256]; static int printmode = 0; static rule_t *printrule = NULL; static enum { P_NONE, P_RULE, P_RECIP } pstate = P_NONE; static int defaultcolors = 0; static criteria_t *setup_criteria(rule_t **currule, recip_t **currcp) { criteria_t *crit = NULL; MEMDEFINE(cfline); switch (pstate) { case P_NONE: *currule = (rule_t *)calloc(1, sizeof(rule_t)); (*currule)->cfid = cfid; pstate = P_RULE; /* Fall through */ case P_RULE: if (!(*currule)->criteria) (*currule)->criteria = (criteria_t *)calloc(1, sizeof(criteria_t)); crit = (*currule)->criteria; crit->cfid = cfid; if (crit->cfline == NULL) crit->cfline = strdup(cfline); *currcp = NULL; break; case P_RECIP: if (!(*currcp)->criteria) { recip_t *rwalk; (*currcp)->criteria = (criteria_t *)calloc(1, sizeof(criteria_t)); /* Make sure other recipients on the same rule also get these criteria */ for (rwalk = (*currule)->recipients; (rwalk); rwalk = rwalk->next) { if (rwalk->cfid == cfid) rwalk->criteria = (*currcp)->criteria; } } crit = (*currcp)->criteria; crit->cfid = cfid; if (crit->cfline == NULL) crit->cfline = strdup(cfline); break; } MEMUNDEFINE(cfline); return crit; } static char *preprocess(char *buf) { /* Expands config-file macros */ static strbuffer_t *result = NULL; char *inp; if (result == NULL) result = newstrbuffer(8192); clearstrbuffer(result); inp = buf; while (inp) { char *p; p = strchr(inp, '$'); if (p == NULL) { addtobuffer(result, inp); inp = NULL; } else { token_t *twalk; char savech; int n; *p = '\0'; addtobuffer(result, inp); p = (p+1); n = strcspn(p, "\t $.,|%!()[]{}+?/&@:;*"); savech = *(p+n); *(p+n) = '\0'; for (twalk = tokhead; (twalk && strcmp(p, twalk->name)); twalk = twalk->next) ; *(p+n) = savech; if (twalk) addtobuffer(result, twalk->value); inp = p+n; } } return STRBUF(result); } static void flush_rule(rule_t *currule) { if (currule == NULL) return; currule->next = NULL; if (rulehead == NULL) { rulehead = ruletail = currule; } else { ruletail->next = currule; ruletail = currule; } } static void free_criteria(criteria_t *crit) { if (crit->cfline) xfree(crit->cfline); if (crit->pagespec) xfree(crit->pagespec); if (crit->pagespecre) pcre_free(crit->pagespecre); if (crit->expagespec) xfree(crit->expagespec); if (crit->expagespecre) pcre_free(crit->expagespecre); if (crit->dgspec) xfree(crit->dgspec); if (crit->dgspecre) pcre_free(crit->dgspecre); if (crit->exdgspec) xfree(crit->exdgspec); if (crit->exdgspecre) pcre_free(crit->exdgspecre); if (crit->hostspec) xfree(crit->hostspec); if (crit->hostspecre) pcre_free(crit->hostspecre); if (crit->exhostspec) xfree(crit->exhostspec); if (crit->exhostspecre) pcre_free(crit->exhostspecre); if (crit->svcspec) xfree(crit->svcspec); if (crit->svcspecre) pcre_free(crit->svcspecre); if (crit->exsvcspec) xfree(crit->exsvcspec); if (crit->exsvcspecre) pcre_free(crit->exsvcspecre); if (crit->classspec) xfree(crit->classspec); if (crit->classspecre) pcre_free(crit->classspecre); if (crit->exclassspec) xfree(crit->exclassspec); if (crit->exclassspecre) pcre_free(crit->exclassspecre); if (crit->groupspec) xfree(crit->groupspec); if (crit->groupspecre) pcre_free(crit->groupspecre); if (crit->exgroupspec) xfree(crit->exgroupspec); if (crit->exgroupspecre) pcre_free(crit->exgroupspecre); if (crit->timespec) xfree(crit->timespec); } int load_alertconfig(char *configfn, int defcolors, int defaultinterval) { /* (Re)load the configuration file without leaking memory */ static void *configfiles = NULL; char fn[PATH_MAX]; FILE *fd; strbuffer_t *inbuf; char *p; rule_t *currule = NULL; recip_t *currcp = NULL, *rcptail = NULL; MEMDEFINE(fn); if (configfn) strcpy(fn, configfn); else sprintf(fn, "%s/etc/alerts.cfg", xgetenv("XYMONHOME")); /* First check if there were no modifications at all */ if (configfiles) { if (!stackfmodified(configfiles)){ dbgprintf("No files modified, skipping reload of %s\n", fn); MEMUNDEFINE(fn); return 0; } else { stackfclist(&configfiles); configfiles = NULL; } } fd = stackfopen(fn, "r", &configfiles); if (!fd) { errprintf("Cannot open configuration file %s: %s\n", fn, strerror(errno)); MEMUNDEFINE(fn); return 0; } /* First, clean out the old rule set */ while (rulehead) { rule_t *trule; if (rulehead->criteria) { free_criteria(rulehead->criteria); xfree(rulehead->criteria); } while (rulehead->recipients) { recip_t *trecip = rulehead->recipients; if (trecip->criteria) { recip_t *rwalk; /* Clear out the duplicate criteria that may exist, to avoid double-free'ing them */ for (rwalk = trecip->next; (rwalk); rwalk = rwalk->next) { if (rwalk->criteria == trecip->criteria) rwalk->criteria = NULL; } free_criteria(trecip->criteria); xfree(trecip->criteria); } if (trecip->recipient) xfree(trecip->recipient); if (trecip->scriptname) xfree(trecip->scriptname); rulehead->recipients = rulehead->recipients->next; xfree(trecip); } trule = rulehead; rulehead = rulehead->next; xfree(trule); } while (tokhead) { token_t *ttok; if (tokhead->name) xfree(tokhead->name); if (tokhead->value) xfree(tokhead->value); ttok = tokhead; tokhead = tokhead->next; xfree(ttok); } defaultcolors = defcolors; MEMDEFINE(cfline); cfid = 0; inbuf = newstrbuffer(0); while (stackfgets(inbuf, NULL)) { int firsttoken = 1; int mailcmdactive = 0, scriptcmdactive = 0; recip_t *curlinerecips = NULL; cfid++; sanitize_input(inbuf, 1, 0); /* Skip empty lines */ if (STRBUFLEN(inbuf) == 0) continue; if ((*STRBUF(inbuf) == '$') && strchr(STRBUF(inbuf), '=')) { /* Define a macro */ token_t *newtok = (token_t *) malloc(sizeof(token_t)); char *delim; delim = strchr(STRBUF(inbuf), '='); *delim = '\0'; newtok->name = strdup(STRBUF(inbuf)+1); /* Skip the '$' */ newtok->value = strdup(preprocess(delim+1)); newtok->next = tokhead; tokhead = newtok; continue; } strncpy(cfline, STRBUF(inbuf), (sizeof(cfline)-1)); cfline[sizeof(cfline)-1] = '\0'; /* Expand macros inside the line before parsing */ p = strtok(preprocess(STRBUF(inbuf)), " \t"); while (p) { if ((strncasecmp(p, "PAGE=", 5) == 0) || (strncasecmp(p, "PAGES=", 6) == 0)) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->pagespec = strdup(val); if (*(crit->pagespec) == '%') crit->pagespecre = compileregex(crit->pagespec+1); firsttoken = 0; } else if ((strncasecmp(p, "EXPAGE=", 7) == 0) || (strncasecmp(p, "EXPAGES=", 8) == 0)) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->expagespec = strdup(val); if (*(crit->expagespec) == '%') crit->expagespecre = compileregex(crit->expagespec+1); firsttoken = 0; } else if ((strncasecmp(p, "DISPLAYGROUP=", 13) == 0) || (strncasecmp(p, "DISPLAYGROUPS=", 14) == 0)) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->dgspec = strdup(val); if (*(crit->dgspec) == '%') crit->dgspecre = compileregex(crit->dgspec+1); firsttoken = 0; } else if ((strncasecmp(p, "EXDISPLAYGROUP=", 15) == 0) || (strncasecmp(p, "EXDISPLAYGROUPS=", 16) == 0)) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->exdgspec = strdup(val); if (*(crit->exdgspec) == '%') crit->exdgspecre = compileregex(crit->exdgspec+1); firsttoken = 0; } else if ((strncasecmp(p, "HOST=", 5) == 0) || (strncasecmp(p, "HOSTS=", 6) == 0)) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->hostspec = strdup(val); if (*(crit->hostspec) == '%') crit->hostspecre = compileregex(crit->hostspec+1); firsttoken = 0; } else if ((strncasecmp(p, "EXHOST=", 7) == 0) || (strncasecmp(p, "EXHOSTS=", 8) == 0)) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->exhostspec = strdup(val); if (*(crit->exhostspec) == '%') crit->exhostspecre = compileregex(crit->exhostspec+1); firsttoken = 0; } else if ((strncasecmp(p, "SERVICE=", 8) == 0) || (strncasecmp(p, "SERVICES=", 9) == 0)) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->svcspec = strdup(val); if (*(crit->svcspec) == '%') crit->svcspecre = compileregex(crit->svcspec+1); firsttoken = 0; } else if ((strncasecmp(p, "EXSERVICE=", 10) == 0) || (strncasecmp(p, "EXSERVICES=", 11) == 0)) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->exsvcspec = strdup(val); if (*(crit->exsvcspec) == '%') crit->exsvcspecre = compileregex(crit->exsvcspec+1); firsttoken = 0; } else if (strncasecmp(p, "CLASS=", 6) == 0) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->classspec = strdup(val); if (*(crit->classspec) == '%') crit->classspecre = compileregex(crit->classspec+1); firsttoken = 0; } else if (strncasecmp(p, "EXCLASS=", 8) == 0) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->exclassspec = strdup(val); if (*(crit->exclassspec) == '%') crit->exclassspecre = compileregex(crit->exclassspec+1); firsttoken = 0; } else if (strncasecmp(p, "GROUP=", 6) == 0) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->groupspec = strdup(val); if (*(crit->groupspec) == '%') crit->groupspecre = compileregex(crit->groupspec+1); firsttoken = 0; } else if (strncasecmp(p, "EXGROUP=", 8) == 0) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->exgroupspec = strdup(val); if (*(crit->exgroupspec) == '%') crit->exgroupspecre = compileregex(crit->exgroupspec+1); firsttoken = 0; } else if ((strncasecmp(p, "COLOR=", 6) == 0) || (strncasecmp(p, "COLORS=", 7) == 0)) { criteria_t *crit; char *c1, *c2; int cval, reverse = 0; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } crit = setup_criteria(&currule, &currcp); /* Put a value in crit->colors so we know there is an explicit color setting */ crit->colors = (1 << 30); c1 = strchr(p, '=')+1; /* * If the first colorspec is "!color", then apply the default colors and * subtract colors from that. */ if (*c1 == '!') crit->colors |= defaultcolors; do { c2 = strchr(c1, ','); if (c2) *c2 = '\0'; if (*c1 == '!') { reverse=1; c1++; } cval = (1 << parse_color(c1)); if (reverse) crit->colors &= (~cval); else crit->colors |= cval; if (c2) c1 = (c2+1); else c1 = NULL; } while (c1); firsttoken = 0; } else if ((strncasecmp(p, "TIME=", 5) == 0) || (strncasecmp(p, "TIMES=", 6) == 0)) { char *val; criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } val = strchr(p, '=')+1; crit = setup_criteria(&currule, &currcp); crit->timespec = strdup(val); firsttoken = 0; } else if (strncasecmp(p, "DURATION", 8) == 0) { criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } crit = setup_criteria(&currule, &currcp); if (*(p+8) == '>') crit->minduration = 60*durationvalue(p+9); else if (*(p+8) == '<') crit->maxduration = 60*durationvalue(p+9); else errprintf("Ignoring invalid DURATION at line %d: %s\n",cfid, p); firsttoken = 0; } else if (strncasecmp(p, "RECOVERED", 9) == 0) { criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } crit = setup_criteria(&currule, &currcp); crit->sendrecovered = SR_WANTED; firsttoken = 0; } else if (strncasecmp(p, "NORECOVERED", 11) == 0) { criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } crit = setup_criteria(&currule, &currcp); crit->sendrecovered = SR_NOTWANTED; firsttoken = 0; } else if (strncasecmp(p, "NOTICE", 6) == 0) { criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } crit = setup_criteria(&currule, &currcp); crit->sendnotice = SR_WANTED; firsttoken = 0; } else if (strncasecmp(p, "NONOTICE", 8) == 0) { criteria_t *crit; if (firsttoken) { flush_rule(currule); currule = NULL; currcp = NULL; pstate = P_NONE; } crit = setup_criteria(&currule, &currcp); crit->sendnotice = SR_NOTWANTED; firsttoken = 0; } else if ((pstate == P_RECIP) && (strncasecmp(p, "FORMAT=", 7) == 0)) { if (!currcp) errprintf("FORMAT used without a recipient (line %d), ignored\n", cfid); else if (strcasecmp(p+7, "TEXT") == 0) currcp->format = ALERTFORM_TEXT; else if (strcasecmp(p+7, "PLAIN") == 0) currcp->format = ALERTFORM_PLAIN; else if (strcasecmp(p+7, "SMS") == 0) currcp->format = ALERTFORM_SMS; else if (strcasecmp(p+7, "PAGER") == 0) currcp->format = ALERTFORM_PAGER; else if (strcasecmp(p+7, "SCRIPT") == 0) currcp->format = ALERTFORM_SCRIPT; else errprintf("Unknown FORMAT setting '%s' ignored\n", p); firsttoken = 0; } else if ((pstate == P_RECIP) && (strncasecmp(p, "REPEAT=", 7) == 0)) { if (!currcp) errprintf("REPEAT used without a recipient (line %d), ignored\n", cfid); else currcp->interval = 60*durationvalue(p+7); firsttoken = 0; } else if ((pstate == P_RECIP) && (strcasecmp(p, "STOP") == 0)) { if (!currcp) errprintf("STOP used without a recipient (line %d), ignored\n", cfid); else currcp->stoprule = 1; firsttoken = 0; } else if ((pstate == P_RECIP) && (strcasecmp(p, "UNMATCHED") == 0)) { if (!currcp) errprintf("UNMATCHED used without a recipient (line %d), ignored\n", cfid); else currcp->unmatchedonly = 1; firsttoken = 0; } else if ((pstate == P_RECIP) && (strncasecmp(p, "NOALERT", 7) == 0)) { if (!currcp) errprintf("NOALERT used without a recipient (line %d), ignored\n", cfid); else currcp->noalerts = 1; firsttoken = 0; } else if (currule && ((strncasecmp(p, "MAIL", 4) == 0) || mailcmdactive) ) { recip_t *newrcp; mailcmdactive = 1; newrcp = (recip_t *)calloc(1, sizeof(recip_t)); newrcp->cfid = cfid; newrcp->method = M_MAIL; newrcp->format = ALERTFORM_TEXT; if (strncasecmp(p, "MAIL=", 5) == 0) { p += 5; } else if (strcasecmp(p, "MAIL") == 0) { p = strtok(NULL, " \t"); } else { /* Second recipient on a rule - do nothing */ } if (p) { newrcp->recipient = strdup(p); newrcp->interval = defaultinterval; currcp = newrcp; if (curlinerecips == NULL) curlinerecips = newrcp; pstate = P_RECIP; if (currule->recipients == NULL) currule->recipients = rcptail = newrcp; else { rcptail->next = newrcp; rcptail = newrcp; } } else { errprintf("Ignoring MAIL with no recipient at line %d\n", cfid); xfree(newrcp); } firsttoken = 0; } else if (currule && ((strncasecmp(p, "SCRIPT", 6) == 0) || scriptcmdactive)) { recip_t *newrcp; scriptcmdactive = 1; newrcp = (recip_t *)calloc(1, sizeof(recip_t)); newrcp->cfid = cfid; newrcp->method = M_SCRIPT; newrcp->format = ALERTFORM_SCRIPT; if (strncasecmp(p, "SCRIPT=", 7) == 0) { p += 7; newrcp->scriptname = strdup(p); p = strtok(NULL, " \t"); } else if (strcasecmp(p, "SCRIPT") == 0) { p = strtok(NULL, " \t"); if (p) { newrcp->scriptname = strdup(p); p = strtok(NULL, " \t"); } else { errprintf("Invalid SCRIPT command at line %d\n", cfid); } } else { /* A second recipient for the same script as the previous one */ newrcp->scriptname = strdup(currcp->scriptname); } if (p) { newrcp->recipient = strdup(p); newrcp->interval = defaultinterval; currcp = newrcp; if (curlinerecips == NULL) curlinerecips = newrcp; pstate = P_RECIP; if (currule->recipients == NULL) currule->recipients = rcptail = newrcp; else { rcptail->next = newrcp; rcptail = newrcp; } } else { errprintf("Ignoring SCRIPT with no recipient at line %d\n", cfid); if (newrcp->scriptname) xfree(newrcp->scriptname); xfree(newrcp); } firsttoken = 0; } else if (currule && (strncasecmp(p, "IGNORE", 6) == 0)) { recip_t *newrcp; newrcp = (recip_t *)calloc(1, sizeof(recip_t)); newrcp->cfid = cfid; newrcp->method = M_IGNORE; newrcp->format = ALERTFORM_NONE; newrcp->interval = defaultinterval; newrcp->stoprule = 1; currcp = newrcp; if (curlinerecips == NULL) curlinerecips = newrcp; pstate = P_RECIP; if (currule->recipients == NULL) currule->recipients = rcptail = newrcp; else { rcptail->next = newrcp; rcptail = newrcp; } firsttoken = 0; } else { errprintf("Ignored unknown/unexpected token '%s' at line %d\n", p, cfid); } if (p) p = strtok(NULL, " \t"); } if (curlinerecips && currcp && (curlinerecips != currcp)) { /* We have multiple recipients on one line. Make sure criteria etc. get copied */ recip_t *rwalk; /* All criteria etc. have been set on the last recipient (currcp) */ for (rwalk = curlinerecips; (rwalk != currcp); rwalk = rwalk->next) { rwalk->format = currcp->format; rwalk->interval = currcp->interval; rwalk->criteria = currcp->criteria; rwalk->noalerts = currcp->noalerts; } } } flush_rule(currule); stackfclose(fd); freestrbuffer(inbuf); MEMUNDEFINE(cfline); MEMUNDEFINE(fn); return 1; } static void dump_criteria(criteria_t *crit, int isrecip) { if (crit->pagespec) printf("PAGE=%s ", crit->pagespec); if (crit->expagespec) printf("EXPAGE=%s ", crit->expagespec); if (crit->dgspec) printf("DISPLAYGROUP=%s ", crit->dgspec); if (crit->exdgspec) printf("EXDISPLAYGROUP=%s ", crit->exdgspec); if (crit->hostspec) printf("HOST=%s ", crit->hostspec); if (crit->exhostspec) printf("EXHOST=%s ", crit->exhostspec); if (crit->svcspec) printf("SERVICE=%s ", crit->svcspec); if (crit->exsvcspec) printf("EXSERVICE=%s ", crit->exsvcspec); if (crit->classspec) printf("CLASS=%s ", crit->classspec); if (crit->exclassspec) printf("EXCLASS=%s ", crit->exclassspec); if (crit->groupspec) printf("GROUP=%s ", crit->groupspec); if (crit->exgroupspec) printf("EXGROUP=%s ", crit->exgroupspec); if (crit->colors) { int i, first = 1; printf("COLOR="); for (i = 0; (i < COL_COUNT); i++) { if ((1 << i) & crit->colors) { dbgprintf("first=%d, i=%d\n", first, i); printf("%s%s", (first ? "" : ","), colorname(i)); first = 0; } } printf(" "); } if (crit->timespec) printf("TIME=%s ", crit->timespec); if (crit->minduration) printf("DURATION>%d ", (crit->minduration / 60)); if (crit->maxduration) printf("DURATION<%d ", (crit->maxduration / 60)); if (isrecip) { switch (crit->sendrecovered) { case SR_UNKNOWN: break; case SR_WANTED: printf("RECOVERED "); break; case SR_NOTWANTED: printf("NORECOVERED "); break; } switch (crit->sendnotice) { case SR_UNKNOWN: break; case SR_WANTED: printf("NOTICE "); break; case SR_NOTWANTED: printf("NONOTICE "); break; } } } void dump_alertconfig(int showlines) { rule_t *rulewalk; recip_t *recipwalk; for (rulewalk = rulehead; (rulewalk); rulewalk = rulewalk->next) { if (showlines) printf("%5d\t", rulewalk->cfid); dump_criteria(rulewalk->criteria, 0); printf("\n"); for (recipwalk = rulewalk->recipients; (recipwalk); recipwalk = recipwalk->next) { printf("\t"); switch (recipwalk->method) { case M_MAIL : printf("MAIL %s ", recipwalk->recipient); break; case M_SCRIPT : printf("SCRIPT %s %s ", recipwalk->scriptname, recipwalk->recipient); break; case M_IGNORE : printf("IGNORE "); break; } switch (recipwalk->format) { case ALERTFORM_TEXT : printf("FORMAT=TEXT "); break; case ALERTFORM_PLAIN : printf("FORMAT=PLAIN "); break; case ALERTFORM_SMS : printf("FORMAT=SMS "); break; case ALERTFORM_PAGER : printf("FORMAT=PAGER "); break; case ALERTFORM_SCRIPT: printf("FORMAT=SCRIPT "); break; case ALERTFORM_NONE : break; } printf("REPEAT=%d ", (int)(recipwalk->interval / 60)); if (recipwalk->criteria) dump_criteria(recipwalk->criteria, 1); if (recipwalk->unmatchedonly) printf("UNMATCHED "); if (recipwalk->stoprule) printf("STOP "); if (recipwalk->noalerts) printf("NOALERT "); printf("\n"); } printf("\n"); } } int stoprulefound = 0; static int criteriamatch(activealerts_t *alert, criteria_t *crit, criteria_t *rulecrit, int *anymatch, time_t *nexttime) { /* * See if the "crit" matches the "alert". * Match on pagespec, dgspec, hostspec, svcspec, classspec, groupspec, colors, timespec, minduration, maxduration, sendrecovered */ static char *pgnames = NULL; int pgmatchres, pgexclres; time_t duration = (getcurrenttime(NULL) - alert->eventstart); int result, cfid = 0; char *pgtok, *cfline = NULL; void *hinfo = hostinfo(alert->hostname); /* The top-level page needs a name - cannot match against an empty string */ if (pgnames) xfree(pgnames); pgnames = strdup((*alert->location == '\0') ? "/" : alert->location); if (crit) { cfid = crit->cfid; cfline = crit->cfline; } if (!cfid && rulecrit) cfid = rulecrit->cfid; if (!cfline && rulecrit) cfline = rulecrit->cfline; if (!cfline) cfline = ""; traceprintf("Matching host:service:dgroup:page '%s:%s:%s:%s' against rule line %d\n", alert->hostname, alert->testname, xmh_item(hinfo, XMH_DGNAME), alert->location, cfid); if (alert->state == A_PAGING) { /* Check max-duration now - it's fast and easy. */ if (crit && crit->maxduration && (duration > crit->maxduration)) { traceprintf("Failed '%s' (max. duration %d>%d)\n", cfline, duration, crit->maxduration); if (!printmode) return 0; } } if (crit && crit->classspec && !namematch(alert->classname, crit->classspec, crit->classspecre)) { traceprintf("Failed '%s' (class not in include list)\n", cfline); return 0; } if (crit && crit->exclassspec && namematch(alert->classname, crit->exclassspec, crit->exclassspecre)) { traceprintf("Failed '%s' (class excluded)\n", cfline); return 0; } /* alert->groups is a comma-separated list of groups, so it needs some special handling */ /* * NB: Dont check groups when RECOVERED - the group list for recovery messages is always empty. * It doesn't matter if we match a recipient who was not in the group that originally * got the alert - we will later check who has received the alert, and only those that * have will get the recovery message. */ if (crit && (crit->groupspec || crit->exgroupspec) && (alert->state != A_RECOVERED)) { char *grouplist; char *tokptr; grouplist = (alert->groups && (*(alert->groups))) ? strdup(alert->groups) : NULL; if (crit->groupspec) { char *onegroup; int iswanted = 0; if (grouplist) { /* There is a group label on the alert, so it must match */ onegroup = strtok_r(grouplist, ",", &tokptr); while (onegroup && !iswanted) { iswanted = (namematch(onegroup, crit->groupspec, crit->groupspecre)); onegroup = strtok_r(NULL, ",", &tokptr); } } if (!iswanted) { /* * Either the alert had a group list that didn't match, or * there was no group list and the rule listed one. * In both cases, it's a failed match. */ traceprintf("Failed '%s' (group not in include list)\n", cfline); if (grouplist) xfree(grouplist); return 0; } } if (crit->exgroupspec && grouplist) { char *onegroup; /* Excluded groups are only handled when the alert does have a group list */ strcpy(grouplist, alert->groups); /* Might have been used in the include list */ onegroup = strtok_r(grouplist, ",", &tokptr); while (onegroup) { if (namematch(onegroup, crit->exgroupspec, crit->exgroupspecre)) { traceprintf("Failed '%s' (group excluded)\n", cfline); xfree(grouplist); return 0; } onegroup = strtok_r(NULL, ",", &tokptr); } } if (grouplist) xfree(grouplist); } pgmatchres = pgexclres = -1; pgtok = strtok(pgnames, ","); while (pgtok) { if (crit && crit->pagespec && (pgmatchres != 1)) pgmatchres = (namematch(pgtok, crit->pagespec, crit->pagespecre) ? 1 : 0); if (crit && crit->expagespec && (pgexclres != 1)) pgexclres = (namematch(pgtok, crit->expagespec, crit->expagespecre) ? 1 : 0); pgtok = strtok(NULL, ","); } if (pgexclres == 1) { traceprintf("Failed '%s' (pagename excluded)\n", cfline); return 0; } if (pgmatchres == 0) { traceprintf("Failed '%s' (pagename not in include list)\n", cfline); return 0; } if (crit && crit->dgspec && !namematch(xmh_item(hinfo, XMH_DGNAME), crit->dgspec, crit->dgspecre)) { traceprintf("Failed '%s' (displaygroup not in include list)\n", cfline); return 0; } if (crit && crit->exdgspec && namematch(xmh_item(hinfo, XMH_DGNAME), crit->exdgspec, crit->exdgspecre)) { traceprintf("Failed '%s' (displaygroup excluded)\n", cfline); return 0; } if (crit && crit->hostspec && !namematch(alert->hostname, crit->hostspec, crit->hostspecre)) { traceprintf("Failed '%s' (hostname not in include list)\n", cfline); return 0; } if (crit && crit->exhostspec && namematch(alert->hostname, crit->exhostspec, crit->exhostspecre)) { traceprintf("Failed '%s' (hostname excluded)\n", cfline); return 0; } if (crit && crit->svcspec && !namematch(alert->testname, crit->svcspec, crit->svcspecre)) { traceprintf("Failed '%s' (service not in include list)\n", cfline); return 0; } if (crit && crit->exsvcspec && namematch(alert->testname, crit->exsvcspec, crit->exsvcspecre)) { traceprintf("Failed '%s' (service excluded)\n", cfline); return 0; } if (alert->state == A_NOTIFY) { /* * Dont do the check until we are checking individual recipients (rulecrit is set). * You dont need to have NOTICE on the top-level rule, it's enough if a recipient * has it set. However, we do want to allow there to be a default defined in the * rule; but it doesn't take effect until we start checking the recipients. */ if (rulecrit) { int n = (crit ? crit->sendnotice : -1); traceprintf("Checking NOTICE setting %d (rule:%d)\n", n, rulecrit->sendnotice); if (crit && (crit->sendnotice == SR_NOTWANTED)) result = 0; /* Explicit NONOTICE */ else if (crit && (crit->sendnotice == SR_WANTED)) result = 1; /* Explicit NOTICE */ else result = (rulecrit->sendnotice == SR_WANTED); /* Not set, but rule has NOTICE */ } else { result = 1; } if (!result) traceprintf("Failed '%s' (notice not wanted)\n", cfline); return result; } /* At this point, we know the configuration may result in an alert. */ if (anymatch) (*anymatch)++; /* * Duration checks should be done on real paging messages only. * Not on recovery- or notify-messages. */ if (alert->state == A_PAGING) { if (crit && crit->minduration && (duration < crit->minduration)) { if (nexttime) { time_t mynext = alert->eventstart + crit->minduration; if ((*nexttime == -1) || (*nexttime > mynext)) *nexttime = mynext; } traceprintf("Failed '%s' (min. duration %d<%d)\n", cfline, duration, crit->minduration); if (!printmode) return 0; } } /* * Time restrictions apply to ALL messages. * Before 4.2, these were only applied to ALERT messages, * not RECOVERED and NOTIFY messages. This caused some * unfortunate alerts in the middle of the night because * some random system recovered ... not good. So apply * this check to all messages. */ if (crit && crit->timespec && !timematch(xmh_item(hinfo, XMH_HOLIDAYS), crit->timespec)) { traceprintf("Failed '%s' (time criteria)\n", cfline); if (!printmode) return 0; } /* Check color. For RECOVERED messages, this holds the color of the alert, not the recovery state */ if (crit && crit->colors) { result = (((1 << alert->color) & crit->colors) != 0); if (printmode) return 1; } else { result = (((1 << alert->color) & defaultcolors) != 0); if (printmode) return 1; } if (!result) { traceprintf("Failed '%s' (color)\n", cfline); return result; } if ((alert->state == A_RECOVERED) || (alert->state == A_DISABLED)) { /* * Dont do the check until we are checking individual recipients (rulecrit is set). * You dont need to have RECOVERED on the top-level rule, it's enough if a recipient * has it set. However, we do want to allow there to be a default defined in the * rule; but it doesn't take effect until we start checking the recipients. */ if (rulecrit) { int n = (crit ? crit->sendrecovered : -1); traceprintf("Checking recovered setting %d (rule:%d)\n", n, rulecrit->sendrecovered); if (crit && (crit->sendrecovered == SR_NOTWANTED)) result = 0; /* Explicit NORECOVERED */ else if (crit && (crit->sendrecovered == SR_WANTED)) result = 1; /* Explicit RECOVERED */ else result = (rulecrit->sendrecovered == SR_WANTED); /* Not set, but rule has RECOVERED */ } else { result = 1; } if (printmode) return result; } if (result) { traceprintf("*** Match with '%s' ***\n", cfline); } return result; } recip_t *next_recipient(activealerts_t *alert, int *first, int *anymatch, time_t *nexttime) { static rule_t *rulewalk = NULL; static recip_t *recipwalk = NULL; if (anymatch) *anymatch = 0; do { if (*first) { /* Start at beginning of rules-list and find the first matching rule. */ *first = 0; rulewalk = rulehead; while (rulewalk && !criteriamatch(alert, rulewalk->criteria, NULL, NULL, NULL)) rulewalk = rulewalk->next; if (rulewalk) { /* Point recipwalk at the list of possible candidates */ dbgprintf("Found a first matching rule\n"); recipwalk = rulewalk->recipients; } else { /* No matching rules */ dbgprintf("Found no first matching rule\n"); recipwalk = NULL; } } else { if (recipwalk->next) { /* Check the next recipient in the current rule */ recipwalk = recipwalk->next; } else { /* End of recipients in current rule. Go to the next matching rule */ do { rulewalk = rulewalk->next; } while (rulewalk && !criteriamatch(alert, rulewalk->criteria, NULL, NULL, NULL)); if (rulewalk) { /* Point recipwalk at the list of possible candidates */ dbgprintf("Found a secondary matching rule\n"); recipwalk = rulewalk->recipients; } else { /* No matching rules */ dbgprintf("No more secondary matching rule\n"); recipwalk = NULL; } } } } while (rulewalk && recipwalk && !criteriamatch(alert, recipwalk->criteria, rulewalk->criteria, anymatch, nexttime)); stoprulefound = (recipwalk && recipwalk->stoprule); printrule = rulewalk; return recipwalk; } int have_recipient(activealerts_t *alert, int *anymatch) { int first = 1; return (next_recipient(alert, &first, anymatch, NULL) != NULL); } void alert_printmode(int on) { printmode = on; } void print_alert_recipients(activealerts_t *alert, strbuffer_t *buf) { char *normalfont = "COLOR=\"#FFFFCC\" FACE=\"Tahoma, Arial, Helvetica\""; char *stopfont = "COLOR=\"#33ebf4\" FACE=\"Tahoma, Arial, Helvetica\""; int first = 1; recip_t *recip; char l[4096]; int count = 0; char *p, *fontspec; char codes[20]; MEMDEFINE(l); MEMDEFINE(codes); if (printmode == 2) { /* For print-out usage - e.g. confreport.cgi */ normalfont = "COLOR=\"#000000\" FACE=\"Tahoma, Arial, Helvetica\""; stopfont = "COLOR=\"#FF0000\" FACE=\"Tahoma, Arial, Helvetica\""; } fontspec = normalfont; stoprulefound = 0; while ((recip = next_recipient(alert, &first, NULL, NULL)) != NULL) { int mindur = 0, maxdur = INT_MAX; char *timespec = NULL; int colors = defaultcolors; int i, firstcolor = 1; int recovered = 0, notice = 0; count++; addtobuffer(buf, ""); if (count == 1) { sprintf(l, "%s", alert->testname); addtobuffer(buf, l); } /* * The min/max duration of an alert can be controlled by both the actual rule, * and by the recipient specification. * The rule must be fulfilled before the recipient even gets into play, so * if there is a min/max duration on the rule then this becomes the default * and recipient-specific settings can only increase the minduration/decrease * the maxduration. * On the other hand, if there is no rule-setting then the recipient-specific * settings determine everything. */ if (printrule->criteria && printrule->criteria->minduration) mindur = printrule->criteria->minduration; if (recip->criteria && recip->criteria->minduration && (recip->criteria->minduration > mindur)) mindur = recip->criteria->minduration; if (printrule->criteria && printrule->criteria->maxduration) maxdur = printrule->criteria->maxduration; if (recip->criteria && recip->criteria->maxduration && (recip->criteria->maxduration < maxdur)) maxdur = recip->criteria->maxduration; if (printrule->criteria && printrule->criteria->timespec) timespec = printrule->criteria->timespec; if (recip->criteria && recip->criteria->timespec) { if (timespec == NULL) timespec = recip->criteria->timespec; else errprintf("Cannot handle nested timespecs yet\n"); } if (printrule->criteria && printrule->criteria->colors) colors = (colors & printrule->criteria->colors); if (recip->criteria && recip->criteria->colors) colors = (colors & recip->criteria->colors); /* * Recoveries are sent if * - there are no recipient criteria, and the rule says yes; * - the recipient criteria does not have a RECOVERED setting, and the rule says yes; * - the recipient criteria says yes. */ if ( (!recip->criteria && printrule->criteria && (printrule->criteria->sendrecovered == SR_WANTED)) || (recip->criteria && (printrule->criteria->sendrecovered == SR_WANTED) && (recip->criteria->sendrecovered == SR_UNKNOWN)) || (recip->criteria && (recip->criteria->sendrecovered == SR_WANTED)) ) recovered = 1; if ( (!recip->criteria && printrule->criteria && (printrule->criteria->sendnotice == SR_WANTED)) || (recip->criteria && (printrule->criteria->sendnotice == SR_WANTED) && (recip->criteria->sendnotice == SR_UNKNOWN)) || (recip->criteria && (recip->criteria->sendnotice == SR_WANTED)) ) notice = 1; *codes = '\0'; if (recip->method == M_IGNORE) { recip->recipient = "-- ignored --"; } if (recip->noalerts) { if (strlen(codes)) strcat(codes, ",A"); else strcat(codes, "-A"); } if (recovered && !recip->noalerts) { if (strlen(codes)) strcat(codes, ",R"); else strcat(codes, "R"); } if (notice) { if (strlen(codes)) strcat(codes, ",N"); else strcat(codes, "N"); } if (recip->stoprule) { if (strlen(codes)) strcat(codes, ",S"); else strcat(codes, "S"); } if (recip->unmatchedonly) { if (strlen(codes)) strcat(codes, ",U"); else strcat(codes, "U"); } if (strlen(codes) == 0) sprintf(l, "%s", fontspec, recip->recipient); else sprintf(l, "%s (%s)", fontspec, recip->recipient, codes); addtobuffer(buf, l); sprintf(l, "%s", durationstring(mindur)); addtobuffer(buf, l); /* maxdur=INT_MAX means "no max duration". So set it to 0 for durationstring() to do the right thing */ if (maxdur == INT_MAX) maxdur = 0; sprintf(l, "%s", durationstring(maxdur)); addtobuffer(buf, l); sprintf(l, "%s", durationstring(recip->interval)); addtobuffer(buf, l); if (timespec) sprintf(l, "%s", timespec); else strcpy(l, "-"); addtobuffer(buf, l); addtobuffer(buf, ""); for (i = 0; (i < COL_COUNT); i++) { if ((1 << i) & colors) { sprintf(l, "%s%s", (firstcolor ? "" : ","), colorname(i)); addtobuffer(buf, l); firstcolor = 0; } } addtobuffer(buf, ""); if (stoprulefound) fontspec = stopfont; addtobuffer(buf, "\n"); } /* This is hackish - patch up the "rowspan" value, so it matches the number of recipient lines */ sprintf(l, "%d ", count); p = strstr(STRBUF(buf), "rowspan=###"); if (p) { p += strlen("rowspan="); memcpy(p, l, 3); } MEMUNDEFINE(l); MEMUNDEFINE(codes); } xymon-4.3.7/lib/cgi.h0000664000175000017500000000207611615341300013703 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __CGI_H__ #define __CGI_H__ typedef struct cgidata_t { char *name; char *value; char *filename; struct cgidata_t *next; } cgidata_t; enum cgi_method_t { CGI_OTHER, CGI_GET, CGI_POST }; extern enum cgi_method_t cgi_method; extern char *cgi_error(void); extern cgidata_t *cgi_request(void); extern char *get_cookie(char *cookiename); #endif xymon-4.3.7/lib/loadcriticalconf.h0000664000175000017500000000343711630612042016444 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module for Xymon, responsible for loading the */ /* critical.cfg file. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __LOADCRITICALCONF_H__ #define __LOADCRITICALCONF_H__ #include typedef struct critconf_t { char *key; int priority; time_t starttime, endtime; char *crittime; char *ttgroup; char *ttextra; char *updinfo; } critconf_t; #define CRITCONF_TIMEFILTER 1 #define CRITCONF_FIRSTMATCH 2 #define CRITCONF_FIRST 3 #define CRITCONF_NEXT 4 #define CRITCONF_RAW_FIRST 5 #define CRITCONF_RAW_NEXT 6 #define CRITCONF_FIRSTHOSTMATCH 7 #define DEFAULT_CRITCONFIGFN "etc/critical.cfg" extern int load_critconfig(char *fn); extern critconf_t *get_critconfig(char *key, int flags, char **resultkey); extern int update_critconfig(critconf_t *rec); extern void addclone_critconfig(char *origin, char *newclone); extern void dropclone_critconfig(char *drop); extern int delete_critconfig(char *dropkey, int evenifcloned); #endif xymon-4.3.7/lib/digest.c0000664000175000017500000001515511615341300014415 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is used to implement message digest functions (MD5, SHA1 etc.) */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: digest.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include "libxymon.h" char *md5hash(char *input) { /* We have a fast MD5 hash function, since that may be used a lot */ static struct digestctx_t *ctx = NULL; unsigned char md_value[16]; static char md_string[2*16+1]; int i; char *p; if (!ctx) { ctx = (digestctx_t *) malloc(sizeof(digestctx_t)); ctx->digestname = strdup("md5"); ctx->digesttype = D_MD5; ctx->mdctx = (void *)malloc(myMD5_Size()); } myMD5_Init(ctx->mdctx); myMD5_Update(ctx->mdctx, input, strlen(input)); myMD5_Final(md_value, ctx->mdctx); for(i = 0, p = md_string; (i < sizeof(md_value)); i++) p += sprintf(p, "%02x", md_value[i]); *p = '\0'; return md_string; } digestctx_t *digest_init(char *digest) { struct digestctx_t *ctx = NULL; if (strcmp(digest, "md5") == 0) { /* Use the built in MD5 routines */ ctx = (digestctx_t *) malloc(sizeof(digestctx_t)); ctx->digestname = strdup(digest); ctx->digesttype = D_MD5; ctx->mdctx = (void *)malloc(myMD5_Size()); myMD5_Init(ctx->mdctx); } else if (strcmp(digest, "sha1") == 0) { /* Use the built in SHA1 routines */ ctx = (digestctx_t *) malloc(sizeof(digestctx_t)); ctx->digestname = strdup(digest); ctx->digesttype = D_SHA1; ctx->mdctx = (void *)malloc(mySHA1_Size()); mySHA1_Init(ctx->mdctx); } else if (strcmp(digest, "rmd160") == 0) { /* Use the built in RMD160 routines */ ctx = (digestctx_t *) malloc(sizeof(digestctx_t)); ctx->digestname = strdup(digest); ctx->digesttype = D_RMD160; ctx->mdctx = (void *)malloc(myRIPEMD160_Size()); myRIPEMD160_Init(ctx->mdctx); } else if (strcmp(digest, "sha512") == 0) { /* Use the built in SHA-512 routines */ ctx = (digestctx_t *) malloc(sizeof(digestctx_t)); ctx->digestname = strdup(digest); ctx->digesttype = D_SHA512; ctx->mdctx = (void *)malloc(mySHA512_Size()); mySHA512_Init(ctx->mdctx); } else if (strcmp(digest, "sha256") == 0) { /* Use the built in SHA-256 routines */ ctx = (digestctx_t *) malloc(sizeof(digestctx_t)); ctx->digestname = strdup(digest); ctx->digesttype = D_SHA256; ctx->mdctx = (void *)malloc(mySHA256_Size()); mySHA256_Init(ctx->mdctx); } else if (strcmp(digest, "sha224") == 0) { /* Use the built in SHA-224 routines */ ctx = (digestctx_t *) malloc(sizeof(digestctx_t)); ctx->digestname = strdup(digest); ctx->digesttype = D_SHA224; ctx->mdctx = (void *)malloc(mySHA224_Size()); mySHA224_Init(ctx->mdctx); } else if (strcmp(digest, "sha384") == 0) { /* Use the built in SHA-384 routines */ ctx = (digestctx_t *) malloc(sizeof(digestctx_t)); ctx->digestname = strdup(digest); ctx->digesttype = D_SHA384; ctx->mdctx = (void *)malloc(mySHA384_Size()); mySHA384_Init(ctx->mdctx); } else { errprintf("digest_init failure: Cannot handle digest %s\n", digest); return NULL; } return ctx; } int digest_data(digestctx_t *ctx, unsigned char *buf, int buflen) { switch (ctx->digesttype) { case D_MD5: myMD5_Update(ctx->mdctx, buf, buflen); break; case D_SHA1: mySHA1_Update(ctx->mdctx, buf, buflen); break; case D_RMD160: myRIPEMD160_Update(ctx->mdctx, buf, buflen); break; case D_SHA512: mySHA512_Update(ctx->mdctx, buf, buflen); break; case D_SHA256: mySHA256_Update(ctx->mdctx, buf, buflen); break; case D_SHA384: mySHA384_Update(ctx->mdctx, buf, buflen); break; case D_SHA224: mySHA224_Update(ctx->mdctx, buf, buflen); break; } return 0; } char *digest_done(digestctx_t *ctx) { unsigned int md_len = 0; unsigned char *md_value = NULL; char *md_string = NULL; int i; char *p; switch (ctx->digesttype) { case D_MD5: /* Built in MD5 hash */ md_len = 16; md_value = (unsigned char *)malloc(md_len*sizeof(unsigned char)); md_string = (char *)malloc((2*md_len + strlen(ctx->digestname) + 2)*sizeof(char)); myMD5_Final(md_value, ctx->mdctx); break; case D_SHA1: /* Built in SHA1 hash */ md_len = 20; md_value = (unsigned char *)malloc(md_len*sizeof(unsigned char)); md_string = (char *)malloc((2*md_len + strlen(ctx->digestname) + 2)*sizeof(char)); mySHA1_Final(md_value, ctx->mdctx); break; case D_RMD160: /* Built in RMD160 hash */ md_len = 20; md_value = (unsigned char *)malloc(md_len*sizeof(unsigned char)); md_string = (char *)malloc((2*md_len + strlen(ctx->digestname) + 2)*sizeof(char)); myRIPEMD160_Final(md_value, ctx->mdctx); break; case D_SHA512: /* Built in SHA-512 hash */ md_len = (512/8); md_value = (unsigned char *)malloc(md_len*sizeof(unsigned char)); md_string = (char *)malloc((2*md_len + strlen(ctx->digestname) + 2)*sizeof(char)); mySHA512_Final(md_value, ctx->mdctx); break; case D_SHA256: /* Built in SHA-256 hash */ md_len = (256/8); md_value = (unsigned char *)malloc(md_len*sizeof(unsigned char)); md_string = (char *)malloc((2*md_len + strlen(ctx->digestname) + 2)*sizeof(char)); mySHA256_Final(md_value, ctx->mdctx); break; case D_SHA384: /* Built in SHA-384 hash */ md_len = (384/8); md_value = (unsigned char *)malloc(md_len*sizeof(unsigned char)); md_string = (char *)malloc((2*md_len + strlen(ctx->digestname) + 2)*sizeof(char)); mySHA384_Final(md_value, ctx->mdctx); break; case D_SHA224: /* Built in SHA-224 hash */ md_len = (224/8); md_value = (unsigned char *)malloc(md_len*sizeof(unsigned char)); md_string = (char *)malloc((2*md_len + strlen(ctx->digestname) + 2)*sizeof(char)); mySHA224_Final(md_value, ctx->mdctx); break; } sprintf(md_string, "%s:", ctx->digestname); for(i = 0, p = md_string + strlen(md_string); (i < md_len); i++) p += sprintf(p, "%02x", md_value[i]); *p = '\0'; xfree(md_value); xfree(ctx->digestname); xfree(ctx->mdctx); xfree(ctx); return md_string; } xymon-4.3.7/lib/sig.c0000664000175000017500000000740311615341300013715 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for handling of signals and crashes. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: sig.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include "libxymon.h" /* Data used while crashing - cannot depend on the stack being usable */ static char signal_xymoncmd[PATH_MAX]; static char signal_xymondserver[1024]; static char signal_msg[1024]; static char signal_tmpdir[PATH_MAX]; static void sigsegv_handler(int signum) { /* * This is a signal handler. Only a very limited number of * library routines can be safely used here, according to * Posix: http://www.opengroup.org/onlinepubs/007904975/functions/xsh_chap02_04.html#tag_02_04_03 * Do not use string, stdio etc. - just basic system calls. * That is why we need to setup all of the strings in advance. */ signal(signum, SIG_DFL); /* * Try to fork a child to send in an alarm message. * If the fork fails, then just attempt to exec() the XYMON command */ if (fork() <= 0) { execl(signal_xymoncmd, "xymon-signal", signal_xymondserver, signal_msg, NULL); } /* Dump core and abort */ chdir(signal_tmpdir); abort(); } static void sigusr2_handler(int signum) { /* SIGUSR2 toggles debugging */ if (debug) { dbgprintf("Debug OFF\n"); debug = 0; } else { debug = 1; dbgprintf("Debug ON\n"); } } void setup_signalhandler(char *programname) { struct rlimit lim; struct sigaction sa; MEMDEFINE(signal_xymoncmd); MEMDEFINE(signal_xymondserver); MEMDEFINE(signal_tmpdir); MEMDEFINE(signal_msg); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigsegv_handler; /* * Try to allow ourselves to generate core files */ getrlimit(RLIMIT_CORE, &lim); lim.rlim_cur = RLIM_INFINITY; setrlimit(RLIMIT_CORE, &lim); if (xgetenv("XYMON") == NULL) return; if (xgetenv("XYMSRV") == NULL) return; /* * Used inside signal-handler. Must be setup in * advance. */ strcpy(signal_xymoncmd, xgetenv("XYMON")); strcpy(signal_xymondserver, xgetenv("XYMSRV")); strcpy(signal_tmpdir, xgetenv("XYMONTMP")); sprintf(signal_msg, "status %s.%s red - Program crashed\n\nFatal signal caught!\n", (xgetenv("MACHINE") ? xgetenv("MACHINE") : "XYMSERVERS"), programname); sigaction(SIGSEGV, &sa, NULL); sigaction(SIGILL, &sa, NULL); #ifdef SIGBUS sigaction(SIGBUS, &sa, NULL); #endif /* * After lengthy debugging and perusing of mail archives: * Need to ignore SIGPIPE since FreeBSD (and others?) can throw this * on a write() instead of simply returning -EPIPE like any sane * OS would. */ signal(SIGPIPE, SIG_IGN); /* Ignore SIGUSR1 unless explicitly set by main program */ signal (SIGUSR1, SIG_IGN); /* SIGUSR2 toggles debugging */ memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigusr2_handler; sigaction(SIGUSR2, &sa, NULL); } xymon-4.3.7/lib/matching.c0000664000175000017500000001124111615341300014720 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module, part of libxymon. */ /* It contains routines for matching names and expressions */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: matching.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include "libxymon.h" static pcre *compileregex_opts(const char *pattern, int flags) { pcre *result; const char *errmsg; int errofs; dbgprintf("Compiling regex %s\n", pattern); result = pcre_compile(pattern, flags, &errmsg, &errofs, NULL); if (result == NULL) { errprintf("pcre compile '%s' failed (offset %d): %s\n", pattern, errofs, errmsg); return NULL; } return result; } pcre *compileregex(const char *pattern) { return compileregex_opts(pattern, PCRE_CASELESS); } pcre *multilineregex(const char *pattern) { return compileregex_opts(pattern, PCRE_CASELESS|PCRE_MULTILINE); } int matchregex(char *needle, pcre *pcrecode) { int ovector[30]; int result; if (!needle || !pcrecode) return 0; result = pcre_exec(pcrecode, NULL, needle, strlen(needle), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); return (result >= 0); } void freeregex(pcre *pcrecode) { if (!pcrecode) return; pcre_free(pcrecode); } int namematch(char *needle, char *haystack, pcre *pcrecode) { char *xhay; char *tokbuf, *tok; char *match; int found = 0; int result = 0; int allneg = 1; if ((needle == NULL) || (*needle == '\0')) return 0; if (pcrecode) { /* Do regex matching. The regex has already been compiled for us. */ return matchregex(needle, pcrecode); } if (strcmp(haystack, "*") == 0) { /* Match anything */ return 1; } /* Implement a simple, no-wildcard match */ xhay = strdup(haystack); tok = strtok_r(xhay, ",", &tokbuf); while (tok) { allneg = (allneg && (*tok == '!')); if (!found) { if (*tok == '!') { found = (strcmp(tok+1, needle) == 0); if (found) result = 0; } else { found = (strcmp(tok, needle) == 0); if (found) result = 1; } } /* We must check all of the items in the haystack to see if they are all negative matches */ tok = strtok_r(NULL, ",", &tokbuf); } xfree(xhay); /* * If we didn't find it, and the list is exclusively negative matches, * we must return a positive result for "no match". */ if (!found && allneg) result = 1; return result; } int patternmatch(char *datatosearch, char *pattern, pcre *pcrecode) { if (pcrecode) { /* Do regex matching. The regex has already been compiled for us. */ return matchregex(datatosearch, pcrecode); } if (strcmp(pattern, "*") == 0) { /* Match anything */ return 1; } return (strstr(datatosearch, pattern) != NULL); } pcre **compile_exprs(char *id, const char **patterns, int count) { pcre **result = NULL; int i; result = (pcre **)calloc(count, sizeof(pcre *)); for (i=0; (i < count); i++) { result[i] = compileregex(patterns[i]); if (!result[i]) { errprintf("Internal error: %s pickdata PCRE-compile failed\n", id); for (i=0; (i < count); i++) if (result[i]) pcre_free(result[i]); xfree(result); return NULL; } } return result; } int pickdata(char *buf, pcre *expr, int dupok, ...) { int res, i; int ovector[30]; va_list ap; char **ptr; char w[100]; if (!expr) return 0; res = pcre_exec(expr, NULL, buf, strlen(buf), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (res < 0) return 0; va_start(ap, dupok); for (i=1; (i < res); i++) { *w = '\0'; pcre_copy_substring(buf, ovector, res, i, w, sizeof(w)); ptr = va_arg(ap, char **); if (dupok) { if (*ptr) xfree(*ptr); *ptr = strdup(w); } else { if (*ptr == NULL) { *ptr = strdup(w); } else { dbgprintf("Internal error: Duplicate match ignored\n"); } } } va_end(ap); return 1; } int timematch(char *holidaykey, char *tspec) { int result; result = within_sla(holidaykey, tspec, 0); return result; } xymon-4.3.7/lib/rmd_locl.h0000664000175000017500000001705111535462534014751 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This file is part of the Xymon monitor library, but was taken from the */ /* FreeBSD sources. It was originally written by Eric Young, and is NOT */ /* licensed under the GPL. Please adhere the original copyright notice below. */ /*----------------------------------------------------------------------------*/ /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * * This package is an SSL implementation written * by Eric Young (eay@cryptsoft.com). * The implementation was written so as to conform with Netscapes SSL. * * This library is free for commercial and non-commercial use as long as * the following conditions are aheared to. The following conditions * apply to all code found in this distribution, be it the RC4, RSA, * lhash, DES, etc., code; not just the SSL code. The SSL documentation * included with this distribution is covered by the same copyright terms * except that the holder is Tim Hudson (tjh@cryptsoft.com). * * Copyright remains Eric Young's, and as such any Copyright notices in * the code are not to be removed. * If this package is used in a product, Eric Young should be given attribution * as the author of the parts of the library used. * This can be in the form of a textual message at program startup or * in documentation (online or textual) provided with the package. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * "This product includes cryptographic software written by * Eric Young (eay@cryptsoft.com)" * The word 'cryptographic' can be left out if the rouines from the library * being used are not cryptographic related :-). * 4. If you include any Windows specific code (or a derivative thereof) from * the apps directory (application code) you must include an acknowledgement: * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" * * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * The licence and distribution terms for any publically available version or * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] */ #include "ripemd.h" #undef c2nl #define c2nl(c,l) (l =(((u_int32_t)(*((c)++)))<<24), \ l|=(((u_int32_t)(*((c)++)))<<16), \ l|=(((u_int32_t)(*((c)++)))<< 8), \ l|=(((u_int32_t)(*((c)++))) )) #undef p_c2nl #define p_c2nl(c,l,n) { \ switch (n) { \ case 0: l =((u_int32_t)(*((c)++)))<<24; \ case 1: l|=((u_int32_t)(*((c)++)))<<16; \ case 2: l|=((u_int32_t)(*((c)++)))<< 8; \ case 3: l|=((u_int32_t)(*((c)++))); \ } \ } #undef c2nl_p /* NOTE the pointer is not incremented at the end of this */ #define c2nl_p(c,l,n) { \ l=0; \ (c)+=n; \ switch (n) { \ case 3: l =((u_int32_t)(*(--(c))))<< 8; \ case 2: l|=((u_int32_t)(*(--(c))))<<16; \ case 1: l|=((u_int32_t)(*(--(c))))<<24; \ } \ } #undef p_c2nl_p #define p_c2nl_p(c,l,sc,len) { \ switch (sc) \ { \ case 0: l =((u_int32_t)(*((c)++)))<<24; \ if (--len == 0) break; \ case 1: l|=((u_int32_t)(*((c)++)))<<16; \ if (--len == 0) break; \ case 2: l|=((u_int32_t)(*((c)++)))<< 8; \ } \ } #undef nl2c #define nl2c(l,c) (*((c)++)=(unsigned char)(((l)>>24)&0xff), \ *((c)++)=(unsigned char)(((l)>>16)&0xff), \ *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ *((c)++)=(unsigned char)(((l) )&0xff)) #undef c2l #define c2l(c,l) (l =(((u_int32_t)(*((c)++))) ), \ l|=(((u_int32_t)(*((c)++)))<< 8), \ l|=(((u_int32_t)(*((c)++)))<<16), \ l|=(((u_int32_t)(*((c)++)))<<24)) #undef p_c2l #define p_c2l(c,l,n) { \ switch (n) { \ case 0: l =((u_int32_t)(*((c)++))); \ case 1: l|=((u_int32_t)(*((c)++)))<< 8; \ case 2: l|=((u_int32_t)(*((c)++)))<<16; \ case 3: l|=((u_int32_t)(*((c)++)))<<24; \ } \ } #undef c2l_p /* NOTE the pointer is not incremented at the end of this */ #define c2l_p(c,l,n) { \ l=0; \ (c)+=n; \ switch (n) { \ case 3: l =((u_int32_t)(*(--(c))))<<16; \ case 2: l|=((u_int32_t)(*(--(c))))<< 8; \ case 1: l|=((u_int32_t)(*(--(c)))); \ } \ } #undef p_c2l_p #define p_c2l_p(c,l,sc,len) { \ switch (sc) \ { \ case 0: l =((u_int32_t)(*((c)++))); \ if (--len == 0) break; \ case 1: l|=((u_int32_t)(*((c)++)))<< 8; \ if (--len == 0) break; \ case 2: l|=((u_int32_t)(*((c)++)))<<16; \ } \ } #undef l2c #define l2c(l,c) (*((c)++)=(unsigned char)(((l) )&0xff), \ *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ *((c)++)=(unsigned char)(((l)>>16)&0xff), \ *((c)++)=(unsigned char)(((l)>>24)&0xff)) #undef ROTATE #if defined(WIN32) #define ROTATE(a,n) _lrotl(a,n) #else #define ROTATE(a,n) (((a)<<(n))|(((a)&0xffffffff)>>(32-(n)))) #endif /* A nice byte order reversal from Wei Dai */ #if defined(WIN32) /* 5 instructions with rotate instruction, else 9 */ #define Endian_Reverse32(a) \ { \ u_int32_t l=(a); \ (a)=((ROTATE(l,8)&0x00FF00FF)|(ROTATE(l,24)&0xFF00FF00)); \ } #else /* 6 instructions with rotate instruction, else 8 */ #define Endian_Reverse32(a) \ { \ u_int32_t l=(a); \ l=(((l&0xFF00FF00)>>8L)|((l&0x00FF00FF)<<8L)); \ (a)=ROTATE(l,16L); \ } #endif #define F1(x,y,z) ((x)^(y)^(z)) #define F2(x,y,z) (((x)&(y))|((~x)&z)) #define F3(x,y,z) (((x)|(~y))^(z)) #define F4(x,y,z) (((x)&(z))|((y)&(~(z)))) #define F5(x,y,z) ((x)^((y)|(~(z)))) #define RIPEMD160_A 0x67452301L #define RIPEMD160_B 0xEFCDAB89L #define RIPEMD160_C 0x98BADCFEL #define RIPEMD160_D 0x10325476L #define RIPEMD160_E 0xC3D2E1F0L #include "rmdconst.h" #define RIP1(a,b,c,d,e,w,s) { \ a+=F1(b,c,d)+X[w]; \ a=ROTATE(a,s)+e; \ c=ROTATE(c,10); } #define RIP2(a,b,c,d,e,w,s,K) { \ a+=F2(b,c,d)+X[w]+K; \ a=ROTATE(a,s)+e; \ c=ROTATE(c,10); } #define RIP3(a,b,c,d,e,w,s,K) { \ a+=F3(b,c,d)+X[w]+K; \ a=ROTATE(a,s)+e; \ c=ROTATE(c,10); } #define RIP4(a,b,c,d,e,w,s,K) { \ a+=F4(b,c,d)+X[w]+K; \ a=ROTATE(a,s)+e; \ c=ROTATE(c,10); } #define RIP5(a,b,c,d,e,w,s,K) { \ a+=F5(b,c,d)+X[w]+K; \ a=ROTATE(a,s)+e; \ c=ROTATE(c,10); } xymon-4.3.7/lib/clientlocal.c0000664000175000017500000000632111630612042015423 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* This is a library module for Xymon, responsible for loading the */ /* client-local.cfg file into memory and finding the proper host entry. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: clientlocal.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #include #include "libxymon.h" static strbuffer_t *clientconfigs = NULL; static void * rbconfigs; void load_clientconfig(void) { static char *configfn = NULL; static void *clientconflist = NULL; FILE *fd; strbuffer_t *buf; char *sectstart; if (!configfn) { configfn = (char *)malloc(strlen(xgetenv("XYMONHOME"))+ strlen("/etc/client-local.cfg") + 1); sprintf(configfn, "%s/etc/client-local.cfg", xgetenv("XYMONHOME")); } /* First check if there were no modifications at all */ if (clientconflist) { if (!stackfmodified(clientconflist)){ dbgprintf("No files modified, skipping reload of %s\n", configfn); return; } else { stackfclist(&clientconflist); clientconflist = NULL; } } if (!clientconfigs) { clientconfigs = newstrbuffer(0); } else { xtreeDestroy(rbconfigs); clearstrbuffer(clientconfigs); } rbconfigs = xtreeNew(strcasecmp); addtobuffer(clientconfigs, "\n"); buf = newstrbuffer(0); fd = stackfopen(configfn, "r", &clientconflist); if (!fd) return; while (stackfgets(buf, NULL)) addtostrbuffer(clientconfigs, buf); stackfclose(fd); sectstart = strstr(STRBUF(clientconfigs), "\n["); while (sectstart) { char *key, *nextsect; sectstart += 2; key = sectstart; sectstart += strcspn(sectstart, "]\n"); if (*sectstart == ']') { *sectstart = '\0'; sectstart++; sectstart += strcspn(sectstart, "\n"); } nextsect = strstr(sectstart, "\n["); if (nextsect) *(nextsect+1) = '\0'; xtreeAdd(rbconfigs, key, sectstart+1); sectstart = nextsect; } freestrbuffer(buf); } char *get_clientconfig(char *hostname, char *hostclass, char *hostos) { xtreePos_t handle; char *result = NULL; if (!clientconfigs) return NULL; /* * Find the client config. Search for a HOSTNAME entry first, * then the CLIENTCLASS, then CLIENTOS. */ handle = xtreeFind(rbconfigs, hostname); if ((handle == xtreeEnd(rbconfigs)) && hostclass && *hostclass) handle = xtreeFind(rbconfigs, hostclass); if ((handle == xtreeEnd(rbconfigs)) && hostos && *hostos) handle = xtreeFind(rbconfigs, hostos); if (handle != xtreeEnd(rbconfigs)) result = (char *)xtreeData(rbconfigs, handle); return result; } xymon-4.3.7/lib/xymonrrd.h0000664000175000017500000000350611630612042015023 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __XYMONRRD_H__ #define __XYMONRRD_H__ #include /* This is for mapping a service -> an RRD file */ typedef struct { char *svcname; char *xymonrrdname; } xymonrrd_t; /* This is for displaying an RRD file. */ typedef struct { char *xymonrrdname; char *xymonpartname; int maxgraphs; } xymongraph_t; typedef enum { HG_WITHOUT_STALE_RRDS, HG_WITH_STALE_RRDS } hg_stale_rrds_t; typedef enum { HG_PLAIN_LINK, HG_META_LINK } hg_link_t; typedef struct rrdtpldata_t { char *template; void *dsnames; /* Tree of tplnames_t records */ } rrdtpldata_t; typedef struct rrdtplnames_t { char *dsnam; int idx; } rrdtplnames_t; extern xymonrrd_t *xymonrrds; extern xymongraph_t *xymongraphs; extern xymonrrd_t *find_xymon_rrd(char *service, char *flags); extern xymongraph_t *find_xymon_graph(char *rrdname); extern char *xymon_graph_data(char *hostname, char *dispname, char *service, int bgcolor, xymongraph_t *graphdef, int itemcount, hg_stale_rrds_t nostale, hg_link_t wantmeta, int locatorbased, time_t starttime, time_t endtime); extern rrdtpldata_t *setup_template(char *params[]); #endif xymon-4.3.7/lib/Makefile0000664000175000017500000000636311655212727014451 0ustar henrikhenrik# Xymon library Makefile # XYMONLIBOBJS = osdefs.o acklog.o availability.o calc.o cgi.o cgiurls.o clientlocal.o color.o crondate.o digest.o encoding.o environ.o errormsg.o eventlog.o files.o headfoot.o xymonrrd.o holidays.o htmllog.o ipaccess.o loadalerts.o loadhosts.o loadcriticalconf.o locator.o links.o matching.o md5.o memory.o misc.o msort.o netservices.o notifylog.o readmib.o reportlog.o rmd160c.o sendmsg.o sha1.o sha2.o sig.o stackio.o strfunc.o suid.o timefunc.o timing.o tree.o url.o webaccess.o CLIENTLIBOBJS = osdefs.o cgiurls.o color-client.o crondate.o digest.o encoding.o environ-client.o errormsg.o holidays.o ipaccess.o loadhosts.o md5.o memory.o misc.o msort.o rmd160c.o sendmsg.o sha1.o sha2.o sig.o stackio.o strfunc.o suid.o timefunc-client.o tree.o ifeq ($(LOCALCLIENT),yes) CLIENTLIBOBJS += matching.o endif CFLAGS += -I. -I../include all: test-endianness libxymon.a xymonclient.a loadhosts stackio availability md5 sha1 rmd160 locator client: test-endianness xymonclient.a test-endianness: test-endianness.c $(CC) $(CFLAGS) -o $@ $< libxymon.a: $(XYMONLIBOBJS) ar cr libxymon.a $(XYMONLIBOBJS) ranlib libxymon.a || echo "" xymonclient.a: $(CLIENTLIBOBJS) ar cr xymonclient.a $(CLIENTLIBOBJS) ranlib xymonclient.a || echo "" loadhosts.o: loadhosts.c loadhosts_file.c loadhosts_net.c $(CC) $(CFLAGS) -c -o $@ loadhosts.c eventlog.o: eventlog.c $(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ $< notifylog.o: notifylog.c $(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ $< headfoot.o: headfoot.c $(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ $< loadalerts.o: loadalerts.c $(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ $< matching.o: matching.c $(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ $< sha1.o: sha1.c $(CC) $(CFLAGS) `./test-endianness` -c -o $@ $< rmd160c.o: rmd160c.c $(CC) $(CFLAGS) `./test-endianness` -c -o $@ $< environ.o: environ.c $(CC) $(CFLAGS) -DXYMONTOPDIR=\"$(XYMONTOPDIR)\" -DXYMONLOGDIR=\"$(XYMONLOGDIR)\" -DXYMONHOSTNAME=\"$(XYMONHOSTNAME)\" -DXYMONHOSTIP=\"$(XYMONHOSTIP)\" -DXYMONHOSTOS=\"$(XYMONHOSTOS)\" -DBUILD_HOME=\"$(XYMONTOPDIR)/server\" -c -o $@ environ.c environ-client.o: environ.c $(CC) $(CFLAGS) -DXYMONTOPDIR=\"$(XYMONTOPDIR)\" -DXYMONLOGDIR=\"$(XYMONLOGDIR)\" -DXYMONHOSTNAME=\"$(XYMONHOSTNAME)\" -DXYMONHOSTIP=\"$(XYMONHOSTIP)\" -DXYMONHOSTOS=\"$(XYMONHOSTOS)\" -DBUILD_HOME=\"$(XYMONTOPDIR)/client\" -c -o $@ environ.c color-client.o: color.c $(CC) $(CFLAGS) -DCLIENTONLY -c -o $@ $< timefunc-client.o: timefunc.c $(CC) $(CFLAGS) -DCLIENTONLY -c -o $@ $< loadhosts: loadhosts.c libxymon.a $(CC) $(CFLAGS) -DSTANDALONE -o $@ loadhosts.c ./libxymon.a $(NETLIBS) $(LIBRTDEF) stackio: stackio.c libxymon.a $(CC) $(CFLAGS) -DSTANDALONE -o $@ stackio.c ./libxymon.a $(NETLIBS) $(LIBRTDEF) availability: availability.c libxymon.a $(CC) $(CFLAGS) -DSTANDALONE -o $@ availability.c ./libxymon.a $(NETLIBS) $(LIBRTDEF) md5: md5.c $(CC) $(CFLAGS) -DSTANDALONE `./test-endianness` -o $@ md5.c sha1: sha1.c $(CC) $(CFLAGS) -DSTANDALONE `./test-endianness` -o $@ sha1.c rmd160: rmd160c.c $(CC) $(CFLAGS) -DSTANDALONE `./test-endianness` -o $@ rmd160c.c locator: locator.c $(CC) $(CFLAGS) -DSTANDALONE -o $@ locator.c ./libxymon.a $(NETLIBS) $(LIBRTDEF) clean: rm -f *.o *.a *~ loadhosts stackio availability test-endianness md5 sha1 rmd160 locator xymon-4.3.7/lib/locator.h0000664000175000017500000000355111615341300014603 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __LOCATOR_H__ #define __LOCATOR_H__ enum locator_servicetype_t { ST_RRD, ST_CLIENT, ST_ALERT, ST_HISTORY, ST_HOSTDATA, ST_MAX } ; extern const char *servicetype_names[]; enum locator_sticky_t { LOC_ROAMING, LOC_STICKY, LOC_SINGLESERVER } ; extern enum locator_servicetype_t get_servicetype(char *typestr); extern int locator_init(char *ipport); extern void locator_prepcache(enum locator_servicetype_t svc, int timeout); extern void locator_flushcache(enum locator_servicetype_t svc, char *key); extern char *locator_ping(void); extern int locator_register_server(char *servername, enum locator_servicetype_t svctype, int weight, enum locator_sticky_t sticky, char *extras); extern int locator_register_host(char *hostname, enum locator_servicetype_t svctype, char *servername); extern int locator_rename_host(char *oldhostname, char *newhostname, enum locator_servicetype_t svctype); extern char *locator_query(char *hostname, enum locator_servicetype_t svctype, char **extras); extern int locator_serverup(char *servername, enum locator_servicetype_t svctype); extern int locator_serverdown(char *servername, enum locator_servicetype_t svctype); #endif xymon-4.3.7/lib/stackio.h0000664000175000017500000000220611615341300014571 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __STACKIO_H__ #define __STACKIO_H__ #define MAX_LINE_LEN 16384 extern int initfgets(FILE *fd); extern char *unlimfgets(strbuffer_t *buffer, FILE *fd); extern FILE *stackfopen(char *filename, char *mode, void **v_filelist); extern int stackfclose(FILE *fd); extern char *stackfgets(strbuffer_t *buffer, char *extraincl); extern int stackfmodified(void *v_listhead); extern void stackfclist(void **v_listhead); #endif xymon-4.3.7/lib/sha2.h0000664000175000017500000000345711615341300014002 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* API for the SHA1 digest routines. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __SHA2_H__ #define __SHA2_H__ extern int mySHA512_Size(void); extern void mySHA512_Init(void *context); extern void mySHA512_Update(void *context, const unsigned char *data, int len); extern void mySHA512_Final(unsigned char digest[20], void *context); extern int mySHA256_Size(void); extern void mySHA256_Init(void *context); extern void mySHA256_Update(void *context, const unsigned char *data, int len); extern void mySHA256_Final(unsigned char digest[20], void *context); extern int mySHA384_Size(void); extern void mySHA384_Init(void *context); extern void mySHA384_Update(void *context, const unsigned char *data, int len); extern void mySHA384_Final(unsigned char digest[20], void *context); extern int mySHA224_Size(void); extern void mySHA224_Init(void *context); extern void mySHA224_Update(void *context, const unsigned char *data, int len); extern void mySHA224_Final(unsigned char digest[20], void *context); #endif xymon-4.3.7/lib/readmib.c0000664000175000017500000001731511630612042014542 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor SNMP data collection tool */ /* */ /* Copyright (C) 2007-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: readmib.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #include "libxymon.h" static void * mibdefs; /* Holds the list of MIB definitions */ static xtreePos_t nexthandle; int readmibs(char *cfgfn, int verbose) { static char *fn = NULL; static void *cfgfiles = NULL; FILE *cfgfd; strbuffer_t *inbuf; mibdef_t *mib = NULL; /* Check if config was modified */ if (cfgfiles) { if (!stackfmodified(cfgfiles)) { dbgprintf("No files changed, skipping reload\n"); return 0; } else { xtreePos_t handle; errprintf("Re-loading MIBs\n"); /* Clear list of config files */ stackfclist(&cfgfiles); cfgfiles = NULL; /* Drop the current data */ for (handle = xtreeFirst(mibdefs); (handle != xtreeEnd(mibdefs)); handle = xtreeNext(mibdefs, handle)) { mibdef_t *mib = (mibdef_t *)xtreeData(mibdefs, handle); oidset_t *swalk, *szombie; mibidx_t *iwalk, *izombie; int i; swalk = mib->oidlisthead; while (swalk) { szombie = swalk; swalk = swalk->next; for (i=0; (i <= szombie->oidcount); i++) { xfree(szombie->oids[i].dsname); xfree(szombie->oids[i].oid); } xfree(szombie->oids); xfree(szombie); } iwalk = mib->idxlist; while (iwalk) { izombie = iwalk; iwalk = iwalk->next; if (izombie->keyoid) xfree(izombie->keyoid); if (izombie->rootoid) xfree(izombie->rootoid); xfree(izombie); } if (mib->mibfn) xfree(mib->mibfn); if (mib->mibname) xfree(mib->mibname); freestrbuffer(mib->resultbuf); xfree(mib); } xtreeDestroy(mibdefs); } } mibdefs = xtreeNew(strcasecmp); nexthandle = xtreeEnd(mibdefs); if (fn) xfree(fn); fn = cfgfn; if (!fn) { fn = (char *)malloc(strlen(xgetenv("XYMONHOME")) + strlen("/etc/snmpmibs.cfg") + 1); sprintf(fn, "%s/etc/snmpmibs.cfg", xgetenv("XYMONHOME")); } cfgfd = stackfopen(fn, "r", &cfgfiles); if (cfgfd == NULL) { errprintf("Cannot open configuration files %s\n", fn); return 0; } inbuf = newstrbuffer(0); while (stackfgets(inbuf, NULL)) { char *bot, *p; sanitize_input(inbuf, 0, 0); bot = STRBUF(inbuf) + strspn(STRBUF(inbuf), " \t"); if ((*bot == '\0') || (*bot == '#')) continue; if (*bot == '[') { char *mibname; mibname = bot+1; p = strchr(mibname, ']'); if (p) *p = '\0'; mib = (mibdef_t *)calloc(1, sizeof(mibdef_t)); mib->mibname = strdup(mibname); mib->oidlisthead = mib->oidlisttail = (oidset_t *)calloc(1, sizeof(oidset_t)); mib->oidlisttail->oidsz = 10; mib->oidlisttail->oidcount = -1; mib->oidlisttail->oids = (oidds_t *)malloc(mib->oidlisttail->oidsz*sizeof(oidds_t)); mib->resultbuf = newstrbuffer(0); mib->tabular = 0; xtreeAdd(mibdefs, mib->mibname, mib); continue; } if (mib && (strncmp(bot, "mibfile", 7) == 0)) { p = bot + 7; p += strspn(p, " \t"); mib->mibfn = strdup(p); continue; } if (mib && (strncmp(bot, "extra", 5) == 0)) { /* Add an extra set of MIB objects to retrieve separately */ mib->oidlisttail->next = (oidset_t *)calloc(1, sizeof(oidset_t)); mib->oidlisttail = mib->oidlisttail->next; mib->oidlisttail->oidsz = 10; mib->oidlisttail->oidcount = -1; mib->oidlisttail->oids = (oidds_t *)malloc(mib->oidlisttail->oidsz*sizeof(oidds_t)); continue; } if (mib && (strncmp(bot, "table", 5) == 0)) { mib->tabular = 1; continue; } if (mib && ((strncmp(bot, "keyidx", 6) == 0) || (strncmp(bot, "validx", 6) == 0))) { /* * Define an index. Looks like: * keyidx (IF-MIB::ifDescr) * validx [IP-MIB::ipAdEntIfIndex] */ char endmarks[6]; mibidx_t *newidx = (mibidx_t *)calloc(1, sizeof(mibidx_t)); p = bot + 6; p += strspn(p, " \t"); newidx->marker = *p; p++; newidx->idxtype = (strncmp(bot, "keyidx", 6) == 0) ? MIB_INDEX_IN_OID : MIB_INDEX_IN_VALUE; newidx->keyoid = strdup(p); sprintf(endmarks, "%s%c", ")]}>", newidx->marker); p = newidx->keyoid + strcspn(newidx->keyoid, endmarks); *p = '\0'; newidx->next = mib->idxlist; mib->idxlist = newidx; mib->tabular = 1; continue; } if (mib) { /* icmpInMsgs = IP-MIB::icmpInMsgs.0 [/u32] [/rrd:TYPE] */ char *tok, *name, *oid = NULL; name = strtok(bot, " \t"); if (name) tok = strtok(NULL, " \t"); if (tok && (*tok == '=')) oid = strtok(NULL, " \t"); else oid = tok; if (name && oid) { mib->oidlisttail->oidcount++; if (mib->oidlisttail->oidcount == mib->oidlisttail->oidsz) { mib->oidlisttail->oidsz += 10; mib->oidlisttail->oids = (oidds_t *)realloc(mib->oidlisttail->oids, mib->oidlisttail->oidsz*sizeof(oidds_t)); } mib->oidlisttail->oids[mib->oidlisttail->oidcount].dsname = strdup(name); mib->oidlisttail->oids[mib->oidlisttail->oidcount].oid = strdup(oid); mib->oidlisttail->oids[mib->oidlisttail->oidcount].conversion = OID_CONVERT_NONE; mib->oidlisttail->oids[mib->oidlisttail->oidcount].rrdtype = RRD_NOTRACK; tok = strtok(NULL, " \t"); while (tok) { if (strcasecmp(tok, "/u32") == 0) { mib->oidlisttail->oids[mib->oidlisttail->oidcount].conversion = OID_CONVERT_U32; } else if (strncasecmp(tok, "/rrd:", 5) == 0) { char *rrdtype = tok+5; if (strcasecmp(rrdtype, "COUNTER") == 0) mib->oidlisttail->oids[mib->oidlisttail->oidcount].rrdtype = RRD_TRACK_COUNTER; else if (strcasecmp(rrdtype, "GAUGE") == 0) mib->oidlisttail->oids[mib->oidlisttail->oidcount].rrdtype = RRD_TRACK_GAUGE; else if (strcasecmp(rrdtype, "DERIVE") == 0) mib->oidlisttail->oids[mib->oidlisttail->oidcount].rrdtype = RRD_TRACK_DERIVE; else if (strcasecmp(rrdtype, "ABSOLUTE") == 0) mib->oidlisttail->oids[mib->oidlisttail->oidcount].rrdtype = RRD_TRACK_ABSOLUTE; } tok = strtok(NULL, " \t"); } } continue; } if (verbose) { errprintf("Unknown MIB definition line: '%s'\n", bot); } } stackfclose(cfgfd); freestrbuffer(inbuf); if (debug) { xtreePos_t handle; for (handle = xtreeFirst(mibdefs); (handle != xtreeEnd(mibdefs)); handle = xtreeNext(mibdefs, handle)) { mibdef_t *mib = (mibdef_t *)xtreeData(mibdefs, handle); oidset_t *swalk; int i; dbgprintf("[%s]\n", mib->mibname); for (swalk = mib->oidlisthead; (swalk); swalk = swalk->next) { dbgprintf("\t*** OID set, %d entries ***\n", swalk->oidcount); for (i=0; (i <= swalk->oidcount); i++) { dbgprintf("\t\t%s = %s\n", swalk->oids[i].dsname, swalk->oids[i].oid); } } } } return 1; } mibdef_t *first_mib(void) { nexthandle = xtreeFirst(mibdefs); if (nexthandle == xtreeEnd(mibdefs)) return NULL; else return (mibdef_t *)xtreeData(mibdefs, nexthandle); } mibdef_t *next_mib(void) { nexthandle = xtreeNext(mibdefs, nexthandle); if (nexthandle == xtreeEnd(mibdefs)) return NULL; else return (mibdef_t *)xtreeData(mibdefs, nexthandle); } mibdef_t *find_mib(char *mibname) { xtreePos_t handle; handle = xtreeFind(mibdefs, mibname); if (handle == xtreeEnd(mibdefs)) return NULL; else return (mibdef_t *)xtreeData(mibdefs, handle); } xymon-4.3.7/lib/rmd160c.h0000664000175000017500000000217711615341300014317 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* API for the RMD160 digest routines. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __RMD160C_H__ #define __RMD160C_H__ extern int myRIPEMD160_Size(void); extern void myRIPEMD160_Init(void *c); extern void myRIPEMD160_Update(void *c, const void *data, size_t len); extern void myRIPEMD160_Final(unsigned char *md, void *c); #endif xymon-4.3.7/lib/calc.c0000664000175000017500000001102011615341300014023 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: calc.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include "libxymon.h" long compute(char *expression, int *error) { /* * This routine evaluates an expression. * * Expressions are of the form "expr [operator expr]" or "(expr)" * "operator" is + - / * % & | && || > >= < <= == * * All operators have equal precedence! * */ char *exp, *startp, *operator; char *inp, *outp; char op; long xval, yval, result; if (*error) return -1; /* Copy expression except whitespace */ exp = (char *) malloc(strlen(expression)+1); inp = expression; outp=exp; do { if (!isspace((int) *inp)) { *outp = *inp; outp++; } inp++; } while (*inp); *outp = '\0'; /* First find the value of the first sub-expression */ startp = exp; while (isspace((int) *startp)) startp++; if (*startp == '(') { /* Starts with parentheses: * - find matching end parentheses * - find the operator following the end parentheses * - compute value of expression inside parentheses (recursive call) */ int pcount = 1; char *endp; for (endp = startp+1; (*endp && pcount); ) { if (*endp == '(') pcount++; else if (*endp == ')') pcount--; if (pcount) endp++; } if (*endp == '\0') { *error = 1; return -1; } operator = endp+1; *endp = '\0'; xval = compute(startp+1, error); } else { /* No parentheses --> it's a number */ xval = strtol(startp, &operator, 10); if (operator == startp) { *error = 2; return -1; } } /* Now loop over the following operators and expressions */ do { /* There may not be an operator */ if (*operator) { while (isspace((int) *operator)) operator++; op = *operator; /* For the && and || operators ... */ if ((op == '&') && (*(operator+1) == '&')) { op = 'a'; operator++; } else if ((op == '|') && (*(operator+1) == '|')) { op = 'o'; operator++; } else if ((op == '>') && (*(operator+1) == '=')) { op = 'g'; operator++; } else if ((op == '<') && (*(operator+1) == '=')) { op = 'l'; operator++; } else if ((op == '=') && (*(operator+1) == '=')) { op = 'e'; operator++; } /* Since there is an operator, there must be a value after the operator */ startp = operator + 1; while (isspace((int) *startp)) startp++; if (*startp == '(') { int pcount = 1; char *endp; for (endp = startp+1; (*endp && pcount);) { if (*endp == '(') pcount++; else if (*endp == ')') pcount--; if (pcount) endp++; } operator = endp+1; *endp = '\0'; yval = compute(startp+1, error); } else { yval = strtol(startp, &operator, 10); if (operator == startp) { *error = 3; return -1; } } switch (op) { case '+': xval = (xval + yval); break; case '-': xval = (xval - yval); break; case '*': xval = (xval * yval); break; case '/': if (yval) xval = (xval / yval); else { *error = 10; return -1; } break; case '%': if (yval) xval = (xval % yval); else { *error = 10; return -1; } break; case '&': xval = (xval & yval); break; case 'a': xval = (xval && yval); break; case '|': xval = (xval | yval); break; case 'o': xval = (xval || yval); break; case '>': xval = (xval > yval); break; case 'g': xval = (xval >= yval); break; case '<': xval = (xval < yval); break; case 'l': xval = (xval <= yval); break; case 'e': xval = (xval == yval); break; default : { *error = 4; return -1; } } } else { /* Do nothing - no operator, so result is the xval */ } result = xval; } while (*operator); xfree(exp); return result; } #ifdef STANDALONE int main(int argc, char *argv[]) { long result; int error = 0; result = compute(argv[1], &error); printf("%s = %ld\n", argv[1], result); return error; } #endif xymon-4.3.7/lib/loadhosts.h0000664000175000017500000000542711665414506015162 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __LOADHOSTS_H__ #define __LOADHOSTS_H__ enum xmh_item_t { XMH_NET, XMH_DISPLAYNAME, XMH_CLIENTALIAS, XMH_COMMENT, XMH_DESCRIPTION, XMH_NK, XMH_NKTIME, XMH_TRENDS, XMH_WML, XMH_NOPROPRED, XMH_NOPROPYELLOW, XMH_NOPROPPURPLE, XMH_NOPROPACK, XMH_REPORTTIME, XMH_WARNPCT, XMH_WARNSTOPS, XMH_DOWNTIME, XMH_SSLDAYS, XMH_SSLMINBITS, XMH_DEPENDS, XMH_BROWSER, XMH_HOLIDAYS, XMH_DELAYRED, XMH_DELAYYELLOW, XMH_FLAG_NOINFO, XMH_FLAG_NOTRENDS, XMH_FLAG_NODISP, XMH_FLAG_NONONGREEN, XMH_FLAG_NOBB2, XMH_FLAG_PREFER, XMH_FLAG_NOSSLCERT, XMH_FLAG_TRACE, XMH_FLAG_NOTRACE, XMH_FLAG_NOCONN, XMH_FLAG_NOPING, XMH_FLAG_DIALUP, XMH_FLAG_TESTIP, XMH_FLAG_LDAPFAILYELLOW, XMH_FLAG_NOCLEAR, XMH_FLAG_HIDEHTTP, XMH_FLAG_PULLDATA, XMH_FLAG_MULTIHOMED, XMH_LDAPLOGIN, XMH_IP, XMH_HOSTNAME, XMH_DOCURL, XMH_NOPROP, XMH_PAGEINDEX, XMH_GROUPID, XMH_DGNAME, XMH_PAGENAME, XMH_PAGEPATH, XMH_PAGETITLE, XMH_PAGEPATHTITLE, XMH_ALLPAGEPATHS, XMH_RAW, XMH_CLASS, XMH_OS, XMH_NOCOLUMNS, XMH_DATA, XMH_NOTBEFORE, XMH_NOTAFTER, XMH_COMPACT, XMH_INTERFACES, XMH_LAST }; enum ghosthandling_t { GH_ALLOW, GH_IGNORE, GH_LOG, GH_MATCH }; extern int load_hostnames(char *hostsfn, char *extrainclude, int fqdn); extern int load_hostinfo(char *hostname); extern char *hostscfg_content(void); extern char *knownhost(char *hostname, char *hostip, enum ghosthandling_t ghosthandling); extern int knownloghost(char *logdir); extern void *hostinfo(char *hostname); extern void *localhostinfo(char *hostname); extern char *xmh_item(void *host, enum xmh_item_t item); extern char *xmh_custom_item(void *host, char *key); extern enum xmh_item_t xmh_key_idx(char *item); extern char *xmh_item_byname(void *host, char *item); extern char *xmh_item_walk(void *host); extern int xmh_item_idx(char *value); extern char *xmh_item_id(enum xmh_item_t idx); extern void *first_host(void); extern void *next_host(void *currenthost, int wantclones); extern void xmh_set_item(void *host, enum xmh_item_t item, void *value); extern char *xmh_item_multi(void *host, enum xmh_item_t item); #endif xymon-4.3.7/xymond/0000775000175000017500000000000011671641716013553 5ustar henrikhenrikxymon-4.3.7/xymond/trimhistory.80000664000175000017500000000607211671641417016244 0ustar henrikhenrik.TH TRIMHISTORY 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME trimhistory \- Remove old Xymon history-log entries .SH SYNOPSIS .B "trimhistory --cutoff=TIME [options]" .SH DESCRIPTION The \fBtrimhistory\fR tool is used to purge old entries from the Xymon history logs. These logfiles accumulate information about all status changes that have occurred for any given service, host, or the entire Xymon system, and is used to generate the event- and history-log webpages. Purging old entries can be done while Xymon is running, since the tool takes care not to commit updates to a file if it changes mid-way through the operation. In that case, the update is aborted and the existing logfile is left untouched. Optionally, this tool will also remove logfiles from hosts that are no longer defined in the Xymon .I hosts.cfg(5) file. As an extension, even logfiles from services can be removed, if the service no longer has a valid status-report logged in the current Xymon status. .SH OPTIONS .IP "--cutoff=TIME" This defines the cutoff-time when processing the history logs. Entries dated before this time are discarded. TIME is specified as the number of seconds since the beginning of the Epoch. This is easily generated by the GNU .I date(1) utility, e.g. the following command will trim history logs of all entries prior to Oct. 1st 2004: .br .sp trimhistory --cutoff=`date +%s --date="1 Oct 2004"` .IP "--outdir=DIRECTORY" Normally, files in the XYMONHISTDIR directory are replaced. This option causes trimhistory to save the shortened history logfiles to another directory, so you can verify that the operation works as intended. The output directory must exist. .IP --drop Causes trimhistory to delete files from hosts that are not listed in the .I hosts.cfg(5) file. .IP --dropsvcs Causes trimhistory to delete files from services that are not currently tracked by Xymon. Normally these files would be left untouched if only the host exists. .IP --droplogs Process the XYMONHISTLOGS directory also, and delete status-logs from events prior to the cut-off time. Note that this can dramatically increase the processing time, since there are often lots and lots of files to process. .IP "--progress[=N]" This will cause trimhistory to output a status line for every N history logs or status-log collections it processes, to indicate how far it has progressed. The default setting for N is 100. .IP "--env=FILENAME" Loads the environment from FILENAME before executing trimhistory. .IP --debug Enable debugging output. .SH FILES .IP "$XYMONHISTDIR/allevents" The eventlog of all events that have happened in Xymon. .IP "$XYMONHISTDIR/HOSTNAME" The per-host eventlogs. .IP "$XYMONHISTDIR/HOSTNAME.SERVICE" The per-service eventlogs. .IP "$XYMONHISTLOGS/*/*" The historical status-logs. .SH "ENVIRONMENT VARIABLES" .IP XYMONHISTDIR The directory holding all history logs. .IP XYMONHISTLOGS The top-level directory for the historical status-log collections. .IP HOSTSCFG The location of the hosts.cfg file, holding the list of currently known hosts in Xymon. .SH "SEE ALSO" xymon(7), hosts.cfg(5) xymon-4.3.7/xymond/xymond_filestore.80000664000175000017500000000613611671641417017242 0ustar henrikhenrik.TH XYMOND_FILESTORE 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymond_filestore \- xymond worker module for storing Xymon data .SH SYNOPSIS .B "xymond_channel --channel=status xymond_filestore --status [options]" .br .B "xymond_channel --channel=data xymond_filestore --data [options]" .br .B "xymond_channel --channel=notes xymond_filestore --notes [options]" .br .B "xymond_channel --channel=enadis xymond_filestore --enadis [options]" .SH DESCRIPTION xymond_filestore is a worker module for xymond, and as such it is normally run via the .I xymond_channel(8) program. It receives xymond messages from a xymond channel via stdin, and stores these in the filesystem in a manner that is compatible with the Big Brother daemon, bbd. This program can be started multiple times, if you want to store messages for more than one channel. .SH OPTIONS .IP "--status" Incoming messages are "status" messages, they will be stored in the $XYMONRAWSTATUSDIR/ directory. If you are using .I xymon(7) throughout your Xymon server, you will not need to run this module to save status messages, unless you have a third-party add-on that reads the status-logs directly. This module is NOT needed to get trend graphs, you should run the .I xymond_rrd(8) module instead. .IP "--data" Incoming messages are "data" messages, they will be stored in the $XYMONDATADIR directory. This module is not needed, unless you have a third-party module that processes the data-files. This module is NOT needed to get trend graphs, you should run the .I xymond_rrd(8) module instead. .IP "--notes" Incoming messages are "notes" messages, they will be stored in the $XYMONNOTESDIR directory. This modules is only needed if you want to allow people to remotely update the notes-files available on the Xymon webpages. .IP "--enadis" Incoming messages are enable/disable messages, they will update files in the $XYMONDISABLEDDIR directory. This is only needed if you have third-party add-ons that use these files. .IP "--dir=DIRECTORY" Overrides the default output directory. .IP "--html" Used together with "--status". Tells xymond_filestore to also save an HTML version of the status-log. Should not be used unless you must run with "XYMONLOGSTATUS=static". .IP "--htmldir=DIRECTORY" The directory where HTML-versions of the status logs are stored. Default: $XYMONHTMLSTATUSDIR .IP "--htmlext=.EXT" Set the filename extension for generated HTML files. By default, HTML files are saved with a ".html" extension. .IP "--multigraphs=TEST1[,TEST2]" This causes xymond_filestore to generate HTML status pages with links to service graphs that are split up into multiple images, with at most 5 graphs per image. If not specified, only the "disk" status is split up this way. .IP "--only=test[,test,test]" Save status messages only for the listed set of tests. This can be useful if you have an external script that needs to parse some of the status logs, but you do not want to save all status logs. .IP "--debug" Enable debugging output. .SH FILES This module does not rely on any configuration files. .SH "SEE ALSO" xymond_channel(8), xymond_rrd(8), xymond(8), xymon(7) xymon-4.3.7/xymond/xymond_ipc.h0000664000175000017500000000245611615341300016064 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __XYMOND_IPC_H__ #define __XYMOND_IPC_H__ #include "xymond_buffer.h" /* Semaphore numbers */ #define BOARDBUSY 0 #define GOCLIENT 1 #define CLIENTCOUNT 2 #define CHAN_MASTER 0 #define CHAN_CLIENT 1 typedef struct xymond_channel_t { enum msgchannels_t channelid; int shmid; int semid; char *channelbuf; unsigned int seq; unsigned long msgcount; struct xymond_channel_t *next; } xymond_channel_t; extern char *channelnames[]; extern xymond_channel_t *setup_channel(enum msgchannels_t chnname, int role); extern void close_channel(xymond_channel_t *chn, int role); #endif xymon-4.3.7/xymond/xymond_client.c0000664000175000017500000020011411667431736016575 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* "PORT" handling (C) Mirko Saam */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond_client.c 6792 2011-12-06 15:25:18Z storner $"; #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymond_worker.h" #include "client_config.h" #define MAX_META 20 /* The maximum number of meta-data items in a message */ enum msgtype_t { MSG_CPU, MSG_DISK, MSG_INODE, MSG_FILES, MSG_MEMORY, MSG_MSGS, MSG_PORTS, MSG_PROCS, MSG_SVCS, MSG_WHO, MSG_LAST }; typedef struct sectlist_t { char *sname; char *sdata; char *nextsectionrestoreptr, *sectdatarestoreptr; char nextsectionrestoreval, sectdatarestoreval; struct sectlist_t *next; } sectlist_t; static sectlist_t *defsecthead = NULL; int pslistinprocs = 1; int portlistinports = 1; int svclistinsvcs = 1; int sendclearmsgs = 1; int sendclearfiles = 1; int sendclearports = 1; int sendclearsvcs = 1; int localmode = 0; int noreportcolor = COL_CLEAR; typedef struct updinfo_t { char *hostname; time_t updtime; int updseq; } updinfo_t; static void * updinfotree; int add_updateinfo(char *hostname, int seq, time_t tstamp) { xtreePos_t handle; updinfo_t *itm; handle = xtreeFind(updinfotree, hostname); if (handle == xtreeEnd(updinfotree)) { itm = (updinfo_t *)calloc(1, sizeof(updinfo_t)); itm->hostname = strdup(hostname); xtreeAdd(updinfotree, itm->hostname, itm); } else { itm = (updinfo_t *)xtreeData(updinfotree, handle); } if (itm->updtime == tstamp) { dbgprintf("%s: Duplicate client message at time %d, seq %d, lastseq %d\n", hostname, (int) tstamp, seq, itm->updseq); return 1; } itm->updtime = tstamp; itm->updseq = seq; return 0; } void nextsection_r_done(void *secthead) { /* Free the old list */ sectlist_t *swalk, *stmp; swalk = (sectlist_t *)secthead; while (swalk) { if (swalk->nextsectionrestoreptr) *swalk->nextsectionrestoreptr = swalk->nextsectionrestoreval; if (swalk->sectdatarestoreptr) *swalk->sectdatarestoreptr = swalk->sectdatarestoreval; stmp = swalk; swalk = swalk->next; xfree(stmp); } } void splitmsg_r(char *clientdata, sectlist_t **secthead) { char *cursection, *nextsection; char *sectname, *sectdata; if (clientdata == NULL) { errprintf("Got a NULL client data message\n"); return; } if (secthead == NULL) { errprintf("BUG: splitmsg_r called with NULL secthead\n"); return; } if (*secthead) { errprintf("BUG: splitmsg_r called with non-empty secthead\n"); nextsection_r_done(*secthead); *secthead = NULL; } /* Find the start of the first section */ if (*clientdata == '[') cursection = clientdata; else { cursection = strstr(clientdata, "\n["); if (cursection) cursection++; } while (cursection) { sectlist_t *newsect = (sectlist_t *)calloc(1, sizeof(sectlist_t)); /* Find end of this section (i.e. start of the next section, if any) */ nextsection = strstr(cursection, "\n["); if (nextsection) { newsect->nextsectionrestoreptr = nextsection; newsect->nextsectionrestoreval = *nextsection; *nextsection = '\0'; nextsection++; } /* Pick out the section name and data */ sectname = cursection+1; sectdata = sectname + strcspn(sectname, "]\n"); newsect->sectdatarestoreptr = sectdata; newsect->sectdatarestoreval = *sectdata; *sectdata = '\0'; sectdata++; if (*sectdata == '\n') sectdata++; /* Save the pointers in the list */ newsect->sname = sectname; newsect->sdata = sectdata; newsect->next = *secthead; *secthead = newsect; /* Next section, please */ cursection = nextsection; } } void splitmsg_done(void) { /* * NOTE: This MUST be called when we're doing using a message, * and BEFORE the next message is read. If called after the * next message is read, the restore-pointers in the "defsecthead" * list will point to data inside the NEW message, and * if the buffer-usage happens to be setup correctly, then * this will write semi-random data over the new message. */ if (defsecthead) { /* Clean up after the previous message */ nextsection_r_done(defsecthead); defsecthead = NULL; } } void splitmsg(char *clientdata) { if (defsecthead) { errprintf("BUG: splitmsg_done() was not called on previous message - data corruption possible.\n"); splitmsg_done(); } splitmsg_r(clientdata, &defsecthead); } char *nextsection_r(char *clientdata, char **name, void **current, void **secthead) { if (clientdata) { *secthead = NULL; splitmsg_r(clientdata, (sectlist_t **)secthead); *current = *secthead; } else { *current = (*current ? ((sectlist_t *)*current)->next : NULL); } if (*current) { *name = ((sectlist_t *)*current)->sname; return ((sectlist_t *)*current)->sdata; } return NULL; } char *nextsection(char *clientdata, char **name) { static void *current = NULL; if (clientdata && defsecthead) { nextsection_r_done(defsecthead); defsecthead = NULL; } return nextsection_r(clientdata, name, ¤t, (void **)&defsecthead); } char *getdata(char *sectionname) { sectlist_t *swalk; for (swalk = defsecthead; (swalk && strcmp(swalk->sname, sectionname)); swalk = swalk->next) ; if (swalk) return swalk->sdata; return NULL; } int linecount(char *msg) { int result = 0; char *nl; if (!msg) return 0; nl = msg - 1; while (nl) { nl++; if (*nl >= ' ') result++; nl = strchr(nl, '\n'); } return result; } int want_msgtype(void *hinfo, enum msgtype_t msg) { static void *currhost = NULL; static unsigned long currset = 0; if (currhost != hinfo) { char *val, *tok; currhost = hinfo; currset = 0; val = xmh_item(currhost, XMH_NOCOLUMNS); if (val) { val = strdup(val); tok = strtok(val, ","); while (tok) { if (strcmp(tok, "cpu") == 0) currset |= (1 << MSG_CPU); else if (strcmp(tok, "disk") == 0) currset |= (1 << MSG_DISK); else if (strcmp(tok, "inode") == 0) currset |= (1 << MSG_INODE); else if (strcmp(tok, "files") == 0) currset |= (1 << MSG_FILES); else if (strcmp(tok, "memory") == 0) currset |= (1 << MSG_MEMORY); else if (strcmp(tok, "msgs") == 0) currset |= (1 << MSG_MSGS); else if (strcmp(tok, "ports") == 0) currset |= (1 << MSG_PORTS); else if (strcmp(tok, "procs") == 0) currset |= (1 << MSG_PROCS); else if (strcmp(tok, "svcs") == 0) currset |= (1 << MSG_SVCS); else if (strcmp(tok, "who") == 0) currset |= (1 << MSG_WHO); tok = strtok(NULL, ","); } xfree(val); } } return ((currset & (1 << msg)) == 0); } char *nocolon(char *txt) { static char *result = NULL; char *p; /* * This function changes all colons in "txt" to semi-colons. * This is needed because some of the data messages we use * for reporting things like file- and directory-sizes, or * linecounts in files, use a colon-delimited string that * is sent to the RRD module, and this breaks for the Windows * Powershell client where filenames may contain a colon. */ if (result) xfree(result); result = strdup(txt); p = result; while ((p = strchr(p, ':')) != NULL) *p = ';'; return result; } void unix_cpu_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *uptimestr, char *clockstr, char *msgcachestr, char *whostr, int usercount, char *psstr, int pscount, char *topstr) { char *p; float load1, load5, load15; float loadyellow, loadred; int recentlimit, ancientlimit, maxclockdiff, uptimecolor, clockdiffcolor; char loadresult[100]; long uptimesecs = -1; char myupstr[100]; int cpucolor = COL_GREEN; char msgline[4096]; strbuffer_t *upmsg; if (!want_msgtype(hinfo, MSG_CPU)) return; if (!uptimestr) return; p = strstr(uptimestr, " up "); if (p) { char *uptimeresult; char *daymark; char *hourmark; long uphour, upmin, upsecs; uptimesecs = 0; uptimeresult = strdup(p+3); /* * Linux: " up 178 days, 9:15," * BSD: "" * Solaris: " up 21 days 20:58," */ daymark = strstr(uptimeresult, " day"); dbgprintf("CPU check host %s: daymark '%s'\n", hostname, daymark); if (daymark) { uptimesecs = atoi(uptimeresult) * 86400; if (strncmp(daymark, " days ", 6) == 0) { hourmark = daymark + 6; } else if (strncmp(daymark, " day ", 5) == 0) { hourmark = daymark + 5; } else { hourmark = strchr(daymark, ','); if (hourmark) hourmark++; else hourmark = ""; } } else { hourmark = uptimeresult; } hourmark += strspn(hourmark, " "); dbgprintf("CPU check host %s: hourmark '%s'\n", hostname, hourmark); if (sscanf(hourmark, "%ld:%ld", &uphour, &upmin) == 2) { uptimesecs += 60*(60*uphour + upmin); } else if (sscanf(hourmark, "%ld hours %ld mins", &uphour, &upmin) == 2) { uptimesecs += 60*(60*uphour + upmin); } else if (strstr(hourmark, " secs") && (sscanf(hourmark, "%ld secs", &upsecs) == 1)) { uptimesecs += upsecs; } else if (strstr(hourmark, "min") && (sscanf(hourmark, "%ld min", &upmin) == 1)) { uptimesecs += 60*upmin; } else if (strncmp(hourmark, "1 hr", 4) == 0) { uptimesecs = 3600; } else { uptimesecs = -1; } xfree(uptimeresult); } if (uptimesecs != -1) { int days = (uptimesecs / 86400); int hours = (uptimesecs % 86400) / 3600; int mins = (uptimesecs % 3600) / 60; if (days) sprintf(myupstr, "up: %d days", days); else sprintf(myupstr, "up: %02d:%02d", hours, mins); } else *myupstr = '\0'; load5 = 0.0; *loadresult = '\0'; p = strstr(uptimestr, "load average: "); if (!p) p = strstr(uptimestr, "load averages: "); /* Many BSD's */ if (p) { p = strchr(p, ':') + 1; p += strspn(p, " "); if ((sscanf(p, "%f, %f, %f", &load1, &load5, &load15) == 3) || (sscanf(p, "%f %f %f", &load1, &load5, &load15) == 3)) { sprintf(loadresult, "%.2f", load5); } } else { p = strstr(uptimestr, " load="); if (p) { char *lstart = p+6; char savech; p = lstart + strspn(lstart, "0123456789."); savech = *p; *p = '\0'; load5 = atof(loadresult); strcpy(loadresult, lstart); *p = savech; if (savech == '%') strcat(loadresult, "%"); } } get_cpu_thresholds(hinfo, clientclass, &loadyellow, &loadred, &recentlimit, &ancientlimit, &uptimecolor, &maxclockdiff, &clockdiffcolor); upmsg = newstrbuffer(0); if (load5 > loadred) { cpucolor = COL_RED; addtobuffer(upmsg, "&red Load is CRITICAL\n"); } else if (load5 > loadyellow) { cpucolor = COL_YELLOW; addtobuffer(upmsg, "&yellow Load is HIGH\n"); } if ((uptimesecs != -1) && (recentlimit != -1) && (uptimesecs < recentlimit)) { if (cpucolor != COL_RED) cpucolor = uptimecolor; sprintf(msgline, "&%s Machine recently rebooted\n", colorname(uptimecolor)); addtobuffer(upmsg, msgline); } if ((uptimesecs != -1) && (ancientlimit != -1) && (uptimesecs > ancientlimit)) { if (cpucolor != COL_RED) cpucolor = uptimecolor; sprintf(msgline, "&%s Machine has been up more than %d days\n", colorname(uptimecolor), (ancientlimit / 86400)); addtobuffer(upmsg, msgline); } if (clockstr) { char *p; struct timeval clockval; p = strstr(clockstr, "epoch:"); if (p && (sscanf(p, "epoch: %ld.%ld", (long int *)&clockval.tv_sec, (long int *)&clockval.tv_usec) == 2)) { struct timeval clockdiff; struct timezone tz; int cachedelay = 0; if (msgcachestr) { /* Message passed through msgcache, so adjust for the cache delay */ p = strstr(msgcachestr, "Cachedelay:"); if (p) cachedelay = atoi(p+11); } gettimeofday(&clockdiff, &tz); clockdiff.tv_sec -= (clockval.tv_sec + cachedelay); clockdiff.tv_usec -= clockval.tv_usec; if (clockdiff.tv_usec < 0) { clockdiff.tv_usec += 1000000; clockdiff.tv_sec -= 1; } if ((maxclockdiff > 0) && (abs(clockdiff.tv_sec) > maxclockdiff)) { if (cpucolor != COL_RED) cpucolor = clockdiffcolor; sprintf(msgline, "&%s System clock is %ld seconds off (max %ld)\n", colorname(clockdiffcolor), (long) clockdiff.tv_sec, (long) maxclockdiff); addtobuffer(upmsg, msgline); } else { sprintf(msgline, "System clock is %ld seconds off\n", (long) clockdiff.tv_sec); addtobuffer(upmsg, msgline); } } } init_status(cpucolor); sprintf(msgline, "status %s.cpu %s %s %s, %d users, %d procs, load=%s\n", commafy(hostname), colorname(cpucolor), (timestr ? timestr : ""), myupstr, (whostr ? linecount(whostr) : usercount), (psstr ? linecount(psstr)-1 : pscount), loadresult); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (topstr) { addtostatus("\n"); addtostatus(topstr); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } void unix_disk_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *freehdr, char *capahdr, char *mnthdr, char *dfstr) { int diskcolor = COL_GREEN; int dchecks = 0; int freecol = -1; int capacol = -1; int mntcol = -1; char *p, *bol, *nl; char msgline[4096]; strbuffer_t *monmsg, *dfstr_filtered; char *dname; int dmin, dmax, dcount, dcolor; char *group; if (!want_msgtype(hinfo, MSG_DISK)) return; if (!dfstr) return; dbgprintf("Disk check host %s\n", hostname); monmsg = newstrbuffer(0); dfstr_filtered = newstrbuffer(0); dchecks = clear_disk_counts(hinfo, clientclass); clearalertgroups(); bol = dfstr; /* Must do this always, to at least grab the column-numbers we need */ while (bol) { int ignored = 0; nl = strchr(bol, '\n'); if (nl) *nl = '\0'; if ((capacol == -1) && (mntcol == -1) && (freecol == -1)) { /* First line: Check the header and find the columns we want */ p = strdup(bol); freecol = selectcolumn(p, freehdr); strcpy(p, bol); capacol = selectcolumn(p, capahdr); strcpy(p, bol); mntcol = selectcolumn(p, mnthdr); xfree(p); dbgprintf("Disk check: header '%s', columns %d and %d\n", bol, freecol, capacol, mntcol); } else { char *fsname = NULL, *levelstr = NULL; int abswarn, abspanic; long levelpct = -1, levelabs = -1, warnlevel, paniclevel; p = strdup(bol); fsname = getcolumn(p, mntcol); if (fsname) { char *msgp = msgline; add_disk_count(fsname); get_disk_thresholds(hinfo, clientclass, fsname, &warnlevel, &paniclevel, &abswarn, &abspanic, &ignored, &group); strcpy(p, bol); levelstr = getcolumn(p, freecol); if (levelstr) levelabs = atol(levelstr); strcpy(p, bol); levelstr = getcolumn(p, capacol); if (levelstr) levelpct = atol(levelstr); dbgprintf("Disk check: FS='%s' level %ld%%/%ldU (thresholds: %lu/%lu, abs: %d/%d)\n", fsname, levelpct, levelabs, warnlevel, paniclevel, abswarn, abspanic); if (ignored) { /* Forget about this one */ } else if ( (abspanic && (levelabs <= paniclevel)) || (!abspanic && (levelpct >= paniclevel)) ) { if (diskcolor < COL_RED) diskcolor = COL_RED; msgp += sprintf(msgp, "&red %s ", fsname); if (abspanic) msgp += sprintf(msgp, "(%lu units free)", levelabs); else msgp += sprintf(msgp, "(%lu%% used)", levelpct); msgp += sprintf(msgp, " has reached the PANIC level "); if (abspanic) msgp += sprintf(msgp, "(%lu units)\n", paniclevel); else msgp += sprintf(msgp, "(%lu%%)\n", paniclevel); addtobuffer(monmsg, msgline); addalertgroup(group); } else if ( (abswarn && (levelabs <= warnlevel)) || (!abswarn && (levelpct >= warnlevel)) ) { if (diskcolor < COL_YELLOW) diskcolor = COL_YELLOW; msgp += sprintf(msgp, "&yellow %s ", fsname); if (abswarn) msgp += sprintf(msgp, "(%lu units free)", levelabs); else msgp += sprintf(msgp, "(%lu%% used)", levelpct); msgp += sprintf(msgp, " has reached the WARNING level "); if (abswarn) msgp += sprintf(msgp, "(%lu units)\n", warnlevel); else msgp += sprintf(msgp, "(%lu%%)\n", warnlevel); addtobuffer(monmsg, msgline); addalertgroup(group); } } xfree(p); } if (!ignored) { addtobuffer(dfstr_filtered, bol); addtobuffer(dfstr_filtered, "\n"); } if (nl) { *nl = '\n'; bol = nl+1; } else bol = NULL; } if ((capacol == -1) && (mntcol == -1)) { /* If this happens, we havent found our headers so no filesystems have been processed */ diskcolor = COL_YELLOW; sprintf(msgline, "&red Expected strings (%s and %s) not found in df output\n", capahdr, mnthdr); addtobuffer(monmsg, msgline); errprintf("Host %s (%s) sent incomprehensible disk report - missing columnheaders '%s' and '%s'\n", hostname, osname(os), capahdr, mnthdr); } /* Check for filesystems that must (not) exist */ while ((dname = check_disk_count(&dcount, &dmin, &dmax, &dcolor, &group)) != NULL) { char limtxt[1024]; *limtxt = '\0'; if (dmax == -1) { if (dmin > 0) sprintf(limtxt, "%d or more", dmin); else if (dmin == 0) sprintf(limtxt, "none"); } else { if (dmin > 0) sprintf(limtxt, "between %d and %d", dmin, dmax); else if (dmin == 0) sprintf(limtxt, "at most %d", dmax); } if (dcolor != COL_GREEN) { if (dcolor > diskcolor) diskcolor = dcolor; sprintf(msgline, "&%s Filesystem %s (found %d, req. %s)\n", colorname(dcolor), dname, dcount, limtxt); addtobuffer(monmsg, msgline); addalertgroup(group); } } /* Now we know the result, so generate a status message */ init_status(diskcolor); group = getalertgroups(); if (group) sprintf(msgline, "status/group:%s ", group); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.disk %s %s - Filesystems %s\n", commafy(hostname), colorname(diskcolor), (timestr ? timestr : ""), ((diskcolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info about what's wrong */ if (STRBUFLEN(monmsg)) { addtostrstatus(monmsg); addtostatus("\n"); } /* And the full df output */ addtostrstatus(dfstr_filtered); if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(monmsg); freestrbuffer(dfstr_filtered); } void unix_inode_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *freehdr, char *capahdr, char *mnthdr, char *dfstr) { int inodecolor = COL_GREEN; int ichecks = 0; int freecol = -1; int capacol = -1; int mntcol = -1; char *p, *bol, *nl; char msgline[4096]; strbuffer_t *monmsg, *dfstr_filtered; char *iname; int imin, imax, icount, icolor; char *group; if (!want_msgtype(hinfo, MSG_INODE)) return; if (!dfstr) return; dbgprintf("Inode check host %s\n", hostname); monmsg = newstrbuffer(0); dfstr_filtered = newstrbuffer(0); ichecks = clear_inode_counts(hinfo, clientclass); clearalertgroups(); bol = dfstr; /* Must do this always, to at least grab the column-numbers we need */ while (bol) { int ignored = 0; nl = strchr(bol, '\n'); if (nl) *nl = '\0'; if ((capacol == -1) && (mntcol == -1) && (freecol == -1)) { /* First line: Check the header and find the columns we want */ p = strdup(bol); freecol = selectcolumn(p, freehdr); strcpy(p, bol); capacol = selectcolumn(p, capahdr); strcpy(p, bol); mntcol = selectcolumn(p, mnthdr); xfree(p); dbgprintf("Inode check: header '%s', columns %d and %d\n", bol, freecol, capacol, mntcol); } else { char *fsname = NULL, *levelstr = NULL; int abswarn, abspanic; long levelpct = -1, levelabs = -1, warnlevel, paniclevel; p = strdup(bol); fsname = getcolumn(p, mntcol); if (fsname) { char *msgp = msgline; add_inode_count(fsname); get_inode_thresholds(hinfo, clientclass, fsname, &warnlevel, &paniclevel, &abswarn, &abspanic, &ignored, &group); strcpy(p, bol); levelstr = getcolumn(p, freecol); if (levelstr) levelabs = atol(levelstr); strcpy(p, bol); levelstr = getcolumn(p, capacol); if (levelstr) levelpct = atol(levelstr); dbgprintf("Inode check: FS='%s' level %ld%%/%ldU (thresholds: %lu/%lu, abs: %d/%d)\n", fsname, levelpct, levelabs, warnlevel, paniclevel, abswarn, abspanic); if (ignored) { /* Forget about this one */ } else if ( (abspanic && (levelabs <= paniclevel)) || (!abspanic && (levelpct >= paniclevel)) ) { if (inodecolor < COL_RED) inodecolor = COL_RED; msgp += sprintf(msgp, "&red %s ", fsname, fsname); if (abspanic) msgp += sprintf(msgp, "(%lu units free)", levelabs); else msgp += sprintf(msgp, "(%lu%% used)", levelpct); msgp += sprintf(msgp, " has reached the PANIC level "); if (abspanic) msgp += sprintf(msgp, "(%lu units)\n", paniclevel); else msgp += sprintf(msgp, "(%lu%%)\n", paniclevel); addtobuffer(monmsg, msgline); addalertgroup(group); } else if ( (abswarn && (levelabs <= warnlevel)) || (!abswarn && (levelpct >= warnlevel)) ) { if (inodecolor < COL_YELLOW) inodecolor = COL_YELLOW; msgp += sprintf(msgp, "&yellow %s ", fsname, fsname); if (abswarn) msgp += sprintf(msgp, "(%lu units free)", levelabs); else msgp += sprintf(msgp, "(%lu%% used)", levelpct); msgp += sprintf(msgp, " has reached the WARNING level "); if (abswarn) msgp += sprintf(msgp, "(%lu units)\n", warnlevel); else msgp += sprintf(msgp, "(%lu%%)\n", warnlevel); addtobuffer(monmsg, msgline); addalertgroup(group); } else { msgp += sprintf(msgp, "&green %s OK\n", fsname, fsname); addtobuffer(monmsg, msgline); } } xfree(p); } if (!ignored) { addtobuffer(dfstr_filtered, bol); addtobuffer(dfstr_filtered, "\n"); } if (nl) { *nl = '\n'; bol = nl+1; } else bol = NULL; } if ((capacol == -1) && (mntcol == -1)) { /* If this happens, we havent found our headers so no filesystems have been processed */ inodecolor = COL_YELLOW; sprintf(msgline, "&red Expected strings (%s and %s) not found in df output\n", capahdr, mnthdr); addtobuffer(monmsg, msgline); errprintf("Host %s (%s) sent incomprehensible inode report - missing columnheaders '%s' and '%s'\n%s\n", hostname, osname(os), capahdr, mnthdr, dfstr); } /* Check for filesystems that must (not) exist */ while ((iname = check_inode_count(&icount, &imin, &imax, &icolor, &group)) != NULL) { char limtxt[1024]; *limtxt = '\0'; if (imax == -1) { if (imin > 0) sprintf(limtxt, "%d or more", imin); else if (imin == 0) sprintf(limtxt, "none"); } else { if (imin > 0) sprintf(limtxt, "between %d and %d", imin, imax); else if (imin == 0) sprintf(limtxt, "at most %d", imax); } if (icolor != COL_GREEN) { if (icolor > inodecolor) inodecolor = icolor; sprintf(msgline, "&%s Filesystem %s (found %d, req. %s)\n", colorname(icolor), iname, iname, icount, limtxt); addtobuffer(monmsg, msgline); addalertgroup(group); } } /* Now we know the result, so generate a status message */ init_status(inodecolor); group = getalertgroups(); if (group) sprintf(msgline, "status/group:%s ", group); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.inode %s %s - Filesystems %s\n", commafy(hostname), colorname(inodecolor), (timestr ? timestr : ""), ((inodecolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info about what's wrong */ if (STRBUFLEN(monmsg)) { addtostrstatus(monmsg); addtostatus("\n"); } /* And the full df output */ addtostrstatus(dfstr_filtered); if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(monmsg); freestrbuffer(dfstr_filtered); } void unix_memory_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, long memphystotal, long memphysused, long memactused, long memswaptotal, long memswapused) { long memphyspct = 0, memswappct = 0, memactpct = 0; int physyellow, physred, swapyellow, swapred, actyellow, actred; int memorycolor = COL_GREEN, physcolor = COL_GREEN, swapcolor = COL_GREEN, actcolor = COL_GREEN; char *memorysummary = "OK"; char msgline[4096]; if (!want_msgtype(hinfo, MSG_MEMORY)) return; if (memphystotal == -1) return; if (memphysused == -1) return; get_memory_thresholds(hinfo, clientclass, &physyellow, &physred, &swapyellow, &swapred, &actyellow, &actred); memphyspct = (memphystotal > 0) ? ((100 * memphysused) / memphystotal) : 0; if (memphyspct <= 100) { if (memphyspct > physyellow) physcolor = COL_YELLOW; if (memphyspct > physred) physcolor = COL_RED; } if (memswapused != -1) memswappct = (memswaptotal > 0) ? ((100 * memswapused) / memswaptotal) : 0; if (memswappct <= 100) { if (memswappct > swapyellow) swapcolor = COL_YELLOW; if (memswappct > swapred) swapcolor = COL_RED; } if (memactused != -1) memactpct = (memphystotal > 0) ? ((100 * memactused) / memphystotal) : 0; if (memactpct <= 100) { if (memactpct > actyellow) actcolor = COL_YELLOW; if (memactpct > actred) actcolor = COL_RED; } if ((physcolor == COL_YELLOW) || (swapcolor == COL_YELLOW) || (actcolor == COL_YELLOW)) { memorycolor = COL_YELLOW; memorysummary = "low"; } if ((physcolor == COL_RED) || (swapcolor == COL_RED) || (actcolor == COL_RED)) { memorycolor = COL_RED; memorysummary = "CRITICAL"; } if ((memphystotal == 0) && (memorycolor == COL_GREEN)) { memorycolor = COL_YELLOW; memorysummary = "detection FAILED"; } init_status(memorycolor); sprintf(msgline, "status %s.memory %s %s - Memory %s\n", commafy(hostname), colorname(memorycolor), (timestr ? timestr : ""), memorysummary); addtostatus(msgline); sprintf(msgline, " %-12s%12s%12s%12s\n", "Memory", "Used", "Total", "Percentage"); addtostatus(msgline); sprintf(msgline, "&%s %-12s%11ldM%11ldM%11ld%%\n", colorname(physcolor), "Physical", memphysused, memphystotal, memphyspct); addtostatus(msgline); if (memactused != -1) { if (memactpct <= 100) sprintf(msgline, "&%s %-12s%11ldM%11ldM%11ld%%\n", colorname(actcolor), "Actual", memactused, memphystotal, memactpct); else sprintf(msgline, "&%s %-12s%11ldM%11ldM%11ld%% - invalid data\n", colorname(COL_CLEAR), "Actual", memactused, memphystotal, 0L); addtostatus(msgline); } if (memswapused != -1) { if (memswappct <= 100) sprintf(msgline, "&%s %-12s%11ldM%11ldM%11ld%%\n", colorname(swapcolor), "Swap", memswapused, memswaptotal, memswappct); else sprintf(msgline, "&%s %-12s%11ldM%11ldM%11ld%% - invalid data\n", colorname(COL_CLEAR), "Swap", memswapused, memswaptotal, 0L); addtostatus(msgline); } if (fromline && !localmode) addtostatus(fromline); finish_status(); } void unix_procs_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *cmdhdr, char *altcmdhdr, char *psstr) { int pscolor = COL_GREEN; int pchecks; int cmdofs = -1; char *p, *eol; char msgline[4096]; strbuffer_t *monmsg; static strbuffer_t *countdata = NULL; int anycountdata = 0; char *group; if (!want_msgtype(hinfo, MSG_PROCS)) return; if (!psstr) return; if (!countdata) countdata = newstrbuffer(0); clearalertgroups(); monmsg = newstrbuffer(0); sprintf(msgline, "data %s.proccounts\n", commafy(hostname)); addtobuffer(countdata, msgline); /* * Find where the command is located. We look for the header for the command, * and calculate the offset from the beginning of the line. * * NOTE: The header strings could show up in the normal "ps" output. So * we look for it only in the first line of output. */ eol = strchr(psstr, '\n'); if (eol) *eol = '\0'; dbgprintf("Host %s need heading %s or %s - ps header line reads '%s'\n", hostname, cmdhdr, (altcmdhdr ? altcmdhdr : ""), psstr); /* Look for the primary key */ p = strstr(psstr, cmdhdr); if (p) { cmdofs = (p - psstr); dbgprintf("Host %s: Found pri. heading '%s' at offset %d\n", hostname, cmdhdr, cmdofs); } /* If there's a secondary key, look for that also */ if (altcmdhdr) { p = strstr(psstr, altcmdhdr); if (p) { dbgprintf("Host %s: Found sec. heading '%s' at offset %d\n", hostname, altcmdhdr, (p - psstr)); if ((cmdofs == -1) || ((p - psstr) < cmdofs)) { /* We'll use the secondary key */ cmdofs = (p - psstr); } } } if (eol) *eol = '\n'; if (debug) { if (cmdofs >= 0) dbgprintf("Host %s: Found ps command line at offset %d\n", hostname, cmdofs); else dbgprintf("Host %s: None of the headings found\n", hostname); } pchecks = clear_process_counts(hinfo, clientclass); if (pchecks == 0) { /* Nothing to check */ sprintf(msgline, "&%s No process checks defined\n", colorname(noreportcolor)); addtobuffer(monmsg, msgline); pscolor = noreportcolor; } else if (cmdofs >= 0) { /* Count how many instances of each monitored process is running */ char *pname, *pid, *bol, *nl; int pcount, pmin, pmax, pcolor, ptrack; bol = psstr; while (bol) { nl = strchr(bol, '\n'); /* Take care - the ps output line may be shorter than what we look at */ if (nl) { *nl = '\0'; if ((nl-bol) > cmdofs) add_process_count(bol+cmdofs); *nl = '\n'; bol = nl+1; } else { if (strlen(bol) > cmdofs) add_process_count(bol+cmdofs); bol = NULL; } } /* Check the number found for each monitored process */ while ((pname = check_process_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, &group)) != NULL) { char limtxt[1024]; if (pmax == -1) { if (pmin > 0) sprintf(limtxt, "%d or more", pmin); else if (pmin == 0) sprintf(limtxt, "none"); } else { if (pmin > 0) sprintf(limtxt, "between %d and %d", pmin, pmax); else if (pmin == 0) sprintf(limtxt, "at most %d", pmax); } if (pcolor == COL_GREEN) { sprintf(msgline, "&green %s (found %d, req. %s)\n", pname, pcount, limtxt); addtobuffer(monmsg, msgline); } else { if (pcolor > pscolor) pscolor = pcolor; sprintf(msgline, "&%s %s (found %d, req. %s)\n", colorname(pcolor), pname, pcount, limtxt); addtobuffer(monmsg, msgline); addalertgroup(group); } if (ptrack) { /* Save the count data for later DATA message to track process counts */ if (!pid) pid = "default"; sprintf(msgline, "%s:%u\n", pid, pcount); addtobuffer(countdata, msgline); anycountdata = 1; } } } else { pscolor = COL_YELLOW; sprintf(msgline, "&yellow Expected string %s not found in ps output header\n", cmdhdr); addtobuffer(monmsg, msgline); } /* Now we know the result, so generate a status message */ init_status(pscolor); group = getalertgroups(); if (group) sprintf(msgline, "status/group:%s ", group); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.procs %s %s - Processes %s\n", commafy(hostname), colorname(pscolor), (timestr ? timestr : ""), ((pscolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info about what's wrong */ if (STRBUFLEN(monmsg)) { addtostrstatus(monmsg); addtostatus("\n"); } /* And the full ps output for those who want it */ if (pslistinprocs) { /* * NB: Process listings may contain HTML special characters. * We must encode these for HTML, cf. * http://www.w3.org/TR/html4/charset.html#h-5.3.2 */ char *inp, *tagpos; inp = psstr; do { tagpos = inp + strcspn(inp, "<>&\""); switch (*tagpos) { case '<': *tagpos = '\0'; addtostatus(inp); addtostatus("<"); *tagpos = '<'; inp = tagpos + 1; break; case '>': *tagpos = '\0'; addtostatus(inp); addtostatus(">"); *tagpos = '>'; inp = tagpos + 1; break; case '&': *tagpos = '\0'; addtostatus(inp); addtostatus("&"); *tagpos = '&'; inp = tagpos + 1; break; case '\"': *tagpos = '\0'; addtostatus(inp); addtostatus("""); *tagpos = '\"'; inp = tagpos + 1; break; default: /* We're done */ addtostatus(inp); inp = NULL; break; } } while (inp && *inp); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(monmsg); if (anycountdata) sendmessage(STRBUF(countdata), NULL, XYMON_TIMEOUT, NULL); clearstrbuffer(countdata); } static void old_msgs_report(char *hostname, void *hinfo, char *fromline, char *timestr, char *msgsstr) { int msgscolor = COL_GREEN; char msgline[4096]; char *summary = "All logs OK"; if (msgsstr) { if (strstr(msgsstr, "&clear ")) { msgscolor = COL_CLEAR; summary = "No log data available"; } if (strstr(msgsstr, "&yellow ")) { msgscolor = COL_YELLOW; summary = "WARNING"; } if (strstr(msgsstr, "&red ")) { msgscolor = COL_RED; summary = "CRITICAL"; } } else if (sendclearmsgs) { msgscolor = noreportcolor; summary = "No log data available"; } else return; init_status(msgscolor); sprintf(msgline, "status %s.msgs %s System logs at %s : %s\n", commafy(hostname), colorname(msgscolor), (timestr ? timestr : ""), summary); addtostatus(msgline); if (msgsstr) addtostatus(msgsstr); else addtostatus("The client did not report any logfile data\n"); if (fromline && !localmode) addtostatus(fromline); finish_status(); } void msgs_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *msgsstr) { static strbuffer_t *greendata = NULL; static strbuffer_t *yellowdata = NULL; static strbuffer_t *reddata = NULL; sectlist_t *swalk; strbuffer_t *logsummary; int msgscolor = COL_GREEN; char msgline[PATH_MAX]; char *group; if (!want_msgtype(hinfo, MSG_MSGS)) return; for (swalk = defsecthead; (swalk && strncmp(swalk->sname, "msgs:", 5)); swalk = swalk->next) ; if (!swalk) { old_msgs_report(hostname, hinfo, fromline, timestr, msgsstr); return; } if (!greendata) greendata = newstrbuffer(0); if (!yellowdata) yellowdata = newstrbuffer(0); if (!reddata) reddata = newstrbuffer(0); clearalertgroups(); logsummary = newstrbuffer(0); while (swalk) { int logcolor; clearstrbuffer(logsummary); logcolor = scan_log(hinfo, clientclass, swalk->sname+5, swalk->sdata, swalk->sname, logsummary); if (logcolor > msgscolor) msgscolor = logcolor; switch (logcolor) { case COL_GREEN: if (!localmode) { sprintf(msgline, "\nNo entries in %s\n", hostsvcclienturl(hostname, swalk->sname), swalk->sname+5); } else { sprintf(msgline, "\nNo entries in %s\n", swalk->sname+5); } addtobuffer(greendata, msgline); break; case COL_YELLOW: if (!localmode) { sprintf(msgline, "\n&yellow Warnings in %s\n", hostsvcclienturl(hostname, swalk->sname), swalk->sname+5); } else { sprintf(msgline, "\n&yellow Warnings in %s\n", swalk->sname+5); } addtobuffer(yellowdata, msgline); addtobuffer(yellowdata, "
\n");
			addtostrbuffer(yellowdata, logsummary);
			addtobuffer(yellowdata, "
\n"); break; case COL_RED: if (!localmode) { sprintf(msgline, "\n&red Critical entries in %s\n", hostsvcclienturl(hostname, swalk->sname), swalk->sname+5); } else { sprintf(msgline, "\n&red Critical entries in %s\n", swalk->sname+5); } addtobuffer(reddata, msgline); addtobuffer(yellowdata, "
\n");
			addtostrbuffer(reddata, logsummary);
			addtobuffer(yellowdata, "
\n"); break; } do { swalk=swalk->next; } while (swalk && strncmp(swalk->sname, "msgs:", 5)); } freestrbuffer(logsummary); init_status(msgscolor); group = getalertgroups(); if (group) sprintf(msgline, "status/group:%s ", group); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.msgs %s System logs at %s\n", commafy(hostname), colorname(msgscolor), (timestr ? timestr : "")); addtostatus(msgline); if (STRBUFLEN(reddata)) { addtostrstatus(reddata); clearstrbuffer(reddata); addtostatus("\n"); } if (STRBUFLEN(yellowdata)) { addtostrstatus(yellowdata); clearstrbuffer(yellowdata); addtostatus("\n"); } if (STRBUFLEN(greendata)) { addtostrstatus(greendata); clearstrbuffer(greendata); addtostatus("\n"); } /* * Add the full log message data from each logfile. * It's probably faster to re-walk the section list than * stuffing the full messages into a temporary buffer. */ for (swalk = defsecthead; (swalk && strncmp(swalk->sname, "msgs:", 5)); swalk = swalk->next) ; while (swalk) { if (!localmode) { sprintf(msgline, "\nFull log %s\n", hostsvcclienturl(hostname, swalk->sname), swalk->sname+5); } else { sprintf(msgline, "\nFull log %s\n", swalk->sname+5); } addtostatus(msgline); addtobuffer(yellowdata, "
\n");
		addtostatus(swalk->sdata);
		addtobuffer(yellowdata, "
\n"); do { swalk=swalk->next; } while (swalk && strncmp(swalk->sname, "msgs:", 5)); } if (fromline && !localmode) addtostatus(fromline); finish_status(); } void file_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr) { static strbuffer_t *greendata = NULL; static strbuffer_t *yellowdata = NULL; static strbuffer_t *reddata = NULL; static strbuffer_t *sizedata = NULL; sectlist_t *swalk; strbuffer_t *filesummary; int filecolor = -1, onecolor; char msgline[PATH_MAX]; char sectionname[PATH_MAX]; int anyszdata = 0; char *group; if (!want_msgtype(hinfo, MSG_FILES)) return; if (!greendata) greendata = newstrbuffer(0); if (!yellowdata) yellowdata = newstrbuffer(0); if (!reddata) reddata = newstrbuffer(0); if (!sizedata) sizedata = newstrbuffer(0); filesummary = newstrbuffer(0); clearalertgroups(); sprintf(msgline, "data %s.filesizes\n", commafy(hostname)); addtobuffer(sizedata, msgline); for (swalk = defsecthead; (swalk); swalk = swalk->next) { int trackit, anyrules; char *sfn = NULL; char *id = NULL; if (strncmp(swalk->sname, "file:", 5) == 0) { off_t sz; sfn = swalk->sname+5; sprintf(sectionname, "file:%s", sfn); onecolor = check_file(hinfo, clientclass, sfn, swalk->sdata, sectionname, filesummary, &sz, &id, &trackit, &anyrules); if (trackit) { /* Save the size data for later DATA message to track file sizes */ if (id == NULL) id = sfn; #ifdef _LARGEFILE_SOURCE sprintf(msgline, "%s:%lld\n", nocolon(id), (long long int)sz); #else sprintf(msgline, "%s:%ld\n", nocolon(id), (long int)sz); #endif addtobuffer(sizedata, msgline); anyszdata = 1; } } else if (strncmp(swalk->sname, "logfile:", 8) == 0) { off_t sz; sfn = swalk->sname+8; sprintf(sectionname, "logfile:%s", sfn); onecolor = check_file(hinfo, clientclass, sfn, swalk->sdata, sectionname, filesummary, &sz, &id, &trackit, &anyrules); if (trackit) { /* Save the size data for later DATA message to track file sizes */ if (id == NULL) id = sfn; #ifdef _LARGEFILE_SOURCE sprintf(msgline, "%s:%lld\n", nocolon(id), (long long int)sz); #else sprintf(msgline, "%s:%ld\n", nocolon(id), (long int)sz); #endif addtobuffer(sizedata, msgline); anyszdata = 1; } if (!anyrules) { /* Dont clutter the display with logfiles unless they have rules */ continue; } } else if (strncmp(swalk->sname, "dir:", 4) == 0) { unsigned long sz; sfn = swalk->sname+4; sprintf(sectionname, "dir:%s", sfn); onecolor = check_dir(hinfo, clientclass, sfn, swalk->sdata, sectionname, filesummary, &sz, &id, &trackit); if (trackit) { /* Save the size data for later DATA message to track directory sizes */ if (id == NULL) id = sfn; sprintf(msgline, "%s:%lu\n", nocolon(id), sz); addtobuffer(sizedata, msgline); anyszdata = 1; } } else continue; if (onecolor > filecolor) filecolor = onecolor; switch (onecolor) { case COL_GREEN: if (!localmode) { sprintf(msgline, "\n&green %s\n", hostsvcclienturl(hostname, sectionname), sfn); } else { sprintf(msgline, "\n&green %s\n", sfn); } addtobuffer(greendata, msgline); break; case COL_YELLOW: if (!localmode) { sprintf(msgline, "\n&yellow %s\n", hostsvcclienturl(hostname, sectionname), sfn); } else { sprintf(msgline, "\n&yellow %s\n", sfn); } addtobuffer(yellowdata, msgline); addtostrbuffer(yellowdata, filesummary); break; case COL_RED: if (!localmode) { sprintf(msgline, "\n&red %s\n", hostsvcclienturl(hostname, sectionname), sfn); } else { sprintf(msgline, "\n&red %s\n", sfn); } addtobuffer(reddata, msgline); addtostrbuffer(reddata, filesummary); break; } clearstrbuffer(filesummary); } freestrbuffer(filesummary); if ((filecolor == -1) && sendclearfiles) { filecolor = noreportcolor; addtobuffer(greendata, "No files checked\n"); } if (filecolor != -1) { init_status(filecolor); group = getalertgroups(); if (group) sprintf(msgline, "status/group:%s ", group); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.files %s Files status at %s\n", commafy(hostname), colorname(filecolor), (timestr ? timestr : "")); addtostatus(msgline); if (STRBUFLEN(reddata)) { addtostrstatus(reddata); clearstrbuffer(reddata); addtostatus("\n"); } if (STRBUFLEN(yellowdata)) { addtostrstatus(yellowdata); clearstrbuffer(yellowdata); addtostatus("\n"); } if (STRBUFLEN(greendata)) { addtostrstatus(greendata); clearstrbuffer(greendata); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); } else { clearstrbuffer(reddata); clearstrbuffer(yellowdata); clearstrbuffer(greendata); } if (anyszdata) sendmessage(STRBUF(sizedata), NULL, XYMON_TIMEOUT, NULL); clearstrbuffer(sizedata); } void linecount_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr) { static strbuffer_t *countdata = NULL; sectlist_t *swalk; char msgline[PATH_MAX]; int anydata = 0; if (!countdata) countdata = newstrbuffer(0); sprintf(msgline, "data %s.linecounts\n", commafy(hostname)); addtobuffer(countdata, msgline); for (swalk = defsecthead; (swalk); swalk = swalk->next) { if (strncmp(swalk->sname, "linecount:", 10) == 0) { char *fn, *boln, *eoln, *id, *countstr; anydata = 1; fn = strchr(swalk->sname, ':'); fn += 1 + strspn(fn+1, "\t "); boln = swalk->sdata; while (boln) { eoln = strchr(boln, '\n'); id = strtok(boln, ":"); countstr = (id ? strtok(NULL, "\n") : NULL); if (id && countstr) { countstr += strspn(countstr, "\t "); sprintf(msgline, "%s#%s:%s\n", nocolon(fn), id, countstr); addtobuffer(countdata, msgline); } boln = (eoln ? eoln + 1 : NULL); } } } if (anydata) sendmessage(STRBUF(countdata), NULL, XYMON_TIMEOUT, NULL); clearstrbuffer(countdata); } void unix_netstat_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *netstatstr) { strbuffer_t *msg; char msgline[4096]; if (!netstatstr) return; msg = newstrbuffer(0); sprintf(msgline, "data %s.netstat\n%s\n", commafy(hostname), osname(os)); addtobuffer(msg, msgline); addtobuffer(msg, netstatstr); sendmessage(STRBUF(msg), NULL, XYMON_TIMEOUT, NULL); freestrbuffer(msg); } void unix_ifstat_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *ifstatstr) { strbuffer_t *msg; char msgline[4096]; if (!ifstatstr) return; msg = newstrbuffer(0); sprintf(msgline, "data %s.ifstat\n%s\n", commafy(hostname), osname(os)); addtobuffer(msg, msgline); addtobuffer(msg, ifstatstr); sendmessage(STRBUF(msg), NULL, XYMON_TIMEOUT, NULL); freestrbuffer(msg); } void unix_vmstat_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *vmstatstr) { strbuffer_t *msg; char msgline[4096]; char *p; if (!vmstatstr) return; p = strrchr(vmstatstr, '\n'); if (!p) return; /* No NL in vmstat output ? Unlikely. */ msg = newstrbuffer(0); if (strlen(p) == 1) { /* Go back to the previous line */ do { p--; } while ((p > vmstatstr) && (*p != '\n')); } sprintf(msgline, "data %s.vmstat\n%s\n", commafy(hostname), osname(os)); addtobuffer(msg, msgline); addtobuffer(msg, p+1); sendmessage(STRBUF(msg), NULL, XYMON_TIMEOUT, NULL); freestrbuffer(msg); } void unix_ports_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, int localcol, int remotecol, int statecol, char *portstr) { int portcolor = -1; int pchecks; char msgline[4096]; static strbuffer_t *monmsg = NULL; static strbuffer_t *countdata = NULL; int anycountdata = 0; char *group; if (!want_msgtype(hinfo, MSG_PORTS)) return; if (!portstr) return; if (!monmsg) monmsg = newstrbuffer(0); if (!countdata) countdata = newstrbuffer(0); clearalertgroups(); pchecks = clear_port_counts(hinfo, clientclass); sprintf(msgline, "data %s.portcounts\n", commafy(hostname)); addtobuffer(countdata, msgline); if (pchecks > 0) { /* Count how many instances of each monitored condition are found */ char *pname, *pid, *bol, *nl; int pcount, pmin, pmax, pcolor, ptrack; char *localstr, *remotestr, *statestr; bol = portstr; while (bol) { char *p; nl = strchr(bol, '\n'); if (nl) *nl = '\0'; /* Data lines */ p = strdup(bol); localstr = getcolumn(p, localcol); strcpy(p, bol); remotestr = getcolumn(p, remotecol); strcpy(p, bol); statestr = getcolumn(p, statecol); add_port_count(localstr, remotestr, statestr); xfree(p); if (nl) { *nl = '\n'; bol = nl+1; } else bol = NULL; } /* Check the number found for each monitored port */ while ((pname = check_port_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, &group)) != NULL) { char limtxt[1024]; *limtxt = '\0'; if (pmax == -1) { if (pmin > 0) sprintf(limtxt, "%d or more", pmin); else if (pmin == 0) sprintf(limtxt, "none"); } else { if (pmin > 0) sprintf(limtxt, "between %d and %d", pmin, pmax); else if (pmin == 0) sprintf(limtxt, "at most %d", pmax); } if (pcolor > portcolor) portcolor = pcolor; if (pcolor == COL_GREEN) { sprintf(msgline, "&green %s (found %d, req. %s)\n", pname, pcount, limtxt); addtobuffer(monmsg, msgline); } else { sprintf(msgline, "&%s %s (found %d, req. %s)\n", colorname(pcolor), pname, pcount, limtxt); addtobuffer(monmsg, msgline); addalertgroup(group); } if (ptrack) { /* Save the size data for later DATA message to track port counts */ if (!pid) pid = "default"; sprintf(msgline, "%s:%u\n", pid, pcount); addtobuffer(countdata, msgline); anycountdata = 1; } } } if ((portcolor == -1) && sendclearports) { /* Nothing to check */ addtobuffer(monmsg, "No port checks defined\n"); portcolor = noreportcolor; } if (portcolor != -1) { /* Now we know the result, so generate a status message */ init_status(portcolor); group = getalertgroups(); if (group) sprintf(msgline, "status/group:%s ", group); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.ports %s %s - Ports %s\n", commafy(hostname), colorname(portcolor), (timestr ? timestr : ""), ((portcolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info about what's wrong */ addtostrstatus(monmsg); addtostatus("\n"); clearstrbuffer(monmsg); /* And the full port output for those who want it */ if (portlistinports) addtostatus(portstr); if (fromline) addtostatus(fromline); finish_status(); } else { clearstrbuffer(monmsg); } if (anycountdata) sendmessage(STRBUF(countdata), NULL, XYMON_TIMEOUT, NULL); clearstrbuffer(countdata); } #include "client/linux.c" #include "client/freebsd.c" #include "client/netbsd.c" #include "client/openbsd.c" #include "client/solaris.c" #include "client/hpux.c" #include "client/osf.c" #include "client/aix.c" #include "client/darwin.c" #include "client/irix.c" #include "client/sco_sv.c" #include "client/bbwin.c" #include "client/powershell.c" /* Must go after client/bbwin.c */ #include "client/zvm.c" #include "client/zvse.c" #include "client/zos.c" #include "client/mqcollect.c" #include "client/snmpcollect.c" static volatile int reloadconfig = 0; void sig_handler(int signum) { switch (signum) { case SIGHUP: reloadconfig = 1; break; default: break; } } void clean_instr(char *s) { char *p; p = s + strcspn(s, "\r\n"); *p = '\0'; } void testmode(char *configfn) { void *hinfo, *oldhinfo = NULL; char hostname[1024], clientclass[1024]; char s[4096]; int cfid; load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); load_client_config(configfn); *hostname = '\0'; *clientclass = '\0'; while (1) { hinfo = NULL; while (!hinfo) { printf("Hostname (.=end, ?=dump, !=reload) [%s]: ", hostname); fflush(stdout); fgets(hostname, sizeof(hostname), stdin); clean_instr(hostname); if (strlen(hostname) == 0) { hinfo = oldhinfo; if (hinfo) strcpy(hostname, xmh_item(hinfo, XMH_HOSTNAME)); } else if (strcmp(hostname, ".") == 0) { exit(0); } else if (strcmp(hostname, "!") == 0) { load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); load_client_config(configfn); *hostname = '\0'; } else if (strcmp(hostname, "?") == 0) { dump_client_config(); if (oldhinfo) strcpy(hostname, xmh_item(oldhinfo, XMH_HOSTNAME)); } else { hinfo = hostinfo(hostname); if (!hinfo) printf("Unknown host\n"); printf("Hosttype [%s]: ", clientclass); fflush(stdout); fgets(clientclass, sizeof(clientclass), stdin); clean_instr(clientclass); } } oldhinfo = hinfo; printf("Test (cpu, mem, disk, proc, log, port): "); fflush(stdout); fgets(s, sizeof(s), stdin); clean_instr(s); if (strcmp(s, "cpu") == 0) { float loadyellow, loadred; int recentlimit, ancientlimit, uptimecolor; int maxclockdiff, clockdiffcolor; cfid = get_cpu_thresholds(hinfo, clientclass, &loadyellow, &loadred, &recentlimit, &ancientlimit, &uptimecolor, &maxclockdiff, &clockdiffcolor); printf("Load: Yellow at %.2f, red at %.2f\n", loadyellow, loadred); printf("Uptime: %s from boot until %s,", colorname(uptimecolor), durationstring(recentlimit)); printf("and after %s uptime\n", durationstring(ancientlimit)); if (maxclockdiff > 0) printf("Max clock diff: %d (%s)\n", maxclockdiff, colorname(clockdiffcolor)); } else if (strcmp(s, "mem") == 0) { int physyellow, physred, swapyellow, swapred, actyellow, actred; get_memory_thresholds(hinfo, clientclass, &physyellow, &physred, &swapyellow, &swapred, &actyellow, &actred); printf("Phys: Yellow at %d, red at %d\n", physyellow, physred); printf("Swap: Yellow at %d, red at %d\n", swapyellow, swapred); printf("Act.: Yellow at %d, red at %d\n", actyellow, actred); } else if (strcmp(s, "disk") == 0) { unsigned long warnlevel, paniclevel; int abswarn, abspanic, ignored; char *groups; printf("Filesystem: "); fflush(stdout); fgets(s, sizeof(s), stdin); clean_instr(s); cfid = get_disk_thresholds(hinfo, clientclass, s, &warnlevel, &paniclevel, &abswarn, &abspanic, &ignored, &groups); if (ignored) printf("Ignored\n"); else printf("Yellow at %lu%c, red at %lu%c\n", warnlevel, (abswarn ? 'U' : '%'), paniclevel, (abspanic ? 'U' : '%')); } else if (strcmp(s, "proc") == 0) { int pchecks = clear_process_counts(hinfo, clientclass); char *pname, *pid; int pcount, pmin, pmax, pcolor, ptrack; char *groups; FILE *fd; if (pchecks == 0) { printf("No process checks for this host\n"); continue; } printf("To read 'ps' data from a file, enter '@FILENAME' at the prompt\n"); do { printf("ps command string: "); fflush(stdout); fgets(s, sizeof(s), stdin); clean_instr(s); if (*s == '@') { fd = fopen(s+1, "r"); while (fd && fgets(s, sizeof(s), fd)) { clean_instr(s); if (*s) add_process_count(s); } fclose(fd); } else { if (*s) add_process_count(s); } } while (*s); while ((pname = check_process_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, &groups)) != NULL) { printf("Process %s color %s: Count=%d, min=%d, max=%d\n", pname, colorname(pcolor), pcount, pmin, pmax); } } else if (strcmp(s, "log") == 0) { FILE *fd; char *sectname; strbuffer_t *logdata, *logsummary; int logcolor; printf("log filename: "); fflush(stdout); fgets(s, sizeof(s), stdin); clean_instr(s); sectname = (char *)malloc(strlen(s) + 20); sprintf(sectname, "msgs:%s", s); logdata = newstrbuffer(0); logsummary = newstrbuffer(0); printf("To read log data from a file, enter '@FILENAME' at the prompt\n"); do { printf("log line: "); fflush(stdout); fgets(s, sizeof(s), stdin); clean_instr(s); if (*s == '@') { fd = fopen(s+1, "r"); while (fd && fgets(s, sizeof(s), fd)) { if (*s) addtobuffer(logdata, s); } fclose(fd); } else { if (*s) addtobuffer(logdata, s); } } while (*s); clearstrbuffer(logsummary); logcolor = scan_log(hinfo, clientclass, sectname+5, STRBUF(logdata), sectname, logsummary); printf("Log status is %s\n\n", colorname(logcolor)); if (STRBUFLEN(logsummary)) printf("%s\n", STRBUF(logsummary)); freestrbuffer(logsummary); freestrbuffer(logdata); } else if (strcmp(s, "port") == 0) { char *localstr, *remotestr, *statestr, *p, *pname, *pid; int pcount, pmin, pmax, pcolor, pchecks, ptrack; char *groups; int localcol = 4, remotecol = 5, statecol = 6, portcolor = COL_GREEN; pchecks = clear_port_counts(hinfo, clientclass); if (pchecks == 0) { printf("No PORT checks for this host\n"); continue; } printf("Need to know netstat columns for 'Local address', 'Remote address' and 'State'\n"); printf("Enter columns [%d %d %d]: ", localcol, remotecol, statecol); fflush(stdout); fgets(s, sizeof(s), stdin); clean_instr(s); if (*s) sscanf(s, "%d %d %d", &localcol, &remotecol, &statecol); printf("To read 'netstat' data from a file, enter '@FILENAME' at the prompt\n"); do { printf("netstat line: "); fflush(stdout); fgets(s, sizeof(s), stdin); clean_instr(s); if (*s == '@') { FILE *fd; fd = fopen(s+1, "r"); while (fd && fgets(s, sizeof(s), fd)) { clean_instr(s); if (*s) { p = strdup(s); localstr = getcolumn(p, localcol-1); strcpy(p, s); remotestr = getcolumn(p, remotecol-1); strcpy(p, s); statestr = getcolumn(p, statecol-1); add_port_count(localstr, remotestr, statestr); xfree(p); } } fclose(fd); } else if (*s) { p = strdup(s); localstr = getcolumn(p, localcol-1); strcpy(p, s); remotestr = getcolumn(p, remotecol-1); strcpy(p, s); statestr = getcolumn(p, statecol-1); add_port_count(localstr, remotestr, statestr); xfree(p); } } while (*s); /* Check the number found for each monitored port */ while ((pname = check_port_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, &groups)) != NULL) { char limtxt[1024]; if (pmax == -1) { if (pmin > 0) sprintf(limtxt, "%d or more", pmin); else if (pmin == 0) sprintf(limtxt, "none"); } else { if (pmin > 0) sprintf(limtxt, "between %d and %d", pmin, pmax); else if (pmin == 0) sprintf(limtxt, "at most %d", pmax); } if (pcolor == COL_GREEN) { printf("&green %s (found %d, req. %s)\n", pname, pcount, limtxt); } else { if (pcolor > portcolor) portcolor = pcolor; printf("&%s %s (found %d, req. %s)\n", colorname(pcolor), pname, pcount, limtxt); } } } } exit(0); } int main(int argc, char *argv[]) { char *msg; int running; int argi, seq; struct sigaction sa; time_t nextconfigload = 0; char *configfn = NULL; char **collectors = NULL; /* Handle program options. */ for (argi = 1; (argi < argc); argi++) { if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (strcmp(argv[argi], "--no-update") == 0) { dontsendmessages = 1; } else if (strcmp(argv[argi], "--no-ps-listing") == 0) { pslistinprocs = 0; } else if (strcmp(argv[argi], "--no-port-listing") == 0) { portlistinports = 0; } else if (strcmp(argv[argi], "--no-clear-msgs") == 0) { sendclearmsgs = 0; } else if (strcmp(argv[argi], "--no-clear-files") == 0) { sendclearfiles = 0; } else if (strcmp(argv[argi], "--no-clear-ports") == 0) { sendclearports = 0; } else if (strncmp(argv[argi], "--clear-color=", 14) == 0) { char *p = strchr(argv[argi], '='); noreportcolor = parse_color(p+1); } else if (argnmatch(argv[argi], "--config=")) { char *lp = strchr(argv[argi], '='); configfn = strdup(lp+1); } else if (argnmatch(argv[argi], "--collectors=")) { char *lp = strdup(strchr(argv[argi], '=')+1); char *tok; int i; tok = strtok(lp, ","); i = 0; collectors = (char **)calloc(1, sizeof(char *)); while (tok) { collectors = (char **)realloc(collectors, (i+2)*sizeof(char *)); if (strcasecmp(tok, "default") == 0) tok = ""; collectors[i++] = tok; collectors[i] = NULL; tok = strtok(NULL, ","); } } else if (argnmatch(argv[argi], "--dump-config")) { load_client_config(configfn); dump_client_config(); return 0; } else if (strcmp(argv[argi], "--local") == 0) { localmode = 1; } else if (strcmp(argv[argi], "--test") == 0) { testmode(configfn); } else if (net_worker_option(argv[argi])) { /* Handled in the subroutine */ } } save_errbuf = 0; if (collectors == NULL) { /* Setup the default collectors */ collectors = (char **)calloc(2, sizeof(char *)); collectors[0] = ""; collectors[1] = NULL; } /* Do the network stuff if needed */ net_worker_run(ST_CLIENT, LOC_ROAMING, NULL); /* Signals */ setup_signalhandler("xymond_client"); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; sigaction(SIGHUP, &sa, NULL); signal(SIGCHLD, SIG_IGN); updinfotree = xtreeNew(strcasecmp); running = 1; while (running) { char *eoln, *restofmsg, *p; char *metadata[MAX_META+1]; int metacount; time_t nowtimer = gettimer(); msg = get_xymond_message(C_CLIENT, argv[0], &seq, NULL); if (msg == NULL) { if (!localmode) errprintf("Failed to get a message, terminating\n"); running = 0; continue; } if (reloadconfig || (nowtimer >= nextconfigload)) { nextconfigload = nowtimer + 600; reloadconfig = 0; if (!localmode) load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); load_client_config(configfn); } /* Split the message in the first line (with meta-data), and the rest */ eoln = strchr(msg, '\n'); if (eoln) { *eoln = '\0'; restofmsg = eoln+1; } else { restofmsg = ""; } metacount = 0; memset(&metadata, 0, sizeof(metadata)); p = gettok(msg, "|"); while (p && (metacount < MAX_META)) { metadata[metacount++] = p; p = gettok(NULL, "|"); } metadata[metacount] = NULL; if ((metacount > 4) && (strncmp(metadata[0], "@@client", 8) == 0)) { int cnum, havecollector; time_t timestamp = atoi(metadata[1]); char *sender = metadata[2]; char *hostname = metadata[3]; char *clientos = metadata[4]; char *clientclass = metadata[5]; char *collectorid = metadata[6]; enum ostype_t os; void *hinfo = NULL; dbgprintf("Client report from host %s\n", (hostname ? hostname : "")); /* Check if we are running a collector module for this type of client */ if (!collectorid) collectorid = ""; for (cnum = 0, havecollector = 0; (collectors[cnum] && !havecollector); cnum++) havecollector = (strcmp(collectorid, collectors[cnum]) == 0); if (!havecollector) continue; hinfo = (localmode ? localhostinfo(hostname) : hostinfo(hostname)); if (!hinfo) continue; os = get_ostype(clientos); /* Default clientclass to the OS name */ if (!clientclass || (*clientclass == '\0')) clientclass = clientos; /* Check for duplicates */ if (add_updateinfo(hostname, seq, timestamp) != 0) continue; combo_start(); switch (os) { case OS_FREEBSD: handle_freebsd_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_NETBSD: handle_netbsd_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_OPENBSD: handle_openbsd_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_LINUX22: case OS_LINUX: case OS_RHEL3: handle_linux_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_DARWIN: handle_darwin_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_SOLARIS: handle_solaris_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_HPUX: handle_hpux_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_OSF: handle_osf_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_AIX: handle_aix_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_IRIX: handle_irix_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_SCO_SV: handle_sco_sv_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_WIN32_BBWIN: handle_win32_bbwin_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_WIN_POWERSHELL: handle_powershell_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_ZVM: handle_zvm_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_ZVSE: handle_zvse_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_ZOS: handle_zos_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_SNMPCOLLECT: handle_snmpcollect_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; case OS_MQCOLLECT: handle_mqcollect_client(hostname, clientclass, os, hinfo, sender, timestamp, restofmsg); break; default: errprintf("No client backend for OS '%s' sent by %s\n", clientos, sender); break; } combo_end(); } else if (strncmp(metadata[0], "@@shutdown", 10) == 0) { printf("Shutting down\n"); running = 0; continue; } else if (strncmp(metadata[0], "@@logrotate", 11) == 0) { char *fn = xgetenv("XYMONCHANNEL_LOGFILENAME"); if (fn && strlen(fn)) { freopen(fn, "a", stdout); freopen(fn, "a", stderr); } continue; } else if (strncmp(metadata[0], "@@reload", 8) == 0) { reloadconfig = 1; } else { /* Unknown message - ignore it */ } } return 0; } xymon-4.3.7/xymond/xymond_history.80000664000175000017500000000535611671641417016752 0ustar henrikhenrik.TH XYMOND_HISTORY 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymond_history \- xymond worker module for logging status changes .SH SYNOPSIS .B "xymond_channel --channel=stachg xymond_history [options]" .SH DESCRIPTION xymond_history is a worker module for xymond, and as such it is normally run via the .I xymond_channel(8) program. It receives xymond status-change messages from the "stachg" channel via stdin, and uses these to update the history logfiles in a manner that is compatible with the standard Big Brother daemon, bbd. .SH OPTIONS .IP "--histdir=DIRECTORY" The directory for the history files. If not specified, the directory given by the XYMONHISTDIR environment is used. .IP "--histlogdir=DIRECTORY" The directory for the historical status-logs. If not specified, the directory given by the XYMONHISTLOGS environment is used. .IP "--minimum-free=N" Sets the minimum percentage of free filesystem space on the $XYMONHISTLOGS directory. If there is less than N% free space, xymond_history will not save the detailed status-logs. Default: 5 .IP "--pidfile=FILENAME" xymond_history writes the process-ID it is running with to this file. This is for use in automated startup scripts. The default file is $XYMONSERVERLOGS/xymond_history.pid. .IP "--debug" Enable debugging output. .SH ENVIRONMENT .IP XYMONALLHISTLOG This environment variable controls if the $XYMONHISTDIR/allevents logfile is updated. This file is used by the event-log display on the nongreen html page and the eventlog-webpage, among other things. You can disable it by setting XYMONALLHISTLOGS=FALSE, but this is not recommended. .IP XYMONHOSTHISTLOG This environment variable controls if the $XYMONHISTDIR/HOSTNAME logfile is updated. This file holds a list of all status changes seen for a single host, but is not used by any of the standard Xymon tools. If you do not want to save this, you can disable it by setting XYMONHOSTHISTLOG=FALSE. .IP SAVESTATUSLOG This environment variable controls if the historical status-logs are saved whenever a status change occurs. These logfiles are stored in the $XYMONHISTLOGS directory, and are used for the detailed log-display of a status from the Xymon "History" page. If you do not want to save these, you can disable it by setting SAVESTATUSLOG=FALSE. If you want to save all except some specific logs, use SAVESTATUSLOG=TRUE,!TEST1[,!TEST2...] If you want to save none except some specific logs, use SAVESTATUSLOG=FALSE,TEST1[,TEST2...] .br NOTE: Status logs will not be saved if there is less than 5% free space on the filesystem hosting the $XYMONHISTLOGS directory. The threshold can be tuned via the "--minimum-free" option. .SH FILES This module does not rely on any configuration files. .SH "SEE ALSO" xymond_channel(8), xymond(8), xymon(7) xymon-4.3.7/xymond/xymon.sh.DIST0000664000175000017500000000363311535462534016026 0ustar henrikhenrik#!/bin/sh # Startup script for Xymon # # This starts the "xymonlaunch" tool, which in turn starts # all of the other Xymon server programs. case "`uname -s`" in "SunOS") ID=/usr/xpg4/bin/id ;; *) ID=id ;; esac if test `$ID -un` != @XYMONUSER@ then echo "Xymon must be started as the @XYMONUSER@ user" exit 1 fi case "$1" in "start") if test -s @XYMONLOGDIR@/xymonlaunch.pid then kill -0 `cat @XYMONLOGDIR@/xymonlaunch.pid` if test $? -eq 0 then echo "Xymon appears to be running, doing restart" $0 stop else rm -f @XYMONLOGDIR@/xymonlaunch.pid fi fi @RUNTIMEDEFS@ @XYMONHOME@/bin/xymonlaunch --config=@XYMONHOME@/etc/tasks.cfg --env=@XYMONHOME@/etc/xymonserver.cfg --log=@XYMONLOGDIR@/xymonlaunch.log --pidfile=@XYMONLOGDIR@/xymonlaunch.pid echo "Xymon started" ;; "stop") if test -s @XYMONLOGDIR@/xymonlaunch.pid then kill -TERM `cat @XYMONLOGDIR@/xymonlaunch.pid` echo "Xymon stopped" else echo "Xymon is not running" fi rm -f @XYMONLOGDIR@/xymonlaunch.pid ;; "status") if test -s @XYMONLOGDIR@/xymonlaunch.pid then kill -0 `cat @XYMONLOGDIR@/xymonlaunch.pid` if test $? -eq 0 then echo "Xymon (xymonlaunch) running with PID `cat @XYMONLOGDIR@/xymonlaunch.pid`" else echo "Xymon not running, removing stale PID file" rm -f @XYMONLOGDIR@/xymonlaunch.pid fi else echo "Xymon (xymonlaunch) does not appear to be running" fi ;; "restart") if test -s @XYMONLOGDIR@/xymonlaunch.pid then $0 stop sleep 10 $0 start else echo "xymonlaunch does not appear to be running, starting it" $0 start fi ;; "reload") if test -s @XYMONLOGDIR@/xymond.pid then kill -HUP `cat @XYMONLOGDIR@/xymond.pid` else echo "xymond not running (no PID file)" fi ;; "rotate") for PIDFILE in @XYMONLOGDIR@/*.pid do kill -HUP `cat $PIDFILE` done ;; *) echo "Usage: $0 start|stop|restart|reload|status|rotate" break; esac exit 0 xymon-4.3.7/xymond/do_rrd.c0000664000175000017500000006437111665400106015170 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: do_rrd.c 6779 2011-11-30 10:07:02Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymond_rrd.h" #include "do_rrd.h" #include "client_config.h" #ifndef NAME_MAX #define NAME_MAX 255 /* Solaris doesn't define NAME_MAX, but ufs limit is 255 */ #endif extern int seq; /* from xymond_rrd.c */ char *rrddir = NULL; int use_rrd_cache = 1; /* Use the cache by default */ static int processorfd = 0; static FILE *processorstream = NULL; static char *exthandler = NULL; static char **extids = NULL; static char rrdvalues[MAX_LINE_LEN]; static char *senderip = NULL; static char rrdfn[PATH_MAX]; /* Base filename without directories, from setupfn() */ static char filedir[PATH_MAX]; /* Full path filename */ static char *fnparams[4] = { NULL, }; /* Saved parameters passed to setupfn() */ /* How often do we feed data into the RRD file */ #define DEFAULT_RRD_INTERVAL 300 static int rrdinterval = DEFAULT_RRD_INTERVAL; #define CACHESZ 12 /* # of updates that can be cached - updates are usually 5 minutes apart */ static int updcache_keyofs = -1; static void * updcache; typedef struct updcacheitem_t { char *key; rrdtpldata_t *tpl; int valcount; char *vals[CACHESZ]; int updseq[CACHESZ]; time_t updtime[CACHESZ]; } updcacheitem_t; static void * flushtree; static int have_flushtree = 0; typedef struct flushtree_t { char *hostname; time_t flushtime; } flushtree_t; void setup_exthandler(char *handlerpath, char *ids) { char *p; int idcount = 0; MEMDEFINE(rrdvalues); exthandler = strdup(handlerpath); idcount=1; p = ids; while ((p = strchr(p, ',')) != NULL) { p++; idcount++; } extids = (char **)malloc((idcount+1)*(sizeof(char *))); idcount = 0; p = strtok(ids, ","); while (p) { extids[idcount++] = strdup(p); p = strtok(NULL, ","); } extids[idcount] = NULL; MEMUNDEFINE(rrdvalues); } void setup_extprocessor(char *cmd) { int n; int pfd[2]; pid_t childpid; if (!cmd) return; processorfd = 0; n = pipe(pfd); if (n == -1) { errprintf("Could not get a pipe: %s\n", strerror(errno)); } else { childpid = fork(); if (childpid == -1) { errprintf("Could not fork channel handler: %s\n", strerror(errno)); } else if (childpid == 0) { /* The channel handler child */ char *argv[2]; argv[0] = strdup(cmd); argv[1] = NULL; n = dup2(pfd[0], STDIN_FILENO); close(pfd[0]); close(pfd[1]); n = execvp(cmd, argv); /* We should never go here */ errprintf("exec() failed for child command %s: %s\n", cmd, strerror(errno)); exit(1); } else { /* Parent process continues */ close(pfd[0]); processorfd = pfd[1]; processorstream = fdopen(processorfd, "w"); errprintf("External processor '%s' started\n", cmd); } } } void shutdown_extprocessor(void) { if (!processorfd) return; close(processorfd); processorfd = 0; processorstream = NULL; errprintf("External processor stopped\n"); } static void setupfn(char *format, char *param) { char *p; memset(fnparams, 0, sizeof(fnparams)); fnparams[0] = param; snprintf(rrdfn, sizeof(rrdfn)-1, format, param); rrdfn[sizeof(rrdfn)-1] = '\0'; while ((p = strchr(rrdfn, ' ')) != NULL) *p = '_'; } static void setupfn2(char *format, char *param1, char *param2) { char *p; while ((p = strchr(param2, '/')) != NULL) *p = ','; memset(fnparams, 0, sizeof(fnparams)); fnparams[0] = param1; fnparams[1] = param2; snprintf(rrdfn, sizeof(rrdfn)-1, format, param1, param2); rrdfn[sizeof(rrdfn)-1] = '\0'; while ((p = strchr(rrdfn, ' ')) != NULL) *p = '_'; } static void setupfn3(char *format, char *param1, char *param2, char *param3) { char *p; memset(fnparams, 0, sizeof(fnparams)); fnparams[0] = param1; fnparams[1] = param2; fnparams[2] = param3; snprintf(rrdfn, sizeof(rrdfn)-1, format, param1, param2, param3); rrdfn[sizeof(rrdfn)-1] = '\0'; while ((p = strchr(rrdfn, ' ')) != NULL) *p = '_'; if (strlen(rrdfn) >= (NAME_MAX - 50)) { /* * Filename is too long. Limit filename length * by replacing the last part of the filename * with an MD5 hash. */ char *hash = md5hash(rrdfn+(NAME_MAX-50)); sprintf(rrdfn+(NAME_MAX-50), "_%s.rrd", hash); } } static void setupinterval(int intvl) { rrdinterval = (intvl ? intvl : DEFAULT_RRD_INTERVAL); } static int flush_cached_updates(updcacheitem_t *cacheitem, char *newdata) { /* Flush any updates we've cached */ char *updparams[5+CACHESZ+1] = { "rrdupdate", filedir, "-t", NULL, NULL, NULL, }; int i, pcount, result; dbgprintf("Flushing '%s' with %d updates pending, template '%s'\n", cacheitem->key, (newdata ? 1 : 0) + cacheitem->valcount, cacheitem->tpl->template); /* ISO C90: parameters cannot be used as initializers */ updparams[3] = cacheitem->tpl->template; /* Setup the parameter list with all of the cached and new readings */ for (i=0; (i < cacheitem->valcount); i++) updparams[4+i] = cacheitem->vals[i]; if (newdata) { updparams[4+cacheitem->valcount] = newdata; updparams[4+cacheitem->valcount+1] = NULL; } else { /* No new data - happens when flushing the cache */ updparams[4+cacheitem->valcount] = NULL; } for (pcount = 0; (updparams[pcount]); pcount++); optind = opterr = 0; rrd_clear_error(); result = rrd_update(pcount, updparams); #if defined(LINUX) && defined(RRDTOOL12) /* * RRDtool 1.2+ uses mmap'ed I/O, but the Linux kernel does not update timestamps when * doing file I/O on mmap'ed files. This breaks our check for stale/nostale RRD's. * So do an explicit timestamp update on the file here. */ utimes(filedir, NULL); #endif /* Clear the cached data */ for (i=0; (i < cacheitem->valcount); i++) { cacheitem->updseq[i] = 0; cacheitem->updtime[i] = 0; if (cacheitem->vals[i]) xfree(cacheitem->vals[i]); } cacheitem->valcount = 0; return result; } static int create_and_update_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *creparams[], void *template) { static int callcounter = 0; struct stat st; int pcount, result; char *updcachekey; xtreePos_t handle; updcacheitem_t *cacheitem = NULL; int pollinterval; char *modifymsg; time_t updtime = 0; /* Reset the RRD poll interval */ pollinterval = rrdinterval; rrdinterval = DEFAULT_RRD_INTERVAL; if ((rrdfn == NULL) || (strlen(rrdfn) == 0)) { errprintf("RRD update for no file\n"); return -1; } MEMDEFINE(rrdvalues); MEMDEFINE(filedir); sprintf(filedir, "%s/%s", rrddir, hostname); if (stat(filedir, &st) == -1) { if (mkdir(filedir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) == -1) { errprintf("Cannot create rrd directory %s : %s\n", filedir, strerror(errno)); MEMUNDEFINE(filedir); MEMUNDEFINE(rrdvalues); return -1; } } /* Watch out here - "rrdfn" may be very large. */ snprintf(filedir, sizeof(filedir)-1, "%s/%s/%s", rrddir, hostname, rrdfn); filedir[sizeof(filedir)-1] = '\0'; /* Make sure it is null terminated */ /* * Prepare to cache the update. Create the cache tree, and find/create a cache record. * Note: Cache records are persistent, once created they remain in place forever. * Only the update-data is flushed from time to time. */ if (updcache_keyofs == -1) { updcache = xtreeNew(strcasecmp); updcache_keyofs = strlen(rrddir); } updcachekey = filedir + updcache_keyofs; handle = xtreeFind(updcache, updcachekey); if (handle == xtreeEnd(updcache)) { if (!template) template = setup_template(creparams); if (!template) { errprintf("BUG: setup_template() returns NULL! host=%s,test=%s,cp[0]=%s, cp[1]=%s\n", hostname, testname, (creparams[0] ? creparams[0] : "NULL"), (creparams[1] ? creparams[1] : "NULL")); return -1; } cacheitem = (updcacheitem_t *)calloc(1, sizeof(updcacheitem_t)); cacheitem->key = strdup(updcachekey); cacheitem->tpl = template; xtreeAdd(updcache, cacheitem->key, cacheitem); } else { cacheitem = (updcacheitem_t *)xtreeData(updcache, handle); if (!template) template = cacheitem->tpl; } /* If the RRD file doesn't exist, create it immediately */ if (stat(filedir, &st) == -1) { char **rrdcreate_params, **rrddefinitions; int rrddefcount, i; char *rrakey = NULL; char stepsetting[10]; int havestepsetting = 0, fixcount = 2; dbgprintf("Creating rrd %s\n", filedir); /* How many parameters did we get? */ for (pcount = 0; (creparams[pcount]); pcount++); /* Add the RRA definitions to the create parameter set */ if (pollinterval != DEFAULT_RRD_INTERVAL) { rrakey = (char *)malloc(strlen(testname) + 10); sprintf(rrakey, "%s/%d", testname, pollinterval); } sprintf(stepsetting, "%d", pollinterval); rrddefinitions = get_rrd_definition((rrakey ? rrakey : testname), &rrddefcount); rrdcreate_params = (char **)calloc(4 + pcount + rrddefcount + 1, sizeof(char *)); rrdcreate_params[0] = "rrdcreate"; rrdcreate_params[1] = filedir; /* Is there already a step-setting in the rrddefinitions? */ for (i=0; (!havestepsetting && (i < rrddefcount)); i++) havestepsetting = ((strcmp(rrddefinitions[i], "-s") == 0) || (strcmp(rrddefinitions[i], "--step") == 0)); if (!havestepsetting) { rrdcreate_params[2] = "-s"; rrdcreate_params[3] = stepsetting; fixcount = 4; } for (i=0; (i < pcount); i++) rrdcreate_params[fixcount+i] = creparams[i]; for (i=0; (i < rrddefcount); i++, pcount++) rrdcreate_params[fixcount+pcount] = rrddefinitions[i]; if (debug) { for (i = 0; (rrdcreate_params[i]); i++) { dbgprintf("RRD create param %02d: '%s'\n", i, rrdcreate_params[i]); } } /* * Ugly! RRDtool uses getopt() for parameter parsing, so * we MUST reset this before every call. */ optind = opterr = 0; rrd_clear_error(); result = rrd_create(4+pcount, rrdcreate_params); xfree(rrdcreate_params); if (rrakey) xfree(rrakey); if (result != 0) { errprintf("RRD error creating %s: %s\n", filedir, rrd_get_error()); MEMUNDEFINE(filedir); MEMUNDEFINE(rrdvalues); return 1; } } updtime = atoi(rrdvalues); if (cacheitem->valcount > 0) { /* Check for duplicate updates */ if (cacheitem->updseq[cacheitem->valcount-1] == seq) { /* * This is usually caused by a configuration error, * e.g. two PORT settings in analysis.cfg that * use the same TRACK string. * Can also be two web checks using the same URL, but * with different POST data. */ dbgprintf("%s/%s: Error - ignored duplicate update for message sequence %d\n", hostname, rrdfn, seq); MEMUNDEFINE(filedir); MEMUNDEFINE(rrdvalues); return 0; } else if (cacheitem->updtime[cacheitem->valcount-1] > updtime) { dbgprintf("%s/%s: Error - RRD time goes backwards: Now=%d, previous=%d\n", hostname, rrdfn, (int) updtime, (int)cacheitem->updtime[cacheitem->valcount-1]); MEMUNDEFINE(filedir); MEMUNDEFINE(rrdvalues); return 0; } else if (cacheitem->updtime[cacheitem->valcount-1] == updtime) { int identical = (strcmp(rrdvalues, cacheitem->vals[cacheitem->valcount-1]) == 0); if (!identical) { int i; errprintf("%s/%s: Bug - duplicate RRD data with same timestamp %d, different data\n", hostname, rrdfn, (int) updtime); for (i=0; (i < cacheitem->valcount); i++) dbgprintf("Val %d: Seq %d: %s\n", i, cacheitem->updseq[i], cacheitem->vals[i]); dbgprintf("NewVal: Seq %d: %s\n", seq, rrdvalues); } else { dbgprintf("%s/%s: Ignored duplicate (and identical) update timestamped %d\n", hostname, rrdfn, (int) updtime); } MEMUNDEFINE(filedir); MEMUNDEFINE(rrdvalues); return 0; } } /* * Match the RRD data against any DS client-configuration modifiers. */ modifymsg = check_rrdds_thresholds(hostname, classname, pagepaths, rrdfn, ((rrdtpldata_t *)template)->dsnames, rrdvalues); if (modifymsg) sendmessage(modifymsg, NULL, XYMON_TIMEOUT, NULL); /* * See if we want the data to go to an external handler. */ if (processorstream) { int i, n; n = fprintf(processorstream, "%s %s %s", ((rrdtpldata_t *)template)->template, rrdvalues, hostname); for (i=0; ((n >= 0) && fnparams[i]); i++) n = fprintf(processorstream, " %s", fnparams[i]); if (n >= 0) n = fprintf(processorstream, "\n"); if (n >= 0) fflush(processorstream); if (n == -1) { errprintf("Ext-processor write failed: %s\n", strerror(errno)); shutdown_extprocessor(); } } /* * We cannot just cache data every time because then after CACHESZ updates * of each RRD, we will flush all of the data at once (all of the caches * fill at the same speed); this would result in huge load-spikes every * rrdinterval*CACHESZ seconds. * * So to smooth the load, we force the update through for every CACHESZ * updates, regardless of how much is in the cache. This gives us a steady * (although slightly higher) load. */ if (use_rrd_cache && (++callcounter < CACHESZ)) { if (cacheitem && (cacheitem->valcount < CACHESZ)) { cacheitem->updseq[cacheitem->valcount] = seq; cacheitem->updtime[cacheitem->valcount] = updtime; cacheitem->vals[cacheitem->valcount] = strdup(rrdvalues); cacheitem->valcount += 1; MEMUNDEFINE(filedir); MEMUNDEFINE(rrdvalues); return 0; } } else callcounter = 0; /* At this point, we will commit the update to disk */ result = flush_cached_updates(cacheitem, rrdvalues); if (result != 0) { char *msg = rrd_get_error(); if (strstr(msg, "(minimum one second step)") != NULL) { dbgprintf("RRD error updating %s from %s: %s\n", filedir, (senderip ? senderip : "unknown"), msg); } else { errprintf("RRD error updating %s from %s: %s\n", filedir, (senderip ? senderip : "unknown"), msg); } MEMUNDEFINE(filedir); MEMUNDEFINE(rrdvalues); return 2; } MEMUNDEFINE(filedir); MEMUNDEFINE(rrdvalues); return 0; } void rrdcacheflushall(void) { xtreePos_t handle; updcacheitem_t *cacheitem; if (updcache_keyofs == -1) return; /* No cache */ for (handle = xtreeFirst(updcache); (handle != xtreeEnd(updcache)); handle = xtreeNext(updcache, handle)) { cacheitem = (updcacheitem_t *) xtreeData(updcache, handle); if (cacheitem->valcount > 0) { sprintf(filedir, "%s%s", rrddir, cacheitem->key); flush_cached_updates(cacheitem, NULL); } } } void rrdcacheflushhost(char *hostname) { xtreePos_t handle; updcacheitem_t *cacheitem; flushtree_t *flushitem; int keylen; time_t now = gettimer(); if (updcache_keyofs == -1) return; /* If we get a full path for the key, skip the leading rrddir */ if (strncmp(hostname, rrddir, updcache_keyofs) == 0) hostname += updcache_keyofs; keylen = strlen(hostname); if (!have_flushtree) { flushtree = xtreeNew(strcasecmp); have_flushtree = 1; } handle = xtreeFind(flushtree, hostname); if (handle == xtreeEnd(flushtree)) { flushitem = (flushtree_t *)calloc(1, sizeof(flushtree_t)); flushitem->hostname = strdup(hostname); flushitem->flushtime = 0; xtreeAdd(flushtree, flushitem->hostname, flushitem); } else { flushitem = (flushtree_t *) xtreeData(flushtree, handle); } if ((flushitem->flushtime + 60) >= now) { dbgprintf("Flush of '%s' skipped, too soon\n", hostname); return; } flushitem->flushtime = now; handle = xtreeFirst(updcache); while (handle != xtreeEnd(updcache)) { cacheitem = (updcacheitem_t *) xtreeData(updcache, handle); switch (strncasecmp(cacheitem->key, hostname, keylen)) { case 1 : handle = xtreeEnd(updcache); break; case 0: if (cacheitem->valcount > 0) { dbgprintf("Flushing cache '%s'\n", cacheitem->key); sprintf(filedir, "%s%s", rrddir, cacheitem->key); flush_cached_updates(cacheitem, NULL); } /* Fall through */ default: handle = xtreeNext(updcache, handle); break; } } } static int rrddatasets(char *hostname, char ***dsnames) { struct stat st; int result; char *fetch_params[] = { "rrdfetch", filedir, "AVERAGE", "-s", "-30m", NULL }; time_t starttime, endtime; unsigned long steptime, dscount; rrd_value_t *rrddata; snprintf(filedir, sizeof(filedir)-1, "%s/%s/%s", rrddir, hostname, rrdfn); filedir[sizeof(filedir)-1] = '\0'; if (stat(filedir, &st) == -1) return 0; optind = opterr = 0; rrd_clear_error(); result = rrd_fetch(5, fetch_params, &starttime, &endtime, &steptime, &dscount, dsnames, &rrddata); if (result == -1) { errprintf("Error while retrieving RRD dataset names from %s: %s\n", filedir, rrd_get_error()); return 0; } free(rrddata); /* No use for the actual data */ return dscount; } /* Include all of the sub-modules. */ #include "rrd/do_xymongen.c" #include "rrd/do_xymonnet.c" #include "rrd/do_xymonproxy.c" #include "rrd/do_xymond.c" #include "rrd/do_citrix.c" #include "rrd/do_ntpstat.c" #include "rrd/do_memory.c" /* Must go before do_la.c */ #include "rrd/do_la.c" /* * From hobbit-perl-client http://sourceforge.net/projects/hobbit-perl-cl/ * version 1.15 Oct. 17 2006 (downloaded on 2008-12-01). * * Include file for netapp.pl dbcheck.pl and beastat.pl scripts * do_fd_lib.c contains some function used by the other library * * Must go before "do_disk.c" */ #include "rrd/do_fd_lib.c" #include "rrd/do_netapp.c" #include "rrd/do_beastat.c" #include "rrd/do_dbcheck.c" #include "rrd/do_disk.c" #include "rrd/do_netstat.c" #include "rrd/do_vmstat.c" #include "rrd/do_iostat.c" #include "rrd/do_ifstat.c" #include "rrd/do_apache.c" #include "rrd/do_sendmail.c" #include "rrd/do_mailq.c" #include "rrd/do_iishealth.c" #include "rrd/do_temperature.c" #include "rrd/do_net.c" #include "rrd/do_ncv.c" #include "rrd/do_external.c" #include "rrd/do_filesizes.c" #include "rrd/do_counts.c" #include "rrd/do_trends.c" #include "rrd/do_ifmib.c" #include "rrd/do_snmpmib.c" /* z/OS, z/VM, z/VME stuff */ #include "rrd/do_paging.c" #include "rrd/do_mdc.c" #include "rrd/do_cics.c" #include "rrd/do_getvis.c" #include "rrd/do_asid.c" /* * From devmon http://sourceforge.net/projects/devmon/ * version 0.3.0 (downloaded on 2008-12-01). */ #include "rrd/do_devmon.c" void update_rrd(char *hostname, char *testname, char *msg, time_t tstamp, char *sender, xymonrrd_t *ldef, char *classname, char *pagepaths) { int res = 0; char *id; MEMDEFINE(rrdvalues); if (ldef) id = ldef->xymonrrdname; else id = testname; senderip = sender; if (strcmp(id, "bbgen") == 0) res = do_xymongen_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "xymongen") == 0) res = do_xymongen_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "bbtest") == 0) res = do_xymonnet_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "xymonnet") == 0) res = do_xymonnet_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "bbproxy") == 0) res = do_xymonproxy_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "xymonproxy") == 0) res = do_xymonproxy_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "hobbitd") == 0) res = do_xymond_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "xymond") == 0) res = do_xymond_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "citrix") == 0) res = do_citrix_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "ntpstat") == 0) res = do_ntpstat_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "la") == 0) res = do_la_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "disk") == 0) res = do_disk_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "memory") == 0) res = do_memory_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "netstat") == 0) res = do_netstat_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "vmstat") == 0) res = do_vmstat_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "iostat") == 0) res = do_iostat_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "ifstat") == 0) res = do_ifstat_rrd(hostname, testname, classname, pagepaths, msg, tstamp); /* These two come from the filerstats2bb.pl script. The reports are in disk-format */ else if (strcmp(id, "inode") == 0) res = do_disk_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "qtree") == 0) res = do_disk_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "apache") == 0) res = do_apache_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "sendmail") == 0) res = do_sendmail_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "mailq") == 0) res = do_mailq_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "iishealth") == 0) res = do_iishealth_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "temperature") == 0) res = do_temperature_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "ncv") == 0) res = do_ncv_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "tcp") == 0) res = do_net_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "filesizes") == 0) res = do_filesizes_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "proccounts") == 0) res = do_counts_rrd("processes", hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "portcounts") == 0) res = do_counts_rrd("ports", hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "linecounts") == 0) res = do_derives_rrd("lines", hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "trends") == 0) res = do_trends_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "ifmib") == 0) res = do_ifmib_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (is_snmpmib_rrd(id)) res = do_snmpmib_rrd(hostname, testname, classname, pagepaths, msg, tstamp); /* z/OS, z/VSE, z/VM from Rich Smrcina */ else if (strcmp(id, "paging") == 0) res = do_paging_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "mdc") == 0) res = do_mdc_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "cics") == 0) res = do_cics_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "getvis") == 0) res = do_getvis_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "maxuser") == 0) res = do_asid_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "nparts") == 0) res = do_asid_rrd(hostname, testname, classname, pagepaths, msg, tstamp); /* * These are from the hobbit-perl-client * NetApp check for netapp.pl, dbcheck.pl and beastat.pl scripts */ else if (strcmp(id, "xtstats") == 0) res = do_netapp_extrastats_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "quotas") == 0) res = do_disk_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "snapshot") == 0) res = do_disk_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "TblSpace") == 0) res = do_disk_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "stats") == 0) res = do_netapp_stats_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "ops") == 0) res = do_netapp_ops_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "cifs") == 0) res = do_netapp_cifs_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "snaplist") == 0) res = do_netapp_snaplist_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "snapmirr") == 0) res = do_netapp_snapmirror_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "HitCache") == 0) res = do_dbcheck_hitcache_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "Session") == 0) res = do_dbcheck_session_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "RollBack") == 0) res = do_dbcheck_rb_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "InvObj") == 0) res = do_dbcheck_invobj_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "MemReq") == 0) res = do_dbcheck_memreq_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "JVM") == 0) res = do_beastat_jvm_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "JMS") == 0) res = do_beastat_jms_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "JTA") == 0) res = do_beastat_jta_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "ExecQueue") == 0) res = do_beastat_exec_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (strcmp(id, "JDBCConn") == 0) res = do_beastat_jdbc_rrd(hostname, testname, classname, pagepaths, msg, tstamp); /* * This is from the devmon SNMP collector */ else if (strcmp(id, "devmon") == 0) res = do_devmon_rrd(hostname, testname, classname, pagepaths, msg, tstamp); else if (extids && exthandler) { int i; for (i=0; (extids[i] && strcmp(extids[i], id)); i++) ; if (extids[i]) res = do_external_rrd(hostname, testname, classname, pagepaths, msg, tstamp); } senderip = NULL; MEMUNDEFINE(rrdvalues); } xymon-4.3.7/xymond/xymond_filestore.c0000664000175000017500000003401211630732124017276 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* This is a xymond worker module, it should be run off xymond_channel. */ /* */ /* This module implements the traditional Big Brother filebased storage of */ /* incoming status messages to the $XYMONVAR/logs/, $XYMONVAR/data/, */ /* $XYMONWWWDIR/notes/ and $XYMONVAR/disabled/ directories. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond_filestore.c 6748 2011-09-04 17:24:36Z storner $"; #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymond_worker.h" static char *multigraphs = ",disk,inode,qtree,quotas,snapshot,TblSpace,if_load,"; static int locatorbased = 0; enum role_t { ROLE_STATUS, ROLE_DATA, ROLE_NOTES, ROLE_ENADIS}; void update_file(char *fn, char *mode, char *msg, time_t expire, char *sender, time_t timesincechange, int seq) { FILE *logfd; char tmpfn[PATH_MAX]; char *p; MEMDEFINE(tmpfn); dbgprintf("Updating seq %d file %s\n", seq, fn); p = strrchr(fn, '/'); if (p) { *p = '\0'; sprintf(tmpfn, "%s/.%s", fn, p+1); *p = '/'; } else { sprintf(tmpfn, ".%s", fn); } logfd = fopen(tmpfn, mode); fwrite(msg, strlen(msg), 1, logfd); if (sender) fprintf(logfd, "\n\nMessage received from %s\n", sender); if (timesincechange >= 0) { char timestr[100]; char *p = timestr; if (timesincechange > 86400) p += sprintf(p, "%ld days, ", (timesincechange / 86400)); p += sprintf(p, "%ld hours, %ld minutes", ((timesincechange % 86400) / 3600), ((timesincechange % 3600) / 60)); fprintf(logfd, "Status unchanged in %s\n", timestr); } fclose(logfd); if (expire) { struct utimbuf logtime; logtime.actime = logtime.modtime = expire; utime(tmpfn, &logtime); } rename(tmpfn, fn); MEMUNDEFINE(tmpfn); } void update_htmlfile(char *fn, char *msg, char *hostname, char *service, int color, int flapping, char *sender, char *flags, time_t logtime, time_t timesincechange, time_t acktime, char *ackmsg, time_t disabletime, char *dismsg) { FILE *output; char *tmpfn; char *firstline, *restofmsg; char *displayname = hostname; char *ip = ""; char timestr[100]; MEMDEFINE(timestr); tmpfn = (char *) malloc(strlen(fn)+5); sprintf(tmpfn, "%s.tmp", fn); output = fopen(tmpfn, "w"); if (output) { firstline = msg; restofmsg = strchr(msg, '\n'); if (restofmsg) { *restofmsg = '\0'; restofmsg++; } else { restofmsg = ""; } if (timesincechange >= 0) { char *p = timestr; if (timesincechange > 86400) p += sprintf(p, "%ld days, ", (timesincechange / 86400)); p += sprintf(p, "%ld hours, %ld minutes", ((timesincechange % 86400) / 3600), ((timesincechange % 3600) / 60)); } generate_html_log(hostname, displayname, service, ip, color, flapping, sender, flags, logtime, timestr, firstline, restofmsg, NULL, acktime, ackmsg, NULL, disabletime, dismsg, 0, 1, 0, locatorbased, multigraphs, NULL, NULL, NULL, NULL, 0, output); fclose(output); rename(tmpfn, fn); } xfree(tmpfn); MEMUNDEFINE(timestr); } void update_enable(char *fn, time_t expiretime) { time_t now = getcurrenttime(NULL); dbgprintf("Enable/disable file %s, time %d\n", fn, (int)expiretime); if (expiretime <= now) { if (unlink(fn) != 0) { errprintf("Could not remove disable-file '%s':%s\n", fn, strerror(errno)); } } else { FILE *enablefd; struct utimbuf logtime; enablefd = fopen(fn, "w"); if (enablefd) { fclose(enablefd); } logtime.actime = logtime.modtime = expiretime; utime(fn, &logtime); } } static int wantedtest(char *wanted, char *key) { char *p, *ckey; if (wanted == NULL) return 1; ckey = (char *)malloc(strlen(key) + 3); sprintf(ckey, ",%s,", key); p = strstr(wanted, ckey); xfree(ckey); if (p) dbgprintf("wantedtest: Found '%s' at '%s'\n", key, p); else dbgprintf("wantedtest: '%s' not found\n", key); return (p != NULL); } int main(int argc, char *argv[]) { char *filedir = NULL; char *htmldir = NULL; char *htmlextension = "html"; char *onlytests = NULL; char *msg; enum role_t role = ROLE_STATUS; enum msgchannels_t chnid = C_STATUS; int argi; int seq; int running = 1; /* Dont save the error buffer */ save_errbuf = 0; for (argi = 1; (argi < argc); argi++) { if (strcmp(argv[argi], "--status") == 0) { role = ROLE_STATUS; chnid = C_STATUS; if (!filedir) filedir = xgetenv("XYMONRAWSTATUSDIR"); } else if (strcmp(argv[argi], "--html") == 0) { role = ROLE_STATUS; chnid = C_STATUS; if (!htmldir) htmldir = xgetenv("XYMONHTMLSTATUSDIR"); } else if (strcmp(argv[argi], "--data") == 0) { role = ROLE_DATA; chnid = C_DATA; if (!filedir) filedir = xgetenv("XYMONDATADIR"); } else if (strcmp(argv[argi], "--notes") == 0) { role = ROLE_NOTES; chnid = C_NOTES; if (!filedir) filedir = xgetenv("XYMONNOTESDIR"); } else if (strcmp(argv[argi], "--enadis") == 0) { role = ROLE_ENADIS; chnid = C_ENADIS; if (!filedir) filedir = xgetenv("XYMONDISABLEDDIR"); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--dir=")) { filedir = strchr(argv[argi], '=')+1; } else if (argnmatch(argv[argi], "--htmldir=")) { htmldir = strchr(argv[argi], '=')+1; } else if (argnmatch(argv[argi], "--htmlext=")) { htmlextension = strchr(argv[argi], '=')+1; } else if (argnmatch(argv[argi], "--only=")) { char *p = strchr(argv[argi], '=') + 1; onlytests = (char *)malloc(3 + strlen(p)); sprintf(onlytests, ",%s,", p); } else if (argnmatch(argv[argi], "--multigraphs=")) { char *p = strchr(argv[argi], '='); multigraphs = (char *)malloc(strlen(p+1) + 3); sprintf(multigraphs, ",%s,", p+1); } else if (argnmatch(argv[argi], "--locator=")) { char *p = strchr(argv[argi], '='); locator_init(p+1); locatorbased = 1; } } if (filedir == NULL) { errprintf("No directory given, aborting\n"); return 1; } /* For picking up lost children */ setup_signalhandler("xymond_filestore"); signal(SIGPIPE, SIG_DFL); if (onlytests) dbgprintf("Storing tests '%s' only\n", onlytests); else dbgprintf("Storing all tests\n"); while (running) { char *metadata[20] = { NULL, }; char *statusdata = ""; char *p; int metacount; char *hostname, *testname; time_t expiretime = 0; char logfn[PATH_MAX]; MEMDEFINE(logfn); msg = get_xymond_message(chnid, "filestore", &seq, NULL); if (msg == NULL) { running = 0; MEMUNDEFINE(logfn); continue; } p = strchr(msg, '\n'); if (p) { *p = '\0'; statusdata = p+1; } metacount = 0; memset(&metadata, 0, sizeof(metadata)); p = gettok(msg, "|"); while (p && (metacount < 20)) { metadata[metacount++] = p; p = gettok(NULL, "|"); } if ((role == ROLE_STATUS) && (metacount >= 14) && (strncmp(metadata[0], "@@status", 8) == 0)) { /* @@status|timestamp|sender|origin|hostname|testname|expiretime|color|testflags|prevcolor|changetime|ackexpiretime|ackmessage|disableexpiretime|disablemessage|clientmsgtstamp|flapping */ int ltime, flapping = 0; time_t logtime = 0, timesincechange = 0, acktime = 0, disabletime = 0; hostname = metadata[4]; testname = metadata[5]; if (!wantedtest(onlytests, testname)) { dbgprintf("Status dropped - not wanted\n"); MEMUNDEFINE(logfn); continue; } sprintf(logfn, "%s/%s.%s", filedir, commafy(hostname), testname); expiretime = atoi(metadata[6]); statusdata = msg_data(statusdata); sscanf(metadata[1], "%d.%*d", <ime); logtime = ltime; timesincechange = logtime - atoi(metadata[10]); update_file(logfn, "w", statusdata, expiretime, metadata[2], timesincechange, seq); if (htmldir) { char *ackmsg = NULL; char *dismsg = NULL; char htmllogfn[PATH_MAX]; MEMDEFINE(htmllogfn); if (metadata[11]) acktime = atoi(metadata[11]); if (metadata[12] && strlen(metadata[12])) ackmsg = metadata[12]; if (ackmsg) nldecode(ackmsg); if (metadata[13]) disabletime = atoi(metadata[13]); if (metadata[14] && strlen(metadata[14]) && (disabletime > 0)) dismsg = metadata[14]; if (dismsg) nldecode(dismsg); flapping = (metadata[16] ? (*metadata[16] == '1') : 0); sprintf(htmllogfn, "%s/%s.%s.%s", htmldir, hostname, testname, htmlextension); update_htmlfile(htmllogfn, statusdata, hostname, testname, parse_color(metadata[7]), flapping, metadata[2], metadata[8], logtime, timesincechange, acktime, ackmsg, disabletime, dismsg); MEMUNDEFINE(htmllogfn); } } else if ((role == ROLE_DATA) && (metacount > 5) && (strncmp(metadata[0], "@@data", 6) == 0)) { /* @@data|timestamp|sender|hostname|testname */ p = hostname = metadata[4]; while ((p = strchr(p, '.')) != NULL) *p = ','; testname = metadata[5]; if (!wantedtest(onlytests, testname)) { dbgprintf("data dropped - not wanted\n"); MEMUNDEFINE(logfn); continue; } statusdata = msg_data(statusdata); if (*statusdata == '\n') statusdata++; sprintf(logfn, "%s/%s.%s", filedir, hostname, testname); expiretime = 0; update_file(logfn, "a", statusdata, expiretime, NULL, -1, seq); } else if ((role == ROLE_NOTES) && (metacount > 3) && (strncmp(metadata[0], "@@notes", 7) == 0)) { /* @@notes|timestamp|sender|hostname */ hostname = metadata[3]; statusdata = msg_data(statusdata); if (*statusdata == '\n') statusdata++; sprintf(logfn, "%s/%s", basename(filedir), hostname); expiretime = 0; update_file(logfn, "w", statusdata, expiretime, NULL, -1, seq); } else if ((role == ROLE_ENADIS) && (metacount > 5) && (strncmp(metadata[0], "@@enadis", 8) == 0)) { /* @@enadis|timestamp|sender|hostname|testname|expiretime */ p = hostname = metadata[3]; while ((p = strchr(p, '.')) != NULL) *p = ','; testname = metadata[4]; expiretime = atoi(metadata[5]); sprintf(logfn, "%s/%s.%s", filedir, hostname, testname); update_enable(logfn, expiretime); } else if (((role == ROLE_STATUS) || (role == ROLE_DATA) || (role == ROLE_ENADIS)) && (metacount > 3) && (strncmp(metadata[0], "@@drophost", 10) == 0)) { /* @@drophost|timestamp|sender|hostname */ DIR *dirfd; struct dirent *de; char *hostlead; p = hostname = metadata[3]; while ((p = strchr(p, '.')) != NULL) *p = ','; hostlead = malloc(strlen(hostname) + 2); strcpy(hostlead, hostname); strcat(hostlead, "."); dirfd = opendir(filedir); if (dirfd) { while ( (de = readdir(dirfd)) != NULL) { if (strncmp(de->d_name, hostlead, strlen(hostlead)) == 0) { sprintf(logfn, "%s/%s", filedir, de->d_name); unlink(logfn); } } closedir(dirfd); } xfree(hostlead); } else if (((role == ROLE_STATUS) || (role == ROLE_DATA) || (role == ROLE_ENADIS)) && (metacount > 4) && (strncmp(metadata[0], "@@droptest", 10) == 0)) { /* @@droptest|timestamp|sender|hostname|testname */ p = hostname = metadata[3]; while ((p = strchr(p, '.')) != NULL) *p = ','; testname = metadata[4]; sprintf(logfn, "%s/%s.%s", filedir, hostname, testname); unlink(logfn); } else if (((role == ROLE_STATUS) || (role == ROLE_DATA) || (role == ROLE_ENADIS)) && (metacount > 4) && (strncmp(metadata[0], "@@renamehost", 12) == 0)) { /* @@renamehost|timestamp|sender|hostname|newhostname */ DIR *dirfd; struct dirent *de; char *hostlead; char *newhostname; char newlogfn[PATH_MAX]; MEMDEFINE(newlogfn); p = hostname = metadata[3]; while ((p = strchr(p, '.')) != NULL) *p = ','; hostlead = malloc(strlen(hostname) + 2); strcpy(hostlead, hostname); strcat(hostlead, "."); p = newhostname = metadata[4]; while ((p = strchr(p, '.')) != NULL) *p = ','; dirfd = opendir(filedir); if (dirfd) { while ( (de = readdir(dirfd)) != NULL) { if (strncmp(de->d_name, hostlead, strlen(hostlead)) == 0) { char *testname = strchr(de->d_name, '.'); sprintf(logfn, "%s/%s", filedir, de->d_name); sprintf(newlogfn, "%s/%s%s", filedir, newhostname, testname); rename(logfn, newlogfn); } } closedir(dirfd); } xfree(hostlead); MEMUNDEFINE(newlogfn); } else if (((role == ROLE_STATUS) || (role == ROLE_DATA) || (role == ROLE_ENADIS)) && (metacount > 5) && (strncmp(metadata[0], "@@renametest", 12) == 0)) { /* @@renametest|timestamp|sender|hostname|oldtestname|newtestname */ char *newtestname; char newfn[PATH_MAX]; MEMDEFINE(newfn); p = hostname = metadata[3]; while ((p = strchr(p, '.')) != NULL) *p = ','; testname = metadata[4]; newtestname = metadata[5]; sprintf(logfn, "%s/%s.%s", filedir, hostname, testname); sprintf(newfn, "%s/%s.%s", filedir, hostname, newtestname); rename(logfn, newfn); MEMUNDEFINE(newfn); } else if (strncmp(metadata[0], "@@shutdown", 10) == 0) { running = 0; } else if (strncmp(metadata[0], "@@logrotate", 11) == 0) { char *fn = xgetenv("XYMONCHANNEL_LOGFILENAME"); if (fn && strlen(fn)) { freopen(fn, "a", stdout); freopen(fn, "a", stderr); } continue; } else if (strncmp(metadata[0], "@@idle", 6) == 0) { /* Ignored */ } else if (strncmp(metadata[0], "@@reload", 8) == 0) { /* Do nothing */ } else { errprintf("Dropping message type %s, metacount=%d\n", metadata[0], metacount); } MEMUNDEFINE(logfn); } return 0; } xymon-4.3.7/xymond/xymond_distribute.c0000664000175000017500000001126611632677163017503 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* xymond worker to distribute enable/disable/drop/rename messages in a */ /* multi-server active/active setup. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond_sample.c 6748 2011-09-04 17:24:36Z storner $"; #include #include #include #include #include #include #include "libxymon.h" #include "xymond_worker.h" #define MAX_META 20 int peercount = 0; char **peers = NULL; char *channelname = NULL; char *myhostname = NULL; int main(int argc, char *argv[]) { char *msg; int running; int argi, seq; char newmsg[4096]; /* Handle program options. */ for (argi = 1; (argi < argc); argi++) { if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (strncmp(argv[argi], "--peer=", 7) == 0) { char *ip = strchr(argv[argi], '=') + 1; if (!peers) { peercount = 1; peers = (char **)calloc((peercount + 1), sizeof(char *)); peers[peercount-1] = strdup(ip); peers[peercount] = NULL; } else { peercount++; peers = (char **)realloc(peers, (peercount + 1)*sizeof(char *)); peers[peercount-1] = strdup(ip); peers[peercount] = NULL; } } else if (strncmp(argv[argi], "--channel=", 10) == 0) { channelname = strdup(strchr(argv[argi], '=') + 1); } } if (!peers) { errprintf("No peers specified, aborting\n"); return 1; } save_errbuf = 0; signal(SIGCHLD, SIG_IGN); running = 1; while (running) { char *eoln, *restofmsg, *p; char *metadata[MAX_META+1]; int metacount; *newmsg = '\0'; msg = get_xymond_message(C_LAST, argv[0], &seq, NULL); /* Split the message in the first line (with meta-data), and the rest */ eoln = strchr(msg, '\n'); if (eoln) { *eoln = '\0'; restofmsg = eoln+1; } else { restofmsg = ""; } metacount = 0; memset(&metadata, 0, sizeof(metadata)); p = gettok(msg, "|"); while (p && (metacount < MAX_META)) { metadata[metacount++] = p; p = gettok(NULL, "|"); } metadata[metacount] = NULL; if ((msg == NULL) || (strncmp(metadata[0], "@@shutdown", 10) == 0)) { printf("Shutting down\n"); running = 0; continue; } else if (strncmp(metadata[0], "@@logrotate", 11) == 0) { char *fn = xgetenv("XYMONCHANNEL_LOGFILENAME"); if (fn && strlen(fn)) { freopen(fn, "a", stdout); freopen(fn, "a", stderr); } continue; } else if ((metacount > 3) && (strncmp(metadata[0], "@@drophost", 10) == 0)) { snprintf(newmsg, sizeof(newmsg)-1, "drop %s", metadata[3]); } else if ((metacount > 4) && (strncmp(metadata[0], "@@droptest", 10) == 0)) { snprintf(newmsg, sizeof(newmsg)-1, "drop %s %s", metadata[3], metadata[4]); } else if ((metacount > 4) && (strncmp(metadata[0], "@@renamehost", 12) == 0)) { snprintf(newmsg, sizeof(newmsg)-1, "rename %s %s", metadata[3], metadata[4]); } else if ((metacount > 5) && (strncmp(metadata[0], "@@renametest", 12) == 0)) { snprintf(newmsg, sizeof(newmsg)-1, "rename %s %s %s", metadata[3], metadata[4], metadata[5]); } else if ((metacount > 5) && (strncmp(metadata[0], "@@enadis", 8) == 0)) { /* @@enadis|timestamp|sender|hostname|testname|expiretime|message */ if (strcmp(metadata[5], "0") == 0) { snprintf(newmsg, sizeof(newmsg)-1, "enable %s.%s", metadata[3], metadata[4]); } else { long int distime; /* Disable until OK has time -1; normal disables has a count of minutes */ distime = (strcmp(metadata[5], "-1") == 0) ? -1 : ((atol(metadata[5]) - time(NULL)) / 60); snprintf(newmsg, sizeof(newmsg)-1, "disable %s.%s %ld", metadata[3], metadata[4], distime); if (metadata[6] && strlen(metadata[6])) { nldecode(metadata[6]); sprintf(newmsg + strlen(newmsg), " %s", metadata[6]); } dbgprintf("Disable: %s\n", newmsg); } } if (strlen(newmsg) > 0) { int i; for (i = 0; (i < peercount); i++) sendmessage(newmsg, peers[i], XYMON_TIMEOUT, NULL); } } return 0; } xymon-4.3.7/xymond/xymond_distribute.80000664000175000017500000000222311671641417017415 0ustar henrikhenrik.TH XYMOND_DISTRIBUTE 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymond_distribute \- xymond worker module for distributing commands .SH SYNOPSIS .B "xymond_channel --channel=enadis xymond_distribute [options]" .SH DESCRIPTION xymond_distribute is a worker module for xymond, and as such it is normally run via the .I xymond_channel(8) program. It is used if you have multiple Xymon servers running in a master/slave configuration. xymond_distribute runs on the master server and receives "drop", "rename", "enable" and "disable" messages from xymond. xymond_distribute then forwards these to the other Xymon servers as standard xymon messages. So if a user on the master Xymon server disables a red status, xymond_distribute will forward this to the other Xymon servers so that the change happens everywhere. NOTE: xymond_distribute does not check to see if a message has already been forwarded, so you can easily create forwarding loops. .SH OPTIONS .IP "--peer=HOSTNAME" The peer that messages are forwarded to. If you have multiple peers, repeat this option. .IP "--debug" Enable debugging output. .SH "SEE ALSO" xymond_channel(8), xymond(8), xymon(7) xymon-4.3.7/xymond/moverrd.sh.DIST0000664000175000017500000000245711535462534016335 0ustar henrikhenrik#!/bin/sh # This script moves or copies rrd files from the flat "all files # in a single directory" structure used by the traditional LARRD # tool, to the "one directory per host" structure used by Xymon # You need to run this tool once, to migrate your RRD files and # historical data to Xymon. # # Set these variables to match your local setup # The directory where Big Brother+LARRD has your RRD files currently SRCDIR=/usr/local/bb/bbvar/rrd # These are the Xymon directories and filenames XYMONHOME=@XYMONHOME@ HOSTSCFG=$XYMONHOME/etc/hosts.cfg DSTDIR=@XYMONVAR@/rrd # Where the files should end up. OP="cp -f" # Set to "mv" to move files instead of copying # You should not need to modify anything below here. export SRCDIR XYMONHOME HOSTSCFG DSTDIR OP cd $SRCDIR || (echo "Cannot go to $SRCDIR"; exit 0) $XYMONHOME/bin/xymoncfg |grep "^[0-9]" | awk '{print $2}' | \ while read HOST do HOSTC=`echo $HOST | sed 's/\./,/g'` mkdir $DSTDIR/${HOST} DFILES=`echo $HOST.*.rrd` if [ "$DFILES" != "$HOST.*.rrd" ] then for f in $HOST.*.rrd do $OP $f $DSTDIR/${HOST}/`echo $f | sed "s/^${HOST}\.//"` done fi CFILES=`echo $HOSTC.*.rrd` if [ "$CFILES" != "$HOSTC.*.rrd" ] then for f in $HOSTC.*.rrd do $OP $f $DSTDIR/${HOST}/`echo $f | sed "s/^${HOSTC}\.//"` done fi done xymon-4.3.7/xymond/combostatus.10000664000175000017500000000350311671641417016177 0ustar henrikhenrik.TH COMBOSTATUS 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME combostatus \- Xymon combination test tool .SH SYNOPSIS .B "combostatus --help" .br .B "combostatus --version" .br .B "combostatus [--debug] [--quiet]" .SH DESCRIPTION \fBcombostatus\fR is a Xymon extension script that runs on the Xymon server. It combines the results of one or more of the normal Xymon test results into a combined test result, using standard arithmetic og logical operators. The resulting tests are sent to the Xymon display server as any normal test - so all of the standard Xymon functions (history, statistics etc.) are available for the combined tests. The tool was born from the need to monitor systems with built-in redundancy and automatic failover - e.g. load-balanced web servers. But other uses are possible. .SH OPTIONS .IP "--error-colors=COLOR[,COLOR]" Specify which colors trigger an error status. By default only a "red" status counts as an error color - all other colors, including yellow, will count as "green" when evaluating the combined status. COLOR is "red", "yellow", "blue", "purple" or "clear". .IP "--quiet" Normally, the test status sent by combostatus includes information about the underlying test results used to determine the current value of the combined test. "--quiet" eliminates this information from the test status page. .IP "--debug" Provide debugging output for use in troubleshooting problems with combostatus. .IP "--no-update" Dont send any status messages - instead, the result of the combotests is simply dumped to stdout. Useful for debugging. .SH FILES .IP $XYMONHOME/etc/combo.cfg Configuration file for combostatus, where the combined tests are defined .IP $XYMONHOME/etc/tasks.cfg Configuration file controlling when combostatus is run. .SH "SEE ALSO" combo.cfg(5), hosts.cfg(5), xymonserver.cfg(5), tasks.cfg(5) xymon-4.3.7/xymond/rrd/0000775000175000017500000000000011671641716014342 5ustar henrikhenrikxymon-4.3.7/xymond/rrd/do_dbcheck.c0000664000175000017500000004124411535424634016555 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2006 Francesco Duranti */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char dbcheck_rcsid[] = "$Id: do_dbcheck.c 6648 2011-03-08 13:05:32Z storner $"; int do_dbcheck_memreq_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *dbcheck_memreq_params[] = { "DS:ResFree:GAUGE:600:0:U", "DS:ResAvgFree:GAUGE:600:0:U", "DS:ResUsed:GAUGE:600:0:U", "DS:ResAvgUsed:GAUGE:600:0:U", "DS:ReqFail:DERIVE:600:0:U", "DS:FailSize:GAUGE:600:0:U", NULL }; static void *dbcheck_memreq_tpl = NULL; unsigned long free=0,used=0,reqf=0,fsz=0; double avfr=0,avus=0; char *start,*end; dbgprintf("dbcheck: host %s test %s\n",hostname, testname); if (strstr(msg, "dbcheck.pl")) { if (dbcheck_memreq_tpl == NULL) dbcheck_memreq_tpl = setup_template(dbcheck_memreq_params); if ((start=strstr(msg, ""))==NULL) return 0; *end='\0'; free=get_long_data(start,"ResFree"); avfr=get_double_data(start,"ResAvgFree"); used=get_long_data(start,"ResUsed"); avus=get_double_data(start,"ResAvgUsed"); reqf=get_long_data(start,"ReqFail"); fsz=get_long_data(start,"FailSize"); *end='-'; dbgprintf("dbcheck: host %s test %s free %ld avgfree %f\n", hostname, testname, free, avfr); dbgprintf("dbcheck: host %s test %s used %ld avgused %f\n", hostname, testname, used, avus); dbgprintf("dbcheck: host %s test %s reqfail %ld failsize %ld\n", hostname, testname, reqf, fsz); sprintf(rrdvalues, "%d:%ld:%f:%ld:%f:%ld:%ld", (int) tstamp, free, avfr, used, avus, reqf,fsz); setupfn("%s.rrd",testname); create_and_update_rrd(hostname, testname, classname, pagepaths, dbcheck_memreq_params, dbcheck_memreq_tpl); } return 0; } int do_dbcheck_hitcache_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *dbcheck_hitcache_params[] = { "DS:PinSQLArea:GAUGE:600:0:100", "DS:PinTblProc:GAUGE:600:0:100", "DS:PinBody:GAUGE:600:0:100", "DS:PinTrigger:GAUGE:600:0:100", "DS:HitSQLArea:GAUGE:600:0:100", "DS:HitTblProc:GAUGE:600:0:100", "DS:HitBody:GAUGE:600:0:100", "DS:HitTrigger:GAUGE:600:0:100", "DS:BlBuffHit:GAUGE:600:0:100", "DS:RowCache:GAUGE:600:0:100", NULL }; static void *dbcheck_hitcache_tpl = NULL; double pinsql=0, pintbl=0, pinbody=0, pintrig=0, hitsql=0, hittbl=0, hitbody=0, hittrig=0, blbuff=0, rowchache=0; dbgprintf("dbcheck: host %s test %s\n",hostname, testname); if (strstr(msg, "dbcheck.pl")) { setupfn("%s.rrd",testname); if (dbcheck_hitcache_tpl == NULL) dbcheck_hitcache_tpl = setup_template(dbcheck_hitcache_params); pinsql=get_double_data(msg,"PinSQLArea"); pintbl=get_double_data(msg,"PinTblProc"); pinbody=get_double_data(msg,"PinBody"); pintrig=get_double_data(msg,"PinTrigger"); hitsql=get_double_data(msg,"HitSQLArea"); hittbl=get_double_data(msg,"HitTblProc"); hitbody=get_double_data(msg,"HitBody"); hittrig=get_double_data(msg,"HitTrigger"); blbuff=get_double_data(msg,"BlBuffHit"); rowchache=get_double_data(msg,"RowCache"); dbgprintf("dbcheck: host %s test %s pinsql %5.2f pintbl %5.2f pinbody %5.2f pintrig %5.2f\n", hostname, testname, pinsql, pintbl, pinbody, pintrig); dbgprintf("dbcheck: host %s test %s hitsql %5.2f hittbl %5.2f hitbody %5.2f hittrig %5.2f\n", hostname, testname, hitsql, hittbl, hitbody, hittrig); dbgprintf("dbcheck: host %s test %s blbuff %5.2f rowchache %5.2f\n", hostname, testname, blbuff, rowchache); sprintf(rrdvalues, "%d:%5.2f:%5.2f:%5.2f:%5.2f:%5.2f:%5.2f:%5.2f:%5.2f:%5.2f:%5.2f", (int) tstamp, pinsql, pintbl, pinbody, pintrig, hitsql, hittbl, hitbody, hittrig, blbuff, rowchache); create_and_update_rrd(hostname, testname, classname, pagepaths, dbcheck_hitcache_params, dbcheck_hitcache_tpl); } return 0; } int do_dbcheck_session_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *dbcheck_session_params[] = { "DS:MaxSession:GAUGE:600:0:U", "DS:CurrSession:GAUGE:600:0:U", "DS:SessUsedPct:GAUGE:600:0:100", "DS:MaxProcs:GAUGE:600:0:U", "DS:CurrProcs:GAUGE:600:0:U", "DS:ProcsUsedPct:GAUGE:600:0:100", NULL }; static void *dbcheck_session_tpl = NULL; unsigned long maxsess=0, currsess=0, maxproc=0, currproc=0 ; double pctsess=0, pctproc=0; dbgprintf("dbcheck: host %s test %s\n",hostname, testname); if (strstr(msg, "dbcheck.pl")) { setupfn("%s.rrd",testname); if (dbcheck_session_tpl == NULL) dbcheck_session_tpl = setup_template(dbcheck_session_params); maxsess=get_long_data(msg, "MaxSession"); currsess=get_long_data(msg,"CurrSession"); pctsess=get_double_data(msg,"SessUsedPct"); maxproc=get_long_data(msg,"MaxProcs"); currproc=get_long_data(msg,"CurrProcs"); pctproc=get_double_data(msg,"ProcsUsedPct"); dbgprintf("dbcheck: host %s test %s maxsess %ld currsess %ld pctsess %5.2f\n", hostname, testname, maxsess, currsess, pctsess); dbgprintf("dbcheck: host %s test %s maxproc %ld currproc %ld pctproc %5.2f\n", hostname, testname, maxproc, currproc, pctproc); sprintf(rrdvalues, "%d:%ld:%ld:%5.2f:%ld:%ld:%5.2f", (int) tstamp, maxsess, currsess, pctsess, maxproc, currproc, pctproc); create_and_update_rrd(hostname, testname, classname, pagepaths, dbcheck_session_params, dbcheck_session_tpl); } return 0; } int do_dbcheck_rb_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { /* This check can be done in slow mode so put a long heartbeat */ static char *dbcheck_rb_params[] = { "DS:pct:GAUGE:28800:0:100", NULL }; static void *dbcheck_rb_tpl = NULL; char *curline; char *eoln; dbgprintf("dbcheck: host %s test %s\n",hostname, testname); if (strstr(msg, "dbcheck.pl")) { if (dbcheck_rb_tpl == NULL) dbcheck_rb_tpl = setup_template(dbcheck_rb_params); curline=strstr(msg, "Rollback Checking"); if (curline) { eoln = strchr(curline, '\n'); curline = (eoln ? (eoln+1) : NULL); } while (curline) { float pct=0; char *execname=NULL; char *start; if ((start = strstr(curline,"ROLLBACK")) == NULL) break; if ((eoln = strchr(start, '\n')) == NULL) break; *eoln = '\0'; dbgprintf("dbcheck: host %s test %s line %s\n", hostname, testname, start); execname=xmalloc(strlen(start)); if ( sscanf(start,"ROLLBACK percentage for %s is %f",execname,&pct) !=2) goto nextline; setupfn2("%s,%s.rrd",testname,execname); dbgprintf("dbcheck: host %s test %s name %s pct %5.2f\n", hostname, testname, execname, pct); sprintf(rrdvalues, "%d:%5.2f", (int) tstamp, pct); create_and_update_rrd(hostname, testname, classname, pagepaths, dbcheck_rb_params, dbcheck_rb_tpl); nextline: if (execname) { xfree(execname); execname = NULL; } if (eoln) *(eoln)='\n'; curline = (eoln ? (eoln+1) : NULL); } } return 0; } int do_dbcheck_invobj_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { /* This check can be done in slow mode so put a long heartbeat */ static char *dbcheck_invobj_params[] = { "DS:red:GAUGE:28800:0:U", "DS:yellow:GAUGE:28800:0:U", "DS:green:GAUGE:28800:0:U", NULL }; static void *dbcheck_invobj_tpl = NULL; char *curline; char *eoln; unsigned long yellow=0,red=0,green=0; dbgprintf("dbcheck: host %s test %s\n",hostname, testname); if (strstr(msg, "dbcheck.pl")) { if (dbcheck_invobj_tpl == NULL) dbcheck_invobj_tpl = setup_template(dbcheck_invobj_params); curline=strstr(msg, "Invalid Object Checking"); if (curline) { eoln = strchr(curline, '\n'); curline = (eoln ? (eoln+1) : NULL); } while (curline) { if ( *curline == '\n') { curline++; continue; } if ((eoln = strchr(curline, '\n')) == NULL) break; *eoln = '\0'; if ( *curline =='&' ) curline++; if ( strstr(curline,"red") == curline) red++; if ( strstr(curline,"yellow") == curline) yellow++; if ( strstr(curline,"green") == curline) green++; nextline: if (eoln) *(eoln)='\n'; curline = (eoln ? (eoln+1) : NULL); } setupfn("%s.rrd",testname); dbgprintf("dbcheck: host %s test %s red %ld yellow %ld green %ld\n", hostname, testname, red,yellow,green); sprintf(rrdvalues, "%d:%ld:%ld:%ld", (int) tstamp, red,yellow,green); create_and_update_rrd(hostname, testname, classname, pagepaths, dbcheck_invobj_params, dbcheck_invobj_tpl); } return 0; } int do_dbcheck_tablespace_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *tablespace_params[] = { "DS:pct:GAUGE:600:0:U", "DS:used:GAUGE:600:0:U", NULL }; static rrdtpldata_t *tablespace_tpl = NULL; char *eoln, *curline; static int ptnsetup = 0; static pcre *inclpattern = NULL; static pcre *exclpattern = NULL; if (tablespace_tpl == NULL) tablespace_tpl = setup_template(tablespace_params); if (!ptnsetup) { const char *errmsg; int errofs; char *ptn; ptnsetup = 1; ptn = getenv("RRDDISKS"); if (ptn && strlen(ptn)) { inclpattern = pcre_compile(ptn, PCRE_CASELESS, &errmsg, &errofs, NULL); if (!inclpattern) errprintf("PCRE compile of RRDDISKS='%s' failed, error %s, offset %d\n", ptn, errmsg, errofs); } ptn = getenv("NORRDDISKS"); if (ptn && strlen(ptn)) { exclpattern = pcre_compile(ptn, PCRE_CASELESS, &errmsg, &errofs, NULL); if (!exclpattern) errprintf("PCRE compile of NORRDDISKS='%s' failed, error %s, offset %d\n", ptn, errmsg, errofs); } } /* * Francesco Duranti noticed that if we use the "/group" option * when sending the status message, this tricks the parser to * create an extra filesystem called "/group". So skip the first * line - we never have any disk reports there anyway. */ curline = strchr(msg, '\n'); if (curline) curline++; /* FD: For dbcheck.pl move after the Header */ curline=strstr(curline, "TableSpace/DBSpace"); if (curline) { eoln = strchr(curline, '\n'); curline = (eoln ? (eoln+1) : NULL); } while (curline) { char *fsline, *p; char *columns[20]; int columncount; char *diskname = NULL; int pused = -1; int wanteddisk = 1; long long aused = 0; /* FD: Using double instead of long long because we can have decimal on Netapp and DbCheck */ double dused = 0; /* FD: used to add a column if the filesystem is named "snap reserve" for netapp.pl */ int snapreserve=0; eoln = strchr(curline, '\n'); if (eoln) *eoln = '\0'; /* FD: Exit if doing DBCHECK and the end of the tablespaces are reached */ if (strstr(eoln+1, "dbcheck.pl") == (eoln+1)) break; /* red/yellow filesystems show up twice */ if (*curline == '&') goto nextline; if ((strstr(curline, " red ") || strstr(curline, " yellow "))) goto nextline; for (columncount=0; (columncount<20); columncount++) columns[columncount] = ""; fsline = xstrdup(curline); columncount = 0; p = strtok(fsline, " "); while (p && (columncount < 20)) { columns[columncount++] = p; p = strtok(NULL, " "); } /* FD: Check TableSpace from dbcheck.pl */ /* FD: Add an initial "/" to TblSpace Name so they're reported in the trends column */ diskname=xmalloc(strlen(columns[0])+2); sprintf(diskname,"/%s",columns[0]); p = strchr(columns[4], '%'); if (p) *p = ' '; pused = atoi(columns[4]); p = columns[2] + strspn(columns[2], "0123456789."); /* FD: Using double instead of long long because we can have decimal */ dused = str2ll(columns[2], NULL); /* FD: dbspace report contains M/G/T Convert to KB if there's a modifier after the numbers */ if (*p == 'M') dused *= 1024; else if (*p == 'G') dused *= (1024*1024); else if (*p == 'T') dused *= (1024*1024*1024); aused=(long long)dused; /* Check include/exclude patterns */ wanteddisk = 1; if (exclpattern) { int ovector[30]; int result; result = pcre_exec(exclpattern, NULL, diskname, strlen(diskname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); wanteddisk = (result < 0); } if (wanteddisk && inclpattern) { int ovector[30]; int result; result = pcre_exec(inclpattern, NULL, diskname, strlen(diskname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); wanteddisk = (result >= 0); } if (wanteddisk && diskname && (pused != -1)) { p = diskname; while ((p = strchr(p, '/')) != NULL) { *p = ','; } if (strcmp(diskname, ",") == 0) { diskname = xrealloc(diskname, 6); strcpy(diskname, ",root"); } /* * Use testname here. * The disk-handler also gets data from NetAPP inode- and qtree-messages, * that are virtually identical to the disk-messages. So lets just handle * all of it by using the testname as part of the filename. */ setupfn2("%s%s.rrd", testname,diskname); sprintf(rrdvalues, "%d:%d:%lld", (int)tstamp, pused, aused); create_and_update_rrd(hostname, testname, classname, pagepaths, tablespace_params, tablespace_tpl); } if (diskname) { xfree(diskname); diskname = NULL; } if (eoln) *eoln = '\n'; xfree(fsline); nextline: curline = (eoln ? (eoln+1) : NULL); } return 0; } xymon-4.3.7/xymond/rrd/do_iishealth.c0000664000175000017500000000447411615341300017133 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char iishealth_rcsid[] = "$Id: do_iishealth.c 6712 2011-07-31 21:01:52Z storner $"; int do_iishealth_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *iishealth_params[] = { "DS:realmempct:GAUGE:600:0:U", NULL }; static void *iishealth_tpl = NULL; char *bol, *eoln, *tok; if (iishealth_tpl == NULL) iishealth_tpl = setup_template(iishealth_params); bol = strchr(msg, '\n'); if (bol) bol++; else return 0; while (bol && *bol) { eoln = strchr(bol, '\n'); if (eoln) *eoln = '\0'; tok = strtok(bol, " \t\r\n"); /* Get color marker */ if (tok) tok = strtok(NULL, " \t\r\n"); /* Get keyword */ if (tok) { int havedata = 0; if (strcmp(tok, "Connections:") == 0) { tok = strtok(NULL, " \t\r\n"); if (tok == NULL) continue; setupfn2("%s.%s.rrd", "iishealth", "connections"); sprintf(rrdvalues, "%d:%lu", (int)tstamp, atol(tok)); havedata = 1; } else if (strcmp(tok, "RequestsQueued:") == 0) { tok = strtok(NULL, " \t\r\n"); if (tok == NULL) continue; setupfn2("%s.%s.rrd", "iishealth", "requestqueued"); sprintf(rrdvalues, "%d:%lu", (int)tstamp, atol(tok)); havedata = 1; } else if (strcmp(tok, "Sessions:") == 0) { tok = strtok(NULL, " \t\r\n"); if (tok == NULL) continue; setupfn2("%s.%s.rrd", "iishealth", "sessions"); sprintf(rrdvalues, "%d:%lu", (int)tstamp, atol(tok)); havedata = 1; } if (havedata) create_and_update_rrd(hostname, testname, classname, pagepaths, iishealth_params, iishealth_tpl); } bol = (eoln ? eoln+1 : NULL); } return 0; } xymon-4.3.7/xymond/rrd/do_memory.c0000664000175000017500000002275511630612042016474 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char memory_rcsid[] = "$Id: do_memory.c 6745 2011-09-04 06:01:06Z storner $"; static char *memory_params[] = { "DS:realmempct:GAUGE:600:0:U", NULL }; static void *memory_tpl = NULL; /* * Use the R/B tree to hold names of the hosts * that we receive "memory" status from. When handling * "cpu" reports, those hosts that are in the tree do * NOT take memory data from the cpu data. */ void * memhosts; int memhosts_init = 0; static int get_mem_percent(char *l) { char *p; p = strchr(l, '%'); if (p == NULL) return 0; p--; while ( (p > l) && (isdigit((int) *p) || (*p == '.')) ) p--; return atoi(p+1); } void do_memory_rrd_update(time_t tstamp, char *hostname, char *testname, char *classname, char *pagepaths, int physval, int swapval, int actval) { if (memory_tpl == NULL) memory_tpl = setup_template(memory_params); setupfn2("%s.%s.rrd", "memory", "real"); sprintf(rrdvalues, "%d:%d", (int)tstamp, physval); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); setupfn2("%s.%s.rrd", "memory", "swap"); sprintf(rrdvalues, "%d:%d", (int)tstamp, swapval); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); if ((actval >= 0) && (actval <= 100)) { setupfn2("%s.%s.rrd", "memory", "actual"); sprintf(rrdvalues, "%d:%d", (int)tstamp, actval); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); } } /* bb-xsnmp.pl memory update - Marco Avissano */ void do_memory_rrd_update_router(time_t tstamp, char *hostname, char *testname, char *classname, char *pagepaths, int procval, int ioval, int fastval) { if (memory_tpl == NULL) memory_tpl = setup_template(memory_params); if ((procval >= 0) && (procval <= 100)) { setupfn2("%s.%s.rrd", "memory", "processor"); sprintf(rrdvalues, "%d:%d", (int)tstamp, procval); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); } if ((ioval >= 0) && (ioval <= 100)) { setupfn2("%s.%s.rrd", "memory", "io"); sprintf(rrdvalues, "%d:%d", (int)tstamp, ioval); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); } if ((fastval >= 0) && (fastval <= 100)) { setupfn2("%s.%s.rrd", "memory", "fast"); sprintf(rrdvalues, "%d:%d", (int)tstamp, fastval); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); } } int do_memory_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char *phys = NULL; char *swap = NULL; char *actual = NULL; xtreePos_t hwalk; /* Log this hostname in the list of hosts we get true "memory" reports from. */ if (!memhosts_init) { memhosts = xtreeNew(strcmp); memhosts_init = 1; } hwalk = xtreeFind(memhosts, hostname); if (hwalk == xtreeEnd(memhosts)) { char *keyp = xstrdup(hostname); if (xtreeAdd(memhosts, keyp, NULL)) { errprintf("Insert into memhosts failed\n"); } } if (strstr(msg, "z/OS Memory Map")) { long j1, j2, j3; int csautil, ecsautil, sqautil, esqautil; char *p; /* z/OS Memory Utilization: Area Alloc Used HWM Util CSA 3524 3034 3436 86.1 ECSA 20172 19979 20014 99.0 SQA 1540 222 399 14.4 ESQA 13988 4436 4726 31.7 */ p = strstr(msg, "CSA ") + 4; if (p) { sscanf(p, "%ld %ld %ld %d", &j1, &j2, &j3, &csautil); } p = strstr(msg, "ECSA ") + 5; if (p) { sscanf(p, "%ld %ld %ld %d", &j1, &j2, &j3, &ecsautil); } p = strstr(msg, "SQA ") + 4; if (p) { sscanf(p, "%ld %ld %ld %d", &j1, &j2, &j3, &sqautil); } p = strstr(msg, "ESQA ") + 5; if (p) { sscanf(p, "%ld %ld %ld %d", &j1, &j2, &j3, &esqautil); } if (memory_tpl == NULL) memory_tpl = setup_template(memory_params); setupfn2("%s.%s.rrd", "memory", "CSA"); sprintf(rrdvalues, "%d:%d", (int)tstamp, csautil); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); setupfn2("%s.%s.rrd", "memory", "ECSA"); sprintf(rrdvalues, "%d:%d", (int)tstamp, ecsautil); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); setupfn2("%s.%s.rrd", "memory", "SQA"); sprintf(rrdvalues, "%d:%d", (int)tstamp, sqautil); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); setupfn2("%s.%s.rrd", "memory", "ESQA"); sprintf(rrdvalues, "%d:%d", (int)tstamp, esqautil); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); return 0; } if (strstr(msg, "z/VSE VSIZE Utilization")) { char *p; float pctused; p = strstr(msg, "Utilization ") + 12; if (p) { sscanf(p, "%f%%", &pctused); } if (memory_tpl == NULL) memory_tpl = setup_template(memory_params); setupfn2("%s.%s.rrd", "memory", "vsize"); sprintf(rrdvalues, "%d:%d", (int)tstamp, (int)pctused); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); return 0; } if (strstr(msg, "bb-xsnmp.pl")) { /* bb-xsnmp.pl memory update - Marco Avissano */ /* Cisco Routers memory report. * Aug 22 10:52:17 2006 * Memory Used Total Percentage * green Processor 2710556 13032864 20.80% * green I/O 1664400 4194304 39.68% * green Fast 1987024 8388608 23.69% */ char *proc, *io, *fast; proc = strstr(msg, "Processor"); if (proc == NULL) proc = strstr(msg, "Processor"); io = strstr(msg, "I/O"); if (io == NULL) io = strstr(msg, "I/O"); fast = strstr(msg, "Fast"); if (fast == NULL) fast = strstr(msg, "Fast"); if (proc) { char *eoln; int procval = -1, ioval = -1, fastval = -1; eoln = strchr(proc, '\n'); if (eoln) *eoln = '\0'; procval = get_mem_percent(proc); if (eoln) *eoln = '\n'; if (io) { eoln = strchr(io, '\n'); if (eoln) *eoln = '\0'; ioval = get_mem_percent(io); if (eoln) *eoln = '\n'; } if (fast) { eoln = strchr(fast, '\n'); if (eoln) *eoln = '\0'; fastval = get_mem_percent(fast); if (eoln) *eoln = '\n'; } do_memory_rrd_update_router(tstamp, hostname, testname, classname, pagepaths, procval, ioval, fastval); } return 0; } if (strstr(msg, "Total Cache Buffers")) { /* Netware nwstat2bb memory report. * * some.host.com|memory|green||1111681798|1111681982|1111699982|0|0|127.0.0.1|-1|| * green Thu Mar 24 17:33:02 CET 2005 - Memory OK * &green Original Cache Buffers : 326622 * &green Total Cache Buffers : 56% * &green Dirty Cache Buffers : 0% * &green Long Term Cache Hit Percentage : 0% * &green Least Recently Used (LRU) sitting time : 3 weeks, 2 days, 4 hours, 25 minutes, 36 seconds */ char *p; int val; p = strstr(msg, "Total Cache Buffers"); if (p) { p = strchr(p, ':'); if (p) { val = atoi(p+1); setupfn2("%s.%s.rrd", "memory", "tcb"); sprintf(rrdvalues, "%d:%d", (int)tstamp, val); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); } } p = strstr(msg, "Dirty Cache Buffers"); if (p) { p = strchr(p, ':'); if (p) { val = atoi(p+1); setupfn2("%s.%s.rrd", "memory", "dcb"); sprintf(rrdvalues, "%d:%d", (int)tstamp, val); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); } } p = strstr(msg, "Long Term Cache Hit Percentage"); if (p) { p = strchr(p, ':'); if (p) { val = atoi(p+1); setupfn2("%s.%s.rrd", "memory", "ltch"); sprintf(rrdvalues, "%d:%d", (int)tstamp, val); create_and_update_rrd(hostname, testname, classname, pagepaths, memory_params, memory_tpl); } } return 0; } else { phys = strstr(msg, "Physical"); if (phys == NULL) phys = strstr(msg, "Real"); swap = strstr(msg, "Swap"); if (swap == NULL) swap = strstr(msg, "Page"); actual = strstr(msg, "Actual"); if (actual == NULL) actual = strstr(msg, "Virtual"); if (phys) { char *eoln; int physval = -1, swapval = -1, actval = -1; eoln = strchr(phys, '\n'); if (eoln) *eoln = '\0'; physval = get_mem_percent(phys); if (eoln) *eoln = '\n'; if (swap) { eoln = strchr(swap, '\n'); if (eoln) *eoln = '\0'; swapval = get_mem_percent(swap); if (eoln) *eoln = '\n'; } if (actual) { eoln = strchr(actual, '\n'); if (eoln) *eoln = '\0'; actval = get_mem_percent(actual); if (eoln) *eoln = '\n'; } do_memory_rrd_update(tstamp, hostname, testname, classname, pagepaths, physval, swapval, actval); } } return 0; } xymon-4.3.7/xymond/rrd/do_asid.c0000664000175000017500000000554111615341300016075 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* This module handles combined z/OS and z/VSE ASID and NPARTS messages. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* Copyright (C) 2007-2009 Rich Smrcina */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char asid_rcsid[] = "$Id: do_asid.c 6585 2010-11-14 15:12:56Z storner $"; static char *asid_params[] = { "DS:util:GAUGE:600:0:100", NULL }; static char *asid_tpl = NULL; int do_asid_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char *p; p=(strstr(msg, "Maxuser")); if (p) { long maxuser, maxufree, maxuused, rsvtstrt, rsvtfree, rsvtused, rsvnonr, rsvnfree, rsvnused; float maxupct, rsvtpct, rsvnpct; sscanf(p, "Maxuser: %ld Free: %ld Used: %ld %f", &maxuser, &maxufree, &maxuused, &maxupct); p=(strstr(p, "RSVTSTRT")); sscanf(p, "RSVTSTRT: %ld Free: %ld Used: %ld %f", &rsvtstrt, &rsvtfree, &rsvtused, &rsvtpct); p=(strstr(p, "RSVNONR")); sscanf(p, "RSVNONR: %ld Free: %ld Used: %ld %f", &rsvnonr, &rsvnfree, &rsvnused, &rsvnpct); setupfn2("%s.%s.rrd", "maxuser", "maxuser"); sprintf(rrdvalues, "%d:%d", (int)tstamp, (int)maxupct); create_and_update_rrd(hostname, testname, classname, pagepaths, asid_params, asid_tpl); setupfn2("%s.%s.rrd", "maxuser", "rsvtstrt"); sprintf(rrdvalues, "%d:%d", (int)tstamp, (int)rsvtpct); create_and_update_rrd(hostname, testname, classname, pagepaths, asid_params, asid_tpl); setupfn2("%s.%s.rrd", "maxuser", "rsvnonr"); sprintf(rrdvalues, "%d:%d", (int)tstamp, (int)rsvnpct); create_and_update_rrd(hostname, testname, classname, pagepaths, asid_params, asid_tpl); } p=(strstr(msg, "Nparts")); if (p) { char *fn = NULL; long nparts, partfree, partused; float partupct; sscanf(p, "Nparts: %ld Free: %ld Used: %ld %f", &nparts, &partfree, &partused, &partupct); setupfn("nparts.rrd", fn); sprintf(rrdvalues, "%d:%d", (int)tstamp, (int)partupct); create_and_update_rrd(hostname, testname, classname, pagepaths, asid_params, asid_tpl); } return 0; } xymon-4.3.7/xymond/rrd/do_xymongen.c0000664000175000017500000001235611615341300017023 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char xymon_rcsid[] = "$Id: do_xymongen.c 6712 2011-07-31 21:01:52Z storner $"; int do_xymongen_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *xymon_params[] = { "DS:runtime:GAUGE:600:0:U", NULL }; static void *xymon_tpl = NULL; static char *xymon2_params[] = { "DS:hostcount:GAUGE:600:0:U", "DS:statuscount:GAUGE:600:0:U", NULL }; static void *xymon2_tpl = NULL; static char *xymon3_params[] = { "DS:redcount:GAUGE:600:0:U", "DS:rednopropcount:GAUGE:600:0:U", "DS:yellowcount:GAUGE:600:0:U", "DS:yellownopropcount:GAUGE:600:0:U", "DS:greencount:GAUGE:600:0:U", "DS:purplecount:GAUGE:600:0:U", "DS:clearcount:GAUGE:600:0:U", "DS:bluecount:GAUGE:600:0:U", "DS:redpct:GAUGE:600:0:100", "DS:rednoproppct:GAUGE:600:0:100", "DS:yellowpct:GAUGE:600:0:100", "DS:yellownoproppct:GAUGE:600:0:100", "DS:greenpct:GAUGE:600:0:100", "DS:purplepct:GAUGE:600:0:100", "DS:clearpct:GAUGE:600:0:100", "DS:bluepct:GAUGE:600:0:100", NULL }; static void *xymon3_tpl = NULL; char *p, *bol, *eoln; float runtime; int hostcount, statuscount; int redcount, rednopropcount, yellowcount, yellownopropcount, greencount, purplecount, clearcount, bluecount; double pctredcount, pctrednopropcount, pctyellowcount, pctyellownopropcount, pctgreencount, pctpurplecount, pctclearcount, pctbluecount; if (xymon_tpl == NULL) xymon_tpl = setup_template(xymon_params); if (xymon2_tpl == NULL) xymon2_tpl = setup_template(xymon2_params); if (xymon3_tpl == NULL) xymon3_tpl = setup_template(xymon3_params); runtime = 0.0; hostcount = statuscount = 0; redcount = rednopropcount = yellowcount = yellownopropcount = 0; greencount = purplecount = clearcount = bluecount = 0; pctredcount = pctrednopropcount = pctyellowcount = pctyellownopropcount = 0.0; pctgreencount = pctpurplecount = pctclearcount = pctbluecount = 0.0; bol = msg; do { int *valptr = NULL; double *pctvalptr = NULL; eoln = strchr(bol, '\n'); if (eoln) *eoln = '\0'; p = bol + strspn(bol, " \t"); if (strncmp(p, "TIME TOTAL", 10) == 0) sscanf(p, "TIME TOTAL %f", &runtime); else if (strncmp(p, "Hosts", 5) == 0) valptr = &hostcount; else if (strncmp(p, "Status messages", 15) == 0) valptr = &statuscount; else if (strncmp(p, "- Red (non-propagating)", 23) == 0) { valptr = &rednopropcount; pctvalptr = &pctrednopropcount; } else if (strncmp(p, "- Red", 5) == 0) { valptr = &redcount; pctvalptr = &pctredcount; } else if (strncmp(p, "- Yellow (non-propagating)", 26) == 0) { valptr = &yellownopropcount; pctvalptr = &pctyellownopropcount; } else if (strncmp(p, "- Yellow", 8) == 0) { valptr = &yellowcount; pctvalptr = &pctyellowcount; } else if (strncmp(p, "- Green", 7) == 0) { valptr = &greencount; pctvalptr = &pctgreencount; } else if (strncmp(p, "- Purple", 8) == 0) { valptr = &purplecount; pctvalptr = &pctpurplecount; } else if (strncmp(p, "- Clear", 7) == 0) { valptr = &clearcount; pctvalptr = &pctclearcount; } else if (strncmp(p, "- Blue", 6) == 0) { valptr = &bluecount; pctvalptr = &pctbluecount; } if (valptr) { p = strchr(bol, ':'); if (p) { *valptr = atoi(p+1); if (pctvalptr) { p = strchr(p, '('); if (p) *pctvalptr = atof(p+1); } } } bol = (eoln ? eoln+1 : NULL); } while (bol); if (strcmp("xymongen", testname) != 0) { setupfn2("%s.%s.rrd", "xymongen", testname); } else { setupfn("%s.rrd", "xymongen"); } sprintf(rrdvalues, "%d:%.2f", (int)tstamp, runtime); create_and_update_rrd(hostname, testname, classname, pagepaths, xymon_params, xymon_tpl); if (strcmp("xymongen", testname) != 0) { setupfn2("%s.%s.rrd", "xymon", testname); } else { setupfn("%s.rrd", "xymon"); } sprintf(rrdvalues, "%d:%d:%d", (int)tstamp, hostcount, statuscount); create_and_update_rrd(hostname, testname, classname, pagepaths, xymon2_params, xymon2_tpl); if (strcmp("xymongen", testname) != 0) { setupfn2("%s.%s.rrd", "xymon2", testname); } else { setupfn("%s.rrd", "xymon2"); } sprintf(rrdvalues, "%d:%d:%d:%d:%d:%d:%d:%d:%d:%5.2f:%5.2f:%5.2f:%5.2f:%5.2f:%5.2f:%5.2f:%5.2f", (int)tstamp, redcount, rednopropcount, yellowcount, yellownopropcount, greencount, purplecount, clearcount, bluecount, pctredcount, pctrednopropcount, pctyellowcount, pctyellownopropcount, pctgreencount, pctpurplecount, pctclearcount, pctbluecount); create_and_update_rrd(hostname, testname, classname, pagepaths, xymon3_params, xymon3_tpl); return 0; } xymon-4.3.7/xymond/rrd/do_ifstat.c0000664000175000017500000003027511665414506016467 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char ifstat_rcsid[] = "$Id: do_ifstat.c 6782 2011-11-30 11:53:42Z storner $"; static char *ifstat_params[] = { "DS:bytesSent:DERIVE:600:0:U", "DS:bytesReceived:DERIVE:600:0:U", NULL }; static void *ifstat_tpl = NULL; /* eth0 Link encap: */ /* RX bytes: 1829192 (265.8 MiB) TX bytes: 1827320 (187.7 MiB */ static const char *ifstat_linux_exprs[] = { "^([a-z]+[0123456789.:]*|lo)\\s", "^\\s+RX bytes:([0-9]+) .*TX bytes.([0-9]+) " }; /* Name MTU Network IP Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll */ /* lnc0 1500 172.16.10.0/24 172.16.10.151 26 - 1818 26 - 1802 - */ static const char *ifstat_freebsd_exprs[] = { "^([a-z0-9]+)\\s+\\d+\\s+[0-9.\\/]+\\s+[0-9.]+\\s+\\d+\\s+[0-9-]+\\s+(\\d+)\\s+\\d+\\s+[0-9-]+\\s+(\\d+)\\s+[0-9-]+" }; /* Name Mtu Network Address Ipkts Ierrs Idrop Ibytes Opkts Oerrs Obytes Coll */ /* bge0 1500 192.168.X.X 192.168.X.X 29292829 - - 1130285651 26543376 - 2832025203 - */ static const char *ifstat_freebsdV8_exprs[] = { "^([a-z0-9]+)\\s+\\d+\\s+[0-9.\\/]+\\s+[0-9.]+\\s+\\d+\\s+[0-9-]+\\s+[0-9-]+\\s+(\\d+)\\s+\\d+\\s+[0-9-]+\\s+(\\d+)\\s+[0-9-]+" }; /* Name MTU Network IP Ibytes Obytes */ /* lnc0 1500 172.16.10.0/24 172.16.10.151 1818 1802 */ static const char *ifstat_openbsd_exprs[] = { "^([a-z0-9]+)\\s+\\d+\\s+[0-9.\\/]+\\s+[0-9.]+\\s+(\\d+)\\s+(\\d+)" }; /* Name MTU Network IP Ibytes Obytes */ /* lnc0 1500 172.16.10.0/24 172.16.10.151 1818 1802 */ static const char *ifstat_netbsd_exprs[] = { "^([a-z0-9]+)\\s+\\d+\\s+[0-9.\\/]+\\s+[0-9.]+\\s+(\\d+)\\s+(\\d+)" }; /* Name Mtu Network Address Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll en0 1500 fe80::20d:9 fe80::20d:93ff:fe 2013711826 - 2131205566781 331648829 - 41815551289 - en0 1500 130.223.20/24 130.223.20.20 2013711826 - 2131205566781 331648829 - 41815551289 - */ static const char *ifstat_darwin_exprs[] = { "^([a-z0-9]+)\\s+\\d+\\s+[0-9.\\/]+\\s+[0-9.]+\\s+\\d+\\s+[0-9-]+\\s+(\\d+)\\s+\\d+\\s+[0-9-]+\\s+(\\d+)\\s+[0-9-]+" }; /* dmfe:0:dmfe0:obytes64 107901705585 */ /* dmfe:0:dmfe0:rbytes64 1224808818952 */ /* dmfe:1:dmfe1:obytes64 0 */ /* dmfe:1:dmfe1:rbytes64 0 */ static const char *ifstat_solaris_exprs[] = { "^[a-z0-9]+:\\d+:([a-z0-9]+):obytes64\\s+(\\d+)", "^[a-z0-9]+:\\d+:([a-z0-9]+):rbytes64\\s+(\\d+)" }; /* ETHERNET STATISTICS (ent0) : Device Type: 2-Port 10/100/1000 Base-TX PCI-X Adapter (14108902) Hardware Address: 00:11:25:e6:0d:36 Elapsed Time: 45 days 20 hours 18 minutes 41 seconds Transmit Statistics: Receive Statistics: -------------------- ------------------- Packets: 1652404 Packets: 768800 Bytes: 1966314449 Bytes: 78793615 */ static const char *ifstat_aix_exprs[] = { "^ETHERNET STATISTICS \\(([a-z0-9]+)\\) :", "^Bytes:\\s+(\\d+)\\s+Bytes:\\s+(\\d+)" }; /* (lines dropped) PPA Number = 0 Description = lan0 Hewlett-Packard LAN Interface Hw Rev 0 Type (value) = ethernet-csmacd(6) MTU Size = 1500 Operation Status (value) = up(1) Inbound Octets = 3111235429 Outbound Octets = 3892111463 */ static const char *ifstat_hpux_exprs[] = { "^PPA Number\\s+= (\\d+)", "^Inbound Octets\\s+= (\\d+)", "^Outbound Octets\\s+= (\\d+)", }; /* Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll net0 1500 195.75.9 10.1.1.2 13096313 0 12257642 0 0 lo0 8232 127 127.0.0.1 26191 0 26191 0 0 Attention, theses numbers are packets, not bytes ! */ static const char *ifstat_sco_sv_exprs[] = { "^([a-z]+[0-9]+)\\s+[0-9]+\\s+[0-9.]+\\s+[0-9.]+\\s+([0-9]+)\\s+[0-9]+\\s+([0-9]+)\\s+" }; /* IP Ibytes Obytes */ /* 192.168.0.1 1818 1802 */ static const char *ifstat_bbwin_exprs[] = { "^([a-zA-Z0-9.:]+)\\s+([0-9]+)\\s+([0-9]+)" }; int do_ifstat_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static int pcres_compiled = 0; static pcre **ifstat_linux_pcres = NULL; static pcre **ifstat_freebsd_pcres = NULL; static pcre **ifstat_freebsdV8_pcres = NULL; static pcre **ifstat_openbsd_pcres = NULL; static pcre **ifstat_netbsd_pcres = NULL; static pcre **ifstat_darwin_pcres = NULL; static pcre **ifstat_solaris_pcres = NULL; static pcre **ifstat_aix_pcres = NULL; static pcre **ifstat_hpux_pcres = NULL; static pcre **ifstat_sco_sv_pcres = NULL; static pcre **ifstat_bbwin_pcres = NULL; enum ostype_t ostype; char *datapart = msg; char *outp; char *bol, *eoln, *ifname, *rxstr, *txstr, *dummy; int dmatch; void *xmh; pcre *ifname_filter_pcre = NULL; xmh = hostinfo(hostname); if (xmh) { char *ifname_filter_expr = xmh_item(xmh, XMH_INTERFACES); if (ifname_filter_expr && *ifname_filter_expr) ifname_filter_pcre = compileregex(ifname_filter_expr); } if (pcres_compiled == 0) { pcres_compiled = 1; ifstat_linux_pcres = compile_exprs("LINUX", ifstat_linux_exprs, (sizeof(ifstat_linux_exprs) / sizeof(ifstat_linux_exprs[0]))); ifstat_freebsd_pcres = compile_exprs("FREEBSD", ifstat_freebsd_exprs, (sizeof(ifstat_freebsd_exprs) / sizeof(ifstat_freebsd_exprs[0]))); ifstat_freebsdV8_pcres = compile_exprs("FREEBSD", ifstat_freebsdV8_exprs, (sizeof(ifstat_freebsdV8_exprs) / sizeof(ifstat_freebsdV8_exprs[0]))); ifstat_openbsd_pcres = compile_exprs("OPENBSD", ifstat_openbsd_exprs, (sizeof(ifstat_openbsd_exprs) / sizeof(ifstat_openbsd_exprs[0]))); ifstat_netbsd_pcres = compile_exprs("NETBSD", ifstat_netbsd_exprs, (sizeof(ifstat_netbsd_exprs) / sizeof(ifstat_netbsd_exprs[0]))); ifstat_darwin_pcres = compile_exprs("DARWIN", ifstat_darwin_exprs, (sizeof(ifstat_darwin_exprs) / sizeof(ifstat_darwin_exprs[0]))); ifstat_solaris_pcres = compile_exprs("SOLARIS", ifstat_solaris_exprs, (sizeof(ifstat_solaris_exprs) / sizeof(ifstat_solaris_exprs[0]))); ifstat_aix_pcres = compile_exprs("AIX", ifstat_aix_exprs, (sizeof(ifstat_aix_exprs) / sizeof(ifstat_aix_exprs[0]))); ifstat_hpux_pcres = compile_exprs("HPUX", ifstat_hpux_exprs, (sizeof(ifstat_hpux_exprs) / sizeof(ifstat_hpux_exprs[0]))); ifstat_sco_sv_pcres = compile_exprs("SCO_SV", ifstat_sco_sv_exprs, (sizeof(ifstat_sco_sv_exprs) / sizeof(ifstat_sco_sv_exprs[0]))); ifstat_bbwin_pcres = compile_exprs("BBWIN", ifstat_bbwin_exprs, (sizeof(ifstat_bbwin_exprs) / sizeof(ifstat_bbwin_exprs[0]))); } if (ifstat_tpl == NULL) ifstat_tpl = setup_template(ifstat_params); if ((strncmp(msg, "status", 6) == 0) || (strncmp(msg, "data", 4) == 0)) { /* Skip the first line of full status- and data-messages. */ datapart = strchr(msg, '\n'); if (datapart) datapart++; else datapart = msg; } ostype = get_ostype(datapart); datapart = strchr(datapart, '\n'); if (datapart) { datapart++; } else { errprintf("Too few lines in ifstat report from %s\n", hostname); return -1; } /* Setup the update string */ outp = rrdvalues + sprintf(rrdvalues, "%d", (int)tstamp); dmatch = 0; ifname = rxstr = txstr = dummy = NULL; bol = datapart; while (bol) { eoln = strchr(bol, '\n'); if (eoln) *eoln = '\0'; switch (ostype) { case OS_LINUX22: case OS_LINUX: case OS_RHEL3: case OS_ZVM: case OS_ZVSE: case OS_ZOS: if (pickdata(bol, ifstat_linux_pcres[0], 1, &ifname)) { /* * Linux' netif aliases mess up things. * Clear everything when we see an interface name. * But we dont want to track the "lo" interface. */ if (strcmp(ifname, "lo") == 0) { xfree(ifname); ifname = NULL; } else { dmatch = 1; if (rxstr) { xfree(rxstr); rxstr = NULL; } if (txstr) { xfree(txstr); txstr = NULL; } } } else if (pickdata(bol, ifstat_linux_pcres[1], 1, &rxstr, &txstr)) dmatch |= 6; break; case OS_FREEBSD: /* * FreeBSD 8 added an "Idrop" counter in the middle of the data. * See if we match this expression, and if not then fall back to * the old regex without that field. */ if (pickdata(bol, ifstat_freebsdV8_pcres[0], 0, &ifname, &rxstr, &txstr)) dmatch = 7; else if (pickdata(bol, ifstat_freebsd_pcres[0], 0, &ifname, &rxstr, &txstr)) dmatch = 7; break; case OS_OPENBSD: if (pickdata(bol, ifstat_openbsd_pcres[0], 0, &ifname, &rxstr, &txstr)) dmatch = 7; break; case OS_NETBSD: if (pickdata(bol, ifstat_netbsd_pcres[0], 0, &ifname, &rxstr, &txstr)) dmatch = 7; break; case OS_SOLARIS: if (pickdata(bol, ifstat_solaris_pcres[0], 0, &ifname, &txstr)) dmatch |= 1; else if (pickdata(bol, ifstat_solaris_pcres[1], 0, &dummy, &rxstr)) dmatch |= 6; if (ifname && dummy && (strcmp(ifname, dummy) != 0)) { /* They must match, drop the data */ errprintf("Host %s has weird ifstat data - device name mismatch %s:%s\n", hostname, ifname, dummy); xfree(ifname); xfree(txstr); xfree(rxstr); xfree(dummy); dmatch = 0; } /* Ignore "mac" and "wrsmd" entries - these are for sub-devices for multiple nic's aggregated into one */ /* See http://www.xymon.com/archive/2009/06/msg00204.html for more info */ if (ifname && ((strcmp(ifname, "mac") == 0) || (strcmp(ifname, "wrsmd") == 0)) ) { xfree(ifname); xfree(txstr); dmatch = 0; } if (dummy && ((strcmp(dummy, "mac") == 0) || (strcmp(dummy, "wrsmd") == 0)) ) { xfree(dummy); xfree(rxstr); dmatch = 0; } break; case OS_AIX: if (pickdata(bol, ifstat_aix_pcres[0], 1, &ifname)) { /* Interface names comes first, so any rx/tx data is discarded */ dmatch |= 1; if (rxstr) { xfree(rxstr); rxstr = NULL; } if (txstr) { xfree(txstr); txstr = NULL; } } else if (pickdata(bol, ifstat_aix_pcres[1], 1, &txstr, &rxstr)) dmatch |= 6; break; case OS_HPUX: if (pickdata(bol, ifstat_hpux_pcres[0], 1, &ifname)) { /* Interface names comes first, so any rx/tx data is discarded */ dmatch |= 1; if (rxstr) { xfree(rxstr); rxstr = NULL; } if (txstr) { xfree(txstr); txstr = NULL; } } else if (pickdata(bol, ifstat_hpux_pcres[1], 1, &rxstr)) dmatch |= 2; else if (pickdata(bol, ifstat_hpux_pcres[2], 1, &txstr)) dmatch |= 4; break; case OS_DARWIN: if (pickdata(bol, ifstat_darwin_pcres[0], 0, &ifname, &rxstr, &txstr)) dmatch = 7; break; case OS_SCO_SV: if (pickdata(bol, ifstat_sco_sv_pcres[0], 0, &ifname, &rxstr, &txstr)) dmatch = 7; break; case OS_WIN32_BBWIN: if (pickdata(bol, ifstat_bbwin_pcres[0], 0, &ifname, &rxstr, &txstr)) dmatch = 7; break; default: break; } if ((dmatch == 7) && ifname && rxstr && txstr) { if (!ifname_filter_pcre || matchregex(ifname, ifname_filter_pcre)) { setupfn2("%s.%s.rrd", "ifstat", ifname); sprintf(rrdvalues, "%d:%s:%s", (int)tstamp, txstr, rxstr); create_and_update_rrd(hostname, testname, classname, pagepaths, ifstat_params, ifstat_tpl); } xfree(ifname); xfree(rxstr); xfree(txstr); if (dummy) xfree(dummy); ifname = rxstr = txstr = dummy = NULL; dmatch = 0; } if (eoln) { *eoln = '\n'; bol = eoln+1; if (*bol == '\0') bol = NULL; } else { bol = NULL; } } if (ifname_filter_pcre) freeregex(ifname_filter_pcre); if (ifname) xfree(ifname); if (rxstr) xfree(rxstr); if (txstr) xfree(txstr); if (dummy) xfree(dummy); return 0; } xymon-4.3.7/xymond/rrd/do_fd_lib.c0000664000175000017500000001127111535424634016406 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2006 Francesco Duranti */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ /* Some function used by do_netapp.pl do_beastat.pl and do_dbcheck.pl */ static char fd_lib_rcs[] = "$Id: do_fd_lib.c 6648 2011-03-08 13:05:32Z storner $"; unsigned long get_kb_data(char *msg,char *search) { char *p,*r,*eoln; unsigned long value=0; /* go to the start of the message */ p = strstr(msg, search); if (p) { /* set the endofline */ eoln = strchr(p, '\n'); if (eoln) *eoln = '\0'; /* search for a ":" or "=" */ if ((r = strchr(p,':')) == NULL) r=strchr(p,'='); // dbgprintf("1getdata %s\n",p); // dbgprintf("2getdata %s\n",r); if (r) { r++; value=atol(r); /* Convert to KB if there's a modifier after the numbers */ r += strspn(r, "0123456789. "); if (*r == 'M') value *= 1024; else if (*r == 'G') value *= (1024*1024); else if (*r == 'T') value *= (1024*1024*1024); } /* reset the endofline */ if (eoln) *eoln = '\n'; } return value; } unsigned long get_long_data(char *msg,char *search) { char *p,*r,*eoln; unsigned long value=0; /* go to the start of the message */ p = strstr(msg, search); if (p) { /* set the endofline */ eoln = strchr(p, '\n'); if (eoln) *eoln = '\0'; /* search for a ":" or "=" */ if ((r = strchr(p, ':')) == NULL) r=strchr(p, '='); // dbgprintf("1getdata %s\n",p); // dbgprintf("2getdata %s\n",r); if (r) { value=atol(++r); } /* reset the endofline */ if (eoln) *eoln = '\n'; } return value; } double get_double_data(char *msg,char *search) { char *p,*r,*eoln; double value=0; /* go to the start of the message */ p = strstr(msg, search); if (p) { /* set the endofline */ eoln = strchr(p, '\n'); if (eoln) *eoln = '\0'; /* search for a ":" or "=" */ if ((r = strchr(p,':')) == NULL) r=strchr(p,'='); // dbgprintf("1getdata %s\n",p); // dbgprintf("2getdata %s\n",r); if (r) value=atof(++r); /* reset the endofline */ if (eoln) *eoln = '\n'; } return value; } int get_int_data(char *msg,char *search) { char *p,*r,*eoln; int value=0; /* go to the start of the message */ p = strstr(msg, search); if (p) { /* set the endofline */ eoln = strchr(p, '\n'); if (eoln) *eoln = '\0'; /* search for a ":" or "=" */ if ((r = strchr(p,':')) == NULL) r=strchr(p,'='); // dbgprintf("\n1getdata\n%s\n",p); // dbgprintf("\n2getdata\n%s\n",r); if (r) value=atoi(++r); /* reset the endofline */ if (eoln) *eoln = '\n'; } return value; } typedef struct sectlist_t { char *sname; char *sdata; struct sectlist_t *next; } sectlist_t; sectlist_t *sections = NULL; void splitmsg(char *clientdata) { char *cursection, *nextsection; char *sectname, *sectdata; /* Free the old list */ if (sections) { sectlist_t *swalk, *stmp; swalk = sections; while (swalk) { stmp = swalk; swalk = swalk->next; xfree(stmp); } sections = NULL; } if (clientdata == NULL) { errprintf("Got a NULL client data message\n"); return; } /* Find the start of the first section */ if (*clientdata == '[') cursection = clientdata; else { cursection = strstr(clientdata, "\n["); if (cursection) cursection++; } while (cursection) { sectlist_t *newsect = (sectlist_t *)malloc(sizeof(sectlist_t)); /* Find end of this section (i.e. start of the next section, if any) */ nextsection = strstr(cursection, "\n["); if (nextsection) { *nextsection = '\0'; nextsection++; } /* Pick out the section name and data */ sectname = cursection+1; sectdata = sectname + strcspn(sectname, "]\n"); *sectdata = '\0'; sectdata++; if (*sectdata == '\n') sectdata++; /* Save the pointers in the list */ newsect->sname = sectname; newsect->sdata = sectdata; newsect->next = sections; sections = newsect; /* Next section, please */ cursection = nextsection; } } char *getdata(char *sectionname) { sectlist_t *swalk; for (swalk = sections; (swalk && strcmp(swalk->sname, sectionname)); swalk = swalk->next) ; if (swalk) return swalk->sdata; return NULL; } xymon-4.3.7/xymond/rrd/do_devmon.c0000664000175000017500000000765511630352262016463 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module for Devmon */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* Copyright (C) 2008 Buchan Milne */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char devmon_rcsid[] = "$Id $"; int do_devmon_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { #define MAXCOLS 20 char *devmon_params[MAXCOLS+7] = { NULL, }; char *eoln, *curline; static int ptnsetup = 0; static pcre *inclpattern = NULL; static pcre *exclpattern = NULL; int in_devmon = 1; int numds = 0; char *rrdbasename; int lineno = 0; rrdbasename = NULL; curline = msg; while (curline) { char *fsline = NULL; char *p; char *columns[MAXCOLS]; int columncount; char *ifname = NULL; int pused = -1; int wanteddisk = 1; long long aused = 0; char *dsval; int i; eoln = strchr(curline, '\n'); if (eoln) *eoln = '\0'; lineno++; if(!strncmp(curline, "",3)) { in_devmon = 1; goto nextline; } if (in_devmon != 0 ) goto nextline; for (columncount=0; (columncount 2) { dbgprintf("Skipping line %d, found %d (max 2) columns in devmon rrd data, space in repeater name?\n",lineno,columncount); goto nextline; } /* Now we should be on to values: * eth0.0 4678222:9966777 */ ifname = xstrdup(columns[0]); dsval = strtok(columns[1],":"); if (dsval == NULL) { dbgprintf("Skipping line %d, line is malformed\n",lineno); goto nextline; } sprintf(rrdvalues, "%d:", (int)tstamp); strcat(rrdvalues,dsval); for (i=1;i < numds;i++) { dsval = strtok(NULL,":"); if (dsval == NULL) { dbgprintf("Skipping line %d, %d tokens present, expecting %d\n",lineno,i,numds); goto nextline; } strcat(rrdvalues,":"); strcat(rrdvalues,dsval); } /* File names in the format if_load.eth0.0.rrd */ setupfn2("%s.%s.rrd", rrdbasename, ifname); dbgprintf("Sending from devmon to RRD for %s %s: %s\n",rrdbasename,ifname,rrdvalues); create_and_update_rrd(hostname, testname, classname, pagepaths, devmon_params, NULL); if (ifname) { xfree(ifname); ifname = NULL; } if (eoln) *eoln = '\n'; nextline: if (fsline) { xfree(fsline); fsline = NULL; } curline = (eoln ? (eoln+1) : NULL); } return 0; } xymon-4.3.7/xymond/rrd/do_filesizes.c0000664000175000017500000000351111615341300017145 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* This module handles "filesizes" messages. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char filesize_rcsid[] = "$Id: do_filesizes.c 6712 2011-07-31 21:01:52Z storner $"; static char *filesize_params[] = { "DS:size:GAUGE:600:0:U", NULL }; static void *filesize_tpl = NULL; int do_filesizes_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char *boln, *eoln; if (filesize_tpl == NULL) filesize_tpl = setup_template(filesize_params); boln = strchr(msg, '\n'); if (boln) boln++; while (boln && *boln) { char *fn, *szstr = NULL; eoln = strchr(boln, '\n'); if (eoln) *eoln = '\0'; fn = strtok(boln, ":"); if (fn) szstr = strtok(NULL, ":"); if (fn && szstr) { char *p; for (p=strchr(fn, '/'); (p); p = strchr(p, '/')) *p = ','; setupfn2("%s.%s.rrd", "filesizes", fn); sprintf(rrdvalues, "%d:%s", (int)tstamp, szstr); create_and_update_rrd(hostname, testname, classname, pagepaths, filesize_params, filesize_tpl); } boln = (eoln ? eoln+1 : NULL); } return 0; } xymon-4.3.7/xymond/rrd/do_mailq.c0000664000175000017500000000606111615341300016256 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char mailq_rcsid[] = "$Id: do_mailq.c 6712 2011-07-31 21:01:52Z storner $"; int do_mailq_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *mailq_params[] = { "DS:mailq:GAUGE:600:0:U", NULL }; static void *mailq_tpl = NULL; char *p; char *inqueue, *outqueue; int mailq, inq, outq; if (mailq_tpl == NULL) mailq_tpl = setup_template(mailq_params); /* * The normail "mailq" report only has a "... N requests" line and a single graph. * Erik's enhanced script has both an incoming and an outgoing mail queue, with * two different RRD's. We'll try to handle both setups. */ outqueue = strstr(msg, "\nMail queue out:"); inqueue = strstr(msg, "\nMail queue in:"); if (inqueue && outqueue) { /* Dual queue message */ /* Skip the "Mail queue X" line */ outqueue = strchr(outqueue+1, '\n'); inqueue = strchr(inqueue+1, '\n'); if ((outqueue == NULL) || (inqueue == NULL)) return 0; outqueue++; inqueue++; /* Next line has "&green Mail Queue (26 requests)". Cut string at end-of-line */ p = strchr(outqueue, '\n'); if (p) *p = '\0'; p = strchr(inqueue, '\n'); if (p) *p = '\0'; /* Skip until we find a number, and get the digit. */ p = outqueue + strcspn(outqueue, "0123456789"); outq = atoi(p); p = inqueue + strcspn(inqueue, "0123456789"); inq = atoi(p); /* Update RRD's */ setupfn("%s.rrd", "mailqin"); sprintf(rrdvalues, "%d:%d", (int)tstamp, inq); create_and_update_rrd(hostname, testname, classname, pagepaths, mailq_params, mailq_tpl); setupfn("%s.rrd", "mailqout"); sprintf(rrdvalues, "%d:%d", (int)tstamp, outq); create_and_update_rrd(hostname, testname, classname, pagepaths, mailq_params, mailq_tpl); return 0; } else { char *valptr; /* Looking for "... N requests ... " */ valptr = strstr(msg, "requests"); if (valptr) { /* Go back past any whitespace before "requests" */ do { valptr--; } while ((valptr > msg) && (*valptr != '\n') && isspace((int)*valptr)); /* Go back to the beginning of the number */ while ((valptr > msg) && isdigit((int) *(valptr-1))) valptr--; mailq = atoi(valptr); setupfn("%s.rrd", "mailq"); sprintf(rrdvalues, "%d:%d", (int)tstamp, mailq); return create_and_update_rrd(hostname, testname, classname, pagepaths, mailq_params, mailq_tpl); } } return 0; } xymon-4.3.7/xymond/rrd/do_getvis.c0000664000175000017500000000443511615341300016457 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* This module handles "getvis" messages. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* Copyright (C) 2008 Rich Smrcina */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char getvis_rcsid[] = "$Id: do_getvis.c 6585 2010-11-14 15:12:56Z storner $"; static char *getvis_params[] = { "DS:below:GAUGE:600:0:100", "DS:any:GAUGE:600:0:100", NULL }; static char *getvis_tpl = NULL; int do_getvis_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char *p; char pid[4], jnm[9]; int j1, j2, j3, j4, j5, j6; /* All junk, don't need it here */ int used24p, usedanyp; if (strstr(msg, "z/VSE Getvis Map")) { p = strstr(msg, "PID "); if (!p) { return 0; } p = strtok(p, "\n"); /* Skip heading line */ if (p) { p = strtok(NULL, "\n"); } while (p != NULL) { sscanf(p, "%s %s %d %d %d %d %d %d %d %d", pid, jnm, &j1, &j2, &j3, &j4, &j5, &j6, &used24p, &usedanyp); setupfn2("%s.%s.rrd", "getvis", pid); sprintf(rrdvalues, "%d:%d:%d", (int)tstamp, used24p, usedanyp); create_and_update_rrd(hostname, testname, classname, pagepaths, getvis_params, getvis_tpl); p = strtok(NULL, "\n"); } } return 0; } xymon-4.3.7/xymond/rrd/do_ifmib.c0000664000175000017500000001341011615341300016235 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char ifmib_rcsid[] = "$Id: do_ifmib.c 6712 2011-07-31 21:01:52Z storner $"; static char *ifmib_params[] = { "DS:ifInNUcastPkts:COUNTER:600:0:U", "DS:ifInDiscards:COUNTER:600:0:U", "DS:ifInErrors:COUNTER:600:0:U", "DS:ifInUnknownProtos:COUNTER:600:0:U", "DS:ifOutNUcastPkts:COUNTER:600:0:U", "DS:ifOutDiscards:COUNTER:600:0:U", "DS:ifOutErrors:COUNTER:600:0:U", "DS:ifOutQLen:GAUGE:600:0:U", "DS:ifInMcastPkts:COUNTER:600:0:U", "DS:ifInBcastPkts:COUNTER:600:0:U", "DS:ifOutMcastPkts:COUNTER:600:0:U", "DS:ifOutBcastPkts:COUNTER:600:0:U", "DS:ifHCInMcastPkts:COUNTER:600:0:U", "DS:ifHCInBcastPkts:COUNTER:600:0:U", "DS:ifHCOutMcastPkts:COUNTER:600:0:U", "DS:ifHCOutBcastPkts:COUNTER:600:0:U", "DS:InOctets:COUNTER:600:0:U", "DS:OutOctets:COUNTER:600:0:U", "DS:InUcastPkts:COUNTER:600:0:U", "DS:OutUcastPkts:COUNTER:600:0:U", NULL }; static void *ifmib_tpl = NULL; static char *ifmib_valnames[] = { /* These are in the standard interface MIB */ "ifInNUcastPkts", /* 0 */ "ifInDiscards", "ifInErrors", "ifInUnknownProtos", "ifOutNUcastPkts", /* 4 */ "ifOutDiscards", "ifOutErrors", "ifOutQLen", /* The following are the 64-bit counters in the extended MIB */ "ifInMulticastPkts", /* 8 */ "ifInBroadcastPkts", "ifOutMulticastPkts", "ifOutBroadcastPkts", "ifHCInMulticastPkts", /* 12 */ "ifHCInBroadcastPkts", "ifHCOutMulticastPkts", "ifHCOutBroadcastPkts", /* The following counters may be in both 32- (standard) and 64-bit (extended) versions. */ "ifInOctets", /* 16 */ "ifHCInOctets", "ifOutOctets", "ifHCOutOctets", "ifInUcastPkts", /* 20 */ "ifHCInUcastPkts", "ifOutUcastPkts", "ifHCOutUcastPkts", NULL }; static void ifmib_flush_data(int ifmibinterval, char *devname, time_t tstamp, char *hostname, char *testname, char *classname, char *pagepaths, char **values, int inidx, int outidx, int inUcastidx, int outUcastidx) { setupfn2("%s.%s.rrd", "ifmib", devname); setupinterval(ifmibinterval); sprintf(rrdvalues, "%d:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s", (int)tstamp, values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8], values[9], values[10], values[11], values[12], values[13], values[14], values[15], values[inidx], values[outidx], values[inUcastidx], values[outUcastidx]); create_and_update_rrd(hostname, testname, classname, pagepaths, ifmib_params, ifmib_tpl); } int do_ifmib_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char *datapart = msg; char *bol, *eoln; char *devname = NULL; char *values[sizeof(ifmib_valnames)/sizeof(ifmib_valnames[0])]; int valcount = 0; int incountidx = 16, outcountidx = 18, inUcastidx = 20, outUcastidx = 22; int pollinterval = 0; if (ifmib_tpl == NULL) ifmib_tpl = setup_template(ifmib_params); if ((strncmp(msg, "status", 6) == 0) || (strncmp(msg, "data", 4) == 0)) { /* Skip the first line of full status- and data-messages. */ datapart = strchr(msg, '\n'); if (datapart) datapart++; else datapart = msg; } memset(values, 0, sizeof(values)); valcount = 0; devname = NULL; bol = datapart; while (bol) { eoln = strchr(bol, '\n'); if (eoln) *eoln = '\0'; bol += strspn(bol, " \t"); if (*bol == '\0') { /* Nothing */ } else if (strncmp(bol, "Interval=", 9) == 0) { pollinterval = atoi(bol+9); } else if (*bol == '[') { /* New interface data begins */ if (devname && (valcount == 24)) { ifmib_flush_data(pollinterval, devname, tstamp, hostname, testname, classname, pagepaths, values, incountidx, outcountidx, inUcastidx, outUcastidx); memset(values, 0, sizeof(values)); valcount = 0; devname = NULL; incountidx = 16; outcountidx = 18; inUcastidx = 20; outUcastidx = 22; } devname = bol+1; bol = strchr(bol, ']'); if (bol) *bol = '\0'; } else { char *valnam, *valstr = NULL; valnam = strtok(bol, " ="); if (valnam) valstr = strtok(NULL, " ="); if (valnam && valstr) { int validx; for (validx = 0; (ifmib_valnames[validx] && strcmp(ifmib_valnames[validx], valnam)); validx++) ; if (ifmib_valnames[validx]) { values[validx] = (isdigit(*valstr) ? valstr : "U"); valcount++; /* See if this is one of the high-speed in/out counts */ if (*values[validx] != 'U') { if (validx == 17) incountidx = validx; if (validx == 19) outcountidx = validx; if (validx == 21) inUcastidx = validx; if (validx == 23) outUcastidx = validx; } } } } bol = (eoln ? eoln+1 : NULL); } /* Flush the last device */ if (devname && (valcount == 24)) { ifmib_flush_data(pollinterval, devname, tstamp, hostname, testname, classname, pagepaths, values, incountidx, outcountidx, inUcastidx, outUcastidx); valcount = 0; } return 0; } xymon-4.3.7/xymond/rrd/do_net.c0000664000175000017500000001202511615341300015736 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char do_net_rcsid[] = "$Id: do_net.c 6712 2011-07-31 21:01:52Z storner $"; int do_net_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *xymonnet_params[] = { "DS:sec:GAUGE:600:0:U", NULL }; static void *xymonnet_tpl = NULL; char *p; float seconds = 0.0; int do_default = 1; if (xymonnet_tpl == NULL) xymonnet_tpl = setup_template(xymonnet_params); if (strcmp(testname, "http") == 0) { char *line1, *url = NULL, *eoln; do_default = 0; line1 = msg; while ((line1 = strchr(line1, '\n')) != NULL) { line1++; /* Skip the newline */ eoln = strchr(line1, '\n'); if (eoln) *eoln = '\0'; if ( (strncmp(line1, "&green", 6) == 0) || (strncmp(line1, "&yellow", 7) == 0) || (strncmp(line1, "&red", 4) == 0) ) { p = strstr(line1, "http"); if (p) { url = xstrdup(p); p = strchr(url, ' '); if (p) *p = '\0'; } } else if (url && ((p = strstr(line1, "Seconds:")) != NULL) && (sscanf(p, "Seconds: %f", &seconds) == 1)) { char *urlfn = url; if (strncmp(urlfn, "http://", 7) == 0) urlfn += 7; p = urlfn; while ((p = strchr(p, '/')) != NULL) *p = ','; setupfn3("%s.%s.%s.rrd", "tcp", "http", urlfn); sprintf(rrdvalues, "%d:%.2f", (int)tstamp, seconds); create_and_update_rrd(hostname, testname, classname, pagepaths, xymonnet_params, xymonnet_tpl); xfree(url); url = NULL; } if (eoln) *eoln = '\n'; } if (url) xfree(url); } else if (strcmp(testname, xgetenv("PINGCOLUMN")) == 0) { /* * Ping-tests, possibly using fping. */ char *tmod = "ms"; do_default = 0; if ((p = strstr(msg, "time=")) != NULL) { /* Standard ping, reports ".... time=0.2 ms" */ seconds = atof(p+5); tmod = p + 5; tmod += strspn(tmod, "0123456789. "); } else if ((p = strstr(msg, "alive")) != NULL) { /* fping, reports ".... alive (0.43 ms)" */ seconds = atof(p+7); tmod = p + 7; tmod += strspn(tmod, "0123456789. "); } if (strncmp(tmod, "ms", 2) == 0) seconds = seconds / 1000.0; else if (strncmp(tmod, "usec", 4) == 0) seconds = seconds / 1000000.0; setupfn2("%s.%s.rrd", "tcp", testname); sprintf(rrdvalues, "%d:%.6f", (int)tstamp, seconds); return create_and_update_rrd(hostname, testname, classname, pagepaths, xymonnet_params, xymonnet_tpl); } else if (strcmp(testname, "ntp") == 0) { /* * sntp output: * 2009 Nov 13 11:29:10.000313 + 0.038766 +/- 0.052900 secs * ntpdate output: * server 172.16.10.2, stratum 3, offset -0.040324, delay 0.02568 * 13 Nov 11:29:06 ntpdate[7038]: adjust time server 172.16.10.2 offset -0.040324 sec */ char dataforntpstat[100]; char *offsetval = NULL; char *msgcopy = strdup(msg); if (strstr(msgcopy, "ntpdate") != NULL) { /* Old-style "ntpdate" output */ char *p; p = strstr(msgcopy, "offset "); if (p) { p += 7; offsetval = strtok(p, " \r\n\t"); } } else if (strstr(msgcopy, " secs") != NULL) { /* Probably new "sntp" output */ char *year, *month, *tm, *offsetdirection, *offset, *plusminus, *errorbound, *secs; month = tm = offsetdirection = plusminus = errorbound = secs = NULL; year = strtok(msgcopy, " "); if (year) tm = strtok(NULL, " "); if (tm) offsetdirection = strtok(NULL, " "); if (offsetdirection) offset = strtok(NULL, " "); if (offset) plusminus = strtok(NULL, " "); if (plusminus) errorbound = strtok(NULL, " "); if (errorbound) secs = strtok(NULL, " "); if ( offsetdirection && ((strcmp(offsetdirection, "+") == 0) || (strcmp(offsetdirection, "-") == 0)) && plusminus && (strcmp(plusminus, "+/-") == 0) && secs && (strcmp(secs, "secs") == 0) ) { /* Looks sane */ sprintf(offsetval, "%s%s", offsetdirection, offset); } } if (offsetval) { sprintf(dataforntpstat, "offset=%s", offsetval); do_ntpstat_rrd(hostname, testname, classname, pagepaths, dataforntpstat, tstamp); } xfree(msgcopy); } if (do_default) { /* * Normal network tests - pick up the "Seconds:" value */ p = strstr(msg, "\nSeconds:"); if (p && (sscanf(p+1, "Seconds: %f", &seconds) == 1)) { setupfn2("%s.%s.rrd", "tcp", testname); sprintf(rrdvalues, "%d:%.2f", (int)tstamp, seconds); return create_and_update_rrd(hostname, testname, classname, pagepaths, xymonnet_params, xymonnet_tpl); } } return 0; } xymon-4.3.7/xymond/rrd/do_xymond.c0000664000175000017500000000675111615341300016477 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char xymond_rcsid[] = "$Id: do_xymond.c 6712 2011-07-31 21:01:52Z storner $"; int do_xymond_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *xymond_params[] = { "DS:inmessages:DERIVE:600:0:U", "DS:statusmessages:DERIVE:600:0:U", "DS:combomessages:DERIVE:600:0:U", "DS:pagemessages:DERIVE:600:0:U", "DS:summarymessages:DERIVE:600:0:U", "DS:datamessages:DERIVE:600:0:U", "DS:notesmessages:DERIVE:600:0:U", "DS:enablemessages:DERIVE:600:0:U", "DS:disablemessages:DERIVE:600:0:U", "DS:ackmessages:DERIVE:600:0:U", "DS:configmessages:DERIVE:600:0:U", "DS:querymessages:DERIVE:600:0:U", "DS:boardmessages:DERIVE:600:0:U", "DS:listmessages:DERIVE:600:0:U", "DS:logmessages:DERIVE:600:0:U", "DS:dropmessages:DERIVE:600:0:U", "DS:renamemessages:DERIVE:600:0:U", "DS:statuschmsgs:DERIVE:600:0:U", "DS:stachgchmsgs:DERIVE:600:0:U", "DS:pagechmsgs:DERIVE:600:0:U", "DS:datachmsgs:DERIVE:600:0:U", "DS:noteschmsgs:DERIVE:600:0:U", "DS:enadischmsgs:DERIVE:600:0:U", NULL }; static void *xymond_tpl = NULL; struct { char *marker; unsigned long val; } xymond_data[] = { { "\nIncoming messages", 0 }, { "\n- status", 0 }, { "\n- combo", 0 }, { "\n- page", 0 }, { "\n- summary", 0 }, { "\n- data", 0 }, { "\n- notes", 0 }, { "\n- enable", 0 }, { "\n- disable", 0 }, { "\n- ack", 0 }, { "\n- config", 0 }, { "\n- query", 0 }, { "\n- xymondboard", 0 }, { "\n- xymondlist", 0 }, { "\n- xymondlog", 0 }, { "\n- drop", 0 }, { "\n- rename", 0 }, { "\nstatus channel messages", 0 }, { "\nstachg channel messages", 0 }, { "\npage channel messages", 0 }, { "\ndata channel messages", 0 }, { "\nnotes channel messages", 0 }, { "\nenadis channel messages", 0 }, { NULL, 0 } }; int i, gotany = 0; char *p; char valstr[10]; MEMDEFINE(valstr); if (xymond_tpl == NULL) xymond_tpl = setup_template(xymond_params); sprintf(rrdvalues, "%d", (int)tstamp); i = 0; while (xymond_data[i].marker) { p = strstr(msg, xymond_data[i].marker); if (p) { if (*p == '\n') p++; p += strcspn(p, ":\r\n"); if (*p == ':') { xymond_data[i].val = atol(p+1); gotany++; sprintf(valstr, ":%lu", xymond_data[i].val); strcat(rrdvalues, valstr); } else strcat(rrdvalues, ":U"); } else strcat(rrdvalues, ":U"); i++; } if (gotany) { if (strcmp("xymond", testname) != 0) { setupfn2("%s.%s.rrd", "xymond", testname); } else { setupfn("%s.rrd", "xymond"); } MEMUNDEFINE(valstr); return create_and_update_rrd(hostname, testname, classname, pagepaths, xymond_params, xymond_tpl); } MEMUNDEFINE(valstr); return 0; } xymon-4.3.7/xymond/rrd/do_iostat.c0000664000175000017500000001634511615341300016464 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char iostat_rcsid[] = "$Id: do_iostat.c 6712 2011-07-31 21:01:52Z storner $"; static char *iostat_params[] = { "DS:rs:GAUGE:600:1:U", "DS:ws:GAUGE:600:1:U", "DS:krs:GAUGE:600:1:U", "DS:kws:GAUGE:600:1:U", "DS:wait:GAUGE:600:1:U", "DS:actv:GAUGE:600:1:U", "DS:wsvc_t:GAUGE:600:1:U", "DS:asvc_t:GAUGE:600:1:U", "DS:w:GAUGE:600:1:U", "DS:b:GAUGE:600:1:U", "DS:sw:GAUGE:600:1:U", "DS:hw:GAUGE:600:1:U", "DS:trn:GAUGE:600:1:U", "DS:tot:GAUGE:600:1:U", NULL }; static void *iostat_tpl = NULL; int do_iostatdisk_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char *dataline; /* * This format is reported in the "iostatdisk" section: * * data HOSTNAME.iostatdisk * solaris * extended device statistics * device,r/s,w/s,kr/s,kw/s,wait,actv,svc_t,%w,%b, * dad0,a,0.0,0.7,0.0,5.8,0.0,0.0,4.3,0,0 * dad0,b,0.0,0.0,0.0,0.0,0.0,0.0,27.9,0,0 * dad0,c,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0 * dad0,e,0.0,0.6,0.0,4.1,0.0,0.0,3.7,0,0 * dad0,f,0.0,17.2,0.0,89.7,0.0,0.0,0.2,0,0 * dad0,h,0.0,0.5,0.0,2.7,0.0,0.0,2.2,0,0 * dad1,c,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0 * dad1,h,0.0,0.0,0.0,0.0,0.0,0.0,27.1,0,0 * nfs1,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0 * extended device statistics * device,r/s,w/s,kr/s,kw/s,wait,actv,svc_t,%w,%b, * dad0,a,0.0,0.6,0.0,5.1,0.0,0.0,4.2,0,0 * dad0,b,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0 * dad0,c,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0 * dad0,e,0.0,0.5,0.0,3.4,0.0,0.0,3.2,0,0 * dad0,f,0.0,12.6,0.0,65.6,0.0,0.0,0.2,0,0 * dad0,h,0.0,0.4,0.0,2.4,0.0,0.0,1.8,0,0 * dad1,c,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0 * dad1,h,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0 * nfs1,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0 * * There are two chunks of data: Like vmstat, we first get a * summary at the start of data collection, and then another * with the 5-minute average. So we must skip the first chunk. * * Note that real disks are identified by "dad0,a" whereas * NFS mounts show up as "nfs1" (no comma!). */ if (iostat_tpl == NULL) iostat_tpl = setup_template(iostat_params); dataline = strstr(msg, "\ndevice,r/s,w/s,kr/s,kw/s,wait,actv,svc_t,%w,%b,"); if (!dataline) return -1; dataline = strstr(dataline+1, "\ndevice,r/s,w/s,kr/s,kw/s,wait,actv,svc_t,%w,%b,"); if (!dataline) return -1; dataline++; while (dataline && *dataline) { char *elems[12]; char *eoln, *p, *id; int i, valofs; eoln = strchr(dataline, '\n'); if (eoln) *eoln = '\0'; memset(elems, 0, sizeof(elems)); p = elems[0] = dataline; i=0; do { p = strchr(p+1, ','); i++; if (p) { *p = '\0'; elems[i] = p+1; } } while (p); if (elems[9] == NULL) goto nextline; else if (elems[10] == NULL) { /* NFS "disk" */ id = elems[0]; valofs = 1; } else { /* Normal disk - re-instate the "," between elems[0] and elems[1] */ *(elems[1]-1) = ','; /* Hack! */ valofs = 2; } setupfn2("%s.%s.rrd", "iostat", id); sprintf(rrdvalues, "%d:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s", (int) tstamp, elems[valofs], /* r/s */ elems[valofs+1], /* w/s */ elems[valofs+2], /* kr/s */ elems[valofs+3], /* kw/s */ elems[valofs+4], /* wait */ elems[valofs+5], /* actv */ elems[valofs+6], /* wsvc_t - we use svc_t here */ "U", /* asvc_t not in this format */ elems[valofs+7], /* %w */ elems[valofs+8], /* %b */ "U", "U", "U", "U" /* sw, hw, trn, tot not in this format */ ); nextline: dataline = (eoln ? eoln+1 : NULL); } return 0; } int do_iostat_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { /* * BEGINKEY * d0 / * d5 /var * d6 /export * ENDKEY * BEGINDATA * r/s w/s kr/s kw/s wait actv wsvc_t asvc_t %w %b s/w h/w trn tot device * 0.9 2.8 7.3 1.8 0.0 0.0 2.7 9.3 1 2 0 0 0 0 d0 * 0.1 0.3 0.8 0.5 0.0 0.0 5.2 11.0 0 0 0 0 0 0 d5 * 0.1 0.2 1.0 1.1 0.0 0.0 6.9 12.9 0 0 0 0 0 0 d6 * ENDDATA */ typedef struct iostatkey_t { char *key; char *value; struct iostatkey_t *next; } iostatkey_t; enum { S_NONE, S_KEYS, S_DATA } state; iostatkey_t *keyhead = NULL; iostatkey_t *newkey; char *eoln, *curline; char *buf, *p; float v[14]; char marker[MAX_LINE_LEN]; MEMDEFINE(marker); if (iostat_tpl == NULL) iostat_tpl = setup_template(iostat_params); curline = msg; state = S_NONE; while (curline) { eoln = strchr(curline, '\n'); if (eoln) *eoln = '\0'; if (strncmp(curline, "BEGINKEY", 8) == 0) { state = S_KEYS; } else if (strncmp(curline, "ENDKEY", 6) == 0) { state = S_NONE; } else if (strncmp(curline, "BEGINDATA", 9) == 0) { state = S_DATA; } else if (strncmp(curline, "ENDDATA", 7) == 0) { state = S_NONE; } else { switch (state) { case S_NONE: break; case S_KEYS: buf = xstrdup(curline); newkey = (iostatkey_t *)xcalloc(1, sizeof(iostatkey_t)); p = strtok(buf, " "); if (p) newkey->key = xstrdup(p); p = strtok(NULL, " "); if (p) { if (strcmp(p, "/") == 0) newkey->value = xstrdup(",root"); else { newkey->value = xstrdup(p); p = newkey->value; while ((p = strchr(p, '/')) != NULL) *p = ','; } } xfree(buf); if (newkey->key && newkey->value) { newkey->next = keyhead; keyhead = newkey; } else { if (newkey->key) xfree(newkey->key); if (newkey->value) xfree(newkey->value); xfree(newkey); } break; case S_DATA: buf = xstrdup(curline); if (sscanf(buf, "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %s", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &v[6], &v[7], &v[8], &v[9], &v[10], &v[11], &v[12], &v[13], marker) == 15) { /* Find the disk name */ for (newkey = keyhead; (newkey && strcmp(newkey->key, marker)); newkey = newkey->next) ; if (newkey) { setupfn2("%s.%s.rrd", "iostat", newkey->value); sprintf(rrdvalues, "%d:%.1f:%.1f:%.1f:%.1f:%.1f:%.1f:%.1f:%.1f:%.1f:%.1f:%.1f:%.1f:%.1f:%.1f", (int) tstamp, v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9], v[10], v[11], v[12], v[13]); create_and_update_rrd(hostname, testname, classname, pagepaths, iostat_params, iostat_tpl); } } xfree(buf); break; } } if (eoln) { *eoln = '\n'; curline = eoln + 1; } else { curline = NULL; } } /* Free the keylist */ while (keyhead) { newkey = keyhead; keyhead = keyhead->next; if (newkey->key) xfree(newkey->key); if (newkey->value) xfree(newkey->value); xfree(newkey); } MEMUNDEFINE(marker); return 0; } xymon-4.3.7/xymond/rrd/do_vmstat.c0000664000175000017500000002502011615341300016465 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char vmstat_rcsid[] = "$Id: do_vmstat.c 6712 2011-07-31 21:01:52Z storner $"; typedef struct vmstat_layout_t { int index; char *name; } vmstat_layout_t; /* This one matches the vmstat output from Solaris 8, possibly earlier ones as well */ /* LARRD 0.43c compatible. */ static vmstat_layout_t vmstat_solaris_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { 2, "cpu_w" }, { 3, "mem_swap" }, { 4, "mem_free" }, { 5, "mem_re" }, { 6, "mem_mf" }, { 7, "mem_pi" }, { 8, "mem_po" }, { 11, "sr" }, { 16, "cpu_int" }, { 17, "cpu_syc" }, { 18, "cpu_csw" }, { 19, "cpu_usr" }, { 20, "cpu_sys" }, { 21, "cpu_idl" }, { -1, NULL } }; /* This one for OSF */ /* LARRD 0.43c compatible */ static vmstat_layout_t vmstat_osf_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { 2, "cpu_w" }, { 4, "mem_free" }, { 6, "mem_mf" }, { 10, "mem_pi" }, { 11, "mem_po" }, { 12, "cpu_int" }, { 13, "cpu_syc" }, { 14, "cpu_csw" }, { 15, "cpu_usr" }, { 16, "cpu_sys" }, { 17, "cpu_idl" }, { -1, NULL } }; /* This one for AIX */ /* LARRD 0.43c compatible */ static vmstat_layout_t vmstat_aix_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { 2, "mem_avm" }, { 3, "mem_free" }, { 4, "mem_re" }, { 5, "mem_pi" }, { 6, "mem_po" }, { 7, "mem_fr" }, { 8, "sr" }, { 9, "mem_cy" }, { 10, "cpu_int" }, { 11, "cpu_syc" }, { 12, "cpu_csw" }, { 13, "cpu_usr" }, { 14, "cpu_sys" }, { 15, "cpu_idl" }, { 16, "cpu_wait" }, { -1, NULL } }; /* This is for AIX running on Power5 cpu's. */ static vmstat_layout_t vmstat_aix_power5_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { 2, "mem_avm" }, { 3, "mem_free" }, { 4, "mem_re" }, { 5, "mem_pi" }, { 6, "mem_po" }, { 7, "mem_fr" }, { 8, "sr" }, { 9, "mem_cy" }, { 10, "cpu_int" }, { 11, "cpu_syc" }, { 12, "cpu_csw" }, { 13, "cpu_usr" }, { 14, "cpu_sys" }, { 15, "cpu_idl" }, { 16, "cpu_wait" }, { 17, "cpu_pc" }, { 18, "cpu_ec" }, { -1, NULL } }; /* This one for Christian Perrier's hacked IRIX "vmstat" done with sar */ static vmstat_layout_t vmstat_irix_layout[] = { { 1, "cpu_usr" }, { 2, "cpu_sys" }, { 3, "cpu_int" }, { 4, "cpu_wait" }, { 5, "cpu_idl" }, { -1, "cpu_csw" }, /* Not available, but having it in the RRD makes vmstat3 graph (int+csw) work */ { -1, NULL } }; /* This one matches FreeBSD 4.10 */ /* LARRD 0.43c compatible */ static vmstat_layout_t vmstat_freebsd_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { 2, "cpu_w" }, { 3, "mem_avm" }, { 4, "mem_free" }, { 5, "mem_flt" }, { 6, "mem_re" }, { 7, "mem_pi" }, { 8, "mem_po" }, { 9, "mem_fr" }, { 10, "sr" }, { 11, "dsk_da0" }, { 12, "dsk_fd0" }, { 13, "cpu_int" }, { 15, "cpu_csw" }, { 16, "cpu_sys" }, { 17, "cpu_usr" }, { 18, "cpu_idl" }, { -1, NULL } }; /* This one matches NetBSD 2.0 */ /* LARRD 0.43c does not support NetBSD */ static vmstat_layout_t vmstat_netbsd_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { 2, "cpu_w" }, { 3, "mem_avm" }, { 4, "mem_free" }, { 5, "mem_flt" }, { 6, "mem_re" }, { 7, "mem_pi" }, { 8, "mem_po" }, { 9, "mem_fr" }, { 10, "sr" }, { 11, "dsk_f0" }, { 12, "dsk_m0" }, { 13, "dsk_w0" }, { 14, "cpu_int" }, { 15, "cpu_syc" }, { 16, "cpu_csw" }, { 17, "cpu_usr" }, { 18, "cpu_sys" }, { 19, "cpu_idl" }, { -1, NULL } }; static vmstat_layout_t vmstat_openbsd_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { 2, "cpu_w" }, { 3, "mem_avm" }, { 4, "mem_free" }, { 5, "mem_flt" }, { 6, "mem_re" }, { 7, "mem_pi" }, { 8, "mem_po" }, { 9, "mem_fr" }, { 10, "sr" }, { 11, "dsk_wd0" }, { 12, "dsk_cd0" }, { 13, "cpu_int" }, { 14, "cpu_syc" }, { 15, "cpu_csw" }, { 16, "cpu_usr" }, { 17, "cpu_sys" }, { 18, "cpu_idl" }, { -1, NULL } }; /* This one for HP/UX */ /* LARRD 0.43c does not support HP-UX */ static vmstat_layout_t vmstat_hpux_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { 2, "cpu_w" }, { 3, "mem_avm" }, { 4, "mem_free" }, { 5, "mem_re" }, { 6, "mem_flt" }, { 7, "mem_pi" }, { 8, "mem_po" }, { 9, "mem_fr" }, { 11, "sr" }, { 12, "cpu_int" }, { 14, "cpu_csw" }, { 15, "cpu_usr" }, { 16, "cpu_sys" }, { 17, "cpu_idl" }, { -1, NULL } }; /* This one is all newer Linux procps versions, with kernel 2.4+ */ /* NOT compatible with LARRD 0.43c */ static vmstat_layout_t vmstat_linux_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { -1, "cpu_w" }, /* Not present for 2.4+ kernels, so log as "Undefined" */ { 2, "mem_swpd" }, { 3, "mem_free" }, { 4, "mem_buff" }, { 5, "mem_cach" }, { 6, "mem_si" }, { 7, "mem_so" }, { 8, "dsk_bi" }, { 9, "dsk_bo" }, { 10, "cpu_int" }, { 11, "cpu_csw" }, { 12, "cpu_usr" }, { 13, "cpu_sys" }, { 14, "cpu_idl" }, { 15, "cpu_wait" }, /* Requires kernel 2.6, but may not be present */ { -1, NULL } }; /* * This one is for Red Hat Enterprise Linux 3. Identical to the "linux" layout, * except Red Hat for some reason decided to swap the cpu_wait and cpu_idle columns. */ /* NOT compatible with LARRD 0.43c */ static vmstat_layout_t vmstat_rhel3_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { -1, "cpu_w" }, { 2, "mem_swpd" }, { 3, "mem_free" }, { 4, "mem_buff" }, { 5, "mem_cach" }, { 6, "mem_si" }, { 7, "mem_so" }, { 8, "dsk_bi" }, { 9, "dsk_bo" }, { 10, "cpu_int" }, { 11, "cpu_csw" }, { 12, "cpu_usr" }, { 13, "cpu_sys" }, { 14, "cpu_wait" }, { 15, "cpu_idl" }, { -1, NULL } }; /* This one is for Debian 3.0 (Woody), and possibly others with a Linux 2.2 kernel */ /* NOT compatible with LARRD 0.43c */ static vmstat_layout_t vmstat_linux22_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { 2, "cpu_w" }, { 3, "mem_swpd" }, { 4, "mem_free" }, { 5, "mem_buff" }, { 6, "mem_cach" }, { 7, "mem_si" }, { 8, "mem_so" }, { 9, "dsk_bi" }, { 10, "dsk_bo" }, { 11, "cpu_int" }, { 12, "cpu_csw" }, { 13, "cpu_usr" }, { 14, "cpu_sys" }, { 15, "cpu_idl" }, { -1, "cpu_wait" }, { -1, NULL } }; /*This one is for sco_sv */ /* NOT compatible with LARRD 0.43c */ static vmstat_layout_t vmstat_sco_sv_layout[] = { { 0, "cpu_r" }, { 1, "cpu_b" }, { 2, "cpu_w" }, { 3, "mem_free" }, { 4, "mem_dmd" }, { 5, "mem_swpd" }, { 6, "mem_cach" }, { 7, "mem_fil" }, { 8, "mem_flt" }, { 9, "mem_frd" }, { 10, "mem_pos" }, { 11, "mem_pif" }, { 12, "mem_pis" }, { 13, "mem_so" }, { 14, "mem_si" }, { 15, "sys_calls" }, { 16, "cpu_csw" }, { 17, "cpu_usr" }, { 18, "cpu_sys" }, { 19, "cpu_idl" }, /* { -1, "cpu_wait" }, */ { -1, NULL } }; #define MAX_VMSTAT_VALUES 30 int do_vmstat_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { enum ostype_t ostype; vmstat_layout_t *layout = NULL; char *datapart = msg; int values[MAX_VMSTAT_VALUES]; int defcount, defidx, datacount, result; char *p; char **creparams; if ((strncmp(msg, "status", 6) == 0) || (strncmp(msg, "data", 4) == 0)) { /* Full message, including "status" or "data" line - so skip the first line. */ datapart = strchr(msg, '\n'); if (datapart) { datapart++; } else { errprintf("Too few lines (only 1) in vmstat report from %s\n", hostname); return -1; } } ostype = get_ostype(datapart); datapart = strchr(datapart, '\n'); if (datapart) { datapart++; } else { errprintf("Too few lines (only 1 or 2) in vmstat report from %s\n", hostname); return -1; } /* Pick up the values in the datapart line. Stop at newline. */ p = strchr(datapart, '\n'); if (p) *p = '\0'; p = strtok(datapart, " "); datacount = 0; while (p && (datacount < MAX_VMSTAT_VALUES)) { values[datacount++] = atoi(p); p = strtok(NULL, " "); } /* Must do this now, to check on the layout of any existing file */ setupfn("%s.rrd", "vmstat"); switch (ostype) { case OS_SOLARIS: layout = vmstat_solaris_layout; break; case OS_OSF: layout = vmstat_osf_layout; break; case OS_AIX: /* Special, because there are two layouts for AIX */ { char **dsnames = NULL; int dscount, i; dscount = rrddatasets(hostname, &dsnames); layout = ((dscount == 17) ? vmstat_aix_layout : vmstat_aix_power5_layout); if ((dscount > 0) && dsnames) { /* Free the dsnames list */ for (i=0; (i= datacount) || (dataidx == -1)) { p += sprintf(p, ":U"); } else { p += sprintf(p, ":%d", values[layout[defidx].index]); } } result = create_and_update_rrd(hostname, testname, classname, pagepaths, creparams, NULL); for (defidx=0; (defidx < defcount); defidx++) xfree(creparams[defidx]); xfree(creparams); return result; } xymon-4.3.7/xymond/rrd/do_ncv.c0000664000175000017500000001402111615341300015734 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* This module handles any message with data in the form */ /* NAME: VALUE */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* split-ncv added by Charles Goyard November 2006 */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char ncv_rcsid[] = "$Id: do_ncv.c 6712 2011-07-31 21:01:52Z storner $"; int do_ncv_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char **params = NULL; int paridx; char dsdef[1024]; /* destination DS syntax for rrd engine */ char *l, *name, *val; char *envnam; char *dstypes = NULL; /* contain NCV_testname value */ int split_ncv = 0; int dslen; sprintf(rrdvalues, "%d", (int)tstamp); params = (char **)calloc(1, sizeof(char *)); paridx = 0; /* Get the NCV_* or SPLITNCV_* environment setting */ envnam = (char *)malloc(9 + strlen(testname) + 1); sprintf(envnam, "SPLITNCV_%s", testname); l = getenv(envnam); if (l) { split_ncv = 1; dslen = 200; } else { split_ncv = 0; dslen = 19; setupfn("%s.rrd", testname); sprintf(envnam, "NCV_%s", testname); l = getenv(envnam); } if (l) { dstypes = (char *)malloc(strlen(l)+3); sprintf(dstypes, ",%s,", l); } xfree(envnam); l = strchr(msg, '\n'); if (l) l++; while (l && *l && strncmp(l, "@@\n", 3)) { name = val = NULL; l += strspn(l, " \t\n"); if (*l) { /* See if this line contains a '=' or ':' sign */ name = l; l += strcspn(l, ":=\n"); if (*l) { if (( *l == '=') || (*l == ':')) { *l = '\0'; l++; } else { /* No marker, so skip this line */ name = NULL; } } else break; /* We've hit the end of the message */ } /* Skip any color marker "&COLOR " in front of the ds name */ if (name && (*name == '&')) { name++; name += strspn(name, "abcdefghijklmnopqrstuvwxyz"); name += strspn(name, " \t"); if (*name == '\0') name = NULL; } if (name) { val = l + strspn(l, " \t"); /* Find the end of the value string */ l = val; if ((*l == '-') || (*l == '+')) l++; /* Pass leading sign */ l += strspn(l, "0123456789.+-"); /* and the numbers. */ if( *val ) { int iseol = (*l == '\n'); *l = '\0'; if (!iseol) { /* If extra data after the value, skip to end of line */ l = strchr(l+1, '\n'); if (l) l++; } else { l++; } } else break; /* No value data */ } if (name && val && *val) { char *endptr; strtod(val, &endptr); if (isspace((int)*endptr) || (*endptr == '\0')) { char dsname[250]; /* name of ncv in status message (with space and all) */ char dskey[252]; /* name of final DS key (stripped) */ char *dstype = NULL; /* type of final DS */ char *inp; int outidx = 0; /* val contains a valid number */ /* rrdcreate(1) says: ds must be in the set [a-zA-Z0-9_] ... */ for (inp=name,outidx=0; (*inp && (outidx < dslen)); inp++) { if ( ((*inp >= 'A') && (*inp <= 'Z')) || ((*inp >= 'a') && (*inp <= 'z')) || ((*inp >= '0') && (*inp <= '9')) ) { dsname[outidx++] = *inp; } /* ... however, for split ncv, we replace anything else */ /* with an underscore, compacting successive invalid */ /* characters into a single one */ else if (split_ncv && ((outidx == 0) || (dsname[outidx - 1] != '_'))) { dsname[outidx++] = '_'; } } if ((outidx > 0) && (dsname[outidx-1] == '_')) { dsname[outidx-1] = '\0'; } else { dsname[outidx] = '\0'; } sprintf(dskey, ",%s:", dsname); if (split_ncv) setupfn2("%s,%s.rrd", testname, dsname); if (dstypes) { dstype = strstr(dstypes, dskey); if (!dstype) { strcpy(dskey, ",*:"); dstype = strstr(dstypes, dskey); } } if (dstype) { /* if ds type is forced */ char *p; dstype += strlen(dskey); p = strchr(dstype, ','); if (p) *p = '\0'; if(split_ncv) { sprintf(dsdef, "DS:lambda:%s:600:U:U", dstype); } else { sprintf(dsdef, "DS:%s:%s:600:U:U", dsname, dstype); } if (p) *p = ','; } else { /* nothing specified in the environnement, and no '*:' default */ if(split_ncv) { strcpy(dsdef, "DS:lambda:DERIVE:600:U:U"); } else { sprintf(dsdef, "DS:%s:DERIVE:600:U:U", dsname); } } if (!dstype || (strncasecmp(dstype, "NONE", 4) != 0)) { /* if we have something */ params[paridx] = strdup(dsdef); paridx++; params = (char **)realloc(params, (1 + paridx)*sizeof(char *)); params[paridx] = NULL; sprintf(rrdvalues+strlen(rrdvalues), ":%s", val); } } if (split_ncv && (paridx > 0)) { create_and_update_rrd(hostname, testname, classname, pagepaths, params, NULL); /* We've created one RRD, so reset the params for the next one */ for (paridx=0; (params[paridx] != NULL); paridx++) xfree(params[paridx]); paridx = 0; params[0] = NULL; sprintf(rrdvalues, "%d", (int)tstamp); } } } /* end of while */ if (!split_ncv && params[0]) create_and_update_rrd(hostname, testname, classname, pagepaths, params, NULL); for (paridx=0; (params[paridx] != NULL); paridx++) xfree(params[paridx]); xfree(params); if (dstypes) xfree(dstypes); return 0; } xymon-4.3.7/xymond/rrd/do_disk.c0000664000175000017500000001700211615341300016102 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char disk_rcsid[] = "$Id: do_disk.c 6712 2011-07-31 21:01:52Z storner $"; int do_disk_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *disk_params[] = { "DS:pct:GAUGE:600:0:100", "DS:used:GAUGE:600:0:U", NULL }; static void *disk_tpl = NULL; enum { DT_IRIX, DT_AS400, DT_NT, DT_UNIX, DT_NETAPP, DT_NETWARE, DT_BBWIN } dsystype; char *eoln, *curline; static int ptnsetup = 0; static pcre *inclpattern = NULL; static pcre *exclpattern = NULL; if (strstr(msg, "netapp.pl")) return do_netapp_disk_rrd(hostname, testname, classname, pagepaths, msg, tstamp); if (strstr(msg, "dbcheck.pl")) return do_dbcheck_tablespace_rrd(hostname, testname, classname, pagepaths, msg, tstamp); if (disk_tpl == NULL) disk_tpl = setup_template(disk_params); if (!ptnsetup) { const char *errmsg; int errofs; char *ptn; ptnsetup = 1; ptn = getenv("RRDDISKS"); if (ptn && strlen(ptn)) { inclpattern = pcre_compile(ptn, PCRE_CASELESS, &errmsg, &errofs, NULL); if (!inclpattern) errprintf("PCRE compile of RRDDISKS='%s' failed, error %s, offset %d\n", ptn, errmsg, errofs); } ptn = getenv("NORRDDISKS"); if (ptn && strlen(ptn)) { exclpattern = pcre_compile(ptn, PCRE_CASELESS, &errmsg, &errofs, NULL); if (!exclpattern) errprintf("PCRE compile of NORRDDISKS='%s' failed, error %s, offset %d\n", ptn, errmsg, errofs); } } if (strstr(msg, " xfs ") || strstr(msg, " efs ") || strstr(msg, " cxfs ")) dsystype = DT_IRIX; else if (strstr(msg, "DASD")) dsystype = DT_AS400; else if (strstr(msg, "NetWare Volumes")) dsystype = DT_NETWARE; else if (strstr(msg, "NetAPP")) dsystype = DT_NETAPP; else if (strstr(msg, "Summary")) dsystype = DT_BBWIN; /* BBWin > 0.10 is almost like Windows/NT */ else if (strstr(msg, "Filesystem")) dsystype = DT_NT; else dsystype = DT_UNIX; if (dsystype == DT_NT) { /* * The MrBig client includes HTML tables with the configured disk thresholds. * We simply cut off that part of the message before doing any trend analysis. */ char *mrbigstuff = strstr(msg, "Limits:"); if (mrbigstuff) *mrbigstuff = '\0'; } /* * Francesco Duranti noticed that if we use the "/group" option * when sending the status message, this tricks the parser to * create an extra filesystem called "/group". So skip the first * line - we never have any disk reports there anyway. */ curline = strchr(msg, '\n'); if (curline) curline++; while (curline) { char *fsline, *p; char *columns[20]; int columncount; char *diskname = NULL; int pused = -1; int wanteddisk = 1; long long aused = 0; eoln = strchr(curline, '\n'); if (eoln) *eoln = '\0'; /* AS/400 reports must contain the word DASD */ if ((dsystype == DT_AS400) && (strstr(curline, "DASD") == NULL)) goto nextline; /* All clients except AS/400 report the mount-point with slashes - ALSO Win32 clients. */ if ((dsystype != DT_AS400) && (strchr(curline, '/') == NULL)) goto nextline; /* red/yellow filesystems show up twice */ if ((dsystype != DT_NETAPP) && (dsystype != DT_NETWARE) && (dsystype != DT_AS400)) { if (*curline == '&') goto nextline; if ((strstr(curline, " red ") || strstr(curline, " yellow "))) goto nextline; } for (columncount=0; (columncount<20); columncount++) columns[columncount] = ""; fsline = xstrdup(curline); columncount = 0; p = strtok(fsline, " "); while (p && (columncount < 20)) { columns[columncount++] = p; p = strtok(NULL, " "); } /* * Some Unix filesystem reports contain the word "Filesystem". * So check if there's a slash in the NT filesystem letter - if yes, * then it's really a Unix system after all. */ if ( (dsystype == DT_NT) && (*(columns[5])) && (strchr(columns[0], '/')) ) dsystype = DT_UNIX; switch (dsystype) { case DT_IRIX: diskname = xstrdup(columns[6]); p = strchr(columns[5], '%'); if (p) *p = ' '; pused = atoi(columns[5]); aused = str2ll(columns[3], NULL); break; case DT_AS400: diskname = xstrdup("/DASD"); p = strchr(columns[columncount-1], '%'); if (p) *p = ' '; /* * Yikes ... the format of this line varies depending on the color. * Red: * March 23, 2005 12:32:54 PM EST DASD on deltacdc at panic level at 90.4967% * Yellow: * April 4, 2005 9:20:26 AM EST DASD on deltacdc at warning level at 81.8919% * Green: * April 3, 2005 7:53:53 PM EST DASD on deltacdc OK at 79.6986% * * So we'll just pick out the number from the last column. */ pused = atoi(columns[columncount-1]); aused = 0; /* Not available */ break; case DT_NT: case DT_BBWIN: diskname = xmalloc(strlen(columns[0])+2); sprintf(diskname, "/%s", columns[0]); p = strchr(columns[4], '%'); if (p) *p = ' '; pused = atoi(columns[4]); aused = str2ll(columns[2], NULL); break; case DT_UNIX: diskname = xstrdup(columns[5]); p = strchr(columns[4], '%'); if (p) *p = ' '; pused = atoi(columns[4]); aused = str2ll(columns[2], NULL); break; case DT_NETAPP: diskname = xstrdup(columns[1]); pused = atoi(columns[5]); p = columns[3] + strspn(columns[3], "0123456789"); aused = str2ll(columns[3], NULL); /* Convert to KB if there's a modifier after the numbers */ if (*p == 'M') aused *= 1024; else if (*p == 'G') aused *= (1024*1024); else if (*p == 'T') aused *= (1024*1024*1024); break; case DT_NETWARE: diskname = xstrdup(columns[1]); aused = str2ll(columns[3], NULL); pused = atoi(columns[7]); break; } /* Check include/exclude patterns */ wanteddisk = 1; if (exclpattern) { int ovector[30]; int result; result = pcre_exec(exclpattern, NULL, diskname, strlen(diskname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); wanteddisk = (result < 0); } if (wanteddisk && inclpattern) { int ovector[30]; int result; result = pcre_exec(inclpattern, NULL, diskname, strlen(diskname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); wanteddisk = (result >= 0); } if (wanteddisk && diskname && (pused != -1)) { p = diskname; while ((p = strchr(p, '/')) != NULL) { *p = ','; } if (strcmp(diskname, ",") == 0) { diskname = xrealloc(diskname, 6); strcpy(diskname, ",root"); } /* * Use testname here. * The disk-handler also gets data from NetAPP inode- and qtree-messages, * that are virtually identical to the disk-messages. So lets just handle * all of it by using the testname as part of the filename. */ setupfn2("%s%s.rrd", testname, diskname); sprintf(rrdvalues, "%d:%d:%lld", (int)tstamp, pused, aused); create_and_update_rrd(hostname, testname, classname, pagepaths, disk_params, disk_tpl); } if (diskname) { xfree(diskname); diskname = NULL; } if (eoln) *eoln = '\n'; xfree(fsline); nextline: curline = (eoln ? (eoln+1) : NULL); } return 0; } xymon-4.3.7/xymond/rrd/do_counts.c0000664000175000017500000000502111615341300016461 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* This module handles various "counts" messages. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char counts_rcsid[] = "$Id: do_counts.c 6712 2011-07-31 21:01:52Z storner $"; static int do_one_counts_rrd(char *counttype, char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp, char *params[], char *tpl) { char *boln, *eoln; boln = strchr(msg, '\n'); if (boln) boln++; while (boln && *boln) { char *fn, *countstr = NULL; eoln = strchr(boln, '\n'); if (eoln) *eoln = '\0'; fn = strtok(boln, ":"); if (fn) countstr = strtok(NULL, ":"); if (fn && countstr) { char *p; for (p=strchr(fn, '/'); (p); p = strchr(p, '/')) *p = ','; setupfn2("%s.%s.rrd", counttype, fn); sprintf(rrdvalues, "%d:%s", (int)tstamp, countstr); create_and_update_rrd(hostname, testname, classname, pagepaths, params, tpl); } boln = (eoln ? eoln+1 : NULL); } return 0; } static char *counts_params[] = { "DS:count:GAUGE:600:0:U", NULL }; static void *counts_tpl = NULL; static char *derive_params[] = { "DS:count:DERIVE:600:0:U", NULL }; static void *derive_tpl = NULL; int do_counts_rrd(char *counttype, char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { if (counts_tpl == NULL) counts_tpl = setup_template(counts_params); return do_one_counts_rrd(counttype, hostname, testname, classname, pagepaths, msg, tstamp, counts_params, counts_tpl); } int do_derives_rrd(char *counttype, char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { if (derive_tpl == NULL) derive_tpl = setup_template(derive_params); return do_one_counts_rrd(counttype, hostname, testname, classname, pagepaths, msg, tstamp, derive_params, derive_tpl); } xymon-4.3.7/xymond/rrd/do_trends.c0000664000175000017500000000702111615341300016447 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* This module handles custom "trends" data. */ /* */ /* Copyright (C) 2007-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char trends_rcsid[] = "$Id: do_trends.c 6712 2011-07-31 21:01:52Z storner $"; /* * This module was inspired by a mail from Stef Coene: * * --------------------------------------------------------------------------- * Date: Wed, 17 Jan 2007 14:04:29 +0100 * From: Stef Coene * Subject: Re: [xymon] xymon monitoring * * Just wondering, how hard would it be to create an extra channel for trending? * So you can use the xymon client to send "numbers" to the xymon server together * with some extra control information. * * xymon trends * * ----------------------------------------------------------------------------- * * Instead of a dedicated Xymon channel for this, I decided to use the * existing "data" message type. To use this, send a "data" message to * xymon formatted like this: * * data $MACHINE.trends * [filename.rrd] * DS-definition1 VALUE2 * DS-definition2 VALUE2 * * E.g. to create/update a custom RRD file "weather.rrd" with two * GAUGE datasets "temp" and "wind", with current values "21" and * "8" respectively, send this message: * * [weather.rrd] * DS:temp:GAUGE:600:0:U 21 * DS:wind:GAUGE:600:0:U 8 */ static int do_trends_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char *boln, *eoln, *p; int dscount; char **creparams; creparams = (char **)calloc(1, sizeof(char *)); dscount = 0; boln = strchr(msg, '\n'); if (boln) boln++; while (boln && *boln) { eoln = strchr(boln, '\n'); if (eoln) *eoln = '\0'; if (*boln == '[') { /* Flush the current RRD file */ if (creparams[0]) create_and_update_rrd(hostname, testname, classname, pagepaths, creparams, NULL); creparams = (char **)realloc(creparams, 1*sizeof(char *)); creparams[0] = NULL; dscount = 0; /* Get the RRD filename */ p = strchr(boln+1, ']'); if (p) *p = '\0'; setupfn("%s", boln+1); /* And setup the initial rrdvalues string */ sprintf(rrdvalues, "%d", (int)tstamp); } else if (strncmp(boln, "DS:", 3) == 0) { char *valptr = boln + strcspn(boln, " \t"); if ((*valptr == ' ') || (*valptr == '\t')) { *valptr = '\0'; valptr += 1 + strspn(valptr+1, " \t"); creparams[dscount] = boln; dscount++; creparams = (char **)realloc(creparams, (1+dscount)*sizeof(char **)); creparams[dscount] = NULL; sprintf(rrdvalues+strlen(rrdvalues), ":%s", valptr); } } boln = (eoln ? eoln+1 : NULL); } /* Do the last RRD set */ if (creparams[0]) create_and_update_rrd(hostname, testname, classname, pagepaths, creparams, NULL); xfree(creparams); return 0; } xymon-4.3.7/xymond/rrd/do_temperature.c0000664000175000017500000000676211615341300017520 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char temperature_rcsid[] = "$Id: do_temperature.c 6712 2011-07-31 21:01:52Z storner $"; int do_temperature_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *temperature_params[] = { "DS:temperature:GAUGE:600:1:U", NULL }; static void *temperature_tpl = NULL; /* Sample input report: Device Temp(C) Temp(F) ----------------------------------- &green Motherboard#0 31 87 &green Motherboard#1 28 82 &green AMBIENT 25 77 &green CPU0 40 104 &green CPU1 40 104 &green CPU2 40 104 &green CPU3 40 104 &green Board 0 29 84 &green Board 1 35 95 &green Board 2 30 86 &green Board 3 37 98 &green Board 4 28 82 &green Board 6 28 82 &green Board CLK 27 80 &green MB 24 75 &green IOB 19 66 &green DBP0 19 66 &green CPU 0 Die 79 174 &green CPU 0 Ambient 27 80 &green CPU 1 Die 73 163 &green CPU 1 Ambient 26 78 ----------------------------------- Status green: All devices look okay */ char *bol, *eol, *comment, *p; int tmpF, tmpC; if (temperature_tpl == NULL) temperature_tpl = setup_template(temperature_params); bol = eol = msg; while (eol && ((p = strstr(eol, "\n&")) != NULL)) { int gotone = 0; bol = p + 1; eol = strchr(bol, '\n'); if (eol) *eol = '\0'; /* See if there's a comment in parenthesis */ comment = strchr(bol, '('); /* Begin comment */ p = strchr(bol, ')'); /* End comment */ if (comment && p && (comment < p)) *comment = '\0'; /* Cut off the comment */ if (strncmp(bol, "&green", 6) == 0) { bol += 6; gotone = 1; } else if (strncmp(bol, "&yellow", 7) == 0) { bol += 7; gotone = 1; } else if (strncmp(bol, "&red", 4) == 0) { bol += 4; gotone = 1; } else if (strncmp(bol, "&clear", 6) == 0) { bol += 6; gotone = 1; } if (gotone) { char savech; bol += strspn(bol, " \t"); p = bol + strlen(bol) - 1; while ((p > bol) && isspace((int)*p)) p--; while ((p > bol) && isdigit((int)*p)) p--; tmpF = atoi(p); while ((p > bol) && isspace((int)*p)) p--; while ((p > bol) && isdigit((int)*p)) p--; tmpC = atoi(p); while ((p > bol) && isspace((int)*p)) p--; savech = *(p+1); *(p+1) = '\0'; setupfn2("%s.%s.rrd", "temperature", bol); *(p+1) = savech; sprintf(rrdvalues, "%d:%d", (int)tstamp, tmpC); create_and_update_rrd(hostname, testname, classname, pagepaths, temperature_params, temperature_tpl); } if (comment) *comment = '('; if (eol) *eol = '\n'; } return 0; } xymon-4.3.7/xymond/rrd/do_apache.c0000664000175000017500000000421611615341300016374 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char apache_rcsid[] = "$Id: do_apache.c 6712 2011-07-31 21:01:52Z storner $"; int do_apache_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *apache_params[] = { "DS:TA:DERIVE:600:0:U", "DS:TKB:DERIVE:600:0:U", "DS:BW:GAUGE:600:1:U", "DS:IW:GAUGE:600:1:U", "DS:CPU:GAUGE:600:0:U", "DS:REQPERSEC:GAUGE:600:0:U", NULL }; static void *apache_tpl = NULL; char *markers[] = { "Total Accesses:", "Total kBytes:", "BusyWorkers:", "IdleWorkers:", "CPULoad:", "ReqPerSec:", NULL }; int i; char *p, *eoln; if (apache_tpl == NULL) apache_tpl = setup_template(apache_params); /* Apache 1.x uses BusyServers/IdleServers. Convert the status to Apache 2.0 format */ if ((p = strstr(msg, "BusyServers:")) != NULL) memcpy(p, "BusyWorkers:", strlen("BusyWorkers:")); if ((p = strstr(msg, "IdleServers:")) != NULL) memcpy(p, "IdleWorkers:", strlen("IdleWorkers:")); setupfn("%s.rrd", "apache"); sprintf(rrdvalues, "%d", (int)tstamp); i = 0; while (markers[i]) { strcat(rrdvalues, ":"); p = strstr(msg, markers[i]); if (p) { eoln = strchr(p, '\n'); if (eoln) *eoln = '\0'; p = strchr(p, ':')+1; p += strspn(p, " "); strcat(rrdvalues, p); if (eoln) *eoln = '\n'; } else { strcat(rrdvalues, "U"); } i++; } return create_and_update_rrd(hostname, testname, classname, pagepaths, apache_params, apache_tpl); } xymon-4.3.7/xymond/rrd/do_sendmail.c0000664000175000017500000001310411615341300016743 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char sendmail_rcsid[] = "$Id: do_sendmail.c 6712 2011-07-31 21:01:52Z storner $"; int do_sendmail_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *sendmail_params_1[] = { "DS:msgsfr:DERIVE:600:0:U", "DS:bytes_from:DERIVE:600:0:U", "DS:msgsto:DERIVE:600:0:U", "DS:bytes_to:DERIVE:600:0:U", "DS:msgsrej:DERIVE:600:0:U", "DS:msgsdis:DERIVE:600:0:U", NULL }; static void *sendmail_tpl_1 = NULL; static char *sendmail_params_2[] = { "DS:msgsfr:DERIVE:600:0:U", "DS:bytes_from:DERIVE:600:0:U", "DS:msgsto:DERIVE:600:0:U", "DS:bytes_to:DERIVE:600:0:U", "DS:msgsrej:DERIVE:600:0:U", "DS:msgsdis:DERIVE:600:0:U", "DS:msgsqur:DERIVE:600:0:U", NULL }; static void *sendmail_tpl_2 = NULL; /* * The data we process is the output from the "mailstats" command. * * Statistics from Mon Apr 25 16:29:41 2005 * M msgsfr bytes_from msgsto bytes_to msgsrej msgsdis msgsqur Mailer * 3 183435 215701K 0 0K 0 0 0 local * 5 0 0K 183435 215544K 0 0 0 esmtp * ===================================================================== * T 183435 215701K 183435 215544K 0 0 0 * C 183435 183435 0 * * We pick up those lines that come before the "============" line, and * create one RRD per "Mailer", with the counters. * * The output of the mailstats command will depend on the version of sendmail * used. This example is from sendmail 8.13.x which added the msgsqur column. * Sendmail versions prior to 8.10.0 did not have the mgsdis and msgsrej * columns. * */ char *bofdata, *eofdata, *eoln = NULL; int done, found; unsigned long msgsfr, bytesfr, msgsto, bytesto, msgsrej, msgsdis, msgsqur; if (sendmail_tpl_1 == NULL) sendmail_tpl_1 = setup_template(sendmail_params_1); if (sendmail_tpl_2 == NULL) sendmail_tpl_2 = setup_template(sendmail_params_2); /* Find the line that begins with "=====" and NULL the message there */ eofdata = strstr(msg, "\n=="); if (eofdata) *(eofdata+1) = '\0'; else return -1; /* Find the start of the Statistics part. */ bofdata = strstr(msg, "\nStatistics "); if (!bofdata) return -1; /* Skip the "Statistics from.... " line */ bofdata = strchr(bofdata+1, '\n'); if (!bofdata) return -1; /* Skip the header line */ bofdata = strchr(bofdata+1, '\n'); if (bofdata) bofdata++; else return -1; done = (bofdata == NULL); while (!done) { char mailer[1024]; MEMDEFINE(mailer); *rrdvalues = '\0'; eoln = strchr(bofdata, '\n'); if (eoln) { *eoln = '\0'; /* First try for sendmail 8.13.x format */ found = sscanf(bofdata, "%*s %lu %luK %lu %luK %lu %lu %lu %s", &msgsfr, &bytesfr, &msgsto, &bytesto, &msgsrej, &msgsdis, &msgsqur, mailer); if (found == 8) { sprintf(rrdvalues, "%d:%lu:%lu:%lu:%lu:%lu:%lu:%lu", (int)tstamp, msgsfr, bytesfr*1024, msgsto, bytesto*1024, msgsrej, msgsdis, msgsqur); goto gotdata; } /* Next sendmail 8.10.x - without msgsqur */ found = sscanf(bofdata, "%*s %lu %luK %lu %luK %lu %lu %s", &msgsfr, &bytesfr, &msgsto, &bytesto, &msgsrej, &msgsdis, mailer); if (found == 7) { sprintf(rrdvalues, "%d:%lu:%lu:%lu:%lu:%lu:%lu:U", (int)tstamp, msgsfr, bytesfr*1024, msgsto, bytesto*1024, msgsrej, msgsdis); goto gotdata; } /* Last resort: Sendmail prior to 8.10 - without msgsrej, msgsdis, msgsqur */ found = sscanf(bofdata, "%*s %lu %luK %lu %luK %s", &msgsfr, &bytesfr, &msgsto, &bytesto, mailer); if (found == 5) { sprintf(rrdvalues, "%d:%lu:%lu:%lu:%lu:U:U:U", (int)tstamp, msgsfr, bytesfr*1024, msgsto, bytesto*1024); goto gotdata; } gotdata: if (*rrdvalues) { int dscount, i; char **dsnames = NULL; setupfn2("%s.%s.rrd", "sendmail", mailer); /* Get the RRD-file dataset count, so we can decide what to do */ dscount = rrddatasets(hostname, &dsnames); if ((dscount > 0) && dsnames) { /* Free the dsnames list */ for (i=0; (i */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char netapp_rcsid[] = "$Id: do_netapp.c 6648 2011-03-08 13:05:32Z storner $"; int do_netapp_stats_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *netapp_stats_params[] = { "DS:NETread:GAUGE:600:0:U", "DS:NETwrite:GAUGE:600:0:U", "DS:DISKread:GAUGE:600:0:U", "DS:DISKwrite:GAUGE:600:0:U", "DS:TAPEread:GAUGE:600:0:U", "DS:TAPEwrite:GAUGE:600:0:U", "DS:FCPin:GAUGE:600:0:U", "DS:FCPout:GAUGE:600:0:U", NULL }; static void *netapp_stats_tpl = NULL; unsigned long netread=0, netwrite=0, diskread=0, diskwrite=0, taperead=0, tapewrite=0, fcpin=0, fcpout=0; dbgprintf("netapp: host %s test %s\n",hostname, testname); if (strstr(msg, "netapp.pl")) { setupfn("%s.rrd", testname); if (netapp_stats_tpl == NULL) netapp_stats_tpl = setup_template(netapp_stats_params); netread=get_kb_data(msg, "NET_read"); netwrite=get_kb_data(msg,"NET_write"); diskread=get_kb_data(msg,"DISK_read"); diskwrite=get_kb_data(msg,"DISK_write"); taperead=get_kb_data(msg,"TAPE_read"); tapewrite=get_kb_data(msg,"TAPE_write"); fcpin=get_kb_data(msg,"FCP_in"); fcpout=get_kb_data(msg,"FCP_out"); dbgprintf("netapp: host %s test %s netread %ld netwrite %ld\n", hostname, testname, netread,netwrite); dbgprintf("netapp: host %s test %s diskread %ld diskwrite %ld\n", hostname, testname, diskread,diskwrite); dbgprintf("netapp: host %s test %s taperead %ld tapewrite %ld\n", hostname, testname, taperead,tapewrite); dbgprintf("netapp: host %s test %s fcpin %ld fcpout %ld\n", hostname, testname, fcpin,fcpout); sprintf(rrdvalues, "%d:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld", (int) tstamp, netread, netwrite, diskread, diskwrite, taperead, tapewrite, fcpin, fcpout); create_and_update_rrd(hostname, testname, classname, pagepaths, netapp_stats_params, netapp_stats_tpl); } return 0; } int do_netapp_cifs_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *netapp_cifs_params[] = { "DS:sessions:GAUGE:600:0:U", "DS:openshares:GAUGE:600:0:U", "DS:openfiles:GAUGE:600:0:U", "DS:locks:GAUGE:600:0:U", "DS:credentials:GAUGE:600:0:U", "DS:opendirectories:GAUGE:600:0:U", "DS:ChangeNotifies:GAUGE:600:0:U", "DS:sessionsusingsecuri:GAUGE:600:0:U", NULL }; static void *netapp_cifs_tpl = NULL; unsigned long sess=0, share=0, file=0, lock=0, cred=0, dir=0, change=0, secsess=0; dbgprintf("netapp: host %s test %s\n",hostname, testname); if (strstr(msg, "netapp.pl")) { setupfn("%s.rrd", testname); if (netapp_cifs_tpl == NULL) netapp_cifs_tpl = setup_template(netapp_cifs_params); sess=get_long_data(msg, "sessions"); share=get_long_data(msg,"open_shares"); file=get_long_data(msg,"open_files"); lock=get_long_data(msg,"locks"); cred=get_long_data(msg,"credentials"); dir=get_long_data(msg,"open_directories"); change=get_long_data(msg,"ChangeNotifies"); secsess=get_long_data(msg,"sessions_using_security_signatures"); dbgprintf("netapp: host %s test %s Session %ld OpenShare %ld\n", hostname, testname, sess, share); dbgprintf("netapp: host %s test %s OpenFile %ld Locks %ld\n", hostname, testname, file, lock); dbgprintf("netapp: host %s test %s Cred %ld OpenDir %ld\n", hostname, testname, cred, dir); dbgprintf("netapp: host %s test %s ChangeNotif %ld SecureSess %ld\n", hostname, testname, change, secsess); sprintf(rrdvalues, "%d:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld", (int) tstamp, sess, share, file, lock, cred, dir, change,secsess); create_and_update_rrd(hostname, testname, classname, pagepaths, netapp_cifs_params, netapp_cifs_tpl); } return 0; } int do_netapp_ops_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *netapp_ops_params[] = { "DS:NFSops:GAUGE:600:0:U", "DS:CIFSops:GAUGE:600:0:U", "DS:iSCSIops:GAUGE:600:0:U", "DS:HTTPops:GAUGE:600:0:U", "DS:FCPops:GAUGE:600:0:U", "DS:Totalops:GAUGE:600:0:U", NULL }; static void *netapp_ops_tpl = NULL; unsigned long nfsops=0, cifsops=0, httpops=0, iscsiops=0, fcpops=0, totalops=0; dbgprintf("netapp: host %s test %s\n",hostname, testname); if (strstr(msg, "netapp.pl")) { setupfn("%s.rrd",testname); if (netapp_ops_tpl == NULL) netapp_ops_tpl = setup_template(netapp_ops_params); nfsops=get_long_data(msg, "NFS_ops"); cifsops=get_long_data(msg,"CIFS_ops"); httpops=get_long_data(msg,"HTTP_ops"); fcpops=get_long_data(msg,"FCP_ops"); iscsiops=get_long_data(msg,"iSCSI_ops"); totalops=get_long_data(msg,"Total_ops"); dbgprintf("netapp: host %s test %s nfsops %ld cifsops %ld\n", hostname, testname, nfsops, cifsops); dbgprintf("netapp: host %s test %s httpops %ld fcpops %ld\n", hostname, testname, httpops, fcpops); dbgprintf("netapp: host %s test %s iscsiops %ld totalops %ld\n", hostname, testname, iscsiops, totalops); sprintf(rrdvalues, "%d:%ld:%ld:%ld:%ld:%ld:%ld", (int) tstamp, nfsops, cifsops, httpops, fcpops, iscsiops, totalops); create_and_update_rrd(hostname, testname, classname, pagepaths, netapp_ops_params, netapp_ops_tpl); } return 0; } int do_netapp_snapmirror_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *netapp_snapmirror_params[] = { "DS:size:GAUGE:600:0:U", NULL }; static void *netapp_snapmirror_tpl = NULL; char *eoln, *curline, *start, *end; dbgprintf("netapp: host %s test %s\n",hostname, testname); if (strstr(msg, "netapp.pl")) { if (netapp_snapmirror_tpl == NULL) netapp_snapmirror_tpl = setup_template(netapp_snapmirror_params); if ((start=strstr(msg, ""))==NULL) return 0; *end='\0'; eoln = strchr(start, '\n'); curline = (eoln ? (eoln+1) : NULL); while (curline && (*curline)) { char *equalsign; long long size; eoln = strchr(curline, '\n'); if (eoln) *eoln = '\0'; equalsign=strchr(curline,'='); if (equalsign) { *(equalsign++)= '\0'; size=str2ll(equalsign,NULL); dbgprintf("netapp: host %s test %s SNAPMIRROR %s size %lld\n", hostname, testname, curline, size); setupfn2("%s,%s.rrd", testname, curline); sprintf(rrdvalues, "%d:%lld", (int)tstamp, size); create_and_update_rrd(hostname, testname, classname, pagepaths, netapp_snapmirror_params, netapp_snapmirror_tpl); *(--equalsign)='='; } if (eoln) *eoln = '\n'; curline = (eoln ? (eoln+1) : NULL); } *end='-'; } return 0; } int do_netapp_snaplist_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *netapp_snaplist_params[] = { "DS:youngsize:GAUGE:600:0:U", "DS:oldsize:GAUGE:600:0:U", NULL }; static void *netapp_snaplist_tpl = NULL; char *eoln, *curline, *start, *end; dbgprintf("netapp: host %s test %s\n",hostname, testname); if (strstr(msg, "netapp.pl")) { if (netapp_snaplist_tpl == NULL) netapp_snaplist_tpl = setup_template(netapp_snaplist_params); if ((start=strstr(msg, ""))==NULL) return 0; *end='\0'; eoln = strchr(start, '\n'); curline = (eoln ? (eoln+1) : NULL); while (curline && (*curline)) { char *fsline, *p; char *columns[5]; int columncount; char *volname = NULL; long long young,old; eoln = strchr(curline, '\n'); if (eoln) *eoln = '\0'; for (columncount=0; (columncount<5); columncount++) columns[columncount] = ""; fsline = xstrdup(curline); columncount = 0; p = strtok(fsline, "="); while (p && (columncount < 5)) { columns[columncount++] = p; p = strtok(NULL, ":"); } volname = xstrdup(columns[0]); young=str2ll(columns[1],NULL); old=str2ll(columns[2],NULL); dbgprintf("netapp: host %s test %s vol %s young %lld old %lld\n", hostname, testname, volname, young, old); setupfn2("%s,%s.rrd", testname, volname); sprintf(rrdvalues, "%d:%lld:%lld", (int)tstamp, young, old); create_and_update_rrd(hostname, testname, classname, pagepaths, netapp_snaplist_params, netapp_snaplist_tpl); if (volname) { xfree(volname); volname = NULL; } if (eoln) *eoln = '\n'; xfree(fsline); curline = (eoln ? (eoln+1) : NULL); } *end='-'; } return 0; } int do_netapp_extratest_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp, char *params[],char *varlist[]) { static void *netapp_tpl = NULL; char *outp; char *eoln,*curline; char *rrdp; /* Setup the update string */ netapp_tpl = setup_template(params); curline = msg; dbgprintf("MESSAGE=%s\n",msg); rrdp = rrdvalues + sprintf(rrdvalues, "%d", (int)tstamp); while (curline && (*curline)) { char *fsline, *p, *sep, *fname=NULL; char *columns[30]; int columncount; char *volname = NULL; int i,flag,l,first,totnum; char *val; outp=rrdp; eoln = strchr(curline, '\n'); if (eoln) *eoln = '\0'; if ((eoln == curline) || (strstr(curline,"netapp.pl"))) { dbgprintf("SKIP LINE=\n",curline); goto nextline; } fsline = xstrdup(curline); dbgprintf("LINE=%s\n",fsline); for (columncount=0; (columncount<30); columncount++) columns[columncount] = NULL; for (totnum=0; varlist[totnum]; totnum++) ; columncount = 0; p = strtok(fsline, ";"); first=0; while (p) { if (first==0) { fname=p; first=1; } else { sep=strchr(p,':'); if (sep) { l=sep-p; sep++; dbgprintf("Checking %s len=%d\n",p,l); flag=0; i = 0; while ((flag==0) && (varlist[i])) { dbgprintf("with %s\n",varlist[i]); if (strncmp(varlist[i], p, l) == 0) { columns[i]=sep; flag=1; } i++; } } } p = strtok(NULL, ";"); } volname = xstrdup(fname); p = volname; while ((p = strchr(p, '/')) != NULL) { *p = ','; } dbgprintf("netapp: host %s test %s name %s \n", hostname, testname, volname); setupfn2("%s,%s.rrd", testname, volname); for (i=0; varlist[i]; i++) { val=columns[i]; if (val) { outp += sprintf(outp, ":%s",val); dbgprintf("var %s value %s \n", varlist[i], columns[i]); } else { outp += sprintf(outp, ":%s","U"); } } create_and_update_rrd(hostname, testname, classname, pagepaths, params, netapp_tpl); if (volname) { xfree(volname); volname = NULL; } if (eoln) *eoln = '\n'; xfree(fsline); nextline: curline = (eoln ? (eoln+1) : NULL); } return 0; } int do_netapp_extrastats_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *netapp_qtree_params[] = { "DS:nfs_ops:GAUGE:600:0:U", "DS:cifs_ops:GAUGE:600:0:U", NULL }; static char *netapp_aggregate_params[] = { "DS:total_transfers:GAUGE:600:0:U", "DS:user_reads:GAUGE:600:0:U", "DS:user_writes:GAUGE:600:0:U", "DS:cp_reads:GAUGE:600:0:U", "DS:user_read_blocks:GAUGE:600:0:U", "DS:user_write_blocks:GAUGE:600:0:U", "DS:cp_read_blocks:GAUGE:600:0:U", NULL }; static char *netapp_iscsi_params[] = { "DS:iscsi_ops:GAUGE:600:0:U", "DS:iscsi_write_data:GAUGE:600:0:U", "DS:iscsi_read_data:GAUGE:600:0:U", NULL }; static char *netapp_fcp_params[] = { "DS:fcp_ops:GAUGE:600:0:U", "DS:fcp_write_data:GAUGE:600:0:U", "DS:fcp_read_data:GAUGE:600:0:U", NULL }; static char *netapp_cifs_params[] = { "DS:cifs_ops:GAUGE:600:0:U", "DS:cifs_latency:GAUGE:600:0:U", NULL }; static char *netapp_volume_params[] = { "DS:avg_latency:GAUGE:600:0:U", "DS:total_ops:GAUGE:600:0:U", "DS:read_data:GAUGE:600:0:U", "DS:read_latency:GAUGE:600:0:U", "DS:read_ops:GAUGE:600:0:U", "DS:write_data:GAUGE:600:0:U", "DS:write_latency:GAUGE:600:0:U", "DS:write_ops:GAUGE:600:0:U", "DS:other_latency:GAUGE:600:0:U", "DS:other_ops:GAUGE:600:0:U", NULL }; static char *netapp_lun_params[] = { "DS:read_ops:GAUGE:600:0:U", "DS:write_ops:GAUGE:600:0:U", "DS:other_ops:GAUGE:600:0:U", "DS:read_data:GAUGE:600:0:U", "DS:write_data:GAUGE:600:0:U", "DS:queue_full:GAUGE:600:0:U", "DS:avg_latency:GAUGE:600:0:U", "DS:total_ops:GAUGE:600:0:U", NULL }; static char *netapp_nfsv3_params[] = { "DS:ops:GAUGE:600:0:U", "DS:read_latency:GAUGE:600:0:U", "DS:read_ops:GAUGE:600:0:U", "DS:write_latency:GAUGE:600:0:U", "DS:write_ops:GAUGE:600:0:U", NULL }; static char *netapp_ifnet_params[] = { "DS:recv_packets:GAUGE:600:0:U", "DS:recv_errors:GAUGE:600:0:U", "DS:send_packets:GAUGE:600:0:U", "DS:send_errors:GAUGE:600:0:U", "DS:collisions:GAUGE:600:0:U", "DS:recv_data:GAUGE:600:0:U", "DS:send_data:GAUGE:600:0:U", "DS:recv_mcasts:GAUGE:600:0:U", "DS:send_mcasts:GAUGE:600:0:U", "DS:recv_drop_packets:GAUGE:600:0:U", NULL }; static char *netapp_processor_params[] = { "DS:proc_busy:GAUGE:600:0:U", NULL }; static char *netapp_disk_params[] = { "DS:total_transfers:GAUGE:600:0:U", "DS:user_read_chain:GAUGE:600:0:U", "DS:user_reads:GAUGE:600:0:U", "DS:user_write_chain:GAUGE:600:0:U", "DS:user_writes:GAUGE:600:0:U", "DS:cp_read_chain:GAUGE:600:0:U", "DS:cp_reads:GAUGE:600:0:U", "DS:gar_read_chain:GAUGE:600:0:U", "DS:gar_reads:GAUGE:600:0:U", "DS:gar_write_chain:GAUGE:600:0:U", "DS:gar_writes:GAUGE:600:0:U", "DS:user_read_latency:GAUGE:600:0:U", "DS:user_read_blocks:GAUGE:600:0:U", "DS:user_write_latency:GAUGE:600:0:U", "DS:user_write_blocks:GAUGE:600:0:U", "DS:cp_read_latency:GAUGE:600:0:U", "DS:cp_read_blocks:GAUGE:600:0:U", "DS:gar_read_latency:GAUGE:600:0:U", "DS:gar_read_blocks:GAUGE:600:0:U", "DS:gar_write_latency:GAUGE:600:0:U", "DS:gar_write_blocks:GAUGE:600:0:U", "DS:disk_busy:GAUGE:600:0:U", NULL }; static char *netapp_system_params[] = { "DS:nfs_ops:GAUGE:600:0:U", "DS:cifs_ops:GAUGE:600:0:U", "DS:http_ops:GAUGE:600:0:U", "DS:dafs_ops:GAUGE:600:0:U", "DS:fcp_ops:GAUGE:600:0:U", "DS:iscsi_ops:GAUGE:600:0:U", "DS:net_data_recv:GAUGE:600:0:U", "DS:net_data_sent:GAUGE:600:0:U", "DS:disk_data_read:GAUGE:600:0:U", "DS:disk_data_written:GAUGE:600:0:U", "DS:cpu_busy:GAUGE:600:0:U", "DS:avg_proc_busy:GAUGE:600:0:U", "DS:total_proc_busy:GAUGE:600:0:U", "DS:num_proc:GAUGE:600:0:U", "DS:time:GAUGE:600:0:U", "DS:uptime:GAUGE:600:0:U", NULL }; static char *qtree_test[] = { "nfs_ops", "cifs_ops" ,NULL }; static char *aggregate_test[] = { "total_transfers", "user_reads", "user_writes", "cp_reads", "user_read_blocks", "user_write_blocks", "cp_read_blocks" ,NULL }; static char *iscsi_test[] = { "iscsi_ops", "iscsi_write_data", "iscsi_read_data" ,NULL }; static char *fcp_test[] = { "fcp_ops", "fcp_write_data", "fcp_read_data" ,NULL }; static char *cifs_test[] = { "cifs_ops", "cifs_latency" ,NULL }; static char *volume_test[] = { "avg_latency", "total_ops", "read_data", "read_latency", "read_ops", "write_data", "write_latency", "write_ops", "other_latency", "other_ops" ,NULL }; static char *lun_test[] = { "read_ops", "write_ops", "other_ops", "read_data", "write_data", "queue_full", "avg_latency", "total_ops" ,NULL }; static char *nfsv3_test[] = { "nfsv3_ops", "nfsv3_read_latency", "nfsv3_read_ops", "nfsv3_write_latency", "nfsv3_write_ops" ,NULL }; static char *ifnet_test[] = { "recv_packets", "recv_errors", "send_packets", "send_errors", "collisions", "recv_data", "send_data", "recv_mcasts", "send_mcasts", "recv_drop_packets" ,NULL }; static char *processor_test[] = { "processor_busy" ,NULL }; static char *disk_test[] = { "total_transfers", "user_read_chain", "user_reads", "user_write_chain", "user_writes", "cp_read_chain", "cp_reads", "guarenteed_read_chain", "guaranteed_reads", "guarenteed_write_chain", "guaranteed_writes", "user_read_latency", "user_read_blocks", "user_write_latency", "user_write_blocks", "cp_read_latency", "cp_read_blocks", "guarenteed_read_latency", "guarenteed_read_blocks", "guarenteed_write_latency", "guarenteed_write_blocks", "disk_busy" ,NULL }; static char *system_test[] = { "nfs_ops", "cifs_ops", "http_ops", "dafs_ops", "fcp_ops", "iscsi_ops", "net_data_recv", "net_data_sent", "disk_data_read", "disk_data_written", "cpu_busy", "avg_processor_busy", "total_processor_busy", "num_processors", "time", "uptime" ,NULL }; char *ifnetstr; char *qtreestr; char *aggregatestr; char *volumestr; char *lunstr; char *diskstr; splitmsg(msg); ifnetstr = getdata("ifnet"); qtreestr = getdata("qtree"); aggregatestr = getdata("aggregate"); volumestr = getdata("volume"); lunstr = getdata("lun"); diskstr = getdata("disk"); do_netapp_extratest_rrd(hostname,"xstatifnet",classname,pagepaths,ifnetstr,tstamp,netapp_ifnet_params,ifnet_test); do_netapp_extratest_rrd(hostname,"xstatqtree",classname,pagepaths,qtreestr,tstamp,netapp_qtree_params,qtree_test); do_netapp_extratest_rrd(hostname,"xstataggregate",classname,pagepaths,aggregatestr,tstamp,netapp_aggregate_params,aggregate_test); do_netapp_extratest_rrd(hostname,"xstatvolume",classname,pagepaths,volumestr,tstamp,netapp_volume_params,volume_test); do_netapp_extratest_rrd(hostname,"xstatlun",classname,pagepaths,lunstr,tstamp,netapp_lun_params,lun_test); do_netapp_extratest_rrd(hostname,"xstatdisk",classname,pagepaths,diskstr,tstamp,netapp_disk_params,disk_test); return 0; } int do_netapp_disk_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *netapp_disk_params[] = { "DS:pct:GAUGE:600:0:U", "DS:used:GAUGE:600:0:U", NULL }; static rrdtpldata_t *netapp_disk_tpl = NULL; char *eoln, *curline; static int ptnsetup = 0; static pcre *inclpattern = NULL; static pcre *exclpattern = NULL; int newdfreport; newdfreport = (strstr(msg,"netappnewdf") != NULL); if (netapp_disk_tpl == NULL) netapp_disk_tpl = setup_template(netapp_disk_params); if (!ptnsetup) { const char *errmsg; int errofs; char *ptn; ptnsetup = 1; ptn = getenv("RRDDISKS"); if (ptn && strlen(ptn)) { inclpattern = pcre_compile(ptn, PCRE_CASELESS, &errmsg, &errofs, NULL); if (!inclpattern) errprintf("PCRE compile of RRDDISKS='%s' failed, error %s, offset %d\n", ptn, errmsg, errofs); } ptn = getenv("NORRDDISKS"); if (ptn && strlen(ptn)) { exclpattern = pcre_compile(ptn, PCRE_CASELESS, &errmsg, &errofs, NULL); if (!exclpattern) errprintf("PCRE compile of NORRDDISKS='%s' failed, error %s, offset %d\n", ptn, errmsg, errofs); } } /* * Francesco Duranti noticed that if we use the "/group" option * when sending the status message, this tricks the parser to * create an extra filesystem called "/group". So skip the first * line - we never have any disk reports there anyway. */ curline = strchr(msg, '\n'); if (curline) curline++; while (curline) { char *fsline, *p; char *columns[20]; int columncount; char *diskname = NULL; int pused = -1; int wanteddisk = 1; long long aused = 0; /* FD: Using double instead of long long because we can have decimal on Netapp and DbCheck */ double dused = 0; /* FD: used to add a column if the filesystem is named "snap reserve" for netapp.pl */ int snapreserve=0; eoln = strchr(curline, '\n'); if (eoln) *eoln = '\0'; /* FD: netapp.pl snapshot line that start with "snap reserve" need a +1 */ if (strstr(curline, "snap reserve")) snapreserve=1; /* All clients except AS/400 and DBCHECK report the mount-point with slashes - ALSO Win32 clients. */ if (strchr(curline, '/') == NULL) goto nextline; /* red/yellow filesystems show up twice */ if (*curline == '&') goto nextline; if ((strstr(curline, " red ") || strstr(curline, " yellow "))) goto nextline; for (columncount=0; (columncount<20); columncount++) columns[columncount] = ""; fsline = xstrdup(curline); columncount = 0; p = strtok(fsline, " "); while (p && (columncount < 20)) { columns[columncount++] = p; p = strtok(NULL, " "); } /* FD: Name column can contain "spaces" so it could be split in multiple columns, create a unique string from columns[5] that point to the complete disk name */ while (columncount-- > 6+snapreserve) { p = strchr(columns[columncount-1],0); if (p) *p = '_'; } /* FD: Add an initial "/" to qtree and quotas */ if (newdfreport) { diskname = xstrdup(columns[0]); } else if (*columns[5+snapreserve] != '/') { diskname=xmalloc(strlen(columns[5+snapreserve])+2); sprintf(diskname,"/%s",columns[5+snapreserve]); } else { diskname = xstrdup(columns[5+snapreserve]); } p = strchr(columns[4+snapreserve], '%'); if (p) *p = ' '; pused = atoi(columns[4+snapreserve]); p = columns[2+snapreserve] + strspn(columns[2+snapreserve], "0123456789."); /* Using double instead of long long because we can have decimal */ dused = str2ll(columns[2+snapreserve], NULL); /* snapshot and qtree contains M/G/T Convert to KB if there's a modifier after the numbers */ if (*p == 'M') dused *= 1024; else if (*p == 'G') dused *= (1024*1024); else if (*p == 'T') dused *= (1024*1024*1024); aused=(long long) dused; /* Check include/exclude patterns */ wanteddisk = 1; if (exclpattern) { int ovector[30]; int result; result = pcre_exec(exclpattern, NULL, diskname, strlen(diskname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); wanteddisk = (result < 0); } if (wanteddisk && inclpattern) { int ovector[30]; int result; result = pcre_exec(inclpattern, NULL, diskname, strlen(diskname), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); wanteddisk = (result >= 0); } if (wanteddisk && diskname && (pused != -1)) { p = diskname; while ((p = strchr(p, '/')) != NULL) { *p = ','; } if (strcmp(diskname, ",") == 0) { diskname = xrealloc(diskname, 6); strcpy(diskname, ",root"); } /* * Use testname here. * The disk-handler also gets data from NetAPP inode- and qtree-messages, * that are virtually identical to the disk-messages. So lets just handle * all of it by using the testname as part of the filename. */ setupfn2("%s%s.rrd", testname, diskname); sprintf(rrdvalues, "%d:%d:%lld", (int)tstamp, pused, aused); create_and_update_rrd(hostname, testname, classname, pagepaths, netapp_disk_params, netapp_disk_tpl); } if (diskname) { xfree(diskname); diskname = NULL; } if (eoln) *eoln = '\n'; xfree(fsline); nextline: curline = (eoln ? (eoln+1) : NULL); } return 0; } xymon-4.3.7/xymond/rrd/do_snmpmib.c0000664000175000017500000001604011654463417016637 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char snmpmib_rcsid[] = "$Id: do_snmpmib.c 6768 2011-11-03 10:03:59Z storner $"; static time_t snmp_nextreload = 0; typedef struct snmpmib_param_t { char *name; char **valnames; char **dsdefs; rrdtpldata_t *tpl; int valcount; } snmpmib_param_t; static void * snmpmib_paramtree; int is_snmpmib_rrd(char *testname) { time_t now = getcurrenttime(NULL); xtreePos_t handle; mibdef_t *mib; oidset_t *swalk; int i; if (now > snmp_nextreload) { int updated = readmibs(NULL, 0); if (updated) { if (snmp_nextreload > 0) { /* Flush the old params and templates */ snmpmib_param_t *walk; int i; for (handle = xtreeFirst(snmpmib_paramtree); (handle != xtreeEnd(snmpmib_paramtree)); handle = xtreeNext(snmpmib_paramtree, handle)) { walk = (snmpmib_param_t *)xtreeData(snmpmib_paramtree, handle); if (walk->valnames) xfree(walk->valnames); for (i=0; (i < walk->valcount); i++) xfree(walk->dsdefs[i]); if (walk->dsdefs) xfree(walk->dsdefs); /* * We don't free the "tpl" data here. We cannot do it, because * there are probably cached RRD updates waiting to use this * template - so freeing it would cause all sorts of bad behaviour. * It DOES cause a memory leak ... */ xfree(walk); } xtreeDestroy(snmpmib_paramtree); } snmpmib_paramtree = xtreeNew(strcasecmp); } snmp_nextreload = now + 600; } mib = find_mib(testname); if (!mib) return 0; handle = xtreeFind(snmpmib_paramtree, mib->mibname); if (handle == xtreeEnd(snmpmib_paramtree)) { snmpmib_param_t *newitem = (snmpmib_param_t *)calloc(1, sizeof(snmpmib_param_t)); int totalvars; newitem->name = mib->mibname; for (swalk = mib->oidlisthead, totalvars = 1; (swalk); swalk = swalk->next) totalvars += swalk->oidcount; newitem->valnames = (char **)calloc(totalvars, sizeof(char *)); newitem->dsdefs = (char **)calloc(totalvars, sizeof(char *)); for (swalk = mib->oidlisthead, newitem->valcount = 0; (swalk); swalk = swalk->next) { for (i=0; (i <= swalk->oidcount); i++) { char *datatypestr, *minimumstr; if (swalk->oids[i].rrdtype == RRD_NOTRACK) continue; switch (swalk->oids[i].rrdtype) { case RRD_TRACK_GAUGE: datatypestr = "GAUGE"; minimumstr = "U"; break; case RRD_TRACK_ABSOLUTE: datatypestr = "ABSOLUTE"; minimumstr = "U"; break; case RRD_TRACK_COUNTER: datatypestr = "COUNTER"; minimumstr = "0"; break; case RRD_TRACK_DERIVE: datatypestr = "DERIVE"; minimumstr = "0"; break; case RRD_NOTRACK: break; } newitem->valnames[newitem->valcount] = swalk->oids[i].dsname; newitem->dsdefs[newitem->valcount] = (char *)malloc(strlen(swalk->oids[i].dsname) + 20); sprintf(newitem->dsdefs[newitem->valcount], "DS:%s:%s:600:%s:U", swalk->oids[i].dsname, datatypestr, minimumstr); newitem->valcount++; } } newitem->valnames[newitem->valcount] = NULL; newitem->dsdefs[newitem->valcount] = NULL; newitem->tpl = setup_template(newitem->dsdefs); xtreeAdd(snmpmib_paramtree, newitem->name, newitem); } return 1; } static void do_simple_snmpmib(char *hostname, char *testname, char *classname, char *pagepaths, char *fnkey, char *msg, time_t tstamp, snmpmib_param_t *params, int *pollinterval) { char *bol, *eoln; char **values; int valcount = 0; values = (char **)calloc(params->valcount, sizeof(char *)); bol = msg; while (bol) { eoln = strchr(bol, '\n'); if (eoln) *eoln = '\0'; bol += strspn(bol, " \t"); if (*bol == '\0') { /* Nothing */ } else if (strncmp(bol, "Interval=", 9) == 0) { *pollinterval = atoi(bol+9); } else if (strncmp(bol, "ActiveIP=", 9) == 0) { /* Nothing */ } else { char *valnam, *valstr = NULL; valnam = strtok(bol, " ="); if (valnam) valstr = strtok(NULL, " ="); if (valnam && valstr) { int validx; for (validx = 0; (params->valnames[validx] && strcmp(params->valnames[validx], valnam)); validx++) ; /* Note: There may be items which are not RRD data (eg text strings) */ if (params->valnames[validx]) { values[validx] = (isdigit(*valstr) ? valstr : "U"); valcount++; } } } bol = (eoln ? eoln+1 : NULL); } if (valcount == params->valcount) { int i; char *ptr; if (fnkey) setupfn2("%s.%s.rrd", testname, fnkey); else setupfn("%s.rrd", testname); setupinterval(*pollinterval); ptr = rrdvalues + sprintf(rrdvalues, "%d", (int)tstamp); for (i = 0; (i < valcount); i++) { ptr += sprintf(ptr, ":%s", values[i]); } create_and_update_rrd(hostname, testname, classname, pagepaths, params->dsdefs, params->tpl); } xfree(values); } static void do_tabular_snmpmib(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp, snmpmib_param_t *params) { char *fnkey; int pollinterval = 0; char *boset, *eoset, *intvl; boset = strstr(msg, "\n["); if (!boset) return; /* See if there's a poll interval value */ *boset = '\0'; boset++; intvl = strstr(msg, "Interval="); if (intvl) pollinterval = atoi(intvl+9); while (boset) { fnkey = boset+1; boset = boset + strcspn(boset, "]\n"); *boset = '\0'; boset++; eoset = strstr(boset, "\n["); if (eoset) *eoset = '\0'; do_simple_snmpmib(hostname, testname, classname, pagepaths, fnkey, boset, tstamp, params, &pollinterval); boset = (eoset ? eoset+1 : NULL); } } int do_snmpmib_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { time_t now = getcurrenttime(NULL); mibdef_t *mib; xtreePos_t handle; snmpmib_param_t *params; int pollinterval = 0; char *datapart; if (now > snmp_nextreload) readmibs(NULL, 0); mib = find_mib(testname); if (!mib) return 0; handle = xtreeFind(snmpmib_paramtree, mib->mibname); if (handle == xtreeEnd(snmpmib_paramtree)) return 0; params = (snmpmib_param_t *)xtreeData(snmpmib_paramtree, handle); if (params->valcount == 0) return 0; if ((strncmp(msg, "status", 6) == 0) || (strncmp(msg, "data", 4) == 0)) { /* Skip the first line of full status- and data-messages. */ datapart = strchr(msg, '\n'); if (datapart) datapart++; else datapart = msg; } if (mib->tabular) do_tabular_snmpmib(hostname, testname, classname, pagepaths, datapart, tstamp, params); else do_simple_snmpmib(hostname, testname, classname, pagepaths, NULL, datapart, tstamp, params, &pollinterval); return 0; } xymon-4.3.7/xymond/rrd/do_ntpstat.c0000664000175000017500000000334011615341300016645 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char ntpstat_rcsid[] = "$Id: do_ntpstat.c 6712 2011-07-31 21:01:52Z storner $"; int do_ntpstat_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *ntpstat_params[] = { "DS:offsetms:GAUGE:600:U:U", NULL }; static void *ntpstat_tpl = NULL; char *p; float offset; int gotdata = 0; if (ntpstat_tpl == NULL) ntpstat_tpl = setup_template(ntpstat_params); /* First check for the old LARRD ntpstat BF script */ p = strstr(msg, "\nOffset:"); gotdata = (p && (sscanf(p+1, "Offset: %f", &offset) == 1)); /* Or maybe it's just the "ntpq -c rv" output */ if (!gotdata) { p = strstr(msg, "offset="); if (p && (isspace((int)*(p-1)) || (*(p-1) == ','))) { gotdata = (p && (sscanf(p, "offset=%f", &offset) == 1)); } } if (gotdata) { setupfn("%s.rrd", "ntpstat"); sprintf(rrdvalues, "%d:%.6f", (int)tstamp, offset); return create_and_update_rrd(hostname, testname, classname, pagepaths, ntpstat_params, ntpstat_tpl); } return 0; } xymon-4.3.7/xymond/rrd/do_external.c0000664000175000017500000001123711535462534017014 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2009 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char external_rcsid[] = "$Id: do_external.c 6650 2011-03-08 17:20:28Z storner $"; int do_external_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { pid_t childpid; dbgprintf("-> do_external(%s, %s)\n", hostname, testname); childpid = fork(); if (childpid == 0) { FILE *fd; char fn[PATH_MAX]; enum { R_DEFS, R_FN, R_DATA, R_NEXT } pstate; FILE *extfd; char extcmd[2*PATH_MAX]; strbuffer_t *inbuf; char *p; char **params = NULL; int paridx = 0; pid_t mypid = getpid(); MEMDEFINE(fn); MEMDEFINE(extcmd); sprintf(fn, "%s/rrd_msg_%d", xgetenv("XYMONTMP"), (int) getpid()); dbgprintf("%09d : Saving msg to file %s\n", (int)mypid, fn); fd = fopen(fn, "w"); if (fd == NULL) { errprintf("Cannot create temp file %s\n", fn); exit(1); } if (fwrite(msg, strlen(msg), 1, fd) != 1) { errprintf("Error writing to file %s: %s\n", fn, strerror(errno)); exit(1) ; } if (fclose(fd)) errprintf("Error closing file %s: %s\n", fn, strerror(errno)); /* * Disable the RRD update cache. * We cannot use the cache, because this child * process terminates without flushing the cache, * and it cannot feed the update-data back to the * parent process which owns the cache. So using * an external handler means the updates will be * sync'ed to disk immediately. * * NB: It is OK to do this now and not re-enable it, * since we're running in the external helper * child process - so this only affects the current * update. * * Thanks to Graham Nayler for the analysis. */ use_rrd_cache = 0; inbuf = newstrbuffer(0); /* Now call the external helper */ sprintf(extcmd, "%s %s %s %s", exthandler, hostname, testname, fn); dbgprintf("%09d : Calling helper script %s\n", (int)mypid, extcmd); extfd = popen(extcmd, "r"); if (extfd) { pstate = R_DEFS; initfgets(extfd); while (unlimfgets(inbuf, extfd)) { p = strchr(STRBUF(inbuf), '\n'); if (p) *p = '\0'; dbgprintf("%09d : Helper input '%s'\n", (int)mypid, STRBUF(inbuf)); if (STRBUFLEN(inbuf) == 0) continue; if (pstate == R_NEXT) { /* After doing one set of data, allow script to re-use the same DS defs */ if (strncasecmp(STRBUF(inbuf), "DS:", 3) == 0) { /* New DS definitions, scratch the old ones */ if (params) { for (paridx=0; (params[paridx] != NULL); paridx++) xfree(params[paridx]); xfree(params); params = NULL; } pstate = R_DEFS; } else pstate = R_FN; } switch (pstate) { case R_DEFS: if (params == NULL) { params = (char **)calloc(1, sizeof(char *)); paridx = 0; } if (strncasecmp(STRBUF(inbuf), "DS:", 3) == 0) { /* Dataset definition */ params[paridx] = strdup(STRBUF(inbuf)); paridx++; params = (char **)realloc(params, (1 + paridx)*sizeof(char *)); params[paridx] = NULL; break; } else { /* No more DS defs */ pstate = R_FN; } /* Fall through */ case R_FN: setupfn("%s", STRBUF(inbuf)); pstate = R_DATA; break; case R_DATA: snprintf(rrdvalues, sizeof(rrdvalues)-1, "%d:%s", (int)tstamp, STRBUF(inbuf)); rrdvalues[sizeof(rrdvalues)-1] = '\0'; create_and_update_rrd(hostname, testname, classname, pagepaths, params, NULL); pstate = R_NEXT; break; case R_NEXT: /* Should not happen */ break; } } pclose(extfd); } else { errprintf("Pipe open of RRD handler failed: %s\n", strerror(errno)); } if (params) { for (paridx=0; (params[paridx] != NULL); paridx++) xfree(params[paridx]); xfree(params); } dbgprintf("%09d : Unlinking temp file\n", (int)mypid); unlink(fn); freestrbuffer(inbuf); exit(0); } else if (childpid > 0) { /* Parent continues */ } else { errprintf("Fork failed in RRD handler: %s\n", strerror(errno)); } dbgprintf("<- do_external(%s, %s)\n", hostname, testname); return 0; } xymon-4.3.7/xymond/rrd/do_xymonproxy.c0000664000175000017500000000312111615341300017421 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char xymonproxy_rcsid[] = "$Id: do_xymonproxy.c 6712 2011-07-31 21:01:52Z storner $"; int do_xymonproxy_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *xymonproxy_params[] = { "DS:runtime:GAUGE:600:0:U", NULL }; static void *xymonproxy_tpl = NULL; char *p; float runtime; if (xymonproxy_tpl == NULL) xymonproxy_tpl = setup_template(xymonproxy_params); p = strstr(msg, "Average queue time"); if (p && (sscanf(p, "Average queue time : %f", &runtime) == 1)) { if (strcmp("xymonproxy", testname) != 0) { setupfn2("%s.%s.rrd", "xymonproxy", testname); } else { setupfn("%s.rrd", "xymonproxy"); } sprintf(rrdvalues, "%d:%.2f", (int) tstamp, runtime); return create_and_update_rrd(hostname, testname, classname, pagepaths, xymonproxy_params, xymonproxy_tpl); } return 0; } xymon-4.3.7/xymond/rrd/do_netstat.c0000664000175000017500000004414011615341300016635 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char netstat_rcsid[] = "$Id: do_netstat.c 6712 2011-07-31 21:01:52Z storner $"; static char *netstat_params[] = { "DS:udpInDatagrams:DERIVE:600:0:U", "DS:udpOutDatagrams:DERIVE:600:0:U", "DS:udpInErrors:DERIVE:600:0:U", "DS:tcpActiveOpens:DERIVE:600:0:U", "DS:tcpPassiveOpens:DERIVE:600:0:U", "DS:tcpAttemptFails:DERIVE:600:0:U", "DS:tcpEstabResets:DERIVE:600:0:U", "DS:tcpCurrEstab:GAUGE:600:0:U", "DS:tcpOutDataBytes:DERIVE:600:0:U", "DS:tcpInInorderBytes:DERIVE:600:0:U", "DS:tcpInUnorderBytes:DERIVE:600:0:U", "DS:tcpRetransBytes:DERIVE:600:0:U", "DS:tcpOutDataPackets:DERIVE:600:0:U", "DS:tcpInInorderPackets:DERIVE:600:0:U", "DS:tcpInUnorderPackets:DERIVE:600:0:U", "DS:tcpRetransPackets:DERIVE:600:0:U", NULL }; static void *netstat_tpl = NULL; static char *udpreceived = NULL, *udpsent = NULL, *udperrors = NULL; static char *tcpconnrequests = NULL, *tcpconnaccepts = NULL, *tcpconnfails = NULL, *tcpconncurrent = NULL, *tcpconnresets = NULL; static char *tcpoutdatabytes = NULL, *tcpoutdatapackets = NULL, *tcpinorderbytes = NULL, *tcpinorderpackets = NULL, *tcpoutorderbytes = NULL, *tcpoutorderpackets = NULL, *tcpretransbytes = NULL, *tcpretranspackets = NULL; static void prepare_update(char *outp) { outp += sprintf(outp, ":%s", (udpreceived ? udpreceived : "U")); if (udpreceived) xfree(udpreceived); outp += sprintf(outp, ":%s", (udpsent ? udpsent : "U")); if (udpsent) xfree(udpsent); outp += sprintf(outp, ":%s", (udperrors ? udperrors : "U")); if (udperrors) xfree(udperrors); outp += sprintf(outp, ":%s", (tcpconnrequests ? tcpconnrequests : "U")); if (tcpconnrequests) xfree(tcpconnrequests); outp += sprintf(outp, ":%s", (tcpconnaccepts ? tcpconnaccepts : "U")); if (tcpconnaccepts) xfree(tcpconnaccepts); outp += sprintf(outp, ":%s", (tcpconnfails ? tcpconnfails : "U")); if (tcpconnfails) xfree(tcpconnfails); outp += sprintf(outp, ":%s", (tcpconncurrent ? tcpconncurrent : "U")); if (tcpconncurrent) xfree(tcpconncurrent); outp += sprintf(outp, ":%s", (tcpconnresets ? tcpconnresets : "U")); if (tcpconnresets) xfree(tcpconnresets); outp += sprintf(outp, ":%s", (tcpoutdatabytes ? tcpoutdatabytes : "U")); if (tcpoutdatabytes) xfree(tcpoutdatabytes); outp += sprintf(outp, ":%s", (tcpinorderbytes ? tcpinorderbytes : "U")); if (tcpinorderbytes) xfree(tcpinorderbytes); outp += sprintf(outp, ":%s", (tcpoutorderbytes ? tcpoutorderbytes : "U")); if (tcpoutorderbytes) xfree(tcpoutorderbytes); outp += sprintf(outp, ":%s", (tcpretransbytes ? tcpretransbytes : "U")); if (tcpretransbytes) xfree(tcpretransbytes); outp += sprintf(outp, ":%s", (tcpoutdatapackets ? tcpoutdatapackets : "U")); if (tcpoutdatapackets) xfree(tcpoutdatapackets); outp += sprintf(outp, ":%s", (tcpinorderpackets ? tcpinorderpackets : "U")); if (tcpinorderpackets) xfree(tcpinorderpackets); outp += sprintf(outp, ":%s", (tcpoutorderpackets ? tcpoutorderpackets : "U")); if (tcpoutorderpackets) xfree(tcpoutorderpackets); outp += sprintf(outp, ":%s", (tcpretranspackets ? tcpretranspackets : "U")); if (tcpretranspackets) xfree(tcpretranspackets); } static int handle_pcre_netstat(char *msg, pcre **pcreset, char *outp) { enum { AT_NONE, AT_TCP, AT_UDP } sect = AT_NONE; int havedata = 0; char *datapart, *eoln; char *udperr1 = NULL, *udperr2 = NULL, *udperr3 = NULL; unsigned long udperrs, udperrtotal = 0; char udpstr[20]; datapart = msg; while (datapart && (havedata != 11)) { eoln = strchr(datapart, '\n'); if (eoln) *eoln = '\0'; if (strncasecmp(datapart, "tcp:", 4) == 0) sect = AT_TCP; else if (strncasecmp(datapart, "udp:", 4) == 0) sect = AT_UDP; else { switch (sect) { case AT_TCP: if (pickdata(datapart, pcreset[0], 0, &tcpretranspackets, &tcpretransbytes) || pickdata(datapart, pcreset[1], 0, &tcpoutdatapackets, &tcpoutdatabytes) || pickdata(datapart, pcreset[2], 0, &tcpinorderpackets, &tcpinorderbytes) || pickdata(datapart, pcreset[3], 0, &tcpoutorderpackets, &tcpoutorderbytes) || pickdata(datapart, pcreset[4], 0, &tcpconnrequests) || pickdata(datapart, pcreset[5], 0, &tcpconnaccepts)) havedata++; break; case AT_UDP: if (pickdata(datapart, pcreset[6], 0, &udpreceived) || pickdata(datapart, pcreset[7], 0, &udpsent) || pickdata(datapart, pcreset[8], 0, &udperr1) || pickdata(datapart, pcreset[9], 0, &udperr2) || pickdata(datapart, pcreset[10], 0, &udperr3)) havedata++; break; default: break; } } if (eoln) { *eoln = '\n'; datapart = (eoln+1); } else datapart = NULL; } if (udperr1) { udperrs = atol(udperr1); udperrtotal += udperrs; xfree(udperr1); } if (udperr2) { udperrs = atol(udperr2); udperrtotal += udperrs; xfree(udperr2); } if (udperr3) { udperrs = atol(udperr3); udperrtotal += udperrs; xfree(udperr3); } sprintf(udpstr, "%ld", udperrtotal); udperrors = strdup(udpstr); prepare_update(outp); return (havedata != 0); } /* PCRE for OSF/1 */ static const char *netstat_osf_exprs[] = { /* TCP patterns */ "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\) retransmitted$", "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) packets \\(([0-9]+) bytes\\) received in-sequence$", "^[\t ]*([0-9]+) out-of-order packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) connection requests$", "^[\t ]*([0-9]+) connection accepts$", /* UDP patterns */ "^[\t ]*([0-9]+) packets received$", "^[\t ]*([0-9]+) packets sent$", "^[\t ]*([0-9]+) incomplete headers$", "^[\t ]*([0-9]+) bad data length fields$", "^[\t ]*([0-9]+) bad checksums$" }; /* PCRE for SCO_SV */ static const char *netstat_sco_sv_exprs[] = { /* TCP patterns */ "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\) retransmitted$", "^[\t ]*([0-9]+) packets \\(([0-9]+) bytes\\) received in-sequence$", "^[\t ]*([0-9]+) out-of-order packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) connection requests$", "^[\t ]*([0-9]+) connection accepts$", /* UDP patterns */ "^[\t ]*([0-9]+) incomplete headers$", "^[\t ]*([0-9]+) bad data length fields$", "^[\t ]*([0-9]+) bad checksums$" "^[\t ]*([0-9]+) input packets delivered$", "^[\t ]*([0-9]+) packets sent$" }; /* PCRE for AIX: Matches AIX 4.3.3 5.1 5.2 5.3 and probably others */ static const char *netstat_aix_exprs[] = { /* TCP patterns */ "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\) retransmitted$", "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) packets \\(([0-9]+) bytes\\) received in-sequence$", "^[\t ]*([0-9]+) out-of-order packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) connection requests$", "^[\t ]*([0-9]+) connection accepts$", /* UDP patterns */ "^[\t ]*([0-9]+) datagrams received$", "^[\t ]*([0-9]+) datagrams output$", "^[\t ]*([0-9]+) incomplete headers$", "^[\t ]*([0-9]+) bad data length fields$", "^[\t ]*([0-9]+) bad checksums$" }; /* PCRE for IRIX: Matches IRIX 6.5, possibly others. */ static const char *netstat_irix_exprs[] = { /* TCP patterns */ "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\) retransmitted$", "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) packets \\(([0-9]+) bytes\\) received in-sequence$", "^[\t ]*([0-9]+) out-of-order packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) connection requests$", "^[\t ]*([0-9]+) connection accepts$", /* UDP patterns */ "^[\t ]*([0-9]+) total datagrams received$", "^[\t ]*([0-9]+) datagrams output$", "^[\t ]*([0-9]+) incomplete header$", "^[\t ]*([0-9]+) bad data length field$", "^[\t ]*([0-9]+) bad checksum$" }; /* PCRE for HP-UX: Matches HP-UX 11.11, possibly others */ static const char *netstat_hpux_exprs[] = { /* TCP patterns */ "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\) retransmitted$", "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) packets \\(([0-9]+) bytes\\) received in-sequence$", "^[\t ]*([0-9]+) out of order packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) connection requests$", "^[\t ]*([0-9]+) connection accepts$", /* UDP patterns */ "^[\t ]*([0-9]+) datagrams received$", "^[\t ]*([0-9]+) datagrams output$", "^[\t ]*([0-9]+) incomplete headers$", "^[\t ]*([0-9]+) bad data length fields$", "^[\t ]*([0-9]+) bad checksums$" }; /* PCRE for *BSD: FreeBSD 4.10, OpenBSD and NetBSD */ static const char *netstat_bsd_exprs[] = { /* TCP patterns */ "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\) retransmitted$", "^[\t ]*([0-9]+) data packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) packets \\(([0-9]+) bytes\\) received in-sequence$", "^[\t ]*([0-9]+) out-of-order packets \\(([0-9]+) bytes\\)$", "^[\t ]*([0-9]+) connection requests$", "^[\t ]*([0-9]+) connection accepts$", /* UDP patterns */ "^[\t ]*([0-9]+) datagrams received$", "^[\t ]*([0-9]+) datagrams output$", "^[\t ]*([0-9]+) with incomplete header$", "^[\t ]*([0-9]+) with bad data length field$", "^[\t ]*([0-9]+) with bad checksum$", }; /* This one matches the netstat output from Solaris 8, and also the hpux and aix from bf-netstat */ static char *netstat_unix_markers[] = { "udpInDatagrams", "udpOutDatagrams", "udpInErrors", "tcpActiveOpens", "tcpPassiveOpens", "tcpAttemptFails", "tcpEstabResets", "tcpCurrEstab", "tcpOutDataBytes", "tcpInInorderBytes", "tcpInUnorderBytes", "tcpRetransBytes", "tcpOutDataSegs", "tcpInInorderSegs", "tcpInUnorderSegs", "tcpRetransSegs", NULL }; /* This one matches the *BSD's */ static char *netstat_freebsd_markers[] = { "datagrams received", "datagrams output", "", /* Multiple counters, wont add them up. */ "connection requests", "connection accepts", "bad connection attempts", "", /* Appears not to count resets */ "connections established", "", "", "", "", "packets sent", "received in-sequence", "out-of-order packets", "", /* N data packets (X bytes) retransmitted */ NULL }; /* This one matches all Linux systems */ static char *netstat_linux_markers[] = { "packets received", "packets sent", "packet receive errors", "active connections openings", "passive connection openings", "failed connection attempts", "connection resets received", "connections established", "", "", "", "", "segments send out", /* Yes, they really do write "send" */ "segments received", "", "segments retransmited", NULL }; // /* This one matches sco_sv systems -- useless :( */ // static char *netstat_sco_sv_markers[] = { // "input packets delivered", // "packets sent", // "", /* may be "system errors during input" */ // "connection requests", // "connection accepts", // "failed connect and accept requests", // "resets received while established", // "connections established", // "", /* XX data packets (YY bytes) */ // "", /* check: XX packets (YY bytes) received in-sequence */ // "", /* check: XX out-of-order packets (YY bytes) */ // "", /* XX data packets (YY bytes) retransmitted */ // "", /* maybe "data packets" ? */ // "", /* check: XX packets (YY bytes) received in-sequence */ // "out-of-order packets", // "", /* data packets (YY bytes) retransmitted */ // NULL // }; /* This one matches the "snmpnetstat" output from UCD-SNMP */ static char *netstat_snmp_markers[] = { "total datagrams received", "output datagram requests", "datagrams dropped due to errors", "active opens", "passive opens", "failed attempts", "resets of established connections", "current established connections", "", "", "", "", "segments sent", "segments received", "", "segments retransmitted", NULL }; /* This one is for the Win32 netstat tool */ static char *netstat_win32_markers[] = { "udpDatagramsReceived", "udpDatagramsSent", "udpReceiveErrors", "tcpActiveOpens", "tcpPassiveOpens", "tcpFailedConnectionAttempts", "tcpResetConnections", "tcpCurrentConnections", "", "", "", "", "tcpSegmentsSent", "tcpSegmentsReceived", "", "tcpSegmentsRetransmitted", NULL }; static int do_valaftermarkerequal(char *layout[], char *msg, char *outp) { int i, gotany = 0; char *ln; i = 0; while (layout[i]) { int gotval = 0; if (strlen(layout[i])) { ln = strstr(msg, layout[i]); if (ln) { ln += strlen(layout[i]); ln += strspn(ln, " \t"); if (*ln == '=') { int numlen; ln++; ln += strspn(ln, " \t"); numlen = strspn(ln, "0123456789"); *outp = ':'; outp++; memcpy(outp, ln, numlen); outp += numlen; *outp = '\0'; gotany = gotval = 1; } } } if (!gotval) outp += sprintf(outp, ":U"); i++; } return gotany; } static int do_valbeforemarker(char *layout[], char *msg, char *outp) { int i, gotany = 0; char *ln; i = 0; while (layout[i]) { int gotval = 0; if (strlen(layout[i])) { ln = strstr(msg, layout[i]); while (ln && (ln > msg) && (*ln != '\n')) ln--; if (ln) { int numlen; if (*ln == '\n') ln++; ln += strspn(ln, " \t"); numlen = strspn(ln, "0123456789"); *outp = ':'; outp++; memcpy(outp, ln, numlen); outp += numlen; *outp = '\0'; gotany = gotval = 1; } } if (!gotval) outp += sprintf(outp, ":U"); i++; } return gotany; } int do_netstat_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static int pcres_compiled = 0; static pcre **netstat_osf_pcres = NULL; static pcre **netstat_aix_pcres = NULL; static pcre **netstat_irix_pcres = NULL; static pcre **netstat_hpux_pcres = NULL; static pcre **netstat_bsd_pcres = NULL; static pcre **netstat_sco_sv_pcres = NULL; enum ostype_t ostype; char *datapart = msg; char *outp; int havedata = 0; if (netstat_tpl == NULL) netstat_tpl = setup_template(netstat_params); if (pcres_compiled == 0) { pcres_compiled = 1; netstat_osf_pcres = compile_exprs("OSF", netstat_osf_exprs, (sizeof(netstat_osf_exprs) / sizeof(netstat_osf_exprs[0]))); netstat_aix_pcres = compile_exprs("AIX", netstat_aix_exprs, (sizeof(netstat_aix_exprs) / sizeof(netstat_aix_exprs[0]))); netstat_irix_pcres = compile_exprs("IRIX", netstat_irix_exprs, (sizeof(netstat_irix_exprs) / sizeof(netstat_irix_exprs[0]))); netstat_hpux_pcres= compile_exprs("HP-UX", netstat_hpux_exprs, (sizeof(netstat_hpux_exprs) / sizeof(netstat_hpux_exprs[0]))); netstat_bsd_pcres = compile_exprs("BSD", netstat_bsd_exprs, (sizeof(netstat_bsd_exprs) / sizeof(netstat_bsd_exprs[0]))); netstat_sco_sv_pcres = compile_exprs("SCO_SV", netstat_sco_sv_exprs, (sizeof(netstat_sco_sv_exprs) / sizeof(netstat_sco_sv_exprs[0]))); } if ((strncmp(msg, "status", 6) == 0) || (strncmp(msg, "data", 4) == 0)) { /* Skip the first line of full status- and data-messages. */ datapart = strchr(msg, '\n'); if (datapart) datapart++; else datapart = msg; } ostype = get_ostype(datapart); datapart = strchr(datapart, '\n'); if (datapart) { datapart++; } else { errprintf("Too few lines in netstat report from %s\n", hostname); return -1; } /* Setup the update string */ outp = rrdvalues + sprintf(rrdvalues, "%d", (int)tstamp); switch (ostype) { case OS_SOLARIS: /* * UDP * udpInDatagrams =420642 udpInErrors = 0 * TCP * tcpRtoAlgorithm = 4 tcpRtoMin = 400 */ havedata = do_valaftermarkerequal(netstat_unix_markers, datapart, outp); break; case OS_OSF: havedata = handle_pcre_netstat(datapart, netstat_osf_pcres, outp); break; case OS_AIX: havedata = handle_pcre_netstat(datapart, netstat_aix_pcres, outp); /* Handle the bf-netstat output, for old clients */ if (!havedata) havedata = do_valaftermarkerequal(netstat_unix_markers, datapart, outp); break; case OS_HPUX: havedata = handle_pcre_netstat(datapart, netstat_hpux_pcres, outp); /* Handle the bf-netstat output, for old clients */ if (!havedata) havedata = do_valaftermarkerequal(netstat_unix_markers, datapart, outp); break; case OS_IRIX: havedata = handle_pcre_netstat(datapart, netstat_irix_pcres, outp); break; case OS_FREEBSD: case OS_NETBSD: case OS_OPENBSD: case OS_DARWIN: havedata = handle_pcre_netstat(datapart, netstat_bsd_pcres, outp); if (!havedata) havedata = do_valbeforemarker(netstat_freebsd_markers, datapart, outp); break; case OS_LINUX22: case OS_LINUX: case OS_RHEL3: case OS_ZVM: case OS_ZVSE: case OS_ZOS: /* These are of the form " 0) { setupfn("%s.rrd", "netstat"); return create_and_update_rrd(hostname, testname, classname, pagepaths, netstat_params, netstat_tpl); } else { return -1; } } xymon-4.3.7/xymond/rrd/do_xymonnet.c0000664000175000017500000000303011615341300017025 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char xymonnet_rcsid[] = "$Id: do_xymonnet.c 6712 2011-07-31 21:01:52Z storner $"; int do_xymonnet_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *xymonnet_params[] = { "DS:runtime:GAUGE:600:0:U", NULL }; static void *xymonnet_tpl = NULL; char *p; float runtime; if (xymonnet_tpl == NULL) xymonnet_tpl = setup_template(xymonnet_params); p = strstr(msg, "TIME TOTAL"); if (p && (sscanf(p, "TIME TOTAL %f", &runtime) == 1)) { if (strcmp("xymonnet", testname) != 0) { setupfn2("%s.%s.rrd", "xymonnet", testname); } else { setupfn("%s.rrd", "xymonnet"); } sprintf(rrdvalues, "%d:%.2f", (int) tstamp, runtime); return create_and_update_rrd(hostname, testname, classname, pagepaths, xymonnet_params, xymonnet_tpl); } return 0; } xymon-4.3.7/xymond/rrd/do_mdc.c0000664000175000017500000000367711615341300015730 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* This module handles z/VM "mdc" data messages */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* Copyright (C) 2007 Rich Smrcina */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char mdc_rcsid[] = "$Id: do_mdc.c 6585 2010-11-14 15:12:56Z storner $"; static char *mdc_params[] = { "DS:reads:GAUGE:600:0:U", "DS:writes:GAUGE:600:0:U", NULL }; static char *mdcpct_params[] = { "DS:hitpct:GAUGE:600:0:100", NULL }; static char *mdc_tpl = NULL; int do_mdc_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char *pr; char *fn = NULL; int mdcreads, mdcwrites, mdchitpct; pr=(strstr(msg, "\n")); pr++; pr=(strstr(pr, "\n")); /* There are two of them... */ if (pr) { pr++; sscanf(pr, "%d:%d:%d", &mdcreads, &mdcwrites, &mdchitpct); setupfn("mdc.rrd", fn); sprintf(rrdvalues, "%d:%d:%d", (int)tstamp, mdcreads, mdcwrites); create_and_update_rrd(hostname, testname, classname, pagepaths, mdc_params, mdc_tpl); setupfn("mdchitpct.rrd", fn); sprintf(rrdvalues, "%d:%d", (int)tstamp, mdchitpct); create_and_update_rrd(hostname, testname, classname, pagepaths, mdcpct_params, mdc_tpl); } return 0; } xymon-4.3.7/xymond/rrd/do_citrix.c0000664000175000017500000000270211615341300016453 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char citrix_rcsid[] = "$Id: do_citrix.c 6712 2011-07-31 21:01:52Z storner $"; int do_citrix_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *citrix_params[] = { "DS:users:GAUGE:600:0:U", NULL }; static void *citrix_tpl = NULL; char *p; int users; if (citrix_tpl == NULL) citrix_tpl = setup_template(citrix_params); p = strstr(msg, " users active\n"); while (p && (p > msg) && (*p != '\n')) p--; if (p && (sscanf(p+1, "\n%d users active\n", &users) == 1)) { setupfn("%s.rrd", "citrix"); sprintf(rrdvalues, "%d:%d", (int)tstamp, users); return create_and_update_rrd(hostname, testname, classname, pagepaths, citrix_params, citrix_tpl); } return 0; } xymon-4.3.7/xymond/rrd/do_paging.c0000664000175000017500000000547211615341300016425 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* This module handles "paging" messages. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* Copyright (C) 2007-2008 Rich Smrcina */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char paging_rcsid[] = "$Id: do_paging.c 6712 2011-07-31 21:01:52Z storner $"; static char *paging_params[] = { "DS:rate:GAUGE:600:0:U", NULL }; static void *paging_tpl = NULL; int do_paging_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char *pr; char *fn = NULL; int pagerate, xstore, migrate; if (paging_tpl == NULL) paging_tpl = setup_template(paging_params); pr=(strstr(msg, "Rate")); if (pr) { pr += 5; sscanf(pr, "%d per", &pagerate); setupfn("paging.pagerate.rrd", fn); sprintf(rrdvalues, "%d:%d", (int)tstamp, pagerate); create_and_update_rrd(hostname, testname, classname, pagepaths, paging_params, paging_tpl); if (strstr(msg, "z/VM")) { /* Additional handling for z/VM */ pr=strstr(msg,"XSTORE-"); if (pr) { /* Extract values if we find XSTORE in results of 'IND' command */ pr += 7; /* Add 7 to get past literal (XSTORE). */ sscanf(pr, "%d/SEC", &xstore); pr=strstr(msg,"MIGRATE-"); pr += 8; /* Add 8 to get past literal (MIGRATE). */ sscanf(pr, "%d/SEC", &migrate); setupfn("paging.xstore.rrd", fn); sprintf(rrdvalues, "%d:%d", (int)tstamp, xstore); create_and_update_rrd(hostname, testname, classname, pagepaths, paging_params, paging_tpl); setupfn("paging.migrate.rrd", fn); sprintf(rrdvalues, "%d:%d", (int)tstamp, migrate); create_and_update_rrd(hostname, testname, classname, pagepaths, paging_params, paging_tpl); } } } return 0; } xymon-4.3.7/xymond/rrd/do_cics.c0000664000175000017500000000422611615341300016075 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* This module handles "cics" messages. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* Copyright (C) 2008 Rich Smrcina */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char cics_rcsid[] = "$Id: do_cics.c 6585 2010-11-14 15:12:56Z storner $"; static char *cicsntrans_params[] = { "DS:numtrans:GAUGE:600:0:U", NULL }; static char *cicsdsa_params[] = { "DS:dsa:GAUGE:600:0:100", "DS:edsa:GAUGE:600:0:100", NULL }; static char *cics_tpl = NULL; int do_cics_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { char *pr; char *fn = NULL; int numtrans; float dsapct, edsapct; char cicsappl[9], rrdfn[20]; pr=(strstr(msg, "Appl")); if (!pr) { return 0; } pr=(strstr(pr, "\n")); if (pr) { pr += 1; pr = strtok(pr, "\n"); while (pr != NULL) { sscanf(pr, "%s %d %f %f", cicsappl, &numtrans, &dsapct, &edsapct); sprintf(rrdfn, "cics.%-s.rrd", cicsappl); setupfn(rrdfn, fn); sprintf(rrdvalues, "%d:%d", (int)tstamp, numtrans); create_and_update_rrd(hostname, testname, classname, pagepaths, cicsntrans_params, cics_tpl); sprintf(rrdfn, "dsa.%-s.rrd", cicsappl); setupfn(rrdfn, fn); sprintf(rrdvalues, "%d:%d:%d", (int)tstamp, (int)dsapct, (int)edsapct); create_and_update_rrd(hostname, testname, classname, pagepaths, cicsdsa_params, cics_tpl); pr = strtok(NULL, "\n"); } } return 0; } xymon-4.3.7/xymond/rrd/do_la.c0000664000175000017500000002014611630612042015550 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char la_rcsid[] = "$Id: do_la.c 6745 2011-09-04 06:01:06Z storner $"; int do_la_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *la_params[] = { "DS:la:GAUGE:600:0:U", NULL }; static void *la_tpl = NULL; static char *clock_params[] = { "DS:la:GAUGE:600:U:U", NULL }; /* "la" is a misnomer, but to stay compatiable with existing RRD files */ static void *clock_tpl = NULL; static pcre *as400_exp = NULL; static pcre *zVM_exp = NULL; static time_t starttime = 0; char *p, *eoln = NULL; int gotusers=0, gotprocs=0, gotload=0, gotclock=0; int users=0, procs=0, load=0, clockdiff=0; time_t now = getcurrenttime(NULL); if (la_tpl == NULL) la_tpl = setup_template(la_params); if (clock_tpl == NULL) clock_tpl = setup_template(clock_params); if (starttime == 0) starttime = now; if (strstr(msg, "bb-xsnmp")) { /* * bb-xsnmp.pl script output. * * green Tue Apr 5 12:57:37 2005 up: 254.58 days, CPU Usage= 9% * * &green CPU Time in Busy Mode: 9% * &green CPU Time in Idle Mode: 91% * * &yellow CPU Usage Threshold: 90% * &red CPU Usage Threshold: 95% * * * bb-xsnmp.pl Version: 1.78 */ p = strstr(msg, "CPU Usage="); if (p) { p += strlen("CPU Usage="); gotload = 1; load = atoi(p); } goto done_parsing; } else if (strstr(msg, "z/VM") || strstr(msg, "VSE/ESA") || strstr(msg, "z/VSE") || strstr(msg, "z/OS")) { /* z/VM cpu message. Looks like this, from Rich Smrcina (client config mode): * green 5 Apr 2005 20:07:34 CPU Utilization 7% z/VM Version 4 Release 4.0, service level 0402 (32-bit) AVGPROC-007% 01 * VSE/ESA or z/VSE cpu message. * VSE/ESA 2.7.2 cr IPLed on ... * or * z/VSE 3.1.1 cr IPLed on ... * In server (centralized) config mode or for z/OS (which is centralized config only) * the operating system name is part of the message (as in the tests above). */ int ovector[30]; char w[100]; int res; if (zVM_exp == NULL) { const char *errmsg = NULL; int errofs = 0; zVM_exp = pcre_compile(".* CPU Utilization *([0-9]+)%", PCRE_CASELESS, &errmsg, &errofs, NULL); } res = pcre_exec(zVM_exp, NULL, msg, strlen(msg), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (res >= 0) { /* We have a match - pick up the data. */ *w = '\0'; if (res > 0) pcre_copy_substring(msg, ovector, res, 1, w, sizeof(w)); if (strlen(w)) { load = atoi(w); gotload = 1; } } goto done_parsing; } eoln = strchr(msg, '\n'); if (eoln) *eoln = '\0'; p = strstr(msg, "up: "); if (!p) p = strstr(msg, "Uptime:"); /* Netapp filerstats2bb script */ if (!p) p = strstr(msg, "uptime:"); if (p) { /* First line of cpu report, contains "up: 159 days, 1 users, 169 procs, load=21" */ p = strchr(p, ','); if (p) { gotusers = (sscanf(p, ", %d users", &users) == 1); p = strchr(p+1, ','); } if (p) { gotprocs = (sscanf(p, ", %d procs", &procs) == 1); p = strchr(p+1, ','); } /* * Load can be either * - "load=xx%" (Windows) * - "load=xx.xx" (Unix, DISPREALLOADAVG=TRUE) * - "load=xx" (Unix, DISPREALLOADAVG=FALSE) * * We want the load in percent (Windows), or LA*100 (Unix). */ p = strstr(msg, "load="); if (p) { p += 5; if (strchr(p, '%')) { gotload = 1; load = atoi(p); } else if (strchr(p, '.')) { gotload = 1; load = 100*atoi(p); /* Find the decimal part, and cut off at 2 decimals */ p = strchr(p, '.'); if (strlen(p) > 3) *(p+3) = '\0'; load += atoi(p+1); } else { gotload = 1; load = atoi(p); } } } else { /* * No "uptime" in message - could be from an AS/400. They look like this: * green March 21, 2005 12:33:24 PM EST deltacdc 108 users 45525 jobs(126 batch,0 waiting for message), load=26% */ int ovector[30]; char w[100]; int res; if (as400_exp == NULL) { const char *errmsg = NULL; int errofs = 0; as400_exp = pcre_compile(".* ([0-9]+) users ([0-9]+) jobs.* load=([0-9]+)\\%", PCRE_CASELESS, &errmsg, &errofs, NULL); } res = pcre_exec(as400_exp, NULL, msg, strlen(msg), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (res >= 0) { /* We have a match - pick up the AS/400 data. */ *w = '\0'; if (res > 0) pcre_copy_substring(msg, ovector, res, 1, w, sizeof(w)); if (strlen(w)) { users = atoi(w); gotusers = 1; } *w = '\0'; if (res > 0) pcre_copy_substring(msg, ovector, res, 3, w, sizeof(w)); if (strlen(w)) { load = atoi(w); gotload = 1; } } } done_parsing: if (eoln) *eoln = '\n'; p = strstr(msg, "System clock is "); if (p) { if (sscanf(p, "System clock is %d seconds off", &clockdiff) == 1) gotclock = 1; } if (!gotload) { /* See if it's a report from the ciscocpu.pl script. */ p = strstr(msg, "
CPU 5 min average:"); if (p) { /* It reports in % cpu utilization */ p = strchr(p, ':'); load = atoi(p+1); gotload = 1; } } if (gotload) { setupfn("%s.rrd", "la"); sprintf(rrdvalues, "%d:%d", (int)tstamp, load); create_and_update_rrd(hostname, testname, classname, pagepaths, la_params, la_tpl); } if (gotprocs) { setupfn("%s.rrd", "procs"); sprintf(rrdvalues, "%d:%d", (int)tstamp, procs); create_and_update_rrd(hostname, testname, classname, pagepaths, la_params, la_tpl); } if (gotusers) { setupfn("%s.rrd", "users"); sprintf(rrdvalues, "%d:%d", (int)tstamp, users); create_and_update_rrd(hostname, testname, classname, pagepaths, la_params, la_tpl); } if (gotclock) { setupfn("%s.rrd", "clock"); sprintf(rrdvalues, "%d:%d", (int)tstamp, clockdiff); create_and_update_rrd(hostname, testname, classname, pagepaths, clock_params, clock_tpl); } /* * If we have run for less than 6 minutes, drop the memory updates here. * We want to be sure not to use memory statistics from the CPU report * if there is a memory add-on sending a separate memory statistics */ if ((now - starttime) < 360) return 0; if (!memhosts_init || (xtreeFind(memhosts, hostname) == xtreeEnd(memhosts))) { /* Pick up memory statistics */ int found, overflow, realuse, swapuse; long long phystotal, physavail, pagetotal, pageavail; found = overflow = realuse = swapuse = 0; phystotal = physavail = pagetotal = pageavail = 0; p = strstr(msg, "Total Physical memory:"); if (p) { phystotal = str2ll(strchr(p, ':') + 1, NULL); if (phystotal != LONG_MAX) found++; else overflow++; } if (found == 1) { p = strstr(msg, "Available Physical memory:"); if (p) { physavail = str2ll(strchr(p, ':') + 1, NULL); if (physavail != LONG_MAX) found++; else overflow++; } } if (found == 2) { p = strstr(msg, "Total PageFile size:"); if (p) { pagetotal = str2ll(strchr(p, ':') + 1, NULL); if (pagetotal != LONG_MAX) found++; else overflow++; } } if (found == 3) { p = strstr(msg, "Available PageFile size:"); if (p) { pageavail = str2ll(strchr(p, ':') + 1, NULL); if (pageavail != LONG_MAX) found++; else overflow++; } } if (found == 4) { phystotal = phystotal / 100; pagetotal = pagetotal / 100; realuse = 100 - (physavail / phystotal); swapuse = 100 - (pageavail / pagetotal); do_memory_rrd_update(tstamp, hostname, testname, classname, pagepaths, realuse, swapuse, -1); } else if (overflow) { errprintf("Host %s cpu report overflows in memory usage calculation\n", hostname); } } return 0; } xymon-4.3.7/xymond/rrd/do_beastat.c0000664000175000017500000003014611535424634016614 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon RRD handler module. */ /* */ /* Copyright (C) 2004-2006 Francesco Duranti */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char beastat_rcsid[] = "$Id: do_beastat.c 6648 2011-03-08 13:05:32Z storner $"; int do_beastat_jta_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *beastat_jta_params[] = { "DS:ActiveTrans:GAUGE:600:0:U", "DS:SecondsActive:DERIVE:600:0:U", "DS:TransAbandoned:DERIVE:600:0:U", "DS:TransCommitted:DERIVE:600:0:U", "DS:TransHeuristics:DERIVE:600:0:U", "DS:TransRBackApp:DERIVE:600:0:U", "DS:TransRBackResource:DERIVE:600:0:U", "DS:TransRBackSystem:DERIVE:600:0:U", "DS:TransRBackTimeout:DERIVE:600:0:U", "DS:TransRBack:DERIVE:600:0:U", "DS:TransTotCount:DERIVE:600:0:U", NULL }; static void *beastat_jta_tpl = NULL; unsigned long heapfree=0, heapsize=0; unsigned long acttrans=0, secact=0, trab=0, trcomm=0, trheur=0, totot=0; unsigned long trrbapp=0, trrbres=0, trrbsys=0, trrbto=0, trrb=0, trtot=0; dbgprintf("beastat: host %s test %s\n",hostname, testname); if (strstr(msg, "beastat.pl")) { setupfn("%s.rrd",testname); if (beastat_jta_tpl == NULL) beastat_jta_tpl = setup_template(beastat_jta_params); acttrans=get_long_data(msg,"ActiveTransactionsTotalCount"); secact=get_long_data(msg,"SecondsActiveTotalCount"); trab=get_long_data(msg,"TransactionAbandonedTotalCount"); trcomm=get_long_data(msg,"TransactionCommittedTotalCount"); trheur=get_long_data(msg,"TransactionHeuristicsTotalCount"); trrbapp=get_long_data(msg,"TransactionRolledBackAppTotalCount"); trrbres=get_long_data(msg,"TransactionRolledBackResourceTotalCount"); trrbsys=get_long_data(msg,"TransactionRolledBackSystemTotalCount"); trrbto=get_long_data(msg,"TransactionRolledBackTimeoutTotalCount"); trrb=get_long_data(msg,"TransactionRolledBackTotalCount"); trtot=get_long_data(msg,"TransactionTotalCount"); dbgprintf("beastat: host %s test %s acttrans %ld secact %ld\n", hostname, testname, acttrans, secact); dbgprintf("beastat: host %s test %s TRANS: aband %ld comm %ld heur %ld total\n", hostname, testname, trab, trcomm, trheur, trtot); dbgprintf("beastat: host %s test %s RB: app %ld res %ld sys %ld timout %ld total %ld\n", hostname, testname, trrbapp, trrbres, trrbsys, trrbto, trrb); sprintf(rrdvalues, "%d:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld", (int) tstamp, acttrans, secact, trab, trcomm, trheur, trrbapp, trrbres, trrbsys, trrbto, trrb, trtot); create_and_update_rrd(hostname, testname, classname, pagepaths, beastat_jta_params, beastat_jta_tpl); } return 0; } int do_beastat_jvm_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *beastat_jvm_params[] = { "DS:HeapFreeCurrent:GAUGE:600:0:U", "DS:HeapSizeCurrent:GAUGE:600:0:U", NULL }; static void *beastat_jvm_tpl = NULL; unsigned long heapfree=0, heapsize=0; dbgprintf("beastat: host %s test %s\n",hostname, testname); if (strstr(msg, "beastat.pl")) { setupfn("%s.rrd",testname); if (beastat_jvm_tpl == NULL) beastat_jvm_tpl = setup_template(beastat_jvm_params); heapfree=get_long_data(msg, "HeapFreeCurrent"); heapsize=get_long_data(msg,"HeapSizeCurrent"); dbgprintf("beastat: host %s test %s heapfree %ld heapsize %ld\n", hostname, testname, heapfree, heapsize); sprintf(rrdvalues, "%d:%ld:%ld", (int) tstamp, heapfree, heapsize); create_and_update_rrd(hostname, testname, classname, pagepaths, beastat_jvm_params, beastat_jvm_tpl); } return 0; } int do_beastat_jms_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *beastat_jms_params[] = { "DS:CurrConn:GAUGE:600:0:U", "DS:HighConn:GAUGE:600:0:U", "DS:TotalConn:DERIVE:600:0:U", "DS:CurrJMSSrv:GAUGE:600:0:U", "DS:HighJMSSrv:GAUGE:600:0:U", "DS:TotalJMSSrv:DERIVE:600:0:U", NULL }; static void *beastat_jms_tpl = NULL; unsigned long conncurr=0, connhigh=0, conntotal=0, jmscurr=0, jmshigh=0, jmstotal=0; dbgprintf("beastat: host %s test %s\n",hostname, testname); if (strstr(msg, "beastat.pl")) { setupfn("%s.rrd",testname); if (beastat_jms_tpl == NULL) beastat_jms_tpl = setup_template(beastat_jms_params); conncurr=get_long_data(msg, "ConnectionsCurrentCount"); connhigh=get_long_data(msg,"ConnectionsHighCount"); conntotal=get_long_data(msg,"ConnectionsTotalCount"); jmscurr=get_long_data(msg,"JMSServersCurrentCount"); jmshigh=get_long_data(msg,"JMSServersHighCount"); jmstotal=get_long_data(msg,"JMSServersTotalCount"); dbgprintf("beastat: host %s test %s conncurr %ld connhigh %ld conntotal %ld\n", hostname, testname, conncurr, connhigh, conntotal); dbgprintf("beastat: host %s test %s jmscurr %ld jmshigh %ld jmstotal %ld\n", hostname, testname, jmscurr, jmshigh,jmstotal); sprintf(rrdvalues, "%d:%ld:%ld:%ld:%ld:%ld:%ld", (int) tstamp, conncurr, connhigh, conntotal, jmscurr, jmshigh, jmstotal); create_and_update_rrd(hostname, testname, classname, pagepaths, beastat_jms_params, beastat_jms_tpl); } return 0; } int do_beastat_exec_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *beastat_exec_params[] = { "DS:ExecThrCurrIdleCnt:GAUGE:600:0:U", "DS:ExecThrTotalCnt:GAUGE:600:0:U", "DS:PendReqCurrCnt:GAUGE:600:0:U", "DS:ServReqTotalCnt:DERIVE:600:0:U", NULL }; static void *beastat_exec_tpl = NULL; static char *checktest = "Type=ExecuteQueueRuntime"; char *curline; char *eoln; dbgprintf("beastat: host %s test %s\n",hostname, testname); if (strstr(msg, "beastat.pl")) { if (beastat_exec_tpl == NULL) beastat_exec_tpl = setup_template(beastat_exec_params); /* ---- Full Status Report ---- Type=ExecuteQueueRuntime - Location=admin - Name=weblogic.kernel.System */ curline=strstr(msg, "---- Full Status Report ----"); if (curline) { eoln = strchr(curline, '\n'); curline = (eoln ? (eoln+1) : NULL); } while (curline) { unsigned long currthr=0, totthr=0,currprq=0,totservrq=0; char *start=NULL, *execname=NULL, *nameptr=NULL; if ((start = strstr(curline,checktest))==NULL) break; if ((eoln = strchr(start, '\n')) == NULL) break; *eoln = '\0'; if ((nameptr=strstr(start,"Name=")) == NULL ) { dbgprintf("do_beastat.c: No name found in host %s test %s line %s\n", hostname,testname,start); goto nextline; } execname=xstrdup(nameptr+5); *eoln = '\n'; start=eoln+1; if ((eoln = strstr(start,checktest))==NULL) eoln=strstr(start,"dbcheck.pl"); if (eoln) *(--eoln)='\0'; setupfn2("%s,%s.rrd",testname,execname); currthr=get_long_data(start, "ExecuteThreadCurrentIdleCount"); totthr=get_long_data(start,"ExecuteThreadTotalCount"); currprq=get_long_data(start,"PendingRequestCurrentCount"); totservrq=get_long_data(start,"ServicedRequestTotalCount"); dbgprintf("beastat: host %s test %s name %s currthr %ld totthr %ld currprq %ld totservrq %ld\n", hostname, testname, execname, currthr, totthr, currprq, totservrq); sprintf(rrdvalues, "%d:%ld:%ld:%ld:%ld", (int) tstamp, currthr, totthr, currprq, totservrq); create_and_update_rrd(hostname, testname, classname, pagepaths, beastat_exec_params, beastat_exec_tpl); if (execname) { xfree(execname); execname = NULL; } nextline: if (eoln) *(eoln)='\n'; curline = (eoln ? (eoln+1) : NULL); } } return 0; } int do_beastat_jdbc_rrd(char *hostname, char *testname, char *classname, char *pagepaths, char *msg, time_t tstamp) { static char *beastat_jdbc_params[] = { "DS:ActConnAvgCnt:GAUGE:600:0:U", "DS:ActConnCurrCnt:GAUGE:600:0:U", "DS:ActConnHighCnt:GAUGE:600:0:U", "DS:WtForConnCurrCnt:GAUGE:600:0:U", "DS:ConnDelayTime:GAUGE:600:0:U", "DS:ConnLeakProfileCnt:GAUGE:600:0:U", "DS:LeakedConnCnt:GAUGE:600:0:U", "DS:MaxCapacity:GAUGE:600:0:U", "DS:NumAvailable:GAUGE:600:0:U", "DS:NumUnavailable:GAUGE:600:0:U", "DS:HighNumAvailable:GAUGE:600:0:U", "DS:HighNumUnavailable:GAUGE:600:0:U", "DS:WaitSecHighCnt:GAUGE:600:0:U", "DS:ConnTotalCnt:DERIVE:600:0:U", "DS:FailToReconnCnt:DERIVE:600:0:U", "DS:WaitForConnHighCnt:GAUGE:600:0:U", NULL }; static void *beastat_jdbc_tpl = NULL; static char *checktest = "Type=JDBCConnectionPoolRuntime"; char *curline; char *eoln; dbgprintf("beastat: host %s test %s\n",hostname, testname); if (strstr(msg, "beastat.pl")) { if (beastat_jdbc_tpl == NULL) beastat_jdbc_tpl = setup_template(beastat_jdbc_params); /* ---- Full Status Report ---- Type=ExecuteQueueRuntime - Location=admin - Name=weblogic.kernel.System */ curline=strstr(msg, "---- Full Status Report ----"); if (curline) { eoln = strchr(curline, '\n'); curline = (eoln ? (eoln+1) : NULL); } while (curline) { unsigned long acac=0, accc=0, achc=0, wfccc=0, cdt=0, clpc=0, lcc=0; unsigned long mc=0, na=0, nu=0, hna=0, hnu=0, wshc=0, ctc=0, ftrc=0, wfchc=0; char *start=NULL, *execname=NULL, *nameptr=NULL; if ((start = strstr(curline,checktest))==NULL) break; if ((eoln = strchr(start, '\n')) == NULL) break; *eoln = '\0'; if ((nameptr=strstr(start,"Name=")) == NULL ) { dbgprintf("do_beastat.c: No name found in host %s test %s line %s\n", hostname,testname,start); goto nextline; } execname=xstrdup(nameptr+5); *eoln = '\n'; start=eoln+1; if ((eoln = strstr(start,checktest))==NULL) eoln=strstr(start,"dbcheck.pl"); if (eoln) *(--eoln)='\0'; setupfn2("%s,%s.rrd",testname,execname); acac=get_long_data(start,"ActiveConnectionsAverageCount"); accc=get_long_data(start,"ActiveConnectionsCurrentCount"); achc=get_long_data(start,"ActiveConnectionsHighCount"); wfccc=get_long_data(start,"WaitingForConnectionCurrentCount"); cdt=get_long_data(start,"ConnectionDelayTime"); clpc=get_long_data(start,"ConnectionLeakProfileCount"); lcc=get_long_data(start,"LeakedConnectionCount"); mc=get_long_data(start,"MaxCapacity"); na=get_long_data(start,"NumAvailable"); nu=get_long_data(start,"NumUnavailable"); hna=get_long_data(start,"HighestNumAvailable"); hnu=get_long_data(start,"HighestNumUnavailable"); wshc=get_long_data(start,"WaitSecondsHighCount"); ctc=get_long_data(start,"ConnectionsTotalCount"); ftrc=get_long_data(start,"FailuresToReconnectCount"); wfchc=get_long_data(start,"WaitingForConnectionHighCount"); dbgprintf("beastat: host %s test %s name %s acac %ld accc %ld achc %ld wfccc %ld cdt %ld clpc %ld lcc %ld\n", hostname, testname, execname, acac, accc, achc, wfccc, cdt, clpc, lcc); dbgprintf("beastat: host %s test %s name %s mc %ld na %ld nu %ld hna %ld hnu %ld wshc %ld ctc %ld ftrc %ld wfchc %ld\n",hostname, testname, execname, mc, na, nu, hna, hnu, wshc, ctc, ftrc, wfchc); sprintf(rrdvalues, "%d:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld", (int) tstamp, acac, accc, achc, wfccc, cdt, clpc, lcc, mc, na, nu, hna, hnu, wshc, ctc, ftrc, wfchc); create_and_update_rrd(hostname, testname, classname, pagepaths, beastat_jdbc_params, beastat_jdbc_tpl); if (execname) { xfree(execname); execname = NULL; } nextline: if (eoln) *(eoln)='\n'; curline = (eoln ? (eoln+1) : NULL); } } return 0; } xymon-4.3.7/xymond/xymond.c0000664000175000017500000046606011666354564015257 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* This is the master daemon, xymond. */ /* */ /* This is a daemon that implements the Big Brother network protocol, with */ /* additional protocol items implemented for Xymon. */ /* */ /* This daemon maintains the full state of the Xymon system in memory, */ /* eliminating the need for file-based storage of e.g. status logs. The web */ /* frontend programs (xymongen, combostatus, hostsvc.cgi etc) can retrieve */ /* current statuslogs from this daemon to build the Xymon webpages. However, */ /* a "plugin" mechanism is also implemented to allow "worker modules" to */ /* pickup various types of events that occur in the system. This allows */ /* such modules to e.g. maintain the standard Xymon file-based storage, or */ /* implement history logging or RRD database updates. This plugin mechanism */ /* uses System V IPC mechanisms for a high-performance/low-latency communi- */ /* cation between xymond and the worker modules - under no circumstances */ /* should the daemon be tasked with storing data to a low-bandwidth channel. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond.c 6787 2011-12-03 08:10:28Z storner $"; #include #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include /* Someday I'll move to GNU Autoconf for this ... */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymond_buffer.h" #include "xymond_ipc.h" #define DISABLED_UNTIL_OK -1 /* * The absolute maximum size we'll grow our buffers to accomodate an incoming message. * This is really just an upper bound to squash the bad guys trying to data-flood us. */ #define MAX_XYMON_INBUFSZ (10*1024*1024) /* 10 MB */ /* The initial size of an input buffer. Make this large enough for most traffic. */ #define XYMON_INBUF_INITIAL (128*1024) /* How much the input buffer grows per re-allocation */ #define XYMON_INBUF_INCREMENT (32*1024) /* How long to keep an ack after the status has recovered */ #define ACKCLEARDELAY 720 /* 12 minutes */ /* How long are sub-client messages valid */ #define MAX_SUBCLIENT_LIFETIME 960 /* 15 minutes + a bit */ #define DEFAULT_FLAPCOUNT 5 int flapcount = DEFAULT_FLAPCOUNT; int flapthreshold = (DEFAULT_FLAPCOUNT+1)*5*60; /* Seconds - if more than flapcount changes during this period, it's flapping */ htnames_t *metanames = NULL; typedef struct xymond_meta_t { htnames_t *metaname; char *value; struct xymond_meta_t *next; } xymond_meta_t; typedef struct ackinfo_t { int level; time_t received, validuntil, cleartime; char *ackedby; char *msg; struct ackinfo_t *next; } ackinfo_t; typedef struct testinfo_t { char *name; int clientsave; } testinfo_t; typedef struct modifier_t { char *source, *cause; int color, valid; struct modifier_t *next; } modifier_t; /* This holds all information about a single status */ typedef struct xymond_log_t { struct xymond_hostlist_t *host; testinfo_t *test; char *origin; int color, oldcolor, activealert, histsynced, downtimeactive, flapping, oldflapcolor, currflapcolor; char *testflags; char *grouplist; /* For extended status reports (e.g. from xymond_client) */ char sender[IP_ADDR_STRLEN]; time_t *lastchange; /* Table of times when the currently logged status began */ time_t logtime; /* time when last update was received */ time_t validtime; /* time when status is no longer valid */ time_t enabletime; /* time when test auto-enables after a disable */ time_t acktime; /* time when test acknowledgement expires */ time_t redstart, yellowstart; unsigned char *message; int msgsz; unsigned char *dismsg, *ackmsg; char *cookie; time_t cookieexpires; struct xymond_meta_t *metas; struct modifier_t *modifiers; ackinfo_t *acklist; /* Holds list of acks */ unsigned long statuschangecount; struct xymond_log_t *next; } xymond_log_t; typedef struct clientmsg_list_t { char *collectorid; time_t timestamp; char *msg; struct clientmsg_list_t *next; } clientmsg_list_t; /* This is a list of the hosts we have seen reports for, and links to their status logs */ typedef struct xymond_hostlist_t { char *hostname; char ip[IP_ADDR_STRLEN]; enum { H_NORMAL, H_SUMMARY } hosttype; xymond_log_t *logs; xymond_log_t *pinglog; /* Points to entry in logs list, but we need it often */ clientmsg_list_t *clientmsgs; time_t clientmsgtstamp; } xymond_hostlist_t; typedef struct filecache_t { char *fn; long len; unsigned char *fdata; } filecache_t; void *rbhosts; /* The hosts we have reports from */ void *rbtests; /* The tests (columns) we have seen */ void *rborigins; /* The origins we have seen */ void *rbcookies; /* The cookies we use */ void *rbfilecache; sender_t *maintsenders = NULL; sender_t *statussenders = NULL; sender_t *adminsenders = NULL; sender_t *wwwsenders = NULL; sender_t *tracelist = NULL; int traceall = 0; int ignoretraced = 0; int clientsavemem = 1; /* In memory */ int clientsavedisk = 0; /* On disk via the CLICHG channel */ int allow_downloads = 1; int defaultvalidity = 30; /* Minutes */ #define NOTALK 0 #define RECEIVING 1 #define RESPONDING 2 /* This struct describes an active connection with a Xymon client */ typedef struct conn_t { int sock; /* Communications socket */ struct sockaddr_in addr; /* Client source address */ unsigned char *buf, *bufp; /* Message buffer and pointer */ size_t buflen, bufsz; /* Active and maximum length of buffer */ int doingwhat; /* Communications state (NOTALK, READING, RESPONDING) */ time_t timeout; /* When the timeout for this connection happens */ struct conn_t *next; } conn_t; enum droprencmd_t { CMD_DROPHOST, CMD_DROPTEST, CMD_RENAMEHOST, CMD_RENAMETEST, CMD_DROPSTATE }; static volatile int running = 1; static volatile int reloadconfig = 0; static volatile time_t nextcheckpoint = 0; static volatile int dologswitch = 0; static volatile int gotalarm = 0; /* Our channels to worker modules */ xymond_channel_t *statuschn = NULL; /* Receives full "status" messages */ xymond_channel_t *stachgchn = NULL; /* Receives brief message about a status change */ xymond_channel_t *pagechn = NULL; /* Receives alert messages (triggered from status changes) */ xymond_channel_t *datachn = NULL; /* Receives raw "data" messages */ xymond_channel_t *noteschn = NULL; /* Receives raw "notes" messages */ xymond_channel_t *enadischn = NULL; /* Receives "enable" and "disable" messages */ xymond_channel_t *clientchn = NULL; /* Receives "client" messages */ xymond_channel_t *clichgchn = NULL; /* Receives "clichg" messages */ xymond_channel_t *userchn = NULL; /* Receives "usermsg" messages */ #define NO_COLOR (COL_COUNT) static char *colnames[COL_COUNT+1]; int alertcolors, okcolors; enum alertstate_t { A_OK, A_ALERT, A_UNDECIDED }; typedef struct ghostlist_t { char *name; char *sender; time_t tstamp, matchtime; } ghostlist_t; void *rbghosts; typedef struct multisrclist_t { char *id; char *senders[2]; time_t tstamp; } multisrclist_t; void *rbmultisrc; enum ghosthandling_t ghosthandling = GH_LOG; char *checkpointfn = NULL; FILE *dbgfd = NULL; char *dbghost = NULL; time_t boottimer = 0; int hostcount = 0; char *ackinfologfn = NULL; FILE *ackinfologfd = NULL; char *defaultreddelay = NULL, *defaultyellowdelay = NULL; typedef struct xymond_statistics_t { char *cmd; unsigned long count; } xymond_statistics_t; xymond_statistics_t xymond_stats[] = { { "status", 0 }, { "combo", 0 }, { "page", 0 }, { "summary", 0 }, { "data", 0 }, { "client", 0 }, { "notes", 0 }, { "enable", 0 }, { "disable", 0 }, { "ack", 0 }, { "config", 0 }, { "query", 0 }, { "xymondboard", 0 }, { "xymondlog", 0 }, { "drop", 0 }, { "rename", 0 }, { "dummy", 0 }, { "ping", 0 }, { "notify", 0 }, { "schedule", 0 }, { "download", 0 }, { NULL, 0 } }; enum boardfield_t { F_NONE, F_HOSTNAME, F_TESTNAME, F_COLOR, F_FLAGS, F_LASTCHANGE, F_LOGTIME, F_VALIDTIME, F_ACKTIME, F_DISABLETIME, F_SENDER, F_COOKIE, F_LINE1, F_ACKMSG, F_DISMSG, F_MSG, F_CLIENT, F_CLIENTTSTAMP, F_ACKLIST, F_HOSTINFO, F_FLAPINFO, F_STATS, F_MODIFIERS, F_LAST }; typedef struct boardfieldnames_t { char *name; enum boardfield_t id; } boardfieldnames_t; boardfieldnames_t boardfieldnames[] = { { "hostname", F_HOSTNAME }, { "testname", F_TESTNAME }, { "color", F_COLOR }, { "flags", F_FLAGS }, { "lastchange", F_LASTCHANGE }, { "logtime", F_LOGTIME }, { "validtime", F_VALIDTIME }, { "acktime", F_ACKTIME }, { "disabletime", F_DISABLETIME }, { "sender", F_SENDER }, { "cookie", F_COOKIE }, { "line1", F_LINE1 }, { "ackmsg", F_ACKMSG }, { "dismsg", F_DISMSG }, { "msg", F_MSG }, { "client", F_CLIENT }, { "clntstamp", F_CLIENTTSTAMP }, { "acklist", F_ACKLIST }, { "XMH_", F_HOSTINFO }, { "flapinfo", F_FLAPINFO }, { "stats", F_STATS }, { "modifiers", F_MODIFIERS }, { NULL, F_LAST }, }; typedef struct boardfields_t { enum boardfield_t field; enum xmh_item_t xmhfield; } boardfield_t; #define BOARDFIELDS_MAX 50 boardfield_t boardfields[BOARDFIELDS_MAX+1]; /* Statistics counters */ unsigned long msgs_total = 0; unsigned long msgs_total_last = 0; time_t last_stats_time = 0; /* List of scheduled (future) tasks */ typedef struct scheduletask_t { int id; time_t executiontime; char *command; char *sender; struct scheduletask_t *next; } scheduletask_t; scheduletask_t *schedulehead = NULL; int nextschedid = 1; void update_statistics(char *cmd) { int i; dbgprintf("-> update_statistics\n"); if (!cmd) { dbgprintf("No command for update_statistics\n"); return; } msgs_total++; i = 0; while (xymond_stats[i].cmd && strncmp(xymond_stats[i].cmd, cmd, strlen(xymond_stats[i].cmd))) { i++; } xymond_stats[i].count++; dbgprintf("<- update_statistics\n"); } char *generate_stats(void) { static strbuffer_t *statsbuf = NULL; time_t now = getcurrenttime(NULL); time_t nowtimer = gettimer(); int i, clients; char bootuptxt[40]; char uptimetxt[40]; xtreePos_t ghandle; time_t uptime = (nowtimer - boottimer); time_t boottstamp = (now - uptime); char msgline[2048]; dbgprintf("-> generate_stats\n"); MEMDEFINE(bootuptxt); MEMDEFINE(uptimetxt); if (statsbuf == NULL) { statsbuf = newstrbuffer(8192); } else { clearstrbuffer(statsbuf); } strftime(bootuptxt, sizeof(bootuptxt), "%d-%b-%Y %T", localtime(&boottstamp)); sprintf(uptimetxt, "%d days, %02d:%02d:%02d", (int)(uptime / 86400), (int)(uptime % 86400)/3600, (int)(uptime % 3600)/60, (int)(uptime % 60)); sprintf(msgline, "status %s.xymond %s\nStatistics for Xymon daemon\nVersion: %s\nUp since %s (%s)\n\n", xgetenv("MACHINE"), colorname(errbuf ? COL_YELLOW : COL_GREEN), VERSION, bootuptxt, uptimetxt); addtobuffer(statsbuf, msgline); sprintf(msgline, "Incoming messages : %10ld\n", msgs_total); addtobuffer(statsbuf, msgline); i = 0; while (xymond_stats[i].cmd) { sprintf(msgline, "- %-20s : %10ld\n", xymond_stats[i].cmd, xymond_stats[i].count); addtobuffer(statsbuf, msgline); i++; } sprintf(msgline, "- %-20s : %10ld\n", "Bogus/Timeouts ", xymond_stats[i].count); addtobuffer(statsbuf, msgline); if ((now > last_stats_time) && (last_stats_time > 0)) { sprintf(msgline, "Incoming messages/sec : %10ld (average last %d seconds)\n", ((msgs_total - msgs_total_last) / (now - last_stats_time)), (int)(now - last_stats_time)); addtobuffer(statsbuf, msgline); } msgs_total_last = msgs_total; addtobuffer(statsbuf, "\n"); clients = semctl(statuschn->semid, CLIENTCOUNT, GETVAL); sprintf(msgline, "status channel messages: %10ld (%d readers)\n", statuschn->msgcount, clients); addtobuffer(statsbuf, msgline); clients = semctl(stachgchn->semid, CLIENTCOUNT, GETVAL); sprintf(msgline, "stachg channel messages: %10ld (%d readers)\n", stachgchn->msgcount, clients); addtobuffer(statsbuf, msgline); clients = semctl(pagechn->semid, CLIENTCOUNT, GETVAL); sprintf(msgline, "page channel messages: %10ld (%d readers)\n", pagechn->msgcount, clients); addtobuffer(statsbuf, msgline); clients = semctl(datachn->semid, CLIENTCOUNT, GETVAL); sprintf(msgline, "data channel messages: %10ld (%d readers)\n", datachn->msgcount, clients); addtobuffer(statsbuf, msgline); clients = semctl(noteschn->semid, CLIENTCOUNT, GETVAL); sprintf(msgline, "notes channel messages: %10ld (%d readers)\n", noteschn->msgcount, clients); addtobuffer(statsbuf, msgline); clients = semctl(enadischn->semid, CLIENTCOUNT, GETVAL); sprintf(msgline, "enadis channel messages: %10ld (%d readers)\n", enadischn->msgcount, clients); addtobuffer(statsbuf, msgline); clients = semctl(clientchn->semid, CLIENTCOUNT, GETVAL); sprintf(msgline, "client channel messages: %10ld (%d readers)\n", clientchn->msgcount, clients); addtobuffer(statsbuf, msgline); clients = semctl(clichgchn->semid, CLIENTCOUNT, GETVAL); sprintf(msgline, "clichg channel messages: %10ld (%d readers)\n", clichgchn->msgcount, clients); addtobuffer(statsbuf, msgline); ghandle = xtreeFirst(rbghosts); if (ghandle != xtreeEnd(rbghosts)) addtobuffer(statsbuf, "\n\nGhost reports:\n"); for (; (ghandle != xtreeEnd(rbghosts)); ghandle = xtreeNext(rbghosts, ghandle)) { ghostlist_t *gwalk = (ghostlist_t *)xtreeData(rbghosts, ghandle); /* Skip records older than 10 minutes */ if (gwalk->tstamp < (nowtimer - 600)) continue; sprintf(msgline, " %-15s reported host %s\n", gwalk->sender, gwalk->name); addtobuffer(statsbuf, msgline); } ghandle = xtreeFirst(rbmultisrc); if (ghandle != xtreeEnd(rbmultisrc)) addtobuffer(statsbuf, "\n\nMulti-source statuses\n"); for (; (ghandle != xtreeEnd(rbmultisrc)); ghandle = xtreeNext(rbmultisrc, ghandle)) { multisrclist_t *mwalk = (multisrclist_t *)xtreeData(rbmultisrc, ghandle); /* Skip records older than 10 minutes */ if (mwalk->tstamp < (nowtimer - 600)) continue; sprintf(msgline, " %-25s reported by %s and %s\n", mwalk->id, mwalk->senders[0], mwalk->senders[1]); addtobuffer(statsbuf, msgline); } if (errbuf) { addtobuffer(statsbuf, "\n\nLatest error messages:\n"); addtobuffer(statsbuf, errbuf); addtobuffer(statsbuf, "\n"); } MEMUNDEFINE(bootuptxt); MEMUNDEFINE(uptimetxt); dbgprintf("<- generate_stats\n"); return STRBUF(statsbuf); } char *totalclientmsg(clientmsg_list_t *msglist) { static strbuffer_t *result = NULL; clientmsg_list_t *mwalk; time_t nowtimer = gettimer(); if (!result) result = newstrbuffer(10240); clearstrbuffer(result); for (mwalk = msglist; (mwalk); mwalk = mwalk->next) { if ((mwalk->timestamp + MAX_SUBCLIENT_LIFETIME) < nowtimer) continue; /* Expired data */ addtobuffer(result, "\n[collector:"); addtobuffer(result, mwalk->collectorid); addtobuffer(result, "]\n"); addtobuffer(result, mwalk->msg); } return STRBUF(result); } enum alertstate_t decide_alertstate(int color) { if ((okcolors & (1 << color)) != 0) return A_OK; else if ((alertcolors & (1 << color)) != 0) return A_ALERT; else return A_UNDECIDED; } xymond_hostlist_t *create_hostlist_t(char *hostname, char *ip) { xymond_hostlist_t *hitem; hitem = (xymond_hostlist_t *) calloc(1, sizeof(xymond_hostlist_t)); hitem->hostname = strdup(hostname); strcpy(hitem->ip, ip); if (strcmp(hostname, "summary") == 0) hitem->hosttype = H_SUMMARY; else hitem->hosttype = H_NORMAL; xtreeAdd(rbhosts, hitem->hostname, hitem); return hitem; } testinfo_t *create_testinfo(char *name) { testinfo_t *newrec; newrec = (testinfo_t *)calloc(1, sizeof(testinfo_t)); newrec->name = strdup(name); newrec->clientsave = clientsavedisk; xtreeAdd(rbtests, newrec->name, newrec); return newrec; } void posttochannel(xymond_channel_t *channel, char *channelmarker, char *msg, char *sender, char *hostname, xymond_log_t *log, char *readymsg) { struct sembuf s; int clients; int n; struct timeval tstamp; struct timezone tz; int semerr = 0; unsigned int bufsz = 1024*shbufsz(channel->channelid); void *hi; char *pagepath, *classname, *osname; time_t timeroffset = (getcurrenttime(NULL) - gettimer()); dbgprintf("-> posttochannel\n"); /* First see how many users are on this channel */ clients = semctl(channel->semid, CLIENTCOUNT, GETVAL); if (clients == 0) { dbgprintf("Dropping message - no readers\n"); return; } /* * Wait for BOARDBUSY to go low. * We need a loop here, because if we catch a signal * while waiting on the semaphore, then we need to * re-start the semaphore wait. Otherwise we may * end up with semaphores that are out of sync * (GOCLIENT goes up while a worker waits for it * to go to 0). */ gotalarm = 0; alarm(5); do { s.sem_num = BOARDBUSY; s.sem_op = 0; s.sem_flg = 0; n = semop(channel->semid, &s, 1); if (n == -1) { semerr = errno; if (semerr != EINTR) errprintf("semop failed, %s\n", strerror(errno)); } } while ((n == -1) && (semerr == EINTR) && running && !gotalarm); alarm(0); if (!running) return; /* Check if the alarm fired */ if (gotalarm) { errprintf("BOARDBUSY locked at %d, GETNCNT is %d, GETPID is %d, %d clients\n", semctl(channel->semid, BOARDBUSY, GETVAL), semctl(channel->semid, BOARDBUSY, GETNCNT), semctl(channel->semid, BOARDBUSY, GETPID), semctl(channel->semid, CLIENTCOUNT, GETVAL)); return; } /* Check if we failed to grab the semaphore */ if (n == -1) { errprintf("Dropping message due to semaphore error\n"); return; } /* All clear, post the message */ if (channel->seq == 999999) channel->seq = 0; channel->seq++; channel->msgcount++; gettimeofday(&tstamp, &tz); if (readymsg) { n = snprintf(channel->channelbuf, (bufsz-5), "@@%s#%u/%s|%d.%06d|%s|%s", channelmarker, channel->seq, (hostname ? hostname : "*"), (int) tstamp.tv_sec, (int) tstamp.tv_usec, sender, readymsg); if (n > (bufsz-5)) { char *p, *overmsg = readymsg; *(overmsg+100) = '\0'; p = strchr(overmsg, '\n'); if (p) *p = '\0'; errprintf("Oversize data/client msg from %s truncated (n=%d, limit %d)\nFirst line: %s\n", sender, n, bufsz, overmsg); } *(channel->channelbuf + bufsz - 5) = '\0'; } else { switch(channel->channelid) { case C_STATUS: hi = hostinfo(hostname); pagepath = (hi ? xmh_item(hi, XMH_ALLPAGEPATHS) : ""); classname = (hi ? xmh_item(hi, XMH_CLASS) : ""); if (!classname) classname = ""; n = snprintf(channel->channelbuf, (bufsz-5), "@@%s#%u/%s|%d.%06d|%s|%s|%s|%s|%d|%s|%s|%s|%d", channelmarker, channel->seq, hostname, /* 0 */ (int) tstamp.tv_sec, (int) tstamp.tv_usec, /* 1 */ sender, /* 2 */ log->origin, /* 3 */ hostname, /* 4 */ log->test->name, /* 5 */ (int) log->validtime, /* 6 */ colnames[log->color], /* 7 */ (log->testflags ? log->testflags : ""), /* 8 */ colnames[log->oldcolor], /* 9 */ (int) log->lastchange[0]); /* 10 */ if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d|%s", /* 11+12 */ (int)log->acktime, nlencode(log->ackmsg)); } if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d|%s", /* 13+14 */ (int)log->enabletime, nlencode(log->dismsg)); } if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d", /* 15 */ (int)(log->host->clientmsgtstamp + timeroffset)); } if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%s", classname); /* 16 */ } if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%s", pagepath); /* 17 */ } if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d", /* 18 */ (int)log->flapping); } if (n < (bufsz-5)) { modifier_t *mwalk; n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|"); mwalk = log->modifiers; /* 19 */ while ((n < (bufsz-5)) && mwalk) { if (mwalk->valid > 0) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "%s", nlencode(mwalk->cause)); } mwalk = mwalk->next; } } if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "\n%s", msg); } if (n > (bufsz-5)) { errprintf("Oversize status msg from %s for %s:%s truncated (n=%d, limit=%d)\n", sender, hostname, log->test->name, n, bufsz); } *(channel->channelbuf + bufsz - 5) = '\0'; break; case C_STACHG: n = snprintf(channel->channelbuf, (bufsz-5), "@@%s#%u/%s|%d.%06d|%s|%s|%s|%s|%d|%s|%s|%d", channelmarker, channel->seq, hostname, /* 0 */ (int) tstamp.tv_sec, (int) tstamp.tv_usec, /* 1 */ sender, /* 2 */ log->origin, /* 3 */ hostname, /* 4 */ log->test->name, /* 5 */ (int) log->validtime, /* 6 */ colnames[log->color], /* 7 */ colnames[log->oldcolor], /* 8 */ (int) log->lastchange[0]) /* 9 */; if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d|%s", /* 10+11 */ (int)log->enabletime, nlencode(log->dismsg)); } if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d", /* 12 */ log->downtimeactive); } if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d", /* 13 */ (int) (log->host->clientmsgtstamp + timeroffset)); } if (n < (bufsz-5)) { modifier_t *mwalk; n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|"); mwalk = log->modifiers; /* 14 */ while ((n < (bufsz-5)) && mwalk) { if (mwalk->valid > 0) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "%s", nlencode(mwalk->cause)); } mwalk = mwalk->next; } } if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "\n%s", msg); } if (n > (bufsz-5)) { errprintf("Oversize stachg msg from %s for %s:%s truncated (n=%d, limit=%d)\n", sender, hostname, log->test->name, n, bufsz); } *(channel->channelbuf + bufsz - 5) = '\0'; break; case C_CLICHG: n = snprintf(channel->channelbuf, (bufsz-5), "@@%s#%u/%s|%d.%06d|%s|%s|%d\n%s", channelmarker, channel->seq, hostname, (int) tstamp.tv_sec, (int) tstamp.tv_usec, sender, hostname, (int) (log->host->clientmsgtstamp + timeroffset), totalclientmsg(log->host->clientmsgs)); if (n > (bufsz-5)) { errprintf("Oversize clichg msg from %s for %s truncated (n=%d, limit=%d)\n", sender, hostname, n, bufsz); } *(channel->channelbuf + bufsz - 5) = '\0'; break; case C_PAGE: if (strcmp(channelmarker, "ack") == 0) { n = snprintf(channel->channelbuf, (bufsz-5), "@@%s#%u/%s|%d.%06d|%s|%s|%s|%s|%d\n%s", channelmarker, channel->seq, hostname, (int) tstamp.tv_sec, (int) tstamp.tv_usec, sender, hostname, log->test->name, log->host->ip, (int) log->acktime, msg); } else { hi = hostinfo(hostname); pagepath = (hi ? xmh_item(hi, XMH_ALLPAGEPATHS) : ""); classname = (hi ? xmh_item(hi, XMH_CLASS) : ""); osname = (hi ? xmh_item(hi, XMH_OS) : ""); if (!classname) classname = ""; if (!osname) osname = ""; n = snprintf(channel->channelbuf, (bufsz-5), "@@%s#%u/%s|%d.%06d|%s|%s|%s|%s|%d|%s|%s|%d|%s|%s|%s|%s|%s", channelmarker, channel->seq, hostname, (int) tstamp.tv_sec, (int) tstamp.tv_usec, sender, hostname, log->test->name, log->host->ip, (int) log->validtime, colnames[log->color], colnames[log->oldcolor], (int) log->lastchange[0], pagepath, (log->cookie ? log->cookie : ""), osname, classname, (log->grouplist ? log->grouplist : "")); if (n < (bufsz-5)) { modifier_t *mwalk; n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|"); mwalk = log->modifiers; while ((n < (bufsz-5)) && mwalk) { if (mwalk->valid > 0) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "%s", nlencode(mwalk->cause)); } mwalk = mwalk->next; } } if (n < (bufsz-5)) { n += snprintf(channel->channelbuf+n, (bufsz-n-5), "\n%s", msg); } } if (n > (bufsz-5)) { errprintf("Oversize page/ack/notify msg from %s for %s:%s truncated (n=%d, limit=%d)\n", sender, hostname, (log->test->name ? log->test->name : ""), n, bufsz); } *(channel->channelbuf + bufsz - 5) = '\0'; break; case C_DATA: case C_CLIENT: /* Data channel messages are pre-formatted so we never go here */ break; case C_NOTES: case C_USER: n = snprintf(channel->channelbuf, (bufsz-5), "@@%s#%u/%s|%d.%06d|%s|%s\n%s", channelmarker, channel->seq, hostname, (int) tstamp.tv_sec, (int) tstamp.tv_usec, sender, hostname, msg); if (n > (bufsz-5)) { char *source; errprintf("Oversize %s msg from %s for %s truncated (n=%d, limit=%d)\n", ((channel->channelid == C_NOTES) ? "notes" : "user"), sender, hostname, n, bufsz); } *(channel->channelbuf + bufsz - 5) = '\0'; break; case C_ENADIS: { char *dism = ""; if (log->dismsg) dism = nlencode(log->dismsg); n = snprintf(channel->channelbuf, (bufsz-5), "@@%s#%u/%s|%d.%06d|%s|%s|%s|%d|%s", channelmarker, channel->seq, hostname, (int) tstamp.tv_sec, (int)tstamp.tv_usec, sender, hostname, log->test->name, (int) log->enabletime, dism); if (n > (bufsz-5)) { errprintf("Oversize enadis msg from %s for %s:%s truncated (n=%d, limit=%d)\n", sender, hostname, log->test->name, n, bufsz); } } *(channel->channelbuf + bufsz - 5) = '\0'; break; case C_LAST: break; } } /* Terminate the message */ strncat(channel->channelbuf, "\n@@\n", (bufsz-1)); /* Let the readers know it is there. */ clients = semctl(channel->semid, CLIENTCOUNT, GETVAL); /* Get it again, maybe changed since last check */ dbgprintf("Posting message %u to %d readers\n", channel->seq, clients); /* Up BOARDBUSY */ s.sem_num = BOARDBUSY; s.sem_op = (clients - semctl(channel->semid, BOARDBUSY, GETVAL)); if (s.sem_op <= 0) { errprintf("How did this happen? clients=%d, s.sem_op=%d\n", clients, s.sem_op); s.sem_op = clients; } s.sem_flg = 0; n = semop(channel->semid, &s, 1); /* Make sure GOCLIENT is 0 */ n = semctl(channel->semid, GOCLIENT, GETVAL); if (n > 0) { errprintf("Oops ... GOCLIENT is high (%d)\n", n); } s.sem_num = GOCLIENT; s.sem_op = clients; s.sem_flg = 0; /* Up GOCLIENT */ n = semop(channel->semid, &s, 1); dbgprintf("<- posttochannel\n"); return; } void posttoall(char *msg) { posttochannel(statuschn, msg, NULL, "xymond", NULL, NULL, ""); posttochannel(stachgchn, msg, NULL, "xymond", NULL, NULL, ""); posttochannel(pagechn, msg, NULL, "xymond", NULL, NULL, ""); posttochannel(datachn, msg, NULL, "xymond", NULL, NULL, ""); posttochannel(noteschn, msg, NULL, "xymond", NULL, NULL, ""); posttochannel(enadischn, msg, NULL, "xymond", NULL, NULL, ""); posttochannel(clientchn, msg, NULL, "xymond", NULL, NULL, ""); posttochannel(clichgchn, msg, NULL, "xymond", NULL, NULL, ""); posttochannel(userchn, msg, NULL, "xymond", NULL, NULL, ""); } char *log_ghost(char *hostname, char *sender, char *msg) { xtreePos_t ghandle; ghostlist_t *gwalk; char *result = NULL; time_t nowtimer = gettimer(); dbgprintf("-> log_ghost\n"); /* If debugging, log the full request */ if (dbgfd) { fprintf(dbgfd, "\n---- combo message from %s ----\n%s---- end message ----\n", sender, msg); fflush(dbgfd); } if ((hostname == NULL) || (sender == NULL)) return NULL; ghandle = xtreeFind(rbghosts, hostname); gwalk = (ghandle != xtreeEnd(rbghosts)) ? (ghostlist_t *)xtreeData(rbghosts, ghandle) : NULL; if ((gwalk == NULL) || ((gwalk->matchtime + 600) < nowtimer)) { int found = 0; if (ghosthandling == GH_MATCH) { /* See if we can find this host just by ignoring domains */ char *hostnodom, *p; void *hrec; hostnodom = strdup(hostname); p = strchr(hostnodom, '.'); if (p) *p = '\0'; for (hrec = first_host(); (hrec && !found); hrec = next_host(hrec, 0)) { char *candname; candname = xmh_item(hrec, XMH_HOSTNAME); p = strchr(candname, '.'); if (p) *p = '\0'; found = (strcasecmp(hostnodom, candname) == 0); if (p) *p = '.'; if (found) { result = candname; xmh_set_item(hrec, XMH_CLIENTALIAS, hostname); errprintf("Matched ghost '%s' to host '%s'\n", hostname, result); } } } if (!found) { if (gwalk == NULL) { gwalk = (ghostlist_t *)calloc(1, sizeof(ghostlist_t)); gwalk->name = strdup(hostname); gwalk->sender = strdup(sender); gwalk->tstamp = gwalk->matchtime = nowtimer; xtreeAdd(rbghosts, gwalk->name, gwalk); } else { if (gwalk->sender) xfree(gwalk->sender); gwalk->sender = strdup(sender); gwalk->tstamp = gwalk->matchtime = nowtimer; } } } else { if (gwalk->sender) xfree(gwalk->sender); gwalk->sender = strdup(sender); gwalk->tstamp = nowtimer; } dbgprintf("<- log_ghost\n"); return result; } void log_multisrc(xymond_log_t *log, char *newsender) { xtreePos_t ghandle; multisrclist_t *gwalk; char id[1024]; dbgprintf("-> log_multisrc\n"); snprintf(id, sizeof(id), "%s:%s", log->host->hostname, log->test->name); ghandle = xtreeFind(rbmultisrc, id); if (ghandle == xtreeEnd(rbmultisrc)) { gwalk = (multisrclist_t *)calloc(1, sizeof(multisrclist_t)); gwalk->id = strdup(id); gwalk->senders[0] = strdup(log->sender); gwalk->senders[1] = strdup(newsender); gwalk->tstamp = gettimer(); xtreeAdd(rbmultisrc, gwalk->id, gwalk); } else { gwalk = (multisrclist_t *)xtreeData(rbmultisrc, ghandle); xfree(gwalk->senders[0]); gwalk->senders[0] = strdup(log->sender); xfree(gwalk->senders[1]); gwalk->senders[1] = strdup(newsender); gwalk->tstamp = gettimer(); } dbgprintf("<- log_multisrc\n"); } xymond_log_t *find_log(char *hostname, char *testname, char *origin, xymond_hostlist_t **host) { xtreePos_t hosthandle, testhandle, originhandle; xymond_hostlist_t *hwalk; char *owalk = NULL; testinfo_t *twalk; xymond_log_t *lwalk; *host = NULL; if ((hostname == NULL) || (testname == NULL)) return NULL; hosthandle = xtreeFind(rbhosts, hostname); if (hosthandle != xtreeEnd(rbhosts)) *host = hwalk = xtreeData(rbhosts, hosthandle); else return NULL; testhandle = xtreeFind(rbtests, testname); if (testhandle != xtreeEnd(rbtests)) twalk = xtreeData(rbtests, testhandle); else return NULL; if (origin) { originhandle = xtreeFind(rborigins, origin); if (originhandle != xtreeEnd(rborigins)) owalk = xtreeData(rborigins, originhandle); } for (lwalk = hwalk->logs; (lwalk && ((lwalk->test != twalk) || (lwalk->origin != owalk))); lwalk = lwalk->next); return lwalk; } void get_hts(char *msg, char *sender, char *origin, xymond_hostlist_t **host, testinfo_t **test, char **grouplist, xymond_log_t **log, int *color, char **downcause, int *alltests, int createhost, int createlog) { /* * This routine takes care of finding existing status log records, or * (if they dont exist) creating new ones for an incoming status. * * "msg" contains an incoming message. First list is of the form "KEYWORD host,domain.test COLOR" */ char *firstline, *p; char *hosttest, *hostname, *testname, *colstr, *grp; char hostip[IP_ADDR_STRLEN]; xtreePos_t hosthandle, testhandle, originhandle; xymond_hostlist_t *hwalk = NULL; testinfo_t *twalk = NULL; char *owalk = NULL; xymond_log_t *lwalk = NULL; dbgprintf("-> get_hts\n"); MEMDEFINE(hostip); *hostip = '\0'; *host = NULL; *test = NULL; *log = NULL; *color = -1; if (grouplist) *grouplist = NULL; if (downcause) *downcause = NULL; if (alltests) *alltests = 0; hosttest = hostname = testname = colstr = grp = NULL; p = strchr(msg, '\n'); if (p == NULL) { firstline = strdup(msg); } else { *p = '\0'; firstline = strdup(msg); *p = '\n'; } p = strtok(firstline, " \t"); /* Keyword ... */ if (p) { /* There might be a group-list */ grp = strstr(p, "/group:"); if (grp) grp += 7; } if (p) hosttest = strtok(NULL, " \t"); /* ... HOST.TEST combo ... */ if (hosttest == NULL) goto done; colstr = strtok(NULL, " \t"); /* ... and the color (if any) */ if (colstr) { *color = parse_color(colstr); /* Dont create log-entries if we get a bad color spec. */ if (*color == -1) createlog = 0; } else createlog = 0; if (strncmp(msg, "summary", 7) == 0) { /* Summary messages are handled specially */ hostname = hosttest; /* This will always be "summary" */ testname = strchr(hosttest, '.'); if (testname) { *testname = '\0'; testname++; } } else { char *knownname; hostname = hosttest; testname = strrchr(hosttest, '.'); if (testname) { *testname = '\0'; testname++; } uncommafy(hostname); /* For BB agent compatibility */ knownname = knownhost(hostname, hostip, ghosthandling); if (knownname == NULL) { knownname = log_ghost(hostname, sender, msg); if (knownname == NULL) goto done; } hostname = knownname; } hosthandle = xtreeFind(rbhosts, hostname); if (hosthandle == xtreeEnd(rbhosts)) hwalk = NULL; else hwalk = xtreeData(rbhosts, hosthandle); if (createhost && (hosthandle == xtreeEnd(rbhosts))) { hwalk = create_hostlist_t(hostname, hostip); hostcount++; } if (testname && *testname) { if (alltests && (*testname == '*')) { *alltests = 1; return; } testhandle = xtreeFind(rbtests, testname); if (testhandle != xtreeEnd(rbtests)) twalk = xtreeData(rbtests, testhandle); if (createlog && (twalk == NULL)) twalk = create_testinfo(testname); } else { if (createlog) errprintf("Bogus message from %s: No testname '%s'\n", sender, msg); } if (origin) { originhandle = xtreeFind(rborigins, origin); if (originhandle != xtreeEnd(rborigins)) owalk = xtreeData(rborigins, originhandle); if (createlog && (owalk == NULL)) { owalk = strdup(origin); xtreeAdd(rborigins, owalk, owalk); } } if (hwalk && twalk && owalk) { for (lwalk = hwalk->logs; (lwalk && ((lwalk->test != twalk) || (lwalk->origin != owalk))); lwalk = lwalk->next); if (createlog && (lwalk == NULL)) { lwalk = (xymond_log_t *)calloc(1, sizeof(xymond_log_t)); lwalk->lastchange = (time_t *)calloc((flapcount > 0) ? flapcount : 1, sizeof(time_t)); lwalk->color = lwalk->oldcolor = NO_COLOR; lwalk->host = hwalk; lwalk->test = twalk; lwalk->origin = owalk; lwalk->next = hwalk->logs; hwalk->logs = lwalk; if (strcmp(testname, xgetenv("PINGCOLUMN")) == 0) hwalk->pinglog = lwalk; } } done: if (colstr) { if ((*color == COL_RED) || (*color == COL_YELLOW)) { char *cause; cause = check_downtime(hostname, testname); if (lwalk) lwalk->downtimeactive = (cause != NULL); if (cause) *color = COL_BLUE; if (downcause) *downcause = cause; } else { if (lwalk) lwalk->downtimeactive = 0; } } if (grouplist && grp) *grouplist = strdup(grp); xfree(firstline); *host = hwalk; *test = twalk; *log = lwalk; MEMUNDEFINE(hostip); dbgprintf("<- get_hts\n"); } void clear_cookie(xymond_log_t *log) { if (!log->cookie) return; xtreeDelete(rbcookies, log->cookie); xfree(log->cookie); log->cookie = NULL; log->cookieexpires = 0; } xymond_log_t *find_cookie(char *cookie) { /* * Find a cookie we have issued. */ xymond_log_t *result = NULL; xtreePos_t cookiehandle; dbgprintf("-> find_cookie\n"); cookiehandle = xtreeFind(rbcookies, cookie); if (cookiehandle != xtreeEnd(rbcookies)) { result = xtreeData(rbcookies, cookiehandle); if (result->cookieexpires <= getcurrenttime(NULL)) { clear_cookie(result); result = NULL; } } dbgprintf("<- find_cookie\n"); return result; } static int changedelay(void *hinfo, int newcolor, char *testname, int currcolor) { char *key, *tok, *dstr = NULL; int keylen, result = 0; /* Ignore any delays when we start with a purple status */ if (currcolor == COL_PURPLE) return 0; switch (newcolor) { case COL_RED: dstr = xmh_item(hinfo, XMH_DELAYRED); if (!dstr) dstr = defaultreddelay; break; case COL_YELLOW: dstr = xmh_item(hinfo, XMH_DELAYYELLOW); if (!dstr) dstr = defaultyellowdelay; break; default: break; } if (!dstr) return result; /* Check "DELAYRED=cpu:10,disk:30,ssh:20" - number is in minutes */ keylen = strlen(testname) + 1; key = (char *)malloc(keylen + 1); sprintf(key, "%s:", testname); dstr = strdup(dstr); tok = strtok(dstr, ","); while (tok && (strncmp(key, tok, keylen) != 0)) tok = strtok(NULL, ","); if (tok) result = 60*atoi(tok+keylen); /* Convert to seconds */ xfree(key); xfree(dstr); return result; } void handle_status(unsigned char *msg, char *sender, char *hostname, char *testname, char *grouplist, xymond_log_t *log, int newcolor, char *downcause, int modifyonly) { int validity = defaultvalidity; time_t now = getcurrenttime(NULL); int msglen, issummary; enum alertstate_t oldalertstatus, newalertstatus; int delayval = 0; void *hinfo = hostinfo(hostname); dbgprintf("->handle_status\n"); if (msg == NULL) { errprintf("handle_status got a NULL message for %s.%s, sender %s, color %s\n", textornull(hostname), textornull(testname), textornull(sender), colorname(newcolor)); return; } msglen = strlen(msg); if (msglen == 0) { errprintf("Bogus status message for %s.%s contains no data: Sent from %s\n", textornull(hostname), textornull(testname), textornull(sender)); return; } if (msg_data(msg) == (char *)msg) { errprintf("Bogus status message: msg_data finds no host.test. Sent from: '%s', data:'%s'\n", sender, msg); return; } issummary = (log->host->hosttype == H_SUMMARY); if (strncmp(msg, "status+", 7) == 0) { validity = durationvalue(msg+7); } if (!modifyonly && log->modifiers) { /* * Original status message - check if there is an active modifier for the color. * We dont do this for status changes triggered by a "modify" command. */ modifier_t *mwalk; int mcolor = -1; mwalk = log->modifiers; while (mwalk) { mwalk->valid--; if (mwalk->valid <= 0) { modifier_t *zombie; /* Modifier no longer valid */ zombie = mwalk; if (zombie->source) xfree(zombie->source); if (zombie->cause) xfree(zombie->cause); /* Remove this modifier from the list. Make sure log->modifiers is updated */ if (mwalk == log->modifiers) log->modifiers = mwalk->next; mwalk = mwalk->next; xfree(zombie); } else { if (mwalk->color > mcolor) mcolor = mwalk->color; mwalk = mwalk->next; } } /* If there was an active modifier, this overrides the current "newcolor" status value */ if ((mcolor != -1) && (mcolor != newcolor)) newcolor = mcolor; } /* * Flap check. * * We check if more than flapcount changes have occurred * within "flapthreshold" seconds. If yes, and the newcolor * is less serious than the old color, then we ignore the * color change and keep the status at the more serious level. */ if (modifyonly || issummary) { /* Nothing */ } else if ((flapcount > 0) && ((now - log->lastchange[flapcount-1]) < flapthreshold)) { if (!log->flapping) { errprintf("Flapping detected for %s:%s - %d changes in %d seconds\n", hostname, testname, flapcount, (now - log->lastchange[flapcount-1])); log->flapping = 1; log->oldflapcolor = log->color; log->currflapcolor = newcolor; } else { log->oldflapcolor = log->currflapcolor; log->currflapcolor = newcolor; } /* Make sure we maintain the most critical level reported by the flapping unit */ if (newcolor < log->color) newcolor = log->color; /* * If the status is actually changing, but we've detected it's a * flap and therefore suppress atatus change events, then we must * update the lastchange-times here because it won't be done in * the status-change handler. */ if ((log->oldflapcolor != log->currflapcolor) && (newcolor == log->color)) { int i; for (i=flapcount-1; (i > 0); i--) log->lastchange[i] = log->lastchange[i-1]; log->lastchange[0] = now; } } else { log->flapping = 0; } if (log->enabletime == DISABLED_UNTIL_OK) { /* The test is disabled until we get an OK status */ if ((newcolor != COL_BLUE) && (decide_alertstate(newcolor) == A_OK)) { /* It's OK now - clear the disable status */ log->enabletime = 0; if (log->dismsg) { xfree(log->dismsg); log->dismsg = NULL; } posttochannel(enadischn, channelnames[C_ENADIS], msg, sender, log->host->hostname, log, NULL); } else { /* Still not OK - keep it BLUE */ newcolor = COL_BLUE; } } else if (log->enabletime > now) { /* The test is currently disabled. */ newcolor = COL_BLUE; } else if (log->enabletime) { /* A disable has expired. Clear the timestamp and the message buffer */ log->enabletime = 0; if (log->dismsg) { xfree(log->dismsg); log->dismsg = NULL; } posttochannel(enadischn, channelnames[C_ENADIS], msg, sender, log->host->hostname, log, NULL); } else { /* If we got a downcause, and the status is not disabled, use downcause as the disable text */ if (log->dismsg) { xfree(log->dismsg); log->dismsg = NULL; } if (downcause && (newcolor == COL_BLUE)) log->dismsg = strdup(downcause); } if (log->acktime) { /* Handling of ack'ed tests */ if (decide_alertstate(newcolor) == A_OK) { /* The test recovered. Clear the ack. */ log->acktime = 0; } if (log->acktime > now) { /* Dont need to do anything about an acked test */ } else { /* The acknowledge has expired. Clear the timestamp and the message buffer */ log->acktime = 0; if (log->ackmsg) { xfree(log->ackmsg); log->ackmsg = NULL; } } } if (!modifyonly) { log->logtime = now; /* * Decide how long this status is valid. * * Normally we'll just set the valid time according * to the validity of the status report. * * If the status is acknowledged, make it valid for the longest period * of the acknowledgment and the normal validity (so an acknowledged status * does not go purple because it is not being updated due to the host being down). * * Same tweak must be done for disabled tests. */ log->validtime = now + validity*60; if (log->acktime && (log->acktime > log->validtime)) log->validtime = log->acktime; if (log->enabletime) { if (log->enabletime == DISABLED_UNTIL_OK) log->validtime = INT_MAX; else if (log->enabletime > log->validtime) log->validtime = log->enabletime; } /* * If we have an existing status, check if the sender has changed. * This could be an indication of a mis-configured host reporting with * the wrong hostname. */ if (*(log->sender) && (strcmp(log->sender, sender) != 0)) { /* * There are a few exceptions: * - if sender is "xymond", then this is an internal update, e.g. a status going purple. * - if the host has "pulldata" enabled, then the sender shows up as the host doing the * data collection, so it does not make sense to check it (thanks to Cade Robinson). * - some multi-homed hosts use a random IP for sending us data. */ if ( (strcmp(log->sender, "xymond") != 0) && (strcmp(sender, "xymond") != 0) ) { if ((xmh_item(hinfo, XMH_FLAG_PULLDATA) == NULL) && (xmh_item(hinfo, XMH_FLAG_MULTIHOMED) == NULL)) { log_multisrc(log, sender); } } } strncpy(log->sender, sender, sizeof(log->sender)-1); *(log->sender + sizeof(log->sender) - 1) = '\0'; } /* Handle delayed red/yellow */ switch (newcolor) { case COL_RED: if (log->redstart == 0) log->redstart = now; /* * Do NOT clear yellowstart. If we changed green->red, then it is already clear. * When changing yellow->red, we may drop down to yellow again later and then we * want to count the red time as part of the yellow status. * But do set yellowstart if it is 0. If we go green->red now, and then later * red->yellow, we do want it to look as if the yellow began when the red status * happened. */ if (log->yellowstart == 0) log->yellowstart = now; break; case COL_YELLOW: if (log->yellowstart == 0) log->yellowstart = now; log->redstart = 0; /* Clear it here, so brief red's from a yellow state does not trigger red */ break; default: log->yellowstart = log->redstart = 0; break; } if ((newcolor == COL_RED) && ((delayval = changedelay(hinfo, COL_RED, testname, log->color)) > 0)) { if ((now - log->redstart) >= delayval) { /* Time's up - we will go red */ } else { delayval = changedelay(hinfo, COL_YELLOW, testname, log->color); if ((now - log->redstart) >= delayval) { /* The yellow delay has been passed, so go yellow */ newcolor = COL_YELLOW; } else { /* Neither yellow nor red delay passed - keep current color */ newcolor = log->color; } } } else if ((newcolor == COL_YELLOW) && ((delayval = changedelay(hinfo, COL_YELLOW, testname, log->color)) > 0)) { if ((now - log->yellowstart) < delayval) newcolor = log->color; /* Keep current color */ } log->oldcolor = log->color; log->color = newcolor; oldalertstatus = decide_alertstate(log->oldcolor); newalertstatus = decide_alertstate(newcolor); if (log->grouplist) xfree(log->grouplist); if (grouplist) log->grouplist = strdup(grouplist); if (log->acklist) { ackinfo_t *awalk; if ((oldalertstatus != A_OK) && (newalertstatus == A_OK)) { /* The status recovered. Set the "clearack" timer, unless it is just because we are in a DOWNTIME period */ if (!log->downtimeactive) { time_t cleartime = now + ACKCLEARDELAY; for (awalk = log->acklist; (awalk); awalk = awalk->next) awalk->cleartime = cleartime; } } else if ((oldalertstatus == A_OK) && (newalertstatus != A_OK)) { /* The status went into a failure-mode. Any acks are revived */ for (awalk = log->acklist; (awalk); awalk = awalk->next) awalk->cleartime = awalk->validuntil; } } if (msg != log->message) { /* They can be the same when called from handle_enadis() or check_purple_status() */ char *p; /* * Note here: * - log->msgsz is the buffer size INCLUDING the final \0. * - msglen is the message length WITHOUT the final \0. */ if ((log->message == NULL) || (log->msgsz == 0)) { /* No buffer - get one */ log->message = (unsigned char *)malloc(msglen+1); memcpy(log->message, msg, msglen+1); log->msgsz = msglen+1; } else if (log->msgsz > msglen) { /* Message - including \0 - fits into the existing buffer. */ memcpy(log->message, msg, msglen+1); } else { /* Message does not fit into existing buffer. Grow it. */ log->message = (unsigned char *)realloc(log->message, msglen+1); memcpy(log->message, msg, msglen+1); log->msgsz = msglen+1; } /* Get at the test flags. They are immediately after the color */ p = msg_data(msg); p += strlen(colorname(newcolor)); if (strncmp(p, " " then remove it from the message */ if ((p = strstr(bom, ""); if (p) addtobuffer(buf, p+3); /* And if there is more than line 1, add it as well */ if (eoln) { *eoln = '\n'; addtobuffer(buf, eoln); } } else { if (eoln) *eoln = '\n'; addtobuffer(buf, bom); } addtobuffer(buf, "\n"); if (recip->format == ALERTFORM_TEXT) { sprintf(info, "See %s%s\n", xgetenv("XYMONWEBHOST"), hostsvcurl(alert->hostname, alert->testname, 0)); addtobuffer(buf, info); } MEMUNDEFINE(info); return STRBUF(buf); case ALERTFORM_SMS: /* * Send a report containing a brief alert * and any lines that begin with a "&COLOR" */ switch (alert->state) { case A_PAGING: case A_ACKED: sprintf(info, "%s:%s %s [%d]", alert->hostname, alert->testname, colorname(alert->color), alert->cookie); break; case A_RECOVERED: sprintf(info, "%s:%s RECOVERED", alert->hostname, alert->testname); break; case A_DISABLED: sprintf(info, "%s:%s DISABLED", alert->hostname, alert->testname); break; case A_NOTIFY: sprintf(info, "%s:%s NOTICE", alert->hostname, alert->testname); break; case A_NORECIP: case A_DEAD: break; } addtobuffer(buf, info); bom = msg_data(alert->pagemessage); eoln = strchr(bom, '\n'); if (eoln) { bom = eoln; while ((bom = strstr(bom, "\n&")) != NULL) { eoln = strchr(bom+1, '\n'); if (eoln) *eoln = '\0'; if ((strncmp(bom+1, "&red", 4) == 0) || (strncmp(bom+1, "&yellow", 7) == 0)) addtobuffer(buf, bom); if (eoln) *eoln = '\n'; bom = (eoln ? eoln+1 : ""); } } MEMUNDEFINE(info); return STRBUF(buf); case ALERTFORM_SCRIPT: sprintf(info, "%s:%s %s [%d]\n", alert->hostname, alert->testname, colorname(alert->color), alert->cookie); addtobuffer(buf, info); addtobuffer(buf, msg_data(alert->pagemessage)); addtobuffer(buf, "\n"); sprintf(info, "See %s%s\n", xgetenv("XYMONWEBHOST"), hostsvcurl(alert->hostname, alert->testname, 0)); addtobuffer(buf, info); MEMUNDEFINE(info); return STRBUF(buf); case ALERTFORM_PAGER: case ALERTFORM_NONE: MEMUNDEFINE(info); return ""; } MEMUNDEFINE(info); return alert->pagemessage; } void send_alert(activealerts_t *alert, FILE *logfd) { recip_t *recip; int first = 1; int alertcount = 0; time_t now = getcurrenttime(NULL); /* A_PAGING, A_NORECIP, A_ACKED, A_RECOVERED, A_DISABLED, A_NOTIFY, A_DEAD */ char *alerttxt[A_DEAD+1] = { "Paging", "Norecip", "Acked", "Recovered", "Disabled", "Notify", "Dead" }; dbgprintf("send_alert %s:%s state %d\n", alert->hostname, alert->testname, (int)alert->state); traceprintf("send_alert %s:%s state %s\n", alert->hostname, alert->testname, alerttxt[alert->state]); stoprulefound = 0; while (!stoprulefound && ((recip = next_recipient(alert, &first, NULL, NULL)) != NULL)) { /* If this is an "UNMATCHED" rule, ignore it if we have already sent out some alert */ if (recip->unmatchedonly && (alertcount != 0)) { traceprintf("Recipient '%s' dropped, not unmatched (count=%d)\n", recip->recipient, alertcount); continue; } if (recip->noalerts && ((alert->state == A_PAGING) || (alert->state == A_RECOVERED) || (alert->state == A_DISABLED))) { traceprintf("Recipient '%s' dropped (NOALERT)\n", recip->recipient); continue; } if (recip->method == M_IGNORE) { traceprintf("IGNORE rule found\n"); continue; } if (alert->state == A_PAGING) { repeat_t *rpt = NULL; /* * This runs in a child-process context, so the record we * might create here is NOT used later on. */ rpt = find_repeatinfo(alert, recip, 1); if (!rpt) continue; /* Happens for e.g. M_IGNORE recipients */ dbgprintf(" repeat %s at %d\n", rpt->recipid, rpt->nextalert); if (rpt->nextalert > now) { traceprintf("Recipient '%s' dropped, next alert due at %ld > %ld\n", rpt->recipid, (long)rpt->nextalert, (long)now); continue; } alertcount++; } else if ((alert->state == A_RECOVERED) || (alert->state == A_DISABLED)) { /* RECOVERED messages require that we've sent out an alert before */ repeat_t *rpt = NULL; rpt = find_repeatinfo(alert, recip, 0); if (!rpt) continue; alertcount++; } dbgprintf(" Alert for %s:%s to %s\n", alert->hostname, alert->testname, recip->recipient); switch (recip->method) { case M_IGNORE: break; case M_MAIL: { char cmd[32768]; char *mailsubj; char *mailrecip; FILE *mailpipe; MEMDEFINE(cmd); mailsubj = message_subject(alert, recip); mailrecip = message_recipient(recip->recipient, alert->hostname, alert->testname, colorname(alert->color)); if (mailsubj) { if (xgetenv("MAIL")) sprintf(cmd, "%s \"%s\" ", xgetenv("MAIL"), mailsubj); else if (xgetenv("MAILC")) sprintf(cmd, "%s -s \"%s\" ", xgetenv("MAILC"), mailsubj); else sprintf(cmd, "mail -s \"%s\" ", mailsubj); } else { if (xgetenv("MAILC")) sprintf(cmd, "%s ", xgetenv("MAILC")); else sprintf(cmd, "mail "); } strcat(cmd, mailrecip); traceprintf("Mail alert with command '%s'\n", cmd); if (testonly) { MEMUNDEFINE(cmd); break; } mailpipe = popen(cmd, "w"); if (mailpipe) { fprintf(mailpipe, "%s", message_text(alert, recip)); pclose(mailpipe); if (logfd) { init_timestamp(); fprintf(logfd, "%s %s.%s (%s) %s[%d] %ld %d", timestamp, alert->hostname, alert->testname, alert->ip, mailrecip, recip->cfid, (long)now, servicecode(alert->testname)); if ((alert->state == A_RECOVERED) || (alert->state == A_DISABLED)) { fprintf(logfd, " %ld\n", (long)(now - alert->eventstart)); } else { fprintf(logfd, "\n"); } fflush(logfd); } } else { errprintf("ERROR: Cannot open command pipe for '%s' - alert lost!\n", cmd); traceprintf("Mail pipe failed - alert lost\n"); } MEMUNDEFINE(cmd); } break; case M_SCRIPT: { pid_t scriptpid; char *scriptrecip; traceprintf("Script alert with command '%s' and recipient %s\n", recip->scriptname, recip->recipient); if (testonly) break; scriptrecip = message_recipient(recip->recipient, alert->hostname, alert->testname, colorname(alert->color)); scriptpid = fork(); if (scriptpid == 0) { /* Setup all of the environment for a paging script */ void *hinfo; char *p; int ip1=0, ip2=0, ip3=0, ip4=0; char *bbalphamsg, *ackcode, *rcpt, *bbhostname, *bbhostsvc, *bbhostsvccommas, *bbnumeric, *machip, *bbsvcname, *bbsvcnum, *bbcolorlevel, *recovered, *downsecs, *eventtstamp, *downsecsmsg, *cfidtxt; char *alertid, *alertidenv; int msglen; cfidtxt = (char *)malloc(strlen("CFID=") + 10); sprintf(cfidtxt, "CFID=%d", recip->cfid); putenv(cfidtxt); p = message_text(alert, recip); msglen = strlen(p); if (msglen > MAX_ALERTMSG_SCRIPTS) { dbgprintf("Cropping large alert message from %d to %d bytes\n", msglen, MAX_ALERTMSG_SCRIPTS); msglen = MAX_ALERTMSG_SCRIPTS; } msglen += strlen("BBALPHAMSG="); bbalphamsg = (char *)malloc(msglen + 1); snprintf(bbalphamsg, msglen+1, "BBALPHAMSG=%s", p); putenv(bbalphamsg); ackcode = (char *)malloc(strlen("ACKCODE=") + 10); sprintf(ackcode, "ACKCODE=%d", alert->cookie); putenv(ackcode); rcpt = (char *)malloc(strlen("RCPT=") + strlen(scriptrecip) + 1); sprintf(rcpt, "RCPT=%s", scriptrecip); putenv(rcpt); bbhostname = (char *)malloc(strlen("BBHOSTNAME=") + strlen(alert->hostname) + 1); sprintf(bbhostname, "BBHOSTNAME=%s", alert->hostname); putenv(bbhostname); bbhostsvc = (char *)malloc(strlen("BBHOSTSVC=") + strlen(alert->hostname) + 1 + strlen(alert->testname) + 1); sprintf(bbhostsvc, "BBHOSTSVC=%s.%s", alert->hostname, alert->testname); putenv(bbhostsvc); bbhostsvccommas = (char *)malloc(strlen("BBHOSTSVCCOMMAS=") + strlen(alert->hostname) + 1 + strlen(alert->testname) + 1); sprintf(bbhostsvccommas, "BBHOSTSVCCOMMAS=%s.%s", commafy(alert->hostname), alert->testname); putenv(bbhostsvccommas); bbnumeric = (char *)malloc(strlen("BBNUMERIC=") + 22 + 1); p = bbnumeric; p += sprintf(p, "BBNUMERIC="); p += sprintf(p, "%03d", servicecode(alert->testname)); sscanf(alert->ip, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4); p += sprintf(p, "%03d%03d%03d%03d", ip1, ip2, ip3, ip4); p += sprintf(p, "%d", alert->cookie); putenv(bbnumeric); machip = (char *)malloc(strlen("MACHIP=") + 13); sprintf(machip, "MACHIP=%03d%03d%03d%03d", ip1, ip2, ip3, ip4); putenv(machip); bbsvcname = (char *)malloc(strlen("BBSVCNAME=") + strlen(alert->testname) + 1); sprintf(bbsvcname, "BBSVCNAME=%s", alert->testname); putenv(bbsvcname); bbsvcnum = (char *)malloc(strlen("BBSVCNUM=") + 10); sprintf(bbsvcnum, "BBSVCNUM=%d", servicecode(alert->testname)); putenv(bbsvcnum); bbcolorlevel = (char *)malloc(strlen("BBCOLORLEVEL=") + strlen(colorname(alert->color)) + 1); sprintf(bbcolorlevel, "BBCOLORLEVEL=%s", colorname(alert->color)); putenv(bbcolorlevel); recovered = (char *)malloc(strlen("RECOVERED=") + 2); switch (alert->state) { case A_RECOVERED: strcpy(recovered, "RECOVERED=1"); break; case A_DISABLED: strcpy(recovered, "RECOVERED=2"); break; default: strcpy(recovered, "RECOVERED=0"); break; } putenv(recovered); downsecs = (char *)malloc(strlen("DOWNSECS=") + 20); sprintf(downsecs, "DOWNSECS=%ld", (long)(getcurrenttime(NULL) - alert->eventstart)); putenv(downsecs); eventtstamp = (char *)malloc(strlen("EVENTSTART=") + 20); sprintf(eventtstamp, "EVENTSTART=%ld", (long)alert->eventstart); putenv(eventtstamp); if ((alert->state == A_RECOVERED) || (alert->state == A_DISABLED)) { downsecsmsg = (char *)malloc(strlen("DOWNSECSMSG=Event duration :") + 20); sprintf(downsecsmsg, "DOWNSECSMSG=Event duration : %ld", (long)(getcurrenttime(NULL) - alert->eventstart)); } else { downsecsmsg = strdup("DOWNSECSMSG="); } putenv(downsecsmsg); alertid = make_alertid(alert->hostname, alert->testname, alert->eventstart); alertidenv = (char *)malloc(strlen("ALERTID=") + strlen(alertid) + 10); sprintf(alertidenv, "ALERTID=%s", alertid); putenv(alertidenv); hinfo = hostinfo(alert->hostname); if (hinfo) { enum xmh_item_t walk; char *itm, *id, *bbhenv; for (walk = 0; (walk < XMH_LAST); walk++) { itm = xmh_item(hinfo, walk); id = xmh_item_id(walk); if (itm && id) { bbhenv = (char *)malloc(strlen(id) + strlen(itm) + 2); sprintf(bbhenv, "%s=%s", id, itm); putenv(bbhenv); } } } /* The child starts the script */ execlp(recip->scriptname, recip->scriptname, NULL); errprintf("Could not launch paging script %s: %s\n", recip->scriptname, strerror(errno)); exit(0); } else if (scriptpid > 0) { /* Parent waits for child to complete */ int childstat; wait(&childstat); if (WIFEXITED(childstat) && (WEXITSTATUS(childstat) != 0)) { errprintf("Paging script %s terminated with status %d\n", recip->scriptname, WEXITSTATUS(childstat)); } else if (WIFSIGNALED(childstat)) { errprintf("Paging script %s terminated by signal %d\n", recip->scriptname, WTERMSIG(childstat)); } if (logfd) { init_timestamp(); fprintf(logfd, "%s %s.%s (%s) %s %ld %d", timestamp, alert->hostname, alert->testname, alert->ip, scriptrecip, (long)now, servicecode(alert->testname)); if ((alert->state == A_RECOVERED) || (alert->state == A_DISABLED)) { fprintf(logfd, " %ld\n", (long)(now - alert->eventstart)); } else { fprintf(logfd, "\n"); } fflush(logfd); } } else { errprintf("ERROR: Fork failed to launch script '%s' - alert lost\n", recip->scriptname); traceprintf("Script fork failed - alert lost\n"); } } break; } } } void finish_alerts(void) { /* No special post-alert setup needed */ return; } time_t next_alert(activealerts_t *alert) { time_t now = getcurrenttime(NULL); int first = 1; int found = 0; time_t nexttime = now+(30*86400); /* 30 days from now */ recip_t *recip; repeat_t *rpt; time_t r_next = -1; stoprulefound = 0; while (!stoprulefound && ((recip = next_recipient(alert, &first, NULL, &r_next)) != NULL)) { found = 1; /* * This runs in the parent xymond_alert proces, so we must create * a repeat-record here - or all alerts will get repeated every minute. */ rpt = find_repeatinfo(alert, recip, 1); if (rpt) { if (rpt->nextalert <= now) rpt->nextalert = (now + recip->interval); if (rpt->nextalert < nexttime) nexttime = rpt->nextalert; } else if (r_next != -1) { if (r_next < nexttime) nexttime = r_next; } else { /* * This can happen, e.g. if we get an alert, but the minimum * DURATION has not been met. * This simply means we dropped the alert -for now - for some * reason, so it should be retried again right away. Put in a * 1 minute delay to prevent run-away alerts from flooding us. */ if ((now + 60) < nexttime) nexttime = now + 60; } } if (r_next != -1) { /* * Waiting for a minimum duration to trigger */ if (r_next < nexttime) nexttime = r_next; } else if (!found) { /* * There IS a potential recipient (or we would not be here). * And it's not a DURATION waiting to happen. * Probably waiting for a TIME restriction to trigger, so try * again soon. */ nexttime = now + 60; } return nexttime; } void cleanup_alert(activealerts_t *alert) { /* * A status has recovered and gone green, or it has been deleted. * So we clear out all info we have about this alert and it's recipients. */ char *id; repeat_t *rptwalk, *rptprev; dbgprintf("cleanup_alert called for host %s, test %s\n", alert->hostname, alert->testname); id = (char *)malloc(strlen(alert->hostname)+strlen(alert->testname)+3); sprintf(id, "%s|%s|", alert->hostname, alert->testname); rptwalk = rpthead; rptprev = NULL; while (rptwalk) { if (strncmp(rptwalk->recipid, id, strlen(id)) == 0) { repeat_t *tmp = rptwalk; dbgprintf("cleanup_alert found recipient %s\n", rptwalk->recipid); if (rptwalk == rpthead) { rptwalk = rpthead = rpthead->next; } else { rptprev->next = rptwalk->next; rptwalk = rptwalk->next; } xfree(tmp->recipid); xfree(tmp); } else { rptprev = rptwalk; rptwalk = rptwalk->next; } } xfree(id); } void clear_interval(activealerts_t *alert) { int first = 1; recip_t *recip; repeat_t *rpt; alert->nextalerttime = 0; stoprulefound = 0; while (!stoprulefound && ((recip = next_recipient(alert, &first, NULL, NULL)) != NULL)) { rpt = find_repeatinfo(alert, recip, 0); if (rpt) { dbgprintf("Cleared repeat interval for %s\n", rpt->recipid); rpt->nextalert = 0; } } } void save_state(char *filename) { FILE *fd = fopen(filename, "w"); repeat_t *walk; if (fd == NULL) return; for (walk = rpthead; (walk); walk = walk->next) { fprintf(fd, "%ld|%s\n", (long) walk->nextalert, walk->recipid); } fclose(fd); } void load_state(char *filename, char *statusbuf) { FILE *fd = fopen(filename, "r"); strbuffer_t *inbuf; char *p; if (fd == NULL) return; initfgets(fd); inbuf = newstrbuffer(0); while (unlimfgets(inbuf, fd)) { sanitize_input(inbuf, 0, 0); p = strchr(STRBUF(inbuf), '|'); if (p) { repeat_t *newrpt; *p = '\0'; if (atoi(STRBUF(inbuf)) > getcurrenttime(NULL)) { char *found = NULL; if (statusbuf) { char *htend; /* statusbuf contains lines with "HOSTNAME|TESTNAME|COLOR" */ htend = strchr(p+1, '|'); if (htend) htend = strchr(htend+1, '|'); if (htend) { *htend = '\0'; *p = '\n'; found = strstr(statusbuf, p); if (!found && (strncmp(statusbuf, p+1, strlen(p+1)) == 0)) found = statusbuf; *htend = '|'; } } if (!found) continue; newrpt = (repeat_t *)malloc(sizeof(repeat_t)); newrpt->recipid = strdup(p+1); newrpt->nextalert = atoi(STRBUF(inbuf)); newrpt->next = rpthead; rpthead = newrpt; } } } fclose(fd); freestrbuffer(inbuf); } xymon-4.3.7/xymond/xymond_buffer.h0000664000175000017500000000170711615341300016560 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __XYMOND_BUFFER_H__ #define __XYMOND_BUFFER_H__ enum msgchannels_t { C_STATUS=1, C_STACHG, C_PAGE, C_DATA, C_NOTES, C_ENADIS, C_CLIENT, C_CLICHG, C_USER, C_LAST }; extern unsigned int shbufsz(enum msgchannels_t chnid); #endif xymon-4.3.7/xymond/xymond_rrd.c0000664000175000017500000003055511630732124016101 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* This is a xymond worker module for the "status" and "data" channels. */ /* This module maintains the RRD database-files, updating them as new */ /* data arrives. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond_rrd.c 6748 2011-09-04 17:24:36Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymond_worker.h" #include "xymond_rrd.h" #include "do_rrd.h" #include "client_config.h" #include #define MAX_META 20 /* The maximum number of meta-data items in a message */ int seq = 0; static int running = 1; static time_t reloadtime = 0; typedef struct rrddeftree_t { char *key; int count; char **defs; } rrddeftree_t; static void * rrddeftree; static void sig_handler(int signum) { switch (signum) { case SIGHUP: reloadtime = 0; break; case SIGCHLD: break; case SIGINT: case SIGTERM: running = 0; break; } } static void update_locator_hostdata(char *id) { DIR *fd; struct dirent *d; fd = opendir(rrddir); if (fd == NULL) { errprintf("Cannot scan directory %s\n", rrddir); return; } while ((d = readdir(fd)) != NULL) { if (*(d->d_name) == '.') continue; locator_register_host(d->d_name, ST_RRD, id); } closedir(fd); } static void load_rrddefs(void) { char fn[PATH_MAX]; FILE *fd; strbuffer_t *inbuf = newstrbuffer(0); char *key = NULL, *p; char **defs = NULL; int defcount = 0; rrddeftree_t *newrec; rrddeftree = xtreeNew(strcasecmp); sprintf(fn, "%s/etc/rrddefinitions.cfg", xgetenv("XYMONHOME")); fd = stackfopen(fn, "r", NULL); if (fd == NULL) goto loaddone; while (stackfgets(inbuf, NULL)) { sanitize_input(inbuf, 1, 0); if (STRBUFLEN(inbuf) == 0) continue; if (*(STRBUF(inbuf)) == '[') { if (key && (defcount > 0)) { /* Save the current record */ newrec = (rrddeftree_t *)malloc(sizeof(rrddeftree_t)); newrec->key = key; newrec->defs = defs; newrec->count = defcount; xtreeAdd(rrddeftree, newrec->key, newrec); key = NULL; defs = NULL; defcount = 0; } key = strdup(STRBUF(inbuf)+1); p = strchr(key, ']'); if (p) *p = '\0'; } else if (key) { if (!defs) { defcount = 1; defs = (char **)malloc(sizeof(char *)); } else { defcount++; defs = (char **)realloc(defs, defcount * sizeof(char *)); } p = STRBUF(inbuf); p += strspn(p, " \t"); defs[defcount-1] = strdup(p); } } if (key && (defcount > 0)) { /* Save the last record */ newrec = (rrddeftree_t *)malloc(sizeof(rrddeftree_t)); newrec->key = key; newrec->defs = defs; newrec->count = defcount; xtreeAdd(rrddeftree, newrec->key, newrec); } stackfclose(fd); loaddone: freestrbuffer(inbuf); /* Check if the default record exists */ if (xtreeFind(rrddeftree, "") == xtreeEnd(rrddeftree)) { /* Create the default record */ newrec = (rrddeftree_t *)malloc(sizeof(rrddeftree_t)); newrec->key = strdup(""); newrec->defs = (char **)malloc(4 * sizeof(char *));; newrec->defs[0] = strdup("RRA:AVERAGE:0.5:1:576"); newrec->defs[1] = strdup("RRA:AVERAGE:0.5:6:576"); newrec->defs[2] = strdup("RRA:AVERAGE:0.5:24:576"); newrec->defs[3] = strdup("RRA:AVERAGE:0.5:288:576"); newrec->count = 4; xtreeAdd(rrddeftree, newrec->key, newrec); } } char **get_rrd_definition(char *key, int *count) { xtreePos_t handle; handle = xtreeFind(rrddeftree, key); if (handle == xtreeEnd(rrddeftree)) { handle = xtreeFind(rrddeftree, ""); /* The default record */ } rrddeftree_t *rec = (rrddeftree_t *)xtreeData(rrddeftree, handle); *count = rec->count; return rec->defs; } int main(int argc, char *argv[]) { char *msg; int argi; struct sigaction sa; char *exthandler = NULL; char *extids = NULL; char *processor = NULL; struct sockaddr_un ctlsockaddr; int ctlsocket; /* Handle program options. */ for (argi = 1; (argi < argc); argi++) { if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--rrddir=")) { char *p = strchr(argv[argi], '='); rrddir = strdup(p+1); } else if (argnmatch(argv[argi], "--extra-script=")) { char *p = strchr(argv[argi], '='); exthandler = strdup(p+1); } else if (argnmatch(argv[argi], "--extra-tests=")) { char *p = strchr(argv[argi], '='); extids = strdup(p+1); } else if (argnmatch(argv[argi], "--processor=")) { char *p = strchr(argv[argi], '='); processor = strdup(p+1); } else if (strcmp(argv[argi], "--no-cache") == 0) { use_rrd_cache = 0; } else if (net_worker_option(argv[argi])) { /* Handled in the subroutine */ } } save_errbuf = 0; if ((rrddir == NULL) && xgetenv("XYMONRRDS")) { rrddir = strdup(xgetenv("XYMONRRDS")); } if (exthandler && extids) setup_exthandler(exthandler, extids); /* Do the network stuff if needed */ net_worker_run(ST_RRD, LOC_STICKY, update_locator_hostdata); setup_signalhandler("xymond_rrd"); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; sigaction(SIGHUP, &sa, NULL); sigaction(SIGCHLD, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); signal(SIGPIPE, SIG_DFL); /* Setup the control socket that receives cache-flush commands */ memset(&ctlsockaddr, 0, sizeof(ctlsockaddr)); sprintf(ctlsockaddr.sun_path, "%s/rrdctl.%d", xgetenv("XYMONTMP"), getpid()); unlink(ctlsockaddr.sun_path); /* In case it was accidentally left behind */ ctlsockaddr.sun_family = AF_UNIX; ctlsocket = socket(AF_UNIX, SOCK_DGRAM, 0); if (ctlsocket == -1) { errprintf("Cannot create cache-control socket (%s)\n", strerror(errno)); return 1; } fcntl(ctlsocket, F_SETFL, O_NONBLOCK); if (bind(ctlsocket, (struct sockaddr *)&ctlsockaddr, sizeof(ctlsockaddr)) == -1) { errprintf("Cannot bind to cache-control socket (%s)\n", strerror(errno)); return 1; } /* Linux obeys filesystem permissions on the socket file, so make it world-accessible */ if (chmod(ctlsockaddr.sun_path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) == -1) { errprintf("Setting permissions on cache-control socket failed: %s\n", strerror(errno)); } /* Load the RRD definitions */ load_rrddefs(); /* If we are passing data to an external processor, create the pipe to it */ setup_extprocessor(processor); running = 1; while (running) { char *eoln, *restofmsg = NULL; char *metadata[MAX_META+1]; int metacount; char *p; char *hostname = NULL, *testname = NULL, *sender = NULL, *classname = NULL, *pagepaths = NULL; xymonrrd_t *ldef = NULL; time_t tstamp; int childstat; ssize_t n; char ctlbuf[PATH_MAX]; int gotcachectlmessage; time_t now; /* See if we have any cache-control messages pending */ do { n = recv(ctlsocket, ctlbuf, sizeof(ctlbuf), 0); gotcachectlmessage = (n > 0); if (gotcachectlmessage) { /* We have a control message */ char *bol, *eol; ctlbuf[n] = '\0'; bol = ctlbuf; do { eol = strchr(bol, '\n'); if (eol) *eol = '\0'; rrdcacheflushhost(bol); if (eol) { bol = eol+1; } else bol = NULL; } while (bol && *bol); } } while (gotcachectlmessage); /* Get next message */ msg = get_xymond_message(C_LAST, argv[0], &seq, NULL); if (msg == NULL) { running = 0; continue; } now = gettimer(); if (reloadtime < now) { /* Reload configuration files */ load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); load_client_config(NULL); reloadtime = now + 600; } /* Split the message in the first line (with meta-data), and the rest */ eoln = strchr(msg, '\n'); if (eoln) { *eoln = '\0'; restofmsg = eoln+1; } /* Parse the meta-data */ metacount = 0; memset(&metadata, 0, sizeof(metadata)); p = gettok(msg, "|"); while (p && (metacount < MAX_META)) { metadata[metacount++] = p; p = gettok(NULL, "|"); } metadata[metacount] = NULL; if ((metacount >= 14) && (strncmp(metadata[0], "@@status", 8) == 0)) { /* * @@status|timestamp|sender|origin|hostname|testname|expiretime|color|testflags|\ * prevcolor|changetime|ackexpiretime|ackmessage|disableexpiretime|disablemessage|\ * clienttstamp|flapping|classname|pagepaths */ int color = parse_color(metadata[7]); switch (color) { case COL_GREEN: case COL_YELLOW: case COL_RED: case COL_BLUE: /* Blue is OK, because it only arrives here when an update is sent */ tstamp = atoi(metadata[1]); sender = metadata[2]; hostname = metadata[4]; testname = metadata[5]; classname = (metadata[17] ? metadata[17] : ""); pagepaths = (metadata[18] ? metadata[18] : ""); ldef = find_xymon_rrd(testname, metadata[8]); update_rrd(hostname, testname, restofmsg, tstamp, sender, ldef, classname, pagepaths); break; default: /* Ignore reports with purple, blue or clear - they have no data we want. */ break; } } else if ((metacount > 5) && (strncmp(metadata[0], "@@data", 6) == 0)) { /* @@data|timestamp|sender|origin|hostname|testname|classname|pagepaths */ tstamp = atoi(metadata[1]); sender = metadata[2]; hostname = metadata[4]; testname = metadata[5]; classname = (metadata[6] ? metadata[6] : ""); pagepaths = (metadata[7] ? metadata[7] : ""); ldef = find_xymon_rrd(testname, ""); update_rrd(hostname, testname, restofmsg, tstamp, sender, ldef, classname, pagepaths); } else if (strncmp(metadata[0], "@@shutdown", 10) == 0) { running = 0; continue; } else if (strncmp(metadata[0], "@@idle", 6) == 0) { /* Ignored */ continue; } else if (strncmp(metadata[0], "@@logrotate", 11) == 0) { char *fn = xgetenv("XYMONCHANNEL_LOGFILENAME"); if (fn && strlen(fn)) { freopen(fn, "a", stdout); freopen(fn, "a", stderr); } continue; } else if (strncmp(metadata[0], "@@reload", 8) == 0) { reloadtime = 0; } else if ((metacount > 3) && (strncmp(metadata[0], "@@drophost", 10) == 0)) { char hostdir[PATH_MAX]; hostname = metadata[3]; MEMDEFINE(hostdir); sprintf(hostdir, "%s/%s", rrddir, hostname); dropdirectory(hostdir, 1); MEMUNDEFINE(hostdir); } else if ((metacount > 4) && (strncmp(metadata[0], "@@droptest", 10) == 0)) { /* * Not implemented. Mappings of testnames -> rrd files is * too complex, so on the rare occasion that a single test * is deleted, they will have to delete the rrd files themselves. */ } else if ((metacount > 4) && (strncmp(metadata[0], "@@renamehost", 12) == 0)) { char oldhostdir[PATH_MAX]; char newhostdir[PATH_MAX]; char *newhostname; MEMDEFINE(oldhostdir); MEMDEFINE(newhostdir); hostname = metadata[3]; newhostname = metadata[4]; sprintf(oldhostdir, "%s/%s", rrddir, hostname); sprintf(newhostdir, "%s/%s", rrddir, newhostname); rename(oldhostdir, newhostdir); if (net_worker_locatorbased()) locator_rename_host(hostname, newhostname, ST_RRD); MEMUNDEFINE(newhostdir); MEMUNDEFINE(oldhostdir); } else if ((metacount > 5) && (strncmp(metadata[0], "@@renametest", 12) == 0)) { /* Not implemented. See "droptest". */ } /* * We fork a subprocess when processing drophost requests. * Pickup any finished child processes to avoid zombies */ while (wait3(&childstat, WNOHANG, NULL) > 0) ; } /* Flush all cached updates to disk */ errprintf("Shutting down, flushing cached updates to disk\n"); rrdcacheflushall(); errprintf("Cache flush completed\n"); /* Close the external processor */ shutdown_extprocessor(); /* Close the control socket */ close(ctlsocket); unlink(ctlsockaddr.sun_path); return 0; } xymon-4.3.7/xymond/combo.cfg.50000664000175000017500000000616611671641417015505 0ustar henrikhenrik.TH COMBO.CFG 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME combo.cfg \- Configuration of combostatus tool .SH SYNOPSIS .B $XYMONHOME/etc/combo.cfg .SH DESCRIPTION .I combostatus(1) uses it's own configuration file, $XYMONHOME/etc/combo.cfg Each line in this file defines a combined test. .SH FILE FORMAT Each line of the file defines a new combined test. Blank lines and lines starting with a hash mark (#) are treated as comments and ignored. .sp The configuration file uses the hostnames and testnames that are already used in your Xymon hosts.cfg file. These are then combined using normal logical operators - "||" for "or", "&&" for "and" etc. A simple test - e.g. "Web1.http" - results in the value "1" if the "http" test for server "Web1" is green, yellow or clear. It yields the value "0" if it is red, purple or blue. Apart from the logical operations, you can also do integer arithmetic and comparisons. E.g. the following is valid: WebCluster.http = (Web1.http + Web2.http + Web3.http) >= 2 This test is green if two or more of the http tests for Web1, Web2 and Web3 are green. The full range of operators are: + Add - Subtract * Multiply / Divide % Modulo | Bit-wise "or" & Bit-wise "and" || Logical "or" && Logical "and" > Greater than < Less than >= Greater than or equal <= Less than or equal == Equal There is currently no support for a "not" operator. If you need it, use the transcription "(host.test == 0)" instead of "!host.test". NB: All operators have EQUAL PRECEDENCE. If you need something evaluated in a specific order, use parentheses to group the expressions together. If the expression comes out as "0", the combined test goes red. If it comes out as non-zero, the combined test is green. Note: If the expression involves hostnames with a character that is also an operator - e.g. if you have a host "t1-router-newyork.foo.com" with a dash in the hostname - then the operator-character must be escaped with a backslash '\\' in the expression, or it will be interpreted as an operator. E.g. like this: nyc.conn = (t1\\-router\\-nyc.conn || backup\\-router\\-nyc.conn) .SH EXAMPLE WebCluster.http = (Web1.http || Web2.http) .br AppSrvCluster.procs = (AppSrv1.conn && AppSrv1.procs) || (AppSrv2.conn && AppSrv2.procs) .br Customer.cluster = WebCluster.http && AppSrvCluster.procs .br The first line defines a new test, with hostname "WebCluster" and the columnname "http". It will be green if the http test on either the "Web1" or the "Web2" server is green. The second line defines a "procs" test for the "AppSrvCluster" host. Each of the AppSrv1 and AppSrv2 hosts is checked for "conn" (ping) and their "procs" test. On each host, both of these must be green, but the combined test is green if that condition is fulfilled on just one of the hosts. The third line uses the two first tests to build a "double combined" test, defining a test that shows the overall health of the system. .SH FILES .BR "$XYMONHOME/etc/combo.cfg" .SH "SEE ALSO" combostatus(1) xymon-4.3.7/xymond/client-local.cfg.50000664000175000017500000001517511671641417016754 0ustar henrikhenrik.TH CLIENT-LOCAL.CFG 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME client-local.cfg \- Local configuration settings for Xymon clients .SH SYNOPSIS .B ~xymon/server/etc/client-local.cfg .SH DESCRIPTION The client-local.cfg file contains settings that are used by each Xymon client when it runs on a monitored host. It provides a convenient way of configuring clients from a central location without having to setup special configuration maintenance tools on all clients. The client-local.cfg file is currently used to configure what logfiles the client should fetch data from, to be used as the basis for the "msgs" status column; and to configure which files and directories are being monitored in the "files" status column. Note that there is a dependency between the client-local.cfg file and the .I anaysis.cfg(5) file. When monitoring e.g. a logfile, you must first enter it into the client-local.cfg file, to trigger the Xymon client into reporting any data about the logfile. Next, you must configure analysis.cfg so the Xymon server knows what to look for in the file data sent by the client. So: client-local.cfg defines what raw data is collected by the client, and analysis.cfg defines how to analyze them. .SH PROPAGATION TO CLIENTS The client-local.cfg file resides on the Xymon server. When clients connect to the Xymon server to send in their client data, they will receive part of this file back from the Xymon server. The configuration received by the client is then used the next time the client runs. This method of propagating the configuration means that there is a delay of up to two poll cycles (i.e. 5-10 minutes) from a configuration change is entered into the client-local.cfg file, and until you see the result in the status messages reported by the client. .SH FILE FORMAT The file is divided into sections, delimited by "[name]" lines. A section name can be either an operating system identifier - linux, solaris, hp-ux, aix, freebsd, openbsd, netbsd, darwin - or a hostname. When deciding which section to send to a client, Xymon will first look for a section named after the hostname of the client; if such a section does not exist, it will look for a section named by the operating system of the client. So you can configure special configurations for individual hosts, and have a default configuration for all other hosts of a certain type. Apart from the section delimiter, the file format is free-form, or rather it is defined by the tools that make use of the configuration. .SH LOGFILE CONFIGURATION ENTRIES A logfile configuration entry looks like this: .sp log:/var/log/messages:10240 .br ignore MARK .br trigger Oops .sp The \fBlog:FILENAME:SIZE\fR line defines the filename of the log, and the maximum amount of data (in bytes) to send to the Xymon server. FILENAME is usually an explicit full-path filename on the client. If it is enclosed in backticks, it is a command which the Xymon client runs and each line of output from this command is then used as a filename. This allows scripting which files to monitor, e.g. if you have logfiles that are named with some sort of timestamp. .sp The \fBignore PATTERN\fR line (optional) defines lines in the logfile which are ignored entirely, i.e. they are stripped from the logfile data before sending it to the Xymon server. It is used to remove completely unwanted "noise" entries from the logdata processed by Xymon. "PATTERN" is a regular expression. .sp The \fBtrigger PATTERN\fR line (optional) is used only when there is more data in the log than the maximum size set in the "log:FILENAME:SIZE" line. The "trigger" pattern is then used to find particularly interesting lines in the logfile - these will always be sent to the Xymon server. After picking out the "trigger" lines, any remaining space up to the maximum size is filled in with the most recent entries from the logfile. "PATTERN" is a regular expression. .SH COUNTING LOGENTRIES A special type of log-handling is possible, where the number of lines matching a regular expressions are merely counted. This is \fBlinecount:FILENAME\fR, followed by a number of lines of the form \fBID:PATTERN\fR. E.g. .sp linecount:/var/log/messages .br diskerrors:I/O error.*device.*hd .br badlogins:Failed login .sp .SH FILE CONFIGURATION ENTRIES A file monitoring entry is used to watch the meta-data of a file: Owner, group, size, permissions, checksum etc. It looks like this: .sp file:/var/log/messages[:HASH] .sp The \fBfile:FILENAME\fR line defines the filename of the file to monitor. As with the "log:" entries, a filename enclosed in backticks means a command which will generate the filenames dynamically. The optional [:HASH] setting defines what type of hash to compute for the file: \fBmd5\fR, \fBsha1\fR or \fBrmd160\fR. By default, no hash is calculated. .br \fBNOTE:\fR If you want to check multiple files using a wildcard, you \fBmust\fR use a command to generate the filenames. Putting wildcards directly into the \fBfile:\fR entry will not work. .SH DIRECTORY CONFIGURATION ENTRIES A directory monitoring entry is used to watch the size of a directory and any sub-directories. It looks like this: .sp dir:DIRECTORYNAME .sp The \fBdir:DIRECTORYNAME\fR line defines the filename of the file to monitor. As with the "log:" entries, a filename enclosed in backticks means a command which will generate the filenames dynamically. The Xymon client will run the .I du(1) command with the directoryname as parameter, and send the output back to the Xymon server. .br \fBNOTE:\fR If you want to check multiple directories using a wildcard, you \fBmust\fR use a command to generate the directory names. Putting wildcards directly into the \fBdir:\fR entry will not work. E.g. use something like .br dir:`find /var/log -maxdepth 1 -type d` The "du" command used can be configured through the \fBDU\fR environment variable. On some systems, by default \fBdu\fR reports data in disk blocks instead of KB (e.g. Solaris). So you may want to configure the Xymon client to use a \fBdu\fR command which reports data in KB, e.g. by setting .br DU="du -k" .br in the xymonclient.cfg file. .SH NOTES The ability of the Xymon client to calculate file hashes and monitor those can be used for file integrity validation on a small scale. However, there is a significant processing overhead in calculating these every time the Xymon client runs, so this should not be considered a replacement for host-based intrusion detection systems such as Tripwire or AIDE. Use of the directory monitoring on directory structures with a large number of files and/or sub-directories can be quite ressource-intensive. .SH "SEE ALSO" analysis.cfg(5), xymond_client(8), xymond(8), xymon(7) xymon-4.3.7/xymond/xymond_worker.h0000664000175000017500000000224511615341300016616 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __XYMOND_WORKER_H__ #define __XYMOND_WORKER_H__ #include #include "xymond_ipc.h" typedef void (update_fn_t)(char *); extern int net_worker_option(char *arg); extern int net_worker_locatorbased(void); extern void net_worker_run(enum locator_servicetype_t svc, enum locator_sticky_t sticky, update_fn_t *updfunc); extern unsigned char *get_xymond_message(enum msgchannels_t chnid, char *id, int *seq, struct timespec *timeout); #endif xymon-4.3.7/xymond/new-daemon.txt0000664000175000017500000001731711535462534016355 0ustar henrikhenrik* New bbd implements a combined BBDISPLAY+BBPAGER. Running a separate BBPAGER is not supported. * "disable" messages sets the internal status color "blue" and logs info about the cause and duration. * "enable" messages simply clears the flag, so new messages are accepted. * bbd sets up a number of channels: - "status" : Receives the contents of all "status" and "summary messages. - "stachg" : Receives notice of all status messages where the color changes from the previous status. - "page" : Receives notice of all status messages where the color changes between a non-alert color and an alert color, or when it changes from one alert-color to another. Also receives notice of "ack" messages. - "data" : Receives all "data" messages. - "notes" : Receives all "notes" messages. - "enadis" : Receives all "enable" and "disable" messages. * A number of "worker" tasks run to handle I/O and other heavy tasks: - statuslog: Subscribes to "status". Updates bbvar/logs/ files. - htmllog : Subscribes to "status". Updates bb/www/html/ files. - history : Subscribes to "stachg". Updates bbvar/hist/allevents, bbvar/hist/{HOST,HOST.TEST}, bbvar/histlogs/HOST/TEST/* - datalog : Subscribes to "data". Updates bbvar/data/ files. - infolog : Subscribes to "notes". Updates bbvar/notes/ files. - enalog : Subscribes to "enadis". Updates bbvar/disabled/ files. - alert : Subscribes to "page". Sends out alerts. - larrd*2 : Subscribes to "status" and "data". Updates LARRD RRD's. * Channel protocols status ------ @@status|timestamp|sender|origin|hostname|testname|expiretime|color|flags|prevcolor|changetime|ackexpiretime|ackmessage|disableexpiretime|disablemessage @@ stachg ------ @@stachg|timestamp|sender|origin|hostname|testname|expiretime|color|prevcolor|changetime @@ page ---- @@page|timestamp|sender|hostname|testname|hostip|expiretime|color|prevcolor|changetime|location|cookie @@ @@ack|timestamp|sender|hostname|testname|hostip|expiretime @@ Note that "page" modules get messages whenever the alert-state of a test changes. I.e. a message is generated whenever a test goes from a color that is non-alerting to a color that is alerting, or vice versa. How does the pager know when a test is disabled ? It will get a "page" message with color=blue, if the old color of the test was in an alert state. (If it wasn't, the pager module does not need to know that the test has been disabled). It should then clear any stored info about active alerts for this host.test combination. data ---- @@data|timestamp|origin|sender|hostname|testname @@ notes ----- @@notes|timestamp|sender|hostname @@ enadis ----- @@enadis|timestamp|sender|hostname|testname|expiretime @@ "expiretime" is 0 for an "enable" message, >0 for a "disable" message. All channels ------------ @@drophost|timestamp|sender|hostname @@droptest|timestamp|sender|hostname|testname @@renamehost|timestamp|sender|hostname|newhostname @@renametest|timestamp|sender|hostname|oldtestname|newtestname @@shutdown|timestamp|sender * Simplified "ack" protocol. - bbd issues a "cookie" when a test goes into an alert state. The cookie is valid for as long as the status message is valid (default: 30 minutes). When it expires, a new cookie is issued if the status is still an alert-state. The cookie applies to the status log and is indifferent about who receives alerts. - Cookies are random 6-digit numbers. bbd keeps track of issued cookies and what host.test combination they relate to. - "bbgendack cookie duration [ack message]" checks the cookie, and if valid the ack-flag is set on that status-log. This message is forwarded on the "page" channel so alert-modules know if the status has been ack'ed. - A negative cookie acts as an acknowledge of all pending alerts for the host. - The old "ack ack_event" message is supported. * "bbgendlog HOST.TEST" request returns the current status-log for a host.test combination. hostname|testname|color|testflags|lastchange|logtime|validtime|acktime|disabletime|sender|cookie|ackmsg|dismsg message * "bbgendboard" request returns 1 line per statuslog with a summary of the status, i.e. all information except the text part of the status log. Also yiels the "meta" information such as sender, time since last status change etc. Anything that was previously determined by scanning the files in bbvar/logs/ hostname|testname|color|testflags|lastchange|logtime|validtime|acktime|disabletime|sender|cookie|1st line of message * "bbgenddrop HOSTNAME [TESTNAME]" drops all information about a host, or a host.test combination. This propagates a "drophost" / "droptest" message to all workers, so they can delete permanent storage. This will enable a simple "bbrm" implementation. * "bbgendrename OLDHOSTNAME NEWHOSTNAME" renames a host. This propagates a "renamehost" message to all workers. * "bbgendrename HOSTNAME OLDTEST NEWTEST" renames a test. This propagates a "renametest" message to all workers. * Master/worker communications - Uses a single shmem segment as a bulletin board for new messages. - Communication synchronized with two semaphores: BOARDBUSY and GOCLIENT. - Number of clients found via registering on a CLIENTCOUNT semaphore (clients up this when registering, and down it when they terminate). - Sequence of events is as follows: MASTER CLIENT 1 CLIENT 2 Wait BOARDBUSY = 0 Down GOCLIENT Down GOCLIENT (block until > 0) (block until > 0) Up BOARDBUSY (+CLIENTCOUNT) Up GOCLIENT (+CLIENTCOUNT) Copy message Copy message Wait GOCLIENT=0 Wait GOCLIENT=0 Down BOARDBUSY Down BOARDBUSY The client wait (GOCLIENT=0) is necessary to prevent one client from snatching a message on behalf of the other clients (looping with the same message multiple times). * Environment settings - bbd_filestore - XYMONRAWSTATUSDIR : Default "--dir" when run with --status - XYMONDATADIR : Default "--dir" when run with --data - XYMONNOTESDIR : Default "--dir" when run with --notes * Environment settings - bbd_history - XYMONALLHISTLOG : If TRUE, the "allevents" file is updated. - XYMONHOSTHISTLOG : If TRUE, the "hostname" host events file is updated - XYMONHISTDIR : Directory for the allevents, host- and status-event logs - SAVESTATUSLOG : If TRUE, the historical full status log is stored - XYMONHISTLOGS : Top level directory for the historical full status logs * Environment settings - bbd_net - HOSTSCFG : Default "--hosts" value * Environment settings - bb-hostsvc.cgi - LARRDS : Comma-separated list of statuses that have a LARRD graph - NONHISTS : Comma-separated list of statuses with no History button - USEBBGEND : If TRUE, get log via the bbgend net-interface instead of files. - CGIBINURL - XYMONWEB - XYMONRAWSTATUSDIR - XYMONHISTLOGS - XYMONSKIN - DOTHEIGHT - DOTWIDTH * bbgend_larrd : Not compatible with - vmstat : All OS ? - netstat : Linux, snmpnetstat, Win32 * Implement a new "streaming" protocol that uses a persistent TCP connection to receive messages. The format is kept as-is, and a NUL byte is used to separate one message from the next. * Use xymonproxy to receive messages in "legacy" (non-streaming) format. xymonproxy then implements the client-side part of the new streaming protocol. * The new bbd only accepts stream messages. xymon-4.3.7/xymond/combostatus.c0000664000175000017500000003005011665413763016262 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon combination test tool. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: combostatus.c 6780 2011-11-30 11:48:03Z storner $"; #include #include #include #include #include #include #include #include #include #include "version.h" #include "libxymon.h" typedef struct value_t { char *symbol; int color; struct value_t *next; } value_t; typedef struct testspec_t { char *reshostname; char *restestname; char *expression; char *comment; char *resultexpr; value_t *valuelist; long result; char *errbuf; struct testspec_t *next; } testspec_t; static testspec_t *testhead = NULL; static int testcount = 0; static int errorcolors = (1 << COL_RED); static char *gethname(char *spec) { static char *result = NULL; char *p; if (result) xfree(result); /* grab the hostname part from a "www.xxx.com.testname" string */ p = strrchr(spec, '.'); if (!p) { errprintf("Item '%s' has no testname part\n", spec); return NULL; } *p = '\0'; result = strdup(spec); *p = '.'; return result; } static char *gettname(char *spec) { static char *result = NULL; char *p; if (result) xfree(result); /* grab the testname part from a "www.xxx.com.testname" string */ p = strrchr(spec, '.'); if (!p) { errprintf("Item '%s' has no testname part\n", spec); return NULL; } result = strdup(p+1); return result; } static void flush_valuelist(value_t *head) { value_t *walk, *zombie; walk = head; while (walk) { zombie = walk; walk = walk->next; xfree(zombie->symbol); xfree(zombie); } } static void flush_testlist(void) { testspec_t *walk, *zombie; walk = testhead; while (walk) { zombie = walk; walk = walk->next; if (zombie->reshostname) xfree(zombie->reshostname); if (zombie->restestname) xfree(zombie->restestname); if (zombie->expression) xfree(zombie->expression); if (zombie->comment) xfree(zombie->comment); if (zombie->resultexpr) xfree(zombie->resultexpr); if (zombie->errbuf) xfree(zombie->errbuf); flush_valuelist(zombie->valuelist); xfree(zombie); } testhead = NULL; testcount = 0; } static void loadtests(void) { static time_t lastupdate = 0; static char *fn = NULL; struct stat st; FILE *fd; strbuffer_t *inbuf; if (!fn) { fn = (char *)malloc(1024 + strlen(xgetenv("XYMONHOME"))); *fn = '\0'; } sprintf(fn, "%s/etc/combo.cfg", xgetenv("XYMONHOME")); if ((stat(fn, &st) == 0) && (st.st_mtime == lastupdate)) return; lastupdate = st.st_mtime; fd = stackfopen(fn, "r", NULL); if (fd == NULL) { errprintf("Cannot open %s/combo.cfg\n", xgetenv("XYMONHOME")); *fn = '\0'; return; } flush_testlist(); inbuf = newstrbuffer(0); while (stackfgets(inbuf, NULL)) { char *p, *comment; char *inp, *outp; p = strchr(STRBUF(inbuf), '\n'); if (p) *p = '\0'; /* Strip whitespace */ for (inp=outp=STRBUF(inbuf); ((*inp >= ' ') && (*inp != '#')); inp++) { if (isspace((int)*inp)) { } else { *outp = *inp; outp++; } } *outp = '\0'; if (strlen(inp)) memmove(outp, inp, strlen(inp)+1); strbufferrecalc(inbuf); if (STRBUFLEN(inbuf) && (*STRBUF(inbuf) != '#') && (p = strchr(STRBUF(inbuf), '=')) ) { testspec_t *newtest; char *hname, *tname; hname = gethname(STRBUF(inbuf)); tname = gettname(STRBUF(inbuf)); if (hname && tname) { *p = '\0'; comment = strchr(p+1, '#'); if (comment) *comment = '\0'; newtest = (testspec_t *) malloc(sizeof(testspec_t)); newtest->reshostname = strdup(gethname(STRBUF(inbuf))); newtest->restestname = strdup(gettname(STRBUF(inbuf))); newtest->expression = strdup(p+1); newtest->comment = (comment ? strdup(comment+1) : NULL); newtest->resultexpr = NULL; newtest->valuelist = NULL; newtest->result = -1; newtest->errbuf = NULL; newtest->next = testhead; testhead = newtest; testcount++; } else { errprintf("Invalid combo test %s - missing host/test names. Perhaps you need to escape dashes?\n", STRBUF(inbuf)); } } } stackfclose(fd); freestrbuffer(inbuf); } static int getxymondvalue(char *hostname, char *testname, char **errptr) { static char *board = NULL; int xymondresult; int result = COL_CLEAR; char *pattern, *found, *colstr; if (board == NULL) { sendreturn_t *sres = newsendreturnbuf(1, NULL); xymondresult = sendmessage("xymondboard fields=hostname,testname,color", NULL, XYMON_TIMEOUT, sres); board = getsendreturnstr(sres, 1); if ((xymondresult != XYMONSEND_OK) || (board == NULL)) { board = ""; *errptr += sprintf(*errptr, "Could not access xymond board, error %d\n", xymondresult); return COL_CLEAR; } freesendreturnbuf(sres); } pattern = (char *)malloc(1 + strlen(hostname) + 1 + strlen(testname) + 1 + 1); sprintf(pattern, "\n%s|%s|", hostname, testname); if (strncmp(board, pattern+1, strlen(pattern+1)) == 0) { /* The first entry in the board doesn't have the "\n" */ found = board; } else { found = strstr(board, pattern); } if (found) { /* hostname|testname|color */ colstr = found + strlen(pattern); result = parse_color(colstr); } xfree(pattern); return result; } static long getvalue(char *hostname, char *testname, int *color, char **errbuf) { testspec_t *walk; char errtext[1024]; char *errptr; *color = -1; errptr = errtext; *errptr = '\0'; /* First check if it is one of our own tests */ for (walk = testhead; (walk && ( (strcmp(walk->reshostname, hostname) != 0) || (strcmp(walk->restestname, testname) != 0) ) ); walk = walk->next); if (walk != NULL) { /* It is a combo test they want the result of. */ return walk->result; } *color = getxymondvalue(hostname, testname, &errptr); /* Save error messages */ if (strlen(errtext) > 0) { if (*errbuf == NULL) *errbuf = strdup(errtext); else { *errbuf = (char *)realloc(*errbuf, strlen(*errbuf)+strlen(errtext)+1); strcat(*errbuf, errtext); } } if (*color == -1) return -1; else return (((1 << *color) & errorcolors) == 0); } static long evaluate(char *symbolicexpr, char **resultexpr, value_t **valuelist, char **errbuf) { char expr[MAX_LINE_LEN]; char *inp, *outp, *symp; char symbol[MAX_LINE_LEN]; int done; int insymbol = 0; int result, error; long oneval; int onecolor; value_t *valhead = NULL, *valtail = NULL; value_t *newval; char errtext[1024]; done = 0; inp=symbolicexpr; outp=expr; symp = NULL; while (!done) { if (isalpha((int)*inp)) { if (!insymbol) { insymbol = 1; symp = symbol; } *symp = *inp; symp++; } else if (insymbol && (isdigit((int) *inp) || (*inp == '.'))) { *symp = *inp; symp++; } else if (insymbol && ((*inp == '\\') && (*(inp+1) > ' '))) { *symp = *(inp+1); symp++; inp++; } else { if (insymbol) { /* Symbol finished - evaluate the symbol */ char *hname, *tname; *symp = '\0'; insymbol = 0; hname = gethname(symbol); tname = gettname(symbol); if (hname && tname) { oneval = getvalue(gethname(symbol), gettname(symbol), &onecolor, errbuf); } else { errprintf("Invalid data for symbol calculation - missing host/testname: %s\n", symbol); oneval = 0; onecolor = COL_CLEAR; } sprintf(outp, "%ld", oneval); outp += strlen(outp); newval = (value_t *) malloc(sizeof(value_t)); newval->symbol = strdup(symbol); newval->color = onecolor; newval->next = NULL; if (valhead == NULL) { valtail = valhead = newval; } else { valtail->next = newval; valtail = newval; } } *outp = *inp; outp++; symp = NULL; } if (*inp == '\0') done = 1; else inp++; } *outp = '\0'; if (resultexpr) *resultexpr = strdup(expr); dbgprintf("Symbolic '%s' converted to '%s'\n", symbolicexpr, expr); error = 0; result = compute(expr, &error); if (error) { sprintf(errtext, "compute(%s) returned error %d\n", expr, error); if (*errbuf == NULL) { *errbuf = strdup(errtext); } else { *errbuf = (char *)realloc(*errbuf, strlen(*errbuf)+strlen(errtext)+1); strcat(*errbuf, errtext); } } *valuelist = valhead; return result; } static char *printify(char *exp, int cleanexpr) { static char result[MAX_LINE_LEN]; char *inp, *outp; size_t n; if (!cleanexpr) { return exp; } inp = exp; outp = result; while (*inp) { n = strcspn(inp, "|&"); memcpy(outp, inp, n); inp += n; outp += n; if (*inp == '|') { inp++; if (*inp == '|') { inp++; strcpy(outp, " OR "); outp += 4; } else { strcpy(outp, " bOR "); outp += 5; } } else if (*inp == '&') { inp++; if (*inp == '&') { inp++; strcpy(outp, " AND "); outp += 5; } else { strcpy(outp, " bAND "); outp += 6; } } } *outp = '\0'; return result; } int update_combotests(int showeval, int cleanexpr) { testspec_t *t; int pending; int remaining = 0; init_timestamp(); loadtests(); /* * Loop over the tests to allow us "forward refs" in expressions. * We continue for as long as progress is being made. */ remaining = testcount; do { pending = remaining; for (t=testhead; (t); t = t->next) { if (t->result == -1) { t->result = evaluate(t->expression, &t->resultexpr, &t->valuelist, &t->errbuf); if (t->result != -1) remaining--; } } } while (pending != remaining); combo_start(); for (t=testhead; (t); t = t->next) { char msgline[MAX_LINE_LEN]; int color; value_t *vwalk; color = (t->result ? COL_GREEN : COL_RED); init_status(color); sprintf(msgline, "status %s.%s %s %s\n\n", commafy(t->reshostname), t->restestname, colorname(color), timestamp); addtostatus(msgline); if (t->comment) { addtostatus(t->comment); addtostatus("\n\n"); } if (showeval) { addtostatus(printify(t->expression, cleanexpr)); addtostatus(" = "); addtostatus(printify(t->resultexpr, cleanexpr)); addtostatus(" = "); sprintf(msgline, "%ld\n", t->result); addtostatus(msgline); for (vwalk = t->valuelist; (vwalk); vwalk = vwalk->next) { sprintf(msgline, "&%s %s\n", colorname(vwalk->color), xgetenv("CGIBINURL"), gethname(vwalk->symbol), gettname(vwalk->symbol), vwalk->symbol); addtostatus(msgline); } if (t->errbuf) { addtostatus("\nErrors occurred during evaluation:\n"); addtostatus(t->errbuf); } } finish_status(); } combo_end(); return 0; } int main(int argc, char *argv[]) { int argi; int showeval = 1; int cleanexpr = 0; setup_signalhandler(argv[0]); for (argi = 1; (argi < argc); argi++) { if ((strcmp(argv[argi], "--help") == 0)) { printf("%s version %s\n\n", argv[0], VERSION); printf("Usage:\n%s [--quiet] [--clean] [--debug] [--no-update]\n", argv[0]); exit(0); } else if ((strcmp(argv[argi], "--version") == 0)) { printf("%s version %s\n", argv[0], VERSION); exit(0); } else if ((strcmp(argv[argi], "--debug") == 0)) { debug = 1; } else if ((strcmp(argv[argi], "--no-update") == 0)) { dontsendmessages = 1; } else if ((strcmp(argv[argi], "--quiet") == 0)) { showeval = 0; } else if ((strcmp(argv[argi], "--clean") == 0)) { cleanexpr = 1; } else if ((strncmp(argv[argi], "--error-colors=", 15) == 0)) { char *tok; int newerrorcolors = 0; tok = strtok(strchr(argv[argi], '=')+1, ","); while (tok) { int col = parse_color(tok); if ((col >= 0) && (col <= COL_RED)) newerrorcolors |= (1 << parse_color(tok)); tok = strtok(NULL, ","); } if (newerrorcolors) errorcolors = newerrorcolors; } } return update_combotests(showeval, cleanexpr); } xymon-4.3.7/xymond/xymond_worker.c0000664000175000017500000004373311615341300016620 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* This is a small library for xymond worker modules, to read a new message */ /* from the xymond_channel process, and also do the decoding of messages */ /* that are passed on the "meta-data" first line of such a message. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond_worker.c 6712 2011-07-31 21:01:52Z storner $"; #include "config.h" #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include /* Someday I'll move to GNU Autoconf for this .. . */ #endif #include #include #include #include #include #include "libxymon.h" #include "xymond_ipc.h" #include "xymond_worker.h" #include static int running = 1; static int inputfd = STDIN_FILENO; #define EXTRABUFSPACE 4095 static char *locatorlocation = NULL; static char *locatorid = NULL; static enum locator_servicetype_t locatorsvc = ST_MAX; static int locatorweight = 1; static char *locatorextra = NULL; static char *listenipport = NULL; static time_t locatorhb = 0; static void netinp_sighandler(int signum) { switch (signum) { case SIGINT: case SIGTERM: running = 0; break; } } static void net_worker_heartbeat(void) { time_t now; if (!locatorid || (locatorsvc == ST_MAX)) return; now = gettimer(); if (now > locatorhb) { locator_serverup(locatorid, locatorsvc); locatorhb = now + 60; } } static int net_worker_listener(char *ipport) { /* * Setup a listener socket on IP+port. When a connection arrives, * pick it up, fork() and let the rest of the input go via the * network socket. */ char *listenip, *p; int listenport = 0; int lsocket = -1; struct sockaddr_in laddr; struct sigaction sa; int opt; listenip = ipport; p = strchr(listenip, ':'); if (p) { *p = '\0'; listenport = atoi(p+1); } if (listenport == 0) { errprintf("Must include PORT number in --listen=IP:PORT option\n"); return -1; } /* Set up a socket to listen for new connections */ errprintf("Setting up network listener on %s:%d\n", listenip, listenport); memset(&laddr, 0, sizeof(laddr)); if ((strlen(listenip) == 0) || (strcmp(listenip, "0.0.0.0") == 0)) { listenip = "0.0.0.0"; laddr.sin_addr.s_addr = INADDR_ANY; } else if (inet_aton(listenip, (struct in_addr *) &laddr.sin_addr.s_addr) == 0) { /* Not an IP */ errprintf("Listener IP must be an IP-address, not hostname\n"); return -1; } laddr.sin_port = htons(listenport); laddr.sin_family = AF_INET; lsocket = socket(AF_INET, SOCK_STREAM, 0); if (lsocket == -1) { errprintf("Cannot create listen socket (%s)\n", strerror(errno)); return -1; } opt = 1; setsockopt(lsocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (bind(lsocket, (struct sockaddr *)&laddr, sizeof(laddr)) == -1) { errprintf("Cannot bind to listen socket (%s)\n", strerror(errno)); return -1; } if (listen(lsocket, 5) == -1) { errprintf("Cannot listen (%s)\n", strerror(errno)); return -1; } /* Make listener socket non-blocking, so we can send keep-alive's while waiting for connections */ fcntl(lsocket, F_SETFL, O_NONBLOCK); /* Catch some signals */ setup_signalhandler("xymond_listener"); memset(&sa, 0, sizeof(sa)); sa.sa_handler = netinp_sighandler; sigaction(SIGCHLD, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); while (running) { struct sockaddr_in netaddr; int addrsz, sock; int childstat; pid_t childpid; fd_set fdread; struct timeval tmo; int n; FD_ZERO(&fdread); FD_SET(lsocket, &fdread); tmo.tv_sec = 60; tmo.tv_usec = 0; n = select(lsocket+1, &fdread, NULL, NULL, &tmo); if (n == -1) { if (errno != EINTR) { errprintf("select() failed while waiting for connection : %s\n", strerror(errno)); running = 0; } } else if (n == 0) { /* Timeout */ net_worker_heartbeat(); } else { /* We have a connection ready */ addrsz = sizeof(netaddr); sock = accept(lsocket, (struct sockaddr *)&netaddr, &addrsz); if (sock >= 0) { /* Got a new connection */ childpid = fork(); if (childpid == 0) { /* Child takes input from the new socket, and starts working */ close(lsocket); /* Close the listener socket */ inputfd = sock; return 0; } else if (childpid > 0) { /* Parent closes the new socket (child has it) */ close(sock); continue; } else { errprintf("Error forking worker for new connection: %s\n", strerror(errno)); running = 0; continue; } } else { /* Error while waiting for accept() to complete */ if (errno != EINTR) { errprintf("accept() failed: %s\n", strerror(errno)); running = 0; } } } /* Pickup failed children */ while ((childpid = wait3(&childstat, WNOHANG, NULL)) > 0); } /* Close the listener socket */ close(lsocket); /* Kill any children that are still around */ kill(0, SIGTERM); return 1; } int net_worker_option(char *arg) { int res = 1; if (argnmatch(arg, "--locator=")) { char *p = strchr(arg, '='); locatorlocation = strdup(p+1); } else if (argnmatch(arg, "--locatorid=")) { char *p = strchr(arg, '='); locatorid = strdup(p+1); } else if (argnmatch(arg, "--locatorweight=")) { char *p = strchr(arg, '='); locatorweight = atoi(p+1); } else if (argnmatch(arg, "--locatorextra=")) { char *p = strchr(arg, '='); locatorextra = strdup(p+1); } else if (argnmatch(arg, "--listen=")) { char *p = strchr(arg, '='); listenipport = strdup(p+1); } else { res = 0; } return res; } int net_worker_locatorbased(void) { return ((locatorsvc != ST_MAX) && listenipport && locatorlocation); } void net_worker_run(enum locator_servicetype_t svc, enum locator_sticky_t sticky, update_fn_t *updfunc) { locatorsvc = svc; if (listenipport) { char *p; struct in_addr dummy; if (!locatorid) locatorid = strdup(listenipport); p = strchr(locatorid, ':'); if (p == NULL) { errprintf("Locator ID must be IP:PORT matching the listener address\n"); exit(1); } *p = '\0'; if (inet_aton(locatorid, &dummy) == 0) { errprintf("Locator ID must be IP:PORT matching the listener address\n"); exit(1); } *p = ':'; } if (listenipport && locatorlocation) { int res; int delay = 10; /* Tell the world we're here */ while (locator_init(locatorlocation) != 0) { errprintf("Locator unavailable, waiting for it to be ready\n"); sleep(delay); if (delay < 240) delay *= 2; } locator_register_server(locatorid, svc, locatorweight, sticky, locatorextra); if (updfunc) (*updfunc)(locatorid); /* Launch the network listener and wait for incoming connections */ res = net_worker_listener(listenipport); /* * Return value is: * -1 : Error in setup. Abort. * 0 : New connection arrived, and this is now a forked worker process. Continue. * 1 : Listener terminates. Exit normally. */ if (res == -1) { errprintf("Listener setup failed, aborting\n"); locator_serverdown(locatorid, svc); exit(1); } else if (res == 1) { errprintf("xymond_listener listener terminated\n"); locator_serverdown(locatorid, svc); exit(0); } else { /* Worker process started. Return from here causes worker to start. */ } } else if (listenipport || locatorlocation || locatorid) { errprintf("Must specify all of --listen, --locator and --locatorid\n"); exit(1); } } unsigned char *get_xymond_message(enum msgchannels_t chnid, char *id, int *seq, struct timespec *timeout) { static unsigned int seqnum = 0; static char *idlemsg = NULL; static char *buf = NULL; static size_t bufsz = 0; static size_t maxmsgsize = 0; static int ioerror = 0; static char *startpos; /* Where our unused data starts */ static char *endpos; /* Where the first message ends */ static char *fillpos; /* Where our unused data ends (the \0 byte) */ int truncated = 0; struct timespec cutoff; int maymove, needmoredata; char *endsrch; /* Where in the buffer do we start looking for the end-message marker */ char *result; /* * The way this works is to read data from stdin into a * buffer. Each read fetches as much data as possible, * i.e. all that is available up to the amount of * buffer space we have. * * When the buffer contains a complete message, * we return a pointer to the message. * * Since a read into the buffer can potentially * fetch multiple messages, we need to keep track of * the start/end positions of the next message, and * where in the buffer new data should be read in. * As long as there is a complete message available * in the buffer, we just return that message - only * when there is no complete message do we read data * from stdin. * * A message is normally NOT copied, we just return * a pointer to our input buffer. The only time we * need to shuffle data around is if the buffer * does not have room left to hold a complete message. */ if (buf == NULL) { /* * Initial setup of the buffers. * We allocate a buffer large enough for the largest message * that can arrive on this channel, and add 4KB extra room. * The EXTRABUFSPACE is to allow the memmove() that will be * needed occasionally some room to work optimally. */ maxmsgsize = 1024*shbufsz(chnid); bufsz = maxmsgsize + EXTRABUFSPACE; buf = (char *)malloc(bufsz+1); *buf = '\0'; startpos = fillpos = buf; endpos = NULL; /* idlemsg is used to return the idle message in case of timeouts. */ idlemsg = strdup("@@idle\n"); /* We dont want to block when reading data. */ fcntl(inputfd, F_SETFL, O_NONBLOCK); } /* * If the start of the next message doesn't begin with "@" then * there's something rotten. */ if (*startpos && (*startpos != '@')) { errprintf("Bad data in channel, skipping it\n"); startpos = strstr(startpos, "\n@@"); endpos = (startpos ? strstr(startpos, "\n@@\n") : NULL); if (startpos && (startpos == endpos)) { startpos = endpos + 4; endpos = strstr(startpos, "\n@@\n"); } if (!startpos) { /* We're lost - flush the buffer and try to recover */ errprintf("Buffer sync lost, flushing data\n"); *buf = '\0'; startpos = fillpos = buf; endpos = NULL; } seqnum = 0; /* After skipping, we dont know what to expect */ } startagain: if (ioerror) { errprintf("get_xymond_message: Returning NULL due to previous i/o error\n"); return NULL; } if (timeout) { /* Calculate when the read should timeout. */ getntimer(&cutoff); cutoff.tv_sec += timeout->tv_sec; cutoff.tv_nsec += timeout->tv_nsec; if (cutoff.tv_nsec > 1000000000) { cutoff.tv_sec += 1; cutoff.tv_nsec -= 1000000000; } } /* * Start looking for the end-of-message marker at the beginning of * the message. The next scans will only look at the new data we've * got when reading data in. */ endsrch = startpos; /* * See if the current available buffer space is enough to hold a full message. * If not, then flag that we may do a memmove() of the buffer data. */ maymove = ((startpos + maxmsgsize) >= (buf + bufsz)); /* We only need to read data, if we do not have an end-of-message marker */ needmoredata = (endpos == NULL); while (needmoredata) { /* Fill buffer with more data until we get an end-of-message marker */ struct timespec now, tmo; struct timeval selecttmo; fd_set fdread; int res; size_t bufleft = bufsz - (fillpos - buf); size_t usedbytes = (fillpos - startpos); dbgprintf("Want msg %d, startpos %ld, fillpos %ld, endpos %ld, usedbytes=%ld, bufleft=%ld\n", (seqnum+1), (startpos-buf), (fillpos-buf), (endpos ? (endpos-buf) : -1), usedbytes, bufleft); if (usedbytes >= maxmsgsize) { /* Over-size message. Truncate it. */ errprintf("Got over-size message, truncating at %d bytes (max: %d)\n", usedbytes, maxmsgsize); endpos = startpos + usedbytes - 5; memcpy(endpos, "\n@@\n", 4); /* Simulate end-of-message and flush data */ needmoredata = 0; truncated = 1; } if (needmoredata) { if (maymove && (bufleft < EXTRABUFSPACE)) { /* Buffer is almost full - move data to accomodate a large message. */ dbgprintf("Moving %d bytes to start of buffer\n", usedbytes); memmove(buf, startpos, usedbytes); startpos = buf; fillpos = startpos + usedbytes; *fillpos = '\0'; endsrch = (usedbytes >= 4) ? (fillpos - 4) : startpos; maymove = 0; bufleft = bufsz - (fillpos - buf); } if (timeout) { /* How long time until the timeout ? */ getntimer(&now); selecttmo.tv_sec = cutoff.tv_sec - now.tv_sec; selecttmo.tv_usec = (cutoff.tv_nsec - now.tv_nsec) / 1000; if (selecttmo.tv_usec < 0) { selecttmo.tv_sec--; selecttmo.tv_usec += 1000000; } } FD_ZERO(&fdread); FD_SET(inputfd, &fdread); res = select(inputfd+1, &fdread, NULL, NULL, (timeout ? &selecttmo : NULL)); if (res < 0) { if (errno == EAGAIN) continue; if (errno == EINTR) { dbgprintf("get_xymond_message: Interrupted\n"); *seq = 0; return idlemsg; } /* Some error happened */ ioerror = 1; dbgprintf("get_xymond_message: Returning NULL due to select error %s\n", strerror(errno)); return NULL; } else if (res == 0) { /* * Timeout - return the "idle" message. * NB: If select() was not passed a timeout parameter, this cannot trigger */ *seq = 0; return idlemsg; } else if (FD_ISSET(inputfd, &fdread)) { res = read(inputfd, fillpos, bufleft); if (res < 0) { if ((errno == EAGAIN) || (errno == EINTR)) continue; ioerror = 1; dbgprintf("get_xymond_message: Returning NULL due to read error %s\n", strerror(errno)); return NULL; } else if (res == 0) { /* read() returns 0 --> End-of-file */ ioerror = 1; dbgprintf("get_xymond_message: Returning NULL due to EOF\n"); return NULL; } else { /* * Got data - null-terminate it, and update fillpos */ dbgprintf("Got %d bytes\n", res); *(fillpos+res) = '\0'; fillpos += res; /* Did we get an end-of-message marker ? Then we're done. */ endpos = strstr(endsrch, "\n@@\n"); needmoredata = (endpos == NULL); /* * If not done, update endsrch. We need to look at the * last 3 bytes of input we got - they could be "\n@@" so * all that is missing is the final "\n". */ if (needmoredata && (res >= 3)) endsrch = fillpos-3; } } } } /* We have a complete message between startpos and endpos */ result = startpos; *endpos = '\0'; if (truncated) { startpos = fillpos = buf; endpos = NULL; } else { startpos = endpos+4; /* +4 because we skip the "\n@@\n" end-marker from the previous message */ endpos = strstr(startpos, "\n@@\n"); /* To see if we already have a full message loaded */ /* fillpos stays where it is */ } /* Check that it really is a message, and not just some garbled data */ if (strncmp(result, "@@", 2) != 0) { errprintf("Dropping (more) garbled data\n"); goto startagain; } if (!locatorid) { /* * Get and check the message sequence number. * We dont do this for network based workers, since the * sequence number is globally generated (by xymond) * but a network-based worker may only see some of the * messages (those that are not handled by other network-based * worker modules). */ char *p = result + strcspn(result, "#/|\n"); if (*p == '#') { *seq = atoi(p+1); if (debug) { p = strchr(result, '\n'); if (p) *p = '\0'; dbgprintf("%s: Got message %u %s\n", id, *seq, result); if (p) *p = '\n'; } if ((seqnum == 0) || (*seq == (seqnum + 1))) { /* First message, or the correct sequence # */ seqnum = *seq; } else if (*seq == seqnum) { /* Duplicate message - drop it */ errprintf("%s: Duplicate message %d dropped\n", id, *seq); goto startagain; } else { /* Out-of-sequence message. Cant do much except accept it */ errprintf("%s: Got message %u, expected %u\n", id, *seq, seqnum+1); seqnum = *seq; } if (seqnum == 999999) seqnum = 0; } } /* Verify checksum - except for truncated messages, where it won't match since we overwrite bytes with the end-marker */ if (result && !truncated) { static struct digestctx_t *ctx = NULL; char *hashstr; if (!ctx) { ctx = (digestctx_t *) malloc(sizeof(digestctx_t)); ctx->digestname = strdup("md5"); ctx->digesttype = D_MD5; ctx->mdctx = (void *)malloc(myMD5_Size()); } hashstr = result + strcspn(result, ":#/|\n"); if (*hashstr == ':') { unsigned char md_value[16]; char md_string[2*16+1]; int i; char *p; myMD5_Init(ctx->mdctx); myMD5_Update(ctx->mdctx, result, (hashstr - result)); myMD5_Update(ctx->mdctx, hashstr + 33, strlen(hashstr + 33)); myMD5_Update(ctx->mdctx, "\n@@\n", 4); /* Stripped earlier */ myMD5_Final(md_value, ctx->mdctx); for(i = 0, p = md_string; (i < sizeof(md_value)); i++) p += sprintf(p, "%02x", md_value[i]); *p = '\0'; if (memcmp(hashstr+1, md_string, 32) != 0) { p = strchr(result, '\n'); if (p) *(p+1) = '\0'; errprintf("get_xymond_message: Invalid checksum, skipping message '%s'\n", result); result = NULL; goto startagain; } } } dbgprintf("startpos %ld, fillpos %ld, endpos %ld\n", (startpos-buf), (fillpos-buf), (endpos ? (endpos-buf) : -1)); return result; } xymon-4.3.7/xymond/xymond_alert.c0000664000175000017500000006667611630732124016436 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* This is the main alert module for xymond. It receives alert messages, */ /* keeps track of active alerts, enable/disable, acks etc., and triggers */ /* outgoing alerts by calling send_alert(). */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ /* * Information from the Xymon docs about "page" modules: * * page * ---- * @@page|timestamp|sender|hostname|testname|expiretime|color|prevcolor|changetime|location * * @@ * * @@ack|timestamp|sender|hostname|testname|expiretime * * @@ * * @@notify|timestamp|sender|hostname|testname|pagepath * * @@ * * Note that "page" modules get messages whenever the alert-state of a test * changes. I.e. a message is generated whenever a test goes from a color * that is non-alerting to a color that is alerting, or vice versa. * * How does the pager know when a test is disabled ? It will get a "page" * message with color=blue, if the old color of the test was in an alert * state. (If it wasn't, the pager module does not need to know that the * test has been disabled). It should then clear any stored info about * active alerts for this host.test combination. */ static char rcsid[] = "$Id: xymond_alert.c 6748 2011-09-04 17:24:36Z storner $"; #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymond_worker.h" #include "do_alert.h" static int running = 1; static time_t nextcheckpoint = 0; static int termsig = -1; void * hostnames; void * testnames; void * locations; typedef struct alertanchor_t { activealerts_t *head; } alertanchor_t; activealerts_t *ahead = NULL; char *statename[] = { /* A_PAGING, A_NORECIP, A_ACKED, A_RECOVERED, A_DISABLED, A_NOTIFY, A_DEAD */ "paging", "norecip", "acked", "recovered", "disabled", "notify", "dead" }; char *find_name(void * tree, char *name) { char *result; xtreePos_t handle; handle = xtreeFind(tree, name); if (handle == xtreeEnd(tree)) { result = strdup(name); if (tree == hostnames) { alertanchor_t *anchor = malloc(sizeof(alertanchor_t)); anchor->head = NULL; xtreeAdd(tree, result, anchor); } else { xtreeAdd(tree, result, result); } } else { result = (char *)xtreeKey(tree, handle); } return result; } void add_active(char *hostname, activealerts_t *rec) { xtreePos_t handle; alertanchor_t *anchor; handle = xtreeFind(hostnames, hostname); if (handle == xtreeEnd(hostnames)) return; anchor = (alertanchor_t *)xtreeData(hostnames, handle); rec->next = anchor->head; anchor->head = rec; } void clean_active(alertanchor_t *anchor) { activealerts_t *newhead = NULL, *tmp, *curr; curr = anchor->head; while (curr) { tmp = curr; curr = curr->next; if (tmp->state == A_DEAD) { if (tmp->osname) xfree(tmp->osname); if (tmp->classname) xfree(tmp->classname); if (tmp->groups) xfree(tmp->groups); if (tmp->pagemessage) xfree(tmp->pagemessage); if (tmp->ackmessage) xfree(tmp->ackmessage); xfree(tmp); } else { tmp->next = newhead; newhead = tmp; } } anchor->head = newhead; } void clean_all_active(void) { xtreePos_t handle; for (handle = xtreeFirst(hostnames); handle != xtreeEnd(hostnames); handle = xtreeNext(hostnames, handle)) { alertanchor_t *anchor = (alertanchor_t *)xtreeData(hostnames, handle); clean_active(anchor); } } activealerts_t *find_active(char *hostname, char *testname) { xtreePos_t handle; alertanchor_t *anchor; char *twalk; activealerts_t *awalk; handle = xtreeFind(hostnames, hostname); if (handle == xtreeEnd(hostnames)) return NULL; anchor = (alertanchor_t *)xtreeData(hostnames, handle); handle = xtreeFind(testnames, testname); if (handle == xtreeEnd(testnames)) return NULL; twalk = (char *)xtreeData(testnames, handle); for (awalk = anchor->head; (awalk && (awalk->testname != twalk)); awalk=awalk->next) ; return awalk; } static xtreePos_t alisthandle; static activealerts_t *alistwalk; activealerts_t *alistBegin(void) { alisthandle = xtreeFirst(hostnames); alistwalk = NULL; while ((alisthandle != xtreeEnd(hostnames)) && (alistwalk == NULL)) { alertanchor_t *anchor = (alertanchor_t *)xtreeData(hostnames, alisthandle); alistwalk = anchor->head; if (alistwalk == NULL) alisthandle = xtreeNext(hostnames, alisthandle); } return alistwalk; } activealerts_t *alistNext(void) { if (!alistwalk) return NULL; alistwalk = alistwalk->next; if (alistwalk) return alistwalk; alisthandle = xtreeNext(hostnames, alisthandle); alistwalk = NULL; while ((alisthandle != xtreeEnd(hostnames)) && (alistwalk == NULL)) { alertanchor_t *anchor = (alertanchor_t *)xtreeData(hostnames, alisthandle); alistwalk = anchor->head; if (alistwalk == NULL) alisthandle = xtreeNext(hostnames, alisthandle); } return alistwalk; } void sig_handler(int signum) { switch (signum) { case SIGCHLD: break; case SIGUSR1: nextcheckpoint = 0; break; default: running = 0; termsig = signum; break; } } void save_checkpoint(char *filename) { char *subfn; FILE *fd = fopen(filename, "w"); activealerts_t *awalk; unsigned char *pgmsg, *ackmsg; if (fd == NULL) return; for (awalk = alistBegin(); (awalk); awalk = alistNext()) { if (awalk->state == A_DEAD) continue; pgmsg = ackmsg = ""; fprintf(fd, "%s|%s|%s|%s|%s|%d|%d|%s|", awalk->hostname, awalk->testname, awalk->location, awalk->ip, colorname(awalk->maxcolor), (int) awalk->eventstart, (int) awalk->nextalerttime, statename[awalk->state]); if (awalk->pagemessage) pgmsg = nlencode(awalk->pagemessage); fprintf(fd, "%s|", pgmsg); if (awalk->ackmessage) ackmsg = nlencode(awalk->ackmessage); fprintf(fd, "%s\n", ackmsg); } fclose(fd); subfn = (char *)malloc(strlen(filename)+5); sprintf(subfn, "%s.sub", filename); save_state(subfn); xfree(subfn); } void load_checkpoint(char *filename) { char *subfn; FILE *fd; strbuffer_t *inbuf; char statuscmd[1024]; char *statusbuf = NULL; sendreturn_t *sres; fd = fopen(filename, "r"); if (fd == NULL) return; sprintf(statuscmd, "xymondboard color=%s fields=hostname,testname,color", xgetenv("ALERTCOLORS")); sres = newsendreturnbuf(1, NULL); sendmessage(statuscmd, NULL, XYMON_TIMEOUT, sres); statusbuf = getsendreturnstr(sres, 1); freesendreturnbuf(sres); initfgets(fd); inbuf = newstrbuffer(0); while (unlimfgets(inbuf, fd)) { char *item[20], *p; int i; sanitize_input(inbuf, 0, 0); i = 0; p = gettok(STRBUF(inbuf), "|"); while (p && (i < 20)) { item[i++] = p; p = gettok(NULL, "|"); } if (i == 9) { /* There was no ack message */ item[i++] = ""; } if (i > 9) { char *valid = NULL; activealerts_t *newalert = (activealerts_t *)calloc(1, sizeof(activealerts_t)); newalert->hostname = find_name(hostnames, item[0]); newalert->testname = find_name(testnames, item[1]); newalert->location = find_name(locations, item[2]); strcpy(newalert->ip, item[3]); newalert->color = newalert->maxcolor = parse_color(item[4]); newalert->eventstart = (time_t) atoi(item[5]); newalert->nextalerttime = (time_t) atoi(item[6]); newalert->state = A_PAGING; if (statusbuf) { char *key; key = (char *)malloc(strlen(newalert->hostname) + strlen(newalert->testname) + 100); sprintf(key, "\n%s|%s|%s\n", newalert->hostname, newalert->testname, colorname(newalert->color)); valid = strstr(statusbuf, key); if (!valid && (strncmp(statusbuf, key+1, strlen(key+1)) == 0)) valid = statusbuf; xfree(key); } if (!valid) { errprintf("Stale alert for %s:%s dropped\n", newalert->hostname, newalert->testname); xfree(newalert); continue; } while (strcmp(item[7], statename[newalert->state]) && (newalert->state < A_DEAD)) newalert->state++; /* Config might have changed while we were down */ if (newalert->state == A_NORECIP) newalert->state = A_PAGING; newalert->pagemessage = newalert->ackmessage = NULL; if (strlen(item[8])) { nldecode(item[8]); newalert->pagemessage = strdup(item[8]); } if (strlen(item[9])) { nldecode(item[9]); newalert->ackmessage = strdup(item[9]); } add_active(newalert->hostname, newalert); } } fclose(fd); freestrbuffer(inbuf); subfn = (char *)malloc(strlen(filename)+5); sprintf(subfn, "%s.sub", filename); load_state(subfn, statusbuf); xfree(subfn); if (statusbuf) xfree(statusbuf); } int main(int argc, char *argv[]) { char *msg; int seq; int argi; int alertcolors, alertinterval; char *configfn = NULL; char *checkfn = NULL; int checkpointinterval = 900; char acklogfn[PATH_MAX]; FILE *acklogfd = NULL; char notiflogfn[PATH_MAX]; FILE *notiflogfd = NULL; char *tracefn = NULL; struct sigaction sa; int configchanged; time_t lastxmit = 0; MEMDEFINE(acklogfn); MEMDEFINE(notiflogfn); /* Dont save the error buffer */ save_errbuf = 0; /* Load alert config */ alertcolors = colorset(xgetenv("ALERTCOLORS"), ((1 << COL_GREEN) | (1 << COL_BLUE))); alertinterval = 60*atoi(xgetenv("ALERTREPEAT")); /* Create our loookup-trees */ hostnames = xtreeNew(strcasecmp); testnames = xtreeNew(strcasecmp); locations = xtreeNew(strcasecmp); for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--debug")) { debug = 1; } else if (argnmatch(argv[argi], "--config=")) { configfn = strdup(strchr(argv[argi], '=')+1); } else if (argnmatch(argv[argi], "--checkpoint-file=")) { checkfn = strdup(strchr(argv[argi], '=')+1); } else if (argnmatch(argv[argi], "--checkpoint-interval=")) { char *p = strchr(argv[argi], '=') + 1; checkpointinterval = atoi(p); } else if (argnmatch(argv[argi], "--dump-config")) { load_alertconfig(configfn, alertcolors, alertinterval); dump_alertconfig(1); return 0; } else if (argnmatch(argv[argi], "--cfid")) { include_configid = 1; } else if (argnmatch(argv[argi], "--test")) { char *testhost = NULL, *testservice = NULL, *testpage = NULL, *testcolor = "red", *testgroups = NULL; void *hinfo; int testdur = 0; FILE *logfd = NULL; activealerts_t *awalk = NULL; int paramno = 0; argi++; if (argi < argc) testhost = argv[argi]; argi++; if (argi < argc) testservice = argv[argi]; argi++; while (argi < argc) { if (strncasecmp(argv[argi], "--duration=", 11) == 0) { testdur = atoi(strchr(argv[argi], '=')+1); } else if (strncasecmp(argv[argi], "--color=", 8) == 0) { testcolor = strchr(argv[argi], '=')+1; } else if (strncasecmp(argv[argi], "--group=", 8) == 0) { testgroups = strchr(argv[argi], '=')+1; } else if (strncasecmp(argv[argi], "--time=", 7) == 0) { fakestarttime = (time_t)atoi(strchr(argv[argi], '=')+1); } else { paramno++; if (paramno == 1) testdur = atoi(argv[argi]); else if (paramno == 2) testcolor = argv[argi]; else if (paramno == 3) fakestarttime = (time_t) atoi(argv[argi]); } argi++; } if ((testhost == NULL) || (testservice == NULL)) { printf("Usage: xymond_alert --test HOST SERVICE [options]\n"); printf("Possible options:\n\t[--duration=SECONDS]\n\t[--color=COLOR]\n\t[--group=GROUPNAME]\n\t[--time=TIMESPEC]\n"); return 1; } load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); hinfo = hostinfo(testhost); if (hinfo) { testpage = strdup(xmh_item(hinfo, XMH_ALLPAGEPATHS)); } else { errprintf("Host not found in hosts.cfg - assuming it is on the top page\n"); testpage = ""; } awalk = (activealerts_t *)calloc(1, sizeof(activealerts_t)); awalk->hostname = find_name(hostnames, testhost); awalk->testname = find_name(testnames, testservice); awalk->location = find_name(locations, testpage); strcpy(awalk->ip, "127.0.0.1"); awalk->color = awalk->maxcolor = parse_color(testcolor); awalk->pagemessage = "Test of the alert configuration"; awalk->eventstart = getcurrenttime(NULL) - testdur*60; awalk->groups = (testgroups ? strdup(testgroups) : NULL); awalk->state = A_PAGING; awalk->cookie = 12345; awalk->next = NULL; logfd = fopen("/dev/null", "w"); starttrace(NULL); testonly = 1; load_alertconfig(configfn, alertcolors, alertinterval); load_holidays(0); send_alert(awalk, logfd); return 0; } else if (argnmatch(argv[argi], "--trace=")) { tracefn = strdup(strchr(argv[argi], '=')+1); starttrace(tracefn); } else if (net_worker_option(argv[argi])) { /* Handled in the subroutine */ } else { errprintf("Unknown option '%s'\n", argv[argi]); } } /* Do the network stuff if needed */ net_worker_run(ST_ALERT, LOC_SINGLESERVER, NULL); if (checkfn) { load_checkpoint(checkfn); nextcheckpoint = gettimer() + checkpointinterval; dbgprintf("Next checkpoint at %d, interval %d\n", (int) nextcheckpoint, checkpointinterval); } setup_signalhandler("xymond_alert"); /* Need to handle these ourselves, so we can shutdown and save state-info */ memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; sigaction(SIGPIPE, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGCHLD, &sa, NULL); sigaction(SIGUSR1, &sa, NULL); if (xgetenv("XYMONSERVERLOGS")) { sprintf(acklogfn, "%s/acknowledge.log", xgetenv("XYMONSERVERLOGS")); acklogfd = fopen(acklogfn, "a"); sprintf(notiflogfn, "%s/notifications.log", xgetenv("XYMONSERVERLOGS")); notiflogfd = fopen(notiflogfn, "a"); } /* * The general idea here is that this loop handles receiving of alert- * and ack-messages from the master daemon, and maintains a list of * host+test combinations that may have alerts going out. * * This module does not deal with any specific alert-configuration, * it just picks up the alert messages, maintains the list of * known tests that are in some sort of critical condition, and * periodically pushes alerts to the do_alert.c module for handling. * * The only modification of alerts that happen here is the handling * of when the next alert is due. It calls into the next_alert() * routine to learn when an alert should be repeated, and also * deals with Acknowledgments that stop alerts from going out for * a period of time. */ while (running) { char *eoln, *restofmsg; char *metadata[20]; char *p; int metacount; char *hostname = NULL, *testname = NULL; struct timespec timeout; time_t now, nowtimer; int anytogo; activealerts_t *awalk; int childstat; nowtimer = gettimer(); if (checkfn && (nowtimer > nextcheckpoint)) { dbgprintf("Saving checkpoint\n"); nextcheckpoint = nowtimer + checkpointinterval; save_checkpoint(checkfn); if (acklogfd) acklogfd = freopen(acklogfn, "a", acklogfd); if (notiflogfd) notiflogfd = freopen(notiflogfn, "a", notiflogfd); } timeout.tv_sec = 60; timeout.tv_nsec = 0; msg = get_xymond_message(C_PAGE, "xymond_alert", &seq, &timeout); if (msg == NULL) { running = 0; continue; } /* See what time it is - must happen AFTER the timeout */ now = getcurrenttime(NULL); /* Split the message in the first line (with meta-data), and the rest */ eoln = strchr(msg, '\n'); if (eoln) { *eoln = '\0'; restofmsg = eoln+1; } else { restofmsg = ""; } /* * Now parse the meta-data into elements. * We use our own "gettok()" routine which works * like strtok(), but can handle empty elements. */ metacount = 0; memset(&metadata, 0, sizeof(metadata)); p = gettok(msg, "|"); while (p && (metacount < 19)) { metadata[metacount] = p; metacount++; p = gettok(NULL, "|"); } metadata[metacount] = NULL; if (metacount > 3) hostname = metadata[3]; if (metacount > 4) testname = metadata[4]; if ((metacount > 10) && (strncmp(metadata[0], "@@page", 6) == 0)) { /* @@page|timestamp|sender|hostname|testname|hostip|expiretime|color|prevcolor|changetime|location|cookie|osname|classname|grouplist|modifiers */ int newcolor, newalertstatus, oldalertstatus; dbgprintf("Got page message from %s:%s\n", hostname, testname); traceprintf("@@page %s:%s:%s=%s\n", hostname, testname, metadata[10], metadata[7]); awalk = find_active(hostname, testname); if (awalk == NULL) { char *hwalk = find_name(hostnames, hostname); char *twalk = find_name(testnames, testname); char *pwalk = find_name(locations, metadata[10]); awalk = (activealerts_t *)calloc(1, sizeof(activealerts_t)); awalk->hostname = hwalk; awalk->testname = twalk; awalk->location = pwalk; awalk->cookie = -1; awalk->state = A_DEAD; /* * Use changetime here, if we restart the alert module then * this gets the duration values more right than using "now". * Also, define this only when a new alert arrives - we should * NOT clear this when a status goes yellow->red, or if it * flaps between yellow and red. */ awalk->eventstart = atoi(metadata[9]); add_active(awalk->hostname, awalk); traceprintf("New record\n"); } newcolor = parse_color(metadata[7]); oldalertstatus = ((alertcolors & (1 << awalk->color)) != 0); newalertstatus = ((alertcolors & (1 << newcolor)) != 0); traceprintf("state %d->%d\n", oldalertstatus, newalertstatus); if (newalertstatus) { /* It's in an alert state. */ awalk->color = newcolor; awalk->state = A_PAGING; if (newcolor > awalk->maxcolor) { if (awalk->maxcolor != 0) { /* * Severity has increased (yellow -> red). * Clear the repeat-interval, and set maxcolor to * the new color. If it drops to yellow again, * maxcolor stays at red, so a test that flaps * between yellow and red will only alert on red * the first time, and then follow the repeat * interval. */ dbgprintf("Severity increased, cleared repeat interval: %s/%s %s->%s\n", awalk->hostname, awalk->testname, colorname(awalk->maxcolor), colorname(newcolor)); clear_interval(awalk); } awalk->maxcolor = newcolor; } } else { /* * Send one "recovered" message out now, then go to A_DEAD. * Dont update the color here - we want recoveries to go out * only if the alert color triggered an alert */ awalk->state = (newcolor == COL_BLUE) ? A_DISABLED : A_RECOVERED; } if (oldalertstatus != newalertstatus) { dbgprintf("Alert status changed from %d to %d\n", oldalertstatus, newalertstatus); clear_interval(awalk); } strcpy(awalk->ip, metadata[5]); awalk->cookie = atoi(metadata[11]); if (awalk->osname) xfree(awalk->osname); awalk->osname = (metadata[12] ? strdup(metadata[12]) : NULL); if (awalk->classname) xfree(awalk->classname); awalk->classname = (metadata[13] ? strdup(metadata[13]) : NULL); if (awalk->groups) xfree(awalk->groups); awalk->groups = (metadata[14] ? strdup(metadata[14]) : NULL); if (awalk->pagemessage) xfree(awalk->pagemessage); if (metadata[15]) { /* Modifiers are more interesting than the message itself */ awalk->pagemessage = (char *)malloc(strlen(awalk->hostname) + strlen(awalk->testname) + strlen(colorname(awalk->color)) + strlen(metadata[15]) + strlen(restofmsg) + 10); sprintf(awalk->pagemessage, "%s:%s %s\n%s\n%s", awalk->hostname, awalk->testname, colorname(awalk->color), metadata[15], restofmsg); } else { awalk->pagemessage = strdup(restofmsg); } } else if ((metacount > 5) && (strncmp(metadata[0], "@@ack", 5) == 0)) { /* @@ack|timestamp|sender|hostname|testname|hostip|expiretime */ /* * An ack is handled simply by setting the next * alert-time to when the ack expires. */ time_t nextalert = atoi(metadata[6]); dbgprintf("Got ack message from %s:%s\n", hostname, testname); traceprintf("@@ack: %s:%s now=%d, ackeduntil %d\n", hostname, testname, (int)now, (int)nextalert); awalk = find_active(hostname, testname); if (acklogfd) { int cookie = (awalk ? awalk->cookie : -1); int color = (awalk ? awalk->color : 0); fprintf(acklogfd, "%d\t%d\t%d\t%d\t%s\t%s.%s\t%s\t%s\n", (int)now, cookie, (int)((nextalert - now) / 60), cookie, "np_filename_not_used", hostname, testname, colorname(color), nlencode(restofmsg)); fflush(acklogfd); } if (awalk && (awalk->state == A_PAGING)) { traceprintf("Record updated\n"); awalk->state = A_ACKED; awalk->nextalerttime = nextalert; if (awalk->ackmessage) xfree(awalk->ackmessage); awalk->ackmessage = strdup(restofmsg); } else { traceprintf("No record\n"); } } else if ((metacount > 4) && (strncmp(metadata[0], "@@notify", 5) == 0)) { /* @@notify|timestamp|sender|hostname|testname|pagepath */ char *hwalk = find_name(hostnames, hostname); char *twalk = find_name(testnames, testname); char *pwalk = find_name(locations, (metadata[5] ? metadata[5] : "")); awalk = (activealerts_t *)calloc(1, sizeof(activealerts_t)); awalk->hostname = hwalk; awalk->testname = twalk; awalk->location = pwalk; awalk->cookie = -1; awalk->pagemessage = strdup(restofmsg); awalk->eventstart = getcurrenttime(NULL); awalk->state = A_NOTIFY; add_active(awalk->hostname, awalk); } else if ((metacount > 3) && ((strncmp(metadata[0], "@@drophost", 10) == 0) || (strncmp(metadata[0], "@@dropstate", 11) == 0))) { /* @@drophost|timestamp|sender|hostname */ /* @@dropstate|timestamp|sender|hostname */ xtreePos_t handle; handle = xtreeFind(hostnames, hostname); if (handle != xtreeEnd(hostnames)) { alertanchor_t *anchor = (alertanchor_t *)xtreeData(hostnames, handle); for (awalk = anchor->head; (awalk); awalk = awalk->next) awalk->state = A_DEAD; } } else if ((metacount > 4) && (strncmp(metadata[0], "@@droptest", 10) == 0)) { /* @@droptest|timestamp|sender|hostname|testname */ awalk = find_active(hostname, testname); if (awalk) awalk->state = A_DEAD; } else if ((metacount > 4) && (strncmp(metadata[0], "@@renamehost", 12) == 0)) { /* @@renamehost|timestamp|sender|hostname|newhostname */ /* * We handle rename's simply by dropping the alert. If there is still an * active alert for the host, it will have to be dealt with when the next * status update arrives. */ xtreePos_t handle; handle = xtreeFind(hostnames, hostname); if (handle != xtreeEnd(hostnames)) { alertanchor_t *anchor = (alertanchor_t *)xtreeData(hostnames, handle); for (awalk = anchor->head; (awalk); awalk = awalk->next) awalk->state = A_DEAD; } } else if ((metacount > 5) && (strncmp(metadata[0], "@@renametest", 12) == 0)) { /* @@renametest|timestamp|sender|hostname|oldtestname|newtestname */ /* * We handle rename's simply by dropping the alert. If there is still an * active alert for the host, it will have to be dealt with when the next * status update arrives. */ awalk = find_active(hostname, testname); if (awalk) awalk->state = A_DEAD; } else if (strncmp(metadata[0], "@@shutdown", 10) == 0) { running = 0; errprintf("Got a shutdown message\n"); continue; } else if (strncmp(metadata[0], "@@logrotate", 11) == 0) { char *fn = xgetenv("XYMONCHANNEL_LOGFILENAME"); if (fn && strlen(fn)) { freopen(fn, "a", stdout); freopen(fn, "a", stderr); if (tracefn) { stoptrace(); starttrace(tracefn); } } continue; } else if (strncmp(metadata[0], "@@reload", 8) == 0) { /* Nothing ... right now */ } else if (strncmp(metadata[0], "@@idle", 6) == 0) { /* Timeout */ } /* * When a burst of alerts happen, we get lots of alert messages * coming in quickly. So lets handle them in bunches and only * do the full alert handling once every 10 secs - that lets us * combine a bunch of alerts into one transmission process. */ if (nowtimer < (lastxmit+10)) continue; lastxmit = nowtimer; /* * Loop through the activealerts list and see if anything is pending. * This is an optimization, we could just as well just fork off the * notification child and let it handle all of it. But there is no * reason to fork a child process unless it is going to do something. */ configchanged = load_alertconfig(configfn, alertcolors, alertinterval); configchanged += load_holidays(0); anytogo = 0; for (awalk = alistBegin(); (awalk); awalk = alistNext()) { int anymatch = 0; switch (awalk->state) { case A_NORECIP: if (!configchanged) break; /* The configuration has changed - switch NORECIP -> PAGING */ awalk->state = A_PAGING; clear_interval(awalk); /* Fall through */ case A_PAGING: if (have_recipient(awalk, &anymatch)) { if (awalk->nextalerttime <= now) anytogo++; } else { if (!anymatch) { awalk->state = A_NORECIP; cleanup_alert(awalk); } } break; case A_ACKED: if (awalk->nextalerttime <= now) { /* An ack has expired, so drop the ack message and switch to A_PAGING */ anytogo++; if (awalk->ackmessage) xfree(awalk->ackmessage); awalk->state = A_PAGING; } break; case A_RECOVERED: case A_DISABLED: case A_NOTIFY: anytogo++; break; case A_DEAD: break; } } dbgprintf("%d alerts to go\n", anytogo); if (anytogo) { pid_t childpid; childpid = fork(); if (childpid == 0) { /* The child */ start_alerts(); for (awalk = alistBegin(); (awalk); awalk = alistNext()) { switch (awalk->state) { case A_PAGING: if (awalk->nextalerttime <= now) { send_alert(awalk, notiflogfd); } break; case A_ACKED: /* Cannot be A_ACKED unless the ack is still valid, so no alert. */ break; case A_RECOVERED: case A_DISABLED: case A_NOTIFY: send_alert(awalk, notiflogfd); break; case A_NORECIP: case A_DEAD: break; } } finish_alerts(); /* Child does not continue */ exit(0); } else if (childpid < 0) { errprintf("Fork failed, cannot send alerts: %s\n", strerror(errno)); } } /* Update the state flag and the next-alert timestamp */ for (awalk = alistBegin(); (awalk); awalk = alistNext()) { switch (awalk->state) { case A_PAGING: if (awalk->nextalerttime <= now) awalk->nextalerttime = next_alert(awalk); break; case A_NORECIP: break; case A_ACKED: /* Still cannot get here except if ack is still valid */ break; case A_RECOVERED: case A_DISABLED: case A_NOTIFY: awalk->state = A_DEAD; /* Fall through */ case A_DEAD: cleanup_alert(awalk); break; } } clean_all_active(); /* Pickup any finished child processes to avoid zombies */ while (wait3(&childstat, WNOHANG, NULL) > 0) ; } if (checkfn) save_checkpoint(checkfn); if (acklogfd) fclose(acklogfd); if (notiflogfd) fclose(notiflogfd); stoptrace(); MEMUNDEFINE(notiflogfn); MEMUNDEFINE(acklogfn); if (termsig >= 0) { errprintf("Terminated by signal %d\n", termsig); } return 0; } xymon-4.3.7/xymond/xymonreports.sh.DIST0000664000175000017500000000507111535462534017443 0ustar henrikhenrik#!/bin/bash # Pre-generate periodic Xymon reports # # This would typically run via cron and xymoncmd. A sample crontab # to generate daily, weekly and monthly reports would be: # # 10 0 * * * @XYMONHOME@/bin/xymoncmd --env=@XYMONHOME@/etc/xymonserver.cfg xymonreports.sh daily # 10 1 * * 1 @XYMONHOME@/bin/xymoncmd --env=@XYMONHOME@/etc/xymonserver.cfg xymonreports.sh weekly # 10 2 1 * * @XYMONHOME@/bin/xymoncmd --env=@XYMONHOME@/etc/xymonserver.cfg xymonreports.sh monthly # # I.e. daily reports run each day at 0:10 AM, weekly reports run # on Monday morning at 1:10 AM, monthly reports run on the first of # each month at 2:10 AM. # This script requires the GNU date utility. Point this # setting to where you have that installed GNUDATE=date export GNUDATE if [ "$1" == "-?" -o "$1" == "--help" -o $# -eq 0 ] then echo "Usage: $0 {daily,weekly,monthly,annual} [fake reporting-date]" echo "The \"fake reporting-date\" defaults to today, meaning" echo "that the report runs until yesterday at 23:59:59." echo "Thus, to generate a daily report for Dec 1st 2001, use" echo " $0 daily \"2 Dec 2001\"" echo "NB: Quotes are required for the fake reporting-date" exit 1 fi if [ "$XYMONHOME" = "" ]; then echo "Must have Xymon environment set." exit 1 fi # This is the top-level directory for the pre-generated reports. REPORTTOPDIR="$XYMONWWWDIR/periodic" REPORTTOPURL="$XYMONWEB/periodic" REPTYPE=$1 if [ "$REPTYPE" == "" ] then REPTYPE="daily" fi TODAY=$2 if [ "$TODAY" == "" ] then TODAY="today" fi if [ "$REPTYPE" == "daily" ] then REPDIR=$REPTYPE/`$GNUDATE --date="$TODAY -1 day" +"%Y/%m/%d"` set `$GNUDATE --date="$TODAY -1 day" +"%d %b %Y"` SDAY=$1; SMON=$2; SYEAR=$3 elif [ "$REPTYPE" == "weekly" ] then REPDIR=$REPTYPE/`$GNUDATE --date="$TODAY -1 week" +"%Y/%V"` set `$GNUDATE --date="$TODAY -1 week" +"%d %b %Y"` SDAY=$1; SMON=$2; SYEAR=$3 elif [ "$REPTYPE" == "monthly" ] then REPDIR=$REPTYPE/`$GNUDATE --date="$TODAY -1 day" +"%Y/%m"` set `$GNUDATE --date="$TODAY -1 month" +"%d %b %Y"` SDAY=$1; SMON=$2; SYEAR=$3 else echo "Unknown report type : $REPTYPE" exit 1 fi # End date is today, meaning today at 00:00 set `$GNUDATE --date="$TODAY -1 day" +"%d %b %Y"` EDAY=$1; EMON=$2; EYEAR=$3 STIME=`$GNUDATE +%s --date "$SDAY $SMON $SYEAR 00:00:00"` ETIME=`$GNUDATE +%s --date "$EDAY $EMON $EYEAR 23:59:59"` # Create the output directory mkdir -p $REPORTTOPDIR/$REPDIR || exit 1 XYMONWEB=$REPORTTOPURL/$REPDIR $XYMONHOME/bin/xymongen --reportopts=$STIME:$ETIME:0:nongr $XYMONGENREPOPTS $REPORTTOPDIR/$REPDIR exit 0 xymon-4.3.7/xymond/client/0000775000175000017500000000000011671641716015031 5ustar henrikhenrikxymon-4.3.7/xymond/client/irix.c0000664000175000017500000001036511615341300016135 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for IRIX */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char irix_rcsid[] = "$Id: irix.c 6712 2011-07-31 21:01:52Z storner $"; void handle_irix_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { static pcre *memptn = NULL; char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *dfstr; char *swapstr; char *msgsstr; char *netstatstr; char *sarstr; char *ifstatstr; char *portsstr; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); swapstr = getdata("swap"); msgsstr = getdata("msgs"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); sarstr = getdata("sar"); portsstr = getdata("ports"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Available", "Capacity", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "COMMAND", NULL, psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); /* unix_sar_report(hostname, clienttype, os, hinfo, fromline, timestr, sarstr); */ if (topstr) { char *memline, *eoln = NULL; int res; int ovector[20]; char w[20]; long memphystotal = -1, memphysused = -1, memphysfree = 0, memactused = -1, memactfree = -1, memswaptotal = -1, memswapused = -1, memswapfree = 0; if (!memptn) { memptn = compileregex("^Memory: (\\d+)M max, (\\d+)M avail, (\\d+)M free, (\\d+)M swap, (\\d+)M free swap"); } memline = strstr(topstr, "\nMemory:"); if (memline) { memline++; eoln = strchr(memline, '\n'); if (eoln) *eoln = '\0'; res = pcre_exec(memptn, NULL, memline, strlen(memline), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); } else res = -1; if (res > 1) { pcre_copy_substring(memline, ovector, res, 1, w, sizeof(w)); memphystotal = atol(w); } if (res > 2) { pcre_copy_substring(memline, ovector, res, 2, w, sizeof(w)); memactfree = atol(w); memactused = memphystotal - memactfree; } if (res > 3) { pcre_copy_substring(memline, ovector, res, 3, w, sizeof(w)); memphysfree = atol(w); memphysused = memphystotal - memphysfree; } if (res > 4) { pcre_copy_substring(memline, ovector, res, 4, w, sizeof(w)); memswaptotal = atol(w); } if (res > 5) { pcre_copy_substring(memline, ovector, res, 5, w, sizeof(w)); memswapfree = atol(w); } memswapused = memswaptotal - memswapfree; if (eoln) *eoln = '\n'; unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, memphysused, memactused, memswaptotal, memswapused); } splitmsg_done(); } xymon-4.3.7/xymond/client/snmpcollect.c0000664000175000017500000000670611615341300017511 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for SNMP collector */ /* */ /* Copyright (C) 2009-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char snmpcollect_rcsid[] = "$Id: snmpcollect.c 6712 2011-07-31 21:01:52Z storner $"; /* * Split the snmpcollect client-message into individual mib-datasets. * Run each dataset through the mib-value configuration module, and * generate a status-message for each dataset. */ void handle_snmpcollect_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { void *ns1var, *ns1sects; char *onemib; char *mibname; char fromline[1024], msgline[1024]; strbuffer_t *summary = newstrbuffer(0); sprintf(fromline, "\nStatus message received from %s\n", sender); onemib = nextsection_r(clientdata, &mibname, &ns1var, &ns1sects); while (onemib) { char *bmark, *emark; char *oneds, *dskey; void *ns2var, *ns2sects; int color, rulecolor, anyrules; char *groups; if (strcmp(mibname, "proxy") == 0) { /* * Data was forwarded through a proxy - skip this section. * We dont want a "proxy" status for all SNMP-enabled hosts. */ goto sectiondone; } /* Convert the "" markers to "[NNN]" */ bmark = onemib; while ((bmark = strstr(bmark, "\n<")) != NULL) { emark = strchr(bmark, '>'); *(bmark+1) = '['; if (emark) *emark = ']'; bmark += 2; } /* Match the mib data against the configuration */ anyrules = 1; color = COL_GREEN; clearalertgroups(); clearstrbuffer(summary); oneds = nextsection_r(onemib, &dskey, &ns2var, &ns2sects); if (oneds) { /* Tabular MIB data. Handle each of the rows in the table. */ while (oneds && anyrules) { rulecolor = check_mibvals(hinfo, clienttype, mibname, dskey, oneds, summary, &anyrules); if (rulecolor > color) color = rulecolor; oneds = nextsection_r(NULL, &dskey, &ns2var, &ns2sects); } nextsection_r_done(ns2sects); } else { /* Non-tabular MIB data - no key */ rulecolor = check_mibvals(hinfo, clienttype, mibname, NULL, onemib, summary, &anyrules); if (rulecolor > color) color = rulecolor; } /* Generate the status message */ groups = getalertgroups(); init_status(color); if (groups) sprintf(msgline, "status/group:%s ", groups); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.%s %s %s\n", hostname, mibname, colorname(color), ctime(×tamp)); addtostatus(msgline); if (STRBUFLEN(summary) > 0) { addtostrstatus(summary); addtostatus("\n"); } addtostatus(onemib); addtostatus(fromline); finish_status(); sectiondone: onemib = nextsection_r(NULL, &mibname, &ns1var, &ns1sects); } nextsection_r_done(ns1sects); freestrbuffer(summary); } xymon-4.3.7/xymond/client/zos.c0000664000175000017500000006077611665414772016033 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for z/VSE, VSE/ESA and z/OS */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* Copyright (C) 2006-2009 Rich Smrcina */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char zos_rcsid[] = "$Id: zos.c 6783 2011-11-30 11:56:42Z storner $"; void zos_cpu_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *cpuutilstr, char *uptimestr) { char *p; float load1, loadyellow, loadred; int recentlimit, ancientlimit, uptimecolor, maxclockdiff, clockdiffcolor; int uphour, upmin; char loadresult[100]; char myupstr[100]; long uptimesecs = -1; long upday; int cpucolor = COL_GREEN; char msgline[4096]; strbuffer_t *upmsg; if (!want_msgtype(hinfo, MSG_CPU)) return; if (!uptimestr) return; if (!cpuutilstr) return; uptimesecs = 0; /* * z/OS: "Uptime: 1 Days, 13 Hours, 38 Minutes" */ sscanf(uptimestr,"Uptime: %ld Days, %d Hours, %d Minutes", &upday, &uphour, &upmin); uptimesecs = upday * 86400; uptimesecs += 60*(60*uphour + upmin); sprintf(myupstr, "%s\n", uptimestr); /* * Looking for average CPU Utilization in CPU message * CPU Utilization=000% */ *loadresult = '\0'; p = strstr(cpuutilstr, "CPU Utilization ") + 16 ; if (p) { if (sscanf(p, "%f%%", &load1) == 1) { sprintf(loadresult, "z/OS CPU Utilization %3.0f%%\n", load1); } } get_cpu_thresholds(hinfo, clientclass, &loadyellow, &loadred, &recentlimit, &ancientlimit, &uptimecolor, &maxclockdiff, &clockdiffcolor); upmsg = newstrbuffer(0); if (load1 > loadred) { cpucolor = COL_RED; addtobuffer(upmsg, "&red Load is CRITICAL\n"); } else if (load1 > loadyellow) { cpucolor = COL_YELLOW; addtobuffer(upmsg, "&yellow Load is HIGH\n"); } if ((uptimesecs != -1) && (recentlimit != -1) && (uptimesecs < recentlimit)) { if (cpucolor != COL_RED) cpucolor = uptimecolor; sprintf(msgline, "&%s Machine recently rebooted\n", colorname(uptimecolor)); addtobuffer(upmsg, msgline); } if ((uptimesecs != -1) && (ancientlimit != -1) && (uptimesecs > ancientlimit)) { if (cpucolor != COL_RED) cpucolor = uptimecolor; sprintf(msgline, "&%s Machine has been up more than %d days\n", colorname(uptimecolor), (ancientlimit / 86400)); addtobuffer(upmsg, msgline); } init_status(cpucolor); sprintf(msgline, "status %s.cpu %s %s %s %s %s\n", commafy(hostname), colorname(cpucolor), (timestr ? timestr : ""), loadresult, myupstr, cpuutilstr); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } void zos_paging_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *pagingstr) { char *p; int ipagerate, pagingyellow, pagingred; float fpagerate=0.0; char pagingresult[100]; int pagingcolor = COL_GREEN; char msgline[4096]; strbuffer_t *upmsg; if (!pagingstr) return; /* * Looking for Paging rate in message * Paging Rate=0 */ *pagingresult = '\0'; ipagerate=0; p = strstr(pagingstr, "Paging Rate ") + 12; if (p) { if (sscanf(p, "%f", &fpagerate) == 1) { ipagerate=fpagerate + 0.5; /* Rounding up */ sprintf(pagingresult, "z/OS Paging Rate %d per second\n", ipagerate); } } else sprintf(pagingresult, "Can not find page rate value in:\n%s\n", pagingstr); get_paging_thresholds(hinfo, clientclass, &pagingyellow, &pagingred); upmsg = newstrbuffer(0); if (ipagerate > pagingred) { pagingcolor = COL_RED; addtobuffer(upmsg, "&red Paging Rate is CRITICAL\n"); } else if (ipagerate > pagingyellow) { pagingcolor = COL_YELLOW; addtobuffer(upmsg, "&yellow Paging Rate is HIGH\n"); } init_status(pagingcolor); sprintf(msgline, "status %s.paging %s %s %s %s\n", commafy(hostname), colorname(pagingcolor), (timestr ? timestr : ""), pagingresult, pagingstr); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } void zos_memory_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *memstr) { char *p; char headstr[100], csastr[100], ecsastr[100], sqastr[100], esqastr[100]; long csaalloc, csaused, csahwm, ecsaalloc, ecsaused, ecsahwm; long sqaalloc, sqaused, sqahwm, esqaalloc, esqaused, esqahwm; float csautil, ecsautil, sqautil, esqautil; int csayellow, csared, ecsayellow, ecsared, sqayellow, sqared, esqayellow, esqared; int memcolor = COL_GREEN; char msgline[4096]; strbuffer_t *upmsg; if (!memstr) return; /* * Looking for memory eyecatchers in message */ p = strstr(memstr, "CSA ") + 4; if (p) { sscanf(p, "%ld %ld %ld", &csaalloc, &csaused, &csahwm); } p = strstr(memstr, "ECSA ") + 5; if (p) { sscanf(p, "%ld %ld %ld", &ecsaalloc, &ecsaused, &ecsahwm); } p = strstr(memstr, "SQA ") + 4; if (p) { sscanf(p, "%ld %ld %ld", &sqaalloc, &sqaused, &sqahwm); } p = strstr(memstr, "ESQA ") + 5; if (p) { sscanf(p, "%ld %ld %ld", &esqaalloc, &esqaused, &esqahwm); } csautil = ((float)csaused / (float)csaalloc) * 100; ecsautil = ((float)ecsaused / (float)ecsaalloc) * 100; sqautil = ((float)sqaused / (float)sqaalloc) * 100; esqautil = ((float)esqaused / (float)esqaalloc) * 100; get_zos_memory_thresholds(hinfo, clientclass, &csayellow, &csared, &ecsayellow, &ecsared, &sqayellow, &sqared, &esqayellow, &esqared); upmsg = newstrbuffer(0); if (csautil > csared) { if (memcolor < COL_RED) memcolor = COL_RED; addtobuffer(upmsg, "&red CSA Utilization is CRITICAL\n"); } else if (csautil > csayellow) { if (memcolor < COL_YELLOW) memcolor = COL_YELLOW; addtobuffer(upmsg, "&yellow CSA Utilization is HIGH\n"); } if (ecsautil > ecsared) { if (memcolor < COL_RED) memcolor = COL_RED; addtobuffer(upmsg, "&red ECSA Utilization is CRITICAL\n"); } else if (ecsautil > ecsayellow) { if (memcolor < COL_YELLOW) memcolor = COL_YELLOW; addtobuffer(upmsg, "&yellow ECSA Utilization is HIGH\n"); } if (sqautil > sqared) { if (memcolor < COL_RED) memcolor = COL_RED; addtobuffer(upmsg, "&red SQA Utilization is CRITICAL\n"); } else if (sqautil > sqayellow) { if (memcolor < COL_YELLOW) memcolor = COL_YELLOW; addtobuffer(upmsg, "&yellow SQA Utilization is HIGH\n"); } if (esqautil > esqared) { if (memcolor < COL_RED) memcolor = COL_RED; addtobuffer(upmsg, "&red ESQA Utilization is CRITICAL\n"); } else if (esqautil > esqayellow) { if (memcolor < COL_YELLOW) memcolor = COL_YELLOW; addtobuffer(upmsg, "&yellow ESQA Utilization is HIGH\n"); } *headstr = '\0'; *csastr = '\0'; *ecsastr = '\0'; *sqastr = '\0'; *esqastr = '\0'; strcpy(headstr, "z/OS Memory Map\n Area Alloc Used HWM Util%\n"); sprintf(csastr, "CSA %8ld %8ld %8ld %3.1f\n", csaalloc, csaused, csahwm, csautil); sprintf(ecsastr, "ECSA %8ld %8ld %8ld %3.1f\n", ecsaalloc, ecsaused, ecsahwm, ecsautil); sprintf(sqastr, "SQA %8ld %8ld %8ld %3.1f\n", sqaalloc, sqaused, sqahwm, sqautil); sprintf(esqastr, "ESQA %8ld %8ld %8ld %3.1f\n", esqaalloc, esqaused, esqahwm, esqautil); init_status(memcolor); sprintf(msgline, "status %s.memory %s %s\n%s %s %s %s %s", commafy(hostname), colorname(memcolor), (timestr ? timestr : ""), headstr, csastr, ecsastr, sqastr, esqastr); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } static void zos_cics_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *cicsstr) { char cicsappl[9], cicsdate[11], cicstime[9]; int numtrans=0, cicsok=1; float dsapct=0.0; float edsapct=0.0; char cicsresult[100]; char tempresult[100]; char *cicsentry = NULL; int cicscolor = COL_GREEN; int dsayel, dsared, edsayel, edsared; char msgline[4096]; char cicsokmsg[]="All CICS Systems OK"; char cicsnotokmsg[]="One or more CICS Systems not OK"; strbuffer_t *headline; strbuffer_t *upmsg; strbuffer_t *cicsmsg; if (!cicsstr) return; cicsmsg = newstrbuffer(0); upmsg = newstrbuffer(0); headline= newstrbuffer(0); addtobuffer(headline, "Appl ID Trans DSA Pct EDSA Pct\n"); /* * * Each CICS system reporting uses one line in the message, the format is: * applid date time numtrans dsapct edsapct * applid is the CICS application id * date and time are the date and time of the report * numtrans is the number of transactions that were executed in CICS since the last report * dsapct is the DSA utilization percentage * edsapct is the EDSA utilization percentage * */ if (cicsstr) { cicsentry=strtok(cicsstr, "\n"); while (cicsentry != NULL) { sscanf(cicsentry, "%8s %10s %8s %d %f %f", cicsappl, cicsdate, cicstime, &numtrans, &dsapct, &edsapct); sprintf(cicsresult,"%-8s %6d %3.1f %3.1f\n", cicsappl, numtrans, dsapct, edsapct); addtobuffer(cicsmsg, cicsresult); if (numtrans == -1 ) { if (cicscolor < COL_YELLOW) cicscolor = COL_YELLOW; cicsok=0; sprintf(tempresult,"&yellow CICS system %s not responding, removed\n", cicsappl); addtobuffer(upmsg, tempresult); } /* Get CICS thresholds for this application ID. */ get_cics_thresholds(hinfo, clientclass, cicsappl, &dsayel, &dsared, &edsayel, &edsared); /* The threshold of the DSA values for each CICS must be checked in this loop. */ if (dsapct > dsared) { if (cicscolor < COL_RED) cicscolor = COL_RED; cicsok=0; sprintf(tempresult,"&red %s DSA Utilization is CRITICAL\n", cicsappl); addtobuffer(upmsg, tempresult); } else if (dsapct > dsayel) { if (cicscolor < COL_YELLOW) cicscolor = COL_YELLOW; cicsok=0; sprintf(tempresult,"&yellow %s DSA Utilization is HIGH\n", cicsappl); addtobuffer(upmsg, tempresult); } if (edsapct > edsared) { if (cicscolor < COL_RED) cicscolor = COL_RED; cicsok=0; sprintf(tempresult,"&red %s EDSA Utilization is CRITICAL\n", cicsappl); addtobuffer(upmsg, tempresult); } else if (edsapct > edsayel) { if (cicscolor < COL_YELLOW) cicscolor = COL_YELLOW; cicsok=0; sprintf(tempresult,"&yellow %s EDSA Utilization is HIGH\n", cicsappl); addtobuffer(upmsg, tempresult); } init_status(cicscolor); cicsentry=strtok(NULL, "\n"); } } sprintf(msgline, "status %s.cics %s %s %s\n", commafy(hostname), colorname(cicscolor), (timestr ? timestr : ""), ( (cicsok==1) ? cicsokmsg : cicsnotokmsg) ); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); } if (STRBUFLEN(cicsmsg)) { addtostrstatus(headline); addtostrstatus(cicsmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(headline); freestrbuffer(upmsg); freestrbuffer(cicsmsg); } void zos_jobs_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *psstr) { int pscolor = COL_GREEN; int pchecks; int cmdofs = -1; char msgline[4096]; strbuffer_t *monmsg; static strbuffer_t *countdata = NULL; int anycountdata = 0; char *group; if (!want_msgtype(hinfo, MSG_PROCS)) return; if (!psstr) return; if (!countdata) countdata = newstrbuffer(0); clearalertgroups(); monmsg = newstrbuffer(0); sprintf(msgline, "data %s.proccounts\n", commafy(hostname)); addtobuffer(countdata, msgline); cmdofs = 0; /* Command offset for z/OS isn't necessary */ pchecks = clear_process_counts(hinfo, clientclass); if (pchecks == 0) { /* Nothing to check */ sprintf(msgline, "&%s No process checks defined\n", colorname(noreportcolor)); addtobuffer(monmsg, msgline); pscolor = noreportcolor; } else if (cmdofs >= 0) { /* Count how many instances of each monitored process is running */ char *pname, *pid, *bol, *nl; int pcount, pmin, pmax, pcolor, ptrack; bol = psstr; while (bol) { nl = strchr(bol, '\n'); /* Take care - the ps output line may be shorter than what we look at */ if (nl) { *nl = '\0'; if ((nl-bol) > cmdofs) add_process_count(bol+cmdofs); *nl = '\n'; bol = nl+1; } else { if (strlen(bol) > cmdofs) add_process_count(bol+cmdofs); bol = NULL; } } /* Check the number found for each monitored process */ while ((pname = check_process_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, &group)) != NULL) { char limtxt[1024]; if (pmax == -1) { if (pmin > 0) sprintf(limtxt, "%d or more", pmin); else if (pmin == 0) sprintf(limtxt, "none"); } else { if (pmin > 0) sprintf(limtxt, "between %d and %d", pmin, pmax); else if (pmin == 0) sprintf(limtxt, "at most %d", pmax); } if (pcolor == COL_GREEN) { sprintf(msgline, "&green %s (found %d, req. %s)\n", pname, pcount, limtxt); addtobuffer(monmsg, msgline); } else { if (pcolor > pscolor) pscolor = pcolor; sprintf(msgline, "&%s %s (found %d, req. %s)\n", colorname(pcolor), pname, pcount, limtxt); addtobuffer(monmsg, msgline); addalertgroup(group); } if (ptrack) { /* Save the count data for later DATA message to track process counts */ if (!pid) pid = "default"; sprintf(msgline, "%s:%u\n", pid, pcount); addtobuffer(countdata, msgline); anycountdata = 1; } } } else { pscolor = COL_YELLOW; sprintf(msgline, "&yellow Expected string not found in ps output header\n"); addtobuffer(monmsg, msgline); } /* Now we know the result, so generate a status message */ init_status(pscolor); group = getalertgroups(); if (group) sprintf(msgline, "status/group:%s ", group); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.procs %s %s - Processes %s\n", commafy(hostname), colorname(pscolor), (timestr ? timestr : ""), ((pscolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info about what's wrong */ if (STRBUFLEN(monmsg)) { addtostrstatus(monmsg); addtostatus("\n"); } /* And the full list of jobs for those who want it */ if (pslistinprocs) { /* * Format the list of virtual machines into four per line, * this list could be fairly long. */ char *tmpstr, *tok; /* Make a copy of psstr, strtok() will be changing it */ tmpstr = strdup(psstr); /* Use strtok() to split string into pieces delimited by newline */ tok = strtok(tmpstr, "\n"); while (tok) { sprintf(msgline, "%s\n", tok); addtostatus(msgline); tok = strtok(NULL, "\n"); } free(tmpstr); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(monmsg); if (anycountdata) sendmessage(STRBUF(countdata), NULL, XYMON_TIMEOUT, NULL); clearstrbuffer(countdata); } void zos_maxuser_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *maxuserstr) { char *p; char maxustr[256]; long maxusers, maxufree, maxuused, rsvtstrt, rsvtfree, rsvtused, rsvnonr, rsvnfree, rsvnused; int maxyellow, maxred; float maxutil, rsvtutil, rsvnutil; int maxcolor = COL_GREEN; char msgline[4096]; strbuffer_t *upmsg; if (!maxuserstr) return; /* * Looking for eyecatchers in message */ p = strstr(maxuserstr, "Maxusers: ") + 9; if (p) { sscanf(p, "%ld Free: %ld", &maxusers, &maxufree); } p = strstr(maxuserstr, "RSVTSTRT: ") + 9; if (p) { sscanf(p, "%ld Free: %ld", &rsvtstrt, &rsvtfree); } p = strstr(maxuserstr, "RSVNONR: ") + 8; if (p) { sscanf(p, "%ld Free: %ld", &rsvnonr, &rsvnfree); } maxuused = maxusers - maxufree; rsvtused = rsvtstrt - rsvtfree; rsvnused = rsvnonr - rsvnfree; if ( maxuused == 0.0 ) maxutil = 0; else maxutil = ((float)maxuused / (float)maxusers) * 100; if ( rsvtused == 0.0 ) rsvtutil = 0; else rsvtutil = ((float)rsvtused / (float)rsvtstrt) * 100; if ( rsvnused == 0.0 ) rsvnutil = 0; else rsvnutil = ((float)rsvnused / (float)rsvnonr) * 100; get_asid_thresholds(hinfo, clientclass, &maxyellow, &maxred); upmsg = newstrbuffer(0); if ((int)maxutil > maxred) { if (maxcolor < COL_RED) maxcolor = COL_RED; addtobuffer(upmsg, "&red ASID (Maxuser) Utilization is CRITICAL\n"); } else if ((int)maxutil > maxyellow) { if (maxcolor < COL_YELLOW) maxcolor = COL_YELLOW; addtobuffer(upmsg, "&yellow ASID (Maxuser) Utilization is HIGH\n"); } *maxustr = '\0'; sprintf(maxustr, " Maxuser: %8ld Free: %8ld Used: %8ld %3.1f\nRSVTSTRT: %8ld Free: %8ld Used: %8ld %3.1f\n RSVNONR: %8ld Free: %8ld Used: %8ld %3.1f\n",maxusers,maxufree,maxuused,maxutil,rsvtstrt,rsvtfree,rsvtused,rsvtutil,rsvnonr,rsvnfree,rsvnused,rsvnutil); init_status(maxcolor); sprintf(msgline, "status %s.maxuser %s %s\n%s", commafy(hostname), colorname(maxcolor), (timestr ? timestr : ""), maxustr); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } void handle_zos_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *cpuutilstr; char *pagingstr; char *uptimestr; char *msgcachestr; char *dfstr; char *cicsstr; /* z/OS CICS Information */ char *jobsstr; /* z/OS Running jobs */ char *memstr; /* z/OS Memory Utilization */ char *maxuserstr; /* z/OS Maxuser */ char *portsstr; char *ifstatstr; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); cpuutilstr = getdata("cpu"); pagingstr = getdata("paging"); msgcachestr = getdata("msgcache"); dfstr = getdata("df"); cicsstr = getdata("cics"); jobsstr = getdata("jobs"); memstr = getdata("memory"); maxuserstr = getdata("maxuser"); portsstr = getdata("ports"); ifstatstr = getdata("ifstat"); zos_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, cpuutilstr, uptimestr); zos_paging_report(hostname, clienttype, os, hinfo, fromline, timestr, pagingstr); zos_cics_report(hostname, clienttype, os, hinfo, fromline, timestr, cicsstr); zos_jobs_report(hostname, clienttype, os, hinfo, fromline, timestr, jobsstr); zos_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memstr); zos_maxuser_report(hostname, clienttype, os, hinfo, fromline, timestr, maxuserstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Available", "Cap", "Mounted", dfstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); splitmsg_done(); } xymon-4.3.7/xymond/client/powershell.c0000664000175000017500000000245611615341300017350 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for the Windows Powershell client */ /* */ /* Copyright (C) 2010-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char powershell_rcsid[] = "$Id: powershell.c 6712 2011-07-31 21:01:52Z storner $"; void handle_powershell_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { /* At the moment, the Powershell client just uses the same handling as the BBWin client */ handle_win32_bbwin_client(hostname, clienttype, os, hinfo, sender, timestamp, clientdata); } xymon-4.3.7/xymond/client/linux.c0000664000175000017500000000733311615341300016322 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for Linux */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char linux_rcsid[] = "$Id: linux.c 6712 2011-07-31 21:01:52Z storner $"; void handle_linux_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *dfstr; char *freestr; char *msgsstr; char *netstatstr; char *vmstatstr; char *ifstatstr; char *portsstr; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); freestr = getdata("free"); msgsstr = getdata("msgs"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); vmstatstr = getdata("vmstat"); portsstr = getdata("ports"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Available", "Capacity", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "CMD", NULL, psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); unix_vmstat_report(hostname, clienttype, os, hinfo, fromline, timestr, vmstatstr); if (freestr) { char *p; long memphystotal, memphysused, memphysfree, memactused, memactfree, memswaptotal, memswapused, memswapfree; memphystotal = memswaptotal = memphysused = memswapused = memactused = memactfree = -1; p = strstr(freestr, "\nMem:"); if (p && (sscanf(p, "\nMem: %ld %ld %ld", &memphystotal, &memphysused, &memphysfree) == 3)) { memphystotal /= 1024; memphysused /= 1024; memphysfree /= 1024; } p = strstr(freestr, "\nSwap:"); if (p && (sscanf(p, "\nSwap: %ld %ld %ld", &memswaptotal, &memswapused, &memswapfree) == 3)) { memswaptotal /= 1024; memswapused /= 1024; memswapfree /= 1024; } p = strstr(freestr, "\n-/+ buffers/cache:"); if (p && (sscanf(p, "\n-/+ buffers/cache: %ld %ld", &memactused, &memactfree) == 2)) { memactused /= 1024; memactfree /= 1024; } unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, memphysused, memactused, memswaptotal, memswapused); } splitmsg_done(); } xymon-4.3.7/xymond/client/openbsd.c0000664000175000017500000000722211615341300016612 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for OpenBSD */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char openbsd_rcsid[] = "$Id: openbsd.c 6712 2011-07-31 21:01:52Z storner $"; void handle_openbsd_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *dfstr; char *meminfostr; char *swapctlstr; char *msgsstr; char *netstatstr; char *ifstatstr; char *portsstr; char *vmstatstr; char *p; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); meminfostr = getdata("meminfo"); swapctlstr = getdata("swapctl"); msgsstr = getdata("msgs"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); portsstr = getdata("ports"); vmstatstr = getdata("vmstat"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Avail", "Capacity", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "COMMAND", NULL, psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); unix_vmstat_report(hostname, clienttype, os, hinfo, fromline, timestr, vmstatstr); if (meminfostr) { unsigned long memphystotal, memphysfree, memphysused; unsigned long memswaptotal, memswapfree, memswapused; int found = 0; memphystotal = memphysfree = memphysused = 0; memswaptotal = memswapfree = memswapused = 0; p = strstr(meminfostr, "Total:"); if (p) { memphystotal = atol(p+6); found++; } p = strstr(meminfostr, "Free:"); if (p) { memphysfree = atol(p+5); found++; } memphysused = memphystotal - memphysfree; p = strstr(meminfostr, "Swaptotal:"); if (p) { memswaptotal = atol(p+10); found++; } p = strstr(meminfostr, "Swapused:"); if (p) { memswapused = atol(p+9); found++; } memswapfree = memswaptotal - memswapused; if (found == 4) { unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, memphysused, -1, memswaptotal, memswapused); } } splitmsg_done(); } xymon-4.3.7/xymond/client/solaris.c0000664000175000017500000001250311615341300016632 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for Solaris */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char solaris_rcsid[] = "$Id: solaris.c 6712 2011-07-31 21:01:52Z storner $"; void handle_solaris_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *prtconfstr; char *memorystr; char *swapstr; char *swapliststr; char *dfstr; char *msgsstr; char *netstatstr; char *ifstatstr; char *portsstr; char *vmstatstr; char *iostatdiskstr; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); prtconfstr = getdata("prtconf"); memorystr = getdata("memory"); swapstr = getdata("swap"); swapliststr = getdata("swaplist"); msgsstr = getdata("msgs"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); portsstr = getdata("ports"); vmstatstr = getdata("vmstat"); iostatdiskstr = getdata("iostatdisk"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "avail", "capacity", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "CMD", "COMMAND", psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 0, 1, 6, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); unix_vmstat_report(hostname, clienttype, os, hinfo, fromline, timestr, vmstatstr); if (prtconfstr && memorystr && (swapstr || swapliststr)) { long memphystotal, memphysfree, memswapused, memswapfree; char *p; memphystotal = memphysfree = memswapfree = memswapused = -1; p = strstr(prtconfstr, "\nMemory size:"); if (p && (sscanf(p, "\nMemory size: %ld Megabytes", &memphystotal) == 1)) ; if (sscanf(memorystr, "%*d %*d %*d %*d %ld", &memphysfree) == 1) memphysfree /= 1024; if (!swapliststr) { /* * No "swap -l" output, so use what "swap -s" reports. * Xymon clients prior to 2010-Dec-14 (roughly 4.3.0 release) does not report "swap -l". */ p = strchr(swapstr, '='); if (p && sscanf(p, "= %ldk used, %ldk available", &memswapused, &memswapfree) == 2) { memswapused /= 1024; memswapfree /= 1024; } } else { /* We prefer using "swap -l" output since it matches what other system tools report */ char *bol; long blktotal, blkfree; blktotal = blkfree = 0; bol = swapliststr; while (bol) { char *nl, *tmpline; nl = strchr(bol, '\n'); if (nl) *nl = '\0'; tmpline = strdup(bol); /* According to the Solaris man-page for versions 8 thru 10, the "swap -l" output is always 5 columns */ /* Note: getcolumn() is zero-based (thanks, Dominique Frise) */ p = getcolumn(tmpline, 3); if (p) blktotal += atol(p); strcpy(tmpline, bol); p = getcolumn(tmpline, 4); if (p) blkfree += atol(p); xfree(tmpline); if (nl) { *nl = '\n'; bol = nl+1; } else { bol = NULL; } } if ((blktotal > 0) && (blkfree > 0)) { /* Values from swap -l are numbers of 512-byte blocks. Convert to MB = N*512/(1024*1024) = N/2048 */ memswapused = (blktotal - blkfree) / 2048; memswapfree = blkfree / 2048; } } if ((memphystotal>=0) && (memphysfree>=0) && (memswapused>=0) && (memswapfree>=0)) { unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, (memphystotal - memphysfree), -1, (memswapused + memswapfree), memswapused); } } if (iostatdiskstr) { char msgline[1024]; strbuffer_t *msg = newstrbuffer(0); char *p; p = strchr(iostatdiskstr, '\n'); if (p) { p++; sprintf(msgline, "data %s.iostatdisk\n%s\n", commafy(hostname), osname(os)); addtobuffer(msg, msgline); addtobuffer(msg, p); sendmessage(STRBUF(msg), NULL, XYMON_TIMEOUT, NULL); } freestrbuffer(msg); } splitmsg_done(); } xymon-4.3.7/xymond/client/bbwin.c0000664000175000017500000004341211665414772016305 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for BBWin/Windoes client */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* Copyright (C) 2007-2008 Francois Lacroix */ /* Copyright (C) 2007-2008 Etienne Grignon */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char bbwin_rcsid[] = "$Id: bbwin.c 6783 2011-11-30 11:56:42Z storner $"; static void bbwin_uptime_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *uptimestr) { char *p, *myuptimestr = NULL; float loadyellow, loadred; int recentlimit, ancientlimit, uptimecolor, maxclockdiff, clockdiffcolor; long uptimesecs = -1; int upcolor = COL_GREEN; char msgline[4096]; strbuffer_t *upmsg; if (!want_msgtype(hinfo, MSG_CPU)) return; if (!uptimestr) return; dbgprintf("Uptime check host %s\n", hostname); uptimesecs = 0; /* Parse to check data */ p = strstr(uptimestr, "sec:"); if (p) { p += strcspn(p, "0123456789\r\n"); uptimesecs = atol(p); dbgprintf("uptimestr [%d]\n", uptimesecs); /* DEBUG TODO REMOVE */ } /* Parse to show a nice msg */ myuptimestr = strchr(uptimestr, '\n'); if (myuptimestr) { ++myuptimestr; } get_cpu_thresholds(hinfo, clientclass, &loadyellow, &loadred, &recentlimit, &ancientlimit, &uptimecolor, &maxclockdiff, &clockdiffcolor); dbgprintf("DEBUG recentlimit: [%d] ancienlimit: [%d]\n", recentlimit, ancientlimit); /* DEBUG TODO REMOVE */ upmsg = newstrbuffer(0); if ((uptimesecs != -1) && (recentlimit != -1) && (uptimesecs < recentlimit)) { if (upcolor != COL_RED) upcolor = uptimecolor; sprintf(msgline, "&%s Machine recently rebooted\n", colorname(uptimecolor)); addtobuffer(upmsg, msgline); } if ((uptimesecs != -1) && (ancientlimit != -1) && (uptimesecs > ancientlimit)) { if (upcolor != COL_RED) upcolor = uptimecolor; sprintf(msgline, "&%s Machine has been up more than %d days\n", colorname(uptimecolor), (ancientlimit / 86400)); addtobuffer(upmsg, msgline); } init_status(upcolor); sprintf(msgline, "status %s.uptime %s %s %s\n", commafy(hostname), colorname(upcolor), (timestr ? timestr : ""), ((upcolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info if pb */ if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } /* And add the msg we recevied */ if (myuptimestr) { addtostatus(myuptimestr); addtostatus("\n"); } dbgprintf("msgline %s", msgline); /* DEBUG TODO REMOVE */ if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } static void bbwin_cpu_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *cpuutilstr) { char *p, *topstr; float load1, loadyellow, loadred; int recentlimit, ancientlimit, uptimecolor, maxclockdiff, clockdiffcolor; int cpucolor = COL_GREEN; char msgline[4096]; strbuffer_t *cpumsg; if (!want_msgtype(hinfo, MSG_CPU)) return; if (!cpuutilstr) return; dbgprintf("CPU check host %s\n", hostname); load1 = 0; p = strstr(cpuutilstr, "load="); if (p) { p += strcspn(p, "0123456789%\r\n"); load1 = atol(p); dbgprintf("load1 [%d]\n", load1); /* DEBUG TODO REMOVE */ } topstr = strstr(cpuutilstr, "CPU states"); if (topstr) { *(topstr - 1) = '\0'; } get_cpu_thresholds(hinfo, clientclass, &loadyellow, &loadred, &recentlimit, &ancientlimit, &uptimecolor, &maxclockdiff, &clockdiffcolor); dbgprintf("loadyellow: %d, loadred: %d\n", loadyellow, loadred); cpumsg = newstrbuffer(0); if (load1 > loadred) { cpucolor = COL_RED; addtobuffer(cpumsg, "&red Load is CRITICAL\n"); } else if (load1 > loadyellow) { cpucolor = COL_YELLOW; addtobuffer(cpumsg, "&yellow Load is HIGH\n"); } init_status(cpucolor); sprintf(msgline, "status %s.cpu %s %s %s", commafy(hostname), colorname(cpucolor), (timestr ? timestr : ""), cpuutilstr); addtostatus(msgline); /* And add the info if pb */ if (STRBUFLEN(cpumsg)) { addtostrstatus(cpumsg); addtostatus("\n"); } /* And add the msg we recevied */ if (topstr) { addtostatus(topstr); addtostatus("\n"); } dbgprintf("msgline %s", msgline); /* DEBUG TODO REMOVE */ if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(cpumsg); } static void bbwin_clock_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *clockstr, char *msgcachestr) { char *myclockstr; int clockcolor = COL_GREEN; float loadyellow, loadred; int recentlimit, ancientlimit, uptimecolor, maxclockdiff, clockdiffcolor; char msgline[4096]; strbuffer_t *clockmsg; if (!want_msgtype(hinfo, MSG_CPU)) return; if (!clockstr) return; dbgprintf("Clock check host %s\n", hostname); clockmsg = newstrbuffer(0); myclockstr = strstr(clockstr, "local"); if (myclockstr) { *(myclockstr - 1) = '\0'; } get_cpu_thresholds(hinfo, clientclass, &loadyellow, &loadred, &recentlimit, &ancientlimit, &uptimecolor, &maxclockdiff, &clockdiffcolor); if (clockstr) { char *p; struct timeval clockval; p = strstr(clockstr, "epoch:"); if (!p) { /* No epoch reported */ } else { struct timeval clockdiff; struct timezone tz; int cachedelay = 0; /* The clock may be reported with a decimal part. But not always */ p += 6; /* Skip "epoch:" */ clockval.tv_sec = atol(p); /* See if there's a decimal part */ p += strspn(p, "0123456789"); if (*p == '.') { p++; clockval.tv_usec = atol(p); } else { clockval.tv_usec = 0; } if (msgcachestr) { /* Message passed through msgcache, so adjust for the cache delay */ p = strstr(msgcachestr, "Cachedelay:"); if (p) cachedelay = atoi(p+11); } gettimeofday(&clockdiff, &tz); clockdiff.tv_sec -= (clockval.tv_sec + cachedelay); clockdiff.tv_usec -= clockval.tv_usec; if (clockdiff.tv_usec < 0) { clockdiff.tv_usec += 1000000; clockdiff.tv_sec -= 1; } if ((maxclockdiff > 0) && (abs(clockdiff.tv_sec) > maxclockdiff)) { if (clockcolor != COL_RED) clockcolor = clockdiffcolor; sprintf(msgline, "&%s System clock is %ld seconds off (max %ld)\n", colorname(clockdiffcolor), (long) clockdiff.tv_sec, (long) maxclockdiff); addtobuffer(clockmsg, msgline); } else { sprintf(msgline, "System clock is %ld seconds off\n", (long) clockdiff.tv_sec); addtobuffer(clockmsg, msgline); } } } init_status(clockcolor); sprintf(msgline, "status %s.timediff %s %s %s\n", commafy(hostname), colorname(clockcolor), (timestr ? timestr : ""), ((clockcolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info if pb */ if (STRBUFLEN(clockmsg)) { addtostrstatus(clockmsg); addtostatus("\n"); } /* And add the msg we recevied */ if (myclockstr) { addtostatus(myclockstr); addtostatus("\n"); } dbgprintf("msgline %s", msgline); /* DEBUG TODO REMOVE */ if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(clockmsg); } void bbwin_who_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *whostr) { int whocolor = COL_GREEN; char msgline[4096]; strbuffer_t *whomsg; if (!want_msgtype(hinfo, MSG_WHO)) return; if (!whostr) return; dbgprintf("Who check host %s\n", hostname); whomsg = newstrbuffer(0); init_status(whocolor); sprintf(msgline, "status %s.who %s %s %s\n", commafy(hostname), colorname(whocolor), (timestr ? timestr : ""), ((whocolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info if pb */ if (STRBUFLEN(whomsg)) { addtostrstatus(whomsg); addtostatus("\n"); } /* And add the msg we recevied */ if (whostr) { addtostatus(whostr); addtostatus("\n"); } dbgprintf("msgline %s", msgline); /* DEBUG TODO REMOVE */ if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(whomsg); } void bbwin_svcs_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, int namecol, int startupcol, int statecol, char *svcstr, char *svcauto) { int svccolor = -1; int schecks; char msgline[4096]; static strbuffer_t *monmsg = NULL; char *group; if (!want_msgtype(hinfo, MSG_SVCS)) return; if (!svcstr) return; if (!monmsg) monmsg = newstrbuffer(0); dbgprintf("Services check host %s\n", hostname); clearalertgroups(); schecks = clear_svc_counts(hinfo, clientclass); dbgprintf("schecks: [%d]\n", schecks); /* DEBUG TODO REMOVE */ if (schecks > 0) { /* Count how many instances of each monitored condition are found */ char *sname, *bol, *nl; int scount, scolor; char *namestr, *startupstr, *statestr; bol = svcstr; while (bol) { char *p; nl = strchr(bol, '\n'); if (nl) *nl = '\0'; /* Data lines */ p = strdup(bol); namestr = getcolumn(p, namecol); strcpy(p, bol); startupstr = getcolumn(p, startupcol); strcpy(p, bol); statestr = getcolumn(p, statecol); add_svc_count(namestr, startupstr, statestr); xfree(p); if (nl) { *nl = '\n'; bol = nl+1; } else bol = NULL; } /* Check the status and state found for each monitored svc */ while ((sname = check_svc_count(&scount, &scolor, &group)) != NULL) { if (scolor > svccolor) svccolor = scolor; if (scolor == COL_GREEN) { sprintf(msgline, "&green %s\n", sname); addtobuffer(monmsg, msgline); } else { sprintf(msgline, "&%s %s\n", colorname(scolor), sname); addtobuffer(monmsg, msgline); addalertgroup(group); } } } if ((svccolor == -1) && sendclearsvcs) { /* Nothing to check */ addtobuffer(monmsg, "No Services checks defined\n"); svccolor = noreportcolor; } if (svccolor != -1) { if (svcauto && strlen(svcauto) > 1 && (svccolor == COL_GREEN || svccolor == noreportcolor)) svccolor = COL_YELLOW; /* Now we know the result, so generate a status message */ init_status(svccolor); group = getalertgroups(); if (group) sprintf(msgline, "status/group:%s ", group); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.svcs %s %s - Services %s\n", commafy(hostname), colorname(svccolor), (timestr ? timestr : ""), ((svccolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info about what's wrong */ addtostrstatus(monmsg); addtostatus("\n"); clearstrbuffer(monmsg); /* Add AutoRestart status */ if (svcauto && strlen(svcauto) > 1) { addtostatus(svcauto); addtostatus("\n\n"); } /* And the full svc output for those who want it */ if (svclistinsvcs) addtostatus(svcstr); if (fromline) addtostatus(fromline); finish_status(); } else { clearstrbuffer(monmsg); } } void handle_win32_bbwin_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *cpuutilstr; char *uptimestr; char *clockstr; char *msgcachestr; char *diskstr; char *procsstr; char *msgsstr; char *portsstr; char *memorystr; char *netstatstr; char *ifstatstr; char *svcstr; char *svcauto; char *whostr; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); /* Get all data by section timestr is the date time for all status */ timestr = getdata("date"); if (!timestr) return; uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); /* TODO check when it is usefull */ cpuutilstr = getdata("cpu"); procsstr = getdata("procs"); diskstr = getdata("disk"); portsstr = getdata("ports"); memorystr = getdata("memory"); msgsstr = getdata("msg"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); svcstr = getdata("svcs"); svcauto = getdata("svcautorestart"); whostr = getdata("who"); bbwin_uptime_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr); bbwin_clock_report(hostname, clienttype, os, hinfo, fromline, timestr, clockstr, msgcachestr); bbwin_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, cpuutilstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "Name", NULL, procsstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 1, 2, 3, portsstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Avail", "Capacity", "Filesystem", diskstr); bbwin_svcs_report(hostname, clienttype, os, hinfo, fromline, timestr, 0, 1, 2, svcstr, svcauto); bbwin_who_report(hostname, clienttype, os, hinfo, fromline, timestr, whostr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); /* Data status */ unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); if (memorystr) { char *p; long memphystotal, memphysused, memactused, memacttotal, memswaptotal, memswapused; memphystotal = memswaptotal = memphysused = memswapused = memactused = memacttotal = -1; p = strstr(memorystr, "\nphysical:"); if (p) sscanf(p, "\nphysical: %ld %ld", &memphystotal, &memphysused); p = strstr(memorystr, "\npage:"); if (p) sscanf(p, "\npage: %ld %ld", &memswaptotal, &memswapused); p = strstr(memorystr, "\nvirtual:"); if (p) sscanf(p, "\nvirtual: %ld %ld", &memacttotal, &memactused); dbgprintf("DEBUG Memory %ld %ld %ld %ld %ld\n", memphystotal, memphysused, memactused, memswaptotal, memswapused); /* DEBUG TODO Remove*/ unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, memphysused, memactused, memswaptotal, memswapused); } splitmsg_done(); } xymon-4.3.7/xymond/client/aix.c0000664000175000017500000000677211617474177015777 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for AIX */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char aix_rcsid[] = "$Id: aix.c 6727 2011-08-07 11:35:27Z storner $"; void handle_aix_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *dfstr; char *msgsstr; char *netstatstr; char *ifstatstr; char *portsstr; char *vmstatstr; char *realmemstr; char *freememstr; char *swapmemstr; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); msgsstr = getdata("msgs"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); portsstr = getdata("ports"); vmstatstr = getdata("vmstat"); realmemstr = getdata("realmem"); freememstr = getdata("freemem"); swapmemstr = getdata("swap"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Free", "%Used", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "COMMAND", "CMD", psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_vmstat_report(hostname, clienttype, os, hinfo, fromline, timestr, vmstatstr); if (realmemstr && freememstr && swapmemstr) { long memphystotal = 0, memphysfree = 0, memswaptotal = 0, memswappct = 0; char *p; if (strncmp(realmemstr, "realmem ", 8) == 0) memphystotal = atol(realmemstr+8) / 1024L; if (sscanf(freememstr, "%*d %*d %*d %ld", &memphysfree) == 1) memphysfree /= 256L; p = strchr(swapmemstr, '\n'); if (p) p++; if (p && (sscanf(p, " %ldMB %ld%%", &memswaptotal, &memswappct) != 2)) { memswaptotal = memswappct = -1L; } unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, (memphystotal - memphysfree), -1L, memswaptotal, ((memswaptotal * memswappct) / 100L)); } splitmsg_done(); } xymon-4.3.7/xymond/client/sco_sv.c0000664000175000017500000001072511615341300016456 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for SCO_SV */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* Copyright (C) 2006-2008 Charles Goyard */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char sco_sv_rcsid[] = "$Id: sco_sv.c 6712 2011-07-31 21:01:52Z storner $"; void handle_sco_sv_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *dfstr; char *freememstr; char *memsizestr; char *swapstr; char *msgsstr; char *netstatstr; char *vmstatstr; char *ifstatstr; char *portsstr; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); memsizestr = getdata("memsize"); freememstr = getdata("freemem"); swapstr = getdata("swap"); msgsstr = getdata("msgs"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); vmstatstr = getdata("vmstat"); portsstr = getdata("ports"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Available", "Capacity", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "COMMAND", NULL, psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); unix_vmstat_report(hostname, clienttype, os, hinfo, fromline, timestr, vmstatstr); if(freememstr && memsizestr && swapstr) { long memphystotal, memphysfree, memswaptotal, memswapfree; char *p; memphystotal = memphysfree = 0; memphystotal = (atoi(memsizestr) / 1048576); if(sscanf(freememstr, "%*s %ld %ld %*d %*d", &memphysfree, &memswapfree) == 2) memphysfree /= 256; /* comes in 4kb pages */ else memphysfree = -1; memswaptotal = memswapfree = 0; if (swapstr) { p = strchr(swapstr, '\n'); /* Skip the header line */ while (p) { long stot, sfree; char *bol; bol = p+1; p = strchr(bol, '\n'); if (p) *p = '\0'; if (sscanf(bol, "%*s %*s %*d %ld %ld", &stot, &sfree) == 2) { memswaptotal += stot; memswapfree += sfree; } if (p) *p = '\n'; } memswaptotal /= 2048 ; memswapfree /= 2048; } unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, (memphystotal - memphysfree), -1, memswaptotal, (memswaptotal - memswapfree)); } splitmsg_done(); } xymon-4.3.7/xymond/client/darwin.c0000664000175000017500000000764211615341300016452 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for Darwin / Mac OS X */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char darwin_rcsid[] = "$Id: darwin.c 6712 2011-07-31 21:01:52Z storner $"; void handle_darwin_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *dfstr; char *meminfostr; char *msgsstr; char *netstatstr; char *ifstatstr; char *portsstr; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); meminfostr = getdata("meminfo"); msgsstr = getdata("msgs"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); portsstr = getdata("ports"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Avail", "Capacity", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "COMMAND", NULL, psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); /* No vmstat on Darwin */ if (meminfostr) { unsigned long pagesfree, pagesactive, pagesinactive, pageswireddown, pgsize; char *p; pagesfree = pagesactive = pagesinactive = pageswireddown = pgsize = -1; p = strstr(meminfostr, "page size of"); if (p && (sscanf(p, "page size of %lu bytes", &pgsize) == 1)) pgsize /= 1024; if (pgsize != -1) { p = strstr(meminfostr, "\nPages free:"); if (p) p = strchr(p, ':'); if (p) pagesfree = atol(p+1); p = strstr(meminfostr, "\nPages active:"); if (p) p = strchr(p, ':'); if (p) pagesactive = atol(p+1); p = strstr(meminfostr, "\nPages inactive:"); if (p) p = strchr(p, ':'); if (p) pagesinactive = atol(p+1); p = strstr(meminfostr, "\nPages wired down:"); if (p) p = strchr(p, ':'); if (p) pageswireddown = atol(p+1); if ((pagesfree >= 0) && (pagesactive >= 0) && (pagesinactive >= 0) && (pageswireddown >= 0)) { unsigned long memphystotal, memphysused; memphystotal = (pagesfree+pagesactive+pagesinactive+pageswireddown); memphystotal = memphystotal * pgsize / 1024; memphysused = (pagesactive+pagesinactive+pageswireddown); memphysused = memphysused * pgsize / 1024; unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, memphysused, -1, -1, -1); } } } splitmsg_done(); } xymon-4.3.7/xymond/client/zvse.c0000664000175000017500000006550711665414772016204 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for z/VSE or VSE/ESA */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* Copyright (C) 2006-2008 Rich Smrcina */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char zvse_rcsid[] = "$Id: zvse.c 6783 2011-11-30 11:56:42Z storner $"; static void zvse_cpu_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *cpuutilstr, char *uptimestr) { char *p; float load1, loadyellow, loadred; int recentlimit, ancientlimit, uptimecolor, maxclockdiff, clockdiffcolor; int uphour, upmin; char loadresult[100]; char myupstr[100]; long uptimesecs = -1; long upday; int cpucolor = COL_GREEN; char msgline[4096]; strbuffer_t *upmsg; if (!want_msgtype(hinfo, MSG_CPU)) return; if (!uptimestr) return; if (!cpuutilstr) return; uptimesecs = 0; /* * z/VSE: "Uptime: 1 Days, 13 Hours, 38 Minutes" */ sscanf(uptimestr,"Uptime: %ld Days, %d Hours, %d Minutes", &upday, &uphour, &upmin); uptimesecs = upday * 86400; uptimesecs += 60*(60*uphour + upmin); sprintf(myupstr, "%s\n", uptimestr); /* * Looking for average CPU Utilization in CPU message * Avg CPU=000% */ *loadresult = '\0'; p = strstr(cpuutilstr, "Avg CPU=") + 8 ; if (p) { if (sscanf(p, "%f%%", &load1) == 1) { sprintf(loadresult, "z/VSE CPU Utilization %3.0f%%\n", load1); } } get_cpu_thresholds(hinfo, clientclass, &loadyellow, &loadred, &recentlimit, &ancientlimit, &uptimecolor, &maxclockdiff, &clockdiffcolor); upmsg = newstrbuffer(0); if (load1 > loadred) { cpucolor = COL_RED; addtobuffer(upmsg, "&red Load is CRITICAL\n"); } else if (load1 > loadyellow) { cpucolor = COL_YELLOW; addtobuffer(upmsg, "&yellow Load is HIGH\n"); } if ((uptimesecs != -1) && (recentlimit != -1) && (uptimesecs < recentlimit)) { if (cpucolor != COL_RED) cpucolor = uptimecolor; sprintf(msgline, "&%s Machine recently rebooted\n", colorname(uptimecolor)); addtobuffer(upmsg, msgline); } if ((uptimesecs != -1) && (ancientlimit != -1) && (uptimesecs > ancientlimit)) { if (cpucolor != COL_RED) cpucolor = uptimecolor; sprintf(msgline, "&%s Machine has been up more than %d days\n", colorname(uptimecolor), (ancientlimit / 86400)); addtobuffer(upmsg, msgline); } init_status(cpucolor); sprintf(msgline, "status %s.cpu %s %s %s %s %s\n", commafy(hostname), colorname(cpucolor), (timestr ? timestr : ""), loadresult, myupstr, cpuutilstr); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } static void zvse_paging_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *pagingstr) { char *p; int ipagerate, pagingyellow, pagingred; float fpagerate=0.0; char pagingresult[100]; int pagingcolor = COL_GREEN; char msgline[4096]; strbuffer_t *upmsg; if (!pagingstr) return; /* * Looking for Paging rate in message * Page Rate=0.00 /sec */ *pagingresult = '\0'; ipagerate=0; p = strstr(pagingstr, "Page Rate=") + 10; if (p) { if (sscanf(p, "%f", &fpagerate) == 1) { ipagerate=fpagerate + 0.5; /* Rounding up */ sprintf(pagingresult, "z/VSE Paging Rate %d per second\n", ipagerate); } } else sprintf(pagingresult, "Can not find page rate value in:\n%s\n", pagingstr); get_paging_thresholds(hinfo, clientclass, &pagingyellow, &pagingred); upmsg = newstrbuffer(0); if (ipagerate > pagingred) { pagingcolor = COL_RED; addtobuffer(upmsg, "&red Paging Rate is CRITICAL\n"); } else if (ipagerate > pagingyellow) { pagingcolor = COL_YELLOW; addtobuffer(upmsg, "&yellow Paging Rate is HIGH\n"); } init_status(pagingcolor); sprintf(msgline, "status %s.paging %s %s %s %s\n", commafy(hostname), colorname(pagingcolor), (timestr ? timestr : ""), pagingresult, pagingstr); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } static void zvse_cics_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *cicsstr) { char cicsappl[9], cicsdate[11], cicstime[9]; int numtrans=0, cicsok=1; float dsapct=0.0; float edsapct=0.0; char cicsresult[100]; char tempresult[100]; char *cicsentry = NULL; int cicscolor = COL_GREEN; int dsayel, dsared, edsayel, edsared; char msgline[4096]; char cicsokmsg[]="All CICS Systems OK"; char cicsnotokmsg[]="One or more CICS Systems not OK"; strbuffer_t *headline; strbuffer_t *upmsg; strbuffer_t *cicsmsg; if (!cicsstr) return; cicsmsg = newstrbuffer(0); upmsg = newstrbuffer(0); headline= newstrbuffer(0); addtobuffer(headline, "Appl ID Trans DSA Pct EDSA Pct\n"); /* * * Each CICS system reporting uses one line in the message, the format is: * applid date time numtrans dsapct edsapct * applid is the CICS application id * date and time are the date and time of the report * numtrans is the number of transactions that were executed in CICS since the last report * dsapct is the DSA utilization percentage * edsapct is the EDSA utilization percentage * */ if (cicsstr) { cicsentry=strtok(cicsstr, "\n"); while (cicsentry != NULL) { sscanf(cicsentry, "%8s %10s %8s %d %f %f", cicsappl, cicsdate, cicstime, &numtrans, &dsapct, &edsapct); sprintf(cicsresult,"%-8s %6d %3.1f %3.1f\n", cicsappl, numtrans, dsapct, edsapct); addtobuffer(cicsmsg, cicsresult); if (numtrans == -1 ) { if (cicscolor < COL_YELLOW) cicscolor = COL_YELLOW; cicsok=0; sprintf(tempresult,"&yellow CICS system %s not responding, removed\n", cicsappl); addtobuffer(upmsg, tempresult); } /* Get CICS thresholds for this application ID. */ get_cics_thresholds(hinfo, clientclass, cicsappl, &dsayel, &dsared, &edsayel, &edsared); /* The threshold of the DSA values for each CICS must be checked in this loop. */ if (dsapct > dsared) { if (cicscolor < COL_RED) cicscolor = COL_RED; cicsok=0; sprintf(tempresult,"&red %s DSA Utilization is CRITICAL\n", cicsappl); addtobuffer(upmsg, tempresult); } else if (dsapct > dsayel) { if (cicscolor < COL_YELLOW) cicscolor = COL_YELLOW; cicsok=0; sprintf(tempresult,"&yellow %s DSA Utilization is HIGH\n", cicsappl); addtobuffer(upmsg, tempresult); } if (edsapct > edsared) { if (cicscolor < COL_RED) cicscolor = COL_RED; cicsok=0; sprintf(tempresult,"&red %s EDSA Utilization is CRITICAL\n", cicsappl); addtobuffer(upmsg, tempresult); } else if (edsapct > edsayel) { if (cicscolor < COL_YELLOW) cicscolor = COL_YELLOW; cicsok=0; sprintf(tempresult,"&yellow %s EDSA Utilization is HIGH\n", cicsappl); addtobuffer(upmsg, tempresult); } init_status(cicscolor); cicsentry=strtok(NULL, "\n"); } } sprintf(msgline, "status %s.cics %s %s %s\n", commafy(hostname), colorname(cicscolor), (timestr ? timestr : ""), ( (cicsok==1) ? cicsokmsg : cicsnotokmsg) ); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); } if (STRBUFLEN(cicsmsg)) { addtostrstatus(headline); addtostrstatus(cicsmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(headline); freestrbuffer(upmsg); freestrbuffer(cicsmsg); } static void zvse_jobs_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *psstr) { int pscolor = COL_GREEN; int pchecks; int cmdofs = -1; char msgline[4096]; strbuffer_t *monmsg; static strbuffer_t *countdata = NULL; int anycountdata = 0; char *group; if (!want_msgtype(hinfo, MSG_PROCS)) return; if (!psstr) return; if (!countdata) countdata = newstrbuffer(0); clearalertgroups(); monmsg = newstrbuffer(0); sprintf(msgline, "data %s.proccounts\n", commafy(hostname)); addtobuffer(countdata, msgline); cmdofs = 0; /* Command offset for z/VSE isn't necessary */ pchecks = clear_process_counts(hinfo, clientclass); if (pchecks == 0) { /* Nothing to check */ sprintf(msgline, "&%s No process checks defined\n", colorname(noreportcolor)); addtobuffer(monmsg, msgline); pscolor = noreportcolor; } else if (cmdofs >= 0) { /* Count how many instances of each monitored process is running */ char *pname, *pid, *bol, *nl; int pcount, pmin, pmax, pcolor, ptrack; bol = psstr; while (bol) { nl = strchr(bol, '\n'); /* Take care - the ps output line may be shorter than what we look at */ if (nl) { *nl = '\0'; if ((nl-bol) > cmdofs) add_process_count(bol+cmdofs); *nl = '\n'; bol = nl+1; } else { if (strlen(bol) > cmdofs) add_process_count(bol+cmdofs); bol = NULL; } } /* Check the number found for each monitored process */ while ((pname = check_process_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, &group)) != NULL) { char limtxt[1024]; if (pmax == -1) { if (pmin > 0) sprintf(limtxt, "%d or more", pmin); else if (pmin == 0) sprintf(limtxt, "none"); } else { if (pmin > 0) sprintf(limtxt, "between %d and %d", pmin, pmax); else if (pmin == 0) sprintf(limtxt, "at most %d", pmax); } if (pcolor == COL_GREEN) { sprintf(msgline, "&green %s (found %d, req. %s)\n", pname, pcount, limtxt); addtobuffer(monmsg, msgline); } else { if (pcolor > pscolor) pscolor = pcolor; sprintf(msgline, "&%s %s (found %d, req. %s)\n", colorname(pcolor), pname, pcount, limtxt); addtobuffer(monmsg, msgline); addalertgroup(group); } if (ptrack) { /* Save the count data for later DATA message to track process counts */ if (!pid) pid = "default"; sprintf(msgline, "%s:%u\n", pid, pcount); addtobuffer(countdata, msgline); anycountdata = 1; } } } else { pscolor = COL_YELLOW; sprintf(msgline, "&yellow Expected string not found in ps output header\n"); addtobuffer(monmsg, msgline); } /* Now we know the result, so generate a status message */ init_status(pscolor); group = getalertgroups(); if (group) sprintf(msgline, "status/group:%s ", group); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.procs %s %s - Processes %s\n", commafy(hostname), colorname(pscolor), (timestr ? timestr : ""), ((pscolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info about what's wrong */ if (STRBUFLEN(monmsg)) { addtostrstatus(monmsg); addtostatus("\n"); } /* And the full list of jobs for those who want it */ if (pslistinprocs) { /* * Format the list of virtual machines into four per line, * this list could be fairly long. */ char *tmpstr, *tok; /* Make a copy of psstr, strtok() will be changing it */ tmpstr = strdup(psstr); /* Use strtok() to split string into pieces delimited by newline */ tok = strtok(tmpstr, "\n"); while (tok) { sprintf(msgline, "%s\n", tok); addtostatus(msgline); tok = strtok(NULL, "\n"); } free(tmpstr); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(monmsg); if (anycountdata) sendmessage(STRBUF(countdata), NULL, XYMON_TIMEOUT, NULL); clearstrbuffer(countdata); } static void zvse_memory_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *memstr) { int usedyellow, usedred; /* Thresholds for total used system memory */ int sysmemok=1; long totmem, availmem; float pctavail, pctused; char memorystr[1024]; int memorycolor = COL_GREEN; char memokmsg[]="Memory OK"; char memnotokmsg[]="Memory Not OK"; char msgline[4096]; strbuffer_t *upmsg; if (!memstr) return; upmsg = newstrbuffer(0); /* * The message is just two values, the total system memory and * the available memory; both values are in K. * tttttt aaaaaa */ sscanf(memstr, "%ld %ld", &totmem, &availmem); pctavail = ((float)availmem / (float)totmem) * 100; pctused = 100 - pctavail; sprintf(memorystr, "z/VSE VSIZE Utilization %3.1f%%\nMemory Allocated %ldK, Memory Available %ldK\n", pctused, totmem, availmem); get_zvsevsize_thresholds(hinfo, clientclass, &usedyellow, &usedred); if (pctused > (float)usedred) { memorycolor = COL_RED; sysmemok=0; addtobuffer(upmsg, "&red VSIZE Utilization is CRITICAL\n"); } else if (pctused > (float)usedyellow) { memorycolor = COL_YELLOW; sysmemok=0; addtobuffer(upmsg, "&yellow VSIZE Utilization is HIGH\n"); } init_status(memorycolor); sprintf(msgline, "status %s.memory %s %s %s\n%s", commafy(hostname), colorname(memorycolor), (timestr ? timestr : ""), ( (sysmemok==1) ? memokmsg : memnotokmsg ), memorystr); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } static void zvse_getvis_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *gvstr) { char *q; int gv24yel, gv24red, gvanyyel, gvanyred; /* Thresholds for getvis */ int getvisok=1; float used24p, usedanyp; char jinfo[11], pid[4], jobname[9]; int size24, used24, free24, sizeany, usedany, freeany; char *getvisentry = NULL; char tempresult[128]; char getvisresult[128]; char msgline[4096]; int memorycolor = COL_GREEN; char getvisokmsg[]="Getvis OK"; char getvisnotokmsg[]="Getvis Not OK"; strbuffer_t *getvismsg; strbuffer_t *upmsg; strbuffer_t *headline; if (!gvstr) return; getvismsg = newstrbuffer(0); upmsg = newstrbuffer(0); headline = newstrbuffer(0); /* * The getvis message is a table if the partitions requested including the SVA. * The format of the table is: * * Partition Used/24 Free/24 Used/Any Free/Any * SVA 748 1500 2056 5604 * F1 824 264 824 264 * Z1-CICSICCF 7160 3844 27516 4992 * O1-CICS1 5912 5092 31584 19352 */ addtobuffer(headline, "z/VSE Getvis Map\nPID Jobname Size24 Used24 Free24 SizeAny UsedAny FreeAny Used24% UsedAny%\n"); getvisentry=strtok(gvstr, "\n"); getvisentry=strtok(NULL, "\n"); /* Skip heading line */ while (getvisentry != NULL) { sscanf(getvisentry, "%s %d %d %d %d", jinfo, &used24, &free24, &usedany, &freeany); q = strchr(jinfo, '-'); /* Check if jobname passed */ if (q) { strncpy(pid, jinfo, 2); /* Copy partition ID */ q++; /* Increment pointer */ strcpy(jobname,q); /* Copy jobname */ } else { strcpy(pid,jinfo); /* Just copy jinfo into partition ID */ strcpy(jobname, "- "); /* Jobname placeholder */ } size24 = used24 + free24; sizeany = usedany + freeany; used24p = ( (float)used24 / (float)size24 ) * 100; usedanyp = ( (float)usedany / (float)sizeany ) * 100; sprintf(getvisresult,"%-3s %-8s %6d %6d %6d %6d %6d %6d %3.0f %3.0f\n", pid, jobname, size24, used24, free24, sizeany, usedany, freeany, used24p, usedanyp); get_zvsegetvis_thresholds(hinfo, clientclass, pid, &gv24yel, &gv24red, &gvanyyel, &gvanyred); if (used24p > (float)gv24red) { memorycolor = COL_RED; getvisok=0; sprintf(tempresult,"&red 24-bit Getvis utilization for %s is CRITICAL\n", pid); addtobuffer(upmsg, tempresult); } else if (used24p > (float)gv24yel) { memorycolor = COL_YELLOW; getvisok=0; sprintf(tempresult,"&yellow 24-bit Getvis utilization for %s is HIGH\n", pid); addtobuffer(upmsg, tempresult); } if (usedanyp > (float)gvanyred) { memorycolor = COL_RED; getvisok=0; sprintf(tempresult,"&red Any Getvis utilization for %s is CRITICAL\n", pid); addtobuffer(upmsg, tempresult); } else if (usedanyp > (float)gvanyyel) { memorycolor = COL_YELLOW; getvisok=0; sprintf(tempresult,"&yellow Any Getvis utilization for %s is HIGH\n", pid); addtobuffer(upmsg, tempresult); } addtobuffer(getvismsg, getvisresult); getvisentry=strtok(NULL, "\n"); } init_status(memorycolor); sprintf(msgline, "status %s.getvis %s %s %s\n", commafy(hostname), colorname(memorycolor), (timestr ? timestr : ""), ( (getvisok==1) ? getvisokmsg : getvisnotokmsg ) ); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (STRBUFLEN(getvismsg)) { addtostrstatus(headline); addtostrstatus(getvismsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(headline); freestrbuffer(upmsg); freestrbuffer(getvismsg); } void zvse_nparts_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *npartstr) { char npdispstr[256]; long nparts, runparts, partsavail; int npartsyellow, npartsred; float partutil; int npartcolor = COL_GREEN; char msgline[4096]; strbuffer_t *upmsg; if (!npartstr) return; sscanf(npartstr, "%ld %ld", &nparts, &runparts); /* * The nparts message is two values that indicate the maximum number of partitions * configured in the system (based on the NPARTS value in the IPL proc) and * the number of partitions currently running jobs: * The format of the data is: * * nnnnnnn mmmmmmm * */ partsavail = nparts - runparts; partutil = ((float)runparts / (float)nparts) * 100; get_asid_thresholds(hinfo, clientclass, &npartsyellow, &npartsred); upmsg = newstrbuffer(0); if ((int)partutil > npartsred) { if (npartcolor < COL_RED) npartcolor = COL_RED; addtobuffer(upmsg, "&red NPARTS Utilization is CRITICAL\n"); } else if ((int)partutil > npartsyellow) { if (npartcolor < COL_YELLOW) npartcolor = COL_YELLOW; addtobuffer(upmsg, "&yellow NPARTS Utilization is HIGH\n"); } *npdispstr = '\0'; sprintf(npdispstr, "Nparts: %8ld Free: %8ld Used: %8ld %3.1f\n",nparts,partsavail,runparts,partutil); init_status(npartcolor); sprintf(msgline, "status %s.nparts %s %s\n%s", commafy(hostname), colorname(npartcolor), (timestr ? timestr : ""), npdispstr); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } void handle_zvse_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *cpuutilstr; char *pagingstr; char *cicsstr; char *uptimestr; char *dfstr; char *jobsstr; /* z/VSE Running jobs */ char *portsstr; char *memstr; /* System Memory data */ char *gvstr; /* GETVIS data */ char *npartstr; /* Num Parts */ char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); cpuutilstr = getdata("cpu"); pagingstr = getdata("paging"); cicsstr = getdata("cics"); dfstr = getdata("df"); jobsstr = getdata("jobs"); portsstr = getdata("ports"); memstr = getdata("memory"); gvstr = getdata("getvis"); npartstr = getdata("nparts"); zvse_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, cpuutilstr, uptimestr); zvse_paging_report(hostname, clienttype, os, hinfo, fromline, timestr, pagingstr); zvse_cics_report(hostname, clienttype, os, hinfo, fromline, timestr, cicsstr); zvse_jobs_report(hostname, clienttype, os, hinfo, fromline, timestr, jobsstr); zvse_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memstr); zvse_getvis_report(hostname, clienttype, os, hinfo, fromline, timestr, gvstr); zvse_nparts_report(hostname, clienttype, os, hinfo, fromline, timestr, npartstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Available", "Cap", "Mounted", dfstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); splitmsg_done(); } xymon-4.3.7/xymond/client/mqcollect.c0000664000175000017500000001723411615341300017147 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for MQ collector */ /* */ /* Copyright (C) 2009-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char mqcollect_rcsid[] = "$Id: mqcollect.c 6712 2011-07-31 21:01:52Z storner $"; void mqcollect_flush_status(int color, char *fromline, time_t timestamp, char *hostname, char *qmid, strbuffer_t *redsummary, strbuffer_t *yellowsummary, strbuffer_t *greensummary, char *clienttext) { char *groups; char msgline[1024]; /* Generate the status message */ groups = getalertgroups(); init_status(color); if (groups) sprintf(msgline, "status/group:%s ", groups); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.mq %s %s\n", hostname, colorname(color), ctime(×tamp)); addtostatus(msgline); if (STRBUFLEN(redsummary) > 0) { addtostrstatus(redsummary); addtostatus("\n"); } if (STRBUFLEN(yellowsummary) > 0) { addtostrstatus(yellowsummary); addtostatus("\n"); } if (STRBUFLEN(greensummary) > 0) { addtostrstatus(greensummary); addtostatus("\n"); } addtostatus(clienttext); addtostatus("\n"); addtostatus(fromline); finish_status(); } void handle_mqcollect_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *qmline = "Starting MQSC for queue manager "; strbuffer_t *redsummary = newstrbuffer(0); strbuffer_t *yellowsummary = newstrbuffer(0); strbuffer_t *greensummary = newstrbuffer(0); char *chngroup, *bol, *eoln, *clienttext; int color = COL_GREEN; char fromline[1024], msgline[1024]; char *qmid = NULL, *qnam = NULL; int qlen = -1, qage = -1; char *chnnam = NULL, *chnstatus = NULL; enum { PARSING_QL, PARSING_QS, PARSING_CHS, PARSER_FLOAT } pstate = PARSER_FLOAT; int lastline = 0; sprintf(fromline, "\nStatus message received from %s\n", sender); bol = strchr(clientdata, '\n'); if (bol) bol++; clienttext = bol; while (bol) { eoln = strchr(bol, '\n'); if (eoln) *eoln = '\0'; else lastline = 1; bol += strspn(bol, " \t"); if (strncmp(bol, qmline, strlen(qmline)) == 0) { char *p; if (qmid) xfree(qmid); qmid = strdup(bol+strlen(qmline)); p = strrchr(qmid, '.'); if (p) *p = '\0'; } else if ( (strncmp(bol, "AMQ8409:", 8) == 0) || /* "ql" command - Queue details, incl. depth */ (strncmp(bol, "AMQ8450:", 8) == 0) || /* "qs" command - Queue status */ (strncmp(bol, "AMQ8417:", 8) == 0) || /* "chs" command - Channel status */ lastline ) { if ( ((pstate == PARSING_QL) || (pstate == PARSING_QS)) && qmid && qnam && (qlen >= 0)) { /* Got a full queue depth status */ int warnlen, critlen, warnage, critage; char *trackid; get_mqqueue_thresholds(hinfo, clienttype, qmid, qnam, &warnlen, &critlen, &warnage, &critage, &trackid); if ((critlen != -1) && (qlen >= critlen)) { color = COL_RED; sprintf(msgline, "&red Queue %s:%s has depth %d (critical: %d, warn: %d)\n", qmid, qnam, qlen, critlen, warnlen); addtobuffer(redsummary, msgline); } else if ((warnlen != -1) && (qlen >= warnlen)) { if (color < COL_YELLOW) color = COL_YELLOW; sprintf(msgline, "&yellow Queue %s:%s has depth %d (warn: %d, critical: %d)\n", qmid, qnam, qlen, warnlen, critlen); addtobuffer(yellowsummary, msgline); } else if ((warnlen != -1) || (critlen != -1)) { sprintf(msgline, "&green Queue %s:%s has depth %d (warn: %d, critical: %d)\n", qmid, qnam, qlen, warnlen, critlen); addtobuffer(greensummary, msgline); } if ((pstate == PARSING_QS) && (qage >= 0)) { if ((critage != -1) && (qage >= critage)) { color = COL_RED; sprintf(msgline, "&red Queue %s:%s has age %d (critical: %d, warn: %d)\n", qmid, qnam, qage, critage, warnage); addtobuffer(redsummary, msgline); } else if ((warnage != -1) && (qage >= warnage)) { if (color < COL_YELLOW) color = COL_YELLOW; sprintf(msgline, "&yellow Queue %s:%s has age %d (warn: %d, critical: %d)\n", qmid, qnam, qage, warnage, critage); addtobuffer(yellowsummary, msgline); } else if ((warnage != -1) || (critage != -1)) { sprintf(msgline, "&green Queue %s:%s has age %d (warn: %d, critical: %d)\n", qmid, qnam, qage, warnage, critage); addtobuffer(greensummary, msgline); } } if (trackid) { /* FIXME: Send "data" message for creating queue-length RRD */ } pstate = PARSER_FLOAT; } if ((pstate == PARSING_CHS) && qmid && chnnam && chnstatus) { /* Got a full channel status */ int chncolor; if (get_mqchannel_params(hinfo, clienttype, qmid, chnnam, chnstatus, &chncolor)) { if (chncolor > color) color = chncolor; switch (chncolor) { case COL_RED: sprintf(msgline, "&red Channel %s:%s has status %s\n", qmid, chnnam, chnstatus); addtobuffer(redsummary, msgline); break; case COL_YELLOW: sprintf(msgline, "&yellow Channel %s:%s has status %s\n", qmid, chnnam, chnstatus); addtobuffer(yellowsummary, msgline); break; case COL_GREEN: sprintf(msgline, "&green Channel %s:%s has status %s\n", qmid, chnnam, chnstatus); addtobuffer(greensummary, msgline); break; } } pstate = PARSER_FLOAT; } if (qnam) xfree(qnam); qlen = qage = -1; if (chnnam) xfree(chnnam); if (chnstatus) xfree(chnstatus); if (strncmp(bol, "AMQ8409:", 8) == 0) pstate = PARSING_QL; else if (strncmp(bol, "AMQ8450:", 8) == 0) pstate = PARSING_QS; else if (strncmp(bol, "AMQ8417:", 8) == 0) pstate = PARSING_CHS; else pstate = PARSER_FLOAT; } else if ((pstate == PARSING_QL) || (pstate == PARSING_QS)) { char *bdup = strdup(bol); char *tok = strtok(bdup, " \t"); while (tok) { if (strncmp(tok, "QUEUE(", 6) == 0) { char *p; qnam = strdup(tok+6); p = strchr(qnam, ')'); if (p) *p = '\0'; } else if (strncmp(tok, "CURDEPTH(", 9) == 0) { qlen = atoi(tok+9); } else if (strncmp(tok, "MSGAGE(", 7) == 0) { if (isdigit(*(tok+7))) qage = atoi(tok+7); } tok = strtok(NULL, " \t"); } xfree(bdup); } else if (pstate == PARSING_CHS) { char *bdup = strdup(bol); char *tok = strtok(bdup, " \t"); while (tok) { if (strncmp(tok, "CHANNEL(", 8) == 0) { char *p; chnnam = strdup(tok+8); p = strchr(chnnam, ')'); if (p) *p = '\0'; } else if (strncmp(tok, "STATUS(", 7) == 0) { char *p; chnstatus = strdup(tok+7); p = strchr(chnstatus, ')'); if (p) *p = '\0'; } tok = strtok(NULL, " \t"); } xfree(bdup); } if (eoln) { *eoln = '\n'; bol = eoln+1; } else bol = NULL; } mqcollect_flush_status(color, fromline, timestamp, hostname, qmid, redsummary, yellowsummary, greensummary, clienttext); if (qmid) xfree(qmid); freestrbuffer(greensummary); freestrbuffer(yellowsummary); freestrbuffer(redsummary); } xymon-4.3.7/xymond/client/osf.c0000664000175000017500000001054411615341300015750 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for OSF */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char osf_rcsid[] = "$Id: osf.c 6712 2011-07-31 21:01:52Z storner $"; void handle_osf_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *dfstr; char *msgsstr; char *netstatstr; char *ifstatstr; char *portsstr; char *vmstatstr; char *memorystr; char *swapstr; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); msgsstr = getdata("msgs"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); portsstr = getdata("ports"); vmstatstr = getdata("vmstat"); memorystr = getdata("memory"); swapstr = getdata("swap"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Available", "Capacity", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "CMD", "COMMAND", psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); unix_vmstat_report(hostname, clienttype, os, hinfo, fromline, timestr, vmstatstr); if (memorystr && swapstr) { char *p, *bol; long phystotal, physfree, swaptotal, swapfree, pagecnt, pagesize; /* * Total Physical Memory = 5120.00 M * = 655360 pages * * ... * * Managed Pages Break Down: * * free pages = 499488 * */ phystotal = physfree = swaptotal = swapfree = -1; pagesize = 8; /* Default - appears to be the OSF/1 standard */ bol = strstr(memorystr, "\nTotal Physical Memory ="); if (bol) { p = strchr(bol, '='); phystotal = atol(p+1); bol = strchr(p, '\n'); if (bol) { bol++; bol += strspn(bol, " \t"); if (*bol == '=') { pagecnt = atol(bol+1); pagesize = (phystotal * 1024) / pagecnt; } } } bol = strstr(memorystr, "\nManaged Pages Break Down:"); if (bol) { bol = strstr(bol, "free pages ="); if (bol) { p = strchr(bol, '='); physfree = atol(p+1) * pagesize / 1024; } } bol = strstr(swapstr, "\nTotal swap allocation:"); if (bol) { unsigned long swappages, freepages; int n1, n2; n1 = n2 = 0; p = strstr(bol, "Allocated space:"); if (p) n1 = sscanf(p, "Allocated space: %lu pages", &swappages); p = strstr(bol, "Available space:"); if (p) n2 = sscanf(p, "Available space: %lu pages", &freepages); if ((n1 == 1) && (n2 == 1)) { swaptotal = swappages * pagesize / 1024; swapfree = freepages * pagesize / 1024; } } unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, phystotal, (phystotal - physfree), -1, swaptotal, (swaptotal - swapfree)); } splitmsg_done(); } xymon-4.3.7/xymond/client/freebsd.c0000664000175000017500000001003211615341300016563 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for FreeBSD */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char freebsd_rcsid[] = "$Id: freebsd.c 6712 2011-07-31 21:01:52Z storner $"; void handle_freebsd_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *dfstr; char *meminfostr; char *swapinfostr; char *vmtotalstr; char *msgsstr; char *netstatstr; char *ifstatstr; char *portsstr; char *vmstatstr; char *p; char fromline[1024]; unsigned long memphystotal = 0, memphysfree = 0, memphysused = 0; unsigned long memswaptotal = 0, memswapfree = 0, memswapused = 0; int found = 0; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clockstr"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); meminfostr = getdata("meminfo"); swapinfostr = getdata("swapinfo"); msgsstr = getdata("msgs"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); portsstr = getdata("ports"); vmstatstr = getdata("vmstat"); vmtotalstr = getdata("vmtotal"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Avail", "Capacity", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "COMMAND", NULL, psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); unix_vmstat_report(hostname, clienttype, os, hinfo, fromline, timestr, vmstatstr); if (meminfostr) { p = strstr(meminfostr, "Total:"); if (p) { memphystotal = atol(p+6); found++; } } if (vmtotalstr) { p = strstr(vmtotalstr, "\nFree Memory Pages:"); if (p) { memphysfree = atol(p + 18); found++; } } if ((found == 1) && meminfostr) { p = strstr(meminfostr, "Free:"); if (p) { memphysfree = atol(p+5); found++; } memphysused = memphystotal - memphysfree; } if (swapinfostr) { found++; p = strchr(swapinfostr, '\n'); /* Skip the header line */ while (p) { long stot, sused, sfree; char *bol; bol = p+1; p = strchr(bol, '\n'); if (p) *p = '\0'; if (sscanf(bol, "%*s %ld %ld %ld", &stot, &sused, &sfree) == 3) { memswaptotal += stot; memswapused += sused; memswapfree += sfree; } if (p) *p = '\n'; } memswaptotal /= 1024; memswapused /= 1024; memswapfree /= 1024; } if (found >= 2) { unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, memphysused, -1, memswaptotal, memswapused); } splitmsg_done(); } xymon-4.3.7/xymond/client/hpux.c0000664000175000017500000000712311615341300016144 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for HP-UX */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char hpux_rcsid[] = "$Id: hpux.c 6712 2011-07-31 21:01:52Z storner $"; void handle_hpux_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *dfstr; char *memorystr; char *swapinfostr; char *msgsstr; char *netstatstr; char *ifstatstr; char *portsstr; char *vmstatstr; char *p; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); memorystr = getdata("memory"); swapinfostr = getdata("swapinfo"); msgsstr = getdata("msgs"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); portsstr = getdata("ports"); vmstatstr = getdata("vmstat"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Available", "Capacity", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "COMMAND", NULL, psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); unix_vmstat_report(hostname, clienttype, os, hinfo, fromline, timestr, vmstatstr); if (memorystr && swapinfostr) { unsigned long memphystotal, memphysfree, memphysused; unsigned long memswaptotal, memswapfree, memswapused; int found = 0; memphystotal = memphysfree = memphysused = 0; memswaptotal = memswapfree = memswapused = 0; p = strstr(memorystr, "Total:"); if (p) { memphystotal = atol(p+6); found++; } p = strstr(memorystr, "Free:"); if (p) { memphysfree = atol(p+5); found++; } memphysused = memphystotal - memphysfree; p = strstr(swapinfostr, "\ntotal"); if (p && (sscanf(p, "\ntotal %lu %lu %lu", &memswaptotal, &memswapused, &memswapfree) >= 2)) { found++; } if (found == 3) { unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, memphysused, -1, memswaptotal, memswapused); } } splitmsg_done(); } xymon-4.3.7/xymond/client/zvm.c0000664000175000017500000003432111665414772016017 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for z/VM */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* Copyright (C) 2006-2008 Rich Smrcina */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char zvm_rcsid[] = "$Id: zvm.c 6783 2011-11-30 11:56:42Z storner $"; static void zvm_cpu_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *cpuutilstr, char *uptimestr) { char *p; float load1, loadyellow, loadred; int recentlimit, ancientlimit, uptimecolor, maxclockdiff, clockdiffcolor; int uphour, upmin; char loadresult[100]; char myupstr[100]; long uptimesecs = -1; long upday; int cpucolor = COL_GREEN; char msgline[1024]; strbuffer_t *upmsg; if (!want_msgtype(hinfo, MSG_CPU)) return; if (!cpuutilstr) return; if (!uptimestr) return; uptimesecs = 0; /* * z/VM: "Uptime: 1 Days, 13 Hours, 38 Minutes" */ sscanf(uptimestr,"Uptime: %ld Days, %d Hours, %d Minutes", &upday, &uphour, &upmin); uptimesecs = upday * 86400; uptimesecs += 60*(60*uphour + upmin); sprintf(myupstr, "%s\n", uptimestr); /* * Looking for average CPU Utilization in 'IND' command response * AVGPROC-000% */ *loadresult = '\0'; p = strstr(cpuutilstr, "AVGPROC-") + 8 ; if (p) { if (sscanf(p, "%f%%", &load1) == 1) { sprintf(loadresult, "z/VM CPU Utilization %3.0f%%\n", load1); } } get_cpu_thresholds(hinfo, clientclass, &loadyellow, &loadred, &recentlimit, &ancientlimit, &uptimecolor, &maxclockdiff, &clockdiffcolor); upmsg = newstrbuffer(0); if (load1 > loadred) { cpucolor = COL_RED; addtobuffer(upmsg, "&red Load is CRITICAL\n"); } else if (load1 > loadyellow) { cpucolor = COL_YELLOW; addtobuffer(upmsg, "&yellow Load is HIGH\n"); } if ((uptimesecs != -1) && (recentlimit != -1) && (uptimesecs < recentlimit)) { if (cpucolor != COL_RED) cpucolor = uptimecolor; sprintf(msgline, "&%s Machine recently rebooted\n", colorname(uptimecolor)); addtobuffer(upmsg, msgline); } if ((uptimesecs != -1) && (ancientlimit != -1) && (uptimesecs > ancientlimit)) { if (cpucolor != COL_RED) cpucolor = uptimecolor; sprintf(msgline, "&%s Machine has been up more than %d days\n", colorname(uptimecolor), (ancientlimit / 86400)); addtobuffer(upmsg, msgline); } init_status(cpucolor); sprintf(msgline, "status %s.cpu %s %s %s %s %s\n", commafy(hostname), colorname(cpucolor), (timestr ? timestr : ""), loadresult, myupstr, cpuutilstr); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } static void zvm_paging_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *cpuutilstr) { char *p; int pagerate, pagingyellow, pagingred; char pagingresult[100]; int pagingcolor = COL_GREEN; char msgline[256]; strbuffer_t *upmsg; if (!cpuutilstr) return; /* * Looking for Paging rate info in 'IND' command response * PAGING-0000/SEC */ *pagingresult = '\0'; /* Skip past three newlines in message to the PAGING text */ p=strstr(cpuutilstr,"PAGING-") + 7; if (sscanf(p, "%d/SEC", &pagerate) == 1) { sprintf(pagingresult, "z/VM Paging Rate %d per second\n", pagerate); } get_paging_thresholds(hinfo, clientclass, &pagingyellow, &pagingred); upmsg = newstrbuffer(0); if (pagerate > pagingred) { pagingcolor = COL_RED; addtobuffer(upmsg, "&red Paging Rate is CRITICAL\n"); } else if (pagerate > pagingyellow) { pagingcolor = COL_YELLOW; addtobuffer(upmsg, "&yellow Paging Rate is HIGH\n"); } init_status(pagingcolor); sprintf(msgline, "status %s.paging %s %s %s %s\n", commafy(hostname), colorname(pagingcolor), (timestr ? timestr : ""), pagingresult, cpuutilstr); addtostatus(msgline); if (STRBUFLEN(upmsg)) { addtostrstatus(upmsg); addtostatus("\n"); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(upmsg); } static void zvm_mdc_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *cpuutilstr) { char *p; int mdcreads, mdcwrites, mdchitpct; char mdcresult[100]; char msgline[256]; strbuffer_t *msg; if (!cpuutilstr) return; msg = newstrbuffer(0); /* * Looking for MDC info in 'IND' command response * MDC READS-000001/SEC WRITES-000001/SEC HIT RATIO-098% */ *mdcresult = '\0'; /* Skip past three newlines in message to the PAGING text */ p=strstr(cpuutilstr,"READS-"); if (p) { p += 6; sscanf(p, "%d/SEC", &mdcreads); p=strstr(cpuutilstr,"WRITES-") + 7; sscanf(p, "%d/SEC", &mdcwrites); p=strstr(cpuutilstr,"RATIO-") + 6; sscanf(p, "%d", &mdchitpct); sprintf(msgline, "data %s.mdc\n%s\n%d:%d:%d\n", commafy(hostname), osname(os), mdcreads, mdcwrites, mdchitpct); addtobuffer(msg, msgline); sendmessage(STRBUF(msg), NULL, XYMON_TIMEOUT, NULL); } freestrbuffer(msg); } static void zvm_users_report(char *hostname, char *clientclass, enum ostype_t os, void *hinfo, char *fromline, char *timestr, char *psstr) { int pscolor = COL_GREEN; int pchecks; int cmdofs = -1; char msgline[4096]; strbuffer_t *monmsg; static strbuffer_t *countdata = NULL; int anycountdata = 0; char *group; if (!want_msgtype(hinfo, MSG_PROCS)) return; if (!psstr) return; if (!countdata) countdata = newstrbuffer(0); clearalertgroups(); monmsg = newstrbuffer(0); sprintf(msgline, "data %s.proccounts\n", commafy(hostname)); addtobuffer(countdata, msgline); cmdofs = 0; /* Command offset for z/VM isn't necessary */ pchecks = clear_process_counts(hinfo, clientclass); if (pchecks == 0) { /* Nothing to check */ sprintf(msgline, "&%s No process checks defined\n", colorname(noreportcolor)); addtobuffer(monmsg, msgline); pscolor = noreportcolor; } else if (cmdofs >= 0) { /* Count how many instances of each monitored process is running */ char *pname, *pid, *bol, *nl; int pcount, pmin, pmax, pcolor, ptrack; bol = psstr; while (bol) { nl = strchr(bol, '\n'); /* Take care - the ps output line may be shorter than what we look at */ if (nl) { *nl = '\0'; if ((nl-bol) > cmdofs) add_process_count(bol+cmdofs); *nl = '\n'; bol = nl+1; } else { if (strlen(bol) > cmdofs) add_process_count(bol+cmdofs); bol = NULL; } } /* Check the number found for each monitored process */ while ((pname = check_process_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, &group)) != NULL) { char limtxt[1024]; if (pmax == -1) { if (pmin > 0) sprintf(limtxt, "%d or more", pmin); else if (pmin == 0) sprintf(limtxt, "none"); } else { if (pmin > 0) sprintf(limtxt, "between %d and %d", pmin, pmax); else if (pmin == 0) sprintf(limtxt, "at most %d", pmax); } if (pcolor == COL_GREEN) { sprintf(msgline, "&green %s (found %d, req. %s)\n", pname, pcount, limtxt); addtobuffer(monmsg, msgline); } else { if (pcolor > pscolor) pscolor = pcolor; sprintf(msgline, "&%s %s (found %d, req. %s)\n", colorname(pcolor), pname, pcount, limtxt); addtobuffer(monmsg, msgline); addalertgroup(group); } if (ptrack) { /* Save the count data for later DATA message to track process counts */ if (!pid) pid = "default"; sprintf(msgline, "%s:%u\n", pid, pcount); addtobuffer(countdata, msgline); anycountdata = 1; } } } else { pscolor = COL_YELLOW; sprintf(msgline, "&yellow Expected string not found in ps output header\n"); addtobuffer(monmsg, msgline); } /* Now we know the result, so generate a status message */ init_status(pscolor); group = getalertgroups(); if (group) sprintf(msgline, "status/group:%s ", group); else strcpy(msgline, "status "); addtostatus(msgline); sprintf(msgline, "%s.procs %s %s - Processes %s\n", commafy(hostname), colorname(pscolor), (timestr ? timestr : ""), ((pscolor == COL_GREEN) ? "OK" : "NOT ok")); addtostatus(msgline); /* And add the info about what's wrong */ if (STRBUFLEN(monmsg)) { addtostrstatus(monmsg); addtostatus("\n"); } /* And the full virtual machine names output for those who want it */ if (pslistinprocs) { /* * Format the list of virtual machines into four per line, * this list could be fairly long. */ char *tmpstr, *tok, *nm[4]; int nmidx = 0; /* Make a copy of psstr, strtok() will be changing it */ tmpstr = strdup(psstr); /* Use strtok() to split string into pieces delimited by newline */ tok = strtok(tmpstr, "\n"); while (tok) { nm[nmidx++] = tok; if (nmidx == 4) { sprintf(msgline, "%-8s %-8s %-8s %-8s\n", nm[0], nm[1], nm[2], nm[3]); addtostatus(msgline); nmidx = 0; nm[0] = nm[1] = nm[2] = nm[3] = " "; } tok = strtok(NULL, "\n"); } /* Print any remaining names */ if (nmidx > 0) { sprintf(msgline, "%-8s %-8s %-8s %-8s\n", nm[0], nm[1], nm[2], nm[3]); addtostatus(msgline); } free(tmpstr); } if (fromline && !localmode) addtostatus(fromline); finish_status(); freestrbuffer(monmsg); if (anycountdata) sendmessage(STRBUF(countdata), NULL, XYMON_TIMEOUT, NULL); clearstrbuffer(countdata); } void handle_zvm_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *cpuutilstr; char *uptimestr; char *msgcachestr; char *dfstr; char *usersstr; /* Logged on z/VM Users */ char *msgsstr; char *netstatstr; char *ifstatstr; char *portsstr; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); cpuutilstr = getdata("cpu"); msgcachestr = getdata("msgcache"); dfstr = getdata("df"); usersstr = getdata("UserID"); msgsstr = getdata("msgs"); portsstr = getdata("ports"); ifstatstr = getdata("ifstat"); zvm_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, cpuutilstr, uptimestr); zvm_paging_report(hostname, clienttype, os, hinfo, fromline, timestr, cpuutilstr); zvm_mdc_report(hostname, clienttype, os, hinfo, fromline, timestr, cpuutilstr); zvm_users_report(hostname, clienttype, os, hinfo, fromline, timestr, usersstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Available", "Capacity", "Mounted", dfstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); splitmsg_done(); } xymon-4.3.7/xymond/client/netbsd.c0000664000175000017500000000722211615341300016437 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Client backend module for NetBSD */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char netbsd_rcsid[] = "$Id: netbsd.c 6712 2011-07-31 21:01:52Z storner $"; void handle_netbsd_client(char *hostname, char *clienttype, enum ostype_t os, void *hinfo, char *sender, time_t timestamp, char *clientdata) { char *timestr; char *uptimestr; char *clockstr; char *msgcachestr; char *whostr; char *psstr; char *topstr; char *dfstr; char *meminfostr; char *swapctlstr; char *msgsstr; char *netstatstr; char *ifstatstr; char *portsstr; char *vmstatstr; char *p; char fromline[1024]; sprintf(fromline, "\nStatus message received from %s\n", sender); splitmsg(clientdata); timestr = getdata("date"); uptimestr = getdata("uptime"); clockstr = getdata("clock"); msgcachestr = getdata("msgcache"); whostr = getdata("who"); psstr = getdata("ps"); topstr = getdata("top"); dfstr = getdata("df"); meminfostr = getdata("meminfo"); swapctlstr = getdata("swapctl"); msgsstr = getdata("msgsstr"); netstatstr = getdata("netstat"); ifstatstr = getdata("ifstat"); portsstr = getdata("ports"); vmstatstr = getdata("vmstat"); unix_cpu_report(hostname, clienttype, os, hinfo, fromline, timestr, uptimestr, clockstr, msgcachestr, whostr, 0, psstr, 0, topstr); unix_disk_report(hostname, clienttype, os, hinfo, fromline, timestr, "Avail", "Capacity", "Mounted", dfstr); unix_procs_report(hostname, clienttype, os, hinfo, fromline, timestr, "COMMAND", NULL, psstr); unix_ports_report(hostname, clienttype, os, hinfo, fromline, timestr, 3, 4, 5, portsstr); msgs_report(hostname, clienttype, os, hinfo, fromline, timestr, msgsstr); file_report(hostname, clienttype, os, hinfo, fromline, timestr); linecount_report(hostname, clienttype, os, hinfo, fromline, timestr); unix_netstat_report(hostname, clienttype, os, hinfo, fromline, timestr, netstatstr); unix_ifstat_report(hostname, clienttype, os, hinfo, fromline, timestr, ifstatstr); unix_vmstat_report(hostname, clienttype, os, hinfo, fromline, timestr, vmstatstr); if (meminfostr) { unsigned long memphystotal, memphysfree, memphysused; unsigned long memswaptotal, memswapfree, memswapused; int found = 0; memphystotal = memphysfree = memphysused = 0; memswaptotal = memswapfree = memswapused = 0; p = strstr(meminfostr, "Total:"); if (p) { memphystotal = atol(p+6); found++; } p = strstr(meminfostr, "Free:"); if (p) { memphysfree = atol(p+5); found++; } memphysused = memphystotal - memphysfree; p = strstr(meminfostr, "Swaptotal:"); if (p) { memswaptotal = atol(p+10); found++; } p = strstr(meminfostr, "Swapused:"); if (p) { memswapused = atol(p+9); found++; } memswapfree = memswaptotal - memswapused; if (found == 4) { unix_memory_report(hostname, clienttype, os, hinfo, fromline, timestr, memphystotal, memphysused, -1, memswaptotal, memswapused); } } splitmsg_done(); } xymon-4.3.7/xymond/xymonfetch.80000664000175000017500000000516611671641417016036 0ustar henrikhenrik.TH XYMONFETCH 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymonfetch \- fetch client data from passive clients .SH SYNOPSIS .B "xymonfetch [--server=XYMON.SERVER.IP] [options]" .SH DESCRIPTION This utility is used to collect data from Xymon clients. Normally, Xymon clients will themselves take care of sending all of their data directly to the Xymon server. In that case, you do not need this utility at all. However, in some network setups clients may be prohibited from establishing a connection to an external server such as the Xymon server, due to firewall policies. In such a setup you can configure the client to store all of the client data locally by enabling the .I msgcache(8) utility on the client, and using \fBxymonfetch\fR on the Xymon server to collect data from the clients. xymonfetch will only collect data from clients that have the \fBpulldata\fR tag listed in the .I hosts.cfg(5) file. The IP-address listed in the hosts.cfg file must be correct, since this is the IP-address where xymonfetch will attempt to contact the client. If the msgcache daemon is running on a non-standard IP-address or portnumber, you can specify the portnumber as in \fBpulldata=192.168.1.2:8084\fR for contacting the msgcache daemon using IP 192.168.1.2 port 8084. If the IP-address is omitted, the default IP in the hosts.cfg file is used. If the port number is omitted, the portnumber from the XYMONDPORT setting in .I xymonserver.cfg(5) is used (normally, this is port 1984). .SH OPTIONS .IP "--server=XYMON.SERVER.IP" Defines the IP address of the Xymon server where the collected client messages are forwarded to. By default, messages are sent to the loopback address 127.0.0.1, i.e. to a Xymon server running on the same host as xymonfetch. .IP "--interval=N" Sets the interval (in seconds) between polls of a client. Default: 60 seconds. .IP "--id=N" Used when you have a setup with multiple Xymon servers. In that case, you must run xymonfetch on each of the Xymon servers, with xymonfetch instance using a different value of N. This allows several Xymon servers to pick up data from the clients running msgcache, and msgcache can distinguish between which messages have already been forwarded to which server. .br N is a number in the range 1-31. .IP "--log-interval=N" Limit how often xymonfetch will log problems with fetching data from a host, in seconds. Default: 900 seconds (15 minutes). This is to prevent a host that is down or where msgcache has not been started from flooding the xymonfetch logs. Note that this is ignored when debugging is enabled. .IP "--debug" Enable debugging output. .SH "SEE ALSO" msgcache(8), xymond(8), xymon(7) xymon-4.3.7/xymond/xymond_history.c0000664000175000017500000005427611630732124017021 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* This is a xymond worker module for the "stachg" channel. */ /* This module implements the file-based history logging, and keeps the */ /* historical logfiles in $XYMONVAR/hist/ and $XYMONVAR/histlogs/ updated */ /* to keep track of the status changes. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond_history.c 6748 2011-09-04 17:24:36Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymond_worker.h" int rotatefiles = 0; time_t nextfscheck = 0; void sig_handler(int signum) { /* * Why this? Because we must have our own signal handler installed to call wait() */ switch (signum) { case SIGCHLD: break; case SIGHUP: rotatefiles = 1; nextfscheck = 0; break; } } typedef struct columndef_t { char *name; int saveit; } columndef_t; void * columndefs; int main(int argc, char *argv[]) { time_t starttime = gettimer(); char *histdir = NULL; char *histlogdir = NULL; char *msg; int argi, seq; int save_allevents = 1; int save_hostevents = 1; int save_statusevents = 1; int save_histlogs = 1, defaultsaveop = 1; FILE *alleventsfd = NULL; int running = 1; struct sigaction sa; char newcol2[3]; char oldcol2[3]; char alleventsfn[PATH_MAX]; char pidfn[PATH_MAX]; int logdirfull = 0; int minlogspace = 5; MEMDEFINE(pidfn); MEMDEFINE(alleventsfn); MEMDEFINE(newcol2); MEMDEFINE(oldcol2); /* Dont save the error buffer */ save_errbuf = 0; sprintf(pidfn, "%s/xymond_history.pid", xgetenv("XYMONSERVERLOGS")); if (xgetenv("XYMONALLHISTLOG")) save_allevents = (strcmp(xgetenv("XYMONALLHISTLOG"), "TRUE") == 0); if (xgetenv("XYMONHOSTHISTLOG")) save_hostevents = (strcmp(xgetenv("XYMONHOSTHISTLOG"), "TRUE") == 0); if (xgetenv("SAVESTATUSLOG")) save_histlogs = (strncmp(xgetenv("SAVESTATUSLOG"), "FALSE", 5) != 0); for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--histdir=")) { histdir = strchr(argv[argi], '=')+1; } else if (argnmatch(argv[argi], "--histlogdir=")) { histlogdir = strchr(argv[argi], '=')+1; } else if (argnmatch(argv[argi], "--pidfile=")) { strcpy(pidfn, strchr(argv[argi], '=')+1); } else if (argnmatch(argv[argi], "--minimum-free=")) { minlogspace = atoi(strchr(argv[argi], '=')+1); } else if (argnmatch(argv[argi], "--debug")) { debug = 1; } } if (xgetenv("XYMONHISTDIR") && (histdir == NULL)) { histdir = strdup(xgetenv("XYMONHISTDIR")); } if (histdir == NULL) { errprintf("No history directory given, aborting\n"); return 1; } if (save_histlogs && (histlogdir == NULL) && xgetenv("XYMONHISTLOGS")) { histlogdir = strdup(xgetenv("XYMONHISTLOGS")); } if (save_histlogs && (histlogdir == NULL)) { errprintf("No history-log directory given, aborting\n"); return 1; } columndefs = xtreeNew(strcmp); { char *defaultsave, *tok; char *savelist; columndef_t *newrec; savelist = strdup(xgetenv("SAVESTATUSLOG")); defaultsave = strtok(savelist, ","); /* * TRUE: Save everything by default; may list some that are not saved. * ONLY: Save nothing by default; may list some that are saved. * FALSE: Save nothing. */ defaultsaveop = (strcasecmp(defaultsave, "TRUE") == 0); tok = strtok(NULL, ","); while (tok) { newrec = (columndef_t *)malloc(sizeof(columndef_t)); if (*tok == '!') { newrec->saveit = 0; newrec->name = strdup(tok+1); } else { newrec->saveit = 1; newrec->name = strdup(tok); } xtreeAdd(columndefs, newrec->name, newrec); tok = strtok(NULL, ","); } xfree(savelist); } { FILE *pidfd = fopen(pidfn, "w"); if (pidfd) { fprintf(pidfd, "%d\n", getpid()); fclose(pidfd); } } sprintf(alleventsfn, "%s/allevents", histdir); if (save_allevents) { alleventsfd = fopen(alleventsfn, "a"); if (alleventsfd == NULL) { errprintf("Cannot open the all-events file '%s'\n", alleventsfn); } setvbuf(alleventsfd, (char *)NULL, _IOLBF, 0); } /* For picking up lost children */ setup_signalhandler("xymond_history"); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; sigaction(SIGCHLD, &sa, NULL); sigaction(SIGHUP, &sa, NULL); signal(SIGPIPE, SIG_DFL); while (running) { char *metadata[20] = { NULL, }; int metacount; char *p; char *statusdata = ""; char *hostname, *hostnamecommas, *testname, *dismsg, *modifiers; time_t tstamp, lastchg, disabletime, clienttstamp; int tstamp_i, lastchg_i; int newcolor, oldcolor; int downtimeactive; struct tm tstamptm; int trend; int childstat; /* Pickup any finished child processes to avoid zombies */ while (wait3(&childstat, WNOHANG, NULL) > 0) ; if (rotatefiles && alleventsfd) { fclose(alleventsfd); alleventsfd = fopen(alleventsfn, "a"); if (alleventsfd == NULL) { errprintf("Cannot re-open the all-events file '%s'\n", alleventsfn); } else { setvbuf(alleventsfd, (char *)NULL, _IOLBF, 0); } } msg = get_xymond_message(C_STACHG, "xymond_history", &seq, NULL); if (msg == NULL) { running = 0; continue; } if (nextfscheck < gettimer()) { logdirfull = (chkfreespace(histlogdir, minlogspace, minlogspace) != 0); if (logdirfull) errprintf("Historylog directory %s has less than %d%% free space - disabling save of data for 5 minutes\n", histlogdir, minlogspace); nextfscheck = gettimer() + 300; } p = strchr(msg, '\n'); if (p) { *p = '\0'; statusdata = msg_data(p+1); } metacount = 0; memset(&metadata, 0, sizeof(metadata)); p = gettok(msg, "|"); while (p && (metacount < 20)) { metadata[metacount++] = p; p = gettok(NULL, "|"); } if ((metacount > 9) && (strncmp(metadata[0], "@@stachg", 8) == 0)) { xtreePos_t handle; columndef_t *saveit = NULL; /* @@stachg#seq|timestamp|sender|origin|hostname|testname|expiretime|color|prevcolor|changetime|disabletime|disablemsg|downtimeactive|clienttstamp|modifiers */ sscanf(metadata[1], "%d.%*d", &tstamp_i); tstamp = tstamp_i; hostname = metadata[4]; testname = metadata[5]; newcolor = parse_color(metadata[7]); oldcolor = parse_color(metadata[8]); lastchg = atoi(metadata[9]); disabletime = atoi(metadata[10]); dismsg = metadata[11]; downtimeactive = (atoi(metadata[12]) > 0); clienttstamp = atoi(metadata[13]); modifiers = metadata[14]; if (newcolor == -1) { errprintf("Bad message: newcolor is unknown '%s'\n", metadata[7]); continue; } p = hostnamecommas = strdup(hostname); while ((p = strchr(p, '.')) != NULL) *p = ','; handle = xtreeFind(columndefs, testname); if (handle == xtreeEnd(columndefs)) { saveit = (columndef_t *)malloc(sizeof(columndef_t)); saveit->name = strdup(testname); saveit->saveit = defaultsaveop; xtreeAdd(columndefs, saveit->name, saveit); } else { saveit = (columndef_t *) xtreeData(columndefs, handle); } if (save_statusevents) { char statuslogfn[PATH_MAX]; int logexists; FILE *statuslogfd; char oldcol[100]; char timestamp[40]; struct stat st; MEMDEFINE(statuslogfn); MEMDEFINE(oldcol); MEMDEFINE(timestamp); sprintf(statuslogfn, "%s/%s.%s", histdir, hostnamecommas, testname); stat(statuslogfn, &st); statuslogfd = fopen(statuslogfn, "r+"); logexists = (statuslogfd != NULL); *oldcol = '\0'; if (logexists) { /* * There is a fair chance xymond has not been * running all the time while this system was monitored. * So get the time of the latest status change from the file, * instead of relying on the "lastchange" value we get * from xymond. This is also needed when migrating from * standard bbd to xymond. */ off_t pos = -1; char l[1024]; int gotit; MEMDEFINE(l); fseeko(statuslogfd, 0, SEEK_END); if (ftello(statuslogfd) > 512) { /* Go back 512 from EOF, and skip to start of a line */ fseeko(statuslogfd, -512, SEEK_END); gotit = (fgets(l, sizeof(l)-1, statuslogfd) == NULL); } else { /* Read from beginning of file */ fseeko(statuslogfd, 0, SEEK_SET); gotit = 0; } while (!gotit) { off_t tmppos = ftello(statuslogfd); time_t dur; int dur_i; if (fgets(l, sizeof(l)-1, statuslogfd)) { /* Sun Oct 10 06:49:42 2004 red 1097383782 602 */ if ((strlen(l) > 24) && (sscanf(l+24, " %s %d %d", oldcol, &lastchg_i, &dur_i) == 2) && (parse_color(oldcol) != -1)) { /* * Record the start location of the line */ pos = tmppos; lastchg = lastchg_i; dur = dur_i; } } else { gotit = 1; } } if (pos == -1) { /* * Couldnt find anything in the log. * Take lastchg from the timestamp of the logfile, * and just append the data. */ lastchg = st.st_mtime; fseeko(statuslogfd, 0, SEEK_END); } else { /* * lastchg was updated above. * Seek to where the last line starts. */ fseeko(statuslogfd, pos, SEEK_SET); } MEMUNDEFINE(l); } else { /* * Logfile does not exist. */ lastchg = tstamp; statuslogfd = fopen(statuslogfn, "a"); if (statuslogfd == NULL) { errprintf("Cannot open status historyfile '%s' : %s\n", statuslogfn, strerror(errno)); } } if (strcmp(oldcol, colorname(newcolor)) == 0) { /* We wont update history unless the color did change. */ if ((gettimer() - starttime) > 300) { errprintf("Will not update %s - color unchanged (%s)\n", statuslogfn, oldcol); } if (hostnamecommas) xfree(hostnamecommas); if (statuslogfd) fclose(statuslogfd); MEMUNDEFINE(statuslogfn); MEMUNDEFINE(oldcol); MEMUNDEFINE(timestamp); continue; } if (statuslogfd) { if (logexists) { struct tm oldtm; /* Re-print the old record, now with the final duration */ memcpy(&oldtm, localtime(&lastchg), sizeof(oldtm)); strftime(timestamp, sizeof(timestamp), "%a %b %e %H:%M:%S %Y", &oldtm); fprintf(statuslogfd, "%s %s %d %d\n", timestamp, oldcol, (int)lastchg, (int)(tstamp - lastchg)); } /* And the new record. */ memcpy(&tstamptm, localtime(&tstamp), sizeof(tstamptm)); strftime(timestamp, sizeof(timestamp), "%a %b %e %H:%M:%S %Y", &tstamptm); fprintf(statuslogfd, "%s %s %d", timestamp, colorname(newcolor), (int)tstamp); fclose(statuslogfd); } MEMUNDEFINE(statuslogfn); MEMUNDEFINE(oldcol); MEMUNDEFINE(timestamp); } if (save_histlogs && saveit->saveit && !logdirfull) { char *hostdash; char fname[PATH_MAX]; FILE *histlogfd; MEMDEFINE(fname); p = hostdash = strdup(hostname); while ((p = strchr(p, '.')) != NULL) *p = '_'; sprintf(fname, "%s/%s", histlogdir, hostdash); mkdir(fname, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); p = fname + sprintf(fname, "%s/%s/%s", histlogdir, hostdash, testname); mkdir(fname, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); p += sprintf(p, "/%s", histlogtime(tstamp)); histlogfd = fopen(fname, "w"); if (histlogfd) { /* * When a host gets disabled or goes purple, the status * message data is not changed - so it will include a * wrong color as the first word of the message. * Therefore we need to fixup this so it matches the * newcolor value. */ int txtcolor = parse_color(statusdata); char *origstatus = statusdata; char *eoln, *restofdata; int written, closestatus, ok = 1; if (txtcolor != -1) { fprintf(histlogfd, "%s", colorname(newcolor)); statusdata += strlen(colorname(txtcolor)); } if (dismsg && *dismsg) nldecode(dismsg); if (disabletime > 0) { fprintf(histlogfd, " Disabled until %s\n%s\n\n", ctime(&disabletime), (dismsg ? dismsg : "")); fprintf(histlogfd, "Status message when disabled follows:\n\n"); statusdata = origstatus; } else if (dismsg && *dismsg) { fprintf(histlogfd, " Planned downtime: %s\n\n", dismsg); fprintf(histlogfd, "Original status message follows:\n\n"); statusdata = origstatus; } restofdata = statusdata; if (modifiers && *modifiers) { char *modtxt; /* We must finish writing the first line before putting in the modifiers */ eoln = strchr(restofdata, '\n'); if (eoln) { restofdata = eoln+1; *eoln = '\0'; fprintf(histlogfd, "%s\n", statusdata); } nldecode(modifiers); modtxt = strtok(modifiers, "\n"); while (modtxt) { fprintf(histlogfd, "%s\n", modtxt); modtxt = strtok(NULL, "\n"); } } written = fwrite(restofdata, 1, strlen(restofdata), histlogfd); if (written != strlen(restofdata)) { ok = 0; errprintf("Error writing to file %s: %s\n", fname, strerror(errno)); closestatus = fclose(histlogfd); /* Ignore any errors on close */ } else { fprintf(histlogfd, "Status unchanged in 0.00 minutes\n"); fprintf(histlogfd, "Message received from %s\n", metadata[2]); if (clienttstamp) fprintf(histlogfd, "Client data ID %d\n", (int) clienttstamp); closestatus = fclose(histlogfd); if (closestatus != 0) { ok = 0; errprintf("Error writing to file %s: %s\n", fname, strerror(errno)); } } if (!ok) remove(fname); } else { errprintf("Cannot create histlog file '%s' : %s\n", fname, strerror(errno)); } xfree(hostdash); MEMUNDEFINE(fname); } strncpy(oldcol2, ((oldcolor >= 0) ? colorname(oldcolor) : "-"), 2); strncpy(newcol2, colorname(newcolor), 2); newcol2[2] = oldcol2[2] = '\0'; if (oldcolor == -1) trend = -1; /* we dont know how bad it was */ else if (newcolor > oldcolor) trend = 2; /* It's getting worse */ else if (newcolor < oldcolor) trend = 1; /* It's getting better */ else trend = 0; /* Shouldn't happen ... */ if (save_hostevents) { char hostlogfn[PATH_MAX]; FILE *hostlogfd; MEMDEFINE(hostlogfn); sprintf(hostlogfn, "%s/%s", histdir, hostname); hostlogfd = fopen(hostlogfn, "a"); if (hostlogfd) { fprintf(hostlogfd, "%s %d %d %d %s %s %d\n", testname, (int)tstamp, (int)lastchg, (int)(tstamp - lastchg), newcol2, oldcol2, trend); fclose(hostlogfd); } else { errprintf("Cannot open host logfile '%s' : %s\n", hostlogfn, strerror(errno)); } MEMUNDEFINE(hostlogfn); } if (save_allevents) { fprintf(alleventsfd, "%s %s %d %d %d %s %s %d\n", hostname, testname, (int)tstamp, (int)lastchg, (int)(tstamp - lastchg), newcol2, oldcol2, trend); fflush(alleventsfd); } xfree(hostnamecommas); } else if ((metacount > 3) && ((strncmp(metadata[0], "@@drophost", 10) == 0))) { /* @@drophost|timestamp|sender|hostname */ hostname = metadata[3]; if (save_histlogs) { char *hostdash; char testdir[PATH_MAX]; MEMDEFINE(testdir); /* Remove all directories below the host-specific histlog dir */ p = hostdash = strdup(hostname); while ((p = strchr(p, '.')) != NULL) *p = '_'; sprintf(testdir, "%s/%s", histlogdir, hostdash); dropdirectory(testdir, 1); xfree(hostdash); MEMUNDEFINE(testdir); } if (save_hostevents) { char hostlogfn[PATH_MAX]; struct stat st; MEMDEFINE(hostlogfn); sprintf(hostlogfn, "%s/%s", histdir, hostname); if ((stat(hostlogfn, &st) == 0) && S_ISREG(st.st_mode)) { unlink(hostlogfn); } MEMUNDEFINE(hostlogfn); } if (save_statusevents) { DIR *dirfd; struct dirent *de; char *hostlead; char statuslogfn[PATH_MAX]; struct stat st; MEMDEFINE(statuslogfn); /* Remove $XYMONVAR/hist/host,name.* */ p = hostnamecommas = strdup(hostname); while ((p = strchr(p, '.')) != NULL) *p = ','; hostlead = malloc(strlen(hostname) + 2); strcpy(hostlead, hostnamecommas); strcat(hostlead, "."); dirfd = opendir(histdir); if (dirfd) { while ((de = readdir(dirfd)) != NULL) { if (strncmp(de->d_name, hostlead, strlen(hostlead)) == 0) { sprintf(statuslogfn, "%s/%s", histdir, de->d_name); if ((stat(statuslogfn, &st) == 0) && S_ISREG(st.st_mode)) { unlink(statuslogfn); } } } closedir(dirfd); } xfree(hostlead); xfree(hostnamecommas); MEMUNDEFINE(statuslogfn); } } else if ((metacount > 4) && ((strncmp(metadata[0], "@@droptest", 10) == 0))) { /* @@droptest|timestamp|sender|hostname|testname */ hostname = metadata[3]; testname = metadata[4]; if (save_histlogs) { char *hostdash; char testdir[PATH_MAX]; MEMDEFINE(testdir); p = hostdash = strdup(hostname); while ((p = strchr(p, '.')) != NULL) *p = '_'; sprintf(testdir, "%s/%s/%s", histlogdir, hostdash, testname); dropdirectory(testdir, 1); xfree(hostdash); MEMUNDEFINE(testdir); } if (save_statusevents) { char *hostnamecommas; char statuslogfn[PATH_MAX]; struct stat st; MEMDEFINE(statuslogfn); p = hostnamecommas = strdup(hostname); while ((p = strchr(p, '.')) != NULL) *p = ','; sprintf(statuslogfn, "%s/%s.%s", histdir, hostnamecommas, testname); if ((stat(statuslogfn, &st) == 0) && S_ISREG(st.st_mode)) unlink(statuslogfn); xfree(hostnamecommas); MEMUNDEFINE(statuslogfn); } } else if ((metacount > 4) && ((strncmp(metadata[0], "@@renamehost", 12) == 0))) { /* @@renamehost|timestamp|sender|hostname|newhostname */ char *newhostname; hostname = metadata[3]; newhostname = metadata[4]; if (save_histlogs) { char *hostdash; char *newhostdash; char olddir[PATH_MAX]; char newdir[PATH_MAX]; MEMDEFINE(olddir); MEMDEFINE(newdir); p = hostdash = strdup(hostname); while ((p = strchr(p, '.')) != NULL) *p = '_'; p = newhostdash = strdup(newhostname); while ((p = strchr(p, '.')) != NULL) *p = '_'; sprintf(olddir, "%s/%s", histlogdir, hostdash); sprintf(newdir, "%s/%s", histlogdir, newhostdash); rename(olddir, newdir); xfree(newhostdash); xfree(hostdash); MEMUNDEFINE(newdir); MEMUNDEFINE(olddir); } if (save_hostevents) { char hostlogfn[PATH_MAX]; char newhostlogfn[PATH_MAX]; MEMDEFINE(hostlogfn); MEMDEFINE(newhostlogfn); sprintf(hostlogfn, "%s/%s", histdir, hostname); sprintf(newhostlogfn, "%s/%s", histdir, newhostname); rename(hostlogfn, newhostlogfn); MEMUNDEFINE(hostlogfn); MEMUNDEFINE(newhostlogfn); } if (save_statusevents) { DIR *dirfd; struct dirent *de; char *hostlead; char *newhostnamecommas; char statuslogfn[PATH_MAX]; char newlogfn[PATH_MAX]; MEMDEFINE(statuslogfn); MEMDEFINE(newlogfn); p = hostnamecommas = strdup(hostname); while ((p = strchr(p, '.')) != NULL) *p = ','; hostlead = malloc(strlen(hostname) + 2); strcpy(hostlead, hostnamecommas); strcat(hostlead, "."); p = newhostnamecommas = strdup(newhostname); while ((p = strchr(p, '.')) != NULL) *p = ','; dirfd = opendir(histdir); if (dirfd) { while ((de = readdir(dirfd)) != NULL) { if (strncmp(de->d_name, hostlead, strlen(hostlead)) == 0) { char *testname = strchr(de->d_name, '.'); sprintf(statuslogfn, "%s/%s", histdir, de->d_name); sprintf(newlogfn, "%s/%s%s", histdir, newhostnamecommas, testname); rename(statuslogfn, newlogfn); } } closedir(dirfd); } xfree(newhostnamecommas); xfree(hostlead); xfree(hostnamecommas); MEMUNDEFINE(statuslogfn); MEMUNDEFINE(newlogfn); } } else if ((metacount > 5) && (strncmp(metadata[0], "@@renametest", 12) == 0)) { /* @@renametest|timestamp|sender|hostname|oldtestname|newtestname */ char *newtestname; hostname = metadata[3]; testname = metadata[4]; newtestname = metadata[5]; if (save_histlogs) { char *hostdash; char olddir[PATH_MAX]; char newdir[PATH_MAX]; MEMDEFINE(olddir); MEMDEFINE(newdir); p = hostdash = strdup(hostname); while ((p = strchr(p, '.')) != NULL) *p = '_'; sprintf(olddir, "%s/%s/%s", histlogdir, hostdash, testname); sprintf(newdir, "%s/%s/%s", histlogdir, hostdash, newtestname); rename(olddir, newdir); xfree(hostdash); MEMUNDEFINE(newdir); MEMUNDEFINE(olddir); } if (save_statusevents) { char *hostnamecommas; char statuslogfn[PATH_MAX]; char newstatuslogfn[PATH_MAX]; MEMDEFINE(statuslogfn); MEMDEFINE(newstatuslogfn); p = hostnamecommas = strdup(hostname); while ((p = strchr(p, '.')) != NULL) *p = ','; sprintf(statuslogfn, "%s/%s.%s", histdir, hostnamecommas, testname); sprintf(newstatuslogfn, "%s/%s.%s", histdir, hostnamecommas, newtestname); rename(statuslogfn, newstatuslogfn); xfree(hostnamecommas); MEMUNDEFINE(newstatuslogfn); MEMUNDEFINE(statuslogfn); } } else if (strncmp(metadata[0], "@@idle", 6) == 0) { /* Nothing */ } else if (strncmp(metadata[0], "@@shutdown", 10) == 0) { running = 0; } else if (strncmp(metadata[0], "@@logrotate", 11) == 0) { char *fn = xgetenv("XYMONCHANNEL_LOGFILENAME"); if (fn && strlen(fn)) { freopen(fn, "a", stdout); freopen(fn, "a", stderr); } continue; } else if (strncmp(metadata[0], "@@reload", 8) == 0) { /* Do nothing */ } } MEMUNDEFINE(newcol2); MEMUNDEFINE(oldcol2); MEMUNDEFINE(alleventsfn); MEMUNDEFINE(pidfn); fclose(alleventsfd); unlink(pidfn); return 0; } xymon-4.3.7/xymond/xymonweb.50000664000175000017500000001142011671641417015505 0ustar henrikhenrik.TH XYMONWEB 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME Xymon web page headers, footers and forms. .SH DESCRIPTION The Xymon webpages are somewhat customizable, by modifying the header- and footer-templates found in the ~xymon/server/web/ directory. There are usually two or more files for a webpage: A \fBtemplate_header\fR file which is the header for this webpage, and a \fBtemplate_footer\fR file which is the footer. Webpages where entry forms are used have a \fBtemplate_form\fR file which is the data-entry form. With the exception of the \fBbulletin\fR files, the header files are inserted into the HTML code at the very beginning and the footer files are inserted at the bottom. The following templates are available: .IP bulletin A \fBbulletin_header\fR and \fBbulletin_footer\fR is not shipped with Xymon, but if they exist then the content of these files will be inserted in all HTML documents generated by Xymon. The "bulletin_header" contents will appear after the normal header for the webpage, and the "bulletin_footer" will appear just before the normal footer for the webpage. These files can be used to post important information about the Xymon system, e.g. to notify users of current operational or monitoring problems. .IP acknowledge Header, footer and form template for the Xymon \fBacknowledge alert\fR webpage generated by .I acknowledge.cgi(1) .IP stdnormal Header and footer for the Xymon \fBMain view\fR webpages, generated by .I xymongen(1) .IP stdnongreen Header and footer for the Xymon \fBAll non-green view\fR webpage, generated by .I xymongen(1) .IP stdcritical (DEPRECATED) Header and footer for the now deprecated \fBold critical\fR webpage, generated by xymongen. You should use the newer .I criticalview.cgi(1) utility instead, which uses the \fBcritical\fR templates. .IP repnormal Header and footer for the Xymon \fBMain view\fR availability report webpages, generated by .I xymongen(1) when running in availability report mode. .IP snapnormal Header and footer for the Xymon \fBMain view\fR snapshot webpages, generated by .I xymongen(1) when running in snapshot report mode. .IP snapnongreen Header and footer for the Xymon \fBAll non-green view\fR snapshot webpage, generated by .I xymongen(1) when running in snapshot report mode. .IP columndoc Header and footer for the Xymon \fBColumn documentation\fR webpages, generated by the .I csvinfo.cgi(1) utility in the default Xymon configuration. .IP confreport Header and footer for the Xymon \fBConfiguration report\fR webpage, generated by the .I confreport.cgi(1) utility. Note that there are also "confreport_front" and "confreport_back" templates, these are inserted into the generated report before the hostlist, and before the column documentation, respectively. .IP event Header, footer and form for the Xymon \fBEventlog report\fR, generated by .I eventlog.cgi(1) .IP findhost Header, footer and form for the Xymon \fBFind host\fR webpage, generated by .I findhost.cgi(1) .IP graphs Header and footer for the Xymon \fBGraph details\fR webpages, generated by .I showgraph.cgi(1) .IP hist Header and footer for the Xymon \fBHistory\fR webpage, generated by .I history.cgi(1) .IP histlog Header and footer for the Xymon \fBHistorical status-log\fR webpage, generated by .I svcstatus.cgi(1) utility when used to show a historical (non-current) status log. .IP critical Header and footer for the Xymon \fBCritical Systems view\fR webpage, generated by .I criticalview.cgi(1) .IP hostsvc Header and footer for the Xymon \fBStatus-log\fR webpage, generated by .I svcstatus.cgi(1) utility when used to show a current status log. .IP info Header and footer for the Xymon \fBInfo column\fR webpage, generated by .I svcstatus.cgi(1) utility when used to show the host configuration page. .IP maintact Header and footer for the Xymon \fB\fR webpage, generated by .I enadis.cgi(1) utility when using the Enable/Disable "preview" mode. .IP maint Header, footer and form for the Xymon \fBEnable/disable\fR webpage, generated by .I enadis.cgi(1) .IP critack Form show on the \fBstatus-log\fR webpage when viewed from the "Critical Systems" overview. This form is used to acknowledge a critical status by the operators monitoring the Critical Systems view. .IP critedit Header, footer and form for the \fBCritical Systems Editor\fR, the .I criticaleditor.cgi(1) utility. .IP replog Header and footer for the Xymon \fBReport status-log\fR webpage, generated by .I svcstatus.cgi(1) utility when used to show a status log for an availability report. .IP report Header, footer and forms for selecting a pre-generated \fBAvailability Report\fR. Handled by the .I datepage.cgi(1) utility. .IP snapshot Header and footer for the Xymon \fBSnapshot report\fR selection webpage, generated by .I snapshot.cgi(1) .SH "SEE ALSO" xymongen(1), svcstatus.cgi(1), xymon(7) xymon-4.3.7/xymond/xymond_ipc.c0000664000175000017500000001223411615341300016052 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* This module implements the setup/teardown of the xymond communications */ /* channel, using standard System V IPC mechanisms: Shared memory and */ /* semaphores. */ /* */ /* The concept is to use a shared memory segment for each "channel" that */ /* xymond supports. This memory segment is used to pass a single xymond */ /* message between the xymond master daemon, and the xymond_channel workers. */ /* Two semaphores are used to synchronize between the master daemon and the */ /* workers, i.e. the workers wait for a semaphore to go up indicating that a */ /* new message has arrived, and the master daemon then waits for the other */ /* semaphore to go 0 indicating that the workers have read the message. A */ /* third semaphore is used as a simple counter to tell how many workers have */ /* attached to a channel. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond_ipc.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymond_ipc.h" char *channelnames[C_LAST+1] = { "", /* First one is index 0 - not used */ "status", "stachg", "page", "data", "notes", "enadis", "client", "clichg", "user", NULL }; xymond_channel_t *setup_channel(enum msgchannels_t chnid, int role) { key_t key; struct stat st; struct sembuf s; xymond_channel_t *newch; unsigned int bufsz; int flags = ((role == CHAN_MASTER) ? (IPC_CREAT | 0600) : 0); char *xymonhome = xgetenv("XYMONHOME"); if ( (xymonhome == NULL) || (stat(xymonhome, &st) == -1) ) { errprintf("XYMONHOME not defined, or points to invalid directory - cannot continue.\n"); return NULL; } bufsz = 1024*shbufsz(chnid); dbgprintf("Setting up %s channel (id=%d)\n", channelnames[chnid], chnid); dbgprintf("calling ftok('%s',%d)\n", xymonhome, chnid); key = ftok(xymonhome, chnid); if (key == -1) { errprintf("Could not generate shmem key based on %s: %s\n", xymonhome, strerror(errno)); return NULL; } dbgprintf("ftok() returns: 0x%X\n", key); newch = (xymond_channel_t *)malloc(sizeof(xymond_channel_t)); newch->seq = 0; newch->channelid = chnid; newch->msgcount = 0; newch->shmid = shmget(key, bufsz, flags); if (newch->shmid == -1) { errprintf("Could not get shm of size %d: %s\n", bufsz, strerror(errno)); xfree(newch); return NULL; } dbgprintf("shmget() returns: 0x%X\n", newch->shmid); newch->channelbuf = (char *) shmat(newch->shmid, NULL, 0); if (newch->channelbuf == (char *)-1) { errprintf("Could not attach shm %s\n", strerror(errno)); if (role == CHAN_MASTER) shmctl(newch->shmid, IPC_RMID, NULL); xfree(newch); return NULL; } newch->semid = semget(key, 3, flags); if (newch->semid == -1) { errprintf("Could not get sem: %s\n", strerror(errno)); shmdt(newch->channelbuf); if (role == CHAN_MASTER) shmctl(newch->shmid, IPC_RMID, NULL); xfree(newch); return NULL; } if (role == CHAN_CLIENT) { /* * Clients must register their presence. * We use SEM_UNDO; so if the client crashes, it wont leave a stale count. */ s.sem_num = CLIENTCOUNT; s.sem_op = +1; s.sem_flg = SEM_UNDO; if (semop(newch->semid, &s, 1) == -1) { errprintf("Could not register presence: %s\n", strerror(errno)); shmdt(newch->channelbuf); xfree(newch); return NULL; } } else if (role == CHAN_MASTER) { int n; n = semctl(newch->semid, CLIENTCOUNT, GETVAL); if (n > 0) { errprintf("FATAL: xymond sees clientcount %d, should be 0\nCheck for hanging xymond_channel processes or stale semaphores\n", n); shmdt(newch->channelbuf); shmctl(newch->shmid, IPC_RMID, NULL); semctl(newch->semid, 0, IPC_RMID); xfree(newch); return NULL; } } #ifdef MEMORY_DEBUG add_to_memlist(newch->channelbuf, bufsz); #endif return newch; } void close_channel(xymond_channel_t *chn, int role) { if (chn == NULL) return; /* No need to de-register, this happens automatically because we registered with SEM_UNDO */ if (role == CHAN_MASTER) semctl(chn->semid, 0, IPC_RMID); MEMUNDEFINE(chn->channelbuf); shmdt(chn->channelbuf); if (role == CHAN_MASTER) shmctl(chn->shmid, IPC_RMID, NULL); } xymon-4.3.7/xymond/webfiles/0000775000175000017500000000000011671641716015353 5ustar henrikhenrikxymon-4.3.7/xymond/webfiles/snapnongreen_footer0000777000175000017500000000000011671641716024650 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/critmulti_header0000664000175000017500000000263011630352060020605 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Status @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Current Critical Systems
&XYMWEBDATE

 
&DIVIDERTEXT
 
xymon-4.3.7/xymond/webfiles/maint_header0000664000175000017500000001103411615612165017707 0ustar henrikhenrik Xymon - Maintenance &XYMONBODYHEADER
 
&XYMONLOGO
Maintenance
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/hostgraphs_footer0000777000175000017500000000000011671641716024335 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/hist_header0000664000175000017500000000234111615226067017551 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - History @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
History
&XYMWEBHOST - &XYMWEBSVC
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/maint_footer0000777000175000017500000000000011671641716023263 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/ghosts_header0000664000175000017500000000206211615226067020111 0ustar henrikhenrik Xymon - Ghost Clients &XYMONBODYHEADER
 
&XYMONLOGO
Ghost Clients
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/critical_footer0000664000175000017500000000456411623437505020453 0ustar henrikhenrik
Priority Color Age Acked Eventlog  



Xymon &XYMONDREL
&XYMONBODYFOOTER xymon-4.3.7/xymond/webfiles/report_form_daily0000664000175000017500000000155111415724561021014 0ustar henrikhenrik
Xymon Daily Availability Report






xymon-4.3.7/xymond/webfiles/graphs_footer0000777000175000017500000000000011671641716023437 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/acknowledge_header0000664000175000017500000000207211615226067021066 0ustar henrikhenrik Xymon - Acknowledge Alert &XYMONBODYHEADER
 
&XYMONLOGO
Acknowledge Alert
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/maint_form0000664000175000017500000001222311070452713017417 0ustar henrikhenrik
Current StatusSelect what to Disable
&DISABLELIST
Currently disabled tests
 
&SCHEDULELIST
Scheduled actions
HostsTests 
 
Filter hostlist
Hostname pattern
Pagename pattern
IP address pattern
 
Cause: 
Duration:     - OR - until OK:
 
Disable now
Schedule disable at
 
   Preview
xymon-4.3.7/xymond/webfiles/report_form_monthly0000664000175000017500000000144411415724561021405 0ustar henrikhenrik
Xymon Monthly Availability Report






xymon-4.3.7/xymond/webfiles/findhost_form0000664000175000017500000000161311070452713020126 0ustar henrikhenrik

Pattern to look for

Case Sensitive        Jump to page       
xymon-4.3.7/xymond/webfiles/confreport_back0000664000175000017500000000000011070452713020413 0ustar henrikhenrikxymon-4.3.7/xymond/webfiles/acknowledge_footer0000777000175000017500000000000011671641716024436 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/snapshot_header0000664000175000017500000000206611615226067020445 0ustar henrikhenrik Xymon - Snapshot Report &XYMONBODYHEADER
 
&XYMONLOGO
Snapshot Report
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/stdcritical_header0000664000175000017500000000230411615226067021106 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Status @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Current Critical Systems
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/stdnongreen_footer0000777000175000017500000000000011671641716024501 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/repnormal_footer0000777000175000017500000000000011671641716024152 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/repnormal_header0000664000175000017500000000231211615226067020577 0ustar henrikhenrik Xymon Availability Report : &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Availability Report
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/report_header0000664000175000017500000000207611615226067020122 0ustar henrikhenrik Xymon - Availability Report &XYMONBODYHEADER
 
&XYMONLOGO
Availability Report
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/report_footer0000777000175000017500000000000011671641716023466 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/hostlist_form0000664000175000017500000000360111535462534020170 0ustar henrikhenrik
Page

Select
 Hosts w/ client  Hosts w/ ping  All hosts

Include
Page path
Comment
Downtime
NK columns
NK time
SLA period
SLA level
Network

 
xymon-4.3.7/xymond/webfiles/confreport_footer0000664000175000017500000000002111070452713021014 0ustar henrikhenrik xymon-4.3.7/xymond/webfiles/snapcritical_footer0000777000175000017500000000000011671641716024627 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/ghosts_footer0000777000175000017500000000000011671641716023462 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/notify_form0000664000175000017500000001372311535462534017635 0ustar henrikhenrik
Starting minutes ago ( Default 1440 )
 - OR - 
From: (ccyy/mm/dd@hh:mm:ss)
To: (ccyy/mm/dd@hh:mm:ss)


Max # of notifications   ( Default 100 )
Hosts to match   ( ex: ^host.*$ )
Hosts to skip   ( ex: ^host.*$ )
Pages to match  
Pages to skip   ( ex: ^webservers/.*$ )
Tests to match   ( ex: cpu|vmstat )
Tests to skip   ( ex: cpu|vmstat )
Recipients to match   ( ex: admin@test.com )
Recipients to skip   ( ex: admin@test.com )


xymon-4.3.7/xymond/webfiles/divider_header0000664000175000017500000000041011630352060020211 0ustar henrikhenrik
 
&DIVIDERTEXT
 
xymon-4.3.7/xymond/webfiles/hist_footer0000777000175000017500000000000011671641716023122 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/hostlist_header0000664000175000017500000000206211615226067020453 0ustar henrikhenrik Xymon - List of Hosts &XYMONBODYHEADER
 
&XYMONLOGO
List of Hosts
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/snapnormal_header0000664000175000017500000000230211615226067020751 0ustar henrikhenrik Xymon Snapshot Report : &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Snapshot Report
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/critedit_form0000664000175000017500000000775311535462534020142 0ustar henrikhenrik
Edit Critical Systems

Host:  Test: 
Last update by: &CRITEDITUPDINFO

Priority: 
 
Monitoring time:   from     to  
Start monitoring    
Stop monitoring    
 
Resolver group: 
Instruction: 
 
 
   Even if it has clones:

Clones of this host: Clone this host to:



xymon-4.3.7/xymond/webfiles/topchanges_footer0000777000175000017500000000000011671641716024306 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/replog_footer0000777000175000017500000000000011671641716023443 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/histlog_footer0000777000175000017500000000000011671641716023624 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/hostsvc_footer0000777000175000017500000000000011671641716023644 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/maintact_header0000664000175000017500000000205611615226067020405 0ustar henrikhenrik Xymon - Maintenance &XYMONBODYHEADER
 
&XYMONLOGO
Maintenance
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/hostgraphs_form0000664000175000017500000000355011535462534020504 0ustar henrikhenrik
Metrics Report

- to -






xymon-4.3.7/xymond/webfiles/info_header0000664000175000017500000000563111615226067017542 0ustar henrikhenrik Xymon - Host Information &XYMONBODYHEADER
 
&XYMONLOGO
&XYMWEBHOST - Information
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/columndoc_footer0000777000175000017500000000000011671641716024136 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/findhost_footer0000777000175000017500000000000011671641716023771 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/divider_footer0000664000175000017500000000001311630352060020256 0ustar henrikhenrik

xymon-4.3.7/xymond/webfiles/graphs_header0000664000175000017500000000210511615226067020064 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Status @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Graphs
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/perfdata_footer0000777000175000017500000000000011671641716023741 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/histlog_header0000664000175000017500000000237711615226067020264 0ustar henrikhenrik Xymon Historical Status: &XYMWEBHOST - &XYMWEBSVC @ &LOGTIME &XYMONBODYHEADER
 
&XYMONLOGO
Historical Status
&XYMWEBHOST - &XYMWEBSVC
Log time
&LOGTIME


xymon-4.3.7/xymond/webfiles/critedit_footer0000777000175000017500000000000011671641716023762 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/hostgraphs_header0000664000175000017500000000206411615226067020766 0ustar henrikhenrik Xymon - Metrics Report &XYMONBODYHEADER
 
&XYMONLOGO
Metrics Report
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/event_footer0000777000175000017500000000000011671641716023274 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/useradm_form0000664000175000017500000000155211074633713017757 0ustar henrikhenrik
Username: Password:

 
xymon-4.3.7/xymond/webfiles/replog_header0000664000175000017500000000216711615226067020100 0ustar henrikhenrik Xymon Availability Report : &XYMWEBHOST - &XYMWEBSVC &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Availability
&XYMWEBHOST - &XYMWEBSVC
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/critical_header0000664000175000017500000000230411615226067020373 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Status @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Current Critical Systems
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/stdnongreen_header0000664000175000017500000000230511615226067021130 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Status @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Current non-green Systems
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/critack_form0000664000175000017500000000323111535462534017736 0ustar henrikhenrik
Priority Resolver group Documentation
&CRITACKTTPRIO &CRITACKTTGROUP Host info
Host docs
&CRITACKTTEXTRA
  Host-ack   
xymon-4.3.7/xymond/webfiles/hostsvc_header0000664000175000017500000000256011615226067020276 0ustar henrikhenrik &XYMWEBCOLOR : Xymon - &XYMWEBSVC status for &XYMWEBHOST (&XYMWEBIP) @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
&XYMWEBHOST - &XYMWEBSVC
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/useradm_header0000664000175000017500000000206011615226067020240 0ustar henrikhenrik Xymon - Manage Users &XYMONBODYHEADER
 
&XYMONLOGO
Manage Users
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/confreport_header0000664000175000017500000000020611415724561020761 0ustar henrikhenrik Xymon configuration Report xymon-4.3.7/xymond/webfiles/snapnongreen_header0000664000175000017500000000212311615226067021275 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Snapshot &XYMONBODYHEADER
 
&XYMONLOGO
Snapshot - All non-green Systems
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/useradm_footer0000777000175000017500000000000011671641716023613 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/notify_header0000664000175000017500000000223511615226067020114 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Notification Log @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Notification Log
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/zoom.js0000664000175000017500000005426211535462534016704 0ustar henrikhenrik xymon-4.3.7/xymond/webfiles/maintact_footer0000777000175000017500000000000011671641716023753 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/stdnormal_footer0000664000175000017500000000051411535462534020655 0ustar henrikhenrik


Xymon &XYMONDREL
&XYMONBODYFOOTER xymon-4.3.7/xymond/webfiles/perfdata_header0000664000175000017500000000223511615226067020372 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Performance data @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Performance data
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/stdnormal_header0000664000175000017500000000247311615226067020613 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Status @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Current Status
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/confreport_front0000664000175000017500000000477511415724561020700 0ustar henrikhenrik


Explanation of Terms
Basics
AliasesNames for this host other than the primary name, e.g. a hostname used by a client installed on the server
Monitoring locationThe location of this host on the Xymon webpages
Comment
Description
Explanatory text about the host
Planned downtimeTime of day/week when the host monitoring is disabled
SLA Reporting PeriodTime of day/week where the status impacts the SLA availability calculation
Network tests
ServiceCorresponds to the column-name on the Xymon webpage
CriticalWhether this test appears on the Critical Systems view
C/Y/R limitsIf set, this is the number of failures that must happen before the status changes to Clear/Yellow/Red
SpecificsDetails about how this status is monitored
Local tests
ServiceCorresponds to the column-name on the Xymon webpage
CriticalWhether this test appears on the Critical view
C/Y/R limitsIf set, this is the number of failures that must happen before the status changes to Clear/Yellow/Red
ConfigurationDetails about how this status is monitored. NOTE: The exact thresholds for each test are configured on the client, and may differ from that listed here.
xymon-4.3.7/xymond/webfiles/snapshot_footer0000777000175000017500000000000011671641716024012 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/snapshot_form0000664000175000017500000000177711415724561020170 0ustar henrikhenrik
Xymon Snapshot Report






xymon-4.3.7/xymond/webfiles/info_footer0000777000175000017500000000000011671641716023106 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/snapnormal_footer0000777000175000017500000000000011671641716024325 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/notify_footer0000777000175000017500000000000011671641716023463 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/event_form0000664000175000017500000001364611535462534017452 0ustar henrikhenrik
Starting minutes ago ( Default 1440 )
 - OR - 
From: (ccyy/mm/dd@hh:mm:ss)
To: (ccyy/mm/dd@hh:mm:ss)


Max # of events   ( Default 100 )
Hosts to match   ( ex: ^host.*$ )
Hosts to skip   ( ex: ^host.*$ )
Pages to match  
Pages to skip   ( ex: ^webservers/.*$ )
Tests to match   ( ex: cpu|vmstat )
Tests to skip   ( ex: cpu|vmstat )
Colors to match   ( ex: red|green )
Ignore dialup hosts    


xymon-4.3.7/xymond/webfiles/perfdata_form0000664000175000017500000000776211535424634020120 0ustar henrikhenrik
Hosts to match   ( ex: ^host.*$ )
Hosts to skip   ( ex: ^host.*$ )
Pages to match  
Pages to skip   ( ex: ^webservers/.*$ )
Start time   ( ex: 2009/01/15 )
End time   ( ex: 2009/02/01 )
 
Format    
Custom RRD filename    
Custom dataset    


xymon-4.3.7/xymond/webfiles/critedit_header0000664000175000017500000000210611615226067020410 0ustar henrikhenrik Xymon - Critical Systems editor &XYMONBODYHEADER
 
&XYMONLOGO
Critical Systems editor
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/snapcritical_header0000664000175000017500000000213411615226067021256 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Snapshot @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Snapshot - Critical Systems
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/topchanges_header0000664000175000017500000000225011615226067020734 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Top Changes @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Most changing hosts and services
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/findhost_header0000664000175000017500000000237311615226067020425 0ustar henrikhenrik Xymon - Find Host &XYMONBODYHEADER
 
&XYMONLOGO
Find Host
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/acknowledge_form0000664000175000017500000000212211070452713020567 0ustar henrikhenrik
Estimated time to resolve issue: minutes
Explanation/Cause of problem:
Acknowledgment Code:


xymon-4.3.7/xymond/webfiles/stdcritical_footer0000777000175000017500000000000011671641716024460 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/event_header0000664000175000017500000000221511615226067017723 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Eventlog @ &XYMWEBDATE &XYMONBODYHEADER
 
&XYMONLOGO
Eventlog
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/hostlist_footer0000777000175000017500000000000011671641716024024 2stdnormal_footerustar henrikhenrikxymon-4.3.7/xymond/webfiles/columndoc_header0000664000175000017500000000210311615226067020561 0ustar henrikhenrik &XYMWEBBACKGROUND : Xymon - Documentation &XYMONBODYHEADER
 
&XYMONLOGO
Column Info
&XYMWEBDATE


xymon-4.3.7/xymond/webfiles/topchanges_form0000664000175000017500000001473111535462534020460 0ustar henrikhenrik
From: (ccyy/mm/dd@hh:mm:ss)
To: (ccyy/mm/dd@hh:mm:ss)


Hosts to match   ( ex: ^host.*$ )
Hosts to skip   ( ex: ^host.*$ )
Pages to match  
Pages to skip   ( ex: ^webservers/.*$ )
Tests to match   ( ex: cpu|vmstat )
Tests to skip   ( ex: cpu|vmstat )
Colors to match   ( ex: red|green )
Only Critical systems     Ignore dialup hosts    
Rank by    
Show top    


xymon-4.3.7/xymond/webfiles/trends_form0000664000175000017500000001633011535462534017621 0ustar henrikhenrik
Trends period

Days:  Hours:  Minutes:  Seconds: 
 - OR - 
From: (ccyy/mm/dd@hh:mm:ss)
To: (ccyy/mm/dd@hh:mm:ss)
Show last
Show last
Show this

xymon-4.3.7/xymond/webfiles/report_form0000664000175000017500000000275511415724561017641 0ustar henrikhenrik
Xymon Availability Report

- to -




xymon-4.3.7/xymond/webfiles/report_form_weekly0000664000175000017500000000144211415724561021211 0ustar henrikhenrik
Xymon Weekly Availability Report






xymon-4.3.7/xymond/xymond_locator.c0000664000175000017500000005657311630612042016761 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon service locator daemon */ /* */ /* xymond_channel allows you to distribute data across multiple servers, eg. */ /* have several servers handling the RRD updates - for performance and/or */ /* resilience purposes. */ /* For this to work, there must be a way of determining which server handles */ /* a particular task for some ID - e.g. which server holds the RRD files for */ /* host "foo" - so Xymon sends data and requests to the right place. */ /* */ /* This daemon provides the locator service. Tasks may register ID's and */ /* services, allowing others to lookup where they are located. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond_locator.c 6745 2011-09-04 06:01:06Z storner $"; #include "config.h" #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include /* Someday I'll move to GNU Autoconf for this ... */ #endif #include #include #include #include #include #include #include #include #include #include #include #include "version.h" #include "libxymon.h" #include volatile int keeprunning = 1; char *logfile = NULL; /* * For each TYPE of service, we keep two trees: * - a tree with information about what servers provide this service; and * - a tree with information about the hosts that have been registered to * run on this particular server. * Some types of services dont have any host-specific data (e.g. "client"), * then the host-specific tree will just be empty. */ typedef struct serverinfo_t { char *servername, *serverextras; int serverconfweight, serveractualweight, serverweightleft; enum locator_sticky_t sticky; } serverinfo_t; void * sitree[ST_MAX]; xtreePos_t sicurrent[ST_MAX]; typedef struct hostinfo_t { char *hostname; serverinfo_t *server; /* Which server handles this host ? */ } hostinfo_t; void * hitree[ST_MAX]; void tree_init(void) { enum locator_servicetype_t stype; for (stype = 0; (stype < ST_MAX); stype++) { sitree[stype] = xtreeNew(strcasecmp); hitree[stype] = xtreeNew(strcasecmp); } } void recalc_current(enum locator_servicetype_t stype) { /* Point our "last-used-server" pointer at the server with the most negative weight */ xtreePos_t handle, wantedserver; serverinfo_t *oneserver; int minweight = INT_MAX; wantedserver = xtreeEnd(sitree[stype]); for (handle = xtreeFirst(sitree[stype]); (handle != xtreeEnd(sitree[stype])); handle = xtreeNext(sitree[stype], handle)) { oneserver = (serverinfo_t *)xtreeData(sitree[stype], handle); if (oneserver->serveractualweight < minweight) { wantedserver = handle; minweight = oneserver->serveractualweight; } } sicurrent[stype] = wantedserver; } serverinfo_t *register_server(char *servername, enum locator_servicetype_t servicetype, int weight, enum locator_sticky_t sticky, char *extras) { xtreePos_t handle; serverinfo_t *itm = NULL; handle = xtreeFind(sitree[servicetype], servername); if (handle == xtreeEnd(sitree[servicetype])) { itm = (serverinfo_t *)calloc(1, sizeof(serverinfo_t)); itm->servername = strdup(servername); xtreeAdd(sitree[servicetype], itm->servername, itm); } else { /* Update existing item */ itm = xtreeData(sitree[servicetype], handle); } itm->serverconfweight = itm->serveractualweight = weight; itm->serverweightleft = 0; itm->sticky = sticky; if (itm->serverextras) xfree(itm->serverextras); itm->serverextras = (extras ? strdup(extras) : NULL); if (weight < 0) recalc_current(servicetype); return itm; } serverinfo_t *downup_server(char *servername, enum locator_servicetype_t servicetype, char action) { xtreePos_t handle; serverinfo_t *itm = NULL; handle = xtreeFind(sitree[servicetype], servername); if (handle == xtreeEnd(sitree[servicetype])) return NULL; /* Update existing item */ itm = xtreeData(sitree[servicetype], handle); switch (action) { case 'F': /* Flag the hosts that point to this server as un-assigned */ for (handle = xtreeFirst(hitree[servicetype]); (handle != xtreeEnd(hitree[servicetype])); handle = xtreeNext(hitree[servicetype], handle)) { hostinfo_t *hitm = (hostinfo_t *)xtreeData(hitree[servicetype], handle); if (hitm->server == itm) hitm->server = NULL; } /* Fall through */ case 'D': dbgprintf("Downing server '%s' type %s\n", servername, servicetype_names[servicetype]); itm->serveractualweight = 0; itm->serverweightleft = 0; if (itm->serverconfweight < 0) recalc_current(servicetype); break; case 'U': dbgprintf("Upping server '%s' type %s to weight %d\n", servername, servicetype_names[servicetype], itm->serverconfweight); itm->serveractualweight = itm->serverconfweight; /* Dont mess with serverweightleft - this may just be an "i'm alive" message */ if (itm->serverconfweight < 0) recalc_current(servicetype); break; } return itm; } hostinfo_t *register_host(char *hostname, enum locator_servicetype_t servicetype, char *servername) { xtreePos_t handle; hostinfo_t *itm = NULL; handle = xtreeFind(hitree[servicetype], hostname); if (handle == xtreeEnd(hitree[servicetype])) { itm = (hostinfo_t *)calloc(1, sizeof(hostinfo_t)); itm->hostname = strdup(hostname); xtreeAdd(hitree[servicetype], itm->hostname, itm); } else { itm = xtreeData(hitree[servicetype], handle); } /* If we dont know this server, then we must register it. If we do, just update the host record */ handle = xtreeFind(sitree[servicetype], servername); if (handle == xtreeEnd(sitree[servicetype])) { dbgprintf("Registering default server '%s'\n", servername); itm->server = register_server(servername, servicetype, 1, 1, NULL); } else { serverinfo_t *newserver = xtreeData(sitree[servicetype], handle); if (itm->server && (itm->server != newserver)) { errprintf("Warning: Host %s:%s moved from %s to %s\n", hostname, servicetype_names[servicetype], itm->server->servername, newserver->servername); } itm->server = newserver; } return itm; } hostinfo_t *rename_host(char *oldhostname, enum locator_servicetype_t servicetype, char *newhostname) { xtreePos_t handle, newhandle; hostinfo_t *itm = NULL; handle = xtreeFind(hitree[servicetype], oldhostname); if (handle == xtreeEnd(hitree[servicetype])) return NULL; newhandle = xtreeFind(hitree[servicetype], newhostname); if (newhandle != xtreeEnd(hitree[servicetype])) { errprintf("Ignored rename of %s to %s - new name already exists\n", oldhostname, newhostname); return NULL; } itm = xtreeData(hitree[servicetype], handle); xtreeDelete(hitree[servicetype], oldhostname); xfree(itm->hostname); itm->hostname = strdup(newhostname); xtreeAdd(hitree[servicetype], itm->hostname, itm); return itm; } serverinfo_t *find_server_by_type(enum locator_servicetype_t servicetype) { serverinfo_t *nextserver = NULL; xtreePos_t endmarker = xtreeEnd(sitree[servicetype]); /* * We must do weight handling here. * * Some weights (the serveractualweight attribute) have special meaning: * Negative: Only one server receives requests. We should use the * server with the lowest weight. * 0 : This server is down and cannot receive any requests. * 1 : Server is up, but should only receive requests for hosts * that have been registered as resident on this server. * >1 : Server is up and handles any request. * * When looking for a server, we do a weighted round-robin of the * servers that are available. The idea is that each server has * "serveractualweight" tokens, and we use them one by one for each * request. "serveractualweightleft" tells how many tokens are left. * When a server has been used once, we go to the next server which * has any tokens left. When all tokens have been used, we replenish * the token counts from "serveractualweight" and start over. * * sicurrent[servicetype] points to the tree-handle of the last server * that was used. */ if (sicurrent[servicetype] != endmarker) { serverinfo_t *lastserver; /* We have a last-server-used for this request */ lastserver = xtreeData(sitree[servicetype], sicurrent[servicetype]); /* * See if our the last server used handles all requests. * If it does, then sicurrent[servicetype] will ALWAYS * contain the server we want to use (it is updated * whenever servers register or weights change). */ if (lastserver->serveractualweight < 0) return lastserver; /* OK, we have now used one token from this server. */ if (lastserver->serveractualweight > 1) lastserver->serverweightleft -= 1; /* Go to the next server with any tokens left */ do { sicurrent[servicetype] = xtreeNext(sitree[servicetype], sicurrent[servicetype]); if (sicurrent[servicetype] == endmarker) { /* Start from the beginning again */ sicurrent[servicetype] = xtreeFirst(sitree[servicetype]); } nextserver = xtreeData(sitree[servicetype], sicurrent[servicetype]); } while ((nextserver->serverweightleft == 0) && (nextserver != lastserver)); if ((nextserver == lastserver) && (nextserver->serverweightleft == 0)) { /* Could not find any servers with a token left for us to use */ nextserver = NULL; } } if (nextserver == NULL) { /* Restart server RR walk */ int totalweight = 0; xtreePos_t handle, firstok; serverinfo_t *srv; firstok = endmarker; /* Walk the list of servers, calculate total weight and find the first active server */ for (handle = xtreeFirst(sitree[servicetype]); ( (handle != endmarker) && (totalweight >= 0) ); handle = xtreeNext(sitree[servicetype], handle) ) { srv = xtreeData(sitree[servicetype], handle); if (srv->serveractualweight <= 1) continue; srv->serverweightleft = (srv->serveractualweight - 1); totalweight += srv->serverweightleft; if (firstok == endmarker) firstok = handle; } sicurrent[servicetype] = firstok; nextserver = (firstok != endmarker) ? xtreeData(sitree[servicetype], firstok) : NULL; } return nextserver; } serverinfo_t *find_server_by_host(enum locator_servicetype_t servicetype, char *hostname) { xtreePos_t handle; hostinfo_t *hinfo; handle = xtreeFind(hitree[servicetype], hostname); if (handle == xtreeEnd(hitree[servicetype])) { return NULL; } hinfo = xtreeData(hitree[servicetype], handle); return hinfo->server; } void load_state(void) { char *tmpdir; char *fn; FILE *fd; char buf[4096]; char *tname, *sname, *sconfweight, *sactweight, *ssticky, *sextra, *hname; enum locator_servicetype_t stype; tmpdir = xgetenv("XYMONTMP"); if (!tmpdir) tmpdir = "/tmp"; fn = (char *)malloc(strlen(tmpdir) + 100); sprintf(fn, "%s/locator.servers.chk", tmpdir); fd = fopen(fn, "r"); if (fd) { while (fgets(buf, sizeof(buf), fd)) { serverinfo_t *srv; tname = sname = sconfweight = sactweight = ssticky = sextra = NULL; tname = strtok(buf, "|\n"); if (tname) sname = strtok(NULL, "|\n"); if (sname) sconfweight = strtok(NULL, "|\n"); if (sconfweight) sactweight = strtok(NULL, "|\n"); if (sactweight) ssticky = strtok(NULL, "|\n"); if (ssticky) sextra = strtok(NULL, "\n"); if (tname && sname && sconfweight && sactweight && ssticky) { enum locator_sticky_t sticky = (atoi(ssticky) == 1) ? LOC_STICKY : LOC_ROAMING; stype = get_servicetype(tname); srv = register_server(sname, stype, atoi(sconfweight), sticky, sextra); srv->serveractualweight = atoi(sactweight); dbgprintf("Loaded server %s/%s (cweight %d, aweight %d, %s)\n", srv->servername, tname, srv->serverconfweight, srv->serveractualweight, (srv->sticky ? "sticky" : "not sticky")); } } fclose(fd); } for (stype = 0; (stype < ST_MAX); stype++) recalc_current(stype); sprintf(fn, "%s/locator.hosts.chk", tmpdir); fd = fopen(fn, "r"); if (fd) { while (fgets(buf, sizeof(buf), fd)) { tname = hname = sname = NULL; tname = strtok(buf, "|\n"); if (tname) hname = strtok(NULL, "|\n"); if (hname) sname = strtok(NULL, "|\n"); if (tname && hname && sname) { enum locator_servicetype_t stype = get_servicetype(tname); register_host(hname, stype, sname); dbgprintf("Loaded host %s/%s for server %s\n", hname, tname, sname); } } fclose(fd); } } void save_state(void) { char *tmpdir; char *fn; FILE *fd; int tidx; tmpdir = xgetenv("XYMONTMP"); if (!tmpdir) tmpdir = "/tmp"; fn = (char *)malloc(strlen(tmpdir) + 100); sprintf(fn, "%s/locator.servers.chk", tmpdir); fd = fopen(fn, "w"); if (fd == NULL) { errprintf("Cannot save state to %s: %s\n", fn, strerror(errno)); return; } for (tidx = 0; (tidx < ST_MAX); tidx++) { const char *tname = servicetype_names[tidx]; xtreePos_t handle; serverinfo_t *itm; for (handle = xtreeFirst(sitree[tidx]); (handle != xtreeEnd(sitree[tidx])); handle = xtreeNext(sitree[tidx], handle)) { itm = xtreeData(sitree[tidx], handle); fprintf(fd, "%s|%s|%d|%d|%d|%s\n", tname, itm->servername, itm->serverconfweight, itm->serveractualweight, ((itm->sticky == LOC_STICKY) ? 1 : 0), (itm->serverextras ? itm->serverextras : "")); } } fclose(fd); sprintf(fn, "%s/locator.hosts.chk", tmpdir); fd = fopen(fn, "w"); if (fd == NULL) { errprintf("Cannot save state to %s: %s\n", fn, strerror(errno)); return; } for (tidx = 0; (tidx < ST_MAX); tidx++) { const char *tname = servicetype_names[tidx]; xtreePos_t handle; hostinfo_t *itm; for (handle = xtreeFirst(hitree[tidx]); (handle != xtreeEnd(hitree[tidx])); handle = xtreeNext(hitree[tidx], handle)) { itm = xtreeData(hitree[tidx], handle); if (itm->server) { fprintf(fd, "%s|%s|%s\n", tname, itm->hostname, itm->server->servername); } } } fclose(fd); } void sigmisc_handler(int signum) { switch (signum) { case SIGTERM: errprintf("Caught TERM signal, terminating\n"); keeprunning = 0; break; case SIGHUP: if (logfile) { freopen(logfile, "a", stdout); freopen(logfile, "a", stderr); errprintf("Caught SIGHUP, reopening logfile\n"); } break; } } void handle_request(char *buf) { const char *delims = "|\r\n\t "; switch (buf[0]) { case 'S': /* Register server|type|weight|sticky|extras */ { char *tok, *servername = NULL; enum locator_servicetype_t servicetype = ST_MAX; int serverweight = 0; enum locator_sticky_t sticky = LOC_ROAMING; char *serverextras = NULL; tok = strtok(buf, delims); if (tok) { tok = strtok(NULL, delims); } if (tok) { servername = tok; tok = strtok(NULL, delims); } if (tok) { servicetype = get_servicetype(tok); tok = strtok(NULL, delims); } if (tok) { serverweight = atoi(tok); tok = strtok(NULL, delims); } if (tok) { sticky = ((atoi(tok) == 1) ? LOC_STICKY : LOC_ROAMING); tok = strtok(NULL, delims); } if (tok) { serverextras = tok; tok = strtok(NULL, delims); } if (servername && (servicetype != ST_MAX)) { dbgprintf("Registering server '%s' handling %s (weight %d, %s)\n", servername, servicetype_names[servicetype], serverweight, (sticky == LOC_STICKY ? "sticky" : "not sticky")); register_server(servername, servicetype, serverweight, sticky, serverextras); strcpy(buf, "OK"); } else strcpy(buf, "BADSYNTAX"); } break; case 'D': case 'U': case 'F': /* Down/Up/Forget server|type */ { char *tok, *servername = NULL; enum locator_servicetype_t servicetype = ST_MAX; tok = strtok(buf, delims); if (tok) { tok = strtok(NULL, delims); } if (tok) { servername = tok; tok = strtok(NULL, delims); } if (tok) { servicetype = get_servicetype(tok); tok = strtok(NULL, delims); } if (servername && (servicetype != ST_MAX)) { downup_server(servername, servicetype, buf[0]); strcpy(buf, "OK"); } else strcpy(buf, "BADSYNTAX"); } break; case 'H': /* Register host|type|server */ { char *tok, *hostname = NULL, *servername = NULL; enum locator_servicetype_t servicetype = ST_MAX; tok = strtok(buf, delims); if (tok) { tok = strtok(NULL, delims); } if (tok) { hostname = tok; tok = strtok(NULL, delims); } if (tok) { servicetype = get_servicetype(tok); tok = strtok(NULL, delims); } if (tok) { servername = tok; tok = strtok(NULL, delims); } if (hostname && (servicetype != ST_MAX) && servername) { dbgprintf("Registering type/host %s/%s handled by server %s\n", servicetype_names[servicetype], hostname, servername); register_host(hostname, servicetype, servername); strcpy(buf, "OK"); } else strcpy(buf, "BADSYNTAX"); } break; case 'M': /* Rename host|type|newhostname */ { char *tok, *oldhostname = NULL, *newhostname = NULL; enum locator_servicetype_t servicetype = ST_MAX; tok = strtok(buf, delims); if (tok) { tok = strtok(NULL, delims); } if (tok) { oldhostname = tok; tok = strtok(NULL, delims); } if (tok) { servicetype = get_servicetype(tok); tok = strtok(NULL, delims); } if (tok) { newhostname = tok; tok = strtok(NULL, delims); } if (oldhostname && (servicetype != ST_MAX) && newhostname) { dbgprintf("Renaming type/host %s/%s to %s\n", servicetype_names[servicetype], oldhostname, newhostname); if (rename_host(oldhostname, servicetype, newhostname)) { strcpy(buf, "OK"); } else { strcpy(buf, "FAILED"); } } else strcpy(buf, "BADSYNTAX"); } break; case 'X': case 'Q': /* Query type|host */ { char *tok, *hostname = NULL; enum locator_servicetype_t servicetype = ST_MAX; int extquery = (buf[0] == 'X'); serverinfo_t *res = NULL; tok = strtok(buf, delims); if (tok) { tok = strtok(NULL, delims); } if (tok) { servicetype = get_servicetype(tok); tok = strtok(NULL, delims); } if (tok) { hostname = tok; tok = strtok(NULL, delims); } if ((servicetype != ST_MAX) && hostname) { res = find_server_by_host(servicetype, hostname); if (res) { /* This host is fixed on a specific server ... */ if (res->serveractualweight > 0) { /* ... and that server is UP */ sprintf(buf, "!|%s", res->servername); } else { /* ... and the server is DOWN, so we cannot service the request */ strcpy(buf, "?"); } } else { /* Roaming or un-registered host */ res = find_server_by_type(servicetype); if (res) { if (res->sticky == LOC_STICKY) { dbgprintf("Host %s/%s now fixed on server %s\n", hostname, servicetype_names[servicetype], res->servername); register_host(hostname, servicetype, res->servername); } sprintf(buf, "*|%s", res->servername); } else { strcpy(buf, "?"); } } if (res && extquery) { int blen = strlen(buf); snprintf(buf+blen, sizeof(buf)-blen-1, "|%s", res->serverextras); } } else strcpy(buf, "BADSYNTAX"); } break; case 'p': /* Locator ping */ sprintf(buf, "PONG|%s", VERSION); break; case '@': /* Save state */ save_state(); strcpy(buf, "OK"); break; default: strcpy(buf, "BADREQUEST"); break; } } int main(int argc, char *argv[]) { int daemonize = 0; int lsocket; struct sockaddr_in laddr; struct sigaction sa; int argi, opt; /* Dont save the output from errprintf() */ save_errbuf = 0; memset(&laddr, 0, sizeof(laddr)); laddr.sin_addr.s_addr = htonl(INADDR_ANY); laddr.sin_port = htons(1984); laddr.sin_family = AF_INET; for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--listen=")) { char *locaddr, *p; int locport; locaddr = strchr(argv[argi], '=')+1; p = strchr(locaddr, ':'); if (p) { locport = atoi(p+1); *p = '\0'; } else locport = 1984; memset(&laddr, 0, sizeof(laddr)); laddr.sin_port = htons(locport); laddr.sin_family = AF_INET; if (inet_aton(locaddr, (struct in_addr *) &laddr.sin_addr.s_addr) == 0) { errprintf("Invalid listen address %s\n", locaddr); return 1; } } else if (strcmp(argv[argi], "--daemon") == 0) { daemonize = 1; } else if (strcmp(argv[argi], "--no-daemon") == 0) { daemonize = 0; } else if (argnmatch(argv[argi], "--logfile=")) { char *p = strchr(argv[argi], '='); logfile = strdup(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } } /* Set up a socket to listen for new connections */ lsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (lsocket == -1) { errprintf("Cannot create listen socket (%s)\n", strerror(errno)); return 1; } opt = 1; setsockopt(lsocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (bind(lsocket, (struct sockaddr *)&laddr, sizeof(laddr)) == -1) { errprintf("Cannot bind to listener address (%s)\n", strerror(errno)); return 1; } /* Make it non-blocking */ fcntl(lsocket, F_SETFL, O_NONBLOCK); /* Redirect logging to the logfile, if requested */ if (logfile) { freopen(logfile, "a", stdout); freopen(logfile, "a", stderr); } errprintf("Xymon locator version %s starting\n", VERSION); errprintf("Listening on %s:%d\n", inet_ntoa(laddr.sin_addr), ntohs(laddr.sin_port)); if (daemonize) { pid_t childpid; freopen("/dev/null", "a", stdin); /* Become a daemon */ childpid = fork(); if (childpid < 0) { /* Fork failed */ errprintf("Could not fork\n"); exit(1); } else if (childpid > 0) { /* Parent - exit */ exit(0); } /* Child (daemon) continues here */ setsid(); } setup_signalhandler("xymond_locator"); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigmisc_handler; sigaction(SIGHUP, &sa, NULL); sigaction(SIGTERM, &sa, NULL); tree_init(); load_state(); do { ssize_t n; struct sockaddr_in remaddr; socklen_t remaddrsz; char buf[32768]; fd_set fdread; /* Wait for a message */ FD_ZERO(&fdread); FD_SET(lsocket, &fdread); n = select(lsocket+1, &fdread, NULL, NULL, NULL); if (n == -1) { /* Select error */ errprintf("select error, aborting: %s\n", strerror(errno)); keeprunning = 0; continue; } /* We know there is some data */ remaddrsz = sizeof(remaddr); n = recvfrom(lsocket, buf, sizeof(buf), 0, (struct sockaddr *)&remaddr, &remaddrsz); if (n == -1) { /* We may get EAGAIN if there is not a full message yet */ if (errno != EAGAIN) { errprintf("Recv error: %s\n", strerror(errno)); } continue; } else if (n == 0) { continue; } buf[n] = '\0'; dbgprintf("Got message from %s:%d : '%s'\n", inet_ntoa(remaddr.sin_addr), ntohs(remaddr.sin_port), buf); handle_request(buf); n = sendto(lsocket, buf, strlen(buf)+1, 0, (struct sockaddr *)&remaddr, remaddrsz); if (n == -1) { if (errno == EAGAIN) { errprintf("Out-queue full to %s, dropping response\n", inet_ntoa(remaddr.sin_addr)); } else { errprintf("Send failure %s while sending to %s\n", strerror(errno), inet_ntoa(remaddr.sin_addr)); } } } while (keeprunning); save_state(); return 0; } xymon-4.3.7/xymond/xymond_hostdata.c0000664000175000017500000001776411630732124017130 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* This Xymon worker module saves the client messages that arrive on the */ /* CLICHG channel, for use when looking at problems with a host. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond_hostdata.c 6748 2011-09-04 17:24:36Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymond_worker.h" #include #define MAX_META 20 /* The maximum number of meta-data items in a message */ typedef struct savetimes_t { char *hostname; time_t tstamp[12]; } savetimes_t; void * savetimes; static char *clientlogdir = NULL; int nextfscheck = 0; void sig_handler(int signum) { /* * Why this? Because we must have our own signal handler installed to call wait() */ switch (signum) { case SIGCHLD: break; case SIGHUP: nextfscheck = 0; break; } } void update_locator_hostdata(char *id) { DIR *fd; struct dirent *d; fd = opendir(clientlogdir); if (fd == NULL) { errprintf("Cannot scan directory %s\n", clientlogdir); return; } while ((d = readdir(fd)) != NULL) { if (*(d->d_name) == '.') continue; locator_register_host(d->d_name, ST_HOSTDATA, id); } closedir(fd); } int main(int argc, char *argv[]) { char *msg; int running; int argi, seq; int recentperiod = 3600; int maxrecentcount = 5; int logdirfull = 0; int minlogspace = 5; struct sigaction sa; /* Handle program options. */ for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--logdir=")) { clientlogdir = strchr(argv[argi], '=')+1; } else if (argnmatch(argv[argi], "--recent-period=")) { char *p = strchr(argv[argi], '='); recentperiod = 60*atoi(p+1); } else if (argnmatch(argv[argi], "--recent-count=")) { char *p = strchr(argv[argi], '='); maxrecentcount = atoi(p+1); } else if (argnmatch(argv[argi], "--minimum-free=")) { minlogspace = atoi(strchr(argv[argi], '=')+1); } else if (strcmp(argv[argi], "--debug") == 0) { /* * A global "debug" variable is available. If * it is set, then "dbgprintf()" outputs debug messages. */ debug = 1; } else if (net_worker_option(argv[argi])) { /* Handled in the subroutine */ } } if (clientlogdir == NULL) clientlogdir = xgetenv("CLIENTLOGS"); if (clientlogdir == NULL) { clientlogdir = (char *)malloc(strlen(xgetenv("XYMONVAR")) + 10); sprintf(clientlogdir, "%s/hostdata", xgetenv("XYMONVAR")); } save_errbuf = 0; /* Do the network stuff if needed */ net_worker_run(ST_HOSTDATA, LOC_STICKY, update_locator_hostdata); setup_signalhandler("xymond_hostdata"); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; sigaction(SIGHUP, &sa, NULL); signal(SIGPIPE, SIG_DFL); savetimes = xtreeNew(strcasecmp); running = 1; while (running) { char *eoln, *restofmsg, *p; char *metadata[MAX_META+1]; int metacount; msg = get_xymond_message(C_CLICHG, "xymond_hostdata", &seq, NULL); if (msg == NULL) { /* * get_xymond_message will return NULL if xymond_channel closes * the input pipe. We should shutdown when that happens. */ running = 0; continue; } if (nextfscheck < gettimer()) { logdirfull = (chkfreespace(clientlogdir, minlogspace, minlogspace) != 0); if (logdirfull) errprintf("Hostdata directory %s has less than %d%% free space - disabling save of data for 5 minutes\n", clientlogdir, minlogspace); nextfscheck = gettimer() + 300; } /* Split the message in the first line (with meta-data), and the rest */ eoln = strchr(msg, '\n'); if (eoln) { *eoln = '\0'; restofmsg = eoln+1; } else { restofmsg = ""; } metacount = 0; memset(&metadata, 0, sizeof(metadata)); p = gettok(msg, "|"); while (p && (metacount < MAX_META)) { metadata[metacount++] = p; p = gettok(NULL, "|"); } metadata[metacount] = NULL; if (strncmp(metadata[0], "@@clichg", 8) == 0) { xtreePos_t handle; savetimes_t *itm; int i, recentcount; time_t now = gettimer(); char hostdir[PATH_MAX]; char fn[PATH_MAX]; FILE *fd; /* metadata[3] is the hostname */ handle = xtreeFind(savetimes, metadata[3]); if (handle != xtreeEnd(savetimes)) { itm = (savetimes_t *)xtreeData(savetimes, handle); } else { itm = (savetimes_t *)calloc(1, sizeof(savetimes_t)); itm->hostname = strdup(metadata[3]); xtreeAdd(savetimes, itm->hostname, itm); } /* See how many times we've saved the hostdata recently (within the past 'recentperiod' seconds) */ for (i=0, recentcount=0; ((i < 12) && (itm->tstamp[i] > (now - recentperiod))); i++) recentcount++; /* If it's been saved less than 'maxrecentcount' times, then save it. Otherwise just drop it */ if (!logdirfull && (recentcount < maxrecentcount)) { int written, closestatus, ok = 1; for (i = 10; (i > 0); i--) itm->tstamp[i+1] = itm->tstamp[i]; itm->tstamp[0] = now; sprintf(hostdir, "%s/%s", clientlogdir, metadata[3]); mkdir(hostdir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); sprintf(fn, "%s/%s", hostdir, metadata[4]); fd = fopen(fn, "w"); if (fd == NULL) { errprintf("Cannot create file %s: %s\n", fn, strerror(errno)); continue; } written = fwrite(restofmsg, 1, strlen(restofmsg), fd); if (written != strlen(restofmsg)) { errprintf("Cannot write hostdata file %s: %s\n", fn, strerror(errno)); closestatus = fclose(fd); /* Ignore any close errors */ ok = 0; } else { closestatus = fclose(fd); if (closestatus != 0) { errprintf("Cannot write hostdata file %s: %s\n", fn, strerror(errno)); ok = 0; } } if (!ok) remove(fn); } } /* * A "shutdown" message is sent when the master daemon * terminates. The child workers should shutdown also. */ else if (strncmp(metadata[0], "@@shutdown", 10) == 0) { running = 0; continue; } else if (strncmp(metadata[0], "@@idle", 6) == 0) { /* Ignored */ continue; } /* * A "logrotate" message is sent when the Xymon logs are * rotated. The child workers must re-open their logfiles, * typically stdin and stderr - the filename is always * provided in the XYMONCHANNEL_LOGFILENAME environment. */ else if (strncmp(metadata[0], "@@logrotate", 11) == 0) { char *fn = xgetenv("XYMONCHANNEL_LOGFILENAME"); if (fn && strlen(fn)) { freopen(fn, "a", stdout); freopen(fn, "a", stderr); } continue; } else if ((metacount > 3) && (strncmp(metadata[0], "@@drophost", 10) == 0)) { /* @@drophost|timestamp|sender|hostname */ char hostdir[PATH_MAX]; sprintf(hostdir, "%s/%s", clientlogdir, metadata[3]); dropdirectory(hostdir, 1); } else if ((metacount > 4) && (strncmp(metadata[0], "@@renamehost", 12) == 0)) { /* @@renamehost|timestamp|sender|hostname|newhostname */ char oldhostdir[PATH_MAX], newhostdir[PATH_MAX]; sprintf(oldhostdir, "%s/%s", clientlogdir, metadata[3]); sprintf(newhostdir, "%s/%s", clientlogdir, metadata[4]); rename(oldhostdir, newhostdir); if (net_worker_locatorbased()) locator_rename_host(metadata[3], metadata[4], ST_HOSTDATA); } else if (strncmp(metadata[0], "@@reload", 8) == 0) { /* Do nothing */ } } return 0; } xymon-4.3.7/xymond/convertnk.c0000664000175000017500000000316111615341300015711 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon utility to convert the deprecated NK tags to a critical.cfg */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: convertnk.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include "libxymon.h" int main(int argc, char *argv[]) { void *walk; load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); for (walk = first_host(); (walk); walk=next_host(walk, 0)) { char *nk, *nktime, *tok; nk = xmh_item(walk, XMH_NK); if (!nk) continue; nktime = xmh_item(walk, XMH_NKTIME); nk = strdup(nk); tok = strtok(nk, ","); while (tok) { char *hostname = xmh_item(walk, XMH_HOSTNAME); char *startstr = "", *endstr = "", *ttgroup = "", *ttextra = "", *updinfo = "Migrated"; int priority = 2; fprintf(stdout, "%s|%s|%s|%s|%s|%d|%s|%s|%s\n", hostname, tok, startstr, endstr, (nktime ? nktime : ""), priority, ttgroup, ttextra, updinfo); tok = strtok(NULL, ","); } xfree(nk); } return 0; } xymon-4.3.7/xymond/do_rrd.h0000664000175000017500000000237411615341300015163 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __DO_RRD_H__ #define __DO_RRD_H__ #include #include "libxymon.h" extern char *rrddir; extern char *trackmax; extern int use_rrd_cache; extern void setup_exthandler(char *handlerpath, char *ids); extern void update_rrd(char *hostname, char *testname, char *restofmsg, time_t tstamp, char *sender, xymonrrd_t *ldef, char *classname, char *pagepaths); extern void rrdcacheflushall(void); extern void rrdcacheflushhost(char *hostname); extern void setup_extprocessor(char *cmd); extern void shutdown_extprocessor(void); #endif xymon-4.3.7/xymond/xymond_channel.c0000664000175000017500000005200311630612042016706 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* This module receives messages from one channel of the Xymon master daemon. */ /* These messages are then forwarded to the actual worker process via stdin; */ /* the worker process can process the messages without having to care about */ /* the tricky details in the xymond/xymond_channel communications. */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymond_channel.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include #include #include #include #include #include "libxymon.h" #include "xymond_ipc.h" #include #define MSGTIMEOUT 30 /* Seconds */ /* Our in-memory queue of messages received from xymond via IPC. One queue per peer. */ typedef struct xymon_msg_t { time_t tstamp; /* When did the message arrive */ char *buf; /* The message data */ char *bufp; /* Next char to send */ int buflen; /* How many bytes left to send */ struct xymon_msg_t *next; } xymon_msg_t; /* Our list of peers we send data to */ typedef struct xymon_peer_t { char *peername; enum { P_DOWN, P_UP, P_FAILED } peerstatus; xymon_msg_t *msghead, *msgtail; /* Message queue */ enum { P_LOCAL, P_NET } peertype; int peersocket; /* File descriptor receiving the data */ time_t lastopentime; /* Last time we attempted to connect to the peer */ /* For P_NET peers */ struct sockaddr_in peeraddr; /* The IP address of the peer */ /* For P_LOCAL peers */ char *childcmd; /* Command and arguments for the child process */ char **childargs; pid_t childpid; /* PID of the running worker child */ } xymon_peer_t; void * peers; xymond_channel_t *channel = NULL; char *logfn = NULL; int locatorbased = 0; enum locator_servicetype_t locatorservice = ST_MAX; static int running = 1; static int gotalarm = 0; static int pendingcount = 0; /* * chksumsize is the space left in front of the message buffer, to * allow room for a message digest checksum to be added to the * message. Since we use an MD5 hash, this will be 32 bytes * plus a one-char marker. */ static int checksumsize = 0; void addnetpeer(char *peername) { xymon_peer_t *newpeer; struct in_addr addr; char *oneip; int peerport = 0; char *delim; dbgprintf("Adding network peer %s\n", peername); oneip = strdup(peername); delim = strchr(oneip, ':'); if (delim) { *delim = '\0'; peerport = atoi(delim+1); } if (inet_aton(oneip, &addr) == 0) { /* peer is not an IP - do DNS lookup */ struct hostent *hent; hent = gethostbyname(oneip); if (hent) { char *realip; memcpy(&addr, *(hent->h_addr_list), sizeof(struct in_addr)); realip = inet_ntoa(addr); if (inet_aton(realip, &addr) == 0) { errprintf("Invalid IP address for %s (%s)\n", oneip, realip); goto done; } } else { errprintf("Cannot determine IP address of peer %s\n", oneip); goto done; } } if (peerport == 0) peerport = atoi(xgetenv("XYMONDPORT")); newpeer = calloc(1, sizeof(xymon_peer_t)); newpeer->peername = strdup(peername); newpeer->peerstatus = P_DOWN; newpeer->peertype = P_NET; newpeer->peeraddr.sin_family = AF_INET; newpeer->peeraddr.sin_addr.s_addr = addr.s_addr; newpeer->peeraddr.sin_port = htons(peerport); xtreeAdd(peers, newpeer->peername, newpeer); done: xfree(oneip); } void addlocalpeer(char *childcmd, char **childargs) { xymon_peer_t *newpeer; int i, count; dbgprintf("Adding local peer using command %s\n", childcmd); for (count=0; (childargs[count]); count++) ; newpeer = (xymon_peer_t *)calloc(1, sizeof(xymon_peer_t)); newpeer->peername = strdup(""); newpeer->peerstatus = P_DOWN; newpeer->peertype = P_LOCAL; newpeer->childcmd = strdup(childcmd); newpeer->childargs = (char **)calloc(count+1, sizeof(char *)); for (i=0; (ichildargs[i] = strdup(childargs[i]); xtreeAdd(peers, newpeer->peername, newpeer); } void openconnection(xymon_peer_t *peer) { int n; int pfd[2]; pid_t childpid; time_t now; peer->peerstatus = P_DOWN; now = gettimer(); if (now < (peer->lastopentime + 60)) return; /* Will only attempt one open per minute */ dbgprintf("Connecting to peer %s:%d\n", inet_ntoa(peer->peeraddr.sin_addr), ntohs(peer->peeraddr.sin_port)); peer->lastopentime = now; switch (peer->peertype) { case P_NET: /* Get a socket, and connect to the peer */ peer->peersocket = socket(PF_INET, SOCK_STREAM, 0); if (peer->peersocket == -1) { errprintf("Cannot get socket: %s\n", strerror(errno)); return; } n = connect(peer->peersocket, (struct sockaddr *)&peer->peeraddr, sizeof(peer->peeraddr)); if (n == -1) { errprintf("Cannot connect to peer %s:%d : %s\n", inet_ntoa(peer->peeraddr.sin_addr), ntohs(peer->peeraddr.sin_port), strerror(errno)); return; } break; case P_LOCAL: /* Create a pipe to the child handler program, and run it */ n = pipe(pfd); if (n == -1) { errprintf("Could not get a pipe: %s\n", strerror(errno)); return; } childpid = fork(); if (childpid == -1) { errprintf("Could not fork channel handler: %s\n", strerror(errno)); return; } else if (childpid == 0) { /* The channel handler child */ if (logfn) { char *logfnenv = (char *)malloc(strlen(logfn) + 30); sprintf(logfnenv, "XYMONCHANNEL_LOGFILENAME=%s", logfn); putenv(logfnenv); } n = dup2(pfd[0], STDIN_FILENO); close(pfd[0]); close(pfd[1]); n = execvp(peer->childcmd, peer->childargs); /* We should never go here */ errprintf("exec() failed for child command %s: %s\n", peer->childcmd, strerror(errno)); exit(1); } /* Parent process continues */ close(pfd[0]); peer->peersocket = pfd[1]; peer->childpid = childpid; break; } fcntl(peer->peersocket, F_SETFL, O_NONBLOCK); peer->peerstatus = P_UP; dbgprintf("Peer is UP\n"); } void flushmessage(xymon_peer_t *peer) { xymon_msg_t *zombie; zombie = peer->msghead; peer->msghead = peer->msghead->next; if (peer->msghead == NULL) peer->msgtail = NULL; xfree(zombie->buf); xfree(zombie); pendingcount--; } static void addmessage_onepeer(xymon_peer_t *peer, char *inbuf, int inlen) { xymon_msg_t *newmsg; newmsg = (xymon_msg_t *) calloc(1, sizeof(xymon_msg_t)); newmsg->tstamp = gettimer(); newmsg->buf = newmsg->bufp = inbuf; newmsg->buflen = inlen; /* * If we've flagged the peer as FAILED, then change status to DOWN so * we will attempt to reconnect to the peer. The locator believes it is * up and running, so it probably is ... */ if (peer->peerstatus == P_FAILED) peer->peerstatus = P_DOWN; /* If the peer is down, we will only permit ONE message in the queue. */ if (peer->peerstatus != P_UP) { errprintf("Peer not up, flushing message queue\n"); while (peer->msghead) flushmessage(peer); } if (peer->msghead == NULL) { peer->msghead = peer->msgtail = newmsg; } else { peer->msgtail->next = newmsg; peer->msgtail = newmsg; } pendingcount++; } int addmessage(char *inbuf) { xtreePos_t phandle; xymon_peer_t *peer; int bcastmsg = 0; int inlen = strlen(inbuf); if (locatorbased) { char *hostname, *hostend, *peerlocation; /* xymond sends us messages with the KEY in the first field, between a '/' and a '|' */ hostname = inbuf + strcspn(inbuf, "/|\r\n"); if (*hostname != '/') { errprintf("No key field in message, dropping it\n"); return -1; /* Malformed input */ } hostname++; bcastmsg = (*hostname == '*'); if (!bcastmsg) { /* Lookup which server handles this host */ hostend = hostname + strcspn(hostname, "|\r\n"); if (*hostend != '|') { errprintf("No delimiter found in input, dropping it\n"); return -1; /* Malformed input */ } *hostend = '\0'; peerlocation = locator_query(hostname, locatorservice, NULL); /* * If we get no response, or an empty response, * then there is no server capable of handling this * request. */ if (!peerlocation || (*peerlocation == '\0')) { errprintf("No response from locator for %s/%s, dropping it\n", servicetype_names[locatorservice], hostname); return -1; } *hostend = '|'; phandle = xtreeFind(peers, peerlocation); if (phandle == xtreeEnd(peers)) { /* New peer - register it */ addnetpeer(peerlocation); phandle = xtreeFind(peers, peerlocation); } } } else { phandle = xtreeFind(peers, ""); } if (bcastmsg) { for (phandle = xtreeFirst(peers); (phandle != xtreeEnd(peers)); phandle = xtreeNext(peers, phandle)) { peer = (xymon_peer_t *)xtreeData(peers, phandle); addmessage_onepeer(peer, inbuf, inlen); } } else { if (phandle == xtreeEnd(peers)) { errprintf("No peer found to handle message, dropping it\n"); return -1; } peer = (xymon_peer_t *)xtreeData(peers, phandle); addmessage_onepeer(peer, inbuf, inlen); } return 0; } void shutdownconnection(xymon_peer_t *peer) { if (peer->peerstatus != P_UP) return; peer->peerstatus = P_DOWN; switch (peer->peertype) { case P_LOCAL: close(peer->peersocket); peer->peersocket = -1; if (peer->childpid > 0) kill(peer->childpid, SIGTERM); peer->childpid = 0; break; case P_NET: shutdown(peer->peersocket, SHUT_RDWR); close(peer->peersocket); peer->peersocket = -1; break; } /* Any messages queued are discarded */ while (peer->msghead) flushmessage(peer); peer->msghead = peer->msgtail = NULL; } void sig_handler(int signum) { int childexit; switch (signum) { case SIGTERM: case SIGINT: /* Shutting down. */ running = 0; break; case SIGCHLD: /* Our worker child died. Avoid zombies. */ wait(&childexit); break; case SIGALRM: gotalarm = 1; break; } } int main(int argc, char *argv[]) { int daemonize = 0; char *pidfile = NULL; char *envarea = NULL; int cnid = -1; pcre *msgfilter = NULL; pcre *stdfilter = NULL; int argi; struct sigaction sa; xtreePos_t handle; /* Dont save the error buffer */ save_errbuf = 0; /* Create the peer container */ peers = xtreeNew(strcasecmp); for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--debug")) { debug = 1; } else if (argnmatch(argv[argi], "--channel=")) { char *cn = strchr(argv[argi], '=') + 1; for (cnid = C_STATUS; (channelnames[cnid] && strcmp(channelnames[cnid], cn)); cnid++) ; if (channelnames[cnid] == NULL) cnid = -1; } else if (argnmatch(argv[argi], "--daemon")) { daemonize = 1; } else if (argnmatch(argv[argi], "--no-daemon")) { daemonize = 0; } else if (argnmatch(argv[argi], "--pidfile=")) { char *p = strchr(argv[argi], '='); pidfile = strdup(p+1); } else if (argnmatch(argv[argi], "--log=")) { char *p = strchr(argv[argi], '='); logfn = strdup(p+1); } else if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } else if (argnmatch(argv[argi], "--locator=")) { char *p = strchr(argv[argi], '='); locator_init(p+1); locatorbased = 1; } else if (argnmatch(argv[argi], "--service=")) { char *p = strchr(argv[argi], '='); locatorservice = get_servicetype(p+1); } else if (argnmatch(argv[argi], "--filter=")) { char *p = strchr(argv[argi], '='); msgfilter = compileregex(p+1); if (!msgfilter) { errprintf("Invalid filter (bad expression): %s\n", p+1); } else { stdfilter = compileregex("^@@(logrotate|shutdown|drophost|droptest|renamehost|renametest)"); } } else if (argnmatch(argv[argi], "--md5")) { checksumsize = 33; } else if (argnmatch(argv[argi], "--no-md5")) { checksumsize = 0; } else { char *childcmd; char **childargs; int i = 0; childcmd = argv[argi]; childargs = (char **) calloc((1 + argc - argi), sizeof(char *)); while (argi < argc) { childargs[i++] = argv[argi++]; } addlocalpeer(childcmd, childargs); } } /* Sanity checks */ if (cnid == -1) { errprintf("No channel/unknown channel specified\n"); return 1; } if (locatorbased && (locatorservice == ST_MAX)) { errprintf("Must specify --service when using locator\n"); return 1; } if (!locatorbased && (xtreeFirst(peers) == xtreeEnd(peers))) { errprintf("Must specify command for local worker\n"); return 1; } /* Do cache responses to avoid doing too many lookups */ if (locatorbased) locator_prepcache(locatorservice, 0); /* Go daemon */ if (daemonize) { /* Become a daemon */ pid_t daemonpid = fork(); if (daemonpid < 0) { /* Fork failed */ errprintf("Could not fork child\n"); exit(1); } else if (daemonpid > 0) { /* Parent creates PID file and exits */ FILE *fd = NULL; if (pidfile) fd = fopen(pidfile, "w"); if (fd) { fprintf(fd, "%d\n", (int)daemonpid); fclose(fd); } exit(0); } /* Child (daemon) continues here */ setsid(); } /* Catch signals */ setup_signalhandler("xymond_channel"); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGCHLD, &sa, NULL); signal(SIGALRM, SIG_IGN); /* Switch stdout/stderr to the logfile, if one was specified */ freopen("/dev/null", "r", stdin); /* xymond_channel's stdin is not used */ if (logfn) { freopen(logfn, "a", stdout); freopen(logfn, "a", stderr); } /* Attach to the channel */ channel = setup_channel(cnid, CHAN_CLIENT); if (channel == NULL) { errprintf("Channel not available\n"); running = 0; } while (running) { /* * Wait for GOCLIENT to go up. * * Note that we use IPC_NOWAIT if there are messages in the * queue, because then we just want to pick up a message if * there is one, and if not we want to continue pushing the * queued data to the worker. */ struct sembuf s; int n; s.sem_num = GOCLIENT; s.sem_op = -1; s.sem_flg = ((pendingcount > 0) ? IPC_NOWAIT : 0); n = semop(channel->semid, &s, 1); if (n == 0) { /* * GOCLIENT went high, and so we got alerted about a new * message arriving. Copy the message to our own buffer queue. */ char *inbuf = NULL; int msgsz = 0; if (!msgfilter || matchregex(channel->channelbuf, msgfilter) || matchregex(channel->channelbuf, stdfilter)) { msgsz = strlen(channel->channelbuf); inbuf = (char *)malloc(msgsz + checksumsize + 1); memcpy(inbuf+checksumsize, channel->channelbuf, msgsz+1); /* Include \0 */ } /* * Now we have safely stored the new message in our buffer. * Wait until any other clients on the same channel have picked up * this message (GOCLIENT reaches 0). * * We wrap this into an alarm handler, because it can occasionally * fail, causing the whole system to lock up. We dont want that.... * We'll set the alarm to trigger after 1 second. Experience shows * that we'll either succeed in a few milliseconds, or fail completely * and wait the full alarm-timer duration. */ gotalarm = 0; signal(SIGALRM, sig_handler); alarm(2); do { s.sem_num = GOCLIENT; s.sem_op = 0; s.sem_flg = 0; n = semop(channel->semid, &s, 1); } while ((n == -1) && (errno == EAGAIN) && running && (!gotalarm)); signal(SIGALRM, SIG_IGN); if (gotalarm) { errprintf("Gave up waiting for GOCLIENT to go low.\n"); } /* * Let master know we got it by downing BOARDBUSY. * This should not block, since BOARDBUSY is upped * by the master just before he ups GOCLIENT. */ do { s.sem_num = BOARDBUSY; s.sem_op = -1; s.sem_flg = IPC_NOWAIT; n = semop(channel->semid, &s, 1); } while ((n == -1) && (errno == EINTR)); if (n == -1) { errprintf("Tried to down BOARDBUSY: %s\n", strerror(errno)); } if (inbuf) { /* * See if they want us to rotate logs. We pass this on to * the worker module as well, but must handle our own logfile. */ if (strncmp(inbuf+checksumsize, "@@logrotate", 11) == 0) { freopen(logfn, "a", stdout); freopen(logfn, "a", stderr); } if (checksumsize > 0) { char *sep1 = inbuf + checksumsize + strcspn(inbuf+checksumsize, "#|\n"); if (*sep1 == '#') { /* * Add md5 hash of the message. I.e. transform the header line from * "@@%s#%u/%s|%d.%06d| channelmarker, seq, hostname, tstamp.tv_sec, tstamp.tv_usec * to * "@@%s:%s#%u/%s|%d.%06d| channelmarker, hashstr, seq, hostname, tstamp.tv_sec, tstamp.tv_usec */ char *hashstr = md5hash(inbuf+checksumsize); int hlen = sep1 - (inbuf + checksumsize); memmove(inbuf, inbuf+checksumsize, hlen); *(inbuf + hlen) = ':'; memcpy(inbuf+hlen+1, hashstr, strlen(hashstr)); } else { /* No sequence number (control message). Skip checksum for these */ memmove(inbuf, inbuf+checksumsize, msgsz+1); } } /* * Put the new message on our outbound queue. */ if (addmessage(inbuf) != 0) { /* Failed to queue message, free the buffer */ xfree(inbuf); } } } else { if (errno != EAGAIN) { dbgprintf("Semaphore wait aborted: %s\n", strerror(errno)); continue; } } /* * We've picked up messages from the master. Now we * must push them to the worker process. Since there * is no way to hang off both a semaphore and select(), * we'll just push as much data as possible into the * pipe. If we get to a point where we would block, * then wait a teeny bit of time and restart the * whole loop with checking for new messages from the * master etc. * * In theory, this could become an almost busy-wait loop. * In practice, however, the queue will be empty most * of the time because we'll just shove the data to the * worker child. */ for (handle = xtreeFirst(peers); (handle != xtreeEnd(peers)); handle = xtreeNext(peers, handle)) { int canwrite = 1, hasfailed = 0; xymon_peer_t *pwalk; time_t msgtimeout = gettimer() - MSGTIMEOUT; int flushcount = 0; pwalk = (xymon_peer_t *) xtreeData(peers, handle); if (pwalk->msghead == NULL) continue; /* Ignore peers with nothing queued */ switch (pwalk->peerstatus) { case P_UP: canwrite = 1; break; case P_DOWN: openconnection(pwalk); canwrite = (pwalk->peerstatus == P_UP); break; case P_FAILED: canwrite = 0; break; } /* See if we have stale messages queued */ while (pwalk->msghead && (pwalk->msghead->tstamp < msgtimeout)) { flushmessage(pwalk); flushcount++; } if (flushcount) { errprintf("Flushed %d stale messages for %s:%d\n", flushcount, inet_ntoa(pwalk->peeraddr.sin_addr), ntohs(pwalk->peeraddr.sin_port)); } while (pwalk->msghead && canwrite) { fd_set fdwrite; struct timeval tmo; /* Check that this peer is ready for writing. */ FD_ZERO(&fdwrite); FD_SET(pwalk->peersocket, &fdwrite); tmo.tv_sec = 0; tmo.tv_usec = 2000; n = select(pwalk->peersocket+1, NULL, &fdwrite, NULL, &tmo); if (n == -1) { errprintf("select() failed: %s\n", strerror(errno)); canwrite = 0; hasfailed = 1; continue; } else if ((n == 0) || (!FD_ISSET(pwalk->peersocket, &fdwrite))) { canwrite = 0; continue; } n = write(pwalk->peersocket, pwalk->msghead->bufp, pwalk->msghead->buflen); if (n >= 0) { pwalk->msghead->bufp += n; pwalk->msghead->buflen -= n; if (pwalk->msghead->buflen == 0) flushmessage(pwalk); } else if (errno == EAGAIN) { /* * Write would block ... stop for now. */ canwrite = 0; } else { hasfailed = 1; } if (hasfailed) { /* Write failed, or message grew stale */ errprintf("Peer at %s:%d failed: %s\n", inet_ntoa(pwalk->peeraddr.sin_addr), ntohs(pwalk->peeraddr.sin_port), strerror(errno)); canwrite = 0; shutdownconnection(pwalk); if (pwalk->peertype == P_NET) locator_serverdown(pwalk->peername, locatorservice); pwalk->peerstatus = P_FAILED; } } } } /* Detach from channels */ close_channel(channel, CHAN_CLIENT); /* Close peer connections */ for (handle = xtreeFirst(peers); (handle != xtreeEnd(peers)); handle = xtreeNext(peers, handle)) { xymon_peer_t *pwalk = (xymon_peer_t *) xtreeData(peers, handle); shutdownconnection(pwalk); } /* Remove the PID file */ if (pidfile) unlink(pidfile); return 0; } xymon-4.3.7/xymond/xymond.80000664000175000017500000003075311671641417015170 0ustar henrikhenrik.TH XYMOND 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymond \- Master network daemon for a Xymon server .SH SYNOPSIS .B "xymond [options]" .SH DESCRIPTION xymond is the core daemon in the Xymon Monitor. It is designed to handle monitoring of a large number of hosts, with a strong focus on being a high-speed, low-overhead implementation of a Big Brother compatible server. To achieve this, xymond stores all information about the state of the monitored systems in memory, instead of storing it in the host filesystem. A number of plug-ins can be enabled to enhance the basic operation; e.g. a set of plugins are provided to implement persistent storage in a way that is compatible with the Big Brother daemon. However, even with these plugins enabled, xymond still performs much faster than the standard bbd daemon. xymond is normally started and controlled by the .I xymonlaunch(8) tool, and the command used to invoke xymond should therefore be in the tasks.cfg file. .SH OPTIONS .IP "--hosts=FILENAME" Specifies the path to the Xymon hosts.cfg file. This is used to check if incoming status messages refer to known hosts; depending on the "--ghosts" option, messages for unknown hosts may be dropped. If this option is omitted, the default path used is set by the HOSTSCFG environment variable. .IP "--checkpoint-file=FILENAME" With regular intervals, xymond will dump all of its internal state to this check-point file. It is also dumped when xymond terminates, or when it receives a SIGUSR1 signal. .IP "--checkpoint-interval=N" Specifies the interval (in seconds) between dumps to the check-point file. The default is 900 seconds (15 minutes). .IP "--restart=FILENAME" Specifies an existing file containing a previously generated xymond checkpoint. When starting up, xymond will restore its internal state from the information in this file. You can use the same filename for "--checkpoint-file" and "--restart". .IP "--ghosts={allow|drop|log|match}" How to handle status messages from unknown hosts. The "allow" setting accepts all status messages, regardless of whether the host is known in the hosts.cfg file or not. "drop" silently ignores reports from unknown hosts. "log" works like drop, but logs the event in the xymond output file. "match" will try to match the name of the unknown host reporting with the known names by ignoring any domain-names - if a match is found, then a temporary client alias is automatically generated. The default is "log". .IP "--no-purple" Prevent status messages from going purple when they are no longer valid. Unlike the standard bbd daemon, purple-handling is done by xymond. .IP "--listen=IP[:PORT]" Specifies the IP-address and port where xymond will listen for incoming connections. By default, xymond listens on IP 0.0.0.0 (i.e. all IP- adresses available on the host) and port 1984. .IP "--daemon" xymond is normally started by .I xymonlaunch(8) . If you do not want to use xymonlaunch, you can start xymond with this option; it will then detach from the terminal and continue running as a background task. .IP "--timeout=N" Set the timeout used for incoming connections. If a status has not been received more than N seconds after the connection was accepted, then the connection is dropped and any status message is discarded. Default: 10 seconds. .IP "--flap-count=N" Track the N latest status-changes for flap-detection. See the \fB--flap-seconds\fR option also. To disable flap-checks, set N to zero. Default: 5 .IP "--flap-seconds=N" If a status changes more than \fBflap-count\fR times in N seconds or less, then it is considered to be flapping. In that case, the status is locked at the most severe level until the flapping stops. The history information is not updated after the flapping is detected. \fBNOTE:\fR If this is set higher than the default value, you should also use the \fB--flap-count\fR option to ensure that enough status-changes are stored for flap detection to work. The flap-count setting should be at least (N/300)-1, e.g. if you set flap-seconds to 3600 (1 hour), then flap-count should be at least (3600/300)-1, i.e. 11. Default: 1800 seconds (30 minutes). .IP "--delay-red=N" / "--delay-yellow=N" Sets the delay before a red/yellow status causes a change in the web page display. Is usually controlled on a per-host basis via the \fBdelayred\fR and \fBdelayyellow\fR settings in .I hosts.cfg(5) but these options allow you to set a default value for the delays. The value N is in minutes. Default: 0 minutes (no delay). Note: Since most tests only execute once every 5 minutes, it will usually not make sense to set N to anything but a multiple of 5. .IP "--env=FILENAME" Loads the content of FILENAME as environment settings before starting xymond. This is mostly used when running as a stand-alone daemon; if xymond is started by xymonlaunch, the environment settings are controlled by the xymonlaunch tasks.cfg file. .IP "--pidfile=FILENAME" xymond writes the process-ID it is running with to this file. This is for use in automated startup scripts. The default file is $XYMONSERVERLOGS/xymond.pid. .IP "--log=FILENAME" Redirect all output from xymond to FILENAME. .IP "--store-clientlogs[=[!]COLUMN]" Determines which status columns can cause a client message to be broadcast to the CLICHG channel. By default, no client messages are pushed to the CLICHG channel. If this option is specified with no parameter list, all status columns that go into an alert state will trigger the client data to be sent to the CLICHG channel. If a paramater list is added to this option, only those status columns listed in the list will cause the client data to be sent to the CLICHG channel. Several column names can be listed, separated by commas. If all columns are given as "!COLUMNNAME", then all status columns except those listed will cause the client data to be sent. .IP "--status-senders=IP[/MASK][,IP/MASK]" Controls which hosts may send "status", "combo", "config" and "query" commands to xymond. By default, any host can send status-updates. If this option is used, then status-updates are accepted only if they are sent by one of the IP-adresses listed here, or if they are sent from the IP-address of the host that the updates pertains to (this is to allow Xymon clients to send in their own status updates, without having to list all clients here). So typically you will need to list your servers running network tests here. The format of this option is a list of IP-adresses, optionally with a network mask in the form of the number of bits. E.g. if you want to accept status-updates from the host 172.16.10.2, you would use .br --status-senders=172.16.10.2 .br whereas if you want to accept status updates from both 172.16.10.2 and from all of the hosts on the 10.0.2.* network (a 24-bit IP network), you would use .br --status-senders=172.16.10.2,10.0.2.0/24 .IP "--maint-senders=IP[/MASK][,IP/MASK]" Controls which hosts may send maintenance commands to xymond. Maintenance commands are the "enable", "disable", "ack" and "notes" commands. Format of this option is as for the --status-senders option. It is strongly recommended that you use this to restrict access to these commands, so that monitoring of a host cannot be disabled by a rogue user - e.g. to hide a system compromise from the monitoring system. \fBNote:\fR If messages are sent through a proxy, the IP-address restrictions are of little use, since the messages will appear to originate from the proxy server address. It is therefore strongly recommended that you do NOT include the address of a server running xymonproxy in the list of allowed addresses. .IP "--www-senders=IP[/MASK][,IP/MASK]" Controls which hosts may send commands to retrieve the state of xymond. These are the "xymondlog", "xymondboard" and "xymondxboard" commands, which are used by .I xymongen(1) and .I combostatus(1) to retrieve the state of the Xymon system so they can generate the Xymon webpages. \fBNote:\fR If messages are sent through a proxy, the IP-address restrictions are of little use, since the messages will appear to originate from the proxy server address. It is therefore strongly recommended that you do NOT include the address of a server running xymonproxy in the list of allowed addresses. .IP "--admin-senders=IP[/MASK][,IP/MASK]" Controls which hosts may send administrative commands to xymond. These commands are the "drop" and "rename" commands. Access to these should be restricted, since they provide an un-authenticated means of completely disabling monitoring of a host, and can be used to remove all traces of e.g. a system compromise from the Xymon monitor. \fBNote:\fR If messages are sent through a proxy, the IP-address restrictions are of little use, since the messages will appear to originate from the proxy server address. It is therefore strongly recommended that you do NOT include the address of a server running xymonproxy in the list of allowed addresses. .IP "--no-download" Disable the "download" and "config" commands which can be used by clients to pull files from the Xymon server. The use of these may be seen as a security risk since they allow file downloads. .IP "--debug" Enable debugging output. .IP "--dbghost=HOSTNAME" For troubleshooting problems with a specific host, it may be useful to track the exact communications from a single host. This option causes xymond to dump all traffic from a single host to the file "/tmp/xymond.dbg". .SH HOW ALERTS TRIGGER When a status arrives, xymond matches the old and new color against the "alert" colors (from the "ALERTCOLORS" setting) and the "OK" colors (from the "OKCOLORS" setting). The old and new color falls into one of three categories: .sp .BR OK: The color is one of the "OK" colors (e.g. "green"). .sp .BR ALERT: The color is one of the "alert" colors (e.g. "red"). .sp .BR UNDECIDED: The color is neither an "alert" color nor an "OK" color (e.g. "yellow"). If the new status shows an ALERT state, then a message to the .I xymond_alert(8) module is triggered. This may be a repeat of a previous alert, but .I xymond_alert(8) will handle that internally, and only send alert messages with the interval configured in .I alerts.cfg(5). If the status goes from a not-OK state (ALERT or UNDECIDED) to OK, and there is a record of having been in a ALERT state previously, then a recovery message is triggered. The use of the OK, ALERT and UNDECIDED states make it possible to avoid being flooded with alerts when a status flip-flops between e.g yellow and red, or green and yellow. .SH CHANNELS A lot of functionality in the Xymon server is delegated to "worker modules" that are fed various events from xymond via a "channel". Programs access a channel using IPC mechanisms - specifically, shared memory and semaphores - or by using an instance of the .I xymond_channel(8) intermediate program. xymond_channel enables access to a channel via a simple file I/O interface. A skeleton program for hooking into a xymond channel is provided as part of Xymon in the .I xymond_sample(8) program. The following channels are provided by xymond: .sp .BR status This channel is fed the contents of all incoming "status" and "summary" messages. .sp .BR stachg This channel is fed information about tests that change status, i.e. the color of the status-log changes. .sp .BR page This channel is fed information about tests where the color changes between an alert color and a non-alert color. It also receives information about "ack" messages. .sp .BR data This channel is fed information about all "data" messages. .sp .BR notes This channel is fed information about all "notes" messages. .sp .BR enadis This channel is fed information about hosts or tests that are being disabled or enabled. .sp .BR client This channel is fed the contents of the client messages sent by Xymon clients installed on the monitored servers. .sp .BR clichg This channel is fed the contents of a host client messages, whenever a status for that host goes red, yellow or purple. Information about the data stream passed on these channels is in the Xymon source-tree, see the "xymond/new-daemon.txt" file. .SH SIGNALS .IP SIGHUP Re-read the hosts.cfg configuration file. .IP SIGUSR1 Force an immediate dump of the checkpoint file. .SH BUGS Timeout of incoming connections are not strictly enforced. The check for a timeout only triggers during the normal network handling loop, so a connection that should timeout after N seconds may persist until some activity happens on another (unrelated) connection. .SH FILES If ghost-handling is enabled via the "--ghosts" option, the hosts.cfg file is read to determine the names of all known hosts. .SH "SEE ALSO" xymon(7), xymonserver.cfg(5). xymon-4.3.7/xymond/Makefile0000664000175000017500000002466111671476651015230 0ustar henrikhenrikPROGRAMS = xymon.sh xymond xymond_channel xymond_locator xymond_filestore xymond_history xymond_alert xymond_rrd xymond_sample xymond_client xymond_hostdata xymond_capture xymond_distribute xymonfetch xymon-mailack trimhistory combostatus xymonreports.sh moverrd.sh convertnk rrdcachectl CLIENTPROGRAMS = ../client/xymond_client LIBOBJS = ../lib/libxymon.a XYMONDOBJS = xymond.o xymond_buffer.o xymond_ipc.o CHANNELOBJS = xymond_channel.o xymond_buffer.o xymond_ipc.o LOCATOROBJS = xymond_locator.o SAMPLEOBJS = xymond_sample.o xymond_worker.o xymond_buffer.o FILESTOREOBJS = xymond_filestore.o xymond_worker.o xymond_buffer.o HISTORYOBJS = xymond_history.o xymond_worker.o xymond_buffer.o ALERTOBJS = xymond_alert.o xymond_worker.o xymond_buffer.o do_alert.o RRDOBJS = xymond_rrd.o xymond_worker.o xymond_buffer.o do_rrd.o client_config.o HOSTDATAOBJS = xymond_hostdata.o xymond_worker.o xymond_buffer.o CAPTUREOBJS = xymond_capture.o xymond_worker.o xymond_buffer.o CLIENTOBJS = xymond_client.o xymond_worker.o xymond_buffer.o client_config.o DISTRIBUTEOBJS= xymond_distribute.o xymond_worker.o xymond_buffer.o COMBOTESTOBJS = combostatus.o MAILACKOBJS = xymon-mailack.o TRIMHISTOBJS = trimhistory.o FETCHOBJS = xymonfetch.o CONVERTNKOBJS = convertnk.o RRDCACHECTLOBJS = rrdcachectl.o IDTOOL := $(shell if test `uname -s` = "SunOS"; then echo /usr/xpg4/bin/id; else echo id; fi) all: $(PROGRAMS) cfgfiles # Need NETLIBS on Solaris for getservbyname(), called by parse_url() client: $(CLIENTPROGRAMS) xymond: $(XYMONDOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(XYMONDOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) xymond_channel: $(CHANNELOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(CHANNELOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) xymond_locator: $(LOCATOROBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(LOCATOROBJS) $(LIBOBJS) $(NETLIBS) $(LIBRTDEF) xymond_filestore: $(FILESTOREOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(FILESTOREOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) xymond_history: $(HISTORYOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(HISTORYOBJS) $(LIBOBJS) $(NETLIBS) $(LIBRTDEF) xymond_alert: $(ALERTOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(ALERTOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) xymond_rrd: $(RRDOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(RRDOBJS) $(LIBOBJS) $(RRDLIBS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) do_alert.o: do_alert.c $(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ do_alert.c do_rrd.o: do_rrd.c do_rrd.h rrd/*.c $(CC) $(CFLAGS) $(RRDINCDIR) $(PCREINCDIR) $(RRDDEF) -c -o $@ do_rrd.c xymond_capture.o: xymond_capture.c $(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ xymond_capture.c xymond_sample: $(SAMPLEOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(SAMPLEOBJS) $(LIBOBJS) $(NETLIBS) $(LIBRTDEF) xymond_capture: $(CAPTUREOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(CAPTUREOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) xymond_distribute: $(DISTRIBUTEOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(DISTRIBUTEOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) xymond_hostdata: $(HOSTDATAOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(HOSTDATAOBJS) $(LIBOBJS) $(NETLIBS) $(LIBRTDEF) xymond_client: $(CLIENTOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(CLIENTOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) ../client/xymond_client: $(CLIENTOBJS) ../lib/xymonclient.a $(CC) -o $@ $(RPATHOPT) $(CLIENTOBJS) ../lib/xymonclient.a $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) xymond_client.o: xymond_client.c client/*.c $(CC) $(CFLAGS) -c -o $@ xymond_client.c combostatus.o: combostatus.c $(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ $< combostatus: $(COMBOTESTOBJS) $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $(RPATHOPT) $(COMBOTESTOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) xymon-mailack.o: xymon-mailack.c $(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ xymon-mailack.c xymon-mailack: $(MAILACKOBJS) $(LIBOBJS) $(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(MAILACKOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) trimhistory: $(TRIMHISTOBJS) $(LIBOBJS) $(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(TRIMHISTOBJS) $(LIBOBJS) $(NETLIBS) $(LIBRTDEF) xymonfetch: $(FETCHOBJS) $(LIBOBJS) $(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(FETCHOBJS) $(LIBOBJS) $(NETLIBS) $(LIBRTDEF) convertnk: $(CONVERTNKOBJS) $(LIBOBJS) $(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(CONVERTNKOBJS) $(LIBOBJS) $(NETLIBS) $(LIBRTDEF) rrdcachectl: $(RRDCACHECTLOBJS) $(LIBOBJS) $(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(RRDCACHECTLOBJS) $(LIBOBJS) $(NETLIBS) $(LIBRTDEF) xymon.sh: xymon.sh.DIST cat $< | sed -e 's!@XYMONHOME@!$(XYMONHOME)!g' | sed -e 's!@XYMONLOGDIR@!$(XYMONLOGDIR)!g' | sed -e 's!@XYMONUSER@!$(XYMONUSER)!g' | sed -e 's!@RUNTIMEDEFS@!$(RUNTIMEDEFS)!g' >$@ chmod 755 $@ xymonreports.sh: xymonreports.sh.DIST cat $< | sed -e 's!@XYMONHOME@!$(XYMONHOME)!g' >$@ chmod 755 $@ moverrd.sh: moverrd.sh.DIST cat $< | sed -e 's!@XYMONHOME@!$(XYMONHOME)!g' | sed -e 's!@XYMONVAR@!$(XYMONVAR)!g' >$@ chmod 755 $@ ifeq ($(XYMONCGIURL),$(SECUREXYMONCGIURL)) APACHECONF = etcfiles/xymon-apache-open.DIST else APACHECONF = etcfiles/xymon-apache-secure.DIST endif cfgfiles: cat $(APACHECONF) | sed -e 's!@XYMONHOME@!$(XYMONHOME)!g' | sed -e 's!@INSTALLETCDIR@!$(INSTALLETCDIR)!g' | sed -e 's!@INSTALLWWWDIR@!$(INSTALLWWWDIR)!g' | sed -e 's!@CGIDIR@!$(CGIDIR)!g' | sed -e 's!@SECURECGIDIR@!$(SECURECGIDIR)!g' | sed -e 's!@XYMONHOSTURL@!$(XYMONHOSTURL)!g' | sed -e 's!@XYMONCGIURL@!$(XYMONCGIURL)!g' | sed -e 's!@SECUREXYMONCGIURL@!$(SECUREXYMONCGIURL)!g' >etcfiles/xymon-apache.conf cat etcfiles/xymonserver.cfg.DIST | sed -e 's!@XYMONTOPDIR@!$(XYMONTOPDIR)!g'| sed -e 's!@XYMONLOGDIR@!$(XYMONLOGDIR)!g'| sed -e 's!@XYMONHOSTNAME@!$(XYMONHOSTNAME)!g'| sed -e 's!@XYMONHOSTIP@!$(XYMONHOSTIP)!g'| sed -e 's!@XYMONHOSTOS@!$(XYMONHOSTOS)!g' | sed -e 's!@XYMONHOSTURL@!$(XYMONHOSTURL)!g' | sed -e 's!@XYMONCGIURL@!$(XYMONCGIURL)!g' | sed -e 's!@SECUREXYMONCGIURL@!$(SECUREXYMONCGIURL)!g' | sed -e 's!@XYMONHOME@!$(XYMONHOME)!g' | sed -e 's!@XYMONVAR@!$(XYMONVAR)!g' | sed -e 's!@FPING@!$(FPING)!g' | sed -e 's!@MAILPROGRAM@!$(MAILPROGRAM)!g' | sed -e 's!@RUNTIMEDEFS@!$(RUNTIMEDEFS)!g' >etcfiles/xymonserver.cfg ../build/bb-commands.sh >>etcfiles/xymonserver.cfg cat etcfiles/hosts.cfg.DIST | sed -e 's!@XYMONHOSTNAME@!$(XYMONHOSTNAME)!g' | sed -e 's!@XYMONHOSTIP@!$(XYMONHOSTIP)!g' >etcfiles/hosts.cfg cat etcfiles/alerts.cfg.DIST | sed -e 's!@XYMONHOSTNAME@!$(XYMONHOSTNAME)!g' | sed -e 's!@XYMONHOSTIP@!$(XYMONHOSTIP)!g' >etcfiles/alerts.cfg cat etcfiles/tasks.cfg.DIST | sed -e 's!@XYMONHOME@!$(XYMONHOME)!g' | sed -e 's!@XYMONTOPDIR@!$(XYMONTOPDIR)!g' >etcfiles/tasks.cfg cat etcfiles/cgioptions.cfg.DIST | sed -e 's!@XYMONHOME@!$(XYMONHOME)!g' >etcfiles/cgioptions.cfg %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< clean: rm -f $(PROGRAMS) *.o *~ client/*~ rrd/*~ rm -f etcfiles/xymon-apache.conf etcfiles/xymonserver.cfg etcfiles/hosts.cfg etcfiles/alerts.cfg etcfiles/tasks.cfg etcfiles/cgioptions.cfg (find etcfiles/ -name "*~"; find wwwfiles/ -name "*~"; find webfiles/ -name "*~") | xargs rm -f install: install-bin install-man install-cfg install-bin: cp -fp $(PROGRAMS) $(INSTALLROOT)$(INSTALLBINDIR)/ install-man: mkdir -p $(INSTALLROOT)$(MANROOT)/man1 $(INSTALLROOT)$(MANROOT)/man5 $(INSTALLROOT)$(MANROOT)/man8 cp -fp *.1 $(INSTALLROOT)$(MANROOT)/man1/ cp -fp *.5 $(INSTALLROOT)$(MANROOT)/man5/ cp -fp *.8 $(INSTALLROOT)$(MANROOT)/man8/ install-cfg: cd etcfiles; ../../build/merge-lines xymonserver.cfg $(INSTALLROOT)$(INSTALLETCDIR)/xymonserver.cfg LARRDCOLUMN=TRENDSCOLUMN LARRDS=TEST2RRD cd etcfiles; ../../build/merge-lines cgioptions.cfg $(INSTALLROOT)$(INSTALLETCDIR)/cgioptions.cfg cd etcfiles; ../../build/merge-sects tasks.cfg $(INSTALLROOT)$(INSTALLETCDIR)/tasks.cfg larrdstatus=rrdstatus larrddata=rrddata cd etcfiles; ../../build/merge-sects client-local.cfg $(INSTALLROOT)$(INSTALLETCDIR)/client-local.cfg cd etcfiles; ../../build/merge-sects graphs.cfg $(INSTALLROOT)$(INSTALLETCDIR)/graphs.cfg cd etcfiles; ../../build/merge-lines columndoc.csv $(INSTALLROOT)$(INSTALLETCDIR)/columndoc.csv cd etcfiles; (echo "hosts.cfg"; echo "alerts.cfg"; echo "analysis.cfg"; echo "combo.cfg"; echo "client-local.cfg"; echo "holidays.cfg"; echo "rrddefinitions.cfg"; echo snmpmibs.cfg; echo xymonmenu.cfg; echo xymon-apache.conf) | ../../build/setup-newfiles $(INSTALLROOT)$(INSTALLETCDIR)/ cd $(INSTALLROOT)$(XYMONHOME); rm -f xymon.sh; ln -sf bin/xymon.sh . cd wwwfiles; find . | grep -v RCS | grep -v ".svn" | grep -v DIST | ../../build/setup-newfiles $(INSTALLROOT)$(INSTALLWWWDIR)/ ../../build/md5.dat cd webfiles; find . | grep -v RCS | grep -v ".svn" | grep -v DIST | ../../build/setup-newfiles $(INSTALLROOT)$(INSTALLWEBDIR)/ ../../build/md5.dat touch $(INSTALLROOT)$(INSTALLETCDIR)/critical.cfg $(INSTALLROOT)$(INSTALLETCDIR)/critical.cfg.bak chmod g+w $(INSTALLROOT)$(INSTALLETCDIR)/critical.cfg $(INSTALLROOT)$(INSTALLETCDIR)/critical.cfg.bak mkdir -p $(INSTALLROOT)$(XYMONLOGDIR) ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(XYMONLOGDIR) $(INSTALLROOT)$(XYMONHOME) $(INSTALLROOT)$(XYMONHOME)/* $(INSTALLROOT)$(INSTALLBINDIR)/* $(INSTALLROOT)$(INSTALLETCDIR)/* $(INSTALLROOT)$(INSTALLEXTDIR)/* $(INSTALLROOT)$(INSTALLWEBDIR)/* $(INSTALLROOT)$(INSTALLWWWDIR)/gifs $(INSTALLROOT)$(INSTALLWWWDIR)/gifs/* $(INSTALLROOT)$(INSTALLWWWDIR)/menu $(INSTALLROOT)$(INSTALLWWWDIR)/menu/* $(INSTALLROOT)$(INSTALLWWWDIR)/help $(INSTALLROOT)$(INSTALLWWWDIR)/notes $(INSTALLROOT)$(INSTALLWWWDIR)/html $(INSTALLROOT)$(INSTALLWWWDIR)/wml $(INSTALLROOT)$(XYMONVAR) $(INSTALLROOT)$(XYMONVAR)/* chgrp `$(IDTOOL) -g $(XYMONUSER)` $(INSTALLROOT)$(XYMONLOGDIR) $(INSTALLROOT)$(XYMONHOME) $(INSTALLROOT)$(XYMONHOME)/* $(INSTALLROOT)$(INSTALLBINDIR)/* $(INSTALLROOT)$(INSTALLETCDIR)/* $(INSTALLROOT)$(INSTALLEXTDIR)/* $(INSTALLROOT)$(INSTALLWEBDIR)/* $(INSTALLROOT)$(INSTALLWWWDIR)/gifs $(INSTALLROOT)$(INSTALLWWWDIR)/gifs/* $(INSTALLROOT)$(INSTALLWWWDIR)/menu $(INSTALLROOT)$(INSTALLWWWDIR)/menu/* $(INSTALLROOT)$(INSTALLWWWDIR)/help $(INSTALLROOT)$(INSTALLWWWDIR)/notes $(INSTALLROOT)$(INSTALLWWWDIR)/html $(INSTALLROOT)$(INSTALLWWWDIR)/wml $(INSTALLROOT)$(XYMONVAR) $(INSTALLROOT)$(XYMONVAR)/* chgrp $(HTTPDGID) $(INSTALLROOT)$(INSTALLETCDIR)/critical.cfg $(INSTALLROOT)$(INSTALLETCDIR)/critical.cfg.bak chown root $(INSTALLROOT)$(INSTALLBINDIR)/xymonping || echo "Could not make xymonping owned by root, continuing" chmod 4755 $(INSTALLROOT)$(INSTALLBINDIR)/xymonping || echo "Could not make xymonping suid-root, continuing" endif xymon-4.3.7/xymond/xymond_capture.80000664000175000017500000000364011671641417016706 0ustar henrikhenrik.TH XYMOND_CAPTURE 8 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymond_capture \- catch selected messages from a xymond channel .SH SYNOPSIS .B "xymond_channel --channel=status xymond_capture [options]" .SH DESCRIPTION xymond_capture is a worker module for xymond, and as such it is normally run via the .I xymond_channel(8) program. It receives messages from xymond via stdin and filters them to select messages based on the hostname, testname or color of the status. By default the resulting messages are printed on stdout, but they can also be fed into a command for further processing. xymond_capture supports the \fBstatus\fR, \fBdata\fR, \fBclient\fR and \fBhostdata\fR channels. .SH OPTIONS .IP "--hosts=PATTERN" Select messages only from hosts matching PATTERN (regular expression). .IP "--exhosts=PATTERN" Exclude messages from hosts matching PATTERN. If used with the --hosts option, then the hostname must match the --hosts pattern, but NOT the --exhosts pattern. .IP "--tests=PATTERN" Select messages only from tests matching PATTERN (regular expression). .IP "--extests=PATTERN" Exclude messages from tests matching PATTERN. If used with the --tests option, then the testname must match the --tests pattern, but NOT the --extests pattern. .IP "--colors=COLOR[,color]" Select messages based on the color of the status message. Multiple colors can be listed, separated by comma. Default: Accept all colors. .IP "--batch-command=COMMAND" Instead of printing the messages to stdout, feed them to COMMAND on stdin. COMMAND can be any command which accepts the mssage on standard input. .IP "--batch-timeout=SECONDS" Collect messages until no messages have arrived in SECONDS seconds, before sending them to the --batch-command COMMAND. .IP "--client" Capture data from the "client" channel instead of the default "status" channel. .IP "--debug" Enable debugging output. .SH "SEE ALSO" xymond_channel(8), xymond(8), xymon(7) xymon-4.3.7/xymond/do_alert.h0000664000175000017500000000234111615341300015475 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon message daemon. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __DO_ALERT_H__ #define __DO_ALERT_H__ #include #include extern int include_configid; extern int testonly; extern time_t next_alert(activealerts_t *alert); extern void cleanup_alert(activealerts_t *alert); extern void clear_interval(activealerts_t *alert); extern void start_alerts(void); extern void send_alert(activealerts_t *alert, FILE *logfd); extern void finish_alerts(void); extern void load_state(char *filename, char *statusbuf); extern void save_state(char *filename); #endif xymon-4.3.7/xymond/xymon-mailack.c0000664000175000017500000001376311615341300016462 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon mail-acknowledgment filter. */ /* */ /* This program runs from the Xymon users' .procmailrc file, and processes */ /* incoming e-mails that are responses to alert mails that Xymon has sent */ /* out. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymon-mailack.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include "libxymon.h" int main(int argc, char *argv[]) { strbuffer_t *inbuf; char *ackbuf; char *subjectline = NULL; char *returnpathline = NULL; char *fromline = NULL; char *firsttxtline = NULL; int inheaders = 1; char *p; pcre *subjexp; const char *errmsg; int errofs, result; int ovector[30]; char cookie[10]; int duration = 0; int argi; char *envarea = NULL; for (argi=1; (argi < argc); argi++) { if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } } initfgets(stdin); inbuf = newstrbuffer(0); while (unlimfgets(inbuf, stdin)) { sanitize_input(inbuf, 0, 0); if (!inheaders) { /* We're in the message body. Look for a "delay=N" line here. */ if ((strncasecmp(STRBUF(inbuf), "delay=", 6) == 0) || (strncasecmp(STRBUF(inbuf), "delay ", 6) == 0)) { duration = durationvalue(STRBUF(inbuf)+6); continue; } else if ((strncasecmp(STRBUF(inbuf), "ack=", 4) == 0) || (strncasecmp(STRBUF(inbuf), "ack ", 4) == 0)) { /* Some systems cannot generate a subject. Allow them to ack * via text in the message body. */ subjectline = (char *)malloc(STRBUFLEN(inbuf) + 1024); sprintf(subjectline, "Subject: Xymon [%s]", STRBUF(inbuf)+4); } else if (*STRBUF(inbuf) && !firsttxtline) { /* Save the first line of the message body, but ignore blank lines */ firsttxtline = strdup(STRBUF(inbuf)); } continue; /* We dont care about the rest of the message body */ } /* See if we're at the end of the mail headers */ if (inheaders && (STRBUFLEN(inbuf) == 0)) { inheaders = 0; continue; } /* Is it one of those we want to keep ? */ if (strncasecmp(STRBUF(inbuf), "return-path:", 12) == 0) returnpathline = strdup(skipwhitespace(STRBUF(inbuf)+12)); else if (strncasecmp(STRBUF(inbuf), "from:", 5) == 0) fromline = strdup(skipwhitespace(STRBUF(inbuf)+5)); else if (strncasecmp(STRBUF(inbuf), "subject:", 8) == 0) subjectline = strdup(skipwhitespace(STRBUF(inbuf)+8)); } freestrbuffer(inbuf); /* No subject ? No deal */ if (subjectline == NULL) { dbgprintf("Subject-line not found\n"); return 1; } /* Get the alert cookie */ subjexp = pcre_compile(".*(Xymon|Hobbit|BB)[ -]* \\[*(-*[0-9]+)[\\]!]*", PCRE_CASELESS, &errmsg, &errofs, NULL); if (subjexp == NULL) { dbgprintf("pcre compile failed - 1\n"); return 2; } result = pcre_exec(subjexp, NULL, subjectline, strlen(subjectline), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (result < 0) { dbgprintf("Subject line did not match pattern\n"); return 3; /* Subject did not match what we expected */ } if (pcre_copy_substring(subjectline, ovector, result, 2, cookie, sizeof(cookie)) <= 0) { dbgprintf("Could not find cookie value\n"); return 4; /* No cookie */ } pcre_free(subjexp); /* See if there's a "DELAY=" delay-value also */ subjexp = pcre_compile(".*DELAY[ =]+([0-9]+[mhdw]*)", PCRE_CASELESS, &errmsg, &errofs, NULL); if (subjexp == NULL) { dbgprintf("pcre compile failed - 2\n"); return 2; } result = pcre_exec(subjexp, NULL, subjectline, strlen(subjectline), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (result >= 0) { char delaytxt[4096]; if (pcre_copy_substring(subjectline, ovector, result, 1, delaytxt, sizeof(delaytxt)) > 0) { duration = durationvalue(delaytxt); } } pcre_free(subjexp); /* See if there's a "msg" text also */ subjexp = pcre_compile(".*MSG[ =]+(.*)", PCRE_CASELESS, &errmsg, &errofs, NULL); if (subjexp == NULL) { dbgprintf("pcre compile failed - 3\n"); return 2; } result = pcre_exec(subjexp, NULL, subjectline, strlen(subjectline), 0, 0, ovector, (sizeof(ovector)/sizeof(int))); if (result >= 0) { char msgtxt[4096]; if (pcre_copy_substring(subjectline, ovector, result, 1, msgtxt, sizeof(msgtxt)) > 0) { firsttxtline = strdup(msgtxt); } } pcre_free(subjexp); /* Use the "return-path:" header if we didn't see a From: line */ if ((fromline == NULL) && returnpathline) fromline = returnpathline; if (fromline) { /* Remove '<' and '>' from the fromline - they mess up HTML */ while ((p = strchr(fromline, '<')) != NULL) *p = ' '; while ((p = strchr(fromline, '>')) != NULL) *p = ' '; } /* Setup the acknowledge message */ if (duration == 0) duration = 60; /* Default: Ack for 60 minutes */ if (firsttxtline == NULL) firsttxtline = ""; ackbuf = (char *)malloc(4096 + strlen(firsttxtline) + (fromline ? strlen(fromline) : 0)); p = ackbuf; p += sprintf(p, "xymondack %s %d %s", cookie, duration, firsttxtline); if (fromline) { p += sprintf(p, "\nAcked by: %s", fromline); } if (debug) { printf("%s\n", ackbuf); return 0; } sendmessage(ackbuf, NULL, XYMON_TIMEOUT, NULL); return 0; } xymon-4.3.7/xymond/xymond_rootlogin.pl0000775000175000017500000000744511535462534017535 0ustar henrikhenrik#!/usr/bin/perl -w #*----------------------------------------------------------------------------*/ #* Xymon client message processor. */ #* */ #* This perl program shows how to create a server-side module using the */ #* data sent by the Xymon clients. This program is fed data from the */ #* Xymon "client" channel via the xymond_channel program; each client */ #* message is processed by looking at the [who] section and generates */ #* a "login" status that goes red when an active "root" login is found. */ #* */ #* Written 2007-Jan-28 by Henrik Storner */ #* */ #* This program is in the public domain, and may be used freely for */ #* creating your own Xymon server-side modules. */ #* */ #*----------------------------------------------------------------------------*/ # $Id: xymond_rootlogin.pl 6650 2011-03-08 17:20:28Z storner $ my $xymon; my $xymsrv; my $statuscolumn = "login"; my $hostname = ""; my $msgtxt = ""; my %sections = (); my $cursection = ""; sub processmessage; # Get the XYMON and XYMSRV environment settings. $xymon = $ENV{"XYMON"} || die "XYMON not defined"; $xymsrv = $ENV{"XYMSRV"} || die "XYMSRV not defined"; # Main routine. # # This reads client messages from , looking for the # delimiters that separate each message, and also looking for the # section markers that delimit each part of the client message. # When a message is complete, the processmessage() subroutine # is invoked. $msgtxt contains the complete message, and the # %sections hash contains the individual sections of the client # message. while ($line = ) { if ($line =~ /^\@\@client\#/) { # It's the start of a new client message - the header looks like this: # @@client#830759/HOSTNAME|1169985951.340108|10.60.65.152|HOSTNAME|sunos|sunos # Grab the hostname field from the header @hdrfields = split(/\|/, $line); $hostname = $hdrfields[3]; # Clear the variables we use to store the message in $msgtxt = ""; %sections = (); } elsif ($line =~ /^\@\@/) { # End of a message. Do something with it. processmessage(); } elsif ($line =~ /^\[(.+)\]/) { # Start of new message section. $cursection = $1; $sections{ $cursection } = "\n"; } else { # Add another line to the entire message text variable, # and the the current section. $msgtxt = $msgtxt . $line; if ($cursection) { $sections{ $cursection } = $sections{ $cursection } . $line; } } } # This subroutine processes the client message. In this case, # we watch the [who] section of the client message and alert # if there is a root login active. sub processmessage { my $color; my $summary; my $statusmsg; my $cmd; # Dont do anything unless we have the "who" section return unless ( $sections{"who"} ); # Is there a "root" login somewhere in the "who" section? # Note that we must match with /m because there are multiple # lines in the [who] section. if ( $sections{"who"} =~ /^root /m ) { $color = "red"; $summary = "ROOT login active"; $statusmsg = "&red ROOT login detected!\n\n" . $sections{"who"}; } else { $color = "green"; $summary = "OK"; $statusmsg = "&green No root login active\n\n" . $sections{"who"}; } # Build the command we use to send a status to the Xymon daemon $cmd = $xymon . " " . $xymsrv . " \"status " . $hostname . "." . $statuscolumn . " " . $color . " " . $summary . "\n\n" . $statusmsg . "\""; # And send the message system $cmd; } xymon-4.3.7/xymond/trimhistory.c0000664000175000017500000003414011615341300016276 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon history log trimming tool. */ /* */ /* This tool trims the history-logs of old entries. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: trimhistory.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" enum ftype_t { F_HOSTHISTORY, F_SERVICEHISTORY, F_ALLEVENTS, F_DROPIT, F_PURGELOGS }; typedef struct filelist_t { char *fname; enum ftype_t ftype; struct filelist_t *next; } filelist_t; filelist_t *flhead = NULL; char *outdir = NULL; int progressinfo = 0; int totalitems = 0; void showprogress(int itemno) { errprintf("Processing item %d/%d ... \n", itemno, totalitems); } int validstatus(char *hname, char *tname) { /* Check if a status-file is for a known host+service combination */ static char *board = NULL; char buf[1024]; char *p; int result = 0; if (!board) { sendreturn_t *sres; sres = newsendreturnbuf(1, NULL); if (sendmessage("xymondboard fields=hostname,testname", NULL, XYMON_TIMEOUT, sres) != XYMONSEND_OK) { errprintf("Cannot get list of host/test combinations\n"); exit(1); } board = getsendreturnstr(sres, 1); freesendreturnbuf(sres); if (debug) { char fname[PATH_MAX]; FILE *fd; sprintf(fname, "%s/board.dbg", xgetenv("XYMONTMP")); fd = fopen(fname, "w"); if (fd) { fwrite(board, strlen(board), 1, fd); fclose(fd); } else { errprintf("Cannot open debug file %s: %s\n", fname, strerror(errno)); } } } sprintf(buf, "%s|%s\n", hname, tname); p = strstr(board, buf); if (p) result = ( (p == board) || (*(p-1) == '\n')); return result; } void add_to_filelist(char *fn, enum ftype_t ftype) { /* This keeps track of what files we must process - and how */ filelist_t *newitem; newitem = (filelist_t *)malloc(sizeof(filelist_t)); newitem->fname = strdup(fn); newitem->ftype = ftype; newitem->next = flhead; flhead = newitem; totalitems++; } void trim_history(FILE *infd, FILE *outfd, enum ftype_t ftype, time_t cutoff) { /* Does the grunt work of going through a file and copying the wanted records */ char l[4096], prevl[4096], l2[4096]; char *cols[10]; int i; int copying = 0; *prevl = '\0'; while (fgets(l, sizeof(l), infd)) { if (copying) { fprintf(outfd, "%s", l); } else { /* Split up the input line into columns, and find the timestamp depending on the file type */ memset(cols, 0, sizeof(cols)); strcpy(l2, l); i = 0; cols[i++] = strtok(l2, " "); while ((i < 10) && ((cols[i++] = strtok(NULL, " ")) != NULL)) ; switch (ftype) { case F_HOSTHISTORY: copying = (!cols[1] || (atoi(cols[1]) >= cutoff)); break; case F_SERVICEHISTORY: copying = (!cols[6] || (atoi(cols[6]) >= cutoff)); break; case F_ALLEVENTS: copying = (!cols[3] || (atoi(cols[3]) >= cutoff)); break; case F_DROPIT: case F_PURGELOGS: /* Cannot happen */ errprintf("Impossible - F_DROPIT/F_PURGELOGS in trim_history\n"); return; } /* If we switched to copy-mode, start by outputting the previous and the current lines */ if (copying) { if (*prevl) fprintf(outfd, "%s", prevl); fprintf(outfd, "%s", l); } else { strcpy(prevl, l); } } } if (!copying) { /* No entries after the cutoff time - keep the last line */ if (*prevl) fprintf(outfd, "%s", prevl); } } void trim_files(time_t cutoff) { filelist_t *fwalk; FILE *infd, *outfd; char outfn[PATH_MAX]; struct stat st; struct utimbuf tstamp; int itemno = 0; /* We have a list of files to trim, so process them */ for (fwalk = flhead; (fwalk); fwalk = fwalk->next) { dbgprintf("Processing %s\n", fwalk->fname); itemno++; if (progressinfo && ((itemno % progressinfo) == 0)) showprogress(itemno); if (fwalk->ftype == F_DROPIT) { /* It's an orphan, and we want to delete it */ unlink(fwalk->fname); continue; } if (stat(fwalk->fname, &st) == -1) { errprintf("Cannot stat input file %s: %s\n", fwalk->fname, strerror(errno)); continue; } tstamp.actime = time(NULL); tstamp.modtime = st.st_mtime; infd = fopen(fwalk->fname, "r"); if (infd == NULL) { errprintf("Cannot open input file %s: %s\n", fwalk->fname, strerror(errno)); continue; } if (outdir) { sprintf(outfn, "%s/%s", outdir, fwalk->fname); } else { sprintf(outfn, "%s.tmp", fwalk->fname); } outfd = fopen(outfn, "w"); if (outfd == NULL) { errprintf("Cannot create output file %s: %s\n", outfn, strerror(errno)); fclose(infd); continue; } trim_history(infd, outfd, fwalk->ftype, cutoff); if (fwalk->ftype == F_ALLEVENTS) { char pidfn[PATH_MAX]; FILE *fd; long pid = -1; sprintf(pidfn, "%s/xymond_history.pid", xgetenv("XYMONSERVERLOGS")); fd = fopen(pidfn, "r"); if (fd) { char l[100]; fgets(l, sizeof(l), fd); fclose(fd); pid = atol(l); } if (pid > 0) kill(pid, SIGHUP); } fclose(infd); fclose(outfd); utime(outfn, &tstamp); /* So the access time is consistent with the last update */ /* Final check to make sure the file didn't change while we were processing it */ if ((stat(fwalk->fname, &st) == 0) && (st.st_mtime == tstamp.modtime)) { if (!outdir) rename(outfn, fwalk->fname); } else { errprintf("File %s changed while processing it - not trimmed\n", fwalk->fname); unlink(outfn); } } } time_t logtime(char *fn) { static char *mnames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; char tstamp[25]; struct tm tmstamp; time_t result; int flen; flen = strlen(fn); strcpy(tstamp, fn); memset(&tmstamp, 0, sizeof(tmstamp)); tmstamp.tm_isdst = -1; if (flen == 24) { /* fn is of the form: WWW_MMM_DD_hh:mm:ss_YYYY */ if (*(tstamp+3) == '_') *(tstamp+3) = ' '; else return -1; if (*(tstamp+7) == '_') *(tstamp+7) = ' '; else return -1; if (*(tstamp+10) == '_') *(tstamp+10) = ' '; else return -1; if (*(tstamp+13) == ':') *(tstamp+13) = ' '; else return -1; if (*(tstamp+16) == ':') *(tstamp+16) = ' '; else return -1; if (*(tstamp+19) == '_') *(tstamp+19) = ' '; else return -1; while (mnames[tmstamp.tm_mon] && strncmp(tstamp+4, mnames[tmstamp.tm_mon], 3)) tmstamp.tm_mon++; tmstamp.tm_mday = atoi(tstamp+8); tmstamp.tm_year = atoi(tstamp+20)-1900; tmstamp.tm_hour = atoi(tstamp+11); tmstamp.tm_min = atoi(tstamp+14); tmstamp.tm_sec = atoi(tstamp+17); } else if (flen == 23) { /* fn is of the form: WWW_MMM_D_hh:mm:ss_YYYY */ if (*(tstamp+3) == '_') *(tstamp+3) = ' '; else return -1; if (*(tstamp+7) == '_') *(tstamp+7) = ' '; else return -1; if (*(tstamp+9) == '_') *(tstamp+9) = ' '; else return -1; if (*(tstamp+12) == ':') *(tstamp+12) = ' '; else return -1; if (*(tstamp+15) == ':') *(tstamp+15) = ' '; else return -1; if (*(tstamp+18) == '_') *(tstamp+18) = ' '; else return -1; while (mnames[tmstamp.tm_mon] && strncmp(tstamp+4, mnames[tmstamp.tm_mon], 3)) tmstamp.tm_mon++; tmstamp.tm_mday = atoi(tstamp+8); tmstamp.tm_year = atoi(tstamp+19)-1900; tmstamp.tm_hour = atoi(tstamp+10); tmstamp.tm_min = atoi(tstamp+13); tmstamp.tm_sec = atoi(tstamp+16); } else { return -1; } result = mktime(&tmstamp); return result; } void trim_logs(time_t cutoff) { filelist_t *fwalk; DIR *ldir = NULL, *sdir = NULL; struct dirent *sent, *lent; time_t ltime; char fn1[PATH_MAX], fn2[PATH_MAX]; int itemno = 0; /* We have a list of directories to trim, so process them */ for (fwalk = flhead; (fwalk); fwalk = fwalk->next) { dbgprintf("Processing %s\n", fwalk->fname); itemno++; if (progressinfo && ((itemno % progressinfo) == 0)) showprogress(itemno); switch (fwalk->ftype) { case F_DROPIT: /* It's an orphan, and we want to delete it */ dropdirectory(fwalk->fname, 0); break; case F_PURGELOGS: sdir = opendir(fwalk->fname); if (sdir == NULL) { errprintf("Cannot process directory %s: %s\n", fwalk->fname, strerror(errno)); break; } while ((sent = readdir(sdir)) != NULL) { int allgone = 1; if (*(sent->d_name) == '.') continue; sprintf(fn1, "%s/%s", fwalk->fname, sent->d_name); ldir = opendir(fn1); if (ldir == NULL) { errprintf("Cannot process directory %s: %s\n", fn1, strerror(errno)); continue; } while ((lent = readdir(ldir)) != NULL) { if (*(lent->d_name) == '.') continue; ltime = logtime(lent->d_name); if ((ltime > 0) && (ltime < cutoff)) { sprintf(fn2, "%s/%s", fn1, lent->d_name); if (unlink(fn2) == -1) { errprintf("Failed to unlink %s: %s\n", fn2, strerror(errno)); } } else allgone = 0; } closedir(ldir); /* Is it empty ? Then remove it */ if (allgone) rmdir(fn1); } closedir(sdir); break; default: break; } } } int main(int argc, char *argv[]) { int argi; DIR *histdir = NULL; struct dirent *hent; struct stat st; time_t cutoff = 0; int dropsvcs = 0; int dropfiles = 0; int droplogs = 0; char *envarea = NULL; for (argi = 1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--cutoff=")) { char *p = strchr(argv[argi], '='); cutoff = atoi(p+1); } else if (argnmatch(argv[argi], "--outdir=")) { char *p = strchr(argv[argi], '='); outdir = strdup(p+1); } else if (strcmp(argv[argi], "--drop") == 0) { dropfiles = 1; } else if (strcmp(argv[argi], "--dropsvcs") == 0) { dropsvcs = 1; } else if (strcmp(argv[argi], "--droplogs") == 0) { droplogs = 1; } else if (strcmp(argv[argi], "--progress") == 0) { progressinfo = 100; } else if (argnmatch(argv[argi], "--progress=")) { char *p = strchr(argv[argi], '='); progressinfo = atoi(p+1); } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (strcmp(argv[argi], "--help") == 0) { printf("Usage:\n\n\t%s --cutoff=TIME\n\nTIME is in seconds since epoch\n", argv[0]); return 0; } else if (argnmatch(argv[argi], "--env=")) { char *p = strchr(argv[argi], '='); loadenv(p+1, envarea); } else if (argnmatch(argv[argi], "--area=")) { char *p = strchr(argv[argi], '='); envarea = strdup(p+1); } } if (cutoff == 0) { errprintf("Must have a cutoff-time\n"); return 1; } if (chdir(xgetenv("XYMONHISTDIR")) == -1) { errprintf("Cannot cd to history directory: %s\n", strerror(errno)); return 1; } histdir = opendir("."); if (!histdir) { errprintf("Cannot read history directory: %s\n", strerror(errno)); return 1; } load_hostnames(xgetenv("HOSTSCFG"), NULL, get_fqdn()); /* First scan the directory for all files, and pick up the ones we want to process */ while ((hent = readdir(histdir)) != NULL) { char *hostname = NULL; char hostip[IP_ADDR_STRLEN]; enum ghosthandling_t ghosthandling = GH_IGNORE; if (stat(hent->d_name, &st) == -1) { errprintf("Odd entry %s - cannot stat: %s\n", hent->d_name, strerror(errno)); continue; } if ((*(hent->d_name) == '.') || S_ISDIR(st.st_mode)) continue; if (strcmp(hent->d_name, "allevents") == 0) { /* Special all-hosts-services event log */ add_to_filelist(hent->d_name, F_ALLEVENTS); continue; } hostname = knownhost(hent->d_name, hostip, ghosthandling); if (hostname) { /* Host history file. */ add_to_filelist(hent->d_name, F_HOSTHISTORY); } else { char *delim, *p, *hname, *tname; delim = strrchr(hent->d_name, '.'); if (!delim) { /* It's a host history file (no dot in filename), but the host does not exist */ errprintf("Orphaned host-history file %s - no host\n", hent->d_name); if (dropfiles) add_to_filelist(hent->d_name, F_DROPIT); continue; } *delim = '\0'; hname = strdup(hent->d_name); tname = delim+1; *delim = '.'; p = strchr(hname, ','); while (p) { *p = '.'; p = strchr(p, ','); } hostname = knownhost(hname, hostip, ghosthandling); if (!hostname) { errprintf("Orphaned service-history file %s - no host\n", hent->d_name); if (dropfiles) add_to_filelist(hent->d_name, F_DROPIT); } else if (dropsvcs && !validstatus(hostname, tname)) { errprintf("Orphaned service-history file %s - no service\n", hent->d_name); if (dropfiles) add_to_filelist(hent->d_name, F_DROPIT); } else { /* Service history file */ add_to_filelist(hent->d_name, F_SERVICEHISTORY); } xfree(hname); } } closedir(histdir); /* Then process the files */ if (progressinfo) errprintf("Starting trim of %d history-logs\n", totalitems); trim_files(cutoff); /* Process statuslogs also ? */ if (!droplogs) return 0; flhead = NULL; /* Dirty - we should clean it up properly - but I dont care */ totalitems = 0; if (chdir(xgetenv("XYMONHISTLOGS")) == -1) { errprintf("Cannot cd to historical statuslogs directory: %s\n", strerror(errno)); return 1; } histdir = opendir("."); if (!histdir) { errprintf("Cannot read historical statuslogs directory: %s\n", strerror(errno)); return 1; } while ((hent = readdir(histdir)) != NULL) { if (stat(hent->d_name, &st) == -1) { errprintf("Odd entry %s - cannot stat: %s\n", hent->d_name, strerror(errno)); continue; } if ((*(hent->d_name) == '.') || !S_ISDIR(st.st_mode)) continue; if (knownloghost(hent->d_name)) { add_to_filelist(hent->d_name, F_PURGELOGS); } else { add_to_filelist(hent->d_name, F_DROPIT); } } closedir(histdir); if (progressinfo) errprintf("Starting trim of %d status-log collections\n", totalitems); trim_logs(cutoff); return 0; } xymon-4.3.7/include/0000775000175000017500000000000011671641715013657 5ustar henrikhenrikxymon-4.3.7/include/libxymon.h0000664000175000017500000000552011655212727015672 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __LIBXYMON_H__ #define __LIBXYMON_H__ #include #include typedef struct htnames_t { char *name; struct htnames_t *next; } htnames_t; typedef struct strbuffer_t { char *s; int used, sz; } strbuffer_t; #define STRBUF(buf) (buf->s) #define STRBUFLEN(buf) (buf->used) #define STRBUFAVAIL(buf) (buf->sz - buf->used) #define STRBUFEND(buf) (buf->s + buf->used) #define IP_ADDR_STRLEN 16 #include "version.h" #include "config.h" #include "../lib/osdefs.h" #ifdef XYMONWINCLIENT #include "../lib/strfunc.h" #include "../lib/errormsg.h" #include "../lib/environ.h" #include "../lib/stackio.h" #include "../lib/timefunc.h" #include "../lib/memory.h" #include "../lib/sendmsg.h" #include "../lib/holidays.h" #include "../lib/rbtr.h" #include "../lib/msort.h" #include "../lib/misc.h" #else /* Defines CGI URL's */ #include "../lib/cgiurls.h" #include "../lib/links.h" /* Generates HTML */ #include "../lib/acklog.h" #include "../lib/eventlog.h" #include "../lib/headfoot.h" #include "../lib/htmllog.h" #include "../lib/notifylog.h" #include "../lib/reportlog.h" #include "../lib/availability.h" #include "../lib/calc.h" #include "../lib/cgi.h" #include "../lib/color.h" #include "../lib/crondate.h" #include "../lib/clientlocal.h" #include "../lib/digest.h" #include "../lib/encoding.h" #include "../lib/environ.h" #include "../lib/errormsg.h" #include "../lib/files.h" #include "../lib/xymonrrd.h" #include "../lib/holidays.h" #include "../lib/ipaccess.h" #include "../lib/loadalerts.h" #include "../lib/loadhosts.h" #include "../lib/loadcriticalconf.h" #include "../lib/locator.h" #include "../lib/matching.h" #include "../lib/md5.h" #include "../lib/memory.h" #include "../lib/misc.h" #include "../lib/msort.h" #include "../lib/netservices.h" #include "../lib/readmib.h" #include "../lib/rmd160c.h" #include "../lib/sendmsg.h" #include "../lib/sha1.h" #include "../lib/sha2.h" #include "../lib/sig.h" #include "../lib/stackio.h" #include "../lib/strfunc.h" #include "../lib/suid.h" #include "../lib/timefunc.h" #include "../lib/timing.h" #include "../lib/tree.h" #include "../lib/url.h" #include "../lib/webaccess.h" #endif #endif xymon-4.3.7/include/version.h0000664000175000017500000000145011671641417015514 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __VERSION_H__ #define __VERSION_H__ #define VERSION "4.3.7" #endif xymon-4.3.7/debian/0000775000175000017500000000000011671641715013456 5ustar henrikhenrikxymon-4.3.7/debian/default0000664000175000017500000000114011535462534015020 0ustar henrikhenrik# Configure the Xymon client settings. # You MUST set the list of Xymon servers that this # client reports to. # It is good to use IP-adresses here instead of DNS # names - DNS might not work if there's a problem. # # E.g. (a single Xymon server) # XYMONSERVERS="192.168.1.1" # or (multiple servers) # XYMONSERVERS="10.0.0.1 192.168.1.1" XYMONSERVERS="" # The defaults usually suffice for the rest of this file, # but you can tweak the hostname that the client reports # data with, and the OS name used (typically needed only on # RHEL or RHAS servers). # CLIENTHOSTNAME="" # CLIENTOS="rhel3" xymon-4.3.7/debian/changelog0000664000175000017500000004514411671641417015337 0ustar henrikhenrikxymon (4.3.7) unstable; urgency=low * rev 6803 * Fix acknowledge CGI (broken in 4.3.6) * Fix broken uptime calculation for systems reporting "1 day" * Workaround Solaris breakage in the LFS-support detection * Fix/add links to the HTML man-page index. * Fix "Stop after" value not being shown on the "info" page. * Fix broken alert texts when using FORMAT=SMS * Fix wrong description of xymondboard CRITERIA in xymon(1) * Fix missing columnname in analysis.cfg(5) DS example * Fix missing space in output from disk IGNORE rules in xymond_client --dump-config * Fix overwrite of xymon-apache.conf when upgrading * Fix installation so it does not remove include/directory lines from configuration files. * Add client/local/ directory for custom client script -- Henrik Stoerner Tue, 13 Dec 2011 13:15:00 +0100 xymon (4.3.6) unstable; urgency=low rev 6788 * Optionally choose the color for the "cpu" status when it goes non-green due to uptime or clock offset. * Allow for "include" and "directory" in combo.cfg and protocols.cfg * New INTERFACES definition in hosts.cfg to select which network interfaces are tracked in graphs. * New access control mechanism for some CGI scripts returning host-specific information. Access optionally checked against an Apache-style "group" file (see xymonwebaccess(5) CGI manpage). * New "vertical" page-definitions (vpage, vsubpage,vsubparent) for listing hosts across and tests down on a page. * Fix hostlist CGI crash when called with HTTP "HEAD" * Fix svcstatus CGI crash when called with non-existing hostname * Fix "ackinfo" updates being cleared when host hits a DOWNTIME period. * Fix compile-errors on Solaris due to network libraries not being included. * Fix "logrotate" messages not being sent to some channels. * STATUSLIFETIME now provides the default time a status is valid (in xymond). * Critical systems view: Use priority 99 for un-categorised priorities (imported from NK tags) and show this as 'No priority' on the webpage. * useradm CGI: Sort usernames * New xymond module - xymond_distribute - can forward administrative commands (drop, rename, disable, enable) from one Xymon server to another. * New tool: appfeed CGI provides data for the Android "xymonQV" app by Darrik Mazey. -- Henrik Stoerner Mon, 5 Dec 2011 08:00:00 +0100 xymon (4.3.5) unstable; urgency=medium rev 6755 * Fix crash in CGI generating the "info" status column. * Fix broken handling of IGNORE for log-file analysis. * Fix broken clean-up of obsolete cookies (no user impact). * Devmon RRD handler: Fix missing initialisation, which might cause crashes of the RRD handler. * Fix crashes in xymond caused by faulty new library for storing cookies and host-information. * Fix memory corruption/crash in xymond caused by logging of multi-source statuses. * New "delayred" and "delayyellow" definitions for a host can be used to delay change to a yellow/red status for any status column (replaces the network-specific "badFOO" definitions). * analysis.cfg and alerts.cfg: New DISPLAYGROUP setting to select hosts by the group/group-only/group-except text. * New HOSTDOCURL setting in xymonserver.cfg. Replaces the xymongen "--docurl" and "--doccgi" options, and is used by all tools. * xymond_history option to control location of PID file. * Critical Systems view: Optionally show eventlog for the hosts present on the CS view. * Critical Systems view: Multiple --config options can now be used, to display critical systems from multiple configurations on one page. * Detailed status display: Speedup by no longer having to load the hosts.cfg file. * xymongen / xymonnet: New "--loadhostsfromxymond" option. -- Henrik Stoerner Fri, 9 Sep 2011 10:35:00 +0200 xymon (4.3.4) unstable; urgency=medium rev 6720 * Fix crashes and data corruption in Xymon worker modules (xymond_client, xymond_rrd etc) after handling large messages. * Fix xymond lock-up when renaming/deleting hosts * Fix xymond cookie lookup mechanism * Fix xymond_client crash if analysis.cfg contains invalid configuration entries, e.g. expressions that do not compile. * Fix showgraph CGI crash when legends contain colon. * CGI utils: Fix potential security issues involving buffer- overruns when generating responses. * CGI utils: Fix crash when invoked with HTTP "HEAD" * CGI utils: Fix crashes on 64-bit platforms due to missing prototype of "basename()" function. * svcstatus CGI: Fix crash if history log is not a file. * Critical systems view CGI: Fix cross-site scripting * Fix recovery-messages for alerts sent to a GROUP * xymonnet: Include hostname when reporting erroneous test-spec * Webpages: Add new HOSTPOPUP setting to control what values from hosts.cfg are displayed as a "comment" to the hostname (either in pop-up's or next to the hostname). * RRD "memory" status handler now recognizes the output from the bb-xsnmp.pl module (for Cisco routers). * Web templates modified so the menu CSS can override the default body CSS. * Acknowledge web page now allows selecting minutes/hours/days * Enable/Disable webpage enhanced, so when selecting multiple hosts the "Tests" column only lists the tests those hosts have. -- Henrik Stoerner Mon, 1 Aug 2011 23:26:00 +0200 xymon (4.3.3) unstable; urgency=high rev 6684 * SECURITY FIX: Some CGI parameters were used to construct filenames of historical logfiles without being sanitized, so they could be abused to read files on the webserver. * SECURITY FIX: More cross-site scripting vulnerabilities. * Remove extra "," before "History" button on status-view * Critical view: Shring priority-column to 10% width * hosts.cfg loader: Check for valid IP spec (nibbles in 0-255 range). Large numbers in a nibble were accepted, triggering problems when trying to ping the host. * Alert macros no longer limited to 8kB -- Henrik Stoerner Mon, 6 May 2011 07:51:00 +0200 xymon (4.3.2) unstable; urgency=medium rev 6672 * Bugfix for problems caused by the XSS fixes. -- Henrik Stoerner Mon, 4 Apr 2011 07:34:00 +0200 xymon (4.3.1) unstable; urgency=medium rev 6667 * Web UI: SECURITY FIX - fix potential cross-site scripting vulnerabilities. Initial report by David Ferrest (email April 1st 2011). * Solaris Makefile: Drop guessing of what linker is being used, since we get it wrong too often. * configure: Add missing include to fix compile failure on some systems. * get_ostype(): Check that we have a valid OS identifier. Dont assume we can write to the string passed us. * xymond user messages: Improve error message for oversize messages. Document the MAXMSG_USER setting. * combostatus: Make the set of error-colors configurable. Change default set so BLUE and PURPLE are not considered errors (only RED is an error by default). * xymon(1) manpage: Add missing description of some fields available in the xymondboard command. * hosts.cfg manpage: Fix wrong NOPROP interpretation. From Thomas Brand. * Demotool: Change Hobbit->Xymon -- Henrik Stoerner Sun, 3 Apr 2011 12:03:00 +0200 xymon (4.3.0) unstable; urgency=medium * Critical view and other webpages: Make the 'All systems OK' message configurable. Also allow the header/footer for the Critical Systems view to be configurable. Suggestion and preliminary patch from Buchan Milne. * xymonnet: Improve error report when HTTP tests get an empty response - 'HTTP error 0' sounds weird. * report / snapshot CGI's: Fix buffer overrun in the HTML delimiter generated in the "please wait..." message. Also, fix potential buffer overrun in report CGI if invoked with a large value for the "style" parameter. Reported by Rolf Biesbroek. * Graph definitions (graphs.cfg): Multi graphs cannot use a regex pattern. Problem report by Brian Majeska * Solaris interface statistics: Filter out "mac" and "wrsmd" devices at the client side. Update RRD handler to also filter "wrsmd" at the server side, like we already did for "mac" devices. Cf. http://www.xymon.com/archive/2009/06/msg00204.html * Documentation: Document the XMH_* fields available in xymondboard commands. * Documentation: Document SPLITNCV and "trends" methods of doing custom graphs. * RRD definitions: Allow override of --step/-s option for rrdcreate, from template supplied in rrddefinitions.cfg. Suggestion from Brian Majeska. * mailack: Remove restriction on how long a subjectline/message body can be. * Build procedure: Add notice about running upgrade script before installing the new version. * xymond_alert: Document --trace option * Alerts: For recovery messages, add information so you can tell whether the recovery was due to the service actually recovering, or if it was merely disabled. * xymond_alert: Fix missing element in array of alert status texts used for tracing. Spotted by Dominique Frise. * Add support for FreeBSD v8 modified ifstat output * Documentation: Update information about the Xymon mailing lists following move to Mailman and new archive URL. * HP/UX client: Use "swapinfo" to extract memory utilisation data, instead of the hpux-meminfo utility. By Earl Flack http://lists.xymon.com/pipermail/xymon/2010-December/030100.html -- Henrik Stoerner Fri, 4 Mar 2011 11:08:00 +0100 xymon (4.3.0-0.20110120.rc1) unstable; urgency=low * 4.3.0 RC1 release - rev 6627 * hosts.cfg badldap documentation: Document that for LDAP URL's you must use 'badldapurl'. Reported by Simo Hmami. * xymond flap detection: Make number of tracked status changes and the flap-check period configurable. Change the defaults to trigger flapping at more than 5 status changes in a 30 minute period. * sendmessage: Enhanced error reporting, to help track down communication problems. * xymond_client: Fix Windows SVC status handling to avoid coredumps, memory corruption and other nasties. Will now report the real name of the service, instead of the pattern used in the analysis.cfg file. NOTE: Slight change to status message format. * Client handler: Fix owner/user check parsing. Reported by Ian Marsh http://www.xymon.com/archive/2011/01/msg00133.html (also broken in 4.2.3). * xymongen: Fix broken --doc-window option handling. Reported by Tom Schmitt. * Xymongen: Fix documentation of the --doc-window/--no-doc-window options. * Webpage background: Use a CSS and a new set of gif's to implement a background that works on all displays, regardless of width. Uses a new xymonbody.css stylesheet which can also control some other aspects of the webpage. From Francois Claire. * Documentation: The xymon 'rename' command should be used AFTER renaming a host in hosts.cfg, not before. From Tom Georgoulias. * Memory status: Add some sanity checks for the memory utilisation reported by clients. Occasionally we get completely bogus data from clients, so only act on them if percentages do not exceed 100. * Critical systems view: Add "--tooltips" option so you can save screen space by hiding the host descriptions in a tooltip, like we do on the statically generated pages. Feature request from Chris Morris. * Solaris client: Report "swap -l" in addition to "swap -s" for swap usage. Backend prefers output from "swap -l" when determining swap utilisation. * Webpage menu: Use the CSS and GIF's by Malcolm Hunter - they are much nicer than the ones from Debian. Distribute both the blue and the grey version, and configure which one to use in xymonserver.cfg. * Graph zoom: Use float variables when calculating the upper/lower limits of the graph. Fixes vertical zoom. * xymond: Make sure we do not perform socket operations on invalid sockets (e.g. those from a scheduled task pseudo-connection) * Installation: Remove any existing old commands before creating symlinks * xymonproxy: Fix broken compatibility option '--bbdisplay' * Fix eventlog summary/count enums so they dont clash with Solaris predefined entities * History- and hostdata-modules: Dont save data if there is less than 5% free space on the filesystem. Also, dont save hostdata info more than 5 times per hour. * Historical statuslog display: Work-around for crash when status-log is empty * fping.sh configure sub-script: Fix syntax error in suggested 'sudoers' configuration, and point to the found fping binary. From Steff Coene. * namematch routine: Fix broken matching when doing simple matching against two strings where one was a subset of the other. http://www.xymon.com/archive/2010/11/msg00177.html . Reported by Elmar Heeb who also provided a patch, although I chose a different solution to this. * Xymon net: Fix broken compile when LDAP-checks are disabled. Reported by Roland Soderstrom, fix from Ralph Mitchell. * xymon(7) manpage: Drop notice that renaming in 4.3.0 is not complete * Installation: Setup links for the commonly used Hobbit binaries (bb, bbcmd, bbdigest, bbhostgrep, bbhostshow) * Upgrade script: Setup symlinks for the old names of the standard webpages * xymonserver.cfg.DIST: Missing end-quote in compatibility BBSERVERSECURECGIURL setting. From Ralph Mitchell * xymongrep: Fix broken commandline parsing resulting from trying to be backwards-compatible. Reported by Jason Chambers. -- Henrik Stoerner Sun, 23 Jan 2011 12:36:00 +0100 xymon (4.3.0-0.20101114.beta3) unstable; urgency=low * Beta-3 release - rev 6590 -- Henrik Stoerner Sun, 14 Nov 2010 19:00:00 +0100 hobbit (4.3.0-0.beta2) unstable; urgency=low * Xymon version 4.3.0 BETA 2 Core changes: * New API's for loadhosts and sendmessage, in preparation for the full 5.0 changes. * Always use getcurrenttime() instead of time(). * Support for defining holidays as non-working days in alerts and SLA calculations. * Hosts which appear on multiple pages in the web display can use any page they are on in the alerting rules and elsewhere. * Worker modules (RRD, client-data parsers etc) can operate on remote hosts from the hobbitd daemon, for load-sharing. * Various bugfixes collected over time. * New client support: z/OS, z/VSE and z/VM. Network test changes: * Merged new network tests from trunk: SOAP-over-HTTP, SSL minimum cipher strength * Changed network test code to always report a validity period for network tests, so it it possible to run network tests less often than every 30 minutes (e.g. once an hour). * Make the content-type setting in HTTP POST tests configurable. * Make the source-address used for TCP tests configurable. * Make the acceptable HTTP result codes configurable. * Use and save HTTP session cookies. Web changes * Support generic drop-down lists in templates. * "NOCOLUMNS" changed to work for all columns. * New "group-sorted" definition to auto-sort hosts in a group * Use browser tooltips for host comments * "Compact" status allows several statuses to appear as a single status on the overview webpages. * Trends page can select the time period to show. Buttons provided for the common selections. * Ghost list report now lists possible candidates for a ghost, based on IP-address or unqualified hostname. * Enhanced eventlog and top-changing hosts webpage Report changes * Number of outages as SLA parameter Miscellaneous * hobbitlaunch support for running tasks only on certain hosts, and for a maximum time. -- Henrik Stoerner Fri, 24 May 2009 10:39:00 +0200 hobbit (4.2.3-1) unstable; urgency=low * Xymon version 4.2.3 release * Time-out code changed to use clock_gettime() with CLOCK_MONOTONIC * Bugfix for hobbitd/hobbitd_worker communication going out-of-sync resulting in "garbled data" being logged and worker modules stopping. * NCV module now works with negative numbers. * Several bugfixes in DNS lookup code - could lead to crashes when performing DNS tests. * Switch to C-ARES 1.6.0 - drop support for older versions. * Run more TCP tests in parallel by not waiting for very slow connections to complete before starting new ones. * Added "hostlist" web utility for spreadsheet-reporting of the hosts in Hobbit. -- Henrik Stoerner Mon, 09 Feb 2009 10:46:00 +0100 hobbit (4.2.2-1) unstable; urgency=low * Xymon version 4.2.2: 4.2.0 plus all-in-one patch * BBWin support added * Project renamed to "Xymon" - preliminary changes in documents and web templates, but no filename changes. -- Henrik Stoerner Thu, 28 Sep 2008 14:52:00 +0100 hobbit (4.2.0-1) unstable; urgency=low * Hobbit version 4.2: New upstream release. -- Henrik Stoerner Wed, 09 Aug 2006 21:48:00 +0200 hobbit (4.2-RC-20060712) unstable; urgency=low * Release candidate of 4.2 -- Henrik Stoerner Wed, 12 Jul 2006 23:13:00 +0200 hobbit (4.2-beta-20060605) unstable; urgency=low * Beta release of 4.2 -- Henrik Stoerner Mon, 05 Jun 2006 16:53:00 +0200 hobbit (4.2-alfa-20060404) unstable; urgency=low * Alfa release of 4.2 -- Henrik Stoerner Tue, 04 Apr 2006 23:30:00 +0200 hobbit (4.1.2p1-1) unstable; urgency=low * New upstream release -- Henrik Stoerner Thu, 10 Nov 2005 17:32:00 +0100 hobbit (4.1.2-1) unstable; urgency=low * New upstream release -- Henrik Stoerner Mon, 10 Oct 2005 22:30:00 +0200 hobbit (4.1.1-1) unstable; urgency=low * New upstream release. -- Henrik Stoerner Mon, 25 Jul 2005 17:49:00 +0200 hobbit (4.1.0-1) unstable; urgency=low * New upstream release. -- Henrik Stoerner Sun, 24 Jul 2005 23:27:00 +0200 hobbit (4.0.4-1) unstable; urgency=low * New upstream release. -- Henrik Stoerner Sun, 29 May 2005 12:08:00 +0200 hobbit (4.0.3-1) unstable; urgency=low * New upstream release. -- Henrik Stoerner Sun, 22 May 2005 09:34:57 +0200 hobbit (4.0.2-1) unstable; urgency=low * New upstream release. -- Henrik Stoerner Sun, 10 Apr 2005 19:39:15 +0200 hobbit (4.0.1-1) unstable; urgency=low * Build problems fixed on Solaris, HP-UX * Zoomed graphs could lose the hostname in the title. -- Henrik Stoerner Fri, 1 Apr 2005 07:43:42 +0200 hobbit (4.0-1) unstable; urgency=low * Upstream release of version 4.0 -- Henrik Stoerner Wed, 30 Mar 2005 21:31:03 +0200 xymon-4.3.7/debian/docs0000664000175000017500000000007411070452713014321 0ustar henrikhenrikREADME README.CLIENT Changes CREDITS RELEASENOTES docs/TODO xymon-4.3.7/debian/xymon-client.config0000664000175000017500000000151411535462534017273 0ustar henrikhenrik#!/bin/sh CONFIGFILE=/etc/default/xymon-client set -e . /usr/share/debconf/confmodule if [ -e $CONFIGFILE ]; then . $CONFIGFILE || true fi # if there is no value configured, look for debconf answers if [ -z "$XYMONSERVERS" ] ; then db_get xymon-client/XYMONSERVERS XYMONSERVERS="$RET" fi # still nothing? set a default if [ -z "$XYMONSERVERS" ] ; then XYMONSERVERS="127.0.0.1" fi # in any case, store the value in debconf db_set xymon-client/XYMONSERVERS "$XYMONSERVERS" if [ -z "$CLIENTHOSTNAME" ] ; then db_get xymon-client/CLIENTHOSTNAME CLIENTHOSTNAME="$RET" fi if [ -z "$CLIENTHOSTNAME" ] ; then CLIENTHOSTNAME="`hostname -f 2> /dev/null || hostname`" fi db_set xymon-client/CLIENTHOSTNAME "$CLIENTHOSTNAME" db_input high xymon-client/XYMONSERVERS || true db_input medium xymon-client/CLIENTHOSTNAME || true db_go || true xymon-4.3.7/debian/xymon-client.files0000664000175000017500000000063311535424634017130 0ustar henrikhenrikusr/share/man/man1/xymon.1 usr/share/man/man1/xymoncmd.1 usr/share/man/man1/xymondigest.1 usr/share/man/man1/xymongrep.1 usr/share/man/man1/xymoncfg.1 usr/share/man/man5/clientlaunch.cfg.5 usr/share/man/man1/clientupdate.1 usr/share/man/man7/xymon.7 usr/share/man/man5/xymonclient.cfg.5 usr/share/man/man8/xymonlaunch.8 usr/share/man/man1/logfetch.1 usr/share/man/man8/msgcache.8 usr/share/man/man1/orcaxymon.1 xymon-4.3.7/debian/xymon.dirs0000664000175000017500000000014011535462534015505 0ustar henrikhenriketc/apache2/conf.d etc/xymon/tasks.d etc/xymon/graphs.d etc/xymon/xymonserver.d usr/share/xymon xymon-4.3.7/debian/xymon-client.init0000664000175000017500000000472211535462534016775 0ustar henrikhenrik#! /bin/sh # # xymonclient This shell script takes care of starting and stopping # the Xymon client. ### BEGIN INIT INFO # Provides: xymon-client # Required-Start: $remote_fs $network # Should-Start: $all # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Xymon system monitor client # Description: Client to feed system data to a remote Xymon server. ### END INIT INFO PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON="/usr/lib/xymon/client/bin/xymonlaunch" NAME=xymonclient DESC="Xymon Client" PIDFILE="/var/run/xymon/clientlaunch.pid" XYMONCLIENTHOME="/usr/lib/xymon/client" test -x $DAEMON || exit 0 . /lib/lsb/init-functions . /usr/share/xymon/init-common.sh # Include xymonclient defaults if available if [ -f /etc/default/xymon-client ] ; then . /etc/default/xymon-client fi [ -z "$MACHINE" ] && MACHINE="$CLIENTHOSTNAME" [ -z "$MACHINEDOTS" ] && MACHINEDOTS="`hostname -f`" export XYMONSERVERS XYMONCLIENTHOME CLIENTHOSTNAME MACHINE MACHINEDOTS case "$1" in start) # do not run the client script on the server [ -x /usr/lib/xymon/server/bin/xymond ] && exit 0 create_includefiles log_daemon_msg "Starting $DESC" "$NAME" start-stop-daemon --exec $DAEMON --chuid xymon --umask 022 --start \ -- \ --config=/etc/xymon/clientlaunch.cfg \ --log=/var/log/xymon/clientlaunch.log \ --pidfile=$PIDFILE log_end_msg $? ;; stop) log_daemon_msg "Stopping $DESC" "$NAME" start-stop-daemon --exec $DAEMON --pidfile $PIDFILE --stop --retry 5 log_end_msg $? ;; status) if test -s $PIDFILE then kill -0 `cat $PIDFILE` if test $? -eq 0 then echo "Xymon client running with PID `cat $PIDFILE`" exit 0 else echo "Xymon client not running, removing stale PID file" rm -f $PIDFILE exit 1 fi else echo "Xymon client does not appear to be running" exit 3 fi ;; restart) if [ -x /usr/lib/xymon/server/bin/xymond ] ; then log_action_msg "Xymon server installed. Please restart 'xymon' instead" exit 0 fi $0 stop sleep 1 $0 start ;; reload|force-reload) [ -x /usr/lib/xymon/server/bin/xymond ] && exit 0 create_includefiles kill -HUP `cat /var/run/xymon/clientlaunch.pid` ;; rotate) for PIDFILE in /var/run/xymon/*.pid do test -e $PIDFILE && kill -HUP `cat $PIDFILE` done ;; *) N=/etc/init.d/$NAME echo "Usage: $N {start|stop|restart|force-reload|status|rotate}" >&2 exit 1 ;; esac exit 0 xymon-4.3.7/debian/xymon-client.logrotate0000664000175000017500000000044611535424634020030 0ustar henrikhenrik# # Logrotate fragment for Xymon (client and server). # /var/log/xymon/*.log { weekly compress delaycompress rotate 5 missingok nocreate sharedscripts postrotate /etc/init.d/xymon rotate endscript } xymon-4.3.7/debian/xymon-client.dirs0000664000175000017500000000016511535462534016770 0ustar henrikhenriketc/default etc/xymon/clientlaunch.d etc/xymon/xymonclient.d usr/bin usr/share/xymon var/lib/xymon/tmp var/log/xymon xymon-4.3.7/debian/xymon-client.install0000664000175000017500000000004611535424634017472 0ustar henrikhenrikdebian/init-common.sh usr/share/xymon xymon-4.3.7/debian/xymon-client.postrm0000664000175000017500000000205611535462534017354 0ustar henrikhenrik#! /bin/sh # postrm script for xymon # # see: dh_installdeb(1) set -e # summary of how this script can be called: # * `remove' # * `purge' # * `upgrade' # * `failed-upgrade' # * `abort-install' # * `abort-install' # * `abort-upgrade' # * `disappear' overwrit>r> # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package # Before deluser so debconf still works #DEBHELPER# case "$1" in purge|disappear) rm -rf /var/lib/xymon /var/log/xymon /var/run/xymon /etc/xymon \ /etc/default/xymon-client if test -x /usr/sbin/deluser ; then deluser xymon || true fi if test -x /usr/sbin/delgroup ; then delgroup --only-if-empty xymon || true fi ;; remove|upgrade|failed-upgrade|abort-install|abort-upgrade) ;; *) echo "postrm called with unknown argument \`$1'" >&2 exit 1 ;; esac exit 0 xymon-4.3.7/debian/xymon.postinst0000664000175000017500000000473611535462534016446 0ustar henrikhenrik#! /bin/sh # postinst script for Xymon # # see: dh_installdeb(1) set -e # summary of how this script can be called: # * `configure' # * `abort-upgrade' # * `abort-remove' `in-favour' # # * `abort-deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package case "$1" in configure) # Setup permissions for the newly created "xymon" user to write # data where he needs to. # And for the Apache-run CGI's to generate reports. test -d /var/run/xymon || mkdir /var/run/xymon chown xymon:xymon /var/run/xymon test -d /var/log/xymon || mkdir /var/log/xymon chown xymon:adm /var/log/xymon ; chmod 2755 /var/log/xymon cd /var/lib/xymon; chown xymon:xymon . acks data disabled hist histlogs hostdata logs rrd tmp www cd /var/lib/xymon/www; chown xymon:xymon html notes wml rep snap; chgrp www-data rep snap; chmod g+w rep snap cd /etc/xymon; chgrp www-data critical.cfg critical.cfg.bak; chmod g+w critical.cfg critical.cfg.bak if ! test -e /etc/xymon/hosts.cfg ; then if test -e /etc/default/xymon-client ; then . /etc/default/xymon-client || true fi cat > /etc/xymon/hosts.cfg <&2 exit 1 ;; esac #DEBHELPER# exit 0 xymon-4.3.7/debian/control0000664000175000017500000000311511535462534015060 0ustar henrikhenrikSource: xymon Section: net Priority: extra Maintainer: Henrik Stoerner Build-Depends: debhelper (>= 7), librrd2-dev, libssl-dev, libldap2-dev, libpcre3-dev Standards-Version: 3.8.3 Homepage: http://xymon.sourceforge.net/ Package: xymon Architecture: any Conflicts: hobbit, hobbit-client Provides: xymon Replaces: hobbit, hobbit-client Depends: xymon-client, ${shlibs:Depends}, ${misc:Depends} Description: monitoring system for systems, networks and applications Xymon (previously called Hobbit) is a network- and applications- monitoring system designed for use in large-scale networks. But it will also work just fine on a small network with just a few nodes. It is low-overhead and high-performance, with an easy to use web front-end. It handles monitoring of network services, and through client packages it can also be used to monitor server- specific items. Alerts can trigger when monitoring detects a problem, resulting in e-mails or calls to your pager or mobile phone. . Xymon has a great deal of inspiration from the non-free Big Brother package, but does not include any Big Brother code. Package: xymon-client Architecture: any Conflicts: hobbit, hobbit-client Provides: xymon-client Replaces: hobbit, hobbit-client Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, lsb-base Description: client for the Xymon network monitor Client data collection package for Xymon (previously known as Hobbit). This gathers statistics and data from a single system and reports it to the Xymon monitor. You should run this on all systems if you have a Xymon server installed. xymon-4.3.7/debian/xymon-client.default.dist0000664000175000017500000000115011535424634020407 0ustar henrikhenrik# Configure the Xymon client settings. # You MUST set the list of Xymon servers that this # client reports to. # It is good to use IP-adresses here instead of DNS # names - DNS might not work if there's a problem. # (Internally this will be translated to XYMSRV and XYMSERVERS # variables in /var/run/xymon/bbdisp-include.cfg) # # E.g. (a single Xymon server) # XYMONSERVERS="192.168.1.1" # or (multiple servers) # XYMONSERVERS="10.0.0.1 192.168.1.1" XYMONSERVERS="" # Hostname used by the client for its reports. # Must match the name for this host in the Xymon servers' # hosts.cfg file. CLIENTHOSTNAME="" xymon-4.3.7/debian/rules0000775000175000017500000001117711535462534014544 0ustar henrikhenrik#!/usr/bin/make -f CFLAGS = -Wall -g ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) CFLAGS += -O0 else CFLAGS += -O2 endif Makefile: configure dh_testdir # Add here commands to configure the package. USEXYMONPING=y \ ENABLESSL=y \ ENABLELDAP=y \ ENABLELDAPSSL=y \ XYMONUSER=xymon \ XYMONTOPDIR=/usr/lib/xymon \ XYMONVAR=/var/lib/xymon \ XYMONHOSTURL=/xymon \ CGIDIR=/usr/lib/xymon/cgi-bin \ XYMONCGIURL=/xymon-cgi \ SECURECGIDIR=/usr/lib/xymon/cgi-secure \ SECUREXYMONCGIURL=/xymon-seccgi \ HTTPDGID=www-data \ XYMONLOGDIR=/var/log/xymon \ XYMONHOSTNAME=localhost \ XYMONHOSTIP=127.0.0.1 \ MANROOT=/usr/share/man \ INSTALLBINDIR=/usr/lib/xymon/server/bin \ INSTALLETCDIR=/etc/xymon \ INSTALLWEBDIR=/etc/xymon/web \ INSTALLEXTDIR=/usr/lib/xymon/server/ext \ INSTALLTMPDIR=/var/lib/xymon/tmp \ INSTALLWWWDIR=/var/lib/xymon/www \ ./configure build: build-stamp build-stamp: Makefile dh_testdir # Parallel building does not work as of 4.3.0~beta2 PKGBUILD=1 $(MAKE) -j1 touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp [ ! -f Makefile ] || $(MAKE) distclean dh_clean # debconf-updatepo S=$(CURDIR)/debian/xymon C=$(CURDIR)/debian/xymon-client install-clean: dh_prep install: install-server install-client install-server: build install-clean #################### Installing server ######################## dh_testdir dh_testroot dh_install -a dh_installdirs -a PKGBUILD=1 INSTALLROOT=$S/ $(MAKE) install # Static content in /usr/share cd $S/var/lib/xymon/www && \ mv gifs ../../../../usr/share/xymon && ln -s ../../../../usr/share/xymon/gifs . && \ mv help ../../../../usr/share/xymon && ln -s ../../../../usr/share/xymon/help . && \ mv menu ../../../../usr/share/xymon && ln -s ../../../../usr/share/xymon/menu . # We depend on the -client package rm -rf $S/usr/lib/xymon/client # This needs root chmod 4755 $S/usr/lib/xymon/server/bin/xymonping mv $S/etc/xymon/xymon-apache.conf \ $S/etc/apache2/conf.d/xymon # Make xymonserver.cfg use the settings we have configured during installation echo "include /etc/default/xymon-client" > $S/etc/xymon/xymonserver.cfg.debian sed -f debian/xymonserver-setup.sed $S/etc/xymon/xymonserver.cfg >> $S/etc/xymon/xymonserver.cfg.debian rm $S/etc/xymon/xymonserver.cfg mv $S/etc/xymon/xymonserver.cfg.debian $S/etc/xymon/xymonserver.cfg # Autogenerated on first install rm $S/etc/xymon/hosts.cfg install-client: build install-clean #################### Installing client ######################## dh_testdir dh_testroot dh_install -a dh_installdirs -a PKGBUILD=1 INSTALLROOT=$C/ $(MAKE) install-client cd $C/usr/lib/xymon/client && mv etc/* $C/etc/xymon && rmdir etc && ln -s ../../../../etc/xymon etc cd $C/usr/lib/xymon/client && rmdir logs && ln -s ../../../../var/log/xymon logs cd $C/usr/lib/xymon/client && rmdir tmp && ln -s ../../../../var/lib/xymon/tmp # the only command needed in /usr/bin is xymoncmd, its PATH includes our private .../bin # but install xymon also, since it is used so much. cd $C/usr/bin && ln -s ../lib/xymon/client/bin/xymoncmd xymoncmd cd $C/usr/bin && ln -s ../lib/xymon/client/bin/xymon xymon cp debian/xymon-client.default.dist $C/usr/share/xymon/xymon-client.default # dynamic list of installed client extensions echo "include /var/run/xymon/clientlaunch-include.cfg" >> \ $C/etc/xymon/clientlaunch.cfg rm $C/usr/lib/xymon/client/runclient.sh binary: binary-arch binary-indep #binary-indep: # #################### Building dummy packages ######################## # dh_testdir -i # dh_installchangelogs -i # dh_installdocs -i # dh_compress -i # dh_gencontrol -i # dh_builddeb -i binary-arch: build install-server install-client #################### Building .deb files ######################## dh_testdir -a dh_testroot -a dh_installchangelogs -a Changes dh_installdocs -a dh_installexamples -a # ignore missing dh_lintian for older dh versions -dh_lintian -a # move some files into the client package dh_movefiles --sourcedir=debian/xymon -a cd $S/usr/lib/xymon/server/bin && \ for f in * ; do \ if [ -f $C/usr/lib/xymon/client/bin/$$f ] ; then \ rm -v $$f ; ln -s ../../client/bin/$$f ; \ fi \ done rmdir $S/usr/share/man/man7 dh_installdebconf -a # use the old file names for now dh_installlogrotate --name=xymon-client -a dh_installinit --name=xymon -p'xymon' -- defaults 98 02 dh_installinit --name=xymon-client -p'xymon-client' -- defaults 98 02 dh_installman -a dh_link -a dh_strip -a dh_compress -a dh_fixperms -a -Xbin/xymonping dh_installdeb -a dh_shlibdeps -a dh_gencontrol -a dh_md5sums -a dh_builddeb -a .PHONY: build clean binary-indep binary-arch binary install install-server install-client xymon-4.3.7/debian/xymon.lintian-overrides0000664000175000017500000000034511535424634020210 0ustar henrikhenrikxymon: package-contains-empty-directory usr/lib/xymon/server/download/ #446982 xymon: possibly-insecure-handling-of-tmp-files-in-maintainer-script preinst:30 xymon: setuid-binary usr/lib/xymon/server/bin/xymonping 4755 root/root xymon-4.3.7/debian/xymon.postrm0000664000175000017500000000225111535462534016075 0ustar henrikhenrik#! /bin/sh # postrm script for Xymon # # see: dh_installdeb(1) set -e # summary of how this script can be called: # * `remove' # * `purge' # * `upgrade' # * `failed-upgrade' # * `abort-install' # * `abort-install' # * `abort-upgrade' # * `disappear' overwrit>r> # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package case "$1" in purge|disappear) rm -f /etc/xymon/hosts.cfg /etc/xymon/xymongroups /etc/xymon/xymonpasswd \ /etc/default/xymon if test -e /usr/share/debconf/confmodule ; then . /usr/share/debconf/confmodule db_purge fi ;; remove) ;; upgrade) # The server package doesn't use debconf anymore if dpkg --compare-versions "$2" lt 4.2.0.dfsg-2 && test -e /usr/share/debconf/confmodule ; then . /usr/share/debconf/confmodule db_purge fi ;; failed-upgrade|abort-install|abort-upgrade) ;; *) echo "postrm called with unknown argument \`$1'" >&2 exit 1 ;; esac #DEBHELPER# exit 0 xymon-4.3.7/debian/xymon.init0000664000175000017500000000434611535462534015523 0ustar henrikhenrik#!/bin/sh # Startup script for the Xymon monitor # # This starts the "xymonlaunch" tool, which in turn starts # all of the other Xymon server programs. ### BEGIN INIT INFO # Provides: xymon # Required-Start: $remote_fs $network # Should-Start: $all # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Xymon system monitor server # Description: Xymon system monitor, server part. # (Also monitors the local host.) ### END INIT INFO PIDFILE=/var/run/xymon/xymonlaunch.pid DAEMON=/usr/lib/xymon/server/bin/xymonlaunch NAME="xymond" DESC="Xymon Server" test -x $DAEMON || exit 0 . /lib/lsb/init-functions . /usr/share/xymon/init-common.sh # Include xymonclient defaults if available if [ -f /etc/default/xymon-client ] ; then . /etc/default/xymon-client fi case "$1" in "start") create_includefiles log_daemon_msg "Starting $DESC" "$NAME" start-stop-daemon --exec $DAEMON --chuid xymon --umask 022 --start \ -- \ --config=/etc/xymon/tasks.cfg \ --env=/etc/xymon/xymonserver.cfg \ --log=/var/log/xymon/xymonlaunch.log \ --pidfile=$PIDFILE log_end_msg $? ;; "stop") log_daemon_msg "Stopping $DESC" "$NAME" start-stop-daemon --exec $DAEMON --pidfile $PIDFILE --stop --retry 5 log_end_msg $? ;; "status") if test -s $PIDFILE then kill -0 `cat $PIDFILE` if test $? -eq 0 then echo "Xymon (xymonlaunch) running with PID `cat $PIDFILE`" exit 0 else echo "Xymon not running, removing stale PID file" rm -f $PIDFILE exit 1 fi else echo "Xymon (xymonlaunch) does not appear to be running" exit 3 fi ;; "restart") if test -s $PIDFILE then $0 stop sleep 1 $0 start else log_action_msg "xymonlaunch does not appear to be running, starting it" $0 start fi ;; "reload"|"force-reload") if test -s $PIDFILE then create_includefiles log_action_msg "Reloading xymond config" kill -HUP `cat /var/run/xymon/xymond.pid` else log_action_msg "xymond not running (no PID file)" fi ;; "rotate") for PIDFILE in /var/run/xymon/*.pid do test -e $PIDFILE && kill -HUP `cat $PIDFILE` done ;; *) echo "Usage: $0 start|stop|restart|force-reload|reload|status|rotate" break; esac exit 0 xymon-4.3.7/debian/xymonserver-setup.sed0000664000175000017500000000031311535424634017705 0ustar henrikhenriks!XYMONSERVERHOSTNAME="localhost"!XYMONSERVERHOSTNAME="$CLIENTHOSTNAME"! s!XYMONSERVERWWWNAME="localhost"!XYMONSERVERWWWNAME="$CLIENTHOSTNAME"! s!XYMONSERVERIP="127.0.0.1"!XYMONSERVERIP="$XYMONSERVERS"! xymon-4.3.7/debian/xymon.preinst0000664000175000017500000000163611535462534016243 0ustar henrikhenrik#! /bin/sh # preinst script for Xymon set -e case "$1" in install|upgrade) # stop the client when the server is installed invoke-rc.d xymon stop || true if test "$2" && dpkg --compare-versions "$2" lt 4.2.0.dfsg-2 && test -d /var/lib/xymon/www ; then if test -e /etc/logrotate.d/xymon-client && ! test -e /etc/logrotate.d/xymon-client.dpkg-old ; then mv /etc/logrotate.d/xymon-client /etc/logrotate.d/xymon-client.dpkg-old fi # we want to replace directories with symlinks, prod dpkg to do it move_dir () { test -d "$1" || return test -h "$1" && return test -e "$1.dpkg-old" && return mv "$1" "$1.dpkg-old" } cd /var/lib/xymon/www move_dir gifs move_dir help move_dir menu fi #446982 if test "$2" && dpkg --compare-versions "$2" ge 4.2.0.dfsg-1 && dpkg --compare-versions "$2" lt 4.2.0.dfsg-6 ; then chown root:root /tmp chmod 1777 /tmp fi ;; esac #DEBHELPER# exit 0 xymon-4.3.7/debian/init-common.sh0000664000175000017500000000257611535424634016253 0ustar henrikhenrik# common init functions for Xymon and Xymon-client create_includefiles () { if [ "$XYMONSERVERS" = "" ]; then echo "Please configure XYMONSERVERS in /etc/default/xymon-client" exit 0 fi umask 022 if ! [ -d /var/run/xymon ] ; then mkdir /var/run/xymon chown xymon:xymon /var/run/xymon fi set -- $XYMONSERVERS if [ $# -eq 1 ]; then echo "XYMSRV=\"$XYMONSERVERS\"" echo "XYMSERVERS=\"\"" else echo "XYMSRV=\"0.0.0.0\"" echo "XYMSERVERS=\"$XYMONSERVERS\"" fi > /var/run/xymon/bbdisp-runtime.cfg for cfg in /etc/xymon/clientlaunch.d/*.cfg ; do test -e $cfg && echo "include $cfg" done > /var/run/xymon/clientlaunch-include.cfg if test -d /etc/xymon/xymonlaunch.d ; then for cfg in /etc/xymon/xymonlaunch.d/*.cfg ; do test -e $cfg && echo "include $cfg" done > /var/run/xymon/xymonlaunch-include.cfg fi if test -d /etc/xymon/xymongraph.d ; then for cfg in /etc/xymon/xymongraph.d/*.cfg ; do test -e $cfg && echo "include $cfg" done > /var/run/xymon/xymongraph-include.cfg fi if test -d /etc/xymon/xymonserver.d ; then for cfg in /etc/xymon/xymonserver.d/*.cfg ; do test -e $cfg && echo "include $cfg" done > /var/run/xymon/xymonserver-include.cfg fi if test -d /etc/xymon/xymonclient.d ; then for cfg in /etc/xymon/xymonclient.d/*.cfg ; do test -e $cfg && echo "include $cfg" done > /var/run/xymon/xymonclient-include.cfg fi return 0 } xymon-4.3.7/debian/xymon-client.postinst0000664000175000017500000000402411535462534017710 0ustar henrikhenrik#! /bin/sh # postinst script for xymon # # see: dh_installdeb(1) # summary of how this script can be called: # * `configure' # * `abort-upgrade' # * `abort-remove' `in-favour' # # * `abort-deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package # . /usr/share/debconf/confmodule set -e case "$1" in configure) getent group xymon > /dev/null || addgroup --system xymon getent passwd xymon > /dev/null || adduser --system \ --home /var/run/xymon --no-create-home \ --ingroup xymon --disabled-password --disabled-login \ --gecos "Xymon System Monitor" xymon test -d /var/run/xymon || mkdir /var/run/xymon chown xymon:xymon /var/run/xymon test -d /var/lib/xymon/tmp || mkdir /var/lib/xymon/tmp chown xymon:xymon /var/lib/xymon/tmp test -d /var/log/xymon || mkdir /var/log/xymon chown xymon:adm /var/log/xymon ; chmod 2755 /var/log/xymon # Do the debconf stuff db_get xymon-client/XYMONSERVERS XYMONSERVERS="$RET" db_get xymon-client/CLIENTHOSTNAME CLIENTHOSTNAME="$RET" db_stop # Update configuration file CONFIGFILE=/etc/default/xymon-client test -e $CONFIGFILE || cp /usr/share/xymon/xymon-client.default $CONFIGFILE if grep -q "^XYMONSERVERS=" $CONFIGFILE ; then sed -i -e "s/^XYMONSERVERS=.*/XYMONSERVERS=\"$XYMONSERVERS\"/" \ $CONFIGFILE else echo "XYMONSERVERS=\"$XYMONSERVERS\"" >> $CONFIGFILE fi if grep -q "^CLIENTHOSTNAME=" $CONFIGFILE ; then sed -i -e "s/^CLIENTHOSTNAME=.*/CLIENTHOSTNAME=\"$CLIENTHOSTNAME\"/" \ $CONFIGFILE else echo "CLIENTHOSTNAME=\"$CLIENTHOSTNAME\"" >> $CONFIGFILE fi ;; abort-upgrade|abort-remove|abort-deconfigure) ;; *) echo "postinst called with unknown argument \`$1'" >&2 exit 1 ;; esac #DEBHELPER# exit 0 xymon-4.3.7/debian/compat0000664000175000017500000000000211535462534014653 0ustar henrikhenrik7 xymon-4.3.7/debian/xymon-client.lintian-overrides0000664000175000017500000000011111535424634021453 0ustar henrikhenrikxymon-client: package-contains-empty-directory usr/lib/xymon/client/ext/ xymon-4.3.7/debian/xymon-client.templates0000664000175000017500000000114411535462534020023 0ustar henrikhenrikTemplate: xymon-client/XYMONSERVERS Type: string Default: 127.0.0.1 Description: Xymon server: Please enter the network address used to access the Xymon server(s). If you use multiple servers, use a space-separated list of addresses. . Using host names instead of IP addresses is discouraged in case the network experiences DNS failures. Template: xymon-client/CLIENTHOSTNAME Type: string Default: Description: Client hostname: Please enter the host name used by the Xymon client when sending reports to the Xymon server. This name must match the name used in the hosts.cfg file on the Xymon server. xymon-4.3.7/debian/copyright0000664000175000017500000000063311535462534015412 0ustar henrikhenrikThis package was debianized by Henrik Stoerner on Fri, 29 Oct 2010 22:31:25 +0200. It was downloaded from http://sourceforge.net/projects/xymon/ Copyright: Henrik Stoerner Upstream author: Henrik Stoerner License: GNU GPL On Debian GNU/Linux systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL'. xymon-4.3.7/COPYING0000664000175000017500000004307711070452713013271 0ustar henrikhenrik GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. xymon-4.3.7/client/0000775000175000017500000000000011671641716013513 5ustar henrikhenrikxymon-4.3.7/client/runclient.sh0000775000175000017500000000777311615341300016053 0ustar henrikhenrik#!/bin/sh #----------------------------------------------------------------------------# # Xymon client bootup script. # # # # This invokes xymonlaunch, which in turn runs the Xymon client and any # # extensions configured. # # # # Copyright (C) 2005-2011 Henrik Storner # # "status" section (C) Scott Smith 2006 # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: runclient.sh 6712 2011-07-31 21:01:52Z storner $ # Default settings for this client MACHINEDOTS="`uname -n`" # This systems hostname SERVEROSTYPE="`uname -s | tr '[ABCDEFGHIJKLMNOPQRSTUVWXYZ/]' '[abcdefghijklmnopqrstuvwxyz_]'`" # This systems operating system in lowercase XYMONOSSCRIPT="xymonclient-$SERVEROSTYPE.sh" # Command-line mods for the defaults while test "$1" != "" do case "$1" in --hostname=*) MACHINEDOTS="`echo $1 | sed -e 's/--hostname=//'`" ;; --os=*) SERVEROSTYPE="`echo $1 | sed -e 's/--os=//' | tr '[ABCDEFGHIJKLMNOPQRSTUVWXYZ/]' '[abcdefghijklmnopqrstuvwxyz_]'`" ;; --class=*) CONFIGCLASS="`echo $1 | sed -e 's/--class=//' | tr '[ABCDEFGHIJKLMNOPQRSTUVWXYZ/]' '[abcdefghijklmnopqrstuvwxyz_]'`" ;; --help) echo "Usage: $0 [--hostname=CLIENTNAME] [--os=rhel3|linux22] [--class=CLASSNAME] start|stop" exit 0 ;; start) CMD=$1 ;; stop) CMD=$1 ;; restart) CMD=$1 ;; status) CMD=$1 ;; esac shift done XYMONCLIENTHOME="`dirname $0`" export MACHINEDOTS SERVEROSTYPE XYMONOSSCRIPT XYMONCLIENTHOME CONFIGCLASS MACHINE="`echo $MACHINEDOTS | sed -e 's/\./,/g'`" export MACHINE case "$CMD" in "start") if test ! -w $XYMONCLIENTHOME/logs; then echo "Cannot write to the $XYMONCLIENTHOME/logs directory" exit 1 fi if test ! -w $XYMONCLIENTHOME/tmp; then echo "Cannot write to the $XYMONCLIENTHOME/tmp directory" exit 1 fi if test -s $XYMONCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid; then echo "Xymon client already running, re-starting it" $0 --hostname="$MACHINEDOTS" stop rm -f $XYMONCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid fi $XYMONCLIENTHOME/bin/xymonlaunch --config=$XYMONCLIENTHOME/etc/clientlaunch.cfg --log=$XYMONCLIENTHOME/logs/clientlaunch.log --pidfile=$XYMONCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid if test $? -eq 0; then echo "Xymon client for $SERVEROSTYPE started on $MACHINEDOTS" else echo "Xymon client startup failed" fi ;; "stop") if test -s $XYMONCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid; then kill `cat $XYMONCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid` echo "Xymon client stopped" else echo "Xymon client not running" fi ;; "restart") if test -s $XYMONCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid; then $0 --hostname="$MACHINEDOTS" stop else echo "Xymon client not running, continuing to start it" fi $0 --hostname="$MACHINEDOTS" --os="$SERVEROSTYPE" start ;; "status") if test -s $XYMONCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid then kill -0 `cat $XYMONCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid` if test $? -eq 0 then echo "Xymon client (clientlaunch) running with PID `cat $XYMONCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid`" else echo "Xymon client not running, removing stale PID file" rm -f $XYMONCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid fi else echo "Xymon client (clientlaunch) does not appear to be running" fi ;; *) echo "Usage: $0 start|stop|restart|status" break; esac exit 0 xymon-4.3.7/client/mq.sh0000775000175000017500000000152411535424634014466 0ustar henrikhenrik#!/bin/sh # Xymon data collector for MQ # # # Collect data from the MQ utility "runmqsc": # - queue depth # - channel status # for all of the queue managers given as parameters. # # Wrap this in a client data message, tagged as # coming from an "mqcollect" client collector. # # Requires Xymon server ver. 4.3.0 # # # Called from xymonlaunch with # # CMD $XYMONHOME/ext/mq.sh QUEUEMGR1 [QUEUEMGR2...] # # where QUEUEMGR* are the names of the queue managers. # # $Id: mq.sh 6648 2011-03-08 13:05:32Z storner $ TMPFILE="$XYMONTMP/mq-$MACHINE.$$" echo "client/mqcollect $MACHINE.mqcollect mqcollect" >$TMPFILE while test "$1" -ne "" do QMGR=$1; shift (echo 'dis ql(*) curdepth'; echo 'dis chs(*)'; echo 'end') | runmqsc $QMGR >> $TMPFILE done $XYMON $XYMSRV "@" < $TMPFILE rm $TMPFILE exit 0 xymon-4.3.7/client/freebsd-meminfo.c0000664000175000017500000000351211615341300016702 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon memory information tool for FreeBSD. */ /* This tool retrieves information about the total and free RAM. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: freebsd-meminfo.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include int main(int argc, char *argv[]) { int hw_physmem[] = { CTL_HW, HW_PHYSMEM }; unsigned long physmem; int hw_pagesize[] = { CTL_HW, HW_PAGESIZE }; unsigned long pagesize; int vm_vmtotal[] = { CTL_VM, VM_METER }; struct vmtotal vmdata; size_t len; int result; len = sizeof(physmem); result = sysctl(hw_physmem, sizeof(hw_physmem) / sizeof(*hw_physmem), &physmem, &len, NULL, 0); if (result != 0) return 1; len = sizeof(pagesize); result = sysctl(hw_pagesize, sizeof(hw_pagesize) / sizeof(*hw_pagesize), &pagesize, &len, NULL, 0); if (result != 0) return 1; len = sizeof(vmdata); result = sysctl(vm_vmtotal, sizeof(vm_vmtotal) / sizeof(*vm_vmtotal), &vmdata, &len, NULL, 0); // printf("Pagesize:%d\n", pagesize); printf("Total:%lu\n", (physmem / (1024 * 1024))); printf("Free:%lu\n", (pagesize / 1024)*(vmdata.t_free / 1024)); } xymon-4.3.7/client/xymonclient-darwin.sh0000775000175000017500000000301611615341300017665 0ustar henrikhenrik#!/bin/sh # #----------------------------------------------------------------------------# # Darwin (Mac OS X) client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-darwin.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who echo "[df]" # The sed stuff is to make sure lines are not split into two. df -H -t nonfs,nullfs,cd9660,procfs,volfs,devfs,fdesc | sed -e '/^[^ ][^ ]*$/{ N s/[ ]*\n[ ]*/ / }' echo "[mount]" mount echo "[meminfo]" vm_stat echo "[ifconfig]" ifconfig -a echo "[route]" netstat -rn echo "[netstat]" netstat -s echo "[ifstat]" netstat -ibn | egrep -v "^lo| # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-irix.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who echo "[df]" df -Plk echo "[mount]" mount echo "[swap]" swap -ln echo "[ifconfig]" ifconfig -a echo "[route]" netstat -rn echo "[netstat]" netstat -s echo "[ports]" netstat -na -f inet -P tcp | tail +3 netstat -na -f inet6 -P tcp | tail +5 echo "[ifstat]" netstat -i -n | egrep -v "^lo|$XYMONTMP/xymon_sar.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_sar.$MACHINEDOTS.$$ $XYMONTMP/xymon_sar.$MACHINEDOTS" /dev/null 2>&1 & sleep 5 if test -f $XYMONTMP/xymon_sar.$MACHINEDOTS; then echo "[sar]"; cat $XYMONTMP/xymon_sar.$MACHINEDOTS; rm -f $XYMONTMP/xymon_sar.$MACHINEDOTS; fi exit xymon-4.3.7/client/xymonclient.sh0000775000175000017500000000626411671476413016433 0ustar henrikhenrik#!/bin/sh #----------------------------------------------------------------------------# # Xymon client main script. # # # # This invokes the OS-specific script to build a client message, and sends # # if off to the Xymon server. # # # # Copyright (C) 2005-2011 Henrik Storner # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient.sh 6800 2011-12-12 22:15:39Z storner $ # Must make sure the commands return standard (english) texts. LANG=C LC_ALL=C LC_MESSAGES=C export LANG LC_ALL LC_MESSAGES LOCALMODE="no" if test $# -ge 1; then if test "$1" = "--local"; then LOCALMODE="yes" fi shift fi if test "$XYMONOSSCRIPT" = ""; then XYMONOSSCRIPT="xymonclient-`uname -s | tr '[ABCDEFGHIJKLMNOPQRSTUVWXYZ/]' '[abcdefghijklmnopqrstuvwxyz_]'`.sh" fi MSGFILE="$XYMONTMP/msg.$MACHINEDOTS.txt" MSGTMPFILE="$MSGFILE.$$" LOGFETCHCFG=$XYMONTMP/logfetch.$MACHINEDOTS.cfg LOGFETCHSTATUS=$XYMONTMP/logfetch.$MACHINEDOTS.status export LOGFETCHCFG LOGFETCHSTATUS rm -f $MSGTMPFILE touch $MSGTMPFILE CLIENTVERSION="`$XYMONHOME/bin/clientupdate --level`" if test "$LOCALMODE" = "yes"; then echo "@@client#1|0|127.0.0.1|$MACHINEDOTS|$SERVEROSTYPE" >> $MSGTMPFILE fi echo "client $MACHINE.$SERVEROSTYPE $CONFIGCLASS" >> $MSGTMPFILE $XYMONHOME/bin/$XYMONOSSCRIPT >> $MSGTMPFILE # logfiles if test -f $LOGFETCHCFG then $XYMONHOME/bin/logfetch $LOGFETCHCFG $LOGFETCHSTATUS >>$MSGTMPFILE fi # Client version echo "[clientversion]" >>$MSGTMPFILE echo "$CLIENTVERSION" >> $MSGTMPFILE # See if there are any local add-ons (must do this before checking the clock) if test -d $XYMONHOME/local; then for MODULE in $XYMONHOME/local/* do if test -x $MODULE then echo "[local:`basename $MODULE`]" >>$MSGTMPFILE $MODULE >>$MSGTMPFILE fi done fi # System clock echo "[clock]" >> $MSGTMPFILE $XYMONHOME/bin/logfetch --clock >> $MSGTMPFILE if test "$LOCALMODE" = "yes"; then echo "@@" >> $MSGTMPFILE $XYMONHOME/bin/xymond_client --local --config=$XYMONHOME/etc/localclient.cfg <$MSGTMPFILE else $XYMON $XYMSRV "@" < $MSGTMPFILE >$LOGFETCHCFG.tmp if test -f $LOGFETCHCFG.tmp then if test -s $LOGFETCHCFG.tmp then mv $LOGFETCHCFG.tmp $LOGFETCHCFG else rm -f $LOGFETCHCFG.tmp fi fi fi # Save the latest file for debugging. rm -f $MSGFILE mv $MSGTMPFILE $MSGFILE if test "$LOCALMODE" != "yes" -a -f $LOGFETCHCFG; then # Check for client updates SERVERVERSION=`grep "^clientversion:" $LOGFETCHCFG | cut -d: -f2` if test "$SERVERVERSION" != "" -a "$SERVERVERSION" != "$CLIENTVERSION"; then exec $XYMONHOME/bin/clientupdate --update=$SERVERVERSION --reexec fi fi exit 0 xymon-4.3.7/client/clientlaunch.cfg.DIST0000664000175000017500000000173411535462534017412 0ustar henrikhenrik# # The clientlaunch.cfg file is loaded by "xymonlaunch". # It controls which of the Xymon client-side modules to run, # (both the main client "xymonclient.sh" and any client-side # extensions); how often, and with which parameters, options # and environment variables. # # Note: On the Xymon *server* itself, this file is normally # NOT used. Instead, both the client- and server-tasks # are controlled by the tasks.cfg file. # # msgcache is used for passive clients, that cannot connect # directly to the Xymon server. This is not the default # setup, so this task is normally disabled. [msgcache] DISABLED ENVFILE $XYMONCLIENTHOME/etc/xymonclient.cfg CMD $XYMONCLIENTHOME/bin/msgcache --no-daemon --pidfile=$XYMONCLIENTLOGS/msgcache.pid LOGFILE $XYMONCLIENTLOGS/msgcache.log # The main client task [client] ENVFILE $XYMONCLIENTHOME/etc/xymonclient.cfg CMD $XYMONCLIENTHOME/bin/xymonclient.sh @CLIENTFLAGS@ LOGFILE $XYMONCLIENTLOGS/xymonclient.log INTERVAL 5m xymon-4.3.7/client/xymonclient-aix.sh0000775000175000017500000000367311615341300017173 0ustar henrikhenrik#!/bin/sh #----------------------------------------------------------------------------# # AIX client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-aix.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who echo "[df]" # The sed stuff is to make sure lines are not split into two. df -Ik | sed -e '/^[^ ][^ ]*$/{ N s/[ ]*\n[ ]*/ / }' echo "[mount]" mount echo "[realmem]" lsattr -El sys0 -a realmem echo "[freemem]" vmstat 1 2 | tail -1 echo "[swap]" lsps -s echo "[ifconfig]" ifconfig -a echo "[route]" netstat -rn echo "[netstat]" netstat -s echo "[ports]" netstat -an | grep "^tcp" echo "[ifstat]" netstat -v echo "[ps]" # I think the -f and -l options are ignored with -o, but this works... ps -A -k -f -l -o pid,ppid,user,stat,pri,pcpu,time,etime,pmem,vsz,args # $TOP must be set, the install utility should do that for us if it exists. if test "$TOP" != "" then if test -x "$TOP" then echo "[top]" $TOP -b 20 fi fi # vmstat nohup sh -c "vmstat 300 2 1>$XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ $XYMONTMP/xymon_vmstat.$MACHINEDOTS" /dev/null 2>&1 & sleep 5 if test -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; then echo "[vmstat]"; cat $XYMONTMP/xymon_vmstat.$MACHINEDOTS; rm -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; fi exit xymon-4.3.7/client/msgcache.c0000664000175000017500000003406611615341300015422 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon client message cache. */ /* */ /* This acts as a local network daemon which saves incoming messages in a */ /* memory cache. */ /* */ /* A "pullclient" command receives the cache content in response. */ /* Any data provided in the "pullclient" request is saved, and passed as */ /* response to the first "client" command seen afterwards. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: msgcache.c 6712 2011-07-31 21:01:52Z storner $"; #include "config.h" #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include /* Someday I'll move to GNU Autoconf for this ... */ #endif #include #include #include #include #include #include #include #include #include #include #include #include "version.h" #include "libxymon.h" volatile int keeprunning = 1; char *client_response = NULL; /* The latest response to a "client" message */ char *logfile = NULL; int maxage = 600; /* Maximum time we will cache messages */ sender_t *serverlist = NULL; /* Who is allowed to grab our messages */ typedef struct conn_t { time_t tstamp; struct sockaddr_in caddr; enum { C_CLIENT_CLIENT, C_CLIENT_OTHER, C_SERVER } ctype; enum { C_READING, C_WRITING, C_DONE } action; int sockfd; strbuffer_t *msgbuf; int sentbytes; struct conn_t *next; } conn_t; conn_t *chead = NULL; conn_t *ctail = NULL; typedef struct msgqueue_t { time_t tstamp; strbuffer_t *msgbuf; unsigned long sentto; struct msgqueue_t *next; } msgqueue_t; msgqueue_t *qhead = NULL; msgqueue_t *qtail = NULL; void sigmisc_handler(int signum) { switch (signum) { case SIGTERM: errprintf("Caught TERM signal, terminating\n"); keeprunning = 0; break; case SIGHUP: if (logfile) { freopen(logfile, "a", stdout); freopen(logfile, "a", stderr); errprintf("Caught SIGHUP, reopening logfile\n"); } break; } } void grabdata(conn_t *conn) { int n; char buf[8192]; int pollid = 0; /* Get data from the connection socket - we know there is some */ n = read(conn->sockfd, buf, sizeof(buf)-1); if (n <= -1) { /* Read failure */ errprintf("Connection lost during read: %s\n", strerror(errno)); conn->action = C_DONE; return; } if (n > 0) { /* Got some data - store it */ buf[n] = '\0'; addtobuffer(conn->msgbuf, buf); return; } /* Done reading - process the data */ if (STRBUFLEN(conn->msgbuf) == 0) { /* No data ? We're done */ conn->action = C_DONE; return; } /* * See what kind of message this is. If it's a "pullclient" message, * save the contents of the message - this is the client configuration * that we'll return the next time a client sends us the "client" message. */ if (strncmp(STRBUF(conn->msgbuf), "pullclient", 10) == 0) { char *clientcfg; int idnum; /* Access check */ if (!oksender(serverlist, NULL, conn->caddr.sin_addr, STRBUF(conn->msgbuf))) { errprintf("Rejected pullclient request from %s\n", inet_ntoa(conn->caddr.sin_addr)); conn->action = C_DONE; return; } dbgprintf("Got pullclient request: %s\n", STRBUF(conn->msgbuf)); /* * The pollid is unique for each Xymon server. It is to allow * multiple servers to pick up the same message, for resiliance. */ idnum = atoi(STRBUF(conn->msgbuf) + 10); if ((idnum <= 0) || (idnum > 31)) { pollid = 0; } else { pollid = (1 << idnum); } conn->ctype = C_SERVER; conn->action = C_WRITING; /* Save any client config sent to us */ clientcfg = strchr(STRBUF(conn->msgbuf), '\n'); if (clientcfg) { clientcfg++; if (client_response) xfree(client_response); client_response = strdup(clientcfg); dbgprintf("Saved client response: %s\n", client_response); } } else if (strncmp(STRBUF(conn->msgbuf), "client ", 7) == 0) { /* * Got a "client" message. Return the client-response saved from * earlier, if there is any. If not, then we're done. */ conn->ctype = C_CLIENT_CLIENT; conn->action = (client_response ? C_WRITING : C_DONE); } else { /* Message from a client, but not the "client" message. So no response. */ conn->ctype = C_CLIENT_OTHER; conn->action = C_DONE; } /* * Messages we receive from clients are stored on our outbound queue. * If it's a local "client" message, respond with the queued response * from the Xymon server. Other client messages get no response. * * Server messages get our outbound queue back in response. */ if (conn->ctype != C_SERVER) { /* Messages from clients go on the outbound queue */ msgqueue_t *newq = calloc(1, sizeof(msgqueue_t)); dbgprintf("Queuing outbound message\n"); newq->tstamp = conn->tstamp; newq->msgbuf = conn->msgbuf; conn->msgbuf = NULL; if (qtail) { qtail->next = newq; qtail = newq; } else { qhead = qtail = newq; } if ((conn->ctype == C_CLIENT_CLIENT) && (conn->action == C_WRITING)) { /* Send the response back to the client */ conn->msgbuf = newstrbuffer(0); addtobuffer(conn->msgbuf, client_response); /* * Dont drop the client response data. If for some reason * the "client" request is repeated, he should still get * the right answer that we have. */ } } else { /* A server has asked us for our list of messages */ time_t now = getcurrenttime(NULL); msgqueue_t *mwalk; if (!qhead) { /* No queued messages */ conn->action = C_DONE; } else { /* Build a message of all the queued data */ clearstrbuffer(conn->msgbuf); /* Index line first */ for (mwalk = qhead; (mwalk); mwalk = mwalk->next) { if ((mwalk->sentto & pollid) == 0) { char idx[20]; sprintf(idx, "%d:%ld ", STRBUFLEN(mwalk->msgbuf), (long)(now - mwalk->tstamp)); addtobuffer(conn->msgbuf, idx); } } if (STRBUFLEN(conn->msgbuf) > 0) addtobuffer(conn->msgbuf, "\n"); /* Then the stream of messages */ for (mwalk = qhead; (mwalk); mwalk = mwalk->next) { if ((mwalk->sentto & pollid) == 0) { if (pollid) mwalk->sentto |= pollid; addtostrbuffer(conn->msgbuf, mwalk->msgbuf); } } if (STRBUFLEN(conn->msgbuf) == 0) { /* No data for this server */ conn->action = C_DONE; } } } } void senddata(conn_t *conn) { int n, togo; char *startp; /* Send data on the connection socket */ togo = STRBUFLEN(conn->msgbuf) - conn->sentbytes; startp = STRBUF(conn->msgbuf) + conn->sentbytes; n = write(conn->sockfd, startp, togo); if (n <= -1) { /* Write failure */ errprintf("Connection lost during write to %s\n", inet_ntoa(conn->caddr.sin_addr)); conn->action = C_DONE; } else { conn->sentbytes += n; if (conn->sentbytes == STRBUFLEN(conn->msgbuf)) conn->action = C_DONE; } } int main(int argc, char *argv[]) { int daemonize = 1; int listenq = 10; char *pidfile = "msgcache.pid"; int lsocket; struct sockaddr_in laddr; struct sigaction sa; int opt; /* Dont save the output from errprintf() */ save_errbuf = 0; memset(&laddr, 0, sizeof(laddr)); inet_aton("0.0.0.0", (struct in_addr *) &laddr.sin_addr.s_addr); laddr.sin_port = htons(1984); laddr.sin_family = AF_INET; for (opt=1; (opt < argc); opt++) { if (argnmatch(argv[opt], "--listen=")) { char *locaddr, *p; int locport; locaddr = strchr(argv[opt], '=')+1; p = strchr(locaddr, ':'); if (p) { locport = atoi(p+1); *p = '\0'; } else locport = 1984; memset(&laddr, 0, sizeof(laddr)); laddr.sin_port = htons(locport); laddr.sin_family = AF_INET; if (inet_aton(locaddr, (struct in_addr *) &laddr.sin_addr.s_addr) == 0) { errprintf("Invalid listen address %s\n", locaddr); return 1; } } else if (argnmatch(argv[opt], "--server=")) { /* Who is allowed to fetch cached messages */ char *p = strchr(argv[opt], '='); serverlist = getsenderlist(p+1); } else if (argnmatch(argv[opt], "--max-age=")) { char *p = strchr(argv[opt], '='); maxage = atoi(p+1); } else if (argnmatch(argv[opt], "--lqueue=")) { char *p = strchr(argv[opt], '='); listenq = atoi(p+1); } else if (strcmp(argv[opt], "--daemon") == 0) { daemonize = 1; } else if (strcmp(argv[opt], "--no-daemon") == 0) { daemonize = 0; } else if (argnmatch(argv[opt], "--pidfile=")) { char *p = strchr(argv[opt], '='); pidfile = strdup(p+1); } else if (argnmatch(argv[opt], "--logfile=")) { char *p = strchr(argv[opt], '='); logfile = strdup(p+1); } else if (strcmp(argv[opt], "--debug") == 0) { debug = 1; } else if (strcmp(argv[opt], "--version") == 0) { printf("xymonproxy version %s\n", VERSION); return 0; } } /* Set up a socket to listen for new connections */ lsocket = socket(AF_INET, SOCK_STREAM, 0); if (lsocket == -1) { errprintf("Cannot create listen socket (%s)\n", strerror(errno)); return 1; } opt = 1; setsockopt(lsocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); fcntl(lsocket, F_SETFL, O_NONBLOCK); if (bind(lsocket, (struct sockaddr *)&laddr, sizeof(laddr)) == -1) { errprintf("Cannot bind to listen socket (%s)\n", strerror(errno)); return 1; } if (listen(lsocket, listenq) == -1) { errprintf("Cannot listen (%s)\n", strerror(errno)); return 1; } /* Redirect logging to the logfile, if requested */ if (logfile) { freopen(logfile, "a", stdout); freopen(logfile, "a", stderr); } errprintf("Xymon msgcache version %s starting\n", VERSION); errprintf("Listening on %s:%d\n", inet_ntoa(laddr.sin_addr), ntohs(laddr.sin_port)); if (daemonize) { pid_t childpid; freopen("/dev/null", "a", stdin); /* Become a daemon */ childpid = fork(); if (childpid < 0) { /* Fork failed */ errprintf("Could not fork\n"); exit(1); } else if (childpid > 0) { /* Parent - save PID and exit */ FILE *fd = fopen(pidfile, "w"); if (fd) { fprintf(fd, "%d\n", (int)childpid); fclose(fd); } exit(0); } /* Child (daemon) continues here */ setsid(); } setup_signalhandler("msgcache"); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigmisc_handler; sigaction(SIGHUP, &sa, NULL); sigaction(SIGTERM, &sa, NULL); do { fd_set fdread, fdwrite; int maxfd; int n; conn_t *cwalk, *cprev; msgqueue_t *qwalk, *qprev; time_t mintstamp; /* Remove any finished connections */ cwalk = chead; cprev = NULL; while (cwalk) { conn_t *zombie; if (cwalk->action != C_DONE) { cprev = cwalk; cwalk = cwalk->next; continue; } /* Close the socket */ close(cwalk->sockfd); zombie = cwalk; if (cprev == NULL) { chead = zombie->next; cwalk = chead; cprev = NULL; } else { cprev->next = zombie->next; cwalk = zombie->next; } freestrbuffer(zombie->msgbuf); xfree(zombie); } ctail = chead; if (ctail) { while (ctail->next) ctail = ctail->next; } /* Remove expired messages */ qwalk = qhead; qprev = NULL; mintstamp = getcurrenttime(NULL) - maxage; while (qwalk) { msgqueue_t *zombie; if (qwalk->tstamp > mintstamp) { /* Hasn't expired yet */ qprev = qwalk; qwalk = qwalk->next; continue; } zombie = qwalk; if (qprev == NULL) { qhead = zombie->next; qwalk = qhead; qprev = NULL; } else { qprev->next = zombie->next; qwalk = zombie->next; } freestrbuffer(zombie->msgbuf); xfree(zombie); } qtail = qhead; if (qtail) { while (qtail->next) qtail = qtail->next; } /* Now we're ready to handle some data */ FD_ZERO(&fdread); FD_ZERO(&fdwrite); /* Add the listen socket */ FD_SET(lsocket, &fdread); maxfd = lsocket; for (cwalk = chead; (cwalk); cwalk = cwalk->next) { switch (cwalk->action) { case C_READING: FD_SET(cwalk->sockfd, &fdread); if (cwalk->sockfd > maxfd) maxfd = cwalk->sockfd; break; case C_WRITING: FD_SET(cwalk->sockfd, &fdwrite); if (cwalk->sockfd > maxfd) maxfd = cwalk->sockfd; break; case C_DONE: break; } } n = select(maxfd+1, &fdread, &fdwrite, NULL, NULL); if (n < 0) { if (errno == EINTR) continue; errprintf("select failed: %s\n", strerror(errno)); return 0; } if (n == 0) continue; /* Timeout */ for (cwalk = chead; (cwalk); cwalk = cwalk->next) { switch (cwalk->action) { case C_READING: if (FD_ISSET(cwalk->sockfd, &fdread)) grabdata(cwalk); break; case C_WRITING: if (FD_ISSET(cwalk->sockfd, &fdwrite)) senddata(cwalk); break; case C_DONE: break; } } if (FD_ISSET(lsocket, &fdread)) { /* New incoming connection */ conn_t *newconn; int caddrsize; dbgprintf("New connection\n"); newconn = calloc(1, sizeof(conn_t)); caddrsize = sizeof(newconn->caddr); newconn->sockfd = accept(lsocket, (struct sockaddr *)&newconn->caddr, &caddrsize); if (newconn->sockfd == -1) { /* accept() failure. Yes, it does happen! */ dbgprintf("accept failure, ignoring connection (%s)\n", strerror(errno)); xfree(newconn); newconn = NULL; } else { fcntl(newconn->sockfd, F_SETFL, O_NONBLOCK); newconn->action = C_READING; newconn->msgbuf = newstrbuffer(0); newconn->tstamp = getcurrenttime(NULL); } if (newconn) { if (ctail) { ctail->next = newconn; ctail = newconn; } else chead = ctail = newconn; } } } while (keeprunning); if (pidfile) unlink(pidfile); return 0; } xymon-4.3.7/client/orcaxymon.c0000664000175000017500000000543711615341300015667 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon ORCA data collector. */ /* This tool grabs the last reading from an ORCA logfile and formats it in */ /* NAME:VALUE format for the client message. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: orcaxymon.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include "libxymon.h" int main(int argc, char *argv[]) { time_t now; char *prefix = NULL, *machinename = NULL; char datestr[12], fn[PATH_MAX]; int i; FILE *fd; char headerline[32768]; char vals[32768]; int gotvals = 0; char *hp, *hdr, *vp, *val; char msgline[4096]; strbuffer_t *msg; machinename = xgetenv("MACHINE"); for (i=1; (i < argc); i++) { if (strncmp(argv[i], "--orca=", 7) == 0) { prefix = argv[i]+7; } else if (strncmp(argv[i], "--machine=", 10) == 0) { machinename = argv[i]+10; } else if (strcmp(argv[i], "--debug") == 0) { debug = dontsendmessages = 1; } } if (!prefix || !machinename) return 0; /* * ORCA logfiles are names PREFIX-%Y-%m-%d-XXX where XXX is a * number starting at 0 and increasing whenever the columns * change. * We will look for the first 20 index numbers only. */ now = getcurrenttime(NULL); strftime(datestr, sizeof(datestr), "%Y-%m-%d", localtime(&now)); i = 0; fd = NULL; while ((i < 20) && !fd) { snprintf(fn, sizeof(fn), "%s-%s-%03d", prefix, datestr, i); fd = fopen(fn, "r"); } if (!fd) return 1; /* Grab the header line, and the last logfile entry. */ if (fgets(headerline, sizeof(headerline), fd)) { while (fgets(vals, sizeof(vals), fd)) gotvals = 1; } fclose(fd); msg = newstrbuffer(0); sprintf(msgline, "data %s.orca\n", machinename); addtobuffer(msg, msgline); /* Match headers and values. */ hdr = strtok_r(headerline, " \t\n", &hp); val = strtok_r(vals, " \t\n", &vp); while (hdr && val) { sprintf(msgline, "%s:%s\n", hdr, val); addtobuffer(msg, msgline); hdr = strtok_r(NULL, " \t\n", &hp); val = strtok_r(NULL, " \t\n", &vp); } sendmessage(STRBUF(msg), NULL, XYMON_TIMEOUT, NULL); return 0; } xymon-4.3.7/client/clientupdate.c0000664000175000017500000002114411615341300016322 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon client update tool. */ /* */ /* This tool is used to fetch the current client version from the config-file */ /* saved in etc/clientversion.cfg. The client script compares this with the */ /* current version on the server, and if they do not match then this utility */ /* is run to fetch the new version from the server and unpack it via "tar". */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: clientupdate.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include "libxymon.h" #define CLIENTVERSIONFILE "etc/clientversion.cfg" #define INPROGRESSFILE "tmp/.inprogress.update" void cleanup(char *inprogressfn, char *selffn) { /* Remove temporary- and lock-files */ unlink(inprogressfn); if (selffn) unlink(selffn); } int main(int argc, char *argv[]) { int argi; char *versionfn, *inprogressfn; FILE *versionfd, *tarpipefd; char version[1024]; char *newversion = NULL; char *newverreq; char *updateparam = NULL; int removeself = 0; int talkstat = 0; sendreturn_t *sres; #ifdef BIG_SECURITY_HOLE /* Immediately drop all root privs, we'll regain them later when needed */ drop_root(); #else /* We WILL not run as suid-root. */ drop_root_and_removesuid(argv[0]); #endif versionfn = (char *)malloc(strlen(xgetenv("XYMONHOME")) + strlen(CLIENTVERSIONFILE) + 2); sprintf(versionfn, "%s/%s", xgetenv("XYMONHOME"), CLIENTVERSIONFILE); inprogressfn = (char *)malloc(strlen(xgetenv("XYMONHOME")) + strlen(INPROGRESSFILE) + 2); sprintf(inprogressfn, "%s/%s", xgetenv("XYMONHOME"), INPROGRESSFILE); versionfd = fopen(versionfn, "r"); if (versionfd) { char *p; fgets(version, sizeof(version), versionfd); p = strchr(version, '\n'); if (p) *p = '\0'; fclose(versionfd); } else { *version = '\0'; } if (chdir(xgetenv("XYMONHOME")) != 0) { errprintf("Cannot chdir to XYMONHOME\n"); return 1; } for (argi=1; (argi < argc); argi++) { if (strcmp(argv[argi], "--level") == 0) { /* For checking what version we're at */ printf("%s\n", version); return 0; } else if (strcmp(argv[argi], "--reexec") == 0) { /* * First step of the update procedure. * * To avoid problems with unpacking a new clientupdate * on top of the running one (some tar's will abort * if they try this), copy ourself to a temp. file and * re-exec it to carry out the update. */ char tmpfn[PATH_MAX]; char *srcfn; FILE *tmpfd, *srcfd; unsigned char buf[8192]; long n; struct stat st; int cperr; if (!updateparam) { errprintf("clientupdate --reexec called with no update version\n"); return 1; } if ( (stat(inprogressfn, &st) == 0) && ((getcurrenttime(NULL) - st.st_mtime) < 3600) ) { errprintf("Found update in progress or failed update (started %ld minutes ago)\n", (long) (getcurrenttime(NULL)-st.st_mtime)/60); return 1; } unlink(inprogressfn); tmpfd = fopen(inprogressfn, "w"); if (tmpfd) fclose(tmpfd); /* Copy the executable */ srcfn = argv[0]; srcfd = fopen(srcfn, "r"); cperr = errno; sprintf(tmpfn, "%s/.update.%s.%ld.tmp", xgetenv("XYMONTMP"), xgetenv("MACHINEDOTS"), (long)getcurrenttime(NULL)); dbgprintf("Starting update by copying %s to %s\n", srcfn, tmpfn); unlink(tmpfn); /* To avoid symlink attacks */ if (srcfd) { tmpfd = fopen(tmpfn, "w"); cperr = errno; } if (!srcfd || !tmpfd) { errprintf("Cannot copy executable: %s\n", strerror(cperr)); return 1; } while ((n = fread(buf, 1, sizeof(buf), srcfd)) > 0) fwrite(buf, 1, n, tmpfd); fclose(srcfd); fclose(tmpfd); /* Make sure the temp. binary has execute permissions set */ chmod(tmpfn, S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP); /* * Set the temp. executable suid-root, and exec() it. * If get_root() fails (because clientupdate was installed * without suid-root privs), just carry on and do what we * can without root privs. (It basically just means that * logfetch() and clientupdate() will continue to run without * root privs). */ #ifdef BIG_SECURITY_HOLE get_root(); chown(tmpfn, 0, getgid()); chmod(tmpfn, S_ISUID|S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP); drop_root(); #endif /* Run the temp. executable */ dbgprintf("Running command '%s %s ..remove-self'\n", tmpfn, updateparam); execl(tmpfn, tmpfn, updateparam, "--remove-self", (char *)NULL); /* We should never go here */ errprintf("exec() failed to launch update: %s\n", strerror(errno)); return 1; } else if (strncmp(argv[argi], "--update=", 9) == 0) { newversion = strdup(argv[argi]+9); updateparam = argv[argi]; } else if (strcmp(argv[argi], "--remove-self") == 0) { removeself = 1; } else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (strcmp(argv[argi], "--suid-setup") == 0) { /* * Final step of the update procedure. * * Become root to setup suid-root privs on utils that need it. * Note: If get_root() fails, we're left with normal user privileges. That is * OK, because that is how the client was installed originally, then. */ #ifdef BIG_SECURITY_HOLE get_root(); chown("bin/logfetch", 0, getgid()); chmod("bin/logfetch", S_ISUID|S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP); drop_root(); #endif return 0; } } if (!newversion) { errprintf("No new version string!\n"); cleanup(inprogressfn, (removeself ? argv[0] : NULL)); return 1; } /* Update to version "newversion" */ dbgprintf("Opening pipe to 'tar'\n"); tarpipefd = popen("tar xf -", "w"); if (tarpipefd == NULL) { errprintf("Cannot launch 'tar xf -': %s\n", strerror(errno)); cleanup(inprogressfn, (removeself ? argv[0] : NULL)); return 1; } sres = newsendreturnbuf(1, tarpipefd); newverreq = (char *)malloc(100+strlen(newversion)); sprintf(newverreq, "download %s.tar", newversion); dbgprintf("Sending command to Xymon: %s\n", newverreq); if ((talkstat = sendmessage(newverreq, NULL, XYMON_TIMEOUT, sres)) != XYMONSEND_OK) { errprintf("Cannot fetch new client tarfile: Status %d\n", talkstat); cleanup(inprogressfn, (removeself ? argv[0] : NULL)); freesendreturnbuf(sres); return 1; } else { dbgprintf("Download command completed OK\n"); freesendreturnbuf(sres); } dbgprintf("Closing tar pipe\n"); if ((talkstat = pclose(tarpipefd)) != 0) { errprintf("Upgrade failed, tar exited with status %d\n", talkstat); cleanup(inprogressfn, (removeself ? argv[0] : NULL)); return 1; } else { dbgprintf("tar pipe exited with status 0 (OK)\n"); } /* Create the new version file */ dbgprintf("Creating new version file %s with version %s\n", versionfn, newversion); unlink(versionfn); versionfd = fopen(versionfn, "w"); if (versionfd) { fprintf(versionfd, "%s", newversion); fclose(versionfd); } else { errprintf("Cannot create version file: %s\n", strerror(errno)); } /* Make sure these have execute permissions */ dbgprintf("Setting execute permissions on xymonclient.sh and clientupdate tools\n"); chmod("bin/xymonclient.sh", S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP); chmod("bin/clientupdate", S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP); /* * Become root to setup suid-root privs on the new clientupdate util. * Note: If get_root() fails, we're left with normal user privileges. That is * OK, because that is how the client was installed originally, then. */ #ifdef BIG_SECURITY_HOLE get_root(); chown("bin/clientupdate", 0, getgid()); chmod("bin/clientupdate", S_ISUID|S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP); drop_root(); #endif dbgprintf("Cleaning up after update\n"); cleanup(inprogressfn, (removeself ? argv[0] : NULL)); /* * Exec the new client-update utility to fix suid-root permissions on * the new files. */ execl("bin/clientupdate", "bin/clientupdate", "--suid-setup", (char *)NULL); /* We should never go here */ errprintf("exec() of clientupdate --suid-setup failed: %s\n", strerror(errno)); return 0; } xymon-4.3.7/client/logfetch.c0000664000175000017500000006362511615341300015446 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon client logfile collection tool. */ /* This tool retrieves data from logfiles. If run continuously, it will pick */ /* out the data stored in the logfile over the past 6 runs (30 minutes with */ /* the default Xymon client polling frequency) and send these data to stdout */ /* for inclusion in the Xymon "client" message. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: logfetch.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include /* Some systems do not have the S_ISSOCK macro for stat() */ #ifdef SCO_SV #include #define S_ISSOCK(m) (((m) & S_IFMT) == C_ISSOCK) #endif #include "libxymon.h" /* Is it ok for these to be hardcoded ? */ #define MAXCHECK 102400 /* When starting, dont look at more than 100 KB of data */ #define MAXMINUTES 30 #define POSCOUNT ((MAXMINUTES / 5) + 1) #define LINES_AROUND_TRIGGER 5 typedef enum { C_NONE, C_LOG, C_FILE, C_DIR, C_COUNT } checktype_t; typedef struct logdef_t { #ifdef _LARGEFILE_SOURCE off_t lastpos[POSCOUNT]; off_t maxbytes; #else long lastpos[POSCOUNT]; long maxbytes; #endif char **trigger; int triggercount; char **ignore; int ignorecount; } logdef_t; typedef struct filedef_t { int domd5, dosha1, dormd160; } filedef_t; typedef struct countdef_t { int patterncount; char **patternnames; char **patterns; int *counts; } countdef_t; typedef struct checkdef_t { char *filename; checktype_t checktype; struct checkdef_t *next; union { logdef_t logcheck; filedef_t filecheck; countdef_t countcheck; } check; } checkdef_t; checkdef_t *checklist = NULL; FILE *fileopen(char *filename, int *err) { /* Open a file */ FILE *fd; #ifdef BIG_SECURITY_HOLE get_root(); #endif fd = fopen(filename, "r"); if (err) *err = errno; #ifdef BIG_SECURITY_HOLE drop_root(); #endif return fd; } char *logdata(char *filename, logdef_t *logdef) { static char *buf = NULL; char *startpos, *fillpos, *triggerstartpos, *triggerendpos; FILE *fd; struct stat st; size_t bytesread, bytesleft; int openerr, i, status, triggerlinecount, done; char *linepos[2*LINES_AROUND_TRIGGER+1]; int lpidx; regex_t *ignexpr = NULL, *trigexpr = NULL; #ifdef _LARGEFILE_SOURCE off_t bufsz; #else long bufsz; #endif if (buf) free(buf); buf = NULL; fd = fileopen(filename, &openerr); if (fd == NULL) { buf = (char *)malloc(1024 + strlen(filename)); sprintf(buf, "Cannot open logfile %s : %s\n", filename, strerror(openerr)); return buf; } /* * See how large the file is, and decide where to start reading. * Save the last POSCOUNT positions so we can scrap 5 minutes of data * from one run to the next. */ fstat(fileno(fd), &st); if ((st.st_size < logdef->lastpos[0]) || (st.st_size < logdef->lastpos[6])) { /* * Logfile shrank - probably it was rotated. * Start from beginning of file. */ for (i=0; (i < 7); i++) logdef->lastpos[i] = 0; } /* Go to the position we were at 6 times ago (corresponds to 30 minutes) */ #ifdef _LARGEFILE_SOURCE fseeko(fd, logdef->lastpos[6], SEEK_SET); bufsz = st.st_size - ftello(fd); if (bufsz > MAXCHECK) { /* * Too much data for us. We have to skip some of the old data. */ logdef->lastpos[6] = st.st_size - MAXCHECK; fseeko(fd, logdef->lastpos[6], SEEK_SET); bufsz = st.st_size - ftello(fd); } #else fseek(fd, logdef->lastpos[6], SEEK_SET); bufsz = st.st_size - ftell(fd); if (bufsz > MAXCHECK) { /* * Too much data for us. We have to skip some of the old data. */ logdef->lastpos[6] = st.st_size - MAXCHECK; fseek(fd, logdef->lastpos[6], SEEK_SET); bufsz = st.st_size - ftell(fd); } #endif /* Shift position markers one down for the next round */ for (i=6; (i > 0); i--) logdef->lastpos[i] = logdef->lastpos[i-1]; logdef->lastpos[0] = st.st_size; /* * Get our read buffer. * * NB: fgets() need some extra room in the input buffer. * If it is missing, we will never detect end-of-file * because fgets() will read 0 bytes, but having read that * it still hasnt reached end-of-file status. * At least, on some platforms (Solaris, FreeBSD). */ bufsz += 1023; startpos = buf = (char *)malloc(bufsz + 1); if (buf == NULL) { /* Couldnt allocate the buffer */ return "Out of memory"; } /* Compile the regex patterns */ if (logdef->ignorecount) { int i; ignexpr = (regex_t *) malloc(logdef->ignorecount * sizeof(regex_t)); for (i=0; (i < logdef->ignorecount); i++) { status = regcomp(&ignexpr[i], logdef->ignore[i], REG_EXTENDED|REG_ICASE|REG_NOSUB); if (status != 0) logdef->ignore[i] = NULL; } } if (logdef->triggercount) { int i; trigexpr = (regex_t *) malloc(logdef->triggercount * sizeof(regex_t)); for (i=0; (i < logdef->triggercount); i++) { status = regcomp(&trigexpr[i], logdef->trigger[i], REG_EXTENDED|REG_ICASE|REG_NOSUB); if (status != 0) logdef->trigger[i] = NULL; } } triggerstartpos = triggerendpos = NULL; triggerlinecount = 0; memset(linepos, 0, sizeof(linepos)); lpidx = 0; /* * Read data. * Discard the ignored lines as we go. * Remember the last trigger line we see. */ fillpos = buf; bytesleft = bufsz; done = 0; while (!ferror(fd) && (bytesleft > 0) && !done && (fgets(fillpos, bytesleft, fd) != NULL)) { if (*fillpos == '\0') { /* * fgets() can return an empty buffer without flagging * end-of-file. It should not happen anymore now that * we have extended the buffer to have room for the * terminating \0 byte, but if it does then we will * catch it here. */ done = 1; continue; } /* Check ignore pattern */ if (logdef->ignorecount) { int i, match = 0; for (i=0; ((i < logdef->ignorecount) && !match); i++) { match = (regexec(&ignexpr[i], fillpos, 0, NULL, 0) == 0); } if (match) continue; } linepos[lpidx] = fillpos; /* See if this is a trigger line */ if (logdef->triggercount) { int i, match = 0; for (i=0; ((i < logdef->ignorecount) && !match); i++) { match = (regexec(&trigexpr[i], fillpos, 0, NULL, 0) == 0); } if (match) { int sidx; sidx = lpidx - LINES_AROUND_TRIGGER; if (sidx < 0) sidx += (2*LINES_AROUND_TRIGGER + 1); triggerstartpos = linepos[sidx]; if (!triggerstartpos) triggerstartpos = buf; triggerlinecount = LINES_AROUND_TRIGGER; } } /* We want this line */ lpidx = ((lpidx + 1) % (2*LINES_AROUND_TRIGGER+1)); fillpos += strlen(fillpos); /* Save the current end-position if we had a trigger within the past LINES_AFTER_TRIGGER lines */ if (triggerlinecount) { triggerlinecount--; triggerendpos = fillpos; } bytesleft = (bufsz - (fillpos - buf)); } /* Was there an error reading the file? */ if (ferror(fd)) { buf = (char *)malloc(1024 + strlen(filename)); sprintf(buf, "Error while reading logfile %s : %s\n", filename, strerror(errno)); startpos = buf; goto cleanup; } bytesread = (fillpos - startpos); *(buf + bytesread) = '\0'; if (bytesread > logdef->maxbytes) { char *skiptxt = "<...SKIPPED...>\n"; /* FIXME: Must make sure to only pass complete lines back to the server */ if (triggerstartpos) { /* Skip the beginning of the data up until the trigger was found */ startpos = triggerstartpos; if ((startpos - strlen(skiptxt)) >= buf) { startpos -= strlen(skiptxt); memcpy(startpos, skiptxt, strlen(skiptxt)); } bytesread = (fillpos - startpos); /* * If it's still too big, show some lines after the trigger, and * then skip until it will fit. */ if (bytesread > logdef->maxbytes) { size_t bytesleft; bytesleft = logdef->maxbytes - (triggerendpos - startpos); if (bytesleft > 0) { char *skipend; skipend = fillpos - bytesleft; memmove(triggerendpos, skipend, bytesleft); *(triggerendpos + bytesleft) = '\0'; if (bytesleft >= strlen(skiptxt)) memcpy(triggerendpos, skiptxt, strlen(skiptxt)); bytesread = (triggerendpos - startpos) + bytesleft; } } } else { /* Just drop what is too much */ startpos += (bytesread - logdef->maxbytes); memcpy(startpos, skiptxt, strlen(skiptxt)); bytesread = logdef->maxbytes; } } /* Avoid sending a '[' as the first char on a line */ { char *p; p = startpos; while (p) { if (*p == '[') *p = '.'; p = strstr(p, "\n["); if (p) p++; } } cleanup: if (fd) fclose(fd); { int i; if (logdef->ignorecount) { for (i=0; (i < logdef->ignorecount); i++) { if (logdef->ignore[i]) regfree(&ignexpr[i]); } xfree(ignexpr); } if (logdef->triggercount) { for (i=0; (i < logdef->triggercount); i++) { if (logdef->trigger[i]) regfree(&trigexpr[i]); } xfree(trigexpr); } } return startpos; } char *ftypestr(unsigned int mode, char *symlink) { static char *result = NULL; char *s = "unknown"; if (S_ISREG(mode)) s = "file"; if (S_ISDIR(mode)) s = "directory"; if (S_ISCHR(mode)) s = "char-device"; if (S_ISBLK(mode)) s = "block-device"; if (S_ISFIFO(mode)) s = "FIFO"; if (S_ISSOCK(mode)) s = "socket"; if (symlink == NULL) return s; /* Special handling for symlinks */ if (result) free(result); result = (char *)malloc(strlen(s) + strlen(symlink) + 100); sprintf(result, "%s, symlink -> %s", s, symlink); return result; } char *fmodestr(unsigned int mode) { static char modestr[11]; if (S_ISREG(mode)) modestr[0] = '-'; else if (S_ISDIR(mode)) modestr[0] = 'd'; else if (S_ISCHR(mode)) modestr[0] = 'c'; else if (S_ISBLK(mode)) modestr[0] = 'b'; else if (S_ISFIFO(mode)) modestr[0] = 'p'; else if (S_ISLNK(mode)) modestr[0] = 'l'; else if (S_ISSOCK(mode)) modestr[0] = 's'; else modestr[0] = '?'; modestr[1] = ((mode & S_IRUSR) ? 'r' : '-'); modestr[2] = ((mode & S_IWUSR) ? 'w' : '-'); modestr[3] = ((mode & S_IXUSR) ? 'x' : '-'); modestr[4] = ((mode & S_IRGRP) ? 'r' : '-'); modestr[5] = ((mode & S_IWGRP) ? 'w' : '-'); modestr[6] = ((mode & S_IXGRP) ? 'x' : '-'); modestr[7] = ((mode & S_IROTH) ? 'r' : '-'); modestr[8] = ((mode & S_IWOTH) ? 'w' : '-'); modestr[9] = ((mode & S_IXOTH) ? 'x' : '-'); if ((mode & S_ISUID)) modestr[3] = 's'; if ((mode & S_ISGID)) modestr[6] = 's'; modestr[10] = '\0'; return modestr; } char *timestr(time_t tstamp) { static char result[20]; strftime(result, sizeof(result), "%Y/%m/%d-%H:%M:%S", localtime(&tstamp)); return result; } char *filesum(char *fn, char *dtype) { static char *result = NULL; digestctx_t *ctx; FILE *fd; unsigned char buf[8192]; int openerr, buflen; if ((ctx = digest_init(dtype)) == NULL) return ""; fd = fileopen(fn, &openerr); if (fd == NULL) return ""; while ((buflen = fread(buf, 1, sizeof(buf), fd)) > 0) digest_data(ctx, buf, buflen); fclose(fd); if (result) xfree(result); result = strdup(digest_done(ctx)); return result; } void printfiledata(FILE *fd, char *fn, int domd5, int dosha1, int dormd160) { struct stat st; struct passwd *pw; struct group *gr; int staterror; char linknam[PATH_MAX]; time_t now = getcurrenttime(NULL); *linknam = '\0'; staterror = lstat(fn, &st); if ((staterror == 0) && S_ISLNK(st.st_mode)) { int n = readlink(fn, linknam, sizeof(linknam)-1); if (n == -1) n = 0; linknam[n] = '\0'; staterror = stat(fn, &st); } if (staterror == -1) { fprintf(fd, "ERROR: %s\n", strerror(errno)); } else { char *stsizefmt; pw = getpwuid(st.st_uid); gr = getgrgid(st.st_gid); fprintf(fd, "type:%o (%s)\n", (unsigned int)(st.st_mode & S_IFMT), ftypestr(st.st_mode, (*linknam ? linknam : NULL))); fprintf(fd, "mode:%o (%s)\n", (unsigned int)(st.st_mode & (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)), fmodestr(st.st_mode)); fprintf(fd, "linkcount:%d\n", (int)st.st_nlink); fprintf(fd, "owner:%u (%s)\n", (unsigned int)st.st_uid, (pw ? pw->pw_name : "")); fprintf(fd, "group:%u (%s)\n", (unsigned int)st.st_gid, (gr ? gr->gr_name : "")); if (sizeof(st.st_size) == sizeof(long long int)) stsizefmt = "size:%lld\n"; else stsizefmt = "size:%ld\n"; fprintf(fd, stsizefmt, st.st_size); fprintf(fd, "clock:%u (%s)\n", (unsigned int)now, timestr(now)); fprintf(fd, "atime:%u (%s)\n", (unsigned int)st.st_atime, timestr(st.st_atime)); fprintf(fd, "ctime:%u (%s)\n", (unsigned int)st.st_ctime, timestr(st.st_ctime)); fprintf(fd, "mtime:%u (%s)\n", (unsigned int)st.st_mtime, timestr(st.st_mtime)); if (S_ISREG(st.st_mode)) { if (domd5) fprintf(fd, "%s\n", filesum(fn, "md5")); else if (dosha1) fprintf(fd, "%s\n", filesum(fn, "sha1")); else if (dormd160) fprintf(fd, "%s\n", filesum(fn, "rmd160")); } } fprintf(fd, "\n"); } void printdirdata(FILE *fd, char *fn) { char *ducmd; FILE *cmdfd; char *cmd; char buf[4096]; int buflen; ducmd = getenv("DU"); if (ducmd == NULL) ducmd = "du -k"; cmd = (char *)malloc(strlen(ducmd) + strlen(fn) + 10); sprintf(cmd, "%s %s 2>&1", ducmd, fn); cmdfd = popen(cmd, "r"); xfree(cmd); if (cmdfd == NULL) return; while ((buflen = fread(buf, 1, sizeof(buf), cmdfd)) > 0) fwrite(buf, 1, buflen, fd); pclose(cmdfd); fprintf(fd, "\n"); } void printcountdata(FILE *fd, checkdef_t *cfg) { int openerr, idx; FILE *logfd; regex_t *exprs; regmatch_t pmatch[1]; int *counts; char l[8192]; logfd = fileopen(cfg->filename, &openerr); if (logfd == NULL) { fprintf(fd, "ERROR: Cannot open file %s: %s\n", cfg->filename, strerror(openerr)); return; } counts = (int *)calloc(cfg->check.countcheck.patterncount, sizeof(int)); exprs = (regex_t *)calloc(cfg->check.countcheck.patterncount, sizeof(regex_t)); for (idx = 0; (idx < cfg->check.countcheck.patterncount); idx++) { int status; status = regcomp(&exprs[idx], cfg->check.countcheck.patterns[idx], REG_EXTENDED|REG_NOSUB); if (status != 0) { /* ... */ }; } while (fgets(l, sizeof(l), logfd)) { for (idx = 0; (idx < cfg->check.countcheck.patterncount); idx++) { if (regexec(&exprs[idx], l, 1, pmatch, 0) == 0) counts[idx] += 1; } } fclose(logfd); for (idx = 0; (idx < cfg->check.countcheck.patterncount); idx++) { fprintf(fd, "%s: %d\n", cfg->check.countcheck.patternnames[idx], counts[idx]); regfree(&exprs[idx]); } free(counts); free(exprs); } int loadconfig(char *cfgfn) { FILE *fd; char l[PATH_MAX + 1024]; checkdef_t *currcfg = NULL; checkdef_t *firstpipeitem = NULL; /* Config items are in the form: * log:filename:maxbytes * ignore ignore-regexp (optional) * trigger trigger-regexp (optional) * * file:filename */ fd = fopen(cfgfn, "r"); if (fd == NULL) return 1; while (fgets(l, sizeof(l), fd) != NULL) { checktype_t checktype; char *bol, *filename; int maxbytes, domd5, dosha1, dormd160; { char *p = strchr(l, '\n'); if (p) *p = '\0'; } bol = l + strspn(l, " \t"); if ((*bol == '\0') || (*bol == '#')) continue; if (strncmp(bol, "log:", 4) == 0) checktype = C_LOG; else if (strncmp(bol, "file:", 5) == 0) checktype = C_FILE; else if (strncmp(bol, "dir:", 4) == 0) checktype = C_DIR; else if (strncmp(bol, "linecount:", 10) == 0) checktype = C_COUNT; else checktype = C_NONE; if (checktype != C_NONE) { char *tok; filename = NULL; maxbytes = -1; domd5 = dosha1 = dormd160 = 0; /* Skip the initial keyword token */ tok = strtok(l, ":"); filename = strtok(NULL, ":"); switch (checktype) { case C_LOG: tok = (filename ? strtok(NULL, ":") : NULL); if (tok) maxbytes = atoi(tok); break; case C_FILE: maxbytes = 0; /* Needed to get us into the put-into-list code */ tok = (filename ? strtok(NULL, ":") : NULL); if (tok) { if (strcmp(tok, "md5") == 0) domd5 = 1; else if (strcmp(tok, "sha1") == 0) dosha1 = 1; else if (strcmp(tok, "rmd160") == 0) dormd160 = 1; } break; case C_DIR: maxbytes = 0; /* Needed to get us into the put-into-list code */ break; case C_COUNT: maxbytes = 0; /* Needed to get us into the put-into-list code */ break; case C_NONE: break; } if ((filename != NULL) && (maxbytes != -1)) { checkdef_t *newitem; firstpipeitem = NULL; if (*filename == '`') { /* Run the command to get filenames */ char *p; char *cmd; FILE *fd; cmd = filename+1; p = strchr(cmd, '`'); if (p) *p = '\0'; fd = popen(cmd, "r"); if (fd) { char pline[PATH_MAX+1]; while (fgets(pline, sizeof(pline), fd)) { p = pline + strcspn(pline, "\r\n"); *p = '\0'; newitem = calloc(sizeof(checkdef_t), 1); newitem->checktype = checktype; newitem->filename = strdup(pline); switch (checktype) { case C_LOG: newitem->check.logcheck.maxbytes = maxbytes; break; case C_FILE: newitem->check.filecheck.domd5 = domd5; newitem->check.filecheck.dosha1 = dosha1; newitem->check.filecheck.dormd160 = dormd160; break; case C_DIR: break; case C_COUNT: newitem->check.countcheck.patterncount = 0; newitem->check.countcheck.patternnames = calloc(1, sizeof(char *)); newitem->check.countcheck.patterns = calloc(1, sizeof(char *)); break; case C_NONE: break; } newitem->next = checklist; checklist = newitem; /* * Since we insert new items at the head of the list, * currcfg points to the first item in the list of * these log configs. firstpipeitem points to the * last item inside the list which is part of this * configuration. */ currcfg = newitem; if (!firstpipeitem) firstpipeitem = newitem; } pclose(fd); } } else { newitem = calloc(sizeof(checkdef_t), 1); newitem->filename = strdup(filename); newitem->checktype = checktype; switch (checktype) { case C_LOG: newitem->check.logcheck.maxbytes = maxbytes; break; case C_FILE: newitem->check.filecheck.domd5 = domd5; newitem->check.filecheck.dosha1 = dosha1; newitem->check.filecheck.dormd160 = dormd160; break; case C_DIR: break; case C_COUNT: newitem->check.countcheck.patterncount = 0; newitem->check.countcheck.patternnames = calloc(1, sizeof(char *)); newitem->check.countcheck.patterns = calloc(1, sizeof(char *)); break; case C_NONE: break; } newitem->next = checklist; checklist = newitem; currcfg = newitem; } } else { currcfg = NULL; firstpipeitem = NULL; } } else if (currcfg && (currcfg->checktype == C_LOG)) { if (strncmp(bol, "ignore ", 7) == 0) { char *p; p = bol + 7; p += strspn(p, " \t"); if (firstpipeitem) { /* Fill in this ignore expression on all items in this pipe set */ checkdef_t *walk = currcfg; do { walk->check.logcheck.ignorecount++; if (walk->check.logcheck.ignore == NULL) { walk->check.logcheck.ignore = (char **)malloc(sizeof(char *)); } else { walk->check.logcheck.ignore = (char **)realloc(walk->check.logcheck.ignore, walk->check.logcheck.ignorecount * sizeof(char **)); } walk->check.logcheck.ignore[walk->check.logcheck.ignorecount-1] = strdup(p); walk = walk->next; } while (walk && (walk != firstpipeitem->next)); } else { currcfg->check.logcheck.ignorecount++; if (currcfg->check.logcheck.ignore == NULL) { currcfg->check.logcheck.ignore = (char **)malloc(sizeof(char *)); } else { currcfg->check.logcheck.ignore = (char **)realloc(currcfg->check.logcheck.ignore, currcfg->check.logcheck.ignorecount * sizeof(char **)); } currcfg->check.logcheck.ignore[currcfg->check.logcheck.ignorecount-1] = strdup(p); } } else if (strncmp(bol, "trigger ", 8) == 0) { char *p; p = bol + 8; p += strspn(p, " \t"); if (firstpipeitem) { /* Fill in this trigger expression on all items in this pipe set */ checkdef_t *walk = currcfg; do { walk->check.logcheck.triggercount++; if (walk->check.logcheck.trigger == NULL) { walk->check.logcheck.trigger = (char **)malloc(sizeof(char *)); } else { walk->check.logcheck.trigger = (char **)realloc(walk->check.logcheck.trigger, walk->check.logcheck.triggercount * sizeof(char **)); } walk->check.logcheck.trigger[walk->check.logcheck.triggercount-1] = strdup(p); walk = walk->next; } while (walk && (walk != firstpipeitem->next)); } else { currcfg->check.logcheck.triggercount++; if (currcfg->check.logcheck.trigger == NULL) { currcfg->check.logcheck.trigger = (char **)malloc(sizeof(char *)); } else { currcfg->check.logcheck.trigger = (char **)realloc(currcfg->check.logcheck.trigger, currcfg->check.logcheck.triggercount * sizeof(char **)); } currcfg->check.logcheck.trigger[currcfg->check.logcheck.triggercount-1] = strdup(p); } } } else if (currcfg && (currcfg->checktype == C_FILE)) { /* Nothing */ } else if (currcfg && (currcfg->checktype == C_DIR)) { /* Nothing */ } else if (currcfg && (currcfg->checktype == C_COUNT)) { int idx; char *name, *ptn = NULL; name = strtok(l, " :"); if (name) ptn = strtok(NULL, "\n"); idx = currcfg->check.countcheck.patterncount; if (name && ptn) { currcfg->check.countcheck.patterncount += 1; currcfg->check.countcheck.patternnames = realloc(currcfg->check.countcheck.patternnames, (currcfg->check.countcheck.patterncount+1)*sizeof(char *)); currcfg->check.countcheck.patterns = realloc(currcfg->check.countcheck.patterns, (currcfg->check.countcheck.patterncount+1)*sizeof(char *)); currcfg->check.countcheck.patternnames[idx] = strdup(name); currcfg->check.countcheck.patterns[idx] = strdup(ptn); } } else if (currcfg && (currcfg->checktype == C_NONE)) { /* Nothing */ } else { currcfg = NULL; firstpipeitem = NULL; } } fclose(fd); return 0; } void loadlogstatus(char *statfn) { FILE *fd; char l[PATH_MAX + 1024]; fd = fopen(statfn, "r"); if (!fd) return; while (fgets(l, sizeof(l), fd)) { char *fn, *tok; checkdef_t *walk; int i; tok = strtok(l, ":"); if (!tok) continue; fn = tok; for (walk = checklist; (walk && ((walk->checktype != C_LOG) || (strcmp(walk->filename, fn) != 0))); walk = walk->next) ; if (!walk) continue; for (i=0; (tok && (i < POSCOUNT)); i++) { tok = strtok(NULL, ":\n"); #ifdef _LARGEFILE_SOURCE if (tok) walk->check.logcheck.lastpos[i] = (off_t)str2ll(tok, NULL); #else if (tok) walk->check.logcheck.lastpos[i] = atol(tok); #endif /* Sanity check */ if (walk->check.logcheck.lastpos[i] < 0) walk->check.logcheck.lastpos[i] = 0; } } fclose(fd); } void savelogstatus(char *statfn) { FILE *fd; checkdef_t *walk; fd = fopen(statfn, "w"); if (fd == NULL) return; for (walk = checklist; (walk); walk = walk->next) { int i; char *fmt; if (walk->checktype != C_LOG) continue; if (sizeof(walk->check.logcheck.lastpos[i]) == sizeof(long long int)) fmt = ":%lld"; else fmt = ":%ld"; fprintf(fd, "%s", walk->filename); for (i = 0; (i < POSCOUNT); i++) fprintf(fd, fmt, walk->check.logcheck.lastpos[i]); fprintf(fd, "\n"); } fclose(fd); } int main(int argc, char *argv[]) { char *cfgfn = NULL, *statfn = NULL; int i; checkdef_t *walk; #ifdef BIG_SECURITY_HOLE drop_root(); #else drop_root_and_removesuid(argv[0]); #endif for (i=1; (inext) { char *data; checkdef_t *fwalk; switch (walk->checktype) { case C_LOG: data = logdata(walk->filename, &walk->check.logcheck); fprintf(stdout, "[msgs:%s]\n", walk->filename); fprintf(stdout, "%s\n", data); /* See if there's a special "file:" entry for this logfile */ for (fwalk = checklist; (fwalk && ((fwalk->checktype != C_FILE) || (strcmp(fwalk->filename, walk->filename) != 0))); fwalk = fwalk->next) ; if (fwalk == NULL) { /* No specific file: entry, so make sure the logfile metadata is available */ fprintf(stdout, "[logfile:%s]\n", walk->filename); printfiledata(stdout, walk->filename, 0, 0, 0); } break; case C_FILE: fprintf(stdout, "[file:%s]\n", walk->filename); printfiledata(stdout, walk->filename, walk->check.filecheck.domd5, walk->check.filecheck.dosha1, walk->check.filecheck.dormd160); break; case C_DIR: fprintf(stdout, "[dir:%s]\n", walk->filename); printdirdata(stdout, walk->filename); break; case C_COUNT: fprintf(stdout, "[linecount:%s]\n", walk->filename); printcountdata(stdout, walk); break; case C_NONE: break; } } savelogstatus(statfn); return 0; } xymon-4.3.7/client/xymonclient-unixware.sh0000664000175000017500000000357311615341300020250 0ustar henrikhenrik#!/bin/sh #----------------------------------------------------------------------------# # SCO Unixware client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # Copyright (C) 2006 Charles Goyard # # Copyright (C) 2009 Buchan Milne # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $$ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who -x echo "[df]" df -P echo "[mount]" mount -v echo "[memsize]" /etc/memsize echo "[freemem]" sar -r 1 2 | tail -1 echo "[swap]" swap -l echo "[ifconfig]" ifconfig -a #echo "[ifstat]" #ifconfig -in echo "[route]" netstat -rn echo "[netstat]" netstat -s echo "[ports]" netstat -an | grep "^tcp" echo "[ps]" #ps -A -o pid,ppid,user,stime,s,pri,pcpu,time,vsz,args ps -A -o pid,ppid,user,pcpu,time,vsz,args # $TOP must be set, the install utility should do that for us if it exists. if test "$TOP" != "" then if test -x "$TOP" then echo "[top]" $TOP -b -n 1 fi fi # vmstat #nohup sh -c "vmstat 300 2 1>$XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ $XYMONTMP/xymon_vmstat.$MACHINEDOTS" /dev/null 2>&1 & #sleep 5 #if test -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; then echo "[vmstat]"; cat $XYMONTMP/xymon_vmstat.$MACHINEDOTS; rm -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; fi exit xymon-4.3.7/client/xymonclient-hp-ux.sh0000775000175000017500000000456511615341300017454 0ustar henrikhenrik#!/bin/sh # #----------------------------------------------------------------------------# # HP-UX client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-hp-ux.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who echo "[df]" # The sed stuff is to make sure lines are not split into two. df -Pk | sed -e '/^[^ ][^ ]*$/{ N s/[ ]*\n[ ]*/ / }' echo "[mount]" mount echo "[memory]" # $XYMONHOME/bin/hpux-meminfo # From Earl Flack http://lists.xymon.com/archive/2010-December/030100.html FREE=`/usr/sbin/swapinfo |grep ^memory |awk {'print $4'}` FREEREPORT=`echo $FREE / 1024 |/usr/bin/bc` TOTAL=`/usr/sbin/swapinfo |grep ^memory |awk {'print $2'}` TOTALREPORT=`echo $TOTAL / 1024 |/usr/bin/bc` echo Total:$TOTALREPORT echo Free:$FREEREPORT echo "[swapinfo]" /usr/sbin/swapinfo -tm echo "[ifconfig]" netstat -in echo "[route]" netstat -rn echo "[netstat]" netstat -s echo "[ifstat]" /usr/sbin/lanscan -p | while read PPA; do /usr/sbin/lanadmin -g mibstats $PPA; done echo "[ports]" netstat -an | grep "^tcp" echo "[ps]" UNIX95=1 ps -Ax -o pid,ppid,user,stime,state,pri,pcpu,time,vsz,args # $TOP must be set, the install utility should do that for us if it exists. if test "$TOP" != "" then if test -x "$TOP" then echo "[top]" # Cits Bogajewski 03-08-2005: redirect of top fails $TOP -d 1 -f $XYMONHOME/tmp/top.OUT cat $XYMONHOME/tmp/top.OUT rm $XYMONHOME/tmp/top.OUT fi fi # vmstat nohup sh -c "vmstat 300 2 1>$XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ $XYMONTMP/xymon_vmstat.$MACHINEDOTS" /dev/null 2>&1 & sleep 5 if test -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; then echo "[vmstat]"; cat $XYMONTMP/xymon_vmstat.$MACHINEDOTS; rm -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; fi exit xymon-4.3.7/client/xymonclient-sco_sv.sh0000775000175000017500000000347111615341300017702 0ustar henrikhenrik#!/bin/sh #----------------------------------------------------------------------------# # SCO_SV client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # Copyright (C) 2006 Charles Goyard # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-sco_sv.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who -x echo "[df]" df -Bk echo "[mount]" mount -v echo "[memsize]" /etc/memsize echo "[freemem]" sar -r 1 2 | tail -1 echo "[swap]" swap -l echo "[ifconfig]" ifconfig -a echo "[ifstat]" ifconfig -in echo "[route]" netstat -rn echo "[netstat]" netstat -s echo "[ports]" netstat -an | grep "^tcp" echo "[ps]" ps -A -o pid,ppid,user,stime,s,pri,pcpu,time,vsz,args # $TOP must be set, the install utility should do that for us if it exists. if test "$TOP" != "" then if test -x "$TOP" then echo "[top]" $TOP -b -n 1 fi fi # vmstat nohup sh -c "vmstat 300 2 1>$XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ $XYMONTMP/xymon_vmstat.$MACHINEDOTS" /dev/null 2>&1 & sleep 5 if test -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; then echo "[vmstat]"; cat $XYMONTMP/xymon_vmstat.$MACHINEDOTS; rm -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; fi exit xymon-4.3.7/client/localclient.cfg0000664000175000017500000005073711535462534016477 0ustar henrikhenrik# localclient.cfg - configuration file for a LOCAL Xymon client. # # By default, Xymon clients send raw data to the Xymon server, # which in turn converts the data into status messages. # In that case, THIS FILE IS NOT USED and you should IGNORE it. # # If you want to configure clients locally (on the server that the # client runs one), you do it here. You MUST also change the # clientlaunch.cfg file and add the "--local" option to the # command launching xymonclient.sh # # The file defines a series of rules: # UP : Changes the "cpu" status when the system has rebooted recently, # or when it has been running for too long. # LOAD : Changes the "cpu" status according to the system load. # CLOCK : Changes the "cpu" status if the client system clock is # not synchronized with the clock of the Xymon server. # DISK : Changes the "disk" status, depending on the amount of space # used of filesystems. # MEMPHYS: Changes the "memory" status, based on the percentage of real # memory used. # MEMACT : Changes the "memory" status, based on the percentage of "actual" # memory used. Note: Not all systems report an "actual" value. # MEMSWAP: Changes the "memory" status, based on the percentage of swap # space used. # PROC : Changes the "procs" status according to which processes were found # in the "ps" listing from the client. # LOG : Changes the "msgs" status according to entries in text-based logfiles. # Note: The "client-local.cfg" file controls which logfiles the client will report. # FILE : Changes the "files" status according to meta-data for files. # Note: The "client-local.cfg" file controls which files the client will report. # DIR : Changes the "files" status according to the size of a directory. # Note: The "client-local.cfg" file controls which directories the client will report. # PORT : Changes the "ports" status according to which tcp ports were found # in the "netstat" listing from the client. # DEFAULT: Set the default values that apply if no other rules match. # # All rules can be qualified so they apply only to certain hosts, or on certain # times of the day (see below). # # Each type of rule takes a number of parameters: # UP bootlimit toolonglimit # The cpu status goes yellow if the system has been up for less than # "bootlimit" time, or longer than "toolonglimit". The time is in # minutes, or you can add h/d/w for hours/days/weeks - eg. "2h" for # two hours, or "4w" for 4 weeks. # Defaults: bootlimit=1h, toolonglimit=-1 (infinite). # # LOAD warnlevel paniclevel # If the system load exceeds "warnlevel" or "paniclevel", the "cpu" # status will go yellow or red, respectively. These are decimal # numbers. # Defaults: warnlevel=5.0, paniclevel=10.0 # # CLOCK maximum-offset # If the system clock of the client differs from that of the Xymon # server by more than "maximum-offset" seconds, then the CPU status # column will go yellow. Note that the accuracy of this test is limited, # since it is affected by the time it takes a client status report to # go from the client to the Xymon server and be processed. You should # therefore allow for a few seconds (5-10) of slack when you define # your max. offset. # It is not wise to use this test, unless your servers are synchronized # to a common clock, e.g. through NTP. # # DISK filesystem warnlevel paniclevel # DISK filesystem IGNORE # If the utilization of "filesystem" is reported to exceed "warnlevel" # or "paniclevel", the "disk" status will go yellow or red, respectively. # "warnlevel" and "paniclevel" are either the percentage used, or the # space available as reported by the local "df" command on the host. # For the latter type of check, the "warnlevel" must be followed by the # letter "U", e.g. "1024U". # The special keyword "IGNORE" causes this filesystem to be ignored # completely, i.e. it will not appear in the "disk" status column and # it will not be tracked in a graph. This is useful for e.g. removable # devices, backup-disks and similar hardware. # "filesystem" is the mount-point where the filesystem is mounted, e.g. # "/usr" or "/home". A filesystem-name that begins with "%" is interpreted # as a Perl-compatible regular expression; e.g. "%^/oracle.*/" will match # any filesystem whose mountpoint begins with "/oracle". # Defaults: warnlevel=90%, paniclevel=95% # # MEMPHYS warnlevel paniclevel # MEMACT warnlevel paniclevel # MEMSWAP warnlevel paniclevel # If the memory utilization exceeds the "warnlevel" or "paniclevel", the # "memory" status will change to yellow or red, respectively. # Note: The words "PHYS", "ACT" and "SWAP" are also recognized. # Defaults: MEMPHYS warnlevel=100 paniclevel=101 (i.e. it will never go red) # MEMSWAP warnlevel=50 paniclevel=80 # MEMACT warnlevel=90 paniclevel=97 # # PROC processname minimumcount maximumcount color [TRACK=id] [TEXT=displaytext] # The "ps" listing sent by the client will be scanned for how many # processes containing "processname" are running, and this is then # matched against the min/max settings defined here. If the running # count is outside the thresholds, the color of the "procs" status # changes to "color". # To check for a process that must NOT be running: Set minimum and # maximum to 0. # # "processname" can be a simple string, in which case this string must # show up in the "ps" listing as a command. The scanner will find # a ps-listing of e.g. "/usr/sbin/cron" if you only specify "processname" # as "cron". # "processname" can also be a Perl-compatiable regular expression, e.g. # "%java.*inst[0123]" can be used to find entries in the ps-listing for # "java -Xmx512m inst2" and "java -Xmx256 inst3". In that case, # "processname" must begin with "%" followed by the reg.expression. # If "processname" contains whitespace (blanks or TAB), you must enclose # the full string in double quotes - including the "%" if you use regular # expression matching. E.g. # PROC "%xymond_channel --channel=data.*xymond_rrd" 1 1 yellow # or # PROC "java -DCLASSPATH=/opt/java/lib" 2 5 # # You can have multiple "PROC" entries for the same host, all of the # checks are merged into the "procs" status and the most severe # check defines the color of the status. # # The TRACK=id option causes the number of processes found to be recorded # in an RRD file, with "id" as part of the filename. This graph will then # appear on the "procs" page as well as on the "trends" page. Note that # "id" must be unique among the processes tracked for each host. # # The TEXT=displaytext option affects how the process appears on the # "procs" status page. By default, the process is listed with the # "processname" as identification, but if this is a regular expression # it may be a bit difficult to understand. You can then use e.g. # "TEXT=Apache" to make these processes appear with the name "Apache" # instead. # # Defaults: mincount=1, maxcount=-1 (unlimited), color="red". # Note: No processes are checked by default. # # Example: Check that "cron" is running: # PROC cron # Example: Check that at least 5 "httpd" processes are running, but # not more than 20: # PROC httpd 5 20 # # LOG filename match-pattern [COLOR=color] [IGNORE=ignore-pattern] [TEXT=displaytext] # In the "client-local.cfg" file, you can list any number of files # that the client will collect log data from. These are sent to the # Xymon server together with the other client data, and you can then # choose how to analyze the log data with LOG entries. # # ************ IMPORTANT *************** # To monitor a logfile, you *MUST* configure both client-local.cfg # and analysis.cfg. If you configure only the client-local.cfg # file, the client will collect the log data and you can view it in # the "client data" display, but it will not affect the color of the # "msgs" status. On the other hand, if you configure only the # analysis.cfg file, then there will be no log data to inspect, # and you will not see any updates of the "msgs" status either. # # "filename" is a filename or pattern. The set of files reported by # the client is matched against "filename", and if they match then # this LOG entry is processed against the data from a file. # # "match-pattern": The log data is matched against this pattern. If # there is a match, this log file causes a status change to "color". # # "ignore-pattern": The log data that matched "match-pattern" is also # matched against "ignore-pattern". If the data matches the "ignore-pattern", # this line of data does not affect the status color. In other words, # the "ignore-pattern" can be used to refine the strings which cause # a match. # Note: The "ignore-pattern" is optional. # # "color": The color which this match will trigger. # Note: "color" is optional, if omitted then "red" will be used. # # Example: Go yellow if the text "WARNING" shows up in any logfile. # LOG %.* WARNING COLOR=yellow # # Example: Go red if the text "I/O error" or "read error" appears. # LOG %/var/(adm|log)/messages %(I/O|read).error COLOR=red # # FILE filename [color] [things to check] [TRACK] # NB: The files you wish to monitor must be listed in a "file:..." # entry in the client-local.cfg file, in order for the client to # report any data about them. # # "filename" is a filename or pattern. The set of files reported by # the client is matched against "filename", and if they match then # this FILE entry is processed against the data from that file. # # [things to check] can be one or more of the following: # - "NOEXIST" triggers a warning if the file exists. By default, # a warning is triggered for files that have a FILE entry, but # which do not exist. # - "TYPE=type" where "type" is one of "file", "dir", "char", "block", # "fifo", or "socket". Triggers warning if the file is not of the # specified type. # - "OWNERID=owner" and "GROUPID=group" triggers a warning if the owner # or group does not match what is listed here. "owner" and "group" is # specified either with the numeric uid/gid, or the user/group name. # - "MODE=mode" triggers a warning if the file permissions are not # as listed. "mode" is written in the standard octal notation, e.g. # "644" for the rw-r--r-- permissions. # - "SIZEmin.size" triggers a warning it the file # size is greater than "max.size" or less than "min.size", respectively. # You can append "K" (KB), "M" (MB), "G" (GB) or "T" (TB) to the size. # If there is no such modifier, KB is assumed. # E.g. to warn if a file grows larger than 1MB (1024 KB): "SIZE<1M". # - "SIZE=size" triggers a warning it the file size is not what is listed. # - "MTIME>min.mtime" and "MTIME86400". # - "MTIME=timestamp" checks if a file was last modified at "timestamp". # "timestamp" is a unix epoch time (seconds since midnight Jan 1 1970 UTC). # - "CTIME>min.ctime", "CTIME0 MTIME<600 yellow # # Example: Check the timestamp, size and SHA-1 hash of the /bin/sh program: # FILE /bin/sh MTIME=1128514608 SIZE=645140 SHA1=5bd81afecf0eb93849a2fd9df54e8bcbe3fefd72 # # DIR directory [color] [SIZEminsize] [TRACK] # NB: The directories you wish to monitor must be listed in a "dir:..." # entry in the client-local.cfg file, in order for the client to # report any data about them. # # "directory" is a filename or pattern. The set of directories reported by # the client is matched against "directory", and if they match then # this DIR entry is processed against the data for that directory. # # "SIZEminsize" defines the size limits that the # directory must stay within. If it goes outside these limits, a warning # will trigger. Note the Xymon uses the raw number reported by the # local "du" command on the client. This is commonly KB, but it may be # disk blocks which are often 512 bytes. # # "TRACK" causes the size of this directory to be tracked in an RRD file, # and shown on the graph on the "files" display. # # PORT [LOCAL=addr] [EXLOCAL=addr] [REMOTE=addr] [EXREMOTE=addr] [STATE=state] [EXSTATE=state] [min=mincount] [max=maxcount] [col=color] [TRACK=id] [TEXT=displaytext] # The "netstat" listing sent by the client will be scanned for how many # sockets match the criteria listed. # "addr" is a (partial) address specification in the format used on # the output from netstat. This is typically "10.0.0.1:80" for the IP # 10.0.0.1, port 80. Or "*:80" for any local address, port 80. # NB: The Xymon clients normally report only the numeric data for # IP-adresses and port-numbers, so you must specify the port # number (e.g. "80") instead of the service name ("www"). # "state" causes only the sockets in the specified state to be included; # it is usually LISTEN or ESTABLISHED. # The socket count is then matched against the min/max settings defined # here. If the count is outside the thresholds, the color of the "ports" # status changes to "color". # To check for a socket that must NOT exist: Set minimum and # maximum to 0. # # "addr" and "state" can be a simple strings, in which case these string must # show up in the "netstat" at the appropriate column. # "addr" and "state" can also be a Perl-compatiable regular expression, e.g. # "LOCAL=%(:80|:443)" can be used to find entries in the netstat local port for # both http (port 80) and https (port 443). In that case, portname or state must # begin with "%" followed by the reg.expression. # # The TRACK=id option causes the number of sockets found to be recorded # in an RRD file, with "id" as part of the filename. This graph will then # appear on the "ports" page as well as on the "trends" page. Note that # "id" must be unique among the ports tracked for each host. # # The TEXT=displaytext option affects how the port appears on the # "ports" status page. By default, the port is listed with the # local/remote/state rules as identification, but this may be somewhat # difficult to understand. You can then use e.g. "TEXT=Secure Shell" to make # these ports appear with the name "Secure Shell" instead. # # Defaults: state="LISTEN", mincount=1, maxcount=-1 (unlimited), color="red". # Note: No ports are checked by default. # # Example: Check that there is someone listening on the https port: # PORT "LOCAL=%([.:]443)$" state=LISTEN TEXT=https # # Example: Check that at least 5 "ssh" connections are established, but # not more than 10; warn but do not error; graph the connection count: # PORT "LOCAL=%([.:]22)$" state=ESTABLISHED min=5 max=20 color=yellow TRACK=ssh "TEXT=SSH logins" # # Example: Check that ONLY ports 22, 80 and 443 are open for incoming connections: # PORT STATE=LISTEN LOCAL=%0.0.0.0[.:].* EXLOCAL=%[.:](22|80|443)$ MAX=0 "TEXT=Bad listeners" # # # To apply rules to specific hosts, you can use the "HOST=", "EXHOST=", "PAGE=" # "EXPAGE=", "CLASS=" or "EXCLASS=" qualifiers. (These act just as in the # alerts.cfg file). # # Hostnames are either a comma-separated list of hostnames (from the hosts.cfg file), # "*" to indicate "all hosts", or a Perl-compatible regular expression. # E.g. "HOST=dns.foo.com,www.foo.com" identifies two specific hosts; # "HOST=%www.*.foo.com EXHOST=www-test.foo.com" matches all hosts with a name # beginning with "www", except the "www-test" host. # "PAGE" and "EXPAGE" match the hostnames against the page on where they are # located in the hosts.cfg file, via the hosts' page/subpage/subparent # directives. This can be convenient to pick out all hosts on a specific page. # # Rules can be dependant on time-of-day, using the standard Xymon syntax # (the hosts.cfg(5) about the NKTIME parameter). E.g. "TIME=W:0800:2200" # applied to a rule will make this rule active only on week-days between # 8AM and 10PM. # # You can also associate a GROUP id with a rule. The group-id is passed to # the alert module, which can then use it to control who gets an alert when # a failure occurs. E.g. the following associates the "httpd" process check # with the "web" group, and the "sshd" check with the "admins" group: # PROC httpd 5 GROUP=web # PROC sshd 1 GROUP=admins # In the alerts.cfg file, you could then have rules like # GROUP=web # MAIL webmaster@foo.com # GROUP=admins # MAIL root@foo.com # # Qualifiers must be placed after each rule, e.g. # LOAD 8.0 12.0 HOST=db.foo.com TIME=*:0800:1600 # # If you have multiple rules that you want to apply the same qualifiers to, # you can write the qualifiers *only* on one line, followed by the rules. E.g. # HOST=%db.*.foo.com TIME=W:0800:1600 # LOAD 8.0 12.0 # DISK /db 98 100 # PROC mysqld 1 # will apply the three rules to all of the "db" hosts on week-days between 8AM # and 4PM. This can be combined with per-rule qualifiers, in which case the # per-rule qualifier overrides the general qualifier; e.g. # HOST=%.*.foo.com # LOAD 7.0 12.0 HOST=bax.foo.com # LOAD 3.0 8.0 # will result in the load-limits being 7.0/12.0 for the "bax.foo.com" host, # and 3.0/8.0 for all other foo.com hosts. # # The special DEFAULT section can modify the built-in defaults - this must # be placed at the end of the file. DEFAULT # These are the built-in defaults. UP 1h LOAD 5.0 10.0 DISK * 90 95 MEMPHYS 100 101 MEMSWAP 50 80 MEMACT 90 97 xymon-4.3.7/client/xymonclient-linux.sh0000775000175000017500000000540511615341300017544 0ustar henrikhenrik#!/bin/sh #----------------------------------------------------------------------------# # Linux client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-linux.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -rsmn echo "[osversion]" if [ -x /bin/lsb_release ]; then /bin/lsb_release -r -i -s | xargs echo /bin/lsb_release -a elif [ -x /usr/bin/lsb_release ]; then /usr/bin/lsb_release -r -i -s | xargs echo /usr/bin/lsb_release -a elif [ -f /etc/redhat-release ]; then cat /etc/redhat-release elif [ -f /etc/gentoo-release ]; then cat /etc/gentoo-release elif [ -f /etc/debian_version ]; then echo -n "Debian " cat /etc/debian_version elif [ -f /etc/S?SE-release ]; then egrep -i "^suse|^opensuse" /etc/S?SE-release elif [ -f /etc/slackware-version ]; then cat /etc/slackware-version elif [ -f /etc/mandrake-release ]; then cat /etc/mandrake-release elif [ -f /etc/fedora-release ]; then cat /etc/fedora-release elif [ -f /etc/arch-release ]; then cat /etc/arch-release fi echo "[uptime]" uptime echo "[who]" who echo "[df]" EXCLUDES=`cat /proc/filesystems | grep nodev | awk '{print $2}' | xargs echo | sed -e 's! ! -x !g'` df -Pl -x iso9660 -x $EXCLUDES | sed -e '/^[^ ][^ ]*$/{ N s/[ ]*\n[ ]*/ / }' echo "[mount]" mount echo "[free]" free echo "[ifconfig]" /sbin/ifconfig echo "[route]" netstat -rn echo "[netstat]" netstat -s echo "[ports]" # Bug in RedHat's netstat spews annoying error messages. netstat -ant 2>/dev/null echo "[ifstat]" /sbin/ifconfig # Report mdstat data if it exists if test -r /proc/mdstat; then echo "[mdstat]"; cat /proc/mdstat; fi echo "[ps]" ps -Aww -o pid,ppid,user,start,state,pri,pcpu,time,pmem,rsz,vsz,cmd # $TOP must be set, the install utility should do that for us if it exists. if test "$TOP" != "" then if test -x "$TOP" then echo "[top]" $TOP -b -n 1 fi fi # vmstat nohup sh -c "vmstat 300 2 1>$XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ $XYMONTMP/xymon_vmstat.$MACHINEDOTS" /dev/null 2>&1 & sleep 5 if test -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; then echo "[vmstat]"; cat $XYMONTMP/xymon_vmstat.$MACHINEDOTS; rm -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; fi exit xymon-4.3.7/client/netbsd-meminfo.c0000664000175000017500000000505211615341300016550 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon memory information tool for NetBSD. */ /* This tool retrieves information about the total and free RAM. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: netbsd-meminfo.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include int main(int argc, char *argv[]) { int hw_physmem[] = { CTL_HW, HW_PHYSMEM64 }; int64 physmem; int hw_pagesize[] = { CTL_HW, HW_PAGESIZE }; int pagesize; int vm_vmtotal[] = { CTL_VM, VM_METER }; struct vmtotal vmdata; size_t len; int result; int swapcount, i; struct swapent *swaplist, *s; unsigned long swaptotal, swapused; len = sizeof(physmem); result = sysctl(hw_physmem, sizeof(hw_physmem) / sizeof(*hw_physmem), &physmem, &len, NULL, 0); if (result != 0) return 1; len = sizeof(pagesize); result = sysctl(hw_pagesize, sizeof(hw_pagesize) / sizeof(*hw_pagesize), &pagesize, &len, NULL, 0); if (result != 0) return 1; len = sizeof(vmdata); result = sysctl(vm_vmtotal, sizeof(vm_vmtotal) / sizeof(*vm_vmtotal), &vmdata, &len, NULL, 0); /* Get swap statistics */ swapcount = swapctl(SWAP_NSWAP, NULL, 0); swaplist = (struct swapent *)malloc(swapcount * sizeof(struct swapent)); result = swapctl(SWAP_STATS, swaplist, swapcount); s = swaplist; swaptotal = swapused = 0; for (i = 0, s = swaplist; (i < swapcount); i++, s++) { if (s->se_flags & SWF_ENABLE) { swaptotal += s->se_nblks; swapused += s->se_inuse; } } free(swaplist); /* swap stats are in disk blocks of 512 bytes, so divide by 2 for KB and 1024 for MB */ swaptotal /= (2*1024); swapused /= (2*1024); // printf("Pagesize:%d\n", pagesize); printf("Total:%d\n", (physmem / (1024 * 1024))); printf("Free:%d\n", (pagesize / 1024)*(vmdata.t_free / 1024)); printf("Swaptotal:%lu\n", swaptotal); printf("Swapused:%lu\n", swapused); return 0; } xymon-4.3.7/client/xymonclient-openbsd.sh0000775000175000017500000000357211615341300020042 0ustar henrikhenrik#!/bin/sh #----------------------------------------------------------------------------# # OpenBSD client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-openbsd.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who echo "[df]" df -P -tnonfs,kernfs,procfs,cd9660 | sed -e '/^[^ ][^ ]*$/{ N s/[ ]*\n[ ]*/ / }' echo "[mount]" mount echo "[meminfo]" $XYMONHOME/bin/openbsd-meminfo echo "[swapctl]" /sbin/swapctl -s echo "[ifconfig]" ifconfig -A echo "[route]" netstat -rn echo "[netstat]" netstat -s echo "[ifstat]" netstat -i -b -n | egrep -v "^lo|$XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ $XYMONTMP/xymon_vmstat.$MACHINEDOTS" /dev/null 2>&1 & sleep 5 if test -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; then echo "[vmstat]"; cat $XYMONTMP/xymon_vmstat.$MACHINEDOTS; rm -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; fi exit xymon-4.3.7/client/openbsd-meminfo.c0000664000175000017500000000504711615341300016727 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon memory information tool for OpenBSD. */ /* This tool retrieves information about the total and free RAM. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: openbsd-meminfo.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include int main(int argc, char *argv[]) { int hw_physmem[] = { CTL_HW, HW_PHYSMEM }; int physmem; int hw_pagesize[] = { CTL_HW, HW_PAGESIZE }; int pagesize; int vm_vmtotal[] = { CTL_VM, VM_METER }; struct vmtotal vmdata; size_t len; int result; int swapcount, i; struct swapent *swaplist, *s; unsigned long swaptotal, swapused; len = sizeof(physmem); result = sysctl(hw_physmem, sizeof(hw_physmem) / sizeof(*hw_physmem), &physmem, &len, NULL, 0); if (result != 0) return 1; len = sizeof(pagesize); result = sysctl(hw_pagesize, sizeof(hw_pagesize) / sizeof(*hw_pagesize), &pagesize, &len, NULL, 0); if (result != 0) return 1; len = sizeof(vmdata); result = sysctl(vm_vmtotal, sizeof(vm_vmtotal) / sizeof(*vm_vmtotal), &vmdata, &len, NULL, 0); /* Get swap statistics */ swapcount = swapctl(SWAP_NSWAP, NULL, 0); swaplist = (struct swapent *)malloc(swapcount * sizeof(struct swapent)); result = swapctl(SWAP_STATS, swaplist, swapcount); s = swaplist; swaptotal = swapused = 0; for (i = 0, s = swaplist; (i < swapcount); i++, s++) { if (s->se_flags & SWF_ENABLE) { swaptotal += s->se_nblks; swapused += s->se_inuse; } } free(swaplist); /* swap stats are in disk blocks of 512 bytes, so divide by 2 for KB and 1024 for MB */ swaptotal /= (2*1024); swapused /= (2*1024); // printf("Pagesize:%d\n", pagesize); printf("Total:%d\n", (physmem / (1024 * 1024))); printf("Free:%d\n", (pagesize / 1024)*(vmdata.t_free / 1024)); printf("Swaptotal:%lu\n", swaptotal); printf("Swapused:%lu\n", swapused); return 0; } xymon-4.3.7/client/xymonclient-netbsd.sh0000775000175000017500000000357511615341300017672 0ustar henrikhenrik#!/bin/sh #----------------------------------------------------------------------------# # NetBSD client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-netbsd.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who echo "[df]" df -P -tnonfs,kernfs,procfs,cd9660,null | sed -e '/^[^ ][^ ]*$/{ N s/[ ]*\n[ ]*/ / }' echo "[mount]" mount echo "[meminfo]" $XYMONHOME/bin/netbsd-meminfo echo "[swapctl]" /sbin/swapctl -s echo "[ifconfig]" ifconfig -a echo "[route]" netstat -rn echo "[netstat]" netstat -s echo "[ifstat]" netstat -i -b -n | egrep -v "^lo|$XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ $XYMONTMP/xymon_vmstat.$MACHINEDOTS" /dev/null 2>&1 & sleep 5 if test -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; then echo "[vmstat]"; cat $XYMONTMP/xymon_vmstat.$MACHINEDOTS; rm -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; fi exit xymon-4.3.7/client/xymonclient-freebsd.sh0000775000175000017500000000405411615341300020016 0ustar henrikhenrik#!/bin/sh # #----------------------------------------------------------------------------# # FreeBSD client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-freebsd.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who echo "[df]" # The sed stuff is to make sure lines are not split into two. df -H -tnonfs,nullfs,cd9660,procfs,devfs,linprocfs,fdescfs | sed -e '/^[^ ][^ ]*$/{ N s/[ ]*\n[ ]*/ / }' echo "[mount]" mount echo "[meminfo]" $XYMONHOME/bin/freebsd-meminfo echo "[swapinfo]" swapinfo -k echo "[vmtotal]" sysctl vm.vmtotal echo "[ifconfig]" ifconfig -a echo "[route]" netstat -rn echo "[ifstat]" netstat -i -b -n | egrep -v "^lo|$XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ $XYMONTMP/xymon_vmstat.$MACHINEDOTS" /dev/null 2>&1 & sleep 5 if test -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; then echo "[vmstat]"; cat $XYMONTMP/xymon_vmstat.$MACHINEDOTS; rm -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; fi exit xymon-4.3.7/client/xymonclient.cfg.DIST0000664000175000017500000000174111535462534017310 0ustar henrikhenrik# Environment settings for the Xymon client. XYMSRV="@XYMONHOSTIP@" # IP address of the Xymon server XYMSERVERS="" # IP of multiple Xymon servers. XYMSRV must be "0.0.0.0". CONFIGCLASS="$SERVEROSTYPE" # Default configuration class for logfiles PATH="/bin:/usr/bin:/sbin:/usr/sbin:/etc" # PATH setting for the client scripts. # You normally dont need to modify anything below here XYMONDPORT="1984" # Portnumber where xymond listens XYMONHOME="$XYMONCLIENTHOME" # Directory for the Xymon client files XYMON="$XYMONHOME/bin/xymon" # The Xymon client "xymon" utility XYMONTMP="$XYMONHOME/tmp" # Where we may store temporary files. XYMONCLIENTLOGS="$XYMONHOME/logs" # Where we store the client logfiles # Compatibility settings HOBBITCLIENTHOME="$XYMONCLIENTHOME" BBDISP="$XYMSRV" BBDISPLAYS="$XYMSERVERS" BBPORT="$XYMONDPORT" BBHOME="$XYMONHOME" BB="$XYMON" BBTMP="$XYMONTMP" BBCLIENTLOGS="$XYMONCLIENTLOGS" xymon-4.3.7/client/Makefile0000664000175000017500000000637211671476413015163 0ustar henrikhenrikOSTYPE := $(shell uname -s | tr '[A-Z]' '[a-z]') #ifeq ($(OSTYPE),hpux) # EXTRATOOLS=hpux-meminfo #endif #ifeq ($(OSTYPE),hp-ux) # EXTRATOOLS=hpux-meminfo #endif ifeq ($(OSTYPE),freebsd) EXTRATOOLS=freebsd-meminfo endif ifeq ($(OSTYPE),netbsd) EXTRATOOLS=netbsd-meminfo endif ifeq ($(OSTYPE),openbsd) EXTRATOOLS=openbsd-meminfo endif PROGRAMS=xymonlaunch logfetch clientupdate orcaxymon msgcache COMMONTOOLS=xymon xymoncmd xymongrep xymoncfg xymondigest all: $(PROGRAMS) $(COMMONTOOLS) xymonclient.cfg clientlaunch.cfg $(EXTRATOOLS) xymonclient.cfg: xymonclient.cfg.DIST cat xymonclient.cfg.DIST | sed -e 's!@XYMONHOSTIP@!$(XYMONHOSTIP)!g' >xymonclient.cfg ../build/bb-commands.sh >>xymonclient.cfg clientlaunch.cfg: clientlaunch.cfg.DIST ifeq ($(LOCALCLIENT),yes) cat clientlaunch.cfg.DIST | sed -e 's!@CLIENTFLAGS@!--local!g' >clientlaunch.cfg else cat clientlaunch.cfg.DIST | sed -e 's!@CLIENTFLAGS@!!g' >clientlaunch.cfg endif logfetch: logfetch.c $(CC) $(CFLAGS) -o $@ logfetch.c ../lib/xymonclient.a $(LIBRTDEF) clientupdate: clientupdate.c $(CC) $(CFLAGS) -o $@ clientupdate.c ../lib/xymonclient.a $(NETLIBS) $(LIBRTDEF) orcaxymon: orcaxymon.c $(CC) $(CFLAGS) -o $@ orcaxymon.c ../lib/xymonclient.a $(NETLIBS) $(LIBRTDEF) msgcache: msgcache.c $(CC) $(CFLAGS) -o $@ msgcache.c ../lib/xymonclient.a $(NETLIBS) $(LIBRTDEF) hpux-meminfo: hpux-meminfo.c $(CC) -o $@ hpux-meminfo.c freebsd-meminfo: freebsd-meminfo.c $(CC) -o $@ freebsd-meminfo.c openbsd-meminfo: openbsd-meminfo.c $(CC) -o $@ openbsd-meminfo.c netbsd-meminfo: netbsd-meminfo.c $(CC) -o $@ netbsd-meminfo.c install: if test ! -d $(INSTALLROOT)$(XYMONHOME) ; then mkdir -p $(INSTALLROOT)$(XYMONHOME) ; fi if test ! -d $(INSTALLROOT)$(XYMONHOME)/bin ; then mkdir -p $(INSTALLROOT)$(XYMONHOME)/bin ; fi if test ! -d $(INSTALLROOT)$(XYMONHOME)/etc ; then mkdir -p $(INSTALLROOT)$(XYMONHOME)/etc ; fi if test ! -d $(INSTALLROOT)$(XYMONHOME)/tmp ; then mkdir -p $(INSTALLROOT)$(XYMONHOME)/tmp ; fi if test ! -d $(INSTALLROOT)$(XYMONHOME)/logs ; then mkdir -p $(INSTALLROOT)$(XYMONHOME)/logs ; fi if test ! -d $(INSTALLROOT)$(XYMONHOME)/ext ; then mkdir -p $(INSTALLROOT)$(XYMONHOME)/ext ; fi if test ! -d $(INSTALLROOT)$(XYMONHOME)/local ; then mkdir -p $(INSTALLROOT)$(XYMONHOME)/local ; fi if test ! -f $(INSTALLROOT)$(XYMONHOME)/etc/localclient.cfg ; then cp localclient.cfg $(INSTALLROOT)$(XYMONHOME)/etc/ ; fi if test ! -f $(INSTALLROOT)$(XYMONHOME)/local/README; then cp README-local $(INSTALLROOT)$(XYMONHOME)/local/README ; chmod 644 $(INSTALLROOT)$(XYMONHOME)/local/README; fi cp -fp runclient.sh $(INSTALLROOT)$(XYMONHOME) cp -fp $(PROGRAMS) xymonclient*.sh $(COMMONTOOLS) $(EXTRATOOLS) $(INSTALLROOT)$(XYMONHOME)/bin/ chmod ugo+x $(INSTALLROOT)$(XYMONHOME)/bin/* ../build/merge-sects clientlaunch.cfg $(INSTALLROOT)$(XYMONHOME)/etc/clientlaunch.cfg ../build/merge-lines xymonclient.cfg $(INSTALLROOT)$(XYMONHOME)/etc/xymonclient.cfg ifndef PKGBUILD chown -R $(XYMONUSER) $(INSTALLROOT)$(XYMONHOME) endif install-localclient: cp -fp xymond_client $(INSTALLROOT)$(XYMONHOME)/bin/ ifndef PKGBUILD chown $(XYMONUSER) $(INSTALLROOT)$(XYMONHOME)/bin/xymond_client endif clean: rm -f $(PROGRAMS) $(COMMONTOOLS) xymond_client xymonclient.cfg clientlaunch.cfg $(EXTRATOOLS) *~ xymon-4.3.7/client/hpux-meminfo.c0000664000175000017500000000244711615341300016262 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon memory information tool for HP-UX. */ /* This tool retrieves information about the total and free RAM. */ /* */ /* Copyright (C) 2005-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: hpux-meminfo.c 6712 2011-07-31 21:01:52Z storner $"; #include #include main(int argc, char *argv[]) { struct pst_static sbuf; struct pst_dynamic dbuf; unsigned long pgsizekb; unsigned long kpages; pstat_getstatic(&sbuf, sizeof(sbuf), 1, 0); pstat_getdynamic(&dbuf, sizeof(dbuf), 1, 0); pgsizekb = sbuf.page_size / 1024; kpages = dbuf.psd_free / 1024; printf("Total:%ld\n", sbuf.physical_memory/256); printf("Free:%lu\n", pgsizekb*kpages); } xymon-4.3.7/client/README-local0000664000175000017500000000116111671476413015462 0ustar henrikhenrikThis directory - the client/local/ directory - can be used to install Xymon client add-on scripts. The Xymon client will run all files in this directory that are executable, and include the output from each script in a separate section in the Xymon client message which is sent to the Xymon server. This output will have to be processed on the Xymon server; there is no default processing done by Xymon on the output from these scripts. They are merely added to the client data. If you want to install an add-on script that direcly generates a status column in Xymon, this should go in the client/ext/ directory instead. xymon-4.3.7/client/xymonclient-sunos.sh0000775000175000017500000000647511615341300017564 0ustar henrikhenrik#!/bin/sh #----------------------------------------------------------------------------# # Solaris client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-sunos.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who echo "[df]" # Bothersome, because Solaris df cannot show multiple fs-types, or exclude certain fs types. # Print the root filesystem first, with the header, and those fs's that have the same type. ROOTFSTYPE=`/bin/df -n / | awk '{print $3}'` /bin/df -F $ROOTFSTYPE -k # Then see what fs types are in use, and weed out those we dont want. FSTYPES=`/bin/df -n -l|cut -d: -f2 | awk '{print $1}'|egrep -v "^${ROOTFSTYPE}|^proc|^fd|^mntfs|^ctfs|^devfs|^objfs|^nfs|^lofs"|sort|uniq` set $FSTYPES while test "$1" != ""; do /bin/df -F $1 -k | grep -v " /var/run" | tail +2 shift done echo "[mount]" mount echo "[prtconf]" /usr/sbin/prtconf echo "[memory]" vmstat 1 2 | tail -1 echo "[swap]" /usr/sbin/swap -s echo "[swaplist]" /usr/sbin/swap -l echo "[ifconfig]" ifconfig -a echo "[route]" netstat -rn echo "[netstat]" netstat -s echo "[ports]" netstat -na -f inet -P tcp | tail +3 netstat -na -f inet6 -P tcp | tail +5 echo "[ifstat]" # Leave out the wrmsd and mac interfaces. See http://www.xymon.com/archive/2009/06/msg00204.html /usr/bin/kstat -p -s '[or]bytes64' | egrep -v 'wrsmd|mac' | sort echo "[ps]" ps -A -o pid,ppid,user,stime,s,pri,pcpu,time,pmem,rss,vsz,args # If TOP is defined, then use it. If not, fall back to the Solaris prstat command. echo "[top]" if test "$TOP" != "" -a -x "$TOP" then "$TOP" -b 20 else prstat -can 20 1 1 fi # vmstat and iostat (iostat -d provides a cpu utilisation with I/O wait number) nohup sh -c "vmstat 300 2 1>$XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ $XYMONTMP/xymon_vmstat.$MACHINEDOTS" /dev/null 2>&1 & nohup sh -c "iostat -c 300 2 1>$XYMONTMP/xymon_iostatcpu.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_iostatcpu.$MACHINEDOTS.$$ $XYMONTMP/xymon_iostatcpu.$MACHINEDOTS" /dev/null 2>&1 & nohup sh -c "iostat -dxsrP 300 2 1>$XYMONTMP/xymon_iostatdisk.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_iostatdisk.$MACHINEDOTS.$$ $XYMONTMP/xymon_iostatdisk.$MACHINEDOTS" /dev/null 2>&1 & sleep 5 if test -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; then echo "[vmstat]"; cat $XYMONTMP/xymon_vmstat.$MACHINEDOTS; rm -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; fi if test -f $XYMONTMP/xymon_iostatcpu.$MACHINEDOTS; then echo "[iostatcpu]"; cat $XYMONTMP/xymon_iostatcpu.$MACHINEDOTS; rm -f $XYMONTMP/xymon_iostatcpu.$MACHINEDOTS; fi if test -f $XYMONTMP/xymon_iostatdisk.$MACHINEDOTS; then echo "[iostatdisk]"; cat $XYMONTMP/xymon_iostatdisk.$MACHINEDOTS; rm -f $XYMONTMP/xymon_iostatdisk.$MACHINEDOTS; fi exit xymon-4.3.7/client/xymonclient-osf1.sh0000775000175000017500000000325411615341300017255 0ustar henrikhenrik#!/bin/sh #----------------------------------------------------------------------------# # OSF1 client for Xymon # # # # Copyright (C) 2005-2011 Henrik Storner # # # # This program is released under the GNU General Public License (GPL), # # version 2. See the file "COPYING" for details. # # # #----------------------------------------------------------------------------# # # $Id: xymonclient-osf1.sh 6712 2011-07-31 21:01:52Z storner $ echo "[date]" date echo "[uname]" uname -a echo "[uptime]" uptime echo "[who]" who echo "[memory]" vmstat -P echo "[swap]" swapon -s echo "[df]" df -t noprocfs | sed -e '/^[^ ][^ ]*$/{ N s/[ ]*\n[ ]*/ / }' echo "[mount]" mount echo "[ifconfig]" ifconfig -a echo "[route]" cat /etc/routes echo "[netstat]" netstat -s echo "[ports]" netstat -an|grep "^tcp" echo "[ps]" ps -ef # $TOP must be set, the install utility should do that for us if it exists. if test "$TOP" != "" then if test -x "$TOP" then echo "[top]" $TOP -b -n 1 fi fi # vmstat nohup sh -c "vmstat 300 2 1>$XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ 2>&1; mv $XYMONTMP/xymon_vmstat.$MACHINEDOTS.$$ $XYMONTMP/xymon_vmstat.$MACHINEDOTS" /dev/null 2>&1 & sleep 5 if test -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; then echo "[vmstat]"; cat $XYMONTMP/xymon_vmstat.$MACHINEDOTS; rm -f $XYMONTMP/xymon_vmstat.$MACHINEDOTS; fi exit xymon-4.3.7/xymongen/0000775000175000017500000000000011671641716014101 5ustar henrikhenrikxymon-4.3.7/xymongen/process.c0000664000175000017500000001744111615341300015712 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* This file contains to to calculate the "color" of hosts and pages, and */ /* handle summary transmission. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: process.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include "xymongen.h" #include "process.h" #include "util.h" void calc_hostcolors(char *nongreenignores) { int color, nongreencolor, criticalcolor, oldage; hostlist_t *h, *cwalk; entry_t *e; for (h = hostlistBegin(); (h); h = hostlistNext()) { color = nongreencolor = criticalcolor = 0; oldage = 1; for (e = h->hostentry->entries; (e); e = e->next) { if (e->propagate && (e->color > color)) color = e->color; oldage &= e->oldage; if (e->propagate && (e->color > nongreencolor) && (strstr(nongreenignores, e->column->listname) == NULL)) { nongreencolor = e->color; } if (e->propagate && e->alert && (e->color > criticalcolor)) { criticalcolor = e->color; } } /* Blue and clear is not propagated upwards */ if ((color == COL_CLEAR) || (color == COL_BLUE)) color = COL_GREEN; h->hostentry->color = color; h->hostentry->nongreencolor = nongreencolor; h->hostentry->criticalcolor = criticalcolor; h->hostentry->oldage = oldage; /* Need to update the clones also */ for (cwalk = h->clones; (cwalk); cwalk = cwalk->clones) { cwalk->hostentry->color = color; cwalk->hostentry->nongreencolor = nongreencolor; cwalk->hostentry->criticalcolor = criticalcolor; cwalk->hostentry->oldage = oldage; } } } void calc_pagecolors(xymongen_page_t *phead) { xymongen_page_t *p, *toppage; group_t *g; host_t *h; int color, oldage; for (toppage=phead; (toppage); toppage = toppage->next) { /* Start with the color of immediate hosts */ color = -1; oldage = 1; for (h = toppage->hosts; (h); h = h->next) { if (h->color > color) color = h->color; oldage &= h->oldage; } /* Then adjust with the color of hosts in immediate groups */ for (g = toppage->groups; (g); g = g->next) { for (h = g->hosts; (h); h = h->next) { if ((g->onlycols == NULL) && (g->exceptcols == NULL)) { /* No group-only or group-except directives - use host color */ if (h->color > color) color = h->color; oldage &= h->oldage; } else if (g->onlycols) { /* This is a group-only directive. Color must be * based on the tests included in the group-only * directive, NOT all tests present for the host. * So we need to re-calculate host color from only * the selected tests. */ entry_t *e; for (e = h->entries; (e); e = e->next) { if ( e->propagate && (e->color > color) && wantedcolumn(e->column->name, g->onlycols) ) color = e->color; oldage &= e->oldage; } /* Blue and clear is not propagated upwards */ if ((color == COL_CLEAR) || (color == COL_BLUE)) color = COL_GREEN; } else if (g->exceptcols) { /* This is a group-except directive. Color must be * based on the tests NOT included in the group-except * directive, NOT all tests present for the host. * So we need to re-calculate host color from only * the selected tests. */ entry_t *e; for (e = h->entries; (e); e = e->next) { if ( e->propagate && (e->color > color) && !wantedcolumn(e->column->name, g->exceptcols) ) color = e->color; oldage &= e->oldage; } /* Blue and clear is not propagated upwards */ if ((color == COL_CLEAR) || (color == COL_BLUE)) color = COL_GREEN; } } } /* Then adjust with the color of subpages, if any. */ /* These must be calculated first! */ if (toppage->subpages) { calc_pagecolors(toppage->subpages); } for (p = toppage->subpages; (p); p = p->next) { if (p->color > color) color = p->color; oldage &= p->oldage; } if (color == -1) { /* * If no hosts or subpages, all goes green. */ color = COL_GREEN; oldage = 1; } toppage->color = color; toppage->oldage = oldage; } } void delete_old_acks(void) { DIR *xymonacks; struct dirent *d; struct stat st; time_t now = getcurrenttime(NULL); char fn[PATH_MAX]; xymonacks = opendir(xgetenv("XYMONACKDIR")); if (!xymonacks) { errprintf("No XYMONACKDIR! Cannot cd to directory %s\n", xgetenv("XYMONACKDIR")); return; } chdir(xgetenv("XYMONACKDIR")); while ((d = readdir(xymonacks))) { strcpy(fn, d->d_name); if (strncmp(fn, "ack.", 4) == 0) { stat(fn, &st); if (S_ISREG(st.st_mode) && (st.st_mtime < now)) { unlink(fn); } } } closedir(xymonacks); } void send_summaries(summary_t *sumhead) { summary_t *s; for (s = sumhead; (s); s = s->next) { char *suburl; int summarycolor = -1; char *summsg; /* Decide which page to pick the color from for this summary. */ suburl = s->url; if (strncmp(suburl, "http://", 7) == 0) { char *p; /* Skip hostname part */ suburl += 7; /* Skip "http://" */ p = strchr(suburl, '/'); /* Find next '/' */ if (p) suburl = p; } if (strncmp(suburl, xgetenv("XYMONWEB"), strlen(xgetenv("XYMONWEB"))) == 0) suburl += strlen(xgetenv("XYMONWEB")); if (*suburl == '/') suburl++; dbgprintf("summ1: s->url=%s, suburl=%s\n", s->url, suburl); if (strcmp(suburl, "xymon.html") == 0) summarycolor = xymon_color; else if (strcmp(suburl, "index.html") == 0) summarycolor = xymon_color; else if (strcmp(suburl, "") == 0) summarycolor = xymon_color; else if (strcmp(suburl, "nongreen.html") == 0) summarycolor = nongreen_color; else if (strcmp(suburl, "critical.html") == 0) summarycolor = critical_color; else { /* * Specific page - find it in the page tree. */ char *p, *pg; xymongen_page_t *pgwalk; xymongen_page_t *sourcepg = NULL; char *urlcopy = strdup(suburl); /* * Walk the page tree */ pg = urlcopy; sourcepg = pagehead; do { p = strchr(pg, '/'); if (p) *p = '\0'; dbgprintf("Searching for page %s\n", pg); for (pgwalk = sourcepg->subpages; (pgwalk && (strcmp(pgwalk->name, pg) != 0)); pgwalk = pgwalk->next); if (pgwalk != NULL) { sourcepg = pgwalk; if (p) { *p = '/'; pg = p+1; } else pg = NULL; } else pg = NULL; } while (pg); dbgprintf("Summary search for %s found page %s (title:%s), color %d\n", suburl, sourcepg->name, sourcepg->title, sourcepg->color); summarycolor = sourcepg->color; xfree(urlcopy); } if (summarycolor == -1) { errprintf("Could not determine sourcepage for summary %s\n", s->url); summarycolor = pagehead->color; } /* Send the summary message */ summsg = (char *)malloc(1024 + strlen(s->name) + strlen(s->url) + strlen(timestamp)); sprintf(summsg, "summary summary.%s %s %s %s", s->name, colorname(summarycolor), s->url, timestamp); sendmessage(summsg, s->receiver, XYMON_TIMEOUT, NULL); xfree(summsg); } } xymon-4.3.7/xymongen/debug.c0000664000175000017500000000676411615341300015330 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* Debugging code for dumping various data in xymongen. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: debug.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include "xymongen.h" #include "util.h" void dumphosts(host_t *head, char *prefix) { host_t *h; entry_t *e; char format[512]; strcpy(format, prefix); strcat(format, "Host: %s, ip: %s, name: %s, color: %d, old: %d, anywaps: %d, wapcolor: %d, pretitle: '%s', noprop-y: %s, noprop-r: %s, noprop-p: %s, noprop-ack: %s, waps: %s\n"); for (h = head; (h); h = h->next) { printf(format, h->hostname, h->ip, textornull(h->displayname), h->color, h->oldage, h->anywaps, h->wapcolor, textornull(h->pretitle), textornull(h->nopropyellowtests), textornull(h->nopropredtests), textornull(h->noproppurpletests), textornull(h->nopropacktests), textornull(h->waps)); for (e = h->entries; (e); e = e->next) { printf("\t\t\t\t\tTest: %s, alert %d, propagate %d, state %d, age: %s, oldage: %d\n", e->column->name, e->alert, e->propagate, e->color, e->age, e->oldage); } } } void dumpgroups(group_t *head, char *prefix, char *hostprefix) { group_t *g; char format[512]; strcpy(format, prefix); strcat(format, "Group: %s, pretitle: '%s'\n"); for (g = head; (g); g = g->next) { printf(format, textornull(g->title), textornull(g->pretitle)); dumphosts(g->hosts, hostprefix); } } void dumphostlist(hostlist_t *head) { hostlist_t *h; for (h=hostlistBegin(); (h); h=hostlistNext()) { printf("Hostlist entry: Hostname %s\n", h->hostentry->hostname); } } void dumpstatelist(state_t *head) { state_t *s; for (s=head; (s); s=s->next) { printf("test:%s, state: %d, alert: %d, propagate: %d, oldage: %d, age: %s\n", s->entry->column->name, s->entry->color, s->entry->alert, s->entry->propagate, s->entry->oldage, s->entry->age); } } void dumponepagewithsubs(xymongen_page_t *curpage, char *indent) { xymongen_page_t *levelpage; char newindent[100]; char newindentextra[105]; strcpy(newindent, indent); strcat(newindent, "\t"); strcpy(newindentextra, newindent); strcat(newindentextra, " "); for (levelpage = curpage; (levelpage); levelpage = levelpage->next) { printf("%sPage: %s, color=%d, oldage=%d, title=%s, pretitle=%s\n", indent, levelpage->name, levelpage->color, levelpage->oldage, textornull(levelpage->title), textornull(levelpage->pretitle)); dumpgroups(levelpage->groups, newindent, newindentextra); dumphosts(levelpage->hosts, newindentextra); dumponepagewithsubs(levelpage->subpages, newindent); } } void dumpall(xymongen_page_t *head) { dumponepagewithsubs(head, ""); } xymon-4.3.7/xymongen/rssgen.h0000664000175000017500000000205311615341300015533 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon webpage generator tool. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __RSSGEN_H__ #define __RSSGEN_H__ extern char *rssversion; extern int rsscolorlimit; extern int nssidebarcolorlimit; extern void do_rss_header(FILE *fd); extern void do_rss_item(FILE *fd, host_t *h, entry_t *e); extern void do_rss_footer(FILE *fd); extern void do_netscape_sidebar(char *rssfilename, host_t *hosts); #endif xymon-4.3.7/xymongen/loaddata.c0000664000175000017500000004125611615341300016006 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* This file contains code to load the current Xymon status data. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: loaddata.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include "xymongen.h" #include "util.h" #include "loadlayout.h" #include "loaddata.h" int statuscount = 0; char *ignorecolumns = NULL; /* Columns that will be ignored totally */ char *dialupskin = NULL; /* XYMONSKIN used for dialup tests */ char *reverseskin = NULL; /* XYMONSKIN used for reverse tests */ time_t recentgif_limit = 86400; /* Limit for recent-gifs display, in seconds */ xymongen_col_t null_column = { "", NULL }; /* Null column */ char *purplelogfn = NULL; static FILE *purplelog = NULL; int colorcount[COL_COUNT] = { 0, }; int colorcount_noprop[COL_COUNT] = { 0, }; static time_t oldestentry; typedef struct compact_t { char *compactname; int color; time_t fileage; char *members; } compact_t; typedef struct logdata_t { /* hostname|testname|color|testflags|lastchange|logtime|validtime|acktime|disabletime|sender|cookie|1st line of message */ char *hostname; char *testname; int color; char *testflags; time_t lastchange; time_t logtime; time_t validtime; time_t acktime; time_t disabletime; char *sender; int cookie; char *msg; } logdata_t; char *parse_testflags(char *l) { char *result = NULL; char *flagstart = strstr(l, "[flags:"); if (flagstart) { char *flagend; flagstart += 7; flagend = strchr(flagstart, ']'); if (flagend) { *flagend = '\0'; result = strdup(flagstart); *flagend = ']'; } } return result; } int testflag_set(entry_t *e, char flag) { if (e->testflags) return (strchr(e->testflags, flag) != NULL); else return 0; } int unwantedcolumn(char *hostname, char *testname) { void *hinfo; char *nc, *tok; int result = 0; hinfo = hostinfo(hostname); if (!hinfo) return 1; nc = xmh_item(hinfo, XMH_NOCOLUMNS); if (!nc) return 0; nc = strdup(nc); tok = strtok(nc, ","); while (tok && (result == 0)) { if (strcmp(tok, testname) == 0) result = 1; tok = strtok(NULL, ","); } return result; } state_t *init_state(char *filename, logdata_t *log) { FILE *fd = NULL; char *p; char *hostname; char *testname; char *testnameidx; state_t *newstate; char fullfn[PATH_MAX]; host_t *host; struct stat log_st; time_t now = getcurrenttime(NULL); time_t histentry_start; int logexpired = 0; int propagating, isacked; dbgprintf("init_state(%s, %d, ...)\n", textornull(filename)); /* Ignore summary files and dot-files (this catches "." and ".." also) */ if ( (strncmp(filename, "summary.", 8) == 0) || (filename[0] == '.')) { return NULL; } if (reportstart || snapshot) { /* Dont do reports for info- and trends-columns */ p = strrchr(filename, '.'); if (p == NULL) return NULL; p++; if (strcmp(p, xgetenv("INFOCOLUMN")) == 0) return NULL; if (strcmp(p, xgetenv("TRENDSCOLUMN")) == 0) return NULL; /* * When doing reports, we are scanning the XYMONHISTDIR directory. It may * contain files that are named as a host only (no test-name). * Skip those. */ if (find_host(filename)) return NULL; } if (!reportstart && !snapshot) { hostname = strdup(log->hostname); testname = strdup(log->testname); logexpired = (log->validtime < now); } else { sprintf(fullfn, "%s/%s", xgetenv("XYMONHISTDIR"), filename); /* Check that we can access this file */ if ( (stat(fullfn, &log_st) == -1) || (!S_ISREG(log_st.st_mode)) || ((fd = fopen(fullfn, "r")) == NULL) ) { errprintf("Weird file '%s' skipped\n", fullfn); return NULL; } /* Pick out host- and test-name */ logexpired = (log_st.st_mtime < now); hostname = strdup(filename); p = strrchr(hostname, '.'); /* Skip files that have no '.' in filename */ if (p) { /* Pick out the testname ... */ *p = '\0'; p++; testname = strdup(p); /* ... and change hostname back into normal form */ for (p=hostname; (*p); p++) { if (*p == ',') *p='.'; } } else { xfree(hostname); fclose(fd); return NULL; } } /* Must do these first to get the propagation value for the statistics */ host = find_host(hostname); isacked = (log->acktime > now); propagating = checkpropagation(host, testname, log->color, isacked); /* Count all of the real columns */ if ( (strcmp(testname, xgetenv("INFOCOLUMN")) != 0) && (strcmp(testname, xgetenv("TRENDSCOLUMN")) != 0) ) { statuscount++; switch (log->color) { case COL_RED: case COL_YELLOW: if (propagating) colorcount[log->color] += 1; else colorcount_noprop[log->color] += 1; break; default: colorcount[log->color] += 1; break; } } testnameidx = (char *)malloc(strlen(testname) + 3); sprintf(testnameidx, ",%s,", testname); if (unwantedcolumn(hostname, testname) || (ignorecolumns && strstr(ignorecolumns, testnameidx))) { xfree(hostname); xfree(testname); xfree(testnameidx); if (fd) fclose(fd); return NULL; /* Ignore this type of test */ } xfree(testnameidx); newstate = (state_t *) calloc(1, sizeof(state_t)); newstate->entry = (entry_t *) calloc(1, sizeof(entry_t)); newstate->next = NULL; newstate->entry->column = find_or_create_column(testname, 1); newstate->entry->color = -1; strcpy(newstate->entry->age, ""); newstate->entry->oldage = 0; newstate->entry->propagate = 1; newstate->entry->testflags = NULL; newstate->entry->skin = NULL; newstate->entry->repinfo = NULL; newstate->entry->causes = NULL; newstate->entry->histlogname = NULL; newstate->entry->shorttext = NULL; if (host) { newstate->entry->alert = checkalert(host->alerts, testname); /* If no WAP's specified, default all tests to be on WAP page */ newstate->entry->onwap = (host->waps ? checkalert(host->waps, testname) : 1); } else { dbgprintf(" hostname %s not found\n", hostname); newstate->entry->alert = newstate->entry->onwap = 0; } newstate->entry->sumurl = NULL; if (reportstart) { /* Determine "color" for this test from the historical data */ newstate->entry->repinfo = (reportinfo_t *) calloc(1, sizeof(reportinfo_t)); newstate->entry->color = parse_historyfile(fd, newstate->entry->repinfo, (dynamicreport ? NULL: hostname), (dynamicreport ? NULL : testname), reportstart, reportend, 0, (host ? host->reportwarnlevel : reportwarnlevel), reportgreenlevel, (host ? host->reportwarnstops : reportwarnstops), (host ? host->reporttime : NULL)); newstate->entry->causes = (dynamicreport ? NULL : save_replogs()); } else if (snapshot) { time_t fileage = snapshot - histentry_start; newstate->entry->color = history_color(fd, snapshot, &histentry_start, &newstate->entry->histlogname); newstate->entry->oldage = (fileage >= recentgif_limit); newstate->entry->fileage = fileage; strcpy(newstate->entry->age, agestring(fileage)); } else { time_t fileage = (now - log->lastchange); newstate->entry->color = log->color; newstate->entry->testflags = strdup(log->testflags); if (testflag_set(newstate->entry, 'D')) newstate->entry->skin = dialupskin; if (testflag_set(newstate->entry, 'R')) newstate->entry->skin = reverseskin; newstate->entry->shorttext = strdup(log->msg); newstate->entry->acked = isacked; newstate->entry->oldage = (fileage >= recentgif_limit); newstate->entry->fileage = (log->lastchange ? fileage : -1); if (log->lastchange == 0) strcpy(newstate->entry->age, ""); else strcpy(newstate->entry->age, agestring(fileage)); } if (purplelog && (newstate->entry->color == COL_PURPLE)) { fprintf(purplelog, "%s %s%s\n", hostname, testname, (host ? " (expired)" : " (unknown host)")); } newstate->entry->propagate = propagating; dbgprintf("init_state: hostname=%s, testname=%s, color=%d, acked=%d, age=%s, oldage=%d, propagate=%d, alert=%d\n", textornull(hostname), textornull(testname), newstate->entry->color, newstate->entry->acked, textornull(newstate->entry->age), newstate->entry->oldage, newstate->entry->propagate, newstate->entry->alert); if (host) { hostlist_t *l; /* Add this state entry to the host's list of state entries. */ newstate->entry->next = host->entries; host->entries = newstate->entry; /* There may be multiple host entries, if a host is * listed in several locations in hosts.cfg (for display purposes). * This is handled by updating ALL of the cloned host records. * Bug reported by Bluejay Adametz of Fuji. */ /* Cannot use "find_host()" here, as we need the hostlink record, not the host record */ l = find_hostlist(hostname); /* Walk through the clone-list and set the "entries" for all hosts */ for (l=l->clones; (l); l = l->clones) l->hostentry->entries = host->entries; } else { /* No host for this test - must be missing from hosts.cfg */ newstate->entry->next = NULL; } xfree(hostname); xfree(testname); if (fd) fclose(fd); return newstate; } dispsummary_t *init_displaysummary(char *fn, logdata_t *log) { char l[MAX_LINE_LEN]; dispsummary_t *newsum = NULL; time_t now = getcurrenttime(NULL); dbgprintf("init_displaysummary(%s)\n", textornull(fn)); if (log->validtime < now) return NULL; strcpy(l, log->msg); if (strlen(l)) { char *p; char *color = (char *) malloc(strlen(l)); newsum = (dispsummary_t *) calloc(1, sizeof(dispsummary_t)); newsum->url = (char *) malloc(strlen(l)); if (sscanf(l, "%s %s", color, newsum->url) == 2) { char *rowcol; newsum->color = parse_color(color); rowcol = (char *) malloc(strlen(fn) + 1); strcpy(rowcol, fn+8); p = strrchr(rowcol, '.'); if (p) *p = ' '; newsum->column = (char *) malloc(strlen(rowcol)+1); newsum->row = (char *) malloc(strlen(rowcol)+1); sscanf(rowcol, "%s %s", newsum->row, newsum->column); newsum->next = NULL; xfree(rowcol); } else { xfree(newsum->url); xfree(newsum); newsum = NULL; } xfree(color); } return newsum; } void generate_compactitems(state_t **topstate) { void *xmh; compact_t **complist = NULL; int complistsz = 0; hostlist_t *h; entry_t *e; char *compacted; char *tok1, *tok2, *savep1, *savep2; compact_t *itm; int i; state_t *newstate; time_t now = getcurrenttime(NULL); for (h = hostlistBegin(); (h); h = hostlistNext()) { xmh = hostinfo(h->hostentry->hostname); compacted = xmh_item(xmh, XMH_COMPACT); if (!compacted) continue; tok1 = strtok_r(compacted, ",", &savep1); while (tok1) { char *members; itm = (compact_t *)calloc(1, sizeof(compact_t)); itm->compactname = strdup(strtok_r(tok1, "=", &savep2)); members = strtok_r(NULL, "\n", &savep2); itm->members = (char *)malloc(3 + strlen(members)); sprintf(itm->members, "|%s|", members); if (complistsz == 0) { complist = (compact_t **)calloc(2, sizeof(compact_t *)); } else { complist = (compact_t **)realloc(complist, (complistsz+2)*sizeof(compact_t *)); } complist[complistsz++] = itm; complist[complistsz] = NULL; tok1 = strtok_r(NULL, ",", &savep1); } for (e = h->hostentry->entries; (e); e = e->next) { for (i = 0; (i < complistsz); i++) { if (wantedcolumn(e->column->name, complist[i]->members)) { e->compacted = 1; if (e->color > complist[i]->color) complist[i]->color = e->color; if (e->fileage > complist[i]->fileage) complist[i]->fileage = e->fileage; } } } for (i = 0; (i < complistsz); i++) { logdata_t log; char fn[PATH_MAX]; memset(&log, 0, sizeof(log)); sprintf(fn, "%s.%s", commafy(h->hostentry->hostname), complist[i]->compactname); log.hostname = h->hostentry->hostname; log.testname = complist[i]->compactname; log.color = complist[i]->color; log.testflags = ""; log.lastchange = now - complist[i]->fileage; log.logtime = getcurrenttime(NULL); log.validtime = log.logtime + 300; log.sender = ""; log.msg = ""; newstate = init_state(fn, &log); if (newstate) { newstate->next = *topstate; *topstate = newstate; } } } } state_t *load_state(dispsummary_t **sumhead) { int xymondresult; char fn[PATH_MAX]; state_t *newstate, *topstate; dispsummary_t *newsum, *topsum; char *board = NULL; char *nextline; int done; logdata_t log; sendreturn_t *sres; dbgprintf("load_state()\n"); sres = newsendreturnbuf(1, NULL); if (!reportstart && !snapshot) { char *dumpfn = getenv("BOARDDUMP"); if (dumpfn) { /* Debugging - read data from a dump file */ struct stat st; FILE *fd; xymondresult = XYMONSEND_ETIMEOUT; if (stat(dumpfn, &st) == 0) { fd = fopen(dumpfn, "r"); if (fd) { board = (char *)malloc(st.st_size + 1); fread(board, 1, st.st_size, fd); fclose(fd); xymondresult = XYMONSEND_OK; } } } else { xymondresult = sendmessage("xymondboard fields=hostname,testname,color,flags,lastchange,logtime,validtime,acktime,disabletime,sender,cookie,line1,acklist", NULL, XYMON_TIMEOUT, sres); board = getsendreturnstr(sres, 1); } } else { xymondresult = sendmessage("xymondboard fields=hostname,testname", NULL, XYMON_TIMEOUT, sres); board = getsendreturnstr(sres, 1); } freesendreturnbuf(sres); if ((xymondresult != XYMONSEND_OK) || (board == NULL) || (*board == '\0')) { errprintf("xymond status-board not available, code %d\n", xymondresult); return NULL; } if (reportstart || snapshot) { oldestentry = getcurrenttime(NULL); purplelog = NULL; purplelogfn = NULL; } else { if (purplelogfn) { purplelog = fopen(purplelogfn, "w"); if (purplelog == NULL) errprintf("Cannot open purplelog file %s\n", purplelogfn); else fprintf(purplelog, "Stale (purple) logfiles as of %s\n\n", timestamp); } } topstate = NULL; topsum = NULL; done = 0; nextline = board; while (!done) { char *bol = nextline; char *onelog, *acklist; char *p; int i; nextline = strchr(nextline, '\n'); if (nextline) { *nextline = '\0'; nextline++; } else done = 1; if (strlen(bol) == 0) { done = 1; continue; } memset(&log, 0, sizeof(log)); onelog = strdup(bol); acklist = NULL; p = gettok(onelog, "|"); i = 0; while (p) { switch (i) { /* hostname|testname|color|testflags|lastchange|logtime|validtime|acktime|disabletime|sender|cookie|1st line of message */ case 0: log.hostname = p; break; case 1: log.testname = p; break; case 2: log.color = parse_color(p); break; case 3: log.testflags = p; break; case 4: log.lastchange = atoi(p); break; case 5: log.logtime = atoi(p); break; case 6: log.validtime = atoi(p); break; case 7: log.acktime = atoi(p); break; case 8: log.disabletime = atoi(p); break; case 9: log.sender = p; break; case 10: log.cookie = atoi(p); break; case 11: log.msg = p; break; case 12: acklist = p; break; } p = gettok(NULL, "|"); i++; } if (!log.msg) log.msg = ""; sprintf(fn, "%s.%s", commafy(log.hostname), log.testname); /* Get the data */ if (strncmp(fn, "summary.", 8) == 0) { if (!reportstart && !snapshot) { newsum = init_displaysummary(fn, &log); if (newsum) { newsum->next = topsum; topsum = newsum; } } } else { if (acklist && *acklist) { /* * It's been acked. acklist looks like * 1149489234:1149510834:1:henrik:Joe promised to take care of this right after lunch\n * The "\n" is the delimiter between multiple acks. */ char *tok; tok = strtok(acklist, ":"); if (tok) tok = strtok(NULL, ":"); if (tok) log.acktime = atol(tok); } newstate = init_state(fn, &log); if (newstate) { newstate->next = topstate; topstate = newstate; if (reportstart && (newstate->entry->repinfo->reportstart < oldestentry)) { oldestentry = newstate->entry->repinfo->reportstart; } } } xfree(onelog); } generate_compactitems(&topstate); if (reportstart) sethostenv_report(oldestentry, reportend, reportwarnlevel, reportgreenlevel); if (purplelog) fclose(purplelog); *sumhead = topsum; return topstate; } xymon-4.3.7/xymongen/pagegen.h0000664000175000017500000000364011615341300015643 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __PAGEGEN_H_ #define __PAGEGEN_H_ extern int subpagecolumns; extern int hostsbeforepages; extern char *includecolumns; extern char *nongreenignorecolumns; extern int nongreencolors; extern int sort_grouponly_items; extern char *documentationurl; extern char *doctargetspec; extern char *rssextension; extern char *defaultpagetitle; extern char *eventignorecolumns; extern char *htaccess; extern char *xymonhtaccess; extern char *xymonpagehtaccess; extern char *xymonsubpagehtaccess; extern int pagetitlelinks; extern int pagetextheadings; extern int underlineheadings; extern int maxrowsbeforeheading; extern int nongreeneventlog; extern int nongreenacklog; extern int nongreeneventlogmaxcount; extern int nongreeneventlogmaxtime; extern int nongreennodialups; extern int nongreenacklogmaxcount; extern int nongreenacklogmaxtime; extern char *logcritstatus; extern int critonlyreds; extern int wantrss; extern void select_headers_and_footers(char *prefix); extern void do_one_page(xymongen_page_t *page, dispsummary_t *sums, int embedded); extern void do_page_with_subs(xymongen_page_t *curpage, dispsummary_t *sums); extern int do_nongreen_page(char *nssidebarfilename, int summarytype); #endif xymon-4.3.7/xymongen/util.c0000664000175000017500000001400311630612201015176 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* Various utility functions specific to xymongen. Generally useful code is */ /* in the library. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: util.c 6746 2011-09-04 06:02:41Z storner $"; #include #include #include #include #include #include #include #include "xymongen.h" #include "util.h" char *htmlextension = ".html"; /* Filename extension for generated HTML files */ static void * hosttree; static int havehosttree = 0; static void * columntree; static int havecolumntree = 0; char *hostpage_link(host_t *host) { /* Provide a link to the page where this host lives, relative to XYMONWEB */ static char pagelink[PATH_MAX]; char tmppath[PATH_MAX]; xymongen_page_t *pgwalk; if (host->parent && (strlen(((xymongen_page_t *)host->parent)->name) > 0)) { sprintf(pagelink, "%s%s", ((xymongen_page_t *)host->parent)->name, htmlextension); for (pgwalk = host->parent; (pgwalk); pgwalk = pgwalk->parent) { if (strlen(pgwalk->name)) { sprintf(tmppath, "%s/%s", pgwalk->name, pagelink); strcpy(pagelink, tmppath); } } } else { sprintf(pagelink, "xymon%s", htmlextension); } return pagelink; } char *hostpage_name(host_t *host) { /* Provide a link to the page where this host lives */ static char pagename[PATH_MAX]; char tmpname[PATH_MAX]; xymongen_page_t *pgwalk; if (host->parent && (strlen(((xymongen_page_t *)host->parent)->name) > 0)) { pagename[0] = '\0'; for (pgwalk = host->parent; (pgwalk); pgwalk = pgwalk->parent) { if (strlen(pgwalk->name)) { strcpy(tmpname, pgwalk->title); if (strlen(pagename)) { strcat(tmpname, "/"); strcat(tmpname, pagename); } strcpy(pagename, tmpname); } } } else { sprintf(pagename, "Top page"); } return pagename; } static int checknopropagation(char *testname, char *noproptests) { if (noproptests == NULL) return 0; if (strcmp(noproptests, ",*,") == 0) return 1; if (strstr(noproptests, testname) != NULL) return 1; return 0; } int checkpropagation(host_t *host, char *test, int color, int acked) { /* NB: Default is to propagate test, i.e. return 1 */ char *testname; int result = 1; if (!host) return 1; testname = (char *) malloc(strlen(test)+3); sprintf(testname, ",%s,", test); if (acked) { if (checknopropagation(testname, host->nopropacktests)) result = 0; } if (result) { if (color == COL_RED) { if (checknopropagation(testname, host->nopropredtests)) result = 0; } else if (color == COL_YELLOW) { if (checknopropagation(testname, host->nopropyellowtests)) result = 0; if (checknopropagation(testname, host->nopropredtests)) result = 0; } else if (color == COL_PURPLE) { if (checknopropagation(testname, host->noproppurpletests)) result = 0; } } xfree(testname); return result; } host_t *find_host(char *hostname) { xtreePos_t handle; if (havehosttree == 0) return NULL; /* Search for the host */ handle = xtreeFind(hosttree, hostname); if (handle != xtreeEnd(hosttree)) { hostlist_t *entry = (hostlist_t *)xtreeData(hosttree, handle); return (entry ? entry->hostentry : NULL); } return NULL; } int host_exists(char *hostname) { return (find_host(hostname) != NULL); } hostlist_t *find_hostlist(char *hostname) { xtreePos_t handle; if (havehosttree == 0) return NULL; /* Search for the host */ handle = xtreeFind(hosttree, hostname); if (handle != xtreeEnd(hosttree)) { hostlist_t *entry = (hostlist_t *)xtreeData(hosttree, handle); return entry; } return NULL; } void add_to_hostlist(hostlist_t *rec) { if (havehosttree == 0) { hosttree = xtreeNew(strcasecmp); havehosttree = 1; } xtreeAdd(hosttree, rec->hostentry->hostname, rec); } static xtreePos_t hostlistwalk; hostlist_t *hostlistBegin(void) { if (havehosttree == 0) return NULL; hostlistwalk = xtreeFirst(hosttree); if (hostlistwalk != xtreeEnd(hosttree)) { return (hostlist_t *)xtreeData(hosttree, hostlistwalk); } else { return NULL; } } hostlist_t *hostlistNext(void) { if (havehosttree == 0) return NULL; if (hostlistwalk != xtreeEnd(hosttree)) hostlistwalk = xtreeNext(hosttree, hostlistwalk); if (hostlistwalk != xtreeEnd(hosttree)) { return (hostlist_t *)xtreeData(hosttree, hostlistwalk); } else { return NULL; } } xymongen_col_t *find_or_create_column(char *testname, int create) { xymongen_col_t *newcol = NULL; xtreePos_t handle; dbgprintf("find_or_create_column(%s)\n", textornull(testname)); if (havecolumntree == 0) { columntree = xtreeNew(strcasecmp); havecolumntree = 1; } handle = xtreeFind(columntree, testname); if (handle != xtreeEnd(columntree)) newcol = (xymongen_col_t *)xtreeData(columntree, handle); if (newcol == NULL) { if (!create) return NULL; newcol = (xymongen_col_t *) calloc(1, sizeof(xymongen_col_t)); newcol->name = strdup(testname); newcol->listname = (char *)malloc(strlen(testname)+1+2); sprintf(newcol->listname, ",%s,", testname); xtreeAdd(columntree, newcol->name, newcol); } return newcol; } int wantedcolumn(char *current, char *wanted) { char *tag; int result; tag = (char *) malloc(strlen(current)+3); sprintf(tag, "|%s|", current); result = (strstr(wanted, tag) != NULL); xfree(tag); return result; } xymon-4.3.7/xymongen/csvreport.h0000664000175000017500000000154211615341300016263 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __CSVREPORT_H__ #define __CSVREPORT_H__ #include "xymongen.h" extern void csv_availability(char *fn, char csvdelim); #endif xymon-4.3.7/xymongen/pagegen.c0000664000175000017500000012030511664525562015656 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* This file contains code to generate the HTML for the Xymon overview */ /* webpages. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: pagegen.c 6775 2011-11-27 21:28:18Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include "xymongen.h" #include "util.h" #include "loadlayout.h" #include "rssgen.h" #include "pagegen.h" int subpagecolumns = 1; int hostsbeforepages = 0; char *includecolumns = NULL; char *nongreenignorecolumns = ""; int nongreennodialups = 0; int sort_grouponly_items = 0; /* Standard Xymon behaviour: Dont sort group-only items */ char *rssextension = ".rss"; /* Filename extension for generated RSS files */ char *defaultpagetitle = NULL; int pagetitlelinks = 0; int pagetextheadings = 0; int underlineheadings = 1; int maxrowsbeforeheading = 0; int nongreeneventlog = 1; int nongreenacklog = 1; int nongreeneventlogmaxcount = 100; int nongreeneventlogmaxtime = 240; int nongreenacklogmaxcount = 25; int nongreenacklogmaxtime = 240; char *logcritstatus = NULL; int critonlyreds = 0; int wantrss = 0; int nongreencolors = ((1 << COL_RED) | (1 << COL_YELLOW) | (1 << COL_PURPLE)); /* Format strings for htaccess files */ char *htaccess = NULL; char *xymonhtaccess = NULL; char *xymonpagehtaccess = NULL; char *xymonsubpagehtaccess = NULL; char *hf_prefix[3]; /* header/footer prefixes for xymon, nongreen, critical pages*/ static int hostblkidx = 0; void select_headers_and_footers(char *prefix) { hf_prefix[PAGE_NORMAL] = (char *) malloc(strlen(prefix)+10); sprintf(hf_prefix[PAGE_NORMAL], "%snormal", prefix); hf_prefix[PAGE_NONGREEN] = (char *) malloc(strlen(prefix)+10); sprintf(hf_prefix[PAGE_NONGREEN], "%snongreen", prefix); hf_prefix[PAGE_CRITICAL] = (char *) malloc(strlen(prefix)+10); sprintf(hf_prefix[PAGE_CRITICAL], "%scritical", prefix); } int interesting_column(int pagetype, int color, int alert, xymongen_col_t *column, char *onlycols, char *exceptcols) { /* * Decides if a given column is to be included on a page. */ if (pagetype == PAGE_NORMAL) { /* Fast-path the Xymon page. */ int result = 1; if (onlycols) { /* onlycols explicitly list the columns to include (for xymon.html page only) */ char *search; /* loaddata::init_group guarantees that onlycols start and end with a '|' */ search = (char *) malloc(strlen(column->name)+3); sprintf(search, "|%s|", column->name); result = (strstr(onlycols, search) != NULL); xfree(search); } if (exceptcols) { /* exceptcols explicitly list the columns to exclude (for xymon.html page only) */ char *search; /* loaddata::init_group guarantees that exceptcols start and end with a '|' */ search = (char *) malloc(strlen(column->name)+3); sprintf(search, "|%s|", column->name); result = (strstr(exceptcols, search) == NULL); xfree(search); } /* This is final. */ return result; } /* pagetype is now known NOT to be PAGE_NORMAL */ /* TRENDS and INFO columns are always included on non-Xymon pages */ if (strcmp(column->name, xgetenv("INFOCOLUMN")) == 0) return 1; if (strcmp(column->name, xgetenv("TRENDSCOLUMN")) == 0) return 1; if (includecolumns) { int result; result = (strstr(includecolumns, column->listname) != NULL); /* If included, done here. Otherwise may be included further down. */ if (result) return result; } switch (pagetype) { case PAGE_NONGREEN: /* Include all non-green tests */ if (( (1 << color) & nongreencolors ) != 0) { return (strstr(nongreenignorecolumns, column->listname) == NULL); } else return 0; case PAGE_CRITICAL: /* Include only RED or YELLOW tests with "alert" property set. * Even then, the "conn" test is included only when RED. */ if (alert) { if (color == COL_RED) return 1; if (critonlyreds) return 0; if ( (color == COL_YELLOW) || (color == COL_CLEAR) ) { if (strcmp(column->name, xgetenv("PINGCOLUMN")) == 0) return 0; if (logcritstatus && (strcmp(column->name, logcritstatus) == 0)) return 0; return 1; } } break; } return 0; } col_list_t *gen_column_list(host_t *hostlist, int pagetype, char *onlycols, char *exceptcols) { /* * Build a list of all the columns that are in use by * any host in the hostlist passed as parameter. * The column list will be sorted by column name, except * when doing a "group-only" and the standard Xymon behaviour. */ col_list_t *head; host_t *h; entry_t *e; col_list_t *newlistitem, *collist_walk; /* Code de-obfuscation trick: Add a null record as the head item */ /* Simplifies handling since head != NULL and we never have to insert at head of list */ head = (col_list_t *) calloc(1, sizeof(col_list_t)); head->column = &null_column; head->next = NULL; if (!sort_grouponly_items && (onlycols != NULL)) { /* * This is the original handling of "group-only". * All items are included, whether there are any test data * for a column or not. The order given in the group-only * directive is maintained. * We simple convert the group-only directive to a * col_list_t linked list. */ char *p1 = onlycols; char *p2; xymongen_col_t *col; collist_walk = head; do { if (*p1 == '|') p1++; p2 = strchr(p1, '|'); if (p2) { *p2 = '\0'; col = find_or_create_column(p1, 0); if (col) { newlistitem = (col_list_t *) calloc(1, sizeof(col_list_t)); newlistitem->column = col; newlistitem->next = NULL; collist_walk->next = newlistitem; collist_walk = collist_walk->next; } *p2 = '|'; } p1 = p2; } while (p1 != NULL); /* Skip the dummy record */ collist_walk = head; head = head->next; xfree(collist_walk); /* We're done - dont even look at the actual test data. */ return (head); } for (h = hostlist; (h); h = h->next) { /* * This is for everything except "standard group-only" handled above. * So also for group-only with --sort-group-only-items. * Note that in a group-only here, items may be left out if there * are no test data for a column at all. */ for (e = h->entries; (e); e = e->next) { if (!e->compacted && interesting_column(pagetype, e->color, e->alert, e->column, onlycols, exceptcols)) { /* See where e->column should go in list */ collist_walk = head; while ( (collist_walk->next && strcmp(e->column->name, ((col_list_t *)(collist_walk->next))->column->name) > 0) ) { collist_walk = collist_walk->next; } if ((collist_walk->next == NULL) || ((col_list_t *)(collist_walk->next))->column != e->column) { /* collist_walk points to the entry before the new one */ newlistitem = (col_list_t *) calloc(1, sizeof(col_list_t)); newlistitem->column = e->column; newlistitem->next = collist_walk->next; collist_walk->next = newlistitem; } } } } /* Skip the dummy record */ collist_walk = head; head = head->next; xfree(collist_walk); return (head); } void setup_htaccess(const char *pagepath) { char htaccessfn[PATH_MAX]; char htaccesscontent[1024]; if (htaccess == NULL) return; htaccesscontent[0] = '\0'; if (strlen(pagepath) == 0) { sprintf(htaccessfn, "%s", htaccess); if (xymonhtaccess) strcpy(htaccesscontent, xymonhtaccess); } else { char *pagename, *subpagename, *p; char *path = strdup(pagepath); for (p = path + strlen(path) - 1; ((p > path) && (*p == '/')); p--) *p = '\0'; sprintf(htaccessfn, "%s/%s", path, htaccess); pagename = path; if (*pagename == '/') pagename++; p = strchr(pagename, '/'); if (p) { *p = '\0'; subpagename = p+1; p = strchr(subpagename, '/'); if (p) *p = '\0'; if (xymonsubpagehtaccess) sprintf(htaccesscontent, xymonsubpagehtaccess, pagename, subpagename); } else { if (xymonpagehtaccess) sprintf(htaccesscontent, xymonpagehtaccess, pagename); } xfree(path); } if (strlen(htaccesscontent)) { FILE *fd; struct stat st; if (stat(htaccessfn, &st) == 0) { dbgprintf("htaccess file %s exists, not overwritten\n", htaccessfn); return; } fd = fopen(htaccessfn, "w"); if (fd) { fprintf(fd, "%s\n", htaccesscontent); fclose(fd); } else { errprintf("Cannot create %s: %s\n", htaccessfn, strerror(errno)); } } } static int host_t_compare(const void *v1, const void *v2) { host_t **n1 = (host_t **)v1; host_t **n2 = (host_t **)v2; return strcmp((*n1)->hostname, (*n2)->hostname); } typedef struct vprec_t { char *testname; host_t **hosts; entry_t **entries; } vprec_t; void do_vertical(host_t *head, FILE *output, char *pagepath) { /* * This routine outputs the host part of a page or a group, * but with the hosts going across the page, and the test going down. * I.e. it generates buttons and links to all the hosts for * a test, and the host docs. */ host_t *h; entry_t *e; char *xymonskin; int rowcount = 0, hostcount = 0; int usetooltip = 0; int width; int hidx; void *vptree; xtreePos_t handle; if (head == NULL) return; vptree = xtreeNew(strcmp); xymonskin = strdup(xgetenv("XYMONSKIN")); switch (tooltipuse) { case TT_STDONLY: case TT_ALWAYS: usetooltip = 1; break; case TT_NEVER: usetooltip = 0; break; } width = atoi(xgetenv("DOTWIDTH")); if ((width < 0) || (width > 50)) width = 16; width += 4; /* Start the table ... */ fprintf(output, "
\n"); /* output column headings */ fprintf(output, ""); for (h = head, hostcount = 0; (h); h = h->next, hostcount++) { fprintf(output, " \n", hostsvcurl(h->hostname, xgetenv("INFOCOLUMN"), 1), xgetenv("XYMONPAGECOLFONT"), h->hostname); } fprintf(output, "\n"); fprintf(output, "\n\n", hostcount); /* Create a tree indexed by the testname, and holding the show/noshow status of each test */ for (h = head, hidx = 0; (h); h = h->next, hidx++) { for (e = h->entries; (e); e = e->next) { vprec_t *itm; char *hptr; handle = xtreeFind(vptree, e->column->name); if (handle == xtreeEnd(vptree)) { itm = (vprec_t *)malloc(sizeof(vprec_t)); itm->testname = e->column->name; itm->hosts = (host_t **)calloc(hostcount, sizeof(host_t *)); itm->entries = (entry_t **)calloc(hostcount, sizeof(entry_t *)); xtreeAdd(vptree, itm->testname, itm); } else { itm = xtreeData(vptree, handle); } (itm->hosts)[hidx] = h; (itm->entries)[hidx] = e; } } for (handle = xtreeFirst(vptree); (handle != xtreeEnd(vptree)); handle = xtreeNext(vptree, handle)) { vprec_t *itm = xtreeData(vptree, handle); fprintf(output, ""); fprintf(output, "", itm->testname); for (hidx = 0; (hidx < hostcount); hidx++) { char *skin, *htmlalttag; host_t *h = (itm->hosts)[hidx]; entry_t *e = (itm->entries)[hidx]; fprintf(output, ""); } fprintf(output, "\n"); } fprintf(output, "
 "); fprintf(output, " %s
 
%s"); if (e == NULL) { fprintf(output, "-"); } else { if (strcmp(e->column->name, xgetenv("INFOCOLUMN")) == 0) { /* show the host ip on the hint display of the "info" column */ htmlalttag = alttag(e->column->name, COL_GREEN, 0, 1, h->ip); } else { htmlalttag = alttag(e->column->name, e->color, e->acked, e->propagate, e->age); } skin = (e->skin ? e->skin : xymonskin); fprintf(output, "", hostsvcurl(h->hostname, e->column->name, 1)); fprintf(output, "\"%s\"", skin, dotgiffilename(e->color, e->acked, e->oldage), htmlalttag, htmlalttag, xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); } fprintf(output, "

\n"); xfree(xymonskin); } void do_hosts(host_t *head, int sorthosts, char *onlycols, char *exceptcols, FILE *output, FILE *rssoutput, char *grouptitle, int pagetype, char *pagepath) { /* * This routine outputs the host part of a page or a group. * I.e. it generates buttons and links to all the tests for * a host, and the host docs. */ host_t *h; entry_t *e; col_list_t *groupcols, *gc; int genstatic; int columncount; char *xymonskin; int rowcount = 0; int usetooltip = 0; if (head == NULL) return; xymonskin = strdup(xgetenv("XYMONSKIN")); switch (tooltipuse) { case TT_STDONLY: usetooltip = (pagetype == PAGE_NORMAL); break; case TT_ALWAYS: usetooltip = 1; break; case TT_NEVER: usetooltip = 0; break; } /* Generate static or dynamic links (from XYMONLOGSTATUS) ? */ genstatic = generate_static(); if (hostblkidx == 0) fprintf(output, " \n\n"); else fprintf(output, " \n\n", hostblkidx); hostblkidx++; groupcols = gen_column_list(head, pagetype, onlycols, exceptcols); for (columncount=0, gc=groupcols; (gc); gc = gc->next, columncount++) ; if (groupcols) { int width; width = atoi(xgetenv("DOTWIDTH")); if ((width < 0) || (width > 50)) width = 16; width += 4; /* Start the table ... */ fprintf(output, "
\n"); /* Generate the host rows */ if (sorthosts) { int i, hcount = 0; host_t **hlist; for (h=head; (h); h=h->next) hcount++; hlist = (host_t **) calloc((hcount+1), sizeof(host_t *)); for (h=head, i=0; (h); h=h->next, i++) hlist[i] = h; qsort(hlist, hcount, sizeof(host_t *), host_t_compare); for (h=head=hlist[0], i=1; (i <= hcount); i++) { h->next = hlist[i]; h = h->next; } xfree(hlist); } for (h = head; (h); h = h->next) { /* If there is a host pretitle, show it. */ dbgprintf("Host:%s, pretitle:%s\n", h->hostname, textornull(h->pretitle)); if (h->pretitle && (pagetype == PAGE_NORMAL)) { fprintf(output, "\n", columncount+1, xgetenv("XYMONPAGETITLE"), h->pretitle); rowcount = 0; } if (rowcount == 0) { /* output group title and column headings */ fprintf(output, ""); fprintf(output, "\n", xgetenv("XYMONPAGETITLE"), (strlen(grouptitle) ? grouptitle : " ")); for (gc=groupcols; (gc); gc = gc->next) { fprintf(output, " \n", columnlink(gc->column->name), xgetenv("XYMONPAGECOLFONT"), gc->column->name); } fprintf(output, " \n\n\n", columncount); } fprintf(output, "\n \n"); } fprintf(output, "\n\n"); } fprintf(output, "

%s
%s
\n"); fprintf(output, " %s

 \n", h->hostname); if (maxrowsbeforeheading) rowcount = (rowcount + 1) % maxrowsbeforeheading; else rowcount++; fprintf(output, "%s", hostnamehtml(h->hostname, ((pagetype != PAGE_NORMAL) ? hostpage_link(h) : NULL), usetooltip)); /* Then the columns. */ for (gc = groupcols; (gc); gc = gc->next) { char *htmlalttag; fprintf(output, ""); /* Any column entry for this host ? */ for (e = h->entries; (e && (e->column != gc->column)); e = e->next) ; if (e == NULL) { fprintf(output, "-"); } else if (e->histlogname) { /* Snapshot points to historical logfile */ htmlalttag = alttag(e->column->name, e->color, e->acked, e->propagate, e->age); fprintf(output, "", histlogurl(h->hostname, e->column->name, 0, e->histlogname)); fprintf(output, "\"%s\"", xymonskin, dotgiffilename(e->color, 0, 1), htmlalttag, htmlalttag, xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); } else if (reportstart == 0) { /* Standard webpage */ char *skin; if (strcmp(e->column->name, xgetenv("INFOCOLUMN")) == 0) { /* Show the host IP on the hint display of the "info" column */ htmlalttag = alttag(e->column->name, COL_GREEN, 0, 1, h->ip); } else { htmlalttag = alttag(e->column->name, e->color, e->acked, e->propagate, e->age); } skin = (e->skin ? e->skin : xymonskin); if (e->sumurl) { /* A summary host. */ fprintf(output, "", e->sumurl); } else if (genstatic && strcmp(e->column->name, xgetenv("INFOCOLUMN")) && strcmp(e->column->name, xgetenv("TRENDSCOLUMN"))) { /* * Dont use htmlextension here - it's for the * pages generated dynamically. * We dont do static pages for the info- and trends-columns, because * they are always generated dynamically. */ fprintf(output, "", xgetenv("XYMONWEB"), h->hostname, e->column->name); do_rss_item(rssoutput, h, e); } else { fprintf(output, "", hostsvcurl(h->hostname, e->column->name, 1)); do_rss_item(rssoutput, h, e); } fprintf(output, "\"%s\"", skin, dotgiffilename(e->color, e->acked, e->oldage), htmlalttag, htmlalttag, xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); } else { /* Report format output */ if ((e->color == COL_GREEN) || (e->color == COL_CLEAR)) { fprintf(output, "\"%s\"", xymonskin, dotgiffilename(e->color, 0, 1), colorname(e->color), colorname(e->color), xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH")); } else { if (dynamicreport) { fprintf(output, "", replogurl(h->hostname, e->column->name, e->color, stylenames[reportstyle], use_recentgifs, e->repinfo, h->reporttime, reportend, h->reportwarnlevel)); } else { FILE *htmlrep, *textrep; char htmlrepfn[PATH_MAX]; char textrepfn[PATH_MAX]; char textrepurl[PATH_MAX]; /* File names are relative - current directory is the output dir */ /* pagepath is either empty, or it ends with a '/' */ sprintf(htmlrepfn, "%s%s-%s%s", pagepath, h->hostname, e->column->name, htmlextension); sprintf(textrepfn, "%savail-%s-%s.txt", pagepath, h->hostname, e->column->name); sprintf(textrepurl, "%s/%s", xgetenv("XYMONWEB"), textrepfn); htmlrep = fopen(htmlrepfn, "w"); if (!htmlrep) { errprintf("Cannot create output file %s: %s\n", htmlrepfn, strerror(errno)); } textrep = fopen(textrepfn, "w"); if (!textrep) { errprintf("Cannot create output file %s: %s\n", textrepfn, strerror(errno)); } if (textrep && htmlrep) { /* Pre-build the test-specific report */ restore_replogs(e->causes); generate_replog(htmlrep, textrep, textrepurl, h->hostname, e->column->name, e->color, reportstyle, h->ip, h->displayname, reportstart, reportend, reportwarnlevel, reportgreenlevel, reportwarnstops, e->repinfo); fclose(textrep); fclose(htmlrep); } fprintf(output, "\n", h->hostname, e->column->name, htmlextension); } /* Only show #stops if we have this as an SLA parameter */ if (h->reportwarnstops >= 0) { fprintf(output, "%.2f (%d)\n", colorname(e->color), e->repinfo->reportavailability, e->repinfo->reportstops); } else { fprintf(output, "%.2f\n", colorname(e->color), e->repinfo->reportavailability); } } } fprintf(output, "

\n"); } /* Free the columnlist allocated by gen_column_list() */ while (groupcols) { gc = groupcols; groupcols = groupcols->next; xfree(gc); } xfree(xymonskin); } void do_groups(group_t *head, FILE *output, FILE *rssoutput, char *pagepath) { /* * This routine generates all the groups on a given page. * It also triggers generating host output for hosts * within the groups. */ group_t *g; if (head == NULL) return; fprintf(output, "
\n\n \n"); for (g = head; (g); g = g->next) { if (g->hosts && g->pretitle) { fprintf(output, "
\n"); fprintf(output, " \n", xgetenv("XYMONPAGETITLE"), g->pretitle); if (underlineheadings) fprintf(output, " \n"); fprintf(output, "
%s

\n"); } do_hosts(g->hosts, g->sorthosts, g->onlycols, g->exceptcols, output, rssoutput, g->title, PAGE_NORMAL, pagepath); } fprintf(output, "\n
\n"); } void do_summaries(dispsummary_t *sums, FILE *output) { /* * Generates output for summary statuses received from others. */ dispsummary_t *s; host_t *sumhosts = NULL; host_t *walk; if (sums == NULL) { /* No summary items */ return; } for (s=sums; (s); s = s->next) { /* Generate host records out of all unique s->row values */ host_t *newhost; entry_t *newentry; dispsummary_t *s2; /* Do we already have it ? */ for (newhost = sumhosts; (newhost && (strcmp(s->row, newhost->hostname) != 0) ); newhost = newhost->next); if (newhost == NULL) { /* New summary "host" */ newhost = init_host(s->row, 1, NULL, NULL, NULL, NULL, 0,0,0,0, 0, 0.0, 0, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL); /* Insert into sorted host list */ if ((!sumhosts) || (strcmp(newhost->hostname, sumhosts->hostname) < 0)) { /* Empty list, or new entry goes before list head item */ newhost->next = sumhosts; sumhosts = newhost; } else { /* Walk list until we find element that goes after new item */ for (walk = sumhosts; (walk->next && (strcmp(newhost->hostname, ((host_t *)walk->next)->hostname) > 0)); walk = walk->next) ; /* "walk" points to element before the new item */ newhost->next = walk->next; walk->next = newhost; } /* Setup the "event" records from the column records */ for (s2 = sums; (s2); s2 = s2->next) { if (strcmp(s2->row, s->row) == 0) { newentry = (entry_t *) calloc(1, sizeof(entry_t)); newentry->column = find_or_create_column(s2->column, 1); newentry->color = s2->color; strcpy(newentry->age, ""); newentry->oldage = 1; /* Use standard gifs */ newentry->propagate = 1; newentry->sumurl = s2->url; newentry->next = newhost->entries; newhost->entries = newentry; } } } } fprintf(output, " \n"); fprintf(output, "
\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "
\n"); do_hosts(sumhosts, 1, NULL, NULL, output, NULL, xgetenv("XYMONPAGEREMOTE"), 0, NULL); fprintf(output, "
\n"); fprintf(output, "
\n"); } void do_page_subpages(FILE *output, xymongen_page_t *subs, char *pagepath) { /* * This routine does NOT generate subpages! * Instead, it generates the LINKS to the subpages below any given page. */ xymongen_page_t *p; int currentcolumn; char pagelink[PATH_MAX]; char *linkurl; if (subs) { fprintf(output, " \n"); fprintf(output, "
\n
\n"); fprintf(output, "\n"); currentcolumn = 0; for (p = subs; (p); p = p->next) { if (p->pretitle) { /* * Output a page-link title text. */ if (currentcolumn != 0) { fprintf(output, "\n"); currentcolumn = 0; } fprintf(output, "\n"); fprintf(output, "\n"); } if (currentcolumn == 0) fprintf(output, "\n"); sprintf(pagelink, "%s/%s/%s/%s%s", xgetenv("XYMONWEB"), pagepath, p->name, p->name, htmlextension); linkurl = hostlink(p->name); fprintf(output, "\n"); fprintf(output, "\n"); if (currentcolumn == (subpagecolumns-1)) { fprintf(output, "\n"); currentcolumn = 0; } else { /* Need to have a little space between columns */ fprintf(output, "", xgetenv("DOTWIDTH")); currentcolumn++; } } if (currentcolumn != 0) fprintf(output, "\n"); fprintf(output, "
\n\n", (2*subpagecolumns + (subpagecolumns - 1)), xgetenv("XYMONPAGETITLE")); fprintf(output, "
%s\n", p->pretitle); fprintf(output, "
", (2*subpagecolumns + (subpagecolumns - 1))); if (underlineheadings) { fprintf(output, "
"); } else { fprintf(output, " "); } fprintf(output, "
", xgetenv("XYMONPAGEROWFONT")); if (linkurl) { fprintf(output, "%s", linkurl, p->title); } else if (pagetitlelinks) { fprintf(output, "%s", cleanurl(pagelink), p->title); } else { fprintf(output, "%s", p->title); } fprintf(output, "
", cleanurl(pagelink)); fprintf(output, "\"%s\"", xgetenv("XYMONSKIN"), dotgiffilename(p->color, 0, ((reportstart > 0) ? 1 : p->oldage)), xgetenv("DOTWIDTH"), xgetenv("DOTHEIGHT"), colorname(p->color), colorname(p->color)); fprintf(output, "
 

\n"); fprintf(output, "
\n"); } } void do_one_page(xymongen_page_t *page, dispsummary_t *sums, int embedded) { FILE *output = NULL; FILE *rssoutput = NULL; char pagepath[PATH_MAX]; char filename[PATH_MAX]; char tmpfilename[PATH_MAX]; char rssfilename[PATH_MAX]; char tmprssfilename[PATH_MAX]; char curdir[PATH_MAX]; char *dirdelim; char *localtext; getcwd(curdir, sizeof(curdir)); localtext = strdup(xgetenv((page->parent ? "XYMONPAGESUBLOCAL" : "XYMONPAGELOCAL"))); pagepath[0] = '\0'; if (embedded) { output = stdout; } else { if (page->parent == NULL) { char indexfilename[PATH_MAX]; /* top level page */ sprintf(filename, "xymon%s", htmlextension); sprintf(rssfilename, "xymon%s", rssextension); sprintf(indexfilename, "index%s", htmlextension); unlink(indexfilename); symlink(filename, indexfilename); dbgprintf("Symlinking %s -> %s\n", filename, indexfilename); } else { char tmppath[PATH_MAX]; xymongen_page_t *pgwalk; for (pgwalk = page; (pgwalk); pgwalk = pgwalk->parent) { if (strlen(pgwalk->name)) { sprintf(tmppath, "%s/%s/", pgwalk->name, pagepath); strcpy(pagepath, tmppath); } } sprintf(filename, "%s/%s%s", pagepath, page->name, htmlextension); sprintf(rssfilename, "%s/%s%s", pagepath, page->name, rssextension); } sprintf(tmpfilename, "%s.tmp", filename); sprintf(tmprssfilename, "%s.tmp", rssfilename); /* Try creating the output file. If it fails, we may need to create the directories */ hostblkidx = 0; output = fopen(tmpfilename, "w"); if (output == NULL) { char indexfilename[PATH_MAX]; char pagebasename[PATH_MAX]; char *p; int res; /* Make sure the directories exist. */ dirdelim = tmpfilename; while ((dirdelim = strchr(dirdelim, '/')) != NULL) { *dirdelim = '\0'; if ((mkdir(tmpfilename, 0755) == -1) && (errno != EEXIST)) { errprintf("Cannot create directory %s (in %s): %s\n", tmpfilename, curdir, strerror(errno)); } *dirdelim = '/'; dirdelim++; } /* We've created the directories. Now retry creating the file. */ output = fopen(tmpfilename, "w"); if (output == NULL) { errprintf("Cannot create file %s (in %s): %s\n", tmpfilename, curdir, strerror(errno)); return; } /* * We had to create the directory. Set up an index.html file for * the directory where we created our new file. */ strcpy(indexfilename, filename); p = strrchr(indexfilename, '/'); if (p) p++; else p = indexfilename; sprintf(p, "index%s", htmlextension); sprintf(pagebasename, "%s%s", page->name, htmlextension); if ((symlink(pagebasename, indexfilename) == -1) && ((res = errno) != EEXIST)) { errprintf("Cannot create symlink %s->%s (in %s): %s\n", indexfilename, pagebasename, curdir, strerror(res)); } if (output == NULL) { return; } } if (wantrss) { /* Just create the RSS files - all the directory stuff is done */ rssoutput = fopen(tmprssfilename, "w"); if (rssoutput == NULL) { errprintf("Cannot open RSS file %s: %s\n", tmprssfilename, strerror(errno)); } } } setup_htaccess(pagepath); headfoot(output, hf_prefix[PAGE_NORMAL], pagepath, "header", page->color); do_rss_header(rssoutput); if (pagetextheadings && page->title && strlen(page->title)) { fprintf(output, "
\n"); fprintf(output, " \n", xgetenv("XYMONPAGETITLE"), page->title); if (underlineheadings) fprintf(output, " \n"); fprintf(output, "
%s

\n"); } else if (page->subpages) { /* If first page does not have a pretitle, use the default ones */ if (page->subpages->pretitle == NULL) { page->subpages->pretitle = (defaultpagetitle ? defaultpagetitle : localtext); } } if (!embedded && !hostsbeforepages && page->subpages) do_page_subpages(output, page->subpages, pagepath); if (page->vertical) { do_vertical(page->hosts, output, pagepath); } else { do_hosts(page->hosts, 0, NULL, NULL, output, rssoutput, "", PAGE_NORMAL, pagepath); do_groups(page->groups, output, rssoutput, pagepath); } if (!embedded && hostsbeforepages && page->subpages) do_page_subpages(output, page->subpages, pagepath); /* Summaries on main page only */ if (!embedded && (page->parent == NULL)) { do_summaries(dispsums, output); } /* Extension scripts */ do_extensions(output, "XYMONSTDEXT", "mkbb"); headfoot(output, hf_prefix[PAGE_NORMAL], pagepath, "footer", page->color); do_rss_footer(rssoutput); if (!embedded) { fclose(output); if (rename(tmpfilename, filename)) { errprintf("Cannot rename %s to %s - error %d\n", tmpfilename, filename, errno); } if (rssoutput) { fclose(rssoutput); if (rename(tmprssfilename, rssfilename)) { errprintf("Cannot rename %s to %s - error %d\n", tmprssfilename, rssfilename, errno); } } } xfree(localtext); } void do_page_with_subs(xymongen_page_t *curpage, dispsummary_t *sums) { xymongen_page_t *levelpage; for (levelpage = curpage; (levelpage); levelpage = levelpage->next) { do_one_page(levelpage, sums, 0); do_page_with_subs(levelpage->subpages, NULL); } } static void do_nongreenext(FILE *output, char *extenv, char *family) { /* * Do the non-green page extensions. Since we have built-in * support for eventlog.sh and acklog.sh, we cannot * use the standard do_extensions() routine. */ char *extensions, *p; FILE *inpipe; char extfn[PATH_MAX]; char buf[4096]; p = xgetenv(extenv); if (p == NULL) { /* No extension */ return; } extensions = strdup(p); p = strtok(extensions, "\t "); while (p) { /* Dont redo the eventlog or acklog things */ if (strcmp(p, "eventlog.sh") == 0) { if (nongreeneventlog && !havedoneeventlog) { do_eventlog(output, nongreeneventlogmaxcount, nongreeneventlogmaxtime, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, nongreennodialups, host_exists, NULL, NULL, NULL, XYMON_COUNT_NONE, XYMON_S_NONE, NULL); } } else if (strcmp(p, "acklog.sh") == 0) { if (nongreenacklog && !havedoneacklog) do_acklog(output, nongreenacklogmaxcount, nongreenacklogmaxtime); } else if (strcmp(p, "summaries") == 0) { do_summaries(dispsums, output); } else { sprintf(extfn, "%s/ext/%s/%s", xgetenv("XYMONHOME"), family, p); inpipe = popen(extfn, "r"); if (inpipe) { while (fgets(buf, sizeof(buf), inpipe)) fputs(buf, output); pclose(inpipe); } } p = strtok(NULL, "\t "); } xfree(extensions); } int do_nongreen_page(char *nssidebarfilename, int summarytype) { xymongen_page_t nongreenpage; FILE *output = NULL; FILE *rssoutput = NULL; char filename[PATH_MAX]; char tmpfilename[PATH_MAX]; char rssfilename[PATH_MAX]; char tmprssfilename[PATH_MAX]; hostlist_t *h; /* Build a "page" with the hosts that should be included in nongreen page */ nongreenpage.name = nongreenpage.title = ""; nongreenpage.color = COL_GREEN; nongreenpage.subpages = NULL; nongreenpage.groups = NULL; nongreenpage.hosts = NULL; nongreenpage.next = NULL; for (h=hostlistBegin(); (h); h=hostlistNext()) { entry_t *e; int useit = 0; /* * Why dont we use the interesting_column() routine here ? * * Well, because what we are interested in for now is * to determine if this HOST should be included on the page. * * We dont care if individual COLUMNS are included if the * host shows up - some columns are always included, e.g. * the info- and trends-columns, but we dont want that to * trigger a host being on the nongreen page! */ switch (summarytype) { case PAGE_NONGREEN: /* Normal non-green page */ if (h->hostentry->nonongreen || (nongreennodialups && h->hostentry->dialup)) useit = 0; else useit = (( (1 << h->hostentry->nongreencolor) & nongreencolors ) != 0); break; case PAGE_CRITICAL: /* The Critical page */ for (useit=0, e=h->hostentry->entries; (e && !useit); e=e->next) { if (e->alert && !e->acked) { if (e->color == COL_RED) { useit = 1; } else { if (!critonlyreds) { useit = ((e->color == COL_YELLOW) && (strcmp(e->column->name, xgetenv("PINGCOLUMN")) != 0)); } } } } break; } if (useit) { host_t *newhost, *walk; switch (summarytype) { case PAGE_NONGREEN: if (h->hostentry->nongreencolor > nongreenpage.color) nongreenpage.color = h->hostentry->nongreencolor; break; case PAGE_CRITICAL: if (h->hostentry->criticalcolor > nongreenpage.color) nongreenpage.color = h->hostentry->criticalcolor; break; } /* We need to create a copy of the original record, */ /* as we will diddle with the pointers */ newhost = (host_t *) calloc(1, sizeof(host_t)); memcpy(newhost, h->hostentry, sizeof(host_t)); newhost->next = NULL; /* Insert into sorted host list */ if ((!nongreenpage.hosts) || (strcmp(newhost->hostname, nongreenpage.hosts->hostname) < 0)) { /* Empty list, or new entry goes before list head item */ newhost->next = nongreenpage.hosts; nongreenpage.hosts = newhost; } else { /* Walk list until we find element that goes after new item */ for (walk = nongreenpage.hosts; (walk->next && (strcmp(newhost->hostname, ((host_t *)walk->next)->hostname) > 0)); walk = walk->next) ; /* "walk" points to element before the new item. * * Check for duplicate hosts. We can have a host on two normal Xymon * pages, but in the non-green page we want it only once. */ if (strcmp(walk->hostname, newhost->hostname) == 0) { /* Duplicate at start of list */ xfree(newhost); } else if (walk->next && (strcmp(((host_t *)walk->next)->hostname, newhost->hostname) == 0)) { /* Duplicate inside list */ xfree(newhost); } else { /* New host */ newhost->next = walk->next; walk->next = newhost; } } } } switch (summarytype) { case PAGE_NONGREEN: sprintf(filename, "nongreen%s", htmlextension); sprintf(rssfilename, "nongreen%s", rssextension); break; case PAGE_CRITICAL: sprintf(filename, "critical%s", htmlextension); sprintf(rssfilename, "critical%s", rssextension); break; } sprintf(tmpfilename, "%s.tmp", filename); output = fopen(tmpfilename, "w"); if (output == NULL) { errprintf("Cannot create file %s: %s\n", tmpfilename, strerror(errno)); return nongreenpage.color; } if (wantrss) { sprintf(tmprssfilename, "%s.tmp", rssfilename); rssoutput = fopen(tmprssfilename, "w"); if (rssoutput == NULL) { errprintf("Cannot create RSS file %s: %s\n", tmpfilename, strerror(errno)); return nongreenpage.color; } } headfoot(output, hf_prefix[summarytype], "", "header", nongreenpage.color); do_rss_header(rssoutput); fprintf(output, "
\n"); fprintf(output, "\n  \n \n"); if (nongreenpage.hosts) { do_hosts(nongreenpage.hosts, 0, NULL, NULL, output, rssoutput, "", summarytype, NULL); } else { /* All Monitored Systems OK */ fprintf(output, "%s", xgetenv("XYMONALLOKTEXT")); } if ((snapshot == 0) && (summarytype == PAGE_NONGREEN)) { do_nongreenext(output, "XYMONNONGREENEXT", "mkbb"); /* Dont redo the eventlog or acklog things */ if (nongreeneventlog && !havedoneeventlog) { do_eventlog(output, nongreeneventlogmaxcount, nongreeneventlogmaxtime, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, nongreennodialups, host_exists, NULL, NULL, NULL, XYMON_COUNT_NONE, XYMON_S_NONE, NULL); } if (nongreenacklog && !havedoneacklog) do_acklog(output, nongreenacklogmaxcount, nongreenacklogmaxtime); } fprintf(output, "
\n"); headfoot(output, hf_prefix[summarytype], "", "footer", nongreenpage.color); do_rss_footer(rssoutput); fclose(output); if (rename(tmpfilename, filename)) { errprintf("Cannot rename %s to %s - error %d\n", tmpfilename, filename, errno); } if (rssoutput) { fclose(rssoutput); if (rename(tmprssfilename, rssfilename)) { errprintf("Cannot rename %s to %s - error %d\n", tmprssfilename, rssfilename, errno); } } if (nssidebarfilename) do_netscape_sidebar(nssidebarfilename, nongreenpage.hosts); if (logcritstatus && (summarytype == PAGE_CRITICAL)) { host_t *hwalk; entry_t *ewalk; char *msgptr; char msgline[MAX_LINE_LEN]; FILE *nklog; char nklogfn[PATH_MAX]; char svcspace; sprintf(nklogfn, "%s/criticalstatus.log", xgetenv("XYMONSERVERLOGS")); nklog = fopen(nklogfn, "a"); if (nklog == NULL) { errprintf("Cannot log Critical status to %s: %s\n", nklogfn, strerror(errno)); } init_timestamp(); combo_start(); init_status(nongreenpage.color); sprintf(msgline, "status %s.%s %s %s Critical page %s\n\n", xgetenv("MACHINE"), logcritstatus, colorname(nongreenpage.color), timestamp, colorname(nongreenpage.color)); addtostatus(msgline); if (nklog) fprintf(nklog, "%u\t%s", (unsigned int)getcurrenttime(NULL), colorname(nongreenpage.color)); for (hwalk = nongreenpage.hosts; hwalk; hwalk = hwalk->next) { msgptr = msgline; msgptr += sprintf(msgline, "&%s %s :", colorname(hwalk->color), hwalk->hostname); if (nklog) fprintf(nklog, "\t%s ", hwalk->hostname); svcspace = '('; for (ewalk = hwalk->entries; (ewalk); ewalk = ewalk->next) { if ((summarytype == PAGE_NONGREEN) || (ewalk->alert)) { if ((ewalk->color == COL_RED) || (ewalk->color == COL_YELLOW)) { msgptr += sprintf(msgptr, "%s", ewalk->column->name); if (nklog) fprintf(nklog, "%c%s:%s", svcspace, ewalk->column->name, colorname(ewalk->color)); svcspace = ' '; } } } strcpy(msgptr, "\n"); addtostatus(msgline); if (nklog) fprintf(nklog, ")"); } finish_status(); combo_end(); if (nklog) { fprintf(nklog, "\n"); fclose(nklog); } } { /* Free temporary hostlist */ host_t *h1, *h2; h1 = nongreenpage.hosts; while (h1) { h2 = h1; h1 = h1->next; xfree(h2); } } return nongreenpage.color; } xymon-4.3.7/xymongen/xymongen.c0000664000175000017500000006226411632345122016110 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* This is the main program for generating Xymon overview webpages, showing */ /* the status of hosts in a Xymon system. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymongen.c 6752 2011-09-09 08:12:02Z storner $"; #include #include #include #include #include #include #include #include "version.h" #include "xymongen.h" #include "util.h" #include "debug.h" #include "loadlayout.h" #include "loaddata.h" #include "process.h" #include "pagegen.h" #include "wmlgen.h" #include "rssgen.h" #include "csvreport.h" /* Global vars */ xymongen_page_t *pagehead = NULL; /* Head of page list */ state_t *statehead = NULL; /* Head of list of all state entries */ summary_t *sumhead = NULL; /* Summaries we send out */ dispsummary_t *dispsums = NULL; /* Summaries we received and display */ int xymon_color, nongreen_color, critical_color; /* Top-level page colors */ int fqdn = 1; /* Xymon FQDN setting */ int loadhostsfromxymond = 0; time_t reportstart = 0; time_t reportend = 0; double reportwarnlevel = 97.0; double reportgreenlevel = 99.995; int reportwarnstops = -1; int reportstyle = STYLE_CRIT; int dynamicreport = 1; enum tooltipuse_t tooltipuse = TT_STDONLY; char *reqenv[] = { "XYMONACKDIR", "XYMONHISTDIR", "XYMONHISTLOGS", "XYMONHOME", "HOSTSCFG", "XYMONRAWSTATUSDIR", "XYMONLOGSTATUS", "XYMONNOTESDIR", "XYMONREPDIR", "XYMONREPURL", "XYMONSKIN", "XYMONTMP", "XYMONVAR", "XYMONWEB", "XYMONWEBHOST", "XYMONWEBHOSTURL", "CGIBINURL", "DOTHEIGHT", "DOTWIDTH", "MACHINE", "MACHINEADDR", "XYMONPAGECOLFONT", "XYMONPAGELOCAL", "XYMONPAGESUBLOCAL", "XYMONPAGEREMOTE", "XYMONPAGEROWFONT", "XYMONPAGETITLE", "PURPLEDELAY", NULL }; int main(int argc, char *argv[]) { char *pagedir; xymongen_page_t *p; dispsummary_t *s; int i; char *pageset = NULL; char *nssidebarfilename = NULL; char *egocolumn = NULL; char *csvfile = NULL; char csvdelim = ','; int embedded = 0; char *envarea = NULL; int do_normal = 1; int do_nongreen = 1; /* Setup standard header+footer (might be modified by option pageset) */ select_headers_and_footers("std"); xymon_color = nongreen_color = critical_color = -1; pagedir = NULL; init_timestamp(); fqdn = get_fqdn(); /* Setup values from env. vars that may be overridden via command-line options */ if (xgetenv("XYMONPAGECOLREPEAT")) { int i = atoi(xgetenv("XYMONPAGECOLREPEAT")); if (i > 0) maxrowsbeforeheading = i; } for (i = 1; (i < argc); i++) { if ( (strcmp(argv[i], "--hobbitd") == 0) || (argnmatch(argv[i], "--purplelifetime=")) || (strcmp(argv[i], "--nopurple") == 0) ) { /* Deprecated */ } else if (argnmatch(argv[i], "--env=")) { char *lp = strchr(argv[i], '='); loadenv(lp+1, envarea); } else if (argnmatch(argv[i], "--area=")) { char *lp = strchr(argv[i], '='); envarea = strdup(lp+1); } else if (argnmatch(argv[i], "--ignorecolumns=")) { char *lp = strchr(argv[i], '='); ignorecolumns = (char *) malloc(strlen(lp)+2); sprintf(ignorecolumns, ",%s,", (lp+1)); } else if (argnmatch(argv[i], "--critical-reds-only") || argnmatch(argv[i], "--nk-reds-only")) { critonlyreds = 1; } else if (argnmatch(argv[i], "--nongreen-ignorecolumns=") || argnmatch(argv[i], "--bb2-ignorecolumns=")) { char *lp = strchr(argv[i], '='); nongreenignorecolumns = (char *) malloc(strlen(lp)+2); sprintf(nongreenignorecolumns, ",%s,", (lp+1)); } else if (argnmatch(argv[i], "--nongreen-colors=") || argnmatch(argv[i], "--bb2-colors=")) { char *lp = strchr(argv[i], '=') + 1; nongreencolors = colorset(lp, (1 << COL_GREEN)); } else if (argnmatch(argv[i], "--nongreen-ignorepurples") || argnmatch(argv[i], "--bb2-ignorepurples")) { nongreencolors = (nongreencolors & ~(1 << COL_PURPLE)); } else if (argnmatch(argv[i], "--nongreen-ignoredialups") || argnmatch(argv[i], "--bb2-ignoredialups")) { nongreennodialups = 1; } else if (argnmatch(argv[i], "--includecolumns=")) { char *lp = strchr(argv[i], '='); includecolumns = (char *) malloc(strlen(lp)+2); sprintf(includecolumns, ",%s,", (lp+1)); } else if (argnmatch(argv[i], "--eventignore=")) { char *lp = strchr(argv[i], '='); eventignorecolumns = (char *) malloc(strlen(lp)+2); sprintf(eventignorecolumns, ",%s,", (lp+1)); } else if (argnmatch(argv[i], "--doccgi=")) { char *lp = strchr(argv[i], '='); char *url = (char *)malloc(strlen(xgetenv("CGIBINURL"))+strlen(lp+1)+2); sprintf(url, "%s/%s", xgetenv("CGIBINURL"), lp+1); setdocurl(url); xfree(url); } else if (argnmatch(argv[i], "--docurl=")) { char *lp = strchr(argv[i], '='); setdocurl(lp+1); } else if (argnmatch(argv[i], "--no-doc-window")) { /* This is a no-op now */ } else if (argnmatch(argv[i], "--doc-window")) { setdoctarget("TARGET=\"_blank\""); } else if (argnmatch(argv[i], "--htmlextension=")) { char *lp = strchr(argv[i], '='); htmlextension = strdup(lp+1); } else if (argnmatch(argv[i], "--htaccess")) { char *lp = strchr(argv[i], '='); if (lp) htaccess = strdup(lp+1); else htaccess = ".htaccess"; } else if ((strcmp(argv[i], "--wml") == 0) || argnmatch(argv[i], "--wml=")) { char *lp = strchr(argv[i], '='); if (lp) { wapcolumns = (char *) malloc(strlen(lp)+2); sprintf(wapcolumns, ",%s,", (lp+1)); } enable_wmlgen = 1; } else if (argnmatch(argv[i], "--nstab=")) { char *lp = strchr(argv[i], '='); if (strlen(lp+1) > 0) { nssidebarfilename = strdup(lp+1); } else errprintf("--nstab requires a filename\n"); } else if (argnmatch(argv[i], "--nslimit=")) { char *lp = strchr(argv[i], '='); nssidebarcolorlimit = parse_color(lp+1); } else if (argnmatch(argv[i], "--rssversion=")) { char *lp = strchr(argv[i], '='); rssversion = strdup(lp+1); } else if (argnmatch(argv[i], "--rsslimit=")) { char *lp = strchr(argv[i], '='); rsscolorlimit = parse_color(lp+1); } else if (argnmatch(argv[i], "--rss")) { wantrss = 1; } else if (argnmatch(argv[i], "--rssextension=")) { char *lp = strchr(argv[i], '='); rssextension = strdup(lp+1); } else if (argnmatch(argv[i], "--reportopts=")) { char style[MAX_LINE_LEN]; unsigned int rstart, rend; int count = sscanf(argv[i], "--reportopts=%u:%u:%d:%s", &rstart, &rend, &dynamicreport, style); reportstart = rstart; reportend = rend; if (count < 2) { errprintf("Invalid --reportopts option: Must have start- and end-times\n"); return 1; } if (count < 3) dynamicreport = 1; if (count == 4) { if (strcmp(style, stylenames[STYLE_CRIT]) == 0) reportstyle = STYLE_CRIT; else if (strcmp(style, stylenames[STYLE_NONGR]) == 0) reportstyle = STYLE_NONGR; else reportstyle = STYLE_OTHER; } if (reportstart < 788918400) reportstart = 788918400; if (reportend > getcurrenttime(NULL)) reportend = getcurrenttime(NULL); if (xgetenv("XYMONREPWARN")) reportwarnlevel = atof(xgetenv("XYMONREPWARN")); if (xgetenv("XYMONREPGREEN")) reportgreenlevel = atof(xgetenv("XYMONREPGREEN")); if ((reportwarnlevel < 0.0) || (reportwarnlevel > 100.0)) reportwarnlevel = 97.0; if ((reportgreenlevel < 0.0) || (reportgreenlevel > 100.0)) reportgreenlevel = 99.995; select_headers_and_footers("rep"); sethostenv_report(reportstart, reportend, reportwarnlevel, reportgreenlevel); } else if (argnmatch(argv[i], "--csv=")) { char *lp = strchr(argv[i], '='); csvfile = strdup(lp+1); } else if (argnmatch(argv[i], "--csvdelim=")) { char *lp = strchr(argv[i], '='); csvdelim = *(lp+1); } else if (argnmatch(argv[i], "--snapshot=")) { char *lp = strchr(argv[i], '='); snapshot = atol(lp+1); select_headers_and_footers("snap"); sethostenv_snapshot(snapshot); } else if (strcmp(argv[i], "--pages-first") == 0) { hostsbeforepages = 0; } else if (strcmp(argv[i], "--pages-last") == 0) { hostsbeforepages = 1; } else if (argnmatch(argv[i], "--subpagecolumns=")) { char *lp = strchr(argv[i], '='); subpagecolumns = atoi(lp+1); if (subpagecolumns < 1) subpagecolumns=1; } else if (argnmatch(argv[i], "--maxrows=")) { char *lp = strchr(argv[i], '='); maxrowsbeforeheading = atoi(lp+1); if (maxrowsbeforeheading < 0) maxrowsbeforeheading=0; } else if (strcmp(argv[i], "--recentgifs") == 0) { use_recentgifs = 1; } else if (argnmatch(argv[i], "--recentgifs=")) { char *lp = strchr(argv[i], '='); use_recentgifs = 1; recentgif_limit = 60*durationvalue(lp+1); } else if (strcmp(argv[i], "--sort-group-only-items") == 0) { sort_grouponly_items = 1; } else if (argnmatch(argv[i], "--page-title=")) { char *lp = strchr(argv[i], '='); defaultpagetitle = strdup(lp+1); } else if (argnmatch(argv[i], "--dialupskin=")) { char *lp = strchr(argv[i], '='); dialupskin = strdup(lp+1); } else if (argnmatch(argv[i], "--reverseskin=")) { char *lp = strchr(argv[i], '='); reverseskin = strdup(lp+1); } else if (strcmp(argv[i], "--pagetitle-links") == 0) { pagetitlelinks = 1; } else if (strcmp(argv[i], "--pagetext-headings") == 0) { pagetextheadings = 1; } else if (strcmp(argv[i], "--underline-headings") == 0) { underlineheadings = 1; } else if (strcmp(argv[i], "--no-underline-headings") == 0) { underlineheadings = 0; } else if (strcmp(argv[i], "--no-eventlog") == 0) { nongreeneventlog = 0; } else if (argnmatch(argv[i], "--max-eventcount=")) { char *lp = strchr(argv[i], '='); nongreeneventlogmaxcount = atoi(lp+1); } else if (argnmatch(argv[i], "--max-eventtime=")) { char *lp = strchr(argv[i], '='); nongreeneventlogmaxtime = atoi(lp+1); } else if (argnmatch(argv[i], "--max-ackcount=")) { char *lp = strchr(argv[i], '='); nongreenacklogmaxcount = atoi(lp+1); } else if (argnmatch(argv[i], "--max-acktime=")) { char *lp = strchr(argv[i], '='); nongreenacklogmaxtime = atoi(lp+1); } else if (strcmp(argv[i], "--no-acklog") == 0) { nongreenacklog = 0; } else if (strcmp(argv[i], "--no-pages") == 0) { do_normal = 0; } else if ((strcmp(argv[i], "--no-nongreen") == 0) || (strcmp(argv[i], "--no-bb2") == 0)) { do_nongreen = 0; } else if (argnmatch(argv[i], "--noprop=")) { char *lp = strchr(argv[i], '='); nopropyellowdefault = (char *) malloc(strlen(lp)+2); sprintf(nopropyellowdefault, ",%s,", (lp+1)); errprintf("--noprop is deprecated - use --nopropyellow instead\n"); } else if (argnmatch(argv[i], "--nopropyellow=")) { char *lp = strchr(argv[i], '='); nopropyellowdefault = (char *) malloc(strlen(lp)+2); sprintf(nopropyellowdefault, ",%s,", (lp+1)); } else if (argnmatch(argv[i], "--nopropred=")) { char *lp = strchr(argv[i], '='); nopropreddefault = (char *) malloc(strlen(lp)+2); sprintf(nopropreddefault, ",%s,", (lp+1)); } else if (argnmatch(argv[i], "--noproppurple=")) { char *lp = strchr(argv[i], '='); noproppurpledefault = (char *) malloc(strlen(lp)+2); sprintf(noproppurpledefault, ",%s,", (lp+1)); } else if (argnmatch(argv[i], "--nopropack=")) { char *lp = strchr(argv[i], '='); nopropackdefault = (char *) malloc(strlen(lp)+2); sprintf(nopropackdefault, ",%s,", (lp+1)); } else if (strcmp(argv[i], "--bbpageONLY") == 0) { /* Deprecated */ errprintf("--bbpageONLY is deprecated - use --pageset=NAME to generate pagesets\n"); } else if (strcmp(argv[i], "--embedded") == 0) { embedded = 1; } else if (argnmatch(argv[i], "--pageset=")) { char *lp = strchr(argv[i], '='); pageset = strdup(lp+1); } else if (argnmatch(argv[i], "--template=")) { char *lp = strchr(argv[i], '='); lp++; select_headers_and_footers(lp); } else if (argnmatch(argv[i], "--tooltips=")) { char *lp = strchr(argv[i], '='); lp++; if (strcmp(lp, "always") == 0) tooltipuse = TT_ALWAYS; else if (strcmp(lp, "never") == 0) tooltipuse = TT_NEVER; else tooltipuse = TT_STDONLY; } else if (argnmatch(argv[i], "--purplelog=")) { char *lp = strchr(argv[i], '='); if (*(lp+1) == '/') purplelogfn = strdup(lp+1); else { purplelogfn = (char *) malloc(strlen(xgetenv("XYMONHOME"))+1+strlen(lp+1)+1); sprintf(purplelogfn, "%s/%s", xgetenv("XYMONHOME"), (lp+1)); } } else if (argnmatch(argv[i], "--report=") || (strcmp(argv[i], "--report") == 0)) { char *lp = strchr(argv[i], '='); if (lp) { egocolumn = strdup(lp+1); } else egocolumn = "xymongen"; timing = 1; } else if ( argnmatch(argv[i], "--criticallog=") || (strcmp(argv[i], "--criticallog") == 0) || argnmatch(argv[i], "--nklog=") || (strcmp(argv[i], "--nklog") == 0) ){ char *lp = strchr(argv[i], '='); if (lp) { logcritstatus = strdup(lp+1); } else logcritstatus = "critical"; } else if (strcmp(argv[i], "--timing") == 0) { timing = 1; } else if (strcmp(argv[i], "--debug") == 0) { debug = 1; } else if (strcmp(argv[i], "--no-update") == 0) { dontsendmessages = 1; } else if (strcmp(argv[i], "--loadhostsfromxymond") == 0) { loadhostsfromxymond = 1; } else if (strcmp(argv[i], "--version") == 0) { printf("xymongen version %s\n", VERSION); printf("\n"); exit(0); } else if ((strcmp(argv[i], "--help") == 0) || (strcmp(argv[i], "-?") == 0)) { printf("xymongen for Xymon version %s\n\n", VERSION); printf("Usage: %s [options] [WebpageDirectory]\n", argv[0]); printf("Options:\n"); printf(" --ignorecolumns=test[,test] : Completely ignore these columns\n"); printf(" --critical-reds-only : Only show red statuses on the Critical page\n"); printf(" --nongreen-ignorecolumns=test[,test]: Ignore these columns for the non-green page\n"); printf(" --nongreen-ignorepurples : Ignore all-purple hosts on non-green page\n"); printf(" --includecolumns=test[,test]: Always include these columns on non-green page\n"); printf(" --max-eventcount=N : Max number of events to include in eventlog\n"); printf(" --max-eventtime=N : Show events that occurred within the last N minutes\n"); printf(" --eventignore=test[,test] : Columns to ignore in non-green event-log display\n"); printf(" --no-eventlog : Do not generate the non-green eventlog display\n"); printf(" --no-acklog : Do not generate the non-green ack-log display\n"); printf(" --no-pages : Generate only the nongreen and critical pages\n"); printf(" --docurl=documentation-URL : Hostnames link to a general (dynamic) web page for docs\n"); printf(" --doc-window : Open doc-links in a new browser window\n"); printf(" --htmlextension=.EXT : Sets filename extension for generated file (default: .html\n"); printf(" --report[=COLUMNNAME] : Send a status report about the running of xymongen\n"); printf(" --reportopts=ST:END:DYN:STL : Run in Xymon Reporting mode\n"); printf(" --csv=FILENAME : For Xymon Reporting, output CSV file\n"); printf(" --csvdelim=CHARACTER : Delimiter in CSV file output (default: comma)\n"); printf(" --snapshot=TIME : Snapshot mode\n"); printf("\nPage layout options:\n"); printf(" --pages-first : Put page- and subpage-links before hosts (default)\n"); printf(" --pages-last : Put page- and subpage-links after hosts\n"); printf(" --subpagecolumns=N : Number of columns for links to pages and subpages\n"); printf(" --maxrows=N : Repeat column headings for every N hosts shown\n"); printf(" --recentgifs : Use xxx-recent.gif icons for newly changed tests\n"); printf(" --sort-group-only-items : Display group-only items in alphabetical order\n"); printf(" --page-title=TITLE : Set a default page title for all pages\n"); printf(" --dialupskin=URL : Use a different icon skin for dialup tests\n"); printf(" --reverseskin=URL : Use a different icon skin for reverse tests\n"); printf(" --pagetitle-links : Make page- and subpage-titles act as links\n"); printf(" --pagetext-headings : Use page texts as headings\n"); printf(" --no-underline-headings : Do not underline the page headings\n"); printf("\nStatus propagation control options:\n"); printf(" --noprop=test[,test] : Disable upwards status propagation when YELLOW\n"); printf(" --nopropred=test[,test] : Disable upwards status propagation when RED or YELLOW\n"); printf(" --noproppurple=test[,test] : Disable upwards status propagation when PURPLE\n"); printf("\nAlternate pageset generation support:\n"); printf(" --pageset=SETNAME : Generate non-standard pageset with tag SETNAME\n"); printf(" --template=TEMPLATE : template for header and footer files\n"); printf("\nAlternate output formats:\n"); printf(" --wml[=test1,test2,...] : Generate a small (All nongreen-style) WML page\n"); printf(" --nstab=FILENAME : Generate a Netscape Sidebar feed\n"); printf(" --nslimit=COLOR : Minimum color to include on Netscape sidebar\n"); printf(" --rss : Generate a RSS/RDF feed of alerts\n"); printf(" --rssextension=.EXT : Sets filename extension for RSS files (default: .rss\n"); printf(" --rssversion={0.91|0.92|1.0|2.0} : Specify RSS/RDF version (default: 0.91)\n"); printf(" --rsslimit=COLOR : Minimum color to include on RSS feed\n"); printf("\nDebugging/troubleshooting options:\n"); printf(" --timing : Collect timing information\n"); printf(" --debug : Debugging information\n"); printf(" --version : Show version information\n"); printf(" --purplelog=FILENAME : Create a log of purple hosts and tests\n"); exit(0); } else if (argnmatch(argv[i], "-")) { errprintf("Unknown option : %s\n", argv[i]); } else { /* Last argument is pagedir */ pagedir = strdup(argv[i]); } } /* In case they changed the name of our column ... */ if (egocolumn) setup_signalhandler(egocolumn); if (debug) { int i; printf("Command: xymongen"); for (i=1; (inext) { if (p->color > pagehead->color) pagehead->color = p->color; } xymon_color = pagehead->color; if (xgetenv("SUMMARY_SET_BKG") && (strcmp(xgetenv("SUMMARY_SET_BKG"), "TRUE") == 0)) { /* * Displayed summaries affect the Xymon page only, * but should not go into the color we report to * others. */ for (s=dispsums; (s); s = s->next) { if (s->color > pagehead->color) pagehead->color = s->color; } } add_timestamp("Color calculation done"); if (debug) dumpall(pagehead); /* Generate pages */ if (chdir(pagedir) != 0) { errprintf("Cannot change to webpage directory %s\n", pagedir); exit(1); } if (embedded) { /* Just generate that one page */ do_one_page(pagehead, NULL, 1); return 0; } /* The main page - xymon.html and pages/subpages thereunder */ add_timestamp("Xymon pagegen start"); if (reportstart && csvfile) { csv_availability(csvfile, csvdelim); } if (do_normal) { do_page_with_subs(pagehead, dispsums); } add_timestamp("Xymon pagegen done"); if (reportstart) { /* Reports end here */ return 0; } /* The full summary page - nongreen.html */ if (do_nongreen) { nongreen_color = do_nongreen_page(nssidebarfilename, PAGE_NONGREEN); add_timestamp("Non-green page generation done"); } /* Reduced summary (alerts) page - critical.html */ critical_color = do_nongreen_page(NULL, PAGE_CRITICAL); add_timestamp("Critical page generation done"); if (snapshot) { /* Snapshots end here */ return 0; } /* Send summary notices - only once, so not on pagesets */ if (pageset == NULL) { send_summaries(sumhead); add_timestamp("Summary transmission done"); } /* Generate WML cards */ if (enable_wmlgen) { do_wml_cards(pagedir); add_timestamp("WML generation done"); } /* Need to do this before sending in our report */ add_timestamp("Run completed"); /* Tell about us */ if (egocolumn) { char msgline[4096]; char *timestamps; long tasksleep = (xgetenv("TASKSLEEP") ? atol(xgetenv("TASKSLEEP")) : 300); int color; /* Go yellow if it runs for too long */ if (total_runtime() > tasksleep) { errprintf("WARNING: Runtime %ld longer than TASKSLEEP (%ld)\n", total_runtime(), tasksleep); } color = (errbuf ? COL_YELLOW : COL_GREEN); combo_start(); init_status(color); sprintf(msgline, "status %s.%s %s %s\n\n", xgetenv("MACHINE"), egocolumn, colorname(color), timestamp); addtostatus(msgline); sprintf(msgline, "xymongen for Xymon version %s\n", VERSION); addtostatus(msgline); addtostatus("\nStatistics:\n"); sprintf(msgline, " Hosts : %5d\n", hostcount); addtostatus(msgline); sprintf(msgline, " Pages : %5d\n", pagecount); addtostatus(msgline); sprintf(msgline, " Status messages : %5d\n", statuscount); addtostatus(msgline); sprintf(msgline, " - Red : %5d (%5.2f %%)\n", colorcount[COL_RED], ((100.0 * colorcount[COL_RED]) / statuscount)); addtostatus(msgline); sprintf(msgline, " - Red (non-propagating) : %5d (%5.2f %%)\n", colorcount_noprop[COL_RED], ((100.0 * colorcount_noprop[COL_RED]) / statuscount)); addtostatus(msgline); sprintf(msgline, " - Yellow : %5d (%5.2f %%)\n", colorcount[COL_YELLOW], ((100.0 * colorcount[COL_YELLOW]) / statuscount)); addtostatus(msgline); sprintf(msgline, " - Yellow (non-propagating) : %5d (%5.2f %%)\n", colorcount_noprop[COL_YELLOW], ((100.0 * colorcount_noprop[COL_YELLOW]) / statuscount)); addtostatus(msgline); sprintf(msgline, " - Clear : %5d (%5.2f %%)\n", colorcount[COL_CLEAR], ((100.0 * colorcount[COL_CLEAR]) / statuscount)); addtostatus(msgline); sprintf(msgline, " - Green : %5d (%5.2f %%)\n", colorcount[COL_GREEN], ((100.0 * colorcount[COL_GREEN]) / statuscount)); addtostatus(msgline); sprintf(msgline, " - Purple : %5d (%5.2f %%)\n", colorcount[COL_PURPLE], ((100.0 * colorcount[COL_PURPLE]) / statuscount)); addtostatus(msgline); sprintf(msgline, " - Blue : %5d (%5.2f %%)\n", colorcount[COL_BLUE], ((100.0 * colorcount[COL_BLUE]) / statuscount)); addtostatus(msgline); if (errbuf) { addtostatus("\n\nError output:\n"); addtostatus(errbuf); } show_timestamps(×tamps); addtostatus(timestamps); finish_status(); combo_end(); } else show_timestamps(NULL); return 0; } xymon-4.3.7/xymongen/wmlgen.h0000664000175000017500000000152011615341300015521 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon WML generator. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __WMLGEN_H__ #define __WMLGEN_H__ extern int enable_wmlgen; extern void do_wml_cards(char *webdir); #endif xymon-4.3.7/xymongen/xymongen.10000664000175000017500000006517411671641417016042 0ustar henrikhenrik.TH XYMONGEN 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymongen \- Xymon webpage generator .SH SYNOPSIS .B "xymongen -?" .br .B "xymongen --help" .br .B "xymongen --version" .br .B "xymongen [options] [output-directory]" .br (See the OPTIONS section for a description of the available command-line options). .SH DESCRIPTION \fBxymongen\fR generates the overview webpages for the Xymon monitor. These are the webpages that show the overall status of your hosts, not the detailed status pages for each test. Note: The data for the webpages is retrieved from the .I xymond(8) daemon, and xymongen uses the values of the XYMSRV / XYMSERVERS environment variables to determine the network address where xymond can be reached. If you have more than one server listed in XYMSERVERS, make sure the first one is the local Xymon server - this is the one that xymongen will query for data. .SH OPTIONS xymongen has a large number of command-line options. The options can be used to change the behaviour of xymongen and affect the web pages generated by it. .SH GENERAL OPTIONS .sp .IP "--help or -?" Provide a summary of available command-line options. .sp .IP "--version" Prints the version number of xymongen .sp .IP "--docurl=URL" This option is deprecated, use the HOSTDOCURL setting in .I xymonserver.cfg(5) instead. .sp .IP "--doccgi=URL" This option is deprecated, use the HOSTDOCURL setting in .I xymonserver.cfg(5) instead. .sp .IP "--doc-window" Causes links to documentation for hosts and services to open in a new window. The default is to show documentation in the same browser window as the Xymon status. .sp .IP "--htmlextension=.EXTENSION" Sets the filename extension used for the webpages generated by xymongen. By default, an extension of ".html" is used. Note that you need to specify the "dot". .sp .IP "--report[=COLUMNNAME]" With this option, xymongen will send a status message with details of how many hosts were processed, how many pages were generated, any errors that occurred during the run, and some timing statistics. The default columnname is "xymongen". .sp .IP "--htaccess[=htaccess-filename]" Create .htaccess files when new web page directories are created. The content of the .htaccess files are determined by the XYMONHTACCESS environment variable (for the top-level directory with xymon.html and nongreen.html); by the XYMONPAGEHTACCESS variable (for the page-level directories); and by the XYMONSUBPAGEHTACCESS variable for subpage- and subparent-level directories. The filename of the .htaccess files default to ".htaccess" if no filename is given with this option. The XYMONHTACCESS variable is copied verbatim into the top-level .htaccess file. The XYMONPAGEHTACCESS variable may contain a "%s" where the name of the page is inserted. The XYMONSUBPAGEHTACCESS variable may contain two "%s" instances: The first is replaced with the pagename, the second with the subpagename. .sp .IP "--max-eventcount=N" Limit the eventlog on the "All non-green" page to only N events. Default: 100. .sp .IP "--max-eventtime=N" Limit the eventlog on the "All non-green" page to events that happened within the past N minutes. Default: 240. .sp .IP "--no-eventlog" Disable the eventlog normally displayed on the "All non-green" page .sp .IP "--max-ackcount=N" Limit the acknowledgment log on the "All non-green" page to only N events. Default: 25. .sp .IP "--max-acktime=N" Limit the acknowledgment log on the "All non-green" page to acks that happened within the past N minutes. Default: 240. .sp .IP "--no-acklog" Disable the acknowledgement log normally displayed on the "All non-green" page. .sp .IP "--cricitcallog[=Critical log column]" This generates a text-based log of what is shown on the critical.html status page, and sends a status message for the Xymon server itself reflecting the color of the Critical status page. This allows you to track when problems have appeared on the critical status page. The logfile is stored in $XYMONSERVERLOGS/criticalstatus.log .sp .IP --loadhostsfromxymond Instead of reading the hosts.cfg file, xymongen will load the hosts.cfg configuration from the xymond daemon. This eliminates the need for reading the hosts.cfg, and if you have xymond and xymongen running on different hosts, it also eliminates the need for copying the hosts.cfg file between systems. Note that the "dispinclude" option in hosts.cfg is ignored when this option is enabled. .SH PAGE LAYOUT OPTIONS These options affect how the webpages generated by xymongen appear in the browser. .sp .IP "--pages-last" Put page- and subpage-links after hosts. .IP "--pages-first" Put page- and subpage-links before hosts (default). .sp These two options decide whether a page with links to subpages and hosts have the hosts or the subpages first. .sp .IP "--subpagecolumns=N" Determines the number of columns used for links to pages and subpages. The default is N=1. .sp .IP "--maxrows=N" Column headings on a page are by default only shown at the beginning of a page, subpage or group of hosts. This options causes the column headings to repeat for every N hosts shown. .sp .IP "--pagetitle-links" Normally, only the colored "dots" next to a page or subpage act as links to the page itself. With this option, the page title will link to the page also. .sp .IP "--pagetext-headings" Use the description text from the "page" or "subpage" tags as a heading for the page, instead of the "Pages hosted locally" or other standard heading. .sp .IP "--no-underline-headings" Normally, page headings are underlined using an HTML "horizontal ruler" tag. This option disables the underlining of headings. .sp .IP "--recentgifs[=MINUTES]" Use images named COLOR-recent.gif for tests, where the test status has changed within the past 24 hours. These GIF files need to be installed in the $XYMONHOME/www/gifs/ directory. By default, the threshold is set to 24 hours - if you want it differently, you can specify the time limit also. E.g. "--recentgifs=3h" will show the recent GIFs for only 3 hours after a status change. .sp .IP "--sort-group-only-items" In a normal "group-only" directive, you can specify the order in which the tests are displayed, from left to right. If you prefer to have the tests listed in alphabetical order, use this option - the page will then generate "group-only" groups like it generates normal groups, and sort the tests alphabetically. .sp .IP "--dialupskin=URL" If you want to visually show that a test is a dialup-test, you can use an alternate set of icons for the green/red/yellow>/etc. images by specifying this option. The URL parameter specified here overrides the normal setting from the XYMONSKIN environment variable, but only for dialup tests. .sp .IP "--reverseskin=URL" Same as "--dialupskin", but for reverse tests (tests with '!' in front). .sp .IP "--tooltips=[always,never,main]" Determines which pages use tooltips to show the description of the host (from the COMMENT entry in the .I hosts.cfg(5) file). If set to \fBalways\fR, tooltips are used on all pages. If set to \fBnever\fR, tooltips are never used. If set to \fBmain\fR, tooltips are used on the main pages, but not on the "All non-green" or "Critical systems" pages. .SH COLUMN SELECTION OPTIONS These options affect which columns (tests) are included in the webpages generated by xymongen. .sp .IP "--ignorecolumns=test[,test]" The given columns will be completely ignored by xymongen when generating webpages. Can be used to generate reports where you eliminate some of the more noisy tests, like "msgs". .sp .IP "--critical-reds-only" Only red status columns will be included on the Critical page. By default, the Critical page will contain hosts with red, yellow and clear status. .sp .IP "--nongreen-colors=COLOR[,COLOR]" Defines which colors cause a test to appear on the "All non-green" status page. COLOR is red, yellow or purple. The default is to include all three. .sp .IP "--nongreen-ignorecolumns=test[,test]" Same as the --ignorecolumns, but applies to hosts on the "All non-green" page only. .sp .IP "--nongreen-ignorepurples" Deprecated, use "--nongreen-colors" instead. .sp .IP "--nongreen-ignoredialups" Ignore all dialup hosts on the "All non-green" page, including the eventlog. .sp .IP "--no-nongreen" Do not generate the "All non-green" page. .sp .IP "--includecolumns=test[,test]" Always include these columns on "All non-green" page Will include certain columns on the nongreen.html page, regardless of its color. Normally, nongreen.html drops a test-column, if all tests are green. This can be used e.g. to always have a link to the trends column (with the RRD graphs) from your nongreen.html page. .sp .IP "--eventignore=test[,test]" Ignore these tests in the "All non-green" event log display. .SH STATUS PROPAGATION OPTIONS These options suppress the normal propagation of a status upwards in the page hierarchy. Thus, you can have a test with status yellow or red, but still have the entire page green. It is useful for tests that need not cause an alarm, but where you still want to know the actual status. These options set global defaults for all hosts; you can use the NOPROPRED and NOPROPYELLOW tags in the .I hosts.cfg(5) file to apply similar limits on a per-host basis. .sp .IP "--nopropyellow=test[,test] or --noprop=test[,test] Disable upwards status propagation when YELLOW. The "--noprop" option is deprecated and should not be used. .sp .IP "--noproppurple=test[,test]" Disable upwards status propagation when PURPLE. .sp .IP "--nopropred=test[,test]" Disable upwards status propagation when RED or YELLOW. .sp .IP "--nopropack=test[,test]" Disable upwards status propagation when status has been acknowledged. If you want to disable all acked tests from being propageted, use "--nopropack=*". .SH PURPLE STATUS OPTIONS Purple statuses occur when reporting of a test status stops. A test status is valid for a limited amount of time - normally 30 minutes - and after this time, the test becomes purple. .sp .IP "--purplelog=FILENAME" Generate a logfile of all purple status messages. .SH ALTERNATE PAGESET OPTIONS .sp .IP "--pageset=PAGESETNAME" Build webpages for an alternate pageset than the default. See the PAGESETS section below. .sp .IP "--template=TEMPLATE" Use an alternate template for header and footer files. Typically used together the the "--pageset" option; see the PAGESETS section below. .SH ALTERNATE OUTPUT FORMATS .sp .IP "--wml[=test1,test2,...]" This option causes xymongen to generate a set of WML "card" files that can be accessed by a WAP device (cell phone, PDA etc.) The generated files contain the hosts that have a RED or YELLOW status on tests specified. This option can define the default tests to include - the defaults can be overridden or amended using the "WML:" or "NK:" tags in the .I hosts.cfg(5) file. If no tests are specified, all tests will be included. .sp .IP "--nstab=FILENAME" Generate an HTML file suitable for a Netscape 6/Mozilla sidebar entry. To actually enable your users to obtain such a sidebar entry, you need this Javascript code in a webpage (e.g. you can include it in the $XYMONHOME/web/stdnormal_header file): .sp .sp and then you can include a "Add this to sidebar" link using this as a template: .sp Add to Sidebar .sp or if you prefer to have the standard Netscape "Add tab" button, you would do it with .sp .br [Add Sidebar] .br .sp The "add-button.gif" is available from Netscape at http://developer.netscape.com/docs/manuals/browser/sidebar/add-button.gif. If FILENAME does not begin with a slash, the Netscape sidebar file is placed in the $XYMONHOME/www/ directory. .IP "--nslimit=COLOR" The minimum color to include in the Netscape Sidebar - default is "red", meaning only critical alerts are included. If you want to include warnings also, use "--nslimit=yellow". .IP "--rss Generate RSS/RDF content delivery stream of your Xymon alerts. This output format can be dynamically embedded in other web pages, much like the live newsfeeds often seen on web sites. Two RSS files will be generated, one reflects the "All non-green" page, the other reflects the "Critical" page. They will be in the "nongreen.rss" and "critical.rss" files, respectively. In addition, an RSS file will be generated for each page and/or subpage listing the hosts present on that page or subpage. .br The FILENAME parameter previously allowed on the --rss option is now obsolete. .br For more information about RSS/RDF content feeds, please see http://www.syndic8.com/. .sp .IP "--rssextension=.EXTENSION" Sets the filename extension used for the RSS files generated by xymongen. By default, an extension of ".rss" is used. Note that you need to specify the "dot". .sp .IP "--rssversion={0.91|0.92|1.0|2.0}" The desired output format of the RSS/RDF feed. Version 0.91 appears to be the most commonly used format, and is the default if this option is omitted. .sp .IP "--rsslimit=COLOR" The minimum color to include in the RSS feed - default is "red", meaning only critical alerts are included. If you want to include warnings also, use "--rsslimit=yellow". .SH OPTIONS USED BY CGI FRONT-ENDS .IP "--reportopts=START:END:DYNAMIC:STYLE" Invoke xymongen in report-generation mode. This is normally used by the .I report.cgi(1) CGI script, but may also be used directly when pre-generating reports. The START parameter is the start-time for the report in Unix time_t format (seconds since Jan 1st 1970 00:00 UTC); END is the end-time for the report; DYNAMIC is 0 for a pre-built report and 1 for a dynamic (on-line) report; STYLE is "crit" to include only critical (red) events, "nongr" to include all non-green events, and "all" to include all events. .sp .IP "--csv=FILENAME" Used together with --reportopts, this causes xymongen to generate an availability report in the form of a comma-separated values (CSV) file. This format is commonly used for importing into spreadsheets for further processing. .br The CSV file includes Unix timestamps. To display these as human readable times in Excel, the formula \fB=C2/86400+DATEVALUE(1-jan-1970)\fR (if you have the Unix timestamp in the cell C2) can be used. The result cell should be formatted as a date/time field. Note that the timestamps are in UTC, so you may also need to handle local timezone and DST issues yourself. .sp .IP "--csvdelim=DELIMITER" By default, a comma is used to delimit fields in the CSV output. Some non-english spreadsheets use a different delimiter, typically semi-colon. To generate a CSV file with the proper delimiter, you can use this option to set the character used as delimiter. E.g. "--csvdelim=;" - note that this normally should be in double quotes, to prevent the Unix shell from interpreting the delimiter character as a command-line delimiter. .sp .IP "--snapshot=TIME" Generate a snapshot of the Xymon pages, as they appeared at TIME. TIME is given as seconds since Jan 1st 1970 00:00 UTC. Normally used via the .I snapshot.cgi(1) CGI script. .SH DEBUGGING OPTIONS .sp .IP "--debug" Causes xymongen to dump large amounts of debugging output to stdout, if it was compiled with the -DDEBUG enabled. When reporting a problem with xymongen, please try to reproduce the problem and provide the output from running xymongen with this option. .sp .IP "--timing" Dump information about the time spent by various parts of xymongen to stdout. This is useful to see what part of the processing is responsible for the run-time of xymongen. .br Note: This information is also provided in the output sent to the Xymon display when using the "--report" option. .SH BUILDING ALTERNATE PAGESETS With version 1.4 of xymongen comes the possibility to generate multiple sets of pages from the same data. .br Suppose you have two groups of people looking at the Xymon webpages. Group A wants to have the hosts grouped by the client, they belong to. This is how you have Xymon set up - the default pageset. Now group B wants to have the hosts grouped by operating system - let us call it the "os" set. Then you would add the page layout to hosts.cfg like this: .sp ospage win Microsoft Windows .br ossubpage win-nt4 MS Windows NT 4 .br osgroup NT4 File servers .br osgroup NT4 Mail servers .br ossubpage win-xp MS Windows XP .br ospage unix Unix .br ossubpage unix-sun Solaris .br ossubpage unix-linux Linux .sp This defines a set of pages with one top-level page (the xymon.html page), two pages linked from xymon.html (win.html and unix.html), and from e.g. the win.html page there are subpages win-nt4.html and win-xp.html .br The syntax is identical to the normal "page" and "subpage" directives in hosts.cfg, but the directive is prefixed with the pageset name. Dont put any hosts in-between the page and subpage directives - just add all the directives at the top of the hosts.cfg file. .br How do you add hosts to the pages, then ? Simple - just put a tag "OS:win-xp" on the host definition line. The "OS" must be the same as prefix used for the pageset names, but in uppercase. The "win-xp" must match one of the pages or subpages defined within this pageset. E.g. .sp 207.46.249.190 www.microsoft.com # OS:win-xp http://www.microsoft.com/ .br 64.124.140.181 www.sun.com # OS:unix-sun http://www.sun.com/ .sp If you want the host to appear inside a group defined on that page, you must identify the group by number, starting at 1. E.g. to put a host inside the "NT4 Mail servers" group in the example above, use "OS:win-nt4,2" (the second group on the "win-nt4" page). .br If you want the host to show up on the frontpage instead of a subpage, use "OS:*" . .sp All of this just defines the layout of the new pageset. To generate it, you must run xymongen once for each pageset you define - i.e. create an extension script like this: .IP .nf #!/bin/sh XYMONWEB="/xymon/os" $XYMONHOME/bin/xymongen \\ --pageset=os --template=os \\ $XYMONHOME/www/os/ .fi .LP Save this to $XYMONHOME/ext/os-display.sh, and set this up to run as a Xymon extension; this means addng an extra section to tasks.cfg to run it. This generates the pages. There are some important options used here: .br * XYMONWEB="/xymon/os" environment variable, and the "$XYMONHOME/www/os/" option work together, and places the new pageset HTML files in a subdirectory off the normal Xymon webroot. If you normally access the Xymon pages as "http://xymon.acme.com/xymon/", you will then access the new pageset as "http://xymon.acme.com/xymon/os/" NB: The directory given as XYMONWEB must contain a symbolic link to the $XYMONHOME/www/html/ directory, or links to individual status messages will not work. Similar links should be made for the gifs/, help/ and notes/ directories. .br * "--pageset=os" tells xymongen to structure the webpages using the "os" layout, instead of the default layout. .br * "--template=os" tells xymongen to use a different set of header- and footer-templates. Normally xymongen uses the standard template in $XYMONHOME/web/stdnormal_header and .../stdnormal_footer - with this option, it will instead use the files "os_header" and "os_footer" from the $XYMONHOME/web/ directory. This allows you to customize headers and footers for each pageset. If you just want to use the normal template, you can omit this option. .SH USING XYMONGEN FOR REPORTS xymongen reporting is implemented via drop-in replacements for the standard Xymon reporting scripts (report.sh and reportlog.sh) installed in your webservers cgi-bin directory. These two shell script have been replaced with two very small shell-scripts, that merely setup the Xymon environment variables, and invoke the .I report.cgi(1) or .I reportlog.cgi(1) scripts in $XYMONHOME/bin/ You can use xymongen command-line options when generating reports, e.g. to exclude certain types of tests (e.g. "--ignorecolumns=msgs") from the reports, to specify the name of the trends- and info- columns that should not be in the report, or to format the report differently (e.g. "--subpagecolumns=2"). If you want certain options to be used when a report is generated from the web interface, put these options into your $XYMONHOME/etc/xymonserver.cfg file in the XYMONGENREPOPTS environment variable. The report files generated by xymongen are stored in individual directories (one per report) below the $XYMONHOME/www/rep/ directory. These should be automatically cleaned up - as new reports are generated, the old ones get removed. After installing, try generating a report. You will probably see that the links in the upper left corner (to ack.html, nongreen.html etc.) no longer works. To fix these, change your $XYMONHOME/web/repnormal_header file so these links do not refer to "&XYMONWEB" but to the normal URL prefix for your Xymon pages. .SH SLA REPORTING xymongen reporting allows for the generation of true SLA (Service Level Agreement) reports, also for service periods that are not 24x7. This is enabled by defining a "REPORTTIME:timespec" tag for the hosts to define the service period, and optionally a "WARNPCT:level" tag to define the agreed availability. Note: See .I hosts.cfg(5) for the exact syntax of these options. "REPORTTIME:timespec" specifies the time of day when the service is expected to be up and running. By default this is 24 hours a day, all days of the week. If your SLA only covers Mon-Fri 7am - 8pm, you define this as "REPORTTIME=W:0700:2000", and the report generator will then compute both the normal 24x7 availability but also a "SLA availability" which only takes the status of the host during the SLA period into account. The DOWNTIME:timespec parameter affects the SLA availability calculation. If an outage occurs during the time defined as possible "DOWNTIME", then the failure is reported with a status of "blue". (The same color is used if you "disable" then host using the Xymon "disable" function). The time when the test status is "blue" is not included in the SLA calculation, neither in the amount of time where the host is considered down, nor in the total amount of time that the report covers. So "blue" time is effectively ignored by the SLA availability calculation, allowing you to have planned downtime without affecting the reported SLA availability. Example: A host has "DOWNTIME:*:0700:0730 REPORTTIME=W:0600:2200" because it is rebooted every day between 7am and 7.30am, but the service must be available from 6am to 10pm. For the day of the report, it was down from 7:10am to 7:15am (the planned reboot), but also from 9:53pm to 10:15pm. So the events for the day are: 0700 : green for 10 minutes (600 seconds) 0710 : blue for 5 minutes (300 seconds) 0715 : green for 14 hours 38 minutes (52680 seconds) 2153 : red for 22 minutes (1320 seconds) 2215 : green The service is available for 600+52680 = 53280 seconds. It is down (red) for 420 seconds (the time from 21:53 until 22:00 when the SLA period ends). The total time included in the report is 15 hours (7am - 10pm) except the 5 minutes blue = 53700 seconds. So the SLA availability is 53280/53700 = 99,22% The "WARNPCT:level" tag is supported in the hosts.cfg file, to set the availability threshold on a host-by-host basis. This threshold determines whether a test is reported as green, yellow or red in the reports. A default value can be set for all hosts with the via the XYMONREPWARN environment variable, but overridden by this tag. The level is given as a percentage, e.g. "WARNPCT:98.5" .SH PRE-GENERATED REPORTS Normally, xymongen produce reports that link to dynamically generated webpages with the detailed status of a test (via the reportlog.sh CGI script). It is possible to have xymongen produce a report without these dynamic links, so the report can be exported to another server. It may also be useful to pre-generate the reports, to lower the load by having multiple users generate the same reports. To do this, you must run xymongen with the "--reportopts" option to select the time interval that the report covers, the reporting style (critical, non-green, or all events), and to request that no dynamic pages are to be generated. The syntax is: xymongen --reportopts=starttime:endtime:nodynamic:style "starttime" and "endtime" are specified as Unix time_t values, i.e. seconds since Jan 1st 1970 00:00 GMT. Fortunately, this can easily be computed with the GNU date utility if you use the "+%s" output option. If you don't have the GNU date utility, either pick that up from www.gnu.org; or you can use the "etime" utility for the same purpose, which is available from the archive at www.deadcat.net. "nodynamic" is either 0 (for dynamic pages, the default) or 1 (for no dynamic, i.e. pre-generated, pages). "style" is either "crit" (include critical i.e. red events only), "nongr" (include all non-green events), or "all" (include all events). Other xymongen options can be used, e.g. "--ignorecolumns" if you want to exclude certain tests from the report. You will normally also need to specify the XYMONWEB environment variable (it must match the base URL for where the report will be made accessible from), and an output directory where the report files are saved. If you specify XYMONWEB, you should probably also define the XYMONHELPSKIN and XYMONNOTESSKIN environment variables. These should point to the URL where your Xymon help- and notes-files are located; if they are not defined, the links to help- and notes-files will point inside the report directory and will probably not work. So a typical invocation of xymongen for a static report would be: START=`date +%s --date="22 Jun 2003 00:00:00"` END=`date +%s --date="22 Jun 2003 23:59:59"` XYMONWEB=/reports/bigbrother/daily/2003/06/22 \\ XYMONHELPSKIN=/xymon/help \\ XYMONNOTESSKIN=/xymon/notes \\ xymongen --reportopts=$START:$END:1:crit \\ --subpagecolumns=2 \\ /var/www/docroot/reports/xymon/daily/2003/06/22 The "XYMONWEB" setting means that the report will be available with a URL of "http://www.server.com/reports/xymon/daily/2003/06/22". The report contains internal links that use this URL, so it cannot be easily moved to another location. The last parameter is the corresponding physical directory on your webserver matching the XYMONWEB URL. You can of course create the report files anywhere you like - perhaps on another machine - and then move them to the webserver later on. Note how the .I date(1) utility is used to calculate the start- and end-time parameters. .SH "SEE ALSO" hosts.cfg(5), xymonserver.cfg(5), tasks.cfg(5), report.cgi(1), snapshot.cgi(1), xymon(7) xymon-4.3.7/xymongen/debug.h0000664000175000017500000000230411615341300015317 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __DEBUG_H_ #define __DEBUG_H_ extern int timing; extern void add_timestamp(const char *msg); extern void show_timestamps(char **buffer); extern long total_runtime(void); extern const char *textornull(const char *text); extern void dumphosts(host_t *head, char *prefix); extern void dumpgroups(group_t *head, char *prefix, char *hostprefix); extern void dumphostlist(hostlist_t *head); extern void dumpstatelist(state_t *head); extern void dumpall(xymongen_page_t *head); #endif xymon-4.3.7/xymongen/csvreport.c0000664000175000017500000000474511615341300016266 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #include #include #include #include "csvreport.h" #include "util.h" void csv_availability(char *fn, char csvdelim) { hostlist_t *hl; FILE *fd; char *header = "Hostname,Testname,Start,End,24x7 availability %,24x7 green %,24x7 green secs,24x7 clear %,24x7 clear secs,24x7 blue %,24x7 blue secs,24x7 purple %,24x7 purple secs,24x7 yellow %,24x7 yellow secs,24x7 red %,24x7 red secs,SLA availability %,SLA green %,SLA green secs,SLA clear %,SLA clear secs,SLA blue %,SLA blue secs,SLA purple %,SLA purple secs,SLA yellow %,SLA yellow secs,SLA red %,SLA red secs"; fd = fopen(fn, "w"); if (fd == NULL) { errprintf("Cannot open output file %s: %s\n", fn, strerror(errno)); return; } if (csvdelim != ',') { char *p, *newheader; newheader = strdup(header); p = newheader; while ((p = strchr(p, ',')) != NULL) *p = csvdelim; header = newheader; } fprintf(fd, "%s\n", header); for (hl = hostlistBegin(); (hl); hl = hostlistNext()) { host_t *hwalk = hl->hostentry; entry_t *ewalk; int i; for (ewalk = hwalk->entries; (ewalk); ewalk = ewalk->next) { fprintf(fd, "%s%c%s%c%ld%c%ld", hwalk->hostname, csvdelim, ewalk->column->name, csvdelim, ewalk->repinfo->reportstart, csvdelim, reportend); fprintf(fd, "%c%.2f", csvdelim, ewalk->repinfo->fullavailability); for (i=0; (irepinfo->fullpct[i], csvdelim, ewalk->repinfo->fullduration[i]); } fprintf(fd, "%c%.2f", csvdelim, ewalk->repinfo->reportavailability); for (i=0; (irepinfo->reportpct[i], csvdelim, ewalk->repinfo->reportduration[i]); } fprintf(fd, "\n"); } } fclose(fd); } xymon-4.3.7/xymongen/loaddata.h0000664000175000017500000000206111615341300016002 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __LOADDATA_H_ #define __LOADDATA_H_ extern int statuscount; extern char *ignorecolumns; extern char *dialupskin; extern char *reverseskin; extern time_t recentgif_limit; extern char *purplelogfn; extern int colorcount[]; extern int colorcount_noprop[]; extern state_t *load_state(dispsummary_t **sumhead); #endif xymon-4.3.7/xymongen/rssgen.c0000664000175000017500000002103111615341300015523 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon webpage generator tool. */ /* */ /* This file contains code to generate RSS/RDF format output of alerts. */ /* It is heavily influenced by Jeff Stoner's bb_content-feed script. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: rssgen.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include "xymongen.h" #include "util.h" #include "rssgen.h" char *rssversion = "0.91"; int rsscolorlimit = COL_RED; int nssidebarcolorlimit = COL_RED; char *rsstitle = "Xymon Critical Alerts"; #define RSS091 0 #define RSS092 1 #define RSS10 2 #define RSS20 3 static int rssver = 0; static int ttlvalue = 300; static int anyshown = 0; static void initial_rss_setup(void) { static int hasrun = 0; if (hasrun) return; if (xgetenv("XYMONRSSTITLE")) rsstitle = strdup(xgetenv("XYMONRSSTITLE")); if (strcmp(rssversion, "0.91") == 0) rssver = RSS091; else if (strcmp(rssversion, "0.92") == 0) rssver = RSS092; else if (strcmp(rssversion, "1.0") == 0) rssver = RSS10; else if (strcmp(rssversion, "2.0") == 0) rssver = RSS20; else { errprintf("Unknown RSS version requested (%s), using 0.91\n", rssversion); rssver = RSS091; } ttlvalue = (xgetenv("TASKSLEEP") ? (atoi(xgetenv("TASKSLEEP")) / 60) : 5); } void do_rss_header(FILE *fd) { if (fd == NULL) return; initial_rss_setup(); switch (rssver) { case RSS091: fprintf(fd, "\n"); fprintf(fd, "\n"); fprintf(fd, "\n"); fprintf(fd, " %s\n", rsstitle); fprintf(fd, " %s/\n", xgetenv("XYMONWEBHOSTURL")); fprintf(fd, " Last updated on %s\n", timestamp); break; case RSS092: fprintf(fd, "\n"); fprintf(fd, "\n"); fprintf(fd, "\n"); fprintf(fd, " %s\n", rsstitle); fprintf(fd, " %s/\n", xgetenv("XYMONWEBHOSTURL")); fprintf(fd, " Last updated on %s\n", timestamp); fprintf(fd, " \n"); fprintf(fd, " %s/gifs/bblogo.gif\n", xgetenv("XYMONWEBHOSTURL")); fprintf(fd, " Xymon\n"); fprintf(fd, " http://xymon.sourceforge.net/\n"); fprintf(fd, " \n"); break; case RSS10: fprintf(fd, "\n"); fprintf(fd, "\n"); fprintf(fd, " \n", xgetenv("XYMONWEBHOSTURL")); fprintf(fd, " %s\n", rsstitle); fprintf(fd, " %s/\n", xgetenv("XYMONWEBHOSTURL")); fprintf(fd, " Last updated on %s\n", timestamp); fprintf(fd, " \n"); break; case RSS20: fprintf(fd, "\n"); fprintf(fd, "\n"); fprintf(fd, " \n"); fprintf(fd, " %s\n", rsstitle); fprintf(fd, " %s/\n", xgetenv("XYMONWEBHOSTURL")); fprintf(fd, " Last updated on %s\n", timestamp); fprintf(fd, " %d\n", ttlvalue); break; } anyshown = 0; } void do_rss_item(FILE *fd, host_t *h, entry_t *e) { if (fd == NULL) return; if (h->color < rsscolorlimit) return; if (e->color < rsscolorlimit) return; anyshown = 1; switch (rssver) { case RSS091: case RSS092: case RSS20: fprintf(fd, " \n"); break; case RSS10: fprintf(fd, " \n", xgetenv("XYMONWEBHOSTURL"), htmlextension); break; } fprintf(fd, " %s (%s)\n", h->hostname, e->column->name); fprintf(fd, " "); if (generate_static()) { /* * Dont use htmlextension here - the .html files are generated by bbd. */ fprintf(fd, "%s/html/%s.%s.html", xgetenv("XYMONWEBHOSTURL"), h->hostname, e->column->name); } else { fprintf(fd, "%s%s", xgetenv("XYMONWEBHOST"), hostsvcurl(h->hostname, e->column->name, 1)); } fprintf(fd, "\n"); if (e->shorttext) { char *inpos = e->shorttext; int len; char savech; fprintf(fd, ""); /* Must escape any '&', '<' and '>' -characters, or RSS readers will choke on them */ while (*inpos) { len = strcspn(inpos, "&<>"); savech = *(inpos+len); *(inpos+len) = '\0'; fprintf(fd, "%s", inpos); *(inpos+len) = savech; inpos += len; if (savech != '\0') { switch (savech) { case '&': fprintf(fd, "&"); break; case '>': fprintf(fd, ">"); break; case '<': fprintf(fd, "<"); break; } inpos++; /* Skip the escaped char we just output */ } } fprintf(fd, "\n"); } fprintf(fd, " \n"); } void do_rss_footer(FILE *fd) { if (fd == NULL) return; if (!anyshown) { fprintf(fd, " \n"); fprintf(fd, " No Critical Alerts\n"); fprintf(fd, " %s/\n", xgetenv("XYMONWEBHOSTURL")); fprintf(fd, " \n"); } switch (rssver) { case RSS091: case RSS092: case RSS20: fprintf(fd, " \n"); fprintf(fd, "\n"); break; case RSS10: fprintf(fd, "\n"); break; } } void do_netscape_sidebar(char *nssidebarfilename, host_t *hosts) { FILE *fd; char tmpfn[PATH_MAX]; char destfn[PATH_MAX]; int ttlvalue; host_t *h; int anyshown; if (nssidebarfilename == NULL) return; if (xgetenv("XYMONRSSTITLE")) rsstitle = strdup(xgetenv("XYMONRSSTITLE")); ttlvalue = (xgetenv("TASKSLEEP") ? atoi(xgetenv("TASKSLEEP")) : 300); if (*nssidebarfilename == '/') { sprintf(tmpfn, "%s.tmp", nssidebarfilename); sprintf(destfn, "%s", nssidebarfilename); } else { sprintf(tmpfn, "%s/www/%s.tmp", xgetenv("XYMONHOME"), nssidebarfilename); sprintf(destfn, "%s/www/%s", xgetenv("XYMONHOME"), nssidebarfilename); } fd = fopen(tmpfn, "w"); if (fd == NULL) { errprintf("Cannot create Netscape sidebar outputfile %s\n", tmpfn); return; } fprintf(fd, "\n"); fprintf(fd, "\n"); fprintf(fd, " \n"); fprintf(fd, " %s\n", rsstitle); fprintf(fd, " \n"); fprintf(fd, " \n", ttlvalue, xgetenv("XYMONWEBHOSTURL"), nssidebarfilename); fprintf(fd, " \n"); fprintf(fd, " \n"); fprintf(fd, " Last updated:
%s
\n", timestamp); fprintf(fd, " \n"); fprintf(fd, " \n"), fprintf(fd, "\n"); fclose(fd); if (rename(tmpfn, destfn) != 0) { errprintf("Cannot move file %s to destination %s\n", tmpfn, destfn); } return; } xymon-4.3.7/xymongen/loadlayout.h0000664000175000017500000000273111615341300016412 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __LOADLAYOUT_H__ #define __LOADLAYOUT_H__ extern int hostcount; extern int pagecount; extern xymongen_page_t *load_layout(char *pgset); /* Needed by the summary handling */ extern host_t *init_host(char *hostname, int issummary, char *displayname, char *clientalias, char *comment, char *description, int ip1, int ip2, int ip3, int ip4, int dialup, double warnpct, int warnstops, char *reporttime, char *alerts, int crittime, char *waps, char *nopropyellowtests, char *nopropredtests, char *noproppurpletests, char *nopropacktests); extern char *nopropyellowdefault; extern char *nopropreddefault; extern char *noproppurpledefault; extern char *nopropackdefault; extern char *wapcolumns; extern time_t snapshot; #endif xymon-4.3.7/xymongen/wmlgen.c0000664000175000017500000003040711615341300015522 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon WML generator. */ /* */ /* This file contains code to generate the WAP/WML documents showing the */ /* Xymon status. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: wmlgen.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include "xymongen.h" #include "wmlgen.h" #include "util.h" int enable_wmlgen = 0; static char wmldir[PATH_MAX]; static void delete_old_cards(char *dirname) { DIR *xymoncards; struct dirent *d; struct stat st; time_t now = getcurrenttime(NULL); char fn[PATH_MAX]; xymoncards = opendir(dirname); if (!xymoncards) { errprintf("Cannot read directory %s\n", dirname); return; } chdir(dirname); while ((d = readdir(xymoncards))) { strcpy(fn, d->d_name); stat(fn, &st); if ((fn[0] != '.') && S_ISREG(st.st_mode) && (st.st_mtime < (now-3600))) { unlink(fn); } } closedir(xymoncards); } static char *wml_colorname(int color) { switch (color) { case COL_GREEN: return "GR"; break; case COL_RED: return "RE"; break; case COL_YELLOW: return "YE"; break; case COL_PURPLE: return "PU"; break; case COL_CLEAR: return "CL"; break; case COL_BLUE: return "BL"; break; } return ""; } static void wml_header(FILE *output, char *cardid, int idpart) { fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n"); fprintf(output, "\n", cardid, idpart); } static void generate_wml_statuscard(host_t *host, entry_t *entry) { char fn[PATH_MAX]; FILE *fd; char *msg = NULL, *logbuf = NULL; char l[MAX_LINE_LEN], lineout[MAX_LINE_LEN]; char *p, *outp, *nextline; char xymondreq[1024]; int xymondresult; sendreturn_t *sres; sres = newsendreturnbuf(1, NULL); sprintf(xymondreq, "xymondlog %s.%s", host->hostname, entry->column->name); xymondresult = sendmessage(xymondreq, NULL, XYMON_TIMEOUT, sres); logbuf = getsendreturnstr(sres, 1); freesendreturnbuf(sres); if ((xymondresult != XYMONSEND_OK) || (logbuf == NULL) || (strlen(logbuf) == 0)) { errprintf("WML: Status not available\n"); return; } msg = strchr(logbuf, '\n'); if (msg) { msg++; } else { errprintf("WML: Unable to parse log data\n"); xfree(logbuf); return; } nextline = msg; sprintf(fn, "%s/%s.%s.wml", wmldir, host->hostname, entry->column->name); fd = fopen(fn, "w"); if (fd == NULL) { errprintf("Cannot create file %s\n", fn); return; } wml_header(fd, "name", 1); fprintf(fd, "

\n"); fprintf(fd, "Host
\n", host->hostname); fprintf(fd, "%s

\n", timestamp); fprintf(fd, "

\n"); fprintf(fd, "%s.%s

\n", host->hostname, entry->column->name); /* * We need to parse the logfile a bit to get a decent WML * card that contains the logfile. bbd does this for * HTML, we need to do it ourselves for WML. * * Empty lines are removed. * DOCTYPE lines (if any) are removed. * "http://" is removed * "" tags are replaced with a newline. * All HTML tags are removed * "&COLOR" is replaced with the shortname color * "<", ">", "&", "\"" and "\'" are replaced with the coded name so they display correctly. */ while (nextline) { p = strchr(nextline, '\n'); if (p) *p = '\0'; strcpy(l, nextline); if (p) nextline = p+1; else nextline = NULL; outp = lineout; for (p=l; (*p && isspace((int) *p)); p++) ; if (strlen(p) == 0) { /* Empty line - ignore */ } else if (strstr(l, "DOCTYPE")) { /* DOCTYPE - ignore */ } else { for (p=l; (*p); ) { if (strncmp(p, "http://", 7) == 0) { p += 7; } else if (strncasecmp(p, "", 4) == 0) { strcpy(outp, "
"); outp += 5; p += 4; } else if (*p == '<') { char *endtag, *newstarttag; /* * Possibilities: * - : Drop it * - < : Output the < equivalent * - <<< : Handle them one '<' at a time */ endtag = strchr(p+1, '>'); newstarttag = strchr(p+1, '<'); if ((endtag == NULL) || (newstarttag && (newstarttag < endtag))) { /* Single '<', or new starttag before the end */ strcpy(outp, "<"); outp += 4; p++; } else { /* Drop all html tags */ *outp = ' '; outp++; p = endtag+1; } } else if (*p == '>') { strcpy(outp, ">"); outp += 4; p++; } else if (strncmp(p, "&red", 4) == 0) { strcpy(outp, "red"); outp += 10; p += 4; } else if (strncmp(p, "&green", 6) == 0) { strcpy(outp, "green"); outp += 12; p += 6; } else if (strncmp(p, "&purple", 7) == 0) { strcpy(outp, "purple"); outp += 13; p += 7; } else if (strncmp(p, "&yellow", 7) == 0) { strcpy(outp, "yellow"); outp += 13; p += 7; } else if (strncmp(p, "&clear", 6) == 0) { strcpy(outp, "clear"); outp += 12; p += 6; } else if (strncmp(p, "&blue", 5) == 0) { strcpy(outp, "blue"); outp += 11; p += 5; } else if (*p == '&') { strcpy(outp, "&"); outp += 5; p++; } else if (*p == '\'') { strcpy(outp, "'"); outp += 6; p++; } else if (*p == '\"') { strcpy(outp, """); outp += 6; p++; } else { *outp = *p; outp++; p++; } } } *outp = '\0'; if (strlen(lineout)) fprintf(fd, "%s\n
\n", lineout); } fprintf(fd, "

\n"); fclose(fd); if (logbuf) xfree(logbuf); } void do_wml_cards(char *webdir) { FILE *nongreenfd, *hostfd; char nongreenfn[PATH_MAX], hostfn[PATH_MAX]; hostlist_t *h; entry_t *t; int nongreenwapcolor; long wmlmaxchars = 1500; int nongreenpart = 1; /* Determine where the WML files go */ sprintf(wmldir, "%s/wml", webdir); /* Make sure the WML directory exists */ if (chdir(wmldir) != 0) mkdir(wmldir, 0755); if (chdir(wmldir) != 0) { errprintf("Cannot access or create the WML output directory %s\n", wmldir); return; } /* Make sure this is set sensibly */ if (xgetenv("WMLMAXCHARS")) { wmlmaxchars = atol(xgetenv("WMLMAXCHARS")); } /* * Cleanup cards that are too old. */ delete_old_cards(wmldir); /* * Find all the test entries that belong on the WAP page, * and calculate the color for the nongreen wap page. * * We want only tests that have the "onwap" flag set, i.e. * tests given in the "WAP:test,..." for this host (of the * "NK:test,..." if no WAP list). * * At the same time, generate the WML card for the tests, * corresponding to the HTML file for the test logfile. */ nongreenwapcolor = COL_GREEN; for (h = hostlistBegin(); (h); h = hostlistNext()) { h->hostentry->wapcolor = COL_GREEN; for (t = h->hostentry->entries; (t); t = t->next) { if (t->onwap && ((t->color == COL_RED) || (t->color == COL_YELLOW))) { generate_wml_statuscard(h->hostentry, t); h->hostentry->anywaps = 1; } else { /* Clear the onwap flag - makes testing later a bit simpler */ t->onwap = 0; } if (t->onwap && (t->color > h->hostentry->wapcolor)) h->hostentry->wapcolor = t->color; } /* Update the nongreenwapcolor */ if ( (h->hostentry->wapcolor == COL_RED) || (h->hostentry->wapcolor == COL_YELLOW) ) { if (h->hostentry->wapcolor > nongreenwapcolor) nongreenwapcolor = h->hostentry->wapcolor; } } /* Start the non-green WML card */ sprintf(nongreenfn, "%s/nongreen.wml.tmp", wmldir); nongreenfd = fopen(nongreenfn, "w"); if (nongreenfd == NULL) { errprintf("Cannot open non-green WML file %s\n", nongreenfn); return; } /* Standard non-green wap header */ wml_header(nongreenfd, "card", nongreenpart); fprintf(nongreenfd, "

\n"); fprintf(nongreenfd, "%s

\n", timestamp); fprintf(nongreenfd, "

\n"); fprintf(nongreenfd, "Summary Status
%s

\n", colorname(nongreenwapcolor)); /* All green ? Just say so */ if (nongreenwapcolor == COL_GREEN) { fprintf(nongreenfd, "All is OK
\n"); } /* Now loop through the hostlist again, and generate the nongreen WAP links and host pages */ for (h = hostlistBegin(); (h); h = hostlistNext()) { if (h->hostentry->anywaps) { /* Create the host WAP card, with links to individual test results */ sprintf(hostfn, "%s/%s.wml", wmldir, h->hostentry->hostname); hostfd = fopen(hostfn, "w"); if (hostfd == NULL) { errprintf("Cannot create file %s\n", hostfn); return; } wml_header(hostfd, "name", 1); fprintf(hostfd, "

\n"); fprintf(hostfd, "Overall
\n"); fprintf(hostfd, "%s

\n", timestamp); fprintf(hostfd, "

\n"); fprintf(hostfd, "%s

\n", h->hostentry->hostname); for (t = h->hostentry->entries; (t); t = t->next) { if (t->onwap) { fprintf(hostfd, "%s%s %s
\n", t->column->name, wml_colorname(t->color), (t->acked ? "x" : ""), h->hostentry->hostname, t->column->name, t->column->name); } } fprintf(hostfd, "\n

\n"); fclose(hostfd); /* Create the link from the nongreen wap card to the host card */ fprintf(nongreenfd, "%s %s
\n", h->hostentry->hostname, wml_colorname(h->hostentry->wapcolor), h->hostentry->hostname, h->hostentry->hostname); /* * Gross hack. Some WAP phones cannot handle large cards. * So if the card grows larger than WMLMAXCHARS, split it into * multiple files and link from one file to the next. */ if (ftello(nongreenfd) >= wmlmaxchars) { char oldnongreenfn[PATH_MAX]; /* WML link is from the nongreenfd except leading wmldir+'/' */ strcpy(oldnongreenfn, nongreenfn+strlen(wmldir)+1); nongreenpart++; fprintf(nongreenfd, "
Next\n", nongreenpart); fprintf(nongreenfd, "

\n"); fclose(nongreenfd); /* Start a new Nongreen WML card */ sprintf(nongreenfn, "%s/nongreen-%d.wml", wmldir, nongreenpart); nongreenfd = fopen(nongreenfn, "w"); if (nongreenfd == NULL) { errprintf("Cannot open Nongreen WML file %s\n", nongreenfd); return; } wml_header(nongreenfd, "card", nongreenpart); fprintf(nongreenfd, "

\n"); fprintf(nongreenfd, "Previous
\n", oldnongreenfn); fprintf(nongreenfd, "%s

\n", timestamp); fprintf(nongreenfd, "

\n"); fprintf(nongreenfd, "Summary Status
%s

\n", colorname(nongreenwapcolor)); } } } fprintf(nongreenfd, "

\n"); fclose(nongreenfd); if (chdir(wmldir) == 0) { /* Rename the top-level file into place now */ rename("nongreen.wml.tmp", "nongreen.wml"); /* Make sure there is the index.wml file pointing to nongreen.wml */ symlink("nongreen.wml", "index.wml"); } return; } xymon-4.3.7/xymongen/loadlayout.c0000664000175000017500000006004511664525562016431 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* This file holds code to load the page-structure from the hosts.cfg file. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: loadlayout.c 6775 2011-11-27 21:28:18Z storner $"; #include #include #include #include #include #include #include #include #include #include #include "xymongen.h" #include "util.h" #include "loadlayout.h" #define MAX_TARGETPAGES_PER_HOST 10 time_t snapshot = 0; /* Set if we are doing a snapshot */ char *null_text = ""; /* List definition to search for page records */ typedef struct xymonpagelist_t { struct xymongen_page_t *pageentry; struct xymonpagelist_t *next; } xymonpagelist_t; static xymonpagelist_t *pagelisthead = NULL; int pagecount = 0; int hostcount = 0; char *wapcolumns = NULL; /* Default columns included in WAP cards */ char *nopropyellowdefault = NULL; char *nopropreddefault = NULL; char *noproppurpledefault = NULL; char *nopropackdefault = NULL; void addtopagelist(xymongen_page_t *page) { xymonpagelist_t *newitem; newitem = (xymonpagelist_t *) calloc(1, sizeof(xymonpagelist_t)); newitem->pageentry = page; newitem->next = pagelisthead; pagelisthead = newitem; } char *build_noprop(char *defset, char *specset) { static char result[MAX_LINE_LEN]; char *set; char *item; char ibuf[MAX_LINE_LEN]; char op; char *p; /* It's guaranteed that specset is non-NULL. defset may be NULL */ if ((*specset != '+') && (*specset != '-')) { /* Old-style - specset is the full set of tests */ sprintf(result, ",%s,", specset); return result; } set = strdup(specset); strcpy(result, ((defset != NULL) ? defset : "")); item = strtok(set, ","); while (item) { if ((*item == '-') || (*item == '+')) { op = *item; sprintf(ibuf, ",%s,", item+1); } else { op = '+'; sprintf(ibuf, ",%s,", item); } p = strstr(result, ibuf); if (p && (op == '-')) { /* Remove this item */ memmove(p, (p+strlen(item)), strlen(p)); } else if ((p == NULL) && (op == '+')) { /* Add this item (it's not already included) */ if (strlen(result) == 0) { sprintf(result, ",%s,", item+1); } else { strcat(result, item+1); strcat(result, ","); } } item = strtok(NULL, ","); } xfree(set); return result; /* This may be an empty string */ } xymongen_page_t *init_page(char *name, char *title, int vertical) { xymongen_page_t *newpage = (xymongen_page_t *) calloc(1, sizeof(xymongen_page_t)); pagecount++; dbgprintf("init_page(%s, %s)\n", textornull(name), textornull(title)); if (name) { newpage->name = strdup(name); } else name = null_text; if (title) { newpage->title = strdup(title); }else title = null_text; newpage->color = -1; newpage->oldage = 1; newpage->vertical = vertical; newpage->pretitle = NULL; newpage->groups = NULL; newpage->hosts = NULL; newpage->parent = NULL; newpage->subpages = NULL; newpage->next = NULL; return newpage; } group_t *init_group(char *title, char *onlycols, char *exceptcols, int sorthosts) { group_t *newgroup = (group_t *) calloc(1, sizeof(group_t)); dbgprintf("init_group(%s, %s)\n", textornull(title), textornull(onlycols)); if (title) { newgroup->title = strdup(title); } else title = null_text; if (onlycols) { newgroup->onlycols = (char *) malloc(strlen(onlycols)+3); /* Add a '|' at start and end */ sprintf(newgroup->onlycols, "|%s|", onlycols); } else newgroup->onlycols = NULL; if (exceptcols) { newgroup->exceptcols = (char *) malloc(strlen(exceptcols)+3); /* Add a '|' at start and end */ sprintf(newgroup->exceptcols, "|%s|", exceptcols); } else newgroup->exceptcols = NULL; newgroup->pretitle = NULL; newgroup->hosts = NULL; newgroup->sorthosts = sorthosts; newgroup->next = NULL; return newgroup; } host_t *init_host(char *hostname, int issummary, char *displayname, char *clientalias, char *comment, char *description, int ip1, int ip2, int ip3, int ip4, int dialup, double warnpct, int warnstops, char *reporttime, char *alerts, int crittime, char *waps, char *nopropyellowtests, char *nopropredtests, char *noproppurpletests, char *nopropacktests) { host_t *newhost = (host_t *) calloc(1, sizeof(host_t)); hostlist_t *oldlist; hostcount++; dbgprintf("init_host(%s)\n", textornull(hostname)); newhost->hostname = newhost->displayname = strdup(hostname); if (displayname) newhost->displayname = strdup(displayname); newhost->clientalias = (clientalias ? strdup(clientalias) : NULL); newhost->comment = (comment ? strdup(comment) : NULL); newhost->description = (description ? strdup(description) : NULL); sprintf(newhost->ip, "%d.%d.%d.%d", ip1, ip2, ip3, ip4); newhost->pretitle = NULL; newhost->entries = NULL; newhost->color = -1; newhost->oldage = 1; newhost->dialup = dialup; newhost->reportwarnlevel = warnpct; newhost->reportwarnstops = warnstops; newhost->reporttime = (reporttime ? strdup(reporttime) : NULL); if (alerts && crittime) { newhost->alerts = strdup(alerts); } else { newhost->alerts = NULL; } newhost->anywaps = 0; newhost->wapcolor = -1; /* Wap set is : * - Specific WML: tag * - NK: tag * - --wap=COLUMN cmdline option * - NULL */ if (waps || alerts) { newhost->waps = strdup(waps ? waps : alerts); } else { newhost->waps = wapcolumns; } if (nopropyellowtests) { char *p; p = skipword(nopropyellowtests); if (*p) *p = '\0'; else p = NULL; newhost->nopropyellowtests = strdup(build_noprop(nopropyellowdefault, nopropyellowtests)); if (p) *p = ' '; } else { newhost->nopropyellowtests = nopropyellowdefault; } if (nopropredtests) { char *p; p = skipword(nopropredtests); if (*p) *p = '\0'; else p = NULL; newhost->nopropredtests = strdup(build_noprop(nopropreddefault, nopropredtests)); if (p) *p = ' '; } else { newhost->nopropredtests = nopropreddefault; } if (noproppurpletests) { char *p; p = skipword(noproppurpletests); if (*p) *p = '\0'; else p = NULL; newhost->noproppurpletests = strdup(build_noprop(noproppurpledefault, noproppurpletests)); if (p) *p = ' '; } else { newhost->noproppurpletests = noproppurpledefault; } if (nopropacktests) { char *p; p = skipword(nopropacktests); if (*p) *p = '\0'; else p = NULL; newhost->nopropacktests = strdup(build_noprop(nopropackdefault, nopropacktests)); if (p) *p = ' '; } else { newhost->nopropacktests = nopropackdefault; } newhost->parent = NULL; newhost->nonongreen = 0; newhost->next = NULL; /* Summary hosts don't go into the host list */ if (issummary) return newhost; /* * Add this host to the hostlist_t list of known hosts. * HOWEVER: It might be a duplicate! In that case, we need * to figure out which host record we want to use. */ oldlist = find_hostlist(hostname); if (oldlist == NULL) { hostlist_t *newlist; newlist = (hostlist_t *) calloc(1, sizeof(hostlist_t)); newlist->hostentry = newhost; newlist->clones = NULL; add_to_hostlist(newlist); } else { hostlist_t *clone = (hostlist_t *) calloc(1, sizeof(hostlist_t)); dbgprintf("Duplicate host definition for host '%s'\n", hostname); clone->hostentry = newhost; clone->clones = oldlist->clones; oldlist->clones = clone; } return newhost; } void getnamelink(char *l, char **name, char **link) { /* "page NAME title-or-link" splitup */ char *p; dbgprintf("getnamelink(%s, ...)\n", textornull(l)); *name = null_text; *link = null_text; /* Skip page/subpage keyword, and whitespace after that */ p = skipwhitespace(skipword(l)); *name = p; p = skipword(p); if (*p) { *p = '\0'; /* Null-terminate pagename */ p++; *link = skipwhitespace(p); } } void getparentnamelink(char *l, xymongen_page_t *toppage, xymongen_page_t **parent, char **name, char **link) { /* "subparent NAME PARENTNAME title-or-link" splitup */ char *p; char *parentname; xymonpagelist_t *walk; dbgprintf("getnamelink(%s, ...)\n", textornull(l)); *name = null_text; *link = null_text; /* Skip page/subpage keyword, and whitespace after that */ parentname = p = skipwhitespace(skipword(l)); p = skipword(p); if (*p) { *p = '\0'; /* Null-terminate pagename */ p++; *name = p = skipwhitespace(p); p = skipword(p); if (*p) { *p = '\0'; /* Null-terminate parentname */ p++; *link = skipwhitespace(p); } } for (walk = pagelisthead; (walk && (strcmp(walk->pageentry->name, parentname) != 0)); walk = walk->next) ; if (walk) { *parent = walk->pageentry; } else { errprintf("Cannot find parent page '%s'\n", parentname); *parent = NULL; } } void getgrouptitle(char *l, char *pageset, char **title, char **onlycols, char **exceptcols) { char grouponlytag[100], groupexcepttag[100], grouptag[100]; *title = null_text; *onlycols = NULL; *exceptcols = NULL; sprintf(grouponlytag, "%sgroup-only", pageset); sprintf(groupexcepttag, "%sgroup-except", pageset); sprintf(grouptag, "%sgroup", pageset); dbgprintf("getgrouptitle(%s, ...)\n", textornull(l)); if (strncmp(l, grouponlytag, strlen(grouponlytag)) == 0) { char *p; *onlycols = skipwhitespace(skipword(l)); p = skipword(*onlycols); if (*p) { *p = '\0'; p++; *title = skipwhitespace(p); } } else if (strncmp(l, groupexcepttag, strlen(groupexcepttag)) == 0) { char *p; *exceptcols = skipwhitespace(skipword(l)); p = skipword(*exceptcols); if (*p) { *p = '\0'; p++; *title = skipwhitespace(p); } } else if (strncmp(l, grouptag, strlen(grouptag)) == 0) { *title = skipwhitespace(skipword(l)); } } summary_t *init_summary(char *name, char *receiver, char *url) { summary_t *newsum; dbgprintf("init_summary(%s, %s, %s)\n", textornull(name), textornull(receiver), textornull(url)); /* Sanity check */ if ((name == NULL) || (receiver == NULL) || (url == NULL)) return NULL; newsum = (summary_t *) calloc(1, sizeof(summary_t)); newsum->name = strdup(name); newsum->receiver = strdup(receiver); newsum->url = strdup(url); newsum->next = NULL; return newsum; } xymongen_page_t *load_layout(char *pgset) { char pagetag[100], subpagetag[100], subparenttag[100], vpagetag[100], vsubpagetag[100], vsubparenttag[100], grouptag[100], summarytag[100], titletag[100], hosttag[100]; char *name, *link, *onlycols, *exceptcols; char hostname[MAX_LINE_LEN]; xymongen_page_t *toppage, *curpage, *cursubpage, *cursubparent; group_t *curgroup; host_t *curhost; char *curtitle; int ip1, ip2, ip3, ip4; char *p; int fqdn = get_fqdn(); char *cfgdata, *inbol, *ineol, insavchar; if (loadhostsfromxymond) { if (load_hostnames("@", NULL, fqdn) != 0) { errprintf("Cannot load host configuration from xymond\n"); return NULL; } } else { if (load_hostnames(xgetenv("HOSTSCFG"), "netinclude", fqdn) != 0) { errprintf("Cannot load host configuration from %s\n", xgetenv("HOSTSCFG")); return NULL; } } if (first_host() == NULL) { errprintf("Empty configuration from %s\n", (loadhostsfromxymond ? "xymond" : xgetenv("HOSTSCFG"))); return NULL; } dbgprintf("load_layout(pgset=%s)\n", textornull(pgset)); /* * load_hostnames() picks up the hostname definitions, but not the page * layout. So we will scan the file again, this time doing the layout. */ if (pgset == NULL) pgset = ""; sprintf(pagetag, "%spage", pgset); sprintf(subpagetag, "%ssubpage", pgset); sprintf(subparenttag, "%ssubparent", pgset); sprintf(vpagetag, "v%spage", pgset); sprintf(vsubpagetag, "v%ssubpage", pgset); sprintf(vsubparenttag, "v%ssubparent", pgset); sprintf(grouptag, "%sgroup", pgset); sprintf(summarytag, "%ssummary", pgset); sprintf(titletag, "%stitle", pgset); sprintf(hosttag, "%s:", pgset); for (p=hosttag; (*p); p++) *p = toupper((int)*p); toppage = init_page("", "", 0); addtopagelist(toppage); curpage = NULL; cursubpage = NULL; curgroup = NULL; curhost = NULL; cursubparent = NULL; curtitle = NULL; inbol = cfgdata = hostscfg_content(); while (inbol && *inbol) { inbol += strspn(inbol, " \t"); ineol = strchr(inbol, '\n'); if (ineol) { while ((ineol > inbol) && (isspace(*ineol) || (*ineol == '\n'))) ineol--; if (*ineol != '\n') ineol++; insavchar = *ineol; *ineol = '\0'; } dbgprintf("load_layout: -- got line '%s'\n", inbol); if ((strncmp(inbol, pagetag, strlen(pagetag)) == 0) || (strncmp(inbol, vpagetag, strlen(vpagetag)) == 0)) { getnamelink(inbol, &name, &link); if (curpage == NULL) { /* First page - hook it on toppage as a subpage from there */ curpage = toppage->subpages = init_page(name, link, (strncmp(inbol, vpagetag, strlen(vpagetag)) == 0)); } else { curpage = curpage->next = init_page(name, link, (strncmp(inbol, vpagetag, strlen(vpagetag)) == 0)); } curpage->parent = toppage; if (curtitle) { curpage->pretitle = curtitle; curtitle = NULL; } cursubpage = NULL; cursubparent = NULL; curgroup = NULL; curhost = NULL; addtopagelist(curpage); } else if ( (strncmp(inbol, subpagetag, strlen(subpagetag)) == 0) || (strncmp(inbol, vsubpagetag, strlen(vsubpagetag)) == 0) ) { if (curpage == NULL) { errprintf("'subpage' ignored, no preceding 'page' tag : %s\n", inbol); goto nextline; } getnamelink(inbol, &name, &link); if (cursubpage == NULL) { cursubpage = curpage->subpages = init_page(name, link, (strncmp(inbol, vsubpagetag, strlen(vsubpagetag)) == 0)); } else { cursubpage = cursubpage->next = init_page(name, link, (strncmp(inbol, vsubpagetag, strlen(vsubpagetag)) == 0)); } cursubpage->parent = curpage; if (curtitle) { cursubpage->pretitle = curtitle; curtitle = NULL; } cursubparent = NULL; curgroup = NULL; curhost = NULL; addtopagelist(cursubpage); } else if ( (strncmp(inbol, subparenttag, strlen(subparenttag)) == 0) || (strncmp(inbol, vsubparenttag, strlen(vsubparenttag)) == 0) ) { xymongen_page_t *parentpage, *walk; getparentnamelink(inbol, toppage, &parentpage, &name, &link); if (parentpage == NULL) { errprintf("'subparent' ignored, unknown parent page: %s\n", inbol); goto nextline; } cursubparent = init_page(name, link, (strncmp(inbol, vsubparenttag, strlen(vsubparenttag)) == 0)); if (parentpage->subpages == NULL) { parentpage->subpages = cursubparent; } else { for (walk = parentpage->subpages; (walk->next); (walk = walk->next)) ; walk->next = cursubparent; } if (curtitle) { cursubparent->pretitle = curtitle; curtitle = NULL; } cursubparent->parent = parentpage; curgroup = NULL; curhost = NULL; addtopagelist(cursubparent); } else if (strncmp(inbol, grouptag, strlen(grouptag)) == 0) { int sorthosts = (strstr(inbol, "group-sorted") != NULL); getgrouptitle(inbol, pgset, &link, &onlycols, &exceptcols); if (curgroup == NULL) { curgroup = init_group(link, onlycols, exceptcols, sorthosts); if (cursubparent != NULL) { cursubparent->groups = curgroup; } else if (cursubpage != NULL) { /* We're in a subpage */ cursubpage->groups = curgroup; } else if (curpage != NULL) { /* We're on a main page */ curpage->groups = curgroup; } else { /* We're on the top page */ toppage->groups = curgroup; } } else { curgroup->next = init_group(link, onlycols, exceptcols, sorthosts); curgroup = curgroup->next; } if (curtitle) { curgroup->pretitle = curtitle; curtitle = NULL; } curhost = NULL; } else if (sscanf(inbol, "%3d.%3d.%3d.%3d %s", &ip1, &ip2, &ip3, &ip4, hostname) == 5) { void *xymonhost = NULL; int dialup, nonongreen, crittime = 1; double warnpct = reportwarnlevel; int warnstops = reportwarnstops; char *displayname, *clientalias, *comment, *description; char *alertlist, *onwaplist, *reporttime; char *nopropyellowlist, *nopropredlist, *noproppurplelist, *nopropacklist; char *targetpagelist[MAX_TARGETPAGES_PER_HOST]; int targetpagecount; char *hval; /* Check for ".default." hosts - they are ignored. */ if (*hostname == '.') goto nextline; if (!fqdn) { /* Strip any domain from the hostname */ char *p = strchr(hostname, '.'); if (p) *p = '\0'; } /* Get the info */ xymonhost = hostinfo(hostname); if (xymonhost == NULL) { errprintf("Confused - hostname '%s' cannot be found. Ignored\n", hostname); goto nextline; } /* Check for no-display hosts - they are ignored. */ /* But only when we're building the default pageset */ if ((strlen(pgset) == 0) && (xmh_item(xymonhost, XMH_FLAG_NODISP) != NULL)) goto nextline; for (targetpagecount=0; (targetpagecount < MAX_TARGETPAGES_PER_HOST); targetpagecount++) targetpagelist[targetpagecount] = NULL; targetpagecount = 0; dialup = (xmh_item(xymonhost, XMH_FLAG_DIALUP) != NULL); nonongreen = (xmh_item(xymonhost, XMH_FLAG_NONONGREEN) != NULL); alertlist = xmh_item(xymonhost, XMH_NK); hval = xmh_item(xymonhost, XMH_NKTIME); if (hval) crittime = within_sla(xmh_item(xymonhost, XMH_HOLIDAYS), hval, 0); onwaplist = xmh_item(xymonhost, XMH_WML); nopropyellowlist = xmh_item(xymonhost, XMH_NOPROPYELLOW); if (nopropyellowlist == NULL) nopropyellowlist = xmh_item(xymonhost, XMH_NOPROP); nopropredlist = xmh_item(xymonhost, XMH_NOPROPRED); noproppurplelist = xmh_item(xymonhost, XMH_NOPROPPURPLE); nopropacklist = xmh_item(xymonhost, XMH_NOPROPACK); displayname = xmh_item(xymonhost, XMH_DISPLAYNAME); comment = xmh_item(xymonhost, XMH_COMMENT); description = xmh_item(xymonhost, XMH_DESCRIPTION); hval = xmh_item(xymonhost, XMH_WARNPCT); if (hval) warnpct = atof(hval); hval = xmh_item(xymonhost, XMH_WARNSTOPS); if (hval) warnstops = atof(hval); reporttime = xmh_item(xymonhost, XMH_REPORTTIME); clientalias = xmh_item(xymonhost, XMH_CLIENTALIAS); if (xymonhost && (strcmp(xmh_item(xymonhost, XMH_HOSTNAME), clientalias) == 0)) clientalias = NULL; if (xymonhost && (strlen(pgset) > 0)) { /* Walk the clone-list and pick up the target pages for this host */ void *cwalk = xymonhost; do { hval = xmh_item_walk(cwalk); while (hval) { if (strncasecmp(hval, hosttag, strlen(hosttag)) == 0) targetpagelist[targetpagecount++] = strdup(hval+strlen(hosttag)); hval = xmh_item_walk(NULL); } cwalk = next_host(cwalk, 1); } while (cwalk && (strcmp(xmh_item(cwalk, XMH_HOSTNAME), xmh_item(xymonhost, XMH_HOSTNAME)) == 0) && (targetpagecount < MAX_TARGETPAGES_PER_HOST) ); /* * HACK: Check if the pageset tag is present at all in the host * entry. If it isn't, then drop this incarnation of the host. * * Without this, the following hosts.cfg file will have the * www.hswn.dk host listed twice on the alternate pageset: * * adminpage nyc NYC * * 127.0.0.1 localhost # bbd http://localhost/ CLIENT:osiris * 172.16.10.2 www.xymon.com # http://www.xymon.com/ ADMIN:nyc ssh noinfo * * page superdome Superdome * 172.16.10.2 www.xymon.com # noconn * */ if (strstr(inbol, hosttag) == NULL) targetpagecount = 0; } if (strlen(pgset) == 0) { /* * Default pageset generated. Put the host into * whatever group or page is current. */ if (curhost == NULL) { curhost = init_host(hostname, 0, displayname, clientalias, comment, description, ip1, ip2, ip3, ip4, dialup, warnpct, warnstops, reporttime, alertlist, crittime, onwaplist, nopropyellowlist, nopropredlist, noproppurplelist, nopropacklist); if (curgroup != NULL) { curgroup->hosts = curhost; } else if (cursubparent != NULL) { cursubparent->hosts = curhost; } else if (cursubpage != NULL) { cursubpage->hosts = curhost; } else if (curpage != NULL) { curpage->hosts = curhost; } else { toppage->hosts = curhost; } } else { curhost = curhost->next = init_host(hostname, 0, displayname, clientalias, comment, description, ip1, ip2, ip3, ip4, dialup, warnpct, warnstops, reporttime, alertlist, crittime, onwaplist, nopropyellowlist,nopropredlist, noproppurplelist, nopropacklist); } curhost->parent = (cursubparent ? cursubparent : (cursubpage ? cursubpage : curpage)); if (curtitle) { curhost->pretitle = curtitle; curtitle = NULL; } curhost->nonongreen = nonongreen; } else if (targetpagecount) { int pgnum; for (pgnum=0; (pgnum < targetpagecount); pgnum++) { char *targetpagename = targetpagelist[pgnum]; char savechar; int wantedgroup = 0; xymonpagelist_t *targetpage = NULL; /* Put the host into the page specified by the PGSET: tag */ p = strchr(targetpagename, ','); if (p) { savechar = *p; *p = '\0'; wantedgroup = atoi(p+1); } else { savechar = '\0'; p = targetpagename + strlen(targetpagename); } /* Find the page */ if (strcmp(targetpagename, "*") == 0) { *targetpagename = '\0'; } for (targetpage = pagelisthead; (targetpage && (strcmp(targetpagename, targetpage->pageentry->name) != 0)); targetpage = targetpage->next) ; *p = savechar; if (targetpage == NULL) { errprintf("Warning: Cannot find any target page named '%s' in set '%s' - dropping host '%s'\n", targetpagename, pgset, hostname); } else { host_t *newhost = init_host(hostname, 0, displayname, clientalias, comment, description, ip1, ip2, ip3, ip4, dialup, warnpct, warnstops, reporttime, alertlist, crittime, onwaplist, nopropyellowlist,nopropredlist, noproppurplelist, nopropacklist); if (wantedgroup > 0) { group_t *gwalk; host_t *hwalk; int i; for (gwalk = targetpage->pageentry->groups, i=1; (gwalk && (i < wantedgroup)); i++,gwalk=gwalk->next) ; if (gwalk) { if (gwalk->hosts == NULL) gwalk->hosts = newhost; else { for (hwalk = gwalk->hosts; (hwalk->next); hwalk = hwalk->next) ; hwalk->next = newhost; } } else { errprintf("Warning: Cannot find group %d for host %s - dropping host\n", wantedgroup, hostname); } } else { /* Just put in on the page's hostlist */ host_t *walk; if (targetpage->pageentry->hosts == NULL) targetpage->pageentry->hosts = newhost; else { for (walk = targetpage->pageentry->hosts; (walk->next); walk = walk->next) ; walk->next = newhost; } } newhost->parent = targetpage->pageentry; if (curtitle) newhost->pretitle = curtitle; } curtitle = NULL; } } } else if (strncmp(inbol, summarytag, strlen(summarytag)) == 0) { /* summary row.column IP-ADDRESS-OF-PARENT http://xymon.com/ */ char sumname[MAX_LINE_LEN]; char receiver[MAX_LINE_LEN]; char url[MAX_LINE_LEN]; summary_t *newsum; if (sscanf(inbol, "summary %s %s %s", sumname, receiver, url) == 3) { newsum = init_summary(sumname, receiver, url); newsum->next = sumhead; sumhead = newsum; } } else if (strncmp(inbol, titletag, strlen(titletag)) == 0) { /* Save the title for the next entry */ curtitle = strdup(skipwhitespace(skipword(inbol))); } nextline: if (ineol) { *ineol = insavchar; if (*ineol != '\n') ineol = strchr(ineol, '\n'); inbol = (ineol ? ineol+1 : NULL); } else inbol = NULL; } xfree(cfgdata); return toppage; } xymon-4.3.7/xymongen/xymongen.h0000664000175000017500000002003611664525562016121 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* This file holds data-definitions and declarations used in xymongen. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef _XYMONGEN_H_ #define _XYMONGEN_H_ #include #include "libxymon.h" /* Structure defs for xymongen */ /* This "drawing" depicts the relations between the various data objects used by xymongen. Think of this as doing object-oriented programming in plain C. xymongen_page_t hostlist_t state_t +-> name hostentry --+ entry --+ | title next | next | | color | | | subpages +-----------+ +-------+ | groups -------> group_t | | | hosts ---+ title V | +--- parent | hosts ---------> host_t | oldage | onlycols hostname | next | next ip | ^ +------------------------> color | | oldage | | nongreencolor | | criticalcolor | +--------------------------------- parent | alerts | waps | anywaps | nopropyellowtests | nopropredtests | noproppurpletests | nopropacktests | rawentry V entries ---------> entry_t dialup column -------> xymongen_col_t reportwarnlevel color name comment age next next oldage acked alert onwap propagate reportinfo fileage next xymongen_page_t structure holds data about one Xymon page - the first record in this list represents the top-level xymon.html page. Other pages in the list are defined using the hosts.cfg "page" directive and access via the page->next link. subpages are stored in xymongen_page_t structures also. Accessible via the "subpages" link from a page. group_t structure holds the data from a "group" directive. groups belong to pages or subpages. Currently, all groups act as "group-compress" directive. host_t structure holds all data about a given host. "color" is calculated as the most critical color of the individual entries belonging to this host. Individual tests are connected to the host via the "entries" link. hostlist_t is a simple 1-dimensional list of all the hosts, for easier traversal of the host list. entry_t holds the data for a given test (basically, a file in $XYMONRAWSTATUSDIR). test-names are not stored directly, but in the linked "xymongen_col_t" list. "age" is the "Status unchanged in X" text from the logfile. "oldage" is a boolean indicating if "age" is more than 1 day. "alert" means this test belongs on the reduced summary (alerts) page. state_t is a simple 1-dimensional list of all tests (entry_t records). */ #define PAGE_NORMAL 0 #define PAGE_NONGREEN 1 #define PAGE_CRITICAL 2 /* Max number of purple messages in one run */ #define MAX_PURPLE_PER_RUN 30 /* Column definitions. */ /* Basically a list of all possible column */ /* names */ typedef struct xymongen_col_t { char *name; char *listname; /* The ",NAME," string used for searches */ struct xymongen_col_t *next; } xymongen_col_t; typedef struct col_list_t { struct xymongen_col_t *column; struct col_list_t *next; } col_list_t; /* Measurement entry definition */ /* This points to a column definition, and */ /* contains the actual color of a measurement */ /* Linked list. */ typedef struct entry_t { struct xymongen_col_t *column; int color; char age[20]; int oldage; time_t fileage; int acked; int alert; int onwap; int propagate; int compacted; char *sumurl; char *skin; char *testflags; struct reportinfo_t *repinfo; struct replog_t *causes; char *histlogname; char *shorttext; struct entry_t *next; } entry_t; /* Aux. list definition for loading state of all tests into one list */ typedef struct state_t { struct entry_t *entry; struct state_t *next; } state_t; /* OSX has a built-in "host_t" type. */ #define host_t xymongen_host_t typedef struct host_t { char *hostname; char *displayname; char *clientalias; char *comment; char *description; char ip[IP_ADDR_STRLEN]; int dialup; struct entry_t *entries; int color; /* Calculated */ int nongreencolor; /* Calculated */ int criticalcolor; /* Calculated */ int oldage; char *alerts; int nktime; int anywaps; int wapcolor; char *waps; char *nopropyellowtests; char *nopropredtests; char *noproppurpletests; char *nopropacktests; char *pretitle; struct xymongen_page_t *parent; double reportwarnlevel; int reportwarnstops; char *reporttime; int nonongreen; struct host_t *next; } host_t; /* Aux. list definition for quick access to host records */ typedef struct hostlist_t { struct host_t *hostentry; struct hostlist_t *clones; } hostlist_t; typedef struct group_t { char *title; char *onlycols; char *exceptcols; struct host_t *hosts; int sorthosts; char *pretitle; struct group_t *next; } group_t; typedef struct xymongen_page_t { char *name; char *title; int color; /* Calculated */ int oldage; char *pretitle; int vertical; struct xymongen_page_t *next; struct xymongen_page_t *subpages; struct xymongen_page_t *parent; struct group_t *groups; struct host_t *hosts; } xymongen_page_t; typedef struct summary_t { char *name; char *receiver; char *url; struct summary_t *next; } summary_t; typedef struct dispsummary_t { char *row; char *column; char *url; int color; struct dispsummary_t *next; } dispsummary_t; enum tooltipuse_t { TT_STDONLY, TT_ALWAYS, TT_NEVER}; extern enum tooltipuse_t tooltipuse; extern xymongen_page_t *pagehead; extern state_t *statehead; extern xymongen_col_t *colhead, null_column; extern summary_t *sumhead; extern dispsummary_t *dispsums; extern int xymon_color, nongreen_color, critical_color; extern time_t reportstart, reportend; extern double reportwarnlevel, reportgreenlevel; extern int reportwarnstops; extern int reportstyle; extern int dynamicreport; extern int fqdn; extern int loadhostsfromxymond; #endif xymon-4.3.7/xymongen/Makefile0000664000175000017500000000136611535462534015545 0ustar henrikhenrik# Makefile for xymongen # PROGRAMS = xymongen GENOBJS = xymongen.o loadlayout.o loaddata.o pagegen.o process.o wmlgen.o rssgen.o util.o debug.o csvreport.o all: $(PROGRAMS) xymongen: $(GENOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(GENOBJS) ../lib/libxymon.a $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) ################################################ # Default compilation rules ################################################ %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< clean: rm -f *.o *.a *~ $(PROGRAMS) install: install-bin install-man install-bin: $(PROGRAMS) cp -fp $(PROGRAMS) $(INSTALLROOT)$(INSTALLBINDIR)/ install-man: mkdir -p $(INSTALLROOT)$(MANROOT)/man1 $(INSTALLROOT)$(MANROOT)/man5 cp -fp *.1 $(INSTALLROOT)$(MANROOT)/man1/ xymon-4.3.7/xymongen/process.h0000664000175000017500000000171211615341300015711 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon overview webpage generator tool. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __PROCESS_H_ #define __PROCESS_H_ extern void calc_hostcolors(char *nongreenignores); extern void calc_pagecolors(xymongen_page_t *phead); extern void delete_old_acks(void); extern void send_summaries(summary_t *sumhead); #endif xymon-4.3.7/xymongen/util.h0000664000175000017500000000250711615341300015213 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* xymon overview webpage generator tool. */ /* */ /* Copyright (C) 2002-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __UTIL_H_ #define __UTIL_H_ extern char *htmlextension; extern char *hostpage_link(host_t *host); extern char *hostpage_name(host_t *host); extern int checkpropagation(host_t *host, char *test, int color, int acked); extern host_t *find_host(char *hostname); extern int host_exists(char *hostname); extern hostlist_t *find_hostlist(char *hostname); extern hostlist_t *hostlistBegin(void); extern hostlist_t *hostlistNext(void); extern void add_to_hostlist(hostlist_t *rec); extern xymongen_col_t *find_or_create_column(char *testname, int create); extern int wantedcolumn(char *current, char *wanted); #endif xymon-4.3.7/rpm/0000775000175000017500000000000011671641715013032 5ustar henrikhenrikxymon-4.3.7/rpm/xymon-client.init0000664000175000017500000000342411535462534016347 0ustar henrikhenrik#! /bin/sh # # xymon-client This shell script takes care of starting and stopping # the Xymon client. # # chkconfig: 2345 80 20 # description: Xymon is a network monitoring tool that allows \ # you to monitor hosts and services. This client reports local \ # system statistics (cpu-, memory-, disk-utilisation etc) \ # to the Xymon server. PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/lib/xymon/client/runclient.sh NAME=xymon-client DESC=xymon-client test -x $DAEMON || exit 0 CMD="$1" # Include xymon-client defaults if available DMNOPTS="" if [ -f /etc/default/xymon-client ] ; then . /etc/default/xymon-client else echo "Installation failure - missing /etc/default/xymon-client" exit 1 fi if [ "$XYMONSERVERS" = "" ]; then echo "Please configure XYMONSERVERS in /etc/default/xymon-client" exit 1 fi set $XYMONSERVERS if [ $# -eq 1 ]; then echo "XYMSRV=\"$XYMONSERVERS\"" >/var/run/xymonclient-runtime.cfg echo "XYMSERVERS=\"\"" >>/var/run/xymonclient-runtime.cfg else echo "XYMSRV=\"0.0.0.0\"" >/var/run/xymonclient-runtime.cfg echo "XYMSERVERS=\"$XYMONSERVERS\"" >>/var/run/xymonclient-runtime.cfg fi if [ "$CLIENTHOSTNAME" != "" ]; then DMNOPTS="${DMNOPTS} --hostname=${CLIENTHOSTNAME}" fi if [ "$CLIENTOS" != "" ]; then DMNOPTS="${DMNOPTS} --os=${CLIENTOS}" fi set -e case "$CMD" in start) echo -n "Starting $DESC: " su -c "$DAEMON $DMNOPTS start" - xymon echo "$NAME." ;; stop) echo -n "Stopping $DESC: " su -c "$DAEMON stop" - xymon echo "$NAME." ;; restart) echo -n "Restarting $DESC: " su -c "$DAEMON stop" - xymon su -c "$DAEMON $DMNOPTS start" - xymon echo "$NAME." ;; *) N=/etc/init.d/$NAME # echo "Usage: $N {start|stop|restart}" >&2 echo "Usage: $N {start|stop|restart}" >&2 exit 1 ;; esac exit 0 xymon-4.3.7/rpm/xymon-init.d0000664000175000017500000000234511535462534015315 0ustar henrikhenrik#! /bin/sh # # xymon This shell script takes care of starting and stopping # xymon (the Xymon network monitor) # # chkconfig: 2345 80 20 # description: Xymon is a network monitoring tool that allows \ # you to monitor hosts and services. The monitor status is available \ # via a webpage. PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/lib/xymon/server/bin/xymon.sh NAME=xymon DESC=Xymon test -x $DAEMON || exit 0 # Include Xymon defaults if available if [ -f /etc/default/xymon ] ; then . /etc/default/xymon fi set -e case "$1" in start) echo -n "Starting $DESC: " su -c "$DAEMON start" - xymon echo "$NAME." ;; stop) echo -n "Stopping $DESC: " su -c "$DAEMON stop" - xymon echo "$NAME." ;; reload|force-reload) echo "Reloading $DESC configuration files." su -c "$DAEMON reload" - xymon echo "$NAME." ;; restart) echo -n "Restarting $DESC: " su -c "$DAEMON restart" - xymon echo "$NAME." ;; rotate) echo -n "Rotating logs for $DESC: " su -c "$DAEMON rotate" - xymon echo "$NAME." ;; *) N=/etc/init.d/$NAME # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 echo "Usage: $N {start|stop|restart|force-reload}" >&2 exit 1 ;; esac exit 0 xymon-4.3.7/rpm/xymon.spec0000664000175000017500000001300511535462534015056 0ustar henrikhenrikName: xymon Version: @VER@ Release: 1 Group: Networking/Daemons URL: http://xymon.sourceforge.net/ License: GPL Source: xymon-@VER@.tar.gz Source1: xymon-init.d Source2: xymon.logrotate Source3: xymon-client.init Source4: xymon-client.default Summary: Xymon network monitor BuildRoot: /tmp/xymon-root #BuildRequires: openssl-devel #BuildRequires: pcre-devel #BuildRequires: rrdtool-devel #BuildRequires: openldap-devel Conflicts: xymon-client %description Xymon (previously known as Hobbit) is a system for monitoring your network servers and applications. This package contains the server side of the Xymon package. %package client Summary: Xymon client reporting data to the Xymon server Group: Applications/System Conflicts: xymon %description client This package contains a client for the Xymon (previously known as Hobbit) monitor. Clients report data about the local system to the monitor, allowing it to check on the status of the system load, filesystem utilisation, processes that must be running etc. %prep rm -rf $RPM_BUILD_ROOT %setup USEXYMONPING=y \ ENABLESSL=y \ ENABLELDAP=y \ ENABLELDAPSSL=y \ XYMONUSER=xymon \ XYMONTOPDIR=/usr/lib/xymon \ XYMONVAR=/var/lib/xymon \ XYMONHOSTURL=/xymon \ CGIDIR=/usr/lib/xymon/cgi-bin \ XYMONCGIURL=/xymon-cgi \ SECURECGIDIR=/usr/lib/xymon/cgi-secure \ SECUREXYMONCGIURL=/xymon-seccgi \ HTTPDGID=apache \ XYMONLOGDIR=/var/log/xymon \ XYMONHOSTNAME=localhost \ XYMONHOSTIP=127.0.0.1 \ MANROOT=/usr/share/man \ INSTALLBINDIR=/usr/lib/xymon/server/bin \ INSTALLETCDIR=/etc/xymon \ INSTALLWEBDIR=/etc/xymon/web \ INSTALLEXTDIR=/usr/lib/xymon/server/ext \ INSTALLTMPDIR=/var/lib/xymon/tmp \ INSTALLWWWDIR=/var/lib/xymon/www \ ./configure %build PKGBUILD=1 make %install INSTALLROOT=$RPM_BUILD_ROOT PKGBUILD=1 make install mkdir -p $RPM_BUILD_ROOT/etc/init.d cp %{SOURCE1} $RPM_BUILD_ROOT/etc/init.d/xymon cp %{SOURCE3} $RPM_BUILD_ROOT/etc/init.d/xymon-client mkdir -p $RPM_BUILD_ROOT/etc/logrotate.d cp %{SOURCE2} $RPM_BUILD_ROOT/etc/logrotate.d/xymon mkdir -p $RPM_BUILD_ROOT/etc/default cp %{SOURCE4} $RPM_BUILD_ROOT/etc/default/xymon-client mkdir -p $RPM_BUILD_ROOT/usr/bin cd $RPM_BUILD_ROOT/usr/bin && ln -sf ../lib/xymon/server/bin/{xymon,xymoncmd} . mkdir -p $RPM_BUILD_ROOT/etc/httpd/conf.d mv $RPM_BUILD_ROOT/etc/xymon/xymon-apache.conf $RPM_BUILD_ROOT/etc/httpd/conf.d/ rmdir $RPM_BUILD_ROOT/usr/lib/xymon/client/tmp cd $RPM_BUILD_ROOT/usr/lib/xymon/client && ln -sf /tmp tmp rmdir $RPM_BUILD_ROOT/usr/lib/xymon/client/logs cd $RPM_BUILD_ROOT/usr/lib/xymon/client && ln -sf ../../../../var/log/xymon logs mv $RPM_BUILD_ROOT/usr/lib/xymon/client/etc/xymonclient.cfg /tmp/xymonclient.cfg.$$ cat /tmp/xymonclient.cfg.$$ | sed -e 's!^XYMSRV=.*!include /var/run/xymonclient-runtime.cfg!' | grep -v "^XYMSERVERS=" >$RPM_BUILD_ROOT/usr/lib/xymon/client/etc/xymonclient.cfg rm /tmp/xymonclient.cfg.$$ %clean rm -rf $RPM_BUILD_ROOT %pre id xymon 1>/dev/null 2>&1 if [ $? -ne 0 ] then groupadd xymon || true useradd -g xymon -c "Xymon user" -d /usr/lib/xymon xymon fi if [ -e /var/log/xymon/xymonlaunch.pid -a -x /etc/init.d/xymon ] then /etc/init.d/xymon stop || true fi %pre client id xymon 1>/dev/null 2>&1 if [ $? -ne 0 ] then groupadd xymon || true useradd -g xymon -c "Xymon user" -d /usr/lib/xymon xymon fi if [ -e /var/log/xymon/clientlaunch.pid -a -x /etc/init.d/xymon-client ] then /etc/init.d/xymon-client stop || true fi %post chkconfig --add xymon %post client chkconfig --add xymon-client %preun if [ -e /var/log/xymon/xymonlaunch.pid -a -x /etc/init.d/xymon ] then /etc/init.d/xymon stop || true fi chkconfig --del xymon %preun client if [ -e /var/log/xymon/clientlaunch.pid -a -x /etc/init.d/xymon-client ] then /etc/init.d/xymon-client stop || true fi chkconfig --del xymon-client %files %attr(-, root, root) %doc README README.CLIENT Changes* COPYING CREDITS RELEASENOTES %attr(644, root, root) %doc /usr/share/man/man*/* %attr(644, root, root) %config /etc/xymon/* %attr(644, root, root) %config /etc/httpd/conf.d/xymon-apache.conf %attr(755, root, root) %dir /etc/xymon %attr(755, root, root) %dir /usr/lib/xymon/server/download %attr(755, root, root) %dir /etc/xymon/web %attr(755, xymon, xymon) %dir /var/log/xymon %attr(755, root, root) /etc/init.d/xymon %attr(644, root, root) /etc/logrotate.d/xymon %attr(-, root, root) /usr/lib/xymon %attr(-, root, root) /usr/bin/* %attr(-, xymon, xymon) /var/lib/xymon %attr(775, xymon, apache) %dir /var/lib/xymon/www/rep %attr(775, xymon, apache) %dir /var/lib/xymon/www/snap %attr(644, root, root) %config /var/lib/xymon/www/menu/xymonmenu.css %attr(755, xymon, xymon) %dir /usr/lib/xymon/client/ext %attr(664, xymon, apache) %config /etc/xymon/critical.cfg %attr(664, xymon, apache) %config /etc/xymon/critical.cfg.bak %attr(4750, root, xymon) /usr/lib/xymon/server/bin/xymonping %attr(750, root, xymon) /usr/lib/xymon/client/bin/logfetch %attr(750, root, xymon) /usr/lib/xymon/client/bin/clientupdate %files client %attr(-, root, root) %doc README README.CLIENT Changes* COPYING CREDITS RELEASENOTES %attr(-, root, root) /usr/lib/xymon/client %attr(755, root, root) /etc/init.d/xymon-client %attr(644, root, root) %config /etc/default/xymon-client %attr(755, xymon, xymon) %dir /var/log/xymon %attr(755, xymon, xymon) %dir /usr/lib/xymon/client/ext %attr(750, root, xymon) /usr/lib/xymon/client/bin/logfetch %attr(750, root, xymon) /usr/lib/xymon/client/bin/clientupdate xymon-4.3.7/rpm/xymon-client.default0000664000175000017500000000134311535462534017026 0ustar henrikhenrik# Configure the Xymon client settings. # You MUST set the list of Xymon servers that this # client reports to. # It is good to use IP-adresses here instead of DNS # names - DNS might not work if there's a problem. # # E.g. (a single Xymon server) # XYMONSERVERS="192.168.1.1" # or (multiple servers) # XYMONSERVERS="10.0.0.1 192.168.1.1" XYMONSERVERS="" # The defaults usually suffice for the rest of this file, # but you can tweak the hostname that the client reports # data with. # CLIENTHOSTNAME="" # Red Hat EL version 3 uses a different vmstat layout # than all other Linux versions. So for a client running this # particular OS, set CLIENTOS as below. # Do NOT set this on any other Red Hat version. # CLIENTOS="rhel3" xymon-4.3.7/rpm/xymon.logrotate0000664000175000017500000000042211535462534016123 0ustar henrikhenrik# # Logrotate fragment for Xymon. # /var/log/xymon/*.log { weekly compress delaycompress rotate 5 missingok nocreate sharedscripts postrotate /etc/init.d/xymon rotate endscript } xymon-4.3.7/configure0000775000175000017500000000115111535462534014140 0ustar henrikhenrik#!/bin/sh # Configuration script for Xymon. # $Id: configure 6650 2011-03-08 17:20:28Z storner $ BASEDIR="`dirname $0`" TARGET="$1" if test "$TARGET" != ""; then shift; fi # Make sure that all shell-scripts are executable. # Subversion has a habit of exporting without the # execute-bit set. chmod 755 $BASEDIR/configure* $BASEDIR/build/*.sh $BASEDIR/client/*.sh case "$TARGET" in "--client") $BASEDIR/configure.client $* ;; "--server"|"") $BASEDIR/configure.server $* ;; "--help") echo "To configure a Xymon server: $0 --server" echo "To configure a Xymon client: $0 --client" ;; esac exit 0 xymon-4.3.7/CREDITS0000664000175000017500000000313211560704415013245 0ustar henrikhenrikThe following people have contributed to the development of Xymon, Hobbit, bbgen and the bbtest tools, by helping me with testing, suggesting new features or improvements, pointing out bugs, or even coming up with patches. I am very grateful for their help. Marco Avvisano Paul Backer Olivier Beau Adamets Bluejay Brian Buchanan Massimo Carnevali Craig Cook Douwe Dijkstra Francesco Duranti Lars Ebeling David Ferrest Tom Georgoulias Laurent Grilli Kevin Hanrahan Malcolm Hunter Knud Hjgaard Asif Iqbal Charles Jones Thomas J Jones Uwe Kirbach Joshua Krause Jeremy Laidman Patrick Lin Brian Lynch Thomas Mattsson Daniel J McDonald Werner Michels Christian Perrier Jure Peternel William Richardson Torsten Richter Chris Ricker Tim Rotunda Thomas Rucker Mirko Saam Thomas Schfer Tom Schmidt Eric Schwimmer Bill Simaz Gavin Stone-Tolcher Jeff Stoner David Stuffle Christian Thibodeau Rick Waegner Rob Watson Several others have contributed bug reports and helped with testing the beta releases - Xymon would not be as good as it is without their efforts. It would have been hard to implement Xymon without the existing libraries for lots of stuff: libCURL (although this is no longer used, it was quite important while it was in use) http://curl.haxx.se/ Daniel Steenberg and others libCARES Based on the ARES library by Greg Hudson of MIT, C-ARES provides asynchronous parallel DNS lookups. http://daniel.haxx.se/projects/c-ares/ Greg Hudson, Daniel Steenberg and others RRDtool http://www.mrtg.org/rrdtool/ OpenSSL http://www.openssl.org/ OpenLDAP http://www.openldap.org/ PCRE http://www.pcre.org/ xymon-4.3.7/xymonnet/0000775000175000017500000000000011671641715014115 5ustar henrikhenrikxymon-4.3.7/xymonnet/contest.h0000664000175000017500000001467711615341300015745 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* This is used to implement the testing of a TCP service. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __CONTEST_H_ #define __CONTEST_H_ #include #include #include #include #include #include #ifdef HAVE_OPENSSL /* * OpenSSL defs. We require OpenSSL 0.9.5 or later * as some of the routines we use are not available * in earlier versions. */ #include #include #include #include #if !defined(OPENSSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x00905000L) #error SSL-protocol testing requires OpenSSL version 0.9.5 or later #endif #else /* * xymonnet without support for SSL protocols. */ #undef TCP_SSL #define TCP_SSL 0x0000 #define SSL_CTX void #define SSL void #endif #include "libxymon.h" extern char *ssl_library_version; extern char *ciphershigh; extern char *ciphersmedium; extern unsigned int warnbytesread; extern int shuffletests; #define SSLVERSION_DEFAULT 0 #define SSLVERSION_V2 1 #define SSLVERSION_V3 2 #define SSLVERSION_TLS1 3 typedef struct { char *cipherlist; int sslversion; char *clientcert; } ssloptions_t; typedef int (*f_callback_data)(unsigned char *buffer, unsigned int size, void *privdata); typedef void (*f_callback_final)(void *privdata); #define CONTEST_ENOERROR 0 #define CONTEST_ETIMEOUT 1 #define CONTEST_ENOCONN 2 #define CONTEST_EDNS 3 #define CONTEST_EIO 4 #define CONTEST_ESSL 5 typedef struct tcptest_t { struct sockaddr_in addr; /* Address (IP+port) to test */ char *srcaddr; struct svcinfo_t *svcinfo; /* svcinfo_t for service */ long int randomizer; int fd; /* Socket filedescriptor */ time_t lastactive; time_t cutoff; char *tspec; unsigned int bytesread; unsigned int byteswritten; /* Connection info */ int connres; /* connect() status returned */ int open; /* Result - is it open? */ int errcode; /* Pick up any errors */ struct timespec timestart; /* Starttime of connection attempt */ struct timespec duration; /* Duration of connection attempt */ struct timespec totaltime; /* Duration of the full transfer */ /* Data we send */ unsigned char *sendtxt; unsigned int sendlen; /* For grabbing banners */ int silenttest; /* Banner grabbing can be disabled per test */ int readpending; /* Temp status while reading banner */ unsigned char *banner; /* Banner text from service */ unsigned int bannerbytes; /* Number of bytes in banner */ /* For testing SSL-wrapped services */ ssloptions_t *ssloptions; /* Specific SSL options requested by user */ SSL_CTX *sslctx; /* SSL context pointer */ SSL *ssldata; /* SSL data (socket) pointer */ char *certinfo; /* Certificate info (subject+expiretime) */ time_t certexpires; /* Expiretime in time_t format */ char *certsubject; int mincipherbits; /* Bits in the weakest encryption supported */ int sslrunning; /* Track state of an SSL session */ int sslagain; /* SSL read/write needs more data */ /* For testing telnet services */ unsigned char *telnetbuf; /* Buffer for telnet option negotiation */ int telnetbuflen; /* Length of telnetbuf - it's binary, so no strlen */ int telnetnegotiate; /* Flag telling if telnet option negotiation is being done */ /* For testing http services */ void *priv; f_callback_data datacallback; f_callback_final finalcallback; struct tcptest_t *next; } tcptest_t; #define CONTENTCHECK_NONE 0 #define CONTENTCHECK_REGEX 1 #define CONTENTCHECK_DIGEST 2 #define CONTENTCHECK_NOREGEX 3 #define CONTENTCHECK_CONTENTTYPE 4 #define HTTPVER_ANY 0 #define HTTPVER_10 1 #define HTTPVER_11 2 #define CHUNK_NOTCHUNKED 0 #define CHUNK_INIT 1 #define CHUNK_GETLEN 2 #define CHUNK_SKIPLENCR 3 #define CHUNK_DATA 4 #define CHUNK_SKIPENDCR 5 #define CHUNK_DONE 6 #define CHUNK_NOMORE 7 typedef struct { tcptest_t *tcptest; char *url; /* URL to request, stripped of configuration artefacts */ int parsestatus; weburl_t weburl; int gotheaders; int contlen; int chunkstate; unsigned int leftinchunk; unsigned char *headers; /* HTTP headers from server */ unsigned int hdrlen; unsigned char *output; /* Data from server */ unsigned int outlen; long httpstatus; /* HTTP status from server */ char *contenttype; /* Content-type: header from server */ int contentcheck; /* 0=no content check, 1=regex check, 2=digest check */ void *exp; /* data for content match (digest, or regexp data) */ digestctx_t *digestctx; /* OpenSSL data for digest handling */ char *digest; /* Digest of the data received from the server */ long contstatus; /* Pseudo HTTP status for content check */ /* Used during status-reporting */ int httpcolor; /* Color of this HTTP test */ char *errorcause; char *faileddeps; /* List of failed dependency checks */ } http_data_t; extern unsigned long tcp_stats_read; extern unsigned long tcp_stats_written; extern unsigned int tcp_stats_total; extern unsigned int tcp_stats_http; extern unsigned int tcp_stats_plain; extern unsigned int tcp_stats_connects; extern char *init_tcp_services(void); extern int default_tcp_port(char *svcname); extern void dump_tcp_services(void); extern tcptest_t *add_tcp_test(char *ip, int port, char *service, ssloptions_t *sslopt, char *srcip, char *tspec, int silent, unsigned char *reqmsg, void *priv, f_callback_data datacallback, f_callback_final finalcallback); extern void do_tcp_tests(int timeout, int concurrency); extern void show_tcp_test_results(void); extern int tcp_got_expected(tcptest_t *test); #endif xymon-4.3.7/xymonnet/protocols.cfg0000664000175000017500000001110111535462534016613 0ustar henrikhenrik# protocols.cfg # # $Id: protocols.cfg 6650 2011-03-08 17:20:28Z storner $ # # This file defines the way TCP services are tested by xymonnet # # A service definition looks like this: # [NAME] # send "HELLO" # expect "OK" # options banner,ssl,telnet # port PORTNUMBER # # The NAME is the name of the test, and of the TCP service. If # multiple tests share a common definition (e.g. ssh, ssh1, ssh2) # then these may be given as "[NAME1|NAME2|NAME3]" # # If the send-string is defined, this data is sent to the service # immediately after a connect. # # If the expect-string is defined, any data returned by the service is # matched against this. If it matches, the status will be green; if it # does not match, the status will turn yellow. Only if the service does # not respond at all will the status go red. If a expect-string is not # defined, the status will always be green if it is possible to connect # to the service. # # The options can include "banner" (to grab the banner from the # service), "telnet" (telnet options are to be negotiated), and # "ssl" (perform an SSL/TLS handshake and pick up the SSL certificate). # # The "port" is the TCP port used for this service. This OVERRIDES # any port number listed in /etc/services - but this also means that # you need not define "unusual" port-numbers in /etc/services. # Of course, you can always define your test in hosts.cfg to use a # specific portnumber. # # The send/expect string definitions must be in double quotes. # The sequences "\r", "\n", "\t" and "\xNN" are recognized and # converted into a carriage-return (ASCII 13), line-feed (ASCII 10), # TAB (ASCII 8), and any byte respectively (NN=hex value). [ftp] send "quit\r\n" expect "220" options banner port 21 [ftps] send "quit\r\n" expect "220" options ssl,banner port 990 [ssh|ssh1|ssh2] send "SSH-2.0-OpenSSH_4.1\r\n" expect "SSH" options banner port 22 [telnet] options banner,telnet port 23 [telnets] options ssl,banner,telnet port 992 [smtp] send "mail\r\nquit\r\n" expect "220" options banner port 25 [smtps] send "mail\r\nquit\r\n" expect "220" options ssl,banner # No default port-number assignment for smtps - nonstandard according to IANA [pop2|pop-2] send "quit\r\n" expect "+OK" options banner port 109 [pop|pop3|pop-3] send "quit\r\n" expect "+OK" options banner port 110 [pop3s] send "quit\r\n" expect "+OK" options ssl,banner port 995 [imap|imap2|imap4] send "ABC123 LOGOUT\r\n" expect "* OK" options banner port 143 [imap3] send "ABC123 LOGOUT\r\n" expect "* OK" options banner port 220 [imaps] send "ABC123 LOGOUT\r\n" expect "* OK" options ssl,banner port 993 [nntp] send "quit\r\n" expect "200" options banner port 119 [nntps] send "quit\r\n" expect "200" options ssl,banner port 563 [ldap] port 389 [ldaps] options ssl port 636 [rsync] expect "@RSYNCD" options banner port 873 [bbd] # send "ping" # expect "xymond" send "dummy" port 1984 # The AV scanning daemon from the ClamAV antivirus package [clamd] send "PING\n" expect "PONG" options banner port 3310 # SpamAssassin spamd [spamd] send "PING SPAMC/Xymon\n" expect "SPAMD" options banner port 783 # From http://www.mail-archive.com/whatsup_forum@list.ipswitch.com/msg06678.html [oratns] send "\x00\x57\x00\x00\x01\x00\x00\x00\x01\x36\x01\x2C\x00\x00\x08\x00\x7F\xFF\xA3\x0A\x00\x00\x01\x00\x00\x1D\x00\x3A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x08\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00(CONNECT_DATA=(COMMAND=ping))" options banner port 1521 # qmail "Quick Mail Transfer Protocol" [qmtp] port 209 # qmail "Quick Mail Queuing Protocol" [qmqp] port 628 # vnc "Virtual Network Computing" - method from bb-vnc.tar.gz # From Richard Finegold [vnc] send "RFB 000.000\r\n" expect "RFB " options banner port 5900 # CUPS print server. It answers to HTTP requests. [cupsd] send "GET /printers\r\n" expect "HTTP/1.1 200 OK" port 631 # AJP (Apache JServ Protocol) 1.3 - sends an AJP "ping" request. # Ref: http://tomcat.apache.org/connectors-doc/common/ajpv13a.html # From Charles Goyard [ajp13] send "\x12\x34\x00\x01\x0a" expect "\x41\x42\x00\x01\x09" port 8009 # Microsoft Terminal Services / Remote Desktop Protocol # From Chris Wopat (http://www.xymon.com/archive/2010/01/msg00039.html) [rdp] port 3389 send "\x03\x00\x00\x1e\x19\xe0\x00\x00\x00\x00\x00Cookie: mstshash=\r\n" expect "\x03\x00\x00\x0b\x06\xd0" xymon-4.3.7/xymonnet/httpcookies.h0000664000175000017500000000234611630612042016611 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* Copyright (C) 2008-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __HTTPCOOKIES_H__ #define __HTTPCOOKIES_H__ typedef struct cookielist_t { char *host; int tailmatch; char *path; int secure; char *name; char *value; struct cookielist_t *next; } cookielist_t; extern cookielist_t *cookiehead; extern void *cookietree; extern void init_session_cookies(char *urlhost, char *cknam, char *ckpath, char *ckval); extern void update_session_cookies(char *hostname, char *urlhost, char *headers); extern void save_session_cookies(void); extern void load_cookies(void); #endif xymon-4.3.7/xymonnet/xymon-snmpcollect.c0000664000175000017500000007304511615341300017746 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor SNMP data collection tool */ /* */ /* Copyright (C) 2007-2011 Henrik Storner */ /* */ /* Inspired by the asyncapp.c file from the "NET-SNMP demo", available from */ /* the Net-SNMP website. This file carries the attribution */ /* "Niels Baggesen (Niels.Baggesen@uni-c.dk), 1999." */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymon-snmpcollect.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include "libxymon.h" /* ----------------- struct's used for the host/requests we need to do ---------------- */ /* List of the OID's we will request */ typedef struct oid_t { mibdef_t *mib; /* pointer to the mib definition for mibs */ oid Oid[MAX_OID_LEN]; /* the internal OID representation */ unsigned int OidLen; /* size of the oid */ char *devname; /* Users' chosen device name. May be a key (e.g "eth0") */ oidds_t *oiddef; /* Points to the oidds_t definition */ int setnumber; /* All vars fetched in one PDU's have the same setnumber */ char *result; /* the printable result data */ struct oid_t *next; } oid_t; /* Used for requests where we must determine the appropriate table index first */ typedef struct keyrecord_t { mibdef_t *mib; /* Pointer to the mib definition */ mibidx_t *indexmethod; /* Pointer to the mib index definition */ char *key; /* The user-provided key we must find */ char *indexoid; /* Result: Index part of the OID */ struct keyrecord_t *next; } keyrecord_t; /* A host and the OID's we will be polling */ typedef struct req_t { char *hostname; /* Hostname used for reporting to Xymon */ char *hostip[10]; /* Hostname(s) or IP(s) used for testing. Max 10 IP's */ int hostipidx; /* Index into hostip[] for the active IP we use */ long version; /* SNMP version to use */ unsigned char *community; /* Community name used to access the SNMP daemon (v1, v2c) */ unsigned char *username; /* Username used to access the SNMP daemon (v3) */ unsigned char *passphrase; /* Passphrase used to access the SNMP daemon (v3) */ enum { SNMP_V3AUTH_MD5, SNMP_V3AUTH_SHA1 } authmethod; /* Authentication method (v3) */ int setnumber; /* Per-host setnumber used while building requests */ struct snmp_session *sess; /* SNMP session data */ keyrecord_t *keyrecords, *currentkey; /* For keyed requests: Key records */ oid_t *oidhead, *oidtail; /* List of the OID's we will fetch */ oid_t *curr_oid, *next_oid; /* Current- and next-OID pointers while fetching data */ struct req_t *next; } req_t; /* Global variables */ req_t *reqhead = NULL; /* Holds the list of requests */ int active_requests = 0; /* Number of active SNMP requests in flight */ /* dataoperation tracks what we are currently doing */ enum { GET_KEYS, /* Scan for the keys */ GET_DATA /* Fetch the actual data */ } dataoperation; /* Tuneables */ int max_pending_requests = 30; int retries = 0; /* Number of retries before timeout. 0 = Net-SNMP default (5). */ long timeout = 0; /* Number of uS until first timeout, then exponential backoff. 0 = Net-SNMP default (1 second). */ /* Statistics */ char *reportcolumn = NULL; int varcount = 0; int pducount = 0; int okcount = 0; int toobigcount = 0; int timeoutcount = 0; int errorcount = 0; struct timeval starttv, endtv; /* Must forward declare these */ void startonehost(struct req_t *r, int ipchange); void starthosts(int resetstart); struct snmp_pdu *generate_datarequest(req_t *item) { struct snmp_pdu *req; int currentset; if (!item->next_oid) return NULL; req = snmp_pdu_create(SNMP_MSG_GET); pducount++; item->curr_oid = item->next_oid; currentset = item->next_oid->setnumber; while (item->next_oid && (currentset == item->next_oid->setnumber)) { varcount++; snmp_add_null_var(req, item->next_oid->Oid, item->next_oid->OidLen); item->next_oid = item->next_oid->next; } return req; } /* * Store data received in response PDU */ int print_result (int status, req_t *req, struct snmp_pdu *pdu) { struct variable_list *vp; int len; keyrecord_t *kwalk; int keyoidlen; oid_t *owalk; int done; switch (status) { case STAT_SUCCESS: if (pdu->errstat == SNMP_ERR_NOERROR) { unsigned char *valstr = NULL, *oidstr = NULL; int valsz = 0, oidsz = 0; okcount++; switch (dataoperation) { case GET_KEYS: /* * Find the keyrecord currently processed for this request, and * look through the unresolved keys to see if we have a match. * If we do, determine the index for data retrieval. */ vp = pdu->variables; len = 0; sprint_realloc_value(&valstr, &valsz, &len, 1, vp->name, vp->name_length, vp); len = 0; sprint_realloc_objid(&oidstr, &oidsz, &len, 1, vp->name, vp->name_length); dbgprintf("Got key-oid '%s' = '%s'\n", oidstr, valstr); for (kwalk = req->currentkey, done = 0; (kwalk && !done); kwalk = kwalk->next) { /* Skip records where we have the result already, or that are not keyed */ if (kwalk->indexoid || (kwalk->indexmethod != req->currentkey->indexmethod)) { continue; } keyoidlen = strlen(req->currentkey->indexmethod->keyoid); switch (kwalk->indexmethod->idxtype) { case MIB_INDEX_IN_OID: /* Does the key match the value we just got? */ if (*kwalk->key == '*') { /* Match all. Add an extra key-record at the end. */ keyrecord_t *newkey; newkey = (keyrecord_t *)calloc(1, sizeof(keyrecord_t)); memcpy(newkey, kwalk, sizeof(keyrecord_t)); newkey->indexoid = strdup(oidstr + keyoidlen + 1); newkey->key = valstr; valstr = NULL; newkey->next = kwalk->next; kwalk->next = newkey; done = 1; } else if (strcmp(valstr, kwalk->key) == 0) { /* Grab the index part of the OID */ kwalk->indexoid = strdup(oidstr + keyoidlen + 1); done = 1; } break; case MIB_INDEX_IN_VALUE: /* Does the key match the index-part of the result OID? */ if (*kwalk->key == '*') { /* Match all. Add an extra key-record at the end. */ keyrecord_t *newkey; newkey = (keyrecord_t *)calloc(1, sizeof(keyrecord_t)); memcpy(newkey, kwalk, sizeof(keyrecord_t)); newkey->indexoid = valstr; valstr = NULL; newkey->key = strdup(oidstr + keyoidlen + 1); newkey->next = kwalk->next; kwalk->next = newkey; done = 1; } else if ((*(oidstr+keyoidlen) == '.') && (strcmp(oidstr+keyoidlen+1, kwalk->key)) == 0) { /* * Grab the index which is the value. * Avoid a strdup by grabbing the valstr pointer. */ kwalk->indexoid = valstr; valstr = NULL; valsz = 0; done = 1; } break; } } break; case GET_DATA: owalk = req->curr_oid; vp = pdu->variables; while (vp) { valsz = len = 0; sprint_realloc_value((unsigned char **)&owalk->result, &valsz, &len, 1, vp->name, vp->name_length, vp); owalk = owalk->next; vp = vp->next_variable; } break; } if (valstr) xfree(valstr); if (oidstr) xfree(oidstr); } else { errorcount++; errprintf("ERROR %s: %s\n", req->hostip[req->hostipidx], snmp_errstring(pdu->errstat)); } return 1; case STAT_TIMEOUT: timeoutcount++; dbgprintf("%s: Timeout\n", req->hostip); if (req->hostip[req->hostipidx+1]) { req->hostipidx++; startonehost(req, 1); } return 0; case STAT_ERROR: errorcount++; snmp_sess_perror(req->hostip[req->hostipidx], req->sess); return 0; } return 0; } /* * response handler */ int asynch_response(int operation, struct snmp_session *sp, int reqid, struct snmp_pdu *pdu, void *magic) { struct req_t *req = (struct req_t *)magic; if (operation == NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE) { struct snmp_pdu *snmpreq = NULL; int okoid = 1; if (dataoperation == GET_KEYS) { /* * We're doing GETNEXT's when retrieving keys, so we will get a response * which has nothing really to do with the data we're looking for. In that * case, we should NOT process data from this response. */ struct variable_list *vp = pdu->variables; okoid = ((vp->name_length >= req->currentkey->indexmethod->rootoidlen) && (memcmp(req->currentkey->indexmethod->rootoid, vp->name, req->currentkey->indexmethod->rootoidlen * sizeof(oid)) == 0)); } switch (pdu->errstat) { case SNMP_ERR_NOERROR: /* Pick up the results, but only if the OID is valid */ if (okoid) print_result(STAT_SUCCESS, req, pdu); break; case SNMP_ERR_NOSUCHNAME: dbgprintf("Host %s item %s: No such name\n", req->hostname, req->curr_oid->devname); if (req->hostip[req->hostipidx+1]) { req->hostipidx++; startonehost(req, 1); } break; case SNMP_ERR_TOOBIG: toobigcount++; errprintf("Host %s item %s: Response too big\n", req->hostname, req->curr_oid->devname); break; default: errorcount++; errprintf("Host %s item %s: SNMP error %d\n", req->hostname, req->curr_oid->devname, pdu->errstat); break; } /* Now see if we should send another request */ switch (dataoperation) { case GET_KEYS: /* * While fetching keys, walk the current key-table until we reach the end of the table. * When we reach the end of one key-table, start with the next. * FIXME: Could optimize so we dont fetch the whole table, but only those rows we need. */ if (pdu->errstat == SNMP_ERR_NOERROR) { struct variable_list *vp = pdu->variables; if ( (vp->name_length >= req->currentkey->indexmethod->rootoidlen) && (memcmp(req->currentkey->indexmethod->rootoid, vp->name, req->currentkey->indexmethod->rootoidlen * sizeof(oid)) == 0) ) { /* Still more data in the current key table, get the next row */ snmpreq = snmp_pdu_create(SNMP_MSG_GETNEXT); pducount++; /* Probably only one variable to fetch, but never mind ... */ while (vp) { varcount++; snmp_add_null_var(snmpreq, vp->name, vp->name_length); vp = vp->next_variable; } } else { /* End of current key table. If more keys to be found, start the next table. */ do { req->currentkey = req->currentkey->next; } while (req->currentkey && req->currentkey->indexoid); if (req->currentkey) { snmpreq = snmp_pdu_create(SNMP_MSG_GETNEXT); pducount++; snmp_add_null_var(snmpreq, req->currentkey->indexmethod->rootoid, req->currentkey->indexmethod->rootoidlen); } } } break; case GET_DATA: /* Generate a request for the next dataset, if any */ if (req->next_oid) { snmpreq = generate_datarequest(req); } else { dbgprintf("No more oids left\n"); } break; } /* Send the request we just made */ if (snmpreq) { if (snmp_send(req->sess, snmpreq)) goto finish; else { snmp_sess_perror("snmp_send", req->sess); snmp_free_pdu(snmpreq); } } } else { dbgprintf("operation not succesful: %d\n", operation); print_result(STAT_TIMEOUT, req, pdu); } /* * Something went wrong (or end of variables). * This host not active any more */ dbgprintf("Finished host %s\n", req->hostname); active_requests--; finish: /* Start some more hosts */ starthosts(0); return 1; } void startonehost(struct req_t *req, int ipchange) { struct snmp_session s; struct snmp_pdu *snmpreq = NULL; if ((dataoperation < GET_DATA) && !req->currentkey) return; /* Are we retrying a cluster with a new IP? Then drop the current session */ if (req->sess && ipchange) { /* * Apparently, we cannot close a session while in a callback. * So leave this for now - it will leak some memory, but * this is not a problem as long as we only run once. */ /* snmp_close(req->sess); */ req->sess = NULL; } /* Setup the SNMP session */ if (!req->sess) { snmp_sess_init(&s); /* * snmp_session has a "remote_port" field, but it does not work. * Instead, the peername should include a port number (IP:PORT) * if (req->portnumber) s.remote_port = req->portnumber; */ s.peername = req->hostip[req->hostipidx]; /* Set the SNMP version and authentication token(s) */ s.version = req->version; switch (s.version) { case SNMP_VERSION_1: case SNMP_VERSION_2c: s.community = req->community; s.community_len = strlen((char *)req->community); break; case SNMP_VERSION_3: /* set the SNMPv3 user name */ s.securityName = strdup(req->username); s.securityNameLen = strlen(s.securityName); /* set the security level to authenticated, but not encrypted */ s.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV; /* set the authentication method */ switch (req->authmethod) { case SNMP_V3AUTH_MD5: s.securityAuthProto = usmHMACMD5AuthProtocol; s.securityAuthProtoLen = sizeof(usmHMACMD5AuthProtocol)/sizeof(oid); s.securityAuthKeyLen = USM_AUTH_KU_LEN; break; case SNMP_V3AUTH_SHA1: s.securityAuthProto = usmHMACSHA1AuthProtocol; s.securityAuthProtoLen = sizeof(usmHMACSHA1AuthProtocol)/sizeof(oid); s.securityAuthKeyLen = USM_AUTH_KU_LEN; break; } /* * set the authentication key to a hashed version of our * passphrase (which must be at least 8 characters long). */ if (generate_Ku(s.securityAuthProto, s.securityAuthProtoLen, (u_char *)req->passphrase, strlen(req->passphrase), s.securityAuthKey, &s.securityAuthKeyLen) != SNMPERR_SUCCESS) { errprintf("Failed to generate Ku from authentication pass phrase for host %s\n", req->hostname); snmp_perror("generate_Ku"); return; } break; } /* Set timeouts and retries */ if (timeout > 0) s.timeout = timeout; if (retries > 0) s.retries = retries; /* Setup the callback */ s.callback = asynch_response; s.callback_magic = req; if (!(req->sess = snmp_open(&s))) { snmp_sess_perror("snmp_open", &s); return; } } switch (dataoperation) { case GET_KEYS: snmpreq = snmp_pdu_create(SNMP_MSG_GETNEXT); pducount++; snmp_add_null_var(snmpreq, req->currentkey->indexmethod->rootoid, req->currentkey->indexmethod->rootoidlen); break; case GET_DATA: /* Build the request PDU and send it */ snmpreq = generate_datarequest(req); break; } if (!snmpreq) return; if (snmp_send(req->sess, snmpreq)) active_requests++; else { errorcount++; snmp_sess_perror("snmp_send", req->sess); snmp_free_pdu(snmpreq); } } void starthosts(int resetstart) { static req_t *startpoint = NULL; static int haverun = 0; struct req_t *rwalk; if (resetstart) { startpoint = NULL; haverun = 0; } if (startpoint == NULL) { if (haverun) { return; } else { startpoint = reqhead; haverun = 1; } } /* startup as many hosts as we want to run in parallel */ for (rwalk = startpoint; (rwalk && (active_requests <= max_pending_requests)); rwalk = rwalk->next) { startonehost(rwalk, 0); } startpoint = rwalk; } void stophosts(void) { struct req_t *rwalk; for (rwalk = reqhead; (rwalk); rwalk = rwalk->next) { if (rwalk->sess) { snmp_close(rwalk->sess); } } } /* * This routine loads MIB files, and computes the key-OID values. * We defer this until the mib-definition is actually being referred to * in snmphosts.cfg, because lots of MIB's are not being used * (and probably do not exist on the host where we're running) and * to avoid spending a lot of time to load MIB's that are not used. */ void setupmib(mibdef_t *mib, int verbose) { mibidx_t *iwalk; int sz, len; if (mib->loadstatus != MIB_STATUS_NOTLOADED) return; if (mib->mibfn && (read_mib(mib->mibfn) == NULL)) { mib->loadstatus = MIB_STATUS_LOADFAILED; if (verbose) { errprintf("Failed to read MIB file %s\n", mib->mibfn); snmp_perror("read_objid"); } } for (iwalk = mib->idxlist; (iwalk); iwalk = iwalk->next) { iwalk->rootoid = calloc(MAX_OID_LEN, sizeof(oid)); iwalk->rootoidlen = MAX_OID_LEN; if (read_objid(iwalk->keyoid, iwalk->rootoid, &iwalk->rootoidlen)) { /* Re-use the iwalk->keyoid buffer */ sz = strlen(iwalk->keyoid) + 1; len = 0; sprint_realloc_objid((unsigned char **)&iwalk->keyoid, &sz, &len, 1, iwalk->rootoid, iwalk->rootoidlen); } else { mib->loadstatus = MIB_STATUS_LOADFAILED; if (verbose) { errprintf("Cannot determine OID for %s\n", iwalk->keyoid); snmp_perror("read_objid"); } } } mib->loadstatus = MIB_STATUS_LOADED; } /* * * Config file syntax * * [HOSTNAME] * ip=ADDRESS[:PORT] * version=VERSION * community=COMMUNITY * username=USERNAME * passphrase=PASSPHRASE * authmethod=[MD5|SHA1] * mibname1[=index] * mibname2[=index] * mibname3[=index] * */ static oid_t *make_oitem(mibdef_t *mib, char *devname, oidds_t *oiddef, char *oidstr, struct req_t *reqitem) { oid_t *oitem = (oid_t *)calloc(1, sizeof(oid_t)); oitem->mib = mib; oitem->devname = strdup(devname); oitem->setnumber = reqitem->setnumber; oitem->oiddef = oiddef; oitem->OidLen = sizeof(oitem->Oid)/sizeof(oitem->Oid[0]); if (read_objid(oidstr, oitem->Oid, &oitem->OidLen)) { if (!reqitem->oidhead) reqitem->oidhead = oitem; else reqitem->oidtail->next = oitem; reqitem->oidtail = oitem; } else { /* Could not parse the OID definition */ errprintf("Cannot determine OID for %s\n", oidstr); snmp_perror("read_objid"); xfree(oitem->devname); xfree(oitem); } return oitem; } void readconfig(char *cfgfn, int verbose) { static void *cfgfiles = NULL; FILE *cfgfd; strbuffer_t *inbuf; struct req_t *reqitem = NULL; int tasksleep = atoi(xgetenv("TASKSLEEP")); mibdef_t *mib; /* Check if config was modified */ if (cfgfiles) { if (!stackfmodified(cfgfiles)) { dbgprintf("No files changed, skipping reload\n"); return; } else { stackfclist(&cfgfiles); cfgfiles = NULL; } } cfgfd = stackfopen(cfgfn, "r", &cfgfiles); if (cfgfd == NULL) { errprintf("Cannot open configuration files %s\n", cfgfn); return; } inbuf = newstrbuffer(0); while (stackfgets(inbuf, NULL)) { char *bot, *p, *mibidx; char savech; sanitize_input(inbuf, 0, 0); bot = STRBUF(inbuf) + strspn(STRBUF(inbuf), " \t"); if ((*bot == '\0') || (*bot == '#')) continue; if (*bot == '[') { char *intvl = strchr(bot, '/'); /* * See if we're running a non-standard interval. * If yes, then process only the records that match * this TASKSLEEP setting. */ if (tasksleep != 300) { /* Non-default interval. Skip the host if it HASN'T got an interval setting */ if (!intvl) continue; /* Also skip the hosts that have an interval different from the current */ *intvl = '\0'; /* Clip the interval from the hostname */ if (atoi(intvl+1) != tasksleep) continue; } else { /* Default interval. Skip the host if it HAS an interval setting */ if (intvl) continue; } reqitem = (req_t *)calloc(1, sizeof(req_t)); p = strchr(bot, ']'); if (p) *p = '\0'; reqitem->hostname = strdup(bot + 1); if (p) *p = ']'; reqitem->hostip[0] = reqitem->hostname; reqitem->version = SNMP_VERSION_1; reqitem->authmethod = SNMP_V3AUTH_MD5; reqitem->next = reqhead; reqhead = reqitem; continue; } /* If we have nowhere to put the data, then skip further processing */ if (!reqitem) continue; if (strncmp(bot, "ip=", 3) == 0) { char *nextip = strtok(strdup(bot+3), ","); int i = 0; do { reqitem->hostip[i++] = nextip; nextip = strtok(NULL, ","); } while (nextip); continue; } if (strncmp(bot, "version=", 8) == 0) { switch (*(bot+8)) { case '1': reqitem->version = SNMP_VERSION_1; break; case '2': reqitem->version = SNMP_VERSION_2c; break; case '3': reqitem->version = SNMP_VERSION_3; break; } continue; } if (strncmp(bot, "community=", 10) == 0) { reqitem->community = strdup(bot+10); continue; } if (strncmp(bot, "username=", 9) == 0) { reqitem->username = strdup(bot+9); continue; } if (strncmp(bot, "passphrase=", 10) == 0) { reqitem->passphrase = strdup(bot+10); continue; } if (strncmp(bot, "authmethod=", 10) == 0) { if (strcasecmp(bot+10, "md5") == 0) reqitem->authmethod = SNMP_V3AUTH_MD5; else if (strcasecmp(bot+10, "sha1") == 0) reqitem->authmethod = SNMP_V3AUTH_SHA1; else errprintf("Unknown SNMPv3 authentication method '%s'\n", bot+10); continue; } /* Custom mibs */ p = bot + strcspn(bot, "= \t\r\n"); savech = *p; *p = '\0'; mib = find_mib(bot); *p = savech; p += strspn(p, "= \t"); mibidx = p; if (mib) { int i; mibidx_t *iwalk = NULL; char *oid, *oidbuf; char *devname; oidset_t *swalk; setupmib(mib, verbose); if (mib->loadstatus != MIB_STATUS_LOADED) continue; /* Cannot use this MIB */ /* See if this is an entry where we must determine the index ourselves */ if (*mibidx) { for (iwalk = mib->idxlist; (iwalk && (*mibidx != iwalk->marker)); iwalk = iwalk->next) ; } if ((*mibidx == '*') && !iwalk) { errprintf("Cannot do wildcard matching without an index (host %s, mib %s)\n", reqitem->hostname, mib->mibname); continue; } if (!iwalk) { /* No key lookup */ swalk = mib->oidlisthead; while (swalk) { reqitem->setnumber++; for (i=0; (i <= swalk->oidcount); i++) { if (*mibidx) { oid = oidbuf = (char *)malloc(strlen(swalk->oids[i].oid) + strlen(mibidx) + 2); sprintf(oidbuf, "%s.%s", swalk->oids[i].oid, mibidx); devname = mibidx; } else { oid = swalk->oids[i].oid; oidbuf = NULL; devname = "-"; } make_oitem(mib, devname, &swalk->oids[i], oid, reqitem); if (oidbuf) xfree(oidbuf); } swalk = swalk->next; } reqitem->next_oid = reqitem->oidhead; } else { /* Add a key-record so we can try to locate the index */ keyrecord_t *newitem = (keyrecord_t *)calloc(1, sizeof(keyrecord_t)); char endmarks[6]; mibidx++; /* Skip the key-marker */ sprintf(endmarks, "%s%c", ")]}>", iwalk->marker); p = mibidx + strcspn(mibidx, endmarks); *p = '\0'; newitem->key = strdup(mibidx); newitem->indexmethod = iwalk; newitem->mib = mib; newitem->next = reqitem->keyrecords; reqitem->currentkey = reqitem->keyrecords = newitem; } continue; } else { errprintf("Unknown MIB (not in snmpmibs.cfg): '%s'\n", bot); } } stackfclose(cfgfd); freestrbuffer(inbuf); } void communicate(void) { /* loop while any active requests */ while (active_requests) { int fds = 0, block = 1; fd_set fdset; struct timeval timeout; FD_ZERO(&fdset); snmp_select_info(&fds, &fdset, &timeout, &block); fds = select(fds, &fdset, NULL, NULL, block ? NULL : &timeout); if (fds < 0) { perror("select failed"); exit(1); } if (fds) snmp_read(&fdset); else snmp_timeout(); } } void resolvekeys(void) { req_t *rwalk; keyrecord_t *kwalk; oidset_t *swalk; int i; char *oid; /* Fetch the key data, and determine the indices we want to use */ dataoperation = GET_KEYS; starthosts(1); communicate(); /* Generate new requests for the datasets we now know the indices of */ for (rwalk = reqhead; (rwalk); rwalk = rwalk->next) { if (!rwalk->keyrecords) continue; for (kwalk = rwalk->keyrecords; (kwalk); kwalk = kwalk->next) { if (!kwalk->indexoid) { /* Dont report failed lookups for the pseudo match-all key record */ if (*kwalk->key != '*') { /* We failed to determine the index */ errprintf("Could not determine index for host=%s mib=%s key=%s\n", rwalk->hostname, kwalk->mib->mibname, kwalk->key); } continue; } swalk = kwalk->mib->oidlisthead; while (swalk) { rwalk->setnumber++; for (i=0; (i <= swalk->oidcount); i++) { oid = (char *)malloc(strlen(swalk->oids[i].oid) + strlen(kwalk->indexoid) + 2); sprintf(oid, "%s.%s", swalk->oids[i].oid, kwalk->indexoid); make_oitem(kwalk->mib, kwalk->key, &swalk->oids[i], oid, rwalk); xfree(oid); } swalk = swalk->next; } rwalk->next_oid = rwalk->oidhead; } } } void getdata(void) { dataoperation = GET_DATA; starthosts(1); communicate(); } void sendresult(void) { struct req_t *rwalk; struct oid_t *owalk; char msgline[1024]; char *currdev, *currhost; mibdef_t *mib; strbuffer_t *clientmsg = newstrbuffer(0); int havemsg = 0; int itemcount = 0; currhost = ""; for (rwalk = reqhead; (rwalk); rwalk = rwalk->next) { if (strcmp(rwalk->hostname, currhost) != 0) { /* Flush buffer */ if (havemsg) { sprintf(msgline, "\n.\n", itemcount); addtobuffer(clientmsg, msgline); sendmessage(STRBUF(clientmsg), NULL, XYMON_TIMEOUT, NULL); } clearstrbuffer(clientmsg); havemsg = 0; itemcount = 0; sprintf(msgline, "client/snmpcollect %s.snmpcollect snmp\n\n", rwalk->hostname); addtobuffer(clientmsg, msgline); } currdev = ""; for (mib = first_mib(); (mib); mib = next_mib()) { clearstrbuffer(mib->resultbuf); mib->haveresult = 0; sprintf(msgline, "\n[%s]\nInterval=%d\nActiveIP=%s\n\n", mib->mibname, atoi(xgetenv("TASKSLEEP")), rwalk->hostip[rwalk->hostipidx]); addtobuffer(mib->resultbuf, msgline); } for (owalk = rwalk->oidhead; (owalk); owalk = owalk->next) { if (strcmp(currdev, owalk->devname)) { currdev = owalk->devname; /* OK, because ->devname is permanent */ if (*owalk->devname && (*owalk->devname != '-') ) { addtobuffer(owalk->mib->resultbuf, "\n<"); addtobuffer(owalk->mib->resultbuf, owalk->devname); addtobuffer(owalk->mib->resultbuf, ">\n"); itemcount++; } } addtobuffer(owalk->mib->resultbuf, "\t"); addtobuffer(owalk->mib->resultbuf, owalk->oiddef->dsname); addtobuffer(owalk->mib->resultbuf, " = "); if (owalk->result) { int ival; unsigned int uval; switch (owalk->oiddef->conversion) { case OID_CONVERT_U32: ival = atoi(owalk->result); memcpy(&uval, &ival, sizeof(uval)); sprintf(msgline, "%u", uval); addtobuffer(owalk->mib->resultbuf, msgline); break; default: addtobuffer(owalk->mib->resultbuf, owalk->result); break; } } else addtobuffer(owalk->mib->resultbuf, "NODATA"); addtobuffer(owalk->mib->resultbuf, "\n"); owalk->mib->haveresult = 1; } for (mib = first_mib(); (mib); mib = next_mib()) { if (mib->haveresult) { addtostrbuffer(clientmsg, mib->resultbuf); havemsg = 1; } } } if (havemsg) { sendmessage(STRBUF(clientmsg), NULL, XYMON_TIMEOUT, NULL); } freestrbuffer(clientmsg); } void egoresult(int color, char *egocolumn) { char msgline[1024]; char *timestamps = NULL; init_timestamp(); combo_start(); init_status(color); sprintf(msgline, "status %s.%s %s snmpcollect %s\n\n", xgetenv("MACHINE"), egocolumn, colorname(color), timestamp); addtostatus(msgline); sprintf(msgline, "Variables : %d\n", varcount); addtostatus(msgline); sprintf(msgline, "PDUs : %d\n", pducount); addtostatus(msgline); sprintf(msgline, "Responses : %d\n", okcount); addtostatus(msgline); sprintf(msgline, "Timeouts : %d\n", timeoutcount); addtostatus(msgline); sprintf(msgline, "Too big : %d\n", toobigcount); addtostatus(msgline); sprintf(msgline, "Errors : %d\n", errorcount); addtostatus(msgline); show_timestamps(×tamps); if (timestamps) { addtostatus(timestamps); xfree(timestamps); } finish_status(); combo_end(); } int main (int argc, char **argv) { int argi; char *configfn = NULL; int cfgcheck = 0; int mibcheck = 0; for (argi = 1; (argi < argc); argi++) { if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (strcmp(argv[argi], "--no-update") == 0) { dontsendmessages = 1; } else if (strcmp(argv[argi], "--cfgcheck") == 0) { cfgcheck = 1; } else if (strcmp(argv[argi], "--mibcheck") == 0) { mibcheck = 1; } else if (argnmatch(argv[argi], "--timeout=")) { char *p = strchr(argv[argi], '='); timeout = 1000000*atoi(p+1); } else if (argnmatch(argv[argi], "--retries=")) { char *p = strchr(argv[argi], '='); retries = atoi(p+1); } else if (argnmatch(argv[argi], "--concurrency=")) { char *p = strchr(argv[argi], '='); max_pending_requests = atoi(p+1); } else if (argnmatch(argv[argi], "--report=")) { char *p = strchr(argv[argi], '='); reportcolumn = strdup(p+1); timing = 1; } else if (*argv[argi] != '-') { configfn = strdup(argv[argi]); } } add_timestamp("xymon-snmpcollect startup"); netsnmp_register_loghandler(NETSNMP_LOGHANDLER_STDERR, 7); init_snmp("xymon-snmpcollect"); snmp_mib_toggle_options("e"); /* Like -Pe: Dont show MIB parsing errors */ snmp_out_toggle_options("qn"); /* Like -Oqn: OID's printed as numbers, values printed without type */ readmibs(NULL, mibcheck); if (configfn == NULL) { configfn = (char *)malloc(PATH_MAX); sprintf(configfn, "%s/etc/snmphosts.cfg", xgetenv("XYMONHOME")); } readconfig(configfn, mibcheck); if (cfgcheck) return 0; add_timestamp("Configuration loaded"); resolvekeys(); add_timestamp("Keys lookup complete"); getdata(); stophosts(); add_timestamp("Data retrieved"); sendresult(); add_timestamp("Results transmitted"); if (reportcolumn) egoresult(COL_GREEN, reportcolumn); xfree(configfn); return 0; } xymon-4.3.7/xymonnet/ldaptest.h0000664000175000017500000000400311615341300016064 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* This is used to implement the testing of a LDAP service. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __LDAPTEST_H_ #define __LDAPTEST_H_ #include #ifdef XYMON_LDAP #include #define LDAP_DEPRECATED 1 #include #ifndef LDAPS_PORT #define LDAPS_PORT 636 #endif #endif typedef struct { void *ldapdesc; /* Result from ldap_url_parse() */ int usetls; int skiptest; /* Skip check if failed TCP connect */ int ldapstatus; /* Status from library of the ldap transaction */ char *output; /* Output from ldap query */ int ldapcolor; /* Final color reported */ char *faileddeps; struct timespec duration; char *certinfo; /* Data about SSL certificate */ time_t certexpires; /* Expiry time for SSL cert */ int mincipherbits; } ldap_data_t; extern char *ldap_library_version; extern int init_ldap_library(void); extern void shutdown_ldap_library(void); extern int add_ldap_test(testitem_t *t); extern void run_ldap_tests(service_t *ldaptest, int sslcertcheck, int timeout); extern void show_ldap_test_results(service_t *ldaptest); extern void send_ldap_results(service_t *ldaptest, testedhost_t *host, char *nonetpage, int failgoesclear); #endif xymon-4.3.7/xymonnet/dns.h0000664000175000017500000000275411630445564015061 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __DNS_H__ #define __DNS_H__ #include /* dnslookup values */ #define DNS_THEN_IP 0 /* Try DNS - if it fails, use IP from hosts.cfg */ #define DNS_ONLY 1 /* DNS only - if it fails, report service down */ #define IP_ONLY 2 /* IP only - dont do DNS lookups */ extern int use_ares_lookup; extern int max_dns_per_run; extern int dnstimeout; extern int dns_stats_total; extern int dns_stats_success; extern int dns_stats_failed; extern int dns_stats_lookups; extern FILE *dnsfaillog; extern void add_host_to_dns_queue(char *hostname); extern void add_url_to_dns_queue(char *hostname); extern void flush_dnsqueue(void); extern char *dnsresolve(char *hostname); extern int dns_test_server(char *serverip, char *hostname, strbuffer_t *banner); #endif xymon-4.3.7/xymonnet/xymonnet.h0000664000175000017500000001665311615341300016143 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __XYMONNET_H__ #define __XYMONNET_H__ #include #define STATUS_CONTENTMATCH_NOFILE 901 #define STATUS_CONTENTMATCH_FAILED 902 #define STATUS_CONTENTMATCH_BADREGEX 903 #define MAX_CONTENT_DATA (1024*1024) /* 1 MB should be enough for most */ /* toolids */ enum toolid_t { TOOL_CONTEST, TOOL_DNS, TOOL_NTP, TOOL_FPING, TOOL_HTTP, TOOL_LDAP, TOOL_RPCINFO }; /* * Structure of the xymonnet in-memory records * * +->service_t * | testname * | namelen * | portnum * | toolid * | items --------------> testitem_t <----------------------------------------------+ * | next +------- service | * | | host ---------------+ * +-----------------------+ testspec | | * dialup +-->testedhost_t <------------+ | * reverse hostname | | * silenttest ip | | * alwaystrue dialup | | * open testip | | * banner nosslcert | | * certinfo dodns | | * duration dnserror | | * badtest ////////// | | * downcount repeattest | | * downstart noconn | | * privdata ----+ noping | | * next | badconn | | * | downcount | | * | downstart | | * | routerdeps | | * | deprouterdown --------+ | * | firsthttp -----------------+ * | firstldap -----------------+ * | ldapuser * | ldappasswd * | sslwarndays * | sslalarmdays * | * +---------> */ typedef struct service_t { char *testname; /* Name of the test = column name in Xymon report */ int namelen; /* Length of name - "testname:port" has this as strlen(testname), others 0 */ int portnum; /* Port number this service runs on */ enum toolid_t toolid; /* Which tool to use */ struct testitem_t *items; /* testitem_t linked list of tests for this service */ } service_t; enum multiping_t { MULTIPING_BEST, MULTIPING_WORST }; typedef struct ipping_t { char *ip; int open; strbuffer_t *banner; struct ipping_t *next; } ipping_t; typedef struct extraping_t { enum multiping_t matchtype; ipping_t *iplist; } extraping_t; typedef struct testedhost_t { char *hostname; char ip[IP_ADDR_STRLEN]; int dialup; /* dialup flag (if set, failed tests report as clear) */ int testip; /* testip flag (dont do dns lookups on hostname) */ int nosslcert; /* nosslcert flag */ int hidehttp; /* hidehttp flag */ int dodns; /* set while loading tests if we need to do a DNS lookup */ int dnserror; /* set internally if we cannot find the host's IP */ int repeattest; /* Set if this host goes on the quick poll list */ char *hosttype; /* For the "Intermediate HOSTTYPE is down" message */ /* The following are for the connectivity test */ int noconn; /* noconn flag (dont report "conn" at all */ int noping; /* noping flag (report "conn" as clear=disabled */ int badconn[3]; /* badconn:x:y:z flag */ int downcount; /* number of successive failed conn tests */ time_t downstart; /* time() of first conn failure */ char *routerdeps; /* Hosts from the "router:" tag */ struct testedhost_t *deprouterdown; /* Set if dependant router is down */ int dotrace; /* Run traceroute for this host */ strbuffer_t *traceroute;/* traceroute results */ struct extraping_t *extrapings; /* The following is for the HTTP/FTP URL tests */ struct testitem_t *firsthttp; /* First HTTP testitem in testitem list */ /* The following is for the LDAP tests */ struct testitem_t *firstldap; /* First LDAP testitem in testitem list */ char *ldapuser; /* Username */ char *ldappasswd; /* Password */ int ldapsearchfailyellow; /* Go red or yellow on failed search */ /* The following is for the SSL certificate checks */ int sslwarndays; int sslalarmdays; int mincipherbits; /* For storing the test dependency tag. */ char *deptests; } testedhost_t; typedef struct testitem_t { struct testedhost_t *host; /* Pointer to testedhost_t record for this host */ struct service_t *service; /* Pointer to service_t record for the service to test */ char *testspec; /* Pointer to the raw testspec in hosts.cfg */ int reverse; /* "!testname" flag */ int dialup; /* "?testname" flag */ int alwaystrue; /* "~testname" flag */ int silenttest; /* "testname:s" flag */ int senddata; /* For tests that merely generate a "data" report */ char *srcip; /* These data may be filled in from the test engine private data */ int open; /* Is the service open ? NB: Shows true state of service, ignores flags */ strbuffer_t *banner; char *certinfo; time_t certexpires; int mincipherbits; struct timespec duration; /* For badTEST handling: Need to track downtime duration and poll count */ int badtest[3]; time_t downstart; int downcount; /* Number of polls when down. */ /* Each test engine has its own data */ void *privdata; /* Private data use by test tool */ int internal; /* For internal use, not to be reported back to Xymon server */ struct testitem_t *next; } testitem_t; typedef struct dnstest_t { int testcount; int okcount; } dnstest_t; extern char *deptest_failed(testedhost_t *host, char *testname); extern int validity; #endif xymon-4.3.7/xymonnet/beastat.c0000664000175000017500000002271511615341300015674 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor BEA statistics tool. */ /* */ /* This is used to collect statistics from a BEA Weblogic server */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: beastat.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "version.h" typedef struct bea_idx_t { char *idx; struct bea_idx_t *next; } bea_idx_t; static bea_idx_t *bea_idxhead = NULL; static char msgline[MAX_LINE_LEN]; static int statuscolor = COL_GREEN; /* Set with environment or command-line options */ static char *location = ""; /* XYMONNETWORK value */ static int testuntagged = 0; static int default_port = 161; static char *default_community = "public"; static int extcmdtimeout = 30; static void find_idxes(char *buf, char *searchstr) { bea_idx_t *idxwalk; char *bol, *eoln, *idxval; /* If we've done it before, clear out the old indexes */ while (bea_idxhead) { idxwalk = bea_idxhead; bea_idxhead = bea_idxhead->next; xfree(idxwalk->idx); xfree(idxwalk); } bea_idxhead = NULL; bol = buf; while ((bol = strstr(bol, searchstr)) != NULL) { idxval = NULL; bol++; eoln = strchr(bol, '\n'); if (eoln) *eoln = '\0'; bol = strchr(bol, '='); if (bol) bol = strchr(bol, '\"'); if (bol) idxval = bol+1; if (bol) bol = strchr(bol+1, '\"'); if (bol) { *bol = '\0'; idxwalk = (bea_idx_t *)malloc(sizeof(bea_idx_t)); idxwalk->idx = strdup(idxval); idxwalk->next = bea_idxhead; bea_idxhead = idxwalk; *bol = '\"'; } if (eoln) *eoln = '\n'; } } int wanted_host(void *host, char *netstring) { char *netlocation = xmh_item(host, XMH_NET); return ((strlen(netstring) == 0) || /* No XYMONNETWORK = do all */ (netlocation && (strcmp(netlocation, netstring) == 0)) || /* XYMONNETWORK && matching NET: tag */ (testuntagged && (netlocation == NULL))); /* No NET: tag for this host */ } char *getstring(char *databuf, char *beaindex, char *key) { static char result[4096]; char keystr[4096]; char *p, *eol; *result = '\0'; sprintf(keystr, "\nBEA-WEBLOGIC-MIB::%s.\"%s\"", key, beaindex); p = strstr(databuf, keystr); if (!p) { /* Might be at the very beginning of the buffer (with no \n first) */ if (strncmp(databuf, keystr+1, strlen(keystr)-1) == 0) p = databuf; } else { p++; } if (p) { eol = strchr(p, '\n'); if (eol) *eol = '\0'; strcpy(result, p); if (eol) *eol = '\n'; } return result; } char *jrockitems[] = { "jrockitRuntimeIndex", "jrockitRuntimeParent", "jrockitRuntimeFreeHeap", "jrockitRuntimeUsedHeap", "jrockitRuntimeTotalHeap", "jrockitRuntimeFreePhysicalMemory", "jrockitRuntimeUsedPhysicalMemory", "jrockitRuntimeTotalPhysicalMemory", "jrockitRuntimeTotalNumberOfThreads", "jrockitRuntimeNumberOfDaemonThreads", "jrockitRuntimeTotalNurserySize", NULL }; char *qitems[] = { "executeQueueRuntimeIndex", "executeQueueRuntimeName", "executeQueueRuntimeParent", "executeQueueRuntimeExecuteThreadCurrentIdleCount", "executeQueueRuntimePendingRequestCurrentCount", "executeQueueRuntimeServicedRequestTotalCount", NULL }; void send_data(void *host, char *beadomain, char *databuf, char **items) { bea_idx_t *idxwalk; strbuffer_t *msgbuf; char *p; int i; msgbuf = newstrbuffer(0); for (idxwalk = bea_idxhead; (idxwalk); idxwalk = idxwalk->next) { sprintf(msgline, "data %s.bea\n\n", commafy(xmh_item(host, XMH_HOSTNAME))); addtobuffer(msgbuf, msgline); if (beadomain && *beadomain) { sprintf(msgline, "DOMAIN:%s\n", beadomain); addtobuffer(msgbuf, msgline); } for (i=0; (items[i]); i++) { p = getstring(databuf, idxwalk->idx, items[i]); sprintf(msgline, "%s\n", p); addtobuffer(msgbuf, msgline); } sendmessage(STRBUF(msgbuf), NULL, XYMON_TIMEOUT, NULL); clearstrbuffer(msgbuf); } freestrbuffer(msgbuf); } int main(int argc, char *argv[]) { void *hwalk; int argi; strbuffer_t *statusmsg, *jrockout, *qout; for (argi = 1; (argi < argc); argi++) { if ((strcmp(argv[argi], "--help") == 0)) { printf("beastat version %s\n\n", VERSION); printf("Usage:\n%s [--debug] [--no-update] [--port=SNMPPORT] [--community=SNMPCOMMUNITY]\n", argv[0]); exit(0); } else if ((strcmp(argv[argi], "--version") == 0)) { printf("beastat version %s\n", VERSION); exit(0); } else if ((strcmp(argv[argi], "--debug") == 0)) { debug = 1; } else if ((strcmp(argv[argi], "--no-update") == 0)) { dontsendmessages = 1; } else if (argnmatch(argv[argi], "--timeout=")) { char *p = strchr(argv[argi], '='); extcmdtimeout = atoi(p+1); } else if (argnmatch(argv[argi], "--port=")) { char *p = strchr(argv[argi], '='); default_port = atoi(p+1); } else if (argnmatch(argv[argi], "--community=")) { char *p = strchr(argv[argi], '='); default_community = strdup(p+1); } } load_hostnames(xgetenv("HOSTSCFG"), "netinclude", get_fqdn()); if (first_host() == NULL) { errprintf("Cannot load file %s\n", xgetenv("HOSTSCFG")); return 1; } if (xgetenv("XYMONNETWORK") && (strlen(xgetenv("XYMONNETWORK")) > 0)) location = strdup(xgetenv("XYMONNETWORK")); else if (xgetenv("BBLOCATION") && (strlen(xgetenv("BBLOCATION")) > 0)) location = strdup(xgetenv("BBLOCATION")); init_timestamp(); combo_start(); statusmsg = newstrbuffer(0); jrockout = newstrbuffer(0); qout = newstrbuffer(0); for (hwalk = first_host(); (hwalk); hwalk = next_host(hwalk, 0)) { char *tspec = xmh_custom_item(hwalk, "bea="); char *snmpcommunity = default_community; char *beadomain = ""; int snmpport = default_port; char *p; char pipecmd[4096]; int jrockres, qres; clearstrbuffer(statusmsg); clearstrbuffer(jrockout); clearstrbuffer(qout); /* Check if we have a "bea" test for this host, and it is a host we want to test */ if (!tspec || !wanted_host(hwalk, location)) continue; /* Parse the testspec: bea=[SNMPCOMMUNITY@]BEADOMAIN[:SNMPPORT] */ tspec = strdup(tspec+strlen("bea=")); p = strchr(tspec, ':'); if (p) { *p = '\0'; snmpport = atoi(p+1); } p = strchr(tspec, '@'); if (p) { *p = '\0'; snmpcommunity = strdup(tspec); beadomain = strdup(p+1); } else { beadomain = strdup(tspec); } /* Prepare for the host status */ statuscolor = COL_GREEN; /* Setup the snmpwalk pipe-command for jrockit stats */ sprintf(pipecmd, "snmpwalk -m BEA-WEBLOGIC-MIB -c %s@%s -v 1 %s:%d enterprises.140.625.302.1", snmpcommunity, beadomain, xmh_item(hwalk, XMH_IP), snmpport); jrockres = run_command(pipecmd, NULL, jrockout, 0, extcmdtimeout); if (jrockres == 0) { find_idxes(STRBUF(jrockout), "BEA-WEBLOGIC-MIB::jrockitRuntimeIndex."); send_data(hwalk, beadomain, STRBUF(jrockout), jrockitems); } else { if (statuscolor < COL_YELLOW) statuscolor = COL_YELLOW; sprintf(msgline, "Could not retrieve BEA jRockit statistics from %s:%d domain %s (code %d)\n", xmh_item(hwalk, XMH_IP), snmpport, beadomain, jrockres); addtobuffer(statusmsg, msgline); } /* Setup the snmpwalk pipe-command for executeQueur stats */ sprintf(pipecmd, "snmpwalk -m BEA-WEBLOGIC-MIB -c %s@%s -v 1 %s:%d enterprises.140.625.180.1", snmpcommunity, beadomain, xmh_item(hwalk, XMH_IP), snmpport); qres = run_command(pipecmd, NULL, qout, 0, extcmdtimeout); if (qres == 0) { find_idxes(STRBUF(qout), "BEA-WEBLOGIC-MIB::executeQueueRuntimeIndex."); send_data(hwalk, beadomain, STRBUF(qout), qitems); } else { if (statuscolor < COL_YELLOW) statuscolor = COL_YELLOW; sprintf(msgline, "Could not retrieve BEA executeQueue statistics from %s:%d domain %s (code %d)\n", xmh_item(hwalk, XMH_IP), snmpport, beadomain, qres); addtobuffer(statusmsg, msgline); } /* FUTURE: Have the statuscolor/statusmsg be updated to check against thresholds */ /* Right now, the "bea" status is always green */ init_status(statuscolor); sprintf(msgline, "status %s.%s %s %s\n\n", commafy(xmh_item(hwalk, XMH_HOSTNAME)), "bea", colorname(statuscolor), timestamp); addtostatus(msgline); if (STRBUFLEN(statusmsg) == 0) addtobuffer(statusmsg, "All BEA monitors OK\n"); addtostrstatus(statusmsg); finish_status(); } combo_end(); freestrbuffer(statusmsg); freestrbuffer(jrockout); freestrbuffer(qout); return 0; } xymon-4.3.7/xymonnet/contest.c0000664000175000017500000012572111615341300015731 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* This is used to implement the testing of a TCP service. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: contest.c 6712 2011-07-31 21:01:52Z storner $"; #include "config.h" #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include /* Someday I'll move to GNU Autoconf for this ... */ #endif #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymonnet.h" #include "contest.h" #include "httptest.h" #include "dns.h" /* BSD uses RLIMIT_OFILE */ #if defined(RLIMIT_OFILE) && !defined(RLIMIT_NOFILE) #define RLIMIT_NOFILE RLIMIT_OFILE #endif #define MAX_TELNET_CYCLES 5 /* Max loops with telnet options before aborting banner */ #define SSLSETUP_PENDING -1 /* Magic value for tcptest_t->sslrunning while handshaking */ #define SLOWLIMSECS 5 /* How long a socket may be inactive before deemed slow */ /* See http://www.openssl.org/docs/apps/ciphers.html for cipher strings */ char *ciphersmedium = "MEDIUM"; /* Must be formatted for openssl library */ char *ciphershigh = "HIGH"; /* Must be formatted for openssl library */ unsigned int tcp_stats_total = 0; unsigned int tcp_stats_http = 0; unsigned int tcp_stats_plain = 0; unsigned int tcp_stats_connects = 0; unsigned long tcp_stats_read = 0; unsigned long tcp_stats_written = 0; unsigned int warnbytesread = 0; static tcptest_t *thead = NULL; int shuffletests = 0; static svcinfo_t svcinfo_http = { "http", NULL, 0, NULL, 0, 0, (TCP_GET_BANNER|TCP_HTTP), 80 }; static svcinfo_t svcinfo_https = { "https", NULL, 0, NULL, 0, 0, (TCP_GET_BANNER|TCP_HTTP|TCP_SSL), 443 }; static ssloptions_t default_sslopt = { NULL, SSLVERSION_DEFAULT }; static time_t sslcert_expiretime(char *timestr) { int res; time_t t1, t2; struct tm *t; struct tm exptime; time_t gmtofs, result; memset(&exptime, 0, sizeof(exptime)); /* expire date: 2004-01-02 08:04:15 GMT */ res = sscanf(timestr, "%4d-%2d-%2d %2d:%2d:%2d", &exptime.tm_year, &exptime.tm_mon, &exptime.tm_mday, &exptime.tm_hour, &exptime.tm_min, &exptime.tm_sec); if (res != 6) { errprintf("Cannot interpret certificate time %s\n", timestr); return 0; } /* tm_year is 1900 based; tm_mon is 0 based */ exptime.tm_year -= 1900; exptime.tm_mon -= 1; result = mktime(&exptime); if (result > 0) { /* * Calculate the difference between localtime and GMT */ t = gmtime(&result); t->tm_isdst = 0; t1 = mktime(t); t = localtime(&result); t->tm_isdst = 0; t2 = mktime(t); gmtofs = (t2-t1); result += gmtofs; } else { /* * mktime failed - probably it expires after the * Jan 19,2038 rollover for a 32-bit time_t. */ result = INT_MAX; } dbgprintf("Output says it expires: %s", timestr); dbgprintf("I think it expires at (localtime) %s\n", asctime(localtime(&result))); return result; } static int tcp_callback(unsigned char *buf, unsigned int len, void *priv) { /* * The default data callback function for simple TCP tests. */ tcptest_t *item = (tcptest_t *) priv; if (item->banner == NULL) { item->banner = (unsigned char *)malloc(len+1); } else { item->banner = (unsigned char *)realloc(item->banner, item->bannerbytes+len+1); } memcpy(item->banner+item->bannerbytes, buf, len); item->bannerbytes += len; *(item->banner + item->bannerbytes) = '\0'; return 1; /* We always just grab the first bit of data for TCP tests */ } tcptest_t *add_tcp_test(char *ip, int port, char *service, ssloptions_t *sslopt, char *srcip, char *tspec, int silent, unsigned char *reqmsg, void *priv, f_callback_data datacallback, f_callback_final finalcallback) { tcptest_t *newtest; dbgprintf("Adding tcp test IP=%s, port=%d, service=%s, silent=%d\n", textornull(ip), port, service, silent); if (port == 0) { errprintf("Trying to scan port 0 for service %s\n", service); errprintf("You probably need to define the %s service in /etc/services\n", service); return NULL; } tcp_stats_total++; newtest = (tcptest_t *) calloc(1, sizeof(tcptest_t)); newtest->tspec = (tspec ? strdup(tspec) : NULL); newtest->fd = -1; newtest->lastactive = 0; newtest->bytesread = 0; newtest->byteswritten = 0; newtest->open = 0; newtest->connres = -1; newtest->errcode = CONTEST_ENOERROR; newtest->duration.tv_sec = newtest->duration.tv_nsec = 0; newtest->totaltime.tv_sec = newtest->totaltime.tv_nsec = 0; memset(&newtest->addr, 0, sizeof(newtest->addr)); newtest->addr.sin_family = PF_INET; newtest->addr.sin_port = htons(port); if ((ip == NULL) || (strlen(ip) == 0) || (inet_aton(ip, (struct in_addr *) &newtest->addr.sin_addr.s_addr) == 0)) { newtest->errcode = CONTEST_EDNS; } newtest->srcaddr = (srcip ? strdup(srcip) : NULL); if (strcmp(service, "http") == 0) { newtest->svcinfo = &svcinfo_http; tcp_stats_http++; } else if (strcmp(service, "https") == 0) { newtest->svcinfo = &svcinfo_https; tcp_stats_http++; } else { newtest->svcinfo = find_tcp_service(service); tcp_stats_plain++; } newtest->sendtxt = (reqmsg ? reqmsg : newtest->svcinfo->sendtxt); newtest->sendlen = (reqmsg ? strlen(reqmsg) : newtest->svcinfo->sendlen); newtest->silenttest = silent; newtest->readpending = 0; newtest->telnetnegotiate = (((newtest->svcinfo->flags & TCP_TELNET) && !silent) ? MAX_TELNET_CYCLES : 0); newtest->telnetbuf = NULL; newtest->telnetbuflen = 0; newtest->ssloptions = (sslopt ? sslopt : &default_sslopt); newtest->sslctx = NULL; newtest->ssldata = NULL; newtest->certinfo = NULL; newtest->certexpires = 0; newtest->sslrunning = ((newtest->svcinfo->flags & TCP_SSL) ? SSLSETUP_PENDING : 0); newtest->sslagain = 0; newtest->banner = NULL; newtest->bannerbytes = 0; if (datacallback == NULL) { /* * Use the default callback-routine, which expects * "priv" to point at the test item. */ newtest->priv = newtest; newtest->datacallback = tcp_callback; } else { /* * Custom callback - handles data output by itself. */ newtest->priv = priv; newtest->datacallback = datacallback; } newtest->finalcallback = finalcallback; if (newtest->errcode == CONTEST_ENOERROR) { newtest->next = thead; thead = newtest; } return newtest; } static void get_connectiontime(tcptest_t *item, struct timespec *timestamp) { tvdiff(&item->timestart, timestamp, &item->duration); } static void get_totaltime(tcptest_t *item, struct timespec *timestamp) { tvdiff(&item->timestart, timestamp, &item->totaltime); } static int do_telnet_options(tcptest_t *item) { /* * Handle telnet options. * * This code was taken from the sources for "netcat" version 1.10 * by "Xymon" . */ unsigned char *obuf; int remain; unsigned char y; unsigned char *inp; unsigned char *outp; int result = 0; if (item->telnetbuflen == 0) { dbgprintf("Ignoring telnet option with length 0\n"); return 0; } obuf = (unsigned char *)malloc(item->telnetbuflen); y = 0; inp = item->telnetbuf; remain = item->telnetbuflen; outp = obuf; while (remain > 0) { if ((remain < 3) || (*inp != 255)) { /* IAC? */ /* * End of options. * We probably have the banner in the remainder of the * buffer, so copy it over, and return it. */ item->banner = strdup(inp); item->bannerbytes = strlen(inp); item->telnetbuflen = 0; xfree(obuf); return 0; } *outp = 255; outp++; inp++; remain--; if ((*inp == 251) || (*inp == 252)) /* WILL or WONT */ y = 254; /* -> DONT */ if ((*inp == 253) || (*inp == 254)) /* DO or DONT */ y = 252; /* -> WONT */ if (y) { *outp = y; outp++; inp++; remain--; *outp = *inp; outp++; /* copy actual option byte */ y = 0; result = 1; } /* if y */ inp++; remain--; } /* while remain */ item->telnetbuflen = (outp-obuf); if (item->telnetbuflen) memcpy(item->telnetbuf, obuf, item->telnetbuflen); item->telnetbuf[item->telnetbuflen] = '\0'; xfree(obuf); return result; } #if TCP_SSL <= 0 char *ssl_library_version = NULL; /* * Define stub routines for plain socket operations without SSL */ static void setup_ssl(tcptest_t *item) { errprintf("SSL test, but xymonnet was built without SSL support\n"); item->sslrunning = 0; item->errcode = CONTEST_ESSL; } static int socket_write(tcptest_t *item, unsigned char *outbuf, int outlen) { int n = write(item->fd, outbuf, outlen); item->byteswritten += n; return n; } static int socket_read(tcptest_t *item, unsigned char *inbuf, int inbufsize) { int n = read(item->fd, inbuf, inbufsize); item->bytesread += n; return n; } static void socket_shutdown(tcptest_t *item) { shutdown(item->fd, SHUT_RDWR); if (warnbytesread && (item->bytesread > warnbytesread)) { if (item->tspec) errprintf("Huge response %u bytes from %s\n", item->bytesread, item->tspec); else errprintf("Huge response %u bytes for %s:%s\n", item->bytesread, inet_ntoa(item->addr.sin_addr), item->svcinfo->svcname); } } #else char *ssl_library_version = OPENSSL_VERSION_TEXT; static int cert_password_cb(char *buf, int size, int rwflag, void *userdata) { FILE *passfd; char *p; char passfn[PATH_MAX]; char passphrase[1024]; tcptest_t *item = (tcptest_t *)userdata; memset(passphrase, 0, sizeof(passphrase)); /* * Private key passphrases are stored in the file named same as the * certificate itself, but with extension ".pass" */ sprintf(passfn, "%s/certs/%s", xgetenv("XYMONHOME"), item->ssloptions->clientcert); p = strrchr(passfn, '.'); if (p == NULL) p = passfn+strlen(passfn); strcpy(p, ".pass"); passfd = fopen(passfn, "r"); if (passfd) { fgets(passphrase, sizeof(passphrase)-1, passfd); p = strchr(passphrase, '\n'); if (p) *p = '\0'; fclose(passfd); } strncpy(buf, passphrase, size); buf[size - 1] = '\0'; /* Clear this buffer for security! Dont want passphrases in core dumps... */ memset(passphrase, 0, sizeof(passphrase)); return strlen(buf); } static char *xymon_ASN1_UTCTIME(ASN1_UTCTIME *tm) { static char result[256]; char *asn1_string; int gmt=0; int len, i; int century=0,year=0,month=0,day=0,hour=0,minute=0,second=0; len=tm->length; asn1_string=(char *)tm->data; if (len < 10) return NULL; if (asn1_string[len-1] == 'Z') gmt=1; for (i=0; i '9') || (asn1_string[i] < '0')) return NULL; } if (len >= 15) { /* 20541024111745Z format */ century = 100 * ((asn1_string[0]-'0')*10+(asn1_string[1]-'0')); asn1_string += 2; } year=(asn1_string[0]-'0')*10+(asn1_string[1]-'0'); if (century == 0 && year < 50) year+=100; month=(asn1_string[2]-'0')*10+(asn1_string[3]-'0'); if ((month > 12) || (month < 1)) return NULL; day=(asn1_string[4]-'0')*10+(asn1_string[5]-'0'); hour=(asn1_string[6]-'0')*10+(asn1_string[7]-'0'); minute=(asn1_string[8]-'0')*10+(asn1_string[9]-'0'); if ( (asn1_string[10] >= '0') && (asn1_string[10] <= '9') && (asn1_string[11] >= '0') && (asn1_string[11] <= '9')) { second= (asn1_string[10]-'0')*10+(asn1_string[11]-'0'); } sprintf(result, "%04d-%02d-%02d %02d:%02d:%02d %s", year+(century?century:1900), month, day, hour, minute, second, (gmt?"GMT":"")); return result; } static void setup_ssl(tcptest_t *item) { static int ssl_init_complete = 0; struct servent *sp; char portinfo[100]; X509 *peercert; char *certcn, *certstart, *certend; int err; strbuffer_t *sslinfo; char msglin[2048]; item->sslrunning = 1; if (!ssl_init_complete) { /* Setup entropy */ if (RAND_status() != 1) { char path[PATH_MAX]; /* Path for the random file */ /* load entropy from files */ RAND_load_file(RAND_file_name(path, sizeof (path)), -1); /* load entropy from egd sockets */ RAND_egd("/var/run/egd-pool"); RAND_egd("/dev/egd-pool"); RAND_egd("/etc/egd-pool"); RAND_egd("/var/spool/prngd/pool"); /* shuffle $RANDFILE (or ~/.rnd if unset) */ RAND_write_file(RAND_file_name(path, sizeof (path))); if (RAND_status() != 1) { errprintf("Failed to find enough entropy on your system"); item->errcode = CONTEST_ESSL; return; } } SSL_load_error_strings(); SSL_library_init(); ssl_init_complete = 1; } if (item->sslctx == NULL) { switch (item->ssloptions->sslversion) { case SSLVERSION_V2: item->sslctx = SSL_CTX_new(SSLv2_client_method()); break; case SSLVERSION_V3: item->sslctx = SSL_CTX_new(SSLv3_client_method()); break; case SSLVERSION_TLS1: item->sslctx = SSL_CTX_new(TLSv1_client_method()); break; default: item->sslctx = SSL_CTX_new(SSLv23_client_method()); break; } if (!item->sslctx) { char sslerrmsg[256]; ERR_error_string(ERR_get_error(), sslerrmsg); errprintf("Cannot create SSL context - IP %s, service %s: %s\n", inet_ntoa(item->addr.sin_addr), item->svcinfo->svcname, sslerrmsg); item->sslrunning = 0; item->errcode = CONTEST_ESSL; return; } /* Workaround SSL bugs */ SSL_CTX_set_options(item->sslctx, SSL_OP_ALL); SSL_CTX_set_quiet_shutdown(item->sslctx, 1); /* Limit set of ciphers, if user wants to */ if (item->ssloptions->cipherlist) SSL_CTX_set_cipher_list(item->sslctx, item->ssloptions->cipherlist); if (item->ssloptions->clientcert) { int status; char certfn[PATH_MAX]; SSL_CTX_set_default_passwd_cb(item->sslctx, cert_password_cb); SSL_CTX_set_default_passwd_cb_userdata(item->sslctx, item); sprintf(certfn, "%s/certs/%s", xgetenv("XYMONHOME"), item->ssloptions->clientcert); status = SSL_CTX_use_certificate_chain_file(item->sslctx, certfn); if (status == 1) { status = SSL_CTX_use_PrivateKey_file(item->sslctx, certfn, SSL_FILETYPE_PEM); } if (status != 1) { char sslerrmsg[256]; ERR_error_string(ERR_get_error(), sslerrmsg); errprintf("Cannot load SSL client certificate/key %s: %s\n", item->ssloptions->clientcert, sslerrmsg); item->sslrunning = 0; item->errcode = CONTEST_ESSL; return; } } } if (item->ssldata == NULL) { item->ssldata = SSL_new(item->sslctx); if (!item->ssldata) { char sslerrmsg[256]; ERR_error_string(ERR_get_error(), sslerrmsg); errprintf("SSL_new failed - IP %s, service %s: %s\n", inet_ntoa(item->addr.sin_addr), item->svcinfo->svcname, sslerrmsg); item->sslrunning = 0; SSL_CTX_free(item->sslctx); item->errcode = CONTEST_ESSL; return; } /* Verify that the client certificate is working */ if (item->ssloptions->clientcert) { X509 *x509; x509 = SSL_get_certificate(item->ssldata); if(x509 != NULL) { EVP_PKEY *pktmp = X509_get_pubkey(x509); EVP_PKEY_copy_parameters(pktmp,SSL_get_privatekey(item->ssldata)); EVP_PKEY_free(pktmp); } if (!SSL_CTX_check_private_key(item->sslctx)) { errprintf("Private/public key mismatch for certificate %s\n", item->ssloptions->clientcert); item->sslrunning = 0; item->errcode = CONTEST_ESSL; return; } } /* SSL setup is done. Now attach the socket FD to the SSL protocol handler */ if (SSL_set_fd(item->ssldata, item->fd) != 1) { char sslerrmsg[256]; ERR_error_string(ERR_get_error(), sslerrmsg); errprintf("Could not initiate SSL on connection - IP %s, service %s: %s\n", inet_ntoa(item->addr.sin_addr), item->svcinfo->svcname, sslerrmsg); item->sslrunning = 0; SSL_free(item->ssldata); SSL_CTX_free(item->sslctx); item->errcode = CONTEST_ESSL; return; } } sp = getservbyport(item->addr.sin_port, "tcp"); if (sp) { sprintf(portinfo, "%s (%d/tcp)", sp->s_name, item->addr.sin_port); } else { sprintf(portinfo, "%d/tcp", item->addr.sin_port); } if ((err = SSL_connect(item->ssldata)) != 1) { char sslerrmsg[256]; switch (SSL_get_error (item->ssldata, err)) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: item->sslrunning = SSLSETUP_PENDING; break; case SSL_ERROR_SYSCALL: ERR_error_string(ERR_get_error(), sslerrmsg); /* Filter out the bogus SSL error */ if (strstr(sslerrmsg, "error:00000000:") == NULL) { errprintf("IO error in SSL_connect to %s on host %s: %s\n", portinfo, inet_ntoa(item->addr.sin_addr), sslerrmsg); } item->errcode = CONTEST_ESSL; item->sslrunning = 0; SSL_free(item->ssldata); SSL_CTX_free(item->sslctx); break; case SSL_ERROR_SSL: ERR_error_string(ERR_get_error(), sslerrmsg); errprintf("Unspecified SSL error in SSL_connect to %s on host %s: %s\n", portinfo, inet_ntoa(item->addr.sin_addr), sslerrmsg); item->errcode = CONTEST_ESSL; item->sslrunning = 0; SSL_free(item->ssldata); SSL_CTX_free(item->sslctx); break; default: ERR_error_string(ERR_get_error(), sslerrmsg); errprintf("Unknown error %d in SSL_connect to %s on host %s: %s\n", err, portinfo, inet_ntoa(item->addr.sin_addr), sslerrmsg); item->errcode = CONTEST_ESSL; item->sslrunning = 0; SSL_free(item->ssldata); SSL_CTX_free(item->sslctx); break; } return; } /* If we get this far, the SSL handshake has completed. So grab the certificate */ peercert = SSL_get_peer_certificate(item->ssldata); if (!peercert) { errprintf("Cannot get peer certificate for %s on host %s\n", portinfo, inet_ntoa(item->addr.sin_addr)); item->errcode = CONTEST_ESSL; item->sslrunning = 0; SSL_free(item->ssldata); SSL_CTX_free(item->sslctx); return; } sslinfo = newstrbuffer(0); certcn = X509_NAME_oneline(X509_get_subject_name(peercert), NULL, 0); certstart = strdup(xymon_ASN1_UTCTIME(X509_get_notBefore(peercert))); certend = strdup(xymon_ASN1_UTCTIME(X509_get_notAfter(peercert))); snprintf(msglin, sizeof(msglin), "Server certificate:\n\tsubject:%s\n\tstart date: %s\n\texpire date:%s\n", certcn, certstart, certend); addtobuffer(sslinfo, msglin); item->certsubject = strdup(certcn); item->certexpires = sslcert_expiretime(certend); xfree(certcn); xfree(certstart); xfree(certend); X509_free(peercert); /* We list the available ciphers in the SSL cert data */ { int i; STACK_OF(SSL_CIPHER) *sk; addtobuffer(sslinfo, "\nAvailable ciphers:\n"); sk = SSL_get_ciphers(item->ssldata); for (i=0; imincipherbits == 0) || (b1 < item->mincipherbits)) item->mincipherbits = b1; } } item->certinfo = grabstrbuffer(sslinfo); } static int socket_write(tcptest_t *item, char *outbuf, int outlen) { int res = 0; if (item->sslrunning) { res = SSL_write(item->ssldata, outbuf, outlen); if (res < 0) { switch (SSL_get_error (item->ssldata, res)) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: res = 0; break; } } } else { res = write(item->fd, outbuf, outlen); } item->byteswritten += res; return res; } static int socket_read(tcptest_t *item, char *inbuf, int inbufsize) { int res = 0; char errtxt[1024]; if (item->svcinfo->flags & TCP_SSL) { if (item->sslrunning) { item->sslagain = 0; res = SSL_read(item->ssldata, inbuf, inbufsize); if (res < 0) { switch (SSL_get_error (item->ssldata, res)) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: item->sslagain = 1; break; default: ERR_error_string(ERR_get_error(), errtxt); dbgprintf("SSL read error %s\n", errtxt); break; } } } else { /* SSL setup failed - flag 0 bytes read. */ res = 0; } } else { res = read(item->fd, inbuf, inbufsize); if (res < 0) { dbgprintf("Read error %s\n", strerror(errno)); } } if (res > 0) item->bytesread += res; return res; } static void socket_shutdown(tcptest_t *item) { if (item->sslrunning) { SSL_shutdown(item->ssldata); SSL_free(item->ssldata); SSL_CTX_free(item->sslctx); } shutdown(item->fd, SHUT_RDWR); if (warnbytesread && (item->bytesread > warnbytesread)) { if (item->tspec) errprintf("Huge response %u bytes from %s\n", item->bytesread, item->tspec); else errprintf("Huge response %u bytes for %s:%s\n", item->bytesread, inet_ntoa(item->addr.sin_addr), item->svcinfo->svcname); } } #endif static int tcptest_compare(void **a, void **b) { tcptest_t **tcpa = (tcptest_t **)a; tcptest_t **tcpb = (tcptest_t **)b; if ((*tcpa)->randomizer < (*tcpb)->randomizer) return -1; else if ((*tcpa)->randomizer > (*tcpb)->randomizer) return 1; else return 0; } static void * tcptest_getnext(void *a) { return ((tcptest_t *)a)->next; } static void tcptest_setnext(void *a, void *newval) { ((tcptest_t *)a)->next = (tcptest_t *)newval; } void do_tcp_tests(int timeout, int concurrency) { int selres; fd_set readfds, writefds; struct timespec timestamp; int absmaxconcurrency; int activesockets = 0; /* Number of allocated sockets */ int pending = 0; /* Total number of tests */ tcptest_t *nextinqueue; /* Points to the next item to start testing */ tcptest_t *firstactive; /* Points to the first item currently being tested */ /* Thus, active connections are between firstactive..nextinqueue */ tcptest_t *item; int sockok; int maxfd; int res; socklen_t connressize; char msgbuf[4096]; struct rlimit lim; /* If timeout or concurrency are 0, set them to reasonable defaults */ if (timeout == 0) timeout = 10; /* seconds */ /* * Decide how many tests to run in parallel. * If no --concurrency set by user, default to (FD_SETSIZE / 4) - typically 256. * But never go above the ressource limit that is set, or above FD_SETSIZE. * And we save 10 fd's for stdio, libs etc. */ absmaxconcurrency = (FD_SETSIZE - 10); getrlimit(RLIMIT_NOFILE, &lim); if ((lim.rlim_cur > 10) && ((lim.rlim_cur - 10) < absmaxconcurrency)) absmaxconcurrency = (lim.rlim_cur - 10); if (concurrency == 0) concurrency = (FD_SETSIZE / 4); if (concurrency > absmaxconcurrency) concurrency = absmaxconcurrency; dbgprintf("Concurrency evaluation: rlim_cur=%lu, FD_SETSIZE=%d, absmax=%d, initial=%d\n", lim.rlim_cur, FD_SETSIZE, absmaxconcurrency, concurrency); if (shuffletests) { struct timeval tv; struct timezone tz; gettimeofday(&tv, &tz); srandom(tv.tv_usec); } /* How many tests to do ? */ for (item = thead; (item); item = item->next) { if (shuffletests) item->randomizer = random(); pending++; } if (shuffletests) thead = msort(thead, tcptest_compare, tcptest_getnext, tcptest_setnext); firstactive = nextinqueue = thead; dbgprintf("About to do %d TCP tests running %d in parallel, abs.max %d\n", pending, concurrency, absmaxconcurrency); while (pending > 0) { int slowrunning, cclimit; time_t slowtimestamp = gettimer() - SLOWLIMSECS; /* * First, see if we need to allocate new sockets and initiate connections. */ /* * We start by counting the number of tests where the latest activity * happened more than SLOWLIMSECS seconds ago. These are ignored when counting * how many more tests we can start concurrenly. But never exceed the absolute * max. number of concurrently open sockets possible. */ for (item=firstactive, slowrunning = 0; (item != nextinqueue); item=item->next) { if ((item->fd > -1) && (item->lastactive < slowtimestamp)) slowrunning++; } cclimit = concurrency + slowrunning; if (cclimit > absmaxconcurrency) cclimit = absmaxconcurrency; sockok = 1; while (sockok && nextinqueue && (activesockets < cclimit)) { /* * We need to allocate a new socket that has O_NONBLOCK set. */ nextinqueue->fd = socket(PF_INET, SOCK_STREAM, 0); sockok = (nextinqueue->fd != -1); if (sockok) { /* Set the source address */ if (nextinqueue->srcaddr) { struct sockaddr_in src; int isip; memset(&src, 0, sizeof(src)); src.sin_family = PF_INET; src.sin_port = 0; isip = (inet_aton(nextinqueue->srcaddr, (struct in_addr *) &src.sin_addr.s_addr) != 0); if (!isip) { char *envaddr = getenv(nextinqueue->srcaddr); isip = (envaddr && (inet_aton(envaddr, (struct in_addr *) &src.sin_addr.s_addr) != 0)); } if (isip) { res = bind(nextinqueue->fd, (struct sockaddr *)&src, sizeof(src)); if (res != 0) errprintf("WARNING: Could not bind to source IP %s for test %s: %s\n", nextinqueue->srcaddr, nextinqueue->tspec, strerror(errno)); } else { errprintf("WARNING: Invalid source IP %s for test %s, using default\n", nextinqueue->srcaddr, nextinqueue->tspec); } } res = fcntl(nextinqueue->fd, F_SETFL, O_NONBLOCK); if (res == 0) { /* * Initiate the connection attempt ... */ getntimer(&nextinqueue->timestart); nextinqueue->lastactive = nextinqueue->timestart.tv_sec; nextinqueue->cutoff = nextinqueue->timestart.tv_sec + timeout + 1; res = connect(nextinqueue->fd, (struct sockaddr *)&nextinqueue->addr, sizeof(nextinqueue->addr)); /* * Did it work ? */ if ((res == 0) || ((res == -1) && (errno == EINPROGRESS))) { /* This is OK - EINPROGRES and res=0 pick up status in select() */ activesockets++; tcp_stats_connects++; } else if (res == -1) { /* connect() failed. Flag the item as "not open" */ nextinqueue->connres = errno; nextinqueue->open = 0; nextinqueue->errcode = CONTEST_ENOCONN; close(nextinqueue->fd); nextinqueue->fd = -1; pending--; switch (nextinqueue->connres) { /* These may happen if connection is refused immediately */ case ECONNREFUSED : break; case EHOSTUNREACH : break; case ENETUNREACH : break; case EHOSTDOWN : break; /* Not likely ... */ case ETIMEDOUT : break; /* These should not happen. */ case EBADF : errprintf("connect returned EBADF!\n"); break; case ENOTSOCK : errprintf("connect returned ENOTSOCK!\n"); break; case EADDRNOTAVAIL: errprintf("connect returned EADDRNOTAVAIL!\n"); break; case EAFNOSUPPORT : errprintf("connect returned EAFNOSUPPORT!\n"); break; case EISCONN : errprintf("connect returned EISCONN!\n"); break; case EADDRINUSE : errprintf("connect returned EADDRINUSE!\n"); break; case EFAULT : errprintf("connect returned EFAULT!\n"); break; case EALREADY : errprintf("connect returned EALREADY!\n"); break; default : errprintf("connect returned %d, errno=%d\n", res, errno); } } else { /* Should NEVER happen. connect returns 0 or -1 */ errprintf("Strange result from connect: %d, errno=%d\n", res, errno); } } else { /* Could net set to non-blocking mode! Hmmm ... */ sockok = 0; errprintf("Cannot set O_NONBLOCK\n"); } nextinqueue=nextinqueue->next; } else { int newconcurrency = ((activesockets > 5) ? (activesockets-1) : 5); /* Could not get a socket */ switch (errno) { case EPROTONOSUPPORT: errprintf("Cannot get socket - EPROTONOSUPPORT\n"); break; case EAFNOSUPPORT : errprintf("Cannot get socket - EAFNOSUPPORT\n"); break; case EMFILE : errprintf("Cannot get socket - EMFILE\n"); break; case ENFILE : errprintf("Cannot get socket - ENFILE\n"); break; case EACCES : errprintf("Cannot get socket - EACCESS\n"); break; case ENOBUFS : errprintf("Cannot get socket - ENOBUFS\n"); break; case ENOMEM : errprintf("Cannot get socket - ENOMEM\n"); break; case EINVAL : errprintf("Cannot get socket - EINVAL\n"); break; default : errprintf("Cannot get socket - errno=%d\n", errno); break; } if (newconcurrency != concurrency) { errprintf("Reducing --concurrency setting from %d to %d\n", concurrency, newconcurrency); concurrency = newconcurrency; } } } /* Ready to go - we have a bunch of connections being established */ dbgprintf("%d tests pending - %d active tests, %d slow tests\n", pending, activesockets, slowrunning); restartselect: /* * Setup the FDSET's */ FD_ZERO(&readfds); FD_ZERO(&writefds); maxfd = -1; for (item=firstactive; (item != nextinqueue); item=item->next) { if (item->fd > -1) { /* * WRITE events are used to signal that a * connection is ready, or it has been refused. * READ events are only interesting for sockets * that have already been found to be open, and * thus have the "readpending" flag set. * * So: On any given socket, we want either a * write-event or a read-event - never both. */ if (item->readpending) FD_SET(item->fd, &readfds); else FD_SET(item->fd, &writefds); if (item->fd > maxfd) maxfd = item->fd; } } if (maxfd == -1) { /* No active connections */ if (activesockets == 0) { /* This can happen, if we get an immediate CONNREFUSED on all connections. */ continue; } else { errprintf("contest logic error: No FD's, active=%d, pending=%d\n", activesockets, pending); continue; } } /* * Wait for something to happen: connect, timeout, banner arrives ... */ if (maxfd < 0) { errprintf("select - no active fd's found, but pending is %d\n", pending); selres = 0; } else { struct timeval tmo = { 1, 0 }; dbgprintf("Doing select with maxfd=%d\n", maxfd); selres = select((maxfd+1), &readfds, &writefds, NULL, &tmo); dbgprintf("select returned %d\n", selres); } if (selres == -1) { int selerr = errno; /* * select() failed - this is BAD! */ switch (selerr) { case EINTR : errprintf("select failed - EINTR\n"); goto restartselect; case EBADF : errprintf("select failed - EBADF\n"); break; case EINVAL: errprintf("select failed - EINVAL\n"); break; case ENOMEM: errprintf("select failed - ENOMEM\n"); break; default : errprintf("Unknown select() error %d\n", selerr); break; } /* Leave this mess ... */ errprintf("Aborting TCP tests with %d tests pending\n", pending); return; } /* selres == 0 (timeout) isn't special - just go through the list of active tests */ /* Fetch the timestamp so we can tell how long the connect took */ getntimer(×tamp); /* Now find out which connections had something happen to them */ for (item=firstactive; (item != nextinqueue); item=item->next) { if (item->fd > -1) { /* Only active sockets have this */ if (timestamp.tv_sec > item->cutoff) { /* * Request timed out. */ if (item->readpending) { /* Final read timeout - just shut this socket */ socket_shutdown(item); item->errcode = CONTEST_ETIMEOUT; } else { /* Connection timeout */ item->open = 0; item->errcode = CONTEST_ETIMEOUT; } get_totaltime(item, ×tamp); close(item->fd); item->fd = -1; activesockets--; pending--; if (item == firstactive) firstactive = item->next; } else { if (FD_ISSET(item->fd, &writefds)) { int do_talk = 1; unsigned char *outbuf = NULL; unsigned int outlen = 0; item->lastactive = timestamp.tv_sec; if (!item->open) { /* * First time here. * * Active response on this socket - either OK, or * connection refused. * We determine what happened by getting the SO_ERROR status. * (cf. select_tut(2) manpage). */ connressize = sizeof(item->connres); res = getsockopt(item->fd, SOL_SOCKET, SO_ERROR, &item->connres, &connressize); item->open = (item->connres == 0); if (!item->open) item->errcode = CONTEST_ENOCONN; do_talk = item->open; get_connectiontime(item, ×tamp); } if (item->open && (item->svcinfo->flags & TCP_SSL)) { /* * Setup the SSL connection, if not done already. * * NB: This can be triggered many times, as setup_ssl() * may need more data from the remote and return with * item->sslrunning == SSLSETUP_PENDING */ if (item->sslrunning == SSLSETUP_PENDING) { setup_ssl(item); if (item->sslrunning == 1) { /* * Update connectiontime to include * time for SSL handshake. */ get_connectiontime(item, ×tamp); } } do_talk = (item->sslrunning == 1); } /* * Connection succeeded - port is open, if SSL then the * SSL handshake is complete. * * If we have anything to send then send it. * If we want the banner, set the "readpending" flag to initiate * select() for read()'s. * NB: We want the banner EITHER if the GET_BANNER flag is set, * OR if we need it to match the expect string in the servicedef. */ item->readpending = (do_talk && !item->silenttest && ( (item->svcinfo->flags & TCP_GET_BANNER) || item->svcinfo->exptext )); if (do_talk) { if (item->telnetnegotiate && item->telnetbuflen) { /* * Return the telnet negotiate data response */ outbuf = item->telnetbuf; outlen = item->telnetbuflen; } else if (item->sendtxt && !item->silenttest) { outbuf = item->sendtxt; outlen = (item->sendlen ? item->sendlen : strlen(outbuf)); } if (outbuf && outlen) { /* * It may be that we cannot write all of the * data we want to. Tough ... */ res = socket_write(item, outbuf, outlen); tcp_stats_written += res; if (res == -1) { /* Write failed - this socket is done. */ dbgprintf("write failed\n"); item->readpending = 0; item->errcode = CONTEST_EIO; } else if (item->svcinfo->flags & TCP_HTTP) { /* * HTTP tests require us to send the full buffer. * So adjust sendtxt/sendlen accordingly. * If no more to send, switch to read-mode. */ item->sendtxt += res; item->sendlen -= res; item->readpending = (item->sendlen == 0); } } } /* If closed and/or no bannergrabbing, shut down socket */ if (item->sslrunning != SSLSETUP_PENDING) { if (!item->open || !item->readpending) { if (item->open) { socket_shutdown(item); } close(item->fd); get_totaltime(item, ×tamp); if (item->finalcallback) item->finalcallback(item->priv); item->fd = -1; activesockets--; pending--; if (item == firstactive) firstactive = item->next; } } } else if (FD_ISSET(item->fd, &readfds)) { /* * Data ready to read on this socket. Grab the * banner - we only do one read (need the socket * for other tests), so if the banner takes more * than one cycle to arrive, too bad! */ int wantmoredata = 0; int datadone = 0; item->lastactive = timestamp.tv_sec; /* * We may be in the process of setting up an SSL connection */ if (item->sslrunning == SSLSETUP_PENDING) setup_ssl(item); if (item->sslrunning == SSLSETUP_PENDING) break; /* Loop again waiting for more data */ /* * Connection is ready - plain or SSL. Read data. */ res = socket_read(item, msgbuf, sizeof(msgbuf)-1); tcp_stats_read += res; dbgprintf("read %d bytes from socket\n", res); if ((res > 0) && item->datacallback) { datadone = item->datacallback(msgbuf, res, item->priv); } if ((res > 0) && item->telnetnegotiate) { /* * telnet data has telnet options first. * We must negotiate the session before we * get the banner. */ item->telnetbuf = item->banner; item->telnetbuflen = res; /* * Safety measure: Dont loop forever doing * telnet options. * This puts a maximum on how many times * we go here. */ item->telnetnegotiate--; if (!item->telnetnegotiate) { dbgprintf("Max. telnet negotiation (%d) reached for host %s\n", MAX_TELNET_CYCLES, inet_ntoa(item->addr.sin_addr)); } if (do_telnet_options(item)) { /* Still havent seen the session banner */ item->banner = NULL; item->bannerbytes = 0; item->readpending = 0; wantmoredata = 1; } else { /* No more options - we have the banner */ item->telnetnegotiate = 0; } } if ((item->svcinfo->flags & TCP_HTTP) && ((res > 0) || item->sslagain) && (!datadone) ) { /* * HTTP : Grab the entire response. */ wantmoredata = 1; } if (!wantmoredata) { if (item->open) { socket_shutdown(item); } item->readpending = 0; close(item->fd); get_totaltime(item, ×tamp); if (item->finalcallback) item->finalcallback(item->priv); item->fd = -1; activesockets--; pending--; if (item == firstactive) firstactive = item->next; } } } } } /* end for loop */ } /* end while (pending) */ dbgprintf("TCP tests completed normally\n"); } void show_tcp_test_results(void) { tcptest_t *item; for (item = thead; (item); item = item->next) { printf("Address=%s:%d, open=%d, res=%d, err=%d, connecttime=%u.%06u, totaltime=%u.%06u, ", inet_ntoa(item->addr.sin_addr), ntohs(item->addr.sin_port), item->open, item->connres, item->errcode, (unsigned int)item->duration.tv_sec, (unsigned int)(item->duration.tv_nsec/1000), (unsigned int)item->totaltime.tv_sec, (unsigned int)(item->totaltime.tv_nsec/1000)); if (item->banner && (item->bannerbytes == strlen(item->banner))) { printf("banner='%s' (%d bytes)", textornull(item->banner), item->bannerbytes); } else { int i; unsigned char *p; for (i=0, p=item->banner; i < item->bannerbytes; i++, p++) { printf("%c", (isprint(*p) ? *p : '.')); } } if (item->certinfo) { printf(", certinfo='%s' (%u %s)", item->certinfo, (unsigned int)item->certexpires, ((item->certexpires > getcurrenttime(NULL)) ? "valid" : "expired")); } printf("\n"); if ((item->svcinfo == &svcinfo_http) || (item->svcinfo == &svcinfo_https)) { http_data_t *httptest = (http_data_t *) item->priv; printf("httpstatus = %ld, open=%d, errcode=%d, parsestatus=%d\n", httptest->httpstatus, httptest->tcptest->open, httptest->tcptest->errcode, httptest->parsestatus); printf("Response:\n"); if (httptest->headers) printf("%s\n", httptest->headers); else printf("(no headers)\n"); if (httptest->contentcheck == CONTENTCHECK_DIGEST) printf("Content digest: %s\n", httptest->digest); if (httptest->output) printf("%s", httptest->output); } } } int tcp_got_expected(tcptest_t *test) { if (test == NULL) return 1; if (test->svcinfo && test->svcinfo->exptext) { int compbytes; /* Number of bytes to compare */ /* Did we get enough data? */ if (test->banner == NULL) { dbgprintf("tcp_got_expected: No data in banner\n"); return 0; } compbytes = (test->svcinfo->explen ? test->svcinfo->explen : strlen(test->svcinfo->exptext)); if ((test->svcinfo->expofs + compbytes) > test->bannerbytes) { dbgprintf("tcp_got_expected: Not enough data\n"); return 0; } return (memcmp(test->svcinfo->exptext+test->svcinfo->expofs, test->banner, compbytes) == 0); } else return 1; } #ifdef STANDALONE int main(int argc, char *argv[]) { int argi; char *argp, *p; testitem_t *thead = NULL; int timeout = 0; int concurrency = 0; if (xgetenv("XYMONNETSVCS") == NULL) putenv("XYMONNETSVCS="); init_tcp_services(); for (argi=1; (argihost = hostitem; testitem->testspec = testspec; strcpy(hostitem->ip, ip); add_url_to_dns_queue(testspec); add_http_test(testitem); testitem->next = NULL; thead = testitem; httptest = (http_data_t *)testitem->privdata; if (httptest && httptest->tcptest) { printf("TCP connection goes to %s:%d\n", inet_ntoa(httptest->tcptest->addr.sin_addr), ntohs(httptest->tcptest->addr.sin_port)); printf("Request:\n%s\n", httptest->tcptest->sendtxt); } } else if (strncmp(argp, "dns=", 4) == 0) { strbuffer_t *banner = newstrbuffer(0); int result; result = dns_test_server(ip, argp+4, banner); printf("DNS test result=%d\nBanner:%s\n", result, STRBUF(banner)); } else { add_tcp_test(ip, atoi(port), testspec, NULL, srcip, NULL, 0, NULL, NULL, NULL, NULL); } } else { printf("Invalid testspec '%s'\n", argv[argi]); } } } do_tcp_tests(timeout, concurrency); show_tcp_test_results(); return 0; } #endif xymon-4.3.7/xymonnet/xymonnet-again.sh.10000664000175000017500000000243711671641417017553 0ustar henrikhenrik.TH XYMONNET-AGAIN.SH 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymonnet-again.sh \- Xymon network re-test tool .SH SYNOPSIS .B "xymonnet-again.sh" .SH DESCRIPTION \fBxymonnet-again.sh\fR is an extension script for Xymon that runs on the network test server. It picks up the failing network tests executed by the .I xymonnet(1) program, and repeats these tests with a faster test cycle than the normal xymonnet schedule. This means that when the server recovers and the network service becomes available again, this is detected quicker resulting in less reported downtime. Only tests whose first failure occurred within 30 minutes are included in the tests that are run by xymonnet-again.sh. The 30 minute limit is there to avoid hosts that are down for longer periods of time to bog down xymonnet-again.sh. You can change this limit with the "--frequenttestlimit=SECONDS" when you run xyxmonnet. .SH INSTALLATION This script runs by default from your .I tasks.cfg(5) file. .SH FILES .IP $XYMONTMP/TESTNAME.LOCATION.status Temporary status file managed by xyxmonnet with status of tests that have currently failed. .IP $XYMONTMP/frequenttests.LOCATION Temporary file managed by xymonnet with the hostnames that xymonnet-again.sh should test. .SH "SEE ALSO" xymonnet(1), xymon(7), tasks.cfg(5) xymon-4.3.7/xymonnet/xymonping.10000664000175000017500000000710211671641417016226 0ustar henrikhenrik.TH XYMONPING 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymonping \- Xymon ping tool .SH SYNOPSIS .B "xymonping [--retries=N] [--timeout=N] [IP-adresses]" .SH DESCRIPTION .I xymonping(1) is used for ping testing of the hosts monitored by the .I xymon(7) monitoring system. It reads a list of IP adresses from stdin, and performs a "ping" check to see if these hosts are alive. It is normally invoked by the .I xymonnet(1) utility, which performs all of the Xymon network tests. Optionally, if a list of IP-adresses is passed as command-line arguments, it will ping those IP's instead of reading them from stdin. xymonping only handles IP-adresses, not hostnames. xymonping was inspired by the .I fping(1) tool, but has been written from scratch to implement a fast ping tester without much of the overhead found in other such utilities. The output from xymonping is similar to that of "fping -Ae". xymonping probes multiple systems in parallel, and the runtime is therefore mostly dependant on the timeout-setting and the number of retries. With the default options, xymonping takes approximately 18 seconds to ping all hosts (tested with an input set of 1500 IP adresses). .SH SUID-ROOT INSTALLATION REQUIRED xymonping needs to be installed with suid-root privileges, since it requires a "raw socket" to send and receive ICMP Echo (ping) packets. xymonping is implemented such that it immediately drops the root privileges, and only regains them to perform two operations: Obtaining the raw socket, and optionally binding it to a specific source address. These operations are performed as root, the rest of the time xymonping runs with normal user privileges. Specifically, no user-supplied data or network data is used while running with root privileges. Therefore it should be safe to provide xymonping with the necessary suid-root privileges. .SH OPTIONS .IP --retries=N Sets the number of retries for hosts that fail to respond to the initial ping, i.e. the number of ping probes sent in addition to the initial probe. The default is --retries=2, to ping a host 3 times before concluding that it is not responding. .IP --timeout=N Determines the timeout (in seconds) for ping probes. If a host does not respond within N seconds, it is regarded as unavailable, unless it responds to one of the retries. The default is --timeout=5. .IP --responses=N xymonping normally stops pinging a host after receiving a single response, and uses that to determine the round-trip time. If the first response takes longer to arrive - e.g. because of additional network overhead when first determining the route to the target host - it may skew the round-trip-time reports. You can then use this option to require N responses, and xymonping will calculate the round-trip time as the average of all of responsetimes. .IP --max-pps=N Maximum number of packets per second. This limits the number of ICMP packets xymonping will send per second, by enforcing a brief delay after each packet is sent. The default setting is to send a maximum of 50 packets per second. Note that increasing this may cause flooding of the network, and since ICMP packets can be discarded by routers and other network equipment, this can cause erratic behaviour with hosts recorded as not responding when they are in fact OK. .IP --source=ADDRESS Use ADDRESS as the source IP address of the ping packets sent. On multi-homed systems, allows you to select the source IP of the hosts going out, which might be necessary for ping to work. .IP --debug Enable debug output. This prints out all packets sent and received. .SH "SEE ALSO" xymon(7), xymonnet(1), fping(1) xymon-4.3.7/xymonnet/httpresult.h0000664000175000017500000000250311615341300016465 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* This is used to implement the testing of a HTTP service. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __HTTPRESULT_H_ #define __HTTPRESULT_H_ #include #include #include extern void show_http_test_results(service_t *httptest); extern void send_http_results(service_t *httptest, testedhost_t *host, testitem_t *firsttest, char *nonetpage, int failgoesclear); extern void send_content_results(service_t *httptest, testedhost_t *host, char *nonetpage, char *contenttestname, int failgoesclear); #endif xymon-4.3.7/xymonnet/dns2.h0000664000175000017500000000200011615341300015105 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __DNS2_H__ #define __DNS2_H__ typedef struct dns_resp_t { int msgstatus; strbuffer_t *msgbuf; struct dns_resp_t *next; } dns_resp_t; extern void dns_detail_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen); extern int dns_name_type(char *name); #endif xymon-4.3.7/xymonnet/xymonnet-again.sh.DIST0000664000175000017500000000050011535462534020143 0ustar henrikhenrik#!/bin/sh # This extension script picks up the $XYMONTMP/frequenttests.$XYMONNETWORK # file generated by xyxmonnet. It then re-does the network tests # with the same parameters. REDOFILE=$XYMONTMP/frequenttests.$XYMONNETWORK if test -r $REDOFILE then @RUNTIMEDEFS@ $XYMONHOME/bin/xymonnet `cat $REDOFILE` fi exit 0 xymon-4.3.7/xymonnet/xymonnet.c0000664000175000017500000022403411632345122016135 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymonnet.c 6752 2011-09-09 08:12:02Z storner $"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #ifdef HAVE_RPCENT_H #include #endif #ifdef BROKEN_HPUX_NETDB /* * Some HP-UX include files fail to define RPC functions * and structs that are purely standard. At the same time, * their own docs claim that the DO define them. Go figure ... */ struct rpcent { char *r_name; /* name of server for this rpc program */ char **r_aliases; /* alias list */ int r_number; /* rpc program number */ }; extern struct rpcent *getrpcbyname(char *); #endif #include "libxymon.h" #include "version.h" #include "xymonnet.h" #include "dns.h" #include "contest.h" #include "httptest.h" #include "httpresult.h" #include "httpcookies.h" #include "ldaptest.h" #define DEFAULT_PING_CHILD_COUNT 1 char *reqenv[] = { "NONETPAGE", "HOSTSCFG", "XYMONTMP", "XYMONHOME", NULL }; void * svctree; /* All known services, has service_t records */ service_t *pingtest = NULL; /* Identifies the pingtest within svctree list */ int pingcount = 0; service_t *dnstest = NULL; /* Identifies the dnstest within svctree list */ service_t *httptest = NULL; /* Identifies the httptest within svctree list */ service_t *ldaptest = NULL; /* Identifies the ldaptest within svctree list */ service_t *rpctest = NULL; /* Identifies the rpctest within svctree list */ void * testhosttree; /* All tested hosts, has testedhost_t records */ char *nonetpage = NULL; /* The "NONETPAGE" env. variable */ int dnsmethod = DNS_THEN_IP; /* How to do DNS lookups */ int timeout=10; /* The timeout (seconds) for all TCP-tests */ char *contenttestname = "content"; /* Name of the content checks column */ char *ssltestname = "sslcert"; /* Name of the SSL certificate checks column */ char *failtext = "not OK"; int sslwarndays = 30; /* If cert expires in fewer days, SSL cert column = yellow */ int sslalarmdays = 10; /* If cert expires in fewer days, SSL cert column = red */ int mincipherbits = 0; /* If weakest cipher is weaker than this # of buts, SSL cert column = red */ int validity = 30; int pingchildcount = DEFAULT_PING_CHILD_COUNT; /* How many ping processes to start */ char *location = ""; /* XYMONNETWORK value */ int hostcount = 0; int testcount = 0; int notesthostcount = 0; char **selectedhosts; int selectedcount = 0; int testuntagged = 0; time_t frequenttestlimit = 1800; /* Interval (seconds) when failing hosts are retried frequently */ int checktcpresponse = 0; int dotraceroute = 0; int fqdn = 1; int dosendflags = 1; char *pingcmd = NULL; char pinglog[PATH_MAX]; char pingerrlog[PATH_MAX]; pid_t *pingpids; int respcheck_color = COL_YELLOW; int extcmdtimeout = 30; int bigfailure = 0; char *defaultsourceip = NULL; int loadhostsfromxymond = 0; void dump_hostlist(void) { xtreePos_t handle; testedhost_t *walk; for (handle = xtreeFirst(testhosttree); (handle != xtreeEnd(testhosttree)); handle = xtreeNext(testhosttree, handle)) { walk = (testedhost_t *)xtreeData(testhosttree, handle); printf("Hostname: %s\n", textornull(walk->hostname)); printf("\tIP : %s\n", textornull(walk->ip)); printf("\tHosttype : %s\n", textornull(walk->hosttype)); printf("\tFlags :"); if (walk->testip) printf(" testip"); if (walk->dialup) printf(" dialup"); if (walk->nosslcert) printf(" nosslcert"); if (walk->dodns) printf(" dodns"); if (walk->dnserror) printf(" dnserror"); if (walk->repeattest) printf(" repeattest"); if (walk->noconn) printf(" noconn"); if (walk->noping) printf(" noping"); if (walk->dotrace) printf(" dotrace"); printf("\n"); printf("\tbadconn : %d:%d:%d\n", walk->badconn[0], walk->badconn[1], walk->badconn[2]); printf("\tdowncount : %d started %s", walk->downcount, ctime(&walk->downstart)); printf("\trouterdeps : %s\n", textornull(walk->routerdeps)); printf("\tdeprouterdown: %s\n", (walk->deprouterdown ? textornull(walk->deprouterdown->hostname) : "")); printf("\tldapauth : '%s' '%s'\n", textornull(walk->ldapuser), textornull(walk->ldappasswd)); printf("\tSSL alerts : %d:%d\n", walk->sslwarndays, walk->sslalarmdays); printf("\n"); } } void dump_testitems(void) { xtreePos_t handle; service_t *swalk; testitem_t *iwalk; for (handle = xtreeFirst(svctree); handle != xtreeEnd(svctree); handle = xtreeNext(svctree, handle)) { swalk = (service_t *)xtreeData(svctree, handle); printf("Service %s, port %d, toolid %d\n", swalk->testname, swalk->portnum, swalk->toolid); for (iwalk = swalk->items; (iwalk); iwalk = iwalk->next) { printf("\tHost : %s\n", textornull(iwalk->host->hostname)); printf("\ttestspec : %s\n", textornull(iwalk->testspec)); printf("\tFlags :"); if (iwalk->dialup) printf(" dialup"); if (iwalk->reverse) printf(" reverse"); if (iwalk->silenttest) printf(" silenttest"); if (iwalk->alwaystrue) printf(" alwaystrue"); printf("\n"); printf("\tOpen : %d\n", iwalk->open); printf("\tBanner : %s\n", textornull(STRBUF(iwalk->banner))); printf("\tcertinfo : %s\n", textornull(iwalk->certinfo)); printf("\tDuration : %ld.%06ld\n", (long int)iwalk->duration.tv_sec, (long int)iwalk->duration.tv_nsec / 1000); printf("\tbadtest : %d:%d:%d\n", iwalk->badtest[0], iwalk->badtest[1], iwalk->badtest[2]); printf("\tdowncount : %d started %s", iwalk->downcount, ctime(&iwalk->downstart)); printf("\n"); } printf("\n"); } } testitem_t *find_test(char *hostname, char *testname) { xtreePos_t handle; testedhost_t *h; service_t *s; testitem_t *t; handle = xtreeFind(svctree, testname); if (handle == xtreeEnd(svctree)) return NULL; s = (service_t *)xtreeData(svctree, handle); handle = xtreeFind(testhosttree, hostname); if (handle == xtreeEnd(testhosttree)) return NULL; h = (testedhost_t *)xtreeData(testhosttree, handle); for (t=s->items; (t && (t->host != h)); t = t->next) ; return t; } char *deptest_failed(testedhost_t *host, char *testname) { static char result[1024]; char *depcopy; char depitem[MAX_LINE_LEN]; char *p, *q; char *dephostname, *deptestname, *nextdep; testitem_t *t; if (host->deptests == NULL) return NULL; depcopy = strdup(host->deptests); sprintf(depitem, "(%s:", testname); p = strstr(depcopy, depitem); if (p == NULL) { xfree(depcopy); return NULL; } result[0] = '\0'; dephostname = p+strlen(depitem); q = strchr(dephostname, ')'); if (q) *q = '\0'; /* dephostname now points to a list of "host1/test1,host2/test2" dependent tests. */ while (dephostname) { p = strchr(dephostname, '/'); if (p) { *p = '\0'; deptestname = (p+1); } else deptestname = ""; p = strchr(deptestname, ','); if (p) { *p = '\0'; nextdep = (p+1); } else nextdep = NULL; t = find_test(dephostname, deptestname); if (t && !t->open) { if (strlen(result) == 0) { strcpy(result, "\nThis test depends on the following test(s) that failed:\n\n"); } if ((strlen(result) + strlen(dephostname) + strlen(deptestname) + 2) < sizeof(result)) { strcat(result, dephostname); strcat(result, "/"); strcat(result, deptestname); strcat(result, "\n"); } } dephostname = nextdep; } xfree(depcopy); if (strlen(result)) strcat(result, "\n\n"); return (strlen(result) ? result : NULL); } service_t *add_service(char *name, int port, int namelen, int toolid) { xtreePos_t handle; service_t *svc; /* Avoid duplicates */ handle = xtreeFind(svctree, name); if (handle != xtreeEnd(svctree)) { svc = (service_t *)xtreeData(svctree, handle); return svc; } svc = (service_t *) malloc(sizeof(service_t)); svc->portnum = port; svc->testname = strdup(name); svc->toolid = toolid; svc->namelen = namelen; svc->items = NULL; xtreeAdd(svctree, svc->testname, svc); return svc; } int getportnumber(char *svcname) { struct servent *svcinfo; int result = 0; result = default_tcp_port(svcname); if (result == 0) { svcinfo = getservbyname(svcname, NULL); if (svcinfo) result = ntohs(svcinfo->s_port); } return result; } void load_services(void) { char *netsvcs; char *p; netsvcs = strdup(init_tcp_services()); p = strtok(netsvcs, " "); while (p) { add_service(p, getportnumber(p), 0, TOOL_CONTEST); p = strtok(NULL, " "); } xfree(netsvcs); /* Save NONETPAGE env. var in ",test1,test2," format for easy and safe grepping */ nonetpage = (char *) malloc(strlen(xgetenv("NONETPAGE"))+3); sprintf(nonetpage, ",%s,", xgetenv("NONETPAGE")); for (p=nonetpage; (*p); p++) if (*p == ' ') *p = ','; } testedhost_t *init_testedhost(char *hostname) { testedhost_t *newhost; hostcount++; newhost = (testedhost_t *) calloc(1, sizeof(testedhost_t)); newhost->hostname = strdup(hostname); newhost->dotrace = dotraceroute; newhost->sslwarndays = sslwarndays; newhost->sslalarmdays = sslalarmdays; newhost->mincipherbits = mincipherbits; return newhost; } testitem_t *init_testitem(testedhost_t *host, service_t *service, char *srcip, char *testspec, int dialuptest, int reversetest, int alwaystruetest, int silenttest, int sendasdata) { testitem_t *newtest; testcount++; newtest = (testitem_t *) calloc(1, sizeof(testitem_t)); newtest->host = host; newtest->service = service; newtest->dialup = dialuptest; newtest->reverse = reversetest; newtest->alwaystrue = alwaystruetest; newtest->silenttest = silenttest; newtest->senddata = sendasdata; newtest->testspec = (testspec ? strdup(testspec) : NULL); if (srcip) newtest->srcip = strdup(srcip); else if (defaultsourceip) newtest->srcip = defaultsourceip; else newtest->srcip = NULL; newtest->privdata = NULL; newtest->open = 0; newtest->banner = newstrbuffer(0); newtest->certinfo = NULL; newtest->certexpires = 0; newtest->mincipherbits = 0; newtest->duration.tv_sec = newtest->duration.tv_nsec = -1; newtest->downcount = 0; newtest->badtest[0] = newtest->badtest[1] = newtest->badtest[2] = 0; newtest->internal = 0; newtest->next = NULL; return newtest; } int wanted_host(void *host, char *netstring) { char *netlocation = xmh_item(host, XMH_NET); if (selectedcount == 0) return ((strlen(netstring) == 0) || /* No XYMONNETWORK = do all */ (netlocation && (strcmp(netlocation, netstring) == 0)) || /* XYMONNETWORK && matching NET: tag */ (testuntagged && (netlocation == NULL))); /* No NET: tag for this host */ else { /* User provided an explicit list of hosts to test */ int i; for (i=0; (i < selectedcount); i++) { if (strcmp(selectedhosts[i], xmh_item(host, XMH_HOSTNAME)) == 0) return 1; } } return 0; } void load_tests(void) { char *p, *routestring = NULL; void *hwalk; testedhost_t *h; int badtagsused = 0; if (loadhostsfromxymond) { if (load_hostnames("@", NULL, fqdn) != 0) { errprintf("Cannot load host configuration from xymond\n"); return; } } else { if (load_hostnames(xgetenv("HOSTSCFG"), "netinclude", fqdn) != 0) { errprintf("Cannot load host configuration from %s\n", xgetenv("HOSTSCFG")); return; } } if (first_host() == NULL) { errprintf("Empty configuration from %s\n", (loadhostsfromxymond ? "xymond" : xgetenv("HOSTSCFG"))); return; } /* Each network test tagged with NET:locationname */ if (strlen(location) > 0) { routestring = (char *) malloc(strlen(location)+strlen("route_:")+1); sprintf(routestring, "route_%s:", location); } for (hwalk = first_host(); (hwalk); hwalk = next_host(hwalk, 0)) { int anytests = 0; int ping_dialuptest = 0, ping_reversetest = 0; char *testspec; if (!wanted_host(hwalk, location)) continue; h = init_testedhost(xmh_item(hwalk, XMH_HOSTNAME)); p = xmh_custom_item(hwalk, "badconn:"); if (p) { sscanf(p+strlen("badconn:"), "%d:%d:%d", &h->badconn[0], &h->badconn[1], &h->badconn[2]); badtagsused = 1; } p = xmh_custom_item(hwalk, "route:"); if (p) h->routerdeps = p + strlen("route:"); if (routestring) { p = xmh_custom_item(hwalk, routestring); if (p) h->routerdeps = p + strlen(routestring); } if (xmh_item(hwalk, XMH_FLAG_NOCONN)) h->noconn = 1; if (xmh_item(hwalk, XMH_FLAG_NOPING)) h->noping = 1; if (xmh_item(hwalk, XMH_FLAG_TRACE)) h->dotrace = 1; if (xmh_item(hwalk, XMH_FLAG_NOTRACE)) h->dotrace = 0; if (xmh_item(hwalk, XMH_FLAG_TESTIP)) h->testip = 1; if (xmh_item(hwalk, XMH_FLAG_DIALUP)) h->dialup = 1; if (xmh_item(hwalk, XMH_FLAG_NOSSLCERT)) h->nosslcert = 1; if (xmh_item(hwalk, XMH_FLAG_LDAPFAILYELLOW)) h->ldapsearchfailyellow = 1; if (xmh_item(hwalk, XMH_FLAG_HIDEHTTP)) h->hidehttp = 1; p = xmh_item(hwalk, XMH_SSLDAYS); if (p) sscanf(p, "%d:%d", &h->sslwarndays, &h->sslalarmdays); p = xmh_item(hwalk, XMH_SSLMINBITS); if (p) h->mincipherbits = atoi(p); p = xmh_item(hwalk, XMH_DEPENDS); if (p) h->deptests = p; p = xmh_item(hwalk, XMH_LDAPLOGIN); if (p) { h->ldapuser = strdup(p); h->ldappasswd = (strchr(h->ldapuser, ':')); if (h->ldappasswd) { *h->ldappasswd = '\0'; h->ldappasswd++; } } p = xmh_item(hwalk, XMH_DESCRIPTION); if (p) { h->hosttype = strdup(p); p = strchr(h->hosttype, ':'); if (p) *p = '\0'; } testspec = xmh_item_walk(hwalk); while (testspec) { service_t *s = NULL; int dialuptest = 0, reversetest = 0, silenttest = 0, sendasdata = 0; char *srcip = NULL; int alwaystruetest = (xmh_item(hwalk, XMH_FLAG_NOCLEAR) != NULL); if (xmh_item_idx(testspec) == -1) { /* Test prefixes: * - '?' denotes dialup test, i.e. report failures as clear. * - '|' denotes reverse test, i.e. service should be DOWN. * - '~' denotes test that ignores ping result (normally, * TCP tests are reported CLEAR if ping check fails; * with this flag report their true status) */ if (*testspec == '?') { dialuptest=1; testspec++; } if (*testspec == '!') { reversetest=1; testspec++; } if (*testspec == '~') { alwaystruetest=1; testspec++; } if (pingtest && argnmatch(testspec, pingtest->testname)) { char *p; /* * Ping/conn test. Save any modifier flags for later use. */ ping_dialuptest = dialuptest; ping_reversetest = reversetest; p = strchr(testspec, '='); if (p) { char *ips; /* Extra ping tests - save them for later */ h->extrapings = (extraping_t *)malloc(sizeof(extraping_t)); h->extrapings->iplist = NULL; if (argnmatch(p, "=worst,")) { h->extrapings->matchtype = MULTIPING_WORST; ips = strdup(p+7); } else if (argnmatch(p, "=best,")) { h->extrapings->matchtype = MULTIPING_BEST; ips = strdup(p+6); } else { h->extrapings->matchtype = MULTIPING_BEST; ips = strdup(p+1); } do { ipping_t *newping = (ipping_t *)malloc(sizeof(ipping_t)); newping->ip = ips; newping->open = 0; newping->banner = newstrbuffer(0); newping->next = h->extrapings->iplist; h->extrapings->iplist = newping; ips = strchr(ips, ','); if (ips) { *ips = '\0'; ips++; } } while (ips && (*ips)); } s = NULL; /* Dont add the test now - ping is special (enabled by default) */ } else if ((argnmatch(testspec, "ldap://")) || (argnmatch(testspec, "ldaps://"))) { /* * LDAP test. This uses ':' a lot, so save it here. */ #ifdef XYMON_LDAP s = ldaptest; add_url_to_dns_queue(testspec); #else errprintf("Host %s: ldap test requested, but xymonnet was built with no ldap support\n", xmh_item(hwalk, XMH_HOSTNAME)); #endif } else if ((strcmp(testspec, "http") == 0) || (strcmp(testspec, "https") == 0)) { errprintf("Host %s: http/https tests requires a full URL\n", xmh_item(hwalk, XMH_HOSTNAME)); } else if ( argnmatch(testspec, "http") || argnmatch(testspec, "content=http") || argnmatch(testspec, "cont;http") || argnmatch(testspec, "cont=") || argnmatch(testspec, "nocont;http") || argnmatch(testspec, "nocont=") || argnmatch(testspec, "post;http") || argnmatch(testspec, "post=") || argnmatch(testspec, "nopost;http") || argnmatch(testspec, "nopost=") || argnmatch(testspec, "soap;http") || argnmatch(testspec, "soap=") || argnmatch(testspec, "nosoap;http") || argnmatch(testspec, "nosoap=") || argnmatch(testspec, "type;http") || argnmatch(testspec, "type=") ) { /* HTTP test. */ weburl_t url; decode_url(testspec, &url); if (url.desturl->parseerror || (url.proxyurl && url.proxyurl->parseerror)) { s = NULL; errprintf("Host %s: Invalid URL for http test - ignored: %s\n", xmh_item(hwalk, XMH_HOSTNAME), testspec); } else { s = httptest; if (!url.desturl->ip) add_url_to_dns_queue(testspec); } } else if (argnmatch(testspec, "apache") || argnmatch(testspec, "apache=")) { char *userfmt = "cont=apache;%s;."; char *deffmt = "cont=apache;http://%s/server-status?auto;."; static char *statusurl = NULL; char *userurl; if (statusurl != NULL) xfree(statusurl); userurl = strchr(testspec, '='); if (userurl) { weburl_t url; userurl++; decode_url(userurl, &url); if (url.desturl->parseerror || (url.proxyurl && url.proxyurl->parseerror)) { s = NULL; errprintf("Host %s: Invalid URL for apache test - ignored: %s\n", xmh_item(hwalk, XMH_HOSTNAME), testspec); } else { statusurl = (char *)malloc(strlen(userurl) + strlen(userfmt) + 1); sprintf(statusurl, userfmt, userurl); s = httptest; } } else { char *ip = xmh_item(hwalk, XMH_IP); statusurl = (char *)malloc(strlen(deffmt) + strlen(ip) + 1); sprintf(statusurl, deffmt, ip); s = httptest; } if (s) { testspec = statusurl; add_url_to_dns_queue(testspec); sendasdata = 1; } } else if (argnmatch(testspec, "rpc")) { /* * rpc check via rpcinfo */ s = rpctest; } else if (argnmatch(testspec, "dns=")) { s = dnstest; } else if (argnmatch(testspec, "dig=")) { s = dnstest; } else { /* * Simple TCP connect test. */ char *option; xtreePos_t handle; /* See if there's a source IP */ srcip = strchr(testspec, '@'); if (srcip) { *srcip = '\0'; srcip++; } /* Remove any trailing ":s", ":q", ":Q", ":portnumber" */ option = strchr(testspec, ':'); if (option) { *option = '\0'; option++; } /* Find the service */ handle = xtreeFind(svctree, testspec); s = ((handle == xtreeEnd(svctree)) ? NULL : (service_t *)xtreeData(svctree, handle)); if (option && s) { /* * Check if it is a service with an explicit portnumber. * If it is, then create a new service record named * "SERVICE_PORT" so we can merge tests for this service+port * combination for multiple hosts. * * According to Xymon docs, this type of services must be in * XYMONNETSVCS - so it is known already. */ int specialport = 0; char *specialname; char *opt2 = strrchr(option, ':'); if (opt2) { if (strcmp(opt2, ":s") == 0) { /* option = "portnumber:s" */ silenttest = 1; *opt2 = '\0'; specialport = atoi(option); *opt2 = ':'; } } else if (strcmp(option, "s") == 0) { /* option = "s" */ silenttest = 1; specialport = 0; } else { /* option = "portnumber" */ specialport = atoi(option); } if (specialport) { specialname = (char *) malloc(strlen(s->testname)+10); sprintf(specialname, "%s_%d", s->testname, specialport); s = add_service(specialname, specialport, strlen(s->testname), TOOL_CONTEST); xfree(specialname); } } if (s) h->dodns = 1; if (option) *(option-1) = ':'; } if (s) { testitem_t *newtest; anytests = 1; newtest = init_testitem(h, s, srcip, testspec, dialuptest, reversetest, alwaystruetest, silenttest, sendasdata); newtest->next = s->items; s->items = newtest; if (s == httptest) h->firsthttp = newtest; else if (s == ldaptest) { xtreePos_t handle; service_t *s2 = NULL; testitem_t *newtest2; h->firstldap = newtest; /* * Add a plain tcp-connect test for the LDAP service. * We don't want the LDAP library to run amok and do * time-consuming connect retries if the service * is down. */ handle = xtreeFind(svctree, "ldap"); s2 = ((handle == xtreeEnd(svctree)) ? NULL : (service_t *)xtreeData(svctree, handle)); if (s2) { newtest2 = init_testitem(h, s2, NULL, "ldap", 0, 0, 0, 0, 1); newtest2->internal = 1; newtest2->next = s2->items; s2->items = newtest2; newtest->privdata = newtest2; } } } } testspec = xmh_item_walk(NULL); } if (pingtest && !h->noconn) { /* Add the ping check */ testitem_t *newtest; anytests = 1; newtest = init_testitem(h, pingtest, NULL, NULL, ping_dialuptest, ping_reversetest, 1, 0, 0); newtest->next = pingtest->items; pingtest->items = newtest; h->dodns = 1; } /* * Setup badXXX values. * * We need to do this last, because the testitem_t records do * not exist until the test has been created. * * So after parsing the badFOO tag, we must find the testitem_t * record created earlier for this test (it may not exist). */ testspec = xmh_item_walk(hwalk); while (testspec) { char *testname, *timespec, *badcounts; int badclear, badyellow, badred; int inscope; testitem_t *twalk; service_t *swalk; if (strncmp(testspec, "bad", 3) != 0) { /* Not a bad* tag - skip it */ testspec = xmh_item_walk(NULL); continue; } badtagsused = 1; badclear = badyellow = badred = 0; inscope = 1; testname = testspec+strlen("bad"); badcounts = strchr(testspec, ':'); if (badcounts) { if (sscanf(badcounts, ":%d:%d:%d", &badclear, &badyellow, &badred) != 3) { errprintf("Host %s: Incorrect 'bad' counts: '%s'\n", xmh_item(hwalk, XMH_HOSTNAME), badcounts); badcounts = NULL; } } timespec = strchr(testspec, '-'); if (timespec) inscope = periodcoversnow(timespec); if (strlen(testname) && badcounts && inscope) { char *p; xtreePos_t handle; twalk = NULL; p = strchr(testname, ':'); if (p) *p = '\0'; handle = xtreeFind(svctree, testname); swalk = ((handle == xtreeEnd(svctree)) ? NULL : (service_t *)xtreeData(svctree, handle)); if (p) *p = ':'; if (swalk) { if (swalk == httptest) twalk = h->firsthttp; else if (swalk == ldaptest) twalk = h->firstldap; else for (twalk = swalk->items; (twalk && (twalk->host != h)); twalk = twalk->next) ; } if (twalk) { twalk->badtest[0] = badclear; twalk->badtest[1] = badyellow; twalk->badtest[2] = badred; } else { dbgprintf("No test for badtest spec host=%s, test=%s\n", h->hostname, testname); } } testspec = xmh_item_walk(NULL); } if (anytests) { xtreeStatus_t res; /* * Check for a duplicate host def. Causes all sorts of funny problems. * However, dont drop the second definition - to do this, we will have * to clean up the testitem lists as well, or we get crashes when * tests belong to a non-existing host. */ res = xtreeAdd(testhosttree, h->hostname, h); if (res == XTREE_STATUS_DUPLICATE_KEY) { errprintf("Host %s appears twice in hosts.cfg! This may cause strange results\n", h->hostname); } strcpy(h->ip, xmh_item(hwalk, XMH_IP)); if (!h->testip && (dnsmethod != IP_ONLY)) add_host_to_dns_queue(h->hostname); } else { /* No network tests for this host, so ignore it */ dbgprintf("Did not find any network tests for host %s\n", h->hostname); xfree(h); notesthostcount++; } } if (badtagsused) { errprintf("WARNING: The 'bad' syntax has been deprecated, please convert to 'delayred' and/or 'delayyellow' tags\n"); } return; } char *ip_to_test(testedhost_t *h) { char *dnsresult; int nullip = (strcmp(h->ip, "0.0.0.0") == 0); if (!nullip && (h->testip || (dnsmethod == IP_ONLY))) { /* Already have the IP setup */ } else if (h->dodns) { dnsresult = dnsresolve(h->hostname); if (dnsresult) { strcpy(h->ip, dnsresult); } else if ((dnsmethod == DNS_THEN_IP) && !nullip) { /* Already have the IP setup */ } else { /* Cannot resolve hostname */ h->dnserror = 1; errprintf("xymonnet: Cannot resolve IP for host %s\n", h->hostname); } } return h->ip; } void load_ping_status(void) { FILE *statusfd; char statusfn[PATH_MAX]; char l[MAX_LINE_LEN]; char host[MAX_LINE_LEN]; int downcount; time_t downstart; xtreePos_t handle; testedhost_t *h; sprintf(statusfn, "%s/ping.%s.status", xgetenv("XYMONTMP"), location); statusfd = fopen(statusfn, "r"); if (statusfd == NULL) return; while (fgets(l, sizeof(l), statusfd)) { unsigned int uidownstart; if (sscanf(l, "%s %d %u", host, &downcount, &uidownstart) == 3) { downstart = uidownstart; handle = xtreeFind(testhosttree, host); if (handle != xtreeEnd(testhosttree)) { h = (testedhost_t *)xtreeData(testhosttree, handle); if (!h->noping && !h->noconn) { h->downcount = downcount; h->downstart = downstart; } } } } fclose(statusfd); } void save_ping_status(void) { FILE *statusfd; char statusfn[PATH_MAX]; testitem_t *t; int didany = 0; sprintf(statusfn, "%s/ping.%s.status", xgetenv("XYMONTMP"), location); statusfd = fopen(statusfn, "w"); if (statusfd == NULL) return; for (t=pingtest->items; (t); t = t->next) { if (t->host->downcount) { fprintf(statusfd, "%s %d %u\n", t->host->hostname, t->host->downcount, (unsigned int)t->host->downstart); didany = 1; t->host->repeattest = ((getcurrenttime(NULL) - t->host->downstart) < frequenttestlimit); } } fclose(statusfd); if (!didany) unlink(statusfn); } void load_test_status(service_t *test) { FILE *statusfd; char statusfn[PATH_MAX]; char l[MAX_LINE_LEN]; char host[MAX_LINE_LEN]; int downcount; time_t downstart; xtreePos_t handle; testedhost_t *h; testitem_t *walk; sprintf(statusfn, "%s/%s.%s.status", xgetenv("XYMONTMP"), test->testname, location); statusfd = fopen(statusfn, "r"); if (statusfd == NULL) return; while (fgets(l, sizeof(l), statusfd)) { unsigned int uidownstart; if (sscanf(l, "%s %d %u", host, &downcount, &uidownstart) == 3) { downstart = uidownstart; handle = xtreeFind(testhosttree, host); if (handle != xtreeEnd(testhosttree)) { h = (testedhost_t *)xtreeData(testhosttree, handle); if (test == httptest) walk = h->firsthttp; else if (test == ldaptest) walk = h->firstldap; else for (walk = test->items; (walk && (walk->host != h)); walk = walk->next) ; if (walk) { walk->downcount = downcount; walk->downstart = downstart; } } } } fclose(statusfd); } void save_test_status(service_t *test) { FILE *statusfd; char statusfn[PATH_MAX]; testitem_t *t; int didany = 0; sprintf(statusfn, "%s/%s.%s.status", xgetenv("XYMONTMP"), test->testname, location); statusfd = fopen(statusfn, "w"); if (statusfd == NULL) return; for (t=test->items; (t); t = t->next) { if (t->downcount) { fprintf(statusfd, "%s %d %u\n", t->host->hostname, t->downcount, (unsigned int)t->downstart); didany = 1; t->host->repeattest = ((getcurrenttime(NULL) - t->downstart) < frequenttestlimit); } } fclose(statusfd); if (!didany) unlink(statusfn); } void save_frequenttestlist(int argc, char *argv[]) { FILE *fd; char fn[PATH_MAX]; xtreePos_t handle; testedhost_t *h; int didany = 0; int i; sprintf(fn, "%s/frequenttests.%s", xgetenv("XYMONTMP"), location); fd = fopen(fn, "w"); if (fd == NULL) return; for (i=1; (irepeattest) { fprintf(fd, "%s ", h->hostname); didany = 1; } } fclose(fd); if (!didany) unlink(fn); } void run_nslookup_service(service_t *service) { testitem_t *t; char *lookup; for (t=service->items; (t); t = t->next) { if (!t->host->dnserror) { if (t->testspec && (lookup = strchr(t->testspec, '='))) { lookup++; } else { lookup = t->host->hostname; } t->open = (dns_test_server(ip_to_test(t->host), lookup, t->banner) == 0); } } } void run_ntp_service(service_t *service) { testitem_t *t; char cmd[1024]; char *p; char cmdpath[PATH_MAX]; int use_sntp = 0; p = getenv("SNTP"); /* Plain "getenv" as we want to know if it's unset */ use_sntp = (p != NULL); if (use_sntp) { strcpy(cmdpath, p); } else { p = xgetenv("NTPDATE"); strcpy(cmdpath, (p ? p : "ntpdate")); } for (t=service->items; (t); t = t->next) { if (!t->host->dnserror) { if (use_sntp) { sprintf(cmd, "%s -u -d %d %s 2>&1", cmdpath, extcmdtimeout-1, ip_to_test(t->host)); } else { sprintf(cmd, "%s -u -q -p 2 %s 2>&1", cmdpath, ip_to_test(t->host)); } t->open = (run_command(cmd, "no server suitable for synchronization", t->banner, 1, extcmdtimeout) == 0); } } } void run_rpcinfo_service(service_t *service) { testitem_t *t; char cmd[1024]; char *p; char cmdpath[PATH_MAX]; p = xgetenv("RPCINFO"); strcpy(cmdpath, (p ? p : "rpcinfo")); for (t=service->items; (t); t = t->next) { if (!t->host->dnserror && (t->host->downcount == 0)) { sprintf(cmd, "%s -p %s 2>&1", cmdpath, ip_to_test(t->host)); t->open = (run_command(cmd, NULL, t->banner, 1, extcmdtimeout) == 0); } } } int start_ping_service(service_t *service) { testitem_t *t; char *cmd; char **cmdargs; int pfd[2]; int i; /* * The idea here is to run ping in a separate process, in parallel * with some other time-consuming task (the TCP network tests). * We cannot use the simple "popen()/pclose()" interface, because * a) ping doesn't start the tests until EOF is reached on stdin * b) EOF on stdin happens with pclose(), but it will also wait * for the process to finish. * * Therefore this slightly more complex solution, which in essence * forks a new process running "xymonping 2>&1 1>$XYMONTMP/ping.$$" * The output is then picked up by the finish_ping_service(). */ pingcount = 0; pingpids = calloc(pingchildcount, sizeof(pid_t)); pingcmd = strdup(getenv_default("FPING", "xymonping", NULL)); pingcmd = realloc(pingcmd, strlen(pingcmd)+5); strcat(pingcmd, " -Ae"); sprintf(pinglog, "%s/ping-stdout.%lu", xgetenv("XYMONTMP"), (unsigned long)getpid()); sprintf(pingerrlog, "%s/ping-stderr.%lu", xgetenv("XYMONTMP"), (unsigned long)getpid()); /* Setup command line and arguments */ cmdargs = setup_commandargs(pingcmd, &cmd); for (i=0; (i < pingchildcount); i++) { /* Get a pipe FD */ if (pipe(pfd) == -1) { errprintf("Could not create pipe for xymonping\n"); return -1; } /* Now fork off the ping child-process */ pingpids[i] = fork(); if (pingpids[i] < 0) { errprintf("Could not fork() the ping child\n"); return -1; } else if (pingpids[i] == 0) { /* * child must have * - stdin fed from the parent * - stdout going to a file * - stderr going to another file. This is important, as * putting it together with stdout will wreak havoc when * we start parsing the output later on. We could just * dump it to /dev/null, but it might be useful to see * what went wrong. */ int outfile, errfile, status; sprintf(pinglog+strlen(pinglog), ".%02d", i); sprintf(pingerrlog+strlen(pingerrlog), ".%02d", i); outfile = open(pinglog, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR); if (outfile == -1) errprintf("Cannot create file %s : %s\n", pinglog, strerror(errno)); errfile = open(pingerrlog, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR); if (errfile == -1) errprintf("Cannot create file %s : %s\n", pingerrlog, strerror(errno)); if ((outfile == -1) || (errfile == -1)) { /* Ouch - cannot create our output files. Abort. */ exit(98); } status = dup2(pfd[0], STDIN_FILENO); status = dup2(outfile, STDOUT_FILENO); status = dup2(errfile, STDERR_FILENO); close(pfd[0]); close(pfd[1]); close(outfile); close(errfile); execvp(cmd, cmdargs); /* Should never go here ... just kill the child */ fprintf(stderr, "Command '%s' failed: %s\n", cmd, strerror(errno)); exit(99); } else { /* parent */ char ip[IP_ADDR_STRLEN+1]; /* Must have room for the \n at the end also */ int hnum, n; close(pfd[0]); /* Feed the IP's to test to the child */ for (t=service->items, hnum = 0; (t); t = t->next, hnum++) { if ((hnum % pingchildcount) != i) continue; if (!t->host->dnserror && !t->host->noping) { sprintf(ip, "%s\n", ip_to_test(t->host)); n = write(pfd[1], ip, strlen(ip)); pingcount++; if (t->host->extrapings) { ipping_t *walk; for (walk = t->host->extrapings->iplist; (walk); walk = walk->next) { sprintf(ip, "%s\n", walk->ip); n = write(pfd[1], ip, strlen(ip)); pingcount++; } } } } close(pfd[1]); /* This is when ping starts doing tests */ } } return 0; } int finish_ping_service(service_t *service) { testitem_t *t; FILE *logfd; char *p; char l[MAX_LINE_LEN]; char pingip[MAX_LINE_LEN]; int ip1, ip2, ip3, ip4; int pingstatus, failed = 0, i; char fn[PATH_MAX]; /* Load status of previously failed tests */ load_ping_status(); /* * Wait for the ping child to finish. * If we're lucky, it will be done already since it has run * while we were doing tcp tests. */ for (i = 0; (i < pingchildcount); i++) { waitpid(pingpids[i], &pingstatus, 0); switch (WEXITSTATUS(pingstatus)) { case 0: /* All hosts reachable */ case 1: /* Some hosts unreachable */ case 2: /* Some IP's not found (should not happen) */ break; case 3: /* Bad command-line args, or not suid-root */ failed = 1; errprintf("Execution of '%s' failed - program not suid root?\n", pingcmd); break; case 98: failed = 1; errprintf("xymonping child could not create outputfiles in %s\n", xgetenv("$XYMONTMP")); break; case 99: failed = 1; errprintf("Could not run the command '%s' (exec failed)\n", pingcmd); break; default: failed = 1; errprintf("Execution of '%s' failed with error-code %d\n", pingcmd, WEXITSTATUS(pingstatus)); } /* Open the new ping result file */ sprintf(fn, "%s.%02d", pinglog, i); logfd = fopen(fn, "r"); if (logfd == NULL) { failed = 1; errprintf("Cannot open ping output file %s\n", fn); } if (!debug) unlink(fn); /* We have an open filehandle, so it's ok to delete the file now */ /* Copy error messages to the Xymon logfile */ sprintf(fn, "%s.%02d", pingerrlog, i); if (failed) { FILE *errfd; char buf[1024]; errfd = fopen(fn, "r"); if (errfd && fgets(buf, sizeof(buf), errfd)) { errprintf("%s", buf); } if (errfd) fclose(errfd); } if (!debug) unlink(fn); if (failed) { /* Flag all ping tests as "undecided" */ bigfailure = 1; for (t=service->items; (t); t = t->next) t->open = -1; } else { /* The test did run, and we have a result-file. Look at it. */ while (fgets(l, sizeof(l), logfd)) { p = strchr(l, '\n'); if (p) *p = '\0'; if (sscanf(l, "%d.%d.%d.%d ", &ip1, &ip2, &ip3, &ip4) == 4) { sprintf(pingip, "%d.%d.%d.%d", ip1, ip2, ip3, ip4); /* * Need to loop through all testitems - there may be multiple entries for * the same IP-address. */ for (t=service->items; (t); t = t->next) { if (strcmp(t->host->ip, pingip) == 0) { if (t->open) dbgprintf("More than one ping result for %s\n", pingip); t->open = (strstr(l, "is alive") != NULL); t->banner = dupstrbuffer(l); } if (t->host->extrapings) { ipping_t *walk; for (walk = t->host->extrapings->iplist; (walk); walk = walk->next) { if (strcmp(walk->ip, pingip) == 0) { if (t->open) dbgprintf("More than one ping result for %s\n", pingip); walk->open = (strstr(l, "is alive") != NULL); walk->banner = dupstrbuffer(l); } } } } } } } if (logfd) fclose(logfd); } /* * Handle the router dependency stuff. I.e. for all hosts * where the ping test failed, go through the list of router * dependencies and if one of the dependent hosts also has * a failed ping test, point the dependency there. */ for (t=service->items; (t); t = t->next) { if (!t->open && t->host->routerdeps) { testitem_t *router; strcpy(l, t->host->routerdeps); p = strtok(l, ","); while (p && (t->host->deprouterdown == NULL)) { for (router=service->items; (router && (strcmp(p, router->host->hostname) != 0)); router = router->next) ; if (router && !router->open) t->host->deprouterdown = router->host; p = strtok(NULL, ","); } } } return 0; } int decide_color(service_t *service, char *svcname, testitem_t *test, int failgoesclear, char *cause) { int color = COL_GREEN; int countasdown = 0; char *deptest = NULL; *cause = '\0'; if (service == pingtest) { /* * "noconn" is handled elsewhere. * "noping" always sends back a status "clear". * If DNS error, return red and count as down. */ if (test->open == -1) { /* Failed to run the ping utility. */ strcpy(cause, "Xymon system failure"); return COL_CLEAR; } else if (test->host->noping) { /* Ping test disabled - go "clear". End of story. */ strcpy(cause, "Ping test disabled (noping)"); return COL_CLEAR; } else if (test->host->dnserror) { strcpy(cause, "DNS lookup failure"); color = COL_RED; countasdown = 1; } else { if (test->host->extrapings == NULL) { /* Red if (open=0, reverse=0) or (open=1, reverse=1) */ if ((test->open + test->reverse) != 1) { sprintf(cause, "Host %s respond to ping", (test->open ? "does" : "does not")); color = COL_RED; countasdown = 1; } } else { /* Host with many pings */ int totalcount = 1; int okcount = test->open; ipping_t *walk; for (walk = test->host->extrapings->iplist; (walk); walk = walk->next) { if (walk->open) okcount++; totalcount++; } switch (test->host->extrapings->matchtype) { case MULTIPING_BEST: if (okcount == 0) { color = COL_RED; countasdown = 1; sprintf(cause, "Host does not respond to ping on any of %d IP's", totalcount); } break; case MULTIPING_WORST: if (okcount < totalcount) { color = COL_RED; countasdown = 1; sprintf(cause, "Host responds to ping on %d of %d IP's", okcount, totalcount); } break; } } } /* Handle the "route" tag dependencies. */ if ((color == COL_RED) && test->host->deprouterdown) { char *routertext; routertext = test->host->deprouterdown->hosttype; if (routertext == NULL) routertext = xgetenv("XYMONROUTERTEXT"); if (routertext == NULL) routertext = "router"; strcat(cause, "\nIntermediate "); strcat(cause, routertext); strcat(cause, " down "); color = COL_YELLOW; } /* Handle "badconn" */ if ((color == COL_RED) && (test->host->downcount < test->host->badconn[2])) { if (test->host->downcount >= test->host->badconn[1]) color = COL_YELLOW; else if (test->host->downcount >= test->host->badconn[0]) color = COL_CLEAR; else color = COL_GREEN; } /* Run traceroute , but not on dialup or reverse-test hosts */ if ((color == COL_RED) && test->host->dotrace && !test->host->dialup && !test->reverse && !test->dialup) { char cmd[PATH_MAX]; if (xgetenv("TRACEROUTE")) { sprintf(cmd, "%s %s 2>&1", xgetenv("TRACEROUTE"), test->host->ip); } else { sprintf(cmd, "traceroute -n -q 2 -w 2 -m 15 %s 2>&1", test->host->ip); } test->host->traceroute = newstrbuffer(0); run_command(cmd, NULL, test->host->traceroute, 0, extcmdtimeout); } } else { /* TCP test */ if (test->host->dnserror) { strcpy(cause, "DNS lookup failure"); color = COL_RED; countasdown = 1; } else { if (test->reverse) { /* * Reverse tests go RED when open. * If not open, they may go CLEAR if the ping test failed */ if (test->open) { strcpy(cause, "Service responds when it should not"); color = COL_RED; countasdown = 1; } else if (failgoesclear && (test->host->downcount != 0) && !test->alwaystrue) { strcpy(cause, "Host appears to be down"); color = COL_CLEAR; countasdown = 0; } } else { if (!test->open) { if (failgoesclear && (test->host->downcount != 0) && !test->alwaystrue) { strcpy(cause, "Host appears to be down"); color = COL_CLEAR; countasdown = 0; } else { tcptest_t *tcptest = (tcptest_t *)test->privdata; strcpy(cause, "Service unavailable"); if (tcptest) { switch (tcptest->errcode) { case CONTEST_ETIMEOUT: strcat(cause, " (connect timeout)"); break; case CONTEST_ENOCONN : strcat(cause, " ("); strcat(cause, strerror(tcptest->connres)); strcat(cause, ")"); break; case CONTEST_EDNS : strcat(cause, " (DNS error)"); break; case CONTEST_EIO : strcat(cause, " (I/O error)"); break; case CONTEST_ESSL : strcat(cause, " (SSL error)"); break; } } color = COL_RED; countasdown = 1; } } else { /* Check if we got the expected data */ if (checktcpresponse && (service->toolid == TOOL_CONTEST) && !tcp_got_expected((tcptest_t *)test->privdata)) { strcpy(cause, "Unexpected service response"); color = respcheck_color; countasdown = 1; } } } } /* Handle test dependencies */ if ( failgoesclear && (color == COL_RED) && !test->alwaystrue && (deptest = deptest_failed(test->host, test->service->testname)) ) { strcpy(cause, deptest); color = COL_CLEAR; } /* Handle the "badtest" stuff for other tests */ if ((color == COL_RED) && (test->downcount < test->badtest[2])) { if (test->downcount >= test->badtest[1]) color = COL_YELLOW; else if (test->downcount >= test->badtest[0]) color = COL_CLEAR; else color = COL_GREEN; } } /* Dialup hosts and dialup tests report red as clear */ if ( ((color == COL_RED) || (color == COL_YELLOW)) && (test->host->dialup || test->dialup) && !test->reverse) { strcat(cause, "\nDialup host or service"); color = COL_CLEAR; countasdown = 0; } /* If a NOPAGENET service, downgrade RED to YELLOW */ if (color == COL_RED) { char *nopagename; /* Check if this service is a NOPAGENET service. */ nopagename = (char *) malloc(strlen(svcname)+3); sprintf(nopagename, ",%s,", svcname); if (strstr(nonetpage, svcname) != NULL) color = COL_YELLOW; xfree(nopagename); } if (service == pingtest) { if (countasdown) { test->host->downcount++; if (test->host->downcount == 1) test->host->downstart = getcurrenttime(NULL); } else test->host->downcount = 0; } else { if (countasdown) { test->downcount++; if (test->downcount == 1) test->downstart = getcurrenttime(NULL); } else test->downcount = 0; } return color; } void send_results(service_t *service, int failgoesclear) { testitem_t *t; int color; char msgline[4096]; char msgtext[4096]; char causetext[1024]; char *svcname; svcname = strdup(service->testname); if (service->namelen) svcname[service->namelen] = '\0'; dbgprintf("Sending results for service %s\n", svcname); for (t=service->items; (t); t = t->next) { char flags[10]; int i; if (t->internal) continue; i = 0; flags[i++] = (t->open ? 'O' : 'o'); flags[i++] = (t->reverse ? 'R' : 'r'); flags[i++] = ((t->dialup || t->host->dialup) ? 'D' : 'd'); flags[i++] = (t->alwaystrue ? 'A' : 'a'); flags[i++] = (t->silenttest ? 'S' : 's'); flags[i++] = (t->host->testip ? 'T' : 't'); flags[i++] = (t->host->dodns ? 'L' : 'l'); flags[i++] = (t->host->dnserror ? 'E' : 'e'); flags[i++] = '\0'; color = decide_color(service, svcname, t, failgoesclear, causetext); init_status(color); if (dosendflags) sprintf(msgline, "status+%d %s.%s %s %s %s %s ", validity, commafy(t->host->hostname), svcname, colorname(color), flags, timestamp, svcname, ( ((color == COL_RED) || (color == COL_YELLOW)) ? "NOT ok" : "ok")); else sprintf(msgline, "status %s.%s %s %s %s %s ", commafy(t->host->hostname), svcname, colorname(color), timestamp, svcname, ( ((color == COL_RED) || (color == COL_YELLOW)) ? "NOT ok" : "ok")); if (t->host->dnserror) { strcat(msgline, ": DNS lookup failed"); sprintf(msgtext, "\nUnable to resolve hostname %s\n\n", t->host->hostname); } else { sprintf(msgtext, "\nService %s on %s is ", svcname, t->host->hostname); switch (color) { case COL_GREEN: strcat(msgtext, "OK "); strcat(msgtext, (t->reverse ? "(down)" : "(up)")); strcat(msgtext, "\n"); break; case COL_RED: case COL_YELLOW: if ((service == pingtest) && t->host->deprouterdown) { char *routertext; routertext = t->host->deprouterdown->hosttype; if (routertext == NULL) routertext = xgetenv("XYMONROUTERTEXT"); if (routertext == NULL) routertext = "router"; strcat(msgline, ": Intermediate "); strcat(msgline, routertext); strcat(msgline, " down"); sprintf(msgtext+strlen(msgtext), "%s.\nThe %s %s (IP:%s) is not reachable, causing this host to be unreachable.\n", failtext, routertext, ((testedhost_t *)t->host->deprouterdown)->hostname, ((testedhost_t *)t->host->deprouterdown)->ip); } else { sprintf(msgtext+strlen(msgtext), "%s : %s\n", failtext, causetext); } break; case COL_CLEAR: strcat(msgtext, "OK\n"); if (service == pingtest) { if (t->host->deprouterdown) { char *routertext; routertext = t->host->deprouterdown->hosttype; if (routertext == NULL) routertext = xgetenv("XYMONROUTERTEXT"); if (routertext == NULL) routertext = "router"; strcat(msgline, ": Intermediate "); strcat(msgline, routertext); strcat(msgline, " down"); strcat(msgtext, "\nThe "); strcat(msgtext, routertext); strcat(msgtext, " "); strcat(msgtext, ((testedhost_t *)t->host->deprouterdown)->hostname); strcat(msgtext, " (IP:"); strcat(msgtext, ((testedhost_t *)t->host->deprouterdown)->ip); strcat(msgtext, ") is not reachable, causing this host to be unreachable.\n"); } else if (t->host->noping) { strcat(msgline, ": Disabled"); strcat(msgtext, "Ping check disabled (noping)\n"); } else if (t->host->dialup) { strcat(msgline, ": Disabled (dialup host)"); strcat(msgtext, "Dialup host\n"); } else if (t->open == -1) { strcat(msgline, ": System failure of the ping test"); strcat(msgtext, "Xymon system error\n"); } /* "clear" due to badconn: no extra text */ } else { /* Non-ping test clear: Dialup test or failed ping */ strcat(msgline, ": Ping failed, or dialup host/service"); strcat(msgtext, "Dialup host/service, or test depends on another failed test\n"); strcat(msgtext, causetext); } break; } strcat(msgtext, "\n"); } strcat(msgline, "\n"); addtostatus(msgline); addtostatus(msgtext); if ((service == pingtest) && t->host->downcount) { sprintf(msgtext, "\nSystem unreachable for %d poll periods (%u seconds)\n", t->host->downcount, (unsigned int)(getcurrenttime(NULL) - t->host->downstart)); addtostatus(msgtext); } if (STRBUFLEN(t->banner)) { if (service == pingtest) { sprintf(msgtext, "\n&%s %s\n", colorname(t->open ? COL_GREEN : COL_RED), STRBUF(t->banner)); addtostatus(msgtext); if (t->host->extrapings) { ipping_t *walk; for (walk = t->host->extrapings->iplist; (walk); walk = walk->next) { if (STRBUFLEN(walk->banner)) { sprintf(msgtext, "&%s %s\n", colorname(walk->open ? COL_GREEN : COL_RED), STRBUF(walk->banner)); addtostatus(msgtext); } } } } else { addtostatus("\n"); addtostrstatus(t->banner); addtostatus("\n"); } } if ((service == pingtest) && t->host->traceroute && (STRBUFLEN(t->host->traceroute) > 0)) { addtostatus("Traceroute results:\n"); addtostrstatus(t->host->traceroute); addtostatus("\n"); } if (t->duration.tv_sec != -1) { sprintf(msgtext, "\nSeconds: %u.%02u\n", (unsigned int)t->duration.tv_sec, (unsigned int)t->duration.tv_nsec / 10000000); addtostatus(msgtext); } addtostatus("\n\n"); finish_status(); } } void send_rpcinfo_results(service_t *service, int failgoesclear) { testitem_t *t; int color; char msgline[1024]; char *msgbuf; char causetext[1024]; msgbuf = (char *)malloc(4096); for (t=service->items; (t); t = t->next) { char *wantedrpcsvcs = NULL; char *p; /* First see if the rpcinfo command succeeded */ *msgbuf = '\0'; color = decide_color(service, service->testname, t, failgoesclear, causetext); p = strchr(t->testspec, '='); if (p) wantedrpcsvcs = strdup(p+1); if ((color == COL_GREEN) && STRBUFLEN(t->banner) && wantedrpcsvcs) { char *rpcsvc, *aline; rpcsvc = strtok(wantedrpcsvcs, ","); while (rpcsvc) { struct rpcent *rpcinfo; int svcfound = 0; int aprogram; int aversion; char aprotocol[10]; int aport; rpcinfo = getrpcbyname(rpcsvc); aline = STRBUF(t->banner); while ((!svcfound) && rpcinfo && aline && (*aline != '\0')) { p = strchr(aline, '\n'); if (p) *p = '\0'; if (sscanf(aline, "%d %d %s %d", &aprogram, &aversion, aprotocol, &aport) == 4) { svcfound = (aprogram == rpcinfo->r_number); } aline = p; if (p) { *p = '\n'; aline++; } } if (svcfound) { sprintf(msgline, "&%s Service %s (ID: %d) found on port %d\n", colorname(COL_GREEN), rpcsvc, rpcinfo->r_number, aport); } else if (rpcinfo) { color = COL_RED; sprintf(msgline, "&%s Service %s (ID: %d) NOT found\n", colorname(COL_RED), rpcsvc, rpcinfo->r_number); } else { color = COL_RED; sprintf(msgline, "&%s Unknown RPC service %s\n", colorname(COL_RED), rpcsvc); } strcat(msgbuf, msgline); rpcsvc = strtok(NULL, ","); } } if (wantedrpcsvcs) xfree(wantedrpcsvcs); init_status(color); sprintf(msgline, "status+%d %s.%s %s %s %s %s, %s\n\n", validity, commafy(t->host->hostname), service->testname, colorname(color), timestamp, service->testname, ( ((color == COL_RED) || (color == COL_YELLOW)) ? "NOT ok" : "ok"), causetext); addtostatus(msgline); /* The summary of wanted RPC services */ addtostatus(msgbuf); /* rpcinfo output */ if (t->open) { if (STRBUFLEN(t->banner)) { addtostatus("\n\n"); addtostrstatus(t->banner); } else { sprintf(msgline, "\n\nNo output from rpcinfo -p %s\n", t->host->ip); addtostatus(msgline); } } else { addtostatus("\n\nCould not connect to the portmapper service\n"); if (STRBUFLEN(t->banner)) addtostrstatus(t->banner); } finish_status(); } xfree(msgbuf); } void send_sslcert_status(testedhost_t *host) { int color = -1; xtreePos_t handle; service_t *s; testitem_t *t; char msgline[1024]; strbuffer_t *sslmsg; time_t now = getcurrenttime(NULL); char *certowner; sslmsg = newstrbuffer(0); for (handle = xtreeFirst(svctree); handle != xtreeEnd(svctree); handle = xtreeNext(svctree, handle)) { s = (service_t *)xtreeData(svctree, handle); certowner = s->testname; for (t=s->items; (t); t=t->next) { if ((t->host == host) && t->certinfo && (t->certexpires > 0)) { int sslcolor = COL_GREEN; int ciphercolor = COL_GREEN; if (s == httptest) certowner = ((http_data_t *)t->privdata)->url; else if (s == ldaptest) certowner = t->testspec; if (t->certexpires < (now+host->sslwarndays*86400)) sslcolor = COL_YELLOW; if (t->certexpires < (now+host->sslalarmdays*86400)) sslcolor = COL_RED; if (sslcolor > color) color = sslcolor; if (host->mincipherbits && (t->mincipherbits < host->mincipherbits)) ciphercolor = COL_RED; if (ciphercolor > color) color = ciphercolor; if (t->certexpires > now) { sprintf(msgline, "\n&%s SSL certificate for %s expires in %u days\n\n", colorname(sslcolor), certowner, (unsigned int)((t->certexpires - now) / 86400)); } else { sprintf(msgline, "\n&%s SSL certificate for %s expired %u days ago\n\n", colorname(sslcolor), certowner, (unsigned int)((now - t->certexpires) / 86400)); } addtobuffer(sslmsg, msgline); if (host->mincipherbits) { sprintf(msgline, "&%s Minimum available SSL encryption is %d bits (should be %d)\n", colorname(ciphercolor), t->mincipherbits, host->mincipherbits); addtobuffer(sslmsg, msgline); } addtobuffer(sslmsg, "\n"); addtobuffer(sslmsg, t->certinfo); } } } if (color != -1) { /* Send off the sslcert status report */ init_status(color); sprintf(msgline, "status+%d %s.%s %s %s\n", validity, commafy(host->hostname), ssltestname, colorname(color), timestamp); addtostatus(msgline); addtostrstatus(sslmsg); addtostatus("\n\n"); finish_status(); } freestrbuffer(sslmsg); } int main(int argc, char *argv[]) { xtreePos_t handle; service_t *s; testedhost_t *h; testitem_t *t; int argi; int concurrency = 0; char *pingcolumn = ""; char *egocolumn = NULL; int failgoesclear = 0; /* IPTEST_2_CLEAR_ON_FAILED_CONN */ int dumpdata = 0; int runtimewarn; /* 300 = default TASKSLEEP setting */ int servicedumponly = 0; int pingrunning = 0; if (init_ldap_library() != 0) { errprintf("Failed to initialize ldap library\n"); return 1; } if (xgetenv("CONNTEST") && (strcmp(xgetenv("CONNTEST"), "FALSE") == 0)) pingcolumn = NULL; runtimewarn = (xgetenv("TASKSLEEP") ? atol(xgetenv("TASKSLEEP")) : 300); for (argi=1; (argi < argc); argi++) { if (argnmatch(argv[argi], "--timeout=")) { char *p = strchr(argv[argi], '='); p++; timeout = atoi(p); } else if (argnmatch(argv[argi], "--conntimeout=")) { int newtimeout; char *p = strchr(argv[argi], '='); p++; newtimeout = atoi(p); if (newtimeout > timeout) timeout = newtimeout; errprintf("Deprecated option '--conntimeout' should not be used\n"); } else if (argnmatch(argv[argi], "--cmdtimeout=")) { char *p = strchr(argv[argi], '='); p++; extcmdtimeout = atoi(p); } else if (argnmatch(argv[argi], "--concurrency=")) { char *p = strchr(argv[argi], '='); p++; concurrency = atoi(p); } else if (argnmatch(argv[argi], "--dns-timeout=") || argnmatch(argv[argi], "--dns-max-all=")) { char *p = strchr(argv[argi], '='); p++; dnstimeout = atoi(p); } else if (argnmatch(argv[argi], "--dns=")) { char *p = strchr(argv[argi], '='); p++; if (strcmp(p, "only") == 0) dnsmethod = DNS_ONLY; else if (strcmp(p, "ip") == 0) dnsmethod = IP_ONLY; else dnsmethod = DNS_THEN_IP; } else if (strcmp(argv[argi], "--no-ares") == 0) { use_ares_lookup = 0; } else if (argnmatch(argv[argi], "--maxdnsqueue=")) { char *p = strchr(argv[argi], '='); max_dns_per_run = atoi(p+1); } else if (argnmatch(argv[argi], "--dnslog=")) { char *fn = strchr(argv[argi], '='); dnsfaillog = fopen(fn+1, "w"); } else if (argnmatch(argv[argi], "--report=") || (strcmp(argv[argi], "--report") == 0)) { char *p = strchr(argv[argi], '='); if (p) { egocolumn = strdup(p+1); } else egocolumn = "xymonnet"; timing = 1; } else if (strcmp(argv[argi], "--test-untagged") == 0) { testuntagged = 1; } else if (argnmatch(argv[argi], "--frequenttestlimit=")) { char *p = strchr(argv[argi], '='); p++; frequenttestlimit = atoi(p); } else if (strcmp(argv[argi], "--timelimit=") == 0) { char *p = strchr(argv[argi], '='); p++; runtimewarn = atol(p); } else if (strcmp(argv[argi], "--huge=") == 0) { char *p = strchr(argv[argi], '='); p++; warnbytesread = atoi(p); } else if (strcmp(argv[argi], "--loadhostsfromxymond") == 0) { loadhostsfromxymond = 1; } /* Options for TCP tests */ else if (strcmp(argv[argi], "--checkresponse") == 0) { checktcpresponse = 1; } else if (argnmatch(argv[argi], "--checkresponse=")) { char *p = strchr(argv[argi], '='); checktcpresponse = 1; respcheck_color = parse_color(p+1); if (respcheck_color == -1) { errprintf("Invalid colorname in '%s' - using yellow\n", argv[argi]); respcheck_color = COL_YELLOW; } } else if (strcmp(argv[argi], "--no-flags") == 0) { dosendflags = 0; } else if (strcmp(argv[argi], "--shuffle") == 0) { shuffletests = 1; } else if (argnmatch(argv[argi], "--source-ip=")) { char *p = strchr(argv[argi], '='); struct in_addr aa; p++; if (inet_aton(p, &aa)) defaultsourceip = strdup(p); else errprintf("Invalid source ip address '%s'\n", argv[argi]); } /* Options for PING tests */ else if (argnmatch(argv[argi], "--ping-tasks=")) { /* Note: must check for this before checking "--ping" option */ char *p = strchr(argv[argi], '='); pingchildcount = atoi(p+1); } else if (argnmatch(argv[argi], "--ping")) { char *p = strchr(argv[argi], '='); if (p) { p++; pingcolumn = p; } else pingcolumn = ""; } else if (strcmp(argv[argi], "--noping") == 0) { pingcolumn = NULL; } else if (strcmp(argv[argi], "--trace") == 0) { dotraceroute = 1; } else if (strcmp(argv[argi], "--notrace") == 0) { dotraceroute = 0; } /* Options for HTTP tests */ else if (argnmatch(argv[argi], "--content=")) { char *p = strchr(argv[argi], '='); contenttestname = strdup(p+1); } else if (strcmp(argv[argi], "--bb-proxy-syntax") == 0) { /* Obey the Big Brother format for http proxy listed as part of the URL */ obeybbproxysyntax = 1; } /* Options for SSL certificates */ else if (argnmatch(argv[argi], "--ssl=")) { char *p = strchr(argv[argi], '='); ssltestname = strdup(p+1); } else if (strcmp(argv[argi], "--no-ssl") == 0) { ssltestname = NULL; } else if (argnmatch(argv[argi], "--sslwarn=")) { char *p = strchr(argv[argi], '='); p++; sslwarndays = atoi(p); } else if (argnmatch(argv[argi], "--sslalarm=")) { char *p = strchr(argv[argi], '='); p++; sslalarmdays = atoi(p); } else if (argnmatch(argv[argi], "--sslbits=")) { char *p = strchr(argv[argi], '='); p++; mincipherbits = atoi(p); } else if (argnmatch(argv[argi], "--validity=")) { char *p = strchr(argv[argi], '='); p++; validity = atoi(p); } /* Debugging options */ else if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (argnmatch(argv[argi], "--dump")) { char *p = strchr(argv[argi], '='); if (p) { if (strcmp(p, "=before") == 0) dumpdata = 1; else if (strcmp(p, "=after") == 0) dumpdata = 2; else dumpdata = 3; } else dumpdata = 2; debug = 1; } else if (strcmp(argv[argi], "--no-update") == 0) { dontsendmessages = 1; } else if (strcmp(argv[argi], "--timing") == 0) { timing = 1; } /* Informational options */ else if (strcmp(argv[argi], "--services") == 0) { servicedumponly = 1; } else if (strcmp(argv[argi], "--version") == 0) { printf("xymonnet version %s\n", VERSION); if (ssl_library_version) printf("SSL library : %s\n", ssl_library_version); if (ldap_library_version) printf("LDAP library: %s\n", ldap_library_version); printf("\n"); return 0; } else if ((strcmp(argv[argi], "--help") == 0) || (strcmp(argv[argi], "-?") == 0)) { printf("xymonnet version %s\n\n", VERSION); printf("Usage: %s [options] [host1 host2 host3 ...]\n", argv[0]); printf("General options:\n"); printf(" --timeout=N : Timeout (in seconds) for service tests\n"); printf(" --concurrency=N : Number of tests run in parallel\n"); printf(" --dns-timeout=N : DNS lookups timeout and fail after N seconds [30]\n"); printf(" --dns=[only|ip|standard] : How IP's are decided\n"); printf(" --no-ares : Use the system resolver library for hostname lookups\n"); printf(" --dnslog=FILENAME : Log failed hostname lookups to file FILENAME\n"); printf(" --report[=COLUMNNAME] : Send a status report about the running of xymonnet\n"); printf(" --test-untagged : Include hosts without a NET: tag in the test\n"); printf(" --frequenttestlimit=N : Seconds after detecting failures in which we poll frequently\n"); printf(" --timelimit=N : Warns if the complete test run takes longer than N seconds [TASKSLEEP]\n"); printf("\nOptions for simple TCP service tests:\n"); printf(" --checkresponse : Check response from known services\n"); printf(" --no-flags : Dont send extra xymonnet test flags\n"); printf("\nOptions for PING (connectivity) tests:\n"); printf(" --ping[=COLUMNNAME] : Enable ping checking, default columname is \"conn\"\n"); printf(" --noping : Disable ping checking\n"); printf(" --trace : Run traceroute on all hosts where ping fails\n"); printf(" --notrace : Disable traceroute when ping fails (default)\n"); printf(" --ping-tasks=N : Run N ping tasks in parallel (default N=1)\n"); printf("\nOptions for HTTP/HTTPS (Web) tests:\n"); printf(" --content=COLUMNNAME : Define default columnname for CONTENT checks (content)\n"); printf("\nOptions for SSL certificate tests:\n"); printf(" --ssl=COLUMNNAME : Define columnname for SSL certificate checks (sslcert)\n"); printf(" --no-ssl : Disable SSL certificate check\n"); printf(" --sslwarn=N : Go yellow if certificate expires in less than N days (default:30)\n"); printf(" --sslalarm=N : Go red if certificate expires in less than N days (default:10)\n"); printf("\nDebugging options:\n"); printf(" --no-update : Send status messages to stdout instead of to Xymon\n"); printf(" --timing : Trace the amount of time spent on each series of tests\n"); printf(" --debug : Output debugging information\n"); printf(" --dump[=before|=after|=all] : Dump internal memory structures before/after tests run\n"); printf(" --maxdnsqueue=N : Only queue N DNS lookups at a time\n"); printf("\nInformational options:\n"); printf(" --services : Dump list of known services and exit\n"); printf(" --version : Show program version and exit\n"); printf(" --help : Show help text and exit\n"); return 0; } else if (strncmp(argv[argi], "-", 1) == 0) { errprintf("Unknown option %s - try --help\n", argv[argi]); } else { /* Must be a hostname */ if (selectedcount == 0) selectedhosts = (char **) malloc(argc*sizeof(char *)); selectedhosts[selectedcount++] = strdup(argv[argi]); } } svctree = xtreeNew(strcasecmp); testhosttree = xtreeNew(strcasecmp); cookietree = xtreeNew(strcmp); init_timestamp(); envcheck(reqenv); fqdn = get_fqdn(); /* Setup SEGV handler */ setup_signalhandler(egocolumn ? egocolumn : "xymonnet"); if (xgetenv("XYMONNETWORK") && (strlen(xgetenv("XYMONNETWORK")) > 0)) location = strdup(xgetenv("XYMONNETWORK")); else if (xgetenv("BBLOCATION") && (strlen(xgetenv("BBLOCATION")) > 0)) location = strdup(xgetenv("BBLOCATION")); if (pingcolumn && (strlen(pingcolumn) == 0)) pingcolumn = xgetenv("PINGCOLUMN"); if (pingcolumn && xgetenv("IPTEST_2_CLEAR_ON_FAILED_CONN")) { failgoesclear = (strcmp(xgetenv("IPTEST_2_CLEAR_ON_FAILED_CONN"), "TRUE") == 0); } if (xgetenv("NETFAILTEXT")) failtext = strdup(xgetenv("NETFAILTEXT")); if (debug) { int i; printf("Command: xymonnet"); for (i=1; (iitems) pingrunning = (start_ping_service(pingtest) == 0); /* Load current status files */ for (handle = xtreeFirst(svctree); handle != xtreeEnd(svctree); handle = xtreeNext(svctree, handle)) { s = (service_t *)xtreeData(svctree, handle); if (s != pingtest) load_test_status(s); } /* First run the TCP/IP and HTTP tests */ for (handle = xtreeFirst(svctree); handle != xtreeEnd(svctree); handle = xtreeNext(svctree, handle)) { s = (service_t *)xtreeData(svctree, handle); if ((s->items) && (s->toolid == TOOL_CONTEST)) { char tname[128]; for (t = s->items; (t); t = t->next) { if (!t->host->dnserror) { strcpy(tname, s->testname); if (s->namelen) tname[s->namelen] = '\0'; t->privdata = (void *)add_tcp_test(ip_to_test(t->host), s->portnum, tname, NULL, t->srcip, NULL, t->silenttest, NULL, NULL, NULL, NULL); } } } } for (t = httptest->items; (t); t = t->next) add_http_test(t); add_timestamp("Test engine setup completed"); do_tcp_tests(timeout, concurrency); add_timestamp("TCP tests completed"); if (pingrunning) { char msg[512]; finish_ping_service(pingtest); sprintf(msg, "PING test completed (%d hosts)", pingcount); add_timestamp(msg); combo_start(); send_results(pingtest, failgoesclear); if (selectedhosts == 0) save_ping_status(); combo_end(); add_timestamp("PING test results sent"); } if (debug) { show_tcp_test_results(); show_http_test_results(httptest); } for (handle = xtreeFirst(svctree); handle != xtreeEnd(svctree); handle = xtreeNext(svctree, handle)) { s = (service_t *)xtreeData(svctree, handle); if ((s->items) && (s->toolid == TOOL_CONTEST)) { for (t = s->items; (t); t = t->next) { /* * If the test fails due to DNS error, t->privdata is NULL */ if (t->privdata) { char *p; int i; tcptest_t *testresult = (tcptest_t *)t->privdata; t->open = testresult->open; t->banner = dupstrbuffer(testresult->banner); t->certinfo = testresult->certinfo; t->certexpires = testresult->certexpires; t->mincipherbits = testresult->mincipherbits; t->duration.tv_sec = testresult->duration.tv_sec; t->duration.tv_nsec = testresult->duration.tv_nsec; /* Binary data in banner ... */ for (i=0, p=STRBUF(t->banner); (i < STRBUFLEN(t->banner)); i++, p++) { if (!isprint((int)*p) && !isspace((int)*p)) *p = '.'; } } } } } for (t = httptest->items; (t); t = t->next) { if (t->privdata) { http_data_t *testresult = (http_data_t *)t->privdata; t->certinfo = testresult->tcptest->certinfo; t->certexpires = testresult->tcptest->certexpires; t->mincipherbits = testresult->tcptest->mincipherbits; } } add_timestamp("Test result collection completed"); /* Run the ldap tests */ for (t = ldaptest->items; (t); t = t->next) add_ldap_test(t); add_timestamp("LDAP test engine setup completed"); run_ldap_tests(ldaptest, (ssltestname != NULL), timeout); add_timestamp("LDAP tests executed"); if (debug) show_ldap_test_results(ldaptest); for (t = ldaptest->items; (t); t = t->next) { if (t->privdata) { ldap_data_t *testresult = (ldap_data_t *)t->privdata; t->certinfo = testresult->certinfo; t->mincipherbits = testresult->mincipherbits; t->certexpires = testresult->certexpires; } } add_timestamp("LDAP tests result collection completed"); /* dns, ntp tests */ for (handle = xtreeFirst(svctree); handle != xtreeEnd(svctree); handle = xtreeNext(svctree, handle)) { s = (service_t *)xtreeData(svctree, handle); if (s->items) { switch(s->toolid) { case TOOL_DNS: run_nslookup_service(s); add_timestamp("DNS tests executed"); break; case TOOL_NTP: run_ntp_service(s); add_timestamp("NTP tests executed"); break; case TOOL_RPCINFO: run_rpcinfo_service(s); add_timestamp("RPC tests executed"); break; default: break; } } } combo_start(); for (handle = xtreeFirst(svctree); handle != xtreeEnd(svctree); handle = xtreeNext(svctree, handle)) { s = (service_t *)xtreeData(svctree, handle); switch (s->toolid) { case TOOL_CONTEST: case TOOL_DNS: case TOOL_NTP: send_results(s, failgoesclear); break; case TOOL_FPING: case TOOL_HTTP: case TOOL_LDAP: /* These handle result-transmission internally */ break; case TOOL_RPCINFO: send_rpcinfo_results(s, failgoesclear); break; } } for (handle = xtreeFirst(testhosttree); (handle != xtreeEnd(testhosttree)); handle = xtreeNext(testhosttree, handle)) { h = (testedhost_t *)xtreeData(testhosttree, handle); send_http_results(httptest, h, h->firsthttp, nonetpage, failgoesclear); send_content_results(httptest, h, nonetpage, contenttestname, failgoesclear); send_ldap_results(ldaptest, h, nonetpage, failgoesclear); if (ssltestname && !h->nosslcert) send_sslcert_status(h); } combo_end(); add_timestamp("Test results transmitted"); /* * The list of hosts to test frequently because of a failure must * be saved - it is then picked up by the frequent-test ext script * that runs xymonnet again with the frequent-test hosts as * parameter. * * Should the retest itself update the frequent-test file ? It * would allow us to kick hosts from the frequent-test file sooner. * However, it is simpler (no races) if we just let the normal * test-engine be alone in updating the file. * At the worst, we'll re-test a host going up a couple of times * too much. * * So for now update the list only if we ran with no host-parameters. */ if (selectedcount == 0) { /* Save current status files */ for (handle = xtreeFirst(svctree); handle != xtreeEnd(svctree); handle = xtreeNext(svctree, handle)) { s = (service_t *)xtreeData(svctree, handle); if (s != pingtest) save_test_status(s); } /* Save frequent-test list */ save_frequenttestlist(argc, argv); } /* Save session cookies - every time */ save_session_cookies(); shutdown_ldap_library(); add_timestamp("xymonnet completed"); if (dumpdata & 2) { dump_hostlist(); dump_testitems(); } /* Tell about us */ if (egocolumn) { char msgline[4096]; char *timestamps; int color; /* Go yellow if it runs for too long */ if (total_runtime() > runtimewarn) { errprintf("WARNING: Runtime %ld longer than time limit (%ld)\n", total_runtime(), runtimewarn); } color = (errbuf ? COL_YELLOW : COL_GREEN); if (bigfailure) color = COL_RED; combo_start(); init_status(color); sprintf(msgline, "status+%d %s.%s %s %s\n\n", validity, xgetenv("MACHINE"), egocolumn, colorname(color), timestamp); addtostatus(msgline); sprintf(msgline, "xymonnet version %s\n", VERSION); addtostatus(msgline); if (ssl_library_version) { sprintf(msgline, "SSL library : %s\n", ssl_library_version); addtostatus(msgline); } if (ldap_library_version) { sprintf(msgline, "LDAP library: %s\n", ldap_library_version); addtostatus(msgline); } sprintf(msgline, "\nStatistics:\n Hosts total : %8d\n Hosts with no tests : %8d\n Total test count : %8d\n Status messages : %8d\n Alert status msgs : %8d\n Transmissions : %8d\n", hostcount, notesthostcount, testcount, xymonstatuscount, xymonnocombocount, xymonmsgcount); addtostatus(msgline); sprintf(msgline, "\nDNS statistics:\n # hostnames resolved : %8d\n # succesful : %8d\n # failed : %8d\n # calls to dnsresolve : %8d\n", dns_stats_total, dns_stats_success, dns_stats_failed, dns_stats_lookups); addtostatus(msgline); sprintf(msgline, "\nTCP test statistics:\n # TCP tests total : %8d\n # HTTP tests : %8d\n # Simple TCP tests : %8d\n # Connection attempts : %8d\n # bytes written : %8ld\n # bytes read : %8ld\n", tcp_stats_total, tcp_stats_http, tcp_stats_plain, tcp_stats_connects, tcp_stats_written, tcp_stats_read); addtostatus(msgline); if (errbuf) { addtostatus("\n\nError output:\n"); addtostatus(errbuf); } show_timestamps(×tamps); addtostatus(timestamps); finish_status(); combo_end(); } else show_timestamps(NULL); if (dnsfaillog) fclose(dnsfaillog); return 0; } xymon-4.3.7/xymonnet/httptest.c0000664000175000017500000004633511615341300016134 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* This is used to implement the testing of HTTP service. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: httptest.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include "version.h" #include "libxymon.h" #include "xymonnet.h" #include "contest.h" #include "httpcookies.h" #include "httptest.h" #include "dns.h" int tcp_http_data_callback(unsigned char *buf, unsigned int len, void *priv) { /* * This callback receives data from HTTP servers. * While doing that, it splits out the data into a * buffer for the HTTP headers, and a buffer for the * HTTP content-data. * Return 1 if data is complete, 0 if more data wanted. */ http_data_t *item = (http_data_t *) priv; if (item->gotheaders) { unsigned int len1chunk = 0; int i; /* * We already have the headers, so just stash away the data */ while (len > 0) { dbgprintf("HDC IN : state=%d, leftinchunk=%d, len=%d\n", item->chunkstate, item->leftinchunk, len); switch (item->chunkstate) { case CHUNK_NOTCHUNKED: len1chunk = len; if ((item->contlen > 0) && (item->contlen >= len)) item->contlen -= len; break; case CHUNK_INIT: /* We're about to pick up a chunk length */ item->leftinchunk = 0; item->chunkstate = CHUNK_GETLEN; len1chunk = 0; break; case CHUNK_GETLEN: /* We are collecting the length of the chunk */ i = hexvalue(*buf); if (i == -1) { item->chunkstate = CHUNK_SKIPLENCR; } else { item->leftinchunk = item->leftinchunk*16 + i; buf++; len--; } len1chunk = 0; break; case CHUNK_SKIPLENCR: /* We've got the length, now skip to the next LF */ if (*buf == '\n') { buf++; len--; item->chunkstate = ((item->leftinchunk > 0) ? CHUNK_DATA : CHUNK_NOMORE); } else if ((*buf == '\r') || (*buf == ' ')) { buf++; len--; } else { errprintf("Yikes - strange data following chunk len. Saw a '%c'\n", *buf); buf++; len--; } len1chunk = 0; break; case CHUNK_DATA: /* Passing off the data */ if (len > item->leftinchunk) len1chunk = item->leftinchunk; else len1chunk = len; item->leftinchunk -= len1chunk; if (item->leftinchunk == 0) item->chunkstate = CHUNK_SKIPENDCR; break; case CHUNK_SKIPENDCR: /* Skip CR/LF after a chunk */ if (*buf == '\n') { buf++; len--; item->chunkstate = CHUNK_DONE; } else if (*buf == '\r') { buf++; len--; } else { errprintf("Yikes - strange data following chunk data. Saw a '%c'\n", *buf); buf++; len--; } len1chunk = 0; break; case CHUNK_DONE: /* One chunk is done, continue with the next */ len1chunk = 0; item->chunkstate = CHUNK_GETLEN; break; case CHUNK_NOMORE: /* All chunks done. Skip the rest (trailers) */ len1chunk = 0; len = 0; } if (len1chunk > 0) { switch (item->contentcheck) { case CONTENTCHECK_NONE: case CONTENTCHECK_CONTENTTYPE: /* No need to save output - just drop it */ break; case CONTENTCHECK_REGEX: case CONTENTCHECK_NOREGEX: /* Save the full data */ if ((item->output == NULL) || (item->outlen == 0)) { item->output = (unsigned char *)malloc(len1chunk+1); } else { item->output = (unsigned char *)realloc(item->output, item->outlen+len1chunk+1); } memcpy(item->output+item->outlen, buf, len1chunk); item->outlen += len1chunk; *(item->output + item->outlen) = '\0'; /* Just in case ... */ break; case CONTENTCHECK_DIGEST: /* Run the data through our digest routine, but discard the raw data */ if ((item->digestctx == NULL) || (digest_data(item->digestctx, buf, len1chunk) != 0)) { errprintf("Failed to hash data for digest\n"); } break; } buf += len1chunk; len -= len1chunk; dbgprintf("HDC OUT: state=%d, leftinchunk=%d, len=%d\n", item->chunkstate, item->leftinchunk, len); } } } else { /* * Havent seen the end of headers yet. */ unsigned char *p; /* First, add this to the header-buffer */ if (item->headers == NULL) { item->headers = (unsigned char *) malloc(len+1); } else { item->headers = (unsigned char *) realloc(item->headers, item->hdrlen+len+1); } memcpy(item->headers+item->hdrlen, buf, len); item->hdrlen += len; *(item->headers + item->hdrlen) = '\0'; check_for_endofheaders: /* * Now see if we have the end-of-headers delimiter. * This SHOULD be , but RFC 2616 says * you SHOULD recognize just plain . * So try the second form, if the first one is not there. */ p=strstr(item->headers, "\r\n\r\n"); if (p) { p += 4; } else { p = strstr(item->headers, "\n\n"); if (p) p += 2; } if (p) { int http1subver, httpstatus; unsigned int bytesindata; char *p1, *xferencoding; int contlen; /* We have an end-of-header delimiter, but it could be just a "100 Continue" response */ sscanf(item->headers, "HTTP/1.%d %d", &http1subver, &httpstatus); if (httpstatus == 100) { /* * It's a "100" continue-status. * Just drop this set of headers, and move on. */ item->hdrlen -= (p - item->headers); if (item->hdrlen > 0) { memmove(item->headers, p, item->hdrlen); *(item->headers + item->hdrlen) = '\0'; goto check_for_endofheaders; } else { xfree(item->headers); item->headers = NULL; item->hdrlen = 0; return 0; } /* Should never go here ... */ } /* We did find the end-of-header delimiter, and it is not a "100" status. */ item->gotheaders = 1; /* p points at the first byte of DATA. So the header ends 1 or 2 bytes before. */ *(p-1) = '\0'; if (*(p-2) == '\r') { *(p-2) = '\0'; } /* NULL-terminate the headers. */ /* See if the transfer uses chunks */ p1 = item->headers; xferencoding = NULL; contlen = 0; do { if (strncasecmp(p1, "Transfer-encoding:", 18) == 0) { p1 += 18; while (isspace((int)*p1)) p1++; xferencoding = p1; } else if (strncasecmp(p1, "Content-Length:", 15) == 0) { p1 += 15; while (isspace((int)*p1)) p1++; contlen = atoi(p1); } else { p1 = strchr(p1, '\n'); if (p1) p1++; } } while (p1 && (xferencoding == NULL)); if (xferencoding && (strncasecmp(xferencoding, "chunked", 7) == 0)) { item->chunkstate = CHUNK_INIT; } item->contlen = (contlen ? contlen : -1); bytesindata = item->hdrlen - (p - item->headers); item->hdrlen = strlen(item->headers); if (*p) { /* * We received some content data together with the * headers. Save these to the content-data area. */ tcp_http_data_callback(p, bytesindata, priv); } } } if (item->chunkstate == CHUNK_NOTCHUNKED) /* Not chunked - we're done if contlen reaches 0 */ return (item->contlen == 0); else /* Chunked - we're done if we reach state NOMORE*/ return (item->chunkstate == CHUNK_NOMORE); } void tcp_http_final_callback(void *priv) { /* * This callback is invoked when a HTTP request is * complete (when the socket is closed). * We use it to pickup some information from the raw * HTTP response, and parse it into some easier to * handle properties. */ http_data_t *item = (http_data_t *) priv; if ((item->contentcheck == CONTENTCHECK_DIGEST) && item->digestctx) { item->digest = digest_done(item->digestctx); } if (item->headers) { int http1subver; char *p; sscanf(item->headers, "HTTP/1.%d %ld", &http1subver, &item->httpstatus); item->contenttype = NULL; p = item->headers; do { if (strncasecmp(p, "Content-Type:", 13) == 0) { char *p2, savechar; p += 13; while (isspace((int)*p)) p++; p2 = (p + strcspn(p, "\r\n ;")); savechar = *p2; *p2 = '\0'; item->contenttype = strdup(p); *p2 = savechar; } else { p = strchr(p, '\n'); if (p) p++; } } while ((item->contenttype == NULL) && p); } if (item->tcptest->errcode != CONTEST_ENOERROR) { /* Flag error by setting httpstatus to 0 */ item->httpstatus = 0; } } void add_http_test(testitem_t *t) { http_data_t *httptest; char *dnsip = NULL; ssloptions_t *sslopt = NULL; char *sslopt_ciphers = NULL; int sslopt_version = SSLVERSION_DEFAULT; char *sslopt_clientcert = NULL; int httpversion = HTTPVER_11; cookielist_t *ck = NULL; int firstcookie = 1; char *decodedurl; strbuffer_t *httprequest = newstrbuffer(0); /* Allocate the private data and initialize it */ httptest = (http_data_t *) calloc(1, sizeof(http_data_t)); t->privdata = (void *) httptest; decodedurl = decode_url(t->testspec, &httptest->weburl); if (!decodedurl) { errprintf("Invalid URL for http check: %s\n", t->testspec); return; } httptest->url = strdup(decodedurl); httptest->contlen = -1; httptest->parsestatus = (httptest->weburl.proxyurl ? httptest->weburl.proxyurl->parseerror : httptest->weburl.desturl->parseerror); /* If there was a parse error in the URL, dont run the test */ if (httptest->parsestatus) return; if (httptest->weburl.proxyurl && (httptest->weburl.proxyurl->ip == NULL)) { dnsip = dnsresolve(httptest->weburl.proxyurl->host); if (dnsip) { httptest->weburl.proxyurl->ip = strdup(dnsip); } else { dbgprintf("Could not resolve URL hostname '%s'\n", httptest->weburl.proxyurl->host); } } else if (httptest->weburl.desturl->ip == NULL) { dnsip = dnsresolve(httptest->weburl.desturl->host); if (dnsip) { httptest->weburl.desturl->ip = strdup(dnsip); } else { dbgprintf("Could not resolve URL hostname '%s'\n", httptest->weburl.desturl->host); } } switch (httptest->weburl.testtype) { case WEBTEST_PLAIN: case WEBTEST_STATUS: httptest->contentcheck = CONTENTCHECK_NONE; break; case WEBTEST_CONTENT: { FILE *contentfd; char contentfn[PATH_MAX]; sprintf(contentfn, "%s/content/%s.substring", xgetenv("XYMONHOME"), commafy(t->host->hostname)); contentfd = fopen(contentfn, "r"); if (contentfd) { char l[MAX_LINE_LEN]; char *p; if (fgets(l, sizeof(l), contentfd)) { p = strchr(l, '\n'); if (p) { *p = '\0'; }; httptest->weburl.expdata = strdup(l); } else { httptest->contstatus = STATUS_CONTENTMATCH_NOFILE; } fclose(contentfd); } else { httptest->contstatus = STATUS_CONTENTMATCH_NOFILE; } httptest->contentcheck = CONTENTCHECK_REGEX; } break; case WEBTEST_CONT: httptest->contentcheck = ((*httptest->weburl.expdata == '#') ? CONTENTCHECK_DIGEST : CONTENTCHECK_REGEX); break; case WEBTEST_NOCONT: httptest->contentcheck = CONTENTCHECK_NOREGEX; break; case WEBTEST_POST: case WEBTEST_SOAP: if (httptest->weburl.expdata == NULL) { httptest->contentcheck = CONTENTCHECK_NONE; } else { httptest->contentcheck = ((*httptest->weburl.expdata == '#') ? CONTENTCHECK_DIGEST : CONTENTCHECK_REGEX); } break; case WEBTEST_NOPOST: case WEBTEST_NOSOAP: if (httptest->weburl.expdata == NULL) { httptest->contentcheck = CONTENTCHECK_NONE; } else { httptest->contentcheck = CONTENTCHECK_NOREGEX; } break; case WEBTEST_TYPE: httptest->contentcheck = CONTENTCHECK_CONTENTTYPE; break; } /* Compile the hashes and regex's for those tests that use it */ switch (httptest->contentcheck) { case CONTENTCHECK_DIGEST: { char *hashfunc; httptest->exp = (void *) strdup(httptest->weburl.expdata+1); hashfunc = strchr(httptest->exp, ':'); if (hashfunc) { *hashfunc = '\0'; httptest->digestctx = digest_init(httptest->exp); *hashfunc = ':'; } } break; case CONTENTCHECK_REGEX: case CONTENTCHECK_NOREGEX: { int status; httptest->exp = (void *) malloc(sizeof(regex_t)); status = regcomp((regex_t *)httptest->exp, httptest->weburl.expdata, REG_EXTENDED|REG_NOSUB); if (status) { errprintf("Failed to compile regexp '%s' for URL %s\n", httptest->weburl.expdata, httptest->url); httptest->contstatus = STATUS_CONTENTMATCH_BADREGEX; } } break; case CONTENTCHECK_CONTENTTYPE: httptest->exp = httptest->weburl.expdata; break; } if (httptest->weburl.desturl->schemeopts) { if (strstr(httptest->weburl.desturl->schemeopts, "3")) sslopt_version = SSLVERSION_V3; else if (strstr(httptest->weburl.desturl->schemeopts, "2")) sslopt_version = SSLVERSION_V2; if (strstr(httptest->weburl.desturl->schemeopts, "h")) sslopt_ciphers = ciphershigh; else if (strstr(httptest->weburl.desturl->schemeopts, "m")) sslopt_ciphers = ciphersmedium; if (strstr(httptest->weburl.desturl->schemeopts, "10")) httpversion = HTTPVER_10; else if (strstr(httptest->weburl.desturl->schemeopts, "11")) httpversion = HTTPVER_11; } /* Get any cookies */ load_cookies(); /* Generate the request */ addtobuffer(httprequest, (httptest->weburl.postdata ? "POST " : "GET ")); switch (httpversion) { case HTTPVER_10: addtobuffer(httprequest, (httptest->weburl.proxyurl ? httptest->url : httptest->weburl.desturl->relurl)); addtobuffer(httprequest, " HTTP/1.0\r\n"); break; case HTTPVER_11: /* * Experience shows that even though HTTP/1.1 says you should send the * full URL, some servers (e.g. SunOne App server 7) choke on it. * So just send the good-old relative URL unless we're proxying. */ addtobuffer(httprequest, (httptest->weburl.proxyurl ? httptest->url : httptest->weburl.desturl->relurl)); addtobuffer(httprequest, " HTTP/1.1\r\n"); addtobuffer(httprequest, "Connection: close\r\n"); break; } addtobuffer(httprequest, "Host: "); addtobuffer(httprequest, httptest->weburl.desturl->host); if ((httptest->weburl.desturl->port != 80) && (httptest->weburl.desturl->port != 443)) { char hostporthdr[20]; sprintf(hostporthdr, ":%d", httptest->weburl.desturl->port); addtobuffer(httprequest, hostporthdr); } addtobuffer(httprequest, "\r\n"); if (httptest->weburl.postdata) { char hdr[100]; int contlen = strlen(httptest->weburl.postdata); if (strncmp(httptest->weburl.postdata, "file:", 5) == 0) { /* Load the POST data from a file */ FILE *pf = fopen(httptest->weburl.postdata+5, "r"); if (pf == NULL) { errprintf("Cannot open POST data file %s\n", httptest->weburl.postdata+5); xfree(httptest->weburl.postdata); httptest->weburl.postdata = strdup(""); contlen = 0; } else { struct stat st; if (fstat(fileno(pf), &st) == 0) { xfree(httptest->weburl.postdata); httptest->weburl.postdata = (char *)malloc(st.st_size + 1); fread(httptest->weburl.postdata, 1, st.st_size, pf); *(httptest->weburl.postdata+st.st_size) = '\0'; contlen = st.st_size; } else { errprintf("Cannot stat file %s\n", httptest->weburl.postdata+5); httptest->weburl.postdata = strdup(""); contlen = 0; } fclose(pf); } } addtobuffer(httprequest, "Content-type: "); if (httptest->weburl.postcontenttype) addtobuffer(httprequest, httptest->weburl.postcontenttype); else if ((httptest->weburl.testtype == WEBTEST_SOAP) || (httptest->weburl.testtype == WEBTEST_NOSOAP)) addtobuffer(httprequest, "application/soap+xml; charset=utf-8"); else addtobuffer(httprequest, "application/x-www-form-urlencoded"); addtobuffer(httprequest, "\r\n"); sprintf(hdr, "Content-Length: %d\r\n", contlen); addtobuffer(httprequest, hdr); } { char useragent[100]; void *hinfo; char *browser = NULL; hinfo = hostinfo(t->host->hostname); if (hinfo) browser = xmh_item(hinfo, XMH_BROWSER); if (browser) { sprintf(useragent, "User-Agent: %s\r\n", browser); } else { sprintf(useragent, "User-Agent: Xymon xymonnet/%s\r\n", VERSION); } addtobuffer(httprequest, useragent); } if (httptest->weburl.desturl->auth) { if (strncmp(httptest->weburl.desturl->auth, "CERT:", 5) == 0) { sslopt_clientcert = httptest->weburl.desturl->auth+5; } else { addtobuffer(httprequest, "Authorization: Basic "); addtobuffer(httprequest, base64encode(httptest->weburl.desturl->auth)); addtobuffer(httprequest, "\r\n"); } } if (httptest->weburl.proxyurl && httptest->weburl.proxyurl->auth) { addtobuffer(httprequest, "Proxy-Authorization: Basic "); addtobuffer(httprequest, base64encode(httptest->weburl.proxyurl->auth)); addtobuffer(httprequest, "\r\n"); } for (ck = cookiehead; (ck); ck = ck->next) { int useit = 0; if (ck->tailmatch) { int startpos = strlen(httptest->weburl.desturl->host) - strlen(ck->host); if (startpos > 0) useit = (strcmp(httptest->weburl.desturl->host+startpos, ck->host) == 0); } else useit = (strcmp(httptest->weburl.desturl->host, ck->host) == 0); if (useit) useit = (strncmp(ck->path, httptest->weburl.desturl->relurl, strlen(ck->path)) == 0); if (useit) { if (firstcookie) { addtobuffer(httprequest, "Cookie: "); firstcookie = 0; } addtobuffer(httprequest, ck->name); addtobuffer(httprequest, "="); addtobuffer(httprequest, ck->value); addtobuffer(httprequest, "\r\n"); } } /* Some standard stuff */ addtobuffer(httprequest, "Accept: */*\r\n"); addtobuffer(httprequest, "Pragma: no-cache\r\n"); if ((httptest->weburl.testtype == WEBTEST_SOAP) || (httptest->weburl.testtype == WEBTEST_NOSOAP)) { /* Must provide a SOAPAction header */ addtobuffer(httprequest, "SOAPAction: "); addtobuffer(httprequest, httptest->url); addtobuffer(httprequest, "\r\n"); } /* The final blank line terminates the headers */ addtobuffer(httprequest, "\r\n"); /* Post data goes last */ if (httptest->weburl.postdata) addtobuffer(httprequest, httptest->weburl.postdata); /* Pickup any SSL options the user wants */ if (sslopt_ciphers || (sslopt_version != SSLVERSION_DEFAULT) || sslopt_clientcert){ sslopt = (ssloptions_t *) malloc(sizeof(ssloptions_t)); sslopt->cipherlist = sslopt_ciphers; sslopt->sslversion = sslopt_version; sslopt->clientcert = sslopt_clientcert; } /* Add to TCP test queue */ if (httptest->weburl.proxyurl == NULL) { httptest->tcptest = add_tcp_test(httptest->weburl.desturl->ip, httptest->weburl.desturl->port, httptest->weburl.desturl->scheme, sslopt, t->srcip, t->testspec, t->silenttest, grabstrbuffer(httprequest), httptest, tcp_http_data_callback, tcp_http_final_callback); } else { httptest->tcptest = add_tcp_test(httptest->weburl.proxyurl->ip, httptest->weburl.proxyurl->port, httptest->weburl.proxyurl->scheme, sslopt, t->srcip, t->testspec, t->silenttest, grabstrbuffer(httprequest), httptest, tcp_http_data_callback, tcp_http_final_callback); } } xymon-4.3.7/xymonnet/dns2.c0000664000175000017500000003521311630445564015132 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: dns2.c 6743 2011-09-03 15:44:52Z storner $"; /* * All of the code for parsing DNS responses and formatting these into * text were taken from the "adig.c" source-file included with the * C-ARES 1.2.0 library. This file carries the following copyright * notice, reproduced in full: * * -------------------------------------------------------------------- * Copyright 1998 by the Massachusetts Institute of Technology. * * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without * fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting * documentation, and that the name of M.I.T. not be used in * advertising or publicity pertaining to distribution of the * software without specific, written prior permission. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" * without express or implied warranty. * -------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include #include #include #include "dns2.h" /* Some systems (AIX, HP-UX) dont know the DNS T_SRV record */ #ifndef T_SRV #define T_SRV 33 #endif static char msg[1024]; static const unsigned char *display_question(const unsigned char *aptr, const unsigned char *abuf, int alen, dns_resp_t *response); static const unsigned char *display_rr(const unsigned char *aptr, const unsigned char *abuf, int alen, dns_resp_t *response); static const char *type_name(int type); static const char *class_name(int dnsclass); struct nv { const char *name; int value; }; static const struct nv flags[] = { { "usevc", ARES_FLAG_USEVC }, { "primary", ARES_FLAG_PRIMARY }, { "igntc", ARES_FLAG_IGNTC }, { "norecurse", ARES_FLAG_NORECURSE }, { "stayopen", ARES_FLAG_STAYOPEN }, { "noaliases", ARES_FLAG_NOALIASES } }; static const int nflags = sizeof(flags) / sizeof(flags[0]); static const struct nv classes[] = { { "IN", C_IN }, { "CHAOS", C_CHAOS }, { "HS", C_HS }, { "ANY", C_ANY } }; static const int nclasses = sizeof(classes) / sizeof(classes[0]); static const struct nv types[] = { { "A", T_A }, { "NS", T_NS }, { "MD", T_MD }, { "MF", T_MF }, { "CNAME", T_CNAME }, { "SOA", T_SOA }, { "MB", T_MB }, { "MG", T_MG }, { "MR", T_MR }, { "NULL", T_NULL }, { "WKS", T_WKS }, { "PTR", T_PTR }, { "HINFO", T_HINFO }, { "MINFO", T_MINFO }, { "MX", T_MX }, { "TXT", T_TXT }, { "RP", T_RP }, { "AFSDB", T_AFSDB }, { "X25", T_X25 }, { "ISDN", T_ISDN }, { "RT", T_RT }, { "NSAP", T_NSAP }, { "NSAP_PTR", T_NSAP_PTR }, { "SIG", T_SIG }, { "KEY", T_KEY }, { "PX", T_PX }, { "GPOS", T_GPOS }, { "AAAA", T_AAAA }, { "LOC", T_LOC }, { "SRV", T_SRV }, { "AXFR", T_AXFR }, { "MAILB", T_MAILB }, { "MAILA", T_MAILA }, { "ANY", T_ANY } }; static const int ntypes = sizeof(types) / sizeof(types[0]); static const char *opcodes[] = { "QUERY", "IQUERY", "STATUS", "(reserved)", "NOTIFY", "(unknown)", "(unknown)", "(unknown)", "(unknown)", "UPDATEA", "UPDATED", "UPDATEDA", "UPDATEM", "UPDATEMA", "ZONEINIT", "ZONEREF" }; static const char *rcodes[] = { "NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN", "NOTIMP", "REFUSED", "(unknown)", "(unknown)", "(unknown)", "(unknown)", "(unknown)", "(unknown)", "(unknown)", "(unknown)", "(unknown)", "NOCHANGE" }; void dns_detail_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen) { int id, qr, opcode, aa, tc, rd, ra, rcode; unsigned int qdcount, ancount, nscount, arcount, i; const unsigned char *aptr; dns_resp_t *response = (dns_resp_t *) arg; clearstrbuffer(response->msgbuf); response->msgstatus = status; /* * Display an error message if there was an error, but only stop if * we actually didn't get an answer buffer. */ switch (status) { case ARES_SUCCESS: break; case ARES_ENODATA: addtobuffer(response->msgbuf, "No data returned from server\n"); if (!abuf) return; break; case ARES_EFORMERR: addtobuffer(response->msgbuf, "Server could not understand query\n"); if (!abuf) return; break; case ARES_ESERVFAIL: addtobuffer(response->msgbuf, "Server failed\n"); if (!abuf) return; break; case ARES_ENOTFOUND: addtobuffer(response->msgbuf, "Name not found\n"); if (!abuf) return; break; case ARES_ENOTIMP: addtobuffer(response->msgbuf, "Not implemented\n"); if (!abuf) return; break; case ARES_EREFUSED: addtobuffer(response->msgbuf, "Server refused query\n"); if (!abuf) return; break; case ARES_EBADNAME: addtobuffer(response->msgbuf, "Invalid name in query\n"); if (!abuf) return; break; case ARES_ETIMEOUT: addtobuffer(response->msgbuf, "Timeout\n"); if (!abuf) return; break; case ARES_ECONNREFUSED: addtobuffer(response->msgbuf, "Server unavailable\n"); if (!abuf) return; break; case ARES_ENOMEM: addtobuffer(response->msgbuf, "Out of memory\n"); if (!abuf) return; break; case ARES_EDESTRUCTION: addtobuffer(response->msgbuf, "Timeout (channel destroyed)\n"); if (!abuf) return; break; default: addtobuffer(response->msgbuf, "Undocumented ARES return code\n"); if (!abuf) return; break; } /* Won't happen, but check anyway, for safety. */ if (alen < HFIXEDSZ) return; /* Parse the answer header. */ id = DNS_HEADER_QID(abuf); qr = DNS_HEADER_QR(abuf); opcode = DNS_HEADER_OPCODE(abuf); aa = DNS_HEADER_AA(abuf); tc = DNS_HEADER_TC(abuf); rd = DNS_HEADER_RD(abuf); ra = DNS_HEADER_RA(abuf); rcode = DNS_HEADER_RCODE(abuf); qdcount = DNS_HEADER_QDCOUNT(abuf); ancount = DNS_HEADER_ANCOUNT(abuf); nscount = DNS_HEADER_NSCOUNT(abuf); arcount = DNS_HEADER_ARCOUNT(abuf); /* Display the answer header. */ sprintf(msg, "id: %d\n", id); addtobuffer(response->msgbuf, msg); sprintf(msg, "flags: %s%s%s%s%s\n", qr ? "qr " : "", aa ? "aa " : "", tc ? "tc " : "", rd ? "rd " : "", ra ? "ra " : ""); addtobuffer(response->msgbuf, msg); sprintf(msg, "opcode: %s\n", opcodes[opcode]); addtobuffer(response->msgbuf, msg); sprintf(msg, "rcode: %s\n", rcodes[rcode]); addtobuffer(response->msgbuf, msg); /* Display the questions. */ addtobuffer(response->msgbuf, "Questions:\n"); aptr = abuf + HFIXEDSZ; for (i = 0; i < qdcount; i++) { aptr = display_question(aptr, abuf, alen, response); if (aptr == NULL) return; } /* Display the answers. */ addtobuffer(response->msgbuf, "Answers:\n"); for (i = 0; i < ancount; i++) { aptr = display_rr(aptr, abuf, alen, response); if (aptr == NULL) return; } /* Display the NS records. */ addtobuffer(response->msgbuf, "NS records:\n"); for (i = 0; i < nscount; i++) { aptr = display_rr(aptr, abuf, alen, response); if (aptr == NULL) return; } /* Display the additional records. */ addtobuffer(response->msgbuf, "Additional records:\n"); for (i = 0; i < arcount; i++) { aptr = display_rr(aptr, abuf, alen, response); if (aptr == NULL) return; } return; } static const unsigned char *display_question(const unsigned char *aptr, const unsigned char *abuf, int alen, dns_resp_t *response) { char *name; int type, dnsclass, status; long len; /* Parse the question name. */ status = ares_expand_name(aptr, abuf, alen, &name, &len); if (status != ARES_SUCCESS) return NULL; aptr += len; /* Make sure there's enough data after the name for the fixed part * of the question. */ if (aptr + QFIXEDSZ > abuf + alen) { xfree(name); return NULL; } /* Parse the question type and class. */ type = DNS_QUESTION_TYPE(aptr); dnsclass = DNS_QUESTION_CLASS(aptr); aptr += QFIXEDSZ; /* * Display the question, in a format sort of similar to how we will * display RRs. */ sprintf(msg, "\t%-15s.\t", name); addtobuffer(response->msgbuf, msg); if (dnsclass != C_IN) { sprintf(msg, "\t%s", class_name(dnsclass)); addtobuffer(response->msgbuf, msg); } sprintf(msg, "\t%s\n", type_name(type)); addtobuffer(response->msgbuf, msg); xfree(name); return aptr; } static const unsigned char *display_rr(const unsigned char *aptr, const unsigned char *abuf, int alen, dns_resp_t *response) { const unsigned char *p; char *name; int type, dnsclass, ttl, dlen, status; long len; struct in_addr addr; /* Parse the RR name. */ status = ares_expand_name(aptr, abuf, alen, &name, &len); if (status != ARES_SUCCESS) return NULL; aptr += len; /* Make sure there is enough data after the RR name for the fixed * part of the RR. */ if (aptr + RRFIXEDSZ > abuf + alen) { xfree(name); return NULL; } /* Parse the fixed part of the RR, and advance to the RR data field. */ type = DNS_RR_TYPE(aptr); dnsclass = DNS_RR_CLASS(aptr); ttl = DNS_RR_TTL(aptr); dlen = DNS_RR_LEN(aptr); aptr += RRFIXEDSZ; if (aptr + dlen > abuf + alen) { xfree(name); return NULL; } /* Display the RR name, class, and type. */ sprintf(msg, "\t%-15s.\t%d", name, ttl); addtobuffer(response->msgbuf, msg); if (dnsclass != C_IN) { sprintf(msg, "\t%s", class_name(dnsclass)); addtobuffer(response->msgbuf, msg); } sprintf(msg, "\t%s", type_name(type)); addtobuffer(response->msgbuf, msg); xfree(name); /* Display the RR data. Don't touch aptr. */ switch (type) { case T_CNAME: case T_MB: case T_MD: case T_MF: case T_MG: case T_MR: case T_NS: case T_PTR: /* For these types, the RR data is just a domain name. */ status = ares_expand_name(aptr, abuf, alen, &name, &len); if (status != ARES_SUCCESS) return NULL; sprintf(msg, "\t%s.", name); addtobuffer(response->msgbuf, msg); xfree(name); break; case T_HINFO: /* The RR data is two length-counted character strings. */ p = aptr; len = *p; if (p + len + 1 > aptr + dlen) return NULL; sprintf(msg, "\t%.*s", (int) len, p + 1); addtobuffer(response->msgbuf, msg); p += len + 1; len = *p; if (p + len + 1 > aptr + dlen) return NULL; sprintf(msg, "\t%.*s", (int) len, p + 1); addtobuffer(response->msgbuf, msg); break; case T_MINFO: /* The RR data is two domain names. */ p = aptr; status = ares_expand_name(p, abuf, alen, &name, &len); if (status != ARES_SUCCESS) return NULL; sprintf(msg, "\t%s.", name); addtobuffer(response->msgbuf, msg); xfree(name); p += len; status = ares_expand_name(p, abuf, alen, &name, &len); if (status != ARES_SUCCESS) return NULL; sprintf(msg, "\t%s.", name); addtobuffer(response->msgbuf, msg); xfree(name); break; case T_MX: /* The RR data is two bytes giving a preference ordering, and then a domain name. */ if (dlen < 2) return NULL; sprintf(msg, "\t%d", (aptr[0] << 8) | aptr[1]); addtobuffer(response->msgbuf, msg); status = ares_expand_name(aptr + 2, abuf, alen, &name, &len); if (status != ARES_SUCCESS) return NULL; sprintf(msg, "\t%s.", name); addtobuffer(response->msgbuf, msg); xfree(name); break; case T_SOA: /* * The RR data is two domain names and then five four-byte * numbers giving the serial number and some timeouts. */ p = aptr; status = ares_expand_name(p, abuf, alen, &name, &len); if (status != ARES_SUCCESS) return NULL; sprintf(msg, "\t%s.\n", name); addtobuffer(response->msgbuf, msg); xfree(name); p += len; status = ares_expand_name(p, abuf, alen, &name, &len); if (status != ARES_SUCCESS) return NULL; sprintf(msg, "\t\t\t\t\t\t%s.\n", name); addtobuffer(response->msgbuf, msg); xfree(name); p += len; if (p + 20 > aptr + dlen) return NULL; sprintf(msg, "\t\t\t\t\t\t( %d %d %d %d %d )", (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3], (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7], (p[8] << 24) | (p[9] << 16) | (p[10] << 8) | p[11], (p[12] << 24) | (p[13] << 16) | (p[14] << 8) | p[15], (p[16] << 24) | (p[17] << 16) | (p[18] << 8) | p[19]); addtobuffer(response->msgbuf, msg); break; case T_TXT: /* The RR data is one or more length-counted character strings. */ p = aptr; while (p < aptr + dlen) { len = *p; if (p + len + 1 > aptr + dlen) return NULL; sprintf(msg, "\t%.*s", (int)len, p + 1); addtobuffer(response->msgbuf, msg); p += len + 1; } break; case T_A: /* The RR data is a four-byte Internet address. */ if (dlen != 4) return NULL; memcpy(&addr, aptr, sizeof(struct in_addr)); sprintf(msg, "\t%s", inet_ntoa(addr)); addtobuffer(response->msgbuf, msg); break; case T_WKS: /* Not implemented yet */ break; case T_SRV: /* * The RR data is three two-byte numbers representing the * priority, weight, and port, followed by a domain name. */ sprintf(msg, "\t%d", DNS__16BIT(aptr)); addtobuffer(response->msgbuf, msg); sprintf(msg, " %d", DNS__16BIT(aptr + 2)); addtobuffer(response->msgbuf, msg); sprintf(msg, " %d", DNS__16BIT(aptr + 4)); addtobuffer(response->msgbuf, msg); status = ares_expand_name(aptr + 6, abuf, alen, &name, &len); if (status != ARES_SUCCESS) return NULL; sprintf(msg, "\t%s.", name); addtobuffer(response->msgbuf, msg); xfree(name); break; default: sprintf(msg, "\t[Unknown RR; cannot parse]"); addtobuffer(response->msgbuf, msg); } sprintf(msg, "\n"); addtobuffer(response->msgbuf, msg); return aptr + dlen; } static const char *type_name(int type) { int i; for (i = 0; i < ntypes; i++) { if (types[i].value == type) return types[i].name; } return "(unknown)"; } static const char *class_name(int dnsclass) { int i; for (i = 0; i < nclasses; i++) { if (classes[i].value == dnsclass) return classes[i].name; } return "(unknown)"; } int dns_name_type(char *name) { int i; for (i = 0; i < ntypes; i++) { if (strcasecmp(types[i].name, name) == 0) return types[i].value; } return T_A; } xymon-4.3.7/xymonnet/xymonnet.10000664000175000017500000005512311671641417016065 0ustar henrikhenrik.TH XYMONNET 1 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME xymonnet \- Xymon network test tool .SH SYNOPSIS .B "xymonnet [--ping|--noping] [--timeout=N] [options] [hostname] [hostname] .br (See the OPTIONS section for a description of the available command-line options). .SH DESCRIPTION .I xymonnet(1) handles the network tests of hosts defined in the Xymon configuration file, hosts.cfg. It is normally run at regular intervals by .I xymonlaunch(8) via an entry in the .I tasks.cfg(5) file. xymonnet does all of the normal tests of TCP-based network services (telnet, ftp, ssh, smtp, pop, imap ....) - i.e. all of the services listed in protocols.cfg. For these tests, a completely new and very speedy service- checker has been implemented. xymonnet has built-in support for testing SSL-enabled protocols, e.g. imaps, pop3s, nntps, telnets, if SSL-support was enabled when configuring xymonnet. The full list of known tests is found in the .I protocols.cfg(5) file in $XYMONHOME/etc/protocols.cfg. In addition, it implements the "dns" and "dig" tests for testing DNS servers. xymonnet also implements a check for NTP servers - this test is called "ntp". If you want to use it, you must define the NTPDATE environment variable to point at the location of your .I ntpdate(1) program. Note: xymonnet performs the connectivity test (ping) based on the hostname, unless the host is tagged with "testip" or the "--dns=ip" option is used. So the target of the connectivity test can be determined by your /etc/hosts file or DNS. By default, all servers are tested - if XYMONNETWORK is set via .I xymonserver.cfg(5) then only the hosts marked as belonging to this network are tested. If the command-line includes one or more hostnames, then only those servers are tested. .SH GENERAL OPTIONS .IP --timeout=N Determines the timeout (in seconds) for each service that is tested. For TCP tests (those from XYMONNETSVCS), if the connection to the service does not succeed within N seconds, the service is reported as being down. For HTTP tests, this is the absolute limit for the entire request to the webserver (the time needed to connect to the server, plus the time it takes the server to respond to the request). Default: 10 seconds .IP --conntimeout=N This option is deprecated, and will be ignored. Use the --timeout option instead. .IP --cmdtimeout=N This option sets a timeout for the external commands used for testing of NTP and RPC services, and to perform traceroute. .IP --concurrency=N Determines the number of network tests that run in parallel. Default is operating system dependent, but will usually be 256. If xymonnet begins to complain about not being able to get a "socket", try running xymonnet with a lower value like 50 or 100. .IP "--dns-timeout=N (default: 30 seconds)" xymonnet will timeout all DNS lookups after N seconds. Any pending DNS lookups are regarded as failed, i.e. the network tests that depend on this DNS lookup will report an error. .br Note: If you use the --no-ares option, timeout of DNS lookups cannot be controlled by xymonnet. .IP --dns-max-all=N Same as "--dns-timeout=N". The "--dns-max-all" option is deprecated and should not be used. .IP --dns=[ip|only|standard] Determines how xymonnet finds the IP adresses of the hosts to test. By default (the "standard"), xymonnet does a DNS lookup of the hostname to determine the IP address, unless the host has the "testip" tag, or the DNS lookup fails. .br With "--dns=only" xymonnet will ONLY do the DNS lookup; it it fails, then all services on that host will be reported as being down. .br With "--dns=ip" xymonnet will never do a DNS lookup; it will use the IP adresse specified in hosts.cfg for the tests. Thus, this setting is equivalent to having the "testip" tag on all hosts. Note that http tests will ignore this setting and still perform a DNS lookup for the hostname given in the URL; see the "xymonnet tags for HTTP tests" section in .I hosts.cfg(5) .IP --no-ares Disable the ARES resolver built into xymonnet. This makes xymonnet resolve hostnames using your system resolver function. You should only use this as a last resort if xymonnet cannot resolve the hostnames you use in the normal way (via DNS or /etc/hosts). One reason for using this would be if you need to resolve hostnames via NIS/NIS+ (a.k.a. Yellow Pages). .br The system resolver function does not provide a mechanism for controlling timeouts of the hostname lookups, so if your DNS or NIS server is down, xymonnet can take a very long time to run. The --dns-timeout option is effectively disabled when using this option. .IP --dnslog=FILENAME Log failed hostname lookups to the file FILENAME. FILENAME should be a full pathname. .IP --report[=COLUMNNAME] With this option, xymonnet will send a status message with details of how many hosts were processed, how many tests were generated, any errors that occurred during the run, and some timing statistics. The default columnname is "xymonnet". .IP --test-untagged When using the XYMONNETWORK environment variable to test only hosts on a particular network segment, xymonnet will ignore hosts that do not have any "NET:x" tag. So only hosts that have a NET:$XYMONNETWORK tag will be tested. .br With this option, hosts with no NET: tag are included in the test, so that all hosts that either have a matching NET: tag, or no NET: tag at all are tested. .IP --frequenttestlimit=N Used with the .I xymonnet-again.sh(1) Xymon extension. This option determines how long failed tests remain in the frequent-test queue. The default is 1800 seconds (30 minutes). .IP --timelimit=N Causes xymonnet to generate a warning if the run-time of xymonnet exceeds N seconds. By default N is set to the value of TASKSLEEP, so a warning triggers if the network tests cannot complete in the time given for one cycle of the xymonnet task. Apart from the warning, this option has no effect, i.e. it will not terminate xymonnet prematurely. So to eliminate any such warnings, use this option with a very high value of N. .IP --huge=N Warn if the response from a TCP test is more than N bytes. If you see from the xymonnet status report that you are transferring large amounts of data for your tests, you can enable this option to see which tests have large replies. .br Default: 0 (disabled). .IP --validity=N Make the test results valid for N minutes before they go purple. By default test results are valid for 30 minutes; if you run xymonnet less often than that, the results will go purple before the next run of xymonnet. This option lets you change how long the status is valid. .IP --source-ip=IPADDRESS On multi-homed hosts, this option can be used to explicitly select the source IP address used for the network tests. "IPADDRESS" must be a valid IP-address on the host running xymonnet. .IP --loadhostsfromxymond Instead of reading the hosts.cfg file, xymonnet will load the hosts.cfg configuration from the xymond daemon. This eliminates the need for reading the hosts.cfg, and if you have xymond and xymonnet running on different hosts, it also eliminates the need for copying the hosts.cfg file between systems. Note that the "netinclude" option in hosts.cfg is ignored when this option is enabled. .SH OPTIONS FOR TESTS OF THE SIMPLE TCP SERVICES .IP --checkresponse[=COLOR] When testing well-known services (e.g. FTP, SSH, SMTP, POP-2, POP-3, IMAP, NNTP and rsync), xymonnet will look for a valid service-specific "OK" response. If another reponse is seen, this will cause the test to report a warning (yellow) status. Without this option, the response from the service is ignored. .br The optional color-name is used to select a color other than yellow for the status message when the response is wrong. E.g. "--checkresponse=red" will cause a "red" status message to be sent when the service does not respond as expected. .IP --no-flags By default, xymonnet sends some extra information in the status messages, called "flags". These are used by xymongen e.g. to pick different icons for reversed tests when generating the Xymon webpages. This option makes xymonnet omit these flags from the status messages. .IP --shuffle By default, TCP tests run roughly in the order that the hosts are listed in the hosts.cfg file. If you have many tests for one server, this may result in an exceptionally large load when Xymon is testing it because Xymon will perform a lot of tests at the same time. To avoid this, the \fB--shuffle\fR option reorders the sequence of tests so they are spread randomly across all of the servers tested. .SH OPTIONS FOR THE PING TEST Note: xymonnet uses the program defined by the FPING environment to execute ping-tests - by default, that is the .I xymonping(1) utility. See .I xymonserver.cfg(5) for a description of how to customize this, e.g. if you need to run it with "sudo" or a similar tool. .IP --ping Enables xymonnet's ping test. The column name used for ping test results is defined by the PINGCOLUMN environment variable in .I xymonserver.cfg(5). .br If not specifed, xymonnet uses the CONNTEST environment variable to determine if it should perform the ping test or not. So if you prefer to use another tool to implement ping checks, either set the CONNTEST environment variable to false, or run xymonnet with the "--noping". .IP --noping Disable the connectivity test. .IP "--trace" .IP "--notrace" Enable/disable the use of traceroute when a ping-test fails. Performing a traceroute for failed ping tests is a slow operation, so the default is not to do any traceroute, unless it is requested on a per-host basis via the "trace" tag in the .I hosts.cfg(5) entry for each host. The "--trace" option changes this, so the default becomes to run traceroute on all hosts where the ping test fails; you can then disable it on specific hosts by putting a "notrace" tag on the host-entry. .IP --ping-tasks=N Spread the task of pinging the hosts over N processes. If you have a very large number of hosts the time it takes to ping all of them can be substantial, even with the use of tools like fping or xymonping that ping many hosts in parallel. This option causes xymonnet to start N separate ping processes, the IP's that are being ping'ed will be divided evenly between these processes. .SH OPTIONS FOR HTTP (WEB) TESTS .IP --content=CONTENTTESTNAME Determines the name of the column Xymon displays for content checks. The default is "content". If you have used the "cont.sh" or "cont2.sh" scripts earlier, you may want to use "--content=cont" to report content checks using the same test name as these scripts do. .IP --bb-proxy-syntax Adhere to the Big Brother syntax for a URL, which allows specifying a HTTP proxy as part of a URL. See \fB"HTTP Testing via proxy"\fR in the .I hosts.cfg(5) file for details. Beginning with Xymon 4.3.0, this behaviour is disabled by default since URL's that include other URL's are now much more common. This option restores the old Big Brother-compatible behaviour. .SH OPTIONS FOR SSL CERTIFICATE TESTS .IP --ssl=SSLCERTTESTNAME Determines the name of the column Xymon displays for the SSL certificate checks. The default is "sslcert". .IP --no-ssl Disables reporting of the SSL certificate check. .IP --sslwarn=N .IP --sslalarm=N Determines the number of days before an SSL certificate expires, where xymonnet will generate a warning or alarm status for the SSL certificate column. .IP --sslbits=N Enables checking that the encryption supported by the SSL protocol uses an encryption key of at least N bits. E.g. to trigger an alert if your SSL-enabled website supports less than 128 bits of encryption, use "--sslbits=128". Note: This can be enabled on a per-host basis using the "sslbits=N" setting in .I hosts.cfg(5) .SH DEBUGGING OPTIONS .IP --no-update Don't send any status updates to the Xymon server. Instead, all messages are dumped to stdout. .IP --timing Causes xymonnet to collect information about the time spent in different parts of the program. The information is printed on stdout just before the program ends. Note that this information is also included in the status report sent with the "--report" option. .IP --debug Dumps a bunch of status about the tests as they progress to stdout. .IP --dump[=before|=after|=both] Dumps internal memory structures before and/or after the tests have executed. .SH INFORMATIONAL OPTIONS .IP "--help or -?" Provide a summary of available command-line options. .IP "--version" Prints the version number of xymonnet .IP --services Dump the list of defined TCP services xymonnet knows how to test. Do not run any tests. .SH USING COOKIES IN WEB TESTS If the file $XYMONHOME/etc/cookies exist, cookies will be read from this file and sent along with the HTTP requests when checking websites. This file is in the Netscape Cookie format, see http://www.netscape.com/newsref/std/cookie_spec.html for details on this format. The .I curl(1) utility can output a file in this format if run with the "--cookie-jar FILENAME" option. .SH ABOUT SSL CERTIFICATE CHECKS When xymonnet tests services that use SSL- or TLS-based protocols, it will check that the server certificate has not expired. This check happens automatically for https (secure web), pop3s, imaps, nntps and all other SSL-enabled services (except ldap, see LDAP TESTS below). All certificates found for a host are reported in one status message. Note: On most systems, the end-date of the certificate is limited to Jan 19th, 2038. If your certificate is valid after this date, xymonnet will report it as valid only until Jan 19, 2038. This is due to limitations in your operating system C library. See http://en.wikipedia.org/wiki/2038_problem . .SH LDAP TESTS ldap testing can be done in two ways. If you just put an "ldap" or "ldaps" tag in hosts.cfg, a simple test is performed that just verifies that it is possible to establish a connection to the port running the ldap service (389 for ldap, 636 for ldaps). Instead you can put an LDAP URI in hosts.cfg. This will cause xymonnet to initiate a full-blown LDAP session with the server, and do an LDAP search for the objects defined by the URI. This requires that xymonnet was built with LDAP support, and relies on an existing LDAP library to be installed. It has been tested with OpenLDAP 2.0.26 (from Red Hat 9) and 2.1.22. The Solaris 8 system ldap library has also been confirmed to work for un-encrypted (plain ldap) access. The format of LDAP URI's is defined in RFC 2255. LDAP URLs look like this: .nf \fBldap://\fP\fIhostport\fP\fB/\fP\fIdn\fP[\fB?\fP\fIattrs\fP[\fB?\fP\fIscope\fP[\fB?\fP\fIfilter\fP[\fB?\fP\fIexts\fP]]]] where: \fIhostport\fP is a host name with an optional ":portnumber" \fIdn\fP is the search base \fIattrs\fP is a comma separated list of attributes to request \fIscope\fP is one of these three strings: base one sub (default=base) \fIfilter\fP is filter \fIexts\fP are recognized set of LDAP and/or API extensions. Example: ldap://ldap.example.net/dc=example,dc=net?cn,sn?sub?(cn=*) .fi .sp All "bind" operations to LDAP servers use simple authentication. Kerberos and SASL are not supported. If your LDAP server requires a username/password, use the "ldaplogin" tag to specify this, cf. .I hosts.cfg(5) If no username/password information is provided, an anonymous bind will be attempted. SSL support requires both a client library and an LDAP server that support LDAPv3; it uses the LDAP "STARTTLS" protocol request after establishing a connection to the standard (non-encrypted) LDAP port (usually port 389). It has only been tested with OpenSSL 2.x, and probably will not work with any other LDAP library. The older LDAPv2 experimental method of tunnelling normal LDAP traffic through an SSL connection - ldaps, running on port 636 - is not supported, unless someone can explain how to get the OpenLDAP library to support it. This method was never formally described in an RFC, and implementations of it are non-standard. For a discussion of the various ways of running encrypted ldap, see .br http://www.openldap.org/lists/openldap-software/200305/msg00079.html .br http://www.openldap.org/lists/openldap-software/200305/msg00084.html .br http://www.openldap.org/lists/openldap-software/200201/msg00042.html .br http://www.openldap.org/lists/openldap-software/200206/msg00387.html When testing LDAP URI's, all of the communications are handled by the ldap library. Therefore, it is not possible to obtain the SSL certificate used by the LDAP server, and it will not show up in the "sslcert" column. .SH USING MULTIPLE NETWORK TEST SYSTEMS If you have more than one system running network tests - e.g. if your network is separated by firewalls - then is is problematic to maintain multiple hosts.cfg files for each of the systems. xymonnet supports the NET:location tag in .I hosts.cfg(5) to distinguish between hosts that should be tested from different network locations. If you set the environment variable XYMONNETWORK e.g. to "dmz" before running xymonnet, then it will only test hosts that have a "NET:dmz" tag in hosts.cfg. This allows you to keep all of your hosts in the same hosts.cfg file, but test different sets of hosts by different systems running xymonnet. .SH XYMONNET INTERNALS xymonnet first reads the protocols.cfg file to see which network tests are defined. It then scans the hosts.cfg file, and collects information about the TCP service tests that need to be tested. It picks out only the tests that were listed in the protocols.cfg file, plus the "dns", "dig" and "ntp" tests. It then runs two tasks in parallel: First, a separate process is started to run the "xymonping" tool for the connectivity tests. While xymonping is busy doing the "ping" checks, xymonnet runs all of the TCP-based network tests. All of the TCP-based service checks are handled by a connection tester written specifically for this purpose. It uses only standard Unix-style network programming, but relies on the Unix "select(2)" system-call to handle many simultaneous connections happening in parallel. Exactly how many parallel connections are being used depends on your operating system - the default is FD_SETSIZE/4, which amounts to 256 on many Unix systems. You can choose the number of concurrent connections with the "--concurrency=N" option to xymonnet. Connection attempts timeout after 10 seconds - this can be changed with the "--timeout=N" option. Both of these settings play a part in deciding how long the testing takes. A conservative estimate for doing N TCP tests is: (1 + (N / concurrency)) * timeout In real life it will probably be less, as the above formula is for every test to require a timeout. Since the most normal use of Xymon is to check for services that are active, you should have a lot less timeouts. The "ntp" and "rpcinfo" checks rely on external programs to do each test. .SH ENVIRONMENT VARIABLES .IP XYMONNETWORK Defines the network segment where xymonnet is currently running. This is used to filter out only the entries in the .I hosts.cfg(5) file that have a matching "NET:LOCATION" tag, and execute the tests for only those hosts. .IP MAXMSGSPERCOMBO Defines the maximum number of status messages that can be sent in one combo message. Default is 0 - no limit. .br In practice, the maximum size of a single Xymon message sets a limit - the default value for the maximum message size is 32 KB, but that will easily accomodate 100 status messages per transmission. So if you want to experiment with this setting, I suggest starting with a value of 10. .IP SLEEPBETWEENMSGS Defines a a delay (in microseconds) after each message is transmitted to the Xymon server. The default is 0, i.e. send the messages as fast as possible. This gives your Xymon server some time to process the message before the next message comes in. Depending on the speed of your Xymon server, it may be necessary to set this value to half a second or even 1 or 2 seconds. Note that the value is specified in MICROseconds, so to define a delay of half a second, this must be set to the value "500000"; a delay of 1 second is achieved by setting this to "1000000" (one million). .IP FPING Command used to run the .I xymonping(1) utility. Used by xymonnet for connectivity (ping) testing. See .I xymonserver.cfg(5) for more information about how to customize the program that is executed to do ping tests. .IP TRACEROUTE Location of the .I traceroute(8) utility, or an equivalent tool e.g. .I mtr(8). Optionally used when a connectivity test fails to pinpoint the network location that is causing the failure. .IP NTPDATE Location of the .I ntpdate(1) utility. Used by xymonnet when checking the "ntp" service. .IP RPCINFO Location of the .I rpcinfo(8) utility. Used by xymonnet for the "rpc" service checks. .SH FILES .IP "~/server/etc/protocols.cfg" This file contains definitions of TCP services that xymonnet can test. Definitions for a default set of common services is built into xymonnet, but these can be overridden or supplemented by defining services in the protocols.cfg file. See .I protocols.cfg(5) for details on this file. .IP "$XYMONHOME/etc/netrc - authentication data for password-protected webs" If you have password-protected sites, you can put the usernames and passwords for these here. They will then get picked up automatically when running your network tests. This works for web-sites that use the "Basic" authentication scheme in HTTP. See .I ftp(1) for details - a sample entry would look like this .br machine www.acme.com login fred password Wilma1 .br Note that the machine-name must be the name you use in the http://machinename/ URL setting - it need not be the one you use for the system-name in Xymon. .sp .IP "$XYMONHOME/etc/cookies" This file may contain website cookies, in the Netscape HTTP Cookie format. If a website requires a static cookie to be present in order for the check to complete, then you can add this cookie to this file, and it will be sent along with the HTTP request. To get the cookies into this file, you can use the "curl --cookie-jar FILE" to request the URL that sets the cookie. .sp .IP "$XYMONTMP/*.status - test status summary" Each time xymonnet runs, if any tests fail (i.e. they result in a red status) then they will be listed in a file name TESTNAME.[LOCATION].status. The LOCATION part may be null. This file is used to determine how long the failure has lasted, which in turn decides if this test should be included in the tests done by .I xymonnet-again.sh(1) .br It is also used internally by xymonnet when determining the color for tests that use the "badconn" or "badTESTNAME" tags. .sp .IP $XYMONTMP/frequenttests.[LOCATION] This file contains the hostnames of those hosts that should be retested by the .I xymonnet-again.sh(1) test tool. It is updated only by xymonnet during the normal runs, and read by xymonnet-again.sh. .SH "SEE ALSO" hosts.cfg(5), protocols.cfg(5), xymonserver.cfg(5), xymonping(1), curl(1), ftp(1), fping(1), ntpdate(1), rpcinfo(8) xymon-4.3.7/xymonnet/ldaptest.c0000664000175000017500000004031111615341300016061 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* This is used to implement the testing of an LDAP service. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: ldaptest.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include #include "libxymon.h" #include "xymonnet.h" #include "ldaptest.h" #define XYMON_LDAP_OK 0 #define XYMON_LDAP_INITFAIL 10 #define XYMON_LDAP_TLSFAIL 11 #define XYMON_LDAP_BINDFAIL 20 #define XYMON_LDAP_TIMEOUT 30 #define XYMON_LDAP_SEARCHFAILED 40 char *ldap_library_version = NULL; static volatile int connect_timeout = 0; int init_ldap_library(void) { #ifdef XYMON_LDAP char versionstring[100]; /* Doesnt really do anything except define the version-number string */ sprintf(versionstring, "%s %d", LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION); ldap_library_version = strdup(versionstring); #endif return 0; } void shutdown_ldap_library(void) { #ifdef XYMON_LDAP /* No-op for LDAP */ #endif } int add_ldap_test(testitem_t *t) { #ifdef XYMON_LDAP testitem_t *basecheck; ldap_data_t *req; LDAPURLDesc *ludp; char *urltotest; int badurl; basecheck = (testitem_t *)t->privdata; /* * t->testspec containts the full testspec * We need to remove any URL-encoding. */ urltotest = urlunescape(t->testspec); badurl = (ldap_url_parse(urltotest, &ludp) != 0); /* Allocate the private data and initialize it */ t->privdata = (void *) calloc(1, sizeof(ldap_data_t)); req = (ldap_data_t *) t->privdata; req->ldapdesc = (void *) ludp; req->usetls = (strncmp(urltotest, "ldaps:", 6) == 0); #ifdef XYMON_LDAP_USESTARTTLS if (req->usetls && (ludp->lud_port == LDAPS_PORT)) { dbgprintf("Forcing port %d for ldaps with STARTTLS\n", LDAP_PORT ); ludp->lud_port = LDAP_PORT; } #endif req->ldapstatus = 0; req->output = NULL; req->ldapcolor = -1; req->faileddeps = NULL; req->duration.tv_sec = req->duration.tv_nsec = 0; req->certinfo = NULL; req->certexpires = 0; req->skiptest = 0; if (badurl) { errprintf("Invalid LDAP URL %s\n", t->testspec); req->skiptest = 1; req->ldapstatus = XYMON_LDAP_BINDFAIL; req->output = "Cannot parse LDAP URL"; } /* * At this point, the plain TCP checks have already run. * So we know from the test found in t->privdata whether * the LDAP port is open. * If it is not open, then dont run this check. */ if (basecheck->open == 0) { /* Cannot connect to LDAP port. */ req->skiptest = 1; req->ldapstatus = XYMON_LDAP_BINDFAIL; req->output = "Cannot connect to server"; } #endif return 0; } static void ldap_alarmhandler(int signum) { signal(signum, SIG_DFL); connect_timeout = 1; } void run_ldap_tests(service_t *ldaptest, int sslcertcheck, int querytimeout) { #ifdef XYMON_LDAP ldap_data_t *req; testitem_t *t; struct timespec starttime; struct timespec endtime; /* Pick a sensible default for the timeout setting */ if (querytimeout == 0) querytimeout = 30; for (t = ldaptest->items; (t); t = t->next) { LDAPURLDesc *ludp; LDAP *ld; int rc, finished; int msgID = -1; struct timeval ldaptimeout; struct timeval openldaptimeout; LDAPMessage *result; LDAPMessage *e; strbuffer_t *response; char buf[MAX_LINE_LEN]; req = (ldap_data_t *) t->privdata; if (req->skiptest) continue; ludp = (LDAPURLDesc *) req->ldapdesc; getntimer(&starttime); /* Initiate session with the LDAP server */ dbgprintf("Initiating LDAP session for host %s port %d\n", ludp->lud_host, ludp->lud_port); if( (ld = ldap_init(ludp->lud_host, ludp->lud_port)) == NULL ) { dbgprintf("ldap_init failed\n"); req->ldapstatus = XYMON_LDAP_INITFAIL; continue; } /* * There is apparently no standard way of defining a network * timeout for the initial connection setup. */ #if (LDAP_VENDOR == OpenLDAP) && defined(LDAP_OPT_NETWORK_TIMEOUT) /* * OpenLDAP has an undocumented ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tv) */ openldaptimeout.tv_sec = querytimeout; openldaptimeout.tv_usec = 0; ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &openldaptimeout); #else /* * So using an alarm() to interrupt any pending operations * seems to be the least insane way of doing this. * * Note that we must do this right after ldap_init(), as * any operation on the session handle (ld) may trigger the * network connection to be established. */ connect_timeout = 0; signal(SIGALRM, ldap_alarmhandler); alarm(querytimeout); #endif /* * This is completely undocumented in the OpenLDAP docs. * But apparently it is documented in * http://www.ietf.org/proceedings/99jul/I-D/draft-ietf-ldapext-ldap-c-api-03.txt * * Both of these routines appear in the file * from OpenLDAP 2.1.22. Their use to enable TLS has * been deciphered from the ldapsearch() utility * sourcecode. * * According to Manon Goo , recent (Jan. 2005) * OpenLDAP implementations refuse to talk LDAPv2. */ #ifdef LDAP_OPT_PROTOCOL_VERSION { int protocol = LDAP_VERSION3; dbgprintf("Attempting to select LDAPv3\n"); if ((rc = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol)) != LDAP_SUCCESS) { dbgprintf("Failed to select LDAPv3, trying LDAPv2\n"); protocol = LDAP_VERSION2; if ((rc = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol)) != LDAP_SUCCESS) { req->output = strdup(ldap_err2string(rc)); req->ldapstatus = XYMON_LDAP_TLSFAIL; } continue; } } #endif #ifdef XYMON_LDAP_USESTARTTLS if (req->usetls) { dbgprintf("Trying to enable TLS for session\n"); if ((rc = ldap_start_tls_s(ld, NULL, NULL)) != LDAP_SUCCESS) { dbgprintf("ldap_start_tls failed\n"); req->output = strdup(ldap_err2string(rc)); req->ldapstatus = XYMON_LDAP_TLSFAIL; continue; } } #endif if (!connect_timeout) { msgID = ldap_simple_bind(ld, (t->host->ldapuser ? t->host->ldapuser : ""), (t->host->ldappasswd ? t->host->ldappasswd : "")); } /* Cancel any pending alarms */ alarm(0); signal(SIGALRM, SIG_DFL); /* Did we connect? */ if (connect_timeout || (msgID == -1)) { req->ldapstatus = XYMON_LDAP_BINDFAIL; req->output = "Cannot connect to server"; continue; } /* Wait for bind to complete */ rc = 0; finished = 0; ldaptimeout.tv_sec = querytimeout; ldaptimeout.tv_usec = 0L; while( ! finished ) { int rc2; rc = ldap_result(ld, msgID, LDAP_MSG_ONE, &ldaptimeout, &result); dbgprintf("ldap_result returned %d for ldap_simple_bind()\n", rc); if(rc == -1) { finished = 1; req->ldapstatus = XYMON_LDAP_BINDFAIL; if (result == NULL) { errprintf("LDAP library problem - NULL result returned\n"); req->output = strdup("LDAP BIND failed\n"); } else { rc2 = ldap_result2error(ld, result, 1); req->output = strdup(ldap_err2string(rc2)); } ldap_unbind(ld); } else if (rc == 0) { finished = 1; req->ldapstatus = XYMON_LDAP_BINDFAIL; req->output = strdup("Connection timeout"); ldap_unbind(ld); } else if( rc > 0 ) { finished = 1; if (result == NULL) { errprintf("LDAP library problem - got a NULL resultcode for status %d\n", rc); req->ldapstatus = XYMON_LDAP_BINDFAIL; req->output = strdup("LDAP library problem: ldap_result2error returned a NULL result for status %d\n"); ldap_unbind(ld); } else { rc2 = ldap_result2error(ld, result, 1); if(rc2 != LDAP_SUCCESS) { req->ldapstatus = XYMON_LDAP_BINDFAIL; req->output = strdup(ldap_err2string(rc)); ldap_unbind(ld); } } } } /* ... while() */ /* We're done connecting. If something went wrong, go to next query. */ if (req->ldapstatus != 0) continue; /* Now do the search. With a timeout */ ldaptimeout.tv_sec = querytimeout; ldaptimeout.tv_usec = 0L; rc = ldap_search_st(ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter, ludp->lud_attrs, 0, &ldaptimeout, &result); if(rc == LDAP_TIMEOUT) { req->ldapstatus = XYMON_LDAP_TIMEOUT; req->output = strdup(ldap_err2string(rc)); ldap_unbind(ld); continue; } if( rc != LDAP_SUCCESS ) { req->ldapstatus = XYMON_LDAP_SEARCHFAILED; req->output = strdup(ldap_err2string(rc)); ldap_unbind(ld); continue; } getntimer(&endtime); response = newstrbuffer(0); sprintf(buf, "Searching LDAP for %s yields %d results:\n\n", t->testspec, ldap_count_entries(ld, result)); addtobuffer(response, buf); for(e = ldap_first_entry(ld, result); (e != NULL); e = ldap_next_entry(ld, e) ) { char *dn; BerElement *ber; char *attribute; char **vals; dn = ldap_get_dn(ld, e); sprintf(buf, "DN: %s\n", dn); addtobuffer(response, buf); /* Addtributes and values */ for (attribute = ldap_first_attribute(ld, e, &ber); (attribute != NULL); attribute = ldap_next_attribute(ld, e, ber) ) { if ((vals = ldap_get_values(ld, e, attribute)) != NULL) { int i; for(i = 0; (vals[i] != NULL); i++) { sprintf(buf, "\t%s: %s\n", attribute, vals[i]); addtobuffer(response, buf); } } /* Free memory used to store values */ ldap_value_free(vals); } /* Free memory used to store attribute */ ldap_memfree(attribute); ldap_memfree(dn); if (ber != NULL) ber_free(ber, 0); addtobuffer(response, "\n"); } req->ldapstatus = XYMON_LDAP_OK; req->output = grabstrbuffer(response); tvdiff(&starttime, &endtime, &req->duration); ldap_msgfree(result); ldap_unbind(ld); ldap_free_urldesc(ludp); } #endif } static int statuscolor(testedhost_t *host, int ldapstatus) { switch (ldapstatus) { case XYMON_LDAP_OK: return COL_GREEN; case XYMON_LDAP_INITFAIL: case XYMON_LDAP_TLSFAIL: case XYMON_LDAP_BINDFAIL: case XYMON_LDAP_TIMEOUT: return COL_RED; case XYMON_LDAP_SEARCHFAILED: return (host->ldapsearchfailyellow ? COL_YELLOW : COL_RED); } errprintf("Unknown ldapstaus value %d\n", ldapstatus); return COL_RED; } void send_ldap_results(service_t *ldaptest, testedhost_t *host, char *nonetpage, int failgoesclear) { testitem_t *t; int color = -1; char msgline[4096]; char *nopagename; int nopage = 0; testitem_t *ldap1 = host->firstldap; int anydown = 0; char *svcname; if (ldap1 == NULL) return; svcname = strdup(ldaptest->testname); if (ldaptest->namelen) svcname[ldaptest->namelen] = '\0'; /* Check if this service is a NOPAGENET service. */ nopagename = (char *) malloc(strlen(svcname)+3); sprintf(nopagename, ",%s,", svcname); nopage = (strstr(nonetpage, svcname) != NULL); xfree(nopagename); dbgprintf("Calc ldap color host %s : ", host->hostname); for (t=host->firstldap; (t && (t->host == host)); t = t->next) { ldap_data_t *req = (ldap_data_t *) t->privdata; req->ldapcolor = statuscolor(host, req->ldapstatus); if (req->ldapcolor == COL_RED) anydown++; /* Dialup hosts and dialup tests report red as clear */ if ((req->ldapcolor != COL_GREEN) && (host->dialup || t->dialup)) req->ldapcolor = COL_CLEAR; /* If ping failed, report CLEAR unless alwaystrue */ if ( ((req->ldapcolor == COL_RED) || (req->ldapcolor == COL_YELLOW)) && /* Test failed */ (host->downcount > 0) && /* The ping check did fail */ (!host->noping && !host->noconn) && /* We are doing a ping test */ (failgoesclear) && (!t->alwaystrue) ) /* No "~testname" flag */ { req->ldapcolor = COL_CLEAR; } /* If test we depend on has failed, report CLEAR unless alwaystrue */ if ( ((req->ldapcolor == COL_RED) || (req->ldapcolor == COL_YELLOW)) && /* Test failed */ failgoesclear && !t->alwaystrue ) /* No "~testname" flag */ { char *faileddeps = deptest_failed(host, t->service->testname); if (faileddeps) { req->ldapcolor = COL_CLEAR; req->faileddeps = strdup(faileddeps); } } dbgprintf("%s(%s) ", t->testspec, colorname(req->ldapcolor)); if (req->ldapcolor > color) color = req->ldapcolor; } if (anydown) ldap1->downcount++; else ldap1->downcount = 0; /* Handle the "badtest" stuff for ldap tests */ if ((color == COL_RED) && (ldap1->downcount < ldap1->badtest[2])) { if (ldap1->downcount >= ldap1->badtest[1]) color = COL_YELLOW; else if (ldap1->downcount >= ldap1->badtest[0]) color = COL_CLEAR; else color = COL_GREEN; } if (nopage && (color == COL_RED)) color = COL_YELLOW; dbgprintf(" --> %s\n", colorname(color)); /* Send off the ldap status report */ init_status(color); sprintf(msgline, "status+%d %s.%s %s %s", validity, commafy(host->hostname), svcname, colorname(color), timestamp); addtostatus(msgline); for (t=host->firstldap; (t && (t->host == host)); t = t->next) { ldap_data_t *req = (ldap_data_t *) t->privdata; sprintf(msgline, "\n&%s %s - %s\n\n", colorname(req->ldapcolor), t->testspec, ((req->ldapcolor != COL_GREEN) ? "failed" : "OK")); addtostatus(msgline); if (req->output) { addtostatus(req->output); addtostatus("\n\n"); } if (req->faileddeps) addtostatus(req->faileddeps); sprintf(msgline, "\nSeconds: %u.%02u\n", (unsigned int)req->duration.tv_sec, (unsigned int)req->duration.tv_nsec / 10000000); addtostatus(msgline); } addtostatus("\n"); finish_status(); xfree(svcname); } void show_ldap_test_results(service_t *ldaptest) { ldap_data_t *req; testitem_t *t; for (t = ldaptest->items; (t); t = t->next) { req = (ldap_data_t *) t->privdata; printf("URL : %s\n", t->testspec); printf("Time spent : %u.%02u\n", (unsigned int)req->duration.tv_sec, (unsigned int)req->duration.tv_nsec / 10000000); printf("LDAP output:\n%s\n", textornull(req->output)); printf("------------------------------------------------------\n"); } } #ifdef STANDALONE /* These are dummy vars needed by stuff in util.c */ hostlist_t *hosthead = NULL; link_t *linkhead = NULL; link_t null_link = { "", "", "", NULL }; char *deptest_failed(testedhost_t *host, char *testname) { return NULL; } int main(int argc, char *argv[]) { testitem_t item; testedhost_t host; service_t ldapservice; int argi = 1; int ldapdebug = 0; while ((argi < argc) && (strncmp(argv[argi], "--", 2) == 0)) { if (strcmp(argv[argi], "--debug") == 0) { debug = 1; } else if (strncmp(argv[argi], "--ldapdebug=", strlen("--ldapdebug=")) == 0) { char *p = strchr(argv[argi], '='); ldapdebug = atoi(p+1); } argi++; } /* For testing, dont crash in sendmsg when no XYMSRV defined */ dontsendmessages = 1; if (xgetenv("XYMSRV") == NULL) putenv("XYMSRV=127.0.0.1"); memset(&item, 0, sizeof(item)); memset(&host, 0, sizeof(host)); memset(&ldapservice, 0, sizeof(ldapservice)); ldapservice.portnum = 389; ldapservice.testname = "ldap"; ldapservice.namelen = strlen(ldapservice.testname); ldapservice.items = &item; item.host = &host; item.service = &ldapservice; item.dialup = item.reverse = item.silenttest = item.alwaystrue = 0; item.testspec = urlunescape(argv[argi]); host.firstldap = &item; host.hostname = "ldaptest.xymon"; host.ldapuser = NULL; host.ldappasswd = NULL; init_ldap_library(); if (ldapdebug) { #if defined(LBER_OPT_DEBUG_LEVEL) && defined(LDAP_OPT_DEBUG_LEVEL) ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &ldapdebug); ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &ldapdebug); #else printf("LDAP library does not support change of debug level\n"); #endif } if (add_ldap_test(&item) == 0) { run_ldap_tests(&ldapservice, 0, 10); combo_start(); send_ldap_results(&ldapservice, &host, "", 0); combo_end(); } shutdown_ldap_library(); return 0; } #endif xymon-4.3.7/xymonnet/dns.c0000664000175000017500000002154411630612042015035 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: dns.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #include #include #include #include #include #include #include #include "libxymon.h" #include #include #include "dns.h" #include "dns2.h" #ifdef HPUX /* Doesn't have hstrerror */ char *hstrerror(int err) { return ""; } #endif static ares_channel mychannel; static int pending_dns_count = 0; int use_ares_lookup = 1; int max_dns_per_run = 0; int dns_stats_total = 0; int dns_stats_success = 0; int dns_stats_failed = 0; int dns_stats_lookups = 0; int dnstimeout = 30; FILE *dnsfaillog = NULL; typedef struct dnsitem_t { char *name; struct in_addr addr; struct dnsitem_t *next; int failed; struct timespec resolvetime; } dnsitem_t; static void * dnscache; static void dns_init(void) { static int initdone = 0; if (initdone) return; dnscache = xtreeNew(strcasecmp); if (use_ares_lookup) { int status = ares_init(&mychannel); if (status != ARES_SUCCESS) { errprintf("Cannot initialize ARES resolver, using standard\n"); errprintf("ARES error was: '%s'\n", ares_strerror(status)); use_ares_lookup = 0; } } initdone = 1; } static char *find_dnscache(char *hostname) { struct in_addr inp; xtreePos_t handle; dnsitem_t *dnsc; dns_init(); if (inet_aton(hostname, &inp) != 0) { /* It is an IP, so just use that */ return hostname; } /* In the cache ? */ handle = xtreeFind(dnscache, hostname); if (handle == xtreeEnd(dnscache)) return NULL; dnsc = (dnsitem_t *)xtreeData(dnscache, handle); return inet_ntoa(dnsc->addr); } static void dns_simple_callback(void *arg, int status, int timeout, struct hostent *hent) { struct dnsitem_t *dnsc = (dnsitem_t *)arg; struct timespec etime; getntimer(&etime); tvdiff(&dnsc->resolvetime, &etime, &dnsc->resolvetime); pending_dns_count--; if (status == ARES_SUCCESS) { memcpy(&dnsc->addr, *(hent->h_addr_list), sizeof(dnsc->addr)); dbgprintf("Got DNS result for host %s : %s\n", dnsc->name, inet_ntoa(dnsc->addr)); dns_stats_success++; } else { memset(&dnsc->addr, 0, sizeof(dnsc->addr)); dbgprintf("DNS lookup failed for %s - status %s (%d)\n", dnsc->name, ares_strerror(status), status); dnsc->failed = 1; dns_stats_failed++; if (dnsfaillog) { fprintf(dnsfaillog, "DNS lookup failed for %s - status %s (%d)\n", dnsc->name, ares_strerror(status), status); } } } static void dns_ares_queue_run(ares_channel channel) { int nfds, selres; fd_set read_fds, write_fds; struct timeval *tvp, tv; int loops = 0; if ((channel == mychannel) && (!pending_dns_count)) return; dbgprintf("Processing %d DNS lookups with ARES\n", pending_dns_count); while (1) { /* Loop continues until all requests handled (or time out) */ loops++; FD_ZERO(&read_fds); FD_ZERO(&write_fds); nfds = ares_fds(channel, &read_fds, &write_fds); if (nfds == 0) { dbgprintf("Finished ARES queue after loop %d\n", loops); break; /* No pending requests */ } /* * Determine how long select() waits before processing timeouts. * "dnstimeout" is the user configurable option which is * the absolute maximum timeout value. However, ARES also * has built in timeouts - these are defined at ares_init() * using ARES_OPT_TIMEOUTMS and ARES_OPT_TRIES (default * 5 secs / 4 tries = 20 second timeout). */ tv.tv_sec = dnstimeout; tv.tv_usec = 0; tvp = ares_timeout(channel, &tv, &tv); selres = select(nfds, &read_fds, &write_fds, NULL, tvp); ares_process(channel, &read_fds, &write_fds); } if (pending_dns_count > 0) { errprintf("Odd ... pending_dns_count=%d after a queue run\n", pending_dns_count); pending_dns_count = 0; } } void add_host_to_dns_queue(char *hostname) { dnsitem_t *dnsc; dns_init(); dns_stats_total++; if (find_dnscache(hostname)) return; /* Already resolved */ if (max_dns_per_run && (pending_dns_count >= max_dns_per_run)) { /* Limit the number of requests we do per run */ dns_ares_queue_run(mychannel); } /* New hostname */ dnsc = (dnsitem_t *)calloc(1, sizeof(dnsitem_t)); dbgprintf("Adding hostname '%s' to resolver queue\n", hostname); pending_dns_count++; dnsc->name = strdup(hostname); getntimer(&dnsc->resolvetime); xtreeAdd(dnscache, dnsc->name, dnsc); if (use_ares_lookup) { ares_gethostbyname(mychannel, hostname, AF_INET, dns_simple_callback, dnsc); } else { /* * This uses the normal resolver functions, but * sends the result through the same processing * functions as used when we use ARES. */ struct hostent *hent; int status; hent = gethostbyname(hostname); if (hent) { status = ARES_SUCCESS; dns_stats_success++; } else { status = ARES_ENOTFOUND; dns_stats_failed++; dbgprintf("gethostbyname() failed with err %d: %s\n", h_errno, hstrerror(h_errno)); if (dnsfaillog) { fprintf(dnsfaillog, "Hostname lookup failed for %s - status %s (%d)\n", hostname, hstrerror(h_errno), h_errno); } } /* Send the result to our normal callback function */ dns_simple_callback(dnsc, status, 0, hent); } } void add_url_to_dns_queue(char *url) { weburl_t weburl; dns_init(); decode_url(url, &weburl); if (weburl.proxyurl) { if (weburl.proxyurl->parseerror) return; add_host_to_dns_queue(weburl.proxyurl->host); } else { if (weburl.desturl->parseerror) return; add_host_to_dns_queue(weburl.desturl->host); } } void flush_dnsqueue(void) { dns_init(); dns_ares_queue_run(mychannel); } char *dnsresolve(char *hostname) { char *result; dns_init(); if (hostname == NULL) return NULL; flush_dnsqueue(); dns_stats_lookups++; result = find_dnscache(hostname); if (result == NULL) { errprintf("dnsresolve - internal error, name '%s' not in cache\n", hostname); return NULL; } if (strcmp(result, "0.0.0.0") == 0) return NULL; return result; } int dns_test_server(char *serverip, char *hostname, strbuffer_t *banner) { ares_channel channel; struct ares_options options; struct in_addr serveraddr; int status; struct timespec starttime, endtime; struct timespec *tspent; char msg[100]; char *tspec, *tst; dns_resp_t *responses = NULL; dns_resp_t *walk = NULL; int i; dns_init(); if (inet_aton(serverip, &serveraddr) == 0) { errprintf("dns_test_server: serverip '%s' not a valid IP\n", serverip); return 1; } options.flags = ARES_FLAG_NOCHECKRESP; options.servers = &serveraddr; options.nservers = 1; options.timeout = dnstimeout; status = ares_init_options(&channel, &options, (ARES_OPT_FLAGS | ARES_OPT_SERVERS | ARES_OPT_TIMEOUT)); if (status != ARES_SUCCESS) { errprintf("Could not initialize ares channel: %s\n", ares_strerror(status)); return 1; } tspec = strdup(hostname); getntimer(&starttime); tst = strtok(tspec, ","); do { dns_resp_t *newtest = (dns_resp_t *)malloc(sizeof(dns_resp_t)); char *p, *tlookup; int atype = T_A; newtest->msgbuf = newstrbuffer(0); newtest->next = NULL; if (responses == NULL) responses = newtest; else walk->next = newtest; walk = newtest; p = strchr(tst, ':'); tlookup = (p ? p+1 : tst); if (p) { *p = '\0'; atype = dns_name_type(tst); *p = ':'; } dbgprintf("ares_search: tlookup='%s', class=%d, type=%d\n", tlookup, C_IN, atype); ares_search(channel, tlookup, C_IN, atype, dns_detail_callback, newtest); tst = strtok(NULL, ","); } while (tst); dns_ares_queue_run(channel); getntimer(&endtime); tspent = tvdiff(&starttime, &endtime, NULL); clearstrbuffer(banner); status = ARES_SUCCESS; strcpy(tspec, hostname); tst = strtok(tspec, ","); for (walk = responses, i=1; (walk); walk = walk->next, i++) { /* Print an identifying line if more than one query */ if ((walk != responses) || (walk->next)) { sprintf(msg, "\n*** DNS lookup of '%s' ***\n", tst); addtobuffer(banner, msg); } addtostrbuffer(banner, walk->msgbuf); if (walk->msgstatus != ARES_SUCCESS) status = walk->msgstatus; xfree(walk->msgbuf); tst = strtok(NULL, ","); } xfree(tspec); sprintf(msg, "\nSeconds: %u.%03u\n", (unsigned int)tspent->tv_sec, (unsigned int)tspent->tv_nsec/1000000); addtobuffer(banner, msg); ares_destroy(channel); return (status != ARES_SUCCESS); } xymon-4.3.7/xymonnet/httptest.h0000664000175000017500000000214411615341300016127 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* This is used to implement the testing of a HTTP service. */ /* */ /* Copyright (C) 2003-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ #ifndef __HTTPTESTT_H_ #define __HTTPTEST_H_ extern int tcp_http_data_callback(unsigned char *buf, unsigned int len, void *priv); extern void tcp_http_final_callback(void *priv); extern void add_http_test(testitem_t *t); #endif xymon-4.3.7/xymonnet/xymonping.c0000664000175000017500000003501211615341300016273 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* This is an implementation of a fast "ping" program, for use with Xymon. */ /* */ /* Copyright (C) 2006-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: xymonping.c 6712 2011-07-31 21:01:52Z storner $"; #include "config.h" #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libxymon.h" #define PING_PACKET_SIZE 64 #define PING_MINIMUM_SIZE ICMP_MINLEN #define SENDLIMIT 100 typedef struct hostdata_t { int id; struct sockaddr_in addr; /* Address of host to ping */ int received; /* how many ICMP_ECHO replies we've got */ struct timespec rtt_total; struct hostdata_t *next; } hostdata_t; hostdata_t *hosthead = NULL; int hostcount = 0; hostdata_t **hosts = NULL; /* Array of pointers to the hostdata records, for fast acces via ID */ int myicmpid; int senddelay = (1000000 / 50); /* Delay between sending packets, in microseconds */ /* This routine more or less taken from the "fping" source. Apparently by W. Stevens (public domain) */ int calc_icmp_checksum(unsigned short *pkt, int pktlen) { unsigned short result; long sum = 0; unsigned short extrabyte = 0; while (pktlen > 1) { sum += *pkt++; pktlen -= 2; } if (pktlen == 1) { *(unsigned char *)(&extrabyte) = *(unsigned char *)pkt; sum += extrabyte; } sum = ( sum >> 16 ) + ( sum & 0xffff ); /* add hi 16 to low 16 */ sum += ( sum >> 16 ); /* add carry */ result = ~sum; /* ones-complement, truncate*/ return result; } char *nextip(int argc, char *argv[], FILE *fd) { static int argi = 0; static int cmdmode = 0; static char buf[4096]; if (argi == 0) { /* Check if there are any command-line IP's */ struct sockaddr_in ina; for (argi=1; ((argi < argc) && (inet_aton(argv[argi], &ina.sin_addr) == 0)); argi++) ; cmdmode = (argi < argc); } if (cmdmode) { /* Skip any options in-between the IP's */ while ((argi < argc) && (*(argv[argi]) == '-')) argi++; if (argi < argc) { argi++; return argv[argi-1]; } } else { if (fgets(buf, sizeof(buf), fd)) { char *p; p = strchr(buf, '\n'); if (p) *p = '\0'; return buf; } } return NULL; } void load_ips(int argc, char *argv[], FILE *fd) { char *l; hostdata_t *tail = NULL; hostdata_t *walk; int i; while ((l = nextip(argc, argv, fd)) != NULL) { hostdata_t *newitem; if (strlen(l) == 0) continue; newitem = (hostdata_t *)calloc(1, sizeof(hostdata_t)); newitem->addr.sin_family = AF_INET; newitem->addr.sin_port = 0; if (inet_aton(l, &newitem->addr.sin_addr) == 0) { errprintf("Dropping %s - not an IP\n", l); free(newitem); continue; } if (tail) { tail->next = newitem; } else { hosthead = newitem; } hostcount++; tail = newitem; } /* Setup the table of hostdata records */ hosts = (hostdata_t **)malloc((hostcount+1) * sizeof(hostdata_t *)); for (i=0, walk=hosthead; (walk); walk=walk->next, i++) hosts[i] = walk; hosts[hostcount] = NULL; } /* This is the data we send with each ping packet */ typedef struct pingdata_t { int id; /* ID for the host this belongs to */ struct timespec timesent; /* time we sent this ping */ } pingdata_t; int send_ping(int sock, int startidx, int minresponses) { static unsigned char buffer[PING_PACKET_SIZE]; struct icmp *icmphdr; struct pingdata_t *pingdata; struct timezone tz; int sentbytes; int idx = startidx; /* * Sends one ICMP "echo-request" packet. * * Note: A first attempt at this kept sending packets until * we got an EWOULDBLOCK or a send error. This causes incoming * responses to be dropped. */ /* Skip the hosts that have already delivered a ping response */ while ((idx < hostcount) && (hosts[idx]->received >= minresponses)) idx++; if (idx >= hostcount) return hostcount; /* * Dont flood the net. * By enforcing a brief sleep here, we force a delay * between sending packets. It is easiest to do before sending * a packet, because if done after the send completes, then * it affects the RTT measurements. */ if (senddelay) usleep(senddelay); /* Build the packet and send it */ memset(buffer, 0, PING_PACKET_SIZE); icmphdr = (struct icmp *)buffer; icmphdr->icmp_type = ICMP_ECHO; icmphdr->icmp_code = 0; icmphdr->icmp_cksum = 0; icmphdr->icmp_seq = htons(idx+1); /* So we can map response to our hosts */ icmphdr->icmp_id = htons(myicmpid); pingdata = (struct pingdata_t *)(buffer + sizeof(struct icmp)); pingdata->id = idx; getntimer(&pingdata->timesent); icmphdr->icmp_cksum = calc_icmp_checksum((unsigned short *)buffer, PING_PACKET_SIZE); sentbytes = sendto(sock, buffer, PING_PACKET_SIZE, 0, (struct sockaddr *) &hosts[idx]->addr, sizeof(struct sockaddr_in)); if (sentbytes == -1) { if (errno != EWOULDBLOCK) { errprintf("Failed to send ICMP packet: %s\n", strerror(errno)); idx++; /* To avoid looping indefinitely trying to send to this host */ } } else if (sentbytes == PING_PACKET_SIZE) { /* We managed to send a ping! */ if (debug) { dbgprintf("Sent a ping to %s: index=%d, id=%d\n", inet_ntoa(hosts[idx]->addr.sin_addr), idx, myicmpid); } idx++; } return idx; } int get_response(int sock) { static unsigned char buffer[4096]; struct sockaddr_in addr; int n, pktcount; unsigned int addrlen; struct ip *iphdr; int iphdrlen; struct icmp *icmphdr; struct pingdata_t *pingdata; int hostidx; struct timespec rtt; /* * Read responses from the network. * We know (because select() told us) that there is some data * to read. To avoid losing packets, read as much as we can. */ pktcount = 0; do { addrlen = sizeof(addr); n = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&addr, &addrlen); if (n < 0) { if (errno != EWOULDBLOCK) errprintf("Failed to receive packet: %s\n", strerror(errno)); continue; } getntimer(&rtt); /* Check the IP header - we need to have at least enough bytes for an ICMP header. */ iphdr = (struct ip *)buffer; iphdrlen = (iphdr->ip_hl << 2); /* IP header always aligned on 4-byte boundary */ if (n < (iphdrlen + PING_MINIMUM_SIZE)) { errprintf("Short packet ignored\n"); continue; } /* * Get the ICMP header, and our host index which is the sequence number. * Thanks to "fping" for this neat way of matching requests and responses. */ icmphdr = (struct icmp *)(buffer + iphdrlen); hostidx = ntohs(icmphdr->icmp_seq)-1; if (debug) { dbgprintf("Got packet from %s: type=%d, index=%d, id=%d\n", inet_ntoa(addr.sin_addr), icmphdr->icmp_type, icmphdr->icmp_id, hostidx); } switch (icmphdr->icmp_type) { case ICMP_ECHOREPLY: if (ntohs(icmphdr->icmp_id) != myicmpid) { /* Not one of our packets. Happens if someone else does ping simultaneously. */ break; } if ((hostidx >= 0) && (hostidx < hostcount)) { /* Looks like one of our packets succeeded. */ pktcount++; hosts[hostidx]->received += 1; pingdata = (struct pingdata_t *)(buffer + iphdrlen + sizeof(struct icmp)); /* Calculate the round-trip time. */ rtt.tv_sec -= pingdata->timesent.tv_sec; rtt.tv_nsec -= pingdata->timesent.tv_nsec; if (rtt.tv_nsec < 0) { rtt.tv_sec--; rtt.tv_nsec += 1000000000; } /* Add RTT to the total time */ hosts[hostidx]->rtt_total.tv_sec += rtt.tv_sec; hosts[hostidx]->rtt_total.tv_nsec += rtt.tv_nsec; if (hosts[hostidx]->rtt_total.tv_nsec >= 1000000000) { hosts[hostidx]->rtt_total.tv_sec++; hosts[hostidx]->rtt_total.tv_nsec -= 1000000000; } } break; case ICMP_ECHO: /* Sometimes, we see our own packets going out (if we ping ourselves) */ break; case ICMP_UNREACH: /* We simply ignore these. Hosts get retried until we succeed, then reported as down. */ break; case ICMP_REDIRECT: /* Ignored - the IP stack handles this. */ break; default: /* Shouldn't happen */ errprintf("Got a packet that wasnt a reply - type %d\n", icmphdr->icmp_type); break; } } while (n > 0); return pktcount; } int count_pending(int minresponses) { int result = 0; int idx; /* Counts how many hosts we haven't seen a reply from yet. */ for (idx = 0; (idx < hostcount); idx++) if (hosts[idx]->received < minresponses) result++; return result; } void show_results(void) { int idx; unsigned long rtt_usecs; /* Big enough for 2147 seconds - larger than we will ever see */ /* * Print out the results. Format is identical to "fping -Ae" so we can use * it directly in Xymon without changing the xymonnet code. */ for (idx = 0; (idx < hostcount); idx++) { if (hosts[idx]->received > 0) { printf("%s is alive", inet_ntoa(hosts[idx]->addr.sin_addr)); rtt_usecs = (hosts[idx]->rtt_total.tv_sec*1000000 + (hosts[idx]->rtt_total.tv_nsec / 1000)) / hosts[idx]->received; if (rtt_usecs >= 1000) { printf(" (%lu ms)\n", rtt_usecs / 1000); } else { printf(" (0.%02lu ms)\n", (rtt_usecs / 10)); } } else { printf("%s is unreachable\n", inet_ntoa(hosts[idx]->addr.sin_addr)); } } } int main(int argc, char *argv[]) { struct protoent *proto; int protonumber, pingsocket = -1, sockerr = 0, binderr = 0; int argi, sendidx, pending, minresponses = 1, tries = 3, timeout = 5; char *srcip = NULL; struct sockaddr_in src_addr; /* Immediately drop all root privileges. */ drop_root(); for (argi = 1; (argi < argc); argi++) { if (strncmp(argv[argi], "--retries=", 10) == 0) { char *delim = strchr(argv[argi], '='); tries = 1 + atoi(delim+1); } else if (strncmp(argv[argi], "--timeout=", 10) == 0) { char *delim = strchr(argv[argi], '='); timeout = atoi(delim+1); } else if (strncmp(argv[argi], "--responses=", 11) == 0) { char *delim = strchr(argv[argi], '='); minresponses = atoi(delim+1); } else if (strncmp(argv[argi], "--source=", 9) == 0) { char *delim = strchr(argv[argi], '='); srcip = strdup(delim+1); } else if (strncmp(argv[argi], "--max-pps=", 10) == 0) { char *delim = strchr(argv[argi], '='); senddelay = (1000000 / atoi(delim+1)); } else if (strncmp(argv[argi], "--debug", 7) == 0) { char *delim = strchr(argv[argi], '='); debug = 1; if (delim) set_debugfile(delim+1, 0); } else if (strcmp(argv[argi], "--help") == 0) { if (pingsocket >= 0) close(pingsocket); fprintf(stderr, "%s [--retries=N] [--timeout=N] [--responses=N] [--max-pps=N] [--source=IP]\n", argv[0]); return 0; } /* fping compatibility options */ else if (strncmp(argv[argi], "-i", 2) == 0) { char *val = argv[argi] + 2; if (isdigit((int) *val)) senddelay = atoi(val); } else if (strncmp(argv[argi], "-r", 2) == 0) { char *val = argv[argi] + 2; if (isdigit((int) *val)) tries = atoi(val); } else if (strncmp(argv[argi], "-S", 2) == 0) { char *val = argv[argi] + 2; srcip = strdup(val); } else if (*(argv[argi]) == '-') { /* Ignore everything else - for fping compatibility */ } } proto = getprotobyname("icmp"); protonumber = (proto ? proto->p_proto : 1); if (srcip != NULL) { /* Setup the source address */ src_addr.sin_family = AF_INET; src_addr.sin_addr.s_addr = inet_addr(srcip); src_addr.sin_port = htons(0); } /* Get a raw socket. Requires root privs. */ get_root(); pingsocket = socket(AF_INET, SOCK_RAW, protonumber); sockerr = errno; if ((pingsocket != -1) && (srcip != NULL)) { /* Bind to a specific source address */ if (bind(pingsocket, (struct sockaddr *) &src_addr, sizeof(src_addr)) == -1) binderr = errno; } drop_root(); if (pingsocket == -1) { errprintf("Cannot get RAW socket: %s\n", strerror(sockerr)); if (sockerr == EPERM) errprintf("This program must be installed suid-root\n"); return 3; } if ((srcip != NULL) && (binderr != 0)) { errprintf("Cannot bind to source address %s: %s\nUsing default address\n", srcip, strerror(binderr)); } /* Set the socket non-blocking - we use select() exclusively */ fcntl(pingsocket, F_SETFL, O_NONBLOCK); load_ips(argc, argv, stdin); pending = count_pending(minresponses); while (tries) { int sendnow = SENDLIMIT; time_t cutoff = getcurrenttime(NULL) + timeout + 1; sendidx = 0; /* Change this on each iteration, so we dont mix packets from each round of pings */ myicmpid = ((getpid()+tries) & 0x7FFF); /* Do one loop over the hosts we havent had responses from yet. */ while (pending > 0) { fd_set readfds, writefds; struct timeval selecttmo; int n; FD_ZERO(&readfds); FD_ZERO(&writefds); FD_SET(pingsocket, &readfds); if (sendnow && (sendidx < hostcount)) FD_SET(pingsocket, &writefds); selecttmo.tv_sec = 0; selecttmo.tv_usec = 100000; n = select(pingsocket+1, &readfds, &writefds, NULL, &selecttmo); if (n < 0) { if (errno == EINTR) continue; errprintf("select failed: %s\n", strerror(errno)); return 4; } else if (n == 0) { /* Time out */ sendnow = SENDLIMIT; } else { if (sendnow && FD_ISSET(pingsocket, &writefds)) { /* OK to send */ sendidx = send_ping(pingsocket, sendidx, minresponses); sendnow--; /* Adjust the cutoff time, so we wait TIMEOUT seconds for a response */ cutoff = getcurrenttime(NULL) + timeout + 1; } if (FD_ISSET(pingsocket, &readfds)) { /* Grab the replies */ int count = get_response(pingsocket); pending -= count; sendnow += count; if (sendnow > SENDLIMIT) sendnow = SENDLIMIT; } } /* See if we have hit the timeout */ if ((getcurrenttime(NULL) >= cutoff) && (sendidx >= hostcount)) { pending = 0; } } tries--; pending = count_pending(minresponses); if (pending == 0) { /* Have got responses for all hosts - we're done */ tries = 0; } } close(pingsocket); show_results(); return 0; } xymon-4.3.7/xymonnet/protocols.cfg.50000664000175000017500000000506211671641417016767 0ustar henrikhenrik.TH PROTOCOLS.CFG 5 "Version 4.3.7: 13 Dec 2011" "Xymon" .SH NAME protocols.cfg \- Configuration of TCP network services .SH SYNOPSIS .BR $XYMONHOME/etc/protocols.cfg .SH DESCRIPTION \fBprotocols.cfg\fR contains definitions of how .I xymonnet(1) should test a TCP-based network service (i.e. all common network services except HTTP and DNS). For each service, a simple dialogue can be defined to check that the service is functioning normally, and optional flags determine if the service has e.g. a banner or requires SSL- or telnet-style handshaking to be tested. .SH FILE FORMAT protocols.cfg is a text file. A simple service definition for the SMTP service would be this: .br .sp [smtp] .br send "mail\\r\\nquit\\r\\n" .br expect "220" .br options banner .br .sp This defines a service called "smtp". When the connection is first established, xymonnet will send the string "mail\\r\\nquit\\r\\n" to the service. It will then expect a response beginning with "220". Any data returned by the service (a so-called "banner") will be recorded and included in the status message. .sp The full set of commands available for the protocols.cfg file are: .IP "[NAME]" Define the name of the TCP service, which will also be the column-name in the resulting display on the test status. If multiple tests share a common definition (e.g. ssh, ssh1 and ssh2 are tested identically), you may list these in a single "[ssh|ssh1|ssh2]" definition, separating each service-name with a pipe-sign. .IP "send STRING" .IP "expect STRING" Defines the strings to send to the service after a connection is established, and the response that is expected. Either of these may be omitted, in which case .I xymonnet(1) will simply not send any data, or match a response against anything. The send- and expect-strings use standard escaping for non-printable characters. "\\r" represents a carriage-return (ASCII 13), "\\n" represents a line-feed (ASCII 10), "\\t" represents a TAB (ASCII 8). Binary data is input as "\\xNN" with NN being the hexadecimal value of the byte. .IP "port NUMBER" Define the default TCP port-number for this service. If no portnumber is defined, .I xymonnet(1) will attempt to lookup the portnumber in the standard /etc/services file. .IP "options option1[,option2][,option3]" Defines test options. The possible options are .br banner - include received data in the status message .br ssl - service uses SSL so perform an SSL handshake .br telnet - service is telnet, so exchange telnet options .SH FILES .BR $XYMONHOME/etc/protocols.cfg .SH "SEE ALSO" xymonnet(1) xymon-4.3.7/xymonnet/Makefile0000664000175000017500000000672411623444202015553 0ustar henrikhenrik# Xymon - xymonnet Makefile # ARES settings. c-ares is included DNSFLAGS = -I./c-ares PROGRAMS = xymonnet xymonping beastat EXTENSIONS = xymonnet-again.sh SNMPPROGRAMS = xymon-snmpcollect DBGTOOLS = contest ifeq ($(DOSNMP),yes) PROGRAMS += $(SNMPPROGRAMS) endif all: $(PROGRAMS) $(EXTENSIONS) $(DBGTOOLS) NETTESTOBJS = xymonnet.o contest.o httptest.o httpresult.o ldaptest.o dns.o dns2.o httpcookies.o PINGTESTOBJS = xymonping.o BEASTATOBJS = beastat.o xymonnet: $(NETTESTOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(NETTESTOBJS) ../lib/libxymon.a libcares.a $(LDAPLIBS) $(SSLLIBS) $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) xymonping: $(PINGTESTOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) -o $@ $(PINGTESTOBJS) ../lib/libxymon.a $(NETLIBS) $(LIBRTDEF) xymonnet.o: xymonnet.c $(CC) $(CFLAGS) $(SSLFLAGS) $(LDAPFLAGS) $(SSLINCDIR) $(LDAPINCDIR) -c -o $@ xymonnet.c ldaptest.o: ldaptest.c $(CC) $(CFLAGS) $(LDAPFLAGS) $(LDAPINCDIR) -c -o $@ ldaptest.c httptest.o: httptest.c $(CC) $(CFLAGS) $(SSLFLAGS) $(SSLINCDIR) -c -o $@ httptest.c httpresult.o: httpresult.c $(CC) $(CFLAGS) $(SSLFLAGS) $(SSLINCDIR) -c -o $@ httpresult.c contest.o: contest.c $(CC) $(CFLAGS) $(SSLFLAGS) $(SSLINCDIR) -c -o $@ contest.c dns.o: libcares.a dns.c $(CC) $(CFLAGS) $(DNSFLAGS) -c -o $@ dns.c dns2.o: dns2.c $(CC) $(CFLAGS) $(DNSFLAGS) -c -o $@ dns2.c libcares.a: c-ares/.libs/libcares.a ranlib c-ares/.libs/libcares.a || echo "ranlib failure - ignored" cp c-ares/.libs/libcares.a . c-ares/.libs/libcares.a: c-ares/Makefile (cd c-ares && $(MAKE)) c-ares/Makefile: c-ares/configure (cd c-ares && CFLAGS="$(CFLAGS)" ./configure --disable-shared) c-ares/configure: c-ares-$(ARESVER).tar.gz gzip -dc $< | tar xf - mv c-ares-$(ARESVER) c-ares # Must touch "configure", or it will trigger a rebuild because it is older than the tar.gz file. touch c-ares/configure beastat: $(BEASTATOBJS) ../lib/libxymon.a $(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(BEASTATOBJS) ../lib/libxymon.a $(PCRELIBS) $(NETLIBS) $(LIBRTDEF) beastat.o: beastat.c $(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ beastat.c contest: contest.c httptest.o dns.o dns2.o httpcookies.o $(LIBOBJS) $(CC) $(CFLAGS) -o contest -I../include -Ic-ares -DSTANDALONE contest.c httptest.o dns.o dns2.o httpcookies.o ./libcares.a ../lib/libxymon.a $(NETLIBS) $(LIBRTDEF) xymon-snmpcollect: xymon-snmpcollect.o $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ xymon-snmpcollect.o `net-snmp-config --libs` ../lib/libxymon.a $(SSLLIBS) $(NETLIBS) $(LIBRTDEF) xymon-snmpcollect.o: xymon-snmpcollect.c $(CC) $(CFLAGS) -I. `net-snmp-config --cflags` -c -o $@ xymon-snmpcollect.c ################################################ # Default compilation rules ################################################ %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< %.sh: %.sh.DIST cat $< | sed -e 's!@XYMONHOME@!$(XYMONHOME)!g' | sed -e 's!@RUNTIMEDEFS@!$(RUNTIMEDEFS)!g' >$@ chmod 755 $@ clean: rm -f *.o *.a *~ $(PROGRAMS) $(EXTENSIONS) $(DBGTOOLS) install: install-bin install-ext install-config install-man install-bin: $(PROGRAMS) cp -fp $(PROGRAMS) $(INSTALLROOT)$(INSTALLBINDIR)/ install-ext: $(EXTENSIONS) cp -fp $(EXTENSIONS) $(INSTALLROOT)$(INSTALLEXTDIR)/ install-config: ../build/convert-bbservices $(INSTALLROOT)$(INSTALLETCDIR)/protocols.cfg ../build/merge-sects protocols.cfg $(INSTALLROOT)$(INSTALLETCDIR)/protocols.cfg install-man: mkdir -p $(INSTALLROOT)$(MANROOT)/man1 $(INSTALLROOT)$(MANROOT)/man5 cp -fp *.1 $(INSTALLROOT)$(MANROOT)/man1/ cp -fp *.5 $(INSTALLROOT)$(MANROOT)/man5/ xymon-4.3.7/xymonnet/httpresult.c0000664000175000017500000004457011615341300016472 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* This is used to implement the testing of HTTP service. */ /* */ /* Copyright (C) 2004-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: httpresult.c 6712 2011-07-31 21:01:52Z storner $"; #include #include #include #include #include #include #include #include "libxymon.h" #include "xymonnet.h" #include "contest.h" #include "httpcookies.h" #include "httpresult.h" static int statuscolor(testedhost_t *h, long status) { int result; switch(status) { case 000: /* transportlayer reports error */ result = (h->dialup ? COL_CLEAR : COL_RED); break; case 100: /* Continue - should be ok */ case 200: case 201: case 202: case 203: case 204: case 205: case 206: case 301: case 302: case 303: case 307: case 401: case 403: /* Is "Forbidden" an OK status ? */ result = COL_GREEN; break; case 400: case 404: case 405: case 406: result = COL_RED; /* Trouble getting page */ break; case 500: case 501: case 502: /* Proxy error */ case 503: case 504: case 505: result = COL_RED; /* Server error */ break; case STATUS_CONTENTMATCH_FAILED: result = COL_RED; /* Pseudo status: content match fails */ break; case STATUS_CONTENTMATCH_BADREGEX: /* Pseudo status: bad regex to match against */ case STATUS_CONTENTMATCH_NOFILE: /* Pseudo status: content match requested, but no match-file */ result = COL_YELLOW; break; default: result = COL_YELLOW; /* Unknown status */ break; } return result; } static int statuscolor_by_set(testedhost_t *h, long status, char *okcodes, char *badcodes) { int result = -1; char codestr[10]; pcre *ptn; /* Use code 999 to indicate we could not fetch the URL */ sprintf(codestr, "%ld", (status ? status : 999)); if (okcodes) { ptn = compileregex(okcodes); if (matchregex(codestr, ptn)) result = COL_GREEN; else result = COL_RED; freeregex(ptn); } if (badcodes) { ptn = compileregex(badcodes); if (matchregex(codestr, ptn)) result = COL_RED; else result = COL_GREEN; freeregex(ptn); } if (result == -1) result = statuscolor(h, status); dbgprintf("Host %s status %s [%s:%s] -> color %s\n", h->hostname, codestr, (okcodes ? okcodes : ""), (badcodes ? badcodes : ""), colorname(result)); return result; } void send_http_results(service_t *httptest, testedhost_t *host, testitem_t *firsttest, char *nonetpage, int failgoesclear) { testitem_t *t; int color = -1; char *svcname; strbuffer_t *msgtext; char *nopagename; int nopage = 0; int anydown = 0, totalreports = 0; if (firsttest == NULL) return; svcname = strdup(httptest->testname); if (httptest->namelen) svcname[httptest->namelen] = '\0'; /* Check if this service is a NOPAGENET service. */ nopagename = (char *) malloc(strlen(svcname)+3); sprintf(nopagename, ",%s,", svcname); nopage = (strstr(nonetpage, svcname) != NULL); xfree(nopagename); dbgprintf("Calc http color host %s : ", host->hostname); msgtext = newstrbuffer(0); for (t=firsttest; (t && (t->host == host)); t = t->next) { http_data_t *req = (http_data_t *) t->privdata; /* Skip the data-reports for now */ if (t->senddata) continue; /* Grab session cookies */ update_session_cookies(host->hostname, req->weburl.desturl->host, req->headers); totalreports++; if (req->weburl.okcodes || req->weburl.badcodes) { req->httpcolor = statuscolor_by_set(host, req->httpstatus, req->weburl.okcodes, req->weburl.badcodes); } else { req->httpcolor = statuscolor(host, req->httpstatus); } if (req->httpcolor == COL_RED) anydown++; /* Dialup hosts and dialup tests report red as clear */ if ((req->httpcolor != COL_GREEN) && (host->dialup || t->dialup)) req->httpcolor = COL_CLEAR; /* If ping failed, report CLEAR unless alwaystrue */ if ( ((req->httpcolor == COL_RED) || (req->httpcolor == COL_YELLOW)) && /* Test failed */ (host->downcount > 0) && /* The ping check did fail */ (!host->noping && !host->noconn) && /* We are doing a ping test */ (failgoesclear) && (!t->alwaystrue) ) /* No "~testname" flag */ { req->httpcolor = COL_CLEAR; } /* If test we depend on has failed, report CLEAR unless alwaystrue */ if ( ((req->httpcolor == COL_RED) || (req->httpcolor == COL_YELLOW)) && /* Test failed */ failgoesclear && !t->alwaystrue ) /* No "~testname" flag */ { char *faileddeps = deptest_failed(host, t->service->testname); if (faileddeps) { req->httpcolor = COL_CLEAR; req->faileddeps = strdup(faileddeps); } } dbgprintf("%s(%s) ", t->testspec, colorname(req->httpcolor)); if (req->httpcolor > color) color = req->httpcolor; /* Build the short msgtext which goes on line 1 of the status message. */ addtobuffer(msgtext, (STRBUFLEN(msgtext) ? " ; " : ": ") ); if (req->tcptest->errcode != CONTEST_ENOERROR) { switch (req->tcptest->errcode) { case CONTEST_ETIMEOUT: req->errorcause = "Server timeout"; break; case CONTEST_ENOCONN : req->errorcause = strdup(strerror(req->tcptest->connres)); break; case CONTEST_EDNS : switch (req->parsestatus) { case 1 : req->errorcause = "Invalid URL"; break; case 2 : req->errorcause = "Hostname not in DNS"; break; default: req->errorcause = "DNS error"; break; } break; case CONTEST_EIO : req->errorcause = "I/O error"; break; case CONTEST_ESSL : req->errorcause = "SSL error"; break; default: req->errorcause = "Xfer failed"; } addtobuffer(msgtext, req->errorcause); } else if (req->tcptest->open == 0) { req->errorcause = "Connect failed"; addtobuffer(msgtext, req->errorcause); } else if ((req->httpcolor == COL_RED) || (req->httpcolor == COL_YELLOW)) { char m1[100]; if (req->weburl.okcodes || req->weburl.badcodes) { sprintf(m1, "Unwanted HTTP status %ld", req->httpstatus); } else if (req->headers) { char *p = req->headers; /* Skip past "HTTP/1.x 200 " and pick up the explanatory text, if any */ if (strncasecmp(p, "http/", 5) == 0) { p += 5; p += strspn(p, "0123456789. "); } strncpy(m1, p, sizeof(m1)-1); m1[sizeof(m1)-1] = '\0'; /* Only show the first line of the HTTP status description */ p = strchr(m1, '\n'); if (p) *p = '\0'; } else { sprintf(m1, "Connected, but got empty response (code:%ld)", req->httpstatus); } addtobuffer(msgtext, m1); req->errorcause = strdup(m1); } else { addtobuffer(msgtext, "OK"); if (req->weburl.okcodes || req->weburl.badcodes) { char m1[100]; sprintf(m1, " (HTTP status %ld)", req->httpstatus); addtobuffer(msgtext, m1); } } } /* It could be that we have 0 http tests - if we only do the apache one */ if (totalreports > 0) { char msgline[4096]; if (anydown) { firsttest->downcount++; if(firsttest->downcount == 1) firsttest->downstart = getcurrenttime(NULL); } else firsttest->downcount = 0; /* Handle the "badtest" stuff for http tests */ if ((color == COL_RED) && (firsttest->downcount < firsttest->badtest[2])) { if (firsttest->downcount >= firsttest->badtest[1]) color = COL_YELLOW; else if (firsttest->downcount >= firsttest->badtest[0]) color = COL_CLEAR; else color = COL_GREEN; } if (nopage && (color == COL_RED)) color = COL_YELLOW; dbgprintf(" --> %s\n", colorname(color)); /* Send off the http status report */ init_status(color); sprintf(msgline, "status+%d %s.%s %s %s", validity, commafy(host->hostname), svcname, colorname(color), timestamp); addtostatus(msgline); addtostrstatus(msgtext); addtostatus("\n"); for (t=firsttest; (t && (t->host == host)); t = t->next) { char *urlmsg; http_data_t *req = (http_data_t *) t->privdata; /* Skip the "data" reports */ if (t->senddata) continue; urlmsg = (char *)malloc(1024 + strlen(req->url)); sprintf(urlmsg, "\n&%s %s - ", colorname(req->httpcolor), req->url); addtostatus(urlmsg); if (req->httpcolor == COL_GREEN) addtostatus("OK"); else { if (req->errorcause) addtostatus(req->errorcause); else addtostatus("failed"); } if (req->weburl.okcodes || req->weburl.badcodes) { char m1[100]; sprintf(m1, " (HTTP status %ld)", req->httpstatus); addtostatus(m1); } addtostatus("\n"); if (req->headers) { addtostatus("\n"); addtostatus(req->headers); } if (req->faileddeps) addtostatus(req->faileddeps); sprintf(urlmsg, "\nSeconds: %5d.%02d\n\n", (unsigned int)req->tcptest->totaltime.tv_sec, (unsigned int)req->tcptest->totaltime.tv_nsec / 10000000 ); addtostatus(urlmsg); xfree(urlmsg); } addtostatus("\n\n"); finish_status(); } /* Send of any HTTP status tests in separate columns */ for (t=firsttest; (t && (t->host == host)); t = t->next) { int color; char msgline[4096]; char *urlmsg; http_data_t *req = (http_data_t *) t->privdata; if ((t->senddata) || (!req->weburl.columnname) || (req->contentcheck != CONTENTCHECK_NONE)) continue; /* Handle the "badtest" stuff */ color = req->httpcolor; if ((color == COL_RED) && (t->downcount < t->badtest[2])) { if (t->downcount >= t->badtest[1]) color = COL_YELLOW; else if (t->downcount >= t->badtest[0]) color = COL_CLEAR; else color = COL_GREEN; } if (nopage && (color == COL_RED)) color = COL_YELLOW; /* Send off the http status report */ init_status(color); sprintf(msgline, "status+%d %s.%s %s %s", validity, commafy(host->hostname), req->weburl.columnname, colorname(color), timestamp); addtostatus(msgline); addtostatus(" : "); addtostatus(req->errorcause ? req->errorcause : "OK"); if (req->weburl.okcodes || req->weburl.badcodes) { char m1[100]; sprintf(m1, " (HTTP status %ld)", req->httpstatus); addtostatus(m1); } addtostatus("\n"); urlmsg = (char *)malloc(1024 + strlen(req->url)); sprintf(urlmsg, "\n&%s %s - ", colorname(req->httpcolor), req->url); addtostatus(urlmsg); xfree(urlmsg); if (req->httpcolor == COL_GREEN) addtostatus("OK"); else { if (req->errorcause) addtostatus(req->errorcause); else addtostatus("failed"); } addtostatus("\n"); if (req->headers) { addtostatus("\n"); addtostatus(req->headers); } if (req->faileddeps) addtostatus(req->faileddeps); sprintf(msgline, "\nSeconds: %5d.%02d\n\n", (unsigned int)req->tcptest->totaltime.tv_sec, (unsigned int)req->tcptest->totaltime.tv_nsec / 10000000 ); addtostatus(msgline); addtostatus("\n\n"); finish_status(); } /* Send off any "data" messages now */ for (t=firsttest; (t && (t->host == host)); t = t->next) { http_data_t *req; char *data = ""; char *msg; if (!t->senddata) continue; req = (http_data_t *) t->privdata; if (req->output) data = req->output; msg = (char *)malloc(1024 + strlen(host->hostname) + strlen(req->weburl.columnname) + strlen(data)); sprintf(msg, "data %s.%s\n%s", commafy(host->hostname), req->weburl.columnname, data); sendmessage(msg, NULL, XYMON_TIMEOUT, NULL); xfree(msg); } xfree(svcname); freestrbuffer(msgtext); } static testitem_t *nextcontenttest(service_t *httptest, testedhost_t *host, testitem_t *current) { testitem_t *result; result = current->next; if ((result == NULL) || (result->host != host)) { result = NULL; } return result; } void send_content_results(service_t *httptest, testedhost_t *host, char *nonetpage, char *contenttestname, int failgoesclear) { testitem_t *t, *firsttest; int color = -1; char *nopagename; int nopage = 0; char *conttest; int contentnum = 0; conttest = (char *) malloc(128); if (host->firsthttp == NULL) return; /* Check if this service is a NOPAGENET service. */ nopagename = (char *) malloc(strlen(contenttestname)+3); sprintf(nopagename, ",%s,", contenttestname); nopage = (strstr(nonetpage, contenttestname) != NULL); xfree(nopagename); dbgprintf("Calc content color host %s : ", host->hostname); firsttest = host->firsthttp; for (t=firsttest; (t && (t->host == host)); t = nextcontenttest(httptest, host, t)) { http_data_t *req = (http_data_t *) t->privdata; char cause[100]; char *msgline; int got_data = 1; /* Skip the "data"-only messages */ if (t->senddata) continue; if (!req->contentcheck) continue; /* We have a content check */ strcpy(cause, "Content OK"); if (req->contstatus == 0) { /* The content check passed initial checks of regexp etc. */ color = statuscolor(t->host, req->httpstatus); if (color == COL_GREEN) { /* We got the data from the server */ int status = 0; switch (req->contentcheck) { case CONTENTCHECK_REGEX: if (req->output) { regmatch_t foo[1]; status = regexec((regex_t *) req->exp, req->output, 0, foo, 0); regfree((regex_t *) req->exp); } else { /* output may be null if we only got a redirect */ status = STATUS_CONTENTMATCH_FAILED; } break; case CONTENTCHECK_NOREGEX: if (req->output) { regmatch_t foo[1]; status = (!regexec((regex_t *) req->exp, req->output, 0, foo, 0)); regfree((regex_t *) req->exp); } else { /* output may be null if we only got a redirect */ status = STATUS_CONTENTMATCH_FAILED; } break; case CONTENTCHECK_DIGEST: if (req->digest == NULL) req->digest = strdup(""); if (strcmp(req->digest, (char *)req->exp) != 0) { status = STATUS_CONTENTMATCH_FAILED; } else status = 0; req->output = (char *) malloc(strlen(req->digest)+strlen((char *)req->exp)+strlen("Expected:\nGot :\n")+1); sprintf(req->output, "Expected:%s\nGot :%s\n", (char *)req->exp, req->digest); break; case CONTENTCHECK_CONTENTTYPE: if (req->contenttype && (strcasecmp(req->contenttype, (char *)req->exp) == 0)) { status = 0; } else { status = STATUS_CONTENTMATCH_FAILED; } if (req->contenttype == NULL) req->contenttype = strdup("No content-type provdied"); req->output = (char *) malloc(strlen(req->contenttype)+strlen((char *)req->exp)+strlen("Expected content-type: %s\nGot content-type : %s\n")+1); sprintf(req->output, "Expected content-type: %s\nGot content-type : %s\n", (char *)req->exp, req->contenttype); break; } req->contstatus = ((status == 0) ? 200 : STATUS_CONTENTMATCH_FAILED); color = statuscolor(t->host, req->contstatus); if (color != COL_GREEN) strcpy(cause, "Content match failed"); } else { /* * Failed to retrieve the webpage. * Report CLEAR, unless "alwaystrue" is set. */ if (failgoesclear && !t->alwaystrue) color = COL_CLEAR; got_data = 0; strcpy(cause, "Failed to get webpage"); } if (nopage && (color == COL_RED)) color = COL_YELLOW; } else { /* This only happens upon internal errors in Xymon test system */ color = statuscolor(t->host, req->contstatus); strcpy(cause, "Internal Xymon error"); } /* Send the content status message */ dbgprintf("Content check on %s is %s\n", req->url, colorname(color)); if (req->weburl.columnname) { strcpy(conttest, req->weburl.columnname); } else { if (contentnum > 0) sprintf(conttest, "%s%d", contenttestname, contentnum); else strcpy(conttest, contenttestname); contentnum++; } msgline = (char *)malloc(4096 + (2 * strlen(req->url))); init_status(color); sprintf(msgline, "status+%d %s.%s %s %s: %s\n", validity, commafy(host->hostname), conttest, colorname(color), timestamp, cause); addtostatus(msgline); if (!got_data) { if (host->hidehttp) { sprintf(msgline, "\nContent check failed\n"); } else { sprintf(msgline, "\nAn error occurred while testing URL %s\n", req->url, req->url); } } else { if (host->hidehttp) { sprintf(msgline, "\n&%s Content check %s\n", colorname(color), ((color == COL_GREEN) ? "OK" : "Failed")); } else { sprintf(msgline, "\n&%s %s - Testing URL yields:\n", colorname(color), req->url, req->url); } } addtostatus(msgline); xfree(msgline); if (req->output == NULL) { addtostatus("\nNo output received from server\n\n"); } else if (!host->hidehttp) { /* Dont flood xymond with data */ if (req->outlen > MAX_CONTENT_DATA) { *(req->output + MAX_CONTENT_DATA) = '\0'; req->outlen = MAX_CONTENT_DATA; } if ( (req->contenttype && (strncasecmp(req->contenttype, "text/html", 9) == 0)) || (strncasecmp(req->output, "output, "output, "'); if (p) bodystart = (p+1); } else bodystart = req->output; bodyend = strstr(bodystart, "\n"); addtostatus(bodystart); addtostatus("\n\n"); } else { addtostatus(req->output); } } addtostatus("\n\n"); finish_status(); } xfree(conttest); } void show_http_test_results(service_t *httptest) { http_data_t *req; testitem_t *t; for (t = httptest->items; (t); t = t->next) { req = (http_data_t *) t->privdata; printf("URL : %s\n", req->url); printf("HTTP status : %lu\n", req->httpstatus); printf("HTTP headers\n%s\n", textornull(req->headers)); printf("HTTP output\n%s\n", textornull(req->output)); printf("------------------------------------------------------\n"); } } xymon-4.3.7/xymonnet/c-ares-1.7.3.tar.gz0000664000175000017500000232634011535424634017072 0ustar henrikhenrikOLZ{sȲͧ%b($כ%^y$i9gNvϞ[u+0ǯ[ fÿ{{Y߫?~ lx#ȟ>srd"W_ٯUk?Xŏ]zX";^tݡktݣkt=!^br 0%S/rmJ-kL('"dzGJc* 2`C&l‹p B_vijp> ǂŸҏ_/oد> isi 7l#XlHFpg7ۉJ6QlyS$+ 0s='d6.1{S–gq@()1 \޶{gW7=ּ|6G07{QĽPdH` {.@t 8nEU{veW׬:^us޼fUd+DYnt:@[\:{8$sl6 3xvs19d̗`$< Yb{U; az v*<^yA/SVj TjSaIX-AGvJ(4 HwtP2WZ0"˗=FbH͚QyԄϸL9+, &Zi#}8RV njՁD$rYq` E|A0#80 k.Ck,2JPvi詿 5H{#`!_Mk|3@NaRf @5D%ԗ.2E-x6xc %EW$c7`a"o %8 }oqNHf531(HCGxx:1xp|v|L*Ygg3C|P2R)D ~i\}W2(Fj645L+mn" Q3s5z>~423p,sPJ2uI8? FHr)58BSCn4?MSx2؟1j3hK a۹e>-^m %(pt@jȧ;'$$P-E>"pySf:8l/Ajc' N*Habbp?@L!lќ6U=0( c` l2hL+[ m&fhjH37M*r#\tL+7(ɹd@s^@`:Rb-Ya4ڐ!IB.nH4#{H;^9WFvQj'&_Wr|X?ysfe (@ VVU fϧ, Z}k"XKI;Mv C`FP@iIA8ᒰE*Q/I!d?" )3?C`;(2Ҵ@7KHqʁpe,0Xp[c^`ҝ˶/1KOr>b~Y zT{k5L !1]t^@Tv48;4f`l_^g.' K;9b_ E 5H }˅E4z#{M..6/`Š|AX wjTD>qx1821<;L([>hȴ_|诠ߌJ(i*崏`MTHb(Ö+e(עF:8dJ[[ m;g1yYOA[uƬ2-OyveSYv)+,]EpU#@_* 2b^n #⒵(Vs(P .8,F(}՝BD} S%gl,ӏ Žcgj[9ҐM(lD=}f8FLBvH )k\6/NY[w7s5Igww{' yF뙫oNPy$O-όAqX\* \lP 6Ih-!9"d }YQF1sg  >q6bg&T<`BNӥ(GBKSl=jٴ2V͑L0Ɠ+s6fp)yt ={L1)%ȣ[[!za `3U[5p 6!jَN<,[ɪS0 ⃬߯&7=Xp<M,HP>Ԃb"`@`Pe-U^ڸâQb9 uuz A,'7yS0̮he9jci{yu> l%6AƑwḨ#@^~)똠 )PnNLslUTQP"@q1ApgLI F;Ề?Ս(C[:q0#!>*Ŧ Gu¤ HJZ"e궴RH_Sj#'eC2f9DCjunyyslnO\_Wf}α t;l@Qv? e#@T+//^5E+t؀&)]w[A3zY&ͬ`ex@6u9ݝ߄%]ϑ p7ZSq!5Sj-MnyRaȝ7URUc+$4]<`=;ݱfվ.g՟fuõZ(4c(} T/KB"=\%D<# >.v!JS'F1PĄPJ/b}6SR]ߋFcG!HRTKL]aYy9kcwO/5i"zV>nŏf1WX-O@&N%4{fmT+B61b%dޟ!8o52^sZ"~o¬kD':ɦߠN:Zm}TW+|c_ũ+Mz`y^9;MO65Y~>ZKy]je<4ϮY*qM7= W=D,hޛy:O==q(ԣ箚: EҒL̡,pcuy/";oY¯q|*26`8tJ?- }oj>9?/.:}ݼyY,[ Gc;gz? ggibv/{q#@*BS-wvJ9hLp1iUP@ETX$DZF]8`-Y21Hrzl%21H%V)ث4jR} p ^O?)^u-+i_1`B@STo.4 Ol[KbYC_ٯVQ5?T;2<5|hNöOd;Q*~K ԻlI>{]Vmh^mbߞވ?#jdt UXЮ6(~ kRU뉭bĐcdw} dBf=3KHs-Bɀ4ot#w7j,FwSQW{ׂ꣱1w%%0q{9@/,PY6#ͻ0<xC8ݍ@ {vDk{szY\8ۦL^˷J^5bRo[ *Z @|}m6Q?w}T̑w 4S`- 3Ͽ h-jb*N|`~tweLJxVH^RY5koG^͍c7!%L ɊbvdE"/zJC;WjܟRa#J"( kǖ:3q!ֱZSdW`VT̿GQB H~Do*˞ CG*tM=8\t^  py۩ﳱ×5ila?D@26\ya'-M3*`NA ~.wYk,,oD( &;ʲm ԩz5_[8=kCH_ur "mdH|601IUUwKR2I; _<>獑 t4} Gh;wޓb7E5rCu&"3ϣ8\m35'̄ o.qǤ)>ʩpg&6ͭ՚!W۵3U Ÿ0V4ԏ5*&EيuFpo( "aQaVgXmneCFcͦ8}Ոſ^$~סO'`cOTUf^U;yki!cxf3Ô9k({ Xr.ǞRւyiwx~xbp-J88zc&-AwmgoQ0*#z"i\&o/Q77Lb9{GUY5e__ }`"k`cv,8dn*BCNIal>/&y֭ok%DګfW=oZOvaӬ,V ~{,{] GH Z |apy^^]05kS \WϽ~1ɧ47ZzϕfϯNlޠXzL4|W +(z}{].eS8:nf,,(LL I 'gmmGÇö  ΫFΡQԫS;:ln 6dytΡQM<./ѧBUw{G{QF%"/bUI46bѸ}^599zoVà}x|{;p0 8Bw| `5IMBC$˕k_ =M(^Z j4lۇSMJT+tp_MDl89c5Fx#m!:HZKqk?Vly^*_إfyv$el~)/0{X$D,Ip~6ƍwaÝK.ċh$i*BJ\n9 :>$N*զ-m=1 H {7Tw;0+]Q%ngk7 = (ǖnIY & ,k[,pf1ҬSCUϡrCQo1}_DO B t] }mGEu0 P]-*P"3@w &Hǹ\Fv9 YrPˤtceO=򪪕KTHLNZf͞8nz2&K&f,Al0ZX /Qh'ASat8maHφ!*oݠKkMR/v]M `G$ *Ϧ *=: ^=L;z,-$^ff̎_g\N9>'f526aG}݌]Xbs=+w}d턬4+Y8T/a͒WwH$E*Jo7]:=9c他V>/ ݌ *SҎPŻ.gLڌIÓK?ǪX"gfq`8ˁpߣSoRUա#6@W"#dyU{%ʕp̰u3u(!*  %RJy+m/Jʩ=P-alWx.N$=DDH~γ#A(rHd;7u_xcLak*Dǹ& -s&A2tNz{*p]!Vc91cjHԷ%,xm=J m`%ҭdٌAؤi ^#dG!2fk/pi}`؈n \N&Mb,rY@ՄAJHϸ&Q+D#_n-_J4/I-;ۯ. }˝U|c6;݃' 4x&8᫊f+>IX,iHh4M' H.(q %rjh4Wh54Yh0|kUܛMkүQ)V ;JN3+gc,sqVzmCgpȸ#6C`\Fit=TuQVì4@P62Q>NķsӘ (~ymb^Fzy \‘cUkm~?nFZtBk`AA؎?mގ+*rY,0CmOKj]g }{xS)K1D)lKś B%b:ܽ^Ã`m<)߬"XZQ9[`מ%bb֫bmr~~ZeMj*/Eo6[Н>+D 9w26TWjd#H\AFQl$/W'DKhugF9+ܢ~^3ę:?߉= B>{ Nozs_)'grx,$<3xtGӄ4^7~lr h ϛە9 @UwKo"~=ԫif D$yYs^.^J7hi sѨ Bp>:/:]N7;/q EZeͮߏxvQ<=&>/1/'ٻ}tEGM(~00t9xIP9w}tG=PÓ0u7zsG f6qhRDműw5$ŗu[Øl/?*\P %у s."x .4Hcɜ0/|BA/F)O@/<]Q@sBтSppZ ͛EG*ԩoթJݽTORZNIu:Uߎ*5̭J Ya8՟%FIw,\ ~EeD1#;%)^ƋTx{G -QѰ25.SjK01rH%DZV38h׌ȱ@#pEO s%zY)ekwA32K-ZvW)!J 1w!txȭ)j߫E.`#] VQ:LЍNig~B|,;x95z^I< 3'`A 苏;]0xR?cms0 |&BM/z޼6M6w5m&Mi3l?&߾݆0( VT Y d6/XEZ3IPU Ik7 7X e~6,TYQ ΆO00,矐2*[fXet[&$>n ޤՐJ6 =^R C|r~)Y^JY o*@Cmw[gbpV"suX3.;gyBo{?A/i&>ba/U ow %}F7WȣS,^%ј^XK%Bz )Efw[v ܿtsHQ:]=O-EX\up;|$SN+00/ (,0C_Zm[6 Egx<.&w M\(me`qZ3 lSz$lk8!vyy=`L+4Zdxj*Z]k#SbQ0%Ϝx9PwPt)&}$q(lMΎVM3__,"dvxN<)/x)*˶e{y,BSR;a3)`4 F4K*(j):LN%|gU78>]ߤif-ag2'TBߨ|2jMYh~xFxpѱ9FUapIɿLO7*4<ՅÃ/…5kWssL~[u9zE+q@ea"Bb`"Ʃ ](mrIt.W>Q̕J?@~ӒL6_ FW9f AP %I"00֟E;=ifo,呬{ 7^,ѭt5;Xyo]?jn l|T2 ۊT y4,N,'VΎR_aH4%)haBwyrƨX3OC8=dowø,$(J!BZ:p> vL#<:K TcY&+$ާlyz4JcnU Z]ET?-EF=8Izy3ݑ{BshF eKj0G),q`rGV⌹f*3q 9w @\ȡX*2} CQvw^&SJ heܷGͮjGseNG 9=ѥN`$`!!'t TRK|b[—VYuQS lUc%Zm`V,I tmlHD17W;qao A*iG'S?/?k:N֫omU ̷ϊjYMɋYdG`iӐ^e1滽V?,ۮ/8o߷_1,6*-'\;Lfλb%2{T#9#aAx`hݽŊGyS]ծы~|ʸ.Gvʙ. 4.Ox'F鏣Db?:E{$93dQ;w9sul\sluƻN׀:\ w,-B=[쎽́}88s2_Rxccl9>c ֝;νwe? &.e T=z.1AreF1џS ' u׿![̎HPlqv~;~$.$牨slNRf<ZXgR"zȊC_8L95b;T:D'9\ -ATUoTO]wwus.Ry$8#z /nw2 ڱʅWmׅw6;wx6][f]poj`ǟ)X9@K<-n3tzJC-wY Xj)XjOROR,OHx^x1=NEaJOCゥ5W U5jv( (+O3䴉lXddaG069C*aeɱP "VV9PpaEA#Z,*@t13C':O W@A}G~)z\{OEm@Q@lT6Hd# cJҦ7aDPTvEAD> 7df`}UݩB<9{}ﻗ4K$ņiˈT .ME È$`HP<966zGTRQ"D--bG.+'oGC6 ]h"~EaL:̿̀^[$'G",ⳡ hT(# F|3,XnhTxGĺTؗ #EЫ<8.ky15& WD?I)p [=hp]C0p R/ku׺YB*!daݸ1\$pˁxcge|mdz:n%e*" q<уt[ފ8}iФ2vc&8HlnR>^,Qch!Wu M [AUI.4D"CDCnFhkgH H@3QXmţfB`8'½13PUU1^ghQ!^Tsx|ȺG)ŤE^x;D-/aaZKˊ-UU(?3,d6cEhA8V6G^QBMQGFFa |,p|IT(T= 6y|XOm&gY++BT21q2K9,47?\|?LqѺvX`FO~M^~_=V3Q/M,ӈ[oh*1(`j&'~ N6`kkvlR!dc0"ځnWN[}7Gpd+TN}(ݒTHTRd")`-z=&U8!ԡ///%(:`m={Y,ؘ8(ef>R} #CmIEQE5jbQ"^wcBNK(]^[5:v}"x#QyƁBG=8* mpv")DAlnU3v8\~FXX$`]N -3cN'U-mRH簶 jem _^9cC!w1_[%AB}owظs+Ë\Sտ3\h)jM,goapXB41|@b'9y"e!+V4 pr{|2mnv}v';5ov3B6Nћ#3;L :܌ Ͳ "'0Yr`٣'@[1*vcSԺ j`)-cv<x$P]i%L[FW |d`@L^?Z$ $JH 2҃m u1c#3e@K3'2Ȃmk=uhqC9",eIX!.ӠVҼA/;XdP}HH"hk"NiDRt\+v pi4a#RdBჱĩaDp-@VBsl(FHV8ZX\x1ZXVqVXBE)LȊ9z1#<y;dDo"aUohyiRh4":maDM^|%^נ'pZ7/3>#l~&I|(ߨHYDQ-葈W`wBP+᱊m yl5 t |z-%GJѴh4zj#>|e'Ȝ޾HܜeHwDz2uHJ)V>4`)sh4+ #@ FqM}IWFuz֦7~w3/ohPԍy#8:ּ%w&̐۩ӧ#'K6g9wxR9f☢m7uԕySX&̎~БE 孻wUo~ϰ39{ݟ&`5q?wkwnxwN7o'ZM\8r^q:9i59Pgd6дo@_Ȧy{:{gs`J˔/|ūt7 u[/ Ȭ0[Z7}q==ؽN/ᄑe6@Q=mZp2WMʈ09!̓MW|W},avue?VI.or?oPzo<+k][+-6kɝse=,/owۗWo˕G1meΔonNpCqaG64q=urw'e~,/?(}_MsPHfmҥg7[iС;>k{&_^uOQՏde'?p3}LͧO$U>'*̰&޲_X3W:1iO\t/%*LKKYr=+ץ&_nѥ;GSf5cԗO[}?-;]=뷜;}wOYs+;o~e~JC*smګotV6%M2pGϝbɦ١Oi,n6I=ڷuY'yz?l]=nN\UPϗL|k1./h[f=K~?}~xry^?+WmΞR;3{̯_]'koDaYo8!k+4٩i@._Gqy(^pt㳯~t&Eu/Y]b{ W]t jݽ: #N^?;Fyv3J=7~46mԕxW=l3>CqE.z&_wgk +/p|orҴ׊{Z91č]5Zﱑ}ke6=y͸_ԯšmOc_w>eTiL?N_tl]$Yz~DZcuյeҩK.|"ms'_=O=}^t9+PP~G.s5ZXٷ1}׬m7Ic䩍 ~)e>~ʝ^://kjD~/*JPl='}}j]H-8{n>vUg턼ld/O_y}?4FF0Z*iiJYYԲ|fgMduԧ* eU AX4jP2ˡU"Z*h@#lY|CK0*M(9n4)oN$?u?3Hb9R9n 1QI" ~ )BC@ g} ^\Қa VPtFF$QQzW,C%ʥ)X^FAad0HKY SZy.Ʉ_ [8nDaCLn'SL4i,:\,X# hZ鴻\5\OodVPF3ik= ;vIrav i$T'eKZ[lA&+lC]M&lj gl2I0=CRBb_IR_DnG=Zi ZNe$֓B'A$c1j7aϯٌr R@)HT!]I`lThT-EQuhR"4*]&[(*Ɠ߾Na3V?;X-|6kMfU0qUia=j#h"z(g9 S?8PjM8U$e. m )hEy9J c [uzEÚ$ELDW DkR4"4(Mz­BBQM O@Ik *!uZZK֐BmУqz&<646" j#ԑi:Z-0*G1-&un4oqTbx(LV fڐcΤrNiPttVZfv!* Ex} ۀq.oGSzSk-q(IWgC(Slً4*8ZXsh񣕔,eu.7Z4+2[?#+##?Dpfj TsU7_v6a珿qs<ءcW2d }tAk9,p&B]bp=٭(a/1@<%?)*.pm =7v!Փi U*i5{*"yPlk}ߏ̀uCD-<^cH8@` dYY@zY&uko1 T.AM)XW<843$qP_ͮo`Ŗ*sAIհ58-?h]Vni'Z@^AUb_t4Z!* Z_ BQ#nB) U R&2`rw(dddjJe7e(P(H#a@^+SZo.4l,EAW܈ y-n>+N쬋H nP]jěB䌃/٠-fMG[RR*ա,U;"CLFƢOTh|Bl_ !q`:z ⎗$e!\`}b)kN HuҨXőHbF"*CG.,.4[(ՂE'{qQXq B"a9"D$*CDC2ujպuQ'j**jq[&$$|{{{wyw\n m3k[\5:jaCj2]f,m0qܶYcfbR,Z|&;PTmhiDsܮ6t@ Jm_m#zRhMh1N9nʡ8iv)omx*OsOsē0<-,3s0h;*g Z jijyZ"Ύeӫh;gWGoqz~ն/NC (Q* (bRҤJ( L&PCzʦ~JTi 6^A=FPc"B'b x;lV[^/x-z!+{[ezj6zƪ^;h;f=xN IGMRn_"!ƄOqX~a'GB"#`D,ĈȘѦS*9<2BFJ6eqQTK[p`nܸUX/fl>\abB0:=RU0 B-A (XVkـgzfZ`XZJ:{Ƞ,*m}=*X0ĶO V9SdM~`@Y16zZ`z$0+-;fVࡒPẊA@,.ejl$Qaqe'Aax 9dcy3l2Bd tZx$ aE8*khv|n@`-x:, ``&+ bV7:G Np6DeY5XU)f4TJ2 mkѢ=o7"@`;I,hT~4IVugr7,P\;w( )pP`|;hp9ù2 IK כ4>PT, d&=9P&HM{ՉV.6Mc.!a}nJ Ƚ3 ׁpFPkغo6c]+&ݵ \Rۗ r*ҍ tX!2ȒGhǙ^%N*CC8Tp~SvF8=AYڄ&hpRCdc 65tFKq`'v|ڪ^(/y's^z%UR薓jM U?^Zrέ:S]Rl3jȫ)Q570uTTذ͑.⃇i={۹,hkRL;,zpwdQ$e׋ϔK \8iEm_v3sJW7Oჶlzֽgcّ%]U nr9GxfĽ֎kQÄikT%'K_=ݲ^ *D_=Ovh Zxǜ$9Ŕ;?6}MNF]z &qڰ"L`.\uާeWv r|6tnNbÍ\6#ܔȭHy=<~P# spV雎;'=ޤ>i7~}~}՗4X"Y5$<(Laӽvvolr;G>ý(1xm}G6OnE'sX?G?9﹬Oy_2ԸTFRzɤkcl<׶F0O3|¹}:ӹ_^%{DgȔSMYrg襌˻>3e@Fyn=ոϴ1_:XIuOv(yu׏}.GZ1ꙻQ!5eGΈ{6? (|gMv[y;u[^q̣೜CF<'4~8%Hq(u\ƕ{m14K.[ G4r aީ=e+?Ο/Ɯ{1eGqM_ZzON|^֤^AY.m, gMKo'΋2mqWc\M"_nk%JNy>UÅ7Yndzf?W==Z7Xsk Khpww% 쓹Z`LGFiѮ|&4o6ȸ0V;zm1O't|QJԲɶD7M'7]=>]v׮Ff_ȅB{egv(~Ztv)S[hϺ++KqޥDVp4gwdȟ>렫dեۯsoUiw |r{Ik(gDM7 z?;W_VYyڻgт.mPLO,gӳLCs]Oyk}:",];l;rγ;&?hɵIރhpu SL-yn̏Us/Ϻmb 859vAfήS7glsLgsvWLcȼR˥l^/?O{tU'Q-e6  ض{dћ"Seߞ/h/tn,E @-= I4"J-HT `fCƿRߗC+LU?caIiAӧ9Ɛ "cLO}b,QM@& -ѥR) &IF0i;,[bQ"APR0F)h8!3+j돍 b(koy]IjȖ'@}F7BЩ* 4,JFLRa!ځv&ZhhԒ(DCCqaKT$NS:z|=Kg$EVM_svKpS/?QW"k[b@F6mm+ ~[YT4F:_ls@} }>JI~00OA skv OȳM8?s`%Z]Uyl+ۮKZzJvi gn)>a39q뼃YId3דh z1.DlK[$SZUŻ3=r TPr}Pd5+{!\̶EԧtUgK%(C. `2c; CΘf/mƋtJ7?S%御5/D-,r۝->S9}4[h kD-upAvF@с[L:|(4d`cqy\`ZXi '|rO$y9oc8y] `wv+Xxz}i)i2D\V}ՕsM搞Y/JKQS#$lP>d9ۺUM^P8][%#8 ?xx\ȵP%$O#`3@k|myn6u\UG 8"\84xZUxQހ Ȩ|>gWR];Y ;ݭ&lpkR$/1Lx:PHKv0OՈl.FqB1+CK%,H#bR7`b#zZ[#`z>Ok9ktLC%vp>g o%Vѻ\9~ykE V}n,)H_[[~<}QuLT{Ҵr ɼRw@KiLlH Qm| MލuQS c4^A(J44/+8IDLYL*** UQ`x#X Gs\w}[ hs6V(g.D@>@06îr(o #y nysжn  3*<2w9T?gޮyk8. ~M+"%Hg㋍/f7W7u~&/O䰣Я KP}+{ZhFn Wuŗ;K_PP<%!pkg)%W=2ߵ@ ?ߵHHA%wTBxv3,údX|$гa[DD\F^$c:vtm1[i8c˅FcG_Jfn[Ԫ͟96ܷu.NsK Sfr4*[j6U4/L'[ K|)$g@%Nvb|ro_CcMj0 QSR_I@CRcGOn`f(o#lzYºo\nm 2v[n`m^M~Xk&GǷp4XbօOgzWx'wTz{{ʟ/?0^&!m?b s^d?;eu],y2 $RbsŪ7M=oJ!XꚸDd%J=XG6YӮoV _L?H?ye9x*썹3 6& *&'9.3 xX߽N-u8֢ >~ycEOK/XRFk1[sڕV㉽_GR5BɊI| cY;80_ڞ+!?C ;1Udє|2yp™R8%ہ3cm.=y9s6vg ;BGo-lN}[^2LЯ\zLf}OBkc`nr\w i#@@D#3FAW0R A)܈ACβQBxI&B?=P 5If;m;nGRډЙϊYpɧtu<&B$+d,;b8 1;4YBkµ5A@g~y!_~hARJK'nm*,l0){!ߠt-$]ېC_/h(l 8P40JZ+i6^\lU/R šn#n zCBr2>5F(<tգ$ʾXs5b9a;~7 /!bJ<{Lszzlu%6(Rfh>a ~48!˕,זP!g؆oaWEtދu+#>NYT)"\zy:mgF {grٸ,vddmr&O祶V±E`Y(Cu +!I)۝cgYJ]L*Eb&!&68F* TbH\` l0*"lB؀Bٙݽ;t~uݯ;y}ժ7ŇNN$a 哯\wxcשos^㢟 //N.4^N~JoZ|Ӫ_1Oضۡ_vaX|_]g.箺^]=wЃW>˯}K'GL~[8ޟqk?x]=XտٿL=+m:>4>@ǿ^ӻc̯8cc'Nmj~~sio_qSoZj䫲ktxot˶wlDN{7Qz~vտw3߹nwٷ+s$o~u-#*/t7#ϽO#_KW׿Xwn8{7iްo뚭? u#l< 3pco?{7һfNr1L֟=%vwG_?ak<|Zy6rg߆'W^{}l}|]wy{ǽJ]]:,^:,^:,^:,>Z'L GX%W_oWS\}}kMOmoW˭kkuOtF$܎EBkzӂsEӺt9+- ]\"=]x8+?2Daqw0pXܴThQo/{aHGQI)UIn@{ QDVL$AɤԅGw >K`D xNM~Oy82tʕY U&6^/E8h cZqF~Q ,4WqW%nolSǃlca(z/0#&Hl$K#%F$ [JB bN9,֗dA"G;g!P8s fjBψA3 4#l醇aqP¨Ie46 'hݹ\6m4i9d y/LtKӀILF LT|-4IZ YbdQY 賀ui)K[9f8FZR%'ӥ"BXh>YQ;`%`BxEjFeE;ԻBbCb d$1ځ/L^)H~1t5wG`Z]H IT$_*O )Ч:, k;YmbDi6FKA;Hj9)LL A~/)KhJM&,1mur hexVmd Y<*OJ>_ t1 F1[QCi镚IY %K{llj֭~toљԟ"^bP)nCb.u) ܀FRR._< HS>e@tA?Q5A ?W)6}W&GY,RZN1;CzQ֨] N^De@2[z%򳨟Zzx1Wp>9FNbf!R3F: *SR "j:L ո&Ԗ1J)zFZθ#I!'R6t&%u[!0zҙky|M_" sJ7Le$;k"sG/h}d^Z܍sT>?rT[bdz$HBX+ ! I/ΎҀtщ(ˆ rz1ApM ,PME‰Ρ5䘷 -[kN7-0 tl/iY#; l V,L*7$7Ut|>ޱ^RkV'!8JjVVF&T1!k-BsbbYtY*2 'GELüfW%4ZLx֫Yu*z 2foЎ9(XnQp>\Ys2`锋b*(F,yad2 q$R2{ P4>S\ܼWjM P_w`o%TɀgZfI:VN/P80!ʯi /~M#^a[&ZVН3g!~2'p֬ GqL3 6  # #ET⟪ZcB%x:WцMK"t{\cw8:7X7-A_@`rvZ/vHS+&AĬ _1)@ ^bJKfxR+nFnp5&@k~tI}k|ʥbydٸc,o\ 1.}_tAUgtKd^eA ]\l B+V29j@-u  %~ x!cㅚ+U>ϋ -B]/s sޛe\`M3LtO\/Q찰pJ 6/O܄rk#m΂!3bjxq>*ζghjDIHDVp&=*ǥ8q h=7F+!IReIA$dC 8QӀH~ede@o# YP+`]HQ)e"%DD"ƈ2,d13RLaHYO StT ,_iDUI TLIXL:IiyF@wР440xMAe)gXHHI yPJ$,tG^KČ xA4)!=P. 4=x>'pKQ)KyN_B*ԃ8p%~nx%`=a4 . 1A%;b!_KBt$h\:(|'~߫%iA7qB {> w53!Fk?\MQJCZHF 3A<^ntMV1XR"k1ơ;Mp(UmrРbjpTM_ BL, j ߲Iqc:mISmiâ ډrj M > r؄!4HLm7ebI "2@ݒvlDN=XYq !Ѭ}cbZai%~MJ$3ձafx"S~m~iPFĥlh2W*v6aAވ+J1e9.>'^+KOi33~Sl o;R6I$lD d T~Iy(jJVtTSmM UP$d0pVj].B\vhŋ9볈$Iؘ+i)׍lf͖)Ki.Ha1sKsAҭw ݍjչ?:NQ1,j3 ¢ߒ^RwZ6g)ña~j6֠j=Dq>$$n{0Ea1(1hAܜKes^$h9+A{ҫkAW/ѻq GI "eg9I~3$_ [\-M&$LrB"2bx L_JPU*aK`Sq,vŤ7"{QUh5>ny<~NGN7D!  !n6<f6D^SzQRCL7_3g*zn(]&gɔ/+ZJ:t^ yJa1K6Ԫ-Eo'_NJLJʕ7馠tu,(dUZm}IiJ<Iɠh8O EȩA ݐ63rӉ&ښ8r藩9IV aÙr5@+Uj쀣ζ 78Jj<7@!|HXRrӑ&1Qe. [4QJ[ ۴祘 $&^æږ*DU%rzdt tq%G#i\ăA) U2*F"$_Ӑ-ESⰚq$ҍyɤL/ 9@3s5bQ 3Y/-s[+eFgit7o%}mwιͷq=Qe07I13.Lyʃ)kz^un۠tFd5-WtÁXzXJ9kȗ97r1!,+d/6ZSQ&+YX/OhzꙿsE&S ^b0;5X ieŲ58 bjʈeѺozp.G],F MufUb/Ⱥo2H*5?s~.ɒ:kjluvI5M**Kd0,ײl9ͽx9̱\b.avs{^y(RuUL{)efDx/^X[//(AǓ5zQI U7L +AXWW߯e["/*jxQ 0jp7Z^IkP߫7~5ʛML+KWdnf|XV*KR R zqGRj9<%H#h ?^|<#ꦜ@_| ?/*eucѯ^f4 ducÂ+ J*F ]6i^^sOۘE3N>U*cMe()|ї!YoQH/XjksX-!nR1y-XE_^1˥dt"}\($ uT~|FvciFj]vs ; uX\uN@oΒk躦sG\ 7B? ;7/{M^NkV*B}̰=EE)V}6spiǃs~8)`p6i[Cn 3Šzc]D`Zx}X ֆeݸW c QE+8(=c_d\5gDR <RK, r 5zcT76 V2 mjY/x%9fEZ0)pm27[x -0٨ZF0dc WЪh\ΞZ`OPoW )`\Yfq}N٠9 0&g)lJڒik {HrUFk/Dgi|VWԐ׊T-ԋB1l-.*@&V, ya=8IUp=U\B>S)qC8>6ZmN kG}s>1:~'Mn䏏%ܬ0 h^v/뗽[):}͇£`j=δ #+l.6> pu D^yp]AKe4:]aR,x~R!lcwX^=׀y0$ZDK*m?kORb#QFQtadb_DŽ.CؖF0۶>.X 51>}kGxL`Z#Eb O_>󐀕Hvƻ2%kRG^LOfv= ϡgcw?m;AU r#V;`I~eiQMGc5Igۺ}aU䄎m*߷tnX=w?+BGРD 1#)4>:?| 벌m#5 ) ?O)bמqŌnǺfYt1;*?̅T39񘞩g3cٲy00 ɿd<1=셁ᭊNW&̥T^x00s9VY1x7`8易0$}hXꚃLP/ ¢q2932X0zh?qZEOKPa30O.GU$="U ُR3ؙSV7>3 T{ʦ7z"ɠx|4r;)#;C]UwȫB^uq6e0IZ,\]ESG5A=<[# l\ّ|Iv==.5K\_fdJߴ67գ;5[KwýiwZ;azH;:.Ps<^}SPa#,)XO,˞K8"4!ˠT}j8G@Gu?h G"^ {G ̇",P t#4e $~W\+Z)K .'2iQ _A޾I7]cŋ7F3]@ኸkJ )!Q^jOinƒrZ3_6.qPi,ViEwT+7#χfzi184f6<)'ykscyRyc!iUTRW%eYN'xo#)EC4༽,v|蜋>)dEyF?a6z_Sxq5~2wDLz#D&ύ{y4s0ΐd1΂Cbru[+E?o x-vvC|!rRAer?ldJ0MaaYL1s( Z0GfcZd7>j՚w/ktJ^nj4@p3ii"b*[Ybv"e?c8}csqcѴpzyz89Fmr"0Nyz5n+&Wq=K̨pJ?J]AG~ m{"%V`8_Β7.d0 Ro_ ߾$H)͑BMd[ r GD._~Yy C9 g/Sg>ѿXFK `yS^ 69b !-HNE.;hzӈOt1òi](e< =sϟj$u!\b*]r%,bKb ?J!9GBN4l/[bh;@,btD3:$wM0O$ExT){L06du!*Uc5~lc\a!m^vƟ.1%=ͼʇﱭR05 RCWM1Ŭ U0~ .q|cnp&(cRރZ*dzj)B9hx~)|$|_2àcug=K<;hm4= sqZ4ImO+;NɑZzٖSess(]'9QE:D܃:=XQ.)㐽>9,qVcO{|l)&F4ڍ~&S.g/a,@v@Q\[z?7Rc=jgaY{=l'Q]ihv>=6wሦq(rڕRu9z5ZWBղ܋^,\`wpuk47;9H-<(أrpJ0U #۷x,ςk:r8yHGYMIw>s(L/cyDA}AWɒ3NkGi`#{?M(}t+no w  :+t |%aVYf'hpnaԾˇw(ba1mvTwoTu7^6w|8ߘ)ZQYыͽ#z]>\ºQ(d!^7NĖ"b[@6(h9T02 70T7Y'!q>4~8XS:$lGJ7FUfsŊ\H+5${,P,8;" *s,1x4 ŠYhBj*L=.ou @{PBG< lc >(:,C jGL3)~ts3"ZMx(smɦ[ s|ynd@Lng0b.V.Ɓ5}f/Z ==[ax԰g|XƁ'pNB~{:< 5xxEXQADptb S'-)Qys#s )Ɓ>ٙHY{?p.[O]"x++zX">}gu]HPVϼ dJՋ9MO:ڠpCk–MTAK B6,Zi#bA$hdHA43|^ xoȎ֏@85bQS<|*`=> K΁q@B|Z=QV{眮žF_K8׻ tO[,%Ӳz(haNbP_G9?n1#,JF QsK1NꢌŃ9/{Y|PGq`9BG בf a<+kY\jfI?E84^Y/]*d3w^sR U;E dwi+rݸĊysz3t.`d>[(\+5 *w%t6R9(Z樤Ve"Mo!(vvnBb( ŀ4"`× p5{,NA=NgvjqIŤXj䝂6ay8eޕΫe8Zm{1Yl,0hDa󲂬B`"َȓ"C&JuG`6D49ֲì^IϢ__^ت2 pX.׫O6=CZjq ZR}M+ulẙlX,nlsQ)V : )}иn]'`8]hY9n{NG~(GMākhB ˰J{s0?NmQFQ5a4r 1Pdș`%SECADM\R-^1rbQxywA`a^[z"o.|D١2wWw].ޱhȄc-y=t=8ˌ5ZU5ԚrtWe Ě[*B\ao#O`䶧nI*/r=1U@FKTjMiSBBIdoy5ITBD- 9 B.Kw8ZdoZ [@ eP)f]o_-o{R΀pk;s dAg03 5%W]4ˇoZ72γRtPyQu@ jEҷ;&٩i}^7tJ!HtstfnEm.'Dr[̬<}O[ L=ENGujlRs:v}v~?3^Y&9Të7aI>$1F~r5O34n~gh Mgj~f go|n{2K`Ӷ/9u3ss.~xMHS#;)q9@&wcut|A .QZx0-Z;??lAk$cs[83s g :Ѥ|022՟ef2_\ g4t5s_Jv/1X2< gq.wYC0w0u*OxiJRp>0%x~e8AP6r m5n_JUG4ki"_&x].=t,q p3}?oiM|"OQ|?u6˔VQG}r.I =I0{KN\4bDDgO=3]N wUddOu &UKS"$5|L?\ex (,<*/Hj%$j \&B;u-K3|fkӴʮYFUJv&_Qt("tŊXʆ o^ Kf5XM b(F* mٴgT03|#+TxvOWZ[SB@誀ӁG i'.Ҫ̞?'?G_.Bolҧ&)}[w.gÌx=P Ă>;ڡ&H_QQmh|iNMGy'y;Ph%(j)+i3R.+(gT0;5"aNgT3%&tŨpgT9) jꁆM &L//GEss1 TXt:zV\k8 RdC5L2 %1I _& uھlSlSqf&#ٸnۗR4[_ImUyۗdq3C}Pۗ Tp.y`VTwU&䎩XtPӕoRqvW˛J x*"׃)iUS|"黪W~To[+5pUY: (J:r.}|A9,&П3iE= AU:]:-E>JvRs|_fЭCP}؛3] >׃/԰P$nٔp4xQɗB);j԰9Y*w*B<TIRˁD\㏨'`\x$_AZ?@zR*rrjJ o6ݔ^Arۢ!029X\ gfJDzḀƫvݘ3D.唥U#RAtZNSX vҨwlk䫈QB] nˣ};݁AQFdmP.-+d03Rh ́ -?-xb2cINyD$9*wpInKXw22:1d 7Xh%g83Fr`0kyb 9d@n0`%JBUev/C !r}Nyi;P^,Cŧ +blc"d0jC0TlyZ2s 5V|*;9e#|D?bs._/MNًϡ/cAQx;u,'#vV~w*vG4٦_&+\2^=X$ g+:P#$+ ISZ~#+oya?~$%pB;!(nȈ**1@Ty,sIğLjQ*=L7JЙR(PME+BJ#FQ 5@8Xv6\}I$? •enmuVN;ΰUgQBg 5]*P2*-\(ro}Pfk bL(w^w{`*v(Ot*:w }c$xrpQ!2a5s?c7mŭĤ-*2LS(+"7m={ ̲p7jt&Nfr,8&Df;3줞nyS(4lGٓyI}:ryԉǃyN"ā^>8?_"y3(ldz9zF0\*-"RCs3x#Kn</V!֌Y OAYLf:%9 ɸUV3cnEK@:u露#\b݁%U+X(-*Nhw@m2>Q$ ] yT0V(Zl܁ux Q.[w:vt* @z'4[,Ǭ q/i. 1Mbp؃dJ`dPS ~Ju_C1x!w"P2K#Kq@L ǡ6slX FӔU?^SGNϹnlv z OI/R3G Pq8\/s`V ۦV }r487t 4w>Ȱ@HN2Z3[gJ֗:+t&H"(C'Rjҙn(0a~g:DZA^13ϪD\$=zZ|_2Pi6r,cK; Za)'`=ùͩf䡎<(x%!V#b$ X)jzaxBa*Ư4 Fr싆tEHjY{kbxNcbq&㞍φ㾇|,w T&w1;- z 轡92Lj!)5ƶkz2HޕS6./G@}̲Ar{@WkwAyb>j 2c\{ kn)ln["]ΩAN*a-#4|@\Q :눁:&E\>-"R??5my\ϲ"TpnRAUB@ '"I{|f9a܋~{;J“.'5iyB*"}G7cgM>.p\@TO+$R!;3G膔GhJS҂Wm'E|6k0Mw @9z$Dw'4c-q%0*ۨtcAfÐD==7Y{٩T~ ~uxV&`H,!NCt~: LOGfMM zE^/̞5'(cP]@T|Ae/:E&>+$H pbRT:nid3Yy >d9IUTDH 3{[_ <RZ -VyWγv%~?S L\4 \<]p'Bn1#XoR^mwt ]T>͊Q#lr[A(+ ==d>.@LT Ž`N@ޥ;*{aZlWPPUuۓmyΡ$CW&Rx܅jL%G2%ûN)P;0EN xuqNðtݕKu7ީr,x'^_Z5w, ]P(= o幀&/dSWoJbRϥAَ> IceJu1InMƞLniC Fӻ?ė۝Rj&ӹ5dZu3;Oel"`*䰑 *' ҄WM\ yAoW5EX\0P21q4@*FcJ1$|I1|F p>y-Sqc0Uu%bƔڹ9_TY(dIHWJ ]7n~MGڻ怔V7 <!:HRf"f\}EIƨi) fΎ ;ƉkG~k\ş.%ҳ4U *59HX`d 5@-y'1;_sC-t=}c1ﴈXE;iu W?~ed,!$Wpo*a@g)vt5Nuˤ5ɼH9rNL/χx]Si;&<3%5:xs,Q>wNCqN$J!<|U@mrrnDPENP:ESP&8$ "+[3"BAPN0Bx/ByPdb吗1ã U#~o@Z C%א*E%qGovңxA8iq7oҝmbÜH>)F~iyJҥ6޶}DƵF#۲]vaAomW>q  :eTW/\Hs˖!˃N:T}ӳ6|bnjUQvpONO/^L!?55ٙ}H{_?nk m[o;pTv-Gɋ u=1ЅcFa\N$m68lU{8d&V1 o4vDхXUZpW=KR:tKb_4ڬV4Ē MY,`/ٸƚ6mxv#hK= ӽnu Q2-U{Xİ%eUW ֑fj_kI\mЃ`]|EkGs*Rp=jif ū ǂ[ w}Zb Ak6SkѾ'7[f]a@?ϲgM`Z7Qj6W LD(K:6^6vD9Z]EVR=Q%m@i3mM++* Mt h3A9P{?Y/^7׺wv2OjzfrbExiJpſfz]+Jb\*fZ<$HLtr_4$+` nsي,yZ6,s M`t޳",v3c7!E{Ytk7iӗaC2C9] 3J6\~n=\Bw t#=i's2eak@ɉ9Lqudgme{uѱ mP2Խ_6Vr՜A"ڠLkɥm2M~~flߪ Z^!73 s8怤7V[.Vlp=X#66nbYŽ82ʡ_WRC5&%e#n&ڛh-^À Y_ļ(>^t` qew7OL_$va^HO[ O28;9T3_kٮ7|~plg!pw[ 3̙wO?8Q̲%oX Kn]6aec,$07a#/I vհpcX+7j-AAMN"t-m*+-T% \-V,uӚ*mnbNEMbWh^GjNՕ+zo%JJjoju4BXfu8⣥LPsK;tw@#AcMT3~#ҵXA} ϝ#9ı;|7 96P4Ъj;D4|8`7m.޲AY'B84ޥUQ/W#,5:6.?i佂~^&+>,i 'R*SEkXYK&CiaS&,nn.q`$Q,.8:֬,3L%hN])VJl5jkˋTjY6z^ՖL%v!󬠩c4LvUU1D?wDFnʎe;F3:)Ђx`l6`קl&؀nrI6Ēҕ:ϋ땍-/AW ]s6KlLӯT uWG!5ReTJn_y)k컅Tƞ5.Eչe] X6;俧/ò~ȁM+fe4m'JzejM AF*]p 0jtL$9 pfL")Ɂmh֩'ullc}$1+"xf~3Ztf/̾4pJrNY*k8׹/}|ԼmQQ9ӮByMՕE{VE)*OYՇC] [:{OtضY/i G;ڔ@3 p$HGrȎ֗Y|^/<5Yil>*P-w:UuYb KS Ч-((*[Mƛ4JV#uX'z ;@j_׳Án hL蚡O QŭQ/잹սm;wak6aũ~vlqmCl?r,jOcouhJ8???}rd6'@{(lll{v-ggg};??[Ô^4|ԭQLI#eEq)=GLou'Nykx;o'Opa:?39{酺/ 䤼O}# Vi!p2Nek] _S@rggg{? iϜ<0Ss3_3_3_{%'ƏS0i1*v#g>g~g~gN{h\?ݭ!ff#|lY{ojbܦ^,_ڬ(j 굕 Q~Y\,_N2KXJ.$DBط3Ͼ$sυ# wj!_nF2N3xvhC*Z[#u:TCzf jh/t3xڶZhs,i^LΞ=w]ONfSLJ{~[ajf 3gy {:]5]Ʒwap ^BGOKoqnٖ=O7o;w 0w<\Ѯ߱$(W<ڽ;BG:}CC/8oAw}ܳnD_l.72j@bt' X.tF?R oS}T\ v.Zx,@]^IQw^O9 W7x2<}frFg~w(+*Ajw6]9_G1`cʃ[kzۆy'u$Fg:sbX5jd=)${ZptҍCihvb9Ǎf5j)f։5'ĦcEtzU? Ԁ0d'cZӁ6O~gcgq9I]:j} l}xJi&4=Mh]9Mpw9n'&S>9zP ⼔d8}bQ'N f1=cPg/юoZ {&x/CmO8rR u⾘ݽٓi,XZjN=3N ONr:&:&:1kksq gs!V65lpۏ?W.g"Mi_<1I?g&/dcv1wZ+LNΠ5cmڹ̺i8%o]F ӡtuل_tmh<` p5(鹬a=%LԬC9]M9o۶ ǀ8 m5PgPGTز=8Zm̂[/X^?7>nڪiPP3~UNfnJ#?⺁XTZkLrt{!ӄ#[x3 egX=0N73Xkp=gzZlc&&#clc#[RC/jNZb0CAkvvŵb<[/A5i([c7h` YZvà͉D؞,,BӪ(,!ؕ'76++D΂_aMKo{/I671}Ff)RӁ뚾 ՛ c{.~`GlLoaIJ ({@1%CDq2-^+9t]rh;uMHlV7\`Omm8TxVp#hn4MRX^٬l'HWzh,=ׁ"cV4LBIMKZ5PO4!-0C <) NzYpC!gF`UCHX 70c0Dn,SXG3 XA!+*6pOHjuM9D4jz%j &>h'v̽p5DNC= V&K儑!!w88 ~Em_E7[ZOlX•Īd I1Vp7{kT^YQv,cю lG[r鱥Dbd A1#50\4Y|i2R񁍗4u'm.n\ C"( Zض]K3jnZ:Jȕ6[Mu+`L|y`ʍ-^VnT|eGx`EDx-6hzZ\]MSjƝ=h*Wn{  NYGcp7ĥVW"j\UAU˥r^-׫>MXӰy@riv0@w&6.2`Pt)b *jj0M46=;0(xj:o]&9BDUIU++#G AAt i7V 6W)4U 䘦51I.od4 J'yk8Nk jq3-i蓶yswM\R ލbǀJ#r,tŗ']/o/f"m_+\-Uqy t ZH擑\s95gz؝T5PmCGW[C@7!5!Dl?ylFO: #?c/7pРw]{t^uez(90A.: \re5qxw58>c-͝mЀvk\ x"Pb(LƷ2ZS6|VFǮ'p` $&: mv fθjT\חjk4j̈́G5AU =2ӂ'!ID N_L<`Sh>P2*H6*弰0λ_I6-Û1 ma؞`E,G0C~w)p}cY뀡 1Yч k|d^ôhqMe WJTh GڀIZo[<V#G7㩀*ĬV3D s"Z=&29 H0)ؿ-);%rߒ} ]$ ikX=542#2znIc[[&mmO3hD4*Wx 8VQ-WWJ*n(?u:zu[, ~VZg*투(Op T&Q[{Z[b5sD&[ 2 껁|fM4,w4Ma ;oo;د˔]xyҺ#_0Y h:ܝ0VeB <:;; vS:,X\^b9F3:S'+ shi>mb6*llV.-ƙ֊@q mo5}46$vils\s h=5چ}wi^;D?PE?ik8!pBe} ӊ b82[F 4KOQ͎ iNWP:pG3ܫA[&+9+*RSrؾB UpsCm".NxOA2 b3>L'tUݐcZG, 9<+FEw6 6&[p_7p) uX,q %ìyJiq.4 IG[)jGw!irf-0D׺jHHZRYj+rJ8f.J*-vZ|p"K!TL"z Prcy51j^i|lNωhucj_V$c'`{ ?$I}_B*BӀO%n AV@_`03@(A{0&%{"uꆳJ\-Zөaݫ{ܢi;/L&1@}  ahn6ܖv[l0èDL?3p?vA"a^:pۊX; M &d\%>![LڕxgqtKm a:}n"Ca[rVJ茪 yQ|+B w%e̽ymC$=QDU1;aP:t}zmT'/`~mtq< eb|xԭ6+Xa^ZL$C½ u#m )ݱ]W2&k ZdD xb*eףnX9Ӻ7'/^83/9/tfL udQRˉxA}L)C)Rz&8HJe4)!1}Od4  [(3u&M}/dؗ1vݣ#B̀'w譎 wROC{|Csm벨uR{%jgY?r;fD|J#NДoPc:j[ra D7RWՈj v %rxgև-tL}m,R *rDGKlsXb'_+7½Se:bU#6?sK}ibȈybaFS P/@HơzyX`ՑPCqG DID[h>؍S Ry0gyɽׯ%7W8e F='7yqUHSE%Sb0pBRfWZĹpc _aZ] 6$`̘d,vC8 ~=% Ǡ8x^8o:7)}Zcu@T1oMtoKJWeϐĭ_ڊ 7(( ym$&_ 5n'/)sƀ_Rm6,7LjL\AKf3"!C wNZ6"2Fltset.ʰe` {K0;׃~zdmtjlI>ILjuJ6O6 ]YfEQxa8 ;Plj@֎HJ6ƿU 7tE$z= ɘw 5+P򂭸m"'U^ kg\@ʍ*uhbļϼQ֍<[+A╕ՕzEY,WWVeUܨ@fT 2B!lhiHƶK]~FO@/sm$ޏ"D&V!༺]X0QͰ<.!VĀf/mɰ+a",d>ö*EUFY LFэ7n/iiP.$+b6|*Nbϳ1WۧC*D:E.i/|PX'F3;WI#yy9?y5fŒ.?P%!$f[F{p:Z'wHg-\Ft /ߧ83(u3)//WAa1 Y.xu_ep f@gĆwܝdR~IqZr,O Yh(:wp(6\.Vܧ*Wh ;Z@{RI_GVNY5VނN< fpjԖK@Roj4)_(dyiHD6"%N F(@.o @D"kts5x ŕ# !a35bM v^eĔmk|3&Rf̒ ʺ=.3=TNk(tP FG@j"HTHDfOkDŽh:;*{պHu/`>9i. _l6ڮ)bW7ʘOܺXtm  sm[HJ51,(8)m>(1wxӏq` @q(xbc!S;BpiN44;V_(%opEJtic6Q1hrGlaxq~4-g;;Zbc/,@]c_[=XA>"XHguE#RN@q<6ymY_jl2qd_6tJ|[2cŶkNQcB|^Ou63ehB" Ka"H =T/""; AU 0|#OV9@+ |H\Q5 4pX3Tm \H6_AXu=8 +\ P~KH#PGO2|(ҥ1AC%#"[D|A}/%P3:7tlZi3d'(`cGl@$EiߔrcL #/ 5 PX]5A?)F+ W,MPcGA 0w; }`a})/A2 ӱV!!g<]A@qg*JO.e$"|@#XZb;nD*uʑ)ͨX%Bifŵ[u9dVf*KCy]eMג P' <0S0_;N@$o,WF9'#~cOO^<1qA| XjhcMЋP5$,!Adww)^8/ G2PGcY1z]~ c00<[(ksBh 9Cii(xo/C)j * '/ee00R1 ob@d#qMIa>oȥYf #0!15  B:'&X碵bf:{g<p$'HK.2O*jCC,ښNW.{zf-x[@eL?@u@Fw4(;y)zu[|F_V) ŵ;$qb-ˈ+$B؀K_]$=l!2X݊XRJX9p $EΥs)R^H@UےM xvj(T8/߻t>zW V v.mP"=2z=+} OE]4&24n??KKi}{doUF@YbDr (DOBxJt.JH 'g,o'@U=.M eAO]MR52YA8 FSHv%JtpcKwO"so*7U,|R8$eilɃ& tpBLC0KАB"hkNdZ3E=Ǵ{~<'⪡inc9.nY:rr#ii+V+7RUM9l',ʥzDGqc'W=cC=ߺ+SEZp[ <_n+\ò-‘o5oKaDdWd' B~[zubxOwx9KPst89$ba ?^*fݙŹ$Q1D-\5_e괼ˉBͣW mT7ǞE"P]W"C VTX}|x lcY@Kp)%4B)F2rBs' 9":'ƕ4jn֝,3DJhptu8a˯-y#v `M#68ug]_|"d;x(vNh!.ʥZIHgϴL8sEaZH(Gx0voěȰ"2P2 *El]XYwu,1M3_샓iϵHNP!~-}t㿨ny Mg.ξh2?]Oߋn.-O'?oN̰If_xU1 _f'qv@ϽGIBI|V|k~?E#w/|ym^֏;[!k?*ްoqTova+#kZ_}[_J]__7|W|9܇>P/oW=׌/~/bϼz׿_7ş ~\'>k_yWK}7?'/?<{/W>Oy{~W~6goy?O;x߸={i7=_ٷ~coTbܘK_~}~S_w~ƘN|[>_ͯ}||o/~oh\~Ͼyڧ?O~~X댏޿?`6=c_x#v'#?sg'>Ͼ:_W_wW?_l{3L|2n#+~uno|Ӆ?|t>o|P{/+>W>>c^/O??Wss%7O/W O~{?}oow>Ϳ^j,}|_}u.Os_3sWh_s^|'~zo٧/Կ~?}gʕ7'Oͯ5k߲oSSo^Ow޻~_7߼?;Fs /f6i5/j_Lm./3/_#ޥO~٫?~#wӭ}Gh~ᷥ=?:/ޥO[Y k][?)}*_~_~͹ro'ex7S~_?;YwP?|W. ^:O[?{?G/O|kͿػo_O;ozG747 ~E_~=?}}ǽʗmow}8q/v?(/wk?K߲>sw\LǿM|nRw?O~S/?Zg;^o?W}ė|_//_+o7}>37~/~?~n#?_z_oxW;nT?__XM~uտ~~ _?_:_In?-[/y}aeZ{l]fDQOIIk tzIz4C&iI'>p ( >quADAP|.SayoߏgPuΩSN:睫 Zί&{ r?w.9uއWu,3>7p3+ݝG=4;~wo65E\>\\9B_FOG'൦߬3Of}r(ڽn-5_?탺^P;ḙlE3OV]/iÚk^_YY.3GaȬ;G 3r߾/wZ溽fӎ.?_p9ﻙz/G?U\ڿ㱽sk_zGwA%_,x8Lش?yO~ T5A1~sVqэ%ዴLZuSgӿoxdTvqpn)ZՓ#_IGEɲ![&]y{:'ܷ55vwlvgwv ^ӿI| \Cs?fUGgC<=0[GIƆZӤ]wvnX >E8hjp=yU=oi/cw7_~/Zg^ۼ/OsV14nys>b߷'hkZW״,'N/X^ǯRc>&aA.\r$㪉''? /?i/֬wb۽^hdzVsgЦO*s/~޵e A㘒L3^]zwֽrt%$֬H7qΝ7|eώJ3%w{[̼bϟXn,_yp~.Ygn{i7K`Ci7TsMgBz)TB@u<ÌgQS9?ASgu/EłL""1 Ze~!BxLƇQ9NBq.?&VkCdT1(E2, f&m]8dc *Gw i>ުw VUmTvCmJihkx^X \S.a֘Ul:@ZZ?::'K kt3E\F)IT# `>%o1tez*N-POk "|HנS%L:NN (U\/ƺcQ>"Nc_lXȄCc .֕ m.Qu`ypD L Nx]`e[(LE&"6L4KVd* 1J IWuy;=b2@TH Wf,D$PKM We5C `WZ/EX*)%xYX TGZ* ɟb7vU Bfc) R8RScYRj UtH dB VxppOe&P{B!4r# ~+lC3g9Js3Ulõ*H F./ *IE?RV1Ғ}ŕlGPõ*CQ.\Mᴝe+pPC,  |9,X`D0_AјT80OHJ#&*"XkeaT#SX!I<Oh) f D:w"` ᢞp1ZAk @݋l05X#q&ųe Ngտ R]x3ТaΠa?h0 L #,ZwpML)ӛfuoʌ2g-oC|^g0啱22]~痙\YJg7"S  oFGxL1s:R]m۪vs6j4L&79 1A1M_믷78;{ &es?]#˩j$ sùi5AvzLU!PsAĚ`:XaKGQ7IW±DaJ. ?w 8eL-Ԏ t<|$ɭ2H&V=Y(@^k`pB3XSmqqbSC0EohrH'mb9?gdx*P!,w_uL`H)IA S$L70|A,. s\302A1{&V0TL@:?Â/3--px&U\p$NxXڳ!)5I ׼bܼԄhYm%{u NPuTyJTTyj|I4ۤ^?* `CXKQ gC76w"e))d8c9>Mkz,$9Y [ ǣj!CO8/,cm@#sj5 4k%Qo㦍:g{;k;SJ^Hm,S(9iATz {&pU6C**N3|0<]cټ t1W*9kp6d1SHip|'}DsfCǽzGJD%o]̓1!1db2Gɤ?±)H87ZEx=,& WB:5("Ѱ<)PMCB+XR D$Bו.BMi^[mWb<*FHP*u.(iU$I5 Yr\8leLRJ\tb@(hJpގ GBA(#1ДYΨ(C:Oc:N5熃.pOi' K 8Smqt@ W3N^4Dt&XP=9vHÃ#VPMxVF^!seH{J֌1@;lΝB *'é6!<+LFS;s||Kihz<f e=DzI^O1AjgVi ~AM"쨡Cr"E)œ "'tGCj&O¤fǩЂGkk<RHzQ`UjUӦ,u;]D+Wwt)dO1 5TZ}d$"@C ^Rc|TXAהl\OQ1n' Hԕ!H; ggY)+tzC??OGodK5ZHD~ffA+tr,:t<Ƿq77Ģt fcz* |‡NNU%͐ίG3dzX2b >yԦ)9*Gl#C)'7X=yn`[:yg%@L ݯXHj4 eWjHBz4/E XF9CQ?DdSd7pe= pcudH+$)$eXZ˒kd~`Ke!ʓdѓvXK@HR  id!tgBgJ @P -={ޓd,s=s9sD?ס+>+W>+l1eDd>]L6QUI. RFLZK` ]9ɣɠ$l)HQPF6@6|pGcQ-aXJcɄ>JơS K7VZ L&(c:@yuKE؈O(&cϒqLkRg.H99E#2 5>E1Z :VuZ,aPoӄ64r4464Z$c+эTcB^|H)3O;WLkqDR "2z2ԑxurTrX옘^2FtH%DKcjL khR7nbm oH =pX06E>ZRUVu:?&ZnHEFEf8]F5 00$)mxO g\Fh_~9/28_/a/ԣeVz⟘ yCOaf3fvSwUwz^3Nn> Xx bƈwC ru]6i,"dU*G7K<}:+'O& Q͈$t$P*zo bik9-$X|"9Ҝ`LN\ OQWF+f<޾ *f 04&i-Jl=C˜uvZ 2լ|XDmm85P@4ZX*J&QYD QLjkCavVHVVb󡊞$W&*=PG 핲n7x*iWD'9y|4vmǵqe*UpܴYǤJ&Rɭ396d6crk\DTȼϸ%$0AİW3bbA#pw{]&h8d6&{V)"Ҹy$mӫk"U( Q>v2 Ôf(h}#Sf^_~_AF@qsҞBS{fcĄ 5Jpx{("< n,\h3T*F_u8OBZSh=g|Ͽ\^1(T;:ĞfifI^ ?Ψ\{g_#O?0~$U3ƞ?Om):gF?WpW.3?u'7 ?5/~׻.n~>K/^Q{'{2#ghw>}߽ܮKNsO*W<}Įx_mz/WyCGm=~ғ޾D{^>˖M,Ec'﬽dJ~[?tFqזg>Oθxͻ;rtGW'[o3 6ܬ찟= wڵ/:ُNo7Bys~vϞ:'x=;+^W{#'tpq\Ʃ|z3E //ӟ?> ~o{^C+3N>ӡmo[𗎙И*8[Y7@;[}3`H"@ AQaICz,<@}\mRt.9@ܨؠR#ADGrVoySջ=웸fqe?VȱE@#kjKuBsf"Gb?|}@Q\<=cU:J~G)2|iB~7% o4{Ϲꎞ5n/~߻OY5=6g[X@TEB5eUVr*"=9~adG3 \0B!حRxOZ_!wUחzXS38PPh/W (?}|ϬT-{qeq<3\WHkL3#l8«ߚ3s+J RȔ\10fI,30HfQiD#nkI3KUaDq-99"[@o/ЪUr"0]d5YrvZ)WRĈ9w>s=ne: G XLDhpsE6(ZϐVQ8ÁVB ]9>pdⰱr|wK2=]T1yVF8r^UBA5ofq7DooOpd1:czh~4;XZ@QE*P(y^/3At=C s*gUWT#"ZjekjhOzBĤ%lEYjMa_X>^ ̓U2 HL >U*>ʙ 3&mE0k =L-[ܜ߶Vj_`:gB%uѐAJ:15<%4Q"6`iTTLl^W0XPJ7a UU'H_OfP% i@.r޵$ˬ ׼4T=ǂ}jQb5zjxBy4R4VlQ".KcU?&KgOfǢ W.r y9דS{)ۦY[zy\w睫H<6nށs+?%13([n݈uPOJIIdf{=_T!^r@-䌸npUbRvkCXʡs0v+>!!ILf3bNJ @6) ްmJ NƤwf:Qf"`l udBdG{y2^cpE(`'{̮/ nq#dž4d)oxV5Oq˹,l9Dl:!-:qkA5>,G64}LNN5i80l-9@d,ق}Wr'IþDc+,N|{$jŵAPϩ]ʯdՀغb@DޝC-DtdHP4-[E7Y4)lΊ\I#=s~ȩͿŁչKF&t\dt"ӧͳaYi89n0I}\KzAd8G@kGp.Mb.ؕ D6ǛA %]4nDFAS=R% mC\*7AoʉRѷf$"jiVYYUpQŧ9lkY+&GOd$΢JS Cƨ=E^-Lξl}y PlƖ?y8hE\U>%Ilhu`]^B xXp h5bs6}4ِ,69Cd~X ?ڬ$B:E#~ye bn7dpb/5f;,ޣ1RY Cs4Me͞0QHMˬOjRn7Nv;I+q~ce*vʅ/s\#z1UEGwZGo(ȁ3W繊SUrH5h5>%Pe1u@LFQ@5D}ӡlLf_wbVS^+W3uoZ V[h\˗])c{(1Ufh.j)k@1S"sqh(̌OF@gf-\ -/q`F|VeBPGrpv7XՉz c7x ;h6KR#I즵%&{u \>Vxۅdui:2Ŭ=7  a@ZzE,=ȳ@HhY1@R<PRyiy8[Rb<Pfbhhҍ Ո@ErB[(Hr³_޶zgNf`뺍s^y'm0l3)eI@g<(7[|&k(6\6T$Μ-$ytNBҧq۠gduSVG螧J a`L3Hhi&pN*u n̙N͖r"lIpfu0,+Dı 1`Qi(K^RUQ# {5W`T^p3)ꈏ$71hL'{5TdZ5(/muq-l a0XŬ\XRеyordQn$qЅ5(0vf P7[h•VE7fXQt籥RWgǨ/fG-^")l"VV& )4Ac zϢ/$xNQ*jQ.N.e+Ŗw>0$6_ iڌ~jq&!.K.IzHl#X`6ǤS}٤rT, J2b%aUzy5MzP)kD0 fGL J)76>Cs)r`2hlv{Li^nW*n5/D JEg`4vb?6-fݝiR )=+8N%d2? nXs㗋`L?Aۿ\]NVAh_kya V.sM~,*7`UKvx0qҮinƩHI|x-mZjcतqZ蘔VFTs|\2thmhL:M|x& (,:x~|`8VC?uɆpǰjP$TsI_-7_jCϋi2zFI]#kW1v@9`Ez|j[p*`L}?:nsXgId[VN>P۸zIm sxPxc@r6my{5P3+v6G1/[}l_N#(b "P`-k$!;MD&Jb1? àʩTByB*hG k3 vY:)#t\Oz 4Ѵ~#< 5(E Z GY;< X%k2Q9.AR~maa'<=-Z~}b܃ֆ'XC51{ qs}|rêK KoK{(,ZڝWNjๆ#AZAء4s0q\Z63Fi57dԇH(!'/w!TJݸ@rZ>R; '3d̜ By9Je<{GQD}Rog$^r>g0-1?0R_+ݦ dRNJeqIxR+7Dw}L*qj:l_m TpA_Uم2)SNJNkJ4XsBBP%qx;NgL}'0W(G:`qN'p\z Vݻ:/zpyu(W,5-\Zb6gl q&${:۞b#3,bV)SR+O':qINVt)OWpo,9-%]Cf!|4>69l Ϣ6l.Mn}D(`jET6} 6AF1-UR(:x:`Uw˵(aot ^ߏ!) /2"Ӽ`DɨKjBL̽#[UkK6if 26Y wFېʥR. H?r(;,j CFܨ|YR)RV:2%ot"Dy!$J9 X Y fu[lH4+IUiCdf0W" P,lߒfhEF]ŘSE>.9B+-}hσKᣡ\'aީVU\ļƯ#`VD AG3EcVE2fzl|Q^vRJB]ʪUc Xa^_T>LG,dLOlã&^h(@hk[A\S-ԃBF"~ əpRj|Da#{xd!-cMcx |X&"ٲu6eGw|1 FZaPo<\fYڣU`K+HP*Sƭ_Bҩ5/LăMrގz^ȾD񻓄;R!eHDÊ2H z9CKD.Ye*D)fK$…ySb)9l#fFr;{DR38S :P:?Q& lDNgM+8'-$#+C.x+ I\NBfE9?)_ύLE7(7 NHn c`D8@Ksoz`e[j 4;&lA@HB$rɪ pbLWaGG̹TiXY#jP_^ӆj(̛O̠ a#PVu>/Nc&?,`CY$?`,SN8lRU p:|N۟_FiP/T%joaӵMY%7W ^EZ虗9 E*7+T-GHѓ@7pD@ch*a_=M pBQ>,LԶ3909vT88=?ҟݐ9E{6ȩ rMnnќ1e^S%Ulk1I<"]AbӉfSL'I⥎5weqo!Z4W*Ζ?PΒb~ȁdf/i.p1? vۥ?~F,8? FeV!C{ 5BpTjgˌҁac&vl8D8SrHSYڅ!-{,uћ5kNlOcq?6ԉ]a}K ?8ˁ*Ҥz:/x (y nN ~cz$Uٛ#L`|/t\25c; 5Z G*`ecT̖",L5hLh}bלڸi<0AP"oM-IIn_iuլSOyr2T0niĆ58oJ :ٳ CZNeI$O2GHBjkVR026N>Du0FMB"i  dZU ID,"@ɮ9N /[YK{Yof3@lR!k֭Է/*S(F.?iVѻ Ik1^kG[M`YV-uq"Wt2WbS0v eDŃ}Z%8^,+E6Aս Hl̰PBib8n:>Z J ~l B'$^W<޲[mLE r@]P:(g8 _ؑ!J<ԆHw\BNv6:& gƁ~W1F="ZٕP,V%cBLIߜt(m -1X]5H-_x\ bS P[wk _"ɐB/`"}QWqFGEVI6]}<lj5%5 ec$@lPՔ6bt{Ǭ EqOWUGz}j SI_w2Ȁc6uN"2PBD% WQ_X=9v R,Ӣ mV*֦MyohR-8ɆPWs^[2b.XҘ(V0OF@ l_J9ZG/|peC <Pr3(!q07i6gi|hJǖES ^Ί1%DNa˙y!loY4 eeFfݺLqfo5.ۇ(X^&Dq5Be`xeJK hׇKGL<`H\LeWi&u~<29L(ƵUI c֬b'w،iCHTGEEm5jLE„#wc ܽ HU<Dui{"rT37Oqsc?ɱFtFTn7b?XsϿB%.j$OGrRɐ”jf&VMZUpfJO)inLy<= x6Stό7b1xRE{7#HWZfE88m3瓧پQɲ5e;r dg,Ry 0zbqi,EyI(#R)NF"Z4pa4o?5MOG57T8q;` ;J.<ŹQ0DVȆLNe2_y8_/L`ɱb1Pt\-ـ՗z`0iH'2pqZmp!,B')ŒBHeLjc50h%D->fE̳9MX.2uh<<"N+ |dUj+0Tè2 L7ZX <TI6Ŝr&3XL̒uτQ⩐c-Ʋt73cLZ 觸')KzU ]g*筏,|onά>C[r;r[H>sTjet1+٪gi* \إ,=|\?)*L;)KH*|_JPj|fjTWWY"140D6Y(RHF@m 5gr@A:56[Ci8ƒ|U3sP=2SHB VGMrԎ%~ ]À8 ;sR"Uy?\6sB|XzM $bY$UYrܚjxf`5Oơ{u{}y K&.>gW-Ni4i/HlUWvմxH'4]h"[LI0x[!IfpHκQo5+;k/#ou&J ~Q&2V.Nv٠uX f7GL7u*>ԬתI}H(FRLe ,c0@sUS2vso2iFxNFj>W*]Ѻa7خ !8V $ N-i.$dRq`53Q F\WzĮ׵Z@Uld%:;xTڍB9Qgq봥}5xS*Lj`vkNWcʏ͎-S٨}UzcSb~.*h6ws&_vulٱ6=ȚF_^ G;SїD>ǀ+ ubjrp0!Rq610;W]<34Qǭ܀VS e==eHbddˊm YhFY?Jh@eo0LwҒ5dC=PZyXKǂ_yh \J*U#P1gH:@@1zא֛+daO5pE#L'DsYN6Ƅl?6ݤ4R iݵ3D3|K4z1Ȭ7W˵Ց)g&|KH;B"$FS<`T:Rމۀ6O2[Q $WS#m7 LN+$FR/f:Y/n5z ըڱ"# {D_Rh#87ۂ8>wjU5eւSe)7a;%-fNm/\#/zXχ-R[8}on̶ s"cp)e&Ӂbrd82 xnw,Y!3<9hV>8p+e'gU!/J$v%I`"A*g'ylljpFoAۡ3^ʏ 'Oݸ,q:ځ/>+`Mp=zL4%H$7FO Ii+7 8Dj<}z>D絟(&+aB[oGQtrW1*!8PYKVeў99 Ad:(I{xaC-!a9Y/C޲:-=d{oEcD ;y/&ӰdYoS16=17S"״16M"yYaqS0WMsu*'JV^/6>[UF1 7[6=ѻ AZ At-԰VsaWE^Ml,q<Ljӑ.#=2VVz|j#M_(=<-[tOmDj#YfC JR}\׫btBIk=٨ IKKQId5`Zpm%aX[t( r}'urowc~~ ,[u V­#Gq+* 赱EojɲT+Kz m mQ7xnUBoJ/Gx,e> +R!hÿ:nB*A$/HMzgs⨇*#jJa& (FB(28MN>Q3hi.%5NMN姨hWJ\[@Un-c da0%XFjoW~W⠉|'GQr1uJcR&&bRzCM$ WnPVUd")5S<ňܬMRǕ7q,L(3"n;uӊ8pƎsV$Qͣmư̳rrf'L3SX7uwU ];ZFYXP,8<ylcBtxH.,4P 'tW/F 6sT3 R޳SlnϜpU9җ.AbhJ>H%4fCXE=,kJ1 ~{)u%SXZЪ 5O4Tס u:gNyb'[C3yY.kO^Q4d cQL$8[vU`J C]ÄL+{^qw@σBWS#}0Vj T[/ɟ.z$.lbzjp߼ڦPj$2\ӊx4d#ۍ\7e$(x; oߨ#aO_^RmGq,jWTF GsNUو &$6C>tldy(D `E)WJ*kϼFE S&{L2TR2lTDW`t4JIZ.jF<ȭ_:dz]= Kɔdxqheb<2OT4Uk&0m`z"[e4$?Ap jZW5M [S?_6umjXWzIN>}הPBO?i+5JtIl@ q\|' /R!)>h;S %$ZJڛ` 4C8 -+ i;S:+L+&gf8<"o%p_DSG΢h0W73<)bBW %CwX*jԘ 4gbN:r(/[-8@fRDžnVUmx}qP<#6Dzbd= )ZqHӬP\Q*TCP{_MmС)H Ug42J m]zʥGCI)l`;DB'6[r d_:d )$"58>\ $ $4oCuh;8UH;P㊢sH*b!lk 8ĭG"GFĶOOߵd;C @ƃbVu ]΋]8s^rP+"fTl;o?eN6\F4j瓂Hީ\6m;6d}akΑD4/ t]HS\Fp73 +\jsm;Q]kT]|l\W80PcNDeURL ߑw܆w5؂iI1V'E:N8fH x ^`F T#xNBeKe3M7PL{$EkU0/.UW@F;q8npc/ڋ QDtGQ$QgK6/dʆDL$A3[[_: X jm\!.Yd+ߟ*7/tQ֍ \:8# -n6,F6D,B׼?.~u܏2׳GDkQX8'(.:[J.?c5o$0]-ML=v} DT&7m؎وeZu߹3J1W@Ex5*G-Cf(DyaGS-fD$j2Ìj4K2S[>p ?W9;09sz E\lFiih hNJD֥f(pFuŢ8ɮC(vN˖0fzi?D,=Py9p(Po*sa6to<"cT51;mtm ovѫmsvUO|/@pGJ,'d K>i2@Ρ(it^46)l3? r# TdV8ƶ*88p>rœ0HQ!nB9{GUQw9Y7ܰ.۪O uq¯\W>%><֛CIlb"*Y-&vh"D  !"ܛKbkVÎʆEĄ^!_K]nI0AUfx&&g1H0K %LA 2)0tJ+Llc+]Ävvzfd^52FaA8 8az) O6װ1:X&eb&ePWsu:e Ā\p⠜]?T/RLf֊V8g?}q ?< Y;"qZ S_Nyj{bR]zj[a(<*b@R:R&aϷRp l?*7mHDeԿs脤Qn*3螘ɱaW ԕG(x|{u"uL za-bgL.#[2ydJ?[5Ja(FrVF}. aL|i_Ap8'fw~T~#Edg 4E"y/Xr " gI*|5fFt.ЧJ30\gP݉ )bkzaçZU'V}E0紱>_ZU9v؁\)It o0k 3҈'W+PAζ a#C9WR5 _#dF#3F5^" ]ē÷bn$EXi},#V9Ɣf y0GaDxRa{+R qc3ʼnQ?Y8hclM}VZ^h4FwB"1kKy[1\^DBakT(m]_RcʹR }E~+K$mtUX=[f@G\ ٠;Xt#ʿ'o0EAՊ,(DZ( TS&j!vLէ:׫r#CPˀQptG[,B3AM1B)ԒFT`AxԔp(ױٹYQ~Ѯ.lTcj hvj=ogA/xH!9RBg$1LFBeh`: ߂ʍm9Ua"EcA'wvG0NrvlJř#H0.E^[M|?'D%G 9.u%0)Q!Y3]̺+J%xtSו74tEuBkp lvm fY7(ۋ͌|m Wh1 M)tt; 0!HYۏMyĥL>}دd.q/zD, 3 XwL; E~z~X{T?4I y%b\ FpR"ųXmնȍ bCR-F'2k]˛ Sxu_ysnJ$ZN;<#YtkNZ_9 c+Ɏ ?[OƻfO+ץ01?\l;䐼P܍\`" (X|#wQ>I{n p ͞Ȯ:}zmvWX>}F3Nެ\13klںᢈ1F ѳFemTikh^dwM#A<F"%tA}1-a"JuL*v!Feb "VKUvRyT zl@zE9eq,\~i1pV'JvwX1s"Ue[\oxiu#ĹK6>> orL5/+!Tt0?h879 "DTlIÓ?JG|zDju و'}xuG媗/ј#|xtk.֖1-umL}ύت%܋fWw:#CU)q6A8xiN"5.Z,#lqIgf&gT6M׭"K`#VyZ_P3rhI G&)Sq; Ƅհ1v8Ԓ,7KěēVbrďn5yNpk\Lv Z اi)Р]oXH"4;2))؆ BK9**sm i֒7,\J0_5'u?؁'jwƦ*.+!S \p|rc2+;ӭ!(I5&U[5=n)xhQ&9j1Y!aC5ʎB; sv`"8fa5.WwB+  024a $F>TO̥z7Y]d LŅ+h.M|"ҹZ3g1psim40Wpk34΅=B8;6s9ci}|~r9S*{rٔ&ίSk_{?TF&iT=y/kY証W7VN cŭ,6T$]Iucc'FW+؂ iZ5.#YeiP nCg4׉qJ8* ΫdyQov2¡}mebaPzm °ذp"Y~He>O'̉քB6dfB<;OJJ~t{LsŨg%>6P[Bpr FF\W& E UR*ZXXټ 'àPJ@h'[D@k#D\ 6#JކR)u׫l[HݻGrh}xǿR3R ܤ8ƪНAD9$HwWPvSivlĈ5myM_ EQ99/׭鎖)HA14:7SS8u%W'4]@O9 Z$e.2&"h9Uw5v 48ZvEj7nv`Vi;ZIxι„Zm3VA$b_ #Fr _DMBqDQKab:?7] o>` u򱇦ҙz  عDٮuC_d*tC ?? PpJ$ =l \Z2'b<.XxjPS @>o`hvp8U2v뤨{moN(2N͔Z\G\tVJo9b1,ƞFMa h ]9ѡqE@tn0G2YIr}ΉDgJDm2qRm:F%N[oG:TnJi Q$ UeP'qѿ 헆u+Z,6Htm%nt:uac;uxdվ˰X+g1TQSHc55*p jmÃK$ 1֯(GTNeS ^l'@8dgoqm;;C \ 3^bSE?8.,6++y%8(*fUD4/*rx_nUlvS:倕hwֽ͎yxQV)Aj`]2DQGچlB!Sh@X%^`KLs!%j "fG"Go;Pw $D4f+ǣq[û} Vs g``Zs!#Xhσ' WȔ=lhꂸuv(%Y(b9C-SUn6οj'$_͆ FP _c!jH,sB[g9׀2A+'aw[ í4QiVհݪgcZgJԇYiZo}V7R۞BJ_5p_GlZ`[20 ݥ ڔGSrt1y=k U/V%&и ;v3TNٝ;&ZFs ߞS99eN]Fwf>-3B+tLDvӫnVMS_$-da^\j20&i4,tt "*L'NJű! [v%WϾZ9kJՋb8[Cf-b4*h!AaR+A^o NxV<P˴h9fr!-0:SZ3qVmg3暵3cy۫Wie2Kf}\s̲Wu20-bZҒj6}0ßr};vɘ^} !Uce+nvt#_i{5Y+[6R^Ael !Р]H=lY `ɪU5<70LǦI_زgt2 =\H*4q؊=/ ]*\Bő["KrAZ cthӆ#*$)73ϠU@|wWH<{xbTGTNmF!Q#]%mۡv@f|AZxq"f6@Nb2 ʥChCZv':=q%ώHT؋)H[ QZbсaDZu@LQCn \©Ӓ+]3o7k z'Яu2jůMM9 .1 ` #ᚏ"UW͖0Mr$F i.!u[.rQWO\c%Zaj0X5ʆ2 2Z V!4aNйAmI Co>ǴXv &fTʝ4=8ZI+\,om{eFIJ܊cWm~D&y(ki-4#Z!VG:Ό_/EO͔q( g5vМ7#E; *v,0:Y>%W]Tc aPet9֤m ̑݃lTn'-LJ-qJ/ԛ &j2K[' A۵3On(ے;3;+% l1>}`ߓ0,h/OO͖I 0nP#92P1 wZn h s@Z&A;m,3*گ0+g U \D?)UG5|mlV2[z #YJ3]_C8< f )e~U(R(>>V##) ~/qlgӥ5v^Co.H bZɾǾsC%O^T"@X&g.Zj:+`bEL+L!*s g6.iw+E7/T P\FRgW{GK=͕zhD5ƮˤR>|'1V=stLfv^2+#Enux~|ilr2 22i2\0ԁ-(0,dTO7aSp3b$ADIwU![9:/h$+Q鱻=,/DIɱZ)2dԖfpV9n.+`gC-s`35iK#]vnF눹Xĩ2  Om+|se!熽;.k_SefW94x#DyWFh^:vNT Hw5U bיEeJ6d'ig$=ߣ:sŐ6!CebIȔ}ӕ@L8iW8Rվ&MKŠ@ks3<7|[ *S럪piYUI#pފ97s5W-xͪ4Џ\ԂvSΝ38! gjÕh'\fĩӿ86OXz)!zX yj#sm6̥ cQdV1k͆Ӽ5DJwټ@F^ 4 e$~-hN%Օz¹Pa4Ʊަqڼ93'z3F&|b`kpkυ*$Oe~Cܮ J++XI=0V={E0>M6QcųcTc i 1^f"_9J gƀѕ3Q?(Uo"{ |[bsb80HPb_(fŜM0 4<6>hDBUd1LaNH'la{,[.F>GCH2mLG>oMgp!#9J2FyE-?430!vA"Sn$Ήb]w'/cjyzo~gK{x~/Kq1?y֯=~P3u٭?vw?}gozܟ}`7;\ooҽ\Z{Ļ^Sg.YzۿgGnϺ.g{ ɇ\vqnc o?|-}^w.|7/O|õs_qw[~O߽,^u_?K>Sy-^yW_h=+҉ܯ}?1/yEbߍS߼ip?_֟w}V7Ϯ|a{>|͏'^7|\jo۞4|MKϮ֝Ͽfғ'ܲo?w?}ϙ7ջ_7q1^Xx=_LOdb;ocN>oOoWOo_yZϜm>t K_/}{3w[?}o?>._w⵿z۾Z[_W->>w?(v>mˮ7{ã~2x;{>޵˃̾ջ=pxg,;oV~{ln7xn׎Yw=}y/^?~_??/37k]ɯ 4?c^snuWï{'_o=~➿Sֻij~W|6~7>_=1|c?[{_W~ks-_|G}Dɗ^/OZtOg/^qWO3oЗnڷ_]7S|'N~'w_g{P-G[_}O>.8\җkfkǽ']wK?w}^7y}jd`W'}K.͟z~O>O'?Pa[/}ÿ?~O}a^5[ߣn`)>y'17Iw۲|{sR_lSxɱơf+wiާ>vusu;3stu+g~{uOw'o{{y!qG_ҫ~yǖ'z |{C|_zȳ/ڣ>y_mw~ǟ>W<{߾{_}>yG+Ѯoz7[Xz?}ںbx~&{xO狟7L5ڻ?}ϯ|]^}cc[OsC_q_GOu_~I?菾zXcKO{[No~3Gӏ_}'w>p^{_=y?~/{ߏ^^|o':x;]|>w-ok>?$o_2O>m{Hk/s#} [ƞo|}ċ_VO?-'&}uKy<)k{Yћ= k⿶ގI\@nAl V߬hMYUDzz(o"7"{dTDcs[ .Y`3VX0 F߼քz"UX.Tvp}C;#߀ 3qK0XhR5 M~j +07r)#oIjn՛SW沉\x==@_cY8[}2p]aD_UF%8=mmb[nݱ ̹˦wsD!2Gsbn 8^߄8t,"CH]}˶N(-U,?&{Z$h;?|{A2jTv>n-هkX+םv_jбջf{Y ˫vMkkeX[9h7U۠"͓WORڪvzs\0 uCl ^īՁXxO_JzN\u˺y=jY v\rlhYM={ GVwrًO΅=fnBu詮@u/6 ڪXAJ:J]3r} h`B)_Nןe{|np6;۵#_J&89mRR6-6܅cg39//01qTZ^Fr\Y`/qQ fը&VEY u DL5MYLc&R,vr}*]e_:P>[Jocf79 T(h*Ҽߠ_b'p qZD0nP.ۅ!l7ԡGdIɃwa-$Fng"+?Ν;#!pG<%pOYvw3\n(\w ]g;C " ʆaٵKlY ⩊C F_Yp Ɯ&c{wڽ{`b]5464׹ĉiFmpWb۶́J -ZrCs١ 9|&'4Oy@MNc[5bZj,YvNBMNwG%zxWT \ c[ns'L8q I Av3=3/fɼQutPخBr?Iʍ0Wr٭y `QL`o Ij(3=ä"ұ^tYEpzvb jU[ 6ZF*!;N(b7#$?8 !Ы9,j4;cfϮo#'Is>}NsC gӘ9 0ǯ~8η齮*^>++1 qvkgn*˵(I2YhY?* J# y)A, 殯jB9 4`ŸtV2 W )s2Q@tQD7d|q gÌn5&y&يIwAI K%jD qUI^Egϯ5=J+\J+40F(J!w bRMP,7u(4Nrd%j)VOOƩY67 .33,3Fɋl~:JÓiPP\vOe.IMf]1 8zf^ڗ&D)~@pD@뼀l74{t^\MK7n~FR1T9NNi&3ll%N=ʱ;1+#:C.wp/Io(ל)aiSc\ $L;Y-L'gt..5p2j~.ugkEpO%gR ezV 9^(sŽNTC9K;x?4s2\?rNWcXjq744drJvI+eR x:I5;)֢C[3=? aofLbwɝjL?t`S.JLP l>=8Ifa[>]v3wnƘɤr `z60bSNTB*5XWУKU VI9a TThH%QIjR_)R_Ĭw`ON t.b5LһYx|@{ N DSs!]\eӋL+DJ`OЗur.y(*Dq;]VJVQFrvC0QcbHL ΐ2lV3\3 dZ\UȽ,ƒJNM'LMOeK˚XU#H{:4A}f(:E葏,9,VX/%Ŧ۷T rAY-ixKk]!IG;<_CC 26>nqARG!XpÞ LCE+|EKlU- iR{N$r[cJ*D=Pn⨰*!u ~Y"-+k`7P_<ـv=wUBT*tyČPzd5>֗T>R!tTz%K"VQ&te.GR]ƴf#GäS%m*BQQ0>\j>Hdz6I65 %~ODwȏog|w|;yvgxvygwvigvvYguvIggg(>?RQshW$w TӕMDt0-:DDAK}n?O;J[t}+\(os M{W0 >4˃CLS@T +w& Ѝk:^zJVX/`dPW5cśjV@_ m%0qyYS\d* ?"PE*qou[f}Ua<−PlۺP% '.0d#Gp 瘯}hĴФΟ~x|㘶>ۓsր}e$#vk˖Ě 3i/MRv4Zľ|Gg/SY|Az,ցv>( [D훚wuP륷=}'~l5IUЖ-o! U0/:3ο/@S B~wBfقoGzv|jl2#Ǭl|IД,ˤKL!ܵ[T]5E偐K]yI B=g<9fs0puW E3"okc}䅔h۶!sC%Gѣvw(9=5.B-Ie\cGwކ+W|b= 8vmKva1RYD &ٮ]6%QĤZ[~981C@K+AltwMdȽۍͼ<*Qӹ3EwQxV[IOP 1n\_;oVH4+E/D~;CZY2dMhVӲtaBfgl'Pz0qi%(bH[$P[UDp1 *bq6.I4s*"KϜ;%<YwUY8I1:]eAnI&3d|Y,:.ѩ ~-eC4=KA;RƲ:9^4<ԠVDU^)+PGƩU7D/xT4eMjTHd,J$6 4 -|!o~@nvn6BU #:n`=^ȥf3s OV+zԩ#:{(Ob!/L.EG^"jjv!2Mft XRViĴL͌ &ʗ"&fn0a A,dѹ<bArh)~cgщqojZb^-0?o_kV0' f/hkE;&J$= YHQUQJqbT6d] YKsa$!N6 qeE::ҡ%^:qu< Uy./B4â;Oiؿ+;\f"WD7 K 9kN{UYAuK,(WFxzKS6臭GA%1&>S Dz6 .uF+ HYѵ 5Ͱc0tRUEvMPO_!i#XO=ZNz!J5uqgT|V쪡ޭFJɏlnuΉAu='m1kNF"" xvN>3!}z̛cӱo:6ꈐ5d$G2<6caSkQ>4qy 4E*ouK )F\-&Mxym)>'Z5tMjA^ژj {0:(Ɯh9s‹ϋ-澦l(`nffeؾl}MZCOiJ[\&jj^MxX!zK#Kp}:G֗k4NFOw)?~;K_c!?ݬg nU3OKya<>?;{w`_JvA&Jq7h(d~DdC%5:۷#e]*QޏWRI<4d˫,,KmllD,8,?ydV`v0y2y(eg'l:;f= ֢r@#PaE|2GuajEbULz(]VZAhfEOf!,9Ch9Z;aZP3?kAo:p 'ܧd k]H.' aMXȇ1 ge2|| uj5vH/OP,+N9^"Mzv+Z^Ru' v;j@ wxHZ뱦ujKV ñ[L܇*VC>G#C26dcsHzd?zǼc Ѵc2lm۲^o5)`{Hw4&$:s&Gmpr `4HP|橏w;. yXe+DO.ae-A"0:89P'AI39p"WTIC/S{.tb\x6@ X\p bF1kmVF)iB1 ܴBx''hCִpJm y鄁Z"'b,GĄmiSڶA\١J4,-*55q>v5iWޗA_wNe%}"goHcd,0n?h8 Cq)#F;J1#.ۘ$J:!e&lĝ('r M`I|U)KYfkz0L.'9[AW]c]fmGs.ZCm˨sfTШLheXlgܝ^t~Nd,ٹyLw[lцV Լ NS3S a֪OBJgs vw% suv4*MLMNm:eʪ7FL95F &ضEUA΂_'& ShՆg2NGi 4y|p{QN;B'S"L!׊jLreԼ?Nsjvb0sJ(k-crO_N\2;73eglf#by x>%N&2MQa/S)-t)huU H{mf_%Ж鱕 OMey,oɹLoO3RCRvS؆Q]C5։t\gsmFbrōau.tjf( 6\mۥ54ʋeif޾q[WU YH2QZh,O_w!@X:8,UT alT}xOx[౲ mn!=ElxXbcCGǣ(x.oyF=j>i{ݍm{|t| Ҍ1EgŸmv§&sFDg;OBeuRn9ŋ+7IוU\=%6\,rU~; 1WL\?.1M&2X%8ɱx g>{M{vi'Qo6o0ڶ ai^[.vD k'x1"#O3l'u8 dJn0 j!'c:ِ::+Qd^p{fĤ+9\x`Z!KD}'Ҋ:qx.Z;i d2Ss089Yg.|x?rzz-JXZr?鹄hN$A.lwPX!0w,!r0lStq@/2*:ׅ݋qXwJ_ B%*s si:M)g,-Kс DQp'XlR͙5i~;2zגLvnC73> P Zt-OӢܑF̧SSS;x ١L"PO2I2,ld(̰X̶R 0r(&s AqwٹY27zB9*8#6ҡqo ] RӋ$xdBO#rY]QuT'$+#O !I/}GOmԷ9<ε6f?>4" 7WDrkMIRrR1rtU)[}ѨVs/K(in1˒GBr6{W_} dI %yxܣɑ<IvB'xlbn%1ar!;ZN.ŅL:!`hnr&\4͙٠ ͻ5eWDrj5e_9'u; @P } mrz"d w&?˖r.T0A D3l.6@b<(T D=/|,/^ZH'2Ok^Y32HYN.exǗNK:e&P(@a3UʌܣsJ}3A堽'zj>Gx"Wd"kG" Zx80jDyuW Rլ kK%Q?)mgftTaV4aDxFZ4`DTroRH4WY6e j/>ECҡ VSj]ZLV!? ^SGlnI;MVr CIfTaѠn@| DOu/ 舲g S0]U ,N pԪtay bQ"Uq\_7*'اM=x`v.~T#?MG}Bab7 d(neʅ B9P @a1r9箺YGЬ*r wj^c/Q#:ssGUcMX2SM<ƯE@{.Ȧ 2anCY,he*6BͰi""BaYT`4x`a@ zYAeW  x8lEw Fv O=:6P b33`nNŤ 13Ȳ~ؓ!%JVKaLu&x[ W/I VV:ItIV<3 :){Aԗ4Tב @ *G+vyxO,w qNRUWL ^9SJʖx瘟`K,D @l,~|y#NbgsW6U+Fq%(Nt^b 8@J.zi-RҠ MGλs0]Q80ƧEr,ma6rJj.kfQmP#he$&PrlAUQrUT )\NS<_E'YhRO +;iAjn(Y[)N =%FOppڏp[m3<qpi1 $daDķi/S ,؂\8(j|lµŦ07MaEucWaM@}SMؔ @lK((#U-)D# ~kj/WB_ s7 n@.$ 1j)ILrM^̄L/VT|Dnd^^b&.ǑH.e<=OϦdꅪJ#r]h7 צu^ 5j ~9.?w” FƖR0r"JM nVJt``J""] bo-B7A g[ 1JFmRk<:YJ$ rcV@RIv0:o:L^vk} )v۝Kqb$>PKx;_-f6mZLC"C`p2us-S=\ۗEƎ[ԑ#N(`zm(FJ>W uqQidj2Sʬm۶4ց&zV8 JSMGp->JiJYMU R"St4F9jI@\N ›E a8)L4'6]ApIUx@=\ yCD/vy7MQX^.oO x>\(|rC]A=xOe 1!JzԔ^X'򫊮ev-lS VTd2 )JjoTS307$OuD}A\-q)4L]-ujSt6_ÅA\ްXNޅ4c kB]xӞALɆ68CY16V+W; J\ ?c~0G:,ZVDYj'2o3']6m{D="Zi6Qsȹ  @ 鬮* L~I7m4ӬuӞk*5E;|?&?#]2<_Y5MeEP#hVd#HUVtpjGˇ$t_ R"}(Au5b2,:NR"8cNޖ@x&0 RhEcU# 3^tEÑ5sB8IL x`. Ǿйܼ/2ajdN-B-7-F\ێV,\-5oQ{ XVa637NZKu,n#6 7C:YZvڢs%N)MxPh9KSU䱶[c=1^CIg47LSssY \±%3\v~a2x:nmHMJ.̥gAa90PF&%rAP݁xӺ1o(oUU\ WX&Ȓuy+CThq葙Nf&t2ak*y#\S]`֝p0c<u砂D9d>inml{汑nc儙\]8fbg@tnI7c2 1EfX/ےrϮ`5WG"{ n!JMm~@D%v0Be,~B6Ŕv-+s'n9jE5*%\Y%D i%XcB9/`(2¼#-@ڸu:⪫bJ8hghDU~#6 45g{,7tIGX5J`å;#m ulqD(؉$ec o--R v4TtMFG/sl]&ڡcˣ vAh `F+3 M!{f:oQf_^zP1T7Ě^eh{!or TͼRO^h2&X)콴%YM,> DWE|HN8̌댼iXZ VyBLhmsrqL\Pרq_dr3<(62t<$:ٶhU4~ao,E[uޱgP!ZTOjq$M@yg-+Ur sk>\[F%V酫I?lj{jIzKFnqŹlz^X=aÀۙ&Z(<0Eq'_Hbi RiIaG[әFn ~&'.`25=JNJ0;.5A}ZAk:Fu@4C De.l|]RJ}EXi ,)5w:!/j-ILi4x59 -Lj\jl6"t=6]h}GyglP2=q"j ̓yI>bdAQw5˾ѲGt#G:m| -ZC- Y'i0 2>5z ܺd^a6~DXđf^ '(xiNQ%CD0{E&4797w E\PF@P2 ~UCilPvWj*[(plLSU'ή_#)'[9Zy LNPd@q,.h [pbyCw0BTb)qk!Ce ڪEHYnpy?P 6[6zImrC/KWx`\7=pTHނ LTҷI{7&r`])Ɨtfq$y|h[Xؾ},ۙxqF|HR(#sGG|냲и}?Q~B7%ˈ;6=H[FQ(yPbzDO;~AZ;SE tg9yCDh̲>A 2aDZzeo-iUYw?k۴nva Kzl#&pV)ѼFWwmJKbOFչ2N5 Hh#"@TAڇW/ @Wj&T+·6mqr;'KWpjYMt q9|,]lĸ%Pfz8xqw]h;sGfߴF=uuwlE>hw`tZSfc-A!op\*7 4/MbXQH"dʆ1ܓJ|.Φ=K.]j+f% _OD>~VK]O9 s˩ӹ|8q@]r+ arOV%+ )kZFE)2mT%x•E{K% /&ք6 rUG6I,V!I6;O."]^I<aTK@@/TřeVqN+-"iOeT#+hP:ڠ eeLVD0[2@m!QqGe`J {;=#|qW#ś%;Ҁ'xy jWæ2 F C̚B,n51N&Eqvsl_AD|ˆ/>796DHA}V,im=Zcг:9,;c<~kWB-StݍMQ"(It#@W^Htg|f \'v~; N+0檊W X/HFWRٺD2SiUXp[dna[nܧDfoh[4Ƈ:rBb3 1aRCɼzAmxw#cN>\cG؉ ݩyԅ[EJ렫giq::D2\BQojOF =3Ga[W@hwp^Zļ"@ۍ˔Eg%ӺRHb +@… UuU:Gw-V%jSJ֏wL%ZxhO[+}~~9 H||R%-W87VI#o󫍎+xIj~7"wvLUi(yMǗjPȯq;uK5Cw;6u{#- T(> 0e!.+fVMܕ 'YtZ 5I&")_5L3/^a T*4_T2R(HF(0V#x5+َ<}ˆjHGDVxc:lp 4vm"%y)w mrCuŅ'A[Vж-ls  u-<+`+Ƽz;NeCU>CasgAj29L@DKձZk$WL(ȟN{Zxǖњl-\tд:"arٹBD$#$\#,o9yy6ASn~gv]-Y's+nr&mk78%2_}eM'CǑ.ʉeS$n̠A 5zC%EE;e `wJTy0n ocE*g*RSm[\6؃ TPJ[C"Ҡ.°Y{,fJr/%gd \N &AO`Y=pۄRV I .U'pާ^s`= J/ DWa6Ҟr/%#1GYev qS#^Ǣ.YE)*.іAY:@ҭ zi(hDxIx@;62t;H 7>;+ hXe])0NyݍVBf5&J3Nݝ*tWBQ']n"f"7#,:& .1 qbڦ_+*1pNFۀ ZܟIfa`֊GbDxXgHV6 @lq%Ε.bEYDo[U= %͐2=i N6U6lU1:驗X\|*$3mNd}վ"mݏ8R}_'ƏYȣ?lqU{"VE]$@p 8 iDF9~:\ Glq-rߐ#ΨN!v0ځ|z$řŞdؙ 0M} h# h6$h6)XaǬeCy#P3:Y>nGJ wk@@rswBiRF/vEmHm@Nmp#R%m]M 5kWjwBƷx{ὒ&=~axd(Uf~u+?ugƇbο&684xxo ?G,?EHޙOt?65V 3D-(abHU6Q2MNbَ,Y#Zc"샀 .{" Q"NExhj!RXJ6E6!58B@ZMA &SR,"-=\<0>ѹM-DHPc$(}+zSY.3- 2s,D\*^'clswb`a[@*a$h*9:99X jqjhѲQU#rI>,r.oPeoe^\5BEEtc?H6$@g0iUGXa8]Vª^0k8Z,6tٻ葆F^nj,6qe†&_[evJZ1DBa'CJc\ڋ^ 2Oiи_1ْ*NJR1$b&ս^"gT$# rÜ+"ԗ)lo ?p;H- 2@- lZ܋Gxlt6]j 7?A!+&w|ˏ!e#1N^Ŵ GS|v8eY;^CY~W3}7K?*"LvSxnQH Ι<gf\2 W0xxj/A֣d6ۗ S ˆF~cv[f¶ ;(Պ />wMݭugt`J X+Zԝ_EZ`'R,XUCQ8)žJfҩ5E,_nE<V⽕]qgϤJ݅WkmxPlr||!7;7(Xl(~,P aV19hWK'gZpY$9G aqDr~=bk4ñpcC$B տmx*f%q nF}0AR:!QtXs#1gPr/2lh+|E`,d %PZ"H\uШp^G ҷ {$^ (1oj!^A.s lC 2'p6ƦA Q(2sz 3d66Le\)Qibm/%J$Lr\U`Tc!\HgrSDi'1ƨLf1Jg2Nv̉C#|DQ%,Ґ@ Dh1тUpA}Pƙt(=6 a&zabϕ pԅh=0={FX 'ZE'!2$-*],&]:[8iDO`*e X:N,"f(Qgw,kP*,׋KXMw.`B,@q:mEb-&r7OLFźGW}ԑ SO(iLLt-}IW9^D!(aV wԁ^МL\U$br 9kl 'C+[D@2>[ư6nj+:j!WWN? wZOb+RV?,R\ rv C$l`) !.%PcBʇ,բIl8{$FK~gqVl!G/TB#UdhSzeu3JX05@ŏ*:}&s悻$5l 1HeW 4;|IB.NO=uMkE R і2[7hyl7 h?v5Rd$#L@'spc}zwzzN咖+5&$G8;:ɲ}?dwH)ΎgY79B^,d#M戬ŚR Z$BN2siv%Na@:ŜIpfB>GI ߸Hǂ0NA"ghy^N"ϑwRHj'G+5 IFބ`ZƋqN9j-bӸf*: 9r|)(隞ya˜#*ėK}9k&*Z::>qHӣT~9.h v-7 `~nc ) djBQY/ Ӷj9质HE;R*j#mAf9}ZRCvbz&ß,]tkӽ6t7_JM4c]a6͝:)~]Ab&Ao~e :CAjWu'^1J]ЃT&Z}l n9؊F\/4GKІ+I߾.8B94л>$NuLZҺhy?ѼfAwǩ}o겢vzF^[&>?*e`.*w=^.ph}Z-]0Huor*n;Twa#Z%dZj:˯phP50p4K8֖hhjyCÖ-t5]SY!0وEk]5`s:KFbEMVP͓:W[VHظZN+2~RC2s>OQ6.Nh}۾*tf~shc镭JYH`MZ %؁FnY\:뚮ߏ%h%RӠbTnԺ߉~ơ%д*҈MT1MWҡ/BoнlFU (Mu}wH"Vu.g۩nhz(mjݲ]sBg@U[=TivPY6Q%h^)sn ͒Q]n΢ muMk W.uc+ĩxTSj0@>.85#ҍUr:|V)kyK>TrzNZϬZQJ]B#FOcZh*R3 邦UPs RJm4) }ߴ~ςr^UVꝯc*^QќV}ł[h5v <ɶ7kTFi[hnsl]i&zCJȠ:Wv:@[敵vnw иT68tIwi$hS@&\ McC밗jTV8t0+٫\V*-Msds L,W;khj9-ZITѝJA IyWj=HBRqjZ.hZ jfaiiWsqJMN2 ҮJ#u K4r3UI-M} j4C6`5 ƜdivlYךGcyz߶.hCWZG0ֽarmj[ԍb94Ŭqhe1.68ZS# Zn*^Xl8qM>bbnLsUn ʊƶyp/GLcwnh9ɖ 2VaYbD(v ǂI`nc&;}jd)ʠgΙ]7m==d;y z3'P:6tEWݤta3Ʀ}s=utdL-T} 驕Gn>;Ro:1iarh(JYSj&y d֕2?Dܖڄ$hg@nCT9ZPq;>bZRu IIZf rF)jx#StXwvT5Zݩ^Cwszj8~JJ2 Wh]UM9kt熦^S-4bՠ9FYbZAY5pCSib$2YjU]]S ŕVx?e{~qCmjꆦ~sh MˇiGU'nY\VOUֱ5 ҍMsl IERptH5LZ3̽ߖR0ʝXIMtּҦohX0\V$E=q$X74:ɖSjwi֒:wJU8Kifz ] m{f!M8¡AݱVM7'Bm=tҳ5^̄ ImR*i8E'ukiI0`UlT;m8<'\[D 1O^ҵNzV^V7i=^~'ъpYRCb65//K2Nہtnf1~{?%>g75 PK *0EnP`+02 AzU$NR[*/|6ͼcdjBѻ. 08(o~ C",(65ps`-w;qyݨUp!w\FD\Wp ܖn+a NGV""^b#YDdpqlij fjKR^K4 `cb0 "p(nEb,oۚƚe цg ٵw ![X; ypc}S`NrOY(V1>0x^3VKpBb; ʣK,Ŏ77qc|XxWbjFrjXTgbpD$L{{0< uX򂈳 yS EB sqs%NW2F ppv a ͣtɀ YQp~?ߔ 6M3\-O(V(#&ۢgַęiVk#ǻXls~Fsx?qC/iDB6(#f8"sQ=p«)!mepx3Kaő[}Y'^^.kCs)nnxǀ 7 p;ʞ$ eͤ4ʣ8+͆IO {BiW|3Hcr+nKo #ܐ\+:wk5sdXϓ#HqG0nWCR0#LMՁ W ;Yd]hHА` R.ͳ.trQ5ށ)<W)ɷ@ Z\`'\ L6B$@uRtH`& Tyi3*يVrd^ʠ<*9{u?3\sYIkh*=e:4ԕ)C2gS[{. vӟQ1i3:,sUU)l?MF.234XQP T.4ڨWy- y.% "$8c y#6pkA-5WP&jo)Ktz.@RM@{P'2>/4uVĎZGS0Q&' Nxmp(,bZMj(itjEc02wMAs.Y\NLMb:؇\8y6)x!X tHk=,u4C='Gn吿B"a|ݙXff34D18GM: 0Q{Gc4#Ĩ} HV[>"-!X!9-w ?dwkYuc*:v;ORL 9XA+y5b4inr bc,p,8?zfN{D*Ubh ?Oܭ?ϏO菞׾I߾}i3OZ WD ͸'acFU)6_#?oğy/|.\ .\zs_=Gzm+˩X3¯ue=W}}-~׫oA?/>Kۿn탏=~xeTI8+_~=ݑ|_hwWTy>=ʭo7m~.m~5o{ɋj=>ZɳG|㷾;>zAwoϧ{o7^Ћ7~S+7w ȿwߵ~]uN{_zE1eNmo@o_{~o\vpAIWl>9po4x]p?ط~kyӻv;5%}'SwK?<-?G޲c/Fz|ͷ_?}\ <~b??qLyEs?kO~W]Cw_xoٿl} 󽇼S_zb3?k_^|wO]|o"C*>~OO#y3nq7|N|~ݣ}_ǟۿ󽧿G?Ϳxy?|ۓx{{_}O?o}ӑ#Wxg5ѽux_gpLf}~7Gx|c/8y}oy mWr=3wk_ F>f֫r`#_=ϼ7x'g}맟uM]-{F>x_KJKmkp_}s_ػ9׿p4:'9Kp/"$K^OB#~{OO-'F9_ں75ON"4o}~<{^TN}gs'_^~Eog8|=Oc} K'uW.+}>;己=~~S=O/<SN^=_"5_ =7oWO{=E]'h撿S*սx[yp9ќ s烛_>j/M߽[6>N>]y5{u/[ѿ|z'ǦGn~'M͇ؐvޛ?uߜyɷ_]?7_{sF/xT+}˅z%OSvvS?g^}hx𚃋_}ڎ:=x2#~U𘧙?|՗<ݕ~c}Rz雟{uiZyž[6t_=_GnyKnz?>?>?{ G=go5yC#?=v򇍽\_{i>ˏwt/=C4l߾257į.ɫ~7OU|׍̨c7g|)ջ/72G}~<џnW;?1{svv~[>\{o9O/~.g_`I7}oW9?_)x͟g_a;?Rz2yo|#Ge_~o2uaO0~uu}~V>+GoxKn|/捿LϽoPcy7Ñozw%#OziGy}w-˯%7U:O nׯ-,<px3M<Pj B{HcYQd*jt^YQ3jASl) r]0A*&CQUW|F-EAqaѬYf ]^`8li h*]Cl@>ǨZhEZdKqww (5dH3jMI=䡍 _tn~59Z"G;/#mao/0@l"Y1,Ǿh\o(nTp"H/%Ni0%khRu%lE 7ܲ!tayKmI-KP_ sѬGfK@tM̯h^OwSiၙL%jjl6j-x~Rb5TEM1jaz6G6˕24mK99 FEjkc#3f5x8ˊUn5 ePY·jmllD6"Fu%߻wo4&a(6͝t3BmrT;GbQY8tUȰ<;ԗZlax0J`brb01X.&dMG ]U HF@y:88?bn;^5 s w02#3t2a"c8F{b /)#0:ut,0Q,#"X$E=9R*33\Fr#ejZV3+j&A!UKxfBSB~RԥDsxYj%u?)Y% ))X`3,Ҥ/T_8\dpb GK@ssR$X`e^ X 6!P kx<>Ѭb(өM f$dU英/fVdYozg}g@óx3lgcDކgC b  mwoؠε*l8hg^4Sli_@|0cӃ{ MNRd|}dR5a&|+JTsJVJ[ wM쿇Ʒ䒯}/Z랯=꾟;ni7ڏ=^)?E?pR?{eÅ.{*{ʫ]WW?_U?cO/?OS=OȉG~i{׿Ȟ>'O'W{ 7^,2_Z_z_{_Ig?GZ>gk{~^Կ箺5,xo}y{eCO}[L# sNjC/zqO§}'nx~O=do7}7|=k:>?u٥v+f~Y_|rS^w5hbx? o7S<ڏ?goD"ZEL)M-U0M4 IC*!m,ic(BA. ̠gDPeeEqTȺ":sIZљm$s=s=sOM;>ЙOOz哻mg?)'wWo<1j>vbϒ76j}3t6~[bԜIB;nr[8{|y&|Pf[Khf't>b>U',/}bEuR:a_=}?ߚ_W\w7~>`%JM;O]kTazcl /z's~tI/<Ӱkg qΥՖ yveݩyK%/B#k-'%{L޹_M̿nӐkWc׫[^I,䑅K ,8uWw]#{>mѻzTj?m?0ͫ ^u#Cy-tp Zg[=;yij/,9-x;۷b5|뢗[7M}#M3j-.z}芟.^Pxϭ'ޚ7#{ٛo_1{߸=ﺶm+v^yk~U眯[]M+^x}AÆtkǞxfzc֬8~Bn؊YڳG' O9;pmۇĮ}?W]?;F.>~gw#K?坉%'/ڮ0g_zs;uIGG3{Oz77&O sul >hn,xoV9uss']۷NjR[}#-u^mcۚ''𒓩OU>Xw~nEC֎7yg7&+kɌ3FgC/n/zGLtްic_2|hѰ#ӳWôϜuľtų?ij3_DO>hSɖ'] 4pBxQɣGV홵zտww觻t8g:w;zcK/ 1e{ןp'i'w6=K:߲xdy|;^3sE|[ܻ|eyݟ?nq| e_\^0 ffv+_;`狳devVmC+|>3nlPl'[/f9ag#no0Cj/{nklvft34um_:^Y0zicr?a0+'QP?x"c)UĂ*MJ-+q1{$h?h#9,jN8GM=FMF2EK^$Fi*+:}2jqW(|zQMSJӠPT~Pf4VD&+2Ez.M2dB(-تm55%emBbD?q&WѨ`,/R1 Jbž05(Z6.ct!ceKdq6 Eڝ|9WǥTrvj!R,N_W$Gf%H*#2$`e-elqUs5y.,E6.6`NJ.3^-ep}͙5 O(Ñ2L9²>ÅLqd4lbJ{,Sr+,iErFd=&!ґoUi/:x*DW3f!!($ jb+T'FlO t 0XUE[Y_K2X RCy ӗ_*`X޿ _~_W}/+ZaM>Jĭa6Bc_ ^])00^`J1Ariơ}J1v:ԓD><6-AEf͋Z#)o>_!N'5\! ixόjLp4R,%=՝q/5:؆9ɴ ,!0ZĸZ4:.@?N #UnQSs&?{(SH@kT)# n.Z)]-LI^C)(HLOj}vڂ +c+3)C A,Oi jݴwFLX&p杆\oR$\1I ^ CÜ@.hT0:4TcYI^u8ͮkӨnzbYO99+=Q@7{&F2uhKL!ݑ*BD}3%dF+Qmz<0yKˍG=Z!$*Tݍh8m#Tk!?R94Os޴qZN0 N*`@# a+̍Z$0P0֪"VP;\^4g8tw0å$A$ HCZF+ HǖIM|}@V H&o 3Y#f h^̟hk ]v$X'; Gck1N،8z)! N` DQx*2VlA4:QM$ޑ0܉?>:nꤸC>n9P$mѠ@O- qlmL$--RsaG'?<K1{Ǜ31lXmvqVy4 oNo:EP :_coŔ#@Jyd$M&$8W2T 惘JQY( I*}*#aBdgui)=w%;`ϋUiDƎ~O>^9&1_mp3 *;]P4J )R/o@P:/Uw֔ ଩gk "H%dGX^{H!S~a&Rm |c+sn*9'J~H.0w{hS^bO[# P3#~]^4"9UU PM=쵼V gSeq+OËtLaf$]uX2Sӡ7/lj{ty;%mv ħ:oE2 Qv(!DŢFnD(CNO [ >貙gFP[мJ2g)~Vr$5ǁ$D" ,{>J$ onKsJ.RzK.Y f Scz3sHA?Zuf+s5[qT|\oM_KDj-nX^X#Z9h4#$R_D(rs"a?V}5UQn2b,;/Sd O3qOJH8IbXWҰav[ɶHe3xIFȥpg*oJ;2wD:8~E !e86J.:#1Ӫ0Yu*~64q@- 2hdnE6b oZEnPF2d&lu dvj['AEp 6-=.r&1 9mdG2MBPT}BZ9qO||A),ȭ7#U#xL?I,5 Gyn2mh*Z&q gpJ8>}br;$&>p`3@NDדּbD&v18rk.`4^;א$P› 8MDU rc=,G΂+P%fI%{^YYQbꝤTeCZ9 ,rW<" yD ɄI,se^3:yՉL!=qM-o1 g1Ysa]s\Ϋ UDpa{JtPmW )vUeW'KҞ&cQ%e4}@Uyr_y~}`|;qB!onqjWمLaQ6^Dq鷝h(/5?Qw99"yet^ts:rI9fu:v/:IKb'@; P[G\^msgP(C5)* =u\+Ҩ=q,S$M?eux Lx0US.^&p8MTKzU+x̢ 08>Yd *>1&u:tAjvPjhp..C8> t^r3AZnp*!ni}bF׃t$fɢ!J/ KD35`qzFgֽ,ilQqP\*ɬNb鬋U6pa Y{},e7\A ̍ D9'#jơu(;EG7W,k5b{.XwbUڛD}jK1 ܗ:Blw174#GVUȫ{x]5ILj{`h$IHBe6wiYnq9>N$xךweYZA<.AhDgm)ỵ@TvQ A W'uhAѯe^*5`oK0pp]ܩRjŷW;QSVR'Ú^h)OYfu0W#G DIf󴬋NS*v<]^8oI!x#eyݣe G4vxbBfH8 e+pME$X)Yi!%rp#C,TNyvBp^q "O BV^t͊0is\:b'i{6R7|I*Ʋ fL Vsn]z)OdxBRF 1. SQ 7.],e$NET8\ZVITϬpfm2WAbkKTzcEiص.di'Zgf2O azEJ$Rg9.=^Jto} YFfhBx'@Hp 6t3}dl恟TA& ZamMc᝔X%q p1bS0H~sʄƭu8v{T'(}'SbN_IT@ЎW}i5p*iH݌&5g?H ag$6Rz"8;(VU*T,l$ƫ2~" ySj+`’daQGT;U^Ƃ-l{E urP9AӋd\Ol=3F Yy6d:Ӛ(GSP:Dkrؘ:CP҂Xx0,2cYTd4Y_RdĢ</ѪdjfHmz,9s;]dwrnaX,k Ӎe=SLT.ek\Y=뚩uygCGfI[b:̴XH!lY^B-}^98k> hTe2PQ%S (E^C*F^屷!^A/pXSy*lxŷnE}#- Q:G[*46h\BL>T '^ͫnz?h36 G ?7P.X>i;-kt,Z Qb! _D$`s غ}ܪC:qa3p% /N v*[G{g{ #A]Tγ4Jdoysqp侌6|mh_)*UQ=/?c^h> [vR R>Hfm;򖸳d ڑ,9e8+T́&7-Hu@n6:E oehH3qݽ5 #ynbb1 F@+gCIB`([xc?9W|=D09s]:#i*z]tUHu_Qm ae2j2xbp65A˿h/> k?*1u6U-F ;Jܢ\̵b6bhĂ`ze6J5'-ʼnVX3*ey,'(W kelEenع ;;ϩu+s:eD^>|MV< _IuINվ~U"(SU)nԵΧ_ 4RòSYA;䚶*QS}TUR)`X&ܼ>i J]6 fq M$2,N&kIDRŖߊd^?v ?G\(U?OmxZLQڜbA,'^GҹLg#6E]X%|@$@tZ+dH`3\A;n{Topc4ETcD / 7m_YcZ+s?&!McP˨KXlSӶ3bx; ڨrB-xxesDjωz{o1u_S|k/{vaY 5f8KdKD\"Iu!NW$%hhܟ{XvU2aB3]11_@Au~%T+K&}+PXҷ,s>2Zq]]̎X߄I^:Cdz%8P 1_ˀ_Z5 g$ZEpCpJ.%Tӫ?ԝ 4!#]dE!)d ʸӖڥ,5ewr\Fj_ՖJ..e-%C;;̓Ied1n#i2 0B@>7 )e_EWjse`e+-(UYliDub#F&++7l){fZZ@ Z^gBZ &@SVSb9A-BčyMƼG4WņmrTSe)H Ɋ MNi_<Ƞ\ !Z39I. P$w)DEzYWU"k-Jaׁ410!o0"H,t Ǻڴ^um伨b٩Y},U7Cc1kc`&(hWI =SfCƆۛu]oOk+ڸ5me1ˀz!2'z4H=?A0=;G]T|՗uΌLxo?qP>Axx/]^J0& 4gn9w>7 +J5TJ@jp.r c+4i$.{E+%lKX5H( 'ΣE1IGAɝk8 @NU!F{8HC:oy1ƒLFaG ̤U#O\T6 J֪9L.?g䈐7JK '}ޓ,]co`Ȅ4JJ2hrԧu 7в9kiYܓG%iQ} 6x7NN'GͽݟW de[lgK{ٖjSPʘtA^T ۅ?,HV ?7&Yʠ55ƯuJ^2w̪وE(t>ۥ\+*)rjhC93bC٘pvqRIu'a{ /o Gq2~?I7 qz8i˽W~wpxAW|"M>WZ{}^6/$eodu.w;dcQ-ZڮEh*($dW'A%Mb׃rqHA)7~ZvP5@SbA fJD6yT0ð Hx$߄!R2+-^'Bq13$1gÇﱌ3O)(cfR*J[Uf01hDz cQ4v^5;;#}Ȩs~".gUr!ܛv -ke]qVVOЄEHxX=7e!1.!xB.rޥW_~!L4ӟzsL+jU,^lVu+A,!*qVwOkPTkb@ofU< tʜU8\k|cQIMuKwHs>)nViPD"oz~2J ZXZ˲P2 O寕{kļmZɌvD.R\5滞DȝYǒXIqxrP4>S&<&lUo"&{B߻sM<cHiI9A{س&ea@br(x,Jڃ*'\c09j2ٞqE.?9jm=-(薤.<o_{p,S2G|VV4 ~rsmkPXk?j\ޑQ*/IEy5a'A׈S]).mSB4e~]q_SV&l]#E۠~ hǬFX?WT ЅQbK:puMָ)0jh[Y{'m7%/Xڋ4DP(Nm8ߺ Hmօ&ؿK-Ʊt)8<9#Eer- gqQ);?WK8r[U,Q'@4grO F07Ø]H/(0t9lW2t q|Tl*m52$: /( .KOP,ympˤ'ݚSQ2"'*w{ Ta&Uq;R"T0HzJB %鑋<߈i-H\mlKאCf礳|=Vىc~Sw'Q;J0FF,Ƞ-LW [zc]ӗX]E:LSBqF㝡9䋦{϶\}1°NVb-39U rKe'MrY 뒜y3>(r|F6wGmVX=}ވ" =#aEC sR#P]=Ψ;hL{$c.MsH XJʯ3Juq&˙,Wq\˝ Jsl|fIm3WE1V4CF֟Ӣ-jIIcZBGX$|0*Q} =Y9U"%V×>0r0gfp/`' |2߻"_#r\̝%mh>8iя .a$HZ, |0+•2G/8E]n">GoO3;+ix:^` ܎7gt58B$ho}z8?ݒ )}|wdC.(.0%oW|=+b'GMB]t6?I&9d _2h.箅W#fyD(7-vܤMna"rm&dN%mv=f>l.jB] oGl:y-2 {7~_€)2cףi,G`3_R@c# ;r=Faȧl99ix֦f И_zմ5%N:A^d4 O}?[?*n` #çZiF뷭MK$@|2  !6g8֛F֛ffnqW|a;NP}2[{LKw3lԄXSq#(g~@J8=GP.]MJ)6q ZBSH [vV\?chd֟onl{CV4dsMDv?_x|wksyG;w`Є!!6_?~^P;±:i͝z_n~PSt![~mllnmnw ,ÝUgʏ 8cx`6Qap769D&U v. oE1y血2`&zƌa7 DIp먅dch>`޳i[ vXnӽ=^ua}cgoc} ">cgR: P릠PU5EM6T/uɓJ3Ɨ-rGcˏ=,L0,') vyy/r', (RazρtU\x~QfC9A<_#[ cTo'$HfpYi֜]ݸ'eCX)<1;lx/!8v(VcBK㟎:)U1*S FyбjRޫW8N#螈{$;wJ#(9-3=5%!=#jlK%[$MTlEr˞-?k招 ?xj$f'“-8ؘB='s:+~dKHNj zOAfKYf~ͣ~mr9mu' S틭VRQ?= |$' G(Lѷ~Rderu}6I=_wbG# vBdR#p~ؔO&kC}@_g75n帺M՜~}G͚/ֳ D'Iv3ׁ!5z",0ru}5>-J?' SmZARj_gtKcuB/f><' S=5jEwb'[Q?135gMzG/U'ٚlzP}-T0lVjc**c<'|ýKQjϫ (]&qT?ZYZ~tq/in}nhޅ:3Do(,+c#9]N/m"y{u.ͽ%='c !YrRj/;KQq'@#V/kZCZ/lzHX1$s8ol`&dGZ:a/:*O +[|Yei=w':4bTqT3PMqR',X jko\ZIdԩM R.\ar`ذZ*MQ/wVeIR]>z~4V)64Nɢ,Uyb645 ݫ®FQ< %"D\L&=L6 ѥ{}Uy k5j9k2GA3.fYy0J[w*vFmvM_2*8F~d^ *Le"3`43v:$; }UW6Alm;#1MaL7֍wҔQ RRVM *FAJ d~qwmPP-mK,,10mǩz)3c RDT-ZDMS4`wOzqH4ChĮ:ٯ%Ͽ3Recv%Q-6! }UB*B8bт,fU5() YU aGƳdTW O?bWI )Z a fR4 ?E1eqǃu–I s1dn`HgD㶪GGF]QǶ9&٦ "z*'ӱ["rW+Zޘ: S8'a)yf|_vљimR> r\2C܌VrM:r*PHmEyL, /:{In;ϐC%;N!aRg)fuɀ5>Ű~iK6@z²+.怇Dv >IX*L"?1fGöO70d3B^x t@׫6:R>d {RQ!4eI֮הs<27 ў *RW-3&l603]ZcZo߰](l"r+I*s $eɁgن~]a^X[t / +Z.. nfQh,Rc$GK\Eݬ/Y!CE&6oT,ݩ1,19"q*5AST.eQsgArP(pӪR\V#o3n{7˜,4Kq9[}M@P쒷S UV-gq͗3ө(gx6^=thde@J8;͍o}+g5Ag dKK0dM]$ц%"hRVu"JiAH{"ye {!Oߣ3-[AТ8A_ox)ԗaT7170,D厠l<te9 D^$ܸJ9F ؘU{joh{k&BU& F! Ƶʂ_?/ @ʢdĒօ8}4&>3fKl`d_p(l&GZuؽ$p =60رJ5 7 r%sxdPCh%CJR-hyazk ¦1ķtfut%0zj,Z^(OEײhTU\H;;xt'"0eñ)85QCJ|%͍;ŴsT=-)8m/eR^ttu Ln$Ʊ#R1q! B!׫QSP:d?4!,{fb\ {UʸXQut5Ի5b#DIZQ}3y+Hj)zZ(r;Q*] yJa@G,}#/ Vl淽ݭ2C&DD0/}JF,2R{BJbVXS3dJ|~b JH5²lQԢRH8 K'?y)4)k]y^@[wx${F5uʊ; :ա 6QWSOVݙ4x‚{a$ۊ j/ B c){#0ӭ[;;=wnДK(Nz~`!J\ nj j'2-["|ƓvGF'o.#$(k'; E@$]oo4[+ƌp޴vjL 0D `x )(S&t%5<|aZs̈al'a:dhΈ2ҕ8 UҢ#LQA/F,D |uaODyH+ssTCz?Kbs,GUEl46da7Q7?+C(r?{7?6(n5vT(voGztd8(hS,pz%m<Ս5̙CGK$m&I.W^v|0<ԕȌvYYՍ\B}nMN::Lcc;ufdeJDVr,kRˀ/H_ܳa6 ˩272V^S6ެ1ŰPU)my)z5|q޿[y.zGf!+3Z dPm԰7gkA@o`yU,V9$rWKh um(/TMy~yK *RgX Bncը,P~0NwmiG@eS]" TswRtȬ5ǵ6]E-ί5g,s[2mtܲa5 +[0VrI%=DAv+bf-u'ftk+]4L.܀ɴ 9lzÕNP 4Ho آwF=w&™+RV˙+8DM=1gMX?@ZJY/ZGRd( g~ɄjוJ#"\ iQ+KK?CQ7@4#(v@wQbXVU8Xb`dž^LIDq3:7HUP + ,FTyLu<4WiUK4l?zW_V+HH+bp #K@8z%mv`^ ;ւ:a,ϵbi!647Ju{ˤ ~C;R5(RԢ [q\$Ø9f;L7E4DtTiqX.l%,bMT l|@u8z0@p-vF\DKQbgYm֙LJbf^~b>C%U/PѨ.Ho/`n`d8x(~9|5FKJ0KϻQ.x!ijrfQœb@C'5fn-h`5$ uZSQ- "d`0bcw@oCk TnG\:yOi8yDG6$sCZI3|f0H<*jCoMg'j7n?V}ut:uOw>c˩XJp)ZHXVb>^o{'*Tҁ vO^*ƞu1]7WimˌդV0~lFQcP\E:./A9 Pѽp=밓l]EQ9Os0No|ZWηX@EK$%t_ ƈ{$E|YXiOo@\cw(<ϰ\g[rQ4 A$2E %Yt2QKTC~E'ʼ؎ ?m"cȯ-Qf؄9el!W">&cn4ڑ79Yh `?Ln8\Ot|  L˟kNY&LCa_h(GׂVLM!jKJv~caVhURZwXGA&ȤJd$wK+kn)">Wc93uafHtsSHJ@njvLҬUe֡(%6י{Mxk|es,gjtاkol>| 9řz #Qi=|rZIuO+R[LQ#U{pT!E2CFgYb~Ug+z/'}T#9zZPobyX] >÷*4}D  #u@82SdGXcE9||}w˹R&ɸ!9豛5ɂ~!eAŠn8>)>T/Y4o"evlebL<[vS(Vi6d|N ՅrhӰ0) G6Ku`RIy`"/g~_#A$M6DAmJi`4(9' EqO&6~:sڧ400[Y?k׏~knpԵXyTJL->-vItHr*TFuޔZ` +&=2D>/L d؎8+2S^ɵu.bd pk.]Ѿ_TF4ԧMelZ`KQ|Q<`0nDhEۡBs'^ M=6Tnb]^x= hC7~MFw$kšOK9ߖ['70-ov=3X& ,Vw(Mh!dy|UQx/gmk!F&&h^S)esԃiNr6ߛՏI=TR.euHr K`OE2TnrqV\)B$+# #{Ba%HMZgM'ySK =!)~G*շ- 5ߎI)qq|d+l`n_-_TӴaSaR\b(Ǘ=SiXYNmb\Q?vfB.v}RR#^YR=OHؐfOǢlb ;lq┮Y ?EyɈrWOݛY?/v]YKOܝKPs3@vl R[G3ςcP>wo=n 6)Yw' S.ݒK026S3Y|UJ*5D(U6ӂBY,}Hutfh=zl ˦R`!Ɋޡ{ ;3 c[O@„1wNUkqr΋\N8tH)2,U O`ע>.$&:8{n_n  '/lY|qc}ض'R8a @{w'=(Uq!"V g&(>]:%n%^Ny9$>-[Χ𮍽O ry &`FqG]"qdp '#d1=sEh`g![(]lMd9[zꋩ+E>߳t"=4gۨ*Iyҋ _k&q\d#{vb+tN>F>_;P% I{y`I2\IIQ[ {H?|ۂ0&奈7y@):w~V],(ϕ59HpUȝD4**;SVSn8k`;1L "gMxݜhƎU-P%uiQ.Hùy (;l990 :QW=D@Ml@n]4OªȪRYWℌC򺱷sT93q͌38#0Ź)m:=]Oҵ*GzCd3YB {`[] ÔO.}ȴ]1O3^pAz ":SE t$J*6>;FKRKB6@1OV7jRQpeי{\+>sp9)Xgr4@xiU^aA5IHbe*m@ɰo eH׏whTp#1g瑻|ufw`>+6)g{0hK#W &mZR9Ε昊SY.3Ȃ*h%ȭ +Y8jJZ2BPk*uI⦥&aX~ÃC|) 7p#؍xFwmE &: UZ >݊0úAgn-L!grLN˭[GAkwosKeF6,y__&92DcLvɧG$/[@*utbV<_V-f)8q] xH L$E4 OaQksɋg'/w6EyW,Le;)xK20^+i !*<vȎr^kK&Vzk,7I5n)@MiSP-[<6vw`n͆o dSf:U`mL&_&X$Txvmt),/M3o%l{ʥe}'Bf؇,H*!++F[ [ Pgnk[kKhu]ʚx]9].MKۉ< z,rIK*FK嚙zkc6R'PUլ\4M'USnYk>(*fZ NhؓXSGfǂC;*+u iV 7ts!!Lt!]'/9onmY;x{ +! Qa$*2oQQ;Ryrv)HiJ@L[YUe𵘶Una+RNvlu7MDLLCܑ Ĩ0ĆǒjOOά dL*j]&&7[)>;bӌߦE|lZԒbxWJ01UD׿Ҽړbn<2'Ӌ*߿W^h5!ney]qUm3(3w7Sh& r_$DU[M1ĜVr~~)?t2;4xRjK> -'7*E@OIANCB}:DQQh 5j$% J",? hx!wOdy2M<ݖEyVoDߔ_,sٺ5;{Qӹu` aXda2[Y.u[6s  qxVlmq,y(I d%e oBs|yy`6~`~zLScBKNuXu *8"O )VM]7#U_(eR%}R`fRz=˞ enN_2 c-(جdqZ(LV<:΂U_ZCe ,`Jޝz9V $Ʊi!OV~0{7nAރHcGH*hVCnpB7 ']aևft<582וTXbHEԨם|Q%:qz\=1>i>ЬiU(q\&Ao2#i6DLGi9D(2J8mН\z^d7haR%T̎wM; k qF!z,jH&JEĝwr0EV2=Qb+"j쟌wCt`Z:(HAСs~;W!t1 n3\cr6cg;mkgGؓ, ](CS- #>^<: g7NQgQ1Jɭr -.tƩ "/~,2 0tz0ggd6ހ( |"5 Ut|!ȥl֢ ,Ff{b$/E::~K}g0}(JNA|cdn/ JK- 1no 5{PY*;u؆@ؾ"a12 9ih l"@;lRHA:'GqiJ[=n&tn XV2)D /tIjJA Y<"p'5 %)qrYD淟(Pn~mb-pN t#a=/>gQDP< a%1MjW EwmZb(~Igޭr;LPkݸ(eb{boHxeZȒKn(Y4rD*dad#Ϩtsb]9ǥRE'g[!ÎQ)!~d@w/^.^U\.]`֤d[w5\,5˜ƴR9"+銊'(n!(yxtG=@,uv­ nn?K 1}~|@pDS<4}-ˡ`%4Q, d[n3먲|׹\wi'D:M)"ִ;k8y}۽z&E=QzCgtyc }R/JKhQS5 h޸4u4m/MJ"R #Nb,I1ү=@-U Z@^"8U\W c ޭ!A:ux}PI ^AGĬ9;bPyUdU KfW礣Hȫ(j* " FGG ȮUCW^,K 2ΒGtd`+J~ 6ZI#Mk ç:ZDGX;FKjc}fDs(EC&İV sN|M9JM~-7,p@k@]Sϯ_-㧨ne|Oh2⑺+=qf>J`',VeʶɛJ~`X}9K0R &"հyDX . wnp<g[={K(/W 5{35Pul?vYNB keY,Еc.>S29OkOn9ęTts1R19XEz*b$k)"yjо:CGscH>Ӱ_=_ޛuI}hZ\ gt6@}VmQU4DvQm`w]{~@Oas Jk;flz!yV!eG`Ji:\w ň>fڢDj0I1w@onWf-ҧjED+Ud¬] 0s qYC]*GJB|w0TsNrPe"~΁I͍~2F+CP)n5聲[AZܑ@|`ȣȫvVQ+!]W"%SyMJ=5Kv!JޔeMpN4ShW+ Wl6FR_EHTmL wH]7^uFU)NqTYPcE]DN`]F o~֔ ⹳ыtsz X7TA QWCISC^X&4*H(&h~ؾOӢp-U193eF Jd-t&u#?\xoqct o V&j ,q/~(I2 3&Gg\{/bcn66r66vp H_]jq>9VM#27)_* Q]K1D%EO̚ika 2 u/)q/(.(~WbʝujcO:D2=ٿ{^vTܹg"1' lc[Cr & HX*h9 Hr8_JS]KAҾuwRF`tmitci6I[e~ڢB^Yn&qM69,w y\{JE51|K!9yi͢r)}"+SY\Ȣ+5ִ?<~Mx9NOˢ􏢦Egv/f\Sڽ:,a m䀺Ym1Hz/~\W}0pL\|_9oߡTror4{GRSvX,L EE:c3S,Wj$ k_3:}&S{YHP;~؍%]SH2Is }6&NWY/܄XMv,BnɨSؚ$elQVOC"8Y8+)-0PsU> %]^z|?Ǩ;Kc]zuqLrӋmgҐܨ}t\sB?+bŸt95Yf}hu]^ ( 13N: U1TI79}e۲,IRbgY]LMeFd<. {>/ς*>HZ&xkZ *4`iez4D"r PeCeJ}Hy (1G" :9Ŀ?wX8^hxeKi予`1DvA#H3dB@O5Pp&#r X~ O]H,҂0|ts~Yq%:.dCVU/hOd(81ENBZ*$4y-~=&o‡}%; q4j#N˯ۀ/QKOI'tN9 NOR*_O@Ȧj4|ѭ+R^"eE\ZoWG!U )ZQȡak+{,_,KJۃIH]6>ET=vކVT~#ne&ܓvQ8;@=/{MGEi Ċ[CるK71 ߄* fPcW x:V%g^9L_y'J77cQIЛŅ1CWXvmc1Wvl0Hf/ٚ'`h(zp\X@1s06!9B @uA=w:<_ӑA-K7VGh eV[\c4Y95Q7ZP[/LvԥUdR15a8]!ѹsSC"T.us;viPUI!6WhsU3I>VE9ZbsŸYMv9S:6BY2pO=gpKdw%⬨jqm`ɍC)gqSvb%l f—8'ީudoT731I+lfIF my0.4Ys^ҧJ͔~߸-;#$}]MM rG$pMMoһLKdw6p lL"Ͻ'cm8ـD^7s9a]a2;p}|ala>bxJjA6Z-mlsg)0X]F=iW-(Y~$ -ltqț7R[tAl&I J=ذ _*&J'?w@UHy~unmcC*Ţ6-0r:O[ouA$ygtgxso}9~ƭ~v#%h fq`(`P``ф]WpV>8/-0x QQ75V^:p%` ^VB:aһ )t2Ǣ|Y1/ ^!߲(7 z:i:󥯠I0P0F1T]ZZ*ӄ`(V@N Ñgu8=A5˜e)5)l.ɶps<V!"c+l/(>DzbKW,VEm`.lS[׷_0N;Of zck#tA! n$(Q!o™HO.+8c^n+L*thEw^慽,aB[l^eI^eq΁^0Dꅺ(,T+ U0 "F_p5A.lW')؉wpi聢h@PE"3JBJ#>)]1$Vq~?z볂Jn87-XBo/mg q m46œZ#N6ԴIm>5n,NJUecf%=^(}#A#Jy !sC}ohXw{\Naullcnn.νOF mj4 zARfRS$y4=~6a($&#+Hq0 vhjfQo H@Hh4$GIHs@7':L # ]O[񴕢\^񩕡m'x/V jlzʫ{K^W+Rx1(E]y?QV(E7'I^3A ^p{^x Fr}i yo D؂[*(CXj+d(\8I@+ި/#`56+/K~$~B7T8<>UE!:Z߻.7<Qʡ,SԜӸ="Bʠ"[ScyStuK4c;7 "O]>TI@l9,u$D"R\ҀXuG4#XDufN!s[%zUVۖNZx2wzA1$JMfY$B֊1aZtzz?f,xpouZbOz !Kh?awC '!^DQ0ꜞ[ /j042F9H68C|E @X,$&we棸ﵾ*e{/[wO,|=/nZţ߻":G8S#k/7󔌸}]ݍBA]p#3rj+[r6h^DW tڬ髃= aD&Z HEZ{i~P"6JOh$uу a-qϝbፒnQǽ6t܅P@.Ҩ 9TZHA+;Pj8ycB'ЬџU WU$#?;_D%h.|b9g=/B<Y10@5sVU7\Б_N~3z1&-XEAMR|C#l,D?d86?!Ϯ^( )^pGy!V"-87ͮZr&χI_j])xרA׸2X'MP$g6R4M99?gZ,egC?~J5^CTyi67Z-OL;Po}վOG>6I12>_-"#U7` trJдE2Yl> qhb6iBVc~T GssF͂MJ7H+V*%޹t需kv*ۮbJl:n3٦#t >K#na*6x:IDGBP':9Gx$tƇUI@JPX톦]ņ4uK UļX^.T!P2 VߏIG&LL)`ndOJu)ސEbB8muMg-Scah+K>y lV${⽠Mhy{Z)BA t͢*J ̜:H/qlwK[7&[8n'ql[ג9R˕:nck&C .}M֧0,oa~%Bǒ2!fU9efoY: ǖ+qleE-%s4ÿwD&.FZ;;GeX2KqGbku bϯ|`PA܂{viN8mǃAHS 4&ehqLf-ÚaԽIZE٪ƹ ufp;!r*e2M)0.W_J:mڅ[fNc;Xs 0H גޤ" gl(܁yGej lu7BEC4uYecV+n u@}]GXxyX.V,CCj c/ f*Hf& 4ځiXkRCްߌ u 4l\jnmm:pMddd<}^hě}$鮑IbmG哊~jNu '6 wZ{RљwNcSw|6^keRyWKר8V5/,1e5B\kwHTGvXIw9t2|ZhU"FoM5 ^SnO3@J2`2>89:wWJqЧڤZ<&-'Ոk\4nt\c.vҙ>[g#װf;wp,="IgC_EVa>Y唏EVg'Ȫ6F܀ZkʠU3U!;!j+4qK*u6:IwOXǀOZ|chێkl;:ǒTON_l65 _9M" 4KX;Il>Y(6:y4 z,qu3}rjWV;QVwrz8D^K6cu=sIE'Nwdj;vښf^Zv5*V'tq^k@|n;w`u18Пf)]b<t FFxĆO.cMK+FEx˻kÛrkcNUg2JJ9`M$JamN̋q}\S'tQ /cyVTjl* {8d@5Fn+!ڏ3JԉӦwžD`*p~liHƌieV<~xl9X[S^2tPq t(-ňyȞkaaZ^xR~/2Hb~hr_-о+x+?fS#WtUԖ_]#~휘v Oq jT px,2*ݴEv\sfcez !^;,/[F6fckIft{0pd#21%1Na}cNaStqO\8z#BF&ޛй. ciw+W^\~3>(rt9[G-+r |zM jUFQjZ Z|BP/+mrzDˆ'QhX͋d);tt.ac`E?$*'0Sntx {0kXOrè2% aM-aokzЉb7) "ee7r-׌w,8NPl̕ a AΔkđ&71 Ar&>V:ш c1Dq(Eaᶎy}DO^D  NjABP-1ӕ[3_11 *)Hs3*GS X9VMqiNP+ ,6Qs)zJ^_1NϟڢX@1]Y3DHjK' JvB)shahwjPbq_DlHڿss7_˥2ir[}3al @cXY?șH=(-ԗLKtmrg ]Nݑ|#N%8Yꦂ8I"ەct cY bKvRL 鿀 DH6_ +rLk\֌ 8bxjj)d$>j'.DOj4u %#g0RDHSw$Sj kya;heR?v\=XA)G뒝Su EsXM/Vޑ@%?Me:[ȪZ"gA7ly0ㇵ ]6}&E_?/ ڢaH1%]p&WEC?jKƝ*-&5ڤD#޴©n4f$hqZg3c<_X⽨6s :nݚHELDG8` 6mXTzc@VMw 2J0J},?_?XZ\pc.ItoX͟qRá;?znÐsƪ.Gp>0=)ay+`=a&!{ ;f倄:͡[֮HTj5I *$RfdV%i.KyiyG'KQvmt'S͂Xpt5w0lD嗜 Al~vMF2vݮHeNrԸšݰ!ג=P/\4jRwoF䮝%|']֖>8,aHt5x:J^YOБ䞈\hVڞpRua奊@wZ1Z!p X*~?}TRV `@ڌK8Lj4%NYKXs#i;R,]xPxĽ~#h;a<`D(8:"eDO}y$ ᢱ-^jf*,Cj,S32?k,I/cuEq㯗Fn&Ruφ`Dyf2N2%xU{Hb OIh. )kV_aXޝI4w(z3VPs7Bo.P"[8-t_0Bݛ*1z$ۏ.cXުJ$kܭLb "9rT=sʜJG-Ƌ/: 0j9lM)7K!_֫IQ sсh:c A쬬kLRh^X Q[È[rfSHyis^Av[ LjXP%1 _n%+ uI#w@\dQs;# YHhW)άӚiFO$Or]NJ$5Ce4ˍW5^q(kQ^oFȤB`c`Jik=C\Ʒg哂/]@ŒnLH{EZ Yg1րbmY;Q;^lD_,*&Ѫ //|+̴/ <ՠ^HB}"L7Z="Hx@.jc-,{fi[Kf4% ^2;!k3Ul2:[*4@}{; O1`pbHoJ콑x`bɂU,R3 ݮ԰If"Bي=kZ]N C%:;E Eo0"[ah2!r#$"joF %щRy[l[G§yXyc6FoYYEam t!f\*$'FK0@]Z7]Ir0Glz"Wڗvg%QGWWp Rr3FW8aP1kf rNԡvD)t:|Ɩqqfƒ.[kL#鏼IWtrU6b7~d`fp67Món{p&w a ʒ bo/N:)ݨ^>ߵiQ:n[t0췒E=x)HdgI杙aIZ~O‛W M{㞽~OYcđY{oI #$WVᏖ"80LYpX,=﬎; r2U"tN.Q$r~E;xhURNŋO"8 'ĐY;W!j'+ĩnvHҤQp)GDaޅ.& %՛0Pi2)2J %LjA4z^xEHLhݩj)_O^ BEDaj.&rMA}~ ^?~~G~ XNk\Su ~9n dKK& (K)0?.e.^'ѳoA M/9cy&P=w%)]y@"|%HR%V"*n+s:IAuwq14~i^L7|4JnWJ0|\ޢ^^uf*e:ܓ\ >KŦޮk>pPPDͤ9Ŷ|bkw:F`qS,n%EMMsLfs|$]Q_,!ZX}oA~rVS`&SQtZ~na`(BׅgbA!vޏ;Q`u.B?Q"Eb4 Jz 0d2X}1 NgLHt$Nx<>uJ) "z)x$6WBlCuqq?tD$2`7$ LP:{~Z"> uRkRerw:>Er7 `gH鋰㳲oaـE(–OC]膇8t9 d9ez98>Q[FN Kȇ1;?_7%?]6v"ňF gUyP!bK0-Ά ސcn cQ6 k~} b\ϋU4#Hy_aeGt 2 \x^Cu {6NwV,H0QfpSwᵂ*co=ֈa{Ok r cJ 3@zWoG؉s@SOe,{cYN=H{8 q5.E#ƋEhn;qb}!ӉlnC֐*3m˜Gì.# `"RjG^ou3JK%xxrU?}8 KϽauH_.p?b耽L.?=~Ղ(7u0?捏u)͚m41 sp6Oʼ@Im:M^ &<,.Ŷb]Amˮ>Ƴ{T ԲJ'd(g"C~LJ e ;KeS9DoecRc\eb>W>NąKKVJuq;z1=ŊWjv_F[4dhќGLPqA*FjNn43ҍ+?<13Td S]Ep1_pfLDdy x< SF9.;^B]% 1:X]HZuYxC] uցQ>$-F<[Y߶vvw[+{tY_I&>A{@Mя n`Z.B{ĕXFҠR4RPZݹ*k: ԅ @cꎾN 39zo`w50_ IT)FQ/bkB<҄gh+4њt baw}{xI *A۬7u_ٳ]Q, ֓ T9]?ޖ ̗x)Kˬ!*+0# 'J@/Rǡ9PQeO!GN17!7pN u/=\?+eNyr/Sael#:*p_R-sA//0 xDl༝?ZJHcCxzSO>uZ\&u5˵a6c.18:j#7i}A-%kQ;61@~N70?S}ɉyAf^ZvSƆOw֟;ECm8ݍJmp>},X c1bp+p קxvt+L=3fehprunIMime&cwFkcw\ ҆5v\}.۬ӥsaZı t6tce<-B5X0߉jfߓZS()tXwt z1α:@ؙ a1>msgg|O '|B8x";b🇍 |'^q3C*ѳX4 C JJ-gӮ.@VpROCK*E6 Q)'s͋R^^35hw6י-X7:ՁTk/ԫԜpê_'E1tJuIiCߗ QZZ@"wf`/*QPtV QO▨i'*) âH5U@~.sa>FEW܄DCRVFaQ?!U4bM IcFo| 9\1ּߤzڻ4WFCȥB;_;>wL%-;5fء2:ͷ7TȢ:QͲ- EiώSR}e ^-S6rIkE8|WiMl?~8XQ 8v'吟^xv}BށzP7F%MU]428ψiR{udcq:ހd T|3EՁ`\_u{ /HhPw[~VFM`y ǀRR9:IH2DZȾ>Xy_-_Q@ј4R 9K!?~$?~h@XC4ȨQw쑝}K}>ҟZ/pT=*/0}a1ʭMg{0`&g"B94U(~|W s9ێ"Mg\a/6jD FRTR_OfĀЬv5sQVR6.][#d|dwGלaڻaYKgvGG}\%Ϸ_jR]^uR*0ucDCӵͶb*#=gb0հ=\C:T#?L*_Lo=D;HTVnUYV;SexE8o0 YbT2^oh}ޛ1F3h\VyLk)o ݺ ȯD.Lg̠bx=~p7wMJr3 er~1wX >!CJ,^(AQ/~ߥfZwOWHK"&fA40inCdOպ**8M)cS:)jڝDE2_#Khx7GwIr4U{~qk~4c,^6RN|i΂HqQ/Xpxcݑ* #>rosa:&T'JO.ʚ]yVN߽\qB 5PkD[9[^Gw!2/?~r7PpaFPp9A=w)NEاӥ;f~i]xc]n*ю!RO.Ӫ]VMT+xǖk*d֠,dy'A V_DN-NUbpHj7c@`Q :y` C^Ojx*`2B'ka2Kybk [%xQKılX1|#9gFAٟ(J[ :':{iOX@Gn9)%{O'@| @5]o D҆쮪nBG928P{c1nCt֯@dxCĽ2-b r͵aMA^!uj?f sbe] ߐիt/%ٌWO;.ğuER*0oZ5O+{ڎ)W+ڣzǧfՓͭgGA8ܤ*K)ٷv F /i^kZ=q̄- "ikKḞkJik{ (x}x* hbqg<Yہʣw*zVv m1Jye]h*IkwQ -YD@a}Atj u]]Nov)G ,ha}R- `m6l!.^łk*Ut#sitQk`d Ὣ¼2?%3? v/ riqEj#Q:RçL qPYk *_IP5' 댔\t@T'vZ2gOW # e-E@a1~b w+8{SpV}V, Nä3RS%a^416 `BL+$nRo{|yniqCk2#'b^Tl֝< \gqc,On%v4% .Ӱ{I]*6~ RZʒD, S=-0ՙ\vfx6\ P6DAPRu E#k ;>ŘSt$vDpw%(#BTe@MOJ/ XvGX|FGH{dQatIy 5!+🿋rP$Kw $ا7/%Ú8x:~q0@PVa^ـ_7-(tkHwz.*h$/on=* \w_W LwAwW5WD:G8^8ԩ.6Q =+~#O)n6x (7J^ܯXEW˯׫ЧEn#"qE$ `t'ziY ,K/k!W0ƥyxADM+UzJLJLJ\DIУ,z㳄LP?3.v{䜈g1Rsk稵0ta=b yuq(1n tw@]}EOԾ26݇> p"EBUMXReCAByA(c6E7R'E:YZRpNҍfgǥ_ʖtauɾg 8Ȣ LB sٛ9qA{۹~vy_D7IG_;w(C1AQнR!E8q 6of_!ْ9[s̕ ,Z3˼ EvF(LD'5b(ty @l7W_C,: $ƑTROxV dfɶr]Hrh>ʸ ";;̙èUtF+~Z{[ : (e$c.<*i=W]sBDQDŽ-wMp̕pu\ gDM٬:DVN>Z4U3ڌ YC +)X]unט^vB}1.lUPq i,}W_]5/2|DžOK|C+74yihgdLX.Dȝ K)l @Qs7SFƘY@=XɁu9CC0[o[*Mb#zP/L-p;U;UH+8޹ 8-M%6|Vozu,1Fpٴo>mnrI̔vN`!X_4}r.t'Ҫ Ht%OcJ܉IHwwGp*PJSj@1aRIhs} *E',DN7oE1r5w)=ɢrp0,6cM|J"ٕI B@ kl&,w''/U8}SM9܍J:,AZ60n(" 3ETIp3w.M Kөbq8ZZ^n%Li^/*t28m5I1)aH5v tuF=_{Bl j7&c!Eni(绝mbgZsɕVA|]Ou(*0N9$Qm0B`TK4zJHj#/I*@!Z#'@6.C]-;WVU̴EW^^ O*w Cֈ5c,kQq\u@VDSEw]sB\XŶ薨!D !i\5۳هtA.}yY,фbTR&ţo@( JD롗C]N`dKÐn0~H-"Xwcp.Xb3 ˉ"u\];|_h.bIi5ޜJfEћр", ޵QKSn츀Y}L)DL,^.9BH`yM3~N`YOKi߁ȀC9x,VY! J0f7՜JV\eb5٪]m??09ڞ"䔘|9[qiXʸ=ި㋟%0_o@߆E^n. H4Afl x}u櫔9/G!pzЈNA}EWdqDϡkd<Ek]r[䡿mo`Jf{n=ÐnnR}E()$C,#0=0ILDVac5/ zvKkv)jcFi3PeX\z"M|ZY [;{n,+bMϰx+le?{/[Eꃗ@(u<v\SoPYr 31ԁ0J˽Xk](S-,t+?TW(6+mB%Y<|vI@ cʢ:|*K"9e)Ng@25cxYs RK3PFNO'gCy\Yѩ M{2ɽop)hsXtnl7ps@ z<, 9Г7`R BS~N} \4_0?/):(o0 A@k;#1^ F6OL)QF:?)(#QH?< MS&J P,[l*v2ѵNRn (gM&ؚX} ="XV &#]dCk|g!Yy:?JKl ӍQD+=mjر_ "4>G">[g}M w?۪ɱtAuU u+Ftb4}HΠOQv`g/dDm΢弎{1p'c.GX?St.zT~zXz/_T'7gtoNWs\ڂ[4W ŗ%(E|x*;@Yy5ˁu#)U}8 8d$<#e%qHĉY$J!ҹ΢^c[Ʉ6*`LƳ4+Sq:IrjU餸<}`~}\i/7fT}YN0ۉK~'\kn 2pT=ҵ BmoazbW Є\n(ӥ69f)8 2/if;v+T@AR?Xg6|^T_7^6[g`4 W|ŎkՊYa-<"km6~Cz+Mg\/Q[6/ۿxk~[<)fJ ! PEfځx!&0^Gg]D8ѡ 0vy~&`cV; dZ>>M=eR$mM3+3Yxuoqc!U_ô_W hjm:4xDj.qJȴ[JY*4*J* ڜDr9A۩-;KViή2fp'/UC9.6fTeh "+&Y)Ua8^W+@ݩ]8[ sȩRRsQWi,udpH/nPVOou/k>=f#YSjHRPE" cXPT UqA^AG܇`'@.lY*b{˙og`+d{^bK jA圤141maSe:nAR(yЇG_upY< n) q݌ iDN)YA'S\\ɇ5w!C[uWeA@V|:b,MبU\8~ ]X}(.auv|?5®X\xhB3O9;XGiltnqVB707j:c~jpEYkÈbG]"-=Zuy'669Rm).Ǎ ʌ.nxZ pivg\Cbⴥً_n뛿íMQnUjtD/Q) Tmjt?4O'~ݎH=W6] Y:T}*Z]ckHl_@ &Va`.ogOh*}g>l@qʐxczG}@Y"h5yw.PNwHˀiՅq; =aD(Ip7 ֶ^(fS˗a,V~+ 7qbfM^䧘$'ˤ kBuz[-'–j:SVBIr4&8~ /–ZVoU1+ '> >=‚ da9B`s8yE"yXxPQ J sᥝE 5~kt'$TsQ-UI}"fHZYP,ޅ8|VՎG^wh{XQGfP\;wxZ8S6ѠNlr":Iy?[Z\> V+'UfB_uUq]v؅*Z-`?=zSHkbbʙGE">ߕYG'2WZ,o>y9<ÚCMBikq]l; ! ..9 j[too+qrX1/P.kuGz[)8>i)+h+ K D8M"^[^Pn41:OScќrrMqi30e;-2>RfV휯RSYi˓K;߹(򇡌Xɲew_.w.:т kȼ`cS'E+,H䨥!ύowg^ZƘ оoЮ \b.Q7;EuwPԁ;rQ\¨uF 7JnZE=Ay磈ǥ%eܭnAb ׅ'(e<#" e ppG~IR{R>K^1_=XG:-^T挐ux&ԇ G1>@h0ė(># maBHlQXU|+À\ xA+ ~YmUA aE$I0N5'T: "Lby4 \@~.=D)=`P ؐ][+6gGʽghM2>`p*$!!)r$k` Z'V%Q zhA:A;;{GԀKW:03#]P<t PUk?wɭE>Ėƞ#MŮk0ڋ|+h@LuAHe#y~im KܜG;mPz$-miS`U?ˮyڐ szbDB87i}!МՃO!A řC<:Fo`ԲnA,"m9$ puĝ(l0?k|=,7㳟k۫hӀN&Ϧ ć%zicUm:Rx~YPwffo4-3g&wjrm5Tvɮx#6! 9(^oh6@5e'RrB{F>!E(<BG?I367qVhem0l9\oTA#ZxĝT_Qx0%ΌGB30g\{Oy&,yر'o@11I%k]ʓ==ޓP:QpʤuN۠>mEo}etA[~{0;Z}U9.YM%mr6gGapyHLԎW; KT{]?C+k֢B&2 VF&/Cn,l+k\FF^ ͊ZmׁĈwD~G;Z+R:Z0Jc7w?| &@A;aXb Oi%'kضj4j-Ru,Ƒ a(,_;V:.F*Ho JneeIPoj`ߙQ~lyq%~*nM 9:.QIC! h}ų %mK; %($\1)` Wèq1> :CrH ey!b*hjkA  = _Q>Ty%TmpÐ$32n@F$o5T-lue(w1'bLIĔ0 }GQh&oq{]( [lF*Nj^-seV?c%kpUxWYL (s [$ev RwU7;^G#<1gw8 @8&U5j&3H!@FF0^O 6! FSVW%])Xq- _,re+WBJ`l׬[6̸Z |blV0 je< +r)9_X_-~^!&.gL]6`ӃR}XLCi +dyV(@L!ԙib#Iٙ c/D NHySu%}~LpwL؛ZcрS}y5`_Pi(CyV!ouX{ 7 k]|P eGV1< 0Jy' ` k E!򽹳}]%ν6P1S*X ]5 FzՓg_ < ظbsh]Joi(qh/Tߎ OI;>̯VH+/9>G##T(zxw'8u 'tJ!V8C *ˇH:!wyBwz>NHVe"c ۏnd(#;в-j~R_aΙ뱹?7\>=_wSlMbMaoGy7V8\( 4xШ|ajW(~[# yևs(hMW<6 v lv(-i!Ą@} 0ѵ#C %d|ݐ#+kI<17ueaG+߃'5$ӗS?sGKXO7 +  5$^vynD|:|zRt(Y'w5QJ@tۭJķOECXy^,?x+?.\2xb1KPwߡW(|RD,OFAwveh |ꤎ"R'5 hRO0ɋks)83LJ~P|bZ8vNPCC}$KdzcT() Xf,sWee{M89sJx·2v;~L#FԧE{ϭIMlƲYKXhqLZ2>{Lv#ٽ"D{Ǎ=ZPk$Ю *_{S2c@Zsa3?^ZCvZZ5']ʼn]#WrQdV6'8jz kbeQ*޿"_7yze9T#+?g"E X4&4!Ǥr8 `y:T&mZb 58.oxyw{oUTć ~)rtkS^Vo[\I@%EIOrOTId(ƉnsJ*0GYp|>XiT"XʔBQ-LT'O7q܋k1sryR6xQ7}߭# 2Ap|Pq%&}L0fR#mqluvIΥ8Lz*Ɵn& Y`Tlj۟& 0wx߸vf?'@.B3@')̠rEy`]n``̨?\_? aTej{yqqKtv9RDߣ uw=@xQ8I@:JLGJ.SYC9#mԫjRKhR!$ēQaD{`-Huj2Zr>KIH֍>xs9E2:GCOD FC#FLev}v!+|1?t+-SU,c1]άzE Ae쮬`VLT 5~ob0??_O[nfFOwWrrk4a ȩɕQ70;OG)|ZDNvѠRm'YU] Ak\[Y4ǞKoU0/Ђآ,aXX&tY* Kql uaL>]{oI#4?jȢ$<5b_-yQh^0^I'&E~υ_V66" `aRUܲ Xҵ VC-*kv 0K,QMx7"PHc d'ZdzG1KbE4xTپX! 4N`qyqJ:C V!njl'erK] $6"1ɊF4--wfsB/w Z#C[wtk1yfx6IKDt=yrO ]'2&ƟWzUDi /"C@roSFI797bD 9Vu2hOS/ XDQ.gM5bc me@)|%:p;2k 'Y.O4D?"[ ۂZ8ҷjek` 3%' o]  `ľҍ?T`_e[ OB}pւRn}IIk:5j6Z1ΑG7ڊi)h#ƛW*(0{ W c +vFmߨM+1HelaQ [, WY &GZ@deLMsno=/*0فŲ%PRxV򖌆XZ+|(0LaBI(=?q2A\w<$m+O鐹Pc݉w_KK+^JK҇ryE7,eI2@)g6?_ @¹}(ҦRclؒk?3_+d؆]-~o;RR itҜcNyaګ6 a[߮=Dn] A'FpNiFvn8Bzr!+rx>ҕ_ xy:6'x;'Vn?/n?_Ź%^}cט#ߖDݴF$"ZoH)N?n"R!H{4A$[RƜQv]z M_$? 0{ڔ|T-LE ]y2nsJ Q#$CD!t=FxcT4{"1&(*}l0w vk-1B?Qx MGJk~@>&M8UK<`QP0f!s/(*)JA3>EeNăawd*S]2-1*nxPb9aXC͖ 片"8]8/Jٚ)ӆREnAEFbg ]+wų8Qn7bkof.X `ǼL9r!тdh) y]TH#7>K2YSzd>{ 4Nia1uh4JE GG"s&L{oib3v2`#bi67OKܝ9an;:-eTr%%BB$S 6~7=|?s*|98zlm #)`eOL^`x>K;gc >Jl(G$'i =j|O`1g*w9Is}*WrY_0wY}`/܊ŋSC J|V. #C>> DN@pUQiG7Na.SrZ5#)9(wzGހ@xy -XAG~RieO>m73o!eY%!zź3#j]`,/Wi2S5ى1Ը8} \e>f|&s;kYcZ 崻)D|?g8 H RzuI@z ok7\/`.a/:AƄ{*z*rM+˩UCu}/ȫJU X1YkȪ$`nG%7>Iz%q M,>ɀ6O@'I bTڄ+wAܸU\C ۆ9V8GMCdy=.N0 dqgEg %x:K]OXTnOҘAcrL,7c'yOݤTb /tCi>ϸ߃&b.H!Izn49}p/!dvqcbqb!F.|6j0 {1'c&իeQ"y 5C U "b8;Z5 :)<:i7 O/dK3qM*ѶiW8m,o:?{Ue g-ZbgVWWY1&>22Pd)~:l$hKwc\-IjDbIPkBCjfVȎw__6v36äv7%Z!(؈Fʿ[}N5GXAO]t4"ųl|8CO\b9O ~>ִ= I! V,ɳ=4=x0T4n*]̨="wO7%fє?xuzt}>D? ^1,\~DCS&-ubrEYZQ3ZӲ3\*Q-̓Y# 2E`<[77aVc::[# d7vFco.rrif2 bo\Y2j/3X"˚Ia}LDp2fpճ$0-.&1)«=I 3Z13zĻOνȫ>;tܒٹt1hğc&5h~\51IO8@m5'jbeITm2btطւLqG#G#1o$fAz:vzĽi{C$qs72Cƪ G)bh ^2m / mA+[Y/ G+cl>?~U7ެm{ ,`` "a`D]IQ7Wc۔DfN7;c'(G ҿqo>arZ\9crN6OٌF(h  QA/QbeeyFaQ ϻ@n#506[K9/r1g5P,PF\ KPoQ_5)f@Wu)-nv@bfjl^q=LxhSd2f4f#Sa iDSD'gQShN n+H9+GQ!EJ0o Hl /*,zmwT+ʵJ+Y4!=,77eha ҊBȦToVгgՄSKjQ.0 yHLt7*%5Y(-Pk7o|^͊ pw,zqUxD`kBC5Txr2DҔYTqPn}h},>*T(#Vw<K΅L($~Zٗt̋e,]TcCQBBxph Z2>`$k\>\iE<4i߁#{ oAByVbQx{35:<ATvZ;f(m"Xxe.AE&ʼ`1/JAIm(O\'YT"],ZLKD!rY{F~k'B1)w]* ?g׋K}:ׁ?Zԗ7#( F_DU* g+KPjQ bŪ*lS9̳V3XC[6TJE+V?ja01$;C׍+J@R J&: 2r/[k zP~,T1@0B_y0s.Bf/zpr@Tiz$W~b¾ۆ m}}whcps˅"Spn rڝU,pިF7n0aHyұĽ3I$ `q/Z~wН!} z sB3p/2KMDE LހA"rv߲gnAtr_#aW1%B-D̹:'х'ɘm3p\s=yN?$!o$|Y:=WTGq!z) {J^To¼zG1̦ӼϒLe'0!#9?3;UT !97PA>}-UKҀ6 x sH6{!=p'O)3UBL' ^^%暑|o4^lRpq,?8ruw %C:ċ{QF<'~[PG| 0}1+qWAKa~|kJ˭ 8ڤW!M@F:\-~R#"G `zY{1 E.^Ss,l )@vhpsO³i-MR}Ùs2&q ǝfs9Tid4D4F9vL–Y]^.L=\?Tyȼ2IS#Ehb ]4R}]Bߍ4Zx F,hc\0Jt?*5L/M=ԀENUxiP,^)2RmQkY醙I0 %?jONCblm밓560b%ro!Xk( 0+jrp0 "80JC7̩;KtQys)F[{_ŚT5" ]Y 69om U"0HMi3}k.y'!'ag0vw;~rT!^0L lm:($#ERG0K&abZʧT ] )Ux<…ZZDlTQ"2p^37>|owx/;<^c*+!ƯPMA α a&rh J&@1@J{,"eI[h]u}$P_[BtE9.5rk6H!pZiC#etUt:I˂ː%@Wt>ʅqlnnl #q %ʌ.R0Q0(9<sWU,'iTyW}_Y:r>[اTʭ(* s&S*M^Nφt{N6Fr  bICVRtV寭ʿ߯{aZ]bEÞ7~j'VÃKWHYuj8hz"Cym̀{c'#rAUzuB QF )L3fwܢ]`9$;= HhS)rݞ{ ]n@%IuM6# #pf39|LVT"pOG@0'H :1L*]<8e0Ȃ^.h@h OER*u8,?gb&#YH_%3T$?ER8>(8Š2MN9I<Kij: b&T Da63-|`'|U, hw\΍V z0cblV(W D!,e$_?$kȫq[vxJj 0K5EzP 2|t /,W5Y冯/W588'gr玕K| /hZ)jJtE:Q Ri.mB=|(l} [{kcP4jcX#Zn4-zDH<ϲL4)ZE v14,\( Kƞ=TW"E~ mQsì (2t 8R:g'q&B)dd z&S7ј myv7dQ7pU]C } m,&q |Ny%sJ?zGb;|;G|1 "݃1zoT_߼mKZ>[54 UC5OEL4әsEԚ&r! SiYsGdN_k)_2iDRo5WiZ@Xbc1 {J}ϡ+QdYg,`oV޼E}dyEtkc$}ŋՂ:#Zj3}ݳI*l &DUN|^سꍰÝ%,d2jSQyڬ§VEPꡇG~3xbA |@wNgDqW>/lY,{б:XBS{|D&sD@(\1lo"eO.JwAS'+jwcۼt[gsw q ƃx,ldտY{M=?ŁC8PמcXH{*] cѥ 3 (eџ[q2 rq%Nڙ|]сlI ![BӖqQ-YwOO,‚J9KEE#.`Ȩ2_T<5 1րy&'pql`X% Sυ)/kułp(5"uݩmN"7#"˅h) _*NOľ3x<9:R9w:iRm1_53$<];,qXyX!]Pj;w?3,8ƅo)WH.%c XI%!@_b۝ݷlw j{%3O”Ol;NR[ʙ ?:Iq.2rI ]&%8d ԣT,c6|ME!]Zy ~1*΍]6ȴf~"[C}"9lgͤEmI ĮB( <#֢saiU *rTB<4Įb܍_79Bt2S&bFl=)a[UaSƐ`eyAHQjgLC J[{knᬏhShlz+jNB --$tQd&և%@Z d "u, ;hyt(c/gppd±! ޤpI2KLagJT~ 0}**iSbV# eVezKܯH@)Rfn4Vf 6Mq$l=2U C&)$xXu_Z7㦢C?)1u}c*v'p6`Y"PohDRp>\vFM[E $\) s.>`FQZ˭bG$.`9! L{Geb ŵ-85'J̰ /^D{1 Ţ"4j٪fE>ն}.$N #kp5$W>H,PO`= !(XQ F+MbEcԼRL8IF+RARY,qI'܋A/2HNƻфGnx6qkǑof㰡=M}}!eEJ%=vm6_o|6WVj_5/#Vj_2+1Ջa [QMIٙt1:H5>k ~|)=ڔR͕#֋*)v4N@]oDS Veם˭/B؜Zyc 5geu iQn,udn?UcLECؚG 97fDjcZG~& vȰYĶQM{Amw<1 WtEVEW ^ n9HNH:(sL}\ I|;Q4,OBՏk09ptxw^a1ڝA'O"4=s8X{{OkmEuY0୲6{਱L* +ޥ]XfJPI]XuX`D*07>H8p|V^̝,^^"Q.bE.RK+Z -޲V/vjb/<ߞȩ5ƨ"+ayIǷOю!WC0;'i!SJ@$6^:@Sڵb\W=RTFO`TY1}?35-bT2&HOT"LY/舡~> ^ \jVw+sKOvWʯ#Pv7VAX?AKL9GHgWvdLz[8)4{~:ӵht78r( )kE@n Ûn̶RR@)[=Kuc1]Ie+ @hY72R ΑW*Dv9vݷK %|Hv1*& {fYR1)1LH"|Q[ a3%n}D>E?èSQW-p<,VR ͇`\JS1S"BG3p%#gZt#8;%bdNrDP.ԍ9pwl\jnSL-㕶XX n lt-uξsV_Z .0L4?/m 2TbJ.osR*Lg+Jz ¢o>-a [[ݱBD>P3 @8;k9xO^2<9nfwo{w~59.V+~BA×9Cd+PY(LTln!}1R ybTȨ&2\ 4n^2jQ 2>M4 ϩW%/.,o C7QhsqYg9X7aL&r*o¿gbs`\qCV9<ϣE9胕s@4G)Hy.4%*-$)YMg-!sU躞mvr57ga'_6 !sƘG u+ɴS8X;o¿gUV|GD'լ6pCwC^@3mNBN}6_Yn|t37n=L{y,Y 볢W<\_ָrYqVS/ı.vU./ yle[pHe+yhٿf+@Չ!ܧ#DNjӘC3HO)0VKxxNqNcw!)- i2-TD$JJ9mx3_'`͛OonMմxm8y=v\Ǐ5Mݽԃ g5/g?'5}HKٝXJx?Wq瑙jQvz&T_wwc0Vq7ߍImaݨR;v*;ؑѻZq߹kle֒-y825u3,~4yRUf) PJ8<:\zf0x qHpۣ(=o_ .Çn]w[ϰ߰}ᛉ0kqEZa<̗C0]x侦p'#~ 7gg{=d'2 hPsjúr\ [?yn$/m?R)P߭³|HǠbt>dܲp˷9鶜 v >\SLkg,k<)6OqWty ^)ES}NN (#e e ?҄1h{% (h'ebF$cT\l<5՛-l7'C nTdLB> <牆*Hۀ!1["\g'W.OK)Y=-7v=L͓6ϑt[Hކ@]!a.Zt?.&ւ<>oȿ`Z_b"xÏt]phẂW€u`RjT =BGa*C/{Av+LL?Nk@ Ǡ-l*Z}59 ᴚ0SCed.tng> ezNysS 6@5FI* :_GϿH =upP?-V*=p,6s]m.R5F,/E{BiU j/>++sʾn^oRR1@Ukum^)q!3o\Ǹ$~BaS BYŠ%*V s^WvEn'kx֨fi1#mg93A0_Ge*np .Fl<8gKNYމ!Hf5hIg -(1R"qٜꍑ7<:zoê3ɜWk) p/'W zxxynb;R^^6$u . `\K[:#DǘT"XWqldx{/RGݟ"/G] >P-Ba햆Kom4 ʈZv奕-XZ][9l~~aH5ݰ5yPCIpў8pa4\nK4TC0%S;ɟYwIE D[}% C٧j9=L|wv ܖ|3ZĒ.kbexyQ°Y쐃L|Q\o?n{RTBXt1;< J\4Ent|HM,#3stiC^k* ZԁʒFU]_wR0QE1.Č)eYAo|0} --#>b!︔ KnYc!O)~a^ҐX}BJM?F(t`zzT* $^\0P-nv=iX..O`jc\ZeRtqV80 THk2V))#6Y[H! Ln=^^ CUn5̻ wN7-Mf,$6"Y0hi_7bUþSU.WE9}X6is:n P4<~@kfdu! y,@keo9>6w_YFw0((;|zX,w%{'0Eg. fI6WobnvHdUkBUu}ꄖ.)M,Ghuz?6R s"3-\9+8YyԼKj#nzOqaeda"v  %頨SY.$': 1(9!(etŸ]N? G}£>QOx'< O6a2+'Q}յh /`ǁ dd|j|y{~!L ȗ`Tj܍ vbr>Yz¢l$ ߖ޸^^ mFi$#4[Aue#-6^E{(G"䇵 xVarNLT)O͘Td4j,MkXj1fNmFAy( b(rNԪ@GY#-;}2*n5EhW-YL8Uݚ)(63*׬JGGGQx}T>*^qךqz2# 8$j\@u[կ¿Q:߀N*|N#v NL2"jle]w|`Ɯvz uGTNQ r֤룃۞lߎO98Mtָf'ӽ*; Ѓ?s[χO L?z/YRdXY]].߬f8 |:rOzA~xoAwd-_G w 6>7^T&JYݽs9Ϙ]!JtA9ud͕#1 \t3~sn;{2-!q "hb!w%eFwfS|Byrfϩ3VG{ۇ=\ wNI5fVZj/rDFƣ ~cs$jZq79eTaVB;LHhL{jk _wԥ_PtS.U+U&+F}ujs!;;1K}em) ?w)N7)G*rH*'0% "N*\,u-*ǵ\_9}.fA P$h28|I!yS^^]芑?$ Nh=95P7(QϷvϫ@$>%2 )45+2>آ^8]JZ8]J[X0y2@l)Z*'a< IS]VVR}[GY^.+{+!i,[ͳvqް70}/yE|@| H%,ηrc-b'vh{y5_ͷW;D}2ac1G'ЄDaRՌda5+]X#aXeVMTlA/ S5wǫ'[cыNv_L\(,M.\> F4r\kB.Oῗƫ$#J&(ոRz^m.sGTF׏Rzt޸* Y&]\7UP7mp}Xgk˸N#yVMB" 8^d+hHV`hokGc?3CЈ}3@soT|U]` X4xsϿoީW$0EgJ :# {t ċyz {kkO ^t6>:B/v?=gPt|1+rHyPjn5Y$3253kp 7ci \<#2?"s58)/nʛboJyTRWr(|(ojj(AeJ֋ v^yljm3d(n }[ݞiӞ' -͉`茠^ F/QPthh6O,Rnb+>ve)үPRp250ܣヽ_w ( ԵQ=+&EF zGH9fF9, <ƒS(OspUy d-S W&bj\'5 \qso.UFb0=P5 nZ ڏ7rUz둏#G>^|_i.xN =B"ab\$ijrJS8b^ k&P҃6Xۏ&A$jxSJ' J VDү4lȕLK >rS,C2"$%_0S r29WC|0a_ @ !˦Nf"v$_ZNaO9!Yx=-k*8N"Nkp\S*Jr;ضR&l!u/&U&Nf&"nq'}B vNJk7mzʈ'v1:YvQe,+͒"Ĥ4`ĹvӠq$ECDAceEZ3.ܔZ}?;GYSU޽h\ɘA!XU-R5 :,KCʸudMNpzzH,ߦ=^߿7 pS(;@v:eY\}$2ۉqR&0'ۀ&0hHc3f$5A-2Y;7`~ٰ Dse2+9D^M 0UsHjZӇD.4U=wJYoi'g2eaM樹~^AwRl#wje千kmaaUR9*,nxU]/2 :}^%ZUBr0P"hDh$tWPl6wq:JWc Gu끘BA_F1u77'kK4 ~ 10bC=095a 4] (,P DP6dt׋Vf-:9dEn몯2++枷h0[l\]e+SzwLwRMhjp70/+ΩW!B6/ ;lD*M/p\"T1FB tٷlfg=;mh2gw WY+~rPתʛeڃ:BXb“'Mh-" }NJ 2 '%ټG.C|3@߈'`h.s? 8ݧO<%`х%9uc^E5o8Xn?瓾,FwtL;&) 1ZcqL8>jst0m fO,#?"q?j>[Ay]Y o[Q^3YG6na,{RBI(<<7 \/҆<혦cdzqihhjC[+qp0SфYF"̢_a<00 ZY0>80ߙ060G0x6Yʘَ+ 9ՃT\ygqZSM![NaovX1| o.Fu=Ay\s/'Ѥe3':Ɛsq5 cJzN^`ES#gb<:l`Ķ#9?Hxo$֤\uk;'0''}ya/ė|LFv vcpxvؖ9V ݧrBɗo_דּ]%: `;mv~NU񢑞Po:@bnXՂ#?@Z@^`#0f  3"O)M +Օ$SW_Vڐ{Ŏ=zN* ߉],ۍ9p[f_N߇{yihqs-{ǶJu5fs>B~a&4.]䭡aw}s ܋s[+lxI.o6=ẩS-^""}{K\uƙ$/o޲` erOeaX\1oY&Vo“yzJ\l?u%Ǻ2r:SR*em1{xh#A'4rDfX^Vݹ~b/y|#^Xb+}ސ:x#}{n=)<2o2]}X· p(+ܼZnoSX/X]m- f*.1l:[yVY^}A0 ו&pj6:p NM@/л{Yt|W~17{'Go+Vٯπ 9 cƩhh okNQq}锉m/כ+sWٓ6ٸ 0^B=|>u.LbndKܽ[X%NB^R:>9zuB^jh9v&P:>fkKb,u>$rۖHX}Yey\]Y>n%(ԺBnA$Jو|e9|[؃w摑 R,J G7U?HW;w|ļaזVW=[{^ŦQk"3,8<l^U\3r!C‚P1M0"잮-"T6P=7 wY/Yd&p>Y溾\`*]v-ïd!%ǻ hX #?d@L͘zAMn/h Mb &Fi(Pěz$INI2 ,8c*dlt M)0cdV+Kk!(DoXa8"1f)^D0vX4L)Ψmtض6vFH3C3 ^`&ȋΘ(FdQpQIGxцQBM"I5xbmS#FIBGN T\I^:@ƌ'K Q<F*ɂwp9,;jm_`yyoo# 5iS>SQpW;#l#T#V+xfQv*BY74A/]r1",gG=IMmđZܓ=V 2WOwTZ=1d>&| =&{L &eP(ʠ f 1e?^4u`Bahr}0Bi0ʥ X"\< wY$B&d "c.s6(o QTp)e @#^L<զ5ݦ#iZЂGJKgsE̗p5XVmōK |Sou"9D+ۥ\TE|1ޒKv⚘; V\W;>Ÿ(QBn>%דh#m)7ƢdŸw3WB3371( igtv sja( KRH:OP ^5؜ e˪eUMY9>WBT*$Ĥ:&z4KK@*{eύ i91%K)iGnOѾz1.u63PjHcR:\HyZ) &S:^ݓGO ՊJ6 qlެIq [g٤t#t1V#l#3b`=7&M&' Xi $WH2ۋha:l偪i]JuWyKc\ eo̽}J!?:,<)EkuZ\VcQX7/+ɟF[$Y ujaŏMQlU 92pӠ:w1!0#@#}5hx7&6y u|qZ}V}^h]4%om< ZLHj)tE"Ɗ*!4`(4RhTD5X^BÓ`,k3B2yŖD`Nij;R#둾 8#}p*)RjùL!zňu?G]VXűl|>v_ou=ѨXtﺭxD:㍉~IԌv6gwF'w]#> t;vh8nkv҅8w_!Ȓ#zِ,Vv ){L'Q!Gd ?4+:^j.OS,C-`odwozIgtGVB ƃhͮ^Z]^1PR"on3 <6ew XC'K|f|erq&ch#Τv&$8EJ;kOZ.u2| 9;p0,2[zIX"\PD4s2l4 )cϓgql X#7 Ce@u!`ZbhT 3 ӪߏIeqQlUaB)|癵zܷ'] P:g(]t#[قMH %aaQGZj$ QL%9f)O+Q`^sͱͭ/;l1W:?EÌ[kkr|l<o奲(>l?A6XEeL FK{*p2[-llZa\ōʫ:vglYxA^DZs/!ص6.QLڋ W |Q:"k}Ǜ{T|jLÖA_:US |QS:;%ڐHfe@MTP< r偬S ML S$Dk]MS=N[Ie^dS~<*@T<:xД z!QeHH+bpdP%f^]Ja*5xċ\*TLM|骚X*t0`9*Qݒ'DZvd):T#8AF5.s6mU#eJ%<|+2&kؾwnYw]#_]r֎3 ؕwr)*tFUtѧsͣW,A-^~7ܢ!U'hѢU7ݠ? Q/&{Q/HzV/;"BIhGhGbDIK\5 *^Fxa\.a*]غ;GR#Btcl5l1/a c-ZmUkBM;A\@\KƇ 3ք5vn@OËŝ6>(^4]?q͗<@?%xˮNR~vL$V:m-hu]J[{;\Maq1e0Qk]3ZK([݄ru{l!"_bى|Zr߯ApOҝlx+t!%V;s`83~ nxUK6[/)M̿tn؅sS 9jIurP.xu_Y!%V'Ep6ΨI~|I `eAig1E7DaJ?8\ẽW6A4y_j~b,JK8&MC & ;m@f L ,fA_8?sd5(? ![*EGqc*-2&% p7Yh;OXd'!3Y`7N(hnyo}HX+j*lD0"^M~;~`Mh~7J0 :hwlRw䊛J V;_6wN^T:|>Z}'{#Nf{kbyqO q7H?mh5|FVu}H xΠ}>Uϑ:4VS1%q0']I"$'DWXo.s{:qKfj1?I8S^L q=>,'I7Vu[dm0 4ϬH7cgAru$P=!%I#QE#'5DfѤ Q=$bh1"kИԆ2hggs{hwA"Æ3R XpҦG;]EnT۽cP! .~9ʄX.^ŰMqlxLA{и}Uqeե3J+~D+I+1Nbx `]WbG%ʹP}_Xf 3/U։I?NȡK_3`cZ U 6ϪK5j2_= K֑b)&I@L08N!}Ւiיɔ}j3u<017Lx:wovn S(;@w/LV v DC q9=5ZSaư5` lzF4-F x %XD 11Otars/'ͮ۹Œ/ow,pV(SrQ$w6_9]uR.t /N^' )X<CPB*d;Ҹ XTՎ]d]߫;?EqʼnQjo$6@~ 'y0$Va g8. eaI(ĐdLJ)==5aͫxcE\4ۨ<)If ]%,"2K8VV=_Wr^T-X\#+Ạh7׮G{qp`wZσ6a L=c/v7bg+ו}e=G8w6br]; #i`n]QYs-VۖPShy&_̉#si„m<pp=; #.0LHc.وi 6?:X!Ka~% dG2m:^Hm S&#J,eQS%L[1X/G;Ǜ/w2'q;6"3 ,Pٍ[lN)2N(s%ӰTVa92uXoa7&)hπTVXXB;,p98 JTK`YܘNɧOO5ޔ1\z>MSkOET1ނjB~B{gw4*;J x LGҢ:Ek [DV5VA%-+~"e9v܋țw91y0}91^+?9Ѿf&'Bn9q[NW%Y㽵JS#HH|P,!jt?bMiƑHfGO $;iIw2HXBԑvŞyy|_]ҮA[u55x:Sw3xicj @I( ^QLs`$Dd*fȯbY)0- )) T1U ۞4RSKM-V4_.;IS4L:ͬц7}dhFF;=#_k"%^{zL92wR.7 % )7g #~iwլG]ͬ` sMWaQ51eRY-k DLg;$͎9,f>]}r|zshO tj'Wͦ÷n?sϟ؎Nxן?<8:i׿2@Tnl:fɜa2iU?Ho2I8Ԥwd`U:ޠn EcPp|5q֙F VIĶm~~ t@= =sb#;%'@8idz ckPspAu͊8VcL xj$87{ '3s*o?)%T.l2ҩgY̅KɅlָv8ow7.LLvCvK[BCnv6mB–Ӷ vVڔ3%lG5\5LsH5P1'G&$,#2ͤc>AwBPxQo=bV~%2wki7߽6{Fz{KQ/r_,ߴ/VgJ [?`sb%i8%9G^0|#@eMur@8Rfʲ=Nͫ!W~X}_]Y}V-`´LZ‹tGz#z`&a 'B`ߋ^Xy%y~{#ZYM߭޼w-}זi$|w-7|H Օω:߭T^}~7TO Ok <gNiE'u*dsX<*uF2{+gCgjߌ,i&T`LF,d f14_j2{gN&Ycls1$BZZY75^Ã$ۺ4u5niX7L٬Կ4HnP9/úw Z7 UݐӺZ7>gbݰ#v.3u#^[7_icֈkA6A6M maes &92t/sQ`'JC3*W u~3 ]1iafi`al%0l wac8<9ac00u6Im@{DEe!{ugA'Ild|v&ݼ/ufe!/PĐӝdDNػ?eWAӌa{҆0h#)F//j&K5^!gjMޛ cy#W \,ѼŧM[vԝYˎMZA̦xٚf1bag֬hv1ۧy y4pc;&UȣWȢ0p-E1 wS4wIkPzM&T'pA7.\;w4Z$6'L^{V|G4/T$YbRP ^ǰ)m܇b3=\ajbDUJWkC6 WGi 9 e f~!p|\vzr'} jQ<օX^v2!܋'M9Aec@9.ך}'V}?cİlljYʙ.b &Sa )ذk ;1"ef4iB Jhhe)q涩ڪCNiZ_Rkcgn ߫ wbL[|=Շ`LX8pmwg337IwOS6_e 9hǧHБ]ԑӗOuoLRk݂SУSngCשYOn׭HxOZv{2`y8~)8rP` ˣPOu L-*Ѿh_C"Pޗj:8 sj݇/-r'>NCi?zUMuO^n2}Y"9DӤZ9$R},#0g2gw&$dj+CLA-md}t‰؞,NhkLX=9Aa0:NBa~ 'K_ y'B=|IvZy'ُٞzq:o{88yn@_8D#p>˹>23Pފ,5q>8%)xj@Xa8&#wc$+P kq:;LDŽoePrqM0+]xL 3K'nG5Hrx 1=GS?ǘ!<2G#s̎cTH>r6[iu{IYrlr ^.Y-4/jmwiÇJk<&ńZ =Zh#>|QC Z&D>LEa2lBg*jzBa ^O]0Lq3Pkh=y_w/o{&T>ԗP+䧍垤(VΤpeH HqLNp2[c 13c")‚"ކ}{'`yEXi˅ؠ)“)HL ,EI 6O }" !?j>?D |b_ʗ(<>ϊųGԛ&bUjĻmY v@"]JNyp\'3LP)ŪdtKFsvW̟p7]bqmv⚆fɲZ{bѳTkf{). riSҰ=CH YD4Vo4l)|&[3BKg= g$\Y($޿`feV!MseY& ،lwF0;,Oߛ$bȬ1#p%a٤0RQX*'/mI7(63KîdMtS)T_Jwn~/Jw.nҝsMwtS|&-އ{~}r>H#ݻzEN sHe ~E}A ;>ޟӣ;G7;fvqt(F^<Lnp+p;pޤ/y݂gM"7M8oU8o]8opޗvλpCM7ލ8k=;q}^&q+nCq~3 zT?MwW#M钜7-9\\\7弇yUn?]9.yoyr^9o71y33/yӹ56獸7]ߜ󾨫s޽ܝ7 缯P.yvλc.E}M{G"ݔ=wǻt=_67tq/yoyٯyyR[u\ݫ=;tN/ݷӛs7w7sN/Mp|,Okӛ=TwWO>{tew{z??ԻP/jɝ3tzO0y#>ߏ'7~#9F @;"XjAZx-֨~u4N 09>90pp WdhFpuVmY:vq.OuSmob4|OKFI5y!"":T9ڰi&LXči '{;%͖cA+o1Ma_(БbV/D|Rv_k5g)6KO]՘P/ݤz9,'!%$$*O 3%fyoW#, xV#/?!3u.V"nAn\Ԟ% ߎvOv~M#(6Y. cˇ/d-bSz4zSCoCp/(p|rf m}ep ??1ֲ #cq RhiQff@ML x;}m%.Jk;-L#ZqOq gAΥy dFq gpyz>"۸4/\%ZG+mm}nGbeG`Nw_De/wLB-%CCKg0pnV&kviqU"pIOuSDx% $8i{E;RF>,V>WvP\o6,ۢ&zp@0;"dĂ1s>vdv:':SsA҉.Bt6_HبR4|'BmhZ<: IhM]R=ɂa=n6Mhlp B s&~ ~"lc24Σ mHoyc BvWK[*/8w'[>1΍;_Y؈ ezH~C/+ޤHcc6v]I#I;BEv>,j,4q q_s nf>.5lǸ\CŶf?:b_<,++g-P}uĮtna_vZf9 4{7ϑDYlΖ߿(Wn;`玏7$g֞W 4/rj$ƿ9#l(4pV§F+f[MhaK$նOorH)i^Qt]HYC08 j|U r\_G*qz( n| 0`}WϨ"칦&P%qq~e_IP"m; Ems-*\K`4QJfQzYϘ%D|<521Ό{=2FWx`|mԏy GEȻPYO*T}j^))iR|!WPutbvTo4}jhw?j<˄z}PB ?P}xkպ;ˡfg{?~η4L]k0yi'eÙͩLx; KJwϖxSG& ո\w:(L]۾WC&AQ?_7rѕLviԨ@Bz[ iݓ|M7u8$)b&Ħ/N{wޜ܂^C_&44w%vw}7XOFHous#6G$7ٔ6֒j%B02;"\-;rΜc}ˠ+8:΄)rAuNgl9}x@3MO0]׀- ږACmpm4&miش6圷n+ڱئ<ۡ_@?آ}s` nn n} 9F@*~n~Oh9Hphk HqӦJ.zhmLD@QDm<o6u5v%ј':.sON:53 f6u֚+ƨ'VĐCHaU PIth_5{=4Lϰ'k R~bJ.ܡN4qr׆m6ل_cڔ Hi=8AjΗԅΆzFV;tE>:v`WO]0I+qdkepZ&%76Շ?R(D,n-4`Rysi+EC}X<_##GC7o5~~k`Bj&?촽22CX{\_>S,q H⯾i,yPQKJ)Ǖa_TPsU76f.aJ ^"Ae%AI+ݽz$  Ry7tywHGА8vAxFdž[@KFJZ$2ИMNShư'3.x q4l%4&ʌ)zIZWSQn.FHUx~SxB2y?C(pQ 4 TAYefn~pHP| ^K"$VT.%CU+Cp PUc0[=pPc| ܂ne2 zԆok .y2X"zV{VHsC a!W 4rBA5[-]-ћnuu@PXJ|hHy NMF1% S )NᲣ;8j3N/;49`Ba֜l0~maQi :~@E(  >&2 |U\&Ia|*gJxm\wȺz!3CV-!;n'bȹp[.bƱDpp(| , [4t}XmJAH&B&qG]f¿tnX2{yqe, vϮD#tթ `yZY4%\z+yοkVڬ^|YxxP.NWjƣT:TCc {fD?g%iZA(x0w#QVmEL7q ݗd!|?oſk~|hkgϹW7d7Dgep5Nc^ˬT3*t*oHC@H6C!O观 σ;r7b\==]މ7Uu0ʵ2V.kdmCn͎7'|>;mEvzR@ZχCN[vxp&s]5'Akdk]Zw ~^GxDѠ!вV;k9Nߟ,jRu+ЍۚA0X~YX e[^Emz~3Sd!ÒeoEUMm ]f^^INkm,RcqɆi}ǥױe/Jt|vUey#ٻ!j3&@mie<!U(Ep 9j>id^nChX} @ `NE@x0OZxp1]qG< %p%ܚq|shc,Ta#hv&r-H"9FdToEZ9ƥכm}LRf0B΅Yr|y^ZxX vˤnJFFO69ÉCzB'R}(e 4OfQR$'+oX=8_9o8cGh腚 &Nc525!K0V7$gŁ+˒TIv·0V.))¸Je]CP BMp7 4d yC ;iα?67^?خ>/>kUR-Pg6~gX~}./9:fAfFo݃}!pw.ppq;,?mޮstq*ϫ2R90 nF dzXg \_DF9/J7QD %`"$u\KVV2~I`o~>> ]WAt'e(1bܛH/*VEّ< r,1p\fɽN/f:`q}jà_;wZk\T NI$eբ$Q>sWP }.0!ZAR kU3"R >iS Mc4jK푢K oϨuTLIoϢSQQ7GȒm6|ܩYpٚ7XHgy* $٦{x׺@woeR K+\V,uѲe]TY),Y|+RQZyY/l-n..Ȗٖ(Qx0Ϣ媋ڙTߓ9$j3m9PiiޝTH(KK|*s҅9:x4[p${5Ci ǹЅ@P8`y <dGXH 4cqyKͽk"QMJ%d=Y@C:Ҁ \7vB#[|" j=x㚮|4a!c$H:fB?p-|8z.s3i"[5D^=a"=D7FDsqsyyrp1AZ@ *v> WC Ҳ,$;^s~^c-.Tm3 X wanq6iכe q.RЌe!hnC>ՂTS:U+bߠҮR5"UZZ'jC0 uL@;&!Lc+:h0dS-$A_tÝWyV]fo~s{p@*aZo.-|AYFB<1T#Aפ 6zƕ"ռ:+Vu$ aw־zgAgG5p5De-ASkZ [SQ+qa|fotX*O聃WxB\dx^ShMxKXiSh&5hď[/Y.S5(Qm?gHr6i7dN= [W 5K䄹On W>t on*{;'';G[;/_{pO %Gm_^Y}w {g _[uFE3*iPcJ+Q=m%HLIycɜ9KT7)M( 7 ΔTqx; P}=WoZu`zb|n+^YK$>΍#KRնc{jEM*k <~@FH?BV+Xhc@<뫗^YD{\1? E!/<1ev4eޑ?7T,Rҋl9K(h"~ʲrI.n(\?É1?x_[Tr0+ 3K3h}?k c5-jg-AT]|}Y(@(Kۗ7Xg(yzYU(;QsZLc+2cM, &E*SߋRfTTim-JaGњ ڳZ(EDhEП'@QKR7 O;ñuD&N1駟776h76Ut G0,MI @2.i^9N>z7(HIqE>moh284lxJ?.ż=QHWB5;.J /a"b `qsvʟgbx'0 p_W^, ݺ9M6XkiQ hQ9BŐ}1 r$}<Ӳ#PButOv^m/XӦcL+nMsw]؊5JF{7,%DO #~ lۤbp^] ~zռN 05^jH&rGN1c͜hO  CMA@Xњ0xfo򜸤,WzCѭIP"]̱FTRg^| )Eΰn n)UzK]+q7Ywrpr8o^@ģ@b4PƋGQRGtƋ1c^5~agq"臥wQW۠[:iNPQ y{ǻ^lǣ= ,&wo|KWr xg[?sTRzmwwrx2'=cLk<7s"7VNΤq`**QГ :ᖯ9>\ߋgFг`Fl`b8?0X vzA=Ń_oj` zoP-Ss!Xbgwiu.{hj{q7H-d3KbEr4BYLEaPi5ߜz,{bJ${h,G&%a1k,{ >T\R '; >16d1Zeܮ$¬sMj[8Zɳqhm%@gmlQ`L]u*#lKCIxZpLOB1$B^ѣa'Uq~` ̓3/a7j(h =49iرy~ކu'DCO+wT䫴TqF5+xj| )*3N3]=+xnL,Ƽ86Fq#smЮwAҡ/rbއjsw7"_wu"O!R ?G#*Z8\ڪ<"W0"hU;eߡŏ_W+~1mRyzp>TnAҪˉ bNן^15u~<&g\ZD2X_5;X|*)墘?/A/3-< p;CN\`jq`x~R5lm5jA8$v'KCRc1OtS9ѫ:vsŸ 5 jGPdxz ]T-  1NV2FB7{'_Q9ܤg<`zNɑ>vK_1|H7\fˇ_69<6`p4|%K2|r{0|?o:zZZF1 OyjCI؊\wlO{no\oB?J{ZaT*b_I-?מ7M *A3~/s?,QO <5E2!<8![q 58&3z_:]¢ C7GNxiO!険QN9[|PCNQ:/VETVA@qd8"G.զ hʜ)P$&&^^$RPnEFZ(ᩦ E,<%WΡX^^ ׿`ff IHtjSfA [¸c% `X|Ov_l_gG KYw\0/ؾU,ԸvF*bwʖ"^}WŅ=z1`O=;Y\(Y !2sLeYUuCfKaU~U[mpֈz}BqQxx(tK1ve#=`x7_z9B0-@MEx|ץZ%sQ?rv`/-Ya.*0y*OU"_2xki4(9Htn1ѕWO)^cR}V$+"ȄDSOkgڈB}4G 8 c:xB_0US^JuANDI`j)qU#!4sIB76yz( bȃGPyȇE0|*f"ip PgQ ?:dAI9 68*M$0&ƝqPs1(EV0`ܹ;SassC+W %q]g2%&u9AdO>p}`} 1]Pf˪)-JU򌭼(|6|7ie{ -[q6?6lzQ-Wa6FJ*Y>x mcF^wyDI&ԈZ77HBbg3H9̚LA26 #}QP-WW8Jd"]e g /<[0-+.(ajn/a,$Ea)um;wBkqru8DKd cnLbx&uim|8N0.jRrpg]r.+Y\35NT S>7?Є`Vy>=~<2~G87KSϵz~@A4* k(T4nLbQueȷ]x,XIڷߔ ZY& Mq@FɍK;3{LPgI;= eZU|fr6WX|+ Ie**FT?#1#PymWd GLˏT aD}2^/.isaFkLzY9EՑo7~Ê<2-zxp1c<@ڋlPx½ 1U!Q;~W0`k^_juNENjpr ({y:_={Ob`xxzƾ]bx z 缱D lz]YJj*p}{:N/x\J'X|_CJA-jT UboY`tܸ#Bbtp/`vw=A bƁȪ=傣1A"YP+|&qIOQ8+VKdcr6BLl"dPUASA%;GPʡ I%!ySh%dO0T# {LfU>`=epOE} j닧jrk5LQLN|X'hX9ų-|I7eJF?Rw HGH@8 DJ(A~`ӭm~ HGC͠zj/R1'"oi@KbNM[lSWYsÁã>&ıIRszw儏 76 ך P+S<*.D:;oP:"L;Y^,bMKHi+It6;*Ov̉3`"c/$'(?]ߊ%))xhQ :0xBΒ.1 չö+I VY>i,";[Yw{ObSO`L~iM[9asQQDejdJ8ҝ‚FѸ#9 x"7g9f8R,Z5=`gN%ד̻UēX@f/Yb=ԵH{@i.IӖOb(1JOBarU ebV"L WM,wFi]|_b.˅'gQ^-,s7|QC:hZO0ty)ϣ묗dsp6x~E` 7ն\I+&}@diPQC@kF~WȯhClPx-OtI(CM.@b-5&J ~Iˁ@E Q_[iN]_ ]8%%vpI4o@IžT!ϳ'"5kX3ƋȔT kMF*Rrq7"i?iuȥ"-*W]x]^5ʋL6!"g^ii>&+VE."j Uv/ޠ[{6V~\=c,ߜB)imom<<0E .wa[>6䞻 ޮ"0%:+sI #f_r"oʙsZ<̀,&!A ӺJ]ٔ, j/n̆S3bʢہЪe.=5;fIi1H+eVt%Ix{&3UC5l.YTi9;42a'/9зf/a. r$Gܗo_KKbV6[9ie&27pXN).x`)zP q%?Q? a[;_Q?:~ o'ɟK|[1E9Nk!X"ݔYz36['cButӢ'~a/$06w_G ϾѠRz+>&y!ٰ[5ߖķo/ >r޿-oEߖwRɷ%E M'$mXh+?&R-s[WM3>T1jQ ІxZxr9#W?2W}bnpIb^.ޅztwH6810+"Nh?I]SW~K)[9ck|Mv$eH4`ӣKSwh%+W/[ 9HfV u>0BIӬGGS_ƞFOGNn/RRUx@F"/1؆=!EvPw'WCVhg(C5gt7mݤ7)ݥ{)e- \9^Dtv5vdݪ땗(U&avǁ8P4bcSF3xN,a5@ N#(ma4J ʹGrFiQ9DSiolޕcuJ?\8inl.3Xdy^?s1†2Ӵo*f!bϯ\R}~ףK@wPůC(&T!AD3B!42JYsŋ_P{U+fLqPؠik/7tCC]Eg~FoX[ynxEH8Zp 4=ݹ%#[[;\Qӹ `>Bt@ k}Q"Cy킐'}Xx9uİ8*9@ncSJ5m!n f9mt i)s!kU--*C }(ϙS.k2zozv": $ O_9:JJ԰ Fn7[œ(v0~Foem߄rY1q˓ty|mJPAbr{NwT5Hɖ))ntqIކ1}>SE1XN[0,h3 p6:q-zh'98G67+f3@kF5R.y~O&y(V~am ~G?,eB1k;={4?/׈/՜,]U%8>ԃ;~.17l9DWoiv `b_5vGA5ԅQR&B+:pxq[\0gU.i `'$' JB\Bct`/%14H|G97-Korq"hs"T3&ވ7/c 8*m6UIH wxV }$`$́1q0/bDrm7iu>8]Sy/ 9jl{;Nݓcmͣݭ7{GrD h8n; Wx X-Q1%DRh3րPT:8}wv-IU$qaSz5k7Z.{ՠA49,A+'I~d jz:T=áo/ K +d˫++˕gHoV)ۉ}>h^^:mEހ` b B5fP/߹9oLAN@6pPZqyLQc¨X=<%K8tP?ׇE#pC^<'ԽT{**F)^Vpy;A6\<^X7S⥂Ѧc|RE9(ThjGCnHhk҄1KH0b>> \"&1ƛיvjxgNYwU6qtzJ-L mJ .K#'=[ OYƅnDɷZ^F5QCo*zlh~;TiUᗪ˰ĉ/S_S:O%! ~Qex΍vE8~  >nKܒF~s>6.-H/.T1M1%8}z;&3qB]8TbPd\}2 )8?(V1-}7<=~5ɗ׬av(?9TYWa19@?(#HO0mB]Bt7'  AHHQ5}c{Qv.E"(kfAE0S"P##IcDo#*y2' #ћfRXti_IԼ5׳d";_AEBtiU+lmK2D&ˠrjnNP ER=b0.2D o^QZ`./0=11o#SȨ[x-!|ZPFLxk}[fbQJ@\楋0#(UΖJ4[駼|L IR'B&h"GQrb4 B5t.8#1fr]g0G@RI Jk='>cEȌ\"9uM踵ԫ)y ~[P3G$qT@eߦӀ3?\v'>[$L|LdC"Z˜#IPSA@d Ԡݮ;vD6EJ ÖfİEJx+xXpa!ozZ)bETd8eaH񝻤o&NNPK}K|+rڈdRGJXRdlD sM@MNJ" )z0c\59}V )bZ=&w=Aik19?tE#tƦC>Z'9ׄ`)_QQ+.UrH:$QFt+EHt,Հ}hnǦ!͏z&P4]1ַ1)=LvN-`to4mribfJI3A:8:VX1sIK?ǘ6I/MË Ru_-f@ 'o-|SF ˑ|iPTFC9p#_הy<p%J{l_2̭L.jƭ]n^IBFc.@bi1 &q@x=tny O dNobyO{%sG8] ?mJ;S]H??ѢԧOl^F9}Ur>@<#R4ar AEMMZEAxhŽK]q EuCܭ`~3so[_}~/sϜ̜=OC@1~7U!S &@Z0@cda2Bh8p-V$b/s|uqCQ`"EG"EQcDΠ;ry f0 "32g X0We @3# P;0t(AD,pv`h$fdAdl8#u&bcv.Bl.B?[4gNæm$rgE5ͥ&h3$׌锽%6هCK@gS-]Jftd|9pjl#c&L)t, J`O(Pӑ'5Gj4rOJ82H 'W,e?Co30 xM&Z, "VmLl(|TR3 PaTtMlH1T{@cOP3U:\YK L\%FlPRs3F/d dDk O4, xL6J@wq2R*d={\,:1rHՠ"dnDC%0 hʞaCcΰ JC@+j \.O|MI;D:{I(bt1jwsDs0Wt_6Ɛ; C|j-@!}#XdAVP կmL;_QŃN/icQah|}=48#nB%ɀws1 wJ*>njI%97^m12NF4!`a,!)Sv$z'ɸs@ƿ蘪P4q{Sʿ<"6*z9i\QIMW &L!56+xMBXC HHq:m00qӏ,@N :<2$w!S?nA ,H7u6DúgYZ|N)Hg/ӁX"d&t d!ĸzGǦ|lY?Z  Ak_'M$O\St#ZP=WD\ tj𠸠XQŲTMu!ʌc`$t d#٩QjEC\ˏ$D ]mtj[AT˨&BHu\Q(%% {YBeZ$`^6tP!2XpC6 F0::#P{jC(a2ڳuP>{v @>G:,%tt &ݍDN]tp2{˃^CBUa:!(B%he98G̗Kqk~5MEp+OK͕C%BÍu5 !FM+wE=B (K{w] I'`NT<9k$ @nM)IRতHKAȏpxD^^:Bkex;é FvN9B3 iӵIE@^̌:aհa5nG/qӞ#p2:%w"6T.:$FSѩx9L%e8<@>9*0H'9:4wAoM↴1hgoNNx'2 ; >7moI``L˂)V'OQqS#Ki c>1 ü>c\jwaM~!),rJ@+.1+4SPx"\䗭juԭC:Ym?Ρ=:Wf/vpn̓y#+}+c̝;+Ӳ)Ѻʧ}קzW5[mḥ0|"w^ҬЃegoQuΒҖG!gЧ?ӧcߜo$i̗#N=s/㫳3>uz9=^%&}9#l6f?fuc-!jĦHZ v(S%RLKH0jN~܏fX=WZD}ӨWG7^1[[kvϲrLB?Ik/f0k^,1XB͙x,ṅHkvM?zb8Xʞ=czF_2o=7EuߓU.[;Z.%0Ӷ&GE >{ӴeX̵ /$&l@b(m']j~=uŗa7߾#ն)򴂷h2'iZev}qʼ9s/pGuBRYN5-WNd~փ)iwwl?-j*^4f}xo_n% z֚I6' β3oZSvt1Çܣݥ/?J ~B/:3 _mZ5ԘcV+= _w_bƭ'u z?cabQ(܆5C7VoN!g53zEWnȲÝī^~9nFOW^=a#|xEdfظr6Y_yGC}n$]%zQ+ڱku-w,?9j1qCZo>wNqui:nE-컶sТ犄^_W_LvCgOtٵ( s"?.:sS-Kx'_E.iyX9hM" =E?4Ƹ4,Vwi}u:6b$!5_}re[uQ? OP Cs2LT:&Y ,f zwx #oB@&Ṁ0 'Np}Ҍ)iH9H |iqA'hG+EGPBlه)w!Pg4#Q@ LC ~ldsC %ELT%)Z 1IӵTˈ19l&FFPT;*&2͕T> BRt'Sy** IYrdIT:HD>S<)"&}|L&Q遱*D-ë_I2qX "B}Gw<+VHσs^uI~ǰ[GCqvw?84| l6t6p u4@M7_+O!86y<.F%0 1+y |va|A0kOk׷}ۧ]mOחמ>./.ok%hb <'(X۩ o*+\3Vx51@  B}0>Bxl  c hѸl+à_oP58~wqv]Й/U0`qݾUر2.~}|{ޫ&[3;[6P炫vgvu?ͽ׷UooiTۓO6+j+zuhŲYlv[y#Clۏ7 !s,߰ ]a=JzD;UX~dg|XmضUȺ|% }[%=RWE?凄n `u{iNj}eyCDzvX ?Jgf.:{gF-_{ywcm|sL0z{IY K{Mu =Jo>\Qޓ98MHW6Uwbm%5j/x_|veI؂%}/X>}ÍZ/rp9) swN+^U% 8qO4*qm Y^}o[uea^}ګNn]=_W۲}tͷ>ޟ,|!{3\q֢ Ƽ7Wxpѭ+Nx}}g#BSS?=s?=zaջ͜ӧC/#w.9qM#W8|߽_ŃX2vJyQZ{)>Ayt1o4tѺuő#/.sxcd WxWo>t̹޳ڂ.K>+7>QG{/Zshx?Ԧ,<%+kG^ɉ&\:к*Ŏ{{CacǾ/W/~ NoMWltdʸaso~f3=q/1l-z|iRՅ!?-T|8kŞcԵ])~nͧKv%G\~a,[]E+oj;ҼٝNqM_93^ਓSZqǞ[s7P[_ަ%k|/.od ~3oMGKgqo~*>{=4\SrGgxo8yʽG|=߻:g穦aGn|֒Mk Nu߿G/o}ùÌNaaaaƜ)X]u{F(u:g3t n0ckÌ ձiZ'x>-D5. HbҀY2I&Fr&2M )ӐBݱ8r@->8]Ժ$Sw` B|i;AdلdC|=ijÐ8 J4ĸEׄ|Mw=Y`äg1꺉&+f E9p9p L:W$ЀfĘJIcV@˄{i`/%>1ŸH O.`yv^ Yd& Jt2ഃa Xca7pZ1lxbia^&k7Ć'k-hM  ^R: tj|Y(nFBk1 Dy0ӮOe@W+e?惀ʂ+UW{aog.1g3*l3u"t?Պ!ompZ$^5!Z_v΄ \M 44ODA\TP42ܒE !8YJ"ړ_>7mfBȤ0EI)8Q`+;!f?=2R [*タG+Hy}IY%9<ĆP}"Qf8 6F c/H`b~uۋ:-&SYB*`@C)]D"GjF cو)|/,}ld䍒}N dYWCC ̝EN>X0 YrCȇʇg!W#k;(+^^gC6hϰd[USK 6{PQݥ &"ˁGрe?ո.I< @`-@-p>g~0 :mY@A&#)i'u8f|ܛFĪuF Nr0! s J|)sX7aSzChCLvZM,|C3 tUmN+r}HMiIq |k _jQ}< kHt4dT2g<ߏg=~_UJ򮼝[k<8}&L"kH]ilm) |*$-pD,vPEϏ]FRB/V* {mdAM&( PRR)u 4D1ClxAi6*NMp]^ZTY:>hq6U"ta5&įDh1i72rQ{Ey50ߴ}MȰۡ8d)DLms9_nVR'R ra~S/;SLr]'Ʈ}\O.d\k y¼~BaH%(T`> 0&>*I՘#/煠}n$}æxfedS? gBwkJ | ~YII%ZsþM/!ڴװG;kgA9rraA|8_\o=Y gjsX̹\x Ik&&KHIpÑ -!6Û- Uuܙn3&un;͝4 BV9xB-9lw9UǴo"{~)3HaмHUAG(B-0^ ՕBpehO!#WղءGY Dz$,DR\(/fKܥ \rg 3xgZw$) Y=B\\ fJP/ zDYVoʐMѻ1@vB)PO:~QL _M 0ThR;ig*[2MKs"GK}w" A,-ыǡ\E#Q.uq1πU&i^T\y\$I }Db(P&{bQ@$TìPH+Tߌ(%S"KBn@.-ZdPj@PRţ76Ŋ-5& DzHFUJH f/Q1X5ޛ,b9#kPiuoR5\r6\r1GM|P2>aazͼ? K8ZW㲢^5Z;dN=ޒ6XY(,_uQ U2%dt [ ގ2pא:e9hGBV0}DK0]?fI, YyW?;]*c&_Pj2&\L;8 S3A/Lg|-EM_@=A` LSQRo SFunE 0"{oF/5CP=fo_+Һ.M]zM-&JbaXxB&CLV&4Nֆ",yǎ]}^A'Zn6}#MYI͑g62%ZSկs2,׺=qzZ3Fz`K( L[5)5 ĞT) K]VԱ["uTFD VL?'ZeƻNH䋧$B{rD20MMY>iO O3# 晱^e2zU}A%2\ 2KxX_Z z"Q' DQ6#.I8·㝧-}:a 3]j΄y죆Ŗltp  S;D}:J-qYq,B&* Η@ N퀢;O{ VQ~yVU+-{rr%XHЅ8ـtjZS! eT6m&HK~6@D"0f@udfa ـt&H]2S2Dwȱ Rj> "蹾L=K?MjZƤ1/]P6aOT S%Hf5c%p6]]h*U!+a U%^}QKj>g,?_t/O{u({6\?bFxV8}?,mmXnv= & HG L9AQ؁=]k~<{ŻvWE̷=MWIqxu.5],̵=~0c߿\i){}˩ٕ_;vVds|Ԙ7'ܻ1ʶs*5Ww̦/7o;|FndggFF|bj$6myГs?~tAUS4n"KWcONep/V\cGs1VWJq3^G=NrH&.y3?X`sUq>/$;<&x6C6 ĶV>?5IScGGI}eעf;%v=[Y+U뮝VI]EFz߄m *~j/~wucʲk^T_gc=JN5hZOX9W-u'{ S:4qywӛzO(uܦu^81/u)v.hyE}N h݌6_ fwݔsVßl͸gJO aխXQ`bJqym~e'_z{>Mnki[_vlsrBCDyNC<[wwٴ[7 W%LK,}g`@QVG^xc׮( /(mV_Ϧ>=\E Omtν\K۹~YrŽJ;v7ʼpxѻwWM*ۯGߎyWChAsWx,{gNݺpq\d[{]8g{Ó{o}jvw5zz$غO,rUXFq J7':QJŇϯV||UR>vO2iUFԷKdd7|<=g7[Fm]kېΗlU5owq ;L0KrWBoϫvCə6.+lo#t .s0U|kle~][kXD솃Z;qȫ>v}Yojj%:o/:|kzVGOl4ٯzSfaNrfe~z/c.o֊!N&dmn8=v,mRlަJ5Yۗ,ˊl_y^[O'Ni=R/o jmu{QatYmcݎ:qEmVPH9 YUbCv߮63kM%޽k//pდ__>|[ivGi0+6Pnk;l<}­g'0jr]͚V*sG#no}Wl7_-oH FǛ|kKFtԩ'@zo 4\vvRsOl&YuyȁY%mtt||5\u17d_GݟZtٻn?xW>_eSfM6^K{Гk<WMCWOyM0xB%nʭ2˭khSv څyVtfG^?q;v+#5,ՆF_*3{[?v Pbo֊9n'7ܟ{cf/4+vBtrO~W 6Y 2i$[}ߞh¸ic]no]?|dmJa18?Ӷ啲W7lHj6 iq̺ζvrj97~k}OO7nSQ_K\tյ%~zq7C[~(Q}zjjc`,vʷ9{ n粋w]AfF~m>v󎰩tҟ3n:Ey̘2Bgi-y$*>tγN t+Y}vmϯ==]s_bmn+~xÀ.nh׾?f %_r0V_W~\Eoj;s}RV^Z\{€=e<; ߮xmX%r6Q9aRѲ^P߸wˀ $ŧƀu);RU]Žya/uգjF~>>sG1_&_'bGoϯ?RrY99qAKzOQbqݘuʧ7\ǡlIf{^ߖꕟ} GJ7(ν}r=r*Sq¯=ͮG=W\{l_>h?5H(׽/J)sːzXWzՊ&[vEvWʗO=b٣{ܲka2߼K)ݱI^kry^j*aÆ\4eۗٳ,u=|yʪVX:ٺ3/DtX6-><^\t~Ҿ<^Y>.f] (1rOUTrfnw?wZA ? fk:j~ޗ2W-/h C>90Z 6=٢jĐ᩻;wuv%|Rr_ƚ/\ԪQġ|Mfr5uKvԲ#j)~f?y$:_9VR.nB}މ*S6G!ήU_jC7ywdyg'kD]>8bK>)Y;sxŧI4Vs?{뒴/Z.ƌ֯4Wxy =/9{Wuh]6jO{icO*w{qg4Zޮc[}LuF[foRk6+7eU_}U0sWI6廻/0Hou?_%:0Ng %s-1{/w^z۳ Ϯ;TkYYv_D!9z[gٜ v|e~f)roև9ѪFXyŪ >(&EtA!/m4bzoysINfɶ'hF}B?=d{uy^(3\1E7?ЬM_t2"}Y!N|{{Gz 9MYmLU3땁mm՞-om[.bYVy6~%uޤ=Wt݁k3HŚy,yYegu)͇FlKUv݈o=Dz&/qKw?0SܲsV0~pIɠ] 17^/Wi&USxZNvW͒W?N-vtrIRHߍgi^2DDFZE-ߍ:FZFZnFZ?h[Qggnn^UT#-.8WY޾^˽xyEz[x}Ep.OOy~]IuMYop`8H,v &t j(Ьi/ps0B]Z닕Ӎg\#Sq%.O=IMllRS'm:}غi𘘧G-=˹[;>hyX2yTQP 2|5ⷈ|;(uϼ3SzǡCo/T=yvmK^:3&-~XKjʙϷ6j'GCW=?~-/k<F럧umw5<3W޽TUX' 92ۓ=OŞkcܤ6b*'0n U>ך\ܨ>ω.fv5}zNy} >׷s.յaDB^_8*fHȋ#ڼ]zo\H~m܉[߮Ƈ;]uo[ϫuO{&5ۃ"^hkr}\Ru{1u-:[/Iq>UrՑʦ,r|\o* N#M)5S){9u>lW,t]gu7V״ܕ+ɺ ײyȹU-vW]xRܙ}_]:7{_ΏwڳI^٭}0YfֳO]3kJMe,c_sC6ڜZu%j\-.kW 6]T5wtW f\VmXC篴RlDžm?%/[6Uݞߢsb=ϝVؔO3]_0'w̹Su,2╬Iw-OxB~W3M׆gԮ>O}l/t5Rz$d/Sz:OPF韋 ,=y3x#0<^{_T̕ʺJ*kerKݻ;c𼙎<+:xGz~<{߼z/>52m`^1]}9wP-:*y<%z\wo]͝f#4*[(]q--E5{]93S+ąrURZ#E!78~feDpItgsr>`eYi#M]{xr?͛/cZ[״!Ć6IU7 O~_S,U-c=%w^Cܚbo{/,S:IeuJ%n_6o*= p0%Ks+0DȰKZFWv{xuVvŎ [Sӟ\}kfćiұԉ:u|9'96WonV+${MK[p_\5BhŲǭ릜Rc͡}ڪk]#wv˱]l]'^}a!Wr{U~1l7<֤3뿩NȁhWv9#v{SZbKe޶9G76,}qǵ7^1 dB^]e!cDׇ\hB3X/][V8A؁ꋵ깇+u{Xzn[X̸8:Yu Ir_}co. *Ym>m|T>O.5+n{,~㬽<#_bπϮ|~,z[v{][s>6xcۘ27Lyhqomp"~.zF'FO]zxgr]_deZ4>8V͛v9'O6dÝr}})-]4(9*5;5V{.6uq9ic=կp55o~R?n:.{>Lb7Kn&s[LJ„ڣv#ge+vtkޙ*N=5{[ǣ܊{Pyen#ȠU#w;(ϖ\u)#7ʟ%>q']5rҀF^kvu|I {}E78lq䆭3;5ϟ]ZYc<R68Zs[to_WJ+wTxᛊ'~zH3E߇۝S/^jYU+opG_M3pm?y3x=z<{7n[ݴu8nNjkP:ҩVҗkEíc|[,柋45F6}!R%ǻqN^kdEW6ٟ藼~ղ^綨^?9P3!voI7J6=볝VjڥOJ;辤ݪs~iX١־u_.}3m⤖Koe*#]}o6~?V?h{o{kG`\ţ j5*o{nfۮ+ضUy'jܸR$ǽ2Z{l4왣5V>Pьޭ]3MZmzԃg#ʴz:y¤SY5MS[t;3{>йg|oJoS|c9־VrN}Ӄ/)gǎ+Zm7 ߨ!3o`d~ug?T\'whУSTb^/wo;^O?Ⰰ w?nPKxz#$D"q R4IQ K9|Z$ٷ7AsJ_ˇ(28" jNC&/ZHAN)j`B"=L.RKp*,-"@e(coX|CVERHކY(Pr )"+RP_Kt?-e5Ҍ 4~7X,&bHD:梎L:4՘( irD^ ];#6}j&RKDN wDtAB@5^ ppP2DL#QL&9 &4^?=jIQ5jS/:vU]e u.bB(r`h̆k΢pJ4&rZ(j > UoqD%Gsu^+أ{%pdR:QPP\F 5MÑ`,1')dd@" Њ;!\ a@17t!JrW ^3t\KDUE7k%a:Tۨ5:2Av W b g `CfBѱBdDr޸ZLVK'$KubCy):*-#IBPB*+F؁"ϖ$^ePX$WvU4:V6 !<Ѥ#,&䴒9j PR/4jsЂWI bJ5&ԑҧL옝98LXxNPyU%A`艊MKa|"n,r He lcTA Qz#6A7Jr䁈H)|CeB<J=#PX> LH68I޺GVUD1U9eK^btx'"Ly@8~6VU^\bIqDTl0-He nD?i:ό`"%Tɷȅ#]JݽFF᪑PЛz]{Hp:oZc &PX%X?6&U"Jiȑ&\1 rE2 S @]9H :}2m-0!Ezu ΀B9 #;&ɡ+XPKjZo ;mACzE\{Q p(UY2Aa !Zt32 J*\ؔH:*Zg|th9603L A%& Hwf|H_FPk^Q*"AL '~ Rё߬3L< d$"B 7ad`=($rD"R"B=s',nhN&0p="XC&WPMem,/EFfWI@@HSo0&%&-B\(P(En?3)\P`_@8gR+! ZwHK2d G$,Hq"0{N zAKTN;褙z*SgI[B߭H"&TY, (qrb0lʐ Q>IA֖CEk\ItБưuH70PM8#Me fg'SEf4`)^ERF. ]$6&C )u `C!PNFK٧2@=0N̠a<㱴b )Fpt&8O''8'ڀщ~Xc+O?:+*ʏKUdNn[XkXF0d4$![\XiA{و8Y\85`EKnʞV <(,7 }9*jB?[BQjgHQMh%ih&>JB=57\#G1 `9uVB77w9+Y-2+P\#KmG@#3J.cCTPݮd(t&[k66[`C#jE7& #$+5'F=0(@JWFFKK$vN8t JpEDYs4cѤˣ(zc)R(LAjU:H!**GD%kDi32i Z)U5(79-[HGcxw Q&*$ 8jրMtdN+*|O shpàR!Hap&6L"DAL7ꨐ@+'RZ6tDEc؄fct!2xZqJr3E BqXNkId 3^|x3d|gNf@-(:QKEs /EiTXwdbQ4IOlx[@t0?ˊ̅R$0y}Q*.U3}L#:j%^JXdO tShp!r7jH `;&c: 6#+J"3q FGX əl)3_`f%t!L r4Yx]R%ڟQ, 3(v%QM7OHFK)N/J2АSިdj`j1 W/!xghJ>BPEE?E IRB t2!b<pPhfYHMAk41otӴN7 =orB{+lsai2p`8/7`xv!C8cp:BL;YzV|C_İXjFTPKd̙ЂFɧH!4Sq'0hj" AB>6YKr^:E,eK}qL35YeZo",lzD),KFL^-S46Rxhclп 4 T t}ߵڔ̨SR) Քi._o3%A0[pL+`ҊA'L8) ;H@$yEXH_GTBjB-UfJp1  r!k9)ۻǶ`=ASBIAܻffl䐓ѫѽQ`e"2^ զ+)f3d z9 Y0&d! 6)SP{\C{R2ӱ@Q((80>[$, :+{b4y~\q|L{)I[EH0G#,.wދ3mWeԚ[ 8A`16|nPPR@A+7NG|ʂǓ}f"%E|fLi~"!9㊘XyNB}̕$Sz_6iriKge& TA;YjA5-r)Y01.Yá͙ʆL Bě셂QD(bgLx̧e)kFrgdyl'3#^ώ%o1+ZNƇ(%@.`5Hrr3ڬN wDDh'jaE01Jtف+Y\~+C!.;J.ن>F ~̴|"^d+xgPIfay^ EQ8D}OCND7ͥ#gjVG{ђE&Eqvv}Sw2ü;=4<2.n>>Ac͂W"$""Ē0> ӕbDQku=3Юq1bh}-  ܣH0 \fgu؇Ӣ+BNr!E]@֡DӣÆdMر,oG[nHMP6aǝF,ciP3`Z=gkt4**¢-XM΁d*f04apY\+hcq=MwgJB,/zi`|ZDxf]ѦZFX QUUgLBTGC"1V7ޜ07닧~ ao}'c~`_'?k Us*T,AԙJ'EuQhi$=%d:C2a֠ඔvsebY+<$ю̕.hڤQhy'Q2豚Ta?X!H,TA*AubisiG-d%rI;@X'^1Qqq@DK!.TBQPYY}-e@0V G:_0PEPzFŊ)HQ%y2"֠X;5g>dRP+2 }[QfE4ŭ|AM&6UĦh䓚`qXy>cqIT YZ{h$(1(wM WWi%;dἥ"ɩD$'l0`jPOwJa>E\$Rѕ 1Ʈbo}%%ٜu'As -AEB[Q+Bit䌙_s.ma 69P 6S )rh.«4DB5j"İ({".-h2-ME9*s|s2 @ x܍߽"[f0)^:<5>(yf,_PQ|W6eV EK7@fdLTDoࢢ}Ɇ-!NdE_>S5hT bQo^h!`jw j5F) ^*5h&_G=' V /:6jpd򁜅ꍉz@Dc`Y0y9sszKO%Ӏ2h;r>5 s Y'DI82-$)!MBqn*7/=o!2)z#B8Jr )L2$Mqf+M*])F18C͖82KQYmvR"Ru,~ܙ.v 0vzzh~6\0v" :a6. +ANv\ʡQ{LJkN=lS'*2 Hp5ɍG.BQVTPL$8ar IG1e4Y,wsi.F:nT(0AP&=), Nt{d<]`R"2eH"0)N)Q p  u&Pd !P vi@:)2IZN-Ѳ V\ִrk-l<1!ÐLA$:Aw@iV$dq4\flC2GhLA(P큉 e1}S^̥izۅ]:3Sy80f!̯ov9XGɤn7^ FOy]ku 1nhEQy*_!a 3pK؊^i1?. 1vDT L )rF:ҺkJ S)@BJ_8aV8)aNɱJOwav>բPcg!v@HD$1B:pvW,"顡JG0vPHMxXBd&\^?H!n68Z8"T*S(0 @Cs4WM:D C:Ԥr>|hE\&,Υ4IƨPw2HC) /D p'U`S %SPTǀ.aQqH>LJAEUפWdh0hL-3:F|)88zfu1 ytGQut\:J8fJT~B/K9!a+V % COWJa;ӱ^O#S5XX4Jdާ`@aM}t^$zbO04cEڔ*af~pOiefr$VMMfT^i(eYJI 2%g"cN>QN.p-j&#O泜,cYt[?"WjafZ1ב0 f0Cs)(âEG%XdX4E<(F:03xyH ?wc9"+RE乔U|4!q1N|l:pQ ({fHVe/ >8Z؎lz" Eut@@:"5(dfJF[4ila9P8YaP_dRkd BD.>`y2ˇ`/d*f`SN;ʨ2(FOX" ,<+L ˣ<}0|mI CLi7^Ez%3㮮jzyR0ٍ(Ψ!1~*9(DPzOd:`Yp* ̋àZWPj<ղLfQGmbur F'XQD^S49 ivPl:LFgȀ8S:(125aP*,l,-*(`,XSsJG(*)'ȰQLjRDH̿E r\kk`% 'Z ڰFBrZ) |rSQMfn^)M6qc2 Zp 4"o/#6 w)|i4b"IZa2)~c r̾fP$B1n2:PeƥsfϢ0gQ(‚,΁BP#h\ӳrQ>И E7TڪJ9gůΜa&~Vka~;kYB*G1`A9 /JmhT\r{ĥS2SͪG;Hb#CnS8D o! b5NMHT7H1 #(hBP1+s:ԟO'M̩C}PG(tD(} 63o4-XPO'dZ,~Z ;cO}D \rVӧ@ F=ًR_.IC"K7Z\БXJ=/F`HcD:,vntSlH3J.,}>t*hy(AE̥@Jee q6mT_+: QNnvZ d[#dD` ri@(A56hׁIlD!"ixsD >'FgepiQ$VV[%EXak8#-)5- x@f^Q'qj`G(XnT]a|Jd ߙ-]T嗦ݬBŎЉ0t %VhkR b/ݼJgA}mV=zQS: zhV7Vh_Ξ&"|AT30\Fޠ*gU0ui\h1^V D J3AXLŢE-7(ÖT뾐G:*_ ]qt#qw$җ{ 3. ՆDVoTղO?#~tD k[bhAa#x,XװqgBXc?#)_&7A?l$/mL#d1[I?-E[$V;evpCYJ?d0ڽ#6iVfcvDZ#vMZlslŢ)g7:D(r 1cBG N ~pӨ?QgUipCe{csp9Y3,!0LC4&h~ **k(I&,|k9`B&{t01(S͂v2Kj#IT!-(`JanK rmOy"i,cw-hV C_Qoi>LJaKKXK4HPqO6r$Mkop*p0T4eVoCf[ςP$1ee636`Urש_2zC{uHޒ)F'mj!t=Q)r43Qơ!Nh [N32pT8*|vWKhV#ʽVO7׋aO p9xNbgp{`s&Uq-7ʸ$u^;cn_c.>sFI`R>iQեiVQ$5}Hģ ^hJV",򁑈k&ߠX9iqP0bU7'{B2ߢ@GLU0rӎW^cؽDSJeu'9|:F T1*[鈬ܒA RJ4Qmila'FF-KOIJ{-ŎVr{괜gsǺ+bbSN ɹFV>R>v<\= *S1.DPSቑ ܆pKhLO=#\jĖD%$, 3 5)84.Y|6fbi;+[.QQQ1q11{Z>ObUZR=;;1 LfOV  ( >wcLryGiJ~ $A': |}6LlvB}…ti/@,` F;?FaܷY)_<ڲ Js, XxPK:HA^&5}3f>ٳ Yn-JSJrL.?>mř[@*ɄZpGóG2- 1:&rٮ?7#+LU]ЃF.L;k!xlVhx0Rk ^Yک5 8x1r->\+j`rr~;e?>6AJ0Q#)V'w+6ݝ@dqRJX=BŲ|>VWߺ8V%M}V"5+:QAPDxL@~AǕl [kVCJ2uK?qRg[-6w1-qAUtE!m\TIőLPbar wn lM&4(g@# ӣy̹d rOL;ޠ'#B q3c@7(` (ձϤA grG>Q@%;RC  y]jgSl{'.F`I2}8{0du&?Ȁt 2A(w,׳uh:H*-/'%`L:=3)c!" Yr_CJ/oMJH C*O;Ldžl{$4SK"6O[pУ`*t#;S.2d fKd^D,qCędm2&qAqZK Z8@hJUoԩAEb (k 2T %D?]f E# Gx@mEvRP^DB #"_~R%B͙) dђ8FG \5@ 8p$mĮZ\-QS$?5=΅ H![\~IF P GrSpȋ@+ہm2CPۭSO'L8sgGp?*ύWiLxtn&1=bZVQ2ٶl 2Rv * nJ Q4@|Rb\J(wK]>9Β k{z^bM3I< &hy#GH -yP~Q԰}\L#bgKDD)83ز&Qy͒!2e"!+Im(!'9nÁKNY rA+;-4LfAn;m% ؑAv5W;eenz}ܛi?5PB5 .Hň>-MLº  t_-.hP5$L(dbĹC.*A)xX?qZ6ҒIlY<, p2 F(S48V4Px8z^}{'؅JMI aAw>~v☵ {Tdԥiln%O?IyޜXC5#T.Ũ* F7tD2` JT7ZɅ1Y&/5]G RTcI&{ ".kel6DXR#*D 9?;6Q9 &RP&\PndŨ²g Yfc":ִIW=9'㟬AņHY-`?y$F5̙g()Qq婄3𚚌+5^J ytaIHC<5Y!;Fc VB(9NAarP*(n:^9+pYI %._εKاM ɦMV9@N3 4t}"|үUH"qT8"1FA녆iw!c1D/8@ŧ$s:J;z Jmͫ}!Y}z*\*cnp Lr pT;5$Q5 uG:YcMh=eL$2?:NjVw:k# R N6)YS1P1B s.BP&2 wk)SXb%Ӈ[bxH pai5y_$İ I"TPA)ĵ!=]R5*[uj`nPᲒ=M>- 5Vebp4rz~@#` BuJQ~Vj#cEƪ6b]]C'%6@\VmT ۧo[M%zW;#*ĩh{v}p+#[&1+8-:Bxoဏe ޭU}E=P8L£NeCu2o'@ z@!l`ĪSq >AAFjI1 ٢k(1n0O;⫂/hwI m 5T7jP>.s HvBwHӊHrԵJJ=)$1*Wpz(v}OOGHE5^4J 4IO KjROw£͆ZɭF Ř" 6Ei(ZHKV ]䗶XMY Q=bc@D+~SN2(Q?$BS+;)b=4zc1`)T8/ 5+2(C-ULH_lK4)agͷi,uId%Gp{ZW nub9:0vK.U*cQ&h~HGY[b"d{eXb-uzd|l,=.R'E5$2*&:._53OIq{ٳEɶ|R21CpÏ`2|nvC`L1!|xtdTJ2Qg6|* QO,-d/ ]()NNhevtLac}u42a40f2dKـ:LbgCåLA`y2C[Z &f35*] i{`MSͰ-F v ~aykt+`X 'VxU~K ]ep%6Ƒ YaPKZ0q } Zjmgݧ:pt7VX)|,̔I - 1@!y= z<82 x"!0T3<5VVx!PT/*`aϩC/wde6ApHLd5e sIYEi #ذXU'ւʤN&8(E @QƠ_exHG$X&h^[a;@&& E+9 PÅ, ִ4%) o2 YFKUcI232 v !pbGEzqz^=qD@3f9i0cRJ0yD:\j3ƈ #s1~^{97>X5Ȟ6C)vH+VӬ1Ip2Pdxc3S\Y(|2m%ɷ-!*_ShuB'0ը_TLw'~ aګ[ Bd2QJSIbPؿ FgxjPp˖z]+HS r I,Psf*!I%)'U)Lˇܭɔܜ̢l%5 %+)3KR%83@Y'xT0UjPkI*Joefeezշt%;-?%}"lPzfa;V*irX2B1&)YIYiJzn>+lRzf~ZJ!ƞW 3PVjRvR?7uPFRaAn̰(:9܂HQAU\Ufj|L7'0? ڑ/+_ZNJTOBrh3S+2 ܢB( dQ80-m^Z>$,8C$`׵_@|e|Qf)I0]32Y>^:_Y8StodZqfNЛlW:0țIyIj zdN ʃm';5O/C켤 n`*D#6G0 12cM'C?0a1Nan\ylmϺO&yX;eaa&97)J_Piblua#v WˌK(O($JEuBfe|ݫa52}_mp])3¡H_BeYFk{ޔE;!3@> tAKEi ! 4b5$)F;,q#Xz-­kt'(N)J`6o(|vdBy>1]!jlP>-?[`1YZ?rcR(Xt(u<k^9 yրSu0TZ5dWC1h>=:2g-QcBڱ dkQb/7v|#os0xR;Ӓsr ɟUA0]%d0̈́x3z] ִchm)\´`#Ge53((#o;'1Fq`͑G>UI :IF ̸hT /zE5 UUв*Jo%K" IDH PUyVB,BF$j'=nQ AF?1#L |ʵ[SlEjBRRO;*߉?Xb,1! OB"N `6O?1?JUC˺)A90/ DWAYb7bJP3: D4Lm'ݺ qш(%JD3>1, £p^P@hltnL ╷IiVIُUy1e4[]fM5u!-\nS r|MA/ȳ>mHwã_xCmNŸlcpD5j1Xҵzg/kۯ٨1u=q&Eǎ$/F :Gz6ɮ9>5UPYwAl|;`;A+InXCBS_j7Uxn9N^utY5h ,GG"X]vWi7@W0p\%bEҨ x-M'q> 8"p".0Нxhvl{U $&[rwsрF?n}@Ű@c9rJ!$xtApjpxTlp;cI+8)Na1lA~hGzŅh2KSJLq%qD +x:W/Djt1a4P1 By  LM@$^&Πqp1׉N%&4r93 TvUce[xKy6_xB PobwP+⏽ut̅):U.p5?8I*\vF?BSR@b%~R9Gv9B,xen/Ƃ`;%2 {OY Т2('sa59$3ED~o {O*ASREy`)À6G`wy$~ʜvAmM*$&PYzid]CT2r["U&"F4Z|5&=385BK1&"HS"hO Ѐ$20g5m\Y>诅x ] MB=0i G@yӱ&b }o'R"(,iP(%Τ8GY![QM輈҉ vU@JLq7|ˉ&v'v@YBRwѴuZvCJ#قw-?$@)dL̸. 90@H)JjJ-TD _Dsvpt) %ܘLjXWd[I/Hx9Pq(3ğEbH"FwtEDW ebhte퓢V(Yc A8M '6Z r̅QJ@@$lQ]8=脰.z㤜+10]f xg}JQ@%0XjL,bvmvdNH"#PC Sal-|;F*5Ǻ>d OlB܂l&Dh yO)!nҧi8^eIۣ^^.I_Rj$mV2Z5 )1zL0?{_A/.N^~.7–v9;0bR&J柴J.Vf`2PJW2=tBjs6)jzߋ&\yyehX6N;Hy~_k!@\} Lo(͜P(h41(NO6-h Z`ZPaL!FUUC-- KG\E<Ъ:m{C?LO3"" ߫ݕh@u1F ;JDi.7ZAvG2T#Z! HU dwQY4JBtjwb0-B bA8{* GJmClLX}֥.(ENDINw=,Oż}m^qykWbٟ^2孤My՞OF>W{WuKMCZ=kSHvugsq`17/{{O[[V~߷NcSmʠEsoo칭yU;uOYtCGl@}z\7v[Oja^؞?iaClꇖu6skg}hYcG-W}1N~9ui}RVܼV-:%.̚p--[|Iߵ'fdq]&ɷ^7zv g%65yBџ{gɓ&fO;RaCC{v }OG6:FM|b%\CN^ض?8[qo#3~{{Qج*oˍ?nuOWFU]Sk}\.2'eZcϘNTbZkWS^ʥxqŲ&6{'/xMg5|hק3e.G>:>QS,|rf/٤ەMYpi'j}hN3]>8h:cV y%YA# 6/6b2}[oade/("~.X5=_%+|w6}]ӗ^?,yO{錉Oxc\u֝|kXM+|̧$:/YQۜ7tY%EVUuzۺm4vŬ{Y M=M>w#tk+=mm&,<97i7wۃ/x=_.usJ#N.{s_Wal\j/->^䉞\n~tsJߦyNؾzkX,nl;lz˅y]_j}N؞옵]܆'>z̅BNO~G`æ|Я6;}7C/g A+obkoV-|3ncRnyyϥ+޾{K>Հ?[gwI73mWC[=v^Fo՜Sߞ;}a*nh?牐3lI}O_r7zY++ ꅧ/ڸ?vHKiu>ݽDV\1`H`-<<ݎ^]h sȫ/jݷ~1њ7.>t!϶nG3&:V4$n^j#: ệ߻D|us;fx)w^Եɯ/zc}RX,_1閼iev6!]S~C73]6._N]ZA3KOlOUnh=nٮ2tܡq{wW1IE=mBߋW*Ѽƛ L4Xq_״};v>6{82{ 6 ,}<%spkJW8%n,9͜b䙈 >+|jS]<Ӗm;fӔ:l%g߉MLvΌYۛܣ#kh}9޶|w[_/Y3o1թmn}춪 KV/[unTb^rվGW}awgW,e/;? ux߰eя2?UF'vtM8;^ɕvʘ0tˑӯ?vSg~W9/&e}v [g9$Vt59}% xFEY.Fi}fޏ{y [z]]†ǣ ~p=þ^{nϲGG'N>=U=ssG_0u}i^eeyC_{`eẄ́3^i7^3žŶck?ѩ; ~w׬'m[v;𛭋O8bs8O/ZrtPeSMSiݑrsm1stbEok[Ϸ]a?n/%rMw$^!.i2s-sug端}>'} u'vٝ!?]ڽ拣{ߺ7`gCZ<7}Nw;U5Z8듎~դ+b?Z1.ꝯ6Le[YvܳpmSNyo&c{wv%o]0Մw6#{|&X2~B9xo9+MS]Ⱦ9!'u/K anuݑ_'\rͥ?4-?zon?q6){f:?fW|%qRĵ'mR6];V!#5d}7e^_Gk7mݱvw2ս67>7m.fe WsjՊ_zwvaaވeC7s}4ݺO [>2"Kgo꫙3_ۍ9zUs[hytUo=sㄇb{zËogI_`voѲۇ^7%lSo|t⣱MK̲fԑ5?ݚ7; <悜a3*[ fP畛}ݾ^q{+_W/-~N޼s_W0lw[OĹ9Ѯ;e^ջsz^tl^^Usdى[5u|qa~q-O;0IϗmIZN^{z 5*_ڴ+L/zQ-ɽ]?bҸiwu8bX?zٵ}XY{`q_Zo/sXl=N|ܢcϑc{w۝?s)O\^Buq{IQ6_ρ%[>߽K>=~ĕ?/o[ؚެ=sEދlŃx⢥-~V~=97{Km+O\5ޡqS?εo=Bϰ-ǻo ?w˝ C{=6܈bɨ7x/*xrGFpw̱ ^A()υ3n;OW{^ЬŇ>5j_}^Olx5wr}9{}֛o?~nD3]wg{N}j򈬁c.O͙sj'?}{߶qA‚>|O癓7&쉰+M_;0vp@O2+]B:bV+N%1tlo/Yx%G{#ar_+Wy='ǚ&^5pW|Sb-{ĭQoS[~17N-ݸ߀&-ȕ/X^ǭ ;JJayE^е1K#^.GҦ5=3ۗ:ԹYۯ{聆3m'Gcᖲmv|/O\6o;989ߟbXċ?6Ҫ䞗]SNK Y1nKnn[(ض"Ql`M-M(Q ,4) B D @6QDETD cÂ**\ S{>yNnfM! _z I\E;6MPwZf2BqqPpgc4Yʶ. Jn)y+cZw{pM.;1vܕ&wz*(asSL[S߳01}:6l^fC6&SVba\q*ugΨtӪ7 \ުHi?)ַVTa7zƚ9A`lxr}0oqMn݉U3墩9 eB?ͼE^,1`KH/^v<FfXrͭ_?W1Ng䷧*wu=/Ҧ)+*ȃ4*< 3j[K{=ވ:~4t1۾LSyÇR/@C4|0>ݶ|N=3сw.o|_7uCΜrosQݻ5 /ikE]گ95jsZSKno#X|`ؕ-1-=M)^+4Zew})'q?^nģ3bǎGp]ƅLzg|ny/؄QSy3NxȯJRa|dz3Ўr>u8v {=r7`%/{?>6kɩwk#oO'V zjի=V8.jbF\O#(\Bò<ϵ],\s̾v$.;?"7_w_VQ7؞Rw}i^^ͨھsGl;.*DOD5  J'3JgaP:_8~E޽)^Ԧ_]>Dq)p|9S3$X5b~Ւs GT;rWy6̜fZwǫ}Kda/_yw'́sV6,6ïL\~ʇJk'zC=e>%PWlЏ=t%\΂kGJ˳yVP&<~b~zI=[悝-SplI{l`:.h{a' s)W/WT8\kNcAm ۟7\HnvMlKx"QyadØTmwwYRlOԋ}'z6w sɎ)MΊZ{5]}eJ:(П$1]ƶ 1M=xjěy=n歭:Q-m#1.3}'d$st4-yX$ oO'9BLLm:Un} .do9Aɍ#z70O.|eʖL:?t-kmw2Š-ݝN^:=]oί=S+Iő%!ܽkz_32;ljeC/5kHp:`Qp&yL2<}y`_ |W8o,9p`~)sF̷rxW)#+G>c2QR]vh%W=O8in{EGݰ6,$oGH@}1:b:U-NVuzZʔ&_2Mx-Spʌ;_'3Y3/m=rfW=ܛ?4Vs{7N{Zqws~q+ snxƶs *#־4̯Sd"qӇ## :H\^~oqMly/3j/uyMjOF-M{֢T᭺sQ.5%yW'?7[ 7>Gu\A4F&voMskYjn vͷNWz3 ڳcBkn=j9k= F%^ -s3xG`ޅpl"~HG ADе 27b ny$~xwŋ8v%BFkRTitK" +;fQhRHg!»204("/M xiC,!|[[3 =0pD"n8D/H"Ӥ-EQخ)E,OwEVX{(64̔N:;' #yKP ,qnN xwAm{?*WS4B:X}YD0%ʁ, Yʼntq>!꙳Я8~_8 Ѣ0xp<$Jtȓ`P$9x)Hx0M0 &0phB#}ko!\?L o) =DFk?Q۪VX+XhS^]6l MZH X ЈD"@g>t6A eJ$: ҨT2Ǣet*o2ܱ͌N!u,1eHtB=:NXfFAW(3*шQˈD"}R ͧ#NhH}RDE ~KP,cPj cdF"*d&7FpV= @c5 HcXD;~,&ՆAfЩ43;6Ht;-id0I?1{8" , 323f /+ o%R(4_ >5fg]R7ZjEɺ .i}^.ykYr9<]+dv3>]֒Bp?߹Ic= ^^4~z;Jd|:1MV{ 6s>:t:yu~OF}}2K@xQyaUՈځZ{im^sfgRbK )}{F^Gkk㣤i~gH]/Hq {ݘ3Wi|5Mˣ<_Zvңy%C3;ҝsmGb]_%<>`hֹhz/єw3{:.G?vZFZDfm_̥#F. ,O/㓨.J0d9{dd[ִ4 /j Z?|O 7=nZEyش+|KJ'-^vՌG]7;;{Ȍ/wޒW^OM8LO2J9Qǁ̾99g:OwIa: %]oTq";x͊ц3W[]w7׭( Ry؃k'ݧ(_ؑŖO^`?^0*${R@;ifWo);$wbҍ/~z`Vꄵn' 4? .ا:tإUU}rYV\bg)OvsSO&Rj9.k{4car05fM^1bƘD.cɽn+Oz]f bPW#&v;(&KFZp.X1 v#?<R.Q9{ZEVr)rWtoqUŵ+X?=܁ݘ^<>wL0yvrճk(X%Ur{[ÒDn}A@@oR ս_ 3{s~5's5/,O)&y*yh/s3ӹӜ%7o<]wkG ږto织}ʼGh54̛ {4i~{$Zgu+N|12#>Iʢ͜T /ۦN Ƅpc_lIxtjɧTJ'~ \e<ރOxDG ܢ7xmj>Y-VB1*g=rd7]z rICk8Yx^3Fl\U[KkHhʬA=F[,$?Tz@̀ ,uXOUm^ug%0ݬ%=kSeun;_:7k9ky 9-no}7~Qk75ԬLmsM/G ïlJ8N fwwnb' ގg\Yq>q|}xyeIzμr·-5o.Y:zxաZUi R99-[: Q7!V̮}(DSj%ni ?egߗeZ' ]9vnўsܣZ̯$}ٚN7.oyI7iV?)5,sli|VNS]J {\4>nS$r}IYឹ >i%"r;br̉~aO *|D4Jy?QsY«ڥIp.@.|re{[({77ϟQq#2mR=*MųeyWpvݸ}45oZb,"QP&jeb&趞aoЈPvesQ54Dn{ +;| ǡvNw,uKDnΎRt U[_rA^LϼhAt-ÙNTS˃>>h^oeC/?Qn υEWʘYC{ uO.*N*=A9%ظz-aC-64 }7_3|{s(PȢKl~K^o>R7Ueơg~3L4->*m=Md\;vL4?=7QK/xSxsq͞qS7|UhSvI'g3_}wuʦp";xDκ$/<{Hd3A#C }<ʵ ǮI9x$ΨtJS8"Ȍ;x+i R2 B )#)tbPHPlόXƠFPG!)R ҈vD$$mld;Lp(H5 *Lrz(R4Bd;ߏqgHp X5"fVnŃXA'` :-,c+AUlP,s U]N`4 } H 4,V N2t,@mA:GArlBU(0)[ >b@D-KI#҆K !xBϠ0V$`P:-¡P>ȟ5hZ/MXXDHH\HtX46; ю"Ywc"8hbE'e)}DzPW#ق܊a{1ϡ"G-OPtV@_C(ym$G,HpHz цPL٩ BmJ}kV x韈p'$⪷- mqb؞+pYhmd W]Ƅs ͈Fn !vusa`\x@!,NX4lgx(n5w?P'Ӑ_G'Q do%u#ggGO/(4 nb oL 9ؿh$ȿo#`"ȳXoo`W(9, DAa}q#q dt+c,:}#I7Pv#X$Y`0m0HP :0Bņ]@U:~H[e@@#%?ۜHCv/~2Y*" ?uhMԢ?_e{PXN$\]E9]QE@QP{>OUE?\k?H% {CyF"xHӐ >:0'=}eYWrʼux1%+wt)AIaLw&~d{t"9xhf@y5F^!j[7wwZҰp;4K&ߵc$r{rHx03 qP@ #K#ՃFQ0!y BJ d7'T)ZN)chRg憂"(b8q2a:GYMVmXqְ%gRZ =#HҦDL{]QT}քR?EҭA1Ĩan($-h H16v `DmX(Ae@tn8'bp7>ZNJŚSx#Իx ʿ ţj)s(Y)N T,mpf4UX/3 Zo& hJRn:Р -dz2!pX4wLMN0<@Ԡ=  I ! rtm+`/ ! mW{IΉkUlC D=+2Jg- E[6ѹ-6a`1VvL{W7'ݞe  ݔ!(.=ܮzQގLN9 ȟ$8Z 5à 3]wT Hd|c'@;r\KD`C?[tQ@ :ibA/oN eLE qI:~rT?)y8kZSnX0\&)HB@ bcyhű@AF8BI *Qmek^$Ѯg,txapIn&xhDd*}'fB16io B:G:`E^йG! '@9t x|T9Z`! Bj9-֍6 &nޮ^ҚmT"<(B$kL JJD tՍa 뙁k0cqk.2,B^.O;7P x?BvLG`V*W ;\ݼY?D}Dx=٬Ak|Q'5H7#9"^dدK#;i^/b(q/ĕ 5_g/GW{ώvNHYrgGG`Mqˎm_mwi͢j?5)wcd=6LI$a%F\(υ5X̜j`1J+6]]>* ^^@=WP-|-0 v #/,CjL79bxO r'#9(By_F}h1A}EX==lׁe6. ٺ(fh jdU{#>JmȢ*# gtSV5'+'06HHCA>='Fp@(n0M adkusa<'#m.3٭Y]SASזbPmoM`~JDA(>`'L7&-Q d' ".BKI=@^!ac}e>jILzmb0R:q|'\[ʰ$mC#҈UVd69/9Ȁ=UΤY6ȷ8B ǐ/$C-BԶB'm4)&󩡫c }c; T_mtd!^GJ_#735}Jac8ѧ4$! |yO5 uLmFc:GGH ĕry?|5ҧX{;iڜN;K~rxgDڔզ }G}pews$"st6츭2'ub79izcߨ?;% , ?ݽٵ yP<\!DZܛ%.nn.\p;Y Jor,ـњ?G llv@ &fC:  _zޅFo~|DPߘ3! PNf#q?(TMkJlzwŷq&>"E vK]>3w9c ֡|d$)\ϠER)xSsГ@ٶ}14U0N՗EV͌|e=Pqp gpJfu"Kj'MPwAZFGk^{3)qneGѡ悍Wpp=<6~P؇\x/|=v˃(7,ZB+ܖ_x-T<<ðzD^*4eH3rwgӂBzT;ևZstώJϽ?ID aMƚ.XpO-Q_)5/,_Y>[?<t&lV* +).hfD7/#)Z飷dOXD͔cdFx]7-I.^ e"WrH7hBQ΃:1B,JMX(K2 гC7S 9LXxJUg2%% q5A1xlo6mTsI2q'dzl(;W<pc93XZxGJ$4 GHN󿙟w`#˽ (~5XNZiN{ xACL-On])pIpօ6:/Lvoݡ{oϟ2@;㩂i썮QK4 S xм^ p¸Fg^1* .HX[P3|75>Bqۓ9 #W4Fjx+6UJěyABfi<{x{ьd D(Rf[ k4 /BdYuM gn/=UM:$a g bE]٪itn2t-ΔnhTgu2U\o?m{^s8R ,`l>mfcqPnXw tfN E7p8*w$&]w5Ļ>.LtvTNŻh¸#@]]J}@C!ͰWV sq Gi>cFztwSGRey&ޕ/RX [[V&:a`1̙k$L9))yȫX>%yg(6U^W핕Oޝ/'{{ q,|lT(c{Ыۂv^l`agΰmE^U?`i! A#Z\R| ҃n֗G/>۱BP_vH_ITN>{$D?Ԟؗ&+.yΖvCch:- ]_k iXͺn"(E!2{`H/_7? h KEpT?n}TH68)'pfo~F:3|6+df[o a) ]}㓊ѩ8]CVq=Ex}< Dӭ+?¡8RǕ'̓ItVcۥJK)lN붔iUW@C.YK-OVU[䑡O^V"-3ZؓޯX㼟iB q=Apu{@+pӜ'w};Iڽ郧GPf?ܼ4'_/6N2F&Oe'A[Tw9"[:-5X-a#CES^;m.lCc|rSpfNCc2p]`}38* IJ$ ,ި;IͷՍBMio3xMRZN&hC:&i;;YV&l5\78МhInj)șpZx'* |^| ۜrgÌP;uϘ0Ө2Fx gpwofT(&掛NNƶNf0ֵ%d՝S2R-ar2(Oj ц 2ne 襃tDj@tQQ6%JܨI}Ʊf0ٙԝ. &~紊$T HR49e_K:Ph a_%~cqՏ&"-hY SDg[!x Pѝ Ţ)3YF}tP26G͟lS_jbLj{T'w*]29 NZF|~IqwIBgq묃fˬTH5u0c\2:ndJHIP{@D$(̾I(R#:φ8&TN=!Ŏ5j%5').;G]]1fչWmh1g#<  sNYRQ%d!w3ܴt>zT!:`:gh_ML$m];Mun/ygc):banO&׽SWF[ %\-o%v*+%@!\*t hRV}S 0qar\WMMv v AlFV);P=ϣFP@[6A| T;D.ybxS6[koNjUv~kFјBt%zTѠIzsHoP iTs!D` YB;HYkݍ<?ע<@Ͽq_ Ev_5؞#¸I P_fNkt🣆ˊJfZ ;Qk- vorӆ9 O$y_I}5nt\'}75uMq3- ^_ШㅅBY \ 8iDjUHv ,TY-1ncF6soސ[}u]Tbp^`S$[}mth i2H2+MS!I%jB3|l{sFP18nAKG--v4z}Tip1),J|eiq;0?ig9{ el~v|ޚɶ8~U_$$~g=*b"t~_܀*D2@/AJ+g^9ϠL`Vi$"DDFϢuwˣcTRPhǍ=(c,5 --׸ 4VQ!WNޅ *@±pJ"<.a31ZͿEQGvgFxƶ8Y lqb+AeaWP{ޟτ@+ý&L{UFfFw:ٸMvH*^M!xIۆ%,vsG3O f}7 %ƨ!&=^NЅNq4v8IN)U U0q<~Snv"LN'/>"]"|Qix"XM=&%@Tp0mx:#jf^SډsC:@a1\f[5^~x s<e +8}`7: ӥCH+Ó/+vU`,Uf&>Vqƣ $Q_#I);Qz&F$!K&L^q*UiWo{-O.n1wqtI$%I/hGlJ>rƃ;J`ikߞ]uw;?ȀբHeNk6fG+ atuFTa4xC.]XtR@ ﬢC 6N/Q7OX Odx6GwkvRLJfrtDinUC^"̓W߫''/=l'umCbvKj?'FyE zN v ?G @{BĈ9-Pu>@^)ܞ yVoɩc@Z cKw[ -"x^l#dB;7gj(/cֶz}Ӵ%nNԱQaŻٜM(KLYӁ(#˓u+~7@#UnߴzkUo}lݵn[Z߶k}ߪLk:/I/]+YfZun|8mb°,8ȡrZ/>t.XBG^ =mp9vf-9yG]OƒT҂Cp3-$nǰW=~,B'ydr'T1Gu )[8R,xQr%| !<~b}0s㨸k~ɗprۘU5*q{V A;q,Tɤ 肰-Iy//]`l?-ѫ:E}1Tē+Bbͮpk0NXXʼf^W77Zkk{Vyύ[6̛}[}z*ff V|@fS?~@-G F [VDZYkpL#I e! ؿ>O VYEB-1)}lNgݛu Rk M!S`c@ >ſC C1=~`xMd1(w]Z=gdmz4㇞fr;eb]b  #|SsA~&@#U轪HIWml)/l+c xĈ[iQ:1͊ӹ׍p`G37[@nG`ŽǴ!lXYVP0]RdPT`7pi =0@a;xGִB|w5`_7t0ƹ)VJ&8@6 :'-.É9` _2r'+w65~I^?E\/IgOl +z?,T^5r(  Ө GC`4v* (t!x`pPs0jU7wazTF}e#1eGhd1:xG3yP)q$m +~`/!?t -{uUv,"Y6< y5QG09͒o,46xH&5=CTۤ&9>ʟ"l\D* U ,,@{CTLRNϾ5*D5$#N=%;I>K/N UӃǓ/kZ W7l qy̮a%I66}6{)6Z^ݞk MAf:pfzQhS^+asiϝ٦J %EIc Dk1Aab0d͠gġE&< o9fHo3\I;]v&sf.(ޓ"]qwgy__ ~MdMᗿ?+/,;% <0r'o1|(a4@HփlDQvfV[hR}/V봍ЉnG4ˀXEP RtKxR(BsWnG9l! 3LCп(B|6 0)|"SҪ9Tԓhvq9Q~ irOu M?c , NT=OZS4_g;4|~ZKPhܷ|+22 -$ aͮnbGV$[_w]о`A[_w\nn,(0 n-?/"QBn킕$,̭~пv6bfZr' UVu3+=Ҥ 56Pi"X [_DUeBiN*+ݙ1r)ȨkOq%Go0F:q̋xbA)hIP'-*]_Em0A y+LFE,In OG^8[#Q:2qrd~ ੫دw{Dp`:-j}tqz ** ucPڙkP>Ys.м=cm*S0[_/{H/l\@Uvog^=YAQFE/t^|kl6 <= ez6LmM.9,**7ʥf;T`]睵 ףϬ%[fLw:"!1nekV ":[Yt^d{"V>]$Sʢ]&4F zP3Iѡ>%M*L)AL.')'"BǐBUŖ Mk ڬDѧ<Ǟ`L<2bHZKw @Xk\> /$w@ӡ6>;h1U%F- }Mwc t&4p3;ONӅ S|I?c\G36+s1M:_# *L 0CWЉu)o 2CĈW=:#H-D(Ws1])Zadv0zn!>1J6%u2 ۲isE`?cCšjmĖ;qm&M.VH"=ynMҌ4 =}\jSK 1T MBik5B9ΠXBlv~S"ބn^},jBYaUX/(slU˛)(l,F6#~.D E3r#snFc y ݴ䋂f)wd |6:6[ʀCtf"0{LjU+\Y"*K1|~MfҨ[Sgn`eP*X>ӥL*2.#T<=mXtٳ珏gG7&Ul|Ͱ-x/5:.<'w?vB0*fݝP?- TJzO+&\d4H"H쇰]?:@\5;r\(٣Fe48$xaSAtI0j W77II|>C wSF¥S@铠qJ MO(8}Kt4)E}Jފ&_BuMv<e5ս(މ!+j?aY o(L']g٦eA.),[mJ}j{oMPC[nO'#T73T!ߚC݅+۩jG}T5jIhwcV2.q*^gWe6qF;%x I{-v9BP9nc"-K{&}YpyL{0\Mq STt4]i^vJkA ToD֊lOd>]3Y:ᨰX fC_cZgh3`( *0EwˊPT8cҋ{=u@HV)uTVW55a x~N7|q(dCF@RP?c8K[h鷁9t3"*38XPʻiY+>'_Z6Z5MЪ0Pu5ہK_&rsU?Srw"c?7DdgUUm*32R:M6?~a8~ /eJߩ>V4̔ṗYȟwnYjaZ;_Rq곿TNb{^ PO=v鬒u?fV|lşZdGDcם̼K1aj(d+RO|/gcE' TFF`4@إs[rEg ;Rt.|U|>Yu22d[wSM3sl?j*+H7EMRZG߸]LҜdûuݱ%1; =v'H*l qƨ4t:'wx1= uBWs`% |45˷&KА 1a%o Au?1)o}8j趤+VV 18+D<S.P@I7UB⇼(+WWMKcPOKfWUdFؤ<SxxΫ|Z+g_xL4, 9zA&!8 I(mM^4K8| (*XQ!|wLQc\>̜^+e]FVJڝzEܦlސ;"y?Uf3)ǼY_#-q8KC=I> 5è:LTWGxFIYlf*%0g N+ 񽧔9!DT"-^7 ysaS: Qj[9[Wჭ}Mwq*5 :g7ٕWWۥ?> ʥ17ߝJ>RT40 @H /H-#G5(fJcqS`Xs:[9sl%C.8"O.֊d ) -{7pZB8"&RxVCvE/nȱZ?,,1['HKȵaE;6a1O.P7ogwbV=y[$r hk=HxqHD&M4oa(%6pfpt;o /0&yx`;k+l+܉@@ M:B$=jIxnGEy%W:yXMs+.bfmX^$ :&M6O!^ӛ.T=ʔȸ`+"#i<- gT+\Bߝh@cJ1L5>(*.;9 5[y=v3t9:7<4p<0*FcE{fߊW=xlxK='M<6[ײA&/1=_[*<9u`EzIvohzڂ4!Li< O=$$jO1T95/m$UL,K ,a5Yv].:3 9 `lV=ϝ>B!4QJ P06zbQ؜]SVe}}8zC^N4ەSTź2aٚq$`HqYSSۆN]RXgɦG G+7y08fi[Y4]P(FgL/zo X?CP)ʊ)ܜh 0eC wb-aFiŠnĻzTݕ~Dp/;I(w晫;6aIglj |T_#'߭?d-NQ[Q1(0|rXniŘ!~{[5?dSiEٿ /@À03D3 ,/t4e D Z>+й3TY?EQux._^K3WKf T@{&upWn9j';9f[3Yiy*^HvX)}`ƊŬ,*4/w,yPƲI튾7ͿMc Aq0l)3qp12g7O`ǫ?=9jZc3_:)xv?2K^FTV0 ('n\ΠƐH7sgԷt}%6\bZe2 @ExtCepq9l]Adߒͧxk o4Y*BjePc)e Dh%/#GuG72>O.b΄Qy$h:ְe t72I v{r=.' LD,4ڡp.X^.Hq?H{D16MtEhtrI¨sO$e.Q zED ^5C7~%V{lU`\D/O^<l65,Qa=kx?SIY*bC+Z&įW*PRΑq{ }r#'>6uynNjuz$$+K 2y^* 3AQ(7& BE":w҈zMB %ks:U>e-x^ݐ4,q>leO=|O ͊5Mt!NZbѼ`xuIfd/=~XR )BW @%jX|9T 0]E-mNp"OhJF>׾d;h;W %jӑNC?BX@Ƣ=Rmd +(_Qq I=ݿdag x"lQdBXj0@R4rP):ov%BтDhP($H? "9#|۸*&~c\X,hTɰwg訒ƙуpQb`4$cFlVKu!x8WYsd9)NcN!:ow`c%c<s 7u~~7?>>8⍆]-P7WK-0q򔫰UXąe{F#gj$|Ɠ{ i*aXךI1FP|'QYV0OfE >x~|{xݬ7m33sUSF)XC,詅${VÖJZ [q- >Me JLI Nb^A~}BBv.8Tڡ!hȅJ:iIOTه|gM39/2آn BAՇC|ݖN;EpUmBkۜYK-oDsOlWOE RЛ9 > EIrڱ-(A EP*ֱRH ]GF$`:u8WҔTh ;Gрmm5uz܉cc4tІ1lMZ oVπl4xRo׾{i=:\9J>* Fן $HZhcR̎{S[,lb@YPu>  Qn[2%DI%BFB\lYjZZXi: iwQXIF$&6aN4?uj>5[C{IC@ 9U g~ pq%|h)1tU_/З-zzhLt918ę`\;zc<.Sh30 zes&5 11-b|4ǯ=N 9{8Zk;h3:Cw|kdC~ÿYXhbWGGOl#>l>}mt2ސ B4H7lqvmsT:?u`C:tS&!jD~HZY,L<^}`8;9uh E$N xUꆼNioY.qᓤ%  ~tB"5kW5sju! Pzm0&FqJa׏1#{ i^3Q\ᄆ7Ό.uy̾ț[^Tz^ ypgqeq`ekS|z=4Wx& )! Aqq\ bbh]Ag$OD ~=@zq$y ȯaUĜG?&*2݄+E'dZ[Uwl|=AHm QJD Цupa0 ^;!8&̞ʊ"݂gYqZ<1$7>{%4+ #$7᱒dMn lSa$Į|9"Ԋ?ހk(6pIlVKgw1%%n ïaVqX3ﺘ !Jv)InJr-.F#ziB9y#vt>㕁˿50'O7ԩŌ쿖gap mmj򙘫FXgoYU{^Nh6cG~;ghy4G ]?xAh{!=*I`{ Eq 77\R+iUAVb?` xQ+ .xGtՊ)SLfm'8 *m5Ίtg'\1SyErgb{0)!');Ԙl'`PWB!waif- s & "v%#lQh7@[k'd1 N1X^""@ Qu*0bT/lS= kS9 |o$IyƙΖQ0^[` 쳢t.!I3/Ԩ#x+fd`퐂jSfy9k⟽{/wOc(ulҴ:V$I o h] g>DEwZU#U"wcO KsҀ}WvM\${5d}>Du)sz&pKdI:b :L΁"ӻz0 [$8l k `x 0xj~d(XesgY. Rxh,}0}YSs3M?Wj,Ւۢ~\E"@C+qEWB3g/*F=_] >[JTx7GʖI~m,yJCrW0n|lxo*N* 6TppҏzNkhP\rśO‹rA,lA%E#_#ƆKD% (։TASV͎A+G`>T{-`&"#vW!P~^+9ߴZ-;nV>m\ . -Ah[~4v!x s`Nh=Xxx=^ߛP.]rbʮ*i*~-[$]6'sxx\7ҁL0}LR`ۚ,`fЁ;zyxD0L"Y9rT+CfſdO_+iDt\ǚb%ly Aŭz/+S0Ήnڌv(;rn=Ψͮnn_s(l)FĶ{;T]C);ͷf_ooI=e[}%40d*4.Y)5c9KG#{,RQYX*&fI";# l<ad;ű|9u=釩OT@cHuZmbX .GOD 0#lQo+:F3bb 5sh9Y}dۓZoKqۚi=Q&[ɷ:o)b 'Tynᅽv|~axg0b\qйB-\Po䜕% }S3,EI' cɏʁv=X$Rߪ~erZQ9w|vwdZg U~!sƵͮ@oL<#Jiz#8T*Wt&S62ޚD4)b>E&o(.1e.6M͚͆uT IF*ehL弋Vva?\%E]\ܺ<[EC1O;+{LRp$Yy@.~^×~__w2тbGe^(SַV\ڪpjwgmaZ꩘ȁ.uJL97 *CmXcTART/Z( =Y_PDƩ@Os0-l׳6KJ!/<{-TZ*{5 +s1\%=/ב`5;6{sRwXtԵ~dQ0y?TL$I]"<<Iz#:.H4)(QƜXhTVy˩3fdmD/_s~V;l M(47 *0vprK]l6_Q} ^ nՋ{9) s1i6;GIU ?2w(7,kGh4;I)sRaިa#(vyяghP/EDR vEJPVL]w^ⳂyֹqwyَѨ)y0% L6%vJLin|S%!lRWpzb4*wdX@OᰒSǎHE*\uW3Z<ܪ3+CR`X|;bT#埡׆&dUh4!^( |[` $Xr4𠖔 x8"a b=ܦįE*J DkdoN@kϳ]V4_߱{y^Xunb8'; xyqz5ț $JbBb5^Np,24b0w. 0zRhú )uZ(T $Ojs bo+#H3Ll@´Wx<9I$)󊿇O~r)9egUz1BlҭJY54vejpKi˹yJ6.%@`.Ñ?v6l0$\З$DQ^Z"ڕ-Vc DV:Ur0 2|? zd6B 9?͐pac,JeM^欸}ef>Bέ̏8R&.Pr~GV̶7 P)t^8j3r!?լ{UVdo W ;.T̴hP$\z@5'~:Q7l]oի_jjk˩XLԀCL9&Qqkpdں;Û΍!>q%qʨԝNǵs½m<ܪeF|UR A9|H -aEB8J%-UtHBS*r?t^K(^tA!ݖ8Nf6f<#(!hJjҸA/k勤qmZŎI arm{f([ mLR^ SlF0e*;"sQcN^ˬobOUX՗G/>3kP}ݧՓg'^MGY+^znx0~4dX |~ )Pt.+F/%u[H @( {hdJx>D1 Em~IhMh_yftm,›6{tN*ݷ ^EM4S՟hR$98'r3//uD>އʓ+wsmYB@1b>ݘ/K}i7YWdԢ6(f?<9+2K1]uۄ)=O]"I=̺y '܀3N `p?éG/Ѭn$/uZk ڷLFc3>){zIB 3x~_@˽Ԋa~\P-np \ı"j&m/շf ڼi6s̷E#0K/gǦd'ؽPV>.X):{6QVYCʲX@}6<:?B_;oMtn*SwWz`*ʙ0딍-.oH)oY#di혿X;P@ N"̆_gEsؔWmWBSc{|mu =SW`Ѿ{xe[j:jwr( kQGi׀y~>ĵNLP@o:1Y:NΎ 7 w6@D!DL!rٙurIgqaG;wSJ䅩z73-#2==5}̜9z0_?~(`}zVht%v\q:Ag5ddjYfP!xtutx+҉h[wTś*S:ˏ ZVaxsw-'I󲁇(D>!ZxM.˽'Oe&?uwG[9(CRV^0IS X 6fxr?~qݣ?%;)LNg:ѧ |=ԆReUJ5+wpa~s?43@MhȻfyVK'6AB(I>5lj#&[h4.4P̥=ALBKC!]ze'JI7zj@uX/ItO`fv͇?DK?usze޷ɡ*^6hxa֒P{&]kٞM\%0TO>=V yX<*}T5˃}//l:ߩ0OnFO$l}MĞ'{{ѫpFSti(ȣuRڊѺUiF|gTO)`D yK3~:k i(?&$WJGС3 {24I}jigZ y*t6p̈́A!,{AX;D^N Zh*kpـޙ.%9~Pʸ>f㐟i҆ \VAT`:g?ǯǤzk-T:5 hB8 FZs1t6I >_WݛG:=2 %]Ue &[[|l<62dy4_qvwέ|.TzI2/-')n ~w/pF(x2+P^Y'#9®5 :eM}NFZnru ceJY2s( g:ÉkMQjG|@pZI=5U;.O7HLOc֍uXmB:wD{e]^.I=M4͂nyKn h,d,e'n"F7Rَsr?Nb>"WsÔ$չy ЌyJ9 팖OO9(f{.#ClH򳝁cRP=z/o{W4L<eӪiEXWgJ &0x}8L_-K<_4VNS<#]凱 ɟ5&"vnzcY ?MfzיvhjwƪD"(Ln ѳ`/%.P~_zFM.UY>y?ug-y;V;-Air:.Aэ%uʢvȪyR1uC{ P&0}lQh_1Jcb_#,UnGܐBp:ElNG=<ռWWeSiPE/We/&I NTV#?X`\&r`~CJo([G?Ày A`sJN:V Zfgٴ?x*B`Fjΐ710 ;GF T)ݸNNъgj!fgA!=f"z;*wjmr+5pDx.Q(=@~L͏dp20ۘ쿎jJk3إQ%UƸ2ýgw)en~ŹL0?%f)vVrS@ h8e%+#\06~ƈ=JΠ _ԂVk;5%fEǯ~<>$5FumUN64@R V=z4N?|_|c3͓OT==ٲ{cX9{?lzma2ї/^)6n)P {F`=ÈܰIinq*(vVcb9EIy iƱAfOWgLZir1AlqLFrH9TAt'#%Fը56q}~EgVbS?P4%0s׭MFc.g؇(2C:]JQ AfAZ]uFji푹 u~ z7 54/ L"Ax~Y bv6t+ldj hWsZ.)UsQG_j\6)ܾ嗵rt2ZUiDN[tΒn.[=DVvtd.ej'](J4ZVPˏ% qͺ8^uE? ZkHymi¼$*OQzڕ*6|,,M<2 Mњ.$Q[e,qDR 5i \ K$:2( G \AĮ7vb H]yv_?j=\̼!lvM ,1fNf,TT uQ*,*utP iM R1P]e +F0_?01-~fy=şpk>ŠY/qVeMg6!٥t8dɰA3IIlP :~fvRa#d5At mLm_A IhϭcN<* S GOPn r>uFFWgFt` DQHk8׮``I*-LR .BGSrSqKRả{P4nY.cD6wkm|Sn[o/eU,x!J. :աg\L/^nfoDԯ#='Ѷns$|cޒȐ:o휮ы77lɼmEGOjS]MVTYSBq_W~9w[}U_j4m5MӿꠢѬWȤNg(&TS"Qw*ɹ&!2Pef8[΅MѧLSNGII+Ԫ[!@zə ] ye$bO 7j@i\2!J\(,7Uȡ b> {q(^EfXF|$r|o2^k81Fj1r˱WDJ\I&3@'HLPê7M4xQ,v, .6q__rJ\>>d) 4qx:5YUEKpJgb(tV<|T^5kad/p4I1!**J%[b[ՑzВW{v zҊʓ,t`K+DoZu?l zO df.#4Ҍ[DS>xQ?^ͧ'4_}9h6G4BP76`̎ zOwOPFf)l|bY~u^TҍdsUrf{9f[9m,KUXi~~tDbN܃b?eS}t5IXY)k 89AK$,}FA *ǒ/7$˘c%K8PK|HHn& y$`ߣph,mKUdlqnMc:bz1'Ȍ!iYwNolۋ.<\M}'0^QlTAm#6<Ɩ:%o_^TmC@Q=Y=|ߓ7t;;\`.1x ڻK/_Oŵd@Eqވba?5~`GNt#؋vyGc82P:Np$SOS{ JRlt:Hg_IUW2dN{Yܴ}TVA0Gz->߅`5C>;Gv&B*@hTgT2}֖U❼ pBVC} mԃ^irR`綠`t as!*?C!oPJCxU;+nnM:Y'{xYG U?ž6O ^s_[Ó6>FŅ*pyq5B[~#Cq1_*M+eX{+ȴ=oGvioқO^Oڏ<HM m`vݣݓ^/ǦYs?Ѽ{mFuZpp`˗Y#~:hȏ5,}g̓/$g`T\EZUssá ?ڣrNH+78uF} >:Xs[lUVJZ(^Ԏ`\wyCMo4ax(jA{xcE몿Ob*~\qȧg/֐n 'Pk?0I':p s-!9i>ґC?y Z:5;F*wѨOe< $ \ ԷXxGs4e;Z).\W SGcjy m 7٠ {&MP=#ãJƮ95z\Gg|k73W725w!|Mc20 -Rv Kʒ\;N\uޥbY)G. 14+B6 CiB \2lO;xE R:@5L2$k@Xt?0~y m4xB}HA,$<ev}L$}lÔ(9S{Tsn!q=Uwo|@18ݺ$mn^kMHV=ʩ6ǞI,{zYG/dQC?Gu?o͟쵁*'q9XÃg\: '{-?*Dm |X%%, o"#Y-oDr2b{-70|kFȭ3~W4QE=Ys'sA 9=z*ڣg`e܇#ӄd|mM~˾o5D'u<ы'/@}mwThE ?1)ڃ9,hQu(4;XicŷMov5 X :d, ɣQ c@WsWυ3'$P)\IF;g@W7$ 36FE:ta.1ĭ1z-5F)qXG|7w̄]fJZ[n9)SRWl$p7oYB'< ; >%p\r섵sP_5Hml^o϶tUlBAGd>Es<ыmB!Dd gn̗p[^M}MO^tn AM0/9CE@*M) 'k}GoO_}4HԓXfvÄđ9ꕕ̇x'~vo/muZZYY%qSnl̆+qJJ 1ֶ6J9í 7']-. I8+)q#1XGیCi@@hx5s23{J x[΢=HqhcEi`HP\˺5aWQ,:0X Լg:S ^зY$[ }B!auOQTh-l-2Քs9MT mH1lQVL<БzDhA@St唁O5 :n^+ӂZTz%KMܢcA2qttd>}܉᝶CD& ft9 S| WDIJ%bNHeթWD#uI溳Q|0 !(^\Zb3).hfts|#qs|6۶7b7uf F~{'ru_ t":sJrKi(}8}AJ9~ckMH( f2El :͘b,Yp.'ԡѱ^GTwbC+XYSgwR)-%Խb| {@n5`+Z3Z_Lvi+ű:q#ҋQ׋ԱVh#DVy޼0d=I|#5Vc%bFDbN-tm t^k>*-]%ts^փdDy.珣+MD.m>(iP@8؜kL2gw 7(ҊgcDU?u4mk&)(Kwo(P-g5?n\m5P{sl?c1Rވv AV&nSKT<#@sZ>;LՍu I$=XgE諾cWH7*#3rHRT@y#E}n$bzEe6I{( 8(' ̫5Dm1#ʴJٿM{D\EHEt';C?kDAoڗYsnhɀ <5E77eڿJxW))/n`SysW>7<${}tVrR2.`KI8TA XVJ)fću=^~ydÄ"TF"׺*vLc+IA:%+ZHA+!!_hӏuELv ݝ jO]0<O vu0KE;D_Q{N*Eod8ߑðNZ]xݾ+D_L?\vf'?8vU9Q/?%g&}hѶ;2yhߣSܣ']leF*w+L^FtX$Nm? Ɨlԅ:UE=YyYmavGRvV5Uτ2)^_*ܱir oC[^bl@]7Z7 Cҹ߾Hp$ °I_pxX%klC6< se&`opkn5rwj]IsVS!wnw?vnL2%̯&mna %SɄX1'9E4Q\ŧ\QpZ/U+Y.$ƙ + XHBbBr*&!>=hGR'O`8ڽ1 ӫ/[u8@0g92a~_ J7P-YګSi]k9O]@=U,5%5"z8 yiWBҬa+2`(`YQ Vʒ*JVj8bE>IOW*Qd.,QBx9+[**4B\q|E{.TONaGFn?<,FdAfu~^~ÿT~ŖaY+ʊl(ž!ӺUG}Ʊ ` E!IQ8LCh ׃Ms,UX$%P rD~ ΄vzhSÆWq^T6ڻہx0ޙõ'^b`gXc^0eh8K:TJMWg}*4 0&{]^_IGAP߆y[2^mE|+Srx,eM0N^* 9/N_\椄bP6hKbe@ Ғj4Z b͢ML"i#7w,}6|*ao 9w = FZcQ}E%"!J%/zaM=9e+sΨuENCp]#\aI̱\'n6r;'h;\(+~5}Ko& -GyϡKۆHZ.>vkS#c);XӸM%xF?cA>N,3JmC lmXmK ›,mo $ͫ7OCo]iX8i?=8|u}{{eaL,Wc ?Eƒ6J_LkĦ|o$%$g@8 >sC& &{r_x 'g4^XhZy*(my NmM:'!ma>;D+~'NR:_;Y^'\%A?:2"ڽaz5Vpm59Akm*=C7Ė=ɯV*~::"ϷX稠%s/.X/[(~ wEڳd:۸}wx@ U g,܇` "sFU-WG'- FW_ ~]w^,f* \(;[Q,^o zkJd{b)P @$ƿF|L ~cyJZ,܅xqQWH=mʘ݈0gK6792/>ǁCj4K›,Qv%*|xu+<߽l[]}\b&7jEg7|GpW5~pSߌ8UgQ2욟 6=?6jYt `ut GY`ke^̆`(F?lA-UoGg rBةMAXvOl7 Aӕ9];|_p,9bsxe} uYU_7<7Utftc }SdiR5S"3;;"k1\f$sG;eo:6.M{;|?b-J~mD7&*=9xl;aȮ-a2_) tgɶ24NfQ%y6 fn%vW=(F}C0 `4 ypשufhHc8j>55 SJ͍rZMA~ º}64.A7&:pu;20vNEڼҝD~1]~hK*Ƞ&3@mcPv 3-fs3nF^;xy复gB"Od--tgt?윎{lG?H CX> Twp13~/ jYh]ڽ nT6N$H?/ "o8:o43 HQ5X~ OT0,k`a^$$2uz{;)[gcM %ι>YfFe]#xlj.2h<`BX5?;:Eq7 |pȳ`}+M9)L&Ki^Y4Bo+%(* _f''2$AH(5-p#~18UVl&'-ĹO WӊW.9㊰ 1 P݆xJ9^&s{HO@#LOלܷ  t,8Lx%pz'^ūO7mtwp[Sx_Kr41ԧ0lg/{ؖA_BtZMxtzRAOb2]&Z `, %`d ]w=aRjbS )Fd,xE;E+W Cz ;Yto.Fx;i_uf6 g4}/18؀zL&uK>g]ūN~H14na&nFc GY59gdi J$C<3t!ɗ.eT\99|xk栃\s0ȕ$xe)U495;vz &V~j4!?&~* f n bE;ruO _Ԭ5~} l1GDŽƲ/da OSp'eBg$g2N|MV IyaƺWqt>VC>pJeEu*u*utK"f \Y2$9VM^_\\kd2̞,%H+WV@=b$-1WL%dXt-u Q@y\!NƜbpfR:ɩ04ʪjݤ,4_~wd1QF4B/%iIҔu4y6ުvlT{Ŋ ;A0FH#NgR˒3McBIqoJQRv\XKJ5Br$U\/R\m9{I`0}ڡ̅P:Sk@jWHL >8M[8/r#Z5/J'‡ M-hRŮh|2]8wqh\]~o}brWBy a 玠o*C셓i/tד<ǛiEi9~A9s9|(iYֹrG{: =+s t5/&#HȻͱL>|4lͺA'mj&^ bLt;HuSR$;63Z8m9ízmTacLu+ 3z&uӣ̜=rWg(MnP6[&7"&ese6fmÑ^tMݦ $mi k\wQ\ݱߋhi)+>o6jH,qLr6۱ۅ %XHQ_UIczjnTǨÐErVk-<66?GY~fGCIzJ/Ҡt))ф#I,$=Ol|`Hla^VXLDcc#hazԐTg.ۜzSBWm>JW T=~qH=|=@:◍_VJ(!\>~TծjS)Bt5jǪyjI"qi?` νt.4ݣGok3b(N@1B<*+Emr\avtIMQ\ (=Stm YCC6) ƲYNtD)) N.*ʮ)#TIC`5xȬ?Cpz8K^0);wU_ AxomaeE/G)wx.5T>(B5k Cp+JC]?8:x-ԕ<&$Teq|88>^'붙lD舕 -r>hۘՁ8a"Y^htj_tNM:m?@}Y T]k9g,C|)+ϦdZfj5+^@R X!6%9;`d2@T KOt9vLV-YKLkѰ6,;⡬+6upN= T= Њgb{S;'\^-~ X! 2 WsY oޝx)ytW~zԍfT_uE917gM(^;BvQ 6zT~-aEowÚ WnپI: %7Ƞ铂<cQ[yggFxfQpp3Z'3٬{Yccv7xؗh9]?&|pxDRz5Yʈ?TaS<\v٨wcB潭[I)$S=fOe-2roe͇|'7_{fr|857|8 5 ]%~'PONTw4Ar~p2 "fnk>@X&-ܣ\c2Ec"7is80)$+;}YJ\CZK}L.t+x﫯l"Od!`x1P~AD&y?3B\fq$)! unj6'*xbT H w^Mwb`SBodt(ffAyj6dkr/,sw$,sp\a?xr 4SۨA辇ll/#x8|XB|b;[ӂ5fC2&٠47c&gP2fqs-{Y_b_[c(ej.WEPsZv6+2Y%;4go4IogR9~z`>jϺExż>ƍfg}^/f|_=ޅ_=~1PؽND65XosuSiJmm~Hj4oJޥ¢uLc?oU{"~s sů^=C9sN֛^G/ۿG|_賯:.9'Uv}CA[w !/-v^c{Nr][?eV]SkJӽ<%=xC ?gt"azpbXhQC K"B{ӓb;|X\pZTS; "+%I)훪Th9|O#SQD>7m~CA05#v~uŲiL~A''$ _j8S5w)t.O7XVAW-Wxf+.7B'ax߲|NGR@}4<~u"B+ +sj~v7$d`F W9 (R8W9wh6LfC+!uov92vڔ7Y}!^F"jQ{O E039 Ϸj#GI2FHv|QDCD\L!wr\J0CoMgցuP.S] 0o**3Lj.EdUܦ߱ RN'<,w8>ZhWJO1>vϪ61"<\RS Vp鉄%R?) Xm1oV{jD8R~΂ޘQskXjc_^9\V^q P'!XEª9 (3y̿<ÜvL?)oٰ,7CTe6J3Q90zf'9p-s~Cxf}d\&Z&/LA*>?-AH[uTYrkw>J߾J_8W)5`y) ꦸ#IEQ{~OpTP7-ZkEg_*![=n_YWDh%4]С|glOٍ@[ 9m#X)*[+`F,h@/,I׶96)(9湽DL׆8a &/θs763&Yp̦: 1夌DG Ϝȉ.Gt'`'GiR @ܙ Iˉ{ SS<=@RZl83JY}"3W7觹Fi? >LPy+σv֋sDY4 J=D q1/N}u2TtFxˌse:8F.Q4qb*uG,.Yj̖v3-tmeõQc3}nHiךy~ZQIQɈΑ@YAߧrQ\iWͶɞQ^t5\ِDHM*$tlVڷi&i*МM6yxQ"ޕX* 5(\(IpÍt0,[&bGBﵵK( GS禢yH˘V{,$WkHC8OAyuVB|M{7[C7[[~6/^MZ9K~1_$гP;b*;ypڿ&۟/[_G,%e?>ˌd_vݴL#( @G~ * qń.b,$,ڴ 9 HB %E\C~ah&J\->w8D5H tzKQiZfrGWݚ$x׍33ϳҺڪt9?i{ŗt`-Rr:% wPM"J*JncomfuU0k:,kNcpֆ at\{-aۖ>\봘lY^RXAlwu^#\K2ϵq مQͿ ٫<&}:7^ mdi4=#Y{y:`fdE b>~%M$d0PŽ@Rõ/ "PؐX<.ùg)#P91A>q!+/-mδT+&TTPPTrLuġW@rxicOZyS\r/Y)Wui@ ˁim[U*E%@m*"-1vCů(/^\$%p䓄b@w 91=gjb[&GȱMSv ]Bh]dZ߱{o2tgJߓ8OS콹X#p0nzO<.Q(ggp-\Gˡ>( oKB1mFBn] U3Oj NؽNTu;k9n;gp5v|~Ӊlv,8 Gq.M:J A*g\ybbYh%X2kz'Ǿw-vVT}Q=FĬYVxHMАj;vBiyBbLW#wqBj;F}qRQ<̬/˘ZAa,{a|8Ճ}f,){vLv;{{NEyQn9S4I@4d$Z o&4כ<Ԋg̭mJAp?/<\@?a1O#K^{*%csSQ8U(Y-Ks4hJ9JR/Tݫ^H2?e#-5zWn]~㩸[?" A!#J 9m9zwO'RdD$i=B^lLVj~HmuZ.@~|h$cD'?}:~v}qv*ĭ:![8;N<_C^P|+QhT\ IrKיtO3>O lVŶIw;lcT'9,ndQqYS8LD>, *o.SA_!zw"gk(+sK=Ssf#:+kFhذ{2ҿ&7W&Z!Yr͠C!9tCQOcİ%n{-cc5&@#U7Y(P>r$(Lye}vT^ܛ4h`;;5Gâ6o3sLbr{{x\ ~kxwlɝ,12(!{ ɜ覂}?+pϳ7GIpzRRE/rj~BADNͮ$%ѹ%ԎZ55Vr9BNb*"{bvdu},;XŇ(~1ŶJ%\mi)7ICt2-Xɮ؊o(fI)/S76H0'y^%c4+X˻O4eҒ\./ZNDFAEJ]#.,AP;O[p۟Jkz moY:[$BiE_5gTpC /~+\S..2J0HДT 1[ӭ;:ozȖjeK+(VDB|4C@/(s >G˦R3' Q-\PXbh tr4Z6P9l)mYPdDŨ׀sv YAVuؗJ—|s g JbQl2s |YUdBV_$jRB/ƹB/Ѿ սF*::p> Ctb51*| Ҥ'2MB7SiIh(#p\s&%ni3h~'`\Ǐ ޡx(?}Ͽf_B!Uөaȼ# F!2v*P"ڴjRtBlRس6o$}VlCFkV`0l륪0m ^v6@b-ˍ7c͸ˆ[fnG-9M_ OR! `\ _/x``'izl.[HAJwl:>gu0mcaw"*+p<lC(m`SK@p QǔGuI;E!c ]A77[|y~,^{J+ҽWGGOpzV@,p^L6Vr9<Ƃ+'0%Is3~С>Q{0T/Rf|q^.(Na&b.O; m1Ptg#`N4_?OQرB|ѥE T]EcˏЁk,\ƾUXƞ$UK<1N"՚&\5ipd j(fڵ8t+E=~0Oد,:b)~hm^h+(y6񹉺[iOT,G_kGx[Og(jtPr?Ӻ4o.wia]ێͷ~E?6-Vi]̚|;"n:wFmLb:?T// 5ޔT[fb[~^Y$Xۆ|ۉECsڬ5}FKYQGMz8e5ȿ_bgWJ!Q3v# {dpޅ*l_RZ?|*qv2;yaV̀Hv\YY0YUSj~Cv@/4fe r7 CB{p h}PPopZAYtbAIڛQ)+ݵ\B}I]==/9y{N`PfoFA9̱X]`SK£y %%#C ';[a~%FdE&IA'#{!vλ ͎'S: t&l?Ym%9)ť5عN;4·uX6ٜa~Ȅ0?p6)1shjFXKf|q3D_2Z0lWr%v FM8OIU֊3F)wYj{#\{^tn w(p5hd@dYGorCb䠒2LoFF狮ϴؒܚ23zoǻG!wMu0cb4(Щz2cاFʹ ZU~-1j.ˊofͺ2!fw=ڑuݫML/;F2 $ES P3gOf!T'0#w`$htS>7aϝƋqvˇVF;uWnjA#ӂyCTV{"69.R=NtJK!oƐd_J֗ˋԶQ0o6-K|kȇ.og7&`/6.o\JN\| oU>@='6-qH׏9C-|/&8Ɠ6Rw7~Gx2X&bӝȀRYɺg/\v٨w/dqn 1o;Rf6w3Ar~efNϲ%3rg]41̠aq^3n 6*s)f),!|'7j  0n,\.Jչ0@!&mf2Bt2x.:&L *z&~vAT!Z*GIKQo2ƯD0KNhA1 GqfM/5zПN>9eu(r)) K$8~Ī~Nk )tAŷ _Q?Qf+<14sH|u$:#&4ޤU[.phEYA-{Cǹ^vSqB~o`n`Sm8`F_Ci__;qb76I<onPW8z|10^= =Cf{joWG`kug!Z*EU=<ۣ>=6-ῲj=`uIb9϶Z{6hi}Ii@Q<JÈ+R]>9ה='ʦ|PO,hPSvOM#ePd]aC Б4Ky2f^"*HR8ʪ'o*)È2`)&j`ڬ3x/<Lp#lԡ#$}MS_ +?>M+8B?EǗyZ 5蹥薦[9tޔ%{1}<~|)g?jG@F &>4a =T/x-AO)-H$yd$nSf&S(I\s'Bᐒsb&{:3+Wq`1+NI37-LE\E\p%\v5ٌ8(Wu0 g`0v[HL3\Z Gq.)yA~)]Jo [ B&Lֶx){݈ie!f"4~i6ޮ*b[JNҺj?#3I@}jx׵/ Sw7,e<":RKr(zCLuzL~Yëb,cP3!Ƃ{uki8Mp^sBס^ezhAQFx\^f^AҌ*D ;<єxsk1aye)RRRN9PCۮb)nm4flR\.-|eiۨ\J-^K4n+dlbu`&+m+IQ>< 4Yg)6儀[h*OGr!^p X L)ҳ7浠-w#Q7ob'$Kx˞\ NN2\]hZh_0*UbڹF?6zBx1ʼnoW=`GE-kst S~}sx/&a04A>*q@ߩ"EZՠffm-quQol$E:. ?7+-Ζфg,7 (37G#Ӥ4ɢذPЪUG  7_s?%Osewiwm6s܌WUGsPY{kq){P-+j^ $e:{X&=&(ٵxfks_M Y'yao4|` :'aqP@|q-tQ8|-w?Of\%hߝc4H4Tk$:+h/O9qbRojN4NfwvB8mد yt3ܸzOm"92SOKLr bJU*;+mYrP ΍mLKڷ>z:Hhc@M SsPnJ9hQvizN$Y͓UhF\[WWX!Rb#2 ^pƮ&Q ̅9p@t%I%0.o{;ũv 7<S-R^oG)y K7Զb3W?PחXb.CycYD5TY)LW\&Ilx>I, I8DxΆX D!)L=N'WQ U G1E{xvMәn8Kw8aŤϕl8Njͯ*PݣV3QB(5L?<Mˁgt V6Lv?eAwwj\UNc.9Oٞp% 5`&~v"e`U6q?5?d0!oޔ`S]G4yz9C6y0~vb'>?(`Oc04?gidM vHe"zhҿhG/Ru 1CXn~(FJsY ,3Ti>K֯8O}qxq>>/=qb};K4(o"ƖגBc.(@h{i=@L΅52d=,s4!}jz@_1^ tYbHJ}L1 AYN`n NL^h:E|U ~tAQVg Ɨ<#dԱb(-N^R&He0 wlԞAGs,K DG0f;Ղ?cu%Iq.xVx H%Ԗ} >U4t>ȁ4:Zd0ؕ]v;>;t%L ضE %)TbӦ &:=#y@~0f{mSg~$_%iI;L{L!}8<24<%'Zp5BŢc ΝU{;mGJt5s ; ޝ$Wtܝ xdW,~]B([_L3)` (N-)Ʉ34V8=*`JL \fwmXdA@ FjTY%-^ ߣpmnJfO6b0tx-9 M`(GbŨm1oq7=6ƴ6ǍVzxQ3Ɨ +$o"0c>_q`g-b9`=cmor`J.‚|VMbu"חAV\lz7aloa|;$,9l܄z,T  j*(h~&Q.ϑRKkj-c!/E5CQ3~MQHP1ˤEzي$HIDpYwÝ&63 ;&f-IU/!->Ru8hM}AAlĿP+'b w#w/{Y6Jt.2<+k9NTWN8q %рt͹;/}38>aL^4;Fb ВSy{9@gHm@):_axY+New-y y{_ggDQB`4UfAhxߡqI'%'٪H?!:<{ Fw Yܒw/ټk4$ v*ǜKZA5'<Ĭ՜jv(DuA#N$o;WH$z|~\dO.T?DRH |c<|CTۨbm+}<&Riol5;^nC$Z)JBN|3hSX*֏EI` 4ir@0(?)c,<R 9qXL:92zo1v\ vk^Fi6+_>%twk?i>9qcN-y|M57z+ A`U݇nfW}}L5[!\kU?e+:8\N y˩CeGlvO2ֶ{9ܕmlg@:1QӸSBv#X$-UDG X3^CYXnmxϓW_;hbW@L񕷬1ؿ OPe~Wn(K] ne?=@M|U p+ [xY>hVewRfkw ]QZg V m??~Hh^mL_Kk@ԫ7c$?󉿃n:f oZ Vl偹1FS^R4c"iJB6~|!yQ3V}$ٸM:NttfGq;_ls s*yA=uqkn XJ%r1?9jZ(M]=gwȦ7rVycLLd4{m^Mó|ż';1Yn21+u*C(f>a*i'k=nWFl3s)l wY-SㅞE!Z@ZC(#CĔB1].tHiCrUJLIhU~[-*Kn^mYgwH"V{L|3|`܅NXi;h,!qk3:.w+Q q5M]$]V녬gdFmǍ7TE_rWZ. (mŒ,#gq7Ó󃓵7\-ȣ4aA *=mFnY^`k+P|e=a-QAolY/=?JH5ao0Ay'1h>3{_}1:pfhauLxᠳ Hkl=h~_|Qk,vמދ/_B7\$JBs=T(,zp8{δMB~ (#~.0OߕW[^1\sOr-k|pl#>d{)C HzZƔjd~@z$ԒtH^V~juRK܄#Rp+Cl`}sȲsخ1v7dtY1ʗ<ެб&"ҽqb#gGK炌UTߍ%\吩QDR9M,#--;dK:7__?ϣL>zz8 >"pgvtw=XDU&_gw6{vζvw$j[{.9+ܲq5eOr5{d/;皹'2X-?I݅uv[*Q0%]]Ӝ< 3J A8`2EM'*/KcdӲm\7Զ NUխiˣOv#/U q[8(ԩsH`cGsTb$OEE,k]q |#ݮ^"ub[W~FNGa QderejxsݹiV+طy9g{΃Z SQ'ĕ-6z^:y/kPc=o2A(_oςsZ'c>fBiP&ۭH?vT~hqz~΂8tnz7*VyvDr[[|*_D@Wڋ<-N~|GR(w!Ӥl|z;!Y/JqL bQr+nJ[d żgQAK-X{{`8j 9ʱ>?4w[ج2]XDAȃ>*>[]ܛ]mtj)@XHtbSS (i0mx2T*0x-05hk )S5u]c 0,B-^bndDF9  ` ͠]Ѷ"e{QhZaQPG*X+8.wdyN>֢L۳!:£ӎ8;x&, )st}-zs;a漧ZPW r]RVs:*W;;B5 |"49-9y[ޅe8z!BMZ1 A΍N!-J7[gmWm~R7bU慍=,Ns9+B[ԓ%pI3rj&>cTdLe g$?!ss}Lq ̓_Frt5oLyK"@SWq룳+5+Headzsĝ<c:Y]S[ж9.yNV>R{,P>uH-c")5 3WV [w&E>;=MomvrWFOG |Q+}{ph?1[cJ՟tMwkCzO<$q* xxQtg_1B )KZ*Mq |մ3Xן s涳eG 4y8΍1=Sˡĕ'!f*kh11O'OMc&.7ZewvI()s0m.d.7mmEnӸT=.'QlK%ٻ(lSh f$ "HFJw%ijCT\Gs\peAggЧfܐ9stsZ: <|}[w{`l$njøGلH7#k5D}s#\=PM?b8Z/w {l 8G(>u1 $œ!7*'3I44t"5䞌W8@.tKxtR=9=Ҹ %dN09)=+U^#fmcB)7sZc25]kXcjU8  \Xz4m]@&s}Y>ՁOuN&,r2dCoƘiVNtؚ(zLʲ'z"k-UE6EgF=+ʀQ%p]VPDEvr,sqEAd gSDVjl ^Yw A`A%l|2VpN?D3Y@5= t2Im'-(vX6f;PQӉXD)?B&)C0}f,7j=,Z=%ۑU-X4=yPSyܚEH@Ye/(yf^sA^4ע* #[Y,vX"vxb2TyT h -jIp)v{pK<3vk1Uw 7G9G0cc*W:PX6TGIE+`p}o5DPqLiQ"yB w%O yp3c:@%)@ 0q3.<sB4a^KTNdb\j'UI1ߺ|K$:#DǓ [ ),&9!t%0u0A gҟ&f\S{Y^S2fo1onX y,to?H5$!?NEOqL![W}HU x[Blɬ/2">GΜUXUW+e&ɵuM@BnC]g8IQEՀjQ-L[\>,R,z"+IdŋD"ű_Ї YlgپƐW8~ɪfeZՔbVס]K!Sw eeY}H@.W`0DOSD B gB"w[c"ut2X{Tt-^i0`"Xi kLteG'Qs]YQđ7QJ5  icJ[(n(c ptq%j#c I8] <trIt?ݒ$ x=cZ8]}ZհY}*ӇZFt7PVbKfScw*[ye%R1uPR^*=7 e sZ˚q{*yTJ'%ڶl7)tH-vN`X.O< `X\0ғ9C$3|)aҹp!ΑK@mKt 5pyJL@G}@-m3h%HH{ks3y UhG#JܪNfC,AQ Ikz@Rޓ̢fqEVHAxƉGVr2X`c{/&ɒMTTQӣ' % R!Ep P=x`V NLzq}ʳKTH;&POR*ݢaM9`_ͦW$mQ)E~DJ'PK%Wf H @Bӆp>Ydž -vw)PGjl&9YCX́fpx5}ؤH.Rvtch9" AgVTZ<%ԤM[ fg"ej@c0Bm3&,>tI !⨭%zոIJZT#Q"Y;IU)seA tH8\i4!wQB礐 VlE9.\xHA 2J0(3pbrjEIݿr@[q^c.v)hYk (W)%L&2zEl6 R, taV%|?n6{ɒ Ai^ Af9 }tdPqb7*EצBXœ8ʊC5d-7S)${"b1sYl>@XthIB`Vk+/Q"Uxq'(m ) =i4HDG#G1TL*F H*NT7 A ]hŘ跥Rqj[r)f#q&etp6=T.;A8&zO-~ 5*-` Q!$LcfEPʗk.cIirmYMdfY:8%2yc`Ǖ64Y[%9uU_#,ga- x(9A "\oe#P*z/&$5#& ,4f:P ic/~Dj QXO$jhXyE㔆&pi2V}ASh0=ޅ2B?#ik: ԁ0]hn˵3}q|&MA'3FdHP>b ɜR"l4xUf~e AuXǨ!"J@A^ ( [c@kIl [G]98M6y0 0JZVK_' UIsCXP24][tP`=P+½2K6%V h>z:h!%ӰhNɈ#M'MOJzH},+S@2@SmGR*$e>0dF^e>nIt%p:W s¢^F!GEUp ifC5篘\Vdmq Á8ܠ7 +!Vѥ sƏ==UZCPM *LnAm]METF jw')9B.ne}NN0]|cVxf6Mk*cj<-4j"rFFI[ɦ`-8DE A~"")htQſSI#q4Z4~|^5]IC@a٪co^⡽] ߾gOz9k~ݺnQ_._VO=T?_}{ucOn4eߤ k|s'z?M$1O.[ 377:|?x(o_ſx[ŋ?9M,;] ν/ZPZْ{_g_y 3VqR#:m볃ݹv/lYڛl=/+XzoY5gR[pǚ}~U(wػwٶnrيvEÛ/m9W|;\u?9?kϬz3M gݸ 3(N,| OQו,!:wH이컿x8px⬏vUt-]ㆮYӸᩦhjH/Vx/+>]{غ٘E4~߽>eP׉pO_sxKz~am{6 zϳׯiŕs7nj-Z|ZޥH=Wj<3;ܝC t[w۵t7W^[&}㞵ɳ_iN\7֝tXс o9w #kS(y=7~?g/˼?Swn?}Co4銗+9r7޺w4dЕ/W<֤%OTgd~mc<{MǯGS/==mu -05HI~Ȼv%5ȳhwX}yfm)I{BO@0v4MLp(nB(7Ik/ƥsyݹ{{*{lձr_ȝ=ooz3w|GW3gn_{'{} Oߛcؿ}9ߙ?ԍg7LWG/8rHzC~=O=];>uٳ{Ź;k9p[_K=s]?zF]~1O{CGnUnm T[n`ߦ3^zuSt?˯}=}۟YX _p)]מ}c]W\}]7:o[| ?*;Hx?M'9=uCCv=2|;v퍾WwOW?i` P˅wFZS0KGYWZ&|CLt<[RxAhCe 2ȈZ$e}BE+)yQzE}SFq/YE0u FǯO P- i'cJiMҌc?(RrژGoђDKXXr>G6F:55|}&#D^ b2kJWat- Ϯ@0)Z!p&?t9ZzbTI%&`9M,(Ŋ6ePˮFUba:4'3'U<"(ٞA`_U6P䎡Tf\+۲jIаYʞ4'66aBh|Q^i*b@E#&?Bf{7 l;ÇY]/w‚w[RgDMuxG[I㢧5zVZQNL *[E' Ee6|̞l0'|)A;L3 v--ښ h HO l69CoOB@A7_ei> #73>_?t/ĚOa&VRRRzgTnxoKksI%MByuG} 5]MAZ>-%eYW%E6UۮoAĵ<gb<*L]t6v|@0 H=~)HN;]/ERfG3ۀp8w|-X;]C@Qy! ӟikbr*zjK=aKP5Oܟ*w+?7;ag{PsEBl8LJ"L&Mq(2g՚16u ]|$N\IHg%Gf!Pý@ӳJ ,IKS>r;q`oCTC8 EFEH6x$–z\ߞRyJbWN=θ՘xY0OS1_:`; 퇗ͅAހ:NR@AptQצcz]%t:׊n`k'<0z aKC0(?A"6%> E[MR*Zcca{eh*Ve%eY\#IC|4W)08G>~+vX³l(Y[ݚR1\x !p~;+i ev RA%}h쬠*NXm& ;j'-˂KijS,ǓaɍIߕq~Rփ42]yosa-0SPrVY"S- 8вFq_1S+0`DdNdž(#hcֶS[H6$CZ%u@E|'[aѫr|p| /"8Yc$8Kz{aD 0c'FFL͓dUЯ14²hLL>j͔_ B#C ec TS'if Jd^aWrzfJ $&bע\ k 8o"E!>B.w%f`xt$ɖVb愞ʰ<.K #-o_& #P5yD%ɴO.:o(Pt5 7*rn%ު %ǕurXn" -'AWd(rUo=l1!#0l( 3V[D3 ZR̖tE%(VƞZݴ8_3Y]`~Zwn ʭZdrFzjJcPtw]["C2fa#`1.ub v Y6q{ApȜ 5- G",!-rL MgapcaN3u4EyAP  1&SقmQ .(X/ X ah 4S-%DmFx&pNgnFtҬ jC7T)p 'Mvi#\0mڃU]H`V7S cr 0,Po4z{fdvBڥKh1j{ÉaU2**Y*Z߻,M"}t2٘;oN4:u yّqz&A/\m; )bs tfZU<[r +v-!qs6C-N^: 0pI8TierO>՝5ӨVnS[Zp E{hlz/V_NQ1r)Q++i 1FD\N#_J}_ nged.+,(1@+۱I0l;hH%6*1&-NdO<iTf*c,>*;aRcC:nf#`VJ 1R+|O;v{+ XyL bx!0A'[[@ -ie_|GDF45Z@@+Y[BrJbڗ*kY%] @hvȒ㑑t婘SrO(Fוܷ#:%L)Mi|31FrK:Bo@_3 ⓅGd™qyzmzuNФ(݂Ss*r-g;}.ӡ41nrʂhK < yfAY";K9zv3R@H*Z|Bg*j1`8b(i`{h21e\ G^f¢FK{f)ch.@y6.Ż1ILn ;D˭$PcѾfyִWiSآ(D,%B/҂5Y.·)#@T9/j`1fT<֕l+~0;Blk*IGnDͭu"#M-ٖ>B!.6D$jQG*DjenB"YtI9,W@1ؚA6bE˸ &SY &6W x#E&6.-7B<PCj p,H bD@T7/EfOt|1>Dώ`gi'/ob7/yo5[ι?u){q_8%a6JiزL%Db Ø0ٷE(셨iqCHZURHu7w{?yy9ss<|>V+TiU6܏Ukx[ :MW}ړKkەpVMB# 2n_i>)лD5dvBא.JLf3;*M.ʷyP:,XfE-+k |d&X7۩JΝK8+%oiW} z,;[vRR=N{fsɞ>eqۜ`>}Ir=I8H5F'u|k[lww="+n|~yDƵzenx5 ccGj.thD{Te:uh~-h]G:U v]ݶ"&хsk.h vxRA=C}*6ƭ~+ qiZ$$_phF@ %^GX`^h}u ڙ]jMMM={j{.w핾ߛM}r|+}'X]K\vqܽ|Gr3nX[7It.Xz 1\9/>u2xS+uBk-c_]_{}Z 4͙k]}f\!\%&Vfl"{j '? wƋip/)11umaέ9)4.>eG3v8laveB .N_\8LN뭒88/7eb|n^ay(p]h o]K.}L,l7sVE;>Sw< {0AKxj2 PW\RMwoB=%&-*RJ`%{L$~up}nTd,4`k8=y|jI EW =UnzѺeOXnye˶R%H/*WUG9Y! ŎcHȜhiUQsgV=bA))NaLaLaLa|%CA M%0N/˟bAu,Ob3X,8SX,r,wX ,58O#/7᚜bSx$}(M{QT"8jkIc&EYQŢQPRhk58MJY^G~f=5a0'`3_4o"_h_0r)߉1%hCRM,4MGL^: 22qa~^4O$M2wIjr~䕦55ogZ3X0HR_̭P~2OW#|N=Yp@&+   u99@`䂂L] |]z#\]XLbm:1Y9Sl$\ֱƧ+9>7Ӆ`ڮb>hgׁrd36(X?O$T1u@($[laa#Π<&9S6=o  BUG09&4B5XV:&H)LY5 nEd{Bceh8G( TOo3Bns*OR݁B$X!sR5ϊld bfWpщƠN֎# R Xŋl{]h8A ]n4j#O"dSr0>s.fh'&x=ٕ6v+ mWWZK@m%& $g%  sA^ d%Gbi'Hi9#b]ERY&i13m@Eh NN5kςV}V"SHGncɂBcɂB ze)3DfʒyZ3&y9N7T@|Iy,'I(;Dh^݅|4'IxJ! +5,%%ۥ  QT7*"%ӓ?yy"ˡz 7e^L`f`7@y&Q㝁=ăKLˎu@;p\!%.Bu7Dߎ 6*=)K>Tp G|P% aachA"2{@l'x|螝 ΅=S̟ V#Dt81dlo3AH+0sx"EOȈ @@q@F7F]s<V==LIS!͞|9$yL=oI'-&/ؠ~ m[$Xjtviȶb;<̨tF/p>1q"/&?~Dʰ1?=G{@|>ˆ21BBdx͚Z,fYJd@Fbdem}iXw;o-n@tc#4?RCXoOI.L94eL%ô_bG 100LяGcyũ)w^dX8|Xc6L(EF@F朗 (8 cy@*p$뒔$!IIt'TG2i" =Y*U[ORH'3e#Ѧ!!-С>G|~ngG4X,`Bκ<l옥ߙ4p~/'b+_~}jނ{B(~YQ]v9芙C7fV v~iHEU.\@-xpnškV=K/P:/ZJ(\:u:u? \Ԭrq1x%qb^;sE=|V^p{͂O٪EZEH}7%# KӥOVkNì#W]i!]nWNT[lS}j">;۳vwQDC#Թ BvЫZEF0;uvU(ZP36nkB^{5BCoy-MՌeV;ѳT؆ !꣌]]\uD++,ﺅ~%_.lQQ|%]EK ˸>iXPA5-=fWTM*g_wJkם~!b{z|JF4g+;ۺKNGEDH^u((~Nׇ,B4 f3^/k-jWUzJVf uW >".~h:Ya,K^Sp]L9u@M{W|ĆʥW2!Bj`X/ږ{&|Py!I eZw,x鰃.wmCV$5;]e<#%38[[fҷLq3MѺ~9p 󋑷=VbJq)#l)ӇjϷ5M 򋠽]-G{\ :꺾f4?`ZftC"Eғ9wu8ߪಟIE#C {J

ɆԢyʼn#!_23^>!]XBwtOg3I6YWdȈ Y7JW#D@Sa2;L,_jd_1QU,eY]/^=6InKs&F@>oym۽݄2H)+FF 2ȝYVkc;lNgrRK1m]b} ggϝ_?*[䯉ZېK8W}QŝUnel7:Vnfd7˃OL1*W'E%) A%%ɫ y%yi G"ÏXc};u/RJowcooIwjPX򥎣eq;qR׊rpPI_zLgb,+W a_w ɬͮU X,>][Ӧca6)=*ywudZGRˣ#/͓j)[WEUYaF̼}y=<˟]1Z%s/95ݨs*4vgX/ɸ(b~j]i_ȉ p=w<7?9Oߤvl[`tl 66bd"2 3CQ Nz,8bChM0 Eo_ЛN'P::."">A)89&n 3Dhmol1C4""П+]K::r?9"8fz4Jw[ߟv=9k#xR V*)"}Gxq> pF)A#; 7I 9L@n ݛrC9!KIK?܏:իadaIdmjv; #b0*"   #r"Нx'AP9ęiyt!ɴ*:(8cCz- ud @yBD eh`D7"!z_WO86OȖkA<\%]g| j R;GD$Qp H3H$*'_9maXѠ6e<vAV\6& V`D@j9;n(fp]@_KvsdOL948< ^7Y!zCvy&{{@{?d}%EB?a0W7G n/ ݂x,VWp08,@\Q6/73|(`|u/w x>(?Z\/$9#s^ aD*) * ^7 #sř1"l,w)#dG~"I1t{00?^rJs0ܼy[ե\;b@~G?B)cC ,(eHc=oT^qJ˗(S}4:qʒsSc'QSMd>)Z$fvN>4:uNj )ɩhݟ3sRͬ)QoxPf=iK^M0[55ď/j-\#)0rP8eQerwCoZvoc8lס2Dk;dWXNjxϥe-oMI‡K,f{s'Y"RgASΎa. b9v& l|wOm`~Di]vÖʞ}13(mBd?m )"vɪ:p`>P[ݵfYw?kL@=/bJONTX$Ѭx4*~ikˉ~"1 4-)'{;ʖ%ʯkEUpgIK+NSWǐ^:eš:_95i9'VVˈ`ĩ~lT_<}ŭ;fzzMk(ōxQ3-褄^K j2ئRKxG]ʸ}@gL;7fW8%8vSϾȟ G^`i}tZk>x7']#:.:sŴF'(Y,$뜁j%3/[qJҗ0g3.M7=/Ak7%{h?.K|ϣ-`#UϵIwTQ7o6yz5E(\U9oX#+R]sI }Z f]Dvh|t'yVnEѝRO%kkO4u|ߊ_JkK.lx?#>d%eNv{[KJm]G!wFV:s߻uM-S:rWT ?[&m,eR~ʙ)um֘nz9foF۾adHNW-E ӃNHP @_>T(rH+qRBo$.(|8<E@x,uX~H*??2^Q@./ K;D,bdlbd304pI42G32 ?_("oD'gpD8/O$uD/|#XQ 7h:3vgC/휭]-,,-=/8 aJ}eT"CqN3 ?)NaK6.FƂٲ ? p-uY<04@~xl'(%< %Sr QaR-m-(Q3 #6 n?0%Ǜ~8s)=!'9ЊI98&fc`/4PBTh:?†P/88Z!!(%B6g!^WNGT@2Da!3/d<cGz8,x+;bHu LaS.c "*%@ P9Y\0$2?$K76l)ߢ@w-_0vP vI<2^;! x j'gqXfgN~ /X Wwv0o://&B k+g;|Qi KG 7_'z/@``F۪ @@M6Sj1ozh_u ]^l<w+4tF4}xD0Yr8 G]yHL<PZ!Q-sE,JH2؃m'rhsWoU ͖BCdD& ơܬh: )y?4ea>%ulIh!DC^_ wm4wP[:ɛcD' Bw5U@)FC-M<(8y:\uƄYn.70\9gknKEoSK.&NOHLj,oR/&1;n,{<] į\HZe3g|}e]/W0gdpLu) I0UC%SEteu,tOE['WVirbljR_׎71e fLQ^a_~_^kW rtr^ }6mW IeOor]U*qQt1eqFqGߝ]''8jI\Ĉ\ ļ?iQ%cfHj5ֹ(zKʆVG[-j_=<,_噠]iyfk1[3jl<휵$i y"Yf8QQRSzVa)+Ԝc޳W|~ 'Jןy+OhBGl>oMC,gJwlNo:%~iΫ&lgev@=Q)v*Dk8VSkxsCj-6+ᢈ̮UtjWz衆"O/?8PE_hl؝G^|ux_qqbbO*,[1ybܾgAg{.l5D~V2 HQKXўQސ9.$)~~der~eI: z槟,0Fy㇚q{]_P\=Gnnٖ9AԅM8 ]}ד_FR$Jc*N0;љd2)?튧ɆuI'?^M7zن{穇TN25Z,m|9{}]DJh5b$1bsfN_8+eaWu^їyE{$ HT,iyQ$HR,waWh $(%) &Jow xιS%Ut==_t_D u-gߔzyUNCQjh6`*al<׳^|ũNxZSk& TLQ D;BFž[yR}b$/ yv:ݚ}B?ry(|_n{-7Ǜj:-a4n.Rr9 hfT#Bٸ?VHk+~k+~k+~k+0 JHJ[IC$ڊBk+ ڊU_9بpm/m & 7_hBZz-;qn@Jr_h0 !{]u/0Ii)쟴O8P& $$$ e`[_@IRߊߊro86no+Y2VVRO{t&te(5xVu2qpu 5b㢯ہ֍(qnlrí%n5\~4+߼cH8?a(אM-}Um !PՙPn?q7") P%$HFMƜD2miT—ICC_1ϖ!)_M~ +9FEşКp I  Z]&~iU8/7{?~@qn%ub05=3 ksiyIӾ Gʙ"o␆t7Ca#zӤ0yƯos{z>M|v ;<(HDS(W+\m]D 7DahX@HE$zfGi#wLwȫ8Q0\޵ YpF٠k Gz> 00Gd+7RF8EB|r'nZԻeB)Ht|tiQS)(V=( Ֆ-=L M ɡcoՕFyjPjtщس.r,E9JBUft`6VLvUHCA]!8/ؙ:<#|ڜq슒BkS2Grf:?2\|\[Zlo ;,C20J|HPײ9^22?'nY9yGM}ymLw0s&8vԪOI9*Pاb &UM`HZHPyN]v%'dnt=v/F @k ӵ&=G1-bɲ Ň!+Gf@P4%[ d- ˀ4=Zr$ n8WCđH[kqW $nC$$+aΖF_y5aNy?y|gReI*O7=hH:C^oQ*% #* 4dvkd8mZ0jY $pFC8K'бϟ}k ;WeG <Z&t㍰_l8os&FSa2͙J:\M}xnD̘H!{Z.f\j,ʥ3Tȹi-oc*K l3(:: zՙoZ :/U {DA:Q}(mѦ6I?i ɴ ͏IRDc~-iR@EOvQDOAчEԪ|\Aޙiig >ߢv2sι{9sn߭bDrgk&l{=^≠/HkTeڛg;hhQ{/g5PcQgWֹ:d'N=`=ԼG?{wF5I=ukg5s֞81i_1Lצ6oR}^ ӷl,V.nٜVM_Jo{O빷aһ<{VKt(~{.pDE^VmX^{`Ϡ]O6<2}eNڇ$ }|vJ)nuƻc#E<}Õ?^E\NQJܑ?ݳlɣOg͝O~ƹn=W0cVZ52HC"Wb%1됉kS_dsϪ*=fԚw+7lt/Su׺Lqm{_"ߚmney oxe{'_Ln^l傥r|gj'oy)s GMtno@Ü{OfP`v4QWPkƥ$IbJ/')VjRrs5/JŦ3-Tg;tBay,y*yDu ypU"Qh$puɕvjETqS 越V_)$zp\kR` W\rE*K&s $K*4^W%)6DT.{ME(ܫdrR&Ox ¨yv8tCвYXNaG1\d1,uB%n'%3DERQOMKıFhHL 5RG64q,,JJXfT]j5,QRPH$e͉Zj j]`Z:k4[G H/ \[m%>">PFs{ \hH -1: t8Pi2Z vw-uڄ漟"F!*aCO|ܼCպ6$@k8,& -`@;}Qc< Q =+%AU%}כiԸWp )cg1 AB5ߍf X6 e@YJAm4k 6݉uj(Ӡk=Nl5L,28I&k(3ajY\%fD 9èhb\H l>`֓x7z, VU+p@H W}QE7vI1`RѱUDbأZmkLEȖcŀgYSdŷ*4R4$`>a$"h+hdsj ",D\oUCǞBJbԎ!c֜ `ąMu!")#w+( 2Pa}h%JIao+P(wED 5-X X 8 5@8F4FpjAl^'aCW 3~1M3)OC݄ᗢ&. fOvP.N` D-Q eҌUUI *s@JҝD~he`x)[O,Ьb0+hŴb%RFpmQfgBb*4ʔ8T@KoDWVcn/A2S `5 ,2 +ٲ̀! 0Z^ruuLQhaP:uP"Nb%36ܝ#ҒAE"3ܢ@@(NA:TΘP%o։!'6ѹ{p"b`~;;X2-AeX) Tac=h9xI ͢ܮAR2GN<<@ 6R"9G%Гxx) _TBU$bhq6g"iƊH '} d;& #Er"c+z)o5eԬVp9/Qn TVR0X/ {u{5a[V1e±nM[`6bAjP}Dek+D(|ї ܛ6fScOIL}JJ{iLG{elbsa|Hp%تnNlȨ,/%EKZKSH[;ǃknx@䌃S[za%E0fmǃL&r!F&X Mj:Z֛t~0OwiZN Tt$W7' G,K-9"u1,C& B۪Z"/;6OH)#]5a4 p,ԇp1 "R7:\, AI PbALZ U'B/7u R&eB fi͢?JRؤ8R1 C[bjLd+bnDŽtOVo܎`Q¤c-sCjEDEkUz@n)$vUF;"H)%ɝfRE"1 7C(lB+"yWՁjk|Z򄡁& |tZG`)0Ui.5a[c I)ަiQ b ({Vo}֠2FT ]U_eYoY8 ۸o/>AyUo}?uԮ8q(>TV7NطvC;3yh㔡[~?7޼pz['-zc|S%}3=uI=?ix/8S\,883ۉ?U?7WwŧG%i KrʹZSݷ)bG7,n2{\2܎+,s⧳_|S^}^|Sy{̞s=n?/ݻaCuGsMwyسb|ɲ=,OmI΋.vG [?ΩIg]B;G,̿x X75;O+:Z/e_}2˵dco{Stw~?t.} s{$}?zKhxFѳJ|}I^ݡNW7g\= *v})sO!:^v^{bsd=oբ'>Sه`-t3t?՘~_~~@> u'sz=6K7w[NK=jnKR]&|_acQi''n95k~ZqS'3c2$gOo-ͺpW:$mi7>#kge}w}ݤN iٱ^5->/*Y1DD]~Bm-l m 4 IJA1?m4M҂(eU|rU.Uo+W\T\DaU+{94tqnh93gΜsfzġ3R6̽_8ԟ?U/>Y=;mҎ}&mXWݝ'YڴUoKzcxz堃mGdu髇oQ?ᄈnILb͝o}y ׁ)-[,>fGӞ{޻g|fцtsƋj7-?|k9suy 9;tc7xڲc_x?k ޾k+F{=q[e9-#V_l}a;3~hOMzӈKw9ʃvu,o4"=W㹽o}=#G|vZvOzm+Xiaˮ '% |phѥޮ;p磷{֔nA­,߹~rDb⦳~65]9"r񘋚vV9XzO+߽u'zvA+7|*OC{~zEf6$~իIM {_Ώ׭i溇.xeoTw'[m'89/LIʓ^zίu&?y9vWݫZ~SNd}ܯ d/'^aΡWۻW55ز~=Zpq&_8q6>s/?~O߽4f~_/԰CN{_^gy귫׮oִ7i߮%]5|5`ۮ!y*ߝnj>{W'k/k7/yb֙}Lf_}W}Uv>:tɺL?yνs/ML]]|[;kϝ/?~coȮSMu5Mt,qZ=tZڹe}-fSW)/l@\b:x -*<ڡoGaF,5LM`xgqpG6I<$RiGJs]>EZw^WW'Uii >-bgEFB{```_ l1: Uw]$}4vE!FX{{4If`JwDK/y?Ƞ?N`o_]Uʀ7 _Ó:@Ř`{ӌ!#F'o bg.(8r{aWΞ]QTuI`\l3FH ?'NQH~Tߡq(tJAw $8390@D*EB&.GgIzkU$d_ T:I&9<ܨTus4YrίqA8* HfD:PT6mBmj\B(^aJr{P),! h< o`+k)SϑR-ݘ!qT8f 5"&OѢ{ R@-%t4Yrڿ?k?d@"/#>,oJ-1}HO_@( 4/A|*G:MvOYdP %cʜ=$' N\%5<|g"Փ@V#@FBD􍨶2ND~2pY|K!B%YYmV`k9cykkr9J]#LQilflcP3C8/O-hZI:xӜ,q ]~e%Zn!/\7{g.V FMB.wpȤȴI :h%3GXoj!UBFb!`s L>pq7<@ղ l.8 51 O +`97W^1,1@|҆Hq6 ~/DO?Q_8Я. ot$dt,bЛȸt3rEUP;T¯Py&Y\:A-4aj0 ի;UZS4f43{FA=.(/5a):_84sio*|g e7EIrwT&UwUEL Hz[ALn8$K:o&zWG7U](j{ߩ T^h:z4cƈ1Ll9g#AxC7 3>0m$:-"a F/pĒv+YB8cu9ɎEuϲʢ&;E=vdIv<"aHB$ c r꼐Q>:+3h12i[x>P[R.x$-I4^VAp$XcB8YEz#?s<4%H (%8_T~59Z٠2FBNl>v;-dX"BvYqGT+ƹBi3]+JʙJ(h.AP4*%jdQ <ٍnZH#Z L *S4 SF.IQ_SpB؉w:pBP PPnatUg4Sd)>QmIS|QfbEO 6Y^<6lH:D=|\kvIa@V]@ kHCut #'Q60^HSϫz}WSQU9/2rTւ^%1TGcJ(TilgX ]G3mr2Ҕw@  茆U.W]h ˗˱obP"aZTU4N{+Az<2ij: d"CE9.OHT-f^DM<ފK(< f9 @0S$ܓ! fn*/q "J ˂d}).Nr e jQnPH (eI~Uk8]\%7qWp0NchxPL+14xMhXNuOzH7ėL QBkB-1dвkE:O;BW=z #۫Ħ u|%V`A\n:URTt3eV\.i*nluys*w\w"EB|l"t12Ρ?%$d:.f X`G46ؤMj4AV[)PbIv-BEMLar^|.^zjjZ>K Z]-Rh SVqؼҪa2/QYZj`2 ˽RmBK ,Lfhb%mN ,B4{gBk6 &6+ԷkEҠ&5}:.Ib#,K/hp؛U-`21W|S8sZ\> gu4(SlDP5 is5{F؛20D9Ihe.%%dow^q ;>w07&@<"SEj9s.4wӸDFjHőOp@.aZhhf|<۶ E6L0 &{l{}l ~lh隦 42S5+2j(|!3oOJvQ?,awO;=`?}q?ma?Gu'C꾄Ԉxx7)*ybEGYwh,Uߢ+J_ Je^Te'mʧ+NKW VR@%*D^9q!sf<,]ҙע\n?x+g}%?4,J(A@~7&VĢEi^iV4 aG/LL"&mgm,忶Vef]΍pdnp ]&8W8A]m}Y;52; R`s/7 wv3 N*tF$5Km(bb0agL$ʂ5o7I\za _ ]HxG(5y}W4MNゐdžzy\4C&aHbVWj6uuf+g5y&{jOd$]=RjKF1i5Ŀguo*b/^v1PŇQyi 81_QS9 Ki8Lg\]Փ 'zMOjǕZ]InK/`"F/YP|q'q!arY H&'=a3HbD3 iEBD PWW8sj . ~z"[HJ8.%I]6 Y,Eeξj̫2Z|tJIf/>Krks vr9hQrBٲXG^3.mv6 '#"pZ5go"˒ >-n`Mv08C4?ގqCD97Sb4eY}k74+EAѷ+2j]Ov: |hh/ c0_AVcBkF٫Hl?^7Tɛ 2bexߤLL>bqQel dW*,5; ut!&0|$e W\5C-ht j/Ț&Ev&3[Cc~dCXsΕ_;ع.v*R٦6SD6nQ ,K贤XP pt$7 ajn7NJiM&*^/*DTC^{, SW)dMd}`P {Ȱ0!k,7RVƩmQPqeqC]x4,gVUJ|•vncfҏk[E P[-/cѮegHf!Gy 7)z; 9lI-)nVͰZPI!$4-UZ~9 u gjS[%:8S~yMMڎ&!NHJ:jB qQA)e.̀PᲦgSn3 B#Jr m* |sXupgy(wPAO4+Ԇj rm~vlYJmBaeKE<ioǭ ]U;:Xɖ2訥p?XpM ePQ4N%)usmOTӯz7-:pAwo{5ד<4K !<[huF2vјa u5"'&%h˛P6ɪLyvO4Xoܸ5h̟J-ρYh>gZL[̰MG{ 6zvQAo2dFϮ  w LWbcxKC%#)K\R,Z᪷E0Mԏڞ϶,RlR%xSEp %1BTD)n {7O,IbiiDmQ#ʘQ5xsr` #,"Q L64)ɠ" oݡfStfndHY*4@BŽl Vr2F7M˓0d -J4QVdD94dt8'󋣘ȴ m#(y ۗ6PHp|(Vz±  -/E٫r@IlI[A 2#1n4\ɤ0~zQ$H+Bo&Ź7#1;`,4}H(S S1 &?f-ٜ1jsg1{Aky=1#ٟ⿃~S mۉۉv&aUU>BXPk:^L7TBRZTX76#2MhEY\tୡ22b_ڶ;42y y ee8~>Xo]56/sE1RZ:,8y,D#jGQRꢔrp/3\6-*pfA<$K\ .K5mC/_HVp~w2q/%@p<7nM\UqS51|S.8Nsd]^0-379ߪقF"Z0_KWJYg?e];371,$(S;$rX ~w tl[vȯ~ }m~E߂(D]. sWWyΘuTɂLsN(Y;Qߺd(Q;$D\lr}JoYZI=0Q+I'Ej(]vKo%]^%8 bQsR Jˁ V%C%RKH6KM b?B/Z/0z'*ݱPwk![,PتP, 2p?HbcǤafbFF65ŔGEsYMʄIM&1f&2,Xh·%edN 0IKq TJ2 SpØd"+rZN[/088Ҩ~n񾋛cni b0*c]5eh rC,͙4n|!C,6y&%|,QH+wTHBM3RH*[~"gS)GG<߯U#/-U[@p=-@Mݎ_3!t |xz(%R-N6Z!$yβ xZ[C"Icp0?~GF %Rs)3g(o|EHFqT oF,k@g*kyK-I$Ts*d2JJGgU0e*vZ F\b9T~UTsH{i)ary+`hhj0ɜKJd vD/8Y>:n0w`Fn D{ V8%}r eIoB[8jj?9+ŪQ€N;w!7d2J5^"C udžWC9^Rq`w/} BJ T0y!Hg(JZ MhgpU*yɂ2Tfs<Cy")jɣ-gOẈ49Gƪ2Y&1DӊPgPaDr<, b J$qw71Tj[$:).t"$Y3e0l9b|n[A0Ac}zNF8+,U^L[ IL8+11 + :s Z!*4o\:Q0i`T2  iWj먠 mZFGkɴJf0 z6M:YHqKXp=~lQ @h4"ᦙ0{qaXn?ojl|}}AewDzEEL0skO ?'#%BOYcJG'q:t7MEAHC@$>XY ٖ1,uju ӱO=v y _o83IQA,臭ZjZU^ENfH1F/?0b(8M P qQ[<G;قvVS+-N2/v dߎ>5}MfkgxM :ˣ28gO5|.dh}(dmfSJ-hX>k$W &H%J{Ur8Q8L `pjWZItI<⹐u9= n1P,$dXoFֳ*6X{coMN·ʱqu -pLU*L(8-%۩$@$kLs@$bvHQZ1l4+F2 0 GE/z҈ Ɠ6F`'Nvb* L~e-X˨$(*Yrbȱ\arS9J] sǠV+*3F́2,86HrUB̶.eB͢x#qc(S ĸWp>Y8tt,mcUqb\ tSI!QXkR;&)$KHb!`)µ.΋X'nPhE?2I:1N'хNQD ן`(sGzյ EEyw(5\*"LJx+H JT $qTdI|SxוJq5"}0V#3XI?4ǡCuE&nIXp%%C  tVd8RT-huųڬ"l@cmԤZZI1O\Qj G[Z"xOY q,NѣUUD `h)=a&=b!Q2|k7%x%lq/`jR} ߡ-Aڡ?&QȹK ht̉ô۝qI!Aȃaq僻ee< v!=/"`t\Ѝu∙ݰN'0Ϫ Z/0}u89*q?$r/ ;M'o,:"JCP.,AQDvr<&V O, pȡZZF[hp|x5oKU@MT ضbef@;!Wfh΂fI5VDYvuT#D!j [VH8H9޼67?4[2 [[)*.>:hO`|g:(jE -Ϩ\B&y̶ טJ 5Ym%,ʧ2Co"02f5830$KGp &ƴ4e5"}Ej_4\2&cI.Rշqc 3 zf_݄^ N5S rnD& 4(Afh doDjvFIh@+XQ..f#B/`). Fqcv_1<::$cW JW,PV>=It x01|4X$,룖 MZPa=K}`0`h?u^-W ߌPY]tYU+PqQKLaڶ ZRI(Q.Op4bn) 49Valdn#J0 V2:q0P+ M@" @EmvDcbeOD\i8C6P `6I,'|&G"DPHH5Amd\&oNc*97:L+Α߳Z̈́5o؄3\2r_ {LHHv Ma9:g  tOx W&.)6.TTԢljd(6MANLz9?kT U2Dۤ(yBQhC).zh-O^8RmbʒƔ3]FRe)1Н慅k rvS٩feNrW9Q8&n ˊ/SW[#0nۭʱ˧,\;J*_WI?jݴπ.7ڑ$[c%`8(\vIH;9u,iZݒ8L42zIw%I[|T-Ku;KA?sX?xƇY9^iY]a4(.2Rj%AåUX3q7 9P`L;iiL2+_v?K'u"HqoXصŖӟ-m}.s3VN`[i 5^ΖxUNyӖ9 \kfx;m"9ނR)[BWc'8u_ ˋVqc@%9˺Gk lH-~Rf GW"6%=CݜPahyLF$87bxvVW4C$ xF%} \L ,Lhn芇gLb꒓WLI(# .#8jd(:h1Ip -t(iSŸ5"GQ(mfKXj_*%22HrbHm=z:QI2/ 6( eYb-̶Y:Q\aj.:=.ufggb"d@mwv]ҹLIe]3CLd1oZ gD5V57¸vlou\kSaǤ__pګ\fekȵ,c+9/ .ˣ&A)W G/ģ!x6HM-#+F#KCQKU5 Q 7%yU/j{Dœ_!Z"]עC$#^+Osh\IBr.c,G~QEIU ]z"aV;4K9a|,;"t&tڮxPؚ M=TM!JUFюb"_k[Px=Mg R&muH[H,ZĊ5A1l)/Z>?ꍄbB?^VĬ@Ҋ{4Q 9FCgX TnZ1L_CquOmFsy`CY*TiHø05Hn%F`,,e Ϥٵq,:`t~;  ζm,b0CvX;Q+Tcl6_|WZg+TVCmsAiM,.aNB$uX3ڪB6)A{ʴ]ߊ͌R*-Jș펹Xn*Ļ٥Ps޷u{ٞgR-%k\f?u cѮ64 ήFwshy^O\:i? %)![5V҅"TѓӖ*a&":/9&CXXu5} cᵃQ<]H`o BP<Xw;ҿPYo/b,lS pk uFz#udO$ޏ@{!xk7%ȡ@,ĺzCp7n~a^؊Po/5 /a uy;H4O]@?b>׆+:%V菅W A9κC}йɂ5 !='6GC0[>0MXIb6;# .ƍI;@rgxEp4~p4:D{L'  g @tEbؚax/7VqCgYxyoƯhM$i;2<ݧ]4,Bݫ#'9`'X jզ/r%[N"-aVa,%2iqzq.R52uw[o4MK)kD D6GAR!EnjlNehy"ᵐSYs6&)7(u81 jLَvNClFHfjVD9pxۧ)|]&^q2A2VWDZk (mgs+&*Uh|59M59 MhK]B(8ZZ%+ÐPKZSȕi#n<`Od(:KFA#!ԂSGjHˍ82r HbVKC\]V-)25V`'FfKx9̆vxX;琰a^Qh&?P 1l9kȰ-2ֆKЄMge2j}"Pm&;[a"fY#9x`[y6'6|bLX9ÕWi& ָζ X-D9p\F$'%33b9{)z;,䦇ESO>57|ԀMMoߒǒ۟9Fcc˖y{y z{4r̽DIdSt}}jOcC_b }_w} >]_ejd/L<-Ym{5'.l{ ]׼_̆.k^w۟N:K7?Ɖ_KiKBW=~g{~uг8 %'??gߴe{Kk/\{;n>zgչ-$=.zS׼x?Gox]:_͟<~^']_L<ZsPGnK\}_伃"{lԽo]ϯ %:9֟XzviS?aԷwDOtyh'n>?_z7.u ~.|ş8k|s;}?30U^Om柚Vێ=ga[-'ݺp>*r˶hOyyo^O|ю#[Wy#+0z~C>k[:cKc}>ٕrwWo'V~3RgU尛8p_k>sp>]ZN:kr_?fr]uߕ?[ݞ/x'_~^^_fkbyeo׻w6t'oy§eSpߐz/[zgqמ*GA7N[ӟo ?yS7?yڻ٢^y;CܥW=[*g𤥓 ^]skz?^x?KvkC{T_ ҿ;nNן3Yָqo+?6bL\M-~AoOI[A56:kA)Ԅ @aSJ07L^xQ(EhkګK|;99陬 c^KKn(.NgK)wZSj@"<\ƫ=>l[;H dY^H4ԃvj~%Fݒl4) &l)o|:o? {rK=̻v0/2}d/ 3#>7|@/˼E{RPP5W#trڵyj20T|_d\kZtϱ8f~IJzBAU?Jrh9/acI-).񇴊Mٱ)0R'Z@:,)pR$l{$ʼ2~9\c7S\ٽ/0.̊jv^N_c7A;UPF>i,XFxaՠ/Y.b}56Z[6嚂e6m4ח6_wͻw;rTT S@+kd@}y#W+gH7[,Ylt?`SO+P =!_g81{گ% %z}cɒ@ϼԼoh6F'G(`7ss|.eO>ܗo ':c3w\oד'󹇟ͧ7_RS)~ne<ֻϫ{[o𐽟]wѹc~ͯԍ|1/d[:׾+|&p>ر{'};n/.Nǽqc>'=`7? F>{^{}+F/1rjWg;OރQvɥ=piKkzO\==ELd_z~ڽL^6Yd?x癩g~b>Ϸ7 }&s}d+VAO_Wzx#G7o\vi^yځCw5x,q>܁ϯgD~ WgE׼{߿ς??]?;3+C6b'6|=}mf=x۟^Ʃ%_{{j_0yl?=Ǔ75wv_GOWc%?5?}j{Ǿ}E݃w=^r~нr=U{nzO+:纯:qmʗ.]O,b_=tt맚na_Y7=qOoκa1G\㈥_g_vgh_z9WǞţ{7?=~G}5\zi۰_t#{my5Gܱ)͗}ѩч:9>Q ?y"ǿcǎK?ӸJ N;p||xI _]o]}ÁK+ٚ6~h]u?]{u_#._]ݶosOyW}{|wlٿ~,u'z˷zcvQg͗_=a׾V86;p5ygwh3tG=nv'w;iG<39Yy} A~^3^</c8:x^/߽Ugxok6'5_=#|{?KnYynOWy'~uZ{7N]ɭWV-Oݏ?p߳|Ǎ' Ï^(}G/9륜u޸ď|E+};Jǿčɿ9><#mą;d荛ދן{zn`pwR[?yt3~aUߵƏ4?yK.]M;_å7L%/zxgsgn7~ܗ}WO#|7T_K%,G >_QME3]Gc35o P]7f>2̿]dfe ϲFin^[e\vT(pa@;@JHFʒ2H1F4̡_7F#{1FӐhc 2&xw#P:4oES:DqȠ!ˡKudCo 1G'UB @1XOH`v7J=%4w_Yۤ#(NJ-(ɒ2%m eld+^Qkb}m#)3ƃ2#.̱IE aAzSh"Tf;f;P ?;b0~Of sH!Ft}чD*Jh$7e deQ's[h8'IGMI 4EM."pP(".xF](S=uV@t*%>;th+4RR&THȎ/MQ($ە&=*rڅi G:7Al/&g'7r7G6O>ONB1]E.Z z&Gny(q9ATX6 64 6 #Hx@#2z I]Z(zCq@Ot "Ņ(`%,-XtAѥ+O@aZD$:ʁʌU`J<)C'L%7!#VUUMU)Қ9&s̪tilUUERUۗkd}>āC=[ ~SS21 L>[ˆBPtb,tJP?1Rq3‰YWV "73a5"tY)|fwx@LbزF4F:;+(WkLXGCؖZDnjJo d_$RPX:4U]Eg  .=w |6u)?&:BV%`yom1LWQ.:8& <(geh : Rk$zp*QC(4?WpG{k#0e]^,3BR|&wGyEg'!.T#Ճ@nH*(V,x3A1JR  ,2$NW_1.η&\B hW0 ? Xe4}a!"2RRY9< G;E"KVnQcލ8qP[`tV" Ȋ>")L2#!P" K37I<")`ѷfhfm$Fq)JBB'r & א9h^f*"P`f2U&sZ%0U_-[ض*ikMWSeqPXf !FDy,qJ 8ɖYilHfB<ODZ'n͙L$PAQTMjX!3RER ҒJ[T[47[I "V+ */I²(91a' EGa[#qT12SMu.&Ijdw NX @kBPci/namY@̪ m;IW Sn+I$n-? {Tv$_^G0&U;0y_w{\ qE7Z^Fxy)Av1f{H40s݋zA:gȥD9eca MH>WΊŕ ί+C*Zg0ǚZdҢl~5_^p%3u7$HL/]@#'x%7z"5[.9胳𝙾\ݗc0JтEԕ@V^Q1QP B6/܍oBK72UPl.Rt@(Џ' ?@^q^PYeKtyϔz.ܟ ( %_ T ЪsޥJV2#ӗPNwc@ Hiq㿲8dfMS!)m}uc-œ@ - > 9𤄆`F-%9 ^ć!*Ou:LDxL"O_䕹IAk>f!rMKI+BV#BpjYQ,H]UL<(F%@4Ei[Vr[/YI:UI+ñP6á1""Zy$Ɯ#qVĴ0ρUhH@;p6QzyeP]haJQODCMEZ'(R#I*ɹ d±T n*=rHE?H!WcS[MҔ8g#k*{ڸ5F^pƄ֞pj…=wAE?NWUTF!#2^:y;e,dcC&LV &ؘHBRV}DUb;ˎ6Z̪Xܬ;hSI] X-fc#/yA^6qjϨÌ&jLV& !U; i1 j3,N[a#>쪬Ah3=9*ZU&2-*p5$|hT2 /K/he:DVD1%[ٝT,/pf皹"kf̮-2k@(竪cӞ(1@Ye8t$7O . (@Q 0 o P@ٔd'BYMq s"3r*d̯d~d"Y洂-f|L2E^+WdzE.yWj~yUMiZ.<+yfG:]iVaIUa!!eN=b5uoSDw9?mX4VՔjj y/q~[]tEG-q6x]n2>Q[%>&m.Zrx no?ђd>g^2c͞e>zabBWǀ\کG#xEOߣ)}[UQ1Uj)/XqA[qW,mfݓݮ'S7մݻmLy6ɃinM–=lGfvޟ}_~~_d1y̻w乶&̛83!)tO_i~lj?W=|͵ol2;;m<'l7ٻCtZ8X%wuk(Zydhk6; ̅-i浏7zgCذW74/v/Z~[~kTvcv?5c%_x3b9jmٱ'okɆoܞes~ΕGG.W_SڮiM8~u? H7n鱳g+oy3ov=td^JyEw8XֺuiOse={gZl۷==m5 5K߸"{Qɺ1K>rE٢uo~qYd/׹NZc}#{_%M/_C_Vo8?W `(+HD ~ؒRl zDIN|-GehL^ pLKz+P3eqEC B :<'DIZ>t`x<ıN;A4oջodSUȚՔvC_-{yu=!/B ,: ݝw 'DhK+B/q;;SU*j+IegVƖ_heJ~ kRҤ }= 1#~~ًGY- H޾>KSx .A!.Oyь{NJDh7WԸ $Q ՉRD^BPтռzg;(Md*%xFux%|9 Lx}+4G'#߅y//"x09 @ت'PX@1y:\_)ET(f3?lz^J*dFD:l=h#A%G"AOZ_;~-BS@t " C `*~ԁ'&4)5P^m<w7&vA.Ah˹7E}:;4RcA!}BJg$2#M. ԖGV#K>xVI@9?P0*Ga ;#*Ã$U ˧PJz\B 0aHzo $K DiagHTRmr \i` bT{[Fc 7B@ Kga/ WQ 3?0 .!phfh~1_< gPW^:-_;ÒJǔ(bT)@@, SHO5 ?rѐz4b8%zH9?Bt$}xi_E8TIr1EBQuNAʣLL7ۆ@ Q}}Qu ו`+W`.x oC>80BȨ]Z@t/ =/a:Ѡ >?E! DUiC%&U*ED8:9"ّBlW5cʅ)lYI"M52RW2F-:FYf")$4A_)x!Hy4&*lIs hhGMb-c?  ⑈0fYGs "W5 70ā'1LЏ[- Tݎ'G˦:OV3R،+.8y&Zb.+bǔ ԳbVH}1X&?roi*ޅDIZxܑV=#ԕ3ȗ7^:fZUP@" ?Cȳ\k^>ҿ^n aB,}ƖJjR EFaIrhEw&K.5rJKXʄf Jc Jb}V ir쿩FYػwFյb"Al%2*K RÜB 46oՀ8CJMĨhhK5EeK6r‹=qfW©T^-~oOK?}\SKӷ] P!T:H 5n MXE P;*v)b]P)b}gI <.&l'w>vwـA ߚ@ƿ0+=-̿=/fʼnd SC/22ȡ9h\+ C*cdeBq,^ 6q1)ISs֟xhL| ppsp&BrQ>F@&jRod~~Ra"KkjjaHbGГ gâuLБ= a{\ 8<,0X;tN^UyDش(ukpϟ!4`C⾰Y`8B[  Lmq*o:"_V4o4ΟMG(**iEdDs8T!;^+Pn7h<`B5o4%7U &1'[lȾ ;'}l tƅջ/2(1?vН 8Xn\eD2;( *C\> Fvi g[5_"`T"t~?M]a S#l>w9(r74 COFڑ4V\eы+p>x4, }*C݅"I8PZ<1f29E` 0}PrgO_IZLZXY80@a0P|@oP` .-UOr`RB0Mat/i)f @iroAc0u=Ѩ 1wrLy,p%Z`bcwWњm,dh6qwpa@eC F[C+fF4(_J cFchPf2 8Iる[<q01(VBM6=o *ZNT ^5#7 FRxG6D@q;/X1j; ~4q4tعgWq~;e'zrDhXWTSϗ;6<סvCC"kU_J^UrWNwYAxi\L%wzxrqWG\aݞd6խ|Z+hPؑA4m^;5SR&>[Q|*ɻ n5rses'Fqiwcӝ&uJ;t܇-O&T:Y9TxMc'lpr.x>6򈕅r);j$EX3U#tz^FΙgҩj) Slq3ڸGo:WgķM&:%{ G8ԾQEXH.BW1"FjpeN҈IO-tHJf{yt_Ƚqe6Cټ#.9߸ g\^xCgo|r!>M;v>UvnJbഐ;Ɋ k+] ^\,9y٪iG2-3\Z0ðV܁ ֛Oʿnj9f) إ3ŕWo?3nߪ5aFmxxjKJe 3_`ȥݗ!#`,Sor%M%i,ܼկ)F|E˳ۊAءDv ٱ+ͺ&`ڰ6CWŚ923ZaGv'4_Nlz!F 6&ib{lrBծℯ_Ki\V {E}qk''?m7N~gWAk Q*G<+eh7%^"eѴW>7w߻}Ys%OS\ujx'Ww*CF?<p@裸l'[5֚xVbf57t;m ƜO1`o8i+RVjǎ7l†ӗX$ul=h7R&FC7\^`WO0H9-!n*'(OY 6sd|2l{reC͕DOp, Rja'iqr$o^{hUn#-ׅXz1hG@\SFYSĖ,:ieb}v)/\' xtFsjr14-C W_pt9IJj8f%춮p߷AAl&++|5I'%^-"qAɌb wg{qcmV>|\$-y覆QOn1`=6eۭ;.~/>)asFյUٍS܏jɑk&]?kQ wF 5^f%A5הA\CZ)9,u}rq)ʛ]Y>WT)kxdт8y8ppv&.o]1W6Q J&o4Z=bXǁmˏp˖ÜT-d ΂jSOHeIw# WG͈}MWu{Xq )uUB2GMXpbK'^ shT9r&W[p ^3 2Ti> *#OP̲$˯_"u-jo-XqS٦ogc+F=zvzW25b1* .[Huz0q2{^#7[ϩvPfQ_~XjɌI% 9I\ie~IIJաuZd#=Kj9 ϝvqdzVyQo)ZGV1}=q$%mLN׾ާtZIDmp@)nf)׼6o[E6"ntA6[ކM0vIo/fm?iw3*9J~zٴTYN5UV{]=Dm㺖=|bBQsCTmv]i?v霺 rCrߡ"kDjiS-Z)GNdJ.XR:n*9X;.Q[,׫vljZwi3_;]taYs+5z_ӻ)-J:\NzA[W`I }hXoФ~DةŭtԾuPh oBRIZa}?cnqmkILN<:uճ{'89K[%"ԼZC#kytR6'CWklʩz"C& Y럺|UCZF끮iV2Ek<:,09vj(V1*)҇)_0pW99YjbLX:]yMߖ4>ҹԛ9- q XLd4dnS$>~tr@Gv[e_E(!\îJ1+{~~+FU *6AmRow?I4923J^T옌Qe;z6;ӀF &]3eyz=f c;ɂ9'H {;Tn>W{vShĕ &{{Ȩћ۰xpeͣV{Yu'>^'_Z3ǎXA{8^Ӽg겳I+nhR䪧->#Gm˷[lPQII|CشqwPӽk2VcW։ˏXr 5+ ZvTSUn&bێOkKPں{ל 'n74YQ˙[$yl[b;tMbq*D0q+IV@Ff(,q3mSԲe Ân&+R\ڦSqA*gή-s]aL9B\Yڼ#ˌ[^-S~)ވ̙]={SzDj|T=jk(JS׿)NqJT,[@\㈡s)ѠPfһgFL/:~6m/VV6EMv)BaTT%fm3(h7W7.-i3A9`Յ 1ӊ;O71Bj@= Z?gfWU~8:}j <(Dc͜ZCK}̰0DbJ=eڳo3CUQ9SXi8sGk~w>{+qĕ+v)Ͷl-=5Ԛb-`#IJo[\;EjНhYzӏ+_xp e4d?*~wHDZi-fa%Y/l5|ذƝ]) KtX7).f3߈HzN24|FsJ^=[9j>=mحenGܼyQuX5n}, 7'Qc&>fll@遛Kv o4Z +wTٯOܙZK_1AJ NiR}XR?yg|»*];ME4gacx튆qu[rGuX 4D\a2 ͨK_0Uej9E6Nlde\.:PoR,k,95xQ}&Iѹӥ`:6:< NnO4p)2ʕø:KUfbjb:需Kg,ʾ]waDHͲC/en5nN7Aj"!7ۍ1&L׮러W>f=۫OHF3b1ϥ3?u^=E>PUCuĺ7-y$Wl'xj4nmfw+We9 nO-Kh˙1 %2ϖ^hM_P̠DjRZ8~Z/뭗J|a_Oz&fFCKIHq4ak)ƍ- ZZ.C=17/;zRJ3e8;$m}æsocU>G{-zUj<TM`Tnhc6~&1I Y>֪Ǿ/J ^)zq[ۅsE) wtV}n|2ƔaG~ˮ@TW]h='Fyv8k=mzn| e7'ZM;pmc;Z>Ibf}-w2鸞=(>f"e2-)`\N+*eTӨ Wܨ:Wk4?o>ix{Jq[H Ǎ:ƪ]65<*]W)cpٰs uş<8o^w!8|rȒ-M"7]Hh .,P4⽥؜f#>ѽZO ]*D#SnJx(<`Zߌc[ʮ8j~잊ֻo?zwZrҖ]%mRS'e)1 Z2m#6)n2yD:EʯZZs՛U8?a̻EMI{ҥI; \h ]Û}#ni{sȥ-95?EޔTjXuX>ac"_r $4^M>14КUyT6zCu{U0`]|P-W(RK .MJC ['fݳwN: ۵QHiȆyB웛5G7e_)ӓ;n,K(tLJKUr}yNHEK4?Yd#ʈu 7Z8P* d놾&Wi &P RHd!"ÇhsĉL_&4~켖@kMՉFD1ј;35q>ZDYHZK q+.Ê[.M/x),l ([klEi‰IAg MMoסuR]߯%"!MQ#.HK@O|.P bbX>]&V><_*.4B2Bh8'sx|tmM< ,B@y8Mo tLN9R:KW}3fXSO/&*$/#SO]L5cQb5i;grSC rowY>duQ"W{4vb.&~$1g-2fA_\>suQЈ !64V:;_vI]A9_q]]WW\,Tm)#O츾g9` !Kd|Ct&c3"n2~Sjy!`ڝ%j%"%(Uכ]f7P!}̌+]eX7myQv`.ifӴe̋^*OނQmI 5-~i1GZpwD,=mo6:GFiv-YTrKƵ|zzĹEJiV=g43:OL--%yHMJ4oI-U\u}F1e{[mL̯ b:Sdbq32AoEF{DxfVͳVw\w7xw=k%c"6vEp}z|~Ǭ-*ۧxFЛ6a0f>NuiԢ{G}⩌\m Oo˜~,16M_ oTM%ү7~Sl*o*ipSIKP!B~x-ᘽ8"b76DpĨo `~D=L'1QK~łMאH622hPL Z8G™Iu?N7/A]OlA'?C$翍 =$'А OFo33"I xMO\>XHBa G4L$ ,ptžۍ `Za~ gE8(r1:F9n#:BP( DH)>(ASymCpcq njHY@PbBiE#sby5ң2ѨgĈ(@2YD#Q3ّALRq #hp$Z t*B& ƫUhՐUC@X乼l|@** @ I'=2b KFcP ) 0XH6wU1? ,*_lU$_V|X5`U?*>)`PUM(fTh {r(&'!cQqa F*  IؼtذbBTP&Q~P4びD;]zɈФ 7 =V/]~NנpcB4n_GQu68t<D!qqw9t(TY Y3>.^Jtx%>kPFc$tM Fìh0` CcY7(JVā8~2ȇ2ђCt%ӯ'IЋԫ n$ S^΁jLj!ۻ1x,! G `{8*] aahL{"q'?Gg8ClPLxA`B!l^uj-ё0  ?9T30/" 1g™LU ::_+L'Kp1 vX0֑P>4@ qL^Ar&R>yB' =P<ڕA*Vb~04z=t{/uAU%\WO-ɼ:U2Z*lsb%z VPTH/.1*2DVoaq:8g-dddqD`qֳ|uRj&c_;!4&z,_6#w.? uXHTASA_Y&kjzT-x`FfK !X_!pB-!m tt ocߋK& x"?ͅ5ls9Xhca]N0F3d 靘N^C<:FJJ7)8VcX0` Y?&<,epy`:P3bL3`OxC)# x<L?p@ sL/T"v7=Ӭ@l[|3Qטhyfl[@ Ί`Y7+H+1@? 64a ;a[8G.a 2ϏD_8 v0_}Vs`Q #\U-<6=9p(`Qo-l ςn0O} z9;`cZh` DVbn&\QS\?68o p, 2n?`0\ [Pn< 7/џkVxOw7xYek4\vp9j68 59mL`6}r\]vNcCLHOQ|K'-, @ ?,\ 8~`қT 3D"NL=/aK mw ɢ3پ}X7BOai'ֺgk9:k* < >\d rsG`Byp\2 b #AZdەG^"W!WoH߷/|` A^|3~S"7)AJ U9 l3x)̐S#!KO4p) .t!sS Wcw,ҷ;0>0}>#P20tt &L4 ?B}=4HO, `oWSI΢Ct.? :o)PXdc*uK4d(%&l7dx~?`(0 1ࡁ@QPR]>p?"0T*ŋet?oKJbD@Z7N gE2xD~Gvޫ1L?5 F:UQN8ٙ $O188%؝Ħw~cH+=uAF͜CcHh . Ut,%$%l ^u(m^"bO ݯ9sգ(`˅UcW؛Θ>#;l}#/4ZAfe~OSPRۜn[D_OiC} Z=-:t2C-H [> AW5^fvld;[Hf =}韛כ@*dz+1!i:-EppCl.[H<{ ^Sxnol3X#,Z@kn%~,^aJmG |#j HTßz~`~:_(+8Z'0Q=[YQo2]t~W?j"/kπ& w?"?i5|k( ӷof~{̃^d|۠Y}GԻm)DF9(Q ^acWK pC]@ s ao|ik/qspH3&li!~&Yo'y6s> H{306]z׃wc~N7N.x]B̨kci;򺒮ŵ:vOfS7;[:1wIX s3sJA"AJJ7Zƙ閍k>:\BɍPy2/2}}I- J=F/>$kVFYHE&߈{fZCD/kWVڇcUiU Y!'/^7FHwj˖^ $Xe~JR;YLH *%Akjy}+ Y?q8G!d"aܕ:O<{ܧo*WzCd攖M%O^C_7˜~#Az5뵺o.UM>r0I'Gq{֚ҫߔlZ:`CVb?(lzOLg^wMw$O2QIl>nIi$BKa'|*n/[$yc{3?)$m4Rgw0mO"'XMVŦkDSHK|طftmk[B#ureFż~rH[S/0xÙVLٰ{nVjUX]+=\bJ=YvWeZxtrtŝz#x7a5 `*UNKkt2/*w<8w_Sx8̄ytSF\)S yHx~qxeɁѐ0t wjQ$âis,D:r{ <4EWJ8 R D -D9:tJAp^n*s \ۂKCD0Q(%–ɈAlj8 @ ABnHhwZGzH(obG3e+(& : y8NB4bcaSD!\.E#pg T!0=2TOP^l%94? @.ĖEPlS{z9$Mqs:"V1ê=nzTM /Rɑ8KQ WFyne*7Z"氡aP7K'`oCT`P(_Iĥ[l#^B Tɥ25ed^"U#L 4D\p 7Y# IktQ'*IrmpL(%,v,Gmśg;\DM%Wȇ^Iߑ $C<јA{B]uV8+˜ɾl-$"<Kk0 =^t$N(TvBW0*#J%pȻ\ҵp@Df:iyYWttH j!/dCajjR]Cz[RNU.98++ZEKǔW^QYU=~I<^7c?Bma׳;:g3!<<Hw2AG djKj bEHH7 <8F x%B崆i:gLZ4'm6Y;2!uaLGSF!1¦#n!)NL}+5zy"z} w ̚[]}mXMezc1D CwvZU'#RH'!(;"\afWbc2P=xL&c :cƤ k"> 6W?Üi ʡÄWTh V9iqh.R'U3@ PܜK*d[iFݔ8\+Ys IlquqEXЎz@.pζTDNAsA` "\i&4-e|RL3q0S`q ,ɠA( xL>4^φ't@]e$<] :QjBTDCD\ $QT"{` ++BQbcG\lEP%yiV U,q˿l .0 Jl#@ixchn`N0Hs ,*s9\+d)QIp3%D51(g,򊗰 K2Ych%:ϼ,`q{ |Q2 $T.@pH\2d  Mԉ*vAx#9Uw7ysH@l_dcW!qNbY4%cPRޜ |ZT.ER6:Ž/JƨBT%A!D됨64XlKٜST JܽP W9Y@r"+qMTVԦ^5^J]̍ id$ǡe|SNz$s_uc;:ɢ=VZG2oAHVפ2rD]M %Ma 2LQm~ {I萔tIOpͤcOz 9_;lY1& ) @vIolBCVdlY*s%l@@c73" GA C#Nc]qe.OIܠĢ~r]iz%)8 ]jd׊$*z@8"K0Ht#U4CvK?/˹Ȓ"3A5s4Z*_X5j4XlILotA),[|Z!TJnXI$@juzf=LANH;ѠFc8GF#rT.EqMhK0)h!$KgnvLl/f@ QʑPcF%ǴD %I[O.jBn4.S4<3uQJai`I*5lLz RW ;4e2wL'!|x4UӐ@|Ignn!9VJ O ϾSSqUlKzsYVHU>v-,#M +f&% ! s/W% 2e [}8ǔl%*ٮ ˢԍ[e?R&.䓱3 5#@bZ {}"0.* &O֫`iF6@Bi1hkR@1jm"k aWQ`0!ZMO^aҁ BX-1 vūbdtbb^$WK IO/7~zNw_IJ}J:A"ļZc_m'F&Zhy)HaV~Th v1;P|5 UUvTpG DM,=`F Sjc+lMJdcrwB5DA*ʨ xW1)ԫd{mU5DbYO/t@@-XN݃h (a/s#ӥjEӁY9N2VOxY;FK6B5T#-{|i 48aK_3Hk[N&ȞlPYVZ[@p3$I yP3y$=HQ#qiiy*h):r"۝ 64TF6CCLw=4"lZI t#lZ!@+Ofڭ^bΜE;p,-)(Uң!p//>ݏVtLk ;)n*lpKr4@BS$u98B,']67: R>^O8(dDDLi(礖j[ssBj osȣ'h@J@<9SѺ6flBh' h}ؿ8FNB6`*`6N_:.S>Ũ3q,)RATJJ#_HjrN/0R$#>@Pgs&QQqO$DD1~I(Z'ׯ``q8T)с636L)g;C/vwD 鏂D|Nْ?ܲEK[%&[CB~5R&l: 2lLg冺\5`/Y d XmDT888U=bld{$Hh 3B{P\و񤺓)fSN_L IPA#$I[ >?to,٬Y};[ٺ æ\1{xh N1`:!O߄h#(Abelo0Dj͢%GjJ%8cHTIKKWX0z7?7_ _l19!@@F(̃;SI]ҁ:$}|Z5ʞk fl0#2Ѽ%X9`/b4476MX4n"^t@ԁ  $~_bM|:85Y6U$sA@ΰ>iLgQj2A͇/@j׬"P6*5HI͡<y@L1ko&Dm%72ek.0H*KjWՎː֔# "!C zX mBF K!("4mʕ,iôs tIѽr?+d&xljśhXC F-v efFsJq{f~7ћ٢⁁/,uvSarJutcK4U.iX$2Bh U XhK2RGZېiM9,/%[̆Y$B~IOqg(!ӎ*Y9L4B ,XD?XdTDvĴ4'>f'X]TW<%HijZ-Y]bzb9.Hں.Q/Ė9As Qޞ<)<˝EՐW:'XLvIcOmKV+EC2 QY@_`ŏ !Z@ѣ7ҿ f 5>Js(+{\ucYYٸk&76uM>׎`㢑i2 n"pA4mQ>܉Yk'WVUזjA҅Q\ 5j٢tB=ec\掼}X"_br@}{K]:m dy?dg!icU8Um=5Ȟk\OI}8%⚲:"(i؝<|l"DLpBP [I<949+5UN(M=}x&=ɥ|"IdT%-j,F' 4F @[NAoi'o4PW\ AĆԕU84Dpm JT)a'&*+kΒ-T<$<\T 4/u5HJ`aH=p<ڠi)3UO[OKXƚ043 CazcЊK<)#Fhejĕ[OJ hZ^Ĉ" TTS#cl%^eRԨrI 06`|^YU"i J*9I 82 ^{ChZ(@X"TC͹E540=k:k&sz|"8T @`TH2d0t%~&}i;#mu/ޑT%DJ28dF,p4ne1-v.Նf?u0E9iPUWe-trEUɹ(SU[1QѲ2'WIAp즰L6Gjˊ7t5njA^ؙSs'RÂj^̈́JwZ&9Caߚm$࿹/kOק;jn& 0+=<@xx-pH>*s]摨%ރ:2CQ7j1.N-@dTpX} JJ 71YTM%XU 1`z4Z5-JMDZW@cb (Tm?N _a3WjIƔq]L{[׬p[ew=ӵ_; ފ̔=o߻pA#9UVԑg&__9r[26ofmg<"W}5f}+gF_9%/V_a9|ev7j|FoO/vNkêA 퓝ztMn/ǟ0CGyv-:= xv/П/y7s?|v ]ziO7r^!i_}m6[n6 x|鷏{WOYQK+iw/_uk_ݠHƯ>nXK^6*vpu)Ww}>޳hVL9w~3_6O\;gM/"S?=^Y[>e~cxW /}H[O}ݟ ;=;I\W覬Z?moufnh۾w /g;qOwN:xGK5 ?bYב?mքg&xxە֞?궶3OjG G`{ (fl&d,m`6d͔j ZS@겑! OBbQ.Ў.F bV-Zӱk ":O8*PUB(Ch f\gvmJMq95X,*"S*# % 1<%5h5-b,W)n`PC=V k9JIzfa?"cb1OET iu! +9H~Tpm7>t>6XΤJ29&(\ ϱPEBa @p8= gyV-X%@ sba+fd#tl'C wfdii9f$4[V=>͒iKH|IJ9Vk|bOGFP6nK$-ז/7^͜k>Zo68"7 D4Ι@ QTV5g8EN4ǖ]̶[rYřfssLՠ:Dr9CU}GG|@qDxl}A?Sg~OzFcP VqI]yBː>I?t){䏋yl?+ŋFpWn8ӛdLj j3窞s߾۪-?:oIK0Uڷ|2SzeOFə!J m /4Q{7kueΛsrzόWsˇm{%)s xve:> >㎴ՑvیSք>??.1O J_e惷h+F{s]-ןe/ٱpSo2n_9vWW|kҕX}C[rZqʵE:3}O y캎W^9 =x}t%͏=jao.|gƖIw]VY<ޭwس]⸡~}\yyM:+xjJӺ}-[O {NI65opwo8wne0n 8S3?j=~|W;ms}#`{ޘvhg /iwq?sNڹ8nmHϿu[UٿfWCS֎̘nj!{KKf绮fM9 O/ ?fw7N?-Nr಴3l/eŻ\b ^&\u) O9|=ekڷgOXʒonKu鳯=5ڋW**ߧTJT*;J8*e1QK%@Z-bm2g=vB A}Z1r 'O ԧN ٓȖYr6Dm%QCT dM,kM̗eM ؓh$iYiٶĶd{dFS\rfggٲRkؒ5n5r #^c[4>B3ϱR`iMwxH瀆ED JF%65JJ!I;"ͭh*j^ZVt!j2q:<[_jF.+;zNGJs7Hi{z|IΥx-7>JB_OsѦaO +W},=VRO&%ZQ$H*W}Ip J': #HVKigp-YTHHɊMv;,M:/-(+,fnDc=>N'aa>Ø!{vdׂ%Oٿk]qBۂk$ vC0m/SSS3h3ͼ&;o&"Hvjm٠6ؠ9P}YmmuQ3]צ`>-IjgBm=vZ5|HNUMwcyS; `{sU\G\?Y仁l3I&@H4$dc2af&3IfHFTsSꆶUQ WjU>GhU*ힻM&@)ýFZWD0EE@+^|vvh*‹ ݦnY` 'h:d2듐߶ro9"CYzEdVˡ֯'`i/L )Ia#NgLIYNwݶw2_7Sn++YHdS$dg`2Q9+]SE9):'Q_dˏ2ȯDER g7ͣ0PM%(22^fQrWTU >ql2]|[ ^T˯a ~;)u6%o薥2^IQ3J 䠊1Z4""*p%ȵ.0G].l c5HYmÈKM 3)|n8wi2KŨ$G-HS#-:^c1\HX'7C;.2HTO&j@z@ǞR\%3YJ4L$1S(hfprۃOyN]4 l#(,0s=d 3fSٰm&(OKY lMh{A$e(D e f;-w AE9Va1a9VeZZbg)\#CX]LV3w0H18N\. "J8#HZ2JDh?HyWK穝|27րk$<az6d&&П% 4{Egɏe; K`fv]@~bvօnԏ9.9*A t "OIa{ xg$ u 3o8>aS?w[u}"8Q]PXH .la )M(^/CL&`WugCh7rYAV<ޞ(4j@5Z>^vCp7Hw#sD" "~a>ꉂ};C"F@bV:RU\!2J\V$X[ՓЂin6PWx 9 [|6Mā@) P %C7zҳyI& Dl)l8L+yx:]W x $H˗&*d x'i@ D5}UMs뺓K Scf)^ڈѝGWQh׌ʆZ VVr4/Lr9M0}hvZ/wʥ0Xm󏵨ee0gt%sqqs6(k{à Txk2͑|tH BIxv,i0D`Xgyl. V`CX[A1J[5juqd&I.hhՍ-V~cWbg'N5p6RJr!5|$I@t-(BGi"?30r1X - b` ;yX"a5׵3a8ƍa_ƨ̭?DDT40!ƒ֦Ԉ8JiZ_+w |` Hр(* ^&0]T&&C]NP)c=AJKYn\{كOc 5b/ w!9RB AI*cFub hi9zEոm`C}ߏqTS Uҡyg)R[GB BiNH/&_6Aދ B^ 0_LMiYA,m<,0"$pM" N36.^vכ3Fj:5l!%9ᒔXO߀mz.uRkLCsZsrb >ual[9q^,n)LؚȢ"]آ]D*Yj Ӊ$5LtiE P2 iT3lyM2jh =tk"6jhs P4))hsLfqC _ K܅8Sx3w`WهCrZzsZߢf@o EvT&(<7D|Z PiQ"H_ E ;A|9I[kMnx na*'oz[Ƃ\F4C"8̰ +}RsU#/W$у -paS+Aߊz1`@']͂ Z穮 6N9 fseziߢrWW5"vHMHF׋N#ۮTVщ%X8Z!kj +iyGi|QZ46 Ya5 ^ [9֡?`ڑMje + {&S18@,A}`.< _AؔНKWtV;嘰y=_Y# 8Xgc\)ȜâeϩF5Øt\ZKKnIza_Ca%|>[)':x\ t2zLH@Fjf臙kLt8<>}4Y W1ư#z6 xkWjbfMLfR:ȃp  2=~ӟK#&GWl'a%؁c/~ُ9#Dc aQH1椊e6e%sjBZBw[5Gp ַXIuAV(7b )t,Dq[_ƈdzՂmPN` "%qR&%O{٦uաT"D65#|.oc \RK^Ψ ;OlMMlho:l P?bh<䦀BOKjh U^oAہ3 <"֦Վ/"i6')b!9֪=^ ^ʾDrn38(XJY z*j1 E9p,VtD)'(56ĭ_$&uL6)&DR!l2@@`ʀ\?aV}!}\4Euu[~?%P[9]\d쿇ϘƪjE1ݵnGVpU/ίDWLr۸{kwvT:ۮ_yg^ę&~zǭSW]ӯ\]i]ѻՅmO1f_]?~;fz9GEK?ɏ_9oN8ȫIf?#?zu_|{7nYɲ#'~zkӽٹ{˦G ٧BmGםr$7[l?jU/cOzfuʺH]\틯|=o)'v.YVuھK)2/0fmiwm '}r߃Ui|UU?oݺ W_]YuOv]saY9v?aYǞJ,qΏ_n}7[Ώw ~9 钂%O|ិFQW3^Y5;^sޛ?gusNykۖţ*mر^Xiw:onԞ1[y׿0دF}[[OwGE=7D?[-[}z\>(ۻhw\wor鞻=;'s|K]oZ1j3Fn̯~ywﵔ>m̜a?nuVWM<ꃭy>Qx55O-|継s_[Ƚyn1~q{O_f߽ӣ׏ZuKϩ}{c}^y/x䲮ӛϬڹDeUo]XvŹ(`V^{؜kG|7_H!9ۛe^4lͼS= wFf/򔴭8h|{ܙ[=iuKw=.{,xJ~=ދY0toW&MGNx˲۷z˄oޟ~zϿ=vYkcʳ=3?-OY=xqk~ޏ7-xe7 wg^*qɼT]Peϊ4@>QnSpD^G*Y*B R1MꨢAP3/8)?x3 wuI8ƅ _ "bW0PGª4w,i-tu맆 1*ocCPKnK p74Ir%S/޺ 1mOpL ?;z?{T5W@BM#$t  I !Pl ;...?soB_>d̙33ݙ3-+-",7чqDhEd 6A8M0@-2:#m$[Y;3&~`0(QPE;6qcBSL? L 񅉒&8-[@b:=庘lAxnhJ< 5{~=>҃ċỦyi,nZ,:?‎y)x(TKk+ؤ48PbbG؀ϰG*X{|7x bRB&0xA32 4<)l1Ri LH D5D!R(栆$U(夘 %xY;~`c3D@"@";r7I=[[h `FZdi+J"EYGr=7YWLdDLd"'vGh׈NB`[G,lvz $<7'A,2݆D![(EM 0~iM,@bP @nLVˎbb~Tq=<| z6βz7.j$DeH.'A$U23k]9xbH`C)_*981R샀psFO֎Ħ_ڎe, ?6 _H!'Cd4уr6j=$w#g$:(PHxP wjߨ51nT)b^iDY eP^faA,BLS*mTztr=U̒FSg\fIV.R5ZQ&Cc1j0E4*! Dfݜ`;5p"3)NL5͙J93tKkзbX.N rO@W0iҲ`a?Kw~#(#bhA,mFPDa$(tps.b.p8Ldd=Lf 6/v2$-T3,wIWB}e'nB.Es^HEX8%R;)w75P;ʠ:z/E:Ur5[$UيyMhM"̝ k^=N]|a93|o?o/_&A|e7il2{-R %ؘIfHYO  N'#< m27YШ>SfZ$Q3Ē *q0쀁\Q@$!Ȁ=B6قD6C|- xu|RxgzHhz-zNdޮ /h DŘ?F{2"X exG;)gשbv &Vv3Gx(CV8'i T5C=@6 /qcDA"NEB H9<+/Čyȵz"N5Aa{+`#vv!Дpy6rc2xx ax ᾡ8_>8Jn.ɤ;tPq#%Üqq4n%12F]bx MЋJ{J87z{{`XhTC#j>v JLhƉKI B?W$<}Y ww|Ppp`(NZIj%V 4ӏzAI8\Nq?}C}ѸHvz*|b.T1z(qj # Fk.̅HO`|oQ8h}08Ь,0h#0(+51!cw}3kɞ)` >z=Yy \6fy'pA{lN MYsEVB@{8\fP&e(UӇЄ"8xGIx9Ɩ o2$QSB :X@6V<<`^.X@\B$]~WA B}5bO x5:hc-1qŢN caFB!x)<.:80@2{J`y2.ݔ"$3W3!xIzǾC\"ADć* E6fn,P^oI؅:+Y|#29r ` bH>J NЫbW ɐ~QI?RLe ^ɖp}(p(yR{ <1%5'9xfb2"ƴh䁢1$q k0P79b> ؀"fhYvs>Pⲻ:6849f?- x8訃(kz׼fπZClR͇(qW$rmQ\,eu6RH-,h?vƉX>Yqr_D3wt vqVLsk<7[oǍ⎪*;_nwtڪ|җgXV~k\Ԍ^V^dZN݇*=GҴ>~ۏIgצFeah'/~t$}sپ$jLՊn]q]Nyړ ۼE޿q~mC޳EeVb*ϻ~.܍67E8OC^mt+f5>~}oC5/m ۱<.oOWq}C~g괔)~ =@Tat$!ݞ5HTe=iIOww9Ic[Qt GDsG=W-}yZPQc;3c[FMyJٰamD5YxȠ'15~x1FM4z:[ǎq{{ˉOeyN uےzZNܻ=n]oA|[ȹf۱YgA{(soާRBƠܺõ {q7tYœ(Ⱦ|-S ]|tfVF-]ghFun?f QWvZ!fN(B:\Oۄ[էvG7/eJڟ7(LxCW>g],zy( ʼ#<Zy]uՌ{IcϪտEuOwx>pεƛlk\c?7a'6B)nց%i~Ƀ{]un{πZsN.`,]\JJIjNY WSYw~͆c9o۩ kvj񓔒U::8Zqm.ӓJ:Y]tZ7qX~%<]jI!k_²l4ս_o ML7XxǶ mc@ߞz 9wvA#oirޥݢK:΋ӲܼYw"O:8yĺΟT׳q_u-tm4[í6;,yh{eB.3grL66eAiu+i 8=]mQRv[ٜeY}XyM+}㚞Nܚ͹K:nft=[9ʿx-f/opxڳ8 kVke$ f> a]֤vf1F<¤3DEIͪ##9GЛ5/T(1qfYtZ@3>M%cd%yPk!Zsl}%K=5C~|Q{=*?8j#Qs'ݫj4S.yi=u/ooOu*.*rHA鄕=(|awq]njr*9} ys'w >ro_FW9C.^ڗyR1v IζT/<8z[us )Ě,'@w'֎9bRgAC7>T8g{mҗ t^P֩lIU-jk5Wa!Ůʈ+iɈ4"׹ŗk^]TrM7 f^"ӷW ']|Q)]=P%˳g_,rM!GOH'>i8Ӽ^:_V;~mpTֆx^!n\W r`Gv\`Q}=D̮^ƧkfQۛ>os^3"[OOST׮{\a 2]uv;1߬yU&ŗyħm٩ވcݥjTr{FxgiNݨk2h;a&k rdQ*𰹯l\hqw6\9.|k2Ϗu횎 nL06ع՝oJjޛhe¬ 6^_ui9{3͈6~ٷFy4a[F}53vVdz$t|KF~"Ҏ]2{ѫb Wn~I݃Oފ|9GLcA C`Z(%=sɾډ'B&<23,2pOO6({:]ͽsbs\gh'gkgn<`r3._܋>!"8j_8C+Ku/pul,7Rw}c#O]VWyL XvVs-ɵw8Ϟ 8EZcW64хWMyjX0pl+ktt֧W&I (bd CbF3"]D()MK3N: X@{=P84YY:LM6!WS G |iХ$K(I]CU&YNlxƍK^ǭ5NL*@5^wq:u%.;zAJi Lۅ9mg! _usFJ3mu79np3fvdsž%-c{Ѝz-k`] p4M-ⶣp=)8medB"~w'OvY}"G y_S|Iϼd'ö[e7=XЍ6O[n-#UA۫>s WTΟ8umAui;.*μVqbyI̞noN%>y>8**i!ʞec/K(U횲xdI_U/=&!:!I}ޟb=>qK>Fx™wbí;&>/CGrG^IA%Kfv;r>0CDɧNw Zaz>wcۊI+ǘX߼Sdvu^ʰfk9lMw.;8@Է}F#=E;].Lb;8]!cmPS+.ܛ1tWWLǸ ;kыخUWGK9׉tOY#:viS=j%I *#E=FZz5xKRU\'Ӻۢ[}WMIa8W0ytoT|H1#q50.y);þ{fyF܁0ߙuwK6TiaF~]pۺgR(]uF8Rg΢ӘZYWO.w&bfO6-A{Z ߞMYJI(=ʼnzy1k}3:7CxJx~RQ{ؽ${v8vލMK"7=r~v(QO?0orN {TmmZXZPZZ8gƵ9 T<;ݶ񏽘V{T>thUob>?V3;:̈:$6;^ N; i7qVU=d13ͬqit!s>3#whj~M>H<9LIGn_ּ .T7/}Sֆ]^ҐUׇYA+Hս2hw:}+96u|iV|מ)ghg:2ՂJcNq3e;|ZZ374&ΔHpeyM0SSsZw?/m+qnētKP% K7I%Z~\ݓ? 4&v>en8Ae)wx a3Z%|$8oPQv3+Ny)|NܨyEi$zUB~n}.D^P2eʘБC9N^u"⬇wݧkO|uvW'" ;o(W5TK!j>/:&{nseFϼy7o'SM6-ʬ~(=.e&XyؕmlTM7UN}|i:{: {40ڧO+Uio4ݦ ?1)uS+蕆Cv/8ٱ~n3NY>[Xն񏉽ӉfdkyNOm]ebӄ-˜1?݄Sys@v%&f34|8na/moW=e^kunݣif5_k>Zkmȷ [ün˶ݿt_$.3?|L)Z4NyD{YTSFl[\zi3{=8~aṨ?θ]̂u8]y-[;qHQ[U'wt!ncw/JUӅS?m YP)dK*czcli;Z5yHݜx_cH#Glo2sFg-zArW*@d)? )te΂;TGSq4_sMSxx[qlgV?xL&F B:_pLN!>&iH&aa͵(1O Kɩ>&?/\S19'T X74 Rbšc4AT*YjEQ>[*Sid2j+VedJ+Y26j+GTKr<$rY#{y ;43X)# 5Ba0)LKk&ne-i/r' %<;翐"`]ȚF ]lO듇2ޑi/g-C:Oď p&13,>lpӣ?{ Yc;1@ZO!4)v'3űK Qe;jc.Xw_(R93ނ=ϼω(VtbͣC m}E{߫*'X%m٤J ۱aoCJzv6yg]usfQѡWrJ$crnEL?zNʼncf/Qw}pͲu]^8r_4.q >ĺ  75lwCΫfgv˲N[rvdu+{LxY8yeVZھ#FSۅ.4q.ǤvzNkwyRPY8žU:n"i^|f9?^nz#co =ǘ72nR{gňTNѠRxaJAM8e~qeR!*}wvEG8yŊ|{˓NSBz]}55l0`K}3&8\mM ;go}ă+ ]-k6dU^sv>|p?ި7;ZnSUfT2X/\Ս+\oQn>svz 6mb.$bZKunKTe~NUӝ9yWJjކGӴ2؄5s^Vϛt0iThgGd| I@$f̧g\+P[~mKlxuyaw=՟Guwڝ_[XSeOܸvK&8TloW<;#?j8<{rƛoz:O7xŖ)_twWKmobms!~L7tc=*W5M[tzZ ~[ ]g3Əm;H7N}-k)g7(6HNx\2l ~foÛm?3U}Gz{|6?q'lER hmAk%x5] ҊC-im)JH^dkR+eʛh I fwPV 'ʉƴrrfXIc::8|F@V;)qa$FvF^Tx҂jIѤI|n`&ZX0:G"pc%<)xrJEQǍQ*7OǗHda4bLB",1l'ō>~"e6 ~*LE AJa-Y,j:A05/ LG",C1B ER>/8b4F$,"4-#h52Z1z%xZąCIgE(ăF`0 ‰MGFhg/łoqleɵXkT=Ki8rt`$\qXbB> gXDx-:;XM;Uٸ^zTl8{9#Tzu &7ȗ4b$6vT vIq)mғrn-Z y`QyR"_FK7`2}Z9Dk-@c7k|טWYkzһ(_P8h+`EP.cQ4 a&d2! 9hi8bP趙3 %hl̈l2[mĉHj;6+f"i&aY[MEˠpK``6hYdYh 2$g=dA;jtd8|V9Cζ90S @80XY[SD^ޡ)1=|enL(/Ƨ6GN0C8.`PB+J>b/E26R X)ͰX~?dQTJ B`~49_g :d)Cf}Ì֭4 (΃~;27PkeL3ѝi$,3A-0 pɜTb!ѴTpˁia ! &V(JV,@r,M%  bZ"RrHP(i' h}eS(:6YZŖ'k@IMqT 0!ljۜK4Yո2ޑ'Aux0X1eGl0:PX˕@Bmd*DD ‚Jm+?.R(@|NLEL7 vtrsfϾnLOV1gV~6N֥ ,>' p$ԏh+P#S bYn%T0lS"DLoȋ˷А8B̚Eqbf8q-#v MȳPda( Kqyz&YF4/1fKe0EaBl )?~Gh{[Fff2y7 L3@/ӂB}J{=K@x9\L`9 qb=S@P`.3X6k$^ PP3 p[XCn|re2>7Og߿AN1 #~{R}ZTYQ Hq,Y]L-ۊS3 `NWcM.`UTS6r6-=_|Ȫ_Ū)m( K2cyLId5XJgwvӖ3kI9cISia2LBg;?}AtJ I }ql~3u'!<$£dK'8%gGx\k[/$3饜%s:KYG@xhH?睐\2[5N"aEp |r 5e.5ݪ[-k$Z9!P>/aGz\Lcjt~W+*5֟ ^}m>$&~u `iRУhslQ7|gcekFp.a{q j bok(?-UiTU:VńT6(HܧW#ʧ?5d'/*;:mTDM?u;TMkӄFnZn3?c_i~/W**a^=KEQp%iot,]A_ȭT˙jڥ]@K2~{x rirR¦&-P.dxėBcQkN$uەg+e7nΩ kMmckhQ[uۑ?8| WV'(1ןUqngR5a͕"¨DJ~-:]pqF1\!EEݕa&el@3fxjX20{lk=Kk}ۦi_bf*]GܒDxu ;`"qWt]ւrt&UɚB s0?,+EBąߺ2hl U=uVnd~,JYYtQ8];OUi~I$\?6t"Nl56>nM,-Qt hP%X.c`B,u 9nj'[%Zdn2QN#m!z{k66̖e6jJ{Yfdʾ⠊5"«w#u½z7]Gm>ufaϮ|OA b-n7[KqqrxzQX1¡w> bGnEd*y`Y=E90ܯ$dpKd:@lDLh#*fB74^YFOy"E.XAm iK5j+ʈ2vaN~hvv嬠&](p$YN<߈u)PSegi܄#pZKK-7;2p+jm%񛃙T֦=F҃lxq#bce.E˱OEz3fȋa#tq1Kr9^oqr1"Ti ǰSd@F@k&{=8:]s#?V͝N,mÚlv 5Ϝ؀r.%rޑ0HmKl6͊YXeBӕ[-y-a+6YG`bN춾*&uYk[B1M&mLCXN)O-5:+ 08*5G]gQ$b,Q*W~q.SUjDMw\'Žwkb-tmmn:_ [4{u,|73Nں,j8hH(8 d!*Qhuj BPp@Q VmH*@u?fK.K"넔eIS|J@tVy:$P>q11Oot9_Lrcǿ Pxg5/'x#%I4[fFHpBZU,zfJ8"a&lJ2h@0ԌyҦjRu_9uǾɏ-<^3ill.63%MG½1Wנ>s2ݭw` b3ho”|>02Wj5`hM3D ] Bs,s%if@E^4+ U3Xat.Ì 6==cj>ՠ A6)d (9u+*U}϶=[p3{*&X'[{#; +Z`-f<|Z_atcN=Ĉd )8sh"剪8>|nb G?ˆy4( b+diO:<&sn$1)s\ooE0!v~~[\\[8{&)"by})C\4pѸ\4!rdn.+M墱pѴӹh|5Rhz;+@Ez\r$[|[{[6SZ4s?X 5y_} qf_f׍!Jߐ|2 e@s?f'ɀPvp:ð) (h~=l p& b$ =%yNL*Dٱ0+pjvoGs&R27FbOmqyGdػ? n?Hs3 "5aeg2gA4@_O5Ѕ2 5< } 6T96TG3P,k?ˌ65t8#MJb<Zb6a<?bo'v& F(M$dh p~é4b}~b _d˜$K(KbdQ_T_rbȳb ϩRmOڭ'_9]treб۫ۆ\Vig;}@ f3.6GRJ3<>s?sL/ VMk_8t}jaA\mهQY}nK8A*lʎP8& 7>g؊3¶Z&9Ϻjvge5g;J=~g5B0rg0 y&Y09k64֒"gi?ghyZjnn4\^3i嚻揇1_-9۲{bi #x<{=?h8糎IKMRU$L8MRX09#ݾ?TGWx ˣ/acޣDqK"VbiU0yzU>Bv?e_DiP 7E^ LbS2مINf{vcע:.ƜHx^Wƚ8n)*7.gXf.=RHZyz'r@.0 Q U^|5(2(@%[? ynt/8?.Gxi"`p['Ƀ- (d׽뷩tL3;!Q~JDH4]Luّ">Ze'63ZEߌFéI] g@$;vi[kMNgÓC[oAn%αOʓe7`k nSg-Y"\avo)L["/pbG~"6$ F hƔE -j&ǖ7"Ҥ_EI{.@tWTf cA]3"2>`QҊDjRXTr2knؿ/!7BR7ko]B&CSQ݉ ro'U򫵵z>&vUqupa(lͬC/K+1ݵNnrvHqJ@8ג8J!rl|ibI]"z`i]R}86+:ɔ3z8ÃÓÃcp\ʯՅ8|D3猾#CNRβ^2fNLa0솿/]V/iE|53퍳XvǸH9ؙQd?n5W] .#䟀~hpB?HZa}ywcL"GF3 ?xzR~fbI!~VMSy'B4\p/A U@O?nVV|p mB2E\p^qRDjCM\1CH PkNg]ARM˸nfQ٘^7Iz{#jʜ-ciWMݲpW"oY{Jӯrxwrt MlG!rDT5iDV^YgƚJ @' %W SJZ1{pȲKEӇk̘.4eߦYu)afQqxw%8O{s^~ )zPk|IQ'$-?!za= ߑV5FaoM)wG;ALv+bbd(öCz@ i["А 3Jf`T-(Fv WYku4zv] d 5R9z0 D934EƣâFSC%vSK![/6Lh.`kb;$ j}A VEOyb^: O au|*Jr' ҅l;8 %[sU;sQ@)Ra"֠^a jsT6NK}xwx"p<B&*q`¡4fy|?^Rgh_7 pW~T{l%v9mڒX`fM Y9nIH#zIڪ}A"W^so6wJ ̹$u[YL5YS.&:/"sq:c) yUϭn'be@<^DohHLm"6t@ e! ;,Tp 1pwmx5w0l8O2- vd?U߼ mк fSjE-tsׅt i՗.6N t`^!CM'Ί Je6BhFq&95-PYMdu|-,f'3w\4NGGFnQ.|tx}}almC*뒵 şpܒ &EņAB;z--pJyKQ-L,"7^ #Ңcp4hbP*Z`0&o`l`vK sj) |xF 2<T/fYi PedfՙEב_G&Lam y,ڸy1ZNc|g7!UφDrM͘ ,8-&**(eGIL@&sYZe.1N:Mȏ 6>pjQ%tʬmỎ|EJ2`oZQͩ4±,;WMU:{A&]섛ҸuDocϺ)ܠ0/l$fu>dHZ /MYrDv%O vnW;2q 6Sɏ5˨ћY|/Vņn'j'ݏ|FIl@jRNl D·{d?ݣ7{{ htX"4{mZ5ql lx$+5TE 6k7#k@R>\cUm0^v[kevj@ZԸ돼ۍJr{U<$+Z'E =Q%Ɏ\0]ፀyi(xj,{yTIwo(z} NeQ 1<%Frd]eDc/bW Q *"nqW&p0M(j5*]׿P%b{^pؤ59l)@Hl0+`kʥ )]tIHHự{&:ݙ;''[{@mB{B^$p0F2ov3JN,tYtT5^HT埈>엱?=ڳ?[Yzjg'n\]~{py$v FߺꮖS@g"Ĥஂɠs60  VƓZ{[dFhYpF\ Mfw|j񫡇+FExDc}׀ `9 $ʗ!蠁Qje*]HwT3DKQ;'y[;)K^inA!Of3 {0dL:΄iT7`4|:XY'(& +2ةCitӱU Kq&o-OI=6kko+|oϛS_[͋7~[;9Y6#mpN~^E4mĐQOGFևAs/׎qR*5z_.qή7:e  #A\;6 l˪'Ԗ.W],/;-(;#To`70 8tNFxd web MZ)e@-$ԈaoG,5=M oFUdUYRIU>t$;xr]|/s]xp۩ ?OVx!?E0Hs>% m kyrFp0.uIcyxŘcfrMٷ"cێ3 &(v6t}@DuZhʎl>+vT-WgІ }< hz~KrU{LK @4r\84۰ۆ>iT8=GX ;۪pLmk;7-:7׾ .ݏ1X|VN6dzH;5>^ G9,~SG"7ִMGQg {dVjEE׏n`&a:<9>iU^zRփ[ls㫭ݽyyn[893aK$XI`UjRQ7Il rxl WFWa* olu Efр;C52(C =Mqw~VNYWo'N >>Vfoh,ǨFi$M'GL6ƻ~ɨm scnc w^il2QC2 [D8w[$^2İ<{;VҼ[؇aX Sjì=zz=3lOsJ9+ᛓAh4 jˋ\T$'I$0R"E-V*;8{®ae>#EJ\ y_6"<.r*28&]j=a/V[+o!ap˻6aV } !260إPpܳ>iℹO8$o82S>9yRM 3=u80oQ&; N5"'$!:;;bkqNa7'?^+ø!#{߰8 6.I}i_o}Kƾ~,KCv7YR:m4G?!WC1#^{rR9Aʺad'^;|3yoO'Gŵ+H͵>pI]3k ^(>qk(_:"Jab I2)YE"Ogm~m\G#{tf=_ KP "}gI $% 7$9G#O25JK/7RJfL:\\昨q'yeTl:I:>:82|˸Wpz{N֫ݓFC:<[hdfoX9>:l}9G0݈LԝIFpԭ 0fT|,/=a$"Ar P_zַÛ~7Jy= 6onnjWq-^ AmFWnz6ࠖI&!F#hV߅5n8UЁѼ"^EǖXY[]]>EšYţÀ'9| ߏwwq2`:07pG2kjSNIQ9RX_x(wZ7*®jWfDq8З}Y;zs~{BavNH_*CV 1ƨu *p&tG(#wG &A7}͕ZB^YJVX"CIIqQ9–Q{SaS8Câ# Q K(I)bYXuR fM)܀l?o4-Yeht `Ra{֘#洚Sqy #Rfz!+9 #,֦qD}䅸KCPq9nyd+~PV=H,ZV`0k|U&<[9rٲhSPߩcN;<'U_֖"G5k"g-֫ݽ7'{7׋hޝJ=7m$;ח^{Z*Ɏ2GD+CtdB|k q3`~o=1c &%z/Y;}} bƤI:E2s{W:seeh:6+ZHՓjOwlp aV_6ݾFXש|?[iq#cNmwvs]so >CۑLh~O G@|G5|9m[߆CZ*GIe69Giɛ㝬0;{[ۓ orzXl8{-`w.\=٩0$ Gj&nȀCe`GlU9JM9{6Ša8r 1g*t-cDE?( 4v˂ж881cW+2^y=zw\6?r|J7>% w$e+o !^uAXR 8IQȫV~S?i"U~C0 db9&蚌vt(]/|lwpeSu16QMdϊE#٭zlja*`nxyX Ǝ F2{FM,ar Ƕ$x <3xlJ6 xf;oo#&L0&)5u=)WʌRbqoXGaXv*֠b6\wp?nvbD!t^zQ%ϓ TCC8sz,*! [-8/&rq=Cv.%7=zmq[|iAR_ R͈ ZQB4~(a[ ~^_807|iJhp`fϻ ZK/yl\4-TtMAo03TJ\c搴sttx|\iRY lRdy" #,:nh~C $a L<9G6Ufv$A9!yNXdD>mo@!dH7Z# U \)wxcP[Eo`hIa aE!mm.:E IvMvsv̺✡-~R"px]֛wD"]tZt}n֩tf`~a0ͼxY`/?leE,j{R$@D/."PyPDi:E*],AuwUhM/I;>h j_YK1Oa9iޅ-AeȩVVURLȱD(pȀ1JT'zwT`x;[!6g|Hrz>,g)gҞǞ̚#Ϝ55kP~ ښCU8{mOwcBӔvqV$LJ- L9a䢑_yRæEI1p'Y$lUUnC)D@sȼ UI*q5.ވҩ,cQ:ONL{ /U/ $s0LJ'!x_;hYNeg)rAYkBvFȧ;&v J3Jݡ_Bz׾ٮ #%ht1CJaFWxlC}IyeVfĥ-u-|p}As@\Օgg'd h<0l{gN^^9 e_&FwK鴱suurx|N8hZ^}4p~pUY/]6޼Of3:0Ly sH%6X)F{bDJzN%v +"$l ZsdbE]_hoDԭQԪ pSݞkVu|~~G!GoBjVJZrϘ `r%~gyxJifDW`Ͱ6 wӸD2Ն3:T>NM\ZDvsX^F5~ο{xoCF=Ũiܐ6.AV6q|  "b#li*] 3" =ʬ Ph/ b6Y,BGIDQ'$h)(Hx&m!T!"uH,۝$\fXod%_Ry/Sa f/#ψ糶ў |2.7@$'.+O&l\.v;t QsRiPQ:'J,ڰL6?^z0d+# 6dh- VNI+a%@7P2 (6 p "RbH;n%2 =Gvaj! b8#jCWWR>%$ϏfANh J܎PUj T.YQgh BC w*C}sa魉p'J,M_N8d! }BzGOX/g[%KmSahbM/⾝fD)  e]>pwa|kZ.~دuK0ãcJ!khY%B q2IY@i>%=†d$j4C gMb&w7$Ff1 _I80 e(sf1 |l:4II_m/MB~Ry3>7, swS0o]Nof)Iڑtl*E@2C5 5ȠR`bOYՔ}Iݘ+FH`Jyie0@jG ÁM|+xhНY!L6e<"$=:5 ތ5Nqwۤo1mh]iRXRoLesZ SW>FsIiI,bEaeB4)(ںvk(LyNVN|-S:qvR)2G4ԣQ k 6;QF"+hFOa&k}&Ov\F{RLr,!fsSc2?+9Z1m͊SbP}U&Mdr i4mBY'OZܹu˲Fbg_5LY.п<8?IHZZ\.e%\NÅOJ3@^>#zf몱z۲UƸjNZr Up[DG[! ,I-T]r2LVg=ф//pd+@{)U/7/vH߄1T0h-1z, 4>]N\< -3,,e~Q-Yg,[_IQߋҥ$8Z%hcY4)m8C;.)̼?i!v+ӷxmY޿8u 靹a({&}}4E AǖX4uaT X_9xU>i|]?]$dD)» v80ᣌz$gS6 Ffr[)W&z2u;dk4CҌrjȔ)=StN9'|9i\43;JF#_*t6 I RWFvf[F t58tEAvT%v@RJgr46ϑ~!&f<*c麒n@e"+9|7Ù3Owݖi\&"}q/Bg^͋U&VqN(%Xt#`$vӠdF##X1BTQɆiy$EyT*ܫ{2.88&FAК$ qT9ȚhBY) M16.JAb8cRg yD\lΆS :mYC`7e nG%1-4(^=ki, g`;%'5Bg_Y&xGs?;™?Օ)J7wN$WȘl,je"҃ yGGp\FiY! 8@ n1m+W{e.h79.|( !SKMB@K,b xMFW枠7t7t+V7[D3q|jo)dAF>%>MpViTwRkQ( r-! Qhe^NAo)hr8er&fE6\[d"= Eߢ &{EhaY:Os@MZƅϯEXҐ `JINl؍P*{QX-VSo~&eQM^]>^m %I6,V $ -!ege ucbZS^z ّdi~b,MV6TH.,,葖 ^{'_^!5 _xnk[hPjG@|(fJR_yaZL J3s^Ti52lk9#pj̾IOVECL@6Cs{})=hG Ӄo>"-9#:nۻwzFbSZp bY٭NH}nߔFtQ|VvOmk>zź`]_(B|u~*m©=G (2ɴg,Gf``YQYW-`(t$0*lAiC^WQ[fz]\Hkzl|Vv+Pcqp_&4iy%LlUUk 4*L NIrcI\3^Lfgq6?h<ď VHXbz!~ (4 D5ްONf/((AAuRW<ؑC1JxD\'fbєhb&vxy""!%8Б(C(,_ғv&5g-EEɒ? ,#^&{ˇ=uCgieSV.eqID:~{Y3x1M Dr.bx } 7 &@ۍQaC8xtyjm}L=Ptob*}˓ >_E*OGeE4+b+N0 bjÆxږZJ=`%n`:l5\)ƴ c^Ͻn/v$꡸ 0:L&5#VWj[Ax([@UzX0yAQpjV粊lCYF0|X6yeXX/uRj[İnbjʉ2PT}qyRI7K$/0`mm{ѝF1[7^rɞ+ڃn?؃ [@R#Z-0FN0lm}3`nG[4Y m߶K5L!rCA Uv+I4 S mo fl.FQ^xpzwRf/=kG)pbS6C.h5?p+@6{VH $ Ię ߴ\Up¥:+ q Q(HDebz^7]L낚IF\vWsY)`,!_lZ(󌫖>c6,űX]~:+{| {4P(}X>Pzm-.j Ќ>,6[vkVgټfNI;NER)Ԑ%Nڲnb=L'< AjQHL70K'AWHA9 Lڮ\t0^ /,ӿne*j -M(N Dž-s-@>ĮcuZ3(=:5 ҿx1: YqTTM=&{$#H^p+0?qKZLWD1ID=:զ""dh,j78r*cna$i ڥY| ͋75;0fOjC&{p^x^Ei2ťy;h'VJGi Xzct yg{C ͔uJ &?ouea4pK]reX6'ͣ7J)eIޕ(ޕs՜ȌNwMV8 -ab#*+لj+xP(͗ۈSH p0~~ nhة*ڄwPb!rsl^b/rtYu|gx 9[mt>y>1#($jK{~QhxUX y0ˢRĿJщRA ԵϖTYE].{EED-KSB0w\{ǔ!qn*xgv&(]_G -֐H%no4qQUlT|!>۸Ҩj`0:Wà_$ȗ[;sHgJ呉w&q >{3ekJȵ6OEo-,)F74퉄#e2s.JD"tuqeF")!<2B˖tG1S8V~Zko:89}b&B!ۏ6%kk<[]wyVՌ&O&G2*.bg=xU<["&[i|JԵ눿Fg~un.s= 9^& K3.yMjS̛x̄:b{:X,mscwU^33b9Oxe1swDPCɱ E9j HGɵɜI$x,$Ȃ@Xfc}ڬkV` 48|!9T/ jƍYm5X0+zlLH$^_ȢaZ!^Ӣcj\1 \(7>3tr5!`y<h``Kmc")Grolg|L모^*L5 eR8!~H A@ƎŝM?_8%[uS)#kAMpTh6ACHs,`m}w:CT6&qU؝{hKZgJ,WiNk[ǯS¼ [ k q$8Nv⨚( =R=) 6D%ʷS$o{mD$m?DqPk2Aɼ7CzCX ᶿ CB"xڨ/W׃Kf_!_IvpoUa"k2("(چʏ,O͝Jlԕ PHOlZWէkhћwݭ먟?]MAiDCE)g/sxNf3fS5Ȭō5f-c9jN8jSCP1_S}YN+’Vt#V]NE&fu#cqj7ZX a@~ZrVBFOy`6XE}~$ bwΥcnO<^ں^ɶ.=YV:V*Ȋ'i7uɦl:j{|<;ft|o,w@x i,R*oSMo Zѐ5)[Q/<z(FưGP󪯒==!`MĒY':R(Ƙ|R^%v+dEQ"'V@J j.k-&JtВ[Nvjoe[TGPy>}Fw3esNrP0C@2CQ vn#R/G &luM.3^"tp;s'Awj2lG!K E^W##P_%FHKDY.ZEqآ(ȔކȨTmhV3{+&ðDDE4D.4*p"ktb9 2]7r= @:oru&1ߑaz9-1*#upx[kpؐܥe>nw=W@bbC:CS%F#DZyjc[P'EPN-MA(x"wIӒv~!SF] a'Y5e#Ĕ=b1 G: htKI@cuL^\$w#m) ʆ–lD1A`°ӶMMِ;XqTWkDQR/ѵ0`, ~iWooH1㽤UrLO7:U@I}ǔU-`vaR~ YB}[s3QEHEV0KxA'%O fMBz'u @IoŲ?jQV^FmX'TJ@YǪqӫ-.⌘ϿILI(]03,ɋI#K38gB;D<$-eMxx_\\WHJ=+M@*_bM|U D3X2Z|#,F(W[asQ8R=gsD'U04)TSi4_T"Ev䵐X'Tt>>8SkE!N%w]<Δ'C ZɐtSW9NpV1_#Ӳ&_Z#-'b!T!&*|7E.V] 3y!%zjSwcɌએ9E;9o4#`{*. RbP;]z!);jTk/-TQ_cJ)8i;)֢&pI^CcB[+tlSlRMkQ(H\$ǐjb)~9nnェLdv k݃ޛ-}wS9}Щ'G;+QzWՓ7ooT[aQeG1QNrGpXcH㙖ɒ)`ձlq czjWSjW TGU]r2z?9jyXehHn`Mr]^Ev+6e5ÅRx‚Sh7gMQӋPwG%+mO[DQ81Kcr"3Fa&qn08`3p|U^Ν*gFgπ)g8} )^҆euʁ -^҅]oŪ‹xN?Ke2ws}~T}Kd)]%Jo+`c6A~!#̔*?!.Φ<1_.aj\Š `ij((pN]cgǔ'lo,כ^s7qM|dc\]\@aJd`?Ap4Mہ^ 3%H>X>G<]S&=:IGUHtV0]U,aVyEғTF~T9LbKTmw,1nk }Mxh]6uX̛t4SgH6S tRfSvWB}Z&|!᧴ЖiP7fwHl-gu8w&ݗ*ruɑT!""ёv_5fX6`o^+[\SM[Mxr҆7]APZj*Z%cѩΜv_RSLj)efHG*+VK iX noTRxU f0;;*btI`1\$j=Ჭ{ͻӧ~;.F[kQ4j3+Ny'[|$Q S?,QJ)Ufe]5SrJ@\6H9[[x98pE[nUc!N?MvLY17cl-%ڗ8?]"x hKkfl"dlld!jՐLM#2h+UHS@FCxnZ~g漆wӣPj,޺r4C0;A8a})g\v6ΞcU|NO.WW0v81Y)kS֟TiTvS/Ue?WtzUl^';S% $ͩ*^D;V[N03ɍ$jUw[D Z>v9lRVN+4HI|oy[@bP4f}I `A!"]3ѿg/IoY [LҨ;\L+P@ -s< \:hOh ̱d= 7};a"&H2D%4tŚu ~( ♯6 FumU g0۶krZ0>y/$[6\g) HfUq ~$nEoR2g'"H:XStȠݤzs'ylOK+{xuNg;57SN/ـ7I=ߨn6̊Qr<*$+$D(Xٕ7]Y`׋SE(@F…I@+m̵ҵ鈴8,i( PN9<.m*手fJO5Qh)*cJpv9 &fo?{:ޮn oY.xGǓQh4 .ѩ'g4,wE ?4Fvs$G߫w;{ %|}mp(y&ȝa6n ):JGN#QIVi:'a)B#y谦).NYE3T^*DVBzi6Y"[ ;y'wQ8 G'ԯۮ0 K.$6vۜ1Väu Aьi\ -t<w0VJu1H 0g'=z1n;acwN*w06@MZ >lKU~ : \B)}FzXلULQ)5+b\#}p/ٷ<[c (m3cJK0![?ojo˂"PZI02ϰ?t#`U{wM8D ɱuLQt@2^2ڵ6eI̼\bbY,_D#{C;#wAhsQ+*ߛZZE iEXED 9ș;ґQoLp`lTdnm`# թǜrQfmEMInT'YV\.ĺwx)&7S{FTYn17= 'eU̽y{ >76p\tϿLP )BO͕rP>xE^&ݝ2]yb~'Սj.9n6'%<}@Pm?k$}c*a(_]AL\.ݹ\ۧD;Ey$'׿X(wǡ`h^򹀠ؙ\) %w)ŞAvYP@edL>&p$۔ډ3t`ez ٳ_z8MORv,].Dk0F]93c עg@5(8:I+JP| T=.uZ}v[`M+vHͅ-Qʒ ð*H`큎/"jE[6"+3m2P1BEXQNv t v2kS wE(]vyZ[8#uvz 󫦓^l7fi2(p,cMNz+$h7!cYTQܡ,o+Q/64 @69lo3i(gWt^G{xɘ5aD)eGt+M$dF,3r8Ra8$$͙68ur1 :c̉SOmBFAϗ*$j PX`iA [Q > Yckn? J3i{dA7]("_@a.:lˬH/9hbB8.F>c$$`0& M_&Zw>oJ(>f鷳ICY /FO2ՠqÑY[YZ[} Z(/7ǵNrYX]\xu/<#kv#D{rv/uizԥasc{_`/WuoiE7VPdl3Ȇ ٞu+ܶ><+\[Z>+ Y,lXr}!UV۔%vr٠gL0s둬DW_]Ldj]f.ƃDvT곩DC(f 8 3mP<̜ p'p Q'T5[]5?Pt9 .Alri 8dUX5 uHNj.?%C-^)Ux#6\q&6˪r|E˔IwogBvxzvO)3:ÙN/1:GlIxfޞ'R(.,/j %qǐH$úq~%J|<%)FjoMW} .IldQ1pƹ5]P9U]u56%1ٴ*~l=*`);3~ 7H5pVfIzb"vaТ,Q_pCI% p DJȔKDQ /E͖Bl;A2\.^r ۻJr-BDMbpA-257to䌆fh>DMg+(ϧN)POUw˶yL?=bJ1vHZ`lFR4C0&̢F0nnKKdA9}&'"8*]?;E炙S2Ogd\n(`OO)E/֪`l B 9H=wh`8ӉLXi_dD6*兣SZ_{kreKb5حB”:&ǘJ}lt'ȓ$f+]i`@p2aEcJAa=Ec:&mni.F@ Iʹ#abǺ@oi}4S{kRw״?hQ1eRg%pOg|NFD)NZ3R:)OkX!x2rwL>Ez@4yi*׿ RE\]Ʒn/+۪!^Sy}%l4%:.Ak]tc4rZY g q?f_4niQI@l*T+NiuE<~!6/ݨjw޼kLsp{^SEH˸?Uq k?_O|=(fL*`[ڝ vz?94~R_g3om6MY&{l9ָ?8L&8'6ag,#`K"7+y'NPCMܳgj4=+R5@q2GoIPjgF~ň;zU|V FjsU p <3X?t#W8T0ᇆ|CyA`bSSeE$3#$Q*?Ҽj2 r~&֭mclQ :wQVrfe/,8Qb`җW%RV1]Y#zVV=mvCi 0c%xSQXÍijW9wmSv1'=2Vt7!)YҴ\;gsϻjg{[)>U܈,RAdNĔdG-ɩc'PS82Xlsfۚzvs$Q^송OaVze>psT-86@(R_i`q(ݏد2C:pYb:}T tȆ\.҇-G~aBDD+_gr1sSGy˭RsQ-KW˻X[Z^~3]iy~-nTQ7O<_*@QF?PEƍ luG4*22qS 9ދL=wa'w, A7 i>R>?/Q }gB@օSE\b -ݍغS` ;"Ur < u 3T@uZ djR(&УVus8G/dScKx!E~ tTlG/*5cKϦ_2Հ,m?!YɞyNMFϿICGc~ Y?jK典-q J/BW4of}Uܵ7UIad2ܱuo%^tiq˛V /g)5@s$I80U&LK6m,gT{)S\ϒI}CM ZhR9g9ehAL)fn53e\Oϊ -Mj U8{ '‘p;=HNQ< Uіi I:D.A@%^7um肼~?3XN0N›co6]4TYQ8<6|!LDŔp_SL_>[?ZXT\ SAmnm^ @fai%RFimM~p{Mw[Rsi|@;Y6dZp. zBKyiTw^:aǞ9igp&\YI>c/SrF l;R& Nwgϫ\GU1qJf7L+T5]*M)H)ހ()PzD%1ئ0$D['IcTm;%eK*@c Ĭ[l4=ցPuid!cձOZd`~IXqHBH _gw=Rqo"ۘ-mrmNJcp r}&EcBFN98ANbULYn(I¦Ӡck3hf1/b&RRb@|1JCƙM?"3c%VlڗX2.ɶnmgrlp^J61 U MD"4z L-%=w"maI F2۟^ 3 jz䀍O^l"LVPfMbʣ4IpNk.sOVuZ=W6gù~3qviNb*fDG.r  *Q9kӀ;N2;'?ng*`ŵFsgXq>& ^y[.\urdj$xƨۨs&KW[DK2D -ޜz()"MpOJ e2W{\FԕRaiLw0ikVE[ĥ߰Tyc ',!_Gh-Ȫ>d9XZØMxA`NVɉrq/i⤬ni3հ8?wkvÐRJ31ZKSdN&!T"MmQ pnbA_th$VT;Nb~e:=L˙u0sH%_"9M IZW~XpFlR' P8SCbZҜVA琴@ )VR=h=it ;}i5yA]>?~\p>Ol{o/{)W7w%nOeiv"_K(_I$ dfCN-&p"-RQ(r s3}-W踢CH2' \2\zEˢlyK1`U0Mb~T ~T[Ć'm A L5}U8ްW-%@O^`fʄ,Di!"b*%>&9ˁ[?Ж5%A fÉFPFD珊b.CV'lWK;ǃ#W0KЋO7tPr Co°JMZxW4wE;:*;%Ʈ-]&ZC%}]kk3yJ0Y,rQ l}TΝ'J3uU;35TǮ?m3G[F]P-<sX?peZ|1KwݴR.:ٳ"*1[ab+DQ'><>5A  )$J2fwXtA ŹbRxМ곜ӍYNF۴cmSf.Cc(3G8{s20Nc"ZՃO7x/DMX@M=\ø{đ'ߍ 3#&1Fy$W=jɈ1WEQ1||k.: )~`굛NiJB.~kX"'pp}gL([Auv.?ORDFێc0ג Zd_xCuL™BڌR۲F_}%Ra*Ñ!㖳Z8;qmxwZ3)jm|WRt#LFbFUF3&S[~lThUpo٠Y~޲lPs7e 5G!O9{)֥:y@6DWе#%܆ۡV7dզA 1#[63 f1I24(vH4!Ik-#%\$-`Yɽ[O꽡V- BY?NJ7ڥ 1*bvE.ކG/"꣏29 ]2ѝH e(G^7pd'LKëP&6.ȾaaIg3] d%$x-.MmX}I^/0WHtj0Snٕ%A놹i6JVTq@π)2<$tқ&쏀ewAG8du1-E{)GעL=L톈~f˫D.@&b~hM[FEPqv@r4DPA0J_%D5](/S@e$M$QF>mhƧ^kk|pN , 0W5^hҺak=e۝;Ѝ}Aʬx-rtn=51<.cF"N ZіXe 1ZDqȄE@Ql4!lnqSW[ #;=_m5U8Z-J#a?K6SY"> z>o{0# m&i7G%ŝ[nm 0wk7Itǰpjr#!BLpL)@ Gi_Kٔ1|5B=Odj)4o[ ̹~AxwH-[d(Qlm*d^ȶ/I*U[15Իfy"jwO:DL? rB$pe.t@"sAZF:adtz"pLO3J躜Z&K 6d6Js+6U0pNvʷ]4QDuQ8KײMSR2 Bsnr*CʟkOTmP o(1d _BSCY7cQz%O/V˶L5gĀ%@ֻqD)C дxj]rxF0D^'GWWo(NWK@! @Hwzee&&U7Mn90tT4 My}%Heh)ߣ CpstG&kZd.pxQ>y6l.ˆ1sՄVڅ_mxz [鼈8qF'$J!O{]\6gVQn+L m͛d2jFt?a8CǛBqN&%>*s 7Ȱ-in#ݻJnQGu֕p鵮k,D"Lfj/|"jK2Ka'VGPR-G;}̔qnL kʒ1mD9? /,GbaWoE>A6V>WRGh׶3ٚ쫳5v/wXy@gl8}xʽ YsQ 0[CūqR^.۳+ճ2ޭv_TBɦ~࢒gyuO"T6Tn҄Q-'x ;4fM.'"«,kJ(3mVI6$+ // !R*઄QԜ*)x %{E{#3(;q9gye},I*C5^IInlbx(dG~G(R +ߑfq3[o)N\1c3P rֿvkKEՊjKA* V|jA+ϲ#O(jI<o?<p~t~V^*>5Z.x NgtVQ6EG|/'߆l `oW٧Oge̿I/<(U%v??enUx4U?C3 `ƗhX,%Y.PԹ:IO֞qqm3b̯\1AWa O8FQ P2d:>osJfR"m%pZ8rcn h,Epz~9im}V zkKjf_8/8JiŘ㟚;ȟyk*$KJL裏M=c""gDaVg߉5P֚jș!E:Dt5̝;evftXXcBX}Df{Xy+o*Ŵ㽤 >^ssQ߻B;}DĪ8VW$R{"UBJ+ţffv87DLhWTR4;هV?I% qrP)-sȿ].эA_ }Y6FO^ZQNV.80'KXxx}t7gEb-(sŞihǐ6)Zc*69f&M1P߹/chDސ"ax: (=`T$v_nU/DK= 2 ,5}^;#'vK>$#Mwc>I˘i1A{>MV8(I3iեp 霓c~҆މWw6LF} g<ϳ =R:B&̔ߍ%{1གRg>@ vjTe2cZte4]xP+H} ! [GXZOj5WL{b䟮=i_b峹4Yc1N/}h X6&E T,V[ %&THfp\q`* erAud6;`Qv,GacxrXuft yewi2B^d3%2b 1 Cٓ0G?4vy|/@/}_jj0GGA'rn)2ήqLBޑIyR5 B4v` OL";iNBZ{$UL9"Xh|r3^m)w E'd{B q;PVJ~9VꜹFnɓ󥶍VM 3"4'Ȱs]k ؙG6!9xg"gNwP%VDH& A616QW}3g%W%#vl!ZNn:NpC.jKmf{هkzApQV*lu8#&SZM0Qm7쎳(^QzDT2e䷳/_&R̀4:&IQGP|'1=^J3n5NDW׭[cƀKҍ*eѥ (y&0BU2[1ja.ézVUFF0_xi~.ad{ 7͂}|q~uPΙ{N $7f4o&ohutEDwwC2#;9@-M [_%R@&Q"H\83ɜXڝG&qDZ:#[)o7V=H`<_t?hTbBYތdDp29v. ?PDpYoAow{ _t)R?QJ{I+qb1 }I/53QgxB{YnbK:#yD@St׿%yѤK:03N[ѪhVxyfX4nQIxnKƛ!(lJRo^[} *_#yQNNXhatN3[AK8+@rr+)k~oqW߼jar 撂~]yeqP_ۊ^S ?UTOTg_:?9)|خI?8Ĵ@qhh#>䤌H0,LZGzϾ|ga&AL~/eyr3"XmbLxvaTFf{’Ƕ!cU[ȇ/zOske^ZLޯr%o/:~뎔iw\E4!:0/2lc `9+xJ,j@~9W`̑lC_Z=QW1PRӕzΤ Dr MD'bgCVuFηϾP5 3jHկxW_]Q_9[5 %I`qlU8w=F}~(jQVEenY=p2&M0eu˷TdQ6JmU뵿2l禜vTRɠJ'O(O!ϯ>@Gu"93￳Q3ZS9Z|Ͻ:Qy ajZz J'z,vp_{ M */й~[;3#յ6k󨺙(g,ڽouf0s)@j͈!h3Ex[(ci˹"9ZIv:nK J'ż%gwzsTkr wU_nNsb?yWR "35.U7p7]}$ſ-v俪*f.XYzg^k4ZNY9fKI(09nw&ܩTP#J_z+40Ep1X^ʚٚkYgIs9fI~˓^פ)gzmm!SX'mx6}t7 3\POc4ͳ'Bgy݃f&̰ZH6f)'BV2@dSrj ) SWOi! ]l--@GNff6{^ |nRJUUzŵXx.xŠI.w^@ߜ50=-#%nj1yF lg9vLo|VCX[]* Ob`vvp ¶1bJдOԱxV,F˵%_&F*F,--sT\'$biz^ #TLj'̓U" 2,TX+Ӏ;Y"Ō`sLx*sֽ4Fi^ĵ'2&4^tAj0022g{s1nϨNϙcƸ{nsFJ[%(ƻ1/L9F^708?_ƃK߬gb{dkGȆM6\bJTr;.&0-ρ2hSIoQLTkQ`~t&T²[Euz/vfmx8SR@~M̕Rݶ,VVnϭNܚ;-&[m eSm pB_xVB<lHq"?tmrbx1OFYF%8F&Z.˜ ]Gld}tANf+uCl6x#@LhC ?yM@>TPV tQxw'H ۤ,PS8JlK"wUQor]&S#vk [|\ȦHfrPf.)ÞɃΙ}hZŵGM菉P>!z,!Etd_9/lh3)<AkFagdũ\ [?KM2n#%̷~Gf+Mu8u8jđV6pe;iNmIRbCAfk(KsYI:n{HhFboBPc_xo#7ҜR0S ęǎkSe{B8)@Khh<I/H#eOOpvG[l :n9 U|QΔ5-pbAJ8U.U:W+@ivж%Uru*gYhgL+`#vc]rU*х i J(ÎQgLj|0y)Ll Η1a[:+529k522`uFzXX$oW (Uimv&Aujq kduV(GLlinc4B2i3=;T}.I Y4 tNMMXZ\niiI8X/*ƑvL}qtkokЗad6&Yi/Ԩ W1|&7opLP!"#O:BI 襤7Dgs;*Q"1tץ{8q#Ɏփ~LPGGH6rA^F[dE($,@Rⴆhq&)C8ΖV5̑N_c(㟚b5qc@z޿}  QlPפ$B4zS8:@\|Rq >]+g.4BP 8r;9ke IVlX"p6&4ca&)-ZX#p-^-҃o^>%ffMD5BNn';ik=:˺P-sQn3?dn{(?#"On$1PSE&z8Sd@ݹ7ܻ'dyQ};IAvB\f{π ٬ޮx\vxʯI ձӈS> X#m'6훊ޯ^%w,[a?z\Z%XޱiLJQ(塘1ү|jH/Dhxz]Vm% fLE,a:'.wSju,[o3i T&.d#$;ƥ~,[rHȬPepľH%I9/ }A +haK++O\i`RU&DEhK;;jw\Bd^840踧r w[ @$Eq%'9Cgu>vɌE]} >? ( ޚ_| ]_Et9|_Z+~(7SY #ۇBX_XklI=_[]_Oկ^f"N_̈́XQ*?W|e$GGrxT&vKMB[W1Z>tfwLG3v V eBIX3T{++l#闆r 18RbͶ*n/e&C\<8~T'UO_y6/JhsQIHOKw‚*UkG{>Ҳ"jgPAGlYࣗ0~Qm9@aj{zڪ# ]|[(}r*_z!\-Uf6:<OARE^[o%"狏!גa\ Y$b\3pL."V"4 ɯ@.j,]y&1N@v!^/fٮFa0Ivvߒ L,AQ$$>J9 G@Eͅ0&)iv sRrNHf.RK?e 9_ _h}%:~rf94 *Q˖)-άL5;{Md_U-Uز/iIk2,3r<lo%D$Ө%9y*;1$ 31)Wb4e}N-}Z`yNCve@&X24x\*\,)& cVGN PWCM=$0A< ذ1@PV adБ 2&D=B%j<k3D<~mg|$=$dڌVg>)?h[j؁ Nbk߮VjOv zhR~qA_ ^O$[a0B{y6xTukg6LJq"X0P܇H)(HY@Oj?XiB~*#>!^1752vN*]n[23\c!G3]-fKTTrV EbH C:oGy*;9LUCN˦̤MD>QQ[ea#9·U۲>ogﻰ"_E¿d?{'tF{|ԓ9a6^l$ɳ5Q^o4wRIS[4bNrՎq D#b;& (8q({,2JӒigc0ɛ ~@DLi~8+n„N5ה*q'U<et2MŎƃ1 ̺A!UO\{|]ȢBןYx&QR.j͢Qr:%+U:Lj䫷 .NƌJ-,{D5d:=.3 %7 wJuFXd' 1Zn  @tb).1zcFAgRS&1«C|ZapoNL$Y E]۩: [Lwh|^=yF"I o1b}Hޤu2Y h< 7N{\qjr3CGa\RcL+"6G: EsOh C[RYHQ승zp"_gmT{ei3!uPEmBebmTn8gKgHOvYTZQa0Iд`cLH2 H ywxӷ-߸7(o]%ܐgۇIQ+ќDt,>ӷPH\sS5~j4pW8e{8]'!qFWJ>"rb%EfU1eN1mA>SeHS;Unw~Ag]l{a:eg o- +ty:VVLSɄMSfW hG ;zD:-~L3.H^: o!-|3,P$1a?t6 `* phՔ0$!ac6 l+-sXId*:ReE F ƫ yFCOօb~5eJ_$ʹ ORnq$tۻT׼LC8kX#cDtYdΥ:/a';defհiXɟ+x`8 >B7'sKt&|Lm0Q‰dt^Óa/$iPIڔ|/愵H1ЄĚW6eQ \#1TGwqhe[jB"t0isc 5R)ÞH۔0plRE|dgJ>I% pd~⧒侷h"\sNǡz,(>F$JP7l)vN4J+*=x kQujHAv2\ }Pjj U~kU1T֓'J-dшbbh WfJ9T̊PiM4fZϖh4U?<2J0 ZQWP_Pٓgo&뾬x-s<Öy\EI}otX m&p;/;я35gQMSںWd=+YqJ)b{]aTYb`&̣{16`|m]PR97$'ga&ɥ]Ixsw7y>iaR yܺ&ߥQ>d|iB8nӷjoc͝F$j@BU%s`>&]+ITCqmxR[]xDKNצsHgOȓ\o#!3Nw&=|o/ORT5qVf9& s,c..w8x 'Շtz00R ﶌ3qgX4G:^鏱b^>V^ O`ቛSfſ\XZ`NCȅ{1ǛB <-k+逥 5Q,؟uaR)TcQ"[Wv,7U r6*Hsu\o7U괶t#-thlYq~$P=tt-i6\:mD-+tc}x^ӉNN=mdj٪(jgSB?R0eseP]ʞj.euW86C*I·gG0~b)uI_<14  <.p쐅{oÉGY`uQcp$o؊5brQ/jG{{{8:L =Pf; Z U0>&d^4v^툃Ã[;36L.n1ޠ`6_'ok8/li,\o&<[2 q_zJl8GA_$ɢ'LpZ;,u(|rb/KP&cW֞I5G>Z?}\ #_~>)Sϟ&Aǐ8T1_y.sh}&5R&TE_Ɏ)}[&֩N'\G+] Spy<,M &sɘ8P6տWʴ`W;: URѴfV|~s8Ŵ؃$vZ&_WijJmȳ&vxH#\x@_i]'٬=[AG;z3 D4Ƈg`YʄIR &`3ՖƋ#CrقɉD!*H.Gڨd$.Y;Xn ނv(twU01x8dlEQUsQywYm?"y͝KrDŽ~/)# YdЇS[d47 >+= VmЍ6zoeG&ܓа`d΂O}>gg?Z@ImCؑ2c~i؁58gMDj3#;?S7CxXOA[hg@ȚAIJZc9 3, 1n-G{[{/e ؄GaEa{Qfƈ vLn'YgZz!&bq_v&>w!ڗqZEaw?w͋{ۘ5bԞ.3&V4ASԦY[sXs̡xWZ܂hc* > <1 @IvtM?} $5==]]9?|mg橭`YܿU*xAW풙vUaz\SXG-$ &]ӄ4_zs%*2 ) &K5n| 1@{`r&ERw'.Tr# B$׿A0͢VDe\Dp<~Lܹ%RT& '`׺V͓5o%C_}= 7}^{ ,Rs[{dn%y,$N2%1f& ̲æm5981,9CWd&Pػ/1yb๨.|BtBf|),/|[w!! 섈H Q::(Vhj[=")KNUtS}鼤j8?:T=6J2htaDpKشftm9u‚kO $ˆ+bԙ+*rFgrK2`hS" MN )>P2-0,R(MwPYD윫Hd:,B L MPjvr!Dw!pu5Iaʰd #yyZ 6r|uYo^.BwʢfW;hw#тxn8yb;#)O"ȷjrA!%v{㼢(u!qNra~O<\ץ%+L/yT7JuF8ښT7vR D87[X+W;QE"gR$e,34hLK[QAgKf㒡tqXy 0|8h1#Jq~isQ `vvqz9½ ֽ"e$D̥IK)pà{v>A>";6fӀ>=/s2ãZN 1w^m;لӀ{Cph22c('O!;[|yG{[';9ڏsh։?:9<''* rpSz ~x#$vy0prs*SއGuDȟt'S>b#±xo`zn/ vQӖ}}J3{g6''2hZT \*0)8P?$?NGJ ]C#^I=H6桊mK0V IF-.AB*{)*GT.dyGdL0;C@^ҋF*.*ڙΊ9K0ڐ3-_8;N }&P+{ρ7ƋsV=Q!2qKDw4ObcEN@^c#lh(G!")MwbT~thUrJS H\=y"h=yvR֏U~J 'TH+ޕN#ڄ6))$9G (yO0;F%J0Fz~D}gk& )aM2㜖ʆrWDxc2Ge+hf00,{%f@J*Yo0I#H&i܈|,LH0J'^'g`]2QC}WeG5h i+]жb 6`_1 Ka7)БX:==?/H7"'s}##&wu;!0y2g,Xs14?L"> vd J'ЍFZO"RVFJ$Q$ 'faoz2H)afr[4c'0m.QHCu,B@p*#M8YHVf'q=xjXG.|B6<9c(.UdF(Pכ/;E)ѵć Qj_ڮx} m {\ m-0Lh&gMeR|r`7ݘ?K1w$J73#]S izhd,euk]WSd۶; ; vBO*ΣXI5iwTs]yab{+1VL,.1 D8k_N, PS/L;h#vԡDbW)!@l3kyE~dŰzULL(vYY9;f"/ƺ)Zp-A`dY8/2[ 8RX8fBBcPr9A:Pa^m2c؇0( Ŗ(o VH&j!EDnw嫜[ܶ2.u!}'C[в [`pf%[ #_zZmAJihѻJ9-4;$5{CvL'bup*w "VKc6H=T(09Bts -lQk T;AS1=k0g6+vd HE$_NΑIR)9~ep(0C95H]IYnDbKt@Jdҿn=_H4$2PQHhw m9}#pv 1^GJƾr)'{M`GDJt6q|> g|eCceҺl7:z:0v 6`QK|WPP~ C(kkO?YkKh럓|*t5 lPiv vvdM%-@\/ efV {Օ(H>R03)hyҒ7|[.N>"+*k!Յ/XD^XbTȢw 0f*F  ݂[ NJQf<=g}ݪ"Ay8Ǜ=c_( b^qhھjͩǔ ۳hCN-v8ޑe=`yHHW!+#`}9,a{sEV!_d-PjVLhNڈc~h? wm365߂9 kDUc6Cd%j#WfcKD9+0FIXè!LsdMNsמybŢӋmbu3d3B|{Ȓ0Ԙ/`+iFRK͎fVk[ٵft=I;C~!0'`?@9g19{[YvGϩS9uԩjȓǪDHzDz;~+;kEf^5xf=%@&dtjעt ϐ2Ό.;=3ܔA\g>XGBNhX7N;HWW 9̃~WA3K3udq25o1g((gY-Y2Zc'8pSN9X_h쀸C\vSJ{3{҅IUׁ>U…nv!GoMdPvTGe.J#BBp! uy`rB^nx9If"yO3"|:'RobIq/]4tis^.%U&lC@"Sk_C9 ;brЕZ s2g'b5Ka!P![r\8DF><pV 4- NϒLpQUeeGt ^öc%@?yX`\";2Xִ|hk<- 'q fe9Pŵ޸ h?&g[d38Ӎ}| C1K d8}-+A<]2{N];18Q wWt=_eɓ9vmBa؃VTn~x/㭽 GIO6=dHN u{)8 z"V>&"y|≘6IBH,C}'ni#'u;j2ǧAWk `Zoz$bF9#NsdP;r@پ9;7JWʈCkgww4%IY}ώ&t7rnzhWaIe,)Yݵ 膧OƁCw}h4nPReEgg,* LADQ$:ܿc ]69}7zRלv|06bxBirsmqt T8Q`?ޣΰ4TJA+x\nV(N4n|\E5G·HXrʱ)H\"Вu_f_g*wX\1^Sǻj18T%vlM5`XDN^Xxxۦ{Dhc2틬>aCQf5ˍPG: |sRqw)h֗ {q\z1h i;@a-m'gX=tLcTxQ j=GO]q4:Tr_CB4 N%$}fj[Ņ/౎=Ci(]%QO g IF(oQ)bw7vӒ&9{ư m.mD#)_#(KUv9{?ʞGH;;6 w 8gzCicQmFӎ)c'Jl1,Bmτ1J%PjwS==]!:LeRu#!鿳 u;/`<<؝dαSvkX*#VS Ƀ eqnN aAֿtCWYخe5Eӎs)oA>tyVEpGp<2aFSQoz& 0w꘭i3wvQyRQ>:ud5{Z`_Yp'Iqzm&GMOljܚR(d6{49 ԁr~ces=3>~Gy0{~QQ맕Ź‚rF/Ν{l,U5tu:? 9d`^7D G:,Iz?,?Lwi%䒑dAb}3>0Q D7H*CpQhLK#e( m+{5Uwf,*r(/M}CJ@hMW7k8O rU*v(LjI؟çfvW2;OH[B$/fR du;1!\ xܴƝ'DY,)'&듻LNf3Q3=ƍS3'7n^`,\(&\fO.fnAn#^ :] bnI <5QZÑ"l*RpEO7DM9 Z|"Cְw]CѮ_[*]FҵQ=eFu@1];մйw4 .S8)iZ˾ NS0=tъp9[ZYx<%-n˦CV868T} qlL>;]a@!PvUy0&lN %?mS;@%Qua)s0M> I2 yEE{ V_k}>[&>w^%Zuew79{?M 7@\mn—iF@٘B=]d2! 1OJ8#S^EqNT$vcܤ0$EQΚ3lUn'F"/OtLs7EI@1T'Cb_ȸVG؍ }0&(f!#:B0 } 'O5[MG&{L?4/ZMNq6z}5Yь}=MɆ oU* i^ڽ}#jGM=rwWv@.㷒@NNk+\I=/C-Z _VᬙP"lr"ɶ 3z ~_h_.ҥu5ft0-|tawE,g[`m$ !Ѹ^U PL=;bt~^o{js)B{a R"('P䅙tP8zrI9}As!NᚥsTfXwN@=̐a8O Kglq])8S l|DF}4vv"ł.` &9G5أc>N#uf-N̵=Y$?g|v _۳ijJRzB&FפÔg@0f'5?r;W7ƒAgD] :4&9u{fqDNI g-rn#Q6+rZMLh!ѕ-8GZ.uHޘ> Vc9 ]Ϟ~6{͢ui/};ΞWEl+posEKijppqFqYe!isمlD{D b }o YXZF~p3=Q^  K] n3-y]KdJ u#3@#+8 ] :;b~&u'-hNn*6c{7n`6ާ4v,هtǵim=`d X TԀwgt?_Dslyr4 {_I"a6 7"CʝYL5׀Q,܅Ʌ]`7qZ_Cܴr6{DaT(zsNc/KetRoJ=dEJ s!Äúgw{{qz~-9|ob') j3F&G;:zpefml&%1z3"Od] Wx(ºeɖz ۴lSѾB"} rx5B5V>;0%fduqao\8UigT<@>\8:&v%-b♹s!/JS=aZIfWv,5!_aht=Y:6hh'I vJ٧ӽsIXht ecs>x2 &ZF>+s릡nr*M~=|, >Fwbwx.3C]\u~\"ul^8;7zͶflv L9mea"zuYTfgnb4m"5I]LNHw&'׌zc\e&..1.{u~ wiNl$Ypˀ>{ mIFI~c4CMi]|P㑩 2Ngp s&x q,"0~MxUaON>!}mKy-t=0R#hQWjftǰG-a8 Ev-.o 0~`DxOG߭ڱc_K9ǙF$Bwx\f,މD/)XN2~98~#%wpI]nu,.0zXt_ lȧQS}>:4;i\P]\P(7X+3i uuS6>v#BA\ @7{mE䮭YS_-淕fyI2'AWVCy ֖ZeBq_(pa2L3 CM mKoU YʱSn桢(8)bJ^X_c<]^)K+-.lo֊VQngfI7D} 4<_/xWJrHo.z }m h. &>xf k+ZN&%\_rtT;KJ"Z]%iʡR%0s`Ԡrp먠uл阝S)XkխͭԍSl02CwڌptvP_J>"cs{ 9HFγ ?4ѥ=yZ6 H Z+d_]qyu 93f /AxU 65;a)0KKvcMhj*u}!vLaN@g֝hmp}Ȼ܍ie z*#:)^HR T&Ӕim">;ߦą51I=KYKMUE/gz"CjbVOEMJLɬa*SMGaE₂sߎKg_.Is +: ʚ,;pxC9}Wvn+Gx>!֍;?/)oosPzN%498b7վlï!<5)Q ^j?͎֏Em@[t]8p6 *{v"_0+U]ŕز8=Ɯ;0N~H ?z-T'˘RzmY|'aـK ǁ.-'i B'ԯMk ΙxA}T)@UQ+}C,G*%p.45P{m/>E=W`$lkaIT[Xq*qQ[Rd$URqgYF+N-_?'"K'5<;kK+zŸFf`CDTWMVQ8s`Pf 5&7iL#nϜIZ.o#évmZBJ`Ox'#O?-N\z6NR‰ѪvO ]g{'MOK1^va~!+:QU40n LQ8_̧"-,`ҶȽqTE0Ѡ dk>vy7:9ҁ| DLydO)zVz _FS7M7lS۸ŦJ ϖj4 z[DII"\eə}On_MN.2)]xm{pSb*46645øN1v#tv7[M@Sw|=\h [r$c=GG'gGҘ\)&a,/ҕgY8fٲr@Y{Ǟd& 0̩nj6yrgѡBpHθ-Mh#9O@*C[jf\7 [|C'~}U޼\]=ws_+t%_84¨h9voCk i:atnGuX4T|C~Ls TSuyX.oxS-4 cai)VxrOz0Ee6.8H$Z#L.JyWB4h+՝ ]d) Q9#̢AwdM+J~agsn9^:էe7>/v)Af\qlU$"+XTGu+vl=DHx̞!d<>>6Z~ryL%lObȴקm3s|s0D8 ?=TILA;'q*E+."LNBpZ=H:qyILJ O6@mYm[ vc*܆8DbZ%}Etsq[KOMĭƀ,¦(8,UZ:J$_w~t`TL#Gw"ҥXN]]SLԖ$䯯.^ܼNa]ZjN؏s=9gw@U4/pUc/!t|/Ž3>Ҩ hP a[SWx$.cۄ ;$8,l@TT(0Ho;h'IG }ZIפ0 |Q/ƋvQ. nEᢐnQ٢h(7Z l B⡭⡭⡭b8[E-T1<|hԪu3;qMrQx. ^E!n?d%}}oRC52222"< tv(G!|P«N 1ɫ۪\g6>kbuV`ex-%IOFjŷ;hNW78g&Pc'+dZݽqSЍ ʝ ɨ4]T q4ܤOi@~H6TD뵧J|юq rKq?%â+F6ujB/0G%,G((CdM66=!3DrF0C(zD"\&X>I蓃.0l`+F/ga/bT+r!L+%ZSei6{J^ yS.+GaVhQ\Rf8yǨ8?R,@1V.dzKPa%-V5}8R0o2/fme.韞JLQY! ..N`^VFu{ss V@ŘFI9"僮֜[#{h+9aKIQ`{062PR*Sͯg.m}УH?X~MTDY2l99OOU  TՃj%$PtJ~έY@J5=QX䆻=|mUoRh==f_W1L!|٫NsidYK@9yjv4Ca@ X.ln^u&-nFmV߇s!{D5K7u^Rce$)W쬏(i^kt{_oot! .>s3O̤=#6QK#a #w}ÿ|M fЌjz *ZQxn27%^Q3cHn-t{Km<..Ʉ4pKvyH-z4jϼRhR_^~pe*d 7$P mpNLPDeY`Ȼmv [O*~Go0XZ,ï }B^=ʗOh(FTXR$G0VV3呒`5J;ŎDOcĄ2ΓXz=Ւ̌;͎KgkF!+A.:BPb~PԣY z2ӳ:3G=ݘɷ_BMJmnnxqo>c&ԠN ~f? $AcSyz3gss g=v%P‡|ԺDHD$Ҥ׏(HRw_`:z[)N dA4)XWƈ3niIop@ R'&KSG7g TP h =8vv* Wu>Y$=g- XHzsh:.3LH7v]匒08nC?pV\Ɍ{sg J62븗 Kb!>tOW֪ #@JaoȮ< ,>X^uXOUJ}1,2ZPTnu.P"H 6U}oEy':*IkL-N:=sgZJx;rWZ3e6 pE :O`< FJ25KYe)&:qʆ{'MeN(SK$ՠ384fy)z69wOF J9 & HR %}f9 yU|˨37LsyO\@#&3]PxF6Y>;w#-!JY jAYCTaS& '^pʄdz8{pïSKrw}fs6Ge6VDpG"?>3B*K #QBĢ#OЅuL\&XzaArqIr`k]Ѻdxp1j{3jekncmyڼ>0 rs,gcmsrg1 $cc L7!Eq0N@8ϴEOb5C;!D Qa9 .XF(tQ5ssrvnet-ڳ \ѓ0PXVƝ(l6ayl]Ǖ<8.#50NI1T\rCo`tUAٴ@sa6%Q ƀǚ"7wVqcE@4]c9._667VJe[kBq zɩvlswS3@r*'s(IƟN+JhyQ(";(Nr% ٖf4܀#:'HG_n| R¨gZB%%tRE3lrS9d݅@N4g SjKᄂwCL/iN;h4jПÇq);g^5z ;v^R각ۧ7|MI[{!u,otvP66[C?R^plRU."VvVhD$+@F&,>ع|K #+OG 4jx^ Hվ_q.h؆@oMB{SMvcXlϖ3x>E뷬lv.[p m@.?HT# q?Mi܍Ze 4;qDb'Y1v DGdiN\;mPatS.vle$W,U;{DӜ3DI]@٦9TtF{Ɏ?Ϝ.khʠ#b0o'R\8}!-7L^e&rebC1uqi]M $[P;]A-Ma-!tTD~ˀC(ױ$JҞ^ 'xb/^IE>Maq^4}SY/]r&(\(xR5qxm,jܢ, p*CyH{Ct#YA: 'ւŞc OgS].?<*3 =X~r=gsp|bG" ^K\Tb$W"Ϳ^l8ݔHڇנj '7g'3,߸qr̅g'\eQQYQryffbI<$F むl>a sB3a\ڳ37G%٩@z| 7F-r7ʒΞN+s%stR}ޔ: V7IWv^YNQdL8h:P,Q;AO&;רּe`+bersLwA -.,ܹ_4nKrs@¹s/Q^ ̲s~`uS;FdaJ=͢|E5tTuбQsA5XwY&pY 󺎼qn6*w-^j=&Ğ惭s6-^WŻJC#8~G5x ={f ^cPXЇMiσOR) ߀j40Iwzvֳv伯A ^I7$#UfA@{@n lTp=q>ΰ["oW*yxmMn3"Λ\V-4.;G؃X '_LXi`w'INAVy_~ӻ0 0bt0#j]cK@,@C6%9v|wRqugoLnlVw6DiqJ9Qi2'-Fӳi^ѫ1ގZGHrP 90 <2l<[]\ .%;5 Y˥ziUެ-Qbg#vikmLdǸDMdc:G]GRNr Χ⚑wi\uzY!{=\[ okB5$"P -07۹A0ݺ֧ۧQ?.0q) DYcwғ2w>懏 TA;ԭ3ky."%GpWALa-;B85@-&*diɴpZ*{U n43SGԣ`j` 8 Ш^Йgzki6֚ߗѲ~`u.nPL)E)8(nJyժx+ú._D1\%x^jVGxK\2F̜U P2{ /pzS?0Tl@q=l ^Gցm52s nEB:M& XmiT209,7M˛oκ6Sq N@J?~8 &]@<\QB*pիLLb_GuE . ΒG5ԹH5Ls'ILmd5LnCg%,?| ˟M=zer#IM5cNHw$lM%{='[ <.UgGOYzYucgdˬtqp'\4iZiceZ%tt3))3ѕs5+s5/<0qlI.7e -[j3Q `(D,ޛxdhIHZ\i!0sDrkb8 !QZ:AJ֍8tzBH(FJ?{k q\Sr,T@W!(0EU@~]u /T+)_Sn Ӫr,>͓kU/}6ctzbAI -Hxa!/@_T&0xG;%#df`p`8JwUʳis `N '3p^* 9Y~R-*EoÎ1SZP(66yI+a35s]UEyJ/r ǓL+a?'`#κżkj[\1-D%}C%vxBD7ʛ]|4Pt +~EeyATDy84ģ@]D:2 Uo.)3N$TӱKzR}Թk!Fd(* @"گ@mr%" 8g)BTˤ.4c,R蘖w%SfΤG3Uو1QwbAV RP/T?GN#-i@gfwZD ;"{hьh(3mR(y.J  s807wnIiu FLq=o(- o{=+U'Ј Lq`HLlg8(ey~ e,`ϲGTDh%4Ԍ8DڮZV/&jkYeiKwZj= x9V,7ݨJ.KUS~f[Q]Z\TT TNH<~< d %\1Pzc4q6Z4Jai({.fc79k =xلq+u@)&=viy'K,L, 0 XA+mp\S@+7HkH+yLV7{фY.>Mٹ%'&f.2{!lpKB(u55U:G/J\P\c$Y`pLI)|]x9FOhčuxeuLVcLj.H k:(fJ_j4ĉҷV |v+ Rh7błg &ūI5-%$F4q:Ϡ#&=pnBbyg k\32 kM f!KnMRG*^^+dfs~=:Ij1+@ 4Xmn@#%A=h(Ti>slY0͏[,}2g e>;;M~2M.1ީKcHO HrgOl" IQzPzjoo0iB-k]J Ol ]`u.$[ũm 9"n\?7 T<4I $e%s&)"~0ZӀ{l >6m8@1DQۙbȼ2[W5 i]oV6+Rl nWϛHd+Ѵp ;6Վ]|lȣƞXs)/RÎW`%)t44 9]:&{%:1SL?8t9\|l1#]2 6ڄ}{۴tFR`G C>t_{-ਛ1Qot%dx/}AgYYeMN0q&x`&]Yn/ 턺R`/ 腤FlfzK"ACaII#m Z\a2[qfϬŽsI g+xى3Rm}=Eb"6H ΰK1UDϯٜI1֛ۥ>^Cv&T dOC6:}k~`:E4F+HF"nn d\)ђݭ}yK"?ʓĘ '9H[ W*!2m#mZ395- C!z*ځV%_Ue"bWl.$̪Qe<:\# M:Z6nIpcxX =P$"jKv{q!^crH!z-Ixo2#dI_ '̨.~|Q+AAbn Ӳ/i [NԿŞ! iؒ\d r_@./m Rno)iijԕin^/i}^ts!F -{18b;H5)BGj6hKGO H2nk0*h+HQH;[ҋYTr6J=:OT<"+=z߳߀sHƎOo0Lrjw0vVMm8t ^y5C!: ۱@û9b`r{5ok,~0= F rhU0ٰ-h; }֞Wg |]Φ)э(–cA F (^o|#u|R-:zDڧ<|zO<~nS|05+uEg{jEfcSe׍J%S׍ @Օz:p pԣYNa^\J9e*>}֤jK&73Վ.%؃s]p%q\ZM“רt] eqq?hY.0 ?ez$x:O[Q~_S]o)-95ժ9 ޺Kd߹B{~̵dM]N,ZQ6}k{ɠ[cz:K |0rμOFmBSsz=pB60/B"L^$#/L]$.!q1hK/F. :t?SEs:v; 6u|(vF}Kk+%$zan <>slWvip,pâHfaن]v|Uv-Xrكz2OmL)71ĨU67VKwEX"6&ډ>+TژHX7~#"i-]Y~֪U [^WVI2y=̓]OOMMS~8tg]=fÐy3/`ŠiL=hNyȃPP%*K :X >q{"W'Fg5Q;!'} n+r]<6&ӈ_g fS<0-ّp4ԖbR9`z}99~:g_0cQn̶,ЗMsOB ΋qk E~a {ʮ0\3vRE'^1Q|GL̑V'(.u'ulox{䳞>' Ց;ۢN}ix6&?#V8 N:P4$|ȯԈeŝ7B(5\U F!N竀j9'Φ"Ւ- tHb`UZE( 0ȺgR>vhk]dʯ3u! ɏFSġ큍jT FY%иAO4њsK^"kvי;V&s^-&e?C'h874 0t$mlnaNP$Z"uv&i5P0ưwPhm8O7 4Y%֣'sg}'m͜ dKaC` ӻ6_ ( / l.^D]&i?{D:w)‹E6Yryj] t wܓbYj[, D*Yxe,A?#aޛ\\v~0Cv`+ZYY\Xsgg忳sgϽ$?;_A:|-Dy9~ϩӣIRK)P0z=i0>dy̻T* /ξ TmKw5hӠY^6`he6(Wpff. ?`ȌTN8,44 ₡g Ay:Q{ e?MR  ٨cTq USfĸ}PM垁M;` |+xz O@raoke:jtm:'QXM7 wvUDWZ-r %2(QHԬ<)Х.S< O=]A &/88Y$QR>4S[Sw'ZӬ 򤦵1*b Y9lԦ2ޥ6 k;+!_÷pw^Q{u,rks+ 1a=кr >0NjX|1sOz:pbL3c;`zז|asO|BzGo Řg(1L IP9f]b>}k sx~u\@,ssYDRe֠ m~VoT+ukmՐ|N ҼCO!#.JgͺkPyr`%|E]m^YE|W#@MX!*o.*sۅֽ>k[kĴǻH{拀ܕx[d7)Cw[VyPTZas)iw̚fYfGkhl,'lMekAa3["9*BCH蜜d xJOf)iVļH#f8vOiAWozey>m#q:s$m67z t08AOs,uBO5(w1R=C#=M<zE͋P7t yS^-P-KB- G,Öĩ;r9C7jB !٬U}X19hMN95?#"g2 ;aNCj[S_cGs/PgU:_a]s`MBwޟ77jbG`v2ѸĶJ?Ѝ>G[86ȼ1E7x$9 _\ƗtA)a@(}ZpcXJO_P'dGtlzgs2ڑxwi[#!B'ȂH꤈k֪UL ᾁ,z<H`*w洸XbT<=V'MXks!|+*\bw2EZs+B8R.Qd WyfN*y? z2ڜa [6dBHy|VDQ|x\"R'#RA},.F~SKK){LvU2"GB=U1ϧjc Crr u0ݶy%hR3 촂PK9qEV6Y F$P`6>o,-r`4++ C4ρ'S?9d&+E#h>$!ȉa䣍5zP+(<=za߉L 2 yf/瞸HoہzG 厺U;bŊj_*c<8O],MYBFN'wmD bb׋PɸL&>j kRCك$nt$H\H ET$A_ɔ# J l7U傦#\]7mqUE;WBYܸƼ<(J N <'E#sbX>8ƈtZs8J jV w(V͵i8V0|LMՏfZ]|/ ވvSR>^+z*)o<%.*pZWaŋ@NOL(<>WJ`D"1W6J'&L@ o ;ő{* .`_X̯KzMSn45` *#QK=m%$R'&LjOOXؙ4]|TVok/OUA٩ONܘJnapUch*Ėr 8Rݤ$^a<#l#@ cSgNgb\gn $„:a`% C/1t Jmƛ,ĶH-:^0ˑ:HU}_DEIdħ_2hqj[DRꥄ)v9Vz0D% MTnWk(SP`_C<t`-un+hMWl+UqKG@mq ݏ۝-9Dˎ*ST z2 }%8١%¸LA.[RH=\LƻjqUGKC>I5Q^E#3 70߄2T:=EMNXDZǴ <1jgJzzسKqqDDA#; 5'@-bGэu!4{9u:fBNkh4M SқvUaOPz\*\٬lT[:\ͿU,Bl bUTb~)HV.;ri~U~ȑVvw'sS3y']T7A,`I`+FQ%l_klm}0rS/>ޞ>j> }(4MQ#& Hb$yL'ëPN~ nr~wX"2Glfn?[r_\C;b vu 欿,ZjM<)9٧b@s!=<R==!!?;=Ng=vS}\vVr( vnءB'&O,SD` Ói?<O&zmM[sj:c(}}bPG9t@ʡӖ@@k&JeU67.6T*eNx_;3:N=_4h&-d:L VNp%5ZεS~Y_mf,R"$U:Xz̤ÁR$=qt)^4BJߢgDFŅK2.L foYDY`qLtАXt=ȮZlPȻ+#LD| :ySiO` 8F룄}- GpɠXsSA\MlVc{i;ڠJU;2okrDh(Lw읆e&gwa&x`6:#t(^i=I`Xh&j70ADP2]Hƾ<Ȑ32܅wMMF"?T!mE z`h 4}`੟L1`$Ȑ7(#.Dp:H}DOd2SGg/!M\?ucegdz1@_t @FbU i@oL܃2A2pV2:r*0h ~>BFڅ@taԊﮞHH*51FFKr…X4:!#1RW0pZ"uKJDOtx>9cLJ!I$#14'# dcP7Q}A F_W#/12gcpoc(w#?HbK1c0!Qw/C 99-o1s0X1FbACH"b$19Dl$19Cz ]E6F+b2$'Bhτ$BFBK\8!cؓ!cquOu>2=28l*$8"2>#28$2񏉌DF"c'E gE0EFӢkvH4În?WaH:14+՝f*ºd2ɔGK7€$U?D/+b ՃJv+Vh$9ܸq 6$̅'y3$5dzhb-Ç:/5 >xEYz,q 3iI4 |1}%/ZG&=i-4fQhe[ jc~oY/̝[{I.00w67x%( ?+Zuc) 0"fԁg錒u͚i4KTF,6T)envvacjGӔj-N+%%8`uA;V.cjVKc>=eqZsH;ʾ~[@h-Xu- 3`2@xׅV,N1$_ -7A@mؓ=]iLsS |զȋ.%FScf JMBKuL֖N'Xi-T➕ͭFzXTء$S?:T׶렠ŠO F~8$2.M1ڥTz ծ۳ju|y.yl|vu=.CɊ+@A ȭPtjڻuhU$[;(`u4R3qn.M(7ck]@ 8*4"Oxq6>wc_4PZ.))9mKa n)9Ûs8_7uiJ} jv6~gxp=38U@ :)&Сni)gUy"ᝍb+TVl nq+,";RtCn3LC{4;P[Sk& Ի0;-?PXScQ@:c\c:5> G B:-'5T;0tڒLQ EQ2V;*&${]tK,cBb44ǂpZےN)ofzu.uB ģ1aS@6 /[#|a/nZuemsH~Sk*/olܳw5<>I&'\%A̫݈rTץr7YSBT=l;J4"bW|e=v zPgh{:%*Рr2%L yiЇ-4Z@fK\fT&9aeL<)K˘& \7R33}(sZyAo-hP|iϱ5mV;͏iءt2_}P^Y[ÿ{TB n^zm{}.xBJZ*E6sJz<_A˨,V `XXEA:,}C0\LHe]lgw*Vx>2?/Az\kZWwh*x-^+mCB|~csTȯUlV+֡hPf/]1 ;k7ǐ 6L-JƐ?0 4,<>Wr a܄>9v$f |][r"~UĶvA/b#2@v|ם6++ŵ-K7AǗ..o=yrq鉋PglX$2aUL,Ƿ|#fJ4^~+_x2P㘬'@8&VY"\f~Ns*o 4o)*#{D35:"+Ղ5BUo5;@An[0#rzZI_P7|~ʃZ)ntWCԾV gc%Ԙ}88ԢS)$N?Rct ?ugzYG 1an X%6نZl0fĮh%$ՋTĎ;6gM|ZJXoi d~0jbT݆wXS^sc78hW%oxI@'$p<#8l8\P_+p;\P **p.cWWG)pae7#@xa ç9DޘLdm~#k< kdM+_ }`Y%kbV("e\˝8:Yc)kR^Y#K~re >r <7wYH.5T|d\+&⦥?S / a*h#Yؑ*Hr /yu#e.h8#䚒D&\S[4:F>tzrPņ,w`Ni)?NfsA1`t,- /N('w7W2>@urq$:8;`i^m(_l&wQ[>1$Rcl%42&Z׽ÂkUqwaA-U7lO@{ZS/SVcFqB%6wO>ZO|0Ew.:2L6T\JWg/p#BJ*8V\vqy *z]!bzWp NɌ~9y^M'[2ϱfŸlGuij,> gV0=[0 °4G&k ES辆R͞RTCIҩSQځQɩ :;)";JՍH9Tu 䜼W$fkv_'Q#{s=(iVM/  H /Wjf y t2Td_4;nOC)ifo&}SHIk;j @f!L̐3 1#a[uh\` D{r(9&[2{uyӆ=wNs&Ј: {:.*$޵eozvpY1cpM >LX`oǖՙCm`KQwE:){^(!,qVXn ۀ ;wKh"$ML=25Ŵcd@7;Z?fki[]#>ZBb 91Y*ᆳixZu`Aۄm1!&7:d&^V$u88.eiq>'/eB/oL^\\̯TW}*ㇴR܂ +zqT`~uucZ.EOt]^ۼǏÂN_@r|+pY^Nxmd+pjeWʘS]W!˹A/m⟭vϒK@M$2^HAϨ|4^ g,;XL5ZrU4`T#dʌ8 gxv tB!:? +DnDs"*cnD˅WET) łب0EU* {Kt+\Y(稾I墉o!EtGM*M}f9PZ?cZ wd# *QEsmAw-3_|xshǜ謱؁Gu\ $4Ǻ!BpMwv·->Vй\vq5&,D\ྪa90Sd.O&&R,a ބsdZqդ*v]<0#z*:"/ReIJ7]nϝ;}Xo)sjZShx"Iն\4iƇ>irb"ﯛ5j$av*,YЈalB/q ^,o᭬=2v[S;xT.Wu-@WRq5i;l(Rv; c^_ع:΂vy Z:]3%or`nX.M 펎wd<ͷX4a?fgVOT3tCO 4|P#lBy7FuivpɏྻX3uRUaRsy$o9.zxOqۼţ~ )4^Rc;߻պA~Ձ@i6 hJy1<6纛 Wq|*7ɞȃ){-eێZSE꥝rxMVpP!32b> HP!qC)|Fz<`Z0LC) ^M +XvHTCqvwON_RI61-X/h\^^qӄ% ;9Sx,2KPcR!_CL2*1)ܹ5tnLXB6-jh I駣wlaNzs 2Ui2X*oHuHNj+;!>M=t$C~}Oka2f az i3L/o?H_ Z$l, ax֟q(O#0d =N8|cs:Dݳn蛷~/M<)l}Ϻ$[b~t V(?{wz0+Uj bʸ _R)gFw"|5 m:`LkY*w-h&xHEF]K2ly'r.OM?jcH"CPNDT۷~p:fMxˆFpm,@\xSҖnט2sS@n0}oh;7oѾ,!,i;NbᡍTȻz@ۦE VIeW.,V@W tak}.Ru|yz%n ;0i!-!oRht1R;T*5ȍc[h[DvƷ?Y%F7M+hyP/Wg:<,.:ߤ n,Kp|34̫ۓ؝3X zP]ݔҕ(K kڬwpMV&FVN\p6NN}Y\CBcy* cִ:=F7_`E,:<@A5U%5LR+.{5Jo i`QqEU=b2 Ʃ&ߔPһEViJi^֞u~ʕ5g~bū[+ ػwWGa6 j/S}ʃPUѫb(\BUӁ# U\W 39s/jqp1ݮL8b(] *w7Y%%.2cofg{By,A«x8p.jB~%ռ֦Ȱƹy u9j0 ]#N20AIfqKX#BA_/lHUыi~}mh<#wYݴrm6A8g:t(wϢh_RiJ”6 V*gs@(#@`pGd=?$7i9^<(PT$܅ :k.,9k=sg֠8[X%K$%}X嫅z1^+6ցq_V[r?RJ6٠śQ Gnh\Ukzڨfմ|Zy TEٴѾ n@7k@VОnƀNU KГ60$)aJ`>)9U+Ѭ8wnː^iA98F]-f,  bF_IeQlzgs2S)V=2y?wWXɂ9Kt5 N8=P| ˔=[mᓈjw##C@Q1_2UʹCK`<btU):=Zu[z:0{LI__"ŹEeJ XpT5Hϭ# Uqn~XGi( mz\nZiG%k;cKOIrHii1[hS6Qe鰕p\B%2HK7JasX->],oL{Γ]}>tvuܤA=LĻUJָ[?cFuYs<9?0 UA]2wPxs5א2.)g͔bcZz4&DL\8IL`^L@ZlLshϺ1"J5V aVd`'TNY !p0dJ0#;0ݥ ,53hfˁo5/:pka3(0 f*]8)Pmfht %_$$5FL4iYߦgKPɥ J Bx̣ -V:yt6wl ;v a˾> kܹTͯ;3X !VwL <Y` T"2)q9w𢍽p_p/z.Of݊#f@,s}›1Qf0AѲx5\U{b 7}L|9 HlIO!T_k$Hi%_sl6%nIILaU0T}BR|3G:#T2k:ELv vtC\R) |n:~Ӛ=6QMzHװ ;ʽu FqorͱAaSd(X=+L OW2)_7gP:Iũ)~7; C!sZ=ſ֝_W g4u:ϗV8>Ho/J vcenc˞=x~y89 70s:CCIz|5RhM94ԩn {'z3ȧ#zP`A+{* L\ѻt蠱f|=Mi7qaVzt'o:4B`cUS ɦ&[nh+"U@V|X~zs!7m>ĤMUkؗ(Q搭,*+p$as$C3pF y2 {aӸY$xNs oɅrqT ٧x#8Qq0 BJ#{A\!_oz==dҚ.f9]fCױh._xma{ԈV٢G$.11VaVi)O(749Ng]{;3YsnInw˯PBBTZsz *}^."yv"v?2xS?Az.fz5_fv]坍ҫv>G^.5\l~Xcf+|=5:ޤc5HbS34Y1/1dӚ pS;t_ qF:8H`^f, Sl)4uEɕRѓŧqJvU7 mۻWv.%F^f:4>KCªB m`\h'M&W})~ATؽƱi5]i5V)fMZ&#a6[.vӧ7Z7BbH^܆N;uE֗:[gl3'I,h (SG%)r7% ؒ aL.|jl AY8yBF ixm?7xsvaׇ?|,K23Ijhb6\E|򬸱]olWO uԈWW;l7tN..hO' =;Ui`@*mUo1 AW#*:>Y"G): 3"yZ$Xo(Ina#qhjip]g9>:%&qO *y}?9T`ֶ:Z1^siTܾ.)v~}q%)*AcҍRZ0S. U G5^SeO p_S:|C6W G9ȑ3zyD@æUuE^, &udH F3K=ѹHPjJ'x;ѼsIۅS~@"Wyi4$ }g,PӾ@C)ԟꥮ!z d/- {0ѻ{'{.oo>y?|׼58wj;)#yfSXtκ0*JHN'C^E.mۛ{NnT.fBzu)/]/Orwb=$tftÝv /׈oY0ſs7w+KOWVwޱZFyF5Vٝq ?KF{gBޞ&Ue^:bTHfKjzTX~Re/V2RR=hH}9]*[g FX\l\\֋PԂqb]~x:p{s"&wwT嫗,ogpkF`KFIЊLzKZ. Z=MX|}9ŻTB0i^-KuԦ C+` "d΁~ހvvcq4~OFnl bCD Y>9p$`3c!fnODnE$Zp +}zs3V[=.kM7NsCqQ5-2A^ỏ* h:}\̯T+N]. jThw777F~]0A9Bv|Cf]x'{q\Cu|)7O 8D~ ب]-oW76UImzۜ,vEe1s)!:R_HC2]2tQ FZP.l֡Ṣg~誉>>s4(4h'=!o]naMKX @Fxj0'@TA"hWX}]ڶiħEi9?cG/6Qwƚ (ShSFD892;a(<7Da6q[TɾjO/7 z&7(n#ձM|M.|'|]O٭YH/ /08bgU*د״j`wӝ`v09\ tARuKU"^+pYF 7c&Ԋܝ˞pwf?^D\~>hx@~v{>YJVF7|Y_8oQ3`cfD|FO/w9 .4{>$8$ wxk4Nrd@lA&P ݏR#T)oc 7^8^+ȉBG&*vm񐂧HY7Q<` aR' ?(6gv`æ8?PDjiZZUU;_R r/->pw~R3X  :0z wѦ ƶV;0̦M)6`=ov n uFooI7P  u5cqPNϤLj4/nVL\7)M}yQG!v6J`թ S<=m|[mu%NPah9VY46z]ܳ=T N7r!`Rc(^BdA/z"ԔI<iji;sm@UO*u \@XCM79d:Md]]Ζ'{~eO_0OOd걧{uW~?g6ǵ;ox }>ʕ_8?x-|'?{o>|?/x~s}>~~l_p߼~ﭼ9Ӝoc3_~7gkL+}s'j|9>s_{Ͻ%r+w_W`'-+O+g~={{tw_vi?;ͧ^wn?sjxM+}G+gG[rǯ,ƿ~}W˟[,oӓozӍw?l}g{?xQ{>g>4z7S_;/XG{'~ITswC§m_ȟe?y~pi~5?}/N?> _}7ZO/zJ}32S/O_W?x?^{>_7ߙW,W9]l~B_˗}~O {3Oe_?yM/=k_\y|񆯭/ҵ[n/t-}yO›oWE_ Gg|G}Bon%1_C>o3o7_KW__{G_\W+j[|;^1h+';?XR_/ ?s_+/?sݏVߡƣY^O|ǝ;5~O|̫G͟^O|~bwc7߲?Ox~=_XGɧ/|>uKWC|GoyǦϽGŸO}?tkϽ~]y|ڻ_VG>eد7|/g}×OG}Py׵7_ϝo_ֿZ򆷾}w5{/U/הS_ioVhQ{|c;^Ro<sCoO~Wi{wc-;O_ϟ.\{_3^7?6ׯ6_>ҹ}Gw:>o|Gϼ}_v7_{'[<_+w_>kE񣿤}W_/}[o{}w>o{'-\d?eZjm^~w~Wgw~/g3w&2i_S?W}gWm|~O]Wgw7o|eһk[w}gilmwsGgRk_oȧ?/'|QO׾uG_%ٝ]wpZ47>q~7Ϋyg`?/_yUn?|~틻eݩ={+δٯOwn~ܗo#ۿ_w?xg}w+~h~x˾ꋚO΃wc?{g{C7~s= *)~}?to~OߛSɟێrcM_ Cy?cekGV'W~xN|ۯ?ƿ>w?_|诬o'n~c]g?-}o'>7fiu^n⻮~_륯?9{W̿5_o\o?_~/^qoڏ~?|`~S_z}ܟj?ekOW?i.3K|w̅𗴲^37j[ga;>;g{>o}x孯ryꝫЯO|ǟ~߿ ~Bk`=c}zGßߟ~OyOoɷ|/Wyb?s}'_w#X_<9? _[~`iҟGy݇?6_fԹ6PY\.(dl:]K摲; ً9IY(<*2YfS,ZE΀lkG]W<^P\UY5:/ IrV$S Rf떲K/n(IUsA| x׵O%i70R3[J'DKRSsT,cNͦrϧγԘuj\R- 5`WQ!5LNTz{xr .z (-Ր! ^Ѻ#A{ͬgvSUiܹn/sFгss:_򨭒oO׍;&ǡ:~bn67E۹sKKsgf3)ZTJJU6ѰCsٜTz|ƃJmG#BrK+Njfw4ik3q2jY- U|É!s]Ԟ>]cKM_8W&1tyz3əK? ≔2r"pCy>A_b O-)H$ɕ%\\ڧ`ULN@1XN}Jp Z S=hԬ;ųg* -,8C-Ϟ?Ҡ0 _ـ6Ν=K[/:^0٧ٮ:η[+r}Iŭ|"`T};GT*nf˦VZC7 j9=>k*Wgw'1j?7V}TSv~m ?1șfjTWy6djJK RBT^R̯TB$a'P=wyvj}RZRj\t߬lx o-O s',@v|_.RIŔa*4SSUVJDas}}srC~ 9l)F@t^mk)zjl|ԌTXŁ ,{o,rj qio(Wⵊ);<:),p:XJRu# EokТjb \@ m-_d_(Wx-T vO)od14^עKo'F20hx ]zF<2ߞT~g8׶ ,R5d? rn+ōB(C;S-Z֮Y[M؄)2[aJ 2OOPR\[E6guYE^ޢ{Bn݃Z-lFYjՂMv`*it^7/ (8.fD8lҰ3R3/yO8O.Y*[elAv̌|&{`n Z)VWJ)[ݩ-՘g@, $fNеVTΗ$yeũT@U)m*%Hί(4*sW5'sŦi}[E aH,n!Q:EA7EB =r-ԉSa+$:<< 7NQ#{깢B˅]1p]-!x}.p&(r-r] &賆USF[vөSphV1 n'չV{VaWhEEKS;;/rZ-'{m] w!U:xD){zY@}"¾hP7! tc J^h^U,l;Ᵹ֦< s5X)/\se2$KR;߮5<ۥR.ԣN> 6ra䘻4ܳٱ3W"_B2 s< IeRIY]UEYtB%j8YhsQHaPy2ڨ%t0TIiĀ(:+;GNv0(? KR?hW'ትC1u6SP Ur(Y+nm k;+T<%6I"/@niPG$xS( h"_@ëkB{v\ Z-ۛL/ eֵeEtH)GLT"Pѝ^[ ڊèZz1U)`FGuA3ʗ WJW9zDlWdK]~MiZܸ t03vTQi&YzV`߻Qʯe ;_EvyGo/DTO[j?T{3~r-ҙi:IJŔ f]~f9^\)҉P"}-E|R $eztJ;J<}(S|"ÿm 6;~5䷔>BZK!ug}RiNjbf> ;EٶS!S~'S}Z%VY }9dfNc>Ir6|ʸ"Yvqm?ٓ?BZh!,}4y1mk%lm·*+/2C sۆ/mЀ*CcނұEd¬|1AXOŕ6 ep8zJn/`H?R( Rv7n₝p1ť'CCVfy[ur$].ߝ\$r>KqJINXr>|u򸘓| oHwR.u2Zbx2|ժHKgvā ˔;.!.}VkZ8,R%JH|څCf޲ŅEWR}Gu+$VٞٵkB\GJ_.tOىtRNԦZv**j힓_D)U0ҷѳSEuL+gC_ Rn">nb>.j o<Q|橦S%I_!j `L9N/KbLw/I QDND<*[a/'HmJb/Lkv3ا)g\%A9P@-i9n Jy(qlQekBDûM1wGKnnQpQTv͎cT}_=SRZ ~ d-Ȕ< (s[B E*X1{jqv"$Z'kЏ}=Æ%!/grfXc6D%ia;˳qMfgr:67>[@!˄=xT3"[pI**cx0wYI9Ӛ;]xR9I~lʲ3[PwKseO˳ vp Jsf+<=u9 37wi*=%.(q2mS\@8Ws5`I~rDMr^jttfsΕ(\܏*M͕5vAo]-ɳr\HOayREOb.͉ӝhYI `n܅~EA0| :@t{zv"VMRbSf388]092,s2&C9|'* pk2$;bógAxǁ$~J'? U0ģ9|$w>Gp;y׍QW<ޤ R]`15{*`?Abxبz* U\ՐA* Ͻn=p9 ˵~'/#[c HMn&>, .W4>;t⴫3!|ݴ3a%F} m8N `0EN;pSNFu#HE"r),vکCT +.g'#ZX́3xU`R8}{#pBfЦ¹d5.)0V;sb>d0+Z6t{.{A,X\ tߛM%%2 Nq7-xj/ޏd6ܘ]W;ۊ)Y"s0``.l"%@_Zia[U  KG1n{~gG‰]t؂4s |0Xʑ2%)e- tOW(ԉx+SҥQv 9Hj ͌7&ҒU,M:IѢJl#Uz'YQ;3z d.(LQ;dB'!hmh"M^}ɸ 5llB]#>3sXF$bji|Dzd/Ȩfˬ%3(PH–1Z\ =-M|?*t?`t04H@$?ؑ.#}qGKۃb NjB5Fv!L;d@u|QF w,>"H<"x6*X̹g$ЄKH %n7 dz 4 u,eͥTPH1AguJɬo1sf}mUffmn4=bqh| K(۶ e/SpAx;Q7ވVc /Nˆ.^+W.ƀlwȇǽ O{ pi'1S7GM3Cé l5k1{2R~$F-CXW{\H&= Qΐ'9@97 I1SCP: 7 ʣ -5}j{@)N(nʧ;ߒ\[{}J+[:2wH-Jp4;cr'1׍p &IbiƑI)P=EY0ŲJ@2u I-_XxLA\P=VFG ڽN򁋹=Yn~I[ԢJ ;ۺq=s]Oa5+Oxܗ*1'0+/qb"^!U{J :}}*ѓWE-Y14KQB_|"\}E>vIg!;&A}l8k`,npXύYj%󡇬bB; ^z_lc>' yВ]=Tʹ&v rwWηezݒf̤Kx:tBsUԞGe/-g/B4Mk E>[2:Y6s"/&'J uD1WXiߪy-[*c}zdGYwo,~.{u.1キTPp{)g *gThG}PX'ȇk<a;理K5TNX0o:(7&X{ԇ=d2{tPk.x0#z-S)RJHDJzp|yCϣX ?].xUO# Y'A59Gҽ&bI>Kz@Cxh`foqޕU+=[rBWTH^ -̶r.7oaĠV"1:{hx1/02y`d>\'| y%_duuy hC~!yrH}<;#u1>.1#|Y%)IJ.d+Kz-xY}y'yI1y@`ʿ.JA]JG= kwERO[]${Ԃǵ>K ٦JfvJ,RnuCT 6|=R"Km[]o 8>Y^ϯTkk0.Vu[ M4]F 8||yJ1LO.ʱPS0;|Ro]P&&* z(IϟR*q+Y*Iih>P܁/C SCoj˞ Ŵ~]IҕK+Mfɞ~=;3(U@`Srb~?:=HHsl漚yqYwL0ݎjX, CtK AVWy\l㈰ '~"́xkYt0i{֥E H^';J#i'chQ i4}_dv辜.ߗ}KJDiPF#Io9/sYڳ`Zx 'Xݎޮb aAP !J3@ i$\"B%@QFVz_)+HRj''FR_rJ?ngX⌂Li4`jRD'(tkіV4EOces\`Re nޠZZ+"JU>ӖoJH/愐Ǿ5CpL 8i= 4',h~+Xz6I]SJ]@buKy(@wxR]uZRp^RBISiҜĸ W\`\97iZ"a&쮴+vĩ vfvp3Qm,h 48 }Sl|Bh͜M L$ڢ=6z>~lƴ(ł*,x9RDMkCX7(h-qtR1㾥k5Iޚ_)\+l.M)2ͼ Uˌa"p&y Cfr@0AcJ T]~ 1[HL\ e`4i{/7B G@7Y7P)-W+ʚk8PnSMf۝0CkS<N5:EJ%&EL7b&)/p6P"K+eXRvE x) OJﳨT>>D@W[%ww[lђf갦{T ѣRɕJѵt$ *gJ$H+j|B7աB !Spy F5{H$\D\ (`ՠ:?7]؈켘bIs,qꟀ"dZxJMr\4~n o Ht"UQbJG(.3߿ 9q=iC2qegt.WΘg f>>ti{*w]B1{gpI~Y c["F3~Ѱ88|E=S;m%e% (b~^@WTve.3?&0֜+`-M3XFm^" h׊fV KDv'u @#kkbwl0s#pf©C\o"?-Pv8蜈Y5>7NB+v}dib;R`șO"\ђ-kכB>`K;9kNyuOy~ v[P }%"`:/!^[ T BNl[bpC20t\ک]*ÎSzIx޽qͪCl׬EWOYj' U"ݔ2 eyjMy}H>*@ZIVtv=.RYao"yO)@9?Bb*H':}F#u̬0s&#%5y3^@(3@X%̽30t,{Iz֙@X9x0Oe "2# qT3k'qpSj+ǟU@jV9K$QH#}bï.;g9qӧ]<$f$vLT%v\y@)ᏸw5%]cYwܵ1mp*=Cz&2@X~V50kс1ߛ/YxlC(%Ljl룟TveYvLzN c?\ k䘷iuuNNL /!* \gֵ#yEk$nw׸( ^ L$4rEcڨMw/@W婙-jjQ/rAɷfVleffJF>0nt +EEʵ6d`Ȧ,?Vө$*2PUt iΰj5 c! a5OrBĴ(xdzC̦Cܷt"ۙ;0d., 1ص K*i~p PdC8wMBM 1zS>c1gil\.3%-K^T!Jkio:UNCmUYT+"C'm1ZVݸuJ赎?P+ljuS*趴V0rfH0/(q m ж.2|ي9W +Ac ' %Lι"/QjaS2,h z%/JM4JbT״J+GJY'@ݳq-n1&83kQ`\W\n䙖xdY˜Ykgiޕ^&,MebD7%ڤbkE%LpfbN2QqLa'`7‚пq1(X#!hg4 gE_0*!<1y3x2+p5-VO:'fd>'xظͶlڡ~$NBDQ`-nƚ'0]{Pj6[~^ s5(GP:VuAP)OA=n,wNY8 F·2X r7FV@t ڝ8omP-҉O~xNZq޿=/ PzTiuI C׍yE=xcpBUS A.~58m8R"֫j?vJnl+eh1 =X[=ȝ)5yNbF:cӉ'J߬W3yaݙ;g/ f tg ``I D @z; ?k]zӐ5Y2wi Gne>`J,dƟs5w_JEq=}__؊*^u1l;rIw>jip@^=,(GJ 1ƪd a:W6-%(X(הxA|A5 BO9qW]lAV׿usK@6hbiS$M^!%1Z0G"m,KSSj(TS nTiFeM}-a"+<?7`cY,))3޹0P}˟m=-ɖ=k}5^zyY DYǪ s7;G=;gG_Ǩm+ w_8ӹl{{{/t`}='\=_;z0~̼ǶkFꃕd YɀLYuk57X0Q1зЏ(u/||aGv@{#,J0KK?aW;KdJ#[*FKaXBpd|_=ϭ6(}h"|衊NkІa91~oq<}:uWPH4usTT =uD), yym 60 EKE3Oa%WQs'&\u6|( )+cKm.߆ؖS0nps7g_U:, D`"Qz'\244mG21 AA #)K_hxMuagz0X wL4p5NO{ G>i^v[}q~+ڇ"UO}퟽CS- U/TNe)B=_Vʕ 'QEY`vtߔcPL[SfBa Jƴ rdRaH!dx=RmFp;U$m$n"M6 SFBlAŢ/EJy AO׳28s@ֺ-pt` ` G.KP@@/~"8;acp٦mʸpm?|JLirR-H*,b帺deewX;ɕK5' ?̡AJ=r \.XS"1pi8 o?=w\SWێ[$$7!0 P* ,3RWՊuh*~E몋R,ZZp*`O@-js!7! }sy0CD'#2L7A SؓA4Fn&&SMs rf_[ E3 |I+$u4\>?ҵ[<Y3aM' s]it4l ߀D6߶("Mర$x`9MR)c7 3Ԑ2kd~pKY 6OqQ' 1Jj'uHd{0H4Ӽ}0^SČCrfL<46^4eFS,m5T65iZC YwCKA-䃩x"Wm[KԒj 1"eX}+ExK]Nhz5u}Ӹ6QFD^TGPP[KGl7bNb4)b&Ich f8\! 2hKհ "#):@MIXk6?B挳#d}0=$|EI{`ܶ 'GIb*%Ah=&Kj18iFY"[3.@fãqpV0!!~3VD{3DPƝI_N*B, @b0/gGT*Ֆiќw? @( "}ԉa?ų& [aInP<*vG\YiH=e1 0G3l.2AT#/h5EqkV RbtT$(k1x`?;MذPD͝4|frԍ*MH$Z͹h*>0{4>:]Ba) ۍQ P \ ctӔ9 i-L82&"2ZB3d'֢qJ\p:񸀤qtJYa gdf>q$yeߣ,27F#E-"lTh==blEtcʰ$LLrpx7r[i\h["R 1MaVaOf6B.BT; ֎HyB+0)w7h wꐆ+'XU ܕD9U:xq^1!EQ/ul\b2hKjHR]FFP\i? ':<+}a#* P X*S?J^GeD@Q33*_R'F!T8jxh X tc!ށ@42*TIΜ8x ;v^zBi 0 v`eZ,.ۑvv^ʥNm0";6| "8VaL)b x=tۉr?N|'^ x;>^`iJ3sh|g~A \Cb{)$zL:D"9(iIPU 9>??C-{qࡃCR|6%̨^gR2ħQL?+;0$jK^oszgՍeKg#m%NϲBF>]0/)vNiZ$m+eCج[n\|gϫ0gg} [uftIG\Qoquy+8)BIWoUg.Wtp=7áѯ0Á m/{®Tr!ư*C^ RM%mؾpk`U{w<<^>6Up{e79TvW; _ݦ=]v <CW?k&KX׊_[ru ٕ|ώX9عc eG/-̳ea%n;Ҷ}U-??ퟃ|EΖjI8dkgU{G3;\1pp­營Ego%c+>-3aDžsK'R99H7]_6ɚA޻?9QFx1idG;vٺ# .LSV~ܽ*"=QqSX'/wpW{xV׮U(:7V,vƤuO?]ẁa?=]3paa6jOonL`†G^|7nuʥ#2f?mǫG;zum3&.)\ e!Ky񧗽׹d n7b)uחS@z֝N<趥s+'+8GcC.}1j囮gj|`d4?xAEߠqFB(DE&؁X䒉$$ P.$FAj=<pM (ƈ:$Pcp60O;g;oD/5h=E Hx !8NaO4?5"KX#zL//B-p;Y4v@?DFLTANx \SCC9e҄gE DhCnCJ}@RXLco"C$jVJTT*W`rGb A?Qxв Fea2PjQ ר/pn\~Y9r9Y51U0<9N ; 7!3 wq>IN(׋ p=yB'/Q5"ix)Am8nyO?12iq(.npQ??4*׫ߛ ˮ\궉=+pB?;}Sm=Uۦxب{л_K[\o?ZX`ӷJ}4ƞ.W{Zu3-kaس-_ TH]|[̿sIg/j?}4C}eAZ:ygg_uom8fmE(t6<[e]CB{C;dwnxYqaܰ $܀7*H?꘹?2bG#;S{;);>Q=vs)n?Ηen}O?w5SKVw΢ʘ;'C$-I G\{~uuΌZJ3+9:|cX͚EuXɽpeOϙ_)?^}w>|^S9(U?~*bB|^jZ2/g'Yx$(:lįk݈)'dwzYQwkywO 7Zܟ146d쥇_Jg"<†.YN]<9 3l)[nKl]7aƢޢikTmLDzr""b'# Oey>6Sņك%lR E{geWs_Sm\ǃKkymg=fX3tlW'k}\޼w9+.<þvYOk'ٽC5'<^}_ϩ ]/^cs?^Qwdxdh(iku~H+0nWHH*pI" FYS@wҊ}I!ɠ F${`ݐ3*;&lkƲ2eţl(5}j Oa֚CӘwYK]`%JF:H4;4jmnd5Ʀ`kv 'l9n, 7FUijq#9^Mn&ȮQ5US%C -PO..B <Ϩ.,mQW(9E(l;Pҩxos!_UWs#mPWl9IzJ L5l1h ?h&r1) %8; ua~]ոmK#oMrT&;avЮ veXH(2s.6( c0ýu,1lqF>,'h4 FTT#eڢ#AJq#<=i?\o QZZ7gKz6*물[VEc˩4%z o"Dž~K A#gaN}&;6A-%O& A}m*o/N[2czV/7bV֊8D,GI: 69I" 5ϜP<Tg*gOdJ$Dك@B !#F3R1i*$@A M/4VB78y?RŖ/S|~BLeNIXʟ1 ?=s 3K~Hn-t+:mG2vS0JukkU,)8uuIu*u!|Hlpy7mza)gp=ǙOVۭV-Rv01΄!7rC]_9""]ў ޤ(Fg#\8 ,9[ +Ou0w&}%&^gp69rSbR D֛Z$\jg rnM 5(G!iimCpVEU=((,@u0ذF/|(02L˒#-#X6&4ؕ} >mRu5`gv|Pv&Jyӕc񲠂 Q9&EqMq$tI5n&J;H`IwHGb]&z)5ɀi-͙r6쮛;H˸%=e!)z&הi\+߂3nLJQ)?ن\CyK3(pP26IQfP"O7nTPMzұdǀ|#Vr o63J>ge3LRJI t̮%"K)DmѰ \, 8xQ%V[Ajɲs6B;c.+D ƅi-mL$i+]3k$|0,+cRDoA$b8*Zavx-rf#b[z(vJƖ`S3675(CcKKìwφφ[;Q&p,\Y_kNjk+/Z#YcFrBDﻢC]*C(ljr=>*jxEjƪ{bӴGkKɴ'gYL9z%?J 342ϒcFa$U$yk 2/;^%bpZQ9lMW,R(>}Md%Us.?Zi)ӿ>?pm\ C|EMYVlɊ 9:rD˕cl5H Z|Aˆ ^nbn)H t Exz阡g380SiNurME|k_47`pU]ױF('t?3;FxF_]Op_-e7#L'rߦaZ\i]ϴuD`(u>^O;C<:( YM&O[J;4>Q ep'>GMH̀HhZ6Q}(S0zIDNgn0| (YmsLlGkvxhLO=c:8[(tCb *.(hwOr:"\ă(+VtWNy u \cI|4 yE~2/֡qZ2,wjoS;$ZvΚ& ]ݟަ)_d0ii<ҁ#}0N͜6m2r]=1w%]ftL{2(63kd k{ƾ.Zg*)kV'mn%NwSŊ qnyI9߶Tp 6Q#C³TK_T?p080ar照sA%j0Sm5M(/}4B򺹖 )5A~#ilR9LB4PČۤtZҢhx<6N>6qo x{\1FeX2@cχqYӝ6MHBoG jwH n[7LUbq6燤>Y-K.G;3TˁxZ)~W/Laa[3$EFCQʹy Xcߵt;hg!a-:ؘ.>_ +B[#9g P86\fz eZAxGc3${ͷE8^Ҵb|KA)ڠHP<ƝSʭtë8zkӴQIKڪM8"6dC]b%eՄgi$q|ASZWvm#)6;tF1bFgMu}LPCƐӳ``[+JlHbɜoX4 wL뛴d1M,SPi ?y&ه\zia7V(MgVYӎQsZ4#a;/*֌md΃ &@^scsw@g9b&PU o'хlr`^Ϯ'Û|UoYʡ)`#qxI?z+EV3`{6{ F{*gтQpkh@Us[0~.jUߪcca?B%Hi<,}/uSij-=霩J;)y_L/1܄+**mPp:3'S&-7> Db#I)+e#"@=stPeѼO#Vp]s; !<kw=,o/;~,nj97=7J&J 1?aNՈvzy|jF_LxW4F<`3'M /uR6N(޼X9ۻŴ(x4RFbuKUqfw8vqabrId2ޭWpTS庙J˴ dׇNhA0BBaT?$,?VUMpwa,&1T6;t;5֌ 6 &3w+St=O0*Ԑ`<;&7PTكfsQG@0J1<+)zJ%w3nO=Z̹+XiRҪJ І6)ݗA9B+8bdaTKev˙|0A +lڤy[1,H߹68*0VAiTZd>KFt3%Y05S*JBE p!|T`{9;bj")6{_xmnʩZ)MKĪ|[W+tΊX3/:U0#k2kuq#9i4PK[Cqm HGL Խmh0Jպʹ@cߗPEy!Gzh#Z."KFty|uPVH9- ƳGQ9%Vp!_juLbpD66YO=cZqEh?\< KbЄd"[Xkcq)wB wEhy"^c~EC؝_ȣJ\]݀C亦W ts9b4 V &"֍$n&tfJ&AZ U*ѪZO BW|ʯ+)U^ =+ñX8mA^h?naޔVZ$_Őj>`8V` gbAO1Α=: ΢Ip'їA+rHU ^IE|\KL@FCV+Rw6*ɫcOԹsYoEF`koHWEa"F3)?YJ*喇WNP3pxuҕ&iCXO(b Z($KF/tX8xS ij vB k!2= u87$s( b}Tqߐ{՜(b$tya@ /onԌ4y3X ]{BnqKC` Ps!-Ճt Z۴E=3q0dT%,{+(i%Zq5l VQ: EQK9narI757vlo֐1֦ ;!Oho꺵W7FfѲhE1wHs87h 8+dp~p/^BM2YYE%!6O/{JNз}'BG>X* }c D q(qP0cZr{-K:kM z -]v\Y"`NzmEo fq.Sխ۫gf;^/ u|08Mj肝`UcN ;ǛWnj<^һuXʈx5)۵-PCã['0ce2?[cbW9gp? t<6{ee,2 JDRy&1c49"2~6 a6`q@ʬ!,lHGE8m*_M 206蘯eyf,ԍ y˹*9`_E𤆧&\t8d] |$Mf`'+FYH' 9DИ'*AHQVvV!] x8ף_X7$x8䨩nStu 0Pxïҟ"\2~kX*@4cp!Lv()5h +&E482]Odm`'FU'E|afo<0K-e2a^wjhV0 6oVHA*Ȳ_uK aG %2ME!Ӓ/=HgI6|d&R籇6CN[J r,_LΘtW&^M;vJ,M<@LGX7yrTss?^ϴ7J:;/~|b"Jx)& 1ukK6@asv5J*"-%ܷ{;Ta}!}8ƣ W`bfxB3oj.[pH9.#=6cYUZ%}^2[VQ` ;3KV|kXIX$Թ6b<pȫ<9?گaJl+]Z%4Npƒ.M~]N&XlS&KCcڂ+3koRs4 j.qeɒzD4 `0C_ZFt5FɖxO"yƧ>:״]wuJNz>:(q zW#>??߷g_^>t^ N^:wkx+큏=u^C;cC-Qm艦+}deotJDZ?9=w~㎳9=e=~S듷4xnW\v]qsλ]g\ksz9v-r6s١O-:sആN{|C/y9O\{c}ɋރL|_O+\ޭ^:\zfk{>ۜg?}Jn~mK5n=9j_ۻ*%<3G_RxewXеp]_@iZw_|!/1l[z67-sG/bx婱~{~&XҵW˶᱇~׿c=JGspjeM<U|[l; a?U.t~{['^ϸhꛯK9+>į'+.C?gO{<Hv8OK<|M͵R~r+O즹w9PڲE^{﹵ՏqI__%wxGz+'{ooq+~#oRsyDy=>WwzzbsC:߼K_{{c-'vև?¶S̶ǖM*9^Iڷ̵ܳfeɇ_NӧsPk>_|iÿᓦWz߸_r{'^xO5=Yg1=Fn8rλkoY 7~Ǻ+ ratZkʅ7 S`;[z(aw7GF>TBS_iLu-U֩PC ]O`΍*8zQmlvkln(CjpRђTE%:B`l mSŮ vnqcFtH2zR?\J`"ezhXE7ثPDs-+΂Q i{yDZ`φQC 6gl\'fW1yv/_%̟`cZk`ɑ$1#YZ5Rk=|sA<KnސlKUJiа^I׍eꉮ6o3@)Κ:H;U~LJKo0j(kaet' TCh{~uVDc W7C:ΜMuYsA5/gc˚)ecꃁzPr%Y֢5H-n uM㺶dc%Ѩh)/Qh ^@[0W;T): RQFjZ]h V<` i_ߴVR -ɖX1Gw)L~ŋ.c7Zp>X=[yQMO H77*bL7A°Ɉl2߆OQ^V|ϛq7.²~tZ vzvSWql¼XOa ;x\EͬfؙwpT0D|hw*ĞC`/v'}˷i[f9_csJ(7B1:H9J"J)xymon-4.3.7/xymonnet/httpcookies.c0000664000175000017500000001230211630612042016575 0ustar henrikhenrik/*----------------------------------------------------------------------------*/ /* Xymon monitor network test tool. */ /* */ /* Copyright (C) 2008-2011 Henrik Storner */ /* */ /* This program is released under the GNU General Public License (GPL), */ /* version 2. See the file "COPYING" for details. */ /* */ /*----------------------------------------------------------------------------*/ static char rcsid[] = "$Id: httpcookies.c 6745 2011-09-04 06:01:06Z storner $"; #include #include #include #include #include "libxymon.h" #include "httpcookies.h" cookielist_t *cookiehead = NULL; void * cookietree; typedef struct hcookie_t { char *key; char *val; } hcookie_t; void init_session_cookies(char *urlhost, char *cknam, char *ckpath, char *ckval) { hcookie_t *itm; itm = (hcookie_t *)malloc(sizeof(hcookie_t)); itm->key = (char *)malloc(strlen(urlhost) + strlen(cknam) + strlen(ckpath) + 3); sprintf(itm->key, "%s\t%s\t%s", urlhost, cknam, ckpath); itm->val = strdup(ckval); xtreeAdd(cookietree, itm->key, itm); } void update_session_cookies(char *hostname, char *urlhost, char *headers) { char *ckhdr, *onecookie; if (!headers) return; ckhdr = headers; do { ckhdr = strstr(ckhdr, "\nSet-Cookie:"); if (ckhdr) { /* Set-Cookie: JSESSIONID=rKy8HZbLgT5W9N8; path=/ */ char *eoln, *cknam, *ckval, *ckpath; cknam = ckval = ckpath = NULL; onecookie = strchr(ckhdr, ':') + 1; onecookie += strspn(onecookie, " \t"); eoln = strchr(onecookie, '\n'); if (eoln) *eoln = '\0'; ckhdr = (eoln ? eoln : NULL); onecookie = strdup(onecookie); if (eoln) *eoln = '\n'; cknam = strtok(onecookie, "="); if (cknam) ckval = strtok(NULL, ";"); if (ckval) { char *tok, *key; xtreePos_t h; hcookie_t *itm; do { tok = strtok(NULL, ";\r"); if (tok) { tok += strspn(tok, " "); if (strncmp(tok, "path=", 5) == 0) { ckpath = tok+5; } } } while (tok); if (ckpath == NULL) ckpath = "/"; key = (char *)malloc(strlen(urlhost) + strlen(cknam) + strlen(ckpath) + 3); sprintf(key, "%s\t%s\t%s", urlhost, cknam, ckpath); h = xtreeFind(cookietree, key); if (h == xtreeEnd(cookietree)) { itm = (hcookie_t *)malloc(sizeof(hcookie_t)); itm->key = key; itm->val = strdup(ckval); xtreeAdd(cookietree, itm->key, itm); } else { itm = (hcookie_t *)xtreeData(cookietree, h); xfree(itm->val); itm->val = strdup(ckval); xfree(key); } } xfree(onecookie); } } while (ckhdr); } void save_session_cookies(void) { FILE *fd = NULL; char cookiefn[PATH_MAX]; xtreePos_t h; hcookie_t *itm; sprintf(cookiefn, "%s/etc/cookies.session", xgetenv("XYMONHOME")); fd = fopen(cookiefn, "w"); if (fd == NULL) return; for (h=xtreeFirst(cookietree); (h != xtreeEnd(cookietree)); h = xtreeNext(cookietree, h)) { char *urlhost, *ckpath, *cknam; itm = (hcookie_t *)xtreeData(cookietree, h); urlhost = strtok(itm->key, "\t"); cknam = strtok(NULL, "\t"); ckpath = strtok(NULL, "\t"); fprintf(fd, "%s\tFALSE\t%s\tFALSE\t0\t%s\t%s\n", urlhost, ckpath, cknam, itm->val); } fclose(fd); } /* This loads the cookies from the cookie-storage file */ static void load_cookies_one(char *cookiefn) { FILE *fd; char l[4096]; char *c_host, *c_path, *c_name, *c_value; int c_tailmatch, c_secure; time_t c_expire; char *p; fd = fopen(cookiefn, "r"); if (fd == NULL) return; c_host = c_path = c_name = c_value = NULL; c_tailmatch = c_secure = 0; c_expire = 0; while (fgets(l, sizeof(l), fd)) { p = strchr(l, '\n'); if (p) { *p = '\0'; p--; if ((p > l) && (*p == '\r')) *p = '\0'; } if ((l[0] != '#') && strlen(l)) { int fieldcount = 0; p = strtok(l, "\t"); if (p) { fieldcount++; c_host = p; p = strtok(NULL, "\t"); } if (p) { fieldcount++; c_tailmatch = (strcmp(p, "TRUE") == 0); p = strtok(NULL, "\t"); } if (p) { fieldcount++; c_path = p; p = strtok(NULL, "\t"); } if (p) { fieldcount++; c_secure = (strcmp(p, "TRUE") == 0); p = strtok(NULL, "\t"); } if (p) { fieldcount++; c_expire = atol(p); p = strtok(NULL, "\t"); } if (p) { fieldcount++; c_name = p; p = strtok(NULL, "\t"); } if (p) { fieldcount++; c_value = p; p = strtok(NULL, "\t"); } if ((fieldcount == 7) && (c_expire > getcurrenttime(NULL))) { /* We have a valid cookie */ cookielist_t *ck = (cookielist_t *)malloc(sizeof(cookielist_t)); ck->host = strdup(c_host); ck->tailmatch = c_tailmatch; ck->path = strdup(c_path); ck->secure = c_secure; ck->name = strdup(c_name); ck->value = strdup(c_value); ck->next = cookiehead; cookiehead = ck; } } } fclose(fd); } void load_cookies(void) { char cookiefn[PATH_MAX]; sprintf(cookiefn, "%s/etc/cookies", xgetenv("XYMONHOME")); load_cookies_one(cookiefn); sprintf(cookiefn, "%s/etc/cookies.session", xgetenv("XYMONHOME")); load_cookies_one(cookiefn); }