Make ACLs IPv6-capable.
authorMark Michelson <mmichelson@digium.com>
Mon, 19 Jul 2010 14:17:16 +0000 (14:17 +0000)
committerMark Michelson <mmichelson@digium.com>
Mon, 19 Jul 2010 14:17:16 +0000 (14:17 +0000)
ACLs can now be configured to match IPv6 networks. This is only
relevant for ACLs in chan_sip for now since other channel drivers
do not support IPv6 addressing. However, once those channel drivers
are outfitted to support IPv6 addressing, the ACLs will already be
ready for IPv6 support.

https://reviewboard.asterisk.org/r/791

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@277814 65c4cc65-6c06-0410-ace0-fbb531ad65f3

channels/chan_iax2.c
channels/chan_sip.c
channels/chan_skinny.c
configs/sip.conf.sample
include/asterisk/acl.h
include/asterisk/netsock2.h
main/acl.c
main/manager.c
main/netsock2.c
tests/test_acl.c

index 72e6b66..92f070b 100644 (file)
@@ -2039,15 +2039,17 @@ static int addr_range_delme_cb(void *obj, void *arg, int flags)
 static int addr_range_hash_cb(const void *obj, const int flags)
 {
        const struct addr_range *lim = obj;
-       return abs((int) lim->ha.netaddr.s_addr);
+       struct sockaddr_in sin;
+       ast_sockaddr_to_sin(&lim->ha.addr, &sin);
+       return abs((int) sin.sin_addr.s_addr);
 }
 
 static int addr_range_cmp_cb(void *obj, void *arg, int flags)
 {
        struct addr_range *lim1 = obj, *lim2 = arg;
-       return ((lim1->ha.netaddr.s_addr == lim2->ha.netaddr.s_addr) &&
-               (lim1->ha.netmask.s_addr == lim2->ha.netmask.s_addr)) ?
-               CMP_MATCH | CMP_STOP : 0;
+       return (!(ast_sockaddr_cmp_addr(&lim1->ha.addr, &lim2->ha.addr)) &&
+                       !(ast_sockaddr_cmp_addr(&lim1->ha.netmask, &lim2->ha.netmask))) ?
+                       CMP_MATCH | CMP_STOP : 0;
 }
 
 static int peercnt_hash_cb(const void *obj, const int flags)
@@ -2066,8 +2068,13 @@ static int addr_range_match_address_cb(void *obj, void *arg, int flags)
 {
        struct addr_range *addr_range = obj;
        struct sockaddr_in *sin = arg;
+       struct sockaddr_in ha_netmask_sin;
+       struct sockaddr_in ha_addr_sin;
+
+       ast_sockaddr_to_sin(&addr_range->ha.netmask, &ha_netmask_sin);
+       ast_sockaddr_to_sin(&addr_range->ha.addr, &ha_addr_sin);
 
-       if ((sin->sin_addr.s_addr & addr_range->ha.netmask.s_addr) == addr_range->ha.netaddr.s_addr) {
+       if ((sin->sin_addr.s_addr & ha_netmask_sin.sin_addr.s_addr) == ha_addr_sin.sin_addr.s_addr) {
                return CMP_MATCH | CMP_STOP;
        }
        return 0;
@@ -7385,6 +7392,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
        int gotcapability = 0;
        struct ast_variable *v = NULL, *tmpvar = NULL;
        struct ao2_iterator i;
+       struct ast_sockaddr addr;
 
        if (!iaxs[callno])
                return res;
@@ -7442,10 +7450,11 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
        }
        /* Search the userlist for a compatible entry, and fill in the rest */
        i = ao2_iterator_init(users, 0);
+       ast_sockaddr_from_sin(&addr, sin);
        while ((user = ao2_iterator_next(&i))) {
                if ((ast_strlen_zero(iaxs[callno]->username) ||                         /* No username specified */
                        !strcmp(iaxs[callno]->username, user->name))    /* Or this username specified */
-                       && ast_apply_ha(user->ha, sin)  /* Access is permitted from this IP */
+                       && ast_apply_ha(user->ha, &addr)        /* Access is permitted from this IP */
                        && (ast_strlen_zero(iaxs[callno]->context) ||                   /* No context specified */
                             apply_context(user->contexts, iaxs[callno]->context))) {                   /* Context is permitted */
                        if (!ast_strlen_zero(iaxs[callno]->username)) {
@@ -7787,6 +7796,7 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies *
        int x;
        int expire = 0;
        int res = -1;
+       struct ast_sockaddr addr;
 
        ast_clear_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED);
        /* iaxs[callno]->peer[0] = '\0'; not necc. any more-- stringfield is pre-inited to null string */
@@ -7841,7 +7851,8 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies *
                goto return_unref;
        }
 
-       if (!ast_apply_ha(p->ha, sin)) {
+       ast_sockaddr_from_sin(&addr, sin);
+       if (!ast_apply_ha(p->ha, &addr)) {
                if (authdebug)
                        ast_log(LOG_NOTICE, "Host %s denied access to register peer '%s'\n", ast_inet_ntoa(sin->sin_addr), p->name);
                goto return_unref;
index 792ca69..b6e167a 100644 (file)
@@ -3084,7 +3084,7 @@ static void build_via(struct sip_pvt *p)
 static void ast_sip_ouraddrfor(const struct ast_sockaddr *them, struct ast_sockaddr *us, struct sip_pvt *p)
 {
        struct ast_sockaddr theirs;
-       struct sockaddr_in theirs_sin, externip_sin, us_sin;
+       struct sockaddr_in externip_sin;
 
        /* Set want_remap to non-zero if we want to remap 'us' to an externally
         * reachable IP address and port. This is done if:
@@ -3112,16 +3112,13 @@ static void ast_sip_ouraddrfor(const struct ast_sockaddr *them, struct ast_socka
                                "remove \"localnet\" and/or \"externip\" settings.\n");
                }
        } else {
-               ast_sockaddr_to_sin(&theirs, &theirs_sin);
-               ast_sockaddr_to_sin(us, &us_sin);
-
                want_remap = localaddr &&
                        !(ast_sockaddr_isnull(&externip) && stunaddr.sin_addr.s_addr) &&
-                       ast_apply_ha(localaddr, &theirs_sin) == AST_SENSE_ALLOW ;
+                       ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
        }
 
        if (want_remap &&
-           (!sip_cfg.matchexterniplocally || !ast_apply_ha(localaddr, &us_sin)) ) {
+           (!sip_cfg.matchexterniplocally || !ast_apply_ha(localaddr, us)) ) {
                /* if we used externhost or stun, see if it is time to refresh the info */
                if (externexpire && time(NULL) >= externexpire) {
                        if (stunaddr.sin_addr.s_addr) {
@@ -12643,7 +12640,6 @@ static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, st
        int transport_type;
        const char *useragent;
        struct ast_sockaddr oldsin, testsa;
-       struct sockaddr_in testsin;
 
        ast_copy_string(contact, get_header(req, "Contact"), sizeof(contact));
 
@@ -12763,16 +12759,13 @@ static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, st
        }
 
        /* Check that they're allowed to register at this IP */
-       if (!ast_sockaddr_is_ipv6(&peer->addr)) {
-               ast_sockaddr_to_sin(&peer->addr, &testsin);
-               if (ast_apply_ha(sip_cfg.contact_ha, &testsin) != AST_SENSE_ALLOW ||
-                               ast_apply_ha(peer->contactha, &testsin) != AST_SENSE_ALLOW) {
-                       ast_log(LOG_WARNING, "Domain '%s' disallowed by contact ACL (violating IP %s)\n", domain,
-                               ast_sockaddr_stringify_addr(&testsa));
-                       ast_string_field_set(peer, fullcontact, "");
-                       ast_string_field_set(pvt, our_contact, "");
-                       return PARSE_REGISTER_DENIED;
-               }
+       if (ast_apply_ha(sip_cfg.contact_ha, &peer->addr) != AST_SENSE_ALLOW ||
+                       ast_apply_ha(peer->contactha, &peer->addr) != AST_SENSE_ALLOW) {
+               ast_log(LOG_WARNING, "Domain '%s' disallowed by contact ACL (violating IP %s)\n", domain,
+                       ast_sockaddr_stringify_addr(&testsa));
+               ast_string_field_set(peer, fullcontact, "");
+               ast_string_field_set(pvt, our_contact, "");
+               return PARSE_REGISTER_DENIED;
        }
 
        /* if the Contact header information copied into peer->addr matches the
@@ -13418,19 +13411,14 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sock
        }
        peer = find_peer(name, NULL, TRUE, FINDPEERS, FALSE, 0);
 
-       if (!ast_sockaddr_is_ipv6(addr)) {
-               struct sockaddr_in sin_tmp;
-
-               ast_sockaddr_to_sin(addr, &sin_tmp);
-               if (!(peer && ast_apply_ha(peer->ha, &sin_tmp))) {
-                       /* Peer fails ACL check */
-                       if (peer) {
-                               unref_peer(peer, "register_verify: unref_peer: from find_peer operation");
-                               peer = NULL;
-                               res = AUTH_ACL_FAILED;
-                       } else {
-                               res = AUTH_NOT_FOUND;
-                       }
+       if (!(peer && ast_apply_ha(peer->ha, addr))) {
+               /* Peer fails ACL check */
+               if (peer) {
+                       unref_peer(peer, "register_verify: unref_peer: from find_peer operation");
+                       peer = NULL;
+                       res = AUTH_ACL_FAILED;
+               } else {
+                       res = AUTH_NOT_FOUND;
                }
        }
 
@@ -14533,15 +14521,11 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of,
                }
                return AUTH_DONT_KNOW;
        }
-       if (!ast_sockaddr_is_ipv6(addr)) {
-               struct sockaddr_in sin_tmp;
 
-               ast_sockaddr_to_sin(addr, &sin_tmp);
-               if (!ast_apply_ha(peer->ha, &sin_tmp)) {
-                       ast_debug(2, "Found peer '%s' for '%s', but fails host access\n", peer->name, of);
-                       unref_peer(peer, "unref_peer: check_peer_ok: from find_peer call, early return of AUTH_ACL_FAILED");
-                       return AUTH_ACL_FAILED;
-               }
+       if (!ast_apply_ha(peer->ha, addr)) {
+               ast_debug(2, "Found peer '%s' for '%s', but fails host access\n", peer->name, of);
+               unref_peer(peer, "unref_peer: check_peer_ok: from find_peer call, early return of AUTH_ACL_FAILED");
+               return AUTH_ACL_FAILED;
        }
        if (debug)
                ast_verbose("Found peer '%s' for '%s' from %s\n",
@@ -16618,12 +16602,11 @@ static char *sip_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_
        {
                struct ast_ha *d;
                const char *prefix = "Localnet:";
-               char buf[INET_ADDRSTRLEN]; /* need to print two addresses */
 
                for (d = localaddr; d ; prefix = "", d = d->next) {
                        ast_cli(a->fd, "  %-24s%s/%s\n",
-                           prefix, ast_inet_ntoa(d->netaddr),
-                           inet_ntop(AF_INET, &d->netmask, buf, sizeof(buf)) );
+                           prefix, ast_strdupa(ast_sockaddr_stringify_addr(&d->addr)),
+                           ast_strdupa(ast_sockaddr_stringify_addr(&d->netmask)));
                }
        }
        ast_cli(a->fd, "  STUN server:            %s:%d\n", ast_inet_ntoa(stunaddr.sin_addr), ntohs(stunaddr.sin_port));
@@ -27195,20 +27178,12 @@ static int reload_config(enum channelreloadreason reason)
 static int apply_directmedia_ha(struct sip_pvt *p, const char *op)
 {
        struct ast_sockaddr us = { { 0, }, }, them = { { 0, }, };
-       struct sockaddr_in them_sin;
        int res = AST_SENSE_ALLOW;
 
        ast_rtp_instance_get_remote_address(p->rtp, &them);
        ast_rtp_instance_get_local_address(p->rtp, &us);
 
-       /* Currently ast_apply_ha doesn't support IPv6 */
-       if (ast_sockaddr_is_ipv6(&them)) {
-               return res;
-       }
-
-       ast_sockaddr_to_sin(&them, &them_sin);
-
-       if ((res = ast_apply_ha(p->directmediaha, &them_sin)) == AST_SENSE_DENY) {
+       if ((res = ast_apply_ha(p->directmediaha, &them)) == AST_SENSE_DENY) {
                ast_debug(3, "Reinvite %s to %s denied by directmedia ACL on %s\n",
                        op, ast_strdupa(ast_sockaddr_stringify(&them)), ast_strdupa(ast_sockaddr_stringify(&us)));
        }
index 31f70ec..6d4968a 100644 (file)
@@ -1877,8 +1877,10 @@ static int skinny_register(struct skinny_req *req, struct skinnysession *s)
 
        AST_LIST_LOCK(&devices);
        AST_LIST_TRAVERSE(&devices, d, list){
+               struct ast_sockaddr addr;
+               ast_sockaddr_from_sin(&addr, &s->sin);
                if (!strcasecmp(req->data.reg.name, d->id)
-                               && ast_apply_ha(d->ha, &(s->sin))) {
+                               && ast_apply_ha(d->ha, &addr)) {
                        s->device = d;
                        d->type = letohl(req->data.reg.type);
                        if (ast_strlen_zero(d->version_id)) {
index ea83d48..1ec5ae3 100644 (file)
@@ -1238,6 +1238,9 @@ srvlookup=yes                   ; Enable DNS SRV lookups on outbound calls
 ;deny=0.0.0.0/0.0.0.0            ; ACL: Control access to this account based on IP address
 ;permit=192.168.0.60/255.255.255.0
 ;permit=192.168.0.60/24          ; we can also use CIDR notation for subnet masks
+;permit=2001:db8::/32            ; IPv6 ACLs can be specified if desired. IPv6 ACLs
+                                 ; apply only to IPv6 addresses, and IPv4 ACLs apply
+                                 ; only to IPv4 addresses.
 
 ;[cisco1]
 ;type=friend
index 2c4f620..a8c311c 100644 (file)
@@ -46,11 +46,11 @@ extern "C" {
  * thing public and let users play with them.
  */
 struct ast_ha {
-        /* Host access rule */
-        struct in_addr netaddr;  
-        struct in_addr netmask;
-        int sense;
-        struct ast_ha *next;
+       /* Host access rule */
+       struct ast_sockaddr addr;
+       struct ast_sockaddr netmask;
+       int sense;
+       struct ast_ha *next;
 };
 
 /*!
@@ -111,11 +111,11 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
  * the one whose sense will be returned.
  *
  * \param ha The head of the list of host access rules to follow
- * \param sin A sockaddr_in whose address is considered when matching rules
+ * \param addr An ast_sockaddr whose address is considered when matching rules
  * \retval AST_SENSE_ALLOW The IP address passes our ACL
  * \retval AST_SENSE_DENY The IP address fails our ACL
  */
-int ast_apply_ha(struct ast_ha *ha, struct sockaddr_in *sin);
+int ast_apply_ha(const struct ast_ha *ha, const struct ast_sockaddr *addr);
 
 /*!
  * \brief Get the IP address given a hostname
@@ -186,7 +186,7 @@ int ast_ouraddrfor(const struct ast_sockaddr *them, struct ast_sockaddr *us);
  * \retval -1 Failure. address is filled with 0s
  * \retval 0 Success
  */
-int ast_lookup_iface(char *iface, struct in_addr *address);
+int ast_lookup_iface(char *iface, struct ast_sockaddr *address);
 
 /*!
  * \brief Duplicate the contents of a list of host access rules
index e7121cb..b73d848 100644 (file)
@@ -56,6 +56,20 @@ struct ast_sockaddr {
 };
 
 /*!
+ * \brief
+ * Convert an IPv4-mapped IPv6 address into an IPv4 address.
+ *
+ * \warning You should rarely need this function. Only call this
+ * if you know what you're doing.
+ *
+ * \param addr The IPv4-mapped address to convert
+ * \param mapped_addr The resulting IPv4 address
+ * \retval 0 Unable to make the conversion
+ * \retval 1 Successful conversion
+ */
+int ast_sockaddr_ipv4_mapped(const struct ast_sockaddr *addr, struct ast_sockaddr *ast_mapped);
+
+/*!
  * \since 1.8
  *
  * \brief
index 663c0b2..4ab1027 100644 (file)
@@ -229,8 +229,8 @@ void ast_free_ha(struct ast_ha *ha)
 /* Copy HA structure */
 void ast_copy_ha(const struct ast_ha *from, struct ast_ha *to)
 {
-       memcpy(&to->netaddr, &from->netaddr, sizeof(from->netaddr));
-       memcpy(&to->netmask, &from->netmask, sizeof(from->netmask));
+       ast_sockaddr_copy(&to->addr, &from->addr);
+       ast_sockaddr_copy(&to->netmask, &from->netmask);
        to->sense = from->sense;
 }
 
@@ -271,14 +271,135 @@ struct ast_ha *ast_duplicate_ha_list(struct ast_ha *original)
        return ret;                             /* Return start of list */
 }
 
+/*!
+ * \brief
+ * Isolate a 32-bit section of an IPv6 address
+ *
+ * An IPv6 address can be divided into 4 32-bit chunks. This gives
+ * easy access to one of these chunks.
+ *
+ * \param sin6 A pointer to a struct sockaddr_in6
+ * \param index Which 32-bit chunk to operate on. Must be in the range 0-3.
+ */
+#define V6_WORD(sin6, index) ((uint32_t *)&((sin6)->sin6_addr))[(index)]
+
+/*!
+ * \brief
+ * Apply a netmask to an address and store the result in a separate structure.
+ *
+ * When dealing with IPv6 addresses, one cannot apply a netmask with a simple
+ * logical and operation. Furthermore, the incoming address may be an IPv4 address
+ * and need to be mapped properly before attempting to apply a rule.
+ *
+ * \param addr The IP address to apply the mask to.
+ * \param netmask The netmask configured in the host access rule.
+ * \param result The resultant address after applying the netmask to the given address
+ * \retval 0 Successfully applied netmask
+ * \reval -1 Failed to apply netmask
+ */
+static int apply_netmask(const struct ast_sockaddr *addr, const struct ast_sockaddr *netmask,
+               struct ast_sockaddr *result)
+{
+       int res = 0;
+
+       if (ast_sockaddr_is_ipv4(addr)) {
+               struct sockaddr_in result4 = { 0, };
+               struct sockaddr_in *addr4 = (struct sockaddr_in *) &addr->ss;
+               struct sockaddr_in *mask4 = (struct sockaddr_in *) &netmask->ss;
+               result4.sin_family = AF_INET;
+               result4.sin_addr.s_addr = addr4->sin_addr.s_addr & mask4->sin_addr.s_addr;
+               ast_sockaddr_from_sin(result, &result4);
+       } else if (ast_sockaddr_is_ipv6(addr)) {
+               struct sockaddr_in6 result6 = { 0, };
+               struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &addr->ss;
+               struct sockaddr_in6 *mask6 = (struct sockaddr_in6 *) &netmask->ss;
+               int i;
+               result6.sin6_family = AF_INET6;
+               for (i = 0; i < 4; ++i) {
+                       V6_WORD(&result6, i) = V6_WORD(addr6, i) & V6_WORD(mask6, i);
+               }
+               memcpy(&result->ss, &result6, sizeof(result6));
+               result->len = sizeof(result6);
+       } else {
+               /* Unsupported address scheme */
+               res = -1;
+       }
+
+       return res;
+}
+
+/*!
+ * \brief
+ * Parse a netmask in CIDR notation
+ *
+ * \details
+ * For a mask of an IPv4 address, this should be a number between 0 and 32. For
+ * a mask of an IPv6 address, this should be a number between 0 and 128. This
+ * function creates an IPv6 ast_sockaddr from the given netmask. For masks of
+ * IPv4 addresses, this is accomplished by adding 96 to the original netmask.
+ *
+ * \param[out] addr The ast_sockaddr produced from the CIDR netmask
+ * \param is_v4 Tells if the address we are masking is IPv4.
+ * \param mask_str The CIDR mask to convert
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+static int parse_cidr_mask(struct ast_sockaddr *addr, int is_v4, const char *mask_str)
+{
+       int mask;
+
+       if (sscanf(mask_str, "%30d", &mask) != 1) {
+               return -1;
+       }
+
+       if (is_v4) {
+               struct sockaddr_in sin;
+               if (mask < 0 || mask > 32) {
+                       return -1;
+               }
+               memset(&sin, 0, sizeof(sin));
+               sin.sin_family = AF_INET;
+               /* If mask is 0, then we already have the
+                * appropriate all 0s address in sin from
+                * the above memset.
+                */
+               if (mask != 0) {
+                       sin.sin_addr.s_addr = htonl(0xFFFFFFFF << (32 - mask));
+               }
+               ast_sockaddr_from_sin(addr, &sin);
+       } else {
+               struct sockaddr_in6 sin6;
+               int i;
+               if (mask < 0 || mask > 128) {
+                       return -1;
+               }
+               memset(&sin6, 0, sizeof(sin6));
+               sin6.sin6_family = AF_INET6;
+               for (i = 0; i < 4; ++i) {
+                       /* Once mask reaches 0, we don't have
+                        * to explicitly set anything anymore
+                        * since sin6 was zeroed out already
+                        */
+                       if (mask > 0) {
+                               V6_WORD(&sin6, i) = htonl(0xFFFFFFFF << (mask < 32 ? (32 - mask) : 0));
+                               mask -= mask < 32 ? mask : 32;
+                       }
+               }
+               memcpy(&addr->ss, &sin6, sizeof(sin6));
+               addr->len = sizeof(sin6);
+       }
+
+       return 0;
+}
+
 struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha *path, int *error)
 {
        struct ast_ha *ha;
-       char *nm;
        struct ast_ha *prev = NULL;
        struct ast_ha *ret;
-       int x;
        char *tmp = ast_strdupa(stuff);
+       char *address = NULL, *mask = NULL;
+       int addr_is_v4;
 
        ret = path;
        while (path) {
@@ -290,52 +411,73 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
                return ret;
        }
 
-       if (!(nm = strchr(tmp, '/'))) {
-               /* assume /32. Yes, htonl does not do anything for this particular mask
-                  but we better use it to show we remember about byte order */
-               ha->netmask.s_addr = htonl(0xFFFFFFFF);
+       address = strsep(&tmp, "/");
+       if (!address) {
+               address = tmp;
        } else {
-               *nm = '\0';
-               nm++;
-
-               if (!strchr(nm, '.')) {
-                       if ((sscanf(nm, "%30d", &x) == 1) && (x >= 0) && (x <= 32)) {
-                               if (x == 0) {
-                                       /* This is special-cased to prevent unpredictable
-                                        * behavior of shifting left 32 bits
-                                        */
-                                       ha->netmask.s_addr = 0;
-                               } else {
-                                       ha->netmask.s_addr = htonl(0xFFFFFFFF << (32 - x));
-                               }
-                       } else {
-                               ast_log(LOG_WARNING, "Invalid CIDR in %s\n", stuff);
-                               ast_free(ha);
-                               if (error) {
-                                       *error = 1;
-                               }
-                               return ret;
-                       }
-               } else if (!inet_aton(nm, &ha->netmask)) {
-                       ast_log(LOG_WARNING, "Invalid mask in %s\n", stuff);
-                       ast_free(ha);
-                       if (error) {
-                               *error = 1;
-                       }
-                       return ret;
-               }
+               mask = tmp;
+       }
+
+       if (!ast_sockaddr_parse(&ha->addr, address, PARSE_PORT_FORBID)) {
+               ast_log(LOG_WARNING, "Invalid IP address: %s\n", address);
+               ast_free_ha(ha);
+               *error = 1;
+               return ret;
        }
 
-       if (!inet_aton(tmp, &ha->netaddr)) {
-               ast_log(LOG_WARNING, "Invalid IP address in %s\n", stuff);
-               ast_free(ha);
-               if (error) {
+       /* If someone specifies an IPv4-mapped IPv6 address,
+        * we just convert this to an IPv4 ACL
+        */
+       if (ast_sockaddr_ipv4_mapped(&ha->addr, &ha->addr)) {
+               ast_log(LOG_NOTICE, "IPv4-mapped ACL network address specified. "
+                               "Converting to an IPv4 ACL network address.\n");
+       }
+
+       addr_is_v4 = ast_sockaddr_is_ipv4(&ha->addr);
+
+       if (!mask) {
+               parse_cidr_mask(&ha->netmask, addr_is_v4, addr_is_v4 ? "32" : "128");
+       } else if (strchr(mask, ':') || strchr(mask, '.')) {
+               int mask_is_v4;
+               /* Mask is of x.x.x.x or x:x:x:x:x:x:x:x variety */
+               if (!ast_sockaddr_parse(&ha->netmask, mask, PARSE_PORT_FORBID)) {
+                       ast_log(LOG_WARNING, "Invalid netmask: %s\n", mask);
+                       ast_free_ha(ha);
+                       *error = 1;
+                       return ret;
+               }
+               /* If someone specifies an IPv4-mapped IPv6 netmask,
+                * we just convert this to an IPv4 ACL
+                */
+               if (ast_sockaddr_ipv4_mapped(&ha->netmask, &ha->netmask)) {
+                       ast_log(LOG_NOTICE, "IPv4-mapped ACL netmask specified. "
+                                       "Converting to an IPv4 ACL netmask.\n");
+               }
+               mask_is_v4 = ast_sockaddr_is_ipv4(&ha->netmask);
+               if (addr_is_v4 ^ mask_is_v4) {
+                       ast_log(LOG_WARNING, "Address and mask are not using same address scheme.\n");
+                       ast_free_ha(ha);
                        *error = 1;
+                       return ret;
                }
+       } else if (parse_cidr_mask(&ha->netmask, addr_is_v4, mask)) {
+               ast_log(LOG_WARNING, "Invalid CIDR netmask: %s\n", mask);
+               ast_free_ha(ha);
+               *error = 1;
                return ret;
        }
 
-       ha->netaddr.s_addr &= ha->netmask.s_addr;
+       if (apply_netmask(&ha->addr, &ha->netmask, &ha->addr)) {
+               /* This shouldn't happen because ast_sockaddr_parse would
+                * have failed much earlier on an unsupported address scheme
+                */
+               char *failmask = ast_strdupa(ast_sockaddr_stringify(&ha->netmask));
+               char *failaddr = ast_strdupa(ast_sockaddr_stringify(&ha->addr));
+               ast_log(LOG_WARNING, "Unable to apply netmask %s to address %s\n", failmask, failaddr);
+               ast_free_ha(ha);
+               *error = 1;
+               return ret;
+       }
 
        ha->sense = strncasecmp(sense, "p", 1) ? AST_SENSE_DENY : AST_SENSE_ALLOW;
 
@@ -346,16 +488,21 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
                ret = ha;
        }
 
-       ast_debug(1, "%s/%s sense %d appended to acl for peer\n", ast_strdupa(ast_inet_ntoa(ha->netaddr)), ast_strdupa(ast_inet_ntoa(ha->netmask)), ha->sense);
+       ast_debug(1, "%s/%s sense %d appended to acl for peer\n", ast_strdupa(ast_sockaddr_stringify(&ha->addr)), ast_strdupa(ast_sockaddr_stringify(&ha->netmask)), ha->sense);
 
        return ret;
 }
 
-int ast_apply_ha(struct ast_ha *ha, struct sockaddr_in *sin)
+int ast_apply_ha(const struct ast_ha *ha, const struct ast_sockaddr *addr)
 {
        /* Start optimistic */
        int res = AST_SENSE_ALLOW;
-       while (ha) {
+       const struct ast_ha *current_ha;
+
+       for (current_ha = ha; current_ha; current_ha = current_ha->next) {
+               struct ast_sockaddr result;
+               struct ast_sockaddr mapped_addr;
+               const struct ast_sockaddr *addr_to_use;
 #if 0  /* debugging code */
                char iabuf[INET_ADDRSTRLEN];
                char iabuf2[INET_ADDRSTRLEN];
@@ -364,12 +511,38 @@ int ast_apply_ha(struct ast_ha *ha, struct sockaddr_in *sin)
                ast_copy_string(iabuf2, ast_inet_ntoa(ha->netaddr), sizeof(iabuf2));
                ast_debug(1, "##### Testing %s with %s\n", iabuf, iabuf2);
 #endif
+               if (ast_sockaddr_is_ipv4(&ha->addr)) {
+                       if (ast_sockaddr_is_ipv6(addr)) {
+                               if (ast_sockaddr_is_ipv4_mapped(addr)) {
+                                       /* IPv4 ACLs apply to IPv4-mapped addresses */
+                                       ast_sockaddr_ipv4_mapped(addr, &mapped_addr);
+                                       addr_to_use = &mapped_addr;
+                               } else {
+                                       /* An IPv4 ACL does not apply to an IPv6 address */
+                                       continue;
+                               }
+                       } else {
+                               /* Address is IPv4 and ACL is IPv4. No biggie */
+                               addr_to_use = addr;
+                       }
+               } else {
+                       if (ast_sockaddr_is_ipv6(addr) && !ast_sockaddr_is_ipv4_mapped(addr)) {
+                               addr_to_use = addr;
+                       } else {
+                               /* Address is IPv4 or IPv4 mapped but ACL is IPv6. Skip */
+                               continue;
+                       }
+               }
+
                /* For each rule, if this address and the netmask = the net address
                   apply the current rule */
-               if ((sin->sin_addr.s_addr & ha->netmask.s_addr) == ha->netaddr.s_addr) {
-                       res = ha->sense;
+               if (apply_netmask(addr_to_use, &current_ha->netmask, &result)) {
+                       /* Unlikely to happen since we know the address to be IPv4 or IPv6 */
+                       continue;
+               }
+               if (!ast_sockaddr_cmp_addr(&result, &current_ha->addr)) {
+                       res = current_ha->sense;
                }
-               ha = ha->next;
        }
        return res;
 }
index e4d4369..42ad091 100644 (file)
@@ -2226,6 +2226,7 @@ static int authenticate(struct mansession *s, const struct message *m)
        struct ast_manager_user *user = NULL;
        regex_t *regex_filter;
        struct ao2_iterator filter_iter;
+       struct ast_sockaddr addr;
 
        if (ast_strlen_zero(username)) {        /* missing username */
                return -1;
@@ -2234,10 +2235,12 @@ static int authenticate(struct mansession *s, const struct message *m)
        /* locate user in locked state */
        AST_RWLIST_WRLOCK(&users);
 
+       ast_sockaddr_from_sin(&addr, &s->session->sin);
+
        if (!(user = get_manager_by_name_locked(username))) {
                report_invalid_user(s, username);
                ast_log(LOG_NOTICE, "%s tried to authenticate with nonexistent user '%s'\n", ast_inet_ntoa(s->session->sin.sin_addr), username);
-       } else if (user->ha && !ast_apply_ha(user->ha, &(s->session->sin))) {
+       } else if (user->ha && !ast_apply_ha(user->ha, &addr)) {
                report_failed_acl(s, username);
                ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_inet_ntoa(s->session->sin.sin_addr), username);
        } else if (!strcasecmp(astman_get_header(m, "AuthType"), "MD5")) {
@@ -5625,6 +5628,7 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
        int u_writeperm;
        int u_writetimeout;
        int u_displayconnects;
+       struct ast_sockaddr addr;
 
        if (method != AST_HTTP_GET && method != AST_HTTP_HEAD && method != AST_HTTP_POST) {
                ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
@@ -5668,8 +5672,9 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
                goto out_401;
        }
 
+       ast_sockaddr_from_sin(&addr, remote_address);
        /* --- We have User for this auth, now check ACL */
-       if (user->ha && !ast_apply_ha(user->ha, remote_address)) {
+       if (user->ha && !ast_apply_ha(user->ha, &addr)) {
                AST_RWLIST_UNLOCK(&users);
                ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_inet_ntoa(remote_address->sin_addr), d.username);
                ast_http_error(ser, 403, "Permission denied", "Permission denied\n");
index 8e4f55e..52b8a8d 100644 (file)
@@ -32,7 +32,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/utils.h"
 #include "asterisk/threadstorage.h"
 
-static int ast_sockaddr_ipv4_mapped(const struct ast_sockaddr *addr, struct ast_sockaddr *ast_mapped)
+int ast_sockaddr_ipv4_mapped(const struct ast_sockaddr *addr, struct ast_sockaddr *ast_mapped)
 {
        const struct sockaddr_in6 *sin6;
        struct sockaddr_in sin4;
index a0f005b..88180aa 100644 (file)
@@ -35,21 +35,46 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/test.h"
 #include "asterisk/acl.h"
 #include "asterisk/module.h"
+#include "asterisk/netsock2.h"
+#include "asterisk/config.h"
 
 AST_TEST_DEFINE(invalid_acl)
 {
        const char * invalid_acls[] = {
+               /* Negative netmask */
                "1.3.3.7/-1",
+               /* Netmask too large */
                "1.3.3.7/33",
+               /* Netmask waaaay too large */
                "1.3.3.7/92342348927389492307420",
+               /* Netmask non-numeric */
                "1.3.3.7/California",
+               /* Too many octets in Netmask */
                "1.3.3.7/255.255.255.255.255",
+               /* Octets in IP address exceed 255 */
                "57.60.278.900/31",
+               /* Octets in IP address exceed 255 and are negative */
                "400.32.201029.-6/24",
+               /* Invalidly formatted IP address */
                "EGGSOFDEATH/4000",
+               /* Too many octets in IP address */
                "33.4.7.8.3/300030",
+               /* Too many octets in Netmask */
                "1.2.3.4/6.7.8.9.0",
+               /* Too many octets in IP address */
                "3.1.4.1.5.9/3",
+               /* IPv6 address has multiple double colons */
+               "ff::ff::ff/3",
+               /* IPv6 address is too long */
+               "1234:5678:90ab:cdef:1234:5678:90ab:cdef:1234/56",
+               /* IPv6 netmask is too large */
+               "::ffff/129",
+               /* IPv4-mapped IPv6 address has too few octets */
+               "::ffff:255.255.255/128",
+               /* Leading and trailing colons for IPv6 address */
+               ":1234:/15",
+               /* IPv6 address and IPv4 netmask */
+               "fe80::1234/255.255.255.0",
        };
 
        enum ast_test_result_state res = AST_TEST_PASS;
@@ -89,10 +114,19 @@ struct acl {
        const char *access;
 };
 
+/* These constants are defined for the sole purpose of being shorter
+ * than their real names. It makes lines in this test quite a bit shorter
+ */
+
+#define TACL_A AST_SENSE_ALLOW
+#define TACL_D AST_SENSE_DENY
+
 AST_TEST_DEFINE(acl)
 {
-       struct acl permitall = { "0.0.0.0/0", "permit" };
-       struct acl denyall = { "0.0.0.0/0", "deny" };
+       struct acl permitallv4 = { "0.0.0.0/0", "permit" };
+       struct acl denyallv4 = { "0.0.0.0/0", "deny" };
+       struct acl permitallv6 = { "::/0", "permit" };
+       struct acl denyallv6 = { "::/0", "deny" };
        struct acl acl1[] = {
                { "0.0.0.0/0.0.0.0", "deny" },
                { "10.0.0.0/255.0.0.0", "permit" },
@@ -105,23 +139,49 @@ AST_TEST_DEFINE(acl)
                { "10.0.0.0/24", "permit" },
        };
 
+       struct acl acl3[] = {
+               { "::/0", "deny" },
+               { "fe80::/64", "permit" },
+       };
+
+       struct acl acl4[] = {
+               { "::/0", "deny" },
+               { "fe80::/64", "permit" },
+               { "fe80::ffff:0:0:0/80", "deny" },
+               { "fe80::ffff:0:ffff:0/112", "permit" },
+       };
+
        struct {
                const char *test_address;
+               int v4_permitall_result;
+               int v4_denyall_result;
+               int v6_permitall_result;
+               int v6_denyall_result;
                int acl1_result;
                int acl2_result;
+               int acl3_result;
+               int acl4_result;
        } acl_tests[] = {
-               { "10.1.1.5", AST_SENSE_ALLOW, AST_SENSE_ALLOW },
-               { "192.168.0.5", AST_SENSE_ALLOW, AST_SENSE_ALLOW },
-               { "192.168.1.5", AST_SENSE_DENY, AST_SENSE_ALLOW },
-               { "10.0.0.1", AST_SENSE_ALLOW, AST_SENSE_ALLOW },
-               { "10.0.10.10", AST_SENSE_ALLOW, AST_SENSE_DENY },
-               { "172.16.0.1", AST_SENSE_DENY, AST_SENSE_ALLOW },
+               { "10.1.1.5", TACL_A, TACL_D, TACL_A, TACL_A, TACL_A, TACL_A, TACL_A, TACL_A },
+               { "192.168.0.5", TACL_A, TACL_D, TACL_A, TACL_A, TACL_A, TACL_A, TACL_A, TACL_A },
+               { "192.168.1.5", TACL_A, TACL_D, TACL_A, TACL_A, TACL_D, TACL_A, TACL_A, TACL_A },
+               { "10.0.0.1", TACL_A, TACL_D, TACL_A, TACL_A, TACL_A, TACL_A, TACL_A, TACL_A },
+               { "10.0.10.10", TACL_A, TACL_D, TACL_A, TACL_A, TACL_A, TACL_D, TACL_A, TACL_A },
+               { "172.16.0.1", TACL_A, TACL_D, TACL_A, TACL_A, TACL_D, TACL_A, TACL_A, TACL_A },
+               { "fe80::1234", TACL_A, TACL_A, TACL_A, TACL_D, TACL_A, TACL_A, TACL_A, TACL_A },
+               { "fe80:1234::1234", TACL_A, TACL_A, TACL_A, TACL_D, TACL_A, TACL_A, TACL_D, TACL_D, },
+               { "fe80::ffff:1213:dead:beef", TACL_A, TACL_A, TACL_A, TACL_D, TACL_A, TACL_A, TACL_A, TACL_D },
+               { "fe80::ffff:0:ffff:ABCD", TACL_A, TACL_A, TACL_A, TACL_D, TACL_A, TACL_A, TACL_A, TACL_A },
        };
 
-       struct ast_ha *permit_ha = NULL;
-       struct ast_ha *deny_ha = NULL;
+       struct ast_ha *permit_hav4 = NULL;
+       struct ast_ha *deny_hav4 = NULL;
+       struct ast_ha *permit_hav6 = NULL;
+       struct ast_ha *deny_hav6 = NULL;
        struct ast_ha *ha1 = NULL;
        struct ast_ha *ha2 = NULL;
+       struct ast_ha *ha3 = NULL;
+       struct ast_ha *ha4 = NULL;
        enum ast_test_result_state res = AST_TEST_PASS;
        int err = 0;
        int i;
@@ -138,13 +198,25 @@ AST_TEST_DEFINE(acl)
                break;
        }
 
-       if (!(permit_ha = ast_append_ha(permitall.access, permitall.host, permit_ha, &err))) {
+       if (!(permit_hav4 = ast_append_ha(permitallv4.access, permitallv4.host, permit_hav4, &err))) {
+               ast_test_status_update(test, "Failed to create permit_all ACL\n");
+               res = AST_TEST_FAIL;
+               goto acl_cleanup;
+       }
+
+       if (!(deny_hav4 = ast_append_ha(denyallv4.access, denyallv4.host, deny_hav4, &err))) {
+               ast_test_status_update(test, "Failed to create deny_all ACL\n");
+               res = AST_TEST_FAIL;
+               goto acl_cleanup;
+       }
+
+       if (!(permit_hav6 = ast_append_ha(permitallv6.access, permitallv6.host, permit_hav6, &err))) {
                ast_test_status_update(test, "Failed to create permit_all ACL\n");
                res = AST_TEST_FAIL;
                goto acl_cleanup;
        }
 
-       if (!(deny_ha = ast_append_ha(denyall.access, denyall.host, deny_ha, &err))) {
+       if (!(deny_hav6 = ast_append_ha(denyallv6.access, denyallv6.host, deny_hav6, &err))) {
                ast_test_status_update(test, "Failed to create deny_all ACL\n");
                res = AST_TEST_FAIL;
                goto acl_cleanup;
@@ -168,62 +240,128 @@ AST_TEST_DEFINE(acl)
                }
        }
 
+       for (i = 0; i < ARRAY_LEN(acl3); ++i) {
+               if (!(ha3 = ast_append_ha(acl3[i].access, acl3[i].host, ha3, &err))) {
+                       ast_test_status_update(test, "Failed to add rule %s with access %s to ha3\n",
+                                       acl3[i].host, acl3[i].access);
+                       res = AST_TEST_FAIL;
+                       goto acl_cleanup;
+               }
+       }
+
+       for (i = 0; i < ARRAY_LEN(acl4); ++i) {
+               if (!(ha4 = ast_append_ha(acl4[i].access, acl4[i].host, ha4, &err))) {
+                       ast_test_status_update(test, "Failed to add rule %s with access %s to ha4\n",
+                                       acl4[i].host, acl4[i].access);
+                       res = AST_TEST_FAIL;
+                       goto acl_cleanup;
+               }
+       }
+
        for (i = 0; i < ARRAY_LEN(acl_tests); ++i) {
-               struct sockaddr_in sin;
-               int permit_res;
-               int deny_res;
+               struct ast_sockaddr addr;
+               int permit_resv4;
+               int permit_resv6;
+               int deny_resv4;
+               int deny_resv6;
                int acl1_res;
                int acl2_res;
+               int acl3_res;
+               int acl4_res;
 
-               inet_aton(acl_tests[i].test_address, &sin.sin_addr);
+               ast_sockaddr_parse(&addr, acl_tests[i].test_address, PARSE_PORT_FORBID);
+
+               permit_resv4 = ast_apply_ha(permit_hav4, &addr);
+               deny_resv4 = ast_apply_ha(deny_hav4, &addr);
+               permit_resv6 = ast_apply_ha(permit_hav6, &addr);
+               deny_resv6 = ast_apply_ha(deny_hav6, &addr);
+               acl1_res = ast_apply_ha(ha1, &addr);
+               acl2_res = ast_apply_ha(ha2, &addr);
+               acl3_res = ast_apply_ha(ha3, &addr);
+               acl4_res = ast_apply_ha(ha4, &addr);
+
+               if (permit_resv4 != acl_tests[i].v4_permitall_result) {
+                       ast_test_status_update(test, "Access not as expected to %s on permitallv4. Expected %d but "
+                                       "got %d instead\n", acl_tests[i].test_address, acl_tests[i].v4_permitall_result, permit_resv4);
+                       res = AST_TEST_FAIL;
+                       goto acl_cleanup;
+               }
 
-               permit_res = ast_apply_ha(permit_ha, &sin);
-               deny_res = ast_apply_ha(deny_ha, &sin);
-               acl1_res = ast_apply_ha(ha1, &sin);
-               acl2_res = ast_apply_ha(ha2, &sin);
+               if (deny_resv4 != acl_tests[i].v4_denyall_result) {
+                       ast_test_status_update(test, "Access not as expected to %s on denyallv4. Expected %d but "
+                                       "got %d instead\n", acl_tests[i].test_address, acl_tests[i].v4_denyall_result, deny_resv4);
+                       res = AST_TEST_FAIL;
+                       goto acl_cleanup;
+               }
 
-               if (permit_res != AST_SENSE_ALLOW) {
-                       ast_test_status_update(test, "Access denied to %s on permit_all ACL\n",
-                                       acl_tests[i].test_address);
+               if (permit_resv6 != acl_tests[i].v6_permitall_result) {
+                       ast_test_status_update(test, "Access not as expected to %s on permitallv6. Expected %d but "
+                                       "got %d instead\n", acl_tests[i].test_address, acl_tests[i].v6_permitall_result, permit_resv6);
                        res = AST_TEST_FAIL;
                        goto acl_cleanup;
                }
 
-               if (deny_res != AST_SENSE_DENY) {
-                       ast_test_status_update(test, "Access allowed to %s on deny_all ACL\n",
-                                       acl_tests[i].test_address);
+               if (deny_resv6 != acl_tests[i].v6_denyall_result) {
+                       ast_test_status_update(test, "Access not as expected to %s on denyallv6. Expected %d but "
+                                       "got %d instead\n", acl_tests[i].test_address, acl_tests[i].v6_denyall_result, deny_resv6);
                        res = AST_TEST_FAIL;
                        goto acl_cleanup;
                }
 
                if (acl1_res != acl_tests[i].acl1_result) {
-                       ast_test_status_update(test, "Access not as expected to %s on acl1. Expected %d but"
+                       ast_test_status_update(test, "Access not as expected to %s on acl1. Expected %d but "
                                        "got %d instead\n", acl_tests[i].test_address, acl_tests[i].acl1_result, acl1_res);
                        res = AST_TEST_FAIL;
                        goto acl_cleanup;
                }
 
                if (acl2_res != acl_tests[i].acl2_result) {
-                       ast_test_status_update(test, "Access not as expected to %s on acl2. Expected %d but"
+                       ast_test_status_update(test, "Access not as expected to %s on acl2. Expected %d but "
                                        "got %d instead\n", acl_tests[i].test_address, acl_tests[i].acl2_result, acl2_res);
                        res = AST_TEST_FAIL;
                        goto acl_cleanup;
                }
+
+               if (acl3_res != acl_tests[i].acl3_result) {
+                       ast_test_status_update(test, "Access not as expected to %s on acl3. Expected %d but "
+                                       "got %d instead\n", acl_tests[i].test_address, acl_tests[i].acl3_result, acl3_res);
+                       res = AST_TEST_FAIL;
+                       goto acl_cleanup;
+               }
+
+               if (acl4_res != acl_tests[i].acl4_result) {
+                       ast_test_status_update(test, "Access not as expected to %s on acl4. Expected %d but "
+                                       "got %d instead\n", acl_tests[i].test_address, acl_tests[i].acl4_result, acl4_res);
+                       res = AST_TEST_FAIL;
+                       goto acl_cleanup;
+               }
        }
 
 acl_cleanup:
-       if (permit_ha) {
-               ast_free_ha(permit_ha);
+       if (permit_hav4) {
+               ast_free_ha(permit_hav4);
+       }
+       if (deny_hav4) {
+               ast_free_ha(deny_hav4);
+       }
+       if (permit_hav6) {
+               ast_free_ha(permit_hav6);
        }
-       if (deny_ha) {
-               ast_free_ha(deny_ha);
+       if (deny_hav6) {
+               ast_free_ha(deny_hav6);
        }
        if (ha1) {
                ast_free_ha(ha1);
        }
-       if (ha1) {
+       if (ha2) {
                ast_free_ha(ha2);
        }
+       if (ha3) {
+               ast_free_ha(ha3);
+       }
+       if (ha4) {
+               ast_free_ha(ha4);
+       }
        return res;
 }