1/*
2 * "$Id: mailto.c 11093 2013-07-03 20:48:42Z msweet $"
3 *
4 *   "mailto" notifier for CUPS.
5 *
6 *   Copyright 2007-2011 by Apple Inc.
7 *   Copyright 1997-2005 by Easy Software Products.
8 *
9 *   These coded instructions, statements, and computer programs are the
10 *   property of Apple Inc. and are protected by Federal copyright
11 *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
12 *   which should have been included with this file.  If this file is
13 *   file is missing or damaged, see the license at "http://www.cups.org/".
14 *
15 * Contents:
16 *
17 *   main()               - Main entry for the mailto notifier.
18 *   email_message()      - Email a notification message.
19 *   load_configuration() - Load the mailto.conf file.
20 *   pipe_sendmail()      - Open a pipe to sendmail...
21 *   print_attributes()   - Print the attributes in a request...
22 */
23
24/*
25 * Include necessary headers...
26 */
27
28#include <cups/cups-private.h>
29#include <sys/wait.h>
30#include <signal.h>
31
32
33/*
34 * Globals...
35 */
36
37char	mailtoCc[1024];			/* Cc email address */
38char	mailtoFrom[1024];		/* From email address */
39char	mailtoReplyTo[1024];		/* Reply-To email address */
40char	mailtoSubject[1024];		/* Subject prefix */
41char	mailtoSMTPServer[1024];		/* SMTP server to use */
42char	mailtoSendmail[1024];		/* Sendmail program to use */
43
44
45/*
46 * Local functions...
47 */
48
49void		email_message(const char *to, const char *subject,
50		              const char *text);
51int		load_configuration(void);
52cups_file_t	*pipe_sendmail(const char *to);
53void		print_attributes(ipp_t *ipp, int indent);
54
55
56/*
57 * 'main()' - Main entry for the mailto notifier.
58 */
59
60int					/* O - Exit status */
61main(int  argc,				/* I - Number of command-line arguments */
62     char *argv[])			/* I - Command-line arguments */
63{
64  int		i;			/* Looping var */
65  ipp_t		*msg;			/* Event message from scheduler */
66  ipp_state_t	state;			/* IPP event state */
67  char		*subject,		/* Subject for notification message */
68		*text;			/* Text for notification message */
69  cups_lang_t	*lang;			/* Language info */
70  char		temp[1024];		/* Temporary string */
71  int		templen;		/* Length of temporary string */
72#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
73  struct sigaction action;		/* POSIX sigaction data */
74#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
75
76
77 /*
78  * Don't buffer stderr...
79  */
80
81  setbuf(stderr, NULL);
82
83 /*
84  * Ignore SIGPIPE signals...
85  */
86
87#ifdef HAVE_SIGSET
88  sigset(SIGPIPE, SIG_IGN);
89#elif defined(HAVE_SIGACTION)
90  memset(&action, 0, sizeof(action));
91  action.sa_handler = SIG_IGN;
92  sigaction(SIGPIPE, &action, NULL);
93#else
94  signal(SIGPIPE, SIG_IGN);
95#endif /* HAVE_SIGSET */
96
97 /*
98  * Validate command-line options...
99  */
100
101  if (argc != 3)
102  {
103    fputs("Usage: mailto mailto:user@domain.com notify-user-data\n", stderr);
104    return (1);
105  }
106
107  if (strncmp(argv[1], "mailto:", 7))
108  {
109    fprintf(stderr, "ERROR: Bad recipient \"%s\"!\n", argv[1]);
110    return (1);
111  }
112
113  fprintf(stderr, "DEBUG: argc=%d\n", argc);
114  for (i = 0; i < argc; i ++)
115    fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);
116
117 /*
118  * Load configuration data...
119  */
120
121  if ((lang = cupsLangDefault()) == NULL)
122    return (1);
123
124  if (!load_configuration())
125    return (1);
126
127 /*
128  * Get the reply-to address...
129  */
130
131  templen = sizeof(temp);
132  httpDecode64_2(temp, &templen, argv[2]);
133
134  if (!strncmp(temp, "mailto:", 7))
135    strlcpy(mailtoReplyTo, temp + 7, sizeof(mailtoReplyTo));
136  else if (temp[0])
137    fprintf(stderr, "WARNING: Bad notify-user-data value (%d bytes) ignored!\n",
138            templen);
139
140 /*
141  * Loop forever until we run out of events...
142  */
143
144  for (;;)
145  {
146   /*
147    * Get the next event...
148    */
149
150    msg = ippNew();
151    while ((state = ippReadFile(0, msg)) != IPP_DATA)
152    {
153      if (state <= IPP_IDLE)
154        break;
155    }
156
157    fprintf(stderr, "DEBUG: state=%d\n", state);
158
159    if (state == IPP_ERROR)
160      fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr);
161
162    if (state <= IPP_IDLE)
163    {
164     /*
165      * Out of messages, free memory and then exit...
166      */
167
168      ippDelete(msg);
169      return (0);
170    }
171
172   /*
173    * Get the subject and text for the message, then email it...
174    */
175
176    subject = cupsNotifySubject(lang, msg);
177    text    = cupsNotifyText(lang, msg);
178
179    fprintf(stderr, "DEBUG: subject=\"%s\"\n", subject);
180    fprintf(stderr, "DEBUG: text=\"%s\"\n", text);
181
182    if (subject && text)
183      email_message(argv[1] + 7, subject, text);
184    else
185    {
186      fputs("ERROR: Missing attributes in event notification!\n", stderr);
187      print_attributes(msg, 4);
188    }
189
190   /*
191    * Free the memory used for this event...
192    */
193
194    if (subject)
195      free(subject);
196
197    if (text)
198      free(text);
199
200    ippDelete(msg);
201  }
202}
203
204
205/*
206 * 'email_message()' - Email a notification message.
207 */
208
209void
210email_message(const char *to,		/* I - Recipient of message */
211              const char *subject,	/* I - Subject of message */
212	      const char *text)		/* I - Text of message */
213{
214  cups_file_t	*fp;			/* Pipe/socket to mail server */
215  const char	*nl;			/* Newline to use */
216  char		response[1024];		/* SMTP response buffer */
217
218
219 /*
220  * Connect to the mail server...
221  */
222
223  if (mailtoSendmail[0])
224  {
225   /*
226    * Use the sendmail command...
227    */
228
229    fp = pipe_sendmail(to);
230
231    if (!fp)
232      return;
233
234    nl = "\n";
235  }
236  else
237  {
238   /*
239    * Use an SMTP server...
240    */
241
242    char	hostbuf[1024];		/* Local hostname */
243
244
245    if (strchr(mailtoSMTPServer, ':'))
246      fp = cupsFileOpen(mailtoSMTPServer, "s");
247    else
248    {
249      char	spec[1024];		/* Host:service spec */
250
251
252      snprintf(spec, sizeof(spec), "%s:smtp", mailtoSMTPServer);
253      fp = cupsFileOpen(spec, "s");
254    }
255
256    if (!fp)
257    {
258      fprintf(stderr, "ERROR: Unable to connect to SMTP server \"%s\"!\n",
259              mailtoSMTPServer);
260      return;
261    }
262
263    fprintf(stderr, "DEBUG: Connected to \"%s\"...\n", mailtoSMTPServer);
264
265    cupsFilePrintf(fp, "HELO %s\r\n",
266                   httpGetHostname(NULL, hostbuf, sizeof(hostbuf)));
267    fprintf(stderr, "DEBUG: >>> HELO %s\n", hostbuf);
268
269    if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
270      goto smtp_error;
271    fprintf(stderr, "DEBUG: <<< %s\n", response);
272
273    cupsFilePrintf(fp, "MAIL FROM:%s\r\n", mailtoFrom);
274    fprintf(stderr, "DEBUG: >>> MAIL FROM:%s\n", mailtoFrom);
275
276    if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
277      goto smtp_error;
278    fprintf(stderr, "DEBUG: <<< %s\n", response);
279
280    cupsFilePrintf(fp, "RCPT TO:%s\r\n", to);
281    fprintf(stderr, "DEBUG: >>> RCPT TO:%s\n", to);
282
283    if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
284      goto smtp_error;
285    fprintf(stderr, "DEBUG: <<< %s\n", response);
286
287    cupsFilePuts(fp, "DATA\r\n");
288    fputs("DEBUG: DATA\n", stderr);
289
290    if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
291      goto smtp_error;
292    fprintf(stderr, "DEBUG: <<< %s\n", response);
293
294    nl = "\r\n";
295  }
296
297 /*
298  * Send the message...
299  */
300
301  cupsFilePrintf(fp, "Date: %s%s", httpGetDateString(time(NULL)), nl);
302  cupsFilePrintf(fp, "From: %s%s", mailtoFrom, nl);
303  cupsFilePrintf(fp, "Subject: %s %s%s", mailtoSubject, subject, nl);
304  if (mailtoReplyTo[0])
305  {
306    cupsFilePrintf(fp, "Sender: %s%s", mailtoReplyTo, nl);
307    cupsFilePrintf(fp, "Reply-To: %s%s", mailtoReplyTo, nl);
308  }
309  cupsFilePrintf(fp, "To: %s%s", to, nl);
310  if (mailtoCc[0])
311    cupsFilePrintf(fp, "Cc: %s%s", mailtoCc, nl);
312  cupsFilePrintf(fp, "Content-Type: text/plain%s", nl);
313  cupsFilePuts(fp, nl);
314  cupsFilePrintf(fp, "%s%s", text, nl);
315  cupsFilePrintf(fp, ".%s", nl);
316
317 /*
318  * Close the connection to the mail server...
319  */
320
321  if (mailtoSendmail[0])
322  {
323   /*
324    * Close the pipe and wait for the sendmail command to finish...
325    */
326
327    int	status;				/* Exit status */
328
329
330    cupsFileClose(fp);
331
332    while (wait(&status))
333    {
334      if (errno != EINTR)
335      {
336        fprintf(stderr, "DEBUG: Unable to get child status: %s\n",
337	        strerror(errno));
338        status = 0;
339	break;
340      }
341    }
342
343   /*
344    * Report any non-zero status...
345    */
346
347    if (status)
348    {
349      if (WIFEXITED(status))
350        fprintf(stderr, "ERROR: Sendmail command returned status %d!\n",
351	        WEXITSTATUS(status));
352      else
353        fprintf(stderr, "ERROR: Sendmail command crashed on signal %d!\n",
354	        WTERMSIG(status));
355    }
356  }
357  else
358  {
359   /*
360    * Finish up the SMTP submission and close the connection...
361    */
362
363    if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
364      goto smtp_error;
365    fprintf(stderr, "DEBUG: <<< %s\n", response);
366
367   /*
368    * Process SMTP errors here...
369    */
370
371    smtp_error:
372
373    cupsFilePuts(fp, "QUIT\r\n");
374    fputs("DEBUG: QUIT\n", stderr);
375
376    if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
377      fprintf(stderr, "ERROR: Got \"%s\" trying to QUIT connection.\n",
378              response);
379    else
380      fprintf(stderr, "DEBUG: <<< %s\n", response);
381
382    cupsFileClose(fp);
383
384    fprintf(stderr, "DEBUG: Closed connection to \"%s\"...\n",
385            mailtoSMTPServer);
386  }
387}
388
389
390/*
391 * 'load_configuration()' - Load the mailto.conf file.
392 */
393
394int					/* I - 1 on success, 0 on failure */
395load_configuration(void)
396{
397  cups_file_t	*fp;			/* mailto.conf file */
398  const char	*server_root,		/* CUPS_SERVERROOT environment variable */
399		*server_admin;		/* SERVER_ADMIN environment variable */
400  char		line[1024],		/* Line from file */
401		*value;			/* Value for directive */
402  int		linenum;		/* Line number in file */
403
404
405 /*
406  * Initialize defaults...
407  */
408
409  mailtoCc[0] = '\0';
410
411  if ((server_admin = getenv("SERVER_ADMIN")) != NULL)
412    strlcpy(mailtoFrom, server_admin, sizeof(mailtoFrom));
413  else
414    snprintf(mailtoFrom, sizeof(mailtoFrom), "root@%s",
415             httpGetHostname(NULL, line, sizeof(line)));
416
417  strlcpy(mailtoSendmail, "/usr/sbin/sendmail", sizeof(mailtoSendmail));
418
419  mailtoSMTPServer[0] = '\0';
420
421  mailtoSubject[0] = '\0';
422
423 /*
424  * Try loading the config file...
425  */
426
427  if ((server_root = getenv("CUPS_SERVERROOT")) == NULL)
428    server_root = CUPS_SERVERROOT;
429
430  snprintf(line, sizeof(line), "%s/mailto.conf", server_root);
431
432  if ((fp = cupsFileOpen(line, "r")) == NULL)
433  {
434    if (errno != ENOENT)
435    {
436      fprintf(stderr, "ERROR: Unable to open \"%s\" - %s\n", line,
437	      strerror(errno));
438      return (1);
439    }
440    else
441      return (0);
442  }
443
444  linenum = 0;
445
446  while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
447  {
448    if (!value)
449    {
450      fprintf(stderr, "ERROR: No value found for %s directive on line %d!\n",
451              line, linenum);
452      cupsFileClose(fp);
453      return (0);
454    }
455
456    if (!_cups_strcasecmp(line, "Cc"))
457      strlcpy(mailtoCc, value, sizeof(mailtoCc));
458    else if (!_cups_strcasecmp(line, "From"))
459      strlcpy(mailtoFrom, value, sizeof(mailtoFrom));
460    else if (!_cups_strcasecmp(line, "Sendmail"))
461    {
462      strlcpy(mailtoSendmail, value, sizeof(mailtoSendmail));
463      mailtoSMTPServer[0] = '\0';
464    }
465    else if (!_cups_strcasecmp(line, "SMTPServer"))
466    {
467      mailtoSendmail[0] = '\0';
468      strlcpy(mailtoSMTPServer, value, sizeof(mailtoSMTPServer));
469    }
470    else if (!_cups_strcasecmp(line, "Subject"))
471      strlcpy(mailtoSubject, value, sizeof(mailtoSubject));
472    else
473    {
474      fprintf(stderr,
475              "ERROR: Unknown configuration directive \"%s\" on line %d!\n",
476              line, linenum);
477    }
478  }
479
480 /*
481  * Close file and return...
482  */
483
484  cupsFileClose(fp);
485
486  return (1);
487}
488
489
490/*
491 * 'pipe_sendmail()' - Open a pipe to sendmail...
492 */
493
494cups_file_t *				/* O - CUPS file */
495pipe_sendmail(const char *to)		/* I - To: address */
496{
497  cups_file_t	*fp;			/* CUPS file */
498  int		pid;			/* Process ID */
499  int		pipefds[2];		/* Pipe file descriptors */
500  int		argc;			/* Number of arguments */
501  char		*argv[100],		/* Argument array */
502		line[1024],		/* Sendmail command + args */
503		*lineptr;		/* Pointer into line */
504
505
506 /*
507  * First break the mailtoSendmail string into arguments...
508  */
509
510  strlcpy(line, mailtoSendmail, sizeof(line));
511  argv[0] = line;
512  argc    = 1;
513
514  for (lineptr = strchr(line, ' '); lineptr; lineptr = strchr(lineptr, ' '))
515  {
516    while (*lineptr == ' ')
517      *lineptr++ = '\0';
518
519    if (*lineptr)
520    {
521     /*
522      * Point to the next argument...
523      */
524
525      argv[argc ++] = lineptr;
526
527     /*
528      * Stop if we have too many...
529      */
530
531      if (argc >= (int)(sizeof(argv) / sizeof(argv[0]) - 2))
532        break;
533    }
534  }
535
536  argv[argc ++] = (char *)to;
537  argv[argc]    = NULL;
538
539 /*
540  * Create the pipe...
541  */
542
543  if (pipe(pipefds))
544  {
545    perror("ERROR: Unable to create pipe");
546    return (NULL);
547  }
548
549 /*
550  * Then run the command...
551  */
552
553  if ((pid = fork()) == 0)
554  {
555   /*
556    * Child goes here - redirect stdin to the input side of the pipe,
557    * redirect stdout to stderr, and exec...
558    */
559
560    close(0);
561    dup(pipefds[0]);
562
563    close(1);
564    dup(2);
565
566    close(pipefds[0]);
567    close(pipefds[1]);
568
569    execvp(argv[0], argv);
570    exit(errno);
571  }
572  else if (pid < 0)
573  {
574   /*
575    * Unable to fork - error out...
576    */
577
578    perror("ERROR: Unable to fork command");
579
580    close(pipefds[0]);
581    close(pipefds[1]);
582
583    return (NULL);
584  }
585
586 /*
587  * Create a CUPS file using the output side of the pipe and close the
588  * input side...
589  */
590
591  close(pipefds[0]);
592
593  if ((fp = cupsFileOpenFd(pipefds[1], "w")) == NULL)
594  {
595    int	status;				/* Status of command */
596
597
598    close(pipefds[1]);
599    wait(&status);
600  }
601
602  return (fp);
603}
604
605
606/*
607 * 'print_attributes()' - Print the attributes in a request...
608 */
609
610void
611print_attributes(ipp_t *ipp,		/* I - IPP request */
612                 int   indent)		/* I - Indentation */
613{
614  ipp_tag_t		group;		/* Current group */
615  ipp_attribute_t	*attr;		/* Current attribute */
616  char			buffer[1024];	/* Value buffer */
617
618
619  for (group = IPP_TAG_ZERO, attr = ipp->attrs; attr; attr = attr->next)
620  {
621    if ((attr->group_tag == IPP_TAG_ZERO && indent <= 8) || !attr->name)
622    {
623      group = IPP_TAG_ZERO;
624      fputc('\n', stderr);
625      continue;
626    }
627
628    if (group != attr->group_tag)
629    {
630      group = attr->group_tag;
631
632      fprintf(stderr, "DEBUG: %*s%s:\n\n", indent - 4, "", ippTagString(group));
633    }
634
635    ippAttributeString(attr, buffer, sizeof(buffer));
636
637    fprintf(stderr, "DEBUG: %*s%s (%s%s) %s", indent, "", attr->name,
638            attr->num_values > 1 ? "1setOf " : "",
639	    ippTagString(attr->value_tag), buffer);
640  }
641}
642
643
644/*
645 * End of "$Id: mailto.c 11093 2013-07-03 20:48:42Z msweet $".
646 */
647