<p>Friendly Automation <strong>submitted</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/18401">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Michael Bradeen: Looks good to me, approved
  Friendly Automation: Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">AST-2022-001 - res_stir_shaken/curl: Limit file size and check start.<br><br>Put checks in place to limit how much we will actually download, as well<br>as a check for the data we receive at the start to ensure it begins with<br>what we would expect a certificate to begin with.<br><br>ASTERISK-29872<br><br>Change-Id: Ifd3c6b8bd52b8b6192a04166ccce4fc8a8000b46<br>---<br>M res/res_stir_shaken/curl.c<br>M res/res_stir_shaken/stir_shaken.c<br>M res/res_stir_shaken/stir_shaken.h<br>3 files changed, 107 insertions(+), 60 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/res/res_stir_shaken/curl.c b/res/res_stir_shaken/curl.c</span><br><span>index b31160f..cd78461 100644</span><br><span>--- a/res/res_stir_shaken/curl.c</span><br><span>+++ b/res/res_stir_shaken/curl.c</span><br><span>@@ -31,12 +31,27 @@</span><br><span> /* Used to check CURL headers */</span><br><span> #define MAX_HEADER_LENGTH 1023</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/* Used to limit download size */</span><br><span style="color: hsl(120, 100%, 40%);">+#define MAX_DOWNLOAD_SIZE 8192</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Used to limit how many bytes we get from CURL per write */</span><br><span style="color: hsl(120, 100%, 40%);">+#define MAX_BUF_SIZE_PER_WRITE 1024</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Certificates should begin with this */</span><br><span style="color: hsl(120, 100%, 40%);">+#define BEGIN_CERTIFICATE_STR "-----BEGIN CERTIFICATE-----"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /* CURL callback data to avoid storing useless info in AstDB */</span><br><span> struct curl_cb_data {</span><br><span>  char *cache_control;</span><br><span>         char *expires;</span><br><span> };</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+struct curl_cb_write_buf {</span><br><span style="color: hsl(120, 100%, 40%);">+  char buf[MAX_DOWNLOAD_SIZE + 1];</span><br><span style="color: hsl(120, 100%, 40%);">+      size_t size;</span><br><span style="color: hsl(120, 100%, 40%);">+  const char *url;</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> struct curl_cb_data *curl_cb_data_create(void)</span><br><span> {</span><br><span>        struct curl_cb_data *data;</span><br><span>@@ -149,94 +164,132 @@</span><br><span>  return curl;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Write callback passed to libcurl</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note If this function returns anything other than the size of the data</span><br><span style="color: hsl(120, 100%, 40%);">+ * libcurl expected us to process, the request will cancel. That's why we return</span><br><span style="color: hsl(120, 100%, 40%);">+ * 0 on error, otherwise the amount of data we were given</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param curl_data The data from libcurl</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param size Always 1 according to libcurl</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param actual_size The actual size of the data</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param our_data The data we passed to libcurl</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval The size of the data we processed</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 if there was an error</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static size_t curl_write_cb(void *curl_data, size_t size, size_t actual_size, void *our_data)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+        /* Just in case size is NOT always 1 or if it's changed in the future, let's go ahead</span><br><span style="color: hsl(120, 100%, 40%);">+  * and do the math for the actual size */</span><br><span style="color: hsl(120, 100%, 40%);">+     size_t real_size = size * actual_size;</span><br><span style="color: hsl(120, 100%, 40%);">+        struct curl_cb_write_buf *buf = our_data;</span><br><span style="color: hsl(120, 100%, 40%);">+     size_t new_size = buf->size + real_size;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (new_size > MAX_DOWNLOAD_SIZE) {</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_log(LOG_WARNING, "Attempted to retrieve certificate from %s failed "</span><br><span style="color: hsl(120, 100%, 40%);">+                    "because it's size exceeds the maximum %d bytes\n", buf->url, MAX_DOWNLOAD_SIZE);</span><br><span style="color: hsl(120, 100%, 40%);">+            return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   memcpy(&(buf->buf[buf->size]), curl_data, real_size);</span><br><span style="color: hsl(120, 100%, 40%);">+       buf->size += real_size;</span><br><span style="color: hsl(120, 100%, 40%);">+    buf->buf[buf->size] = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      return real_size;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data)</span><br><span> {</span><br><span>       FILE *public_key_file;</span><br><span style="color: hsl(0, 100%, 40%);">-  RAII_VAR(char *, tmp_filename, NULL, ast_free);</span><br><span style="color: hsl(0, 100%, 40%);">- const char *template_name = "certXXXXXX";</span><br><span>  char *filename;</span><br><span>      char *serial;</span><br><span style="color: hsl(0, 100%, 40%);">-   int fd;</span><br><span>      long http_code;</span><br><span>      CURL *curl;</span><br><span>  char curl_errbuf[CURL_ERROR_SIZE + 1];</span><br><span style="color: hsl(120, 100%, 40%);">+        struct curl_cb_write_buf *buf;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    buf = ast_calloc(1, sizeof(*buf));</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!buf) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "Failed to allocate memory for CURL write buffer for %s\n", public_cert_url);</span><br><span style="color: hsl(120, 100%, 40%);">+            return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   buf->url = public_cert_url;</span><br><span>       curl_errbuf[CURL_ERROR_SIZE] = '\0';</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-        /* For now, it's fine to pass in path as is - it shouldn't end with a '/'. However,</span><br><span style="color: hsl(0, 100%, 40%);">-      * if we decide to change how certificates are stored in the future (configurable paths),</span><br><span style="color: hsl(0, 100%, 40%);">-        * then we will need to check to see if path ends with '/', copy everything up to the '/',</span><br><span style="color: hsl(0, 100%, 40%);">-       * and use this new variable for ast_create_temp_file as well as for ast_asprintf below.</span><br><span style="color: hsl(0, 100%, 40%);">-         */</span><br><span style="color: hsl(0, 100%, 40%);">-     fd = ast_file_fdtemp(path, &tmp_filename, template_name);</span><br><span style="color: hsl(0, 100%, 40%);">-   if (fd == -1) {</span><br><span style="color: hsl(0, 100%, 40%);">-         ast_log(LOG_ERROR, "Failed to get temporary file descriptor for CURL\n");</span><br><span style="color: hsl(0, 100%, 40%);">-             return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-    }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       public_key_file = fdopen(fd, "wb");</span><br><span style="color: hsl(0, 100%, 40%);">-   if (!public_key_file) {</span><br><span style="color: hsl(0, 100%, 40%);">-         ast_log(LOG_ERROR, "Failed to open file '%s' to write public key from '%s': %s (%d)\n",</span><br><span style="color: hsl(0, 100%, 40%);">-                       tmp_filename, public_cert_url, strerror(errno), errno);</span><br><span style="color: hsl(0, 100%, 40%);">-         close(fd);</span><br><span style="color: hsl(0, 100%, 40%);">-              remove(tmp_filename);</span><br><span style="color: hsl(0, 100%, 40%);">-           return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-    }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span>    curl = get_curl_instance(data);</span><br><span>      if (!curl) {</span><br><span>                 ast_log(LOG_ERROR, "Failed to set up CURL instance for '%s'\n", public_cert_url);</span><br><span style="color: hsl(0, 100%, 40%);">-             fclose(public_key_file);</span><br><span style="color: hsl(0, 100%, 40%);">-                remove(tmp_filename);</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_free(buf);</span><br><span>               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        curl_easy_setopt(curl, CURLOPT_URL, public_cert_url);</span><br><span style="color: hsl(0, 100%, 40%);">-   curl_easy_setopt(curl, CURLOPT_WRITEDATA, public_key_file);</span><br><span style="color: hsl(120, 100%, 40%);">+   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, buf);</span><br><span>      curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);</span><br><span style="color: hsl(120, 100%, 40%);">+     curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, MAX_BUF_SIZE_PER_WRITE);</span><br><span> </span><br><span>      if (curl_easy_perform(curl)) {</span><br><span>               ast_log(LOG_ERROR, "%s\n", curl_errbuf);</span><br><span>           curl_easy_cleanup(curl);</span><br><span style="color: hsl(0, 100%, 40%);">-                fclose(public_key_file);</span><br><span style="color: hsl(0, 100%, 40%);">-                remove(tmp_filename);</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_free(buf);</span><br><span>               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);</span><br><span> </span><br><span>         curl_easy_cleanup(curl);</span><br><span style="color: hsl(0, 100%, 40%);">-        fclose(public_key_file);</span><br><span> </span><br><span>         if (http_code / 100 != 2) {</span><br><span>          ast_log(LOG_ERROR, "Failed to retrieve URL '%s': code %ld\n", public_cert_url, http_code);</span><br><span style="color: hsl(0, 100%, 40%);">-            remove(tmp_filename);</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_free(buf);</span><br><span>               return NULL;</span><br><span>         }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   serial = stir_shaken_get_serial_number_x509(tmp_filename);</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!ast_begins_with(buf->buf, BEGIN_CERTIFICATE_STR)) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_WARNING, "Certificate from %s does not begin with what we expect\n", public_cert_url);</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_free(buf);</span><br><span style="color: hsl(120, 100%, 40%);">+                return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   serial = stir_shaken_get_serial_number_x509(buf->buf, buf->size);</span><br><span>      if (!serial) {</span><br><span style="color: hsl(0, 100%, 40%);">-          ast_log(LOG_ERROR, "Failed to get serial from cert %s\n", tmp_filename);</span><br><span style="color: hsl(0, 100%, 40%);">-              remove(tmp_filename);</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_log(LOG_ERROR, "Failed to get serial from CURL buffer from %s\n", public_cert_url);</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_free(buf);</span><br><span>               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        if (ast_asprintf(&filename, "%s/%s.pem", path, serial) < 0) {</span><br><span style="color: hsl(0, 100%, 40%);">-          ast_log(LOG_ERROR, "Failed to allocate memory for new filename for temporary "</span><br><span style="color: hsl(0, 100%, 40%);">-                        "file %s after CURL\n", tmp_filename);</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_log(LOG_ERROR, "Failed to allocate memory for filename after CURL from %s\n", public_cert_url);</span><br><span>                ast_free(serial);</span><br><span style="color: hsl(0, 100%, 40%);">-               remove(tmp_filename);</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_free(buf);</span><br><span>               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        ast_free(serial);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   if (rename(tmp_filename, filename)) {</span><br><span style="color: hsl(0, 100%, 40%);">-           ast_log(LOG_ERROR, "Failed to rename temporary file %s to %s after CURL\n", tmp_filename, filename);</span><br><span style="color: hsl(120, 100%, 40%);">+        public_key_file = fopen(filename, "w");</span><br><span style="color: hsl(120, 100%, 40%);">+     if (!public_key_file) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_log(LOG_ERROR, "Failed to open file '%s' to write public key from '%s': %s (%d)\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                     filename, public_cert_url, strerror(errno), errno);</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_free(buf);</span><br><span>               ast_free(filename);</span><br><span style="color: hsl(0, 100%, 40%);">-             remove(tmp_filename);</span><br><span>                return NULL;</span><br><span>         }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ if (fputs(buf->buf, public_key_file) == EOF) {</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_log(LOG_ERROR, "Failed to write string to file from URL %s\n", public_cert_url);</span><br><span style="color: hsl(120, 100%, 40%);">+                fclose(public_key_file);</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_free(buf);</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_free(filename);</span><br><span style="color: hsl(120, 100%, 40%);">+           return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   fclose(public_key_file);</span><br><span style="color: hsl(120, 100%, 40%);">+      ast_free(buf);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>     return filename;</span><br><span> }</span><br><span>diff --git a/res/res_stir_shaken/stir_shaken.c b/res/res_stir_shaken/stir_shaken.c</span><br><span>index 6bc07ea..9e17f7c 100644</span><br><span>--- a/res/res_stir_shaken/stir_shaken.c</span><br><span>+++ b/res/res_stir_shaken/stir_shaken.c</span><br><span>@@ -137,41 +137,35 @@</span><br><span>       return key;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-char *stir_shaken_get_serial_number_x509(const char *path)</span><br><span style="color: hsl(120, 100%, 40%);">+char *stir_shaken_get_serial_number_x509(const char *buf, size_t buf_size)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">- FILE *fp;</span><br><span style="color: hsl(120, 100%, 40%);">+     BIO *certBIO;</span><br><span>        X509 *cert;</span><br><span>  ASN1_INTEGER *serial;</span><br><span>        BIGNUM *bignum;</span><br><span>      char *serial_hex;</span><br><span>    char *ret;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-  fp = fopen(path, "r");</span><br><span style="color: hsl(0, 100%, 40%);">-        if (!fp) {</span><br><span style="color: hsl(0, 100%, 40%);">-              ast_log(LOG_ERROR, "Failed to open file %s\n", path);</span><br><span style="color: hsl(0, 100%, 40%);">-         return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-    }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       cert = PEM_read_X509(fp, NULL, NULL, NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+   certBIO = BIO_new(BIO_s_mem());</span><br><span style="color: hsl(120, 100%, 40%);">+       BIO_write(certBIO, buf, buf_size);</span><br><span style="color: hsl(120, 100%, 40%);">+    cert = PEM_read_bio_X509(certBIO, NULL, NULL, NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+  BIO_free(certBIO);</span><br><span>   if (!cert) {</span><br><span style="color: hsl(0, 100%, 40%);">-            ast_log(LOG_ERROR, "Failed to read X.509 cert from file %s\n", path);</span><br><span style="color: hsl(0, 100%, 40%);">-         fclose(fp);</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "Failed to read X.509 cert from buffer\n");</span><br><span>             return NULL;</span><br><span>         }</span><br><span> </span><br><span>        serial = X509_get_serialNumber(cert);</span><br><span>        if (!serial) {</span><br><span style="color: hsl(0, 100%, 40%);">-          ast_log(LOG_ERROR, "Failed to get serial number from certificate %s\n", path);</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_log(LOG_ERROR, "Failed to get serial number from certificate\n");</span><br><span>              X509_free(cert);</span><br><span style="color: hsl(0, 100%, 40%);">-                fclose(fp);</span><br><span>          return NULL;</span><br><span>         }</span><br><span> </span><br><span>        bignum = ASN1_INTEGER_to_BN(serial, NULL);</span><br><span>   if (bignum == NULL) {</span><br><span style="color: hsl(0, 100%, 40%);">-           ast_log(LOG_ERROR, "Failed to convert serial to bignum for certificate %s\n", path);</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_log(LOG_ERROR, "Failed to convert serial to bignum for certificate\n");</span><br><span>                X509_free(cert);</span><br><span style="color: hsl(0, 100%, 40%);">-                fclose(fp);</span><br><span>          return NULL;</span><br><span>         }</span><br><span> </span><br><span>@@ -181,18 +175,17 @@</span><br><span>         */</span><br><span>  serial_hex = BN_bn2hex(bignum);</span><br><span>      X509_free(cert);</span><br><span style="color: hsl(0, 100%, 40%);">-        fclose(fp);</span><br><span>  BN_free(bignum);</span><br><span> </span><br><span>         if (!serial_hex) {</span><br><span style="color: hsl(0, 100%, 40%);">-              ast_log(LOG_ERROR, "Failed to convert bignum to hex for certificate %s\n", path);</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "Failed to convert bignum to hex for certificate\n");</span><br><span>           return NULL;</span><br><span>         }</span><br><span> </span><br><span>        ret = ast_strdup(serial_hex);</span><br><span>        OPENSSL_free(serial_hex);</span><br><span>    if (!ret) {</span><br><span style="color: hsl(0, 100%, 40%);">-             ast_log(LOG_ERROR, "Failed to dup serial from openssl for certificate %s\n", path);</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_log(LOG_ERROR, "Failed to dup serial from openssl for certificate\n");</span><br><span>                 return NULL;</span><br><span>         }</span><br><span> </span><br><span>diff --git a/res/res_stir_shaken/stir_shaken.h b/res/res_stir_shaken/stir_shaken.h</span><br><span>index 90df4e9..a707c3b 100644</span><br><span>--- a/res/res_stir_shaken/stir_shaken.h</span><br><span>+++ b/res/res_stir_shaken/stir_shaken.h</span><br><span>@@ -53,15 +53,16 @@</span><br><span> EVP_PKEY *stir_shaken_read_key(const char *path, int priv);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(0, 100%, 40%);">- * \brief Gets the serial number in hex form from the X509 certificate at path</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Gets the serial number in hex form from the buffer (for X509)</span><br><span>  *</span><br><span>  * \note The returned string will need to be freed by the caller</span><br><span>  *</span><br><span style="color: hsl(0, 100%, 40%);">- * \param path The full path of the X509 certificate</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param buf The BASE64 encoded buffer</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param buf_size The size of the data in buf</span><br><span>  *</span><br><span>  * \retval NULL on failure</span><br><span>  * \retval serial number on success</span><br><span>  */</span><br><span style="color: hsl(0, 100%, 40%);">-char *stir_shaken_get_serial_number_x509(const char *path);</span><br><span style="color: hsl(120, 100%, 40%);">+char *stir_shaken_get_serial_number_x509(const char *buf, size_t buf_size);</span><br><span> </span><br><span> #endif /* _STIR_SHAKEN_H */</span><br><span></span><br></pre><div style="white-space:pre-wrap"></div><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/18401">change 18401</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/c/asterisk/+/18401"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 18 </div>
<div style="display:none"> Gerrit-Change-Id: Ifd3c6b8bd52b8b6192a04166ccce4fc8a8000b46 </div>
<div style="display:none"> Gerrit-Change-Number: 18401 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: Michael Bradeen <mbradeen@sangoma.com> </div>
<div style="display:none"> Gerrit-CC: Benjamin Keith Ford <bford@digium.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>