--- Makefile | 13 +++- email.c | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ email.h | 34 ++++++++++ mcelog.c | 93 ++++++++++++++++++++++++++++- mcelog.h | 1 msg.c | 8 ++ 6 files changed, 346 insertions(+), 3 deletions(-) --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ +CONFIG_EMAIL := 1 CFLAGS := -g -Os prefix := /usr etcprefix := @@ -38,15 +39,23 @@ broadwell_de.o broadwell_epex.o skylake_xeon.o \ denverton.o i10nm.o \ msr.o bus.o unknown.o +EMAIL_OBJ := email.o CLEAN := mcelog dmi tsc dbquery .depend .depend.X dbquery.o \ - version.o version.c version.tmp + ${EMAIL_OBJ} version.o version.c version.tmp DOC := mce.pdf ADD_DEFINES := +ifdef CONFIG_EMAIL +ADD_DEFINES := -DCONFIG_EMAIL=1 +LIBS := -lesmtp +OBJ += ${EMAIL_OBJ} +endif + SRC := $(OBJ:.o=.c) mcelog: ${OBJ} version.o + $(CC) $(LDFLAGS) $^ ${LIBS} -o $@ # dbquery intentionally not installed by default install: mcelog mcelog.conf mcelog.conf.5 mcelog.triggers.5 @@ -81,7 +90,7 @@ depend: .depend %.o: %.c - $(CC) -c $(CFLAGS) $(CPPFLAGS) $(WARNINGS) $(ADD_DEFINES) -o $@ $< + $(CC) -c $(CFLAGS) $(CPPFLAGS) $(WARNINGS) $(ADD_DEFINES) $< -o $@ version.tmp: FORCE ( printf "char version[] = \"" ; \ --- /dev/null +++ b/email.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include + +#define __USE_GNU +/* To fetch the dnsname */ +#include +#include +#include + +#include +#include "mcelog.h" +#include "email.h" + +#define MAX_STRING_LEN 512 +char c_recipient[MAX_STRING_LEN] = ""; +static int debug; +static char dnsname[MAX_STRING_LEN]; + +static char buf[128]; +#define ERROR() { fprintf (stderr, "SMTP problem [%d] %s\n", __LINE__, \ + smtp_strerror (smtp_errno (), buf, sizeof buf)); \ + return -1; } + + +void email_usage(void) { + fprintf(stderr, + "--email address Requires daemon mode\n"); +} + +int email_cmd(int opt, int ac, char **av) +{ + char *arg = optarg; + + switch (opt) { + case O_EMAIL_ADDRESS: + if (arg) { + if (strlen(arg) >= MAX_STRING_LEN) { + Eprintf("email address too long" + " [max:%d]\n", MAX_STRING_LEN); + return 0; + } + strcpy(c_recipient, arg); + return 1; + } + case O_EMAIL_DEBUG: + debug = 1; + return 0; + } + return 0; +} + +int email_env(void) +{ + char *email_env = getenv("MCELOG_EMAIL_DEBUG"); + + if (email_env) + debug=0; + + email_env = getenv("MCELOG_ADMIN_EMAIL"); + /* No email validation, but at least check for not being empty... */ + if (email_env && strlen(email_env) > 1) { + strncpy(c_recipient, email_env, MAX_STRING_LEN - 1); + return 1; + } + return 0; +} + +/* Callback to prnt the recipient status */ +static void +print_recipient_status (smtp_recipient_t recipient, + const char *mailbox, void *arg) +{ + const smtp_status_t *status; + + status = smtp_recipient_status (recipient); + if (debug) + printf ("%s: %d %s", mailbox, status->code, status->text); +} + +void setup_mail_header(FILE *fp, struct mce *m) +{ + char host[MAX_STRING_LEN]; + struct addrinfo hints; + struct addrinfo *res=NULL; + int ret, retry=3; + + /* Taken from net-tools hostname.c showhname() */ + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_CANONNAME | AI_CANONIDN; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (gethostname(host, MAX_STRING_LEN)) { + fprintf(stderr, "Cannot get host name\n"); + return; + } + + do { + ret = getaddrinfo(host, NULL, &hints, &res); + } while(ret == EAI_AGAIN && retry-- > 0 + && usleep(50000) == 0); + + if (ret != 0 || res == NULL) { + fprintf(stderr, "Could not retrieve hostname\n"); + return; + } + + memset(dnsname, '\0', MAX_STRING_LEN); + strncpy(dnsname, res->ai_canonname, MAX_STRING_LEN - 1); + + fprintf(fp, "Return-Path: \r\n" + "Subject: Machine Check Exception on %s detected\r\n" + "MIME-Version: 1.0\r\n" + "Content-Type: text/plain;\r\n" + " charset=iso-8859-1\r\n" + "Content-Transfer-Encoding: 7bit\r\n\r\n", dnsname); + freeaddrinfo(res); +} + + +int send_mail(FILE *fp) +{ + char smtp_host[MAX_STRING_LEN] = "localhost:25"; + char from[MAX_STRING_LEN]; + + const smtp_status_t *status; + smtp_session_t session; + smtp_message_t message; + smtp_recipient_t recipient; + struct sigaction sa; + + session = smtp_create_session (); + message = smtp_add_message (session); + + snprintf(from, MAX_STRING_LEN, "root@%s", dnsname); + + /* NB. libESMTP sets timeouts as it progresses through the protocol. + In addition the remote server might close its socket on a timeout. + Consequently libESMTP may sometimes try to write to a socket with + no reader. Ignore SIGPIPE, then the program doesn't get killed + if/when this happens. */ + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + + /* Set the host running the SMTP server. LibESMTP has a default port + number of 587, however this is not widely deployed so the port + is specified as 25 along with the default MTA host. */ + if (!smtp_set_server (session, smtp_host)) + ERROR(); + + smtp_set_reverse_path (message, from); + + /* RFC 2822 doesn't require recipient headers but a To: header would + be nice to have if not present. */ + smtp_set_header (message, "To", NULL, NULL); + + /* RFC 2822 doesn't require recipient headers but a To: header would + be nice to have if not present. */ + if (!smtp_set_header (message, "From", "mcelog", from)) + ERROR(); + + smtp_set_message_fp (message, fp); + + recipient = smtp_add_recipient (message, c_recipient); + if (!recipient) + ERROR(); + if (!smtp_dsn_set_notify (recipient, Notify_NEVER)) + ERROR(); + + /* Initiate a connection to the SMTP server and transfer the + message. */ + if (!smtp_start_session (session)) + Eprintf("SMTP server problem %s\n", + smtp_strerror (smtp_errno (), buf, sizeof buf)); + else { + /* Report on the success or otherwise of the mail transfer. + */ + if (debug) { + status = smtp_message_transfer_status (message); + printf ("%d %s", status->code, + (status->text != NULL) ? status->text : "\n"); + } + smtp_enumerate_recipients (message, print_recipient_status, NULL); + } + + if (debug) + fprintf(stderr, "Email sent successfully!\n"); + + /* Free resources consumed by the program. + */ + smtp_destroy_session (session); + return 0; +} --- /dev/null +++ b/email.h @@ -0,0 +1,34 @@ +#ifndef _MCELOG_EMAIL_H_ +#define _MCELOG_EMAIL_H_ + +extern FILE *email_fd; +extern int email_mode; + +#ifdef CONFIG_EMAIL +extern int send_mail(FILE *email_fd); +extern void setup_mail_header(FILE *email_fd, struct mce *m); +extern void email_usage(void); +extern int email_cmd(int opt, int ac, char **av); +extern int email_env(void); + +#define EMAIL_OPTIONS \ + { "email", 1, NULL, O_EMAIL_ADDRESS }, \ + { "email-debug", 0, NULL, O_EMAIL_DEBUG }, + +enum email_options { + O_EMAIL_ADDRESS = O_EMAIL, + O_EMAIL_DEBUG, +}; + +#else +/* +static int send_mail(FILE *email_fd) { return 0; } +static void setup_mail_header(FILE *email_fd) { return; }; +*/ +static void email_usage(void) { return; } +static int email_cmd(int opt, int ac, char **av) { return 0; } +static int email_env(void) { return 0; } +#define EMAIL_OPTIONS +#endif + +#endif --- a/mcelog.c +++ b/mcelog.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include "mcelog.h" #include "paths.h" @@ -60,6 +61,9 @@ #include "bus.h" #include "unknown.h" +#include "email.h" +int email_mode; + enum cputype cputype = CPU_GENERIC; char *logfn = LOG_DEV_FILENAME; @@ -71,7 +75,7 @@ static int cpumhz_forced; int ascii_mode; int dump_raw_ascii; -int daemon_mode; +int daemon_mode = 0; static char *inputfile; char *processor_flags; static int foreground; @@ -1022,6 +1026,7 @@ "--max-corr-err-counters Max page correctable error counters\n" "--help Display this message.\n" ); + email_usage(); printf("\n"); print_cputypes(); } @@ -1093,6 +1098,7 @@ { "max-corr-err-counters", 1, NULL, O_MAX_CORR_ERR_COUNTERS }, { "help", 0, NULL, O_HELP }, { "is-cpu-supported", 0, NULL, O_IS_CPU_SUPPORTED }, + EMAIL_OPTIONS {} }; @@ -1287,11 +1293,86 @@ } } +#ifdef CONFIG_EMAIL +pid_t c_pid; + +/* Not more than 12 mails in 5 mins... */ +#define LAST_LIMIT_COUNT (60 * 5) +#define LIMIT_COUNT 12 +static time_t last_limit_count; +static int limit_count; +static const char *mail_thread = "mcelog_mail_thread"; + + +static int setup_email(struct mce *m) { + int pdes[2]; + static int suppressed; + int ret; + + if (time(NULL) - last_limit_count < LAST_LIMIT_COUNT) { + if (limit_count >= LIMIT_COUNT && !suppressed) { + Eprintf("email rate limit [%d mails per %d mins]" + " reached, mails supressed\n", + LIMIT_COUNT, LAST_LIMIT_COUNT / 60); + suppressed = 1; + } + if (suppressed) + return -1; + } else { + suppressed = 0; + limit_count = 0; + last_limit_count = time(NULL); + } + + limit_count++; + + ret = pipe(pdes); + if (ret) + return ret; + + c_pid = mcelog_fork(mail_thread); + if ( c_pid == 0 ) { /* child */ + FILE *x = fdopen(pdes[0], "r"); + close(pdes[1]); + send_mail(x); + exit(0); + } else { + close(pdes[0]); + /* something went wrong, better close... */ + if (email_fd) + fclose(email_fd); + /* Wprintf will now also write into this pipe */ + email_fd = fdopen(pdes[1], "w"); + setup_mail_header(email_fd, m); + } + return 0; +} + +static int finish_email(void) { + int status; + + fclose(email_fd); + fprintf(stderr, "Email set up for sending\n"); + /* Anything else we can make sure we do not get orphaned threads? */ + waitpid (c_pid, &status, WUNTRACED); + if (WIFSTOPPED(status)){ + kill(c_pid, 9); + SYSERRprintf("Killed stopped email thread %d\n", + c_pid); + return -1; + } + email_fd = NULL; + return 0; +} + +#endif + static void process(int fd, unsigned recordlen, unsigned loglen, char *buf) { int i; int len, count; int finish = 0, flags; + int mail_setup = 0; if (recordlen == 0) { Wprintf("no data in mce record\n"); @@ -1318,12 +1399,16 @@ finish = 1; if (!mce_filter(mce, recordlen)) continue; + if (email_mode) + mail_setup = setup_email(mce); if (!dump_raw_ascii) { disclaimer(); Wprintf("MCE %d\n", i); dump_mce(mce, recordlen); } else dump_mce_raw_ascii(mce, recordlen); + if (email_mode && !mail_setup) + finish_email(); flushlog(); } @@ -1437,6 +1522,8 @@ noargs(ac, av); fprintf(stderr, "mcelog %s\n", MCELOG_VERSION); exit(0); + } else if (email_cmd(opt, ac, av)) { + email_mode = 1; } else if (opt == 0) break; } @@ -1471,6 +1558,10 @@ usage(); exit(1); } + if (email_mode == 0) + email_mode = email_env(); + /* email sending only in daemon mode */ + email_mode &= daemon_mode; checkdmi(); general_setup(); --- a/mcelog.h +++ b/mcelog.h @@ -156,6 +156,7 @@ enum option_ranges { O_COMMON = 500, O_DISKDB = 1000, + O_EMAIL = 1500, }; enum syslog_opt { --- a/msg.c +++ b/msg.c @@ -8,10 +8,13 @@ #include "mcelog.h" #include "msg.h" #include "memutil.h" +#include "email.h" + enum syslog_opt syslog_opt = SYSLOG_REMARK; int syslog_level = LOG_WARNING; static FILE *output_fh; + FILE *email_fd; static char *output_fn; int need_stdout(void) @@ -135,6 +138,11 @@ n = vfprintf(output_fh ? output_fh : stdout, fmt, ap); va_end(ap); } + if (email_fd) { + va_start(ap,fmt); + n = vfprintf(email_fd, fmt, ap); + va_end(ap); + } return n; }