/* * Copyright (C) 2011 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** @file * * Syslog protocol * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Set default console usage if applicable */ #if ! ( defined ( CONSOLE_SYSLOG ) && CONSOLE_EXPLICIT ( CONSOLE_SYSLOG ) ) #undef CONSOLE_SYSLOG #define CONSOLE_SYSLOG ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_TUI ) #endif /** The syslog server */ static union { struct sockaddr sa; struct sockaddr_tcpip st; struct sockaddr_in sin; struct sockaddr_in6 sin6; } logserver = { .st = { .st_port = htons ( SYSLOG_PORT ), }, }; /** Syslog UDP interface operations */ static struct interface_operation syslogger_operations[] = {}; /** Syslog UDP interface descriptor */ static struct interface_descriptor syslogger_desc = INTF_DESC_PURE ( syslogger_operations ); /** The syslog UDP interface */ static struct interface syslogger = INTF_INIT ( syslogger_desc ); /****************************************************************************** * * Console driver * ****************************************************************************** */ /** Host name (for log messages) */ static char *syslog_hostname; /** Domain name (for log messages) */ static char *syslog_domain; /** * Transmit formatted syslog message * * @v xfer Data transfer interface * @v severity Severity * @v message Message * @v terminator Message terminator * @ret rc Return status code */ int syslog_send ( struct interface *xfer, unsigned int severity, const char *message, const char *terminator ) { const char *hostname = ( syslog_hostname ? syslog_hostname : "" ); const char *domain = ( ( hostname[0] && syslog_domain ) ? syslog_domain : "" ); return xfer_printf ( xfer, "<%d>%s%s%s%sipxe: %s%s", SYSLOG_PRIORITY ( SYSLOG_DEFAULT_FACILITY, severity ), hostname, ( domain[0] ? "." : "" ), domain, ( hostname[0] ? " " : "" ), message, terminator ); } /****************************************************************************** * * Console driver * ****************************************************************************** */ /** Syslog line buffer */ static char syslog_buffer[SYSLOG_BUFSIZE]; /** Syslog severity */ static unsigned int syslog_severity = SYSLOG_DEFAULT_SEVERITY; /** * Handle ANSI set syslog priority (private sequence) * * @v ctx ANSI escape sequence context * @v count Parameter count * @v params List of graphic rendition aspects */ static void syslog_handle_priority ( struct ansiesc_context *ctx __unused, unsigned int count __unused, int params[] ) { if ( params[0] >= 0 ) { syslog_severity = params[0]; } else { syslog_severity = SYSLOG_DEFAULT_SEVERITY; } } /** Syslog ANSI escape sequence handlers */ static struct ansiesc_handler syslog_handlers[] = { { ANSIESC_LOG_PRIORITY, syslog_handle_priority }, { 0, NULL } }; /** Syslog line console */ static struct line_console syslog_line = { .buffer = syslog_buffer, .len = sizeof ( syslog_buffer ), .ctx = { .handlers = syslog_handlers, }, }; /** Syslog recursion marker */ static int syslog_entered; /** * Print a character to syslog console * * @v character Character to be printed */ static void syslog_putchar ( int character ) { int rc; /* Ignore if we are already mid-logging */ if ( syslog_entered ) return; /* Fill line buffer */ if ( line_putchar ( &syslog_line, character ) == 0 ) return; /* Guard against re-entry */ syslog_entered = 1; /* Send log message */ if ( ( rc = syslog_send ( &syslogger, syslog_severity, syslog_buffer, "" ) ) != 0 ) { DBG ( "SYSLOG could not send log message: %s\n", strerror ( rc ) ); } /* Clear re-entry flag */ syslog_entered = 0; } /** Syslog console driver */ struct console_driver syslog_console __console_driver = { .putchar = syslog_putchar, .disabled = CONSOLE_DISABLED, .usage = CONSOLE_SYSLOG, }; /****************************************************************************** * * Settings * ****************************************************************************** */ /** IPv4 syslog server setting */ const struct setting syslog_setting __setting ( SETTING_MISC, syslog ) = { .name = "syslog", .description = "Syslog server", .tag = DHCP_LOG_SERVERS, .type = &setting_type_ipv4, }; /** IPv6 syslog server setting */ const struct setting syslog6_setting __setting ( SETTING_MISC, syslog6 ) = { .name = "syslog6", .description = "Syslog server", .tag = DHCPV6_LOG_SERVERS, .type = &setting_type_ipv6, .scope = &ipv6_scope, }; /** * Strip invalid characters from host/domain name * * @v name Name to strip */ static void syslog_fix_name ( char *name ) { char *fixed = name; int c; /* Do nothing if name does not exist */ if ( ! name ) return; /* Strip any non-printable or whitespace characters from the name */ do { c = *(name++); *fixed = c; if ( isprint ( c ) && ! isspace ( c ) ) fixed++; } while ( c ); } /** * Apply syslog settings * * @ret rc Return status code */ static int apply_syslog_settings ( void ) { struct sockaddr old_logserver; int rc; /* Fetch hostname and domain name */ free ( syslog_hostname ); fetch_string_setting_copy ( NULL, &hostname_setting, &syslog_hostname ); syslog_fix_name ( syslog_hostname ); free ( syslog_domain ); fetch_string_setting_copy ( NULL, &domain_setting, &syslog_domain ); syslog_fix_name ( syslog_domain ); /* Fetch log server */ syslog_console.disabled = CONSOLE_DISABLED; memcpy ( &old_logserver, &logserver, sizeof ( old_logserver ) ); logserver.sa.sa_family = 0; if ( fetch_ipv6_setting ( NULL, &syslog6_setting, &logserver.sin6.sin6_addr ) >= 0 ) { logserver.sin6.sin6_family = AF_INET6; } else if ( fetch_ipv4_setting ( NULL, &syslog_setting, &logserver.sin.sin_addr ) >= 0 ) { logserver.sin.sin_family = AF_INET; } if ( logserver.sa.sa_family ) { syslog_console.disabled = 0; DBG ( "SYSLOG using log server %s\n", sock_ntoa ( &logserver.sa ) ); } /* Do nothing unless log server has changed */ if ( memcmp ( &logserver, &old_logserver, sizeof ( logserver ) ) == 0 ) return 0; /* Reset syslog connection */ intf_restart ( &syslogger, 0 ); /* Do nothing unless we have a log server */ if ( syslog_console.disabled ) { DBG ( "SYSLOG has no log server\n" ); return 0; } /* Connect to log server */ if ( ( rc = xfer_open_socket ( &syslogger, SOCK_DGRAM, &logserver.sa, NULL ) ) != 0 ) { DBG ( "SYSLOG cannot connect to log server: %s\n", strerror ( rc ) ); return rc; } return 0; } /** Syslog settings applicator */ struct settings_applicator syslog_applicator __settings_applicator = { .apply = apply_syslog_settings, };