]>
jfr.im git - irc/evilnet/x3.git/blob - src/sendmail.c
1 /* sendmail.c - mail sending utilities
2 * Copyright 2002-2004 srvx Development Team
4 * This file is part of x3.
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.
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.
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.
26 #ifdef HAVE_SYS_WAIT_H
30 #define KEY_PROHIBITED "prohibited"
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." },
44 static dict_t prohibited_addrs
, prohibited_masks
;
45 struct module *sendmail_module
;
48 sendmail_prohibited_address(const char *addr
)
53 if (prohibited_addrs
&& (data
= dict_find(prohibited_addrs
, addr
, NULL
)))
56 for (it
= dict_first(prohibited_masks
); it
; it
= iter_next(it
))
57 if (match_ircglob(addr
, iter_key(it
)))
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.
67 send_flowed_text(FILE *where
, const char *para
)
69 const char *eol
= strchr(para
, '\n');
73 /* Do we need to space-stuff the line? */
74 if ((*para
== ' ') || (*para
== '>') || !strncmp(para
, "From ", 5)) {
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. */
86 } else if (eol
&& (eol
< para
+ (80 - shift
))) {
87 /* Newline inside paragraph, no need to wrap. */
88 fprintf(where
, "%.*s\n", eol
- para
, para
);
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
);
99 if (eol
&& (eol
< para
)) eol
= strchr(para
, '\n');
107 for (i=0;i<strlen(email);i++)
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] != '_' )
122 sendmail(struct userNode
*from
, struct handle_info
*to
, const char *subject
, const char *body
, int first_time
)
125 int infds
[2], outfds
[2];
126 const char *fromaddr
, *str
;
128 /* Grab some config items first. */
129 str
= conf_get_data("mail/enable", RECDB_QSTRING
);
130 if (!str
|| !enabled_string(str
))
132 fromaddr
= conf_get_data("mail/from_address", RECDB_QSTRING
);
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.
143 log_module(MAIN_LOG
, LOG_ERROR
, "sendmail() to %s couldn't fork(): %s (%d)", to
->email_addr
, strerror(errno
), errno
);
145 } else if (child
> 0) {
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
);
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
);
159 log_module(MAIN_LOG
, LOG_ERROR
, "sendmail() child to %s couldn't fork(): %s (%d)", to
->email_addr
, strerror(errno
), errno
);
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
;
168 /* Close the end of pipes we do not use. */
172 /* Do we have any "extra" headers to send? */
173 extras
= conf_get_data("mail/extra_headers", RECDB_STRING_LIST
);
175 for (nn
=0; nn
<extras
->used
; nn
++) {
176 fputs(extras
->list
[nn
], out
);
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.)
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
);
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
);
195 fputs("\n", out
); /* terminate headers */
196 extras
= conf_get_data((first_time
?"mail/body_prefix_first":"mail/body_prefix"), RECDB_STRING_LIST
);
198 for (nn
=0; nn
<extras
->used
; nn
++) {
199 send_flowed_text(out
, extras
->list
[nn
]);
203 send_flowed_text(out
, body
);
204 extras
= conf_get_data((first_time
?"mail/body_suffix_first":"mail/body_suffix"), RECDB_STRING_LIST
);
207 for (nn
=0; nn
<extras
->used
; nn
++)
208 send_flowed_text(out
, extras
->list
[nn
]);
211 /* Close file (sending mail) and check for return code */
215 rv
= wait4(child
, &res
, 0, NULL
);
216 } while ((rv
== -1) && (errno
== EINTR
));
218 /* accept the wait() result */
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
);
224 log_module(MAIN_LOG
, LOG_ERROR
, "sendmail() grandchild to %s: Exited with code %d", to
->email_addr
, res
);
227 log_module(MAIN_LOG
, LOG_INFO
, "sendmail() sent email to %s <%s>: %s", to
->handle
, to
->email_addr
, subject
);
231 /* Grandchild; dup2 the fds and exec the mailer. */
232 const char *argv
[10], *mpath
;
233 unsigned int argc
= 0;
235 /* Close the end of pipes we do not use. */
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
;
246 argv
[argc
++] = fromaddr
;
248 if(!valid_email(to
->email_addr
))
250 log_module(MAIN_LOG
, LOG_ERROR
, "email address contained illegal chars. Refusing to execv() sendmail.");
254 argv
[argc
++] = to
->email_addr
;
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
);
264 sendmail_ban_address(struct userNode
*user
, struct userNode
*bot
, const char *addr
, const char *reason
) {
268 target
= strpbrk(addr
, "*?") ? prohibited_masks
: prohibited_addrs
;
269 if ((str
= dict_find(target
, addr
, NULL
))) {
271 send_message(user
, bot
, "MAILMSG_EMAIL_ALREADY_BANNED", addr
, str
);
274 dict_insert(target
, strdup(addr
), strdup(reason
));
275 if (user
) send_message(user
, bot
, "MAILMSG_EMAIL_BANNED", addr
);
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
);
284 static MODCMD_FUNC(cmd_unbanemail
) {
289 target
= strpbrk(addr
, "*?") ? prohibited_masks
: prohibited_addrs
;
290 if (dict_remove(target
, addr
))
291 reply("MAILMSG_EMAIL_UNBANNED", addr
);
293 reply("MAILMSG_EMAIL_NOT_BANNED", addr
);
297 static MODCMD_FUNC(cmd_stats_email
) {
301 reply("MAILMSG_PROHIBITED_EMAIL_HEADER");
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
));
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
));
312 reply("MAILMSG_NO_PROHIBITED_EMAIL");
314 reply("MAILMSG_PROHIBITED_EMAIL_END");
319 sendmail_saxdb_read(struct dict
*db
) {
321 struct record_data
*rd
;
324 if ((subdb
= database_get_data(db
, KEY_PROHIBITED
, RECDB_OBJECT
))) {
325 for (it
= dict_first(subdb
); it
; it
= iter_next(it
)) {
327 if (rd
->type
== RECDB_QSTRING
)
328 sendmail_ban_address(NULL
, NULL
, iter_key(it
), rd
->d
.qstring
);
335 sendmail_saxdb_write(struct saxdb_context
*ctx
) {
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
);
348 sendmail_cleanup(void)
350 dict_delete(prohibited_addrs
);
351 dict_delete(prohibited_masks
);
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
);