/* $Id: ciscoconfd.c,v 1.4 1998/05/05 16:23:30 jabley Exp jabley $ * * Monitor log files for syslogged "config changed" messages, and spawn * config-grabbing processes when necessary. * */ #include #include #include #include #if defined(HAVE_SYSLOGFACILITYNAMES) #define SYSLOG_NAMES #endif #include #include #include #include #include #include #include #include #include #if defined(HAVE_SETPROCTITLE) && defined(NEED_LIBUTIL) #include #endif #include "ciscoconfd.h" struct logfile { char *name; ino_t ino; fpos_t offset; struct logfile *next; }; struct config { struct logfile head; unsigned int interval; int syslog_facility; uid_t uid; gid_t gid; char *retriever; }; struct changed { char *router; char *logline; struct changed *next; }; #if !defined(HAVE_STRICMP) int stricmp(char *a, char *b) { while (*a && *b) { if (tolower(*a) != tolower(*b)) return(1); a++; b++; } return (*a == *b ? 0 : 1); } #endif void cleanlist(struct logfile *p) { struct logfile *q; while (p) { q = p->next; if (p->name) free(p->name); free(p); p = q; } } void moanf(struct config *cfg, int priority, char *fmt, ...) { va_list ap; #if !defined(HAVE_VSYSLOG) char scratch[MAX_LINE_LENGTH + 1]; #endif va_start(ap, fmt); openlog(SYSLOG_IDENT, LOG_PID, cfg->syslog_facility); #if defined(HAVE_VSYSLOG) vsyslog(priority, fmt, ap); #else vsprintf(scratch, fmt, ap); syslog(priority, "%s", scratch); #endif closelog(); va_end(ap); } void do_daemon_stuff(struct config *cfg) { struct logfile *p; struct stat sb; FILE *f; char line[MAX_LINE_LENGTH + 1], *getsrc; struct changed head, *q, *r; int i, j, k, already; pid_t child; struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = SA_NOCLDWAIT; act.sa_handler = SIG_IGN; if (sigaction(SIGCHLD, &act, NULL) < 0) { perror("sigaction"); exit(EXIT_FAILURE); } while (1) { head.router = NULL; head.logline = NULL; head.next = NULL; p = &(cfg->head); while (p->next) { p = p->next; #if defined(HAVE_SETPROCTITLE) setproctitle("Checking logfile '%s'", p->name); #endif if (stat(p->name, &sb) == -1) { p->offset = (fpos_t) 0; moanf(cfg, LOG_WARNING, "Unable to stat file '%s': %m (unnecessary config check-ins may result)", p->name); } else { if (sb.st_ino != p->ino) { p->offset = (fpos_t) 0; p->ino = sb.st_ino; moanf(cfg, LOG_INFO, "Log file '%s' has rotated", p->name); } } if ((f = fopen(p->name, "r")) == NULL) moanf(cfg, LOG_WARNING, "Log file '%s' is un-openable: %m", p->name); else { if (fsetpos(f, &(p->offset)) == -1) moanf(cfg, LOG_WARNING, "Unable to set position on file '%s': %m (unnecessary config check-ins may result)", p->name); do { fgetpos(f, &(p->offset)); if (getsrc = fgets(line, MAX_LINE_LENGTH, f)) { if (strstr(line, MAGIC_STRING)) { j = 0; for (i = 0; i < 4; i++) { k = j; while (!isspace(line[j])) j++; while (isspace(line[j])) j++; } line[j - 1] = 0; q = &head; already = 0; while (q->next) { q = q->next; if (strcmp(q->router, line + k) == 0) already = 1; } if (!already) { if ((q = q->next = (struct changed *) malloc(sizeof(struct changed))) == NULL) moanf(cfg, LOG_CRIT, "Memory allocation failed [1d]"); else { q->next = NULL; if ((q->router = (char *) calloc(1 + j - k, sizeof(char))) == NULL) { moanf(cfg, LOG_CRIT, "Memory allocation failed [2d]"); q->logline = NULL; } else { strcpy(q->router, line + k); if ((q->logline = (char *) calloc(1 + strlen(line + j), sizeof(char))) == NULL) moanf(cfg, LOG_CRIT, "Memory allocation failed [3d]"); else strcpy(q->logline, line + j); } } } } } } while (getsrc); } fclose(f); q = head.next; while (q) { if (q->router) { moanf(cfg, LOG_INFO, "Logfile '%s' reveals config change on '%s'", p->name, q->router); /* spawn the thing with args q->router and q->logline */ child = fork(); switch (child) { case -1: moanf(cfg, LOG_CRIT, "Unable to fork [1d]: %m"); break; case 0: moanf(cfg, LOG_DEBUG, "Executing '%s' to retrieve configuration", cfg->retriever); if (execl(cfg->retriever, cfg->retriever, q->router, q->logline, NULL) == -1) moanf(cfg, LOG_CRIT, "Failed to execute '%s': %m", cfg->retriever); else moanf(cfg, LOG_CRIT, "Unexpected return from exec! Has the world gone mad?"); exit(0); default: moanf(cfg, LOG_DEBUG, "Spawned process %u to retrieve configuration", (unsigned int) child); break; } free(q->router); } if (q->logline) free(q->logline); r = q->next; free(q); q = r; } } #if defined(HAVE_SETPROCTITLE) setproctitle("Sleeping"); #endif moanf(cfg, LOG_DEBUG, "Sleeping for %d seconds", cfg->interval); sleep(cfg->interval); } } int main(int argc, char **argv) { int ch; struct config cfg; struct logfile *p; struct passwd *pw; struct group *gr; int i, facility; pid_t child; char *pidfile = NULL; FILE *f; cfg.syslog_facility = DEFAULT_SYSLOG_FACILITY; cfg.interval = (unsigned int) DEFAULT_INTERVAL; cfg.head.name = NULL; cfg.head.ino = (ino_t) 0; cfg.head.offset = (fpos_t) 0; cfg.retriever = NULL; cfg.head.next = NULL; p = &cfg.head; while ((ch = getopt(argc, argv, "t:s:u:g:p:r:")) != -1) switch (ch) { case 't': cfg.interval = (unsigned int) atoi(optarg); moanf(&cfg, LOG_DEBUG, "Log checking interval set to %u", \ (unsigned int) cfg.interval); break; case 's': facility = -1; for (i = 0; facilitynames[i].c_name != NULL; i++) if (stricmp(optarg, facilitynames[i].c_name) == 0) facility = facilitynames[i].c_val; if (facility == -1) { cleanlist(cfg.head.next); moanf(&cfg, LOG_ERR, "Unknown syslog facility '%s'", optarg); exit(1); } moanf(&cfg, LOG_DEBUG, "Switching to syslog facility '%s'", optarg); cfg.syslog_facility = facility; break; case 'u': if ((pw = getpwnam(optarg)) == NULL) cfg.uid = atoi(optarg); else cfg.uid = pw->pw_uid; moanf(&cfg, LOG_DEBUG, "Will run with uid %u", (unsigned int) cfg.uid); break; case 'g': if ((gr = getgrnam(optarg)) == NULL) cfg.gid = atoi(optarg); else cfg.gid = gr->gr_gid; moanf(&cfg, LOG_DEBUG, "Will run with gid %u", (unsigned int) cfg.gid); break; case 'p': if (pidfile) { moanf(&cfg, LOG_ERR, "Multiple PID files specified ('%s' and '%s')", \ pidfile, optarg); free(pidfile); if (cfg.retriever) free(cfg.retriever); exit(1); } if ((pidfile = calloc(1 + strlen(optarg), sizeof(char))) == NULL) { if (cfg.retriever) free(cfg.retriever); moanf(&cfg, LOG_CRIT, "Memory allocation failed [3]"); exit(1); } strcpy(pidfile, optarg); moanf(&cfg, LOG_DEBUG, "Will write PID to file '%s'", pidfile); break; case 'r': if (cfg.retriever) { moanf(&cfg, LOG_ERR, "Multiple retrieval programs specified ('%s' and '%s'", \ cfg.retriever, optarg); free(cfg.retriever); if (pidfile) free(pidfile); exit(1); } if ((cfg.retriever = calloc(1 + strlen(optarg), sizeof(char))) == NULL) { if (pidfile) free(pidfile); moanf(&cfg, LOG_CRIT, "Memory allocation failed [4]"); exit(1); } strcpy(cfg.retriever, optarg); moanf(&cfg, LOG_DEBUG, "Will use retrieval program '%s'", cfg.retriever); break; case '?': fprintf(stderr, SYNTAX_MESSAGE "\n"); if (pidfile) free(pidfile); if (cfg.retriever) free(cfg.retriever); moanf(&cfg, LOG_DEBUG, "Attempted to start with bad syntax - aborted"); exit(1); break; } if ((cfg.retriever) == NULL) { if (pidfile) free(pidfile); moanf(&cfg, LOG_ERR, "No retrieval program specified"); exit(1); } /* gather the remaining "logfile" arguments, which have no opt leader */ while (optind < argc) { if ((p = p->next = (struct logfile *) malloc(sizeof(struct logfile))) == NULL) { cleanlist(cfg.head.next); moanf(&cfg, LOG_CRIT, "Memory allocation failed [1]"); exit(1); } p->ino = (ino_t) 0; p->offset = (long) 0; p->next = NULL; if ((p->name = (char *) calloc(sizeof(char), 1 + strlen(argv[optind]))) == NULL) { cleanlist(cfg.head.next); moanf(&cfg, LOG_CRIT, "Memory allocation failed [2]"); exit(1); } strcpy(p->name, argv[optind]); moanf(&cfg, LOG_DEBUG, "Will watch log file '%s'", p->name); break; } /* forking hell */ if ((child = fork()) == -1) { cleanlist(cfg.head.next); if (pidfile) free(pidfile); moanf(&cfg, LOG_CRIT, "Unable to fork: %m"); exit(1); } if (child == (pid_t) 0) { if (pidfile) free(pidfile); close(0); close(1); close(2); open("/dev/null", O_RDWR | O_NONBLOCK, 644); open("/dev/null", O_RDWR | O_NONBLOCK, 644); open("/dev/null", O_RDWR | O_NONBLOCK, 644); setsid(); if (cfg.gid) setgid(cfg.gid); if (cfg.uid) setuid(cfg.uid); do_daemon_stuff(&cfg); exit(0); } /* write the pid */ if (pidfile) { if ((f = fopen(pidfile, "w")) == NULL) moanf(&cfg, LOG_ERR, "Unable to open pid file '%s' for writing: %m", pidfile); else { moanf(&cfg, LOG_INFO, "Running with pid %u", (unsigned int) child); fprintf(f, "%u", (unsigned int) child); fclose(f); } free(pidfile); } cleanlist(cfg.head.next); exit(0); return(0); /* never reached */ }