[cvslog] Module eggdrop1.7: Change committed

cvslog cvs at tsss.org
Sat Oct 27 11:36:02 CST 2001


CVSROOT    : /usr/local/cvsroot
Module     : eggdrop1.7
Commit time: 2001-10-27 16:35:14 UTC
Commited by: Federico Mennite <ite at techmonkeys.org>

Modified files:
     Makefile.am configure.ac src/Makefile.am src/logfile.c
     src/modules.h

Added files:
     lib/Makefile.am lib/eggdrop/.cvsignore lib/eggdrop/Makefile.am
     lib/eggdrop/module.h lib/eggdrop/modvals.h modules/.cvsignore
     modules/Makefile.am modules/assoc/.cvsignore
     modules/assoc/Makefile.am modules/assoc/assoc.c
     modules/assoc/assoc.h modules/assoc/modinfo
     modules/assoc/help/assoc.help modules/blowfish/.cvsignore
     modules/blowfish/Makefile.am modules/blowfish/bf_tab.h
     modules/blowfish/blowfish.c modules/blowfish/blowfish.h
     modules/blowfish/modinfo modules/channels/.cvsignore
     modules/channels/Makefile.am modules/channels/channels.c
     modules/channels/channels.h modules/channels/cmdschan.c
     modules/channels/flagmaps.c modules/channels/modinfo
     modules/channels/tclchan.c modules/channels/udefchan.c
     modules/channels/userchan.c modules/channels/help/chaninfo.help
     modules/channels/help/channels.help
     modules/channels/help/set/channels.help
     modules/compress/.cvsignore modules/compress/Makefile.am
     modules/compress/compress.c modules/compress/compress.h
     modules/compress/modinfo modules/compress/tclcompress.c
     modules/compress/help/set/compress.help
     modules/console/.cvsignore modules/console/Makefile.am
     modules/console/console.c modules/console/console.h
     modules/console/modinfo modules/console/help/console.help
     modules/console/help/set/console.help modules/ctcp/.cvsignore
     modules/ctcp/Makefile.am modules/ctcp/ctcp.c modules/ctcp/ctcp.h
     modules/ctcp/modinfo modules/ctcp/help/set/ctcp.help
     modules/filesys/.cvsignore modules/filesys/Makefile.am
     modules/filesys/dbcompat.c modules/filesys/dbcompat.h
     modules/filesys/filedb3.c modules/filesys/filedb3.h
     modules/filesys/filelist.c modules/filesys/filelist.h
     modules/filesys/files.c modules/filesys/files.h
     modules/filesys/filesys.c modules/filesys/filesys.h
     modules/filesys/modinfo modules/filesys/tclfiles.c
     modules/filesys/help/filesys.help
     modules/filesys/help/set/filesys.help modules/irc/Makefile.am
     modules/irc/chan.c modules/irc/cmdsirc.c modules/irc/irc.c
     modules/irc/irc.h modules/irc/mode.c modules/irc/modinfo
     modules/irc/msgcmds.c modules/irc/tclirc.c
     modules/irc/help/irc.help modules/irc/help/msg/irc.help
     modules/irc/help/set/irc.help modules/notes/.cvsignore
     modules/notes/Makefile.am modules/notes/cmdsnote.c
     modules/notes/modinfo modules/notes/notes.c modules/notes/notes.h
     modules/notes/help/notes.help modules/notes/help/msg/notes.help
     modules/notes/help/set/notes.help modules/perlscript/.cvsignore
     modules/perlscript/Makefile.am modules/perlscript/perlscript.c
     modules/server/.cvsignore modules/server/Makefile.am
     modules/server/cmdsserv.c modules/server/modinfo
     modules/server/server.c modules/server/server.h
     modules/server/servmsg.c modules/server/tclserv.c
     modules/server/help/server.help
     modules/server/help/set/server.help modules/share/.cvsignore
     modules/share/Makefile.am modules/share/modinfo
     modules/share/share.c modules/share/share.h
     modules/share/uf_features.c modules/share/help/share.help
     modules/tclscript/.cvsignore modules/tclscript/Makefile.am
     modules/tclscript/tclscript.c modules/transfer/.cvsignore
     modules/transfer/Makefile.am modules/transfer/modinfo
     modules/transfer/transfer.c modules/transfer/transfer.h
     modules/transfer/help/set/transfer.help modules/uptime/.cvsignore
     modules/uptime/Makefile.am modules/uptime/modinfo
     modules/uptime/uptime.c modules/uptime/uptime.h
     modules/woobie/.cvsignore modules/woobie/Makefile.am
     modules/woobie/modinfo modules/woobie/woobie.c

Removed files:
     src/mod/.cvsignore src/mod/Makefile.am src/mod/module.h
     src/mod/modvals.h src/mod/assoc.mod/.cvsignore
     src/mod/assoc.mod/Makefile.am src/mod/assoc.mod/assoc.c
     src/mod/assoc.mod/assoc.h src/mod/assoc.mod/modinfo
     src/mod/assoc.mod/help/assoc.help src/mod/blowfish.mod/.cvsignore
     src/mod/blowfish.mod/Makefile.am src/mod/blowfish.mod/bf_tab.h
     src/mod/blowfish.mod/blowfish.c src/mod/blowfish.mod/blowfish.h
     src/mod/blowfish.mod/modinfo src/mod/channels.mod/.cvsignore
     src/mod/channels.mod/Makefile.am src/mod/channels.mod/channels.c
     src/mod/channels.mod/channels.h src/mod/channels.mod/cmdschan.c
     src/mod/channels.mod/flagmaps.c src/mod/channels.mod/modinfo
     src/mod/channels.mod/tclchan.c src/mod/channels.mod/udefchan.c
     src/mod/channels.mod/userchan.c
     src/mod/channels.mod/help/chaninfo.help
     src/mod/channels.mod/help/channels.help
     src/mod/channels.mod/help/set/channels.help
     src/mod/compress.mod/.cvsignore src/mod/compress.mod/Makefile.am
     src/mod/compress.mod/compress.c src/mod/compress.mod/compress.h
     src/mod/compress.mod/modinfo src/mod/compress.mod/tclcompress.c
     src/mod/compress.mod/help/set/compress.help
     src/mod/console.mod/.cvsignore src/mod/console.mod/Makefile.am
     src/mod/console.mod/console.c src/mod/console.mod/console.h
     src/mod/console.mod/modinfo src/mod/console.mod/help/console.help
     src/mod/console.mod/help/set/console.help
     src/mod/ctcp.mod/.cvsignore src/mod/ctcp.mod/Makefile.am
     src/mod/ctcp.mod/ctcp.c src/mod/ctcp.mod/ctcp.h
     src/mod/ctcp.mod/modinfo src/mod/ctcp.mod/help/set/ctcp.help
     src/mod/filesys.mod/.cvsignore src/mod/filesys.mod/Makefile.am
     src/mod/filesys.mod/dbcompat.c src/mod/filesys.mod/dbcompat.h
     src/mod/filesys.mod/filedb3.c src/mod/filesys.mod/filedb3.h
     src/mod/filesys.mod/filelist.c src/mod/filesys.mod/filelist.h
     src/mod/filesys.mod/files.c src/mod/filesys.mod/files.h
     src/mod/filesys.mod/filesys.c src/mod/filesys.mod/filesys.h
     src/mod/filesys.mod/modinfo src/mod/filesys.mod/tclfiles.c
     src/mod/filesys.mod/help/filesys.help
     src/mod/filesys.mod/help/set/filesys.help
     src/mod/irc.mod/.cvsignore src/mod/irc.mod/Makefile.am
     src/mod/irc.mod/chan.c src/mod/irc.mod/cmdsirc.c
     src/mod/irc.mod/irc.c src/mod/irc.mod/irc.h
     src/mod/irc.mod/mode.c src/mod/irc.mod/modinfo
     src/mod/irc.mod/msgcmds.c src/mod/irc.mod/tclirc.c
     src/mod/irc.mod/help/irc.help src/mod/irc.mod/help/msg/irc.help
     src/mod/irc.mod/help/set/irc.help src/mod/notes.mod/.cvsignore
     src/mod/notes.mod/Makefile.am src/mod/notes.mod/cmdsnote.c
     src/mod/notes.mod/modinfo src/mod/notes.mod/notes.c
     src/mod/notes.mod/notes.h src/mod/notes.mod/help/notes.help
     src/mod/notes.mod/help/msg/notes.help
     src/mod/notes.mod/help/set/notes.help
     src/mod/perlscript.mod/.cvsignore
     src/mod/perlscript.mod/Makefile.am
     src/mod/perlscript.mod/perlscript.c src/mod/server.mod/.cvsignore
     src/mod/server.mod/Makefile.am src/mod/server.mod/cmdsserv.c
     src/mod/server.mod/modinfo src/mod/server.mod/server.c
     src/mod/server.mod/server.h src/mod/server.mod/servmsg.c
     src/mod/server.mod/tclserv.c src/mod/server.mod/help/server.help
     src/mod/server.mod/help/set/server.help
     src/mod/share.mod/.cvsignore src/mod/share.mod/Makefile.am
     src/mod/share.mod/modinfo src/mod/share.mod/share.c
     src/mod/share.mod/share.h src/mod/share.mod/uf_features.c
     src/mod/share.mod/help/share.help
     src/mod/tclscript.mod/.cvsignore
     src/mod/tclscript.mod/Makefile.am
     src/mod/tclscript.mod/tclscript.c src/mod/transfer.mod/.cvsignore
     src/mod/transfer.mod/Makefile.am src/mod/transfer.mod/modinfo
     src/mod/transfer.mod/transfer.c src/mod/transfer.mod/transfer.h
     src/mod/transfer.mod/help/set/transfer.help
     src/mod/uptime.mod/.cvsignore src/mod/uptime.mod/Makefile.am
     src/mod/uptime.mod/modinfo src/mod/uptime.mod/uptime.c
     src/mod/uptime.mod/uptime.h src/mod/woobie.mod/.cvsignore
     src/mod/woobie.mod/Makefile.am src/mod/woobie.mod/modinfo
     src/mod/woobie.mod/woobie.c

Log message:

* Renamed src/mod to modules.
* Created lib as top dir for eggdrop's libraries.

---------------------- diff included ----------------------
Index: eggdrop1.7/Makefile.am
diff -u eggdrop1.7/Makefile.am:1.6 eggdrop1.7/Makefile.am:1.7
--- eggdrop1.7/Makefile.am:1.6	Sun Oct 21 11:05:51 2001
+++ eggdrop1.7/Makefile.am	Sat Oct 27 11:34:46 2001
@@ -1,4 +1,4 @@
-# $Id: Makefile.am,v 1.6 2001/10/21 16:05:51 tothwolf Exp $
+# $Id: Makefile.am,v 1.7 2001/10/27 16:34:46 ite Exp $
 
 AUX_DIST		= $(ac_aux_dir)/install-sh \
 			$(ac_aux_dir)/ltmain.sh \
@@ -19,7 +19,7 @@
 
 EXTRA_DIST		= bootstrap
 
-SUBDIRS			= intl libltdl src scripts @POSUB@
+SUBDIRS			= intl libltdl lib modules src scripts @POSUB@
 DISTCLEANFILES		= lush.h
 MAINTAINERCLEANFILES	= Makefile.in aclocal.m4 configure config-h.in \
 			config.cache config.log config.status \
Index: eggdrop1.7/configure.ac
diff -u eggdrop1.7/configure.ac:1.5 eggdrop1.7/configure.ac:1.6
--- eggdrop1.7/configure.ac:1.5	Sat Oct 27 08:34:16 2001
+++ eggdrop1.7/configure.ac	Sat Oct 27 11:34:46 2001
@@ -160,6 +160,6 @@
 AC_SUBST(ac_aux_dir)
 
 # FIXME: module's Makefiles list will prolly become dynamic
-AC_OUTPUT([Makefile doc/Makefile scripts/Makefile src/Makefile src/compat/Makefile src/egglib/Makefile src/mod/Makefile src/adns/Makefile src/mod/Makefile intl/Makefile po/Makefile.in src/mod/assoc.mod/Makefile src/mod/blowfish.mod/Makefile src/mod/channels.mod/Makefile src/mod/compress.mod/Makefile src/mod/console.mod/Makefile src/mod/ctcp.mod/Makefile src/mod/filesys.mod/Makefile src/mod/irc.mod/Makefile src/mod/notes.mod/Makefile src/mod/perlscript.mod/Makefile src/mod/server.mod/Makefile src/mod/share.mod/Makefile src/mod/tclscript.mod/Makefile src/mod/transfer.mod/Makefile src/mod/uptime.mod/Makefile src/mod/woobie.mod/Makefile])
+AC_OUTPUT([Makefile doc/Makefile scripts/Makefile lib/Makefile lib/eggdrop/Makefile src/Makefile src/compat/Makefile src/egglib/Makefile src/adns/Makefile modules/Makefile intl/Makefile po/Makefile.in modules/assoc/Makefile modules/blowfish/Makefile modules/channels/Makefile modules/compress/Makefile modules/console/Makefile modules/ctcp/Makefile modules/filesys/Makefile modules/irc/Makefile modules/notes/Makefile modules/perlscript/Makefile modules/server/Makefile modules/share/Makefile modules/tclscript/Makefile modules/transfer/Makefile modules/uptime/Makefile modules/woobie/Makefile])
 
 EGG_MSG_CONFIGURE_END
Index: eggdrop1.7/lib/Makefile.am
diff -u /dev/null eggdrop1.7/lib/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/lib/Makefile.am	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,5 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:47 ite Exp $
+
+SUBDIRS			= eggdrop
+
+MAINTAINERCLEANFILES	= Makefile.in
Index: eggdrop1.7/lib/eggdrop/.cvsignore
diff -u /dev/null eggdrop1.7/lib/eggdrop/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/lib/eggdrop/.cvsignore	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,3 @@
+Makefile
+Makefile.in
+
Index: eggdrop1.7/lib/eggdrop/Makefile.am
diff -u /dev/null eggdrop1.7/lib/eggdrop/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/lib/eggdrop/Makefile.am	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,3 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:47 ite Exp $
+
+MAINTAINERCLEANFILES	= Makefile.in
Index: eggdrop1.7/lib/eggdrop/module.h
diff -u /dev/null eggdrop1.7/lib/eggdrop/module.h:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/lib/eggdrop/module.h	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,481 @@
+/*
+ * module.h
+ *
+ * $Id: module.h,v 1.1 2001/10/27 16:34:47 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_MODULE_H
+#define _EGG_MOD_MODULE_H
+
+/* FIXME: remove this ugliness ASAP! */
+#define MAKING_MODS
+
+/* Just include *all* the include files...it's slower but EASIER */
+#include "src/main.h"		/* NOTE: when removing this, include config.h */
+#include "modvals.h"
+#include "src/tandem.h"
+#include "src/registry.h"
+
+/*
+ * This file contains all the orrible stuff required to do the lookup
+ * table for symbols, rather than getting the OS to do it, since most
+ * OS's require all symbols resolved, this can cause a problem with
+ * some modules.
+ *
+ * This is intimately related to the table in `modules.c'. Don't change
+ * the files unless you have flamable underwear.
+ *
+ * Do not read this file whilst unless heavily sedated, I will not be
+ * held responsible for mental break-downs caused by this file <G>
+ */
+
+/* #undef feof */
+#undef dprintf
+#undef Context
+#undef ContextNote
+
+#if defined (__CYGWIN__) && !defined(STATIC)
+#  define EXPORT_SCOPE	__declspec(dllexport)
+#else
+#  define EXPORT_SCOPE
+#endif
+
+/* Redefine for module-relevance */
+
+/* 0 - 3 */
+/* 0: nmalloc -- UNUSED (Tothwolf) */
+/* 1: nfree -- UNUSED (Tothwolf) */
+#ifdef DEBUG_CONTEXT
+#  define Context (global[2](__FILE__, __LINE__, MODULE_NAME))
+#else
+#  define Context {}
+#endif
+#define module_rename ((int (*)(char *, char *))global[3])
+/* 4 - 7 */
+#define module_register ((int (*)(char *, Function *, int, int))global[4])
+#define module_find ((module_entry * (*)(char *,int,int))global[5])
+#define module_depend ((Function *(*)(char *,char *,int,int))global[6])
+#define module_undepend ((int(*)(char *))global[7])
+/* 8 - 11 */
+/* #define add_bind_table ((p_tcl_bind_list(*)(const char *,int,Function))global[8]) */
+/* #define del_bind_table ((void (*) (p_tcl_bind_list))global[9]) */
+/* #define find_bind_table ((p_tcl_bind_list(*)(const char *))global[10]) */
+/* #define check_tcl_bind ((int (*) (p_tcl_bind_list,const char *,struct flag_record *,const char *, int))global[11]) */
+/* 12 - 15 */
+#define add_builtins ((int (*) (p_tcl_bind_list, cmd_t *))global[12])
+#define rem_builtins ((int (*) (p_tcl_bind_list, cmd_t *))global[13])
+#define add_tcl_commands ((void (*) (tcl_cmds *))global[14])
+#define rem_tcl_commands ((void (*) (tcl_cmds *))global[15])
+/* 16 - 19 */
+#define add_tcl_ints ((void (*) (tcl_ints *))global[16])
+#define rem_tcl_ints ((void (*) (tcl_ints *))global[17])
+#define add_tcl_strings ((void (*) (tcl_strings *))global[18])
+#define rem_tcl_strings ((void (*) (tcl_strings *))global[19])
+/* 20 - 23 */
+#define base64_to_int ((int (*) (char *))global[20])
+#define int_to_base64 ((char * (*) (int))global[21])
+#define int_to_base10 ((char * (*) (int))global[22])
+#define simple_sprintf ((int (*)())global[23])
+/* 24 - 27 */
+#define botnet_send_zapf ((void (*)(int, char *, char *, char *))global[24])
+#define botnet_send_zapf_broad ((void (*)(int, char *, char *, char *))global[25])
+#define botnet_send_unlinked ((void (*)(int, char *, char *))global[26])
+#define botnet_send_bye ((void(*)(void))global[27])
+/* 28 - 31 */
+#define botnet_send_chat ((void(*)(int,char*,char*))global[28])
+#define botnet_send_filereject ((void(*)(int,char*,char*,char*))global[29])
+#define botnet_send_filesend ((void(*)(int,char*,char*,char*))global[30])
+#define botnet_send_filereq ((void(*)(int,char*,char*,char*))global[31])
+/* 32 - 35 */
+#define botnet_send_join_idx ((void(*)(int,int))global[32])
+#define botnet_send_part_idx ((void(*)(int,char *))global[33])
+#define updatebot ((void(*)(int,char*,char,int))global[34])
+#define nextbot ((int (*)(char *))global[35])
+/* 36 - 39 */
+#define zapfbot ((void (*)(int))global[36])
+/* 37: n_free -- UNUSED (Tothwolf) */
+#define u_pass_match ((int (*)(struct userrec *,char *))global[38])
+/* 39: user_malloc -- UNUSED (Tothwolf) */
+/* 40 - 43 */
+#define get_user ((void *(*)(struct user_entry_type *,struct userrec *))global[40])
+#define set_user ((int(*)(struct user_entry_type *,struct userrec *,void *))global[41])
+#define add_entry_type ((int (*) ( struct user_entry_type * ))global[42])
+#define del_entry_type ((int (*) ( struct user_entry_type * ))global[43])
+/* 44 - 47 */
+#define get_user_flagrec ((void (*)(struct userrec *, struct flag_record *, const char *))global[44])
+#define set_user_flagrec ((void (*)(struct userrec *, struct flag_record *, const char *))global[45])
+#define get_user_by_host ((struct userrec * (*)(char *))global[46])
+#define get_user_by_handle ((struct userrec *(*)(struct userrec *,char *))global[47])
+/* 48 - 51 */
+#define find_entry_type ((struct user_entry_type * (*) ( char * ))global[48])
+#define find_user_entry ((struct user_entry * (*)( struct user_entry_type *, struct userrec *))global[49])
+#define adduser ((struct userrec *(*)(struct userrec *,char*,char*,char*,int))global[50])
+#define deluser ((int (*)(char *))global[51])
+/* 52 - 55 */
+#define addhost_by_handle ((void (*) (char *, char *))global[52])
+#define delhost_by_handle ((int(*)(char *,char *))global[53])
+#define readuserfile ((int (*)(char *,struct userrec **))global[54])
+#define write_userfile ((void(*)(int))global[55])
+/* 56 - 59 */
+#define geticon ((char (*) (int))global[56])
+#define clear_chanlist ((void (*)(void))global[57])
+#define reaffirm_owners ((void (*)(void))global[58])
+#define change_handle ((int(*)(struct userrec *,char*))global[59])
+/* 60 - 63 */
+#define write_user ((int (*)(struct userrec *, FILE *,int))global[60])
+#define clear_userlist ((void (*)(struct userrec *))global[61])
+#define count_users ((int(*)(struct userrec *))global[62])
+#define sanity_check ((int(*)(int))global[63])
+/* 64 - 67 */
+#define break_down_flags ((void (*)(const char *,struct flag_record *,struct flag_record *))global[64])
+#define build_flags ((void (*)(char *, struct flag_record *, struct flag_record *))global[65])
+#define flagrec_eq ((int(*)(struct flag_record*,struct flag_record *))global[66])
+#define flagrec_ok ((int(*)(struct flag_record*,struct flag_record *))global[67])
+/* 68 - 71 */
+#define shareout (*(Function *)(global[68]))
+#define dprintf (global[69])
+#define chatout (global[70])
+#define chanout_but ((void(*)())global[71])
+/* 72 - 75 */
+#define check_validity ((int (*) (char *,Function))global[72])
+#define list_delete ((int (*)( struct list_type **, struct list_type *))global[73])
+#define list_append ((int (*) ( struct list_type **, struct list_type *))global[74])
+#define list_contains ((int (*) (struct list_type *, struct list_type *))global[75])
+/* 76 - 79 */
+#define answer ((int (*) (int,char *,char *,unsigned short *,int))global[76])
+#define getmyip ((IP (*) (void))global[77])
+#define neterror ((void (*) (char *))global[78])
+#define tputs ((void (*) (int, char *,unsigned int))global[79])
+/* 80 - 83 */
+#define new_dcc ((int (*) (struct dcc_table *, int))global[80])
+#define lostdcc ((void (*) (int))global[81])
+#define getsock ((int (*) (int))global[82])
+#define killsock ((void (*) (int))global[83])
+/* 84 - 87 */
+#define open_listen ((int (*) (int *,int))global[84])
+#define open_telnet_dcc ((int (*) (int,char *,char *))global[85])
+/* 86: get_data_ptr -- UNUSED (Tothwolf) */
+#define open_telnet ((int (*) (char *, int))global[87])
+/* 88 - 91 */
+#define check_bind_event ((void * (*) (const char *))global[88])
+#ifndef HAVE_MEMCPY
+# define memcpy ((void * (*) (void *, const void *, size_t))global[89])
+#endif
+/* #define my_atoul ((IP(*)(char *))global[90]) */
+#define my_strcpy ((int (*)(char *, const char *))global[91])
+/* 92 - 95 */
+#define dcc (*(struct dcc_t **)global[92])
+#define chanset (*(struct chanset_t **)(global[93]))
+#define userlist (*(struct userrec **)global[94])
+#define lastuser (*(struct userrec **)(global[95]))
+/* 96 - 99 */
+#define global_bans (*(maskrec **)(global[96]))
+#define global_ign (*(struct igrec **)(global[97]))
+#define password_timeout (*(int *)(global[98]))
+#define share_greet (*(int *)global[99])
+/* 100 - 103 */
+#define max_dcc (*(int *)global[100])
+#define require_p (*(int *)global[101])
+#define ignore_time (*(int *)(global[102]))
+/* #define use_console_r (*(int *)(global[103])) */
+/* 104 - 107 */
+#define reserved_port_min (*(int *)(global[104]))
+#define reserved_port_max (*(int *)(global[105]))
+#define debug_output (*(int *)(global[106]))
+#define noshare (*(int *)(global[107]))
+/* 108 - 111 */
+#define gban_total (*(int*)global[108])
+#define make_userfile (*(int*)global[109])
+#define default_flags (*(int*)global[110])
+#define dcc_total (*(int*)global[111])
+/* 112 - 115 */
+#define tempdir ((char *)(global[112]))
+#define natip ((char *)(global[113]))
+/* 114: hostname -- UNUSED (drummer) */
+#define origbotname ((char *)(global[115]))
+/* 116 - 119 */
+#define botuser ((char *)(global[116]))
+#define admin ((char *)(global[117]))
+#define userfile ((char *)global[118])
+#define ver ((char *)global[119])
+/* 120 - 123 */
+#define notify_new ((char *)global[120])
+#define helpdir ((char *)global[121])
+#define Version ((char *)global[122])
+#define botnetnick ((char *)global[123])
+/* 124 - 127 */
+#define DCC_CHAT_PASS (*(struct dcc_table *)(global[124]))
+#define DCC_BOT (*(struct dcc_table *)(global[125]))
+#define DCC_LOST (*(struct dcc_table *)(global[126]))
+#define DCC_CHAT (*(struct dcc_table *)(global[127]))
+/* 128 - 131 */
+#define interp (*(Tcl_Interp **)(global[128]))
+#define now (*(time_t*)global[129])
+#define findanyidx ((int (*)(int))global[130])
+#define findchan ((struct chanset_t *(*)(char *))global[131])
+/* 132 - 135 */
+#define cmd_die (global[132])
+#define days ((void (*)(time_t,time_t,char *))global[133])
+#define daysago ((void (*)(time_t,time_t,char *))global[134])
+#define daysdur ((void (*)(time_t,time_t,char *))global[135])
+/* 136 - 139 */
+#define ismember ((memberlist * (*) (struct chanset_t *, char *))global[136])
+#define newsplit ((char *(*)(char **))global[137])
+/* 138: splitnick -- UNUSED (Tothwolf) */
+#define splitc ((void (*)(char *,char *,char))global[139])
+/* 140 - 143 */
+#define addignore ((void (*) (char *, char *, char *,time_t))global[140])
+#define match_ignore ((int (*)(char *))global[141])
+#define delignore ((int (*)(char *))global[142])
+#define fatal (global[143])
+/* 144 - 147 */
+#define xtra_kill ((void (*)(struct user_entry *))global[144])
+#define xtra_unpack ((void (*)(struct userrec *, struct user_entry *))global[145])
+#define movefile ((int (*) (char *, char *))global[146])
+#define copyfile ((int (*) (char *, char *))global[147])
+/* 148 - 151 */
+#define do_tcl ((void (*)(char *, char *))global[148])
+#define readtclprog ((int (*)(const char *))global[149])
+/* 150: get_language() -- UNUSED */
+#define def_get ((void *(*)(struct userrec *, struct user_entry *))global[151])
+/* 152 - 155 */
+#define makepass ((void (*) (char *))global[152])
+#define wild_match ((int (*)(const char *, const char *))global[153])
+#define maskhost ((void (*)(const char *, char *))global[154])
+#define show_motd ((void(*)(int))global[155])
+/* 156 - 159 */
+#define tellhelp ((void(*)(int, char *, struct flag_record *, int))global[156])
+#define showhelp ((void(*)(char *, char *, struct flag_record *, int))global[157])
+#define add_help_reference ((void(*)(char *))global[158])
+#define rem_help_reference ((void(*)(char *))global[159])
+/* 160 - 163 */
+#define touch_laston ((void (*)(struct userrec *,char *,time_t))global[160])
+#define add_mode ((void (*)(struct chanset_t *,char,char,char *))(*(Function**)(global[161])))
+#define rmspace ((void (*)(char *))global[162])
+#define in_chain ((int (*)(char *))global[163])
+/* 164 - 167 */
+#define add_note ((int (*)(char *,char*,char*,int,int))global[164])
+/* 165: del_lang_section() -- UNUSED */
+#define detect_dcc_flood ((int (*) (time_t *,struct chat_info *,int))global[166])
+#define flush_lines ((void(*)(int,struct chat_info*))global[167])
+/* 168 - 171 */
+/* 168: expected_memory -- UNUSED (Tothwolf) */
+/* 169: tell_mem_status -- UNUSED (Tothwolf) */
+#define do_restart (*(int *)(global[170]))
+#define check_tcl_filt ((const char *(*)(int, const char *))global[171])
+/* 172 - 175 */
+#define add_hook(a,b) (((void (*) (int, Function))global[172])(a,b))
+#define del_hook(a,b) (((void (*) (int, Function))global[173])(a,b))
+/* 174: H_dcc -- UNUSED (stdarg) */
+/* 175: H_filt -- UNUSED (oskar) */
+/* 176 - 179 */
+/* 176: H_chon -- UNUSED (oskar) */
+/* 177: H_chof -- UNUSED (oskar) */
+/*#define H_load (*(p_tcl_bind_list *)(global[178])) */
+/*#define H_unld (*(p_tcl_bind_list *)(global[179])) */
+/* 180 - 183 */
+/* 180: H_chat -- UNUSED (oskar) */
+/* 181: H_act -- UNUSED (oskar) */
+/* 182: H_bcst -- UNUSED (oskar) */
+/* 183: H_bot -- UNUSED (oskar) */
+/* 184 - 187 */
+/* 184: H_link -- UNUSED (oskar) */
+/* 185: H_disc -- UNUSED (oskar) */
+/* 186: H_away -- UNUSED (oskar) */
+/* 187: H_nkch -- UNUSED (oskar) */
+/* 188 - 191 */
+#define USERENTRY_BOTADDR (*(struct user_entry_type *)(global[188]))
+#define USERENTRY_BOTFL (*(struct user_entry_type *)(global[189]))
+#define USERENTRY_HOSTS (*(struct user_entry_type *)(global[190]))
+#define USERENTRY_PASS (*(struct user_entry_type *)(global[191]))
+/* 192 - 195 */
+#define USERENTRY_XTRA (*(struct user_entry_type *)(global[192]))
+#define user_del_chan ((void(*)(char *))(global[193]))
+#define USERENTRY_INFO (*(struct user_entry_type *)(global[194]))
+#define USERENTRY_COMMENT (*(struct user_entry_type *)(global[195]))
+/* 196 - 199 */
+#define USERENTRY_LASTON (*(struct user_entry_type *)(global[196]))
+#define putlog (global[197])
+#define botnet_send_chan ((void(*)(int,char*,char*,int,char*))global[198])
+#define list_type_kill ((void(*)(struct list_type *))global[199])
+/* 200 - 203 */
+#define logmodes ((int(*)(char *))global[200])
+#define masktype ((const char *(*)(int))global[201])
+#define stripmodes ((int(*)(char *))global[202])
+#define stripmasktype ((const char *(*)(int))global[203])
+/* 204 - 207 */
+#define sub_lang ((void(*)(int,char *))global[204])
+#define online_since (*(int *)(global[205]))
+/* 206: cmd_loadlanguage() -- UNUSED */
+#define check_dcc_attrs ((int (*)(struct userrec *,int))global[207])
+/* 208 - 211 */
+#define check_dcc_chanattrs ((int (*)(struct userrec *,char *,int,int))global[208])
+#define add_tcl_coups ((void (*) (tcl_coups *))global[209])
+#define rem_tcl_coups ((void (*) (tcl_coups *))global[210])
+#define botname ((char *)(global[211]))
+/* 212 - 215 */
+/* 212: remove_gunk() -- UNUSED (drummer) */
+#define check_tcl_chjn ((void (*) (const char *,const char *,int,char,int,const char *))global[213])
+/* 214: sanitycheck_dcc() -- UNUSED (guppy) */
+#define isowner ((int (*)(char *))global[215])
+/* 216 - 219 */
+/* 216: min_dcc_port -- UNUSED (guppy) */
+/* 217: max_dcc_port -- UNUSED (guppy) */
+#define irccmp ((int(*)(char *, char *))(*(Function**)(global[218])))
+#define ircncmp ((int(*)(char *, char *, int *))(*(Function**)(global[219])))
+/* 220 - 223 */
+#define global_exempts (*(maskrec **)(global[220]))
+#define global_invites (*(maskrec **)(global[221]))
+#define ginvite_total (*(int*)global[222])
+#define gexempt_total (*(int*)global[223])
+/* 224 - 227 */
+/* 224: H_event -- UNUSED (stdarg) */
+#define use_exempts (*(int *)(global[225]))	/* drummer/Jason */
+#define use_invites (*(int *)(global[226]))	/* drummer/Jason */
+#define force_expire (*(int *)(global[227]))	/* Rufus */
+/* 228 - 231 */
+/* 228: add_lang_section() -- UNUSED */
+/* 229: user_realloc -- UNUSED (Tothwolf) */
+/* 230: nrealloc -- UNUSED (Tothwolf) */
+#define xtra_set ((int(*)(struct userrec *,struct user_entry *, void *))global[231])
+/* 232 - 235 */
+#ifdef DEBUG_CONTEXT
+#  define ContextNote(note) (global[232](__FILE__, __LINE__, MODULE_NAME, note))
+#else
+#  define ContextNote(note)	do {	} while (0)
+#endif
+/* 233: Assert -- UNUSED (Tothwolf) */
+#define allocsock ((int(*)(int sock,int options))global[234])
+#define call_hostbyip ((void(*)(char *, char *, int))global[235])
+/* 236 - 239 */
+#define call_ipbyhost ((void(*)(char *, char *, int))global[236])
+#define iptostr ((char *(*)(IP))global[237])
+#define DCC_DNSWAIT (*(struct dcc_table *)(global[238]))
+/* 239: hostsanitycheck_dcc() -- UNUSED (guppy) */
+/* 240 - 243 */
+#define dcc_dnsipbyhost ((void (*)(char *))global[240])
+#define dcc_dnshostbyip ((void (*)(char *))global[241])
+#define changeover_dcc ((void (*)(int, struct dcc_table *, int))global[242])
+#define make_rand_str ((void (*) (char *, int))global[243])
+/* 244 - 247 */
+#define protect_readonly (*(int *)(global[244]))
+#define findchan_by_dname ((struct chanset_t *(*)(char *))global[245])
+#define removedcc ((void (*) (int))global[246])
+#define userfile_perm (*(int *)global[247])
+/* 248 - 251 */
+#define sock_has_data ((int(*)(int, int))global[248])
+#define bots_in_subtree ((int (*)(tand_t *))global[249])
+#define users_in_subtree ((int (*)(tand_t *))global[250])
+#ifndef HAVE_INET_ATON
+# define inet_aton ((int (*)(const char *cp, struct in_addr *addr))global[251])
+#endif
+/* 252 - 255 */
+#ifndef HAVE_SNPRINTF
+# define snprintf (global[252])
+#endif
+#ifndef HAVE_VSNPRINTF
+# define vsnprintf ((int (*)(char *, size_t, const char *, va_list))global[253])
+#endif
+#ifndef HAVE_MEMSET
+# define memset ((void *(*)(void *, int, size_t))global[254])
+#endif
+#ifndef HAVE_STRCASECMP
+# define strcasecmp ((int (*)(const char *, const char *))global[255])
+#endif
+/* 256 - 259 */
+#ifndef HAVE_STRNCASECMP
+# define strncasecmp ((int (*)(const char *, const char *, size_t))global[256])
+#endif
+#define is_file ((int (*)(const char *))global[257])
+#define must_be_owner (*(int *)(global[258]))
+#define tandbot (*(tand_t **)(global[259]))
+/* 260 - 263 */
+#define party (*(party_t **)(global[260]))
+#define open_address_listen ((int (*)(char *addr, int *port))global[261])
+#define str_escape ((char *(*)(const char *, const char, const char))global[262])
+#define strchr_unescape ((char *(*)(char *, const char, register const char))global[263])
+/* 264 - 267 */
+#define str_unescape ((void (*)(char *, register const char))global[264])
+#define egg_strcatn ((int (*)(char *dst, const char *src, size_t max))global[265])
+#define clear_chanlist_member ((void (*)(const char *nick))global[266])
+#if (TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 1) || (TCL_MAJOR_VERSION >= 9)
+#define str_nutf8tounicode ((int (*)(char *str, int len))global[267])
+#endif
+/* 268 - 271 */
+/* Please don't modify socklist directly, unless there's no other way.
+ * Its structure might be changed, or it might be completely removed,
+ * so you can't rely on it without a version-check.
+ */
+#define socklist (*(struct sock_list **)global[268])
+#define sockoptions ((int (*)(int, int, int))global[269])
+#define flush_inbuf ((int (*)(int))global[270])
+#ifdef IPV6
+#  define getmyip6 ((struct in6_addr (*) (void))global[271])
+#endif
+/* 272 - 275 */
+#define getlocaladdr ((char* (*) (int))global[272])
+#define kill_bot ((void (*)(char *, char *))global[273])
+#define quit_msg ((char *)(global[274]))
+#define add_bind_table2 ((bind_table_t *(*)(const char *, int, char *, int, int))global[275])
+/* 276 - 279 */
+#define del_bind_table2 ((void (*)(bind_table_t *))global[276])
+#define add_builtins2 ((void (*)(bind_table_t *, cmd_t *))global[277])
+#define rem_builtins2 ((void (*)(bind_table_t *, cmd_t *))global[278])
+#define find_bind_table2 ((bind_table_t *(*)(const char *))global[279])
+/* 280 - 283 */
+#define check_bind ((int (*)(bind_table_t *, const char *, struct flag_record *, ...))global[280])
+#define registry_lookup ((int (*)(const char *, const char *, Function *, void **))global[281])
+#define registry_add_simple_chains ((int (*)(registry_simple_chain_t *))global[282])
+#ifndef HAVE_STRFTIME
+# define strftime ((size_t (*)(char *, size_t, const char *, const struct tm *))global[283])
+#endif
+/* 284 - 287 */
+#ifndef HAVE_INET_NTOP
+# define inet_ntop ((const char (*)(int, const void *, char *, socklen_t size))global[284])
+#endif
+#ifndef HAVE_INET_PTON
+# define inet_pton ((int (*)(int, const char *, void *))global[285])
+#endif
+#ifndef HAVE_VASPRINTF
+# define vasprintf ((int (*)(char **, const char *, va_list))global[286])
+#endif
+#ifndef HAVE_ASPRINTF
+# define asprintf ((int (*)(char **, const char *, ...))global[287])
+#endif
+
+/* 288 - 291 */
+#define msprintf ((char *(*)())global[288])
+#define mstack_new ((mstack_t *(*)())global[289])
+#define mstack_push ((void *(*)())global[290])
+#define mstack_destroy ((void *(*)())global[291])
+
+/* This is for blowfish module, couldnt be bothered making a whole new .h
+ * file for it ;)
+ */
+#ifndef MAKING_ENCRYPTION
+
+#  define encrypt_string(a, b)						\
+	(((char *(*)(char *,char*))encryption_funcs[4])(a,b))
+#  define decrypt_string(a, b)						\
+	(((char *(*)(char *,char*))encryption_funcs[5])(a,b))
+#endif
+
+#endif				/* _EGG_MOD_MODULE_H */
Index: eggdrop1.7/lib/eggdrop/modvals.h
diff -u /dev/null eggdrop1.7/lib/eggdrop/modvals.h:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/lib/eggdrop/modvals.h	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,98 @@
+/*
+ * modvals.h
+ *
+ * $Id: modvals.h,v 1.1 2001/10/27 16:34:47 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_MODVALS_H
+#define _EGG_MOD_MODVALS_H
+
+/* #define HOOK_GET_FLAGREC	  0 	*/
+/* #define HOOK_BUILD_FLAGREC	  1 	*/
+/* #define HOOK_SET_FLAGREC	  2 	*/
+#define HOOK_READ_USERFILE	  3
+#define HOOK_REHASH		  4
+#define HOOK_MINUTELY		  5
+#define HOOK_DAILY		  6
+#define HOOK_HOURLY		  7
+#define HOOK_USERFILE		  8
+#define HOOK_SECONDLY		  9
+#define HOOK_PRE_REHASH		 10
+#define HOOK_IDLE		 11
+#define HOOK_5MINUTELY		 12
+#define HOOK_LOADED		 13
+#define HOOK_BACKUP     	 14
+#define HOOK_DIE		 15
+#define REAL_HOOKS		 16
+#define HOOK_SHAREOUT		105
+#define HOOK_SHAREIN		106
+#define HOOK_ENCRYPT_PASS	107
+#define HOOK_QSERV		108
+#define HOOK_ADD_MODE		109
+#define HOOK_MATCH_NOTEREJ	110
+#define HOOK_IRCCMP		111
+#define HOOK_DNS_HOSTBYIP	112
+#define HOOK_DNS_IPBYHOST	113
+#define HOOK_ENCRYPT_STRING     114
+#define HOOK_DECRYPT_STRING     115
+
+/* These are FIXED once they are in a release they STAY
+ */
+#define MODCALL_START		  0
+#define MODCALL_CLOSE		  1
+#define MODCALL_EXPMEM		  2	/* This is no longer used (Tothwolf) */
+#define MODCALL_REPORT		  3
+/* Filesys */
+#define FILESYS_REMOTE_REQ	  4
+#define FILESYS_ADDFILE		  5
+#define FILESYS_INCRGOTS	  6
+#define FILESYS_ISVALID		  7
+/* Share */
+#define SHARE_FINISH		  4
+#define SHARE_DUMP_RESYNC	  5
+/* Channels */
+#define CHANNEL_CLEAR		 15
+/* Server */
+#define SERVER_BOTNAME		  4
+#define SERVER_BOTUSERHOST	  5
+#define SERVER_NICKLEN		 38
+/* IRC */
+#define IRC_RECHECK_CHANNEL	  4
+#define IRC_RECHECK_CHANNEL_MODES 6
+#define IRC_DO_CHANNEL_PART	  7
+#define IRC_CHECK_THIS_BAN	  8
+/* Notes */
+#define NOTES_CMD_NOTE		  4
+/* Console */
+#define CONSOLE_DOSTORE		  4
+
+#include <ltdl.h>
+
+typedef struct _module_entry {
+  struct _module_entry	*next;
+  char			*name;	/* Name of the module (without .so)	*/
+  int			 major;	/* Major version number MUST match	*/
+  int			 minor;	/* Minor version number MUST be >=	*/
+  lt_dlhandle		hand;
+  Function		*funcs;
+} module_entry;
+
+#endif		/* _EGG_MOD_MODVALS_H */
Index: eggdrop1.7/modules/.cvsignore
diff -u /dev/null eggdrop1.7/modules/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/.cvsignore	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,3 @@
+Makefile
+Makefile.in
+
Index: eggdrop1.7/modules/Makefile.am
diff -u /dev/null eggdrop1.7/modules/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/Makefile.am	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,6 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:47 ite Exp $
+
+# FIXME: list will prolly become dynamic
+SUBDIRS			= assoc blowfish channels compress console ctcp filesys irc notes perlscript server share tclscript transfer uptime woobie
+
+MAINTAINERCLEANFILES	= Makefile.in
Index: eggdrop1.7/modules/assoc/.cvsignore
diff -u /dev/null eggdrop1.7/modules/assoc/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/assoc/.cvsignore	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,8 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
Index: eggdrop1.7/modules/assoc/Makefile.am
diff -u /dev/null eggdrop1.7/modules/assoc/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/assoc/Makefile.am	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,15 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:47 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+pkglib_LTLIBRARIES	= assoc.la
+assoc_la_SOURCES	= assoc.c
+assoc_la_LDFLAGS	= -module -avoid-version -no-undefined
+assoc_la_LIBADD		= @TCL_LIBS@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/assoc/assoc.c
diff -u /dev/null eggdrop1.7/modules/assoc/assoc.c:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/assoc/assoc.c	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,419 @@
+/*
+ * assoc.c -- part of assoc.mod
+ *   the assoc code, moved here mainly from botnet.c for module work
+ *
+ * $Id: assoc.c,v 1.1 2001/10/27 16:34:47 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define MODULE_NAME "assoc"
+#define MAKING_ASSOC
+#include "lib/eggdrop/module.h"
+#include "src/tandem.h"
+#include <stdlib.h>
+#include "assoc.h"
+
+#define start assoc_LTX_start
+
+#undef global
+static Function *global = NULL;
+
+/* Keep track of channel associations */
+typedef struct assoc_t_ {
+  char name[21];
+  unsigned int channel;
+  struct assoc_t_ *next;
+} assoc_t;
+
+/* Channel name-number associations */
+static assoc_t *assoc;
+
+static bind_table_t *BT_dcc, *BT_bot, *BT_link;
+
+static void botnet_send_assoc(int idx, int chan, char *nick,
+			      char *buf)
+{
+  char x[1024];
+  int idx2;
+
+  simple_sprintf(x, "assoc %D %s %s", chan, nick, buf);
+  for (idx2 = 0; idx2 < dcc_total; idx2++)
+    if ((dcc[idx2].type == &DCC_BOT) && (idx2 != idx) &&
+	(b_numver(idx2) >= NEAT_BOTNET) &&
+	!(bot_flags(dcc[idx2].user) & BOT_ISOLATE))
+      botnet_send_zapf(idx2, botnetnick, dcc[idx2].nick, x);
+}
+
+static void link_assoc(char *bot, char *via)
+{
+  char x[1024];
+
+  if (!strcasecmp(via, botnetnick)) {
+    int idx = nextbot(bot);
+    assoc_t *a;
+
+    if (!(bot_flags(dcc[idx].user) & BOT_ISOLATE)) {
+      for (a = assoc; a && a->name[0]; a = a->next) {
+          simple_sprintf(x, "assoc %D %s %s", (int) a->channel, botnetnick,
+           		a->name);
+	  botnet_send_zapf(idx, botnetnick, dcc[idx].nick, x);
+	}
+    }
+  }
+}
+
+static void kill_assoc(int chan)
+{
+  assoc_t *a = assoc, *last = NULL;
+
+  while (a) {
+    if (a->channel == chan) {
+      if (last != NULL)
+	last->next = a->next;
+      else
+	assoc = a->next;
+      free_null(a);
+    } else {
+      last = a;
+      a = a->next;
+    }
+  }
+}
+
+static void kill_all_assoc()
+{
+  assoc_t *a, *x;
+
+  for (a = assoc; a; a = x) {
+    x = a->next;
+    free(a);
+  }
+  assoc = NULL;
+}
+
+static void add_assoc(char *name, int chan)
+{
+  assoc_t *a, *b, *old = NULL;
+
+  for (a = assoc; a; a = a->next) {
+    if (name[0] != 0 && !strcasecmp(a->name, name)) {
+      kill_assoc(a->channel);
+      add_assoc(name, chan);
+      return;
+    }
+    if (a->channel == chan) {
+      strncpyz(a->name, name, sizeof a->name);
+      return;
+    }
+  }
+  /* Add in numerical order */
+  for (a = assoc; a; old = a, a = a->next) {
+    if (a->channel > chan) {
+      b = (assoc_t *) malloc(sizeof(assoc_t));
+      b->next = a;
+      b->channel = chan;
+      strncpyz(b->name, name, sizeof b->name);
+      if (old == NULL)
+	assoc = b;
+      else
+	old->next = b;
+      return;
+    }
+  }
+  /* Add at the end */
+  b = (assoc_t *) malloc(sizeof(assoc_t));
+  b->next = NULL;
+  b->channel = chan;
+  strncpyz(b->name, name, sizeof b->name);
+  if (old == NULL)
+    assoc = b;
+  else
+    old->next = b;
+}
+
+static int get_assoc(char *name)
+{
+  assoc_t *a;
+
+  for (a = assoc; a; a = a->next)
+    if (!strcasecmp(a->name, name))
+      return a->channel;
+  return -1;
+}
+
+static char *get_assoc_name(int chan)
+{
+  assoc_t *a;
+
+  for (a = assoc; a; a = a->next)
+    if (a->channel == chan)
+      return a->name;
+  return NULL;
+}
+
+static void dump_assoc(int idx)
+{
+  assoc_t *a = assoc;
+
+  if (a == NULL) {
+    dprintf(idx, _("No channel names\n"));
+    return;
+  }
+  dprintf(idx, _(" Chan  Name\n"));
+  for (; a && a->name[0]; a = a->next)
+      dprintf(idx, "%c%5d %s\n", (a->channel < 100000) ? ' ' : '*',
+	      a->channel % 100000, a->name);
+  return;
+}
+
+static int cmd_assoc(struct userrec *u, int idx, char *par)
+{
+  char *num;
+  int chan;
+
+  if (!par[0]) {
+    putlog(LOG_CMDS, "*", "#%s# assoc", dcc[idx].nick);
+    dump_assoc(idx);
+  } else if (!u || !(u->flags & USER_BOTMAST)) {
+    dprintf(idx, _("What?  You need '.help'\n"));
+  } else {
+    num = newsplit(&par);
+    if (num[0] == '*') {
+      chan = 100000 + atoi(num + 1);
+      if ((chan < 100000) || (chan > 199999)) {
+	   dprintf(idx, _("Channel # out of range: must be *0-*99999\n"));
+	return 0;
+      }
+    } else {
+      chan = atoi(num);
+      if (chan == 0) {
+	    dprintf(idx, _("You cant name the main party line; its just a party line.\n"));
+	return 0;
+      } else if ((chan < 1) || (chan > 99999)) {
+	    dprintf(idx, _("Channel # out of range: must be 1-99999\n"));
+	return 0;
+      }
+    }
+    if (!par[0]) {
+      /* Remove an association */
+      if (get_assoc_name(chan) == NULL) {
+	    dprintf(idx, _("Channel %s%d has no name.\n"), (chan < 100000) ? "" : "*",
+	            chan % 100000);
+	return 0;
+      }
+      kill_assoc(chan);
+      putlog(LOG_CMDS, "*", "#%s# assoc %d", dcc[idx].nick, chan);
+      dprintf(idx, _("Okay, removed name for channel %s%d.\n"), (chan < 100000) ? "" : "*",
+              chan % 100000);
+      chanout_but(-1, chan, _("--- %s removed this channels name.\n"), dcc[idx].nick);
+      if (chan < 100000)
+	botnet_send_assoc(-1, chan, dcc[idx].nick, "0");
+      return 0;
+    }
+    if (strlen(par) > 20) {
+      dprintf(idx, _("Channels name cant be that long (20 chars max).\n"));
+      return 0;
+    }
+    if ((par[0] >= '0') && (par[0] <= '9')) {
+      dprintf(idx, _("First character of the channel name cant be a digit.\n"));
+      return 0;
+    }
+    add_assoc(par, chan);
+    putlog(LOG_CMDS, "*", "#%s# assoc %d %s", dcc[idx].nick, chan, par);
+    dprintf(idx, _("Okay, channel %s%d is %s now.\n"), (chan < 100000) ? "" : "*",
+            chan % 100000, par);
+    chanout_but(-1, chan, _("--- %s named this channel %s\n"), dcc[idx].nick,
+		par);
+    if (chan < 100000)
+      botnet_send_assoc(-1, chan, dcc[idx].nick, par);
+  }
+  return 0;
+}
+
+static int tcl_killassoc STDVAR {
+  int chan;
+
+  BADARGS(2, 2, " chan");
+  if (argv[1][0] == '&')
+    kill_all_assoc();
+  else {
+    chan = atoi(argv[1]);
+    if ((chan < 1) || (chan > 199999)) {
+      Tcl_AppendResult(irp, _("invalid channel #"), NULL);
+      return TCL_ERROR;
+    }
+    kill_assoc(chan);
+    botnet_send_assoc(-1, chan, "*script*", "0");
+  }
+  return TCL_OK;
+}
+
+static int tcl_assoc STDVAR {
+  int chan;
+  char name[21], *p;
+
+  BADARGS(2, 3, " chan ?name?");
+  if ((argc == 2) && ((argv[1][0] < '0') || (argv[1][0] > '9'))) {
+    chan = get_assoc(argv[1]);
+    if (chan == -1)
+      Tcl_AppendResult(irp, "", NULL);
+    else {
+      simple_sprintf(name, "%d", chan);
+      Tcl_AppendResult(irp, name, NULL);
+    }
+    return TCL_OK;
+  }
+  chan = atoi(argv[1]);
+  if ((chan < 1) || (chan > 199999)) {
+    Tcl_AppendResult(irp, _("invalid channel #"), NULL);
+    return TCL_ERROR;
+  }
+  if (argc == 3) {
+    strncpy(name, argv[2], 20);
+    name[20] = 0;
+    add_assoc(name, chan);
+    botnet_send_assoc(-1, chan, "*script*", name);
+  }
+  p = get_assoc_name(chan);
+  if (p == NULL)
+    name[0] = 0;
+  else
+    strcpy(name, p);
+  Tcl_AppendResult(irp, name, NULL);
+  return TCL_OK;
+}
+
+static void zapf_assoc(char *botnick, char *code, char *par)
+{
+  int idx = nextbot(botnick);
+  char *s, *s1, *nick;
+  int linking = 0, chan;
+
+  if ((idx >= 0) && !(bot_flags(dcc[idx].user) & BOT_ISOLATE)) {
+    if (!strcasecmp(dcc[idx].nick, botnick))
+      linking = b_status(idx) & STAT_LINKING;
+    s = newsplit(&par);
+    chan = base64_to_int(s);
+    if ((chan > 0) || (chan < GLOBAL_CHANS)) {
+      nick = newsplit(&par);
+      s1 = get_assoc_name(chan);
+      if (linking && ((s1 == NULL) || (s1[0] == 0) ||
+		      (((int) get_user(find_entry_type("BOTFL"),
+				       dcc[idx].user) & BOT_HUB)))) {
+	add_assoc(par, chan);
+	botnet_send_assoc(idx, chan, nick, par);
+	chanout_but(-1, chan, _("--- (%s) named this channel %s.\n"), nick, par);
+      } else if (par[0] == '0') {
+	kill_assoc(chan);
+	chanout_but(-1, chan, _("--- (%s) %s removed this channels name.\n"), botnick, nick);
+      } else if (get_assoc(par) != chan) {
+	/* New one i didn't know about -- pass it on */
+	s1 = get_assoc_name(chan);
+	add_assoc(par, chan);
+	chanout_but(-1, chan, _("--- (%s) %s named this channel '%s'.\n"), botnick, nick, par);
+      }
+    }
+  }
+}
+
+/* A report on the module status.
+ */
+static void assoc_report(int idx, int details)
+{
+  assoc_t *a;
+  int size = 0, count = 0;;
+
+  if (details) {
+    for (a = assoc; a; a = a->next) {
+      count++;
+      size += sizeof(assoc_t);
+    }
+    dprintf(idx, _("    %d assocs using %d bytes\n"),
+	    count, size);
+  }
+}
+
+static cmd_t mydcc[] =
+{
+  {"assoc",	"",	cmd_assoc,		NULL},
+  {NULL, 	NULL,	NULL,			NULL}
+};
+
+static cmd_t mybot[] =
+{
+  {"assoc",	"",	(Function) zapf_assoc,	NULL},
+  {NULL,        NULL,   NULL,                   NULL}
+};
+
+static cmd_t mylink[] =
+{
+  {"*",		"",	(Function) link_assoc,	"assoc"},
+  {NULL,	NULL,	NULL,			NULL}
+};
+
+static tcl_cmds mytcl[] =
+{
+  {"assoc",		tcl_assoc},
+  {"killassoc",		tcl_killassoc},
+  {NULL,		NULL}
+};
+
+static char *assoc_close()
+{
+  kill_all_assoc();
+  if (BT_dcc) rem_builtins2(BT_dcc, mydcc);
+  if (BT_bot) rem_builtins2(BT_bot, mybot);
+  if (BT_link) rem_builtins2(BT_link, mylink);
+  rem_tcl_commands(mytcl);
+  module_undepend(MODULE_NAME);
+  return NULL;
+}
+
+EXPORT_SCOPE char *start();
+
+static Function assoc_table[] =
+{
+  (Function) start,
+  (Function) assoc_close,
+  (Function) 0,
+  (Function) assoc_report,
+};
+
+char *start(Function * global_funcs)
+{
+  global = global_funcs;
+
+  module_register(MODULE_NAME, assoc_table, 2, 0);
+  if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+    module_undepend(MODULE_NAME);
+    return _("This module requires eggdrop1.7.0 or later");
+  }
+  assoc = NULL;
+  BT_dcc = find_bind_table2("dcc");
+  if (BT_dcc) add_builtins2(BT_dcc, mydcc);
+  BT_bot = find_bind_table2("bot");
+  if (BT_bot) add_builtins2(BT_bot, mybot);
+  BT_link = find_bind_table2("link");
+  if (BT_link) add_builtins2(BT_link, mylink);
+  add_tcl_commands(mytcl);
+
+  return NULL;
+}
+
Index: eggdrop1.7/modules/assoc/assoc.h
diff -u /dev/null eggdrop1.7/modules/assoc/assoc.h:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/assoc/assoc.h	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,28 @@
+/*
+ * assoc.h -- part of assoc.mod
+ *
+ * $Id: assoc.h,v 1.1 2001/10/27 16:34:47 ite Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_ASSOC_ASSOC_H
+#define _EGG_MOD_ASSOC_ASSOC_H
+
+#endif				/* _EGG_MOD_ASSOC_ASSOC_H */
+
Index: eggdrop1.7/modules/assoc/help/assoc.help
diff -u /dev/null eggdrop1.7/modules/assoc/help/assoc.help:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/assoc/help/assoc.help	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,27 @@
+%{help=assoc module}
+###  help on the %bassoc module%b
+   This module provides botnet channel naming, see help on the
+   %b'.assoc'%b command.
+%{help=assoc}
+###  %bassoc%b
+   Displays a list of current botnet channel names.
+%{+t}
+###  %bassoc%b <channel #> <name>
+   Creates a name for a channel.  The name will propagate across your
+   botnet (if you're hooked into one) and can be used instead of the
+   channel # when changing chat channels (see '.chat').  If you omit
+   the channel # and name, it will just dump a list of the current
+   named channels.
+
+###  %bassoc%b <*channel #> <name>
+   Creates a name for a local channel.  These channels are local to
+   the bot itself.
+
+###  %bassoc%b <[*]channel #>
+   Wipes out the name for a channel, if there was one.
+%{end}
+%{help=all}
+%{+t}
+###  commands for the %bassoc module%b
+  %bassoc%b
+%{end}
Index: eggdrop1.7/modules/assoc/modinfo
diff -u /dev/null eggdrop1.7/modules/assoc/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/assoc/modinfo	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,7 @@
+DESC:This module allows users to give party-line channels descriptive names
+DESC:instead of using only boring numbers.  The names are also distributed
+DESC:over the botnet.
+DESC:Don't mistaken this type of channel with the IRC channels. They're
+DESC:something totally unrelated.
+DESC:
+DESC:If you don't need this feature, you can safely DISABLE the module.
Index: eggdrop1.7/modules/blowfish/.cvsignore
diff -u /dev/null eggdrop1.7/modules/blowfish/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/blowfish/.cvsignore	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,8 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
Index: eggdrop1.7/modules/blowfish/Makefile.am
diff -u /dev/null eggdrop1.7/modules/blowfish/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/blowfish/Makefile.am	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,15 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:47 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+pkglib_LTLIBRARIES	= blowfish.la
+blowfish_la_SOURCES	= blowfish.c
+blowfish_la_LDFLAGS	= -module -avoid-version -no-undefined
+blowfish_la_LIBADD	= @TCL_LIBS@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/blowfish/bf_tab.h
diff -u /dev/null eggdrop1.7/modules/blowfish/bf_tab.h:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/blowfish/bf_tab.h	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,300 @@
+/*
+ * bf_tab.h -- part of blowfish.mod
+ *   Blowfish P-box and S-box tables
+ *
+ * $Id: bf_tab.h,v 1.1 2001/10/27 16:34:47 ite Exp $
+ */
+/*
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_BLOWFISH_BF_TAB_H
+#define _EGG_MOD_BLOWFISH_BF_TAB_H
+
+static u_32int_t initbf_P[bf_N + 2] =
+{
+  0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
+  0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
+  0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
+  0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
+  0x9216d5d9, 0x8979fb1b,
+};
+static u_32int_t initbf_S[4][256] =
+{
+  {
+    0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
+    0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
+    0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
+    0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
+    0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
+    0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+    0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
+    0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
+    0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
+    0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
+    0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
+    0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
+    0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
+    0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
+    0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+    0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
+    0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
+    0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
+    0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
+    0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
+    0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
+    0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
+    0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
+    0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+    0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
+    0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
+    0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
+    0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
+    0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
+    0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
+    0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
+    0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
+    0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+    0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
+    0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
+    0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
+    0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
+    0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
+    0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
+    0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
+    0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
+    0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+    0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
+    0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
+    0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
+    0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
+    0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
+    0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
+    0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
+    0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
+    0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+    0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
+    0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
+    0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
+    0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
+    0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
+    0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
+    0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
+    0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
+    0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+    0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
+    0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
+    0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
+    0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a},
+  {
+    0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
+    0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
+    0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
+    0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
+    0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
+    0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
+    0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
+    0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
+    0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
+    0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
+    0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
+    0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
+    0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
+    0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
+    0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
+    0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
+    0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
+    0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
+    0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
+    0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
+    0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
+    0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
+    0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
+    0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
+    0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
+    0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
+    0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
+    0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
+    0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
+    0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
+    0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
+    0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
+    0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
+    0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
+    0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
+    0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
+    0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
+    0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
+    0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
+    0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
+    0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
+    0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
+    0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
+    0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
+    0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
+    0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
+    0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
+    0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
+    0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
+    0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
+    0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
+    0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
+    0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
+    0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
+    0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
+    0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
+    0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
+    0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
+    0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
+    0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
+    0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
+    0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
+    0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
+    0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7},
+  {
+    0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
+    0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
+    0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
+    0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
+    0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
+    0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
+    0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
+    0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
+    0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
+    0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
+    0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
+    0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
+    0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
+    0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
+    0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
+    0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
+    0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
+    0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
+    0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
+    0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
+    0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
+    0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
+    0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
+    0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
+    0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
+    0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
+    0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
+    0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
+    0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
+    0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
+    0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
+    0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
+    0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
+    0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
+    0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
+    0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
+    0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
+    0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
+    0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
+    0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
+    0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
+    0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
+    0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
+    0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
+    0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
+    0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
+    0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
+    0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
+    0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
+    0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
+    0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
+    0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
+    0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
+    0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
+    0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
+    0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
+    0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
+    0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
+    0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
+    0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
+    0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
+    0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
+    0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
+    0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0},
+  {
+    0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
+    0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
+    0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+    0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
+    0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
+    0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
+    0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
+    0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
+    0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
+    0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
+    0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
+    0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+    0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
+    0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
+    0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
+    0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
+    0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
+    0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
+    0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
+    0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
+    0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+    0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
+    0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
+    0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
+    0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
+    0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
+    0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
+    0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
+    0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
+    0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+    0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
+    0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
+    0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
+    0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
+    0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
+    0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
+    0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
+    0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
+    0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+    0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
+    0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
+    0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
+    0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
+    0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
+    0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
+    0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
+    0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
+    0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+    0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
+    0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
+    0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
+    0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
+    0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
+    0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
+    0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
+    0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
+    0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+    0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
+    0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
+    0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
+    0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
+    0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
+    0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
+    0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6}
+};
+
+#endif				/* _EGG_MOD_BLOWFISH_BF_TAB_H */
Index: eggdrop1.7/modules/blowfish/blowfish.c
diff -u /dev/null eggdrop1.7/modules/blowfish/blowfish.c:1.1
--- /dev/null	Sat Oct 27 11:35:14 2001
+++ eggdrop1.7/modules/blowfish/blowfish.c	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,475 @@
+/*
+ * blowfish.c -- part of blowfish.mod
+ *   encryption and decryption of passwords
+ *
+ * $Id: blowfish.c,v 1.1 2001/10/27 16:34:47 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+/*
+ * This code was originally in the public domain.
+ */
+
+#define MODULE_NAME "encryption"
+#define MAKING_ENCRYPTION
+
+#include "lib/eggdrop/module.h"
+#include "blowfish.h"
+#include "bf_tab.h"		/* P-box P-array, S-box */
+
+#define start blowfish_LTX_start
+
+#undef global
+static Function *global = NULL;
+
+/* Each box takes up 4k so be very careful here */
+#define BOXES 3
+
+/* #define S(x,i) (bf_S[i][x.w.byte##i]) */
+#define S0(x) (bf_S[0][x.w.byte0])
+#define S1(x) (bf_S[1][x.w.byte1])
+#define S2(x) (bf_S[2][x.w.byte2])
+#define S3(x) (bf_S[3][x.w.byte3])
+#define bf_F(x) (((S0(x) + S1(x)) ^ S2(x)) + S3(x))
+#define ROUND(a,b,n) (a.word ^= bf_F(b) ^ bf_P[n])
+
+/* Keep a set of rotating P & S boxes */
+static struct box_t {
+  u_32int_t *P;
+  u_32int_t **S;
+  char key[81];
+  char keybytes;
+  time_t lastuse;
+} box[BOXES];
+
+/* static u_32int_t bf_P[bf_N+2]; */
+/* static u_32int_t bf_S[4][256]; */
+static u_32int_t *bf_P;
+static u_32int_t **bf_S;
+
+static void blowfish_encipher(u_32int_t * xl, u_32int_t * xr)
+{
+  union aword Xl;
+  union aword Xr;
+
+  Xl.word = *xl;
+  Xr.word = *xr;
+
+  Xl.word ^= bf_P[0];
+  ROUND(Xr, Xl, 1);
+  ROUND(Xl, Xr, 2);
+  ROUND(Xr, Xl, 3);
+  ROUND(Xl, Xr, 4);
+  ROUND(Xr, Xl, 5);
+  ROUND(Xl, Xr, 6);
+  ROUND(Xr, Xl, 7);
+  ROUND(Xl, Xr, 8);
+  ROUND(Xr, Xl, 9);
+  ROUND(Xl, Xr, 10);
+  ROUND(Xr, Xl, 11);
+  ROUND(Xl, Xr, 12);
+  ROUND(Xr, Xl, 13);
+  ROUND(Xl, Xr, 14);
+  ROUND(Xr, Xl, 15);
+  ROUND(Xl, Xr, 16);
+  Xr.word ^= bf_P[17];
+
+  *xr = Xl.word;
+  *xl = Xr.word;
+}
+
+static void blowfish_decipher(u_32int_t * xl, u_32int_t * xr)
+{
+  union aword Xl;
+  union aword Xr;
+
+  Xl.word = *xl;
+  Xr.word = *xr;
+
+  Xl.word ^= bf_P[17];
+  ROUND(Xr, Xl, 16);
+  ROUND(Xl, Xr, 15);
+  ROUND(Xr, Xl, 14);
+  ROUND(Xl, Xr, 13);
+  ROUND(Xr, Xl, 12);
+  ROUND(Xl, Xr, 11);
+  ROUND(Xr, Xl, 10);
+  ROUND(Xl, Xr, 9);
+  ROUND(Xr, Xl, 8);
+  ROUND(Xl, Xr, 7);
+  ROUND(Xr, Xl, 6);
+  ROUND(Xl, Xr, 5);
+  ROUND(Xr, Xl, 4);
+  ROUND(Xl, Xr, 3);
+  ROUND(Xr, Xl, 2);
+  ROUND(Xl, Xr, 1);
+  Xr.word ^= bf_P[0];
+
+  *xl = Xr.word;
+  *xr = Xl.word;
+}
+
+
+static void blowfish_report(int idx, int details)
+{
+  int i, tot = 0;
+
+  if (details) {
+    for (i = 0; i < BOXES; i++)
+      if (box[i].P != NULL)
+	tot++;
+    dprintf(idx, _("    Blowfish encryption module:\n"));
+    dprintf(idx, _("    %d of %d boxes in use:"), tot, BOXES);
+    for (i = 0; i < BOXES; i++)
+      if (box[i].P != NULL) {
+	dprintf(idx, _(" (age: %d)"), now - box[i].lastuse);
+      }
+    dprintf(idx, "\n");
+  }
+}
+
+static void blowfish_init(u_8bit_t * key, int keybytes)
+{
+  int i, j, bx;
+  time_t lowest;
+  u_32int_t data;
+  u_32int_t datal;
+  u_32int_t datar;
+  union aword temp;
+
+  /* drummer: Fixes crash if key is longer than 80 char. This may cause the key
+   *          to not end with \00 but that's no problem.
+   */
+  if (keybytes > 80)
+    keybytes = 80;
+
+  /* Is buffer already allocated for this? */
+  for (i = 0; i < BOXES; i++)
+    if (box[i].P != NULL) {
+      if ((box[i].keybytes == keybytes) &&
+	  (!strncmp((char *) (box[i].key), (char *) key, keybytes))) {
+	/* Match! */
+	box[i].lastuse = now;
+	bf_P = box[i].P;
+	bf_S = box[i].S;
+	return;
+      }
+    }
+  /* No pre-allocated buffer: make new one */
+  /* Set 'bx' to empty buffer */
+  bx = (-1);
+  for (i = 0; i < BOXES; i++) {
+    if (box[i].P == NULL) {
+      bx = i;
+      i = BOXES + 1;
+    }
+  }
+  if (bx < 0) {
+    /* Find oldest */
+    lowest = now;
+    for (i = 0; i < BOXES; i++)
+      if (box[i].lastuse <= lowest) {
+	lowest = box[i].lastuse;
+	bx = i;
+      }
+    free(box[bx].P);
+    for (i = 0; i < 4; i++)
+      free(box[bx].S[i]);
+    free(box[bx].S);
+  }
+  /* Initialize new buffer */
+  /* uh... this is over 4k */
+  box[bx].P = (u_32int_t *) malloc((bf_N + 2) * sizeof(u_32int_t));
+  box[bx].S = (u_32int_t **) malloc(4 * sizeof(u_32int_t *));
+  for (i = 0; i < 4; i++)
+    box[bx].S[i] = (u_32int_t *) malloc(256 * sizeof(u_32int_t));
+  bf_P = box[bx].P;
+  bf_S = box[bx].S;
+  box[bx].keybytes = keybytes;
+  strncpy(box[bx].key, key, keybytes);
+  box[bx].key[keybytes] = 0;
+  box[bx].lastuse = now;
+  /* Robey: Reset blowfish boxes to initial state
+   * (I guess normally it just keeps scrambling them, but here it's
+   * important to get the same encrypted result each time)
+   */
+  for (i = 0; i < bf_N + 2; i++)
+    bf_P[i] = initbf_P[i];
+  for (i = 0; i < 4; i++)
+    for (j = 0; j < 256; j++)
+      bf_S[i][j] = initbf_S[i][j];
+
+  j = 0;
+  if (keybytes > 0) { /* drummer: fixes crash if key=="" */
+    for (i = 0; i < bf_N + 2; ++i) {
+      temp.word = 0;
+      temp.w.byte0 = key[j];
+      temp.w.byte1 = key[(j + 1) % keybytes];
+      temp.w.byte2 = key[(j + 2) % keybytes];
+      temp.w.byte3 = key[(j + 3) % keybytes];
+      data = temp.word;
+      bf_P[i] = bf_P[i] ^ data;
+      j = (j + 4) % keybytes;
+    }
+  }
+  datal = 0x00000000;
+  datar = 0x00000000;
+  for (i = 0; i < bf_N + 2; i += 2) {
+    blowfish_encipher(&datal, &datar);
+    bf_P[i] = datal;
+    bf_P[i + 1] = datar;
+  }
+  for (i = 0; i < 4; ++i) {
+    for (j = 0; j < 256; j += 2) {
+      blowfish_encipher(&datal, &datar);
+      bf_S[i][j] = datal;
+      bf_S[i][j + 1] = datar;
+    }
+  }
+}
+
+/* Of course, if you change either of these, then your userfile will
+ * no longer be able to be shared. :)
+ */
+#define SALT1  0xdeadd061
+#define SALT2  0x23f6b095
+
+/* Convert 64-bit encrypted password to text for userfile */
+static char *base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+static int base64dec(char c)
+{
+  int i;
+
+  for (i = 0; i < 64; i++)
+    if (base64[i] == c)
+      return i;
+  return 0;
+}
+
+static void blowfish_encrypt_pass(char *text, char *new)
+{
+  u_32int_t left, right;
+  int n;
+  char *p;
+
+  blowfish_init((unsigned char *) text, strlen(text));
+  left = SALT1;
+  right = SALT2;
+  blowfish_encipher(&left, &right);
+  p = new;
+  *p++ = '+';			/* + means encrypted pass */
+  n = 32;
+  while (n > 0) {
+    *p++ = base64[right & 0x3f];
+    right = (right >> 6);
+    n -= 6;
+  }
+  n = 32;
+  while (n > 0) {
+    *p++ = base64[left & 0x3f];
+    left = (left >> 6);
+    n -= 6;
+  }
+  *p = 0;
+}
+
+/* Returned string must be freed when done with it!
+ */
+static char *encrypt_string(char *key, char *str)
+{
+  u_32int_t left, right;
+  unsigned char *p;
+  char *s, *dest, *d;
+  int i;
+
+  /* Pad fake string with 8 bytes to make sure there's enough */
+  s = (char *) malloc(strlen(str) + 9);
+  strcpy(s, str);
+  if ((!key) || (!key[0]))
+    return s;
+  p = s;
+  dest = (char *) malloc((strlen(str) + 9) * 2);
+  while (*p)
+    p++;
+  for (i = 0; i < 8; i++)
+    *p++ = 0;
+  blowfish_init((unsigned char *) key, strlen(key));
+  p = s;
+  d = dest;
+  while (*p) {
+    left = ((*p++) << 24);
+    left += ((*p++) << 16);
+    left += ((*p++) << 8);
+    left += (*p++);
+    right = ((*p++) << 24);
+    right += ((*p++) << 16);
+    right += ((*p++) << 8);
+    right += (*p++);
+    blowfish_encipher(&left, &right);
+    for (i = 0; i < 6; i++) {
+      *d++ = base64[right & 0x3f];
+      right = (right >> 6);
+    }
+    for (i = 0; i < 6; i++) {
+      *d++ = base64[left & 0x3f];
+      left = (left >> 6);
+    }
+  }
+  *d = 0;
+  free(s);
+  return dest;
+}
+
+/* Returned string must be freed when done with it!
+ */
+static char *decrypt_string(char *key, char *str)
+{
+  u_32int_t left, right;
+  char *p, *s, *dest, *d;
+  int i;
+
+  /* Pad encoded string with 0 bits in case it's bogus */
+  s = (char *) malloc(strlen(str) + 12);
+  strcpy(s, str);
+  if ((!key) || (!key[0]))
+    return s;
+  p = s;
+  dest = (char *) malloc(strlen(str) + 12);
+  while (*p)
+    p++;
+  for (i = 0; i < 12; i++)
+    *p++ = 0;
+  blowfish_init((unsigned char *) key, strlen(key));
+  p = s;
+  d = dest;
+  while (*p) {
+    right = 0L;
+    left = 0L;
+    for (i = 0; i < 6; i++)
+      right |= (base64dec(*p++)) << (i * 6);
+    for (i = 0; i < 6; i++)
+      left |= (base64dec(*p++)) << (i * 6);
+    blowfish_decipher(&left, &right);
+    for (i = 0; i < 4; i++)
+      *d++ = (left & (0xff << ((3 - i) * 8))) >> ((3 - i) * 8);
+    for (i = 0; i < 4; i++)
+      *d++ = (right & (0xff << ((3 - i) * 8))) >> ((3 - i) * 8);
+  }
+  *d = 0;
+  free(s);
+  return dest;
+}
+
+static int tcl_encrypt STDVAR
+{
+  char *p;
+
+  BADARGS(3, 3, " key string");
+  p = encrypt_string(argv[1], argv[2]);
+  Tcl_AppendResult(irp, p, NULL);
+  free(p);
+  return TCL_OK;
+}
+
+static int tcl_decrypt STDVAR
+{
+  char *p;
+
+  BADARGS(3, 3, " key string");
+  p = decrypt_string(argv[1], argv[2]);
+  Tcl_AppendResult(irp, p, NULL);
+  free(p);
+  return TCL_OK;
+}
+
+static int tcl_encpass STDVAR
+{
+  BADARGS(2, 2, " string");
+  if (strlen(argv[1]) > 0) {
+    char p[16];
+    blowfish_encrypt_pass(argv[1], p);
+    Tcl_AppendResult(irp, p, NULL);
+  } else
+    Tcl_AppendResult(irp, "", NULL);
+  return TCL_OK;
+}
+
+static tcl_cmds mytcls[] =
+{
+  {"encrypt",	tcl_encrypt},
+  {"decrypt",	tcl_decrypt},
+  {"encpass",	tcl_encpass},
+  {NULL,	NULL}
+};
+
+/* You CANT -module an encryption module , so -module just resets it.
+ */
+static char *blowfish_close()
+{
+  return _("You can't unload an encryption module");
+}
+
+EXPORT_SCOPE char *start(Function *);
+
+static Function blowfish_table[] =
+{
+  /* 0 - 3 */
+  (Function) start,
+  (Function) blowfish_close,
+  (Function) 0,
+  (Function) blowfish_report,
+  /* 4 - 7 */
+  (Function) encrypt_string,
+  (Function) decrypt_string,
+};
+
+char *start(Function *global_funcs)
+{
+  int i;
+
+  /* `global_funcs' is NULL if eggdrop is recovering from a restart.
+   *
+   * As the encryption module is never unloaded, only initialise stuff
+   * that got reset during restart, e.g. the tcl bindings.
+   */
+  if (global_funcs) {
+    global = global_funcs;
+
+    if (!module_rename("blowfish", MODULE_NAME))
+      return _("Already loaded.");
+    /* Initialize buffered boxes */
+    for (i = 0; i < BOXES; i++) {
+      box[i].P = NULL;
+      box[i].S = NULL;
+      box[i].key[0] = 0;
+      box[i].lastuse = 0L;
+    }
+    module_register(MODULE_NAME, blowfish_table, 2, 1);
+    if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+      module_undepend(MODULE_NAME);
+      return _("This module requires eggdrop1.7.0 or later");
+    }
+    add_hook(HOOK_ENCRYPT_PASS, (Function) blowfish_encrypt_pass);
+    add_hook(HOOK_ENCRYPT_STRING, (Function) encrypt_string);
+    add_hook(HOOK_DECRYPT_STRING, (Function) decrypt_string);
+  }
+  add_tcl_commands(mytcls);
+  return NULL;
+}
Index: eggdrop1.7/modules/blowfish/blowfish.h
diff -u /dev/null eggdrop1.7/modules/blowfish/blowfish.h:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/blowfish/blowfish.h	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,52 @@
+/*
+ * blowfish.h -- part of blowfish.mod
+ *
+ * $Id: blowfish.h,v 1.1 2001/10/27 16:34:47 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_BLOWFISH_BLOWFISH_H
+#define _EGG_MOD_BLOWFISH_BLOWFISH_H
+
+#define MAXKEYBYTES	56		/* 448 bits */
+#define bf_N		16
+#define noErr		 0
+#define DATAERROR	-1
+#define KEYBYTES	 8
+
+union aword {
+  u_32int_t word;
+  u_8bit_t byte[4];
+  struct {
+#ifdef WORDS_BIGENDIAN
+    unsigned int byte0:8;
+    unsigned int byte1:8;
+    unsigned int byte2:8;
+    unsigned int byte3:8;
+#else				/* !WORDS_BIGENDIAN */
+    unsigned int byte3:8;
+    unsigned int byte2:8;
+    unsigned int byte1:8;
+    unsigned int byte0:8;
+#endif				/* !WORDS_BIGENDIAN */
+  } w;
+};
+
+#endif				/* _EGG_MOD_BLOWFISH_BLOWFISH_H */
Index: eggdrop1.7/modules/blowfish/modinfo
diff -u /dev/null eggdrop1.7/modules/blowfish/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/blowfish/modinfo	Sat Oct 27 11:34:47 2001
@@ -0,0 +1,5 @@
+DESC:The blowfish module provides encryption support for eggdrop. You always
+DESC:need to load an encryption module to run eggdrop.  Currently, blowfish
+DESC:is the only encryption module so you don't have much choice ...
+DESC:
+DESC:ENABLE this module.
Index: eggdrop1.7/modules/channels/.cvsignore
diff -u /dev/null eggdrop1.7/modules/channels/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/.cvsignore	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,8 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
Index: eggdrop1.7/modules/channels/Makefile.am
diff -u /dev/null eggdrop1.7/modules/channels/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/Makefile.am	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,15 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:48 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+pkglib_LTLIBRARIES	= channels.la
+channels_la_SOURCES	= channels.c
+channels_la_LDFLAGS	= -module -avoid-version -no-undefined
+channels_la_LIBADD	= @TCL_LIBS@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/channels/channels.c
diff -u /dev/null eggdrop1.7/modules/channels/channels.c:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/channels.c	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,871 @@
+/*
+ * channels.c -- part of channels.mod
+ *   support for channels within the bot
+ *
+ * $Id: channels.c,v 1.1 2001/10/27 16:34:48 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define MODULE_NAME "channels"
+#define MAKING_CHANNELS
+#include <sys/stat.h>
+#include "lib/eggdrop/module.h"
+
+#define start channels_LTX_start
+
+static Function *global		= NULL;
+
+static int  setstatic;
+static int  use_info;
+static int  ban_time;
+static int  exempt_time;		/* If exempt_time = 0, never remove
+					   them */
+static int  invite_time;		/* If invite_time = 0, never remove
+					   them */
+static char chanfile[121];
+static int  chan_hack;
+static int  quiet_save;
+static char glob_chanmode[64];		/* Default chanmode (drummer,990731) */
+static struct udef_struct *udef;
+static int global_stopnethack_mode;
+static int global_revenge_mode;
+static int global_idle_kick;		/* Default idle-kick setting. */
+static int global_aop_min;
+static int global_aop_max;
+
+/* Global channel settings (drummer/dw) */
+static char glob_chanset[512];
+
+/* Global flood settings */
+static int gfld_chan_thr;
+static int gfld_chan_time;
+static int gfld_deop_thr;
+static int gfld_deop_time;
+static int gfld_kick_thr;
+static int gfld_kick_time;
+static int gfld_join_thr;
+static int gfld_join_time;
+static int gfld_ctcp_thr;
+static int gfld_ctcp_time;
+static int gfld_nick_thr;
+static int gfld_nick_time;
+
+static bind_table_t *BT_dcc, *BT_chon;
+
+#include "channels.h"
+#include "cmdschan.c"
+#include "tclchan.c"
+#include "userchan.c"
+#include "udefchan.c"
+
+
+static void set_mode_protect(struct chanset_t *chan, char *set)
+{
+  int i, pos = 1;
+  char *s, *s1;
+
+  /* Clear old modes */
+  chan->mode_mns_prot = chan->mode_pls_prot = 0;
+  chan->limit_prot = 0;
+  chan->key_prot[0] = 0;
+  for (s = newsplit(&set); *s; s++) {
+    i = 0;
+    switch (*s) {
+    case '+':
+      pos = 1;
+      break;
+    case '-':
+      pos = 0;
+      break;
+    case 'i':
+      i = CHANINV;
+      break;
+    case 'p':
+      i = CHANPRIV;
+      break;
+    case 's':
+      i = CHANSEC;
+      break;
+    case 'm':
+      i = CHANMODER;
+      break;
+    case 'c':
+      i = CHANNOCLR;
+      break;
+    case 'R':
+      i = CHANREGON;
+      break;
+    case 't':
+      i = CHANTOPIC;
+      break;
+    case 'n':
+      i = CHANNOMSG;
+      break;
+    case 'a':
+      i = CHANANON;
+      break;
+    case 'q':
+      i = CHANQUIET;
+      break;
+    case 'l':
+      i = CHANLIMIT;
+      chan->limit_prot = 0;
+      if (pos) {
+	s1 = newsplit(&set);
+	if (s1[0])
+	  chan->limit_prot = atoi(s1);
+      }
+      break;
+    case 'k':
+      i = CHANKEY;
+      chan->key_prot[0] = 0;
+      if (pos) {
+	s1 = newsplit(&set);
+	if (s1[0])
+	  strcpy(chan->key_prot, s1);
+      }
+      break;
+    }
+    if (i) {
+      if (pos) {
+	chan->mode_pls_prot |= i;
+	chan->mode_mns_prot &= ~i;
+      } else {
+	chan->mode_pls_prot &= ~i;
+	chan->mode_mns_prot |= i;
+      }
+    }
+  }
+  /* Prevents a +s-p +p-s flood  (fixed by drummer) */
+  if (chan->mode_pls_prot & CHANSEC)
+    chan->mode_pls_prot &= ~CHANPRIV;
+}
+
+static void get_mode_protect(struct chanset_t *chan, char *s)
+{
+  char *p = s, s1[121];
+  int ok = 0, i, tst;
+
+  s1[0] = 0;
+  for (i = 0; i < 2; i++) {
+    ok = 0;
+    if (i == 0) {
+      tst = chan->mode_pls_prot;
+      if ((tst) || (chan->limit_prot != 0) || (chan->key_prot[0]))
+	*p++ = '+';
+      if (chan->limit_prot != 0) {
+	*p++ = 'l';
+	sprintf(&s1[strlen(s1)], "%d ", chan->limit_prot);
+      }
+      if (chan->key_prot[0]) {
+	*p++ = 'k';
+	sprintf(&s1[strlen(s1)], "%s ", chan->key_prot);
+      }
+    } else {
+      tst = chan->mode_mns_prot;
+      if (tst)
+	*p++ = '-';
+      if (tst & CHANKEY)
+	*p++ = 'k';
+      if (tst & CHANLIMIT)
+	*p++ = 'l';
+    }
+    if (tst & CHANINV)
+      *p++ = 'i';
+    if (tst & CHANPRIV)
+      *p++ = 'p';
+    if (tst & CHANSEC)
+      *p++ = 's';
+    if (tst & CHANMODER)
+      *p++ = 'm';
+    if (tst & CHANNOCLR)
+      *p++ = 'c';
+    if (tst & CHANREGON)
+      *p++ = 'R';
+    if (tst & CHANTOPIC)
+      *p++ = 't';
+    if (tst & CHANNOMSG)
+      *p++ = 'n';
+    if (tst & CHANANON)
+      *p++ = 'a';
+    if (tst & CHANQUIET)
+      *p++ = 'q';
+  }
+  *p = 0;
+  if (s1[0]) {
+    s1[strlen(s1) - 1] = 0;
+    strcat(s, " ");
+    strcat(s, s1);
+  }
+}
+
+/* Returns true if this is one of the channel masks
+ */
+static int ismodeline(masklist *m, char *user)
+{
+  for (; m && m->mask[0]; m = m->next)  
+    if (!irccmp(m->mask, user))
+      return 1;
+  return 0;
+}
+
+/* Returns true if user matches one of the masklist -- drummer
+ */
+static int ismasked(masklist *m, char *user)
+{
+  for (; m && m->mask[0]; m = m->next)
+    if (wild_match(m->mask, user))
+      return 1;
+  return 0;
+}
+
+/* Unlink chanset element from chanset list.
+ */
+inline static int chanset_unlink(struct chanset_t *chan)
+{
+  struct chanset_t	*c, *c_old = NULL;
+
+  for (c = chanset; c; c_old = c, c = c->next) {
+    if (c == chan) {
+      if (c_old)
+	c_old->next = c->next;
+      else
+	chanset = c->next;
+      return 1;
+    }
+  }
+  return 0;
+}
+
+/* Completely removes a channel.
+ *
+ * This includes the removal of all channel-bans, -exempts and -invites, as
+ * well as all user flags related to the channel.
+ */
+static void remove_channel(struct chanset_t *chan)
+{
+   int		 i;
+   module_entry	*me;
+
+   /* Remove the channel from the list, so that noone can pull it
+      away from under our feet during the check_tcl_part() call. */
+   (void) chanset_unlink(chan);
+
+   if ((me = module_find("irc", 1, 3)) != NULL)
+     (me->funcs[IRC_DO_CHANNEL_PART])(chan);
+
+   clear_channel(chan, 0);
+   noshare = 1;
+   /* Remove channel-bans */
+   while (chan->bans)
+     u_delban(chan, chan->bans->mask, 1);
+   /* Remove channel-exempts */
+   while (chan->exempts)
+     u_delexempt(chan, chan->exempts->mask, 1);
+   /* Remove channel-invites */
+   while (chan->invites)
+     u_delinvite(chan, chan->invites->mask, 1);
+   /* Remove channel specific user flags */
+   user_del_chan(chan->dname);
+   noshare = 0;
+   free(chan->channel.key);
+   for (i = 0; i < 6 && chan->cmode[i].op; i++)
+     free(chan->cmode[i].op);
+   if (chan->key)
+     free(chan->key);
+   if (chan->rmkey)
+     free(chan->rmkey);
+   free(chan);
+}
+
+/* Bind this to chon and *if* the users console channel == ***
+ * then set it to a specific channel
+ */
+static int channels_chon(char *handle, int idx)
+{
+  struct flag_record fr = {FR_CHAN | FR_ANYWH | FR_GLOBAL, 0, 0, 0, 0, 0};
+  int find, found = 0;
+  struct chanset_t *chan = chanset;
+
+  if (dcc[idx].type == &DCC_CHAT) {
+    if (!findchan_by_dname(dcc[idx].u.chat->con_chan) &&
+	((dcc[idx].u.chat->con_chan[0] != '*') ||
+	 (dcc[idx].u.chat->con_chan[1] != 0))) {
+      get_user_flagrec(dcc[idx].user, &fr, NULL);
+      if (glob_op(fr))
+	found = 1;
+      if (chan_owner(fr))
+	find = USER_OWNER;
+      else if (chan_master(fr))
+	find = USER_MASTER;
+      else
+	find = USER_OP;
+      fr.match = FR_CHAN;
+      while (chan && !found) {
+	get_user_flagrec(dcc[idx].user, &fr, chan->dname);
+	if (fr.chan & find)
+	  found = 1;
+	else
+	  chan = chan->next;
+      }
+      if (!chan)
+	chan = chanset;
+      if (chan)
+	strcpy(dcc[idx].u.chat->con_chan, chan->dname);
+      else
+	strcpy(dcc[idx].u.chat->con_chan, "*");
+    }
+  }
+  return 0;
+}
+
+static char *convert_element(char *src, char *dst)
+{
+  int flags;
+
+  Tcl_ScanElement(src, &flags);
+  Tcl_ConvertElement(src, dst, flags);
+  return dst;
+}
+
+#define PLSMNS(x) (x ? '+' : '-')
+
+/*
+ * Note:
+ *  - We write chanmode "" too, so that the bot won't use default-chanmode
+ *    instead of ""
+ */
+static void write_channels()
+{
+  FILE *f;
+  char s[121], w[1024], w2[1024], name[163];
+  struct chanset_t *chan;
+  struct udef_struct *ul;
+
+  if (!chanfile[0])
+    return;
+  sprintf(s, "%s~new", chanfile);
+  f = fopen(s, "w");
+  chmod(s, userfile_perm);
+  if (f == NULL) {
+    putlog(LOG_MISC, "*", "ERROR writing channel file.");
+    return;
+  }
+  if (!quiet_save)
+    putlog(LOG_MISC, "*", "Writing channel file ...");
+  fprintf(f, "#Dynamic Channel File for %s (%s) -- written %s\n",
+	  origbotname, ver, ctime(&now));
+  for (chan = chanset; chan; chan = chan->next) {
+    convert_element(chan->dname, name);
+    get_mode_protect(chan, w);
+    convert_element(w, w2);
+    fprintf(f, "channel %s %s%schanmode %s idle-kick %d stopnethack-mode %d \
+revenge-mode %d flood-chan %d:%d flood-ctcp %d:%d flood-join %d:%d \
+flood-kick %d:%d flood-deop %d:%d flood-nick %d:%d aop-delay %d:%d \
+%cenforcebans %cdynamicbans %cuserbans %cautoop %cbitch \
+%cgreet %cprotectops %cprotectfriends %cdontkickops \
+%cstatuslog %crevenge %crevengebot %cautovoice %csecret \
+%cshared %ccycle %cinactive %cdynamicexempts %cuserexempts \
+%cdynamicinvites %cuserinvites %cnodesynch ",
+	channel_static(chan) ? "set" : "add",
+	name,
+	channel_static(chan) ? " " : " { ",
+	w2,
+	chan->idle_kick, /* idle-kick 0 is same as dont-idle-kick (less code)*/
+	chan->stopnethack_mode,
+        chan->revenge_mode,
+	chan->flood_pub_thr, chan->flood_pub_time,
+        chan->flood_ctcp_thr, chan->flood_ctcp_time,
+        chan->flood_join_thr, chan->flood_join_time,
+        chan->flood_kick_thr, chan->flood_kick_time,
+        chan->flood_deop_thr, chan->flood_deop_time,
+	chan->flood_nick_thr, chan->flood_nick_time,
+	chan->aop_min, chan->aop_max,
+	PLSMNS(channel_enforcebans(chan)),
+	PLSMNS(channel_dynamicbans(chan)),
+	PLSMNS(!channel_nouserbans(chan)),
+	PLSMNS(channel_autoop(chan)),
+	PLSMNS(channel_bitch(chan)),
+	PLSMNS(channel_greet(chan)),
+	PLSMNS(channel_protectops(chan)),
+        PLSMNS(channel_protectfriends(chan)),
+	PLSMNS(channel_dontkickops(chan)),
+	PLSMNS(channel_logstatus(chan)),
+	PLSMNS(channel_revenge(chan)),
+	PLSMNS(channel_revengebot(chan)),
+	PLSMNS(channel_autovoice(chan)),
+	PLSMNS(channel_secret(chan)),
+	PLSMNS(channel_shared(chan)),
+	PLSMNS(channel_cycle(chan)),
+	PLSMNS(channel_inactive(chan)),
+        PLSMNS(channel_dynamicexempts(chan)),
+        PLSMNS(!channel_nouserexempts(chan)),
+ 	PLSMNS(channel_dynamicinvites(chan)),
+        PLSMNS(!channel_nouserinvites(chan)),
+	PLSMNS(channel_nodesynch(chan)));
+    for (ul = udef; ul; ul = ul->next) {
+      if (ul->defined && ul->name) {
+	if (ul->type == UDEF_FLAG)
+	  fprintf(f, "%c%s%s ", getudef(ul->values, chan->dname) ? '+' : '-',
+		  "udef-flag-", ul->name);
+	else if (ul->type == UDEF_INT)
+	  fprintf(f, "%s%s %d ", "udef-int-", ul->name, getudef(ul->values,
+		  chan->dname));
+	else if (ul->type == UDEF_STR) {
+		char *p;
+		p = (char *)getudef(ul->values, chan->dname);
+		if (!p) strcpy(s, "{}");
+		fprintf(f, "udef-str-%s %s ", ul->name, p);
+	}
+	else
+	  debug1("UDEF-ERROR: unknown type %d", ul->type);
+      }
+    }
+    fprintf(f, "%s\n", channel_static(chan) ? "" : "}");
+    if (fflush(f)) {
+      putlog(LOG_MISC, "*", "ERROR writing channel file.");
+      fclose(f);
+      return;
+    }
+  }
+  fclose(f);
+  unlink(chanfile);
+  movefile(s, chanfile);
+}
+
+static void read_channels(int create)
+{
+  struct chanset_t *chan, *chan_next;
+
+  if (!chanfile[0])
+    return;
+  for (chan = chanset; chan; chan = chan->next)
+    if (!channel_static(chan))
+      chan->status |= CHAN_FLAGGED;
+  chan_hack = 1;
+  if (!readtclprog(chanfile) && create) {
+    FILE *f;
+
+    /* Assume file isnt there & therfore make it */
+    putlog(LOG_MISC, "*", "Creating channel file");
+    f = fopen(chanfile, "w");
+    if (!f)
+      putlog(LOG_MISC, "*", "Couldn't create channel file: %s.  Dropping",
+	     chanfile);
+    else
+      fclose(f);
+  }
+  chan_hack = 0;
+  for (chan = chanset; chan; chan = chan_next) {
+    chan_next = chan->next;
+    if (chan->status & CHAN_FLAGGED) {
+      putlog(LOG_MISC, "*", "No longer supporting channel %s", chan->dname);
+      remove_channel(chan);
+    }
+  }
+}
+
+static void backup_chanfile()
+{
+  char s[125];
+
+  putlog(LOG_MISC, "*", "Backing up channel file...");
+  snprintf(s, sizeof s, "%s~bak", chanfile);
+  copyfile(chanfile, s);
+}
+
+static void channels_prerehash()
+{
+  struct chanset_t *chan;
+
+  /* Flag will be cleared as the channels are re-added by the
+   * config file. Any still flagged afterwards will be removed.
+   */
+  for (chan = chanset; chan; chan = chan->next) {
+    chan->status |= CHAN_FLAGGED;
+    /* Flag is only added to channels read from config file */
+    if (chan->status & CHAN_STATIC)
+      chan->status &= ~CHAN_STATIC;
+  }
+  setstatic = 1;
+}
+
+static void channels_rehash()
+{
+  struct chanset_t *chan;
+
+  setstatic = 0;
+  read_channels(1);
+  /* Remove any extra channels, by checking the flag. */
+  chan = chanset;
+  for (chan = chanset; chan;) {
+    if (chan->status & CHAN_FLAGGED) {
+      putlog(LOG_MISC, "*", "No longer supporting channel %s", chan->dname);
+      remove_channel(chan);
+      chan = chanset;
+    } else
+      chan = chan->next;
+  }
+}
+
+static cmd_t my_chon[] =
+{
+  {"*",		"",	(Function) channels_chon,	"channels:chon"},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+static void channels_report(int idx, int details)
+{
+  struct chanset_t *chan;
+  int i;
+  char s[1024], s2[100];
+  struct flag_record fr = {FR_CHAN | FR_GLOBAL, 0, 0, 0, 0, 0};
+
+  for (chan = chanset; chan; chan = chan->next) {
+    if (idx != DP_STDOUT)
+      get_user_flagrec(dcc[idx].user, &fr, chan->dname);
+    if ((idx == DP_STDOUT) || glob_master(fr) || chan_master(fr)) {
+      s[0] = 0;
+      if (channel_greet(chan))
+	strcat(s, "greet, ");
+      if (channel_autoop(chan))
+	strcat(s, "auto-op, ");
+      if (channel_bitch(chan))
+	strcat(s, "bitch, ");
+      if (s[0])
+	s[strlen(s) - 2] = 0;
+      if (!s[0])
+	strcpy(s, _("lurking"));
+      get_mode_protect(chan, s2);
+      if (!channel_inactive(chan)) {
+	if (channel_active(chan)) {
+	  /* If it's a !chan, we want to display it's unique name too <cybah> */
+	  if (chan->dname[0]=='!') {
+	    dprintf(idx, "    %-10s: %2d member%s enforcing \"%s\" (%s), "
+	            "unique name %s\n", chan->dname, chan->channel.members,
+	            (chan->channel.members==1) ? "," : "s,", s2, s, chan->name);
+	  } else {
+	    dprintf(idx, "    %-10s: %2d member%s enforcing \"%s\" (%s)\n",
+	            chan->dname, chan->channel.members,
+	            chan->channel.members == 1 ? "," : "s,", s2, s);
+	  }
+	} else {
+	  dprintf(idx, "    %-10s: (%s), enforcing \"%s\"  (%s)\n", chan->dname,
+		  channel_pending(chan) ? "pending" : "not on channel", s2, s);
+	}
+      } else {
+	dprintf(idx, "    %-10s: channel is set +inactive\n",
+		chan->dname);
+      }
+      if (details) {
+	s[0] = 0;
+	i = 0;
+	if (channel_enforcebans(chan))
+	  i += my_strcpy(s + i, "enforce-bans ");
+	if (channel_dynamicbans(chan))
+	  i += my_strcpy(s + i, "dynamic-bans ");
+	if (channel_nouserbans(chan))
+	  i += my_strcpy(s + i, "forbid-user-bans ");
+	if (channel_autoop(chan))
+	  i += my_strcpy(s + i, "op-on-join ");
+	if (channel_bitch(chan))
+	  i += my_strcpy(s + i, "bitch ");
+	if (channel_greet(chan))
+	  i += my_strcpy(s + i, "greet ");
+	if (channel_protectops(chan))
+	  i += my_strcpy(s + i, "protect-ops ");
+        if (channel_protectfriends(chan))
+          i += my_strcpy(s + i, "protect-friends ");
+	if (channel_dontkickops(chan))
+	  i += my_strcpy(s + i, "dont-kick-ops ");
+	if (channel_logstatus(chan))
+	  i += my_strcpy(s + i, "log-status ");
+	if (channel_revenge(chan))
+	  i += my_strcpy(s + i, "revenge ");
+	if (channel_secret(chan))
+	  i += my_strcpy(s + i, "secret ");
+	if (channel_shared(chan))
+	  i += my_strcpy(s + i, "shared ");
+	if (!channel_static(chan))
+	  i += my_strcpy(s + i, "dynamic ");
+	if (channel_autovoice(chan))
+	  i += my_strcpy(s + i, "autovoice ");
+	if (channel_cycle(chan))
+	  i += my_strcpy(s + i, "cycle ");
+	if (channel_dynamicexempts(chan))
+	  i += my_strcpy(s + i, "dynamic-exempts ");
+	if (channel_nouserexempts(chan))
+	  i += my_strcpy(s + i, "forbid-user-exempts ");
+	if (channel_dynamicinvites(chan))
+	  i += my_strcpy(s + i, "dynamic-invites ");
+	if (channel_nouserinvites(chan))
+	  i += my_strcpy(s + i, "forbid-user-invites ");
+	if (channel_inactive(chan))
+	  i += my_strcpy(s + i, "inactive ");
+	if (channel_nodesynch(chan))
+	  i += my_strcpy(s + i, "nodesynch ");
+	dprintf(idx, "      Options: %s\n", s);
+	if (chan->idle_kick)
+	  dprintf(idx, "      Kicking idle users after %d min\n",
+		  chan->idle_kick);
+	if (chan->stopnethack_mode)
+	  dprintf(idx, "      stopnethack-mode %d\n",
+		  chan->stopnethack_mode);
+        if (chan->revenge_mode)
+          dprintf(idx, "      revenge-mode %d\n",
+                  chan->revenge_mode);
+      }
+    }
+  }
+  if (details) {
+    dprintf(idx, "    Bans last %d mins.\n", ban_time);
+    dprintf(idx, "    Exemptions last %d mins.\n", exempt_time);
+    dprintf(idx, "    Invitations last %d mins.\n", invite_time);
+  }
+}
+
+static char *traced_globchanset(ClientData cdata, Tcl_Interp * irp,
+				char *name1, char *name2, int flags)
+{
+  char *s;
+  char *t;
+  int i;
+  int items;
+  char **item;
+
+  if (flags & (TCL_TRACE_READS | TCL_TRACE_UNSETS)) {
+    Tcl_SetVar2(interp, name1, name2, glob_chanset, TCL_GLOBAL_ONLY);
+    if (flags & TCL_TRACE_UNSETS)
+      Tcl_TraceVar(interp, "global-chanset",
+	    TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+	    traced_globchanset, NULL);
+  } else { /* Write */
+    s = Tcl_GetVar2(interp, name1, name2, TCL_GLOBAL_ONLY);
+    Tcl_SplitList(interp, s, &items, &item);
+    for (i = 0; i<items; i++) {
+      if (!(item[i]) || (strlen(item[i]) < 2)) continue;
+      s = glob_chanset;
+      while (s[0]) {
+	t = strchr(s, ' '); /* Can't be NULL coz of the extra space */
+	t[0] = 0;
+	if (!strcmp(s + 1, item[i] + 1)) {
+	  s[0] = item[i][0]; /* +- */
+	  t[0] = ' ';
+	  break;
+	}
+	t[0] = ' ';
+	s = t + 1;
+      }
+    }
+    if (item) /* hmm it cant be 0 */
+      Tcl_Free((char *) item);
+    Tcl_SetVar2(interp, name1, name2, glob_chanset, TCL_GLOBAL_ONLY);
+  }
+  return NULL;
+}
+
+static tcl_ints my_tcl_ints[] =
+{
+  {"share-greet",		NULL,				0},
+  {"use-info",			&use_info,			0},
+  {"ban-time",			&ban_time,			0},
+  {"exempt-time",		&exempt_time,			0},
+  {"invite-time",		&invite_time,			0},
+  {"quiet-save",		&quiet_save,			0},
+  {"global-stopnethack-mode",	&global_stopnethack_mode,	0},
+  {"global-revenge-mode",       &global_revenge_mode,           0},
+  {"global-idle-kick",		&global_idle_kick,		0},
+  {NULL,			NULL,				0}
+};
+
+static tcl_coups mychan_tcl_coups[] =
+{
+  {"global-flood-chan",		&gfld_chan_thr,		&gfld_chan_time},
+  {"global-flood-deop",		&gfld_deop_thr,		&gfld_deop_time},
+  {"global-flood-kick",		&gfld_kick_thr,		&gfld_kick_time},
+  {"global-flood-join",		&gfld_join_thr,		&gfld_join_time},
+  {"global-flood-ctcp",		&gfld_ctcp_thr,		&gfld_ctcp_time},
+  {"global-flood-nick",		&gfld_nick_thr, 	&gfld_nick_time},
+  {"global-aop-delay",		&global_aop_min,	&global_aop_max},
+  {NULL,			NULL,			NULL}
+};
+
+static tcl_strings my_tcl_strings[] =
+{
+  {"chanfile",		chanfile,	120,	STR_PROTECT},
+  {"global-chanmode",	glob_chanmode,	64,	0},
+  {NULL,		NULL,		0,	0}
+};
+
+static char *channels_close()
+{
+  write_channels();
+  free_udef(udef);
+  if (BT_chon) rem_builtins2(BT_chon, my_chon);
+  if (BT_dcc) rem_builtins2(BT_dcc, C_dcc_irc);
+  rem_tcl_commands(channels_cmds);
+  rem_tcl_strings(my_tcl_strings);
+  rem_tcl_ints(my_tcl_ints);
+  rem_tcl_coups(mychan_tcl_coups);
+  del_hook(HOOK_USERFILE, (Function) channels_writeuserfile);
+  del_hook(HOOK_BACKUP, (Function) backup_chanfile);
+  del_hook(HOOK_REHASH, (Function) channels_rehash);
+  del_hook(HOOK_PRE_REHASH, (Function) channels_prerehash);
+  del_hook(HOOK_MINUTELY, (Function) check_expired_bans);
+  del_hook(HOOK_MINUTELY, (Function) check_expired_exempts);
+  del_hook(HOOK_MINUTELY, (Function) check_expired_invites);
+  Tcl_UntraceVar(interp, "global-chanset",
+		 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+		 traced_globchanset, NULL);
+  rem_help_reference("channels.help");
+  rem_help_reference("chaninfo.help");
+  module_undepend(MODULE_NAME);
+  return NULL;
+}
+
+EXPORT_SCOPE char *start();
+
+static Function channels_table[] =
+{
+  /* 0 - 3 */
+  (Function) start,
+  (Function) channels_close,
+  (Function) 0,
+  (Function) channels_report,
+  /* 4 - 7 */
+  (Function) u_setsticky_mask,
+  (Function) u_delban,
+  (Function) u_addban,
+  (Function) write_bans,
+  /* 8 - 11 */
+  (Function) get_chanrec,
+  (Function) add_chanrec,
+  (Function) del_chanrec,
+  (Function) set_handle_chaninfo,
+  /* 12 - 15 */
+  (Function) 0,
+  (Function) u_match_mask,
+  (Function) u_equals_mask,
+  (Function) clear_channel,
+  /* 16 - 19 */
+  (Function) set_handle_laston,
+  (Function) & ban_time,
+  (Function) & use_info,
+  (Function) get_handle_chaninfo,
+  /* 20 - 23 */
+  (Function) u_sticky_mask,
+  (Function) ismasked,
+  (Function) add_chanrec_by_handle,
+  (Function) NULL, /* [23] used to be isexempted() <cybah> */
+  /* 24 - 27 */
+  (Function) & exempt_time,
+  (Function) NULL, /* [25] used to be isinvited() <cybah> */
+  (Function) & invite_time,
+  (Function) NULL,
+  /* 28 - 31 */
+  (Function) NULL, /* [28] used to be u_setsticky_exempt() <cybah> */
+  (Function) u_delexempt,
+  (Function) u_addexempt,
+  (Function) NULL,
+  /* 32 - 35 */
+  (Function) NULL,/* [32] used to be u_sticky_exempt() <cybah> */
+  (Function) NULL,
+  (Function) NULL,	/* [34] used to be killchanset().	*/
+  (Function) u_delinvite,
+  /* 36 - 39 */
+  (Function) u_addinvite,
+  (Function) tcl_channel_add,
+  (Function) tcl_channel_modify,
+  (Function) write_exempts,
+  /* 40 - 43 */
+  (Function) write_invites,
+  (Function) ismodeline,
+  (Function) initudef,
+  (Function) ngetudef,
+  /* 44 - 47 */
+  (Function) expired_mask,
+  (Function) remove_channel,
+};
+
+char *start(Function * global_funcs)
+{
+  global = global_funcs;
+
+  gfld_chan_thr = 10;
+  gfld_chan_time = 60;
+  gfld_deop_thr = 3;
+  gfld_deop_time = 10;
+  gfld_kick_thr = 3;
+  gfld_kick_time = 10;
+  gfld_join_thr = 5;
+  gfld_join_time = 60;
+  gfld_ctcp_thr = 5;
+  gfld_ctcp_time = 60;
+  global_idle_kick = 0;
+  global_aop_min = 5;
+  global_aop_max = 30;
+  setstatic = 0;
+  use_info = 1;
+  ban_time = 60;
+  exempt_time = 0;
+  invite_time = 0;
+  strcpy(chanfile, "chanfile");
+  chan_hack = 0;
+  quiet_save = 0;
+  strcpy(glob_chanmode, "nt");
+  udef = NULL;
+  global_stopnethack_mode = 0;
+  global_revenge_mode = 1;
+  strcpy(glob_chanset, "\
+-enforcebans +dynamicbans +userbans -autoop -bitch +greet \
++protectops +statuslog -revenge -secret -autovoice +cycle \
++dontkickops -inactive -protectfriends +shared \
++userexempts +dynamicexempts +userinvites +dynamicinvites -revengebot \
+-nodesynch" /* Do not remove this extra space: */ " ");
+  module_register(MODULE_NAME, channels_table, 1, 0);
+  if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+    module_undepend(MODULE_NAME);
+    return "This module needs eggdrop1.7.0 or later";
+  }
+  add_hook(HOOK_MINUTELY, (Function) check_expired_bans);
+  add_hook(HOOK_MINUTELY, (Function) check_expired_exempts);
+  add_hook(HOOK_MINUTELY, (Function) check_expired_invites);
+  add_hook(HOOK_USERFILE, (Function) channels_writeuserfile);
+  add_hook(HOOK_BACKUP, (Function) backup_chanfile);
+  add_hook(HOOK_REHASH, (Function) channels_rehash);
+  add_hook(HOOK_PRE_REHASH, (Function) channels_prerehash);
+  Tcl_TraceVar(interp, "global-chanset",
+	       TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+	       traced_globchanset, NULL);
+  BT_chon = find_bind_table2("chon");
+  if (BT_chon) add_builtins2(BT_chon, my_chon);
+  BT_dcc = find_bind_table2("dcc");
+  if (BT_dcc) add_builtins2(BT_dcc, C_dcc_irc);
+  add_tcl_commands(channels_cmds);
+  add_tcl_strings(my_tcl_strings);
+  add_help_reference("channels.help");
+  add_help_reference("chaninfo.help");
+  my_tcl_ints[0].val = &share_greet;
+  add_tcl_ints(my_tcl_ints);
+  add_tcl_coups(mychan_tcl_coups);
+  read_channels(0);
+  setstatic = 1;
+  return NULL;
+}
Index: eggdrop1.7/modules/channels/channels.h
diff -u /dev/null eggdrop1.7/modules/channels/channels.h:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/channels.h	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,188 @@
+/*
+ * channels.h -- part of channels.mod
+ *
+ * $Id: channels.h,v 1.1 2001/10/27 16:34:48 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_CHANNELS_CHANNELS_H
+#define _EGG_MOD_CHANNELS_CHANNELS_H
+
+/* User defined chanmodes/settings */
+#define UDEF_FLAG 1
+#define UDEF_INT 2
+#define UDEF_STR 3
+
+#define MASKREASON_MAX	160	/* Max length of ban/invite/exempt/etc.
+				   reasons.				*/
+#define MASKREASON_LEN	(MASKREASON_MAX + 1)
+
+
+#ifdef MAKING_CHANNELS
+
+/* Structure for udef channel values. Udef setting have one such
+ * structure for each channel where they have a defined value.
+ */
+struct udef_chans {
+  struct udef_chans *next;	/* Ptr to next value.			*/
+  char *chan;			/* Dname of channel name.		*/
+  int value;			/* Actual value.			*/
+};
+
+/* Structure for user defined channel settings.
+ */
+struct udef_struct {
+  struct udef_struct *next;	/* Ptr to next setting.			*/
+  char *name;			/* Name of setting.			*/
+  int defined;			/* Boolean that specifies whether this
+				   flag was defined by, e.g. a Tcl
+				   script yet.				*/
+  int type;			/* Type of setting: UDEF_FLAG, UDEF_INT	*/
+  struct udef_chans *values;	/* Ptr to linked list of udef channel
+				   structures.				*/
+};
+
+static void del_chanrec(struct userrec *u, char *);
+static struct chanuserrec *get_chanrec(struct userrec *u, char *chname);
+static struct chanuserrec *add_chanrec(struct userrec *u, char *chname);
+static void add_chanrec_by_handle(struct userrec *bu, char *hand, char *chname);
+static void get_handle_chaninfo(char *handle, char *chname, char *s);
+static void set_handle_chaninfo(struct userrec *bu, char *handle,
+				char *chname, char *info);
+static void set_handle_laston(char *chan, struct userrec *u, time_t n);
+static int u_sticky_mask(maskrec *u, char *uhost);
+static int u_setsticky_mask(struct chanset_t *chan, maskrec *m, char *uhost,
+			    int sticky, char *botcmd);
+
+static int u_equals_mask(maskrec *u, char *uhost);
+static int u_match_mask(struct maskrec *rec, char *mask);
+static int u_delexempt (struct chanset_t * c, char * who, int doit);
+static int u_addexempt (struct chanset_t * chan, char * exempt, char * from,
+ 			char * note,  time_t expire_time, int flags);
+static int u_delinvite (struct chanset_t * c, char * who, int doit);
+static int u_addinvite (struct chanset_t * chan, char * invite, char * from,
+ 			char * note,  time_t expire_time, int flags);
+static int u_delban(struct chanset_t *c, char *who, int doit);
+static int u_addban(struct chanset_t *chan, char *ban, char *from, char *note,
+		    time_t expire_time, int flags);
+static void tell_bans(int idx, int show_inact, char *match);
+static int write_bans(FILE * f, int idx);
+static void check_expired_bans(void);
+static void tell_exempts (int idx, int show_inact, char * match);
+static int write_exempts (FILE * f, int idx);
+static void check_expired_exempts(void);
+static void tell_invites (int idx, int show_inact, char * match);
+static int write_invites (FILE * f, int idx);
+static void check_expired_invites(void);
+static void write_channels(void);
+static void read_channels(int);
+static void clear_channel(struct chanset_t *, int);
+static void get_mode_protect(struct chanset_t *chan, char *s);
+static void set_mode_protect(struct chanset_t *chan, char *set);
+static int ismasked(masklist *m, char *user);
+static int ismodeline(masklist *m, char *user);
+static int tcl_channel_modify(Tcl_Interp * irp, struct chanset_t *chan,
+			      int items, char **item);
+static int tcl_channel_add(Tcl_Interp * irp, char *, char *);
+static char *convert_element(char *src, char *dst);
+static void free_udef(struct udef_struct *);
+static void free_udef_chans(struct udef_chans *, int);
+static int getudef(struct udef_chans *, char *);
+static void initudef(int type, char *, int);
+static void setudef(struct udef_struct *, char *, int);
+static void remove_channel(struct chanset_t *);
+static int ngetudef(char *, char *);
+static int expired_mask(struct chanset_t *chan, char *who);
+inline static int chanset_unlink(struct chanset_t *chan);
+
+#else
+
+/* 4 - 7 */
+#define u_setsticky_mask ((int (*)(struct chanset_t *, maskrec *, char *, int, char *))channels_funcs[4])
+#define u_delban ((int (*)(struct chanset_t *, char *, int))channels_funcs[5])
+#define u_addban ((int (*)(struct chanset_t *, char *, char *, char *, time_t, int))channels_funcs[6])
+#define write_bans ((int (*)(FILE *, int))channels_funcs[7])
+/* 8 - 11 */
+#define get_chanrec ((struct chanuserrec *(*)(struct userrec *, char *))channels_funcs[8])
+#define add_chanrec ((struct chanuserrec *(*)(struct userrec *, char *))channels_funcs[9])
+#define del_chanrec ((void (*)(struct userrec *, char *))channels_funcs[10])
+#define set_handle_chaninfo ((void (*)(struct userrec *, char *, char *, char *))channels_funcs[11])
+/* 12 - 15 */
+/* 12: channel_malloc -- UNUSED (Tothwolf) */
+#define u_match_mask ((int (*)(maskrec *, char *))channels_funcs[13])
+#define u_equals_mask ((int (*)(maskrec *, char *))channels_funcs[14])
+#define clear_channel ((void (*)(struct chanset_t *, int))channels_funcs[15])
+/* 16 - 19 */
+#define set_handle_laston ((void (*)(char *,struct userrec *,time_t))channels_funcs[16])
+#define ban_time (*(int *)(channels_funcs[17]))
+#define use_info (*(int *)(channels_funcs[18]))
+#define get_handle_chaninfo ((void (*)(char *, char *, char *))channels_funcs[19])
+/* 20 - 23 */
+#define u_sticky_mask ((int (*)(maskrec *, char *))channels_funcs[20])
+#define ismasked ((int (*)(masklist *, char *))channels_funcs[21])
+#define add_chanrec_by_handle ((void (*)(struct userrec *, char *, char *))channels_funcs[22])
+/* *HOLE* channels_funcs[23] used to be isexempted() <cybah> */
+/* 24 - 27 */
+#define exempt_time (*(int *)(channels_funcs[24]))
+/* *HOLE* channels_funcs[25] used to be isinvited() by arthur2 <cybah> */
+#define invite_time (*(int *)(channels_funcs[26]))
+/* *HOLE* channels_funcs[27] used to be u_match_exempt() by arthur2 <cybah> */
+/* 28 - 31 */
+/* *HOLE* channels_funcs[28] used to be u_setsticky_exempt() <cybah> */
+#define u_delexempt ((int (*)(struct chanset_t *, char *, int))channels_funcs[29])
+#define u_addexempt ((int (*)(struct chanset_t *, char *, char *, char *, time_t, int))channels_funcs[30])
+/* *HOLE* channels_funcs[31] used to be u_equals_exempt() <cybah> */
+/* 32 - 35 */
+/* *HOLE* channels_funcs[32] used to be u_sticky_exempt() <cybah> */
+/* *HOLE* channels_funcs[33] used to be u_match_invite() <cybah> */
+/* *HOLE* channels_funcs[34] used to be killchanset().			*/
+#define u_delinvite ((int (*)(struct chanset_t *, char *, int))channels_funcs[35])
+/* 36 - 39 */
+#define u_addinvite ((int (*)(struct chanset_t *, char *, char *, char *, time_t, int))channels_funcs[36])
+#define tcl_channel_add ((int (*)(Tcl_Interp *, char *, char *))channels_funcs[37])
+#define tcl_channel_modify ((int (*)(Tcl_Interp *, struct chanset_t *, int, char **))channels_funcs[38])
+#define write_exempts ((int (*)(FILE *, int))channels_funcs[39])
+/* 40 - 43 */
+#define write_invites ((int (*)(FILE *, int))channels_funcs[40])
+#define ismodeline ((int(*)(masklist *, char *))channels_funcs[41])
+#define initudef ((void(*)(int, char *,int))channels_funcs[42])
+#define ngetudef ((int(*)(char *, char *))channels_funcs[43])
+/* 44 - 47 */
+#define expired_mask ((int (*)(struct chanset_t *, char *))channels_funcs[44])
+#define remove_channel ((void (*)(struct chanset_t *))channels_funcs[45])
+
+#endif				/* MAKING_CHANNELS */
+
+/* Macro's here because their functions were replaced by something more
+ * generic. <cybah>
+ */
+#define isbanned(chan, user)    ismasked((chan)->channel.ban, user)
+#define isexempted(chan, user)  ismasked((chan)->channel.exempt, user)
+#define isinvited(chan, user)   ismasked((chan)->channel.invite, user)
+
+#define ischanban(chan, user)    ismodeline((chan)->channel.ban, user)
+#define ischanexempt(chan, user) ismodeline((chan)->channel.exempt, user)
+#define ischaninvite(chan, user) ismodeline((chan)->channel.invite, user)
+
+#define u_setsticky_ban(chan, host, sticky)     u_setsticky_mask(chan, ((struct chanset_t *)chan) ? ((struct chanset_t *)chan)->bans : global_bans, host, sticky, "s")
+#define u_setsticky_exempt(chan, host, sticky)  u_setsticky_mask(chan, ((struct chanset_t *)chan) ? ((struct chanset_t *)chan)->exempts : global_exempts, host, sticky, "se")
+#define u_setsticky_invite(chan, host, sticky)  u_setsticky_mask(chan, ((struct chanset_t *)chan) ? ((struct chanset_t *)chan)->invites : global_invites, host, sticky, "sInv")
+
+#endif				/* _EGG_MOD_CHANNELS_CHANNELS_H */
Index: eggdrop1.7/modules/channels/cmdschan.c
diff -u /dev/null eggdrop1.7/modules/channels/cmdschan.c:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/cmdschan.c	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,1449 @@
+/*
+ * cmdschan.c -- part of channels.mod
+ *   commands from a user via dcc that cause server interaction
+ *
+ * $Id: cmdschan.c,v 1.1 2001/10/27 16:34:48 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <ctype.h>
+
+static struct flag_record user	 = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+static struct flag_record victim = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+
+static void cmd_pls_ban(struct userrec *u, int idx, char *par)
+{
+  char *chname, *who, s[UHOSTLEN], s1[UHOSTLEN], *p, *p_expire;
+  unsigned long int expire_time = 0, expire_foo;
+  int sticky = 0;
+  struct chanset_t *chan = NULL;
+  module_entry *me;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: +ban <hostmask> [channel] [%%<XdXhXm>] [reason]\n");
+  } else {
+    who = newsplit(&par);
+    if (par[0] && strchr(CHANMETA, par[0]))
+      chname = newsplit(&par);
+    else
+      chname = 0;
+    if (chname || !(u->flags & USER_MASTER)) {
+      if (!chname)
+	chname = dcc[idx].u.chat->con_chan;
+      get_user_flagrec(u, &user, chname);
+      chan = findchan_by_dname(chname);
+      /* *shrug* ??? (guppy:10Feb1999) */
+      if (!chan) {
+	dprintf(idx, "That channel doesn't exist!\n");
+	return;
+      } else if (!((glob_op(user) && !chan_deop(user)) || chan_op(user))) {
+	dprintf(idx, "You don't have access to set bans on %s.\n", chname);
+	return;
+      }
+    } else
+      chan = 0;
+    /* Added by Q and Solal -- Requested by Arty2, special thanx :) */
+    if (par[0] == '%') {
+      p = newsplit(&par);
+      p_expire = p + 1;
+      while (*(++p) != 0) {
+	switch (tolower(*p)) {
+	case 'd':
+	  *p = 0;
+	  expire_foo = strtol(p_expire, NULL, 10);
+	  if (expire_foo > 365)
+	    expire_foo = 365;
+	  expire_time += 86400 * expire_foo;
+	  p_expire = p + 1;
+	  break;
+	case 'h':
+	  *p = 0;
+	  expire_foo = strtol(p_expire, NULL, 10);
+	  if (expire_foo > 8760)
+	    expire_foo = 8760;
+	  expire_time += 3600 * expire_foo;
+	  p_expire = p + 1;
+	  break;
+	case 'm':
+	  *p = 0;
+	  expire_foo = strtol(p_expire, NULL, 10);
+	  if (expire_foo > 525600)
+	    expire_foo = 525600;
+	  expire_time += 60 * expire_foo;
+	  p_expire = p + 1;
+	}
+      }
+    }
+    if (!par[0])
+      par = "requested";
+    else if (strlen(par) > MASKREASON_MAX)
+      par[MASKREASON_MAX] = 0;
+    if (strlen(who) > UHOSTMAX - 4)
+      who[UHOSTMAX - 4] = 0;
+    /* Fix missing ! or @ BEFORE checking against myself */
+    if (!strchr(who, '!')) {
+      if (!strchr(who, '@'))
+	snprintf(s, sizeof s, "%s!*@*", who);	/* Lame nick ban */
+      else
+	snprintf(s, sizeof s, "*!%s", who);
+    } else if (!strchr(who, '@'))
+      snprintf(s, sizeof s, "%s@*", who);	/* brain-dead? */
+    else
+      strncpyz(s, who, sizeof s);
+    if ((me = module_find("server", 0, 0)) && me->funcs)
+      snprintf(s1, sizeof s1, "%s!%s", me->funcs[SERVER_BOTNAME],
+	            me->funcs[SERVER_BOTUSERHOST]);
+    else
+      s1[0] = 0;
+    if (s1[0] && wild_match(s, s1)) {
+      dprintf(idx, "I'm not going to ban myself.\n");
+      putlog(LOG_CMDS, "*", "#%s# attempted +ban %s", dcc[idx].nick, s);
+    } else {
+      /* IRC can't understand bans longer than 70 characters */
+      if (strlen(s) > 70) {
+	s[69] = '*';
+	s[70] = 0;
+      }
+      if (chan) {
+	u_addban(chan, s, dcc[idx].nick, par,
+		 expire_time ? now + expire_time : 0, 0);
+	if (par[0] == '*') {
+	  sticky = 1;
+	  par++;
+	  putlog(LOG_CMDS, "*", "#%s# (%s) +ban %s %s (%s) (sticky)",
+		 dcc[idx].nick, dcc[idx].u.chat->con_chan, s, chan->dname, par);
+	  dprintf(idx, "New %s sticky ban: %s (%s)\n", chan->dname, s, par);
+	} else {
+	  putlog(LOG_CMDS, "*", "#%s# (%s) +ban %s %s (%s)", dcc[idx].nick,
+		 dcc[idx].u.chat->con_chan, s, chan->dname, par);
+	  dprintf(idx, "New %s ban: %s (%s)\n", chan->dname, s, par);
+	}
+	/* Avoid unnesessary modes if you got +dynamicbans, and there is
+	 * no reason to set mode if irc.mod aint loaded. (dw 001120)
+	 */
+	if ((me = module_find("irc", 0, 0)))
+	  (me->funcs[IRC_CHECK_THIS_BAN])(chan, s, sticky);
+      } else {
+	u_addban(NULL, s, dcc[idx].nick, par,
+		 expire_time ? now + expire_time : 0, 0);
+	if (par[0] == '*') {
+	  sticky = 1;
+	  par++;
+	  putlog(LOG_CMDS, "*", "#%s# (GLOBAL) +ban %s (%s) (sticky)",
+		 dcc[idx].nick, s, par);
+	  dprintf(idx, "New sticky ban: %s (%s)\n", s, par);
+	} else {
+	  putlog(LOG_CMDS, "*", "#%s# (GLOBAL) +ban %s (%s)", dcc[idx].nick,
+		 s, par);
+	  dprintf(idx, "New ban: %s (%s)\n", s, par);
+	}
+	if ((me = module_find("irc", 0, 0)))
+	  for (chan = chanset; chan != NULL; chan = chan->next)
+	    (me->funcs[IRC_CHECK_THIS_BAN])(chan, s, sticky);
+      }
+    }
+  }
+}
+
+static void cmd_pls_exempt(struct userrec *u, int idx, char *par)
+{
+  char *chname, *who, s[UHOSTLEN], *p, *p_expire;
+  unsigned long int expire_time = 0, expire_foo;
+  struct chanset_t *chan = NULL;
+
+  if (!use_exempts) {
+    dprintf(idx, "This command can only be used with use-exempts enabled.\n");
+    return;
+  }
+  if (!par[0]) {
+    dprintf(idx, "Usage: +exempt <hostmask> [channel] [%%<XdXhXm>] [reason]\n");
+  } else {
+    who = newsplit(&par);
+    if ((par[0] == '#') || (par[0] == '&') || (par[0] == '+'))
+      chname = newsplit(&par);
+    else
+      chname = 0;
+    if (chname || !(u->flags & USER_MASTER)) {
+      if (!chname)
+	chname = dcc[idx].u.chat->con_chan;
+      get_user_flagrec(u,&user,chname);
+      chan = findchan_by_dname(chname);
+      /* *shrug* ??? (guppy:10Feb99) */
+      if (!chan) {
+        dprintf(idx, "That channel doesn't exist!\n");
+	return;
+      } else if (!((glob_op(user) && !chan_deop(user)) || chan_op(user))) {
+        dprintf(idx, "You don't have access to set exempts on %s.\n", chname);
+        return;
+      }
+    } else
+      chan = 0;
+    /* Added by Q and Solal  - Requested by Arty2, special thanx :) */
+    if (par[0] == '%') {
+      p = newsplit (&par);
+      p_expire = p + 1;
+      while (*(++p) != 0) {
+	switch (tolower(*p)) {
+	case 'd':
+	  *p = 0;
+	  expire_foo = strtol (p_expire, NULL, 10);
+	  if (expire_foo > 365)
+	    expire_foo = 365;
+	  expire_time += 86400 * expire_foo;
+	  p_expire = p + 1;
+	  break;
+	case 'h':
+	  *p = 0;
+	  expire_foo = strtol (p_expire, NULL, 10);
+	  if (expire_foo > 8760)
+	    expire_foo = 8760;
+	  expire_time += 3600 * expire_foo;
+	  p_expire = p + 1;
+	  break;
+	case 'm':
+	  *p = 0;
+	  expire_foo = strtol (p_expire, NULL, 10);
+	  if (expire_foo > 525600)
+	    expire_foo = 525600;
+	  expire_time += 60 * expire_foo;
+	  p_expire = p + 1;
+	}
+      }
+    }
+    if (!par[0])
+      par = "requested";
+    else if (strlen(par) > MASKREASON_MAX)
+      par[MASKREASON_MAX] = 0;
+    if (strlen(who) > UHOSTMAX - 4)
+      who[UHOSTMAX - 4] = 0;
+    /* Fix missing ! or @ BEFORE checking against myself */
+    if (!strchr(who, '!')) {
+      if (!strchr(who, '@'))
+	snprintf(s, sizeof s, "%s!*@*", who);	/* Lame nick exempt */
+      else
+	snprintf(s, sizeof s, "*!%s", who);
+    } else if (!strchr(who, '@'))
+      snprintf(s, sizeof s, "%s@*", who);		/* brain-dead? */
+    else
+      strncpyz(s, who, sizeof s);
+
+    /* IRC can't understand exempts longer than 70 characters */
+    if (strlen(s) > 70) {
+      s[69] = '*';
+      s[70] = 0;
+    }
+    if (chan) {
+      u_addexempt(chan, s, dcc[idx].nick, par,
+		  expire_time ? now + expire_time : 0, 0);
+      if (par[0] == '*') {
+	par++;
+	putlog(LOG_CMDS, "*", "#%s# (%s) +exempt %s %s (%s) (sticky)",
+	       dcc[idx].nick, dcc[idx].u.chat->con_chan, s, chan->dname, par);
+	dprintf(idx, "New %s sticky exempt: %s (%s)\n", chan->dname, s, par);
+      } else {
+	putlog(LOG_CMDS, "*", "#%s# (%s) +exempt %s %s (%s)", dcc[idx].nick,
+	       dcc[idx].u.chat->con_chan, s, chan->dname, par);
+	dprintf(idx, "New %s exempt: %s (%s)\n", chan->dname, s, par);
+      }
+      add_mode(chan, '+', 'e', s);
+    } else {
+      u_addexempt(NULL, s, dcc[idx].nick, par,
+		  expire_time ? now + expire_time : 0, 0);
+      if (par[0] == '*') {
+	par++;
+	putlog(LOG_CMDS, "*", "#%s# (GLOBAL) +exempt %s (%s) (sticky)",
+	       dcc[idx].nick, s, par);
+	dprintf(idx, "New sticky exempt: %s (%s)\n", s, par);
+      } else {
+	putlog(LOG_CMDS, "*", "#%s# (GLOBAL) +exempt %s (%s)", dcc[idx].nick,
+	       s, par);
+	dprintf(idx, "New exempt: %s (%s)\n", s, par);
+      }
+      for (chan = chanset; chan != NULL; chan = chan->next)
+	add_mode(chan, '+', 'e', s);
+    }
+  }
+}
+
+static void cmd_pls_invite(struct userrec *u, int idx, char *par)
+{
+  char *chname, *who, s[UHOSTLEN], *p, *p_expire;
+  unsigned long int expire_time = 0, expire_foo;
+  struct chanset_t *chan = NULL;
+
+  if (!use_invites) {
+    dprintf(idx, "This command can only be used with use-invites enabled.\n");
+    return;
+  }
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: +invite <hostmask> [channel] [%%<XdXhXm>] [reason]\n");
+  } else {
+    who = newsplit(&par);
+    if ((par[0] == '#') || (par[0] == '&') || (par[0] == '+'))
+      chname = newsplit(&par);
+    else
+      chname = 0;
+    if (chname || !(u->flags & USER_MASTER)) {
+      if (!chname)
+	chname = dcc[idx].u.chat->con_chan;
+      get_user_flagrec(u,&user,chname);
+      chan = findchan_by_dname(chname);
+      /* *shrug* ??? (guppy:10Feb99) */
+      if (!chan) {
+	dprintf(idx, "That channel doesn't exist!\n");
+	return;
+      } else if (!((glob_op(user) && !chan_deop(user)) || chan_op(user))) {
+	dprintf(idx, "You don't have access to set invites on %s.\n", chname);
+	return;
+      }
+    } else
+      chan = 0;
+    /* Added by Q and Solal  - Requested by Arty2, special thanx :) */
+    if (par[0] == '%') {
+      p = newsplit (&par);
+      p_expire = p + 1;
+      while (*(++p) != 0) {
+	switch (tolower(*p)) {
+	case 'd':
+	  *p = 0;
+	  expire_foo = strtol (p_expire, NULL, 10);
+	  if (expire_foo > 365)
+	    expire_foo = 365;
+	  expire_time += 86400 * expire_foo;
+	  p_expire = p + 1;
+	  break;
+	case 'h':
+	  *p = 0;
+	  expire_foo = strtol (p_expire, NULL, 10);
+	  if (expire_foo > 8760)
+	    expire_foo = 8760;
+	  expire_time += 3600 * expire_foo;
+	  p_expire = p + 1;
+	  break;
+	case 'm':
+	  *p = 0;
+	  expire_foo = strtol (p_expire, NULL, 10);
+	  if (expire_foo > 525600)
+	    expire_foo = 525600;
+	  expire_time += 60 * expire_foo;
+	  p_expire = p + 1;
+	}
+      }
+    }
+    if (!par[0])
+      par = "requested";
+    else if (strlen(par) > MASKREASON_MAX)
+      par[MASKREASON_MAX] = 0;
+    if (strlen(who) > UHOSTMAX - 4)
+      who[UHOSTMAX - 4] = 0;
+    /* Fix missing ! or @ BEFORE checking against myself */
+    if (!strchr(who, '!')) {
+      if (!strchr(who, '@'))
+	snprintf(s, sizeof s, "%s!*@*", who);	/* Lame nick invite */
+      else
+	snprintf(s, sizeof s, "*!%s", who);
+    } else if (!strchr(who, '@'))
+      snprintf(s, sizeof s, "%s@*", who);		/* brain-dead? */
+    else
+      strncpyz(s, who, sizeof s);
+
+    /* IRC can't understand invites longer than 70 characters */
+    if (strlen(s) > 70) {
+      s[69] = '*';
+      s[70] = 0;
+    }
+    if (chan) {
+      u_addinvite(chan, s, dcc[idx].nick, par,
+		  expire_time ? now + expire_time : 0, 0);
+      if (par[0] == '*') {
+	par++;
+	putlog(LOG_CMDS, "*", "#%s# (%s) +invite %s %s (%s) (sticky)",
+	       dcc[idx].nick, dcc[idx].u.chat->con_chan, s, chan->dname, par);
+	dprintf(idx, "New %s sticky invite: %s (%s)\n", chan->dname, s, par);
+      } else {
+	putlog(LOG_CMDS, "*", "#%s# (%s) +invite %s %s (%s)", dcc[idx].nick,
+	       dcc[idx].u.chat->con_chan, s, chan->dname, par);
+	dprintf(idx, "New %s invite: %s (%s)\n", chan->dname, s, par);
+      }
+      add_mode(chan, '+', 'I', s);
+    } else {
+      u_addinvite(NULL, s, dcc[idx].nick, par,
+		  expire_time ? now + expire_time : 0, 0);
+      if (par[0] == '*') {
+	par++;
+	putlog(LOG_CMDS, "*", "#%s# (GLOBAL) +invite %s (%s) (sticky)",
+	       dcc[idx].nick, s, par);
+	dprintf(idx, "New sticky invite: %s (%s)\n", s, par);
+      } else {
+	putlog(LOG_CMDS, "*", "#%s# (GLOBAL) +invite %s (%s)", dcc[idx].nick,
+	       s, par);
+	dprintf(idx, "New invite: %s (%s)\n", s, par);
+      }
+      for (chan = chanset; chan != NULL; chan = chan->next)
+	add_mode(chan, '+', 'I', s);
+    }
+  }
+}
+
+static void cmd_mns_ban(struct userrec *u, int idx, char *par)
+{
+  int i = 0, j;
+  struct chanset_t *chan = NULL;
+  char s[UHOSTLEN], *ban, *chname;
+  masklist *b;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: -ban <hostmask|ban #> [channel]\n");
+    return;
+  }
+  ban = newsplit(&par);
+  if (par[0] && strchr(CHANMETA, par[0]))
+    chname = newsplit(&par);
+  else
+    chname = dcc[idx].u.chat->con_chan;
+  if (chname || !(u->flags & USER_MASTER)) {
+    if (!chname)
+      chname = dcc[idx].u.chat->con_chan;
+    get_user_flagrec(u, &user, chname);
+    if (!((glob_op(user) && !chan_deop(user)) || chan_op(user)))
+      return;
+  }
+  strncpyz(s, ban, sizeof s);
+  i = u_delban(NULL, s, (u->flags & USER_MASTER));
+  if (i > 0) {
+    putlog(LOG_CMDS, "*", "#%s# -ban %s", dcc[idx].nick, s);
+    dprintf(idx, "%s: %s\n", _("Removed ban"), s);
+    for (chan = chanset; chan != NULL; chan = chan->next)
+      add_mode(chan, '-', 'b', s);
+    return;
+  }
+  /* Channel-specific ban? */
+  if (chname)
+    chan = findchan_by_dname(chname);
+  if (chan) {
+    if (atoi(ban) > 0) {
+      snprintf(s, sizeof s, "%d", -i);
+      j = u_delban(chan, s, 1);
+      if (j > 0) {
+	putlog(LOG_CMDS, "*", "#%s# (%s) -ban %s", dcc[idx].nick,
+	       chan->dname, s);
+	dprintf(idx, "Removed %s channel ban: %s\n", chan->dname, s);
+	add_mode(chan, '-', 'b', s);
+	return;
+      }
+      i = 0;
+      for (b = chan->channel.ban; b && b->mask && b->mask[0]; b = b->next) {
+	if ((!u_equals_mask(global_bans, b->mask)) &&
+	    (!u_equals_mask(chan->bans, b->mask))) {
+	  i++;
+	  if (i == -j) {
+	    add_mode(chan, '-', 'b', b->mask);
+	    dprintf(idx, "%s '%s' on %s.\n", _("Removed ban"),
+		    b->mask, chan->dname);
+	    putlog(LOG_CMDS, "*", "#%s# (%s) -ban %s [on channel]",
+		   dcc[idx].nick, dcc[idx].u.chat->con_chan, ban);
+	    return;
+	  }
+	}
+      }
+    } else {
+      j = u_delban(chan, ban, 1);
+      if (j > 0) {
+	putlog(LOG_CMDS, "*", "#%s# (%s) -ban %s", dcc[idx].nick,
+	       dcc[idx].u.chat->con_chan, ban);
+	dprintf(idx, "Removed %s channel ban: %s\n", chname, ban);
+	add_mode(chan, '-', 'b', ban);
+	return;
+      }
+      for (b = chan->channel.ban; b && b->mask && b->mask[0]; b = b->next) {
+	if (!irccmp(b->mask, ban)) {
+	  add_mode(chan, '-', 'b', b->mask);
+	  dprintf(idx, "%s '%s' on %s.\n",
+		  _("Removed ban"), b->mask, chan->dname);
+	  putlog(LOG_CMDS, "*", "#%s# (%s) -ban %s [on channel]",
+		 dcc[idx].nick, dcc[idx].u.chat->con_chan, ban);
+	  return;
+	}
+      }
+    }
+  }
+  dprintf(idx, "No such ban.\n");
+}
+
+static void cmd_mns_exempt (struct userrec *u, int idx, char *par)
+{
+  int i = 0, j;
+  struct chanset_t *chan = NULL;
+  char s[UHOSTLEN], *exempt, *chname;
+  masklist *e;
+
+  if (!use_exempts) {
+    dprintf(idx, "This command can only be used with use-exempts enabled.\n");
+    return;
+  }
+  if (!par[0]) {
+    dprintf(idx, "Usage: -exempt <hostmask|exempt #> [channel]\n");
+    return;
+  }
+  exempt = newsplit(&par);
+  if ((par[0] == '#') || (par[0] == '&') || (par[0] == '+'))
+    chname = newsplit(&par);
+  else
+    chname = dcc[idx].u.chat->con_chan;
+  if (chname || !(u->flags & USER_MASTER)) {
+    if (!chname)
+      chname = dcc[idx].u.chat->con_chan;
+    get_user_flagrec(u,&user,chname);
+    if (!((glob_op(user) && !chan_deop(user)) || chan_op(user)))
+      return;
+  }
+  strncpyz(s, exempt, sizeof s);
+  i = u_delexempt(NULL, s, (u->flags & USER_MASTER));
+  if (i > 0) {
+    putlog(LOG_CMDS, "*", "#%s# -exempt %s", dcc[idx].nick, s);
+    dprintf(idx, "%s: %s\n", _("Removed exempt"), s);
+    for (chan = chanset; chan != NULL; chan = chan->next)
+      add_mode(chan, '-', 'e', s);
+    return;
+  }
+  /* Channel-specific exempt? */
+  if (chname)
+    chan = findchan_by_dname(chname);
+  if (chan) {
+    if (atoi(exempt) > 0) {
+      snprintf(s, sizeof s, "%d", -i);
+      j = u_delexempt(chan, s, 1);
+      if (j > 0) {
+	putlog(LOG_CMDS, "*", "#%s# (%s) -exempt %s", dcc[idx].nick,
+	       chan->dname, s);
+	dprintf(idx, "Removed %s channel exempt: %s\n", chan->dname, s);
+	add_mode(chan, '-', 'e', s);
+	return;
+      }
+      i = 0;
+      for (e = chan->channel.exempt; e && e->mask && e->mask[0]; e = e->next) {
+	if (!u_equals_mask(global_exempts, e->mask) &&
+	    !u_equals_mask(chan->exempts, e->mask)) {
+	  i++;
+	  if (i == -j) {
+	    add_mode(chan, '-', 'e', e->mask);
+	    dprintf(idx, "%s '%s' on %s.\n", _("Removed exempt"),
+		    e->mask, chan->dname);
+	    putlog(LOG_CMDS, "*", "#%s# (%s) -exempt %s [on channel]",
+		   dcc[idx].nick, dcc[idx].u.chat->con_chan, exempt);
+	    return;
+	  }
+	}
+      }
+    } else {
+      j = u_delexempt(chan, exempt, 1);
+      if (j > 0) {
+	putlog(LOG_CMDS, "*", "#%s# (%s) -exempt %s", dcc[idx].nick,
+	       dcc[idx].u.chat->con_chan, exempt);
+	dprintf(idx, "Removed %s channel exempt: %s\n", chname, exempt);
+	add_mode(chan, '-', 'e', exempt);
+	return;
+      }
+      for (e = chan->channel.exempt; e && e->mask && e->mask[0]; e = e->next) {
+	if (!irccmp(e->mask, exempt)) {
+	  add_mode(chan, '-', 'e', e->mask);
+	  dprintf(idx, "%s '%s' on %s.\n",
+		  _("Removed exempt"), e->mask, chan->dname);
+	  putlog(LOG_CMDS, "*", "#%s# (%s) -exempt %s [on channel]",
+		 dcc[idx].nick, dcc[idx].u.chat->con_chan, exempt);
+	  return;
+	}
+      }
+    }
+  }
+  dprintf(idx, "No such exemption.\n");
+}
+
+static void cmd_mns_invite (struct userrec *u, int idx, char *par)
+{
+  int i = 0, j;
+  struct chanset_t *chan = NULL;
+  char s[UHOSTLEN], *invite, *chname;
+  masklist *inv;
+
+  if (!use_invites) {
+    dprintf(idx, "This command can only be used with use-invites enabled.\n");
+    return;
+  }
+  if (!par[0]) {
+    dprintf(idx, "Usage: -invite <hostmask|invite #> [channel]\n");
+    return;
+  }
+  invite = newsplit(&par);
+  if ((par[0] == '#') || (par[0] == '&') || (par[0] == '+'))
+    chname = newsplit(&par);
+  else
+    chname = dcc[idx].u.chat->con_chan;
+  if (chname || !(u->flags & USER_MASTER)) {
+    if (!chname)
+      chname = dcc[idx].u.chat->con_chan;
+    get_user_flagrec(u,&user,chname);
+    if (!((glob_op(user) && !chan_deop(user)) || chan_op(user)))
+      return;
+  }
+  strncpyz(s, invite, sizeof s);
+  i = u_delinvite(NULL, s, (u->flags & USER_MASTER));
+  if (i > 0) {
+    putlog(LOG_CMDS, "*", "#%s# -invite %s", dcc[idx].nick, s);
+    dprintf(idx, "%s: %s\n", _("Removed invite"), s);
+    for (chan = chanset; chan != NULL; chan = chan->next)
+      add_mode(chan, '-', 'I', s);
+    return;
+  }
+  /* Channel-specific invite? */
+  if (chname)
+    chan = findchan_by_dname(chname);
+  if (chan) {
+    if (atoi(invite) > 0) {
+      snprintf(s, sizeof s, "%d", -i);
+      j = u_delinvite(chan, s, 1);
+      if (j > 0) {
+	putlog(LOG_CMDS, "*", "#%s# (%s) -invite %s", dcc[idx].nick,
+	       chan->dname, s);
+	dprintf(idx, "Removed %s channel invite: %s\n", chan->dname, s);
+	add_mode(chan, '-', 'I', s);
+	return;
+      }
+      i = 0;
+      for (inv = chan->channel.invite; inv && inv->mask && inv->mask[0];
+	   inv = inv->next) {
+	if (!u_equals_mask(global_invites, inv->mask) &&
+	    !u_equals_mask(chan->invites, inv->mask)) {
+	  i++;
+	  if (i == -j) {
+	    add_mode(chan, '-', 'I', inv->mask);
+	    dprintf(idx, "%s '%s' on %s.\n", _("Removed invite"),
+		    inv->mask, chan->dname);
+	    putlog(LOG_CMDS, "*", "#%s# (%s) -invite %s [on channel]",
+		   dcc[idx].nick, dcc[idx].u.chat->con_chan, invite);
+	    return;
+	  }
+	}
+      }
+    } else {
+      j = u_delinvite(chan, invite, 1);
+      if (j > 0) {
+	putlog(LOG_CMDS, "*", "#%s# (%s) -invite %s", dcc[idx].nick,
+	       dcc[idx].u.chat->con_chan, invite);
+	dprintf(idx, "Removed %s channel invite: %s\n", chname, invite);
+	add_mode(chan, '-', 'I', invite);
+	return;
+      }
+      for (inv = chan->channel.invite; inv && inv->mask && inv->mask[0];
+	   inv = inv->next) {
+	if (!irccmp(inv->mask, invite)) {
+	  add_mode(chan, '-', 'I', inv->mask);
+	  dprintf(idx, "%s '%s' on %s.\n",
+		  _("Removed invite"), inv->mask, chan->dname);
+	  putlog(LOG_CMDS, "*", "#%s# (%s) -invite %s [on channel]",
+		 dcc[idx].nick, dcc[idx].u.chat->con_chan, invite);
+	  return;
+	}
+      }
+    }
+  }
+  dprintf(idx, "No such invite.\n");
+}
+
+static void cmd_bans(struct userrec *u, int idx, char *par)
+{
+  if (!strcasecmp(par, "all")) {
+    putlog(LOG_CMDS, "*", "#%s# bans all", dcc[idx].nick);
+    tell_bans(idx, 1, "");
+  } else {
+    putlog(LOG_CMDS, "*", "#%s# bans %s", dcc[idx].nick, par);
+    tell_bans(idx, 0, par);
+  }
+}
+
+static void cmd_exempts (struct userrec *u, int idx, char *par)
+{
+  if (!use_exempts) {
+    dprintf(idx, "This command can only be used with use-exempts enabled.\n");
+    return;
+  }
+  if (!strcasecmp(par, "all")) {
+    putlog(LOG_CMDS, "*", "#%s# exempts all", dcc[idx].nick);
+    tell_exempts(idx, 1, "");
+  } else {
+    putlog(LOG_CMDS, "*", "#%s# exempts %s", dcc[idx].nick, par);
+    tell_exempts(idx, 0, par);
+  }
+}
+
+static void cmd_invites (struct userrec *u, int idx, char *par)
+{
+  if (!use_invites) {
+    dprintf(idx, "This command can only be used with use-invites enabled.\n");
+    return;
+  }
+  if (!strcasecmp(par, "all")) {
+    putlog(LOG_CMDS, "*", "#%s# invites all", dcc[idx].nick);
+    tell_invites(idx, 1, "");
+  } else {
+    putlog(LOG_CMDS, "*", "#%s# invites %s", dcc[idx].nick, par);
+    tell_invites(idx, 0, par);
+  }
+}
+
+static void cmd_info(struct userrec *u, int idx, char *par)
+{
+  char s[512], *chname, *s1;
+  int locked = 0;
+
+  if (!use_info) {
+    dprintf(idx, "Info storage is turned off.\n");
+    return;
+  }
+  s1 = get_user(&USERENTRY_INFO, u);
+  if (s1 && s1[0] == '@')
+    locked = 1;
+  if (par[0] && strchr(CHANMETA, par[0])) {
+    chname = newsplit(&par);
+    if (!findchan_by_dname(chname)) {
+      dprintf(idx, "No such channel.\n");
+      return;
+    }
+    get_handle_chaninfo(dcc[idx].nick, chname, s);
+    if (s[0] == '@')
+      locked = 1;
+    s1 = s;
+  } else
+    chname = 0;
+  if (!par[0]) {
+    if (s1 && s1[0] == '@')
+      s1++;
+    if (s1 && s1[0]) {
+      if (chname) {
+	dprintf(idx, "Info on %s: %s\n", chname, s1);
+	dprintf(idx, "Use '.info %s none' to remove it.\n", chname);
+      } else {
+	dprintf(idx, "Default info: %s\n", s1);
+	dprintf(idx, "Use '.info none' to remove it.\n");
+      }
+    } else
+      dprintf(idx, "No info has been set for you.\n");
+    putlog(LOG_CMDS, "*", "#%s# info %s", dcc[idx].nick, chname ? chname : "");
+    return;
+  }
+  if (locked && !(u && (u->flags & USER_MASTER))) {
+    dprintf(idx, "Your info line is locked.  Sorry.\n");
+    return;
+  }
+  if (!strcasecmp(par, "none")) {
+    if (chname) {
+      par[0] = 0;
+      set_handle_chaninfo(userlist, dcc[idx].nick, chname, NULL);
+      dprintf(idx, "Removed your info line on %s.\n", chname);
+      putlog(LOG_CMDS, "*", "#%s# info %s none", dcc[idx].nick, chname);
+    } else {
+      set_user(&USERENTRY_INFO, u, NULL);
+      dprintf(idx, "Removed your default info line.\n");
+      putlog(LOG_CMDS, "*", "#%s# info none", dcc[idx].nick);
+    }
+    return;
+  }
+/*  if (par[0] == '@')    This is stupid, and prevents a users info from being locked */
+/*    par++;              without .tcl, or a tcl script, aka, 'half-assed' -poptix 4Jun01 */
+  if (chname) {
+    set_handle_chaninfo(userlist, dcc[idx].nick, chname, par);
+    dprintf(idx, "Your info on %s is now: %s\n", chname, par);
+    putlog(LOG_CMDS, "*", "#%s# info %s ...", dcc[idx].nick, chname);
+  } else {
+    set_user(&USERENTRY_INFO, u, par);
+    dprintf(idx, "Your default info is now: %s\n", par);
+    putlog(LOG_CMDS, "*", "#%s# info ...", dcc[idx].nick);
+  }
+}
+
+static void cmd_chinfo(struct userrec *u, int idx, char *par)
+{
+  char *handle, *chname;
+  struct userrec *u1;
+
+  if (!use_info) {
+    dprintf(idx, "Info storage is turned off.\n");
+    return;
+  }
+  handle = newsplit(&par);
+  if (!handle[0]) {
+    dprintf(idx, "Usage: chinfo <handle> [channel] <new-info>\n");
+    return;
+  }
+  u1 = get_user_by_handle(userlist, handle);
+  if (!u1) {
+    dprintf(idx, "No such user.\n");
+    return;
+  }
+  if (par[0] && strchr(CHANMETA, par[0])) {
+    chname = newsplit(&par);
+    if (!findchan_by_dname(chname)) {
+      dprintf(idx, "No such channel.\n");
+      return;
+    }
+  } else
+    chname = 0;
+  if ((u1->flags & USER_BOT) && !(u->flags & USER_MASTER)) {
+    dprintf(idx, "You have to be master to change bots info.\n");
+    return;
+  }
+  if ((u1->flags & USER_OWNER) && !(u->flags & USER_OWNER)) {
+    dprintf(idx, "Can't change info for the bot owner.\n");
+    return;
+  }
+  if (chname) {
+    get_user_flagrec(u, &user, chname);
+    get_user_flagrec(u1, &victim, chname);
+    if ((chan_owner(victim) || glob_owner(victim)) &&
+	!(glob_owner(user) || chan_owner(user))) {
+      dprintf(idx, "Can't change info for the channel owner.\n");
+      return;
+    }
+  }
+  putlog(LOG_CMDS, "*", "#%s# chinfo %s %s %s", dcc[idx].nick, handle,
+	 chname ? chname : par, chname ? par : "");
+  if (!strcasecmp(par, "none"))
+    par[0] = 0;
+  if (chname) {
+    set_handle_chaninfo(userlist, handle, chname, par);
+    if (par[0] == '@')
+      dprintf(idx, "New info (LOCKED) for %s on %s: %s\n", handle, chname,
+	      &par[1]);
+    else if (par[0])
+      dprintf(idx, "New info for %s on %s: %s\n", handle, chname, par);
+    else
+      dprintf(idx, "Wiped info for %s on %s\n", handle, chname);
+  } else {
+    set_user(&USERENTRY_INFO, u1, par[0] ? par : NULL);
+    if (par[0] == '@')
+      dprintf(idx, "New default info (LOCKED) for %s: %s\n", handle, &par[1]);
+    else if (par[0])
+      dprintf(idx, "New default info for %s: %s\n", handle, par);
+    else
+      dprintf(idx, "Wiped default info for %s\n", handle);
+  }
+}
+
+static void cmd_stick_yn(int idx, char *par, int yn)
+{
+  int i = 0, j;
+  struct chanset_t *chan, *achan;
+  char *stick_type, s[UHOSTLEN], chname[81];
+  module_entry *me;
+
+  stick_type = newsplit(&par);
+  strncpyz(s, newsplit(&par), sizeof s);
+  strncpyz(chname, newsplit(&par), sizeof chname);
+
+  if (strcasecmp(stick_type, "exempt") &&
+      strcasecmp(stick_type, "invite") &&
+      strcasecmp(stick_type, "ban")) {
+    strncpyz(chname, s, sizeof chname);
+    strncpyz(s, stick_type, sizeof s);
+  }
+  if (!s[0]) {
+    dprintf(idx, "Usage: %sstick [ban/exempt/invite] <hostmask or number> [channel]\n",
+            yn ? "" : "un");
+    return;
+  }
+  /* Now deal with exemptions */
+  if (!strcasecmp(stick_type, "exempt")) {
+    if (!use_exempts) {
+      dprintf(idx, "This command can only be used with use-exempts enabled.\n");
+      return;
+    }
+    if (!chname[0]) {
+      i = u_setsticky_exempt(NULL, s,
+                             (dcc[idx].user->flags & USER_MASTER) ? yn : -1);
+      if (i > 0) {
+        putlog(LOG_CMDS, "*", "#%s# %sstick exempt %s",
+               dcc[idx].nick, yn ? "" : "un", s);
+        dprintf(idx, "%stuck exempt: %s\n", yn ? "S" : "Uns", s);
+        return;
+      }
+      strncpyz(chname, dcc[idx].u.chat->con_chan, sizeof chname);
+    }
+    /* Channel-specific exempt? */
+    if (!(chan = findchan_by_dname(chname))) {
+      dprintf(idx, "No such channel.\n");
+      return;
+    }
+    if (i)
+      snprintf(s, sizeof s, "%d", -i);
+    j = u_setsticky_exempt(chan, s, yn);
+    if (j > 0) {
+      putlog(LOG_CMDS, "*", "#%s# %sstick exempt %s %s", dcc[idx].nick,
+             yn ? "" : "un", s, chname);
+      dprintf(idx, "%stuck %s exempt: %s\n", yn ? "S" : "Uns", chname, s);
+      return;
+    }
+    dprintf(idx, "No such exempt.\n");
+    return;
+  /* Now the invites */
+  } else if (!strcasecmp(stick_type, "invite")) {
+    if (!use_invites) {
+      dprintf(idx, "This command can only be used with use-invites enabled.\n");
+      return;
+    }
+    if (!chname[0]) {
+      i = u_setsticky_invite(NULL, s,
+                             (dcc[idx].user->flags & USER_MASTER) ? yn : -1);
+      if (i > 0) {
+        putlog(LOG_CMDS, "*", "#%s# %sstick invite %s",
+               dcc[idx].nick, yn ? "" : "un", s);
+        dprintf(idx, "%stuck invite: %s\n", yn ? "S" : "Uns", s);
+        return;
+      }
+      strncpyz(chname, dcc[idx].u.chat->con_chan, sizeof chname);
+    }
+    /* Channel-specific invite? */
+    if (!(chan = findchan_by_dname(chname))) {
+      dprintf(idx, "No such channel.\n");
+      return;
+    }
+    if (i)
+      snprintf(s, sizeof s, "%d", -i);
+    j = u_setsticky_invite(chan, s, yn);
+    if (j > 0) {
+      putlog(LOG_CMDS, "*", "#%s# %sstick invite %s %s", dcc[idx].nick,
+             yn ? "" : "un", s, chname);
+      dprintf(idx, "%stuck %s invite: %s\n", yn ? "S" : "Uns", chname, s);
+      return;
+    }
+    dprintf(idx, "No such invite.\n");
+    return;
+  }
+  if (!chname[0]) {
+    i = u_setsticky_ban(NULL, s,
+                        (dcc[idx].user->flags & USER_MASTER) ? yn : -1);
+    if (i > 0) {
+      putlog(LOG_CMDS, "*", "#%s# %sstick ban %s",
+             dcc[idx].nick, yn ? "" : "un", s);
+      dprintf(idx, "%stuck ban: %s\n", yn ? "S" : "Uns", s);
+      if ((me = module_find("irc", 0, 0)))
+	for (achan = chanset; achan != NULL; achan = achan->next)
+	  (me->funcs[IRC_CHECK_THIS_BAN])(achan, s, yn);
+      return;
+    }
+    strncpyz(chname, dcc[idx].u.chat->con_chan, sizeof chname);
+  }
+  /* Channel-specific ban? */
+  if (!(chan = findchan_by_dname(chname))) {
+    dprintf(idx, "No such channel.\n");
+    return;
+  }
+  if (i)
+    snprintf(s, sizeof s, "%d", -i);
+  j = u_setsticky_ban(chan, s, yn);
+  if (j > 0) {
+    putlog(LOG_CMDS, "*", "#%s# %sstick ban %s %s", dcc[idx].nick,
+           yn ? "" : "un", s, chname);
+    dprintf(idx, "%stuck %s ban: %s\n", yn ? "S" : "Uns", chname, s);
+    if ((me = module_find("irc", 0, 0)))
+      (me->funcs[IRC_CHECK_THIS_BAN])(chan, s, yn);
+    return;
+  }
+  dprintf(idx, "No such ban.\n");
+}
+
+
+static void cmd_stick(struct userrec *u, int idx, char *par)
+{
+  cmd_stick_yn(idx, par, 1);
+}
+
+static void cmd_unstick(struct userrec *u, int idx, char *par)
+{
+  cmd_stick_yn(idx, par, 0);
+}
+
+static void cmd_pls_chrec(struct userrec *u, int idx, char *par)
+{
+  char *nick, *chn;
+  struct chanset_t *chan;
+  struct userrec *u1;
+  struct chanuserrec *chanrec;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: +chrec <user> [channel]\n");
+    return;
+  }
+  nick = newsplit(&par);
+  u1 = get_user_by_handle(userlist, nick);
+  if (!u1) {
+    dprintf(idx, "No such user.\n");
+    return;
+  }
+  if (!par[0])
+    chan = findchan_by_dname(dcc[idx].u.chat->con_chan);
+  else {
+    chn = newsplit(&par);
+    chan = findchan_by_dname(chn);
+  }
+  if (!chan) {
+    dprintf(idx, "No such channel.\n");
+    return;
+  }
+  get_user_flagrec(u, &user, chan->dname);
+  get_user_flagrec(u1, &victim, chan->dname);
+  if ((!glob_master(user) && !chan_master(user)) ||  /* drummer */
+      (chan_owner(victim) && !chan_owner(user) && !glob_owner(user)) ||
+      (glob_owner(victim) && !glob_owner(user))) {
+    dprintf(idx, "You have no permission to do that.\n");
+    return;
+  }
+  chanrec = get_chanrec(u1, chan->dname);
+  if (chanrec) {
+    dprintf(idx, "User %s already has a channel record for %s.\n",
+	    nick, chan->dname);
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# +chrec %s %s", dcc[idx].nick,
+	 nick, chan->dname);
+  add_chanrec(u1, chan->dname);
+  dprintf(idx, "Added %s channel record for %s.\n", chan->dname, nick);
+}
+
+static void cmd_mns_chrec(struct userrec *u, int idx, char *par)
+{
+  char *nick, *chn = NULL;
+  struct userrec *u1;
+  struct chanuserrec *chanrec;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: -chrec <user> [channel]\n");
+    return;
+  }
+  nick = newsplit(&par);
+  u1 = get_user_by_handle(userlist, nick);
+  if (!u1) {
+    dprintf(idx, "No such user.\n");
+    return;
+  }
+  if (!par[0]) {
+    struct chanset_t *chan;
+
+    chan = findchan_by_dname(dcc[idx].u.chat->con_chan);
+    if (chan)
+      chn = chan->dname;
+    else {
+      dprintf(idx, "Invalid console channel.\n");
+      return;
+    }
+  } else
+    chn = newsplit(&par);
+  get_user_flagrec(u, &user, chn);
+  get_user_flagrec(u1, &victim, chn);
+  if ((!glob_master(user) && !chan_master(user)) ||  /* drummer */
+      (chan_owner(victim) && !chan_owner(user) && !glob_owner(user)) ||
+      (glob_owner(victim) && !glob_owner(user))) {
+    dprintf(idx, "You have no permission to do that.\n");
+    return;
+  }
+  chanrec = get_chanrec(u1, chn);
+  if (!chanrec) {
+    dprintf(idx, "User %s doesn't have a channel record for %s.\n", nick, chn);
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# -chrec %s %s", dcc[idx].nick, nick, chn);
+  del_chanrec(u1, chn);
+  dprintf(idx, "Removed %s channel record from %s.\n", chn, nick);
+}
+
+static void cmd_pls_chan(struct userrec *u, int idx, char *par)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: +chan [%s]<channel>\n", CHANMETA);
+    return;
+  }
+
+  chname = newsplit(&par);
+  if (findchan_by_dname(chname)) {
+    dprintf(idx, "That channel already exists!\n");
+    return;
+  } else if ((chan = findchan(chname))) {
+    /* This prevents someone adding a channel by it's unique server
+     * name <cybah>
+     */
+    dprintf(idx, "That channel already exists as %s!\n", chan->dname);
+    return;
+  }
+
+  if (tcl_channel_add(0, chname, par) == TCL_ERROR) /* drummer */
+    dprintf(idx, "Invalid channel.\n");
+  else
+    putlog(LOG_CMDS, "*", "#%s# +chan %s", dcc[idx].nick, chname);
+}
+
+static void cmd_mns_chan(struct userrec *u, int idx, char *par)
+{
+  char *chname;
+  struct chanset_t *chan;
+  int i;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: -chan [%s]<channel>\n", CHANMETA);
+    return;
+  }
+  chname = newsplit(&par);
+  chan = findchan_by_dname(chname);
+  if (!chan) {
+    if ((chan = findchan(chname)))
+      dprintf(idx, "That channel exists with a short name of %s, use that.\n",
+              chan->dname);
+    else
+      dprintf(idx, "That channel doesn't exist!\n");
+    return;
+  }
+  if (channel_static(chan)) {
+    dprintf(idx, "Cannot remove %s, it is not a dynamic channel!.\n",
+	    chname);
+    return;
+  }
+
+  remove_channel(chan);
+  dprintf(idx, "Channel %s removed from the bot.\n", chname);
+  dprintf(idx, "This includes any channel specific bans, invites, exemptions and user records that you set.\n");
+  putlog(LOG_CMDS, "*", "#%s# -chan %s", dcc[idx].nick, chname);
+  for (i = 0; i < dcc_total; i++)
+    if ((dcc[i].type->flags & DCT_CHAT) &&
+	!irccmp(dcc[i].u.chat->con_chan, chan->dname)) {
+      dprintf(i, "%s is no longer a valid channel, changing your console to '*'\n",
+	      chname);
+      strcpy(dcc[i].u.chat->con_chan, "*");
+    }
+}
+
+static void cmd_chaninfo(struct userrec *u, int idx, char *par)
+{
+  char *chname, work[512];
+  struct chanset_t *chan;
+  int ii, tmp;
+  struct udef_struct *ul;
+
+  if (!par[0]) {
+    chname = dcc[idx].u.chat->con_chan;
+    if (chname[0] == '*') {
+      dprintf(idx, "You console channel is invalid\n");
+      return;
+    }
+  } else {
+    chname = newsplit(&par);
+    get_user_flagrec(u, &user, chname);
+    if (!glob_master(user) && !chan_master(user)) {
+      dprintf(idx, "You don't have access to %s. \n", chname);
+      return;
+    }
+  }
+  if (!(chan = findchan_by_dname(chname)))
+    dprintf(idx, "No such channel defined.\n");
+  else {
+    dprintf(idx, "Settings for %s channel %s\n",
+	    channel_static(chan) ? "static" : "dynamic", chan->dname);
+    get_mode_protect(chan, work);
+    dprintf(idx, "Protect modes (chanmode): %s\n", work[0] ? work : "None");
+    if (chan->idle_kick)
+      dprintf(idx, "Idle Kick after (idle-kick): %d\n", chan->idle_kick);
+    else
+      dprintf(idx, "Idle Kick after (idle-kick): DON'T!\n");
+    if (chan->stopnethack_mode)
+      dprintf(idx, "stopnethack-mode: %d\n", chan->stopnethack_mode);
+    else
+      dprintf(idx, "stopnethack: DON'T!\n");
+      dprintf(idx, "aop-delay: %d:%d\n", chan->aop_min, chan->aop_max);
+    if (chan->revenge_mode)
+      dprintf(idx, "revenge-mode: %d\n", chan->revenge_mode);
+    else
+      dprintf(idx, "revenge-mode: 0\n");
+    dprintf(idx, "Other modes:\n");
+    dprintf(idx, "     %cinactive       %cstatuslog      %csecret         %cshared\n",
+	    (chan->status & CHAN_INACTIVE) ? '+' : '-',
+	    (chan->status & CHAN_LOGSTATUS) ? '+' : '-',
+	    (chan->status & CHAN_SECRET) ? '+' : '-',
+	    (chan->status & CHAN_SHARED) ? '+' : '-');
+    dprintf(idx, "     %cgreet          %ccycle          %cdontkickops\n",
+	    (chan->status & CHAN_GREET) ? '+' : '-',
+	    (chan->status & CHAN_CYCLE) ? '+' : '-',
+	    (chan->status & CHAN_DONTKICKOPS) ? '+' : '-');
+    dprintf(idx, "     %cprotectops     %cprotectfriends %crevenge        %crevengebot\n",
+	    (chan->status & CHAN_PROTECTOPS) ? '+' : '-',
+            (chan->status & CHAN_PROTECTFRIENDS) ? '+' : '-',
+	    (chan->status & CHAN_REVENGE) ? '+' : '-',
+	    (chan->status & CHAN_REVENGEBOT) ? '+' : '-');
+    dprintf(idx, "     %cbitch          %cautoop         %cautovoice      %cnodesynch\n",
+	    (chan->status & CHAN_BITCH) ? '+' : '-',
+	    (chan->status & CHAN_OPONJOIN) ? '+' : '-',
+	    (chan->status & CHAN_AUTOVOICE) ? '+' : '-',
+	    (chan->status & CHAN_NODESYNCH) ? '+' : '-');
+    dprintf(idx, "     %cenforcebans    %cdynamicbans    %cuserbans\n",
+	    (chan->status & CHAN_ENFORCEBANS) ? '+' : '-',
+	    (chan->status & CHAN_DYNAMICBANS) ? '+' : '-',
+	    (!(chan->status & CHAN_USERBANS)) ? '-' : '+');
+    dprintf(idx, "     %cdynamicexempts %cuserexempts    %cdynamicinvites %cuserinvites\n",
+	    (chan->ircnet_status & CHAN_DYNAMICEXEMPTS) ? '+' : '-',
+	    (!(chan->ircnet_status & CHAN_USEREXEMPTS)) ? '-' : '+',
+	    (chan->ircnet_status & CHAN_DYNAMICINVITES) ? '+' : '-',
+	    (!(chan->ircnet_status & CHAN_USERINVITES)) ? '-' : '+');
+
+    ii = 1;
+    tmp = 0;
+    for (ul = udef; ul; ul = ul->next)
+      if (ul->defined && ul->type == UDEF_FLAG) {
+	int	work_len;
+
+        if (!tmp) {
+          dprintf(idx, "User defined channel flags:\n");
+          tmp = 1;
+        }
+	if (ii == 1)
+	  snprintf(work, sizeof work, "    ");
+	work_len = strlen(work);
+        snprintf(work + work_len, sizeof(work) - work_len, " %c%s",
+		     getudef(ul->values, chan->dname) ? '+' : '-', ul->name);
+        ii++;
+        if (ii > 4) {
+          dprintf(idx, "%s\n", work);
+          ii = 1;
+        }
+      }
+    if (ii > 1)
+      dprintf(idx, "%s\n", work);
+
+    work[0] = 0;
+    ii = 1;
+    tmp = 0;
+    for (ul = udef; ul; ul = ul->next)
+      if (ul->defined && ul->type == UDEF_INT) {
+	int	work_len = strlen(work);
+
+        if (!tmp) {
+          dprintf(idx, "User defined channel settings:\n");
+          tmp = 1;
+        }
+        snprintf(work + work_len, sizeof(work) - work_len, "%s: %d   ",
+		     ul->name, getudef(ul->values, chan->dname));
+        ii++;
+        if (ii > 4) {
+          dprintf(idx, "%s\n", work);
+	  work[0] = 0;
+          ii = 1;
+        }
+      }
+    if (ii > 1)
+      dprintf(idx, "%s\n", work);
+
+	if (u->flags & USER_OWNER) {
+		tmp = 0;
+		for (ul = udef; ul; ul = ul->next)
+			if (ul->defined && ul->type == UDEF_STR) {
+				char *p = (char *)getudef(ul->values, chan->dname);
+				if (!p) p = "{}";
+				if (!tmp) {
+					dprintf(idx, "User defined channel strings:\n");
+					tmp = 1;
+				}
+				dprintf(idx, "%s: %s\n", ul->name, p);
+			}
+	}
+
+    dprintf(idx, "flood settings: chan ctcp join kick deop nick\n");
+    dprintf(idx, "number:          %3d  %3d  %3d  %3d  %3d  %3d\n",
+	    chan->flood_pub_thr, chan->flood_ctcp_thr,
+	    chan->flood_join_thr, chan->flood_kick_thr,
+	    chan->flood_deop_thr, chan->flood_nick_thr);
+    dprintf(idx, "time  :          %3d  %3d  %3d  %3d  %3d  %3d\n",
+	    chan->flood_pub_time, chan->flood_ctcp_time,
+	    chan->flood_join_time, chan->flood_kick_time,
+	    chan->flood_deop_time, chan->flood_nick_time);
+    putlog(LOG_CMDS, "*", "#%s# chaninfo %s", dcc[idx].nick, chname);
+  }
+}
+
+static void cmd_chanset(struct userrec *u, int idx, char *par)
+{
+  char *chname = NULL, answers[512], *parcpy;
+  char *list[2], *bak, *buf;
+  struct chanset_t *chan = NULL;
+  int all = 0;
+
+  if (!par[0])
+    dprintf(idx, "Usage: chanset [%schannel] <settings>\n", CHANMETA);
+  else {
+    if (strlen(par) > 2 && par[0] == '*' && par[1] == ' ') {
+      all = 1;
+      get_user_flagrec(u, &user, chanset ? chanset->dname : "");
+      if (!glob_master(user)) {
+	dprintf(idx, "You need to be a global master to use .chanset *.\n");
+	return;
+      }
+      newsplit(&par);
+    } else {
+      if (strchr(CHANMETA, par[0])) {
+        chname = newsplit(&par);
+        get_user_flagrec(u, &user, chname);
+        if (!glob_master(user) && !chan_master(user)) {
+	  dprintf(idx, "You don't have access to %s. \n", chname);
+	  return;
+	} else if (!(chan = findchan_by_dname(chname)) && (chname[0] != '+')) {
+	  dprintf(idx, "That channel doesn't exist!\n");
+	  return;
+	}
+	if (!chan) {
+	  if (par[0])
+	    *--par = ' ';
+	  par = chname;
+	}
+      }
+      if (!par[0] || par[0] == '*') {
+        dprintf(idx, "Usage: chanset [%schannel] <settings>\n", CHANMETA);
+        return;
+      }
+      if (!chan &&
+          !(chan = findchan_by_dname(chname = dcc[idx].u.chat->con_chan))) {
+        dprintf(idx, "Invalid console channel.\n");
+        return;
+      }
+    }
+    if (all)
+      chan = chanset;
+    bak = par;
+    buf = malloc(strlen(par) + 1);
+    while (chan) {
+      chname = chan->dname;
+      strcpy(buf, bak);
+      par = buf;
+      list[0] = newsplit(&par);
+      answers[0] = 0;
+      while (list[0][0]) {
+	if (list[0][0] == '+' || list[0][0] == '-' ||
+	    (!strcmp(list[0], "dont-idle-kick"))) {
+	  if (tcl_channel_modify(0, chan, 1, list) == TCL_OK) {
+	    strcat(answers, list[0]);
+	    strcat(answers, " ");
+	  } else if (!all || !chan->next)
+	    dprintf(idx, "Error trying to set %s for %s, invalid mode\n",
+		    list[0], all ? "all channels" : chname);
+	  list[0] = newsplit(&par);
+	  continue;
+	}
+	/* The rest have an unknown amount of args, so assume the rest of the
+	 * line is args. 
+	 */
+	  list[1] = par;
+	  /* Par gets modified in tcl_channel_modify under some
+  	   * circumstances, so save it now.
+	   */
+	  malloc_strcpy(parcpy, par);
+          if (tcl_channel_modify(0, chan, 2, list) == TCL_OK) {
+	    strcat(answers, list[0]);
+	    strcat(answers, " { ");
+	    strcat(answers, parcpy);
+	    strcat(answers, " }");
+	  } else if (!all || !chan->next)
+	    dprintf(idx, "Error trying to set %s for %s, invalid option\n",
+		    list[0], all ? "all channels" : chname);
+          free(parcpy);
+	break;
+      }
+      if (!all && answers[0]) {
+	dprintf(idx, "Successfully set modes { %s } on %s.\n",
+		answers, chname);
+	putlog(LOG_CMDS, "*", "#%s# chanset %s %s", dcc[idx].nick, chname,
+	       answers);
+      }
+      if (!all)
+        chan = NULL;
+      else
+        chan = chan->next;
+    }
+    if (all && answers[0]) {
+      dprintf(idx, "Successfully set modes { %s } on all channels.\n",
+	      answers);
+      putlog(LOG_CMDS, "*", "#%s# chanset * %s", dcc[idx].nick, answers);
+    }
+    free(buf);
+  }
+}
+
+static void cmd_chansave(struct userrec *u, int idx, char *par)
+{
+  if (!chanfile[0])
+    dprintf(idx, "No channel saving file defined.\n");
+  else {
+    dprintf(idx, "Saving all dynamic channel settings.\n");
+    putlog(LOG_CMDS, "*", "#%s# chansave", dcc[idx].nick);
+    write_channels();
+  }
+}
+
+static void cmd_chanload(struct userrec *u, int idx, char *par)
+{
+  if (!chanfile[0])
+    dprintf(idx, "No channel saving file defined.\n");
+  else {
+    dprintf(idx, "Reloading all dynamic channel settings.\n");
+    putlog(LOG_CMDS, "*", "#%s# chanload", dcc[idx].nick);
+    setstatic = 0;
+    read_channels(1);
+  }
+}
+
+/* DCC CHAT COMMANDS
+ *
+ * Function call should be:
+ *    int cmd_whatever(idx,"parameters");
+ *
+ * NOTE: As with msg commands, the function is responsible for any logging.
+ */
+static cmd_t C_dcc_irc[] =
+{
+  {"+ban",	"o|o",	(Function) cmd_pls_ban,		NULL},
+  {"+exempt",	"o|o",	(Function) cmd_pls_exempt,	NULL},
+  {"+invite",	"o|o",	(Function) cmd_pls_invite,	NULL},
+  {"+chan",	"n",	(Function) cmd_pls_chan,	NULL},
+  {"+chrec",	"m|m",	(Function) cmd_pls_chrec,	NULL},
+  {"-ban",	"o|o",	(Function) cmd_mns_ban,		NULL},
+  {"-chan",	"n",	(Function) cmd_mns_chan,	NULL},
+  {"-chrec",	"m|m",	(Function) cmd_mns_chrec,	NULL},
+  {"bans",	"o|o",	(Function) cmd_bans,		NULL},
+  {"-exempt",	"o|o",	(Function) cmd_mns_exempt,	NULL},
+  {"-invite",	"o|o",	(Function) cmd_mns_invite,	NULL},
+  {"exempts",	"o|o",	(Function) cmd_exempts,		NULL},
+  {"invites",	"o|o",	(Function) cmd_invites,		NULL},
+  {"chaninfo",	"m|m",	(Function) cmd_chaninfo,	NULL},
+  {"chanload",	"n|n",	(Function) cmd_chanload,	NULL},
+  {"chanset",	"n|n",	(Function) cmd_chanset,		NULL},
+  {"chansave",	"n|n",	(Function) cmd_chansave,	NULL},
+  {"chinfo",	"m|m",	(Function) cmd_chinfo,		NULL},
+  {"info",	"",	(Function) cmd_info,		NULL},
+  {"stick",	"o|o",	(Function) cmd_stick,		NULL},
+  {"unstick",	"o|o",	(Function) cmd_unstick,		NULL},
+  {NULL,	NULL,	NULL,				NULL}
+};
Index: eggdrop1.7/modules/channels/flagmaps.c
diff -u /dev/null eggdrop1.7/modules/channels/flagmaps.c:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/flagmaps.c	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,34 @@
+typedef struct {
+	int flagval;
+	char *name;
+} channel_flag_map_t;
+
+static channel_flag_map_t normal_flag_map[] = {
+	{CHAN_ENFORCEBANS, "enforcebans"},
+	{CHAN_DYNAMICBANS, "dynamicbans"},
+	{CHAN_USERBANS, "userbans"},
+	{CHAN_OPONJOIN, "autoop"},
+	{CHAN_BITCH, "bitch"},
+	{CHAN_NODESYNCH, "nodesynch"},
+	{CHAN_GREET, "greet"},
+	{CHAN_PROTECTOPS, "protectops"},
+	{CHAN_PROTECTFRIENDS, "protectfriends"},
+	{CHAN_DONTKICKOPS, "dontkickops"},
+	{CHAN_INACTIVE, "inactive"},
+	{CHAN_LOGSTATUS, "statuslog"},
+	{CHAN_REVENGE, "revenge"},
+	{CHAN_REVENGEBOT, "revengebot"},
+	{CHAN_SECRET, "secret"},
+	{CHAN_SHARED, "shared"},
+	{CHAN_AUTOVOICE, "autovoice"},
+	{CHAN_CYCLE, "cycle"},
+	{0, 0}
+};
+
+static channel_flag_map_t stupid_ircnet_flag_map[] = {
+	{CHAN_DYNAMICEXEMPTS, "dynamicexempts"},
+	{CHAN_USEREXEMPTS, "userexempts"},
+	{CHAN_DYNAMICINVITES, "dynamicinvites"},
+	{CHAN_USERINVITES, "userinvites"},
+	{0, 0}
+};
Index: eggdrop1.7/modules/channels/help/chaninfo.help
diff -u /dev/null eggdrop1.7/modules/channels/help/chaninfo.help:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/help/chaninfo.help	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,78 @@
+%{help=chaninfo}%{+m|m}
+###  %bchaninfo%b <channel>
+     This lists all the settings for the bot on the given channel.
+     It shows any of the following:
+        %bchanmode%b   which modes are enforced on the channel, both + and -
+                   modes can be enforced
+        %bidle-kick%b  kick idle users (non +f's anyway) on the channel after
+                   how many minutes (use 0, or dont-idle-kick to turn this off)
+%{+m|m}
+        %bstopnethack-mode%b    de-op anyone who enters the channel with server
+                       ops
+                       0 turn off,
+                       1 isoptest (allow serverop if registered op)
+                       2 wasoptest (allow serverop if op before split)
+                       3 allow serverop if isop or wasop
+                       4 allow serverop if isop and wasop
+                       5 if channel -bitch: see stopnethack-mode 3
+                         if channel +bitch: see stopnethack-mode 1
+                       6 if channel -bitch: see stopnethack-mode 2
+                         if channel +bitch: see stopnethack-mode 4
+        %brevenge-mode%b      defines how the bot should punish bad users when
+                          revenging
+                       0 for deop
+                       1 for deop and +d
+                       2 for deop, +d and kick
+                       3 for deop, chattr +d, kick and ban
+ %baop-delay%b        autoop or autovoice delaytime
+                       0:0 no delay
+                       x:x x sec delaytime
+                       x:y random delay, minimum x sec, maximum y sec
+     The following can be set + or - (eg .chanset #channel -enforcebans)
+        %benforcebans%b    kicks people who match channel bans?
+        %bdynamicbans%b    only activate bans on channel when they are needed
+        %buserbans%b       allow other users to place bans on the channel
+        %bdynamicexempts%b only activate exempts on channel when needed?
+        %buserexempts%b    allow exempts to be set by users directly?
+        %bdynamicinvites%b only activate invites on channel when needed?
+        %buserinvites%b    allow invites to be made by users directly?
+        %bautoop%b         automatically op users when they join the channel
+        %bbitch%b          only allow users with the +o flag to be ops on the
+                       channel
+        %bgreet%b          say a users info line when they join the channel
+        %bprotectops%b     re-op a +o user who gets deopped?
+        %bprotectfriends%b re-op a +f user who gets deopped?
+        %bdontkickops%b    never kick +o flag people
+        %binactive%b        never join this channel, or leave if already
+                        joined.
+                       Usefull as you can make the bot leave a channel without
+                       loosing it's settings or affecting any sharing.
+        %bstatuslog%b      log the channel status every 5 minutes
+        %bcycle%b          cycle the channel when it becomes op-less
+        %brevenge%b         take revenge on anyone who deops or kicks friends
+                        or ops
+        %brevengebot%b      take revenge on anyone who deops or kicks the bot
+        %bsecret%b          don't provide info about the channel over the
+                        botnet
+        %bshared%b         share user settings for this channel
+                        (NOTE: this can be set online now)
+        %bautovoice%b      this causes the bot to also monitor channel voice
+                       settings, using the +v/+q settings.
+        %bnodesynch%b       allow non-ops to perform channel modes? (Stops bot from
+                   fighting with services such as ChanServ)
+     The following are flood settings, they are set by doing
+     .chanset <channel> flood-type number:seconds where number and second are
+     integers indicating the number of times in how many seconds the flood will
+     be triggered.
+          .chanset <channel> flood-type 0:0
+     (or 0:1 or 1:0) will deactivate the respective flood setting.
+        %bflood-chan%b  this defines the flood level for public chatter and
+                    actions
+        %bflood-ctcp%b  this defines the flood level for ctcp's to the channel
+                    (include PING & VERSION), except for ACTION's
+        %bflood-join%b  this defines the number of joins from a give user at host
+                    that constituts a flood
+        %bflood-kick%b   how many kicks in the given time are a flood
+        %bflood-deop%b   deops in the given time
+%{+n|n}
+See also: %{+n}+chan, -chan,%{+n|n} chansave, chanload, chanset
Index: eggdrop1.7/modules/channels/help/channels.help
diff -u /dev/null eggdrop1.7/modules/channels/help/channels.help:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/help/channels.help	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,316 @@
+%{help=+ban}%{+o|o}
+###  %b+ban%b <hostmask> [channel] [%%<XdXhXm>] [comment]
+   Adds a ban to the list of permanent bans stored on the bot,
+   with optional comment and bantime.  This ban will be in effect for
+   every channel on the bot if no channel is supplied, and is stored
+   with your nickname and comment.  Prefixing a comment with @ will
+   make it only visible within the bot. Bantime has to be expressed
+   in days, hours and/or minutes.
+See also: bans, -ban, stick, unstick
+%{help=+exempt}%{+o|o}
+###  %b+exempt%b <hostmask> [channel] [%%<XdXhXm>] [comment]
+   Adds a ban exempt to the list of permanent exempts stored on the bot,
+   with optional comment and exempttime. This exempt will be in effect for
+   every channel on the bot if no channel is supplied, and is stored
+   with your nickname and comment.  Prefixing a comment with @ will
+   make it only visible within the bot. Exempttime has to be expressed
+   in days, hours and/or minutes.
+See also: exempts, -exempt, stick, unstick
+%{help=+invite}%{+o|o}
+###  %b+invite%b <hostmask> [channel] [%%<XdXhXm>] [comment]
+   Adds a invite to the list of permanent invites stored on the bot,
+   with optional comment and invitetime. This invite will be in effect for
+   every channel on the bot if no channel is supplied, and is stored
+   with your nickname and comment.  Prefixing a comment with @ will
+   make it only visible within the bot. invitetime has to be expressed
+   in days, hours and/or minutes.
+See also: invites, -invite, stick, unstick
+%{help=+chan}%{+n}
+### %b+chan%b <channel>
+     Lets you add another channel to the bot, this cause the bot to
+     join the channel, and for you to make changes to it's settings.
+     This channel is not permanent unless %b"chanfile"%b has been
+     defined in the config file, and is saved everytime the userfile
+     is saved, or by using %b'.chansave'%b
+
+See also: +chan, -chan, chanload, chanset, chaninfo
+%{help=+chrec}%{+m|m}
+###  %b+chrec%b <handle> [channel]
+   Adds an empty channel record for the user so that channel lastons and info
+   lines can be saved.  No flags are associated with the channel.
+
+See also: chattr
+%{help=-ban}%{+o|o}
+###  %b-ban%b <hostmask OR number>
+   Removes the ban from the list of permanent bans stored on the
+   bot -- you may reference the ban by the number shown in '.bans'.
+
+See also: bans, +ban, stick, unstick
+%{help=-exempt}%{+o|o}
+###  %b-exempt%b <hostmask OR number>
+   Removes the exemption from the list of permanent exempts stored
+   on the bot -- you may reference the exempt by the number shown in
+   '.exempts'.
+
+See also: exempts, +exempt, stick, unstick
+%{help=-invite}%{+o|o}
+###  %b-invite%b <hostmask OR number>
+   Removes the invite from the list of permanent invites stored
+   on the bot -- you may reference the ban by the number shown in
+   '.invites'.
+
+See also: invites, +invite, stick, unstick
+%{help=-chan}%{+n}
+### %b-chan%b <channel>
+     This removes ALL information about a channel from the bot. It's not
+     made permanent unless a channel-file has been defined in the user
+     file, in which case it's saved each time the usefile is, or by using
+     .chansave.
+
+     %f*** IMPORTANT ***%f
+
+     This erases ALL information about the channel, including channel
+     settings and channel records for users--%bEVERYTHING%b.
+
+     %bDO NOT%b use it to have the bot temporarily leave a channel.  This
+     command is for abandoning a channel (e.g. the channel will have
+     to be redefined and all user flags for that channel will have to
+     be redone.
+
+     Even if you don't have chanfile defined, it will still erase
+     all the channel records for users on the next userfile save.
+
+See also: +chan, chansave, chanload, chanset, chaninfo
+%{help=-chrec}%{+m|m}
+###  %b-chrec%b <handle> [channel]
+   Deletes a channel record for the user, including channel lastons, info
+   lines, and flags.
+
+See also: chattr
+%{help=bans}%{+o|o}
+###  %bbans%b [[channel] wildcard]
+###  %bbans%b all
+   Shows you a list of the global bans active on the current channel,
+   and the list of channel-specific bans, as well as any bans that
+   are on the channel but weren't placed by the bot.
+
+   Here's a sample entry;
+      [ 5] *!*habib@*frys.com (perm)
+           paulie: revolving check policy
+           Created 15:10
+   The number (5) can be used to reference the ban if you wish
+   to remove it (see '.-ban').  Next is the actual hostmask being
+   banned.  The "(perm)" means that the ban is "permanent": that
+   is, it doesn't automatically expire.  If there is an elapsed time
+   showing instead, the time displayed is how long the ban has been
+   active.  Those kind of bans expire after one hour.  The second line
+   of the ban entry is the comment ("revolving check policy" -- it
+   would seem that paulie had to stand in line for a while) and who put
+   the ban there (paulie).  The last line shows when the ban was added,
+   and possibly the last time the ban was activated on the channel (if
+   it's different from the creation time).
+
+   Sometimes there will be a "!" or "*" right before the number.  a "!"
+   means the ban is in the bot's permban list, but is not currently on
+   the channel.  a "*" marks a ban which is NOT in the permban list but
+   IS on the channel.
+
+   Af you use 'bans' it will show you only the bans which are
+   currently in action on the channel.  If you use 'bans all' it
+   will show you every ban in memory (with 'perm-bans' on, these
+   are identical).
+
+   If you use 'bans <wildcard>' it will list all the bans (active
+   or not) that match against your wildcard.  Consider it a 'bans
+   all' list matched against your wildcard.
+
+   The ban list may change according to which channel you're
+   currently viewing in the console.  Different bans may be active
+   on different channels.  If you specify a channel name, that
+   channel will be used instead of your current console channel.
+
+See also: -ban, +ban, console, set ban-time, stick, unstick
+%{help=invites}%{+o|o}
+###  %binvites%b [[channel] wildcard]
+###  %binvites%b all
+   Shows you a list of the global invites active on the current channel,
+   and the list of channel-specific invites, as well as any invites that
+   are on the channel but weren't placed by the bot.
+
+   Here's a sample entry;
+      [ 5] *!*habib@*frys.com (perm)
+           paulie: revolving check policy
+           Created 15:10
+   The number (5) can be used to reference the invite if you wish
+   to remove it (see '.-invite').  Next is the actual hostmask being
+   invitened.  The "(perm)" means that the invite is "permanent": that
+   is, it doesn't automatically expire.  If there is an elapsed time
+   showing instead, the time displayed is how long the invite has been
+   active.  Those kind of invites expire after one hour.  The second line
+   of the invite entry is the comment ("revolving check policy" -- it
+   would seem that paulie had to stand in line for a while) and who put
+   the invite there (paulie).  The last line shows when the invite was added,
+   and possibly the last time the invite was activated on the channel (if
+   it's different from the creation time).
+
+   Sometimes there will be a "!" or "*" right before the number.  a "!"
+   means the invite is in the bot's perminvite list, but is not currently on
+   the channel.  a "*" marks a invite which is NOT in the perminvite list but
+   IS on the channel.
+
+   Af you use 'invites' it will show you only the invites which are
+   currently in action on the channel.  If you use 'invites all' it
+   will show you every invite in memory (with 'perm-invites' on, these
+   are identical).
+
+   If you use 'invites <wildcard>' it will list all the invites (active
+   or not) that match against your wildcard.  Consider it a 'invites
+   all' list matched against your wildcard.
+
+   The invite list may change according to which channel you're
+   currently viewing in the console.  Different invites may be active
+   on different channels.  If you specify a channel name, that
+   channel will be used instead of your current console channel.
+
+See also: -invite, +invite, console, set invite-time, stick, unstick
+%{help=exempts}%{+o|o}
+###  %bexempts%b [[channel] wildcard]
+###  %bexempts%b all
+   Shows you a list of the global ban exemptions active on the current
+   channel, and the list of channel-specific ban exemptions, as well
+   as any ban exemptions that are on the channel but weren't placed by
+   the bot.
+
+   Here's a sample entry;
+      [ 5] *!*habib@*frys.com (perm)
+           paulie: revolving check policy
+           Created 15:10
+   The number (5) can be used to reference the ban exemption if you wish
+   to remove it (see '.-exempt').  Next is the actual hostmask being
+   exempted. The "(perm)" means that the ban exemption is "permanent":
+   that is, it doesn't automatically expire.  If there is an elapsed time
+   showing instead, the time displayed is how long the ban exemption has
+   been active.  Those kind of ban exemptions expire after one hour.  The
+   second line of the ban exemption entry is the comment ("revolving check
+   policy" -- it would seem that paulie had to stand in line for a while)
+   and who put the ban exemption there (paulie).  The last line shows when
+   the ban exemption was added, and possibly the last time the ban exemption
+   was activated on the channel (if it's different from the creation time).
+
+   Sometimes there will be a "!" or "*" right before the number.  a "!"
+   means the ban exemption is in the bot's permban exemption list, but is
+   not currently on the channel.  a "*" marks a ban exemption which is NOT
+   in the permban exemption list but IS on the channel.
+
+   Af you use 'exempts' it will show you only the ban exemptions which are
+   currently in action on the channel.  If you use 'ban exemptions all' it
+   will show you every ban exemption in memory (with 'perm-exempts' on,
+   these are identical).
+
+   If you use 'exempts <wildcard>' it will list all the ban exemptions
+   (active or not) that match against your wildcard.  Consider it a 'exempts
+   all' list matched against your wildcard.
+
+   The ban exemption list may change according to which channel you're
+   currently viewing in the console.  Different ban exemptions may be active
+   on different channels.  If you specify a channel name, that
+   channel will be used instead of your current console channel.
+
+See also: -exempt, +exempt, console, set exempt-time, stick, unstick
+%{help=chanload}%{+n|n}
+###  %bchanload%b
+     Reloads the channel settings from the file defined by the
+     %b"chanfile"%b setting.  This allows you to return to
+     a previous setting if you've really messed things up.
+See also: %{+n}+chan, -chan,%{+N} chanload, chanset, chaninfo
+%{help=chanset}%{+n|n}
+###  %bchanset%b <channel> <settings>
+     Allows you to change the channel settings for a channel.
+     (See %b'.help chaninfo'%b for the settings)
+     These are used until the next rehash/chanload/restart,
+     and are saved whenever the channel settings are saved.
+See also: %{+n}+chan, -chan%{+N} chanload, chanset, chaninfo
+%{help=chansave}%{+n|n}
+###  %bchansave%b
+     Saves the channel settings to the file defined by the %b"chanfile"%b
+     setting.  This file is reloaded during rehash and restarting the bot.
+     This allows you to change channel modes without having to edit the
+     config file every time.
+See also: %{+n}+chan, -chan%{+N} chanload, chanset, chaninfo
+%{help=chinfo}%{+m|m}
+###  %bchinfo%b <user> [channel] [info-line]
+   Sets the information line for a user.  This line is shown via /msg
+   commands %b'who'%b and %b'whois'%b, and if you have set greet on, it is
+   shown when a user joins the channel.  If the info line begins with a
+   '@', then it is "locked", and that user may no longer change it.
+   If the channel name is omitted, the default info line is changed.
+###  %bchinfo%b <user> [channel] none
+   Erases a user's info line.
+See also: info
+%{help=info}
+###  %binfo%b [channel] [info-line]
+   sets your info line.  this is shown via /msg commands %b'who'%b and
+   %b'whois'%b, and possibly when you join the channel.  if you omit the
+   info line, it will just show you what your current info line is.
+   if you omit the channel name, your default info line will be
+   displayed/changed.  (this is the info line shown for channels
+   where you have no specific info line set.)
+
+   Note that by beginning a users info line with '@' it will be locked
+   and they will not be able to change it.
+###  %binfo%b [channel] none
+   removes your info line.
+%{help=stick}%{+o|o}
+###  %bstick%b [ban/exempt/invite] <hostmask OR number> [channel]
+   makes a ban/exempt/invite "sticky" -- meaning the bot will
+   always try to keep it active on the channel, even if the
+   channel is using dynamic bans.  obviously if the channel isn't
+   using dynamic bans, this has no effect.
+see also: bans, exempts, invites, unstick, +ban, +exempt, +invite
+%{help=unstick}%{+o|o}
+###  %bunstick%b [ban/exempt/invite] <hostmask OR number> [channel]
+   turns a "sticky" ban/exempt/invite normal again.
+
+see also: bans, exempts, invites, stick, -ban, -exempt, -invite
+%{help=channels module}%{+n}
+###  help on the %bchannels module%b
+   The channels module provides the means for STORING channel
+   information about users, it DOES NOT provide the means
+   for sitting on a channel (this is in the irc module).
+   The following commands are provided by the channels module:
+      %binfo%b
+%{+o|o}
+   for channel ops:
+      %b+ban%b      %b-ban%b      %bbans%b      %bstick%b
+      %bunstick%b   %b+exempt%b   %b-exempt%b   %bexempts%b
+      %b+invite%b   %b-invite%b   %binvites%b
+%{+m|m}
+   for channel masters:
+      %b+chrec%b    %b-chrec%b    %bchaninfo%b  %bchinfo%b
+%{+n|n}
+   for channel owners:
+      %bchanload%b  %bchansave%b  %bchanset%b
+%{+n}
+   for global owners:
+      %b+chan%b     %b-chan%b
+   Tcl variables for configuring the channels module:
+     %bshare-greet%b  %buse-info%b    %bban-time%b
+     %bexempt-time%b  %binvite-time%b %bchanfile%b
+   (Use %b'.help set <variable>'%b for more info)
+%{help=all}%{+n}
+###  commands for the %bchannels module%b
+%{+o|o}
+  for channel ops:
+     %b+ban%b      %b-ban%b      %bbans%b      %bstick%b
+     %bunstick%b   %b+exempt%b   %b-exempt%b   %bexempts%b
+     %b+invite%b   %b-invite%b   %binvites%b
+%{+m|m}
+  for channel masters:
+     %b+chrec%b    %b-chrec%b    %bchaninfo%b  %bchinfo%b
+%{+n|n}
+  for channel owners:
+     %bchanload%b  %bchansave%b  %bchanset%b
+%{+n}
+  for global owners:
+     %b+chan%b     %b-chan%b
+
Index: eggdrop1.7/modules/channels/help/set/channels.help
diff -u /dev/null eggdrop1.7/modules/channels/help/set/channels.help:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/help/set/channels.help	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,41 @@
+%{help=set share-greet}%{+n}
+###  %bset share-greet%b 0/1
+   if on (1) then share-bots will share their user's info lines
+   with other share bots.
+%{help=set use-info}
+%{+n}
+###  %bset use-info%b <0/1>
+   specifies whether to activate the info system.  if this is off,
+   the bot will not accept changes to any user's "info line" (the
+   line shown for '/msg whois' and '/msg who' requests, and shown
+   when a user joins the channel is you have set 'greet' on).
+   this disables '/msg whois' and '/msg who' obviously, and makes
+   the setting of 'greet' meaningless.
+%{help=set ban-time}
+%{+n}
+###  %bset ban-time%b <#>
+   specifies how long (in minutes) a temporary ban will last
+   on the bot before being purged.
+see also: bans, +ban, kickban, set ignore-time
+%{help=set exempt-time}
+%{+n}
+###  %bset exempt-time%b <#>
+   specifies how long (in minutes) a temporary exemption will last
+   on the channel before being purged. if set to 0, bot never
+   removes it. this is an IRCNET feature.
+%{help=set invite-time}
+%{+n}
+###  %bset invite-time%b <#>
+   specifies how long (in minutes) a temporary invitation will
+   last on the channel before being purged. if set to 0, bot never
+   removes it. this is an IRCNET feature.
+%{help=set chanfile}
+%{+n}
+###  %bset chanfile%b
+   this sets the file used to store dynamic channel information
+   for the bot.  it can only be set in the config file.
+%{help=set quiet-save}
+%{+n}
+###  %bset quiet-save%b 0/1
+   this sets if "Writing user file..." & "Writing channel file..."
+   are logged.
Index: eggdrop1.7/modules/channels/modinfo
diff -u /dev/null eggdrop1.7/modules/channels/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/modinfo	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,5 @@
+DESC:The channels module provides channel related support for the bot. It's
+DESC:essential for hubs that share userfiles, as well as normal bots
+DESC:monitoring channels on IRC.
+DESC:
+DESC:You will normally want to ENABLE this module.
Index: eggdrop1.7/modules/channels/tclchan.c
diff -u /dev/null eggdrop1.7/modules/channels/tclchan.c:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/tclchan.c	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,1708 @@
+/*
+ * tclchan.c -- part of channels.mod
+ *
+ * $Id: tclchan.c,v 1.1 2001/10/27 16:34:48 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/* flagmaps.c defines flag name to flag value structures. */
+#include "flagmaps.c"
+
+static int lookup_flag_by_name(channel_flag_map_t *map, char *name, int *flagval);
+
+static int tcl_killban STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " ban");
+  if (u_delban(NULL, argv[1], 1) > 0) {
+    chan = chanset;
+    while (chan != NULL) {
+      add_mode(chan, '-', 'b', argv[1]);
+      chan = chan->next;
+    }
+    Tcl_AppendResult(irp, "1", NULL);
+  } else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_killchanban STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(3, 3, " channel ban");
+  chan = findchan_by_dname(argv[1]);
+  if (!chan) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (u_delban(chan, argv[2], 1) > 0) {
+    add_mode(chan, '-', 'b', argv[2]);
+    Tcl_AppendResult(irp, "1", NULL);
+  } else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_killexempt STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " exempt");
+  if (u_delexempt(NULL,argv[1],1) > 0) {
+    chan = chanset;
+    while (chan != NULL) {
+      add_mode(chan, '-', 'e', argv[1]);
+      chan = chan->next;
+    }
+    Tcl_AppendResult(irp, "1", NULL);
+  } else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_killchanexempt STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(3, 3, " channel exempt");
+  chan = findchan_by_dname(argv[1]);
+  if (!chan) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (u_delexempt(chan, argv[2],1) > 0) {
+    add_mode(chan, '-', 'e', argv[2]);
+    Tcl_AppendResult(irp, "1", NULL);
+  } else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_killinvite STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " invite");
+  if (u_delinvite(NULL,argv[1],1) > 0) {
+    chan = chanset;
+    while (chan != NULL) {
+      add_mode(chan, '-', 'I', argv[1]);
+      chan = chan->next;
+    }
+    Tcl_AppendResult(irp, "1", NULL);
+  } else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_killchaninvite STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(3, 3, " channel invite");
+  chan = findchan_by_dname(argv[1]);
+  if (!chan) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (u_delinvite(chan, argv[2],1) > 0) {
+    add_mode(chan, '-', 'I', argv[2]);
+    Tcl_AppendResult(irp, "1", NULL);
+   } else
+     Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_stick STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " ban ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (!chan) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_setsticky_ban(chan, argv[1], !strncmp(argv[0], "un", 2) ? 0 : 1))
+      ok = 1;
+  }
+  if (!ok && u_setsticky_ban(NULL, argv[1],
+      !strncmp(argv[0], "un", 2) ? 0 : 1))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_stickinvite STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " ban ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (!chan) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_setsticky_invite(chan, argv[1], !strncmp(argv[0], "un", 2) ? 0 : 1))
+      ok = 1;
+  }
+  if (!ok && u_setsticky_invite(NULL, argv[1],
+      !strncmp(argv[0], "un", 2) ? 0 : 1))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_stickexempt STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " ban ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (!chan) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_setsticky_exempt(chan, argv[1], !strncmp(argv[0], "un", 2) ? 0 : 1))
+      ok = 1;
+  }
+  if (!ok && u_setsticky_exempt(NULL, argv[1],
+      !strncmp(argv[0], "un", 2) ? 0 : 1))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_isban STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " ban ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (!chan) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_equals_mask(chan->bans, argv[1]))
+      ok = 1;
+  }
+  if (u_equals_mask(global_bans, argv[1]))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_isexempt STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " exempt ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (!chan) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_equals_mask(chan->exempts, argv[1]))
+      ok = 1;
+  }
+  if (u_equals_mask(global_exempts,argv[1]))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_isinvite STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " invite ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (!chan) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_equals_mask(chan->invites, argv[1]))
+      ok = 1;
+  }
+  if (u_equals_mask(global_invites,argv[1]))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+
+static int tcl_isbansticky STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " ban ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (!chan) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_sticky_mask(chan->bans, argv[1]))
+      ok = 1;
+  }
+  if (u_sticky_mask(global_bans, argv[1]))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_isexemptsticky STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " exempt ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (!chan) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_sticky_mask(chan->exempts, argv[1]))
+      ok = 1;
+  }
+  if (u_sticky_mask(global_exempts,argv[1]))
+    ok = 1;
+  if (ok)
+       Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_isinvitesticky STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " invite ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (!chan) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_sticky_mask(chan->invites, argv[1]))
+      ok = 1;
+  }
+  if (u_sticky_mask(global_invites,argv[1]))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+       Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_ispermban STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " ban ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_equals_mask(chan->bans, argv[1]) == 2)
+      ok = 1;
+  }
+  if (u_equals_mask(global_bans, argv[1]) == 2)
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_ispermexempt STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " exempt ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_equals_mask(chan->exempts, argv[1]) == 2)
+      ok = 1;
+  }
+  if (u_equals_mask(global_exempts,argv[1]) == 2)
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+   else
+     Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_isperminvite STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " invite ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_equals_mask(chan->invites, argv[1]) == 2)
+      ok = 1;
+  }
+  if (u_equals_mask(global_invites,argv[1]) == 2)
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_matchban STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " user!nick at host ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_match_mask(chan->bans, argv[1]))
+      ok = 1;
+  }
+  if (u_match_mask(global_bans, argv[1]))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_matchexempt STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " user!nick at host ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_match_mask(chan->exempts, argv[1]))
+      ok = 1;
+  }
+  if (u_match_mask(global_exempts,argv[1]))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_matchinvite STDVAR
+{
+  struct chanset_t *chan;
+  int ok = 0;
+
+  BADARGS(2, 3, " user!nick at host ?channel?");
+  if (argc == 3) {
+    chan = findchan_by_dname(argv[2]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+    if (u_match_mask(chan->invites, argv[1]))
+      ok = 1;
+  }
+  if (u_match_mask(global_invites,argv[1]))
+    ok = 1;
+  if (ok)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_newchanban STDVAR
+{
+  time_t expire_time;
+  struct chanset_t *chan;
+  char ban[161], cmt[MASKREASON_LEN], from[HANDLEN + 1];
+  int sticky = 0;
+
+  BADARGS(5, 7, " channel ban creator comment ?lifetime? ?options?");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (argc == 7) {
+    if (!strcasecmp(argv[6], "none"));
+    else if (!strcasecmp(argv[6], "sticky"))
+      sticky = 1;
+    else {
+      Tcl_AppendResult(irp, "invalid option ", argv[6], " (must be one of: ",
+		       "sticky, none)", NULL);
+      return TCL_ERROR;
+    }
+  }
+  strncpyz(ban, argv[2], sizeof ban);
+  strncpyz(from, argv[3], sizeof from);
+  strncpyz(cmt, argv[4], sizeof cmt);
+  if (argc == 5)
+    expire_time = now + (60 * ban_time);
+  else {
+    if (atoi(argv[5]) == 0)
+      expire_time = 0L;
+    else
+      expire_time = now + (atoi(argv[5]) * 60);
+  }
+  if (u_addban(chan, ban, from, cmt, expire_time, sticky))
+    if (sticky || !channel_dynamicbans(chan))
+    add_mode(chan, '+', 'b', ban);
+  return TCL_OK;
+}
+
+static int tcl_newban STDVAR
+{
+  time_t expire_time;
+  struct chanset_t *chan;
+  char ban[UHOSTLEN], cmt[MASKREASON_LEN], from[HANDLEN + 1];
+  int sticky = 0;
+
+  BADARGS(4, 6, " ban creator comment ?lifetime? ?options?");
+  if (argc == 6) {
+    if (!strcasecmp(argv[5], "none"));
+    else if (!strcasecmp(argv[5], "sticky"))
+      sticky = 1;
+    else {
+      Tcl_AppendResult(irp, "invalid option ", argv[5], " (must be one of: ",
+		       "sticky, none)", NULL);
+      return TCL_ERROR;
+    }
+  }
+  strncpyz(ban, argv[1], sizeof ban);
+  strncpyz(from, argv[2], sizeof from);
+  strncpyz(cmt, argv[3], sizeof cmt);
+  if (argc == 4)
+    expire_time = now + (60 * ban_time);
+  else {
+    if (atoi(argv[4]) == 0)
+      expire_time = 0L;
+    else
+      expire_time = now + (atoi(argv[4]) * 60);
+  }
+  u_addban(NULL, ban, from, cmt, expire_time, sticky);
+  chan = chanset;
+  while (chan != NULL) {
+    if (sticky || !channel_dynamicbans(chan))
+    add_mode(chan, '+', 'b', ban);
+    chan = chan->next;
+  }
+  return TCL_OK;
+}
+
+static int tcl_newchanexempt STDVAR
+{
+  time_t expire_time;
+  struct chanset_t *chan;
+  char exempt[161], cmt[MASKREASON_LEN], from[HANDLEN + 1];
+  int sticky = 0;
+
+  BADARGS(5, 7, " channel exempt creator comment ?lifetime? ?options?");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (argc == 7) {
+    if (!strcasecmp(argv[6], "none"));
+    else if (!strcasecmp(argv[6], "sticky"))
+      sticky = 1;
+    else {
+      Tcl_AppendResult(irp, "invalid option ", argv[6], " (must be one of: ",
+		       "sticky, none)", NULL);
+      return TCL_ERROR;
+    }
+  }
+  strncpyz(exempt, argv[2], sizeof exempt);
+  strncpyz(from, argv[3], sizeof from);
+  strncpyz(cmt, argv[4], sizeof cmt);
+  if (argc == 5)
+    expire_time = now + (60 * exempt_time);
+  else {
+    if (atoi(argv[5]) == 0)
+      expire_time = 0L;
+    else
+      expire_time = now + (atoi(argv[5]) * 60);
+  }
+  if (u_addexempt(chan, exempt, from, cmt, expire_time,sticky))
+    if (sticky || !channel_dynamicexempts(chan))
+    add_mode(chan, '+', 'e', exempt);
+  return TCL_OK;
+}
+
+static int tcl_newexempt STDVAR
+{
+  time_t expire_time;
+  struct chanset_t *chan;
+  char exempt[UHOSTLEN], cmt[MASKREASON_LEN], from[HANDLEN + 1];
+  int sticky = 0;
+
+  BADARGS(4, 6, " exempt creator comment ?lifetime? ?options?");
+  if (argc == 6) {
+    if (!strcasecmp(argv[5], "none"));
+    else if (!strcasecmp(argv[5], "sticky"))
+      sticky = 1;
+    else {
+      Tcl_AppendResult(irp, "invalid option ", argv[5], " (must be one of: ",
+		       "sticky, none)", NULL);
+      return TCL_ERROR;
+    }
+  }
+  strncpyz(exempt, argv[1], sizeof exempt);
+  strncpyz(from, argv[2], sizeof from);
+  strncpyz(cmt, argv[3], sizeof cmt);
+  if (argc == 4)
+    expire_time = now + (60 * exempt_time);
+  else {
+    if (atoi(argv[4]) == 0)
+      expire_time = 0L;
+    else
+      expire_time = now + (atoi(argv[4]) * 60);
+  }
+  u_addexempt(NULL,exempt, from, cmt, expire_time,sticky);
+  chan = chanset;
+  while (chan != NULL) {
+    if (sticky || !channel_dynamicexempts(chan))
+    add_mode(chan, '+', 'e', exempt);
+    chan = chan->next;
+  }
+  return TCL_OK;
+}
+
+static int tcl_newchaninvite STDVAR
+{
+  time_t expire_time;
+  struct chanset_t *chan;
+  char invite[161], cmt[MASKREASON_LEN], from[HANDLEN + 1];
+  int sticky = 0;
+
+  BADARGS(5, 7, " channel invite creator comment ?lifetime? ?options?");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (argc == 7) {
+    if (!strcasecmp(argv[6], "none"));
+    else if (!strcasecmp(argv[6], "sticky"))
+      sticky = 1;
+    else {
+      Tcl_AppendResult(irp, "invalid option ", argv[6], " (must be one of: ",
+		       "sticky, none)", NULL);
+      return TCL_ERROR;
+    }
+  }
+  strncpyz(invite, argv[2], sizeof invite);
+  strncpyz(from, argv[3], sizeof from);
+  strncpyz(cmt, argv[4], sizeof cmt);
+  if (argc == 5)
+    expire_time = now + (60 * invite_time);
+  else {
+    if (atoi(argv[5]) == 0)
+      expire_time = 0L;
+    else
+      expire_time = now + (atoi(argv[5]) * 60);
+  }
+  if (u_addinvite(chan, invite, from, cmt, expire_time,sticky))
+    if (sticky || !channel_dynamicinvites(chan))
+    add_mode(chan, '+', 'I', invite);
+  return TCL_OK;
+}
+
+static int tcl_newinvite STDVAR
+{
+  time_t expire_time;
+  struct chanset_t *chan;
+  char invite[UHOSTLEN], cmt[MASKREASON_LEN], from[HANDLEN + 1];
+  int sticky = 0;
+
+  BADARGS(4, 6, " invite creator comment ?lifetime? ?options?");
+  if (argc == 6) {
+    if (!strcasecmp(argv[5], "none"));
+    else if (!strcasecmp(argv[5], "sticky"))
+      sticky = 1;
+    else {
+      Tcl_AppendResult(irp, "invalid option ", argv[5], " (must be one of: ",
+		       "sticky, none)", NULL);
+      return TCL_ERROR;
+    }
+  }
+  strncpyz(invite, argv[1], sizeof invite);
+  strncpyz(from, argv[2], sizeof from);
+  strncpyz(cmt, argv[3], sizeof cmt);
+  if (argc == 4)
+     expire_time = now + (60 * invite_time);
+  else {
+    if (atoi(argv[4]) == 0)
+      expire_time = 0L;
+    else
+      expire_time = now + (atoi(argv[4]) * 60);
+  }
+  u_addinvite(NULL,invite, from, cmt, expire_time,sticky);
+  chan = chanset;
+  while (chan != NULL) {
+    if (sticky || !channel_dynamicinvites(chan))
+     add_mode(chan, '+', 'I', invite);
+     chan = chan->next;
+  }
+  return TCL_OK;
+}
+
+static int tcl_channel_info(Tcl_Interp * irp, struct chanset_t *chan)
+{
+  char s[121];
+  struct udef_struct *ul;
+  channel_flag_map_t *flag_map;
+
+  get_mode_protect(chan, s);
+  Tcl_AppendElement(irp, s);
+  simple_sprintf(s, "%d", chan->idle_kick);
+  Tcl_AppendElement(irp, s);
+  simple_sprintf(s, "%d", chan->stopnethack_mode);
+  Tcl_AppendElement(irp, s);
+  simple_sprintf(s, "%d", chan->revenge_mode);
+  Tcl_AppendElement(irp, s);
+  simple_sprintf(s, "%d:%d", chan->flood_pub_thr, chan->flood_pub_time);
+  Tcl_AppendElement(irp, s);
+  simple_sprintf(s, "%d:%d", chan->flood_ctcp_thr, chan->flood_ctcp_time);
+  Tcl_AppendElement(irp, s);
+  simple_sprintf(s, "%d:%d", chan->flood_join_thr, chan->flood_join_time);
+  Tcl_AppendElement(irp, s);
+  simple_sprintf(s, "%d:%d", chan->flood_kick_thr, chan->flood_kick_time);
+  Tcl_AppendElement(irp, s);
+  simple_sprintf(s, "%d:%d", chan->flood_deop_thr, chan->flood_deop_time);
+  Tcl_AppendElement(irp, s);
+  simple_sprintf(s, "%d:%d", chan->flood_nick_thr, chan->flood_nick_time);
+  Tcl_AppendElement(irp, s);
+  simple_sprintf(s, "%d:%d", chan->aop_min, chan->aop_max);
+  Tcl_AppendElement(irp, s);
+  for (flag_map = normal_flag_map; flag_map->name; flag_map++) {
+    char buf[50], dir;
+    if (chan->status & flag_map->flagval) dir = '+';
+    else dir = '-';
+    sprintf(buf, "%c%s", dir, flag_map->name);
+    Tcl_AppendElement(irp, buf);
+  }
+  for (flag_map = stupid_ircnet_flag_map; flag_map->name; flag_map++) {
+    char buf[50], dir;
+    if (chan->status & flag_map->flagval) dir = '+';
+    else dir = '-';
+    sprintf(buf, "%c%s", dir, flag_map->name);
+    Tcl_AppendElement(irp, buf);
+  }
+  for (ul = udef; ul && ul->defined && ul->name; ul = ul->next) {
+      if (ul->type == UDEF_FLAG) {
+        simple_sprintf(s, "%c%s", getudef(ul->values, chan->dname) ? '+' : '-',
+		       ul->name);
+        Tcl_AppendElement(irp, s);
+      } else if (ul->type == UDEF_INT) {
+        simple_sprintf(s, "%s %d", ul->name, getudef(ul->values, chan->dname));
+        Tcl_AppendElement(irp, s);
+	} else if (ul->type == UDEF_STR) {
+		char *p;
+		char *buf;
+
+		p = (char *)getudef(ul->values, chan->dname);
+		if (!p) p = "{}";
+		buf = malloc(strlen(ul->name) + strlen(p) + 2);
+		simple_sprintf(buf, "%s %s", ul->name, p);
+		Tcl_AppendElement(irp, buf);
+		free(buf);
+      } else
+        debug1("UDEF-ERROR: unknown type %d", ul->type);
+    }
+  return TCL_OK;
+}
+
+static int tcl_channel_get(Tcl_Interp * irp, struct chanset_t *chan, char *setting)
+{
+	char s[121];
+	struct udef_struct *ul;
+	int flagval;
+
+#define CHECK(x) !strcmp(setting, x)
+	if (CHECK("chanmode")) {
+		get_mode_protect(chan, s);
+	}
+	else if (CHECK("idle-kick")) simple_sprintf(s, "%d", chan->idle_kick);
+	else if (CHECK("stop-net-hack")) simple_sprintf(s, "%d", chan->stopnethack_mode);
+	else if (CHECK("revenge-mode")) simple_sprintf(s, "%d", chan->revenge_mode);
+	else if (CHECK("flood-pub")) simple_sprintf(s, "%d %d", chan->flood_pub_thr, chan->flood_pub_time);
+	else if (CHECK("flood-ctcp")) simple_sprintf(s, "%d %d", chan->flood_ctcp_thr, chan->flood_ctcp_time);
+	else if (CHECK("flood-join")) simple_sprintf(s, "%d %d", chan->flood_join_thr, chan->flood_join_time);
+	else if (CHECK("flood-kick")) simple_sprintf(s, "%d %d", chan->flood_kick_thr, chan->flood_kick_time);
+	else if (CHECK("flood-deop")) simple_sprintf(s, "%d %d", chan->flood_deop_thr, chan->flood_deop_time);
+	else if (CHECK("flood-nick")) simple_sprintf(s, "%d %d", chan->flood_nick_thr, chan->flood_nick_time);
+	else if (CHECK("aop-delay")) simple_sprintf(s, "%d %d", chan->aop_min, chan->aop_max);
+	else if (lookup_flag_by_name(normal_flag_map, setting, &flagval)) simple_sprintf(s, "%d", chan->status & flagval);
+	else if (lookup_flag_by_name(stupid_ircnet_flag_map, setting, &flagval)) simple_sprintf(s, "%d", chan->ircnet_status & flagval);
+	else {
+		/* Hopefully it's a user-defined flag. */
+		for (ul = udef; ul && ul->name; ul = ul->next) {
+			if (!strcmp(setting, ul->name)) break;
+		}
+		if (!ul || !ul->name) {
+			/* Error if it wasn't found. */
+			Tcl_AppendResult(irp, "Unknown channel setting.", NULL);
+			return(TCL_ERROR);
+		}
+		if (ul->type == UDEF_STR) {
+			char **elms = NULL, *result, *value;
+			int nelms = 0;
+
+			/* If it's unset then give them an empty string. */
+			value = (char *)getudef(ul->values, chan->dname);
+			if (!value) value = "";
+
+			Tcl_SplitList(irp, (char *)value, &nelms, &elms);
+			if (nelms < 1) result = "";
+			else result = elms[0];
+
+			Tcl_AppendResult(irp, result, NULL);
+			if (elms) Tcl_Free((char *)elms);
+			return(TCL_OK);
+		}
+		else {
+			/* Flag or int, all the same. */
+			simple_sprintf(s, "%d", getudef(ul->values, chan->dname));
+			Tcl_AppendResult(irp, s, NULL);
+			return(TCL_OK);
+		}
+	}
+	/* Ok, if we make it this far, the result is "s". */
+	Tcl_AppendResult(irp, s, NULL);
+	return(TCL_OK);
+}
+
+static int tcl_channel STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 999, " command ?options?");
+  if (!strcmp(argv[1], "add")) {
+    BADARGS(3, 4, " add channel-name ?options-list?");
+    if (argc == 3)
+      return tcl_channel_add(irp, argv[2], "");
+    return tcl_channel_add(irp, argv[2], argv[3]);
+  }
+  if (!strcmp(argv[1], "set")) {
+    BADARGS(3, 999, " set channel-name ?options?");
+    chan = findchan_by_dname(argv[2]);
+    if (chan == NULL) {
+      if (chan_hack == 1)
+	return TCL_OK;		/* Ignore channel settings for a static
+				 * channel which has been removed from
+				 * the config */
+      Tcl_AppendResult(irp, "no such channel record", NULL);
+      return TCL_ERROR;
+    }
+    return tcl_channel_modify(irp, chan, argc - 3, &argv[3]);
+  }
+  if (!strcmp(argv[1], "get")) {
+    BADARGS(4, 4, " get channel-name setting-name");
+    chan = findchan_by_dname(argv[2]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "no such channel record", NULL);
+      return TCL_ERROR;
+    }
+    return(tcl_channel_get(irp, chan, argv[3]));
+  }
+  if (!strcmp(argv[1], "info")) {
+    BADARGS(3, 3, " info channel-name");
+    chan = findchan_by_dname(argv[2]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "no such channel record", NULL);
+      return TCL_ERROR;
+    }
+    return tcl_channel_info(irp, chan);
+  }
+  if (!strcmp(argv[1], "remove")) {
+    BADARGS(3, 3, " remove channel-name");
+    chan = findchan_by_dname(argv[2]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "no such channel record", NULL);
+      return TCL_ERROR;
+    }
+    remove_channel(chan);
+    return TCL_OK;
+  }
+  Tcl_AppendResult(irp, "unknown channel command: should be one of: ",
+		   "add, set, info, remove", NULL);
+  return TCL_ERROR;
+}
+
+static int lookup_flag_by_name(channel_flag_map_t *map, char *name, int *flagval)
+{
+	channel_flag_map_t *flagmap;
+
+	for (flagmap = map; flagmap->name; flagmap++) {
+		if (!strcmp(flagmap->name, name)) {
+			*flagval = flagmap->flagval;
+			return(1);
+		}
+	}
+	return(0);
+}
+
+/* Parse options for a channel.
+ */
+static int tcl_channel_modify(Tcl_Interp * irp, struct chanset_t *chan,
+			      int items, char **item)
+{
+  int i, x = 0, found,
+      old_status = chan->status,
+      old_mode_mns_prot = chan->mode_mns_prot,
+      old_mode_pls_prot = chan->mode_pls_prot;
+  struct udef_struct *ul = udef;
+  module_entry *me;
+
+  for (i = 0; i < items; i++) {
+    if (!strcmp(item[i], "chanmode")) {
+      i++;
+      if (i >= items) {
+	if (irp)
+	  Tcl_AppendResult(irp, "channel chanmode needs argument", NULL);
+	return TCL_ERROR;
+      }
+      if (strlen(item[i]) > 120)
+	item[i][120] = 0;
+      set_mode_protect(chan, item[i]);
+    } else if (!strcmp(item[i], "idle-kick")) {
+      i++;
+      if (i >= items) {
+	if (irp)
+	  Tcl_AppendResult(irp, "channel idle-kick needs argument", NULL);
+	return TCL_ERROR;
+      }
+      chan->idle_kick = atoi(item[i]);
+    } else if (!strcmp(item[i], "dont-idle-kick"))
+      chan->idle_kick = 0;
+    else if (!strcmp(item[i], "stopnethack-mode")) {
+      i++;
+      if (i >= items) {
+	if (irp)
+	  Tcl_AppendResult(irp, "channel stopnethack-mode needs argument", NULL);
+	return TCL_ERROR;
+      }
+      chan->stopnethack_mode = atoi(item[i]);
+    } else if (!strcmp(item[i], "revenge-mode")) {
+      i++;
+      if (i >= items) {
+        if (irp)
+          Tcl_AppendResult(irp, "channel revenge-mode needs argument", NULL);
+        return TCL_ERROR;
+      }
+      chan->revenge_mode = atoi(item[i]);
+    }
+    else if (item[i][0] == '+' || item[i][0] == '-') {
+      int flagval;
+
+      if (lookup_flag_by_name(normal_flag_map, item[i]+1, &flagval)) {
+        if (item[i][0] == '-') chan->status &= ~flagval;
+        else chan->status |= flagval;
+      }
+      else if (lookup_flag_by_name(stupid_ircnet_flag_map, item[i]+1, &flagval)) {
+        if (item[i][0] == '-') chan->ircnet_status &= ~flagval;
+        else chan->ircnet_status |= flagval;
+      }
+      else {
+        /* Hopefully it's a user-defined flag! */
+        goto check_for_udef_flags;
+      }
+    }
+    else if (!strncmp(item[i], "flood-", 6)) {
+      int *pthr = 0, *ptime;
+      char *p;
+
+      if (!strcmp(item[i] + 6, "chan")) {
+	pthr = &chan->flood_pub_thr;
+	ptime = &chan->flood_pub_time;
+      } else if (!strcmp(item[i] + 6, "join")) {
+	pthr = &chan->flood_join_thr;
+	ptime = &chan->flood_join_time;
+      } else if (!strcmp(item[i] + 6, "ctcp")) {
+	pthr = &chan->flood_ctcp_thr;
+	ptime = &chan->flood_ctcp_time;
+      } else if (!strcmp(item[i] + 6, "kick")) {
+	pthr = &chan->flood_kick_thr;
+	ptime = &chan->flood_kick_time;
+      } else if (!strcmp(item[i] + 6, "deop")) {
+	pthr = &chan->flood_deop_thr;
+	ptime = &chan->flood_deop_time;
+      } else if (!strcmp(item[i] + 6, "nick")) {
+	pthr = &chan->flood_nick_thr;
+	ptime = &chan->flood_nick_time;
+      } else {
+	if (irp)
+	  Tcl_AppendResult(irp, "illegal channel flood type: ", item[i], NULL);
+	return TCL_ERROR;
+      }
+      i++;
+      if (i >= items) {
+	if (irp)
+	  Tcl_AppendResult(irp, item[i - 1], " needs argument", NULL);
+	return TCL_ERROR;
+      }
+      p = strchr(item[i], ':');
+      if (p) {
+	*p++ = 0;
+	*pthr = atoi(item[i]);
+	*ptime = atoi(p);
+	*--p = ':';
+      } else {
+	*pthr = atoi(item[i]);
+	*ptime = 1;
+      }
+    } else if (!strncmp(item[i], "aop-delay", 9)) {
+      char *p;
+      i++;
+      if (i >= items) {
+	if (irp)
+	  Tcl_AppendResult(irp, item[i - 1], " needs argument", NULL);
+	return TCL_ERROR;
+      }
+      p = strchr(item[i], ':');
+      if (p) {
+	p++;
+	chan->aop_min = atoi(item[i]);
+	chan->aop_max = atoi(p);
+      } else {
+	chan->aop_min = atoi(item[i]);
+	chan->aop_max = chan->aop_min;
+      }
+    } else {
+      if (!strncmp(item[i] + 1, "udef-flag-", 10))
+        initudef(UDEF_FLAG, item[i] + 11, 0);
+      else if (!strncmp(item[i], "udef-int-", 9))
+        initudef(UDEF_INT, item[i] + 9, 0);
+	else if (!strncmp(item[i], "udef-str-", 9))
+		initudef(UDEF_STR, item[i] + 9, 0);
+check_for_udef_flags:
+      found = 0;
+      for (ul = udef; ul; ul = ul->next) {
+        if (ul->type == UDEF_FLAG &&
+	     /* Direct match when set during .chanset ... */
+	    (!strcasecmp(item[i] + 1, ul->name) ||
+	     /* ... or with prefix when set during chanfile load. */
+	     (!strncmp(item[i] + 1, "udef-flag-", 10) &&
+	      !strcasecmp(item[i] + 11, ul->name)))) {
+          if (item[i][0] == '+')
+            setudef(ul, chan->dname, 1);
+          else
+            setudef(ul, chan->dname, 0);
+          found = 1;
+	  break;
+        } else if (ul->type == UDEF_INT &&
+		    /* Direct match when set during .chanset ... */
+		   (!strcasecmp(item[i], ul->name) ||
+		    /* ... or with prefix when set during chanfile load. */
+		    (!strncmp(item[i], "udef-int-", 9) &&
+		     !strcasecmp(item[i] + 9, ul->name)))) {
+          i++;
+          if (i >= items) {
+            if (irp)
+              Tcl_AppendResult(irp, "this setting needs an argument", NULL);
+            return TCL_ERROR;
+          }
+          setudef(ul, chan->dname, atoi(item[i]));
+          found = 1;
+	  break;
+        }
+	else if (ul->type == UDEF_STR && (!strcasecmp(item[i], ul->name) || (!strncmp(item[i], "udef-str-", 9) && !strcasecmp(item[i] + 9, ul->name)))) {
+		char *val;
+		i++;
+		if (i >= items) {
+			if (irp) Tcl_AppendResult(irp, "this setting needs an aargument", NULL);
+			return TCL_ERROR;
+		}
+		val = (char *)getudef(ul->values, chan->dname);
+		if (val) free(val);
+		/* Get extra room for new braces, etc */
+		val = malloc(3 * strlen(item[i]) + 10);
+		convert_element(item[i], val);
+		val = realloc(val, strlen(val)+1);
+		setudef(ul, chan->dname, (int)val);
+		found = 1;
+		break;
+	}
+      }
+      if (!found) {
+        if (irp && item[i][0]) /* ignore "" */
+      	  Tcl_AppendResult(irp, "illegal channel option: ", item[i], NULL);
+      	x++;
+      }
+    }
+  }
+  /* If protect_readonly == 0 and chan_hack == 0 then
+   * bot is now processing the configfile, so dont do anything,
+   * we've to wait the channelfile that maybe override these settings
+   * (note: it may cause problems if there is no chanfile!)
+   * <drummer/1999/10/21>
+   */
+  if (protect_readonly || chan_hack) {
+    if (((old_status ^ chan->status) & CHAN_INACTIVE) &&
+	module_find("irc", 0, 0)) {
+      if (channel_inactive(chan) &&
+	  (chan->status & (CHAN_ACTIVE | CHAN_PEND)))
+	dprintf(DP_SERVER, "PART %s\n", chan->name);
+      if (!channel_inactive(chan) &&
+	  !(chan->status & (CHAN_ACTIVE | CHAN_PEND)))
+	dprintf(DP_SERVER, "JOIN %s %s\n", (chan->name[0]) ?
+					   chan->name : chan->dname,
+					   chan->channel.key[0] ?
+					   chan->channel.key : chan->key_prot);
+    }
+    if ((old_status ^ chan->status) &
+	(CHAN_ENFORCEBANS | CHAN_OPONJOIN | CHAN_BITCH | CHAN_AUTOVOICE)) {
+      if ((me = module_find("irc", 0, 0)))
+	(me->funcs[IRC_RECHECK_CHANNEL])(chan, 1);
+    } else if (old_mode_pls_prot != chan->mode_pls_prot ||
+	       old_mode_mns_prot != chan->mode_mns_prot)
+      if ((me = module_find("irc", 1, 2)))
+	(me->funcs[IRC_RECHECK_CHANNEL_MODES])(chan);
+  }
+  if (x > 0)
+    return TCL_ERROR;
+  return TCL_OK;
+}
+
+static int tcl_do_masklist(maskrec *m, Tcl_Interp *irp)
+{
+  char ts[21], ts1[21], ts2[21], *list[6], *p;
+
+  for (; m; m = m->next) {
+    list[0] = m->mask;
+    list[1] = m->desc;
+    sprintf(ts, "%lu", m->expire);
+    list[2] = ts;
+    sprintf(ts1, "%lu", m->added);
+    list[3] = ts1;
+    sprintf(ts2, "%lu", m->lastactive);
+    list[4] = ts2;
+    list[5] = m->user;
+    p = Tcl_Merge(6, list);
+    Tcl_AppendElement(irp, p);
+    Tcl_Free((char *) p);
+  }
+  return TCL_OK;
+}
+
+static int tcl_banlist STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(1, 2, " ?channel?");
+  if (argc == 2) {
+    chan = findchan_by_dname(argv[1]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+      return TCL_ERROR;
+    }
+    return tcl_do_masklist(chan->bans, irp);
+  }
+
+  return tcl_do_masklist(global_bans, irp);
+}
+
+static int tcl_exemptlist STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(1, 2, " ?channel?");
+  if (argc == 2) {
+    chan = findchan_by_dname(argv[1]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+      return TCL_ERROR;
+    }
+    return tcl_do_masklist(chan->exempts, irp);
+  }
+
+  return tcl_do_masklist(global_exempts, irp);
+}
+
+static int tcl_invitelist STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(1, 2, " ?channel?");
+  if (argc == 2) {
+    chan = findchan_by_dname(argv[1]);
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+      return TCL_ERROR;
+    }
+    return tcl_do_masklist(chan->invites, irp);
+  }
+  return tcl_do_masklist(global_invites, irp);
+}
+
+static int tcl_channels STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(1, 1, "");
+  for (chan = chanset; chan; chan = chan->next) 
+    Tcl_AppendElement(irp, chan->dname);
+  return TCL_OK;
+}
+
+static int tcl_savechannels STDVAR
+{
+  BADARGS(1, 1, "");
+  if (!chanfile[0]) {
+    Tcl_AppendResult(irp, "no channel file");
+    return TCL_ERROR;
+  }
+  write_channels();
+  return TCL_OK;
+}
+
+static int tcl_loadchannels STDVAR
+{
+  BADARGS(1, 1, "");
+  if (!chanfile[0]) {
+    Tcl_AppendResult(irp, "no channel file");
+    return TCL_ERROR;
+  }
+  setstatic = 0;
+  read_channels(1);
+  return TCL_OK;
+}
+
+static int tcl_validchan STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL)
+    Tcl_AppendResult(irp, "0", NULL);
+  else
+    Tcl_AppendResult(irp, "1", NULL);
+  return TCL_OK;
+}
+
+static int tcl_isdynamic STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan != NULL)
+    if (!channel_static(chan)) {
+      Tcl_AppendResult(irp, "1", NULL);
+      return TCL_OK;
+    }
+  Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_getchaninfo STDVAR
+{
+  char s[161];
+  struct userrec *u;
+
+  BADARGS(3, 3, " handle channel");
+  u = get_user_by_handle(userlist, argv[1]);
+  if (!u || (u->flags & USER_BOT))
+    return TCL_OK;
+  get_handle_chaninfo(argv[1], argv[2], s);
+  Tcl_AppendResult(irp, s, NULL);
+  return TCL_OK;
+}
+
+static int tcl_setchaninfo STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(4, 4, " handle channel info");
+  chan = findchan_by_dname(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  if (!strcasecmp(argv[3], "none")) {
+    set_handle_chaninfo(userlist, argv[1], argv[2], NULL);
+    return TCL_OK;
+  }
+  set_handle_chaninfo(userlist, argv[1], argv[2], argv[3]);
+  return TCL_OK;
+}
+
+static int tcl_setlaston STDVAR
+{
+  time_t t = now;
+  struct userrec *u;
+
+  BADARGS(2, 4, " handle ?channel? ?timestamp?");
+  u = get_user_by_handle(userlist, argv[1]);
+  if (!u) {
+    Tcl_AppendResult(irp, "No such user: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (argc == 4)
+    t = (time_t) atoi(argv[3]);
+  if (argc == 3 && ((argv[2][0] != '#') && (argv[2][0] != '&')))
+    t = (time_t) atoi(argv[2]);
+  if (argc == 2 || (argc == 3 && ((argv[2][0] != '#') && (argv[2][0] != '&'))))
+    set_handle_laston("*", u, t);
+  else
+    set_handle_laston(argv[2], u, t);
+  return TCL_OK;
+}
+
+static int tcl_addchanrec STDVAR
+{
+  struct userrec *u;
+
+  BADARGS(3, 3, " handle channel");
+  u = get_user_by_handle(userlist, argv[1]);
+  if (!u) {
+    Tcl_AppendResult(irp, "0", NULL);
+    return TCL_OK;
+  }
+  if (!findchan_by_dname(argv[2])) {
+    Tcl_AppendResult(irp, "0", NULL);
+    return TCL_OK;
+  }
+  if (get_chanrec(u, argv[2]) != NULL) {
+    Tcl_AppendResult(irp, "0", NULL);
+    return TCL_OK;
+  }
+  add_chanrec(u, argv[2]);
+  Tcl_AppendResult(irp, "1", NULL);
+  return TCL_OK;
+}
+
+static int tcl_delchanrec STDVAR
+{
+  struct userrec *u;
+
+  BADARGS(3, 3, " handle channel");
+  u = get_user_by_handle(userlist, argv[1]);
+  if (!u) {
+    Tcl_AppendResult(irp, "0", NULL);
+    return TCL_OK;
+  }
+  if (get_chanrec(u, argv[2]) == NULL) {
+    Tcl_AppendResult(irp, "0", NULL);
+    return TCL_OK;
+  }
+  del_chanrec(u, argv[2]);
+  Tcl_AppendResult(irp, "1", NULL);
+  return TCL_OK;
+}
+
+static int tcl_haschanrec STDVAR
+{
+  struct userrec *u;
+  struct chanset_t *chan;
+  struct chanuserrec *chanrec;
+
+  BADARGS(3, 3, " handle channel");
+
+  chan = findchan_by_dname(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  u = get_user_by_handle(userlist, argv[1]);
+  if (!u) {
+    Tcl_AppendResult(irp, "No such user: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  chanrec = get_chanrec(u, chan->dname);
+  if (chanrec) {
+    Tcl_AppendResult(irp, "1", NULL);
+    return TCL_OK;
+  }
+  Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static void init_masklist(masklist *m)
+{
+  m->mask = calloc(1, 1);
+  m->who = NULL;
+  m->next = NULL;
+}
+
+/* Initialize out the channel record.
+ */
+static void init_channel(struct chanset_t *chan, int reset)
+{
+  chan->channel.maxmembers = 0;
+  chan->channel.mode = 0;
+  chan->channel.members = 0;
+  if (!reset)
+    chan->channel.key = calloc(1, 1);
+
+  chan->channel.ban = (masklist *) malloc(sizeof(masklist));
+  init_masklist(chan->channel.ban);
+
+  chan->channel.exempt = (masklist *) malloc(sizeof(masklist));
+  init_masklist(chan->channel.exempt);
+
+  chan->channel.invite = (masklist *) malloc(sizeof(masklist));
+  init_masklist(chan->channel.invite);
+
+  chan->channel.member = (memberlist *) malloc(sizeof(memberlist));
+  chan->channel.member->nick[0] = 0;
+  chan->channel.member->next = NULL;
+  chan->channel.topic = NULL;
+}
+
+static void clear_masklist(masklist *m)
+{
+  masklist *temp;
+
+  for (; m; m = temp) {
+    temp = m->next;
+    if (m->mask)
+      free(m->mask);
+    if (m->who)
+      free(m->who);
+    free(m);
+  }
+}
+
+/* Clear out channel data from memory.
+ */
+static void clear_channel(struct chanset_t *chan, int reset)
+{
+  memberlist *m, *m1;
+
+  if (chan->channel.topic)
+    free(chan->channel.topic);
+  for (m = chan->channel.member; m; m = m1) {
+    m1 = m->next;
+    free(m);
+  }
+
+  clear_masklist(chan->channel.ban);
+  chan->channel.ban = NULL;
+  clear_masklist(chan->channel.exempt);
+  chan->channel.exempt = NULL;
+  clear_masklist(chan->channel.invite);
+  chan->channel.invite = NULL;
+
+  if (reset)
+    init_channel(chan, 1);
+}
+
+/* Create new channel and parse commands.
+ */
+static int tcl_channel_add(Tcl_Interp *irp, char *newname, char *options)
+{
+  struct chanset_t *chan;
+  int items;
+  int ret = TCL_OK;
+  int join = 0;
+  char **item;
+  char buf[2048], buf2[256];
+
+  if (!newname || !newname[0] || !strchr(CHANMETA, newname[0]))
+    return TCL_ERROR;
+
+  convert_element(glob_chanmode, buf2);
+  simple_sprintf(buf, "chanmode %s ", buf2);
+  strncat(buf, glob_chanset, 2047 - strlen(buf));
+  strncat(buf, options, 2047 - strlen(buf));
+  buf[2047] = 0;
+#if (TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 1) || (TCL_MAJOR_VERSION >= 9)
+  str_nutf8tounicode(newname, strlen(newname) + 1);
+  str_nutf8tounicode(buf2, sizeof buf2);
+  str_nutf8tounicode(buf, sizeof buf);
+#endif
+
+  if (Tcl_SplitList(NULL, buf, &items, &item) != TCL_OK)
+    return TCL_ERROR;
+  if ((chan = findchan_by_dname(newname))) {
+    /* Already existing channel, maybe a reload of the channel file */
+    chan->status &= ~CHAN_FLAGGED;	/* don't delete me! :) */
+  } else {
+    chan = calloc(1, sizeof(struct chanset_t));
+    chan->limit_prot = 0;
+    chan->limit = 0;
+    chan->flood_pub_thr = gfld_chan_thr;
+    chan->flood_pub_time = gfld_chan_time;
+    chan->flood_ctcp_thr = gfld_ctcp_thr;
+    chan->flood_ctcp_time = gfld_ctcp_time;
+    chan->flood_join_thr = gfld_join_thr;
+    chan->flood_join_time = gfld_join_time;
+    chan->flood_deop_thr = gfld_deop_thr;
+    chan->flood_deop_time = gfld_deop_time;
+    chan->flood_kick_thr = gfld_kick_thr;
+    chan->flood_kick_time = gfld_kick_time;
+    chan->flood_nick_thr = gfld_nick_thr;
+    chan->flood_nick_time = gfld_nick_time;
+    chan->stopnethack_mode = global_stopnethack_mode;
+    chan->revenge_mode = global_revenge_mode;
+    chan->idle_kick = global_idle_kick;
+    chan->aop_min = global_aop_min;
+    chan->aop_max = global_aop_max;
+
+    /* We _only_ put the dname (display name) in here so as not to confuse
+     * any code later on. chan->name gets updated with the channel name as
+     * the server knows it, when we join the channel. <cybah>
+     */
+    strncpy(chan->dname, newname, 81);
+    chan->dname[80] = 0;
+
+    /* Initialize chan->channel info */
+    init_channel(chan, 0);
+    list_append((struct list_type **) &chanset, (struct list_type *) chan);
+    /* Channel name is stored in xtra field for sharebot stuff */
+    join = 1;
+  }
+  if (setstatic)
+    chan->status |= CHAN_STATIC;
+  /* If chan_hack is set, we're loading the userfile. Ignore errors while
+   * reading userfile and just return TCL_OK. This is for compatability
+   * if a user goes back to an eggdrop that no-longer supports certain
+   * (channel) options.
+   */
+  if ((tcl_channel_modify(irp, chan, items, item) != TCL_OK) && !chan_hack) {
+    ret = TCL_ERROR;
+  }
+  Tcl_Free((char *) item);
+  if (join && !channel_inactive(chan) && module_find("irc", 0, 0))
+    dprintf(DP_SERVER, "JOIN %s %s\n", chan->dname, chan->key_prot);
+  return ret;
+}
+
+static int tcl_setudef STDVAR
+{
+  int type;
+
+  BADARGS(3, 3, " type name");
+  if (!strcasecmp(argv[1], "flag"))
+    type = UDEF_FLAG;
+  else if (!strcasecmp(argv[1], "int"))
+    type = UDEF_INT;
+	/*## ADD CODE FOR STRING SETTINGS*/
+	else if (!strcasecmp(argv[1], "str"))
+		type = UDEF_STR;
+  else {
+    Tcl_AppendResult(irp, "invalid type. Must be one of: flag, int, str", NULL);
+    return TCL_ERROR;
+  }
+  initudef(type, argv[2], 1);
+  return TCL_OK;
+}
+
+static int tcl_renudef STDVAR
+{
+  struct udef_struct *ul;
+  int type, found = 0;
+
+  BADARGS(4, 4, " type oldname newname");
+  if (!strcasecmp(argv[1], "flag"))
+    type = UDEF_FLAG;
+  else if (!strcasecmp(argv[1], "int"))
+    type = UDEF_INT;
+	/*## ADD CODE FOR STRING SETTINGS*/
+	else if (!strcasecmp(argv[1], "str"))
+		type = UDEF_STR;
+  else {
+    Tcl_AppendResult(irp, "invalid type. Must be one of: flag, int, str", NULL);
+    return TCL_ERROR;
+  }
+  for (ul = udef; ul; ul = ul->next) {
+    if (ul->type == type && !strcasecmp(ul->name, argv[2])) {
+      free(ul->name);
+      malloc_strcpy(ul->name, argv[3]);
+      found = 1;
+    }
+  }
+  if (!found) {
+    Tcl_AppendResult(irp, "not found", NULL);
+    return TCL_ERROR;
+  } else
+    return TCL_OK;
+}
+
+static int tcl_deludef STDVAR
+{
+  struct udef_struct *ul, *ull;
+  int type, found = 0;
+
+  BADARGS(3, 3, " type name");
+  if (!strcasecmp(argv[1], "flag"))
+    type = UDEF_FLAG;
+  else if (!strcasecmp(argv[1], "int"))
+    type = UDEF_INT;
+	/*## ADD CODE FOR STRING SETTINGS*/
+	else if (!strcasecmp(argv[1], "str"))
+		type = UDEF_STR;
+  else {
+    Tcl_AppendResult(irp, "invalid type. Must be one of: flag, int, str", NULL);
+    return TCL_ERROR;
+  }
+  for (ul = udef; ul; ul = ul->next) {
+    ull = ul->next;
+    if (!ull)
+      break;
+    if (ull->type == type && !strcasecmp(ull->name, argv[2])) {
+      ul->next = ull->next;
+      free(ull->name);
+      free_udef_chans(ull->values, ull->type);
+      free(ull);
+      found = 1;
+    }
+  }
+  if (udef) {
+    if (udef->type == type && !strcasecmp(udef->name, argv[2])) {
+      ul = udef->next;
+      free(udef->name);
+      free_udef_chans(udef->values, udef->type);
+      free(udef);
+      udef = ul;
+      found = 1;
+    }
+  }
+  if (!found) {
+    Tcl_AppendResult(irp, "not found", NULL);
+    return TCL_ERROR;
+  } else
+    return TCL_OK;
+}
+
+static tcl_cmds channels_cmds[] =
+{
+  {"killban",		tcl_killban},
+  {"killchanban",	tcl_killchanban},
+  {"isbansticky",	tcl_isbansticky},
+  {"isban",		tcl_isban},
+  {"ispermban",		tcl_ispermban},
+  {"matchban",		tcl_matchban},
+  {"newchanban",	tcl_newchanban},
+  {"newban",		tcl_newban},
+  {"killexempt",	tcl_killexempt},
+  {"killchanexempt",	tcl_killchanexempt},
+  {"isexemptsticky",	tcl_isexemptsticky},
+  {"isexempt",		tcl_isexempt},
+  {"ispermexempt",	tcl_ispermexempt},
+  {"matchexempt",	tcl_matchexempt},
+  {"newchanexempt",	tcl_newchanexempt},
+  {"newexempt",		tcl_newexempt},
+  {"killinvite",	tcl_killinvite},
+  {"killchaninvite",	tcl_killchaninvite},
+  {"isinvitesticky",	tcl_isinvitesticky},
+  {"isinvite",		tcl_isinvite},
+  {"isperminvite",	tcl_isperminvite},
+  {"matchinvite",	tcl_matchinvite},
+  {"newchaninvite",	tcl_newchaninvite},
+  {"newinvite",		tcl_newinvite},
+  {"channel",		tcl_channel},
+  {"channels",		tcl_channels},
+  {"exemptlist",	tcl_exemptlist},
+  {"invitelist",	tcl_invitelist},
+  {"banlist",		tcl_banlist},
+  {"savechannels",	tcl_savechannels},
+  {"loadchannels",	tcl_loadchannels},
+  {"validchan",		tcl_validchan},
+  {"isdynamic",		tcl_isdynamic},
+  {"getchaninfo",	tcl_getchaninfo},
+  {"setchaninfo",	tcl_setchaninfo},
+  {"setlaston",		tcl_setlaston},
+  {"addchanrec",	tcl_addchanrec},
+  {"delchanrec",	tcl_delchanrec},
+  {"stick",		tcl_stick},
+  {"unstick",		tcl_stick},
+  {"stickinvite",	tcl_stickinvite},
+  {"unstickinvite",	tcl_stickinvite},
+  {"stickexempt",	tcl_stickexempt},
+  {"unstickexempt",	tcl_stickexempt},
+  {"setudef",		tcl_setudef},
+  {"renudef",		tcl_renudef},
+  {"deludef",		tcl_deludef},
+  {"haschanrec",	tcl_haschanrec},
+  {NULL,		NULL}
+};
Index: eggdrop1.7/modules/channels/udefchan.c
diff -u /dev/null eggdrop1.7/modules/channels/udefchan.c:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/udefchan.c	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,127 @@
+/*
+ * udefchan.c -- part of channels.mod
+ *   user definable channel flags/settings
+ *
+ * $Id: udefchan.c,v 1.1 2001/10/27 16:34:48 ite Exp $
+ */
+/*
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static int getudef(struct udef_chans *ul, char *name)
+{
+  int val = 0;
+
+  for (; ul; ul = ul->next)
+    if (!strcasecmp(ul->chan, name)) {
+      val = ul->value;
+      break;
+    }
+  return val;
+}
+
+static int ngetudef(char *name, char *chan)
+{
+  struct udef_struct *l;
+  struct udef_chans *ll;
+
+  for (l = udef; l; l = l->next)
+    if (!strcasecmp(l->name, name)) {
+      for (ll = l->values; ll; ll = ll->next)
+        if (!strcasecmp(ll->chan, chan))
+          return ll->value;
+      break;
+    }
+  return 0;
+}
+
+static void setudef(struct udef_struct *us, char *name, int value)
+{
+  struct udef_chans *ul, *ul_last = NULL;
+
+  for (ul = us->values; ul; ul_last = ul, ul = ul->next)
+    if (!strcasecmp(ul->chan, name)) {
+      ul->value = value;
+      return;
+    }
+
+  ul = malloc(sizeof(struct udef_chans));
+  malloc_strcpy(ul->chan, name);
+  ul->value = value;
+  ul->next = NULL;
+  if (ul_last)
+    ul_last->next = ul;
+  else
+    us->values = ul;
+}
+
+static void initudef(int type, char *name, int defined)
+{
+  struct udef_struct *ul, *ul_last = NULL;
+
+  if (strlen(name) < 1)
+    return;
+  for (ul = udef; ul; ul_last = ul, ul = ul->next)
+    if (ul->name && !strcasecmp(ul->name, name)) {
+      if (defined) {
+        debug1("UDEF: %s defined", ul->name);
+        ul->defined = 1;
+      }
+      return;
+    }
+
+  debug2("Creating %s (type %d)", name, type);
+  ul = malloc(sizeof(struct udef_struct));
+  malloc_strcpy(ul->name, name);
+  if (defined)
+    ul->defined = 1;
+  else
+    ul->defined = 0;
+  ul->type = type;
+  ul->values = NULL;
+  ul->next = NULL;
+  if (ul_last)
+    ul_last->next = ul;
+  else
+    udef = ul;
+}
+
+static void free_udef(struct udef_struct *ul)
+{
+  struct udef_struct *ull;
+
+  for (; ul; ul = ull) {
+    ull = ul->next;
+    free_udef_chans(ul->values, ul->type);
+    free(ul->name);
+    free(ul);
+  }
+}
+
+static void free_udef_chans(struct udef_chans *ul, int type)
+{
+  struct udef_chans *ull;
+
+  for (; ul; ul = ull) {
+    ull = ul->next;
+	if (type == UDEF_STR && ul->value) {
+		free((void *)ul->value);
+	}
+    free(ul->chan);
+    free(ul);
+  }
+}
Index: eggdrop1.7/modules/channels/userchan.c
diff -u /dev/null eggdrop1.7/modules/channels/userchan.c:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/channels/userchan.c	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,1434 @@
+/*
+ * userchan.c -- part of channels.mod
+ *
+ * $Id: userchan.c,v 1.1 2001/10/27 16:34:48 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct chanuserrec *get_chanrec(struct userrec *u, char *chname)
+{
+  struct chanuserrec *ch;
+
+  for (ch = u->chanrec; ch; ch = ch->next) 
+    if (!irccmp(ch->channel, chname))
+      return ch;
+  return NULL;
+}
+
+static struct chanuserrec *add_chanrec(struct userrec *u, char *chname)
+{
+  struct chanuserrec *ch = NULL;
+
+  if (findchan_by_dname(chname)) {
+    ch = malloc(sizeof(struct chanuserrec));
+
+    ch->next = u->chanrec;
+    u->chanrec = ch;
+    ch->info = NULL;
+    ch->flags = 0;
+    ch->flags_udef = 0;
+    ch->laston = 0;
+    strncpy(ch->channel, chname, 81);
+    ch->channel[80] = 0;
+    if (!noshare && !(u->flags & USER_UNSHARED))
+      shareout(findchan_by_dname(chname), "+cr %s %s\n", u->handle, chname);
+  }
+  return ch;
+}
+
+static void add_chanrec_by_handle(struct userrec *bu, char *hand, char *chname)
+{
+  struct userrec *u;
+
+  u = get_user_by_handle(bu, hand);
+  if (!u)
+    return;
+  if (!get_chanrec(u, chname))
+    add_chanrec(u, chname);
+}
+
+static void get_handle_chaninfo(char *handle, char *chname, char *s)
+{
+  struct userrec *u;
+  struct chanuserrec *ch;
+
+  u = get_user_by_handle(userlist, handle);
+  if (u == NULL) {
+    s[0] = 0;
+    return;
+  }
+  ch = get_chanrec(u, chname);
+  if (ch == NULL) {
+    s[0] = 0;
+    return;
+  }
+  if (ch->info == NULL) {
+    s[0] = 0;
+    return;
+  }
+  strcpy(s, ch->info);
+  return;
+}
+
+static void set_handle_chaninfo(struct userrec *bu, char *handle,
+				char *chname, char *info)
+{
+  struct userrec *u;
+  struct chanuserrec *ch;
+  struct chanset_t *cst;
+
+  u = get_user_by_handle(bu, handle);
+  if (!u)
+    return;
+  ch = get_chanrec(u, chname);
+  if (!ch) {
+    add_chanrec_by_handle(bu, handle, chname);
+    ch = get_chanrec(u, chname);
+  }
+  if (info)
+    if (strlen(info) > 80)
+      info[80] = 0;
+  if (ch->info != NULL)
+    free(ch->info);
+  if (info && info[0])
+    malloc_strcpy(ch->info, info);
+  else
+    ch->info = NULL;
+  cst = findchan_by_dname(chname);
+  if ((!noshare) && (bu == userlist) &&
+      !(u->flags & (USER_UNSHARED | USER_BOT)) && share_greet) {
+    shareout(cst, "chchinfo %s %s %s\n", handle, chname, info ? info : "");
+  }
+}
+
+static void del_chanrec(struct userrec *u, char *chname)
+{
+  struct chanuserrec *ch = u->chanrec, *lst = NULL;
+
+  while (ch) {
+    if (!irccmp(chname, ch->channel)) {
+      if (lst == NULL)
+	u->chanrec = ch->next;
+      else
+	lst->next = ch->next;
+      if (ch->info != NULL)
+	free(ch->info);
+      free(ch);
+      if (!noshare && !(u->flags & USER_UNSHARED))
+	shareout(findchan_by_dname(chname), "-cr %s %s\n", u->handle, chname);
+      return;
+    }
+    lst = ch;
+    ch = ch->next;
+  }
+}
+
+static void set_handle_laston(char *chan, struct userrec *u, time_t n)
+{
+  struct chanuserrec *ch;
+
+  if (!u)
+    return;
+  touch_laston(u, chan, n);
+  ch = get_chanrec(u, chan);
+  if (!ch)
+    return;
+  ch->laston = n;
+}
+
+/* Is this mask sticky?
+ */
+static int u_sticky_mask(maskrec *u, char *uhost)
+{
+  for (; u; u = u->next)
+    if (!irccmp(u->mask, uhost))
+      return (u->flags & MASKREC_STICKY);
+  return 0;
+}
+
+/* Set sticky attribute for a mask.
+ */
+static int u_setsticky_mask(struct chanset_t *chan, maskrec *u, char *uhost,
+			    int sticky, char *botcmd)
+{
+  int j;
+
+  j = atoi(uhost);
+  if (!j)
+    j = (-1);
+  while(u) {
+    if (j >= 0)
+      j--;
+
+    if (!j || ((j < 0) && !irccmp(u->mask, uhost))) {
+      if (sticky > 0)
+	u->flags |= MASKREC_STICKY;
+      else if (!sticky)
+	u->flags &= ~MASKREC_STICKY;
+      else	/* We don't actually want to change, just skip over */
+	return 0;
+      if (!j)
+	strcpy(uhost, u->mask);
+
+      if (!noshare)
+        shareout(chan, "%s %s %d %s\n", botcmd, uhost, sticky,
+                                        (chan) ? chan->dname : "");
+      return 1;
+    }
+
+    u = u->next;
+  }
+  if (j >= 0)
+    return -j;
+
+  return 0;
+}
+
+/* Merge of u_equals_ban(), u_equals_exempt() and u_equals_invite().
+ *
+ * Returns:
+ *   0       not a ban
+ *   1       temporary ban
+ *   2       perm ban
+ */
+static int u_equals_mask(maskrec *u, char *mask)
+{
+  for (; u; u = u->next)
+    if (!irccmp(u->mask, mask)) {
+      if (u->flags & MASKREC_PERM)
+        return 2;
+      else
+        return 1;
+    }
+  return 0;
+}
+
+static int u_match_mask(maskrec *rec, char *mask)
+{
+  for (; rec; rec = rec->next)
+    if (wild_match(rec->mask, mask))
+      return 1;
+  return 0;
+}
+
+static int u_delban(struct chanset_t *c, char *who, int doit)
+{
+  int j, i = 0;
+  maskrec *t;
+  maskrec **u = (c) ? &c->bans : &global_bans;
+
+  if (!strchr(who, '!') && (j = atoi(who))) {
+    j--;
+    for (; (*u) && j; u = &((*u)->next), j--);
+    if (*u) {
+      strcpy(who, (*u)->mask);
+      i = 1;
+    } else
+      return -j - 1;
+  } else {
+    /* Find matching host, if there is one */
+    for (; *u && !i; u = &((*u)->next))
+      if (!irccmp((*u)->mask, who)) {
+	i = 1;
+	break;
+      }
+    if (!*u)
+      return 0;
+  }
+  if (i && doit) {
+    if (!noshare) {
+      char *mask = str_escape(who, ':', '\\');
+
+      if (mask) {
+	/* Distribute chan bans differently */
+	if (c)
+	  shareout(c, "-bc %s %s\n", c->dname, mask);
+	else
+	  shareout(NULL, "-b %s\n", mask);
+	free(mask);
+      }
+    }
+    if (!c)
+      gban_total--;
+    free((*u)->mask);
+    if ((*u)->desc)
+      free((*u)->desc);
+    if ((*u)->user)
+      free((*u)->user);
+    t = *u;
+    *u = (*u)->next;
+    free(t);
+  }
+  return i;
+}
+
+static int u_delexempt (struct chanset_t * c, char * who, int doit)
+{
+  int j, i = 0;
+  maskrec *t;
+  maskrec **u = c ? &(c->exempts) : &global_exempts;
+
+  if (!strchr(who,'!') && (j = atoi(who))) {
+    j--;
+    for (;(*u) && j;u=&((*u)->next),j--);
+    if (*u) {
+      strcpy(who, (*u)->mask);
+      i = 1;
+    } else
+      return -j-1;
+  } else {
+    /* Find matching host, if there is one */
+    for (;*u && !i;u=&((*u)->next))
+      if (!irccmp((*u)->mask,who)) {
+	i = 1;
+	break;
+      }
+    if (!*u)
+      return 0;
+  }
+  if (i && doit) {
+    if (!noshare) {
+      char *mask = str_escape(who, ':', '\\');
+
+      if (mask) {
+	/* Distribute chan exempts differently */
+	if (c)
+	  shareout(c, "-ec %s %s\n", c->dname, mask);
+	else
+	  shareout(NULL, "-e %s\n", mask);
+	free(mask);
+      }
+    }
+    if (!c)
+      gexempt_total--;
+    free((*u)->mask);
+    if ((*u)->desc)
+      free((*u)->desc);
+    if ((*u)->user)
+      free((*u)->user);
+    t = *u;
+    *u = (*u)->next;
+    free(t);
+  }
+  return i;
+}
+
+static int u_delinvite(struct chanset_t *c, char *who, int doit)
+{
+  int j, i = 0;
+  maskrec *t;
+  maskrec **u = c ? &(c->invites) : &global_invites;
+
+  if (!strchr(who,'!') && (j = atoi(who))) {
+    j--;
+    for (;(*u) && j;u=&((*u)->next),j--);
+    if (*u) {
+      strcpy(who, (*u)->mask);
+      i = 1;
+    } else
+      return -j-1;
+  } else {
+    /* Find matching host, if there is one */
+    for (;*u && !i; u = &((*u)->next))
+      if (!irccmp((*u)->mask,who)) {
+	i = 1;
+	break;
+      }
+    if (!*u)
+      return 0;
+  }
+  if (i && doit) {
+    if (!noshare) {
+      char *mask = str_escape(who, ':', '\\');
+
+      if (mask) {
+	/* Distribute chan invites differently */
+	if (c)
+	  shareout(c, "-invc %s %s\n", c->dname, mask);
+	else
+	  shareout(NULL, "-inv %s\n", mask);
+	free(mask);
+      }
+    }
+    if (!c)
+      ginvite_total--;
+    free((*u)->mask);
+    if ((*u)->desc)
+      free((*u)->desc);
+    if ((*u)->user)
+      free((*u)->user);
+    t = *u;
+    *u = (*u)->next;
+    free(t);
+  }
+  return i;
+}
+
+/* Note: If first char of note is '*' it's a sticky ban.
+ */
+static int u_addban(struct chanset_t *chan, char *ban, char *from, char *note,
+		    time_t expire_time, int flags)
+{
+  char host[1024], s[1024];
+  maskrec *p, **u = chan ? &chan->bans : &global_bans;
+  module_entry *me;
+
+  strcpy(host, ban);
+  /* Choke check: fix broken bans (must have '!' and '@') */
+  if ((strchr(host, '!') == NULL) && (strchr(host, '@') == NULL))
+    strcat(host, "!*@*");
+  else if (strchr(host, '@') == NULL)
+    strcat(host, "@*");
+  else if (strchr(host, '!') == NULL) {
+    char *i = strchr(host, '@');
+
+    strcpy(s, i);
+    *i = 0;
+    strcat(host, "!*");
+    strcat(host, s);
+  }
+  if ((me = module_find("server", 0, 0)) && me->funcs)
+    simple_sprintf(s, "%s!%s", me->funcs[SERVER_BOTNAME],
+		   me->funcs[SERVER_BOTUSERHOST]);
+  else
+    s[0] = 0;
+  if (s[0] && wild_match(host, s)) {
+    putlog(LOG_MISC, "*", _("Wanted to ban myself--deflected."));
+    return 0;
+  }
+  if (expire_time == now)
+    return 1;
+  if (u_equals_mask(*u, host))
+    u_delban(chan, host, 1);	/* Remove old ban */
+  /* It shouldn't expire and be sticky also */
+  if (note[0] == '*') {
+    flags |= MASKREC_STICKY;
+    note++;
+  }
+  if ((expire_time == 0L) || (flags & MASKREC_PERM)) {
+    flags |= MASKREC_PERM;
+    expire_time = 0L;
+  }
+
+  p = malloc(sizeof(maskrec));
+  p->next = *u;
+  *u = p;
+  p->expire = expire_time;
+  p->added = now;
+  p->lastactive = 0;
+  p->flags = flags;
+  malloc_strcpy(p->mask, host);
+  malloc_strcpy(p->user, from);
+  malloc_strcpy(p->desc, note);
+  if (!noshare) {
+    char *mask = str_escape(host, ':', '\\');
+
+    if (mask) {
+      if (!chan)
+	shareout(NULL, "+b %s %lu %s%s %s %s\n", mask, expire_time - now,
+		 (flags & MASKREC_STICKY) ? "s" : "",
+		 (flags & MASKREC_PERM) ? "p" : "-", from, note);
+      else
+	shareout(chan, "+bc %s %lu %s %s%s %s %s\n", mask, expire_time - now,
+		 chan->dname, (flags & MASKREC_STICKY) ? "s" : "",
+		 (flags & MASKREC_PERM) ? "p" : "-", from, note);
+      free(mask);
+    }
+  }
+  return 1;
+}
+
+/* Note: If first char of note is '*' it's a sticky invite.
+ */
+static int u_addinvite(struct chanset_t *chan, char *invite, char *from,
+		       char *note, time_t expire_time, int flags)
+{
+  char host[1024], s[1024];
+  maskrec *p, **u = chan ? &chan->invites : &global_invites;
+
+  strcpy(host, invite);
+  /* Choke check: fix broken invites (must have '!' and '@') */
+  if ((strchr(host, '!') == NULL) && (strchr(host, '@') == NULL))
+    strcat(host, "!*@*");
+  else if (strchr(host, '@') == NULL)
+    strcat(host, "@*");
+  else if (strchr(host, '!') == NULL) {
+    char * i = strchr(host, '@');
+    strcpy(s, i);
+    *i = 0;
+    strcat(host, "!*");
+    strcat(host, s);
+  }
+
+  if (u_equals_mask(*u, host))
+    u_delinvite(chan, host,1);	/* Remove old invite */
+  /* It shouldn't expire and be sticky also */
+  if (note[0] == '*') {
+    flags |= MASKREC_STICKY;
+    note++;
+  }
+  if ((expire_time == 0L) || (flags & MASKREC_PERM)) {
+    flags |= MASKREC_PERM;
+    expire_time = 0L;
+  }
+
+  p = malloc(sizeof(maskrec));
+  p->next = *u;
+  *u = p;
+  p->expire = expire_time;
+  p->added = now;
+  p->lastactive = 0;
+  p->flags = flags;
+  malloc_strcpy(p->mask, host);
+  malloc_strcpy(p->user, from);
+  malloc_strcpy(p->desc, note);
+  if (!noshare) {
+    char *mask = str_escape(host, ':', '\\');
+
+    if (mask) {
+      if (!chan)
+	shareout(NULL, "+inv %s %lu %s%s %s %s\n", mask, expire_time - now,
+		 (flags & MASKREC_STICKY) ? "s" : "",
+		 (flags & MASKREC_PERM) ? "p": "-", from, note);
+      else
+	shareout(chan, "+invc %s %lu %s %s%s %s %s\n", mask, expire_time - now,
+		 chan->dname, (flags & MASKREC_STICKY) ? "s" : "",
+		 (flags & MASKREC_PERM) ? "p": "-", from, note);
+      free(mask);
+    }
+  }
+  return 1;
+}
+
+/* Note: If first char of note is '*' it's a sticky exempt.
+ */
+static int u_addexempt(struct chanset_t *chan, char *exempt, char *from,
+		       char *note, time_t expire_time, int flags)
+{
+  char host[1024], s[1024];
+  maskrec *p, **u = chan ? &chan->exempts : &global_exempts;
+
+  strcpy(host, exempt);
+  /* Choke check: fix broken exempts (must have '!' and '@') */
+  if ((strchr(host, '!') == NULL) && (strchr(host, '@') == NULL))
+    strcat(host, "!*@*");
+  else if (strchr(host, '@') == NULL)
+    strcat(host, "@*");
+  else if (strchr(host, '!') == NULL) {
+    char * i = strchr(host, '@');
+    strcpy(s, i);
+    *i = 0;
+    strcat(host, "!*");
+    strcat(host, s);
+  }
+
+  if (u_equals_mask(*u, host))
+    u_delexempt(chan, host,1);	/* Remove old exempt */
+  /* It shouldn't expire and be sticky also */
+  if (note[0] == '*') {
+    flags |= MASKREC_STICKY;
+    note++;
+  }
+  if ((expire_time == 0L) || (flags & MASKREC_PERM)) {
+    flags |= MASKREC_PERM;
+    expire_time = 0L;
+  }
+
+  p = malloc(sizeof(maskrec));
+  p->next = *u;
+  *u = p;
+  p->expire = expire_time;
+  p->added = now;
+  p->lastactive = 0;
+  p->flags = flags;
+  malloc_strcpy(p->mask, host);
+  malloc_strcpy(p->user, from);
+  malloc_strcpy(p->desc, note);
+  if (!noshare) {
+    char *mask = str_escape(host, ':', '\\');
+
+    if (mask) {
+      if (!chan)
+	shareout(NULL, "+e %s %lu %s%s %s %s\n", mask, expire_time - now,
+		 (flags & MASKREC_STICKY) ? "s" : "",
+		 (flags & MASKREC_PERM) ? "p": "-", from, note);
+      else
+	shareout(chan, "+ec %s %lu %s %s%s %s %s\n", mask, expire_time - now,
+		 chan->dname, (flags & MASKREC_STICKY) ? "s" : "",
+		 (flags & MASKREC_PERM) ? "p": "-", from, note);
+      free(mask);
+    }
+  }
+  return 1;
+}
+
+/* Take host entry from ban list and display it ban-style.
+ */
+static void display_ban(int idx, int number, maskrec *ban,
+			struct chanset_t *chan, int show_inact)
+{
+  char dates[81], s[41];
+
+  if (ban->added) {
+    daysago(now, ban->added, s);
+    sprintf(dates, "%s %s", _("Created"), s);
+    if (ban->added < ban->lastactive) {
+      strcat(dates, ", ");
+      strcat(dates, _("last used"));
+      strcat(dates, " ");
+      daysago(now, ban->lastactive, s);
+      strcat(dates, s);
+    }
+  } else
+    dates[0] = 0;
+  if (ban->flags & MASKREC_PERM)
+    strcpy(s, "(perm)");
+  else {
+    char s1[41];
+
+    days(ban->expire, now, s1);
+    sprintf(s, "(expires %s)", s1);
+  }
+  if (ban->flags & MASKREC_STICKY)
+    strcat(s, " (sticky)");
+  if (!chan || ischanban(chan, ban->mask)) {
+    if (number >= 0) {
+      dprintf(idx, "  [%3d] %s %s\n", number, ban->mask, s);
+    } else {
+      dprintf(idx, "BAN: %s %s\n", ban->mask, s);
+    }
+  } else if (show_inact) {
+    if (number >= 0) {
+      dprintf(idx, "! [%3d] %s %s\n", number, ban->mask, s);
+    } else {
+      dprintf(idx, "BAN (%s): %s %s\n", _("inactive"), ban->mask, s);
+    }
+  } else
+    return;
+  dprintf(idx, "        %s: %s\n", ban->user, ban->desc);
+  if (dates[0])
+    dprintf(idx, "        %s\n", dates);
+}
+
+/* Take host entry from exempt list and display it ban-style.
+ */
+static void display_exempt(int idx, int number, maskrec *exempt,
+			   struct chanset_t *chan, int show_inact)
+{
+  char dates[81], s[41];
+
+  if (exempt->added) {
+    daysago(now, exempt->added, s);
+    sprintf(dates, "%s %s", _("Created"), s);
+    if (exempt->added < exempt->lastactive) {
+      strcat(dates, ", ");
+      strcat(dates, _("last used"));
+      strcat(dates, " ");
+      daysago(now, exempt->lastactive, s);
+      strcat(dates, s);
+    }
+  } else
+    dates[0] = 0;
+  if (exempt->flags & MASKREC_PERM)
+    strcpy(s, "(perm)");
+  else {
+    char s1[41];
+
+    days(exempt->expire, now, s1);
+    sprintf(s, "(expires %s)", s1);
+  }
+  if (exempt->flags & MASKREC_STICKY)
+    strcat(s, " (sticky)");
+  if (!chan || ischanexempt(chan, exempt->mask)) {
+    if (number >= 0) {
+      dprintf(idx, "  [%3d] %s %s\n", number, exempt->mask, s);
+    } else {
+      dprintf(idx, "EXEMPT: %s %s\n", exempt->mask, s);
+    }
+  } else if (show_inact) {
+    if (number >= 0) {
+      dprintf(idx, "! [%3d] %s %s\n", number, exempt->mask, s);
+    } else {
+      dprintf(idx, "EXEMPT (%s): %s %s\n", _("inactive"), exempt->mask, s);
+    }
+  } else
+    return;
+  dprintf(idx, "        %s: %s\n", exempt->user, exempt->desc);
+  if (dates[0])
+    dprintf(idx, "        %s\n", dates);
+}
+
+/* Take host entry from invite list and display it ban-style.
+ */
+static void display_invite (int idx, int number, maskrec *invite,
+			    struct chanset_t *chan, int show_inact)
+{
+  char dates[81], s[41];
+
+  if (invite->added) {
+    daysago(now, invite->added, s);
+    sprintf(dates, "%s %s", _("Created"), s);
+    if (invite->added < invite->lastactive) {
+      strcat(dates, ", ");
+      strcat(dates, _("last used"));
+      strcat(dates, " ");
+      daysago(now, invite->lastactive, s);
+      strcat(dates, s);
+    }
+  } else
+    dates[0] = 0;
+  if (invite->flags & MASKREC_PERM)
+    strcpy(s, "(perm)");
+  else {
+    char s1[41];
+
+    days(invite->expire, now, s1);
+    sprintf(s, "(expires %s)", s1);
+  }
+  if (invite->flags & MASKREC_STICKY)
+    strcat(s, " (sticky)");
+  if (!chan || ischaninvite(chan, invite->mask)) {
+    if (number >= 0) {
+      dprintf(idx, "  [%3d] %s %s\n", number, invite->mask, s);
+    } else {
+      dprintf(idx, "INVITE: %s %s\n", invite->mask, s);
+    }
+  } else if (show_inact) {
+    if (number >= 0) {
+      dprintf(idx, "! [%3d] %s %s\n", number, invite->mask, s);
+    } else {
+      dprintf(idx, "INVITE (%s): %s %s\n", _("inactive"), invite->mask, s);
+    }
+  } else
+    return;
+  dprintf(idx, "        %s: %s\n", invite->user, invite->desc);
+  if (dates[0])
+    dprintf(idx, "        %s\n", dates);
+}
+
+static void tell_bans(int idx, int show_inact, char *match)
+{
+  char *p = NULL, *chname;
+  int k = 1;
+  struct chanset_t *chan = NULL;
+  maskrec *u;
+
+  /* Was a channel given? */
+  if (match[0]) {
+    p = strdup(match);
+    chname = strtok(p, " ");
+    if (chname && (strchr(CHANMETA, chname[0]))) {
+      chan = findchan_by_dname(chname);
+      if (!chan) {
+	dprintf(idx, "%s.\n", _("No such channel defined"));
+	return;
+      }
+    } else
+      match = chname;
+    if (p)
+      free_null(p);
+  }
+
+  /* don't return here, we want to show global bans even if no chan */
+  if (!chan && !(chan = findchan_by_dname(dcc[idx].u.chat->con_chan)) &&
+      !(chan = chanset))
+    chan = NULL;
+
+  if (chan && show_inact)
+    dprintf(idx, "%s:   (! = %s %s)\n", _("Global bans"),
+	    _("not active on"), chan->dname);
+  else
+    dprintf(idx, "%s:\n", _("Global bans"));
+  for (u = global_bans; u; u = u->next) {
+    if (match[0]) {
+      if ((wild_match(match, u->mask)) ||
+	  (wild_match(match, u->desc)) ||
+	  (wild_match(match, u->user)))
+	display_ban(idx, k, u, chan, 1);
+      k++;
+    } else
+      display_ban(idx, k++, u, chan, show_inact);
+  }
+  if (chan) {
+    if (show_inact)
+      dprintf(idx, _("Channel bans for %s:   (! = not active, * = not placed by bot)\n"),
+	      chan->dname);
+    else
+      dprintf(idx, _("Channel bans for %s:  (* = not placed by bot)\n"),
+	      chan->dname);
+    for (u = chan->bans; u; u = u->next) {
+      if (match[0]) {
+        if ((wild_match(match, u->mask)) ||
+  	    (wild_match(match, u->desc)) ||
+	    (wild_match(match, u->user)))
+	  display_ban(idx, k, u, chan, 1);
+	k++;
+      } else
+	display_ban(idx, k++, u, chan, show_inact);
+    }
+    if (chan->status & CHAN_ACTIVE) {
+      masklist *b;
+      /* FIXME: possible buffer overflow in fill[] */
+      char buf[UHOSTLEN], *nick, *uhost, fill[UHOSTLEN * 2];
+      int min, sec;
+
+      for (b = chan->channel.ban; b && b->mask[0]; b = b->next) {    
+	if ((!u_equals_mask(global_bans, b->mask)) &&
+	    (!u_equals_mask(chan->bans, b->mask))) {
+	  strncpyz(buf, b->who, sizeof buf);
+	  nick = strtok(buf, "!");
+	  uhost = strtok(NULL, "!");
+	  if (nick)
+	    sprintf(fill, "%s (%s!%s)", b->mask, nick, uhost);
+	  else
+	    sprintf(fill, "%s (server %s)", b->mask, uhost);
+	  if (b->timer != 0) {
+	    min = (now - b->timer) / 60;
+	    sec = (now - b->timer) - (min * 60);
+	    sprintf(buf, " (active %02d:%02d)", min, sec);
+	    strcat(fill, buf);
+	  }
+	  if ((!match[0]) || (wild_match(match, b->mask)))
+	    dprintf(idx, "* [%3d] %s\n", k, fill);
+	  k++;
+	}
+      }
+    }
+  }
+  if (k == 1)
+    dprintf(idx, _("(There are no bans, permanent or otherwise.)\n"));
+  if ((!show_inact) && (!match[0]))
+    dprintf(idx, _("Use .bans all to see the total list.\n"));
+}
+
+static void tell_exempts(int idx, int show_inact, char *match)
+{
+  char *p = NULL, *chname;
+  int k = 1;
+  struct chanset_t *chan = NULL;
+  maskrec *u;
+
+  /* Was a channel given? */
+  if (match[0]) {
+    p = strdup(match);
+    chname = strtok(p, " ");
+    if (chname && (strchr(CHANMETA, chname[0]))) {
+      chan = findchan_by_dname(chname);
+      if (!chan) {
+	dprintf(idx, _("No such channel defined.\n"));
+	return;
+      }
+    } else
+      match = chname;
+    if (p)
+      free_null(p);
+  }
+
+  /* don't return here, we want to show global exempts even if no chan */
+  if (!chan && !(chan = findchan_by_dname(dcc[idx].u.chat->con_chan))
+      && !(chan = chanset))
+    chan = NULL;
+
+  if (chan && show_inact)
+    dprintf(idx, "%s:   (! = %s %s)\n", _("Global exempts"),
+	    _("not active on"), chan->dname);
+  else
+    dprintf(idx, _("Global exempts:\n"));
+  for (u = global_exempts; u; u = u->next) {
+    if (match[0]) {
+      if ((wild_match(match, u->mask)) ||
+	  (wild_match(match, u->desc)) ||
+	  (wild_match(match, u->user)))
+	display_exempt(idx, k, u, chan, 1);
+      k++;
+    } else
+      display_exempt(idx, k++, u, chan, show_inact);
+  }
+  if (chan) {
+    if (show_inact)
+      dprintf(idx, "%s %s:   (! = %s, * = %s)\n",
+	      _("Channel exempts for"), chan->dname,
+	      _("not active"),
+	      _("not placed by bot"));
+    else
+      dprintf(idx, "%s %s:  (* = %s)\n",
+	      _("Channel exempts for"), chan->dname,
+	      _("not placed by bot"));
+    for (u = chan->exempts; u; u = u->next) {
+      if (match[0]) {
+	if ((wild_match(match, u->mask)) ||
+	    (wild_match(match, u->desc)) ||
+	    (wild_match(match, u->user)))
+	  display_exempt(idx, k, u, chan, 1);
+	k++;
+      } else
+	display_exempt(idx, k++, u, chan, show_inact);
+    }
+    if (chan->status & CHAN_ACTIVE) {
+      masklist *e;
+      /* FIXME: possible buffer overflow in fill[] */
+      char buf[UHOSTLEN], *nick, *uhost, fill[UHOSTLEN * 2];
+      int min, sec;
+
+      for (e = chan->channel.exempt; e && e->mask[0]; e = e->next) {
+	if ((!u_equals_mask(global_exempts,e->mask)) &&
+	    (!u_equals_mask(chan->exempts, e->mask))) {
+	  strncpyz(buf, e->who, sizeof buf);
+	  nick = strtok(buf, "!");
+	  uhost = strtok(NULL, "!");
+	  if (nick)
+	    sprintf(fill, "%s (%s!%s)", e->mask, nick, uhost);
+	  else
+	    sprintf(fill, "%s (server %s)", e->mask, uhost);
+	  if (e->timer != 0) {
+	    min = (now - e->timer) / 60;
+	    sec = (now - e->timer) - (min * 60);
+	    sprintf(buf, " (active %02d:%02d)", min, sec);
+	    strcat(fill, buf);
+	  }
+	  if ((!match[0]) || (wild_match(match, e->mask)))
+	    dprintf(idx, "* [%3d] %s\n", k, fill);
+	  k++;
+	}
+      }
+    }
+  }
+  if (k == 1)
+    dprintf(idx, "(There are no ban exempts, permanent or otherwise.)\n");
+  if ((!show_inact) && (!match[0]))
+    dprintf(idx, "%s.\n", _("Use .exempts all to see the total list"));
+}
+
+static void tell_invites(int idx, int show_inact, char *match)
+{
+  char *p = NULL, *chname;
+  int k = 1;
+  struct chanset_t *chan = NULL;
+  maskrec *u;
+
+  /* Was a channel given? */
+  if (match[0]) {
+    p = strdup(match);
+    chname = strtok(p, " ");
+    if (chname && (strchr(CHANMETA, chname[0]))) {
+      chan = findchan_by_dname(chname);
+      if (!chan) {
+	dprintf(idx, "%s.\n", _("No such channel defined"));
+	return;
+      }
+    } else
+      match = chname;
+    if (p)
+      free_null(p);
+  }
+
+  /* don't return here, we want to show global invites even if no chan */
+  if (!chan && !(chan = findchan_by_dname(dcc[idx].u.chat->con_chan))
+      && !(chan = chanset))
+    chan = NULL;
+
+  if (chan && show_inact)
+    dprintf(idx, "%s:   (! = %s %s)\n", _("Global invites"),
+	    _("not active on"), chan->dname);
+  else
+    dprintf(idx, "%s:\n", _("Global invites"));
+  for (u = global_invites; u; u = u->next) {
+    if (match[0]) {
+      if ((wild_match(match, u->mask)) ||
+	  (wild_match(match, u->desc)) ||
+	  (wild_match(match, u->user)))
+	display_invite(idx, k, u, chan, 1);
+      k++;
+    } else
+      display_invite(idx, k++, u, chan, show_inact);
+  }
+  if (chan) {
+    if (show_inact)
+      dprintf(idx, "%s %s:   (! = %s, * = %s)\n",
+	      _("Channel invites for"), chan->dname,
+	      _("not active"),
+	      _("not placed by bot"));
+    else
+      dprintf(idx, "%s %s:  (* = %s)\n",
+	      _("Channel invites for"), chan->dname,
+	      _("not placed by bot"));
+    for (u = chan->invites; u; u = u->next) {
+      if (match[0]) {
+	if ((wild_match(match, u->mask)) ||
+	    (wild_match(match, u->desc)) ||
+	    (wild_match(match, u->user)))
+	  display_invite(idx, k, u, chan, 1);
+	k++;
+      } else
+	display_invite(idx, k++, u, chan, show_inact);
+    }
+    if (chan->status & CHAN_ACTIVE) {
+      masklist *i;
+      /* FIXME: possible buffer overflow in fill[] */
+      char buf[UHOSTLEN], *nick, *uhost, fill[UHOSTLEN * 2];
+      int min, sec;
+
+      for (i = chan->channel.invite; i && i->mask[0]; i = i->next) {
+	if ((!u_equals_mask(global_invites,i->mask)) &&
+	    (!u_equals_mask(chan->invites, i->mask))) {
+	  strncpyz(buf, i->who, sizeof buf);
+	  nick = strtok(buf, "!");
+	  uhost = strtok(NULL, "!");
+	  if (nick)
+	    sprintf(fill, "%s (%s!%s)", i->mask, nick, uhost);
+	  else
+	    sprintf(fill, "%s (server %s)", i->mask, uhost);
+	  if (i->timer != 0) {
+	    min = (now - i->timer) / 60;
+	    sec = (now - i->timer) - (min * 60);
+	    sprintf(buf, " (active %02d:%02d)", min, sec);
+	    strcat(fill, buf);
+	  }
+	  if ((!match[0]) || (wild_match(match, i->mask)))
+	    dprintf(idx, "* [%3d] %s\n", k, fill);
+	  k++;
+	}
+      }
+    }
+  }
+  if (k == 1)
+    dprintf(idx, "(There are no invites, permanent or otherwise.)\n");
+  if ((!show_inact) && (!match[0]))
+    dprintf(idx, "%s.\n", _("Use .invites all to see the total list"));
+}
+
+/* Write the ban lists and the ignore list to a file.
+ */
+static int write_bans(FILE *f, int idx)
+{
+  struct chanset_t *chan;
+  maskrec *b;
+  struct igrec *i;
+  char	*mask;
+
+  if (global_ign)
+    if (fprintf(f, IGNORE_NAME " - -\n") == EOF)	/* Daemus */
+      return 0;
+  for (i = global_ign; i; i = i->next) {
+    mask = str_escape(i->igmask, ':', '\\');
+    if (!mask ||
+	fprintf(f, "- %s:%s%lu:%s:%lu:%s\n", mask,
+		(i->flags & IGREC_PERM) ? "+" : "", i->expire,
+		i->user ? i->user : botnetnick, i->added,
+		i->msg ? i->msg : "") == EOF) {
+      if (mask)
+	free(mask);
+      return 0;
+    }
+    free(mask);
+  }
+  if (global_bans)
+    if (fprintf(f, BAN_NAME " - -\n") == EOF)	/* Daemus */
+      return 0;
+  for (b = global_bans; b; b = b->next) {
+    mask = str_escape(b->mask, ':', '\\');
+    if (!mask ||
+	fprintf(f, "- %s:%s%lu%s:+%lu:%lu:%s:%s\n", mask,
+		(b->flags & MASKREC_PERM) ? "+" : "", b->expire,
+		(b->flags & MASKREC_STICKY) ? "*" : "", b->added,
+		b->lastactive, b->user ? b->user : botnetnick,
+		b->desc ? b->desc : "requested") == EOF) {
+      if (mask)
+	free(mask);
+      return 0;
+    }
+    free(mask);
+  }
+  for (chan = chanset; chan; chan = chan->next)
+    if ((idx < 0) || (chan->status & CHAN_SHARED)) {
+      struct flag_record fr = {FR_CHAN | FR_GLOBAL | FR_BOT, 0, 0, 0, 0, 0};
+
+      if (idx >= 0)
+	get_user_flagrec(dcc[idx].user, &fr, chan->dname);
+      else
+	fr.chan = BOT_SHARE;
+      if ((fr.chan & BOT_SHARE) || (fr.bot & BOT_GLOBAL)) {
+	if (fprintf(f, "::%s bans\n", chan->dname) == EOF)
+	  return 0;
+	for (b = chan->bans; b; b = b->next) {
+	  mask = str_escape(b->mask, ':', '\\');
+	  if (!mask ||
+	      fprintf(f, "- %s:%s%lu%s:+%lu:%lu:%s:%s\n", mask,
+		      (b->flags & MASKREC_PERM) ? "+" : "", b->expire,
+		      (b->flags & MASKREC_STICKY) ? "*" : "", b->added,
+		      b->lastactive, b->user ? b->user : botnetnick,
+		      b->desc ? b->desc : "requested") == EOF) {
+	    if (mask)
+	      free(mask);
+	    return 0;
+	  }
+	  free(mask);
+	}
+      }
+    }
+  return 1;
+}
+
+/* Write the exemptlists to a file.
+ */
+static int write_exempts(FILE *f, int idx)
+{
+  struct chanset_t *chan;
+  maskrec *e;
+  char	*mask;
+
+  if (global_exempts)
+    if (fprintf(f, EXEMPT_NAME " - -\n") == EOF) /* Daemus */
+      return 0;
+  for (e = global_exempts; e; e = e->next) {
+    mask = str_escape(e->mask, ':', '\\');
+    if (!mask ||
+	fprintf(f, "%s %s:%s%lu%s:+%lu:%lu:%s:%s\n", "%", e->mask,
+		(e->flags & MASKREC_PERM) ? "+" : "", e->expire,
+		(e->flags & MASKREC_STICKY) ? "*" : "", e->added,
+		e->lastactive, e->user ? e->user : botnetnick,
+		e->desc ? e->desc : "requested") == EOF) {
+      if (mask)
+	free(mask);
+      return 0;
+    }
+    free(mask);
+  }
+  for (chan = chanset;chan;chan=chan->next)
+    if ((idx < 0) || (chan->status & CHAN_SHARED)) {
+      struct flag_record fr = {FR_CHAN | FR_GLOBAL | FR_BOT, 0, 0, 0, 0, 0};
+
+      if (idx >= 0)
+	get_user_flagrec(dcc[idx].user,&fr,chan->dname);
+      else
+	fr.chan = BOT_SHARE;
+      if ((fr.chan & BOT_SHARE) || (fr.bot & BOT_GLOBAL)) {
+	if (fprintf(f, "&&%s exempts\n", chan->dname) == EOF)
+	  return 0;
+	for (e = chan->exempts; e; e = e->next) {
+	  mask = str_escape(e->mask, ':', '\\');
+	  if (!mask ||
+	      fprintf(f,"%s %s:%s%lu%s:+%lu:%lu:%s:%s\n","%",e->mask,
+		      (e->flags & MASKREC_PERM) ? "+" : "", e->expire,
+		      (e->flags & MASKREC_STICKY) ? "*" : "", e->added,
+		      e->lastactive, e->user ? e->user : botnetnick,
+		      e->desc ? e->desc : "requested") == EOF) {
+	    if (mask)
+	      free(mask);
+	    return 0;
+	  }
+	  free(mask);
+	}
+      }
+    }
+  return 1;
+}
+
+/* Write the invitelists to a file.
+ */
+static int write_invites(FILE *f, int idx)
+{
+  struct chanset_t *chan;
+  maskrec *ir;
+  char	*mask;
+
+  if (global_invites)
+    if (fprintf(f, INVITE_NAME " - -\n") == EOF) /* Daemus */
+      return 0;
+  for (ir = global_invites; ir; ir = ir->next)  {
+    mask = str_escape(ir->mask, ':', '\\');
+    if (!mask ||
+	fprintf(f,"@ %s:%s%lu%s:+%lu:%lu:%s:%s\n",ir->mask,
+		(ir->flags & MASKREC_PERM) ? "+" : "", ir->expire,
+		(ir->flags & MASKREC_STICKY) ? "*" : "", ir->added,
+		ir->lastactive, ir->user ? ir->user : botnetnick,
+		ir->desc ? ir->desc : "requested") == EOF) {
+      if (mask)
+	free(mask);
+      return 0;
+    }
+    free(mask);
+  }
+  for (chan = chanset; chan; chan = chan->next)
+    if ((idx < 0) || (chan->status & CHAN_SHARED)) {
+      struct flag_record fr = {FR_CHAN | FR_GLOBAL | FR_BOT, 0, 0, 0, 0, 0};
+
+      if (idx >= 0)
+	get_user_flagrec(dcc[idx].user,&fr,chan->dname);
+      else
+	fr.chan = BOT_SHARE;
+      if ((fr.chan & BOT_SHARE) || (fr.bot & BOT_GLOBAL)) {
+	if (fprintf(f, "$$%s invites\n", chan->dname) == EOF)
+	  return 0;
+	for (ir = chan->invites; ir; ir = ir->next) {
+	  mask = str_escape(ir->mask, ':', '\\');
+	  if (!mask ||
+	      fprintf(f,"@ %s:%s%lu%s:+%lu:%lu:%s:%s\n",ir->mask,
+		      (ir->flags & MASKREC_PERM) ? "+" : "", ir->expire,
+		      (ir->flags & MASKREC_STICKY) ? "*" : "", ir->added,
+		      ir->lastactive, ir->user ? ir->user : botnetnick,
+		      ir->desc ? ir->desc : "requested") == EOF) {
+	    if (mask)
+	      free(mask);
+	    return 0;
+	  }
+	  free(mask);
+	}
+      }
+    }
+  return 1;
+}
+
+static void channels_writeuserfile(void)
+{
+  char	 s[1024];
+  FILE	*f;
+  int	 ret = 0;
+
+  simple_sprintf(s, "%s~new", userfile);
+  f = fopen(s, "a");
+  if (f) {
+    ret = write_bans(f, -1);
+    ret += write_exempts(f, -1);
+    ret += write_invites(f, -1);
+    fclose(f);
+  }
+  if (ret < 3)
+    putlog(LOG_MISC, "*", _("ERROR writing user file."));
+  write_channels();
+}
+
+/* Expire mask originally set by `who' on `chan'?
+ *
+ * We might not want to expire masks in all cases, as other bots
+ * often tend to immediately reset masks they've listed in their
+ * internal ban list, making it quite senseless for us to remove
+ * them in the first place.
+ *
+ * Returns 1 if a mask on `chan' by `who' may be expired and 0 if
+ * not.
+ */
+static int expired_mask(struct chanset_t *chan, char *who)
+{
+  char buf[UHOSTLEN], *nick, *uhost;
+  struct userrec *u;
+  memberlist *m, *m2;
+
+  /* Always expire masks, regardless of who set it? */
+  if (force_expire)
+    return 1;
+
+  strncpyz(buf, who, sizeof buf);
+  nick = strtok(buf, "!");
+  uhost = strtok(NULL, "!");
+
+  if (!nick)
+    return 1;
+
+  m = ismember(chan, nick);
+  if (!m)
+    for (m2 = chan->channel.member; m2 && m2->nick[0]; m2 = m2->next)
+      if (!strcasecmp(uhost, m2->userhost)) {
+	m = m2;
+	break;
+      }
+
+  if (!m || !chan_hasop(m) || !irccmp(m->nick, botname))
+    return 1;
+
+  /* At this point we know the person/bot who set the mask is currently
+   * present in the channel and has op.
+   */
+
+  if (m->user)
+    u = m->user;
+  else {
+    simple_sprintf(buf, "%s!%s", m->nick, m->userhost);
+    u = get_user_by_host(buf);
+  }
+  /* Do not expire masks set by bots. */
+  if (u && u->flags & USER_BOT)
+    return 0;
+  else
+    return 1;
+}
+
+/* Check for expired timed-bans.
+ */
+static void check_expired_bans(void)
+{
+  maskrec *u, *u2;
+  struct chanset_t *chan;
+  masklist *b;
+
+  for (u = global_bans; u; u = u2) { 
+    u2 = u->next;
+    if (!(u->flags & MASKREC_PERM) && (now >= u->expire)) {
+      putlog(LOG_MISC, "*", "%s %s (%s)", _("No longer banning"),
+	     u->mask, _("expired"));
+      for (chan = chanset; chan; chan = chan->next)
+	for (b = chan->channel.ban; b->mask[0]; b = b->next)
+	  if (!irccmp(b->mask, u->mask) &&
+	      expired_mask(chan, b->who) && b->timer != now) {
+	    add_mode(chan, '-', 'b', u->mask);
+	    b->timer = now;
+	  }
+      u_delban(NULL, u->mask, 1);
+    }
+  }
+  /* Check for specific channel-domain bans expiring */
+  for (chan = chanset; chan; chan = chan->next) {
+    for (u = chan->bans; u; u = u2) {
+      u2 = u->next;
+      if (!(u->flags & MASKREC_PERM) && (now >= u->expire)) {
+	putlog(LOG_MISC, "*", "%s %s %s %s (%s)", _("No longer banning"),
+	       u->mask, _("on"), chan->dname, _("expired"));
+	for (b = chan->channel.ban; b->mask[0]; b = b->next)
+	  if (!irccmp(b->mask, u->mask) &&
+	      expired_mask(chan, b->who) && b->timer != now) {
+	    add_mode(chan, '-', 'b', u->mask);
+	    b->timer = now;
+	  }
+	u_delban(chan, u->mask, 1);
+      }
+    }
+  }
+}
+
+/* Check for expired timed-exemptions
+ */
+static void check_expired_exempts(void)
+{
+  maskrec *u, *u2;
+  struct chanset_t *chan;
+  masklist *b, *e;
+  int match;
+
+  if (!use_exempts)
+    return;
+  for (u = global_exempts; u; u = u2) {
+    u2 = u->next;
+    if (!(u->flags & MASKREC_PERM) && (now >= u->expire)) {
+      putlog(LOG_MISC, "*", "%s %s (%s)", _("No longer ban exempting"),
+	     u->mask, _("expired"));
+      for (chan = chanset; chan; chan = chan->next) {
+        match = 0;
+        b = chan->channel.ban;
+        while (b->mask[0] && !match) {
+          if (wild_match(b->mask, u->mask) ||
+            wild_match(u->mask, b->mask))
+            match = 1;
+          else
+            b = b->next;
+        }
+        if (match)
+          putlog(LOG_MISC, chan->dname,
+            "Exempt not expired on channel %s. Ban still set!",
+            chan->dname);
+	else
+	  for (e = chan->channel.exempt; e->mask[0]; e = e->next)
+	    if (!irccmp(e->mask, u->mask) &&
+		expired_mask(chan, e->who) && e->timer != now) {
+	      add_mode(chan, '-', 'e', u->mask);
+	      e->timer = now;
+	    }
+      }
+      u_delexempt(NULL, u->mask,1);
+    }
+  }
+  /* Check for specific channel-domain exempts expiring */
+  for (chan = chanset; chan; chan = chan->next) {
+    for (u = chan->exempts; u; u = u2) {
+      u2 = u->next;
+      if (!(u->flags & MASKREC_PERM) && (now >= u->expire)) {
+        match=0;
+        b = chan->channel.ban;
+        while (b->mask[0] && !match) {
+          if (wild_match(b->mask, u->mask) ||
+            wild_match(u->mask, b->mask))
+            match=1;
+          else
+            b = b->next;
+        }
+        if (match)
+          putlog(LOG_MISC, chan->dname,
+            "Exempt not expired on channel %s. Ban still set!",
+            chan->dname);
+        else {
+          putlog(LOG_MISC, "*", "%s %s %s %s (%s)", _("No longer ban exempting"),
+		 u->mask, _("on"), chan->dname, _("expired"));
+	  for (e = chan->channel.exempt; e->mask[0]; e = e->next)
+	    if (!irccmp(e->mask, u->mask) &&
+		expired_mask(chan, e->who) && e->timer != now) {
+	      add_mode(chan, '-', 'e', u->mask);
+	      e->timer = now;
+	    }
+          u_delexempt(chan, u->mask, 1);
+        }
+      }
+    }
+  }
+}
+
+/* Check for expired timed-invites.
+ */
+static void check_expired_invites(void)
+{
+  maskrec *u, *u2;
+  struct chanset_t *chan;
+  masklist *b;
+
+  if (!use_invites)
+    return;
+  for (u = global_invites; u; u = u2) {
+    u2 = u->next;
+    if (!(u->flags & MASKREC_PERM) && (now >= u->expire)) {
+      putlog(LOG_MISC, "*", "%s %s (%s)", _("No longer inviteing"),
+	     u->mask, _("expired"));
+      for (chan = chanset; chan; chan = chan->next)
+	if (!(chan->channel.mode & CHANINV))
+	  for (b = chan->channel.invite; b->mask[0]; b = b->next)
+	    if (!irccmp(b->mask, u->mask) &&
+		expired_mask(chan, b->who) && b->timer != now) {
+	      add_mode(chan, '-', 'I', u->mask);
+	      b->timer = now;
+	    }
+      u_delinvite(NULL, u->mask,1);
+    }
+  }
+  /* Check for specific channel-domain invites expiring */
+  for (chan = chanset; chan; chan = chan->next) {
+    for (u = chan->invites; u; u = u2) {
+      u2 = u->next;
+      if (!(u->flags & MASKREC_PERM) && (now >= u->expire)) {
+	putlog(LOG_MISC, "*", "%s %s %s %s (%s)", _("No longer inviteing"),
+	       u->mask, _("on"), chan->dname, _("expired"));
+	if (!(chan->channel.mode & CHANINV))
+	  for (b = chan->channel.invite; b->mask[0]; b = b->next)
+	    if (!irccmp(b->mask, u->mask) &&
+		expired_mask(chan, b->who) && b->timer != now) {
+	      add_mode(chan, '-', 'I', u->mask);
+	      b->timer = now;
+	    }
+	u_delinvite(chan, u->mask, 1);
+      }
+    }
+  }
+}
Index: eggdrop1.7/modules/compress/.cvsignore
diff -u /dev/null eggdrop1.7/modules/compress/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/compress/.cvsignore	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,9 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
+
Index: eggdrop1.7/modules/compress/Makefile.am
diff -u /dev/null eggdrop1.7/modules/compress/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/compress/Makefile.am	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,22 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:48 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+# FIXME: test if there's dependency on gnu make. It shouldn't.
+if EGG_COMPRESS
+  EGG_CMP = compress.la
+else
+  EGG_CMP =
+endif
+
+pkglib_LTLIBRARIES	= $(EGG_CMP)
+compress_la_SOURCES	= compress.c
+compress_la_LDFLAGS	= -module -avoid-version -no-undefined
+compress_la_LIBADD	= @TCL_LIBS@ @ZLIB@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/compress/compress.c
diff -u /dev/null eggdrop1.7/modules/compress/compress.c:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/compress/compress.c	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,441 @@
+/*
+ * compress.c -- part of compress.mod
+ *   uses the compression library libz to compress and uncompress the
+ *   userfiles during the sharing process
+ *
+ * Written by Fabian Knittel <fknittel at gmx.de>. Based on zlib examples
+ * by Jean-loup Gailly and Miguel Albrecht.
+ *
+ * $Id: compress.c,v 1.1 2001/10/27 16:34:48 ite Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define MODULE_NAME "compress"
+#define MAKING_COMPRESS
+
+#include <string.h>
+#include <errno.h>
+#include <zlib.h>
+
+#include "lib/eggdrop/module.h"
+#include "modules/share/share.h"
+
+#ifdef HAVE_MMAP
+#  include <sys/types.h>
+#  include <sys/mman.h>
+#  include <sys/stat.h>
+#endif /* HAVE_MMAP */
+#include "compress.h"
+
+#define start compress_LTX_start
+
+#define BUFLEN	512
+
+
+static Function *global = NULL,
+		*share_funcs = NULL;
+
+static unsigned int compressed_files;	/* Number of files compressed.      */
+static unsigned int uncompressed_files;	/* Number of files uncompressed.    */
+static unsigned int share_compressed;	/* Compress userfiles when sharing? */
+static unsigned int compress_level;	/* Default compression used.	    */
+
+
+static int uncompress_to_file(char *f_src, char *f_target);
+static int compress_to_file(char *f_src, char *f_target, int mode_num);
+static int compress_file(char *filename, int mode_num);
+static int uncompress_file(char *filename);
+static int is_compressedfile(char *filename);
+
+#include "tclcompress.c"
+
+
+/*
+ *    Misc functions.
+ */
+
+static int is_compressedfile(char *filename)
+{
+  char		  buf1[50], buf2[50];
+  FILE		 *fin;
+  register int    len1, len2, i;
+
+  memset(buf1, 0, 50);
+  memset(buf2, 0, 50);
+  if (!is_file(filename))
+    return COMPF_FAILED;
+
+  /* Read data with zlib routines.
+   */
+  fin = gzopen(filename, "rb");
+  if (!fin)
+    return COMPF_FAILED;
+  len1 = gzread(fin, buf1, sizeof(buf1));
+  if (len1 < 0)
+    return COMPF_FAILED;
+  if (gzclose(fin) != Z_OK)
+    return COMPF_FAILED;
+
+  /* Read raw data.
+   */
+  fin = fopen(filename, "rb");
+  if (!fin)
+    return COMPF_FAILED;
+  len2 = fread(buf2, 1, sizeof(buf2), fin);
+  if (ferror(fin))
+    return COMPF_FAILED;
+  fclose(fin);
+
+  /* Compare what we found.
+   */
+  if (len1 != len2)
+    return COMPF_COMPRESSED;
+  for (i = 0; i < sizeof(buf1); i++)
+    if (buf1[i] != buf2[i])
+      return COMPF_COMPRESSED;
+  return COMPF_UNCOMPRESSED;
+}
+
+
+/*
+ *    General compression / uncompression functions
+ */
+
+/* Uncompresses a file `f_src' and saves it as `f_target'.
+ */
+static int uncompress_to_file(char *f_src, char *f_target)
+{
+  char buf[BUFLEN];
+  int len;
+  FILE *fin, *fout;
+
+  if (!is_file(f_src)) {
+    putlog(LOG_MISC, "*", _("Failed to uncompress file `%s': not a file."),
+	   f_src);
+    return COMPF_ERROR;
+  }
+  fin = gzopen(f_src, "rb");
+  if (!fin) {
+    putlog(LOG_MISC, "*", _("Failed to uncompress file `%s': gzopen failed."),
+	   f_src);
+    return COMPF_ERROR;
+  }
+
+  fout = fopen(f_target, "wb");
+  if (!fout) {
+    putlog(LOG_MISC, "*", _("Failed to uncompress file `%1$s': open failed: %2$s."),
+	   f_src, strerror(errno));
+    return COMPF_ERROR;
+  }
+
+  while (1) {
+    len = gzread(fin, buf, sizeof(buf));
+    if (len < 0) {
+      putlog(LOG_MISC, "*", _("Failed to uncompress file `%s': gzread failed."),
+	     f_src);
+      return COMPF_ERROR;
+    }
+    if (!len)
+      break;
+    if ((int) fwrite(buf, 1, (unsigned int) len, fout) != len) {
+      putlog(LOG_MISC, "*", _("Failed to uncompress file `%1$s': fwrite failed: %2$s."),
+	     f_src, strerror(errno));
+      return COMPF_ERROR;
+    }
+  }
+  if (fclose(fout)) {
+    putlog(LOG_MISC, "*", _("Failed to uncompress file `%1$s': fclose failed: %2$s."),
+	   f_src, strerror(errno));
+    return COMPF_ERROR;
+  }
+  if (gzclose(fin) != Z_OK) {
+    putlog(LOG_MISC, "*", _("Failed to uncompress file `%s': gzclose failed."),
+	   f_src);
+    return COMPF_ERROR;
+  }
+  uncompressed_files++;
+  return COMPF_SUCCESS;
+}
+
+/* Enforce limits.
+ */
+inline static void adjust_mode_num(int *mode)
+{
+  if (*mode > 9)
+    *mode = 9;
+  else if (*mode < 0)
+    *mode = 0;
+}
+
+#ifdef HAVE_MMAP
+/* Attempt to compress in one go, by mmap'ing the file to memory.
+ */
+static int compress_to_file_mmap(FILE *fout, FILE *fin)
+{
+    int		  len, ifd = fileno(fin);
+    char	 *buf;
+    struct stat	  st;
+
+    /* Find out size of file */
+    if (fstat(ifd, &st) < 0)
+      return COMPF_ERROR;
+    if (st.st_size <= 0)
+      return COMPF_ERROR;
+
+    /* mmap file contents to memory */
+    buf = mmap(0, st.st_size, PROT_READ, MAP_SHARED, ifd, 0);
+    if (buf < 0)
+      return COMPF_ERROR;
+
+    /* Compress the whole file in one go */
+    len = gzwrite(fout, buf, st.st_size);
+    if (len != (int) st.st_size)
+      return COMPF_ERROR;
+
+    munmap(buf, st.st_size);
+    fclose(fin);
+    if (gzclose(fout) != Z_OK)
+      return COMPF_ERROR;
+    return COMPF_SUCCESS;
+}
+#endif /* HAVE_MMAP */
+
+/* Compresses a file `f_src' and saves it as `f_target'.
+ */
+static int compress_to_file(char *f_src, char *f_target, int mode_num)
+{
+  char  buf[BUFLEN], mode[5];
+  FILE *fin, *fout;
+  int   len;
+
+  adjust_mode_num(&mode_num);
+  snprintf(mode, sizeof mode, "wb%d", mode_num);
+
+  if (!is_file(f_src)) {
+    putlog(LOG_MISC, "*", _("Failed to compress file `%s': not a file."),
+	   f_src);
+    return COMPF_ERROR;
+  }
+  fin = fopen(f_src, "rb");
+  if (!fin) {
+    putlog(LOG_MISC, "*", _("Failed to compress file `%1$s': open failed: %2$s."),
+	   f_src, strerror(errno));
+    return COMPF_ERROR;
+  }
+
+  fout = gzopen(f_target, mode);
+  if (!fout) {
+    putlog(LOG_MISC, "*", _("Failed to compress file `%s': gzopen failed."),
+	   f_src);
+    return COMPF_ERROR;
+  }
+
+#ifdef HAVE_MMAP
+  if (compress_to_file_mmap(fout, fin) == COMPF_SUCCESS) {
+    compressed_files++;
+    return COMPF_SUCCESS;
+  } else {
+    /* To be on the safe side, close the file before attempting
+     * to write again.
+     */
+    gzclose(fout);
+    fout = gzopen(f_target, mode);
+  }
+#endif /* HAVE_MMAP */
+
+  while (1) {
+    len = fread(buf, 1, sizeof(buf), fin);
+    if (ferror(fin)) {
+      putlog(LOG_MISC, "*", _("Failed to compress file `%1$s': fread failed: %2$s"),
+	     f_src, strerror(errno));
+      return COMPF_ERROR;
+    }
+    if (!len)
+      break;
+    if (gzwrite(fout, buf, (unsigned int) len) != len) {
+      putlog(LOG_MISC, "*", _("Failed to compress file `%s': gzwrite failed."),
+	     f_src);
+      return COMPF_ERROR;
+    }
+  }
+  fclose(fin);
+  if (gzclose(fout) != Z_OK) {
+    putlog(LOG_MISC, "*", _("Failed to compress file `%s': gzclose failed."),
+	   f_src);
+    return COMPF_ERROR;
+  }
+  compressed_files++;
+  return COMPF_SUCCESS;
+}
+
+/* Compresses a file `filename' and saves it as `filename'.
+ */
+static int compress_file(char *filename, int mode_num)
+{
+  char *temp_fn, randstr[5];
+  int   ret;
+
+  /* Create temporary filename. */
+  temp_fn = malloc(strlen(filename) + 5);
+  make_rand_str(randstr, 4);
+  strcpy(temp_fn, filename);
+  strcat(temp_fn, randstr);
+
+  /* Compress file. */
+  ret = compress_to_file(filename, temp_fn, mode_num);
+
+  /* Overwrite old file with compressed version.  Only do so
+   * if the compression routine succeeded.
+   */
+  if (ret == COMPF_SUCCESS)
+    movefile(temp_fn, filename);
+
+  free(temp_fn);
+  return ret;
+}
+
+/* Uncompresses a file `filename' and saves it as `filename'.
+ */
+static int uncompress_file(char *filename)
+{
+  char *temp_fn, randstr[5];
+  int   ret;
+
+  /* Create temporary filename. */
+  temp_fn = malloc(strlen(filename) + 5);
+  make_rand_str(randstr, 4);
+  strcpy(temp_fn, filename);
+  strcat(temp_fn, randstr);
+
+  /* Uncompress file. */
+  ret = uncompress_to_file(filename, temp_fn);
+
+  /* Overwrite old file with uncompressed version.  Only do so
+   * if the uncompression routine succeeded.
+   */
+  if (ret == COMPF_SUCCESS)
+    movefile(temp_fn, filename);
+
+  free(temp_fn);
+  return ret;
+}
+
+
+/*
+ *    Userfile feature releated functions
+ */
+
+static int uff_comp(int idx, char *filename)
+{
+  debug1("NOTE: Compressing user file for %s.", dcc[idx].nick);
+  return compress_file(filename, compress_level);
+}
+
+static int uff_uncomp(int idx, char *filename)
+{
+  debug1("NOTE: Uncompressing user file from %s.", dcc[idx].nick);
+  return uncompress_file(filename);
+}
+
+static int uff_ask_compress(int idx)
+{
+  if (share_compressed)
+    return 1;
+  else
+    return 0;
+}
+
+static uff_table_t compress_uff_table[] = {
+  {"compress",	UFF_COMPRESS,	uff_ask_compress, 100, uff_comp, uff_uncomp},
+  {NULL,	0,		NULL,		    0,	   NULL,       NULL}
+};
+
+/*
+ *    Compress module related code
+ */
+
+static tcl_ints my_tcl_ints[] = {
+  {"share-compressed",  	&share_compressed},
+  {"compress-level",		&compress_level},
+  {NULL,                	NULL}
+};
+
+static int compress_report(int idx, int details)
+{
+  /* FIXME PLURAL: do it in the proper way. */
+  if (details)
+    dprintf(idx, "    Compressed %u file(s), uncompressed %u file(s).\n",
+	    compressed_files, uncompressed_files);
+  return 0;
+}
+
+static char *compress_close()
+{
+  rem_help_reference("compress.help");
+  rem_tcl_commands(my_tcl_cmds);
+  rem_tcl_ints(my_tcl_ints);
+  uff_deltable(compress_uff_table);
+
+  module_undepend(MODULE_NAME);
+  return NULL;
+}
+
+EXPORT_SCOPE char *start();
+
+static Function compress_table[] =
+{
+  /* 0 - 3 */
+  (Function) start,
+  (Function) compress_close,
+  (Function) 0,
+  (Function) compress_report,
+  /* 4 - 7 */
+  (Function) compress_to_file,
+  (Function) compress_file,
+  (Function) uncompress_to_file,
+  (Function) uncompress_file,
+  /* 8 - 11 */
+  (Function) is_compressedfile,
+};
+
+char *start(Function *global_funcs)
+{
+  global = global_funcs;
+  compressed_files	= 0;
+  uncompressed_files	= 0;
+  share_compressed	= 0;
+  compress_level	= 9;
+
+  module_register(MODULE_NAME, compress_table, 1, 1);
+  if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+    module_undepend(MODULE_NAME);
+    return _("This module needs eggdrop1.7.0 or later");
+  }
+  share_funcs = module_depend(MODULE_NAME, "share", 2, 3);
+  if (!share_funcs) {
+    module_undepend(MODULE_NAME);
+    return _("You need share module version 2.3 to use the compress module.");
+  }
+
+  uff_addtable(compress_uff_table);
+  add_tcl_ints(my_tcl_ints);
+  add_tcl_commands(my_tcl_cmds);
+  add_help_reference("compress.help");
+  return NULL;
+}
Index: eggdrop1.7/modules/compress/compress.h
diff -u /dev/null eggdrop1.7/modules/compress/compress.h:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/compress/compress.h	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,52 @@
+/*
+ * compress.h -- part of src/mod/compress.mod
+ *   header file for the zlib compression module
+ *
+ * $Id: compress.h,v 1.1 2001/10/27 16:34:48 ite Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_COMPRESS_COMPRESS_H
+#define _EGG_MOD_COMPRESS_COMPRESS_H
+
+#define UFF_COMPRESS	0x000008	/* Compress the user file	*/
+
+typedef enum {
+  COMPF_ERROR,		/* Compression failed.			*/
+  COMPF_SUCCESS		/* Compression succeeded.		*/
+} compf_result;
+
+typedef enum {
+  COMPF_UNCOMPRESSED,	/* File is uncompressed.		*/
+  COMPF_COMPRESSED,	/* File is compressed.			*/
+  COMPF_FAILED		/* Could not determine file type.	*/
+} compf_type;
+
+
+#ifndef MAKING_COMPRESS
+/* 4 - 7 */
+# define compress_to_file   ((int (*)(char *, char *, int))(compress_funcs[4]))
+# define compress_file	    ((int (*)(char *, int))(compress_funcs[5]))
+# define uncompress_to_file ((int (*)(char *, char *))(uncompress_funcs[6]))
+# define uncompress_file    ((int (*)(char *))(uncompress_funcs[7]))
+/* 8 - 11 */
+# define is_compressedfile  ((int (*)(char *))(uncompress_funcs[8]))
+#endif /* !MAKING_COMPRESS */
+
+#endif /* !_EGG_MOD_COMPRESS_COMPRESS_H */
Index: eggdrop1.7/modules/compress/help/set/compress.help
diff -u /dev/null eggdrop1.7/modules/compress/help/set/compress.help:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/compress/help/set/compress.help	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,7 @@
+%{help=set share-compressed}%{+n}
+###  %bset share-compressed%b <0/1>
+  Specifies wether to allow compressed sending of user files. The user files
+  are compressed with the compression level defined in `compress-level'.
+%{help=set compress-level}%{+n}
+###  %bset compress-level%b <0-9>
+  Defines the default compression level used.
Index: eggdrop1.7/modules/compress/modinfo
diff -u /dev/null eggdrop1.7/modules/compress/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/compress/modinfo	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,8 @@
+DESC:The compress module provides support for file compression. This
+DESC:allows the bot to transfer compressed user files and therefore
+DESC:saves a significant amount of bandwidth, especially on very active
+DESC:hubs.  The module depends on the zlib library.
+DESC:
+DESC:You will want to ENABLE this module if you have enough CPU time
+DESC:to spare.
+DEPENDS:share
Index: eggdrop1.7/modules/compress/tclcompress.c
diff -u /dev/null eggdrop1.7/modules/compress/tclcompress.c:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/compress/tclcompress.c	Sat Oct 27 11:34:48 2001
@@ -0,0 +1,117 @@
+/*
+ * tclcompress.c -- part of compress.mod
+ *   contains all tcl functions
+ *
+ * Written by Fabian Knittel <fknittel at gmx.de>
+ *
+ * $Id: tclcompress.c,v 1.1 2001/10/27 16:34:48 ite Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+
+#define NEXT_ARG	{ curr_arg++; argc--; }
+
+static int tcl_compress_file STDVAR
+{
+  int	 mode_num = compress_level, result, curr_arg = 1;
+  char	*fn_src = NULL, *fn_target = NULL;
+
+  BADARGS(2, 5, " ?options...? src-file ?target-file?");
+  while ((argc > 1) && ((argv[curr_arg])[0] == '-')) {
+    if (!strcmp(argv[curr_arg], "-level")) {
+      argc--;
+      if (argc <= 1) {
+	Tcl_AppendResult(irp, "option `-level' needs parameter", NULL);
+	return TCL_ERROR;
+      }
+      curr_arg++;
+      mode_num = atoi(argv[curr_arg]);
+    } else {
+      Tcl_AppendResult(irp, "unknown option `", argv[curr_arg], "'", NULL);
+      return TCL_ERROR;
+    }
+    NEXT_ARG;
+  }
+  if (argc <= 1) {
+    Tcl_AppendResult(irp, "expecting src-filename as parameter", NULL);
+    return TCL_ERROR;
+  }
+  fn_src = argv[curr_arg];
+  NEXT_ARG;
+  if (argc > 1) {
+    fn_target = argv[curr_arg];
+    NEXT_ARG;
+  }
+  if (argc > 1) {
+    Tcl_AppendResult(irp, "trailing, unexpected parameter to command", NULL);
+    return TCL_ERROR;
+  }
+
+  if (fn_target)
+    result = compress_to_file(fn_src, fn_target, mode_num);
+  else
+    result = compress_file(fn_src, mode_num);
+
+  if (result)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_uncompress_file STDVAR
+{
+  int	 result;
+
+  BADARGS(2, 3, " src-file ?target-file?");
+  if (argc == 2)
+    result = uncompress_file(argv[1]);
+  else
+    result = uncompress_to_file(argv[1], argv[2]);
+
+  if (result)
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_iscompressed STDVAR
+{
+  int	 result;
+
+  BADARGS(2, 2, " compressed-file");
+  result = is_compressedfile(argv[1]);
+  if (result == COMPF_UNCOMPRESSED)
+    Tcl_AppendResult(irp, "0", NULL);	/* Uncompressed.	*/
+  else if (result == COMPF_COMPRESSED)
+    Tcl_AppendResult(irp, "1", NULL);	/* Compressed.		*/
+  else
+    Tcl_AppendResult(irp, "2", NULL);	/* Failed to detect.	*/
+  return TCL_OK;
+}
+
+
+static tcl_cmds my_tcl_cmds[] =
+{
+  {"compressfile",	tcl_compress_file},
+  {"uncompressfile",	tcl_uncompress_file},
+  {"iscompressed",	tcl_iscompressed},
+  {NULL,		NULL}
+};
Index: eggdrop1.7/modules/console/.cvsignore
diff -u /dev/null eggdrop1.7/modules/console/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/console/.cvsignore	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,8 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
Index: eggdrop1.7/modules/console/Makefile.am
diff -u /dev/null eggdrop1.7/modules/console/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/console/Makefile.am	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,15 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:49 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+pkglib_LTLIBRARIES	= console.la
+console_la_SOURCES	= console.c
+console_la_LDFLAGS	= -module -avoid-version -no-undefined
+console_la_LIBADD	= @TCL_LIBS@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/console/console.c
diff -u /dev/null eggdrop1.7/modules/console/console.c:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/console/console.c	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,383 @@
+/*
+ * console.c -- part of console.mod
+ *   saved console settings based on console.tcl
+ *   by cmwagner/billyjoe/D. Senso
+ *
+ * $Id: console.c,v 1.1 2001/10/27 16:34:49 ite Exp $
+ */
+/*
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define MODULE_NAME "console"
+#define MAKING_CONSOLE
+#include "lib/eggdrop/module.h"
+#include <stdlib.h>
+#include "console.h"
+
+#define start console_LTX_start
+
+static Function *global = NULL;
+static int console_autosave = 0;
+static int force_channel = 0;
+static int info_party = 0;
+
+static bind_table_t *BT_dcc, *BT_chon;
+
+struct console_info {
+  char *channel;
+  int conflags;
+  int stripflags;
+  int echoflags;
+  int page;
+  int conchan;
+};
+
+static struct user_entry_type USERENTRY_CONSOLE;
+
+
+static int console_unpack(struct userrec *u, struct user_entry *e)
+{
+  struct console_info *ci = malloc(sizeof(struct console_info));
+  char *par, *arg;
+
+  par = e->u.list->extra;
+  arg = newsplit(&par);
+  malloc_strcpy(ci->channel, arg);
+  arg = newsplit(&par);
+  ci->conflags = logmodes(arg);
+  arg = newsplit(&par);
+  ci->stripflags = stripmodes(arg);
+  arg = newsplit(&par);
+  ci->echoflags = (arg[0] == '1') ? 1 : 0;
+  arg = newsplit(&par);
+  ci->page = atoi(arg);
+  arg = newsplit(&par);
+  ci->conchan = atoi(arg);
+  list_type_kill(e->u.list);
+  e->u.extra = ci;
+  return 1;
+}
+
+static int console_pack(struct userrec *u, struct user_entry *e)
+{
+  char work[1024];
+  struct console_info *ci;
+
+  ci = (struct console_info *) e->u.extra;
+
+  simple_sprintf(work, "%s %s %s %d %d %d",
+		     ci->channel, masktype(ci->conflags),
+		     stripmasktype(ci->stripflags), ci->echoflags,
+		     ci->page, ci->conchan);
+
+  e->u.list = malloc(sizeof(struct list_type));
+  e->u.list->next = NULL;
+  malloc_strcpy(e->u.list->extra, work);
+
+  free(ci->channel);
+  free(ci);
+  return 1;
+}
+
+static int console_kill(struct user_entry *e)
+{
+  struct console_info *i = e->u.extra;
+
+  free(i->channel);
+  free(i);
+  free(e);
+  return 1;
+}
+
+static int console_write_userfile(FILE *f, struct userrec *u,
+				  struct user_entry *e)
+{
+  struct console_info *i = e->u.extra;
+
+  if (fprintf(f, "--CONSOLE %s %s %s %d %d %d\n",
+	      i->channel, masktype(i->conflags),
+	      stripmasktype(i->stripflags), i->echoflags,
+	      i->page, i->conchan) == EOF)
+    return 0;
+  return 1;
+}
+
+static int console_set(struct userrec *u, struct user_entry *e, void *buf)
+{
+  struct console_info *ci = (struct console_info *) e->u.extra;
+
+  if (!ci && !buf)
+    return 1;
+
+  if (ci != buf) {
+    if (ci) {
+      free(ci->channel);
+      free(ci);
+    }
+    ci = e->u.extra = buf;
+  }
+
+  /* Note: Do not share console info */
+  return 1;
+}
+
+static int console_tcl_get(Tcl_Interp *irp, struct userrec *u,
+			   struct user_entry *e, int argc, char **argv)
+{
+  char work[1024];
+  struct console_info *i = e->u.extra;
+
+  simple_sprintf(work, "%s %s %s %d %d %d",
+		 i->channel, masktype(i->conflags),
+		 stripmasktype(i->stripflags), i->echoflags,
+		 i->page, i->conchan);
+  Tcl_AppendResult(irp, work, NULL);
+  return TCL_OK;
+}
+
+static int console_tcl_set(Tcl_Interp *irp, struct userrec *u,
+		    struct user_entry *e, int argc, char **argv)
+{
+  struct console_info *i = e->u.extra;
+  int l;
+
+  BADARGS(4, 9, " handle CONSOLE channel flags strip echo page conchan");
+  if (!i)
+    i = calloc(1, sizeof(struct console_info));
+  if (i->channel)
+    free(i->channel);
+  l = strlen(argv[3]);
+  if (l > 80)
+    l = 80;
+  i->channel = malloc(l + 1);
+  strncpy(i->channel, argv[3], l);
+  i->channel[l] = 0;
+  if (argc > 4) {
+    i->conflags = logmodes(argv[4]);
+    if (argc > 5) {
+      i->stripflags = stripmodes(argv[5]);
+      if (argc > 6) {
+	i->echoflags = (argv[6][0] == '1') ? 1 : 0;
+	if (argc > 7) {
+	  i->page = atoi(argv[7]);
+	  if (argc > 8)
+	    i->conchan = atoi(argv[8]);
+	}
+      }
+    }
+  }
+  set_user(&USERENTRY_CONSOLE, u, i);
+  return TCL_OK;
+}
+
+static void console_display(int idx, struct user_entry *e)
+{
+  struct console_info *i = e->u.extra;
+
+  if (dcc[idx].user && (dcc[idx].user->flags & USER_MASTER)) {
+    dprintf(idx, "  %s\n", _("Saved Console Settings:"));
+    dprintf(idx, "    %s %s\n", _("Channel:"), i->channel);
+    dprintf(idx, "    %s %s, %s %s, %s %s\n", _("Console flags:"),
+    	masktype(i->conflags), _("Strip flags:"),
+	    stripmasktype(i->stripflags), _("Echo:"),
+	    i->echoflags ? _("yes") : _("no"));
+    dprintf(idx, "    %s %d, %s %s%d\n", _("Page setting:"), i->page,
+            _("Console channel:"), (i->conchan < 100000) ? "" : "*",
+            i->conchan % 100000);
+  }
+}
+
+static int console_dupuser(struct userrec *new, struct userrec *old,
+			   struct user_entry *e)
+{
+  struct console_info *i = e->u.extra, *j;
+
+  j = malloc(sizeof(struct console_info));
+  memcpy(j, i, sizeof(struct console_info));
+
+  malloc_strcpy(j->channel, i->channel);
+  return set_user(e->type, new, j);
+}
+
+static struct user_entry_type USERENTRY_CONSOLE =
+{
+  NULL,				/* always 0 ;) */
+  NULL,
+  console_dupuser,
+  console_unpack,
+  console_pack,
+  console_write_userfile,
+  console_kill,
+  NULL,
+  console_set,
+  console_tcl_get,
+  console_tcl_set,
+  console_display,
+  "CONSOLE"
+};
+
+static int console_chon(char *handle, int idx)
+{
+  struct console_info *i = get_user(&USERENTRY_CONSOLE, dcc[idx].user);
+
+  if (dcc[idx].type == &DCC_CHAT) {
+    if (i) {
+      if (i->channel && i->channel[0])
+	strcpy(dcc[idx].u.chat->con_chan, i->channel);
+      dcc[idx].u.chat->con_flags = i->conflags;
+      dcc[idx].u.chat->strip_flags = i->stripflags;
+      if (i->echoflags)
+	dcc[idx].status |= STAT_ECHO;
+      else
+	dcc[idx].status &= ~STAT_ECHO;
+      if (i->page) {
+	dcc[idx].status |= STAT_PAGE;
+	dcc[idx].u.chat->max_line = i->page;
+	if (!dcc[idx].u.chat->line_count)
+	  dcc[idx].u.chat->current_lines = 0;
+      }
+      dcc[idx].u.chat->channel = i->conchan;
+    } else if (force_channel > -1)
+      dcc[idx].u.chat->channel = force_channel;
+    if ((dcc[idx].u.chat->channel >= 0) &&
+	(dcc[idx].u.chat->channel < GLOBAL_CHANS)) {
+      botnet_send_join_idx(idx, -1);
+      check_tcl_chjn(botnetnick, dcc[idx].nick, dcc[idx].u.chat->channel,
+		     geticon(idx), dcc[idx].sock, dcc[idx].host);
+    }
+    if (info_party) {
+      char *p = get_user(&USERENTRY_INFO, dcc[idx].user);
+
+      if (p) {
+	if (dcc[idx].u.chat->channel >= 0) {
+	    char x[1024];
+
+	    chanout_but(-1, dcc[idx].u.chat->channel,
+			"*** [%s] %s\n", dcc[idx].nick, p);
+	    simple_sprintf(x, "[%s] %s", dcc[idx].nick, p);
+	    botnet_send_chan(-1, botnetnick, NULL,
+			     dcc[idx].u.chat->channel, x);
+	}
+      }
+    }
+  }
+  return 0;
+}
+
+static int console_store(struct userrec *u, int idx, char *par)
+{
+  struct console_info *i = get_user(&USERENTRY_CONSOLE, u);
+
+  if (!i)
+    i = calloc(1, sizeof(struct console_info));
+  if (i->channel)
+    free(i->channel);
+  malloc_strcpy(i->channel, dcc[idx].u.chat->con_chan);
+  i->conflags = dcc[idx].u.chat->con_flags;
+  i->stripflags = dcc[idx].u.chat->strip_flags;
+  i->echoflags = (dcc[idx].status & STAT_ECHO) ? 1 : 0;
+  if (dcc[idx].status & STAT_PAGE)
+    i->page = dcc[idx].u.chat->max_line;
+  else
+    i->page = 0;
+  i->conchan = dcc[idx].u.chat->channel;
+  if (par) {
+    dprintf(idx, "%s\n", _("Saved your Console Settings:"));
+    dprintf(idx, "  %s %s\n", i->channel, _("Channel:"));
+    dprintf(idx, "  %s %s, %s %s, %s %s\n", _("Console flags:"),
+	    masktype(i->conflags), _("Strip flags:"),
+	    stripmasktype(i->stripflags), _("Echo:"),
+	    i->echoflags ? _("yes") : _("no"));
+    dprintf(idx, "  %s %d, %s %d\n", _("Page setting:"), i->page,
+            _("Console channel:"), i->conchan);
+  }
+  set_user(&USERENTRY_CONSOLE, u, i);
+  return 0;
+}
+
+/* cmds.c:cmd_console calls this, better than chof bind - drummer,07/25/1999 */
+static int console_dostore(int idx)
+{
+  if (console_autosave)
+    console_store(dcc[idx].user, idx, NULL);
+  return 0;
+}
+
+static tcl_ints myints[] =
+{
+  {"console-autosave",	&console_autosave,	0},
+  {"force-channel",	&force_channel,		0},
+  {"info-party",	&info_party,		0},
+  {NULL,		NULL,			0}
+};
+
+static cmd_t mychon[] =
+{
+  {"*",		"",	console_chon,		"console:chon"},
+  {NULL,	NULL,	NULL,			NULL}
+};
+
+static cmd_t mydcc[] =
+{
+  {"store",	"",	console_store,		NULL},
+  {NULL,	NULL,	NULL,			NULL}
+};
+
+static char *console_close()
+{
+  if (BT_chon) rem_builtins2(BT_chon, mychon);
+  if (BT_dcc) rem_builtins2(BT_dcc, mydcc);
+  rem_tcl_ints(myints);
+  rem_help_reference("console.help");
+  del_entry_type(&USERENTRY_CONSOLE);
+  module_undepend(MODULE_NAME);
+  return NULL;
+}
+
+EXPORT_SCOPE char *start();
+
+static Function console_table[] =
+{
+  (Function) start,
+  (Function) console_close,
+  (Function) NULL,
+  (Function) NULL,
+  (Function) console_dostore,
+};
+
+char *start(Function * global_funcs)
+{
+  global = global_funcs;
+
+  module_register(MODULE_NAME, console_table, 1, 1);
+  if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+    module_undepend(MODULE_NAME);
+    return "This module requires eggdrop1.7.0 or later";
+  }
+
+  BT_dcc = find_bind_table2("dcc");
+  BT_chon = find_bind_table2("chon");
+
+  if (BT_dcc) add_builtins2(BT_dcc, mydcc);
+  if (BT_chon) add_builtins2(BT_chon, mychon);
+  add_tcl_ints(myints);
+  add_help_reference("console.help");
+  USERENTRY_CONSOLE.get = def_get;
+  add_entry_type(&USERENTRY_CONSOLE);
+  return NULL;
+}
Index: eggdrop1.7/modules/console/console.h
diff -u /dev/null eggdrop1.7/modules/console/console.h:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/console/console.h	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,27 @@
+/*
+ * console.h -- part of console.mod
+ *
+ * $Id: console.h,v 1.1 2001/10/27 16:34:49 ite Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_CONSOLE_CONSOLE_H
+#define _EGG_MOD_CONSOLE_CONSOLE_H
+
+#endif				/* _EGG_MOD_CONSOLE_CONSOLE_H */
Index: eggdrop1.7/modules/console/help/console.help
diff -u /dev/null eggdrop1.7/modules/console/help/console.help:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/console/help/console.help	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,21 @@
+%{help=console module}
+###  help for the %bconsole module%b
+   The console module provides the ability to store your console
+   settings so that next time you use the party line they are set.
+   You can use the %b'.store'%b command at any time to store
+   it, or it may be configured to save automatically.
+%{+n}
+   The following tcl variables are avaliable for setting up the
+   console module: (%b'.help set <variable>'%b for more info)
+     %bconsole-autosave%b   %bforce-channel%b
+     %binfo-party%b
+%{help=store}
+###  %bstore%b
+   Stores you console settings so that they are set automatically
+   next time you join the party line.
+
+See Also: set console-autosave
+%{help=all}
+###  commands for the %bconsole module%b
+  %bstore%b
+
Index: eggdrop1.7/modules/console/help/set/console.help
diff -u /dev/null eggdrop1.7/modules/console/help/set/console.help:1.1
--- /dev/null	Sat Oct 27 11:35:15 2001
+++ eggdrop1.7/modules/console/help/set/console.help	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,15 @@
+%{help=set console-autosave}%{+n}
+###  %bset console-autosave%b 0/1
+   When this flag is set, a users console settings are automatically
+   saved for the next time the user joins the party line.
+
+See Also: store
+%{help=set force-channel}%{+n}
+###  %bset force-channel%b <chan#>
+   This specifies the channel to dumb users who have no stored
+   console settings on, 0 is the party line, 1-99999 are global
+   chat lines & 10000-19999 are bot-only chat lines.
+%{help=set info-party}%{+n}
+###  %bset info-party%b 0/1
+   When set, this flag causes a users info line to be displayed
+   to the *current* chat channel when they join it.
Index: eggdrop1.7/modules/console/modinfo
diff -u /dev/null eggdrop1.7/modules/console/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/console/modinfo	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,4 @@
+DESC:This module provides storage of console settings when you exit the bot
+DESC:(or .store).
+DESC:
+DESC:Normally you will want to ENABLE this module.
Index: eggdrop1.7/modules/ctcp/.cvsignore
diff -u /dev/null eggdrop1.7/modules/ctcp/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/ctcp/.cvsignore	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,8 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
Index: eggdrop1.7/modules/ctcp/Makefile.am
diff -u /dev/null eggdrop1.7/modules/ctcp/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/ctcp/Makefile.am	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,15 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:49 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+pkglib_LTLIBRARIES	= ctcp.la
+ctcp_la_SOURCES		= ctcp.c
+ctcp_la_LDFLAGS		= -module -avoid-version -no-undefined
+ctcp_la_LIBADD		= @TCL_LIBS@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/ctcp/ctcp.c
diff -u /dev/null eggdrop1.7/modules/ctcp/ctcp.c:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/ctcp/ctcp.c	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,251 @@
+/*
+ * ctcp.c -- part of ctcp.mod
+ *   all the ctcp handling (except DCC, it's special ;)
+ *
+ * $Id: ctcp.c,v 1.1 2001/10/27 16:34:49 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define MODULE_NAME "ctcp"
+#define MAKING_CTCP
+#include "ctcp.h"
+#include "lib/eggdrop/module.h"
+#include "modules/server/server.h"
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#define start ctcp_LTX_start
+
+/* Import this bind table from server.mod */
+static bind_table_t *BT_ctcp;
+
+static Function *global = NULL, *server_funcs = NULL;
+
+static char ctcp_version[121];
+static char ctcp_finger[121];
+static char ctcp_userinfo[121];
+static int ctcp_mode = 0;
+
+
+static int ctcp_FINGER(char *nick, char *uhost, struct userrec *u,
+		       char *object, char *keyword, char *text)
+{
+  if (ctcp_mode != 1 && ctcp_finger[0])
+    simple_sprintf(ctcp_reply, "%s\001FINGER %s\001", ctcp_reply, ctcp_finger);
+  return 1;
+}
+
+static int ctcp_ECHOERR(char *nick, char *uhost, struct userrec *u,
+			char *object, char *keyword, char *text)
+{
+  if (ctcp_mode != 1 && strlen(text) <= 80)
+    simple_sprintf(ctcp_reply, "%s\001%s %s\001", ctcp_reply, keyword, text);
+  return 1;
+}
+
+static int ctcp_PING(char *nick, char *uhost, struct userrec *u,
+		     char *object, char *keyword, char *text)
+{
+  int atr = u ? u->flags : 0;
+
+  if ((ctcp_mode != 1 || (atr & USER_OP)) && strlen(text) <= 80)
+      simple_sprintf(ctcp_reply, "%s\001%s %s\001", ctcp_reply, keyword, text);
+  return 1;
+}
+
+static int ctcp_VERSION(char *nick, char *uhost, struct userrec *u,
+			char *object, char *keyword, char *text)
+{
+  if (ctcp_mode != 1 && ctcp_version[0])
+    simple_sprintf(ctcp_reply, "%s\001VERSION %s\001", ctcp_reply,
+		   ctcp_version);
+  return 1;
+}
+
+static int ctcp_USERINFO(char *nick, char *uhost, struct userrec *u,
+			 char *object, char *keyword, char *text)
+{
+  if (ctcp_mode != 1 && ctcp_userinfo[0])
+    simple_sprintf(ctcp_reply, "%s\001USERINFO %s\001", ctcp_reply,
+		   ctcp_userinfo);
+  return 1;
+}
+
+static int ctcp_CLIENTINFO(char *nick, char *uhosr, struct userrec *u,
+			   char *object, char *keyword, char *msg)
+{
+  char *p = NULL;
+
+  if (ctcp_mode == 1)
+    return 1;
+  else if (!msg[0])
+    p = CLIENTINFO;
+  else if (!strcasecmp(msg, "sed"))
+    p = CLIENTINFO_SED;
+  else if (!strcasecmp(msg, "version"))
+    p = CLIENTINFO_VERSION;
+  else if (!strcasecmp(msg, "clientinfo"))
+    p = CLIENTINFO_CLIENTINFO;
+  else if (!strcasecmp(msg, "userinfo"))
+    p = CLIENTINFO_USERINFO;
+  else if (!strcasecmp(msg, "errmsg"))
+    p = CLIENTINFO_ERRMSG;
+  else if (!strcasecmp(msg, "finger"))
+    p = CLIENTINFO_FINGER;
+  else if (!strcasecmp(msg, "time"))
+    p = CLIENTINFO_TIME;
+  else if (!strcasecmp(msg, "action"))
+    p = CLIENTINFO_ACTION;
+  else if (!strcasecmp(msg, "dcc"))
+    p = CLIENTINFO_DCC;
+  else if (!strcasecmp(msg, "utc"))
+    p = CLIENTINFO_UTC;
+  else if (!strcasecmp(msg, "ping"))
+    p = CLIENTINFO_PING;
+  else if (!strcasecmp(msg, "echo"))
+    p = CLIENTINFO_ECHO;
+  if (p == NULL) {
+    simple_sprintf(ctcp_reply,
+	       "%s\001ERRMSG CLIENTINFO: %s is not a valid function\001",
+		   ctcp_reply, msg);
+  } else
+    simple_sprintf(ctcp_reply, "%s\001CLIENTINFO %s\001", ctcp_reply, p);
+  return 1;
+}
+
+static int ctcp_TIME(char *nick, char *uhost, struct userrec *u, char *object,
+		     char *keyword, char *text)
+{
+  char tms[25];
+
+  if (ctcp_mode == 1)
+    return 1;
+  strncpy(tms, ctime(&now), 24);
+  tms[24] = 0;
+  simple_sprintf(ctcp_reply, "%s\001TIME %s\001", ctcp_reply, tms);
+  return 1;
+}
+
+static int ctcp_CHAT(char *nick, char *uhost, struct userrec *u, char *object,
+		     char *keyword, char *text)
+{
+  int atr = u ? u->flags : 0, i;
+
+  if ((atr & (USER_PARTY | USER_XFER)) ||
+      ((atr & USER_OP) && !require_p)) {
+
+    for (i = 0; i < dcc_total; i++) {
+      if ((dcc[i].type->flags & DCT_LISTEN) &&
+	  (!strcmp(dcc[i].nick, "(telnet)") ||
+	   !strcmp(dcc[i].nick, "(users)"))) {
+        /* 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 %s %u\001\n",
+		nick, getlocaladdr(-1), dcc[i].port);
+        return 1;
+      }
+    }
+    simple_sprintf(ctcp_reply, "%s\001ERROR no telnet port\001",
+                   ctcp_reply);
+  }
+  return 1;
+}
+
+static cmd_t myctcp[] =
+{
+  {"FINGER",		"",	ctcp_FINGER,		NULL},
+  {"ECHO",		"",	ctcp_ECHOERR,		NULL},
+  {"PING",		"",	ctcp_PING,		NULL},
+  {"ERRMSG",		"",	ctcp_ECHOERR,		NULL},
+  {"VERSION",		"",	ctcp_VERSION,		NULL},
+  {"USERINFO",		"",	ctcp_USERINFO,		NULL},
+  {"CLIENTINFO",	"",	ctcp_CLIENTINFO,	NULL},
+  {"TIME",		"",	ctcp_TIME,		NULL},
+  {"CHAT",		"",	ctcp_CHAT,		NULL},
+  {NULL,		NULL,	NULL,			NULL}
+};
+
+static tcl_strings mystrings[] =
+{
+  {"ctcp-version",	ctcp_version,	120,	0},
+  {"ctcp-finger",	ctcp_finger,	120,	0},
+  {"ctcp-userinfo",	ctcp_userinfo,	120,	0},
+  {NULL,		NULL,		0,	0}
+};
+
+static tcl_ints myints[] =
+{
+  {"ctcp-mode",		&ctcp_mode},
+  {NULL,		NULL}
+};
+
+static char *ctcp_close()
+{
+  rem_tcl_strings(mystrings);
+  rem_tcl_ints(myints);
+  if (BT_ctcp) rem_builtins2(BT_ctcp, myctcp);
+  rem_help_reference("ctcp.help");
+  module_undepend(MODULE_NAME);
+  return NULL;
+}
+
+EXPORT_SCOPE char *start();
+
+static Function ctcp_table[] =
+{
+  (Function) start,
+  (Function) ctcp_close,
+  (Function) NULL,
+  (Function) NULL,
+};
+
+char *start(Function * global_funcs)
+{
+  global = global_funcs;
+
+  module_register(MODULE_NAME, ctcp_table, 1, 0);
+  if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+    module_undepend(MODULE_NAME);
+    return "This module requires eggdrop1.7.0 or later";
+  }
+  if (!(server_funcs = module_depend(MODULE_NAME, "server", 1, 0))) {
+    module_undepend(MODULE_NAME);
+    return "You need the server module to use the ctcp module.";
+  }
+  add_tcl_strings(mystrings);
+  add_tcl_ints(myints);
+  BT_ctcp = find_bind_table2("ctcp");
+  if (BT_ctcp) add_builtins2(BT_ctcp, myctcp);
+  add_help_reference("ctcp.help");
+  if (!ctcp_version[0]) {
+    strncpy(ctcp_version, ver, 120);
+    ctcp_version[120] = 0;
+  }
+  if (!ctcp_finger[0]) {
+    strncpy(ctcp_finger, ver, 120);
+    ctcp_finger[120] = 0;
+  }
+  if (!ctcp_userinfo[0]) {
+    strncpy(ctcp_userinfo, ver, 120);
+    ctcp_userinfo[120] = 0;
+  }
+  return NULL;
+}
Index: eggdrop1.7/modules/ctcp/ctcp.h
diff -u /dev/null eggdrop1.7/modules/ctcp/ctcp.h:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/ctcp/ctcp.h	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,43 @@
+/*
+ * ctcp.h -- part of ctcp.mod
+ *   all the defines for ctcp.c
+ *
+ * $Id: ctcp.h,v 1.1 2001/10/27 16:34:49 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_CTCP_CTCP_H
+#define _EGG_MOD_CTCP_CTCP_H
+
+#define CLIENTINFO "SED VERSION CLIENTINFO USERINFO ERRMSG FINGER TIME ACTION DCC UTC PING ECHO  :Use CLIENTINFO <COMMAND> to get more specific information"
+#define CLIENTINFO_SED "SED contains simple_encrypted_data"
+#define CLIENTINFO_VERSION "VERSION shows client type, version and environment"
+#define CLIENTINFO_CLIENTINFO "CLIENTINFO gives information about available CTCP commands"
+#define CLIENTINFO_USERINFO "USERINFO returns user settable information"
+#define CLIENTINFO_ERRMSG "ERRMSG returns error messages"
+#define CLIENTINFO_FINGER "FINGER shows real name, login name and idle time of user"
+#define CLIENTINFO_TIME "TIME tells you the time on the user's host"
+#define CLIENTINFO_ACTION "ACTION contains action descriptions for atmosphere"
+#define CLIENTINFO_DCC "DCC requests a direct_client_connection"
+#define CLIENTINFO_UTC "UTC substitutes the local timezone"
+#define CLIENTINFO_PING "PING returns the arguments it receives"
+#define CLIENTINFO_ECHO "ECHO returns the arguments it receives"
+
+#endif				/* _EGG_MOD_CTCP_CTCP_H */
Index: eggdrop1.7/modules/ctcp/help/set/ctcp.help
diff -u /dev/null eggdrop1.7/modules/ctcp/help/set/ctcp.help:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/ctcp/help/set/ctcp.help	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,23 @@
+%{help=ctcp module}%{+n}
+###  help on the %bctcp module%b
+   This module provides all the normal ctcp replies for the bot,
+   including handling of dcc chat. The following settings allow
+   you to configure it:
+      %bctcp-finger%b  %bctcp-userinfo%b  %bctcp-version%b
+   (use %b'.help set <variable>'%b for more info)
+%{help=set ctcp-finger}%{+n}
+###  %bset ctcp-finger%b <text>
+   specifies the response to send to a CTCP FINGER request.  one
+   example is:
+      Robey (robey at wc130), idle 0 seconds
+see also: set ctcp-version, set ctcp-userinfo
+%{help=set ctcp-userinfo}%{+n}
+###  %bset ctcp-userinfo%b <text>
+   specifies the response to send to a CTCP USERINFO request.
+see also: set ctcp-version, set ctcp-finger
+%{help=set ctcp-version}%{+n}
+###  %bset ctcp-version%b <text>
+   specifies the response to send to a CTCP VERSION request.  one
+   example is:
+      ircII 2.8 Linux 1.2.4 :ircii 2.6: almost there...
+see also: set ctcp-finger, set ctcp-userinfo
Index: eggdrop1.7/modules/ctcp/modinfo
diff -u /dev/null eggdrop1.7/modules/ctcp/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/ctcp/modinfo	Sat Oct 27 11:34:49 2001
@@ -0,0 +1,4 @@
+DESC:This provides the normal ctcp replies that you'd expect.
+DESC:
+DESC:Enabling this module is recommended for bots active on IRC.
+DEPENDS:server
Index: eggdrop1.7/modules/filesys/.cvsignore
diff -u /dev/null eggdrop1.7/modules/filesys/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/.cvsignore	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,8 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
Index: eggdrop1.7/modules/filesys/Makefile.am
diff -u /dev/null eggdrop1.7/modules/filesys/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/Makefile.am	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,15 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:50 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+pkglib_LTLIBRARIES	= filesys.la
+filesys_la_SOURCES	= filesys.c
+filesys_la_LDFLAGS	= -module -avoid-version -no-undefined
+filesys_la_LIBADD	= @TCL_LIBS@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/filesys/dbcompat.c
diff -u /dev/null eggdrop1.7/modules/filesys/dbcompat.c:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/dbcompat.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,293 @@
+/*
+ * dbcompat.c -- part of filesys.mod
+ *   Compability functions to convert older DBs to the newest version.
+ *
+ * Written for filedb3 by Fabian Knittel <fknittel at gmx.de>
+ *
+ * $Id: dbcompat.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/* Convert '.files' db to newest db. Returns 1 if a valid file is
+ * found and could be converted, 0 in all other cases.
+ *
+ * '.files' is a text file which contains file records built up in the
+ * following way:
+ *      '<filename> <nick> <tm> <gots>\n'
+ *      '- <comment>\n'
+ *      '- <comment>\n'
+ *      ...
+ */
+static int convert_old_files(char *path, char *newfiledb)
+{
+  FILE *f, *fdb;
+  char *s, *fn, *nick, *tm, *s1;
+  filedb_entry *fdbe = NULL;
+  int in_file = 0, i;
+  struct stat st;
+
+  s = malloc(strlen(path) + 8);
+  sprintf(s, "%s/.files", path);
+  f = fopen(s, "r");
+  free_null(s);
+  if (f == NULL)
+    return 0;
+
+  fdb = fopen(newfiledb, "w+b");
+  if (!fdb) {
+    putlog(LOG_MISC, "(!) Can't create filedb in %s", newfiledb);
+    fclose(f);
+    return 0;
+  }
+  lockfile(fdb);
+  lockfile(f);
+  filedb_initdb(fdb);
+
+  putlog(LOG_FILES, "*", _("Converting filesystem image in %s ..."), path);
+  /* Scan contents of .files and painstakingly create .filedb entries */
+  while (!feof(f)) {
+    s = malloc(121);
+    s1 = s;
+    fgets(s, 120, f);
+    if (s[strlen(s) - 1] == '\n')
+      s[strlen(s) - 1] = 0;
+    if (!feof(f)) {
+      fn = newsplit(&s1);
+      rmspace(fn);
+      if ((fn[0]) && (fn[0] != ';') && (fn[0] != '#')) {
+	/* Not comment */
+	if (fn[0] == '-') {
+	  /* Adjust comment for current file */
+	  if (in_file && fdbe) {
+	    rmspace(s);
+	    if (fdbe->desc) {
+	      fdbe->desc = realloc(fdbe->desc,
+				    strlen(fdbe->desc) + strlen(s) + 2);
+	      strcat(fdbe->desc, "\n");
+	    } else
+	      fdbe->desc = malloc(strlen(s) + 2);
+	    strcat(fdbe->desc, s);
+	  }
+	} else {
+	  if (fdbe) {
+	    /* File pending. Write to DB */
+	    filedb_addfile(fdb, fdbe);
+	    free_fdbe(&fdbe);
+	  }
+	  fdbe = malloc_fdbe();
+	  in_file = 1;
+	  nick = newsplit(&s1);
+	  rmspace(nick);
+	  tm = newsplit(&s1);
+	  rmspace(tm);
+	  rmspace(s1);
+	  i = strlen(fn) - 1;
+	  if (fn[i] == '/')
+	    fn[i] = 0;
+	  realloc_strcpy(fdbe->filename, fn);
+	  realloc_strcpy(fdbe->uploader, nick);
+	  fdbe->gots = atoi(s1);
+	  fdbe->uploaded = atoi(tm);
+	  sprintf(s, "%s/%s", path, fn);
+	  if (stat(s, &st) == 0) {
+	    /* File is okay */
+	    if (S_ISDIR(st.st_mode)) {
+	      fdbe->stat |= FILE_DIR;
+	      if (nick[0] == '+') {
+		char x[100];
+		/* Only do global flags, it's an old one */
+		struct flag_record fr = {FR_GLOBAL, 0, 0, 0, 0, 0};
+
+		break_down_flags(nick + 1, &fr, NULL);
+		build_flags(x, &fr, NULL);
+		/* We only want valid flags */
+		realloc_strcpy(fdbe->flags_req, x);
+	      }
+	    }
+	    fdbe->size = st.st_size;
+	  } else
+	    in_file = 0;	/* skip */
+	}
+      }
+    }
+    free_null(s);
+  }
+  if (fdbe) {
+    /* File pending. Write to DB */
+    filedb_addfile(fdb, fdbe);
+    free_fdbe(&fdbe);
+  }
+  fseek(fdb, 0L, SEEK_END);
+  unlockfile(f);
+  unlockfile(fdb);
+  fclose(fdb);
+  fclose(f);
+  return 1;
+}
+
+/* Reads file DB v1 entries from fdb_s and saves them to fdb_t in
+ * v3 format.
+ */
+static void convert_version1(FILE *fdb_s, FILE *fdb_t)
+{
+  long where;
+  filedb1 fdb1;
+
+  fseek(fdb_s, 0L, SEEK_SET);
+  while (!feof(fdb_s)) {
+    where = ftell(fdb_s);
+    fread(&fdb1, sizeof(filedb1), 1, fdb_s);
+    if (!feof(fdb_s)) {
+      if (!(fdb1.stat & FILE_UNUSED)) {
+	filedb_entry *fdbe = malloc_fdbe();
+
+	fdbe->stat = fdb1.stat;
+	if (fdb1.filename[0])
+	  realloc_strcpy(fdbe->filename, fdb1.filename);
+	if (fdb1.desc[0])
+	  realloc_strcpy(fdbe->desc, fdb1.desc);
+	if (fdb1.uploader[0])
+	  realloc_strcpy(fdbe->uploader, fdb1.uploader);
+	if (fdb1.flags_req[0])
+	  realloc_strcpy(fdbe->flags_req, fdb1.flags_req);
+	fdbe->uploaded = fdb1.uploaded;
+	fdbe->size = fdb1.size;
+	fdbe->gots = fdb1.gots;
+	if (fdb1.sharelink[0])
+	  realloc_strcpy(fdbe->sharelink, fdb1.sharelink);
+	filedb_addfile(fdb_s, fdbe);
+	free_fdbe(&fdbe);
+      }
+    }
+  }
+}
+
+/* Reads file DB v2 entries from fdb_s and saves them to fdb_t in
+ * v3 format.
+ */
+static void convert_version2(FILE *fdb_s, FILE *fdb_t)
+{
+  long where;
+  filedb2 fdb2;
+
+  fseek(fdb_s, 0L, SEEK_SET);
+  while (!feof(fdb_s)) {
+    where = ftell(fdb_s);
+    fread(&fdb2, sizeof(filedb2), 1, fdb_s);
+    if (!feof(fdb_s)) {
+      if (!(fdb2.stat & FILE_UNUSED)) {
+	filedb_entry *fdbe = malloc_fdbe();
+
+	fdbe->stat = fdb2.stat;
+	if (fdb2.filename[0])
+	  realloc_strcpy(fdbe->filename, fdb2.filename);
+	if (fdb2.desc[0])
+	  realloc_strcpy(fdbe->desc, fdb2.desc);
+	if (fdb2.chname[0])
+	  realloc_strcpy(fdbe->chan, fdb2.chname);
+	if (fdb2.uploader[0])
+	  realloc_strcpy(fdbe->uploader, fdb2.uploader);
+	if (fdb2.flags_req[0])
+	  realloc_strcpy(fdbe->flags_req, fdb2.flags_req);
+	fdbe->uploaded = fdb2.uploaded;
+	fdbe->size = fdb2.size;
+	fdbe->gots = fdb2.gots;
+	if (fdb2.sharelink[0])
+	  realloc_strcpy(fdbe->sharelink, fdb2.sharelink);
+	filedb_addfile(fdb_t, fdbe);
+	free_fdbe(&fdbe);
+      }
+    }
+  }
+}
+
+/* Converts old versions of the filedb to the newest. Returns 1 if all went
+ * well and otherwise 0. The new db is first written to a temporary place
+ * and then moved over to the original db's position.
+ *
+ * Note: Unfortunately there is a small time-frame where aren't locking the
+ *       DB, but want to replace it with a new one, using movefile().
+ *       TODO: Copy old db to tmp file and then build the new db directly
+ *             in the original file. This solves the tiny locking problem.
+ *
+ * Also remember to check the returned *fdb_s on failure, as it could be
+ * NULL.
+ */
+static int convert_old_db(FILE **fdb_s, char *filedb)
+{
+  filedb_top fdbt;
+  FILE *fdb_t;
+  int ret = 0;					/* Default to 'failure'	*/
+
+  filedb_readtop(*fdb_s, &fdbt);
+  /* Old DB version? */
+  if (fdbt.version > 0 && fdbt.version < FILEDB_VERSION3) {
+    char *tempdb;
+
+    putlog(LOG_MISC, "*", "Converting old filedb %s to newest format.",
+	   filedb);
+    /* Create temp DB name */
+    tempdb = malloc(strlen(filedb) + 5);
+    simple_sprintf(tempdb, "%s-tmp", filedb);
+
+    fdb_t = fopen(tempdb, "w+b");		/* Open temp DB		*/
+    if (fdb_t) {
+      filedb_initdb(fdb_t);			/* Initialise new DB	*/
+
+      /* Convert old database to new one, saving
+       * in temporary db file
+       */
+      if (fdbt.version == FILEDB_VERSION1)
+        convert_version1(*fdb_s, fdb_t);	/* v1 -> v3		*/
+      else
+	convert_version2(*fdb_s, fdb_t);	/* v2 -> v3		*/
+
+      unlockfile(*fdb_s);
+      fclose(fdb_t);
+      fclose(*fdb_s);
+
+      /* Move over db to new location */
+      if (movefile(tempdb, filedb))
+	putlog(LOG_MISC, "*", "(!) Moving file db from %s to %s failed.",
+			tempdb, filedb);
+
+      *fdb_s = fopen(filedb, "r+b");		/* Reopen new db	*/
+      if (*fdb_s) {
+	lockfile(*fdb_s);
+	/* Now we should have recreated the original situation,
+	 * with the file pointer just pointing to the new version
+	 * of the DB instead of the original one.
+	 */
+	ret = 1;
+      } else
+        putlog(LOG_MISC, "*", "(!) Reopening db %s failed.", filedb);
+    }
+    free_null(tempdb);
+  /* Database already at the newest version? */
+  } else if (fdbt.version == FILEDB_VERSION3) {
+    ret = 1;					/* Always successfull	*/
+  /* Unknown version? */
+  } else {
+    putlog(LOG_MISC, "*", "(!) Unknown db version: %d", fdbt.version);
+  }
+  if (!ret)
+    putlog(LOG_MISC, "*", "Conversion of filedb %s failed.", filedb);
+  return ret;
+}
Index: eggdrop1.7/modules/filesys/dbcompat.h
diff -u /dev/null eggdrop1.7/modules/filesys/dbcompat.h:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/dbcompat.h	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,89 @@
+/*
+ * dbcompat.h -- part of filesys.mod
+ *   this header file contains old db formats which are
+ *   needed or converting old dbs to the new format.
+ *
+ * Written for filedb3 by Fabian Knittel <fknittel at gmx.de>
+ *
+ * $Id: dbcompat.h,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_FILESYS_DBCOMPAT_H
+#define _EGG_MOD_FILESYS_DBCOMPAT_H
+
+/*
+ *    DB entry structures for v1 and v2
+ */
+
+/* Structure for file database (per directory) */
+struct filler1 {
+  char			xxx[1 + 61 + 301 + 10 + 11 + 61];
+  unsigned short int	uuu[2];
+  time_t		ttt[2];
+  unsigned int		iii[2];
+};
+
+typedef struct {
+  char			version;
+  unsigned short int	stat;		/* Misc */
+  time_t		timestamp;	/* Last time this db was updated */
+  char			filename[61];
+  char			desc[301];	/* Should be plenty */
+  char			uploader[10];	/* Where this file came from */
+  unsigned char		flags_req[11];	/* Access flags required */
+  time_t		uploaded;	/* Time it was uploaded */
+  unsigned int		size;		/* File length */
+  unsigned short int	gots;		/* Times the file was downloaded */
+  char			sharelink[61];	/* Points to where? */
+  char			unused[512 - sizeof(struct filler1)];
+} filedb1;
+
+struct filler2 {
+  char			xxx[1 + 61 + 186 + 81 + 33 + 22 + 61];
+  unsigned short int	uuu[2];
+  time_t		ttt[2];
+  unsigned int		iii[1];
+};
+
+typedef struct {
+  char			version;
+  unsigned short int	stat;		/* Misc */
+  time_t		timestamp;	/* Last time this db was updated */
+  char			filename[61];
+  char			desc[186];	/* Should be plenty - shrink it, we
+					 * Need the  space :) */
+  char			chname[81];	/* Channel for chan spec stuff */
+  char			uploader[33];	/* Where this file came from */
+  char			flags_req[22];	/* Access flags required */
+  time_t		uploaded;	/* Time it was uploaded */
+  unsigned int		size;		/* File length */
+  unsigned short int	gots;		/* Times the file was downloaded */
+  char			sharelink[61];	/* Points to where? */
+  char			unused[512 - sizeof(struct filler2)];
+} filedb2;
+
+/*
+ *    Prototypes
+ */
+
+static int convert_old_db(FILE **fdb, char *s);
+static int convert_old_files(char *npath, char *s);
+
+#endif				/* _EGG_MOD_FILESYS_DBCOMPAT.H */
Index: eggdrop1.7/modules/filesys/filedb3.c
diff -u /dev/null eggdrop1.7/modules/filesys/filedb3.c:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/filedb3.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,1300 @@
+/*
+ * filedb3.c -- part of filesys.mod
+ *   low level functions for file db handling
+ *
+ * Rewritten by Fabian Knittel <fknittel at gmx.de>
+ *
+ * $Id: filedb3.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/*  filedb structure:
+ *
+ *  +---------------+                           _
+ *  | filedb_top    |                           _|  DB header
+ *  |---------------|
+ *  .               .
+ *  .               .
+ *  | ...           |
+ *  |---------------|     _                     _
+ *  | filedb_header |     _|  Static length      |
+ *  |- --- - --- - -|     _                      |
+ *  | filename      |      |                     |
+ *  |- - - - - - - -|      |                     |
+ *  | description   |      |                     |
+ *  |- - - - - - - -|      |  Dynamic length     |  Complete entry
+ *  | channel name  |      |                     |
+ *  |- - - - - - - -|      |                     |
+ *  | uploader      |      |                     |
+ *  |- - - - - - - -|      |                     |
+ *  | flags_req     |      |                     |
+ *  |- - - - - - - -|      |                     |
+ *  | share link    |      |                     |
+ *  |- - - - - - - -|      |                     |
+ *  | buffer        |     _|                    _|
+ *  |---------------|
+ *  | ...           |
+ *  .               .
+ *  .               .
+ *
+ *  To know how long the complete entry is, you need to read out the
+ *  header first. This concept allows us to have unlimited filename
+ *  lengths, unlimited description lengths, etc.
+ *
+ *  Buffer is an area which doesn't contain any information and which
+ *  is just skipped. It only serves as a placeholder to allow entries
+ *  which shrink in size to maintain their position without messing up
+ *  the position of all following entries.
+ */
+
+/* Number of databases opened simultaneously. More than 2 is
+ * be unnormal.
+ */
+static int count = 0;
+
+
+/*
+ *   Memory management helper functions
+ */
+
+/* Frees a filedb entry and all it's elements.
+ */
+static void free_fdbe(filedb_entry **fdbe)
+{
+  if (!fdbe || !*fdbe)
+    return;
+  if ((*fdbe)->filename)
+    free_null((*fdbe)->filename);
+  if ((*fdbe)->desc)
+    free_null((*fdbe)->desc);
+  if ((*fdbe)->sharelink)
+    free_null((*fdbe)->sharelink);
+  if ((*fdbe)->chan)
+    free_null((*fdbe)->chan);
+  if ((*fdbe)->uploader)
+    free_null((*fdbe)->uploader);
+  if ((*fdbe)->flags_req)
+    free_null((*fdbe)->flags_req);
+  free_null(*fdbe);
+}
+
+/* Allocates and initialises a filedb entry
+ */
+static filedb_entry *_malloc_fdbe(char *file, int line)
+{
+  filedb_entry *fdbe = NULL;
+
+  fdbe = calloc(1, sizeof(filedb_entry));
+
+  /* Mark as new, will be overwritten if necessary. */
+  fdbe->_type = TYPE_NEW;
+  return fdbe;
+}
+
+
+/*
+ *  File locking
+ */
+
+/* Locks the file, using fcntl. Execution is locked until we
+ * have exclusive access to it.
+ */
+static void lockfile(FILE *fdb)
+{
+  struct flock fl;
+
+  fl.l_type = F_WRLCK;
+  fl.l_start = 0;
+  fl.l_whence = SEEK_SET;
+  fl.l_len = 0;
+  fcntl(fileno(fdb), F_SETLKW, &fl);
+}
+
+/* Unlocks the file using fcntl.
+ */
+static void unlockfile(FILE * f)
+{
+  struct flock fl;
+
+  fl.l_type = F_UNLCK;
+  fl.l_start = 0;
+  fl.l_whence = SEEK_SET;
+  fl.l_len = 0;
+  fcntl(fileno(f), F_SETLKW, &fl);
+}
+
+
+/*
+ *   filedb functions
+ */
+
+/* Copies the DB header to fdbt or just positions the file
+ * position pointer onto the first entry after the db header.
+ */
+static int filedb_readtop(FILE *fdb, filedb_top *fdbt)
+{
+  if (fdbt) {
+    /* Read header */
+    fseek(fdb, 0L, SEEK_SET);
+    if (feof(fdb))
+      return 0;
+    fread(fdbt, 1, sizeof(filedb_top), fdb);
+  } else
+    fseek(fdb, sizeof(filedb_top), SEEK_SET);
+  return 1;
+}
+
+/* Writes the DB header to the top of the filedb.
+ */
+static int filedb_writetop(FILE *fdb, filedb_top *fdbt)
+{
+  fseek(fdb, 0L, SEEK_SET);
+  fwrite(fdbt, 1, sizeof(filedb_top), fdb);
+  return 1;
+}
+
+/* Deletes the entry at 'pos'. It just adjusts the header to
+ * mark it as 'unused' and to assign all dynamic space to
+ * the buffer.
+ */
+static int filedb_delfile(FILE *fdb, long pos)
+{
+  filedb_header fdh;
+
+  fseek(fdb, pos, SEEK_SET);			/* Go to start of entry	*/
+  if (feof(fdb))
+    return 0;
+  fread(&fdh, 1, sizeof(filedb_header), fdb);	/* Read header		*/
+  fdh.stat = FILE_UNUSED;
+
+  /* Assign all available space to buffer. Simplifies
+   * space calculation later on.
+   */
+  fdh.buffer_len += filedb_tot_dynspace(fdh);
+  filedb_zero_dynspace(fdh);
+
+  fseek(fdb, pos, SEEK_SET);			/* Go back to start	*/
+  fwrite(&fdh, 1, sizeof(filedb_header), fdb);	/* Write new header	*/
+  return 1;
+}
+
+/* Searches for a suitable place to write an entry which uses tot
+ * bytes for dynamic data.
+ *
+ *  * If there is no such existing entry, it just points to the
+ *    end of the DB.
+ *  * If it finds an empty entry and it has enough space to fit
+ *    in another entry, we split it up and only use the space we
+ *    really need.
+ *
+ * Note: We can assume that empty entries' dyn_lengths are zero.
+ *       Therefore we only need to check buf_len.
+ */
+static filedb_entry *filedb_findempty(FILE *fdb, int tot)
+{
+  filedb_entry *fdbe;
+
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_getfile(fdb, ftell(fdb), GET_HEADER);
+  while (fdbe) {
+    /* Found an existing, empty entry? */
+    if ((fdbe->stat & FILE_UNUSED) && (fdbe->buf_len >= tot)) {
+      /* Do we have enough space to split up the entry to form
+       * another empty entry? That way we would use the space
+       * more efficiently.
+       */
+      if (fdbe->buf_len > (tot + sizeof(filedb_header) + FILEDB_ESTDYN)) {
+	filedb_entry *fdbe_oe;
+
+	/* Create new entry containing the additional space */
+	fdbe_oe = malloc_fdbe();
+	fdbe_oe->stat = FILE_UNUSED;
+	fdbe_oe->pos = fdbe->pos + sizeof(filedb_header) + tot;
+	fdbe_oe->buf_len = fdbe->buf_len - tot - sizeof(filedb_header);
+	filedb_movefile(fdb, fdbe_oe->pos, fdbe_oe);
+	free_fdbe(&fdbe_oe);
+
+	/* Cut down buf_len of entry as the rest is now used in the new
+	 * entry.
+	 */
+	fdbe->buf_len = tot;
+      }
+      return fdbe;
+    }
+    free_fdbe(&fdbe);
+    fdbe = filedb_getfile(fdb, ftell(fdb), GET_HEADER);
+  }
+
+  /* No existing entries, so create new entry at end of DB instead. */
+  fdbe = malloc_fdbe();
+  fseek(fdb, 0L, SEEK_END);
+  fdbe->pos = ftell(fdb);
+  return fdbe;
+}
+
+/* Updates or creates entries and information in the filedb.
+ *
+ *   * If the new entry is the same size or smaller than the original
+ *     one, we use the old position and just adjust the buffer length
+ *     apropriately.
+ *   * If the entry is completely _new_ or if the entry's new size is
+ *     _bigger_ than the old one, we search for a new position which
+ *     suits our needs.
+ *
+ * Note that the available space also includes the buffer.
+ *
+ * The file pointer will _always_ position directly after the updated
+ * entry.
+ */
+static int _filedb_updatefile(FILE *fdb, long pos, filedb_entry *fdbe,
+			      int update, char *file, int line)
+{
+  filedb_header fdh;
+  int reposition = 0;
+  int ndyntot, odyntot, nbuftot, obuftot;
+
+  memset(&fdh, 0, sizeof(filedb_header));
+  fdh.uploaded = fdbe->uploaded;
+  fdh.size = fdbe->size;
+  fdh.stat = fdbe->stat;
+  fdh.gots = fdbe->gots;
+
+  /* Only add the buffer length if the buffer is not empty. Otherwise it
+   * would result in lots of 1 byte entries which actually don't contain
+   * any data.
+   */
+  if (fdbe->filename)
+    fdh.filename_len = strlen(fdbe->filename) + 1;
+  if (fdbe->desc)
+    fdh.desc_len = strlen(fdbe->desc) + 1;
+  if (fdbe->chan)
+    fdh.chan_len = strlen(fdbe->chan) + 1;
+  if (fdbe->uploader)
+    fdh.uploader_len = strlen(fdbe->uploader) + 1;
+  if (fdbe->flags_req)
+    fdh.flags_req_len = strlen(fdbe->flags_req) + 1;
+  if (fdbe->sharelink)
+    fdh.sharelink_len = strlen(fdbe->sharelink) + 1;
+
+  odyntot = fdbe->dyn_len;		/* Old length of dynamic data	*/
+  obuftot = fdbe->buf_len;		/* Old length of spare space	*/
+  ndyntot = filedb_tot_dynspace(fdh);	/* New length of dynamic data	*/
+  nbuftot = obuftot;
+
+  if (fdbe->_type == TYPE_EXIST) {
+    /* If we only update the header, we don't need to worry about
+     * sizes and just use the old place (i.e. the place pointed
+     * to by pos).
+     */
+    if (update < UPDATE_ALL) {
+      /* Unless forced to it, we ignore new buffer sizes if we do not
+       * run in UPDATE_ALL mode.
+       */
+      if (update != UPDATE_SIZE) {
+        ndyntot = odyntot;
+        nbuftot = obuftot;
+      }
+    } else {
+      /* If we have a given/preferred position */
+      if ((pos != POS_NEW) &&
+	 /* and if our new size is smaller than the old size, we
+	  * just adjust the buffer length and still use the same
+	  * position
+	  */
+          (ndyntot <= (odyntot + obuftot))) {
+	nbuftot = (odyntot + obuftot) - ndyntot;
+      } else {
+	/* If we have an existing position, but the new entry doesn't
+	 * fit into it's old home, we need to delete it before
+	 * repositioning.
+	 */
+	if (pos != POS_NEW)
+          filedb_delfile(fdb, pos);
+	reposition = 1;
+      }
+    }
+  } else {
+    fdbe->_type = TYPE_EXIST;		/* Update type			*/
+    reposition = 1;
+  }
+
+  /* Search for a new home */
+  if (reposition) {
+    filedb_entry *n_fdbe;
+
+    n_fdbe = filedb_findempty(fdb, filedb_tot_dynspace(fdh));
+    fdbe->pos = pos = n_fdbe->pos;
+    /* If we create a new entry (instead of using an existing one),
+     * buf_len is zero
+     */
+    if (n_fdbe->buf_len > 0)
+      /* Note: empty entries have dyn_len set to zero, so we only
+       *       need to consider buf_len.
+       */
+      nbuftot = n_fdbe->buf_len - ndyntot;
+    else
+      nbuftot = 0;
+    free_fdbe(&n_fdbe);
+  }
+
+  /* Set length of dynamic data and buffer */
+  fdbe->dyn_len = ndyntot;
+  fdbe->buf_len = fdh.buffer_len = nbuftot;
+
+  /* Write header */
+  fseek(fdb, pos, SEEK_SET);
+  fwrite(&fdh, 1, sizeof(filedb_header), fdb);
+  /* Write dynamic data */
+  if (update == UPDATE_ALL) {
+    if (fdbe->filename)
+      fwrite(fdbe->filename, 1, fdh.filename_len, fdb);
+    if (fdbe->desc)
+      fwrite(fdbe->desc, 1, fdh.desc_len, fdb);
+    if (fdbe->chan)
+      fwrite(fdbe->chan, 1, fdh.chan_len, fdb);
+    if (fdbe->uploader)
+      fwrite(fdbe->uploader, 1, fdh.uploader_len, fdb);
+    if (fdbe->flags_req)
+      fwrite(fdbe->flags_req, 1, fdh.flags_req_len, fdb);
+    if (fdbe->sharelink)
+      fwrite(fdbe->sharelink, 1, fdh.sharelink_len, fdb);
+  } else
+    fseek(fdb, ndyntot, SEEK_CUR);	/* Skip over dynamic data */
+  fseek(fdb, nbuftot, SEEK_CUR);	/* Skip over buffer	  */
+  return 0;
+}
+
+/* Moves an existing file entry, saved in fdbe to a new position.
+ */
+static int _filedb_movefile(FILE *fdb, long pos, filedb_entry *fdbe,
+			    char *file, int line)
+{
+  fdbe->_type = TYPE_EXIST;
+  _filedb_updatefile(fdb, pos, fdbe, UPDATE_ALL, file, line);
+  return 0;
+}
+
+/* Adds a completely new file.
+ */
+static int _filedb_addfile(FILE *fdb, filedb_entry *fdbe, char *file, int line)
+{
+  fdbe->_type = TYPE_NEW;
+  _filedb_updatefile(fdb, POS_NEW, fdbe, UPDATE_ALL, file, line);
+  return 0;
+}
+
+/* Short-cut macro to read an entry from disc to memory. Only
+ * useful for filedb_getfile().
+ */
+#define filedb_read(fdb, entry, len)	\
+{					\
+  if ((len) > 0) {			\
+    (entry) = malloc((len));		\
+    fread((entry), 1, (len), (fdb));	\
+  }					\
+}
+
+/* Reads an entry from the fildb at the specified position. The
+ * amount of information returned depends on the get flag.
+ * It always positions the file position pointer exactly behind
+ * the entry.
+ */
+static filedb_entry *_filedb_getfile(FILE *fdb, long pos, int get,
+				     char *file, int line)
+{
+  filedb_entry *fdbe;
+  filedb_header fdh;
+
+  /* Read header */
+  fseek(fdb, pos, SEEK_SET);
+  fread(&fdh, 1, sizeof(filedb_header), fdb);
+  if (feof(fdb))
+    return NULL;
+
+  /* Allocate memory for file db entry */
+  fdbe = _malloc_fdbe(file, line);
+
+  /* Optimisation: maybe use memcpy here? */
+  fdbe->uploaded = fdh.uploaded;
+  fdbe->size = fdh.size;
+  fdbe->stat = fdh.stat;
+  fdbe->gots = fdh.gots;
+
+  fdbe->buf_len = fdh.buffer_len;
+  fdbe->dyn_len = filedb_tot_dynspace(fdh);
+  fdbe->pos = pos;			/* Save position		*/
+  fdbe->_type = TYPE_EXIST;		/* Entry exists in DB		*/
+
+  /* This is useful for cases where we don't read the rest of the
+   * data, but need to know whether the file is a link.
+   */
+  if (fdh.sharelink_len > 0)
+    fdbe->stat |= FILE_ISLINK;
+  else
+    fdbe->stat &= ~FILE_ISLINK;
+
+  /* Read additional data from db */
+  if (get >= GET_FILENAME) {
+    filedb_read(fdb, fdbe->filename, fdh.filename_len);
+  } else
+    fseek(fdb, fdh.filename_len, SEEK_CUR);
+  if (get < GET_FULL || (fdh.stat & FILE_UNUSED))
+    fseek(fdb, filedb_tot_dynspace(fdh) - fdh.filename_len, SEEK_CUR);
+  else if (get == GET_FULL) {
+    filedb_read(fdb, fdbe->desc, fdh.desc_len);
+    filedb_read(fdb, fdbe->chan, fdh.chan_len);
+    filedb_read(fdb, fdbe->uploader, fdh.uploader_len);
+    filedb_read(fdb, fdbe->flags_req, fdh.flags_req_len);
+    filedb_read(fdb, fdbe->sharelink, fdh.sharelink_len);
+  }
+  fseek(fdb, fdh.buffer_len, SEEK_CUR);	/* Skip buffer			*/
+  return fdbe;				/* Return the ready structure	*/
+}
+
+/* Searches the filedb for a file matching the specified mask, starting
+ * at position 'pos'. The first matching file is returned.
+ */
+static filedb_entry *_filedb_matchfile(FILE *fdb, long pos, char *match,
+				       char *file, int line)
+{
+  filedb_entry *fdbe = NULL;
+
+  fseek(fdb, pos, SEEK_SET);
+  while (!feof(fdb))
+  {
+    pos = ftell(fdb);
+    fdbe = filedb_getfile(fdb, pos, GET_FILENAME);
+    if (fdbe) {
+      if (!(fdbe->stat & FILE_UNUSED) &&	    /* Not unused?	   */
+	  wild_match_file(match, fdbe->filename)) { /* Matches our mask?   */
+        free_fdbe(&fdbe);
+        fdbe = _filedb_getfile(fdb, pos, GET_FULL,
+			       file, line);	    /* Save all data now   */
+        return fdbe;
+      }
+      free_fdbe(&fdbe);
+    }
+  }
+  return NULL;
+}
+
+/* Throws out all entries marked as unused, by walking through the
+ * filedb and moving all good ones towards the top.
+ */
+static void filedb_cleanup(FILE *fdb)
+{
+  long oldpos, newpos, temppos;
+  filedb_entry *fdbe = NULL;
+
+  filedb_readtop(fdb, NULL);				/* Skip DB header  */
+  newpos = temppos = oldpos = ftell(fdb);
+  fseek(fdb, oldpos, SEEK_SET);				/* Go to beginning */
+  while (!feof(fdb))					/* Loop until EOF  */
+  {
+    fdbe = filedb_getfile(fdb, oldpos, GET_HEADER);	/* Read header	   */
+    if (fdbe) {
+      if (fdbe->stat & FILE_UNUSED) {			/* Found dirt!	   */
+        free_fdbe(&fdbe);
+	while (!feof(fdb)) {				/* Loop until EOF  */
+	  newpos = ftell(fdb);
+	  fdbe = filedb_getfile(fdb, newpos, GET_FULL);	/* Read next entry */
+	  if (!fdbe)
+	    break;
+	  if (!(fdbe->stat & FILE_UNUSED)) {		/* Not unused?	   */
+	    temppos = ftell(fdb);
+	    filedb_movefile(fdb, oldpos, fdbe);		/* Move to top	   */
+	    oldpos = ftell(fdb);
+	    fseek(fdb, temppos, SEEK_SET);
+	  }
+	  free_fdbe(&fdbe);
+	}
+      } else {
+        free_fdbe(&fdbe);
+	oldpos = ftell(fdb);
+      }
+    }
+  }
+  ftruncate(fileno(fdb), oldpos);			/* Shorten file	   */
+}
+
+/* Merges empty entries to one big entry, if they directly
+ * follow each other. Does this for the complete DB.
+ * This considerably speeds up several actions performed on
+ * the db.
+ */
+static void filedb_mergeempty(FILE *fdb)
+{
+  filedb_entry *fdbe_t, *fdbe_i;
+  int modified;
+
+  filedb_readtop(fdb, NULL);
+  while (!feof(fdb))
+  {
+    fdbe_t = filedb_getfile(fdb, ftell(fdb), GET_HEADER);
+    if (fdbe_t) {
+      if (fdbe_t->stat & FILE_UNUSED) {
+	modified = 0;
+	fdbe_i = filedb_getfile(fdb, ftell(fdb), GET_HEADER);
+	while (fdbe_i) {
+	  /* Is this entry in use? */
+	  if (!(fdbe_i->stat & FILE_UNUSED))
+	    break;	/* It is, exit loop. */
+
+	  /* Woohoo, found an empty entry. Append it's space to
+	   * our target entry's buffer space.
+	   */
+	  fdbe_t->buf_len += sizeof(filedb_header) + fdbe_i->buf_len;
+	  modified++;
+	  free_fdbe(&fdbe_i);
+	  /* Get next file entry */
+	  fdbe_i = filedb_getfile(fdb, ftell(fdb), GET_HEADER);
+	}
+
+	/* Did we exit the loop because of a used entry? */
+	if (fdbe_i) {
+	  free_fdbe(&fdbe_i);
+	  /* Did we find any empty entries before? */
+	  if (modified)
+	    filedb_updatefile(fdb, fdbe_t->pos, fdbe_t, UPDATE_SIZE);
+	/* ... or because we hit EOF? */
+	} else {
+	  /* Truncate trailing empty entries and exit. */
+	  ftruncate(fileno(fdb), fdbe_t->pos);
+	  free_fdbe(&fdbe_t);
+	  return;
+	}
+      }
+      free_fdbe(&fdbe_t);
+    }
+  }
+}
+
+/* Returns the filedb entry matching the filename 'fn' in
+ * directory 'dir'.
+ */
+static filedb_entry *filedb_getentry(char *dir, char *fn)
+{
+  FILE *fdb;
+  filedb_entry *fdbe = NULL;
+
+  fdb = filedb_open(dir, 0);
+  if (fdb) {
+    filedb_readtop(fdb, NULL);
+    fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
+    filedb_close(fdb);
+  }
+  return fdbe;
+}
+
+/* Initialises a new filedb by writing the db header, etc.
+ */
+static void filedb_initdb(FILE *fdb)
+{
+  filedb_top fdbt;
+
+  fdbt.version = FILEDB_NEWEST_VER;
+  fdbt.timestamp = now;
+  filedb_writetop(fdb, &fdbt);
+}
+
+static void filedb_timestamp(FILE * fdb)
+{
+  filedb_top fdbt;
+
+  filedb_readtop(fdb, &fdbt);
+  fdbt.timestamp = now;
+  filedb_writetop(fdb, &fdbt);
+}
+
+/* Updates the specified filedb in several ways:
+ *
+ * 1. Adds all new files from the directory to the db.
+ * 2. Removes all stale entries from the db.
+ * 3. Optimises the db.
+ */
+static void filedb_update(char *path, FILE *fdb, int sort)
+{
+  struct dirent *dd = NULL;
+  struct stat st;
+  filedb_entry *fdbe = NULL;
+  DIR *dir = NULL;
+  long where = 0;
+  char *name = NULL, *s = NULL;
+
+  /*
+   * FIRST: make sure every real file is in the database
+   */
+  dir = opendir(path);
+  if (dir == NULL) {
+    putlog(LOG_MISC, "*", _("filedb-update: cant open directory!"));
+    return;
+  }
+  dd = readdir(dir);
+  while (dd != NULL) {
+    realloc_strcpy(name, dd->d_name);
+    if (name[0] != '.') {
+      s = malloc(strlen(path) + strlen(name) + 2);
+      sprintf(s, "%s/%s", path, name);
+      stat(s, &st);
+      free_null(s);
+      filedb_readtop(fdb, NULL);
+      fdbe = filedb_matchfile(fdb, ftell(fdb), name);
+      if (!fdbe) {
+	/* new file! */
+	fdbe = malloc_fdbe();
+	realloc_strcpy(fdbe->filename, name);
+	realloc_strcpy(fdbe->uploader, botnetnick);
+	fdbe->uploaded = now;
+	fdbe->size = st.st_size;
+	if (S_ISDIR(st.st_mode))
+	  fdbe->stat |= FILE_DIR;
+	filedb_addfile(fdb, fdbe);
+      } else if (fdbe->size != st.st_size) {
+	/* update size if needed */
+	fdbe->size = st.st_size;
+	filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_HEADER);
+      }
+      free_fdbe(&fdbe);
+    }
+    dd = readdir(dir);
+  }
+  if (name)
+    free_null(name);
+  closedir(dir);
+
+  /*
+   * SECOND: make sure every db file is real
+   */
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_getfile(fdb, ftell(fdb), GET_FILENAME);
+  while (fdbe) {
+    where = ftell(fdb);
+    if (!(fdbe->stat & FILE_UNUSED) && !(fdbe->stat & FILE_ISLINK) &&
+	fdbe->filename) {
+      s = malloc(strlen(path) + 1 + strlen(fdbe->filename) + 1);
+      sprintf(s, "%s/%s", path, fdbe->filename);
+      if (stat(s, &st) != 0)
+	/* gone file */
+	filedb_delfile(fdb, fdbe->pos);
+      free_null(s);
+    }
+    free_fdbe(&fdbe);
+    fdbe = filedb_getfile(fdb, where, GET_FILENAME);
+  }
+
+  /*
+   * THIRD: optimise database
+   *
+   * Instead of sorting, we only clean up the db, because sorting is now
+   * done on-the-fly when we display the file list.
+   */
+  if (sort)
+    filedb_cleanup(fdb);			/* Cleanup DB		*/
+  filedb_timestamp(fdb);			/* Write new timestamp	*/
+}
+
+/* Converts all slashes to dots. Returns an allocated buffer, so
+ * do not forget to FREE it after use.
+ */
+static char *make_point_path(char *path)
+{
+    char *s2 = NULL, *p = NULL;
+
+    realloc_strcpy(s2, path);
+    if (s2[strlen(s2) - 1] == '/')
+      s2[strlen(s2) - 1] = 0;		/* remove trailing '/' */
+    p = s2;
+    while (*p++)
+      if (*p == '/')
+	*p = '.';
+    return s2;
+}
+
+/* Opens the filedb responsible to the specified directory.
+ */
+static FILE *filedb_open(char *path, int sort)
+{
+  char *s, *npath;
+  FILE *fdb;
+  filedb_top fdbt;
+  struct stat st;
+
+  if (count >= 2)
+    putlog(LOG_MISC, "*", "(@) warning: %d open filedb's", count);
+  npath = malloc(strlen(dccdir) + strlen(path) + 1);
+  simple_sprintf(npath, "%s%s", dccdir, path);
+  /* Use alternate filename if requested */
+  if (filedb_path[0]) {
+    char *s2;
+
+    s2 = make_point_path(path);
+    s = malloc(strlen(filedb_path) + strlen(s2) + 8);
+    simple_sprintf(s, "%sfiledb.%s", filedb_path, s2);
+    free_null(s2);
+  } else {
+    s = malloc(strlen(npath) + 10);
+    simple_sprintf(s, "%s/.filedb", npath);
+  }
+  fdb = fopen(s, "r+b");
+  if (!fdb) {
+    if (convert_old_files(npath, s)) {
+      fdb = fopen(s, "r+b");
+      if (fdb == NULL) {
+	putlog(LOG_MISC, "*", _("(!) Broken convert to filedb in %s"), npath);
+	free_null(s);
+	free_null(npath);
+        return NULL;
+      }
+      lockfile(fdb);
+      filedb_update(npath, fdb, sort);
+      count++;
+      free_null(s);
+      free_null(npath);
+      return fdb;
+    } else {
+      filedb_top fdbt;
+
+      /* Create new database and fix it up */
+      fdb = fopen(s, "w+b");
+      if (!fdb) {
+	free_null(s);
+	free_null(npath);
+	return NULL;
+      }
+      lockfile(fdb);
+      fdbt.version = FILEDB_NEWEST_VER;
+      fdbt.timestamp = now;
+      filedb_writetop(fdb, &fdbt);
+      filedb_update(npath, fdb, sort);
+      count++;
+      free_null(s);
+      free_null(npath);
+      return fdb;
+    }
+  }
+
+  lockfile(fdb);			/* Lock it from other bots */
+  filedb_readtop(fdb, &fdbt);
+  if (fdbt.version < FILEDB_NEWEST_VER) {
+    if (!convert_old_db(&fdb, s)) {
+      /* Conversion failed. Unlock file again and error out.
+       * (convert_old_db() could have modified fdb, so check
+       * for fdb != NULL.)
+       */
+      if (fdb)
+        unlockfile(fdb);
+      free_null(npath);
+      free_null(s);
+      return NULL;
+    }
+    filedb_update(npath, fdb, sort);
+  }
+  stat(npath, &st);
+  /* Update filedb if:
+   * + it's been 6 hours since it was last updated
+   * + the directory has been visibly modified since then
+   * (6 hours may be a bit often)
+   */
+  if (sort || ((now - fdbt.timestamp) > (6 * 3600)) ||
+      (fdbt.timestamp < st.st_mtime) ||
+      (fdbt.timestamp < st.st_ctime))
+    /* File database isn't up-to-date! */
+    filedb_update(npath, fdb, sort & 1);
+  else if ((now - fdbt.timestamp) > 300)
+    filedb_mergeempty(fdb);
+
+  count++;
+  free_null(npath);
+  free_null(s);
+  return fdb;
+}
+
+/* Closes the filedb. Also removes the lock and updates the
+ * timestamp.
+ */
+static void filedb_close(FILE * fdb)
+{
+  filedb_timestamp(fdb);
+  fseek(fdb, 0L, SEEK_END);
+  count--;
+  unlockfile(fdb);
+  fclose(fdb);
+}
+
+/* Adds information for a newly added file. Actually the name
+ * is misleading, as the file is added in filedb_open() and we
+ * only add information in here.
+ */
+static void filedb_add(FILE * fdb, char *filename, char *nick)
+{
+  filedb_entry *fdbe = NULL;
+
+  filedb_readtop(fdb, NULL);
+  /* When the filedb was opened, a record was already created. */
+  fdbe = filedb_matchfile(fdb, ftell(fdb), filename);
+  if (!fdbe)
+    return;
+  free_null(fdbe->uploader);
+  realloc_strcpy(fdbe->uploader, nick);
+  fdbe->uploaded = now;
+  filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+  free_fdbe(&fdbe);
+}
+
+/* Outputs a sorted list of files/directories matching the mask,
+ * to idx.
+ */
+static void filedb_ls(FILE *fdb, int idx, char *mask, int showall)
+{
+  int  ok = 0, cnt = 0, is = 0;
+  char s1[81], *p = NULL;
+  struct flag_record user  = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  filedb_entry *fdbe  = NULL;
+  filelist_t   *flist = NULL;
+
+  flist = filelist_new();
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_getfile(fdb, ftell(fdb), GET_FULL);
+  while (fdbe) {
+    ok = 1;
+    if (fdbe->stat & FILE_UNUSED)
+      ok = 0;
+    if (ok && (fdbe->stat & FILE_DIR) && fdbe->flags_req) {
+      /* Check permissions */
+      struct flag_record req = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+      break_down_flags(fdbe->flags_req, &req, NULL);
+      get_user_flagrec(dcc[idx].user, &user, dcc[idx].u.file->chat->con_chan);
+      if (!flagrec_ok(&req, &user)) {
+	ok = 0;
+      }
+    }
+    if (ok)
+      is = 1;
+    if (ok && !wild_match_file(mask, fdbe->filename))
+      ok = 0;
+    if (ok && (fdbe->stat & FILE_HIDDEN) && !(showall))
+      ok = 0;
+    if (ok) {
+      /* Display it! */
+      if (cnt == 0) {
+	dprintf(idx, _("Filename                        Size  Sent by/Date         # Gets\n"));
+	dprintf(idx, _("------------------------------  ----  -------------------  ------\n"));
+      }
+      filelist_add(flist, fdbe->filename);
+      if (fdbe->stat & FILE_DIR) {
+	char *s2 = NULL, *s3 = NULL;
+
+	/* Too long? */
+	if (strlen(fdbe->filename) > 45) {
+	  /* Display the filename on its own line. */
+	  s2 = malloc(strlen(fdbe->filename) + 3);
+	  sprintf(s2, "%s/\n", fdbe->filename);
+	  filelist_addout(flist, s2);
+	  free_null(s2);
+	} else {
+	  s2 = malloc(strlen(fdbe->filename) + 2);
+	  sprintf(s2, "%s/", fdbe->filename);
+	}
+	/* Note: You have to keep the sprintf and the malloc statements
+	 *       in sync, i.e. always check that you allocate enough
+	 *       memory.
+	 */
+	if ((fdbe->flags_req) &&
+	    (user.global &(USER_MASTER | USER_JANITOR))) {
+	  s3 = malloc(42 + strlen(s2 ? s2 : "") + 6 +
+		       strlen(_("requires")) + strlen(fdbe->flags_req) + 1 +
+		       strlen(fdbe->chan ? fdbe->chan : "") + 1);
+	  sprintf(s3, "%-30s <DIR%s>  (%s %s%s%s)\n", s2,
+		  fdbe->stat & FILE_SHARE ?
+		  " SHARE" : "", _("requires"), fdbe->flags_req,
+		  fdbe->chan ? " " : "", fdbe->chan ? fdbe->chan : "");
+	} else {
+	  s3 = malloc(38 + strlen(s2 ? s2 : ""));
+	  sprintf(s3, "%-30s <DIR>\n", s2 ? s2 : "");
+	}
+	if (s2)
+	  free_null(s2);
+	filelist_addout(flist, s3);
+	free_null(s3);
+      } else {
+	char s2[41], t[50], *s3 = NULL, *s4;
+
+	s2[0] = 0;
+	if (showall) {
+	  if (fdbe->stat & FILE_SHARE)
+	    strcat(s2, " (shr)");
+	  if (fdbe->stat & FILE_HIDDEN)
+	    strcat(s2, " (hid)");
+	}
+	strftime(t, 10, "%d%b%Y", localtime(&fdbe->uploaded));
+	if (fdbe->size < 1024)
+	  sprintf(s1, "%5d", fdbe->size);
+	else
+	  sprintf(s1, "%4dk", (int) (fdbe->size / 1024));
+	if (fdbe->sharelink)
+	  strcpy(s1, "     ");
+	/* Too long? */
+	if (strlen(fdbe->filename) > 30) {
+	  s3 = malloc(strlen(fdbe->filename) + 2);
+	  sprintf(s3, "%s\n", fdbe->filename);
+	  filelist_addout(flist, s3);
+	  free_null(s3);
+	  /* Causes filename to be displayed on its own line */
+	} else
+	  realloc_strcpy(s3, fdbe->filename);
+	s4 = malloc(69 + strlen(s3 ? s3 : "") + strlen(s1) +
+		     strlen(fdbe->uploader) + strlen(t) + strlen(s2));
+	sprintf(s4, "%-30s %s  %-9s (%s)  %6d%s\n", s3 ? s3 : "", s1,
+		fdbe->uploader, t, fdbe->gots, s2);
+	if (s3)
+	  free_null(s3);
+	filelist_addout(flist, s4);
+	free_null(s4);
+	if (fdbe->sharelink) {
+	  s4 = malloc(9 + strlen(fdbe->sharelink));
+	  sprintf(s4, "   --> %s\n", fdbe->sharelink);
+	  filelist_addout(flist, s4);
+	  free_null(s4);
+	}
+      }
+      if (fdbe->desc) {
+	p = strchr(fdbe->desc, '\n');
+	while (p != NULL) {
+	  *p = 0;
+	  if ((fdbe->desc)[0]) {
+	    char *sd;
+
+	    sd = malloc(strlen(fdbe->desc) + 5);
+	    sprintf(sd, "   %s\n", fdbe->desc);
+	    filelist_addout(flist, sd);
+	    free_null(sd);
+	  }
+	  strcpy(fdbe->desc, p + 1);
+	  p = strchr(fdbe->desc, '\n');
+	}
+	if ((fdbe->desc)[0]) {
+	  char *sd;
+
+	  sd = malloc(strlen(fdbe->desc) + 5);
+	  sprintf(sd, "   %s\n", fdbe->desc);
+	  filelist_addout(flist, sd);
+	  free_null(sd);
+	}
+      }
+      cnt++;
+    }
+    free_fdbe(&fdbe);
+    fdbe = filedb_getfile(fdb, ftell(fdb), GET_FULL);
+  }
+  if (is == 0)
+    dprintf(idx, _("No files in this directory.\n"));
+  else if (cnt == 0)
+    dprintf(idx, _("No matching files.\n"));
+  else {
+    filelist_sort(flist);
+    filelist_idxshow(flist, idx);
+    dprintf(idx, "--- %d file%s.\n", cnt, cnt > 1 ? "s" : "");
+  }
+  filelist_free(flist);
+}
+
+static void remote_filereq(int idx, char *from, char *file)
+{
+  char *p   = NULL, *what   = NULL, *dir = NULL,
+       *s1  = NULL, *reject = NULL, *s   = NULL;
+  FILE *fdb = NULL;
+  int	i   = 0;
+  filedb_entry *fdbe = NULL;
+
+  realloc_strcpy(what, file);
+  p = strrchr(what, '/');
+  if (p) {
+    *p = 0;
+    realloc_strcpy(dir, what);
+    strcpy(what, p + 1);
+  } else {
+    realloc_strcpy(dir, "");
+  }
+  fdb = filedb_open(dir, 0);
+  if (!fdb) {
+    reject = _("Directory does not exist");
+  } else {
+    filedb_readtop(fdb, NULL);
+    fdbe = filedb_matchfile(fdb, ftell(fdb), what);
+    filedb_close(fdb);
+    if (!fdbe) {
+      reject = _("File does not exist");
+    } else {
+      if ((!(fdbe->stat & FILE_SHARE)) ||
+	  (fdbe->stat & (FILE_HIDDEN | FILE_DIR)))
+	reject = _("File is not shared");
+      else {
+	s1 = malloc(strlen(dccdir) + strlen(dir) + strlen(what) + 2);
+	/* Copy to /tmp if needed */
+	sprintf(s1, "%s%s%s%s", dccdir, dir, dir[0] ? "/" : "", what);
+	if (copy_to_tmp) {
+	  s = malloc(strlen(tempdir) + strlen(what) + 1);
+	  sprintf(s, "%s%s", tempdir, what);
+	  copyfile(s1, s);
+	} else
+	  s = s1;
+	i = raw_dcc_send(s, "*remote", _("(remote)"), s, 0);
+	if (i > 0) {
+	  wipe_tmp_filename(s, -1);
+	  reject = _("Error trying to send file");
+	}
+	if (s1 != s)
+	  free_null(s);
+	free_null(s1);
+      }
+      free_fdbe(&fdbe);
+    }
+  }
+  s1 = malloc(strlen(botnetnick) + strlen(dir) + strlen(what) + 3);
+  simple_sprintf(s1, "%s:%s/%s", botnetnick, dir, what);
+  if (reject) {
+    botnet_send_filereject(idx, s1, from, reject);
+    free_null(s1);
+    free_null(what);
+    free_null(dir);
+    return;
+  }
+  /* Grab info from dcc struct and bounce real request across net */
+  i = dcc_total - 1;
+  s = malloc(40);	/* Enough? */
+  simple_sprintf(s, "%d %u %d", iptolong(getmyip()), dcc[i].port,
+		dcc[i].u.xfer->length);
+  botnet_send_filesend(idx, s1, from, s);
+  putlog(LOG_FILES, "*", _("Remote request for /%s%s%s (sending)"), dir, dir[0] ? "/" : "", what);
+  free_null(s1);
+  free_null(s);
+  free_null(what);
+  free_null(dir);
+}
+
+
+/*
+ *    Tcl functions
+ */
+
+static void filedb_getdesc(char *dir, char *fn, char **desc)
+{
+  filedb_entry *fdbe = NULL;
+
+  fdbe = filedb_getentry(dir, fn);
+  if (fdbe) {
+    malloc_strcpy(*desc, fdbe->desc);
+    free_fdbe(&fdbe);
+  } else
+    *desc = NULL;
+}
+
+static void filedb_getowner(char *dir, char *fn, char **owner)
+{
+  filedb_entry *fdbe = NULL;
+
+  fdbe = filedb_getentry(dir, fn);
+  if (fdbe) {
+    malloc_strcpy(*owner, fdbe->uploader);
+    free_fdbe(&fdbe);
+  } else
+    *owner = NULL;
+}
+
+static int filedb_getgots(char *dir, char *fn)
+{
+  filedb_entry *fdbe = NULL;
+  int	        gots = 0;
+
+  fdbe = filedb_getentry(dir, fn);
+  if (fdbe) {
+    gots = fdbe->gots;
+    free_fdbe(&fdbe);
+  }
+  return gots;
+}
+
+static void filedb_setdesc(char *dir, char *fn, char *desc)
+{
+  filedb_entry *fdbe = NULL;
+  FILE         *fdb  = NULL;
+
+  fdb = filedb_open(dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
+  if (fdbe) {
+    free_null(fdbe->desc);
+    realloc_strcpy(fdbe->desc, desc);
+    filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+    free_fdbe(&fdbe);
+  }
+  filedb_close(fdb);
+}
+
+static void filedb_setowner(char *dir, char *fn, char *owner)
+{
+  filedb_entry *fdbe = NULL;
+  FILE         *fdb  = NULL;
+
+  fdb = filedb_open(dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
+  if (fdbe) {
+    free_null(fdbe->uploader);
+    realloc_strcpy(fdbe->uploader, owner);
+    filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+    free_fdbe(&fdbe);
+  }
+  filedb_close(fdb);
+}
+
+static void filedb_setlink(char *dir, char *fn, char *link)
+{
+  filedb_entry *fdbe = NULL;
+  FILE	       *fdb  = NULL;
+
+  fdb = filedb_open(dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
+  if (fdbe) {
+    /* Change existing one? */
+    if ((fdbe->stat & FILE_DIR) || !fdbe->sharelink)
+      return;
+    if (!link || !link[0])
+      filedb_delfile(fdb, fdbe->pos);
+    else {
+      free_null(fdbe->sharelink);
+      realloc_strcpy(fdbe->sharelink, link);
+      filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+    }
+    free_fdbe(&fdbe);
+    return;
+  }
+
+  fdbe = malloc_fdbe();
+  realloc_strcpy(fdbe->uploader, botnetnick);
+  realloc_strcpy(fdbe->filename, fn);
+  realloc_strcpy(fdbe->sharelink, link);
+  fdbe->uploaded = now;
+  filedb_addfile(fdb, fdbe);
+  free_fdbe(&fdbe);
+  filedb_close(fdb);
+}
+
+static void filedb_getlink(char *dir, char *fn, char **link)
+{
+  filedb_entry *fdbe = NULL;
+
+  fdbe = filedb_getentry(dir, fn);
+  if (fdbe && (!(fdbe->stat & FILE_DIR))) {
+    malloc_strcpy(*link, fdbe->sharelink);
+  } else
+    *link = NULL;
+  if (fdbe)
+    free_fdbe(&fdbe);
+  return;
+}
+
+static void filedb_getfiles(Tcl_Interp * irp, char *dir)
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+
+  fdb = filedb_open(dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  while (!feof(fdb)) {
+    fdbe = filedb_getfile(fdb, ftell(fdb), GET_FILENAME);
+    if (fdbe) {
+      if (!(fdbe->stat & (FILE_DIR | FILE_UNUSED)))
+	Tcl_AppendElement(irp, fdbe->filename);
+      free_fdbe(&fdbe);
+    }
+  }
+  filedb_close(fdb);
+}
+
+static void filedb_getdirs(Tcl_Interp * irp, char *dir)
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+
+  fdb = filedb_open(dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  while (!feof(fdb)) {
+    fdbe = filedb_getfile(fdb, ftell(fdb), GET_FILENAME);
+    if (fdbe) {
+      if ((!(fdbe->stat & FILE_UNUSED)) && (fdbe->stat & FILE_DIR))
+	Tcl_AppendElement(irp, fdbe->filename);
+      free_fdbe(&fdbe);
+    }
+  }
+  filedb_close(fdb);
+}
+
+static void filedb_change(char *dir, char *fn, int what)
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+  int changed = 0;
+
+  fdb = filedb_open(dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
+  if (fdbe) {
+    if (!(fdbe->stat & FILE_DIR)) {
+      switch (what) {
+      case FILEDB_SHARE:
+	fdbe->stat |= FILE_SHARE;
+	break;
+      case FILEDB_UNSHARE:
+	fdbe->stat &= ~FILE_SHARE;
+	break;
+      }
+      changed = 1;
+    }
+    switch (what) {
+    case FILEDB_HIDE:
+      fdbe->stat |= FILE_HIDDEN;
+      changed = 1;
+      break;
+    case FILEDB_UNHIDE:
+      fdbe->stat &= ~FILE_HIDDEN;
+      changed = 1;
+      break;
+    }
+    if (changed)
+      filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_HEADER);
+    free_fdbe(&fdbe);
+  }
+  filedb_close(fdb);
+}
Index: eggdrop1.7/modules/filesys/filedb3.h
diff -u /dev/null eggdrop1.7/modules/filesys/filedb3.h:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/filedb3.h	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,159 @@
+/*
+ * filedb3.h -- part of filesys.mod
+ *   filedb header file
+ *
+ * Written by Fabian Knittel <fknittel at gmx.de>
+ *
+ * $Id: filedb3.h,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_FILESYS_FILEDB3_H
+#define _EGG_MOD_FILESYS_FILEDB3_H
+
+#include <time.h>	/* for time_t */
+
+/* Top of each DB */
+typedef struct {
+  char version;				/* DB version			*/
+  time_t timestamp;			/* Last updated			*/
+} filedb_top;
+
+/* Header of each entry */
+typedef struct {
+  time_t uploaded;			/* Upload time			*/
+  unsigned int size;			/* File size			*/
+  unsigned short int stat;		/* Misc information		*/
+  unsigned short int gots;		/* Number of gets		*/
+  unsigned short int filename_len; 	/* Length of filename buf	*/
+  unsigned short int desc_len;	 	/* Length of description buf	*/
+  unsigned short int sharelink_len;	/* Length of sharelink buf	*/
+  unsigned short int chan_len;		/* Length of channel name buf	*/
+  unsigned short int uploader_len;	/* Length of uploader buf	*/
+  unsigned short int flags_req_len;	/* Length of flags buf		*/
+  unsigned short int buffer_len;	/* Length of additional buffer	*/
+} filedb_header;
+
+/* Structure used to pass data between lower level
+ * and higher level functions.
+ */
+typedef struct {
+  time_t uploaded;			/* Upload time			*/
+  unsigned int size;			/* File size			*/
+  unsigned short int stat;		/* Misc information		*/
+  unsigned short int gots;		/* Number of gets		*/
+  unsigned short int _type;		/* Type of entry (private)	*/
+
+  /* NOTE: These three are only valid during one db open/close. Entry
+   *       movements often invalidate them too, so make sure you know
+   *       what you're doing before using/relying on them.
+   */
+  long pos;				/* Last position in the filedb	*/
+  unsigned short int dyn_len;		/* Length of dynamic data in DB	*/
+  unsigned short int buf_len;		/* Length of additional buffer	*/
+
+  char *filename;			/* Filename			*/
+  char *desc;				/* Description			*/
+  char *sharelink;			/* Share link. Points to remote
+		 			   file on linked bot.		*/
+  char *chan;				/* Channel name			*/
+  char *uploader;			/* Uploader			*/
+  char *flags_req;			/* Required flags		*/
+} filedb_entry;
+
+
+/*
+ * Macros
+ */
+
+/* Macro to calculate the total length of dynamic data. */
+#define filedb_tot_dynspace(fdh) ((fdh).filename_len + (fdh).desc_len +	\
+	(fdh).chan_len + (fdh).uploader_len + (fdh).flags_req_len + \
+	(fdh).sharelink_len)
+
+#define filedb_zero_dynspace(fdh) {					\
+	(fdh).filename_len	= 0;					\
+	(fdh).desc_len		= 0;					\
+	(fdh).chan_len		= 0;					\
+	(fdh).uploader_len	= 0;					\
+	(fdh).flags_req_len	= 0;					\
+	(fdh).sharelink_len	= 0;					\
+}
+
+/* Memory debugging makros */
+#define malloc_fdbe() _malloc_fdbe(__FILE__, __LINE__)
+#define filedb_getfile(fdb, pos, get) _filedb_getfile(fdb, pos, get, __FILE__, __LINE__)
+#define filedb_matchfile(fdb, pos, match) _filedb_matchfile(fdb, pos, match, __FILE__, __LINE__)
+#define filedb_updatefile(fdb, pos, fdbe, update) _filedb_updatefile(fdb, pos, fdbe, update, __FILE__, __LINE__)
+#define filedb_addfile(fdb, fdbe) _filedb_addfile(fdb, fdbe, __FILE__, __LINE__)
+#define filedb_movefile(fdb, pos, fdbe) _filedb_movefile(fdb, pos, fdbe, __FILE__, __LINE__)
+
+
+/*
+ *  Constants
+ */
+
+#define FILEDB_VERSION1	0x0001
+#define FILEDB_VERSION2	0x0002	/* DB version used for 1.3, 1.4		*/
+#define FILEDB_VERSION3	0x0003
+#define FILEDB_NEWEST_VER FILEDB_VERSION3	/* Newest DB version	*/
+
+#define POS_NEW		0	/* Position which indicates that the
+				   entry wants to be repositioned.	*/
+
+#define FILE_UNUSED	0x0001	/* Deleted entry.			*/
+#define FILE_DIR	0x0002	/* It's actually a directory.		*/
+#define FILE_SHARE	0x0004	/* Can be shared on the botnet.		*/
+#define FILE_HIDDEN	0x0008	/* Hidden file.				*/
+#define FILE_ISLINK	0x0010	/* The file is a link to another bot.	*/
+
+#define FILEDB_ESTDYN	50	/* Estimated dynamic length of an entry	*/
+
+enum {
+  GET_HEADER,			/* Only save minimal data		*/
+  GET_FILENAME,			/* Additionally save filename		*/
+  GET_FULL,			/* Save all data			*/
+
+  UPDATE_HEADER,		/* Only update header			*/
+  UPDATE_SIZE,			/* Update header, enforce new buf sizes	*/
+  UPDATE_ALL,			/* Update additional data too		*/
+
+  TYPE_NEW,			/* New entry				*/
+  TYPE_EXIST			/* Existing entry			*/
+};
+
+
+/*
+ *  filedb3.c prototypes
+ */
+
+static void free_fdbe(filedb_entry **);
+static filedb_entry *_malloc_fdbe(char *, int);
+static int filedb_readtop(FILE *, filedb_top *);
+static int filedb_writetop(FILE *, filedb_top *);
+static int filedb_delfile(FILE *, long);
+static filedb_entry *filedb_findempty(FILE *, int);
+static int _filedb_updatefile(FILE *, long, filedb_entry *, int, char *, int);
+static int _filedb_movefile(FILE *, long, filedb_entry *, char *, int);
+static int _filedb_addfile(FILE *, filedb_entry *, char *, int);
+static filedb_entry *_filedb_getfile(FILE *, long, int, char *, int);
+static filedb_entry *_filedb_matchfile(FILE *, long, char *, char *, int);
+static filedb_entry *filedb_getentry(char *, char *);
+
+#endif				/* _EGG_MOD_FILESYS_FILEDB3_H */
Index: eggdrop1.7/modules/filesys/filelist.c
diff -u /dev/null eggdrop1.7/modules/filesys/filelist.c:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/filelist.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,125 @@
+/*
+ * filelist.c -- part of filesys.mod
+ *   functions to sort and manage file lists
+ *
+ * Written by Fabian Knittel <fknittel at gmx.de>
+ *
+ * $Id: filelist.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include "filelist.h"
+
+static filelist_t *filelist_new(void)
+{
+  filelist_t *flist;
+
+  flist = malloc(sizeof(filelist_t));
+  flist->tot = 0;
+  flist->elements = NULL;
+  return flist;
+}
+
+static void filelist_free(filelist_t *flist)
+{
+  int i;
+
+  if (!flist)
+    return;
+  for (i = 0; i < flist->tot; i++) {
+    if (flist->elements[i].output)
+      free_null(flist->elements[i].output);
+    free_null(flist->elements[i].fn);
+  }
+  if (flist->elements)
+    free_null(flist->elements);
+  free_null(flist);
+}
+
+/* Increase number of filelist entries.
+ */
+static void filelist_add(filelist_t *flist, char *filename)
+{
+  flist->tot++;
+  flist->elements = realloc(flist->elements, flist->tot * sizeof(filelist_t));
+  malloc_strcpy(FILELIST_LE(flist).fn, filename);
+  FILELIST_LE(flist).output = NULL;
+}
+
+/* Add data to the end of filelist entry's output string
+ */
+static void filelist_addout(filelist_t *flist, char *desc)
+{
+  if (FILELIST_LE(flist).output) {
+    FILELIST_LE(flist).output = realloc(FILELIST_LE(flist).output, strlen(FILELIST_LE(flist).output) + strlen(desc) + 1);
+    strcat(FILELIST_LE(flist).output, desc);
+  } else
+    malloc_strcpy(FILELIST_LE(flist).output, desc);
+}
+
+/* Dump all data to specified idx */
+static inline void filelist_idxshow(filelist_t *flist, int idx)
+{
+  int i;
+
+  for (i = 0; i < flist->tot; i++)
+    dprintf(idx, "%s", flist->elements[i].output);
+}
+
+/* Uses QSort to sort the list of filenames. This function is
+ * called recursively.
+ */
+static void filelist_qsort(filelist_t *flist, int l, int r)
+{
+  int i = l, j = r, middle;
+  filelist_element_t *el = flist->elements, elt;
+
+  middle = ((l + r) / 2);
+  do {
+    while (strcmp(el[i].fn, el[middle].fn) < 0)
+      i++;
+    while (strcmp(el[j].fn, el[middle].fn) > 0)
+      j--;
+    if (i <= j) {
+      if (strcmp(el[j].fn, el[i].fn)) {
+	elt.fn = el[j].fn;
+	elt.output = el[j].output;
+	el[j].fn = el[i].fn;
+	el[j].output = el[i].output;
+	el[i].fn = elt.fn;
+	el[i].output = elt.output;
+      }
+      i++;
+      j--;
+    }
+  } while (i <= j);
+  if (l < j)
+    filelist_qsort(flist, l, j);
+  if (i < r)
+    filelist_qsort(flist, i, r);
+}
+
+/* Sort list of filenames.
+ */
+static void filelist_sort(filelist_t *flist)
+{
+  if (flist->tot < 2)
+    return;
+  filelist_qsort(flist, 0, (flist->tot - 1));
+}
Index: eggdrop1.7/modules/filesys/filelist.h
diff -u /dev/null eggdrop1.7/modules/filesys/filelist.h:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/filelist.h	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,50 @@
+/*
+ * filelist.h -- part of filesys.mod
+ *   header file for filelist.c
+ *
+ * Written by Fabian Knittel <fknittel at gmx.de>
+ *
+ * $Id: filelist.h,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_FILESYS_FILELIST_H
+#define _EGG_MOD_FILESYS_FILELIST_H
+
+typedef struct {
+  char			*fn;
+  char			*output;
+} filelist_element_t;
+
+typedef struct {
+  int			tot;
+  filelist_element_t	*elements;
+} filelist_t;
+
+/* Short-cut to access the last element in filelist */
+#define FILELIST_LE(flist) ((flist)->elements[(flist)->tot - 1])
+
+static filelist_t *filelist_new(void);
+static void filelist_free(filelist_t *);
+static void filelist_add(filelist_t *, char *);
+static inline void filelist_addout(filelist_t *, char *);
+static inline void filelist_idxshow(filelist_t *, int);
+static void filelist_sort(filelist_t *);
+
+#endif				/* _EGG_MOD_FILESYS_FILELIST_H */
Index: eggdrop1.7/modules/filesys/files.c
diff -u /dev/null eggdrop1.7/modules/filesys/files.c:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/files.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,1476 @@
+/*
+ * files.c - part of filesys.mod
+ *   handles all file system commands
+ *
+ * $Id: files.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#if HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# if HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# if HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# if HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+#endif
+
+#include "src/stat.h"
+
+
+/* Are there too many people in the file system?
+ */
+static int too_many_filers()
+{
+  int i, n = 0;
+
+  if (dcc_users == 0)
+    return 0;
+  for (i = 0; i < dcc_total; i++)
+    if (dcc[i].type == &DCC_FILES)
+      n++;
+  return (n >= dcc_users);
+}
+
+/* Someone uploaded a file -- add it
+ */
+static void add_file(char *dir, char *file, char *nick)
+{
+  FILE *f;
+
+  /* Gave me a full pathname.
+   * Only continue if the destination is within the visible file system.
+   */
+  if (!strncmp(dccdir, dir, strlen(dccdir)) &&
+      (f = filedb_open(&dir[strlen(dccdir)], 2))) {
+    filedb_add(f, file, nick);
+    filedb_close(f);
+  }
+}
+
+static int welcome_to_files(int idx)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  FILE *f;
+  char *p = get_user(&USERENTRY_DCCDIR, dcc[idx].user);
+
+  dprintf(idx, "\n");
+  if (fr.global & USER_JANITOR)
+    fr.global |= USER_MASTER;
+
+  /* Show motd if the user went straight here without going thru the
+   * party line.
+   */
+  if (!(dcc[idx].status & STAT_CHAT))
+    show_motd(idx);
+  sub_lang(idx, _("Welcome to the %B file server\n"));
+  sub_lang(idx, _("All file transfers will be sent to '%N' by default.\n(You can specify an alternate nick with the 'get' command.)\nType 'help' for help.\n"));
+  if (p)
+    strcpy(dcc[idx].u.file->dir, p);
+  else
+    dcc[idx].u.file->dir[0] = 0;
+  /* Does this dir even exist any more? */
+  f = filedb_open(dcc[idx].u.file->dir, 0);
+  if (f == NULL) {
+    dcc[idx].u.file->dir[0] = 0;
+    f = filedb_open(dcc[idx].u.file->dir, 0);
+    if (f == NULL) {
+      dprintf(idx, _("\nThe file system seems to be broken right now.\n"));
+      dprintf(idx, _("(The files-path is set to an invalid directory.)\n"));
+      dprintf(idx, "\n\n");
+      dccdir[0] = 0;
+      chanout_but(-1, dcc[idx].u.file->chat->channel,
+		  "*** %s rejoined the party line.\n",
+		  dcc[idx].nick);
+      botnet_send_join_idx(idx, dcc[idx].u.file->chat->channel);
+      return 0;			/* failed */
+    }
+  }
+  filedb_close(f);
+  dprintf(idx, "%s: /%s\n\n", _("Current directory"), dcc[idx].u.file->dir);
+  return 1;
+}
+
+static void cmd_optimise(int idx, char *par)
+{
+  struct userrec *u = get_user_by_handle(userlist, dcc[idx].nick);
+  FILE *fdb = NULL;
+  char *p   = NULL;
+
+  putlog(LOG_FILES, "*", "files: #%s# optimise", dcc[idx].nick);
+  p = get_user(&USERENTRY_DCCDIR, u);
+  /* Does this dir even exist any more? */
+  if (p) {
+    fdb = filedb_open(p, 1);
+    if (!fdb) {
+      set_user(&USERENTRY_DCCDIR, u, NULL);
+      p = NULL;
+    }
+  }
+  if (!p)
+    fdb = filedb_open("", 1);
+  if (!fdb) {
+    dprintf(idx, _("Illegal directory.\n"));
+    return;
+  }
+  filedb_close(fdb);
+  dprintf(idx, "Current directory is now optimised.\n");
+}
+
+/* Given current directory, and the desired changes, fill 'real' with
+ * the new current directory.  check directory parmissions along the
+ * way.  return 1 if the change can happen, 0 if not. 'real' will be
+ * assigned newly allocated memory, so don't forget to free it...
+ */
+static int resolve_dir(char *current, char *change, char **real, int idx)
+{
+  char *elem = NULL, *s = NULL, *new = NULL, *work = NULL, *p = NULL;
+  FILE *fdb  = NULL;
+  DIR  *dir  = NULL;
+  filedb_entry *fdbe = NULL;
+  struct flag_record user = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0},
+		     req  = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  *real = NULL;
+  realloc_strcpy(*real, current);
+  if (!change[0])
+    return 1;				/* No change? */
+  new = malloc(strlen(change) + 2);	/* Add 2, because we add '/' below */
+  strcpy(new, change);
+  if (new[0] == '/') {
+    /* EVERYONE has access here */
+    (*real)[0] = 0;
+    strcpy(new, &new[1]);
+  }
+  /* Cycle thru the elements */
+  strcat(new, "/");
+  p = strchr(new, '/');
+  while (p) {
+    *p = 0;
+    p++;
+    realloc_strcpy(elem, new);
+    strcpy(new, p);
+    if (!(strcmp(elem, ".")) || (!elem[0])) {	/* Do nothing */
+    } else if (!strcmp(elem, "..")) {	/* Go back */
+      /* Always allowed */
+      p = strrchr(*real, '/');
+      if (p == NULL) {
+	/* Can't go back from here? */
+	if (!(*real)[0]) {
+	  free_null(elem);
+	  free_null(new);
+	  realloc_strcpy(*real, current);
+	  return 0;
+	}
+	(*real)[0] = 0;
+      } else
+	*p = 0;
+    } else {
+      /* Allowed access here? */
+      fdb = filedb_open(*real, 0);
+      if (!fdb) {
+	/* Non-existent starting point! */
+	free_null(elem);
+	free_null(new);
+	realloc_strcpy(*real, current);
+	return 0;
+      }
+      filedb_readtop(fdb, NULL);
+      fdbe = filedb_matchfile(fdb, ftell(fdb), elem);
+      filedb_close(fdb);
+      if (!fdbe) {
+	/* Non-existent */
+	free_null(elem);
+	free_null(new);
+	realloc_strcpy(*real, current);
+	return 0;
+      }
+      if (!(fdbe->stat & FILE_DIR) || fdbe->sharelink) {
+	/* Not a dir */
+	free_fdbe(&fdbe);
+	free_null(elem);
+	free_null(new);
+	realloc_strcpy(*real, current);
+	return 0;
+      }
+      if (idx >= 0)
+	get_user_flagrec(dcc[idx].user, &user, fdbe->chan);
+      else
+	user.global = USER_OWNER | USER_BOT | USER_MASTER | USER_OP |
+		      USER_FRIEND;
+
+      if (fdbe->flags_req) {
+        break_down_flags(fdbe->flags_req, &req, NULL);
+        if (!flagrec_ok(&req, &user)) {
+	  free_fdbe(&fdbe);
+	  free_null(elem);
+	  free_null(new);
+	  realloc_strcpy(*real, current);
+	  return 0;
+	}
+      }
+      free_fdbe(&fdbe);
+      realloc_strcpy(s, *real);
+      if (s[0])
+	if (s[strlen(s) - 1] != '/')
+	  strcat(s, "/");
+      work = malloc(strlen(s) + strlen(elem) + 1);
+      sprintf(work, "%s%s", s, elem);
+      realloc_strcpy(*real, work);
+      s = realloc(s, strlen(dccdir) + strlen(*real) + 1);
+      sprintf(s, "%s%s", dccdir, *real);
+    }
+    p = strchr(new, '/');
+  }
+  free_null(new);
+  if (elem)
+    free_null(elem);
+  if (work)
+    free_null(work);
+  /* Sanity check: does this dir exist? */
+  s = realloc(s, strlen(dccdir) + strlen(*real) + 1);
+  sprintf(s, "%s%s", dccdir, *real);
+  dir = opendir(s);
+  free_null(s);
+  if (!dir)
+    return 0;
+  closedir(dir);
+  return 1;
+}
+
+static void incr_file_gots(char *ppath)
+{
+  char *p, *path = NULL, *destdir = NULL, *fn = NULL;
+  filedb_entry *fdbe;
+  FILE *fdb;
+
+  /* Absolute dir?  probably a tcl script sending it, and it might not
+   * be in the file system at all, so just leave it alone.
+   */
+  if ((ppath[0] == '*') || (ppath[0] == '/'))
+    return;
+  realloc_strcpy(path, ppath);
+  p = strrchr(path, '/');
+  if (p != NULL) {
+    *p = 0;
+    realloc_strcpy(destdir, path);
+    realloc_strcpy(fn, p + 1);
+    *p = '/';
+  } else {
+    realloc_strcpy(destdir, "");
+    realloc_strcpy(fn, path);
+  }
+  fdb = filedb_open(destdir, 0);
+  if (!fdb) {
+    free_null(path);
+    free_null(destdir);
+    free_null(fn);
+    return;			/* Not my concern, then */
+  }
+  free_null(path);
+  free_null(destdir);
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
+  free_null(fn);
+  if (fdbe) {
+    fdbe->gots++;
+    filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_HEADER);
+    free_fdbe(&fdbe);
+  }
+  filedb_close(fdb);
+}
+
+/*** COMMANDS ***/
+
+static void cmd_pwd(int idx, char *par)
+{
+  putlog(LOG_FILES, "*", "files: #%s# pwd", dcc[idx].nick);
+  dprintf(idx, "%s: /%s\n", _("Current directory"), dcc[idx].u.file->dir);
+}
+
+static void cmd_pending(int idx, char *par)
+{
+  show_queued_files(idx);
+  putlog(LOG_FILES, "*", "files: #%s# pending", dcc[idx].nick);
+}
+
+static void cmd_cancel(int idx, char *par)
+{
+  if (!par[0]) {
+    dprintf(idx, "%s: cancel <file-mask>\n", _("Usage"));
+    return;
+  }
+  fileq_cancel(idx, par);
+  putlog(LOG_FILES, "*", "files: #%s# cancel %s", dcc[idx].nick, par);
+}
+
+static void cmd_chdir(int idx, char *msg)
+{
+  char *s;
+
+  if (!msg[0]) {
+    dprintf(idx, "%s: cd <new-dir>\n", _("Usage"));
+    return;
+  }
+  if (!resolve_dir(dcc[idx].u.file->dir, msg, &s, idx)) {
+    dprintf(idx, _("No such directory.\n"));
+    free_null(s);
+    return;
+  }
+  strncpy(dcc[idx].u.file->dir, s, 160);
+  free_null(s);
+  dcc[idx].u.file->dir[160] = 0;
+  set_user(&USERENTRY_DCCDIR, dcc[idx].user, dcc[idx].u.file->dir);
+  putlog(LOG_FILES, "*", "files: #%s# cd /%s", dcc[idx].nick,
+	 dcc[idx].u.file->dir);
+  dprintf(idx, "%s: /%s\n", _("New current directory"), dcc[idx].u.file->dir);
+}
+
+static void files_ls(int idx, char *par, int showall)
+{
+  char *p, *s = NULL, *destdir = NULL, *mask = NULL;
+  FILE *fdb;
+
+  if (par[0]) {
+    putlog(LOG_FILES, "*", "files: #%s# ls %s", dcc[idx].nick, par);
+    p = strrchr(par, '/');
+    if (p != NULL) {
+      *p = 0;
+      realloc_strcpy(s, par);
+      realloc_strcpy(mask, p + 1);
+      if (!resolve_dir(dcc[idx].u.file->dir, s, &destdir, idx)) {
+	dprintf(idx, _("Illegal directory.\n"));
+	free_null(s);
+	free_null(mask);
+	free_null(destdir);
+	return;
+      }
+      free_null(s);
+    } else {
+      realloc_strcpy(destdir, dcc[idx].u.file->dir);
+      realloc_strcpy(mask, par);
+    }
+    /* Might be 'ls dir'? */
+    if (resolve_dir(destdir, mask, &s, idx)) {
+      /* Aha! it was! */
+      realloc_strcpy(destdir, s);
+      realloc_strcpy(mask, "*");
+    }
+    free_null(s);
+    fdb = filedb_open(destdir, 0);
+    if (!fdb) {
+      dprintf(idx, _("Illegal directory.\n"));
+      free_null(destdir);
+      free_null(mask);
+      return;
+    }
+    filedb_ls(fdb, idx, mask, showall);
+    filedb_close(fdb);
+    free_null(destdir);
+    free_null(mask);
+  } else {
+    putlog(LOG_FILES, "*", "files: #%s# ls", dcc[idx].nick);
+    fdb = filedb_open(dcc[idx].u.file->dir, 0);
+    if (fdb) {
+      filedb_ls(fdb, idx, "*", showall);
+      filedb_close(fdb);
+    } else
+      dprintf(idx, _("Illegal directory.\n"));
+  }
+}
+
+static void cmd_ls(int idx, char *par)
+{
+  files_ls(idx, par, 0);
+}
+
+static void cmd_lsa(int idx, char *par)
+{
+  files_ls(idx, par, 1);
+}
+
+static void cmd_reget_get(int idx, char *par, int resend)
+{
+  int ok = 0, i;
+  char *p, *what, *destdir = NULL, *s = NULL;
+  filedb_entry *fdbe;
+  FILE *fdb;
+  long where = 0;
+  int nicklen = NICKLEN;
+
+  /* Get the nick length if necessary. */
+  if (NICKLEN > 9) {
+    module_entry *me = module_find("server", 1, 1);
+
+    if (me && me->funcs)
+      nicklen = (int) me->funcs[SERVER_NICKLEN];
+  }
+  if (!par[0]) {
+    dprintf(idx, "%s: %sget <file(s)> [nickname]\n", _("Usage"),
+	    resend ? "re" : "");
+    return;
+  }
+  what = newsplit(&par);
+  if (strlen(par) > nicklen) {
+    dprintf(idx, _("Be reasonable.\n"));
+    return;
+  }
+  p = strrchr(what, '/');
+  if (p != NULL) {
+    *p = 0;
+    realloc_strcpy(s, what);
+    strcpy(what, p + 1);
+    if (!resolve_dir(dcc[idx].u.file->dir, s, &destdir, idx)) {
+      free_null(destdir);
+      free_null(s);
+      dprintf(idx, _("Illegal directory.\n"));
+      return;
+    }
+    free_null(s);
+  } else
+    realloc_strcpy(destdir, dcc[idx].u.file->dir);
+  fdb = filedb_open(destdir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), what);
+  if (!fdbe) {
+    filedb_close(fdb);
+    free_fdbe(&fdbe);
+    free_null(destdir);
+    dprintf(idx, _("No matching files.\n"));
+    return;
+  }
+  while (fdbe) {
+    where = ftell(fdb);
+    if (!(fdbe->stat & (FILE_HIDDEN | FILE_DIR))) {
+      ok = 1;
+      if (fdbe->sharelink) {
+	char *bot, *whoto = NULL;
+
+	/* This is a link to a file on another bot... */
+	bot = malloc(strlen(fdbe->sharelink) + 1);
+	splitc(bot, fdbe->sharelink, ':');
+	if (!strcasecmp(bot, botnetnick)) {
+	  dprintf(idx, "Can't get that file, it's linked to this bot!\n");
+	} else if (!in_chain(bot)) {
+	  dprintf(idx, _("%s isnt available right now.\n"), fdbe->filename);
+	} else {
+	  i = nextbot(bot);
+	  realloc_strcpy(whoto, par);
+	  if (!whoto[0])
+	    realloc_strcpy(whoto, dcc[idx].nick);
+	  s = malloc(strlen(whoto) + strlen(botnetnick) + 13);
+	  simple_sprintf(s, "%d:%s@%s", dcc[idx].sock, whoto, botnetnick);
+	  botnet_send_filereq(i, s, bot, fdbe->sharelink);
+	  dprintf(idx, _("Requested %s from %s ...\n"), fdbe->sharelink, bot);
+	  /* Increase got count now (or never) */
+	  fdbe->gots++;
+	  s = realloc(s, strlen(bot) + strlen(fdbe->sharelink) + 2);
+	  sprintf(s, "%s:%s", bot, fdbe->sharelink);
+	  realloc_strcpy(fdbe->sharelink, s);
+	  filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+	  free_null(whoto);
+	  free_null(s);
+	}
+	free_null(bot);
+      } else {
+	do_dcc_send(idx, destdir, fdbe->filename, par, resend);
+	/* Don't increase got count till later */
+      }
+    }
+    free_fdbe(&fdbe);
+    fdbe = filedb_matchfile(fdb, where, what);
+  }
+  filedb_close(fdb);
+  free_null(destdir);
+  if (!ok)
+    dprintf(idx, _("No matching files.\n"));
+  else
+    putlog(LOG_FILES, "*", "files: #%s# %sget %s %s", dcc[idx].nick,
+	   resend ? "re" : "", what, par);
+}
+
+static void cmd_reget(int idx, char *par)
+{
+  cmd_reget_get(idx, par, 1);
+}
+
+static void cmd_get(int idx, char *par)
+{
+  cmd_reget_get(idx, par, 0);
+}
+
+static void cmd_file_help(int idx, char *par)
+{
+  char *s;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.file->chat->con_chan);
+  if (par[0]) {
+    putlog(LOG_FILES, "*", "files: #%s# help %s", dcc[idx].nick, par);
+    s = malloc(strlen(par) + 9);
+    sprintf(s, "filesys/%s", par);
+    s[256] = 0;
+    tellhelp(idx, s, &fr, 0);
+    free_null(s);
+  } else {
+    putlog(LOG_FILES, "*", "files: #%s# help", dcc[idx].nick);
+    tellhelp(idx, "filesys/help", &fr, 0);
+  }
+}
+
+static void cmd_hide(int idx, char *par)
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+  long where = 0;
+  int ok = 0;
+
+  if (!par[0]) {
+    dprintf(idx, "%s: hide <file(s)>\n", _("Usage"));
+    return;
+  }
+  fdb = filedb_open(dcc[idx].u.file->dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), par);
+  if (!fdbe) {
+    filedb_close(fdb);
+    dprintf(idx, _("No matching files.\n"));
+    return;
+  }
+  while (fdbe) {
+    where = ftell(fdb);
+    if (!(fdbe->stat & FILE_HIDDEN)) {
+      fdbe->stat |= FILE_HIDDEN;
+      ok++;
+      dprintf(idx, "%s: %s\n", _("Hid"), fdbe->filename);
+      filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_HEADER);
+    }
+    free_fdbe(&fdbe);
+    fdbe = filedb_matchfile(fdb, where, par);
+  }
+  filedb_close(fdb);
+  if (!ok)
+    dprintf(idx, _("No matching files.\n"));
+  else {
+    putlog(LOG_FILES, "*", "files: #%s# hide %s", dcc[idx].nick, par);
+    if (ok > 1)
+      dprintf(idx, "%s %d file%s.\n", _("Hid"), ok, ok == 1 ? "" : "s");
+  }
+}
+
+static void cmd_unhide(int idx, char *par)
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+  long where;
+  int ok = 0;
+
+  if (!par[0]) {
+    dprintf(idx, "%s: unhide <file(s)>\n", _("Usage"));
+    return;
+  }
+  fdb = filedb_open(dcc[idx].u.file->dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), par);
+  if (!fdbe) {
+    filedb_close(fdb);
+    dprintf(idx, _("No matching files.\n"));
+    return;
+  }
+  while (fdbe) {
+    where = ftell(fdb);
+    if (fdbe->stat & FILE_HIDDEN) {
+      fdbe->stat &= ~FILE_HIDDEN;
+      ok++;
+      dprintf(idx, "%s: %s\n", _("Unhid"), fdbe->filename);
+      filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_HEADER);
+    }
+    free_fdbe(&fdbe);
+    fdbe = filedb_matchfile(fdb, where, par);
+  }
+  filedb_close(fdb);
+  if (!ok)
+    dprintf(idx, _("No matching files.\n"));
+  else {
+    putlog(LOG_FILES, "*", "files: #%s# unhide %s", dcc[idx].nick, par);
+    if (ok > 1)
+      dprintf(idx, "%s %d file%s.\n", _("Unhid"), ok, ok == 1 ? "" : "s");
+  }
+}
+
+static void cmd_share(int idx, char *par)
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+  long where;
+  int ok = 0;
+
+  if (!par[0]) {
+    dprintf(idx, "%s: share <file(s)>\n", _("Usage"));
+    return;
+  }
+  fdb = filedb_open(dcc[idx].u.file->dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), par);
+  if (!fdbe) {
+    filedb_close(fdb);
+    dprintf(idx, _("No matching files.\n"));
+    return;
+  }
+  while (fdbe) {
+    where = ftell(fdb);
+    if (!(fdbe->stat & (FILE_HIDDEN | FILE_DIR | FILE_SHARE))) {
+      fdbe->stat |= FILE_SHARE;
+      ok++;
+      dprintf(idx, "%s: %s\n", _("Shared"), fdbe->filename);
+      filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_HEADER);
+    }
+    free_fdbe(&fdbe);
+    fdbe = filedb_matchfile(fdb, where, par);
+  }
+  filedb_close(fdb);
+  if (!ok)
+    dprintf(idx, _("No matching files.\n"));
+  else {
+    putlog(LOG_FILES, "*", "files: #%s# share %s", dcc[idx].nick, par);
+    if (ok > 1)
+      dprintf(idx, "%s %d file%s.\n", _("Shared"), ok, ok == 1 ? "" : "s");
+  }
+}
+
+static void cmd_unshare(int idx, char *par)
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+  long where;
+  int ok = 0;
+
+  if (!par[0]) {
+    dprintf(idx, "%s: unshare <file(s)>\n", _("Usage"));
+    return;
+  }
+  fdb = filedb_open(dcc[idx].u.file->dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), par);
+  if (!fdbe) {
+    filedb_close(fdb);
+    dprintf(idx, _("No matching files.\n"));
+    return;
+  }
+  while (fdbe) {
+    where = ftell(fdb);
+    if ((fdbe->stat & FILE_SHARE) &&
+	!(fdbe->stat & (FILE_DIR | FILE_HIDDEN))) {
+      fdbe->stat &= ~FILE_SHARE;
+      ok++;
+      dprintf(idx, "%s: %s\n", _("Unshared"), fdbe->filename);
+      filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_HEADER);
+    }
+    free_fdbe(&fdbe);
+    fdbe = filedb_matchfile(fdb, where, par);
+  }
+  filedb_close(fdb);
+  if (!ok)
+    dprintf(idx, _("No matching files.\n"));
+  else {
+    putlog(LOG_FILES, "*", "files: #%s# unshare %s", dcc[idx].nick, par);
+    if (ok > 1)
+      dprintf(idx, "%s %d file%s.\n", _("Unshared"), ok,
+	      ok == 1 ? "" : "s");
+  }
+}
+
+/* Link a file from another bot.
+ */
+static void cmd_ln(int idx, char *par)
+{
+  char *share, *newpath = NULL, *newfn = NULL, *p;
+  FILE *fdb;
+  filedb_entry *fdbe;
+
+  share = newsplit(&par);
+  if (strlen(share) > 60)
+    share[60] = 0;
+  /* Correct format? */
+  if (!(p = strchr(share, ':')) || !par[0])
+    dprintf(idx, "%s: ln <bot:path> <localfile>\n", _("Usage"));
+  else if (p[1] != '/')
+    dprintf(idx, "Links to other bots must have absolute paths.\n");
+  else {
+    if ((p = strrchr(par, '/'))) {
+      *p = 0;
+      realloc_strcpy(newfn, p + 1);
+      if (!resolve_dir(dcc[idx].u.file->dir, par, &newpath, idx)) {
+	dprintf(idx, _("No such directory.\n"));
+	free_null(newfn);
+	free_null(newpath);
+	return;
+      }
+    } else {
+      realloc_strcpy(newpath, dcc[idx].u.file->dir);
+      realloc_strcpy(newfn, par);
+    }
+    fdb = filedb_open(newpath, 0);
+    if (!fdb) {
+      free_null(newfn);
+      free_null(newpath);
+      return;
+    }
+    filedb_readtop(fdb, NULL);
+    fdbe = filedb_matchfile(fdb, ftell(fdb), newfn);
+    if (fdbe) {
+      if (!fdbe->sharelink) {
+	dprintf(idx, _("%s is already a normal file.\n"), newfn);
+	filedb_close(fdb);
+      } else {
+	realloc_strcpy(fdbe->sharelink, share);
+	filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+	filedb_close(fdb);
+	dprintf(idx, _("Changed link to %s\n"), share);
+	putlog(LOG_FILES, "*", "files: #%s# ln %s %s",
+	       dcc[idx].nick, par, share);
+      }
+    } else {
+      /* New entry */
+      fdbe = malloc_fdbe();
+      realloc_strcpy(fdbe->filename, newfn);
+      realloc_strcpy(fdbe->uploader, dcc[idx].nick);
+      fdbe->uploaded = now;
+      realloc_strcpy(fdbe->sharelink, share);
+      filedb_addfile(fdb, fdbe);
+      filedb_close(fdb);
+      dprintf(idx, "%s %s -> %s\n", _("Added link"), fdbe->filename, share);
+      putlog(LOG_FILES, "*", "files: #%s# ln /%s%s%s %s", dcc[idx].nick,
+	     newpath, newpath[0] ? "/" : "", newfn, share);
+    }
+    free_fdbe(&fdbe);
+    free_null(newpath);
+    free_null(newfn);
+  }
+}
+
+static void cmd_desc(int idx, char *par)
+{
+  char *fn, *desc, *p, *q;
+  int ok = 0, lin;
+  FILE *fdb;
+  filedb_entry *fdbe;
+  long where;
+
+  fn = newsplit(&par);
+  if (!fn[0]) {
+    dprintf(idx, "%s: desc <filename> <new description>\n", _("Usage"));
+    return;
+  }
+  /* Fix up desc */
+  desc = malloc(strlen(par) + 2);
+  strcpy(desc, par);
+  strcat(desc, "|");
+  /* Replace | with linefeeds, limit 5 lines */
+  lin = 0;
+  q = desc;
+  while ((*q <= 32) && (*q))
+    strcpy(q, &q[1]);		/* Zapf leading spaces */
+  p = strchr(q, '|');
+  while (p != NULL) {
+    /* Check length */
+    *p = 0;
+    if (strlen(q) > 60) {
+      /* Cut off at last space or truncate */
+      *p = '|';
+      p = q + 60;
+      while ((*p != ' ') && (p != q))
+	p--;
+      if (p == q)
+	*(q + 60) = '|';	/* No space, so truncate it */
+      else
+	*p = '|';
+      p = strchr(q, '|');	/* Go back, find my place, and continue */
+    }
+    *p = '\n';
+    q = p + 1;
+    lin++;
+    while ((*q <= 32) && (*q))
+      strcpy(q, &q[1]);
+    if (lin == 5) {
+      *p = 0;
+      p = NULL;
+    } else
+      p = strchr(q, '|');
+  }
+  if (desc[strlen(desc) - 1] == '\n')
+    desc[strlen(desc) - 1] = 0;
+  fdb = filedb_open(dcc[idx].u.file->dir, 0);
+  if (!fdb) {
+    free_null(desc);
+    return;
+  }
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), fn);
+  if (!fdbe) {
+    filedb_close(fdb);
+    dprintf(idx, _("No matching files.\n"));
+    free_null(desc);
+    return;
+  }
+  while (fdbe) {
+    where = ftell(fdb);
+    if (!(fdbe->stat & FILE_HIDDEN)) {
+      ok = 1;
+      if ((!(dcc[idx].user->flags & USER_JANITOR)) &&
+	  (strcasecmp(fdbe->uploader, dcc[idx].nick)))
+	dprintf(idx, _("You didnt upload %s\n"), fdbe->filename);
+      else {
+	if (desc[0]) {
+	  /* If the current description is the same as the new
+	   * one, don't change anything.
+	   */
+	  if (fdbe->desc && !strcmp(fdbe->desc, desc)) {
+	    free_fdbe(&fdbe);
+	    fdbe = filedb_matchfile(fdb, where, fn);
+	    continue;
+	  }
+	  realloc_strcpy(fdbe->desc, desc);
+	} else if (fdbe->desc) {
+	  free_null(fdbe->desc);
+	}
+	filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+	if (par[0])
+	  dprintf(idx, "%s: %s\n", _("Changed"), fdbe->filename);
+	else
+	  dprintf(idx, "%s: %s\n", _("Blanked"), fdbe->filename);
+      }
+    }
+    free_fdbe(&fdbe);
+    fdbe = filedb_matchfile(fdb, where, fn);
+  }
+  filedb_close(fdb);
+  if (!ok)
+    dprintf(idx, _("No matching files.\n"));
+  else
+    putlog(LOG_FILES, "*", "files: #%s# desc %s", dcc[idx].nick, fn);
+  free_null(desc);
+}
+
+static void cmd_rm(int idx, char *par)
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+  long where;
+  int ok = 0;
+  char *s;
+
+  if (!par[0]) {
+    dprintf(idx, "%s: rm <file(s)>\n", _("Usage"));
+    return;
+  }
+  fdb = filedb_open(dcc[idx].u.file->dir, 0);
+  if (!fdb)
+    return;
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), par);
+  if (!fdbe) {
+    filedb_close(fdb);
+    dprintf(idx, _("No matching files.\n"));
+    return;
+  }
+  while (fdbe) {
+    where = ftell(fdb);
+    if (!(fdbe->stat & (FILE_HIDDEN | FILE_DIR))) {
+      s = malloc(strlen(dccdir) + strlen(dcc[idx].u.file->dir)
+		  + strlen(fdbe->filename) + 2);
+      sprintf(s, "%s%s/%s", dccdir, dcc[idx].u.file->dir, fdbe->filename);
+      ok++;
+      filedb_delfile(fdb, fdbe->pos);
+      /* Shared file links won't be able to be unlinked */
+      if (!(fdbe->sharelink))
+	unlink(s);
+      dprintf(idx, "%s: %s\n", _("Erased"), fdbe->filename);
+      free_null(s);
+    }
+    free_fdbe(&fdbe);
+    fdbe = filedb_matchfile(fdb, where, par);
+  }
+  filedb_close(fdb);
+  if (!ok)
+    dprintf(idx, _("No matching files.\n"));
+  else {
+    putlog(LOG_FILES, "*", "files: #%s# rm %s", dcc[idx].nick, par);
+    if (ok > 1)
+      dprintf(idx, "%s %d file%s.\n", _("Erased"), ok, ok == 1 ? "" : "s");
+  }
+}
+
+static void cmd_mkdir(int idx, char *par)
+{
+  char *name, *flags, *chan, *s;
+  FILE *fdb;
+  filedb_entry *fdbe;
+  int ret;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  if (!par[0]) {
+    dprintf(idx, "%s: mkdir <dir> [required-flags] [channel]\n", _("Usage"));
+    return;
+  }
+  name = newsplit(&par);
+  ret = strlen(name);
+  if (ret > 60)
+    name[(ret = 60)] = 0;
+  if (name[ret] == '/')
+    name[ret] = 0;
+  if (strchr(name, '/'))
+    dprintf(idx, "You can only create directories in the current directory\n");
+  else {
+    flags = newsplit(&par);
+    chan = newsplit(&par);
+    if (!chan[0] && flags[0] && (strchr(CHANMETA, flags[0]) != NULL)) {
+      /* Need some extra checking here to makesure we dont mix up
+       * the flags with a +channel. <cybah>
+       */
+      if(!findchan(flags) && flags[0] != '+') {
+	dprintf(idx, "Invalid channel!\n");
+	return;
+      } else if(findchan(flags)) {
+	/* Flags is a channel. */
+	chan = flags;
+	flags = par;
+      }	/* (else) Couldnt find the channel and flags[0] is a '+', these
+	 * are flags. */
+    }
+    if (chan[0] && !findchan(chan)) {
+      dprintf(idx, "Invalid channel!\n");
+      return;
+    }
+    fdb = filedb_open(dcc[idx].u.file->dir, 0);
+    if (!fdb)
+      return;
+    filedb_readtop(fdb, NULL);
+    fdbe = filedb_matchfile(fdb, ftell(fdb), name);
+    if (!fdbe) {
+      s = malloc(strlen(dccdir) + strlen(dcc[idx].u.file->dir)
+		  + strlen(name) + 2);
+      sprintf(s, "%s%s/%s", dccdir, dcc[idx].u.file->dir, name);
+      if (mkdir(s, 0755) != 0) {
+	dprintf(idx, _("Failed.\n"));
+	filedb_close(fdb);
+	free_null(s);
+	return;
+      }
+      free_null(s);
+      fdbe = malloc_fdbe();
+      fdbe->stat = FILE_DIR;
+      realloc_strcpy(fdbe->filename, name);
+      fdbe->uploaded = now;
+      dprintf(idx, "%s /%s%s%s\n", _("Created directory"), dcc[idx].u.file->dir,
+	      dcc[idx].u.file->dir[0] ? "/" : "", name);
+    } else if (!(fdbe->stat & FILE_DIR)) {
+      dprintf(idx, _("No such directory.\n"));
+      free_fdbe(&fdbe);
+      filedb_close(fdb);
+      return;
+    }
+    if (flags[0]) {
+      char buffer[100];
+
+      break_down_flags(flags, &fr, NULL);
+      build_flags(buffer, &fr, NULL);
+      realloc_strcpy(fdbe->flags_req, buffer);
+      dprintf(idx, _("Changed %s/ to require +%s to access\n"), name, buffer);
+    } else if (!chan[0]) {
+      free_null(fdbe->flags_req);
+      dprintf(idx, _("Changes %s/ to require no flags to access\n"), name);
+    }
+    if (chan[0]) {
+      realloc_strcpy(fdbe->chan, chan);
+      dprintf(idx, "Access set to channel: %s\n", chan);
+    } else if (!flags[0]) {
+      free_null(fdbe->chan);
+      dprintf(idx, "Access set to all channels.\n");
+    }
+    if (!fdbe->pos)
+      fdbe->pos = POS_NEW;
+    filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+    filedb_close(fdb);
+    free_fdbe(&fdbe);
+    putlog(LOG_FILES, "*", "files: #%s# mkdir %s %s", dcc[idx].nick, name, par);
+  }
+}
+
+static void cmd_rmdir(int idx, char *par)
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+  char *s, *name = NULL;
+
+  realloc_strcpy(name, par);
+  if (name[strlen(name) - 1] == '/')
+    name[strlen(name) - 1] = 0;
+  if (strchr(name, '/'))
+    dprintf(idx, "You can only create directories in the current directory\n");
+  else {
+    fdb = filedb_open(dcc[idx].u.file->dir, 0);
+    if (!fdb) {
+      free_null(name);
+      return;
+    }
+    filedb_readtop(fdb, NULL);
+    fdbe = filedb_matchfile(fdb, ftell(fdb), name);
+    if (!fdbe) {
+      dprintf(idx, _("No such directory.\n"));
+      filedb_close(fdb);
+      free_null(name);
+      return;
+    }
+    if (!(fdbe->stat & FILE_DIR)) {
+      dprintf(idx, _("No such directory.\n"));
+      filedb_close(fdb);
+      free_null(name);
+      free_fdbe(&fdbe);
+      return;
+    }
+    /* Erase '.filedb' and '.files' if they exist */
+    s = malloc(strlen(dccdir) + strlen(dcc[idx].u.file->dir)
+		+ strlen(name) + 10);
+    sprintf(s, "%s%s/%s/.filedb", dccdir, dcc[idx].u.file->dir, name);
+    unlink(s);
+    sprintf(s, "%s%s/%s/.files", dccdir, dcc[idx].u.file->dir, name);
+    unlink(s);
+    sprintf(s, "%s%s/%s", dccdir, dcc[idx].u.file->dir, name);
+    if (rmdir(s) == 0) {
+      dprintf(idx, "%s /%s%s%s\n", _("Removed directory"), dcc[idx].u.file->dir,
+	      dcc[idx].u.file->dir[0] ? "/" : "", name);
+      filedb_delfile(fdb, fdbe->pos);
+      filedb_close(fdb);
+      free_fdbe(&fdbe);
+      free_null(s);
+      free_null(name);
+      putlog(LOG_FILES, "*", "files: #%s# rmdir %s", dcc[idx].nick, name);
+      return;
+    }
+    dprintf(idx, _("Failed.\n"));
+    filedb_close(fdb);
+    free_fdbe(&fdbe);
+    free_null(s);
+    free_null(name);
+  }
+}
+
+static void cmd_mv_cp(int idx, char *par, int copy)
+{
+  char *p, *fn, *oldpath = NULL, *s = NULL, *s1, *newfn = NULL;
+  char *newpath = NULL;
+  int   ok = 0, only_first = 0, skip_this = 0;
+  FILE *fdb_old = NULL, *fdb_new = NULL;
+  filedb_entry *fdbe_old = NULL, *fdbe_new = NULL;
+  long where = 0;
+
+  fn = newsplit(&par);
+  if (!par[0]) {
+    dprintf(idx, "%s: %s <oldfilepath> <newfilepath>\n",
+	    _("Usage"), copy ? "cp" : "mv");
+    return;
+  }
+  p = strrchr(fn, '/');
+  if (p != NULL) {
+    *p = 0;
+    realloc_strcpy(s, fn);
+    strcpy(fn, p + 1);
+    if (!resolve_dir(dcc[idx].u.file->dir, s, &oldpath, idx)) {
+      dprintf(idx, _("Illegal source directory.\n"));
+      free_null(s);
+      free_null(oldpath);
+      return;
+    }
+    free_null(s);
+  } else
+    realloc_strcpy(oldpath, dcc[idx].u.file->dir);
+  realloc_strcpy(s, par);
+  if (!resolve_dir(dcc[idx].u.file->dir, s, &newpath, idx)) {
+    /* Destination is not just a directory */
+    p = strrchr(s, '/');
+    if (p == NULL) {
+      realloc_strcpy(newfn, s);
+      s[0] = 0;
+    } else {
+      *p = 0;
+      realloc_strcpy(newfn, p + 1);
+    }
+    if (!resolve_dir(dcc[idx].u.file->dir, s, &newpath, idx)) {
+      dprintf(idx, _("Illegal destination directory.\n"));
+      free_null(newfn);
+      free_null(s);
+      free_null(oldpath);
+      free_null(newpath);
+      return;
+    }
+  } else
+    realloc_strcpy(newfn, "");
+  free_null(s);
+  /* Stupidness checks */
+  if ((!strcmp(oldpath, newpath)) &&
+      ((!newfn[0]) || (!strcmp(newfn, fn)))) {
+    dprintf(idx, _("You cant %s files on top of themselves.\n"), copy ? _("copy") : _("move"));
+    free_null(oldpath);
+    free_null(newpath);
+    free_null(newfn);
+    return;
+  }
+  /* Be aware of 'cp * this.file' possibility: ONLY COPY FIRST ONE */
+  if ((strchr(fn, '?') || strchr(fn, '*')) && newfn[0])
+    only_first = 1;
+  else
+    only_first = 0;
+
+  fdb_old = filedb_open(oldpath, 0);
+  if (!strcmp(oldpath, newpath))
+    fdb_new = fdb_old;
+  else
+    fdb_new = filedb_open(newpath, 0);
+  if (!fdb_old || !fdb_new) {
+    free_null(oldpath);
+    free_null(newpath);
+    free_null(newfn);
+    return;
+  }
+
+  filedb_readtop(fdb_old, NULL);
+  fdbe_old = filedb_matchfile(fdb_old, ftell(fdb_old), fn);
+  if (!fdbe_old) {
+    if (fdb_new != fdb_old)
+      filedb_close(fdb_new);
+    filedb_close(fdb_old);
+    free_null(oldpath);
+    free_null(newpath);
+    free_null(newfn);
+    return;
+  }
+  while (fdbe_old) {
+    where = ftell(fdb_old);
+    skip_this = 0;
+    if (!(fdbe_old->stat & (FILE_HIDDEN | FILE_DIR))) {
+      s = malloc(strlen(dccdir) + strlen(oldpath)
+		  + strlen(fdbe_old->filename) + 2);
+      s1 = malloc(strlen(dccdir) + strlen(newpath)
+		   + strlen(newfn[0] ? newfn : fdbe_old->filename) + 2);
+      sprintf(s, "%s%s%s%s", dccdir, oldpath,
+	      oldpath[0] ? "/" : "", fdbe_old->filename);
+      sprintf(s1, "%s%s%s%s", dccdir, newpath,
+	      newpath[0] ? "/" : "", newfn[0] ? newfn : fdbe_old->filename);
+      if (!strcmp(s, s1)) {
+	dprintf(idx, "%s /%s%s%s %s\n", _("onto itself?  Nuh uh."),
+	        copy ? _("copy") : _("move"), newpath,
+	        newpath[0] ? "/" : "", newfn[0] ? newfn : fdbe_old->filename);
+	skip_this = 1;
+      }
+      /* Check for existence of file with same name in new dir */
+      filedb_readtop(fdb_new, NULL);
+      fdbe_new = filedb_matchfile(fdb_new, ftell(fdb_new),
+				  newfn[0] ? newfn : fdbe_old->filename);
+      if (fdbe_new) {
+	/* It's ok if the entry in the new dir is a normal file (we'll
+	 * just scrap the old entry and overwrite the file) -- but if
+	 * it's a directory, this file has to be skipped.
+	 */
+	if (fdbe_new->stat & FILE_DIR) {
+	  /* Skip */
+	  skip_this = 1;
+	} else {
+	  filedb_delfile(fdb_new, fdbe_new->pos);
+	}
+	free_fdbe(&fdbe_new);
+      }
+      if (!skip_this) {
+	if ((fdbe_old->sharelink) || (copyfile(s, s1) == 0)) {
+	  /* Raw file moved okay: create new entry for it */
+	  ok++;
+	  fdbe_new = malloc_fdbe();
+	  fdbe_new->stat = fdbe_old->stat;
+	  /* We don't have to worry about any entries to be
+	   * NULL, because realloc_strcpy takes care of that.
+	   */
+	  realloc_strcpy(fdbe_new->flags_req, fdbe_old->flags_req);
+	  realloc_strcpy(fdbe_new->chan, fdbe_old->chan);
+	  realloc_strcpy(fdbe_new->filename, fdbe_old->filename);
+	  realloc_strcpy(fdbe_new->desc, fdbe_old->desc);
+	  if (newfn[0])
+	    realloc_strcpy(fdbe_new->filename, newfn);
+	  realloc_strcpy(fdbe_new->uploader, fdbe_old->uploader);
+	  fdbe_new->uploaded = fdbe_old->uploaded;
+	  fdbe_new->size = fdbe_old->size;
+	  fdbe_new->gots = fdbe_old->gots;
+	  realloc_strcpy(fdbe_new->sharelink, fdbe_old->sharelink);
+	  filedb_addfile(fdb_new, fdbe_new);
+	  if (!copy) {
+	    unlink(s);
+	    filedb_delfile(fdb_old, fdbe_old->pos);
+	  }
+	  free_fdbe(&fdbe_new);
+	}
+      }
+      free_null(s);
+      free_null(s1);
+    }
+    free_fdbe(&fdbe_old);
+    fdbe_old = filedb_matchfile(fdb_old, where, fn);
+    if (ok && only_first)
+      free_fdbe(&fdbe_old);
+  }
+  if (fdb_old != fdb_new)
+    filedb_close(fdb_new);
+  filedb_close(fdb_old);
+  if (!ok)
+    dprintf(idx, _("No matching files.\n"));
+  else {
+    putlog(LOG_FILES, "*", "files: #%s# %s %s%s%s %s", dcc[idx].nick,
+	   copy ? "cp" : "mv", oldpath, oldpath[0] ? "/" : "", fn, par);
+    if (ok > 1)
+      dprintf(idx, "%s %d file%s.\n",
+	      copy ? _("Copied") : _("Moved"), ok, ok == 1 ? "" : "s");
+  }
+  free_null(oldpath);
+  free_null(newpath);
+  free_null(newfn);
+}
+
+static void cmd_mv(int idx, char *par)
+{
+  cmd_mv_cp(idx, par, 0);
+}
+
+static void cmd_cp(int idx, char *par)
+{
+  cmd_mv_cp(idx, par, 1);
+}
+
+static int cmd_stats(int idx, char *par)
+{
+  putlog(LOG_FILES, "*", "#%s# stats", dcc[idx].nick);
+  tell_file_stats(idx, dcc[idx].nick);
+  return 0;
+}
+
+static int cmd_filestats(int idx, char *par)
+{
+  char *nick;
+  struct userrec *u;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: filestats <user>\n");
+    return 0;
+  }
+  nick = newsplit(&par);
+  putlog(LOG_FILES, "*", "#%s# filestats %s", dcc[idx].nick, nick);
+  if (nick[0] == 0)
+    tell_file_stats(idx, dcc[idx].nick);
+  else if (!(u = get_user_by_handle(userlist, nick)))
+    dprintf(idx, "No such user.\n");
+  else if (!strcmp(par, "clear") && dcc[idx].user &&
+	   (dcc[idx].user->flags & USER_JANITOR)) {
+    set_user (&USERENTRY_FSTAT, u, NULL);
+    dprintf(idx, "Cleared filestats for %s.\n", nick);
+  } else
+    tell_file_stats(idx, nick);
+  return 0;
+}
+
+/* This function relays the dcc call to cmd_note() in the notes module,
+ * if loaded.
+ */
+static void filesys_note(int idx, char *par)
+{
+  struct userrec *u = get_user_by_handle(userlist, dcc[idx].nick);
+  module_entry *me = module_find("notes", 2, 1);
+
+  if (me && me->funcs) {
+    Function f = me->funcs[NOTES_CMD_NOTE];
+
+    (f) (u, idx, par);
+  } else {
+    dprintf(idx, "Sending of notes is not supported.\n");
+  }
+}
+
+static cmd_t myfiles[] =
+{
+  {"cancel",	"",	(Function) cmd_cancel,		NULL},
+  {"cd",	"",	(Function) cmd_chdir,		NULL},
+  {"chdir",	"",	(Function) cmd_chdir,		NULL},
+  {"cp",	"j",	(Function) cmd_cp,		NULL},
+  {"desc",	"",	(Function) cmd_desc,		NULL},
+  {"filestats",	"j",	(Function) cmd_filestats,	NULL},
+  {"get",	"",	(Function) cmd_get,		NULL},
+  {"reget",	"",	(Function) cmd_reget,		NULL},
+  {"help",	"",	(Function) cmd_file_help,	NULL},
+  {"hide",	"j",	(Function) cmd_hide,		NULL},
+  {"ln",	"j",	(Function) cmd_ln,		NULL},
+  {"ls",	"",	(Function) cmd_ls,		NULL},
+  {"lsa",	"j",	(Function) cmd_lsa,		NULL},
+  {"mkdir",	"j",	(Function) cmd_mkdir,		NULL},
+  {"mv",	"j",	(Function) cmd_mv,		NULL},
+  {"note",	"",	(Function) filesys_note,	NULL},
+  {"pending",	"",	(Function) cmd_pending,		NULL},
+  {"pwd",	"",	(Function) cmd_pwd,		NULL},
+  {"quit",	"",	(Function) CMD_LEAVE,		NULL},
+  {"rm",	"j",	(Function) cmd_rm,		NULL},
+  {"rmdir",	"j",	(Function) cmd_rmdir,		NULL},
+  {"share",	"j",	(Function) cmd_share,		NULL},
+  {"optimise",	"j",	(Function) cmd_optimise,	NULL},
+  {"stats",	"",	(Function) cmd_stats,		NULL},
+  {"unhide",	"j",	(Function) cmd_unhide,		NULL},
+  {"unshare",	"j",	(Function) cmd_unshare,		NULL},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+
+/*
+ *    Tcl stub functions
+ */
+
+static int files_reget(int idx, char *fn, char *nick, int resend)
+{
+  int i = 0;
+  char *p = NULL, *what = NULL, *destdir, *s = NULL;
+  filedb_entry *fdbe = NULL;
+  FILE *fdb = NULL;
+
+  p = strrchr(fn, '/');
+  if (p != NULL) {
+    *p = 0;
+    realloc_strcpy(s, fn);
+    realloc_strcpy(what, p + 1);
+    if (!resolve_dir(dcc[idx].u.file->dir, s, &destdir, idx)) {
+      free_null(s);
+      free_null(what);
+      free_null(destdir);
+      return 0;
+    }
+    free_null(s);
+  } else {
+    realloc_strcpy(destdir, dcc[idx].u.file->dir);
+    realloc_strcpy(what, fn);
+  }
+  fdb = filedb_open(destdir, 0);
+  if (!fdb) {
+    free_null(what);
+    free_null(destdir);
+    return 0;
+  }
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), what);
+  if (!fdbe) {
+    filedb_close(fdb);
+    free_null(what);
+    free_null(destdir);
+    return 0;
+  }
+  if (fdbe->stat & (FILE_HIDDEN | FILE_DIR)) {
+    filedb_close(fdb);
+    free_null(what);
+    free_null(destdir);
+    free_fdbe(&fdbe);
+    return 0;
+  }
+  if (fdbe->sharelink) {
+    char *bot, *whoto = NULL;
+
+    /* This is a link to a file on another bot... */
+    bot = malloc(strlen(fdbe->sharelink) + 1);
+    splitc(bot, fdbe->sharelink, ':');
+    if (!strcasecmp(bot, botnetnick)) {
+      /* Linked to myself *duh* */
+      filedb_close(fdb);
+      free_null(what);
+      free_null(destdir);
+      free_null(bot);
+      free_fdbe(&fdbe);
+      return 0;
+    } else if (!in_chain(bot)) {
+      filedb_close(fdb);
+      free_null(what);
+      free_null(destdir);
+      free_null(bot);
+      free_fdbe(&fdbe);
+      return 0;
+    } else {
+      i = nextbot(bot);
+      if (nick[0]) {
+        realloc_strcpy(whoto, nick);
+      } else {
+	realloc_strcpy(whoto, dcc[idx].nick);
+      }
+      s = malloc(strlen(whoto) + strlen(botnetnick) + 13);
+      simple_sprintf(s, "%d:%s@%s", dcc[idx].sock, whoto, botnetnick);
+      botnet_send_filereq(i, s, bot, fdbe->sharelink);
+      dprintf(idx, _("Requested %s from %s ...\n"), fdbe->sharelink, bot);
+      /* Increase got count now (or never) */
+      fdbe->gots++;
+      s = realloc(s, strlen(bot) + strlen(fdbe->sharelink) + 2);
+      sprintf(s, "%s:%s", bot, fdbe->sharelink);
+      realloc_strcpy(fdbe->sharelink, s);
+      filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+      filedb_close(fdb);
+      free_fdbe(&fdbe);
+      free_null(what);
+      free_null(destdir);
+      free_null(bot);
+      free_null(whoto);
+      free_null(s);
+      return 1;
+    }
+  }
+  filedb_close(fdb);
+  do_dcc_send(idx, destdir, fdbe->filename, nick, resend);
+  free_null(what);
+  free_null(destdir);
+  free_fdbe(&fdbe);
+  /* Don't increase got count till later */
+  return 1;
+}
+
+static void files_setpwd(int idx, char *where)
+{
+  char *s;
+
+  if (!resolve_dir(dcc[idx].u.file->dir, where, &s, idx))
+    return;
+  strcpy(dcc[idx].u.file->dir, s);
+  set_user(&USERENTRY_DCCDIR, get_user_by_handle(userlist, dcc[idx].nick),
+	   dcc[idx].u.file->dir);
+  free_null(s);
+}
Index: eggdrop1.7/modules/filesys/files.h
diff -u /dev/null eggdrop1.7/modules/filesys/files.h:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/files.h	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,28 @@
+/*
+ * files.h -- part of filesys.mod
+ *
+ * $Id: files.h,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_FILESYS_FILES_H
+#define _EGG_MOD_FILESYS_FILES_H
+
+#endif				/* _EGG_MOD_FILESYS_FILES_H */
Index: eggdrop1.7/modules/filesys/filesys.c
diff -u /dev/null eggdrop1.7/modules/filesys/filesys.c:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/filesys.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,958 @@
+/*
+ * filesys.c -- part of filesys.mod
+ *   main file of the filesys eggdrop module
+ *
+ * $Id: filesys.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#define MODULE_NAME "filesys"
+#define MAKING_FILESYS
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <sys/file.h>
+#if HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# if HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# if HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# if HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+#endif
+#include "lib/eggdrop/module.h"
+#include "filedb3.h"
+#include "filesys.h"
+#include "src/tandem.h"
+#include "files.h"
+#include "dbcompat.h"
+#include "filelist.h"
+
+#define start filesys_LTX_start
+
+static bind_table_t *BT_dcc, *BT_load, *BT_file;
+static Function *transfer_funcs = NULL;
+
+/* fcntl.h sets this :/ */
+#undef global
+static Function *global = NULL;
+
+/* Root dcc directory */
+static char dccdir[121] = "";
+
+/* Directory to put incoming dcc's into */
+static char dccin[121] = "";
+
+/* Let all uploads go to the user's current directory? */
+static int upload_to_cd = 0;
+
+/* Maximum allowable file size for dcc send (1M). 0 indicates
+ * unlimited file size.
+ */
+static int dcc_maxsize = 1024;
+
+/* Maximum number of users can be in the file area at once */
+static int dcc_users = 0;
+
+/* Where to put the filedb, if not in a hidden '.filedb' file in
+ * each directory.
+ */
+static char filedb_path[121] = "";
+
+/* Prototypes */
+static int is_valid();
+static void eof_dcc_files(int idx);
+static void dcc_files(int idx, char *buf, int i);
+static void disp_dcc_files(int idx, char *buf);
+static void kill_dcc_files(int idx, void *x);
+static void out_dcc_files(int idx, char *buf, void *x);
+static char *mktempfile(char *filename);
+
+static struct dcc_table DCC_FILES =
+{
+  "FILES",
+  DCT_MASTER | DCT_VALIDIDX | DCT_SHOWWHO | DCT_SIMUL | DCT_CANBOOT | DCT_FILES,
+  eof_dcc_files,
+  dcc_files,
+  NULL,
+  NULL,
+  disp_dcc_files,
+  kill_dcc_files,
+  out_dcc_files
+};
+
+static struct user_entry_type USERENTRY_DCCDIR =
+{
+  NULL,				/* always NULL ;) */
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "DCCDIR"
+};
+
+#include "files.c"
+#include "filedb3.c"
+#include "tclfiles.c"
+#include "dbcompat.c"
+#include "filelist.c"
+
+/* Check for tcl-bound file command, return 1 if found
+ * fil: proc-name <handle> <dcc-handle> <args...>
+ */
+static int check_tcl_fil(char *cmd, int idx, char *text)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  int x;
+
+  get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.file->chat->con_chan);
+  x = check_bind(BT_file, cmd, &fr, dcc[idx].nick, dcc[idx].sock, text);
+  if (x & BIND_RET_LOG) {
+    putlog(LOG_FILES, "*", "#%s# files: %s %s", dcc[idx].nick, cmd, text);
+  }
+  return 0;
+}
+
+static void dcc_files_pass(int idx, char *buf, int x)
+{
+  struct userrec *u = get_user_by_handle(userlist, dcc[idx].nick);
+
+  if (!x)
+    return;
+  if (u_pass_match(u, buf)) {
+    if (too_many_filers()) {
+      dprintf(idx, "Too many people are in the file system right now.\n");
+      dprintf(idx, "Please try again later.\n");
+      putlog(LOG_MISC, "*", "File area full: DCC chat [%s]%s", dcc[idx].nick,
+	     dcc[idx].host);
+      killsock(dcc[idx].sock);
+      lostdcc(idx);
+      return;
+    }
+    dcc[idx].type = &DCC_FILES;
+    if (dcc[idx].status & STAT_TELNET)
+      dprintf(idx, "\377\374\001\n");	/* turn echo back on */
+    putlog(LOG_FILES, "*", "File system: [%s]%s/%d", dcc[idx].nick,
+	   dcc[idx].host, dcc[idx].port);
+    if (!welcome_to_files(idx)) {
+      putlog(LOG_FILES, "*", "File system broken.");
+      killsock(dcc[idx].sock);
+      lostdcc(idx);
+    } else {
+      struct userrec *u = get_user_by_handle(userlist, dcc[idx].nick);
+
+      touch_laston(u, "filearea", now);
+    }
+    return;
+  }
+  dprintf(idx, "Negative on that, Houston.\n");
+  putlog(LOG_MISC, "*", "Bad password: DCC chat [%s]%s", dcc[idx].nick,
+	 dcc[idx].host);
+  killsock(dcc[idx].sock);
+  lostdcc(idx);
+}
+
+/* Hash function for file area commands.
+ */
+static int got_files_cmd(int idx, char *msg)
+{
+  char *code;
+
+  strcpy(msg, check_tcl_filt(idx, msg));
+  if (!msg[0])
+    return 1;
+  if (msg[0] == '.')
+    msg++;
+  code = newsplit(&msg);
+  return check_tcl_fil(code, idx, msg);
+}
+
+static void dcc_files(int idx, char *buf, int i)
+{
+  if (buf[0] &&
+      detect_dcc_flood(&dcc[idx].timeval, dcc[idx].u.file->chat, idx))
+    return;
+  dcc[idx].timeval = now;
+  strcpy(buf, check_tcl_filt(idx, buf));
+  if (!buf[0])
+    return;
+  touch_laston(dcc[idx].user, "filearea", now);
+  if (buf[0] == ',') {
+    for (i = 0; i < dcc_total; i++) {
+      if ((dcc[i].type->flags & DCT_MASTER) && dcc[idx].user &&
+	  (dcc[idx].user->flags & USER_MASTER) &&
+	  ((dcc[i].type == &DCC_FILES) ||
+	   (dcc[i].u.chat->channel >= 0)) &&
+	  ((i != idx) || (dcc[idx].status & STAT_ECHO)))
+	dprintf(i, "-%s- %s\n", dcc[idx].nick, &buf[1]);
+    }
+  } else if (got_files_cmd(idx, buf)) {
+    dprintf(idx, "*** Ja mata!\n");
+    flush_lines(idx, dcc[idx].u.file->chat);
+    putlog(LOG_FILES, "*", "DCC user [%s]%s left file system", dcc[idx].nick,
+	   dcc[idx].host);
+    set_user(&USERENTRY_DCCDIR, dcc[idx].user, dcc[idx].u.file->dir);
+    if (dcc[idx].status & STAT_CHAT) {
+      struct chat_info *ci;
+
+      dprintf(idx, "Returning you to command mode...\n");
+      ci = dcc[idx].u.file->chat;
+      free_null(dcc[idx].u.file);
+      dcc[idx].u.chat = ci;
+      dcc[idx].status &= (~STAT_CHAT);
+      dcc[idx].type = &DCC_CHAT;
+      if (dcc[idx].u.chat->channel >= 0) {
+	chanout_but(-1, dcc[idx].u.chat->channel,
+		    "*** %s has returned.\n", dcc[idx].nick);
+	if (dcc[idx].u.chat->channel < 100000)
+	  botnet_send_join_idx(idx, -1);
+      }
+    } else {
+      dprintf(idx, "Dropping connection now.\n");
+      putlog(LOG_FILES, "*", "Left files: [%s]%s/%d", dcc[idx].nick,
+	     dcc[idx].host, dcc[idx].port);
+      killsock(dcc[idx].sock);
+      lostdcc(idx);
+    }
+  }
+  if (dcc[idx].status & STAT_PAGE)
+    flush_lines(idx, dcc[idx].u.file->chat);
+}
+
+static void tell_file_stats(int idx, char *hand)
+{
+  struct userrec *u;
+  struct filesys_stats *fs;
+  float fr = (-1.0), kr = (-1.0);
+
+  u = get_user_by_handle(userlist, hand);
+  if (u == NULL)
+    return;
+  if (!(fs = get_user(&USERENTRY_FSTAT, u))) {
+    dprintf(idx, "No file statistics for %s.\n", hand);
+  } else {
+    dprintf(idx, "  uploads: %4u / %6luk\n", fs->uploads, fs->upload_ks);
+    dprintf(idx, "downloads: %4u / %6luk\n", fs->dnloads, fs->dnload_ks);
+    if (fs->uploads)
+      fr = ((float) fs->dnloads / (float) fs->uploads);
+    if (fs->upload_ks)
+      kr = ((float) fs->dnload_ks / (float) fs->upload_ks);
+    if (fr < 0.0)
+      dprintf(idx, "(infinite file leech)\n");
+    else
+      dprintf(idx, "leech ratio (files): %6.2f\n", fr);
+    if (kr < 0.0)
+      dprintf(idx, "(infinite size leech)\n");
+    else
+      dprintf(idx, "leech ratio (size) : %6.2f\n", kr);
+  }
+}
+
+static int cmd_files(struct userrec *u, int idx, char *par)
+{
+  int atr = u ? u->flags : 0;
+  static struct chat_info *ci;
+
+  if (dccdir[0] == 0)
+    dprintf(idx, "There is no file transfer area.\n");
+  else if (too_many_filers()) {
+    dprintf(idx, "The maximum of %d people are in the file area right now.\n",
+	    dcc_users);
+    dprintf(idx, "Please try again later.\n");
+  } else {
+    if (!(atr & (USER_MASTER | USER_XFER)))
+      dprintf(idx, "You don't have access to the file area.\n");
+    else {
+      putlog(LOG_CMDS, "*", "#%s# files", dcc[idx].nick);
+      dprintf(idx, "Entering file system...\n");
+      if (dcc[idx].u.chat->channel >= 0) {
+
+	chanout_but(-1, dcc[idx].u.chat->channel,
+		    "*** %s has left: file system\n",
+		    dcc[idx].nick);
+	if (dcc[idx].u.chat->channel < 100000)
+	  botnet_send_part_idx(idx, "file system");
+      }
+      ci = dcc[idx].u.chat;
+      dcc[idx].u.file = calloc(1, sizeof(struct file_info));
+      dcc[idx].u.file->chat = ci;
+      dcc[idx].type = &DCC_FILES;
+      dcc[idx].status |= STAT_CHAT;
+      if (!welcome_to_files(idx)) {
+	struct chat_info *ci = dcc[idx].u.file->chat;
+
+	free_null(dcc[idx].u.file);
+	dcc[idx].u.chat = ci;
+	dcc[idx].type = &DCC_CHAT;
+	putlog(LOG_FILES, "*", "File system broken.");
+	if (dcc[idx].u.chat->channel >= 0) {
+	  chanout_but(-1, dcc[idx].u.chat->channel,
+		      "*** %s has returned.\n",
+		      dcc[idx].nick);
+	  if (dcc[idx].u.chat->channel < 100000)
+	    botnet_send_join_idx(idx, -1);
+	}
+      } else
+	touch_laston(u, "filearea", now);
+    }
+  }
+  return 0;
+}
+
+static int _dcc_send(int idx, char *filename, char *nick, char *dir,
+		     int resend)
+{
+  int x;
+  char *nfn, *buf = NULL;
+
+  if (strlen(nick) > NICKMAX)
+    nick[NICKMAX] = 0;
+  if (resend)
+    x = raw_dcc_resend(filename, nick, dcc[idx].nick, dir, 0);
+  else
+    x = raw_dcc_send(filename, nick, dcc[idx].nick, dir, 0);
+  if (x == DCCSEND_FULL) {
+    dprintf(idx, "Sorry, too many DCC connections.  (try again later)\n");
+    putlog(LOG_FILES, "*", "DCC connections full: %sGET %s [%s]", filename,
+	   resend ? "RE" : "", dcc[idx].nick);
+    return 0;
+  }
+  if (x == DCCSEND_NOSOCK) {
+    if (reserved_port_min) {
+      dprintf(idx, "All my DCC SEND ports are in use.  Try later.\n");
+      putlog(LOG_FILES, "*", "DCC port in use (can't open): %sGET %s [%s]",
+	     resend ? "RE" : "", filename, dcc[idx].nick);
+    } else {
+      dprintf(idx, "Unable to listen at a socket.\n");
+      putlog(LOG_FILES, "*", "DCC socket error: %sGET %s [%s]", filename,
+	     resend ? "RE" : "", dcc[idx].nick);
+    }
+    return 0;
+  }
+  if (x == DCCSEND_BADFN) {
+    dprintf(idx, "File not found ?\n");
+    putlog(LOG_FILES, "*", "DCC file not found: %sGET %s [%s]", filename,
+	   resend ? "RE" : "", dcc[idx].nick);
+    return 0;
+  }
+  if (x == DCCSEND_FEMPTY) {
+    dprintf(idx, "The file is empty.  Aborted transfer.\n");
+    putlog(LOG_FILES, "*", "DCC file is empty: %s [%s]", filename,
+	   dcc[idx].nick);
+    return 0;
+  }
+  nfn = strrchr(dir, '/');
+  if (nfn == NULL)
+    nfn = dir;
+  else
+    nfn++;
+
+  /* Eliminate any spaces in the filename. */
+  if (strchr(nfn, ' ')) {
+    char *p;
+
+    realloc_strcpy(buf, nfn);
+    p = nfn = buf;
+    while ((p = strchr(p, ' ')) != NULL)
+      *p = '_';
+  }
+
+  if (strcasecmp(nick, dcc[idx].nick))
+    dprintf(DP_HELP, "NOTICE %s :Here is %s file from %s %s...\n", nick,
+	    resend ? "the" : "a", dcc[idx].nick, resend ? "again " : "");
+  dprintf(idx, "%sending: %s to %s\n", resend ? "Res" : "S", nfn, nick);
+  free_null(buf);
+  return 1;
+}
+
+static int do_dcc_send(int idx, char *dir, char *fn, char *nick, int resend)
+{
+  char *s = NULL, *s1 = NULL;
+  FILE *f;
+  int x;
+
+  if (nick && strlen(nick) > NICKMAX)
+    nick[NICKMAX] = 0;
+  if (dccdir[0] == 0) {
+    dprintf(idx, "DCC file transfers not supported.\n");
+    putlog(LOG_FILES, "*", "Refused dcc %sget %s from [%s]", resend ? "re" : "",
+	   fn, dcc[idx].nick);
+    return 0;
+  }
+  if (strchr(fn, '/') != NULL) {
+    dprintf(idx, "Filename cannot have '/' in it...\n");
+    putlog(LOG_FILES, "*", "Refused dcc %sget %s from [%s]", resend ? "re" : "",
+	   fn, dcc[idx].nick);
+    return 0;
+  }
+  if (dir[0]) {
+    s = malloc(strlen(dccdir) + strlen(dir) + strlen(fn) + 2);
+    sprintf(s, "%s%s/%s", dccdir, dir, fn);
+  } else {
+    s = malloc(strlen(dccdir) + strlen(fn) + 1);
+    sprintf(s, "%s%s", dccdir, fn);
+  }
+  f = fopen(s, "r");
+  if (f == NULL) {
+    dprintf(idx, "No such file.\n");
+    putlog(LOG_FILES, "*", "Refused dcc %sget %s from [%s]", resend ? "re" : "",
+	   fn, dcc[idx].nick);
+    free_null(s);
+    return 0;
+  }
+  fclose(f);
+  if (!nick || !nick[0])
+    nick = dcc[idx].nick;
+  /* Already have too many transfers active for this user?  queue it */
+  if (at_limit(nick)) {
+    char xxx[1024];
+
+    sprintf(xxx, "%d*%s%s", strlen(dccdir), dccdir, dir);
+    queue_file(xxx, fn, dcc[idx].nick, nick);
+    dprintf(idx, "Queued: %s to %s\n", fn, nick);
+    free_null(s);
+    return 1;
+  }
+  if (copy_to_tmp) {
+    char *tempfn = mktempfile(fn);
+
+    /* Copy this file to /tmp, add a random prefix to the filename. */
+    s = realloc(s, strlen(dccdir) + strlen(dir) + strlen(fn) + 2);
+    sprintf(s, "%s%s%s%s", dccdir, dir, dir[0] ? "/" : "", fn);
+    s1 = realloc(s1, strlen(tempdir) + strlen(tempfn) + 1);
+    sprintf(s1, "%s%s", tempdir, tempfn);
+    free_null(tempfn);
+    if (copyfile(s, s1) != 0) {
+      dprintf(idx, "Can't make temporary copy of file!\n");
+      putlog(LOG_FILES | LOG_MISC, "*",
+	     "Refused dcc %sget %s: copy to %s FAILED!",
+	     resend ? "re" : "", fn, tempdir);
+      free_null(s);
+      free_null(s1);
+      return 0;
+    }
+  } else {
+    s1 = realloc(s1, strlen(dccdir) + strlen(dir) + strlen(fn) + 2);
+    sprintf(s1, "%s%s%s%s", dccdir, dir, dir[0] ? "/" : "", fn);
+  }
+  s = realloc(s, strlen(dir) + strlen(fn) + 2);
+  sprintf(s, "%s%s%s", dir, dir[0] ? "/" : "", fn);
+  x = _dcc_send(idx, s1, nick, s, resend);
+  if (x != DCCSEND_OK)
+    wipe_tmp_filename(s1, -1);
+  free_null(s);
+  free_null(s1);
+  return x;
+}
+
+static void tout_dcc_files_pass(int i)
+{
+  dprintf(i, "Timeout.\n");
+  putlog(LOG_MISC, "*", "Password timeout on dcc chat: [%s]%s", dcc[i].nick,
+	 dcc[i].host);
+  killsock(dcc[i].sock);
+  lostdcc(i);
+}
+
+static void disp_dcc_files(int idx, char *buf)
+{
+  sprintf(buf, "file  flags: %c%c%c%c%c",
+	  dcc[idx].status & STAT_CHAT ? 'C' : 'c',
+	  dcc[idx].status & STAT_PARTY ? 'P' : 'p',
+	  dcc[idx].status & STAT_TELNET ? 'T' : 't',
+	  dcc[idx].status & STAT_ECHO ? 'E' : 'e',
+	  dcc[idx].status & STAT_PAGE ? 'P' : 'p');
+}
+
+static void disp_dcc_files_pass(int idx, char *buf)
+{
+  sprintf(buf, "fpas  waited %lus", now - dcc[idx].timeval);
+}
+
+static void kill_dcc_files(int idx, void *x)
+{
+  register struct file_info *f = (struct file_info *) x;
+
+  if (f->chat)
+    DCC_CHAT.kill(idx, f->chat);
+  free_null(x);
+}
+
+static void eof_dcc_files(int idx)
+{
+  dcc[idx].u.file->chat->con_flags = 0;
+  putlog(LOG_MISC, "*", "Lost dcc connection to %s (%s/%d)", dcc[idx].nick,
+	 dcc[idx].host, dcc[idx].port);
+  killsock(dcc[idx].sock);
+  lostdcc(idx);
+}
+
+static void out_dcc_files(int idx, char *buf, void *x)
+{
+  register struct file_info *p = (struct file_info *) x;
+
+  if (p->chat)
+    DCC_CHAT.output(idx, buf, p->chat);
+  else
+    tputs(dcc[idx].sock, buf, strlen(buf));
+}
+
+static cmd_t mydcc[] =
+{
+  {"files",		"-",	cmd_files,		NULL},
+  {NULL,		NULL,	NULL,			NULL}
+};
+
+static tcl_strings mystrings[] =
+{
+  {"files-path",	dccdir,		120,	STR_DIR | STR_PROTECT},
+  {"incoming-path",	dccin,		120,	STR_DIR | STR_PROTECT},
+  {"filedb-path",	filedb_path,	120,	STR_DIR | STR_PROTECT},
+  {NULL,		NULL,		0,	0}
+};
+
+static tcl_ints myints[] =
+{
+  {"max-filesize",	&dcc_maxsize},
+  {"max-file-users",	&dcc_users},
+  {"upload-to-pwd",	&upload_to_cd},
+  {NULL,		NULL}
+};
+
+static struct dcc_table DCC_FILES_PASS =
+{
+  "FILES_PASS",
+  0,
+  eof_dcc_files,
+  dcc_files_pass,
+  NULL,
+  tout_dcc_files_pass,
+  disp_dcc_files_pass,
+  kill_dcc_files,
+  out_dcc_files
+};
+
+
+static void filesys_dcc_send_hostresolved(int);
+
+/* Received a ctcp-dcc.
+ */
+static void filesys_dcc_send(char *nick, char *from, struct userrec *u,
+			     char *text)
+{
+  char *param, *ip, *prt, *buf = NULL, *msg;
+  int atr = u ? u->flags : 0, i;
+
+  buf = malloc(strlen(text) + 1);
+  msg = buf;
+  strcpy(buf, text);
+  param = newsplit(&msg);
+  if (!(atr & USER_XFER)) {
+    putlog(LOG_FILES, "*",
+	   "Refused DCC SEND %s (no access): %s!%s", param,
+	   nick, from);
+  } else if (!dccin[0] && !upload_to_cd) {
+    dprintf(DP_HELP,
+	    "NOTICE %s :DCC file transfers not supported.\n", nick);
+    putlog(LOG_FILES, "*",
+	   "Refused dcc send %s from %s!%s", param, nick, from);
+  } else if (strchr(param, '/')) {
+    dprintf(DP_HELP,
+	    "NOTICE %s :Filename cannot have '/' in it...\n", nick);
+    putlog(LOG_FILES, "*",
+	   "Refused dcc send %s from %s!%s", param, nick, from);
+  } else {
+    ip = newsplit(&msg);
+    prt = newsplit(&msg);
+    if (atoi(prt) < 1024 || atoi(prt) > 65535) {
+      /* Invalid port */
+      dprintf(DP_HELP, "NOTICE %s :%s (invalid port)\n", nick,
+	      _("Failed to connect"));
+      putlog(LOG_FILES, "*", "Refused dcc send %s (%s): invalid port", param,
+	     nick);
+    } else if (atoi(msg) == 0) {
+      dprintf(DP_HELP,
+	      "NOTICE %s :Sorry, file size info must be included.\n",
+	      nick);
+      putlog(LOG_FILES, "*", "Refused dcc send %s (%s): no file size",
+	     param, nick);
+    } else if (dcc_maxsize && (atoi(msg) > (dcc_maxsize * 1024))) {
+      dprintf(DP_HELP, "NOTICE %s :Sorry, file too large.\n", nick);
+      putlog(LOG_FILES, "*", "Refused dcc send %s (%s): file too large", param,
+	     nick);
+    } else {
+      struct in_addr ip4;
+      i = new_dcc(&DCC_DNSWAIT, sizeof(struct dns_info));
+      if (i < 0) {
+	dprintf(DP_HELP, "NOTICE %s :Sorry, too many DCC connections.\n",
+		nick);
+	putlog(LOG_MISC, "*", "DCC connections full: SEND %s (%s!%s)",
+	       param, nick, from);
+	return;
+      }
+debug1("|FILESYS| dcc send ip: (%s)", ip);
+    if (inet_aton(ip, &ip4)) /* fix the format! */
+	strncpyz(dcc[i].addr, inet_ntoa(ip4), ADDRLEN);
+    else
+	strncpyz(dcc[i].addr, ip, ADDRLEN);
+debug1("|FILESYS| addr: (%s)", dcc[i].addr);
+      dcc[i].port = atoi(prt);
+      dcc[i].sock = (-1);
+      dcc[i].user = u;
+      strcpy(dcc[i].nick, nick);
+      strcpy(dcc[i].host, from);
+      dcc[i].u.dns->cbuf = calloc(1, strlen(param) + 1);
+      strcpy(dcc[i].u.dns->cbuf, param);
+      dcc[i].u.dns->ibuf = atoi(msg);
+      
+      dcc[i].u.dns->host = calloc(1, strlen(dcc[i].addr) + 1);
+      strcpy(dcc[i].u.dns->host, dcc[i].addr);
+
+      dcc[i].u.dns->dns_type = RES_HOSTBYIP;
+      dcc[i].u.dns->dns_success = filesys_dcc_send_hostresolved;
+      dcc[i].u.dns->dns_failure = filesys_dcc_send_hostresolved;
+      dcc[i].u.dns->type = &DCC_FORK_SEND;
+      dcc_dnshostbyip(dcc[i].addr);
+    }
+  }
+  free_null(buf);
+}
+
+/* Create a temporary filename with random elements. Shortens
+ * the filename if the total string is longer than NAME_MAX.
+ * The original buffer is not modified.   (Fabian)
+ *
+ * Please adjust MKTEMPFILE_TOT if you change any lengths
+ *   7 - size of the random string
+ *   2 - size of additional characters in "%u-%s-%s" format string
+ *   8 - estimated size of getpid()'s output converted to %u */
+#define MKTEMPFILE_TOT (7 + 2 + 8)
+static char *mktempfile(char *filename)
+{
+  char rands[8], *tempname, *fn = filename;
+  int l;
+
+  make_rand_str(rands, 7);
+  l = strlen(filename);
+  if ((l + MKTEMPFILE_TOT) > NAME_MAX) {
+    fn[NAME_MAX - MKTEMPFILE_TOT] = 0;
+    l = NAME_MAX - MKTEMPFILE_TOT;
+    fn = malloc(l + 1);
+    strncpy(fn, filename, l);
+    fn[l] = 0;
+  }
+  tempname = malloc(l + MKTEMPFILE_TOT + 1);
+  sprintf(tempname, "%u-%s-%s", getpid(), rands, fn);
+  if (fn != filename)
+    free_null(fn);
+  return tempname;
+}
+
+static void filesys_dcc_send_hostresolved(int i)
+{
+  FILE *f;
+  char *s1, *param = NULL, prt[100], *tempf;
+  int len = dcc[i].u.dns->ibuf, j;
+
+  sprintf(prt, "%d", dcc[i].port);
+  realloc_strcpy(param, dcc[i].u.dns->cbuf);
+
+  changeover_dcc(i, &DCC_FORK_SEND, sizeof(struct xfer_info));
+  if (param[0] == '.')
+    param[0] = '_';
+  /* Save the original filename */
+  dcc[i].u.xfer->origname = calloc(1, strlen(param) + 1);
+  strcpy(dcc[i].u.xfer->origname, param);
+  tempf = mktempfile(param);
+  dcc[i].u.xfer->filename = calloc(1, strlen(tempf) + 1);
+  strcpy(dcc[i].u.xfer->filename, tempf);
+  /* We don't need the temporary buffers anymore */
+  free_null(tempf);
+  free_null(param);
+
+  if (upload_to_cd) {
+    char *p = get_user(&USERENTRY_DCCDIR, dcc[i].user);
+
+    if (p)
+      sprintf(dcc[i].u.xfer->dir, "%s%s/", dccdir, p);
+    else
+      sprintf(dcc[i].u.xfer->dir, "%s", dccdir);
+  } else
+    strcpy(dcc[i].u.xfer->dir, dccin);
+  dcc[i].u.xfer->length = len;
+  s1 = malloc(strlen(dcc[i].u.xfer->dir) +
+	       strlen(dcc[i].u.xfer->origname) + 1);
+  sprintf(s1, "%s%s", dcc[i].u.xfer->dir, dcc[i].u.xfer->origname);
+  f = fopen(s1, "r");
+  free_null(s1);
+  if (f) {
+    fclose(f);
+    dprintf(DP_HELP, "NOTICE %s :File `%s' already exists.\n",
+	    dcc[i].nick, dcc[i].u.xfer->origname);
+    lostdcc(i);
+  } else {
+    /* Check for dcc-sends in process with the same filename */
+    for (j = 0; j < dcc_total; j++)
+      if (j != i) {
+        if ((dcc[j].type->flags & (DCT_FILETRAN | DCT_FILESEND))
+	    == (DCT_FILETRAN | DCT_FILESEND)) {
+	  if (!strcmp(dcc[i].u.xfer->origname, dcc[j].u.xfer->origname)) {
+	    dprintf(DP_HELP, "NOTICE %s :File `%s' is already being sent.\n",
+		    dcc[i].nick, dcc[i].u.xfer->origname);
+	    lostdcc(i);
+	    return;
+	  }
+	}
+      }
+    /* Put uploads in /tmp first */
+    s1 = malloc(strlen(tempdir) + strlen(dcc[i].u.xfer->filename) + 1);
+    sprintf(s1, "%s%s", tempdir, dcc[i].u.xfer->filename);
+    dcc[i].u.xfer->f = fopen(s1, "w");
+    free_null(s1);
+    if (dcc[i].u.xfer->f == NULL) {
+      dprintf(DP_HELP,
+	      "NOTICE %s :Can't create file `%s' (temp dir error)\n",
+	      dcc[i].nick, dcc[i].u.xfer->origname);
+      lostdcc(i);
+    } else {
+      dcc[i].timeval = now;
+      dcc[i].sock = getsock(SOCK_BINARY);
+      if (dcc[i].sock < 0 || open_telnet_dcc(dcc[i].sock, dcc[i].addr, prt) < 0)
+	dcc[i].type->eof(i);
+    }
+  }
+}
+
+/* This only handles CHAT requests, otherwise it's handled in filesys.
+ */
+static int filesys_DCC_CHAT(char *nick, char *from, char *handle,
+			    char *object, char *keyword, char *text)
+{
+  char *param, *ip, *prt, buf[512], *msg = buf;
+  int i, sock;
+  struct userrec *u = get_user_by_handle(userlist, handle);
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0};
+
+  if (strcasecmp(object, botname))
+    return 0;
+  if (!strncasecmp(text, "SEND ", 5)) {
+    filesys_dcc_send(nick, from, u, text + 5);
+    return 1;
+  }
+  if (strncasecmp(text, "CHAT ", 5) || !u)
+    return 0;
+  strcpy(buf, text + 5);
+  get_user_flagrec(u, &fr, 0);
+  param = newsplit(&msg);
+  if (dcc_total == max_dcc) {
+    putlog(LOG_MISC, "*", _("DCC connections full: %s %s (%s!%s)"), "CHAT(file)", param, nick, from);
+  } else if (glob_party(fr) || (!require_p && chan_op(fr)))
+    return 0;			/* Allow ctcp.so to pick up the chat */
+  else if (!glob_xfer(fr)) {
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("No access"));
+    putlog(LOG_MISC, "*", "%s: %s!%s", _("Refused DCC chat (no access)"), nick, from);
+  } else if (u_pass_match(u, "-")) {
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You must have a password set."));
+    putlog(LOG_MISC, "*", "%s: %s!%s", _("Refused DCC chat (no password)"), nick, from);
+  } else if (!dccdir[0]) {
+    putlog(LOG_MISC, "*", "%s: %s!%s", _("Refused DCC chat (+x but no file area)"), nick, from);
+  } else {
+    ip = newsplit(&msg);
+    prt = newsplit(&msg);
+    sock = getsock(0);
+    if (sock < 0 || open_telnet_dcc(sock, ip, prt) < 0) {
+      neterror(buf);
+      if (!quiet_reject)
+        dprintf(DP_HELP, "NOTICE %s :%s (%s)\n", nick,
+	        _("Failed to connect"), buf);
+      putlog(LOG_MISC, "*", "%s: CHAT(file) (%s!%s)", _("DCC connection failed"),
+	     nick, from);
+      putlog(LOG_MISC, "*", "    (%s)", buf);
+      killsock(sock);
+    } else if (atoi(prt) < 1024 || atoi(prt) > 65535) {
+      /* Invalid port */
+      if (!quiet_reject)
+        dprintf(DP_HELP, "NOTICE %s :%s", nick,
+	        _("Failed to connect (invalid port)\n"));
+      putlog(LOG_FILES, "*", "%s: %s!%s", _("Refused DCC chat (invalid port)"), nick, from);
+
+    } else {
+      unsigned int ip_int;
+
+      i = new_dcc(&DCC_FILES_PASS, sizeof(struct file_info));
+      sscanf(ip, "%u", &ip_int);
+      strcpy(dcc[i].addr, iptostr(htonl(ip_int)));
+      dcc[i].port = atoi(prt);
+      dcc[i].sock = sock;
+      strcpy(dcc[i].nick, u->handle);
+      strcpy(dcc[i].host, from);
+      dcc[i].status = STAT_ECHO;
+      dcc[i].timeval = now;
+      dcc[i].u.file->chat = calloc(1, sizeof(struct chat_info));
+      strcpy(dcc[i].u.file->chat->con_chan, "*");
+      dcc[i].user = u;
+      putlog(LOG_MISC, "*", "DCC connection: CHAT(file) (%s!%s)", nick, from);
+      dprintf(i, "%s\n", _("Enter your password."));
+    }
+  }
+  return 1;
+}
+
+static cmd_t myctcp[] =
+{
+  {"DCC",	"",	filesys_DCC_CHAT,		"files:DCC"},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+static void init_server_ctcps(char *module)
+{
+  bind_table_t *BT_ctcp;
+
+  if (BT_ctcp = find_bind_table2("ctcp"))
+    add_builtins2(BT_ctcp, myctcp);
+}
+
+static cmd_t myload[] =
+{
+  {"server",	"",	(Function) init_server_ctcps,	"filesys:server"},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+static void filesys_report(int idx, int details)
+{
+  if (dccdir[0]) {
+    dprintf(idx, "    DCC file path: %s", dccdir);
+    if (upload_to_cd)
+      dprintf(idx, "\n        incoming: (go to the current dir)\n");
+    else if (dccin[0])
+      dprintf(idx, "\n        incoming: %s\n", dccin);
+    else
+      dprintf(idx, "    (no uploads)\n");
+    if (dcc_users)
+      dprintf(idx, "        max users is %d\n", dcc_users);
+    if ((upload_to_cd) || (dccin[0]))
+      dprintf(idx, "    DCC max file size: %dk\n", dcc_maxsize);
+  } else
+    dprintf(idx, "  (Filesystem module loaded, but no active dcc path.)\n");
+}
+
+static char *filesys_close()
+{
+  int i;
+  bind_table_t *BT_ctcp;
+
+  putlog(LOG_MISC, "*", "Unloading filesystem, killing all filesystem connections..");
+  for (i = 0; i < dcc_total; i++)
+    if (dcc[i].type == &DCC_FILES) {
+      dprintf(i, _("-=- poof -=-\n"));
+      dprintf(i,
+	 "You have been booted from the filesystem, module unloaded.\n");
+      killsock(dcc[i].sock);
+      lostdcc(i);
+    } else if (dcc[i].type == &DCC_FILES_PASS) {
+      killsock(dcc[i].sock);
+      lostdcc(i);
+    }
+  rem_tcl_commands(mytcls);
+  rem_tcl_strings(mystrings);
+  rem_tcl_ints(myints);
+  if (BT_dcc) rem_builtins2(BT_dcc, mydcc);
+  if (BT_load) rem_builtins2(BT_load, myload);
+  rem_help_reference("filesys.help");
+  if (BT_ctcp = find_bind_table2("ctcp"))
+    rem_builtins2(BT_ctcp, myctcp);
+  del_bind_table2(BT_file);
+  del_entry_type(&USERENTRY_DCCDIR);
+  module_undepend(MODULE_NAME);
+  return NULL;
+}
+
+EXPORT_SCOPE char *start();
+
+static Function filesys_table[] =
+{
+  /* 0 - 3 */
+  (Function) start,
+  (Function) filesys_close,
+  (Function) 0,
+  (Function) filesys_report,
+  /* 4 - 7 */
+  (Function) remote_filereq,
+  (Function) add_file,
+  (Function) incr_file_gots,
+  (Function) is_valid,
+};
+
+char *start(Function * global_funcs)
+{
+  global = global_funcs;
+
+  module_register(MODULE_NAME, filesys_table, 2, 0);
+  if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+    module_undepend(MODULE_NAME);
+    return "This module requires eggdrop1.7.0 or later";
+  }
+  if (!(transfer_funcs = module_depend(MODULE_NAME, "transfer", 2, 0))) {
+    module_undepend(MODULE_NAME);
+    return "You need the transfer module to user the file system.";
+  }
+  add_tcl_commands(mytcls);
+  add_tcl_strings(mystrings);
+  add_tcl_ints(myints);
+  BT_file = add_bind_table2("file", 3, "sis", MATCH_MASK, BIND_STACKABLE);
+  BT_dcc = find_bind_table2("dcc");
+  BT_load = find_bind_table2("load");
+  if (BT_dcc) add_builtins2(BT_dcc, mydcc);
+  if (BT_load) add_builtins2(BT_load, myload);
+  add_builtins2(BT_file, myfiles);
+  add_help_reference("filesys.help");
+  init_server_ctcps(0);
+  memcpy(&USERENTRY_DCCDIR, &USERENTRY_INFO,
+	    sizeof(struct user_entry_type) - sizeof(char *));
+
+  USERENTRY_DCCDIR.got_share = 0;	/* We dont want it shared tho */
+  add_entry_type(&USERENTRY_DCCDIR);
+  DCC_FILES_PASS.timeout_val = &password_timeout;
+  return NULL;
+}
+
+static int is_valid()
+{
+  return dccdir[0];
+}
Index: eggdrop1.7/modules/filesys/filesys.h
diff -u /dev/null eggdrop1.7/modules/filesys/filesys.h:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/filesys.h	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,61 @@
+/*
+ * filesysc.h -- part of filesys.mod
+ *   header file for the filesys2 eggdrop module
+ *
+ * $Id: filesys.h,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_FILESYS_FILESYS_H
+#define _EGG_MOD_FILESYS_FILESYS_H
+
+#include "modules/transfer/transfer.h"
+
+#ifdef MAKING_FILESYS
+static int too_many_filers();
+static int welcome_to_files(int);
+static void add_file(char *, char *, char *);
+static void incr_file_gots(char *);
+static void remote_filereq(int, char *, char *);
+static FILE *filedb_open(char *, int);
+static void filedb_close(FILE *);
+static void filedb_add(FILE *, char *, char *);
+static void filedb_ls(FILE *, int, char *, int);
+static void filedb_getowner(char *, char *, char **);
+static void filedb_setowner(char *, char *, char *);
+static void filedb_getdesc(char *, char *, char **);
+static void filedb_setdesc(char *, char *, char *);
+static int filedb_getgots(char *, char *);
+static void filedb_setlink(char *, char *, char *);
+static void filedb_getlink(char *, char *, char **);
+static void filedb_getfiles(Tcl_Interp *, char *);
+static void filedb_getdirs(Tcl_Interp *, char *);
+static void filedb_change(char *, char *, int);
+static void tell_file_stats(int, char *);
+static int do_dcc_send(int, char *, char *, char *, int);
+static int files_reget(int, char *, char *, int);
+static void files_setpwd(int, char *);
+static int resolve_dir(char *, char *, char **, int);
+
+#else
+#define H_fil (*(p_tcl_hash_list *)(filesys_funcs[8]))
+#endif				/* MAKING_FILESYS */
+
+#endif				/* _EGG_MOD_FILESYS_FILESYS_H */
Index: eggdrop1.7/modules/filesys/help/filesys.help
diff -u /dev/null eggdrop1.7/modules/filesys/help/filesys.help:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/help/filesys.help	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,194 @@
+%{help=files}%{+x}
+###  %bfiles%b
+   will move you into the file transfer sub-system, if it has been
+   enabled on this bot.  from there you can browse through the
+   files online and use dcc file transfers to download and upload.
+%{help=filesys/cancel}%{+x}
+###  %bcancel%b <file(s)>
+   tells the bot to stop sending a file that is pending (either
+   queued, waiting, or in the process of being transferred).
+
+see also: pending
+%{help=filesys/cd}%{+x}
+###  %bcd%b <directory>
+   changes your current directory if possible.  this works exactly
+   like the unix command.
+
+see also: pwd
+%{help=filesys/cp}%{+j}
+###  %bcp%b <source> <dest>
+   copies a file or group of files from one place to another.
+
+see also: mv
+%{help=filesys/desc}%{+x}
+###  %bdesc%b <file> <description>
+   changes the description for a file.  if you are a master or
+   file janitor, you can change the description for any file.
+   otherwise you can only change the descriptions for files you
+   have uploaded.
+
+   the description is restricted to 3 lines of 60 characters
+   each, and is broken up between words.  you can force a line
+   break by putting a '|' in the comment.
+%{help=filesys/filestats}%{+j}
+### %bfilestats%b <user>
+   Reports on the users upload & download statistics.
+
+### %bfilestats%b <user> %bclear%b
+   Clears a users upload & download statistics.
+%{help=filesys/stats}%{+j}
+### %bstats%b
+   Reports your upload & download statistics.
+%{help=filesys/get}%{+x}
+###  %bget%b <filename(s)> [nickname]
+   sends you the file(s) requested, over IRC.  you should get a
+   DCC SEND notice on IRC, and have your client accept it.  if
+   your nickname on IRC is different than the one you use on the
+   bot, you should specify a nickname too.  you can also use that
+   to send files to other people.  if a file you want is actually
+   on another bot, it may take a little bit longer so be patient.
+   if that bot isn't on the botnet right now, it will say the file
+   isn't availble.
+
+   there is a limit to the number of transfers you can have going
+   simultaneously, so if you go over this limit, the remainder of
+   your file requests will be queued.  as the first files finish
+   transferring, the queued files will be sent.
+
+see also: pending, cancel
+%{help=filesys/help}%{+x}
+%B file system commands:   (wildcard expressions are allowed)
+  %bget%b <file> [nick]     send a file over IRC (DCC send)
+  %bpending%b               list queued file transfers
+  %bcancel%b <file>         cancel a queued file transfer
+  %bls%b [filemask]         show list of files in this directory
+  %bpwd%b                   display present working directory
+  %bcd%b <dir>              change current directory
+  %bdesc%b <file> <desc>    set description of <file> to <desc>
+  %bstats%b                 see your upload/download statistics
+  %bquit%b                  leave the file system
+%{+j}
+Janitor only:
+  %bhide      share     mkdir     cp        rm        lsa%b
+  %bunhide    unshare   rmdir     mv        ln        optimise%b
+  %bfilestats%b
+%{+x}
+You can get help on any command via:  %bhelp <command>%b
+(To upload a file, just start your dcc transfer.)
+%{+n}
+As a bot owner, there are a number of setting that can be set to
+configure the file area, use %b'.help set <setting>'%b for more info.
+Settings:
+   %bfiles-path%b        %bincoming-path%b
+   %bfiledb-path%b       %bmax-filesize%b
+   %bmax-file-users%b    %bupload-to-pwd%b
+%{help=filesys/hide}%{+j}
+###  %bhide%b <file(s)>
+   marks a file as hidden, so that normal users can't see it.
+   only a master or file janitor using %b'lsa'%b can see hidden files.
+
+see also: unhide, lsa
+%{help=filesys/ln}%{+j}
+###  %bln%b <bot:filepath> <localfile>
+   creates a link to a file on another bot.  the filepath has to
+   be complete, like '/gifs/uglyman.gif'.  if the bot is not
+   connected to the botnet, nobody will be able to download the
+   file until that bot connects again.  the local filename can be
+   anything you want.
+   example:  ln Snowbot:/gifs/uglyman.gif ugly.gif
+
+see also: share, unshare, get
+%{help=filesys/ls}%{+x}
+###  %bls%b [filemask]
+   displays the files in the current directory.  subdirectories
+   are shown with "<DIR>" next to them, and other files will display
+   their size (typically in kilobytes), who uploaded them (and when),
+   and how many times each file has been downloaded.  if a descrip-
+   tion of the file exists, it is displayed below the filename.  you
+   can restrict the file listing by specifying a mask, just like in
+   unix.
+
+see also: cd, pwd, get
+%{help=filesys/lsa}%{+j}
+###  %blsa%b [filemask]
+   works just like ls, but hidden files are displayed too.
+   hidden files are displayed with a "(hid)" next to them, and
+   shared files are displayed with a "(shr)" next to them.
+
+see also: hide, share
+%{help=filesys/mkdir}%{+j}
+###  %bmkdir%b <dir> [flags [channel]]
+   creates a subdirectory from this one, with the given name.  if
+   flags are specified, then those flags are required to enter or
+   even see the directory.  you can even specify a channel that the
+   flags are matched against. you can use the %b'mkdir'%b command again
+   to alter or remove those flags.
+
+see also: rmdir
+%{help=filesys/mv}%{+j}
+###  %bmv%b <source> <dest>
+   moves a file or group of files from one place to another.  (it
+   can also be used to rename files.)
+
+see also: cp
+%{help=filesys/pending}%{+x}
+###  %bpending%b
+   gives you a listing of every file you've requested which is
+   still waiting, queued, or in the process of transferring.
+   it shows you the nickname on IRC that the file is being sent
+   to, and, if the transfer is in progress, tells you how far
+   along the transfer is.
+
+see also: cancel
+%{help=filesys/pwd}%{+x}
+###  %bpwd%b
+   tells you what your current directory is.
+
+see also: cd
+%{help=filesys/quit}%{+x}
+###  %bquit%b
+   exits the file system
+%{help=filesys/rm}%{+j}
+###  %brm%b <file(s)>
+   erase a file for good.
+%{help=filesys/rmdir}%{+j}
+###  %brmdir%b <dir>
+   removes an existing directory, if there are no files in it.
+
+see also: mkdir
+%{help=filesys/share}%{+j}
+###  %bshare%b <file(s)>
+   marks a file as shared.  this means that other bots can get
+   the file remotely for users on their file systems.  by default,
+   files are marked as unshared.
+
+see also: unshare, lsa, ln
+%{help=filesys/optimise}%{+j}
+###  %boptimise%b
+   cleans up the current directory's database.  if you have a large
+   directory with many files you may want to use this command if
+   you experience slow-downs/delays over time.  normally, the db
+   should clean up itsself though.
+%{help=filesys/sort}%{+j}
+###  %bsort%b
+   this command is obsolete, because the directory is always
+   sorted on the fly.
+%{help=filesys/unhide}%{+j}
+###  %bunhide%b <file(s)>
+   makes a file be not hidden any more.
+
+see also: hide, lsa
+%{help=filesys/unshare}%{+j}
+###  %bunshare%b <file(s)>
+   removes the shared tag from a file.
+
+see also: share, lsa, ln
+%{help=filesys module}%{+x}
+### commands for the %bfilesys module%b
+  %bfiles%b
+
+see also: help in filearea
+%{help=all}%{+x}
+###  commands for the %bfilesys module%b
+  %bfiles%b
+
Index: eggdrop1.7/modules/filesys/help/set/filesys.help
diff -u /dev/null eggdrop1.7/modules/filesys/help/set/filesys.help:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/help/set/filesys.help	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,47 @@
+%{help=set files-path}%{+n}
+###  %bset files-path%b <path>
+   specifies where the bot's dcc file directory is.  if you don't
+   want a file transfer section on your bot, define this as "".
+   otherwise, the directory given will be the root directory for
+   users in the file section.  they may move into any subdirectory
+   and download any file you make visible, but may never move out
+   past the files-path directory.
+see also: set incoming-path
+%{help=set incoming-path}%{+n}
+###  %bset incoming-path%b <path>
+   specifies the directory where files dcc'd to the bot will go.
+   if you have turned off the file section by setting %b'files-path'%b
+   to "", then this has no effect since all files offered by dcc
+   to the bot will be rejected anyway.  if you have set the
+   %b'upload-to-pwd'%b to 1, then this will be ignored, and all files
+   uploaded (sent) to the bot will go into whichever directory
+   the user is in currently, or was in last.
+see also: set files-path, set upload-to-pwd
+%{help=set filedb-path}%{+n}
+###  %bset filedb-path%b <path>
+   specifies where database files for the file system should go.
+   (this only matters if your bot has a file system.)  if this
+   is left blank, a hidden file is created in each subdirectory
+   of the file system.  if you set a filedb-path, though, the
+   bot will store all the file system database files in the path
+   you specify.  this is useful if you only have read access to
+   the files-path, or just don't want hidden files lurking around.
+see also: set files-path
+%{help=set max-filesize}%{+n}
+###  %bset max-filesize%b <#>
+   specifies the maximum allowable size (in kilobytes) of a file that
+   the bot will accept in a dcc send.  the default is 1024, which is
+   1 meg.
+see also: set max-dloads, set max-file-users
+%{help=set max-file-users}%{+n}
+###  %bset max-file-users%b <#>
+   specifies the maximum number of people to allow in the bot's
+   file system at one time.  this can be used to keep your file
+   system from being abused if you offer a lot of files.
+see also: set max-dloads, set max-filesize
+%{help=set upload-to-pwd}%{+n}
+###  %bset upload-to-pwd%b <0/1>
+   specifies whether uploads to the bot's file area go to the
+   user's current directory (1), or to the directory specified
+   in %b'incoming-path'%b (0).
+see also: set incoming-path
Index: eggdrop1.7/modules/filesys/modinfo
diff -u /dev/null eggdrop1.7/modules/filesys/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/modinfo	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,5 @@
+DESC:The filesys module provides an area within the bot where you can store
+DESC:files.
+DESC:
+DESC:Not everyone will need this module. If unsure, ENABLE it.
+DEPENDS:transfer
Index: eggdrop1.7/modules/filesys/tclfiles.c
diff -u /dev/null eggdrop1.7/modules/filesys/tclfiles.c:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/filesys/tclfiles.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,673 @@
+/*
+ * tclfiles.c -- part of filesys.mod
+ *   Tcl stubs for file system commands moved here to support modules
+ *
+ * $Id: tclfiles.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static int tcl_getdesc STDVAR
+{
+  char *s = NULL;
+
+  BADARGS(3, 3, " dir file");
+  filedb_getdesc(argv[1], argv[2], &s);
+  if (s) {
+    Tcl_AppendResult(irp, s, NULL);
+    free_null(s);
+    return TCL_OK;
+  } else {
+    Tcl_AppendResult(irp, "filedb access failed", NULL);
+    return TCL_ERROR;
+  }
+}
+
+static int tcl_setdesc STDVAR
+{
+  BADARGS(4, 4, " dir file desc");
+  filedb_setdesc(argv[1], argv[2], argv[3]);
+  return TCL_OK;
+}
+
+static int tcl_getowner STDVAR
+{
+  char *s = NULL;
+
+  BADARGS(3, 3, " dir file");
+  filedb_getowner(argv[1], argv[2], &s);
+  if (s) {
+    Tcl_AppendResult(irp, s, NULL);
+    free_null(s);
+    return TCL_OK;
+  } else {
+    Tcl_AppendResult(irp, "filedb access failed", NULL);
+    return TCL_ERROR;
+  }
+}
+
+static int tcl_setowner STDVAR
+{
+  BADARGS(4, 4, " dir file owner");
+  filedb_setowner(argv[1], argv[2], argv[3]);
+  return TCL_OK;
+}
+
+static int tcl_getgots STDVAR
+{
+  int i;
+  char s[10];
+
+  BADARGS(3, 3, " dir file");
+  i = filedb_getgots(argv[1], argv[2]);
+  sprintf(s, "%d", i);
+  Tcl_AppendResult(irp, s, NULL);
+  return TCL_OK;
+}
+
+static int tcl_setlink STDVAR
+{
+  BADARGS(4, 4, " dir file link");
+  filedb_setlink(argv[1], argv[2], argv[3]);
+  return TCL_OK;
+}
+
+static int tcl_getlink STDVAR
+{
+  char *s = NULL;
+
+  BADARGS(3, 3, " dir file");
+  filedb_getlink(argv[1], argv[2], &s);
+  if (s) {
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  } else {
+    Tcl_AppendResult(irp, "filedb access failed", NULL);
+    return TCL_ERROR;
+  }
+}
+
+static int tcl_setpwd STDVAR
+{
+  int i, idx;
+
+  BADARGS(3, 3, " idx dir");
+  i = atoi(argv[1]);
+  idx = findanyidx(i);
+  if ((idx < 0) || (dcc[idx].type != &DCC_FILES)) {
+    Tcl_AppendResult(irp, "invalid idx", NULL);
+    return TCL_ERROR;
+  }
+  files_setpwd(idx, argv[2]);
+
+  return TCL_OK;
+}
+
+static int tcl_getpwd STDVAR
+{
+  int i, idx;
+
+  BADARGS(2, 2, " idx");
+  i = atoi(argv[1]);
+  idx = findanyidx(i);
+  if ((idx < 0) || (dcc[idx].type != &DCC_FILES)) {
+    Tcl_AppendResult(irp, "invalid idx", NULL);
+    return TCL_ERROR;
+  }
+  Tcl_AppendResult(irp, dcc[idx].u.file->dir, NULL);
+
+  return TCL_OK;
+}
+
+static int tcl_getfiles STDVAR
+{
+  BADARGS(2, 2, " dir");
+  filedb_getfiles(irp, argv[1]);
+  return TCL_OK;
+}
+
+static int tcl_getdirs STDVAR
+{
+  BADARGS(2, 2, " dir");
+  filedb_getdirs(irp, argv[1]);
+  return TCL_OK;
+}
+
+static int tcl_hide STDVAR
+{
+  BADARGS(3, 3, " dir file");
+  filedb_change(argv[1], argv[2], FILEDB_HIDE);
+  return TCL_OK;
+}
+
+static int tcl_unhide STDVAR
+{
+  BADARGS(3, 3, " dir file");
+  filedb_change(argv[1], argv[2], FILEDB_UNHIDE);
+  return TCL_OK;
+}
+
+static int tcl_share STDVAR
+{
+  BADARGS(3, 3, " dir file");
+  filedb_change(argv[1], argv[2], FILEDB_SHARE);
+  return TCL_OK;
+}
+
+static int tcl_unshare STDVAR
+{
+  BADARGS(3, 3, " dir file");
+  filedb_change(argv[1], argv[2], FILEDB_UNSHARE);
+  return TCL_OK;
+}
+
+static int tcl_setflags STDVAR
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+  char *s = NULL, *p, *d;
+
+  BADARGS(3, 4, " dir ?flags ?channel??");
+  realloc_strcpy(s, argv[1]);
+  if (s[strlen(s) - 1] == '/')
+     s[strlen(s) - 1] = 0;
+  p = strrchr(s, '/');
+  if (p == NULL) {
+    p = s;
+    d = "";
+  } else {
+    *p = 0;
+    p++;
+    d = s;
+  }
+
+  fdb = filedb_open(d, 0);
+  if (!fdb) {
+    Tcl_AppendResult(irp, "-3", NULL);		/* filedb access failed */
+    free_null(s);
+    return TCL_OK;
+  }
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), p);
+  free_null(s);
+
+  if (!fdbe) {
+    Tcl_AppendResult(irp, "-1", NULL);	/* No such dir */
+    return TCL_OK;
+  }
+  if (!(fdbe->stat & FILE_DIR)) {
+    Tcl_AppendResult(irp, "-2", NULL);	/* Not a dir */
+    return TCL_OK;
+  }
+  if (argc >= 3) {
+    struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+    char f[100];
+
+    break_down_flags(argv[2], &fr, NULL);
+    build_flags(f, &fr, NULL);
+    realloc_strcpy(fdbe->flags_req, f);
+  } else {
+    free_null(fdbe->flags_req);
+  }
+  if (argc == 4)
+    realloc_strcpy(fdbe->chan, argv[3]);
+
+  filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+  free_fdbe(&fdbe);
+  filedb_close(fdb);
+  Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_getflags STDVAR
+{
+  filedb_entry *fdbe;
+  char *s = NULL, *p, *d;
+
+  BADARGS(2, 2, " dir");
+  realloc_strcpy(s, argv[1]);
+  if (s[strlen(s) - 1] == '/')
+     s[strlen(s) - 1] = 0;
+  p = strrchr(s, '/');
+  if (p == NULL) {
+    p = s;
+    d = "";
+  } else {
+    *p = 0;
+    p++;
+    d = s;
+  }
+
+  fdbe = filedb_getentry(d, p);
+  /* Directory doesn't exist? */
+  if (!fdbe ||
+      /* Not a directory? */
+      !(fdbe->stat & FILE_DIR)) {
+    Tcl_AppendResult(irp, "", NULL);
+    free_null(s);
+    free_fdbe(&fdbe);
+    return TCL_OK;
+  }
+  if (fdbe->flags_req) {
+    realloc_strcpy(s, fdbe->flags_req);
+    if (s[0] == '-')
+      s[0] = 0;
+  } else
+    s[0] = 0;
+  Tcl_AppendElement(irp, s);
+  Tcl_AppendElement(irp, fdbe->chan);
+  free_null(s);
+  free_fdbe(&fdbe);
+  return TCL_OK;
+}
+
+static int tcl_mkdir STDVAR
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+  char *s = NULL, *t, *d, *p;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  BADARGS(2, 4, " dir ?required-flags ?channel??");
+  realloc_strcpy(s, argv[1]);
+  if (s[strlen(s) - 1] == '/')
+     s[strlen(s) - 1] = 0;
+  p = strrchr(s, '/');
+  if (p == NULL) {
+    p = s;
+    d = "";
+  } else {
+    *p = 0;
+    p++;
+    d = s;
+  }
+
+  fdb = filedb_open(d, 0);
+  if (!fdb) {
+    Tcl_AppendResult(irp, "-3", NULL);		/* filedb access failed */
+    free_null(s);
+    return TCL_OK;
+  }
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), p);
+
+  if (!fdbe) {
+    t = malloc(strlen(dccdir) + strlen(d) + strlen(p) + 2);
+    sprintf(t, "%s%s/%s", dccdir, d, p);
+    if (mkdir(t, 0755) != 0) {
+      Tcl_AppendResult(irp, "1", NULL);
+      free_null(t);
+      free_null(s);
+      filedb_close(fdb);
+      return TCL_OK;
+    }
+    fdbe = malloc_fdbe();
+    fdbe->stat = FILE_DIR;
+    realloc_strcpy(fdbe->filename, argv[1]);
+    fdbe->uploaded = now;
+  } else if (!(fdbe->stat & FILE_DIR)) {
+    Tcl_AppendResult(irp, "2", NULL);
+    free_fdbe(&fdbe);
+    free_null(s);
+    filedb_close(fdb);
+    return TCL_OK;
+  }
+  if (argc >= 3) {
+    char f[100];
+
+    break_down_flags(argv[2], &fr, NULL);
+    build_flags(f, &fr, NULL);
+    realloc_strcpy(fdbe->flags_req, f);
+  } else if (fdbe->flags_req) {
+    free_null(fdbe->flags_req);
+  }
+  if (argc == 4) {
+    realloc_strcpy(fdbe->chan, argv[3]);
+  } else
+    if (fdbe->chan)
+      free_null(fdbe->chan);
+
+  if (fdbe->pos)
+      filedb_addfile(fdb, fdbe);
+  else
+      filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
+  filedb_close(fdb);
+  Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_rmdir STDVAR
+{
+  FILE *fdb;
+  filedb_entry *fdbe;
+  char *s = NULL, *t, *d, *p;
+
+  BADARGS(2, 2, " dir");
+  realloc_strcpy(s, argv[1]);
+  if (s[strlen(s) - 1] == '/')
+     s[strlen(s) - 1] = 0;
+  p = strrchr(s, '/');
+  if (p == NULL) {
+    p = s;
+    d = "";
+  } else {
+    *p = 0;
+    p++;
+    d = s;
+  }
+
+  fdb = filedb_open(d, 0);
+  if (!fdb) {
+    Tcl_AppendResult(irp, "1", NULL);
+    free_null(s);
+    return TCL_OK;
+  }
+  filedb_readtop(fdb, NULL);
+  fdbe = filedb_matchfile(fdb, ftell(fdb), p);
+
+  if (!fdbe) {
+    Tcl_AppendResult(irp, "1", NULL);
+    filedb_close(fdb);
+    free_null(s);
+    return TCL_OK;
+  }
+  if (!(fdbe->stat & FILE_DIR)) {
+    Tcl_AppendResult(irp, "1", NULL);
+    filedb_close(fdb);
+    free_fdbe(&fdbe);
+    free_null(s);
+    return TCL_OK;
+  }
+  /* Erase '.filedb' and '.files' if they exist */
+  t = malloc(strlen(dccdir) + strlen(d) + strlen(p) + 11);
+  sprintf(t, "%s%s/%s/.filedb", dccdir, d, p);
+  unlink(t);
+  sprintf(t, "%s%s/%s/.files", dccdir, d, p);
+  unlink(t);
+  sprintf(t, "%s%s/%s", dccdir, d, p);
+  free_null(s);
+  if (rmdir(t) == 0) {
+    filedb_delfile(fdb, fdbe->pos);
+    filedb_close(fdb);
+    free_fdbe(&fdbe);
+    free_null(t);
+    Tcl_AppendResult(irp, "0", NULL);
+    return TCL_OK;
+  }
+  free_null(t);
+  free_fdbe(&fdbe);
+  filedb_close(fdb);
+  Tcl_AppendResult(irp, "1", NULL);
+  return TCL_OK;
+}
+
+static int tcl_mv_cp(Tcl_Interp * irp, int argc, char **argv, int copy)
+{
+  char *p, *fn = NULL, *oldpath = NULL, *s = NULL, *s1 = NULL;
+  char *newfn = NULL, *newpath = NULL;
+  int ok = 0, only_first, skip_this;
+  FILE *fdb_old, *fdb_new;
+  filedb_entry *fdbe_old, *fdbe_new;
+  long where;
+
+  BADARGS(3, 3, " oldfilepath newfilepath");
+  realloc_strcpy(fn, argv[1]);
+  p = strrchr(fn, '/');
+  if (p != NULL) {
+    *p = 0;
+    realloc_strcpy(s, fn);
+    strcpy(fn, p + 1);
+    if (!resolve_dir("/", s, &oldpath, -1)) {
+      /* Tcl can do * anything */
+      Tcl_AppendResult(irp, "-1", NULL);	/* Invalid source */
+      free_null(fn);
+      free_null(oldpath);
+      return TCL_OK;
+    }
+    free_null(s);
+  } else
+    realloc_strcpy(oldpath, "/");
+  realloc_strcpy(s, argv[2]);
+  if (!resolve_dir("/", s, &newpath, -1)) {
+    /* Destination is not just a directory */
+    p = strrchr(s, '/');
+    if (!p) {
+      realloc_strcpy(newfn, s);
+      s[0] = 0;
+    } else {
+      *p = 0;
+      realloc_strcpy(newfn, p + 1);
+    }
+    free_null(newpath);
+    if (!resolve_dir("/", s, &newpath, -1)) {
+      Tcl_AppendResult(irp, "-2", NULL);	/* Invalid desto */
+      free_null(newpath);
+      free_null(s);
+      free_null(newfn);
+      return TCL_OK;
+    }
+  } else
+    realloc_strcpy(newfn, "");
+  free_null(s);
+
+  /* Stupidness checks */
+  if ((!strcmp(oldpath, newpath)) &&
+      (!newfn[0] || !strcmp(newfn, fn))) {
+    free_null(newfn);
+    free_null(fn);
+    free_null(oldpath);
+    free_null(newpath);
+    Tcl_AppendResult(irp, "-3", NULL);	/* Stupid copy to self */
+    return TCL_OK;
+  }
+  /* Be aware of 'cp * this.file' possibility: ONLY COPY FIRST ONE */
+  if ((strchr(fn, '?') || strchr(fn, '*')) && newfn[0])
+    only_first = 1;
+  else
+    only_first = 0;
+
+  fdb_old = filedb_open(oldpath, 0);
+  if (!strcmp(oldpath, newpath))
+    fdb_new = fdb_old;
+  else
+    fdb_new = filedb_open(newpath, 0);
+  if (!fdb_old || !fdb_new) {
+    free_null(newfn);
+    free_null(fn);
+    free_null(oldpath);
+    free_null(newpath);
+    if (fdb_old)
+      filedb_close(fdb_old);
+    else if (fdb_new)
+      filedb_close(fdb_new);
+    Tcl_AppendResult(irp, "-5", NULL);	/* DB access failed */
+    return -1;
+  }
+
+  filedb_readtop(fdb_old, NULL);
+  fdbe_old = filedb_matchfile(fdb_old, ftell(fdb_old), fn);
+  if (!fdbe_old) {
+    free_null(newfn);
+    free_null(fn);
+    free_null(oldpath);
+    free_null(newpath);
+    if (fdb_new != fdb_old)
+      filedb_close(fdb_new);
+    filedb_close(fdb_old);
+    Tcl_AppendResult(irp, "-4", NULL);  /* No match */
+    return -2;
+  }
+  while (fdbe_old) {
+    where = ftell(fdb_old);
+    skip_this = 0;
+    if (!(fdbe_old->stat & (FILE_HIDDEN | FILE_DIR))) {
+      s = malloc(strlen(dccdir) + strlen(oldpath)
+		  + strlen(fdbe_old->filename) + 2);
+      s1 = malloc(strlen(dccdir) + strlen(newpath)
+		   + strlen(newfn[0] ? newfn : fdbe_old->filename) + 2);
+      sprintf(s, "%s%s%s%s", dccdir, oldpath,
+	      oldpath[0] ? "/" : "", fdbe_old->filename);
+      sprintf(s1, "%s%s%s%s", dccdir, newpath,
+	      newpath[0] ? "/" : "", newfn[0] ? newfn : fdbe_old->filename);
+      if (!strcmp(s, s1)) {
+	Tcl_AppendResult(irp, "-3", NULL); /* Stupid copy to self */
+	skip_this = 1;
+      }
+      /* Check for existence of file with same name in new dir */
+      filedb_readtop(fdb_new, NULL);
+      fdbe_new = filedb_matchfile(fdb_new, ftell(fdb_new),
+				  newfn[0] ? newfn : fdbe_old->filename);
+      if (fdbe_new) {
+	/* It's ok if the entry in the new dir is a normal file (we'll
+	 * just scrap the old entry and overwrite the file) -- but if
+	 * it's a directory, this file has to be skipped.
+	 */
+	if (fdbe_new->stat & FILE_DIR) {
+	  /* Skip */
+	  skip_this = 1;
+	} else {
+	  filedb_delfile(fdb_new, fdbe_new->pos);
+	}
+	free_fdbe(&fdbe_new);
+      }
+      if (!skip_this) {
+	if ((fdbe_old->sharelink) || (copyfile(s, s1) == 0)) {
+	  /* Raw file moved okay: create new entry for it */
+	  ok++;
+	  fdbe_new = malloc_fdbe();
+	  fdbe_new->stat = fdbe_old->stat;
+	  /* We don't have to worry about any entries to be
+	   * NULL, because realloc_strcpy takes care of that.
+	   */
+	  realloc_strcpy(fdbe_new->flags_req, fdbe_old->flags_req);
+	  realloc_strcpy(fdbe_new->chan, fdbe_old->chan);
+	  realloc_strcpy(fdbe_new->filename, fdbe_old->filename);
+	  realloc_strcpy(fdbe_new->desc, fdbe_old->desc);
+	  if (newfn[0])
+	    realloc_strcpy(fdbe_new->filename, newfn);
+	  realloc_strcpy(fdbe_new->uploader, fdbe_old->uploader);
+	  fdbe_new->uploaded = fdbe_old->uploaded;
+	  fdbe_new->size = fdbe_old->size;
+	  fdbe_new->gots = fdbe_old->gots;
+	  realloc_strcpy(fdbe_new->sharelink, fdbe_old->sharelink);
+	  filedb_addfile(fdb_new, fdbe_new);
+	  if (!copy) {
+	    unlink(s);
+	    filedb_delfile(fdb_old, fdbe_old->pos);
+	  }
+	  free_fdbe(&fdbe_new);
+	}
+      }
+      free_null(s);
+      free_null(s1);
+    }
+    free_fdbe(&fdbe_old);
+    fdbe_old = filedb_matchfile(fdb_old, where, fn);
+    if (ok && only_first) {
+      free_fdbe(&fdbe_old);
+    }
+  }
+  if (fdb_old != fdb_new)
+    filedb_close(fdb_new);
+  filedb_close(fdb_old);
+  if (!ok)
+    Tcl_AppendResult(irp, "-4", NULL);	/* No match */
+  else {
+    char x[30];
+
+    sprintf(x, "%d", ok);
+    Tcl_AppendResult(irp, x, NULL);
+  }
+  free_null(newfn);
+  free_null(fn);
+  free_null(oldpath);
+  free_null(newpath);
+  return TCL_OK;
+}
+
+static int tcl_mv STDVAR
+{
+  return tcl_mv_cp(irp, argc, argv, 0);
+}
+
+static int tcl_cp STDVAR
+{
+  return tcl_mv_cp(irp, argc, argv, 1);
+}
+
+static int tcl_fileresend_send(ClientData cd, Tcl_Interp *irp, int argc,
+	       		       char *argv[], int resend)
+{
+  int i, idx;
+  char s[21];
+
+  BADARGS(3, 4, " idx filename ?nick?");
+  i = atoi(argv[1]);
+  idx = findanyidx(i);
+  if (idx < 0 || dcc[idx].type != &DCC_FILES) {
+    Tcl_AppendResult(irp, "invalid idx", NULL);
+    return TCL_ERROR;
+  }
+  if (argc == 4)
+     i = files_reget(idx, argv[2], argv[3], resend);
+  else
+     i = files_reget(idx, argv[2], "", resend);
+  sprintf(s, "%d", i);
+  Tcl_AppendResult(irp, s, NULL);
+  return TCL_OK;
+}
+
+static int tcl_fileresend STDVAR
+{
+  return tcl_fileresend_send(cd, irp, argc, argv, 1);
+}
+
+static int tcl_filesend STDVAR
+{
+  return tcl_fileresend_send(cd, irp, argc, argv, 0);
+}
+
+static tcl_cmds mytcls[] =
+{
+  {"getdesc",		tcl_getdesc},
+  {"getowner",		tcl_getowner},
+  {"setdesc",		tcl_setdesc},
+  {"setowner",		tcl_setowner},
+  {"getgots",		tcl_getgots},
+  {"getpwd",		tcl_getpwd},
+  {"setpwd",		tcl_setpwd},
+  {"getlink",		tcl_getlink},
+  {"setlink",		tcl_setlink},
+  {"getfiles",		tcl_getfiles},
+  {"getdirs",		tcl_getdirs},
+  {"hide",		tcl_hide},
+  {"unhide",		tcl_unhide},
+  {"share",		tcl_share},
+  {"unshare",		tcl_unshare},
+  {"filesend",		tcl_filesend},
+  {"fileresend",	tcl_fileresend},
+  {"mkdir",		tcl_mkdir},
+  {"rmdir",		tcl_rmdir},
+  {"cp",		tcl_cp},
+  {"mv",		tcl_mv},
+  {"getflags",		tcl_getflags},
+  {"setflags",		tcl_setflags},
+  {NULL,		NULL}
+};
+
Index: eggdrop1.7/modules/irc/Makefile.am
diff -u /dev/null eggdrop1.7/modules/irc/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/irc/Makefile.am	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,15 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:50 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+pkglib_LTLIBRARIES	= irc.la
+irc_la_SOURCES		= irc.c
+irc_la_LDFLAGS		= -module -avoid-version -no-undefined
+irc_la_LIBADD		= @TCL_LIBS@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/irc/chan.c
diff -u /dev/null eggdrop1.7/modules/irc/chan.c:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/irc/chan.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,2219 @@
+/*
+ * chan.c -- part of irc.mod
+ *   almost everything to do with channel manipulation
+ *   telling channel status
+ *   'who' response
+ *   user kickban, kick, op, deop
+ *   idle kicking
+ *
+ * $Id: chan.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static time_t last_ctcp = (time_t) 0L;
+static int    count_ctcp = 0;
+static time_t last_invtime = (time_t) 0L;
+static char   last_invchan[300] = "";
+
+/* ID length for !channels.
+ */
+#define CHANNEL_ID_LEN 5
+
+
+/* Returns a pointer to a new channel member structure.
+ */
+static memberlist *newmember(struct chanset_t *chan)
+{
+  memberlist *x;
+
+  for (x = chan->channel.member; x && x->nick[0]; x = x->next); 
+  x->next = calloc(1, sizeof(memberlist));
+  x->next->next = NULL;
+  x->next->nick[0] = 0;
+  x->next->split = 0L;
+  x->next->last = 0L;
+  x->next->delay = 0L;
+  chan->channel.members++;
+  return x;
+}
+
+/* Always pass the channel dname (display name) to this function <cybah>
+ */
+static void update_idle(char *chname, char *nick)
+{
+  memberlist *m;
+  struct chanset_t *chan;
+
+  chan = findchan_by_dname(chname);
+  if (chan) {
+    m = ismember(chan, nick);
+    if (m)
+      m->last = now;
+  }
+}
+
+/* Returns the current channel mode.
+ */
+static char *getchanmode(struct chanset_t *chan)
+{
+  static char s[121];
+  int atr, i;
+
+  s[0] = '+';
+  i = 1;
+  atr = chan->channel.mode;
+  if (atr & CHANINV)
+    s[i++] = 'i';
+  if (atr & CHANPRIV)
+    s[i++] = 'p';
+  if (atr & CHANSEC)
+    s[i++] = 's';
+  if (atr & CHANMODER)
+    s[i++] = 'm';
+  if (atr & CHANNOCLR)
+    s[i++] = 'c';
+  if (atr & CHANREGON)
+    s[i++] = 'R';
+  if (atr & CHANTOPIC)
+    s[i++] = 't';
+  if (atr & CHANNOMSG)
+    s[i++] = 'n';
+  if (atr & CHANANON)
+    s[i++] = 'a';
+  if (atr & CHANKEY)
+    s[i++] = 'k';
+  if (chan->channel.maxmembers != 0)
+    s[i++] = 'l';
+  s[i] = 0;
+  if (chan->channel.key[0])
+    i += sprintf(s + i, " %s", chan->channel.key);
+  if (chan->channel.maxmembers != 0)
+    sprintf(s + i, " %d", chan->channel.maxmembers);
+  return s;
+}
+
+static void check_exemptlist(struct chanset_t *chan, char *from)
+{
+  masklist *e;
+  int ok = 0;
+
+  if (!use_exempts)
+    return;
+
+  for (e = chan->channel.exempt; e->mask[0]; e = e->next)
+    if (wild_match(e->mask, from)) {
+      add_mode(chan, '-', 'e', e->mask);
+      ok = 1;
+    }
+  if (prevent_mixing && ok)
+    flush_mode(chan, QUICK);
+}
+
+/* Check a channel and clean-out any more-specific matching masks.
+ *
+ * Moved all do_ban(), do_exempt() and do_invite() into this single function
+ * as the code bloat is starting to get rediculous <cybah>
+ */
+static void do_mask(struct chanset_t *chan, masklist *m, char *mask, char Mode)
+{
+  for (; m && m->mask[0]; m = m->next)
+    if (wild_match(mask, m->mask) && irccmp(mask, m->mask))
+      add_mode(chan, '-', Mode, m->mask);
+  add_mode(chan, '+', Mode, mask);
+  flush_mode(chan, QUICK);
+}
+
+/* This is a clone of detect_flood, but works for channel specificity now
+ * and handles kick & deop as well.
+ */
+static int detect_chan_flood(char *floodnick, char *floodhost, char *from,
+			     struct chanset_t *chan, int which, char *victim)
+{
+  char h[UHOSTLEN], ftype[12], *p;
+  struct userrec *u;
+  memberlist *m;
+  int thr = 0, lapse = 0;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  if (!chan || (which < 0) || (which >= FLOOD_CHAN_MAX))
+    return 0;
+  m = ismember(chan, floodnick);
+  /* Do not punish non-existant channel members and IRC services like
+   * ChanServ
+   */
+  if (!m && (which != FLOOD_JOIN))
+    return 0;
+
+  get_user_flagrec(get_user_by_host(from), &fr, chan->dname);
+  if (glob_bot(fr) ||
+      ((which == FLOOD_DEOP) && (glob_master(fr) || chan_master(fr))) ||
+      ((which != FLOOD_DEOP) && (glob_friend(fr) || chan_friend(fr))) ||
+      (channel_dontkickops(chan) &&
+       (chan_op(fr) || (glob_op(fr) && !chan_deop(fr)))))	/* arthur2 */
+    return 0;
+
+  /* Determine how many are necessary to make a flood. */
+  switch (which) {
+  case FLOOD_PRIVMSG:
+  case FLOOD_NOTICE:
+    thr = chan->flood_pub_thr;
+    lapse = chan->flood_pub_time;
+    strcpy(ftype, "pub");
+    break;
+  case FLOOD_CTCP:
+    thr = chan->flood_ctcp_thr;
+    lapse = chan->flood_ctcp_time;
+    strcpy(ftype, "pub");
+    break;
+  case FLOOD_NICK:
+    thr = chan->flood_nick_thr;
+    lapse = chan->flood_nick_time;
+    strcpy(ftype, "nick");
+    break;
+  case FLOOD_JOIN:
+    thr = chan->flood_join_thr;
+    lapse = chan->flood_join_time;
+      strcpy(ftype, "join");
+    break;
+  case FLOOD_DEOP:
+    thr = chan->flood_deop_thr;
+    lapse = chan->flood_deop_time;
+    strcpy(ftype, "deop");
+    break;
+  case FLOOD_KICK:
+    thr = chan->flood_kick_thr;
+    lapse = chan->flood_kick_time;
+    strcpy(ftype, "kick");
+    break;
+  }
+  if ((thr == 0) || (lapse == 0))
+    return 0;			/* no flood protection */
+  /* Okay, make sure i'm not flood-checking myself */
+  if (match_my_nick(floodnick))
+    return 0;
+  if (!strcasecmp(floodhost, botuserhost))
+    return 0;
+  /* My user at host (?) */
+  if ((which == FLOOD_KICK) || (which == FLOOD_DEOP))
+    p = floodnick;
+  else {
+    p = strchr(floodhost, '@');
+    if (p) {
+      p++;
+    }
+    if (!p)
+      return 0;
+  }
+  if (irccmp(chan->floodwho[which], p)) {	/* new */
+    strncpy(chan->floodwho[which], p, 81);
+    chan->floodwho[which][81] = 0;
+    chan->floodtime[which] = now;
+    chan->floodnum[which] = 1;
+    return 0;
+  }
+  if (chan->floodtime[which] < now - lapse) {
+    /* Flood timer expired, reset it */
+    chan->floodtime[which] = now;
+    chan->floodnum[which] = 1;
+    return 0;
+  }
+  /* Deop'n the same person, sillyness ;) - so just ignore it */
+  if (which == FLOOD_DEOP) {
+    if (!irccmp(chan->deopd, victim))
+      return 0;
+    else
+      strcpy(chan->deopd, victim);
+  }
+  chan->floodnum[which]++;
+  if (chan->floodnum[which] >= thr) {	/* FLOOD */
+    /* Reset counters */
+    chan->floodnum[which] = 0;
+    chan->floodtime[which] = 0;
+    chan->floodwho[which][0] = 0;
+    if (which == FLOOD_DEOP)
+      chan->deopd[0] = 0;
+    u = get_user_by_host(from);
+    if (check_tcl_flud(floodnick, floodhost, u, ftype, chan->dname))
+      return 0;
+    switch (which) {
+    case FLOOD_PRIVMSG:
+    case FLOOD_NOTICE:
+    case FLOOD_CTCP:
+      /* Flooding chan! either by public or notice */
+      if (me_op(chan) && !chan_sentkick(m)) {
+	putlog(LOG_MODES, chan->dname, _("Channel flood from %s -- kicking"), floodnick);
+	dprintf(DP_MODE, "KICK %s %s :%s\n", chan->name, floodnick,
+		_("flood"));
+	m->flags |= SENTKICK;
+      }
+      return 1;
+    case FLOOD_JOIN:
+    case FLOOD_NICK:
+      if (use_exempts &&
+	  (u_match_mask(global_exempts, from) ||
+	   u_match_mask(chan->exempts, from)))
+	return 1;
+      simple_sprintf(h, "*!*@%s", p);
+      if (!isbanned(chan, h) && me_op(chan)) {
+	check_exemptlist(chan, from);
+	do_mask(chan, chan->channel.ban, h, 'b');
+      }
+      if ((u_match_mask(global_bans, from))
+	  || (u_match_mask(chan->bans, from)))
+	return 1;		/* Already banned */
+      if (which == FLOOD_JOIN)
+	putlog(LOG_MISC | LOG_JOIN, chan->dname, _("JOIN flood from @%s!  Banning."), p);
+      else
+	putlog(LOG_MISC | LOG_JOIN, chan->dname, _("NICK flood from @%s!  Banning."), p);
+      strcpy(ftype + 4, " flood");
+      u_addban(chan, h, origbotname, ftype, now + (60 * ban_time), 0);
+      if (!channel_enforcebans(chan) && me_op(chan)) {
+	  char s[UHOSTLEN];
+	  for (m = chan->channel.member; m && m->nick[0]; m = m->next) {	  
+	    sprintf(s, "%s!%s", m->nick, m->userhost);
+	    if (wild_match(h, s) &&
+		(m->joined >= chan->floodtime[which]) &&
+		   !chan_sentkick(m) && !match_my_nick(m->nick)) {
+	      m->flags |= SENTKICK;
+	      if (which == FLOOD_JOIN)
+	      dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, m->nick,
+		      _("join flood"));
+	      else
+	        dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, m->nick,
+		      _("nick flood"));
+	    }
+	  }
+	}
+      return 1;
+    case FLOOD_KICK:
+      if (me_op(chan) && !chan_sentkick(m)) {
+	putlog(LOG_MODES, chan->dname, "Kicking %s, for mass kick.", floodnick);
+	dprintf(DP_MODE, "KICK %s %s :%s\n", chan->name, floodnick,
+		_("mass kick, go sit in a corner"));
+	m->flags |= SENTKICK;
+      }
+    return 1;
+    case FLOOD_DEOP:
+      if (me_op(chan) && !chan_sentkick(m)) {
+	putlog(LOG_MODES, chan->dname,
+	       _("Mass deop on %s by %s"), chan->dname, from);
+	dprintf(DP_MODE, "KICK %s %s :%s\n",
+		chan->name, floodnick, _("Mass deop.  Go sit in a corner."));
+	m->flags |= SENTKICK;
+      }
+      return 1;
+    }
+  }
+  return 0;
+}
+
+/* Given a [nick!]user at host, place a quick ban on them on a chan.
+ */
+static char *quickban(struct chanset_t *chan, char *uhost)
+{
+  static char s1[512];
+
+  maskhost(uhost, s1);
+  if ((strlen(s1) != 1) && (strict_host == 0))
+    s1[2] = '*';		/* arthur2 */
+  do_mask(chan, chan->channel.ban, s1, 'b');
+  return s1;
+}
+
+/* Kick any user (except friends/masters) with certain mask from channel
+ * with a specified comment.  Ernst 18/3/1998
+ */
+static void kick_all(struct chanset_t *chan, char *hostmask, char *comment, int bantype)
+{
+  memberlist *m;
+  char kicknick[512], s[UHOSTLEN];
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  int k, l, flushed;
+
+  if (!me_op(chan))
+    return;
+  k = 0;
+  flushed = 0;
+  kicknick[0] = 0;
+  for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+    sprintf(s, "%s!%s", m->nick, m->userhost);
+    get_user_flagrec(m->user ? m->user : get_user_by_host(s), &fr, chan->dname);
+    if (wild_match(hostmask, s) && !chan_sentkick(m) &&
+	!match_my_nick(m->nick) && !chan_issplit(m) &&
+	!glob_friend(fr) && !chan_friend(fr) &&
+	!(use_exempts &&
+	  ((bantype && isexempted(chan, s)) ||
+	   (u_match_mask(global_exempts,s) ||
+	    u_match_mask(chan->exempts, s)))) &&
+	!(channel_dontkickops(chan) &&
+	  (chan_op(fr) || (glob_op(fr) && !chan_deop(fr))))) {	/* arthur2 */
+      if (!flushed) {
+	/* We need to kick someone, flush eventual bans first */
+	flush_mode(chan, QUICK);
+	flushed += 1;
+      }
+      m->flags |= SENTKICK;	/* Mark as pending kick */
+      if (kicknick[0])
+	strcat(kicknick, ",");
+      strcat(kicknick, m->nick);
+      k += 1;
+      l = strlen(chan->name) + strlen(kicknick) + strlen(comment) + 5;
+      if ((kick_method != 0 && k == kick_method) || (l > 480)) {
+	dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, kicknick, comment);
+	k = 0;
+	kicknick[0] = 0;
+      }
+    }
+  }
+  if (k > 0)
+    dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, kicknick, comment);
+}
+
+/* If any bans match this wildcard expression, refresh them on the channel.
+ */
+static void refresh_ban_kick(struct chanset_t *chan, char *user, char *nick)
+{
+  register maskrec	*b;
+  memberlist		*m;
+  int			 cycle;
+
+  m = ismember(chan, nick);
+  if (!m)
+    return;
+  /* Check channel bans in first cycle and global bans
+     in second cycle. */
+  for (cycle = 0; cycle < 2; cycle++) {
+    for (b = cycle ? chan->bans : global_bans; b; b = b->next) {
+      if (wild_match(b->mask, user)) {
+	struct flag_record	fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+	char			c[512];		/* The ban comment.	*/
+	char			s[UHOSTLEN];
+
+	sprintf(s, "%s!%s", m->nick, m->userhost);
+	get_user_flagrec(m->user ? m->user : get_user_by_host(s), &fr,
+			 chan->dname);
+	if (!glob_friend(fr) && !chan_friend(fr))
+	  add_mode(chan, '-', 'o', nick);	/* Guess it can't hurt.	*/
+	check_exemptlist(chan, user);
+	do_mask(chan, chan->channel.ban, b->mask, 'b');
+	b->lastactive = now;
+	if (b->desc && b->desc[0] != '@')
+	  snprintf(c, sizeof c, "%s%s", _("banned: "), b->desc);
+	else
+	  c[0] = 0;
+	kick_all(chan, b->mask, c[0] ? c : _("You are banned"), 0);
+	return;					/* Drop out on 1st ban.	*/
+      }
+    }
+  }
+}
+
+/* This is a bit cumbersome at the moment, but it works... Any improvements
+ * then feel free to have a go.. Jason
+ */
+static void refresh_exempt(struct chanset_t *chan, char *user)
+{
+  maskrec	*e;
+  masklist	*b;
+  int		 cycle;
+
+  /* Check channel exempts in first cycle and global exempts
+     in second cycle. */
+  for (cycle = 0; cycle < 2; cycle++) {
+    for (e = cycle ? chan->exempts : global_exempts; e; e = e->next) {
+      if (wild_match(user, e->mask) || wild_match(e->mask,user)) {
+        for (b = chan->channel.ban; b && b->mask[0]; b = b->next) {
+          if (wild_match(b->mask, user) || wild_match(user, b->mask)) {
+            if (e->lastactive < now - 60 && !isexempted(chan, e->mask)) {
+              do_mask(chan, chan->channel.exempt, e->mask, 'e');
+              e->lastactive = now;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+static void refresh_invite(struct chanset_t *chan, char *user)
+{
+  maskrec	*i;
+  int		 cycle;
+
+  /* Check channel invites in first cycle and global invites
+     in second cycle. */
+  for (cycle = 0; cycle < 2; cycle++) {
+    for (i = cycle ? chan->invites : global_invites; i; i = i->next) {
+      if (wild_match(i->mask, user) &&
+	  ((i->flags & MASKREC_STICKY) || (chan->channel.mode & CHANINV))) {
+        if (i->lastactive < now - 60 && !isinvited(chan, i->mask)) {
+          do_mask(chan, chan->channel.invite, i->mask, 'I');
+	  i->lastactive = now;
+	  return;
+	}
+      }
+    }
+  }
+}
+
+/* Enforce all channel bans in a given channel.  Ernst 18/3/1998
+ */
+static void enforce_bans(struct chanset_t *chan)
+{
+  char		 me[UHOSTLEN];
+  masklist	*b;
+
+  if (!me_op(chan))
+    return;			/* Can't do it :( */
+  simple_sprintf(me, "%s!%s", botname, botuserhost);
+  /* Go through all bans, kicking the users. */
+  for (b = chan->channel.ban; b && b->mask[0]; b = b->next) {
+    if (!wild_match(b->mask, me))
+      if (!isexempted(chan, b->mask))
+	kick_all(chan, b->mask, _("You are banned"), 1);
+  }
+}
+
+/* Make sure that all who are 'banned' on the userlist are actually in fact
+ * banned on the channel.
+ *
+ * Note: Since i was getting a ban list, i assume i'm chop.
+ */
+static void recheck_bans(struct chanset_t *chan)
+{
+  maskrec	*u;
+  int		 cycle;
+
+  /* Check channel bans in first cycle and global bans
+     in second cycle. */
+  for (cycle = 0; cycle < 2; cycle++) {
+    for (u = cycle ? chan->bans : global_bans; u; u = u->next)
+      if (!isbanned(chan, u->mask) && (!channel_dynamicbans(chan) ||
+				       (u->flags & MASKREC_STICKY)))
+	add_mode(chan, '+', 'b', u->mask);
+  }
+}
+
+/* Make sure that all who are exempted on the userlist are actually in fact
+ * exempted on the channel.
+ *
+ * Note: Since i was getting an excempt list, i assume i'm chop.
+ */
+static void recheck_exempts(struct chanset_t *chan)
+{
+  maskrec	*e;
+  masklist	*b;
+  int		 cycle;
+
+  /* Check channel exempts in first cycle and global exempts
+     in second cycle. */
+  for (cycle = 0; cycle < 2; cycle++) {
+    for (e = cycle ? chan->exempts : global_exempts; e; e = e->next) {
+      if (!isexempted(chan, e->mask) &&
+          (!channel_dynamicexempts(chan) || (e->flags & MASKREC_STICKY)))
+        add_mode(chan, '+', 'e', e->mask);
+      for (b = chan->channel.ban; b && b->mask[0]; b = b->next) {
+        if ((wild_match(b->mask, e->mask) || wild_match(e->mask, b->mask)) &&
+            !isexempted(chan, e->mask))
+	  add_mode(chan,'+','e',e->mask);
+	/* do_mask(chan, chan->channel.exempt, e->mask, 'e');*/
+      }
+    }
+  }
+}
+
+/* Make sure that all who are invited on the userlist are actually in fact
+ * invited on the channel.
+ *
+ * Note: Since i was getting an invite list, i assume i'm chop.
+ */
+static void recheck_invites(struct chanset_t *chan)
+{
+  maskrec	*ir;
+  int		 cycle;
+
+  /* Check channel invites in first cycle and global invites
+     in second cycle. */
+  for (cycle = 0; cycle < 2; cycle++)  {
+    for (ir = cycle ? chan->invites : global_invites; ir; ir = ir->next) {
+      /* If invite isn't set and (channel is not dynamic invites and not invite
+       * only) or invite is sticky.
+       */
+      if (!isinvited(chan, ir->mask) && ((!channel_dynamicinvites(chan) &&
+          !(chan->channel.mode & CHANINV)) || ir->flags & MASKREC_STICKY))
+	add_mode(chan, '+', 'I', ir->mask);
+	/* do_mask(chan, chan->channel.invite, ir->mask, 'I');*/
+    }
+  }
+}
+
+/* Resets the masks on the channel.
+ */
+static void resetmasks(struct chanset_t *chan, masklist *m, maskrec *mrec,
+		       maskrec *global_masks, char mode)
+{
+  if (!me_op(chan))
+    return;                     /* Can't do it */
+
+  /* Remove masks we didn't put there */
+  for (; m && m->mask[0]; m = m->next) {
+    if (!u_equals_mask(global_masks, m->mask) && !u_equals_mask(mrec, m->mask))
+      add_mode(chan, '-', mode, m->mask);
+  }
+
+  /* Make sure the intended masks are still there */
+  switch (mode) {
+    case 'b':
+      recheck_bans(chan);
+      break;
+    case 'e':
+      recheck_exempts(chan);
+      break;
+    case 'I':
+      recheck_invites(chan);
+      break;
+    default:
+      putlog(LOG_MISC, "*", "(!) Invalid mode '%c' in resetmasks()", mode);
+      break;
+  }
+}
+static void check_this_ban(struct chanset_t *chan, char *banmask, int sticky)
+{
+  memberlist *m;
+  char user[UHOSTLEN];
+
+  if (!me_op(chan))
+    return;
+  for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+    sprintf(user, "%s!%s", m->nick, m->userhost);
+    if (wild_match(banmask, user) &&
+        !(use_exempts &&
+          (u_match_mask(global_exempts, user) ||
+           u_match_mask(chan->exempts, user))))
+      refresh_ban_kick(chan, user, m->nick);
+  }
+  if (!isbanned(chan, banmask) &&
+      (!channel_dynamicbans(chan) || sticky))
+    add_mode(chan, '+', 'b', banmask);
+}
+
+static void recheck_channel_modes(struct chanset_t *chan)
+{
+  int cur = chan->channel.mode,
+      mns = chan->mode_mns_prot,
+      pls = chan->mode_pls_prot;
+
+  if (!(chan->status & CHAN_ASKEDMODES)) {
+    if (pls & CHANINV && !(cur & CHANINV))
+      add_mode(chan, '+', 'i', "");
+    else if (mns & CHANINV && cur & CHANINV)
+      add_mode(chan, '-', 'i', "");
+    if (pls & CHANPRIV && !(cur & CHANPRIV))
+      add_mode(chan, '+', 'p', "");
+    else if (mns & CHANPRIV && cur & CHANPRIV)
+      add_mode(chan, '-', 'p', "");
+    if (pls & CHANSEC && !(cur & CHANSEC))
+      add_mode(chan, '+', 's', "");
+    else if (mns & CHANSEC && cur & CHANSEC)
+      add_mode(chan, '-', 's', "");
+    if (pls & CHANMODER && !(cur & CHANMODER))
+      add_mode(chan, '+', 'm', "");
+    else if (mns & CHANMODER && cur & CHANMODER)
+      add_mode(chan, '-', 'm', "");
+    if (pls & CHANNOCLR && !(cur & CHANNOCLR))
+      add_mode(chan, '+', 'c', "");
+    else if (mns & CHANNOCLR && cur & CHANNOCLR)
+      add_mode(chan, '-', 'c', "");
+    if (pls & CHANREGON && !(cur & CHANREGON))
+      add_mode(chan, '+', 'R', "");
+    else if (mns & CHANREGON && cur & CHANREGON)
+      add_mode(chan, '-', 'R', "");
+    if (pls & CHANTOPIC && !(cur & CHANTOPIC))
+      add_mode(chan, '+', 't', "");
+    else if (mns & CHANTOPIC && cur & CHANTOPIC)
+      add_mode(chan, '-', 't', "");
+    if (pls & CHANNOMSG && !(cur & CHANNOMSG))
+      add_mode(chan, '+', 'n', "");
+    else if ((mns & CHANNOMSG) && (cur & CHANNOMSG))
+      add_mode(chan, '-', 'n', "");
+    if ((pls & CHANANON) && !(cur & CHANANON))
+      add_mode(chan, '+', 'a', "");
+    else if ((mns & CHANANON) && (cur & CHANANON))
+      add_mode(chan, '-', 'a', "");
+    if ((pls & CHANQUIET) && !(cur & CHANQUIET))
+      add_mode(chan, '+', 'q', "");
+    else if ((mns & CHANQUIET) && (cur & CHANQUIET))
+      add_mode(chan, '-', 'q', "");
+    if ((chan->limit_prot != 0) && (chan->channel.maxmembers == 0)) {
+      char s[50];
+
+      sprintf(s, "%d", chan->limit_prot);
+      add_mode(chan, '+', 'l', s);
+    } else if ((mns & CHANLIMIT) && (chan->channel.maxmembers != 0))
+      add_mode(chan, '-', 'l', "");
+    if (chan->key_prot[0]) {
+      if (irccmp(chan->channel.key, chan->key_prot) != 0) {
+        if (chan->channel.key[0])
+	  add_mode(chan, '-', 'k', chan->channel.key);
+        add_mode(chan, '+', 'k', chan->key_prot);
+      }
+    } else if ((mns & CHANKEY) && (chan->channel.key[0]))
+      add_mode(chan, '-', 'k', chan->channel.key);
+  }
+}
+
+/* Things to do when i just became a chanop:
+ */
+static void recheck_channel(struct chanset_t *chan, int dobans)
+{
+  memberlist *m;
+  char s[UHOSTLEN], *p;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  static int stacking = 0;
+  int stop_reset = 0;
+
+  if (stacking)
+    return;			/* wewps */
+  if (!userlist)                /* Bot doesnt know anybody */
+    return;                     /* ... it's better not to deop everybody */
+  stacking++;
+  /* Okay, sort through who needs to be deopped. */
+  for (m = chan->channel.member; m && m->nick[0]; m = m->next) { 
+    sprintf(s, "%s!%s", m->nick, m->userhost);
+    if (!m->user)
+      m->user = get_user_by_host(s);
+    get_user_flagrec(m->user, &fr, chan->dname);
+    /* ignore myself */
+    if (!match_my_nick(m->nick)) {
+      /* if channel user is current a chanop */
+      if (chan_hasop(m)) {
+	if (glob_bot(fr))
+	  stop_reset = 1;
+	/* if user is channel deop */
+	if (chan_deop(fr) ||
+	/* OR global deop and NOT channel op */
+	    (glob_deop(fr) && !chan_op(fr))) {
+	  /* de-op! */
+	  add_mode(chan, '-', 'o', m->nick);
+	/* if channel mode is bitch */
+	} else if (channel_bitch(chan) &&
+	  /* AND the user isnt a channel op */
+		   (!chan_op(fr) &&
+	  /* AND the user isnt a global op, (or IS a chan deop) */
+		   !(glob_op(fr) && !chan_deop(fr)))) {
+	  /* de-op! mmmbop! */
+	  add_mode(chan, '-', 'o', m->nick);
+	}
+      }
+      /* check vs invites */
+      if (use_invites &&
+	  (u_match_mask(global_invites,s) ||
+	   u_match_mask(chan->invites, s)))
+	refresh_invite(chan, s);
+      /* don't kickban if permanent exempted */
+      if (!(use_exempts &&
+	    (u_match_mask(global_exempts,s) ||
+	     u_match_mask(chan->exempts, s)))) {
+        /* if match a ban */
+        if (u_match_mask(global_bans, s) ||
+            u_match_mask(chan->bans, s)) {
+	  /* bewm */
+	  refresh_ban_kick(chan, s, m->nick);
+	/* ^ will use the ban comment */
+	}
+	/* are they +k ? */
+	if (chan_kick(fr) || glob_kick(fr)) {
+	  check_exemptlist(chan, s);
+	  quickban(chan, m->userhost);
+	  p = get_user(&USERENTRY_COMMENT, m->user);
+	  dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, m->nick,
+		  p ? p : _("...and thank you for playing."));
+	  m->flags |= SENTKICK;
+	}
+      }
+      /* now lets look at de-op'd ppl */
+      if (!chan_hasop(m) &&
+	  /* if they're an op, channel or global (without channel +d) */
+	  (chan_op(fr) || (glob_op(fr) && !chan_deop(fr))) &&
+	  /* and the channel is op on join, or they are auto-opped */
+	  (channel_autoop(chan) || (glob_autoop(fr) || chan_autoop(fr)))) {
+	/* op them! */
+	add_mode(chan, '+', 'o', m->nick);
+	  /* otherwise, lets check +v stuff if the llamas want it */
+      } else if (!chan_hasvoice(m) && !chan_hasop(m)) {
+	if ((channel_autovoice(chan) && !chan_quiet(fr) &&
+	     (chan_voice(fr) || glob_voice(fr))) ||
+	    (!chan_quiet(fr) && (glob_gvoice(fr) || chan_gvoice(fr)))) {
+	  add_mode(chan, '+', 'v', m->nick);
+	}
+	/* do they have a voice on the channel */
+	if (chan_hasvoice(m) &&
+	    /* do they have the +q & no +v */
+	    (chan_quiet(fr) || (glob_quiet(fr) && !chan_voice(fr)))) {
+	  add_mode(chan, '-', 'v', m->nick);
+	}
+      }
+    }
+  }
+  if (dobans) {
+    if (channel_nouserbans(chan) && !stop_reset)
+      resetbans(chan);
+    else
+      recheck_bans(chan);
+    if (use_invites) {
+      if (channel_nouserinvites(chan) && !stop_reset)
+	resetinvites(chan);
+      else
+	recheck_invites(chan);
+    }
+    if (use_exempts) {
+      if (channel_nouserexempts(chan) && !stop_reset)
+	resetexempts(chan);
+      else
+	recheck_exempts(chan);
+    }
+    if (channel_enforcebans(chan))
+      enforce_bans(chan);
+    if ((chan->status & CHAN_ASKEDMODES) &&
+	!channel_inactive(chan)) /* Spot on guppy, this just keeps the
+	                          * checking sane */
+      dprintf(DP_SERVER, "MODE %s\n", chan->name);
+    recheck_channel_modes(chan);
+  }
+  stacking--;
+}
+
+/* got 324: mode status
+ * <server> 324 <to> <channel> <mode>
+ */
+static int got324(char *from, char *ignore, char *msg)
+{
+  int i = 1, ok =0;
+  char *p, *q, *chname;
+  struct chanset_t *chan;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  chan = findchan(chname);
+  if (!chan) {
+    putlog(LOG_MISC, "*", "%s: %s", _("Hmm, mode info from a channel Im not on"), chname);
+    dprintf(DP_SERVER, "PART %s\n", chname);
+    return 0;
+  }
+  if (chan->status & CHAN_ASKEDMODES)
+    ok = 1;
+  chan->status &= ~CHAN_ASKEDMODES;
+  chan->channel.mode = 0;
+  while (msg[i] != 0) {
+    if (msg[i] == 'i')
+      chan->channel.mode |= CHANINV;
+    if (msg[i] == 'p')
+      chan->channel.mode |= CHANPRIV;
+    if (msg[i] == 's')
+      chan->channel.mode |= CHANSEC;
+    if (msg[i] == 'm')
+      chan->channel.mode |= CHANMODER;
+    if (msg[i] == 'c')
+      chan->channel.mode |= CHANNOCLR;
+    if (msg[i] == 'R')
+      chan->channel.mode |= CHANREGON;
+    if (msg[i] == 't')
+      chan->channel.mode |= CHANTOPIC;
+    if (msg[i] == 'n')
+      chan->channel.mode |= CHANNOMSG;
+    if (msg[i] == 'a')
+      chan->channel.mode |= CHANANON;
+    if (msg[i] == 'q')
+      chan->channel.mode |= CHANQUIET;
+    if (msg[i] == 'k') {
+      chan->channel.mode |= CHANKEY;
+      p = strchr(msg, ' ');
+      if (p != NULL) {		/* Test for null key assignment */
+	p++;
+	q = strchr(p, ' ');
+	if (q != NULL) {
+	  *q = 0;
+	  set_key(chan, p);
+	  strcpy(p, q + 1);
+	} else {
+	  set_key(chan, p);
+	  *p = 0;
+	}
+      }
+      if ((chan->channel.mode & CHANKEY) && !(chan->channel.key[0]))
+        chan->status |= CHAN_ASKEDMODES;
+    }
+    if (msg[i] == 'l') {
+      p = strchr(msg, ' ');
+      if (p != NULL) {		/* test for null limit assignment */
+	p++;
+	q = strchr(p, ' ');
+	if (q != NULL) {
+	  *q = 0;
+	  chan->channel.maxmembers = atoi(p);
+	  strcpy(p, q + 1);
+	} else {
+	  chan->channel.maxmembers = atoi(p);
+	  *p = 0;
+	}
+      }
+    }
+    i++;
+  }
+  if (ok)
+    recheck_channel_modes(chan);
+  return 0;
+}
+
+static int got352or4(struct chanset_t *chan, char *user, char *host,
+		     char *nick, char *flags)
+{
+  char userhost[UHOSTLEN];
+  memberlist *m;
+
+  m = ismember(chan, nick);	/* In my channel list copy? */
+  if (!m) {			/* Nope, so update */
+    m = newmember(chan);	/* Get a new channel entry */
+    m->joined = m->split = m->delay = 0L;	/* Don't know when he joined */
+    m->flags = 0;		/* No flags for now */
+    m->last = now;		/* Last time I saw him */
+  }
+  strcpy(m->nick, nick);	/* Store the nick in list */
+  /* Store the userhost */
+  simple_sprintf(m->userhost, "%s@%s", user, host);
+  simple_sprintf(userhost, "%s!%s", nick, m->userhost);
+  /* Combine n!u at h */
+  m->user = NULL;		/* No handle match (yet) */
+  if (match_my_nick(nick)) {	/* Is it me? */
+    strcpy(botuserhost, m->userhost);	/* Yes, save my own userhost */
+    m->joined = now;		/* set this to keep the whining masses happy */
+  }
+  if (strchr(flags, '@') != NULL)	/* Flags say he's opped? */
+    m->flags |= (CHANOP | WASOP);	/* Yes, so flag in my table */
+  else
+    m->flags &= ~(CHANOP | WASOP);
+  if (strchr(flags, '+') != NULL)	/* Flags say he's voiced? */
+    m->flags |= CHANVOICE;	/* Yes */
+  else
+    m->flags &= ~CHANVOICE;
+  if (!(m->flags & (CHANVOICE | CHANOP)))
+    m->flags |= STOPWHO;
+  if (match_my_nick(nick) && any_ops(chan) && !me_op(chan))
+    check_tcl_need(chan->dname, "op");
+  m->user = get_user_by_host(userhost);
+  return 0;
+}
+
+/* got a 352: who info!
+ */
+static int got352(char *from, char *ignore, char *msg)
+{
+  char *nick, *user, *host, *chname, *flags;
+  struct chanset_t *chan;
+
+  newsplit(&msg);		/* Skip my nick - effeciently */
+  chname = newsplit(&msg);	/* Grab the channel */
+  chan = findchan(chname);	/* See if I'm on channel */
+  if (chan) {			/* Am I? */
+    user = newsplit(&msg);	/* Grab the user */
+    host = newsplit(&msg);	/* Grab the host */
+    newsplit(&msg);		/* Skip the server */
+    nick = newsplit(&msg);	/* Grab the nick */
+    flags = newsplit(&msg);	/* Grab the flags */
+    got352or4(chan, user, host, nick, flags);
+  }
+  return 0;
+}
+
+/* got a 354: who info! - iru style
+ */
+static int got354(char *from, char *ignore, char *msg)
+{
+  char *nick, *user, *host, *chname, *flags;
+  struct chanset_t *chan;
+
+  if (use_354) {
+    newsplit(&msg);		/* Skip my nick - effeciently */
+    if (msg[0] && (strchr(CHANMETA, msg[0]) != NULL)) {
+      chname = newsplit(&msg);	/* Grab the channel */
+      chan = findchan(chname);	/* See if I'm on channel */
+      if (chan) {		/* Am I? */
+	user = newsplit(&msg);	/* Grab the user */
+	host = newsplit(&msg);	/* Grab the host */
+	nick = newsplit(&msg);	/* Grab the nick */
+	flags = newsplit(&msg);	/* Grab the flags */
+	got352or4(chan, user, host, nick, flags);
+      }
+    }
+  }
+  return 0;
+}
+
+/* got 315: end of who
+ * <server> 315 <to> <chan> :End of /who
+ */
+static int got315(char *from, char *ignore, char *msg)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  chan = findchan(chname);
+  /* May have left the channel before the who info came in */
+  if (!chan || !channel_pending(chan))
+    return 0;
+  /* Finished getting who list, can now be considered officially ON CHANNEL */
+  chan->status |= CHAN_ACTIVE;
+  chan->status &= ~CHAN_PEND;
+  /* Am *I* on the channel now? if not, well d0h. */
+  if (!ismember(chan, botname)) {
+    putlog(LOG_MISC | LOG_JOIN, chan->dname, "Oops, I'm not really on %s",
+	   chan->dname);
+    clear_channel(chan, 1);
+    chan->status &= ~CHAN_ACTIVE;
+    dprintf(DP_MODE, "JOIN %s %s\n",
+	    (chan->name[0]) ? chan->name : chan->dname,
+	    chan->channel.key[0] ? chan->channel.key : chan->key_prot);
+  }
+  else if (me_op(chan))
+    recheck_channel(chan, 1);
+  else if (chan->channel.members == 1)
+    chan->status |= CHAN_STOP_CYCLE;
+  /* do not check for i-lines here. */
+  return 0;
+}
+
+/* got 367: ban info
+ * <server> 367 <to> <chan> <ban> [placed-by] [timestamp]
+ */
+static int got367(char *from, char *ignore, char *origmsg)
+{
+  char s[UHOSTLEN], *ban, *who, *chname, buf[511], *msg;
+  struct chanset_t *chan;
+  struct userrec *u;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  strncpy(buf, origmsg, 510);
+  buf[510] = 0;
+  msg = buf;
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  chan = findchan(chname);
+  if (!chan || !(channel_pending(chan) || channel_active(chan)))
+    return 0;
+  ban = newsplit(&msg);
+  who = newsplit(&msg);
+  /* Extended timestamp format? */
+  if (who[0])
+    newban(chan, ban, who);
+  else
+    newban(chan, ban, "existent");
+  simple_sprintf(s, "%s!%s", botname, botuserhost);
+  if (wild_match(ban, s))
+    add_mode(chan, '-', 'b', ban);
+  u = get_user_by_host(ban);
+  if (u) {		/* Why bother check no-user :) - of if Im not an op */
+    get_user_flagrec(u, &fr, chan->dname);
+    if (chan_op(fr) || (glob_op(fr) && !chan_deop(fr)))
+      add_mode(chan, '-', 'b', ban);
+    /* These will be flushed by 368: end of ban info */
+  }
+  return 0;
+}
+
+/* got 368: end of ban list
+ * <server> 368 <to> <chan> :etc
+ */
+static int got368(char *from, char *ignore, char *msg)
+{
+  struct chanset_t *chan;
+  char *chname;
+
+  /* Okay, now add bans that i want, which aren't set yet */
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  chan = findchan(chname);
+  if (chan)
+    chan->status &= ~CHAN_ASKEDBANS;
+  /* If i sent a mode -b on myself (deban) in got367, either
+   * resetbans() or recheck_bans() will flush that.
+   */
+  return 0;
+}
+
+/* got 348: ban exemption info
+ * <server> 348 <to> <chan> <exemption>
+ */
+static int got348(char *from, char *ignore, char *origmsg)
+{
+  char *exempt, *who, *chname, buf[511], *msg;
+  struct chanset_t *chan;
+
+  if (use_exempts == 0)
+    return 0;
+
+  strncpy(buf, origmsg, 510);
+  buf[510] = 0;
+  msg = buf;
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  chan = findchan(chname);
+  if (!chan || !(channel_pending(chan) || channel_active(chan)))
+    return 0;
+  exempt = newsplit(&msg);
+  who = newsplit(&msg);
+  /* Extended timestamp format? */
+  if (who[0])
+    newexempt(chan, exempt, who);
+  else
+    newexempt(chan, exempt, "existent");
+  return 0;
+}
+
+/* got 349: end of ban exemption list
+ * <server> 349 <to> <chan> :etc
+ */
+static int got349(char *from, char *ignore, char *msg)
+{
+  struct chanset_t *chan;
+  char *chname;
+
+  if (use_exempts == 1) {
+    newsplit(&msg);
+    chname = newsplit(&msg);
+    chan = findchan(chname);
+    if (chan)
+      chan->ircnet_status &= ~CHAN_ASKED_EXEMPTS;
+  }
+  return 0;
+}
+
+/* got 346: invite exemption info
+ * <server> 346 <to> <chan> <exemption>
+ */
+static int got346(char *from, char *ignore, char *origmsg)
+{
+  char *invite, *who, *chname, buf[511], *msg;
+  struct chanset_t *chan;
+
+  strncpy(buf, origmsg, 510);
+  buf[510] = 0;
+  msg = buf;
+  if (use_invites == 0)
+    return 0;
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  chan = findchan(chname);
+  if (!chan || !(channel_pending(chan) || channel_active(chan)))
+    return 0;
+  invite = newsplit(&msg);
+  who = newsplit(&msg);
+  /* Extended timestamp format? */
+  if (who[0])
+    newinvite(chan, invite, who);
+  else
+    newinvite(chan, invite, "existent");
+  return 0;
+}
+
+/* got 347: end of invite exemption list
+ * <server> 347 <to> <chan> :etc
+ */
+static int got347(char *from, char *ignore, char *msg)
+{
+  struct chanset_t *chan;
+  char *chname;
+
+  if (use_invites == 1) {
+    newsplit(&msg);
+    chname = newsplit(&msg);
+    chan = findchan(chname);
+    if (chan)
+      chan->ircnet_status &= ~CHAN_ASKED_INVITED;
+  }
+  return 0;
+}
+
+/* Too many channels.
+ */
+static int got405(char *from, char *ignore, char *msg)
+{
+  char *chname;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  putlog(LOG_MISC, "*", _("Im on too many channels--cant join: %s"), chname);
+  return 0;
+}
+
+/* This is only of use to us with !channels. We get this message when
+ * attempting to join a non-existant !channel... The channel must be
+ * created by sending 'JOIN !!<channel>'. <cybah>
+ *
+ * 403 - ERR_NOSUCHCHANNEL
+ */
+static int got403(char *from, char *ignore, char *msg)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  if (chname && chname[0]=='!') {
+    chan = findchan_by_dname(chname);
+    if (!chan) {
+      chan = findchan(chname);
+      if (!chan)
+        return 0;       /* Ignore it */
+      /* We have the channel unique name, so we have attempted to join
+       * a specific !channel that doesnt exist. Now attempt to join the
+       * channel using it's short name.
+       */
+      putlog(LOG_MISC, "*",
+             "Unique channel %s does not exist... Attempting to join with "
+             "short name.", chname);
+      dprintf(DP_SERVER, "JOIN %s\n", chan->dname);
+    } else {
+      /* We have found the channel, so the server has given us the short
+       * name. Prefix another '!' to it, and attempt the join again...
+       */
+      putlog(LOG_MISC, "*",
+             "Channel %s does not exist... Attempting to create it.", chname);
+      dprintf(DP_SERVER, "JOIN !%s\n", chan->dname);
+    }
+  }
+  return 0;
+}
+
+/* got 471: can't join channel, full
+ */
+static int got471(char *from, char *ignore, char *msg)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  /* !channel short names (also referred to as 'description names'
+   * can be received by skipping over the unique ID.
+   */
+  if ((chname[0] == '!') && (strlen(chname) > CHANNEL_ID_LEN)) {
+    chname += CHANNEL_ID_LEN;
+    chname[0] = '!';
+  }
+  /* We use dname because name is first set on JOIN and we might not
+   * have joined the channel yet.
+   */
+  chan = findchan_by_dname(chname);
+  if (chan) {
+    putlog(LOG_JOIN, chan->dname, _("Channel full--cant join: %s"), chan->dname);
+    check_tcl_need(chan->dname, "limit");
+  } else
+    putlog(LOG_JOIN, chname, _("Channel full--cant join: %s"), chname);
+  return 0;
+}
+
+/* got 473: can't join channel, invite only
+ */
+static int got473(char *from, char *ignore, char *msg)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  /* !channel short names (also referred to as 'description names'
+   * can be received by skipping over the unique ID.
+   */
+  if ((chname[0] == '!') && (strlen(chname) > CHANNEL_ID_LEN)) {
+    chname += CHANNEL_ID_LEN;
+    chname[0] = '!';
+  }
+  /* We use dname because name is first set on JOIN and we might not
+   * have joined the channel yet.
+   */
+  chan = findchan_by_dname(chname);
+  if (chan) {
+    putlog(LOG_JOIN, chan->dname, _("Channel invite only--cant join: %s"), chan->dname);
+    check_tcl_need(chan->dname, "invite");
+  } else
+    putlog(LOG_JOIN, chname, _("Channel invite only--cant join: %s"), chname);
+  return 0;
+}
+
+/* got 474: can't join channel, banned
+ */
+static int got474(char *from, char *ignore, char *msg)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  /* !channel short names (also referred to as 'description names'
+   * can be received by skipping over the unique ID.
+   */
+  if ((chname[0] == '!') && (strlen(chname) > CHANNEL_ID_LEN)) {
+    chname += CHANNEL_ID_LEN;
+    chname[0] = '!';
+  }
+  /* We use dname because name is first set on JOIN and we might not
+   * have joined the channel yet.
+   */
+  chan = findchan_by_dname(chname);
+  if (chan) {
+    putlog(LOG_JOIN, chan->dname, _("Banned from channel--can't join: %s"), chan->dname);
+    check_tcl_need(chan->dname, "unban");
+  } else
+    putlog(LOG_JOIN, chname, _("Banned from channel--can't join: %s"), chname);
+  return 0;
+}
+
+/* got 475: can't goin channel, bad key
+ */
+static int got475(char *from, char *ignore, char *msg)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  /* !channel short names (also referred to as 'description names'
+   * can be received by skipping over the unique ID.
+   */
+  if ((chname[0] == '!') && (strlen(chname) > CHANNEL_ID_LEN)) {
+    chname += CHANNEL_ID_LEN;
+    chname[0] = '!';
+  }
+  /* We use dname because name is first set on JOIN and we might not
+   * have joined the channel yet.
+   */
+  chan = findchan_by_dname(chname);
+  if (chan) {
+    putlog(LOG_JOIN, chan->dname, _("Bad key--cant join: %s"), chan->dname);
+    if (chan->channel.key[0]) {
+      free(chan->channel.key);
+      chan->channel.key = calloc(1, 1);
+      dprintf(DP_MODE, "JOIN %s %s\n", chan->dname, chan->key_prot);
+    } else
+      check_tcl_need(chan->dname, "key");
+  } else
+    putlog(LOG_JOIN, chname, _("Bad key--cant join: %s"), chname);
+  return 0;
+}
+
+/* got invitation
+ */
+static int gotinvite(char *from, char *ignore, char *msg)
+{
+  char buf[UHOSTLEN], *nick, *uhost;
+  struct chanset_t *chan;
+
+  newsplit(&msg);
+  fixcolon(msg);
+  strncpyz(buf, from, sizeof buf);
+  nick = strtok(buf, "!");
+  uhost = strtok(NULL, "!");
+  if (!irccmp(last_invchan, msg))
+    if (now - last_invtime < 30)
+      return 0;		/* Two invites to the same channel in 30 seconds? */
+  putlog(LOG_MISC, "*", "%s!%s invited me to %s", nick, uhost, msg);
+  strncpy(last_invchan, msg, 299);
+  last_invchan[299] = 0;
+  last_invtime = now;
+  chan = findchan(msg);
+  if (!chan)
+    /* Might be a short-name */
+    chan = findchan_by_dname(msg);
+  if (chan && (channel_pending(chan) || channel_active(chan)))
+    dprintf(DP_HELP, "NOTICE %s :I'm already here.\n", nick);
+  else if (chan && !channel_inactive(chan))
+    dprintf(DP_MODE, "JOIN %s %s\n", (chan->name[0]) ? chan->name : chan->dname,
+            chan->channel.key[0] ? chan->channel.key : chan->key_prot);
+  return 0;
+}
+
+/* Set the topic.
+ */
+static void set_topic(struct chanset_t *chan, char *k)
+{
+  if (chan->channel.topic)
+    free(chan->channel.topic);
+  if (k && k[0]) {
+    chan->channel.topic = calloc(1, strlen(k) + 1);
+    strcpy(chan->channel.topic, k);
+  } else
+    chan->channel.topic = NULL;
+}
+
+/* Topic change.
+ */
+static int gottopic(char *from, char *ignore, char *msg)
+{
+  char buf[UHOSTLEN], *nick, *uhost, *chname;
+  memberlist *m;
+  struct chanset_t *chan;
+  struct userrec *u;
+
+  chname = newsplit(&msg);
+  fixcolon(msg);
+  u = get_user_by_host(from);
+  strncpyz(buf, from, sizeof buf);
+  nick = strtok(buf, "!");
+  uhost = strtok(NULL, "!");
+  chan = findchan(chname);
+  if (chan) {
+    putlog(LOG_JOIN, chan->dname, "Topic changed on %s by %s!%s: %s",
+	   chan->dname, nick, uhost, msg);
+    m = ismember(chan, nick);
+    if (m != NULL)
+      m->last = now;
+    set_topic(chan, msg);
+    check_tcl_topc(nick, uhost, u, chan->dname, msg);
+  }
+  return 0;
+}
+
+/* 331: no current topic for this channel
+ * <server> 331 <to> <chname> :etc
+ */
+static int got331(char *from, char *ignore, char *msg)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  chan = findchan(chname);
+  if (chan) {
+    set_topic(chan, NULL);
+    check_tcl_topc("*", "*", NULL, chan->dname, "");
+  }
+  return 0;
+}
+
+/* 332: topic on a channel i've just joined
+ * <server> 332 <to> <chname> :topic goes here
+ */
+static int got332(char *from, char *ignore, char *msg)
+{
+  struct chanset_t *chan;
+  char *chname;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  chan = findchan(chname);
+  if (chan) {
+    fixcolon(msg);
+    set_topic(chan, msg);
+    check_tcl_topc("*", "*", NULL, chan->dname, msg);
+  }
+  return 0;
+}
+
+static void set_delay(struct chanset_t *chan, char *nick)
+{
+  time_t a_delay;
+  int aop_min = chan->aop_min, aop_max = chan->aop_max, count = 0;
+  memberlist *m, *m2;
+
+  m = ismember(chan, nick);
+  if (!m)
+    return;
+  if (aop_min >= aop_max)
+    a_delay = now + aop_min;
+  else
+    a_delay = now + (random() % (aop_max - aop_min)) + aop_min + 1;
+  for (m2 = chan->channel.member; m2 && m2->nick[0]; m2 = m2->next)
+    if (m2->delay && !(m2->flags & FULL_DELAY))
+      count++;
+  if (count)
+    for (m2 = chan->channel.member; m2 && m2->nick[0]; m2 = m2->next)
+      if (m2->delay && !(m2->flags & FULL_DELAY)) {
+ m2->delay = a_delay;
+ if (count + 1 >=  modesperline)
+   m2->flags |= FULL_DELAY;
+      }
+  if (count + 1 >=modesperline)
+    m->flags |= FULL_DELAY;
+  m->delay = a_delay;
+}
+
+/* Got a join
+ */
+static int gotjoin(char *from, char *ignore, char *chname)
+{
+  char buf[UHOSTLEN], *nick, *uhost, *p;
+  char *ch_dname = NULL;
+  struct chanset_t *chan;
+  memberlist *m;
+  masklist *b;
+  struct userrec *u;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  fixcolon(chname);
+  chan = findchan(chname);
+  if (!chan && chname[0] == '!') {
+    /* As this is a !channel, we need to search for it by display (short)
+     * name now. This will happen when we initially join the channel, as we
+     * dont know the unique channel name that the server has made up. <cybah>
+     */
+    int	l_chname = strlen(chname);
+
+    if (l_chname > (CHANNEL_ID_LEN + 1)) {
+      ch_dname = malloc(l_chname + 1);
+      if (ch_dname) {
+	snprintf(ch_dname, l_chname + 2, "!%s",
+		     chname + (CHANNEL_ID_LEN + 1));
+	chan = findchan_by_dname(ch_dname);
+	if (!chan) {
+	  /* Hmm.. okay. Maybe the admin's a genius and doesn't know the
+	   * difference between id and descriptive channel names. Search
+	   * the channel name in the dname list using the id-name.
+	   */
+	   chan = findchan_by_dname(chname);
+	   if (chan) {
+	     /* Duh, I was right. Mark this channel as inactive and log
+	      * the incident.
+	      */
+	     chan->status |= CHAN_INACTIVE;
+	     putlog(LOG_MISC, "*", "Deactivated channel %s, because it uses "
+		    "an ID channel-name. Use the descriptive name instead.",
+		    chname);
+	     dprintf(DP_SERVER, "PART %s\n", chname);
+	     goto exit;
+	   }
+	}
+      }
+    }
+  } else if (!chan) {
+    /* As this is not a !chan, we need to search for it by display name now.
+     * Unlike !chan's, we dont need to remove the unique part.
+     */
+    chan = findchan_by_dname(chname);
+  }
+
+  if (!chan || channel_inactive(chan)) {
+    putlog(LOG_MISC, "*", "joined %s but didn't want to!", chname);
+    dprintf(DP_MODE, "PART %s\n", chname);
+  } else if (!channel_pending(chan)) {
+    chan->status &= ~CHAN_STOP_CYCLE;
+    strncpyz(buf, from, sizeof buf);
+    nick = strtok(buf, "!");
+    uhost = strtok(NULL, "!");
+    detect_chan_flood(nick, uhost, from, chan, FLOOD_JOIN, NULL);
+    /* Grab last time joined before we update it */
+    u = get_user_by_host(from);
+    get_user_flagrec(u, &fr, chname);
+    if (!channel_active(chan) && !match_my_nick(nick)) {
+      /* uh, what?!  i'm on the channel?! */
+      putlog(LOG_MISC, chan->dname,
+	     "confused bot: guess I'm on %s and didn't realize it",
+	     chan->dname);
+      chan->status |= CHAN_ACTIVE;
+      chan->status &= ~CHAN_PEND;
+      reset_chan_info(chan);
+    } else {
+      m = ismember(chan, nick);
+      if (m && m->split && !strcasecmp(m->userhost, uhost)) {
+	check_tcl_rejn(nick, uhost, u, chan->dname);
+	/* The tcl binding might have deleted the current user. Recheck. */
+	u = get_user_by_host(from);
+	m->split = 0;
+	m->last = now;
+	m->delay = 0L;
+        m->flags = (chan_hasop(m) ? WASOP : 0);
+	m->user = u;
+	set_handle_laston(chan->dname, u, now);
+	m->flags |= STOPWHO;
+	putlog(LOG_JOIN, chan->dname, "%s (%s) returned to %s.", nick, uhost,
+	       chan->dname);
+      } else {
+	if (m)
+	  killmember(chan, nick);
+	m = newmember(chan);
+	m->joined = now;
+	m->split = 0L;
+	m->flags = 0;
+	m->last = now;
+	m->delay = 0L;
+        strcpy(m->nick, nick);
+	strcpy(m->userhost, uhost);
+	m->user = u;
+	m->flags |= STOPWHO;
+
+	check_tcl_join(nick, uhost, u, chan->dname);
+
+	/* The tcl binding might have deleted the current user and the
+	 * current channel, so we'll now have to re-check whether they
+	 * both still exist.
+	 */
+	chan = findchan(chname);
+	if (!chan) {
+	  if (ch_dname)
+	    chan = findchan_by_dname(ch_dname);
+	  else
+	    chan = findchan_by_dname(chname);
+	}
+	if (!chan)
+	  /* The channel doesn't exist anymore, so get out of here. */
+	  goto exit;
+
+	/* The record saved in the channel record always gets updated,
+	   so we can use that. */
+	u = m->user;
+
+	if (match_my_nick(nick)) {
+	  /* It was me joining! Need to update the channel record with the
+	   * unique name for the channel (as the server see's it). <cybah>
+	   */
+	  strncpy(chan->name, chname, 81);
+	  chan->name[80] = 0;
+	  chan->status &= ~CHAN_JUPED;
+
+          /* ... and log us joining. Using chan->dname for the channel is
+	   * important in this case. As the config file will never contain
+	   * logs with the unique name.
+           */
+	  if (chname[0] == '!')
+	    putlog(LOG_JOIN | LOG_MISC, chan->dname, "%s joined %s (%s)",
+	           nick, chan->dname, chname);
+	  else
+	    putlog(LOG_JOIN | LOG_MISC, chan->dname, "%s joined %s.", nick,
+	           chname);
+	  reset_chan_info(chan);
+	} else {
+	  struct chanuserrec *cr;
+
+	  putlog(LOG_JOIN, chan->dname,
+		 "%s (%s) joined %s.", nick, uhost, chan->dname);
+	  /* Don't re-display greeting if they've been on the channel
+	   * recently.
+	   */
+	  if (u) {
+	    struct laston_info *li = 0;
+
+	    cr = get_chanrec(m->user, chan->dname);
+	    if (!cr && no_chanrec_info)
+	      li = get_user(&USERENTRY_LASTON, m->user);
+	    if (channel_greet(chan) && use_info &&
+		((cr && now - cr->laston > wait_info) ||
+		 (no_chanrec_info &&
+		  (!li || now - li->laston > wait_info)))) {
+	      char s1[512], *s;
+
+	      if (!(u->flags & USER_BOT)) {
+		s = get_user(&USERENTRY_INFO, u);
+		get_handle_chaninfo(u->handle, chan->dname, s1);
+		/* Locked info line overides non-locked channel specific
+		 * info line.
+		 */
+		if (!s || (s1[0] && (s[0] != '@' || s1[0] == '@')))
+		  s = s1;
+		if (s[0] == '@')
+		  s++;
+		if (s && s[0])
+		  dprintf(DP_HELP, "PRIVMSG %s :[%s] %s\n",
+			  chan->name, nick, s);
+	      }
+	    }
+	  }
+	  set_handle_laston(chan->dname, u, now);
+	}
+      }
+      /* ok, the op-on-join,etc, tests...first only both if Im opped */
+      if (me_op(chan)) {
+	/* Check for and reset exempts and invites.
+	 *
+	 * This will require further checking to account for when to use the
+	 * various modes.
+	 */
+	if (u_match_mask(global_invites,from) ||
+	    u_match_mask(chan->invites, from))
+	  refresh_invite(chan, from);
+	if (!(use_exempts &&
+	      (u_match_mask(global_exempts,from) ||
+	       u_match_mask(chan->exempts, from)))) {
+          if (channel_enforcebans(chan) && !chan_op(fr) && !glob_op(fr) &&
+              !glob_friend(fr) && !chan_friend(fr) && !chan_sentkick(m) &&
+              !(use_exempts && isexempted(chan, from))) {
+            for (b = chan->channel.ban; b->mask[0]; b = b->next) {
+              if (wild_match(b->mask, from)) {
+                dprintf(DP_SERVER, "KICK %s %s :%s\n", chname, m->nick,
+                        _("You are banned"));
+                m->flags |= SENTKICK;
+                goto exit;
+              }
+            }
+          }
+	  /* If it matches a ban, dispose of them. */
+	  if (u_match_mask(global_bans, from) ||
+	      u_match_mask(chan->bans, from)) {
+	    refresh_ban_kick(chan, from, nick);
+	  /* Likewise for kick'ees */
+	  } else if (glob_kick(fr) || chan_kick(fr)) {
+	    check_exemptlist(chan, from);
+	    quickban(chan, from);
+	    p = get_user(&USERENTRY_COMMENT, m->user);
+	    dprintf(DP_MODE, "KICK %s %s :%s\n", chname, nick,
+		    (p && (p[0] != '@')) ? p : _("...and dont come back."));
+	    m->flags |= SENTKICK;
+	  }
+	}
+	/* Are they a chan op, or global op without chan deop? */
+	if ((chan_op(fr) || (glob_op(fr) && !chan_deop(fr))) &&
+	   /* ... and is it op-on-join or is the use marked auto-op? */
+	    (channel_autoop(chan) || glob_autoop(fr) || chan_autoop(fr))) {
+	  /* Yes! do the honors. */
+	  if (!chan->aop_min)
+	    add_mode(chan, '+', 'o', nick);
+	  else {
+            set_delay(chan, nick);
+            m->flags |= SENTOP;
+	  }
+	} else if ((channel_autovoice(chan) &&
+		    (chan_voice(fr) || (glob_voice(fr) && !chan_quiet(fr)))) ||
+                   ((glob_gvoice(fr) || chan_gvoice(fr)) && !chan_quiet(fr))) {
+           if (!chan->aop_min)
+             add_mode(chan, '+', 'v', nick);
+           else {
+             set_delay(chan, nick);
+             m->flags |= SENTVOICE;
+           }
+         }
+      }
+    }
+  }
+
+exit:
+  if (ch_dname)
+    free(ch_dname);
+  return 0;
+}
+
+/* Got a part
+ */
+static int gotpart(char *from, char *ignore, char *msg)
+{
+  char buf[UHOSTLEN], *nick, *uhost, *chname;
+  struct chanset_t *chan;
+  struct userrec *u;
+
+  chname = newsplit(&msg);
+  fixcolon(msg);
+  chan = findchan(chname);
+  if (chan && channel_inactive(chan)) {
+    clear_channel(chan, 1);
+    chan->status &= ~(CHAN_ACTIVE | CHAN_PEND);
+    return 0;
+  }
+  if (chan && !channel_pending(chan)) {
+    if (!channel_active(chan)) {
+      /* whoa! */
+      putlog(LOG_MISC, chan->dname,
+	  "confused bot: guess I'm on %s and didn't realize it", chan->dname);
+      chan->status |= CHAN_ACTIVE;
+      chan->status &= ~CHAN_PEND;
+      reset_chan_info(chan);
+    }
+    u = get_user_by_host(from);
+    set_handle_laston(chan->dname, u, now);
+    strncpyz(buf, from, sizeof buf);
+    nick = strtok(buf, "!");
+    uhost = strtok(NULL, "!");
+    check_tcl_part(nick, uhost, u, chan->dname, msg); /* This must be directly above the killmember, in case
+    							we're doing anything to the record that would affect
+							the above */
+    killmember(chan, nick);
+    if (msg[0])
+      putlog(LOG_JOIN, chan->dname, "%s (%s) left %s (%s).", nick, uhost, chan->dname, msg);
+    else
+      putlog(LOG_JOIN, chan->dname, "%s (%s) left %s.", nick, uhost, chan->dname);
+    /* If it was me, all hell breaks loose... */
+    if (match_my_nick(nick)) {
+      clear_channel(chan, 1);
+      chan->status &= ~(CHAN_ACTIVE | CHAN_PEND);
+      if (!channel_inactive(chan))
+	dprintf(DP_MODE, "JOIN %s %s\n",
+	        (chan->name[0]) ? chan->name : chan->dname,
+	        chan->channel.key[0] ? chan->channel.key : chan->key_prot);
+    } else
+      check_lonely_channel(chan);
+  }
+  return 0;
+}
+
+/* Got a kick
+ */
+static int gotkick(char *from, char *ignore, char *origmsg)
+{
+  char buf[UHOSTLEN], *nick, *uhost, *kicked, *chname, s1[UHOSTLEN];
+  char buf2[511], *msg;
+  memberlist *m;
+  struct chanset_t *chan;
+  struct userrec *u;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  strncpyz(buf2, origmsg, sizeof buf2);
+  msg = buf2;
+  chname = newsplit(&msg);
+  chan = findchan(chname);
+  if (chan && channel_active(chan)) {
+    kicked = newsplit(&msg);
+    fixcolon(msg);
+    u = get_user_by_host(from);
+    strncpyz(buf, from, sizeof buf);
+    nick = strtok(buf, "!");
+    uhost = strtok(NULL, "!");
+    detect_chan_flood(nick, uhost, from, chan, FLOOD_KICK, kicked);
+    m = ismember(chan, nick);
+    if (m)
+      m->last = now;
+    /* This _needs_ to use chan->dname <cybah> */
+    get_user_flagrec(u, &fr, chan->dname);
+    set_handle_laston(chan->dname, u, now);
+    check_tcl_kick(nick, uhost, u, chan->dname, kicked, msg);
+    m = ismember(chan, kicked);
+    if (m) {
+      struct userrec *u2;
+
+      simple_sprintf(s1, "%s!%s", m->nick, m->userhost);
+      u2 = get_user_by_host(s1);
+      set_handle_laston(chan->dname, u2, now);
+      maybe_revenge(chan, from, s1, REVENGE_KICK);
+    }
+    putlog(LOG_MODES, chan->dname, "%s kicked from %s by %s: %s", s1,
+	   chan->dname, from, msg);
+    /* Kicked ME?!? the sods! */
+    if (match_my_nick(kicked)) {
+      chan->status &= ~(CHAN_ACTIVE | CHAN_PEND);
+      dprintf(DP_MODE, "JOIN %s %s\n",
+              (chan->name[0]) ? chan->name : chan->dname,
+              chan->channel.key[0] ? chan->channel.key : chan->key_prot);
+      clear_channel(chan, 1);
+    } else {
+      killmember(chan, kicked);
+      check_lonely_channel(chan);
+    }
+  }
+  return 0;
+}
+
+/* Got a nick change
+ */
+static int gotnick(char *from, char *ignore, char *msg)
+{
+  char buf[UHOSTLEN], *nick, *uhost, s1[UHOSTLEN];
+  memberlist *m, *mm;
+  struct chanset_t *chan;
+  struct userrec *u;
+
+  fixcolon(msg);
+  strncpyz(buf, from, sizeof buf);
+  nick = strtok(buf, "!");
+  uhost = strtok(NULL, "!");
+  for (chan = chanset; chan; chan = chan->next) { 
+    m = ismember(chan, nick);
+    if (m) {
+      putlog(LOG_JOIN, chan->dname, "Nick change: %s -> %s", nick, msg);
+      m->last = now;
+      if (irccmp(nick, msg)) {
+	/* Not just a capitalization change */
+	mm = ismember(chan, msg);
+	if (mm) {
+	  /* Someone on channel with old nick?! */
+	  if (mm->split)
+	    putlog(LOG_JOIN, chan->dname,
+		   "Possible future nick collision: %s", mm->nick);
+	  else
+	    putlog(LOG_MISC, chan->dname,
+		   "* Bug: nick change to existing nick");
+	  killmember(chan, mm->nick);
+	}
+      }
+      /*
+       * Banned?
+       */
+      /* Compose a nick!user at host for the new nick */
+      sprintf(s1, "%s!%s", msg, uhost);
+      /* Enforcing bans & haven't already kicked them? */
+      if (channel_enforcebans(chan) && chan_sentkick(m) &&
+	  (u_match_mask(global_bans, s1) ||
+	   u_match_mask(chan->bans, s1)) &&
+	  !(use_exempts &&
+	   (u_match_mask(global_exempts,s1) ||
+	    u_match_mask(chan->exempts, s1))))
+	refresh_ban_kick(chan, s1, msg);
+      strcpy(m->nick, msg);
+      detect_chan_flood(msg, uhost, from, chan, FLOOD_NICK, NULL);
+      /* Any pending kick to the old nick is lost. Ernst 18/3/1998 */
+      if (chan_sentkick(m))
+	m->flags &= ~SENTKICK;
+      u = get_user_by_host(from); /* make sure this is in the loop, someone could have changed the record
+                                     in an earlier iteration of the loop */
+      check_tcl_nick(nick, uhost, u, chan->dname, msg);
+    }
+  }
+  clear_chanlist_member(msg);	/* Cache for nick 'msg' is meaningless now. */
+  return 0;
+}
+
+/* Signoff, similar to part.
+ */
+static int gotquit(char *from, char *ignore, char *msg)
+{
+  char buf[UHOSTLEN], *nick, *uhost, *p, *alt;
+  int split = 0;
+  memberlist *m;
+  struct chanset_t *chan;
+  struct userrec *u;
+
+  fixcolon(msg);
+  strncpyz(buf, from, sizeof buf);
+  nick = strtok(from, "!");
+  uhost = strtok(NULL, "!");
+  /* Fred1: Instead of expensive wild_match on signoff, quicker method.
+   *        Determine if signoff string matches "%.% %.%", and only one
+   *        space.
+   */
+  p = strchr(msg, ' ');
+  if (p && (p == strrchr(msg, ' '))) {
+    char *z1, *z2;
+
+    *p = 0;
+    z1 = strchr(p + 1, '.');
+    z2 = strchr(msg, '.');
+    if (z1 && z2 && (*(z1 + 1) != 0) && (z1 - 1 != p) &&
+	(z2 + 1 != p) && (z2 != msg)) {
+      /* Server split, or else it looked like it anyway (no harm in
+       * assuming)
+       */
+      split = 1;
+    } else
+      *p = ' ';
+  }
+  for (chan = chanset; chan; chan = chan->next) {
+    m = ismember(chan, nick);
+    if (m) {
+      u = get_user_by_host(from);
+      if (u) {
+        set_handle_laston(chan->dname, u, now); /* If you remove this, the bot will crash when the user record in question
+						   is removed/modified during the tcl binds below, and the users was on more
+						   than one monitored channel */
+      }
+      if (split) {
+	m->split = now;
+	check_tcl_splt(nick, uhost, u, chan->dname);
+	putlog(LOG_JOIN, chan->dname, "%s (%s) got netsplit.", nick,
+	       uhost);
+      } else {
+	check_tcl_sign(nick, uhost, u, chan->dname, msg);
+	putlog(LOG_JOIN, chan->dname, "%s (%s) left irc: %s", nick,
+	       uhost, msg);
+	killmember(chan, nick);
+	check_lonely_channel(chan);
+      }
+    }
+  }
+  /* Our nick quit? if so, grab it. Heck, our altnick quit maybe, maybe
+   * we want it.
+   */
+  if (keepnick) {
+    alt = get_altbotnick();
+    if (!irccmp(nick, origbotname)) {
+      putlog(LOG_MISC, "*", _("Switching back to nick %s"), origbotname);
+      dprintf(DP_SERVER, "NICK %s\n", origbotname);
+    } else if (alt[0]) {
+      if (!irccmp(nick, alt) && strcmp(botname, origbotname)) {
+	putlog(LOG_MISC, "*", _("Switching back to altnick %s"), alt);
+	dprintf(DP_SERVER, "NICK %s\n", alt);
+      }
+    }
+  }
+  return 0;
+}
+
+/* Got a private message.
+ */
+static int gotmsg(char *from, char *ignore, char *msg)
+{
+  char buf[UHOSTLEN], *nick, *uhost, *to, *realto, buf2[512], *p, *p1;
+  char *code, *ctcp;
+  int ctcp_count = 0;
+  struct chanset_t *chan;
+  int ignoring;
+  struct userrec *u;
+  memberlist *m;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  if (!strchr("&#!+@$", msg[0]))
+    return 0;
+  ignoring = match_ignore(from);
+  to = newsplit(&msg);
+  realto = (to[0] == '@') ? to + 1 : to;
+  chan = findchan(realto);
+  if (!chan)
+    return 0;			/* Private msg to an unknown channel?? */
+  fixcolon(msg);
+  strncpyz(buf, from, sizeof buf);
+  nick = strtok(buf, "!");
+  uhost = strtok(NULL, "!");
+  /* Only check if flood-ctcp is active */
+  if (flud_ctcp_thr && detect_avalanche(msg)) {
+    u = get_user_by_host(from);
+    get_user_flagrec(u, &fr, chan->dname);
+    m = ismember(chan, nick);
+    /* Discard -- kick user if it was to the channel */
+    if (me_op(chan) && m && !chan_sentkick(m) &&
+	!chan_friend(fr) && !glob_friend(fr) &&
+	!(channel_dontkickops(chan) &&
+	  (chan_op(fr) || (glob_op(fr) && !chan_deop(fr)))) &&	/* arthur2 */
+	!(use_exempts && ban_fun &&
+	  /* don't kickban if permanent exempted -- Eule */
+	  (u_match_mask(global_exempts, from) ||
+	   u_match_mask(chan->exempts, from)))) {
+      if (ban_fun) {
+	check_exemptlist(chan, from);
+	u_addban(chan, quickban(chan, uhost), origbotname,
+		_("that was fun, lets do it again!"), now + (60 * ban_time), 0);
+      }
+      if (kick_fun) {
+	/* This can induce kickflood - arthur2 */
+	dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, nick,
+		_("that was fun, lets do it again!"));
+	m->flags |= SENTKICK;
+      }
+    }
+    if (!ignoring) {
+      putlog(LOG_MODES, "*", "Avalanche from %s!%s in %s - ignoring",
+	     nick, uhost, chan->dname);
+      /* FIXME: get rid of this mess */
+      p = strchr(uhost, '@');
+      if (p)
+	p++;
+      else
+	p = uhost;
+      simple_sprintf(buf2, "*!*@%s", p);
+      addignore(buf2, origbotname, "ctcp avalanche", now + (60 * ignore_time));
+    }
+    return 0;
+  }
+  /* Check for CTCP: */
+  ctcp_reply[0] = 0;
+  p = strchr(msg, 1);
+  while (p && *p) {
+    p++;
+    p1 = p;
+    while ((*p != 1) && *p)
+      p++;
+    if (*p == 1) {
+      *p = 0;
+      ctcp = buf2;
+      strcpy(ctcp, p1);
+      strcpy(p1 - 1, p + 1);
+      detect_chan_flood(nick, uhost, from, chan,
+			strncmp(ctcp, "ACTION ", 7) ?
+			FLOOD_CTCP : FLOOD_PRIVMSG, NULL);
+      /* Respond to the first answer_ctcp */
+      p = strchr(msg, 1);
+      if (ctcp_count < answer_ctcp) {
+	ctcp_count++;
+	if (ctcp[0] != ' ') {
+	  code = newsplit(&ctcp);
+	  u = get_user_by_host(from);
+	  if (!ignoring || trigger_on_ignore) {
+	    if (!check_tcl_ctcp(nick, uhost, u, to, code, ctcp))
+	      update_idle(chan->dname, nick);
+	    if (!ignoring) {
+	      /* Log DCC, it's to a channel damnit! */
+	      if (!strcmp(code, "ACTION")) {
+		putlog(LOG_PUBLIC, chan->dname, "Action: %s %s", nick, ctcp);
+	      } else {
+		putlog(LOG_PUBLIC, chan->dname,
+		       "CTCP %s: %s from %s (%s) to %s", code, ctcp, nick,
+		       from, to);
+	      }
+	    }
+	  }
+	}
+      }
+    }
+  }
+  /* Send out possible ctcp responses */
+  if (ctcp_reply[0]) {
+    if (ctcp_mode != 2) {
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+    } else {
+      if (now - last_ctcp > flud_ctcp_time) {
+	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+	count_ctcp = 1;
+      } else if (count_ctcp < flud_ctcp_thr) {
+	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+	count_ctcp++;
+      }
+      last_ctcp = now;
+    }
+  }
+  if (msg[0]) {
+    /* Check even if we're ignoring the host. (modified by Eule 17.7.99) */
+    detect_chan_flood(nick, uhost, from, chan, FLOOD_PRIVMSG, NULL);
+    if (!ignoring || trigger_on_ignore) {
+      if (check_tcl_pub(nick, uhost, chan->dname, msg))
+	return 0;
+      check_tcl_pubm(nick, uhost, chan->dname, msg);
+    }
+    if (!ignoring) {
+      if (to[0] == '@')
+	putlog(LOG_PUBLIC, chan->dname, "@<%s> %s", nick, msg);
+      else
+	putlog(LOG_PUBLIC, chan->dname, "<%s> %s", nick, msg);
+    }
+    update_idle(chan->dname, nick);
+  }
+  return 0;
+}
+
+/* Got a private notice.
+ */
+static int gotnotice(char *from, char *ignore, char *msg)
+{
+  char buf[UHOSTLEN], *nick, *uhost, *to, *realto, buf2[512], *p, *p1;
+  char *ctcp, *code;
+  struct userrec *u;
+  memberlist *m;
+  struct chanset_t *chan;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  int ignoring;
+
+  if (!strchr(CHANMETA "@", *msg))
+    return 0;
+  ignoring = match_ignore(from);
+  to = newsplit(&msg);
+  realto = (*to == '@') ? to + 1 : to;
+  chan = findchan(realto);
+  if (!chan)
+    return 0;			/* Notice to an unknown channel?? */
+  fixcolon(msg);
+  strncpyz(buf, from, sizeof buf);
+  nick = strtok(buf, "!");
+  uhost = strtok(NULL, "!");
+  u = get_user_by_host(from);
+  if (flud_ctcp_thr && detect_avalanche(msg)) {
+    get_user_flagrec(u, &fr, chan->dname);
+    m = ismember(chan, nick);
+    /* Discard -- kick user if it was to the channel */
+    if (me_op(chan) && m && !chan_sentkick(m) &&
+	!chan_friend(fr) && !glob_friend(fr) &&
+	!(channel_dontkickops(chan) &&
+	  (chan_op(fr) || (glob_op(fr) && !chan_deop(fr)))) &&	/* arthur2 */
+	!(use_exempts && ban_fun &&
+	  /* don't kickban if permanent exempted -- Eule */
+	  (u_match_mask(global_exempts,from) ||
+	   u_match_mask(chan->exempts, from)))) {
+      if (ban_fun) {
+	check_exemptlist(chan, from);
+	u_addban(chan, quickban(chan, uhost), origbotname,
+		_("that was fun, lets do it again!"), now + (60 * ban_time), 0);
+      }
+      if (kick_fun) {
+	/* This can induce kickflood - arthur2 */
+	dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, nick,
+		_("that was fun, lets do it again!"));
+	m->flags |= SENTKICK;
+      }
+    }
+    if (!ignoring)
+      putlog(LOG_MODES, "*", "Avalanche from %s", from);
+    return 0;
+  }
+  /* Check for CTCP: */
+  p = strchr(msg, 1);
+  while (p && *p) {
+    p++;
+    p1 = p;
+    while ((*p != 1) && *p)
+      p++;
+    if (*p == 1) {
+      *p = 0;
+      ctcp = buf2;
+      strcpy(ctcp, p1);
+      strcpy(p1 - 1, p + 1);
+      p = strchr(msg, 1);
+      detect_chan_flood(nick, uhost, from, chan,
+			strncmp(ctcp, "ACTION ", 7) ?
+			FLOOD_CTCP : FLOOD_PRIVMSG, NULL);
+      if (ctcp[0] != ' ') {
+	code = newsplit(&ctcp);
+	if (!ignoring || trigger_on_ignore) {
+	  check_tcl_ctcr(nick, uhost, u, chan->dname, code, msg);
+	  if (!ignoring) {
+	    putlog(LOG_PUBLIC, chan->dname, "CTCP reply %s: %s from %s (%s) to %s",
+		   code, msg, nick, from, chan->dname);
+	    update_idle(chan->dname, nick);
+	  }
+	}
+      }
+    }
+  }
+  if (msg[0]) {
+    /* Check even if we're ignoring the host. (modified by Eule 17.7.99) */
+    detect_chan_flood(nick, uhost, from, chan, FLOOD_NOTICE, NULL);
+    if (!ignoring || trigger_on_ignore)
+      check_tcl_notc(nick, uhost, u, to, msg);
+    if (!ignoring)
+      putlog(LOG_PUBLIC, chan->dname, "-%s:%s- %s", nick, to, msg);
+    update_idle(chan->dname, nick);
+  }
+  return 0;
+}
+
+static cmd_t irc_raw[] =
+{
+  {"324",	"",	(Function) got324,	"irc:324"},
+  {"352",	"",	(Function) got352,	"irc:352"},
+  {"354",	"",	(Function) got354,	"irc:354"},
+  {"315",	"",	(Function) got315,	"irc:315"},
+  {"367",	"",	(Function) got367,	"irc:367"},
+  {"368",	"",	(Function) got368,	"irc:368"},
+  {"403",	"",	(Function) got403,	"irc:403"},
+  {"405",	"",	(Function) got405,	"irc:405"},
+  {"471",	"",	(Function) got471,	"irc:471"},
+  {"473",	"",	(Function) got473,	"irc:473"},
+  {"474",	"",	(Function) got474,	"irc:474"},
+  {"475",	"",	(Function) got475,	"irc:475"},
+  {"INVITE",	"",	(Function) gotinvite,	"irc:invite"},
+  {"TOPIC",	"",	(Function) gottopic,	"irc:topic"},
+  {"331",	"",	(Function) got331,	"irc:331"},
+  {"332",	"",	(Function) got332,	"irc:332"},
+  {"JOIN",	"",	(Function) gotjoin,	"irc:join"},
+  {"PART",	"",	(Function) gotpart,	"irc:part"},
+  {"KICK",	"",	(Function) gotkick,	"irc:kick"},
+  {"NICK",	"",	(Function) gotnick,	"irc:nick"},
+  {"QUIT",	"",	(Function) gotquit,	"irc:quit"},
+  {"PRIVMSG",	"",	(Function) gotmsg,	"irc:msg"},
+  {"NOTICE",	"",	(Function) gotnotice,	"irc:notice"},
+  {"MODE",	"",	(Function) gotmode,	"irc:mode"},
+  {"346",	"",	(Function) got346,	"irc:346"},
+  {"347",	"",	(Function) got347,	"irc:347"},
+  {"348",	"",	(Function) got348,	"irc:348"},
+  {"349",	"",	(Function) got349,	"irc:349"},
+  {NULL,	NULL,	NULL,			NULL}
+};
Index: eggdrop1.7/modules/irc/cmdsirc.c
diff -u /dev/null eggdrop1.7/modules/irc/cmdsirc.c:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/irc/cmdsirc.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,994 @@
+/*
+ * chancmds.c -- part of irc.mod
+ *   handles commands direclty relating to channel interaction
+ *
+ * $Id: cmdsirc.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/* Do we have any flags that will allow us ops on a channel?
+ */
+static struct chanset_t *has_op(int idx, char *chname)
+{
+  struct chanset_t *chan;
+
+  if (chname && chname[0]) {
+    chan = findchan_by_dname(chname);
+    if (!chan) {
+      dprintf(idx, "No such channel.\n");
+      return 0;
+    }
+  } else {
+    chname = dcc[idx].u.chat->con_chan;
+    chan = findchan_by_dname(chname);
+    if (!chan) {
+      dprintf(idx, "Invalid console channel.\n");
+      return 0;
+    }
+  }
+  get_user_flagrec(dcc[idx].user, &user, chname);
+  if (chan_op(user) || (glob_op(user) && !chan_deop(user)))
+    return chan;
+  dprintf(idx, "You are not a channel op on %s.\n", chan->dname);
+  return 0;
+}
+
+/* Finds a nick of the handle. Returns m->nick if
+ * the nick was found, otherwise NULL (Sup 1Nov2000)
+ */
+static char *getnick(char *handle, struct chanset_t *chan)
+{
+  char			 s[UHOSTLEN];
+  struct userrec	*u;
+  register memberlist	*m;
+
+  for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+    snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost);
+    if ((u = get_user_by_host(s)) && !strcasecmp(u->handle, handle))
+      return m->nick;
+  }
+  return NULL;
+}
+
+static void cmd_act(struct userrec *u, int idx, char *par)
+{
+  char *chname;
+  struct chanset_t *chan;
+  memberlist *m;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: act [channel] <action>\n");
+    return;
+  }
+  if (strchr(CHANMETA, par[0]) != NULL)
+    chname = newsplit(&par);
+  else
+    chname = 0;
+  if (!(chan = has_op(idx, chname)))
+    return;
+  m = ismember(chan, botname);
+  if (!m) {
+    dprintf(idx, "Cannot say to %s: I'm not on that channel.\n", chan->dname);
+    return;
+  }
+  if ((chan->channel.mode & CHANMODER) && !(m->flags & (CHANOP | CHANVOICE))) {
+    dprintf(idx, "Cannot say to %s, it is moderated.\n", chan->dname);
+    return;
+  }
+  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);
+  dprintf(idx, "Action to %s: %s\n", chan->dname, par);
+}
+
+static void cmd_msg(struct userrec *u, int idx, char *par)
+{
+  char *nick;
+
+  nick = newsplit(&par);
+  if (!par[0]) {
+    dprintf(idx, "Usage: msg <nick> <message>\n");
+  } else {
+    putlog(LOG_CMDS, "*", "#%s# msg %s %s", dcc[idx].nick, nick, par);
+    dprintf(DP_HELP, "PRIVMSG %s :%s\n", nick, par);
+    dprintf(idx, "Msg to %s: %s\n", nick, par);
+  }
+}
+
+static void cmd_say(struct userrec *u, int idx, char *par)
+{
+  char *chname;
+  struct chanset_t *chan;
+  memberlist *m;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: say [channel] <message>\n");
+    return;
+  }
+  if (strchr(CHANMETA, par[0]) != NULL)
+    chname = newsplit(&par);
+  else
+    chname = 0;
+  if (!(chan = has_op(idx, chname)))
+    return;
+  m = ismember(chan, botname);
+  if (!m) {
+    dprintf(idx, "Cannot say to %s: I'm not on that channel.\n", chan->dname);
+    return;
+  }
+  if ((chan->channel.mode & CHANMODER) && !(m->flags & (CHANOP | CHANVOICE))) {
+    dprintf(idx, "Cannot say to %s, it is moderated.\n", chan->dname);
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# (%s) say %s", dcc[idx].nick, chan->dname, par);
+  dprintf(DP_HELP, "PRIVMSG %s :%s\n", chan->name, par);
+  dprintf(idx, "Said to %s: %s\n", chan->dname, par);
+}
+
+static void cmd_kickban(struct userrec *u, int idx, char *par)
+{
+  struct chanset_t *chan;
+  char *chname, *nick, *s1;
+  memberlist *m;
+  char s[UHOSTLEN];
+  char bantype = 0;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: kickban [channel] [-|@]<nick> [reason]\n");
+    return;
+  }
+  if (strchr(CHANMETA, par[0]) != NULL)
+    chname = newsplit(&par);
+  else
+    chname = 0;
+  if (!(chan = has_op(idx, chname)))
+    return;
+  if (!channel_active(chan)) {
+    dprintf(idx, "I'm not on %s right now!\n", chan->dname);
+    return;
+  }
+  if (!me_op(chan)) {
+    dprintf(idx, "I can't help you now because I'm not a channel op on %s.\n",
+	    chan->dname);
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# (%s) kickban %s", dcc[idx].nick,
+	 chan->dname, par);
+  nick = newsplit(&par);
+  if ((nick[0] == '@') || (nick[0] == '-')) {
+    bantype = nick[0];
+    nick++;
+  }
+  if (match_my_nick(nick)) {
+    dprintf(idx, "I'm not going to kickban myself.\n");
+    return;
+  } else {
+    m = ismember(chan, nick);
+    if (!m)
+      dprintf(idx, "%s is not on %s\n", nick, chan->dname);
+    else {
+      snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost);
+      u = get_user_by_host(s);
+      get_user_flagrec(u, &victim, chan->dname);
+      if ((chan_op(victim) || (glob_op(victim) && !chan_deop(victim))) &&
+	  !(chan_master(user) || glob_master(user))) {
+	dprintf(idx, "%s is a legal op.\n", nick);
+	return;
+      }
+      if ((chan_master(victim) || glob_master(victim)) &&
+	  !(glob_owner(user) || chan_owner(user))) {
+	dprintf(idx, "%s is a %s master.\n", nick, chan->dname);
+	return;
+      }
+      if (glob_bot(victim) && !(glob_owner(victim) || chan_owner(victim))) {
+	dprintf(idx, "%s is another channel bot!\n", nick);
+	return;
+      }
+      if (use_exempts &&
+	  (u_match_mask(global_exempts,s) || u_match_mask(chan->exempts, s))) {
+	dprintf(idx, "%s is permanent exempted!\n", nick);
+	return;
+      }
+      if (m->flags & CHANOP)
+	add_mode(chan, '-', 'o', m->nick);
+      check_exemptlist(chan, s);
+      switch (bantype) {
+	case '@':
+	  s1 = strchr(s, '@');
+	  s1 -= 3;
+	  s1[0] = '*';
+	  s1[1] = '!';
+	  s1[2] = '*';
+	  break;
+	case '-':
+	  s1 = strchr(s, '-');
+	  s1[1] = '*';
+	  s1--;
+	  s1[0] = '*';
+	  break;
+	default:
+	  s1 = quickban(chan, m->userhost);
+	  break;
+      }
+      if (bantype == '@' || bantype == '-')
+	do_mask(chan, chan->channel.ban, s1, 'b');
+      if (!par[0])
+	par = "requested";
+      dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, m->nick, par);
+      m->flags |= SENTKICK;
+      u_addban(chan, s1, dcc[idx].nick, par, now + (60 * ban_time), 0);
+      dprintf(idx, "Okay, done.\n");
+    }
+  }
+}
+
+static void cmd_voice(struct userrec *u, int idx, char *par)
+{
+  struct chanset_t *chan;
+  char *nick;
+  memberlist *m;
+  char s[UHOSTLEN];
+
+  nick = newsplit(&par);
+  if (!(chan = has_op(idx, par)))
+    return;
+  if (!nick[0] && !(nick = getnick(u->handle, chan))) {
+    dprintf(idx, "Usage: voice <nick> [channel]\n");
+    return;
+  }
+  if (!channel_active(chan)) {
+    dprintf(idx, "I'm not on %s right now!\n", chan->dname);
+    return;
+  }
+  if (!me_op(chan)) {
+    dprintf(idx, "I can't help you now because I'm not a chan op on %s.\n",
+	    chan->dname);
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# (%s) voice %s %s", dcc[idx].nick,
+	 dcc[idx].u.chat->con_chan, nick, par);
+  m = ismember(chan, nick);
+  if (!m) {
+    dprintf(idx, "%s is not on %s.\n", nick, chan->dname);
+    return;
+  }
+  snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost);
+  add_mode(chan, '+', 'v', nick);
+  dprintf(idx, "Gave voice to %s on %s\n", nick, chan->dname);
+}
+
+static void cmd_devoice(struct userrec *u, int idx, char *par)
+{
+  struct chanset_t *chan;
+  char *nick;
+  memberlist *m;
+  char s[UHOSTLEN];
+
+  nick = newsplit(&par);
+  if (!(chan = has_op(idx, par)))
+    return;
+  if (!nick[0] && !(nick = getnick(u->handle, chan))) {
+    dprintf(idx, "Usage: devoice <nick> [channel]\n");
+    return;
+  }
+  if (!channel_active(chan)) {
+    dprintf(idx, "I'm not on %s right now!\n", chan->dname);
+    return;
+  }
+  if (!me_op(chan)) {
+    dprintf(idx, "I can't do that right now I'm not a chan op on %s.\n",
+	    chan->dname);
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# (%s) devoice %s %s", dcc[idx].nick,
+	 dcc[idx].u.chat->con_chan, nick, par);
+  m = ismember(chan, nick);
+  if (!m) {
+    dprintf(idx, "%s is not on %s.\n", nick, chan->dname);
+    return;
+  }
+  snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost);
+  add_mode(chan, '-', 'v', nick);
+  dprintf(idx, "Devoiced %s on %s\n", nick, chan->dname);
+}
+
+static void cmd_op(struct userrec *u, int idx, char *par)
+{
+  struct chanset_t *chan;
+  char *nick;
+  memberlist *m;
+  char s[UHOSTLEN];
+
+  nick = newsplit(&par);
+  if (!(chan = has_op(idx, par)))
+    return;
+  if (!nick[0] && !(nick = getnick(u->handle, chan))) {
+    dprintf(idx, "Usage: op <nick> [channel]\n");
+    return;
+  }
+  if (!channel_active(chan)) {
+    dprintf(idx, "I'm not on %s right now!\n", chan->dname);
+    return;
+  }
+  if (!me_op(chan)) {
+    dprintf(idx, "I can't help you now because I'm not a chan op on %s.\n",
+	    chan->dname);
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# (%s) op %s %s", dcc[idx].nick,
+	 dcc[idx].u.chat->con_chan, nick, par);
+  m = ismember(chan, nick);
+  if (!m) {
+    dprintf(idx, "%s is not on %s.\n", nick, chan->dname);
+    return;
+  }
+  snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost);
+  u = get_user_by_host(s);
+  get_user_flagrec(u, &victim, chan->dname);
+  if (chan_deop(victim) || (glob_deop(victim) && !glob_op(victim))) {
+    dprintf(idx, "%s is currently being auto-deopped.\n", m->nick);
+    return;
+  }
+  if (channel_bitch(chan)
+      && !(chan_op(victim) || (glob_op(victim) && !chan_deop(victim)))) {
+    dprintf(idx, "%s is not a registered op.\n", m->nick);
+    return;
+  }
+  add_mode(chan, '+', 'o', nick);
+  dprintf(idx, "Gave op to %s on %s\n", nick, chan->dname);
+}
+
+static void cmd_deop(struct userrec *u, int idx, char *par)
+{
+  struct chanset_t *chan;
+  char *nick;
+  memberlist *m;
+  char s[UHOSTLEN];
+
+  nick = newsplit(&par);
+  if (!(chan = has_op(idx, par)))
+    return;
+  if (!nick[0] && !(nick = getnick(u->handle, chan))) {
+    dprintf(idx, "Usage: deop <nick> [channel]\n");
+    return;
+  }
+  if (!channel_active(chan)) {
+    dprintf(idx, "I'm not on %s right now!\n", chan->dname);
+    return;
+  }
+  if (!me_op(chan)) {
+    dprintf(idx, "I can't help you now because I'm not a chan op on %s.\n",
+	    chan->dname);
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# (%s) deop %s %s", dcc[idx].nick,
+	 dcc[idx].u.chat->con_chan, nick, par);
+  m = ismember(chan, nick);
+  if (!m) {
+    dprintf(idx, "%s is not on %s.\n", nick, chan->dname);
+    return;
+  }
+  if (match_my_nick(nick)) {
+    dprintf(idx, "I'm not going to deop myself.\n");
+    return;
+  }
+  snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost);
+  u = get_user_by_host(s);
+  get_user_flagrec(u, &victim, chan->dname);
+  if ((chan_master(victim) || glob_master(victim)) &&
+      !(chan_owner(user) || glob_owner(user))) {
+    dprintf(idx, "%s is a master for %s\n", m->nick, chan->dname);
+    return;
+  }
+  if ((chan_op(victim) || (glob_op(victim) && !chan_deop(victim))) &&
+      !(chan_master(user) || glob_master(user))) {
+    dprintf(idx, "%s has the op flag for %s\n", m->nick, chan->dname);
+    return;
+  }
+  add_mode(chan, '-', 'o', nick);
+  dprintf(idx, "Took op from %s on %s\n", nick, chan->dname);
+}
+
+static void cmd_kick(struct userrec *u, int idx, char *par)
+{
+  struct chanset_t *chan;
+  char *chname, *nick;
+  memberlist *m;
+  char s[UHOSTLEN];
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: kick [channel] <nick> [reason]\n");
+    return;
+  }
+  if (strchr(CHANMETA, par[0]) != NULL)
+    chname = newsplit(&par);
+  else
+    chname = 0;
+  if (!(chan = has_op(idx, chname)))
+    return;
+  if (!channel_active(chan)) {
+    dprintf(idx, "I'm not on %s right now!\n", chan->dname);
+    return;
+  }
+  if (!me_op(chan)) {
+    dprintf(idx, "I can't help you now because I'm not a channel op on %s.\n",
+	    chan->dname);
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# (%s) kick %s", dcc[idx].nick,
+	 chan->dname, par);
+  nick = newsplit(&par);
+  if (!par[0])
+    par = "request";
+  if (match_my_nick(nick)) {
+    dprintf(idx, "I'm not going to kick myself.\n");
+    return;
+  }
+  m = ismember(chan, nick);
+  if (!m) {
+    dprintf(idx, "%s is not on %s\n", nick, chan->dname);
+    return;
+  }
+  snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost);
+  u = get_user_by_host(s);
+  get_user_flagrec(u, &victim, chan->dname);
+  if ((chan_op(victim) || (glob_op(victim) && !chan_deop(victim))) &&
+      !(chan_master(user) || glob_master(user))) {
+    dprintf(idx, "%s is a legal op.\n", nick);
+    return;
+  }
+  if ((chan_master(victim) || glob_master(victim)) &&
+      !(glob_owner(user) || chan_owner(user))) {
+    dprintf(idx, "%s is a %s master.\n", nick, chan->dname);
+    return;
+  }
+  if (glob_bot(victim) && !(glob_owner(victim) || chan_owner(victim))) {
+    dprintf(idx, "%s is another channel bot!\n", nick);
+    return;
+  }
+  dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, m->nick, par);
+  m->flags |= SENTKICK;
+  dprintf(idx, "Okay, done.\n");
+}
+
+static void cmd_invite(struct userrec *u, int idx, char *par)
+{
+  struct chanset_t *chan;
+  memberlist *m;
+  char *nick;
+
+  if (!par[0])
+    par = dcc[idx].nick;
+  nick = newsplit(&par);
+  if (!(chan = has_op(idx, par)))
+    return;
+  putlog(LOG_CMDS, "*", "#%s# (%s) invite %s", dcc[idx].nick, chan->dname,
+	 nick);
+  if (!me_op(chan)) {
+    if (chan->channel.mode & CHANINV) {
+      dprintf(idx, "I can't help you now because I'm not a channel op on %s.\n",
+	      chan->dname);
+      return;
+    }
+    if (!channel_active(chan)) {
+      dprintf(idx, "I'm not on %s right now!\n", chan->dname);
+      return;
+    }
+  }
+  m = ismember(chan, nick);
+  if (m && !chan_issplit(m)) {
+    dprintf(idx, "%s is already on %s!\n", nick, chan->dname);
+    return;
+  }
+  dprintf(DP_SERVER, "INVITE %s %s\n", nick, chan->name);
+  dprintf(idx, "Inviting %s to %s.\n", nick, chan->dname);
+}
+
+static void cmd_channel(struct userrec *u, int idx, char *par)
+{
+  char handle[HANDLEN + 1], s[UHOSTLEN], s1[UHOSTLEN], atrflag, chanflag,
+       *chname;
+  struct chanset_t *chan;
+  memberlist *m;
+  static char spaces[33] = "                              ";
+  static char spaces2[33] = "                              ";
+  int len, len2;
+
+  if (!has_op(idx, par))
+    return;
+  chname = newsplit(&par);
+  putlog(LOG_CMDS, "*", "#%s# (%s) channel %s", dcc[idx].nick,
+	 dcc[idx].u.chat->con_chan, chname);
+  if (!chname[0])
+    chan = findchan_by_dname(dcc[idx].u.chat->con_chan);
+  else
+    chan = findchan_by_dname(chname);
+  if (chan == NULL) {
+    dprintf(idx, "%s %s\n", _("Not active on channel"), chname);
+    return;
+  }
+  strncpyz(s, getchanmode(chan), sizeof s);
+  if (channel_pending(chan))
+    snprintf(s1, sizeof s1, "%s %s", _("Processing channel"), chan->dname);
+  else if (channel_active(chan))
+    snprintf(s1, sizeof s1, "%s %s", _("Channel"), chan->dname);
+  else
+    snprintf(s1, sizeof s1, "%s %s", _("Desiring channel"), chan->dname);
+  dprintf(idx, "%s, %d member%s, mode %s:\n", s1, chan->channel.members,
+	  chan->channel.members == 1 ? "" : "s", s);
+  if (chan->channel.topic)
+    dprintf(idx, "%s: %s\n", _("Channel Topic"), chan->channel.topic);
+  if (channel_active(chan)) {
+    dprintf(idx, "(n = owner, m = master, o = op, d = deop, b = bot)\n");
+    spaces[nick_len - 9] = 0;
+    spaces2[HANDLEN - 9] = 0;
+    dprintf(idx, " NICKNAME %s HANDLE   %s JOIN   IDLE  USER at HOST\n",
+	    spaces, spaces2);
+    spaces[nick_len - 9] = ' ';
+    spaces2[HANDLEN - 9] = ' ';
+    for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+      if (m->joined > 0) {
+	if ((now - (m->joined)) > 86400)
+	  strftime(s, 6, "%d%b", localtime(&(m->joined)));
+	else
+	  strftime(s, 6, "%H:%M", localtime(&(m->joined)));
+      } else
+	strncpyz(s, " --- ", sizeof s);
+      if (m->user == NULL) {
+	snprintf(s1, sizeof s1, "%s!%s", m->nick, m->userhost);
+	m->user = get_user_by_host(s1);
+      }
+      if (m->user == NULL) {
+	strncpyz(handle, "*", sizeof handle);
+      } else {
+	strncpyz(handle, m->user->handle, sizeof handle);
+      }
+      get_user_flagrec(m->user, &user, chan->dname);
+      /* Determine status char to use */
+      if (glob_bot(user) && (glob_op(user)||chan_op(user)))
+	atrflag = 'B';
+      else if (glob_bot(user))
+	atrflag = 'b';
+      else if (glob_owner(user))
+	atrflag = 'N';
+      else if (chan_owner(user))
+	atrflag = 'n';
+      else if (glob_master(user))
+	atrflag = 'M';
+      else if (chan_master(user))
+	atrflag = 'm';
+      else if (glob_deop(user))
+	atrflag = 'D';
+      else if (chan_deop(user))
+	atrflag = 'd';
+      else if (glob_autoop(user))
+	atrflag = 'A';
+      else if (chan_autoop(user))
+	atrflag = 'a';
+      else if (glob_op(user))
+	atrflag = 'O';
+      else if (chan_op(user))
+	atrflag = 'o';
+      else if (glob_quiet(user))
+	atrflag = 'Q';
+      else if (chan_quiet(user))
+	atrflag = 'q';
+      else if (glob_gvoice(user))
+	atrflag = 'G';
+      else if (chan_gvoice(user))
+	atrflag = 'g';
+      else if (glob_voice(user))
+	atrflag = 'V';
+      else if (chan_voice(user))
+	atrflag = 'v';
+      else if (glob_friend(user))
+        atrflag = 'F';
+      else if (chan_friend(user))
+        atrflag = 'f';
+      else if (glob_kick(user))
+        atrflag = 'K';
+      else if (chan_kick(user))
+        atrflag = 'k';
+      else if (glob_wasoptest(user))
+        atrflag = 'W';
+      else if (chan_wasoptest(user))
+        atrflag = 'w';
+      else if (glob_exempt(user))
+        atrflag = 'E';
+      else if (chan_exempt(user))
+        atrflag = 'e';
+      else
+	atrflag = ' ';
+      if (chan_hasop(m))
+	chanflag = '@';
+      else if (chan_hasvoice(m))
+	chanflag = '+';
+      else
+	chanflag = ' ';
+      spaces[len = (nick_len - strlen(m->nick))] = 0;
+      spaces2[len2 = (HANDLEN - strlen(handle))] = 0;
+      if (chan_issplit(m))
+	dprintf(idx, "%c%s%s %s%s %s %c     <- netsplit, %lus\n", chanflag,
+		m->nick, spaces, handle, spaces2, s, atrflag,
+		now - (m->split));
+      else if (!irccmp(m->nick, botname))
+	dprintf(idx, "%c%s%s %s%s %s %c     <- it's me!\n", chanflag, m->nick,
+		spaces, handle, spaces2, s, atrflag);
+      else {
+	/* Determine idle time */
+	if (now - (m->last) > 86400)
+	  snprintf(s1, sizeof s1, "%2lud", ((now - (m->last)) / 86400));
+	else if (now - (m->last) > 3600)
+	  snprintf(s1, sizeof s1, "%2luh", ((now - (m->last)) / 3600));
+	else if (now - (m->last) > 180)
+	  snprintf(s1, sizeof s1, "%2lum", ((now - (m->last)) / 60));
+	else
+	  strncpyz(s1, "   ", sizeof s1);
+	dprintf(idx, "%c%s%s %s%s %s %c %s  %s\n", chanflag, m->nick,
+		spaces, handle, spaces2, s, atrflag, s1, m->userhost);
+      }
+      spaces[len] = ' ';
+      spaces2[len2] = ' ';
+      if (chan_fakeop(m))
+	dprintf(idx, "    (%s)\n", _("FAKE CHANOP GIVEN BY SERVER"));
+      if (chan_sentop(m))
+	dprintf(idx, "    (%s)\n", _("pending +o -- Im lagged"));
+      if (chan_sentdeop(m))
+	dprintf(idx, "    (%s)\n", _("pending -o -- Im lagged"));
+      if (chan_sentkick(m))
+	dprintf(idx, "    (%s)\n", _("pending kick"));
+    }
+  }
+  dprintf(idx, "%s\n", _("End of channel info."));
+}
+
+static void cmd_topic(struct userrec *u, int idx, char *par)
+{
+  struct chanset_t *chan;
+
+  if (par[0] && (strchr(CHANMETA, par[0]) != NULL)) {
+    char *chname = newsplit(&par);
+    chan = has_op(idx, chname);
+  } else
+    chan = has_op(idx, "");
+  if (chan) {
+    if (!channel_active(chan)) {
+      dprintf(idx, "I'm not on %s right now!\n", chan->dname);
+      return;
+    }
+    if (!par[0]) {
+      if (chan->channel.topic) {
+	dprintf(idx, "The topic for %s is: %s\n", chan->dname,
+		chan->channel.topic);
+      } else {
+	dprintf(idx, "No topic is set for %s\n", chan->dname);
+      }
+    } else if (channel_optopic(chan) && !me_op(chan)) {
+      dprintf(idx, "I'm not a channel op on %s and the channel is +t.\n",
+	      chan->dname);
+    } else {
+      dprintf(DP_SERVER, "TOPIC %s :%s\n", chan->name, par);
+      dprintf(idx, "Changing topic...\n");
+      putlog(LOG_CMDS, "*", "#%s# (%s) topic %s", dcc[idx].nick,
+	     chan->dname, par);
+    }
+  }
+}
+
+static void cmd_resetbans(struct userrec *u, int idx, char *par)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  chname = newsplit(&par);
+  rmspace(chname);
+
+  if (chname[0]) {
+    chan = findchan_by_dname(chname);
+    if (!chan) {
+      dprintf(idx, "That channel doesnt exist!\n");
+      return;
+    }
+    get_user_flagrec(u, &user, chname);
+  } else {
+    chan = findchan_by_dname(dcc[idx].u.chat->con_chan);
+    if (!chan) {
+      dprintf(idx, "Invalid console channel.\n");
+      return;
+    }
+    get_user_flagrec(u, &user, dcc[idx].u.chat->con_chan);
+  }
+  if (glob_op(user) || chan_op(user)) {
+    putlog(LOG_CMDS, "*", "#%s# (%s) resetbans", dcc[idx].nick, chan->dname);
+    dprintf(idx, "Resetting bans on %s...\n", chan->dname);
+    resetbans(chan);
+  }
+}
+
+static void cmd_resetexempts(struct userrec *u, int idx, char *par)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  chname = newsplit(&par);
+  rmspace(chname);
+  if (chname[0]) {
+    chan = findchan_by_dname(chname);
+    if (!chan) {
+      dprintf(idx, "That channel doesnt exist!\n");
+      return;
+    }
+    get_user_flagrec(u, &user, chname);
+  } else {
+    chan = findchan_by_dname(dcc[idx].u.chat->con_chan);
+    if (!chan) {
+    dprintf(idx, "Invalid console channel.\n");
+      return;
+    }
+    get_user_flagrec(u, &user, dcc[idx].u.chat->con_chan);
+  }
+  if (glob_op(user) || chan_op(user)) {
+    putlog(LOG_CMDS, "*", "#%s# (%s) resetexempts", dcc[idx].nick, chan->dname);
+    dprintf(idx, "Resetting exemptions on %s...\n", chan->dname);
+    resetexempts(chan);
+  }
+}
+
+static void cmd_resetinvites(struct userrec *u, int idx, char *par)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  chname = newsplit(&par);
+  rmspace(chname);
+  if (chname[0]) {
+    chan = findchan_by_dname(chname);
+    if (!chan) {
+      dprintf(idx, "That channel doesnt exist!\n");
+      return;
+    }
+    get_user_flagrec(u, &user, chname);
+  } else {
+    chan = findchan_by_dname(dcc[idx].u.chat->con_chan);
+    if (!chan) {
+    dprintf(idx, "Invalid console channel.\n");
+      return;
+    }
+    get_user_flagrec(u, &user, dcc[idx].u.chat->con_chan);
+  }
+  if (glob_op(user) || chan_op(user)) {
+    putlog(LOG_CMDS, "*", "#%s# (%s) resetinvites", dcc[idx].nick, chan->dname);
+    dprintf(idx, "Resetting invitations on %s...\n", chan->dname);
+    resetinvites(chan);
+  }
+}
+
+static void cmd_adduser(struct userrec *u, int idx, char *par)
+{
+  char *nick, *hand;
+  struct chanset_t *chan;
+  memberlist *m = NULL;
+  char s[UHOSTLEN], s1[UHOSTLEN];
+  int atr = u ? u->flags : 0;
+  int statichost = 0;
+  char *p1 = s1;
+
+  if ((!par[0]) || ((par[0]=='!') && (!par[1]))) {
+    dprintf(idx, "Usage: adduser <nick> [handle]\n");
+    return;
+  }
+  nick = newsplit(&par);
+
+  /* This flag allows users to have static host (added by drummer, 20Apr99) */
+  if (nick[0] == '!') {
+    statichost = 1;
+    nick++;
+  }
+
+  if (!par[0]) {
+    hand = nick;
+  } else {
+    char *p;
+    int ok = 1;
+
+    for (p = par; *p; p++)
+      if ((*p <= 32) || (*p >= 127))
+	ok = 0;
+    if (!ok) {
+      dprintf(idx, "You can't have strange characters in a nick.\n");
+      return;
+    } else if (strchr("-,+*=:!.@#;$", par[0]) != NULL) {
+      dprintf(idx, "You can't start a nick with '%c'.\n", par[0]);
+      return;
+    }
+    hand = par;
+  }
+
+  for (chan = chanset; chan; chan = chan->next) {
+    m = ismember(chan, nick);
+    if (m)
+      break;
+  }
+  if (!m) {
+    dprintf(idx, "%s is not on any channels I monitor\n", nick);
+    return;
+  }
+  if (strlen(hand) > HANDLEN)
+    hand[HANDLEN] = 0;
+  snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost);
+  u = get_user_by_host(s);
+  if (u) {
+    dprintf(idx, "%s is already known as %s.\n", nick, u->handle);
+    return;
+  }
+  u = get_user_by_handle(userlist, hand);
+  if (u && (u->flags & (USER_OWNER|USER_MASTER)) &&
+      !(atr & USER_OWNER) && strcasecmp(dcc[idx].nick, hand)) {
+    dprintf(idx, "You can't add hostmasks to the bot owner/master.\n");
+    return;
+  }
+  if (!statichost)
+    maskhost(s, s1);
+  else {
+    strncpyz(s1, s, sizeof s1);
+    p1 = strchr(s1, '!');
+    if (strchr("~^+=-", p1[1])) {
+      if (strict_host)
+	p1[1] = '?';
+      else {
+	p1[1] = '!';
+	p1++;
+      }
+    }
+    p1--;
+    p1[0] = '*';
+  }
+  if (!u) {
+    dprintf(idx, "Added [%s]%s with no password.\n", hand, p1);
+    userlist = adduser(userlist, hand, p1, "-", USER_DEFAULT);
+  } else {
+    dprintf(idx, "Added hostmask %s to %s.\n", p1, u->handle);
+    addhost_by_handle(hand, p1);
+    get_user_flagrec(u, &user, chan->dname);
+    if ((chan_op(user) || (glob_op(user) && !chan_deop(user))) &&
+	(channel_autoop(chan) || glob_autoop(user) || chan_autoop(user)))
+      add_mode(chan, '+', 'o', m->nick);
+  }
+  putlog(LOG_CMDS, "*", "#%s# adduser %s %s", dcc[idx].nick, nick,
+	 hand == nick ? "" : hand);
+}
+
+static void cmd_deluser(struct userrec *u, int idx, char *par)
+{
+  char *nick, s[UHOSTLEN];
+  struct chanset_t *chan;
+  memberlist *m = NULL;
+  struct flag_record victim = {FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0};
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: deluser <nick>\n");
+    return;
+  }
+  nick = newsplit(&par);
+
+  for (chan = chanset; chan; chan = chan->next) {
+    m = ismember(chan, nick);
+    if (m)
+      break;
+  }
+  if (!m) {
+    dprintf(idx, "%s is not on any channels I monitor\n", nick);
+    return;
+  }
+  get_user_flagrec(u, &user, chan->dname);
+  snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost);
+  u = get_user_by_host(s);
+  if (!u) {
+    dprintf(idx, "%s is not a valid user.\n", nick);
+    return;
+  }
+  get_user_flagrec(u, &victim, NULL);
+  /* This maybe should allow glob +n's to deluser glob +n's but I don't
+   * like that - beldin
+   */
+  /* Checks vs channel owner/master ANYWHERE now -
+   * so deluser on a channel they're not on should work
+   */
+  /* Shouldn't allow people to remove permanent owners (guppy 9Jan1999) */
+  if ((glob_owner(victim) && strcasecmp(dcc[idx].nick, nick)) ||
+      isowner(u->handle)) {
+    dprintf(idx, "Can't remove the bot owner!\n");
+  } else if (glob_botmast(victim) && !glob_owner(user)) {
+    dprintf(idx, "Can't delete a master!\n");
+  } else if (chan_owner(victim) && !glob_owner(user)) {
+    dprintf(idx, "Can't remove a channel owner!\n");
+  } else if (chan_master(victim) && !(glob_owner(user) || chan_owner(user))) {
+    dprintf(idx, "Can't delete a channel master!\n");
+  } else if (glob_bot(victim) && !glob_owner(user)) {
+    dprintf(idx, "Can't delete a bot!\n");
+  } else {
+    char buf[HANDLEN + 1];
+
+    strncpyz(buf, u->handle, sizeof buf);
+    buf[HANDLEN] = 0;
+    if (deluser(u->handle)) {
+      dprintf(idx, "Deleted %s.\n", buf); /* ?!?! :) */
+      putlog(LOG_CMDS, "*", "#%s# deluser %s [%s]", dcc[idx].nick, nick, buf);
+    } else {
+      dprintf(idx, "Failed.\n");
+    }
+  }
+}
+
+static void cmd_reset(struct userrec *u, int idx, char *par)
+{
+  struct chanset_t *chan;
+
+  if (par[0]) {
+    chan = findchan_by_dname(par);
+    if (!chan)
+      dprintf(idx, "%s\n", _("I dont monitor that channel."));
+    else {
+      get_user_flagrec(u, &user, par);
+      if (!glob_master(user) && !chan_master(user)) {
+	dprintf(idx, "You are not a master on %s.\n", chan->dname);
+      } else if (!channel_active(chan)) {
+	dprintf(idx, "I'm not on %s at the moment!\n", chan->dname);
+      } else {
+	putlog(LOG_CMDS, "*", "#%s# reset %s", dcc[idx].nick, par);
+	dprintf(idx, "Resetting channel info for %s...\n", chan->dname);
+	reset_chan_info(chan);
+      }
+    }
+  } else if (!(u->flags & USER_MASTER)) {
+    dprintf(idx, "You are not a Bot Master.\n");
+  } else {
+    putlog(LOG_CMDS, "*", "#%s# reset all", dcc[idx].nick);
+    dprintf(idx, "Resetting channel info for all channels...\n");
+    for (chan = chanset; chan; chan = chan->next) {
+      if (channel_active(chan))
+	reset_chan_info(chan);
+    }
+  }
+}
+
+static cmd_t irc_dcc[] =
+{
+  {"adduser",		"m|m",	(Function) cmd_adduser,		NULL},
+  {"deluser",		"m|m",	(Function) cmd_deluser,		NULL},
+  {"reset",		"m|m",	(Function) cmd_reset,		NULL},
+  {"resetbans",		"o|o",	(Function) cmd_resetbans,	NULL},
+  {"resetexempts",	"o|o",	(Function) cmd_resetexempts,	NULL},
+  {"resetinvites",	"o|o",	(Function) cmd_resetinvites,	NULL},
+  {"act",		"o|o",	(Function) cmd_act,		NULL},
+  {"channel",		"o|o",	(Function) cmd_channel,		NULL},
+  {"deop",		"o|o",	(Function) cmd_deop,		NULL},
+  {"invite",		"o|o",	(Function) cmd_invite,		NULL},
+  {"kick",		"o|o",	(Function) cmd_kick,		NULL},
+  {"kickban",		"o|o",	(Function) cmd_kickban,		NULL},
+  {"msg",		"o",	(Function) cmd_msg,		NULL},
+  {"voice",		"o|o",	(Function) cmd_voice,		NULL},
+  {"devoice",		"o|o",	(Function) cmd_devoice,		NULL},
+  {"op",		"o|o",	(Function) cmd_op,		NULL},
+  {"say",		"o|o",	(Function) cmd_say,		NULL},
+  {"topic",		"o|o",	(Function) cmd_topic,		NULL},
+  {NULL,		NULL,	NULL,				NULL}
+};
Index: eggdrop1.7/modules/irc/help/irc.help
diff -u /dev/null eggdrop1.7/modules/irc/help/irc.help:1.1
--- /dev/null	Sat Oct 27 11:35:16 2001
+++ eggdrop1.7/modules/irc/help/irc.help	Sat Oct 27 11:34:51 2001
@@ -0,0 +1,212 @@
+%{help=adduser}%{+m|m}
+###  %badduser%b [!]<nickname> [handle]
+   Creates a new user record for a user on the channel, using their
+   current hostname.  It's similar to a user msg'ing the bot 'hello'
+   except that no information is sent to that user.  If the bot
+   already knows someone by that nickname, and the user on the channel
+   doesn't have a bot record, then it does the equivalent of an
+   'ident' for that user -- except that, again, no information is
+   sent to the user telling them that anything was done.
+
+   If the user is using a different nickname than the bot normally
+   knows her by, you can specify her "handle" (the nickname that the
+   bot remembers).
+
+   If you want to add a user using a static hostmask, prefix their nick
+   with a '!'. ie .adduser !Lamer
+
+see also: +host, -host, +user, -user
+%{help=deluser}%{+m|m}
+###  %bdeluser%b <nickname>
+   deletes a user record for a user on the channel, using their
+   current hostname.  Channel masters can remove users so long as
+   the user isn't a bot master.
+
+see also: adduser, +user, -user
+%{help=reset}%{+m}
+###  %breset%b [channel]
+   clears out the bot's channel information and makes it gather the
+   information from the server all over again, as if it had just
+   joined that channel.  it's not really useful much, but could be
+   if an odd bug causes the channel information to get scrambled.
+   unfortunately this command used to get a lot of use.  you can omit
+   the channel name to make it reset ALL channels.
+%{help=resetbans}%{+o|o}
+###  %bresetbans%b [channel]
+   resets the bot's ban list for the channel. any bans on the channel
+   that aren't in the ban list (either the global list or the local
+   channel ban list) will be removed, and if there are any bans in the
+   global ban list or channel ban list that are not currently on the
+   channel, they will be added.
+
+see also: bans, reset, console
+%{help=resetexempts}%{+o|o}
+###  %bresetexempts%b [channel]
+   resets the bot's exemption list for the channel. this command
+   behaves exactly like resetbans, except it is for exempts.
+
+See also: resetbans, resetinvites
+%{help=resetinvites}%{+o|o}
+###  %bresetinvites%b [channel]
+   resets the bot's invitation list for the channel. this command
+   behaves exactly like resetbans, except it is for invites.
+
+See also: resetbans, resetinvites
+%{help=act}%{+o|o}
+###  %bact%b [channel] <text>
+   Performs an action on the current console channel (or otherwise
+   specified channel), as if the bot did it.  Just like the /me
+   command in IRC.
+
+See also: console
+%{help=channel}%{+o|o}
+###  %bchannel%b [channel-name]
+   Shows you an extensive display of the users on a channel, and
+   the current channel attributes.  By default, it shows you the
+   channel you are currently viewing on the console, but you can
+   specify another channel if you wish.
+
+   The first line will look like:
+      Channel #hiya, 8 members, 45 users, mode +tn:
+   This means that the bot is sitting on channel #hiya, where 8
+   other irc'ers are.  There are 45 people that the bot knows by
+   hostmask, and the channel mode is +tn.  If the bot isn't on
+   the channel it is supposed to be on, it will say "Desiring
+   channel #hiya" instead.  Next is a list of the users on the
+   channel, with each entry looking like this:
+       NICKNAME  HANDLE    JOIN   IDLE  USER at HOST
+      @kantSF    kantSF    14:53 o  6m  josh at random.edu
+   The "@kantSF" means that the user's nickname is kantSF and that
+   he is a chanop.  The second "kantSF" is the nickname that the
+   bot knows him by.  Sometimes this will differ from the nickname
+   a person is using.  The time displayed is the time the user
+   joined the channel.  The next field is the attributes:
+      n - bot owner                      o - can get ops (+o)
+      m - bot master or owner            f - channel friend
+      b - another bot                    d - cannot get ops (+d)
+   The last field is the user at host he is using irc from.
+
+See also: status, whois
+%{help=voice}%{+o|o}
+###  %bvoice%b <nickname> [channel]
+    will give a +v voice to a person you specify, so long as the
+    bot is opped on that channel.
+
+see also: devoice
+
+%{help=devoice}%{+o|o}
+### %bdevoice%b <nickname> [channel]
+    will remove the +v voice from the person you specify, so long as
+    the bot is opped on that channel.
+
+%{help=deop}%{+o|o}
+###  %bdeop%b <nickname> [channel]
+   will remove chanop from the person you specify, so long as the
+   bot is opped on that channel, and the person you specify isn't
+   on the bot's list of authorized chanops.
+
+see also: op, console
+%{help=invite}%{+o|o}
+###  %binvite%b <nickname> [channel]
+   invites someone from irc into your current console channel (or
+   specified other channel).  this is most useful when the channel
+   is +i.  a user with the +o flag can also request an invite from
+   the bot with /MSG INVITE
+
+see also: console
+%{help=kick}%{+o|o}
+###  %bkick%b [channel] <nickname> [reason]
+   will kick a user off your current console channel (or specified
+   other channel) with the comment given.  if you omit the reason,
+   the default kick comment is "requested".
+
+see also: kickban, console
+%{help=kickban}%{+o|o}
+###  %bkickban%b [channel] [-|@]<nickname> [comment]
+   kicks a user off the channel and bans her by a reasonable host-
+   mask.  your nickname will be attached to the ban in the bot's
+   internal ban list, and the ban will last for whatever is set in
+   ban-time -- only on this channel.  use %b'.+ban'%b for a more
+   permanent ban which will be activated on every channel the bot
+   monitors.  if you use a comment, that will also be attached to
+   the ban in the ban list, and used as the kick comment.
+     appending a prefix of ! or @ to a nickname changes the ban
+   mask used:
+        eg. with a host of nick!ident at host.name.domain
+
+        command        banmask
+        .kickban nick  *!*dent@*.name.domain
+        .kickban -nick *!*dent at host.name.domain
+        .kickban @nick *!*@host.name.domain
+
+        with a host of nick!~ident at host.name.domain (strict-host set to 1)
+
+        command        banmask
+        .kickban nick  *!*ident@*.name.domain
+        .kickban -nick *!*ident at host.name.domain
+
+see also: +ban, bans, stick
+%{help=msg}%{+o}
+###  %bmsg%b <nickname> <text>
+   sends a private message to someone from the bot, just as if the
+   bot had typed /msg.
+%{help=op}%{+o|o}
+###  %bop%b <nickname> [channel]
+   will grant chanop to the person you specify, so long as the bot
+   is opped on that channel, and the person you specify isn't being
+   actively deopped by the bot.
+
+see also: deop, console
+%{help=say}%{+o|o}
+###  %bsay%b [channel] <text>
+   dumps the text to your current console channel (or other specified
+   channel), as if the bot "said" it.
+%{help=topic}%{+o|o}
+###  %btopic%b <text>
+   changes the channel's topic, assuming the bot is a chanop or the
+   channel is not +t (uses your current console channel).
+
+see also: console
+%{help=irc module}%{+o|o}
+###  help on the %birc module%b
+   the irc module provides the following dcc commmands:
+%{+o|o}
+     for channel ops:
+       %bresetbans%b %bact%b      %bchannel%b    %bdeop%b     %bsay%b
+       %binvite%b    %bkick%b     %bkickban%b    %bop%b       %btopic%b
+       %bresetexempts%b       %bresetinvites%b
+%{+o}
+     for global ops:
+       %bmsg%b
+%{+m|m}
+     for channel masters:
+       %badduser%b   %bdeluser%b
+%{+m}
+     for global masters:
+       %breset%b
+%{+n}
+   various tcl settings are avaliable and can be viewed by using
+   %b'help set <setting>'%b. Thes include:
+      %blearn-users%b      %bwait-split%b
+      %bwait-info%b        %bmodes-per-line%b
+      %bmode-buf-length%b  %bbounce-bans%b
+      %bbounce-exempts%b   %bbounce-invites%b
+      %bbounce-modes%b     %bmax-bans%b
+      %bmax-exempts%b      %bmax-invites%b
+      %bmax-modes%b        %buse-354%b
+%{help=all}%{+o|o}
+###  commands for the %birc module%b
+  for channel ops:
+    %bresetbans%b %bact%b      %bchannel%b    %bdeop%b     %bsay%b
+    %binvite%b    %bkick%b     %bkickban%b    %bop%b       %btopic%b
+    %bresetexempts%b       %bresetinvites%b
+%{+o}
+  for global ops:
+    %bmsg%b
+%{+m|m}
+  for channel masters:
+    %badduser%b   %bdeluser%b
+%{+m}
+  for global masters:
+    %breset%b
+
Index: eggdrop1.7/modules/irc/help/msg/irc.help
diff -u /dev/null eggdrop1.7/modules/irc/help/msg/irc.help:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/irc/help/msg/irc.help	Sat Oct 27 11:34:51 2001
@@ -0,0 +1,130 @@
+%{help=die}%{+m}
+%b/MSG%b %B %bDIE%b <password> [message]
+   This makes me die (go away) -- not something you will
+   probably be doing very often.
+%{help=go}%{+o|o}
+%b/MSG%b %B %bGO%b <channel>
+   This makes me leave a channel if I'm not a channel op.
+   (It's useful for regaining ops.)
+%{help=help}
+%bMSG%b commands for %b%B%b:
+%{cols=4/50}%{-}
+ADDHOST
+INFO
+WHO
+IDENT
+HELP
+VOICE
+WHOIS
+PASS
+%{+o|o}
+OP
+INVITE
+GO
+KEY
+%{-}%{+m}
+DIE
+JUMP
+MEMORY
+SAVE
+REHASH
+RESET
+%{+m|m}
+STATUS
+%{end}
+For help on a command, %b/MSG%b %B %bHELP%b <command>
+%{+m}
+You are a master.  Many many more commands are
+available via dcc chat.
+%{-}
+Admin: %A
+There may also be additional commands provided by other modules.
+%{help=addhost}
+%b/MSG%b %B %bADDHOST%b <password> <hostmask>
+   This lets me recognize you from a new hostmask.  You must
+   use your password (the one you set with PASS) so I know
+   it's really you. The hostmask you tell me must not match
+   any existing ones.
+%{help=ident}
+%b/MSG%b %B %bIDENT%b <password> [nickname]
+   This lets me recognize you from a new address.  You must
+   use your password (the one you set with PASS) so I know
+   it's really you.  If you're using a different nickname
+   than you were when you registered, you'll have to give
+   your original nickname too.
+%{help=info}
+%b/MSG%b %B %bINFO%b <password> [channel] [an info line]
+   Whatever you set as your info line will be shown when
+   you join the channel, as long as you haven't been there
+   in the past three minutes.  It is also shown to people
+   when they ask the bot for WHO or WHOIS.  You may set an
+   info line specific to a channel like so:
+      %b/MSG%b %B %bINFO%b mypass #channel This is my info.
+   Or you may set the default info line (used when there
+   is no channel-specific one) like so:
+      %b/MSG%b %B %bINFO%b mypass This is my default info.
+%b/MSG%b %B %bINFO%b [password] [channel] %bNONE%b
+   This erases your info line.
+%{help=invite}%{+o|o}
+%b/MSG%b %B %bINVITE%b <password> <channel>
+   This will make me invite you to a channel (if I'm on
+   that channel).
+%{help=jump}%{+m}
+%b/MSG%b %B %bJUMP%b <password> [server [port [server password]]]
+   This will make me jump to a new server.  You can
+   optionally tell me a server to jump to, in the form of
+   'new.server.com' or 'new.server.com 6667'.
+%{help=memory}%{+m}
+%b/MSG%b %B %bMEMORY%b <password>
+   This makes me tell you info about how much memory I'm
+   using.  Pretty boring stuff.
+%{help=op}%{+o|o}
+%b/MSG%b %B %bOP%b <password> [channel]
+   This will tell me to op you on any channel where I have
+   ops and you don't.  If you give a channel name, I'll just
+   op you on that channel.
+%{help=voice}%{+o|o}
+%b/MSG%b %B %bVOICE%b <password> <channel>
+   This will tell me to voice you on the channel if I have
+   ops and you don't have a voice, and I monitor that channels
+   voices.
+%{help=key}%{+o|o}
+%b/MSG%b %B %bKEY%b <password> <channel>
+   This will send you the key for a channel that is +k
+   and possibly also invite if it is +i (if I'm on that
+   channel).
+%{help=pass}
+%b/MSG%b %B %bPASS%b <password>
+   This sets a password, which lets you use other commands,
+   like %bIDENT%b.
+%{+o|o}
+   Ops and masters: You need a password to use ANY op or
+   master command.
+%{-}
+%b/MSG%b %B %bPASS%b <oldpass> <newpass>
+   This is how you change your password.
+%{help=save}%{+m}
+%b/MSG%b %B %bSAVE%b <password>
+   This makes me save my userfile.
+%{help=rehash}%{+m}
+%b/MSG%b %B %bREHASH%b <password>
+   This makes me reload my config file.
+%{help=reset}%{+m}
+%b/MSG%b %B %bRESET%b <password> [channel]
+   This makes the me reset my channel information, in case
+   I'm out of sync with reality.  It isn't used much any
+   more.
+%{help=status}%{+m|m}
+%b/MSG%b %B %bSTATUS%b <password>
+   This gives you a little three line display of my current
+   status.  The status command in dcc chat is much better.
+%{help=who}
+%b/MSG%b %B %bWHO%b <channel>
+   This will show you a list of who's on that channel right
+   now, and each person's info line (if they have one).
+%{help=whois}
+%b/MSG%b %B %bWHOIS%b <hand>
+   This will give you information about someone else I
+   know, including his or her default info line, email
+   address, when they were last on the channel, and
+   if they are an op or master.
Index: eggdrop1.7/modules/irc/help/set/irc.help
diff -u /dev/null eggdrop1.7/modules/irc/help/set/irc.help:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/irc/help/set/irc.help	Sat Oct 27 11:34:51 2001
@@ -0,0 +1,72 @@
+%{help=set learn-users}%{+n}
+###  %bset learn-users%b <0/1>
+   specifies whether the bot will add users on the fly when they
+   send the "hello" msg.  if turned off, the only way to add users
+   is with the %b'.adduser'%b or %b'.+user'%b commands.  if turned on,
+   users can introduce themselves without your intervention.
+%{help=set wait-split}%{+n}
+### %bset wait-split%b <#>
+   allows you to set the number of seconds to wait before
+   considering a split user as gone for good.
+%{help=set wait-info}%{+n}
+### %bset wait-info%b <#>
+   allows you to set the number of seconds to wait before
+   redisplaying a users info line when they join the channel
+   (only relevant if %buse-info%b is 1).
+%{help=set modes-per-line}%{+n}
+### %bset modes-per-line%b <3-6>
+   this allows you to set the number of mode changes the bot will
+   send in one line, the irc default is 3 but some networks these
+   days allow up to 6 or more mode changes.
+%{help=set mode-buf-length}%{+n}
+### %bset mode-buf-length%b <#>
+   most servers limit the amount of data that can be sent in the
+   arguments of mode changes, this setting allows you to match it,
+   should your server be using a non-standard setup (200 is standard)
+%{help=set bounce-bans}%{+n}
+### %bset bounce-bans%b 0/1
+   do you want the bot to unban any bans set by servers?
+%{help=set bounce-exempts}%{+n}
+### %bset bounce-exempts%b 0/1
+   do you want the bot to reverse any +e modes set by servers?
+   this is an IRCNET feature.
+%{help=set bounce-invites}%{+n}
+### %bset bounce-invites%b 0/1
+   do you want the bot to reverse any +I modes set by servers?
+   this is an IRCNET feature.
+%{help=set bounce-modes}%{+n}
+### %bset bounce-modes%b 0/1
+   do you want the bot to reverse all server modes?
+%{help=set max-bans}%{+n}
+### %bset max-bans%b <#>
+   if there are more than max-bans active bans on a channel, then
+   the bot won't try to put more bans. thus it won't flood the irc
+   server.
+%{help=set max-exempts}%{+n}
+### %bset max-exempts%b <#>
+   if there are more than max-exempts active +e modes on a channel,
+   then the bot won't try to put more exemptions. thus it won't flood
+   the irc server. this is an IRCNET feature.
+%{help=set max-invites}%{+n}
+### %bset max-invites%b <#>
+   if there are more than max-invites active +I modes on a channel,
+   then the bot won't try to put more invitations. thus it won't flood
+   the irc server. this is an IRCNET feature.
+%{help=set max-modes}%{+n}
+### %bset max-modes%b <#>
+   there is a global limit for +b/+e/+I modes. this limit is set to
+   30 on 2.10 irc servers. this is an IRCNET feature.
+%{help=set kick-bogus}%{+n}
+### %bset kick-bogus%b 0/1
+   do you want the bot to kick bogus usernames?
+%{help=set kick-bogus}%{+n}
+### %bset kick-bogus%b 0/1
+   do you want the bot to kick for ctcp avalanches to a channel?
+   remember that if it does, as it won't ban them, it can be at the
+   origin of a nice kick-flood.
+%{help=set use-354}%{+n}
+### %bset use-354%b 0/1
+   ircu has a new format for /who channel (which returns 354 numeric)
+   by using this you reduce the amount of data retrieved about each
+   user when the bot joins the channel to the minimum required,
+   saving bandwidth, and being a good netizen ;)
Index: eggdrop1.7/modules/irc/irc.c
diff -u /dev/null eggdrop1.7/modules/irc/irc.c:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/irc/irc.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,1062 @@
+/*
+ * irc.c -- part of irc.mod
+ *   support for channels within the bot
+ *
+ * $Id: irc.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define MODULE_NAME "irc"
+#define MAKING_IRC
+#include "lib/eggdrop/module.h"
+#include "modules/server/server.h"
+#include "irc.h"
+#include "modules/channels/channels.h"
+#ifdef HAVE_UNAME
+#include <sys/utsname.h>
+#endif
+
+#define start irc_LTX_start
+
+/* We import some bind tables from server.mod */
+static bind_table_t *BT_dcc, *BT_raw, *BT_msg, *BT_ctcp, *BT_ctcr;
+
+/* We also create a few. */
+static bind_table_t *BT_topic, *BT_split, *BT_rejoin, *BT_quit, *BT_join, *BT_part, *BT_kick, *BT_nick, *BT_mode, *BT_need, *BT_pub, *BT_pubm;
+
+static Function *global = NULL, *channels_funcs = NULL, *server_funcs = NULL;
+
+static int ctcp_mode;
+static int net_type;
+static int strict_host;
+static int wait_split = 300;		/* Time to wait for user to return from
+					   net-split. */
+static int max_bans = 20;
+static int max_exempts = 20;
+static int max_invites = 20;
+static int max_modes = 30;
+static int bounce_bans = 1;
+static int bounce_exempts = 0;
+static int bounce_invites = 0;
+static int bounce_modes = 0;
+static int learn_users = 0;
+static int wait_info = 15;
+static int invite_key = 1;
+static int no_chanrec_info = 0;
+static int modesperline = 3;		/* Number of modes per line to send. */
+static int mode_buf_len = 200;		/* Maximum bytes to send in 1 mode. */
+static int use_354 = 0;			/* Use ircu's short 354 /who
+					   responses. */
+static int kick_method = 1;		/* How many kicks does the irc network
+					   support at once?
+					   0 = as many as possible.
+					       (Ernst 18/3/1998) */
+static int kick_fun = 0;
+static int ban_fun = 0;
+static int keepnick = 1;		/* Keep nick */
+static int prevent_mixing = 1;		/* To prevent mixing old/new modes */
+static int rfc_compliant = 1;		/* net-type changing modifies this */
+
+static int include_lk = 1;		/* For correct calculation
+					   in real_add_mode. */
+
+#include "chan.c"
+#include "mode.c"
+#include "cmdsirc.c"
+#include "msgcmds.c"
+#include "tclirc.c"
+
+
+/* Contains the logic to decide wether we want to punish someone. Returns
+ * true (1) if we want to, false (0) if not.
+ */
+static int want_to_revenge(struct chanset_t *chan, struct userrec *u,
+			   struct userrec *u2, char *badnick, char *victim,
+			   int mevictim)
+{
+  struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
+
+  /* Do not take revenge upon ourselves. */
+  if (match_my_nick(badnick))
+    return 0;
+
+  get_user_flagrec(u, &fr, chan->dname);
+
+  /* Kickee is not a friend? */
+  if (!chan_friend(fr) && !glob_friend(fr) &&
+      /* ... and they didn't kick themself? */
+      irccmp(badnick, victim)) {
+    /* They kicked me? */
+    if (mevictim) {
+      /* ... and I'm allowed to take revenge? <snicker> */
+      if (channel_revengebot(chan))
+        return 1;
+    /* Do we revenge for our users ... and do we actually know the victim? */
+    } else if (channel_revenge(chan) && u2) {
+      struct flag_record fr2 = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
+
+      get_user_flagrec(u2, &fr2, chan->dname);
+      /* Protecting friends? */
+      if ((channel_protectfriends(chan) &&
+	   /* ... and victim is valid friend? */
+	   (chan_friend(fr2) || (glob_friend(fr2) && !chan_deop(fr2)))) ||
+	  /* ... or protecting ops */
+	  (channel_protectops(chan) &&
+	   /* ... and kicked is valid op? */
+	   (chan_op(fr2) || (glob_op(fr2) && !chan_deop(fr2)))))
+	return 1;
+    }
+  }
+  return 0;
+}
+
+/* Dependant on revenge_mode, punish the offender.
+ */
+static void punish_badguy(struct chanset_t *chan, char *whobad,
+			  struct userrec *u, char *badnick, char *victim,
+			  int mevictim, int type)
+{
+  char reason[1024], ct[81], *kick_msg;
+  memberlist *m;
+  struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
+
+  m = ismember(chan, badnick);
+  if (!m)
+    return;
+  get_user_flagrec(u, &fr, chan->dname);
+
+  /* Get current time into a string */
+  strftime(ct, 7, "%d %b", localtime(&now));
+
+  /* Put together log and kick messages */
+  reason[0] = 0;
+  switch (type) {
+  case REVENGE_KICK:
+    kick_msg = _("dont kick my friends, bud");
+    simple_sprintf(reason, "kicked %s off %s", victim, chan->dname);
+    break;
+  case REVENGE_DEOP:
+    simple_sprintf(reason, "deopped %s on %s", victim, chan->dname);
+    kick_msg = _("dont deop my friends, bud");
+    break;
+  default:
+    kick_msg = "revenge!";
+  }
+  putlog(LOG_MISC, chan->dname, "Punishing %s (%s)", badnick, reason);
+
+  /* Set the offender +d */
+  if ((chan->revenge_mode > 0) &&
+      /* ... unless there's no more to do */
+      !(chan_deop(fr) || glob_deop(fr))) {
+    char s[UHOSTLEN], s1[UHOSTLEN];
+    memberlist *mx = NULL;
+
+    /* Removing op */
+    if (chan_op(fr) || (glob_op(fr) && !chan_deop(fr))) {
+      fr.match = FR_CHAN;
+      if (chan_op(fr)) {
+        fr.chan &= ~USER_OP;
+      } else {
+        fr.chan |= USER_DEOP;
+      }
+      set_user_flagrec(u, &fr, chan->dname);
+      putlog(LOG_MISC, "*", "No longer opping %s[%s] (%s)", u->handle, whobad,
+	     reason);
+    }
+    /* ... or just setting to deop */
+    else if (u) {
+      /* In the user list already, cool :) */
+      fr.match = FR_CHAN;
+      fr.chan |= USER_DEOP;
+      set_user_flagrec(u, &fr, chan->dname);
+      simple_sprintf(s, "(%s) %s", ct, reason);
+      putlog(LOG_MISC, "*", "Now deopping %s[%s] (%s)", u->handle, whobad, s);
+    }
+    /* ... or creating new user and setting that to deop */
+    else {
+      strcpy(s1, whobad);
+      maskhost(s1, s);
+      strcpy(s1, badnick);
+      /* If that handle exists use "badX" (where X is an increasing number)
+       * instead.
+       */
+      while (get_user_by_handle(userlist, s1)) {
+        if (!strncmp(s1, "bad", 3)) {
+          int i;
+
+          i = atoi(s1 + 3);
+          simple_sprintf(s1 + 3, "%d", i + 1);
+        } else
+          strcpy(s1, "bad1");		/* Start with '1' */
+      }
+      userlist = adduser(userlist, s1, s, "-", 0);
+      fr.match = FR_CHAN;
+      fr.chan = USER_DEOP;
+      fr.udef_chan = 0;
+      u = get_user_by_handle(userlist, s1);
+      if ((mx = ismember(chan, badnick)))
+        mx->user = u;
+      set_user_flagrec(u, &fr, chan->dname);
+      simple_sprintf(s, "(%s) %s (%s)", ct, reason, whobad);
+      set_user(&USERENTRY_COMMENT, u, (void *) s);
+      putlog(LOG_MISC, "*", "Now deopping %s (%s)", whobad, reason);
+    }
+  }
+
+  /* Always try to deop the offender */
+  if (!mevictim)
+    add_mode(chan, '-', 'o', badnick);
+  /* Ban. Should be done before kicking. */
+  if (chan->revenge_mode > 2) {
+    char *baduhost, s[UHOSTLEN], s1[UHOSTLEN], s2[UHOSTLEN];
+
+    /* FIXME: clean this mess up */
+    strncpyz(s, whobad, sizeof s);
+    strtok(s, "!");
+    baduhost = strtok(NULL, "!");
+    maskhost(baduhost, s1);
+    simple_sprintf(s2, "(%s) %s", ct, reason);
+    u_addban(chan, s1, origbotname, s2, now + (60 * ban_time), 0);
+    if (!mevictim && me_op(chan)) {
+      add_mode(chan, '+', 'b', s1);
+      flush_mode(chan, QUICK);
+    }
+  }
+  /* Kick the offender */
+  if ((chan->revenge_mode > 1) &&
+      /* ... or don't we kick ops? */
+      (channel_dontkickops(chan) &&
+        !(chan_op(fr) || (glob_op(fr) && !chan_deop(fr)))) &&
+      /* ... or have we sent the kick already? */
+      !chan_sentkick(m) &&
+      /* ... and can I actually do anything about it? */
+      me_op(chan) && !mevictim) {
+    dprintf(DP_MODE, "KICK %s %s :%s\n", chan->name, badnick, kick_msg);
+    m->flags |= SENTKICK;
+  }
+}
+
+/* Punishes bad guys under certain circumstances using methods as defined
+ * by the revenge_mode flag.
+ */
+static void maybe_revenge(struct chanset_t *chan, char *whobad,
+			  char *whovictim, int type)
+{
+  char buf[UHOSTLEN], buf2[UHOSTLEN], *badnick, *victim;
+  int mevictim;
+  struct userrec *u, *u2;
+
+  if (!chan || (type < 0))
+    return;
+
+  /* Get info about offender */
+  u = get_user_by_host(whobad);
+  strncpyz(buf, whobad, sizeof buf);
+  badnick = strtok(buf, "!");
+
+  /* Get info about victim */
+  u2 = get_user_by_host(whovictim);
+  strncpyz(buf2, whovictim, sizeof buf2);
+  victim = strtok(buf2, "!");
+  mevictim = match_my_nick(victim);
+
+  /* Do we want to revenge? */
+  if (!want_to_revenge(chan, u, u2, badnick, victim, mevictim))
+    return;	/* No, leave them alone ... */
+
+  /* Haha! Do the vengeful thing ... */
+  punish_badguy(chan, whobad, u, badnick, victim, mevictim, type);
+}
+
+/* Set the key.
+ */
+static void set_key(struct chanset_t *chan, char *k)
+{
+  free(chan->channel.key);
+  if (k == NULL) {
+    chan->channel.key = calloc(1, 1);
+    return;
+  }
+  chan->channel.key = calloc(1, strlen(k) + 1);
+  strcpy(chan->channel.key, k);
+}
+
+static int hand_on_chan(struct chanset_t *chan, struct userrec *u)
+{
+  char s[UHOSTLEN];
+  memberlist *m;
+
+  for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+    sprintf(s, "%s!%s", m->nick, m->userhost);
+    if (u == get_user_by_host(s))
+      return 1;
+  }
+  return 0;
+}
+
+/* Adds a ban, exempt or invite mask to the list
+ * m should be chan->channel.(exempt|invite|ban)
+ */
+static void newmask(masklist *m, char *s, char *who)
+{
+  for (; m && m->mask[0] && irccmp(m->mask, s); m = m->next);
+  if (m->mask[0])
+    return;			/* Already existent mask */
+
+  m->next = calloc(1, sizeof(masklist));
+  m->next->next = NULL;
+  m->next->mask = calloc(1, 1);
+  m->next->mask[0] = 0;
+  free(m->mask);
+  m->mask = calloc(1, strlen(s) + 1);
+  strcpy(m->mask, s);
+  m->who = calloc(1, strlen(who) + 1);
+  strcpy(m->who, who);
+  m->timer = now;
+}
+
+/* Removes a nick from the channel member list (returns 1 if successful)
+ */
+static int killmember(struct chanset_t *chan, char *nick)
+{
+  memberlist *x, *old;
+
+  old = NULL;
+  for (x = chan->channel.member; x && x->nick[0]; old = x, x = x->next)
+    if (!irccmp(x->nick, nick))
+      break;
+  if (!x || !x->nick[0]) {
+    if (!channel_pending(chan))
+      putlog(LOG_MISC, "*", "(!) killmember(%s) -> nonexistent", nick);
+    return 0;
+  }
+  if (old)
+    old->next = x->next;
+  else
+    chan->channel.member = x->next;
+  free(x);
+  chan->channel.members--;
+
+  /* The following two errors should NEVER happen. We will try to correct
+   * them though, to keep the bot from crashing.
+   */
+  if (chan->channel.members < 0) {
+     putlog(LOG_MISC, "*", "(!) BUG: number of members is negative: %d",
+	    chan->channel.members);
+     chan->channel.members = 0;
+     for (x = chan->channel.member; x && x->nick[0]; x = x->next)
+       chan->channel.members++;
+     putlog(LOG_MISC, "*", "(!) actually I know of %d members.",
+	    chan->channel.members);
+  }
+  if (!chan->channel.member) {
+    putlog(LOG_MISC, "*", "(!) BUG: memberlist is NULL");
+    chan->channel.member = calloc(1, sizeof(memberlist));
+    chan->channel.member->nick[0] = 0;
+    chan->channel.member->next = NULL;
+  }
+  return 1;
+}
+
+/* Check if I am a chanop. Returns boolean 1 or 0.
+ */
+static int me_op(struct chanset_t *chan)
+{
+  memberlist *mx = NULL;
+
+  mx = ismember(chan, botname);
+  if (!mx)
+    return 0;
+  if (chan_hasop(mx))
+    return 1;
+  else
+    return 0;
+}
+
+/* Check if there are any ops on the channel. Returns boolean 1 or 0.
+ */
+static int any_ops(struct chanset_t *chan)
+{
+  memberlist *x;
+
+  for (x = chan->channel.member; x && x->nick[0]; x = x->next)
+    if (chan_hasop(x))
+      break;
+  if (!x || !x->nick[0])
+    return 0;
+  return 1;
+}
+
+/* Check whether I'm voice. Returns boolean 1 or 0.
+ */
+static int me_voice(struct chanset_t *chan)
+{
+  memberlist	*mx;
+
+  mx = ismember(chan, botname);
+  if (!mx)
+    return 0;
+  if (chan_hasvoice(mx))
+    return 1;
+  else
+    return 0;
+}
+
+/* Reset the channel information.
+ */
+static void reset_chan_info(struct chanset_t *chan)
+{
+  /* Don't reset the channel if we're already resetting it */
+  if (channel_inactive(chan)) {
+    dprintf(DP_MODE,"PART %s\n", chan->name);
+    return;
+  }
+  if (!channel_pending(chan)) {
+    free(chan->channel.key);
+    chan->channel.key = calloc(1, 1);
+    clear_channel(chan, 1);
+    chan->status |= CHAN_PEND;
+    chan->status &= ~(CHAN_ACTIVE | CHAN_ASKEDMODES);
+    if (!(chan->status & CHAN_ASKEDBANS)) {
+      chan->status |= CHAN_ASKEDBANS;
+      dprintf(DP_MODE, "MODE %s +b\n", chan->name);
+    }
+    if (!(chan->ircnet_status & CHAN_ASKED_EXEMPTS) &&
+	use_exempts == 1) {
+      chan->ircnet_status |= CHAN_ASKED_EXEMPTS;
+      dprintf(DP_MODE, "MODE %s +e\n", chan->name);
+    }
+    if (!(chan->ircnet_status & CHAN_ASKED_INVITED) &&
+	use_invites == 1) {
+      chan->ircnet_status |= CHAN_ASKED_INVITED;
+      dprintf(DP_MODE, "MODE %s +I\n", chan->name);
+    }
+    /* These 2 need to get out asap, so into the mode queue */
+    dprintf(DP_MODE, "MODE %s\n", chan->name);
+    if (use_354)
+      dprintf(DP_MODE, "WHO %s %%c%%h%%n%%u%%f\n", chan->name);
+    else
+      dprintf(DP_MODE, "WHO %s\n", chan->name);
+    /* clear_channel nuked the data...so */
+  }
+}
+
+/* Leave the specified channel and notify registered Tcl procs. This
+ * should not be called by itsself.
+ */
+static void do_channel_part(struct chanset_t *chan)
+{
+  if (!channel_inactive(chan) && chan->name[0]) {
+    /* Using chan->name is important here, especially for !chans <cybah> */
+    dprintf(DP_SERVER, "PART %s\n", chan->name);
+
+    /* As we don't know of this channel anymore when we receive the server's
+       ack for the above PART, we have to notify about it _now_. */
+    check_tcl_part(botname, botuserhost, NULL, chan->dname, NULL);
+  }
+}
+
+/* Report the channel status of every active channel to dcc chat every
+ * 5 minutes.
+ */
+static void status_log()
+{
+  masklist *b;
+  memberlist *m;
+  struct chanset_t *chan;
+  char s[20], s2[20];
+  int chops, voice, nonops, bans, invites, exempts;
+
+  for (chan = chanset; chan != NULL; chan = chan->next) {
+    if (channel_active(chan) && channel_logstatus(chan) &&
+        !channel_inactive(chan)) {
+      chops = 0;
+      voice = 0;
+      for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+	if (chan_hasop(m))
+	  chops++;
+	else if (chan_hasvoice(m))
+	  voice++;
+      }
+      nonops = (chan->channel.members - (chops + voice));
+
+      for (bans = 0, b = chan->channel.ban; b->mask[0]; b = b->next)
+	bans++;
+      for (exempts = 0, b = chan->channel.exempt; b->mask[0]; b = b->next)
+	exempts++;
+      for (invites = 0, b = chan->channel.invite; b->mask[0]; b = b->next)
+	invites++;
+
+      sprintf(s, "%d", exempts);
+      sprintf(s2, "%d", invites);
+
+      putlog(LOG_MISC, chan->dname,
+	     "%s%-10s (%s) : [m/%d o/%d v/%d n/%d b/%d e/%s I/%s]",
+             me_op(chan) ? "@" : me_voice(chan) ? "+" : "", chan->dname,
+             getchanmode(chan), chan->channel.members, chops, voice, nonops,
+	     bans, use_exempts ? s : "-", use_invites ? s2 : "-");
+    }
+  }
+}
+
+/* If i'm the only person on the channel, and i'm not op'd,
+ * might as well leave and rejoin.
+ */
+static void check_lonely_channel(struct chanset_t *chan)
+{
+  memberlist *m;
+  int i = 0;
+
+  if (channel_pending(chan) || !channel_active(chan) || me_op(chan) ||
+      channel_inactive(chan) || (chan->channel.mode & CHANANON))
+    return;
+  /* Count non-split channel members */
+  for (m = chan->channel.member; m && m->nick[0]; m = m->next)
+    if (!chan_issplit(m))
+      i++;
+  if (i == 1 && channel_cycle(chan) && !channel_stop_cycle(chan) && (chan->name[0] != '+')) {
+    putlog(LOG_MISC, "*", "Trying to cycle %s to regain ops.", chan->dname);
+    dprintf(DP_MODE, "PART %s\n", chan->name);
+    /* If it's a !chan, we need to recreate the channel with !!chan <cybah> */
+    dprintf(DP_MODE, "JOIN %s%s %s\n", (chan->dname[0] == '!') ? "!" : "",
+	    chan->dname, chan->key_prot);
+  } else 
+    check_tcl_need(chan->dname, "cycle");
+}
+
+static void check_expired_chanstuff()
+{
+  masklist *b, *e;
+  memberlist *m, *n;
+  char s[UHOSTLEN];
+  struct chanset_t *chan;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  if (!server_online)
+    return;
+  for (chan = chanset; chan; chan = chan->next) {
+    if (channel_active(chan)) {
+      if (me_op(chan)) {
+	if (channel_dynamicbans(chan) && ban_time)
+	  for (b = chan->channel.ban; b->mask[0]; b = b->next)
+	    if (now - b->timer > 60 * ban_time &&
+		!u_sticky_mask(chan->bans, b->mask) &&
+		!u_sticky_mask(global_bans, b->mask) &&
+		expired_mask(chan, b->who)) {
+	      putlog(LOG_MODES, chan->dname,
+		     "(%s) Channel ban on %s expired.",
+		     chan->dname, b->mask);
+	      add_mode(chan, '-', 'b', b->mask);
+	      b->timer = now;
+	    }
+
+	if (use_exempts && channel_dynamicexempts(chan) && exempt_time)
+	  for (e = chan->channel.exempt; e->mask[0]; e = e->next)
+	    if (now - e->timer > 60 * exempt_time &&
+		!u_sticky_mask(chan->exempts, e->mask) &&
+		!u_sticky_mask(global_exempts, e->mask) &&
+		expired_mask(chan, e->who)) {
+	      /* Check to see if it matches a ban */
+	      int match = 0;
+
+	      for (b = chan->channel.ban; b->mask[0]; b = b->next)
+		if (wild_match(b->mask, e->mask) ||
+		    wild_match(e->mask, b->mask)) {
+		  match = 1;
+		  break;
+	      }
+	      /* Leave this extra logging in for now. Can be removed later
+	       * Jason
+	       */
+	      if (match) {
+		putlog(LOG_MODES, chan->dname,
+		       "(%s) Channel exemption %s NOT expired. Exempt still set!",
+		       chan->dname, e->mask);
+	      } else {
+		putlog(LOG_MODES, chan->dname,
+		       "(%s) Channel exemption on %s expired.",
+		       chan->dname, e->mask);
+		add_mode(chan, '-', 'e', e->mask);
+	      }
+	      e->timer = now;
+	    }
+
+	if (use_invites && channel_dynamicinvites(chan) &&
+	    invite_time && !(chan->channel.mode & CHANINV))
+	  for (b = chan->channel.invite; b->mask[0]; b = b->next)
+	    if (now - b->timer > 60 * invite_time &&
+		!u_sticky_mask(chan->invites, b->mask) &&
+		!u_sticky_mask(global_invites, b->mask) &&
+		expired_mask(chan, b->who)) {
+	      putlog(LOG_MODES, chan->dname,
+		     "(%s) Channel invitation on %s expired.",
+		     chan->dname, b->mask);
+	      add_mode(chan, '-', 'I', b->mask);
+	      b->timer = now;
+	    }
+
+	if (chan->idle_kick)
+	  for (m = chan->channel.member; m && m->nick[0]; m = m->next)
+	    if (now - m->last >= chan->idle_kick * 60 &&
+		!match_my_nick(m->nick) && !chan_issplit(m)) {
+	      sprintf(s, "%s!%s", m->nick, m->userhost);
+	      get_user_flagrec(m->user ? m->user : get_user_by_host(s),
+			       &fr, chan->dname);
+	      if (!(glob_bot(fr) || glob_friend(fr) ||
+		    (glob_op(fr) && !chan_deop(fr)) ||
+		    chan_friend(fr) || chan_op(fr))) {
+		dprintf(DP_SERVER, "KICK %s %s :idle %d min\n", chan->name,
+			m->nick, chan->idle_kick);
+		m->flags |= SENTKICK;
+	      }
+	    }
+      }
+      for (m = chan->channel.member; m && m->nick[0]; m = n) {
+	n = m->next;
+	if (m->split && now - m->split > wait_split) {
+	  sprintf(s, "%s!%s", m->nick, m->userhost);
+	  check_tcl_sign(m->nick, m->userhost,
+			 m->user ? m->user : get_user_by_host(s),
+			 chan->dname, "lost in the netsplit");
+	  putlog(LOG_JOIN, chan->dname,
+		 "%s (%s) got lost in the net-split.",
+		 m->nick, m->userhost);
+	  killmember(chan, m->nick);
+	}
+	m = n;
+      }
+      check_lonely_channel(chan);
+    }
+    else if (!channel_inactive(chan) && !channel_pending(chan))
+      dprintf(DP_MODE, "JOIN %s %s\n",
+              (chan->name[0]) ? chan->name : chan->dname,
+              chan->channel.key[0] ? chan->channel.key : chan->key_prot);
+  }
+}
+
+static void check_tcl_joinspltrejn(char *nick, char *uhost, struct userrec *u,
+			       char *chname, bind_table_t *table)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  char args[1024];
+
+  simple_sprintf(args, "%s %s!%s", chname, nick, uhost);
+  get_user_flagrec(u, &fr, chname);
+
+  check_bind(table, args, &fr, nick, uhost, u, chname);
+}
+
+/* we handle part messages now *sigh* (guppy 27Jan2000) */
+
+static void check_tcl_part(char *nick, char *uhost, struct userrec *u,
+			       char *chname, char *text)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  char args[1024];
+
+  simple_sprintf(args, "%s %s!%s", chname, nick, uhost);
+  get_user_flagrec(u, &fr, chname);
+
+  check_bind(BT_part, args, &fr, nick, uhost, u, chname, text);
+}
+
+static void check_tcl_signtopcnick(char *nick, char *uhost, struct userrec *u,
+				   char *chname, char *reason,
+				   bind_table_t *table)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  char args[1024];
+
+  if (table == BT_quit) {
+    simple_sprintf(args, "%s %s!%s", chname, nick, uhost);
+  }
+  else {
+    simple_sprintf(args, "%s %s", chname, reason);
+  }
+  get_user_flagrec(u, &fr, chname);
+  check_bind(table, args, &fr, nick, uhost, u, chname, reason);
+}
+
+static void check_tcl_kickmode(char *nick, char *uhost, struct userrec *u,
+			       char *chname, char *dest, char *reason,
+			       bind_table_t *table)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  char args[1024];
+
+  get_user_flagrec(u, &fr, chname);
+  if (table == BT_mode) {
+    simple_sprintf(args, "%s %s", chname, dest);
+  }
+  else {
+    simple_sprintf(args, "%s %s %s", chname, dest, reason);
+  }
+  check_bind(table, args, &fr, nick, uhost, u, chname, dest, reason);
+}
+
+static int check_tcl_pub(char *nick, char *from, char *chname, char *msg)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  int x;
+  char *cmd, *text, host[161];
+  struct userrec *u;
+
+  text = msg;
+  cmd = newsplit(&text);
+  simple_sprintf(host, "%s!%s", nick, from);
+  u = get_user_by_host(host);
+  get_user_flagrec(u, &fr, chname);
+
+  x = check_bind(BT_pub, cmd, &fr, nick, from, u, chname, text);
+
+  if (x & BIND_RET_LOG) {
+    putlog(LOG_CMDS, chname, "<<%s>> !%s! %s %s", nick, u ? u->handle : "*", cmd, text);
+  }
+  /* This should work.. undoes the "newsplit" */
+  if (text > cmd) *(text-1) = ' ';
+  if (x & BIND_RET_BREAK) return(1);
+  return(0);
+}
+
+static void check_tcl_pubm(char *nick, char *from, char *chname, char *msg)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  char buf[1024], host[161];
+  struct userrec *u;
+
+  simple_sprintf(buf, "%s %s", chname, msg);
+  simple_sprintf(host, "%s!%s", nick, from);
+  u = get_user_by_host(host);
+  get_user_flagrec(u, &fr, chname);
+
+  check_bind(BT_pubm, buf, &fr, nick, from, u, chname, msg);
+}
+
+static void check_tcl_need(char *chname, char *type)
+{
+  char buf[1024];
+
+  simple_sprintf(buf, "%s %s", chname, type);
+  check_bind(BT_need, buf, NULL, chname, type);
+}
+
+static tcl_ints myints[] =
+{
+  {"learn-users",		&learn_users,		0},	/* arthur2 */
+  {"wait-split",		&wait_split,		0},
+  {"wait-info",			&wait_info,		0},
+  {"bounce-bans",		&bounce_bans,		0},
+  {"bounce-exempts",		&bounce_exempts,	0},
+  {"bounce-invites",		&bounce_invites,	0},
+  {"bounce-modes",		&bounce_modes,		0},
+  {"modes-per-line",		&modesperline,		0},
+  {"mode-buf-length",		&mode_buf_len,		0},
+  {"use-354",			&use_354,		0},
+  {"kick-method",		&kick_method,		0},
+  {"kick-fun",			&kick_fun,		0},
+  {"ban-fun",			&ban_fun,		0},
+  {"invite-key",		&invite_key,		0},
+  {"no-chanrec-info",		&no_chanrec_info,	0},
+  {"max-bans",			&max_bans,		0},
+  {"max-exempts",		&max_exempts,		0},
+  {"max-invites",		&max_invites,		0},
+  {"max-modes",			&max_modes,		0},
+  {"net-type",			&net_type,		0},
+  {"strict-host",		&strict_host,		0},	/* arthur2 */
+  {"ctcp-mode",			&ctcp_mode,		0},	/* arthur2 */
+  {"keep-nick",			&keepnick,		0},	/* guppy */
+  {"prevent-mixing",		&prevent_mixing,	0},
+  {"rfc-compliant",		&rfc_compliant,		0},
+  {"include-lk",		&include_lk,		0},
+  {NULL,			NULL,			0}	/* arthur2 */
+};
+
+/* Flush the modes for EVERY channel.
+ */
+static void flush_modes()
+{
+  struct chanset_t *chan;
+  memberlist *m;
+
+  for (chan = chanset; chan; chan = chan->next) {
+    for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+      if (m->delay && m->delay <= now) {
+	m->delay = 0L;
+	m->flags &= ~FULL_DELAY;
+        if (chan_sentop(m)) {
+          m->flags &= ~SENTOP;
+          add_mode(chan, '+', 'o', m->nick);
+        }
+        if (chan_sentvoice(m)) {
+          m->flags &= ~SENTVOICE;
+          add_mode(chan, '+', 'v', m->nick);
+        }
+      }
+    }
+    flush_mode(chan, NORMAL);
+  }
+}
+
+static void irc_report(int idx, int details)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  char ch[1024], q[160], *p;
+  int k, l;
+  struct chanset_t *chan;
+
+  strcpy(q, "Channels: ");
+  k = 10;
+  for (chan = chanset; chan; chan = chan->next) {
+    if (idx != DP_STDOUT)
+      get_user_flagrec(dcc[idx].user, &fr, chan->dname);
+    if (idx == DP_STDOUT || glob_master(fr) || chan_master(fr)) {
+      p = NULL;
+      if (!channel_inactive(chan)) {
+	if (chan->status & CHAN_JUPED)
+	  p = _("juped");
+	else if (!(chan->status & CHAN_ACTIVE))
+	  p = _("trying");
+	else if (chan->status & CHAN_PEND)
+	  p = _("pending");
+        else if (!any_ops(chan))
+          p = _("opless");
+	else if (!me_op(chan))
+	  p = _("want ops!");
+      }
+      l = simple_sprintf(ch, "%s%s%s%s, ", chan->dname, p ? "(" : "",
+			 p ? p : "", p ? ")" : "");
+      if ((k + l) > 70) {
+	dprintf(idx, "   %s\n", q);
+	strcpy(q, "          ");
+	k = 10;
+      }
+      k += my_strcpy(q + k, ch);
+    }
+  }
+  if (k > 10) {
+    q[k - 2] = 0;
+    dprintf(idx, "    %s\n", q);
+  }
+}
+
+static void do_nettype()
+{
+  switch (net_type) {
+  case 0:		/* Efnet */
+    kick_method = 1;
+    modesperline = 4;
+    use_354 = 0;
+    use_exempts = 0;
+    use_invites = 0;
+    rfc_compliant = 1;
+    include_lk = 0;
+    break;
+  case 1:		/* Ircnet */
+    kick_method = 4;
+    modesperline = 3;
+    use_354 = 0;
+    use_exempts = 1;
+    use_invites = 1;
+    rfc_compliant = 1;
+    include_lk = 1;
+    break;
+  case 2:		/* Undernet */
+    kick_method = 1;
+    modesperline = 6;
+    use_354 = 1;
+    use_exempts = 0;
+    use_invites = 0;
+    rfc_compliant = 1;
+    include_lk = 1;
+    break;
+  case 3:		/* Dalnet */
+    kick_method = 1;
+    modesperline = 6;
+    use_354 = 0;
+    use_exempts = 0;
+    use_invites = 0;
+    rfc_compliant = 0;
+    include_lk = 1;
+    break;
+  case 4:		/* hybrid-6+ */
+    kick_method = 1;
+    modesperline = 4;
+    use_354 = 0;
+    use_exempts = 1;
+    use_invites = 0;
+    rfc_compliant = 1;
+    include_lk = 0;
+    break;
+  default:
+    break;
+  }
+  /* Update irccmp function pointers */
+  add_hook(HOOK_IRCCMP, (Function) rfc_compliant);
+}
+
+static char *traced_nettype(ClientData cdata, Tcl_Interp *irp, char *name1,
+			    char *name2, int flags)
+{
+  do_nettype();
+  return NULL;
+}
+
+static char *traced_rfccompliant(ClientData cdata, Tcl_Interp *irp,
+				 char *name1, char *name2, int flags)
+{
+  /* This hook forces eggdrop core to change the irccmp match
+   * function links to point to the rfc compliant versions if
+   * rfc_compliant is 1, or to the normal version if it's 0.
+   */
+  add_hook(HOOK_IRCCMP, (Function) rfc_compliant);
+  return NULL;
+}
+
+static char *irc_close()
+{
+  struct chanset_t *chan;
+
+  dprintf(DP_MODE, "JOIN 0\n");
+  for (chan = chanset; chan; chan = chan->next)
+    clear_channel(chan, 1);
+  del_bind_table2(BT_topic);
+  del_bind_table2(BT_split);
+  del_bind_table2(BT_quit);
+  del_bind_table2(BT_rejoin);
+  del_bind_table2(BT_part);
+  del_bind_table2(BT_nick);
+  del_bind_table2(BT_mode);
+  del_bind_table2(BT_kick);
+  del_bind_table2(BT_join);
+  del_bind_table2(BT_pubm);
+  del_bind_table2(BT_pub);
+  del_bind_table2(BT_need);
+  rem_tcl_ints(myints);
+
+  if (BT_dcc) rem_builtins2(BT_dcc, irc_dcc);
+  if (BT_raw) rem_builtins2(BT_raw, irc_raw);
+  if (BT_msg) rem_builtins2(BT_msg, C_msg);
+
+  rem_tcl_commands(tclchan_cmds);
+  rem_help_reference("irc.help");
+  del_hook(HOOK_MINUTELY, (Function) check_expired_chanstuff);
+  del_hook(HOOK_5MINUTELY, (Function) status_log);
+  del_hook(HOOK_ADD_MODE, (Function) real_add_mode);
+  del_hook(HOOK_IDLE, (Function) flush_modes);
+  Tcl_UntraceVar(interp, "rfc-compliant",
+		 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+		 traced_rfccompliant, NULL);
+  Tcl_UntraceVar(interp, "net-type",
+		 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+		 traced_nettype, NULL);
+  module_undepend(MODULE_NAME);
+  return NULL;
+}
+
+EXPORT_SCOPE char *start();
+
+static Function irc_table[] =
+{
+  /* 0 - 3 */
+  (Function) start,
+  (Function) irc_close,
+  (Function) 0,
+  (Function) irc_report,
+  /* 4 - 7 */
+  (Function) recheck_channel,
+  (Function) me_op,
+  (Function) recheck_channel_modes,
+  (Function) do_channel_part,
+  /* 8 - 11 */
+  (Function) check_this_ban
+};
+
+char *start(Function * global_funcs)
+{
+  struct chanset_t *chan;
+
+  global = global_funcs;
+
+  module_register(MODULE_NAME, irc_table, 1, 3);
+  if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+    module_undepend(MODULE_NAME);
+    return "This module needs eggdrop1.7.0 or later";
+  }
+  if (!(server_funcs = module_depend(MODULE_NAME, "server", 1, 0))) {
+    module_undepend(MODULE_NAME);
+    return "You need the server module to use the irc module.";
+  }
+  if (!(channels_funcs = module_depend(MODULE_NAME, "channels", 1, 0))) {
+    module_undepend(MODULE_NAME);
+    return "You need the channels module to use the irc module.";
+  }
+  for (chan = chanset; chan; chan = chan->next) {
+    if (!channel_inactive(chan))
+      dprintf(DP_MODE, "JOIN %s %s\n",
+              (chan->name[0]) ? chan->name : chan->dname, chan->key_prot);
+    chan->status &= ~(CHAN_ACTIVE | CHAN_PEND | CHAN_ASKEDBANS);
+    chan->ircnet_status &= ~(CHAN_ASKED_INVITED | CHAN_ASKED_EXEMPTS);
+  }
+  add_hook(HOOK_MINUTELY, (Function) check_expired_chanstuff);
+  add_hook(HOOK_5MINUTELY, (Function) status_log);
+  add_hook(HOOK_ADD_MODE, (Function) real_add_mode);
+  add_hook(HOOK_IDLE, (Function) flush_modes);
+  Tcl_TraceVar(interp, "net-type",
+	       TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+	       traced_nettype, NULL);
+  Tcl_TraceVar(interp, "rfc-compliant",
+	       TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+	       traced_rfccompliant, NULL);
+  add_tcl_ints(myints);
+
+  /* Import bind tables from other places. */
+  BT_dcc = find_bind_table2("dcc");
+  BT_raw = find_bind_table2("raw");
+  BT_msg = find_bind_table2("msg");
+  BT_ctcp = find_bind_table2("ctcp");
+  BT_ctcr = find_bind_table2("ctcr");
+
+  /* Add our commands to the imported tables. */
+  if (BT_dcc) add_builtins2(BT_dcc, irc_dcc);
+  else putlog(LOG_MISC, "*", "Couldn't load dcc bind table!");
+  if (BT_raw) add_builtins2(BT_raw, irc_raw);
+  else putlog(LOG_MISC, "*", "Couldn't load raw bind table!");
+  if (BT_msg) add_builtins2(BT_msg, C_msg);
+  else putlog(LOG_MISC, "*", "Couldn't load msg bind table!");
+
+  /* Create our own bind tables. */
+  BT_topic = add_bind_table2("topic", 5, "ssUss", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+  BT_split = add_bind_table2("split", 4, "ssUs", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+  BT_rejoin = add_bind_table2("rejoin", 4, "ssUs", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+  BT_quit = add_bind_table2("sign", 5, "ssUss", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+  BT_join = add_bind_table2("join", 4, "ssUs", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+  BT_part = add_bind_table2("part", 5, "ssUss", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+  BT_nick = add_bind_table2("nick", 5, "ssUss", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+  BT_mode = add_bind_table2("mode", 6, "ssUsss", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+  BT_kick = add_bind_table2("kick", 6, "ssUsss", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+  BT_need = add_bind_table2("need", 2, "ss", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+  BT_pub = add_bind_table2("pub", 5, "ssUss", 0, BIND_USE_ATTR);
+  BT_pubm = add_bind_table2("pubm", 5, "ssUss", MATCH_MASK, BIND_STACKABLE | BIND_USE_ATTR);
+
+  add_tcl_commands(tclchan_cmds);
+  add_help_reference("irc.help");
+  do_nettype();
+  return NULL;
+}
Index: eggdrop1.7/modules/irc/irc.h
diff -u /dev/null eggdrop1.7/modules/irc/irc.h:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/irc/irc.h	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,100 @@
+/*
+ * irc.h -- part of irc.mod
+ *
+ * $Id: irc.h,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_IRC_IRC_H
+#define _EGG_MOD_IRC_IRC_H
+
+#define check_tcl_join(a,b,c,d) check_tcl_joinspltrejn(a,b,c,d,BT_join)
+#define check_tcl_splt(a,b,c,d) check_tcl_joinspltrejn(a,b,c,d,BT_split)
+#define check_tcl_rejn(a,b,c,d) check_tcl_joinspltrejn(a,b,c,d,BT_rejoin)
+#define check_tcl_sign(a,b,c,d,e) check_tcl_signtopcnick(a,b,c,d,e,BT_quit)
+#define check_tcl_topc(a,b,c,d,e) check_tcl_signtopcnick(a,b,c,d,e,BT_topic)
+#define check_tcl_nick(a,b,c,d,e) check_tcl_signtopcnick(a,b,c,d,e,BT_nick)
+#define check_tcl_mode(a,b,c,d,e,f) check_tcl_kickmode(a,b,c,d,e,f,BT_mode)
+#define check_tcl_kick(a,b,c,d,e,f) check_tcl_kickmode(a,b,c,d,e,f,BT_kick)
+
+#define REVENGE_KICK 1		/* Kicked victim	*/
+#define REVENGE_DEOP 2		/* Took op		*/
+
+#ifdef MAKING_IRC
+static void check_tcl_need(char *, char *);
+static void check_tcl_kickmode(char *, char *, struct userrec *, char *,
+			       char *, char *, bind_table_t *);
+static void check_tcl_joinspltrejn(char *, char *, struct userrec *, char *,
+			       bind_table_t *);
+static void check_tcl_part(char *, char *, struct userrec *, char *, char *);
+static void check_tcl_signtopcnick(char *, char *, struct userrec *u, char *,
+				   char *, bind_table_t *);
+static void check_tcl_pubm(char *, char *, char *, char *);
+static int check_tcl_pub(char *, char *, char *, char *);
+static int me_op(struct chanset_t *);
+static int any_ops(struct chanset_t *);
+static int hand_on_chan(struct chanset_t *, struct userrec *);
+static char *getchanmode(struct chanset_t *);
+static void flush_mode(struct chanset_t *, int);
+
+/* reset(bans|exempts|invites) are now just macros that call resetmasks
+ * in order to reduce the code duplication. <cybah>
+ */
+#define resetbans(chan)	    resetmasks((chan), (chan)->channel.ban,	\
+				       (chan)->bans, global_bans, 'b')
+#define resetexempts(chan)  resetmasks((chan), (chan)->channel.exempt,	\
+				       (chan)->exempts, global_exempts, 'e')
+#define resetinvites(chan)  resetmasks((chan), (chan)->channel.invite,	\
+				       (chan)->invites, global_invites, 'I')
+
+static void reset_chan_info(struct chanset_t *);
+static void recheck_channel(struct chanset_t *, int);
+static void set_key(struct chanset_t *, char *);
+static void maybe_revenge(struct chanset_t *, char *, char *, int);
+static int detect_chan_flood(char *, char *, char *, struct chanset_t *, int,
+			     char *);
+static void newmask(masklist *, char *, char *);
+static char *quickban(struct chanset_t *, char *);
+static void got_op(struct chanset_t *chan, char *nick, char *from, char *who,
+ 		   struct userrec *opu, struct flag_record *opper);
+static int killmember(struct chanset_t *chan, char *nick);
+static void check_lonely_channel(struct chanset_t *chan);
+static int gotmode(char *, char *, char *);
+
+#define newban(chan, mask, who)         newmask((chan)->channel.ban, mask, who)
+#define newexempt(chan, mask, who)      newmask((chan)->channel.exempt, mask, \
+						who)
+#define newinvite(chan, mask, who)      newmask((chan)->channel.invite, mask, \
+						who)
+
+#else
+
+/* 4-7 */
+#define recheck_channel ((void(*)(struct chanset_t *,int))irc_funcs[4])
+#define me_op ((int(*)(struct chanset_t *))irc_funcs[5])
+#define recheck_channel_modes ((void(*)(struct chanset_t *))irc_funcs[6])
+#define do_channel_part ((void(*)(struct chanset_t *))irc_funcs[7])
+
+/* 8-11 */
+#define check_this_ban ((void(*)(struct chanset_t *,char *,int))irc_funcs[8])
+
+#endif				/* MAKING_IRC */
+
+#endif				/* _EGG_MOD_IRC_IRC_H */
Index: eggdrop1.7/modules/irc/mode.c
diff -u /dev/null eggdrop1.7/modules/irc/mode.c:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/irc/mode.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,1142 @@
+/*
+ * mode.c -- part of irc.mod
+ *   queueing and flushing mode changes made by the bot
+ *   channel mode changes and the bot's reaction to them
+ *   setting and getting the current wanted channel modes
+ *
+ * $Id: mode.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/* Reversing this mode? */
+static int reversing = 0;
+
+#define PLUS    1
+#define MINUS   2
+#define CHOP    4
+#define BAN     8
+#define VOICE   16
+#define EXEMPT  32
+#define INVITE  64
+
+static struct flag_record user   = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+static struct flag_record victim = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+static void flush_mode(struct chanset_t *chan, int pri)
+{
+  char		*p, out[512], post[512];
+  size_t	postsize = sizeof(post);
+  int		i, plus = 2;		/* 0 = '-', 1 = '+', 2 = none */
+
+  p = out;
+  post[0] = 0, postsize--;
+
+  if (chan->mns[0]) {
+    *p++ = '-', plus = 0;
+    for (i = 0; i < strlen(chan->mns); i++)
+      *p++ = chan->mns[i];
+    chan->mns[0] = 0;
+  }
+
+  if (chan->pls[0]) {
+    *p++ = '+', plus = 1;
+    for (i = 0; i < strlen(chan->pls); i++)
+      *p++ = chan->pls[i];
+    chan->pls[0] = 0;
+  }
+
+  chan->bytes = 0;
+  chan->compat = 0;
+
+  /* +k or +l ? */
+  if (chan->key) {
+    if (plus != 1) {
+      *p++ = '+', plus = 1;
+    }
+    *p++ = 'k';
+
+    postsize -= egg_strcatn(post, chan->key, sizeof(post));
+    postsize -= egg_strcatn(post, " ", sizeof(post));
+
+    free(chan->key), chan->key = NULL;
+  }
+
+  /* max +l is signed 2^32 on ircnet at least... so makesure we've got at least
+   * a 13 char buffer for '-2147483647 \0'. We'll be overwriting the existing
+   * terminating null in 'post', so makesure postsize >= 12.
+   */
+  if (chan->limit != 0 && postsize >= 12) {
+    if (plus != 1) {
+      *p++ = '+', plus = 1;
+    }
+    *p++ = 'l';
+
+    /* 'sizeof(post) - 1' is used because we want to overwrite the old null */
+    postsize -= sprintf(&post[(sizeof(post) - 1) - postsize], "%d ", chan->limit);
+
+    chan->limit = 0;
+  }
+
+  /* -k ? */
+  if (chan->rmkey) {
+    if (plus) {
+      *p++ = '-', plus = 0;
+    }
+    *p++ = 'k';
+
+    postsize -= egg_strcatn(post, chan->rmkey, sizeof(post));
+    postsize -= egg_strcatn(post, " ", sizeof(post));
+
+    free(chan->rmkey), chan->rmkey = NULL;
+  }
+
+  /* Do -{b,e,I} before +{b,e,I} to avoid the server ignoring overlaps */
+  for (i = 0; i < modesperline; i++) {
+    if (chan->cmode[i].type & MINUS && postsize > strlen(chan->cmode[i].op)) {
+      if (plus) {
+        *p++ = '-', plus = 0;
+      }
+
+      *p++ = ((chan->cmode[i].type & BAN) ? 'b' :
+              ((chan->cmode[i].type & CHOP) ? 'o' :
+               ((chan->cmode[i].type & EXEMPT) ? 'e' :
+                ((chan->cmode[i].type & INVITE) ? 'I' : 'v'))));
+
+      postsize -= egg_strcatn(post, chan->cmode[i].op, sizeof(post));
+      postsize -= egg_strcatn(post, " ", sizeof(post));
+
+      free(chan->cmode[i].op), chan->cmode[i].op = NULL;
+      chan->cmode[i].type = 0;
+    }
+  }
+
+  /* now do all the + modes... */
+  for (i = 0; i < modesperline; i++) {
+    if (chan->cmode[i].type & PLUS && postsize > strlen(chan->cmode[i].op)) {
+      if (plus != 1) {
+        *p++ = '+', plus = 1;
+      }
+
+      *p++ = ((chan->cmode[i].type & BAN) ? 'b' :
+              ((chan->cmode[i].type & CHOP) ? 'o' :
+               ((chan->cmode[i].type & EXEMPT) ? 'e' :
+                ((chan->cmode[i].type & INVITE) ? 'I' : 'v'))));
+
+      postsize -= egg_strcatn(post, chan->cmode[i].op, sizeof(post));
+      postsize -= egg_strcatn(post, " ", sizeof(post));
+
+      free(chan->cmode[i].op), chan->cmode[i].op = NULL;
+      chan->cmode[i].type = 0;
+    }
+  }
+
+  /* remember to terminate the buffer ('out')... */
+  *p = 0;
+
+  if (post[0]) {
+    /* remove the trailing space... */
+    size_t index = (sizeof(post) - 1) - postsize;
+    if (index > 0 && post[index - 1] == ' ')
+      post[index - 1] = 0;
+
+    egg_strcatn(out, " ", sizeof(out));
+    egg_strcatn(out, post, sizeof(out));
+  }
+  if (out[0]) {
+    if (pri == QUICK)
+      dprintf(DP_MODE, "MODE %s %s\n", chan->name, out);
+    else
+      dprintf(DP_SERVER, "MODE %s %s\n", chan->name, out);
+  }
+}
+
+/* Queue a channel mode change
+ */
+static void real_add_mode(struct chanset_t *chan,
+			  char plus, char mode, char *op)
+{
+  int i, type, modes, l;
+  masklist *m;
+  memberlist *mx;
+  char s[21];
+
+  if (!me_op(chan))
+    return;			/* No point in queueing the mode */
+
+  if (mode == 'o' || mode == 'v') {
+    mx = ismember(chan, op);
+    if (!mx)
+      return;
+
+    if (plus == '-' && mode == 'o') {
+      if (chan_sentdeop(mx) || !chan_hasop(mx))
+	return;
+      mx->flags |= SENTDEOP;
+    }
+    if (plus == '+' && mode == 'o') {
+      if (chan_sentop(mx) || chan_hasop(mx))
+	return;
+      mx->flags |= SENTOP;
+    }
+    if (plus == '-' && mode == 'v') {
+      if (chan_sentdevoice(mx) || !chan_hasvoice(mx))
+	return;
+      mx->flags |= SENTDEVOICE;
+    }
+    if (plus == '+' && mode == 'v') {
+      if (chan_sentvoice(mx) || chan_hasvoice(mx))
+	return;
+      mx->flags |= SENTVOICE;
+    }
+  }
+
+  if (chan->compat == 0) {
+    if (mode == 'e' || mode == 'I')
+      chan->compat = 2;
+    else
+      chan->compat = 1;
+  } else if (mode == 'e' || mode == 'I') {
+    if (prevent_mixing && chan->compat == 1)
+      flush_mode(chan, NORMAL);
+  } else if (prevent_mixing && chan->compat == 2)
+    flush_mode(chan, NORMAL);
+
+  if (mode == 'o' || mode == 'b' || mode == 'v' || mode == 'e' || mode == 'I') {
+    type = (plus == '+' ? PLUS : MINUS) |
+	   (mode == 'o' ? CHOP :
+	    (mode == 'b' ? BAN :
+	     (mode == 'v' ? VOICE :
+	      (mode == 'e' ? EXEMPT : INVITE))));
+
+    /*
+     * FIXME: Some networks remove overlapped bans, IrcNet does not
+     *        (poptix/drummer)
+     *
+     * Note:  On ircnet ischanXXX() should be used, otherwise isXXXed().
+     */
+    /* If removing a non-existant mask... */
+    if ((plus == '-' &&
+         ((mode == 'b' && !ischanban(chan, op)) ||
+          (mode == 'e' && !ischanexempt(chan, op)) ||
+          (mode == 'I' && !ischaninvite(chan, op)))) ||
+        /* or adding an existant mask... */
+        (plus == '+' &&
+         ((mode == 'b' && ischanban(chan, op)) ||
+          (mode == 'e' && ischanexempt(chan, op)) ||
+          (mode == 'I' && ischaninvite(chan, op)))))
+      return;	/* ...nuke it */
+
+    /* If there are already max_bans bans, max_exempts exemptions,
+     * max_invites invitations or max_modes +b/+e/+I modes on the
+     * channel, don't try to add one more.
+     */
+    if (plus == '+' && (mode == 'b' || mode == 'e' || mode == 'I')) {
+      int	bans = 0, exempts = 0, invites = 0;
+
+      for (m = chan->channel.ban; m && m->mask[0]; m = m->next)
+	bans++;
+      if (mode == 'b')
+	if (bans >= max_bans)
+	  return;
+
+      for (m = chan->channel.exempt; m && m->mask[0]; m = m->next)
+	exempts++;
+      if (mode == 'e')
+	if (exempts >= max_exempts)
+	  return;
+
+      for (m = chan->channel.invite; m && m->mask[0]; m = m->next)
+	invites++;
+      if (mode == 'I')
+	if (invites >= max_invites)
+	  return;
+
+      if (bans + exempts + invites >= max_modes)
+	return;
+    }
+
+    /* op-type mode change */
+    for (i = 0; i < modesperline; i++)
+      if (chan->cmode[i].type == type && chan->cmode[i].op != NULL &&
+	  !irccmp(chan->cmode[i].op, op))
+	return;			/* Already in there :- duplicate */
+    l = strlen(op) + 1;
+    if (chan->bytes + l > mode_buf_len)
+      flush_mode(chan, NORMAL);
+    for (i = 0; i < modesperline; i++)
+      if (chan->cmode[i].type == 0) {
+	chan->cmode[i].type = type;
+	chan->cmode[i].op = calloc(1, l);
+	chan->bytes += l;	/* Add 1 for safety */
+	strcpy(chan->cmode[i].op, op);
+	break;
+      }
+  }
+
+  /* +k ? store key */
+  else if (plus == '+' && mode == 'k') {
+    if (chan->key)
+      free(chan->key);
+    chan->key = calloc(1, strlen(op) + 1);
+    if (chan->key)
+      strcpy(chan->key, op);
+  }
+  /* -k ? store removed key */
+  else if (plus == '-' && mode == 'k') {
+    if (chan->rmkey)
+      free(chan->rmkey);
+    chan->rmkey = calloc(1, strlen(op) + 1);
+    if (chan->rmkey)
+      strcpy(chan->rmkey, op);
+  }
+  /* +l ? store limit */
+  else if (plus == '+' && mode == 'l')
+    chan->limit = atoi(op);
+  else {
+    /* Typical mode changes */
+    if (plus == '+')
+      strcpy(s, chan->pls);
+    else
+      strcpy(s, chan->mns);
+    if (!strchr(s, mode)) {
+      if (plus == '+') {
+	chan->pls[strlen(chan->pls) + 1] = 0;
+	chan->pls[strlen(chan->pls)] = mode;
+      } else {
+	chan->mns[strlen(chan->mns) + 1] = 0;
+	chan->mns[strlen(chan->mns)] = mode;
+      }
+    }
+  }
+  modes = modesperline;			/* Check for full buffer. */
+  for (i = 0; i < modesperline; i++)
+    if (chan->cmode[i].type)
+      modes--;
+  if (include_lk && chan->limit != -1)
+    modes--;
+  if (include_lk && chan->rmkey)
+    modes--;
+  if (include_lk && chan->key)
+    modes--;
+  if (modes < 0)
+    flush_mode(chan, NORMAL);		/* Full buffer! Flush modes. */
+}
+
+
+/*
+ *    Mode parsing functions
+ */
+
+static void got_key(struct chanset_t *chan, char *nick, char *from,
+		    char *key)
+{
+  if ((!nick[0]) && (bounce_modes))
+    reversing = 1;
+  if (((reversing) && !(chan->key_prot[0])) ||
+      ((chan->mode_mns_prot & CHANKEY) &&
+       !(glob_master(user) || glob_bot(user) || chan_master(user)))) {
+    if (strlen(key) != 0) {
+      add_mode(chan, '-', 'k', key);
+    } else {
+      add_mode(chan, '-', 'k', "");
+    }
+  }
+}
+
+static void got_op(struct chanset_t *chan, char *nick, char *from,
+		   char *who, struct userrec *opu, struct flag_record *opper)
+{
+  memberlist *m;
+  char s[UHOSTLEN];
+  struct userrec *u;
+  int check_chan = 0;
+  int snm = chan->stopnethack_mode;
+
+  m = ismember(chan, who);
+  if (!m) {
+    if (channel_pending(chan))
+      return;
+    putlog(LOG_MISC, chan->dname, _("* Mode change on %s for nonexistant %s!"), chan->dname, who);
+    dprintf(DP_MODE, "WHO %s\n", who);
+    return;
+  }
+  /* Did *I* just get opped? */
+  if (!me_op(chan) && match_my_nick(who))
+    check_chan = 1;
+
+  if (!m->user) {
+    simple_sprintf(s, "%s!%s", m->nick, m->userhost);
+    u = get_user_by_host(s);
+  } else
+    u = m->user;
+
+  get_user_flagrec(u, &victim, chan->dname);
+  /* Flags need to be set correctly right from the beginning now, so that
+   * add_mode() doesn't get irritated.
+   */
+  m->flags |= CHANOP;
+  check_tcl_mode(nick, from, opu, chan->dname, "+o", who);
+  /* Added new meaning of WASOP:
+   * in mode binds it means: was he op before get (de)opped
+   * (stupid IrcNet allows opped users to be opped again and
+   *  opless users to be deopped)
+   * script now can use [wasop nick chan] proc to check
+   * if user was op or wasnt  (drummer)
+   */
+  m->flags &= ~SENTOP;
+
+  if (channel_pending(chan))
+    return;
+
+  /* I'm opped, and the opper isn't me */
+  if (me_op(chan) && !match_my_nick(who) &&
+    /* and it isn't a server op */
+	   nick[0]) {
+    /* Channis is +bitch, and the opper isn't a global master or a bot */
+    if (channel_bitch(chan) && !(glob_master(*opper) || glob_bot(*opper)) &&
+	/* ... and the *opper isn't a channel master */
+	!chan_master(*opper) &&
+	/* ... and the oppee isn't global op/master/bot */
+	!(glob_op(victim) || glob_bot(victim)) &&
+	/* ... and the oppee isn't a channel op/master */
+	!chan_op(victim))
+      add_mode(chan, '-', 'o', who);
+      /* Opped is channel +d or global +d */
+    else if ((chan_deop(victim) ||
+		(glob_deop(victim) && !chan_op(victim))) &&
+	       !glob_master(*opper) && !chan_master(*opper))
+      add_mode(chan, '-', 'o', who);
+    else if (reversing)
+      add_mode(chan, '-', 'o', who);
+  } else if (reversing && !match_my_nick(who))
+    add_mode(chan, '-', 'o', who);
+  if (!nick[0] && me_op(chan) && !match_my_nick(who)) {
+    if (chan_deop(victim) || (glob_deop(victim) && !chan_op(victim))) {
+      m->flags |= FAKEOP;
+      add_mode(chan, '-', 'o', who);
+    } else if (snm > 0 && snm < 7 && !(m->delay) &&
+	       !glob_exempt(victim) && !chan_exempt(victim)) {
+      if (snm == 5) snm = channel_bitch(chan) ? 1 : 3;
+      if (snm == 6) snm = channel_bitch(chan) ? 4 : 2;
+      if (chan_wasoptest(victim) || glob_wasoptest(victim) ||
+      snm == 2) {
+        if (!chan_wasop(m)) {
+          m->flags |= FAKEOP;
+          add_mode(chan, '-', 'o', who);
+        }
+      } else if (!(chan_op(victim) ||
+		 (glob_op(victim) && !chan_deop(victim)))) {
+		  if (snm == 1 || snm == 4 || (snm == 3 && !chan_wasop(m))) {
+		    add_mode(chan, '-', 'o', who);
+		    m->flags |= FAKEOP;
+        }
+      } else if (snm == 4 && !chan_wasop(m)) {
+        add_mode(chan, '-', 'o', who);
+        m->flags |= FAKEOP;
+      }
+    }
+  }
+  m->flags |= WASOP;
+  if (check_chan)
+    recheck_channel(chan, 1);
+}
+
+static void got_deop(struct chanset_t *chan, char *nick, char *from,
+		     char *who, struct userrec *opu)
+{
+  memberlist *m;
+  char s[UHOSTLEN], s1[UHOSTLEN];
+  struct userrec *u;
+  int had_op;
+
+  m = ismember(chan, who);
+  if (!m) {
+    if (channel_pending(chan))
+      return;
+    putlog(LOG_MISC, chan->dname, _("* Mode change on %s for nonexistant %s!"), chan->dname, who);
+    dprintf(DP_MODE, "WHO %s\n", who);
+    return;
+  }
+
+  simple_sprintf(s, "%s!%s", m->nick, m->userhost);
+  simple_sprintf(s1, "%s!%s", nick, from);
+  u = get_user_by_host(s);
+  get_user_flagrec(u, &victim, chan->dname);
+
+  had_op = chan_hasop(m);
+  /* Flags need to be set correctly right from the beginning now, so that
+   * add_mode() doesn't get irritated.
+   */
+  m->flags &= ~(CHANOP | SENTDEOP | FAKEOP);
+  check_tcl_mode(nick, from, opu, chan->dname, "-o", who);
+  /* Check comments in got_op()  (drummer) */
+  m->flags &= ~WASOP;
+
+  if (channel_pending(chan))
+    return;
+
+  /* Deop'd someone on my oplist? */
+  if (me_op(chan)) {
+    int ok = 1;
+
+    if (glob_master(victim) || chan_master(victim))
+      ok = 0;
+    else if ((glob_op(victim) || glob_friend(victim)) && !chan_deop(victim))
+      ok = 0;
+    else if (chan_op(victim) || chan_friend(victim))
+      ok = 0;
+    if (!ok && !match_my_nick(nick) &&
+       irccmp(who, nick) && had_op &&
+	!match_my_nick(who)) {	/* added 25mar1996, robey */
+      /* Do we want to reop? */
+      /* Is the deopper NOT a master or bot? */
+      if (!glob_master(user) && !chan_master(user) && !glob_bot(user) &&
+	  /* and is the channel protectops? */
+	  ((channel_protectops(chan) &&
+	    /* and it's not +bitch ... */
+	    (!channel_bitch(chan) ||
+	    /* or the user's a valid op? */
+	     chan_op(victim) || (glob_op(victim) && !chan_deop(victim)))) ||
+	   /* or is the channel protectfriends? */
+           (channel_protectfriends(chan) &&
+	     /* and the users a valid friend? */
+             (chan_friend(victim) || (glob_friend(victim) &&
+				      !chan_deop(victim))))) &&
+       /* and the users not a de-op? */
+       !(chan_deop(victim) || (glob_deop(victim) && !chan_op(victim))))
+	/* Then we'll bless them */
+	add_mode(chan, '+', 'o', who);
+      else if (reversing)
+	add_mode(chan, '+', 'o', who);
+    }
+  }
+
+  if (!nick[0])
+    putlog(LOG_MODES, chan->dname, "TS resync (%s): %s deopped by %s",
+	   chan->dname, who, from);
+  /* Check for mass deop */
+  if (nick[0])
+    detect_chan_flood(nick, from, s1, chan, FLOOD_DEOP, who);
+  /* Having op hides your +v status -- so now that someone's lost ops,
+   * check to see if they have +v
+   */
+  if (!(m->flags & (CHANVOICE | STOPWHO))) {
+    dprintf(DP_HELP, "WHO %s\n", m->nick);
+    m->flags |= STOPWHO;
+  }
+  /* Was the bot deopped? */
+  if (match_my_nick(who)) {
+    /* Cancel any pending kicks and modes */
+    memberlist *m2;
+
+    for (m2 = chan->channel.member; m2 && m2->nick[0]; m2 = m2->next)
+	m2->flags &= ~(SENTKICK | SENTDEOP | SENTOP | SENTVOICE | SENTDEVOICE);
+
+    check_tcl_need(chan->dname, "op");
+
+    if (!nick[0])
+      putlog(LOG_MODES, chan->dname, "TS resync deopped me on %s :(",
+	     chan->dname);
+  }
+  if (nick[0])
+    maybe_revenge(chan, s1, s, REVENGE_DEOP);
+}
+
+
+static void got_ban(struct chanset_t *chan, char *nick, char *from,
+		    char *who)
+{
+  char me[UHOSTLEN], s[UHOSTLEN], s1[UHOSTLEN];
+  int check = 1;
+  memberlist *m;
+  struct userrec *u;
+
+  simple_sprintf(me, "%s!%s", botname, botuserhost);
+  simple_sprintf(s, "%s!%s", nick, from);
+  newban(chan, who, s);
+
+  if (channel_pending(chan))
+    return;
+
+  if (wild_match(who, me) && me_op(chan)) {
+    /* First of all let's check whether some luser banned us ++rtc */
+    if (match_my_nick(nick))
+      /* Bot banned itself -- doh! ++rtc */
+      putlog(LOG_MISC, "*", "Uh, banned myself on %s, reversing...",
+	     chan->dname);
+    reversing = 1;
+    check = 0;
+  } else if (!match_my_nick(nick)) {	/* It's not my ban */
+    if (channel_nouserbans(chan) && nick[0] && !glob_bot(user) &&
+	!glob_master(user) && !chan_master(user)) {
+      /* No bans made by users */
+      add_mode(chan, '-', 'b', who);
+      return;
+    }
+    /* Don't enforce a server ban right away -- give channel users a chance
+     * to remove it, in case it's fake
+     */
+    if (!nick[0]) {
+      check = 0;
+      if (bounce_modes)
+	reversing = 1;
+    }
+    /* Does this remotely match against any of our hostmasks?
+     * just an off-chance...
+     */
+    u = get_user_by_host(who);
+    if (u) {
+      get_user_flagrec(u, &victim, chan->dname);
+      if (glob_friend(victim) || (glob_op(victim) && !chan_deop(victim)) ||
+	  chan_friend(victim) || chan_op(victim)) {
+	if (!glob_master(user) && !glob_bot(user) && !chan_master(user))
+	  /* commented out: reversing = 1; */ /* arthur2 - 99/05/31 */
+	  check = 0;
+	if (glob_master(victim) || chan_master(victim))
+	  check = 0;
+      }
+    } else {
+      /* Banning an oplisted person who's on the channel? */
+      for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+	sprintf(s1, "%s!%s", m->nick, m->userhost);
+	if (wild_match(who, s1)) {
+	  u = get_user_by_host(s1);
+	  if (u) {
+	    get_user_flagrec(u, &victim, chan->dname);
+	    if (glob_friend(victim) ||
+		(glob_op(victim) && !chan_deop(victim))
+		|| chan_friend(victim) || chan_op(victim)) {
+	      /* Remove ban on +o/f/m user, unless placed by another +m/b */
+	      if (!glob_master(user) && !glob_bot(user) &&
+		  !chan_master(user)) {
+		if (!isexempted(chan, s1)) {
+		  /* Crotale - if the victim is not +e, then ban is removed */
+		  add_mode(chan, '-', 'b', who);
+		  check = 0;
+		}
+	      }
+	      if (glob_master(victim) || chan_master(victim))
+		check = 0;
+	    }
+	  }
+	}
+      }
+    }
+  }
+  /* If a ban is set on an exempted user then we might as well set exemption
+   * at the same time.
+   */
+  refresh_exempt(chan,who);
+  /* Does the ban match anyone in the internal banlist in that case
+   * use the stored ban reason when kicking
+   */
+  if (check && channel_enforcebans(chan)) {
+    register maskrec *b;
+    int cycle;
+    char resn[512];
+    for (cycle = 0; cycle < 2; cycle++) {
+      for (b = cycle ? chan->bans : global_bans; b; b = b->next) {
+	if (wild_match(b->mask, who)) {
+	  if (b->desc && b->desc[0] != '@')
+	    snprintf(resn, sizeof resn, "%s%s", _("banned: "), b->desc);
+	  else
+	    resn[0] = 0;
+	}
+      }
+    }
+      kick_all(chan, who, resn[0] ? resn : _("Banned"), match_my_nick(nick) ? 0 : 1);
+  }
+  /* Is it a server ban from nowhere? */
+  if (reversing ||
+      (bounce_bans && (!nick[0]) &&
+       (!u_equals_mask(global_bans, who) ||
+	!u_equals_mask(chan->bans, who)) && (check)))
+    add_mode(chan, '-', 'b', who);
+}
+
+static void got_unban(struct chanset_t *chan, char *nick, char *from,
+		      char *who, struct userrec *u)
+{
+  masklist *b, *old;
+
+  old = NULL;
+  for (b = chan->channel.ban; b->mask[0] && irccmp(b->mask, who);
+       old = b, b = b->next)
+    ;
+  if (b->mask[0]) {
+    if (old)
+      old->next = b->next;
+    else
+      chan->channel.ban = b->next;
+    free(b->mask);
+    free(b->who);
+    free(b);
+  }
+
+  if (channel_pending(chan))
+    return;
+
+  if (u_sticky_mask(chan->bans, who) || u_sticky_mask(global_bans, who)) {
+    /* That's a sticky ban! No point in being
+     * sticky unless we enforce it!!
+     */
+    add_mode(chan, '+', 'b', who);
+  }
+  if ((u_equals_mask(global_bans, who) || u_equals_mask(chan->bans, who)) &&
+      me_op(chan) && !channel_dynamicbans(chan)) {
+    /* That's a permban! */
+    if ((!glob_bot(user) || !(bot_flags(u) & BOT_SHARE)) &&
+        ((!glob_op(user) || chan_deop(user)) && !chan_op(user)))
+      add_mode(chan, '+', 'b', who);
+  }
+}
+
+static void got_exempt(struct chanset_t *chan, char *nick, char *from,
+		       char *who)
+{
+  char s[UHOSTLEN];
+
+  simple_sprintf(s, "%s!%s", nick, from);
+  newexempt(chan, who, s);
+
+  if (channel_pending(chan))
+    return;
+
+  if (!match_my_nick(nick)) {	/* It's not my exemption */
+    if (channel_nouserexempts(chan) && nick[0] && !glob_bot(user) &&
+	!glob_master(user) && !chan_master(user)) {
+      /* No exempts made by users */
+      add_mode(chan, '-', 'e', who);
+      return;
+    }
+    if (!nick[0] && bounce_modes)
+      reversing = 1;
+  }
+  if (reversing || (bounce_exempts && !nick[0] &&
+		   (!u_equals_mask(global_exempts, who) ||
+		    !u_equals_mask(chan->exempts, who))))
+    add_mode(chan, '-', 'e', who);
+}
+
+static void got_unexempt(struct chanset_t *chan, char *nick, char *from,
+			 char *who, struct userrec *u)
+{
+  masklist *e = chan->channel.exempt, *old = NULL;
+  masklist *b ;
+  int match = 0;
+
+  while (e && e->mask[0] && irccmp(e->mask, who)) {
+    old = e;
+    e = e->next;
+  }
+  if (e && e->mask[0]) {
+    if (old)
+      old->next = e->next;
+    else
+      chan->channel.exempt = e->next;
+    free(e->mask);
+    free(e->who);
+    free(e);
+  }
+
+  if (channel_pending(chan))
+    return;
+
+  if (u_sticky_mask(chan->exempts, who) || u_sticky_mask(global_exempts, who)) {
+    /* That's a sticky exempt! No point in being sticky unless we enforce it!!
+     */
+    add_mode(chan, '+', 'e', who);
+  }
+  /* If exempt was removed by master then leave it else check for bans */
+  if (!nick[0] && glob_bot(user) && !glob_master(user) && !chan_master(user)) {
+    b = chan->channel.ban;
+    while (b->mask[0] && !match) {
+      if (wild_match(b->mask, who) ||
+          wild_match(who, b->mask)) {
+        add_mode(chan, '+', 'e', who);
+        match = 1;
+      } else
+        b = b->next;
+    }
+  }
+  if ((u_equals_mask(global_exempts, who) || u_equals_mask(chan->exempts, who)) &&
+      me_op(chan) && !channel_dynamicexempts(chan)) {
+    /* That's a permexempt! */
+    if (glob_bot(user) && (bot_flags(u) & BOT_SHARE)) {
+      /* Sharebot -- do nothing */
+    } else
+      add_mode(chan, '+', 'e', who);
+  }
+}
+
+static void got_invite(struct chanset_t *chan, char *nick, char *from,
+		       char *who)
+{
+  char s[UHOSTLEN];
+
+  simple_sprintf(s, "%s!%s", nick, from);
+  newinvite(chan, who, s);
+
+  if (channel_pending(chan))
+    return;
+
+  if (!match_my_nick(nick)) {	/* It's not my invitation */
+    if (channel_nouserinvites(chan) && nick[0] && !glob_bot(user) &&
+	!glob_master(user) && !chan_master(user)) {
+      /* No exempts made by users */
+      add_mode(chan, '-', 'I', who);
+      return;
+    }
+    if ((!nick[0]) && (bounce_modes))
+      reversing = 1;
+  }
+  if (reversing || (bounce_invites && (!nick[0])  &&
+		    (!u_equals_mask(global_invites, who) ||
+		     !u_equals_mask(chan->invites, who))))
+    add_mode(chan, '-', 'I', who);
+}
+
+static void got_uninvite(struct chanset_t *chan, char *nick, char *from,
+			 char *who, struct userrec *u)
+{
+  masklist *inv = chan->channel.invite, *old = NULL;
+
+  while (inv->mask[0] && irccmp(inv->mask, who)) {
+    old = inv;
+    inv = inv->next;
+  }
+  if (inv->mask[0]) {
+    if (old)
+      old->next = inv->next;
+    else
+      chan->channel.invite = inv->next;
+    free(inv->mask);
+    free(inv->who);
+    free(inv);
+  }
+
+  if (channel_pending(chan))
+    return;
+
+  if (u_sticky_mask(chan->invites, who) || u_sticky_mask(global_invites, who)) {
+    /* That's a sticky invite! No point in being sticky unless we enforce it!!
+     */
+    add_mode(chan, '+', 'I', who);
+  }
+  if (!nick[0] && glob_bot(user) && !glob_master(user) && !chan_master(user)
+      &&  (chan->channel.mode & CHANINV))
+    add_mode(chan, '+', 'I', who);
+  if ((u_equals_mask(global_invites, who) ||
+       u_equals_mask(chan->invites, who)) && me_op(chan) &&
+      !channel_dynamicinvites(chan)) {
+    /* That's a perminvite! */
+    if (glob_bot(user) && (bot_flags(u) & BOT_SHARE)) {
+      /* Sharebot -- do nothing */
+    } else
+      add_mode(chan, '+', 'I', who);
+  }
+}
+
+static int gotmode(char *from, char *ignore, char *origmsg)
+{
+  char buf[UHOSTLEN], *nick, *uhost, *ch, *op, *chg, *msg;
+  char s[256], buf2[512];
+  char ms2[3];
+  int z;
+  struct userrec *u;
+  memberlist *m;
+  struct chanset_t *chan;
+
+  strncpyz(buf2, origmsg, sizeof buf2);
+  msg = buf2;
+  /* Usermode changes? */
+  if (msg[0] && (strchr(CHANMETA, msg[0]) != NULL)) {
+    ch = newsplit(&msg);
+    chg = newsplit(&msg);
+    reversing = 0;
+    chan = findchan(ch);
+    if (!chan) {
+      putlog(LOG_MISC, "*", _("Oops.   Someone made me join %s... leaving..."), ch);
+      dprintf(DP_SERVER, "PART %s\n", ch);
+    } else if (channel_active(chan) || channel_pending(chan)) {
+      z = strlen(msg);
+      if (msg[--z] == ' ')	/* I hate cosmetic bugs :P -poptix */
+	msg[z] = 0;
+      putlog(LOG_MODES, chan->dname, "%s: mode change '%s %s' by %s",
+	     ch, chg, msg, from);
+      u = get_user_by_host(from);
+      get_user_flagrec(u, &user, ch);
+      strncpyz(buf, from, sizeof buf);
+      nick = strtok(buf, "!");
+      uhost = strtok(NULL, "!");
+      m = ismember(chan, nick);
+      if (m)
+	m->last = now;
+      if (channel_active(chan) && m && me_op(chan)  &&
+	  !(glob_friend(user) || chan_friend(user) ||
+	    (channel_dontkickops(chan) &&
+	     (chan_op(user) || (glob_op(user) && !chan_deop(user)))))) {
+	if (chan_fakeop(m)) {
+	  putlog(LOG_MODES, ch, _("Mode change by fake op on %s!  Reversing..."), ch);
+	  dprintf(DP_MODE, "KICK %s %s :%s\n", ch, nick,
+		  _("Abusing ill-gained server ops"));
+	  m->flags |= SENTKICK;
+	  reversing = 1;
+	} else if (!chan_hasop(m) && !channel_nodesynch(chan)) {
+	  putlog(LOG_MODES, ch, _("Mode change by non-chanop on %s!  Reversing..."), ch);
+	  dprintf(DP_MODE, "KICK %s %s :%s\n", ch, nick,
+		  _("Abusing desync"));
+	  m->flags |= SENTKICK;
+	  reversing = 1;
+	}
+      }
+      ms2[0] = '+';
+      ms2[2] = 0;
+      while ((ms2[1] = *chg)) {
+	int todo = 0;
+
+	switch (*chg) {
+	case '+':
+	  ms2[0] = '+';
+	  break;
+	case '-':
+	  ms2[0] = '-';
+	  break;
+	case 'i':
+	  todo = CHANINV;
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  break;
+	case 'p':
+	  todo = CHANPRIV;
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  break;
+	case 's':
+	  todo = CHANSEC;
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  break;
+	case 'm':
+	  todo = CHANMODER;
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  break;
+	case 'c':
+	  todo = CHANNOCLR;
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  break;
+	case 'R':
+	  todo = CHANREGON;
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  break;
+	case 't':
+	  todo = CHANTOPIC;
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  break;
+	case 'n':
+	  todo = CHANNOMSG;
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  break;
+	case 'a':
+	  todo = CHANANON;
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  break;
+	case 'q':
+	  todo = CHANQUIET;
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  break;
+	case 'l':
+	  if (!nick && bounce_modes)
+	    reversing = 1;
+	  if (ms2[0] == '-') {
+	    check_tcl_mode(nick, uhost, u, chan->dname, ms2, "");
+	    if (channel_active(chan)) {
+	      if ((reversing) && (chan->channel.maxmembers != 0)) {
+		simple_sprintf(s, "%d", chan->channel.maxmembers);
+		add_mode(chan, '+', 'l', s);
+	      } else if ((chan->limit_prot != 0) && !glob_master(user) &&
+			 !chan_master(user)) {
+		simple_sprintf(s, "%d", chan->limit_prot);
+		add_mode(chan, '+', 'l', s);
+	      }
+	    }
+	    chan->channel.maxmembers = 0;
+	  } else {
+	    op = newsplit(&msg);
+	    fixcolon(op);
+	    if (op == '\0')
+	      break;
+	    chan->channel.maxmembers = atoi(op);
+	    check_tcl_mode(nick, uhost, u, chan->dname, ms2,
+			   int_to_base10(chan->channel.maxmembers));
+	    if (channel_pending(chan))
+	      break;
+	    if (((reversing) &&
+		 !(chan->mode_pls_prot & CHANLIMIT)) ||
+		((chan->mode_mns_prot & CHANLIMIT) &&
+		 !glob_master(user) && !chan_master(user))) 
+	      add_mode(chan, '-', 'l', "");
+	    if ((chan->limit_prot != chan->channel.maxmembers) &&
+		(chan->mode_pls_prot & CHANLIMIT) &&
+		(chan->limit_prot != 0) &&	/* arthur2 */
+		!glob_master(user) && !chan_master(user)) {
+	      simple_sprintf(s, "%d", chan->limit_prot);
+	      add_mode(chan, '+', 'l', s);
+	    }
+	  }
+	  break;
+	case 'k':
+ 	  if (ms2[0] == '+')
+	    chan->channel.mode |= CHANKEY;
+	  else
+            chan->channel.mode &= ~CHANKEY;
+          op = newsplit(&msg);
+	  fixcolon(op);
+	  if (op == '\0') {
+	    break;
+	  }
+	  check_tcl_mode(nick, uhost, u, chan->dname, ms2, op);
+	  if (ms2[0] == '+') {
+	    set_key(chan, op);
+	    if (channel_active(chan))
+	      got_key(chan, nick, uhost, op);
+	  } else {
+	    if (channel_active(chan)) {
+	      if ((reversing) && (chan->channel.key[0]))
+		add_mode(chan, '+', 'k', chan->channel.key);
+	      else if ((chan->key_prot[0]) && !glob_master(user)
+		       && !chan_master(user))
+		add_mode(chan, '+', 'k', chan->key_prot);
+	    }
+	    set_key(chan, NULL);
+	  }
+	  break;
+	case 'o':
+	  op = newsplit(&msg);
+	  fixcolon(op);
+	  if (ms2[0] == '+')
+	    got_op(chan, nick, uhost, op, u, &user);
+	  else
+	    got_deop(chan, nick, uhost, op, u);
+	  break;
+	case 'v':
+	  op = newsplit(&msg);
+	  fixcolon(op);
+	  m = ismember(chan, op);
+	  if (!m) {
+	    if (channel_pending(chan))
+	      break;
+	    putlog(LOG_MISC, chan->dname,
+		   _("* Mode change on %s for nonexistant %s!"), chan->dname, op);
+	    dprintf(DP_MODE, "WHO %s\n", op);
+	  } else {
+	    simple_sprintf(s, "%s!%s", m->nick, m->userhost);
+	    get_user_flagrec(m->user ? m->user : get_user_by_host(s),
+			     &victim, chan->dname);
+	    if (ms2[0] == '+') {
+	      m->flags &= ~SENTVOICE;
+	      m->flags |= CHANVOICE;
+	      check_tcl_mode(nick, uhost, u, chan->dname, ms2, op);
+	      if (channel_active(chan) &&
+		  !glob_master(user) && !chan_master(user)) {
+		if (channel_autovoice(chan) &&
+		    (chan_quiet(victim) ||
+		     (glob_quiet(victim) && !chan_voice(victim)))) {
+		  add_mode(chan, '-', 'v', op);
+		} else if (reversing)
+		  add_mode(chan, '-', 'v', op);
+	      }
+	    } else {
+	      m->flags &= ~SENTDEVOICE;
+	      m->flags &= ~CHANVOICE;
+	      check_tcl_mode(nick, uhost, u, chan->dname, ms2, op);
+	      if (channel_active(chan) &&
+		  !glob_master(user) && !chan_master(user)) {
+		if ((channel_autovoice(chan) && !chan_quiet(victim) &&
+		    (chan_voice(victim) || glob_voice(victim))) ||
+		    (!chan_quiet(victim) &&
+		    (glob_gvoice(victim) || chan_gvoice(victim)))) {
+		  add_mode(chan, '+', 'v', op);
+		} else if (reversing)
+		  add_mode(chan, '+', 'v', op);
+	      }
+	    }
+	  }
+	  break;
+	case 'b':
+	  op = newsplit(&msg);
+	  fixcolon(op);
+	  check_tcl_mode(nick, uhost, u, chan->dname, ms2, op);
+	  if (ms2[0] == '+')
+	    got_ban(chan, nick, uhost, op);
+	  else
+	    got_unban(chan, nick, uhost, op, u);
+	  break;
+	case 'e':
+	  op = newsplit(&msg);
+	  fixcolon(op);
+	  check_tcl_mode(nick, uhost, u, chan->dname, ms2, op);
+	  if (ms2[0] == '+')
+	    got_exempt(chan, nick, uhost, op);
+	  else
+	    got_unexempt(chan, nick, uhost, op, u);
+	  break;
+	case 'I':
+	  op = newsplit(&msg);
+	  fixcolon(op);
+	  check_tcl_mode(nick, uhost, u, chan->dname, ms2, op);
+	  if (ms2[0] == '+')
+	    got_invite(chan, nick, uhost, op);
+	  else
+	    got_uninvite(chan, nick, uhost, op, u);
+	  break;
+	}
+	if (todo) {
+	  check_tcl_mode(nick, uhost, u, chan->dname, ms2, "");
+	  if (ms2[0] == '+')
+	    chan->channel.mode |= todo;
+	  else
+	    chan->channel.mode &= ~todo;
+	  if (channel_active(chan)) {
+	    if ((((ms2[0] == '+') && (chan->mode_mns_prot & todo)) ||
+		 ((ms2[0] == '-') && (chan->mode_pls_prot & todo))) &&
+		!glob_master(user) && !chan_master(user))
+	      add_mode(chan, ms2[0] == '+' ? '-' : '+', *chg, "");
+	    else if (reversing &&
+		     ((ms2[0] == '+') || (chan->mode_pls_prot & todo)) &&
+		     ((ms2[0] == '-') || (chan->mode_mns_prot & todo)))
+	      add_mode(chan, ms2[0] == '+' ? '-' : '+', *chg, "");
+	  }
+	}
+	chg++;
+      }
+      if (!me_op(chan) && !nick)
+        chan->status |= CHAN_ASKEDMODES;
+    }
+  }
+  return 0;
+}
Index: eggdrop1.7/modules/irc/modinfo
diff -u /dev/null eggdrop1.7/modules/irc/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/irc/modinfo	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,5 @@
+DESC:The irc module provides all normal irc interaction. If you want the
+DESC:normal join & maintain channels stuff, this is the module.
+DESC:
+DESC:Normally you will want to ENABLE this module.
+DEPENDS:server channels
Index: eggdrop1.7/modules/irc/msgcmds.c
diff -u /dev/null eggdrop1.7/modules/irc/msgcmds.c:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/irc/msgcmds.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,1005 @@
+/*
+ * msgcmds.c -- part of irc.mod
+ *   all commands entered via /MSG
+ *
+ * $Id: msgcmds.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static int msg_hello(char *nick, char *h, struct userrec *u, char *p)
+{
+  char host[UHOSTLEN], s[UHOSTLEN], s1[UHOSTLEN], handle[HANDLEN + 1];
+  char *p1;
+  int common = 0;
+  int atr = 0;
+  struct chanset_t *chan;
+  struct flag_record fr = {FR_GLOBAL, 0, 0, 0, 0, 0};
+
+  if (!learn_users && !make_userfile)
+    return 0;
+  if (match_my_nick(nick))
+    return 1;
+  if (u)
+    atr = u->flags;
+  if (u && !(atr & USER_COMMON)) {
+    dprintf(DP_HELP, "NOTICE %s :%s, %s.\n", nick, _("Hi"), u->handle);
+    return 1;
+  }
+  strncpyz(handle, nick, sizeof handle);
+  if (get_user_by_handle(userlist, handle)) {
+    struct flag_record fr = {FR_GLOBAL, 0, 0, 0, 0, 0};
+    fr.global = atr;
+
+    dprintf(DP_HELP, _("NOTICE %s :I dont recognize you from that host.\n"), nick);
+    dprintf(DP_HELP, _("NOTICE %s :Either you are using someone elses nickname or you need to type: /MSG %s IDENT (password)\n"), nick, botname);
+    return 1;
+  }
+  snprintf(s, sizeof s, "%s!%s", nick, h);
+  if (u_match_mask(global_bans, s)) {
+    dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("You're banned, goober."));
+    return 1;
+  }
+  if (atr & USER_COMMON) {
+    maskhost(s, host);
+    strcpy(s, host);
+    snprintf(host, sizeof host, "%s!%s", nick, s + 2);
+    userlist = adduser(userlist, handle, host, "-", USER_DEFAULT);
+    putlog(LOG_MISC, "*", "%s %s (%s) -- %s",
+	   _("Introduced to"), nick, host, _("common site"));
+    common = 1;
+  } else {
+    maskhost(s, host);
+    if (make_userfile) {
+      userlist = adduser(userlist, handle, host, "-",
+		 sanity_check(default_flags | USER_MASTER | USER_OWNER));
+      set_user(&USERENTRY_HOSTS, get_user_by_handle(userlist, handle),
+	       "-telnet!*@*");
+    } else
+      userlist = adduser(userlist, handle, host, "-",
+			 sanity_check(default_flags));
+    putlog(LOG_MISC, "*", "%s %s (%s)", _("Introduced to"), nick, host);
+  }
+  for (chan = chanset; chan; chan = chan->next)
+    if (ismember(chan, handle))
+      add_chanrec_by_handle(userlist, handle, chan->dname);
+  dprintf(DP_HELP, _("NOTICE %s :Hi %s!  Im %s, an eggdrop bot.\n"), nick, nick, botname);
+  dprintf(DP_HELP, _("NOTICE %s :Ill recognize you by hostmask %s from now on.\n"), nick, host);
+  if (common) {
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Since you come from a common irc site, this means you should"));
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("always use this nickname when talking to me"));
+  }
+  if (make_userfile) {
+    fr.global = sanity_check(default_flags | USER_OWNER);
+
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("YOU ARE THE OWNER ON THIS BOT NOW"));
+    dprintf(DP_HELP, _("NOTICE %s :As master you really need to set a password: with /MSG %s pass <your-chosen-password>.\n"), nick, botname);
+    dprintf(DP_HELP, _("NOTICE %s :All major commands are used from DCC chat. From now on, you dont need to use the -m option when starting the bot.  Enjoy !!!\n"), nick);
+    putlog(LOG_MISC, "*", _("Bot installation complete, first master is %s"), handle);
+    make_userfile = 0;
+    write_userfile(-1);
+    add_note(handle, origbotname, _("Welcome to Eggdrop! =]"), -1, 0);
+  } else {
+    fr.global = default_flags;
+
+    dprintf(DP_HELP, _("NOTICE %s :All commands are done via /MSG. For the complete list, /MSG %s help   Cya!\n"), nick, botname);
+  }
+  if (strlen(nick) > HANDLEN)
+    /* Notify the user that his/her handle was truncated. */
+    dprintf(DP_HELP, _("NOTICE %s :Your nick was too long and therefore it was truncated to %s.\n"), nick, handle);
+  if (notify_new[0]) {
+    snprintf(s, sizeof s, _("introduced to %s from %s"), nick, host);
+    strcpy(s1, notify_new);
+    while (s1[0]) {
+      p1 = strchr(s1, ',');
+      if (p1 != NULL) {
+	*p1 = 0;
+	p1++;
+	rmspace(p1);
+      }
+      rmspace(s1);
+      add_note(s1, origbotname, s, -1, 0);
+      if (p1 == NULL)
+	s1[0] = 0;
+      else
+	strcpy(s1, p1);
+    }
+  }
+  return 1;
+}
+
+static int msg_pass(char *nick, char *host, struct userrec *u, char *par)
+{
+  char *old, *new;
+
+  if (match_my_nick(nick))
+    return 1;
+  if (!u)
+    return 1;
+  if (u->flags & (USER_BOT | USER_COMMON))
+    return 1;
+  if (!par[0]) {
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick,
+	    u_pass_match(u, "-") ? _("You dont have a password set.") : _("You have a password set."));
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! PASS?", nick, host, u->handle);
+    return 1;
+  }
+  old = newsplit(&par);
+  if (!u_pass_match(u, "-") && !par[0]) {
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You already have a password set."));
+    return 1;
+  }
+  if (par[0]) {
+    if (!u_pass_match(u, old)) {
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Incorrect password."));
+      return 1;
+    }
+    new = newsplit(&par);
+  } else {
+    new = old;
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! PASS...", nick, host, u->handle);
+  if (strlen(new) > 15)
+    new[15] = 0;
+  if (strlen(new) < 6) {
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Please use at least 6 characters."));
+    return 0;
+  }
+  set_user(&USERENTRY_PASS, u, new);
+  dprintf(DP_HELP, "NOTICE %s :%s '%s'.\n", nick,
+	  new == old ? _("Password set to:") : _("Password changed to:"), new);
+  return 1;
+}
+
+static int msg_ident(char *nick, char *host, struct userrec *u, char *par)
+{
+  char s[UHOSTLEN], s1[UHOSTLEN], *pass, who[NICKLEN];
+  struct userrec *u2;
+  memberlist *mx;
+
+  if (match_my_nick(nick))
+    return 1;
+  if (u && (u->flags & USER_BOT))
+    return 1;
+  if (u && (u->flags & USER_COMMON)) {
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Youre at a common site; you cant IDENT."));
+    return 1;
+  }
+  pass = newsplit(&par);
+  if (!par[0])
+    strcpy(who, nick);
+  else {
+    strncpy(who, par, NICKMAX);
+    who[NICKMAX] = 0;
+  }
+  u2 = get_user_by_handle(userlist, who);
+  if (!u2) {
+    if (u && !quiet_reject) {
+      dprintf(DP_HELP, _("NOTICE %s :Youre not %s, youre %s.\n"), nick, nick, u->handle);
+    }
+  } else if (irccmp(who, origbotname) && !(u2->flags & USER_BOT)) {
+    /* This could be used as detection... */
+    if (u_pass_match(u2, "-")) {
+      putlog(LOG_CMDS, "*", "(%s!%s) !*! IDENT %s", nick, host, who);
+      if (!quiet_reject)
+	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You dont have a password set."));
+    } else if (!u_pass_match(u2, pass)) {
+      if (!quiet_reject)
+	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Access denied."));
+    } else if (u == u2) {
+      if (!quiet_reject)
+	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("I recognize you there."));
+      return 1;
+    } else if (u) {
+      if (!quiet_reject)
+	dprintf(DP_HELP, _("NOTICE %s :Youre not %s, youre %s.\n"), nick, who, u->handle);
+      return 1;
+    } else {
+      struct chanset_t *chan;
+      struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+      putlog(LOG_CMDS, "*", "(%s!%s) !*! IDENT %s", nick, host, who);
+      snprintf(s, sizeof s, "%s!%s", nick, host);
+      maskhost(s, s1);
+      dprintf(DP_HELP, "NOTICE %s :%s: %s\n", nick, _("Added hostmask"), s1);
+      addhost_by_handle(who, s1);
+      for (chan = chanset; chan; chan = chan->next) {
+	get_user_flagrec(u2, &fr, chan->dname);
+	/* Is the channel or the user marked auto-op? */
+	if ((channel_autoop(chan) || glob_autoop(fr) || chan_autoop(fr)) &&
+           (mx = ismember(chan, nick)) &&
+	   /* ... and isn't the user chanop already? */
+	   !chan_hasop(mx) && !chan_sentop(mx) &&
+	   /* ... and are they actually validly +o? */
+	   !chan_sentop(mx) && (chan_op(fr) || (glob_op(fr) &&
+	   !chan_deop(fr)))) {
+	  add_mode(chan, '+', 'o', nick);
+          mx->flags |= SENTOP;
+        }
+      }
+      return 1;
+    }
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !*! failed IDENT %s", nick, host, who);
+  return 1;
+}
+
+static int msg_addhost(char *nick, char *host, struct userrec *u, char *par)
+{
+  char *pass;
+
+  if (match_my_nick(nick))
+    return 1;
+  if (!u || (u->flags & USER_BOT))
+    return 1;
+  if (u->flags & USER_COMMON) {
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Youre at a common site; you cant IDENT."));
+    return 1;
+  }
+  pass = newsplit(&par);
+  if (!par[0]) {
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :You must supply a hostmask\n", nick);
+  } else if (irccmp(u->handle, origbotname)) {
+    /* This could be used as detection... */
+    if (u_pass_match(u, "-")) {
+      if (!quiet_reject)
+	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You dont have a password set."));
+    } else if (!u_pass_match(u, pass)) {
+      if (!quiet_reject)
+	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Access denied."));
+    } else if (get_user_by_host(par)) {
+      if (!quiet_reject)
+	dprintf(DP_HELP, "NOTICE %s :That hostmask clashes with another already in use.\n", nick);
+    } else {
+      putlog(LOG_CMDS, "*", "(%s!%s) !*! ADDHOST %s", nick, host, par);
+      dprintf(DP_HELP, "NOTICE %s :%s: %s\n", nick, _("Added hostmask"), par);
+      addhost_by_handle(u->handle, par);
+      return 1;
+    }
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !*! failed ADDHOST %s", nick, host, par);
+  return 1;
+}
+
+static int msg_info(char *nick, char *host, struct userrec *u, char *par)
+{
+  char s[121], *pass, *chname, *p;
+  int locked = 0;
+
+  if (match_my_nick(nick))
+    return 1;
+  if (!use_info)
+    return 1;
+  if (!u)
+    return 0;
+  if (u->flags & (USER_COMMON | USER_BOT))
+    return 1;
+  if (!u_pass_match(u, "-")) {
+    pass = newsplit(&par);
+    if (!u_pass_match(u, pass)) {
+      putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed INFO", nick, host, u->handle);
+      return 1;
+    }
+  } else {
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed INFO", nick, host, u->handle);
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You dont have a password set."));
+    return 1;
+  }
+  if (par[0] && (strchr(CHANMETA, par[0]) != NULL)) {
+    if (!findchan_by_dname(chname = newsplit(&par))) {
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("I dont monitor that channel."));
+      return 1;
+    }
+  } else
+    chname = 0;
+  if (par[0]) {
+    p = get_user(&USERENTRY_INFO, u);
+    if (p && (p[0] == '@'))
+      locked = 1;
+    if (chname) {
+      get_handle_chaninfo(u->handle, chname, s);
+      if (s[0] == '@')
+	locked = 1;
+    }
+    if (locked) {
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Your info line is locked"));
+      return 1;
+    }
+    if (!strcasecmp(par, "none")) {
+      par[0] = 0;
+      if (chname) {
+	set_handle_chaninfo(userlist, u->handle, chname, NULL);
+	dprintf(DP_HELP, "NOTICE %s :%s %s.\n", nick, _("Removed your info line on"), chname);
+	putlog(LOG_CMDS, "*", "(%s!%s) !%s! INFO %s NONE", nick, host,
+	       u->handle, chname);
+      } else {
+	set_user(&USERENTRY_INFO, u, NULL);
+	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Removed your info line."));
+	putlog(LOG_CMDS, "*", "(%s!%s) !%s! INFO NONE", nick, host, u->handle);
+      }
+      return 1;
+    }
+    if (par[0] == '@')
+      par++;
+    dprintf(DP_HELP, "NOTICE %s :%s %s\n", nick, _("Now:"), par);
+    if (chname) {
+      set_handle_chaninfo(userlist, u->handle, chname, par);
+      putlog(LOG_CMDS, "*", "(%s!%s) !%s! INFO %s ...", nick, host, u->handle,
+	     chname);
+    } else {
+      set_user(&USERENTRY_INFO, u, par);
+      putlog(LOG_CMDS, "*", "(%s!%s) !%s! INFO ...", nick, host, u->handle);
+    }
+    return 1;
+  }
+  if (chname) {
+    get_handle_chaninfo(u->handle, chname, s);
+    p = s;
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! INFO? %s", nick, host, u->handle,
+	   chname);
+  } else {
+    p = get_user(&USERENTRY_INFO, u);
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! INFO?", nick, host, u->handle);
+  }
+  if (p && p[0]) {
+    dprintf(DP_HELP, "NOTICE %s :%s %s\n", nick, _("Currently:"), p);
+    dprintf(DP_HELP, "NOTICE %s :%s /msg %s info <pass>%s%s none\n",
+	    nick, _("To remove it:"), botname, chname ? " " : "", chname
+	    ? chname : "");
+  } else {
+    if (chname)
+      dprintf(DP_HELP, "NOTICE %s :%s %s.\n", nick, _("You have no info set on"), chname);
+    else
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You have no info set."));
+  }
+  return 1;
+}
+
+static int msg_who(char *nick, char *host, struct userrec *u, char *par)
+{
+  struct chanset_t *chan;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  memberlist *m;
+  char s[UHOSTLEN], also[512], *info;
+  int i;
+
+  if (match_my_nick(nick))
+    return 1;
+  if (!u)
+    return 0;
+  if (!use_info)
+    return 1;
+  if (!par[0]) {
+    dprintf(DP_HELP, "NOTICE %s :%s: /msg %s who <channel>\n", nick,
+	    _("Usage"), botname);
+    return 0;
+  }
+  chan = findchan_by_dname(par);
+  if (!chan) {
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("I dont monitor that channel."));
+    return 0;
+  }
+  get_user_flagrec(u, &fr, par);
+  if (channel_hidden(chan) && !hand_on_chan(chan, u) &&
+      !glob_op(fr) && !glob_friend(fr) &&
+      !chan_op(fr) && !chan_friend(fr)) {
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Channel is currently hidden."));
+    return 1;
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! WHO", nick, host, u->handle);
+  also[0] = 0;
+  i = 0;
+  for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+    struct userrec *u;
+
+    snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost);
+    u = get_user_by_host(s);
+    info = get_user(&USERENTRY_INFO, u);
+    if (u && (u->flags & USER_BOT))
+      info = 0;
+    if (info && (info[0] == '@'))
+      info++;
+    else if (u) {
+      get_handle_chaninfo(u->handle, chan->dname, s);
+      if (s[0]) {
+	info = s;
+	if (info[0] == '@')
+	  info++;
+      }
+    }
+    if (info && info[0])
+      dprintf(DP_HELP, "NOTICE %s :[%9s] %s\n", nick, m->nick, info);
+    else {
+      if (match_my_nick(m->nick))
+	dprintf(DP_HELP, "NOTICE %s :[%9s] <-- I'm the bot, of course.\n",
+		nick, m->nick);
+      else if (u && (u->flags & USER_BOT)) {
+	if (bot_flags(u) & BOT_SHARE)
+	  dprintf(DP_HELP, "NOTICE %s :[%9s] <-- a twin of me\n",
+		  nick, m->nick);
+	else
+	  dprintf(DP_HELP, "NOTICE %s :[%9s] <-- another bot\n",
+		  nick, m->nick);
+      } else {
+	if (i) {
+	  also[i++] = ',';
+	  also[i++] = ' ';
+	}
+	i += my_strcpy(also + i, m->nick);
+	if (i > 400) {
+	  dprintf(DP_HELP, "NOTICE %s :No info: %s\n", nick, also);
+	  i = 0;
+	  also[0] = 0;
+	}
+      }
+    }
+  }
+  if (i) {
+    dprintf(DP_HELP, "NOTICE %s :No info: %s\n", nick, also);
+  }
+  return 1;
+}
+
+static int msg_whois(char *nick, char *host, struct userrec *u, char *par)
+{
+  char s[UHOSTLEN], s1[81], *s2;
+  int ok;
+  struct chanset_t *chan;
+  memberlist *m;
+  struct chanuserrec *cr;
+  struct userrec *u2;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+  struct xtra_key *xk;
+  time_t tt = 0;
+
+  if (match_my_nick(nick))
+    return 1;
+  if (!u)
+    return 0;
+  if (strlen(par) > NICKMAX)
+    par[NICKMAX] = 0;
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! WHOIS %s", nick, host, u->handle, par);
+  u2 = get_user_by_handle(userlist, par);
+  if (!u2) {
+    /* No such handle -- maybe it's a nickname of someone on a chan? */
+    ok = 0;
+    for (chan = chanset; chan && !ok; chan = chan->next) {
+      m = ismember(chan, par);
+      if (m) {
+	snprintf(s, sizeof s, "%s!%s", par, m->userhost);
+	u2 = get_user_by_host(s);
+	if (u2) {
+	  ok = 1;
+	  dprintf(DP_HELP, "NOTICE %s :[%s] AKA '%s':\n", nick,
+		  par, u2->handle);
+	}
+      }
+    }
+    if (!ok) {
+      dprintf(DP_HELP, "NOTICE %s :[%s] %s\n", nick, par, _("No user record."));
+      return 1;
+    }
+  }
+  s2 = get_user(&USERENTRY_INFO, u2);
+  if (s2 && (s2[0] == '@'))
+    s2++;
+  if (s2 && s2[0] && !(u2->flags & USER_BOT))
+    dprintf(DP_HELP, "NOTICE %s :[%s] %s\n", nick, u2->handle, s2);
+  for (xk = get_user(&USERENTRY_XTRA, u2); xk; xk = xk->next)
+    if (!strcasecmp(xk->key, "EMAIL"))
+      dprintf(DP_HELP, "NOTICE %s :[%s] email: %s\n", nick, u2->handle,
+	      xk->data);
+  ok = 0;
+  for (chan = chanset; chan; chan = chan->next) {
+    if (hand_on_chan(chan, u2)) {
+      snprintf(s1, sizeof s1, "NOTICE %s :[%s] %s %s.", nick, u2->handle,
+		   _("Now on channel"), chan->dname);
+      ok = 1;
+    } else {
+      get_user_flagrec(u, &fr, chan->dname);
+      cr = get_chanrec(u2, chan->dname);
+      if (cr && (cr->laston > tt) &&
+	  (!channel_hidden(chan) ||
+	   hand_on_chan(chan, u) ||
+	   (glob_op(fr) && !chan_deop(fr)) ||
+	   glob_friend(fr) || chan_op(fr) || chan_friend(fr))) {
+	tt = cr->laston;
+	strftime(s, 14, "%b %d %H:%M", localtime(&tt));
+	ok = 1;
+	snprintf(s1, sizeof s1, "NOTICE %s :[%s] %s %s on %s", nick, u2->handle,
+		     _("Last seen at"), s, chan->dname);
+      }
+    }
+  }
+  if (!ok)
+    snprintf(s1, sizeof s1, "NOTICE %s :[%s] %s", nick, u2->handle, _("Never joined one of my channels."));
+  if (u2->flags & USER_OP)
+    strcat(s1, _("  (is a global op)"));
+  if (u2->flags & USER_BOT)
+    strcat(s1, _("  (is a bot)"));
+  if (u2->flags & USER_MASTER)
+    strcat(s1, _("  (is a master)"));
+  dprintf(DP_HELP, "%s\n", s1);
+  return 1;
+}
+
+static int msg_help(char *nick, char *host, struct userrec *u, char *par)
+{
+  char *p;
+
+  if (match_my_nick(nick))
+    return 1;
+  if (!u) {
+    if (!quiet_reject) {
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("I dont know you; please introduce yourself first."));
+      dprintf(DP_HELP, "NOTICE %s :/MSG %s hello\n", nick, botname);
+    }
+    return 0;
+  }
+  if (helpdir[0]) {
+    struct flag_record fr = {FR_ANYWH | FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+    get_user_flagrec(u, &fr, 0);
+    if (!par[0])
+      showhelp(nick, "help", &fr, 0);
+    else {
+      for (p = par; *p != 0; p++)
+	if ((*p >= 'A') && (*p <= 'Z'))
+	  *p += ('a' - 'A');
+      showhelp(nick, par, &fr, 0);
+    }
+  } else
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("No help."));
+  return 1;
+}
+
+/* I guess just op them on every channel they're on, unless they specify
+ * a parameter.
+ */
+static int msg_op(char *nick, char *host, struct userrec *u, char *par)
+{
+  struct chanset_t *chan;
+  char *pass;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  if (match_my_nick(nick))
+    return 1;
+  pass = newsplit(&par);
+  if (u_pass_match(u, pass)) {
+    /* Prevent people from gaining ops when no password set */
+    if (!u_pass_match(u, "-")) {
+      if (par[0]) {
+	chan = findchan_by_dname(par);
+	if (chan && channel_active(chan)) {
+	  get_user_flagrec(u, &fr, par);
+	  if (chan_op(fr) || (glob_op(fr) && !chan_deop(fr)))
+	    add_mode(chan, '+', 'o', nick);
+	    putlog(LOG_CMDS, "*", "(%s!%s) !%s! OP %s",
+		   nick, host, u->handle, par);
+	  return 1;
+	}
+      } else {
+	for (chan = chanset; chan; chan = chan->next) {
+	  get_user_flagrec(u, &fr, chan->dname);
+	  if (chan_op(fr) || (glob_op(fr) && !chan_deop(fr)))
+	    add_mode(chan, '+', 'o', nick);
+	}
+	putlog(LOG_CMDS, "*", "(%s!%s) !%s! OP", nick, host, u->handle);
+	return 1;
+      }
+    }
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !*! failed OP", nick, host);
+  return 1;
+}
+
+static int msg_key(char *nick, char *host, struct userrec *u, char *par)
+{
+  struct chanset_t *chan;
+  char *pass;
+  struct flag_record fr =
+  {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  if (match_my_nick(nick))
+    return 1;
+  pass = newsplit(&par);
+  if (u_pass_match(u, pass)) {
+    /* Prevent people from getting key with no pass set */
+    if (!u_pass_match(u, "-")) {
+      if (!(chan = findchan_by_dname(par))) {
+	dprintf(DP_HELP, "NOTICE %s :%s: /MSG %s key <pass> <channel>\n",
+		nick, _("Usage"), botname);
+	return 1;
+      }
+      if (!channel_active(chan)) {
+	dprintf(DP_HELP, "NOTICE %s :%s: %s\n", nick, par, _("Not on that channel right now."));
+	return 1;
+      }
+      chan = findchan_by_dname(par);
+      if (chan && channel_active(chan)) {
+	get_user_flagrec(u, &fr, par);
+	if (chan_op(fr) || (glob_op(fr) && !chan_deop(fr))) {
+	  if (chan->channel.key[0]) {
+	    dprintf(DP_SERVER, "NOTICE %s :%s: key is %s\n", nick, par,
+		    chan->channel.key);
+	    if (invite_key && (chan->channel.mode & CHANINV)) {
+	      dprintf(DP_SERVER, "INVITE %s %s\n", nick, par);
+	      putlog(LOG_CMDS, "*", "(%s!%s) !%s! KEY %s",
+		     nick, host, u->handle, par);
+	    }
+	  } else {
+	    dprintf(DP_HELP, "NOTICE %s :%s: no key set for this channel\n",
+		    nick, par);
+	    putlog(LOG_CMDS, "*", "(%s!%s) !%s! KEY %s", nick, host, u->handle,
+		   par);
+	  }
+	}
+	return 1;
+      }
+    }
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed KEY %s", nick, host,
+	 (u ? u->handle : "*"), par);
+  return 1;
+}
+
+/* Don't have to specify a channel now and can use this command
+ * regardless of +autovoice or being a chanop. (guppy 7Jan1999)
+ */
+static int msg_voice(char *nick, char *host, struct userrec *u, char *par)
+{
+  struct chanset_t *chan;
+  char *pass;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  if (match_my_nick(nick))
+    return 1;
+  pass = newsplit(&par);
+  if (u_pass_match(u, pass)) {
+    if (!u_pass_match(u, "-")) {
+      if (par[0]) {
+	chan = findchan_by_dname(par);
+	if (chan && channel_active(chan)) {
+	  get_user_flagrec(u, &fr, par);
+	  if (chan_voice(fr) || glob_voice(fr) ||
+	      chan_op(fr) || glob_op(fr)) {
+	    add_mode(chan, '+', 'v', nick);
+	    putlog(LOG_CMDS, "*", "(%s!%s) !%s! VOICE %s",
+		   nick, host, u->handle, par);
+	  } else
+	    putlog(LOG_CMDS, "*", "(%s!%s) !*! failed VOICE %s",
+		nick, host, par);
+	  return 1;
+	}
+      } else {
+	for (chan = chanset; chan; chan = chan->next) {
+	  get_user_flagrec(u, &fr, chan->dname);
+	  if (chan_voice(fr) || glob_voice(fr) ||
+	      chan_op(fr) || glob_op(fr))
+	    add_mode(chan, '+', 'v', nick);
+	}
+	putlog(LOG_CMDS, "*", "(%s!%s) !%s! VOICE", nick, host, u->handle);
+	return 1;
+      }
+    }
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !*! failed VOICE", nick, host);
+  return 1;
+}
+
+static int msg_invite(char *nick, char *host, struct userrec *u, char *par)
+{
+  char *pass;
+  struct chanset_t *chan;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+
+  if (match_my_nick(nick))
+    return 1;
+  pass = newsplit(&par);
+  if (u_pass_match(u, pass) && !u_pass_match(u, "-")) {
+    if (par[0] == '*') {
+      for (chan = chanset; chan; chan = chan->next) {
+	get_user_flagrec(u, &fr, chan->dname);
+	if ((chan_op(fr) || (glob_op(fr) && !chan_deop(fr))) &&
+	    (chan->channel.mode & CHANINV))
+	  dprintf(DP_SERVER, "INVITE %s %s\n", nick, chan->name);
+      }
+      putlog(LOG_CMDS, "*", "(%s!%s) !%s! INVITE ALL", nick, host,
+	     u->handle);
+      return 1;
+    }
+    if (!(chan = findchan_by_dname(par))) {
+      dprintf(DP_HELP, "NOTICE %s :%s: /MSG %s invite <pass> <channel>\n",
+	      nick, _("Usage"), botname);
+      return 1;
+    }
+    if (!channel_active(chan)) {
+      dprintf(DP_HELP, "NOTICE %s :%s: %s\n", nick, par, _("Not on that channel right now."));
+      return 1;
+    }
+    /* We need to check access here also (dw 991002) */
+    get_user_flagrec(u, &fr, par);
+    if (chan_op(fr) || (glob_op(fr) && !chan_deop(fr))) {
+      dprintf(DP_SERVER, "INVITE %s %s\n", nick, par);
+      putlog(LOG_CMDS, "*", "(%s!%s) !%s! INVITE %s", nick, host,
+	     u->handle, par);
+      return 1;
+    }
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed INVITE %s", nick, host,
+	 (u ? u->handle : "*"), par);
+  return 1;
+}
+
+static int msg_status(char *nick, char *host, struct userrec *u, char *par)
+{
+  char s[256];
+  char *ve_t, *un_t;
+  char *pass;
+  int i, l;
+  struct chanset_t *chan;
+#ifdef HAVE_UNAME
+  struct utsname un;
+
+  if (!uname(&un) < 0) {
+#endif
+    ve_t = " ";
+    un_t = "*unknown*";
+#ifdef HAVE_UNAME
+  } else {
+    ve_t = un.release;
+    un_t = un.sysname;
+  }
+#endif
+
+  if (match_my_nick(nick))
+    return 1;
+  if (!u_pass_match(u, "-")) {
+    pass = newsplit(&par);
+    if (!u_pass_match(u, pass)) {
+      putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed STATUS", nick, host,
+	     u->handle);
+      return 1;
+    }
+  } else {
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed STATUS", nick, host, u->handle);
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You dont have a password set."));
+    return 1;
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! STATUS", nick, host, u->handle);
+  dprintf(DP_HELP, "NOTICE %s :I am %s, running %s.\n", nick, botnetnick,
+	  Version);
+  dprintf(DP_HELP, "NOTICE %s :Running on %s %s\n", nick, un_t, ve_t);
+  if (admin[0])
+    dprintf(DP_HELP, "NOTICE %s :Admin: %s\n", nick, admin);
+  /* Fixed previous lame code. Well it's still lame, will overflow the
+   * buffer with a long channel-name. <cybah>
+   */
+  strcpy(s, "Channels: ");
+  l = 10;
+  for (chan = chanset; chan; chan = chan->next) {
+    l += my_strcpy(s + l, chan->dname);
+    if (!channel_active(chan))
+      l += my_strcpy(s + l, " (trying)");
+    else if (channel_pending(chan))
+      l += my_strcpy(s + l, " (pending)");
+    else if (!any_ops(chan))
+      l += my_strcpy(s + l, " (opless)");
+    else if (!me_op(chan))
+      l += my_strcpy(s + l, " (want ops!)");
+    s[l++] = ',';
+    s[l++] = ' ';
+    if (l > 70) {
+      s[l] = 0;
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, s);
+      strcpy(s, "          ");
+      l = 10;
+    }
+  }
+  if (l > 10) {
+    s[l] = 0;
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, s);
+  }
+  i = count_users(userlist);
+  dprintf(DP_HELP, "NOTICE %s :%d user%s\n", nick, i, i == 1 ? "" : "s");
+  daysdur(now, server_online, s);
+  dprintf(DP_HELP, "NOTICE %s :Connected %s\n", nick, s);
+  dprintf(DP_HELP, "NOTICE %s :Online as: %s%s%s\n", nick, botname,
+	  botuserhost[0] ? "!" : "", botuserhost[0] ? botuserhost : "");
+  return 1;
+}
+
+static int msg_die(char *nick, char *host, struct userrec *u, char *par)
+{
+  char s[1024];
+  char *pass;
+
+  if (match_my_nick(nick))
+    return 1;
+  if (!u_pass_match(u, "-")) {
+    pass = newsplit(&par);
+    if (!u_pass_match(u, pass)) {
+      putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed DIE", nick, host, u->handle);
+      return 1;
+    }
+  } else {
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed DIE", nick, host, u->handle);
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You dont have a password set."));
+    return 1;
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! DIE", nick, host, u->handle);
+  dprintf(-serv, "NOTICE %s :%s\n", nick, _("Bot shut down beginning...."));
+  if (!par[0])
+    snprintf(s, sizeof s, "BOT SHUTDOWN (authorized by %s)", u->handle);
+  else
+    snprintf(s, sizeof s, "BOT SHUTDOWN (%s: %s)", u->handle, par);
+  chatout("*** %s\n", s);
+  botnet_send_chat(-1, botnetnick, s);
+  botnet_send_bye();
+  if (!par[0])
+    nuke_server(nick);
+  else
+    nuke_server(par);
+  write_userfile(-1);
+  sleep(1);			/* Give the server time to understand */
+  snprintf(s, sizeof s, "DEAD BY REQUEST OF %s!%s", nick, host);
+  fatal(s, 0);
+  return 1;
+}
+
+static int msg_rehash(char *nick, char *host, struct userrec *u, char *par)
+{
+  if (match_my_nick(nick))
+    return 1;
+  if (u_pass_match(u, par)) {
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! REHASH", nick, host, u->handle);
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Rehashing..."));
+    if (make_userfile)
+      make_userfile = 0;
+    write_userfile(-1);
+    do_restart = -2;
+    return 1;
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed REHASH", nick, host, u->handle);
+  return 1;
+}
+
+static int msg_save(char *nick, char *host, struct userrec *u, char *par)
+{
+  if (match_my_nick(nick))
+    return 1;
+  if (u_pass_match(u, par)) {
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! SAVE", nick, host, u->handle);
+    dprintf(DP_HELP, "NOTICE %s :Saving user file...\n", nick);
+    write_userfile(-1);
+    return 1;
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed SAVE", nick, host, u->handle);
+  return 1;
+}
+
+static int msg_reset(char *nick, char *host, struct userrec *u, char *par)
+{
+  struct chanset_t *chan;
+  char *pass;
+
+  if (match_my_nick(nick))
+    return 1;
+  if (!u_pass_match(u, "-")) {
+    pass = newsplit(&par);
+    if (!u_pass_match(u, pass)) {
+      putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed RESET", nick, host,
+	     u->handle);
+      return 1;
+    }
+  } else {
+    putlog(LOG_CMDS, "*", "(%s!%s) !*! failed RESET", nick, host);
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You dont have a password set."));
+    return 1;
+  }
+  if (par[0]) {
+    chan = findchan_by_dname(par);
+    if (!chan) {
+      dprintf(DP_HELP, "NOTICE %s :%s: %s\n", nick, par, _("I dont monitor that channel."));
+      return 0;
+    }
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! RESET %s", nick, host, u->handle, par);
+    dprintf(DP_HELP, "NOTICE %s :%s: %s\n", nick, par, _("Resetting channel info."));
+    reset_chan_info(chan);
+    return 1;
+  }
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! RESET ALL", nick, host, u->handle);
+  dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Resetting channel info."));
+  for (chan = chanset; chan; chan = chan->next)
+    reset_chan_info(chan);
+  return 1;
+}
+
+static int msg_jump(char *nick, char *host, struct userrec *u, char *par)
+{
+  char *s;
+  int port;
+
+  if (match_my_nick(nick))
+    return 1;
+  if (u_pass_match(u, "-")) {
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed JUMP", nick, host, u->handle);
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You dont have a password set."));
+    return 1;
+  }
+  s = newsplit(&par);		/* Password */
+  if (u_pass_match(u, s)) {
+    if (par[0]) {
+      s = newsplit(&par);
+      port = atoi(newsplit(&par));
+      if (!port)
+	port = default_port;
+      putlog(LOG_CMDS, "*", "(%s!%s) !%s! JUMP %s %d %s", nick, host,
+	     u->handle, s, port, par);
+      strcpy(newserver, s);
+      newserverport = port;
+      strcpy(newserverpass, par);
+    } else
+      putlog(LOG_CMDS, "*", "(%s!%s) !%s! JUMP", nick, host, u->handle);
+    dprintf(-serv, "NOTICE %s :%s\n", nick, _("Jumping servers..."));
+    cycle_time = 0;
+    nuke_server("changing servers");
+  } else
+    putlog(LOG_CMDS, "*", "(%s!%s) !%s! failed JUMP", nick, host, u->handle);
+  return 1;
+}
+
+/* MSG COMMANDS
+ *
+ * Function call should be:
+ *    int msg_cmd("handle","nick","user at host","params");
+ *
+ * The function is responsible for any logging. Return 1 if successful,
+ * 0 if not.
+ */
+static cmd_t C_msg[] =
+{
+  {"addhost",		"",	(Function) msg_addhost,		NULL},
+  {"die",		"n",	(Function) msg_die,		NULL},
+  {"hello",		"",	(Function) msg_hello,		NULL},
+  {"help",		"",	(Function) msg_help,		NULL},
+  {"ident",		"",	(Function) msg_ident,		NULL},
+  {"info",		"",	(Function) msg_info,		NULL},
+  {"invite",		"o|o",	(Function) msg_invite,		NULL},
+  {"jump",		"m",	(Function) msg_jump,		NULL},
+  {"key",		"o|o",	(Function) msg_key,		NULL},
+  {"op",		"",	(Function) msg_op,		NULL},
+  {"pass",		"",	(Function) msg_pass,		NULL},
+  {"rehash",		"m",	(Function) msg_rehash,		NULL},
+  {"reset",		"m",	(Function) msg_reset,		NULL},
+  {"save",		"m",	(Function) msg_save,		NULL},
+  {"status",		"m|m",	(Function) msg_status,		NULL},
+  {"voice",		"",	(Function) msg_voice,		NULL},
+  {"who",		"",	(Function) msg_who,		NULL},
+  {"whois",		"",	(Function) msg_whois,		NULL},
+  {NULL,		NULL,	NULL,				NULL}
+};
Index: eggdrop1.7/modules/irc/tclirc.c
diff -u /dev/null eggdrop1.7/modules/irc/tclirc.c:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/irc/tclirc.c	Sat Oct 27 11:34:50 2001
@@ -0,0 +1,761 @@
+/*
+ * tclirc.c -- part of irc.mod
+ *
+ * $Id: tclirc.c,v 1.1 2001/10/27 16:34:50 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/* Streamlined by answer.
+ */
+static int tcl_chanlist STDVAR
+{
+  char s1[121];
+  int f;
+  memberlist *m;
+  struct userrec *u;
+  struct chanset_t *chan;
+  struct flag_record plus = {FR_CHAN | FR_GLOBAL | FR_BOT, 0, 0, 0, 0, 0},
+ 		     minus = {FR_CHAN | FR_GLOBAL | FR_BOT, 0, 0, 0, 0, 0},
+		     user = {FR_CHAN | FR_GLOBAL | FR_BOT, 0, 0, 0, 0, 0};
+
+  BADARGS(2, 3, " channel ?flags?");
+  chan = findchan_by_dname(argv[1]);
+  if (!chan) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (argc == 2) {
+    /* No flag restrictions so just whiz it thru quick */
+    for (m = chan->channel.member; m && m->nick[0]; m = m->next)
+      Tcl_AppendElement(irp, m->nick);
+    return TCL_OK;
+  }
+  break_down_flags(argv[2], &plus, &minus);
+  f = (minus.global || minus.udef_global ||
+       minus.chan || minus.udef_chan || minus.bot);
+  /* Return empty set if asked for flags but flags don't exist */
+  if (!plus.global && !plus.udef_global &&
+      !plus.chan && !plus.udef_chan && !plus.bot && !f)
+    return TCL_OK;
+  minus.match = plus.match ^ (FR_AND | FR_OR);
+  for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+    simple_sprintf(s1, "%s!%s", m->nick, m->userhost);
+    u = get_user_by_host(s1);
+    get_user_flagrec(u, &user, argv[1]);
+    user.match = plus.match;
+    if (flagrec_eq(&plus, &user)) {
+      if (!f || !flagrec_eq(&minus, &user))
+	Tcl_AppendElement(irp, m->nick);
+    }
+  }
+  return TCL_OK;
+}
+
+static int tcl_botisop STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (me_op(chan))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_ischanjuped STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (channel_juped(chan))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_botisvoice STDVAR
+{
+  struct chanset_t *chan;
+  memberlist *mx;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if ((mx = ismember(chan, botname)) && chan_hasvoice(mx))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_botonchan STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (ismember(chan, botname))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_isop STDVAR
+{
+  struct chanset_t *chan;
+  memberlist *mx;
+
+  BADARGS(3, 3, " nick channel");
+  chan = findchan_by_dname(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  if ((mx = ismember(chan, argv[1])) && chan_hasop(mx))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_isvoice STDVAR
+{
+  struct chanset_t *chan;
+  memberlist *mx;
+
+  BADARGS(3, 3, " nick channel");
+  chan = findchan_by_dname(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  if ((mx = ismember(chan, argv[1])) && chan_hasvoice(mx))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_wasop STDVAR
+{
+  struct chanset_t *chan;
+  memberlist *mx;
+
+  BADARGS(3, 3, " nick channel");
+  chan = findchan(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  if ((mx = ismember(chan, argv[1])) && chan_wasop(mx))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+
+static int tcl_onchan STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(3, 3, " nickname channel");
+  chan = findchan_by_dname(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  if (!ismember(chan, argv[1]))
+    Tcl_AppendResult(irp, "0", NULL);
+  else
+    Tcl_AppendResult(irp, "1", NULL);
+  return TCL_OK;
+}
+
+static int tcl_handonchan STDVAR
+{
+  struct chanset_t *chan;
+  struct userrec *u;
+
+  BADARGS(3, 3, " handle channel");
+  chan = findchan_by_dname(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  if ((u = get_user_by_handle(userlist, argv[1])))
+    if (hand_on_chan(chan, u)) {
+      Tcl_AppendResult(irp, "1", NULL);
+      return TCL_OK;
+    }
+  Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_ischanban STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(3, 3, " ban channel");
+  chan = findchan_by_dname(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  if (ischanban(chan, argv[1]))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_ischanexempt STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(3, 3, " exempt channel");
+  chan = findchan_by_dname(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  if (ischanexempt(chan, argv[1]))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_ischaninvite STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(3, 3, " invite channel");
+  chan = findchan_by_dname(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  if (ischaninvite(chan, argv[1]))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_getchanhost STDVAR
+{
+  struct chanset_t *chan;
+  struct chanset_t *thechan = NULL;
+  memberlist *m;
+
+  BADARGS(2, 3, " nickname ?channel?");	/* drummer */
+  if (argv[2]) {
+    thechan = findchan_by_dname(argv[2]);
+    if (!thechan) {
+      Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+  }
+  for (chan = chanset; chan; chan = chan->next) {
+    m = ismember(chan, argv[1]);
+    if (m && ((chan == thechan) || (thechan == NULL))) {
+      Tcl_AppendResult(irp, m->userhost, NULL);
+      return TCL_OK;
+    }
+  }
+  return TCL_OK;
+}
+
+static int tcl_onchansplit STDVAR
+{
+  struct chanset_t *chan;
+  memberlist *m;
+
+  BADARGS(3, 3, " nickname channel");
+  if (!(chan = findchan_by_dname(argv[2]))) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  m = ismember(chan, argv[1]);
+  if (m && chan_issplit(m))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_maskhost STDVAR
+{
+  char new[121];
+  BADARGS(2, 2, " nick!user at host");
+  maskhost(argv[1], new);
+  Tcl_AppendResult(irp, new, NULL);
+  return TCL_OK;
+}
+
+static int tcl_getchanidle STDVAR
+{
+  memberlist *m;
+  struct chanset_t *chan;
+  char s[20];
+  int x;
+
+  BADARGS(3, 3, " nickname channel");
+  if (!(chan = findchan_by_dname(argv[2]))) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  m = ismember(chan, argv[1]);
+  if (m) {
+    x = (now - (m->last)) / 60;
+    simple_sprintf(s, "%d", x);
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  }
+  Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static inline int tcl_chanmasks(masklist *m, Tcl_Interp *irp)
+{
+  char *list[3], work[20], *p;
+
+  for (; m && m->mask && m->mask[0]; m = m->next) {
+    list[0] = m->mask;
+    list[1] = m->who;
+    simple_sprintf(work, "%d", now - m->timer);
+    list[2] = work;
+    p = Tcl_Merge(3, list);
+    Tcl_AppendElement(irp, p);
+    Tcl_Free((char *) p);
+  }
+  return TCL_OK;
+}
+
+static int tcl_chanbans STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  return tcl_chanmasks(chan->channel.ban, irp);
+}
+
+static int tcl_chanexempts STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  return tcl_chanmasks(chan->channel.exempt, irp);
+}
+
+static int tcl_chaninvites STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  return tcl_chanmasks(chan->channel.invite, irp);
+}
+
+static int tcl_getchanmode STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  Tcl_AppendResult(irp, getchanmode(chan), NULL);
+  return TCL_OK;
+}
+
+static int tcl_getchanjoin STDVAR
+{
+  struct chanset_t *chan;
+  char s[21];
+  memberlist *m;
+
+  BADARGS(3, 3, " nick channel");
+  chan = findchan_by_dname(argv[2]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid cahnnel: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  m = ismember(chan, argv[1]);
+  if (m == NULL) {
+    Tcl_AppendResult(irp, argv[1], " is not on ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  sprintf(s, "%lu", m->joined);
+  Tcl_AppendResult(irp, s, NULL);
+  return TCL_OK;
+}
+
+static int tcl_channame2dname STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel-name");
+  chan = findchan(argv[1]);
+  if (chan) {
+      Tcl_AppendResult(irp, chan->dname, NULL);
+      return TCL_OK;
+  } else {
+      Tcl_AppendResult(irp, "invalid channel-name: ", argv[1], NULL);
+      return TCL_ERROR;
+  }
+}
+
+static int tcl_chandname2name STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel-dname");
+  chan = findchan_by_dname(argv[1]);
+  if (chan) {
+      Tcl_AppendResult(irp, chan->name, NULL);
+      return TCL_OK;
+  } else {
+      Tcl_AppendResult(irp, "invalid channel-dname: ", argv[1], NULL);
+      return TCL_ERROR;
+  }
+}
+
+/* flushmode <chan> */
+static int tcl_flushmode STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  flush_mode(chan, NORMAL);
+  return TCL_OK;
+}
+
+static int tcl_pushmode STDVAR
+{
+  struct chanset_t *chan;
+  char plus, mode;
+
+  BADARGS(3, 4, " channel mode ?arg?");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  plus = argv[2][0];
+  mode = argv[2][1];
+  if ((plus != '+') && (plus != '-')) {
+    mode = plus;
+    plus = '+';
+  }
+  if (!(((mode >= 'a') && (mode <= 'z')) || ((mode >= 'A') && (mode <= 'Z')))) {
+    Tcl_AppendResult(irp, "invalid mode: ", argv[2], NULL);
+    return TCL_ERROR;
+  }
+  if (argc < 4) {
+    if (strchr("bvoeIk", mode) != NULL) {
+      Tcl_AppendResult(irp, "modes b/v/o/e/I/k/l require an argument", NULL);
+      return TCL_ERROR;
+    } else if (plus == '+' && mode == 'l') {
+      Tcl_AppendResult(irp, "modes b/v/o/e/I/k/l require an argument", NULL);
+      return TCL_ERROR;
+    }
+  }
+  if (argc == 4)
+    add_mode(chan, plus, mode, argv[3]);
+  else
+    add_mode(chan, plus, mode, "");
+  return TCL_OK;
+}
+
+static int tcl_resetbans STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  resetbans(chan);
+  return TCL_OK;
+}
+
+static int tcl_resetexempts STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  resetexempts(chan);
+  return TCL_OK;
+}
+
+static int tcl_resetinvites STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  resetinvites(chan);
+  return TCL_OK;
+}
+
+static int tcl_resetchan STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  reset_chan_info(chan);
+  return TCL_OK;
+}
+
+static int tcl_topic STDVAR
+{
+  struct chanset_t *chan;
+
+  BADARGS(2, 2, " channel");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "invalid channel ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  Tcl_AppendResult(irp, chan->channel.topic, NULL);
+  return TCL_OK;
+}
+
+static int tcl_hand2nick STDVAR
+{
+  memberlist *m;
+  char s[161];
+  struct chanset_t *chan;
+  struct chanset_t *thechan = NULL;
+  struct userrec *u;
+
+  BADARGS(2, 3, " handle ?channel?");	/* drummer */
+  if (argv[2]) {
+    chan = findchan_by_dname(argv[2]);
+    thechan = chan;
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+  } else {
+    chan = chanset;
+  }
+  while ((chan != NULL) && ((thechan == NULL) || (thechan == chan))) {
+    for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+      simple_sprintf(s, "%s!%s", m->nick, m->userhost);
+      u = get_user_by_host(s);
+      if (u && !strcasecmp(u->handle, argv[1])) {
+	Tcl_AppendResult(irp, m->nick, NULL);
+	return TCL_OK;
+      }
+    }
+    chan = chan->next;
+  }
+  return TCL_OK;		/* blank */
+}
+
+static int tcl_nick2hand STDVAR
+{
+  memberlist *m;
+  char s[161];
+  struct chanset_t *chan;
+  struct chanset_t *thechan = NULL;
+  struct userrec *u;
+
+  BADARGS(2, 3, " nick ?channel?");	/* drummer */
+  if (argv[2]) {
+    chan = findchan_by_dname(argv[2]);
+    thechan = chan;
+    if (chan == NULL) {
+      Tcl_AppendResult(irp, "invalid channel: ", argv[2], NULL);
+      return TCL_ERROR;
+    }
+  } else {
+    chan = chanset;
+  }
+  while ((chan != NULL) && ((thechan == NULL) || (thechan == chan))) {
+    m = ismember(chan, argv[1]);
+    if (m) {
+      simple_sprintf(s, "%s!%s", m->nick, m->userhost);
+      u = get_user_by_host(s);
+      Tcl_AppendResult(irp, u ? u->handle : "*", NULL);
+      return TCL_OK;
+    }
+    chan = chan->next;
+  }
+  return TCL_OK;		/* blank */
+}
+
+/* Sends an optimal number of kicks per command (as defined by kick_method)
+ * to the server, simialer to kick_all.
+ */
+static int tcl_putkick STDVAR
+{
+  struct chanset_t *chan;
+  int k = 0, l;
+  char kicknick[512], *nick, *p, *comment = NULL;
+  memberlist *m;
+
+  BADARGS(3, 4, " channel nick?s? ?comment?");
+  chan = findchan_by_dname(argv[1]);
+  if (chan == NULL) {
+    Tcl_AppendResult(irp, "illegal channel: ", argv[1], NULL);
+    return TCL_ERROR;
+  }
+  if (argc == 4)
+    comment = argv[3];
+  else
+    comment = "";
+  if (!me_op(chan)) {
+    Tcl_AppendResult(irp, "need op", NULL);
+    return TCL_ERROR;
+  }
+
+  kicknick[0] = 0;
+  p = argv[2];
+  /* Loop through all given nicks */
+  while(p) {
+    nick = p;
+    p = strchr(nick, ',');	/* Search for beginning of next nick */
+    if (p) {
+      *p = 0;
+      p++;
+    }
+
+    m = ismember(chan, nick);
+    if (!m)
+      continue;			/* Skip non-existant nicks */
+    m->flags |= SENTKICK;	/* Mark as pending kick */
+    if (kicknick[0])
+      strcat(kicknick, ",");
+    strcat(kicknick, nick);	/* Add to local queue */
+    k++;
+
+    /* Check if we should send the kick command yet */
+    l = strlen(chan->name) + strlen(kicknick) + strlen(comment);
+    if (((kick_method != 0) && (k == kick_method)) || (l > 480)) {
+      dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, kicknick, comment);
+      k = 0;
+      kicknick[0] = 0;
+    }
+  }
+  /* Clear out all pending kicks in our local kick queue */
+  if (k > 0)
+    dprintf(DP_SERVER, "KICK %s %s :%s\n", chan->name, kicknick, comment);
+  return TCL_OK;
+}
+
+static tcl_cmds tclchan_cmds[] =
+{
+  {"chanlist",		tcl_chanlist},
+  {"botisop",		tcl_botisop},
+  {"botisvoice",	tcl_botisvoice},
+  {"isop",		tcl_isop},
+  {"wasop",		tcl_wasop},
+  {"isvoice",		tcl_isvoice},
+  {"onchan",		tcl_onchan},
+  {"handonchan",	tcl_handonchan},
+  {"ischanban",		tcl_ischanban},
+  {"ischanexempt",	tcl_ischanexempt},
+  {"ischaninvite",	tcl_ischaninvite},
+  {"ischanjuped",	tcl_ischanjuped},
+  {"getchanhost",	tcl_getchanhost},
+  {"onchansplit",	tcl_onchansplit},
+  {"maskhost",		tcl_maskhost},
+  {"getchanidle",	tcl_getchanidle},
+  {"chanbans",		tcl_chanbans},
+  {"chanexempts",	tcl_chanexempts},
+  {"chaninvites",	tcl_chaninvites},
+  {"hand2nick",		tcl_hand2nick},
+  {"nick2hand",		tcl_nick2hand},
+  {"getchanmode",	tcl_getchanmode},
+  {"getchanjoin",	tcl_getchanjoin},
+  {"flushmode",		tcl_flushmode},
+  {"pushmode",		tcl_pushmode},
+  {"resetbans",		tcl_resetbans},
+  {"resetexempts",	tcl_resetexempts},
+  {"resetinvites",	tcl_resetinvites},
+  {"resetchan",		tcl_resetchan},
+  {"topic",		tcl_topic},
+  {"botonchan",		tcl_botonchan},
+  {"putkick",		tcl_putkick},
+  {"channame2dname",	tcl_channame2dname},
+  {"chandname2name",	tcl_chandname2name},
+  {NULL,		NULL}
+};
Index: eggdrop1.7/modules/notes/.cvsignore
diff -u /dev/null eggdrop1.7/modules/notes/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/notes/.cvsignore	Sat Oct 27 11:34:51 2001
@@ -0,0 +1,8 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
Index: eggdrop1.7/modules/notes/Makefile.am
diff -u /dev/null eggdrop1.7/modules/notes/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/notes/Makefile.am	Sat Oct 27 11:34:51 2001
@@ -0,0 +1,15 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:51 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+pkglib_LTLIBRARIES	= notes.la
+notes_la_SOURCES	= notes.c
+notes_la_LDFLAGS	= -module -avoid-version -no-undefined
+notes_la_LIBADD		= @TCL_LIBS@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/notes/cmdsnote.c
diff -u /dev/null eggdrop1.7/modules/notes/cmdsnote.c:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/notes/cmdsnote.c	Sat Oct 27 11:34:51 2001
@@ -0,0 +1,248 @@
+/*
+ * cmdsnote.c -- part of notes.mod
+ *   handles all notes interaction over the party line
+ *
+ * $Id: cmdsnote.c,v 1.1 2001/10/27 16:34:51 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static void cmd_pls_noteign(struct userrec *u, int idx, char *par)
+{
+  struct userrec *u2;
+  char *handle, *mask, *buf, *p;
+  if (!par[0]) {
+    dprintf(idx, "%s: +noteign [handle] <ignoremask>\n", _("Usage"));
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# +noteign %s", dcc[idx].nick, par);
+
+  p = buf = malloc(strlen(par) + 1);
+  strcpy(p, par);
+  handle = newsplit(&p);
+  mask = newsplit(&p);
+  if (mask[0]) {
+    u2 = get_user_by_handle(userlist, handle);
+    if (u != u2) {
+      struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+      get_user_flagrec(u, &fr, dcc[idx].u.chat->con_chan);
+      if (!(glob_master(fr) || glob_owner(fr))) {
+	dprintf(idx, _("You are not allowed to change note ignores for %s\n"), handle);
+	free(buf);
+        return;
+      }
+    }
+    if (!u2) {
+      dprintf(idx, _("User %s does not exist.\n"), handle);
+      free(buf);
+      return;
+    }
+  } else {
+    u2 = u;
+    mask = handle;
+  }
+  if (add_note_ignore(u2, mask))
+    dprintf(idx, _("Now ignoring notes from %s\n"), mask);
+  else
+    dprintf(idx, _("Already ignoring %s\n"), mask);
+  free(buf);
+  return;
+}
+
+static void cmd_mns_noteign(struct userrec *u, int idx, char *par)
+{
+  struct userrec *u2;
+  char *handle, *mask, *buf, *p;
+  if (!par[0]) {
+    dprintf(idx, "%s: -noteign [handle] <ignoremask>\n", _("Usage"));
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# -noteign %s", dcc[idx].nick, par);
+  p = buf = malloc(strlen(par) + 1);
+  strcpy(p, par);
+  handle = newsplit(&p);
+  mask = newsplit(&p);
+  if (mask[0]) {
+    u2 = get_user_by_handle(userlist, handle);
+    if (u != u2) {
+      struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+      get_user_flagrec(u, &fr, dcc[idx].u.chat->con_chan);
+      if (!(glob_master(fr) || glob_owner(fr))) {
+	dprintf(idx, _("You are not allowed to change note ignores for %s\n"), handle);
+	free(buf);
+        return;
+      }
+    }
+    if (!u2) {
+      dprintf(idx, _("User %s does not exist.\n"), handle);
+      free(buf);
+      return;
+    }
+  } else {
+    u2 = u;
+    mask = handle;
+  }
+
+  if (del_note_ignore(u2, mask))
+    dprintf(idx, _("No longer ignoring notes from %s\n"), mask);
+  else
+    dprintf(idx, _("Note ignore %s not found in list.\n"), mask);
+  free(buf);
+  return;
+}
+
+static void cmd_noteigns(struct userrec *u, int idx, char *par)
+{
+  struct userrec *u2;
+  char **ignores;
+  int ignoresn, i;
+
+  if (par[0]) {
+    u2 = get_user_by_handle(userlist, par);
+    if (u != u2) {
+      struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0};
+      get_user_flagrec(u, &fr, dcc[idx].u.chat->con_chan);
+      if (!(glob_master(fr) || glob_owner(fr))) {
+	dprintf(idx, _("You are not allowed to change note ignores for %s\n"), par);
+        return;
+      }
+    }
+    if (!u2) {
+      dprintf(idx, _("User %s does not exist.\n"), par);
+      return;
+    }
+  } else
+    u2 = u;
+
+  ignoresn = get_note_ignores(u2, &ignores);
+  if (!ignoresn) {
+    dprintf(idx, "%s", _("No note ignores present.\n"));
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# noteigns %s", dcc[idx].nick, par);
+  dprintf(idx, _("Note ignores for %s:\n"), u2->handle);
+  for (i = 0; i < ignoresn; i++)
+    dprintf(idx, " %s", ignores[i]);
+  dprintf(idx, "\n");
+  free(ignores[0]);		/* Free the string buffer	*/
+  free(ignores);		/* Free the ptr array		*/
+}
+
+static void cmd_fwd(struct userrec *u, int idx, char *par)
+{
+  char *handle;
+  struct userrec *u1;
+
+  if (!par[0]) {
+    dprintf(idx, "%s: fwd <handle> [user at bot]\n", _("Usage"));
+    return;
+  }
+  handle = newsplit(&par);
+  u1 = get_user_by_handle(userlist, handle);
+  if (!u1) {
+    dprintf(idx, "%s\n", _("No such user."));
+    return;
+  }
+  if ((u1->flags & USER_OWNER) && strcasecmp(handle, dcc[idx].nick)) {
+    dprintf(idx, "%s\n", _("Cant change notes forwarding of the bot owner.\n"));
+    return;
+  }
+  if (!par[0]) {
+    putlog(LOG_CMDS, "*", "#%s# fwd %s", dcc[idx].nick, handle);
+    dprintf(idx, _("Wiped notes forwarding for %s\n"), handle);
+    set_user(&USERENTRY_FWD, u1, NULL);
+    return;
+  }
+  /* Thanks to vertex & dw */
+  if (strchr(par, '@') == NULL) {
+    dprintf(idx, "%s\n", _("You must supply a botname to forward to."));
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# fwd %s %s", dcc[idx].nick, handle, par);
+  dprintf(idx, _("Changed notes forwarding for %s to: %s\n"), handle, par);
+  set_user(&USERENTRY_FWD, u1, par);
+}
+
+static void cmd_notes(struct userrec *u, int idx, char *par)
+{
+  char *fcn;
+
+  if (!par[0]) {
+    dprintf(idx, "%s: notes index\n", _("Usage"));
+    dprintf(idx, "       notes read <# or ALL>\n");
+    dprintf(idx, "       notes erase <# or ALL>\n");
+    dprintf(idx, "       %s\n", _("# may be numbers and/or intervals separated by ;"));
+    dprintf(idx, "       ex: notes erase 2-4;8;16-\n");
+    return;
+  }
+  fcn = newsplit(&par);
+  if (!strcasecmp(fcn, "index"))
+    notes_read(dcc[idx].nick, "", "+", idx);
+  else if (!strcasecmp(fcn, "read")) {
+    if (!strcasecmp(par, "all"))
+      notes_read(dcc[idx].nick, "", "-", idx);
+    else
+      notes_read(dcc[idx].nick, "", par, idx);
+  } else if (!strcasecmp(fcn, "erase")) {
+    if (!strcasecmp(par, "all"))
+      notes_del(dcc[idx].nick, "", "-", idx);
+    else
+      notes_del(dcc[idx].nick, "", par, idx);
+  } else {
+    dprintf(idx, "%s\n", _("Function must be one of INDEX, READ, or ERASE."));
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# notes %s %s", dcc[idx].nick, fcn, par);
+}
+
+static void cmd_note(struct userrec *u, int idx, char *par)
+{
+  char handle[512], *p;
+  int echo;
+
+  p = newsplit(&par);
+  if (!par[0]) {
+    dprintf(idx, "%s: note <to-whom> <message>\n", _("Usage"));
+    return;
+  }
+  while ((*par == ' ') || (*par == '<') || (*par == '>'))
+    par++;			/* These are now illegal *starting* notes
+				 * characters */
+  echo = (dcc[idx].status & STAT_ECHO);
+  splitc(handle, p, ',');
+  while (handle[0]) {
+    rmspace(handle);
+    add_note(handle, dcc[idx].nick, par, idx, echo);
+    splitc(handle, p, ',');
+  }
+  rmspace(p);
+  add_note(p, dcc[idx].nick, par, idx, echo);
+}
+
+static cmd_t notes_cmds[] =
+{
+  {"fwd",	"m",	(Function) cmd_fwd,		NULL},
+  {"notes",	"",	(Function) cmd_notes,		NULL},
+  {"+noteign",	"",	(Function) cmd_pls_noteign,	NULL},
+  {"-noteign",	"",	(Function) cmd_mns_noteign,	NULL},
+  {"noteigns",	"",	(Function) cmd_noteigns,	NULL},
+  {"note",	"",	(Function) cmd_note,		NULL},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
Index: eggdrop1.7/modules/notes/help/msg/notes.help
diff -u /dev/null eggdrop1.7/modules/notes/help/msg/notes.help:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/notes/help/msg/notes.help	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,16 @@
+%{help=notes}
+You must use your password for any NOTES command.
+%b/MSG%b %B %bNOTES%b <password> %bINDEX%b
+   This lists all the notes stored up for you.
+%b/MSG%b %B %bNOTES%b <password> %bREAD%b <# or ALL>
+   This will display some notes for you, or, if you used
+   'READ ALL', it will show you every note stored for you.
+   # may be numbers and/or intervals separated by semicolon.
+     ex: notes read 2-4;8;16-
+%b/MSG%b %B %bNOTES%b <password> %bERASE%b <# or ALL>
+   This works like READ, except it erases whichever note you
+   tell it to (or all of them).
+%b/MSG%b %B %bNOTES%b <password> %bTO%b <nickname> <message...>
+   This stores a note to someone, as long as I know him or
+   her.  They will be informed of a note waiting for them
+   the next time they join the channel.
Index: eggdrop1.7/modules/notes/help/notes.help
diff -u /dev/null eggdrop1.7/modules/notes/help/notes.help:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/notes/help/notes.help	Sat Oct 27 11:34:51 2001
@@ -0,0 +1,69 @@
+%{help=notes}
+###  %bnotes index%b
+###  %bnotes read%b <# or ALL>
+###  %bnotes erase%b <# or ALL>
+   lets you manipulate notes that have been stored up for you while
+   you were gone.  %b'notes index'%b gives a listing of all the notes
+   stored up: who they are from, and when they were left.
+   %b'notes read'%b lets you read some or all notes, according to a
+   list of numbers and/or intervals separated by semicolon.
+   and %b'notes erase'%b erases notes after you are done with them.
+     ex: notes erase 2-4;8;16-
+
+see also: note, whom, noteigns
+%{help=notes module}
+###  help on the %bnotes modules%b
+   This module provides the means for storing & retrieving notes
+   at a later stage.
+   Commands:
+      %bnotes%b%{+m}  %bfwd%b%{-}
+   (use %b'.help notes'%b for more info)
+%{+n}
+   Tcl variables:
+      %bnote-life%b  %bmax-notes%b  %ballow-fwd%b  %bnotefile%b
+   (use %b'.help set <variable>'%b for more info)
+%{help=fwd}%{+m}
+###   %bfwd%b <handle> [desto]
+   This allows you to set a note forwarding address for a user,
+   this means if a note needs to be stored for the user, the
+   bot will attempt to pass it on to the given user at bot, if
+   the bot is not online then the note is still stored locally,
+   if the other user doesn't exist, the note is lost. Boohoo.
+%{help=all}
+###  commands for the %bnotes module%b
+  for users:
+    %bnotes       +noteign        -noteign        noteigns%b
+    %bnote%b
+%{+m}
+  for admins:
+    %bfwd%b
+%{-}
+
+%{help=+noteign}
+###  %b+noteign%b%{+m} [user]%{-} <ignoremask>
+   Add a new ignore to the user's note ignore list.
+
+See also: -noteign noteigns
+%{help=-noteign}
+###  %b-noteign%b%{+m} [user]%{-} <ignoremask>
+   Remove an existing ignore from the user's note ignore list.
+
+See also: +noteign noteigns
+%{help=noteigns}
+###  %bnoteigns%b%{+m} [user]%{-}
+   List all note ignores. All notes sent from users who match one of the
+   ignore masks will be rejected.
+
+See also: +noteign -noteign
+%{help=note}
+###  %bnote%b <nickname[@bot]> <message>
+   sends a private note to a user on the party line.  if that user
+   is currently on the party line, and not marked as away, she will
+   receive the message immediately.  otherwise it may be stored
+   and displayed the next time that user joins the party line.  if
+   you join the channel, and have notes stored for you on the bot,
+   it will tell you.  to send a note to someone on a different bot,
+   use "nick at bot" for the nickname.
+
+see also: whom, notes, noteigns
+
Index: eggdrop1.7/modules/notes/help/set/notes.help
diff -u /dev/null eggdrop1.7/modules/notes/help/set/notes.help:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/notes/help/set/notes.help	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,22 @@
+%{help=set note-life}%{+n}
+### %bset note-life%b <#>
+   sets the number of days to hold a note before expiring it
+   (erasing it from the bot).
+%{help=set max-notes}%{+n}
+### %bset max-notes%b <#>
+   sets the maximum number of notes a user may have stored for them
+   on the bot.  once this is full, no more notes will be accepted
+   for the user.
+%{help=set allow-fwd}%{+n}
+###  %bset allow-fwd%b 0/1
+   This setting determines if note forwarding is permitted. If a
+   note is about to be stored (since the user is note currently
+   online) and they have a forwarding setting and this value is
+   1 the note will be forwarded to the appropriate user.
+%{help=set notefile}%{+n}
+###  %bset notefile%b <filename>
+   specifies the filename where bots should store notes that
+   are sent to someone who isn't on the party line currently.
+   normally, you don't want to change this after the first
+   time you load the bot.  if you want to disable note storage,
+   set this to "" (blank), or unload the notes module.
Index: eggdrop1.7/modules/notes/modinfo
diff -u /dev/null eggdrop1.7/modules/notes/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/notes/modinfo	Sat Oct 27 11:34:51 2001
@@ -0,0 +1,6 @@
+DESC:This provides support for storing of notes for users from each other.
+DESC:Notes between currently online users is supported in the core, this is
+DESC:only for storing the notes for later retrieval, direct user->user
+DESC:notes are built-in.
+DESC:
+DESC:You will normally want to ENABLE this module.
Index: eggdrop1.7/modules/notes/notes.c
diff -u /dev/null eggdrop1.7/modules/notes/notes.c:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/notes/notes.c	Sat Oct 27 11:34:51 2001
@@ -0,0 +1,1246 @@
+/*
+ * notes.c -- part of notes.mod
+ *   reading and sending notes
+ *   killing old notes and changing the destinations
+ *   note cmds
+ *   note ignores
+ *
+ * $Id: notes.c,v 1.1 2001/10/27 16:34:51 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define MODULE_NAME "notes"
+#define MAKING_NOTES
+#include <fcntl.h>
+#include <sys/stat.h> /* chmod(..) */
+#include "lib/eggdrop/module.h"
+#include "src/tandem.h"
+#undef global
+#include "notes.h"
+
+#define start notes_LTX_start
+
+static int maxnotes = 50;	/* Maximum number of notes to allow stored
+				 * for each user */
+static int note_life = 60;	/* Number of DAYS a note lives */
+static char notefile[121];	/* Name of the notefile */
+static int allow_fwd = 0;	/* Allow note forwarding */
+static int notify_users = 0;	/* Notify users they have notes every hour? */
+static int notify_onjoin = 1;   /* Notify users they have notes on join?
+				   drummer */
+static Function *global = NULL;	/* DAMN fcntl.h */
+
+static bind_table_t *BT_dcc, *BT_load, *BT_away, *BT_nkch, *BT_chon;
+
+static struct user_entry_type USERENTRY_FWD =
+{
+  NULL,				/* always 0 ;) */
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  fwd_display,
+  "FWD"
+};
+
+#include "cmdsnote.c"
+
+
+static void fwd_display(int idx, struct user_entry *e)
+{
+  if (dcc[idx].user && (dcc[idx].user->flags & USER_BOTMAST))
+    dprintf(idx, _("  Forward notes to: %.70s\n"), e->u.string);
+}
+
+/* Determine how many notes are waiting for a user.
+ */
+static int num_notes(char *user)
+{
+  int tot = 0;
+  FILE *f;
+  char s[513], *to, *s1;
+
+  if (!notefile[0])
+    return 0;
+  f = fopen(notefile, "r");
+  if (f == NULL)
+    return 0;
+  while (!feof(f)) {
+    fgets(s, 512, f);
+    if (!feof(f)) {
+      if (s[strlen(s) - 1] == '\n')
+	s[strlen(s) - 1] = 0;
+      rmspace(s);
+      if ((s[0]) && (s[0] != '#') && (s[0] != ';')) {	/* Not comment */
+	s1 = s;
+	to = newsplit(&s1);
+	if (!strcasecmp(to, user))
+	  tot++;
+      }
+    }
+  }
+  fclose(f);
+  return tot;
+}
+
+/* Change someone's handle.
+ */
+static void notes_change(char *oldnick, char *newnick)
+{
+  FILE *f, *g;
+  char s[513], *to, *s1;
+  int tot = 0;
+
+  if (!strcasecmp(oldnick, newnick))
+    return;
+  if (!notefile[0])
+    return;
+  f = fopen(notefile, "r");
+  if (f == NULL)
+    return;
+  sprintf(s, "%s~new", notefile);
+  g = fopen(s, "w");
+  if (g == NULL) {
+    fclose(f);
+    return;
+  }
+  chmod(s, userfile_perm);	/* Use userfile permissions. */
+  while (!feof(f)) {
+    fgets(s, 512, f);
+    if (!feof(f)) {
+      if (s[strlen(s) - 1] == '\n')
+	s[strlen(s) - 1] = 0;
+      rmspace(s);
+      if ((s[0]) && (s[0] != '#') && (s[0] != ';')) {	/* Not comment */
+	s1 = s;
+	to = newsplit(&s1);
+	if (!strcasecmp(to, oldnick)) {
+	  tot++;
+	  fprintf(g, "%s %s\n", newnick, s1);
+	} else
+	  fprintf(g, "%s %s\n", to, s1);
+      } else
+	fprintf(g, "%s\n", s);
+    }
+  }
+  fclose(f);
+  fclose(g);
+  unlink(notefile);
+  sprintf(s, "%s~new", notefile);
+  movefile(s, notefile);
+  putlog(LOG_MISC, "*", _("Switched %d note%s from %s to %s."), tot, tot == 1 ? "" : "s",
+         oldnick, newnick);
+}
+
+/* Get rid of old useless notes.
+ */
+static void expire_notes()
+{
+  FILE *f, *g;
+  char s[513], *to, *from, *ts, *s1;
+  int tot = 0, lapse;
+
+  if (!notefile[0])
+    return;
+  f = fopen(notefile, "r");
+  if (f == NULL)
+    return;
+  sprintf(s, "%s~new", notefile);
+  g = fopen(s, "w");
+  if (g == NULL) {
+    fclose(f);
+    return;
+  }
+  chmod(s, userfile_perm);	/* Use userfile permissions. */
+  while (!feof(f)) {
+    fgets(s, 512, f);
+    if (!feof(f)) {
+      if (s[strlen(s) - 1] == '\n')
+	s[strlen(s) - 1] = 0;
+      rmspace(s);
+      if ((s[0]) && (s[0] != '#') && (s[0] != ';')) {	/* Not comment */
+	s1 = s;
+	to = newsplit(&s1);
+	from = newsplit(&s1);
+	ts = newsplit(&s1);
+	lapse = (now - (time_t) atoi(ts)) / 86400;
+	if (lapse > note_life)
+	  tot++;
+	else if (!get_user_by_handle(userlist, to))
+	  tot++;
+	else
+	  fprintf(g, "%s %s %s %s\n", to, from, ts, s1);
+      } else
+	fprintf(g, "%s\n", s);
+    }
+  }
+  fclose(f);
+  fclose(g);
+  unlink(notefile);
+  sprintf(s, "%s~new", notefile);
+  movefile(s, notefile);
+  if (tot > 0)
+    putlog(LOG_MISC, "*", _("Expired %d note%s"), tot, tot == 1 ? "" : "s");
+}
+
+/* Add note to notefile.
+ */
+static int tcl_storenote STDVAR
+{
+  FILE *f;
+  int idx;
+  char u[20], *f1, *to = NULL, work[1024];
+  struct userrec *ur;
+  struct userrec *ur2;
+
+  BADARGS(5, 5, " from to msg idx");
+  idx = findanyidx(atoi(argv[4]));
+  ur = get_user_by_handle(userlist, argv[2]);
+  if (ur && allow_fwd && (f1 = get_user(&USERENTRY_FWD, ur))) {
+    char fwd[161], fwd2[161], *f2, *p, *q, *r;
+    int ok = 1;
+    /* User is valid & has a valid forwarding address */
+     strcpy(fwd, f1);		/* Only 40 bytes are stored in the userfile */
+     p = strchr(fwd, '@');
+    if (p && !strcasecmp(p + 1, botnetnick)) {
+      *p = 0;
+      if (!strcasecmp(fwd, argv[2]))
+	/* They're forwarding to themselves on the same bot, llama's */
+	ok = 0;
+      strcpy(fwd2, fwd);
+      splitc(fwd2, fwd2, '@');
+      /* Get the user record of the user that we're forwarding to locally */
+      ur2 = get_user_by_handle(userlist, fwd2);
+      if (!ur2)
+	ok = 0;
+      if ((f2 = get_user(&USERENTRY_FWD, ur2))) {
+	strcpy(fwd2, f2);
+	splitc(fwd2, fwd2, '@');
+	if (!strcasecmp(fwd2, argv[2]))
+	/* They're forwarding to someone who forwards back to them! */
+	ok = 0;
+      }
+      p = NULL;
+    }
+    if ((argv[1][0] != '@') && ((argv[3][0] == '<') || (argv[3][0] == '>')))
+       ok = 0;			/* Probablly fake pre 1.3 hax0r */
+
+    if (ok && (!p || in_chain(p + 1))) {
+      if (p)
+	p++;
+      q = argv[3];
+      while (ok && q && (q = strchr(q, '<'))) {
+	q++;
+	if ((r = strchr(q, ' '))) {
+	  *r = 0;
+	  if (!strcasecmp(fwd, q))
+	    ok = 0;
+	  *r = ' ';
+	}
+      }
+      if (ok) {
+	if (p && strchr(argv[1], '@')) {
+	  simple_sprintf(work, "<%s@%s >%s %s", argv[2], botnetnick,
+			 argv[1], argv[3]);
+	  simple_sprintf(u, "@%s", botnetnick);
+	  p = u;
+	} else {
+	  simple_sprintf(work, "<%s@%s %s", argv[2], botnetnick,
+			 argv[3]);
+	  p = argv[1];
+	}
+      }
+    } else
+      ok = 0;
+    if (ok) {
+      if ((add_note(fwd, p, work, idx, 0) == NOTE_OK) && (idx >= 0))
+	dprintf(idx, _("Not online; forwarded to %s.\n"), f1);
+      Tcl_AppendResult(irp, f1, NULL);
+      to = NULL;
+    } else {
+      strcpy(work, argv[3]);
+      to = argv[2];
+    }
+  } else
+    to = argv[2];
+  if (to) {
+    if (notefile[0] == 0) {
+      if (idx >= 0)
+	dprintf(idx, "%s\n", _("Notes are not supported by this bot."));
+    } else if (num_notes(to) >= maxnotes) {
+      if (idx >= 0)
+	dprintf(idx, "%s\n", _("Sorry, that user has too many notes already."));
+    } else {			/* Time to unpack it meaningfully */
+      f = fopen(notefile, "a");
+      if (f == NULL)
+	f = fopen(notefile, "w");
+      if (f == NULL) {
+	if (idx >= 0)
+	  dprintf(idx, "%s\n", _("Cant create notefile.  Sorry."));
+	putlog(LOG_MISC, "*", "%s", _("Notefile unreachable!"));
+      } else {
+	char *p, *from = argv[1];
+	int l = 0;
+
+	chmod(notefile, userfile_perm);	/* Use userfile permissions. */
+	while ((argv[3][0] == '<') || (argv[3][0] == '>')) {
+	  p = newsplit(&(argv[3]));
+	  if (*p == '<')
+	    l += simple_sprintf(work + l, "via %s, ", p + 1);
+	  else if (argv[1][0] == '@')
+	    from = p + 1;
+	}
+	fprintf(f, "%s %s %lu %s%s\n", to, from, now,
+		l ? work : "", argv[3]);
+	fclose(f);
+	if (idx >= 0)
+	  dprintf(idx, "%s.\n", _("Stored message"));
+      }
+    }
+  }
+  return TCL_OK;
+}
+
+/* Convert a string like "2-4;8;16-"
+ * in an array {2, 4, 8, 8, 16, maxnotes, -1}
+ */
+static void notes_parse(int dl[], char *s)
+{
+  int i = 0;
+  int idl = 0;
+
+  do {
+    while (s[i] == ';')
+      i++;
+    if (s[i]) {
+      if (s[i] == '-')
+	dl[idl] = 1;
+      else
+	dl[idl] = atoi(s + i);
+      idl++;
+      while ((s[i]) && (s[i] != '-') && (s[i] != ';'))
+	i++;
+      if (s[i] == '-') {
+	dl[idl] = atoi(s + i + 1);	/* Will be 0 if not a number */
+	if (dl[idl] == 0)
+	  dl[idl] = maxnotes;
+      } else
+	dl[idl] = dl[idl - 1];
+      idl++;
+      while ((s[i]) && (s[i] != ';'))
+	i++;
+    }
+  }
+  while ((s[i]) && (idl < 124));
+  dl[idl] = -1;
+}
+
+/* Return true if 'in' is in intervals of 'dl'
+ */
+static int notes_in(int dl[], int in)
+{
+  int i = 0;
+
+  while (dl[i] != -1) {
+    if ((dl[i] <= in) && (in <= dl[i + 1]))
+      return 1;
+    i += 2;
+  }
+  return 0;
+}
+
+static int tcl_erasenotes STDVAR
+{
+  FILE *f, *g;
+  char s[601], *to, *s1;
+  int read, erased;
+  int nl[128];			/* Is it enough ? */
+
+  BADARGS(3, 3, " handle noteslist#");
+  if (!get_user_by_handle(userlist, argv[1])) {
+    Tcl_AppendResult(irp, "-1", NULL);
+    return TCL_OK;
+  }
+  if (!notefile[0]) {
+    Tcl_AppendResult(irp, "-2", NULL);
+    return TCL_OK;
+  }
+  f = fopen(notefile, "r");
+  if (f == NULL) {
+    Tcl_AppendResult(irp, "-2", NULL);
+    return TCL_OK;
+  }
+  sprintf(s, "%s~new", notefile);
+  g = fopen(s, "w");
+  if (g == NULL) {
+    fclose(f);
+    Tcl_AppendResult(irp, "-2", NULL);
+    return TCL_OK;
+  }
+  chmod(s, userfile_perm);	/* Use userfile permissions. */
+  read = 0;
+  erased = 0;
+  notes_parse(nl, (argv[2][0] == 0) ? "-" : argv[2]);
+  while (!feof(f)) {
+    fgets(s, 600, f);
+    if (s[strlen(s) - 1] == '\n')
+      s[strlen(s) - 1] = 0;
+    if (!feof(f)) {
+      rmspace(s);
+      if ((s[0]) && (s[0] != '#') && (s[0] != ';')) {	/* Not comment */
+	s1 = s;
+	to = newsplit(&s1);
+	if (!strcasecmp(to, argv[1])) {
+	  read++;
+	  if (!notes_in(nl, read)) {
+	    fprintf(g, "%s %s\n", to, s1);
+	  } else {
+	    erased++;
+	  }
+	} else {
+	  fprintf(g, "%s %s\n", to, s1);
+	}
+      }
+    }
+  }
+  sprintf(s, "%d", erased);
+  Tcl_AppendResult(irp, s, NULL);
+  fclose(f);
+  fclose(g);
+  unlink(notefile);
+  sprintf(s, "%s~new", notefile);
+  movefile(s, notefile);
+  return TCL_OK;
+}
+
+static int tcl_listnotes STDVAR
+{
+  int i, numnotes;
+  int ln[128];			/* Is it enough? */
+  char s[8];
+
+  BADARGS(3, 3, " handle noteslist#");
+  if (!get_user_by_handle(userlist, argv[1])) {
+    Tcl_AppendResult(irp, "-1", NULL);
+    return TCL_OK;
+  }
+  numnotes = num_notes(argv[1]);
+  notes_parse(ln, argv[2]);
+  for (i = 1; i <= numnotes; i++) {
+    if (notes_in(ln, i)) {
+      sprintf(s, "%d", i);
+      Tcl_AppendElement(irp, s);
+    }
+  }
+  return TCL_OK;
+}
+
+/*
+ * srd="+" : index
+ * srd="-" : read all msgs
+ * else    : read msg in list : (ex: .notes read 5-9;12;13;18-)
+ * idx=-1  : /msg
+ */
+static void notes_read(char *hand, char *nick, char *srd, int idx)
+{
+  FILE *f;
+  char s[601], *to, *dt, *from, *s1, wt[100];
+  time_t tt;
+  int ix = 1;
+  int ir = 0;
+  int rd[128];			/* Is it enough ? */
+  int i;
+
+  if (srd[0] == 0)
+    srd = "-";
+  if (!notefile[0]) {
+    if (idx >= 0)
+      dprintf(idx, "%s.\n", _("You have no messages"));
+    else
+      dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("You have no messages"));
+    return;
+  }
+  f = fopen(notefile, "r");
+  if (f == NULL) {
+    if (idx >= 0)
+      dprintf(idx, "%s.\n", _("You have no messages"));
+    else
+      dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("You have no messages"));
+    return;
+  }
+  notes_parse(rd, srd);
+  while (!feof(f)) {
+    fgets(s, 600, f);
+    i = strlen(s);
+    if (i && s[i - 1] == '\n')
+      s[i - 1] = 0;
+    if (!feof(f)) {
+      rmspace(s);
+      if ((s[0]) && (s[0] != '#') && (s[0] != ';')) {	/* Not comment */
+	s1 = s;
+	to = newsplit(&s1);
+	if (!strcasecmp(to, hand)) {
+	  int lapse;
+
+	  from = newsplit(&s1);
+	  dt = newsplit(&s1);
+	  tt = atoi(dt);
+	  strftime(wt, 14, "%b %d %H:%M", localtime(&tt));
+	  dt = wt;
+	  lapse = (int) ((now - tt) / 86400);
+	  if (lapse > note_life - 7) {
+	    if (lapse >= note_life)
+	      strcat(dt, _(" -- EXPIRES TODAY"));
+	    else
+	      sprintf(&dt[strlen(dt)], _(" -- EXPIRES IN %d DAY%s"), note_life - lapse,
+		      (note_life - lapse) == 1 ? "" : "S");
+	  }
+	  if (srd[0] == '+') {
+	    if (idx >= 0) {
+	      if (ix == 1)
+		dprintf(idx, "### %s:\n", _("You have the following notes waiting"));
+	      dprintf(idx, "  %2d. %s (%s)\n", ix, from, dt);
+	    } else {
+	      dprintf(DP_HELP, "NOTICE %s :%2d. %s (%s)\n",
+		      nick, ix, from, dt);
+	    }
+	  } else if (notes_in(rd, ix)) {
+	    if (idx >= 0)
+	      dprintf(idx, "%2d. %s (%s): %s\n", ix, from, dt, s1);
+	    else
+	      dprintf(DP_HELP, "NOTICE %s :%2d. %s (%s): %s\n",
+		      nick, ix, from, dt, s1);
+	    ir++;
+	  }
+	  ix++;
+	}
+      }
+    }
+  }
+  fclose(f);
+  if ((srd[0] != '+') && (ir == 0) && (ix > 1)) {
+    if (idx >= 0)
+      dprintf(idx, "%s.\n", _("You dont have that many messages"));
+    else
+      dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("You dont have that many messages"));
+  }
+  if (srd[0] == '+') {
+    if (ix == 1) {
+      if (idx >= 0)
+	dprintf(idx, "%s.\n", _("You have no messages"));
+      else
+	dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("You have no messages"));
+    } else {
+      if (idx >= 0)
+	dprintf(idx, "### %s.\n", _("Use .notes read to read them."));
+      else
+	dprintf(DP_HELP, "NOTICE %s :(%d %s)\n", nick, ix - 1, "total");
+    }
+  } else if ((ir == 0) && (ix == 1)) {
+    if (idx >= 0)
+      dprintf(idx, "%s.\n", _("You have no messages"));
+    else
+      dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("You have no messages"));
+  }
+}
+
+/*
+ * sdl="-" : erase all msgs
+ * else    : erase msg in list : (ex: .notes erase 2-4;8;16-)
+ * idx=-1  : /msg
+ */
+static void notes_del(char *hand, char *nick, char *sdl, int idx)
+{
+  FILE *f, *g;
+  char s[513], *to, *s1;
+  int in = 1;
+  int er = 0;
+  int dl[128];			/* Is it enough ? */
+
+  if (sdl[0] == 0)
+    sdl = "-";
+  if (!notefile[0]) {
+    if (idx >= 0)
+      dprintf(idx, "%s.\n", _("You have no messages"));
+    else
+      dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("You have no messages"));
+    return;
+  }
+  f = fopen(notefile, "r");
+  if (f == NULL) {
+    if (idx >= 0)
+      dprintf(idx, "%s.\n", _("You have no messages"));
+    else
+      dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("You have no messages"));
+    return;
+  }
+  sprintf(s, "%s~new", notefile);
+  g = fopen(s, "w");
+  if (g == NULL) {
+    if (idx >= 0)
+      dprintf(idx, "%s. :(\n", _("Cant modify the note file"));
+    else
+      dprintf(DP_HELP, "NOTICE %s :%s. :(\n", nick, _("Cant modify the note file"));
+    fclose(f);
+    return;
+  }
+  chmod(s, userfile_perm);	/* Use userfile permissions. */
+  notes_parse(dl, sdl);
+  while (!feof(f)) {
+    fgets(s, 512, f);
+    if (s[strlen(s) - 1] == '\n')
+      s[strlen(s) - 1] = 0;
+    if (!feof(f)) {
+      rmspace(s);
+      if ((s[0]) && (s[0] != '#') && (s[0] != ';')) {	/* Not comment */
+	s1 = s;
+	to = newsplit(&s1);
+	if (!strcasecmp(to, hand)) {
+	  if (!notes_in(dl, in))
+	    fprintf(g, "%s %s\n", to, s1);
+	  else
+	    er++;
+	  in++;
+	} else
+	  fprintf(g, "%s %s\n", to, s1);
+      } else
+	fprintf(g, "%s\n", s);
+    }
+  }
+  fclose(f);
+  fclose(g);
+  unlink(notefile);
+  sprintf(s, "%s~new", notefile);
+  movefile(s, notefile);
+  if ((er == 0) && (in > 1)) {
+    if (idx >= 0)
+      dprintf(idx, "%s.\n", _("You dont have that many messages"));
+    else
+      dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("You dont have that many messages"));
+  } else if (in == 1) {
+    if (idx >= 0)
+      dprintf(idx, "%s.\n", _("You have no messages"));
+    else
+      dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("You have no messages"));
+  } else {
+    if (er == (in - 1)) {
+      if (idx >= 0)
+	dprintf(idx, "%s.\n", _("Erased all notes"));
+      else
+	dprintf(DP_HELP, "NOTICE %s :%s.\n", nick, _("Erased all notes"));
+    } else {
+      if (idx >= 0)
+	dprintf(idx, "%s %d note%s; %d %s.\n", _("Erased"), er,
+		er > 1 ? "s" : "", in - 1 - er, _("left"));
+      else
+	dprintf(DP_HELP, "NOTICE %s :%s %d note%s; %d %s.\n", nick, _("Erased"),
+		er, (er > 1) ? "s" : "", in - 1 - er, _("left"));
+    }
+  }
+}
+
+static int tcl_notes STDVAR
+{
+  FILE *f;
+  char s[601], *to, *from, *dt, *s1;
+  int count, read, nl[128];	/* Is it enough? */
+  char *list[3], *p;
+
+  BADARGS(2, 3, " handle ?noteslist#?");
+  if (!get_user_by_handle(userlist, argv[1])) {
+    Tcl_AppendResult(irp, "-1", NULL);
+    return TCL_OK;
+  }
+  if (argc == 2) {
+    sprintf(s, "%d", num_notes(argv[1]));
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  }
+  if (!notefile[0]) {
+    Tcl_AppendResult(irp, "-2", NULL);
+    return TCL_OK;
+  }
+  f = fopen(notefile, "r");
+  if (f == NULL) {
+    Tcl_AppendResult(irp, "-2", NULL);
+    return TCL_OK;
+  }
+  count = 0;
+  read = 0;
+  notes_parse(nl, (argv[2][0] == 0) ? "-" : argv[2]);
+  while (!feof(f)) {
+    fgets(s, 600, f);
+    if (s[strlen(s) - 1] == '\n')
+      s[strlen(s) - 1] = 0;
+    if (!feof(f)) {
+      rmspace(s);
+      if ((s[0]) && (s[0] != '#') && (s[0] != ';')) {	/* Not comment */
+	s1 = s;
+	to = newsplit(&s1);
+	if (!strcasecmp(to, argv[1])) {
+	  read++;
+	  if (notes_in(nl, read)) {
+	    count++;
+	    from = newsplit(&s1);
+	    dt = newsplit(&s1);
+	    list[0] = from;
+	    list[1] = dt;
+	    list[2] = s1;
+	    p = Tcl_Merge(3, list);
+	    Tcl_AppendElement(irp, p);
+	    Tcl_Free((char *) p);
+	  }
+	}
+      }
+    }
+  }
+  if (count == 0)
+    Tcl_AppendResult(irp, "0", NULL);
+  fclose(f);
+  return TCL_OK;
+}
+
+
+/* notes <pass> <func>
+ */
+static int msg_notes(char *nick, char *host, struct userrec *u, char *par)
+{
+  char *pwd, *fcn;
+
+  if (!u)
+    return 0;
+  if (u->flags & (USER_BOT | USER_COMMON))
+    return 1;
+  if (!par[0]) {
+    dprintf(DP_HELP, "NOTICE %s :%s: NOTES [pass] INDEX\n", nick, _("Usage"));
+    dprintf(DP_HELP, "NOTICE %s :       NOTES [pass] TO <hand> <msg>\n", nick);
+    dprintf(DP_HELP, "NOTICE %s :       NOTES [pass] READ <# or ALL>\n", nick);
+    dprintf(DP_HELP, "NOTICE %s :       NOTES [pass] ERASE <# or ALL>\n", nick);
+    dprintf(DP_HELP, "NOTICE %s :       %s\n", nick, _("# may be numbers and/or intervals separated by ;"));
+    dprintf(DP_HELP, "NOTICE %s :       ex: NOTES mypass ERASE 2-4;8;16-\n", nick);
+    return 1;
+  }
+  if (!u_pass_match(u, "-")) {
+    /* they have a password set */
+    pwd = newsplit(&par);
+    if (!u_pass_match(u, pwd))
+      return 0;
+  }
+  fcn = newsplit(&par);
+  if (!strcasecmp(fcn, "INDEX"))
+    notes_read(u->handle, nick, "+", -1);
+  else if (!strcasecmp(fcn, "READ")) {
+    if (!strcasecmp(par, "ALL"))
+      notes_read(u->handle, nick, "-", -1);
+    else
+      notes_read(u->handle, nick, par, -1);
+  } else if (!strcasecmp(fcn, "ERASE")) {
+    if (!strcasecmp(par, "ALL"))
+      notes_del(u->handle, nick, "-", -1);
+    else
+      notes_del(u->handle, nick, par, -1);
+  } else if (!strcasecmp(fcn, "TO")) {
+    char *to;
+    int i;
+    FILE *f;
+    struct userrec *u2;
+
+    to = newsplit(&par);
+    if (!par[0]) {
+      dprintf(DP_HELP, "NOTICE %s :%s: NOTES [pass] TO <hand> <message>\n",
+	      nick, _("Usage"));
+      return 0;
+    }
+    u2 = get_user_by_handle(userlist, to);
+    if (!u2) {
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("I don't know anyone by that name.\n"));
+      return 1;
+    } else if (is_bot(u2)) {
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Thats a bot.  You cant leave notes for a bot."));
+      return 1;
+    }
+    for (i = 0; i < dcc_total; i++) {
+      if ((!strcasecmp(dcc[i].nick, to)) &&
+	  (dcc[i].type->flags & DCT_GETNOTES)) {
+	int aok = 1;
+
+	if (dcc[i].type->flags & DCT_CHAT)
+	  if (dcc[i].u.chat->away != NULL)
+	    aok = 0;
+	if (!(dcc[i].type->flags & DCT_CHAT))
+	  aok = 0;		/* Assume non dcc-chat == something weird, so
+				 * store notes for later */
+	if (aok) {
+	  dprintf(i, "\007%s [%s]: %s\n", u->handle, _("Outside note"), par);
+	  dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Note delivered."));
+	  return 1;
+	}
+      }
+    }
+    if (notefile[0] == 0) {
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Notes are not supported by this bot."));
+      return 1;
+    }
+    f = fopen(notefile, "a");
+    if (f == NULL)
+      f = fopen(notefile, "w");
+    if (f == NULL) {
+      dprintf(DP_HELP, "NOTICE %s :%s", nick, _("Cant create notefile.  Sorry."));
+      putlog(LOG_MISC, "*", "* %s", _("Notefile unreachable!"));
+      return 1;
+    }
+    chmod(notefile, userfile_perm);	/* Use userfile permissions. */
+    fprintf(f, "%s %s %lu %s\n", to, u->handle, now, par);
+    fclose(f);
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Note delivered."));
+    return 1;
+  } else
+    dprintf(DP_HELP, "NOTICE %s :%s INDEX, READ, ERASE, TO\n",
+	    nick, _("Use .notes read to read them."));
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! NOTES %s %s", nick, host, u->handle, fcn,
+	 par[0] ? "..." : "");
+  return 1;
+}
+
+static void notes_hourly()
+{
+  expire_notes();
+  if (notify_users) {
+    register struct chanset_t	*chan;
+    register memberlist		*m;
+    int				 k;
+    register int		 l;
+    char			 s1[256];
+    struct userrec		*u;
+
+    for (chan = chanset; chan; chan = chan->next) {
+      for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+	sprintf(s1, "%s!%s", m->nick, m->userhost);
+	u = get_user_by_host(s1);
+	if (u) {
+	  k = num_notes(u->handle);
+	  for (l = 0; l < dcc_total; l++)
+	    if ((dcc[l].type->flags & DCT_CHAT) &&
+		!strcasecmp(dcc[l].nick, u->handle)) {
+	      k = 0;		/* They already know they have notes */
+	      break;
+	    }
+	  if (k) {
+	    dprintf(DP_HELP, "NOTICE %s :You have %d note%s waiting on %s.\n",
+		    m->nick, k, k == 1 ? "" : "s", botname);
+	    dprintf(DP_HELP, "NOTICE %s :%s /MSG %s NOTES [pass] INDEX\n",
+		        m->nick, _("For a list:"), botname);
+	  }
+	}
+      }
+    }
+    for (l = 0; l < dcc_total; l++) {
+      k = num_notes(dcc[l].nick);
+      if ((k > 0) && (dcc[l].type->flags & DCT_CHAT)) {
+	dprintf(l, _("### You have %d note%s waiting.\n"), k, k == 1 ? "" : "s");
+	dprintf(l, _("### Use '.notes read' to read them.\n"));
+      }
+    }
+  }
+}
+
+static void away_notes(char *bot, int sock, char *msg)
+{
+  int idx = findanyidx(sock);
+
+  if (strcasecmp(bot, botnetnick))
+    return;
+  if (msg && msg[0])
+    dprintf(idx, "%s\n", _("Notes will be stored."));
+  else
+    notes_read(dcc[idx].nick, 0, "+", idx);
+}
+
+static int chon_notes(char *nick, int idx)
+{
+  if (dcc[idx].type == &DCC_CHAT)
+    notes_read(nick, 0, "+", idx);
+  return 0;
+}
+
+static void join_notes(char *nick, char *uhost, char *handle, char *par)
+{
+  int i = -1, j;
+  struct chanset_t *chan = chanset;
+
+  if (notify_onjoin) { /* drummer */
+    for (j = 0; j < dcc_total; j++)
+      if ((dcc[j].type->flags & DCT_CHAT)
+	  && (!strcasecmp(dcc[j].nick, handle))) {
+	return;			/* They already know they have notes */
+      }
+
+    while (!chan) {
+      if (ismember(chan, nick))
+        return;			/* They already know they have notes */
+      chan = chan->next;
+    }
+
+    i = num_notes(handle);
+    if (i) {
+      dprintf(DP_HELP, _("NOTICE %s :You have %d note%s waiting on %s.\n"), nick, i, i == 1 ? "" : "s",
+	      botname);
+      dprintf(DP_HELP, "NOTICE %s :%s /MSG %s NOTES [pass] INDEX\n",
+	      nick, _("For a list:"), botname);
+    }
+  }
+}
+
+/* Return either NULL or a pointer to the xtra_key structure
+ * where the not ignores are kept.
+ */
+static struct xtra_key *getnotesentry(struct userrec *u)
+{
+  struct user_entry *ue = find_user_entry(&USERENTRY_XTRA, u);
+  struct xtra_key *xk, *nxk = NULL;
+
+  if (!ue)
+    return NULL;
+  /* Search for the notes ignore list entry */
+  for (xk = ue->u.extra; xk; xk = xk->next)
+    if (xk->key && !strcasecmp(xk->key, NOTES_IGNKEY)) {
+      nxk = xk;
+      break;
+    }
+  if (!nxk || !nxk->data || !(nxk->data[0]))
+    return NULL;
+  return nxk;
+}
+
+/* Parse the NOTES_IGNKEY xtra field. You must free the memory allocated
+ * in here: the buffer 'ignores[0]' and the array 'ignores'.
+ */
+int get_note_ignores(struct userrec *u, char ***ignores)
+{
+  struct xtra_key *xk;
+  char *buf, *p;
+  int ignoresn;
+
+  /* Hullo? sanity? */
+  if (!u)
+    return 0;
+  xk = getnotesentry(u);
+  if (!xk)
+    return 0;
+
+  rmspace(xk->data);
+  malloc_strcpy(buf, xk->data);
+  p = buf;
+
+  /* Split up the string into small parts */
+  *ignores = malloc(sizeof(char *) + 100);
+  **ignores = p;
+  ignoresn = 1;
+  while ((p = strchr(p, ' ')) != NULL) {
+    *ignores = realloc(*ignores, sizeof(char *) * (ignoresn+1));
+    (*ignores)[ignoresn] = p + 1;
+    ignoresn++;
+    *p = 0;
+    p++;
+  }
+  return ignoresn;
+}
+
+int add_note_ignore(struct userrec *u, char *mask)
+{
+  struct xtra_key *xk;
+  char **ignores;
+  int ignoresn, i;
+
+  ignoresn = get_note_ignores(u, &ignores);
+  if (ignoresn > 0) {
+    /* Search for existing mask */
+    for (i = 0; i < ignoresn; i++)
+      if (!strcmp(ignores[i], mask)) {
+        free(ignores[0]);	/* Free the string buffer	*/
+        free(ignores);		/* Free the ptr array		*/
+	/* The mask already exists, exit. */
+        return 0;
+      }
+    free(ignores[0]);		/* Free the string buffer	*/
+    free(ignores);		/* Free the ptr array		*/
+  }
+
+  xk = getnotesentry(u);
+  /* First entry? */
+  if (!xk) {
+    struct xtra_key *mxk = malloc(sizeof(struct xtra_key));
+    struct user_entry *ue = find_user_entry(&USERENTRY_XTRA, u);
+
+    if (!ue)
+      return 0;
+    mxk->next = 0;
+    malloc_strcpy(mxk->data, mask);
+    malloc_strcpy(mxk->key, NOTES_IGNKEY);
+    xtra_set(u, ue, mxk);
+  } else { /* ... else, we already have other entries. */
+    xk->data = realloc(xk->data, strlen(xk->data) + strlen(mask) + 2);
+    strcat(xk->data, " ");
+    strcat(xk->data, mask);
+  }
+  return 1;
+}
+
+int del_note_ignore(struct userrec *u, char *mask)
+{
+  struct user_entry *ue;
+  struct xtra_key *xk;
+  char **ignores, *buf = NULL;
+  int ignoresn, i, size = 0, foundit = 0;
+
+  ignoresn = get_note_ignores(u, &ignores);
+  if (!ignoresn)
+    return 0;
+
+  buf = malloc(1);
+  buf[0] = 0;
+  for (i = 0; i < ignoresn; i++) {
+    if (strcmp(ignores[i], mask)) {
+      size += strlen(ignores[i]);
+      if (buf[0])
+	size++;
+      buf = realloc(buf, size + 1);
+      if (buf[0])
+	strcat(buf, " ");
+      strcat(buf, ignores[i]);
+    } else
+      foundit = 1;
+  }
+  free(ignores[0]);		/* Free the string buffer	*/
+  free(ignores);		/* Free the ptr array		*/
+  /* Entry not found */
+  if (!foundit) {
+    free(buf);
+    return 0;
+  }
+  ue = find_user_entry(&USERENTRY_XTRA, u);
+  /* Delete the entry if the buffer is empty */
+
+  xk = malloc(sizeof(struct xtra_key));
+  xk->key = malloc(strlen(NOTES_IGNKEY)+1);
+  xk->next = 0;
+
+  if (!buf[0]) {
+    free(buf); /* The allocated byte needs to be free'd too */
+    strcpy(xk->key, NOTES_IGNKEY);
+    xk->data = 0;
+  } else {
+    xk->data = buf;
+    strcpy(xk->key, NOTES_IGNKEY);
+  }
+  xtra_set(u, ue, xk);
+  return 1;
+}
+
+/* Returns 1 if the user u has an note ignore which
+ * matches from
+ */
+int match_note_ignore(struct userrec *u, char *from)
+{
+  char **ignores;
+  int ignoresn, i;
+
+  ignoresn = get_note_ignores(u, &ignores);
+  if (!ignoresn)
+    return 0;
+  for (i = 0; i < ignoresn; i++)
+    if (wild_match(ignores[i], from)) {
+      free(ignores[0]);
+      free(ignores);
+      return 1;
+    }
+  free(ignores[0]);		/* Free the string buffer	*/
+  free(ignores);		/* Free the ptr array		*/
+  return 0;
+}
+
+
+static cmd_t notes_join[] =
+{
+  {"*",		"",	(Function) join_notes,		"notes"},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+static cmd_t notes_nkch[] =
+{
+  {"*",		"",	(Function) notes_change,	"notes"},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+static cmd_t notes_away[] =
+{
+  {"*",		"",	(Function) away_notes,		"notes"},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+static cmd_t notes_chon[] =
+{
+  {"*",		"",	(Function) chon_notes,		"notes"},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+static cmd_t notes_msgs[] =
+{
+  {"notes",	"",	(Function) msg_notes,		NULL},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+static tcl_ints notes_ints[] =
+{
+  {"note-life",		&note_life},
+  {"max-notes",		&maxnotes},
+  {"allow-fwd",		&allow_fwd},
+  {"notify-users",	&notify_users},
+  {"notify-onjoin",	&notify_onjoin},
+  {NULL,		NULL}
+};
+
+static tcl_strings notes_strings[] =
+{
+  {"notefile",		notefile,		120,	0},
+  {NULL,		NULL,			0,	0}
+};
+
+static tcl_cmds notes_tcls[] =
+{
+  {"notes",		tcl_notes},
+  {"erasenotes",	tcl_erasenotes},
+  {"listnotes",		tcl_listnotes},
+  {"storenote",		tcl_storenote},
+  {NULL,		NULL}
+};
+
+static int notes_irc_setup(char *mod)
+{
+  bind_table_t *table;
+
+  if ((table = find_bind_table2("join")))
+    add_builtins2(table, notes_join);
+  return 0;
+}
+
+static int notes_server_setup(char *mod)
+{
+  bind_table_t *table;
+
+  if ((table = find_bind_table2("msg")))
+    add_builtins2(table, notes_msgs);
+  return 0;
+}
+
+static cmd_t notes_load[] =
+{
+  {"server",	"",	notes_server_setup,		"notes:server"},
+  {"irc",	"",	notes_irc_setup,		"notes:irc"},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+static char *notes_close()
+{
+  bind_table_t *table;
+
+  rem_tcl_ints(notes_ints);
+  rem_tcl_strings(notes_strings);
+  rem_tcl_commands(notes_tcls);
+  if ((table = find_bind_table2("msg")))
+    rem_builtins2(table, notes_msgs);
+  if ((table = find_bind_table2("join")))
+    rem_builtins2(table, notes_join);
+  if (BT_dcc) rem_builtins2(BT_dcc, notes_cmds);
+  if (BT_chon) rem_builtins2(BT_chon, notes_chon);
+  if (BT_away) rem_builtins2(BT_away, notes_away);
+  if (BT_nkch) rem_builtins2(BT_nkch, notes_nkch);
+  if (BT_load) rem_builtins2(BT_load, notes_load);
+  rem_help_reference("notes.help");
+  del_hook(HOOK_MATCH_NOTEREJ, (Function) match_note_ignore);
+  del_hook(HOOK_HOURLY, (Function) notes_hourly);
+  del_entry_type(&USERENTRY_FWD);
+  module_undepend(MODULE_NAME);
+  return NULL;
+}
+
+static void notes_report(int idx, int details)
+{
+  if (details) {
+    if (notefile[0])
+      dprintf(idx, "    Notes can be stored, in: %s\n", notefile);
+    else
+      dprintf(idx, "    Notes can not be stored.\n");
+  }
+}
+
+EXPORT_SCOPE char *start();
+
+static Function notes_table[] =
+{
+  (Function) start,
+  (Function) notes_close,
+  (Function) 0,
+  (Function) notes_report,
+  (Function) cmd_note,
+};
+
+char *start(Function * global_funcs)
+{
+
+  global = global_funcs;
+
+  notefile[0] = 0;
+  module_register(MODULE_NAME, notes_table, 2, 1);
+  if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+    module_undepend(MODULE_NAME);
+    return "This module requires eggdrop1.7.0 or later";
+  }
+  add_hook(HOOK_HOURLY, (Function) notes_hourly);
+  add_hook(HOOK_MATCH_NOTEREJ, (Function) match_note_ignore);
+  add_tcl_ints(notes_ints);
+  add_tcl_strings(notes_strings);
+  add_tcl_commands(notes_tcls);
+
+  BT_dcc = find_bind_table2("dcc");
+  BT_load = find_bind_table2("load");
+  BT_away = find_bind_table2("away");
+  BT_nkch = find_bind_table2("nkch");
+  BT_chon = find_bind_table2("chon");
+  if (BT_dcc) add_builtins2(BT_dcc, notes_cmds);
+  if (BT_load) add_builtins2(BT_load, notes_load);
+
+  if (BT_chon) add_builtins2(BT_chon, notes_chon);
+  if (BT_away) add_builtins2(BT_away, notes_away);
+  if (BT_nkch) add_builtins2(BT_nkch, notes_nkch);
+  add_help_reference("notes.help");
+  notes_server_setup(0);
+  notes_irc_setup(0);
+  memcpy(&USERENTRY_FWD, &USERENTRY_INFO, sizeof(void *) * 12);
+  add_entry_type(&USERENTRY_FWD);
+  return NULL;
+}
+
Index: eggdrop1.7/modules/notes/notes.h
diff -u /dev/null eggdrop1.7/modules/notes/notes.h:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/notes/notes.h	Sat Oct 27 11:34:51 2001
@@ -0,0 +1,41 @@
+/*
+ * notes.h -- part of notes.mod
+ *
+ * $Id: notes.h,v 1.1 2001/10/27 16:34:51 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_NOTES_NOTES_H
+#define _EGG_MOD_NOTES_NOTES_H
+
+#define NOTES_IGNKEY "NOTESIGNORE"
+
+#ifdef MAKING_NOTES
+static int get_note_ignores(struct userrec *, char ***);
+static int add_note_ignore(struct userrec *, char *);
+static int del_note_ignore(struct userrec *, char *);
+static int match_note_ignore(struct userrec *, char *);
+static void notes_read(char *, char *, char *, int);
+static void notes_del(char *, char *, char *, int);
+static void fwd_display(int, struct user_entry *);
+#endif				/* MAKING_NOTES */
+
+#endif				/* _EGG_MOD_NOTES_H */
+
Index: eggdrop1.7/modules/perlscript/.cvsignore
diff -u /dev/null eggdrop1.7/modules/perlscript/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/perlscript/.cvsignore	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,9 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
+
Index: eggdrop1.7/modules/perlscript/Makefile.am
diff -u /dev/null eggdrop1.7/modules/perlscript/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/perlscript/Makefile.am	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,23 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:52 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir = $(exec_prefix)/modules
+
+# FIXME: test if there's dependency on gnu make. It shouldn't.
+if EGG_PERLSCRIPT
+  EGG_PRL = perlscript.la
+else
+  EGG_PRL =
+endif
+
+pkglib_LTLIBRARIES  = $(EGG_PRL)
+perlscript_la_SOURCES = perlscript.c
+perlscript_la_LDFLAGS = -module -avoid-version -no-undefined
+perlscript_la_LIBADD  = @PERL_LDFLAGS@ @LIBS@
+
+MAINTAINERCLEANFILES    = Makefile.in
+
+INCLUDES                = -I$(top_builddir) -I$(top_srcdir) @PERL_CCFLAGS@
+
+DEFS = $(EGG_DEBUG) @DEFS@
+
Index: eggdrop1.7/modules/perlscript/perlscript.c
diff -u /dev/null eggdrop1.7/modules/perlscript/perlscript.c:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/perlscript/perlscript.c	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,445 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+#include "lib/eggdrop/module.h"
+#include "src/egglib/mstack.h"
+#include "src/egglib/msprintf.h"
+#include "src/script_api.h"
+
+#define MODULE_NAME "perlscript"
+
+static Function *global = NULL;
+
+static PerlInterpreter *ginterp; /* Our global interpreter. */
+
+static XS(my_command_handler);
+static SV *my_resolve_variable(script_var_t *v);
+
+static int my_load_script(registry_entry_t * entry, char *fname)
+{
+	FILE *fp;
+	char *data;
+	int size, len;
+
+	/* Check the filename and make sure it ends in .pl */
+	len = strlen(fname);
+	if (len < 3 || fname[len-1] != 'l' || fname[len-2] != 'p' || fname[len-3] != '.') {
+		/* Nope, not ours. */
+		return(0);
+	}
+
+	fp = fopen(fname, "r");
+	if (!fp) return (0);
+	fseek(fp, 0, SEEK_END);
+	size = ftell(fp);
+	data = (char *)malloc(size + 1);
+	fseek(fp, 0, SEEK_SET);
+	fread(data, size, 1, fp);
+	data[size] = 0;
+	fclose(fp);
+	eval_pv(data, TRUE);
+	if (SvTRUE(ERRSV)) {
+		char *msg;
+		int len;
+
+		msg = SvPV(ERRSV, len);
+		putlog(LOG_MISC, "*", "Perl error: %s", msg);
+	}
+	free(data);
+	return(0);
+}
+
+static int my_perl_callbacker(script_callback_t *me, ...)
+{
+	int retval, i, n, count;
+	script_var_t var;
+	SV *arg;
+	int *al;
+	dSP;
+
+	ENTER;
+	SAVETMPS;
+	PUSHMARK(SP);
+
+	al = (int *)&me;
+	al++;
+	if (me->syntax) n = strlen(me->syntax);
+	else n = 0;
+	for (i = 0; i < n; i++) {
+		var.type = me->syntax[i];
+		var.value = (void *)al[i];
+		var.len = -1;
+		arg = my_resolve_variable(&var);
+		XPUSHs(sv_2mortal(arg));
+	}
+	PUTBACK;
+
+	count = call_sv((SV *)me->callback_data, G_EVAL|G_SCALAR);
+
+	SPAGAIN;
+
+	if (SvTRUE(ERRSV)) {
+		char *msg;
+		int len;
+
+		msg = SvPV(ERRSV, len);
+		retval = POPi;
+		putlog(LOG_MISC, "*", "Perl error: $s", msg);
+	}
+	if (count > 0) {
+		retval = POPi;
+	}
+	else retval = 0;
+
+	PUTBACK;
+	FREETMPS;
+	LEAVE;
+
+	/* If it's a one-time callback, delete it. */
+	if (me->flags & SCRIPT_CALLBACK_ONCE) me->delete(me);
+
+	return(retval);
+}
+
+static int my_perl_cb_delete(script_callback_t *me)
+{
+	if (me->syntax) free(me->syntax);
+	if (me->name) free(me->name);
+	sv_2mortal((SV *)me->callback_data);
+	SvREFCNT_dec((SV *)me->callback_data);
+	free(me);
+	return(0);
+}
+
+static int my_create_cmd(void *ignore, script_command_t *info)
+{
+	char *cmdname;
+	CV *cv;
+
+	if (info->class && strlen(info->class)) {
+		cmdname = msprintf("%s_%s", info->class, info->name);
+	}
+	else {
+		malloc_strcpy(cmdname, info->name);
+	}
+	cv = newXS(cmdname, my_command_handler, "eggdrop");
+	XSANY.any_i32 = (int) info;
+	free(cmdname);
+
+	return (0);
+}
+
+static SV *my_resolve_variable(script_var_t *v)
+{
+	SV *result;
+
+	switch (v->type & SCRIPT_TYPE_MASK) {
+		case SCRIPT_INTEGER:
+			result = newSViv((int) v->value);
+			break;
+		case SCRIPT_STRING:
+		case SCRIPT_BYTES:
+			if (v->len == -1) v->len = strlen((char *)v->value);
+			result = newSVpv((char *)v->value, v->len);
+			if (v->type & SCRIPT_FREE) free(v->value);
+			break;
+
+/* Save for later when we do arrays again
+	else if (v->type & (SCRIPTING_ARRAY | SCRIPTING_VARRAY)) {
+		AV *array;
+		int i;
+
+		array = newAV();
+		for (i = 0; i < v->len; i++) {
+			SV *item;
+
+			if (v->type & SCRIPTING_ARRAY) item = my_resolve_variable(v->ptrarray[i]);
+			else item = my_resolve_variable(&v->varray[i]);
+			av_push(array, item);
+		}
+		result = newRV_noinc((SV *)array);
+	}
+end of array code */
+		case SCRIPT_POINTER: {
+			char str[32];
+			int str_len;
+
+			sprintf(str, "#%u", (unsigned int) v->value);
+			str_len = strlen(str);
+			result = newSVpv(str, str_len);
+			break;
+		}
+		case SCRIPT_USER: {
+			char *handle;
+			struct userrec *u;
+			int str_len;
+
+			u = (struct userrec *)v->value;
+			if (u && u->handle) handle = u->handle;
+			else handle = "*";
+
+			str_len = strlen(handle);
+			result = newSVpv(handle, str_len);
+			break;
+		}
+		default:
+			result = &PL_sv_undef;
+	}
+	return(result);
+}
+
+static XS(my_command_handler)
+{
+	dXSARGS;
+	dXSI32;
+
+	/* Now we have an "items" variable for number of args and also an XSANY.any_i32 variable for client data. This isn't what you would call a "well documented" feature of perl heh. */
+
+	script_command_t *cmd = (script_command_t *) XSANY.any_i32;
+	script_var_t retval;
+	SV *result = NULL;
+	mstack_t *args, *cbacks;
+	int i, len, my_err, simple_retval;
+	int skip, nopts;
+	char *syntax;
+	void **al;
+
+	/* Check for proper number of args. */
+	/* -1 means "any number" and implies pass_array. */
+	if (cmd->flags & SCRIPT_VAR_ARGS) i = (cmd->nargs <= items);
+	else i = (cmd->nargs >= 0 && cmd->nargs == items);
+	if (!i) {
+		Perl_croak(aTHX_ cmd->syntax_error);
+		return;
+	}
+
+	/* We want at least 10 items. */
+	args = mstack_new(2*items+10);
+	cbacks = mstack_new(items);
+
+	/* Reserve space for 3 optional args. */
+	mstack_push(args, NULL);
+	mstack_push(args, NULL);
+	mstack_push(args, NULL);
+
+	/* Parse arguments. */
+	syntax = cmd->syntax;
+	if (cmd->flags & SCRIPT_VAR_FRONT) {
+		skip = items - cmd->nargs;
+		if (skip < 0) skip = 0;
+		syntax += skip;
+	}
+	else skip = 0;
+	for (i = skip; i < items; i++) {
+		switch (*syntax++) {
+			case SCRIPT_BYTES: /* Byte-array. */
+			case SCRIPT_STRING: { /* String. */
+				char *val;
+				val = SvPV(ST(i), len);
+				mstack_push(args, (void *)val);
+				break;
+			}
+			case SCRIPT_INTEGER: { /* Integer. */
+				int val;
+				val = SvIV(ST(i));
+				mstack_push(args, (void *)val);
+				break;
+			}
+			case SCRIPT_CALLBACK: { /* Callback. */
+				script_callback_t *cback;
+				char *name;
+
+				cback = (script_callback_t *)calloc(1, sizeof(*cback));
+				cback->callback = (Function) my_perl_callbacker;
+				cback->delete = (Function) my_perl_cb_delete;
+				name = SvPV(ST(i), len);
+				malloc_strcpy(cback->name, name);
+				cback->callback_data = (void *)newSVsv(ST(i));
+				mstack_push(args, cback);
+				break;
+			}
+			case SCRIPT_USER: { /* User. */
+				struct userrec *u;
+				char *handle;
+
+				handle = SvPV(ST(i), len);
+				if (handle) u = get_user_by_handle(userlist, handle);
+				else u = NULL;
+				mstack_push(args, u);
+				break;
+			}
+			case 'l':
+				/* Length of previous string or byte-array. */
+				mstack_push(args, (void *)len);
+				/* Doesn't take up a perl object. */
+				i--;
+				break;
+			default:
+				goto argerror;
+		} /* End of switch. */
+	} /* End of for loop. */
+
+	/* Ok, now we have our args. */
+
+	memset(&retval, 0, sizeof(retval));
+
+	al = (void **)args->stack; /* Argument list shortcut name. */
+	nopts = 0;
+	if (cmd->flags & SCRIPT_PASS_COUNT) {
+		al[2-nopts] = (void *)(args->len - 3);
+		nopts++;
+	}
+	if (cmd->flags & SCRIPT_PASS_RETVAL) {
+		al[2-nopts] = (void *)&retval;
+		nopts++;
+	}
+	if (cmd->flags & SCRIPT_PASS_CDATA) {
+		al[2-nopts] = cmd->client_data;
+		nopts++;
+	}
+	al += (3-nopts);
+	args->len -= (3-nopts);
+
+	if (cmd->flags & SCRIPT_PASS_ARRAY) {
+		simple_retval = cmd->callback(args->len, al);
+	}
+	else {
+		simple_retval = cmd->callback(al[0], al[1], al[2], al[3], al[4], al[5], al[6], al[7], al[8], al[9]);
+	}
+
+	if (!(cmd->flags & SCRIPT_PASS_RETVAL)) {
+		retval.type = cmd->retval_type;
+		retval.len = -1;
+		retval.value = (void *)simple_retval;
+	}
+
+	my_err = retval.type & SCRIPT_ERROR;
+	result = my_resolve_variable(&retval);
+
+	mstack_destroy(args);
+
+	if (result) {
+		XSprePUSH;
+		PUSHs(result);
+		XSRETURN(1);
+	}
+	else {
+		XSRETURN_EMPTY;
+	}
+
+argerror:
+	mstack_destroy(args);
+	for (i = 0; i < cbacks->len; i++) {
+		script_callback_t *cback;
+		cback = (script_callback_t *)cbacks->stack[i];
+		cback->delete(cback);
+	}
+	mstack_destroy(cbacks);
+	Perl_croak(aTHX_ cmd->syntax_error);
+}
+
+static int dcc_cmd_perl(struct userrec *u, int idx, char *text)
+{
+	SV *result;
+	char *msg;
+	int len;
+
+	if (must_be_owner && !(isowner(dcc[idx].nick))) return(0);
+
+	len = strlen(text);
+
+	result = eval_pv(text, FALSE);
+	if (SvTRUE(ERRSV)) {
+		msg = SvPV(ERRSV, len);
+		dprintf(idx, "Perl error: %s\n", msg);
+	}
+	else {
+		msg = SvPV(result, len);
+		dprintf(idx, "Perl result: %s\n", msg);
+	}
+
+	return(0);
+}
+
+static cmd_t my_dcc_cmds[] = {
+	{"perl", "n", (Function) dcc_cmd_perl, NULL},
+	{0}
+};
+
+static registry_simple_chain_t my_functions[] = {
+	{"script", NULL, 0},
+	{"load script", my_load_script, 2},
+	{"create cmd", my_create_cmd, 2},
+	{0}
+};
+
+static void init_xs_stuff()
+{
+	extern void boot_DynaLoader();
+	newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, "eggdrop");
+}
+
+static Function journal_table[] = {
+        (Function)1, /* Version */
+        (Function)SCRIPT_EVENT_MAX, /* Our length */
+        my_load_script,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+        my_create_cmd,
+	NULL
+};
+
+static Function journal_playback;
+static void *journal_playback_h;
+
+EXPORT_SCOPE char *perlscript_LTX_start();
+static char *perlscript_close();
+
+static Function perlscript_table[] = {
+	(Function) perlscript_LTX_start,
+	(Function) perlscript_close,
+	(Function) 0,
+	(Function) 0
+};
+
+char *perlscript_LTX_start(Function *global_funcs)
+{
+	char *embedding[] = {"", "-e", "0"};
+	bind_table_t *BT_dcc;
+
+	global = global_funcs;
+
+	module_register("perlscript", perlscript_table, 1, 2);
+	if (!module_depend("perlscript", "eggdrop", 107, 0)) {
+		module_undepend("perlscript");
+		return "This module requires eggdrop1.7.0 of later";
+	}
+
+	ginterp = perl_alloc();
+	perl_construct(ginterp);
+	perl_parse(ginterp, init_xs_stuff, 3, embedding, NULL);
+	registry_add_simple_chains(my_functions);
+        registry_lookup("script", "playback", &journal_playback, &journal_playback_h);
+        if (journal_playback) journal_playback(journal_playback_h, journal_table);
+
+	BT_dcc = find_bind_table2("dcc");
+	if (BT_dcc) add_builtins2(BT_dcc, my_dcc_cmds);
+	return(NULL);
+}
+
+static char *perlscript_close()
+{
+	bind_table_t *BT_dcc = find_bind_table2("dcc");
+	if (BT_dcc) rem_builtins2(BT_dcc, my_dcc_cmds);
+	PL_perl_destruct_level = 1;
+	perl_destruct(ginterp);
+	perl_free(ginterp);
+	module_undepend("perlscript");
+	return(NULL);
+}
Index: eggdrop1.7/modules/server/.cvsignore
diff -u /dev/null eggdrop1.7/modules/server/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/server/.cvsignore	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,8 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
Index: eggdrop1.7/modules/server/Makefile.am
diff -u /dev/null eggdrop1.7/modules/server/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/server/Makefile.am	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,15 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:52 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+pkglib_LTLIBRARIES	= server.la
+server_la_SOURCES	= server.c
+server_la_LDFLAGS	= -module -avoid-version -no-undefined
+server_la_LIBADD	= @TCL_LIBS@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/server/cmdsserv.c
diff -u /dev/null eggdrop1.7/modules/server/cmdsserv.c:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/server/cmdsserv.c	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,139 @@
+/*
+ * cmdsserv.c -- part of server.mod
+ *   handles commands from a user via dcc that cause server interaction
+ *
+ * $Id: cmdsserv.c,v 1.1 2001/10/27 16:34:52 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static void cmd_servers(struct userrec *u, int idx, char *par)
+{
+  struct server_list *x = serverlist;
+  int i;
+  char s[1024];
+
+  putlog(LOG_CMDS, "*", "#%s# servers", dcc[idx].nick);
+  if (!x) {
+    dprintf(idx, "No servers.\n");
+  } else {
+    dprintf(idx, "My server list:\n");
+    i = 0;
+    for (; x; x = x->next) {
+      snprintf(s, sizeof s, "%14s %20.20s:%-10d",
+		   (i == curserv) ? "I am here ->" : "", x->name,
+		   x->port ? x->port : default_port);
+      if (x->realname)
+	snprintf(s + 46, sizeof s - 46, " (%-.20s)", x->realname);
+      dprintf(idx, "%s\n", s);
+      i++;
+    }
+  }
+}
+
+static void cmd_dump(struct userrec *u, int idx, char *par)
+{
+  if (!(isowner(dcc[idx].nick)) && (must_be_owner == 2)) {
+    dprintf(idx, _("What?  You need .help\n"));
+    return;
+  }
+  if (!par[0]) {
+    dprintf(idx, "Usage: dump <server stuff>\n");
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# dump %s", dcc[idx].nick, par);
+  dprintf(DP_SERVER, "%s\n", par);
+}
+
+static void cmd_jump(struct userrec *u, int idx, char *par)
+{
+  char *other;
+  int port;
+
+  if (par[0]) {
+    other = newsplit(&par);
+    port = atoi(newsplit(&par));
+    if (!port)
+      port = default_port;
+    putlog(LOG_CMDS, "*", "#%s# jump %s %d %s", dcc[idx].nick, other,
+	   port, par);
+    strncpyz(newserver, other, sizeof newserver);
+    newserverport = port;
+    strncpyz(newserverpass, par, sizeof newserverpass);
+  } else
+    putlog(LOG_CMDS, "*", "#%s# jump", dcc[idx].nick);
+  dprintf(idx, "%s...\n", _("Jumping servers..."));
+  cycle_time = 0;
+  nuke_server("changing servers");
+}
+
+static void cmd_clearqueue(struct userrec *u, int idx, char *par)
+{
+  int	msgs;
+
+  if (!par[0]) {
+    dprintf(idx, "Usage: clearqueue <mode|server|help|all>\n");
+    return;
+  }
+  if (!strcasecmp(par, "all")) {
+    msgs = modeq.tot + mq.tot + hq.tot;
+    msgq_clear(&modeq);
+    msgq_clear(&mq);
+    msgq_clear(&hq);
+    double_warned = burst = 0;
+    dprintf(idx, "Removed %d msgs from all queues\n", msgs);
+  } else if (!strcasecmp(par, "mode")) {
+    msgs = modeq.tot;
+    msgq_clear(&modeq);
+    if (mq.tot == 0)
+      burst = 0;
+    double_warned = 0;
+    dprintf(idx, "Removed %d msgs from the %s queue\n", msgs, "mode");
+  } else if (!strcasecmp(par, "help")) {
+    msgs = hq.tot;
+    msgq_clear(&hq);
+    double_warned = 0;
+    dprintf(idx, "Removed %d msgs from the %s queue\n", msgs, "help");
+  } else if (!strcasecmp(par, "server")) {
+    msgs = mq.tot;
+    msgq_clear(&mq);
+    if (modeq.tot == 0)
+      burst = 0;
+    double_warned = 0;
+    dprintf(idx, "Removed %d msgs from the %s queue\n", msgs, "server");
+  } else {
+    dprintf(idx, "Usage: clearqueue <mode|server|help|all>\n");
+    return;
+  }
+  putlog(LOG_CMDS, "*", "#%s# clearqueue %s", dcc[idx].nick, par);
+}
+
+/* Function call should be:
+ *   int cmd_whatever(idx,"parameters");
+ *
+ * As with msg commands, function is responsible for any logging.
+ */
+static cmd_t C_dcc_serv[] =
+{
+  {"dump",		"m",	(Function) cmd_dump,		NULL},
+  {"jump",		"m",	(Function) cmd_jump,		NULL},
+  {"servers",		"o",	(Function) cmd_servers,		NULL},
+  {"clearqueue",	"m",	(Function) cmd_clearqueue,	NULL},
+  {NULL,		NULL,	NULL,				NULL}
+};
Index: eggdrop1.7/modules/server/help/server.help
diff -u /dev/null eggdrop1.7/modules/server/help/server.help:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/server/help/server.help	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,39 @@
+%{help=dump}%{+m}
+###  %bdump%b <text>
+   dumps the text to the server.  keep in mind that this bot doesn't
+   run through ircII, so ircII commands will most likely not work this
+   way.  they need to be raw irc codes. read rfc1459 from ftp.internic.net
+   for more help.
+%{help=jump}%{+m}
+###  %bjump%b [server [port [pass]]]
+   makes the bot jump to another server.  if you don't specify a
+   server, it will jump to the next server in its internal list (see
+   %b'help servers'%b).  if you specify a server, it will jump to that
+   server (default port is 6667), and if that server is not in the
+   internal list already, it will add it.  jumping servers ALWAYS
+   makes the bot lose ops! be careful!
+%{help=clearqueue}%{+m}
+###  %bclearqueue%b <queue>
+   removes all msgs from the specified queue (mode/server/help/all)
+%{help=servers}%{+o|o}
+###  %bservers%b
+   lists the servers that the bot has in its server list.  this is
+   the list it rotates through when changing servers.  it starts
+   with a static list which it loads from its config-file when the
+   bot is booted up.  after that, you can add servers with the
+   %b'.jump'%b command.  the server list will indicate which server the
+   bot is currently on.
+%{help=server module}%{+o|o}
+###  help on the %bserver module%b
+   This module provides all that's need to get a bot on a server and
+   sitting there, it provides server connection, raw irc, private
+   message/notice/ctcp handling.
+   Command(s) avalible: (use %b'.help <command>'%b for more info)
+      %bservers%b  %{+m}%bjump%b  %bdump%b
+%{+n}
+   There is also a list of tcl settings avalible,
+   use %b'.help server settings'%b
+%{help=all}%{+o|o}
+###  commands for the %bserver module%b
+  %bservers%b  %{+m}%bjump%b  %bdump%b
+
Index: eggdrop1.7/modules/server/help/set/server.help
diff -u /dev/null eggdrop1.7/modules/server/help/set/server.help:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/server/help/set/server.help	Sat Oct 27 11:34:53 2001
@@ -0,0 +1,173 @@
+%{help=set altnick}%{+n}
+###  %bset altnick%b <nickname>
+   sets an alternate nickname to use if the preferred nickname
+   (in 'nick') is in use.  if the alternate nickname is also in
+   use, or is blank, the bot will try sticking digits at the end
+   of the preferred nick until it gets one that works.
+
+see also: set nick, set botnet-nick
+%{help=set realname}%{+n}
+###  %bset realname%b <text>
+   specifies the real name field to use on IRC.  this usually
+   shows up in a /WHOIS as the thing in parenthesis after some-
+   one's user at host.  it can be anything you want, but IRC sets
+   a maximum length so it might get cut off if you set it too
+   long.
+
+see also: set username
+%{help=set botnick}%{+n}
+###  %bset botnick%b
+   this read-only variable returns the bots current nickname on irc.
+%{help=set server-timeout}%{+n}
+###  %bset server-timeout%b <seconds>
+   specifies how long to wait when connecting to a server.  if the
+   server doesn't connect after this many seconds, the bot will just
+   hang up and move on to the next server.
+%{help=set server-online}%{+n}
+###  %bset server-online%b
+   This read-only variables returns the unix-time that the bot
+   successfully connected to the server, so if you can read unix
+   time, you'll be in luck ;)
+%{help=set strict-host}%{+n}
+###  %bset strict-host%b <0/1>
+   specifies whether the bot should leave leading ~ in users' host-
+   names.  if this is on, leading ~ will be left in (the bot will
+   differentiate between valid-ident usernames and invalid ones).
+   typically this is set off, since most sites are not running the
+   ident protocol, and therefore may get ~ put in front of their
+   username when their username is valid.
+%{help=set never-give-up}%{+n}
+###  %bset never-give-up%b <0/1>
+   specifies whether the bot should loop forever.  if never-give-up
+   is set false, then the bot will shut down if it goes through the
+   entire server list once without getting a connection.  this is to
+   keep eggdrop from going through an endless loop, if, for example,
+   your machine gets disconnected from the rest of the net.  however,
+   some people prefer their bot to never shut down, no matter what.
+%{help=set keep-nick}%{+n}
+###  %bset keep-nick%b <0/1>
+   specifies whether the bot will keep trying to regain its nickname if
+   it is forced to change nicks.  if this is on, and you change the bot's
+   nick on irc through a TCL script or the %b'.dump'%b command, then the bot
+   will change back to its intended nick within 60 seconds.
+
+see also: set nick, set altnick
+%{help=set strict-servernames}%{+n}
+###  %bset strict-servernames%b <0/1>
+   Some irc servers have different names on the IRC network to
+   what you use to connect, and this name may not resolve,
+   hence the bot no-longer changes the server name to reflect this
+   (it does record it however), enabling this setting will cause
+   the bot to change the entry for the server the name the server
+   thinks is it's name.
+%{help=set check-stoned}%{+n}
+###  %bset check-stoned%b 0/1
+   This setting, when enabled, will cause the bot to ping the server
+   occasionally to make sure it's actually still connected. It also
+   provides a 'lag-meter'. This is slightly broken on IRCNet.
+%{help=set serverror-quit}%{+n}
+###  %bset serverror-quit%b 0/1
+   Enabling this will cause the bot to assume that an ERROR message
+   from the server indicates it's about to drop the connection,
+   and so the bot closes the server connection immediately.
+%{help=set quiet-reject}%{+n}
+###  %bset quiet-reject%b 0/1
+   Some of the message & ctcp commands return negative messages to
+   unknown users, whilst this is polite & usefull, it's also a
+   possible detection method, enabling this will cause the bot
+   to only send positive responses to know users.
+%{help=set max-queue-msg}%{+n}
+###  %bset max-queue-msg%b <#>
+   This sets the maximum number of messages the bot will store in EACH
+   of it's message queues (it has 3) before it considers them full,
+   and starts dropping messages.
+%{help=set trigger-on-ignore}%{+n}
+###  %bset trigger-on-ignore%b 0/1
+   Enadling this will cause ignored users to trigger bindings anyway,
+   normally and ignored user is just that, someone who is ignored.
+%{help=set answer-ctcp}%{+n}
+###  %bset answer-ctcp%b 0/1
+   This sets the maximum number of stacked CTCP's to answer from any
+   given message.
+%{help=set server-cycle-wait}%{+n}
+###  %bset server-cycle-wait%b <#>
+   This sets the number of seconds to wait between successive server
+   connects, this can be used to prevent the throttling problems
+   with ircu & ocnnecting to quickly.
+%{help=set default-port}%{+n}
+###  %bset default-port%b <#>
+   sets the default port the bot tries to connect to when there
+   is not port specified in the server list.
+%{help=set nick}%{+n}
+###  %bset nick%b <nickname>
+   specifies the bot's default nickname to use on IRC and on
+   the botnet, unless botnet-nick is set differently.
+
+see also: set altnick, set botnet-nick
+%{help=set flood-ctcp}%{+n}
+###  %bset flood-ctcp%b <#ctcps>[:<#seconds>]
+   specifies the flood threshold for CTCP queries (to the bot).
+   if the same host dumps more than this many CTCPs to the bot
+   within 60 seconds, they will be placed on automatic ignore.
+   you can also specify the number of seconds to scan by adding
+   that after a colon (:).  if you set the number of ctcps to 0,
+   you disable ctcp flood protection (including avalanche flood
+   protection).
+
+see also: set flood-msg
+%{help=set flood-msg}%{+n}
+###  %bset flood-msg%b <#msgs>[:<#seconds>]
+   specifies the flood threshold for /MSG's. if the same host
+   dumps more than this many /MSG's to the bot within 60 seconds,
+   it is considered a flood and that host is put on automatic
+   ignore.  you can change the scanning time from 60 seconds by
+   specifying the number of seconds after a colon (:).  setting
+   the # of msgs to 0 turns off msg flood protection.
+
+   examples:
+     set flood-msg 10
+       (10 msgs in 60 seconds is a flood)
+     set flood-msg 5:15
+       (5 msgs in 15 seconds is a flood)
+     set flood-msg 0
+       (turns off msg flood protection)
+
+see also: set flood-ctcp
+%{help=set servers}%{+n}
+###  %bset servers%b <server-list>
+   gives the bot a list of servers to use for IRC.  you can change
+   this at any time on the fly, but if the bot's current IRC server
+   (the one it's on right now) isn't in your new list, it will be
+   added, to maintain coherency.  here's an example:
+      set servers {
+        goblin.irc.com:6667
+        dragon.irc.org
+      }
+   note that if you omit the port, it will assume the value of the
+   %bdefault-port%b setting.
+
+see also: default-port
+%{help=set botname}%{+n}
+###  %bset botname%b
+   This read-only variable returns the current nick!user at host of
+   the bot.
+%{help=set server}%{+n}
+###  %bset server%b
+   This read-only variable returns the current server of the bot.
+%{help=set net-type}%{+n}
+###  %bset net-type%b
+   Defines the network the bot uses. 0 = Efnet, 1 = IRCnet, 2 = Undernet,
+   3 = Dalnet, 4 = Others.
+%{help=server settings}%{+n}
+###  Settings for the %bserver module%b
+   This is a list of the tcl variables the can be used to setup
+   the server module, for a list of revelant commands type
+   %b'.help server module'%b.
+     %bserver-online%b        %bnet-type%b       %bnick%b
+     %bnever-give-up%b        %bflood-ctcp%b     %bserver%b
+     %bmax-queue-msg%b        %bstrict-host%b    %bbotnick%b
+     %bserver-timeout%b       %bservers%b        %bkeep-nick%b
+     %bstrict-servernames%b   %bbotname%b        %bquiet-reject%b
+     %bserverror-quit%b       %banswer-ctcp%b    %baltnick%b
+     %btrigger-on-ignore%b    %bdefault-port%b   %brealname%b
+     %bserver-cycle-wait%b    %bcheck-stoned%b   %bflood-msg%b
Index: eggdrop1.7/modules/server/modinfo
diff -u /dev/null eggdrop1.7/modules/server/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/server/modinfo	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,5 @@
+DESC:This module provides the core server support. If you want your bot to
+DESC:connect to servers, this is the module. Hub-only bots will probably not
+DESC:need it.  It is essential for the irc and ctcp modules.
+DESC:
+DESC:If unsure, ENABLE this module.
Index: eggdrop1.7/modules/server/server.c
diff -u /dev/null eggdrop1.7/modules/server/server.c:1.1
--- /dev/null	Sat Oct 27 11:35:17 2001
+++ eggdrop1.7/modules/server/server.c	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,1859 @@
+/*
+ * server.c -- part of server.mod
+ *   basic irc server support
+ *
+ * $Id: server.c,v 1.1 2001/10/27 16:34:52 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define MODULE_NAME "server"
+#define MAKING_SERVER
+#include "lib/eggdrop/module.h"
+#include "server.h"
+
+#define start server_LTX_start
+
+static Function *global = NULL;
+
+static int ctcp_mode;
+static int serv;		/* sock # of server currently */
+static int strict_host;		/* strict masking of hosts ? */
+static char newserver[121];	/* new server? */
+static int newserverport;	/* new server port? */
+static char newserverpass[121];	/* new server password? */
+static time_t trying_server;	/* trying to connect to a server right now? */
+static int server_lag;		/* how lagged (in seconds) is the server? */
+static char altnick[NICKLEN];	/* possible alternate nickname to use */
+static char raltnick[NICKLEN];	/* random nick created from altnick */
+static int curserv;		/* current position in server list: */
+static int flud_thr;		/* msg flood threshold */
+static int flud_time;		/* msg flood time */
+static int flud_ctcp_thr;	/* ctcp flood threshold */
+static int flud_ctcp_time;	/* ctcp flood time */
+static char botuserhost[121];	/* bot's user at host (refreshed whenever the
+				   bot joins a channel) */
+				/* may not be correct user at host BUT it's
+				   how the server sees it */
+static int keepnick;		/* keep trying to regain my intended
+				   nickname? */
+static int nick_juped = 0;	/* True if origbotname is juped(RPL437) (dw) */
+static int check_stoned;	/* Check for a stoned server? */
+static int serverror_quit;	/* Disconnect from server if ERROR
+				   messages received? */
+static int quiet_reject;	/* Quietly reject dcc chat or sends from
+				   users without access? */
+static int waiting_for_awake;	/* set when i unidle myself, cleared when
+				   i get the response */
+static time_t server_online;	/* server connection time */
+static time_t server_cycle_wait;	/* seconds to wait before
+					   re-beginning the server list */
+static char botrealname[121];	/* realname of bot */
+static int server_timeout;	/* server timeout for connecting */
+static int never_give_up;	/* never give up when connecting to servers? */
+static int strict_servernames;	/* don't update server list */
+static struct server_list *serverlist;	/* old-style queue, still used by
+					   server list */
+static int cycle_time;		/* cycle time till next server connect */
+static int default_port;	/* default IRC port */
+static char oldnick[NICKLEN];	/* previous nickname *before* rehash */
+static int trigger_on_ignore;	/* trigger bindings if user is ignored ? */
+static int answer_ctcp;		/* answer how many stacked ctcp's ? */
+static int check_mode_r;	/* check for IRCNET +r modes */
+static int net_type;
+static int resolvserv;		/* in the process of resolving a server host */
+static int double_mode;		/* allow a msgs to be twice in a queue? */
+static int double_server;
+static int double_help;
+static int double_warned;
+static int lastpingtime;	/* IRCNet LAGmeter support -- drummer */
+static char stackablecmds[511];
+static char stackable2cmds[511];
+static time_t last_time;
+static int use_penalties;
+static int use_fastdeq;
+static int nick_len;		/* Maximal nick length allowed on the
+				   network. */
+static int kick_method;
+static int optimize_kicks;
+
+
+static void empty_msgq(void);
+static void next_server(int *, char *, unsigned int *, char *);
+static void disconnect_server(int);
+static char *get_altbotnick(void);
+static int calc_penalty(char *);
+static int fast_deq(int);
+static void check_queues(char *, char *);
+static void parse_q(struct msgq_head *, char *, char *);
+static void purge_kicks(struct msgq_head *);
+static int deq_kick(int);
+static void msgq_clear(struct msgq_head *qh);
+
+/* New bind tables. */
+static bind_table_t *BT_wall, *BT_raw, *BT_notice, *BT_msg, *BT_msgm;
+static bind_table_t *BT_flood, *BT_ctcr, *BT_ctcp;
+
+/* Imported bind tables. */
+static bind_table_t *BT_dcc;
+
+#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
+
+/* Maximum messages to store in each queue. */
+static int maxqmsg;
+static struct msgq_head mq, hq, modeq;
+static int burst;
+
+#include "cmdsserv.c"
+#include "tclserv.c"
+
+
+/*
+ *     Bot server queues
+ */
+
+/* Called periodically to shove out another queued item.
+ *
+ * 'mode' queue gets priority now.
+ *
+ * Most servers will allow 'busts' of upto 5 msgs, so let's put something
+ * in to support flushing modeq a little faster if possible.
+ * 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).
+ */
+static void deq_msg()
+{
+  struct msgq *q;
+  int ok = 0;
+
+  /* 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 (serv < 0)
+    return;
+  /* 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 (deq_kick(DP_MODE)) {
+        burst++;
+        continue;
+      }
+      if (!modeq.head)
+        break;
+      if (fast_deq(DP_MODE)) {
+        burst++;
+        continue;
+      }
+      tputs(serv, modeq.head->msg, modeq.head->len);
+      if (debug_output) {
+	modeq.head->msg[strlen(modeq.head->msg) - 1] = 0; /* delete the "\n" */
+        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++;
+    }
+    if (!modeq.head)
+      modeq.last = 0;
+    return;
+  }
+  /* Send something from the normal msg q even if we're slightly bursting */
+  if (burst > 1)
+    return;
+  if (mq.head) {
+    burst++;
+    if (deq_kick(DP_SERVER))
+      return;
+    if (fast_deq(DP_SERVER))
+      return;
+    tputs(serv, mq.head->msg, mq.head->len);
+    if (debug_output) {
+      mq.head->msg[strlen(mq.head->msg) - 1] = 0; /* delete the "\n" */
+      putlog(LOG_SRVOUT, "*", "[s->] %s", mq.head->msg);
+    }
+    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 (deq_kick(DP_HELP))
+    return;
+  if (fast_deq(DP_HELP))
+    return;
+  tputs(serv, hq.head->msg, hq.head->len);
+  if (debug_output) {
+    hq.head->msg[strlen(hq.head->msg) - 1] = 0; /* delete the "\n" */
+    putlog(LOG_SRVOUT, "*", "[h->] %s", hq.head->msg);
+  }
+  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;
+}
+
+static int calc_penalty(char * msg)
+{
+  char *cmd, *par1, *par2, *par3;
+  register int penalty, i, ii;
+
+  if (!use_penalties &&
+      net_type != NETT_UNDERNET && net_type != NETT_HYBRID_EFNET)
+    return 0;
+  if (msg[strlen(msg) - 1] == '\n')
+    msg[strlen(msg) - 1] = '\0';
+  cmd = newsplit(&msg);
+  if (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;
+  }
+  penalty = (1 + i / 100);
+  if (!strcasecmp(cmd, "KICK")) {
+    par1 = newsplit(&msg); /* channel */
+    par2 = newsplit(&msg); /* victim(s) */
+    par3 = strtok(par2, ",");
+    while (par3) {
+      penalty++;
+      par3 = strtok(NULL, ",");
+    }
+    ii = penalty;
+    par3 = strtok(par1, " ");
+    while (par3) {
+      penalty += ii;
+      par3 = strtok(NULL, ",");
+    }
+  } else if (!strcasecmp(cmd, "MODE")) {
+    i = 0;
+    par1 = newsplit(&msg); /* channel */
+    par2 = newsplit(&msg); /* mode(s) */
+    if (!strlen(par2))
+      i++;
+    while (strlen(par2) > 0) {
+      if (strchr("ntimps", par2[0]))
+        i += 3;
+      else if (!strchr("+-", par2[0]))
+        i += 1;
+      par2++;
+    }
+    while (strlen(msg) > 0) {
+      newsplit(&msg);
+      i += 2;
+    }
+    ii = 0;
+    par3 = strtok(par1, ",");
+    while (par3) {
+      ii++;
+      par3 = strtok(NULL, ",");
+    }
+    penalty += (ii * i);
+  } else if (!strcasecmp(cmd, "TOPIC")) {
+    penalty++;
+    par1 = newsplit(&msg); /* channel */
+    par2 = newsplit(&msg); /* topic */
+    if (strlen(par2) > 0) {  /* topic manipulation => 2 penalty points */
+      par3 = strtok(par1, ",");
+      while (par3) {
+        penalty += 2;
+        strtok(NULL, ",");
+      }
+    }
+  } else if (!strcasecmp(cmd, "PRIVMSG") ||
+	     !strcasecmp(cmd, "NOTICE")) {
+    par1 = newsplit(&msg); /* channel(s)/nick(s) */
+    /* Add one sec penalty for each recipient */
+    par3 = strtok(par1, ",");
+    while (par3) {
+      penalty++;
+      par3 = strtok(NULL, ",");
+    }
+  } else if (!strcasecmp(cmd, "WHO")) {
+    par1 = newsplit(&msg); /* masks */
+    par2 = strtok(par1, ",");
+    while (par2) {
+      if (strlen(par2) > 4)   /* long WHO-masks receive less penalty */
+	penalty += 3;
+      else
+	penalty += 5;
+      par2 = strtok(NULL, ",");
+    }
+  } else if (!strcasecmp(cmd, "AWAY")) {
+    if (strlen(msg) > 0)
+      penalty += 2;
+    else
+      penalty += 1;
+  } else if (!strcasecmp(cmd, "INVITE")) {
+    /* Successful invite receives 2 or 3 penalty points. Let's go
+     * with the maximum.
+     */
+    penalty += 3;
+  } else if (!strcasecmp(cmd, "JOIN")) {
+    penalty += 2;
+  } else if (!strcasecmp(cmd, "PART")) {
+    penalty += 4;
+  } else if (!strcasecmp(cmd, "VERSION")) {
+    penalty += 2;
+  } else if (!strcasecmp(cmd, "TIME")) {
+    penalty += 2;
+  } else if (!strcasecmp(cmd, "TRACE")) {
+    penalty += 2;
+  } else if (!strcasecmp(cmd, "NICK")) {
+    penalty += 3;
+  } else if (!strcasecmp(cmd, "ISON")) {
+    penalty += 1;
+  } else if (!strcasecmp(cmd, "WHOIS")) {
+    penalty += 2;
+  } else if (!strcasecmp(cmd, "DNS")) {
+    penalty += 2;
+  } else
+    penalty++; /* just add standard-penalty */
+  /* Shouldn't happen, but you never know... */
+  if (penalty > 99)
+    penalty = 99;
+  if (penalty < 2) {
+    putlog(LOG_SRVOUT, "*", "Penalty < 2sec, that's impossible!");
+    penalty = 2;
+  }
+  if (debug_output && penalty != 0)
+    putlog(LOG_SRVOUT, "*", "Adding penalty: %i", penalty);
+  return penalty;
+}
+
+static int fast_deq(int which)
+{
+  struct msgq_head *h;
+  struct msgq *m, *nm;
+  char msgstr[511], nextmsgstr[511], tosend[511], victims[511], stackable[511],
+       *msg, *nextmsg, *cmd, *nextcmd, *to, *nextto, *stckbl;
+  int len, doit = 0, found = 0, who_count =0, stack_method = 1;
+
+  if (!use_fastdeq)
+    return 0;
+  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;
+  strncpyz(msgstr, m->msg, sizeof msgstr);
+  msg = msgstr;
+  cmd = newsplit(&msg);
+  if (use_fastdeq > 1) {
+    strncpyz(stackable, stackablecmds, sizeof stackable);
+    stckbl = stackable;
+    while (strlen(stckbl) > 0)
+      if (!strcasecmp(newsplit(&stckbl), cmd)) {
+        found = 1;
+        break;
+      }
+    /* If use_fastdeq is 2, only commands in the list should be stacked. */
+    if (use_fastdeq == 2 && !found)
+      return 0;
+    /* If use_fastdeq is 3, only commands that are _not_ in the list
+     * should be stacked.
+     */
+    if (use_fastdeq == 3 && found)
+      return 0;
+    /* we check for the stacking method (default=1) */
+    strncpyz(stackable, stackable2cmds, sizeof stackable);
+    stckbl = stackable;
+    while (strlen(stckbl) > 0)
+      if (!strcasecmp(newsplit(&stckbl), cmd)) {
+        stack_method = 2;
+        break;
+      }    
+  }
+  to = newsplit(&msg);
+  len = strlen(to);
+  if (to[len - 1] == '\n')
+    to[len -1] = 0;
+  simple_sprintf(victims, "%s", to);
+  while (m) {
+    nm = m->next;
+    if (!nm)
+      break;
+    strncpyz(nextmsgstr, nm->msg, sizeof nextmsgstr);
+    nextmsg = nextmsgstr;
+    nextcmd = newsplit(&nextmsg);
+    nextto = newsplit(&nextmsg);
+    len = strlen(nextto);
+    if (nextto[len - 1] == '\n')
+      nextto[len - 1] = 0;
+    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(msg) + 2) < 510)
+        && (strcasecmp(cmd, "WHO") || who_count < MAXPENALTY - 1)) {
+      if (!strcasecmp(cmd, "WHO"))
+        who_count++;
+      if (stack_method == 1)
+      	simple_sprintf(victims, "%s,%s", victims, nextto);
+      else
+      	simple_sprintf(victims, "%s %s", victims, nextto);
+      doit = 1;
+      m->next = nm->next;
+      if (!nm->next)
+        h->last = m;
+      free(nm->msg);
+      free(nm);
+      h->tot--;
+    } else
+      m = m->next;
+  }
+  if (doit) {
+    simple_sprintf(tosend, "%s %s %s", cmd, victims, msg);
+    len = strlen(tosend);
+    tosend[len - 1] = '\n';
+    tputs(serv, tosend, len);
+    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) {
+      tosend[len - 1] = 0;
+      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);
+    return 1;
+  }
+  return 0;
+}
+
+static void check_queues(char *oldnick, char *newnick)
+{
+  if (optimize_kicks == 2) {
+    if (modeq.head)
+      parse_q(&modeq, oldnick, newnick);
+    if (mq.head)
+      parse_q(&mq, oldnick, newnick);
+    if (hq.head)
+      parse_q(&hq, oldnick, newnick);
+  }
+}
+
+static void parse_q(struct msgq_head *q, char *oldnick, char *newnick)
+{
+  struct msgq *m, *lm = NULL;
+  char buf[511], *msg, *nicks, *nick, *chan, newnicks[511], newmsg[511];
+  int changed;
+
+  for (m = q->head; m;) {
+    changed = 0;
+    if (optimize_kicks == 2 && !strncasecmp(m->msg, "KICK ", 5)) {
+      newnicks[0] = 0;
+      strncpyz(buf, m->msg, sizeof buf);
+      if (buf[0] && (buf[strlen(buf)-1] == '\n'))
+	buf[strlen(buf)-1] = '\0';
+      msg = buf;
+      newsplit(&msg);
+      chan = newsplit(&msg);
+      nicks = newsplit(&msg);
+      nick = strtok(nicks, ",");
+      while (nick) {
+	if (!strcasecmp(nick, oldnick) &&
+	    ((9 + strlen(chan) + strlen(newnicks) + strlen(newnick) +
+	  strlen(nicks) + strlen(msg)) < 510)) {
+	  if (newnick)
+	    snprintf(newnicks, sizeof newnicks, "%s,%s", newnicks, newnick);
+	  changed = 1;
+	} else
+	  snprintf(newnicks, sizeof newnicks, ",%s", nick);
+	nick = strtok(NULL, ",");
+      }
+      snprintf(newmsg, sizeof newmsg, "KICK %s %s %s\n", chan,
+		   newnicks + 1, msg);
+    }
+    if (changed) {
+      if (newnicks[0] == 0) {
+	if (!lm)
+	  q->head = m->next;
+	else
+	  lm->next = m->next;
+	free(m->msg);
+	free(m);
+	m = lm;
+	q->tot--;
+	if (!q->head)
+	  q->last = 0;
+      } else {
+	free(m->msg);
+	m->msg = malloc(strlen(newmsg) + 1);
+	m->len = strlen(newmsg);
+	strcpy(m->msg, newmsg);
+      }
+    }
+    lm = m;
+    if (m)
+      m = m->next;
+    else
+      m = q->head;
+  }
+}
+
+static void purge_kicks(struct msgq_head *q)
+{
+  struct msgq *m, *lm = NULL;
+  char buf[511], *reason, *nicks, *nick, *chan, newnicks[511],
+       newmsg[511], chans[511], *chns, *ch;
+  int changed, found;
+  struct chanset_t *cs;
+
+  for (m = q->head; m;) {
+    if (!strncasecmp(m->msg, "KICK", 4)) {
+      newnicks[0] = 0;
+      changed = 0;
+      strncpyz(buf, m->msg, sizeof buf);
+      if (buf[0] && (buf[strlen(buf)-1] == '\n'))
+	buf[strlen(buf)-1] = '\0';
+      reason = buf;
+      newsplit(&reason);
+      chan = newsplit(&reason);
+      nicks = newsplit(&reason);
+      nick = strtok(nicks, ",");
+      while (nick) {
+	found = 0;
+	strncpyz(chans, chan, sizeof chans);
+	chns = chans;
+	while (strlen(chns) > 0) {
+	  ch = newsplit(&chns);
+	  cs = findchan(ch);
+	  if (!cs)
+	    continue;
+	  if (ismember(cs, nick))
+	    found = 1;
+	}
+	if (found)
+	  snprintf(newnicks, sizeof newnicks, "%s,%s", newnicks, nick);
+	else {
+	  putlog(LOG_SRVOUT, "*", "%s isn't on any target channel, removing "
+		 "kick...", nick);
+	  changed = 1;
+	}
+	nick = strtok(NULL, ",");
+      }
+      if (changed) {
+	if (newnicks[0] == 0) {
+	  if (!lm)
+	    q->head = m->next;
+	  else
+	    lm->next = m->next;
+	  free(m->msg);
+	  free(m);
+	  m = lm;
+	  q->tot--;
+	  if (!q->head)
+	    q->last = 0;
+	} else {
+	  free(m->msg);
+	  snprintf(newmsg, sizeof newmsg, "KICK %s %s %s\n", chan,
+		       newnicks + 1, reason);
+	  m->msg = malloc(strlen(newmsg) + 1);
+	  m->len = strlen(newmsg);
+	  strcpy(m->msg, newmsg);
+	}
+      }
+    }
+    lm = m;
+    if (m)
+      m = m->next;
+    else
+      m = q->head;
+  }
+}
+
+static int deq_kick(int which)
+{
+  struct msgq_head *h;
+  struct msgq *msg, *m, *lm;
+  char buf[511], buf2[511], *reason2, *nicks, *chan, *chan2, *reason, *nick,
+       newnicks[511], newnicks2[511], newmsg[511];
+  int changed = 0, nr = 0;
+
+  if (!optimize_kicks)
+    return 0;
+  newnicks[0] = 0;
+  switch (which) {
+    case DP_MODE:
+      h = &modeq;
+      break;
+    case DP_SERVER:
+      h = &mq;
+      break;
+    case DP_HELP:
+      h = &hq;
+      break;
+    default:
+      return 0;
+  }
+  if (strncasecmp(h->head->msg, "KICK", 4))
+    return 0;
+  if (optimize_kicks == 2) {
+    purge_kicks(h);
+    if (!h->head)
+      return 1;
+  }
+  if (strncasecmp(h->head->msg, "KICK", 4))
+    return 0;
+  msg = h->head;
+  strncpyz(buf, msg->msg, sizeof buf);
+  reason = buf;
+  newsplit(&reason);
+  chan = newsplit(&reason);
+  nicks = newsplit(&reason);
+  while (strlen(nicks) > 0) {
+    snprintf(newnicks, sizeof newnicks, "%s,%s", newnicks,
+		 newsplit(&nicks));
+    nr++;
+  }
+  for (m = msg->next, lm = NULL; m && (nr < kick_method);) {
+    if (!strncasecmp(m->msg, "KICK", 4)) {
+      changed = 0;
+      newnicks2[0] = 0;
+      strncpyz(buf2, m->msg, sizeof buf2);
+      if (buf2[0] && (buf2[strlen(buf2)-1] == '\n'))
+        buf2[strlen(buf2)-1] = '\0';
+      reason2 = buf2;
+      newsplit(&reason2);
+      chan2 = newsplit(&reason2);
+      nicks = newsplit(&reason2);
+      if (!strcasecmp(chan, chan2) && !strcasecmp(reason, reason2)) {
+        nick = strtok(nicks, ",");
+	while (nick) {
+          if ((nr < kick_method) &&
+             ((9 + strlen(chan) + strlen(newnicks) + strlen(nick) +
+             strlen(reason)) < 510)) {
+            snprintf(newnicks, sizeof newnicks, "%s,%s", newnicks, nick);
+            nr++;
+            changed = 1;
+          } else
+            snprintf(newnicks2, sizeof newnicks2, "%s,%s", newnicks2, nick);
+	  nick = strtok(NULL, ",");
+        }
+      }
+      if (changed) {
+        if (newnicks2[0] == 0) {
+          if (!lm)
+            h->head->next = m->next;
+          else
+            lm->next = m->next;
+          free(m->msg);
+          free(m);
+          m = lm;
+          h->tot--;
+          if (!h->head)
+            h->last = 0;
+        } else {
+          free(m->msg);
+          snprintf(newmsg, sizeof newmsg, "KICK %s %s %s\n", chan2,
+		       newnicks2 + 1, reason);
+          m->msg = malloc(strlen(newmsg) + 1);
+          m->len = strlen(newmsg);
+          strcpy(m->msg, newmsg);
+        }
+      }
+    }
+    lm = m;
+    if (m)
+      m = m->next;
+    else
+      m = h->head->next;
+  }
+  snprintf(newmsg, sizeof newmsg, "KICK %s %s %s\n", chan, newnicks + 1,
+	       reason);
+  tputs(serv, newmsg, strlen(newmsg));
+  if (debug_output) {
+    newmsg[strlen(newmsg) - 1] = 0;
+    switch (which) {
+      case DP_MODE:
+        putlog(LOG_SRVOUT, "*", "[m->] %s", newmsg);
+        break;
+      case DP_SERVER:
+        putlog(LOG_SRVOUT, "*", "[s->] %s", newmsg);
+        break;
+      case DP_HELP:
+        putlog(LOG_SRVOUT, "*", "[h->] %s", newmsg);
+        break;
+    }
+    debug3("Changed: %d, kick-method: %d, nr: %d", changed, kick_method, nr);
+  }
+  h->tot--;
+  last_time += calc_penalty(newmsg);
+  m = h->head->next;
+  free(h->head->msg);
+  free(h->head);
+  h->head = m;
+  if (!h->head)
+    h->last = 0;
+  return 1;
+}
+
+/* Clean out the msg queues (like when changing servers).
+ */
+static void empty_msgq()
+{
+  msgq_clear(&modeq);
+  msgq_clear(&mq);
+  msgq_clear(&hq);
+  burst = 0;
+}
+
+/* Use when sending msgs... will spread them out so there's no flooding.
+ */
+static void queue_server(int which, char *buf, int len)
+{
+  struct msgq_head *h = NULL,
+  		    tempq;
+  struct msgq	   *q, *tq, *tqq;
+  int		    doublemsg = 0,
+  		    qnext = 0;
+
+  /* Don't even BOTHER if there's no server online. */
+  if (serv < 0)
+    return;
+  /* No queue for PING and PONG - drummer */
+  if (!strncasecmp(buf, "PING", 4) || !strncasecmp(buf, "PONG", 4)) {
+    if (buf[1] == 'I' || buf[1] == 'i')
+      lastpingtime = now;	/* lagmeter */
+    tputs(serv, buf, len);
+    return;
+  }
+
+  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;
+
+  default:
+    putlog(LOG_MISC, "*", "!!! queueing unknown type to server!!");
+    return;
+  }
+
+  if (h->tot < maxqmsg) {
+    /* Don't queue msg if it's already queued?  */
+    if (!doublemsg)
+      for (tq = tempq.head; tq; tq = tqq) {
+	tqq = 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);
+	    double_warned = 1;
+	  }
+	  return;
+	}
+      }
+
+    q = malloc(sizeof(struct msgq));
+    if (qnext)
+      q->next = h->head;
+    else
+      q->next = NULL;
+    if (h->head) {
+      if (!qnext)
+        h->last->next = q;
+    } else
+      h->head = q;
+    if (qnext)
+       h->head = q;
+    h->last = q;
+    q->len = len;
+    q->msg = malloc(len + 1);
+    strncpyz(q->msg, buf, len + 1);
+    h->tot++;
+    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;
+      }
+    }
+    h->warned = 1;
+  }
+
+  if (debug_output) {
+    if (buf[len - 1] == '\n')
+      buf[len - 1] = 0;
+    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;
+    }
+  }
+
+  if (which == DP_MODE || which == DP_MODE_NEXT)
+    deq_msg();		/* DP_MODE needs to be sent ASAP, flush if
+			   possible. */
+}
+
+/* Add a new server to the server_list.
+ */
+static void add_server(char *ss)
+{
+  struct server_list *x, *z;
+  char *p, *q;
+
+  for (z = serverlist; z && z->next; z = z->next);
+  while (ss) {
+    p = strchr(ss, ',');
+    if (p)
+      *p++ = 0;
+    x = malloc(sizeof(struct server_list));
+
+    x->next = 0;
+    x->realname = 0;
+    if (z)
+      z->next = x;
+    else
+      serverlist = x;
+    z = x;
+    if (*ss == '[') {
+	ss++;
+	if ((q = strchr(ss, ']'))) {
+	    *(q++) = 0;
+	    if (*q != ':')
+		q = 0;
+	}
+    } else
+	q = strchr(ss, ':');
+    if (!q) {
+      x->port = default_port;
+      x->pass = 0;
+      malloc_strcpy(x->name, ss);
+    } else {
+      *(q++) = 0;
+      malloc_strcpy(x->name, ss);
+      ss = q;
+      q = strchr(ss, ':');
+      if (!q) {
+	x->pass = 0;
+      } else {
+	*q++ = 0;
+	malloc_strcpy(x->pass, q);
+      }
+      x->port = atoi(ss);
+    }
+    ss = p;
+  }
+}
+
+/* Clear out the given server_list.
+ */
+static void clearq(struct server_list *xx)
+{
+  struct server_list *x;
+
+  while (xx) {
+    x = xx->next;
+    if (xx->name)
+      free(xx->name);
+    if (xx->pass)
+      free(xx->pass);
+    if (xx->realname)
+      free(xx->realname);
+    free(xx);
+    xx = x;
+  }
+}
+
+/* Set botserver to the next available server.
+ *
+ * -> if (*ptr == -1) then jump to that particular server
+ */
+static void next_server(int *ptr, char *serv, unsigned int *port, char *pass)
+{
+  struct server_list *x = serverlist;
+  int i = 0;
+
+  if (x == NULL)
+    return;
+  /* -1  -->  Go to specified server */
+  if (*ptr == (-1)) {
+    for (; x; x = x->next) {
+      if (x->port == *port) {
+	if (!strcasecmp(x->name, serv)) {
+	  *ptr = i;
+	  return;
+	} else if (x->realname && !strcasecmp(x->realname, serv)) {
+	  *ptr = i;
+	  strncpyz(serv, x->realname, sizeof serv);
+	  return;
+	}
+      }
+      i++;
+    }
+    /* Gotta add it: */
+    x = malloc(sizeof(struct server_list));
+
+    x->next = 0;
+    x->realname = 0;
+    malloc_strcpy(x->name, serv);
+    x->port = *port ? *port : default_port;
+    if (pass && pass[0])
+      malloc_strcpy(x->pass, pass);
+    else
+      x->pass = NULL;
+    list_append((struct list_type **) (&serverlist), (struct list_type *) x);
+    *ptr = i;
+    return;
+  }
+  /* Find where i am and boogie */
+  i = (*ptr);
+  while (i > 0 && x != NULL) {
+    x = x->next;
+    i--;
+  }
+  if (x != NULL) {
+    x = x->next;
+    (*ptr)++;
+  }				/* Go to next server */
+  if (x == NULL) {
+    x = serverlist;
+    *ptr = 0;
+  }				/* Start over at the beginning */
+  strcpy(serv, x->name);
+  *port = x->port ? x->port : default_port;
+  if (x->pass)
+    strcpy(pass, x->pass);
+  else
+    pass[0] = 0;
+}
+
+static int server_6char STDVAR
+{
+  Function F = (Function) cd;
+  char x[20];
+
+  BADARGS(7, 7, " nick user at host handle desto/chan keyword/nick text");
+  CHECKVALIDITY(server_6char);
+  snprintf(x, sizeof x, "%d",
+	       F(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]));
+  Tcl_AppendResult(irp, x, NULL);
+  return TCL_OK;
+}
+
+static int server_5char STDVAR
+{
+  Function F = (Function) cd;
+
+  BADARGS(6, 6, " nick user at host handle channel text");
+  CHECKVALIDITY(server_5char);
+  F(argv[1], argv[2], argv[3], argv[4], argv[5]);
+  return TCL_OK;
+}
+
+static int server_2char STDVAR
+{
+  Function F = (Function) cd;
+
+  BADARGS(3, 3, " nick msg");
+  CHECKVALIDITY(server_2char);
+  F(argv[1], argv[2]);
+  return TCL_OK;
+}
+
+static int server_msg STDVAR
+{
+  Function F = (Function) cd;
+
+  BADARGS(5, 5, " nick uhost hand buffer");
+  CHECKVALIDITY(server_msg);
+  F(argv[1], argv[2], get_user_by_handle(userlist, argv[3]), argv[4]);
+  return TCL_OK;
+}
+
+static int server_raw STDVAR
+{
+  Function F = (Function) cd;
+
+  BADARGS(4, 4, " from code args");
+  CHECKVALIDITY(server_raw);
+  Tcl_AppendResult(irp, int_to_base10(F(argv[1], argv[3])), NULL);
+  return TCL_OK;
+}
+
+/* Read/write normal string variable.
+ */
+static char *nick_change(ClientData cdata, Tcl_Interp *irp, char *name1,
+			 char *name2, int flags)
+{
+  char *new;
+
+  if (flags & (TCL_TRACE_READS | TCL_TRACE_UNSETS)) {
+    Tcl_SetVar2(interp, name1, name2, origbotname, TCL_GLOBAL_ONLY);
+    if (flags & TCL_TRACE_UNSETS)
+      Tcl_TraceVar(irp, name1, TCL_TRACE_READS | TCL_TRACE_WRITES |
+        	   TCL_TRACE_UNSETS, nick_change, cdata);
+  } else {			/* writes */
+    new = Tcl_GetVar2(interp, name1, name2, TCL_GLOBAL_ONLY);
+    if (irccmp(origbotname, new)) {
+      if (origbotname[0]) {
+	putlog(LOG_MISC, "*", "* IRC NICK CHANGE: %s -> %s",
+	       origbotname, new);
+        nick_juped = 0;
+      }
+      strncpyz(origbotname, new, NICKLEN);
+      if (server_online)
+	dprintf(DP_SERVER, "NICK %s\n", origbotname);
+    }
+  }
+  return NULL;
+}
+
+/* Replace all '?'s in s with a random number.
+ */
+static void rand_nick(char *nick)
+{
+  register char *p = nick;
+
+  while ((p = strchr(p, '?')) != NULL) {
+    *p = '0' + random() % 10;
+    p++;
+  }
+}
+
+/* Return the alternative bot nick.
+ */
+static char *get_altbotnick(void)
+{
+  /* A random-number nick? */
+  if (strchr(altnick, '?')) {
+    if (!raltnick[0]) {
+      strncpyz(raltnick, altnick, NICKLEN);
+      rand_nick(raltnick);
+    }
+    return raltnick;
+  } else
+    return altnick;
+}
+
+static char *altnick_change(ClientData cdata, Tcl_Interp *irp, char *name1,
+			    char *name2, int flags)
+{
+  /* Always unset raltnick. Will be regenerated when needed. */
+  raltnick[0] = 0;
+  return NULL;
+}
+
+static char *traced_server(ClientData cdata, Tcl_Interp *irp, char *name1,
+			   char *name2, int flags)
+{
+  char s[1024];
+
+  if (server_online) {
+    int servidx = findanyidx(serv);
+    char *z = strchr(dcc[servidx].host, ':');
+    simple_sprintf(s, "%s%s%s:%u",
+	z ? "[" : "", dcc[servidx].host, z ? "]" : "", dcc[servidx].port);
+  } else
+    s[0] = 0;
+  Tcl_SetVar2(interp, name1, name2, s, TCL_GLOBAL_ONLY);
+  if (flags & TCL_TRACE_UNSETS)
+    Tcl_TraceVar(irp, name1, TCL_TRACE_READS | TCL_TRACE_WRITES |
+		 TCL_TRACE_UNSETS, traced_server, cdata);
+  return NULL;
+}
+
+static char *traced_botname(ClientData cdata, Tcl_Interp *irp, char *name1,
+			    char *name2, int flags)
+{
+  char s[1024];
+
+  simple_sprintf(s, "%s%s%s", botname, 
+      botuserhost[0] ? "!" : "", botuserhost[0] ? botuserhost : "");
+  Tcl_SetVar2(interp, name1, name2, s, TCL_GLOBAL_ONLY);
+  if (flags & TCL_TRACE_UNSETS)
+    Tcl_TraceVar(irp, name1, TCL_TRACE_READS | TCL_TRACE_WRITES |
+		 TCL_TRACE_UNSETS, traced_botname, cdata);
+  return NULL;
+}
+
+static void do_nettype(void)
+{
+  switch (net_type) {
+  case NETT_EFNET:
+    check_mode_r = 0;
+    nick_len = 9;
+    break;
+  case NETT_IRCNET:
+    check_mode_r = 1;
+    use_penalties = 1;
+    use_fastdeq = 3;
+    nick_len = 9;
+    simple_sprintf(stackablecmds, "INVITE AWAY VERSION NICK");
+    kick_method = 4;
+    break;
+  case NETT_UNDERNET:
+    check_mode_r = 0;
+    use_fastdeq = 2;
+    nick_len = 9;
+    simple_sprintf(stackablecmds, "PRIVMSG NOTICE TOPIC PART WHOIS USERHOST USERIP ISON");
+    simple_sprintf(stackable2cmds, "USERHOST USERIP ISON");
+    break;
+  case NETT_DALNET:
+    check_mode_r = 0;
+    use_fastdeq = 2;
+    nick_len = 32;
+    simple_sprintf(stackablecmds, "PRIVMSG NOTICE PART WHOIS WHOWAS USERHOST ISON WATCH DCCALLOW");
+    simple_sprintf(stackable2cmds, "USERHOST ISON WATCH");
+    break;
+  case NETT_HYBRID_EFNET:
+    check_mode_r = 0;
+    nick_len = 9;
+    break;
+  }
+}
+
+static char *traced_nettype(ClientData cdata, Tcl_Interp *irp, char *name1,
+			    char *name2, int flags)
+{
+  do_nettype();
+  return NULL;
+}
+
+static char *traced_nicklen(ClientData cdata, Tcl_Interp *irp, char *name1,
+			    char *name2, int flags)
+{
+  if (flags & (TCL_TRACE_READS | TCL_TRACE_UNSETS)) {
+    char s[40];
+
+    snprintf(s, sizeof s, "%d", nick_len);
+    Tcl_SetVar2(interp, name1, name2, s, TCL_GLOBAL_ONLY);
+    if (flags & TCL_TRACE_UNSETS)
+      Tcl_TraceVar(irp, name1, TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+		   traced_nicklen, cdata);
+  } else {
+    char *cval = Tcl_GetVar2(interp, name1, name2, TCL_GLOBAL_ONLY);
+    long lval = 0;
+
+    if (cval && Tcl_ExprLong(interp, cval, &lval) != TCL_ERROR) {
+      if (lval > NICKMAX)
+	lval = NICKMAX;
+      nick_len = (int) lval;
+    }
+  }
+  return NULL;
+}
+
+static tcl_strings my_tcl_strings[] =
+{
+  {"botnick",			NULL,		0,		STR_PROTECT},
+  {"altnick",			altnick,	NICKMAX,	0},
+  {"realname",			botrealname,	80,		0},
+  {"stackable-commands",	stackablecmds,	510,		0},
+  {"stackable2-commands",	stackable2cmds,	510,		0},
+  {NULL,			NULL,		0,		0}
+};
+
+static tcl_coups my_tcl_coups[] =
+{
+  {"flood-msg",		&flud_thr,		&flud_time},
+  {"flood-ctcp",	&flud_ctcp_thr, 	&flud_ctcp_time},
+  {NULL,		NULL,			NULL}
+};
+
+static tcl_ints my_tcl_ints[] =
+{
+  {"server-timeout",		&server_timeout,		0},
+  {"server-online",		(int *) &server_online,		2},
+  {"never-give-up",		&never_give_up,			0},
+  {"keep-nick",			&keepnick,			0},
+  {"strict-servernames",	&strict_servernames,		0},
+  {"check-stoned",		&check_stoned,			0},
+  {"serverror-quit",		&serverror_quit,		0},
+  {"quiet-reject",		&quiet_reject,			0},
+  {"max-queue-msg",		&maxqmsg,			0},
+  {"trigger-on-ignore",		&trigger_on_ignore,		0},
+  {"answer-ctcp",		&answer_ctcp,			0},
+  {"server-cycle-wait",		(int *) &server_cycle_wait,	0},
+  {"default-port",		&default_port,			0},
+  {"check-mode-r",		&check_mode_r,			0},
+  {"net-type",			&net_type,			0},
+  {"ctcp-mode",			&ctcp_mode,			0},
+  {"double-mode",		&double_mode,			0},/* G`Quann */
+  {"double-server",		&double_server,			0},
+  {"double-help",		&double_help,			0},
+  {"use-penalties",		&use_penalties,			0},
+  {"use-fastdeq",		&use_fastdeq,			0},
+  {"nick-len",			&nick_len,			0},
+  {"optimize-kicks",		&optimize_kicks,		0},
+  {"isjuped",			&nick_juped,			0},
+  {NULL,			NULL,				0}
+};
+
+
+/*
+ *     Tcl variable trace functions
+ */
+
+/* Read or write the server list.
+ */
+static char *tcl_eggserver(ClientData cdata, Tcl_Interp *irp, char *name1,
+			   char *name2, int flags)
+{
+  Tcl_DString ds;
+  char *slist, **list, x[1024];
+  struct server_list *q;
+  int lc, code, i;
+
+  if (flags & (TCL_TRACE_READS | TCL_TRACE_UNSETS)) {
+    /* Create server list */
+    Tcl_DStringInit(&ds);
+    for (q = serverlist; q; q = q->next) {
+      char *z = strchr(q->name, ':');
+      snprintf(x, sizeof x, "%s%s%s:%d%s%s %s",
+                   z ? "[" : "", q->name, z ? "]" : "",
+		   q->port ? q->port : default_port, q->pass ? ":" : "",
+		   q->pass ? q->pass : "", q->realname ? q->realname : "");
+      Tcl_DStringAppendElement(&ds, x);
+    }
+    slist = Tcl_DStringValue(&ds);
+    Tcl_SetVar2(interp, name1, name2, slist, TCL_GLOBAL_ONLY);
+    Tcl_DStringFree(&ds);
+  } else {		/* TCL_TRACE_WRITES */
+    if (serverlist) {
+      clearq(serverlist);
+      serverlist = NULL;
+    }
+    slist = Tcl_GetVar2(interp, name1, name2, TCL_GLOBAL_ONLY);
+    if (slist != NULL) {
+      code = Tcl_SplitList(interp, slist, &lc, &list);
+      if (code == TCL_ERROR)
+	return interp->result;
+      for (i = 0; i < lc && i < 50; i++)
+	add_server(list[i]);
+
+      /* Tricky way to make the bot reset its server pointers
+       * perform part of a '.jump <current-server>':
+       */
+      if (server_online) {
+	int servidx = findanyidx(serv);
+
+	curserv = (-1);
+	next_server(&curserv, dcc[servidx].host, &dcc[servidx].port, "");
+      }
+      Tcl_Free((char *) list);
+    }
+  }
+  return NULL;
+}
+
+/* Trace the servers */
+#define tcl_traceserver(name, ptr) \
+  Tcl_TraceVar(interp, name, TCL_TRACE_READS | TCL_TRACE_WRITES |	\
+	       TCL_TRACE_UNSETS, tcl_eggserver, (ClientData) ptr)
+
+#define tcl_untraceserver(name, ptr) \
+  Tcl_UntraceVar(interp, name, TCL_TRACE_READS | TCL_TRACE_WRITES |	\
+		 TCL_TRACE_UNSETS, tcl_eggserver, (ClientData) ptr)
+
+
+/*
+ *     CTCP DCC CHAT functions
+ */
+
+static void dcc_chat_hostresolved(int);
+
+/* This only handles CHAT requests, otherwise it's handled in filesys.
+ */
+static int ctcp_DCC_CHAT(char *nick, char *from, struct userrec *u,
+			 char *object, char *keyword, char *text)
+{
+  char *action, *param, *ip, *prt, buf[512], *msg = buf;
+  int i;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0};
+  struct in_addr ip4;
+
+  strcpy(msg, text);
+  action = newsplit(&msg);
+  param = newsplit(&msg);
+  ip = newsplit(&msg);
+  prt = newsplit(&msg);
+  if (strcasecmp(action, "CHAT") || strcasecmp(object, botname) || !u)
+    return 0;
+  get_user_flagrec(u, &fr, 0);
+  if (dcc_total == max_dcc) {
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("Sorry, too many DCC connections."));
+    putlog(LOG_MISC, "*", _("DCC connections full: %s %s (%s!%s)"), "CHAT", param, nick, from);
+  } else if (!(glob_party(fr) || (!require_p && chan_op(fr)))) {
+    if (glob_xfer(fr))
+      return 0;			/* Allow filesys to pick up the chat */
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("No access"));
+    putlog(LOG_MISC, "*", "%s: %s!%s", _("Refused DCC chat (no access)"), nick, from);
+  } else if (u_pass_match(u, "-")) {
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, _("You must have a password set."));
+    putlog(LOG_MISC, "*", "%s: %s!%s", _("Refused DCC chat (no password)"), nick, from);
+  } else if (atoi(prt) < 1024 || atoi(prt) > 65535) {
+    /* Invalid port */
+    if (!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s (invalid port)\n", nick,
+	      _("Failed to connect"));
+    putlog(LOG_MISC, "*", "%s: CHAT (%s!%s)", _("DCC invalid port"),
+	   nick, from);
+  } else {
+    i = new_dcc(&DCC_DNSWAIT, sizeof(struct dns_info));
+    if (i < 0) {
+      putlog(LOG_MISC, "*", "DCC connection: CHAT (%s!%s)", dcc[i].nick, ip);
+      return 1;
+    }
+debug1("|SERVER| dcc chat ip: (%s)", ip);
+    if (inet_aton(ip, &ip4))
+	strncpyz(dcc[i].addr, inet_ntoa(ip4), ADDRLEN);
+    else
+	strncpyz(dcc[i].addr, ip, ADDRLEN);
+debug1("|SERVER| addr: (%s)", dcc[i].addr);
+    dcc[i].port = atoi(prt);
+    dcc[i].sock = -1;
+    strcpy(dcc[i].nick, u->handle);
+    strcpy(dcc[i].host, from);
+    dcc[i].timeval = now;
+    dcc[i].user = u;
+    dcc[i].u.dns->host = calloc(1, strlen(dcc[i].addr) + 1);
+    strcpy(dcc[i].u.dns->host, dcc[i].addr);
+    dcc[i].u.dns->dns_type = RES_HOSTBYIP;
+    dcc[i].u.dns->dns_success = dcc_chat_hostresolved;
+    dcc[i].u.dns->dns_failure = dcc_chat_hostresolved;
+    dcc[i].u.dns->type = &DCC_CHAT_PASS;
+    dcc_dnshostbyip(dcc[i].addr);
+  }
+  return 1;
+}
+
+static void dcc_chat_hostresolved(int i)
+{
+  char buf[512];
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0};
+
+  snprintf(buf, sizeof buf, "%d", dcc[i].port);
+  dcc[i].sock = getsock(0);
+  if (dcc[i].sock < 0 ||
+          open_telnet_dcc(dcc[i].sock, dcc[i].addr, buf) < 0) {
+    neterror(buf);
+    if(!quiet_reject)
+      dprintf(DP_HELP, "NOTICE %s :%s (%s)\n", dcc[i].nick,
+	      _("Failed to connect"), buf);
+    putlog(LOG_MISC, "*", "%s: CHAT (%s!%s)", _("DCC connection failed"),
+	   dcc[i].nick, dcc[i].host);
+    putlog(LOG_MISC, "*", "    (%s)", buf);
+    killsock(dcc[i].sock);
+    lostdcc(i);
+  } else {
+    changeover_dcc(i, &DCC_CHAT_PASS, sizeof(struct chat_info));
+    dcc[i].status = STAT_ECHO;
+    get_user_flagrec(dcc[i].user, &fr, 0);
+    if (glob_party(fr))
+      dcc[i].status |= STAT_PARTY;
+    strcpy(dcc[i].u.chat->con_chan, (chanset) ? chanset->dname : "*");
+    dcc[i].timeval = now;
+    /* Ok, we're satisfied with them now: attempt the connect */
+    putlog(LOG_MISC, "*", "DCC connection: CHAT (%s!%s)", dcc[i].nick,
+	   dcc[i].host);
+    dprintf(i, "%s\n", _("Enter your password."));
+  }
+  return;
+}
+
+
+/*
+ *     Server timer functions
+ */
+
+static void server_secondly()
+{
+  if (cycle_time)
+    cycle_time--;
+  deq_msg();
+  if (!resolvserv && serv < 0)
+    connect_server();
+}
+
+static void server_5minutely()
+{
+  if (check_stoned) {
+    if (waiting_for_awake) {
+      /* Uh oh!  Never got pong from last time, five minutes ago!
+       * Server is probably stoned.
+       */
+      int servidx = findanyidx(serv);
+
+      disconnect_server(servidx);
+      lostdcc(servidx);
+      putlog(LOG_SERV, "*", _("Server got stoned; jumping..."));
+    } else if (!trying_server) {
+      /* Check for server being stoned. */
+      dprintf(DP_MODE, "PING :%lu\n", (unsigned long) now);
+      waiting_for_awake = 1;
+    }
+  }
+}
+
+static void server_prerehash()
+{
+  strcpy(oldnick, botname);
+}
+
+static void server_postrehash()
+{
+  strncpyz(botname, origbotname, NICKLEN);
+  if (!botname[0])
+    fatal("NO BOT NAME.", 0);
+  if (serverlist == NULL)
+    fatal("NO SERVER.", 0);
+    if (oldnick[0] && !irccmp(oldnick, botname)
+       && !irccmp(oldnick, get_altbotnick())) {
+    /* Change botname back, don't be premature. */
+    strcpy(botname, oldnick);
+    dprintf(DP_SERVER, "NICK %s\n", origbotname);
+  }
+  /* Change botname back incase we were using altnick previous to rehash. */
+  else if (oldnick[0])
+    strcpy(botname, oldnick);
+  check_bind_event("init-server");
+}
+
+static void server_die()
+{
+  cycle_time = 100;
+  if (server_online) {
+    dprintf(-serv, "QUIT :%s\n", quit_msg[0] ? quit_msg : "");
+    sleep(3); /* Give the server time to understand */
+  }
+  nuke_server(NULL);
+}
+
+/* A report on the module status.
+ */
+static void server_report(int idx, int details)
+{
+  char s1[64], s[128];
+  int servidx;
+
+  if (server_online) {
+    dprintf(idx, "    Online as: %s%s%s (%s)\n", botname,
+        botuserhost[0] ? "!" : "", botuserhost[0] ? botuserhost : "",
+	botrealname);
+    if (nick_juped)
+	dprintf(idx, "    NICK IS JUPED: %s %s\n", origbotname,
+		keepnick ? "(trying)" : "");
+    nick_juped = 0;	/* WHY?? -- drummer */
+
+    daysdur(now, server_online, s1);
+    snprintf(s, sizeof s, "(connected %s)", s1);
+    if ((server_lag) && !(waiting_for_awake)) {
+	if (server_lag == (-1))
+	    snprintf(s1, sizeof s1, " (bad pong replies)");
+	else
+	    snprintf(s1, sizeof s1, " (lag: %ds)", server_lag);
+	strcat(s, s1);
+    }
+  }
+  if ((trying_server || server_online) &&
+          ((servidx = findanyidx(serv)) != (-1))) {
+    dprintf(idx, "    Server %s %d %s\n", dcc[servidx].host, dcc[servidx].port,
+	    trying_server ? "(trying)" : s);
+  } else
+    dprintf(idx, "    %s\n", _("No server currently."));
+  if (modeq.tot)
+    dprintf(idx, "    %s %d%%, %d msgs\n", _("Mode queue is at"),
+            (int) ((float) (modeq.tot * 100.0) / (float) maxqmsg),
+	    (int) modeq.tot);
+  if (mq.tot)
+    dprintf(idx, "    %s %d%%, %d msgs\n", _("Server queue is at"),
+           (int) ((float) (mq.tot * 100.0) / (float) maxqmsg), (int) mq.tot);
+  if (hq.tot)
+    dprintf(idx, "    %s %d%%, %d msgs\n", _("Help queue is at"),
+           (int) ((float) (hq.tot * 100.0) / (float) maxqmsg), (int) hq.tot);
+  if (details) {
+    dprintf(idx, "    Flood is: %d msg/%ds, %d ctcp/%ds\n",
+	    flud_thr, flud_time, flud_ctcp_thr, flud_ctcp_time);
+  }
+}
+
+static void msgq_clear(struct msgq_head *qh)
+{
+  register struct msgq	*q, *qq;
+
+  for (q = qh->head; q; q = qq) {
+    qq = q->next;
+    free(q->msg);
+    free(q);
+  }
+  qh->head = qh->last = NULL;
+  qh->tot = qh->warned = 0;
+}
+
+static cmd_t my_ctcps[] =
+{
+  {"DCC",	"",	ctcp_DCC_CHAT,		"server:DCC"},
+  {NULL,	NULL,	NULL,			NULL}
+};
+
+static char *server_close()
+{
+  cycle_time = 100;
+  nuke_server("Connection reset by peer");
+  clearq(serverlist);
+
+  rem_builtins2(BT_dcc, C_dcc_serv);
+  rem_builtins2(BT_raw, my_raw_binds);
+  rem_builtins2(BT_ctcp, my_ctcps);
+
+  del_bind_table2(BT_wall);
+  del_bind_table2(BT_raw);
+  del_bind_table2(BT_notice);
+  del_bind_table2(BT_msgm);
+  del_bind_table2(BT_msg);
+  del_bind_table2(BT_flood);
+  del_bind_table2(BT_ctcr);
+  del_bind_table2(BT_ctcp);
+  rem_tcl_coups(my_tcl_coups);
+  rem_tcl_strings(my_tcl_strings);
+  rem_tcl_ints(my_tcl_ints);
+  rem_help_reference("server.help");
+  rem_tcl_commands(my_tcl_cmds);
+  Tcl_UntraceVar(interp, "nick",
+		 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+		 nick_change, NULL);
+  Tcl_UntraceVar(interp, "altnick",
+		 TCL_TRACE_WRITES | TCL_TRACE_UNSETS, altnick_change, NULL);
+  Tcl_UntraceVar(interp, "botname",
+		 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+		 traced_botname, NULL);
+  Tcl_UntraceVar(interp, "server",
+		 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+		 traced_server, NULL);
+  Tcl_UntraceVar(interp, "net-type",
+		 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+		 traced_nettype, NULL);
+  Tcl_UntraceVar(interp, "nick-len",
+		 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+		 traced_nicklen, NULL);
+  tcl_untraceserver("servers", NULL);
+  empty_msgq();
+  del_hook(HOOK_SECONDLY, (Function) server_secondly);
+  del_hook(HOOK_5MINUTELY, (Function) server_5minutely);
+  del_hook(HOOK_QSERV, (Function) queue_server);
+  del_hook(HOOK_MINUTELY, (Function) minutely_checks);
+  del_hook(HOOK_PRE_REHASH, (Function) server_prerehash);
+  del_hook(HOOK_REHASH, (Function) server_postrehash);
+  del_hook(HOOK_DIE, (Function) server_die);
+  module_undepend(MODULE_NAME);
+  return NULL;
+}
+
+EXPORT_SCOPE char *start();
+
+static Function server_table[] =
+{
+  (Function) start,
+  (Function) server_close,
+  (Function) 0,
+  (Function) server_report,
+  /* 4 - 7 */
+  (Function) NULL,		/* char * (points to botname later on)	*/
+  (Function) botuserhost,	/* char *				*/
+  (Function) & quiet_reject,	/* int					*/
+  (Function) & serv,		/* int					*/
+  /* 8 - 11 */
+  (Function) & flud_thr,	/* int					*/
+  (Function) & flud_time,	/* int					*/
+  (Function) & flud_ctcp_thr,	/* int					*/
+  (Function) & flud_ctcp_time,	/* int					*/
+  /* 12 - 15 */
+  (Function) match_my_nick,
+  (Function) check_tcl_flud,
+  (Function) NULL,		/* fixfrom - moved to the core (drummer) */
+  (Function) & answer_ctcp,	/* int					*/
+  /* 16 - 19 */
+  (Function) & trigger_on_ignore, /* int				*/
+  (Function) check_tcl_ctcpr,
+  (Function) detect_avalanche,
+  (Function) nuke_server,
+  /* 20 - 23 */
+  (Function) newserver,		/* char *				*/
+  (Function) & newserverport,	/* int					*/
+  (Function) newserverpass,	/* char *				*/
+  /* 24 - 27 */
+  (Function) & cycle_time,	/* int					*/
+  (Function) & default_port,	/* int					*/
+  (Function) & server_online,	/* int					*/
+  (Function) NULL,		/* min_servs - removed useless feature	*/
+  /* 28 - 31 */
+  (Function) 0,		/* p_tcl_bind_list			*/
+  (Function) 0,		/* p_tcl_bind_list			*/
+  (Function) 0,		/* p_tcl_bind_list			*/
+  (Function) 0,		/* p_tcl_bind_list			*/
+  /* 32 - 35 */
+  (Function) 0,		/* p_tcl_bind_list			*/
+  (Function) 0,		/* p_tcl_bind_list			*/
+  (Function) 0,		/* p_tcl_bind_list			*/
+  (Function) 0,		/* p_tcl_bind_list			*/
+  /* 36 - 39 */
+  (Function) ctcp_reply,
+  (Function) get_altbotnick,	/* char *				*/
+  (Function) & nick_len,	/* int					*/
+  (Function) check_tcl_notc
+};
+
+char *start(Function *global_funcs)
+{
+  char *s;
+
+  global = global_funcs;
+
+  /*
+   * Init of all the variables *must* be done in _start rather than
+   * globally.
+   */
+  serv = -1;
+  strict_host = 1;
+  botname[0] = 0;
+  trying_server = 0L;
+  server_lag = 0;
+  altnick[0] = 0;
+  raltnick[0] = 0;
+  curserv = 0;
+  flud_thr = 5;
+  flud_time = 60;
+  flud_ctcp_thr = 3;
+  flud_ctcp_time = 60;
+  botuserhost[0] = 0;
+  keepnick = 1;
+  check_stoned = 1;
+  serverror_quit = 1;
+  quiet_reject = 1;
+  waiting_for_awake = 0;
+  server_online = 0;
+  server_cycle_wait = 60;
+  strcpy(botrealname, "A deranged product of evil coders");
+  server_timeout = 60;
+  never_give_up = 0;
+  strict_servernames = 0;
+  serverlist = NULL;
+  cycle_time = 0;
+  default_port = 6667;
+  oldnick[0] = 0;
+  trigger_on_ignore = 0;
+  answer_ctcp = 1;
+  check_mode_r = 0;
+  maxqmsg = 300;
+  burst = 0;
+  net_type = NETT_EFNET;
+  double_mode = 0;
+  double_server = 0;
+  double_help = 0;
+  use_penalties = 0;
+  use_fastdeq = 0;
+  stackablecmds[0] = 0;
+  strcpy(stackable2cmds, "USERHOST ISON");
+  resolvserv = 0;
+  lastpingtime = 0;
+  last_time = 0;
+  nick_len = 9;
+  kick_method = 1;
+  optimize_kicks = 0;
+
+  server_table[4] = (Function) botname;
+  module_register(MODULE_NAME, server_table, 1, 2);
+  if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+    module_undepend(MODULE_NAME);
+    return "This module requires eggdrop1.7.0 or later";
+  }
+
+  /* Fool bot in reading the values. */
+  tcl_eggserver(NULL, interp, "servers", NULL, 0);
+  tcl_traceserver("servers", NULL);
+  s = Tcl_GetVar(interp, "nick", TCL_GLOBAL_ONLY);
+  if (s)
+    strncpyz(origbotname, s, NICKLEN);
+  Tcl_TraceVar(interp, "nick",
+	       TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+	       nick_change, NULL);
+  Tcl_TraceVar(interp, "altnick",
+	       TCL_TRACE_WRITES | TCL_TRACE_UNSETS, altnick_change, NULL);
+  Tcl_TraceVar(interp, "botname",
+	       TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+	       traced_botname, NULL);
+  Tcl_TraceVar(interp, "server",
+	       TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+	       traced_server, NULL);
+  Tcl_TraceVar(interp, "net-type",
+	       TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+	       traced_nettype, NULL);
+  Tcl_TraceVar(interp, "nick-len",
+	       TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
+	       traced_nicklen, NULL);
+
+	/* Create our own bind tables. */
+	BT_wall = add_bind_table2("wall", 2, "ss", MATCH_MASK, BIND_STACKABLE);
+	BT_raw = add_bind_table2("raw", 3, "sss", MATCH_MASK, BIND_STACKABLE);
+	BT_notice = add_bind_table2("notice", 5, "ssUss", MATCH_MASK, BIND_USE_ATTR | BIND_STACKABLE);
+	BT_msg = add_bind_table2("msg", 4, "ssUs", 0, BIND_USE_ATTR);
+	BT_msgm = add_bind_table2("msgm", 4, "ssUs", MATCH_MASK, BIND_USE_ATTR | BIND_STACKABLE);
+	BT_flood = add_bind_table2("flood", 5, "ssUss", MATCH_MASK, BIND_STACKABLE);
+	BT_ctcr = add_bind_table2("ctcr", 6, "ssUsss", MATCH_MASK, BIND_USE_ATTR | BIND_STACKABLE);
+	BT_ctcp = add_bind_table2("ctcp", 6, "ssUsss", MATCH_MASK, BIND_USE_ATTR | BIND_STACKABLE);
+
+	/* Import bind tables. */
+	BT_dcc = find_bind_table2("dcc");
+
+  add_builtins2(BT_raw, my_raw_binds);
+  add_builtins2(BT_ctcp, my_ctcps);
+  add_builtins2(BT_dcc, C_dcc_serv);
+
+  add_help_reference("server.help");
+  my_tcl_strings[0].buf = botname;
+  add_tcl_strings(my_tcl_strings);
+  add_tcl_ints(my_tcl_ints);
+  add_tcl_commands(my_tcl_cmds);
+  add_tcl_coups(my_tcl_coups);
+  add_hook(HOOK_SECONDLY, (Function) server_secondly);
+  add_hook(HOOK_5MINUTELY, (Function) server_5minutely);
+  add_hook(HOOK_MINUTELY, (Function) minutely_checks);
+  add_hook(HOOK_QSERV, (Function) queue_server);
+  add_hook(HOOK_PRE_REHASH, (Function) server_prerehash);
+  add_hook(HOOK_REHASH, (Function) server_postrehash);
+  add_hook(HOOK_DIE, (Function) server_die);
+  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;
+  double_warned = 0;
+  newserver[0] = 0;
+  newserverport = 0;
+  curserv = 999;
+  do_nettype();
+  return NULL;
+}
Index: eggdrop1.7/modules/server/server.h
diff -u /dev/null eggdrop1.7/modules/server/server.h:1.1
--- /dev/null	Sat Oct 27 11:35:18 2001
+++ eggdrop1.7/modules/server/server.h	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,95 @@
+/*
+ * server.h -- part of server.mod
+ *
+ * $Id: server.h,v 1.1 2001/10/27 16:34:52 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_MOD_SERVER_SERVER_H
+#define _EGG_MOD_SERVER_SERVER_H
+
+#define check_tcl_ctcp(a,b,c,d,e,f) check_tcl_ctcpr(a,b,c,d,e,f,BT_ctcp)
+#define check_tcl_ctcr(a,b,c,d,e,f) check_tcl_ctcpr(a,b,c,d,e,f,BT_ctcr)
+
+#ifndef MAKING_SERVER
+/* 4 - 7 */
+#define botuserhost ((char *)(server_funcs[5]))
+#define quiet_reject (*(int *)(server_funcs[6]))
+#define serv (*(int *)(server_funcs[7]))
+/* 8 - 11 */
+#define flud_thr (*(int*)(server_funcs[8]))
+#define flud_time (*(int*)(server_funcs[9]))
+#define flud_ctcp_thr (*(int*)(server_funcs[10]))
+#define flud_ctcp_time (*(int*)(server_funcs[11]))
+/* 12 - 15 */
+#define match_my_nick ((int(*)(char *))server_funcs[12])
+#define check_tcl_flud ((int (*)(char *,char *,struct userrec *,char *,char *))server_funcs[13])
+/* #define fixfrom ((void (*)(char *))server_funcs[14]) -- moved to core */
+#define answer_ctcp (*(int *)(server_funcs[15]))
+/* 16 - 19 */
+#define trigger_on_ignore (*(int *)(server_funcs[16]))
+#define check_tcl_ctcpr ((int(*)(char*,char*,struct userrec*,char*,char*,char*,bind_table_t *))server_funcs[17])
+#define detect_avalanche ((int(*)(char *))server_funcs[18])
+#define nuke_server ((void(*)(char *))server_funcs[19])
+/* 20 - 22 */
+#define newserver ((char *)(server_funcs[20]))
+#define newserverport (*(int *)(server_funcs[21]))
+#define newserverpass ((char *)(server_funcs[22]))
+/* 23 - 26 */
+#define cycle_time (*(int *)(server_funcs[23]))
+#define default_port (*(int *)(server_funcs[24]))
+#define server_online (*(int *)(server_funcs[25]))
+/* #define min_servs (*(int *)(server_funcs[26])) -- removed useless feature */
+/* 27 - 30 */
+#define H_raw (*(p_tcl_bind_list *)(server_funcs[27]))
+#define H_wall (*(p_tcl_bind_list *)(server_funcs[28]))
+#define H_msg (*(p_tcl_bind_list *)(server_funcs[29]))
+#define H_msgm (*(p_tcl_bind_list *)(server_funcs[30]))
+/* 31 - 34 */
+#define H_notc (*(p_tcl_bind_list *)(server_funcs[31]))
+#define H_flud (*(p_tcl_bind_list *)(server_funcs[32]))
+/* #define H_ctcp (*(p_tcl_bind_list *)(server_funcs[33])) */
+/* #define H_ctcr (*(p_tcl_bind_list *)(server_funcs[34])) */
+/* 35 - 38 */
+#define ctcp_reply ((char *)(server_funcs[35]))
+#define get_altbotnick ((char *(*)(void))(server_funcs[36]))
+#define nick_len (*(int *)(server_funcs[37]))
+#define check_tcl_notc ((int (*)(char *,char *,struct userrec *,char *,char *))server_funcs[38])
+#endif		/* MAKING_SERVER */
+
+struct server_list {
+  struct server_list	*next;
+
+  char			*name;
+  int			 port;
+  char			*pass;
+  char			*realname;
+};
+
+/* Available net types.  */
+enum {
+	NETT_EFNET		= 0,	/* EfNet except new +e/+I hybrid. */
+	NETT_IRCNET		= 1,	/* Ircnet.			  */
+	NETT_UNDERNET		= 2,	/* Undernet.			  */
+	NETT_DALNET		= 3,	/* Dalnet.			  */
+	NETT_HYBRID_EFNET	= 4	/* new +e/+I Efnet hybrid.	  */
+} nett_t;
+
+#endif		/* _EGG_MOD_SERVER_SERVER_H */
Index: eggdrop1.7/modules/server/servmsg.c
diff -u /dev/null eggdrop1.7/modules/server/servmsg.c:1.1
--- /dev/null	Sat Oct 27 11:35:18 2001
+++ eggdrop1.7/modules/server/servmsg.c	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,1187 @@
+/*
+ * servmsg.c -- part of server.mod
+ *
+ * $Id: servmsg.c,v 1.1 2001/10/27 16:34:52 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+
+static time_t last_ctcp    = (time_t) 0L;
+static int    count_ctcp   = 0;
+static char   altnick_char = 0;
+
+/* We try to change to a preferred unique nick here. We always first try the
+ * specified alternate nick. If that failes, we repeatedly modify the nick
+ * until it gets accepted.
+ *
+ * sent nick:
+ *     "<altnick><c>"
+ *                ^--- additional count character: 1-9^-_\\[]`a-z
+ *          ^--------- given, alternate nick
+ *
+ * The last added character is always saved in altnick_char. At the very first
+ * attempt (were altnick_char is 0), we try the alternate nick without any
+ * additions.
+ *
+ * fixed by guppy (1999/02/24) and Fabian (1999/11/26)
+ */
+static int gotfake433(char *from)
+{
+  int l = strlen(botname) - 1;
+
+  /* First run? */
+  if (altnick_char == 0) {
+    char *alt = get_altbotnick();
+
+    if (alt[0] && (irccmp(alt, botname)))
+      /* Alternate nickname defined. Let's try that first. */
+      strcpy(botname, alt);
+    else {
+      /* Fall back to appending count char. */
+      altnick_char = '0';
+      if ((l + 1) == nick_len) {
+	botname[l] = altnick_char;
+      } else {
+	botname[++l]   = altnick_char;
+	botname[l + 1] = 0;
+      }
+    }
+  /* No, we already tried the default stuff. Now we'll go through variations
+   * of the original alternate nick.
+   */
+  } else {
+    char *oknicks = "^-_\\[]`";
+    char *p = strchr(oknicks, altnick_char);
+
+    if (p == NULL) {
+      if (altnick_char == '9')
+        altnick_char = oknicks[0];
+      else
+	altnick_char = altnick_char + 1;
+    } else {
+      p++;
+      if (!*p)
+	altnick_char = 'a' + random() % 26;
+      else
+	altnick_char = (*p);
+    }
+    botname[l] = altnick_char;
+  }
+  putlog(LOG_MISC, "*", _("NICK IN USE: Trying %s"), botname);
+  dprintf(DP_MODE, "NICK %s\n", botname);
+  return 0;
+}
+
+/* Check for tcl-bound msg command, return 1 if found
+ *
+ * msg: proc-name <nick> <user at host> <handle> <args...>
+ */
+static int check_tcl_msg(char *cmd, char *nick, char *uhost,
+			 struct userrec *u, char *args)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0};
+  char *hand = u ? u->handle : "*";
+  int x;
+
+  get_user_flagrec(u, &fr, NULL);
+
+  x = check_bind(BT_msg, cmd, &fr, nick, uhost, u, args);
+  if (x & BIND_RET_LOG) putlog(LOG_CMDS, "*", "(%s!%s) !%s! %s %s", nick, uhost, hand, args);
+  if (x) return(1);
+  else return(0);
+}
+
+static void check_tcl_notc(char *nick, char *uhost, struct userrec *u,
+	       		   char *dest, char *arg)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0};
+
+  get_user_flagrec(u, &fr, NULL);
+  check_bind(BT_notice, arg, &fr, nick, uhost, u, arg, dest);
+}
+
+static void check_tcl_msgm(char *nick, char *uhost, struct userrec *u, char *arg)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0};
+
+/*
+  if (arg[0])
+    simple_sprintf(args, "%s %s", cmd, arg);
+  else
+    strcpy(args, cmd);
+*/
+
+  get_user_flagrec(u, &fr, NULL);
+  check_bind(BT_msgm, arg, &fr, nick, uhost, u, arg);
+}
+
+/* Return 1 if processed.
+ */
+static int check_tcl_raw(char *from, char *code, char *msg)
+{
+  return check_bind(BT_raw, code, NULL, from, code, msg);
+}
+
+static int check_tcl_ctcpr(char *nick, char *uhost, struct userrec *u,
+			   char *dest, char *keyword, char *args,
+			   bind_table_t *table)
+{
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0};
+  get_user_flagrec(u, &fr, NULL);
+
+  return check_bind(table, keyword, &fr, nick, uhost, u, dest, keyword, args);
+}
+
+static int check_tcl_wall(char *from, char *msg)
+{
+  int x;
+
+  x = check_bind(BT_wall, msg, NULL, from, msg);
+  if (x & BIND_RET_LOG) {
+    putlog(LOG_WALL, "*", "!%s! %s", from, msg);
+    return 1;
+  }
+  else return 0;
+}
+
+static int check_tcl_flud(char *nick, char *uhost, struct userrec *u,
+			  char *ftype, char *chname)
+{
+  return check_bind(BT_flood, ftype, NULL, nick, uhost, u, ftype, chname);
+}
+
+static int match_my_nick(char *nick)
+{
+  if (!irccmp(nick, botname))
+    return 1;
+  return 0;
+}
+
+/* 001: welcome to IRC (use it to fix the server name)
+ */
+static int got001(char *from, char *ignore, char *msg)
+{
+  struct server_list *x;
+  int i, servidx = findanyidx(serv);
+  struct chanset_t *chan;
+
+  /* Ok...param #1 of 001 = what server thinks my nick is */
+  server_online = now;
+  fixcolon(msg);
+  strncpyz(botname, msg, NICKLEN);
+  altnick_char = 0;
+  dprintf(DP_SERVER, "WHOIS %s\n", botname); /* get user at host */
+  check_bind_event("init-server");
+  x = serverlist;
+  if (x == NULL)
+    return 0;			/* Uh, no server list */
+  /* Only join if the IRC module is loaded. */
+  if (module_find("irc", 0, 0))
+    for (chan = chanset; chan; chan = chan->next) {
+      chan->status &= ~(CHAN_ACTIVE | CHAN_PEND);
+      if (!channel_inactive(chan))
+	dprintf(DP_SERVER, "JOIN %s %s\n",
+	        (chan->name[0]) ? chan->name : chan->dname,
+	        chan->channel.key[0] ? chan->channel.key : chan->key_prot);
+    }
+  if (strcasecmp(from, dcc[servidx].host)) {
+    putlog(LOG_MISC, "*", "(%s claims to be %s; updating server list)",
+	   dcc[servidx].host, from);
+    for (i = curserv; i > 0 && x != NULL; i--)
+      x = x->next;
+    if (x == NULL) {
+      putlog(LOG_MISC, "*", "Invalid server list!");
+      return 0;
+    }
+    if (x->realname)
+      free(x->realname);
+    if (strict_servernames == 1) {
+      x->realname = NULL;
+      if (x->name)
+	free(x->name);
+      malloc_strcpy(x->name, from);
+    } else
+      malloc_strcpy(x->realname, from);
+  }
+  return 0;
+}
+
+/* Got 442: not on channel
+ */
+static int got442(char *from, char *ignore, char *msg)
+{
+  char			*chname;
+  struct chanset_t	*chan;
+  struct server_list	*x;
+  int			 i;
+
+  for (x = serverlist, i = 0; x; x = x->next, i++)
+    if (i == curserv) {
+      if (strcasecmp(from, x->realname ? x->realname : x->name))
+	return 0;
+      break;
+    }
+  newsplit(&msg);
+  chname = newsplit(&msg);
+  chan = findchan(chname);
+  if (chan)
+    if (!channel_inactive(chan)) {
+      module_entry	*me = module_find("channels", 0, 0);
+
+      putlog(LOG_MISC, chname, _("Server says Im not on channel: %s"), chname);
+      if (me && me->funcs)
+	(me->funcs[CHANNEL_CLEAR])(chan, 1);
+      chan->status &= ~CHAN_ACTIVE;
+      dprintf(DP_MODE, "JOIN %s %s\n", chan->name,
+	      chan->channel.key[0] ? chan->channel.key : chan->key_prot);
+    }
+
+  return 0;
+}
+
+/* Close the current server connection.
+ */
+static void nuke_server(char *reason)
+{
+  if (serv >= 0) {
+    int servidx = findanyidx(serv);
+
+    if (reason && (servidx > 0))
+      dprintf(servidx, "QUIT :%s\n", reason);
+    disconnect_server(servidx);
+    lostdcc(servidx);
+  }
+}
+
+static char ctcp_reply[1024] = "";
+
+static int lastmsgs[FLOOD_GLOBAL_MAX];
+static char lastmsghost[FLOOD_GLOBAL_MAX][81];
+static time_t lastmsgtime[FLOOD_GLOBAL_MAX];
+
+/* Do on NICK, PRIVMSG, NOTICE and JOIN.
+ */
+static int detect_flood(char *floodnick, char *floodhost, char *from, int which)
+{
+  char *p, ftype[10], h[1024];
+  struct userrec *u;
+  int thr = 0, lapse = 0, atr;
+
+  u = get_user_by_host(from);
+  atr = u ? u->flags : 0;
+  if (atr & (USER_BOT | USER_FRIEND))
+    return 0;
+
+  /* Determine how many are necessary to make a flood */
+  switch (which) {
+  case FLOOD_PRIVMSG:
+  case FLOOD_NOTICE:
+    thr = flud_thr;
+    lapse = flud_time;
+    strcpy(ftype, "msg");
+    break;
+  case FLOOD_CTCP:
+    thr = flud_ctcp_thr;
+    lapse = flud_ctcp_time;
+    strcpy(ftype, "ctcp");
+    break;
+  }
+  if ((thr == 0) || (lapse == 0))
+    return 0;			/* No flood protection */
+  /* Okay, make sure i'm not flood-checking myself */
+  if (match_my_nick(floodnick))
+    return 0;
+  if (!strcasecmp(floodhost, botuserhost))
+    return 0;			/* My user at host (?) */
+  p = strchr(floodhost, '@');
+  if (p) {
+    p++;
+    if (strcasecmp(lastmsghost[which], p)) {	/* New */
+      strcpy(lastmsghost[which], p);
+      lastmsgtime[which] = now;
+      lastmsgs[which] = 0;
+      return 0;
+    }
+  } else
+    return 0;			/* Uh... whatever. */
+
+  if (lastmsgtime[which] < now - lapse) {
+    /* Flood timer expired, reset it */
+    lastmsgtime[which] = now;
+    lastmsgs[which] = 0;
+    return 0;
+  }
+  lastmsgs[which]++;
+  if (lastmsgs[which] >= thr) {	/* FLOOD */
+    /* Reset counters */
+    lastmsgs[which] = 0;
+    lastmsgtime[which] = 0;
+    lastmsghost[which][0] = 0;
+    u = get_user_by_host(from);
+    if (check_tcl_flud(floodnick, floodhost, u, ftype, "*"))
+      return 0;
+    /* Private msg */
+    simple_sprintf(h, "*!*@%s", p);
+    putlog(LOG_MISC, "*", _("Flood from @%s!  Placing on ignore!"), p);
+    addignore(h, origbotname, (which == FLOOD_CTCP) ? "CTCP flood" :
+	      "MSG/NOTICE flood", now + (60 * ignore_time));
+  }
+  return 0;
+}
+
+/* Check for more than 8 control characters in a line.
+ * This could indicate: beep flood CTCP avalanche.
+ */
+static int detect_avalanche(char *msg)
+{
+  int count = 0;
+  unsigned char *p;
+
+  for (p = (unsigned char *) msg; (*p) && (count < 8); p++)
+    if ((*p == 7) || (*p == 1))
+      count++;
+  if (count >= 8)
+    return 1;
+  else
+    return 0;
+}
+
+/* Got a private message.
+ */
+static int gotmsg(char *from, char *ignore, char *msg)
+{
+  char *to, buf[UHOSTLEN], *nick, *uhost, ctcpbuf[512], *ctcp, *p, *p1;
+  char *code;
+  struct userrec *u;
+  int ctcp_count = 0;
+  int ignoring;
+
+  if (msg[0] && ((strchr(CHANMETA, *msg) != NULL) ||
+     (*msg == '@')))           /* Notice to a channel, not handled here */
+    return 0;
+  ignoring = match_ignore(from);
+  to = newsplit(&msg);
+  fixcolon(msg);
+  strncpyz(buf, from, sizeof buf);
+  nick = strtok(buf, "!");
+  uhost = strtok(NULL, "!");
+  /* Only check if flood-ctcp is active */
+  if (flud_ctcp_thr && detect_avalanche(msg)) {
+    if (!ignoring) {
+      putlog(LOG_MODES, "*", "Avalanche from %s - ignoring", from);
+      /* FIXME: get rid of this mess */
+      p = strchr(uhost, '@');
+      if (p != NULL)
+	p++;
+      else
+	p = uhost;
+      simple_sprintf(ctcpbuf, "*!*@%s", p);
+      addignore(ctcpbuf, origbotname, "ctcp avalanche",
+		now + (60 * ignore_time));
+    }
+  }
+  /* Check for CTCP: */
+  ctcp_reply[0] = 0;
+  p = strchr(msg, 1);
+  while ((p != NULL) && (*p)) {
+    p++;
+    p1 = p;
+    while ((*p != 1) && (*p != 0))
+      p++;
+    if (*p == 1) {
+      *p = 0;
+      ctcp = strcpy(ctcpbuf, p1);
+      strcpy(p1 - 1, p + 1);
+      if (!ignoring)
+	detect_flood(nick, uhost, from,
+		     strncmp(ctcp, "ACTION ", 7) ? FLOOD_CTCP : FLOOD_PRIVMSG);
+      /* Respond to the first answer_ctcp */
+      p = strchr(msg, 1);
+      if (ctcp_count < answer_ctcp) {
+	ctcp_count++;
+	if (ctcp[0] != ' ') {
+	  code = newsplit(&ctcp);
+	  if ((to[0] == '$') || strchr(to, '.')) {
+	    if (!ignoring)
+	      /* Don't interpret */
+	      putlog(LOG_PUBLIC, to, "CTCP %s: %s from %s (%s) to %s",
+		     code, ctcp, nick, uhost, to);
+	  } else {
+	    u = get_user_by_host(from);
+	    if (!ignoring || trigger_on_ignore) {
+	      if (!check_tcl_ctcp(nick, uhost, u, to, code, ctcp) &&
+		  !ignoring) {
+		if (!strcasecmp(code, "DCC")) {
+		  /* If it gets this far unhandled, it means that
+		   * the user is totally unknown.
+		   */
+		  code = newsplit(&ctcp);
+		  if (!strcmp(code, "CHAT")) {
+		    if (!quiet_reject) {
+		      if (u)
+			dprintf(DP_HELP, "NOTICE %s :%s\n", nick,
+				"I'm not accepting call at the moment.");
+		      else
+			dprintf(DP_HELP, "NOTICE %s :%s\n",
+				nick, _("I dont accept DCC chats from strangers."));
+		    }
+		    putlog(LOG_MISC, "*", "%s: %s",
+			   _("Refused DCC chat (no access)"), from);
+		  } else
+		    putlog(LOG_MISC, "*", "Refused DCC %s: %s",
+			   code, from);
+		}
+	      }
+	      if (!strcmp(code, "ACTION")) {
+		putlog(LOG_MSGS, "*", "Action to %s: %s %s",
+		       to, nick, ctcp);
+	      } else {
+		putlog(LOG_MSGS, "*", "CTCP %s: %s from %s (%s)",
+		       code, ctcp, nick, uhost);
+	      }			/* I love a good close cascade ;) */
+	    }
+	  }
+	}
+      }
+    }
+  }
+  /* Send out possible ctcp responses */
+  if (ctcp_reply[0]) {
+    if (ctcp_mode != 2) {
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+    } else {
+      if (now - last_ctcp > flud_ctcp_time) {
+	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+	count_ctcp = 1;
+      } else if (count_ctcp < flud_ctcp_thr) {
+	dprintf(DP_HELP, "NOTICE %s :%s\n", nick, ctcp_reply);
+	count_ctcp++;
+      }
+      last_ctcp = now;
+    }
+  }
+  if (msg[0]) {
+    if ((to[0] == '$') || (strchr(to, '.') != NULL)) {
+      /* Msg from oper */
+      if (!ignoring) {
+	detect_flood(nick, uhost, from, FLOOD_PRIVMSG);
+	/* Do not interpret as command */
+	putlog(LOG_MSGS | LOG_SERV, "*", "[%s!%s to %s] %s",
+	       nick, uhost, to, msg);
+      }
+    } else {
+      char *code;
+      struct userrec *u;
+
+      detect_flood(nick, uhost, from, FLOOD_PRIVMSG);
+      u = get_user_by_host(from);
+      if (!ignoring || trigger_on_ignore) check_tcl_msgm(nick, uhost, u, msg);
+      code = newsplit(&msg);
+      rmspace(msg);
+      if (!ignoring)
+	if (!check_tcl_msg(code, nick, uhost, u, msg))
+	  putlog(LOG_MSGS, "*", "[%s] %s %s", from, code, msg);
+    }
+  }
+  return 0;
+}
+
+/* Got a private notice.
+ */
+static int gotnotice(char *from, char *ignore, char *msg)
+{
+  char *to, buf[UHOSTLEN], *nick, *uhost, ctcpbuf[512], *ctcp, *p, *p1;
+  char *code;
+  struct userrec *u;
+  int ignoring;
+
+  if (msg[0] && ((strchr(CHANMETA, *msg) != NULL) ||
+      (*msg == '@')))           /* Notice to a channel, not handled here */
+    return 0;
+  ignoring = match_ignore(from);
+  to = newsplit(&msg);
+  fixcolon(msg);
+  strncpyz(buf, from, sizeof buf);
+  nick = strtok(buf, "!");
+  uhost = strtok(NULL, "!");
+  if (flud_ctcp_thr && detect_avalanche(msg)) {
+    /* Discard -- kick user if it was to the channel */
+    if (!ignoring)
+      putlog(LOG_MODES, "*", "Avalanche from %s", from);
+    return 0;
+  }
+  /* Check for CTCP: */
+  p = strchr(msg, 1);
+  while ((p != NULL) && (*p)) {
+    p++;
+    p1 = p;
+    while ((*p != 1) && (*p != 0))
+      p++;
+    if (*p == 1) {
+      *p = 0;
+      ctcp = strcpy(ctcpbuf, p1);
+      strcpy(p1 - 1, p + 1);
+      if (!ignoring)
+	detect_flood(nick, uhost, from, FLOOD_CTCP);
+      p = strchr(msg, 1);
+      if (ctcp[0] != ' ') {
+	code = newsplit(&ctcp);
+	if ((to[0] == '$') || strchr(to, '.')) {
+	  if (!ignoring)
+	    putlog(LOG_PUBLIC, "*",
+		   "CTCP reply %s: %s from %s (%s) to %s", code, ctcp,
+		   nick, uhost, to);
+	} else {
+	  u = get_user_by_host(from);
+	  if (!ignoring || trigger_on_ignore) {
+	    check_tcl_ctcr(nick, uhost, u, to, code, ctcp);
+	    if (!ignoring)
+	      /* Who cares? */
+	      putlog(LOG_MSGS, "*",
+		     "CTCP reply %s: %s from %s (%s) to %s",
+		     code, ctcp, nick, uhost, to);
+	  }
+	}
+      }
+    }
+  }
+  if (msg[0]) {
+    if (((to[0] == '$') || strchr(to, '.')) && !ignoring) {
+      detect_flood(nick, uhost, from, FLOOD_NOTICE);
+      putlog(LOG_MSGS | LOG_SERV, "*", "-%s (%s) to %s- %s",
+	     nick, uhost, to, msg);
+    } else {
+      /* Server notice? */
+      if (!nick || !uhost) {
+	/* Hidden `250' connection count message from server */
+	if (strncmp(msg, "Highest connection count:", 25))
+	  putlog(LOG_SERV, "*", "-NOTICE- %s", msg);
+      } else {
+        detect_flood(nick, uhost, from, FLOOD_NOTICE);
+        u = get_user_by_host(from);
+        if (!ignoring || trigger_on_ignore)
+          check_tcl_notc(nick, uhost, u, botname, msg);
+        if (!ignoring)
+  	      putlog(LOG_MSGS, "*", "-%s (%s)- %s", nick, uhost, msg);
+      }
+    }
+  }
+  return 0;
+}
+
+/* WALLOPS: oper's nuisance
+ */
+static int gotwall(char *from, char *ignore, char *msg)
+{
+  char *p, buf[UHOSTLEN], *nick, *uhost;
+  int r;
+
+  fixcolon(msg);
+  p = strchr(from, '!');
+  if (p && (p == strrchr(from, '!'))) {
+    strncpyz(buf, from, sizeof buf);
+    nick = strtok(buf, "!");
+    uhost = strtok(NULL, "!");
+    r = check_tcl_wall(nick, msg);
+    if (r == 0)
+      putlog(LOG_WALL, "*", "!%s(%s)! %s", nick, uhost, msg);
+  } else {
+    r = check_tcl_wall(from, msg);
+    if (r == 0)
+      putlog(LOG_WALL, "*", "!%s! %s", from, msg);
+  }
+  return 0;
+}
+
+/* Called once a minute... but if we're the only one on the
+ * channel, we only wanna send out "lusers" once every 5 mins.
+ */
+static void minutely_checks()
+{
+  char *alt;
+
+  /* Only check if we have already successfully logged in.  */
+  if (!server_online)
+    return;
+  if (keepnick) {
+    /* NOTE: now that botname can but upto NICKLEN bytes long,
+     * check that it's not just a truncation of the full nick.
+     */
+    if (strncmp(botname, origbotname, strlen(botname))) {
+      /* See if my nickname is in use and if if my nick is right.  */
+	alt = get_altbotnick();
+	if (alt[0] && strcasecmp (botname, alt))
+	  dprintf(DP_SERVER, "ISON :%s %s %s\n", botname, origbotname, alt);
+	else
+          dprintf(DP_SERVER, "ISON :%s %s\n", botname, origbotname);
+    }
+  }
+}
+
+/* Pong from server.
+ */
+static int gotpong(char *from, char *ignore, char *msg)
+{
+  unsigned int arg;
+
+  newsplit(&msg);
+  fixcolon(msg);		/* Scrap server name */
+  waiting_for_awake = 0;
+  sscanf(msg, "%u", &arg);
+  server_lag = now - arg;
+  if (server_lag > 99999) {
+    /* IRCnet lagmeter support by drummer */
+    server_lag = now - lastpingtime;
+  }
+  return 0;
+}
+
+/* This is a reply on ISON :<current> <orig> [<alt>]
+ */
+static void got303(char *from, char *ignore, char *msg)
+{
+  char *tmp, *alt;
+  int ison_orig = 0, ison_alt = 0;
+
+  if (!keepnick ||
+      !strncmp(botname, origbotname, strlen(botname))) {
+    return;
+  }
+  newsplit(&msg);
+  fixcolon(msg);
+  alt = get_altbotnick();
+  tmp = newsplit(&msg);
+  if (tmp[0] && !irccmp(botname, tmp)) {
+    while ((tmp = newsplit(&msg))[0]) { /* no, it's NOT == */
+      if (!irccmp(tmp, origbotname))
+        ison_orig = 1;
+      else if (alt[0] && !irccmp(tmp, alt))
+        ison_alt = 1;
+    }
+    if (!ison_orig) {
+      if (!nick_juped)
+        putlog(LOG_MISC, "*", _("Switching back to nick %s"), origbotname);
+      dprintf(DP_SERVER, "NICK %s\n", origbotname);
+    } else if (alt[0] && !ison_alt && irccmp(botname, alt)) {
+      putlog(LOG_MISC, "*", _("Switching back to altnick %s"), alt);
+      dprintf(DP_SERVER, "NICK %s\n", alt);
+    }
+  }
+}
+
+/* 432 : Bad nickname
+ */
+static int got432(char *from, char *ignore, char *msg)
+{
+  char *erroneus;
+
+  newsplit(&msg);
+  erroneus = newsplit(&msg);
+  if (server_online)
+    putlog(LOG_MISC, "*", "NICK IS INVALID: %s (keeping '%s').", erroneus,
+	   botname);
+  else {
+    putlog(LOG_MISC, "*", _("Server says my nickname is invalid."));
+    if (!keepnick) {
+      makepass(erroneus);
+      erroneus[NICKMAX] = 0;
+      dprintf(DP_MODE, "NICK %s\n", erroneus);
+    }
+    return 0;
+  }
+  return 0;
+}
+
+/* 433 : Nickname in use
+ * Change nicks till we're acceptable or we give up
+ */
+static int got433(char *from, char *ignore, char *msg)
+{
+  char *tmp;
+
+  if (server_online) {
+    /* We are online and have a nickname, we'll keep it */
+    newsplit(&msg);
+    tmp = newsplit(&msg);
+    putlog(LOG_MISC, "*", "NICK IN USE: %s (keeping '%s').", tmp, botname);
+    nick_juped = 0;
+    return 0;
+  }
+  gotfake433(from);
+  return 0;
+}
+
+/* 437 : Nickname juped (IRCnet)
+ */
+static int got437(char *from, char *ignore, char *msg)
+{
+  char *s;
+  struct chanset_t *chan;
+
+  newsplit(&msg);
+  s = newsplit(&msg);
+  if (s[0] && (strchr(CHANMETA, s[0]) != NULL)) {
+    chan = findchan(s);
+    if (chan) {
+      if (chan->status & CHAN_ACTIVE) {
+	putlog(LOG_MISC, "*", _("Cant change nickname on %s.  Is my nickname banned?"), s);
+      } else {
+	if (!channel_juped(chan)) {
+	  putlog(LOG_MISC, "*", _("Channel %s is juped. :("), s);
+	  chan->status |= CHAN_JUPED;
+	}
+      }
+    }
+  } else if (server_online) {
+    if (!nick_juped)
+      putlog(LOG_MISC, "*", "NICK IS JUPED: %s (keeping '%s').", s, botname);
+    if (!irccmp(s, origbotname))
+      nick_juped = 1;
+  } else {
+    putlog(LOG_MISC, "*", "%s: %s", _("Nickname has been juped"), s);
+    gotfake433(from);
+  }
+  return 0;
+}
+
+/* 438 : Nick change too fast
+ */
+static int got438(char *from, char *ignore, char *msg)
+{
+  newsplit(&msg);
+  newsplit(&msg);
+  fixcolon(msg);
+  putlog(LOG_MISC, "*", "%s", msg);
+  return 0;
+}
+
+static int got451(char *from, char *ignore, char *msg)
+{
+  /* Usually if we get this then we really messed up somewhere
+   * or this is a non-standard server, so we log it and kill the socket
+   * hoping the next server will work :) -poptix
+   */
+  /* Um, this does occur on a lagged anti-spoof server connection if the
+   * (minutely) sending of joins occurs before the bot does its ping reply.
+   * Probably should do something about it some time - beldin
+   */
+  putlog(LOG_MISC, "*", _("%s says Im not registered, trying next one."), from);
+  nuke_server(_("You have a fucked up server."));
+  return 0;
+}
+
+/* Got error notice
+ */
+static int goterror(char *from, char *ignore, char *msg)
+{
+  fixcolon(msg);
+  putlog(LOG_SERV | LOG_MSGS, "*", "-ERROR from server- %s", msg);
+  if (serverror_quit) {
+    putlog(LOG_SERV, "*", "Disconnecting from server.");
+    nuke_server("Bah, stupid error messages.");
+  }
+  return 1;
+}
+
+/* Got nick change.
+ */
+static int gotnick(char *from, char *ignore, char *msg)
+{
+  char *nick, *alt = get_altbotnick();
+  struct userrec *u;
+
+  fixcolon(msg);
+  u = get_user_by_host(from);
+  nick = strtok(from, "!");
+  check_queues(nick, msg);
+  if (match_my_nick(nick)) {
+    /* Regained nick! */
+    strncpyz(botname, msg, NICKLEN);
+    altnick_char = 0;
+    waiting_for_awake = 0;
+    if (!strcmp(msg, origbotname)) {
+      putlog(LOG_SERV | LOG_MISC, "*", "Regained nickname '%s'.", msg);
+      nick_juped = 0;
+    } else if (alt[0] && !strcmp(msg, alt))
+      putlog(LOG_SERV | LOG_MISC, "*", "Regained alternate nickname '%s'.",
+	     msg);
+    else if (keepnick && strcmp(nick, msg)) {
+      putlog(LOG_SERV | LOG_MISC, "*", "Nickname changed to '%s'???", msg);
+      if (!irccmp(nick, origbotname)) {
+        putlog(LOG_MISC, "*", _("Switching back to nick %s"), origbotname);
+        dprintf(DP_SERVER, "NICK %s\n", origbotname);
+      } else if (alt[0] && !irccmp(nick, alt)
+		 && strcasecmp(botname, origbotname)) {
+        putlog(LOG_MISC, "*", _("Switching back to altnick %s"), alt);
+        dprintf(DP_SERVER, "NICK %s\n", alt);
+      }
+    } else
+      putlog(LOG_SERV | LOG_MISC, "*", "Nickname changed to '%s'???", msg);
+  } else if ((keepnick) && (irccmp(nick, msg))) {
+    /* Only do the below if there was actual nick change, case doesn't count */
+    if (!irccmp(nick, origbotname)) {
+      putlog(LOG_MISC, "*", _("Switching back to nick %s"), origbotname);
+      dprintf(DP_SERVER, "NICK %s\n", origbotname);
+    } else if (alt[0] && !irccmp(nick, alt) &&
+	    strcasecmp(botname, origbotname)) {
+      putlog(LOG_MISC, "*", _("Switching back to altnick %s"), altnick);
+      dprintf(DP_SERVER, "NICK %s\n", altnick);
+    }
+  }
+  return 0;
+}
+
+static int gotmode(char *from, char *ignore, char *msg)
+{
+  char *ch;
+
+  ch = newsplit(&msg);
+  /* Usermode changes? */
+  if (strchr(CHANMETA, ch[0]) == NULL) {
+    if (match_my_nick(ch) && check_mode_r) {
+      /* umode +r? - D0H dalnet uses it to mean something different */
+      fixcolon(msg);
+      if ((msg[0] == '+') && strchr(msg, 'r')) {
+	int servidx = findanyidx(serv);
+
+	putlog(LOG_MISC | LOG_JOIN, "*",
+	       "%s has me i-lined (jumping)", dcc[servidx].host);
+	nuke_server("i-lines suck");
+      }
+    }
+  }
+  return 0;
+}
+
+static void disconnect_server(int idx)
+{
+  if (server_online > 0)
+    check_bind_event("disconnect-server");
+  server_online = 0;
+  if (dcc[idx].sock >= 0)
+    killsock(dcc[idx].sock);
+  dcc[idx].sock = (-1);
+  serv = (-1);
+  botuserhost[0] = 0;
+}
+
+static void eof_server(int idx)
+{
+  putlog(LOG_SERV, "*", "%s %s", _("Disconnected from"), dcc[idx].host);
+  disconnect_server(idx);
+  lostdcc(idx);
+}
+
+static void display_server(int idx, char *buf)
+{
+  sprintf(buf, "%s  (lag: %d)", trying_server ? "conn" : "serv",
+	  server_lag);
+}
+
+static void connect_server(void);
+
+static void kill_server(int idx, void *x)
+{
+  module_entry *me;
+
+  disconnect_server(idx);
+  if ((me = module_find("channels", 0, 0)) && me->funcs) {
+    struct chanset_t *chan;
+
+    for (chan = chanset; chan; chan = chan->next)
+      (me->funcs[CHANNEL_CLEAR]) (chan, 1);
+  }
+  /* A new server connection will be automatically initiated in
+     about 2 seconds. */
+}
+
+static void timeout_server(int idx)
+{
+  putlog(LOG_SERV, "*", "Timeout: connect to %s", dcc[idx].host);
+  disconnect_server(idx);
+  lostdcc(idx);
+}
+
+static void server_activity(int idx, char *msg, int len);
+
+static struct dcc_table SERVER_SOCKET =
+{
+  "SERVER",
+  0,
+  eof_server,
+  server_activity,
+  NULL,
+  timeout_server,
+  display_server,
+  kill_server,
+  NULL
+};
+
+static void server_activity(int idx, char *msg, int len)
+{
+  char *from, *code;
+
+  if (trying_server) {
+    strcpy(dcc[idx].nick, "(server)");
+    putlog(LOG_SERV, "*", "Connected to %s", dcc[idx].host);
+    trying_server = 0;
+    waiting_for_awake = 0;
+    SERVER_SOCKET.timeout_val = 0;
+  }
+  from = "";
+  if (msg[0] == ':') {
+    msg++;
+    from = newsplit(&msg);
+  }
+  code = newsplit(&msg);
+  if (debug_output)
+    putlog(LOG_RAW, "*", "[@] %s %s %s", from, code, msg);
+
+  /* This has GOT to go into the raw binding table, * merely because this
+   * is less effecient.
+   */
+  check_tcl_raw(from, code, msg);
+}
+
+static int gotping(char *from, char *ignore, char *msg)
+{
+  fixcolon(msg);
+  dprintf(DP_MODE, "PONG :%s\n", msg);
+  return 0;
+}
+
+static int gotkick(char *from, char *ignore, char *msg)
+{
+  char *nick;
+
+  nick = from;
+  if (irccmp(nick, botname))
+    /* Not my kick, I don't need to bother about it. */
+    return 0;
+  if (use_penalties) {
+    last_time += 2;
+    if (debug_output)
+      putlog(LOG_SRVOUT, "*", "adding 2secs penalty (successful kick)");
+  }
+  return 0;
+}
+
+/* Another sec penalty if bot did a whois on another server.
+ */
+static int whoispenalty(char *from, char *msg)
+{
+  struct server_list *x = serverlist;
+  int i, ii;
+
+  if (x && use_penalties) {
+    i = ii = 0;
+    for (; x; x = x->next) {
+      if (i == curserv) {
+        if ((strict_servernames == 1) || !x->realname) {
+          if (strcmp(x->name, from))
+            ii = 1;
+        } else {
+          if (strcmp(x->realname, from))
+            ii = 1;
+        }
+      }
+      i++;
+    }
+    if (ii) {
+      last_time += 1;
+      if (debug_output)
+        putlog(LOG_SRVOUT, "*", "adding 1sec penalty (remote whois)");
+    }
+  }
+  return 0;
+}
+
+static int got311(char *from, char *ignore, char *msg)
+{
+  char *n1, *n2, *u, *h;
+  
+  n1 = newsplit(&msg);
+  n2 = newsplit(&msg);
+  u = newsplit(&msg);
+  h = newsplit(&msg);
+  
+  if (!n1 || !n2 || !u || !h)
+    return 0;
+    
+  if (match_my_nick(n2))
+    snprintf(botuserhost, sizeof botuserhost, "%s@%s", u, h);
+  
+  return 0;
+}
+
+
+static cmd_t my_raw_binds[] =
+{
+  {"PRIVMSG",	"",	(Function) gotmsg,		NULL},
+  {"NOTICE",	"",	(Function) gotnotice,		NULL},
+  {"MODE",	"",	(Function) gotmode,		NULL},
+  {"PING",	"",	(Function) gotping,		NULL},
+  {"PONG",	"",	(Function) gotpong,		NULL},
+  {"WALLOPS",	"",	(Function) gotwall,		NULL},
+  {"001",	"",	(Function) got001,		NULL},
+  {"303",	"",	(Function) got303,		NULL},
+  {"432",	"",	(Function) got432,		NULL},
+  {"433",	"",	(Function) got433,		NULL},
+  {"437",	"",	(Function) got437,		NULL},
+  {"438",	"",	(Function) got438,		NULL},
+  {"451",	"",	(Function) got451,		NULL},
+  {"442",	"",	(Function) got442,		NULL},
+  {"NICK",	"",	(Function) gotnick,		NULL},
+  {"ERROR",	"",	(Function) goterror,		NULL},
+  {"KICK",	"",	(Function) gotkick,		NULL},
+  {"318",	"",	(Function) whoispenalty,	NULL},
+  {"311",	"",	(Function) got311,		NULL},
+  {NULL,	NULL,	NULL,				NULL}
+};
+
+static void server_resolve_success(int);
+static void server_resolve_failure(int);
+
+/* Hook up to a server
+ */
+static void connect_server(void)
+{
+  char pass[121], botserver[UHOSTLEN];
+  static int oldserv = -1;
+  int servidx;
+  unsigned int botserverport = 0;
+
+  waiting_for_awake = 0;
+  trying_server = now;
+  empty_msgq();
+  /* Start up the counter (always reset it if "never-give-up" is on) */
+  if ((oldserv < 0) || (never_give_up))
+    oldserv = curserv;
+  if (newserverport) {		/* Jump to specified server */
+    curserv = (-1);		/* Reset server list */
+    strcpy(botserver, newserver);
+    botserverport = newserverport;
+    strcpy(pass, newserverpass);
+    newserver[0] = 0;
+    newserverport = 0;
+    newserverpass[0] = 0;
+    oldserv = (-1);
+  } else
+    pass[0] = 0;
+  if (!cycle_time) {
+    struct chanset_t *chan;
+    struct server_list *x = serverlist;
+
+    if (!x) {
+      putlog(LOG_SERV, "*", "No servers in server list");
+      cycle_time = 300;
+      return;
+    }
+ 
+    servidx = new_dcc(&DCC_DNSWAIT, sizeof(struct dns_info));
+    if (servidx < 0) {
+      putlog(LOG_SERV, "*",
+	     "NO MORE DCC CONNECTIONS -- Can't create server connection.");
+      return;
+    }
+
+    check_bind_event("connect-server");
+    next_server(&curserv, botserver, &botserverport, pass);
+    putlog(LOG_SERV, "*", "%s %s %d", _("Trying server"), botserver, botserverport);
+
+    dcc[servidx].port = botserverport;
+    strcpy(dcc[servidx].nick, "(server)");
+    strncpyz(dcc[servidx].host, botserver, UHOSTLEN);
+
+    botuserhost[0] = 0;
+
+    nick_juped = 0;
+    for (chan = chanset; chan; chan = chan->next)
+      chan->status &= ~CHAN_JUPED;
+
+    dcc[servidx].timeval = now;
+    dcc[servidx].sock = -1;
+    dcc[servidx].u.dns->host = calloc(1, strlen(dcc[servidx].host) + 1);
+    strcpy(dcc[servidx].u.dns->host, dcc[servidx].host);
+    dcc[servidx].u.dns->cbuf = calloc(1, strlen(pass) + 1);
+    strcpy(dcc[servidx].u.dns->cbuf, pass);
+    dcc[servidx].u.dns->dns_success = server_resolve_success;
+    dcc[servidx].u.dns->dns_failure = server_resolve_failure;
+    dcc[servidx].u.dns->dns_type = RES_IPBYHOST;
+    dcc[servidx].u.dns->type = &SERVER_SOCKET;
+
+    if (server_cycle_wait)
+      /* Back to 1st server & set wait time.
+       * Note: Put it here, just in case the server quits on us quickly
+       */
+      cycle_time = server_cycle_wait;
+    else
+      cycle_time = 0;
+
+    /* I'm resolving... don't start another server connect request */
+    resolvserv = 1;
+    /* Resolve the hostname. */
+    dcc_dnsipbyhost(dcc[servidx].host);
+  }
+}
+
+static void server_resolve_failure(int servidx)
+{
+  serv = -1;
+  resolvserv = 0;
+  putlog(LOG_SERV, "*", "%s %s (%s)", _("Failed connect to"), dcc[servidx].host,
+	 _("DNS lookup failed"));
+  lostdcc(servidx);
+}
+
+static void server_resolve_success(int servidx)
+{
+  int oldserv = dcc[servidx].u.dns->ibuf;
+  char s[121], pass[121];
+
+  resolvserv = 0;
+  
+  strcpy(dcc[servidx].addr, dcc[servidx].u.dns->host);
+  strcpy(pass, dcc[servidx].u.dns->cbuf);
+  changeover_dcc(servidx, &SERVER_SOCKET, 0);
+  serv = open_telnet(dcc[servidx].addr, dcc[servidx].port);
+  if (serv < 0) {
+    neterror(s);
+    putlog(LOG_SERV, "*", "%s %s (%s)", _("Failed connect to"), dcc[servidx].host,
+	   s);
+    lostdcc(servidx);
+    if (oldserv == curserv && !never_give_up)
+      fatal("NO SERVERS WILL ACCEPT MY CONNECTION.", 0);
+  } else {
+    dcc[servidx].sock = serv;
+    /* Queue standard login */
+    dcc[servidx].timeval = now;
+    SERVER_SOCKET.timeout_val = &server_timeout;
+    /* Another server may have truncated it, so use the original */
+    strcpy(botname, origbotname);
+    /* Start alternate nicks from the beginning */
+    altnick_char = 0;
+    if (pass[0])
+      dprintf(DP_MODE, "PASS %s\n", pass);
+    dprintf(DP_MODE, "NICK %s\n", botname);
+    dprintf(DP_MODE, "USER %s localhost %s :%s\n", botuser, dcc[servidx].host, botrealname);
+    /* Wait for async result now */
+  }
+}
Index: eggdrop1.7/modules/server/tclserv.c
diff -u /dev/null eggdrop1.7/modules/server/tclserv.c:1.1
--- /dev/null	Sat Oct 27 11:35:18 2001
+++ eggdrop1.7/modules/server/tclserv.c	Sat Oct 27 11:34:52 2001
@@ -0,0 +1,254 @@
+/*
+ * tclserv.c -- part of server.mod
+ *
+ * $Id: tclserv.c,v 1.1 2001/10/27 16:34:52 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static int tcl_isbotnick STDVAR
+{
+  BADARGS(2, 2, " nick");
+  if (match_my_nick(argv[1]))
+    Tcl_AppendResult(irp, "1", NULL);
+  else
+    Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static int tcl_putquick STDVAR
+{
+  char s[511], *p;
+
+  BADARGS(2, 3, " text ?options?");
+  if ((argc == 3) &&
+      strcasecmp(argv[2], "-next") && strcasecmp(argv[2], "-normal")) {
+      Tcl_AppendResult(irp, "unknown putquick option: should be one of: ",
+		       "-normal -next", NULL);
+    return TCL_ERROR;
+  }
+  strncpy(s, argv[1], 510);
+  s[510] = 0;
+  p = strchr(s, '\n');
+  if (p != NULL)
+    *p = 0;
+   p = strchr(s, '\r');
+  if (p != NULL)
+    *p = 0;
+  if (argc == 3 && !strcasecmp(argv[2], "-next"))
+    dprintf(DP_MODE_NEXT, "%s\n", s);
+  else
+    dprintf(DP_MODE, "%s\n", s);
+  return TCL_OK;
+}
+
+static int tcl_putserv STDVAR
+{
+  char s[511], *p;
+
+  BADARGS(2, 3, " text ?options?");
+  if ((argc == 3) &&
+    strcasecmp(argv[2], "-next") && strcasecmp(argv[2], "-normal")) {
+    Tcl_AppendResult(irp, "unknown putserv option: should be one of: ",
+		     "-normal -next", NULL);
+    return TCL_ERROR;
+  }
+  strncpy(s, argv[1], 510);
+  s[510] = 0;
+  p = strchr(s, '\n');
+  if (p != NULL)
+    *p = 0;
+   p = strchr(s, '\r');
+  if (p != NULL)
+    *p = 0;
+  if (argc == 3 && !strcasecmp(argv[2], "-next"))
+    dprintf(DP_SERVER_NEXT, "%s\n", s);
+  else
+    dprintf(DP_SERVER, "%s\n", s);
+  return TCL_OK;
+}
+
+static int tcl_puthelp STDVAR
+{
+  char s[511], *p;
+
+  BADARGS(2, 3, " text ?options?");
+  if ((argc == 3) &&
+    strcasecmp(argv[2], "-next") && strcasecmp(argv[2], "-normal")) {
+    Tcl_AppendResult(irp, "unknown puthelp option: should be one of: ",
+		     "-normal -next", NULL);
+    return TCL_ERROR;
+  }
+  strncpy(s, argv[1], 510);
+  s[510] = 0;
+  p = strchr(s, '\n');
+  if (p != NULL)
+    *p = 0;
+   p = strchr(s, '\r');
+  if (p != NULL)
+    *p = 0;
+  if (argc == 3 && !strcasecmp(argv[2], "-next"))
+    dprintf(DP_HELP_NEXT, "%s\n", s);
+  else
+    dprintf(DP_HELP, "%s\n", s);
+  return TCL_OK;
+}
+
+static int tcl_jump STDVAR
+{
+  BADARGS(1, 4, " ?server? ?port? ?pass?");
+  if (argc >= 2) {
+    strcpy(newserver, argv[1]);
+    if (argc >= 3)
+      newserverport = atoi(argv[2]);
+    else
+      newserverport = default_port;
+    if (argc == 4)
+      strcpy(newserverpass, argv[3]);
+  }
+  cycle_time = 0;
+  nuke_server("changing servers\n");
+  return TCL_OK;
+}
+
+static int tcl_clearqueue STDVAR
+{
+  struct msgq *q, *qq;
+  int msgs;
+  char s[20];
+
+  msgs = 0;
+  BADARGS(2,2, " queue");
+  if (!strcmp(argv[1],"all")) {
+    msgs = (int) (modeq.tot + mq.tot + hq.tot);
+    for (q = modeq.head; q; q = qq) { 
+      qq = q->next;
+      free(q->msg);
+      free(q);
+    }
+    for (q = mq.head; q; q = qq) {
+      qq = q->next;
+      free(q->msg);
+      free(q);
+    }
+    for (q = hq.head; q; q = qq) {
+      qq = q->next;
+      free(q->msg);
+      free(q);
+    }
+    modeq.tot = mq.tot = hq.tot = modeq.warned = mq.warned = hq.warned = 0;
+    mq.head = hq.head = modeq.head = mq.last = hq.last = modeq.last = 0;
+    double_warned = 0;
+    burst = 0;
+    simple_sprintf(s, "%d", msgs);
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  } else if (!strncmp(argv[1],"serv", 4)) {
+    msgs = mq.tot;
+    for (q = mq.head; q; q = qq) {
+      qq = q->next;
+      free(q->msg);
+      free(q);
+    }
+    mq.tot = mq.warned = 0;
+    mq.head = mq.last = 0;
+    if (modeq.tot == 0)
+      burst = 0;
+    double_warned = 0;
+    mq.tot = mq.warned = 0;
+    mq.head = mq.last = 0;
+    simple_sprintf(s, "%d", msgs);
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  } else if (!strcmp(argv[1],"mode")) {
+    msgs = modeq.tot;
+    for (q = modeq.head; q; q = qq) { 
+      qq = q->next;
+      free(q->msg);
+      free(q);
+    }
+    if (mq.tot == 0)
+      burst = 0;
+    double_warned = 0;
+    modeq.tot = modeq.warned = 0;
+    modeq.head = modeq.last = 0;
+    simple_sprintf(s, "%d", msgs);
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  } else if (!strcmp(argv[1],"help")) {
+    msgs = hq.tot;
+    for (q = hq.head; q; q = qq) {
+      qq = q->next;
+      free(q->msg);
+      free(q);
+    }
+    double_warned = 0;
+    hq.tot = hq.warned = 0;
+    hq.head = hq.last = 0;
+    simple_sprintf(s, "%d", msgs);
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  }
+  Tcl_AppendResult(irp, "bad option: must be mode, server, help, or all",
+                   NULL);
+  return TCL_ERROR;
+}
+
+static int tcl_queuesize STDVAR
+{
+  char s[20];
+  int x;
+
+  BADARGS(1, 2, " ?queue?");
+  if (argc == 1) {
+    x = (int) (modeq.tot + hq.tot + mq.tot);
+    simple_sprintf(s, "%d", x);
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  } else if (!strncmp(argv[1], "serv", 4)) {
+    x = (int) (mq.tot);
+    simple_sprintf(s, "%d", x);
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  } else if (!strcmp(argv[1], "mode")) {
+    x = (int) (modeq.tot);
+    simple_sprintf(s, "%d", x);
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  } else if (!strcmp(argv[1], "help")) {
+    x = (int) (hq.tot);
+    simple_sprintf(s, "%d", x);
+    Tcl_AppendResult(irp, s, NULL);
+    return TCL_OK;
+  }
+  Tcl_AppendResult(irp, "bad option: must be mode, server, or help", NULL);
+  return TCL_ERROR;
+}
+
+static tcl_cmds my_tcl_cmds[] =
+{
+  {"jump",		tcl_jump},
+  {"isbotnick",		tcl_isbotnick},
+  {"clearqueue",	tcl_clearqueue},
+  {"queuesize",		tcl_queuesize},
+  {"puthelp",		tcl_puthelp},
+  {"putserv",		tcl_putserv},
+  {"putquick",		tcl_putquick},
+  {NULL,		NULL},
+};
Index: eggdrop1.7/modules/share/.cvsignore
diff -u /dev/null eggdrop1.7/modules/share/.cvsignore:1.1
--- /dev/null	Sat Oct 27 11:35:18 2001
+++ eggdrop1.7/modules/share/.cvsignore	Sat Oct 27 11:34:53 2001
@@ -0,0 +1,8 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.obj
Index: eggdrop1.7/modules/share/Makefile.am
diff -u /dev/null eggdrop1.7/modules/share/Makefile.am:1.1
--- /dev/null	Sat Oct 27 11:35:18 2001
+++ eggdrop1.7/modules/share/Makefile.am	Sat Oct 27 11:34:53 2001
@@ -0,0 +1,15 @@
+# $Id: Makefile.am,v 1.1 2001/10/27 16:34:53 ite Exp $
+
+# FIXME: optionally allow a system wide install by ignoring the line below.
+pkglibdir		= $(exec_prefix)/modules
+
+pkglib_LTLIBRARIES	= share.la
+share_la_SOURCES	= share.c
+share_la_LDFLAGS	= -module -avoid-version -no-undefined
+share_la_LIBADD		= @TCL_LIBS@ @LIBS@
+
+MAINTAINERCLEANFILES	= Makefile.in
+
+INCLUDES		= -I$(top_builddir) -I$(top_srcdir)
+
+DEFS			= $(EGG_DEBUG) @DEFS@
Index: eggdrop1.7/modules/share/help/share.help
diff -u /dev/null eggdrop1.7/modules/share/help/share.help:1.1
--- /dev/null	Sat Oct 27 11:35:18 2001
+++ eggdrop1.7/modules/share/help/share.help	Sat Oct 27 11:34:53 2001
@@ -0,0 +1,73 @@
+%{help=flush}%{+n}
+###  %bflush%b <bot>
+   clears the resync buffer for a share-bot.  this is useful if you
+   want to start over with a userfile transfer: you can unlink the
+   sharebots, flush the resync buffer, and relink.
+
+see also: link, chattr, unlink
+%{help=set allow-resync}%{+n}
+###  %bset allow-resync%b 0/1
+   This setting determines if the bot will re-send the userfile every
+   single time a sharebot reconnects, or if it will store changes
+   that occur for %bresync-time%b seconds and send only these when
+   the sharebot reconnects, this has been know to be a bit unreliable
+   in the past, hence the option.
+
+see also: set resync-time
+%{help=set resync-time}%{+n}
+###  %bset resync-time%b <#>
+   This sets the number of seconds to store resync information for
+   a sharebot before it's assumed to be dead & buried, and therefore
+   the userfile needs to be re-sent.
+
+see also: set allow-resync
+%{help=set private-global}%{+n}
+###  %bset private-global%b 0/1
+   If you are sharing userfiles with someone else, and you don't
+   want %bany%b global flags on the other bots propogated to your bot,
+   set this.  It overrides the setting of private-globals.
+
+see also: set private-globals
+%{help=set private-globals}%{+n}
+###  %bset private-globals%b [flags]
+   If you are sharing userfiles with someone else, and you don't
+   want various global flags on the other bots propogated to your bot,
+   set this to the global flags you would like to remain unaffected.
+   The setting of private-global will override this.
+
+see also: set private-global
+%{help=share module}%{+n}
+###  help on the %bshare module%b
+   This module provides userfile sharing between two bots, it
+   transfers the userfile when they first connect, and then
+   send updates whilst they are connected to keep the userfiles
+   in sync.
+   Commands available: %bflush%b
+   Tcl settings      : %ballow-resync%b  %bresync-time%b
+                       %bprivate-global%b %bprivate-globals%b
+   see %b'.help share howto'%b for a step-by-step list of what
+   needs to be done to setup sharing.
+%{help=share howto}%{+n}
+###   how to setup sharing with the %bshare module%b
+   First you need to decide 2 things:
+     (1) which bot will be 'active' and which 'passive',
+         the passive bot's userfile WILL GET OVERWRITTEN
+         so choose carefully.
+         We shall call the ACTIVE bot A, the PASSIVE bot B
+     (2) which channels you want to share between the 2 bots.
+         Only the channels you choose to share will have their
+         info shared, ALL global info will be shared.
+
+   Steps:
+     (1) Do for each of the channels you want to share a
+         .chanset #channel +shared
+     (2) on A type: '.botattr B +s'   +s == actively share
+         on B type: '.botattr A +p'   +p == passively share
+     (3) for each channel you want shared between the two,
+           on A type: '.botattr B |+s #channel'
+           on B type: '.botattr A |+s #channel'
+     (4) link them together, they should start sharing immediately
+%{help=all}%{+n}
+###  commands for the %bshare module%b
+  %bflush%b
+
Index: eggdrop1.7/modules/share/modinfo
diff -u /dev/null eggdrop1.7/modules/share/modinfo:1.1
--- /dev/null	Sat Oct 27 11:35:18 2001
+++ eggdrop1.7/modules/share/modinfo	Sat Oct 27 11:34:53 2001
@@ -0,0 +1,5 @@
+DESC:This provides the userfile sharing support. You also have to enable the
+DESC:channels & transfer modules to use it.
+DESC:
+DESC:If unsure, ENABLE it.
+DEPENDS:transfer channels
Index: eggdrop1.7/modules/share/share.c
diff -u /dev/null eggdrop1.7/modules/share/share.c:1.1
--- /dev/null	Sat Oct 27 11:35:18 2001
+++ eggdrop1.7/modules/share/share.c	Sat Oct 27 11:34:53 2001
@@ -0,0 +1,2211 @@
+/*
+ * share.c -- part of share.mod
+ *
+ * $Id: share.c,v 1.1 2001/10/27 16:34:53 ite Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define MODULE_NAME "share"
+#define MAKING_SHARE
+
+#include "lib/eggdrop/module.h"
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/stat.h>
+
+#include "src/chan.h"
+#include "src/users.h"
+#include "modules/transfer/transfer.h"
+#include "modules/channels/channels.h"
+
+#define start share_LTX_start
+
+/* Minimum version I will share with. */
+static const int min_share		= 1029900;
+/* Earliest version that supports exempts and invites. */
+static const int min_exemptinvite	= 1032800;
+/* Minimum version that supports userfile features. */
+static const int min_uffeature		= 1050200;
+
+static Function *global = NULL, *transfer_funcs = NULL, *channels_funcs = NULL;
+
+static int private_global = 0;
+static int private_user = 0;
+static char private_globals[50];
+static int allow_resync = 0;
+static struct flag_record fr = {0, 0, 0, 0, 0, 0};
+static int resync_time = 900;
+static int overr_local_bots = 0;	/* Override local bots?		    */
+
+static bind_table_t *BT_dcc;
+
+
+struct delay_mode {
+  struct delay_mode *next;
+  struct chanset_t *chan;
+  int plsmns;
+  int mode;
+  char *mask;
+  int seconds;
+};
+
+static struct delay_mode *start_delay = NULL;
+
+
+/* Store info for sharebots */
+struct share_msgq {
+  struct chanset_t *chan;
+  char *msg;
+  struct share_msgq *next;
+};
+
+typedef struct tandbuf_t {
+  char bot[HANDLEN + 1];
+  time_t timer;
+  struct share_msgq *q;
+  struct tandbuf_t *next;
+} tandbuf;
+
+tandbuf *tbuf;
+
+/* Prototypes */
+static void start_sending_users(int);
+static void shareout_but EGG_VARARGS(struct chanset_t *, arg1);
+static int flush_tbuf(char *);
+static int can_resync(char *);
+static void dump_resync(int);
+static void q_resync(char *, struct chanset_t *);
+static void cancel_user_xfer(int, void *);
+static int private_globals_bitmask();
+
+#include "share.h"
+
+#include "uf_features.c"
+
+/*
+ *   Sup's delay code
+ */
+
+static void add_delay(struct chanset_t *chan, int plsmns, int mode, char *mask)
+{
+  struct delay_mode *d = NULL;
+
+  d = (struct delay_mode *) malloc(sizeof(struct delay_mode));
+  if (!d)
+    return;
+  d->chan = chan;
+  d->plsmns = plsmns;
+  d->mode = mode;
+  d->mask = (char *) malloc(strlen(mask) + 1);
+  if (!d->mask) {
+    free(d);
+    return;
+  }
+  strncpyz(d->mask, mask, strlen(mask) + 1);
+  d->seconds = (int) (now + (random() % 20));
+  d->next = start_delay;
+  start_delay = d;
+}
+
+static void del_delay(struct delay_mode *delay)
+{
+  struct delay_mode *d = NULL, *old = NULL;
+
+  for (d = start_delay; d; old = d, d = d->next) {
+    if (d == delay) {
+     if (old)
+        old->next = d->next;
+      else
+        start_delay = d->next;
+      if (d->mask)
+        free(d->mask);
+      free(d);
+      break;
+    }
+  }
+}
+
+static void check_delay()
+{
+  struct delay_mode *d = NULL, *dnext = NULL;
+
+  for (d = start_delay; d; d = dnext) {
+    dnext = d->next;
+    if (d->seconds <= now) {
+      add_mode(d->chan, d->plsmns, d->mode, d->mask);
+      del_delay(d);
+    }
+  }
+}
+
+static void delay_free_mem()
+{
+  struct delay_mode *d = NULL, *dnext = NULL;
+
+  for (d = start_delay; d; d = dnext) {
+    dnext = d->next;
+    if (d->mask)
+      free(d->mask);
+    free(d);
+  }
+  start_delay = NULL;
+}
+
+/*
+ *   Botnet commands
+ */
+
+static void share_stick_ban(int idx, char *par)
+{
+  char *host, *val;
+  int yn;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    host = newsplit(&par);
+    val = newsplit(&par);
+    yn = atoi(val);
+    noshare = 1;
+    if (!par[0]) {		/* Global ban */
+      if (u_setsticky_ban(NULL, host, yn) > 0) {
+	putlog(LOG_CMDS, "*", "%s: stick %s %c", dcc[idx].nick, host,
+	       yn ? 'y' : 'n');
+	shareout_but(NULL, idx, "s %s %d\n", host, yn);
+      }
+    } else {
+      struct chanset_t *chan = findchan_by_dname(par);
+      struct chanuserrec *cr;
+
+      if ((chan !=NULL) && ((channel_shared(chan) &&
+                             ((cr = get_chanrec(dcc[idx].user, par)) &&
+                              (cr->flags & BOT_AGGRESSIVE))) ||
+                            (bot_flags(dcc[idx].user) & BOT_GLOBAL)))
+	if (u_setsticky_ban(chan, host, yn) > 0) {
+	  putlog(LOG_CMDS, "*", "%s: stick %s %c %s", dcc[idx].nick, host,
+		 yn ? 'y' : 'n', par);
+	  shareout_but(chan, idx, "s %s %d %s\n", host, yn, chan->dname);
+	  noshare = 0;
+	  return;
+	}
+      putlog(LOG_CMDS, "*", "Rejecting invalid sticky ban: %s on %s, %c",
+	     host, par, yn ? 'y' : 'n');
+    }
+    noshare = 0;
+  }
+}
+
+/* Same as share_stick_ban, only for exempts.
+ */
+static void share_stick_exempt(int idx, char *par)
+{
+  char *host, *val;
+  int yn;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    host = newsplit(&par);
+    val = newsplit(&par);
+    yn = atoi(val);
+    noshare = 1;
+    if (!par[0]) {		/* Global exempt */
+      if (u_setsticky_exempt(NULL, host, yn) > 0) {
+	putlog(LOG_CMDS, "*", "%s: stick %s %c", dcc[idx].nick, host,
+	       yn ? 'y' : 'n');
+	shareout_but(NULL, idx, "se %s %d\n", host, yn);
+      }
+    } else {
+      struct chanset_t *chan = findchan_by_dname(par);
+      struct chanuserrec * cr;
+
+      if ((chan != NULL) && ((channel_shared(chan) &&
+                             ((cr = get_chanrec(dcc[idx].user, par)) &&
+                              (cr->flags & BOT_AGGRESSIVE))) ||
+                            (bot_flags(dcc[idx].user) & BOT_GLOBAL)))
+	if (u_setsticky_exempt(chan, host, yn) > 0) {
+	  putlog(LOG_CMDS, "*", "%s: stick %s %c %s", dcc[idx].nick, host,
+		 yn ? 'y' : 'n', par);
+	  shareout_but(chan, idx, "se %s %d %s\n", host, yn, chan->dname);
+	  noshare = 0;
+	  return;
+	}
+      putlog(LOG_CMDS, "*", "Rejecting invalid sticky exempt: %s on %s, %c",
+	     host, par, yn ? 'y' : 'n');
+    }
+    noshare = 0;
+  }
+}
+
+/* Same as share_stick_ban, only for invites.
+ */
+static void share_stick_invite (int idx, char * par) {
+  char *host, *val;
+  int yn;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    host = newsplit(&par);
+    val = newsplit(&par);
+    yn = atoi(val);
+    noshare = 1;
+    if (!par[0]) {		/* Global invite */
+      if (u_setsticky_invite(NULL, host, yn) > 0) {
+ 	    putlog(LOG_CMDS, "*", "%s: stick %s %c", dcc[idx].nick, host,
+ 		   yn ? 'y' : 'n');
+ 	    shareout_but(NULL, idx, "sInv %s %d\n", host, yn);
+      }
+    } else {
+      struct chanset_t *chan = findchan_by_dname(par);
+      struct chanuserrec * cr;
+
+      if ((chan != NULL) && ((channel_shared(chan) &&
+                             ((cr = get_chanrec(dcc[idx].user, par)) &&
+                              (cr->flags & BOT_AGGRESSIVE))) ||
+                            (bot_flags(dcc[idx].user) & BOT_GLOBAL)))
+	if (u_setsticky_invite(chan, host, yn) > 0) {
+	  putlog(LOG_CMDS, "*", "%s: stick %s %c %s", dcc[idx].nick, host,
+		 yn ? 'y' : 'n', par);
+	  shareout_but(chan, idx, "sInv %s %d %s\n", host, yn, chan->dname);
+	  noshare = 0;
+	  return;
+	}
+      putlog(LOG_CMDS, "*", "Rejecting invalid sticky invite: %s on %s, %c",
+	     host, par, yn ? 'y' : 'n');
+    }
+    noshare = 0;
+  }
+}
+
+static void share_chhand(int idx, char *par)
+{
+  char *hand;
+  struct userrec *u;
+
+  if ((dcc[idx].status & STAT_SHARE) && !private_user) {
+    hand = newsplit(&par);
+    u = get_user_by_handle(userlist, hand);
+    if (u && !(u->flags & USER_UNSHARED)) {
+      shareout_but(NULL, idx, "h %s %s\n", hand, par);
+      noshare = 1;
+      if (change_handle(u, par))
+	putlog(LOG_CMDS, "*", "%s: handle %s->%s", dcc[idx].nick, hand,
+	       par);
+      noshare = 0;
+    }
+  }
+}
+
+static void share_chattr(int idx, char *par)
+{
+  char *hand, *atr, s[100];
+  struct chanset_t *cst;
+  struct userrec *u;
+  struct flag_record fr2;
+  int bfl, ofl;
+  module_entry *me;
+
+  if ((dcc[idx].status & STAT_SHARE) && !private_user) {
+    hand = newsplit(&par);
+    u = get_user_by_handle(userlist, hand);
+    if (u && !(u->flags & USER_UNSHARED)) {
+      atr = newsplit(&par);
+      cst = findchan_by_dname(par);
+      if (!par[0] || (cst && channel_shared(cst))) {
+	if (!(dcc[idx].status & STAT_GETTING) && (cst || !private_global))
+	  shareout_but(cst, idx, "a %s %s %s\n", hand, atr, par);
+	noshare = 1;
+	if (par[0] && cst) {
+	  fr.match = (FR_CHAN | FR_BOT);
+	  get_user_flagrec(dcc[idx].user, &fr, par);
+	  if (bot_chan(fr) || bot_global(fr)) {
+	    fr.match = FR_CHAN;
+	    fr2.match = FR_CHAN;
+	    break_down_flags(atr, &fr, 0);
+	    get_user_flagrec(u, &fr2, par);
+	    fr.chan = (fr2.chan & BOT_AGGRESSIVE) |
+	      (fr.chan & ~BOT_AGGRESSIVE);
+	    set_user_flagrec(u, &fr, par);
+	    check_dcc_chanattrs(u, par, fr.chan, fr2.chan);
+	    noshare = 0;
+	    build_flags(s, &fr, 0);
+	    if (!(dcc[idx].status & STAT_GETTING))
+	      putlog(LOG_CMDS, "*", "%s: chattr %s %s %s",
+		     dcc[idx].nick, hand, s, par);
+	    if ((me = module_find("irc", 0, 0))) {
+	      Function *func = me->funcs;
+
+	      (func[IRC_RECHECK_CHANNEL]) (cst, 0);
+	    }
+	  } else
+	    putlog(LOG_CMDS, "*",
+		   "Rejected flags for unshared channel %s from %s",
+		   par, dcc[idx].nick);
+	} else if (!private_global) {
+	  int pgbm = private_globals_bitmask();
+
+	  /* Don't let bot flags be altered */
+	  fr.match = FR_GLOBAL;
+	  break_down_flags(atr, &fr, 0);
+	  bfl = u->flags & USER_BOT;
+	  ofl = fr.global;
+	  fr.global = (fr.global &~pgbm)
+	  |(u->flags & pgbm);
+	  fr.global = sanity_check(fr.global |bfl);
+
+	  set_user_flagrec(u, &fr, 0);
+	  check_dcc_attrs(u, ofl);
+	  noshare = 0;
+	  build_flags(s, &fr, 0);
+	  fr.match = FR_CHAN;
+	  if (!(dcc[idx].status & STAT_GETTING))
+	    putlog(LOG_CMDS, "*", "%s: chattr %s %s", dcc[idx].nick, hand, s);
+	  if ((me = module_find("irc", 0, 0))) {
+	    Function *func = me->funcs;
+
+	    for (cst = chanset; cst; cst = cst->next)
+	      (func[IRC_RECHECK_CHANNEL]) (cst, 0);
+	  }
+	} else
+	  putlog(LOG_CMDS, "*", "Rejected global flags for %s from %s",
+		 hand, dcc[idx].nick);
+        noshare = 0;
+      }
+    }
+  }
+}
+
+static void share_pls_chrec(int idx, char *par)
+{
+  char *user;
+  struct chanset_t *chan;
+  struct userrec *u;
+
+  if ((dcc[idx].status & STAT_SHARE) && !private_user) {
+    user = newsplit(&par);
+    if ((u = get_user_by_handle(userlist, user))) {
+      chan = findchan_by_dname(par);
+      fr.match = (FR_CHAN | FR_BOT);
+      get_user_flagrec(dcc[idx].user, &fr, par);
+      if (!chan || !channel_shared(chan) ||
+	  !(bot_chan(fr) || bot_global(fr)))
+	putlog(LOG_CMDS, "*",
+	       "Rejected info for unshared channel %s from %s",
+	       par, dcc[idx].nick);
+      else {
+	noshare = 1;
+	shareout_but(chan, idx, "+cr %s %s\n", user, par);
+	if (!get_chanrec(u, par)) {
+	  add_chanrec(u, par);
+	  putlog(LOG_CMDS, "*", "%s: +chrec %s %s", dcc[idx].nick, user, par);
+	}
+	noshare = 0;
+      }
+    }
+  }
+}
+
+static void share_mns_chrec(int idx, char *par)
+{
+  char *user;
+  struct chanset_t *chan;
+  struct userrec *u;
+
+  if ((dcc[idx].status & STAT_SHARE) && !private_user) {
+    user = newsplit(&par);
+    if ((u = get_user_by_handle(userlist, user))) {
+      chan = findchan_by_dname(par);
+      fr.match = (FR_CHAN | FR_BOT);
+      get_user_flagrec(dcc[idx].user, &fr, par);
+      if (!chan || !channel_shared(chan) ||
+	  !(bot_chan(fr) || bot_global(fr)))
+	putlog(LOG_CMDS, "*",
+	       "Rejected info for unshared channel %s from %s",
+	       par, dcc[idx].nick);
+      else {
+	noshare = 1;
+	del_chanrec(u, par);
+	shareout_but(chan, idx, "-cr %s %s\n", user, par);
+	noshare = 0;
+	putlog(LOG_CMDS, "*", "%s: -chrec %s %s", dcc[idx].nick, user, par);
+      }
+    }
+  }
+}
+
+static void share_newuser(int idx, char *par)
+{
+  char *nick, *host, *pass, s[100];
+  struct userrec *u;
+
+  if ((dcc[idx].status & STAT_SHARE) && !private_user) {
+    nick = newsplit(&par);
+    host = newsplit(&par);
+    pass = newsplit(&par);
+
+    if (!(u = get_user_by_handle(userlist, nick)) ||
+	!(u->flags & USER_UNSHARED)) {
+      fr.global = 0;
+
+      fr.match = FR_GLOBAL;
+      break_down_flags(par, &fr, NULL);
+
+      /* If user already exists, ignore command */
+      shareout_but(NULL, idx, "n %s %s %s %s\n", nick, host, pass,
+		   private_global ? (fr.global & USER_BOT ? "b" : "-") : par);
+
+      if (!u) {
+	noshare = 1;
+	if (strlen(nick) > HANDLEN)
+	  nick[HANDLEN] = 0;
+
+	if (private_global)
+	  fr.global &= USER_BOT;
+	else {
+	  /* It shouldn't be done before sending to other bots? */
+	  int pgbm = private_globals_bitmask();
+
+	  fr.match = FR_GLOBAL;
+	  fr.global &=~pgbm;
+	}
+
+	build_flags(s, &fr, 0);
+	userlist = adduser(userlist, nick, host, pass, 0);
+
+	/* Support for userdefinedflag share - drummer */
+	u = get_user_by_handle(userlist, nick);
+	set_user_flagrec(u, &fr, 0);
+	fr.match = FR_CHAN; /* why?? */
+	noshare = 0;
+	putlog(LOG_CMDS, "*", "%s: newuser %s %s", dcc[idx].nick, nick, s);
+      }
+    }
+  }
+}
+
+static void share_killuser(int idx, char *par)
+{
+  struct userrec *u;
+
+  /* If user is a share bot, ignore command */
+  if ((dcc[idx].status & STAT_SHARE) && !private_user &&
+      (u = get_user_by_handle(userlist, par)) &&
+      !(u->flags & USER_UNSHARED) &&
+      !((u->flags & USER_BOT) && (bot_flags(u) & BOT_SHARE))) {
+    noshare = 1;
+    if (deluser(par)) {
+      shareout_but(NULL, idx, "k %s\n", par);
+      putlog(LOG_CMDS, "*", "%s: killuser %s", dcc[idx].nick, par);
+    }
+    noshare = 0;
+  }
+}
+
+static void share_pls_host(int idx, char *par)
+{
+  char *hand;
+  struct userrec *u;
+
+  if ((dcc[idx].status & STAT_SHARE) && !private_user) {
+    hand = newsplit(&par);
+    if ((u = get_user_by_handle(userlist, hand)) &&
+	!(u->flags & USER_UNSHARED)) {
+      shareout_but(NULL, idx, "+h %s %s\n", hand, par);
+      set_user(&USERENTRY_HOSTS, u, par);
+      putlog(LOG_CMDS, "*", "%s: +host %s %s", dcc[idx].nick, hand, par);
+    }
+  }
+}
+
+static void share_pls_bothost(int idx, char *par)
+{
+  char *hand, p[32];
+  struct userrec *u;
+
+  if ((dcc[idx].status & STAT_SHARE) && !private_user) {
+    hand = newsplit(&par);
+    if (!(u = get_user_by_handle(userlist, hand)) ||
+	!(u->flags & USER_UNSHARED)) {
+      if (!(dcc[idx].status & STAT_GETTING))
+	shareout_but(NULL, idx, "+bh %s %s\n", hand, par);
+      /* Add bot to userlist if not there */
+      if (u) {
+	if (!(u->flags & USER_BOT))
+	  return;		/* ignore */
+	set_user(&USERENTRY_HOSTS, u, par);
+      } else {
+	makepass(p);
+	userlist = adduser(userlist, hand, par, p, USER_BOT);
+      }
+      if (!(dcc[idx].status & STAT_GETTING))
+	putlog(LOG_CMDS, "*", "%s: +host %s %s", dcc[idx].nick, hand, par);
+    }
+  }
+}
+
+static void share_mns_host(int idx, char *par)
+{
+  char *hand;
+  struct userrec *u;
+
+  if ((dcc[idx].status & STAT_SHARE) && !private_user) {
+    hand = newsplit(&par);
+    if ((u = get_user_by_handle(userlist, hand)) &&
+	!(u->flags & USER_UNSHARED)) {
+      shareout_but(NULL, idx, "-h %s %s\n", hand, par);
+      noshare = 1;
+      delhost_by_handle(hand, par);
+      noshare = 0;
+      putlog(LOG_CMDS, "*", "%s: -host %s %s", dcc[idx].nick, hand, par);
+    }
+  }
+}
+
+static void share_change(int idx, char *par)
+{
+  char *key, *hand;
+  struct userrec *u;
+  struct user_entry_type *uet;
+  struct user_entry *e;
+
+  if ((dcc[idx].status & STAT_SHARE) && !private_user) {
+    key = newsplit(&par);
+    hand = newsplit(&par);
+    if (!(u = get_user_by_handle(userlist, hand)) ||
+	!(u->flags & USER_UNSHARED)) {
+      if (!(uet = find_entry_type(key)))
+	/* If it's not a supported type, forget it */
+	putlog(LOG_CMDS, "*", "Ignore ch %s from %s (unknown type)",
+	       key, dcc[idx].nick);
+      else {
+	if (!(dcc[idx].status & STAT_GETTING))
+	  shareout_but(NULL, idx, "c %s %s %s\n", key, hand, par);
+	noshare = 1;
+	if (!u && (uet == &USERENTRY_BOTADDR)) {
+	  char pass[30];
+
+	  makepass(pass);
+	  userlist = adduser(userlist, hand, "none", pass, USER_BOT);
+	  u = get_user_by_handle(userlist, hand);
+	} else if (!u)
+	  return;
+	if (uet->got_share) {
+	  if (!(e = find_user_entry(uet, u))) {
+	    e = malloc(sizeof(struct user_entry));
+
+	    e->type = uet;
+	    e->name = NULL;
+	    e->u.list = NULL;
+	    list_insert((&(u->entries)), e);
+	  }
+	  uet->got_share(u, e, par, idx);
+	  if (!e->u.list) {
+	    list_delete((struct list_type **) &(u->entries),
+			(struct list_type *) e);
+	    free(e);
+	  }
+	}
+	noshare = 0;
+      }
+    }
+  }
+}
+
+static void share_chchinfo(int idx, char *par)
+{
+  char *hand, *chan;
+  struct chanset_t *cst;
+  struct userrec *u;
+
+  if ((dcc[idx].status & STAT_SHARE) && !private_user) {
+    hand = newsplit(&par);
+    if ((u = get_user_by_handle(userlist, hand)) &&
+	!(u->flags & USER_UNSHARED) && share_greet) {
+      chan = newsplit(&par);
+      cst = findchan_by_dname(chan);
+      fr.match = (FR_CHAN | FR_BOT);
+      get_user_flagrec(dcc[idx].user, &fr, chan);
+      if (!cst || !channel_shared(cst) || !(bot_chan(fr) || bot_global(fr)))
+	putlog(LOG_CMDS, "*",
+	       "Info line change from %s denied.  Channel %s not shared.",
+	       dcc[idx].nick, chan);
+      else {
+	shareout_but(cst, idx, "chchinfo %s %s %s\n", hand, chan, par);
+	noshare = 1;
+	set_handle_chaninfo(userlist, hand, chan, par);
+	noshare = 0;
+	putlog(LOG_CMDS, "*", "%s: change info %s %s", dcc[idx].nick,
+	       chan, hand);
+      }
+    }
+  }
+}
+
+static void share_mns_ban(int idx, char *par)
+{
+  struct chanset_t *chan = NULL;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    shareout_but(NULL, idx, "-b %s\n", par);
+    putlog(LOG_CMDS, "*", "%s: cancel ban %s", dcc[idx].nick, par);
+    str_unescape(par, '\\');
+    noshare = 1;
+    if (u_delban(NULL, par, 1) > 0) {
+      for (chan = chanset; chan; chan = chan->next)
+	add_delay(chan, '-', 'b', par);
+    }
+    noshare = 0;
+  }
+}
+
+static void share_mns_exempt(int idx, char *par)
+{
+  struct chanset_t *chan = NULL;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    shareout_but(NULL, idx, "-e %s\n", par);
+    putlog(LOG_CMDS, "*", "%s: cancel exempt %s", dcc[idx].nick, par);
+    str_unescape(par, '\\');
+    noshare = 1;
+    if (u_delexempt(NULL, par, 1) > 0) {
+      for (chan = chanset; chan; chan = chan->next)
+	add_delay(chan, '-', 'e', par);
+    }
+    noshare = 0;
+  }
+}
+
+static void share_mns_invite(int idx, char *par)
+{
+  struct chanset_t *chan = NULL;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    shareout_but(NULL, idx, "-inv %s\n", par);
+    putlog(LOG_CMDS, "*", "%s: cancel invite %s", dcc[idx].nick, par);
+    str_unescape(par, '\\');
+    noshare = 1;
+    if (u_delinvite(NULL, par, 1) > 0) {
+      for (chan = chanset; chan; chan = chan->next)
+	add_delay(chan, '-', 'I', par);
+    }
+    noshare = 0;
+  }
+}
+
+static void share_mns_banchan(int idx, char *par)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    chname = newsplit(&par);
+    chan = findchan_by_dname(chname);
+    fr.match = (FR_CHAN | FR_BOT);
+    get_user_flagrec(dcc[idx].user, &fr, chname);
+    if (!chan || !channel_shared(chan) ||
+        !(bot_chan(fr) || bot_global(fr)))
+      putlog(LOG_CMDS, "*",
+             "Cancel channel ban %s on %s rejected - channel not shared.",
+             par, chname);
+    else {
+      shareout_but(chan, idx, "-bc %s %s\n", chname, par);
+      putlog(LOG_CMDS, "*", "%s: cancel ban %s on %s", dcc[idx].nick,
+	     par, chname);
+      str_unescape(par, '\\');
+      noshare = 1;
+      if (u_delban(chan, par, 1) > 0)
+	add_delay(chan, '-', 'b', par);
+      noshare = 0;
+    }
+  }
+}
+
+static void share_mns_exemptchan(int idx, char *par)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    chname = newsplit(&par);
+    chan = findchan_by_dname(chname);
+    fr.match = (FR_CHAN | FR_BOT);
+    get_user_flagrec(dcc[idx].user,&fr,chname);
+    if (!chan || !channel_shared(chan) ||
+        !(bot_chan(fr) || bot_global(fr)))
+      putlog(LOG_CMDS, "*",
+        "Cancel channel exempt %s on %s rejected - channel not shared.",
+        par, chname);
+    else {
+      shareout_but(chan,idx, "-ec %s %s\n", chname, par);
+      putlog(LOG_CMDS, "*", "%s: cancel exempt %s on %s", dcc[idx].nick,
+	     par, chname);
+      str_unescape(par, '\\');
+      noshare = 1;
+      if (u_delexempt(chan, par, 1) > 0)
+	add_delay(chan, '-', 'e', par);
+      noshare = 0;
+    }
+  }
+}
+
+static void share_mns_invitechan (int idx, char *par)
+{
+  char *chname;
+  struct chanset_t *chan;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    chname = newsplit(&par);
+    chan = findchan_by_dname(chname);
+    fr.match = (FR_CHAN | FR_BOT);
+    get_user_flagrec(dcc[idx].user,&fr,chname);
+    if (!chan || !channel_shared(chan) ||
+        !(bot_chan(fr) || bot_global(fr)))
+      putlog(LOG_CMDS, "*",
+        "Cancel channel invite %s on %s rejected - channel not shared.",
+        par, chname);
+    else {
+      shareout_but(chan,idx, "-invc %s %s\n", chname, par);
+      putlog(LOG_CMDS, "*", "%s: cancel invite %s on %s", dcc[idx].nick,
+	     par, chname);
+      str_unescape(par, '\\');
+      noshare = 1;
+      if (u_delinvite(chan, par, 1) > 0)
+	add_delay(chan, '-', 'I', par);
+      noshare = 0;
+    }
+  }
+}
+
+static void share_mns_ignore(int idx, char *par)
+{
+  if (dcc[idx].status & STAT_SHARE) {
+    shareout_but(NULL, idx, "-i %s\n", par);
+    putlog(LOG_CMDS, "*", "%s: cancel ignore %s", dcc[idx].nick, par);
+    str_unescape(par, '\\');
+    noshare = 1;
+    delignore(par);
+    noshare = 0;
+  }
+}
+
+static void share_pls_ban(int idx, char *par)
+{
+  time_t expire_time;
+  char *ban, *tm, *from;
+  int flags = 0;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    shareout_but(NULL, idx, "+b %s\n", par);
+    noshare = 1;
+    ban = newsplit(&par);
+    str_unescape(ban, '\\');
+    tm = newsplit(&par);
+    from = newsplit(&par);
+    if (strchr(from, 's'))
+      flags |= MASKREC_STICKY;
+    if (strchr(from, 'p'))
+      flags |= MASKREC_PERM;
+    from = newsplit(&par);
+    expire_time = (time_t) atoi(tm);
+    if (expire_time != 0L)
+      expire_time += now;
+    u_addban(NULL, ban, from, par, expire_time, flags);
+    putlog(LOG_CMDS, "*", "%s: global ban %s (%s:%s)", dcc[idx].nick, ban,
+	   from, par);
+    noshare = 0;
+  }
+}
+
+static void share_pls_banchan(int idx, char *par)
+{
+  time_t expire_time;
+  int flags = 0;
+  struct chanset_t *chan;
+  char *ban, *tm, *chname, *from;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    ban = newsplit(&par);
+    tm = newsplit(&par);
+    chname = newsplit(&par);
+    chan = findchan_by_dname(chname);
+    fr.match = (FR_CHAN | FR_BOT);
+    get_user_flagrec(dcc[idx].user, &fr, chname);
+    if (!chan || !channel_shared(chan) ||
+	!(bot_chan(fr) || bot_global(fr)))
+      putlog(LOG_CMDS, "*",
+	     "Channel ban %s on %s rejected - channel not shared.",
+	     ban, chname);
+    else {
+      shareout_but(chan, idx, "+bc %s %s %s %s\n", ban, tm, chname, par);
+      str_unescape(ban, '\\');
+      from = newsplit(&par);
+      if (strchr(from, 's'))
+	flags |= MASKREC_STICKY;
+      if (strchr(from, 'p'))
+	flags |= MASKREC_PERM;
+      from = newsplit(&par);
+      putlog(LOG_CMDS, "*", "%s: ban %s on %s (%s:%s)", dcc[idx].nick,
+	     ban, chname, from, par);
+      noshare = 1;
+      expire_time = (time_t) atoi(tm);
+      if (expire_time != 0L)
+	expire_time += now;
+      u_addban(chan, ban, from, par, expire_time, flags);
+      noshare = 0;
+    }
+  }
+}
+
+/* Same as share_pls_ban, only for exempts.
+ */
+static void share_pls_exempt(int idx, char *par)
+{
+  time_t expire_time;
+  char *exempt, *tm, *from;
+  int flags = 0;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    shareout_but(NULL,idx, "+e %s\n", par);
+    noshare = 1;
+    exempt = newsplit(&par);
+    str_unescape(exempt, '\\');
+    tm = newsplit(&par);
+    from = newsplit(&par);
+    if (strchr(from,'s'))
+      flags |= MASKREC_STICKY;
+    if (strchr(from,'p'))
+      flags |= MASKREC_PERM;
+    from = newsplit(&par);
+    expire_time = (time_t) atoi(tm);
+    if (expire_time != 0L)
+      expire_time += now;
+    u_addexempt(NULL,exempt, from, par, expire_time,flags);
+    putlog(LOG_CMDS, "*", "%s: global exempt %s (%s:%s)", dcc[idx].nick, exempt,
+	   from, par);
+    noshare = 0;
+  }
+}
+
+/* Same as share_pls_banchan, only for exempts.
+ */
+static void share_pls_exemptchan(int idx, char *par)
+{
+  time_t expire_time;
+  int flags = 0;
+  struct chanset_t *chan;
+  char *exempt, *tm, *chname, *from;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    exempt = newsplit(&par);
+    tm = newsplit(&par);
+    chname = newsplit(&par);
+    chan = findchan_by_dname(chname);
+    fr.match = (FR_CHAN | FR_BOT);
+    get_user_flagrec(dcc[idx].user,&fr,chname);
+    if (!chan || !channel_shared(chan) ||
+	!(bot_chan(fr) || bot_global(fr)))
+      putlog(LOG_CMDS, "*",
+	     "Channel exempt %s on %s rejected - channel not shared.",
+	     exempt, chname);
+    else {
+      shareout_but(chan,idx, "+ec %s %s %s %s\n", exempt, tm, chname, par);
+      str_unescape(exempt, '\\');
+      from = newsplit(&par);
+      if (strchr(from,'s'))
+	flags |= MASKREC_STICKY;
+      if (strchr(from,'p'))
+	flags |= MASKREC_PERM;
+      from = newsplit(&par);
+      putlog(LOG_CMDS, "*", "%s: exempt %s on %s (%s:%s)", dcc[idx].nick,
+	     exempt, chname, from, par);
+      noshare = 1;
+      expire_time = (time_t) atoi(tm);
+      if (expire_time != 0L)
+	expire_time += now;
+      u_addexempt(chan, exempt, from, par, expire_time,flags);
+      noshare = 0;
+    }
+  }
+}
+
+/* Same as share_pls_ban, only for invites.
+ */
+static void share_pls_invite(int idx, char *par)
+{
+  time_t expire_time;
+  char *invite, *tm, *from;
+  int flags = 0;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    shareout_but(NULL,idx, "+inv %s\n", par);
+    noshare = 1;
+    invite = newsplit(&par);
+    str_unescape(invite, '\\');
+    tm = newsplit(&par);
+    from = newsplit(&par);
+    if (strchr(from,'s'))
+      flags |= MASKREC_STICKY;
+    if (strchr(from,'p'))
+      flags |= MASKREC_PERM;
+    from = newsplit(&par);
+    expire_time = (time_t) atoi(tm);
+    if (expire_time != 0L)
+      expire_time += now;
+    u_addinvite(NULL,invite, from, par, expire_time,flags);
+    putlog(LOG_CMDS, "*", "%s: global invite %s (%s:%s)", dcc[idx].nick,
+	   invite, from, par);
+    noshare = 0;
+  }
+}
+
+/* Same as share_pls_banchan, only for invites.
+ */
+static void share_pls_invitechan(int idx, char *par)
+{
+  time_t expire_time;
+  int flags = 0;
+  struct chanset_t *chan;
+  char *invite, *tm, *chname, *from;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    invite = newsplit(&par);
+    tm = newsplit(&par);
+    chname = newsplit(&par);
+    chan = findchan_by_dname(chname);
+    fr.match = (FR_CHAN | FR_BOT);
+    get_user_flagrec(dcc[idx].user,&fr,chname);
+    if (!chan || !channel_shared(chan) ||
+	!(bot_chan(fr) || bot_global(fr)))
+      putlog(LOG_CMDS, "*",
+	     "Channel invite %s on %s rejected - channel not shared.",
+	     invite, chname);
+    else {
+      shareout_but(chan,idx, "+invc %s %s %s %s\n", invite, tm, chname, par);
+      str_unescape(invite, '\\');
+      from = newsplit(&par);
+      if (strchr(from,'s'))
+	flags |= MASKREC_STICKY;
+      if (strchr(from,'p'))
+	flags |= MASKREC_PERM;
+      from = newsplit(&par);
+      putlog(LOG_CMDS, "*", "%s: invite %s on %s (%s:%s)", dcc[idx].nick,
+	     invite, chname, from, par);
+      noshare = 1;
+      expire_time = (time_t) atoi(tm);
+      if (expire_time != 0L)
+	expire_time += now;
+      u_addinvite(chan, invite, from, par, expire_time,flags);
+      noshare = 0;
+    }
+  }
+}
+
+/* +i <host> +<seconds-left> <from> <note>
+ */
+static void share_pls_ignore(int idx, char *par)
+{
+  time_t expire_time;
+  char *ign, *from, *ts;
+
+  if (dcc[idx].status & STAT_SHARE) {
+    shareout_but(NULL, idx, "+i %s\n", par);
+    noshare = 1;
+    ign = newsplit(&par);
+    str_unescape(ign, '\\');
+    ts = newsplit(&par);
+    if (!atoi(ts))
+      expire_time = 0L;
+    else
+      expire_time = now + atoi(ts);
+    from = newsplit(&par);
+    if (strchr(from, 'p'))
+      expire_time = 0;
+    from = newsplit(&par);
+    if (strlen(from) > HANDLEN + 1)
+      from[HANDLEN + 1] = 0;
+    par[65] = 0;
+    putlog(LOG_CMDS, "*", "%s: ignore %s (%s: %s)",
+	   dcc[idx].nick, ign, from, par);
+    addignore(ign, from, par, expire_time);
+    noshare = 0;
+  }
+}
+
+static void share_ufno(int idx, char *par)
+{
+  putlog(LOG_BOTS, "*", "User file rejected by %s: %s",
+	 dcc[idx].nick, par);
+  dcc[idx].status &= ~STAT_OFFERED;
+  if (!(dcc[idx].status & STAT_GETTING))
+    dcc[idx].status &= ~(STAT_SHARE | STAT_AGGRESSIVE);
+}
+
+static void share_ufyes(int idx, char *par)
+{
+  if (dcc[idx].status & STAT_OFFERED) {
+    dcc[idx].status &= ~STAT_OFFERED;
+    dcc[idx].status |= STAT_SHARE;
+    dcc[idx].status |= STAT_SENDING;
+    uf_features_parse(idx, par);
+    start_sending_users(idx);
+    putlog(LOG_BOTS, "*", "Sending user file send request to %s",
+	   dcc[idx].nick);
+  }
+}
+
+static void share_userfileq(int idx, char *par)
+{
+  int ok = 1, i, bfl = bot_flags(dcc[idx].user);
+
+  flush_tbuf(dcc[idx].nick);
+  if (bfl & BOT_AGGRESSIVE)
+    dprintf(idx, "s un I have you marked for Agressive sharing.\n");
+  else if (!(bfl & BOT_PASSIVE))
+    dprintf(idx, "s un You are not marked for sharing with me.\n");
+  else if (min_share > dcc[idx].u.bot->numver)
+    dprintf(idx,
+	    "s un Your version is not high enough, need v%d.%d.%d\n",
+	    (min_share / 1000000), (min_share / 10000) % 100,
+	    (min_share / 100) % 100);
+  else {
+    for (i = 0; i < dcc_total; i++)
+      if (dcc[i].type->flags & DCT_BOT) {
+	if ((dcc[i].status & STAT_SHARE) &&
+	    (dcc[i].status & STAT_AGGRESSIVE) && (i != idx)) {
+	  ok = 0;
+	  break;
+	}
+      }
+    if (!ok)
+      dprintf(idx, "s un Already sharing.\n");
+    else {
+      if (dcc[idx].u.bot->numver >= min_uffeature)
+	dprintf(idx, "s uy %s\n", uf_features_dump(idx));
+      else
+        dprintf(idx, "s uy\n");
+      /* Set stat-getting to astatic void race condition (robey 23jun1996) */
+      dcc[idx].status |= STAT_SHARE | STAT_GETTING | STAT_AGGRESSIVE;
+      putlog(LOG_BOTS, "*", "Downloading user file from %s", dcc[idx].nick);
+    }
+  }
+}
+
+/* us <ip> <port> <length>
+ */
+static void share_ufsend(int idx, char *par)
+{
+  char *ip, *port;
+  char s[1024];
+  int i, sock;
+  FILE *f;
+
+  snprintf(s, sizeof s, ".share.%s.%lu.users", botnetnick, now);
+  if (!(b_status(idx) & STAT_SHARE)) {
+    dprintf(idx, "s e You didn't ask; you just started sending.\n");
+    dprintf(idx, "s e Ask before sending the userfile.\n");
+    zapfbot(idx);
+  } else if (dcc_total == max_dcc) {
+    putlog(LOG_MISC, "*", "NO MORE DCC CONNECTIONS -- can't grab userfile");
+    dprintf(idx, "s e I can't open a DCC to you; I'm full.\n");
+    zapfbot(idx);
+  } else if (!(f = fopen(s, "wb"))) {
+    putlog(LOG_MISC, "*", "CAN'T WRITE USERFILE DOWNLOAD FILE!");
+    zapfbot(idx);
+  } else {
+    ip = newsplit(&par);
+    port = newsplit(&par);
+    sock = getsock(SOCK_BINARY);	/* Don't buffer this -> mark binary. */
+    if (sock < 0 || open_telnet_dcc(sock, ip, port) < 0) {
+      killsock(sock);
+      putlog(LOG_BOTS, "*", "Asynchronous connection failed!");
+      dprintf(idx, "s e Can't connect to you!\n");
+      zapfbot(idx);
+    } else {
+      unsigned int ip_int;
+
+      i = new_dcc(&DCC_FORK_SEND, sizeof(struct xfer_info));
+      sscanf(ip, "%u", &ip_int);
+      strcpy(dcc[i].addr, iptostr(htonl(ip_int)));
+      dcc[i].port = atoi(port);
+      strcpy(dcc[i].nick, "*users");
+      malloc_strcpy(dcc[i].u.xfer->filename, s);
+      dcc[i].u.xfer->origname = dcc[i].u.xfer->filename;
+      dcc[i].u.xfer->length = atoi(par);
+      dcc[i].u.xfer->f = f;
+      dcc[i].sock = sock;
+      strcpy(dcc[i].host, dcc[idx].nick);
+
+      dcc[idx].status |= STAT_GETTING;
+    }
+  }
+}
+
+static void share_resyncq(int idx, char *par)
+{
+  if (!allow_resync)
+    dprintf(idx, "s rn Not permitting resync.\n");
+  else {
+    int bfl = bot_flags(dcc[idx].user);
+
+    if (!(bfl & BOT_SHARE))
+      dprintf(idx, "s rn You are not marked for sharing with me.\n");
+    else if (can_resync(dcc[idx].nick)) {
+      dprintf(idx, "s r!\n");
+      dump_resync(idx);
+      dcc[idx].status &= ~STAT_OFFERED;
+      dcc[idx].status |= STAT_SHARE;
+      putlog(LOG_BOTS, "*", "Resync'd user file with %s", dcc[idx].nick);
+      updatebot(-1, dcc[idx].nick, '+', 0);
+    } else if (bfl & BOT_PASSIVE) {
+      dprintf(idx, "s r!\n");
+      dcc[idx].status &= ~STAT_OFFERED;
+      dcc[idx].status |= STAT_SHARE;
+      updatebot(-1, dcc[idx].nick, '+', 0);
+      putlog(LOG_BOTS, "*", "Resyncing user file from %s", dcc[idx].nick);
+    } else
+      dprintf(idx, "s rn No resync buffer.\n");
+  }
+}
+
+static void share_resync(int idx, char *par)
+{
+  if ((dcc[idx].status & STAT_OFFERED) && can_resync(dcc[idx].nick)) {
+    dump_resync(idx);
+    dcc[idx].status &= ~STAT_OFFERED;
+    dcc[idx].status |= STAT_SHARE;
+    updatebot(-1, dcc[idx].nick, '+', 0);
+    putlog(LOG_BOTS, "*", "Resync'd user file with %s", dcc[idx].nick);
+  }
+}
+
+static void share_resync_no(int idx, char *par)
+{
+  putlog(LOG_BOTS, "*", "Resync refused by %s: %s", dcc[idx].nick, par);
+  flush_tbuf(dcc[idx].nick);
+  dprintf(idx, "s u?\n");
+}
+
+static void share_version(int idx, char *par)
+{
+  /* Cleanup any share flags */
+  dcc[idx].status &= ~(STAT_SHARE | STAT_GETTING | STAT_SENDING |
+		       STAT_OFFERED | STAT_AGGRESSIVE);
+  dcc[idx].u.bot->uff_flags = 0;
+  if ((dcc[idx].u.bot->numver >= min_share)
+      && (bot_flags(dcc[idx].user) & BOT_AGGRESSIVE)) {
+    if (can_resync(dcc[idx].nick))
+      dprintf(idx, "s r?\n");
+    else
+      dprintf(idx, "s u?\n");
+    dcc[idx].status |= STAT_OFFERED;
+  }
+}
+
+static void hook_read_userfile()
+{
+  int i;
+
+  if (!noshare) {
+    for (i = 0; i < dcc_total; i++)
+      if ((dcc[i].type->flags & DCT_BOT) && (dcc[i].status & STAT_SHARE) &&
+	  !(dcc[i].status & STAT_AGGRESSIVE)) {
+	/* Cancel any existing transfers */
+	if (dcc[i].status & STAT_SENDING)
+	  cancel_user_xfer(-i, 0);
+	dprintf(i, "s u?\n");
+	dcc[i].status |= STAT_OFFERED;
+      }
+  }
+}
+
+static void share_endstartup(int idx, char *par)
+{
+  dcc[idx].status &= ~STAT_GETTING;
+  /* Send to any other sharebots */
+  hook_read_userfile();
+}
+
+static void share_end(int idx, char *par)
+{
+  putlog(LOG_BOTS, "*", "Ending sharing with %s, (%s).", dcc[idx].nick, par);
+  cancel_user_xfer(-idx, 0);
+  dcc[idx].status &= ~(STAT_SHARE | STAT_GETTING | STAT_SENDING |
+		       STAT_OFFERED | STAT_AGGRESSIVE);
+  dcc[idx].u.bot->uff_flags = 0;
+}
+
+static void share_feats(int idx, char *par)
+{
+  (int) uf_features_check(idx, par);
+}
+
+
+/* Note: these MUST be sorted. */
+static botcmd_t C_share[] =
+{
+  {"!",		(Function) share_endstartup},
+  {"+b",	(Function) share_pls_ban},
+  {"+bc",	(Function) share_pls_banchan},
+  {"+bh",	(Function) share_pls_bothost},
+  {"+cr",	(Function) share_pls_chrec},
+  {"+e",	(Function) share_pls_exempt},
+  {"+ec",	(Function) share_pls_exemptchan},
+  {"+h",	(Function) share_pls_host},
+  {"+i",	(Function) share_pls_ignore},
+  {"+inv",	(Function) share_pls_invite},
+  {"+invc",	(Function) share_pls_invitechan},
+  {"-b",	(Function) share_mns_ban},
+  {"-bc",	(Function) share_mns_banchan},
+  {"-cr",	(Function) share_mns_chrec},
+  {"-e",	(Function) share_mns_exempt},
+  {"-ec",	(Function) share_mns_exemptchan},
+  {"-h",	(Function) share_mns_host},
+  {"-i",	(Function) share_mns_ignore},
+  {"-inv",	(Function) share_mns_invite},
+  {"-invc",	(Function) share_mns_invitechan},
+  {"a",		(Function) share_chattr},
+  {"c",		(Function) share_change},
+  {"chchinfo",	(Function) share_chchinfo},
+  {"e",		(Function) share_end},
+  {"feats",	(Function) share_feats},
+  {"h",		(Function) share_chhand},
+  {"k",		(Function) share_killuser},
+  {"n",		(Function) share_newuser},
+  {"r!",	(Function) share_resync},
+  {"r?",	(Function) share_resyncq},
+  {"rn",	(Function) share_resync_no},
+  {"s",		(Function) share_stick_ban},
+  {"se",	(Function) share_stick_exempt},
+  {"sInv",	(Function) share_stick_invite},
+  {"u?",	(Function) share_userfileq},
+  {"un",	(Function) share_ufno},
+  {"us",	(Function) share_ufsend},
+  {"uy",	(Function) share_ufyes},
+  {"v",		(Function) share_version},
+  {NULL,	NULL}
+};
+
+
+static void sharein_mod(int idx, char *msg)
+{
+  char *code;
+  int f, i;
+
+  code = newsplit(&msg);
+  for (f = 0, i = 0; C_share[i].name && !f; i++) {
+    int y = strcasecmp(code, C_share[i].name);
+
+    if (!y)
+      /* Found a match */
+      (C_share[i].func)(idx, msg);
+    if (y < 0)
+      f = 1;
+  }
+}
+
+static void shareout_mod EGG_VARARGS_DEF(struct chanset_t *, arg1)
+{
+  int i, l;
+  char *format;
+  char s[601];
+  struct chanset_t *chan;
+  va_list va;
+
+  chan = EGG_VARARGS_START(struct chanset_t *, arg1, va);
+  if (!chan || channel_shared(chan)) {
+    format = va_arg(va, char *);
+    strcpy(s, "s ");
+    if ((l = vsnprintf(s + 2, 509, format, va)) < 0)
+      s[2 + (l = 509)] = 0;
+    for (i = 0; i < dcc_total; i++)
+      if ((dcc[i].type->flags & DCT_BOT) &&
+	  (dcc[i].status & STAT_SHARE) &&
+	  !(dcc[i].status & (STAT_GETTING | STAT_SENDING))) {
+	if (chan) {
+	  fr.match = (FR_CHAN | FR_BOT);
+	  get_user_flagrec(dcc[i].user, &fr, chan->dname);
+	}
+	if (!chan || bot_chan(fr) || bot_global(fr))
+	  tputs(dcc[i].sock, s, l + 2);
+      }
+    q_resync(s, chan);
+  }
+  va_end(va);
+}
+
+static void shareout_but EGG_VARARGS_DEF(struct chanset_t *, arg1)
+{
+  int i, x, l;
+  char *format;
+  char s[601];
+  struct chanset_t *chan;
+  va_list va;
+
+  chan = EGG_VARARGS_START(struct chanset_t *, arg1, va);
+  x = va_arg(va, int);
+  format = va_arg(va, char *);
+
+  strcpy(s, "s ");
+  if ((l = vsnprintf(s + 2, 509, format, va)) < 0)
+    s[2 + (l = 509)] = 0;
+  for (i = 0; i < dcc_total; i++)
+    if ((dcc[i].type->flags & DCT_BOT) && (i != x) &&
+	(dcc[i].status & STAT_SHARE) &&
+	(!(dcc[i].status & STAT_GETTING)) &&
+	(!(dcc[i].status & STAT_SENDING))) {
+      if (chan) {
+	fr.match = (FR_CHAN | FR_BOT);
+	get_user_flagrec(dcc[i].user, &fr, chan->dname);
+      }
+      if (!chan || bot_chan(fr) || bot_global(fr))
+	tputs(dcc[i].sock, s, l + 2);
+    }
+  q_resync(s, chan);
+  va_end(va);
+}
+
+
+/*
+ *    Resync buffers
+ */
+
+/* Create a tandem buffer for 'bot'.
+ */
+static void new_tbuf(char *bot)
+{
+  tandbuf **old = &tbuf, *new;
+
+  new = malloc(sizeof(tandbuf));
+  strcpy(new->bot, bot);
+  new->q = NULL;
+  new->timer = now;
+  new->next = *old;
+  *old = new;
+      putlog(LOG_BOTS, "*", "Creating resync buffer for %s", bot);
+}
+
+static void del_tbuf(tandbuf *goner)
+{
+  struct share_msgq *q, *r;
+  tandbuf *t = NULL, *old = NULL;
+
+  for (t = tbuf; t; old = t, t = t->next) {
+    if (t == goner) {
+      if (old)
+        old->next = t->next;
+      else
+        tbuf = t->next;
+      for (q = t->q; q && q->msg[0]; q = r) {
+	r = q->next;
+	free(q->msg);
+	free(q);
+      }
+      free(t);
+      break;
+    }
+  }
+}
+
+/* Flush a certain bot's tbuf.
+ */
+static int flush_tbuf(char *bot)
+{
+  tandbuf *t, *tnext = NULL;
+
+  for (t = tbuf; t; t = tnext) {
+    tnext = t->next;
+    if (!strcasecmp(t->bot, bot)) {
+      del_tbuf(t);
+      return 1;
+    }
+  }
+  return 0;
+}
+
+/* Flush all tbufs older than 15 minutes.
+ */
+static void check_expired_tbufs()
+{
+  int i;
+  tandbuf *t, *tnext = NULL;
+
+  for (t = tbuf; t; t = tnext) {
+    tnext = t->next;
+    if ((now - t->timer) > resync_time) {
+	putlog(LOG_BOTS, "*", "Flushing resync buffer for clonebot %s.",
+	t->bot);
+      del_tbuf(t);
+    }
+  }
+  /* Resend userfile requests */
+  for (i = 0; i < dcc_total; i++)
+    if (dcc[i].type->flags & DCT_BOT) {
+      if (dcc[i].status & STAT_OFFERED) {
+	if (now - dcc[i].timeval > 120) {
+	  if (dcc[i].user && (bot_flags(dcc[i].user) & BOT_AGGRESSIVE))
+	    dprintf(i, "s u?\n");
+	  /* ^ send it again in case they missed it */
+	}
+	/* If it's a share bot that hasnt been sharing, ask again */
+      } else if (!(dcc[i].status & STAT_SHARE)) {
+	if (dcc[i].user && (bot_flags(dcc[i].user) & BOT_AGGRESSIVE))
+	  dprintf(i, "s u?\n");
+	dcc[i].status |= STAT_OFFERED;
+      }
+    }
+}
+
+static struct share_msgq *q_addmsg(struct share_msgq *qq,
+				   struct chanset_t *chan, char *s)
+{
+  struct share_msgq *q;
+  int cnt;
+
+  if (!qq) {
+    q = (struct share_msgq *) malloc(sizeof(struct share_msgq));
+
+    q->chan = chan;
+    q->next = NULL;
+    malloc_strcpy(q->msg, s);
+    return q;
+  }
+  cnt = 0;
+  for (q = qq; q->next; q = q->next)
+    cnt++;
+  if (cnt > 1000)
+    return NULL;		/* Return null: did not alter queue */
+  q->next = (struct share_msgq *) malloc(sizeof(struct share_msgq));
+
+  q = q->next;
+  q->chan = chan;
+  q->next = NULL;
+  malloc_strcpy(q->msg, s);
+  return qq;
+}
+
+/* Add stuff to a specific bot's tbuf.
+ */
+static void q_tbuf(char *bot, char *s, struct chanset_t *chan)
+{
+  struct share_msgq *q;
+  tandbuf *t;
+
+  for (t = tbuf; t && t->bot[0]; t = t->next)
+    if (!strcasecmp(t->bot, bot)) {
+      if (chan) {
+	fr.match = (FR_CHAN | FR_BOT);
+	get_user_flagrec(get_user_by_handle(userlist, bot), &fr, chan->dname);
+      }
+      if ((!chan || bot_chan(fr) || bot_global(fr))
+	  && (q = q_addmsg(t->q, chan, s)))
+	t->q = q;
+      break;
+    }
+}
+
+/* Add stuff to the resync buffers.
+ */
+static void q_resync(char *s, struct chanset_t *chan)
+{
+  struct share_msgq *q;
+  tandbuf *t;
+
+  for (t = tbuf; t && t->bot[0]; t = t->next) {
+      if (chan) {
+	fr.match = (FR_CHAN | FR_BOT);
+      get_user_flagrec(get_user_by_handle(userlist, t->bot), &fr,
+	chan->dname);
+      }
+      if ((!chan || bot_chan(fr) || bot_global(fr))
+	  && (q = q_addmsg(t->q, chan, s)))
+      t->q = q;
+    }
+}
+
+/* Is bot in resync list?
+ */
+static int can_resync(char *bot)
+{
+  tandbuf *t;
+
+  for (t = tbuf; t && t->bot[0]; t = t->next)
+    if (!strcasecmp(bot, t->bot))
+      return 1;
+  return 0;
+}
+
+/* Dump the resync buffer for a bot.
+ */
+static void dump_resync(int idx)
+{
+  struct share_msgq *q;
+  tandbuf *t;
+
+  for (t = tbuf; t && t->bot[0]; t = t->next)
+    if (!strcasecmp(dcc[idx].nick, t->bot)) {
+      for (q = t->q; q && q->msg[0]; q = q->next) {
+	dprintf(idx, "%s", q->msg);
+      }
+      flush_tbuf(dcc[idx].nick);
+      break;
+    }
+}
+
+/* Give status report on tbufs.
+ */
+static void status_tbufs(int idx)
+{
+  int count, off = 0;
+  struct share_msgq *q;
+  char s[121];
+  tandbuf *t;
+
+  off = 0;
+  for (t = tbuf; t && t->bot[0]; t = t->next) 
+    if (off < (110 - HANDLEN)) {
+      off += my_strcpy(s + off, t->bot);
+      count = 0;
+      for (q = t->q; q; q = q->next)
+	count++;
+      off += simple_sprintf(s + off, " (%d), ", count);
+    }
+  if (off) {
+    s[off - 2] = 0;
+    dprintf(idx, "Pending sharebot buffers: %s\n", s);
+  }
+}
+
+static int write_tmp_userfile(char *fn, struct userrec *bu, int idx)
+{
+  FILE *f;
+  struct userrec *u;
+  int ok = 0;
+
+  if (!(f = fopen(fn, "wb")))
+    putlog(LOG_MISC, "*", _("ERROR writing user file to transfer."));
+  else {
+    chmod(fn, 0600);		/* make it -rw------- */
+    fprintf(f, "#4v: %s -- %s -- transmit\n", ver, origbotname);
+    ok = 1;
+    for (u = bu; u && ok; u = u->next)
+      ok = write_user(u, f, idx);
+    ok = write_bans(f, idx);
+    /* Only share with bots which support exempts and invites.
+     *
+     * If UFF is supported, we also check the UFF flags before sharing. If
+     * UFF isn't supported, but +I/+e is supported, we just share.
+     */
+    if (dcc[idx].u.bot->numver >= min_exemptinvite) {
+      if ((dcc[idx].u.bot->uff_flags & UFF_EXEMPT) ||
+	  (dcc[idx].u.bot->numver < min_uffeature))
+	ok = write_exempts(f, idx);
+      if ((dcc[idx].u.bot->uff_flags & UFF_INVITE) ||
+	  (dcc[idx].u.bot->numver < min_uffeature))
+	ok = write_invites(f, idx);
+    } else
+      putlog(LOG_BOTS, "*", "%s is too old: not sharing exempts and invites.",
+             dcc[idx].nick);
+    fclose(f);
+    if (!ok)
+      putlog(LOG_MISC, "*", _("ERROR writing user file to transfer."));
+  }
+  return ok;
+}
+
+
+/* Create a copy of the entire userlist (for sending user lists to clone
+ * bots) -- userlist is reversed in the process, which is OK because the
+ * receiving bot reverses the list AGAIN when saving.
+ *
+ * t = 0:   copy everything BUT tandem-bots
+ * t = 1:   copy only tandem-bots
+ * t = 2;   copy all entries
+ */
+static struct userrec *dup_userlist(int t)
+{
+  struct userrec *u, *u1, *retu, *nu;
+  struct chanuserrec *ch;
+  struct user_entry *ue;
+  char *p;
+
+  nu = retu = NULL;
+  noshare = 1;
+  for (u = userlist; u; u = u->next)
+    /* Only copying non-bot entries? */
+    if (((t == 0) && !(u->flags & (USER_BOT | USER_UNSHARED))) ||
+	/* ... or only copying bot entries? */
+	((t == 1) && (u->flags & (USER_BOT | USER_UNSHARED))) ||
+	/* ... or copying everything? */
+	(t == 2)) {
+      p = get_user(&USERENTRY_PASS, u);
+      u1 = adduser(NULL, u->handle, 0, p, u->flags);
+      u1->flags_udef = u->flags_udef;
+      if (!nu)
+	nu = retu = u1;
+      else {
+	nu->next = u1;
+	nu = nu->next;
+      }
+      for (ch = u->chanrec; ch; ch = ch->next) {
+	struct chanuserrec *z = add_chanrec(nu, ch->channel);
+
+	if (z) {
+	  z->flags = ch->flags;
+	  z->flags_udef = ch->flags_udef;
+	  z->laston = ch->laston;
+	  set_handle_chaninfo(nu, nu->handle, ch->channel, ch->info);
+	}
+      }
+      for (ue = u->entries; ue; ue = ue->next) {
+	if (ue->name) {
+	  struct list_type *lt;
+	  struct user_entry *nue;
+
+	  nue = malloc(sizeof(struct user_entry));
+	  nue->name = malloc(strlen(ue->name) + 1);
+	  nue->type = NULL;
+	  nue->u.list = NULL;
+	  strcpy(nue->name, ue->name);
+	  list_insert((&nu->entries), nue);
+	  for (lt = ue->u.list; lt; lt = lt->next) {
+	    struct list_type *list;
+
+	    list = malloc(sizeof(struct list_type));
+	    list->next = NULL;
+	    malloc_strcpy(list->extra, lt->extra);
+	    list_append((&nue->u.list), list);
+	  }
+	} else {
+	  if (ue->type->dup_user && (t || ue->type->got_share))
+	    ue->type->dup_user(nu, u, ue);
+	}
+      }
+    }
+  noshare = 0;
+  return retu;
+}
+
+/* Erase old user list, switch to new one.
+ */
+static void finish_share(int idx)
+{
+  struct userrec *u = NULL, *ou = NULL;
+  struct chanset_t *chan;
+  int i, j = -1;
+
+  for (i = 0; i < dcc_total; i++)
+    if (!strcasecmp(dcc[i].nick, dcc[idx].host) &&
+	(dcc[i].type->flags & DCT_BOT))
+      j = i;
+  if (j == -1)
+    return;
+
+  if (!uff_call_receiving(j, dcc[idx].u.xfer->filename)) {
+    putlog(LOG_BOTS, "*", "A uff parsing function failed for the userfile!");
+    unlink(dcc[idx].u.xfer->filename);
+    return;
+  }
+
+  if (dcc[j].u.bot->uff_flags & UFF_OVERRIDE)
+    debug1("NOTE: Sharing passively with %s, overriding local bots.",
+	   dcc[j].nick);
+  else
+    /* Copy the bots over. The entries will be used in the new user list. */
+    u = dup_userlist(1);
+
+  /*
+   * This is where we remove all global and channel bans/exempts/invites and
+   * ignores since they will be replaced by what our hub gives us.
+   */
+
+  noshare = 1;
+  fr.match = (FR_CHAN | FR_BOT);
+  while (global_bans)
+    u_delban(NULL, global_bans->mask, 1);
+  while (global_ign)
+    delignore(global_ign->igmask);
+  while (global_invites)
+    u_delinvite(NULL, global_invites->mask, 1);
+  while (global_exempts)
+    u_delexempt(NULL, global_exempts->mask, 1);
+  for (chan = chanset; chan; chan = chan->next)
+    if (channel_shared(chan)) {
+      get_user_flagrec(dcc[j].user, &fr, chan->dname);
+      if (bot_chan(fr) || bot_global(fr)) {
+	while (chan->bans)
+	  u_delban(chan, chan->bans->mask, 1);
+	while (chan->exempts)
+	  u_delexempt(chan, chan->exempts->mask, 1);
+	while (chan->invites)
+	  u_delinvite(chan, chan->invites->mask, 1);
+      }
+    }
+  noshare = 0;
+  ou = userlist;		/* Save old user list			*/
+  userlist = (void *) -1;	/* Do this to prevent .user messups	*/
+
+  /* Bot user pointers are updated to point to the new list, all others
+   * are set to NULL. If our userfile will be overriden, just set _all_
+   * to NULL directly.
+   */
+  if (u == NULL)
+    for (i = 0; i < dcc_total; i++)
+      dcc[i].user = NULL;
+  else
+    for (i = 0; i < dcc_total; i++)
+      dcc[i].user = get_user_by_handle(u, dcc[i].nick);
+
+  /* Read the transferred userfile. Add entries to u, which already holds
+   * the bot entries in non-override mode.
+   */
+  if (!readuserfile(dcc[idx].u.xfer->filename, &u)) {
+    putlog(LOG_MISC, "*", "%s", _("CANT READ NEW USERFILE"));
+    clear_userlist(u);		/* Clear new, obsolete, user list.	*/
+    clear_chanlist();		/* Remove all user references from the
+				   channel lists.			*/
+    for (i = 0; i < dcc_total; i++)
+      dcc[i].user = get_user_by_handle(ou, dcc[i].nick);
+    userlist = ou;		/* Revert to old user list.		*/
+    lastuser = NULL;		/* Reset last accessed user ptr.	*/
+    return;
+  }
+  putlog(LOG_BOTS, "*", "%s.", _("Userlist transfer complete; switched over"));
+
+  clear_chanlist();		/* Remove all user references from the
+				   channel lists.			*/
+  userlist = u;			/* Set new user list.			*/
+  lastuser = NULL;		/* Reset last accessed user ptr.	*/
+
+  /*
+   * Migrate:
+   *   - old channel flags over (unshared channels see)
+   *   - unshared (got_share == 0) user entries
+   *   - old bot flags and passwords
+   */
+  fr.match = (FR_CHAN | FR_BOT);
+  for (u = userlist; u; u = u->next) {
+    struct userrec *u2 = get_user_by_handle(ou, u->handle);
+
+    if ((dcc[j].u.bot->uff_flags & UFF_OVERRIDE) &&
+	u2 && (u2->flags & USER_BOT)) {
+      /* We knew this bot before, copy flags and the password back over. */
+      set_user(&USERENTRY_BOTFL, u, get_user(&USERENTRY_BOTFL, u2));
+      set_user(&USERENTRY_PASS, u, get_user(&USERENTRY_PASS, u2));
+    } else if ((dcc[j].u.bot->uff_flags & UFF_OVERRIDE) &&
+	       (u->flags & USER_BOT)) {
+      /* This bot was unknown to us, reset it's flags and password. */
+      set_user(&USERENTRY_BOTFL, u, NULL);
+      set_user(&USERENTRY_PASS, u, NULL);
+    } else if (u2 && !(u2->flags & (USER_BOT | USER_UNSHARED))) {
+      struct chanuserrec *cr, *cr_next, *cr_old = NULL;
+      struct user_entry *ue;
+
+      if (private_global) {
+	u->flags = u2->flags;
+	u->flags_udef = u2->flags_udef;
+      } else {
+	int pgbm = private_globals_bitmask();
+
+	u->flags = (u2->flags & pgbm) | (u->flags & ~pgbm);
+      }
+      for (cr = u2->chanrec; cr; cr = cr_next) {
+	struct chanset_t *chan = findchan_by_dname(cr->channel);
+
+	cr_next = cr->next;
+	if (chan) {
+	  int not_shared = 0;
+
+	  if (!channel_shared(chan))
+	    not_shared = 1;
+	  else {
+	    get_user_flagrec(dcc[j].user, &fr, chan->dname);
+	    if (!bot_chan(fr) && !bot_global(fr))
+	      not_shared = 1;
+	  }
+	  if (not_shared) {
+	    del_chanrec(u, cr->channel);
+	    if (cr_old)
+	      cr_old->next = cr_next;
+	    else
+	      u2->chanrec = cr_next;
+	    cr->next = u->chanrec;
+	    u->chanrec = cr;
+	  } else {
+	    /* Shared channel, still keep old laston time */
+	    for (cr_old = u->chanrec; cr_old; cr_old = cr_old->next)
+	      if (!irccmp(cr_old->channel, cr->channel)) {
+		cr_old->laston = cr->laston;
+		break;
+	      }
+	    cr_old = cr;
+	  }
+	}
+      }
+      /* Any unshared user entries need copying over */
+      for (ue = u2->entries; ue; ue = ue->next)
+	if (ue->type && !ue->type->got_share && ue->type->dup_user)
+	  ue->type->dup_user(u, u2, ue);
+    } else if (!u2 && private_global) {
+      u->flags = 0;
+      u->flags_udef = 0;
+    } else
+      u->flags = (u->flags & ~private_globals_bitmask());
+  }
+  clear_userlist(ou);
+  unlink(dcc[idx].u.xfer->filename);	/* Done with you!		*/
+  reaffirm_owners();			/* Make sure my owners are +n	*/
+  updatebot(-1, dcc[j].nick, '+', 0);
+}
+
+/* Begin the user transfer process.
+ */
+static void start_sending_users(int idx)
+{
+  struct userrec *u;
+  char share_file[1024], s1[64];
+  int i = 1;
+  struct chanuserrec *ch;
+  struct chanset_t *cst;
+
+  snprintf(share_file, sizeof share_file, ".share.%s.%lu", dcc[idx].nick,
+	       now);
+  if (dcc[idx].u.bot->uff_flags & UFF_OVERRIDE) {
+    debug1("NOTE: Sharing aggressively with %s, overriding its local bots.",
+	   dcc[idx].nick);
+    u = dup_userlist(2);		/* All entries		*/
+  } else
+    u = dup_userlist(0);		/* Only non-bots	*/
+  write_tmp_userfile(share_file, u, idx);
+  clear_userlist(u);
+
+  if (!uff_call_sending(idx, share_file)) {
+    unlink(share_file);
+    dprintf(idx, "s e %s\n", "uff parsing failed");
+    putlog(LOG_BOTS, "*", "uff parsing failed");
+    dcc[idx].status &= ~(STAT_SHARE | STAT_SENDING | STAT_AGGRESSIVE);
+    return;
+  }
+
+  if ((i = raw_dcc_send(share_file, "*users", "(users)",
+			share_file, getlocaladdr(dcc[idx].sock))) > 0) {
+    unlink(share_file);
+    dprintf(idx, "s e %s\n", _("Cant send userfile to you (internal error)"));
+    putlog(LOG_BOTS, "*", "%s -- can't send userfile",
+	   i == DCCSEND_FULL   ? "NO MORE DCC CONNECTIONS" :
+	   i == DCCSEND_NOSOCK ? "CAN'T OPEN A LISTENING SOCKET" :
+	   i == DCCSEND_BADFN  ? "BAD FILE" :
+	   i == DCCSEND_FEMPTY ? "EMPTY FILE" : "UNKNOWN REASON!");
+    dcc[idx].status &= ~(STAT_SHARE | STAT_SENDING | STAT_AGGRESSIVE);
+  } else {
+    updatebot(-1, dcc[idx].nick, '+', 0);
+    dcc[idx].status |= STAT_SENDING;
+    i = dcc_total - 1;
+    strcpy(dcc[i].host, dcc[idx].nick);		/* Store bot's nick */
+    dprintf(idx, "s us %s %d %lu\n", getlocaladdr(dcc[idx].sock),
+	    dcc[i].port, dcc[i].u.xfer->length);
+    /* Start up a tbuf to queue outgoing changes for this bot until the
+     * userlist is done transferring.
+     */
+    new_tbuf(dcc[idx].nick);
+    /* Immediately, queue bot hostmasks & addresses (jump-start) - if we
+     * don't override the leaf's local bots.
+     */
+    if (!(dcc[idx].u.bot->uff_flags & UFF_OVERRIDE)) {
+      for (u = userlist; u; u = u->next) {
+        if ((u->flags & USER_BOT) && !(u->flags & USER_UNSHARED)) {
+	  struct bot_addr *bi = get_user(&USERENTRY_BOTADDR, u);
+	  struct list_type *t;
+	  char s2[1024];
+
+	  /* Send hostmasks */
+	  for (t = get_user(&USERENTRY_HOSTS, u); t; t = t->next) {
+	    snprintf(s2, sizeof s2, "s +bh %s %s\n", u->handle, t->extra);
+	    q_tbuf(dcc[idx].nick, s2, NULL);
+	  }
+	  /* Send address */
+	  if (bi) {
+	    register char *tmp = str_escape(bi->address, ':', '\\');
+	    snprintf(s2, sizeof s2, "s c BOTADDR %s %s %d %d\n", u->handle,
+			 tmp, bi->telnet_port, bi->relay_port);
+	    free(tmp);
+	  }
+	  q_tbuf(dcc[idx].nick, s2, NULL);
+	  fr.match = FR_GLOBAL;
+	  fr.global = u->flags;
+
+	  fr.udef_global = u->flags_udef;
+	  build_flags(s1, &fr, NULL);
+	  snprintf(s2, sizeof s2, "s a %s %s\n", u->handle, s1);
+	  q_tbuf(dcc[idx].nick, s2, NULL);
+	  for (ch = u->chanrec; ch; ch = ch->next) {
+	    if ((ch->flags & ~BOT_SHARE) &&
+		((cst = findchan_by_dname(ch->channel)) &&
+		 channel_shared(cst))) {
+	      fr.match = (FR_CHAN | FR_BOT);
+	      get_user_flagrec(dcc[idx].user, &fr, ch->channel);
+	      if (bot_chan(fr) || bot_global(fr)) {
+	        fr.match = FR_CHAN;
+	        fr.chan = ch->flags & ~BOT_SHARE;
+	        fr.udef_chan = ch->flags_udef;
+	        build_flags(s1, &fr, NULL);
+	        snprintf(s2, sizeof s2, "s a %s %s %s\n", u->handle, s1,
+			     ch->channel);
+	        q_tbuf(dcc[idx].nick, s2, cst);
+	      }
+	    }
+	  }
+        }
+      }
+    }
+    q_tbuf(dcc[idx].nick, "s !\n", NULL);
+    /* Unlink the file. We don't really care whether this causes problems
+     *