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