static void timestamp_logfiles(server_conf_t *conf) { /* Writes a timestamp message into all of the console logfiles. */ char *now; ListIterator i; obj_t *logfile; char buf[MAX_LINE]; int gotLogs = 0; now = create_long_time_string(0); i = list_iterator_create(conf->objs); while ((logfile = list_next(i))) { if (!is_logfile_obj(logfile)) { continue; } snprintf(buf, sizeof(buf), "%sConsole [%s] log at %s%s", CONMAN_MSG_PREFIX, logfile->aux.logfile.console->name, now, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(logfile, buf, strlen(buf), 1); gotLogs = 1; } list_iterator_destroy(i); free(now); /* If any logfile objs exist, schedule a timer for the next timestamp. */ if (gotLogs) { schedule_timestamp(conf); } return; }
int write_log_data(obj_t *log, const void *src, int len) { /* Writes a potentially modified version of the buffer (src) of length (len) * into the logfile obj (log); the original (src) buffer is not modified. * This routine is intended for writing data that was read from a console, * not informational messages. * If sanitized logs are enabled, data is stripped to 7-bit ASCII and * control/binary characters are displayed as two-character printable * sequences. * If newline timestamping is enabled, the current timestamp is appended * after each newline. * Returns the number of bytes written into the logfile obj's buffer. */ const int minbuf = 25; /* cr/lf + timestamp + meta/char */ unsigned char buf[MAX_BUF_SIZE - 1]; const unsigned char *p; unsigned char *q; const unsigned char * const qLast = buf + sizeof(buf); int n = 0; assert(is_logfile_obj(log)); assert(sizeof(buf) >= minbuf); /* If no additional processing is needed, listen to Biff Tannen: * "make like a tree and get outta here". */ if (!log->aux.logfile.gotProcessing) { return(write_obj_data(log, src, len, 0)); } DPRINTF((15, "Processing %d bytes for [%s] log \"%s\".\n", len, log->aux.logfile.console->name, log->name)); for (p=src, q=buf; len>0; p++, len--) { /* * A newline state machine is used to properly sanitize CR/LF line * terminations. This is responsible for coalescing multiple CRs, * swapping LF/CR to CR/LF, transcribing CR/NUL to CR/LF, * prepending a CR to a lonely LF, and appending a LF to a * lonely CR to prevent characters from being overwritten. */ if (*p == '\r') { if (log->aux.logfile.lineState == CONMAN_LOG_LINE_DATA) { log->aux.logfile.lineState = CONMAN_LOG_LINE_CR; } else if (log->aux.logfile.lineState == CONMAN_LOG_LINE_INIT) { if (log->aux.logfile.opts.enableTimestamp) q += write_time_string(0, (char *) q, qLast - q); log->aux.logfile.lineState = CONMAN_LOG_LINE_CR; } else { ; /* ignore */ } } else if (*p == '\n') { if ( (log->aux.logfile.lineState == CONMAN_LOG_LINE_INIT) || (log->aux.logfile.lineState == CONMAN_LOG_LINE_LF) ) { if (log->aux.logfile.opts.enableTimestamp) q += write_time_string(0, (char *) q, qLast - q); } *q++ = '\r'; *q++ = '\n'; log->aux.logfile.lineState = CONMAN_LOG_LINE_LF; } else if ( (*p == '\0') && ( (log->aux.logfile.lineState == CONMAN_LOG_LINE_CR) || (log->aux.logfile.lineState == CONMAN_LOG_LINE_LF) ) ) { ; /* ignore */ } else { if (log->aux.logfile.lineState == CONMAN_LOG_LINE_CR) { *q++ = '\r'; *q++ = '\n'; } if (log->aux.logfile.lineState != CONMAN_LOG_LINE_DATA) { if (log->aux.logfile.opts.enableTimestamp) q += write_time_string(0, (char *) q, qLast - q); } log->aux.logfile.lineState = CONMAN_LOG_LINE_DATA; if (log->aux.logfile.opts.enableSanitize) { int c = *p & 0x7F; /* strip data to 7-bit ASCII */ if (c < 0x20) { /* ASCII ctrl-chars */ *q++ = (*p & 0x80) ? '~' : '^'; *q++ = c + '@'; } else if (c == 0x7F) { /* ASCII DEL char */ *q++ = (*p & 0x80) ? '~' : '^'; *q++ = '?'; } else { if (*p & 0x80) *q++ = '`'; *q++ = c; } } else { *q++ = *p; } } /* Flush internal buffer before it overruns. */ if ((qLast - q) < minbuf) { assert((q >= buf) && (q <= qLast)); n += write_obj_data(log, buf, q - buf, 0); q = buf; } } assert((q >= buf) && (q <= qLast)); n += write_obj_data(log, buf, q - buf, 0); return(n); }
int open_logfile_obj(obj_t *logfile) { /* (Re)opens the specified 'logfile' obj. * Since this logfile can be re-opened after the daemon has chdir()'d, * it must be specified with an absolute pathname. * Returns 0 if the logfile is successfully opened; o/w, returns -1. */ char dirname[PATH_MAX]; int flags; char *now; char *msg; assert(logfile != NULL); assert(is_logfile_obj(logfile)); assert(logfile->name != NULL); assert(logfile->name[0] == '/'); assert(logfile->aux.logfile.console != NULL); assert(logfile->aux.logfile.console->name != NULL); if (logfile->fd >= 0) { if (close(logfile->fd) < 0) /* log err and continue */ log_msg(LOG_WARNING, "Unable to close logfile \"%s\": %s", logfile->name, strerror(errno)); logfile->fd = -1; } /* Perform conversion specifier expansion. */ if (logfile->aux.logfile.fmtName) { char buf[MAX_LINE]; if (format_obj_string(buf, sizeof(buf), logfile->aux.logfile.console, logfile->aux.logfile.fmtName) < 0) { log_msg(LOG_WARNING, "Unable to open logfile for [%s]: filename exceeded buffer", logfile->aux.logfile.console->name); logfile->fd = -1; return(-1); } free(logfile->name); logfile->name = create_string(buf); } /* Create intermediate directories. */ if (get_dir_name(logfile->name, dirname, sizeof(dirname))) { (void) create_dirs(dirname); } /* Only truncate on the initial open if ZeroLogs was enabled. */ flags = O_WRONLY | O_CREAT | O_APPEND | O_NONBLOCK; if (logfile->aux.logfile.gotTruncate) { logfile->aux.logfile.gotTruncate = 0; flags |= O_TRUNC; } if ((logfile->fd = open(logfile->name, flags, S_IRUSR | S_IWUSR)) < 0) { log_msg(LOG_WARNING, "Unable to open logfile \"%s\": %s", logfile->name, strerror(errno)); return(-1); } if (logfile->aux.logfile.opts.enableLock && (get_write_lock(logfile->fd) < 0)) { log_msg(LOG_WARNING, "Unable to lock \"%s\"", logfile->name); close(logfile->fd); /* ignore err on close() */ logfile->fd = -1; return(-1); } logfile->gotEOF = 0; set_fd_nonblocking(logfile->fd); /* redundant, just playing it safe */ set_fd_closed_on_exec(logfile->fd); now = create_long_time_string(0); msg = create_format_string("%sConsole [%s] log opened at %s%s", CONMAN_MSG_PREFIX, logfile->aux.logfile.console->name, now, CONMAN_MSG_SUFFIX); write_obj_data(logfile, msg, strlen(msg), 0); free(now); free(msg); /* * Since the above console log message is not marked "informational", * the test in write_obj_data() to re-init the line state will not * be triggered. Thusly, we re-initialize the line state here. */ logfile->aux.logfile.lineState = CONMAN_LOG_LINE_INIT; DPRINTF((9, "Opened [%s] logfile: fd=%d file=%s.\n", logfile->aux.logfile.console->name, logfile->fd, logfile->name)); return(0); }
static void check_console_state(obj_t *console, obj_t *client) { /* Checks the state of the console and warns the client if needed. * Informs the newly-connected client if strange things are afoot. * Attempts an immediate reconnect if the console connection is down. */ char buf[MAX_LINE]; assert(is_console_obj(console)); assert(is_client_obj(client)); if (is_process_obj(console) && (console->fd < 0)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from \"%s\"%s", CONMAN_MSG_PREFIX, console->name, console->aux.process.prog, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); open_process_obj(console); } else if (is_serial_obj(console) && (console->fd < 0)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from \"%s\"%s", CONMAN_MSG_PREFIX, console->name, console->aux.serial.dev, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); open_serial_obj(console); } else if (is_telnet_obj(console) && (console->aux.telnet.state != CONMAN_TELNET_UP)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from <%s:%d>%s", CONMAN_MSG_PREFIX, console->name, console->aux.telnet.host, console->aux.telnet.port, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); console->aux.telnet.delay = TELNET_MIN_TIMEOUT; /* * Do not call connect_telnet_obj() while in the PENDING state since * it would be misinterpreted as the completion of the non-blocking * connect(). */ if (console->aux.telnet.state == CONMAN_TELNET_DOWN) { open_telnet_obj(console); } } else if (is_unixsock_obj(console) && (console->fd < 0)) { assert(console->aux.unixsock.state == CONMAN_UNIXSOCK_DOWN); snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from \"%s\"%s", CONMAN_MSG_PREFIX, console->name, console->aux.unixsock.dev, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); open_unixsock_obj(console); } #if WITH_FREEIPMI else if (is_ipmi_obj(console) && (console->aux.ipmi.state != CONMAN_IPMI_UP)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from <%s>%s", CONMAN_MSG_PREFIX, console->name, console->aux.ipmi.host, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); if (console->aux.ipmi.state == CONMAN_IPMI_DOWN) { open_ipmi_obj(console); } } #endif /* WITH_FREEIPMI */ return; }