]> jfr.im git - solanum.git/blobdiff - authd/providers/opm.c
opm: minor fixes
[solanum.git] / authd / providers / opm.c
index 71d1153e499240dc76351c9de88ecf3f0c363f5b..704e18157d86957a95997785103a1f0b11682e03 100644 (file)
@@ -25,6 +25,8 @@
 #include "notice.h"
 #include "provider.h"
 
+#define SELF_PID (opm_provider.id)
+
 #define OPM_READSIZE 128
 
 typedef enum protocol_t
@@ -32,18 +34,24 @@ typedef enum protocol_t
        PROTO_NONE,
        PROTO_SOCKS4,
        PROTO_SOCKS5,
+       PROTO_HTTP_CONNECT,
+       PROTO_HTTPS_CONNECT,
 } protocol_t;
 
 struct opm_lookup
 {
        rb_dlink_list scans;    /* List of scans */
+       bool in_progress;
 };
 
 struct opm_proxy
 {
-       const char *note;
+       char note[16];
        protocol_t proto;
        uint16_t port;
+       bool ssl;
+
+       rb_dlink_node node;
 };
 
 struct opm_scan
@@ -65,17 +73,7 @@ struct opm_listener
 };
 
 /* Proxies that we scan for */
-static struct opm_proxy opm_proxy_scans[] =
-{
-       { "socks4-1080",        PROTO_SOCKS4,           1080  },
-       { "socks5-1080",        PROTO_SOCKS5,           1080  },
-       { "socks4-80",          PROTO_SOCKS4,           80    },
-       { "socks5-80",          PROTO_SOCKS5,           80    },
-       { "socks4-8080",        PROTO_SOCKS4,           8080  },
-       { "socks5-8080",        PROTO_SOCKS5,           8080  },
-       { NULL,                 PROTO_NONE,             0     }
-};
-
+static rb_dlink_list proxy_scanners;
 
 static ACCB accept_opm;
 static PF read_opm_reply;
@@ -84,6 +82,7 @@ static CNCB socks4_connected;
 static CNCB socks5_connected;
 
 static void opm_cancel(struct auth_client *auth);
+static bool create_listener(const char *ip, uint16_t port);
 
 static int opm_timeout = 10;
 static bool opm_enable = false;
@@ -94,20 +93,50 @@ static bool opm_enable = false;
 /* IPv4 and IPv6 */
 static struct opm_listener listeners[2];
 
+static inline protocol_t
+get_protocol_from_string(const char *str)
+{
+       if(strcasecmp(str, "socks4") == 0)
+               return PROTO_SOCKS4;
+       else if(strcasecmp(str, "socks5") == 0)
+               return PROTO_SOCKS5;
+       else if(strcasecmp(str, "httpconnect") == 0)
+               return PROTO_HTTP_CONNECT;
+       else if(strcasecmp(str, "httpsconnect") == 0)
+               return PROTO_HTTPS_CONNECT;
+       else
+               return PROTO_NONE;
+}
+
+static inline struct opm_proxy *
+find_proxy_scanner(protocol_t proto, uint16_t port)
+{
+       rb_dlink_node *ptr;
+
+       RB_DLINK_FOREACH(ptr, proxy_scanners.head)
+       {
+               struct opm_proxy *proxy = ptr->data;
+
+               if(proxy->proto == proto && proxy->port == port)
+                       return proxy;
+       }
 
+       return NULL;
+}
+
+/* This is called when an open proxy connects to us */
 static void
 read_opm_reply(rb_fde_t *F, void *data)
 {
+       rb_dlink_node *ptr;
        struct auth_client *auth = data;
        struct opm_lookup *lookup;
        char readbuf[OPM_READSIZE];
        ssize_t len;
 
-       if(auth == NULL || (lookup = auth->data[PROVIDER_OPM]) == NULL)
-       {
-               rb_close(F);
-               return;
-       }
+       lrb_assert(auth != NULL);
+       lookup = get_provider_data(auth, SELF_PID);
+       lrb_assert(lookup != NULL);
 
        if((len = rb_read(F, readbuf, sizeof(readbuf))) < 0 && rb_ignore_errno(errno))
        {
@@ -121,9 +150,11 @@ read_opm_reply(rb_fde_t *F, void *data)
                return;
        }
 
-       for(struct opm_proxy *proxy = opm_proxy_scans; proxy->note != NULL; proxy++)
+       RB_DLINK_FOREACH(ptr, proxy_scanners.head)
        {
-               if(strncmp(proxy->note, readbuf, sizeof(readbuf)) == 0)
+               struct opm_proxy *proxy = ptr->data;
+
+               if(strncmp(proxy->note, readbuf, strlen(proxy->note)) == 0)
                {
                        rb_dlink_node *ptr, *nptr;
 
@@ -139,7 +170,7 @@ read_opm_reply(rb_fde_t *F, void *data)
                        /* No longer needed, client is going away */
                        rb_free(lookup);
 
-                       reject_client(auth, PROVIDER_OPM, readbuf, "Open proxy detected");
+                       reject_client(auth, SELF_PID, readbuf, "Open proxy detected");
                        break;
                }
        }
@@ -225,12 +256,14 @@ socks4_connected(rb_fde_t *F, int error, void *data)
        uint8_t *c = sendbuf;
        ssize_t ret;
 
-       if(scan == NULL || (auth = scan->auth) == NULL || (lookup = auth->data[PROVIDER_OPM]) == NULL)
-               return;
-
        if(error || !opm_enable)
                goto end;
 
+       lrb_assert(scan != NULL);
+
+       auth = scan->auth;
+       lookup = get_provider_data(auth, SELF_PID);
+
        memcpy(c, "\x04\x01", 2); c += 2; /* Socks version 4, connect command */
 
        switch(GET_SS_FAMILY(&auth->c_addr))
@@ -271,21 +304,23 @@ static void
 socks5_connected(rb_fde_t *F, int error, void *data)
 {
        struct opm_scan *scan = data;
-       struct opm_lookup *lookup; 
+       struct opm_lookup *lookup;
        struct opm_listener *listener;
        struct auth_client *auth;
        uint8_t sendbuf[25]; /* Size we're building */
        uint8_t *c = sendbuf;
        ssize_t ret;
 
-       if(scan == NULL || (auth = scan->auth) == NULL || (lookup = auth->data[PROVIDER_OPM]) == NULL)
-               return;
-
        if(error || !opm_enable)
                goto end;
 
+       lrb_assert(scan != NULL);
+
+       auth = scan->auth;
+       lookup = get_provider_data(auth, SELF_PID);
+
        /* Build the version header and socks request
-        * version header (3 bytes): version, number of auth methods, auth type (0 for none) 
+        * version header (3 bytes): version, number of auth methods, auth type (0 for none)
         * connect req (3 bytes): version, command (1 = connect), reserved (0)
         */
        memcpy(c, "\x05\x01\x00\x05\x01\x00", 6); c += 6;
@@ -330,21 +365,82 @@ end:
        rb_free(scan);
 }
 
+static void
+http_connect_connected(rb_fde_t *F, int error, void *data)
+{
+       struct opm_scan *scan = data;
+       struct opm_lookup *lookup;
+       struct opm_listener *listener;
+       struct auth_client *auth;
+       char sendbuf[128]; /* A bit bigger than we need but better safe than sorry */
+       char *c = sendbuf;
+       ssize_t ret;
+
+       if(error || !opm_enable)
+               goto end;
+
+       lrb_assert(scan != NULL);
+
+       auth = scan->auth;
+       lookup = get_provider_data(auth, SELF_PID);
+
+       switch(GET_SS_FAMILY(&auth->c_addr))
+       {
+       case AF_INET:
+               listener = &listeners[LISTEN_IPV4];
+               if(!listener->F)
+                       goto end;
+               break;
+#ifdef RB_IPV6
+       case AF_INET6:
+               listener = &listeners[LISTEN_IPV6];
+               if(!listener->F)
+                       goto end;
+               break;
+#endif
+       default:
+               goto end;
+       }
+
+       /* Simple enough to build */
+       snprintf(sendbuf, sizeof(sendbuf), "CONNECT %s:%hu HTTP/1.0\r\n\r\n", listener->ip, listener->port);
+
+       /* Send request */
+       if(rb_write(scan->F, sendbuf, strlen(sendbuf)) <= 0)
+               goto end;
+
+       /* Now the note in a separate write */
+       if(rb_write(scan->F, scan->proxy->note, strlen(scan->proxy->note) + 1) <= 0)
+               goto end;
+
+       /* MiroTik needs this, and as a separate write */
+       if(rb_write(scan->F, "\r\n", 2) <= 0)
+               goto end;
+
+end:
+       rb_close(scan->F);
+       rb_dlinkDelete(&scan->node, &lookup->scans);
+       rb_free(scan);
+}
+
 /* Establish connections */
 static inline void
 establish_connection(struct auth_client *auth, struct opm_proxy *proxy)
 {
-       struct opm_lookup *lookup = auth->data[PROVIDER_OPM];
+       struct opm_lookup *lookup = get_provider_data(auth, SELF_PID);
        struct opm_listener *listener;
        struct opm_scan *scan = rb_malloc(sizeof(struct opm_scan));
        struct rb_sockaddr_storage c_a, l_a;
        int opt = 1;
        CNCB *callback;
 
+       lrb_assert(lookup != NULL);
+
        switch(proxy->proto)
        {
        case PROTO_SOCKS4:
 #ifdef RB_IPV6
+               /* SOCKS4 is IPv4 only */
                if(GET_SS_FAMILY(&auth->c_addr) == AF_INET6)
                {
                        rb_free(scan);
@@ -356,6 +452,10 @@ establish_connection(struct auth_client *auth, struct opm_proxy *proxy)
        case PROTO_SOCKS5:
                callback = socks5_connected;
                break;
+       case PROTO_HTTP_CONNECT:
+       case PROTO_HTTPS_CONNECT:
+               callback = http_connect_connected;
+               break;
        default:
                return;
        }
@@ -374,7 +474,7 @@ establish_connection(struct auth_client *auth, struct opm_proxy *proxy)
        scan->proxy = proxy;
        if((scan->F = rb_socket(GET_SS_FAMILY(&auth->c_addr), SOCK_STREAM, 0, proxy->note)) == NULL)
        {
-               warn_opers(L_CRIT, "OPM: could not create OPM socket (proto %s): %s", proxy->note, strerror(errno));
+               warn_opers(L_WARN, "OPM: could not create OPM socket (proto %s): %s", proxy->note, strerror(errno));
                rb_free(scan);
                return;
        }
@@ -386,58 +486,214 @@ establish_connection(struct auth_client *auth, struct opm_proxy *proxy)
        SET_SS_PORT(&c_a, htons(proxy->port));
 
        rb_dlinkAdd(scan, &scan->node, &lookup->scans);
-       rb_connect_tcp(scan->F,
-                       (struct sockaddr *)&c_a,
-                       (struct sockaddr *)&l_a,
-                       GET_SS_LEN(&l_a),
-                       callback, scan, opm_timeout);
+
+       if(!proxy->ssl)
+               rb_connect_tcp(scan->F,
+                               (struct sockaddr *)&c_a,
+                               (struct sockaddr *)&l_a,
+                               GET_SS_LEN(&l_a),
+                               callback, scan, opm_timeout);
+       else
+               rb_connect_tcp_ssl(scan->F,
+                               (struct sockaddr *)&c_a,
+                               (struct sockaddr *)&l_a,
+                               GET_SS_LEN(&l_a),
+                               callback, scan, opm_timeout);
 }
 
-static void
-opm_destroy(void)
+static bool
+create_listener(const char *ip, uint16_t port)
 {
        struct auth_client *auth;
+       struct opm_listener *listener;
+       struct rb_sockaddr_storage addr;
        rb_dictionary_iter iter;
+       rb_fde_t *F;
+       int opt = 1;
 
-       /* Nuke all opm lookups */
+       if(!rb_inet_pton_sock(ip, (struct sockaddr *)&addr))
+       {
+               warn_opers(L_CRIT, "OPM: got a bad listener: %s:%hu", ip, port);
+               exit(EX_PROVIDER_ERROR);
+       }
+
+       SET_SS_PORT(&addr, htons(port));
+
+#ifdef RB_IPV6
+       if(GET_SS_FAMILY(&addr) == AF_INET6)
+       {
+               struct sockaddr_in6 *a1, *a2;
+
+               listener = &listeners[LISTEN_IPV6];
+
+               a1 = (struct sockaddr_in6 *)&addr;
+               a2 = (struct sockaddr_in6 *)&listener->addr;
+
+               if(IN6_ARE_ADDR_EQUAL(&a1->sin6_addr, &a2->sin6_addr) &&
+                       GET_SS_PORT(&addr) == GET_SS_PORT(&listener->addr) &&
+                       listener->F != NULL)
+               {
+                       /* Listener already exists */
+                       return false;
+               }
+       }
+       else
+#endif
+       {
+               struct sockaddr_in *a1, *a2;
+
+               listener = &listeners[LISTEN_IPV4];
+
+               a1 = (struct sockaddr_in *)&addr;
+               a2 = (struct sockaddr_in *)&listener->addr;
+
+               if(a1->sin_addr.s_addr == a2->sin_addr.s_addr &&
+                       GET_SS_PORT(&addr) == GET_SS_PORT(&listener->addr) &&
+                       listener->F != NULL)
+               {
+                       /* Listener already exists */
+                       return false;
+               }
+       }
+
+       if((F = rb_socket(GET_SS_FAMILY(&addr), SOCK_STREAM, 0, "OPM listener socket")) == NULL)
+       {
+               /* This shouldn't fail, or we have big problems... */
+               warn_opers(L_CRIT, "OPM: cannot create socket: %s", strerror(errno));
+               exit(EX_PROVIDER_ERROR);
+       }
+
+       if(setsockopt(rb_get_fd(F), SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)))
+       {
+               /* This shouldn't fail either... */
+               warn_opers(L_CRIT, "OPM: cannot set options on socket: %s", strerror(errno));
+               exit(EX_PROVIDER_ERROR);
+       }
+
+       if(bind(rb_get_fd(F), (struct sockaddr *)&addr, GET_SS_LEN(&addr)))
+       {
+               /* Shit happens, let's not cripple authd over /this/ since it could be user error */
+               warn_opers(L_WARN, "OPM: cannot bind on socket: %s", strerror(errno));
+               rb_close(F);
+               return false;
+       }
+
+       if(rb_listen(F, SOMAXCONN, false)) /* deferred accept could interfere with detection */
+       {
+               /* Again, could be user error */
+               warn_opers(L_WARN, "OPM: cannot listen on socket: %s", strerror(errno));
+               rb_close(F);
+               return false;
+       }
+
+       /* From this point forward we assume we have a listener */
+
+       if(listener->F != NULL)
+               /* Close old listener */
+               rb_close(listener->F);
+
+       listener->F = F;
+
+       /* Cancel clients that may be on old listener
+        * XXX - should rescan clients that need it
+        */
        RB_DICTIONARY_FOREACH(auth, &iter, auth_clients)
        {
                opm_cancel(auth);
        }
+
+       /* Copy data */
+       rb_strlcpy(listener->ip, ip, sizeof(listener->ip));
+       listener->port = port;
+       listener->addr = addr;
+
+       opm_enable = true; /* Implicitly set this to true for now if we have a listener */
+       rb_accept_tcp(listener->F, NULL, accept_opm, listener);
+       return true;
+}
+
+static void
+opm_scan(struct auth_client *auth)
+{
+       rb_dlink_node *ptr;
+       struct opm_lookup *lookup;
+
+       lrb_assert(auth != NULL);
+
+       lookup = get_provider_data(auth, SELF_PID);
+       set_provider_timeout_relative(auth, SELF_PID, opm_timeout);
+
+       lookup->in_progress = true;
+
+       RB_DLINK_FOREACH(ptr, proxy_scanners.head)
+       {
+               struct opm_proxy *proxy = ptr->data;
+               establish_connection(auth, proxy);
+       }
+
+       notice_client(auth->cid, "*** Scanning for open proxies...");
+}
+
+/* This is called every time a provider is completed as long as we are marked not done */
+static void
+opm_initiate(struct auth_client *auth, uint32_t provider)
+{
+       struct opm_lookup *lookup = get_provider_data(auth, SELF_PID);
+       uint32_t rdns_pid, ident_pid;
+
+       lrb_assert(provider != SELF_PID);
+       lrb_assert(!is_provider_done(auth, SELF_PID));
+       lrb_assert(rb_dlink_list_length(&proxy_scanners) > 0);
+
+       if(lookup == NULL || lookup->in_progress)
+               /* Nothing to do */
+               return;
+       else if((!get_provider_id("rdns", &rdns_pid) || is_provider_done(auth, rdns_pid)) &&
+               (!get_provider_id("ident", &ident_pid) || is_provider_done(auth, ident_pid)))
+               /* Don't start until ident and rdns are finished (or not loaded) */
+               return;
+       else
+               opm_scan(auth);
 }
 
 static bool
 opm_start(struct auth_client *auth)
 {
-       struct opm_lookup *lookup = rb_malloc(sizeof(struct opm_lookup));
+       uint32_t rdns_pid, ident_pid;
+
+       lrb_assert(get_provider_data(auth, SELF_PID) == NULL);
 
-       if(!opm_enable)
+       if(!opm_enable || rb_dlink_list_length(&proxy_scanners) == 0)
        {
+               /* Nothing to do... */
                notice_client(auth->cid, "*** Proxy scanning disabled, not scanning");
                return true;
        }
 
-       auth->data[PROVIDER_OPM] = lookup = rb_malloc(sizeof(struct opm_lookup));
-       auth->timeout[PROVIDER_OPM] = rb_current_time() + opm_timeout;
+       set_provider_data(auth, SELF_PID, rb_malloc(sizeof(struct opm_lookup)));
 
-       for(struct opm_proxy *proxy = opm_proxy_scans; proxy->note != NULL; proxy++)
-               establish_connection(auth, proxy);
-
-       notice_client(auth->cid, "*** Scanning for open proxies...");
-       set_provider_on(auth, PROVIDER_OPM);
+       if((!get_provider_id("rdns", &rdns_pid) || is_provider_done(auth, rdns_pid)) &&
+               (!get_provider_id("ident", &ident_pid) || is_provider_done(auth, ident_pid)))
+       {
+               /* Don't start until ident and rdns are finished (or not loaded) */
+               opm_scan(auth);
+       }
 
+       set_provider_running(auth, SELF_PID);
        return true;
 }
 
 static void
 opm_cancel(struct auth_client *auth)
 {
-       struct opm_lookup *lookup = auth->data[PROVIDER_OPM];
+       struct opm_lookup *lookup = get_provider_data(auth, SELF_PID);
 
        if(lookup != NULL)
        {
                rb_dlink_node *ptr, *nptr;
 
+               notice_client(auth->cid, "*** Did not detect open proxies");
+
                RB_DLINK_FOREACH_SAFE(ptr, nptr, lookup->scans.head)
                {
                        struct opm_scan *scan = ptr->data;
@@ -447,10 +703,27 @@ opm_cancel(struct auth_client *auth)
                }
 
                rb_free(lookup);
-               provider_done(auth, PROVIDER_OPM);
+
+               set_provider_data(auth, SELF_PID, NULL);
+               set_provider_timeout_absolute(auth, SELF_PID, 0);
+               provider_done(auth, SELF_PID);
+       }
+}
+
+static void
+opm_destroy(void)
+{
+       struct auth_client *auth;
+       rb_dictionary_iter iter;
+
+       /* Nuke all opm lookups */
+       RB_DICTIONARY_FOREACH(auth, &iter, auth_clients)
+       {
+               opm_cancel(auth);
        }
 }
 
+
 static void
 add_conf_opm_timeout(const char *key __unused, int parc __unused, const char **parv)
 {
@@ -468,116 +741,213 @@ add_conf_opm_timeout(const char *key __unused, int parc __unused, const char **p
 static void
 set_opm_enabled(const char *key __unused, int parc __unused, const char **parv)
 {
-       if(listeners[LISTEN_IPV4].F != NULL || listeners[LISTEN_IPV6].F != NULL)
-               opm_enable = (*parv[0] == '1');
+       bool enable = (*parv[0] == '1');
+
+       if(!enable)
+       {
+               if(listeners[LISTEN_IPV4].F != NULL || listeners[LISTEN_IPV6].F != NULL)
+               {
+                       struct auth_client *auth;
+                       rb_dictionary_iter iter;
+
+                       /* Close the listening socket */
+                       if(listeners[LISTEN_IPV4].F != NULL)
+                               rb_close(listeners[LISTEN_IPV4].F);
+
+                       if(listeners[LISTEN_IPV6].F != NULL)
+                               rb_close(listeners[LISTEN_IPV6].F);
+
+                       listeners[LISTEN_IPV4].F = listeners[LISTEN_IPV6].F = NULL;
+
+                       RB_DICTIONARY_FOREACH(auth, &iter, auth_clients)
+                       {
+                               opm_cancel(auth);
+                       }
+               }
+       }
        else
-               /* No listener, no point... */
-               opm_enable = false;
+       {
+               if(listeners[LISTEN_IPV4].ip[0] != '\0' && listeners[LISTEN_IPV4].port != 0)
+               {
+                       if(listeners[LISTEN_IPV4].F == NULL)
+                               /* Pre-configured IP/port, just re-establish */
+                               create_listener(listeners[LISTEN_IPV4].ip, listeners[LISTEN_IPV4].port);
+               }
+
+               if(listeners[LISTEN_IPV6].ip[0] != '\0' && listeners[LISTEN_IPV6].port != 0)
+               {
+                       if(listeners[LISTEN_IPV6].F == NULL)
+                               /* Pre-configured IP/port, just re-establish */
+                               create_listener(listeners[LISTEN_IPV6].ip, listeners[LISTEN_IPV6].port);
+               }
+       }
+
+       opm_enable = enable;
 }
 
 static void
 set_opm_listener(const char *key __unused, int parc __unused, const char **parv)
 {
-       struct auth_client *auth;
-       struct rb_sockaddr_storage addr;
-       struct opm_listener *listener;
-       int port = atoi(parv[1]), opt = 1;
-       rb_dictionary_iter iter;
-       rb_fde_t *F;
-       size_t i;
+       const char *ip = parv[0];
+       int iport = atoi(parv[1]);
 
-       if(port > 65535 || port <= 0 || !rb_inet_pton_sock(parv[0], (struct sockaddr *)&addr))
+       if(iport > 65535 || iport <= 0)
        {
                warn_opers(L_CRIT, "OPM: got a bad listener: %s:%s", parv[0], parv[1]);
                exit(EX_PROVIDER_ERROR);
        }
 
-#ifdef RB_IPV6
-       if(GET_SS_FAMILY(&addr) == AF_INET6)
-               listener = &listeners[LISTEN_IPV6];
-       else
-#endif
-               listener = &listeners[LISTEN_IPV4];
+       create_listener(ip, (uint16_t)iport);
+}
 
-       if(strcmp(listener->ip, parv[0]) == 0 || listener->port == port)
-               return;
+static void
+create_opm_scanner(const char *key __unused, int parc __unused, const char **parv)
+{
+       int iport = atoi(parv[1]);
+       struct opm_proxy *proxy = rb_malloc(sizeof(struct opm_proxy));
 
-       if((F = rb_socket(GET_SS_FAMILY(&addr), SOCK_STREAM, 0, "OPM listener socket")) == NULL)
+       if(iport <= 0 || iport > 65535)
        {
-               /* This shouldn't fail, or we have problems... */
-               warn_opers(L_CRIT, "OPM: cannot create socket: %s", strerror(errno));
+               warn_opers(L_CRIT, "OPM: got a bad scanner: %s (port %s)", parv[0], parv[1]);
                exit(EX_PROVIDER_ERROR);
        }
 
-       if(setsockopt(rb_get_fd(F), SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)))
+       proxy->port = (uint16_t)iport;
+
+       switch((proxy->proto = get_protocol_from_string(parv[0])))
        {
-               /* This shouldn't fail either... */
-               warn_opers(L_CRIT, "OPM: cannot set options on socket: %s", strerror(errno));
+       case PROTO_SOCKS4:
+               snprintf(proxy->note, sizeof(proxy->note), "socks4:%hu", proxy->port);
+               proxy->ssl = false;
+               break;
+       case PROTO_SOCKS5:
+               snprintf(proxy->note, sizeof(proxy->note), "socks5:%hu", proxy->port);
+               proxy->ssl = false;
+               break;
+       case PROTO_HTTP_CONNECT:
+               snprintf(proxy->note, sizeof(proxy->note), "httpconnect:%hu", proxy->port);
+               proxy->ssl = false;
+               break;
+       case PROTO_HTTPS_CONNECT:
+               snprintf(proxy->note, sizeof(proxy->note), "httpsconnect:%hu", proxy->port);
+               proxy->ssl = true;
+               break;
+       default:
+               warn_opers(L_CRIT, "OPM: got an unknown proxy type: %s (port %hu)", parv[0], proxy->port);
                exit(EX_PROVIDER_ERROR);
        }
 
-       /* Set up ports for binding */
-#ifdef RB_IPV6
-       if(GET_SS_FAMILY(&addr) == AF_INET6)
-               ((struct sockaddr_in6 *)&addr)->sin6_port = htons(port);
-       else
-#endif
-               ((struct sockaddr_in *)&addr)->sin_port = htons(port);
+       if(find_proxy_scanner(proxy->proto, proxy->port) != NULL)
+       {
+               warn_opers(L_CRIT, "OPM: got a duplicate scanner: %s (port %hu)", parv[0], proxy->port);
+               exit(EX_PROVIDER_ERROR);
+       }
 
-       if(bind(rb_get_fd(F), (struct sockaddr *)&addr, GET_SS_LEN(&addr)))
+       rb_dlinkAdd(proxy, &proxy->node, &proxy_scanners);
+}
+
+static void
+delete_opm_scanner(const char *key __unused, int parc __unused, const char **parv)
+{
+       struct auth_client *auth;
+       struct opm_proxy *proxy;
+       protocol_t proto = get_protocol_from_string(parv[0]);
+       int iport = atoi(parv[1]);
+       rb_dictionary_iter iter;
+
+       if(iport <= 0 || iport > 65535)
        {
-               /* Shit happens, let's not cripple authd over this */
-               warn_opers(L_WARN, "OPM: cannot bind on socket: %s", strerror(errno));
-               rb_close(F);
-               return;
+               warn_opers(L_CRIT, "OPM: got a bad scanner to delete: %s (port %s)", parv[0], parv[1]);
+               exit(EX_PROVIDER_ERROR);
        }
 
-       if(rb_listen(F, SOMAXCONN, false)) /* deferred accept could interfere with detection */
+       if(proto == PROTO_NONE)
        {
-               warn_opers(L_WARN, "OPM: cannot listen on socket: %s", strerror(errno));
-               rb_close(F);
-               return;
+               warn_opers(L_CRIT, "OPM: got an unknown proxy type to delete: %s (port %d)", parv[0], iport);
+               exit(EX_PROVIDER_ERROR);
        }
 
-       /* From this point forward we assume we have a listener */
+       if((proxy = find_proxy_scanner(proto, (uint16_t)iport)) == NULL)
+       {
+               warn_opers(L_CRIT, "OPM: cannot find proxy to delete: %s (port %d)", parv[0], iport);
+               exit(EX_PROVIDER_ERROR);
+       }
 
-       if(listener->F != NULL)
-               /* Close old listener */
-               rb_close(listener->F);
+       /* Abort remaining clients on this scanner */
+       RB_DICTIONARY_FOREACH(auth, &iter, auth_clients)
+       {
+               rb_dlink_node *ptr;
+               struct opm_lookup *lookup = get_provider_data(auth, SELF_PID);
 
-       listener->F = F;
+               if(lookup == NULL)
+                       continue;
+
+               RB_DLINK_FOREACH(ptr, lookup->scans.head)
+               {
+                       struct opm_scan *scan = ptr->data;
+
+                       if(scan->proxy->port == proxy->port && scan->proxy->proto == proxy->proto)
+                       {
+                               /* Match */
+                               rb_dlinkDelete(&scan->node, &lookup->scans);
+                               rb_free(scan);
+
+                               if(rb_dlink_list_length(&lookup->scans) == 0)
+                                       opm_cancel(auth);
+
+                               break;
+                       }
+               }
+       }
+
+       rb_dlinkDelete(&proxy->node, &proxy_scanners);
+       rb_free(proxy);
+
+       if(rb_dlink_list_length(&proxy_scanners) == 0)
+               opm_enable = false;
+}
+
+static void
+delete_opm_scanner_all(const char *key __unused, int parc __unused, const char **parv __unused)
+{
+       struct auth_client *auth;
+       rb_dlink_node *ptr, *nptr;
+       rb_dictionary_iter iter;
+
+       RB_DLINK_FOREACH_SAFE(ptr, nptr, proxy_scanners.head)
+       {
+               rb_free(ptr->data);
+               rb_dlinkDelete(ptr, &proxy_scanners);
+       }
 
-       /* Cancel old clients that may be on old stale listener
-        * XXX - should rescan clients that need it
-        */
        RB_DICTIONARY_FOREACH(auth, &iter, auth_clients)
        {
                opm_cancel(auth);
        }
 
-       /* Copy data */
-       rb_strlcpy(listener->ip, parv[0], sizeof(listener->ip));
-       listener->port = (uint16_t)port;
-       listener->addr = addr;
-
-       opm_enable = true; /* Implicitly set this to true for now if we have a listener */
-       rb_accept_tcp(listener->F, NULL, accept_opm, listener);
+       opm_enable = false;
 }
 
+
 struct auth_opts_handler opm_options[] =
 {
        { "opm_timeout", 1, add_conf_opm_timeout },
        { "opm_enabled", 1, set_opm_enabled },
        { "opm_listener", 2, set_opm_listener },
+       { "opm_scanner", 2, create_opm_scanner },
+       { "opm_scanner_del", 2, delete_opm_scanner },
+       { "opm_scanner_del_all", 0, delete_opm_scanner_all },
        { NULL, 0, NULL },
 };
 
 struct auth_provider opm_provider =
 {
-       .id = PROVIDER_OPM,
+       .name = "opm",
+       .letter = 'O',
        .destroy = opm_destroy,
        .start = opm_start,
        .cancel = opm_cancel,
        .timeout = opm_cancel,
+       .completed = opm_initiate,
        .opt_handlers = opm_options,
 };