summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--channels/chan_iax2.c25
-rw-r--r--channels/chan_sip.c75
-rw-r--r--channels/chan_skinny.c4
-rw-r--r--configs/sip.conf.sample3
-rw-r--r--include/asterisk/acl.h16
-rw-r--r--include/asterisk/netsock2.h14
-rw-r--r--main/acl.c269
-rw-r--r--main/manager.c9
-rw-r--r--main/netsock2.c2
-rw-r--r--tests/test_acl.c204
10 files changed, 471 insertions, 150 deletions
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index 72e6b666c..92f070be2 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -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;
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 792ca6980..b6e167a79 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -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)));
}
diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c
index 31f70ec46..6d4968a7b 100644
--- a/channels/chan_skinny.c
+++ b/channels/chan_skinny.c
@@ -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)) {
diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample
index ea83d483e..1ec5ae331 100644
--- a/configs/sip.conf.sample
+++ b/configs/sip.conf.sample
@@ -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
diff --git a/include/asterisk/acl.h b/include/asterisk/acl.h
index 2c4f62051..a8c311cb2 100644
--- a/include/asterisk/acl.h
+++ b/include/asterisk/acl.h
@@ -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
diff --git a/include/asterisk/netsock2.h b/include/asterisk/netsock2.h
index e7121cb94..b73d848a0 100644
--- a/include/asterisk/netsock2.h
+++ b/include/asterisk/netsock2.h
@@ -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
diff --git a/main/acl.c b/main/acl.c
index 663c0b225..4ab102736 100644
--- a/main/acl.c
+++ b/main/acl.c
@@ -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;
}
diff --git a/main/manager.c b/main/manager.c
index e4d436903..42ad0919c 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -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");
diff --git a/main/netsock2.c b/main/netsock2.c
index 8e4f55ebf..52b8a8d39 100644
--- a/main/netsock2.c
+++ b/main/netsock2.c
@@ -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;
diff --git a/tests/test_acl.c b/tests/test_acl.c
index a0f005bc2..88180aa1b 100644
--- a/tests/test_acl.c
+++ b/tests/test_acl.c
@@ -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;
}