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