// nagless - mail delivery agent (MDA) that forwards but suppresses // messages for some amount of time, to cut down on monitoring spam #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // PORTABILITY some OS put this at /usr/lib/sendmail or you may instead // need to use something like msmtp to send the mail off elsewhere #ifndef SENDMAIL_PATH #define SENDMAIL_PATH "/usr/sbin/sendmail" #endif // NOTE syslog any other things have limits if you want to increase the // maximum Subject length sent #define MAX_BLATHER 70 #define NAGFAIL EX_TEMPFAIL enum { PIPE_R, PIPE_W }; char *Checkpoint_File; char *Contact_Address; int Nagless_Sec; int Flag_Syslog; // -s static void emit_help(void); static void handle_subject(char *subj, ssize_t subjlen); static void maybe_notify(char *subj, ssize_t subjlen); int main(int argc, char *argv[]) { int ch; const char *errstr; char *line = NULL; size_t linesize = 0; ssize_t linelen; if (!setlocale(LC_ALL, "C")) err(1, "setlocale"); while ((ch = getopt(argc, argv, "s")) != -1) { switch (ch) { case 's': Flag_Syslog = 1; break; default: emit_help(); } } argc -= optind; argv += optind; if (argc != 3 || !argv[0] || !argv[0][0] || !argv[1] || !argv[1][0] || !argv[2] || !argv[2][0]) emit_help(); Checkpoint_File = argv[0]; // PORTABILITY use libbsd or strtoul(3) or hopefully not atoi(3) Nagless_Sec = (int) strtonum(argv[1], 0, INT_MAX, &errstr); if (errstr) err(NAGFAIL, "strtonum '%s': %s", argv[1], errstr); Contact_Address = argv[2]; if (Flag_Syslog) openlog("nagless", LOG_NDELAY, LOG_USER); #ifdef __OpenBSD__ if (pledge("exec fattr proc rpath stdio unveil", NULL) == -1) err(1, "pledge"); if (unveil(SENDMAIL_PATH, "x") == -1) err(1, "unveil"); if (unveil(Checkpoint_File, "rw") == -1) err(1, "unveil"); if (unveil(NULL, NULL) == -1) err(1, "unveil"); #endif while ((linelen = getline(&line, &linesize, stdin)) != -1) { // TODO but what if we get a \r\n line?? if (linelen == 1) goto DONE; // blank line, end of headers if (!strncmp(line, "Subject: ", 9)) { handle_subject(line + 9, linelen - 10); goto DONE; } } //free(line); //if (ferror(stdin)) err(NAGFAIL, "getline"); DONE: exit(EXIT_SUCCESS); } static void emit_help(void) { fputs( "Usage: nagless [-s] checkpoint-file window-seconds notify-address\n", stderr); exit(NAGFAIL); } inline static void handle_subject(char *subj, ssize_t subjlen) { char *cp; if (subjlen <= 0) return; if (subjlen > MAX_BLATHER) subjlen = MAX_BLATHER; cp = subj; for (size_t i = 0; i < (size_t) subjlen; ++i, ++cp) if (!isprint(*cp)) *cp = '.'; if (Flag_Syslog) syslog(LOG_NOTICE, "%.*s", (int) subjlen, subj); maybe_notify(subj, subjlen); } inline static void maybe_notify(char *subj, ssize_t subjlen) { char *failure = "unknown"; int ret, status, tochild[2]; pid_t child, pid; struct stat sb; time_t now; if (time(&now) == (time_t) -1) err(NAGFAIL, "time"); if (stat(Checkpoint_File, &sb) == -1) err(NAGFAIL, "stat '%s'", Checkpoint_File); if (llabs(now - sb.st_mtim.tv_sec) < Nagless_Sec) return; utimes(Checkpoint_File, NULL); if (pipe(tochild) == -1) { failure = "pipe"; goto NOTIFY_FAIL; } pid = fork(); if (pid < 0) { failure = "fork"; goto NOTIFY_FAIL; } else if (pid == 0) { // child close(tochild[PIPE_W]); if (dup2(tochild[PIPE_R], STDIN_FILENO) != STDIN_FILENO) err(1, "dup2"); close(tochild[PIPE_R]); execl(SENDMAIL_PATH, "sendmail", "-t", (char *) 0); err(1, "execl"); } close(tochild[PIPE_R]); ret = dprintf(tochild[PIPE_W], "To: %s\nSubject: %.*s\n\nfyi", Contact_Address, (int) subjlen, subj); if (ret < 0) warn("dprintf"); else if (ret == 0) warn("dprintf zero write??"); if (close(tochild[PIPE_W]) == -1) warn("close"); // TODO may need timeout here, in the event the mail program gets stuck child = wait(&status); if (child == -1) warn("wait"); if (status) warnx("non-zero exit (%d)", status); return; NOTIFY_FAIL: if (Flag_Syslog) syslog(LOG_ERR, "notification failure: %s", failure); warn("notification failure: %s", failure); }