X-Git-Url: https://jfr.im/git/solanum.git/blobdiff_plain/1d657e0b0893f9cb43ec88e27ac1a9b50f20f178..fbc97166a6e455f5cccf173abca3b9baedee4066:/ircd/authproc.c diff --git a/ircd/authproc.c b/ircd/authproc.c index 4f1a5496..79fa4cb7 100644 --- a/ircd/authproc.c +++ b/ircd/authproc.c @@ -4,7 +4,7 @@ * * Copyright (C) 2005 Aaron Sethman * Copyright (C) 2005-2012 ircd-ratbox development team - * Copyright (C) 2016 William Pitcock + * Copyright (C) 2016 Ariadne Conill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -67,7 +67,10 @@ uint32_t cid; static rb_dictionary *cid_clients; static struct ev_entry *timeout_ev; -rb_dictionary *bl_stats; +rb_dictionary *dnsbl_stats; + +rb_dlink_list opm_list; +struct OPMListener opm_listeners[LISTEN_LAST]; static struct authd_cb authd_cmd_tab[256] = { @@ -85,24 +88,19 @@ static int start_authd(void) { char fullpath[PATH_MAX + 1]; -#ifdef _WIN32 - const char *suffix = ".exe"; -#else - const char *suffix = ""; -#endif + if(authd_path == NULL) { - snprintf(fullpath, sizeof(fullpath), "%s%cauthd%s", ircd_paths[IRCD_PATH_LIBEXEC], RB_PATH_SEPARATOR, suffix); + snprintf(fullpath, sizeof(fullpath), "%s/authd", ircd_paths[IRCD_PATH_LIBEXEC]); if(access(fullpath, X_OK) == -1) { - snprintf(fullpath, sizeof(fullpath), "%s%cbin%cauthd%s", - ConfigFileEntry.dpath, RB_PATH_SEPARATOR, RB_PATH_SEPARATOR, suffix); + snprintf(fullpath, sizeof(fullpath), "%s/bin/authd", ConfigFileEntry.dpath); if(access(fullpath, X_OK) == -1) { ierror("Unable to execute authd in %s or %s/bin", ircd_paths[IRCD_PATH_LIBEXEC], ConfigFileEntry.dpath); - sendto_realops_snomask(SNO_GENERAL, L_ALL, + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "Unable to execute authd in %s or %s/bin", ircd_paths[IRCD_PATH_LIBEXEC], ConfigFileEntry.dpath); return 1; @@ -116,9 +114,6 @@ start_authd(void) if(cid_clients == NULL) cid_clients = rb_dictionary_create("authd cid to uid mapping", rb_uint32cmp); - if(bl_stats == NULL) - bl_stats = rb_dictionary_create("blacklist statistics", strcasecmp); - if(timeout_ev == NULL) timeout_ev = rb_event_addish("timeout_dead_authd_clients", timeout_dead_authd_clients, NULL, 1); @@ -127,11 +122,12 @@ start_authd(void) if(authd_helper == NULL) { ierror("Unable to start authd helper: %s", strerror(errno)); - sendto_realops_snomask(SNO_GENERAL, L_ALL, "Unable to start authd helper: %s", strerror(errno)); + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "Unable to start authd helper: %s", strerror(errno)); return 1; } + ilog(L_MAIN, "authd helper started"); - sendto_realops_snomask(SNO_GENERAL, L_ALL, "authd helper started"); + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "authd helper started"); rb_helper_run(authd_helper); return 0; } @@ -152,14 +148,14 @@ str_to_cid(const char *str) } static inline struct Client * -cid_to_client(uint32_t cid, bool delete) +cid_to_client(uint32_t ncid, bool del) { struct Client *client_p; - if(delete) - client_p = rb_dictionary_delete(cid_clients, RB_UINT_TO_POINTER(cid)); + if(del) + client_p = rb_dictionary_delete(cid_clients, RB_UINT_TO_POINTER(ncid)); else - client_p = rb_dictionary_retrieve(cid_clients, RB_UINT_TO_POINTER(cid)); + client_p = rb_dictionary_retrieve(cid_clients, RB_UINT_TO_POINTER(ncid)); /* If the client's not found, that's okay, it may have already gone away. * --Elizafox */ @@ -168,14 +164,14 @@ cid_to_client(uint32_t cid, bool delete) } static inline struct Client * -str_cid_to_client(const char *str, bool delete) +str_cid_to_client(const char *str, bool del) { - uint32_t cid = str_to_cid(str); + uint32_t ncid = str_to_cid(str); - if(cid == 0) + if(ncid == 0) return NULL; - return cid_to_client(cid, delete); + return cid_to_client(ncid, del); } static void @@ -201,7 +197,10 @@ cmd_notice_client(int parc, char **parv) { struct Client *client_p; - if((client_p = str_cid_to_client(parv[1], false)) == NULL) + if ((client_p = str_cid_to_client(parv[1], false)) == NULL) + return; + + if (IsAnyDead(client_p)) return; sendto_one_notice(client_p, ":%s", parv[2]); @@ -222,27 +221,27 @@ cmd_reject_client(int parc, char **parv) static void cmd_oper_warn(int parc, char **parv) { - switch(*parv[2]) + switch(*parv[1]) { case 'D': /* Debug */ - sendto_realops_snomask(SNO_DEBUG, L_ALL, "authd debug: %s", parv[3]); - idebug("authd: %s", parv[3]); + sendto_realops_snomask(SNO_DEBUG, L_NETWIDE, "authd debug: %s", parv[2]); + idebug("authd: %s", parv[2]); break; case 'I': /* Info */ - sendto_realops_snomask(SNO_GENERAL, L_ALL, "authd info: %s", parv[3]); - inotice("authd: %s", parv[3]); + sendto_realops_snomask(SNO_DEBUG, L_NETWIDE, "authd info: %s", parv[2]); + inotice("authd: %s", parv[2]); break; case 'W': /* Warning */ - sendto_realops_snomask(SNO_GENERAL, L_ALL, "authd WARNING: %s", parv[3]); - iwarn("authd: %s", parv[3]); + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "authd WARNING: %s", parv[2]); + iwarn("authd: %s", parv[2]); break; case 'C': /* Critical (error) */ - sendto_realops_snomask(SNO_GENERAL, L_ALL, "authd CRITICAL: %s", parv[3]); - ierror("authd: %s", parv[3]); + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "authd CRITICAL: %s", parv[2]); + ierror("authd: %s", parv[2]); break; default: /* idk */ - sendto_realops_snomask(SNO_GENERAL, L_ALL, "authd sent us an unknown oper notice type (%s): %s", parv[2], parv[3]); - ilog(L_MAIN, "authd unknown oper notice type (%s): %s", parv[2], parv[3]); + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "authd sent us an unknown oper notice type (%s): %s", parv[1], parv[2]); + ilog(L_MAIN, "authd unknown oper notice type (%s): %s", parv[1], parv[2]); break; } } @@ -274,14 +273,14 @@ parse_authd_reply(rb_helper * helper) ssize_t len; int parc; char buf[READBUF_SIZE]; - char *parv[MAXPARA + 1]; + char *parv[MAXPARA]; while((len = rb_helper_read(helper, buf, sizeof(buf))) > 0) { struct authd_cb *cmd; - parc = rb_string_to_array(buf, parv, MAXPARA+1); - cmd = &authd_cmd_tab[*parv[0]]; + parc = rb_string_to_array(buf, parv, sizeof(parv)); + cmd = &authd_cmd_tab[(unsigned char)*parv[0]]; if(cmd->fn != NULL) { if(cmd->min_parc > parc) @@ -316,21 +315,85 @@ init_authd(void) void configure_authd(void) { - /* These will do for now */ + /* Timeouts */ set_authd_timeout("ident_timeout", GlobalSetOptions.ident_timeout); set_authd_timeout("rdns_timeout", ConfigFileEntry.connect_timeout); set_authd_timeout("rbl_timeout", ConfigFileEntry.connect_timeout); + ident_check_enable(!ConfigFileEntry.disable_auth); + + /* Configure OPM */ + if(rb_dlink_list_length(&opm_list) > 0 && + (opm_listeners[LISTEN_IPV4].ipaddr[0] != '\0' || + opm_listeners[LISTEN_IPV6].ipaddr[0] != '\0')) + { + rb_dlink_node *ptr; + + if(opm_listeners[LISTEN_IPV4].ipaddr[0] != '\0') + rb_helper_write(authd_helper, "O opm_listener %s %hu", + opm_listeners[LISTEN_IPV4].ipaddr, opm_listeners[LISTEN_IPV4].port); + + if(opm_listeners[LISTEN_IPV6].ipaddr[0] != '\0') + rb_helper_write(authd_helper, "O opm_listener %s %hu", + opm_listeners[LISTEN_IPV6].ipaddr, opm_listeners[LISTEN_IPV6].port); + + RB_DLINK_FOREACH(ptr, opm_list.head) + { + struct OPMScanner *scanner = ptr->data; + rb_helper_write(authd_helper, "O opm_scanner %s %hu", + scanner->type, scanner->port); + } + + opm_check_enable(true); + } + else + opm_check_enable(false); + + /* Configure DNSBLs */ + rb_dictionary_iter iter; + struct DNSBLEntry *entry; + RB_DICTIONARY_FOREACH(entry, &iter, dnsbl_stats) + { + rb_helper_write(authd_helper, "O rbl %s %hhu %s :%s", entry->host, + entry->iptype, entry->filters, entry->reason); + } } static void -restart_authd_cb(rb_helper * helper) +authd_free_client(struct Client *client_p) { - rb_dictionary_iter iter; - struct Client *client_p; + if(client_p == NULL || client_p->preClient == NULL) + return; + + if(client_p->preClient->auth.cid == 0) + return; + + if(authd_helper != NULL) + rb_helper_write(authd_helper, "E %x", client_p->preClient->auth.cid); + + client_p->preClient->auth.accepted = true; + client_p->preClient->auth.cid = 0; +} + +static void +authd_free_client_cb(rb_dictionary_element *delem, void *unused) +{ + struct Client *client_p = delem->data; + authd_free_client(client_p); +} - iwarn("authd: restart_authd_cb called, authd died?"); - sendto_realops_snomask(SNO_GENERAL, L_ALL, "authd: restart_authd_cb called, authd died?"); +void +authd_abort_client(struct Client *client_p) +{ + rb_dictionary_delete(cid_clients, RB_UINT_TO_POINTER(client_p->preClient->auth.cid)); + authd_free_client(client_p); +} + +static void +restart_authd_cb(rb_helper * helper) +{ + iwarn("authd helper died - attempting to restart"); + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "authdd helper died - attempting to restart"); if(helper != NULL) { @@ -338,19 +401,17 @@ restart_authd_cb(rb_helper * helper) authd_helper = NULL; } - RB_DICTIONARY_FOREACH(client_p, &iter, cid_clients) - { - /* Abort any existing clients */ - authd_abort_client(client_p); - } + rb_dictionary_destroy(cid_clients, authd_free_client_cb, NULL); + cid_clients = NULL; start_authd(); - rehash(false); /* FIXME - needed to reload authd configuration */ + configure_authd(); } void restart_authd(void) { + ierror("authd restarting..."); restart_authd_cb(authd_helper); } @@ -358,7 +419,6 @@ void rehash_authd(void) { rb_helper_write(authd_helper, "R"); - configure_authd(); } void @@ -384,19 +444,25 @@ generate_cid(void) * gonna accept the client and ignore authd's suggestion. * * --Elizafox + * + * If this is an SSL connection we must defer handing off the client for + * reading until it is open and we have the certificate fingerprint, otherwise + * it's possible for the client to immediately send data before authd completes + * and before the status of the connection is communicated via ssld. This data + * could then be processed too early by read_packet(). */ void -authd_initiate_client(struct Client *client_p) +authd_initiate_client(struct Client *client_p, bool defer) { char client_ipaddr[HOSTIPLEN+1]; char listen_ipaddr[HOSTIPLEN+1]; uint16_t client_port, listen_port; uint32_t authd_cid; - if(client_p->preClient == NULL || client_p->preClient->authd_cid != 0) + if(client_p->preClient == NULL || client_p->preClient->auth.cid != 0) return; - authd_cid = client_p->preClient->authd_cid = generate_cid(); + authd_cid = client_p->preClient->auth.cid = generate_cid(); /* Collisions are extremely unlikely, so disregard the possibility */ rb_dictionary_add(cid_clients, RB_UINT_TO_POINTER(authd_cid), client_p); @@ -409,25 +475,51 @@ authd_initiate_client(struct Client *client_p) listen_port = ntohs(GET_SS_PORT(&client_p->preClient->lip)); client_port = ntohs(GET_SS_PORT(&client_p->localClient->ip)); + if(defer) + client_p->preClient->auth.flags |= AUTHC_F_DEFERRED; + /* Add a bit of a fudge factor... */ - client_p->preClient->authd_timeout = rb_current_time() + ConfigFileEntry.connect_timeout + 10; + client_p->preClient->auth.timeout = rb_current_time() + ConfigFileEntry.connect_timeout + 10; - rb_helper_write(authd_helper, "C %x %s %hu %s %hu", authd_cid, listen_ipaddr, listen_port, client_ipaddr, client_port); + rb_helper_write(authd_helper, "C %x %s %hu %s %hu %x", authd_cid, listen_ipaddr, listen_port, client_ipaddr, client_port, +#ifdef HAVE_LIBSCTP + IsSCTP(client_p) ? IPPROTO_SCTP : IPPROTO_TCP); +#else + IPPROTO_TCP); +#endif +} + +static inline void +authd_read_client(struct Client *client_p) +{ + /* + * When a client has auth'ed, we want to start reading what it sends + * us. This is what read_packet() does. + * -- adrian + * + * Above comment was originally in s_auth.c, but moved here with below code. + * --Elizafox + */ + rb_dlinkAddTail(client_p, &client_p->node, &global_client_list); + read_packet(client_p->localClient->F, client_p); } /* When this is called we have a decision on client acceptance. * - * After this point authd no longer "owns" the client. + * After this point authd no longer "owns" the client, but if + * it's flagged as deferred then we're still waiting for a call + * to authd_deferred_client(). */ static inline void authd_decide_client(struct Client *client_p, const char *ident, const char *host, bool accept, char cause, const char *data, const char *reason) { - if(client_p->preClient == NULL || client_p->preClient->authd_cid == 0) + if(client_p->preClient == NULL || client_p->preClient->auth.cid == 0) return; if(*ident != '*') { rb_strlcpy(client_p->username, ident, sizeof(client_p->username)); + SetGotId(client_p); ServerStats.is_asuc++; } else @@ -436,24 +528,25 @@ authd_decide_client(struct Client *client_p, const char *ident, const char *host if(*host != '*') rb_strlcpy(client_p->host, host, sizeof(client_p->host)); - rb_dictionary_delete(cid_clients, RB_UINT_TO_POINTER(client_p->preClient->authd_cid)); + rb_dictionary_delete(cid_clients, RB_UINT_TO_POINTER(client_p->preClient->auth.cid)); - client_p->preClient->authd_accepted = accept; - client_p->preClient->authd_cause = cause; - client_p->preClient->authd_data = (data == NULL ? NULL : rb_strdup(data)); - client_p->preClient->authd_reason = (reason == NULL ? NULL : rb_strdup(reason)); - client_p->preClient->authd_cid = 0; + client_p->preClient->auth.accepted = accept; + client_p->preClient->auth.cause = cause; + client_p->preClient->auth.data = (data == NULL ? NULL : rb_strdup(data)); + client_p->preClient->auth.reason = (reason == NULL ? NULL : rb_strdup(reason)); + client_p->preClient->auth.cid = 0; - /* - * When a client has auth'ed, we want to start reading what it sends - * us. This is what read_packet() does. - * -- adrian - * - * Above comment was originally in s_auth.c, but moved here with below code. - * --Elizafox - */ - rb_dlinkAddTail(client_p, &client_p->node, &global_client_list); - read_packet(client_p->localClient->F, client_p); + client_p->preClient->auth.flags |= AUTHC_F_COMPLETE; + if((client_p->preClient->auth.flags & AUTHC_F_DEFERRED) == 0) + authd_read_client(client_p); +} + +void +authd_deferred_client(struct Client *client_p) +{ + client_p->preClient->auth.flags &= ~AUTHC_F_DEFERRED; + if(client_p->preClient->auth.flags & AUTHC_F_COMPLETE) + authd_read_client(client_p); } /* Convenience function to accept client */ @@ -470,46 +563,43 @@ authd_reject_client(struct Client *client_p, const char *ident, const char *host authd_decide_client(client_p, ident, host, false, cause, data, reason); } -void -authd_abort_client(struct Client *client_p) -{ - if(client_p == NULL || client_p->preClient == NULL) - return; - - if(client_p->preClient->authd_cid == 0) - return; - - rb_dictionary_delete(cid_clients, RB_UINT_TO_POINTER(client_p->preClient->authd_cid)); - - if(authd_helper != NULL) - rb_helper_write(authd_helper, "E %x", client_p->preClient->authd_cid); - - client_p->preClient->authd_accepted = true; - client_p->preClient->authd_cid = 0; -} - static void timeout_dead_authd_clients(void *notused __unused) { rb_dictionary_iter iter; struct Client *client_p; + rb_dlink_list freelist = { NULL, NULL, 0 }; + rb_dlink_node *ptr, *nptr; RB_DICTIONARY_FOREACH(client_p, &iter, cid_clients) { - if(client_p->preClient->authd_timeout < rb_current_time()) - authd_abort_client(client_p); + if(client_p->preClient->auth.timeout < rb_current_time()) + { + rb_dlinkAddAlloc(client_p, &freelist); + } + } + + /* RB_DICTIONARY_FOREACH is not safe for deletion, so we do this crap */ + RB_DLINK_FOREACH_SAFE(ptr, nptr, freelist.head) + { + client_p = ptr->data; + authd_abort_client(client_p); + rb_dlinkDestroy(ptr, &freelist); } } -/* Send a new blacklist to authd */ +/* Send a new DNSBL entry to authd */ void -add_blacklist(const char *host, const char *reason, uint8_t iptype, rb_dlink_list *filters) +add_dnsbl_entry(const char *host, const char *reason, uint8_t iptype, rb_dlink_list *filters) { rb_dlink_node *ptr; - struct blacklist_stats *stats = rb_malloc(sizeof(struct blacklist_stats)); + struct DNSBLEntry *entry = rb_malloc(sizeof(*entry)); char filterbuf[BUFSIZE] = "*"; size_t s = 0; + if(dnsbl_stats == NULL) + dnsbl_stats = rb_dictionary_create("dnsbl statistics", rb_strcasecmp); + /* Build a list of comma-separated values for authd. * We don't check for validity - do it elsewhere. */ @@ -529,34 +619,52 @@ add_blacklist(const char *host, const char *reason, uint8_t iptype, rb_dlink_lis if(s) filterbuf[s - 1] = '\0'; - stats->iptype = iptype; - stats->hits = 0; - rb_dictionary_add(bl_stats, host, stats); + entry->host = rb_strdup(host); + entry->reason = rb_strdup(reason); + entry->filters = rb_strdup(filterbuf); + entry->iptype = iptype; + entry->hits = 0; + rb_dictionary_add(dnsbl_stats, entry->host, entry); rb_helper_write(authd_helper, "O rbl %s %hhu %s :%s", host, iptype, filterbuf, reason); } -/* Delete a blacklist */ +/* Delete a DNSBL entry. */ void -del_blacklist(const char *host) +del_dnsbl_entry(const char *host) { - rb_dictionary_delete(bl_stats, host); + struct DNSBLEntry *entry = rb_dictionary_retrieve(dnsbl_stats, host); + + if(entry != NULL) + { + rb_dictionary_delete(dnsbl_stats, entry->host); + rb_free(entry->host); + rb_free(entry->reason); + rb_free(entry->filters); + rb_free(entry); + } rb_helper_write(authd_helper, "O rbl_del %s", host); } -/* Delete all the blacklists */ -void -del_blacklist_all(void) +static void +dnsbl_delete_elem(rb_dictionary_element *delem, void *unused) { - struct blacklist_stats *stats; - rb_dictionary_iter iter; + struct DNSBLEntry *entry = delem->data; - RB_DICTIONARY_FOREACH(stats, &iter, bl_stats) - { - rb_free(stats); - rb_dictionary_delete(bl_stats, iter.cur->key); - } + rb_free(entry->host); + rb_free(entry->reason); + rb_free(entry->filters); + rb_free(entry); +} + +/* Delete all the DNSBL entries. */ +void +del_dnsbl_entry_all(void) +{ + if(dnsbl_stats != NULL) + rb_dictionary_destroy(dnsbl_stats, dnsbl_delete_elem, NULL); + dnsbl_stats = NULL; rb_helper_write(authd_helper, "O rbl_del_all"); } @@ -579,11 +687,35 @@ ident_check_enable(bool enabled) rb_helper_write(authd_helper, "O ident_enabled %d", enabled ? 1 : 0); } -/* Create an OPM listener */ +/* Create an OPM listener + * XXX - This is a big nasty hack, but it avoids resending duplicate data when + * configure_authd() is called. + */ +void +conf_create_opm_listener(const char *ip, uint16_t port) +{ + char ipbuf[HOSTIPLEN]; + struct OPMListener *listener; + + rb_strlcpy(ipbuf, ip, sizeof(ipbuf)); + if(ipbuf[0] == ':') + { + memmove(ipbuf + 1, ipbuf, sizeof(ipbuf) - 1); + ipbuf[0] = '0'; + } + + /* I am much too lazy to use rb_inet_pton and GET_SS_FAMILY for now --Elizafox */ + listener = &opm_listeners[(strchr(ipbuf, ':') != NULL ? LISTEN_IPV6 : LISTEN_IPV4)]; + rb_strlcpy(listener->ipaddr, ipbuf, sizeof(listener->ipaddr)); + listener->port = port; +} + void create_opm_listener(const char *ip, uint16_t port) { char ipbuf[HOSTIPLEN]; + + /* XXX duplicated in conf_create_opm_listener */ rb_strlcpy(ipbuf, ip, sizeof(ipbuf)); if(ipbuf[0] == ':') { @@ -591,9 +723,17 @@ create_opm_listener(const char *ip, uint16_t port) ipbuf[0] = '0'; } + conf_create_opm_listener(ip, port); rb_helper_write(authd_helper, "O opm_listener %s %hu", ipbuf, port); } +void +delete_opm_listener_all(void) +{ + memset(&opm_listeners, 0, sizeof(opm_listeners)); + rb_helper_write(authd_helper, "O opm_listener_del_all"); +} + /* Disable all OPM scans */ void opm_check_enable(bool enabled) @@ -601,21 +741,60 @@ opm_check_enable(bool enabled) rb_helper_write(authd_helper, "O opm_enabled %d", enabled ? 1 : 0); } -/* Create an OPM proxy scanner */ +/* Create an OPM proxy scanner + * XXX - This is a big nasty hack, but it avoids resending duplicate data when + * configure_authd() is called. + */ +void +conf_create_opm_proxy_scanner(const char *type, uint16_t port) +{ + struct OPMScanner *scanner = rb_malloc(sizeof(struct OPMScanner)); + + rb_strlcpy(scanner->type, type, sizeof(scanner->type)); + scanner->port = port; + rb_dlinkAdd(scanner, &scanner->node, &opm_list); +} + void create_opm_proxy_scanner(const char *type, uint16_t port) { + conf_create_opm_proxy_scanner(type, port); rb_helper_write(authd_helper, "O opm_scanner %s %hu", type, port); } void delete_opm_proxy_scanner(const char *type, uint16_t port) { + rb_dlink_node *ptr, *nptr; + + RB_DLINK_FOREACH_SAFE(ptr, nptr, opm_list.head) + { + struct OPMScanner *scanner = ptr->data; + + if(rb_strncasecmp(scanner->type, type, sizeof(scanner->type)) == 0 && + scanner->port == port) + { + rb_dlinkDelete(ptr, &opm_list); + rb_free(scanner); + break; + } + } + rb_helper_write(authd_helper, "O opm_scanner_del %s %hu", type, port); } void delete_opm_proxy_scanner_all(void) { + rb_dlink_node *ptr, *nptr; + + RB_DLINK_FOREACH_SAFE(ptr, nptr, opm_list.head) + { + struct OPMScanner *scanner = ptr->data; + + rb_dlinkDelete(ptr, &opm_list); + rb_free(scanner); + } + rb_helper_write(authd_helper, "O opm_scanner_del_all"); }