]> jfr.im git - irc/evilnet/x3.git/blame - src/sendmail.c
Author: Rubin
[irc/evilnet/x3.git] / src / sendmail.c
CommitLineData
d76ed9a9
AS
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
32static 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
42static dict_t prohibited_addrs, prohibited_masks;
43struct module *sendmail_module;
44
45const char *
46sendmail_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 */
64static void
65send_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
101void
102sendmail(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
237static int
238sendmail_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
253static 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
258static 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
271static 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
288static int
289sendmail_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
304static int
305sendmail_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
317static void
318sendmail_cleanup(void)
319{
320 dict_delete(prohibited_addrs);
321 dict_delete(prohibited_masks);
322}
323
324void
325sendmail_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}