diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c index 72e6b666c3406ffd74ec8d63e5408b00cb3d357b..92f070be2bb28653e105b1545429416f81ec94ea 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 792ca698076d8a77ff061ac1e09cba99f2c22e2b..b6e167a791921ffb8324d804b591a248af8b55ce 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 31f70ec46aa458397bd100b408b78307c0fefa42..6d4968a7b73e803ab0cd900b4c9775cb240a9e15 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 ea83d483eb8030069df5a7588c2c770b6ccbd21b..1ec5ae3314eebf5bc782e88338ff537b18d40c1c 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 2c4f6205189ee296a401cd25b3f9957155a51e86..a8c311cb25f316b6f35cf3baf135843c917940b6 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 e7121cb949592d72a691b946ce998e0889430838..b73d848a0d898991593896b6d358e47b2f2dfc0e 100644 --- a/include/asterisk/netsock2.h +++ b/include/asterisk/netsock2.h @@ -55,6 +55,20 @@ struct ast_sockaddr { 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 * diff --git a/main/acl.c b/main/acl.c index 663c0b22596d3704f180a415e1cc4ca59c72d5a8..4ab102736c6c71a91b87d3e724f0bf011b10a1c1 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, ¤t_ha->netmask, &result)) { + /* Unlikely to happen since we know the address to be IPv4 or IPv6 */ + continue; + } + if (!ast_sockaddr_cmp_addr(&result, ¤t_ha->addr)) { + res = current_ha->sense; } - ha = ha->next; } return res; } diff --git a/main/manager.c b/main/manager.c index e4d436903ef1ebdded28832e619457b71b102b86..42ad0919cf4639a1384ab21113cbaab74b99e296 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 8e4f55ebf7e90111f3143cdfa41cea9fa500b550..52b8a8d391dcc784469f2de95e71382055c9014c 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 a0f005bc2fc662e3471e45d7afefb2a7228016c8..88180aa1b2bb8bebfd19f1a807b9c9ae7d412a54 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; }