]> jfr.im git - irc/quakenet/snircd.git/blobdiff - ircd/s_user.c
merge 07 in
[irc/quakenet/snircd.git] / ircd / s_user.c
index 24ea9f6b13b32ff109eec17b17a6bf8473d90961..009000673c734c48f7f35aac1baf0cabfff9ad35 100644 (file)
@@ -22,7 +22,7 @@
  */
 /** @file
  * @brief Miscellaneous user-related helper functions.
- * @version $Id: s_user.c,v 1.99 2005/08/17 01:57:03 entrope Exp $
+ * @version $Id: s_user.c,v 1.99.2.2 2006/02/16 03:49:54 entrope Exp $
  */
 #include "config.h"
 
@@ -34,7 +34,6 @@
 #include "hash.h"
 #include "ircd.h"
 #include "ircd_alloc.h"
-#include "ircd_auth.h"
 #include "ircd_chattr.h"
 #include "ircd_features.h"
 #include "ircd_log.h"
@@ -51,6 +50,7 @@
 #include "parse.h"
 #include "querycmds.h"
 #include "random.h"
+#include "s_auth.h"
 #include "s_bsd.h"
 #include "s_conf.h"
 #include "s_debug.h"
@@ -73,6 +73,9 @@
 #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;
 
@@ -304,40 +307,6 @@ int hunt_server_prio_cmd(struct Client *from, const char *cmd, const char *tok,
 }
 
 
-/** Copy a cleaned-up version of a username.
- * Replace all instances of '~' and "invalid" username characters
- * (!isIrcUi()) with underscores, truncating at USERLEN or the first
- * control character.  \a dest and \a source may be the same buffer.
- * @param[out] dest Destination buffer.
- * @param[in] source Source buffer.
- * @param[in] tilde If non-zero, prepend a '~' to \a dest.
- */
-static char *clean_user_id(char *dest, char *source, int tilde)
-{
-  char ch;
-  char *d = dest;
-  char *s = source;
-  int rlen = USERLEN;
-
-  ch = *s++;                        /* Store first character to copy: */
-  if (tilde)
-  {
-    *d++ = '~';                        /* If `dest' == `source', then this overwrites `ch' */
-    --rlen;
-  }
-  while (ch && !IsCntrl(ch) && rlen--)
-  {
-    char nch = *s++;        /* Store next character to copy */
-    *d++ = IsUserChar(ch) ? ch : '_';        /* This possibly overwrites it */
-    if (nch == '~')
-      ch = '_';
-    else
-      ch = nch;
-  }
-  *d = 0;
-  return dest;
-}
-
 /*
  * register_user
  *
@@ -369,29 +338,13 @@ static char *clean_user_id(char *dest, char *source, int tilde)
  *
  * @param[in] cptr Client who introduced the user.
  * @param[in,out] sptr Client who has been fully introduced.
- * @param[in] nick Client's new nickname.
- * @param[in] username Client's username.
  * @return Zero or CPTR_KILLED.
  */
-int register_user(struct Client *cptr, struct Client *sptr,
-                  const char *nick, char *username)
+int register_user(struct Client *cptr, struct Client *sptr)
 {
-  struct ConfItem* aconf;
   char*            parv[4];
   char*            tmpstr;
-  char*            tmpstr2;
-  char             c = 0;    /* not alphanum */
-  char             d = 'a';  /* not a digit */
-  short            upper = 0;
-  short            lower = 0;
-  short            pos = 0;
-  short            leadcaps = 0;
-  short            other = 0;
-  short            digits = 0;
-  short            badid = 0;
-  short            digitgroups = 0;
   struct User*     user = cli_user(sptr);
-  int              killreason;
   char             ip_base64[25];
 
   user->last = CurrentTime;
@@ -400,166 +353,19 @@ int register_user(struct Client *cptr, struct Client *sptr,
 
   if (MyConnect(sptr))
   {
-    static time_t last_too_many1;
-    static time_t last_too_many2;
-
     assert(cptr == sptr);
-    assert(cli_unreg(sptr) == 0);
-    if (!IsIAuthed(sptr)) {
-      if (iauth_active)
-        return iauth_start_client(iauth_active, sptr);
-      else
-        SetIAuthed(sptr);
-    }
-    switch (conf_check_client(sptr))
-    {
-      case ACR_OK:
-        break;
-      case ACR_NO_AUTHORIZATION:
-        sendto_opmask_butone(0, SNO_UNAUTH, "Unauthorized connection from %s.",
-                             get_client_name(sptr, HIDE_IP));
-        ++ServerStats->is_ref;
-        return exit_client(cptr, sptr, &me,
-                           "No Authorization - use another server");
-      case ACR_TOO_MANY_IN_CLASS:
-        if (CurrentTime - last_too_many1 >= (time_t) 60)
-        {
-          last_too_many1 = CurrentTime;
-          sendto_opmask_butone(0, SNO_TOOMANY, "Too many connections in "
-                               "class %s for %s.", get_client_class(sptr),
-                               get_client_name(sptr, SHOW_IP));
-        }
-        ++ServerStats->is_ref;
-        IPcheck_connect_fail(sptr);
-        return exit_client(cptr, sptr, &me,
-                           "Sorry, your connection class is full - try "
-                           "again later or try another server");
-      case ACR_TOO_MANY_FROM_IP:
-        if (CurrentTime - last_too_many2 >= (time_t) 60)
-        {
-          last_too_many2 = CurrentTime;
-          sendto_opmask_butone(0, SNO_TOOMANY, "Too many connections from "
-                               "same IP for %s.",
-                               get_client_name(sptr, SHOW_IP));
-        }
-        ++ServerStats->is_ref;
-        return exit_client(cptr, sptr, &me,
-                           "Too many connections from your host");
-      case ACR_ALREADY_AUTHORIZED:
-        /* Can this ever happen? */
-      case ACR_BAD_SOCKET:
-        ++ServerStats->is_ref;
-        IPcheck_connect_fail(sptr);
-        return exit_client(cptr, sptr, &me, "Unknown error -- Try again");
-    }
-    ircd_strncpy(user->host, cli_sockhost(sptr), HOSTLEN);
-    ircd_strncpy(user->realhost, cli_sockhost(sptr), HOSTLEN);
-    aconf = cli_confs(sptr)->value.aconf;
 
-    clean_user_id(user->username,
-                  HasFlag(sptr, FLAG_GOTID) ? cli_username(sptr) : username,
-                  HasFlag(sptr, FLAG_DOID) && !HasFlag(sptr, FLAG_GOTID));
-
-    if ((user->username[0] == '\0')
-        || ((user->username[0] == '~') && (user->username[1] == '\000')))
-      return exit_client(cptr, sptr, &me, "USER: Bogus userid.");
+    Count_unknownbecomesclient(sptr, UserStats);
 
-    if (!EmptyString(aconf->passwd)
-        && strcmp(cli_passwd(sptr), aconf->passwd))
-    {
-      ServerStats->is_ref++;
-      send_reply(sptr, ERR_PASSWDMISMATCH);
-      return exit_client(cptr, sptr, &me, "Bad Password");
-    }
-    memset(cli_passwd(sptr), 0, sizeof(cli_passwd(sptr)));
-    /*
-     * following block for the benefit of time-dependent K:-lines
-     */
-    killreason = find_kill(sptr, 1);
-    if (killreason) {
-      ServerStats->is_ref++;
-      return exit_client(cptr, sptr, &me,
-                         (killreason == -1 ? "K-lined" : "G-lined"));
-    }
-    /*
-     * Check for mixed case usernames, meaning probably hacked.  Jon2 3-94
-     * Summary of rules now implemented in this patch:         Ensor 11-94
-     * In a mixed-case name, if first char is upper, one more upper may
-     * appear anywhere.  (A mixed-case name *must* have an upper first
-     * char, and may have one other upper.)
-     * A third upper may appear if all 3 appear at the beginning of the
-     * name, separated only by "others" (-/_/.).
-     * A single group of digits is allowed anywhere.
-     * Two groups of digits are allowed if at least one of the groups is
-     * at the beginning or the end.
-     * Only one '-', '_', or '.' is allowed (or two, if not consecutive).
-     * But not as the first or last char.
-     * No other special characters are allowed.
-     * Name must contain at least one letter.
-     */
-    tmpstr2 = tmpstr = (username[0] == '~' ? &username[1] : username);
-    while (*tmpstr && !badid)
-    {
-      pos++;
-      c = *tmpstr;
-      tmpstr++;
-      if (IsLower(c))
-      {
-        lower++;
-      }
-      else if (IsUpper(c))
-      {
-        upper++;
-        if ((leadcaps || pos == 1) && !lower && !digits)
-          leadcaps++;
-      }
-      else if (IsDigit(c))
-      {
-        digits++;
-        if (pos == 1 || !IsDigit(d))
-        {
-          digitgroups++;
-          if (digitgroups > 2)
-            badid = 1;
-        }
-      }
-      else if (c == '-' || c == '_' || c == '.')
-      {
-        other++;
-        if (pos == 1)
-          badid = 1;
-        else if (d == '-' || d == '_' || d == '.' || other > 2)
-          badid = 1;
+    if (MyConnect(sptr) && feature_bool(FEAT_AUTOINVISIBLE))
+      SetInvisible(sptr);
+    
+    if(MyConnect(sptr) && feature_bool(FEAT_SETHOST_AUTO)) {
+      if (conf_check_slines(sptr)) {
+        send_reply(sptr, RPL_USINGSLINE);
+        SetSetHost(sptr);
       }
-      else
-        badid = 1;
-      d = c;
-    }
-    if (!badid)
-    {
-      if (lower && upper && (!leadcaps || leadcaps > 3 ||
-          (upper > 2 && upper > leadcaps)))
-        badid = 1;
-      else if (digitgroups == 2 && !(IsDigit(tmpstr2[0]) || IsDigit(c)))
-        badid = 1;
-      else if ((!lower && !upper) || !IsAlnum(c))
-        badid = 1;
-    }
-    if (badid && (!HasFlag(sptr, FLAG_GOTID) ||
-        strcmp(cli_username(sptr), username) != 0))
-    {
-      ServerStats->is_ref++;
-
-      send_reply(cptr, SND_EXPLICIT | ERR_INVALIDUSERNAME,
-                 ":Your username is invalid.");
-      send_reply(cptr, SND_EXPLICIT | ERR_INVALIDUSERNAME,
-                 ":Connect with your real username, in lowercase.");
-      send_reply(cptr, SND_EXPLICIT | ERR_INVALIDUSERNAME,
-                 ":If your mail address were foo@bar.com, your username "
-                 "would be foo.");
-      return exit_client(cptr, sptr, &me, "USER: Bad username");
     }
-    Count_unknownbecomesclient(sptr, UserStats);
 
     SetUser(sptr);
     cli_handler(sptr) = CLIENT_HANDLER;
@@ -569,7 +375,7 @@ int register_user(struct Client *cptr, struct Client *sptr,
                feature_str(FEAT_NETWORK),
                feature_str(FEAT_PROVIDER) ? " via " : "",
                feature_str(FEAT_PROVIDER) ? feature_str(FEAT_PROVIDER) : "",
-               nick);
+               cli_name(sptr));
     /*
      * This is a duplicate of the NOTICE but see below...
      */
@@ -621,12 +427,10 @@ int register_user(struct Client *cptr, struct Client *sptr,
     }
   }
   else {
-    struct Client *acptr;
+    struct Client *acptr = user->server;
 
-    ircd_strncpy(user->username, username, USERLEN);
-    Count_newremoteclient(UserStats, user->server);
+    Count_newremoteclient(UserStats, acptr);
 
-    acptr = user->server;
     if (cli_from(acptr) != cli_from(sptr))
     {
       sendcmdto_one(&me, CMD_KILL, cptr, "%C :%s (%s != %s[%s])",
@@ -658,6 +462,14 @@ int register_user(struct Client *cptr, struct Client *sptr,
                     sptr, cli_name(&me));
       return exit_client(cptr, sptr, &me,"Too many connections from your host -- throttled");
     }
+
+    if(MyConnect(sptr) && feature_bool(FEAT_SETHOST_AUTO)) {
+      if (conf_check_slines(sptr)) {
+        send_reply(sptr, RPL_USINGSLINE);
+        SetSetHost(sptr);
+      }
+    }
+
     SetUser(sptr);
   }
 
@@ -671,8 +483,9 @@ int register_user(struct Client *cptr, struct Client *sptr,
   sendcmdto_flag_serv_butone(user->server, CMD_NICK, cptr,
                              FLAG_IPV6, FLAG_LAST_FLAG,
                              "%s %d %Tu %s %s %s%s%s%s %s%s :%s",
-                             nick, cli_hopcount(sptr) + 1, cli_lastnick(sptr),
-                             user->username, user->realhost,
+                             cli_name(sptr), cli_hopcount(sptr) + 1,
+                             cli_lastnick(sptr),
+                             user->realusername, user->realhost,
                              *tmpstr ? "+" : "", tmpstr, *tmpstr ? " " : "",
                              iptobase64(ip_base64, &cli_ip(sptr), sizeof(ip_base64), 1),
                              NumNick(sptr), cli_info(sptr));
@@ -680,8 +493,9 @@ int register_user(struct Client *cptr, struct Client *sptr,
   sendcmdto_flag_serv_butone(user->server, CMD_NICK, cptr,
                              FLAG_LAST_FLAG, FLAG_IPV6,
                              "%s %d %Tu %s %s %s%s%s%s %s%s :%s",
-                             nick, cli_hopcount(sptr) + 1, cli_lastnick(sptr),
-                             user->username, user->realhost,
+                             cli_name(sptr), cli_hopcount(sptr) + 1,
+                             cli_lastnick(sptr),
+                             user->realusername, user->realhost,
                              *tmpstr ? "+" : "", tmpstr, *tmpstr ? " " : "",
                              iptobase64(ip_base64, &cli_ip(sptr), sizeof(ip_base64), 0),
                              NumNick(sptr), cli_info(sptr));
@@ -690,6 +504,13 @@ int register_user(struct Client *cptr, struct Client *sptr,
   if (MyUser(sptr))
   {
     static struct Flags flags; /* automatically initialized to zeros */
+    /* To avoid sending +r to the client due to auth-on-connect, set
+     * the "old" FLAG_ACCOUNT bit to match the client's value.
+     */
+    if (IsAccount(cptr))
+      FlagSet(&flags, FLAG_ACCOUNT);
+    else
+      FlagClr(&flags, FLAG_ACCOUNT);
     send_umode(cptr, sptr, &flags, ALL_UMODES);
     if ((cli_snomask(sptr) != SNO_DEFAULT) && HasFlag(sptr, FLAG_SERVNOTICE))
       send_reply(sptr, RPL_SNOMASK, cli_snomask(sptr), cli_snomask(sptr));
@@ -711,7 +532,13 @@ static const struct UserMode {
   { FLAG_CHSERV,      'k' },
   { FLAG_DEBUG,       'g' },
   { FLAG_ACCOUNT,     'r' },
-  { FLAG_HIDDENHOST,  'x' }
+  { FLAG_HIDDENHOST,  'x' },
+  { FLAG_ACCOUNTONLY, 'R' },
+  { FLAG_XTRAOP,      'X' },
+  { FLAG_NOCHAN,      'n' },
+  { FLAG_NOIDLE,      'I' },
+  { FLAG_SETHOST,     'h' },
+  { FLAG_PARANOID,    'P' }
 };
 
 /** Length of #userModeList. */
@@ -738,6 +565,8 @@ int set_nick_name(struct Client* cptr, struct Client* sptr,
   if (IsServer(sptr)) {
     int   i;
     const char* account = 0;
+    char* hostmask = 0;
+    char* host = 0;
     const char* p;
 
     /*
@@ -759,6 +588,8 @@ int set_nick_name(struct Client* cptr, struct Client* sptr,
             SetFlag(new_client, userModeList[i].flag);
            if (userModeList[i].flag == FLAG_ACCOUNT)
              account = parv[7];
+            if (userModeList[i].flag == FLAG_SETHOST)
+              hostmask = parv[parc - 4];
             break;
           }
         }
@@ -782,6 +613,7 @@ int set_nick_name(struct Client* cptr, struct Client* sptr,
 
     cli_serv(sptr)->ghost = 0;        /* :server NICK means end of net.burst */
     ircd_strncpy(cli_username(new_client), parv[4], USERLEN);
+    ircd_strncpy(cli_user(new_client)->realusername, parv[4], USERLEN);
     ircd_strncpy(cli_user(new_client)->host, parv[5], HOSTLEN);
     ircd_strncpy(cli_user(new_client)->realhost, parv[5], HOSTLEN);
     ircd_strncpy(cli_info(new_client), parv[parc - 1], REALLEN);
@@ -799,8 +631,15 @@ int set_nick_name(struct Client* cptr, struct Client* sptr,
     if (HasHiddenHost(new_client))
       ircd_snprintf(0, cli_user(new_client)->host, HOSTLEN, "%s.%s",
         account, feature_str(FEAT_HIDDEN_HOST));
+    if (HasSetHost(new_client)) {
+      if ((host = strrchr(hostmask, '@')) != NULL) {
+        *host++ = '\0';
+        ircd_strncpy(cli_username(new_client), hostmask, USERLEN);
+        ircd_strncpy(cli_user(new_client)->host, host, HOSTLEN);
+      }
+    }
 
-    return register_user(cptr, new_client, cli_name(new_client), parv[4]);
+    return register_user(cptr, new_client);
   }
   else if ((cli_name(sptr))[0]) {
     /*
@@ -813,7 +652,7 @@ int set_nick_name(struct Client* cptr, struct Client* sptr,
     if (MyUser(sptr)) {
       const char* channel_name;
       struct Membership *member;
-      if ((channel_name = find_no_nickchange_channel(sptr))) {
+      if ((channel_name = find_no_nickchange_channel(sptr)) && !IsXtraOp(sptr)) {
         return send_reply(cptr, ERR_BANNICKCHANGE, channel_name);
       }
       /*
@@ -873,36 +712,9 @@ int set_nick_name(struct Client* cptr, struct Client* sptr,
   }
   else {
     /* Local client setting NICK the first time */
-
     strcpy(cli_name(sptr), nick);
-    if (!cli_user(sptr)) {
-      cli_user(sptr) = make_user(sptr);
-      cli_user(sptr)->server = &me;
-    }
     hAddClient(sptr);
-
-    cli_unreg(sptr) &= ~CLIREG_NICK; /* nickname now set */
-
-    /*
-     * If the client hasn't gotten a cookie-ping yet,
-     * choose a cookie and send it. -record!jegelhof@cloud9.net
-     */
-    if (!cli_cookie(sptr)) {
-      do {
-        cli_cookie(sptr) = (ircrandom() & 0x7fffffff);
-      } while (!cli_cookie(sptr));
-      sendrawto_one(cptr, MSG_PING " :%u", cli_cookie(sptr));
-    }
-    else if (!cli_unreg(sptr)) {
-      /*
-       * USER and PONG already received, now we have NICK.
-       * register_user may reject the client and call exit_client
-       * for it - must test this and exit m_nick too !
-       */
-      cli_lastnick(sptr) = TStime();        /* Always local client */
-      if (register_user(cptr, sptr, nick, cli_user(sptr)->username) == CPTR_KILLED)
-        return CPTR_KILLED;
-    }
+    return auth_set_nick(cli_auth(sptr), nick);
   }
   return 0;
 }
@@ -970,6 +782,10 @@ int check_target_limit(struct Client *sptr, void *target, const char *name,
   if (IsChannelName(name) && IsInvited(sptr, target))
     return 0;
 
+  /* opers always have a free target */
+  if (IsAnOper(sptr))
+    return 0;
+
   /*
    * Same target as last time?
    */
@@ -1092,7 +908,7 @@ void send_umode_out(struct Client *cptr, struct Client *sptr,
   {
     if ((acptr = LocalClientArray[i]) && IsServer(acptr) &&
         (acptr != cptr) && (acptr != sptr) && *umodeBuf)
-      sendcmdto_one(sptr, CMD_MODE, acptr, "%s :%s", cli_name(sptr), umodeBuf);
+      sendcmdto_one(sptr, CMD_MODE, acptr, "%s %s", cli_name(sptr), umodeBuf);
   }
   if (cptr && MyUser(cptr))
     send_umode(cptr, sptr, old, ALL_UMODES);
@@ -1148,6 +964,11 @@ hide_hostmask(struct Client *cptr, unsigned int flag)
     /* Local users cannot set +x unless FEAT_HOST_HIDING is true. */
     if (MyConnect(cptr) && !feature_bool(FEAT_HOST_HIDING))
       return 0;
+    /* If the user is +h, we don't hide the hostmask.  Set the flag to keep sync though */
+    if (HasSetHost(cptr)) {
+      SetFlag(cptr, flag);
+       return 0;
+    }
     break;
   case FLAG_ACCOUNT:
     /* Invalidate all bans against the user so we check them again */
@@ -1194,6 +1015,190 @@ hide_hostmask(struct Client *cptr, unsigned int flag)
   return 0;
 }
 
+/*
+ * set_hostmask() - derived from hide_hostmask()
+ *
+ */
+int set_hostmask(struct Client *cptr, char *hostmask, char *password)
+{
+  int restore = 0;
+  int freeform = 0;
+  char *host, *new_vhost, *vhost_pass;
+  char hiddenhost[USERLEN + HOSTLEN + 2];
+  struct Membership *chan;
+
+  Debug((DEBUG_INFO, "set_hostmask() %C, %s, %s", cptr, hostmask, password));
+
+  /* sethost enabled? */
+  if (MyConnect(cptr) && !feature_bool(FEAT_SETHOST)) {
+    send_reply(cptr, ERR_DISABLED, "SETHOST");
+    return 0;
+  }
+
+  /* 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? */
+    if (IsSetHost(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';
+    else /* user can only specifiy host.cc [password] */
+      host = hostmask;
+    /*
+     * Oper sethost
+     */
+    if (MyConnect(cptr)) {
+      if (IsAnOper(cptr)) {
+        if ((new_vhost = IsVhost(host, 1)) == NULL) {
+          if (!feature_bool(FEAT_SETHOST_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);
+    }
+  }
+
+  if (restore)
+    ClearSetHost(cptr);
+  else
+    SetSetHost(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);
+  }
+
+#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
+   */
+  for (chan = cli_user(cptr)->channel; chan; chan = chan->next_channel) {
+    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 {
+      sendcmdto_channel_butserv_butone(cptr, CMD_JOIN, chan->channel, cptr, 0,
+        "%H", chan->channel);
+    }
+    if (IsChanOp(chan) && HasVoice(chan)) {
+      sendcmdto_channel_butserv_butone(&me, CMD_MODE, chan->channel, cptr, 0,
+        "%H +ov %C %C", chan->channel, cptr, cptr);
+    } else if (IsChanOp(chan) || HasVoice(chan)) {
+      sendcmdto_channel_butserv_butone(&me, CMD_MODE, chan->channel, cptr, 0,
+        "%H +%c %C", chan->channel, IsChanOp(chan) ? 'o' : 'v', cptr);
+    }
+  }
+  return 1;
+}
+
 /** Set a user's mode.  This function checks that \a cptr is trying to
  * set his own mode, prevents local users from setting inappropriate
  * modes through this function, and applies any other side effects of
@@ -1216,9 +1221,12 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv
   unsigned int tmpmask = 0;
   int snomask_given = 0;
   char buf[BUFSIZE];
+  char *hostmask, *password;
   int prop = 0;
   int do_host_hiding = 0;
+  int do_set_host = 0;
 
+  hostmask = password = NULL;
   what = MODE_ADD;
 
   if (parc < 2)
@@ -1249,7 +1257,8 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv
     for (i = 0; i < USERMODELIST_SIZE; i++)
     {
       if (HasFlag(sptr, userModeList[i].flag) &&
-          userModeList[i].flag != FLAG_ACCOUNT)
+          ((userModeList[i].flag != FLAG_ACCOUNT) &&
+          (userModeList[i].flag != FLAG_SETHOST)))
         *m++ = userModeList[i].c;
     }
     *m = '\0';
@@ -1333,7 +1342,8 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv
         if (what == MODE_ADD)
           SetInvisible(sptr);
         else
-          ClearInvisible(sptr);
+          if (!feature_bool(FEAT_AUTOINVISIBLE) || IsOper(sptr)) /* Don't allow non-opers to -i if FEAT_AUTOINVISIBLE is set */
+            ClearInvisible(sptr);
         break;
       case 'd':
         if (what == MODE_ADD)
@@ -1347,6 +1357,24 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv
         else
           ClearChannelService(sptr);
         break;
+      case 'X':
+        if (what == MODE_ADD)
+          SetXtraOp(sptr);
+        else
+          ClearXtraOp(sptr);
+        break;
+      case 'n':
+        if (what == MODE_ADD)
+          SetNoChan(sptr);
+        else
+          ClearNoChan(sptr);
+        break;
+      case 'I':
+        if (what == MODE_ADD)
+          SetNoIdle(sptr);
+        else
+          ClearNoIdle(sptr);
+        break;
       case 'g':
         if (what == MODE_ADD)
           SetDebug(sptr);
@@ -1355,7 +1383,43 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv
         break;
       case 'x':
         if (what == MODE_ADD)
-         do_host_hiding = 1;
+          do_host_hiding = 1;
+         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");
+             else {
+               send_reply(sptr, ERR_BADHOSTMASK, *(p+1));
+               p++; /* Swallow the arg anyway */
+             }
+           }
+         } else { /* MODE_DEL */
+           do_set_host = 1;
+           hostmask = NULL;
+           password = NULL;
+         }
+         break;
+      case 'R':
+        if (what == MODE_ADD)
+          SetAccountOnly(sptr);
+        else
+          ClearAccountOnly(sptr);
+        break;
+      case 'P':
+       if (what == MODE_ADD)
+          SetParanoid(sptr);
+        else
+          ClearParanoid(sptr);
        break;
       default:
         send_reply(sptr, ERR_UMODEUNKNOWNFLAG, *m);
@@ -1377,8 +1441,17 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv
      * new umode; servers can set it, local users cannot;
      * prevents users from /kick'ing or /mode -o'ing
      */
-    if (!FlagHas(&setflags, FLAG_CHSERV))
+    if (!FlagHas(&setflags, FLAG_CHSERV) && !IsOper(sptr))
       ClearChannelService(sptr);
+    if (!FlagHas(&setflags, FLAG_XTRAOP) && !IsOper(sptr))
+      ClearXtraOp(sptr);
+    if (!FlagHas(&setflags, FLAG_NOCHAN) && !(IsOper(sptr) || feature_bool(FEAT_USER_HIDECHANS)))
+      ClearNoChan(sptr);
+    if (!FlagHas(&setflags, FLAG_NOIDLE) && !IsOper(sptr))
+      ClearNoIdle(sptr);
+    if (!FlagHas(&setflags, FLAG_PARANOID) && !IsOper(sptr))
+      ClearParanoid(sptr);
+
     /*
      * only send wallops to opers
      */
@@ -1436,6 +1509,12 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv
     ++UserStats.inv_clients;
   if (!FlagHas(&setflags, FLAG_HIDDENHOST) && do_host_hiding)
     hide_hostmask(sptr, FLAG_HIDDENHOST);
+  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);
+  }
   send_umode_out(cptr, sptr, &setflags, prop);
 
   return 0;
@@ -1481,10 +1560,15 @@ char *umode_str(struct Client *cptr)
       while ((*m++ = *t++))
        ; /* Empty loop */
     }
+    m--; /* Step back over the '\0' */
   }
 
-  *m = '\0';
-
+  if (IsSetHost(cptr)) {
+    *m++ = ' ';
+    ircd_snprintf(0, m, USERLEN + HOSTLEN + 2, "%s@%s", cli_user(cptr)->username,
+         cli_user(cptr)->host);
+  } else
+    *m = '\0';
   return umodeBuf;                /* Note: static buffer, gets
                                    overwritten by send_umode() */
 }
@@ -1501,6 +1585,7 @@ void send_umode(struct Client *cptr, struct Client *sptr, struct Flags *old,
 {
   int i;
   int flag;
+  int needhost = 0;
   char *m;
   int what = MODE_NULL;
 
@@ -1530,6 +1615,16 @@ void send_umode(struct Client *cptr, struct Client *sptr, struct Flags *old,
         continue;
       break;      
     }
+    /* 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)) 
+       needhost++;    
+    }
     if (FlagHas(old, flag))
     {
       if (what == MODE_DEL)
@@ -1553,9 +1648,14 @@ void send_umode(struct Client *cptr, struct Client *sptr, struct Flags *old,
       }
     }
   }
-  *m = '\0';
+  if (needhost) {
+    *m++ = ' ';
+    ircd_snprintf(0, m, USERLEN + HOSTLEN + 1, "%s@%s", cli_user(sptr)->username,
+         cli_user(sptr)->host);
+  } else
+    *m = '\0';
   if (*umodeBuf && cptr)
-    sendcmdto_one(sptr, CMD_MODE, cptr, "%s :%s", cli_name(sptr), umodeBuf);
+    sendcmdto_one(sptr, CMD_MODE, cptr, "%s %s", cli_name(sptr), umodeBuf);
 }
 
 /**
@@ -1578,6 +1678,110 @@ int is_snomask(char *word)
   return 0;
 }
 
+ /*
+  * Check to see if it resembles a valid hostmask.
+  */
+int is_hostmask(char *word)
+{
+  int i = 0;
+  char *host;
+
+  Debug((DEBUG_INFO, "is_hostmask() %s", word));
+
+  if (strlen(word) > (HOSTLEN + USERLEN + 1) || strlen(word) <= 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;
+  }
+
+  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;
+}
+
 /** Update snomask \a oldmask according to \a arg and \a what.
  * @param[in] oldmask Original user mask.
  * @param[in] arg Update string (either a number or '+'/'-' followed by a number).