]> diplodocus.org Git - nmh/blobdiff - sbr/netsec.c
Garbage-collect all of this unused code.
[nmh] / sbr / netsec.c
index cb297cc1b9714c61f16026c6c7956aada8b3cde4..3fd31854f568c56655493f6fbf499a978931cd41 100644 (file)
@@ -11,6 +11,7 @@
 #include <h/mh.h>
 #include <h/utils.h>
 #include <h/netsec.h>
 #include <h/mh.h>
 #include <h/utils.h>
 #include <h/netsec.h>
+#include <h/oauth.h>
 #include <stdarg.h>
 #include <sys/select.h>
 
 #include <stdarg.h>
 #include <sys/select.h>
 
@@ -55,6 +56,8 @@ static SSL_CTX *sslctx = NULL;                /* SSL Context */
 struct _netsec_context {
     int ns_fd;                 /* Descriptor for network connection */
     int ns_snoop;              /* If true, display network data */
 struct _netsec_context {
     int ns_fd;                 /* Descriptor for network connection */
     int ns_snoop;              /* If true, display network data */
+    int ns_snoop_noend;                /* If true, didn't get a CR/LF on last line */
+    netsec_snoop_callback ns_snoop_cb; /* Snoop output callback */
     int ns_timeout;            /* Network read timeout, in seconds */
     char *ns_userid;           /* Userid for authentication */
     unsigned char *ns_inbuffer;        /* Our read input buffer */
     int ns_timeout;            /* Network read timeout, in seconds */
     char *ns_userid;           /* Userid for authentication */
     unsigned char *ns_inbuffer;        /* Our read input buffer */
@@ -65,9 +68,12 @@ struct _netsec_context {
     unsigned char *ns_outptr;  /* Output buffer pointer */
     unsigned int ns_outbuflen; /* Output buffer data length */
     unsigned int ns_outbufsize;        /* Output buffer size */
     unsigned char *ns_outptr;  /* Output buffer pointer */
     unsigned int ns_outbuflen; /* Output buffer data length */
     unsigned int ns_outbufsize;        /* Output buffer size */
+    char *sasl_mech;           /* User-requested mechanism */
+#ifdef OAUTH_SUPPORT
+    char *oauth_service;       /* OAuth2 service name */
+#endif /* OAUTH_SUPPORT */
 #ifdef CYRUS_SASL
     char *sasl_hostname;       /* Hostname we've connected to */
 #ifdef CYRUS_SASL
     char *sasl_hostname;       /* Hostname we've connected to */
-    char *sasl_mech;           /* User-requested mechanism */
     sasl_conn_t *sasl_conn;    /* SASL connection context */
     sasl_ssf_t sasl_ssf;       /* SASL Security Strength Factor */
     netsec_sasl_callback sasl_proto_cb; /* SASL callback we use */
     sasl_conn_t *sasl_conn;    /* SASL connection context */
     sasl_ssf_t sasl_ssf;       /* SASL Security Strength Factor */
     netsec_sasl_callback sasl_proto_cb; /* SASL callback we use */
@@ -85,12 +91,6 @@ struct _netsec_context {
 #endif /* TLS_SUPPORT */
 };
 
 #endif /* TLS_SUPPORT */
 };
 
-/*
- * Function to allocate error message strings
- */
-
-static void netsec_err(char **errstr, const char *format, ...);
-
 /*
  * Function to read data from the actual network socket
  */
 /*
  * Function to read data from the actual network socket
  */
@@ -124,6 +124,8 @@ netsec_init(void)
 
     nsc->ns_fd = -1;
     nsc->ns_snoop = 0;
 
     nsc->ns_fd = -1;
     nsc->ns_snoop = 0;
+    nsc->ns_snoop_noend = 0;
+    nsc->ns_snoop_cb = NULL;
     nsc->ns_userid = NULL;
     nsc->ns_timeout = 60;      /* Our default */
     nsc->ns_inbufsize = NETSEC_BUFSIZE;
     nsc->ns_userid = NULL;
     nsc->ns_timeout = 60;      /* Our default */
     nsc->ns_inbufsize = NETSEC_BUFSIZE;
@@ -134,10 +136,13 @@ netsec_init(void)
     nsc->ns_outbuffer = mh_xmalloc(nsc->ns_outbufsize);
     nsc->ns_outptr = nsc->ns_outbuffer;
     nsc->ns_outbuflen = 0;
     nsc->ns_outbuffer = mh_xmalloc(nsc->ns_outbufsize);
     nsc->ns_outptr = nsc->ns_outbuffer;
     nsc->ns_outbuflen = 0;
+    nsc->sasl_mech = NULL;
+#ifdef OAUTH_SUPPORT
+    nsc->oauth_service = NULL;
+#endif /* OAUTH_SUPPORT */
 #ifdef CYRUS_SASL
     nsc->sasl_conn = NULL;
     nsc->sasl_hostname = NULL;
 #ifdef CYRUS_SASL
     nsc->sasl_conn = NULL;
     nsc->sasl_hostname = NULL;
-    nsc->sasl_mech = NULL;
     nsc->sasl_cbs = NULL;
     nsc->sasl_creds = NULL;
     nsc->sasl_secret = NULL;
     nsc->sasl_cbs = NULL;
     nsc->sasl_creds = NULL;
     nsc->sasl_secret = NULL;
@@ -156,11 +161,11 @@ netsec_init(void)
 
 /*
  * Shutdown the connection completely and free all resources.
 
 /*
  * Shutdown the connection completely and free all resources.
- * The connection is not closed, however.
+ * The connection is only closed if the flag is given.
  */
 
 void
  */
 
 void
-netsec_shutdown(netsec_context *nsc)
+netsec_shutdown(netsec_context *nsc, int closeflag)
 {
     if (nsc->ns_userid)
        free(nsc->ns_userid);
 {
     if (nsc->ns_userid)
        free(nsc->ns_userid);
@@ -168,13 +173,17 @@ netsec_shutdown(netsec_context *nsc)
        free(nsc->ns_inbuffer);
     if (nsc->ns_outbuffer)
        free(nsc->ns_outbuffer);
        free(nsc->ns_inbuffer);
     if (nsc->ns_outbuffer)
        free(nsc->ns_outbuffer);
+    if (nsc->sasl_mech)
+       free(nsc->sasl_mech);
+#ifdef OAUTH_SERVICE
+    if (nsc->oauth_service)
+       free(nsc->oauth_service);
+#endif /* OAUTH_SERVICE */
 #ifdef CYRUS_SASL
     if (nsc->sasl_conn)
        sasl_dispose(&nsc->sasl_conn);
     if (nsc->sasl_hostname)
        free(nsc->sasl_hostname);
 #ifdef CYRUS_SASL
     if (nsc->sasl_conn)
        sasl_dispose(&nsc->sasl_conn);
     if (nsc->sasl_hostname)
        free(nsc->sasl_hostname);
-    if (nsc->sasl_mech)
-       free(nsc->sasl_mech);
     if (nsc->sasl_cbs)
        free(nsc->sasl_cbs);
     if (nsc->sasl_creds) {
     if (nsc->sasl_cbs)
        free(nsc->sasl_cbs);
     if (nsc->sasl_creds) {
@@ -203,6 +212,9 @@ netsec_shutdown(netsec_context *nsc)
        BIO_free_all(nsc->ssl_io);
 #endif /* TLS_SUPPORT */
 
        BIO_free_all(nsc->ssl_io);
 #endif /* TLS_SUPPORT */
 
+    if (closeflag && nsc->ns_fd != -1)
+       close(nsc->ns_fd);
+
     free(nsc);
 }
 
     free(nsc);
 }
 
@@ -313,7 +325,7 @@ netsec_read(netsec_context *nsc, void *buffer, size_t size, char **errstr)
  */
 
 char *
  */
 
 char *
-netsec_readline(netsec_context *nsc, char **errstr)
+netsec_readline(netsec_context *nsc, size_t *len, char **errstr)
 {
     unsigned char *ptr = nsc->ns_inptr;
     size_t count = 0, offset;
 {
     unsigned char *ptr = nsc->ns_inptr;
     size_t count = 0, offset;
@@ -326,12 +338,29 @@ retry:
     while (count < nsc->ns_inbuflen) {
        count++;
        if (*ptr++ == '\n') {
     while (count < nsc->ns_inbuflen) {
        count++;
        if (*ptr++ == '\n') {
-           char *sptr = nsc->ns_inptr;
+           char *sptr = (char *) nsc->ns_inptr;
            if (count > 1 && *(ptr - 2) == '\r')
                ptr--;
            *--ptr = '\0';
            if (count > 1 && *(ptr - 2) == '\r')
                ptr--;
            *--ptr = '\0';
+           if (len)
+               *len = ptr - nsc->ns_inptr;
            nsc->ns_inptr += count;
            nsc->ns_inbuflen -= count;
            nsc->ns_inptr += count;
            nsc->ns_inbuflen -= count;
+           if (nsc->ns_snoop) {
+#ifdef CYRUS_SASL
+               if (nsc->sasl_seclayer)
+                   fprintf(stderr, "(sasl-encrypted) ");
+#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+               if (nsc->tls_active)
+                   fprintf(stderr, "(tls-encrypted) ");
+#endif /* TLS_SUPPORT */
+               fprintf(stderr, "<= ");
+               if (nsc->ns_snoop_cb)
+                   nsc->ns_snoop_cb(nsc, sptr, strlen(sptr));
+               else
+                   fprintf(stderr, "%s\n", sptr);
+           }
            return sptr;
        }
     }
            return sptr;
        }
     }
@@ -355,7 +384,7 @@ retry:
     offset = ptr - nsc->ns_inptr;
 
     if (netsec_fillread(nsc, errstr) != OK)
     offset = ptr - nsc->ns_inptr;
 
     if (netsec_fillread(nsc, errstr) != OK)
-       return NOTOK;
+       return NULL;
 
     ptr = nsc->ns_inptr + offset;
 
 
     ptr = nsc->ns_inptr + offset;
 
@@ -553,6 +582,11 @@ netsec_write(netsec_context *nsc, const void *buffer, size_t size,
     const unsigned char *bufptr = buffer;
     int rc, remaining;
 
     const unsigned char *bufptr = buffer;
     int rc, remaining;
 
+    /* Just in case */
+
+    if (size == 0)
+       return OK;
+
     /*
      * If TLS is active, then bypass all of our buffering logic; just
      * write it directly to our BIO.  We have a buffering BIO first in
     /*
      * If TLS is active, then bypass all of our buffering logic; just
      * write it directly to our BIO.  We have a buffering BIO first in
@@ -608,6 +642,23 @@ netsec_write(netsec_context *nsc, const void *buffer, size_t size,
     return OK;
 }
 
     return OK;
 }
 
+/*
+ * Our network printf() routine, which really just calls netsec_vprintf().
+ */
+
+int
+netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...)
+{
+    va_list ap;
+    int rc;
+
+    va_start(ap, format);
+    rc = netsec_vprintf(nsc, errstr, format, ap);
+    va_end(ap);
+
+    return rc;
+}
+
 /*
  * Write bytes to the network using printf()-style formatting.
  *
 /*
  * Write bytes to the network using printf()-style formatting.
  *
@@ -616,9 +667,9 @@ netsec_write(netsec_context *nsc, const void *buffer, size_t size,
  */
 
 int
  */
 
 int
-netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...)
+netsec_vprintf(netsec_context *nsc, char **errstr, const char *format,
+              va_list ap)
 {
 {
-    va_list ap;
     int rc;
 
     /*
     int rc;
 
     /*
@@ -626,9 +677,7 @@ netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...)
      */
 #ifdef TLS_SUPPORT
     if (nsc->tls_active) {
      */
 #ifdef TLS_SUPPORT
     if (nsc->tls_active) {
-       va_start(ap, format);
        rc = BIO_vprintf(nsc->ssl_io, format, ap);
        rc = BIO_vprintf(nsc->ssl_io, format, ap);
-       va_end(ap);
 
        if (rc <= 0) {
            netsec_err(errstr, "Error writing to TLS connection: %s",
 
        if (rc <= 0) {
            netsec_err(errstr, "Error writing to TLS connection: %s",
@@ -646,10 +695,8 @@ netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...)
      */
 
 retry:
      */
 
 retry:
-    va_start(ap, format);
     rc = vsnprintf((char *) nsc->ns_outptr,
                   nsc->ns_outbufsize - nsc->ns_outbuflen, format, ap);
     rc = vsnprintf((char *) nsc->ns_outptr,
                   nsc->ns_outbufsize - nsc->ns_outbuflen, format, ap);
-    va_end(ap);
 
     if (rc >= (int) (nsc->ns_outbufsize - nsc->ns_outbuflen)) {
        /*
 
     if (rc >= (int) (nsc->ns_outbufsize - nsc->ns_outbuflen)) {
        /*
@@ -683,6 +730,34 @@ retry:
        }
     }
 
        }
     }
 
+    if (nsc->ns_snoop) {
+       int outlen = rc;
+       if (outlen > 0 && nsc->ns_outptr[outlen - 1] == '\n') {
+           outlen--;
+           if (outlen > 0 && nsc->ns_outptr[outlen - 1] == '\r')
+               outlen--;
+       } else {
+           nsc->ns_snoop_noend = 1;
+       }
+       if (outlen > 0 || nsc->ns_snoop_noend == 0) {
+#ifdef CYRUS_SASL
+           if (nsc->sasl_seclayer)
+               fprintf(stderr, "(sasl-encrypted) ");
+#endif /* CYRUS_SASL */
+#ifdef TLS_SUPPORT
+           if (nsc->tls_active)
+               fprintf(stderr, "(tls-encrypted) ");
+#endif /* TLS_SUPPORT */
+           fprintf(stderr, "=> ");
+           if (nsc->ns_snoop_cb)
+               nsc->ns_snoop_cb(nsc, nsc->ns_outptr, outlen);
+           else
+                fprintf(stderr, "%.*s\n", outlen, nsc->ns_outptr); 
+       } else {
+           nsc->ns_snoop_noend = 0;
+       }
+    }
+
     nsc->ns_outptr += rc;
     nsc->ns_outbuflen += rc;
 
     nsc->ns_outptr += rc;
     nsc->ns_outbuflen += rc;
 
@@ -719,6 +794,13 @@ netsec_flush(netsec_context *nsc, char **errstr)
     }
 #endif /* TLS_SUPPORT */
 
     }
 #endif /* TLS_SUPPORT */
 
+    /*
+     * Small optimization
+     */
+
+    if (netoutlen == 0)
+       return OK;
+
     /*
      * If SASL security layers are in effect, run the data through
      * sasl_encode() first and then write it.
     /*
      * If SASL security layers are in effect, run the data through
      * sasl_encode() first and then write it.
@@ -936,8 +1018,104 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
     unsigned char *outbuf;
     unsigned int saslbuflen, outbuflen;
     sasl_ssf_t *ssf;
     unsigned char *outbuf;
     unsigned int saslbuflen, outbuflen;
     sasl_ssf_t *ssf;
-    int rc, *outbufmax;
+    int *outbufmax;
+#endif
+#ifdef OAUTH_SUPPORT
+    unsigned char *xoauth_client_res;
+    size_t xoauth_client_res_len;
+#endif /* OAUTH_SUPPORT */
+    int rc;
 
 
+    /*
+     * If we've been passed a requested mechanism, check our mechanism
+     * list from the protocol.  If it's not supported, return an error.
+     */
+
+    if (nsc->sasl_mech) {
+       char **str, *mlist = getcpy(mechlist);
+       int i;
+
+       str = brkstring(mlist, " ", NULL);
+
+       for (i = 0; str[i] != NULL; i++) {
+           if (strcasecmp(nsc->sasl_mech, str[i]) == 0) {
+               break;
+           }
+       }
+
+       i = (str[i] == NULL);
+
+       free(str);
+       free(mlist);
+
+       if (i) {
+           netsec_err(errstr, "Chosen mechanism %s not supported by server",
+                      nsc->sasl_mech);
+           return NOTOK;
+       }
+    }
+
+#ifdef OAUTH_SUPPORT
+    if (nsc->sasl_mech && strcasecmp(nsc->sasl_mech, "XOAUTH2") == 0) {
+       /*
+        * This should be relatively straightforward, but requires some
+        * help from the plugin.  Basically, if XOAUTH2 is a success,
+        * the callback has to return success, but no output data.  If
+        * there is output data, it will be assumed that it is the JSON
+        * error message.
+        */
+
+       if (! nsc->oauth_service) {
+           netsec_err(errstr, "Internal error: OAuth2 service name not given");
+           return NOTOK;
+       }
+
+       nsc->sasl_chosen_mech = getcpy(nsc->sasl_mech);
+
+       if (mh_oauth_do_xoauth(nsc->ns_userid, nsc->oauth_service,
+                              &xoauth_client_res, &xoauth_client_res_len,
+                              nsc->ns_snoop ? stderr : NULL) != OK) {
+           netsec_err(errstr, "Internal error: Unable to get OAuth2 "
+                      "bearer token");
+           return NOTOK;
+       }
+
+       rc = nsc->sasl_proto_cb(NETSEC_SASL_START, xoauth_client_res,
+                               xoauth_client_res_len, NULL, 0, errstr);
+       free(xoauth_client_res);
+
+       if (rc != OK)
+           return NOTOK;
+
+       /*
+        * Okay, we need to do a NETSEC_SASL_FINISH now.  If we return
+        * success, we indicate that with no output data.  But if we
+        * fail, then send a blank message and get the resulting
+        * error.
+        */
+
+       rc = nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0, errstr);
+
+       if (rc != OK) {
+           /*
+            * We're going to assume the error here is a JSON response;
+            * we ignore it and send a blank message in response.  We should
+            * then get either an +OK or -ERR
+            */
+           free(errstr);
+           nsc->sasl_proto_cb(NETSEC_SASL_WRITE, NULL, 0, NULL, 0, NULL);
+           rc = nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0,
+                                   errstr);
+           if (rc == 0) {
+               netsec_err(errstr, "Unexpected success after OAuth failure!");
+           }
+           return NOTOK;
+       }
+       return OK;
+    }
+#endif /* OAUTH_SUPPORT */
+
+#ifdef CYRUS_SASL
     /*
      * In netsec_set_sasl_params, we've already done all of our setup with
      * sasl_client_init() and sasl_client_new().  So time to set security
     /*
      * In netsec_set_sasl_params, we've already done all of our setup with
      * sasl_client_init() and sasl_client_new().  So time to set security
@@ -971,7 +1149,8 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
      * sasl_client_step() loop (after sasl_client_start, of course).
      */
 
      * sasl_client_step() loop (after sasl_client_start, of course).
      */
 
-    rc = sasl_client_start(nsc->sasl_conn, mechlist, NULL,
+    rc = sasl_client_start(nsc->sasl_conn,
+                          nsc->sasl_mech ? nsc->sasl_mech : mechlist, NULL,
                           (const char **) &saslbuf, &saslbuflen,
                           &chosen_mech);
 
                           (const char **) &saslbuf, &saslbuflen,
                           &chosen_mech);
 
@@ -983,18 +1162,8 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
 
     nsc->sasl_chosen_mech = getcpy(chosen_mech);
 
 
     nsc->sasl_chosen_mech = getcpy(chosen_mech);
 
-    if (nsc->sasl_proto_cb(NETSEC_SASL_START, saslbuf, saslbuflen, &outbuf,
-                          &outbuflen, errstr) != OK)
-       return NOTOK;
-
-    if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) {
-       free(outbuf);
-       return NOTOK;
-    }
-
-    free(outbuf);
-
-    if (netsec_flush(nsc, errstr) != OK)
+    if (nsc->sasl_proto_cb(NETSEC_SASL_START, saslbuf, saslbuflen, NULL, 0,
+                          errstr) != OK)
        return NOTOK;
 
     /*
        return NOTOK;
 
     /*
@@ -1009,52 +1178,28 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr)
 
        if (nsc->sasl_proto_cb(NETSEC_SASL_READ, NULL, 0, &outbuf, &outbuflen,
                               errstr) != OK) {
 
        if (nsc->sasl_proto_cb(NETSEC_SASL_READ, NULL, 0, &outbuf, &outbuflen,
                               errstr) != OK) {
-           if (nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, &outbuf,
-                                  &outbuflen, NULL) == OK) {
-               netsec_write(nsc, outbuf, outbuflen, NULL);
-               netsec_flush(nsc, NULL);
-               free(outbuf);
-           }
+           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL);
            return NOTOK;
        }
 
        rc = sasl_client_step(nsc->sasl_conn, (char *) outbuf, outbuflen, NULL,
                              (const char **) &saslbuf, &saslbuflen);
 
            return NOTOK;
        }
 
        rc = sasl_client_step(nsc->sasl_conn, (char *) outbuf, outbuflen, NULL,
                              (const char **) &saslbuf, &saslbuflen);
 
-       free(outbuf);
+       if (outbuf)
+           free(outbuf);
 
        if (rc != SASL_OK && rc != SASL_CONTINUE) {
            netsec_err(errstr, "SASL client negotiation failed: %s",
                       sasl_errdetail(nsc->sasl_conn));
 
        if (rc != SASL_OK && rc != SASL_CONTINUE) {
            netsec_err(errstr, "SASL client negotiation failed: %s",
                       sasl_errdetail(nsc->sasl_conn));
-           if (nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, &outbuf,
-                                  &outbuflen, NULL) == OK) {
-               netsec_write(nsc, outbuf, outbuflen, NULL);
-               netsec_flush(nsc, NULL);
-               free(outbuf);
-           }
+           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL);
            return NOTOK;
        }
 
        if (nsc->sasl_proto_cb(NETSEC_SASL_WRITE, saslbuf, saslbuflen,
            return NOTOK;
        }
 
        if (nsc->sasl_proto_cb(NETSEC_SASL_WRITE, saslbuf, saslbuflen,
-                              &outbuf, &outbuflen, errstr) != OK) {
-           if (nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, &outbuf,
-                                  &outbuflen, NULL) == OK) {
-               netsec_write(nsc, outbuf, outbuflen, NULL);
-               netsec_flush(nsc, NULL);
-               free(outbuf);
-           }
+                              NULL, 0, errstr) != OK) {
+           nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL);
            return NOTOK;
        }
            return NOTOK;
        }
-
-       if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) {
-           free(outbuf);
-           return NOTOK;
-       }
-
-       free(outbuf);
-
-       if (netsec_flush(nsc, errstr) != OK)
-           return NOTOK;
     }
 
     /*
     }
 
     /*
@@ -1164,6 +1309,21 @@ netsec_get_sasl_mechanism(netsec_context *nsc)
 #endif /* CYRUS_SASL */
 }
 
 #endif /* CYRUS_SASL */
 }
 
+/*
+ * Set an OAuth2 service name, if we support it.
+ */
+
+int
+netsec_set_oauth_service(netsec_context *nsc, const char *service)
+{
+#ifdef OAUTH_SUPPORT
+    nsc->oauth_service = getcpy(service);
+    return OK;
+#else /* OAUTH_SUPPORT */
+    return NOTOK;
+#endif /* OAUTH_SUPPORT */
+}
+
 /*
  * Initialize (and enable) TLS for this connection
  */
 /*
  * Initialize (and enable) TLS for this connection
  */
@@ -1345,7 +1505,7 @@ netsec_negotiate_tls(netsec_context *nsc, char **errstr)
  * Generate an (allocated) error string
  */
 
  * Generate an (allocated) error string
  */
 
-static void
+void
 netsec_err(char **errstr, const char *fmt, ...)
 {
     va_list ap;
 netsec_err(char **errstr, const char *fmt, ...)
 {
     va_list ap;