/*
 * "mailto" notifier for CUPS.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * 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);
  }
}
