]> jfr.im git - irc/quakenet/snircd-patchqueue.git/commitdiff
sethost: minor overhaul of sethost - see file for more info
authorwiebe <redacted>
Sat, 24 Apr 2010 13:11:36 +0000 (15:11 +0200)
committerwiebe <redacted>
Sat, 24 Apr 2010 13:11:36 +0000 (15:11 +0200)
series
sethost.patch [new file with mode: 0644]

diff --git a/series b/series
index a1905d3b424cce4163e969d54265eb9376e9ac87..8f8d4df3a9d28b1d4022d4ac9e499e42770b7167 100644 (file)
--- a/series
+++ b/series
@@ -1,3 +1,4 @@
+sethost.patch
 realusernamesethost.patch
 sethostoldcode.patch
 sethostnewhostmask.patch
diff --git a/sethost.patch b/sethost.patch
new file mode 100644 (file)
index 0000000..cc800bf
--- /dev/null
@@ -0,0 +1,1553 @@
+sethost patch: minor overhaul of sethost system
+
+this patch replaces a number of other patches:
+autosethost.patch invalidatebanssethost.patch issethost.patch
+sethostnewhostmask.patch sethostoldcode.patch sethostprotocolviolation.patch
+showumodehtoclients.patch ulined.patch
+
+makes usermode +h visible to clients
+
+removes HasSetHost() macro (was same as IsSetHost())
+
+removed 'Using S-line privilege' message on connect
+client is informed of spoofhost by 396 RPL_HOSTHIDDEN (host :is now your hidden host)
+
+change syntax to "SETHOST [<user>@]<host> [<password>]"
+
+disallow client to use MODE +h [<user>@]<host> [<pass>] (bug: mode +h took 2 parameters, even from remote users!)
+
+sethost can only be unset with "MODE <nick> -h"
+
+sethost #N no longer supported (N being the Nth configured spoof block)
+
+remote sethost can now be undone by using 0 as username and host,
+but only when the user has an account set and is allowed by server settings to set mode +x,
+to avoid revealing the real host out of the blue
+
+SETHOST now same as OPER
+modes h and o can only be set with SETHOST and OPER
+modes h and o are visible to clients
+modes h and o can only be unset with MODE <nick> -ho
+
+user sethosts are now also checked against their user@host/ip (and not just the password)
+
+applying the sethost and syncing of clients (quit user, rejoin user, restore modes on user) is now done in one place
+
+remote sethost can come from any server, does not need to have a Uworld block or be a service (ACCOUNT doesnt require that either)
+
+STATS s
+show if spoofhost is valid or not (S = valid, s = invalid)
+removed numbering (not required anymore?)
+merged showing of user@host
+
+diff -r c6f3803ee169 include/client.h
+--- a/include/client.h
++++ b/include/client.h
+@@ -90,7 +90,7 @@
+ #define FlagClr(set,flag) ((set)->bits[FLAGSET_INDEX(flag)] &= ~FLAGSET_MASK(flag))
+ /** String containing valid user modes, in no particular order. */
+-#define infousermodes "dioOswkgxRXInP"
++#define infousermodes "dioOswkghxRXInP"
+ /** Character to indicate no oper name available */
+ #define NOOPERNAMECHARACTER '-'
+@@ -627,7 +627,6 @@
+ #define HasHiddenHost(x)        (IsHiddenHost(x) && IsAccount(x))
+ /** Return non-zero if the client is using a spoofhost */
+ #define IsSetHost(x)            HasFlag(x, FLAG_SETHOST)
+-#define HasSetHost(x)           (IsSetHost(x))
+ /** Mark a client as having an in-progress net.burst. */
+ #define SetBurst(x)             SetFlag(x, FLAG_BURST)
+diff -r c6f3803ee169 include/numeric.h
+--- a/include/numeric.h
++++ b/include/numeric.h
+@@ -314,7 +314,7 @@
+ /*      RPL_NOUSERS          395        Dalnet/EFnet/IRCnet */
+ #define RPL_HOSTHIDDEN       396      /* UMODE +x completed succesfuly */
+ #define RPL_STATSSLINE       398      /* QuakeNet extension -froo */
+-#define RPL_USINGSLINE       399      /* QuakeNet extension -froo */
++/*      RPL_USINGSLINE       399         QuakeNet extension -froo */
+ /*
+  * Errors are in the range from 400-599 currently and are grouped by what
+diff -r c6f3803ee169 include/s_conf.h
+--- a/include/s_conf.h
++++ b/include/s_conf.h
+@@ -205,7 +205,8 @@
+ extern void conf_add_sline(const char* const* fields, int count);
+ extern void clear_slines(void);
+-extern int conf_check_slines(struct Client *cptr);
++extern struct sline *find_spoofblock(struct Client *cptr, char *spoofhost, char *password);
++extern int apply_spoofblock(struct Client *cptr);
+ extern void free_spoofhost(struct sline *spoof);
+ extern void yyerror(const char *msg);
+diff -r c6f3803ee169 include/s_user.h
+--- a/include/s_user.h
++++ b/include/s_user.h
+@@ -79,15 +79,15 @@
+ extern int set_nick_name(struct Client* cptr, struct Client* sptr,
+                          const char* nick, int parc, char* parv[]);
+ extern void send_umode_out(struct Client* cptr, struct Client* sptr,
+-                          struct Flags* old, int prop);
++                          struct Flags* old, int prop, int alreadyh);
+ extern int whisper(struct Client* source, const char* nick,
+                    const char* channel, const char* text, int is_notice);
+ extern void send_user_info(struct Client* to, char* names, int rpl,
+                            InfoFormatter fmt);
+ extern int hide_hostmask(struct Client *cptr, unsigned int flags);
+-extern int set_hostmask(struct Client *cptr, char *hostmask, char *password);
+-extern int is_hostmask(char *word);
++extern int set_hostmask(struct Client *sptr, char *user, char *host);
++extern int is_validsethost(char *mask, int maxlen);
+ extern int set_user_mode(struct Client *cptr, struct Client *sptr,
+                          int parc, char *parv[], int allow_modes);
+ extern int is_silenced(struct Client *sptr, struct Client *acptr);
+@@ -102,7 +102,7 @@
+ extern struct Client* next_client(struct Client* next, const char* ch);
+ extern char *umode_str(struct Client *cptr, int type);
+ extern void send_umode(struct Client *cptr, struct Client *sptr,
+-                       struct Flags *old, int sendset, int opernames);
++                       struct Flags *old, int sendset, int opernames, int alreadyh);
+ extern void set_snomask(struct Client *, unsigned int, int);
+ extern int is_snomask(char *);
+ extern int check_target_limit(struct Client *sptr, void *target, const char *name,
+diff -r c6f3803ee169 ircd/channel.c
+--- a/ircd/channel.c
++++ b/ircd/channel.c
+@@ -384,12 +384,12 @@
+   ircd_ntoa_r(iphost, &cli_ip(cptr));
+   /* sr is real host if +h */
+-  if (HasSetHost(cptr))
++  if (IsSetHost(cptr))
+     sr = cli_user(cptr)->realhost;
+   /* if +x and not +h sa is real host, if -x or +h sa is the account host */
+   if (IsAccount(cptr)) {
+-    if (HasHiddenHost(cptr) && !HasSetHost(cptr)) {
++    if (HasHiddenHost(cptr) && !IsSetHost(cptr)) {
+       sa = cli_user(cptr)->realhost;
+     } else {
+       ircd_snprintf(0, tmphost, HOSTLEN, "%s.%s",
+diff -r c6f3803ee169 ircd/m_oper.c
+--- a/ircd/m_oper.c
++++ b/ircd/m_oper.c
+@@ -189,7 +189,7 @@
+     
+     set_snomask(sptr, SNO_OPERDEFAULT, SNO_ADD);
+     cli_max_sendq(sptr) = 0; /* Get the sendq from the oper's class */
+-    send_umode_out(cptr, sptr, &old_mode, HasPriv(sptr, PRIV_PROPAGATE));
++    send_umode_out(cptr, sptr, &old_mode, HasPriv(sptr, PRIV_PROPAGATE), 0);
+     send_reply(sptr, RPL_YOUREOPER);
+     sendto_opmask_butone(0, SNO_OLDSNO, "%s (%s@%s) is now operator (%c) as %s",
+diff -r c6f3803ee169 ircd/m_sethost.c
+--- a/ircd/m_sethost.c
++++ b/ircd/m_sethost.c
+@@ -82,174 +82,222 @@
+ #include "config.h"
+ #include "client.h"
++#include "ircd_features.h"
++#include "ircd_log.h"
+ #include "ircd_reply.h"
+ #include "ircd_string.h"
+ #include "ircd_snprintf.h"
+-#include "ircd_features.h"
++#include "msg.h"
+ #include "msgq.h"
+ #include "numeric.h"
+ #include "s_conf.h"
++#include "s_debug.h"
+ #include "s_user.h"
+-#include "s_debug.h"
+ #include "send.h"
+ #include "struct.h"
+ #include "numnicks.h"
+-#include "channel.h"
+-#include "msg.h"
+-
+-#include <assert.h>
+ #include <stdlib.h>
+-/*
++/**
+  * m_sethost - generic message handler
+  *
+- * mimic old lain syntax:
++ * SETHOST [<user>@]<host> [<password>]
+  *
+- * (Oper) /SETHOST ident host.cc [quit-message]
+- * (User) /SETHOST host.cc password
+- * (Both) /SETHOST undo
++ * parv[0] = sender prefix
++ * parv[1] = [user@]host
++ * parv[2] = password
+  *
+- * check for undo, prepend parv w. <nick> -h or +h
++ * "MODE <nick> -h" to remove sethost
++ *
+  */
+ int m_sethost(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
+ {
+-  char hostmask[USERLEN + HOSTLEN + 2];
+-  char curhostmask[USERLEN + HOSTLEN + 2];
++  int alreadyh = 0;
++  int freeform = 0;
++  char *hostmask;
++  char *user = NULL;
++  char *host;
++  char *password = NULL;
++  struct Flags setflags;
++  struct sline *sconf;
+-  struct Flags setflags;
++  /* disabled */
++  if (!feature_bool(FEAT_SETHOST))
++    return send_reply(sptr, ERR_DISABLED, "SETHOST");
+-  /* Back up the flags first */
++  /* disabled for ordinary users */
++  if (!IsAnOper(sptr) && !feature_bool(FEAT_SETHOST_USER))
++    return send_reply(sptr, ERR_NOPRIVILEGES);
++
++  /* need hostmask parameter
++   *   and need password parameter from an ordinary user
++   */
++  if (parc < 2 || EmptyString(parv[1]) || (parc < 3 && !IsAnOper(sptr)))
++    return need_more_params(sptr, "SETHOST");
++
++  hostmask = parv[1];
++
++  /* get user and host */
++  if ((host = strrchr(hostmask, '@'))) {
++    *host++ = '\0';
++    user = hostmask;
++  }
++  else
++    host = hostmask;
++
++  /* got a pasword */
++  if (parc > 2 && !EmptyString(parv[2]))
++    password = parv[2];
++
++  /* freeform - do not bother with password */
++  if (IsAnOper(sptr) && HasPriv(sptr, PRIV_FREEFORM)) {
++    freeform = 1;
++    password = NULL;
++  }
++
++  /* check if user and host are valid */
++  if ((user && !is_validsethost(user, USERLEN)) ||
++               !is_validsethost(host, HOSTLEN))
++    return send_reply(sptr, ERR_BADHOSTMASK, user ? user : "", user ? "@" : "", host);
++
++  /* not freeform */
++  if (!freeform) {
++
++    /* find the spoof block */
++    if (!(sconf = find_spoofblock(sptr, host, password)))
++      return send_reply(sptr, ERR_HOSTUNAVAIL, user ? user : "", user ? "@" : "", host);
++    else
++     host = (char *)sconf->spoofhost;
++
++     /* only freeform allowed to specify user */
++     user = NULL;
++  }
++
++  /* backup flags */
+   setflags = cli_flags(sptr);
+-  if (parc < 2)
+-    return need_more_params(sptr, "SETHOST");
++  /* already +h, clear flag to force mode +h to be sent out again */
++  if (IsSetHost(sptr)) {
++    FlagClr(&setflags, FLAG_SETHOST);
++    alreadyh = 1;
++  }
+-  if (0 == ircd_strcmp("undo", parv[1])) {
+-    set_hostmask(sptr, NULL, NULL);
+-  } else {
+-    if (parc<3)
+-      return need_more_params(sptr, "SETHOST");
+-    if (IsAnOper(sptr)) {
+-      ircd_snprintf(0, hostmask, USERLEN + HOSTLEN + 2, "%s@%s", parv[1], parv[2]);
+-      if (!is_hostmask(hostmask)) {
+-      send_reply(sptr, ERR_BADHOSTMASK, hostmask);
+-      return 0;
+-      }
+-      if (IsSetHost(sptr) || IsAccount(sptr)) {
+-        ircd_snprintf(0, curhostmask, USERLEN + HOSTLEN + 2, "%s@%s", sptr->cli_user->username, sptr->cli_user->host);
+-        if (0 == strcmp(hostmask, curhostmask)) {
+-          send_reply(sptr, RPL_HOSTHIDDEN, curhostmask);
+-          return 0; 
+-        }
+-      }
+-      if (set_hostmask(sptr, hostmask, NULL))
+-              FlagClr(&setflags, FLAG_SETHOST);
+-    } else {
+-      if (!is_hostmask(parv[1])) {
+-      send_reply(sptr, ERR_BADHOSTMASK, parv[1]);
+-      return 0;
+-      }
+-      if (IsSetHost(sptr) || IsAccount(sptr)) {
+-        if (0 == strcmp(parv[1], sptr->cli_user->host)) {
+-          send_reply(sptr, RPL_HOSTHIDDEN, parv[1]);
+-          return 0;
+-        }
+-      }
+-      if (set_hostmask(sptr, parv[1], parv[2]))
+-        FlagClr(&setflags, FLAG_SETHOST);
+-    }
+-  }  
++  /* check if new sethost is different from before */
++  if (IsSetHost(sptr) && 
++      (!user || strcmp(cli_user(sptr)->username, user) == 0) &&
++                strcmp(cli_user(sptr)->host, host) == 0)
++    return send_reply(sptr, RPL_HOSTHIDDEN, user ? user : "", user ? "@" : "", host);
+-  send_umode_out(cptr, sptr, &setflags, 0);
++  /* do it */
++  set_hostmask(sptr, user, host);
++
++  /* log freeform */
++  if (freeform)
++    log_write(LS_SETHOST, L_NOTICE, 0,
++      "SETHOST (%s@%s) by (%#R) using freeform",
++      cli_user(cptr)->username, cli_user(cptr)->host, cptr);
++
++  /* send the mode out */
++  send_umode_out(cptr, sptr, &setflags, 0, alreadyh);
++
+   return 0;
+ }
+-/*
++
++/**
+  * ms_sethost - sethost server message handler
+  *
+  * parv[0] = sender prefix
+  * parv[1] = target user numeric
+- * parv[2] = target user's new ident
+- * parv[3] = target user's new host 
++ * parv[2] = spoof username
++ * parv[3] = spoof host
++ *
++ * undo sethost when spoof username and host are 0
++ *
+  */
++ /* TODO: IsRemoteSetHost() */
+ int ms_sethost(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
+ {
+-  struct Client *target;
+-  char hostmask[USERLEN + HOSTLEN + 2];
+-  struct Membership *chan;
++  int alreadyh = 0;
++  char *target;
++  char *user;
++  char *host;
++  struct Client *acptr;
+   struct Flags setflags;
++  /* check paramaters */
+   if (parc < 4)
+     return need_more_params(sptr, "SETHOST");
++  target = parv[1];
++  user = parv[2];
++  host = parv[3];
++
++  /* not from a server */
+   if (!IsServer(sptr))
+-    return protocol_violation(cptr, "SETHOST from non-server %s",
+-                              cli_name(sptr));
++    return protocol_violation(cptr, "SETHOST from non-server %s", cli_name(sptr));
+-  /* Locate our target user; ignore the message if we can't */
+-  if(!(target = findNUser(parv[1])))
++  /* find user */
++  if(!(acptr = findNUser(target)))
+     return 0;
+-  /* Fake host assignments must be from services */
+-  if (!find_conf_byhost(cli_confs(sptr), cli_name(sptr), CONF_UWORLD))
+-    return protocol_violation(cptr, "Non-U:lined server %s set fake host on user %s", cli_name(sptr), cli_name(target));
+-
+-  if (!MyConnect(target)) {
+-    sendcmdto_one(sptr, CMD_SETHOST, cli_user(target)->server, "%C %s %s", target,
+-                 parv[2], parv[3]);
++  /* not for my user, pass it along */
++  if (!MyConnect(acptr)) {
++    sendcmdto_one(sptr, CMD_SETHOST, acptr, "%C %s %s", acptr, user, host);
+     return 0;
+   }
+-  /* Back up the flags first */
+-  setflags = cli_flags(target);
+-  FlagClr(&setflags, FLAG_SETHOST);
++  /* backup flags */
++  setflags = cli_flags(acptr);
+-  if (IsSetHost(target) || IsAccount(target)) { 
+-    if ((0 == strcmp(parv[2], target->cli_user->username)) && (0 == strcmp(parv[3], target->cli_user->host))) 
++  /* check user and host are valid */
++  if (!is_validsethost(user, USERLEN) || !is_validsethost(host, HOSTLEN))
++    return protocol_violation(cptr,
++      "Server %C tried to SETHOST user %C with a badmask: %s@%s",
++      sptr, acptr, user, host);
++
++  /* 'user host' is '0 0' - undo sethost */
++  if (user[0] == '0' && user[1] == '\0' &&
++      host[0] == '0' && host[1] == '\0') {
++
++    /* user has no sethost or has no account
++     *
++     * user has +h their host is hidden, do not remove it
++     *   unless the user has an account set
++     *     we should not out of the blue expose the real host
++     */
++    if (!IsSetHost(acptr) || !IsAccount(acptr))
+       return 0;
++
++    /* user not +x and not allowed to set it */
++    if (!IsHiddenHost(acptr) && !feature_bool(FEAT_HOST_HIDING))
++      return 0;
++
++    /* set +x */
++    SetHiddenHost(acptr);
++    user = NULL;
++    host = NULL;
+   }
+-  ircd_snprintf(0, hostmask, USERLEN + HOSTLEN + 2, "%s@%s", parv[2], parv[3]);
+-  if (!is_hostmask(hostmask))
+-    return protocol_violation(cptr, "Bad Host mask %s for user %s", hostmask, cli_name(target));
++  /* check if new sethost is different from before */
++  else if (IsSetHost(acptr) && 
++      strcmp(cli_user(acptr)->username, user) == 0 &&
++      strcmp(cli_user(acptr)->host, host) == 0)
++    return 0;
+-  sendcmdto_common_channels_butone(target, CMD_QUIT, target, ":Host change");
+-
+-  /* Assign and propagate the fakehost */
+-  SetSetHost(target);
+-  ircd_strncpy(cli_user(target)->username, parv[2], USERLEN);
+-  ircd_strncpy(cli_user(target)->host, parv[3], HOSTLEN);
+-  
+-  send_reply(target, RPL_HOSTHIDDEN, hostmask);
+-
+-  /*
+-   * Go through all channels the client was on, rejoin him
+-   * and set the modes, if any
+-   */
+-  for (chan = cli_user(target)->channel; chan; chan = chan->next_channel) {
+-    if (IsZombie(chan))
+-      continue;
+-    /* If this channel has delayed joins and the user has no modes, just set
+-     * the delayed join flag rather than showing the join, even if the user
+-     * was visible before */
+-    if (!IsChanOp(chan) && !HasVoice(chan)
+-        && (chan->channel->mode.mode & MODE_DELJOINS)) {
+-      SetDelayedJoin(chan);
+-    } else {
+-      sendcmdto_channel_butserv_butone(target, CMD_JOIN, chan->channel, target, 0,
+-        "%H", chan->channel);
+-    }
+-    if (IsChanOp(chan) && HasVoice(chan)) {
+-      sendcmdto_channel_butserv_butone(&his, CMD_MODE, chan->channel, target, 0,
+-        "%H +ov %C %C", chan->channel, target, target);
+-    } else if (IsChanOp(chan) || HasVoice(chan)) {
+-      sendcmdto_channel_butserv_butone(&his, CMD_MODE, chan->channel, target, 0,
+-        "%H +%c %C", chan->channel, IsChanOp(chan) ? 'o' : 'v', target);
+-    }
++  /* already +h, clear flag to force mode +h to be sent out again */
++  else if (IsSetHost(acptr)) {
++    FlagClr(&setflags, FLAG_SETHOST);
++    alreadyh = 1;
+   }
+-  send_umode_out(target, target, &setflags, 0);
++  /* do it */
++  set_hostmask(acptr, user, host);
++
++  /* send out the mode */
++  send_umode_out(acptr, acptr, &setflags, 0, alreadyh);
++
+   return 0;
+ }
+diff -r c6f3803ee169 ircd/m_userhost.c
+--- a/ircd/m_userhost.c
++++ b/ircd/m_userhost.c
+@@ -104,7 +104,7 @@
+              * of +x.  If an oper wants the real host, he should go to
+              * /whois to get it.
+              */
+-            (HasHiddenHost(cptr) || HasSetHost(cptr)) && (sptr != cptr) ?
++            (HasHiddenHost(cptr) || IsSetHost(cptr)) && (sptr != cptr) ?
+             cli_user(cptr)->host : cli_user(cptr)->realhost);
+ }
+diff -r c6f3803ee169 ircd/m_userip.c
+--- a/ircd/m_userip.c
++++ b/ircd/m_userip.c
+@@ -106,7 +106,7 @@
+              * of +x.  If an oper wants the real IP, he should go to
+              * /whois to get it.
+              */
+-            ((HasHiddenHost(cptr) || HasSetHost(cptr) || feature_bool(FEAT_HIS_USERIP)) && (sptr != cptr)) ?
++            ((HasHiddenHost(cptr) || IsSetHost(cptr) || feature_bool(FEAT_HIS_USERIP)) && (sptr != cptr)) ?
+             feature_str(FEAT_HIDDEN_IP) :
+             ircd_ntoa(&cli_ip(cptr)));
+ }
+diff -r c6f3803ee169 ircd/m_who.c
+--- a/ircd/m_who.c
++++ b/ircd/m_who.c
+@@ -394,14 +394,14 @@
+               && ((!(matchsel & WHO_FIELD_HOS))
+               || matchexec(cli_user(acptr)->host, mymask, minlen))
+               && ((!(matchsel & WHO_FIELD_HOS))
+-              || !HasSetHost(acptr)
++              || !IsSetHost(acptr)
+             || !HasHiddenHost(acptr)
+             || !IsAnOper(sptr)
+               || matchexec(cli_user(acptr)->realhost, mymask, minlen))
+               && ((!(matchsel & WHO_FIELD_REN))
+               || matchexec(cli_info(acptr), mymask, minlen))
+               && ((!(matchsel & WHO_FIELD_NIP))
+-              || ((HasHiddenHost(acptr) || HasSetHost(acptr)) && !IsAnOper(sptr))
++              || ((HasHiddenHost(acptr) || IsSetHost(acptr)) && !IsAnOper(sptr))
+               || !ipmask_check(&cli_ip(acptr), &imask, ibits))
+               && ((!(matchsel & WHO_FIELD_ACC))
+               || matchexec(cli_user(acptr)->account, mymask, minlen)))
+@@ -433,14 +433,14 @@
+             && ((!(matchsel & WHO_FIELD_HOS))
+             || matchexec(cli_user(acptr)->host, mymask, minlen))
+             && ((!(matchsel & WHO_FIELD_HOS))
+-            || !HasSetHost(acptr)
++            || !IsSetHost(acptr)
+           || !HasHiddenHost(acptr)
+           || !IsAnOper(sptr)
+             || matchexec(cli_user(acptr)->realhost, mymask, minlen))
+             && ((!(matchsel & WHO_FIELD_REN))
+             || matchexec(cli_info(acptr), mymask, minlen))
+             && ((!(matchsel & WHO_FIELD_NIP))
+-            || ((HasHiddenHost(acptr) || HasSetHost(acptr)) && !IsAnOper(sptr))
++            || ((HasHiddenHost(acptr) || IsSetHost(acptr)) && !IsAnOper(sptr))
+             || !ipmask_check(&cli_ip(acptr), &imask, ibits))
+             && ((!(matchsel & WHO_FIELD_ACC))
+             || matchexec(cli_user(acptr)->account, mymask, minlen)))
+diff -r c6f3803ee169 ircd/m_whois.c
+--- a/ircd/m_whois.c
++++ b/ircd/m_whois.c
+@@ -214,7 +214,7 @@
+     if (IsAccount(acptr))
+       send_reply(sptr, RPL_WHOISACCOUNT, name, user->account);
+-    if ((HasHiddenHost(acptr) || HasSetHost(acptr)) && ((IsAnOper(sptr) && HasPriv(sptr, PRIV_USER_PRIVACY)) || acptr == sptr))
++    if ((HasHiddenHost(acptr) || IsSetHost(acptr)) && ((IsAnOper(sptr) && HasPriv(sptr, PRIV_USER_PRIVACY)) || acptr == sptr))
+       send_reply(sptr, RPL_WHOISACTUALLY, name, user->realusername,
+                  user->realhost, ircd_ntoa(&cli_ip(acptr)));
+diff -r c6f3803ee169 ircd/s_conf.c
+--- a/ircd/s_conf.c
++++ b/ircd/s_conf.c
+@@ -52,6 +52,7 @@
+ #include "s_bsd.h"
+ #include "s_debug.h"
+ #include "s_misc.h"
++#include "s_user.h"
+ #include "send.h"
+ #include "struct.h"
+ #include "sys.h"
+@@ -1239,44 +1240,162 @@
+  * -froo 1/2003
+  *
+  */
++/** 
++ * find_spoofblock
++ *
++ *  Find matching spoof block for a user for the given spoofhost and password
++ *
++ *  @param cptr       User wanting to get a spoof host.
++ *  @param spoofhost  Spoof host to look for.
++ *  @param password   Password given by user (can be NULL).
++ *
++ *  @return pointer to the matching spoofblock is found, else NULL
++ *
++ */
++struct sline *find_spoofblock(struct Client *cptr, char *spoofhost, char *password) {
++  struct sline *sconf;
++  int result = 0;
++  int r = 0;
++  Debug((DEBUG_INFO, "find_spoofblock() cptr=%C spoofhost=%s password=%s",
++    cptr, spoofhost, password));
++
++  for (sconf = GlobalSList; sconf; sconf = sconf->next) {
++
++    /* check result of previous loop */
++    if (r > result)
++      result = r;
++
++    /* check spoofhost */
++    if (strcasecmp(sconf->spoofhost, spoofhost) != 0)
++      continue;
++    r = 1;
++
++    /* check cptr's host */
++    /* cidr mask */
++    if (sconf->flags == SLINE_FLAGS_IP) {
++      if (!ipmask_check(&(cli_ip(cptr)), &(sconf->address), sconf->bits))
++        continue;
++    }
++
++    /* hostname */
++    else if (sconf->flags == SLINE_FLAGS_HOSTNAME) {
++      if ((match(sconf->realhost, cli_sockhost(cptr)) != 0) &&
++          (match(sconf->realhost, cli_sock_ip(cptr)) != 0))      /* wildcarded IP address */
++        continue;
++    }
++
++    /* not cidr or hostname.. */ 
++    else
++      continue;
++    r = 2;
++
++    /* check cptr's username */
++    if (!EmptyString(sconf->username) && match(sconf->username, cli_user(cptr)->realusername) != 0)
++      continue;
++    r = 3;
++
++    /* check password */
++    if (password) {
++      if (EmptyString(sconf->passwd) || strcmp(sconf->passwd, password) != 0)
++        continue;
++    }
++
++    /* no password, but need one for this spoofblock */
++    else if (!EmptyString(sconf->passwd))
++      continue;
++
++    /* got one */
++    log_write(LS_SETHOST, L_INFO, 0,
++      "SETHOST (%s) by (%#R): ", sconf->spoofhost, cptr);
++    return sconf;
++  }
++
++  /* TODO: lookup LOG stuff */
++  /* TODO: L_INFO LOG_NOSNOTICE */
++  /* log of best result we got */
++  if (result == 0)
++    log_write(LS_SETHOST, L_INFO, 0,
++      "SETHOST (%s) by (%#R): no such Spoof block", spoofhost, cptr);
++  if (result == 1)
++    log_write(LS_SETHOST, L_INFO, 0,
++      "SETHOST (%s) by (%#R): IP / host mismatch", spoofhost, cptr); 
++  if (result == 2)
++    log_write(LS_SETHOST, L_INFO, 0,
++      "SETHOST (%s) by (%#R): username mismatch", spoofhost, cptr);
++  if (result == 3)
++    log_write(LS_SETHOST, L_INFO, 0,
++      "SETHOST (%s) by (%#R): %s", spoofhost, cptr,
++      password ? "password mismatch" : "password required");
++  return NULL;
++}
++
++
++
++/** 
++ * apply_spoofblock
++ *
++ * @param cptr  User to apply Spoof block to on connect
++ * 
++ * @return 1 for success, else 0
++ * 
++ */
+ int
+-conf_check_slines(struct Client *cptr)
++apply_spoofblock(struct Client *cptr)
+ {
+   struct sline *sconf;
+   char *hostonly;
++  /* disabled */
++  if(!feature_bool(FEAT_SETHOST_AUTO))
++    return 0;
++
++  /* go over spoof blocks */
+   for (sconf = GlobalSList; sconf; sconf = sconf->next) {
++
++    /* check IP */
+     if (sconf->flags == SLINE_FLAGS_IP) {
+       if (!ipmask_check(&(cli_ip(cptr)), &(sconf->address), sconf->bits))
+         continue;
++
++    /* check host */
+     } else if (sconf->flags == SLINE_FLAGS_HOSTNAME) {
+         if ((match(sconf->realhost, cli_sockhost(cptr)) != 0) &&
+-           (match(sconf->realhost, cli_sock_ip(cptr)) != 0))  /* wildcarded IP address */
++            (match(sconf->realhost, cli_sock_ip(cptr)) != 0))   /* wildcarded IP address */
+           continue;
+-    } else {
+-        continue;
+-    }
++    } else
++      continue;
+-    if (match(sconf->username, cli_user(cptr)->username) == 0) {
+-     /* Ignore user part if u@h. */
+-     if ((hostonly = strchr(sconf->spoofhost, '@')))
+-        hostonly++;
+-      else
+-        hostonly = sconf->spoofhost;
++    /* check username */
++    if (match(sconf->username, cli_user(cptr)->username) != 0)
++      continue;
+-      if(!*hostonly)
+-        continue;
++    /* Ignore user part if u@h. */
++    if ((hostonly = strchr(sconf->spoofhost, '@')))
++      hostonly++;
++    else
++      hostonly = sconf->spoofhost;
+-      ircd_strncpy(cli_user(cptr)->host, hostonly, HOSTLEN);
+-      log_write(LS_USER, L_INFO, LOG_NOSNOTICE, "S-Line (%s@%s) by (%#R)",
+-          cli_user(cptr)->username, hostonly, cptr);
+-      return 1;
+-    }
++    /* invalid */
++    if (!is_validsethost(hostonly, HOSTLEN))
++      continue;
++
++    /* do it and log */
++    set_hostmask(cptr, NULL, hostonly);
++    /* LOG_NOSNOTICE */
++    log_write(LS_USER, L_INFO, 0, "AUTO SETHOST %s on %s",
++      hostonly, get_client_name(cptr, SHOW_IP));
++    return 1;
+   }
+   return 0;
+ }
++
++
++/**
++ *
++ *
++ */
+ void free_spoofhost(struct sline *spoof) {
+       MyFree(spoof->spoofhost);
+       MyFree(spoof->passwd);
+diff -r c6f3803ee169 ircd/s_err.c
+--- a/ircd/s_err.c
++++ b/ircd/s_err.c
+@@ -824,13 +824,13 @@
+ /* 395 */
+   { 0 },
+ /* 396 */
+-  { RPL_HOSTHIDDEN, "%s :is now your hidden host", "396" },
++  { RPL_HOSTHIDDEN, "%s%s%s :is now your hidden host", "396" },
+ /* 397 */
+   { 0 },
+ /* 398 */
+-  { RPL_STATSSLINE, "%d %s %s %s %s", "398" },
++  { RPL_STATSSLINE, "%c %s %s %s%s%s", "398" },
+ /* 399 */
+-  { RPL_USINGSLINE, ":Using S-line privilege", "399" },
++  { 0 },
+ /* 400 */
+   { 0 },
+ /* 401 */
+@@ -1092,9 +1092,9 @@
+ /* 529 */
+   { 0 },
+ /* 530 */
+-  { ERR_BADHOSTMASK, "%s :Invalid username/hostmask", "530" },
++  { ERR_BADHOSTMASK, "%s%s%s :Invalid username/hostmask", "530" },
+ /* 531 */
+-  { ERR_HOSTUNAVAIL, "%s :sethost not found", "531" },
++  { ERR_HOSTUNAVAIL, "%s%s%s :Sethost not found", "531" },
+ /* 532 */
+   { 0 },
+ /* 533 */
+diff -r c6f3803ee169 ircd/s_stats.c
+--- a/ircd/s_stats.c
++++ b/ircd/s_stats.c
+@@ -400,41 +400,37 @@
+   }
+ }
++/* TODO: */
++/** List spoof blocks.
++ * @param[in] to Client requesting statistics.
++ * @param[in] sd Stats descriptor for request (ignored).
++ * @param[in] param Filter for spoofhost names.
++ */
+ static void
+ stats_sline(struct Client* to, const struct StatDesc* sd, char* param)
+ {
+-  int y = 1, i = 1;
+   struct sline *sline;
+   if (IsAnOper(to))
+-    send_reply(to, SND_EXPLICIT | RPL_TEXT, "# Type Spoofhost Realhost Ident");
++    send_reply(to, SND_EXPLICIT | RPL_TEXT, "S Type Spoofhost Hostmask");
+   else
+-    send_reply(to, SND_EXPLICIT | RPL_TEXT, "# Type Spoofhost");
++    send_reply(to, SND_EXPLICIT | RPL_TEXT, "S Type Spoofhost");
+   for (sline = GlobalSList; sline; sline = sline->next) {
+-    if (param && match(param, sline->spoofhost)) { /* narrow search */
+-      if (IsAnOper(to))
+-          y++;
+-      else
+-        if (!EmptyString(sline->passwd))
+-          y++;
++
++    if (param && match(param, sline->spoofhost))
+       continue;
+-    }
+-    if (IsAnOper(to)) {
+-      send_reply(to, RPL_STATSSLINE, (param) ? y : i, 
+-         (EmptyString(sline->passwd)) ? "oper" : "user",
+-         sline->spoofhost, 
+-         (EmptyString(sline->realhost)) ? "" : sline->realhost,
+-         (EmptyString(sline->username)) ? "" : sline->username);
+-      i++;
+-    } else {
+-      if (!EmptyString(sline->passwd)) {
+-        send_reply(to, RPL_STATSSLINE, (param) ? y : i, "user", sline->spoofhost,
+-           "", "", "");
+-        i++;
+-      }
+-    }
++    if (IsAnOper(to))
++      send_reply(to, RPL_STATSSLINE,
++        is_validsethost(sline->spoofhost, HOSTLEN) ? 'S' : 's',   /* valid show S else s */ 
++        (EmptyString(sline->passwd)) ? "Oper" : "User",
++        sline->spoofhost,
++        (EmptyString(sline->username)) ? "" : sline->username,
++        (!EmptyString(sline->username)) ? "@" : "",               /* always place a @ after the username */
++        (EmptyString(sline->realhost)) ? "" : sline->realhost);
++    else if (!EmptyString(sline->passwd) && is_validsethost(sline->spoofhost, HOSTLEN))
++      send_reply(to, RPL_STATSSLINE, 'S', "User", sline->spoofhost, "", "", "");
+   }
+ }
+diff -r c6f3803ee169 ircd/s_user.c
+--- a/ircd/s_user.c
++++ b/ircd/s_user.c
+@@ -73,9 +73,6 @@
+ #include <string.h>
+ #include <sys/stat.h>
+-static char *IsVhost(char *hostmask, int oper);
+-static char *IsVhostPass(char *hostmask);
+-
+ /** Count of allocated User structures. */
+ static int userCount = 0;
+@@ -373,13 +370,6 @@
+     if (feature_bool(FEAT_AUTOINVISIBLE))
+       SetInvisible(sptr);
+-    
+-    if(feature_bool(FEAT_SETHOST_AUTO)) {
+-      if (conf_check_slines(sptr)) {
+-        send_reply(sptr, RPL_USINGSLINE);
+-        SetSetHost(sptr);
+-      }
+-    }
+     SetUser(sptr);
+     cli_handler(sptr) = CLIENT_HANDLER;
+@@ -411,6 +401,10 @@
+                            cli_info(sptr), NumNick(cptr) /* two %s's */);
+     IPcheck_connect_succeeded(sptr);
++
++    /* TODO: */
++    /* apply auto sethost if needed */
++    apply_spoofblock(sptr);
+   }
+   else {
+     struct Client *acptr = user->server;
+@@ -519,7 +513,7 @@
+     else
+       FlagClr(&flags, FLAG_ACCOUNT);
+     client_set_privs(sptr, NULL);
+-    send_umode(cptr, sptr, &flags, ALL_UMODES, 0);
++    send_umode(cptr, sptr, &flags, ALL_UMODES, 0, 0);
+     if ((cli_snomask(sptr) != SNO_DEFAULT) && HasFlag(sptr, FLAG_SERVNOTICE))
+       send_reply(sptr, RPL_SNOMASK, cli_snomask(sptr), cli_snomask(sptr));
+   }
+@@ -874,14 +868,15 @@
+  * @param[in] sptr Client who sent us the mode change message.
+  * @param[in] old Prior set of user flags.
+  * @param[in] prop If non-zero, also include FLAG_OPER.
++ * @param[in] alreadyh Client is already +h, do not show +h change
+  */
+ void send_umode_out(struct Client *cptr, struct Client *sptr,
+-                    struct Flags *old, int prop)
++                    struct Flags *old, int prop, int alreadyh)
+ {
+   int i;
+   struct Client *acptr;
+-  send_umode(NULL, sptr, old, prop ? SEND_UMODES : SEND_UMODES_BUT_OPER, 0);
++  send_umode(NULL, sptr, old, prop ? SEND_UMODES : SEND_UMODES_BUT_OPER, 0, 0);
+   for (i = HighestFd; i >= 0; i--)
+   {
+@@ -890,7 +885,7 @@
+         sendcmdto_one(sptr, CMD_MODE, acptr, "%s %s", cli_name(sptr), umodeBuf);
+   }
+-  send_umode(NULL, sptr, old, prop ? SEND_UMODES : SEND_UMODES_BUT_OPER, 1);
++  send_umode(NULL, sptr, old, prop ? SEND_UMODES : SEND_UMODES_BUT_OPER, 1, 0);
+   for (i = HighestFd; i >= 0; i--)
+   {
+@@ -900,7 +895,7 @@
+   }
+   if (cptr && MyUser(cptr))
+-    send_umode(cptr, sptr, old, ALL_UMODES, 0);
++    send_umode(cptr, sptr, old, ALL_UMODES, 0, alreadyh);
+ }
+@@ -965,7 +960,7 @@
+   }
+   SetFlag(cptr, flag);
+-  if (!HasFlag(cptr, FLAG_HIDDENHOST) || !HasFlag(cptr, FLAG_ACCOUNT) || HasSetHost(cptr))
++  if (!HasFlag(cptr, FLAG_HIDDENHOST) || !HasFlag(cptr, FLAG_ACCOUNT) || IsSetHost(cptr))
+     return 0;
+   sendcmdto_common_channels_butone(cptr, CMD_QUIT, cptr, ":Registered");
+@@ -974,7 +969,7 @@
+   /* ok, the client is now fully hidden, so let them know -- hikari */
+   if (MyConnect(cptr))
+-   send_reply(cptr, RPL_HOSTHIDDEN, cli_user(cptr)->host);
++   send_reply(cptr, RPL_HOSTHIDDEN, "", "", cli_user(cptr)->host);
+   /*
+    * Go through all channels the client was on, rejoin him
+@@ -999,201 +994,104 @@
+   return 0;
+ }
++/* TODO: */
+ /*
+  * set_hostmask() - derived from hide_hostmask()
+  *
+  */
+-int set_hostmask(struct Client *cptr, char *hostmask, char *password)
++int set_hostmask(struct Client *cptr, char *user, char *host)
+ {
+-  int restore = 0;
+-  int freeform = 0;
+-  char *host, *new_vhost, *vhost_pass;
+-  char hiddenhost[USERLEN + HOSTLEN + 2];
++
++  int userchange = 0;
++  char hiddenhost[USERLEN + 1 + HOSTLEN + 1];
++  char *msg = "Host change";
+   struct Membership *chan;
+-  Debug((DEBUG_INFO, "set_hostmask() %C, %s, %s", cptr, hostmask, password));
++  assert(0 != cptr);
+-  /* sethost enabled? */
+-  if (MyConnect(cptr) && !feature_bool(FEAT_SETHOST)) {
+-    send_reply(cptr, ERR_DISABLED, "SETHOST");
++  Debug((DEBUG_INFO, "set_hostmask() cptr=%C user=%s host=%s",
++    cptr, user ? user : "<null>", host ? host : "<null>"));
++
++  /* remove sethost, but user has none */
++  if (!host && !IsSetHost(cptr))
+     return 0;
++
++  /* remove sethost, user has +x host, realusername and username are the same
++   * pretend the user just set +x
++   */
++  if (!host && HasHiddenHost(cptr) &&
++      strcmp(cli_user(cptr)->username, cli_user(cptr)->realusername) == 0)
++    msg = "Registered";
++
++  /* quit user */
++  sendcmdto_common_channels_butone(cptr, CMD_QUIT, cptr, ":%s", msg);
++
++  /* remove sethost */
++  if (!host) {
++
++    /* clear flag */
++    ClearSetHost(cptr);
++
++    /* restore user and host */
++    if (HasHiddenHost(cptr))
++      ircd_snprintf(0, cli_user(cptr)->host, HOSTLEN, "%s.%s",
++        cli_user(cptr)->account, feature_str(FEAT_HIDDEN_HOST));
++    else
++      strncpy(cli_user(cptr)->host, cli_user(cptr)->realhost, HOSTLEN);
++    if (MyConnect(cptr) && strcmp(cli_user(cptr)->username, cli_user(cptr)->realusername) != 0)
++      userchange = 1;
++    strncpy(cli_user(cptr)->username, cli_user(cptr)->realusername, USERLEN);
+   }
+-  /* sethost enabled for users? */
+-  if (MyConnect(cptr) && !IsAnOper(cptr) && !feature_bool(FEAT_SETHOST_USER)) {
+-    send_reply(cptr, ERR_NOPRIVILEGES);
+-    return 0;
+-  }
+- 
+-  /* MODE_DEL: restore original hostmask */
+-  if (EmptyString(hostmask)) {
+-    /* is already sethost'ed? and only opers can remove a sethost */
+-    if (IsSetHost(cptr) && IsAnOper(cptr)) {
+-      restore = 1;
+-      sendcmdto_common_channels_butone(cptr, CMD_QUIT, cptr, ":Host change");
+-      /* If they are +rx, we need to return to their +x host, not their "real" host */
+-      if (HasHiddenHost(cptr))
+-        ircd_snprintf(0, cli_user(cptr)->host, HOSTLEN, "%s.%s",
+-          cli_user(cptr)->account, feature_str(FEAT_HIDDEN_HOST));
+-      else
+-        strncpy(cli_user(cptr)->host, cli_user(cptr)->realhost, HOSTLEN);
+-      strncpy(cli_user(cptr)->username, cli_user(cptr)->realusername, USERLEN);
+-      /* log it */
+-      if (MyConnect(cptr))
+-        log_write(LS_SETHOST, L_INFO, LOG_NOSNOTICE,
+-            "SETHOST (%s@%s) by (%#R): restoring real hostmask",
+-            cli_user(cptr)->username, cli_user(cptr)->host, cptr);
+-    } else
+-      return 0;
+-  /* MODE_ADD: set a new hostmask */
+-  } else {
+-    /* chop up ident and host.cc */
+-    if ((host = strrchr(hostmask, '@'))) { /* oper can specifiy ident@host.cc */
+-      *host++ = '\0';
+-      if ( MyConnect(cptr) && (0 == strcmp(host, cli_user(cptr)->host)) && (0 == strcmp(hostmask, cli_user(cptr)->username))) {
+-        ircd_snprintf(0, hiddenhost, HOSTLEN + USERLEN + 2, "%s@%s",
+-            cli_user(cptr)->username, cli_user(cptr)->host);
+-        send_reply(cptr, RPL_HOSTHIDDEN, hiddenhost);
+-        return 0;
+-      }
+-    } else { /* user can only specifiy host.cc [password] */
+-      host = hostmask;
+-      if ( MyConnect(cptr) && (0 == strcmp(host, cli_user(cptr)->host))) {
+-        ircd_snprintf(0, hiddenhost, HOSTLEN + USERLEN + 2, "%s@%s",
+-            cli_user(cptr)->username, cli_user(cptr)->host);
+-        send_reply(cptr, RPL_HOSTHIDDEN, hiddenhost);
+-        return 0;
+-      }
+-    }
+-    /*
+-     * Oper sethost
+-     */
+-    if (MyConnect(cptr)) {
+-      if (IsAnOper(cptr)) {
+-        if ((new_vhost = IsVhost(host, 1)) == NULL) {
+-          if (!HasPriv(cptr, PRIV_FREEFORM)) {
+-            send_reply(cptr, ERR_HOSTUNAVAIL, hostmask);
+-            log_write(LS_SETHOST, L_INFO, LOG_NOSNOTICE,
+-                "SETHOST (%s@%s) by (%#R): no such s-line",
+-                (host != hostmask) ? hostmask : cli_user(cptr)->username, host, cptr);
+-            return 0;
+-          } else /* freeform active, log and go */
+-            freeform = 1;
+-        }
+-        sendcmdto_common_channels_butone(cptr, CMD_QUIT, cptr, ":Host change");
+-        /* set the new ident and host */
+-        if (host != hostmask) /* oper only specified host.cc */
+-          strncpy(cli_user(cptr)->username, hostmask, USERLEN);
+-        strncpy(cli_user(cptr)->host, host, HOSTLEN);
+-        /* log it */
+-        log_write(LS_SETHOST, (freeform) ? L_NOTICE : L_INFO,
+-            (freeform) ? 0 : LOG_NOSNOTICE, "SETHOST (%s@%s) by (%#R)%s",
+-            cli_user(cptr)->username, cli_user(cptr)->host, cptr,
+-            (freeform) ? ": using freeform" : "");
+-      /*
+-       * plain user sethost, handled here
+-       */
+-      } else {
+-        /* empty password? */
+-        if (EmptyString(password)) {
+-          send_reply(cptr, ERR_NEEDMOREPARAMS, "MODE");
+-          return 0;
+-        }
+-        /* no such s-line */
+-        if ((new_vhost = IsVhost(host, 0)) == NULL) {
+-          send_reply(cptr, ERR_HOSTUNAVAIL, hostmask);
+-          log_write(LS_SETHOST, L_INFO, LOG_NOSNOTICE, "SETHOST (%s@%s %s) by (%#R): no such s-line",
+-              cli_user(cptr)->username, host, password, cptr);
+-          return 0;
+-        }
+-        /* no password */
+-        if ((vhost_pass = IsVhostPass(new_vhost)) == NULL) {
+-          send_reply(cptr, ERR_PASSWDMISMATCH);
+-          log_write(LS_SETHOST, L_INFO, 0, "SETHOST (%s@%s %s) by (%#R): trying to use an oper s-line",
+-              cli_user(cptr)->username, host, password, cptr);
+-          return 0;
+-        }
+-        /* incorrect password */
+-        if (strCasediff(vhost_pass, password)) {
+-          send_reply(cptr, ERR_PASSWDMISMATCH);
+-          log_write(LS_SETHOST, L_NOTICE, 0, "SETHOST (%s@%s %s) by (%#R): incorrect password",
+-              cli_user(cptr)->username, host, password, cptr);
+-          return 0;
+-        }
+-        sendcmdto_common_channels_butone(cptr, CMD_QUIT, cptr, ":Host change");
+-        /* set the new host */
+-        strncpy(cli_user(cptr)->host, new_vhost, HOSTLEN);
+-        /* log it */
+-        log_write(LS_SETHOST, L_INFO, LOG_NOSNOTICE, "SETHOST (%s@%s) by (%#R)",
+-            cli_user(cptr)->username, cli_user(cptr)->host, cptr);
+-      }
+-    } else { /* remote user */
+-        sendcmdto_common_channels_butone(cptr, CMD_QUIT, cptr, ":Host change");
+-        if (host != hostmask) /* oper only specified host.cc */
+-          strncpy(cli_user(cptr)->username, hostmask, USERLEN);
+-        strncpy(cli_user(cptr)->host, host, HOSTLEN);
+-    }
++  /* apply sethost */
++  else {
++
++    /* set flag */
++    SetSetHost(cptr);
++
++    /* update user and host */
++    if (user)
++      strncpy(cli_user(cptr)->username, user, USERLEN);
++    strncpy(cli_user(cptr)->host, host, HOSTLEN);
+   }
+-  if (restore)
+-    ClearSetHost(cptr);
+-  else
+-    SetSetHost(cptr);
++  /* tell user */
++  if (MyConnect(cptr)) {
+-  if (MyConnect(cptr)) {
+-    ircd_snprintf(0, hiddenhost, HOSTLEN + USERLEN + 2, "%s@%s",
+-      cli_user(cptr)->username, cli_user(cptr)->host);
+-    send_reply(cptr, RPL_HOSTHIDDEN, hiddenhost);
++    /* user and host changed */
++    if (userchange || strcmp(cli_user(cptr)->username, cli_user(cptr)->realusername) != 0)
++      send_reply(cptr, RPL_HOSTHIDDEN, cli_user(cptr)->username, "@", cli_user(cptr)->host);
++
++    /* just host changed */
++    else
++      send_reply(cptr, RPL_HOSTHIDDEN, "", "", cli_user(cptr)->host);
+   }
+-#if 0
+-  /* Code copied from hide_hostmask().  This is the old (pre-delayedjoin) 
+-   * version.  Switch this in if you're not using the delayed join patch. */
+-  /*
+-   * Go through all channels the client was on, rejoin him
+-   * and set the modes, if any
+-   */
++  /* go over the channels */
+   for (chan = cli_user(cptr)->channel; chan; chan = chan->next_channel) {
++
++    /* invalidate bans so they are rechecked */
++    ClearBanValid(chan);
++
++   /* zombie */
+     if (IsZombie(chan))
+       continue;
+-    sendcmdto_channel_butserv_butone(cptr, CMD_JOIN, chan->channel, cptr,
+-      "%H", chan->channel);
+-    if (IsChanOp(chan) && HasVoice(chan)) {
+-      sendcmdto_channel_butserv_butone(&me, CMD_MODE, chan->channel, cptr,
+-        "%H +ov %C %C", chan->channel, cptr, cptr);
+-    } else if (IsChanOp(chan) || HasVoice(chan)) {
+-      sendcmdto_channel_butserv_butone(&me, CMD_MODE, chan->channel, cptr,
+-        "%H +%c %C", chan->channel, IsChanOp(chan) ? 'o' : 'v', cptr);
+-    }
+-  }
+-#endif
+-  /*
+-   * Go through all channels the client was on, rejoin him
+-   * and set the modes, if any
+-   */
+-  for (chan = cli_user(cptr)->channel; chan; chan = chan->next_channel) {
+-    if (IsZombie(chan))
+-      continue;
+-    /* If this channel has delayed joins and the user has no modes, just set
+-     * the delayed join flag rather than showing the join, even if the user
+-     * was visible before */
+-    if (!IsChanOp(chan) && !HasVoice(chan)
+-        && (chan->channel->mode.mode & MODE_DELJOINS)) {
+-      SetDelayedJoin(chan);
+-    } else {
++    /* not delayed join, rejoin user to chan */
++    if (!IsDelayedJoin(chan))
+       sendcmdto_channel_butserv_butone(cptr, CMD_JOIN, chan->channel, cptr, 0,
+         "%H", chan->channel);
+-    }
+-    if (IsChanOp(chan) && HasVoice(chan)) {
++
++    /* restore modes */
++    if (IsChanOp(chan) && HasVoice(chan))
+       sendcmdto_channel_butserv_butone(&his, CMD_MODE, chan->channel, cptr, 0,
+         "%H +ov %C %C", chan->channel, cptr, cptr);
+-    } else if (IsChanOp(chan) || HasVoice(chan)) {
++    else if (IsChanOp(chan) || HasVoice(chan))
+       sendcmdto_channel_butserv_butone(&his, CMD_MODE, chan->channel, cptr, 0,
+         "%H +%c %C", chan->channel, IsChanOp(chan) ? 'o' : 'v', cptr);
+-    }
+   }
+-  return 1;
++
++  return 0;
+ }
+ /** Set a user's mode.  This function checks that \a cptr is trying to
+@@ -1220,15 +1118,17 @@
+   unsigned int tmpmask = 0;
+   int snomask_given = 0;
+   char buf[BUFSIZE];
+-  char *hostmask, *password;
++  char *hostmask = NULL;
+   int prop = 0;
+   int do_host_hiding = 0;
+   int do_set_host = 0;
++  int alreadyh = 0;
+   size_t opernamelen;
+   char *opername = 0;
+   char* account = NULL;
++  char *user = NULL;
++  char *host = NULL;
+-  hostmask = password = NULL;
+   what = MODE_ADD;
+   if (parc < 3)
+@@ -1238,8 +1138,7 @@
+     for (i = 0; i < USERMODELIST_SIZE; i++)
+     {
+       if (HasFlag(sptr, userModeList[i].flag) &&
+-          ((userModeList[i].flag != FLAG_ACCOUNT) &&
+-          (userModeList[i].flag != FLAG_SETHOST)))
++          userModeList[i].flag != FLAG_ACCOUNT)
+         *m++ = userModeList[i].c;
+     }
+     *m = '\0';
+@@ -1251,6 +1150,9 @@
+     return 0;
+   }
++  if (IsSetHost(sptr))
++    alreadyh = 1;
++
+   /*
+    * find flags already set for user
+    * why not just copy them?
+@@ -1394,26 +1296,18 @@
+          break;
+       case 'h':
+          if (what == MODE_ADD) {
+-           if (*(p + 1) && is_hostmask(*(p + 1))) {
+-             do_set_host = 1;
+-             hostmask = *++p;
+-             /* DON'T step p onto the trailing NULL in the parameter array! - splidge */
+-             if (*(p+1))
+-               password = *++p;
+-             else
+-               password = NULL;
+-           } else {
+-             if (!*(p+1))
+-               send_reply(sptr, ERR_NEEDMOREPARAMS, "SETHOST");
++           /* TODO: */
++           if (IsServer(cptr)) {
++             if (!*(p + 1))
++               protocol_violation(cptr, "Received mode +h for user %C without sethost parameter", sptr);
+              else {
+-               send_reply(sptr, ERR_BADHOSTMASK, *(p+1));
+-               p++; /* Swallow the arg anyway */
++               hostmask = *++p;
++               do_set_host = 1;
+              }
+            }
+          } else { /* MODE_DEL */
++           hostmask = NULL;
+            do_set_host = 1;
+-           hostmask = NULL;
+-           password = NULL;
+          }
+          break;
+       case 'R':
+@@ -1468,6 +1362,11 @@
+     if (!FlagHas(&setflags, FLAG_PARANOID) && !(IsOper(sptr) && HasPriv(sptr, PRIV_PARANOID)))
+       ClearParanoid(sptr);
++    /* TODO: */
++    /* only opers can remove a sethost */
++    if (do_set_host && !hostmask && !FlagHas(&setflags, FLAG_LOCOP) && !FlagHas(&setflags, FLAG_OPER))
++      do_set_host = 0;
++
+     /*
+      * only send wallops to opers
+      */
+@@ -1521,11 +1420,35 @@
+   }
+   if (!FlagHas(&setflags, FLAG_HIDDENHOST) && do_host_hiding && allow_modes != ALLOWMODES_DEFAULT)
+     hide_hostmask(sptr, FLAG_HIDDENHOST);
++
++  /* TODO: */
+   if (do_set_host) {
+-    /* We clear the flag in the old mask, so that the +h will be sent */
+-    /* Only do this if we're SETTING +h and it succeeded */
+-    if (set_hostmask(sptr, hostmask, password) && hostmask)
+-      FlagClr(&setflags, FLAG_SETHOST);
++
++    /* mode -h */
++    if (!hostmask)
++      set_hostmask(sptr, NULL, NULL);
++
++    /* mode +h */
++    else {
++      if ((host = strrchr(hostmask, '@'))) {
++        *host++ = '\0';
++        user = hostmask;
++      }
++      else
++        host = hostmask;
++
++      /* invalid */
++      if (!user || !is_validsethost(user, USERLEN) || !is_validsethost(host, HOSTLEN))
++        protocol_violation(cptr, "Received mode +h for user %C with an invalid sethost parameter '%s@%s'",
++          sptr, user ? user : "", host);
++
++      /* apply it */
++      else {
++        /* clear flag in old mask so that +h will be sent again */
++        FlagClr(&setflags, FLAG_SETHOST);
++        set_hostmask(sptr, user, host);
++      }
++    }
+   }
+   if (IsRegistered(sptr)) {
+@@ -1577,7 +1500,7 @@
+     }
+     assert(UserStats.opers <= UserStats.clients + UserStats.unknowns);
+     assert(UserStats.inv_clients <= UserStats.clients + UserStats.unknowns);
+-    send_umode_out(cptr, sptr, &setflags, prop);
++    send_umode_out(cptr, sptr, &setflags, prop, alreadyh);
+   }
+   return 0;
+@@ -1652,9 +1575,11 @@
+  * @param[in] old Pre-change set of modes for \a sptr.
+  * @param[in] sendset One of ALL_UMODES, SEND_UMODES_BUT_OPER,
+  * SEND_UMODES, to select which changed user modes to send.
++ * @param[in] opernames Include opername parameter.
++ * @param[in] alreadyh Client already has +h set, do not show +h change.
+  */
+ void send_umode(struct Client *cptr, struct Client *sptr, struct Flags *old,
+-                int sendset, int opernames)
++                int sendset, int opernames, int alreadyh)
+ {
+   int i;
+   int flag;
+@@ -1697,12 +1622,15 @@
+     }
+     /* Special case for SETHOST.. */
+     if (flag == FLAG_SETHOST) {
+-      /* Don't send to users */
+-      if (cptr && MyUser(cptr))
+-              continue;
+-      
+-      /* If we're setting +h, add the parameter later */
+-      if (!FlagHas(old, flag))        
++
++      /* do not show +h if client already had it */
++      if (cptr && MyUser(cptr) && IsSetHost(cptr) && alreadyh)
++        continue;
++
++      /* If we're setting +h, add the parameter later,
++       * but not when showing to the user
++       */
++      if (!FlagHas(old, flag) && (!cptr || !MyUser(cptr)))    
+               needhost++;    
+     }
+     if (FlagHas(old, flag))
+@@ -1769,108 +1697,30 @@
+   return 0;
+ }
+- /*
+-  * Check to see if it resembles a valid hostmask.
+-  */
+-int is_hostmask(char *word)
++/**
++ * Check to see if it resembles a valid sethost.
++ *
++ * @param[in] mask     Mask to check (user or host part from a sethost)
++ * @param[in] maxlen   Max length for parameter mask (USERLEN or HOSTLEN)
++ * @return Non-zero if \a mask looks like a valid sethost
++ *
++ */
++int is_validsethost(char *mask, int maxlen)
+ {
+-  int i = 0;
+-  char *host;
++  assert(mask != NULL);
++  assert(maxlen > 0);
+-  Debug((DEBUG_INFO, "is_hostmask() %s", word));
+-
+-  if (strlen(word) > (HOSTLEN + USERLEN + 1) || strlen(word) <= 0)
++  /* too long or too short */
++  if (strlen(mask) > maxlen || strlen(mask) <= 0)
+     return 0;
+-  /* if a host is specified, make sure it's valid */
+-  host = strrchr(word, '@');
+-  if (host) {
+-     if (strlen(++host) < 1)
+-       return 0;
+-     if (strlen(host) > HOSTLEN)
+-       return 0;
++  /* check chars */
++  for (; *mask; mask++) {
++    if (!IsHostChar(*mask))
++      return 0;
+   }
+-  if (word) {
+-    if ('@' == *word) /* no leading @'s */
+-        return 0;
+-
+-    if ('#' == *word) {       /* numeric index given? */
+-      for (word++; *word; word++) {
+-        if (!IsDigit(*word))
+-          return 0;
+-      }
+-      return 1;
+-    }
+-
+-    /* normal hostmask, account for at most one '@' */
+-    for (; *word; word++) {
+-      if ('@' == *word) {
+-        i++;
+-        continue;
+-      }
+-      if (!IsHostChar(*word))
+-        return 0;
+-    }
+-    return (1 < i) ? 0 : 1; /* no more than on '@' */
+-  }
+-  return 0;
+-}
+-
+- /*
+-  * IsVhost() - Check if given host is a valid spoofhost
+-  * (ie: configured thru a S:line)
+-  */
+-static char *IsVhost(char *hostmask, int oper)
+-{
+-  unsigned int i = 0, y = 0;
+-  struct sline *sconf;
+-
+-  Debug((DEBUG_INFO, "IsVhost() %s", hostmask));
+-
+-  if (EmptyString(hostmask))
+-    return NULL;
+-
+-  /* spoofhost specified as index, ie: #27 */
+-  if ('#' == hostmask[0]) {
+-    y = atoi(hostmask + 1);
+-    for (i = 0, sconf = GlobalSList; sconf; sconf = sconf->next) {
+-      if (!oper && EmptyString(sconf->passwd))
+-        continue;
+-      if (y == ++i)
+-        return sconf->spoofhost;
+-    }
+-    return NULL;
+-  }
+-
+-  /* spoofhost specified as host, ie: host.cc */
+-  for (sconf = GlobalSList; sconf; sconf = sconf->next)
+-    if (strCasediff(hostmask, sconf->spoofhost) == 0)
+-      return sconf->spoofhost;
+-
+-  return NULL;
+-}
+-
+- /*
+-  * IsVhostPass() - Check if given spoofhost has a password
+-  * associated with it, and if, return the password (cleartext)
+-  */
+-static char *IsVhostPass(char *hostmask)
+-{
+-  struct sline *sconf;
+-
+-  Debug((DEBUG_INFO, "IsVhostPass() %s", hostmask));
+-
+-  if (EmptyString(hostmask))
+-    return NULL;
+-
+-  for (sconf = GlobalSList; sconf; sconf = sconf->next)
+-    if (strCasediff(hostmask, sconf->spoofhost) == 0) {
+-      Debug((DEBUG_INFO, "sconf->passwd %s", sconf->passwd));
+-      return EmptyString(sconf->passwd) ? NULL : sconf->passwd;
+-    }
+-
+-  return NULL;
++  return 1;
+ }
+ /** Update snomask \a oldmask according to \a arg and \a what.
+diff -r c6f3803ee169 ircd/send.c
+--- a/ircd/send.c
++++ b/ircd/send.c
+@@ -281,7 +281,7 @@
+   {
+     case MATCH_HOST:
+       return (match(mask, cli_user(one)->host) == 0 ||
+-        ((HasHiddenHost(one) || HasSetHost(one)) && match(mask, cli_user(one)->realhost) == 0));
++        ((HasHiddenHost(one) || IsSetHost(one)) && match(mask, cli_user(one)->realhost) == 0));
+     case MATCH_SERVER:
+     default:
+       return (match(mask, cli_name(cli_user(one)->server)) == 0);
+diff -r c6f3803ee169 ircd/whocmds.c
+--- a/ircd/whocmds.c
++++ b/ircd/whocmds.c
+@@ -134,7 +134,7 @@
+   if (fields & WHO_FIELD_NIP)
+   {
+-    const char* p2 = (HasHiddenHost(acptr) || HasSetHost(acptr) || feature_bool(FEAT_HIS_USERIP)) && (!IsAnOper(sptr) || (IsAnOper(sptr) && !HasPriv(sptr, PRIV_USER_PRIVACY))) ?
++    const char* p2 = (HasHiddenHost(acptr) || IsSetHost(acptr) || feature_bool(FEAT_HIS_USERIP)) && (!IsAnOper(sptr) || (IsAnOper(sptr) && !HasPriv(sptr, PRIV_USER_PRIVACY))) ?
+       feature_str(FEAT_HIDDEN_IP) :
+       ircd_ntoa(&cli_ip(acptr));
+     *(p1++) = ' ';
+@@ -210,7 +210,7 @@
+         *(p1++) = 'w';
+       if (SendDebug(acptr))
+         *(p1++) = 'g';
+-      if (HasSetHost(acptr))
++      if (IsSetHost(acptr))
+         *(p1++) = 'h';
+     }
+     if (HasHiddenHost(acptr))