fix possible crash on user deletion
[srvx.git] / src / mail-sendmail.c
1 /* mail-common.c - mail sending utilities
2  * Copyright 2002-2004, 2007 srvx Development Team
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with srvx; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
19  */
20
21 #include "mail-common.c"
22
23 #ifdef HAVE_SYS_WAIT_H
24 #include <sys/wait.h>
25 #endif
26
27 /* This function sends the given "paragraph" as flowed text, as
28  * defined in RFC 2646.  It lets us only worry about line wrapping
29  * here, and not in the code that generates mail.
30  */
31 static void
32 send_flowed_text(FILE *where, const char *para)
33 {
34     const char *eol = strchr(para, '\n');
35     unsigned int shift;
36
37     while (*para) {
38         /* Do we need to space-stuff the line? */
39         if ((*para == ' ') || (*para == '>') || !strncmp(para, "From ", 5)) {
40             fputc(' ', where);
41             shift = 1;
42         } else {
43             shift = 0;
44         }
45         /* How much can we put on this line? */
46         if (!eol && (strlen(para) < (80 - shift))) {
47             /* End of paragraph; can put on one line. */
48             fputs(para, where);
49             fputs("\n", where);
50             break;
51         } else if (eol && (eol < para + (80 - shift))) {
52             /* Newline inside paragraph, no need to wrap. */
53             fprintf(where, "%.*s\n", (int)(eol - para), para);
54             para = eol + 1;
55         } else {
56             int pos;
57             /* Need to wrap.  Where's the last space in the line? */
58             for (pos=72-shift; pos && (para[pos] != ' '); pos--) ;
59             /* If we didn't find a space, look ahead instead. */
60             if (pos == 0) pos = strcspn(para, " \n");
61             fprintf(where, "%.*s\n", pos+1, para);
62             para += pos + 1;
63         }
64         if (eol && (eol < para)) eol = strchr(para, '\n');
65     }
66 }
67
68 void
69 mail_send(struct userNode *from, struct handle_info *to, const char *subject, const char *body, int first_time)
70 {
71     struct sigaction sv;
72     pid_t child;
73     int infds[2], outfds[2];
74     const char *fromaddr;
75     const char *str;
76
77     /* Grab some config items first. */
78     str = conf_get_data("mail/enable", RECDB_QSTRING);
79     if (!str || !enabled_string(str))
80         return;
81     fromaddr = conf_get_data("mail/from_address", RECDB_QSTRING);
82
83     /* How this works: We fork, and the child tries to send the mail.
84      * It does this by setting up a pipe pair, and forking again (the
85      * grandchild exec()'s the mailer program).  The mid-level child
86      * sends the text to the grandchild's stdin, and then logs the
87      * success or failure.
88      */
89
90     child = fork();
91     if (child < 0) {
92         log_module(MAIN_LOG, LOG_ERROR, "sendmail() to %s couldn't fork(): %s (%d)", to->email_addr, strerror(errno), errno);
93         return;
94     } else if (child > 0) {
95         return;
96     }
97     /* Reset the SIGCHLD signal handler to the default. */
98     memset(&sv, 0, sizeof(sv));
99     sigemptyset(&sv.sa_mask);
100     sv.sa_handler = SIG_DFL;
101     sigaction(SIGCHLD, &sv, NULL);
102     /* We're in a child now; must _exit() to die properly. */
103     if (pipe(infds) < 0) {
104         log_module(MAIN_LOG, LOG_ERROR, "sendmail() child to %s couldn't pipe(infds): %s (%d)", to->email_addr, strerror(errno), errno);
105         _exit(1);
106     }
107     if (pipe(outfds) < 0) {
108         log_module(MAIN_LOG, LOG_ERROR, "sendmail() child to %s couldn't pipe(outfds): %s (%d)", to->email_addr, strerror(errno), errno);
109         _exit(1);
110     }
111     child = fork();
112     if (child < 0) {
113         log_module(MAIN_LOG, LOG_ERROR, "sendmail() child to %s couldn't fork(): %s (%d)", to->email_addr, strerror(errno), errno);
114         _exit(1);
115     } else if (child > 0) {
116         /* Mid-level child; get ready to send the mail. */
117         FILE *out = fdopen(infds[1], "w");
118         struct string_list *extras;
119         unsigned int nn;
120         int res, rv;
121
122         /* Close the end of pipes we do not use. */
123         close(infds[0]);
124         close(outfds[1]);
125
126         /* Do we have any "extra" headers to send? */
127         extras = conf_get_data("mail/extra_headers", RECDB_STRING_LIST);
128         if (extras) {
129             for (nn=0; nn<extras->used; nn++) {
130                 fputs(extras->list[nn], out);
131                 fputs("\n", out);
132             }
133         }
134
135         /* Content type?  (format=flowed is a standard for plain text
136          * that lets the receiver reconstruct paragraphs, defined in
137          * RFC 2646.  See comment above send_flowed_text() for more.)
138          */
139         if (!(str = conf_get_data("mail/charset", RECDB_QSTRING))) str = "us-ascii";
140         fprintf(out, "Content-Type: text/plain; charset=%s; format=flowed\n", str);
141
142         /* Send From, To and Subject headers */
143         if (!fromaddr) fromaddr = "admin@poorly.configured.network";
144         fprintf(out, "From: %s <%s>\n", from->nick, fromaddr);
145         fprintf(out, "To: \"%s\" <%s>\n", to->handle, to->email_addr);
146         fprintf(out, "Subject: %s\n", subject);
147
148         /* Send mail body */
149         fputs("\n", out); /* terminate headers */
150         extras = conf_get_data((first_time?"mail/body_prefix_first":"mail/body_prefix"), RECDB_STRING_LIST);
151         if (extras) {
152             for (nn=0; nn<extras->used; nn++) {
153                 send_flowed_text(out, extras->list[nn]);
154             }
155             fputs("\n", out);
156         }
157         send_flowed_text(out, body);
158         extras = conf_get_data((first_time?"mail/body_suffix_first":"mail/body_suffix"), RECDB_STRING_LIST);
159         if (extras) {
160             fputs("\n", out);
161             for (nn=0; nn<extras->used; nn++)
162                 send_flowed_text(out, extras->list[nn]);
163         }
164
165         /* Close file (sending mail) and check for return code */
166         fflush(out);
167         fclose(out);
168         do {
169             rv = wait4(child, &res, 0, NULL);
170         } while ((rv == -1) && (errno == EINTR));
171         if (rv == child) {
172             /* accept the wait() result */
173         } else {
174             log_module(MAIN_LOG, LOG_ERROR, "sendmail() child to %s: Bad wait() return code %d: %s (%d)", to->email_addr, rv, strerror(errno), errno);
175             _exit(1);
176         }
177         if (res) {
178             log_module(MAIN_LOG, LOG_ERROR, "sendmail() grandchild to %s: Exited with code %d", to->email_addr, res);
179             _exit(1);
180         } else {
181             log_module(MAIN_LOG, LOG_INFO, "sendmail() sent email to %s <%s>: %s", to->handle, to->email_addr, subject);
182         }
183         _exit(0);
184     } else {
185         /* Grandchild; dup2 the fds and exec the mailer. */
186         const char *argv[10], *mpath;
187         unsigned int argc = 0;
188
189         /* Close the end of pipes we do not use. */
190         close(infds[1]);
191         close(outfds[0]);
192
193         dup2(infds[0], STDIN_FILENO);
194         dup2(outfds[1], STDOUT_FILENO);
195         mpath = conf_get_data("mail/mailer", RECDB_QSTRING);
196         if (!mpath) mpath = "/usr/sbin/sendmail";
197         argv[argc++] = mpath;
198         if (fromaddr) {
199             argv[argc++] = "-f";
200             argv[argc++] = fromaddr;
201         }
202         argv[argc++] = to->email_addr;
203         argv[argc++] = NULL;
204         if (execv(mpath, (char**)argv) < 0) {
205             log_module(MAIN_LOG, LOG_ERROR, "sendmail() grandchild to %s couldn't execv(): %s (%d)", to->email_addr, strerror(errno), errno);
206         }
207         _exit(1);
208     }
209 }
210
211 void
212 mail_init(void)
213 {
214     mail_common_init();
215 }