summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWalter Doekes <walter+asterisk@wjd.nu>2011-11-01 21:02:56 +0000
committerWalter Doekes <walter+asterisk@wjd.nu>2011-11-01 21:02:56 +0000
commitb41b49ea0e0ebdb7250f9d560b13f6fa21456694 (patch)
tree7bc973c1dc9f6e499c92491895b3a6d55ba825dd
parent25ee5f83b5c9e77be328a92b430d911396f1ac29 (diff)
Several fixes to the chan_sip dynamic realtime peer/user lookup
There were several problems with the dynamic realtime peer/user lookup code. The lookup logic had become rather hard to read due to lots of incremental changes to the realtime_peer function. And, during the addition of the sipregs functionality, several possibilities for memory leaks had been introduced. The insecure=port matching has always been broken for anyone using the sipregs family. And, related, the broken implementation forced those using sipregs to *still* have an ipaddr column on their sippeers table. Thanks Terry Wilson for comprehensive testing and finding and fixing unexpected behaviour from the multientry realtime call which caused the realtime_peer to have a completely unused code path. This changeset fixes the leaks, the lookup inconsistenties and that you won't need an ipaddr column on your sippeers table anymore (when you're using sipregs). Beware that when you're using sipregs, peers with insecure=port will now start matching! (closes issue ASTERISK-17792) (closes issue ASTERISK-18356) Reported by: marcelloceschia, Walter Doekes Reviewed by: Terry Wilson Review: https://reviewboard.asterisk.org/r/1395 ........ Merged revisions 342927 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 342929 from http://svn.asterisk.org/svn/asterisk/branches/10 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@342930 65c4cc65-6c06-0410-ace0-fbb531ad65f3
-rw-r--r--channels/chan_sip.c366
-rw-r--r--configs/extconfig.conf.sample2
-rw-r--r--include/asterisk/config.h11
-rw-r--r--main/config.c22
4 files changed, 255 insertions, 146 deletions
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 8b9b7cb7e..ff9db3ff7 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -1448,7 +1448,7 @@ static void set_socket_transport(struct sip_socket *socket, int transport);
static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr, const char *username, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms);
static void update_peer(struct sip_peer *p, int expire);
static struct ast_variable *get_insecure_variable_from_config(struct ast_config *config);
-static const char *get_name_from_variable(struct ast_variable *var, const char *newpeername);
+static const char *get_name_from_variable(const struct ast_variable *var);
static struct sip_peer *realtime_peer(const char *peername, struct ast_sockaddr *sin, int devstate_only, int which_objects);
static char *sip_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
@@ -4666,177 +4666,256 @@ static struct ast_variable *get_insecure_variable_from_config(struct ast_config
return var;
}
-static const char *get_name_from_variable(struct ast_variable *var, const char *newpeername)
+static struct ast_variable *get_insecure_variable_from_sippeers(const char *column, const char *value)
{
- struct ast_variable *tmp;
- for (tmp = var; tmp; tmp = tmp->next) {
- if (!newpeername && !strcasecmp(tmp->name, "name"))
- newpeername = tmp->value;
+ struct ast_config *peerlist;
+ struct ast_variable *var = NULL;
+ if ((peerlist = ast_load_realtime_multientry("sippeers", column, value, "insecure LIKE", "%port%", SENTINEL))) {
+ if ((var = get_insecure_variable_from_config(peerlist))) {
+ /* Must clone, because var will get freed along with
+ * peerlist. */
+ var = ast_variables_dup(var);
+ }
+ ast_config_destroy(peerlist);
}
- return newpeername;
+ return var;
}
-/*! \brief realtime_peer: Get peer from realtime storage
- * Checks the "sippeers" realtime family from extconfig.conf
- * Checks the "sipregs" realtime family from extconfig.conf if it's configured.
- * This returns a pointer to a peer and because we use build_peer, we can rest
- * assured that the refcount is bumped.
-*/
-static struct sip_peer *realtime_peer(const char *newpeername, struct ast_sockaddr *addr, int devstate_only, int which_objects)
+/* Yes.. the only column that makes sense to pass is "ipaddr", but for
+ * consistency's sake, we require the column name to be passed. As extra
+ * argument, we take a pointer to var. We already got the info, so we better
+ * return it and save the caller a query. If return value is nonzero, then *var
+ * is nonzero too (and the other way around). */
+static struct ast_variable *get_insecure_variable_from_sipregs(const char *column, const char *value, struct ast_variable **var)
{
- struct sip_peer *peer;
- struct ast_variable *var = NULL;
struct ast_variable *varregs = NULL;
- struct ast_variable *tmp;
- struct ast_config *peerlist = NULL;
- char ipaddr[INET6_ADDRSTRLEN];
- char portstring[6]; /*up to 5 digits plus null terminator*/
- int realtimeregs = ast_check_realtime("sipregs");
+ struct ast_config *regs, *peers;
+ char *regscat;
+ const char *regname;
- /* First check on peer name */
- if (newpeername) {
- if (realtimeregs)
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
-
- var = ast_load_realtime("sippeers", "name", newpeername, "host", "dynamic", SENTINEL);
- if (!var && addr) {
- var = ast_load_realtime("sippeers", "name", newpeername, "host", ast_sockaddr_stringify_addr(addr), SENTINEL);
- }
- if (!var) {
- var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
- /*!\note
- * If this one loaded something, then we need to ensure that the host
- * field matched. The only reason why we can't have this as a criteria
- * is because we only have the IP address and the host field might be
- * set as a name (and the reverse PTR might not match).
- */
- if (var && addr) {
- for (tmp = var; tmp; tmp = tmp->next) {
- if (!strcasecmp(tmp->name, "host")) {
- struct ast_sockaddr *addrs = NULL;
-
- if (ast_sockaddr_resolve(&addrs,
- tmp->value,
- PARSE_PORT_FORBID,
- get_address_family_filter(&bindaddr)) <= 0 ||
- ast_sockaddr_cmp(&addrs[0], addr)) {
- /* No match */
- ast_variables_destroy(var);
- var = NULL;
+ if (!(regs = ast_load_realtime_multientry("sipregs", column, value, SENTINEL))) {
+ return NULL;
+ }
+
+ /* Load *all* peers that are probably insecure=port */
+ if (!(peers = ast_load_realtime_multientry("sippeers", "insecure LIKE", "%port%", SENTINEL))) {
+ ast_config_destroy(regs);
+ return NULL;
+ }
+
+ /* Loop over the sipregs that match IP address and attempt to find an
+ * insecure=port match to it in sippeers. */
+ regscat = NULL;
+ while ((regscat = ast_category_browse(regs, regscat)) && (regname = ast_variable_retrieve(regs, regscat, "name"))) {
+ char *peerscat;
+ const char *peername;
+
+ peerscat = NULL;
+ while ((peerscat = ast_category_browse(peers, peerscat)) && (peername = ast_variable_retrieve(peers, peerscat, "name"))) {
+ if (!strcasecmp(regname, peername)) {
+ /* Ensure that it really is insecure=port and
+ * not something else. */
+ const char *insecure = ast_variable_retrieve(peers, peerscat, "insecure");
+ struct ast_flags flags = {0};
+ set_insecure_flags(&flags, insecure, -1);
+ if (ast_test_flag(&flags, SIP_INSECURE_PORT)) {
+ /* ENOMEM checks till the bitter end. */
+ if ((varregs = ast_variables_dup(ast_category_root(regs, regscat)))) {
+ if (!(*var = ast_variables_dup(ast_category_root(peers, peerscat)))) {
+ ast_variables_destroy(varregs);
+ varregs = NULL;
}
- ast_free(addrs);
- break;
}
+ goto done;
}
}
}
}
- if (!var && addr) { /* Then check on IP address for dynamic peers */
- ast_copy_string(ipaddr, ast_sockaddr_stringify_addr(addr), sizeof(ipaddr));
- ast_copy_string(portstring, ast_sockaddr_stringify_port(addr), sizeof(portstring));
- var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, SENTINEL); /* First check for fixed IP hosts */
- if (var) {
- if (realtimeregs) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- } else {
- if (realtimeregs)
- varregs = ast_load_realtime("sipregs", "ipaddr", ipaddr, "port", portstring, SENTINEL); /* Then check for registered hosts */
- else
- var = ast_load_realtime("sippeers", "ipaddr", ipaddr, "port", portstring, SENTINEL); /* Then check for registered hosts */
- if (varregs) {
- newpeername = get_name_from_variable(varregs, newpeername);
- var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
- }
- }
- if (!var) { /*We couldn't match on ipaddress and port, so we need to check if port is insecure*/
- peerlist = ast_load_realtime_multientry("sippeers", "host", ipaddr, SENTINEL);
- if (peerlist) {
- var = get_insecure_variable_from_config(peerlist);
- if(var) {
- if (realtimeregs) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- } else { /*var wasn't found in the list of "hosts", so try "ipaddr"*/
- peerlist = NULL;
- peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, SENTINEL);
- if(peerlist) {
- var = get_insecure_variable_from_config(peerlist);
- if(var) {
- if (realtimeregs) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- }
- }
- }
- } else {
- if (realtimeregs) {
- peerlist = ast_load_realtime_multientry("sipregs", "ipaddr", ipaddr, SENTINEL);
- if (peerlist) {
- varregs = get_insecure_variable_from_config(peerlist);
- if (varregs) {
- newpeername = get_name_from_variable(varregs, newpeername);
- var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
- }
- }
- } else {
- peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, SENTINEL);
- if (peerlist) {
- var = get_insecure_variable_from_config(peerlist);
- if (var) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
+done:
+ ast_config_destroy(regs);
+ ast_config_destroy(peers);
+ return varregs;
+}
+
+static const char *get_name_from_variable(const struct ast_variable *var)
+{
+ const struct ast_variable *tmp;
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "name")) {
+ return tmp->value;
+ }
+ }
+ return NULL;
+}
+
+/* If varregs is NULL, we don't use sipregs.
+ * Using empty if-bodies instead of goto's while avoiding unnecessary indents */
+static int realtime_peer_by_name(const char *const *name, struct ast_sockaddr *addr, const char *ipaddr, struct ast_variable **var, struct ast_variable **varregs)
+{
+ /* Peer by name and host=dynamic */
+ if ((*var = ast_load_realtime("sippeers", "name", *name, "host", "dynamic", SENTINEL))) {
+ ;
+ /* Peer by name and host=IP */
+ } else if (addr && !(*var = ast_load_realtime("sippeers", "name", *name, "host", ipaddr, SENTINEL))) {
+ ;
+ /* Peer by name and host=HOSTNAME */
+ } else if ((*var = ast_load_realtime("sippeers", "name", *name, SENTINEL))) {
+ /*!\note
+ * If this one loaded something, then we need to ensure that the host
+ * field matched. The only reason why we can't have this as a criteria
+ * is because we only have the IP address and the host field might be
+ * set as a name (and the reverse PTR might not match).
+ */
+ if (addr) {
+ struct ast_variable *tmp;
+ for (tmp = *var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "host")) {
+ struct ast_sockaddr *addrs = NULL;
+
+ if (ast_sockaddr_resolve(&addrs,
+ tmp->value,
+ PARSE_PORT_FORBID,
+ get_address_family_filter(&bindaddr)) <= 0 ||
+ ast_sockaddr_cmp(&addrs[0], addr)) {
+ /* No match */
+ ast_variables_destroy(*var);
+ *var = NULL;
}
+ ast_free(addrs);
+ break;
}
}
}
}
- if (!var) {
- if (peerlist)
- ast_config_destroy(peerlist);
- return NULL;
+ /* Did we find anything? */
+ if (*var) {
+ if (varregs) {
+ *varregs = ast_load_realtime("sipregs", "name", *name, SENTINEL);
+ }
+ return 1;
}
+ return 0;
+}
- for (tmp = var; tmp; tmp = tmp->next) {
- if (!strcasecmp(tmp->name, "type") && (!strcasecmp(tmp->value, "peer") && which_objects == FINDUSERS)) {
- if (peerlist) {
- ast_config_destroy(peerlist);
- } else {
- ast_variables_destroy(var);
- ast_variables_destroy(varregs);
- }
- return NULL;
- } else if (!newpeername && !strcasecmp(tmp->name, "name")) {
- newpeername = tmp->value;
+/* Another little helper function for backwards compatibility: this
+ * checks/fetches the sippeer that belongs to the sipreg. If none is
+ * found, we free the sipreg and return false. This way we can do the
+ * check inside the if-condition below. In the old code, not finding
+ * the sippeer also had it continue look for another match, so we do
+ * the same. */
+static struct ast_variable *realtime_peer_get_sippeer_helper(const char **name, struct ast_variable **varregs) {
+ struct ast_variable *var;
+ const char *old_name = *name;
+ *name = get_name_from_variable(*varregs);
+ if (!(var = ast_load_realtime("sippeers", "name", *name, SENTINEL))) {
+ ast_variables_destroy(*varregs);
+ *varregs = NULL;
+ *name = old_name;
+ }
+ return var;
+}
+
+/* If varregs is NULL, we don't use sipregs. If we return true, then *name is
+ * set. Using empty if-bodies instead of goto's while avoiding unnecessary
+ * indents. */
+static int realtime_peer_by_addr(const char **name, struct ast_sockaddr *addr, const char *ipaddr, struct ast_variable **var, struct ast_variable **varregs)
+{
+ char portstring[6]; /* up to 5 digits plus null terminator */
+ ast_copy_string(portstring, ast_sockaddr_stringify_port(addr), sizeof(portstring));
+
+ /* We're not finding this peer by this name anymore. Reset it. */
+ *name = NULL;
+
+ /* First check for fixed IP hosts */
+ if ((*var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, SENTINEL))) {
+ ;
+ /* Check for registered hosts (in sipregs) */
+ } else if (varregs && (*varregs = ast_load_realtime("sipregs", "ipaddr", ipaddr, "port", portstring, SENTINEL)) &&
+ (*var = realtime_peer_get_sippeer_helper(name, varregs))) {
+ ;
+ /* Check for registered hosts (in sippeers) */
+ } else if (!varregs && (*var = ast_load_realtime("sippeers", "ipaddr", ipaddr, "port", portstring, SENTINEL))) {
+ ;
+ /* We couldn't match on ipaddress and port, so we need to check if port is insecure */
+ } else if ((*var = get_insecure_variable_from_sippeers("host", ipaddr))) {
+ ;
+ /* Same as above, but try the IP address field (in sipregs)
+ * Observe that it fetches the name/var at the same time, without the
+ * realtime_peer_get_sippeer_helper. Also note that it is quite inefficient.
+ * Avoid sipregs if possible. */
+ } else if (varregs && (*varregs = get_insecure_variable_from_sipregs("ipaddr", ipaddr, var))) {
+ ;
+ /* Same as above, but try the IP address field (in sippeers) */
+ } else if (!varregs && (*var = get_insecure_variable_from_sippeers("ipaddr", ipaddr))) {
+ ;
+ }
+
+ /* Did we find anything? */
+ if (*var) {
+ /* Make sure name is populated. */
+ if (!*name) {
+ *name = get_name_from_variable(*var);
+ }
+ /* Make sure varregs is populated if var is. Ensuring
+ * that var is set when varregs is, is taken care of by
+ * realtime_peer_get_sippeer_helper(). */
+ if (varregs && !*varregs) {
+ *varregs = ast_load_realtime("sipregs", "name", *name, SENTINEL);
}
+ return 1;
}
+ return 0;
+}
- if (!newpeername) { /* Did not find peer in realtime */
+/*! \brief realtime_peer: Get peer from realtime storage
+ * Checks the "sippeers" realtime family from extconfig.conf
+ * Checks the "sipregs" realtime family from extconfig.conf if it's configured.
+ * This returns a pointer to a peer and because we use build_peer, we can rest
+ * assured that the refcount is bumped.
+ *
+ * \note This is never called with both newpeername and addr at the same time.
+ * If you do, be prepared to get a peer with a different name than newpeername.
+ */
+static struct sip_peer *realtime_peer(const char *newpeername, struct ast_sockaddr *addr, int devstate_only, int which_objects)
+{
+ struct sip_peer *peer = NULL;
+ struct ast_variable *var = NULL;
+ struct ast_variable *varregs = NULL;
+ char ipaddr[INET6_ADDRSTRLEN];
+ int realtimeregs = ast_check_realtime("sipregs");
+
+ if (addr) {
+ ast_copy_string(ipaddr, ast_sockaddr_stringify_addr(addr), sizeof(ipaddr));
+ } else {
+ ipaddr[0] = '\0';
+ }
+
+ if (newpeername && realtime_peer_by_name(&newpeername, addr, ipaddr, &var, realtimeregs ? &varregs : NULL)) {
+ ;
+ } else if (addr && realtime_peer_by_addr(&newpeername, addr, ipaddr, &var, realtimeregs ? &varregs : NULL)) {
+ ;
+ } else {
ast_log(LOG_WARNING, "Cannot Determine peer name ip=%s\n", ipaddr);
- if(peerlist)
- ast_config_destroy(peerlist);
- else
- ast_variables_destroy(var);
return NULL;
}
+ /* If we're looking for users, don't return peers (although this check
+ * should probably be done in realtime_peer_by_* instead...) */
+ if (which_objects == FINDUSERS) {
+ struct ast_variable *tmp;
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "type") && (!strcasecmp(tmp->value, "peer"))) {
+ goto cleanup;
+ }
+ }
+ }
/* Peer found in realtime, now build it in memory */
peer = build_peer(newpeername, var, varregs, TRUE, devstate_only);
if (!peer) {
- if(peerlist)
- ast_config_destroy(peerlist);
- else {
- ast_variables_destroy(var);
- ast_variables_destroy(varregs);
- }
- return NULL;
+ goto cleanup;
}
ast_debug(3, "-REALTIME- loading peer from database to memory. Name: %s. Peer objects: %d\n", peer->name, rpeerobjs);
@@ -4856,13 +4935,10 @@ static struct sip_peer *realtime_peer(const char *newpeername, struct ast_sockad
}
}
peer->is_realtime = 1;
- if (peerlist)
- ast_config_destroy(peerlist);
- else {
- ast_variables_destroy(var);
- ast_variables_destroy(varregs);
- }
+cleanup:
+ ast_variables_destroy(var);
+ ast_variables_destroy(varregs);
return peer;
}
diff --git a/configs/extconfig.conf.sample b/configs/extconfig.conf.sample
index e08674e9d..70d76ac08 100644
--- a/configs/extconfig.conf.sample
+++ b/configs/extconfig.conf.sample
@@ -64,7 +64,7 @@
;iaxusers => odbc,asterisk
;iaxpeers => odbc,asterisk
;sippeers => odbc,asterisk
-;sipregs => odbc,asterisk
+;sipregs => odbc,asterisk ; (avoid sipregs if possible, e.g. by using a view)
;voicemail => odbc,asterisk
;extensions => odbc,asterisk
;meetme => mysql,general
diff --git a/include/asterisk/config.h b/include/asterisk/config.h
index 932404d14..86c2bb5dd 100644
--- a/include/asterisk/config.h
+++ b/include/asterisk/config.h
@@ -450,6 +450,17 @@ int ast_check_realtime(const char *family);
int ast_realtime_enabled(void);
/*!
+ * \brief Duplicate variable list
+ * \param var the linked list of variables to clone
+ * \return A duplicated list which you'll need to free with
+ * ast_variables_destroy or NULL when out of memory.
+ *
+ * \note Do not depend on this to copy more than just name, value and filename
+ * (the arguments to ast_variables_new).
+ */
+struct ast_variable *ast_variables_dup(struct ast_variable *var);
+
+/*!
* \brief Free variable list
* \param var the linked list of variables to free
*
diff --git a/main/config.c b/main/config.c
index 23d98da8a..498ae993d 100644
--- a/main/config.c
+++ b/main/config.c
@@ -525,6 +525,28 @@ static void ast_variable_destroy(struct ast_variable *doomed)
ast_free(doomed);
}
+struct ast_variable *ast_variables_dup(struct ast_variable *var)
+{
+ struct ast_variable *cloned;
+ struct ast_variable *tmp;
+
+ if (!(cloned = ast_variable_new(var->name, var->value, var->file))) {
+ return NULL;
+ }
+
+ tmp = cloned;
+
+ while ((var = var->next)) {
+ if (!(tmp->next = ast_variable_new(var->name, var->value, var->file))) {
+ ast_variables_destroy(cloned);
+ return NULL;
+ }
+ tmp = tmp->next;
+ }
+
+ return cloned;
+}
+
void ast_variables_destroy(struct ast_variable *v)
{
struct ast_variable *vn;