| /* |
| * "mailto" notifier for CUPS. |
| * |
| * Copyright © 2007-2018 by Apple Inc. |
| * Copyright © 1997-2005 by Easy Software Products. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more |
| * information. |
| */ |
| |
| /* |
| * Include necessary headers... |
| */ |
| |
| #include <cups/cups-private.h> |
| #include <sys/wait.h> |
| #include <signal.h> |
| |
| |
| /* |
| * Globals... |
| */ |
| |
| char mailtoCc[1024]; /* Cc email address */ |
| char mailtoFrom[1024]; /* From email address */ |
| char mailtoReplyTo[1024]; /* Reply-To email address */ |
| char mailtoSubject[1024]; /* Subject prefix */ |
| char mailtoSMTPServer[1024]; /* SMTP server to use */ |
| char mailtoSendmail[1024]; /* Sendmail program to use */ |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| void email_message(const char *to, const char *subject, const char *text); |
| int load_configuration(void); |
| cups_file_t *pipe_sendmail(const char *to); |
| void print_attributes(ipp_t *ipp, int indent); |
| |
| |
| /* |
| * 'main()' - Main entry for the mailto notifier. |
| */ |
| |
| int /* O - Exit status */ |
| main(int argc, /* I - Number of command-line arguments */ |
| char *argv[]) /* I - Command-line arguments */ |
| { |
| int i; /* Looping var */ |
| ipp_t *msg; /* Event message from scheduler */ |
| ipp_state_t state; /* IPP event state */ |
| char *subject, /* Subject for notification message */ |
| *text; /* Text for notification message */ |
| cups_lang_t *lang; /* Language info */ |
| char temp[1024]; /* Temporary string */ |
| int templen; /* Length of temporary string */ |
| #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) |
| struct sigaction action; /* POSIX sigaction data */ |
| #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ |
| |
| |
| /* |
| * Don't buffer stderr... |
| */ |
| |
| setbuf(stderr, NULL); |
| |
| /* |
| * Ignore SIGPIPE signals... |
| */ |
| |
| #ifdef HAVE_SIGSET |
| sigset(SIGPIPE, SIG_IGN); |
| #elif defined(HAVE_SIGACTION) |
| memset(&action, 0, sizeof(action)); |
| action.sa_handler = SIG_IGN; |
| sigaction(SIGPIPE, &action, NULL); |
| #else |
| signal(SIGPIPE, SIG_IGN); |
| #endif /* HAVE_SIGSET */ |
| |
| /* |
| * Validate command-line options... |
| */ |
| |
| if (argc != 3) |
| { |
| fputs("Usage: mailto mailto:user@domain.com notify-user-data\n", stderr); |
| return (1); |
| } |
| |
| if (strncmp(argv[1], "mailto:", 7)) |
| { |
| fprintf(stderr, "ERROR: Bad recipient \"%s\"!\n", argv[1]); |
| return (1); |
| } |
| |
| fprintf(stderr, "DEBUG: argc=%d\n", argc); |
| for (i = 0; i < argc; i ++) |
| fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]); |
| |
| /* |
| * Load configuration data... |
| */ |
| |
| if ((lang = cupsLangDefault()) == NULL) |
| return (1); |
| |
| if (!load_configuration()) |
| return (1); |
| |
| /* |
| * Get the reply-to address... |
| */ |
| |
| templen = sizeof(temp); |
| httpDecode64_2(temp, &templen, argv[2]); |
| |
| if (!strncmp(temp, "mailto:", 7)) |
| strlcpy(mailtoReplyTo, temp + 7, sizeof(mailtoReplyTo)); |
| else if (temp[0]) |
| fprintf(stderr, "WARNING: Bad notify-user-data value (%d bytes) ignored!\n", |
| templen); |
| |
| /* |
| * Loop forever until we run out of events... |
| */ |
| |
| for (;;) |
| { |
| /* |
| * Get the next event... |
| */ |
| |
| msg = ippNew(); |
| while ((state = ippReadFile(0, msg)) != IPP_DATA) |
| { |
| if (state <= IPP_IDLE) |
| break; |
| } |
| |
| fprintf(stderr, "DEBUG: state=%d\n", state); |
| |
| if (state == IPP_ERROR) |
| fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr); |
| |
| if (state <= IPP_IDLE) |
| { |
| /* |
| * Out of messages, free memory and then exit... |
| */ |
| |
| ippDelete(msg); |
| return (0); |
| } |
| |
| /* |
| * Get the subject and text for the message, then email it... |
| */ |
| |
| subject = cupsNotifySubject(lang, msg); |
| text = cupsNotifyText(lang, msg); |
| |
| fprintf(stderr, "DEBUG: subject=\"%s\"\n", subject); |
| fprintf(stderr, "DEBUG: text=\"%s\"\n", text); |
| |
| if (subject && text) |
| email_message(argv[1] + 7, subject, text); |
| else |
| { |
| fputs("ERROR: Missing attributes in event notification!\n", stderr); |
| print_attributes(msg, 4); |
| } |
| |
| /* |
| * Free the memory used for this event... |
| */ |
| |
| if (subject) |
| free(subject); |
| |
| if (text) |
| free(text); |
| |
| ippDelete(msg); |
| } |
| } |
| |
| |
| /* |
| * 'email_message()' - Email a notification message. |
| */ |
| |
| void |
| email_message(const char *to, /* I - Recipient of message */ |
| const char *subject, /* I - Subject of message */ |
| const char *text) /* I - Text of message */ |
| { |
| cups_file_t *fp; /* Pipe/socket to mail server */ |
| const char *nl; /* Newline to use */ |
| char response[1024]; /* SMTP response buffer */ |
| |
| |
| /* |
| * Connect to the mail server... |
| */ |
| |
| if (mailtoSendmail[0]) |
| { |
| /* |
| * Use the sendmail command... |
| */ |
| |
| fp = pipe_sendmail(to); |
| |
| if (!fp) |
| return; |
| |
| nl = "\n"; |
| } |
| else |
| { |
| /* |
| * Use an SMTP server... |
| */ |
| |
| char hostbuf[1024]; /* Local hostname */ |
| |
| |
| if (strchr(mailtoSMTPServer, ':')) |
| { |
| fp = cupsFileOpen(mailtoSMTPServer, "s"); |
| } |
| else |
| { |
| char spec[1024]; /* Host:service spec */ |
| |
| |
| snprintf(spec, sizeof(spec), "%s:smtp", mailtoSMTPServer); |
| fp = cupsFileOpen(spec, "s"); |
| } |
| |
| if (!fp) |
| { |
| fprintf(stderr, "ERROR: Unable to connect to SMTP server \"%s\"!\n", |
| mailtoSMTPServer); |
| return; |
| } |
| |
| fprintf(stderr, "DEBUG: Connected to \"%s\"...\n", mailtoSMTPServer); |
| |
| if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) |
| goto smtp_error; |
| fprintf(stderr, "DEBUG: <<< %s\n", response); |
| |
| cupsFilePrintf(fp, "HELO %s\r\n", |
| httpGetHostname(NULL, hostbuf, sizeof(hostbuf))); |
| fprintf(stderr, "DEBUG: >>> HELO %s\n", hostbuf); |
| |
| if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) |
| goto smtp_error; |
| fprintf(stderr, "DEBUG: <<< %s\n", response); |
| |
| cupsFilePrintf(fp, "MAIL FROM:%s\r\n", mailtoFrom); |
| fprintf(stderr, "DEBUG: >>> MAIL FROM:%s\n", mailtoFrom); |
| |
| if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) |
| goto smtp_error; |
| fprintf(stderr, "DEBUG: <<< %s\n", response); |
| |
| cupsFilePrintf(fp, "RCPT TO:%s\r\n", to); |
| fprintf(stderr, "DEBUG: >>> RCPT TO:%s\n", to); |
| |
| if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) |
| goto smtp_error; |
| fprintf(stderr, "DEBUG: <<< %s\n", response); |
| |
| cupsFilePuts(fp, "DATA\r\n"); |
| fputs("DEBUG: DATA\n", stderr); |
| |
| if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) |
| goto smtp_error; |
| fprintf(stderr, "DEBUG: <<< %s\n", response); |
| |
| nl = "\r\n"; |
| } |
| |
| /* |
| * Send the message... |
| */ |
| |
| cupsFilePrintf(fp, "Date: %s%s", httpGetDateString(time(NULL)), nl); |
| cupsFilePrintf(fp, "From: %s%s", mailtoFrom, nl); |
| cupsFilePrintf(fp, "Subject: %s %s%s", mailtoSubject, subject, nl); |
| if (mailtoReplyTo[0]) |
| { |
| cupsFilePrintf(fp, "Sender: %s%s", mailtoReplyTo, nl); |
| cupsFilePrintf(fp, "Reply-To: %s%s", mailtoReplyTo, nl); |
| } |
| cupsFilePrintf(fp, "To: %s%s", to, nl); |
| if (mailtoCc[0]) |
| cupsFilePrintf(fp, "Cc: %s%s", mailtoCc, nl); |
| cupsFilePrintf(fp, "Content-Type: text/plain%s", nl); |
| cupsFilePuts(fp, nl); |
| cupsFilePrintf(fp, "%s%s", text, nl); |
| cupsFilePrintf(fp, ".%s", nl); |
| |
| /* |
| * Close the connection to the mail server... |
| */ |
| |
| if (mailtoSendmail[0]) |
| { |
| /* |
| * Close the pipe and wait for the sendmail command to finish... |
| */ |
| |
| int status; /* Exit status */ |
| |
| |
| cupsFileClose(fp); |
| |
| while (wait(&status)) |
| { |
| if (errno != EINTR) |
| { |
| fprintf(stderr, "DEBUG: Unable to get child status: %s\n", |
| strerror(errno)); |
| status = 0; |
| break; |
| } |
| } |
| |
| /* |
| * Report any non-zero status... |
| */ |
| |
| if (status) |
| { |
| if (WIFEXITED(status)) |
| fprintf(stderr, "ERROR: Sendmail command returned status %d!\n", |
| WEXITSTATUS(status)); |
| else |
| fprintf(stderr, "ERROR: Sendmail command crashed on signal %d!\n", |
| WTERMSIG(status)); |
| } |
| } |
| else |
| { |
| /* |
| * Finish up the SMTP submission and close the connection... |
| */ |
| |
| if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) |
| goto smtp_error; |
| fprintf(stderr, "DEBUG: <<< %s\n", response); |
| |
| /* |
| * Process SMTP errors here... |
| */ |
| |
| smtp_error: |
| |
| cupsFilePuts(fp, "QUIT\r\n"); |
| fputs("DEBUG: QUIT\n", stderr); |
| |
| if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) |
| fprintf(stderr, "ERROR: Got \"%s\" trying to QUIT connection.\n", |
| response); |
| else |
| fprintf(stderr, "DEBUG: <<< %s\n", response); |
| |
| cupsFileClose(fp); |
| |
| fprintf(stderr, "DEBUG: Closed connection to \"%s\"...\n", |
| mailtoSMTPServer); |
| } |
| } |
| |
| |
| /* |
| * 'load_configuration()' - Load the mailto.conf file. |
| */ |
| |
| int /* I - 1 on success, 0 on failure */ |
| load_configuration(void) |
| { |
| cups_file_t *fp; /* mailto.conf file */ |
| const char *server_root, /* CUPS_SERVERROOT environment variable */ |
| *server_admin; /* SERVER_ADMIN environment variable */ |
| char line[1024], /* Line from file */ |
| *value; /* Value for directive */ |
| int linenum; /* Line number in file */ |
| |
| |
| /* |
| * Initialize defaults... |
| */ |
| |
| mailtoCc[0] = '\0'; |
| |
| if ((server_admin = getenv("SERVER_ADMIN")) != NULL) |
| strlcpy(mailtoFrom, server_admin, sizeof(mailtoFrom)); |
| else |
| snprintf(mailtoFrom, sizeof(mailtoFrom), "root@%s", |
| httpGetHostname(NULL, line, sizeof(line))); |
| |
| strlcpy(mailtoSendmail, "/usr/sbin/sendmail", sizeof(mailtoSendmail)); |
| |
| mailtoSMTPServer[0] = '\0'; |
| |
| mailtoSubject[0] = '\0'; |
| |
| /* |
| * Try loading the config file... |
| */ |
| |
| if ((server_root = getenv("CUPS_SERVERROOT")) == NULL) |
| server_root = CUPS_SERVERROOT; |
| |
| snprintf(line, sizeof(line), "%s/mailto.conf", server_root); |
| |
| if ((fp = cupsFileOpen(line, "r")) == NULL) |
| { |
| if (errno != ENOENT) |
| { |
| fprintf(stderr, "ERROR: Unable to open \"%s\" - %s\n", line, |
| strerror(errno)); |
| return (1); |
| } |
| else |
| return (0); |
| } |
| |
| linenum = 0; |
| |
| while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) |
| { |
| if (!value) |
| { |
| fprintf(stderr, "ERROR: No value found for %s directive on line %d!\n", |
| line, linenum); |
| cupsFileClose(fp); |
| return (0); |
| } |
| |
| if (!_cups_strcasecmp(line, "Cc")) |
| strlcpy(mailtoCc, value, sizeof(mailtoCc)); |
| else if (!_cups_strcasecmp(line, "From")) |
| strlcpy(mailtoFrom, value, sizeof(mailtoFrom)); |
| else if (!_cups_strcasecmp(line, "Sendmail")) |
| { |
| strlcpy(mailtoSendmail, value, sizeof(mailtoSendmail)); |
| mailtoSMTPServer[0] = '\0'; |
| } |
| else if (!_cups_strcasecmp(line, "SMTPServer")) |
| { |
| mailtoSendmail[0] = '\0'; |
| strlcpy(mailtoSMTPServer, value, sizeof(mailtoSMTPServer)); |
| } |
| else if (!_cups_strcasecmp(line, "Subject")) |
| strlcpy(mailtoSubject, value, sizeof(mailtoSubject)); |
| else |
| { |
| fprintf(stderr, |
| "ERROR: Unknown configuration directive \"%s\" on line %d!\n", |
| line, linenum); |
| } |
| } |
| |
| /* |
| * Close file and return... |
| */ |
| |
| cupsFileClose(fp); |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'pipe_sendmail()' - Open a pipe to sendmail... |
| */ |
| |
| cups_file_t * /* O - CUPS file */ |
| pipe_sendmail(const char *to) /* I - To: address */ |
| { |
| cups_file_t *fp; /* CUPS file */ |
| int pid; /* Process ID */ |
| int pipefds[2]; /* Pipe file descriptors */ |
| int argc; /* Number of arguments */ |
| char *argv[100], /* Argument array */ |
| line[1024], /* Sendmail command + args */ |
| *lineptr; /* Pointer into line */ |
| |
| |
| /* |
| * First break the mailtoSendmail string into arguments... |
| */ |
| |
| strlcpy(line, mailtoSendmail, sizeof(line)); |
| argv[0] = line; |
| argc = 1; |
| |
| for (lineptr = strchr(line, ' '); lineptr; lineptr = strchr(lineptr, ' ')) |
| { |
| while (*lineptr == ' ') |
| *lineptr++ = '\0'; |
| |
| if (*lineptr) |
| { |
| /* |
| * Point to the next argument... |
| */ |
| |
| argv[argc ++] = lineptr; |
| |
| /* |
| * Stop if we have too many... |
| */ |
| |
| if (argc >= (int)(sizeof(argv) / sizeof(argv[0]) - 2)) |
| break; |
| } |
| } |
| |
| argv[argc ++] = (char *)to; |
| argv[argc] = NULL; |
| |
| /* |
| * Create the pipe... |
| */ |
| |
| if (pipe(pipefds)) |
| { |
| perror("ERROR: Unable to create pipe"); |
| return (NULL); |
| } |
| |
| /* |
| * Then run the command... |
| */ |
| |
| if ((pid = fork()) == 0) |
| { |
| /* |
| * Child goes here - redirect stdin to the input side of the pipe, |
| * redirect stdout to stderr, and exec... |
| */ |
| |
| close(0); |
| dup(pipefds[0]); |
| |
| close(1); |
| dup(2); |
| |
| close(pipefds[0]); |
| close(pipefds[1]); |
| |
| execvp(argv[0], argv); |
| exit(errno); |
| } |
| else if (pid < 0) |
| { |
| /* |
| * Unable to fork - error out... |
| */ |
| |
| perror("ERROR: Unable to fork command"); |
| |
| close(pipefds[0]); |
| close(pipefds[1]); |
| |
| return (NULL); |
| } |
| |
| /* |
| * Create a CUPS file using the output side of the pipe and close the |
| * input side... |
| */ |
| |
| close(pipefds[0]); |
| |
| if ((fp = cupsFileOpenFd(pipefds[1], "w")) == NULL) |
| { |
| int status; /* Status of command */ |
| |
| |
| close(pipefds[1]); |
| wait(&status); |
| } |
| |
| return (fp); |
| } |
| |
| |
| /* |
| * 'print_attributes()' - Print the attributes in a request... |
| */ |
| |
| void |
| print_attributes(ipp_t *ipp, /* I - IPP request */ |
| int indent) /* I - Indentation */ |
| { |
| ipp_tag_t group; /* Current group */ |
| ipp_attribute_t *attr; /* Current attribute */ |
| char buffer[1024]; /* Value buffer */ |
| |
| |
| for (group = IPP_TAG_ZERO, attr = ipp->attrs; attr; attr = attr->next) |
| { |
| if ((attr->group_tag == IPP_TAG_ZERO && indent <= 8) || !attr->name) |
| { |
| group = IPP_TAG_ZERO; |
| fputc('\n', stderr); |
| continue; |
| } |
| |
| if (group != attr->group_tag) |
| { |
| group = attr->group_tag; |
| |
| fprintf(stderr, "DEBUG: %*s%s:\n\n", indent - 4, "", ippTagString(group)); |
| } |
| |
| ippAttributeString(attr, buffer, sizeof(buffer)); |
| |
| fprintf(stderr, "DEBUG: %*s%s (%s%s) %s", indent, "", attr->name, |
| attr->num_values > 1 ? "1setOf " : "", |
| ippTagString(attr->value_tag), buffer); |
| } |
| } |