Index: ChangeLog
===================================================================
RCS file: /home/coder-com/cvs/ircu2.10/ChangeLog,v
retrieving revision 1.340
diff -u -r1.340 ChangeLog
--- ChangeLog	2002/04/12 00:19:52	1.340
+++ ChangeLog	2002/04/12 13:17:10
@@ -1,5 +1,33 @@
 2002-04-12  Alex Badea  <vampire@p16.pub.ro>
 
+	* include/ircd_features.h: new feature LOGIN_ON_CONNECT
+
+	* ircd/ircd_features.c: new feature LOGIN_ON_CONNECT
+
+	* include/client.h (struct Client): new fields for storing
+	bot name, username and password for login-on-connect
+
+	* ircd/m_account.c: extensions for login-on-connect: route
+	and process auth-request and auth-reply messages
+
+	* ircd/m_pass.c: store bot name, username and password for
+	service login
+
+	* ircd/m_user.c: store username/hostname for the client
+	even if he finished registration, as register_user may not
+	do that anymore
+
+	* ircd/s_user.c (register_user): if the client specified
+	a service login in the PASS command, attempt to log him in;
+	also, don't set his hostname if it was set remotely by a
+	service bot
+
+	* doc/example.conf: default value for FEAT_LOGIN_ON_CONNECT
+
+	* doc/readme.features: documented FEAT_LOGIN_ON_CONNECT
+
+2002-04-12  Alex Badea  <vampire@p16.pub.ro>
+
 	* ircd/m_invite.c: don't propagate invites for local channels
 
 	* include/patchlevel.h (PATCHLEVEL): bump patchlevel
Index: doc/example.conf
===================================================================
RCS file: /home/coder-com/cvs/ircu2.10/doc/example.conf,v
retrieving revision 1.20
diff -u -r1.20 example.conf
--- doc/example.conf	2002/04/03 15:23:47	1.20
+++ doc/example.conf	2002/04/12 13:17:10
@@ -702,6 +702,7 @@
 #  "HOST_HIDING"="FALSE";
 #  "HIDDEN_HOST"="users.undernet.org";
 #  "HIDDEN_IP"="127.0.0.1";
+#  "LOGIN_ON_CONNECT"="FALSE";
 #  "KILLCHASETIMELIMIT"="30";
 #  "MAXCHANNELSPERUSER"="10";
 #  "AVBANLEN"="40";
Index: doc/readme.features
===================================================================
RCS file: /home/coder-com/cvs/ircu2.10/doc/readme.features,v
retrieving revision 1.5
diff -u -r1.5 readme.features
--- doc/readme.features	2002/04/03 15:23:47	1.5
+++ doc/readme.features	2002/04/12 13:17:10
@@ -242,6 +242,13 @@
 This selects a fake IP to be shown on /USERIP and /WHO %i when the
 target has a hidden host (see HOST_HIDING).
 
+LOGIN_ON_CONNECT
+  * Type: boolean
+  * Default: FALSE
+
+This selects whether local clients can use specify a service bot login
+in the connection phase. Read readme.login-on-connect for more details.
+
 KILLCHASETIMELIMIT
  * Type: integer
  * Default: 30
Index: include/client.h
===================================================================
RCS file: /home/coder-com/cvs/ircu2.10/include/client.h,v
retrieving revision 1.26
diff -u -r1.26 client.h
--- include/client.h	2002/04/05 11:36:58	1.26
+++ include/client.h	2002/04/12 13:17:10
@@ -196,6 +196,10 @@
   char cli_name[HOSTLEN + 1];   /* Unique name of the client, nick or host */
   char cli_username[USERLEN + 1]; /* username here now for auth stuff */
   char cli_info[REALLEN + 1];   /* Free form additional client information */
+  
+  char *cli_cs_user;		/* channel service authentication (user)... */
+  char *cli_cs_pass;		/* ...and password... */
+  char *cli_cs_service;		/* ...and the service bot's nick */
 };
 
 #define CLIENT_MAGIC 0x4ca08286
Index: include/ircd_features.h
===================================================================
RCS file: /home/coder-com/cvs/ircu2.10/include/ircd_features.h,v
retrieving revision 1.15
diff -u -r1.15 ircd_features.h
--- include/ircd_features.h	2002/04/03 15:23:47	1.15
+++ include/ircd_features.h	2002/04/12 13:17:10
@@ -45,6 +45,7 @@
   FEAT_HOST_HIDING,
   FEAT_HIDDEN_HOST,
   FEAT_HIDDEN_IP,
+  FEAT_LOGIN_ON_CONNECT,
 
   /* features that probably should not be touched */
   FEAT_KILLCHASETIMELIMIT,
Index: ircd/ircd_features.c
===================================================================
RCS file: /home/coder-com/cvs/ircu2.10/ircd/ircd_features.c,v
retrieving revision 1.19
diff -u -r1.19 ircd_features.c
--- ircd/ircd_features.c	2002/04/03 15:23:48	1.19
+++ ircd/ircd_features.c	2002/04/12 13:17:10
@@ -252,6 +252,7 @@
   F_B(HOST_HIDING, 0, 0, 0),
   F_S(HIDDEN_HOST, FEAT_CASE, "users.undernet.org", 0),
   F_S(HIDDEN_IP, 0, "127.0.0.1", 0),
+  F_B(LOGIN_ON_CONNECT, 0, 0, 0),
 
   /* features that probably should not be touched */
   F_I(KILLCHASETIMELIMIT, 0, 30, 0),
Index: ircd/m_account.c
===================================================================
RCS file: /home/coder-com/cvs/ircu2.10/ircd/m_account.c,v
retrieving revision 1.2
diff -u -r1.2 m_account.c
--- ircd/m_account.c	2002/02/14 00:20:42	1.2
+++ ircd/m_account.c	2002/04/12 13:17:10
@@ -84,9 +84,11 @@
 #include "ircd.h"
 #include "ircd_reply.h"
 #include "ircd_string.h"
+#include "ircd_alloc.h"
 #include "msg.h"
 #include "numnicks.h"
 #include "s_user.h"
+#include "s_debug.h"
 #include "send.h"
 
 #include <assert.h>
@@ -96,35 +98,108 @@
  *
  * parv[0] = sender prefix
  * parv[1] = numeric of client to act on
- * parv[2] = account name (12 characters or less)
+ * parv[2] = message type
+ *
+ * for *parv[2] == 'R' (remote auth):
+ * parv[3] = account name (12 characters or less)
+ *
+ * for *parv[2] == 'C' (auth check):
+ * parv[3] = numeric of client to check
+ * parv[4] = username
+ * parv[parc-1] = password
+ *
+ * for *parv[2] == 'A' (auth ok) or
+ * for *parv[2] == 'D' (auth denied) or
+ * parv[3] = numeric of client to check 
  */
 int ms_account(struct Client* cptr, struct Client* sptr, int parc,
 	       char* parv[])
 {
   struct Client *acptr;
+  char type;
 
   if (parc < 3)
     return need_more_params(sptr, "ACCOUNT");
+  
+  if (parc < 4) {
+    /* old-style message, remap it */
+    parv[4] = NULL;
+    parv[3] = parv[2];
+    parv[2] = "R";
+  }
 
   if (!IsServer(sptr))
     return protocol_violation(cptr, "ACCOUNT from non-server %s",
 			      cli_name(sptr));
-
-  if (!(acptr = findNUser(parv[1])))
-    return 0; /* Ignore ACCOUNT for a user that QUIT; probably crossed */
-
-  if (IsAccount(acptr))
-    return protocol_violation(cptr, "ACCOUNT for already registered user %s "
-			      "(%s -> %s)", cli_name(acptr),
-			      cli_user(acptr)->account, parv[2]);
-
-  assert(0 == cli_user(acptr)->account[0]);
-
-  ircd_strncpy(cli_user(acptr)->account, parv[2], ACCOUNTLEN);
-  hide_hostmask(acptr, FLAGS_ACCOUNT);
 
-  sendcmdto_serv_butone(sptr, CMD_ACCOUNT, cptr, "%C %s", acptr,
-			cli_user(acptr)->account);
+  type = *parv[2];
+  if (type == 'R') {
+    if (!(acptr = findNUser(parv[1])))
+      return 0; /* Ignore ACCOUNT for a user that QUIT; probably crossed */
+
+    if (IsAccount(acptr))
+      return protocol_violation(cptr, "ACCOUNT for already registered user %s "
+			        "(%s -> %s)", cli_name(acptr),
+			        cli_user(acptr)->account, parv[3]);
+
+    assert(0 == cli_user(acptr)->account[0]);
+
+    ircd_strncpy(cli_user(acptr)->account, parv[3], ACCOUNTLEN);
+    hide_hostmask(acptr, FLAGS_ACCOUNT);
+
+#if 0
+    /* XXX Enable this when all servers speak the same language */
+    sendcmdto_serv_butone(sptr, CMD_ACCOUNT, cptr, "%C R %s", acptr,
+			  cli_user(acptr)->account);
+#else
+    sendcmdto_serv_butone(sptr, CMD_ACCOUNT, cptr, "%C %s", acptr,
+			  cli_user(acptr)->account);
+#endif
+  } else {
+    if (!(acptr = findNUser(parv[1])) && !(acptr = FindNServer(parv[1])))
+      return 0;
+
+    if (type == 'C' && parc < 6)
+      return need_more_params(sptr, "ACCOUNT");
+
+    if (!IsMe(acptr)) {
+      /* in-transit message, forward it */
+      sendcmdto_one(sptr, CMD_ACCOUNT, acptr,
+		    type == 'C' ? "%C %s %s %s :%s" : "%C %s %s",
+		    acptr, parv[2], parv[3], parv[4], parv[parc-1]);
+      return 0;
+    }
+    
+    /* the message is for me, process it */
+    if (type == 'C')
+      return protocol_violation(cptr, "ACCOUNT check (%s %s %s)", parv[3], parv[4], parv[parc-1]);
+
+    if (!(acptr = findNUser(parv[3])))
+      return 0;
+    if (IsRegistered(acptr) || !acptr->cli_cs_service)
+      return protocol_violation(cptr, "Invalid ACCOUNT %s for %s", parv[2], cli_name(acptr));
+    
+    if (type == 'A') {
+      ircd_strncpy(cli_user(acptr)->account, acptr->cli_cs_user, ACCOUNTLEN);
+      hide_hostmask(acptr, FLAGS_ACCOUNT | FLAGS_HIDDENHOST);
+    }
+    
+    sendcmdto_one(&me, CMD_NOTICE, acptr, "%C :AUTHENTICATION %s as %s", acptr,
+    		  type == 'A' ? "SUCCESSFUL" : "FAILED",
+		  acptr->cli_cs_user);
+
+    MyFree(acptr->cli_cs_service);
+    MyFree(acptr->cli_cs_user);
+    MyFree(acptr->cli_cs_pass);
+    acptr->cli_cs_service = acptr->cli_cs_user = acptr->cli_cs_pass = NULL;
+
+    if (type != 'A') {
+      sendcmdto_one(&me, CMD_NOTICE, acptr, "%C :Type /QUOTE PASS to connect anyway", acptr);
+      return 0;
+    }
+    
+    return register_user(acptr, acptr, cli_name(acptr), cli_user(acptr)->username);
+  }
 
   return 0;
 }
Index: ircd/m_pass.c
===================================================================
RCS file: /home/coder-com/cvs/ircu2.10/ircd/m_pass.c,v
retrieving revision 1.7
diff -u -r1.7 m_pass.c
--- ircd/m_pass.c	2001/06/29 15:51:02	1.7
+++ ircd/m_pass.c	2002/04/12 13:17:10
@@ -84,7 +84,11 @@
 #include "client.h"
 #include "ircd_reply.h"
 #include "ircd_string.h"
+#include "ircd_alloc.h"
+#include "ircd_features.h"
+#include "s_user.h"
 #include "send.h"
+#include "struct.h"
 
 #include <assert.h>
 
@@ -99,15 +103,28 @@
   assert(cptr == sptr);
   assert(!IsRegistered(sptr));
 
-  if (EmptyString(password))
-    return need_more_params(cptr, "PASS");
-
   /* TODO: For protocol negotiation */
 #if 0
   if (ircd_strcmp(password,"PROT")==0) {
   	/* Do something here */
   }
 #endif
-  ircd_strncpy(cli_passwd(cptr), password, PASSWDLEN);
+
+  if (!EmptyString(password))
+    ircd_strncpy(cli_passwd(cptr), password, PASSWDLEN);
+
+  if (!feature_bool(FEAT_LOGIN_ON_CONNECT) || cptr->cli_cs_service)
+    return 0;
+
+  if (parc > 3) {
+    DupString(cptr->cli_cs_service, parv[parc-3]);
+    DupString(cptr->cli_cs_user, parv[parc-2]);
+    DupString(cptr->cli_cs_pass, parv[parc-1]);
+  }
+
+  /* Deal with password retries */
+  if ((cli_name(cptr))[0] && cli_cookie(cptr) == COOKIE_VERIFIED)
+    return register_user(cptr, cptr, cli_name(cptr), cli_user(cptr)->username);
+
   return 0;
 }
Index: ircd/m_user.c
===================================================================
RCS file: /home/coder-com/cvs/ircu2.10/ircd/m_user.c,v
retrieving revision 1.7
diff -u -r1.7 m_user.c
--- ircd/m_user.c	2001/06/08 23:12:17	1.7
+++ ircd/m_user.c	2002/04/12 13:17:10
@@ -142,16 +142,14 @@
 
   user->server = &me;
   ircd_strncpy(cli_info(cptr), info, REALLEN);
+  ircd_strncpy(user->username, username, USERLEN);
+  ircd_strncpy(user->host, cli_sockhost(cptr), HOSTLEN);
 
   if ((cli_name(cptr))[0] && cli_cookie(cptr) == COOKIE_VERIFIED) {
     /*
      * NICK and PONG already received, now we have USER...
      */
     return register_user(cptr, sptr, cli_name(sptr), username);
-  }
-  else {
-    ircd_strncpy(user->username, username, USERLEN);
-    ircd_strncpy(user->host, cli_sockhost(cptr), HOSTLEN);
   }
   return 0;
 }
Index: ircd/s_user.c
===================================================================
RCS file: /home/coder-com/cvs/ircu2.10/ircd/s_user.c,v
retrieving revision 1.58
diff -u -r1.58 s_user.c
--- ircd/s_user.c	2002/04/05 11:36:59	1.58
+++ ircd/s_user.c	2002/04/12 13:17:10
@@ -394,6 +394,25 @@
   parv[0] = cli_name(sptr);
   parv[1] = parv[2] = NULL;
 
+  if (MyConnect(sptr) && sptr->cli_cs_service && sptr->cli_cs_user && sptr->cli_cs_pass) {
+    struct Client *acptr;
+    
+    if (!(acptr = FindUser(sptr->cli_cs_service)) || !IsChannelService(acptr)) {
+      sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Service '%s' is not available", sptr, sptr->cli_cs_service);
+      sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Type /QUOTE PASS to connect anyway", sptr);
+      MyFree(sptr->cli_cs_service);
+      MyFree(sptr->cli_cs_user);
+      MyFree(sptr->cli_cs_pass);
+      sptr->cli_cs_service = sptr->cli_cs_user = sptr->cli_cs_pass = NULL;
+    } else {
+      sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Attempting service login to %s",
+      		    sptr, cli_name(acptr));
+      sendcmdto_one(&me, CMD_ACCOUNT, acptr, "%C C %s%s %s :%s", acptr,
+		    NumNick(sptr), sptr->cli_cs_user, sptr->cli_cs_pass);
+    }
+    return 0;
+  }
+
   if (MyConnect(sptr))
   {
     static time_t last_too_many1;
@@ -441,7 +460,9 @@
         IPcheck_connect_fail(cli_ip(sptr));
         return exit_client(cptr, sptr, &me, "Unknown error -- Try again");
     }
-    ircd_strncpy(user->host, cli_sockhost(sptr), HOSTLEN);
+    /* The host might be already set from login-on-connect */
+    if (!HasHiddenHost(sptr))
+	    ircd_strncpy(user->host, cli_sockhost(sptr), HOSTLEN);
     ircd_strncpy(user->realhost, cli_sockhost(sptr), HOSTLEN);
     aconf = cli_confs(sptr)->value.aconf;
 
--- /dev/null	Thu Aug 24 12:00:32 2000
+++ doc/readme.login-on-connect	Fri Apr 12 16:16:56 2002
@@ -0,0 +1,182 @@
+1. This feature is experimental.
+
+2. The main point is to allow clients to log in to a service bot (i.e., X)
+*before* being announced to the network. Otherwise, a combination of a
+malicious user, /ISON, /USERIP and low latency can reveal it's real host/IP
+before he gets a chance to log in and set himself +x
+
+3. Client<->Server changes:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The PASS command now has the following syntax:
+
+PASS [optional Client block password] <bot nick> <username> :<passphrase>
+
+If the client specifies sends such a password message, after sending NICK,
+USER and PONG, it's username/passphrase are sent to the specified bot
+for validation, while holding the client in the 'registration' state.
+If the authentication succeeds, the client's account and umode +x are set,
+after which he is introduced to the network, continuing the regular connect
+phase. If authentication fails (or the bot is not on the network), the user
+is given a chance to retry (he can do this by sending another PASS command),
+or to disconnect from the server. If he wishes to connect without a hidden
+hostmask, he can send a PASS command with no parameters.
+
+4. Server<->Server changes:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The ACCOUNT message now has the following syntax:
+
+Auth check: 
+<client's server numeric> AC <bot's numeric> C <client's numeric> <username> :<passphrase>
+Servers send this message to request a service bot to authenticate the client.
+Note that <client's numeric> will only be used by the originating server for
+matching auth replies, as the user has not yet been announced to the network.
+Hubs will have to propagate this message as-is towards the service bot,
+much like they do for PRIVMSGs.
+
+Auth reply:
+<bot's server numeric> AC <client's server numeric> A|D <client's numeric>
+Service bots send this in reply to an 'auth check' message from a server.
+"A" stands for "accepted", "D" for "denied". Again, hubs will have
+to proagate this message back to the client's server.
+
+Remote auth:
+<bot's server numeric> AC <client numeric> R <account>
+This is the current message used by service bots to announce the network
+that a user has logged in. The old format is still supported:
+<bot's server numeric> AC <client numeric> <account>
+
+5. ircu code changes
+~~~~~~~~~~~~~~~~~~~~
+A new feature, FEAT_LOGIN_ON_CONNECT (default FALSE) will specify whether
+ircu will honour the login scheme as specified above for the PASS command.
+ircu will route ACCOUNT messages anyway, regardless of this feature's value.
+
+6. GNUWorld patches
+~~~~~~~~~~~~~~~~~~~
+A patch follows that will implement auth checks and replies.
+
+Index: mod.cservice/cservice.cc
+===================================================================
+RCS file: /cvsroot/gnuworld/gnuworld/mod.cservice/cservice.cc,v
+retrieving revision 1.213
+diff -u -r1.213 cservice.cc
+--- mod.cservice/cservice.cc	10 Apr 2002 19:00:10 -0000	1.213
++++ mod.cservice/cservice.cc	12 Apr 2002 12:33:45 -0000
+@@ -2491,15 +2491,45 @@
+ 	{
+ 	case EVT_ACCOUNT:
+ 		{
+-		iClient* tmpUser = static_cast< iClient* >( data1 ) ;
+-		networkData* tmpData = static_cast< networkData* >(tmpUser->getCustomData(this) ) ;
+-		/* Lookup this user account, if its not there.. trouble */
+-		sqlUser* theUser = getUserRecord(tmpUser->getAccount());
+-		if (theUser)
+-			{
+-			tmpData->currentUser = theUser;
+-			theUser->addAuthedClient(tmpUser);
++		char *ac_type = static_cast <char *> (data1);
++		if (*ac_type == 'R') {
++			iClient* tmpUser = static_cast< iClient* >( data2 ) ;
++			networkData* tmpData = static_cast< networkData* >(tmpUser->getCustomData(this) ) ;
++			/* Lookup this user account, if its not there.. trouble */
++			sqlUser* theUser = getUserRecord(tmpUser->getAccount());
++			if (theUser)
++				{
++				tmpData->currentUser = theUser;
++				theUser->addAuthedClient(tmpUser);
++				}
++		} else if (*ac_type == 'C') {
++			// server prefix, client prefix, username, password
++			char **param_list = static_cast <char **> (data2);
++			sqlUser* theUser = getUserRecord(param_list[2]);
++			strstream ac;
++
++			LogDebugMessage("Checking account: user=%s pass=%s ok=%d susp=%d maxlog=%d/%d",
++				param_list[2],
++				param_list[3],
++				isPasswordRight(theUser, param_list[3]),
++				theUser->getFlag(sqlUser::F_GLOBAL_SUSPEND),
++				theUser->networkClientList.size() + 1,
++				theUser->getMaxLogins());
++
++			ac << getCharYY() << " AC " << param_list[0];
++			
++			if (theUser && !theUser->getFlag(sqlUser::F_GLOBAL_SUSPEND) &&
++				isPasswordRight(theUser, param_list[3]) &&
++				theUser->networkClientList.size() + 1 <= theUser->getMaxLogins()) {
++				ac << " A ";
++			} else {
++				ac << " D ";
+ 			}
++
++			ac << param_list[1] << ends;
++			Write(ac);
++			delete[] ac.str();
++		}
+ 		break;
+ 		}
+ 	case EVT_BURST_ACK:
+Index: src/msg_AC.cc
+===================================================================
+RCS file: /cvsroot/gnuworld/gnuworld/src/msg_AC.cc,v
+retrieving revision 1.1
+diff -u -r1.1 msg_AC.cc
+--- src/msg_AC.cc	12 Jan 2002 21:42:17 -0000	1.1
++++ src/msg_AC.cc	12 Apr 2002 12:33:46 -0000
+@@ -14,23 +14,48 @@
+  * SOURCE AC TARGET ACCOUNT
+  * Eg:
+  * AXAAA AC BQrTd Gte
++ *
++ * AX AC BQrTd R Gte
++ * BQ AC AX C BqrTd Gte :Gte's Pass
+  */
+-int xServer::MSG_AC( xParameters& Param )
++int xServer::MSG_AC(xParameters &Param)
+ {
+ /*
+  * First, update this users information.
+  */
+ 
+-iClient* theClient = Network->findClient(Param[1]);
+-if(!theClient)
+-	{
+-		return 0;
+-	}
++const char *numeric = NULL, *account = NULL;
++static char *ac_type_C = "C";
++static char *ac_type_R = "R";
+ 
+-theClient->setAccount(Param[2]);
++if (Param.size() < 4) {
++	numeric = Param[1];
++	account = Param[2];
++} else if (Param[2][0] == 'R') {
++	numeric = Param[1];
++	account = Param[3];
++} else if (Param[2][0] == 'C') {
++	const char *param_list[4];
++	param_list[0] = Param[0];
++	param_list[1] = Param[3];
++	param_list[2] = Param[4];
++	param_list[3] = Param[5];
++	PostEvent(EVT_ACCOUNT, static_cast <void *> (ac_type_C), static_cast <void *> (param_list));
++	return 0;
++}
++
++if (!numeric || !account)
++	return 0;
++
++iClient *theClient = Network->findClient(numeric);
++if (!theClient)
++	return 0;
++
++theClient->setAccount(account);
++PostEvent(EVT_ACCOUNT, static_cast <void *> (ac_type_R), static_cast <void *> (theClient)) ;
+ 
+-PostEvent( EVT_ACCOUNT, static_cast< void* >( theClient ) ) ;
+ return 0;
++
+ }
+ 
+ } // namespace gnuworld
