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