[svn-commits] mmichelson: trunk r277814 - in /trunk: channels/ configs/ include/asterisk/ m...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Mon Jul 19 09:17:21 CDT 2010


Author: mmichelson
Date: Mon Jul 19 09:17:16 2010
New Revision: 277814

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=277814
Log:
Make ACLs IPv6-capable.

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


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

Modified: trunk/channels/chan_iax2.c
URL: http://svnview.digium.com/svn/asterisk/trunk/channels/chan_iax2.c?view=diff&rev=277814&r1=277813&r2=277814
==============================================================================
--- trunk/channels/chan_iax2.c (original)
+++ trunk/channels/chan_iax2.c Mon Jul 19 09:17:16 2010
@@ -2039,15 +2039,17 @@
 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 @@
 {
 	struct addr_range *addr_range = obj;
 	struct sockaddr_in *sin = arg;
-
-	if ((sin->sin_addr.s_addr & addr_range->ha.netmask.s_addr) == addr_range->ha.netaddr.s_addr) {
+	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 & 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 @@
 	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 @@
 	}
 	/* 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 @@
 	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 @@
 		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;

Modified: trunk/channels/chan_sip.c
URL: http://svnview.digium.com/svn/asterisk/trunk/channels/chan_sip.c?view=diff&rev=277814&r1=277813&r2=277814
==============================================================================
--- trunk/channels/chan_sip.c (original)
+++ trunk/channels/chan_sip.c Mon Jul 19 09:17:16 2010
@@ -3084,7 +3084,7 @@
 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 @@
 				"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 @@
 	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 @@
 	}
 
 	/* 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 @@
 	}
 	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 @@
 		}
 		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 @@
 	{
 		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 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)));
 	}

Modified: trunk/channels/chan_skinny.c
URL: http://svnview.digium.com/svn/asterisk/trunk/channels/chan_skinny.c?view=diff&rev=277814&r1=277813&r2=277814
==============================================================================
--- trunk/channels/chan_skinny.c (original)
+++ trunk/channels/chan_skinny.c Mon Jul 19 09:17:16 2010
@@ -1877,8 +1877,10 @@
 
 	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)) {

Modified: trunk/configs/sip.conf.sample
URL: http://svnview.digium.com/svn/asterisk/trunk/configs/sip.conf.sample?view=diff&rev=277814&r1=277813&r2=277814
==============================================================================
--- trunk/configs/sip.conf.sample (original)
+++ trunk/configs/sip.conf.sample Mon Jul 19 09:17:16 2010
@@ -1238,6 +1238,9 @@
 ;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

Modified: trunk/include/asterisk/acl.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/acl.h?view=diff&rev=277814&r1=277813&r2=277814
==============================================================================
--- trunk/include/asterisk/acl.h (original)
+++ trunk/include/asterisk/acl.h Mon Jul 19 09:17:16 2010
@@ -46,11 +46,11 @@
  * 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 @@
  * 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 @@
  * \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

Modified: trunk/include/asterisk/netsock2.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/netsock2.h?view=diff&rev=277814&r1=277813&r2=277814
==============================================================================
--- trunk/include/asterisk/netsock2.h (original)
+++ trunk/include/asterisk/netsock2.h Mon Jul 19 09:17:16 2010
@@ -54,6 +54,20 @@
 	struct sockaddr_storage	 ss;
 	socklen_t len;
 };
+
+/*!
+ * \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

Modified: trunk/main/acl.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/acl.c?view=diff&rev=277814&r1=277813&r2=277814
==============================================================================
--- trunk/main/acl.c (original)
+++ trunk/main/acl.c Mon Jul 19 09:17:16 2010
@@ -229,8 +229,8 @@
 /* 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 @@
 	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 @@
 		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;
-			}
+		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 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 (!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 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 @@
 		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 @@
 		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;
-		}
-		ha = ha->next;
+		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;
+		}
 	}
 	return res;
 }

Modified: trunk/main/manager.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/manager.c?view=diff&rev=277814&r1=277813&r2=277814
==============================================================================
--- trunk/main/manager.c (original)
+++ trunk/main/manager.c Mon Jul 19 09:17:16 2010
@@ -2226,6 +2226,7 @@
 	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;
@@ -2233,11 +2234,13 @@
 
 	/* 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 @@
 	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 @@
 		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");

Modified: trunk/main/netsock2.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/netsock2.c?view=diff&rev=277814&r1=277813&r2=277814
==============================================================================
--- trunk/main/netsock2.c (original)
+++ trunk/main/netsock2.c Mon Jul 19 09:17:16 2010
@@ -32,7 +32,7 @@
 #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;

Modified: trunk/tests/test_acl.c
URL: http://svnview.digium.com/svn/asterisk/trunk/tests/test_acl.c?view=diff&rev=277814&r1=277813&r2=277814
==============================================================================
--- trunk/tests/test_acl.c (original)
+++ trunk/tests/test_acl.c Mon Jul 19 09:17:16 2010
@@ -35,21 +35,46 @@
 #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 @@
 	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 @@
 		{ "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 },
-	};
-
-	struct ast_ha *permit_ha = NULL;
-	struct ast_ha *deny_ha = NULL;
+		{ "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_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 @@
 		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_ha = ast_append_ha(denyall.access, denyall.host, deny_ha, &err))) {
+	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_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,61 +240,127 @@
 		}
 	}
 
+	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;
-
-		inet_aton(acl_tests[i].test_address, &sin.sin_addr);
-
-		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 (permit_res != AST_SENSE_ALLOW) {
-			ast_test_status_update(test, "Access denied to %s on permit_all ACL\n",
-					acl_tests[i].test_address);
-			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);
+		int acl3_res;
+		int acl4_res;
+
+		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;
+		}
+
+		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_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_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 (deny_ha) {
-		ast_free_ha(deny_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_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;
 }




More information about the svn-commits mailing list