/* * do_notice_ctcp: a re-entrant form of a CTCP reply parser. * See the implementation notes in do_ctcp(). */ char * do_notice_ctcp (const char *from, const char *to, char *str) { int flag; char local_ctcp_buffer [BIG_BUFFER_SIZE + 1], the_ctcp [IRCD_BUFFER_SIZE + 1], last [IRCD_BUFFER_SIZE + 1]; char *ctcp_command, *ctcp_argument; int i; char *ptr; int allow_ctcp_reply = 1; int l; int delim_char = charcount(str, CTCP_DELIM_CHAR); if (delim_char < 2) return str; /* No CTCPs. */ if (delim_char > 8) allow_ctcp_reply = 0; /* Ignore all the CTCPs. */ /* We handle ignore, but not flooding (obviously) */ flag = check_ignore_channel(from, FromUserHost, to, LEVEL_CTCP); in_ctcp_flag++; strlcpy(local_ctcp_buffer, str, sizeof(local_ctcp_buffer) - 2); for (;;strlcat(local_ctcp_buffer, last, sizeof(local_ctcp_buffer) - 2)) { if (split_CTCP(local_ctcp_buffer, the_ctcp, last)) break; /* All done! */ if (!*the_ctcp) continue; /* Empty requests are ignored */ /* * The logic of all this is essentially the same as * do_ctcp */ if (!allow_ctcp_reply) continue; if (flag == IGNORED) { if (x_debug & DEBUG_CTCPS) yell("CTCP REPLY from [%s] ignored", from); allow_ctcp_reply = 0; continue; } /* But we don't check ctcp flooding (obviously) */ /* Global messages -- just drop the CTCP */ if (*to == '$' || (is_channel(to) && !im_on_channel(to, from_server))) { allow_ctcp_reply = 0; continue; } /* * Parse CTCP message * CTCP spec says word delim MUST be space */ ctcp_command = the_ctcp; ctcp_argument = strchr(the_ctcp, ' '); if (ctcp_argument) *ctcp_argument++ = 0; else ctcp_argument = endstr(the_ctcp); /* Set up the window level/logging */ if (is_channel(to)) l = message_from(to, LEVEL_CTCP); else l = message_from(from, LEVEL_CTCP); /* * Find the correct CTCP and run it. */ for (i = 0; i < NUMBER_OF_CTCPS; i++) if (!strcmp(ctcp_command, ctcp_cmd[i].name)) break; /* * If its a built in CTCP command, check to see if its * got a reply handler, call if appropriate. */ if (i < NUMBER_OF_CTCPS && ctcp_cmd[i].repl) { if ((ptr = ctcp_cmd[i].repl(ctcp_cmd + i, from, to, ctcp_argument))) { strlcat(local_ctcp_buffer, ptr, sizeof local_ctcp_buffer); new_free(&ptr); pop_message_from(l); continue; } } /* Toss it at the user. */ if (ctcp_cmd[i].flag & CTCP_TELLUSER) { if (do_hook(CTCP_REPLY_LIST, "%s %s %s %s", from, to, ctcp_command, ctcp_argument)) say("CTCP %s reply from %s: %s", ctcp_command, from, ctcp_argument); } if (!(ctcp_cmd[i].flag & CTCP_NOLIMIT)) allow_ctcp_reply = 0; pop_message_from(l); } in_ctcp_flag--; /* * local_ctcp_buffer is derived from 'str', so its always * smaller or equal in size to 'str', so this copy is safe. */ strlcpy(str, local_ctcp_buffer, BIG_BUFFER_SIZE); return str; }
/* * do_notice_ctcp: a re-entrant form of a CTCP reply parser. * See the implementation notes in do_ctcp(). */ char * do_notice_ctcp (const char *from, const char *to, char *str) { int flag; int lastlog_level; char local_ctcp_buffer [BIG_BUFFER_SIZE + 1], the_ctcp [IRCD_BUFFER_SIZE + 1], last [IRCD_BUFFER_SIZE + 1]; char *ctcp_command, *ctcp_argument; int i; char *ptr; int allow_ctcp_reply = 1; int delim_char = charcount(str, CTCP_DELIM_CHAR); if (delim_char < 2) return str; /* No CTCPs. */ if (delim_char > 8) allow_ctcp_reply = 0; /* Ignore all the CTCPs. */ flag = check_ignore_channel(from, FromUserHost, to, IGNORE_CTCPS); if (!in_ctcp_flag) in_ctcp_flag = -1; strlcpy(local_ctcp_buffer, str, IRCD_BUFFER_SIZE - 2); for (;;strlcat(local_ctcp_buffer, last, sizeof local_ctcp_buffer)) { if (split_CTCP(local_ctcp_buffer, the_ctcp, last)) break; /* All done! */ if (!*the_ctcp) continue; /* Empty requests are ignored */ /* * Apply sanity rules */ if (!allow_ctcp_reply) continue; if (flag == IGNORED) { if (x_debug & DEBUG_CTCPS) yell("CTCP REPLY from [%s] ignored", from); allow_ctcp_reply = 0; continue; } /* Global messages -- just drop the CTCP */ if (*to == '$' || (*to == '#' && !im_on_channel(to, from_server))) { allow_ctcp_reply = 0; continue; } /* * Parse CTCP message * CTCP spec says word delim MUST be space. */ ctcp_command = the_ctcp; ctcp_argument = strchr(the_ctcp, ' '); if (ctcp_argument) *ctcp_argument++ = 0; else ctcp_argument = empty_string; /* * Find the correct CTCP and run it. */ for (i = 0; i < NUMBER_OF_CTCPS; i++) if (!strcmp(ctcp_command, ctcp_cmd[i].name)) break; /* * If its a built in CTCP command, check to see if its * got a reply handler, call if appropriate. */ if (i < NUMBER_OF_CTCPS && ctcp_cmd[i].repl) { if ((ptr = ctcp_cmd[i].repl(ctcp_cmd + i, from, to, ctcp_argument))) { strlcat(local_ctcp_buffer, ptr, sizeof local_ctcp_buffer); new_free(&ptr); continue; } } /* Set up the window level/logging */ lastlog_level = set_lastlog_msg_level(LOG_CTCP); message_from(NULL, LOG_CTCP); /* Toss it at the user. */ if (do_hook(CTCP_REPLY_LIST, "%s %s %s %s", from, to, ctcp_command, ctcp_argument)) say("CTCP %s reply from %s: %s", ctcp_command, from, ctcp_argument); /* Reset the window level/logging */ set_lastlog_msg_level(lastlog_level); if (!(ctcp_cmd[i].flag & CTCP_NOLIMIT)) allow_ctcp_reply = 0; } if (in_ctcp_flag == -1) in_ctcp_flag = 0; /* * local_ctcp_buffer is derived from 'str', so its always * smaller or equal in size to 'str', so this copy is safe. */ strlcpy(str, local_ctcp_buffer, BIG_BUFFER_SIZE); return str; }
/* * do_ctcp: a re-entrant form of a CTCP parser. The old one was lame, * so i took a hatchet to it so it didnt suck. * * XXXX - important! The third argument -- 'str', is expected to be * 'BIG_BUFFER_SIZE + 1' or larger. If it isnt, chaos will probably * ensue if you get spammed with lots of CTCP UTC requests. * * UTC requests can be at minimum 5 bytes, and the expansion is always 24. * That means you can cram (510 - strlen("PRIVMSG x :") / 5) UTCs (100) * into a privmsg. That means itll expand to 2400 characters. We silently * limit the number of valid CTCPs to 4. Anything more than that we dont * even bother with. (4 * 24 + 11 -> 106), which is less than * IRCD_BUFFER_SIZE, which gives us plenty of safety. * * XXXX - The normal way of implementation required two copies -- once into a * temporary buffer, once back into the original buffer -- for the best case * scenario. This is horrendously inefficient, since most privmsgs dont * contain any CTCPs. So we check to see if there are any CTCPs in the * message before we bother doing anything. THIS IS AN INELEGANT HACK! * But the call to charcount() is less expensive than even one copy to * strlcpy() since they both evaluate *each* character, and charcount() * doesnt have to do a write unless the character is present. So it is * definitely worth the cost to save CPU time for 99% of the PRIVMSGs. */ char * do_ctcp (const char *from, const char *to, char *str) { int flag; int fflag; char local_ctcp_buffer [BIG_BUFFER_SIZE + 1], the_ctcp [IRCD_BUFFER_SIZE + 1], last [IRCD_BUFFER_SIZE + 1]; char *ctcp_command, *ctcp_argument; int i; char *ptr = NULL; int allow_ctcp_reply = 1; static time_t last_ctcp_parsed = 0; int l; char * extra = NULL; int delim_char = charcount(str, CTCP_DELIM_CHAR); if (delim_char < 2) return str; /* No CTCPs. */ if (delim_char > 8) allow_ctcp_reply = 0; /* Historical limit of 4 CTCPs */ flag = check_ignore_channel(from, FromUserHost, to, LEVEL_CTCP); fflag = new_check_flooding(from, FromUserHost, is_channel(to) ? to : NULL, str, LEVEL_CTCP); in_ctcp_flag++; strlcpy(local_ctcp_buffer, str, sizeof(local_ctcp_buffer) - 2); for (;;strlcat(local_ctcp_buffer, last, sizeof(local_ctcp_buffer) - 2)) { if (split_CTCP(local_ctcp_buffer, the_ctcp, last)) break; /* All done! */ if (!*the_ctcp) continue; /* Empty requests are ignored */ /* * Apply some integrety rules: * -- If we've already replied to a CTCP, ignore it. * -- If user is ignoring sender, ignore it. * -- If we're being flooded, ignore it. * -- If CTCP was a global msg, ignore it. */ /* * Yes, this intentionally ignores "unlimited" CTCPs like * UTC and SED. Ultimately, we have to make sure that * CTCP expansions dont overrun any buffers that might * contain this string down the road. So by allowing up to * 4 CTCPs, we know we cant overflow -- but if we have more * than 40, it might overflow, and its probably a spam, so * no need to shed tears over ignoring them. Also makes * the sanity checking much simpler. */ if (!allow_ctcp_reply) continue; /* * Check to see if the user is ignoring person. * Or if we're suppressing a flood. */ if (flag == IGNORED || fflag == 1) { if (x_debug & DEBUG_CTCPS) yell("CTCP from [%s] ignored", from); allow_ctcp_reply = 0; continue; } /* * Check for CTCP flooding */ if (get_int_var(NO_CTCP_FLOOD_VAR)) { if (time(NULL) - last_ctcp_parsed < 2) { /* * This extends the flood protection until * we dont get a CTCP for 2 seconds. */ last_ctcp_parsed = time(NULL); allow_ctcp_reply = 0; if (x_debug & DEBUG_CTCPS) say("CTCP flood from [%s] ignored", from); continue; } } /* * Check for global message */ if (*to == '$' || (*to == '#' && !im_on_channel(to, from_server))) { allow_ctcp_reply = 0; continue; } /* * Now its ok to parse the CTCP. * First we remove the argument. * XXX - CTCP spec says word delim MUST be space. */ ctcp_command = the_ctcp; ctcp_argument = strchr(the_ctcp, ' '); if (ctcp_argument) *ctcp_argument++ = 0; else ctcp_argument = endstr(the_ctcp); /* Set up the window level/logging */ if (im_on_channel(to, from_server)) l = message_from(to, LEVEL_CTCP); else l = message_from(from, LEVEL_CTCP); /* * Then we look for the correct CTCP. */ for (i = 0; i < NUMBER_OF_CTCPS; i++) if (!strcmp(ctcp_command, ctcp_cmd[i].name)) break; /* * We didnt find it? */ if (i == NUMBER_OF_CTCPS) { /* * Offer it to the user. * Maybe they know what to do with it. */ if (do_hook(CTCP_REQUEST_LIST, "%s %s %s %s", from, to, ctcp_command, ctcp_argument)) { if (do_hook(CTCP_LIST, "%s %s %s %s", from, to, ctcp_command, ctcp_argument)) { say("Unknown CTCP %s from %s to %s: %s%s", ctcp_command, from, to, *ctcp_argument ? ": " : empty_string, ctcp_argument); } } time(&last_ctcp_parsed); allow_ctcp_reply = 0; pop_message_from(l); continue; } /* * rfc1459_any_to_utf8 specifically ignores CTCPs, because * recoding binary data (such as an encrypted message) would * corrupt the message. * * So some CTCPs are "recodable" and some are not. * * The CTCP_NORECODE is set for any CTCPs which are NOT * to be recoded prior to handling. These are the encryption * CTCPS. * * All other CTCPs have not been recoded by the time they * reach here, so we must do it here! */ if (!(ctcp_cmd[i].flag & CTCP_NORECODE)) { /* * We must recode to UTF8 */ inbound_recode(from, from_server, to, ctcp_argument, &extra); if (extra) ctcp_argument = extra; } /* * We did find it. Acknowledge it. */ ptr = NULL; if (do_hook(CTCP_REQUEST_LIST, "%s %s %s %s", from, to, ctcp_command, ctcp_argument)) { ptr = ctcp_cmd[i].func(ctcp_cmd + i, from, to, ctcp_argument); } /* * If this isnt an 'unlimited' CTCP, set up flood protection. * * No, this wont allow users to flood any more than they * would normally. The UTC/SED gets converted into a * regular privmsg body, which is flagged via FLOOD_PUBLIC. */ if (!(ctcp_cmd[i].flag & CTCP_NOLIMIT)) { time(&last_ctcp_parsed); allow_ctcp_reply = 0; } /* * We've only gotten to this point if its a valid CTCP * query and we decided to parse it. */ /* * If its an ``INLINE'' CTCP, we paste it back in. */ if (ctcp_cmd[i].flag & CTCP_INLINE) strlcat(local_ctcp_buffer, ptr ? ptr : empty_string, sizeof local_ctcp_buffer); /* * If its ``INTERESTING'', tell the user. * Note that this isnt mutex with ``INLINE'' in theory, * even though it is in practice. Dont use 'else' here. */ if (ctcp_cmd[i].flag & CTCP_TELLUSER) { if (do_hook(CTCP_LIST, "%s %s %s %s", from, to, ctcp_command, ctcp_argument)) { if (is_me(from_server, to)) say("CTCP %s from %s%s%s", ctcp_command, from, *ctcp_argument ? ": " : empty_string, ctcp_argument); else say("CTCP %s from %s to %s%s%s", ctcp_command, from, to, *ctcp_argument ? ": " : empty_string, ctcp_argument); } } new_free(&extra); new_free(&ptr); pop_message_from(l); } in_ctcp_flag--; /* * 'str' is required to be BIG_BUFFER_SIZE + 1 or bigger per the API. */ strlcpy(str, local_ctcp_buffer, BIG_BUFFER_SIZE); return str; }
/* * The main handler for those wacky NOTICE commands... * This is as much like p_privmsg as i can get away with. */ void p_notice (const char *from, const char *comm, const char **ArgList) { const char *target, *message; int level, hook_type; const char * flood_channel = NULL; char * high; PasteArgs(ArgList, 1); if (!(target = ArgList[0])) { rfc1459_odd(from, comm, ArgList); return; } if (!(message = ArgList[1])) { rfc1459_odd(from, comm, ArgList); return; } set_server_doing_notice(from_server, 1); sed = 0; /* Do normal /CTCP reply handling */ /* XXX -- Casting "message" to (char *) is cheating. */ message = do_notice_ctcp(from, target, (char *) #ifdef HAVE_INTPTR_T (intptr_t) #endif message); if (!*message) { set_server_doing_notice(from_server, 0); return; } /* Check to see if it is a "Server Notice" */ if ((!from || !*from) || !strcmp(get_server_itsname(from_server), from)) { parse_local_server_notice(from, target, message); set_server_doing_notice(from_server, 0); return; } /* For pesky prefix-less NOTICEs substitute the server's name */ if (!from || !*from) from = get_server_name(from_server); /* * Note that NOTICEs from servers are not "server notices" unless * the target is not a channel (ie, it is sent to us). Any notice * that is sent to a channel is a normal NOTICE, notwithstanding * _who_ sent it. */ if (is_channel(target) && im_on_channel(target, from_server)) { flood_channel = target; hook_type = PUBLIC_NOTICE_LIST; } else if (!is_me(from_server, target)) { flood_channel = NULL; hook_type = NOTICE_LIST; } else { flood_channel = NULL; hook_type = NOTICE_LIST; target = from; } /* Check for /ignore's */ switch (check_ignore_channel(from, FromUserHost, target, IGNORE_NOTICES)) { case IGNORED: set_server_doing_notice(from_server, 0); return; case HIGHLIGHTED: high = highlight_char; break; /* oops! */ default: high = empty_string; } /* Let the user know if it is an encrypted notice */ /* Note that this is always hooked, even during a flood */ if (sed) { int do_return = 1; sed = 0; level = set_lastlog_msg_level(LOG_NOTICE); message_from(target, LOG_NOTICE); if (do_hook(ENCRYPTED_NOTICE_LIST, "%s %s %s", from, target, message)) do_return = 0; set_lastlog_msg_level(level); message_from(NULL, LOG_CRAP); if (do_return) { set_server_doing_notice(from_server, 0); return; } } if (new_check_flooding(from, FromUserHost, flood_channel, message, NOTICE_FLOOD)) { set_server_doing_notice(from_server, 0); return; } /* Beep the user if they asked us to */ if (beep_on_level & LOG_NOTICE) beep_em(1); /* Go ahead and throw it to the user */ level = set_lastlog_msg_level(LOG_NOTICE); message_from(target, LOG_NOTICE); if (do_hook(GENERAL_NOTICE_LIST, "%s %s %s", from, target, message)) { if (hook_type == NOTICE_LIST) { if (do_hook(hook_type, "%s %s", from, message)) put_it("%s-%s-%s %s", high, from, high, message); } else { if (do_hook(hook_type, "%s %s %s", from, target, message)) put_it("%s-%s:%s-%s %s", high, from, target, high, message); } } /* Clean up and go home. */ set_lastlog_msg_level(level); message_from(NULL, LOG_CRAP); set_server_doing_notice(from_server, 0); /* Alas, this is not protected by protocol enforcement. :( */ notify_mark(from_server, from, 1, 0); }