Просмотр исходного кода

Merge branch 'server-queue-speedup'

* server-queue-speedup: (55 commits)
  * Fix code formatting
  * Fix compiler warning
  * Fix single-use timers not getting free'd properly
  * Add more output from cmd_play
  * Block cmd_play from being used massively by botcmd. There's no good reason for allowing this.
  * Un-inline timer_get_now()
  * Fix channel joining causing issues with restarting
  * Fix compile warnings due to implicit conversions
  * Chain WHO requests to try avoiding Max SendQ
  * If lagging by more than 5 seconds, increase penalty by 2 seconds to avoid a burst->excess flood
  * Only use flood_count system on ratbox type servers
  * Move burst debug output
  * Don't flush out CACHE queue in deq_msg()
  * Utilize CPRIVMSG/CNOTICE if available
  * Change all PRIVMSG/NOTICE cmds into privmsg()/notice() to prepare for CPRIVMSG/CNOTICE and later use of FiSH
  * Add a DP_PLAY queue which is the lowest priority, but is burstable, allowing double messages and 10,000 messages. (ASCII)
  * Cleanup redundant server dequeue code
  * Add cmd_play for playing files to irc. (ASCII art gallery exhibition)
  * Capture PRIVMSG/NOTICE during burst mode
  * Send first 2 CTCP responses for dronemones, not just the first
  ...

Conflicts:
	doc/UPDATES
	doc/help.txt
	lib/bdlib
	src/mod/irc.mod/chan.c
	src/mod/irc.mod/cmdsirc.c
	src/mod/server.mod/server.c
	src/mod/server.mod/server.h
	src/mod/server.mod/servmsg.c
	src/set.c
Bryan Drewery 16 лет назад
Родитель
Сommit
06a78c8f70

+ 8 - 0
doc/UPDATES

@@ -117,6 +117,14 @@
   * Channel settings 'mop' and 'mdop' now react to non-users.
   * set 'fight-threshold' now requires +protect and is a trigger for +protect. See 'help chanset' for +protect info.
   * Bots no longer cycle channels when restarting/upgrading. (This will take effect AFTER upgrading to 1.3)
+  * Speedup irc server queue by bursting more often and fully. Thanks VXP.
+  * Add set 'msgrate' to define how often to dequeue to the server. (1 or 2 is good)
+  * Add set 'msgburst' to define how many commands to burst to server per msgrate.
+  * On hybrid/ratbox servers, burst some commands on connect
+  * On ratbox type servers, use an optimized queue for better throughput on the queue.
+  * Add cmd_play for playing files to irc. (ASCII art gallery exhibition)
+  * Utilize CPRIVMSG/CNOTICE if available.
+  * Chain WHO requests to try avoiding Max SendQ
 
 1.2.16.1
 * Fix linux compile errors

+ 16 - 1
doc/help.txt

@@ -702,7 +702,7 @@ See also: chhandle, chpass
 See also: +host, -host
 :leaf:clearqueue
 ###  $bclearqueue$b <queue>
-   Removes all msgs from the specified queue (mode/server/help/all)
+   Removes all msgs from the specified queue (mode/server/help/play/all)
 :hub:cmdpass:
 ###  $bcmdpass$b <command> <pass> [newpassword]
    Places the specified pass on the cmd so that the cmd will need to be 
@@ -1477,6 +1477,18 @@ See also: deop, console
    many pending lines, you may be booted off the bot.
  
 See also: color, console, echo, login, strip
+:leaf:play
+###  $bplay$b [channel] <file>
+   Plays the specified file to the specified channel. If no channel is
+   given then your console channel is used.
+ 
+   The 'play' queue is used for this, which is the lowest priority on
+   the bot. Any IRC related queue needs will immediately trump
+   the 'play' queue and delay the playing of the file.
+ 
+   Only files in the bots directory path may be played. (Symlinked
+   paths are fine). This is to prevent a user from playing /etc/passwd
+   since the cmd is +m.
 ::ps:
 ###  $bps$b [ps-param]
    Will run 'ps' on the bot's shell and display any results. If
@@ -1643,6 +1655,9 @@ See also: reload, backup
 [L]  $bservers$b         Comma-separated list of servers the bot will use.
 [L]  $bservers6$b        Comma-separated list of servers the bot will use (FOR IPv6).
  
+[N]  $bmsgburst$b        How many messages to burst at once to server. (Too high will excess flood)
+[N]  $bmsgrate$b         How often (seconds) to dequeue msgs to the server. (1 or 2 is good)
+
 [L]  $brbl-servers$b     Servers to use for RBL checking in channels that are +rbl.
  
 [S]  $brealname$b        The bot's "real name" when connecting. (supports '$n' expansion)

+ 1 - 1
lib/bdlib

@@ -1 +1 @@
-Subproject commit cdb81535c8b1960eb5e6b14c8de5aa6821f813ce
+Subproject commit ba445e8659587e4bdd6217d704bf956847d2a2cc

+ 1 - 0
src/binds.c

@@ -454,3 +454,4 @@ void rem_builtins(const char *table_name, cmd_t *cmds)
 		bind_entry_del(table, -1, cmds->name, name, NULL);
 	}
 }
+/* vim: set sts=4 sw=4 ts=4 noet: */

+ 2 - 2
src/chan.h

@@ -320,8 +320,8 @@ struct chanset_t *findchan_by_dname(const char *name);
 struct msgq_head {
   struct msgq *head;
   struct msgq *last;
-  int tot;
-  int warned;
+  size_t tot;
+  bool warned;
 };
 
 /* Used to queue a lot of things */

+ 31 - 0
src/chanprog.c

@@ -33,6 +33,7 @@
 #include "common.h"
 #include "chanprog.h"
 #include "settings.h"
+#include "src/mod/irc.mod/irc.h"
 #include "src/mod/channels.mod/channels.h"
 #include "src/mod/server.mod/server.h"
 #include "src/mod/share.mod/share.h"
@@ -947,3 +948,33 @@ samechans(const char *nick, const char *delim)
 
   return ret;
 }
+
+struct chanset_t* find_common_opped_chan(const char* nick) {
+  for (struct chanset_t* chan = chanset; chan; chan = chan->next) {
+    if (channel_active(chan) && (me_op(chan) || me_voice(chan))) {
+      if (ismember(chan, nick))
+        return chan;
+    }
+  }
+  return NULL;
+}
+
+void privmsg(const char* target, const char* msg, int idx) {
+  struct chanset_t* chan = NULL;
+  if (have_cprivmsg && !strchr(CHANMETA, target[0]))
+    chan = find_common_opped_chan(target);
+  if (chan)
+    dprintf(idx, "CPRIVMSG %s %s :%s\n", target, chan->name, msg);
+  else
+    dprintf(idx, "PRIVMSG %s :%s\n", target, msg);
+}
+
+void notice(const char* target, const char* msg, int idx) {
+  struct chanset_t* chan = NULL;
+  if (have_cnotice && !strchr(CHANMETA, target[0]))
+    chan = find_common_opped_chan(target);
+  if (chan)
+    dprintf(idx, "CNOTICE %s %s :%s\n", target, chan->name, msg);
+  else
+    dprintf(idx, "NOTICE %s :%s\n", target, msg);
+}

+ 2 - 0
src/chanprog.h

@@ -32,6 +32,8 @@ void add_child_bots();
 bool is_hub(const char*);
 void load_internal_users();
 void setup_HQ(int);
+void privmsg(const char* target, const char* msg, int idx);
+void notice(const char* target, const char* msg, int idx);
 
 extern struct chanset_t		*chanset, *chanset_default;
 extern char			admin[], origbotnick[HANDLEN + 1], origbotname[NICKLEN], jupenick[NICKLEN], botname[NICKLEN], *def_chanset;

+ 4 - 4
src/cmds.c

@@ -1349,7 +1349,7 @@ static void cmd_botcmd(int idx, char *par)
 
   // Restrict dangerous mass commands ('botcmd *' (any *) or 'botcmd &')
   if ((strchr(botm, '*') && !findbot(botm)) || !strcmp(botm, "&")) {
-    if (!strncasecmp(cmd, "di", 2) || (!strncasecmp(cmd, "res", 3) && strncasecmp(cmd, "reset", 5)) || !strncasecmp(cmd, "sui", 3) ||
+    if (!strncasecmp(cmd, "di", 2) || (!strncasecmp(cmd, "res", 3) && strncasecmp(cmd, "reset", 5)) || !strncasecmp(cmd, "sui", 3) || !strncasecmp(cmd, "pl", 2) ||
         !strncasecmp(cmd, "j", 1) || (!strncasecmp(cmd, "dump", 4) && (!strncasecmp(par, "privmsg", 7) || !strncasecmp(par, "notice", 6) || !strncasecmp(par, "quit", 4)))) {
       dprintf(idx, "Not a good idea.\n");
       return;
@@ -2034,8 +2034,8 @@ static void cmd_timers(int idx, char *par)
 
       timer_info(ids[i], &name, &howlong, &trigger_time, &called);
       timer_diff(&mynow, &trigger_time, &diff);
-      simple_snprintf(interval, sizeof interval, "(%li.%li secs)", howlong.sec, howlong.usec);
-      simple_snprintf(next, sizeof next, "%li.%li secs", diff.sec, diff.usec);
+      simple_snprintf(interval, sizeof interval, "(%lis %lims)", howlong.sec, howlong.usec / 1000);
+      simple_snprintf(next, sizeof next, "%lis %lims", diff.sec, diff.usec / 1000);
       dprintf(idx, "%-2d: %-25s %-15s Next: %-25s Called: %d\n", i, name, interval, next, called);
     }
     free(ids);
@@ -4008,7 +4008,7 @@ static void rcmd_msg(char * tobot, char * frombot, char * fromhand, char * fromi
   if (!conf.bot->hub) {
     char *nick = newsplit(&par);
 
-    dprintf(DP_SERVER, "PRIVMSG %s :%s\n", nick, par);
+    privmsg(nick, par, DP_SERVER);
     if (!strcasecmp(tobot, conf.bot->nick)) {
       char buf[1024] = "";
 

+ 2 - 1
src/dccutil.c

@@ -307,6 +307,7 @@ dprintf_real(int idx, char* buf, size_t len, size_t bufsiz)
       case DP_SERVER:
       case DP_HELP:
       case DP_MODE:
+      case DP_PLAY:
       case DP_MODE_NEXT:
       case DP_SERVER_NEXT:
       case DP_HELP_NEXT:
@@ -343,7 +344,7 @@ dprintf_real(int idx, char* buf, size_t len, size_t bufsiz)
 
 //      simple_snprintf(ircbuf, size, "PRIVMSG %s :%s", dcc[idx].simulbot, buf);
 //      tputs(dcc[idx].sock, ircbuf, strlen(ircbuf));
-      dprintf(DP_HELP, "PRIVMSG %s :%s\n", dcc[idx].simulbot, buf);
+      privmsg(dcc[idx].simulbot, buf, DP_HELP);
 //      free(ircbuf);
     } else {
       if (dcc[idx].type && ((long) (dcc[idx].type->output) == 1)) {

+ 1 - 0
src/dccutil.h

@@ -32,6 +32,7 @@ namespace bd {
 #define DP_HELP_NEXT    0x7FF9
 #define DP_DUMP		0x8000
 #define DP_CACHE	0x8001
+#define DP_PLAY		0x8002
 
 
 

+ 96 - 63
src/egg_timer.c

@@ -44,13 +44,13 @@ typedef struct egg_timer_b {
 } egg_timer_t;
 
 /* We keep a sorted list of active timers. */
-static egg_timer_t *timer_list_head = NULL;
+static egg_timer_t *timer_repeat_head = NULL, *timer_once_head = NULL;
 static int timer_next_id = 1;
 
 /* Based on TclpGetTime from Tcl 8.3.3 */
-int timer_get_time(egg_timeval_t *curtime)
+static inline int timer_get_time(egg_timeval_t *curtime)
 {
-	struct timeval tv;
+	static struct timeval tv;
 
 	(void) gettimeofday(&tv, NULL);
 	curtime->sec = tv.tv_sec;
@@ -58,17 +58,12 @@ int timer_get_time(egg_timeval_t *curtime)
 	return(0);
 }
 
-int timer_update_now(egg_timeval_t *_now)
+void timer_update_now(egg_timeval_t *_now)
 {
 	timer_get_time(&now);
-	if (_now) {
-		_now->sec = now.sec;
-		_now->usec = now.usec;
-	}
-	return(now.sec);
+	if (_now) timer_get_now(_now);
 }
 
-
 void timer_get_now(egg_timeval_t *_now)
 {
 	_now->sec = now.sec;
@@ -99,19 +94,34 @@ int timer_diff(egg_timeval_t *from_time, egg_timeval_t *to_time, egg_timeval_t *
 			diff->usec = 0;
 			return(1);
 		}
-		diff->sec -= 1;
+		--(diff->sec);
 		diff->usec += 1000000;
 	}
 
 	return(0);
 }
 
-static int timer_add_to_list(egg_timer_t *timer)
+/*
+ * Return milliseconds difference between two timevals
+ */
+long timeval_diff(const egg_timeval_t *tv1, const egg_timeval_t *tv2)
+{
+	long secs = tv1->sec - tv2->sec, usecs = tv1->usec - tv2->usec;
+	if (usecs < 0) {
+		usecs += 1000000;
+		--secs;
+	}
+	usecs = (usecs / 1000) + (secs * 1000);
+
+	return usecs;
+}
+
+static int timer_add_to_list(egg_timer_t* &timer_list, egg_timer_t *timer)
 {
 	egg_timer_t *prev = NULL, *ptr = NULL;
 
 	/* Find out where this should go in the list. */
-	for (ptr = timer_list_head; ptr; ptr = ptr->next) {
+	for (ptr = timer_list; ptr; ptr = ptr->next) {
 		if (timer->trigger_time.sec < ptr->trigger_time.sec) break;
 		if (timer->trigger_time.sec == ptr->trigger_time.sec && timer->trigger_time.usec < ptr->trigger_time.usec) break;
 		prev = ptr;
@@ -123,8 +133,8 @@ static int timer_add_to_list(egg_timer_t *timer)
 		prev->next = timer;
 	}
 	else {
-		timer->next = timer_list_head;
-		timer_list_head = timer;
+		timer->next = timer_list;
+		timer_list = timer;
 	}
 	return(0);
 }
@@ -157,18 +167,20 @@ int timer_create_complex(egg_timeval_t *howlong, const char *name, Function call
 	timer->trigger_time.usec = now.usec + howlong->usec;
 	timer->called = 0;
 
-	timer_add_to_list(timer);
+	if (timer->flags & TIMER_REPEAT)
+		timer_add_to_list(timer_repeat_head, timer);
+	else
+		timer_add_to_list(timer_once_head, timer);
 
 	return(timer->id);
 }
 
-/* Destroy a timer, given an id. */
-int timer_destroy(int timer_id)
+static int timer_destroy_list(egg_timer_t* &timer_list, int timer_id)
 {
 	egg_timer_t *prev = NULL, *timer = NULL;
 
 	prev = NULL;
-	for (timer = timer_list_head; timer; timer = timer->next) {
+	for (timer = timer_list; timer; timer = timer->next) {
 		if (timer->id == timer_id) break;
 		prev = timer;
 	}
@@ -177,13 +189,24 @@ int timer_destroy(int timer_id)
 
 	/* Unlink it. */
 	if (prev) prev->next = timer->next;
-	else timer_list_head = timer->next;
+	else timer_list = timer->next;
 
-	if (timer->name) free(timer->name);
+	if (timer->name)
+		free(timer->name);
 	free(timer);
 	return(0);
 }
 
+/* Destroy a timer, given an id. */
+int timer_destroy(int timer_id)
+{
+	if (timer_destroy_list(timer_repeat_head, timer_id))
+		if (timer_destroy_list(timer_once_head, timer_id))
+			return 1;
+	return 0;
+}
+
+#ifdef not_used
 int timer_destroy_all()
 {
 	egg_timer_t *timer = NULL, *next = NULL;
@@ -194,10 +217,11 @@ int timer_destroy_all()
 	timer_list_head = NULL;
 	return(0);
 }
+#endif
 
 int timer_get_shortest(egg_timeval_t *howlong)
 {
-	egg_timer_t *timer = timer_list_head;
+	egg_timer_t *timer = timer_repeat_head;
 
 	/* No timers? Boo. */
 	if (!timer) return(1);
@@ -206,49 +230,58 @@ int timer_get_shortest(egg_timeval_t *howlong)
 	return(0);
 }
 
-int timer_run()
-{
-	egg_timer_t *timer = NULL;
-        TimerFunc callback;
-	void *client_data = NULL;
-
-	while (timer_list_head) {
-		timer = timer_list_head;
-
-		if (timer->trigger_time.sec > now.sec || 
-			(timer->trigger_time.sec == now.sec && timer->trigger_time.usec > now.usec)) break;
-
-		timer_list_head = timer_list_head->next;
-
-		callback = timer->callback;
-		client_data = timer->client_data;
+static bool process_timer(egg_timer_t* timer) {
+	TimerFunc callback = timer->callback;
+	void *client_data = timer->client_data;
+	bool deleted = 0;
+
+	if (timer->flags & TIMER_REPEAT) {
+		/* Update timer. */
+		/* This used to be '+= howlong.sec' but, if the time changed say 3 years (happened), this function
+		 * would end up executing all timers for 3 years until it is caught up.
+		 */
+		timer->trigger_time.sec = now.sec + timer->howlong.sec;
+		timer->trigger_time.usec = now.usec + timer->howlong.usec;
+
+		if (timer->trigger_time.usec >= 1000000) {
+			timer->trigger_time.usec -= 1000000;
+			++(timer->trigger_time.sec);
+		}
 
-		if (timer->flags & TIMER_REPEAT) {
-			/* Update timer. */
-                        /* This used to be '+= howlong.sec' but, if the time changed say 3 years (happened), this function 
-                         * would end up executing all timers for 3 years until it is caught up.
-                         */
-			timer->trigger_time.sec = now.sec + timer->howlong.sec;
-			timer->trigger_time.usec = now.usec + timer->howlong.usec;
+		++(timer->called);
+	} else {
+		deleted = 1;
+	}
 
-			if (timer->trigger_time.usec >= 1000000) {
-				timer->trigger_time.usec -= 1000000;
-				timer->trigger_time.sec += 1;
-			}
+	callback(client_data);
+	return deleted;
+}
 
-			/* Add it back into the list. */
-			timer_add_to_list(timer);
+static void process_timer_list(egg_timer_t* &timer_list) {
+	egg_timer_t *timer = NULL, *prev = NULL, *next = timer_list;
+	while (next) {
+		timer = next;
+		// Timers are sorted by lowest->highest, so if the current one isn't ready to trigger, the rest are not either
+		if (timer->trigger_time.sec > now.sec || (timer->trigger_time.sec == now.sec && timer->trigger_time.usec > now.usec))
+			break;
+		next = timer->next;
+		if (process_timer(timer)) {
+			// Deleted, need to shift the queue
+			if (prev) prev->next = timer->next;
+			else timer_list = timer->next;
 
-			timer->called++;
-		}
-		else {
-			if (timer->name) free(timer->name);
+			if (timer->name)
+				free(timer->name);
 			free(timer);
-		}
-
-		callback(client_data);
+		} else
+			prev = timer;
 	}
-	return(0);
+}
+
+void timer_run()
+{
+	process_timer_list(timer_once_head);
+	process_timer_list(timer_repeat_head);
 }
 
 int timer_list(int **ids)
@@ -257,12 +290,12 @@ int timer_list(int **ids)
 	int ntimers = 0;
 
 	/* Count timers. */
-	for (timer = timer_list_head; timer; timer = timer->next) ntimers++;
+	for (timer = timer_repeat_head; timer; timer = timer->next) ntimers++;
 
 	/* Fill in array. */
 	*ids = (int *) my_calloc(1, sizeof(int) * (ntimers+1));
 	ntimers = 0;
-	for (timer = timer_list_head; timer; timer = timer->next) {
+	for (timer = timer_repeat_head; timer; timer = timer->next) {
 		(*ids)[ntimers++] = timer->id;
 	}
 	return(ntimers);
@@ -272,7 +305,7 @@ int timer_info(int id, char **name, egg_timeval_t *initial_len, egg_timeval_t *t
 {
         egg_timer_t *timer = NULL;
 
-        for (timer = timer_list_head; timer; timer = timer->next) {
+        for (timer = timer_repeat_head; timer; timer = timer->next) {
                 if (timer->id == id) break;
         }
         if (!timer) return(-1);
@@ -283,4 +316,4 @@ int timer_info(int id, char **name, egg_timeval_t *initial_len, egg_timeval_t *t
         return(0);
 }
 
-
+/* vim: set sts=4 sw=4 ts=4 noet: */

+ 5 - 3
src/egg_timer.h

@@ -16,17 +16,19 @@ typedef struct egg_timeval_b {
 /* Create a simple timer with no client data, but it repeats. */
 #define timer_create_repeater(howlong,name,callback) timer_create_complex(howlong, name, callback, NULL, TIMER_REPEAT)
 
-int timer_get_time(egg_timeval_t *curtime);
 void timer_get_now(egg_timeval_t *_now);
 int timer_get_now_sec(int *sec);
-int timer_update_now(egg_timeval_t *_now);
+void timer_update_now(egg_timeval_t *_now);
 int timer_diff(egg_timeval_t *from_time, egg_timeval_t *to_time, egg_timeval_t *diff);
+long timeval_diff(const egg_timeval_t *tv1, const egg_timeval_t *tv2);
 int timer_create_secs(int, const char *, Function);
 int timer_create_complex(egg_timeval_t *howlong, const char *name, Function callback, void *client_data, int flags);
 int timer_destroy(int timer_id);
+#ifdef not_used
 int timer_destroy_all();
+#endif
 int timer_get_shortest(egg_timeval_t *howlong);
-int timer_run();
+void timer_run();
 int timer_list(int **ids);
 int timer_info(int id, char **name, egg_timeval_t *initial_len, egg_timeval_t *trigger_time, int *called);
 #endif /* _EGG_TIMER_H_ */

+ 5 - 2
src/log.c

@@ -342,8 +342,11 @@ irc_log(struct chanset_t *chan, const char *format, ...)
   egg_vsnprintf(va_out, sizeof(va_out), format, va);
   va_end(va);
 
-  if ((chan && strcasecmp(chan->dname, "#!obs")) || !chan)
-    dprintf(DP_HELP, "PRIVMSG %s :[%s] %s\n", relay_chan, chan ? chan->dname : "*" , va_out);
+  if ((chan && strcasecmp(chan->dname, "#...")) || !chan) {
+    bd::String msg;
+    msg.printf("[%s] %s", chan ? chan->dname : "*" , va_out);
+    privmsg(relay_chan, msg.c_str(), DP_HELP);
+  }
 /*
   chanout_but(-1, 1, "[%s] %s\n", chan->dname, va_out);
   botnet_send_chan(-1, conf.bot->nick, chan->dname, 1, va_out);

+ 1 - 1
src/main.c

@@ -120,6 +120,7 @@ char	ver[101] = "";		/* Version info (short form) */
 bool	use_stderr = 1;		/* Send stuff to stderr instead of logfiles? */
 char	quit_msg[1024];		/* quit message */
 time_t	now;			/* duh, now :) */
+egg_timeval_t egg_timeval_now;
 
 int do_confedit = 0;		/* show conf menu if -C */
 static char do_killbot[21] = "";
@@ -697,7 +698,6 @@ void transfer_init();
 
 int main(int argc, char **argv)
 {
-  egg_timeval_t egg_timeval_now;
 
 #ifndef DEBUG
 #ifndef CYGWIN_HACKS

+ 2 - 0
src/main.h

@@ -2,6 +2,7 @@
 #define _MAIN_H
 
 #include <sys/types.h>
+#include "egg_timer.h"
 
 enum {
   UPDATE_AUTO = 1,
@@ -18,6 +19,7 @@ extern int		role, default_flags, default_uflags, do_confedit,
 extern bool		use_stderr, backgrd, used_B, term_z, loading, have_linked_to_hub, restart_was_update, restarting;
 extern char		tempdir[], *binname, owner[], version[151], ver[101], quit_msg[], *socksfile;
 extern time_t		online_since, now, restart_time;
+extern egg_timeval_t	egg_timeval_now;
 extern uid_t		myuid;
 extern pid_t            mypid;
 extern const time_t	buildts;

+ 1 - 1
src/misc.c

@@ -1082,7 +1082,7 @@ int goodpass(char *pass, int idx, char *nick)
     if (idx)
       dprintf(idx, "%s\n", tell);
     else if (nick[0])
-      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, tell);
+      notice(nick, tell, DP_HELP);
     return 0;
   }
   return 1;

+ 51 - 21
src/mod/ctcp.mod/ctcp.c

@@ -43,6 +43,7 @@
 #include <arpa/inet.h>
 #include <sys/utsname.h>
 #include <ctype.h>
+#include <bdlib/src/String.h>
 
 #define AVGAWAYTIME             60
 #define AVGHERETIME             5
@@ -403,8 +404,10 @@ static int ctcp_FINGER(char *nick, char *uhost, struct userrec *u, char *object,
     idletime = now - cloak_awaytime;
   else if (cloak_heretime)
     idletime = now - cloak_heretime;
-  dprintf(DP_HELP, "NOTICE %s :\001%s %s (%s) Idle %d second%s\001\n", nick, keyword, "",
+  bd::String msg;
+  msg.printf("\001%s %s (%s) Idle %d second%s\001", keyword, "",
                    botuserhost, (int) idletime, idletime == 1 ? "" : "s");
+  notice(nick, msg.c_str(), DP_HELP);
   return BIND_RET_BREAK;
 }
 
@@ -413,17 +416,22 @@ static int ctcp_ECHO(char *nick, char *uhost, struct userrec *u, char *object, c
   char reply[60] = "";
 
   strlcpy(reply, text, sizeof(reply));
-  dprintf(DP_HELP, "NOTICE %s :\001%s %s\001\n", nick, keyword, reply);
+  bd::String msg;
+  msg.printf("\001%s %s\001", keyword, reply);
+  notice(nick, msg.c_str(), DP_HELP);
   return BIND_RET_BREAK;
 }
 static int ctcp_PING(char *nick, char *uhost, struct userrec *u, char *object, char *keyword, char *text)
 {
-  if (strlen(text) <= 80)       /* bitchx ignores > 80 */
-    dprintf(DP_HELP, "NOTICE %s :\001%s %s\001\n", nick, keyword, text);
+  if (strlen(text) <= 80) {       /* bitchx ignores > 80 */
+    bd::String msg;
+    msg.printf("\001%s %s\001", keyword, text);
+    notice(nick, msg.c_str(), DP_HELP);
+  }
   return BIND_RET_BREAK;
 }
 
-bool first_ctcp_check = 0;
+int first_ctcp_check = 0;
 
 static int ctcp_VERSION(char *nick, char *uhost, struct userrec *u, char *object, char *keyword, char *text)
 {
@@ -460,22 +468,30 @@ static int ctcp_VERSION(char *nick, char *uhost, struct userrec *u, char *object
   
   int queue = DP_HELP;
 
-  if (!first_ctcp_check) {
-    queue = DP_DUMP;
-    first_ctcp_check = 1;
+  // Send out first few replies right away for dronemons
+  if (first_ctcp_check < 2) {
+    queue = DP_SERVER;
+    ++first_ctcp_check;
   }
 
-  dprintf(queue, "NOTICE %s :\001%s %s%s\001\n", nick, keyword, ctcpversion, s);
+  bd::String msg;
+  msg.printf("\001%s %s%s\001", keyword, ctcpversion, s);
+  notice(nick, msg.c_str(), queue);
 
-  if (ctcpversion2[0])
-    dprintf(DP_HELP, "NOTICE %s :\001%s %s\001\n", nick, keyword, ctcpversion2);
+  if (ctcpversion2[0]) {
+    msg.printf("\001%s %s\001", keyword, ctcpversion2);
+    notice(nick, msg.c_str(), DP_HELP);
+  }
   return BIND_RET_BREAK;
 }
 
 static int ctcp_WHOAMI(char *nick, char *uhost, struct userrec *u, char *object, char *keyword, char *text)
 {
-  if (cloak_script > 0 && cloak_script < 9)
-    dprintf(DP_HELP, "NOTICE %s :\002BitchX\002: Access Denied\n", nick);
+  if (cloak_script > 0 && cloak_script < 9) {
+    bd::String msg;
+    msg.printf("\002BitchX\002: Access Denied");
+    notice(nick, msg.c_str(), DP_HELP);
+  }
   return BIND_RET_BREAK;
 }
 
@@ -488,7 +504,9 @@ static int ctcp_OP(char *nick, char *uhost, struct userrec *u, char *object, cha
     p = strchr(chan, ' ');
     if (p)
       *p = 0;
-    dprintf(DP_HELP, "NOTICE %s :\002BitchX\002: I'm not on %s or I'm not opped\n", nick, chan);
+    bd::String msg;
+    msg.printf("\002BitchX\002: I'm not on %s or I'm not opped", chan);
+    notice(nick, msg.c_str(), DP_HELP);
   }
   return BIND_RET_BREAK;
 }
@@ -498,6 +516,7 @@ static int ctcp_INVITE_UNBAN(char *nick, char *uhost, struct userrec *u, char *o
   if (text[0] == '#' && cloak_script > 0 && cloak_script < 9) {
     struct chanset_t *chan = chanset;
     char chname[256] = "", *p = NULL;
+    bd::String msg;
 
     strlcpy(chname, text, sizeof(chname));
     p = strchr(chname, ' ');
@@ -506,13 +525,15 @@ static int ctcp_INVITE_UNBAN(char *nick, char *uhost, struct userrec *u, char *o
     while (chan) {
       if (chan->ircnet_status & CHAN_ACTIVE) {
         if (!strcasecmp(chan->name, chname)) {
-          dprintf(DP_HELP, "NOTICE %s :\002BitchX\002: Access Denied\n", nick);
+          msg.printf("\002BitchX\002: Access Denied");
+          notice(nick, msg.c_str(), DP_HELP);
           return BIND_RET_LOG;
         }
       }
       chan = chan->next;
     }
-    dprintf(DP_HELP, "NOTICE %s :\002BitchX\002: I'm not on that channel\n", nick);
+    msg.printf("\002BitchX\002: I'm not on that channel");
+    notice(nick, msg.c_str(), DP_HELP);
   }
   return BIND_RET_BREAK;
 }
@@ -525,7 +546,9 @@ static int ctcp_USERINFO(char *nick, char *uhost, struct userrec *u, char *objec
     strlcpy(ctcpuserinfo, botname, sizeof(ctcpuserinfo));
     strlcat(ctcpuserinfo, " ?", sizeof(ctcpuserinfo));
   }
-  dprintf(DP_HELP, "NOTICE %s :\001%s %s\001\n", nick, keyword, ctcpuserinfo);
+  bd::String msg;
+  msg.printf("\001%s %s\001", keyword, ctcpuserinfo);
+  notice(nick, msg.c_str(), DP_HELP);
   return BIND_RET_BREAK;
 }
 
@@ -535,6 +558,7 @@ static int ctcp_CLIENTINFO(char *nick, char *uhost, struct userrec *u, char *obj
     return BIND_RET_BREAK;
 
   char buf[256] = "";
+  bd::String msg;
 
   if (!text[0]) {
     strlcpy(buf, "SED UTC ACTION DCC CDCC BDCC XDCC VERSION CLIENTINFO USERINFO ERRMSG FINGER TIME PING ECHO INVITE WHOAMI OP OPS UNBAN IDENT XLINK UPTIME :Use CLIENTINFO <COMMAND> to get more specific information", sizeof(buf));
@@ -585,10 +609,12 @@ static int ctcp_CLIENTINFO(char *nick, char *uhost, struct userrec *u, char *obj
   else if (!strcasecmp(text, "UPTIME"))
     strlcpy(buf, "UPTIME my uptime", sizeof(buf));
   else {
-    dprintf(DP_HELP, "NOTICE %s :\001ERRMSG %s is not a valid function\001\n", nick, text);
+    msg.printf("\001ERRMSG %s is not a valid function\001", text);
+    notice(nick, msg.c_str(), DP_HELP);
     return BIND_RET_LOG;
   }
-  dprintf(DP_HELP, "NOTICE %s :\001%s %s\001\n", nick, keyword, buf);
+  msg.printf("\001%s %s\001", keyword, buf);
+  notice(nick, msg.c_str(), DP_HELP);
   return BIND_RET_BREAK;
 }
 
@@ -597,7 +623,9 @@ static int ctcp_TIME(char *nick, char *uhost, struct userrec *u, char *object, c
   char tms[81] = "";
 
   strlcpy(tms, ctime(&now), sizeof(tms));
-  dprintf(DP_HELP, "NOTICE %s :\001%s %s\001\n", nick, keyword, tms);
+  bd::String msg;
+  msg.printf("\001%s %s\001", keyword, tms);
+  notice(nick, msg.c_str(), DP_HELP);
   return BIND_RET_BREAK;
 }
 
@@ -629,7 +657,9 @@ static int ctcp_CHAT(char *nick, char *uhost, struct userrec *u, char *object, c
       /* do me a favour and don't change this back to a CTCP reply,
        * CTCP replies are NOTICE's this has to be a PRIVMSG
        * -poptix 5/1/1997 */
-      dprintf(DP_SERVER, "PRIVMSG %s :\001DCC CHAT chat %lu %u\001\n", nick, iptolong(getmyip()), dcc[ix].port);
+      bd::String msg;
+      msg.printf("\001DCC CHAT chat %lu %u\001", iptolong(getmyip()), dcc[ix].port);
+      privmsg(nick, msg.c_str(), DP_SERVER);
     }
     return BIND_RET_BREAK;
 }

+ 1 - 1
src/mod/ctcp.mod/ctcp.h

@@ -22,6 +22,6 @@
 void ctcp_init();
 void scriptchanged();
 extern char		kickprefix[], bankickprefix[];
-extern bool		first_ctcp_check;
+extern int		first_ctcp_check;
 
 #endif				/* _EGG_MOD_CTCP_CTCP_H */

+ 38 - 9
src/mod/irc.mod/chan.c

@@ -1544,6 +1544,13 @@ void recheck_channel(struct chanset_t *chan, int dobans)
   --stacking;
 }
 
+static int got001(char *from, char *msg)
+{
+  //Just connected, cleanup some vars
+  chained_who.clear();
+  return 0;
+}
+
 #ifdef CACHE
 /* got 302: userhost
  * <server> 302 <to> :<nick??user@host>
@@ -1594,7 +1601,7 @@ static int got302(char *from, char *msg)
       }
       if (cchan->ban) {
         cchan->ban = 0;
-        dprintf(DP_DUMP, "MODE %s +b *!%s\n", cchan->dname, uhost);
+        dprintf(DP_MODE_NEXT, "MODE %s +b *!%s\n", cchan->dname, uhost);
       }
     }
   }
@@ -1654,14 +1661,16 @@ static int got341(char *from, char *msg)
     cchan = cache_chan_add(cache, chan->dname);
 
   if (!cache->uhost[0]) {
-    dprintf(DP_DUMP, "MODE %s +b %s!*@*\n", chan->name, nick);
+    dprintf(DP_MODE_NEXT, "MODE %s +b %s!*@*\n", chan->name, nick);
     cchan->ban = 1;
-    dprintf(DP_DUMP, "USERHOST %s\n", nick);
+    dprintf(DP_MODE_NEXT, "USERHOST %s\n", nick);
   } else {
-    dprintf(DP_DUMP, "MODE %s +b *!*%s\n", chan->name, cache->uhost);
+    dprintf(DP_MODE_NEXT, "MODE %s +b *!*%s\n", chan->name, cache->uhost);
   }
   putlog(LOG_MISC, "*", "HIJACKED invite detected: %s to %s", nick, chan->dname);
-  dprintf(DP_DUMP, "PRIVMSG %s :ALERT! \002%s was invited via a hijacked connection/process.\002\n", chan->name, nick);
+  bd::String msg;
+  msg.printf("ALERT! \002%s was invited via a hijacked connection/process.\002", nick);
+  privmsg(chan->name, msg.c_str(), DP_MODE_NEXT);
   return 0;
 }
 #endif /* CACHE */
@@ -2008,7 +2017,26 @@ static int got315(char *from, char *msg)
 
   newsplit(&msg);
   chname = newsplit(&msg);
+
+  if (!chained_who.isEmpty()) {
+    // Send off next WHO request
+    while (1) {
+      if (chained_who.isEmpty()) break;
+      // Dequeue the next chan in the chain
+      chan = findchan(bd::String(chained_who.dequeue()).c_str());
+      if (chan) {
+        if (!strcmp(chan->name, chname)) continue; // First reply got queued too
+        if (!shouldjoin(chan)) continue; // No longer care about this channel
+        // Somehow got the WHO already
+        if (channel_active(chan) && !channel_pending(chan)) continue;
+        send_chan_who(chained_who_idx, chan);
+        break;
+      }
+    }
+  }
+
   chan = findchan(chname);
+
   /* May have left the channel before the who info came in */
   if (!chan || !channel_pending(chan))
     return 0;
@@ -2424,7 +2452,7 @@ static int gotinvite(char *from, char *msg)
 
   if (chan) {
     if (channel_pending(chan) || channel_active(chan))
-      dprintf(DP_HELP, "NOTICE %s :I'm already here.\n", nick);
+      notice(nick, "I'm already here.", DP_HELP);
     else if (shouldjoin(chan))
       join_chan(chan);
   }
@@ -3180,13 +3208,13 @@ static int gotmsg(char *from, char *msg)
   /* Send out possible ctcp responses */
   if (ctcp_reply[0]) {
     if (ctcp_mode != 2) {
-      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+      notice(nick, ctcp_reply, DP_HELP);
     } else {
       if (now - last_ctcp > flood_ctcp.time) {
-	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+        notice(nick, ctcp_reply, DP_HELP);
 	count_ctcp = 1;
       } else if (count_ctcp < flood_ctcp.count) {
-	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+        notice(nick, ctcp_reply, DP_HELP);
 	count_ctcp++;
       }
       last_ctcp = now;
@@ -3332,6 +3360,7 @@ static int gotnotice(char *from, char *msg)
 
 static cmd_t irc_raw[] =
 {
+  {"001",       "",     (Function) got001,      "irc:001", LEAF},
 #ifdef CACHE
   {"302",       "",     (Function) got302,      "irc:302", LEAF},
   {"341",       "",     (Function) got341,      "irc:341", LEAF},

+ 85 - 5
src/mod/irc.mod/cmdsirc.c

@@ -24,6 +24,9 @@
  *
  */
 
+#include <bdlib/src/Stream.h>
+#include <bdlib/src/String.h>
+#include "src/misc_file.h"
 
 /* Do we have any flags that will allow us ops on a channel?
  */
@@ -113,8 +116,9 @@ static void cmd_act(int idx, char *par)
   }
   putlog(LOG_CMDS, "*", "#%s# (%s) act %s", dcc[idx].nick,
 	 chan->dname, par);
-  dprintf(DP_HELP, "PRIVMSG %s :\001ACTION %s\001\n",
-	  chan->name, par);
+  bd::String msg;
+  msg.printf("\001ACTION %s\001", par);
+  privmsg(chan->name, msg.c_str(), DP_HELP);
   dprintf(idx, "Action to %s: %s\n", chan->dname, par);
 }
 
@@ -134,7 +138,7 @@ static void cmd_msg(int idx, char *par)
   char *nick = newsplit(&par);
 
   putlog(LOG_CMDS, "*", "#%s# msg %s %s", dcc[idx].nick, nick, par);
-  dprintf(DP_HELP, "PRIVMSG %s :%s\n", nick, par);
+  privmsg(nick, par, DP_HELP);
   dprintf(idx, "Msg to %s: %s\n", nick, par);
 }
 
@@ -182,7 +186,7 @@ static void cmd_say(int idx, char *par)
     return;
   }
   putlog(LOG_CMDS, "*", "#%s# (%s) say %s", dcc[idx].nick, chan->dname, par);
-  dprintf(DP_HELP, "PRIVMSG %s :%s\n", chan->name, par);
+  privmsg(chan->name, par, DP_HELP);
   dprintf(idx, "Said to %s: %s\n", chan->dname, par);
 }
 
@@ -1785,7 +1789,9 @@ static void cmd_adduser(int idx, char *par)
     dprintf(idx, "%s's initial password set to \002%s\002\n", hand, s2);
     dprintf(idx, "%s's initial secpass set to \002%s\002\n", hand, s3);
 
-    dprintf(DP_HELP, "PRIVMSG %s :*** You've been add to this botnet as '%s' with the host '%s'. Ask a botnet admin for the msg cmds. Your initial password is: %s\n", nick, hand, p1, s2);
+    bd::String msg;
+    msg.printf("*** You've been add to this botnet as '%s' with the host '%s'. Ask a botnet admin for the msg cmds. Your initial password is: %s", hand, p1, s2);
+    privmsg(nick, msg.c_str(), DP_HELP);
   } else {
     dprintf(idx, "Added hostmask %s to %s.\n", p1, u->handle);
     addhost_by_handle(hand, p1);
@@ -1890,6 +1896,79 @@ static void cmd_reset(int idx, char *par)
   }
 }
 
+static void cmd_play(int idx, char *par)
+{
+  if (!par[0]) {
+    dprintf(idx, "Usage: play [channel] <file>\n");
+    return;
+  }
+
+  char *chname = NULL;
+  struct chanset_t *chan = NULL;
+
+  if (strchr(CHANMETA, par[0]) != NULL)
+    chname = newsplit(&par);
+  else
+    chname = 0;
+  chan = get_channel(idx, chname);
+  if (!chan)
+    return;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: play [channel] <file>\n");
+    return;
+  }
+
+  memberlist *m = ismember(chan, botname);
+
+  if (!m) {
+    dprintf(idx, "Cannot play to %s: I'm not on that channel.\n", chan->dname);
+    return;
+  }
+
+  get_user_flagrec(dcc[idx].user, &user, chan->dname);
+
+  if (!me_op(chan) && !me_voice(chan)) {
+    dprintf(idx, "Cannot play to %s: I am not voiced or opped.\n", chan->dname);
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# (%s) play %s", dcc[idx].nick, chan->dname, par);
+
+  // Ensure file exists and is within proper path
+  bd::String file(par);
+
+  if (file[0] == '/' || file(0, 2) == "..") {
+    dprintf(idx, "Cannot play '%s': Illegal path.\n", par);
+    return;
+  }
+
+  if (!can_stat(par)) {
+    dprintf(idx, "Cannot play '%s': Cannot access file.\n", par);
+    return;
+  }
+
+  bd::String prefix;
+  bd::Stream stream;
+  stream.loadFile(par);
+  bd::String str;
+  size_t lines = 0;
+  while (stream.tell() < stream.length()) {
+    str = stream.getline().chomp();
+    if (str.length()) {
+      privmsg(chan->name, str.c_str(), DP_PLAY);
+      ++lines;
+    }
+  }
+  dprintf(idx, "Playing %zu lines from %s to %s\n", lines, par, chan->dname);
+  long time_to_play = 0;
+  if (lines < 10)
+    time_to_play = 3;
+  else
+    time_to_play = 3 + ((lines - 10) / 2);
+
+  dprintf(idx, "Estimated time-to-play: %li seconds\n", time_to_play);
+}
+
 static cmd_t irc_dcc[] =
 {
   {"act",		"o|o",	 (Function) cmd_act,		NULL, LEAF},
@@ -1912,6 +1991,7 @@ static cmd_t irc_dcc[] =
   {"msg",		"o",	 (Function) cmd_msg,		NULL, LEAF|AUTH},
   {"nick",		"m",	 (Function) cmd_nick,		NULL, LEAF},
   {"op",		"o|o",	 (Function) cmd_op,		NULL, LEAF|AUTH},
+  {"play",		"m|m",	 (Function) cmd_play,		NULL, LEAF|AUTH},
   {"release",		"m",	 (Function) cmd_release,	NULL, LEAF|AUTH},
   {"reset",		"m|m",	 (Function) cmd_reset,		NULL, LEAF|AUTH},
   {"resetbans",		"o|o",	 (Function) cmd_resetbans,	NULL, LEAF|AUTH},

+ 24 - 11
src/mod/irc.mod/irc.c

@@ -56,6 +56,7 @@
 #include "src/mod/ctcp.mod/ctcp.h"
 #include <bdlib/src/String.h>
 #include <bdlib/src/HashTable.h>
+#include <bdlib/src/Queue.h>
 #include <bdlib/src/base64.h>
 
 #include <stdarg.h>
@@ -91,6 +92,9 @@ bool include_lk = 1;      /* For correct calculation
 bd::HashTable<bd::String, unsigned long> bot_counters;
 static unsigned long my_counter = 0;
 
+static bd::Queue<bd::String> chained_who;
+static int chained_who_idx;
+
 static int
 voice_ok(memberlist *m, struct chanset_t *chan)
 {
@@ -177,7 +181,7 @@ void detected_drone_flood(struct chanset_t* chan, memberlist* m) {
     chan->channel.drone_joins = 0;
     chan->channel.drone_jointime = 0;
 
-    dprintf(DP_DUMP, "MODE %s +%s\n", chan->name[0] ? chan->name : chan->dname, buf);
+    dprintf(DP_MODE_NEXT, "MODE %s +%s\n", chan->name[0] ? chan->name : chan->dname, buf);
     howlong.sec = chan->flood_lock_time;
     howlong.usec = 0;
     timer_create_complex(&howlong, "unlock", (Function) unlock_chan, (void *) chan, 0);
@@ -194,8 +198,10 @@ void notice_invite(struct chanset_t *chan, char *handle, char *nick, char *uhost
   if (handle)
     simple_snprintf(fhandle, sizeof(fhandle), "\002%s\002 ", handle);
   putlog(LOG_MISC, "*", "Invited %s%s(%s%s%s) to %s.", handle ? handle : "", handle ? " " : "", nick, uhost ? "!" : "", uhost ? uhost : "", chan->dname);
-  dprintf(DP_MODE, "PRIVMSG %s :\001ACTION has invited %s(%s%s%s) to %s.%s\001\n",
-    chan->name, fhandle, nick, uhost ? "!" : "", uhost ? uhost : "", chan->dname, op ? ops : "");
+  bd::String msg;
+  msg.printf("\001ACTION has invited %s(%s%s%s) to %s.%s\001",
+    fhandle, nick, uhost ? "!" : "", uhost ? uhost : "", chan->dname, op ? ops : "");
+  privmsg(chan->name, msg.c_str(), DP_MODE);
 }
 
 #ifdef CACHE
@@ -1248,7 +1254,7 @@ me_op(struct chanset_t *chan)
 
 /* Check whether I'm voice. Returns boolean 1 or 0.
  */
-static bool
+bool
 me_voice(struct chanset_t *chan)
 {
   memberlist *mx = ismember(chan, botname);
@@ -1333,17 +1339,24 @@ reset_chan_info(struct chanset_t *chan)
     }
     /* These 2 need to get out asap, so into the mode queue */
     dprintf(DP_MODE, "MODE %s\n", chan->name);
-    send_chan_who(DP_MODE, chan);
+    send_chan_who(DP_MODE, chan, 1);
     /* clear_channel nuked the data...so */
-    dprintf(DP_MODE, "TOPIC %s\n", chan->name);
+    dprintf(DP_HELP, "TOPIC %s\n", chan->name);//Topic is very low priority
   }
 }
 
-static void send_chan_who(int queue, struct chanset_t *chan) {
-    if (use_354) /* Added benefit of getting numeric IP! :) */
-      dprintf(queue, "WHO %s %%c%%h%%n%%u%%f%%r%%d%%i\n", chan->name);
-    else
-      dprintf(queue, "WHO %s\n", chan->name);
+static void send_chan_who(int queue, struct chanset_t *chan, bool chain) {
+  if (chain) {
+    if (!chained_who.contains(chan->name))
+      chained_who.enqueue(chan->name);
+    chained_who_idx = queue;
+    if (chained_who.size() > 1)
+      return;
+  }
+  if (use_354) /* Added benefit of getting numeric IP! :) */
+    dprintf(queue, "WHO %s %%c%%h%%n%%u%%f%%r%%d%%i\n", chan->name);
+  else
+    dprintf(queue, "WHO %s\n", chan->name);
 }
 
 void force_join_chan(struct chanset_t* chan, int idx) {

+ 2 - 2
src/mod/irc.mod/irc.h

@@ -66,7 +66,6 @@ static void cache_invite(struct chanset_t *, char *, char *, char *, bool, bool)
 void makecookie(char*, size_t, const char *, const memberlist*, const memberlist*, const memberlist* = NULL, const memberlist* = NULL);
 static int checkcookie(const char*, const memberlist*, const memberlist*, const char*, int);
 extern void counter_clear(const char*);
-static bool me_voice(struct chanset_t *);
 static bool any_ops(struct chanset_t *);
 static char *getchanmode(struct chanset_t *);
 static void flush_mode(struct chanset_t *, int);
@@ -97,7 +96,7 @@ static void check_lonely_channel(struct chanset_t *chan);
 static int gotmode(char *, char *);
 void unset_im(struct chanset_t* chan);
 void detected_drone_flood(struct chanset_t* chan, memberlist*);
-static void send_chan_who(int queue, struct chanset_t* chan);
+static void send_chan_who(int queue, struct chanset_t* chan, bool chain = 0);
 #define newban(chan, mask, who)         new_mask((chan)->channel.ban, mask, who)
 #define newexempt(chan, mask, who)      new_mask((chan)->channel.exempt, mask, who)
 #define newinvite(chan, mask, who)      new_mask((chan)->channel.invite, mask, who)
@@ -119,6 +118,7 @@ void real_add_mode(struct chanset_t *, const char, const char, const char *, boo
 #define add_mode(chan, pls, mode, nick) real_add_mode(chan, pls, mode, nick, 0)
 #define add_cookie(chan, nick) real_add_mode(chan, '+', 'o', nick, 1)
 bool me_op(struct chanset_t *);
+bool me_voice(struct chanset_t *);
 
 void check_this_ban(struct chanset_t *, char *, bool);
 void check_this_exempt(struct chanset_t *, char *, bool);

+ 62 - 35
src/mod/irc.mod/msgcmds.c

@@ -39,9 +39,12 @@ static int msg_bewm(char *nick, char *host, struct userrec *u, char *par)
 
   if (match_my_nick(nick))
     return BIND_RET_BREAK;
+
+  bd::String msg;
+
   if (!u) {
-    dprintf(DP_SERVER, STR("PRIVMSG %s :---- (%s!%s) attempted to gain secure invite, but is not a recognized user.\n"), 
-          chan->dname, nick, host);
+    msg.printf(STR("---- (%s!%s) attempted to gain secure invite, but is not a recognized user."), nick, host);
+    privmsg(chan->name, msg.c_str(), DP_SERVER);
 
     putlog(LOG_CMDS, "*", STR("(%s!%s) !*! BEWM"), nick, host);
     return BIND_RET_BREAK;
@@ -55,13 +58,13 @@ static int msg_bewm(char *nick, char *host, struct userrec *u, char *par)
 
   if (!chk_op(fr, chan))  {
     putlog(LOG_CMDS, "*", STR("(%s!%s) !%s! !BEWM"), nick, host, u->handle);
-    dprintf(DP_SERVER, STR("PRIVMSG %s :---- %s (%s!%s) attempted to gain secure invite, but is missing a flag.\n"), 
-            chan->dname, u->handle, nick, host);
+    msg.printf(STR("---- %s (%s!%s) attempted to gain secure invite, but is missing a flag."), u->handle, nick, host);
+    privmsg(chan->name, msg.c_str(), DP_SERVER);
     return BIND_RET_BREAK;
   }
 
-  dprintf(DP_SERVER, "PRIVMSG %s :\001ACTION has invited \002%s\002 (%s!%s) to %s.\001\n"
-           , chan->dname, u->handle, nick, host, chan->dname);
+  msg.printf("\001ACTION has invited \002%s\002 (%s!%s) to %s.\001", u->handle, nick, host, chan->dname);
+  privmsg(chan->name, msg.c_str(), DP_SERVER);
 
   cache_invite(chan, nick, host, u->handle, 0, 0);
   putlog(LOG_CMDS, "*", STR("(%s!%s) !%s! BEWM"), nick, host, u->handle);
@@ -82,20 +85,20 @@ static int msg_pass(char *nick, char *host, struct userrec *u, char *par)
   if (u->bot)
     return BIND_RET_BREAK;
   if (!par[0]) {
-    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, u_pass_match(u, "-") ? "You don't have a password set." : "You have a password set.");
+    notice(nick, u_pass_match(u, "-") ? "You don't have a password set." : "You have a password set.", DP_HELP);
     putlog(LOG_CMDS, "*", "(%s!%s) !%s! PASS?", nick, host, u->handle);
     return BIND_RET_BREAK;
   }
   old = newsplit(&par);
   if (!u_pass_match(u, "-") && !par[0]) {
     putlog(LOG_CMDS, "*", "(%s!%s) !%s! $b!$bPASS...", nick, host, u->handle);
-    dprintf(DP_HELP, "NOTICE %s :You already have a password set.\n", nick);
+    notice(nick, "You already have a password set.", DP_HELP);
     return BIND_RET_BREAK;
   }
   if (par[0]) {
     if (!u_pass_match(u, old)) {
       putlog(LOG_CMDS, "*", "(%s!%s) !%s! $b!$bPASS...", nick, host, u->handle);
-      dprintf(DP_HELP, "NOTICE %s :Incorrect password.\n", nick);
+      notice(nick, "Incorrect password.", DP_HELP);
       return BIND_RET_BREAK;
     }
     mynew = newsplit(&par);
@@ -113,8 +116,9 @@ static int msg_pass(char *nick, char *host, struct userrec *u, char *par)
   putlog(LOG_CMDS, "*", "(%s!%s) !%s! PASS", nick, host, u->handle);
 
   set_user(&USERENTRY_PASS, u, mynew);
-  dprintf(DP_HELP, "NOTICE %s :%s '%s'.\n", nick,
-	  mynew == old ? "Password set to:" : "Password changed to:", mynew);
+  bd::String msg;
+  msg.printf("%s '%s'.", mynew == old ? "Password set to:" : "Password changed to:", mynew);
+  notice(nick, msg.c_str(), DP_HELP);
   return BIND_RET_BREAK;
 }
 
@@ -128,6 +132,8 @@ static int msg_op(char *nick, char *host, struct userrec *u, char *par)
     return BIND_RET_BREAK;
   pass = newsplit(&par);
 
+  bd::String msg;
+
   if (homechan[0]) {
     struct chanset_t *hchan = NULL;
 
@@ -136,11 +142,10 @@ static int msg_op(char *nick, char *host, struct userrec *u, char *par)
     if (hchan && channel_active(hchan) && !ismember(hchan, nick)) {
       putlog(LOG_CMDS, "*", "(%s!%s) !*! failed OP %s (not in %s)", nick, host, par, homechan);
       if (par[0]) 
-        dprintf(DP_SERVER, "PRIVMSG %s :---- (%s!%s) attempted to OP for %s but is not currently in %s.\n", 
-              homechan, nick, host, par, homechan);
+        msg.printf("---- (%s!%s) attempted to OP for %s but is not currently in %s.", nick, host, par, homechan);
       else
-        dprintf(DP_SERVER, "PRIVMSG %s :---- (%s!%s) attempted to OP but is not currently in %s.\n", 
-              homechan, nick, host, homechan);
+        msg.printf("---- (%s!%s) attempted to OP but is not currently in %s.", nick, host, homechan);
+      privmsg(homechan, msg.c_str(), DP_SERVER);
       return BIND_RET_BREAK;
     }
   }
@@ -155,8 +160,10 @@ static int msg_op(char *nick, char *host, struct userrec *u, char *par)
             if (do_op(nick, chan, 0, 1)) {
               stats_add(u, 0, 1);
               putlog(LOG_CMDS, "*", "(%s!%s) !%s! OP %s", nick, host, u->handle, par);
-              if (manop_warn && chan->manop)
-                dprintf(DP_HELP, "NOTICE %s :%s is currently set to punish for manual op.\n", nick, chan->dname);
+              if (manop_warn && chan->manop) {
+                msg.printf("%s is currently set to punish for manual op.", chan->dname);
+                notice(nick, msg.c_str(), DP_HELP);
+              }
             }
           }
           return BIND_RET_BREAK;
@@ -168,8 +175,10 @@ static int msg_op(char *nick, char *host, struct userrec *u, char *par)
           if (chk_op(fr, chan)) {
             if (do_op(nick, chan, 0, 1)) {
               stats++;
-              if (manop_warn && chan->manop)
-                dprintf(DP_HELP, "NOTICE %s :%s is currently set to punish for manual op.\n", nick, chan->dname);
+              if (manop_warn && chan->manop) {
+                msg.printf("%s is currently set to punish for manual op.", chan->dname);
+                notice(nick, msg.c_str(), DP_HELP);
+              }
             }
           }
         }
@@ -198,6 +207,7 @@ static int msg_ident(char *nick, char *host, struct userrec *u, char *par)
   else {
     strlcpy(who, par, sizeof(who));
   }
+  bd::String msg;
   u2 = get_user_by_handle(userlist, who);
   if (u2 && rfc_casecmp(who, origbotname) && !u2->bot) {
     /* This could be used as detection... */
@@ -207,16 +217,18 @@ static int msg_ident(char *nick, char *host, struct userrec *u, char *par)
       putlog(LOG_CMDS, "*", "(%s!%s) !*! failed IDENT %s", nick, host, who);
       return BIND_RET_BREAK;
     } else if (u == u2) {
-      dprintf(DP_HELP, "NOTICE %s :I recognize you there.\n", nick);
+      notice(nick, "I recognize you there.", DP_HELP);
       return BIND_RET_BREAK;
     } else if (u) {
-      dprintf(DP_HELP, "NOTICE %s :You're not %s, you're %s.\n", nick, who, u->handle);
+      msg.printf("You're not %s, you're %s.", who, u->handle);
+      notice(nick, msg.c_str(), DP_HELP);
       return BIND_RET_BREAK;
     } else {
       putlog(LOG_CMDS, "*", "(%s!%s) !*! IDENT %s", nick, host, who);
       simple_snprintf(s, sizeof s, "%s!%s", nick, host);
       maskaddr(s, s1, 0); /* *!user@host */
-      dprintf(DP_HELP, "NOTICE %s :Added hostmask: %s\n", nick, s1);
+      msg.printf("Added hostmask: %s", s1);
+      notice(nick, msg.c_str(), DP_HELP);
       addhost_by_handle(who, s1);
       check_this_user(who, 0, NULL);
       return BIND_RET_BREAK;
@@ -247,12 +259,15 @@ static int msg_invite(char *nick, char *host, struct userrec *u, char *par)
       putlog(LOG_CMDS, "*", "(%s!%s) !%s! INVITE ALL", nick, host, u->handle);
       return BIND_RET_BREAK;
     }
+    bd::String msg;
     if (!(chan = findchan_by_dname(par))) {
-      dprintf(DP_HELP, "NOTICE %s :Usage: /MSG %s %s <pass> <channel>\n", nick, botname, msginvite);
+      msg.printf("Usage: /MSG %s %s <pass> <channel>", botname, msginvite);
+      notice(nick, msg.c_str(), DP_HELP);
       return BIND_RET_BREAK;
     }
     if (!channel_active(chan)) {
-      dprintf(DP_HELP, "NOTICE %s :%s: Not on that channel right now.\n", nick, par);
+      msg.printf("%s: Not on that channel right now.", par);
+      notice(nick, msg.c_str(), DP_HELP);
       return BIND_RET_BREAK;
     }
     /* We need to check access here also (dw 991002) */
@@ -279,9 +294,9 @@ static void reply(char *nick, struct chanset_t *chan, const char *format, ...)
   va_end(va);
 
   if (chan)
-    dprintf(DP_HELP, "PRIVMSG %s :%s", chan->name, buf);
+    privmsg(chan->name, buf, DP_HELP);
   else
-    dprintf(DP_HELP, "NOTICE %s :%s", nick, buf);
+    notice(nick, buf, DP_HELP);
 }
 
 static void logc(const char *cmd, Auth *a, char *chname, char *par)
@@ -313,7 +328,7 @@ static int msg_authstart(char *nick, char *host, struct userrec *u, char *par)
 
   if (auth) {
     if (auth->Authed()) {
-      dprintf(DP_HELP, STR("NOTICE %s :You are already authed.\n"), nick);
+      notice(nick, "You are already authed.", DP_HELP);
       return 0;
     }
   } else
@@ -321,7 +336,9 @@ static int msg_authstart(char *nick, char *host, struct userrec *u, char *par)
 
   /* Send "auth." if they are recognized, otherwise "auth!" */
   auth->Status(AUTH_PASS);
-  dprintf(DP_HELP, STR("PRIVMSG %s :auth%s %s\n"), nick, u ? "." : "!", conf.bot->nick);
+  bd::String msg;
+  msg.printf(STR("auth%s %s"), u ? "." : "!", conf.bot->nick);
+  privmsg(nick, msg.c_str(), DP_HELP);
 
   return BIND_RET_BREAK;
 }
@@ -331,7 +348,9 @@ AuthFinish(Auth *auth)
 {
   putlog(LOG_CMDS, "*", STR("(%s!%s) !%s! +AUTH"), auth->nick, auth->host, auth->handle);
   auth->Done();
-  dprintf(DP_HELP, STR("NOTICE %s :You are now authorized for cmds, see %chelp\n"), auth->nick, auth_prefix[0]);
+  bd::String msg;
+  msg.printf(STR("You are now authorized for cmds, see %chelp"), auth_prefix[0]);
+  notice(auth->nick, msg.c_str(), DP_HELP);
 }
 
 static int msg_auth(char *nick, char *host, struct userrec *u, char *par)
@@ -356,7 +375,9 @@ static int msg_auth(char *nick, char *host, struct userrec *u, char *par)
       putlog(LOG_CMDS, "*", STR("(%s!%s) !%s! AUTH"), nick, host, u->handle);
       auth->Status(AUTH_HASH);
       auth->MakeHash();
-      dprintf(DP_HELP, STR("PRIVMSG %s :-Auth %s %s\n"), nick, auth->rand, conf.bot->nick);
+      bd::String msg;
+      msg.printf(STR("-Auth %s %s"), auth->rand, conf.bot->nick);
+      privmsg(nick, msg.c_str(), DP_HELP);
     } else {
       /* no auth_key and/or no SECPASS for the user, don't require a hash auth */
       AuthFinish(auth);
@@ -388,7 +409,7 @@ static int msg_pls_auth(char *nick, char *host, struct userrec *u, char *par)
       char s[300] = "";
 
       putlog(LOG_CMDS, "*", STR("(%s!%s) !%s! failed +AUTH"), nick, host, u->handle);
-      dprintf(DP_HELP, STR("NOTICE %s :Invalid hash.\n"), nick);
+      notice(nick, STR("Invalid hash."), DP_HELP);
       simple_snprintf(s, sizeof(s), "*!%s", host);
       addignore(s, origbotname, STR("Invalid auth hash."), now + (60 * ignore_time));
       delete auth;
@@ -411,7 +432,7 @@ static int msg_unauth(char *nick, char *host, struct userrec *u, char *par)
     return BIND_RET_BREAK;
 
   delete auth;
-  dprintf(DP_HELP, STR("NOTICE %s :You are now unauthorized.\n"), nick);
+  notice(nick, STR("You are now unauthorized."), DP_HELP);
   putlog(LOG_CMDS, "*", STR("(%s!%s) !%s! UNAUTH"), nick, host, u->handle);
 
   return BIND_RET_BREAK;
@@ -509,7 +530,9 @@ static int msgc_op(Auth *a, char *chname, char *par)
     if (!strcasecmp(tmp, "force") || !strcasecmp(tmp, "f")) 
       force = 1;
     else {
-      dprintf(DP_HELP, "NOTICE %s :Invalid option: %s\n", a->nick, tmp);
+      bd::String msg;
+      msg.printf("Invalid option: %s", tmp);
+      notice(a->nick, msg.c_str(), DP_HELP);
       return 0;
     }
   }
@@ -558,7 +581,9 @@ static int msgc_voice(Auth *a, char *chname, char *par)
     if (!strcasecmp(tmp, "force") || !strcasecmp(tmp, "f")) 
       force = 1;
     else {
-      dprintf(DP_HELP, "NOTICE %s :Invalid option: %s\n", a->nick, tmp);
+      bd::String msg;
+      msg.printf("Invalid option: %s", tmp);
+      notice(a->nick, msg.c_str(), DP_HELP);
       return 0;
     }
   }
@@ -715,7 +740,9 @@ static int msgc_invite(Auth *a, char *chname, char *par)
     if (!strcasecmp(tmp, "force") || !strcasecmp(tmp, "f")) 
       force = 1;
     else {
-      dprintf(DP_HELP, "NOTICE %s :Invalid option: %s\n", a->nick, tmp);
+      bd::String msg;
+      msg.printf("Invalid option: %s", tmp);
+      notice(a->nick, msg.c_str(), DP_HELP);
       return 0;
     }
   }

+ 8 - 1
src/mod/server.mod/cmdsserv.c

@@ -115,10 +115,11 @@ static void cmd_clearqueue(int idx, char *par)
     return;
   }
   if (!strcasecmp(par, "all")) {
-    msgs = modeq.tot + mq.tot + hq.tot;
+    msgs = modeq.tot + mq.tot + hq.tot + aq.tot;
     msgq_clear(&modeq);
     msgq_clear(&mq);
     msgq_clear(&hq);
+    msgq_clear(&aq);
     burst = 0;
     double_warned = 0;
     dprintf(idx, "Removed %d message%s from all queues.\n", msgs, 
@@ -137,6 +138,12 @@ static void cmd_clearqueue(int idx, char *par)
     double_warned = 0;
     dprintf(idx, "Removed %d message%s from the help queue.\n", msgs,
         (msgs != 1) ? "s" : "");
+  } else if (!strcasecmp(par, "play")) {
+    msgs = aq.tot;
+    msgq_clear(&aq);
+    double_warned = 0;
+    dprintf(idx, "Removed %d message%s from the play queue.\n", msgs,
+        (msgs != 1) ? "s" : "");
   } else if (!strcasecmp(par, "server")) {
     msgs = mq.tot;
     msgq_clear(&mq);

+ 301 - 253
src/mod/server.mod/server.c

@@ -89,28 +89,32 @@ interval_t cycle_time;			/* cycle time till next server connect */
 port_t default_port = 6667;		/* default IRC port */
 bool trigger_on_ignore;	/* trigger bindings if user is ignored ? */
 int answer_ctcp = 1;		/* answer how many stacked ctcp's ? */
-static int net_type = NETT_EFNET;
 static bool resolvserv;		/* in the process of resolving a server host */
 static time_t lastpingtime;	/* IRCNet LAGmeter support -- drummer */
 static char stackablecmds[511] = "";
 static char stackable2cmds[511] = "";
-static time_t last_time;
-static bool use_penalties = 0;
+static egg_timeval_t last_time;
+static time_t connect_bursting = 0;
+static int real_msgburst = 0;
+static int real_msgrate = 0;
+static int flood_count = 0;
+static bool use_flood_count = 0;
+static egg_timeval_t flood_time = {0, 0};
+static bool use_penalties;
 static int use_fastdeq;
 size_t nick_len = 9;			/* Maximal nick length allowed on the network. */
 char deaf_char = 0;
 bool in_deaf = 0;
 char callerid_char = 0;
 bool in_callerid = 0;
+bool have_cprivmsg = 0;
+bool have_cnotice = 0;
 
-static bool double_mode = 0;		/* allow a msgs to be twice in a queue? */
-static bool double_server = 0;
-static bool double_help = 0;
 static bool double_warned = 0;
 
 static void empty_msgq(void);
 static void disconnect_server(int, int);
-static int calc_penalty(char *);
+static void calc_penalty(char *, size_t);
 static bool fast_deq(int);
 static char *splitnicks(char **);
 static void msgq_clear(struct msgq_head *qh);
@@ -120,20 +124,41 @@ static bool replaying_cache = 0;
 /* New bind tables. */
 static bind_table_t *BT_raw = NULL, *BT_msg = NULL;
 bind_table_t *BT_ctcr = NULL, *BT_ctcp = NULL, *BT_msgc = NULL;
+// Ratbox is (5*8):30, ircd-seven is (5*8):20, try to not push th elimits.
+#define SERVER_CONNECT_BURST_TIME 18
+#define SERVER_CONNECT_BURST_RATE 5 * 7
 
 #include "servmsg.c"
 
 #define MAXPENALTY 10
 
-/* Number of seconds to wait between transmitting queued lines to the server
- * lower this value at your own risk.  ircd is known to start flood control
- * at 512 bytes/2 seconds.
- */
-#define msgrate 2
+// If use_flood_count, don't bother with msgrate, otherwise use the user specified msgrate
+#define MSGRATE (use_flood_count ? DEQ_RATE : msgrate)
 
 /* Maximum messages to store in each queue. */
-static int maxqmsg = 300;
-static struct msgq_head mq, hq, modeq, cacheq;
+static struct msgq_head mq, hq, modeq, aq, cacheq;
+
+static const struct {
+  struct msgq_head* const q;
+  const int idx;
+  const char* name;
+  const char pfx;
+  const bool double_msg;
+  const bool burst;
+  const bool connect_burst;
+  const size_t maxqmsg;
+} qdsc[5] = {
+  { &modeq, 	DP_MODE,	"MODE", 	'm',	0,	1, 	1,	300 },
+  { &mq, 	DP_SERVER,	"SERVER", 	's',	0,	1,	1,	300 },
+  { &hq, 	DP_HELP,	"HELP", 	'h',	0,	0,	0,	300 },
+  { &aq, 	DP_PLAY,	"PLAY", 	'p',	1,	1,	0,	10000 },
+  { &cacheq, 	DP_CACHE,	"CACHE", 	'c',	0,	0,	0,	1000 },
+};
+#define Q_MODE 0
+#define Q_SERVER 1
+#define Q_HELP 2
+#define Q_PLAY 3
+#define Q_CACHE 4
 static int burst;
 
 #include "cmdsserv.c"
@@ -142,6 +167,55 @@ static int burst;
 /*
  *     Bot server queues
  */
+static bool burst_mode_ok(const char *msg, size_t len) {
+  bd::String mode(msg, len);
+  bd::Array<bd::String> list(mode.split(' '));
+  if (list.length() == 2) return 1;
+  if (list.length() == 3) {
+    if (!strchr(CHANMETA, bd::String(list[1]).at(0))) return 1;
+    else if (bd::String(list[2]).at(0) == 'b') return 1;
+  }
+  return 0;
+}
+
+// Hybrid/ratbox allows bursting 5*8 lines on connect until certain commands are sent, for up to 30 seconds
+/*
+   BAD:
+     JOIN 0
+     MODE #chan b
+     NICK
+     PART
+     KICK
+     CPRIVMSG
+     CNOTICE
+     WHO 0/mask
+     TIME
+     TOPIC
+     INVITE
+     AWAY
+     OPER
+
+   OK:
+     WHO *
+     WHO !
+     WHO #Chan
+     WHO NICK
+*/
+static bool burst_ok(const char* msg, size_t len) {
+  if (strstr(msg, "JOIN 0") ||
+      (strstr(msg, "MODE") && !burst_mode_ok(msg, len)) ||
+      strstr(msg, "NICK") ||
+      strstr(msg, "PRIVMSG") ||
+      strstr(msg, "NOTICE") ||
+      strstr(msg, "PART") ||
+      strstr(msg, "KICK") ||
+      strstr(msg, "INVITE") ||
+      strstr(msg, "AWAY")) {
+    sdprintf("BURST MODE VIOLATION!!!: %s\n", msg);
+    return 0;
+  }
+  return 1;
+}
 
 /* Called periodically to shove out another queued item.
  *
@@ -152,93 +226,129 @@ static int burst;
  * Will send upto 4 msgs from modeq, and then send 1 msg every time
  * it will *not* send anything from hq until the 'burst' value drops
  * down to 0 again (allowing a sudden mq flood to sneak through).
+ *
+ * ratbox:
+ * Every msg sent is added to a count, every second this count decreases by 2.
+ * Typical max count is 20, then excess flood is triggered.
+ * So after 1 bursts:
+ * Count = 5
+ * Decaying by 1 every second
  */
-static void deq_msg()
+void deq_msg()
 {
-  bool ok = 0;
+  if (serv < 0)
+    return;
 
-  /* now < last_time tested 'cause clock adjustments could mess it up */
-  if ((now - last_time) >= msgrate || now < (last_time - 90)) {
-    last_time = now;
-    if (burst > 0)
-      burst--;
-    ok = 1;
+  if (timeval_diff(&egg_timeval_now, &flood_time) >= 1000) {
+    // Increase flood_count by 1 every msg, but decrease by 2 every second, use this to determine an acceptable burst rate
+    if (flood_count > 1)
+      flood_count -= 2;
+    else if (flood_count == 1)
+      flood_count = 0;
+
+    flood_time.sec = egg_timeval_now.sec;
+    flood_time.usec = egg_timeval_now.usec;
   }
-  if (serv < 0)
+
+  /* now < last_time tested 'cause clock adjustments could mess it up */
+  if (timeval_diff(&egg_timeval_now, &last_time) >= MSGRATE || now < (last_time.sec - 90)) {
+    last_time.sec = egg_timeval_now.sec;
+    last_time.usec = egg_timeval_now.usec;
+
+    if (burst > 0) {
+      if (use_flood_count) {
+        if (flood_count < 5)
+          burst = 0;
+        else if (flood_count < 10)
+          burst -= 4;
+        else if (flood_count < 13)
+          burst -= 3;
+        else if (flood_count < 15)
+          burst -= 2;
+        else
+          --burst;
+        if (burst < 0)
+          burst = 0;
+      } else
+        --burst;
+    }
+  } else
     return;
 
   struct msgq *q = NULL;
 
-  /* Send upto 4 msgs to server if the *critical queue* has anything in it */
-  if (modeq.head) {
-    while (modeq.head && (burst < 4) && ((last_time - now) < MAXPENALTY)) {
-      if (!modeq.head)
-        break;
-      if (fast_deq(DP_MODE)) {
-        burst++;
+  /* Send upto 'set msgburst' msgs to server if the *critical queue* has anything in it;
+   * otherwise, dequeue and burst up to 'set msgburst' messages from the `normal' message
+   * queue.
+   */
+  egg_timeval_t last_time_save = { last_time.sec, last_time.usec };
+  bool bursted = 0;
+  // -1 here to avoid DP_CACHE
+  for(size_t nq = 0; nq < (sizeof(qdsc) / sizeof(qdsc[0])) - 1; ++nq) {
+    while (qdsc[nq].q->head &&
+        // If burstable queue and can burst, or not a burstable queue and not connect bursting
+        ((qdsc[nq].burst && (burst < msgburst)) || (!qdsc[nq].burst && !connect_bursting)) &&
+        ((last_time.sec - now) < MAXPENALTY)) {
+#ifdef not_implemented
+      if (deq_kick(qdsc[nq].idx)) {
+        ++burst;++flood_count;
         continue;
       }
-      write_to_server(modeq.head->msg, modeq.head->len);
+#endif
+      if (fast_deq(nq))
+        continue;
+      write_to_server(qdsc[nq].q->head->msg, qdsc[nq].q->head->len);
+      ++burst;++flood_count;
       if (debug_output)
-        putlog(LOG_SRVOUT, "@", "[m->] %s", modeq.head->msg);
-      modeq.tot--;
-      last_time += calc_penalty(modeq.head->msg);
-      q = modeq.head->next;
-      free(modeq.head->msg);
-      free(modeq.head);
-      modeq.head = q;
-      burst++;
+        putlog(LOG_SRVOUT, "*", "[%c->] %s", qdsc[nq].pfx, qdsc[nq].q->head->msg);
+      --(qdsc[nq].q->tot);
+      calc_penalty(qdsc[nq].q->head->msg, qdsc[nq].q->head->len);
+      q = qdsc[nq].q->head->next;
+      free(qdsc[nq].q->head->msg);
+      free(qdsc[nq].q->head);
+      qdsc[nq].q->head = q;
+      if (qdsc[nq].burst)
+        bursted = 1;
+      else // Help Queue does not burst, push out 1 line then go to next queue.
+        break;
     }
-    if (!modeq.head)
-      modeq.last = 0;
-    return;
+    if (!qdsc[nq].q->head)
+      qdsc[nq].q->last = NULL;
   }
-  /* Send something from the normal msg q even if we're slightly bursting */
-  if (burst > 1)
-    return;
-  if (mq.head) {
-    burst++;
-    if (fast_deq(DP_SERVER))
-      return;
-    write_to_server(mq.head->msg, mq.head->len);
-    if (debug_output) {
-      putlog(LOG_SRVOUT, "@", "[s->] %s", mq.head->msg);
+
+  // Do this penalty calc here as it's dependant on burst/flood_count
+  if (use_flood_count && !connect_bursting) {
+    // The penalty includes a length-based penalty from calc_penalty
+
+    last_time.sec -= (MSGRATE / 1000); // Remove normal msgrate
+    // Add 150ms for each current burst
+    last_time.usec += (150*burst) * 1000;
+    // Add some penalty for each flood_count
+    last_time.usec += (40*flood_count) * 1000;
+    // Cap the penalty at 1800 and depent more on flood_count
+    if (timeval_diff(&last_time, &last_time_save) > 1800) {
+      last_time.sec = last_time_save.sec;
+      last_time.usec = 1800 * 1000;
     }
-    mq.tot--;
-    last_time += calc_penalty(mq.head->msg);
-    q = mq.head->next;
-    free(mq.head->msg);
-    free(mq.head);
-    mq.head = q;
-    if (!mq.head)
-      mq.last = NULL;
-    return;
-  }
-  /* Never send anything from the help queue unless everything else is
-   * finished.
-   */
-  if (!hq.head || burst || !ok)
-    return;
-  if (fast_deq(DP_HELP))
-    return;
-  write_to_server(hq.head->msg, hq.head->len);
-  if (debug_output) {
-    putlog(LOG_SRVOUT, "@", "[h->] %s", hq.head->msg);
+    // If lagging, raise the penalty up to avoid TCP burst/excess flood
+    if (server_lag > 5)
+      last_time.sec += 2;
+#ifdef DEBUG
+    if (timeval_diff(&last_time, &last_time_save))
+      sdprintf("PENALTY (%d): %lims", flood_count, timeval_diff(&last_time, &last_time_save));
+#endif
   }
-  hq.tot--;
-  last_time += calc_penalty(hq.head->msg);
-  q = hq.head->next;
-  free(hq.head->msg);
-  free(hq.head);
-  hq.head = q;
-  if (!hq.head)
-    hq.last = NULL;
+#ifdef DEBUG
+  else if (connect_bursting && bursted)
+    sdprintf("BURSTING!!!!!\n");
+#endif
+
 }
 
-static int calc_penalty(char * msg)
+static void calc_penalty(char * msg, size_t len)
 {
-  if (!use_penalties && net_type != NETT_UNDERNET && net_type != NETT_HYBRID_EFNET)
-    return 0;
+  if (connect_bursting)
+    return;
 
   char *cmd = NULL, *par1 = NULL, *par2 = NULL, *par3 = NULL;
   register int penalty, i, ii;
@@ -248,11 +358,17 @@ static int calc_penalty(char * msg)
     i = strlen(msg);
   else
     i = strlen(cmd);
-  last_time -= 2; /* undo eggdrop standard flood prot */
-  if (net_type == NETT_UNDERNET || net_type == NETT_HYBRID_EFNET) {
-    last_time += (2 + i / 120);
-    return 0;
+  if (!use_penalties) {
+    // Add some penalty for large messages
+    if (use_flood_count)
+      last_time.usec += long(((double)i / 300.0) * (1000*1000));
+    else
+      last_time.usec += long(((double)i / 120.0) * (1000*1000));
+    return;
   }
+
+  last_time.sec -= (MSGRATE / 1000); // Remove normal msgrate
+
   penalty = (1 + i / 100);
   if (!strcasecmp(cmd, "KICK")) {
     par1 = newsplit(&msg); /* channel */
@@ -361,7 +477,7 @@ static int calc_penalty(char * msg)
   }
   if (debug_output && penalty != 0)
     putlog(LOG_SRVOUT, "*", "Adding penalty: %i", penalty);
-  return penalty;
+  last_time.sec += penalty;
 }
 
 char *splitnicks(char **rest)
@@ -410,27 +526,16 @@ static bool fast_deq(int which)
   if (!use_fastdeq)
     return 0;
 
-  struct msgq_head *h = NULL;
+  struct msgq_head *h = qdsc[which].q;
   struct msgq *m = NULL, *nm = NULL;
-  char msgstr[511] = "", nextmsgstr[511] = "", tosend[511] = "", victims[511] = "", stackable[511] = "",
+  char msgstr[511] = "", nextmsgstr[511] = "", tosend[511] = "", stackable[511] = "",
        *msg = NULL, *nextmsg = NULL, *cmd = NULL, *nextcmd = NULL, *to = NULL, *nextto = NULL, *stckbl = NULL;
-  int cmd_count = 0, stack_method = 1;
+  int cmd_count = 0;
+  char stack_delim = ',';
   size_t len;
   bool found = 0, doit = 0;
+  bd::String victims;
 
-  switch (which) {
-    case DP_MODE:
-      h = &modeq;
-      break;
-    case DP_SERVER:
-      h = &mq;
-      break;
-    case DP_HELP:
-      h = &hq;
-      break;
-    default:
-      return 0;
-  }
   m = h->head;
   strlcpy(msgstr, m->msg, sizeof msgstr);
   msg = msgstr;
@@ -456,13 +561,13 @@ static bool fast_deq(int which)
     stckbl = stackable;
     while (strlen(stckbl) > 0)
       if (!strcasecmp(newsplit(&stckbl), cmd)) {
-        stack_method = 2;
+        stack_delim = ' ';
         break;
       }    
   }
   to = newsplit(&msg);
   len = strlen(to);
-  strlcpy(victims, to, sizeof(victims));
+  victims = to;
   while (m) {
     nm = m->next;
     if (!nm)
@@ -474,15 +579,11 @@ static bool fast_deq(int which)
     len = strlen(nextto);
     if ( strcmp(to, nextto) /* we don't stack to the same recipients */
         && !strcmp(cmd, nextcmd) && !strcmp(msg, nextmsg)
-        && ((strlen(cmd) + strlen(victims) + strlen(nextto)
+        && ((strlen(cmd) + victims.length() + strlen(nextto)
 	     + strlen(msg) + 2) < 510)
         && (!stack_limit || cmd_count < stack_limit - 1)) {
-      cmd_count++;
-      if (stack_method == 1)
-        strlcat(victims, ",", sizeof(victims));
-      else
-        strlcat(victims, " ", sizeof(victims));
-      strlcat(victims, nextto, sizeof(victims));
+      ++cmd_count;
+      victims += stack_delim + nextto;
 
       doit = 1;
       m->next = nm->next;
@@ -490,35 +591,26 @@ static bool fast_deq(int which)
         h->last = m;
       free(nm->msg);
       free(nm);
-      h->tot--;
+      --(h->tot);
     } else
       m = m->next;
   }
   if (doit) {
-    simple_snprintf(tosend, sizeof(tosend), "%s %s %s", cmd, victims, msg);
-    len = strlen(tosend);
+    len = simple_snprintf(tosend, sizeof(tosend), "%s %s %s", cmd, victims.c_str(), msg);
     write_to_server(tosend, len);
+    ++burst;++flood_count;
     m = h->head->next;
     free(h->head->msg);
     free(h->head);
     h->head = m;
     if (!h->head)
       h->last = 0;
-    h->tot--;
-    if (debug_output) {
-      switch (which) {
-        case DP_MODE:
-          putlog(LOG_SRVOUT, "*", "[m=>] %s", tosend);
-          break;
-        case DP_SERVER:
-          putlog(LOG_SRVOUT, "*", "[s=>] %s", tosend);
-          break;
-        case DP_HELP:
-          putlog(LOG_SRVOUT, "*", "[h=>] %s", tosend);
-          break;
-      }
-    }
-    last_time += calc_penalty(tosend);
+    --(h->tot);
+
+    if (debug_output)
+      putlog(LOG_SRVOUT, "*", "[%c=>] %s", qdsc[which].pfx, tosend);
+
+    calc_penalty(tosend, len);
     return 1;
   }
   return 0;
@@ -528,11 +620,11 @@ static bool fast_deq(int which)
  */
 static void empty_msgq()
 {
-  msgq_clear(&modeq);
-  msgq_clear(&mq);
-  msgq_clear(&hq);
-  msgq_clear(&cacheq);
+  for (size_t i = 0; i < (sizeof(qdsc) / sizeof(qdsc[0])); ++i)
+    msgq_clear(qdsc[i].q);
   burst = 0;
+  flood_count = 0;
+  flood_time.sec = flood_time.usec = 0;
 }
 
 /* Use when sending msgs... will spread them out so there's no flooding.
@@ -543,65 +635,53 @@ void queue_server(int which, char *buf, int len)
   if (serv < 0)
     return;
 
-  struct msgq_head *h = NULL, tempq;
-  struct msgq *q = NULL;
+  // If connect bursting, hold off any commands which would end the gracetime (flood_endgrace)
+  if (connect_bursting && (which == DP_MODE || which == DP_MODE_NEXT || which == DP_SERVER || which == DP_SERVER_NEXT)) {
+    if (!burst_ok(buf, len))
+      which = DP_HELP;
+  }
+
   int qnext = 0;
-  bool doublemsg = 0;
+  int which_q = 0;
 
   switch (which) {
-  case DP_MODE_NEXT:
-    qnext = 1;
-    /* Fallthrough */
-  case DP_MODE:
-    h = &modeq;
-    tempq = modeq;
-    if (double_mode)
-      doublemsg = 1;
-    break;
-
-  case DP_SERVER_NEXT:
-    qnext = 1;
-    /* Fallthrough */
-  case DP_SERVER:
-    h = &mq;
-    tempq = mq;
-    if (double_server)
-      doublemsg = 1;
-    break;
-
-  case DP_HELP_NEXT:
-    qnext = 1;
-    /* Fallthrough */
-  case DP_HELP:
-    h = &hq;
-    tempq = hq;
-    if (double_help)
-      doublemsg = 1;
-    break;
-
-  case DP_CACHE:
-    h = &cacheq;
-    tempq = cacheq;
-    doublemsg = 0;
-    break;
-
-  default:
-    putlog(LOG_MISC, "*", "!!! queuing unknown type to server!!");
-    return;
+    case DP_MODE_NEXT:
+      qnext = 1;
+    case DP_MODE:
+      which_q = Q_MODE;
+      break;
+    case DP_SERVER_NEXT:
+      qnext = 1;
+    case DP_SERVER:
+      which_q = Q_SERVER;
+      break;
+    case DP_HELP_NEXT:
+      qnext = 1;
+    case DP_HELP:
+      which_q = Q_HELP;
+      break;
+    case DP_PLAY:
+      which_q = Q_PLAY;
+      break;
+    case DP_CACHE:
+      which_q = Q_CACHE;
+      break;
+    default:
+      putlog(LOG_MISC, "*", "!!! queuing unknown type to server!!");
+      return;
   }
 
-  if (likely(h->tot < maxqmsg)) {
-    /* Don't queue msg if it's already queued?  */
-    if (!doublemsg) {
-      struct msgq *tq = NULL, *tqq = NULL;
+  struct msgq_head *h = qdsc[which_q].q;
 
-      for (tq = tempq.head; tq; tq = tqq) {
-	tqq = tq->next;
+  if (h->tot < qdsc[which_q].maxqmsg) {
+    /* Don't queue msg if it's already queued?  */
+    if (!qdsc[which_q].double_msg) {
+      for (struct msgq* tq = qdsc[which_q].q->head; tq; tq = tq->next) {
 	if (!strcasecmp(tq->msg, buf)) {
 	  if (!double_warned) {
 	    if (buf[len - 1] == '\n')
 	      buf[len - 1] = 0;
-	    debug1("msg already queued. skipping: %s", buf);
+	    putlog(LOG_DEBUG, "*", "msg already queued. skipping: %s", buf);
 	    double_warned = 1;
 	  }
 	  return;
@@ -609,7 +689,7 @@ void queue_server(int which, char *buf, int len)
       }
     }
 
-    q = (struct msgq *) my_calloc(1, sizeof(struct msgq));
+    struct msgq *q = (struct msgq *) my_calloc(1, sizeof(struct msgq));
     if (qnext)
       q->next = h->head;
     else
@@ -629,61 +709,16 @@ void queue_server(int which, char *buf, int len)
     h->warned = 0;
     double_warned = 0;
   } else {
-    if (!h->warned) {
-      switch (which) {   
-	case DP_MODE_NEXT:
- 	/* Fallthrough */
-	case DP_MODE:
-      putlog(LOG_MISC, "*", "!!! OVER MAXIMUM MODE QUEUE");
- 	break;
-    
-	case DP_SERVER_NEXT:
- 	/* Fallthrough */
- 	case DP_SERVER:
-	putlog(LOG_MISC, "*", "!!! OVER MAXIMUM SERVER QUEUE");
-	break;
-            
-	case DP_HELP_NEXT:
-	/* Fallthrough */
-	case DP_HELP:
-	putlog(LOG_MISC, "*", "!!! OVER MAXIMUM HELP QUEUE");
-	break;
-      }
-    }
+    if (!h->warned)
+      putlog(LOG_MISC, "*", "!!! OVER MAXIMUM %s QUEUE", qdsc[which_q].name);
     h->warned = 1;
   }
 
-  if (debug_output && !h->warned) {
-    switch (which) {
-    case DP_MODE:
-      putlog(LOG_SRVOUT, "@", "[!m] %s", buf);
-      break;
-    case DP_SERVER:
-      putlog(LOG_SRVOUT, "@", "[!s] %s", buf);
-      break;
-    case DP_HELP:
-      putlog(LOG_SRVOUT, "@", "[!h] %s", buf);
-      break;
-    case DP_MODE_NEXT:
-      putlog(LOG_SRVOUT, "@", "[!!m] %s", buf);
-      break;
-    case DP_SERVER_NEXT:
-      putlog(LOG_SRVOUT, "@", "[!!s] %s", buf);
-      break;
-    case DP_HELP_NEXT:
-      putlog(LOG_SRVOUT, "@", "[!!h] %s", buf);
-      break;
-#ifdef DEBUG
-    case DP_CACHE:
-      sdprintf("CACHE: %s", buf);
-      break;
-#endif
-    }
-  }
+  if (debug_output && !h->warned)
+    putlog(LOG_SRVOUT, "@", "[%s%c] %s", qnext ? "!!" : "!", qdsc[which_q].pfx, buf);
 
-  if (which == DP_MODE || which == DP_MODE_NEXT)
-    deq_msg();		/* DP_MODE needs to be sent ASAP, flush if
-			   possible. */
+  /* Try flushing immediately */
+  deq_msg();
 }
 
 /* Add a new server to the server_list.
@@ -956,11 +991,18 @@ static void dcc_chat_hostresolved(int i)
  *     Server timer functions
  */
 
+static void end_burstmode() {
+  if (connect_bursting) {
+    connect_bursting = 0;
+    msgburst = real_msgburst;
+    msgrate = real_msgrate;
+  }
+}
+
 static void server_secondly()
 {
   if (cycle_time)
     --cycle_time;
-  deq_msg();
   if (!resolvserv && serv < 0 && !trying_server)
     connect_server();
 
@@ -1007,13 +1049,17 @@ static void server_secondly()
       } else
         ++cnt_10;
     }
+    if (connect_bursting && (now - SERVER_CONNECT_BURST_TIME) >= connect_bursting) {
+      end_burstmode();
+      putlog(LOG_DEBUG, "*", "Ending server burst mode");
+    }
   }
 }
 
 static void server_check_lag()
 {
   if (server_online && !waiting_for_awake && !trying_server) {
-    dprintf(DP_DUMP, "PING :%li\n", (long)now);
+    dprintf(DP_MODE_NEXT, "PING :%li\n", (long)now);
     lastpingtime = now;
     waiting_for_awake = 1;
   } else if (servidx != -1 && waiting_for_awake && ((now - lastpingtime) >= stoned_timeout)) {
@@ -1064,16 +1110,17 @@ void server_report(int idx, int details)
 	    trying_server ? "(trying)" : s);
   } else
     dprintf(idx, "    No server currently.\n");
-  if (modeq.tot)
-    dprintf(idx, "    Mode queue is at %d%%, %d msgs\n",
-            (int) ((float) (modeq.tot * 100.0) / (float) maxqmsg),
-	    (int) modeq.tot);
-  if (mq.tot)
-    dprintf(idx, "    Server queue is at %d%%, %d msgs\n",
-           (int) ((float) (mq.tot * 100.0) / (float) maxqmsg), (int) mq.tot);
-  if (hq.tot)
-    dprintf(idx, "    Help queue is at %d%%, %d msgs\n",
-           (int) ((float) (hq.tot * 100.0) / (float) maxqmsg), (int) hq.tot);
+
+  if (server_online)
+    dprintf(idx, "    burst: %d flood_count: %d\n", burst, flood_count);
+
+  for (size_t i = 0; i < (sizeof(qdsc) / sizeof(qdsc[0])); ++i) {
+    if (qdsc[i].q->tot)
+      dprintf(idx, "    %s queue is at %d%%, %d msgs\n",
+          qdsc[i].name,
+          (int) ((float) (qdsc[i].q->tot * 100.0) / (float) qdsc[i].maxqmsg),
+          (int) qdsc[i].q->tot);
+  }
   if (details) {
     dprintf(idx, "    Flood is: %d msg/%ds, %d ctcp/%ds\n",
 	    flood_msg.count, flood_msg.time, flood_ctcp.count, flood_ctcp.time);
@@ -1103,11 +1150,6 @@ void server_init()
 {
   strlcpy(botrealname, "A deranged product of evil coders", sizeof(botrealname));
 
-  mq.head = hq.head = modeq.head = NULL;
-  mq.last = hq.last = modeq.last = NULL;
-  mq.tot = hq.tot = modeq.tot = 0;
-  mq.warned = hq.warned = modeq.warned = 0;
-
   /*
    * Init of all the variables *must* be done in _start rather than
    * globally.
@@ -1123,6 +1165,12 @@ void server_init()
   add_builtins("dcc", C_dcc_serv);
   add_builtins("ctcp", my_ctcps);
 
+  egg_timeval_t howlong;
+
+  howlong.sec = 0;
+  howlong.usec = DEQ_RATE * 1000;
+
+  timer_create_repeater(&howlong, "server_queue", (Function) deq_msg);
   timer_create_secs(1, "server_secondly", (Function) server_secondly);
   timer_create_secs(30, "server_check_lag", (Function) server_check_lag);
 //  timer_create_secs(60, "minutely_checks", (Function) minutely_checks);

+ 2 - 1
src/mod/server.mod/server.h

@@ -12,6 +12,7 @@
 
 #define DO_LOST 1
 #define NO_LOST 0
+#define DEQ_RATE 200
 
 #define fixcolon(x)             do {                                    \
         if ((x)[0] == ':')                                              \
@@ -46,7 +47,7 @@ enum {
 
 extern bind_table_t	*BT_ctcp, *BT_ctcr, *BT_msgc;
 extern size_t		nick_len;
-extern bool		trigger_on_ignore, floodless, keepnick, in_deaf, in_callerid;
+extern bool		trigger_on_ignore, floodless, keepnick, in_deaf, in_callerid, have_cprivmsg, have_cnotice;
 extern int 		servidx, ctcp_mode, answer_ctcp, serv, curserv, default_alines;
 extern unsigned int     rolls;
 extern port_t		default_port, newserverport, curservport;

+ 41 - 11
src/mod/server.mod/servmsg.c

@@ -198,8 +198,8 @@ void rehash_server(const char *servname, const char *nick)
   if (nick && nick[0]) {
     strlcpy(botname, nick, sizeof(botname));
 
-    dprintf(DP_SERVER, "WHOIS %s\n", botname); /* get user@host */
-    dprintf(DP_SERVER, "USERHOST %s\n", botname); /* get user@ip */
+    dprintf(DP_MODE, "WHOIS %s\n", botname); /* get user@host */
+    dprintf(DP_MODE, "USERHOST %s\n", botname); /* get user@ip */
     dprintf(DP_SERVER, "MODE %s %s\n", botname, var_get_str_by_name("usermode"));
     rehash_monitor_list();
   }
@@ -225,8 +225,6 @@ static int got001(char *from, char *msg)
   rehash_server(from, msg);
   /* Ok...param #1 of 001 = what server thinks my nick is */
 
-  join_chans();
-
 #ifdef no
   if (strcasecmp(from, dcc[servidx].host)) {
     struct server_list *x = serverlist;
@@ -263,14 +261,29 @@ got004(char *from, char *msg)
 
   tmp = newsplit(&msg);
 
+  bool connect_burst = 0;
+
   /* cookies won't work on ircu or Unreal or snircd */
   if (strstr(tmp, "u2.") || strstr(tmp, "Unreal") || strstr(tmp, "snircd")) {
     putlog(LOG_DEBUG, "*", "Disabling cookies as they are not supported on %s", cursrvname);
     cookies_disabled = true;
   } else if (strstr(tmp, "hybrid") || strstr(tmp, "ratbox") || strstr(tmp, "Charybdis") || strstr(tmp, "ircd-seven")) {
-    include_lk = 0;
+    connect_burst = 1;
+    if (!strstr(tmp, "hybrid"))
+      use_flood_count = 1;
+  }
+
+  if (!replaying_cache && connect_burst) {
+    connect_bursting = now;
+    msgburst = SERVER_CONNECT_BURST_RATE;
+    msgrate = 200;
+    last_time.sec = now - 100;
+    putlog(LOG_DEBUG, "*", "Server allows connect bursting, bursting for %d seconds", SERVER_CONNECT_BURST_TIME);
   }
 
+  if (!replaying_cache)
+    join_chans();
+
   return 0;
 }
 
@@ -347,6 +360,10 @@ got005(char *from, char *msg)
       }
     } else if (!strcasecmp(tmp, "EXCEPTS"))
       use_exempts = 1;
+    else if (!strcasecmp(tmp, "CPRIVMSG"))
+      have_cprivmsg = 1;
+    else if (!strcasecmp(tmp, "CNOTICE"))
+      have_cnotice = 1;
     else if (!strcasecmp(tmp, "INVEX"))
       use_invites = 1;
     else if (!strcasecmp(tmp, "MAXBANS")) {
@@ -489,7 +506,7 @@ static bool detect_flood(char *floodnick, char *floodhost, char *from, int which
     in_callerid = 1;
     dronemsgs = 0;
     dronemsgtime = 0;
-    dprintf(DP_DUMP, "MODE %s :+%c\n", botname, callerid_char);
+    dprintf(DP_MODE_NEXT, "MODE %s :+%c\n", botname, callerid_char);
     howlong.sec = flood_callerid_time;
     howlong.usec = 0;
     timer_create(&howlong, "Unset CALLERID", (Function) unset_callerid);
@@ -649,13 +666,13 @@ static int gotmsg(char *from, char *msg)
   /* Send out possible ctcp responses */
   if (ctcp_reply[0]) {
     if (ctcp_mode != 2) {
-      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+      notice(nick, ctcp_reply, DP_HELP);
     } else {
       if (now - last_ctcp > flood_ctcp.time) {
-        dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+        notice(nick, ctcp_reply, DP_HELP);
 	count_ctcp = 1;
       } else if (count_ctcp < flood_ctcp.count) {
-        dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+        notice(nick, ctcp_reply, DP_HELP);
 	count_ctcp++;
       }
       last_ctcp = now;
@@ -1239,6 +1256,7 @@ static int gotmode(char *from, char *msg)
   return 0;
 }
 
+static void end_burstmode();
 
 static void disconnect_server(int idx, int dolost)
 {
@@ -1264,11 +1282,15 @@ static void disconnect_server(int idx, int dolost)
   in_callerid = 0;
   use_exempts = 0;
   use_invites = 0;
+  have_cprivmsg = 0;
+  have_cnotice = 0;
+  use_flood_count = 0;
   if (dolost) {
     Auth::DeleteAll();
     trying_server = 0;
     lostdcc(idx);
   }
+  end_burstmode();
 
   /* Invalidate the cmd_swhois cache callback data */
   for (int i = 0; i < dcc_total; i++) {
@@ -1381,7 +1403,7 @@ static int gotkick(char *from, char *msg)
     /* Not my kick, I don't need to bother about it. */
     return 0;
   if (use_penalties) {
-    last_time += 2;
+    last_time.sec += 2;
     if (debug_output)
       putlog(LOG_SRVOUT, "*", "adding 2secs penalty (successful kick)");
   }
@@ -1406,7 +1428,7 @@ static int whoispenalty(char *from, char *msg)
       i++;
     }
     if (ii) {
-      last_time += 1;
+      last_time.sec += 1;
       if (debug_output)
         putlog(LOG_SRVOUT, "*", "adding 1sec penalty (remote whois)");
     }
@@ -1998,6 +2020,14 @@ static void server_dns_callback(int id, void *client_data, const char *host, bd:
     /* reset counter so first ctcp is dumped for tcms */
     first_ctcp_check = 0;
 
+    // Just connecting, set last queue time to the past.
+    last_time.sec = now - 100;
+    last_time.usec = 0;
+    end_burstmode();
+    use_flood_count = 0;
+    real_msgburst = msgburst;
+    real_msgrate = msgrate;
+
     if (serverpass[0])
       dprintf(DP_MODE, "PASS %s\n", serverpass);
     dprintf(DP_MODE, "NICK %s\n", botname);

+ 7 - 2
src/mod/transfer.mod/transfer.c

@@ -45,6 +45,9 @@
 #include "src/mod/share.mod/share.h"
 #include "src/mod/update.mod/update.h"
 
+#include "src/chanprog.h"
+#include <bdlib/src/String.h>
+
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <netinet/in.h>
@@ -491,7 +494,7 @@ void dcc_send(int idx, char *buf, int len)
   dcc[idx].timeval = now;
   if (dcc[idx].status > dcc[idx].u.xfer->length &&
       dcc[idx].u.xfer->length > 0) {
-    dprintf(DP_HELP,"NOTICE %s :Bogus file length.\n", dcc[idx].nick);
+    notice(dcc[idx].nick, "Bogus file length.", DP_HELP);
     putlog(LOG_FILES, "*",
 	   "File too long: dropping dcc send %s from %s!%s",
 	   dcc[idx].u.xfer->origname, dcc[idx].nick, dcc[idx].host);
@@ -747,7 +750,9 @@ static void dcc_get_pending(int idx, char *buf, int len)
   dcc[idx].addr = ip;
   dcc[idx].port = (int) port;
   if (dcc[idx].sock == -1) {
-    dprintf(DP_HELP, "NOTICE %s :Bad connection (%s)\n", dcc[idx].nick, strerror(errno));
+    bd::String msg;
+    msg.printf("Bad connection (%s)", strerror(errno));
+    notice(dcc[idx].nick, msg.c_str(), DP_HELP);
     putlog(LOG_FILES, "*", "DCC bad connection: GET %s (%s!%s)",
 	   dcc[idx].u.xfer->origname, dcc[idx].nick, dcc[idx].host);
     fclose(dcc[idx].u.xfer->f);

+ 8 - 0
src/set.c

@@ -53,6 +53,12 @@ int ison_time = 10;
 int kill_threshold;
 int lag_threshold;
 int login;
+/* Number of seconds to wait between transmitting queued lines to the server
+ * lower this value at your own risk.  ircd is known to start flood control
+ * at 512 bytes/2 seconds.
+ */
+int msgburst;
+int msgrate;
 char motd[512] = "";
 char msgident[21] = "";
 char msginvite[21] = "";
@@ -105,6 +111,8 @@ static variable_t vars[] = {
  VAR("msg-op",		msgop,			VAR_WORD|VAR_NOLHUB,				0, 0, NULL),
  VAR("msg-pass",	msgpass,		VAR_WORD|VAR_NOLHUB,				0, 0, NULL),
  VAR("msg-release",	msgrelease,		VAR_WORD|VAR_NOLHUB,				0, 0, NULL),
+ VAR("msgburst",	&msgburst,		VAR_INT|VAR_NOLHUB,				1, 90, "5"),
+ VAR("msgrate",		&msgrate,		VAR_INT|VAR_NOLHUB,				DEQ_RATE, 10000, "2200"),
  VAR("nick",		origbotname,		VAR_WORD|VAR_NOHUB|VAR_NICK|VAR_NODEF,	0, 0, NULL),
  VAR("notify-time",	&ison_time,		VAR_INT|VAR_NOLHUB,				1, 30, "10"),
  VAR("oidentd",		&oidentd,		VAR_INT|VAR_BOOL|VAR_NOLHUB,			0, 1, "0"),

+ 1 - 1
src/set.h

@@ -73,7 +73,7 @@ extern char		auth_key[], auth_prefix[2], motd[], alias[], rbl_servers[1024],
 extern bool		dccauth, auth_obscure, manop_warn, auth_chan, oidentd, ident_botnick, irc_autoaway, link_cleartext, use_deaf, use_callerid;
 extern int		cloak_script, fight_threshold, fork_interval, in_bots, set_noshare, dcc_autoaway,
 			kill_threshold, lag_threshold, op_bots, hijack, login, promisc, trace,
-                        ison_time;
+                        ison_time, msgrate, msgburst;
 extern rate_t		op_requests, close_threshold;
 
 namespace bd {