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