From 755ec717d9cd6e25767584a528034ce0ab0ee5aa Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Tue, 25 Oct 2005 15:50:08 +0000 Subject: dual-threading code looks now fairly complete and stable --- syslogd.c | 3271 +++++++++++++++++++++++++++++++------------------------------ 1 file changed, 1660 insertions(+), 1611 deletions(-) (limited to 'syslogd.c') diff --git a/syslogd.c b/syslogd.c index 418acd69..46ccb24c 100644 --- a/syslogd.c +++ b/syslogd.c @@ -343,8 +343,7 @@ int funix[MAXFUNIX] = { -1, }; /* read-only after startup */ #define TABLE_ALLPRI 0xFF /* Value to indicate all priorities in f_pmask */ #define LOG_MARK LOG_MAKEPRI(LOG_NFACILITIES, 0) /* mark "facility" */ -/* - * Flags to logmsg(). +/* Flags to logmsg(). */ /* NO LONGER NEEDED: #define IGN_CONS 0x001 * don't print on console */ #define SYNC_FILE 0x002 /* do fsync on file after printing */ @@ -591,6 +590,7 @@ typedef struct { pthread_cond_t *notFull, *notEmpty; } msgQueue; +int bRunningMultithreaded = 0; /* Is this program running in multithreaded mode? */ msgQueue *pMsgQueue = NULL; static pthread_t thrdWorker; static int bGlblDone = 0; @@ -794,33 +794,22 @@ static char template_StdDBFmt[] = "\"insert into SystemEvents (Message, Facility /* up to the next comment, prototypes that should be removed by reordering */ #ifdef USE_PTHREADS -msgQueue *queueInit (void); +static msgQueue *queueInit (void); static void *singleWorker(void *vParam); /* REMOVEME later 2005-10-24 */ #endif /* Function prototypes. */ -int main(int argc, char **argv); static char **crunch_list(char *list); -static int usage(void); -static void untty(void); static void printchopped(char *hname, char *msg, int len, int fd, int iSourceType); static void printline(char *hname, char *msg, int iSource); static void logmsg(int pri, struct msg*, int flags); static void fprintlog(register struct filed *f); -static void endtty(); static void wallmsg(register struct filed *f); static void reapchild(); static const char *cvthname(struct sockaddr_in *f); -static void domark(void); -static void domarkAlarmHdlr(); static void debug_switch(); static void logerror(char *type); static void logerrorInt(char *type, int errCode); static void logerrorSz(char *type, char *errMsg); -static void die(int sig); -#ifndef TESTING -static void doexit(int sig); -#endif -static void init(); static rsRetVal cfline(char *line, register struct filed *f); static int decode(char *name, struct code *codetab); static void sighup_handler(); @@ -2784,807 +2773,196 @@ static char *MsgGetProp(struct msg *pMsg, struct templateEntry *pTpe, */ -int main(int argc, char **argv) -{ register int i; - register char *p; -#if !defined(__GLIBC__) - int len, num_fds; -#else /* __GLIBC__ */ -#ifndef TESTING - size_t len; -#endif - int num_fds; -#endif /* __GLIBC__ */ - fd_set readfds; -#ifdef SYSLOG_INET - fd_set writefds; - struct filed *f; -#endif - -#ifdef MTRACE - mtrace(); /* this is a debug aid for leak detection - either remove - * or put in conditional compilation. 2005-01-18 RGerhards */ -#endif - -#ifndef TESTING - int fd; -#ifdef SYSLOG_INET - struct sockaddr_in frominet; - char *from; - int iTCPSess; -#endif - pid_t ppid = getpid(); -#endif - int ch; - struct hostent *hent; +static int usage(void) +{ + fprintf(stderr, "usage: rsyslogd [-dhvw] [-l hostlist] [-m markinterval] [-n] [-p path]\n" \ + " [-s domainlist] [-r port] [-t port] [-f conffile]\n"); + exit(1); /* "good" exit - done to terminate usage() */ +} +#ifdef SYSLOG_UNIXAF +static int create_unix_socket(const char *path) +{ + struct sockaddr_un sunx; + int fd; char line[MAXLINE +1]; - extern int optind; - extern char *optarg; - int maxfds; - char *pTmp; -#ifndef TESTING - chdir ("/"); -#endif - for (i = 1; i < MAXFUNIX; i++) { - funixn[i] = ""; - funix[i] = -1; - } + if (path[0] == '\0') + return -1; - while ((ch = getopt(argc, argv, "a:dhi:f:l:m:nop:r:s:t:vw")) != EOF) - switch((char)ch) { - case 'a': - if (nfunix < MAXFUNIX) - if(*optarg == ':') { - funixParseHost[nfunix] = 1; - funixn[nfunix++] = optarg+1; - } - else { - funixParseHost[nfunix] = 0; - funixn[nfunix++] = optarg; - } - else - fprintf(stderr, "Out of descriptors, ignoring %s\n", optarg); - break; - case 'd': /* debug */ - Debug = 1; - break; - case 'f': /* configuration file */ - ConfFile = optarg; - break; - case 'h': - NoHops = 0; - break; - case 'i': /* pid file name */ - PidFile = optarg; - break; - case 'l': - if (LocalHosts) { - fprintf (stderr, "Only one -l argument allowed," \ - "the first one is taken.\n"); - break; - } - LocalHosts = crunch_list(optarg); - break; - case 'm': /* mark interval */ - MarkInterval = atoi(optarg) * 60; - break; - case 'n': /* don't fork */ - NoFork = 1; - break; - case 'o': /* omit local logging (/dev/log) */ - startIndexUxLocalSockets = 1; - break; - case 'p': /* path to regular log socket */ - funixn[0] = optarg; - break; - case 'r': /* accept remote messages */ - AcceptRemote = 1; - LogPort = atoi(optarg); - break; - case 's': - if (StripDomains) { - fprintf (stderr, "Only one -s argument allowed," \ - "the first one is taken.\n"); - break; - } - StripDomains = crunch_list(optarg); - break; - case 't': /* enable tcp logging */ - bEnableTCP = -1; - TCPLstnPort = atoi(optarg); - break; - case 'v': - printf("rsyslogd %s.%s, ", VERSION, PATCHLEVEL); - printf("compiled with:\n"); -#ifdef USE_PTHREADS - printf("\tFEATURE_PTHREADS (dual-threading)\n"); -#endif -#ifdef FEATURE_REGEXP - printf("\tFEATURE_REGEXP\n"); -#endif -#ifdef WITH_DB - printf("\tFEATURE_DB\n"); -#endif -#ifndef NOLARGEFILE - printf("\tFEATURE_LARGEFILE\n"); -#endif -#ifdef SYSLOG_INET - printf("\tSYSLOG_INET (Internet/remote support)\n"); -#endif -#ifndef NDEBUG - printf("\tFEATURE_DEBUG (debug build, slow code)\n"); -#endif - printf("\nSee http://www.rsyslog.com for more information.\n"); - exit(0); /* exit for -v option - so this is a "good one" */ - case 'w': /* disable disallowed host warnigs */ - option_DisallowWarning = 0; - break; - case '?': - default: - usage(); - } - if ((argc -= optind)) - usage(); + (void) unlink(path); -#ifndef TESTING - if ( !(Debug || NoFork) ) - { - dprintf("Checking pidfile.\n"); - if (!check_pid(PidFile)) - { - signal (SIGTERM, doexit); - if (fork()) { - /* - * Parent process - */ - sleep(300); - /* - * Not reached unless something major went wrong. 5 - * minutes should be a fair amount of time to wait. - * Please note that this procedure is important since - * the father must not exit before syslogd isn't - * initialized or the klogd won't be able to flush its - * logs. -Joey - */ - exit(1); /* "good" exit - after forking, not diasabling anything */ - } - num_fds = getdtablesize(); - for (i= 0; i < num_fds; i++) - (void) close(i); - untty(); - } - else - { - fputs("rsyslogd: Already running.\n", stderr); - exit(1); /* "good" exit, done if syslogd is already running */ - } + memset(&sunx, 0, sizeof(sunx)); + sunx.sun_family = AF_UNIX; + (void) strncpy(sunx.sun_path, path, sizeof(sunx.sun_path)); + fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (fd < 0 || bind(fd, (struct sockaddr *) &sunx, + SUN_LEN(&sunx)) < 0 || + chmod(path, 0666) < 0) { + snprintf(line, sizeof(line), "cannot create %s", path); + logerror(line); + dprintf("cannot create %s (%d).\n", path, errno); + close(fd); + return -1; } - else -#endif - debugging_on = 1; -#ifndef SYSV - else - setlinebuf(stdout); -#endif - -#ifndef TESTING - /* tuck my process id away */ - if ( !Debug ) - { - dprintf("Writing pidfile.\n"); - if (!check_pid(PidFile)) - { - if (!write_pid(PidFile)) - { - dprintf("Can't write pid.\n"); - exit(1); /* exit during startup - questionable */ - } - } - else - { - dprintf("Pidfile (and pid) already exist.\n"); - exit(1); /* exit during startup - questionable */ - } - } /* if ( !Debug ) */ + return fd; +} #endif - myPid = getpid(); /* save our pid for further testing (also used for messages) */ - /* initialize the default templates - * we use template names with a SP in front - these - * can NOT be generated via the configuration file - */ - pTmp = template_TraditionalFormat; - tplAddLine(" TradFmt", &pTmp); - pTmp = template_WallFmt; - tplAddLine(" WallFmt", &pTmp); - pTmp = template_StdFwdFmt; - tplAddLine(" StdFwdFmt", &pTmp); - pTmp = template_StdUsrMsgFmt; - tplAddLine(" StdUsrMsgFmt", &pTmp); - pTmp = template_StdDBFmt; - tplAddLine(" StdDBFmt", &pTmp); +#ifdef SYSLOG_INET +static int create_udp_socket() +{ + int fd, on = 1; + struct sockaddr_in sin; + int sockflags; - /* prepare emergency logging system */ + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + logerror("syslog: Unknown protocol, suspending inet service."); + return fd; + } - consfile.f_type = F_CONSOLE; - (void) strcpy(consfile.f_un.f_fname, ctty); - cflineSetTemplateAndIOV(&consfile, " TradFmt"); - (void) gethostname(LocalHostName, sizeof(LocalHostName)); - if ( (p = strchr(LocalHostName, '.')) ) { - *p++ = '\0'; - LocalDomain = p; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(LogPort); + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, \ + (char *) &on, sizeof(on)) < 0 ) { + logerror("setsockopt(REUSEADDR), suspending inet"); + close(fd); + return -1; } - else - { - LocalDomain = ""; - + /* We need to enable BSD compatibility. Otherwise an attacker + * could flood our log files by sending us tons of ICMP errors. + */ +#ifndef BSD + if (should_use_so_bsdcompat()) { + if (setsockopt(fd, SOL_SOCKET, SO_BSDCOMPAT, \ + (char *) &on, sizeof(on)) < 0) { + logerror("setsockopt(BSDCOMPAT), suspending inet"); + close(fd); + return -1; + } + } +#endif + /* We must not block on the network socket, in case a packet + * gets lost between select and recv, otherwise the process + * will stall until the timeout, and other processes trying to + * log will also stall. + * Patch vom Colin Phipps to the original + * sysklogd source. Applied to rsyslogd on 2005-10-19. + */ + if ((sockflags = fcntl(fd, F_GETFL)) != -1) { + sockflags |= O_NONBLOCK; /* - * It's not clearly defined whether gethostname() - * should return the simple hostname or the fqdn. A - * good piece of software should be aware of both and - * we want to distribute good software. Joey - * - * Good software also always checks its return values... - * If syslogd starts up before DNS is up & /etc/hosts - * doesn't have LocalHostName listed, gethostbyname will - * return NULL. + * SETFL could fail too, so get it caught by the subsequent + * error check. */ - hent = gethostbyname(LocalHostName); - if ( hent ) - snprintf(LocalHostName, sizeof(LocalHostName), "%s", hent->h_name); - - if ( (p = strchr(LocalHostName, '.')) ) - { - *p++ = '\0'; - LocalDomain = p; - } + sockflags = fcntl(fd, F_SETFL, sockflags); + } + if (sockflags == -1) { + logerror("fcntl(O_NONBLOCK), suspending inet"); + close(fd); + return -1; } - /* - * Convert to lower case to recognize the correct domain laterly - */ - for (p = (char *)LocalDomain; *p ; p++) - if (isupper(*p)) - *p = tolower(*p); + if (bind(fd, (struct sockaddr *) &sin, sizeof(sin)) < 0) { + logerror("bind, suspending inet"); + close(fd); + return -1; + } + return fd; +} +#endif - (void) signal(SIGTERM, die); - (void) signal(SIGINT, Debug ? die : SIG_IGN); - (void) signal(SIGQUIT, Debug ? die : SIG_IGN); - (void) signal(SIGCHLD, reapchild); - (void) signal(SIGALRM, domarkAlarmHdlr); - (void) signal(SIGUSR1, Debug ? debug_switch : SIG_IGN); - (void) signal(SIGPIPE, SIG_IGN); - (void) signal(SIGXFSZ, SIG_IGN); /* do not abort if 2gig file limit is hit */ - (void) alarm(TIMERINTVL); +/* rgerhards, 2005-10-24: crunch_list is called only during option processing. So + * it is never called once rsyslogd is running (not even when HUPed). This code + * contains some exist, but they are considered safe because they only happen + * during startup. Anyhow, when we review the code here, we might want to + * reconsider the exit()s. + */ +static char **crunch_list(char *list) +{ + int count, i; + char *p, *q; + char **result = NULL; - /* Create a partial message table for all file descriptors. */ - num_fds = getdtablesize(); - dprintf("Allocated parts table for %d file descriptors.\n", num_fds); - if ((parts = (char **) malloc(num_fds * sizeof(char *))) == NULL) - { - logerror("Cannot allocate memory for message parts table."); - die(0); + p = list; + + /* strip off trailing delimiters */ + while (p[strlen(p)-1] == LIST_DELIMITER) { + count--; + p[strlen(p)-1] = '\0'; } - for(i= 0; i < num_fds; ++i) - parts[i] = NULL; - - dprintf("Starting.\n"); - init(); -#ifndef TESTING - if(Debug) { - dprintf("Debugging enabled, SIGUSR1 to turn off debugging.\n"); - debugging_on = 1; + /* cut off leading delimiters */ + while (p[0] == LIST_DELIMITER) { + count--; + p++; + } + + /* count delimiters to calculate elements */ + for (count=i=0; p[i]; i++) + if (p[i] == LIST_DELIMITER) count++; + + if ((result = (char **)malloc(sizeof(char *) * (count+2))) == NULL) { + printf ("Sorry, can't get enough memory, exiting.\n"); + exit(0); /* safe exit, because only called during startup */ } + /* - * Send a signal to the parent to it can terminate. - */ - if (myPid != ppid) - kill (ppid, SIGTERM); -#endif - /* END OF INTIALIZATION - * ... but keep in mind that we might do a restart and thus init() might - * be called again. If that happens, we must shut down all active threads, - * do the init() and then restart things. - * rgerhards, 2005-10-24 + * We now can assume that the first and last + * characters are different from any delimiters, + * so we don't have to care about this. */ -#ifdef USE_PTHREADS - /* create message queue */ - pMsgQueue = queueInit(); - if(pMsgQueue == NULL) { - fprintf (stderr, "error: could not create message queue - terminating.\n"); - exit (1); + count = 0; + while ((q=strchr(p, LIST_DELIMITER))) { + result[count] = (char *) malloc((q - p + 1) * sizeof(char)); + if (result[count] == NULL) { + printf ("Sorry, can't get enough memory, exiting.\n"); + exit(0); /* safe exit, because only called during startup */ + } + strncpy(result[count], p, q - p); + result[count][q - p] = '\0'; + p = q; p++; + count++; } - /* start up worker thread */ - { int i; - i = pthread_create(&thrdWorker, NULL, singleWorker, NULL); - dprintf("worker thread started with state %d\n", i); + if ((result[count] = \ + (char *)malloc(sizeof(char) * strlen(p) + 1)) == NULL) { + printf ("Sorry, can't get enough memory, exiting.\n"); + exit(0); /* safe exit, because only called during startup */ } + strcpy(result[count],p); + result[++count] = NULL; + +#if 0 + count=0; + while (result[count]) + dprintf ("#%d: %s\n", count, StripDomains[count++]); #endif + return result; +} - /* --------------------- Main loop begins here. ----------------------------------------- */ - for (;;) { - int nfds; - errno = 0; - FD_ZERO(&readfds); - maxfds = 0; -#ifdef SYSLOG_UNIXAF -#ifndef TESTING - /* - * Add the Unix Domain Sockets to the list of read - * descriptors. - * rgerhards 2005-08-01: we must now check if there are - * any local sockets to listen to at all. If the -o option - * is given without -a, we do not need to listen at all.. - */ - /* Copy master connections */ - for (i = startIndexUxLocalSockets; i < nfunix; i++) { - if (funix[i] != -1) { - FD_SET(funix[i], &readfds); - if (funix[i]>maxfds) maxfds=funix[i]; - } + +static void untty() +#ifdef SYSV +{ + if ( !Debug ) { + setsid(); + } + return; +} + +#else +{ + int i; + + if ( !Debug ) { + i = open(_PATH_TTY, O_RDWR); + if (i >= 0) { + (void) ioctl(i, (int) TIOCNOTTY, (char *)0); + (void) close(i); } -#endif -#endif -#ifdef SYSLOG_INET -#ifndef TESTING - /* - * Add the Internet Domain Socket to the list of read - * descriptors. - */ - if ( InetInuse && AcceptRemote ) { - FD_SET(inetm, &readfds); - if (inetm>maxfds) maxfds=inetm; - dprintf("Listening on syslog UDP port.\n"); - } - - /* Add the TCP socket to the list of read descriptors. - */ - if(bEnableTCP && sockTCPLstn != -1) { - FD_SET(sockTCPLstn, &readfds); - if (sockTCPLstn>maxfds) maxfds=sockTCPLstn; - dprintf("Listening on syslog TCP port.\n"); - /* do the sessions */ - iTCPSess = TCPSessGetNxtSess(-1); - while(iTCPSess != -1) { - int fd; - fd = TCPSessions[iTCPSess].sock; - dprintf("Adding TCP Session %d\n", fd); - FD_SET(fd, &readfds); - if (fd>maxfds) maxfds=fd; - /* now get next... */ - iTCPSess = TCPSessGetNxtSess(iTCPSess); - } - } - - /* TODO: activate the code below only if we actually need to check - * for outstanding writefds. - */ - if(1) { - /* Now add the TCP output sockets to the writefds set. This implementation - * is not optimal (performance-wise) and it should be replaced with something - * better in the longer term. I've not yet done this, as this code is - * scheduled to be replaced after the liblogging integration. - * rgerhards 2005-07-20 - */ - FD_ZERO(&writefds); - for (f = Files; f != NULL ; f = f->f_next) { - if( (f->f_type == F_FORW) - && (f->f_un.f_forw.protocol == FORW_TCP) - && (TCPSendGetStatus(f) == TCP_SEND_CONNECTING)) { - FD_SET(f->f_file, &writefds); - if(f->f_file > maxfds) - maxfds = f->f_file; - } - } - } -#endif -#endif -#ifdef TESTING - FD_SET(fileno(stdin), &readfds); - if (fileno(stdin) > maxfds) maxfds = fileno(stdin); - - dprintf("Listening on stdin. Press Ctrl-C to interrupt.\n"); -#endif - - if ( debugging_on ) { - dprintf("----------------------------------------\nCalling select, active file descriptors (max %d): ", maxfds); - for (nfds= 0; nfds <= maxfds; ++nfds) - if ( FD_ISSET(nfds, &readfds) ) - dprintf("%d ", nfds); - dprintf("\n"); - } -#ifdef SYSLOG_INET - nfds = select(maxfds+1, (fd_set *) &readfds, (fd_set *) &writefds, - (fd_set *) NULL, (struct timeval *) NULL); -#else - nfds = select(maxfds+1, (fd_set *) &readfds, (fd_set *) NULL, - (fd_set *) NULL, (struct timeval *) NULL); -#endif - if(bRequestDoMark) { - domark(); - bRequestDoMark = 0; - /* We do not use continue, because domark() is carried out - * only when something else happened. - */ - } - if (restart) { - dprintf("\nReceived SIGHUP, reloading rsyslogd.\n"); - init(); - restart = 0; - continue; - } - if (nfds == 0) { - dprintf("No select activity.\n"); - continue; - } - if (nfds < 0) { - if (errno != EINTR) - logerror("select"); - dprintf("Select interrupted.\n"); - continue; - } - - if ( debugging_on ) - { - dprintf("\nSuccessful select, descriptor count = %d, " \ - "Activity on: ", nfds); - for (nfds= 0; nfds <= maxfds; ++nfds) - if ( FD_ISSET(nfds, &readfds) ) - dprintf("%d ", nfds); - dprintf(("\n")); - } - -#ifndef TESTING -#ifdef SYSLOG_INET - /* TODO: activate the code below only if we actually need to check - * for outstanding writefds. - */ - if(1) { - /* Now check the TCP send sockets. So far, we only see if they become - * writable and then change their internal status. No real async - * writing is currently done. This code will be replaced once liblogging - * is used, thus we try not to focus too much on it. - * - * IMPORTANT: With the current code, the writefds must be checked first, - * because the readfds might have messages to be forwarded, which - * rely on the status setting that is done here! - * - * rgerhards 2005-07-20 - */ - for (f = Files; f != NULL ; f = f->f_next) { - if( (f->f_type == F_FORW) - && (f->f_un.f_forw.protocol == FORW_TCP) - && (TCPSendGetStatus(f) == TCP_SEND_CONNECTING) - && (FD_ISSET(f->f_file, &writefds))) { - dprintf("tcp send socket %d ready for writing.\n", f->f_file); - TCPSendSetStatus(f, TCP_SEND_READY); - /* Send stored message (if any) */ - if(f->f_un.f_forw.savedMsg != NULL) { - if(TCPSend(f, f->f_un.f_forw.savedMsg) != 0) { - /* error! */ - f->f_type = F_FORW_SUSP; - errno = 0; - logerror("error forwarding via tcp, suspending..."); - } - free(f->f_un.f_forw.savedMsg); - f->f_un.f_forw.savedMsg = NULL; - } - } - } - } -#endif /* #ifdef SYSLOG_INET */ -#ifdef SYSLOG_UNIXAF - for (i = 0; i < nfunix; i++) { - if ((fd = funix[i]) != -1 && FD_ISSET(fd, &readfds)) { - int iRcvd; - memset(line, '\0', sizeof(line)); - iRcvd = recv(fd, line, MAXLINE - 2, 0); - dprintf("Message from UNIX socket: #%d\n", fd); - if (iRcvd > 0) { - line[iRcvd] = line[iRcvd+1] = '\0'; - printchopped(LocalHostName, line, iRcvd + 2, fd, funixParseHost[i]); - } else if (iRcvd < 0 && errno != EINTR) { - dprintf("UNIX socket error: %d = %s.\n", \ - errno, strerror(errno)); - logerror("recvfrom UNIX"); - } - } - } -#endif - -#ifdef SYSLOG_INET - if (InetInuse && AcceptRemote && FD_ISSET(inetm, &readfds)) { - len = sizeof(frominet); - memset(line, '\0', sizeof(line)); - i = recvfrom(finet, line, MAXLINE - 2, 0, \ - (struct sockaddr *) &frominet, &len); - dprintf("Message from UDP inetd socket: #%d, host: %s\n", - inetm, inet_ntoa(frominet.sin_addr)); - if (i > 0) { - from = (char *)cvthname(&frominet); - /* Here we check if a host is permitted to send us - * syslog messages. If it isn't, we do not further - * process the message but log a warning (if we are - * configured to do this). - * rgerhards, 2005-09-26 - */ - if(isAllowedSender(pAllowedSenders_UDP, &frominet)) { - line[i] = line[i+1] = '\0'; - printchopped(from, line, i + 2, finet, 1); - } else { - if(option_DisallowWarning) { - logerrorSz("UDP message from disallowed sender %s discarded", - from); - } - } - } else if (i < 0 && errno != EINTR && errno != EAGAIN) { - /* see link below why we check EAGAIN: - * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=188194 - */ - dprintf("INET socket error: %d = %s.\n", \ - errno, strerror(errno)); - logerror("recvfrom inet"); - /* should be harmless now that we set - * BSDCOMPAT on the socket */ - sleep(1); - } - } - - if(bEnableTCP && sockTCPLstn != -1) { - /* Now check for TCP input */ - if(FD_ISSET(sockTCPLstn, &readfds)) { - dprintf("New connect on TCP inetd socket: #%d\n", sockTCPLstn); - TCPSessAccept(); - } - - /* now check the sessions */ - /* TODO: optimize the whole thing. We could stop enumerating as - * soon as we have found all sockets flagged as active. */ - iTCPSess = TCPSessGetNxtSess(-1); - while(iTCPSess != -1) { - int fd; - int state; - fd = TCPSessions[iTCPSess].sock; - if(FD_ISSET(fd, &readfds)) { - char buf[MAXLINE]; - dprintf("tcp session socket with new data: #%d\n", fd); - - /* Receive message */ - state = recv(fd, buf, sizeof(buf), 0); - if(state == 0) { - /* Session closed */ - TCPSessClose(iTCPSess); - } else if(state == -1) { - char errmsg[128]; - snprintf(errmsg, sizeof(errmsg)/sizeof(char), - "TCP session %d will be closed, error ignored", - fd); - logerror(errmsg); - TCPSessClose(iTCPSess); - } else { - /* valid data received, process it! */ - TCPSessDataRcvd(iTCPSess, buf, state); - } - } - iTCPSess = TCPSessGetNxtSess(iTCPSess); - } - } - -#endif -#else - if ( FD_ISSET(fileno(stdin), &readfds) ) { - dprintf("Message from stdin.\n"); - memset(line, '\0', sizeof(line)); - line[0] = '.'; - parts[fileno(stdin)] = NULL; - i = read(fileno(stdin), line, MAXLINE); - if (i > 0) { - printchopped(LocalHostName, line, i+1, - fileno(stdin), 0); - } else if (i < 0) { - if (errno != EINTR) { - logerror("stdin"); - } - } - FD_CLR(fileno(stdin), &readfds); - } - -#endif - } -} - -static int usage(void) -{ - fprintf(stderr, "usage: rsyslogd [-dhvw] [-l hostlist] [-m markinterval] [-n] [-p path]\n" \ - " [-s domainlist] [-r port] [-t port] [-f conffile]\n"); - exit(1); /* "good" exit - done to terminate usage() */ -} - -#ifdef SYSLOG_UNIXAF -static int create_unix_socket(const char *path) -{ - struct sockaddr_un sunx; - int fd; - char line[MAXLINE +1]; - - if (path[0] == '\0') - return -1; - - (void) unlink(path); - - memset(&sunx, 0, sizeof(sunx)); - sunx.sun_family = AF_UNIX; - (void) strncpy(sunx.sun_path, path, sizeof(sunx.sun_path)); - fd = socket(AF_UNIX, SOCK_DGRAM, 0); - if (fd < 0 || bind(fd, (struct sockaddr *) &sunx, - SUN_LEN(&sunx)) < 0 || - chmod(path, 0666) < 0) { - snprintf(line, sizeof(line), "cannot create %s", path); - logerror(line); - dprintf("cannot create %s (%d).\n", path, errno); - close(fd); - return -1; - } - return fd; -} -#endif - -#ifdef SYSLOG_INET -static int create_udp_socket() -{ - int fd, on = 1; - struct sockaddr_in sin; - int sockflags; - - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) { - logerror("syslog: Unknown protocol, suspending inet service."); - return fd; - } - - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_port = htons(LogPort); - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, \ - (char *) &on, sizeof(on)) < 0 ) { - logerror("setsockopt(REUSEADDR), suspending inet"); - close(fd); - return -1; - } - /* We need to enable BSD compatibility. Otherwise an attacker - * could flood our log files by sending us tons of ICMP errors. - */ -#ifndef BSD - if (should_use_so_bsdcompat()) { - if (setsockopt(fd, SOL_SOCKET, SO_BSDCOMPAT, \ - (char *) &on, sizeof(on)) < 0) { - logerror("setsockopt(BSDCOMPAT), suspending inet"); - close(fd); - return -1; - } - } -#endif - /* We must not block on the network socket, in case a packet - * gets lost between select and recv, otherwise the process - * will stall until the timeout, and other processes trying to - * log will also stall. - * Patch vom Colin Phipps to the original - * sysklogd source. Applied to rsyslogd on 2005-10-19. - */ - if ((sockflags = fcntl(fd, F_GETFL)) != -1) { - sockflags |= O_NONBLOCK; - /* - * SETFL could fail too, so get it caught by the subsequent - * error check. - */ - sockflags = fcntl(fd, F_SETFL, sockflags); - } - if (sockflags == -1) { - logerror("fcntl(O_NONBLOCK), suspending inet"); - close(fd); - return -1; - } - - if (bind(fd, (struct sockaddr *) &sin, sizeof(sin)) < 0) { - logerror("bind, suspending inet"); - close(fd); - return -1; - } - return fd; -} -#endif - -/* rgerhards, 2005-10-24: crunch_list is called only during option processing. So - * it is never called once rsyslogd is running (not even when HUPed). This code - * contains some exist, but they are considered safe because they only happen - * during startup. Anyhow, when we review the code here, we might want to - * reconsider the exit()s. - */ -static char **crunch_list(char *list) -{ - int count, i; - char *p, *q; - char **result = NULL; - - p = list; - - /* strip off trailing delimiters */ - while (p[strlen(p)-1] == LIST_DELIMITER) { - count--; - p[strlen(p)-1] = '\0'; - } - /* cut off leading delimiters */ - while (p[0] == LIST_DELIMITER) { - count--; - p++; - } - - /* count delimiters to calculate elements */ - for (count=i=0; p[i]; i++) - if (p[i] == LIST_DELIMITER) count++; - - if ((result = (char **)malloc(sizeof(char *) * (count+2))) == NULL) { - printf ("Sorry, can't get enough memory, exiting.\n"); - exit(0); /* safe exit, because only called during startup */ - } - - /* - * We now can assume that the first and last - * characters are different from any delimiters, - * so we don't have to care about this. - */ - count = 0; - while ((q=strchr(p, LIST_DELIMITER))) { - result[count] = (char *) malloc((q - p + 1) * sizeof(char)); - if (result[count] == NULL) { - printf ("Sorry, can't get enough memory, exiting.\n"); - exit(0); /* safe exit, because only called during startup */ - } - strncpy(result[count], p, q - p); - result[count][q - p] = '\0'; - p = q; p++; - count++; - } - if ((result[count] = \ - (char *)malloc(sizeof(char) * strlen(p) + 1)) == NULL) { - printf ("Sorry, can't get enough memory, exiting.\n"); - exit(0); /* safe exit, because only called during startup */ - } - strcpy(result[count],p); - result[++count] = NULL; - -#if 0 - count=0; - while (result[count]) - dprintf ("#%d: %s\n", count, StripDomains[count++]); -#endif - return result; -} - - -static void untty() -#ifdef SYSV -{ - if ( !Debug ) { - setsid(); - } - return; -} - -#else -{ - int i; - - if ( !Debug ) { - i = open(_PATH_TTY, O_RDWR); - if (i >= 0) { - (void) ioctl(i, (int) TIOCNOTTY, (char *)0); - (void) close(i); - } - } -} + } +} #endif @@ -4076,7 +3454,55 @@ static void processMsg(struct msg *pMsg) * do so many external definitons. * rgerhards, 2005-10-24 */ -msgQueue *queueInit (void) + +/* shuts down the worker process. The worker will first finish + * with the message queue. Control returns, when done. + * This function is intended to be called during syslogd shutdown + * AND restat (init()!). + * rgerhards, 2005-10-25 + */ +static void stopWorker(void) +{ + if(bRunningMultithreaded) { + /* we could run single-threaded if there was an error + * during startup. Then, we obviously do not need to + * do anything to stop the worker ;) + */ + dprintf("Initiating worker thread shutdown sequence...\n"); + /* We are now done with all messages, so we need to wake up the + * worker thread and then wait for it to finish. + */ + bGlblDone = 1; + /* It's actually not "not empty" below but awaking the worker. The worker + * then finds out that it shall terminate and does so. + */ + pthread_cond_signal(pMsgQueue->notEmpty); + pthread_join(thrdWorker, NULL); + bRunningMultithreaded = 0; + dprintf("Worker thread terminated.\n"); + } +} + + +/* starts the worker thread. It must be made sure that the queue is + * already existing and the worker is NOT already running. + * rgerhards 2005-10-25 + */ +static void startWorker(void) +{ + int i; + if(pMsgQueue == NULL) { + bGlblDone = 0; /* we are NOT done (else worker would immediately terminate) */ + i = pthread_create(&thrdWorker, NULL, singleWorker, NULL); + dprintf("Worker thread started with state %d.\n", i); + bRunningMultithreaded = 1; + } else { + dprintf("message queue not existing, remaining single-threaded.\n"); + } +} + + +static msgQueue *queueInit (void) { msgQueue *q; @@ -4097,7 +3523,7 @@ msgQueue *queueInit (void) return (q); } -void queueDelete (msgQueue *q) +static void queueDelete (msgQueue *q) { pthread_mutex_destroy (q->mut); free (q->mut); @@ -4108,7 +3534,7 @@ void queueDelete (msgQueue *q) free (q); } -void queueAdd (msgQueue *q, void* in) +static void queueAdd (msgQueue *q, void* in) { q->buf[q->tail] = in; q->tail++; @@ -4121,7 +3547,7 @@ void queueAdd (msgQueue *q, void* in) return; } -void queueDel (msgQueue *q, struct msg **out) +static void queueDel (msgQueue *q, struct msg **out) { *out = (struct msg*) q->buf[q->head]; @@ -4165,6 +3591,8 @@ static void *singleWorker(void *vParam) MsgDestruct(pMsg); /* If you need a delay for testing, here do a */ /* sleep(1); */ + } else { /* the mutex must be unlocked in any case (important for termination) */ + pthread_mutex_unlock(fifo->mut); } if(debugging_on && bGlblDone && !fifo->empty) dprintf("Worker does not yet terminate because it still has messages to process.\n"); @@ -4195,7 +3623,7 @@ static void enqueueMsg(struct msg *pMsg) assert(pMsg != NULL); - if(fifo == NULL) { + if(bRunningMultithreaded == 0) { /* multi-threading is not yet initialized, happens e.g. * during startup and restart. rgerhards, 2005-10-25 */ @@ -4376,6 +3804,10 @@ void logmsg(int pri, struct msg *pMsg, int flags) * * To aid this functionality, I am moving the rest of the code (the actual * consumer) to its own method, now called "processMsg()". + * + * rgerhards, 2005-10-25: as of now, the dual-threading code is now in place. + * It is an optional feature and even when enabled, rsyslogd will run single-threaded + * if it gets any errors during thread creation. */ pMsg->msgFlags = flags; @@ -5053,7 +4485,6 @@ void endutent(void) * Adjust the size of a variable to prevent a buffer overflow * should _PATH_DEV ever contain something different than "/dev/". */ - static void wallmsg(register struct filed *f) { @@ -5167,8 +4598,7 @@ static void reapchild() errno = saved_errno; } -/* - * Return a printable representation of a host address. +/* Return a printable representation of a host address. */ static const char *cvthname(struct sockaddr_in *f) { @@ -5187,15 +4617,13 @@ static const char *cvthname(struct sockaddr_in *f) inet_ntoa(f->sin_addr)); return (inet_ntoa(f->sin_addr)); } - /* - * Convert to lower case, just like LocalDomain above + /* Convert to lower case, just like LocalDomain above */ for (p = (char *)hp->h_name; *p ; p++) if (isupper(*p)) *p = tolower(*p); - /* - * Notice that the string still contains the fqdn, but your + /* Notice that the string still contains the fqdn, but your * hostname and domain are separated by a '\0'. */ if ((p = strchr(hp->h_name, '.'))) { @@ -5318,8 +4746,7 @@ static void logerrorInt(char *type, int errCode) return; } -/* - * Print syslogd errors some place. +/* Print syslogd errors some place. */ static void logerror(char *type) { @@ -5379,19 +4806,9 @@ static void die(int sig) } #ifdef USE_PTHREADS - /* We are now done with all messages, so we need to wake up the - * worker thread and then wait for it to finish. - */ - /* TODO: this code is not really working. It's just good as a test - * harness!! It dumps *at least* because we have no qeuue! - */ - bGlblDone = 1; - /* It's actually not "not empty" below but awaking the worker. The worker - * then finds out that it shall terminate and does so. - */ - pthread_cond_signal(pMsgQueue->notEmpty); - pthread_join(thrdWorker, NULL); - /* delete fifo here! */ + stopWorker(); + queueDelete(pMsgQueue); /* delete fifo here! */ + pMsgQueue = 0; #endif @@ -5634,8 +5051,7 @@ void cfsysline(char *p) } -/* - * INIT -- Initialize syslogd from configuration table +/* INIT -- Initialize syslogd from configuration table */ static void init() { @@ -5681,17 +5097,19 @@ static void init() dprintf("Called init.\n"); Initialized = 0; if(Files != NULL) { + struct filed *fPrev; dprintf("Initializing log structures.\n"); f = Files; while (f != NULL) { /* flush any pending output */ - if (f->f_prevcount) + if (f->f_prevcount) { /* rgerhards: 2004-11-09: I am now changing it, but * I am not sure it is correct as done. * TODO: verify later! */ fprintlog(f); + } /* free iovec if it was allocated */ if(f->f_iov != NULL) { @@ -5725,8 +5143,9 @@ static void init() } # endif /* done with this entry, we now need to delete itself */ + fPrev = f; f = f->f_next; - free(f); + free(fPrev); } /* Reflect the deletion of the Files linked list. */ @@ -5879,8 +5298,9 @@ static void init() if(Debug) { printf("Active selectors:\n"); - for (f = Files; f; f = f->f_next) { - if (f->f_type != F_UNUSED) { + for (f = Files; f != NULL ; f = f->f_next) { + if (1) { + //if (f->f_type != F_UNUSED) { if(f->pCSProgNameComp != NULL) printf("tag: '%s'\n", rsCStrGetSzStr(f->pCSProgNameComp)); if(f->eHostnameCmpMode != HN_NO_COMP) @@ -6326,916 +5746,1545 @@ static rsRetVal cflineProcessTradPRIFilter(char **pline, register struct filed * while (*p == ',' || *p == ' ') p++; } - - p = q; + + p = q; + } + + /* skip to action part */ + while (*p == '\t' || *p == ' ') + p++; + + *pline = p; + return RS_RET_OK; +} + + +/* + * Helper to cfline(). This function takes the filter part of a property + * based filter and decodes it. It processes the line up to the beginning + * of the action part. A pointer to that beginnig is passed back to the caller. + * rgerhards 2005-09-15 + */ +static rsRetVal cflineProcessPropFilter(char **pline, register struct filed *f) +{ + rsParsObj *pPars; + rsCStrObj *pCSCompOp; + rsRetVal iRet; + int iOffset; /* for compare operations */ + + assert(pline != NULL); + assert(*pline != NULL); + assert(f != NULL); + + dprintf(" - property-based filter\n"); + errno = 0; /* keep strerror() stuff out of logerror messages */ + + f->f_filter_type = FILTER_PROP; + + /* create parser object starting with line string without leading colon */ + if((iRet = rsParsConstructFromSz(&pPars, (*pline)+1)) != RS_RET_OK) { + logerrorInt("Error %d constructing parser object - ignoring selector", iRet); + return(iRet); + } + + /* read property */ + iRet = parsDelimCStr(pPars, &f->f_filterData.prop.pCSPropName, ',', 1, 1); + if(iRet != RS_RET_OK) { + logerrorInt("error %d parsing filter property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* read operation */ + iRet = parsDelimCStr(pPars, &pCSCompOp, ',', 1, 1); + if(iRet != RS_RET_OK) { + logerrorInt("error %d compare operation property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* we now first check if the condition is to be negated. To do so, we first + * must make sure we have at least one char in the param and then check the + * first one. + * rgerhards, 2005-09-26 + */ + if(rsCStrLen(pCSCompOp) > 0) { + if(*rsCStrGetBufBeg(pCSCompOp) == '!') { + f->f_filterData.prop.isNegated = 1; + iOffset = 1; /* ignore '!' */ + } else { + f->f_filterData.prop.isNegated = 0; + iOffset = 0; + } + } else { + f->f_filterData.prop.isNegated = 0; + iOffset = 0; + } + + if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, "contains", 8)) { + f->f_filterData.prop.operation = FIOP_CONTAINS; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, "isequal", 7)) { + f->f_filterData.prop.operation = FIOP_ISEQUAL; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, "startswith", 10)) { + f->f_filterData.prop.operation = FIOP_STARTSWITH; + } else { + logerrorSz("error: invalid compare operation '%s' - ignoring selector", + rsCStrGetSzStr(pCSCompOp)); + } + RSFREEOBJ(pCSCompOp); /* no longer needed */ + + /* read compare value */ + iRet = parsQuotedCStr(pPars, &f->f_filterData.prop.pCSCompValue); + if(iRet != RS_RET_OK) { + logerrorInt("error %d compare value property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* skip to action part */ + if((iRet = parsSkipWhitespace(pPars)) != RS_RET_OK) { + logerrorInt("error %d skipping to action part - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* cleanup */ + *pline = *pline + rsParsGetParsePointer(pPars) + 1; + /* we are adding one for the skipped initial ":" */ + + return rsParsDestruct(pPars); +} + + +/* + * Helper to cfline(). This function interprets a BSD host selector line + * from the config file ("+/-hostname"). It stores it for further reference. + * rgerhards 2005-10-19 + */ +static rsRetVal cflineProcessHostSelector(char **pline, register struct filed *f) +{ + rsRetVal iRet; + + assert(pline != NULL); + assert(*pline != NULL); + assert(**pline == '-' || **pline == '+'); + assert(f != NULL); + + dprintf(" - host selector line\n"); + + /* check include/exclude setting */ + if(**pline == '+') { + eDfltHostnameCmpMode = HN_COMP_MATCH; + } else { /* we do not check for '-', it must be, else we wouldn't be here */ + eDfltHostnameCmpMode = HN_COMP_NOMATCH; + } + (*pline)++; /* eat + or - */ + + /* the below is somewhat of a quick hack, but it is efficient (this is + * why it is in here. "+*" resets the tag selector with BSD syslog. We mimic + * this, too. As it is easy to check that condition, we do not fire up a + * parser process, just make sure we do not address beyond our space. + * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 + */ + if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { + dprintf("resetting BSD-like hostname filter\n"); + eDfltHostnameCmpMode = HN_NO_COMP; + if(pDfltHostnameCmp != NULL) { + if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, NULL)) != RS_RET_OK) + return(iRet); + pDfltHostnameCmp = NULL; + } + } else { + dprintf("setting BSD-like hostname filter to '%s'\n", *pline); + if(pDfltHostnameCmp == NULL) { + /* create string for parser */ + if((iRet = rsCStrConstructFromszStr(&pDfltHostnameCmp, *pline)) != RS_RET_OK) + return(iRet); + } else { /* string objects exists, just update... */ + if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, *pline)) != RS_RET_OK) + return(iRet); + } } - - /* skip to action part */ - while (*p == '\t' || *p == ' ') - p++; - - *pline = p; return RS_RET_OK; } /* - * Helper to cfline(). This function takes the filter part of a property - * based filter and decodes it. It processes the line up to the beginning - * of the action part. A pointer to that beginnig is passed back to the caller. - * rgerhards 2005-09-15 + * Helper to cfline(). This function interprets a BSD tag selector line + * from the config file ("!tagname"). It stores it for further reference. + * rgerhards 2005-10-18 */ -static rsRetVal cflineProcessPropFilter(char **pline, register struct filed *f) +static rsRetVal cflineProcessTagSelector(char **pline, register struct filed *f) { - rsParsObj *pPars; - rsCStrObj *pCSCompOp; rsRetVal iRet; - int iOffset; /* for compare operations */ assert(pline != NULL); assert(*pline != NULL); + assert(**pline == '!'); assert(f != NULL); - dprintf(" - property-based filter\n"); - errno = 0; /* keep strerror() stuff out of logerror messages */ + dprintf(" - programname selector line\n"); - f->f_filter_type = FILTER_PROP; + (*pline)++; /* eat '!' */ - /* create parser object starting with line string without leading colon */ - if((iRet = rsParsConstructFromSz(&pPars, (*pline)+1)) != RS_RET_OK) { - logerrorInt("Error %d constructing parser object - ignoring selector", iRet); - return(iRet); + /* the below is somewhat of a quick hack, but it is efficient (this is + * why it is in here. "!*" resets the tag selector with BSD syslog. We mimic + * this, too. As it is easy to check that condition, we do not fire up a + * parser process, just make sure we do not address beyond our space. + * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 + */ + if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { + dprintf("resetting programname filter\n"); + if(pDfltProgNameCmp != NULL) { + if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, NULL)) != RS_RET_OK) + return(iRet); + pDfltProgNameCmp = NULL; + } + } else { + dprintf("setting programname filter to '%s'\n", *pline); + if(pDfltProgNameCmp == NULL) { + /* create string for parser */ + if((iRet = rsCStrConstructFromszStr(&pDfltProgNameCmp, *pline)) != RS_RET_OK) + return(iRet); + } else { /* string objects exists, just update... */ + if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, *pline)) != RS_RET_OK) + return(iRet); + } } + return RS_RET_OK; +} - /* read property */ - iRet = parsDelimCStr(pPars, &f->f_filterData.prop.pCSPropName, ',', 1, 1); - if(iRet != RS_RET_OK) { - logerrorInt("error %d parsing filter property - ignoring selector", iRet); - rsParsDestruct(pPars); - return(iRet); + +/* + * Crack a configuration file line + * rgerhards 2004-11-17: well, I somewhat changed this function. It now does NOT + * handle config lines in general, but only lines that reflect actual filter + * pairs (the original syslog message line format). Extended lines (those starting + * with "$" have been filtered out by the caller and are passed to another function (cfsysline()). + * Please note, however, that I needed to make changes in the line syntax to support + * assignment of format definitions to a file. So it is not (yet) 100% transparent. + * Eventually, we can overcome this limitation by prefixing the actual action indicator + * (e.g. "/file..") by something (e.g. "$/file..") - but for now, we just modify it... + */ +static rsRetVal cfline(char *line, register struct filed *f) +{ + char *p; + register char *q; + register int i; + int syncfile; + rsRetVal iRet; +#ifdef SYSLOG_INET + struct hostent *hp; + int bErr; +#endif + char szTemplateName[128]; +#ifdef WITH_DB + int iMySQLPropErr = 0; +#endif + + dprintf("cfline(%s)", line); + + errno = 0; /* keep strerror() stuff out of logerror messages */ + p = line; + + /* check which filter we need to pull... */ + switch(*p) { + case ':': + iRet = cflineProcessPropFilter(&p, f); + break; + case '!': + iRet = cflineProcessTagSelector(&p, f); + return iRet; /* in this case, we are done */ + case '+': + case '-': + iRet = cflineProcessHostSelector(&p, f); + return iRet; /* in this case, we are done */ + default: + iRet = cflineProcessTradPRIFilter(&p, f); + break; } - /* read operation */ - iRet = parsDelimCStr(pPars, &pCSCompOp, ',', 1, 1); + /* check if that went well... */ if(iRet != RS_RET_OK) { - logerrorInt("error %d compare operation property - ignoring selector", iRet); - rsParsDestruct(pPars); - return(iRet); + f->f_type = F_UNUSED; + return iRet; } - - /* we now first check if the condition is to be negated. To do so, we first - * must make sure we have at least one char in the param and then check the - * first one. - * rgerhards, 2005-09-26 + + /* we now check if there are some global (BSD-style) filter conditions + * and, if so, we copy them over. rgerhards, 2005-10-18 */ - if(rsCStrLen(pCSCompOp) > 0) { - if(*rsCStrGetBufBeg(pCSCompOp) == '!') { - f->f_filterData.prop.isNegated = 1; - iOffset = 1; /* ignore '!' */ + if(pDfltProgNameCmp != NULL) + if((iRet = rsCStrConstructFromCStr(&(f->pCSProgNameComp), pDfltProgNameCmp)) != RS_RET_OK) + return(iRet); + + if(eDfltHostnameCmpMode != HN_NO_COMP) { + f->eHostnameCmpMode = eDfltHostnameCmpMode; + if((iRet = rsCStrConstructFromCStr(&(f->pCSHostnameComp), pDfltHostnameCmp)) != RS_RET_OK) + return(iRet); + } + + if (*p == '-') { + syncfile = 0; + p++; + } else + syncfile = 1; + + dprintf("leading char in action: %c\n", *p); + switch (*p) + { + case '@': +#ifdef SYSLOG_INET + ++p; /* eat '@' */ + if(*p == '@') { /* indicator for TCP! */ + f->f_un.f_forw.protocol = FORW_TCP; + ++p; /* eat this '@', too */ + /* in this case, we also need a mutex... */ +# ifdef USE_PTHREADS + pthread_mutex_init(&f->f_un.f_forw.mtxTCPSend, 0); +# endif } else { - f->f_filterData.prop.isNegated = 0; - iOffset = 0; + f->f_un.f_forw.protocol = FORW_UDP; } - } else { - f->f_filterData.prop.isNegated = 0; - iOffset = 0; - } + /* extract the host first (we do a trick - we + * replace the ';' or ':' with a '\0') + * now skip to port and then template name + * rgerhards 2005-07-06 + */ + for(q = p ; *p && *p != ';' && *p != ':' ; ++p) + /* JUST SKIP */; + if(*p == ':') { /* process port */ + register int i = 0; + *p = '\0'; /* trick to obtain hostname (later)! */ + for(++p ; *p && isdigit(*p) ; ++p) { + i = i * 10 + *p - '0'; + } + f->f_un.f_forw.port = i; + } + + /* now skip to template */ + bErr = 0; + while(*p && *p != ';') { + if(*p && *p != ';' && !isspace(*p)) { + if(bErr == 0) { /* only 1 error msg! */ + bErr = 1; + errno = 0; + logerror("invalid selector line (port), probably not doing what was intended"); + } + } + ++p; + } + + if(*p == ';') { + *p = '\0'; /* trick to obtain hostname (later)! */ + ++p; + /* Now look for the template! */ + cflineParseTemplateName(f, &p, szTemplateName, + sizeof(szTemplateName) / sizeof(char)); + } else + szTemplateName[0] = '\0'; + if(szTemplateName[0] == '\0') { + /* we do not have a template, so let's use the default */ + strcpy(szTemplateName, " StdFwdFmt"); + } + + /* first set the f->f_type */ + if ( (hp = gethostbyname(q)) == NULL ) { + f->f_type = F_FORW_UNKN; + f->f_prevcount = INET_RETRY_MAX; + f->f_time = time((time_t *) NULL); + } else { + f->f_type = F_FORW; + } + + /* then try to find the template and re-set f_type to UNUSED + * if it can not be found. */ + cflineSetTemplateAndIOV(f, szTemplateName); + if(f->f_type == F_UNUSED) + /* safety measure to make sure we have a valid + * selector line before we continue down below. + * rgerhards 2005-07-29 + */ + break; + + (void) strcpy(f->f_un.f_forw.f_hname, q); + memset((char *) &f->f_un.f_forw.f_addr, 0, + sizeof(f->f_un.f_forw.f_addr)); + f->f_un.f_forw.f_addr.sin_family = AF_INET; + if(f->f_un.f_forw.port == 0) + f->f_un.f_forw.port = 514; + f->f_un.f_forw.f_addr.sin_port = htons(f->f_un.f_forw.port); + + dprintf("forwarding host: '%s:%d/%s' template '%s'\n", + q, f->f_un.f_forw.port, + f->f_un.f_forw.protocol == FORW_UDP ? "udp" : "tcp", + szTemplateName); + + if ( f->f_type == F_FORW ) + memcpy((char *) &f->f_un.f_forw.f_addr.sin_addr, hp->h_addr, hp->h_length); + /* + * Otherwise the host might be unknown due to an + * inaccessible nameserver (perhaps on the same + * host). We try to get the ip number later, like + * FORW_SUSP. + */ +#endif + break; - if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, "contains", 8)) { - f->f_filterData.prop.operation = FIOP_CONTAINS; - } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, "isequal", 7)) { - f->f_filterData.prop.operation = FIOP_ISEQUAL; - } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, "startswith", 10)) { - f->f_filterData.prop.operation = FIOP_STARTSWITH; - } else { - logerrorSz("error: invalid compare operation '%s' - ignoring selector", - rsCStrGetSzStr(pCSCompOp)); - } - RSFREEOBJ(pCSCompOp); /* no longer needed */ + case '$': + /* rgerhards 2005-06-21: this is a special setting for output-channel + * defintions. In the long term, this setting will probably replace + * anything else, but for the time being we must co-exist with the + * traditional mode lines. + */ + cflineParseOutchannel(f, p); + f->f_file = open(f->f_un.f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, + 0644); + break; - /* read compare value */ - iRet = parsQuotedCStr(pPars, &f->f_filterData.prop.pCSCompValue); - if(iRet != RS_RET_OK) { - logerrorInt("error %d compare value property - ignoring selector", iRet); - rsParsDestruct(pPars); - return(iRet); - } + case '|': + case '/': + /* rgerhards 2004-11-17: from now, we need to have different + * processing, because after the first comma, the template name + * to use is specified. So we need to scan for the first coma first + * and then look at the rest of the line. + */ + cflineParseFileName(f, p); + if(f->f_type == F_UNUSED) + /* safety measure to make sure we have a valid + * selector line before we continue down below. + * rgerhards 2005-07-29 + */ + break; - /* skip to action part */ - if((iRet = parsSkipWhitespace(pPars)) != RS_RET_OK) { - logerrorInt("error %d skipping to action part - ignoring selector", iRet); - rsParsDestruct(pPars); - return(iRet); - } + if (syncfile) + f->f_flags |= SYNC_FILE; + if (f->f_type == F_PIPE) { + f->f_file = open(f->f_un.f_fname, O_RDWR|O_NONBLOCK); + } else { + f->f_file = open(f->f_un.f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, + 0644); + } + + if ( f->f_file < 0 ){ + f->f_file = -1; + dprintf("Error opening log file: %s\n", f->f_un.f_fname); + logerror(f->f_un.f_fname); + break; + } + if (isatty(f->f_file)) { + f->f_type = F_TTY; + untty(); + } + if (strcmp(p, ctty) == 0) + f->f_type = F_CONSOLE; + break; - /* cleanup */ - *pline = *pline + rsParsGetParsePointer(pPars) + 1; - /* we are adding one for the skipped initial ":" */ + case '*': + dprintf ("write-all"); + f->f_type = F_WALL; + if(*(p+1) == ';') { + /* we have a template specifier! */ + p += 2; /* eat "*;" */ + cflineParseTemplateName(f, &p, szTemplateName, + sizeof(szTemplateName) / sizeof(char)); + } + else /* assign default format if none given! */ + szTemplateName[0] = '\0'; + if(szTemplateName[0] == '\0') + strcpy(szTemplateName, " WallFmt"); + cflineSetTemplateAndIOV(f, szTemplateName); + if(f->f_type == F_UNUSED) + /* safety measure to make sure we have a valid + * selector line before we continue down below. + * rgerhards 2005-07-29 + */ + break; - return rsParsDestruct(pPars); -} + dprintf(" template '%s'\n", szTemplateName); + break; + case '~': /* rgerhards 2005-09-09: added support for discard */ + dprintf ("discard\n"); + f->f_type = F_DISCARD; + break; -/* - * Helper to cfline(). This function interprets a BSD host selector line - * from the config file ("+/-hostname"). It stores it for further reference. - * rgerhards 2005-10-19 - */ -static rsRetVal cflineProcessHostSelector(char **pline, register struct filed *f) -{ - rsRetVal iRet; + case '>': /* rger 2004-10-28: added support for MySQL + * >server,dbname,userid,password + * rgerhards 2005-08-12: changed rsyslogd so that + * if no DB is selected and > is used, an error + * message is logged. + */ +#ifndef WITH_DB + f->f_type = F_UNUSED; + errno = 0; + logerror("write to database action in config file, but rsyslogd compiled without " + "database functionality - ignored"); +#else /* WITH_DB defined! */ + f->f_type = F_MYSQL; + p++; + + /* Now we read the MySQL connection properties + * and verify that the properties are valid. + */ + if(getSubString(&p, f->f_dbsrv, MAXHOSTNAMELEN+1, ',')) + iMySQLPropErr++; + if(*f->f_dbsrv == '\0') + iMySQLPropErr++; + if(getSubString(&p, f->f_dbname, _DB_MAXDBLEN+1, ',')) + iMySQLPropErr++; + if(*f->f_dbname == '\0') + iMySQLPropErr++; + if(getSubString(&p, f->f_dbuid, _DB_MAXUNAMELEN+1, ',')) + iMySQLPropErr++; + if(*f->f_dbuid == '\0') + iMySQLPropErr++; + if(getSubString(&p, f->f_dbpwd, _DB_MAXPWDLEN+1, ';')) + iMySQLPropErr++; + if(*p == '\n' || *p == '\0') { + /* assign default format if none given! */ + szTemplateName[0] = '\0'; + } else { + /* we have a template specifier! */ + cflineParseTemplateName(f, &p, szTemplateName, + sizeof(szTemplateName) / sizeof(char)); + } - assert(pline != NULL); - assert(*pline != NULL); - assert(**pline == '-' || **pline == '+'); - assert(f != NULL); + if(szTemplateName[0] == '\0') + strcpy(szTemplateName, " StdDBFmt"); - dprintf(" - host selector line\n"); + cflineSetTemplateAndIOV(f, szTemplateName); + + /* we now check if the template was present. If not, we + * can abort this run as the selector line has been + * disabled. If we don't abort, we'll core dump + * below. rgerhards 2005-07-29 + */ + if(f->f_type == F_UNUSED) + break; - /* check include/exclude setting */ - if(**pline == '+') { - eDfltHostnameCmpMode = HN_COMP_MATCH; - } else { /* we do not check for '-', it must be, else we wouldn't be here */ - eDfltHostnameCmpMode = HN_COMP_NOMATCH; - } - (*pline)++; /* eat + or - */ + dprintf(" template '%s'\n", szTemplateName); + + /* If db used, the template have to use the SQL option. + This is for your own protection (prevent sql injection). */ + if (f->f_pTpl->optFormatForSQL == 0) { + f->f_type = F_UNUSED; + logerror("DB logging disabled. You have to use" + " the SQL or stdSQL option in your template!\n"); + break; + } + + /* If we dedect invalid properties, we disable logging, + * because right properties are vital at this place. + * Retries make no sense. + */ + if (iMySQLPropErr) { + f->f_type = F_UNUSED; + dprintf("Trouble with MySQL conncetion properties.\n" + "MySQL logging disabled.\n"); + } else { + initMySQL(f); + } +#endif /* #ifdef WITH_DB */ + break; - /* the below is somewhat of a quick hack, but it is efficient (this is - * why it is in here. "+*" resets the tag selector with BSD syslog. We mimic - * this, too. As it is easy to check that condition, we do not fire up a - * parser process, just make sure we do not address beyond our space. - * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 - */ - if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { - dprintf("resetting BSD-like hostname filter\n"); - eDfltHostnameCmpMode = HN_NO_COMP; - if(pDfltHostnameCmp != NULL) { - if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, NULL)) != RS_RET_OK) - return(iRet); - pDfltHostnameCmp = NULL; + case '^': /* bkalkbrenner 2005-09-20: execute shell command */ + dprintf("exec\n"); + ++p; + cflineParseFileName(f, p); + if (f->f_type == F_FILE) { + f->f_type = F_SHELL; } - } else { - dprintf("setting BSD-like hostname filter to '%s'\n", *pline); - if(pDfltHostnameCmp == NULL) { - /* create string for parser */ - if((iRet = rsCStrConstructFromszStr(&pDfltHostnameCmp, *pline)) != RS_RET_OK) - return(iRet); - } else { /* string objects exists, just update... */ - if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, *pline)) != RS_RET_OK) - return(iRet); + break; + + default: + dprintf ("users: %s\n", p); /* ASP */ + f->f_type = F_USERS; + for (i = 0; i < MAXUNAMES && *p && *p != ';'; i++) { + for (q = p; *q && *q != ',' && *q != ';'; ) + q++; + (void) strncpy(f->f_un.f_uname[i], p, UNAMESZ); + if ((q - p) > UNAMESZ) + f->f_un.f_uname[i][UNAMESZ] = '\0'; + else + f->f_un.f_uname[i][q - p] = '\0'; + while (*q == ',' || *q == ' ') + q++; + p = q; + } + /* done, now check if we have a template name + * TODO: we need to handle the case where i >= MAXUNAME! + */ + szTemplateName[0] = '\0'; + if(*p == ';') { + /* we have a template specifier! */ + ++p; /* eat ";" */ + cflineParseTemplateName(f, &p, szTemplateName, + sizeof(szTemplateName) / sizeof(char)); } + if(szTemplateName[0] == '\0') + strcpy(szTemplateName, " StdUsrMsgFmt"); + cflineSetTemplateAndIOV(f, szTemplateName); + /* Please note that we would need to check if the template + * was found. If not, f->f_type would be F_UNUSED and we + * can NOT carry on processing. These checks can be seen + * on all other selector line code above. However, as we + * do not have anything else to do here, we do not include + * this check. Should you add any further processing at + * this point here, you must first add a check for this + * condition! + * rgerhards 2005-07-29 + */ + break; } return RS_RET_OK; } /* - * Helper to cfline(). This function interprets a BSD tag selector line - * from the config file ("!tagname"). It stores it for further reference. - * rgerhards 2005-10-18 + * Decode a symbolic name to a numeric value */ -static rsRetVal cflineProcessTagSelector(char **pline, register struct filed *f) -{ - rsRetVal iRet; - assert(pline != NULL); - assert(*pline != NULL); - assert(**pline == '!'); - assert(f != NULL); +int decode(name, codetab) + char *name; + struct code *codetab; +{ + register struct code *c; + register char *p; + char buf[80]; - dprintf(" - programname selector line\n"); + dprintf ("symbolic name: %s", name); + if (isdigit(*name)) + { + dprintf ("\n"); + return (atoi(name)); + } + (void) strncpy(buf, name, 79); + for (p = buf; *p; p++) + if (isupper(*p)) + *p = tolower(*p); + for (c = codetab; c->c_name; c++) + if (!strcmp(buf, c->c_name)) + { + dprintf (" ==> %d\n", c->c_val); + return (c->c_val); + } + return (-1); +} - (*pline)++; /* eat '!' */ +void dprintf(char *fmt, ...) +{ +# ifdef USE_PTHREADS + static int bWasNL = FALSE; +# endif + va_list ap; - /* the below is somewhat of a quick hack, but it is efficient (this is - * why it is in here. "!*" resets the tag selector with BSD syslog. We mimic - * this, too. As it is easy to check that condition, we do not fire up a - * parser process, just make sure we do not address beyond our space. - * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 + if ( !(Debug && debugging_on) ) + return; + +# ifdef USE_PTHREADS + /* TODO: The bWasNL handler does not really work. It works if no thread + * switching occurs during non-NL messages. Else, things are messed + * up. Anyhow, it works well enough to provide useful help during + * getting this up and running. It is questionable if the extra effort + * is worth fixing it, giving the limited appliability. + * rgerhards, 2005-10-25 */ - if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { - dprintf("resetting programname filter\n"); - if(pDfltProgNameCmp != NULL) { - if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, NULL)) != RS_RET_OK) - return(iRet); - pDfltProgNameCmp = NULL; - } - } else { - dprintf("setting programname filter to '%s'\n", *pline); - if(pDfltProgNameCmp == NULL) { - /* create string for parser */ - if((iRet = rsCStrConstructFromszStr(&pDfltProgNameCmp, *pline)) != RS_RET_OK) - return(iRet); - } else { /* string objects exists, just update... */ - if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, *pline)) != RS_RET_OK) - return(iRet); - } + if(bWasNL) { + fprintf(stdout, "%8.8d: ", (unsigned int) pthread_self()); } - return RS_RET_OK; + bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? TRUE : FALSE; +# endif + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + + fflush(stdout); + return; } /* - * Crack a configuration file line - * rgerhards 2004-11-17: well, I somewhat changed this function. It now does NOT - * handle config lines in general, but only lines that reflect actual filter - * pairs (the original syslog message line format). Extended lines (those starting - * with "$" have been filtered out by the caller and are passed to another function (cfsysline()). - * Please note, however, that I needed to make changes in the line syntax to support - * assignment of format definitions to a file. So it is not (yet) 100% transparent. - * Eventually, we can overcome this limitation by prefixing the actual action indicator - * (e.g. "/file..") by something (e.g. "$/file..") - but for now, we just modify it... + * The following function is resposible for handling a SIGHUP signal. Since + * we are now doing mallocs/free as part of init we had better not being + * doing this during a signal handler. Instead this function simply sets + * a flag variable which will tell the main loop to go through a restart. */ -static rsRetVal cfline(char *line, register struct filed *f) +void sighup_handler() { - char *p; - register char *q; - register int i; - int syncfile; - rsRetVal iRet; -#ifdef SYSLOG_INET - struct hostent *hp; - int bErr; -#endif - char szTemplateName[128]; -#ifdef WITH_DB - int iMySQLPropErr = 0; -#endif - - dprintf("cfline(%s)", line); - - errno = 0; /* keep strerror() stuff out of logerror messages */ - p = line; + restart = 1; + signal(SIGHUP, sighup_handler); + return; +} - /* check which filter we need to pull... */ - switch(*p) { - case ':': - iRet = cflineProcessPropFilter(&p, f); - break; - case '!': - iRet = cflineProcessTagSelector(&p, f); - return iRet; /* in this case, we are done */ - case '+': - case '-': - iRet = cflineProcessHostSelector(&p, f); - return iRet; /* in this case, we are done */ - default: - iRet = cflineProcessTradPRIFilter(&p, f); - break; - } +#ifdef WITH_DB +/* + * The following function is responsible for initiatlizing a + * MySQL connection. + * Initially added 2004-10-28 mmeckelein + */ +static void initMySQL(register struct filed *f) +{ + int iCounter = 0; + assert(f != NULL); - /* check if that went well... */ - if(iRet != RS_RET_OK) { - f->f_type = F_UNUSED; - return iRet; - } + if (checkDBErrorState(f)) + return; - /* we now check if there are some global (BSD-style) filter conditions - * and, if so, we copy them over. rgerhards, 2005-10-18 - */ - if(pDfltProgNameCmp != NULL) - if((iRet = rsCStrConstructFromCStr(&(f->pCSProgNameComp), pDfltProgNameCmp)) != RS_RET_OK) - return(iRet); - - if(eDfltHostnameCmpMode != HN_NO_COMP) { - f->eHostnameCmpMode = eDfltHostnameCmpMode; - if((iRet = rsCStrConstructFromCStr(&(f->pCSHostnameComp), pDfltHostnameCmp)) != RS_RET_OK) - return(iRet); - } - - if (*p == '-') { - syncfile = 0; - p++; - } else - syncfile = 1; - - dprintf("leading char in action: %c\n", *p); - switch (*p) - { - case '@': -#ifdef SYSLOG_INET - ++p; /* eat '@' */ - if(*p == '@') { /* indicator for TCP! */ - f->f_un.f_forw.protocol = FORW_TCP; - ++p; /* eat this '@', too */ - /* in this case, we also need a mutex... */ -# ifdef USE_PTHREADS - pthread_mutex_init(&f->f_un.f_forw.mtxTCPSend, 0); -# endif + mysql_init(&f->f_hmysql); + do { + /* Connect to database */ + if (!mysql_real_connect(&f->f_hmysql, f->f_dbsrv, f->f_dbuid, f->f_dbpwd, f->f_dbname, 0, NULL, 0)) { + /* if also the second attempt failed + we call the error handler */ + if(iCounter) + DBErrorHandler(f); } else { - f->f_un.f_forw.protocol = FORW_UDP; - } - /* extract the host first (we do a trick - we - * replace the ';' or ':' with a '\0') - * now skip to port and then template name - * rgerhards 2005-07-06 - */ - for(q = p ; *p && *p != ';' && *p != ':' ; ++p) - /* JUST SKIP */; - if(*p == ':') { /* process port */ - register int i = 0; - *p = '\0'; /* trick to obtain hostname (later)! */ - for(++p ; *p && isdigit(*p) ; ++p) { - i = i * 10 + *p - '0'; - } - f->f_un.f_forw.port = i; - } - - /* now skip to template */ - bErr = 0; - while(*p && *p != ';') { - if(*p && *p != ';' && !isspace(*p)) { - if(bErr == 0) { /* only 1 error msg! */ - bErr = 1; - errno = 0; - logerror("invalid selector line (port), probably not doing what was intended"); - } - } - ++p; + f->f_timeResumeOnError = 0; /* We have a working db connection */ + dprintf("connected successfully to db\n"); } + iCounter++; + } while (mysql_errno(&f->f_hmysql) && iCounter<2); +} + +/* + * The following function is responsible for closing a + * MySQL connection. + * Initially added 2004-10-28 + */ +static void closeMySQL(register struct filed *f) +{ + assert(f != NULL); + dprintf("in closeMySQL\n"); + mysql_close(&f->f_hmysql); +} + +/* + * Reconnect a MySQL connection. + * Initially added 2004-12-02 + */ +static void reInitMySQL(register struct filed *f) +{ + assert(f != NULL); + + dprintf("reInitMySQL\n"); + closeMySQL(f); /* close the current handle */ + initMySQL(f); /* new connection */ +} + +/* + * The following function writes the current log entry + * to an established MySQL session. + * Initially added 2004-10-28 + */ +static void writeMySQL(register struct filed *f) +{ + char *psz; + int iCounter=0; + assert(f != NULL); + /* dprintf("in writeMySQL()\n"); */ + iovCreate(f); + psz = iovAsString(f); - if(*p == ';') { - *p = '\0'; /* trick to obtain hostname (later)! */ - ++p; - /* Now look for the template! */ - cflineParseTemplateName(f, &p, szTemplateName, - sizeof(szTemplateName) / sizeof(char)); - } else - szTemplateName[0] = '\0'; - if(szTemplateName[0] == '\0') { - /* we do not have a template, so let's use the default */ - strcpy(szTemplateName, " StdFwdFmt"); - } + if (checkDBErrorState(f)) + return; - /* first set the f->f_type */ - if ( (hp = gethostbyname(q)) == NULL ) { - f->f_type = F_FORW_UNKN; - f->f_prevcount = INET_RETRY_MAX; - f->f_time = time((time_t *) NULL); - } else { - f->f_type = F_FORW; + /* + * Now we are trying to insert the data. + * + * If the first attampt failes we simply try a second one. If also + * the second attampt failed we discard this message and enable + * the "delay" error hanlding. + */ + do { + /* query */ + if(mysql_query(&f->f_hmysql, psz)) { + + /* if also the second attempt failed + we call the error handler */ + if(iCounter) + DBErrorHandler(f); + } + else { + /* dprintf("db insert sucessfully\n"); */ } + iCounter++; + } while (mysql_errno(&f->f_hmysql) && iCounter<2); +} - /* then try to find the template and re-set f_type to UNUSED - * if it can not be found. */ - cflineSetTemplateAndIOV(f, szTemplateName); - if(f->f_type == F_UNUSED) - /* safety measure to make sure we have a valid - * selector line before we continue down below. - * rgerhards 2005-07-29 - */ - break; +/** + * DBErrorHandler + * + * Call this function if an db error apears. It will initiate + * the "delay" handling which stopped the db logging for some + * time. + */ +static void DBErrorHandler(register struct filed *f) +{ + char errMsg[512]; + /* TODO: + * NO DB connection -> Can not log to DB + * -------------------- + * Case 1: db server unavailable + * We can check after a specified time interval if the server is up. + * Also a reason can be a down DNS service. + * Case 2: uid, pwd or dbname are incorrect + * If this is a fault in the syslog.conf we have no chance to recover. But + * if it is a problem of the DB we can make a retry after some time. Possible + * are that the admin has not already set up the database table. Or he has not + * created the database user yet. + * Case 3: unkown error + * If we get an unkowon error here, we should in any case try to recover after + * a specified time interval. + * + * Insert failed -> Can not log to DB + * -------------------- + * If the insert fails it is never a good idea to give up. Only an + * invalid sql sturcture (wrong template) force us to disable db + * logging. + * + * Think about diffrent "delay" for diffrent errors! + */ + errno = 0; + snprintf(errMsg, sizeof(errMsg)/sizeof(char), + "db error (%d): %s\n", mysql_errno(&f->f_hmysql), + mysql_error(&f->f_hmysql)); - (void) strcpy(f->f_un.f_forw.f_hname, q); - memset((char *) &f->f_un.f_forw.f_addr, 0, - sizeof(f->f_un.f_forw.f_addr)); - f->f_un.f_forw.f_addr.sin_family = AF_INET; - if(f->f_un.f_forw.port == 0) - f->f_un.f_forw.port = 514; - f->f_un.f_forw.f_addr.sin_port = htons(f->f_un.f_forw.port); + /* Enable "delay" */ + f->f_timeResumeOnError = time(&f->f_timeResumeOnError) + _DB_DELAYTIMEONERROR ; + f->f_iLastDBErrNo = mysql_errno(&f->f_hmysql); - dprintf("forwarding host: '%s:%d/%s' template '%s'\n", - q, f->f_un.f_forw.port, - f->f_un.f_forw.protocol == FORW_UDP ? "udp" : "tcp", - szTemplateName); + /* Log error is the last step. */ + logerror(errMsg); +} - if ( f->f_type == F_FORW ) - memcpy((char *) &f->f_un.f_forw.f_addr.sin_addr, hp->h_addr, hp->h_length); - /* - * Otherwise the host might be unknown due to an - * inaccessible nameserver (perhaps on the same - * host). We try to get the ip number later, like - * FORW_SUSP. - */ -#endif - break; +/** + * checkDBErrorState + * + * Check if we can go on with database logging or if we should wait + * a little bit longer. It also check if the DB hanlde is still valid. + * If it is necessary, it takes action to reinitiate the db connection. + * + * \ret int Returns 0 if successful (no error) + */ +int checkDBErrorState(register struct filed *f) +{ + assert(f != NULL); + /* dprintf("in checkDBErrorState, timeResumeOnError: %d\n", f->f_timeResumeOnError); */ - case '$': - /* rgerhards 2005-06-21: this is a special setting for output-channel - * defintions. In the long term, this setting will probably replace - * anything else, but for the time being we must co-exist with the - * traditional mode lines. - */ - cflineParseOutchannel(f, p); - f->f_file = open(f->f_un.f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, - 0644); - break; + /* If timeResumeOnError == 0 no error occured, + we can return with 0 (no error) */ + if (f->f_timeResumeOnError == 0) + return 0; + + (void) time(&now); + /* Now we know an error occured. We check timeResumeOnError + if we can process. If we have not reach the resume time + yet, we return an error status. */ + if (f->f_timeResumeOnError > now) + { + /* dprintf("Wait time is not over yet.\n"); */ + return 1; + } + + /* Ok, we can try to resume the database logging. First + we have to reset the status (timeResumeOnError) and + the last error no. */ + /* TODO: + * To improve this code it would be better to check + if we really need to reInit the db connection. If + only the insert failed and the db conncetcion is + still valid, we need no reInit. + Of course, if an unkown error appeared, we should + reInit. */ + /* rgerhards 2004-12-08: I think it is pretty unlikely + * that we can re-use a connection after the error. So I guess + * the connection must be closed and re-opened in all cases + * (as it is done currently). When we come back to optimize + * this code, we should anyhow see if there are cases where + * we could keep it open. I just doubt this won't be the case. + * I added this comment (and did not remove Michaels) just so + * that we all know what we are looking for. + */ + f->f_timeResumeOnError = 0; + f->f_iLastDBErrNo = 0; + reInitMySQL(f); + return 0; - case '|': - case '/': - /* rgerhards 2004-11-17: from now, we need to have different - * processing, because after the first comma, the template name - * to use is specified. So we need to scan for the first coma first - * and then look at the rest of the line. +} + +#endif /* #ifdef WITH_DB */ + +/** + * getSubString + * + * Copy a string byte by byte until the occurrence + * of a given separator. + * + * \param ppSrc Pointer to a pointer of the source array of characters. If a + separator detected the Pointer points to the next char after the + separator. Except if the end of the string is dedected ('\n'). + Then it points to the terminator char. + * \param pDst Pointer to the destination array of characters. Here the substing + will be stored. + * \param DstSize Maximum numbers of characters to store. + * \param cSep Separator char. + * \ret int Returns 0 if no error occured. + */ +int getSubString(char **ppSrc, char *pDst, size_t DstSize, char cSep) +{ + char *pSrc = *ppSrc; + int iErr = 0; /* 0 = no error, >0 = error */ + while(*pSrc != cSep && *pSrc != '\0' && DstSize>1) { + *pDst++ = *(pSrc)++; + DstSize--; + } + /* check if the Dst buffer was to small */ + if (*pSrc != cSep && *pSrc != '\0') + { + dprintf("in getSubString, error Src buffer > Dst buffer\n"); + iErr = 1; + } + if (*pSrc == '\0') + /* this line was missing, causing ppSrc to be invalid when it + * was returned in case of end-of-string. rgerhards 2005-07-29 */ - cflineParseFileName(f, p); - if(f->f_type == F_UNUSED) - /* safety measure to make sure we have a valid - * selector line before we continue down below. - * rgerhards 2005-07-29 - */ - break; + *ppSrc = pSrc; + else + *ppSrc = pSrc+1; + *pDst = '\0'; + return iErr; +} + - if (syncfile) - f->f_flags |= SYNC_FILE; - if (f->f_type == F_PIPE) { - f->f_file = open(f->f_un.f_fname, O_RDWR|O_NONBLOCK); - } else { - f->f_file = open(f->f_un.f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, - 0644); - } - - if ( f->f_file < 0 ){ - f->f_file = -1; - dprintf("Error opening log file: %s\n", f->f_un.f_fname); - logerror(f->f_un.f_fname); - break; - } - if (isatty(f->f_file)) { - f->f_type = F_TTY; - untty(); - } - if (strcmp(p, ctty) == 0) - f->f_type = F_CONSOLE; - break; - case '*': - dprintf ("write-all"); - f->f_type = F_WALL; - if(*(p+1) == ';') { - /* we have a template specifier! */ - p += 2; /* eat "*;" */ - cflineParseTemplateName(f, &p, szTemplateName, - sizeof(szTemplateName) / sizeof(char)); - } - else /* assign default format if none given! */ - szTemplateName[0] = '\0'; - if(szTemplateName[0] == '\0') - strcpy(szTemplateName, " WallFmt"); - cflineSetTemplateAndIOV(f, szTemplateName); - if(f->f_type == F_UNUSED) - /* safety measure to make sure we have a valid - * selector line before we continue down below. - * rgerhards 2005-07-29 - */ - break; +static void mainloop(void) +{ + int i; +#if !defined(__GLIBC__) + int len, num_fds; +#else /* __GLIBC__ */ +#ifndef TESTING + size_t len; +#endif +#endif /* __GLIBC__ */ + fd_set readfds; +#ifdef SYSLOG_INET + fd_set writefds; + struct filed *f; +#endif - dprintf(" template '%s'\n", szTemplateName); - break; +#ifndef TESTING + int fd; +#ifdef SYSLOG_INET + struct sockaddr_in frominet; + char *from; + int iTCPSess; +#endif +#endif - case '~': /* rgerhards 2005-09-09: added support for discard */ - dprintf ("discard\n"); - f->f_type = F_DISCARD; - break; + char line[MAXLINE +1]; + int maxfds; - case '>': /* rger 2004-10-28: added support for MySQL - * >server,dbname,userid,password - * rgerhards 2005-08-12: changed rsyslogd so that - * if no DB is selected and > is used, an error - * message is logged. - */ -#ifndef WITH_DB - f->f_type = F_UNUSED; + /* --------------------- Main loop begins here. ----------------------------------------- */ + for (;;) { + int nfds; errno = 0; - logerror("write to database action in config file, but rsyslogd compiled without " - "database functionality - ignored"); -#else /* WITH_DB defined! */ - f->f_type = F_MYSQL; - p++; - - /* Now we read the MySQL connection properties - * and verify that the properties are valid. + FD_ZERO(&readfds); + maxfds = 0; +#ifdef SYSLOG_UNIXAF +#ifndef TESTING + /* + * Add the Unix Domain Sockets to the list of read + * descriptors. + * rgerhards 2005-08-01: we must now check if there are + * any local sockets to listen to at all. If the -o option + * is given without -a, we do not need to listen at all.. */ - if(getSubString(&p, f->f_dbsrv, MAXHOSTNAMELEN+1, ',')) - iMySQLPropErr++; - if(*f->f_dbsrv == '\0') - iMySQLPropErr++; - if(getSubString(&p, f->f_dbname, _DB_MAXDBLEN+1, ',')) - iMySQLPropErr++; - if(*f->f_dbname == '\0') - iMySQLPropErr++; - if(getSubString(&p, f->f_dbuid, _DB_MAXUNAMELEN+1, ',')) - iMySQLPropErr++; - if(*f->f_dbuid == '\0') - iMySQLPropErr++; - if(getSubString(&p, f->f_dbpwd, _DB_MAXPWDLEN+1, ';')) - iMySQLPropErr++; - if(*p == '\n' || *p == '\0') { - /* assign default format if none given! */ - szTemplateName[0] = '\0'; - } else { - /* we have a template specifier! */ - cflineParseTemplateName(f, &p, szTemplateName, - sizeof(szTemplateName) / sizeof(char)); + /* Copy master connections */ + for (i = startIndexUxLocalSockets; i < nfunix; i++) { + if (funix[i] != -1) { + FD_SET(funix[i], &readfds); + if (funix[i]>maxfds) maxfds=funix[i]; + } + } +#endif +#endif +#ifdef SYSLOG_INET +#ifndef TESTING + /* + * Add the Internet Domain Socket to the list of read + * descriptors. + */ + if ( InetInuse && AcceptRemote ) { + FD_SET(inetm, &readfds); + if (inetm>maxfds) maxfds=inetm; + dprintf("Listening on syslog UDP port.\n"); } - if(szTemplateName[0] == '\0') - strcpy(szTemplateName, " StdDBFmt"); + /* Add the TCP socket to the list of read descriptors. + */ + if(bEnableTCP && sockTCPLstn != -1) { + FD_SET(sockTCPLstn, &readfds); + if (sockTCPLstn>maxfds) maxfds=sockTCPLstn; + dprintf("Listening on syslog TCP port.\n"); + /* do the sessions */ + iTCPSess = TCPSessGetNxtSess(-1); + while(iTCPSess != -1) { + int fd; + fd = TCPSessions[iTCPSess].sock; + dprintf("Adding TCP Session %d\n", fd); + FD_SET(fd, &readfds); + if (fd>maxfds) maxfds=fd; + /* now get next... */ + iTCPSess = TCPSessGetNxtSess(iTCPSess); + } + } - cflineSetTemplateAndIOV(f, szTemplateName); - - /* we now check if the template was present. If not, we - * can abort this run as the selector line has been - * disabled. If we don't abort, we'll core dump - * below. rgerhards 2005-07-29 + /* TODO: activate the code below only if we actually need to check + * for outstanding writefds. */ - if(f->f_type == F_UNUSED) - break; + if(1) { + /* Now add the TCP output sockets to the writefds set. This implementation + * is not optimal (performance-wise) and it should be replaced with something + * better in the longer term. I've not yet done this, as this code is + * scheduled to be replaced after the liblogging integration. + * rgerhards 2005-07-20 + */ + FD_ZERO(&writefds); + for (f = Files; f != NULL ; f = f->f_next) { + if( (f->f_type == F_FORW) + && (f->f_un.f_forw.protocol == FORW_TCP) + && (TCPSendGetStatus(f) == TCP_SEND_CONNECTING)) { + FD_SET(f->f_file, &writefds); + if(f->f_file > maxfds) + maxfds = f->f_file; + } + } + } +#endif +#endif +#ifdef TESTING + FD_SET(fileno(stdin), &readfds); + if (fileno(stdin) > maxfds) maxfds = fileno(stdin); - dprintf(" template '%s'\n", szTemplateName); - - /* If db used, the template have to use the SQL option. - This is for your own protection (prevent sql injection). */ - if (f->f_pTpl->optFormatForSQL == 0) { - f->f_type = F_UNUSED; - logerror("DB logging disabled. You have to use" - " the SQL or stdSQL option in your template!\n"); - break; + dprintf("Listening on stdin. Press Ctrl-C to interrupt.\n"); +#endif + + if ( debugging_on ) { + dprintf("----------------------------------------\nCalling select, active file descriptors (max %d): ", maxfds); + for (nfds= 0; nfds <= maxfds; ++nfds) + if ( FD_ISSET(nfds, &readfds) ) + dprintf("%d ", nfds); + dprintf("\n"); } - - /* If we dedect invalid properties, we disable logging, - * because right properties are vital at this place. - * Retries make no sense. - */ - if (iMySQLPropErr) { - f->f_type = F_UNUSED; - dprintf("Trouble with MySQL conncetion properties.\n" - "MySQL logging disabled.\n"); - } else { - initMySQL(f); +#ifdef SYSLOG_INET + nfds = select(maxfds+1, (fd_set *) &readfds, (fd_set *) &writefds, + (fd_set *) NULL, (struct timeval *) NULL); +#else + nfds = select(maxfds+1, (fd_set *) &readfds, (fd_set *) NULL, + (fd_set *) NULL, (struct timeval *) NULL); +#endif + if(bRequestDoMark) { + domark(); + bRequestDoMark = 0; + /* We do not use continue, because domark() is carried out + * only when something else happened. + */ + } + if (restart) { + dprintf("\nReceived SIGHUP, reloading rsyslogd.\n"); +# ifdef USE_PTHREADS + stopWorker(); +# endif + init(); +# ifdef USE_PTHREADS + startWorker(); +# endif + restart = 0; + continue; + } + if (nfds == 0) { + dprintf("No select activity.\n"); + continue; + } + if (nfds < 0) { + if (errno != EINTR) + logerror("select"); + dprintf("Select interrupted.\n"); + continue; } -#endif /* #ifdef WITH_DB */ - break; - case '^': /* bkalkbrenner 2005-09-20: execute shell command */ - dprintf("exec\n"); - ++p; - cflineParseFileName(f, p); - if (f->f_type == F_FILE) { - f->f_type = F_SHELL; + if ( debugging_on ) + { + dprintf("\nSuccessful select, descriptor count = %d, " \ + "Activity on: ", nfds); + for (nfds= 0; nfds <= maxfds; ++nfds) + if ( FD_ISSET(nfds, &readfds) ) + dprintf("%d ", nfds); + dprintf(("\n")); } - break; - default: - dprintf ("users: %s\n", p); /* ASP */ - f->f_type = F_USERS; - for (i = 0; i < MAXUNAMES && *p && *p != ';'; i++) { - for (q = p; *q && *q != ',' && *q != ';'; ) - q++; - (void) strncpy(f->f_un.f_uname[i], p, UNAMESZ); - if ((q - p) > UNAMESZ) - f->f_un.f_uname[i][UNAMESZ] = '\0'; - else - f->f_un.f_uname[i][q - p] = '\0'; - while (*q == ',' || *q == ' ') - q++; - p = q; - } - /* done, now check if we have a template name - * TODO: we need to handle the case where i >= MAXUNAME! +#ifndef TESTING +#ifdef SYSLOG_INET + /* TODO: activate the code below only if we actually need to check + * for outstanding writefds. */ - szTemplateName[0] = '\0'; - if(*p == ';') { - /* we have a template specifier! */ - ++p; /* eat ";" */ - cflineParseTemplateName(f, &p, szTemplateName, - sizeof(szTemplateName) / sizeof(char)); + if(1) { + /* Now check the TCP send sockets. So far, we only see if they become + * writable and then change their internal status. No real async + * writing is currently done. This code will be replaced once liblogging + * is used, thus we try not to focus too much on it. + * + * IMPORTANT: With the current code, the writefds must be checked first, + * because the readfds might have messages to be forwarded, which + * rely on the status setting that is done here! + * + * rgerhards 2005-07-20 + */ + for (f = Files; f != NULL ; f = f->f_next) { + if( (f->f_type == F_FORW) + && (f->f_un.f_forw.protocol == FORW_TCP) + && (TCPSendGetStatus(f) == TCP_SEND_CONNECTING) + && (FD_ISSET(f->f_file, &writefds))) { + dprintf("tcp send socket %d ready for writing.\n", f->f_file); + TCPSendSetStatus(f, TCP_SEND_READY); + /* Send stored message (if any) */ + if(f->f_un.f_forw.savedMsg != NULL) { + if(TCPSend(f, f->f_un.f_forw.savedMsg) != 0) { + /* error! */ + f->f_type = F_FORW_SUSP; + errno = 0; + logerror("error forwarding via tcp, suspending..."); + } + free(f->f_un.f_forw.savedMsg); + f->f_un.f_forw.savedMsg = NULL; + } + } + } } - if(szTemplateName[0] == '\0') - strcpy(szTemplateName, " StdUsrMsgFmt"); - cflineSetTemplateAndIOV(f, szTemplateName); - /* Please note that we would need to check if the template - * was found. If not, f->f_type would be F_UNUSED and we - * can NOT carry on processing. These checks can be seen - * on all other selector line code above. However, as we - * do not have anything else to do here, we do not include - * this check. Should you add any further processing at - * this point here, you must first add a check for this - * condition! - * rgerhards 2005-07-29 - */ - break; - } - return RS_RET_OK; -} +#endif /* #ifdef SYSLOG_INET */ +#ifdef SYSLOG_UNIXAF + for (i = 0; i < nfunix; i++) { + if ((fd = funix[i]) != -1 && FD_ISSET(fd, &readfds)) { + int iRcvd; + memset(line, '\0', sizeof(line)); + iRcvd = recv(fd, line, MAXLINE - 2, 0); + dprintf("Message from UNIX socket: #%d\n", fd); + if (iRcvd > 0) { + line[iRcvd] = line[iRcvd+1] = '\0'; + printchopped(LocalHostName, line, iRcvd + 2, fd, funixParseHost[i]); + } else if (iRcvd < 0 && errno != EINTR) { + dprintf("UNIX socket error: %d = %s.\n", \ + errno, strerror(errno)); + logerror("recvfrom UNIX"); + } + } + } +#endif +#ifdef SYSLOG_INET + if (InetInuse && AcceptRemote && FD_ISSET(inetm, &readfds)) { + len = sizeof(frominet); + memset(line, '\0', sizeof(line)); + i = recvfrom(finet, line, MAXLINE - 2, 0, \ + (struct sockaddr *) &frominet, &len); + dprintf("Message from UDP inetd socket: #%d, host: %s\n", + inetm, inet_ntoa(frominet.sin_addr)); + if (i > 0) { + from = (char *)cvthname(&frominet); + /* Here we check if a host is permitted to send us + * syslog messages. If it isn't, we do not further + * process the message but log a warning (if we are + * configured to do this). + * rgerhards, 2005-09-26 + */ + if(isAllowedSender(pAllowedSenders_UDP, &frominet)) { + line[i] = line[i+1] = '\0'; + printchopped(from, line, i + 2, finet, 1); + } else { + if(option_DisallowWarning) { + logerrorSz("UDP message from disallowed sender %s discarded", + from); + } + } + } else if (i < 0 && errno != EINTR && errno != EAGAIN) { + /* see link below why we check EAGAIN: + * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=188194 + */ + dprintf("INET socket error: %d = %s.\n", \ + errno, strerror(errno)); + logerror("recvfrom inet"); + /* should be harmless now that we set + * BSDCOMPAT on the socket */ + sleep(1); + } + } -/* - * Decode a symbolic name to a numeric value - */ + if(bEnableTCP && sockTCPLstn != -1) { + /* Now check for TCP input */ + if(FD_ISSET(sockTCPLstn, &readfds)) { + dprintf("New connect on TCP inetd socket: #%d\n", sockTCPLstn); + TCPSessAccept(); + } -int decode(name, codetab) - char *name; - struct code *codetab; -{ - register struct code *c; - register char *p; - char buf[80]; + /* now check the sessions */ + /* TODO: optimize the whole thing. We could stop enumerating as + * soon as we have found all sockets flagged as active. */ + iTCPSess = TCPSessGetNxtSess(-1); + while(iTCPSess != -1) { + int fd; + int state; + fd = TCPSessions[iTCPSess].sock; + if(FD_ISSET(fd, &readfds)) { + char buf[MAXLINE]; + dprintf("tcp session socket with new data: #%d\n", fd); - dprintf ("symbolic name: %s", name); - if (isdigit(*name)) - { - dprintf ("\n"); - return (atoi(name)); - } - (void) strncpy(buf, name, 79); - for (p = buf; *p; p++) - if (isupper(*p)) - *p = tolower(*p); - for (c = codetab; c->c_name; c++) - if (!strcmp(buf, c->c_name)) - { - dprintf (" ==> %d\n", c->c_val); - return (c->c_val); + /* Receive message */ + state = recv(fd, buf, sizeof(buf), 0); + if(state == 0) { + /* Session closed */ + TCPSessClose(iTCPSess); + } else if(state == -1) { + char errmsg[128]; + snprintf(errmsg, sizeof(errmsg)/sizeof(char), + "TCP session %d will be closed, error ignored", + fd); + logerror(errmsg); + TCPSessClose(iTCPSess); + } else { + /* valid data received, process it! */ + TCPSessDataRcvd(iTCPSess, buf, state); + } + } + iTCPSess = TCPSessGetNxtSess(iTCPSess); + } } - return (-1); -} - -void dprintf(char *fmt, ...) -{ -# ifdef USE_PTHREADS - static int bWasNL = FALSE; -# endif - va_list ap; - if ( !(Debug && debugging_on) ) - return; - -# ifdef USE_PTHREADS - /* TODO: The bWasNL handler does not really work. It works if no thread - * switching occurs during non-NL messages. Else, things are messed - * up. Anyhow, it works well enough to provide useful help during - * getting this up and running. It is questionable if the extra effort - * is worth fixing it, giving the limited appliability. - * rgerhards, 2005-10-25 - */ - if(bWasNL) { - fprintf(stdout, "%8.8d: ", (unsigned int) pthread_self()); - } - bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? TRUE : FALSE; -# endif - va_start(ap, fmt); - vfprintf(stdout, fmt, ap); - va_end(ap); +#endif +#else + if ( FD_ISSET(fileno(stdin), &readfds) ) { + dprintf("Message from stdin.\n"); + memset(line, '\0', sizeof(line)); + line[0] = '.'; + parts[fileno(stdin)] = NULL; + i = read(fileno(stdin), line, MAXLINE); + if (i > 0) { + printchopped(LocalHostName, line, i+1, + fileno(stdin), 0); + } else if (i < 0) { + if (errno != EINTR) { + logerror("stdin"); + } + } + FD_CLR(fileno(stdin), &readfds); + } - fflush(stdout); - return; +#endif + } } +int main(int argc, char **argv) +{ register int i; + register char *p; +#if !defined(__GLIBC__) + int len, num_fds; +#else /* __GLIBC__ */ + int num_fds; +#endif /* __GLIBC__ */ -/* - * The following function is resposible for handling a SIGHUP signal. Since - * we are now doing mallocs/free as part of init we had better not being - * doing this during a signal handler. Instead this function simply sets - * a flag variable which will tell the main loop to go through a restart. - */ -void sighup_handler() +#ifdef MTRACE + mtrace(); /* this is a debug aid for leak detection - either remove + * or put in conditional compilation. 2005-01-18 RGerhards */ +#endif -{ - restart = 1; - signal(SIGHUP, sighup_handler); - return; -} +#ifndef TESTING + pid_t ppid = getpid(); +#endif + int ch; + struct hostent *hent; + + extern int optind; + extern char *optarg; + char *pTmp; + +#ifndef TESTING + chdir ("/"); +#endif + for (i = 1; i < MAXFUNIX; i++) { + funixn[i] = ""; + funix[i] = -1; + } + while ((ch = getopt(argc, argv, "a:dhi:f:l:m:nop:r:s:t:vw")) != EOF) + switch((char)ch) { + case 'a': + if (nfunix < MAXFUNIX) + if(*optarg == ':') { + funixParseHost[nfunix] = 1; + funixn[nfunix++] = optarg+1; + } + else { + funixParseHost[nfunix] = 0; + funixn[nfunix++] = optarg; + } + else + fprintf(stderr, "Out of descriptors, ignoring %s\n", optarg); + break; + case 'd': /* debug */ + Debug = 1; + break; + case 'f': /* configuration file */ + ConfFile = optarg; + break; + case 'h': + NoHops = 0; + break; + case 'i': /* pid file name */ + PidFile = optarg; + break; + case 'l': + if (LocalHosts) { + fprintf (stderr, "Only one -l argument allowed," \ + "the first one is taken.\n"); + break; + } + LocalHosts = crunch_list(optarg); + break; + case 'm': /* mark interval */ + MarkInterval = atoi(optarg) * 60; + break; + case 'n': /* don't fork */ + NoFork = 1; + break; + case 'o': /* omit local logging (/dev/log) */ + startIndexUxLocalSockets = 1; + break; + case 'p': /* path to regular log socket */ + funixn[0] = optarg; + break; + case 'r': /* accept remote messages */ + AcceptRemote = 1; + LogPort = atoi(optarg); + break; + case 's': + if (StripDomains) { + fprintf (stderr, "Only one -s argument allowed," \ + "the first one is taken.\n"); + break; + } + StripDomains = crunch_list(optarg); + break; + case 't': /* enable tcp logging */ + bEnableTCP = -1; + TCPLstnPort = atoi(optarg); + break; + case 'v': + printf("rsyslogd %s.%s, ", VERSION, PATCHLEVEL); + printf("compiled with:\n"); +#ifdef USE_PTHREADS + printf("\tFEATURE_PTHREADS (dual-threading)\n"); +#endif +#ifdef FEATURE_REGEXP + printf("\tFEATURE_REGEXP\n"); +#endif #ifdef WITH_DB -/* - * The following function is responsible for initiatlizing a - * MySQL connection. - * Initially added 2004-10-28 mmeckelein - */ -static void initMySQL(register struct filed *f) -{ - int iCounter = 0; - assert(f != NULL); + printf("\tFEATURE_DB\n"); +#endif +#ifndef NOLARGEFILE + printf("\tFEATURE_LARGEFILE\n"); +#endif +#ifdef SYSLOG_INET + printf("\tSYSLOG_INET (Internet/remote support)\n"); +#endif +#ifndef NDEBUG + printf("\tFEATURE_DEBUG (debug build, slow code)\n"); +#endif + printf("\nSee http://www.rsyslog.com for more information.\n"); + exit(0); /* exit for -v option - so this is a "good one" */ + case 'w': /* disable disallowed host warnigs */ + option_DisallowWarning = 0; + break; + case '?': + default: + usage(); + } + if ((argc -= optind)) + usage(); - if (checkDBErrorState(f)) - return; - - mysql_init(&f->f_hmysql); - do { - /* Connect to database */ - if (!mysql_real_connect(&f->f_hmysql, f->f_dbsrv, f->f_dbuid, f->f_dbpwd, f->f_dbname, 0, NULL, 0)) { - /* if also the second attempt failed - we call the error handler */ - if(iCounter) - DBErrorHandler(f); +#ifndef TESTING + if ( !(Debug || NoFork) ) + { + dprintf("Checking pidfile.\n"); + if (!check_pid(PidFile)) + { + signal (SIGTERM, doexit); + if (fork()) { + /* + * Parent process + */ + sleep(300); + /* + * Not reached unless something major went wrong. 5 + * minutes should be a fair amount of time to wait. + * Please note that this procedure is important since + * the father must not exit before syslogd isn't + * initialized or the klogd won't be able to flush its + * logs. -Joey + */ + exit(1); /* "good" exit - after forking, not diasabling anything */ + } + num_fds = getdtablesize(); + for (i= 0; i < num_fds; i++) + (void) close(i); + untty(); } else { - f->f_timeResumeOnError = 0; /* We have a working db connection */ - dprintf("connected successfully to db\n"); + fputs("rsyslogd: Already running.\n", stderr); + exit(1); /* "good" exit, done if syslogd is already running */ } - iCounter++; - } while (mysql_errno(&f->f_hmysql) && iCounter<2); -} - -/* - * The following function is responsible for closing a - * MySQL connection. - * Initially added 2004-10-28 - */ -static void closeMySQL(register struct filed *f) -{ - assert(f != NULL); - dprintf("in closeMySQL\n"); - mysql_close(&f->f_hmysql); -} - -/* - * Reconnect a MySQL connection. - * Initially added 2004-12-02 - */ -static void reInitMySQL(register struct filed *f) -{ - assert(f != NULL); - - dprintf("reInitMySQL\n"); - closeMySQL(f); /* close the current handle */ - initMySQL(f); /* new connection */ -} - -/* - * The following function writes the current log entry - * to an established MySQL session. - * Initially added 2004-10-28 - */ -static void writeMySQL(register struct filed *f) -{ - char *psz; - int iCounter=0; - assert(f != NULL); - /* dprintf("in writeMySQL()\n"); */ - iovCreate(f); - psz = iovAsString(f); - - if (checkDBErrorState(f)) - return; - - /* - * Now we are trying to insert the data. - * - * If the first attampt failes we simply try a second one. If also - * the second attampt failed we discard this message and enable - * the "delay" error hanlding. - */ - do { - /* query */ - if(mysql_query(&f->f_hmysql, psz)) { - - /* if also the second attempt failed - we call the error handler */ - if(iCounter) - DBErrorHandler(f); + } + else +#endif + debugging_on = 1; +#ifndef SYSV + else + setlinebuf(stdout); +#endif + +#ifndef TESTING + /* tuck my process id away */ + if ( !Debug ) + { + dprintf("Writing pidfile.\n"); + if (!check_pid(PidFile)) + { + if (!write_pid(PidFile)) + { + dprintf("Can't write pid.\n"); + exit(1); /* exit during startup - questionable */ + } } - else { - /* dprintf("db insert sucessfully\n"); */ + else + { + dprintf("Pidfile (and pid) already exist.\n"); + exit(1); /* exit during startup - questionable */ } - iCounter++; - } while (mysql_errno(&f->f_hmysql) && iCounter<2); -} + } /* if ( !Debug ) */ +#endif + myPid = getpid(); /* save our pid for further testing (also used for messages) */ -/** - * DBErrorHandler - * - * Call this function if an db error apears. It will initiate - * the "delay" handling which stopped the db logging for some - * time. - */ -static void DBErrorHandler(register struct filed *f) -{ - char errMsg[512]; - /* TODO: - * NO DB connection -> Can not log to DB - * -------------------- - * Case 1: db server unavailable - * We can check after a specified time interval if the server is up. - * Also a reason can be a down DNS service. - * Case 2: uid, pwd or dbname are incorrect - * If this is a fault in the syslog.conf we have no chance to recover. But - * if it is a problem of the DB we can make a retry after some time. Possible - * are that the admin has not already set up the database table. Or he has not - * created the database user yet. - * Case 3: unkown error - * If we get an unkowon error here, we should in any case try to recover after - * a specified time interval. - * - * Insert failed -> Can not log to DB - * -------------------- - * If the insert fails it is never a good idea to give up. Only an - * invalid sql sturcture (wrong template) force us to disable db - * logging. - * - * Think about diffrent "delay" for diffrent errors! + /* initialize the default templates + * we use template names with a SP in front - these + * can NOT be generated via the configuration file */ - errno = 0; - snprintf(errMsg, sizeof(errMsg)/sizeof(char), - "db error (%d): %s\n", mysql_errno(&f->f_hmysql), - mysql_error(&f->f_hmysql)); - - /* Enable "delay" */ - f->f_timeResumeOnError = time(&f->f_timeResumeOnError) + _DB_DELAYTIMEONERROR ; - f->f_iLastDBErrNo = mysql_errno(&f->f_hmysql); - - /* Log error is the last step. */ - logerror(errMsg); -} + pTmp = template_TraditionalFormat; + tplAddLine(" TradFmt", &pTmp); + pTmp = template_WallFmt; + tplAddLine(" WallFmt", &pTmp); + pTmp = template_StdFwdFmt; + tplAddLine(" StdFwdFmt", &pTmp); + pTmp = template_StdUsrMsgFmt; + tplAddLine(" StdUsrMsgFmt", &pTmp); + pTmp = template_StdDBFmt; + tplAddLine(" StdDBFmt", &pTmp); -/** - * checkDBErrorState - * - * Check if we can go on with database logging or if we should wait - * a little bit longer. It also check if the DB hanlde is still valid. - * If it is necessary, it takes action to reinitiate the db connection. - * - * \ret int Returns 0 if successful (no error) - */ -int checkDBErrorState(register struct filed *f) -{ - assert(f != NULL); - /* dprintf("in checkDBErrorState, timeResumeOnError: %d\n", f->f_timeResumeOnError); */ + /* prepare emergency logging system */ - /* If timeResumeOnError == 0 no error occured, - we can return with 0 (no error) */ - if (f->f_timeResumeOnError == 0) - return 0; - - (void) time(&now); - /* Now we know an error occured. We check timeResumeOnError - if we can process. If we have not reach the resume time - yet, we return an error status. */ - if (f->f_timeResumeOnError > now) + consfile.f_type = F_CONSOLE; + (void) strcpy(consfile.f_un.f_fname, ctty); + cflineSetTemplateAndIOV(&consfile, " TradFmt"); + (void) gethostname(LocalHostName, sizeof(LocalHostName)); + if ( (p = strchr(LocalHostName, '.')) ) { + *p++ = '\0'; + LocalDomain = p; + } + else { - /* dprintf("Wait time is not over yet.\n"); */ - return 1; + LocalDomain = ""; + + /* + * It's not clearly defined whether gethostname() + * should return the simple hostname or the fqdn. A + * good piece of software should be aware of both and + * we want to distribute good software. Joey + * + * Good software also always checks its return values... + * If syslogd starts up before DNS is up & /etc/hosts + * doesn't have LocalHostName listed, gethostbyname will + * return NULL. + */ + hent = gethostbyname(LocalHostName); + if ( hent ) + snprintf(LocalHostName, sizeof(LocalHostName), "%s", hent->h_name); + + if ( (p = strchr(LocalHostName, '.')) ) + { + *p++ = '\0'; + LocalDomain = p; + } } - - /* Ok, we can try to resume the database logging. First - we have to reset the status (timeResumeOnError) and - the last error no. */ - /* TODO: - * To improve this code it would be better to check - if we really need to reInit the db connection. If - only the insert failed and the db conncetcion is - still valid, we need no reInit. - Of course, if an unkown error appeared, we should - reInit. */ - /* rgerhards 2004-12-08: I think it is pretty unlikely - * that we can re-use a connection after the error. So I guess - * the connection must be closed and re-opened in all cases - * (as it is done currently). When we come back to optimize - * this code, we should anyhow see if there are cases where - * we could keep it open. I just doubt this won't be the case. - * I added this comment (and did not remove Michaels) just so - * that we all know what we are looking for. - */ - f->f_timeResumeOnError = 0; - f->f_iLastDBErrNo = 0; - reInitMySQL(f); - return 0; -} + /* Convert to lower case to recognize the correct domain laterly + */ + for (p = (char *)LocalDomain; *p ; p++) + if (isupper(*p)) + *p = tolower(*p); -#endif /* #ifdef WITH_DB */ + (void) signal(SIGTERM, die); + (void) signal(SIGINT, Debug ? die : SIG_IGN); + (void) signal(SIGQUIT, Debug ? die : SIG_IGN); + (void) signal(SIGCHLD, reapchild); + (void) signal(SIGALRM, domarkAlarmHdlr); + (void) signal(SIGUSR1, Debug ? debug_switch : SIG_IGN); + (void) signal(SIGPIPE, SIG_IGN); + (void) signal(SIGXFSZ, SIG_IGN); /* do not abort if 2gig file limit is hit */ + (void) alarm(TIMERINTVL); -/** - * getSubString - * - * Copy a string byte by byte until the occurrence - * of a given separator. - * - * \param ppSrc Pointer to a pointer of the source array of characters. If a - separator detected the Pointer points to the next char after the - separator. Except if the end of the string is dedected ('\n'). - Then it points to the terminator char. - * \param pDst Pointer to the destination array of characters. Here the substing - will be stored. - * \param DstSize Maximum numbers of characters to store. - * \param cSep Separator char. - * \ret int Returns 0 if no error occured. - */ -int getSubString(char **ppSrc, char *pDst, size_t DstSize, char cSep) -{ - char *pSrc = *ppSrc; - int iErr = 0; /* 0 = no error, >0 = error */ - while(*pSrc != cSep && *pSrc != '\0' && DstSize>1) { - *pDst++ = *(pSrc)++; - DstSize--; + /* Create a partial message table for all file descriptors. */ + num_fds = getdtablesize(); + dprintf("Allocated parts table for %d file descriptors.\n", num_fds); + if ((parts = (char **) malloc(num_fds * sizeof(char *))) == NULL) + { + logerror("Cannot allocate memory for message parts table."); + die(0); } - /* check if the Dst buffer was to small */ - if (*pSrc != cSep && *pSrc != '\0') - { - dprintf("in getSubString, error Src buffer > Dst buffer\n"); - iErr = 1; - } - if (*pSrc == '\0') - /* this line was missing, causing ppSrc to be invalid when it - * was returned in case of end-of-string. rgerhards 2005-07-29 - */ - *ppSrc = pSrc; - else - *ppSrc = pSrc+1; - *pDst = '\0'; - return iErr; + for(i= 0; i < num_fds; ++i) + parts[i] = NULL; + + dprintf("Starting.\n"); + init(); +#ifndef TESTING + if(Debug) { + dprintf("Debugging enabled, SIGUSR1 to turn off debugging.\n"); + debugging_on = 1; + } + /* + * Send a signal to the parent to it can terminate. + */ + if (myPid != ppid) + kill (ppid, SIGTERM); +#endif + /* END OF INTIALIZATION + * ... but keep in mind that we might do a restart and thus init() might + * be called again. If that happens, we must shut down all active threads, + * do the init() and then restart things. + * rgerhards, 2005-10-24 + */ +#ifdef USE_PTHREADS + /* create message queue */ + pMsgQueue = queueInit(); + if(pMsgQueue != NULL) { + errno = 0; + logerror("error: could not create message queue - running single-threaded!\n"); + } else { /* start up worker thread */ + startWorker(); + } +#endif + + /* --------------------- Main loop begins here. ----------------------------------------- */ + mainloop(); + return 0; } /* -- cgit v1.2.3