]>
Commit | Line | Data |
---|---|---|
d76ed9a9 AS |
1 | /* sendmail.c - mail sending utilities |
2 | * Copyright 2002-2004 srvx Development Team | |
3 | * | |
83ff05c3 | 4 | * This file is part of x3. |
d76ed9a9 AS |
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." }, | |
de9510bc | 37 | { "MAILMSG_PROHIBITED_EMAIL_HEADER", "$bBanned Email Address Masks$b" }, |
d76ed9a9 | 38 | { "MAILMSG_PROHIBITED_EMAIL", "%s: %s" }, |
de9510bc | 39 | { "MAILMSG_PROHIBITED_EMAIL_END", "-------End of Banned Address Masks------" }, |
d76ed9a9 AS |
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", 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 | ||
4c26ef3e AS |
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 | ||
d76ed9a9 AS |
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 | } | |
4c26ef3e AS |
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 | ||
d76ed9a9 AS |
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 | ||
de9510bc AS |
301 | reply("MAILMSG_PROHIBITED_EMAIL_HEADER"); |
302 | reply("MSG_BAR"); | |
d76ed9a9 AS |
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"); | |
de9510bc AS |
313 | else |
314 | reply("MAILMSG_PROHIBITED_EMAIL_END"); | |
d76ed9a9 AS |
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 | } |