--- Makefile | 10 ++- email.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ email.h | 34 ++++++++++ mcelog.c | 93 +++++++++++++++++++++++++++++ mcelog.h | 1 msg.c | 8 ++ 6 files changed, 343 insertions(+), 2 deletions(-) Index: mcelog-1.0.8/Makefile =================================================================== --- mcelog-1.0.8.orig/Makefile 2014-12-20 19:35:05.000000000 +0100 +++ mcelog-1.0.8/Makefile 2015-01-22 14:55:13.323708502 +0100 @@ -1,3 +1,4 @@ +CONFIG_EMAIL := 1 CFLAGS := -g -Os prefix := /usr etcprefix := @@ -37,7 +38,8 @@ client.o cache.o sysfs.o yellow.o page.o rbtree.o \ xeon75xx.o sandy-bridge.o ivy-bridge.o haswell.o msr.o bus.o unknown.o DISKDB_OBJ := diskdb.o dimm.o db.o -CLEAN := mcelog dmi tsc dbquery .depend .depend.X dbquery.o ${DISKDB_OBJ} +EMAIL_OBJ := email.o +CLEAN := mcelog dmi tsc dbquery .depend .depend.X dbquery.o ${DISKDB_OBJ} ${EMAIL_OBJ} DOC := mce.pdf ADD_DEFINES := @@ -49,6 +51,12 @@ all: dbquery endif +ifdef CONFIG_EMAIL +ADD_DEFINES := -DCONFIG_EMAIL=1 +LDFLAGS := -lesmtp +OBJ += ${EMAIL_OBJ} +endif + SRC := $(OBJ:.o=.c) mcelog: ${OBJ} Index: mcelog-1.0.8/email.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ mcelog-1.0.8/email.c 2015-01-22 14:55:13.327708502 +0100 @@ -0,0 +1,199 @@ +#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"); + if (email_env) { + 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; +} Index: mcelog-1.0.8/email.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ mcelog-1.0.8/email.h 2015-01-22 14:55:13.331708502 +0100 @@ -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 Index: mcelog-1.0.8/mcelog.c =================================================================== --- mcelog-1.0.8.orig/mcelog.c 2014-12-20 19:35:05.000000000 +0100 +++ mcelog-1.0.8/mcelog.c 2015-01-22 14:55:13.335708502 +0100 @@ -37,6 +37,7 @@ #include #include #include +#include #include #include "mcelog.h" #include "paths.h" @@ -61,6 +62,9 @@ #include "bus.h" #include "unknown.h" +#include "email.h" +int email_mode; + enum cputype cputype = CPU_GENERIC; char *logfn = LOG_DEV_FILENAME; @@ -72,7 +76,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; @@ -944,6 +948,7 @@ "--pidfile file Write pid of daemon into file\n" "--no-imc-log Disable extended iMC logging\n" ); + email_usage(); diskdb_usage(); print_cputypes(); exit(1); @@ -1011,6 +1016,7 @@ { "debug-numerrors", 0, NULL, O_DEBUG_NUMERRORS }, /* undocumented: for testing */ { "no-imc-log", 0, NULL, O_NO_IMC_LOG }, DISKDB_OPTIONS + EMAIL_OPTIONS {} }; @@ -1188,11 +1194,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"); @@ -1219,12 +1300,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(); } @@ -1335,6 +1420,8 @@ exit(0); } else if (diskdb_cmd(opt, ac, av)) { exit(0); + } else if (email_cmd(opt, ac, av)) { + email_mode = 1; } else if (opt == 0) break; } @@ -1363,6 +1450,10 @@ logfn = av[optind++]; if (av[optind]) usage(); + if (email_mode == 0) + email_mode = email_env(); + /* email sending only in daemon mode */ + email_mode &= daemon_mode; checkdmi(); general_setup(); Index: mcelog-1.0.8/mcelog.h =================================================================== --- mcelog-1.0.8.orig/mcelog.h 2014-12-20 19:35:05.000000000 +0100 +++ mcelog-1.0.8/mcelog.h 2015-01-22 14:55:13.339708502 +0100 @@ -125,6 +125,7 @@ enum option_ranges { O_COMMON = 500, O_DISKDB = 1000, + O_EMAIL = 1500, }; enum syslog_opt { Index: mcelog-1.0.8/msg.c =================================================================== --- mcelog-1.0.8.orig/msg.c 2014-12-20 19:35:05.000000000 +0100 +++ mcelog-1.0.8/msg.c 2015-01-22 14:55:13.343708502 +0100 @@ -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; }