]> jfr.im git - solanum.git/blobdiff - ircd/s_serv.c
Implement hook priorities
[solanum.git] / ircd / s_serv.c
index cf1a2b6670d8088b51677b3397a62cfdb19670e9..a001cd163ba850e94bdf5fb716927410b9916300 100644 (file)
@@ -20,8 +20,6 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
  *  USA
- *
- *  $Id: s_serv.c 3550 2007-08-09 06:47:26Z nenolod $
  */
 
 #include "stdinc.h"
 #include "s_serv.h"
 #include "class.h"
 #include "client.h"
-#include "common.h"
 #include "hash.h"
 #include "match.h"
 #include "ircd.h"
 #include "ircd_defs.h"
 #include "numeric.h"
 #include "packet.h"
-#include "res.h"
 #include "s_conf.h"
 #include "s_newconf.h"
 #include "logger.h"
 #include "capability.h"
 #include "s_assert.h"
 
-#ifndef INADDR_NONE
-#define INADDR_NONE ((unsigned int) 0xffffffff)
-#endif
-
 int MaxConnectionCount = 1;
 int MaxClientCount = 1;
 int refresh_user_links = 0;
@@ -73,6 +65,7 @@ static char buf[BUFSIZE];
  * extra argument to "PASS" takes care of checking that.  -orabidoo
  */
 struct CapabilityIndex *serv_capindex = NULL;
+struct CapabilityIndex *cli_capindex = NULL;
 
 unsigned int CAP_CAP;
 unsigned int CAP_QS;
@@ -95,6 +88,15 @@ unsigned int CAP_EOPMOD;
 unsigned int CAP_BAN;
 unsigned int CAP_MLOCK;
 
+unsigned int CLICAP_MULTI_PREFIX;
+unsigned int CLICAP_ACCOUNT_NOTIFY;
+unsigned int CLICAP_EXTENDED_JOIN;
+unsigned int CLICAP_AWAY_NOTIFY;
+unsigned int CLICAP_USERHOST_IN_NAMES;
+unsigned int CLICAP_CAP_NOTIFY;
+unsigned int CLICAP_CHGHOST;
+unsigned int CLICAP_ECHO_MESSAGE;
+
 /*
  * initialize our builtin capability table. --nenolod
  */
@@ -107,33 +109,45 @@ init_builtin_capabs(void)
        CAP_CAP = capability_put_anonymous(serv_capindex);
        CAP_TS6 = capability_put_anonymous(serv_capindex);
 
-       CAP_QS = capability_put(serv_capindex, "QS");
-       CAP_EX = capability_put(serv_capindex, "EX");
-       CAP_CHW = capability_put(serv_capindex, "CHW");
-       CAP_IE = capability_put(serv_capindex, "IE");
-       CAP_KLN = capability_put(serv_capindex, "KLN");
-       CAP_KNOCK = capability_put(serv_capindex, "KNOCK");
-       CAP_ZIP = capability_put(serv_capindex, "ZIP");
-       CAP_TB = capability_put(serv_capindex, "TB");
-       CAP_UNKLN = capability_put(serv_capindex, "UNKLN");
-       CAP_CLUSTER = capability_put(serv_capindex, "CLUSTER");
-       CAP_ENCAP = capability_put(serv_capindex, "ENCAP");
-       CAP_SERVICE = capability_put(serv_capindex, "SERVICES");
-       CAP_RSFNC = capability_put(serv_capindex, "RSFNC");
-       CAP_SAVE = capability_put(serv_capindex, "SAVE");
-       CAP_EUID = capability_put(serv_capindex, "EUID");
-       CAP_EOPMOD = capability_put(serv_capindex, "EOPMOD");
-       CAP_BAN = capability_put(serv_capindex, "BAN");
-       CAP_MLOCK = capability_put(serv_capindex, "MLOCK");
+       CAP_QS = capability_put(serv_capindex, "QS", NULL);
+       CAP_EX = capability_put(serv_capindex, "EX", NULL);
+       CAP_CHW = capability_put(serv_capindex, "CHW", NULL);
+       CAP_IE = capability_put(serv_capindex, "IE", NULL);
+       CAP_KLN = capability_put(serv_capindex, "KLN", NULL);
+       CAP_KNOCK = capability_put(serv_capindex, "KNOCK", NULL);
+       CAP_ZIP = capability_put(serv_capindex, "ZIP", NULL);
+       CAP_TB = capability_put(serv_capindex, "TB", NULL);
+       CAP_UNKLN = capability_put(serv_capindex, "UNKLN", NULL);
+       CAP_CLUSTER = capability_put(serv_capindex, "CLUSTER", NULL);
+       CAP_ENCAP = capability_put(serv_capindex, "ENCAP", NULL);
+       CAP_SERVICE = capability_put(serv_capindex, "SERVICES", NULL);
+       CAP_RSFNC = capability_put(serv_capindex, "RSFNC", NULL);
+       CAP_SAVE = capability_put(serv_capindex, "SAVE", NULL);
+       CAP_EUID = capability_put(serv_capindex, "EUID", NULL);
+       CAP_EOPMOD = capability_put(serv_capindex, "EOPMOD", NULL);
+       CAP_BAN = capability_put(serv_capindex, "BAN", NULL);
+       CAP_MLOCK = capability_put(serv_capindex, "MLOCK", NULL);
 
        capability_require(serv_capindex, "QS");
        capability_require(serv_capindex, "EX");
        capability_require(serv_capindex, "IE");
        capability_require(serv_capindex, "ENCAP");
+
+       cli_capindex = capability_index_create("client capabilities");
+
+       CLICAP_MULTI_PREFIX = capability_put(cli_capindex, "multi-prefix", NULL);
+       CLICAP_ACCOUNT_NOTIFY = capability_put(cli_capindex, "account-notify", NULL);
+       CLICAP_EXTENDED_JOIN = capability_put(cli_capindex, "extended-join", NULL);
+       CLICAP_AWAY_NOTIFY = capability_put(cli_capindex, "away-notify", NULL);
+       CLICAP_USERHOST_IN_NAMES = capability_put(cli_capindex, "userhost-in-names", NULL);
+       CLICAP_CAP_NOTIFY = capability_put(cli_capindex, "cap-notify", NULL);
+       CLICAP_CHGHOST = capability_put(cli_capindex, "chghost", NULL);
+       CLICAP_ECHO_MESSAGE = capability_put(cli_capindex, "echo-message", NULL);
 }
 
 static CNCB serv_connect_callback;
 static CNCB serv_connect_ssl_callback;
+static SSL_OPEN_CB serv_connect_ssl_open_callback;
 
 /*
  * hunt_server - Do the basic thing in delivering the message (command)
@@ -243,7 +257,7 @@ try_connections(void *unused)
        struct server_conf *tmp_p;
        struct Class *cltmp;
        rb_dlink_node *ptr;
-       int connecting = FALSE;
+       bool connecting = false;
        int confrq = 0;
        time_t next = 0;
 
@@ -255,7 +269,7 @@ try_connections(void *unused)
                        continue;
 
                /* don't allow ssl connections if ssl isn't setup */
-               if(ServerConfSSL(tmp_p) && (!ssl_ok || !get_ssld_count()))
+               if(ServerConfSSL(tmp_p) && (!ircd_ssl_ok || !get_ssld_count()))
                        continue;
 
                cltmp = tmp_p->class;
@@ -288,7 +302,7 @@ try_connections(void *unused)
                        server_p = tmp_p;
 
                        /* We connect only one at time... */
-                       connecting = TRUE;
+                       connecting = true;
                }
 
                if((next > tmp_p->hold) || (next == 0))
@@ -330,6 +344,9 @@ check_server(const char *name, struct Client *client_p)
        rb_dlink_node *ptr;
        int error = -1;
        const char *encr;
+       bool name_matched = false;
+       bool host_matched = false;
+       bool certfp_failed = false;
 
        s_assert(NULL != client_p);
        if(client_p == NULL)
@@ -343,6 +360,8 @@ check_server(const char *name, struct Client *client_p)
 
        RB_DLINK_FOREACH(ptr, server_conf_list.head)
        {
+               struct rb_sockaddr_storage client_addr;
+
                tmp_p = ptr->data;
 
                if(ServerConfIllegal(tmp_p))
@@ -351,14 +370,21 @@ check_server(const char *name, struct Client *client_p)
                if(!match(tmp_p->name, name))
                        continue;
 
-               error = -3;
+               name_matched = true;
+
+               if(rb_inet_pton_sock(client_p->sockhost, &client_addr) <= 0)
+                       SET_SS_FAMILY(&client_addr, AF_UNSPEC);
 
-               /* XXX: Fix me for IPv6 */
-               /* XXX sockhost is the IPv4 ip as a string */
-               if(match(tmp_p->host, client_p->host) ||
-                  match(tmp_p->host, client_p->sockhost))
+               if((tmp_p->connect_host && match(tmp_p->connect_host, client_p->host))
+                       || (GET_SS_FAMILY(&client_addr) == GET_SS_FAMILY(&tmp_p->connect4)
+                               && comp_with_mask_sock((struct sockaddr *)&client_addr,
+                                       (struct sockaddr *)&tmp_p->connect4, 32))
+                       || (GET_SS_FAMILY(&client_addr) == GET_SS_FAMILY(&tmp_p->connect6)
+                               && comp_with_mask_sock((struct sockaddr *)&client_addr,
+                                       (struct sockaddr *)&tmp_p->connect6, 128))
+                       )
                {
-                       error = -2;
+                       host_matched = true;
 
                        if(tmp_p->passwd)
                        {
@@ -380,8 +406,10 @@ check_server(const char *name, struct Client *client_p)
 
                        if(tmp_p->certfp)
                        {
-                               if(!client_p->certfp || strcasecmp(tmp_p->certfp, client_p->certfp) != 0)
+                               if(!client_p->certfp || rb_strcasecmp(tmp_p->certfp, client_p->certfp) != 0) {
+                                       certfp_failed = true;
                                        continue;
+                               }
                        }
 
                        server_p = tmp_p;
@@ -390,13 +418,28 @@ check_server(const char *name, struct Client *client_p)
        }
 
        if(server_p == NULL)
+       {
+               /* return the most specific error */
+               if(certfp_failed)
+                       error = -6;
+               else if(host_matched)
+                       error = -2;
+               else if(name_matched)
+                       error = -3;
+
                return error;
+       }
 
        if(ServerConfSSL(server_p) && client_p->localClient->ssl_ctl == NULL)
        {
                return -5;
        }
 
+       if (client_p->localClient->att_sconf && client_p->localClient->att_sconf->class == server_p->class) {
+               /* this is an outgoing connection that is already attached to the correct class */
+       } else if (CurrUsers(server_p->class) >= MaxUsers(server_p->class)) {
+               return -7;
+       }
        attach_server_conf(client_p, server_p);
 
        /* clear ZIP/TB if they support but we dont want them */
@@ -418,7 +461,6 @@ check_server(const char *name, struct Client *client_p)
  *             - int flag of capabilities that this server has
  * output      - NONE
  * side effects        - send the CAPAB line to a server  -orabidoo
- *
  */
 void
 send_capabilities(struct Client *client_p, unsigned int cap_can_send)
@@ -462,7 +504,7 @@ burst_ban(struct Client *client_p)
                         * to other servers, so rewrite to our server
                         * name.
                         */
-                       rb_strlcpy(operbuf, aconf->info.oper, sizeof buf);
+                       rb_strlcpy(operbuf, aconf->info.oper, sizeof operbuf);
                        p = strrchr(operbuf, '{');
                        if (p != NULL &&
                                        operbuf + sizeof operbuf - p > (ptrdiff_t)(melen + 2))
@@ -504,7 +546,7 @@ burst_modes_TS6(struct Client *client_p, struct Channel *chptr,
        int mlen;
        int cur_len;
 
-       cur_len = mlen = rb_sprintf(buf, ":%s BMASK %ld %s %c :",
+       cur_len = mlen = sprintf(buf, ":%s BMASK %ld %s %c :",
                                    me.id, (long) chptr->channelts, chptr->chname, flag);
        t = buf + mlen;
 
@@ -532,9 +574,9 @@ burst_modes_TS6(struct Client *client_p, struct Channel *chptr,
                }
 
                if (banptr->forward)
-                       rb_sprintf(t, "%s$%s ", banptr->banstr, banptr->forward);
+                       sprintf(t, "%s$%s ", banptr->banstr, banptr->forward);
                else
-                       rb_sprintf(t, "%s ", banptr->banstr);
+                       sprintf(t, "%s ", banptr->banstr);
                t += tlen;
                cur_len += tlen;
        }
@@ -578,6 +620,9 @@ burst_TS6(struct Client *client_p)
                if(!IsPerson(target_p))
                        continue;
 
+               if(MyClient(target_p->from) && target_p->localClient->att_sconf != NULL && ServerConfNoExport(target_p->localClient->att_sconf))
+                       continue;
+
                send_umode(NULL, target_p, 0, ubuf);
                if(!*ubuf)
                {
@@ -624,6 +669,12 @@ burst_TS6(struct Client *client_p)
                                   use_id(target_p),
                                   target_p->user->away);
 
+               if(IsOper(target_p) && target_p->user && target_p->user->opername && target_p->user->privset)
+                       sendto_one(client_p, ":%s OPER %s %s",
+                                       use_id(target_p),
+                                       target_p->user->opername,
+                                       target_p->user->privset->name);
+
                hclientinfo.target = target_p;
                call_hook(h_burst_client, &hclientinfo);
        }
@@ -635,7 +686,7 @@ burst_TS6(struct Client *client_p)
                if(*chptr->chname != '#')
                        continue;
 
-               cur_len = mlen = rb_sprintf(buf, ":%s SJOIN %ld %s %s :", me.id,
+               cur_len = mlen = sprintf(buf, ":%s SJOIN %ld %s %s :", me.id,
                                (long) chptr->channelts, chptr->chname,
                                channel_modes(chptr, client_p));
 
@@ -659,7 +710,7 @@ burst_TS6(struct Client *client_p)
                                t = buf + mlen;
                        }
 
-                       rb_sprintf(t, "%s%s ", find_channel_status(msptr, 1),
+                       sprintf(t, "%s%s ", find_channel_status(msptr, 1),
                                   use_id(msptr->client_p));
 
                        cur_len += tlen;
@@ -780,19 +831,6 @@ server_estab(struct Client *client_p)
        /* Its got identd , since its a server */
        SetGotId(client_p);
 
-       /* If there is something in the serv_list, it might be this
-        * connecting server..
-        */
-       if(!ServerInfo.hub && serv_list.head)
-       {
-               if(client_p != serv_list.head->data || serv_list.head->next)
-               {
-                       ServerStats.is_ref++;
-                       sendto_one(client_p, "ERROR :I'm a leaf not a hub");
-                       return exit_client(client_p, client_p, client_p, "I'm a leaf");
-               }
-       }
-
        if(IsUnknown(client_p))
        {
                /* the server may be linking based on certificate fingerprint now. --nenolod */
@@ -800,7 +838,7 @@ server_estab(struct Client *client_p)
                           EmptyString(server_p->spasswd) ? "*" : server_p->spasswd, TS_CURRENT, me.id);
 
                /* pass info to new server */
-               send_capabilities(client_p, default_server_capabs
+               send_capabilities(client_p, default_server_capabs | CAP_MASK
                                  | (ServerConfCompressed(server_p) ? CAP_ZIP_SUPPORTED : 0)
                                  | (ServerConfTb(server_p) ? CAP_TB : 0));
 
@@ -818,14 +856,13 @@ server_estab(struct Client *client_p)
        {
                start_zlib_session(client_p);
        }
-       sendto_one(client_p, "SVINFO %d %d 0 :%ld", TS_CURRENT, TS_MIN, (long int)rb_current_time());
 
        client_p->servptr = &me;
 
        if(IsAnyDead(client_p))
                return CLIENT_EXITED;
 
-       SetServer(client_p);
+       sendto_one(client_p, "SVINFO %d %d 0 :%ld", TS_CURRENT, TS_MIN, (long int)rb_current_time());
 
        rb_dlinkAdd(client_p, &client_p->lnode, &me.serv->servers);
        rb_dlinkMoveNode(&client_p->localClient->tnode, &unknown_list, &serv_list);
@@ -837,6 +874,7 @@ server_estab(struct Client *client_p)
        add_to_client_hash(client_p->name, client_p);
        /* doesnt duplicate client_p->serv if allocated this struct already */
        make_server(client_p);
+       SetServer(client_p);
 
        client_p->serv->caps = client_p->localClient->caps;
 
@@ -869,7 +907,7 @@ server_estab(struct Client *client_p)
        hdata.target = client_p;
        call_hook(h_server_introduced, &hdata);
 
-       rb_snprintf(note, sizeof(note), "Server: %s", client_p->name);
+       snprintf(note, sizeof(note), "Server: %s", client_p->name);
        rb_note(client_p->localClient->F, note);
 
        /*
@@ -884,6 +922,9 @@ server_estab(struct Client *client_p)
                if(target_p == client_p)
                        continue;
 
+               if(target_p->localClient->att_sconf != NULL && ServerConfNoExport(target_p->localClient->att_sconf))
+                       continue;
+
                if(has_id(target_p) && has_id(client_p))
                {
                        sendto_one(target_p, ":%s SID %s 2 %s :%s%s",
@@ -932,6 +973,10 @@ server_estab(struct Client *client_p)
                if(IsMe(target_p) || target_p->from == client_p)
                        continue;
 
+               /* don't distribute downstream leaves of servers that are no-export */
+               if(MyClient(target_p->from) && target_p->from->localClient->att_sconf != NULL && ServerConfNoExport(target_p->from->localClient->att_sconf))
+                       continue;
+
                /* presumption, if target has an id, so does its uplink */
                if(has_id(client_p) && has_id(target_p))
                        sendto_one(client_p, ":%s SID %s %d %s :%s%s",
@@ -971,135 +1016,6 @@ server_estab(struct Client *client_p)
  *   -- adrian
  */
 
-static int
-serv_connect_resolved(struct Client *client_p)
-{
-       struct rb_sockaddr_storage myipnum;
-       char vhoststr[HOSTIPLEN];
-       struct server_conf *server_p;
-       uint16_t port;
-
-       if((server_p = client_p->localClient->att_sconf) == NULL)
-       {
-               sendto_realops_snomask(SNO_GENERAL, is_remote_connect(client_p) ? L_NETWIDE : L_ALL, "Lost connect{} block for %s",
-                               client_p->name);
-               exit_client(client_p, client_p, &me, "Lost connect{} block");
-               return 0;
-       }
-
-#ifdef RB_IPV6
-       if(client_p->localClient->ip.ss_family == AF_INET6)
-               port = ntohs(((struct sockaddr_in6 *)&client_p->localClient->ip)->sin6_port);
-       else
-#endif
-               port = ntohs(((struct sockaddr_in *)&client_p->localClient->ip)->sin_port);
-
-       if(ServerConfVhosted(server_p))
-       {
-               memcpy(&myipnum, &server_p->my_ipnum, sizeof(server_p->my_ipnum));
-               ((struct sockaddr_in *)&myipnum)->sin_port = 0;
-               myipnum.ss_family = server_p->aftype;
-
-       }
-       else if(server_p->aftype == AF_INET && ServerInfo.specific_ipv4_vhost)
-       {
-               memcpy(&myipnum, &ServerInfo.ip, sizeof(ServerInfo.ip));
-               ((struct sockaddr_in *)&myipnum)->sin_port = 0;
-               myipnum.ss_family = AF_INET;
-               SET_SS_LEN(&myipnum, sizeof(struct sockaddr_in));
-       }
-
-#ifdef RB_IPV6
-       else if((server_p->aftype == AF_INET6) && ServerInfo.specific_ipv6_vhost)
-       {
-               memcpy(&myipnum, &ServerInfo.ip6, sizeof(ServerInfo.ip6));
-               ((struct sockaddr_in6 *)&myipnum)->sin6_port = 0;
-               myipnum.ss_family = AF_INET6;
-               SET_SS_LEN(&myipnum, sizeof(struct sockaddr_in6));
-       }
-#endif
-       else
-       {
-               /* log */
-               ilog(L_SERVER, "Connecting to %s[%s] port %d (%s)", client_p->name, client_p->sockhost, port,
-#ifdef RB_IPV6
-                               server_p->aftype == AF_INET6 ? "IPv6" :
-#endif
-                               (server_p->aftype == AF_INET ? "IPv4" : "?"));
-
-               if(ServerConfSSL(server_p))
-               {
-                       rb_connect_tcp(client_p->localClient->F, (struct sockaddr *)&client_p->localClient->ip,
-                                        NULL, 0, serv_connect_ssl_callback,
-                                        client_p, ConfigFileEntry.connect_timeout);
-               }
-               else
-                       rb_connect_tcp(client_p->localClient->F, (struct sockaddr *)&client_p->localClient->ip,
-                                        NULL, 0, serv_connect_callback,
-                                        client_p, ConfigFileEntry.connect_timeout);
-                return 1;
-       }
-
-       /* log */
-       rb_inet_ntop_sock((struct sockaddr *)&myipnum, vhoststr, sizeof vhoststr);
-       ilog(L_SERVER, "Connecting to %s[%s] port %d (%s) (vhost %s)", client_p->name, client_p->sockhost, port,
-#ifdef RB_IPV6
-                       server_p->aftype == AF_INET6 ? "IPv6" :
-#endif
-                       (server_p->aftype == AF_INET ? "IPv4" : "?"), vhoststr);
-
-
-       if(ServerConfSSL(server_p))
-               rb_connect_tcp(client_p->localClient->F, (struct sockaddr *)&client_p->localClient->ip,
-                                (struct sockaddr *) &myipnum,
-                                GET_SS_LEN(&myipnum), serv_connect_ssl_callback, client_p,
-                                ConfigFileEntry.connect_timeout);
-       else
-               rb_connect_tcp(client_p->localClient->F, (struct sockaddr *)&client_p->localClient->ip,
-                                (struct sockaddr *) &myipnum,
-                                GET_SS_LEN(&myipnum), serv_connect_callback, client_p,
-                                ConfigFileEntry.connect_timeout);
-
-       return 1;
-}
-
-static void
-serv_connect_dns_callback(void *vptr, struct DNSReply *reply)
-{
-       struct Client *client_p = vptr;
-       uint16_t port;
-
-       rb_free(client_p->localClient->dnsquery);
-       client_p->localClient->dnsquery = NULL;
-
-       if (reply == NULL)
-       {
-               sendto_realops_snomask(SNO_GENERAL, is_remote_connect(client_p) ? L_NETWIDE : L_ALL, "Cannot resolve hostname for %s",
-                               client_p->name);
-               ilog(L_SERVER, "Cannot resolve hostname for %s",
-                               log_client_name(client_p, HIDE_IP));
-               exit_client(client_p, client_p, &me, "Cannot resolve hostname");
-               return;
-       }
-#ifdef RB_IPV6
-       if(reply->addr.ss_family == AF_INET6)
-               port = ((struct sockaddr_in6 *)&client_p->localClient->ip)->sin6_port;
-       else
-#endif
-               port = ((struct sockaddr_in *)&client_p->localClient->ip)->sin_port;
-       memcpy(&client_p->localClient->ip, &reply->addr, sizeof(client_p->localClient->ip));
-#ifdef RB_IPV6
-       if(reply->addr.ss_family == AF_INET6)
-               ((struct sockaddr_in6 *)&client_p->localClient->ip)->sin6_port = port;
-       else
-#endif
-               ((struct sockaddr_in *)&client_p->localClient->ip)->sin_port = port;
-       /* Set sockhost properly now -- jilles */
-       rb_inet_ntop_sock((struct sockaddr *)&client_p->localClient->ip,
-                       client_p->sockhost, sizeof client_p->sockhost);
-       serv_connect_resolved(client_p);
-}
-
 /*
  * serv_connect() - initiate a server connection
  *
@@ -1121,14 +1037,69 @@ int
 serv_connect(struct server_conf *server_p, struct Client *by)
 {
        struct Client *client_p;
-       struct rb_sockaddr_storage theiripnum;
-       rb_fde_t *F;
+       struct sockaddr_storage sa_connect[2];
+       struct sockaddr_storage sa_bind[ARRAY_SIZE(sa_connect)];
        char note[HOSTLEN + 10];
+       rb_fde_t *F;
 
        s_assert(server_p != NULL);
        if(server_p == NULL)
                return 0;
 
+       for (int i = 0; i < ARRAY_SIZE(sa_connect); i++) {
+               SET_SS_FAMILY(&sa_connect[i], AF_UNSPEC);
+               SET_SS_FAMILY(&sa_bind[i], AF_UNSPEC);
+       }
+
+       if(server_p->aftype == AF_UNSPEC
+               && GET_SS_FAMILY(&server_p->connect4) == AF_INET
+               && GET_SS_FAMILY(&server_p->connect6) == AF_INET6)
+       {
+               if(rand() % 2 == 0)
+               {
+                       sa_connect[0] = server_p->connect4;
+                       sa_connect[1] = server_p->connect6;
+                       sa_bind[0] = server_p->bind4;
+                       sa_bind[1] = server_p->bind6;
+               }
+               else
+               {
+                       sa_connect[0] = server_p->connect6;
+                       sa_connect[1] = server_p->connect4;
+                       sa_bind[0] = server_p->bind6;
+                       sa_bind[1] = server_p->bind4;
+               }
+       }
+       else if(server_p->aftype == AF_INET || GET_SS_FAMILY(&server_p->connect4) == AF_INET)
+       {
+               sa_connect[0] = server_p->connect4;
+               sa_bind[0] = server_p->bind4;
+       }
+       else if(server_p->aftype == AF_INET6 || GET_SS_FAMILY(&server_p->connect6) == AF_INET6)
+       {
+               sa_connect[0] = server_p->connect6;
+               sa_bind[0] = server_p->bind6;
+       }
+
+       /* log */
+#ifdef HAVE_LIBSCTP
+       if (ServerConfSCTP(server_p) && GET_SS_FAMILY(&sa_connect[1]) != AF_UNSPEC) {
+               char buf2[HOSTLEN + 1];
+
+               buf[0] = 0;
+               buf2[0] = 0;
+               rb_inet_ntop_sock((struct sockaddr *)&sa_connect[0], buf, sizeof(buf));
+               rb_inet_ntop_sock((struct sockaddr *)&sa_connect[1], buf2, sizeof(buf2));
+               ilog(L_SERVER, "Connect to *[%s] @%s&%s", server_p->name, buf, buf2);
+       } else {
+#else
+       {
+#endif
+               buf[0] = 0;
+               rb_inet_ntop_sock((struct sockaddr *)&sa_connect[0], buf, sizeof(buf));
+               ilog(L_SERVER, "Connect to *[%s] @%s", server_p->name, buf);
+       }
+
        /*
         * Make sure this server isn't already connected
         */
@@ -1143,28 +1114,50 @@ serv_connect(struct server_conf *server_p, struct Client *by)
                return 0;
        }
 
+       if (CurrUsers(server_p->class) >= MaxUsers(server_p->class)) {
+               sendto_realops_snomask(SNO_GENERAL, L_ALL,
+                                    "No more connections allowed in class \"%s\" for server %s",
+                                    server_p->class->class_name, server_p->name);
+               if(by && IsPerson(by) && !MyClient(by))
+                       sendto_one_notice(by, ":No more connections allowed in class \"%s\" for server %s",
+                                    server_p->class->class_name, server_p->name);
+               return 0;
+       }
+
        /* create a socket for the server connection */
-       if((F = rb_socket(server_p->aftype, SOCK_STREAM, 0, NULL)) == NULL)
-       {
+       if(GET_SS_FAMILY(&sa_connect[0]) == AF_UNSPEC) {
+               ilog_error("unspecified socket address family");
+               return 0;
+#ifdef HAVE_LIBSCTP
+       } else if (ServerConfSCTP(server_p)) {
+               if ((F = rb_socket(AF_INET6, SOCK_STREAM, IPPROTO_SCTP, NULL)) == NULL) {
+                       ilog_error("opening a stream socket");
+                       return 0;
+               }
+#endif
+       } else if ((F = rb_socket(GET_SS_FAMILY(&sa_connect[0]), SOCK_STREAM, IPPROTO_TCP, NULL)) == NULL) {
                ilog_error("opening a stream socket");
                return 0;
        }
 
-       rb_snprintf(note, sizeof note, "Server: %s", server_p->name);
+       /* servernames are always guaranteed under HOSTLEN chars */
+       snprintf(note, sizeof(note), "Server: %s", server_p->name);
        rb_note(F, note);
 
        /* Create a local client */
        client_p = make_client(NULL);
 
-       /* Copy in the server, hostname, fd
-        * The sockhost may be a hostname, this will be corrected later
-        * -- jilles
-        */
+       /* Copy in the server, hostname, fd */
        rb_strlcpy(client_p->name, server_p->name, sizeof(client_p->name));
-       rb_strlcpy(client_p->host, server_p->host, sizeof(client_p->host));
-       rb_strlcpy(client_p->sockhost, server_p->host, sizeof(client_p->sockhost));
+       if(server_p->connect_host)
+               rb_strlcpy(client_p->host, server_p->connect_host, sizeof(client_p->host));
+       else
+               rb_strlcpy(client_p->host, buf, sizeof(client_p->host));
+       rb_strlcpy(client_p->sockhost, buf, sizeof(client_p->sockhost));
        client_p->localClient->F = F;
-       add_to_cli_connid_hash(client_p);
+       /* shove the port number into the sockaddr */
+       SET_SS_PORT(&sa_connect[0], htons(server_p->port));
+       SET_SS_PORT(&sa_connect[1], htons(server_p->port));
 
        /*
         * Set up the initial server evilness, ripped straight from
@@ -1191,57 +1184,40 @@ serv_connect(struct server_conf *server_p, struct Client *by)
         * The socket has been connected or connect is in progress.
         */
        make_server(client_p);
-       if(by && IsPerson(by))
-       {
-               rb_strlcpy(client_p->serv->by, by->name,
-                               sizeof client_p->serv->by);
-               if(client_p->serv->user)
-                       free_user(client_p->serv->user, NULL);
-               client_p->serv->user = by->user;
-               by->user->refcnt++;
-       }
+       if(by && IsClient(by))
+               rb_strlcpy(client_p->serv->by, by->name, sizeof(client_p->serv->by));
        else
-       {
-               rb_strlcpy(client_p->serv->by, "AutoConn.",
-                               sizeof client_p->serv->by);
-               if(client_p->serv->user)
-                       free_user(client_p->serv->user, NULL);
-               client_p->serv->user = NULL;
-       }
+               strcpy(client_p->serv->by, "AutoConn.");
+
        SetConnecting(client_p);
        rb_dlinkAddTail(client_p, &client_p->node, &global_client_list);
 
-       if (rb_inet_pton_sock(server_p->host, (struct sockaddr *)&theiripnum) > 0)
-       {
-               memcpy(&client_p->localClient->ip, &theiripnum, sizeof(client_p->localClient->ip));
-#ifdef RB_IPV6
-               if(theiripnum.ss_family == AF_INET6)
-                       ((struct sockaddr_in6 *)&client_p->localClient->ip)->sin6_port = htons(server_p->port);
-               else
-#endif
-                       ((struct sockaddr_in *)&client_p->localClient->ip)->sin_port = htons(server_p->port);
-
-               return serv_connect_resolved(client_p);
+       for (int i = 0; i < ARRAY_SIZE(sa_connect); i++) {
+               if (GET_SS_FAMILY(&sa_bind[i]) == AF_UNSPEC) {
+                       if (GET_SS_FAMILY(&sa_connect[i]) == GET_SS_FAMILY(&ServerInfo.bind4))
+                               sa_bind[i] = ServerInfo.bind4;
+                       if (GET_SS_FAMILY(&sa_connect[i]) == GET_SS_FAMILY(&ServerInfo.bind6))
+                               sa_bind[i] = ServerInfo.bind6;
+               }
        }
-       else
+
+#ifdef HAVE_LIBSCTP
+       if (ServerConfSCTP(server_p)) {
+               rb_connect_sctp(client_p->localClient->F,
+                       sa_connect, ARRAY_SIZE(sa_connect), sa_bind, ARRAY_SIZE(sa_bind),
+                       ServerConfSSL(server_p) ? serv_connect_ssl_callback : serv_connect_callback,
+                       client_p, ConfigFileEntry.connect_timeout);
+       } else {
+#else
        {
-#ifdef RB_IPV6
-               if(theiripnum.ss_family == AF_INET6)
-                       ((struct sockaddr_in6 *)&client_p->localClient->ip)->sin6_port = htons(server_p->port);
-               else
 #endif
-                       ((struct sockaddr_in *)&client_p->localClient->ip)->sin_port = htons(server_p->port);
-
-               client_p->localClient->dnsquery = rb_malloc(sizeof(struct DNSQuery));
-               client_p->localClient->dnsquery->ptr = client_p;
-               client_p->localClient->dnsquery->callback = serv_connect_dns_callback;
-               gethost_byname_type(server_p->host, client_p->localClient->dnsquery,
-#ifdef RB_IPV6
-                               server_p->aftype == AF_INET6 ? T_AAAA :
-#endif
-                               T_A);
-               return 1;
+               rb_connect_tcp(client_p->localClient->F,
+                       (struct sockaddr *)&sa_connect[0],
+                       GET_SS_FAMILY(&sa_bind[0]) == AF_UNSPEC ? NULL : (struct sockaddr *)&sa_bind[0],
+                       ServerConfSSL(server_p) ? serv_connect_ssl_callback : serv_connect_callback,
+                       client_p, ConfigFileEntry.connect_timeout);
        }
+       return 1;
 }
 
 static void
@@ -1263,13 +1239,23 @@ serv_connect_ssl_callback(rb_fde_t *F, int status, void *data)
                return;
 
        }
-       del_from_cli_connid_hash(client_p);
        client_p->localClient->F = xF[0];
-       add_to_cli_connid_hash(client_p);
+       client_p->localClient->ssl_callback = serv_connect_ssl_open_callback;
 
-       client_p->localClient->ssl_ctl = start_ssld_connect(F, xF[1], rb_get_fd(xF[0]));
+       client_p->localClient->ssl_ctl = start_ssld_connect(F, xF[1], connid_get(client_p));
+       if(!client_p->localClient->ssl_ctl)
+       {
+               serv_connect_callback(client_p->localClient->F, RB_ERROR, data);
+               return;
+       }
        SetSSL(client_p);
-       serv_connect_callback(client_p->localClient->F, RB_OK, client_p);
+}
+
+static int
+serv_connect_ssl_open_callback(struct Client *client_p, int status)
+{
+       serv_connect_callback(client_p->localClient->F, status, client_p);
+       return 1; /* suppress default exit_client handler for status != RB_OK */
 }
 
 /*
@@ -1313,7 +1299,7 @@ serv_connect_callback(rb_fde_t *F, int status, void *data)
                /* COMM_ERR_TIMEOUT wont have an errno associated with it,
                 * the others will.. --fl
                 */
-               if(status == RB_ERR_TIMEOUT)
+               if(status == RB_ERR_TIMEOUT || status == RB_ERROR_SSL)
                {
                        sendto_realops_snomask(SNO_GENERAL, is_remote_connect(client_p) ? L_NETWIDE : L_ALL,
                                        "Error connecting to %s[%s]: %s",
@@ -1351,6 +1337,18 @@ serv_connect_callback(rb_fde_t *F, int status, void *data)
                return;
        }
 
+       if(server_p->certfp && (!client_p->certfp || rb_strcasecmp(server_p->certfp, client_p->certfp) != 0))
+       {
+               sendto_realops_snomask(SNO_GENERAL, is_remote_connect(client_p) ? L_NETWIDE : L_ALL,
+                    "Connection to %s has invalid certificate fingerprint %s",
+                    client_p->name, client_p->certfp);
+               ilog(L_SERVER, "Access denied, invalid certificate fingerprint %s from %s",
+                    client_p->certfp, log_client_name(client_p, SHOW_IP));
+
+               exit_client(client_p, client_p, &me, "Invalid fingerprint.");
+               return;
+       }
+
        /* Next, send the initial handshake */
        SetHandshake(client_p);
 
@@ -1359,7 +1357,7 @@ serv_connect_callback(rb_fde_t *F, int status, void *data)
                   EmptyString(server_p->spasswd) ? "*" : server_p->spasswd, TS_CURRENT, me.id);
 
        /* pass my info to the new server */
-       send_capabilities(client_p, default_server_capabs
+       send_capabilities(client_p, default_server_capabs | CAP_MASK
                          | (ServerConfCompressed(server_p) ? CAP_ZIP_SUPPORTED : 0)
                          | (ServerConfTb(server_p) ? CAP_TB : 0));