diff -rdu a/include/h.h b/include/h.h
--- a/include/h.h	2016-02-05 21:36:45.000000000 +0300
+++ b/include/h.h	2016-02-08 23:22:58.000000000 +0300
@@ -264,6 +264,11 @@
 #define PREFIX_OP	0x4
 #define PREFIX_ADMIN	0x08
 #define PREFIX_OWNER	0x10
+extern void send_channel_join(aChannel *, aClient *, int notify);
+extern void sendto_channel_local_with_capability(aClient *from, unsigned int, unsigned int,
+	aChannel *chptr, char *pattern, ...) __attribute__((format(printf,5,6)));
+extern void sendto_channel_local_with_capability_butone(aClient *from, aClient *one, unsigned int, unsigned int,
+	aChannel *chptr, char *pattern, ...) __attribute__((format(printf,6,7)));
 extern void sendto_channelprefix_butone(aClient *one, aClient *from, aChannel *chptr,
     int prefix, char *pattern, ...) __attribute__((format(printf,5,6)));
 extern void sendto_channel_butone(aClient *, aClient *, aChannel *,
@@ -273,7 +278,7 @@
 extern void sendto_channel_butserv_butone(aChannel *chptr, aClient *from, aClient *one,
                                           char *pattern, ...) __attribute__((format(printf,4,5)));
 extern void sendto_common_channels(aClient *, char *, ...) __attribute__((format(printf,2,3)));
-extern void sendto_common_channels_local_butone(aClient *, int, char *, ...) __attribute__((format(printf,3,4)));
+extern void sendto_common_channels_local_butone(aClient *, unsigned int, unsigned int, char *, ...) __attribute__((format(printf,4,5)));
 extern void sendto_channel_butserv(aChannel *, aClient *, char *, ...) __attribute__((format(printf,3,4)));
 extern void sendto_match_servs(aChannel *, aClient *, char *, ...) __attribute__((format(printf,3,4)));
 extern void sendto_match_butone(aClient *, aClient *, char *, int,
diff -rdu a/include/numeric.h b/include/numeric.h
--- a/include/numeric.h	2015-12-18 19:54:11.000000000 +0300
+++ b/include/numeric.h	2016-03-10 20:14:33.000000000 +0300
@@ -61,6 +61,8 @@
 #define ERR_NOTOPLEVEL       413
 #define ERR_WILDTOPLEVEL     414
 
+#define ERR_TOOMANYMATCHES   416
+
 #define ERR_UNKNOWNCOMMAND   421
 #define	ERR_NOMOTD           422
 #define	ERR_NOADMININFO      423
diff -rdu a/include/proto.h b/include/proto.h
--- a/include/proto.h	2015-12-18 19:54:11.000000000 +0300
+++ b/include/proto.h	2016-02-08 22:48:49.000000000 +0300
@@ -40,6 +40,7 @@
 /* send.c */
 extern void sendto_one(aClient *, char *, ...) __attribute__((format(printf,2,3)));
 extern void sendto_chanops_butone(aClient *one, aChannel *chptr, char *pattern, ...) __attribute__((format(printf,3,4)));
+extern void sendto_chanops_with_capability_butone(aClient *one, aChannel *chptr, unsigned int, unsigned int, char *pattern, ...) __attribute__((format(printf,5,6)));
 extern void sendto_realops(char *pattern, ...) __attribute__((format(printf,1,2)));
 extern void sendto_channel_ntadmins(aClient *from, aChannel *chptr, char *pattern, ...) __attribute__((format(printf,3,4))); 
 
diff -rdu a/include/struct.h b/include/struct.h
--- a/include/struct.h	2015-12-18 19:54:11.000000000 +0300
+++ b/include/struct.h	2016-03-10 20:33:28.000000000 +0300
@@ -305,7 +305,7 @@
 #define FLAGS_PING       0x80000
 #define FLAGS_EAUTH      0x100000
 #define FLAGS_NETINFO    0x200000
-//0x400000 was hybnotice
+#define FLAGS_MARK	 0x400000
 #define FLAGS_QUARANTINE 0x800000
 //0x1000000 unused (was ziplinks)
 #define FLAGS_DCCNOTICE  0x2000000 /* Has the user seen a notice on how to use DCCALLOW already? */
@@ -332,7 +332,8 @@
 #define	FLAGS_ID	(FLAGS_DOID|FLAGS_GOTID)
 
 #define PROTO_NOQUIT	0x0001	/* Negotiated NOQUIT protocol */
-#define PROTO_SJOIN		0x0004	/* Negotiated SJOIN protocol */
+#define PROTO_CHGHOST	0x0002	/* client supports chghost */
+#define PROTO_SJOIN	0x0004	/* Negotiated SJOIN protocol */
 #define PROTO_NICKv2	0x0008	/* Negotiated NICKv2 protocol */
 #define PROTO_SJOIN2	0x0010	/* Negotiated SJOIN2 protocol */
 #define PROTO_UMODE2	0x0020	/* Negotiated UMODE2 protocol */
@@ -345,7 +346,7 @@
 #define PROTO_TKLEXT	0x1000	/* TKL extension: 10 parameters instead of 8 (3.2RC2) */
 #define PROTO_NICKIP	0x2000  /* Send IP addresses in the NICK command */
 #define PROTO_NAMESX	0x4000  /* Send all rights in NAMES output */
-#define PROTO_CLK		0x8000	/* Send cloaked host in the NICK command (regardless of +x/-x) */
+#define PROTO_CLK	0x8000	/* Send cloaked host in the NICK command (regardless of +x/-x) */
 #define PROTO_UHNAMES	0x10000  /* Send n!u@h in NAMES */
 #define PROTO_CLICAP	0x20000  /* client capability negotiation in process */
 #define PROTO_STARTTLS	0x40000	 /* client supports STARTTLS */
@@ -354,6 +355,8 @@
 #define PROTO_ACCOUNT_NOTIFY	0x200000	/* client supports account-notify */
 #define PROTO_MLOCK		0x400000	/* server supports MLOCK */
 #define PROTO_EXTSWHOIS 0x800000	/* extended SWHOIS support */
+#define PROTO_EXTENDED_JOIN	0x1000000	/* client supports extended-join */
+
 
 /*
  * flags macros.
@@ -437,9 +440,16 @@
 
 #define SetIPV6(x)			do { x->flags |= FLAGS_IPV6; } while(0)
 #define IsIPV6(x)			((x)->flags & FLAGS_IPV6)
+
+#define SetMark(x)           ((x)->flags |= FLAGS_MARK)
+#define ClearMark(x)         ((x)->flags &= ~FLAGS_MARK)
+#define IsMarked(x)          ((x)->flags & FLAGS_MARK)
+
 /*
  * ProtoCtl options
  */
+#define NotCapable(x,cap)	(((x)->local->proto & (cap)) == 0)
+#define IsCapable(x,cap)	(((x)->local->proto & (cap)) == cap)
 #ifndef DEBUGMODE
 #define CHECKPROTO(x,y)	(((x)->local->proto & y) == y)
 #else
diff -rdu a/src/channel.c b/src/channel.c
--- a/src/channel.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/channel.c	2016-02-08 23:29:57.000000000 +0300
@@ -1297,9 +1297,11 @@
 		if ((i != 0) &&
 		    !(tmp->flags & (CHFL_CHANOWNER|CHFL_CHANPROT|CHFL_CHANOP|CHFL_HALFOP|CHFL_VOICE)))
 		{
-			sendto_chanops_butone(sptr, chptr, ":%s!%s@%s PART %s :%s", sptr->name, sptr->user->username, GetHost(sptr), chptr->chname, comment);
-		} else
-			sendto_channel_butserv_butone(chptr, sptr, sptr, ":%s PART %s :%s", sptr->name, chptr->chname, comment);
+			sendto_chanops_with_capability_butone(sptr, chptr, 0, PROTO_CHGHOST, ":%s!%s@%s PART %s :%s", sptr->name, sptr->user->username, GetHost(sptr),
+				chptr->chname, comment);
+		}
+		else
+			sendto_channel_local_with_capability_butone(sptr, sptr, 0, PROTO_CHGHOST, chptr, ":%s PART %s :%s", sptr->name, chptr->chname, comment);
 	}
 }
 
@@ -1338,9 +1340,18 @@
 		if ((k != 0) &&
 		    !(flags & (CHFL_CHANOWNER|CHFL_CHANPROT|CHFL_CHANOP|CHFL_HALFOP|CHFL_VOICE)))
 		{
-			sendto_chanops_butone(sptr, chptr, ":%s!%s@%s JOIN :%s", sptr->name, sptr->user->username, GetHost(sptr), chptr->chname);
-		} else
-			sendto_channel_butserv_butone(chptr, sptr, sptr, ":%s JOIN :%s", sptr->name, chptr->chname);
+			sendto_chanops_with_capability_butone(sptr, chptr, 0, PROTO_EXTENDED_JOIN|PROTO_CHGHOST, ":%s!%s@%s JOIN :%s",
+				sptr->name, sptr->user->username, GetHost(sptr), chptr->chname);
+			sendto_chanops_with_capability_butone(sptr, chptr, PROTO_EXTENDED_JOIN, PROTO_CHGHOST, ":%s!%s@%s JOIN %s %s :%s",
+				sptr->name, chptr->chname, (isdigit(*sptr->user->svid)) ? "*" : sptr->user->svid, sptr->info);
+		}
+		else
+		{
+			sendto_channel_local_with_capability_butone(sptr, sptr, 0, PROTO_EXTENDED_JOIN|PROTO_CHGHOST, chptr,
+				":%s JOIN :%s", sptr->name, chptr->chname);
+			sendto_channel_local_with_capability_butone(sptr, sptr, PROTO_EXTENDED_JOIN, PROTO_CHGHOST, chptr,
+				":%s JOIN %s %s :%s", sptr->name, chptr->chname, (isdigit(*sptr->user->svid)) ? "*" : sptr->user->svid, sptr->info);
+		}
 
 		/* Set the modes (if any) */
 		if (flags)
@@ -1367,13 +1378,17 @@
 					if (i < n - 1)
 						strcat(parabuf, " ");
 				}
-				sendto_channel_butserv_butone(chptr, &me, sptr, ":%s MODE %s +%s %s",
+				sendto_channel_local_with_capability_butone(&me, sptr, 0, PROTO_CHGHOST, chptr, ":%s MODE %s +%s %s",
 					me.name, chptr->chname, flagbuf, parabuf);
 			}
 		}
 		
 		tmp->flags &= ~CHFL_REJOINING; /* esthetics.. ;) */
 	}
+
+	/* Resend away message to away-notify enabled clients. */
+	if (sptr->user->away)
+		sendto_common_channels_local_butone(sptr, PROTO_AWAY_NOTIFY, PROTO_CHGHOST, ":%s AWAY :%s", sptr->name, sptr->user->away);
 }
 
 /* set_channel_mlock()
@@ -1532,3 +1547,48 @@
 		return 1;
 	}
 }
+
+void send_channel_join(aChannel *chptr, aClient *client_p, int notify)
+{
+	if (!IsClient(client_p))
+		return;
+
+	if (notify != 0)
+	{
+		if (MyClient(client_p))
+		{
+			if (IsCapable(client_p, PROTO_EXTENDED_JOIN))
+				sendto_one(client_p, ":%s!%s@%s JOIN %s %s :%s", client_p->name,
+					client_p->user->username, GetHost(client_p), chptr->chname,
+					(isdigit(*client_p->user->svid)) ? "*" : client_p->user->svid,
+					client_p->info);
+			else
+				sendto_one(client_p, ":%s!%s@%s JOIN :%s", client_p->name,
+					client_p->user->username, GetHost(client_p), chptr->chname);
+		}
+		sendto_chanops_with_capability_butone(NULL, chptr, 0, PROTO_EXTENDED_JOIN, ":%s!%s@%s JOIN :%s",
+			client_p->name, client_p->user->username, GetHost(client_p), chptr->chname);
+		sendto_chanops_with_capability_butone(NULL, chptr, PROTO_EXTENDED_JOIN, 0, ":%s!%s@%s JOIN %s %s :%s",
+			client_p->name, client_p->user->username, GetHost(client_p), chptr->chname,
+			(isdigit(*client_p->user->svid)) ? "*" : client_p->user->svid, client_p->info);
+	}
+	else
+	{
+		sendto_channel_local_with_capability(client_p, 0, PROTO_EXTENDED_JOIN, chptr, ":%s JOIN :%s",
+			client_p->name, chptr->chname);
+		sendto_channel_local_with_capability(client_p, PROTO_EXTENDED_JOIN, 0, chptr, ":%s JOIN %s %s :%s",
+			client_p->name, chptr->chname, (isdigit(*client_p->user->svid)) ? "*" : client_p->user->svid,
+			client_p->info);
+	}
+
+	/* Send away message to away-notify enabled clients. */
+	if (client_p->user->away)
+	{
+		if (notify != 0)
+			sendto_chanops_with_capability_butone(NULL, chptr, PROTO_AWAY_NOTIFY, 0, ":%s!%s@%s AWAY :%s",
+				client_p->name, client_p->user->username, GetHost(client_p), client_p->user->away);
+		else
+			sendto_channel_local_with_capability_butone(client_p, client_p, PROTO_AWAY_NOTIFY, 0, chptr, ":%s AWAY :%s",
+				client_p->name, client_p->user->away);
+	}
+}
diff -rdu a/src/modules/chanmodes/delayjoin.c b/src/modules/chanmodes/delayjoin.c
--- a/src/modules/chanmodes/delayjoin.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/chanmodes/delayjoin.c	2016-02-09 00:18:42.000000000 +0300
@@ -203,8 +203,14 @@
 	for (i = chptr->members; i; i = i->next)
 	{
 		if (!is_skochanop(i->cptr,chptr) && i->cptr != sptr)
-			sendto_one(i->cptr,
-			    ":%s!%s@%s JOIN :%s", sptr->name, sptr->user->username, GetHost(sptr), chptr->chname);
+			if (IsCapable(i->cptr, PROTO_EXTENDED_JOIN))
+				sendto_one(i->cptr, ":%s!%s@%s JOIN %s %s :%s", sptr->name,
+					sptr->user->username, GetHost(sptr), chptr->chname,
+					(isdigit(*sptr->user->svid)) ? "*" : sptr->user->svid,
+					sptr->info);
+			else
+				sendto_one(i->cptr,
+					":%s!%s@%s JOIN :%s", sptr->name, sptr->user->username, GetHost(sptr), chptr->chname);
 	}
 }
 
@@ -316,7 +322,13 @@
 					if (i->cptr == user)
 						continue;
 					if (moded_user_invisible(i->cptr,chptr))
-						sendto_one(user,":%s!%s@%s JOIN :%s", i->cptr->name, i->cptr->user->username, GetHost(i->cptr), chptr->chname);
+						if (IsCapable(user, PROTO_EXTENDED_JOIN))
+							sendto_one(user,":%s!%s@%s JOIN %s %s :%s", i->cptr->name,
+								i->cptr->user->username, GetHost(i->cptr), chptr->chname,
+								(isdigit(*i->cptr->user->svid)) ? "*" : i->cptr->user->svid,
+								i->cptr->info);
+						else
+							sendto_one(user,":%s!%s@%s JOIN :%s", i->cptr->name, i->cptr->user->username, GetHost(i->cptr), chptr->chname);
 				}
 
 			}
diff -rdu a/src/modules/m_away.c b/src/modules/m_away.c
--- a/src/modules/m_away.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/m_away.c	2016-02-08 23:41:14.000000000 +0300
@@ -107,7 +107,7 @@
 	                sendto_server(cptr, 0, 0, ":%s AWAY", sptr->name);
 	                hash_check_watch(cptr, RPL_NOTAWAY);
 
-			sendto_common_channels_local_butone(sptr, PROTO_AWAY_NOTIFY, ":%s AWAY", sptr->name);
+			sendto_common_channels_local_butone(sptr, PROTO_AWAY_NOTIFY, 0, ":%s AWAY", sptr->name);
                 }
                 /* hope this works XX */
                 if (MyConnect(sptr))
@@ -161,7 +161,7 @@
                 sendto_one(sptr, rpl_str(RPL_NOWAWAY), me.name, sptr->name);
 
 	hash_check_watch(cptr, wasaway ? RPL_REAWAY : RPL_GONEAWAY);
-	sendto_common_channels_local_butone(sptr, PROTO_AWAY_NOTIFY, ":%s AWAY :%s", sptr->name, away);
+	sendto_common_channels_local_butone(sptr, PROTO_AWAY_NOTIFY, 0, ":%s AWAY :%s", sptr->name, away);
 
 	RunHook2(HOOKTYPE_AWAY, sptr, away);
         return 0;
diff -rdu a/src/modules/m_cap.c b/src/modules/m_cap.c
--- a/src/modules/m_cap.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/m_cap.c	2016-02-08 22:39:46.000000000 +0300
@@ -55,6 +55,16 @@
 	ClientCapabilityAdd(modinfo->handle, &c);
 
 	memset(&c, 0, sizeof(c));
+	c.name = "chghost";
+	c.cap = PROTO_CHGHOST;
+	ClientCapabilityAdd(modinfo->handle, &c);
+
+	memset(&c, 0, sizeof(c));
+	c.name = "extended-join";
+	c.cap = PROTO_EXTENDED_JOIN;
+	ClientCapabilityAdd(modinfo->handle, &c);
+
+	memset(&c, 0, sizeof(c));
 	c.name = "multi-prefix";
 	c.cap = PROTO_NAMESX;
 	ClientCapabilityAdd(modinfo->handle, &c);
diff -rdu a/src/modules/m_chghost.c b/src/modules/m_chghost.c
--- a/src/modules/m_chghost.c	2016-02-05 21:36:45.000000000 +0300
+++ b/src/modules/m_chghost.c	2016-02-08 23:42:01.000000000 +0300
@@ -160,7 +160,7 @@
 				/* join sent later when the host has been changed */
 				break;
 		}
-				
+		sendto_common_channels_local_butone(acptr, PROTO_CHGHOST, 0, ":%s CHGHOST %s %s", acptr->name, acptr->user->username, parv[2]);
 		if (!IsULine(sptr))
 		{
 			sendto_snomask(SNO_EYES,
diff -rdu a/src/modules/m_chgident.c b/src/modules/m_chgident.c
--- a/src/modules/m_chgident.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/m_chgident.c	2016-02-08 23:42:13.000000000 +0300
@@ -163,6 +163,7 @@
 				/* join sent later when the ident has been changed */
 				break;
 		}
+		sendto_common_channels_local_butone(acptr, PROTO_CHGHOST, 0, ":%s CHGHOST %s %s", acptr->name, parv[2], GetHost(acptr));
 		if (!IsULine(sptr))
 		{
 			sendto_snomask(SNO_EYES,
diff -rdu a/src/modules/m_join.c b/src/modules/m_join.c
--- a/src/modules/m_join.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/m_join.c	2016-02-08 23:30:57.000000000 +0300
@@ -211,19 +211,7 @@
 	/*
 	   ** notify all other users on the new channel
 	 */
-	if (i != 0)
-	{
-		if (MyClient(sptr))
-			sendto_one(sptr, ":%s!%s@%s JOIN :%s",
-			    sptr->name, sptr->user->username,
-			    GetHost(sptr), chptr->chname);
-		sendto_chanops_butone(NULL, chptr, ":%s!%s@%s JOIN :%s",
-		    sptr->name, sptr->user->username,
-		    GetHost(sptr), chptr->chname);
-	}
-	else
-		sendto_channel_butserv(chptr, sptr,
-		    ":%s JOIN :%s", sptr->name, chptr->chname);
+	send_channel_join(chptr, sptr, i);
 	
 	sendto_server(cptr, 0, PROTO_SJ3, ":%s JOIN :%s", sptr->name, chptr->chname);
 
diff -rdu a/src/modules/m_mode.c b/src/modules/m_mode.c
--- a/src/modules/m_mode.c	2016-02-05 21:36:45.000000000 +0300
+++ b/src/modules/m_mode.c	2016-02-08 23:42:35.000000000 +0300
@@ -1787,6 +1787,12 @@
 		if (!dontspread)
 			sendto_server(cptr, PROTO_VHP, 0, ":%s SETHOST :%s",
 				sptr->name, sptr->user->virthost);
+
+		sptr->umodes &= ~UMODE_HIDE;
+		sendto_common_channels_local_butone(sptr, PROTO_CHGHOST, 0, ":%s CHGHOST %s %s", sptr->name,
+			sptr->user->username, sptr->user->virthost);
+		sptr->umodes |= UMODE_HIDE;
+
 		if (UHOST_ALLOWED == UHALLOW_REJOIN)
 		{
 			/* LOL, this is ugly ;) */
@@ -1804,6 +1810,11 @@
 	/* -x */
 	if (!IsHidden(sptr) && (setflags & UMODE_HIDE))
 	{
+		sptr->umodes |= UMODE_HIDE;
+		sendto_common_channels_local_butone(sptr, PROTO_CHGHOST, 0, ":%s CHGHOST %s %s", sptr->name,
+			sptr->user->username, sptr->user->realhost);
+		sptr->umodes &= ~UMODE_HIDE;
+
 		if (UHOST_ALLOWED == UHALLOW_REJOIN)
 		{
 			/* LOL, this is ugly ;) */
diff -rdu a/src/modules/m_sethost.c b/src/modules/m_sethost.c
--- a/src/modules/m_sethost.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/m_sethost.c	2016-02-08 23:43:09.000000000 +0300
@@ -172,6 +172,7 @@
 				break;
 		}
 
+		sendto_common_channels_local_butone(sptr, PROTO_CHGHOST, 0, ":%s CHGHOST %s %s", sptr->name, sptr->user->username, vhost);
 		/* hide it */
 		sptr->umodes |= UMODE_HIDE;
 		sptr->umodes |= UMODE_SETHOST;
diff -rdu a/src/modules/m_setident.c b/src/modules/m_setident.c
--- a/src/modules/m_setident.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/m_setident.c	2016-02-08 23:43:22.000000000 +0300
@@ -198,6 +198,7 @@
 				break;
 		}
 
+		sendto_common_channels_local_butone(sptr, PROTO_CHGHOST, 0, ":%s CHGHOST %s %s", sptr->name, vident, GetHost(sptr));
 		/* get it in */
 		ircsnprintf(sptr->user->username, sizeof(sptr->user->username), "%s", vident);
 		/* spread it out */
diff -rdu a/src/modules/m_sjoin.c b/src/modules/m_sjoin.c
--- a/src/modules/m_sjoin.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/m_sjoin.c	2016-02-08 23:39:03.000000000 +0300
@@ -507,12 +507,44 @@
 				if (k != 0)
 				{
 					if (modeflags & (CHFL_CHANOP|CHFL_CHANPROT|CHFL_CHANOWNER|CHFL_HALFOP|CHFL_VOICE))
-						sendto_channel_butserv(chptr, acptr, ":%s JOIN :%s", nick, chptr->chname);
+					{
+						sendto_channel_local_with_capability(acptr, 0, PROTO_EXTENDED_JOIN, chptr, ":%s JOIN :%s",
+							nick, chptr->chname);
+						sendto_channel_local_with_capability(acptr, PROTO_EXTENDED_JOIN, 0, chptr, ":%s JOIN %s %s :%s",
+							nick, chptr->chname, (isdigit(*acptr->user->svid)) ? "*" : acptr->user->svid, acptr->info);
+					}
 					else
-						sendto_chanops_butone(NULL, chptr, ":%s!%s@%s JOIN :%s",
+					{
+						sendto_chanops_with_capability_butone(NULL, chptr, 0, PROTO_EXTENDED_JOIN, ":%s!%s@%s JOIN :%s",
 							acptr->name, acptr->user->username, GetHost(acptr), chptr->chname);
-				} else
-					sendto_channel_butserv(chptr, acptr, ":%s JOIN :%s", nick, chptr->chname);
+						sendto_chanops_with_capability_butone(NULL, chptr, PROTO_EXTENDED_JOIN, 0, ":%s!%s@%s JOIN %s %s :%s",
+							acptr->name, acptr->user->username, GetHost(acptr), chptr->chname,
+							(isdigit(*acptr->user->svid)) ? "*" : acptr->user->svid, acptr->info);
+					}
+				}
+				else
+				{
+					sendto_channel_local_with_capability(acptr, 0, PROTO_EXTENDED_JOIN, chptr, ":%s JOIN :%s",
+						nick, chptr->chname);
+					sendto_channel_local_with_capability(acptr, PROTO_EXTENDED_JOIN, 0, chptr, ":%s JOIN %s %s :%s",
+						nick, chptr->chname, (isdigit(*acptr->user->svid)) ? "*" : acptr->user->svid, acptr->info);
+				}
+
+				if (acptr->user->away)
+				{
+					if (k != 0)
+					{
+						if (modeflags & (CHFL_CHANOP|CHFL_CHANPROT|CHFL_CHANOWNER|CHFL_HALFOP|CHFL_VOICE))
+							sendto_channel_local_with_capability_butone(acptr, acptr, PROTO_AWAY_NOTIFY, 0, chptr, ":%s AWAY :%s",
+								nick, acptr->user->away);
+						else
+							sendto_chanops_with_capability_butone(NULL, chptr, PROTO_AWAY_NOTIFY, 0, ":%s!%s@%s AWAY :%s",
+								acptr->name, acptr->user->username, GetHost(acptr), acptr->user->away);
+					}
+					else
+						sendto_channel_local_with_capability_butone(acptr, acptr, PROTO_AWAY_NOTIFY, 0, chptr, ":%s AWAY :%s",
+							nick, acptr->user->away);
+				}
 			}
 			sendto_server(cptr, 0, PROTO_SJOIN, ":%s JOIN %s",
 			    nick, chptr->chname);
diff -rdu a/src/modules/m_svsmode.c b/src/modules/m_svsmode.c
--- a/src/modules/m_svsmode.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/m_svsmode.c	2016-02-08 23:42:47.000000000 +0300
@@ -482,7 +482,7 @@
 				if (parv[3])
 				{
 					strlcpy(acptr->user->svid, parv[3], sizeof(acptr->user->svid));
-					sendto_common_channels_local_butone(acptr, PROTO_ACCOUNT_NOTIFY, ":%s ACCOUNT %s",
+					sendto_common_channels_local_butone(acptr, PROTO_ACCOUNT_NOTIFY, 0, ":%s ACCOUNT %s",
 									    acptr->name,
 									    !isdigit(*acptr->user->svid) ? acptr->user->svid : "*");
 				}
diff -rdu a/src/modules/m_vhost.c b/src/modules/m_vhost.c
--- a/src/modules/m_vhost.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/m_vhost.c	2016-02-08 23:41:33.000000000 +0300
@@ -132,6 +132,9 @@
 				/* join sent later when the host has been changed */
 				break;
 		}
+		sendto_common_channels_local_butone(sptr, PROTO_CHGHOST, 0, ":%s CHGHOST %s %s", sptr->name,
+			vhost->virtuser ? vhost->virtuser : sptr->user->username, vhost->virthost);
+
 		safestrdup(sptr->user->virthost, vhost->virthost);
 		if (vhost->virtuser)
 		{
diff -rdu a/src/modules/m_who.c b/src/modules/m_who.c
--- a/src/modules/m_who.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/modules/m_who.c	2016-03-10 22:48:33.000000000 +0300
@@ -1,30 +1,8 @@
-/*   m_who.c - Because s_user.c was just crazy.
- *   Copyright (C) 1990 Jarkko Oikarinen and
- *                      University of Oulu, Computing Center
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free softwmare; 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+/*   m_who.c / WHOX.
+ *   based on code from charybdis and ircu.
+ *   - 2016 i <i@servx.ru>
  */
 
-/* rewritten 06/02 by larne, the old one was unreadable. */
-/* changed indentation + some parts rewritten by Syzop. */
-
-/* $Id$ */
-
 #include "config.h"
 #include "struct.h"
 #include "common.h"
@@ -66,6 +44,7 @@
 {
 	CommandAdd(modinfo->handle, MSG_WHO, m_who, MAXPARA, M_USER);
 	MARK_AS_OFFICIAL_MODULE(modinfo);
+	IsupportAdd(NULL, "WHOX", NULL);
 	return MOD_SUCCESS;
 }
 
@@ -79,821 +58,734 @@
 /* Called when module is unloaded */
 MOD_UNLOAD(m_who)
 {
+	Isupport *hunted = IsupportFind("WHOX");
+
+	if (hunted)
+		IsupportDel(hunted);
+	
 	return MOD_SUCCESS;
 }
 
-static void do_channel_who(aClient *sptr, aChannel *channel, char *mask);
-static void make_who_status(aClient *, aClient *, aChannel *, Member *, char *, int);
-static void do_other_who(aClient *sptr, char *mask);
-static void send_who_reply(aClient *, aClient *, char *, char *, char *);
-static char *first_visible_channel(aClient *, aClient *, int *);
-static int parse_who_options(aClient *, int, char**);
-static void who_sendhelp(aClient *);
-static int has_common_channels(aClient *, aClient *);
+#define EmptyString(x) ((x) == NULL || *(x) == '\0')
+#define CheckEmpty(x) EmptyString(x) ? "" : x
+#define HasField(x, y) ((x)->fields & (y))
+#define IsMatch(x, y) ((x)->matchsel & (y))
 
-#define WF_OPERONLY  0x01 /**< only show opers */
-#define WF_ONCHANNEL 0x02 /**< we're on the channel we're /who'ing */
-#define WF_WILDCARD  0x04 /**< a wildcard /who */
-#define WF_REALHOST  0x08 /**< want real hostnames */
-#define WF_IP	     0x10 /**< want IP addresses */
+#define FIELD_CHANNEL    0x0001
+#define FIELD_HOP        0x0002
+#define FIELD_FLAGS      0x0004
+#define FIELD_HOST       0x0008
+#define FIELD_IP         0x0010
+#define FIELD_IDLE       0x0020
+#define FIELD_NICK       0x0040
+#define FIELD_INFO       0x0080
+#define FIELD_SERVER     0x0100
+#define FIELD_QUERYTYPE	 0x0200 /* cookie for client */
+#define FIELD_USER       0x0400
+#define FIELD_ACCOUNT    0x0800
+#define FIELD_OPLEVEL    0x1000 /* meaningless and stupid, but whatever */
+#define FIELD_REALHOST	 0x2000
+#define FIELD_MODES	 0x4000
 
-static int who_flags;
+#define WMATCH_NICK	 0x0001
+#define WMATCH_USER	 0x0002
+#define WMATCH_OPER	 0x0004
+#define WMATCH_HOST	 0x0008
+#define WMATCH_INFO	 0x0010
+#define WMATCH_SERVER	 0x0020
+#define WMATCH_ACCOUNT	 0x0040
+#define WMATCH_IP	 0x0080
+#define WMATCH_MODES	 0x0100
 
-#define WHO_CANTSEE 0x01 /**< set if we can't see them */
-#define WHO_CANSEE  0x02 /**< set if we can */
-#define WHO_OPERSEE 0x04 /**< set if we only saw them because we're an oper */
+#define RPL_WHOSPCRPL	 354
 
-#define FVC_HIDDEN  0x01
+#define WHO_ADD 1
+#define WHO_DEL 2
 
-#define WHO_WANT 1
-#define WHO_DONTWANT 2
-#define WHO_DONTCARE 0
+struct who_format
+{
+	int fields;
+	int matchsel;
+	int umodes;
+	int noumodes;
+	const char *querytype;
+};
 
-struct {
-	int want_away;
-	int want_channel;
-	char *channel; /**< if they want one */
-	int want_gecos;
-	char *gecos;
-	int want_server;
-	char *server;
-	int want_host;
-	char *host;
-	int want_nick;
-	char *nick;
-	int want_user;
-	char *user;
-	int want_ip;
-	char *ip;
-	int want_port;
-	int port;
-	int want_umode;
-	int umodes_dontwant;
-	int umodes_want;
-	int common_channels_only;
-} wfl;
+static void who_global(aClient *source_p, char *mask, int operspy, struct who_format *fmt);
+static void do_who(aClient *source_p, aClient *target_p, aChannel *chptr, struct who_format *fmt);
+static void do_who_on_channel(aClient *source_p, aChannel *chptr,
+        int member, int operspy, struct who_format *fmt);
+
+/*
+** m_who
+**      parv[1] = nickname mask list
+**      parv[2] = additional selection flag and format options
+*/
 
-/** The /who command: retrieves information from users. */
 CMD_FUNC(m_who)
 {
-aChannel *target_channel;
-char *mask = parv[1];
-char star[] = "*";
-int i = 0;
+	static time_t last_used = 0;
+	char *mask;
+	char ch; /* Scratch char register */
+	char *p; /* Scratch char pointer */
+	int member;
+	int operspy = 0;
+	struct who_format fmt;
+	const char *s;
+	char maskcopy[BUFSIZE];
+	Membership *lp;
+	aClient *target_p;
 
-	who_flags = 0;
-	memset(&wfl, 0, sizeof(wfl));
+	if ((parc < 2) && (IsPerson(sptr)))
+	{
+		sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS),
+			me.name, sptr->name, "WHO");
+		return 0;
+	}
 
-	if (parc > 1)
+	fmt.fields = 0;
+	fmt.matchsel = 0;
+	fmt.umodes = 0;
+	fmt.noumodes = 0;
+	fmt.querytype = NULL;
+
+	/* Evaluate the flags now, we consider the second parameter
+	 * as "matchFlags%fieldsToInclude,querytype" */
+
+	if ((parc > 2) && parv[2] && *parv[2])
 	{
-		i = parse_who_options(sptr, parc - 1, parv + 1);
-		if (i < 0)
+		p = parv[2];
+		while (((ch = *(p++))) && (ch != '%') && (ch != ','))
+			switch (ch)
+			{
+				case 'o': fmt.matchsel |= WMATCH_OPER; continue;
+				case 'n': fmt.matchsel |= WMATCH_NICK; continue;
+				case 'u': fmt.matchsel |= WMATCH_USER; continue;
+				case 'h': fmt.matchsel |= WMATCH_HOST; continue;
+				case 'i': fmt.matchsel |= WMATCH_IP; continue;
+				case 'r': fmt.matchsel |= WMATCH_INFO; continue;
+				case 's': fmt.matchsel |= WMATCH_SERVER; continue;
+				case 'a': fmt.matchsel |= WMATCH_ACCOUNT; continue;
+				case 'm': fmt.matchsel |= WMATCH_MODES; continue;
+			}
+	}
+
+	if ((parc > 2) && (s = strchr(parv[2], '%')) != NULL)
+	{
+		s++;
+		for (; *s != '\0'; s++)
 		{
-			sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask);
-			return 0;
+			switch (*s)
+			{
+				case 'c': fmt.fields |= FIELD_CHANNEL; break;
+				case 'd': fmt.fields |= FIELD_HOP; break;
+				case 'f': fmt.fields |= FIELD_FLAGS; break;
+				case 'h': fmt.fields |= FIELD_HOST; break;
+				case 'H': fmt.fields |= FIELD_REALHOST; break;
+				case 'i': fmt.fields |= FIELD_IP; break;
+				case 'l': fmt.fields |= FIELD_IDLE; break;
+				case 'n': fmt.fields |= FIELD_NICK; break;
+				case 'r': fmt.fields |= FIELD_INFO; break;
+				case 's': fmt.fields |= FIELD_SERVER; break;
+				case 't': fmt.fields |= FIELD_QUERYTYPE; break;
+				case 'u': fmt.fields |= FIELD_USER; break;
+				case 'a': fmt.fields |= FIELD_ACCOUNT; break;
+				case 'm': fmt.fields |= FIELD_MODES; break;
+				case 'o': fmt.fields |= FIELD_OPLEVEL; break;
+				case ',':
+					s++;
+					fmt.querytype = s;
+					s += strlen(s);
+					s--;
+					break;
+			}
 		}
+		if (EmptyString(fmt.querytype) || (strlen(fmt.querytype) > 3))
+			fmt.querytype = "0";
 	}
 
-	if (parc-i < 2 || strcmp(parv[1 + i], "0") == 0)
-		mask = star;
-	else
-		mask = parv[1 + i];
-
-	if (!i && parc > 2 && *parv[2] == 'o')
-		who_flags |= WF_OPERONLY;
+	strlcpy(maskcopy, parv[1], sizeof maskcopy);
+	mask = maskcopy;
 
 	collapse(mask);
 
-	if (*mask == '\0')
+	/* '/who *' */
+	if((*(mask + 1) == '\0') && (*mask == '*'))
 	{
-		/* no mask given */
+		if(sptr->user == NULL)
+			return 0;
+
+		if ((lp = sptr->user->channel) != NULL)
+			do_who_on_channel(sptr, lp->chptr, 1, 0, &fmt);
+
 		sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, "*");
 		return 0;
 	}
 
-	if ((target_channel = find_channel(mask, NULL)) != NULL)
+	if((ValidatePermissionsForPath("override:see:who:secret",sptr,NULL,NULL,NULL) ||
+		ValidatePermissionsForPath("override:see:whois",sptr,NULL,NULL,NULL)) && (*mask == '!'))
 	{
-		do_channel_who(sptr, target_channel, mask);
-		sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask);
-		return 0;
-	}
+		mask++;
+		operspy = 1;
 
-	if (wfl.channel && wfl.want_channel == WHO_WANT && 
-	    (target_channel = find_channel(wfl.channel, NULL)) != NULL)
-	{
-		do_channel_who(sptr, target_channel, mask);
-		sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask);
-		return 0;
-	}
-	else
-	{
-		do_other_who(sptr, mask);
-		sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask);
-		return 0;
+		if(EmptyString(mask))
+		{
+			sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, parv[1]);
+			return 0;
+		}
 	}
 
-	return 0;
-}
+        if (fmt.matchsel & WMATCH_MODES)
+        {
+        	char *s = mask;
+		int *umodes;
+		int what = WHO_ADD;
 
-static void who_sendhelp(aClient *sptr)
-{
-  char *who_help[] = {
-    "/WHO [+|-][achmnsuM] [args]",
-    "Flags are specified like channel modes, the flags chmnsu all have arguments",
-    "Flags are set to a positive check by +, a negative check by -",
-    "The flags work as follows:",
-    "Flag a: user is away",
-    "Flag c <channel>:       user is on <channel>,",
-    "                        no wildcards accepted",
-    "Flag h <host>:          user has string <host> in their hostname,",
-    "                        wildcards accepted",
-    "Flag m <usermodes>:     user has <usermodes> set, only",
-    "                        O/o/C/A/a/N/B are allowed",
-    "Flag n <nick>:          user has string <nick> in their nickname,",
-    "                        wildcards accepted",
-    "Flag s <server>:        user is on server <server>,",
-    "                        wildcards not accepted",
-    "Flag u <user>:          user has string <user> in their username,",
-    "                        wildcards accepted",
-    "Behavior flags:",
-    "Flag M: check for user in channels I am a member of",
-    NULL
-  };
+                while (*s)
+                {
+                        int i;
 
-  char *who_oper_help[] = {
-    "/WHO [+|-][acghimnsuMRI] [args]",
-    "Flags are specified like channel modes, the flags chigmnsu all have arguments",
-    "Flags are set to a positive check by +, a negative check by -",
-    "The flags work as follows:",
-    "Flag a: user is away",
-    "Flag c <channel>:       user is on <channel>,",
-    "                        no wildcards accepted",
-    "Flag g <gcos/realname>: user has string <gcos> in their GCOS,",
-    "                        wildcards accepted",
-    "Flag h <host>:          user has string <host> in their hostname,",
-    "                        wildcards accepted",
-    "Flag i <ip>:            user has string <ip> in their IP address,",
-    "                        wildcards accepted",
-    "Flag p <port>:          user is connecting on port <port>,",
-    "                        local connections only",
-    "Flag m <usermodes>:     user has <usermodes> set",
-    "Flag n <nick>:          user has string <nick> in their nickname,",
-    "                        wildcards accepted",
-    "Flag s <server>:        user is on server <server>,",
-    "                        wildcards not accepted",
-    "Flag u <user>:          user has string <user> in their username,",
-    "                        wildcards accepted",
-    "Behavior flags:",
-    "Flag M: check for user in channels I am a member of",
-    "Flag R: show users' real hostnames",
-    "Flag I: show users' IP addresses",
-    NULL
-  };
-  char **s;
+			switch (*s)
+			{
+				case '+':
+					what = WHO_ADD;
+					s++;
+					break;
+				case '-':
+					what = WHO_DEL;
+					s++;
+					break;
+			}
 
-	if (IsOper(sptr))
-		s = who_oper_help;
-	else
-		s = who_help;
+			if (!*s)
+				break;
 
-	for (; *s; s++)
-		sendto_one(sptr, getreply(RPL_LISTSYNTAX), me.name, sptr->name, *s);
-}
+			if (what == WHO_ADD)
+				umodes = &fmt.umodes;
+			else
+				umodes = &fmt.noumodes;
 
-#define WHO_ADD 1
-#define WHO_DEL 2
+                        for (i = 0; i <= Usermode_highest; i++)
+                                if (*s == Usermode_Table[i].flag)
+                                {
+                                        *umodes |= Usermode_Table[i].mode;
+                                        break;
+                                }
+                        s++;
+                }
 
-static int parse_who_options(aClient *sptr, int argc, char **argv)
-{
-char *s = argv[0];
-int what = WHO_ADD;
-int i = 1;
+                if (!IsOper(sptr))
+                        *umodes = *umodes & UMODE_OPER; /* these are usermodes regular users may search for. just oper now. */
+        }
 
-/* A few helper macro's because this is used a lot, added during recode by Syzop. */
+	/* '/who #some_channel' */
+	if(IsChannelName(mask))
+	{
+		aChannel *chptr = NULL;
 
-/** function requiress a parameter: check if there's one, if not: return -1. */
-#define REQUIRE_PARAM() { if (i >= argc) { \
-                           who_sendhelp(sptr); \
-                           return -1; \
-                      } } while(0);
-/** set option 'x' depending on 'what' (add/want or del/dontwant) */
-#define SET_OPTION(x) { if (what == WHO_ADD) \
-                           x = WHO_WANT; \
-                      else \
-                           x = WHO_DONTWANT; \
-                      } while(0);
-/** Eat a param, set the param in memory and set the option to want or dontwant */
-#define DOIT(x,y) { REQUIRE_PARAM(); x = argv[i]; SET_OPTION(y); i++; } while(0);
+		/* List all users on a given channel */
+		if((chptr = find_channel(parv[1] + operspy, NULL)) != NULL)
+		{
+			if(operspy)
+				sendto_snomask_global(SNO_EYES, "*** %s (%s@%s) did a /who on %s",
+					sptr->name, sptr->user->username, GetHost(sptr),
+					chptr->chname);
 
-	if (*s != '-' && *s != '+')
+			if(IsMember(sptr, chptr) || operspy)
+				do_who_on_channel(sptr, chptr, 1, operspy, &fmt);
+			else if(!SecretChannel(chptr))
+				do_who_on_channel(sptr, chptr, 0, operspy, &fmt);
+		}
+
+		sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask);
 		return 0;
+	}
 
-	while (*s)
- 	{
-		switch (*s)
+	/* '/who nick' */
+	if(((target_p = find_person(mask, NULL)) != NULL) &&
+		(!(fmt.matchsel & WMATCH_MODES)) &&
+		(!(fmt.matchsel & WMATCH_OPER) || IsOper(target_p)))
+	{
+		int isinvis = 0;
+		int i = 0;
+		Hook *h;
+
+		isinvis = IsInvisible(target_p);
+		for (lp = target_p->user->channel; lp; lp = lp->next)
 		{
-			case '+':
-	  			what = WHO_ADD;
-	  			break;
-			case '-':
-				what = WHO_DEL;
-				break;
-			case 'a':
-				SET_OPTION(wfl.want_away);
-				break;
-			case 'c':
-				DOIT(wfl.channel, wfl.want_channel);
-				break;
-			case 'g':
-				REQUIRE_PARAM()
-				if (!IsOper(sptr))
-					break; /* oper-only */
-				wfl.gecos = argv[i];
-				SET_OPTION(wfl.want_gecos);
-				i++;
-				break;
-			case 's':
-				DOIT(wfl.server, wfl.want_server);
-				break;
-			case 'h':
-				DOIT(wfl.host, wfl.want_host);
-				break;
-			case 'i':
-				REQUIRE_PARAM()
-				if (!IsOper(sptr))
-					break; /* oper-only */
-				wfl.ip = argv[i];
-				SET_OPTION(wfl.want_ip);
-				i++;
-				break;
-			case 'n':
-				DOIT(wfl.nick, wfl.want_nick);
-				break;
-			case 'u':
-				DOIT(wfl.user, wfl.want_user);
-				break;
-			case 'm':
-				REQUIRE_PARAM()
-				{
-					char *s = argv[i];
-					int *umodes;
+			member = IsMember(sptr, lp->chptr);
 
-					if (what == WHO_ADD)
-						umodes = &wfl.umodes_want;
-					else
-						umodes = &wfl.umodes_dontwant;
+			if(isinvis && !member)
+				continue;
 
-					while (*s)
-					{
-					int i;
-						for (i = 0; i <= Usermode_highest; i++)
-							if (*s == Usermode_Table[i].flag)
-							{
-								*umodes |= Usermode_Table[i].mode;
-								break;
-							}
-					s++;
-					}
 
-					if (!IsOper(sptr))
-						*umodes = *umodes & UMODE_OPER; /* these are usermodes regular users may search for. just oper now. */
-					if (*umodes == 0)
-						return -1;
-				}
-				i++;
-				break;
-			case 'p':
-				REQUIRE_PARAM()
-				if (!IsOper(sptr))
-					break; /* oper-only */
-				wfl.port = atoi(argv[i]);
-				SET_OPTION(wfl.want_port);
-				i++;
-				break;
-			case 'M':
-				SET_OPTION(wfl.common_channels_only);
-				break;
-			case 'R':
-				if (!IsOper(sptr))
-					break;
-				if (what == WHO_ADD)
-					who_flags |= WF_REALHOST;
-				else
-					who_flags &= ~WF_REALHOST;
-				break;
-			case 'I':
-				if (!IsOper(sptr))
+			for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
+			{
+				i = (*(h->func.intfunc))(target_p,lp->chptr);
+				if (i != 0)
 					break;
-				if (what == WHO_ADD)
-					who_flags |= WF_IP;
-				
-				else
-					who_flags &= ~WF_IP;
+			}
+
+			if (i != 0 && !(is_skochanop(sptr, lp->chptr)) && !(is_skochanop(target_p, lp->chptr) || has_voice(target_p,lp->chptr)))
+				continue;
+
+			if(member || (!isinvis && PubChannel(lp->chptr)))
 				break;
-			default:
-				who_sendhelp(sptr);
-				return -1;
 		}
-		s++;
-    }
 
-  return i;
-#undef REQUIRE_PARAM
-#undef SET_OPTION
-#undef DOIT
+		if (lp != NULL)
+			do_who(sptr, target_p, lp->chptr, &fmt);
+		else
+			do_who(sptr, target_p, NULL, &fmt);
+
+		sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, parv[1]);
+		return 0;
+	}
+
+	if(ValidatePermissionsForPath("override:see:who:secret",sptr,NULL,NULL,NULL) ||
+                ValidatePermissionsForPath("override:see:whois",sptr,NULL,NULL,NULL))
+		operspy = 1;
+
+	/* '/who 0' for a global list.  this forces clients to actually
+	 * request a full list.  I presume its because of too many typos
+	 * with "/who" ;) --fl
+	 */
+	if((*(mask + 1) == '\0') && (*mask == '0'))
+		who_global(sptr, NULL, 0, &fmt);
+	else
+		who_global(sptr, mask, operspy, &fmt);
+
+	sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask);
+
+	return 0;
 }
 
-static int can_see(aClient *sptr, aClient *acptr, aChannel *channel)
+/* do_match
+ * inputs       - pointer to client requesting who
+ *              - pointer to client to do who on
+ *              - char * mask to match
+ *              - format options
+ * output       - 1 if match, 0 if no match
+ * side effects - NONE
+ */
+static int
+do_match(aClient *source_p, aClient *target_p, char *mask, struct who_format *fmt)
 {
-int ret = 0;
-int i=0;
-Hook *h;
-char has_common_chan = 0;
-	do {
-		/* can only see people */
-		if (!IsPerson(acptr))
-			return WHO_CANTSEE;
+	if (mask == NULL)
+		return 1;
 
-		/* can only see opers if thats what they want */
-		if (who_flags & WF_OPERONLY)
-		{
-			if (!IsOper(acptr))
-				return ret | WHO_CANTSEE;
-			if (IsHideOper(acptr)) {
-				if (IsOper(sptr))
-					ret |= WHO_OPERSEE;
-				else
-					return ret | WHO_CANTSEE;
-			}
-		}
+	/* default */
+	if (fmt->matchsel == 0 && (!match(mask, target_p->name) ||
+		!match(mask, target_p->user->username) ||
+		!match(mask, GetHost(target_p)) ||
+		(IsOper(source_p) &&
+		(!match(mask, target_p->user->realhost) ||
+		(target_p->ip &&
+		!match(mask, target_p->ip))))))
+		return 1;
 
-		/* if they only want people who are away */
-		if ((wfl.want_away == WHO_WANT && !acptr->user->away) ||
-		    (wfl.want_away == WHO_DONTWANT && acptr->user->away))
-			return WHO_CANTSEE;
+	/* match nick */
+	if (IsMatch(fmt, WMATCH_NICK) && !match(mask, target_p->name))
+		return 1;
 
-		/* if they only want people on a certain channel. */
-		if (wfl.want_channel != WHO_DONTCARE)
- 		{
-			aChannel *chan = find_channel(wfl.channel, NULL);
-			if (!chan && wfl.want_channel == WHO_WANT)
-				return WHO_CANTSEE;
-			if ((wfl.want_channel == WHO_WANT) && !IsMember(acptr, chan))
-				return WHO_CANTSEE;
-			if ((wfl.want_channel == WHO_DONTWANT) && IsMember(acptr, chan))
-				return WHO_CANTSEE;
-		}
+	/* match username */
+	if (IsMatch(fmt, WMATCH_USER) && !match(mask, target_p->user->username))
+		return 1;
 
-		/* if they only want people with a certain gecos */
-		if (wfl.want_gecos != WHO_DONTCARE)
-		{
-			if (((wfl.want_gecos == WHO_WANT) && match(wfl.gecos, acptr->info)) ||
-			    ((wfl.want_gecos == WHO_DONTWANT) && !match(wfl.gecos, acptr->info)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
+	/* match server */
+	if (IsMatch(fmt, WMATCH_SERVER) && IsOper(source_p) && !match(mask, target_p->user->server))
+		return 1;
 
-		/* if they only want people with a certain server */
-		if (wfl.want_server != WHO_DONTCARE)
-		{
-			if (((wfl.want_server == WHO_WANT) && stricmp(wfl.server, acptr->user->server)) ||
-			    ((wfl.want_server == WHO_DONTWANT) && !stricmp(wfl.server, acptr->user->server)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
+	/* match hostname */
+	if (IsMatch(fmt, WMATCH_HOST) && (!match(mask, GetHost(target_p)) ||
+		(IsOper(source_p) && (!match(mask, target_p->user->realhost) ||
+		(target_p->ip && !match(mask, target_p->ip))))))
+		return 1;
 
-		/* if they only want people with a certain host */
-		if (wfl.want_host != WHO_DONTCARE)
-		{
-			char *host;
+	/* match realname */
+	if (IsMatch(fmt, WMATCH_INFO) && !match(mask, target_p->info))
+		return 1;
 
-			if (IsOper(sptr))
-				host = acptr->user->realhost;
-			else
-				host = GetHost(acptr);
+	/* match ip address */
+	if (IsMatch(fmt, WMATCH_IP) && IsOper(source_p) && target_p->ip &&
+		!match(mask, target_p->ip))
+		return 1;
 
-			if (((wfl.want_host == WHO_WANT) && match(wfl.host, host)) ||
-			    ((wfl.want_host == WHO_DONTWANT) && !match(wfl.host, host)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
+	/* match account */
+	if (IsMatch(fmt, WMATCH_ACCOUNT) && !EmptyString(target_p->user->svid) &&
+		!isdigit(*target_p->user->svid) && !match(mask, target_p->user->svid))
+		return 1;
 
-		/* if they only want people with a certain IP */
-		if (wfl.want_ip != WHO_DONTCARE)
-		{
-			char *ip;
+	/* match usermodes */
+	if (IsMatch(fmt, WMATCH_MODES) &&
+		((target_p->umodes & fmt->umodes) &&
+		!(target_p->umodes & fmt->noumodes) &&
+		(!(target_p->umodes & UMODE_HIDEOPER) || IsOper(source_p))))
+		return 1;
 
-			ip = acptr->ip;
-			if (!ip)
-				return WHO_CANTSEE;
+	return 0;
+}
 
-			if (((wfl.want_ip == WHO_WANT) && match(wfl.ip, ip)) ||
-			    ((wfl.want_ip == WHO_DONTWANT) && !match(wfl.ip, ip)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
+/* who_common_channel
+ * inputs       - pointer to client requesting who
+ *              - pointer to channel.
+ *              - char * mask to match
+ *              - int if oper on a server or not
+ *              - pointer to int maxmatches
+ *              - format options
+ * output       - NONE
+ * side effects - lists matching invisible clients on specified channel,
+ *                marks matched clients.
+ */
 
-		/* if they only want people connecting on a certain port */
-		if (wfl.want_port != WHO_DONTCARE)
-		{
-			int port;
-			
-			if (!MyClient(acptr))
-				return WHO_CANTSEE;
+static void
+who_common_channel(aClient *source_p, aChannel *chptr,
+	char *mask, int *maxmatches,
+	struct who_format *fmt)
+{
+	Member *cm = chptr->members;
+	aClient *target_p;
+	Hook *h;
+	int i = 0;
 
-			port = acptr->local->listener->port;
+	for (cm = chptr->members; cm; cm = cm->next)
+	{
+		target_p = cm->cptr;
 
-			if (((wfl.want_port == WHO_WANT) && wfl.port != port) ||
-			    ((wfl.want_port == WHO_DONTWANT) && wfl.port == port))
-			{
-				return WHO_CANTSEE;
-			}
-		}
+		if(!IsInvisible(target_p) || IsMarked(target_p))
+			continue;
 
-		/* if they only want people with a certain nick.. */
-		if (wfl.want_nick != WHO_DONTCARE)
-		{
-			if (((wfl.want_nick == WHO_WANT) && match(wfl.nick, acptr->name)) ||
-			    ((wfl.want_nick == WHO_DONTWANT) && !match(wfl.nick, acptr->name)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
+		if(IsMatch(fmt, WMATCH_OPER) && !IsOper(target_p))
+			continue;
 
-		/* if they only want people with a certain username */
-		if (wfl.want_user != WHO_DONTCARE)
+                for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
+                {
+                        i = (*(h->func.intfunc))(target_p,chptr);
+                        if (i != 0)
+                                break;
+                }
+
+                if (i != 0 && !(is_skochanop(source_p, chptr)) && !(is_skochanop(target_p, chptr) || has_voice(target_p,chptr)))
+                        continue;
+
+		SetMark(target_p);
+
+		if(*maxmatches > 0)
 		{
-			if (((wfl.want_user == WHO_WANT) && match(wfl.user, acptr->user->username)) ||
-			    ((wfl.want_user == WHO_DONTWANT) && !match(wfl.user, acptr->user->username)))
+			if (do_match(source_p, target_p, mask, fmt))
 			{
-				return WHO_CANTSEE;
+				do_who(source_p, target_p, NULL, fmt);
+				--(*maxmatches);
 			}
 		}
+	}
+}
 
-		/* if they only want people with a certain umode */
-		if (wfl.umodes_want)
-		{
-			if (!(acptr->umodes & wfl.umodes_want) || (!IsOper(sptr) && (acptr->umodes & UMODE_HIDEOPER)))
-				return WHO_CANTSEE;
-		}
-
-		if (wfl.umodes_dontwant)
-		{
-			if ((acptr->umodes & wfl.umodes_dontwant) && (!(acptr->umodes & UMODE_HIDEOPER) || IsOper(sptr)))
-				return WHO_CANTSEE;
-		}
+/*
+ * who_global
+ *
+ * inputs       - pointer to client requesting who
+ *              - char * mask to match
+ *              - int if oper on a server or not
+ *              - int if operspy or not
+ *              - format options
+ * output       - NONE
+ * side effects - do a global scan of all clients looking for match
+ *                this is slightly expensive on EFnet ...
+ *                marks assumed cleared for all clients initially
+ *                and will be left cleared on return
+ */
 
-		/* if they only want common channels */
-		if (wfl.common_channels_only)
-		{
-			if (!has_common_channels(sptr, acptr))
-				return WHO_CANTSEE;
-			has_common_chan = 1;
-		}
+static void
+who_global(aClient *source_p, char *mask, int operspy, struct who_format *fmt)
+{
+	aClient *target_p;
+	int maxmatches = WHOLIMIT;
 
-		if (channel)
-		{
-			int member = who_flags & WF_ONCHANNEL;
+	/* first, list all matching INvisible clients on common channels
+	 * if this is not an operspy who
+	 */
+	if(!operspy)
+	{
+		Membership *lp;
 
-			if (SecretChannel(channel) || HiddenChannel(channel))
-			{
-				/* if they aren't on it.. they can't see it */
-				if (!(who_flags & WF_ONCHANNEL))
-					break;
-			}
-			if (IsInvisible(acptr) && !member)
-				break;
+		for (lp = source_p->user->channel; lp; lp = lp->next)
+			who_common_channel(source_p, lp->chptr, mask, &maxmatches, fmt);
+	}
 
-			for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
-			{
-				i = (*(h->func.intfunc))(acptr,channel);
-				if (i != 0)
-					break;
-			}
+	/* second, list all matching visible clients and clear all marks
+	 * on invisible clients
+	 * if this is an operspy who, list all matching clients, no need
+	 * to clear marks
+	 */
+	list_for_each_entry(target_p, &client_list, client_node)
+	{
+		if(!IsPerson(target_p))
+			continue;
 
-			if (i != 0 && !(is_skochanop(sptr, channel)) && !(is_skochanop(acptr, channel) || has_voice(acptr,channel)))
-				break;
-		}
-		else
+		if(IsInvisible(target_p) && !operspy)
 		{
-			/* a user/mask who */
+			ClearMark(target_p);
+			continue;
+		}
 
-			/* If the common channel info hasn't been set, set it now */
-			if (!wfl.common_channels_only)
-				has_common_chan = has_common_channels(sptr, acptr);
+		if(IsMatch(fmt, WMATCH_OPER) && !IsOper(target_p))
+			continue;
 
-			if (IsInvisible(acptr) && !has_common_chan)
+		if(maxmatches > 0)
+		{
+			if (do_match(source_p, target_p, mask, fmt))
 			{
-				/* don't show them unless it's an exact match 
-				   or it is the user requesting the /who */
-				if ((who_flags & WF_WILDCARD) && sptr != acptr)
-					break;
+				do_who(source_p, target_p, NULL, fmt);
+				--maxmatches;
 			}
 		}
+	}
 
-		/* phew.. show them. */
-		return WHO_CANSEE;
-	} while (0);
+	if (maxmatches <= 0)
+		sendto_one(source_p, rpl_str(ERR_TOOMANYMATCHES), me.name, source_p->name, "WHO");
+}
 
-	/* if we get here, it's oper-dependant. */
-	if (IsOper(sptr))
-		return ret | WHO_OPERSEE | WHO_CANSEE;
-	else
+/*
+ * do_who_on_channel
+ *
+ * inputs       - pointer to client requesting who
+ *              - pointer to channel to do who on
+ *              - The "real name" of this channel
+ *              - int if source_p is a server oper or not
+ *              - int if client is member or not
+ *              - format options
+ * output       - NONE
+ * side effects - do a who on given channel
+ */
+
+static void
+do_who_on_channel(aClient *source_p, aChannel *chptr,
+	int member, int operspy, struct who_format *fmt)
+{
+	Member *cm = chptr->members;
+	Hook *h;
+	int i = 0;
+
+	for (cm = chptr->members; cm; cm = cm->next)
 	{
-		if (sptr == acptr)
-			return ret | WHO_CANSEE;
-		else
-			return ret | WHO_CANTSEE;
+		aClient *target_p = cm->cptr;
+
+		if(IsMatch(fmt, WMATCH_OPER) && !IsOper(target_p))
+			continue;
+
+                for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
+                {
+                        i = (*(h->func.intfunc))(target_p,chptr);
+                        if (i != 0)
+                                break;
+                }
+
+                if (!operspy && (target_p != source_p) && i != 0 && !(is_skochanop(source_p, chptr)) && !(is_skochanop(target_p, chptr) || has_voice(target_p,chptr)))
+                        continue;
+
+		if(member || !IsInvisible(target_p))
+			do_who(source_p, target_p, chptr, fmt);
 	}
 }
 
-static void do_channel_who(aClient *sptr, aChannel *channel, char *mask)
+/*
+ * append_format
+ *
+ * inputs       - pointer to buffer
+ *              - size of buffer
+ *              - pointer to position
+ *              - format string
+ *              - arguments for format
+ * output       - NONE
+ * side effects - position incremented, possibly beyond size of buffer
+ *                this allows detecting overflow
+ */
+
+static void
+append_format(char *buf, size_t bufsize, size_t *pos, const char *fmt, ...)
 {
-	Member *cm = channel->members;
-	if (IsMember(sptr, channel) || ValidatePermissionsForPath("override:see:who:onchannel",sptr,NULL,channel,NULL))
-		who_flags |= WF_ONCHANNEL;
+	size_t max, result;
+	va_list ap;
 
-	for (cm = channel->members; cm; cm = cm->next)
-	{
-		aClient *acptr = cm->cptr;
-		char status[20];
-		int cansee;
-		if ((cansee = can_see(sptr, acptr, channel)) & WHO_CANTSEE)
-			continue;
+	max = *pos >= bufsize ? 0 : bufsize - *pos;
+	va_start(ap, fmt);
+	result = vsnprintf(buf + *pos, max, fmt, ap);
+	va_end(ap);
+	*pos += result;
+}
 
-		make_who_status(sptr, acptr, channel, cm, status, cansee);
-		send_who_reply(sptr, acptr, channel->chname, status, "");
-    }
+/*
+ * show_ip() - asks if the true IP should be shown when source is
+ *             asking for info about target
+ *
+ * Inputs       - source_p who is asking
+ *              - target_p who do we want the info on
+ * Output       - returns 1 if clear IP can be shown, otherwise 0
+ * Side Effects - none
+ */
+
+static int
+show_ip(aClient *source_p, aClient *target_p)
+{
+	if(IsServer(target_p))
+		return 0;
+	else if((source_p != NULL) && (MyConnect(source_p) && !IsOper(source_p)) && (source_p == target_p))
+		return 1;
+	else if(IsHidden(target_p) && ((source_p != NULL) && !IsOper(source_p)))
+		return 0;
+	else
+		return 1;
 }
 
-static void make_who_status(aClient *sptr, aClient *acptr, aChannel *channel, 
-			    Member *cm, char *status, int cansee)
+/*
+ * do_who
+ *
+ * inputs       - pointer to client requesting who
+ *              - pointer to client to do who on
+ *              - channel or NULL
+ *              - format options
+ * output       - NONE
+ * side effects - do a who on given person
+ */
+
+static void
+do_who(aClient *source_p, aClient *target_p, aChannel *chptr, struct who_format *fmt)
 {
-int i = 0;
-Hook *h;
+	char status[20];
+	char str[510 + 1];
+	size_t pos;
+	int hide = (FLAT_MAP && !IsOper(source_p)) ? 1 : 0;
+	int i = 0;
+	Hook *h;
 
-	if (acptr->user->away)
+	if (target_p->user->away)
 		status[i++] = 'G';
 	else
 		status[i++] = 'H';
 
-	if (IsARegNick(acptr))
+	if (IsARegNick(target_p))
 		status[i++] = 'r';
 
 	for (h = Hooks[HOOKTYPE_WHO_STATUS]; h; h = h->next)
 	{
-		int ret = (*(h->func.intfunc))(sptr, acptr, channel, cm, status, cansee);
+		int ret = (*(h->func.intfunc))(source_p, target_p, NULL, NULL, status, 0);
 		if (ret != 0)
 			status[i++] = (char)ret;
 	}
-	
-	if (IsOper(acptr) && (!IsHideOper(acptr) || sptr == acptr || IsOper(sptr)))
+
+	if (IsOper(target_p) && (!IsHideOper(target_p) || source_p == target_p || IsOper(source_p)))
 		status[i++] = '*';
 
-	if (IsOper(acptr) && (IsHideOper(acptr) && sptr != acptr && IsOper(sptr)))
+	if (IsOper(target_p) && (IsHideOper(target_p) && source_p != target_p && IsOper(source_p)))
 		status[i++] = '!';
-  
-	if (cansee & WHO_OPERSEE)
-		status[i++] = '?';
-
-	if (cm)
-        {
-#ifdef PREFIX_AQ
-		if (cm->flags & CHFL_CHANOWNER)
-			status[i++] = '~';
-		else if (cm->flags & CHFL_CHANPROT)
-			status[i++] = '&';
-		else
-#endif
-		if (cm->flags & CHFL_CHANOP)
-			status[i++] = '@';
-		else if (cm->flags & CHFL_HALFOP)
-			status[i++] = '%';
-		else if (cm->flags & CHFL_VOICE)
-			status[i++] = '+';
-	}
-
-	status[i] = '\0';
-}
-
-static void do_other_who(aClient *sptr, char *mask)
-{
-int oper = IsOper(sptr);
 
-	if (strchr(mask, '*') || strchr(mask, '?'))
+	if (chptr)
 	{
-		int i = 0;
-		/* go through all users.. */
-		aClient *acptr;
-		who_flags |= WF_WILDCARD;
+		Membership *lp;
 
-		list_for_each_entry(acptr, &client_list, client_node)
+		if (lp = find_membership_link(target_p->user->channel, chptr))
 		{
-		int cansee;
-		char status[20];
-		char *channel;
-		int flg;
-
-			if (!IsPerson(acptr))
-				continue;
-			if (!oper) {
-				/* non-opers can only search on nick here */
-				if (match(mask, acptr->name))
-					continue;
-			} else {
-				/* opers can search on name, ident, virthost, ip and realhost.
-				 * Yes, I like readable if's -- Syzop.
-				 */
-				if (!match(mask, acptr->name) || !match(mask, acptr->user->realhost) ||
-				    !match(mask, acptr->user->username))
-					goto matchok;
-				if (IsHidden(acptr) && !match(mask, acptr->user->virthost))
-					goto matchok;
-				if (acptr->ip && !match(mask, acptr->ip))
-					goto matchok;
-				/* nothing matched... */
-				continue;
-			}
-matchok:
-			if ((cansee = can_see(sptr, acptr, NULL)) & WHO_CANTSEE)
-				continue;
-			if (WHOLIMIT && !IsOper(sptr) && ++i > WHOLIMIT)
+			if (!(fmt->fields || SupportNAMESX(source_p)))
 			{
-				sendto_one(sptr, rpl_str(ERR_WHOLIMEXCEED), me.name, sptr->name, WHOLIMIT);
-				return;
+				/* Standard NAMES reply */
+#ifdef PREFIX_AQ
+				if (lp->flags & CHFL_CHANOWNER)
+                    			status[i++] = '~';
+            			else if (lp->flags & CHFL_CHANPROT)
+                    			status[i++] = '&';
+            			else
+#endif
+            			if (lp->flags & CHFL_CHANOP)
+                    			status[i++] = '@';
+            			else if (lp->flags & CHFL_HALFOP)
+                    			status[i++] = '%';
+            			else if (lp->flags & CHFL_VOICE)
+                    			status[i++] = '+';
+    			} else {
+            		/* NAMES reply with all rights included (NAMESX) */
+#ifdef PREFIX_AQ
+            			if (lp->flags & CHFL_CHANOWNER)
+                    			status[i++] = '~';
+            			if (lp->flags & CHFL_CHANPROT)
+                    			status[i++] = '&';
+#endif
+            			if (lp->flags & CHFL_CHANOP)
+                			status[i++] = '@';
+        		        if (lp->flags & CHFL_HALFOP)
+            			        status[i++] = '%';
+        		        if (lp->flags & CHFL_VOICE)
+                		        status[i++] = '+';
 			}
-
-			channel = first_visible_channel(sptr, acptr, &flg);
-			make_who_status(sptr, acptr, NULL, NULL, status, cansee);
-			send_who_reply(sptr, acptr, channel, status, (flg & FVC_HIDDEN) ? "~" : "");
 		}
 	}
-	else
-	{
-		/* just a single client (no wildcards detected) */
-		aClient *acptr = find_client(mask, NULL);
-		int cansee;
-		char status[20];
-		char *channel;
-		int flg;
-
-		if (!acptr)
-			return;
-
-		if ((cansee = can_see(sptr, acptr, NULL)) == WHO_CANTSEE)
-			return;
-
-		channel = first_visible_channel(sptr, acptr, &flg);
-		make_who_status(sptr, acptr, NULL, NULL, status, cansee);
-		send_who_reply(sptr, acptr, channel, status, (flg & FVC_HIDDEN) ? "~" : "");
-	}
-}
 
-static void send_who_reply(aClient *sptr, aClient *acptr, 
-			   char *channel, char *status, char *xstat)
-{
-	char *stat;
-	char *host;
-	int flat = (FLAT_MAP && !IsOper(sptr)) ? 1 : 0;
-
-	stat = malloc(strlen(status) + strlen(xstat) + 1);
-	sprintf(stat, "%s%s", status, xstat);
-
-	if (IsOper(sptr))
-	{
-		if (who_flags & WF_REALHOST)
-			host = acptr->user->realhost;
-		else if (who_flags & WF_IP)
-			host = (acptr->ip ? acptr->ip : acptr->user->realhost);
-		else
-			host = GetHost(acptr);
-	}
-	else
-		host = GetHost(acptr);
-					
-
-	if (IsULine(acptr) && !IsOper(sptr) && !ValidatePermissionsForPath("map:ulines",sptr,acptr,NULL,NULL) && HIDE_ULINES)
-	        sendto_one(sptr, getreply(RPL_WHOREPLY), me.name, sptr->name,
-        	     channel,       /* channel name */
-	             acptr->user->username, /* user name */
-        	     host,		    /* hostname */
-	             "hidden",              /* let's hide the server from normal users if the server is a uline and HIDE_ULINES is on */
-        	     acptr->name,           /* nick */
-	             stat,                  /* status */
-        	     0,                     /* hops (hidden) */
-	             acptr->info            /* realname */
-             	);
+	status[i] = '\0';
 
+	if (fmt->fields == 0)
+		sendto_one(source_p, getreply(RPL_WHOREPLY), me.name,
+			source_p->name, chptr ? chptr->chname : "*",
+			target_p->user->username, GetHost(target_p),
+			hide ? "*" : target_p->user->server,
+			target_p->name, status, hide ? 0 : target_p->hopcount, target_p->info);
 	else
-		sendto_one(sptr, getreply(RPL_WHOREPLY), me.name, sptr->name,      
-		     channel,       /* channel name */
-		     acptr->user->username,      /* user name */
-		     host,		         /* hostname */
-		     acptr->user->server,        /* server name */
-		     acptr->name,                /* nick */
-		     stat,                       /* status */
-		     flat ? 0 : acptr->hopcount, /* hops */ 
-		     acptr->info                 /* realname */
-		     );
-	free(stat);
-}
-
-static char *first_visible_channel(aClient *sptr, aClient *acptr, int *flg)
-{
-	Membership *lp;
-
-	*flg = 0;
-
-	for (lp = acptr->user->channel; lp; lp = lp->next)
 	{
-		aChannel *chptr = lp->chptr;
-		Hook *h;
-		int ret = EX_ALLOW;
-		int operoverride = 0;
-		int showchannel = 0;
-		
-		/* Note that the code below is almost identical to the one in /WHOIS */
-
-		if (ShowChannel(sptr, chptr))
-			showchannel = 1;
-
-		for (h = Hooks[HOOKTYPE_SEE_CHANNEL_IN_WHOIS]; h; h = h->next)
+		str[0] = '\0';
+		pos = 0;
+		append_format(str, sizeof str, &pos, ":%s %d %s", me.name, RPL_WHOSPCRPL, source_p->name);
+		if (HasField(fmt, FIELD_QUERYTYPE))
+			append_format(str, sizeof str, &pos, " %s", fmt->querytype);
+		if (HasField(fmt, FIELD_CHANNEL))
+			append_format(str, sizeof str, &pos, " %s", chptr ? chptr->chname : "*");
+		if (HasField(fmt, FIELD_USER))
+			append_format(str, sizeof str, &pos, " %s", target_p->user->username);
+		if (HasField(fmt, FIELD_IP))
 		{
-			int n = (*(h->func.intfunc))(sptr, acptr, chptr);
-			/* Hook return values:
-			 * EX_ALLOW means 'yes is ok, as far as modules are concerned'
-			 * EX_DENY means 'hide this channel, unless oper overriding'
-			 * EX_ALWAYS_DENY means 'hide this channel, always'
-			 * ... with the exception that we always show the channel if you /WHOIS yourself
-			 */
-			if (n == EX_DENY)
-			{
-				ret = EX_DENY;
-			}
-			else if (n == EX_ALWAYS_DENY)
-			{
-				ret = EX_ALWAYS_DENY;
-				break;
-			}
+			if (show_ip(source_p, target_p) && target_p->ip)
+				append_format(str, sizeof str, &pos, " %s", target_p->ip);
+			else
+				append_format(str, sizeof str, &pos, " %s", "255.255.255.255");
 		}
-		
-		if (ret == EX_DENY)
-			showchannel = 0;
-		
-		if (!showchannel && (ValidatePermissionsForPath("override:see:who:secret",sptr,NULL,chptr,NULL) || ValidatePermissionsForPath("override:see:whois",sptr,NULL,chptr,NULL)))
+		if (HasField(fmt, FIELD_HOST) || HasField(fmt, FIELD_REALHOST))
 		{
-			showchannel = 1; /* OperOverride */
-			operoverride = 1;
+			if (IsOper(source_p) && HasField(fmt, FIELD_REALHOST))
+				append_format(str, sizeof str, &pos, " %s", target_p->user->realhost);
+			else
+				append_format(str, sizeof str, &pos, " %s", GetHost(target_p));
 		}
-		
-		if ((ret == EX_ALWAYS_DENY) && (acptr != sptr))
-			continue; /* a module asked us to really not expose this channel, so we don't (except target==ourselves). */
-
-		if (acptr == sptr)
-			showchannel = 1;
-
-		if (operoverride)
-			*flg |= FVC_HIDDEN;
-
-		if (showchannel)
-			return chptr->chname;
-	}
-
-	/* no channels that they can see */
-	return "*";
-}
-
-static int has_common_channels(aClient *c1, aClient *c2)
-{
-	Membership *lp;
-	Hook *h;
-	int j = 0, k = 0;
-
-	for (lp = c1->user->channel; lp; lp = lp->next)
-	{
-		if (IsMember(c2, lp->chptr))
+		if (HasField(fmt, FIELD_SERVER))
+			append_format(str, sizeof str, &pos, " %s", hide ? "*" : target_p->user->server);
+		if (HasField(fmt, FIELD_NICK))
+			append_format(str, sizeof str, &pos, " %s", target_p->name);
+		if (HasField(fmt, FIELD_FLAGS))
+			append_format(str, sizeof str, &pos, " %s", status);
+		if (HasField(fmt, FIELD_MODES))
 		{
-			if (c1 == c2)
-				return 1;
-
-			for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
-							{
-								j = (*(h->func.intfunc))(c2,lp->chptr);
-								if (j != 0)
-									break;
-							}
-
-			/* We must ensure that c1 is allowed to "see" c2 */
-                        if (j != 0 &&
-                        		!(is_skochanop(c2, lp->chptr) || has_voice(c2,lp->chptr)) && !is_skochanop(c1, lp->chptr))
-                                break;
+			if (IsOper(source_p))
+				append_format(str, sizeof str, &pos, " %s", strtok(get_mode_str(target_p), "+"));
+			else
+				append_format(str, sizeof str, &pos, " %s", "*");
+		}
+		if (HasField(fmt, FIELD_HOP))
+			append_format(str, sizeof str, &pos, " %d", hide ? 0 : target_p->hopcount);
+		if (HasField(fmt, FIELD_IDLE))
+			append_format(str, sizeof str, &pos, " %d", (int)(MyClient(target_p) &&
+				(!(target_p->umodes & UMODE_HIDLE) || IsOper(source_p) ||
+				(source_p == target_p)) ? TStime() - target_p->local->last : 0));
+		if (HasField(fmt, FIELD_ACCOUNT))
+			append_format(str, sizeof str, &pos, " %s", (!isdigit(*target_p->user->svid)) ? target_p->user->svid : "0");
+		if (HasField(fmt, FIELD_OPLEVEL))
+			append_format(str, sizeof str, &pos, " %s", (chptr && is_skochanop(target_p, chptr)) ? "999" : "n/a");
+		if (HasField(fmt, FIELD_INFO))
+			append_format(str, sizeof str, &pos, " :%s", target_p->info);
 
-			return 1;
+		if (pos >= sizeof str)
+		{
+			static int warned = 0;
+			if (!warned)
+				sendto_snomask(SNO_JUNK, "*** WHOX overflow while sending information about %s to %s", target_p->name, source_p->name);
+			warned = 1;
 		}
+		sendto_one(source_p, "%s", str);
 	}
-	return 0;
 }
diff -rdu a/src/s_err.c b/src/s_err.c
--- a/src/s_err.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/s_err.c	2016-03-10 20:15:26.000000000 +0300
@@ -456,7 +456,7 @@
 /* 413    ERR_NOTOPLEVEL */ ":%s 413 %s %s :No toplevel domain specified",
 /* 414    ERR_WILDTOPLEVEL */ ":%s 414 %s %s :Wildcard in toplevel Domain",
 /* 415 */ NULL, /* rfc2812 */
-/* 416 */ NULL, /* ircnet, ircu */
+/* 416    ERR_TOOMANYMATCHES */ ":%s 416 %s %s :output too large, truncated",
 /* 417 */ NULL,
 /* 418 */ NULL,
 /* 419 */ NULL, /* aircd */
diff -rdu a/src/s_user.c b/src/s_user.c
--- a/src/s_user.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/s_user.c	2016-02-08 23:19:22.000000000 +0300
@@ -67,6 +67,8 @@
 
 	if (UHOST_ALLOWED == UHALLOW_REJOIN)
 		rejoin_leave(sptr);
+
+	sendto_common_channels_local_butone(sptr, PROTO_CHGHOST, 0, ":%s CHGHOST %s %s", sptr->name, sptr->user->username, host);
 	if (sptr->user->virthost)
 	{
 		MyFree(sptr->user->virthost);
diff -rdu a/src/send.c b/src/send.c
--- a/src/send.c	2015-12-18 19:54:11.000000000 +0300
+++ b/src/send.c	2016-02-08 23:03:09.000000000 +0300
@@ -450,6 +450,30 @@
 	}
 }
 
+void sendto_chanops_with_capability_butone(aClient *one, aChannel *chptr, unsigned int caps, unsigned int negcaps, char *pattern, ...)
+{
+	va_list vl;
+	Member *lp;
+	aClient *acptr;
+
+	for (lp = chptr->members; lp; lp = lp->next)
+	{
+		acptr = lp->cptr;
+		if (acptr == one || !(lp->flags & (CHFL_CHANOP|CHFL_CHANOWNER|CHFL_CHANPROT)))
+			continue;	/* ...was the one I should skip
+					   or user not not a channel op */
+		if (MyConnect(acptr) && IsRegisteredUser(acptr))
+		{
+			if (!IsCapable(acptr, caps) ||
+			    !NotCapable(acptr, negcaps))
+				continue;
+
+			va_start(vl, pattern);
+			vsendto_one(acptr, pattern, vl);
+			va_end(vl);
+		}
+	}
+}
 
 /*
  * sendto_server
@@ -548,7 +572,7 @@
  * Sends a message to all people on local server who are
  * in same channel with user and have the specified capability.
  */
-void sendto_common_channels_local_butone(aClient *user, int cap, char *pattern, ...)
+void sendto_common_channels_local_butone(aClient *user, unsigned int cap, unsigned int negcap, char *pattern, ...)
 {
 	va_list vl;
 
@@ -573,7 +597,8 @@
 			{
 				cptr = users->cptr;
 				if (!MyConnect(cptr) || (cptr->local->serial == current_serial) ||
-				    !CHECKPROTO(cptr, cap))
+				    !IsCapable(cptr, cap) ||
+				    !NotCapable(cptr, negcap))
 					continue;
 				cptr->local->serial = current_serial;
 				sendbufto_one(cptr, sendbuf, sendlen);
@@ -612,6 +637,34 @@
 	return;
 }
 
+void sendto_channel_local_with_capability(aClient *from, unsigned int caps, unsigned int negcaps, aChannel *chptr, char *pattern, ...)
+{
+	va_list vl;
+	Member *lp;
+	aClient *acptr;
+	int sendlen;
+
+	/* We now create the buffer _before_ we send it to the clients. Rather than
+	 * rebuilding the buffer 1000 times for a 1000 local-users channel. -- Syzop
+	 */
+	*sendbuf = '\0';
+	va_start(vl, pattern);
+	sendlen = vmakebuf_local_withprefix(sendbuf, sizeof sendbuf, from, pattern, vl);
+	va_end(vl);
+
+	for (lp = chptr->members; lp; lp = lp->next)
+		if (MyConnect(acptr = lp->cptr))
+		{
+			if (!IsCapable(lp->cptr, caps) ||
+			    !NotCapable(lp->cptr, negcaps))
+				continue;
+
+			sendbufto_one(acptr, sendbuf, sendlen);
+		}
+
+	return;
+}
+
 void sendto_channel_butserv_butone(aChannel *chptr, aClient *from, aClient *one, char *pattern, ...)
 {
 	va_list vl;
@@ -632,6 +685,30 @@
 	}
 }
 
+void sendto_channel_local_with_capability_butone(aClient *from, aClient *one, unsigned int caps, unsigned int negcaps, aChannel *chptr, char *pattern, ...)
+{
+	va_list vl;
+	Member *lp;
+	aClient *acptr;
+
+	for (lp = chptr->members; lp; lp = lp->next)
+	{
+		if (lp->cptr == one)
+			continue;
+
+		if (MyConnect(acptr = lp->cptr))
+		{
+			if (!IsCapable(lp->cptr, caps) ||
+			    !NotCapable(lp->cptr, negcaps))
+				continue;
+
+			va_start(vl, pattern);
+			vsendto_prefix_one(acptr, from, pattern, vl);
+			va_end(vl);
+		}
+	}
+}
+
 /*
 ** send a msg to all ppl on servers/hosts that match a specified mask
 ** (used for enhanced PRIVMSGs)
