res_pjsip_endpoint_identifier_ip.c: Add port matching support
authorSean Bright <sean.bright@gmail.com>
Tue, 24 Dec 2019 15:16:23 +0000 (10:16 -0500)
committerSean Bright <sean.bright@gmail.com>
Wed, 8 Jan 2020 14:37:53 +0000 (08:37 -0600)
Adds source port matching support when IP matching is used:

  [example]
  type = identify
  match = 1.2.3.4:5060/32, 1.2.3.4:6000/32, asterisk.org:4444

If the IP matches but the source port does not, we reject and search for
alternatives. SRV lookups are still performed if enabled (srv_lookups = yes),
unless the configured FQDN includes a port number in which case just a host
lookup is performed.

ASTERISK-28639 #close
Reported by: Mitch Claborn

Change-Id: I256d5bd5d478b95f526e2f80ace31b690eebba92

configs/samples/pjsip.conf.sample
doc/CHANGES-staging/res_pjsip_endpoint_identifier_ip_match_port.txt [new file with mode: 0644]
include/asterisk/acl.h
main/acl.c
res/res_pjsip_endpoint_identifier_ip.c

index c7553b7..92d9aaa 100644 (file)
 ;endpoint=mytrunk
 ;match=198.51.100.1
 ;match=198.51.100.2
+;match=192.168.10.0:5061/24
 
 
 ;=============ENDPOINT CONFIGURED AS A TRUNK, INBOUND AUTH AND REGISTRATION===
diff --git a/doc/CHANGES-staging/res_pjsip_endpoint_identifier_ip_match_port.txt b/doc/CHANGES-staging/res_pjsip_endpoint_identifier_ip_match_port.txt
new file mode 100644 (file)
index 0000000..3881d64
--- /dev/null
@@ -0,0 +1,8 @@
+Subject: res_pjsip_endpoint_identifier_ip
+
+In 'type = identify' sections, the addresses specified for the 'match'
+clause can now include a port number. For IP addresses, the port is
+provided by including a colon after the address, followed by the
+desired port number. If supplied, the netmask should follow the port
+number. To specify a port for IPv6 addresses, the address itself must
+be enclosed in brackets to be parsed correctly.
index f7655fe..4abe9fb 100644 (file)
@@ -135,6 +135,29 @@ void ast_copy_ha(const struct ast_ha *from, struct ast_ha *to);
 struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha *path, int *error);
 
 /*!
+ * \brief Add a new rule with optional port to a list of HAs
+ * \since 13.31.0, 16.8.0, 17.2.0
+ *
+ * \details
+ * This adds the new host access rule to the end of the list
+ * whose head is specified by the path parameter. Rules are
+ * evaluated in a way such that if multiple rules apply to
+ * a single IP address/subnet mask, then the rule latest
+ * in the list will be used.
+ *
+ * \param sense Either "permit" or "deny" (Actually any 'p' word will result
+ * in permission, and any other word will result in denial)
+ * \param stuff The IP address and subnet mask, separated with a '/'. The subnet
+ * mask can either be in dotted-decimal format or in CIDR notation (i.e. 0-32). A
+ * port can be provided by placing it after the IP address, separated with a ':'.
+ * \param path The head of the HA list to which we wish to append our new rule. If
+ * NULL is passed, then the new rule will become the head of the list
+ * \param[out] error The integer error points to will be set non-zero if an error occurs
+ * \return The head of the HA list
+ */
+struct ast_ha *ast_append_ha_with_port(const char *sense, const char *stuff, struct ast_ha *path, int *error);
+
+/*!
  * \brief Convert HAs to a comma separated string value
  * \param ha the starting ha head
  * \param buf string buffer to convert data to
index 27da4ee..bca1e50 100644 (file)
@@ -572,7 +572,7 @@ static void debug_ha_sense_appended(struct ast_ha *ha)
                ha->sense);
 }
 
-struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha *path, int *error)
+static struct ast_ha *append_ha_core(const char *sense, const char *stuff, struct ast_ha *path, int *error, int port_flags)
 {
        struct ast_ha *ha;
        struct ast_ha *prev = NULL;
@@ -589,6 +589,8 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
        }
 
        while ((tmp = strsep(&list, ","))) {
+               uint16_t save_port;
+
                if (!(ha = ast_calloc(1, sizeof(*ha)))) {
                        if (error) {
                                *error = 1;
@@ -610,7 +612,7 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
                        ha->sense = allowing;
                }
 
-               if (!ast_sockaddr_parse(&ha->addr, address, PARSE_PORT_FORBID)) {
+               if (!ast_sockaddr_parse(&ha->addr, address, port_flags)) {
                        ast_log(LOG_WARNING, "Invalid IP address: %s\n", address);
                        ast_free_ha(ha);
                        if (error) {
@@ -619,6 +621,11 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
                        return ret;
                }
 
+               /* Be pedantic and zero out the port if we don't want it */
+               if ((port_flags & PARSE_PORT_MASK) == PARSE_PORT_FORBID) {
+                       ast_sockaddr_set_port(&ha->addr, 0);
+               }
+
                /* If someone specifies an IPv4-mapped IPv6 address,
                 * we just convert this to an IPv4 ACL
                 */
@@ -667,6 +674,10 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
                        return ret;
                }
 
+               /* ast_sockaddr_apply_netmask() does not preserve the port, so we need to save and
+                * restore it */
+               save_port = ast_sockaddr_port(&ha->addr);
+
                if (ast_sockaddr_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
@@ -681,6 +692,8 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
                        return ret;
                }
 
+               ast_sockaddr_set_port(&ha->addr, save_port);
+
                if (prev) {
                        prev->next = ha;
                } else {
@@ -696,12 +709,30 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
        return ret;
 }
 
+struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha *path, int *error)
+{
+       return append_ha_core(sense, stuff, path, error, PARSE_PORT_FORBID);
+}
+
+struct ast_ha *ast_append_ha_with_port(const char *sense, const char *stuff, struct ast_ha *path, int *error)
+{
+       return append_ha_core(sense, stuff, path, error, 0);
+}
+
 void ast_ha_join(const struct ast_ha *ha, struct ast_str **buf)
 {
        for (; ha; ha = ha->next) {
+               const char *addr;
+
+               if (ast_sockaddr_port(&ha->addr)) {
+                       addr = ast_sockaddr_stringify(&ha->addr);
+               } else {
+                       addr = ast_sockaddr_stringify_addr(&ha->addr);
+               }
+
                ast_str_append(buf, 0, "%s%s/",
                        ha->sense == AST_SENSE_ALLOW ? "!" : "",
-                       ast_sockaddr_stringify_addr(&ha->addr));
+                       addr);
                /* Separated to avoid duplicating stringified addresses. */
                ast_str_append(buf, 0, "%s", ast_sockaddr_stringify_addr(&ha->netmask));
                if (ha->next) {
@@ -783,6 +814,7 @@ enum ast_acl_sense ast_apply_ha(const struct ast_ha *ha, const struct ast_sockad
                struct ast_sockaddr result;
                struct ast_sockaddr mapped_addr;
                const struct ast_sockaddr *addr_to_use;
+               uint16_t save_port;
 #if 0  /* debugging code */
                char iabuf[INET_ADDRSTRLEN];
                char iabuf2[INET_ADDRSTRLEN];
@@ -818,13 +850,22 @@ enum ast_acl_sense ast_apply_ha(const struct ast_ha *ha, const struct ast_sockad
                        }
                }
 
+               /* ast_sockaddr_apply_netmask() does not preserve the port, so we need to save and
+                * restore it */
+               save_port = ast_sockaddr_port(addr_to_use);
+
                /* For each rule, if this address and the netmask = the net address
                   apply the current rule */
                if (ast_sockaddr_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)) {
+
+               ast_sockaddr_set_port(&result, save_port);
+
+               if (!ast_sockaddr_cmp_addr(&result, &current_ha->addr)
+                  && (!ast_sockaddr_port(&current_ha->addr)
+                         || ast_sockaddr_port(&current_ha->addr) == ast_sockaddr_port(&result))) {
                        res = current_ha->sense;
                }
        }
index a4bdab4..db4edbf 100644 (file)
@@ -63,7 +63,9 @@
                                                hostnames.  IP addresses may have a subnet mask appended.  The
                                                subnet mask may be written in either CIDR or dotted-decimal
                                                notation.  Separate the IP address and subnet mask with a slash
-                                               ('/').
+                                               ('/'). A source port can also be specified by adding a colon (':')
+                                               after the address but before the subnet mask, e.g.
+                                               3.2.1.0:5061/24.
                                                </para>
                                        </description>
                                </configOption>
@@ -310,7 +312,7 @@ static int ip_identify_match_host_lookup(struct ip_identify_match *identify, con
        int num_addrs = 0, error = 0, i;
        int results = 0;
 
-       num_addrs = ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC);
+       num_addrs = ast_sockaddr_resolve(&addrs, host, 0, AST_AF_UNSPEC);
        if (!num_addrs) {
                return -1;
        }
@@ -322,7 +324,7 @@ static int ip_identify_match_host_lookup(struct ip_identify_match *identify, con
                }
 
                /* We deny what we actually want to match because there is an implicit permit all rule for ACLs */
-               identify->matches = ast_append_ha("d", ast_sockaddr_stringify_addr(&addrs[i]), identify->matches, &error);
+               identify->matches = ast_append_ha_with_port("d", ast_sockaddr_stringify(&addrs[i]), identify->matches, &error);
 
                if (!identify->matches || error) {
                        results = -1;
@@ -380,15 +382,20 @@ static int ip_identify_match_handler(const struct aco_option *opt, struct ast_va
        }
 
        while ((current_string = ast_strip(strsep(&input_string, ",")))) {
-               char *mask = strrchr(current_string, '/');
+               char *mask;
+               struct ast_sockaddr address;
                int error = 0;
 
                if (ast_strlen_zero(current_string)) {
                        continue;
                }
 
-               if (mask) {
-                       identify->matches = ast_append_ha("d", current_string, identify->matches, &error);
+               mask = strrchr(current_string, '/');
+
+               /* If it looks like a netmask is present, or we can immediately parse as an IP,
+                * hand things off to the ACL */
+               if (mask || ast_sockaddr_parse(&address, current_string, 0)) {
+                       identify->matches = ast_append_ha_with_port("d", current_string, identify->matches, &error);
 
                        if (!identify->matches || error) {
                                ast_log(LOG_ERROR, "Failed to add address '%s' to ip endpoint identifier '%s'\n",
@@ -498,20 +505,23 @@ static int ip_identify_apply(const struct ast_sorcery *sorcery, void *obj)
        /* Resolve the match addresses now */
        i = ao2_iterator_init(identify->hosts, 0);
        while ((current_string = ao2_iterator_next(&i))) {
-               struct ast_sockaddr address;
                int results = 0;
-
-               /* If the provided string is not an IP address perform SRV resolution on it */
-               if (identify->srv_lookups && !ast_sockaddr_parse(&address, current_string, 0)) {
-                       results = ip_identify_match_srv_lookup(identify, "_sip._udp", current_string,
-                               results);
-                       if (results != -1) {
-                               results = ip_identify_match_srv_lookup(identify, "_sip._tcp",
-                                       current_string, results);
-                       }
-                       if (results != -1) {
-                               results = ip_identify_match_srv_lookup(identify, "_sips._tcp",
-                                       current_string, results);
+               char *colon = strrchr(current_string, ':');
+
+               /* We skip SRV lookup if a colon is present, assuming a port was specified */
+               if (!colon) {
+                       /* No port, and we know this is not an IP address, so perform SRV resolution on it */
+                       if (identify->srv_lookups) {
+                               results = ip_identify_match_srv_lookup(identify, "_sip._udp", current_string,
+                                       results);
+                               if (results != -1) {
+                                       results = ip_identify_match_srv_lookup(identify, "_sip._tcp",
+                                               current_string, results);
+                               }
+                               if (results != -1) {
+                                       results = ip_identify_match_srv_lookup(identify, "_sips._tcp",
+                                               current_string, results);
+                               }
                        }
                }
 
@@ -554,7 +564,14 @@ static int match_to_str(const void *obj, const intptr_t *args, char **buf)
 static void match_to_var_list_append(struct ast_variable **head, struct ast_ha *ha)
 {
        char str[MAX_OBJECT_FIELD];
-       const char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
+       const char *addr;
+
+       if (ast_sockaddr_port(&ha->addr)) {
+               addr = ast_strdupa(ast_sockaddr_stringify(&ha->addr));
+       } else {
+               addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
+       }
+
        snprintf(str, MAX_OBJECT_FIELD, "%s%s/%s", ha->sense == AST_SENSE_ALLOW ? "!" : "",
                         addr, ast_sockaddr_stringify_addr(&ha->netmask));
 
@@ -737,7 +754,13 @@ static int cli_print_body(void *obj, void *arg, int flags)
                indent = CLI_INDENT_TO_SPACES(context->indent_level);
 
                for (match = ident->matches; match; match = match->next) {
-                       const char *addr = ast_sockaddr_stringify_addr(&match->addr);
+                       const char *addr;
+
+                       if (ast_sockaddr_port(&match->addr)) {
+                               addr = ast_sockaddr_stringify(&match->addr);
+                       } else {
+                               addr = ast_sockaddr_stringify_addr(&match->addr);
+                       }
 
                        ast_str_append(&context->output_buffer, 0, "%*s: %s%s/%d\n",
                                indent,