Initial import (again)
[srvx.git] / src / sendmail.c
1 /* sendmail.c - mail sending utilities
2  * Copyright 2002-2004 srvx Development Team
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.  Important limitations are
8  * listed in the COPYING file that accompanies this software.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, email srvx-maintainers@srvx.net.
17  */
18
19 #include "conf.h"
20 #include "modcmd.h"
21 #include "nickserv.h"
22 #include "saxdb.h"
23
24 #ifdef HAVE_SYS_WAIT_H
25 #include <sys/wait.h>
26 #endif
27
28 #define KEY_PROHIBITED   "prohibited"
29
30 static const struct message_entry msgtab[] = {
31     { "MAILMSG_EMAIL_ALREADY_BANNED", "%s is already banned (%s)." },
32     { "MAILMSG_EMAIL_BANNED", "Email to %s has been forbidden." },
33     { "MAILMSG_EMAIL_NOT_BANNED", "Email to %s was not forbidden." },
34     { "MAILMSG_EMAIL_UNBANNED", "Email to %s is now allowed." },
35     { "MAILMSG_PROHIBITED_EMAIL", "%s: %s" },
36     { "MAILMSG_NO_PROHIBITED_EMAIL", "All email addresses are accepted." },
37     { NULL, NULL }
38 };
39
40 static dict_t prohibited_addrs, prohibited_masks;
41 struct module *sendmail_module;
42
43 const char *
44 sendmail_prohibited_address(const char *addr)
45 {
46     dict_iterator_t it;
47     const char *data;
48
49     if (prohibited_addrs && (data = dict_find(prohibited_addrs, addr, NULL)))
50         return data;
51     if (prohibited_masks)
52         for (it = dict_first(prohibited_masks); it; it = iter_next(it))
53             if (match_ircglob(addr, iter_key(it)))
54                 return iter_data(it);
55     return NULL;
56 }
57
58 /* This function sends the given "paragraph" as flowed text, as
59  * defined in RFC 2646.  It lets us only worry about line wrapping
60  * here, and not in the code that generates mail.
61  */
62 static void
63 send_flowed_text(FILE *where, const char *para)
64 {
65     const char *eol = strchr(para, '\n');
66     unsigned int shift;
67
68     while (*para) {
69         /* Do we need to space-stuff the line? */
70         if ((*para == ' ') || (*para == '>') || !strncmp(para, "From ", 5)) {
71             fputc(' ', where);
72             shift = 1;
73         } else {
74             shift = 0;
75         }
76         /* How much can we put on this line? */
77         if (!eol && (strlen(para) < (80 - shift))) {
78             /* End of paragraph; can put on one line. */
79             fputs(para, where);
80             fputs("\n", where);
81             break;
82         } else if (eol && (eol < para + (80 - shift))) {
83             /* Newline inside paragraph, no need to wrap. */
84             fprintf(where, "%.*s\n", eol - para, para);
85             para = eol + 1;
86         } else {
87             int pos;
88             /* Need to wrap.  Where's the last space in the line? */
89             for (pos=72-shift; pos && (para[pos] != ' '); pos--) ;
90             /* If we didn't find a space, look ahead instead. */
91             if (pos == 0) pos = strcspn(para, " \n");
92             fprintf(where, "%.*s\n", pos+1, para);
93             para += pos + 1;
94         }
95         if (eol && (eol < para)) eol = strchr(para, '\n');
96     }
97 }
98
99 void
100 sendmail(struct userNode *from, struct handle_info *to, const char *subject, const char *body, int first_time)
101 {
102     pid_t child;
103     int infds[2], outfds[2];
104     const char *fromaddr, *str;
105
106     /* Grab some config items first. */
107     str = conf_get_data("mail/enable", RECDB_QSTRING);
108     if (!str || !enabled_string(str))
109         return;
110     fromaddr = conf_get_data("mail/from_address", RECDB_QSTRING);
111
112     /* How this works: We fork, and the child tries to send the mail.
113      * It does this by setting up a pipe pair, and forking again (the
114      * grandchild exec()'s the mailer program).  The mid-level child
115      * sends the text to the grandchild's stdin, and then logs the
116      * success or failure.
117      */
118
119     child = fork();
120     if (child < 0) {
121         log_module(MAIN_LOG, LOG_ERROR, "sendmail() to %s couldn't fork(): %s (%d)", to->email_addr, strerror(errno), errno);
122         return;
123     } else if (child > 0) {
124         return;
125     }
126     /* We're in a child now; must _exit() to die properly. */
127     if (pipe(infds) < 0) {
128         log_module(MAIN_LOG, LOG_ERROR, "sendmail() child to %s couldn't pipe(infds): %s (%d)", to->email_addr, strerror(errno), errno);
129         _exit(1);
130     }
131     if (pipe(outfds) < 0) {
132         log_module(MAIN_LOG, LOG_ERROR, "sendmail() child to %s couldn't pipe(outfds): %s (%d)", to->email_addr, strerror(errno), errno);
133         _exit(1);
134     }
135     child = fork();
136     if (child < 0) {
137         log_module(MAIN_LOG, LOG_ERROR, "sendmail() child to %s couldn't fork(): %s (%d)", to->email_addr, strerror(errno), errno);
138         _exit(1);
139     } else if (child > 0) {
140         /* Mid-level child; get ready to send the mail. */
141         FILE *out = fdopen(infds[1], "w");
142         struct string_list *extras;
143         unsigned int nn;
144         int res, rv;
145
146         /* Close the end of pipes we do not use. */
147         close(infds[0]);
148         close(outfds[1]);
149
150         /* Do we have any "extra" headers to send? */
151         extras = conf_get_data("mail/extra_headers", RECDB_STRING_LIST);
152         if (extras) {
153             for (nn=0; nn<extras->used; nn++) {
154                 fputs(extras->list[nn], out);
155                 fputs("\n", out);
156             }
157         }
158
159         /* Content type?  (format=flowed is a standard for plain text
160          * that lets the receiver reconstruct paragraphs, defined in
161          * RFC 2646.  See comment above send_flowed_text() for more.)
162          */
163         if (!(str = conf_get_data("mail/charset", RECDB_QSTRING))) str = "us-ascii";
164         fprintf(out, "Content-Type: text/plain; charset=%s; format=flowed\n", str);
165
166         /* Send From, To and Subject headers */
167         if (!fromaddr) fromaddr = "admin@poorly.configured.network";
168         fprintf(out, "From: %s <%s>\n", from->nick, fromaddr);
169         fprintf(out, "To: \"%s\" <%s>\n", to->handle, to->email_addr);
170         fprintf(out, "Subject: %s\n", subject);
171
172         /* Send mail body */
173         fputs("\n", out); /* terminate headers */
174         extras = conf_get_data((first_time?"mail/body_prefix_first":"mail/body_prefix"), RECDB_STRING_LIST);
175         if (extras) {
176             for (nn=0; nn<extras->used; nn++) {
177                 send_flowed_text(out, extras->list[nn]);
178             }
179             fputs("\n", out);
180         }
181         send_flowed_text(out, body);
182         extras = conf_get_data((first_time?"mail/body_suffix_first":"mail/body_suffix"), RECDB_STRING_LIST);
183         if (extras) {
184             fputs("\n", out);
185             for (nn=0; nn<extras->used; nn++)
186                 send_flowed_text(out, extras->list[nn]);
187         }
188
189         /* Close file (sending mail) and check for return code */
190         fflush(out);
191         fclose(out);
192         do {
193             rv = wait4(child, &res, 0, NULL);
194         } while ((rv == -1) && (errno == EINTR));
195         if (rv == child) {
196             /* accept the wait() result */
197         } else {
198             log_module(MAIN_LOG, LOG_ERROR, "sendmail() child to %s: Bad wait() return code %d: %s (%d)", to->email_addr, rv, strerror(errno), errno);
199             _exit(1);
200         }
201         if (res) {
202             log_module(MAIN_LOG, LOG_ERROR, "sendmail() grandchild to %s: Exited with code %d", to->email_addr, res);
203             _exit(1);
204         } else {
205             log_module(MAIN_LOG, LOG_INFO, "sendmail() sent email to %s <%s>: %s", to->handle, to->email_addr, subject);
206         }
207         _exit(0);
208     } else {
209         /* Grandchild; dup2 the fds and exec the mailer. */
210         const char *argv[10], *mpath;
211         unsigned int argc = 0;
212
213         /* Close the end of pipes we do not use. */
214         close(infds[1]);
215         close(outfds[0]);
216
217         dup2(infds[0], STDIN_FILENO);
218         dup2(outfds[1], STDOUT_FILENO);
219         mpath = conf_get_data("mail/mailer", RECDB_QSTRING);
220         if (!mpath) mpath = "/usr/sbin/sendmail";
221         argv[argc++] = mpath;
222         if (fromaddr) {
223             argv[argc++] = "-f";
224             argv[argc++] = fromaddr;
225         }
226         argv[argc++] = to->email_addr;
227         argv[argc++] = NULL;
228         if (execv(mpath, (char**)argv) < 0) {
229             log_module(MAIN_LOG, LOG_ERROR, "sendmail() grandchild to %s couldn't execv(): %s (%d)", to->email_addr, strerror(errno), errno);
230         }
231         _exit(1);
232     }
233 }
234
235 static int
236 sendmail_ban_address(struct userNode *user, struct userNode *bot, const char *addr, const char *reason) {
237     dict_t target;
238     const char *str;
239
240     target = strpbrk(addr, "*?") ? prohibited_masks : prohibited_addrs;
241     if ((str = dict_find(target, addr, NULL))) {
242         if (user)
243             send_message(user, bot, "MAILMSG_EMAIL_ALREADY_BANNED", addr, str);
244         return 0;
245     }
246     dict_insert(target, strdup(addr), strdup(reason));
247     if (user) send_message(user, bot, "MAILMSG_EMAIL_BANNED", addr);
248     return 1;
249 }
250
251 static MODCMD_FUNC(cmd_banemail) {
252     char *reason = unsplit_string(argv+2, argc-2, NULL);
253     return sendmail_ban_address(user, cmd->parent->bot, argv[1], reason);
254 }
255
256 static MODCMD_FUNC(cmd_unbanemail) {
257     dict_t target;
258     const char *addr;
259
260     addr = argv[1];
261     target = strpbrk(addr, "*?") ? prohibited_masks : prohibited_addrs;
262     if (dict_remove(target, addr))
263         reply("MAILMSG_EMAIL_UNBANNED", addr);
264     else
265         reply("MAILMSG_EMAIL_NOT_BANNED", addr);
266     return 1;
267 }
268
269 static MODCMD_FUNC(cmd_stats_email) {
270     dict_iterator_t it;
271     int found = 0;
272
273     for (it=dict_first(prohibited_addrs); it; it=iter_next(it)) {
274         reply("MAILMSG_PROHIBITED_EMAIL", iter_key(it), (const char*)iter_data(it));
275         found = 1;
276     }
277     for (it=dict_first(prohibited_masks); it; it=iter_next(it)) {
278         reply("MAILMSG_PROHIBITED_EMAIL", iter_key(it), (const char*)iter_data(it));
279         found = 1;
280     }
281     if (!found)
282         reply("MAILMSG_NO_PROHIBITED_EMAIL");
283     return 0;
284 }
285
286 static int
287 sendmail_saxdb_read(struct dict *db) {
288     struct dict *subdb;
289     struct record_data *rd;
290     dict_iterator_t it;
291
292     if ((subdb = database_get_data(db, KEY_PROHIBITED, RECDB_OBJECT))) {
293         for (it = dict_first(subdb); it; it = iter_next(it)) {
294             rd = iter_data(it);
295             if (rd->type == RECDB_QSTRING)
296                 sendmail_ban_address(NULL, NULL, iter_key(it), rd->d.qstring);
297         }
298     }
299     return 0;
300 }
301
302 static int
303 sendmail_saxdb_write(struct saxdb_context *ctx) {
304     dict_iterator_t it;
305
306     saxdb_start_record(ctx, KEY_PROHIBITED, 0);
307     for (it = dict_first(prohibited_masks); it; it = iter_next(it))
308         saxdb_write_string(ctx, iter_key(it), iter_data(it));
309     for (it = dict_first(prohibited_addrs); it; it = iter_next(it))
310         saxdb_write_string(ctx, iter_key(it), iter_data(it));
311     saxdb_end_record(ctx);
312     return 0;
313 }
314
315 static void
316 sendmail_cleanup(void)
317 {
318     dict_delete(prohibited_addrs);
319     dict_delete(prohibited_masks);
320 }
321
322 void
323 sendmail_init(void)
324 {
325     prohibited_addrs = dict_new();
326     dict_set_free_keys(prohibited_addrs, free);
327     dict_set_free_data(prohibited_addrs, free);
328     prohibited_masks = dict_new();
329     dict_set_free_keys(prohibited_masks, free);
330     dict_set_free_data(prohibited_masks, free);
331     reg_exit_func(sendmail_cleanup);
332     saxdb_register("sendmail", sendmail_saxdb_read, sendmail_saxdb_write);
333     sendmail_module = module_register("sendmail", MAIN_LOG, "sendmail.help", NULL);
334     modcmd_register(sendmail_module, "banemail", cmd_banemail, 3, 0, "level", "601", NULL);
335     modcmd_register(sendmail_module, "stats email", cmd_stats_email, 0, 0, "flags", "+oper", NULL);
336     modcmd_register(sendmail_module, "unbanemail", cmd_unbanemail, 2, 0, "level", "601", NULL);
337     message_register_table(msgtab);
338 }