diff --git a/README.markdown b/README.markdown index 5659ffd..4ce188b 100644 --- a/README.markdown +++ b/README.markdown @@ -1,20 +1,12 @@ -[![DNSCrypt](https://raw.github.com/dyne/dnscrypt-proxy/master/dnscrypt-small.png)](https://dowse.eu) - -[![Build Status](https://travis-ci.org/dyne/dnscrypt-proxy.png?branch=master)](https://travis-ci.org/dyne/dnscrypt-proxy) +![image](https://github.com/dyne/dnscrypt-proxy/assets/148059/4b242442-e1cf-4d0e-9ff7-d599ba3b6e81) ## Status of the project -This project was taken offline by its creator and maintainer Frank Denis on the 6th December 2017, after announcing in November 2017 that [the project needs a new maintainer](https://twitter.com/jedisct1/status/928942292202860544). - -The old webpage [dnscrypt.org]() now points to a new domain, endorsing the usage of competing protocol "DNS-over-TLS" and competing software in particular the "getdns" library and an immature implementation that could substitute dnscrypt-proxy, called "stubby". - -The new website also links a [critical analysis of DNSCrypt vs DNS-over-TLS protocols](https://tenta.com/blog/post/2017/12/dns-over-tls-vs-dnscrypt) by a company marketing their own open-source Android web browser and offering a new DNS resolver implemented in Go. - -While this sounds all very new and exciting to us, at Dyne.org we already rely on DNSCrypt-proxy for our project [Dowse.eu]() and are intentioned to maintain this software unless a viable and mature alternative arises, supporting our application of it in Dowse. +The DNScrypt v2 C++ implementation was taken offline by its creator and maintainer Frank Denis on the 6th December 2017, after announcing in November 2017 that [the project needs a new maintainer](https://twitter.com/jedisct1/status/928942292202860544). -We intend to maintain the DNSCrypt-proxy codebase without the intention of adding any new features, just patch bugs. We are also available to archive older versions and setup the website and the wiki, if we are given these archives. Frank Denis: if you are reading this please contact us on info@dyne.org. It won't take long and we are happy to keep your project alive, many thanks for all the fish so far! +The [dnscrypt.org](https://dnscrypt.org) webpage lists a good number of end-user resources built from a new implementation written in Go. -Anyone running a DNSCrypt server, interested in the future of this software, willing to share more insights or wanting to help with development and documentation: be welcome to [join our dnscrypt mailinglist](https://mailinglists.dyne.org/cgi-bin/mailman/listinfo/dnscrypt) where we are setting up a campfire for all of us to make a sustainable plan and take collectively informed decisions. +At Dyne.org we rely on the v2 of the DNScrypt protocol and this older but still working C++ implementation of dnscrypt-proxy for our [Dowse.eu](https://dyne.org/software/dowse) project and we keep maintaining the C++ implementation of dnscrypt-proxy. ## What is DNSCrypt @@ -26,16 +18,15 @@ While not providing end-to-end security, it protects the local network, which is often the weakest point of the chain, against man-in-the-middle attacks. `dnscrypt-proxy` is a client-implementation of the protocol. It -requires a DNS server available via the [DNSCrypt -wrapper](https://github.com/cofyc/dnscrypt-wrapper) to function. A -number of public DNSCrypt servers are already available. +requires a DNS server made available by the [DNSCrypt](https://github.com/DNSCrypt/) project. Plugins ------- -Aside from implementing the protocol, dnscrypt-proxy can be extended +Aside from implementing the DNSCrypt v2 protocol, the C++ dnscrypt-proxy can be extended with plug-ins, and gives a lot of control on the local DNS traffic: +- Provide nifty real-time traffic visualization using the Dowse plugin. - Review the DNS traffic originating from your network in real time, and detect compromised hosts and applications phoning home. - Locally block ads, trackers, malware, spam, and any website whose diff --git a/configure.ac b/configure.ac index d7d789b..86bc99e 100644 --- a/configure.ac +++ b/configure.ac @@ -502,6 +502,7 @@ AC_CONFIG_FILES([Makefile src/plugins/example/Makefile src/plugins/example-cache/Makefile src/plugins/example-logging/Makefile + src/plugins/dowse/Makefile src/plugins/example-ldns-aaaa-blocking/Makefile src/plugins/example-ldns-blocking/Makefile src/plugins/example-ldns-forwarding/Makefile diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index cb1f7d4..99d6a72 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -6,6 +6,7 @@ SUBDIRS = \ if USE_LDNS SUBDIRS += \ + dowse \ example-ldns-aaaa-blocking \ example-ldns-blocking \ example-ldns-forwarding \ diff --git a/src/plugins/dowse/Makefile.am b/src/plugins/dowse/Makefile.am new file mode 100644 index 0000000..0fce58a --- /dev/null +++ b/src/plugins/dowse/Makefile.am @@ -0,0 +1,41 @@ + +pkglib_LTLIBRARIES = \ + libdcplugin_dowse.la + +libdcplugin_dowse_la_LIBTOOLFLAGS = --tag=disable-static + +libdcplugin_dowse_la_SOURCES = database.h dnscrypt-dowse.h dowse.h \ +ip2mac.c dnscrypt-dowse.c domainlist.c hashmap.c redis.c log.c + +libdcplugin_dowse_la_LIBADD = @LDNS_LIBS@ -lhiredis + +libdcplugin_dowse_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -avoid-version \ + -export-dynamic \ + -module \ + -no-undefined + +libdcplugin_dowse_la_CPPFLAGS = \ + $(LTDLINCL) \ + -I../../include + + + +bin_PROGRAMS = \ + dowse-to-gource dowse-to-osc dowse-to-mqtt + +dowse_to_gource_SOURCES = \ + dowse-to-gource.c log.c redis.c dowse.h + +dowse_to_osc_SOURCES = \ + dowse-to-osc.c log.c redis.c dowse.h + +dowse_to_mqtt_SOURCES = \ + dowse-to-mqtt.c log.c redis.c dowse.h + +dowse_to_gource_LDADD = -lhiredis +dowse_to_osc_LDADD = -lhiredis -llo +dowse_to_mqtt_LDADD = -lhiredis -lmosquitto + +AM_CFLAGS = @CWFLAGS@ diff --git a/src/plugins/dowse/database.h b/src/plugins/dowse/database.h new file mode 100644 index 0000000..f3af5d2 --- /dev/null +++ b/src/plugins/dowse/database.h @@ -0,0 +1,3 @@ +#define db_dynamic 0 +#define db_runtime 1 +#define db_storage 2 diff --git a/src/plugins/dowse/dnscrypt-dowse.c b/src/plugins/dowse/dnscrypt-dowse.c new file mode 100644 index 0000000..e9a5517 --- /dev/null +++ b/src/plugins/dowse/dnscrypt-dowse.c @@ -0,0 +1,794 @@ +/* Dowse - DNSCrypt proxy plugin for DNS management + * + * (c) Copyright 2016-202430 Dyne.org foundation, Amsterdam + * Written by Denis Roio aka jaromil + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#define _GNU_SOURCE + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +// #include + +// #include + +#include + +// 24 hours +#define CACHE_EXPIRY 5 + +#ifndef DEBUG +#define DEBUG 1 +#endif + +DCPLUGIN_MAIN(__FILE__); + +int publish_query(plugin_data_t *data); + +// returns the answer in a wire format buffer (allocated in this function) +// fills in wire packet lenght in *asize +// returned buffer must be freed with LDNS_FREE +uint8_t *answer_to_question(uint16_t pktid, ldns_rr *question_rr, char *answer, size_t *asize); + +// ip2mac address translation tool +int ip4_derive_mac(plugin_data_t *data); + +const char * dcplugin_description(DCPlugin * const dcplugin) { + return "Dowse plugin to filter dnscrypt queries"; +} + + +const char * dcplugin_long_description(DCPlugin * const dcplugin) { + return + "This plugin checks all settings in Dowse and operates filtering on dnscrypt queries accordingly\n"; +} +int dcplugin_init(DCPlugin * const dcplugin, int argc, char *argv[]) { + int i; + + + func("compile time : %s", __TIME__); + + + plugin_data_t *data = malloc(sizeof(plugin_data_t)); + memset(data, 0x0, sizeof(plugin_data_t)); + + + // TODO: check succesful result or refuse to init + + data->cache = NULL; + data->offline = 0; + data->domainlist = NULL; + data->debug = DEBUG; + data->query[0] = 0x0; + data->ownip4[0] = 0x0; + + if(data->debug) act("BUILD TIME DEBUG ON"); + + for(i=0; idebug = 1; + act("RUN TIME DEBUG ON"); + // TODO: check error + } + + if( strncmp(argv[i], "offline", 7) == 0) data->offline=1; + + } + + data->listpath = getenv("DOWSE_DOMAINLIST"); + if(!data->listpath) { + warn("environmental variable DOWSE_DOMAINLIST not set"); + data->listpath = NULL; + } else { + act("Loading domainlist from: %s", data->listpath); + load_domainlist(data); + } + + { + char *stmp = getenv("DOWSE_LAN_ADDRESS_IP4"); + if(!stmp) { + warn("own IP4 on LAN not known, variable DOWSE_LAN_ADDRESS_IP4 not set"); + data->ownip4[0] = 0x0; + } else { + strncpy(data->ownip4, stmp, NI_MAXHOST); + act("Own IPv4 address on LAN: %s", data->ownip4); + } + } + + + { + char *stmp = getenv("DOWSE_LAN_NETMASK_IP4"); + if(!stmp) { + warn("IP4 netmask not known, variable DOWSE_LAN_NETMASK_IP4 not set"); + data->netmask_ip4[0] = 0x0; + } else { + strncpy(data->netmask_ip4, stmp, NI_MAXHOST); + act("Own IPv4 netmask for LAN: %s", data->netmask_ip4); + } + // // cheap trick to verify if ip4 + // if( sscanf(ipaddr, "%d.%d.%d.%d", &a, &b, &c, &d) != 4) + // return 1; + } + + // data->interface = getenv("interface"); + // if(!data->interface) { + // err("Error: no interface configured in Dowse"); + // return DCP_SYNC_FILTER_RESULT_ERROR; + // } + + // Get an internet domain socket. + data->sock = socket(AF_INET, SOCK_DGRAM, 0); + if(data->sock == -1) { + err("Cannot open an internet domain socket for mac resolution: %s", + strerror(errno)); + return DCP_SYNC_FILTER_RESULT_ERROR; + } + + data->redis = connect_redis(db_dynamic); + if(!data->redis) return 1; + + data->redis_stor = connect_redis(db_storage); + if(!data->redis_stor) return 1; + + // data->cache = connect_redis(REDIS_HOST, REDIS_PORT, db_runtime); + // if(!data->cache) return 1; + + // // save the cache connection to runtime db as logger + // log_redis = data->cache; + + dcplugin_set_user_data(dcplugin, data); + notice("Dowse plugin initialisation succesfull"); + return 0; +} + +int dcplugin_destroy(DCPlugin * const dcplugin) { + + plugin_data_t *data = dcplugin_get_user_data(dcplugin); + + if(data->debug) { + act("dnscrypt dowse plugin quit"); + } + + if(data->domainlist) free_domainlist(data); + redisFree(data->redis); + redisFree(data->redis_stor); + if(data->cache) redisFree(data->cache); + close(data->sock); + free(data); + + return 0; +} + +DCPluginSyncFilterResult dcplugin_sync_pre_filter(DCPlugin *dcplugin, DCPluginDNSPacket *dcp_packet) { + + + uint8_t* wire; + size_t wirelen; + uint16_t packet_id; + + ldns_pkt *packet = NULL; + ldns_rr_list *question_rr_list; + ldns_rr *question_rr; + ldns_rdf *question_rdf; + + char question_str[MAX_QUERY]; + int question_len; + + // to get the source ip + struct sockaddr_storage *from_sa; + // socklen_t from_len; + + size_t answer_size = 0; + uint8_t *outbuf = NULL; + + char rr_to_redirect[2048]; + int party_mode=0; + int enable_to_browse = 0; + + plugin_data_t *data = dcplugin_get_user_data(dcplugin); + + + wire = dcplugin_get_wire_data(dcp_packet); + + + wirelen = dcplugin_get_wire_data_len(dcp_packet); + + + // enforce max dns query size + if(wirelen > MAX_DNS) // (RFC 6891) + return DCP_SYNC_FILTER_RESULT_KILL; + + // TODO: throttling to something like 200 calls per second + // FUNCTION LIMIT_API_CALL(ip) + // ts = CURRENT_UNIX_TIME() + // keyname = ip+":"+ts + // current = GET(keyname) + // IF current != NULL AND current > 10 THEN + // ERROR "too many requests per second" + // ELSE + // MULTI + // INCR(keyname,1) + // EXPIRE(keyname,10) + // EXEC + // PERFORM_API_CALL() + // END + + + // import the wire packet to ldns format + if (ldns_wire2pkt(&packet, wire, wirelen) != LDNS_STATUS_OK) + return DCP_SYNC_FILTER_RESULT_KILL; + + + packet_id = ldns_pkt_id(packet); + + // debug + if(data->debug) { + func("-- packet received with id %u:\n", packet_id); + ldns_pkt_print(stderr, packet); + func("-- \n"); + } + + + // retrieve the actual question string + question_rr_list = ldns_pkt_question(packet); + + + question_rr = ldns_rr_list_rr(question_rr_list, 0U); // first in list) + + + question_rdf = ldns_rr_owner(question_rr); + + { + char *tmp; + tmp = ldns_rdf2str(question_rdf); + if(tmp) { + snprintf(question_str, MAX_QUERY, "%s", tmp); + free(tmp); + } + } + + question_len = strlen(question_str); + // TODO: check if we need to query for more questions here + + if (question_len == 0) { + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_KILL; + } + + // debug + if(data->debug) + func("%s (%u, last: '%c')\n", question_str, question_len, + question_str[question_len-1]); + + data->reverse = 0; + ///////////////////////////////////// + // resolve reverse ip to domain (PTR) + /// + if(question_str[question_len-1] == '.') { // .arpa. query? + if(question_str[question_len-5] == 'a' && + question_str[question_len-4] == 'r' && + question_str[question_len-3] == 'p' && + question_str[question_len-2] == 'a') { + // TODO: shall we check for .in.addr? (RFC?) + // this is now considered a reverse call + func("reverse resolution call: %s", question_str); + ldns_rdf *tmpname, *reverse; + char reverse_str[MAX_QUERY]; + + data->reverse = 1; + + // import in ldns dname format + tmpname = ldns_dname_new_frm_str(question_str); + if (ldns_rdf_get_type(tmpname) != LDNS_RDF_TYPE_DNAME) { + ldns_rdf_deep_free(tmpname); + ldns_pkt_free(packet); + func("dropped packet: .arpa address is not dname"); + return DCP_SYNC_FILTER_RESULT_KILL; + } + + // reverse the domain + reverse = ldns_dname_reverse(tmpname); + // chop left twice (lazy with ldns, TODO: optimise) + ldns_rdf_deep_free(tmpname); + tmpname = ldns_dname_left_chop((const ldns_rdf*)reverse); + ldns_rdf_deep_free(reverse); + reverse = ldns_dname_left_chop(tmpname); + ldns_rdf_deep_free(tmpname); + + snprintf(reverse_str, MAX_QUERY, "%s", ldns_rdf2str(reverse)); + ldns_rdf_deep_free(reverse); + reverse_str[ strlen(reverse_str)-1]='\0'; + + if(data->debug) + func("reverse: %s\n", reverse_str); + + // resolve locally leased hostnames with a O(1) operation on redis + data->reply = cmd_redis(data->redis_stor, + "GET dns-reverse-%s", + reverse_str); + if(data->reply) + if(data->reply->len) { // it exists, return that + char tmprr[2048]; + + if(data->debug) + func("found local reverse: %s", data->reply->str); + + snprintf(tmprr, 2048, "%s 0 IN PTR %s", + question_str, data->reply->str); + freeReplyObject(data->reply); + // render the packet into wire format (outbuf is saved with a memcpy) + outbuf = answer_to_question(packet_id, question_rr, + tmprr, &answer_size); + + // free all the packet structure here, outbuf is the + // wire format result + ldns_pkt_free(packet); + + + if(!outbuf) return DCP_SYNC_FILTER_RESULT_KILL; + + dcplugin_set_wire_data(dcp_packet, outbuf, answer_size); + + if(outbuf) LDNS_FREE(outbuf); + return DCP_SYNC_FILTER_RESULT_DIRECT; + } + + freeReplyObject(data->reply); + + // check if the ip is part of the LAN, if yes avoid forwarding it and return not-found + // { + // struct in_addr query_ip4_ia; + // inet_pton(AF_INET, reverse_str, &query_ip4_ia); + // inet_pton(AF_INET, reverse_str, &query_ip4_ia); + + // inet_pton(AF_INET, data->netmask_ip4, &data->netmask_ip4_ia); + // inet_pton(AF_INET, data->network_ip4, &data->network_ip4_ia); + // if ( + // ( + // ((int32_t)query_ip4_ia) & ((int32_t)data->netmask_ip4_ia) + // ) + // == + // ( + // ((int32_t)data->network_ip4_ia) & ((int32_t)data->netmask_ip4_ia) + // ) + // ) { + // // request about LAN. TODO: Return here, don't go further + // } + // } + } + } + // end of reverse resolution (PTR) + ///////////////////////////////// + + // save the query string in the user plugin_data structure + strncpy(data->query, question_str, MAX_QUERY); + data->query_len = strlen(data->query); + data->query[data->query_len-1] = '\0'; // eliminate terminating dot + + // sockaddr + from_sa = dcplugin_get_client_address(dcp_packet); + // from_len = dcplugin_get_client_address_len(dcp_packet); + + // TODO: check if convenient to resolve also the hostname in this way + // if( getnameinfo((struct sockaddr *)from_sa, from_len, + // data->from, sizeof(data->from), NULL, 0, 0x0) != 0) { + // getnameinfo((struct sockaddr *)from_sa, from_len, + // data->from, sizeof(data->from), NULL, 0, NI_NUMERICHOST); + + // TODO: check on return value if error + data->ip4_addr = ((struct sockaddr_in *)from_sa)->sin_addr; + data->ip4 = inet_ntoa(data->ip4_addr); + if(data->ip4 == 0) { + error("Query from invalid address, error inet_ntoa(sockaddr_in)"); + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_KILL; + } + + snprintf(data->from, NI_MAXHOST, "%s", data->ip4); + + // publish info to redis channel + publish_query(data); + + // skip checks if query comes from localhost + if(strcmp(data->ip4, "127.0.0.1") != 0) { + + // retrieve mac_address of client and writes it into data->mac + if ( ip4_derive_mac(data) != 0) { + + warn("can't resolv mac address of IP: %s", data->ip4); + warn("redirect on captive portal due to ip2mac() internal error"); + if(data->query[0] && data->ownip4[0]) + snprintf(rr_to_redirect, 2048, "%s 0 IN A %s", data->query, data->ownip4); + // double free?! + // if(data->reply) + // freeReplyObject(data->reply); + + func("return a wire packet immediately"); + outbuf = answer_to_question(packet_id, question_rr, rr_to_redirect, &answer_size); + if (!outbuf) { + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_KILL; + } + + dcplugin_set_wire_data(dcp_packet, outbuf, answer_size); + LDNS_FREE(outbuf); + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_DIRECT; + + } + + // check if party_mode is on then no need to control authorization to browse + data->reply = cmd_redis(data->redis_stor,"GET party_mode"); + if(data->reply) { + if(data->reply->str) + if( strncmp(data->reply->str,"yes",3) == 0) + party_mode = 1; + freeReplyObject(data->reply); + } + + if(!party_mode) { + // check if the mac address is authorized + data->reply = cmd_redis(data->redis_stor, "HGET thing_%s enable_to_browse", data->mac); + if(data->reply) { + if(data->reply->str) + if( strncmp(data->reply->str, "yes", 3) == 0) + enable_to_browse = 1; + freeReplyObject(data->reply); + } + + if(!enable_to_browse) { + // redirect to dowse if it is not authorized + func("redirect on captive portal for ip %s mac %s", data->ip4, data->mac); + snprintf(rr_to_redirect, 2048, "%s 0 IN A %s", data->query, data->ownip4); + + // return a wire packet immediately + outbuf = answer_to_question(packet_id, question_rr, rr_to_redirect, &answer_size); + if (!outbuf) { + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_KILL; + } + + dcplugin_set_wire_data(dcp_packet, outbuf, answer_size); + LDNS_FREE(outbuf); + + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_DIRECT; + + } + } + } // check if not localhost + + // DIRECT ENDPOINT + // resolve locally leased hostnames with a O(1) operation on redis + data->reply = cmd_redis(data->redis, "GET dns-lease-%s", data->query); + if(data->reply) { + if(data->reply->len) { // it exists, return that + size_t answer_size = 0; + uint8_t *outbuf = NULL; + char tmprr[2048]; + + if(data->debug) + func("local lease found: %s", data->reply->str); + + snprintf(tmprr, 2048, "%s 0 IN A %s", data->query, data->reply->str); + freeReplyObject(data->reply); + + outbuf = answer_to_question(packet_id, question_rr, + tmprr, &answer_size); + + if(!outbuf) { + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_KILL; + } + + dcplugin_set_wire_data(dcp_packet, outbuf, answer_size); + + if(outbuf) LDNS_FREE(outbuf); + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_DIRECT; + } + //////////////// + freeReplyObject(data->reply); + } + + if(data->cache) { + // check if the answer is cached (the key is the domain string) + data->reply = cmd_redis(data->cache, "GET dns-cache-%s", data->query); + if(data->reply) + if(data->reply->len) { // it exists in cache, return that + + if(data->debug) + func("found in cache wire packet of %u bytes", data->reply->len); + + + // a bit dangerous, but veeery fast: working directly on the wire packet + // copy message ID (first 16 bits) + data->reply->str[0] = wire[0]; + data->reply->str[1] = wire[1]; + + dcplugin_set_wire_data(dcp_packet, data->reply->str, data->reply->len); + freeReplyObject(data->reply); + + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_DIRECT; + } + } + + // if(from_sa->ss_family == AF_PACKET) { // if contains mac address + // char *p = ((struct sockaddr_ll*) from_sa)->sll_addr; + // snprintf(data->mac, 32, "%02x:%02x:%02x:%02x:%02x:%02x", + // p[0], p[1], p[2], p[3], p[4], p[5]); + // // this is here as a pro-memoria, since we never get AF_P + + if(data->offline) { + size_t answer_size = 0; + uint8_t *outbuf = NULL; + char tmprr[2048]; + if(data->reverse) + snprintf(tmprr, 2048, "%s 0 IN PTR localhost", question_str); + else + snprintf(tmprr, 2048, "%s 0 IN A 127.0.0.1", data->query); + + outbuf = answer_to_question(packet_id, question_rr, + tmprr, &answer_size); + if(!outbuf) { + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_KILL; + } + dcplugin_set_wire_data(dcp_packet, outbuf, answer_size); + + if(outbuf) { + LDNS_FREE(outbuf); + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_DIRECT; + } + } + + + dcplugin_set_user_data(dcplugin, data); + if(data->debug) + func("%s (forwarding to dnscrypt)", data->query); + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_OK; + // return DCP_SYNC_FILTER_RESULT_OK; +} + +DCPluginSyncFilterResult dcplugin_sync_post_filter(DCPlugin *dcplugin, DCPluginDNSPacket *dcp_packet) { + + return DCP_SYNC_FILTER_RESULT_OK; + +#if 0 + + plugin_data_t *data; + uint8_t *wire; + size_t wirelen; + + ldns_pkt *packet = NULL; + ldns_rr_list *answers; + ldns_rr *answer; + size_t answers_count; + + + data = dcplugin_get_user_data(dcplugin); + wire = dcplugin_get_wire_data(dcp_packet); + wirelen = dcplugin_get_wire_data_len(dcp_packet); + + if (ldns_wire2pkt(&packet, wire, wirelen) != LDNS_STATUS_OK) + return DCP_SYNC_FILTER_RESULT_KILL; + + // just cache if return packet contains an answer + answers = ldns_pkt_answer(packet); + answers_count = ldns_rr_list_rr_count(answers); + if(answers_count && data->cache) { + + if(data->debug) { + func("-- caching reply for: %s", data->query); + ldns_pkt_print(stderr, packet); + func("-- \n"); + } + + // check if the query is cached + func("SETEX dns-cache-%s %u (wire of %u bytes)", + data->query, CACHE_EXPIRY, wirelen); + data->reply = redisCommand(data->cache, "SETEX dns-cache-%s %u %b", + data->query, CACHE_EXPIRY, wire, wirelen); + okredis(data->cache, data->reply); + } + ldns_pkt_free(packet); + return DCP_SYNC_FILTER_RESULT_OK; + + + // test print out ip from packet + { + ldns_rr_list *answers; + ldns_rr *answer; + char *answer_str; + ldns_rr_type type; + size_t answers_count; + size_t i; + + answers = ldns_pkt_answer(packet); + answers_count = ldns_rr_list_rr_count(answers); + for (i = (size_t) 0U; i < answers_count; i++) { + answer = ldns_rr_list_rr(answers, i); + type = ldns_rr_get_type(answer); + if (type != LDNS_RR_TYPE_A && type != LDNS_RR_TYPE_AAAA) { + continue; + } + if ((answer_str = ldns_rdf2str(ldns_rr_a_address(answer))) == NULL) { + return DCP_SYNC_FILTER_RESULT_KILL; + } + func("ip: %s", answer_str); + free(answer_str); + } + } +#endif + + +} + + +uint8_t *answer_to_question(uint16_t pktid, ldns_rr *question_rr, char *answer, size_t *asize) { + ldns_pkt *answer_pkt = NULL; + ldns_status status; + ldns_rr_list *answer_qr; + ldns_rr_list *answer_an; + ldns_rr *answer_an_rr; + uint8_t *outbuf = NULL; + + // clone the question_rr in answer_qr + answer_qr = ldns_rr_list_new(); + ldns_rr_list_push_rr(answer_qr, ldns_rr_clone(question_rr)); + + // create the answer_an rr_list + answer_an = ldns_rr_list_new(); + // answer_an_rdf = ldns_rdf_new_frm_str // makes a copy of data buffer + // (LDNS_RDF_TYPE_A, data->reply->str); + + // TODO: optimise by avoiding the string parsing and creating the rdf manually + // i.e. ldns_rr_set_rdf(answer_an_rr, answer_an_rdf, 0U); + // this may need the creation of more functions rather than remove this one + ldns_rr_new_frm_str(&answer_an_rr, answer, 0, NULL, NULL); + + // after this push will free both with free(answer_an) + ldns_rr_list_push_rr(answer_an, answer_an_rr); + + // create the packet and empty fields + answer_pkt = ldns_pkt_new(); + + ldns_pkt_set_qr(answer_pkt, 1); + ldns_pkt_set_aa(answer_pkt, 1); + ldns_pkt_set_ad(answer_pkt, 0); + + ldns_pkt_set_id(answer_pkt, pktid); + + ldns_pkt_push_rr_list(answer_pkt, LDNS_SECTION_QUESTION, answer_qr); + ldns_pkt_push_rr_list(answer_pkt, LDNS_SECTION_ANSWER, answer_an); + + // render the packet into wire format (outbuf is saved with a memcpy) + status = ldns_pkt2wire(&outbuf, answer_pkt, asize); + + // free all the packet structure here, outbuf is the wire format result + ldns_pkt_free(answer_pkt); + // ldns_rr_free(answer_an_rr); + ldns_rr_list_free(answer_an); + ldns_rr_list_free(answer_qr); + + if (status != LDNS_STATUS_OK) { + err("Error in answer_to_question : %s", + ldns_get_errorstr_by_id(status)); + if(outbuf) LDNS_FREE(outbuf); + outbuf = NULL; // NULL on error + } + + return outbuf; + +} + + +/* a debug tool */ +void print_data_redis( redisContext *redis, char*prefix) { + #define PRINT_POINTER(p) {\ + FILE *fp=fopen("log.txt","a+");\ + fprintf(fp,"%s %s %p\n",prefix,#p,p);\ + fclose(fp);\ + } + PRINT_POINTER(redis); + PRINT_POINTER(redis->obuf); + PRINT_POINTER(redis->reader); + PRINT_POINTER(redis->reader->fn); +// PRINT_POINTER(redis->reader->rstack); +} + + +int publish_query(plugin_data_t *data) { + char *extracted; + int val = 1; + int res; + char *sval; + + time_t epoch_t; + char outnew[MAX_OUTPUT]; + + // domain hit count + extracted = extract_domain(data); + data->reply = cmd_redis(data->redis, "INCR dns-query-%s", extracted); + if(data->reply) { + val = data->reply->integer; + freeReplyObject(data->reply); + } + + data->reply = cmd_redis(data->redis, "EXPIRE dns-query-%s %u", extracted, DNS_HIT_EXPIRE); // DNS_HIT_EXPIRE + if(data->reply) freeReplyObject(data->reply); + + // timestamp + time(&epoch_t); + + // retrieve thing's name from redis + data->reply = cmd_redis(data->redis_stor, "HGET thing_%s name", data->mac); + if(data->reply) { + if(data->reply->str) { // we have the name + // compose the path of the detected query + snprintf(outnew, MAX_OUTPUT, + "DNS,%s,%d,%lu,%s,%s", + data->reply->str, val, + epoch_t, extracted, data->tld); + } else { + snprintf(outnew, MAX_OUTPUT, + "DNS,%s,%d,%lu,%s,%s", + data->from, val, + epoch_t, extracted, data->tld); + } + freeReplyObject(data->reply); + } + + // add domainlist group if found + if(data->listpath) { + res = hashmap_get(data->domainlist, extracted, (void**)(&sval)); + if(res==MAP_OK) {// add domain group + strncat(outnew,",",2); + strncat(outnew,sval,MAX_OUTPUT-128); + //snprintf(outnew,MAX_OUTPUT,"%s,%s",outnew,sval); + } + } + + data->reply = cmd_redis(data->redis, "PUBLISH dns-query-channel %s", outnew); + if(data->reply) freeReplyObject(data->reply); + + + + return 0; +} diff --git a/src/plugins/dowse/dnscrypt-dowse.h b/src/plugins/dowse/dnscrypt-dowse.h new file mode 100644 index 0000000..c89fbcf --- /dev/null +++ b/src/plugins/dowse/dnscrypt-dowse.h @@ -0,0 +1,98 @@ +/* Dowse - DNSCrypt proxy plugin for DNS management + * + * (c) Copyright 2016 Dyne.org foundation, Amsterdam + * Written by Denis Roio aka jaromil + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#ifndef __DOWSE_H__ +#define __DOWSE_H__ + +// just for NI_MAXHOST +#include +#include + +// expiration in seconds for the domain hit counter +#define DNS_HIT_EXPIRE 21600 + +// expiration in seconds for the cache on dns replies +#define DNS_CACHE_EXPIRE 60 + +#define MAX_QUERY 512 +#define MAX_DOMAIN 256 +#define MAX_TLD 32 +#define MAX_DNS 512 // RFC 6891 + +#define MAX_LINE 512 + +typedef struct { + //////// + // data + + char query[MAX_QUERY]; // incoming query + size_t query_len; // size of incoming query string + + char domain[MAX_DOMAIN]; // domain part parsed (2nd last dot) + char tld[MAX_TLD]; // tld (domain extension, 1st dot) + + char from[NI_MAXHOST]; // hostname or ip originating the query + char mac[32]; // mac address (could be just 12 chars) + char *ip4; // sin_addr conversion returned by inet_ntoa + struct in_addr ip4_addr; + + char ownip4[NI_MAXHOST]; + char netmask_ip4[NI_MAXHOST]; + char network_ip4[NI_MAXHOST]; + + struct in_addr ownip4_ia; + struct in_addr netmask_ip4_ia; + struct in_addr network_ip4_ia; + char *interface; // from getenv + int sock; + + // map of known domains + char *listpath; + map_t domainlist; + + redisContext *redis; + redisContext *redis_stor; + redisReply *reply; + + // using db_runtime to store cached hits + redisContext *cache; + + int reverse; + + int debug; + int offline; +} plugin_data_t; + + +// ldns/error.h +void warning(const char *fmt, ...); +void error(const char *fmt, ...); +void mesg(const char *fmt, ...); + +/// from domainlist.c +size_t trim(char *out, size_t len, const char *str); +void load_domainlist(plugin_data_t *data); +char *extract_domain(plugin_data_t *data); +void free_domainlist(plugin_data_t *data); +/// + +#endif diff --git a/src/plugins/dowse/domainlist.c b/src/plugins/dowse/domainlist.c new file mode 100644 index 0000000..3186e76 --- /dev/null +++ b/src/plugins/dowse/domainlist.c @@ -0,0 +1,170 @@ +/* Dowse - DNSCrypt proxy plugin for DNS management + * + * (c) Copyright 2016-2024 Dyne.org foundation, Amsterdam + * Written by Denis Roio aka jaromil + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + + +// Stores the trimmed input string into the given output buffer, which must be +// large enough to store the result. If it is too small, the output is +// truncated. +size_t trim(char *out, size_t len, const char *str) { + if(len == 0) + return 0; + + const char *end; + size_t out_size; + + // Trim leading space + while(isspace(*str)) str++; + + if(*str == 0) // All spaces? + { + *out = 0; + return 1; + } + + // Trim trailing space + end = str + strlen(str) - 1; + while(end > str && isspace(*end)) end--; + end++; + + // Set output size to minimum of trimmed string length and buffer size minus 1 + out_size = (end - str) < len-1 ? (end - str) : len-1; + + // Copy trimmed string and add null terminator + memcpy(out, str, out_size); + out[out_size] = 0; + + return out_size; +} + +void load_domainlist(plugin_data_t *data) { + char line[MAX_LINE]; + char trimmed[MAX_LINE]; + DIR *listdir = 0; + struct dirent *dp; + FILE *fp; + + data->domainlist = hashmap_new(); + + // parse all files in directory + listdir = opendir(data->listpath); + if(!listdir) { + err("domainlist cannot open dir: %s",strerror(errno)); + exit(1); } + + // read file by file + dp = readdir (listdir); + while (dp) { + char fullpath[MAX_LINE]; + snprintf(fullpath,MAX_LINE,"%s/%s",data->listpath,dp->d_name); + // open and read line by line + fp = fopen(fullpath,"r"); + if(!fp) { + err("domainlist cannot open file %s: %s",fullpath,strerror(errno)); + continue; } + while(fgets(line,MAX_LINE, fp)) { + // save lines in hashmap with filename as value + if(line[0]=='#') continue; // skip comments + trim(trimmed, strlen(line), line); + if(trimmed[0]=='\0') continue; // skip blank lines + // logerr("(%u) %s\t%s", trimmed[0], trimmed, dp->d_name); + + // here valgrind complains about dereferencing but the + // hashmap holds the addresses so it is not a problem + hashmap_put(data->domainlist, strdup(trimmed), strdup(dp->d_name)); + } + fclose(fp); + dp = readdir (listdir); + } + closedir(listdir); + fprintf(stderr,"size of parsed domain-list: %u\n", hashmap_length(data->domainlist)); +} + + +char *extract_domain(plugin_data_t *data) { + // extracts the last two or three strings of a dotted domain string + + int c; + int dots = 0; + int first = 1; + char *last; + int positions = 2; // minimum, can become three if sld too short + int len; + char address[MAX_QUERY]; + + strncpy(address, data->query, MAX_QUERY); + len = strlen(address); + + /* logerr("extract_domain: %s (%u)",address, len); */ + + if(len<3) return(NULL); // a.i + + data->domain[len+1]='\0'; + for(c=len; c>=0; c--) { + last=address+c; + if(*last=='.') { + dots++; + // take the tld as first dot hits + if(first) { + strncpy(data->tld,last,MAX_TLD); + first=0; } + } + if(dots>=positions) { + char *test = strtok(last+1,"."); + if( strlen(test) > 3 ) break; // its not a short SLD + else positions++; + } + data->domain[c]=*last; + } + + // logerr("extracted: %s (%p) (dots: %u)", domain+c+1, domain+c+1, dots); + + return(data->domain+c+1); +} + + +// free all buffers of a loaded domainlist +int free_domainlist_f(any_t arg, any_t element) { + free(element); + return MAP_OK; } + +void free_domainlist(plugin_data_t *data) { + if(!data) { + fprintf(stderr, "ERROR: data NULL in domainlist\n"); + return; + } + + hashmap_iterate(data->domainlist, free_domainlist_f, NULL); + hashmap_free(data->domainlist); +} diff --git a/src/plugins/dowse/dowse-to-gource.c b/src/plugins/dowse/dowse-to-gource.c new file mode 100644 index 0000000..ecc05c7 --- /dev/null +++ b/src/plugins/dowse/dowse-to-gource.c @@ -0,0 +1,91 @@ +/* Dowse - Gource listener for DNS query events + * + * (c) Copyright 2016-2024 Dyne.org foundation, Amsterdam + * Designed and written by Denis Roio + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * TODO: trap signals for clean quit, catch more redis errors + * and most importantly establish an internal data structure for dns query + */ + +#include +#include +#include +#include +#include + +#include + +static char output[MAX_OUTPUT]; +static int quit = 0; + + +redisContext *redis = NULL; +redisReply *reply = NULL; + +void ctrlc(int sig) { + act("\nQuit."); + redisFree(redis); + quit = 1; +} + +int main(int argc, char **argv) { + + char *dns, *ip, *action, *epoch, *domain, *tld, *group; + long long int hits; + + redis = connect_redis(db_dynamic); + + signal(SIGINT, ctrlc); + + reply = cmd_redis(redis,"SUBSCRIBE dns-query-channel"); + freeReplyObject(reply); + while(redisGetReply(redis,(void**)&reply) == REDIS_OK) { + if(quit) break; + + dns = strtok(reply->element[2]->str,","); + if(!dns) continue; + ip = strtok(NULL,","); + if(!ip) continue; + action = strtok(NULL,","); + if(!action) continue; + epoch = strtok(NULL,","); + if(!epoch) continue; + domain = strtok(NULL,","); + if(!domain) continue; + tld = strtok(NULL,","); + if(!tld) continue; + group = strtok(NULL,","); // optional + + + hits = atoll(action); + + // render + if(!group) + snprintf(output,MAX_OUTPUT,"%s|%s|%c|%s/%s", + epoch,ip,(hits==1)?'A':'M',tld,domain); + else + snprintf(output,MAX_OUTPUT,"%s|%s|%c|%s/%s/%s", + epoch,ip,(hits==1)?'A':'M',tld,group,domain); + + fprintf(stdout,"%s\n",output); + fflush(stdout); + + freeReplyObject(reply); + } + + exit(0); +} diff --git a/src/plugins/dowse/dowse-to-mqtt.c b/src/plugins/dowse/dowse-to-mqtt.c new file mode 100644 index 0000000..a283866 --- /dev/null +++ b/src/plugins/dowse/dowse-to-mqtt.c @@ -0,0 +1,210 @@ +/* Dowse Spring to MQTT via libmosquitto + * + * (c) Copyright 2016 Dyne.org foundation, Amsterdam + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * TODO: trap signals for clean quit, catch more redis errors + * and most importantly establish an internal data structure for dns query + */ + +#include +#include +#include +#include +#include +#include +#include + +// libmosquitto +#include + +#include + +// static char output[MAX_OUTPUT]; +static int quit = 0; + + +redisContext *redis = NULL; +redisReply *reply = NULL; + +struct mosquitto *mosq = NULL; + +extern int optind; + +void ctrlc(int sig) { + act("\nQuit."); + if(redis) redisFree(redis); + if(mosq) mosquitto_destroy(mosq); + mosquitto_lib_cleanup(); + quit = 1; +} + +int main(int argc, char **argv) { + // settings for + char *channel_list; int len_channel_list; + + #define DNS_QUERY_CHANNEL "dns-query-channel" + len_channel_list=strlen(DNS_QUERY_CHANNEL); + + channel_list=(char*) malloc(len_channel_list+1); + + snprintf(channel_list,len_channel_list+1,DNS_QUERY_CHANNEL); + + char id[25]; + const char *host; + int port = 1883; + int keepalive = 60; + + // char *dns, *ip, *action, *epoch, *domain, *tld, *group; + // long long int hits; + + int mres; + + int opt; + char pidfile[MAX_OUTPUT]; + // pid_t pid; + + pidfile[0]=0x0; + + while((opt = getopt(argc, argv, "p:c:")) != -1) { + switch(opt) { + case 'p': + snprintf(pidfile,MAX_OUTPUT,"%s",optarg); + break; + case 'c': + len_channel_list+=(strlen(optarg)+1); /* +1 for the blank */ + char *new_list=malloc(len_channel_list+1); + snprintf(new_list,len_channel_list+1,"%s %s",channel_list,optarg); + + free(channel_list); + channel_list=new_list; + + break; + } + } + + + + if(argv[optind] == NULL) { + err("usage: %s [-p pidfile] host [port]", argv[0]); + exit(1); + } + host = argv[optind]; + // TODO: get port from argv[2] when present + + signal(SIGINT, ctrlc); + + redis = connect_redis(db_dynamic); + + mosquitto_lib_init(); + srand(time(NULL)); + snprintf(id,24,"%u",rand()); + mosq = mosquitto_new(id, true, NULL); + + + // libmosq_EXPORT int mosquitto_connect( struct mosquitto * mosq, + // const char * host, + // int port, + // int keepalive ) + + mres = mosquitto_connect( mosq, host, port, keepalive); + if(mres != MOSQ_ERR_SUCCESS) { + err("can't connect to mosquitto server %s on port %u",host, port); + mosquitto_destroy(mosq); + mosquitto_lib_cleanup(); + return(1); + } else { + notice("connected to mosquitto server %s on port %u", host, port); + } + + reply = cmd_redis(redis,"SUBSCRIBE %s ",channel_list); + freeReplyObject(reply); + + if(pidfile[0]) { + pid_t pid; + FILE *fpid; + pid = getpid(); + fpid = fopen(pidfile,"w"); + if(!fpid) perror("writing pidfile"); + else { + fprintf(fpid,"%u\n",pid); + fclose(fpid); + } + } + + while(redisGetReply(redis,(void**)&reply) == REDIS_OK) { + if(quit) break; + + // mosquitto_publish( struct mosquitto *mosq, + // int *mid, + // const char *topic, + // int payloadlen, + // const void *payload, + // int qos, + // bool retain ) + + // Parameters + // mosq a valid mosquitto instance. + // mid pointer to an int. If not NULL, the function will set this to the message id of this particular message. + // payloadlen the size of the payload (bytes). Valid values are between 0 and 268,435,455. + // payload pointer to the data to send. If payloadlen > 0 this must be a valid memory location. + // qos integer value 0, 1 or 2 indicating the Quality of Service to be used for the message. + // retain set to true to make the message retained. + + mres = mosquitto_loop(mosq, -1, 1); + if(mres) mosquitto_reconnect(mosq); + + if (strcmp(reply->element[0]->str,"message")!=0) { + /* only the "message" should be forwarded on mqtt to the "subscribe" */ + continue; + } + /* every message is forwarded on the mqtt using the same name channel (reply->element[1]->str) + */ + mres = mosquitto_publish(mosq, NULL, reply->element[1]->str, + reply->element[2]->len, reply->element[2]->str, + 1 /*qos*/, false); + /* + for (int i=0;ielements;i++){ + fprintf(stderr," %d %s\n",i,reply->element[i]->str); + } + fprintf(stderr,"\n------------\n"); + */ + + // Returns + // MOSQ_ERR_SUCCESS on success. + // MOSQ_ERR_INVAL if the input parameters were invalid. + // MOSQ_ERR_NOMEM if an out of memory condition occurred. + // MOSQ_ERR_NO_CONN if the client isn’t connected to a broker. + // MOSQ_ERR_PROTOCOL if there is a protocol error communicating with the broker. + // MOSQ_ERR_PAYLOAD_SIZE if payloadlen is too large. + if(mres != MOSQ_ERR_SUCCESS) + err("error publishing message to mosquitto"); + + mosquitto_loop(mosq, -1, 1); + + // TODO: check returned value + + + freeReplyObject(reply); + } + sleep(1); + + mosquitto_disconnect(mosq); + mosquitto_destroy(mosq); + mosquitto_lib_cleanup(); + + exit(0); +} diff --git a/src/plugins/dowse/dowse-to-osc.c b/src/plugins/dowse/dowse-to-osc.c new file mode 100644 index 0000000..3372fe9 --- /dev/null +++ b/src/plugins/dowse/dowse-to-osc.c @@ -0,0 +1,103 @@ +/* Dowse - Open Sound Control listener for DNS query events + * + * (c) Copyright 2016 Dyne.org foundation, Amsterdam + * Written by Denis Roio aka jaromil + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +// liblo +#include + +#include + +redisContext *redis = NULL; +redisReply *reply = NULL; + +static int quit = 0; +void ctrlc(int sig) { + act("\nQuit."); + if(redis) redisFree(redis); + quit = 1; +} + +int main(int argc, char **argv) { + + int res; + lo_address osc; + char *dns, *ip, *action, *epoch, *domain, *tld; + unsigned int hits; + + if(argv[1] == NULL) { + err("usage: %s osc.URL (i.e: osc.udp://localhost:666/pd)", argv[0]); + exit(1); + } + + osc = lo_address_new_from_url( argv[1] ); + lo_address_set_ttl(osc, 1); // subnet scope + + redis = connect_redis(db_dynamic); + + reply = cmd_redis(redis,"SUBSCRIBE dns-query-channel"); + freeReplyObject(reply); + + signal(SIGINT, ctrlc); + + while(redisGetReply(redis,(void**)&reply) == REDIS_OK) { + + // reusable block to parse redis into variables + dns = strtok(reply->element[2]->str,","); + if(!dns) continue; + ip = strtok(NULL,","); + if(!ip) continue; + action = strtok(NULL,","); + if(!action) continue; + epoch = strtok(NULL,","); + if(!epoch) continue; + domain = strtok(NULL,","); + if(!domain) continue; + tld = strtok(NULL,","); + if(!tld) continue; + // -- + + hits = atoll(action); + + // TODO: use a more refined lo_send with low-latency flags + res = lo_send(osc, "/dowse/dns", "siss", + ip, hits, domain, tld); + if(res == -1) + err("OSC send error: %s",lo_address_errstr(osc)); + // just for console debugging + else + func("/dowse/dns %s %u %s %s", + ip, hits, domain, tld); + + fflush(stderr); + + freeReplyObject(reply); + if(quit) break; + } + + lo_address_free(osc); + + exit(0); +} diff --git a/src/plugins/dowse/dowse.h b/src/plugins/dowse/dowse.h new file mode 100644 index 0000000..b8c4f46 --- /dev/null +++ b/src/plugins/dowse/dowse.h @@ -0,0 +1,169 @@ +/* Dowse - public header for libdowse functions + * + * (c) Copyright 2016-2024 Dyne.org foundation, Amsterdam + * Written by Denis Roio aka jaromil + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LIBDOWSE_H__ +#define __LIBDOWSE_H__ + +#include +#include + +// logging channel singleton in libdowse +extern redisContext *log_redis; + + +// to quickly return 404 +#define HTTP404 err("HTTP 404, %s:%u, %s()", \ + __FILE__, __LINE__, __func__); \ + http_response(req, 404, NULL, 0); \ + return(KORE_RESULT_OK) + +// for use in debugging +#define FLAG func("reached: %s:%u, %s()", __FILE__, __LINE__, __func__) + +// log functions +void notice(const char *fmt, ...); + +void warn(const char *fmt, ...); + +void func(const char *fmt, ...); + +void err(const char *fmt, ...); + +void act(const char *fmt, ...); + + + +// parsetime +// int relative_time(char *utc, char *out); + +#define REDIS_PORT 6379 + +#define MAX_OUTPUT 2048 + +/* typedef struct redisReply { */ +/* int type; // REDIS_REPLY */ +/* long long integer; // The integer when type is REDIS_REPLY_INTEGER */ +/* int len; // Length of string */ +/* char *str; // Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ +/* size_t elements; // number of elements, for REDIS_REPLY_ARRAY */ +/* struct redisReply **element; // elements vector for REDIS_REPLY_ARRAY */ +/* } redisReply; */ +int okredis(redisContext *r, redisReply *res); + +redisReply *cmd_redis(redisContext *redis, const char *format, ...); + +redisContext *connect_redis(int db); + +/////////// +// hashmap + +#define MAP_MISSING -3 /* No such element */ +#define MAP_FULL -2 /* Hashmap is full */ +#define MAP_OMEM -1 /* Out of Memory */ +#define MAP_OK 0 /* OK */ + +/* + * any_t is a pointer. This allows you to put arbitrary structures in + * the hashmap. + */ +typedef void *any_t; + +/* + * PFany is a pointer to a function that can take two any_t arguments + * and return an integer. Returns status code.. + */ +typedef int (*PFany)(any_t, any_t); + +/* */ +#define INITIAL_SIZE (16) +#define MAX_CHAIN_LENGTH (8) + +/* We need to keep keys and values */ +typedef struct _hashmap_element{ + char* key; + int in_use; + any_t data; +} hashmap_element; + +/* A hashmap has some maximum size and current size, + * as well as the data to hold. */ +typedef struct _hashmap_map{ + int table_size; + int size; + hashmap_element *data; +} hashmap_map; + +/* + * map_t is a pointer to an internally maintained data structure. + * Clients of this package do not need to know how hashmaps are + * represented. They see and manipulate only map_t's. + */ +/*typedef any_t map_t;*/ + +/* Nicola: it's beautiful typing . Type is your friend*/ +typedef hashmap_map *map_t; + +/* + * Return an empty hashmap. Returns NULL if empty. +*/ +extern map_t hashmap_new(); + +/* + * Iteratively call f with argument (item, data) for + * each element data in the hashmap. The function must + * return a map status code. If it returns anything other + * than MAP_OK the traversal is terminated. f must + * not reenter any hashmap functions, or deadlock may arise. + */ +extern int hashmap_iterate(map_t in, PFany f, any_t item); + +/* + * Add an element to the hashmap. Return MAP_OK or MAP_OMEM. + */ +extern int hashmap_put(map_t in, char* key, any_t value); + +/* + * Get an element from the hashmap. Return MAP_OK or MAP_MISSING. + */ +extern int hashmap_get(map_t in, char* key, any_t *arg); + +/* + * Remove an element from the hashmap. Return MAP_OK or MAP_MISSING. + */ +extern int hashmap_remove(map_t in, char* key); + +/* + * Get any element. Return MAP_OK or MAP_MISSING. + * remove - should the element be removed from the hashmap + */ +extern int hashmap_get_one(map_t in, any_t *arg, int remove); + +/* + * Free the hashmap + */ +extern void hashmap_free(map_t in); + +/* + * Get the current size of a hashmap + */ +extern int hashmap_length(map_t in); + + +#endif diff --git a/src/plugins/dowse/hashmap.c b/src/plugins/dowse/hashmap.c new file mode 100644 index 0000000..c11b571 --- /dev/null +++ b/src/plugins/dowse/hashmap.c @@ -0,0 +1,413 @@ +/* + * Generic map implementation. + */ +#include + +#include +#include +#include + +/* +void hashmap_element_init(hashmap_element *p){ + p->in_use=0; + p->key=NULL; + p->data=NULL; +}*/ + +/* + * Return an empty hashmap, or NULL on failure. + */ +map_t hashmap_new() { + hashmap_map* m = (hashmap_map*) malloc(sizeof(hashmap_map)); + if(!m) goto err; + m->data = (hashmap_element*) calloc(INITIAL_SIZE,sizeof(hashmap_element)); + + if(!m->data) goto err; + + m->table_size = INITIAL_SIZE; + m->size = 0; + + return m; + err: + if (m) + hashmap_free(m); + return NULL; +} + +/* The implementation here was originally done by Gary S. Brown. I have + borrowed the tables directly, and made some minor changes to the + crc32-function (including changing the interface). //ylo */ + + /* ============================================================= */ + /* COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or */ + /* code or tables extracted from it, as desired without restriction. */ + /* */ + /* First, the polynomial itself and its table of feedback terms. The */ + /* polynomial is */ + /* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */ + /* */ + /* Note that we take it "backwards" and put the highest-order term in */ + /* the lowest-order bit. The X^32 term is "implied"; the LSB is the */ + /* X^31 term, etc. The X^0 term (usually shown as "+1") results in */ + /* the MSB being 1. */ + /* */ + /* Note that the usual hardware shift register implementation, which */ + /* is what we're using (we're merely optimizing it by doing eight-bit */ + /* chunks at a time) shifts bits into the lowest-order term. In our */ + /* implementation, that means shifting towards the right. Why do we */ + /* do it this way? Because the calculated CRC must be transmitted in */ + /* order from highest-order term to lowest-order term. UARTs transmit */ + /* characters in order from LSB to MSB. By storing the CRC this way, */ + /* we hand it to the UART in the order low-byte to high-byte; the UART */ + /* sends each low-bit to hight-bit; and the result is transmission bit */ + /* by bit from highest- to lowest-order term without requiring any bit */ + /* shuffling on our part. Reception works similarly. */ + /* */ + /* The feedback terms table consists of 256, 32-bit entries. Notes: */ + /* */ + /* The table can be generated at runtime if desired; code to do so */ + /* is shown later. It might not be obvious, but the feedback */ + /* terms simply represent the results of eight shift/xor opera- */ + /* tions for all combinations of data and CRC register values. */ + /* */ + /* The values must be right-shifted by eight bits by the "updcrc" */ + /* logic; the shift must be unsigned (bring in zeroes). On some */ + /* hardware you could probably optimize the shift in assembler by */ + /* using byte-swap instructions. */ + /* polynomial $edb88320 */ + /* */ + /* -------------------------------------------------------------------- */ + +static unsigned long crc32_tab[] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL + }; + +/* Return a 32-bit CRC of the contents of the buffer. */ + +unsigned long crc32(const unsigned char *s, unsigned int len) +{ + unsigned int i; + unsigned long crc32val; + + crc32val = 0; + for (i = 0; i < len; i ++) + { + crc32val = + crc32_tab[(crc32val ^ s[i]) & 0xff] ^ + (crc32val >> 8); + } + return crc32val; +} + +/* + * Hashing function for a string + */ +unsigned int hashmap_hash_int(hashmap_map * m, char* keystring){ + + unsigned long key = crc32((unsigned char*)(keystring), strlen(keystring)); + + /* Robert Jenkins' 32 bit Mix Function */ + key += (key << 12); + key ^= (key >> 22); + key += (key << 4); + key ^= (key >> 9); + key += (key << 10); + key ^= (key >> 2); + key += (key << 7); + key ^= (key >> 12); + + /* Knuth's Multiplicative Method */ + key = (key >> 3) * 2654435761; + + return key % m->table_size; +} + +/* + * Return the integer of the location in data + * to store the point to the item, or MAP_FULL. + */ +int hashmap_hash(map_t in, char* key){ + int curr; + int i; + + /* Cast the hashmap */ + hashmap_map* m = (hashmap_map *) in; + + /* If full, return immediately */ + if(m->size >= (m->table_size/2)) return MAP_FULL; + + /* Find the best index */ + curr = hashmap_hash_int(m, key); + + /* Linear probing */ + for(i = 0; i< MAX_CHAIN_LENGTH; i++){ + if(m->data[curr].in_use == 0) + return curr; + + if(m->data[curr].in_use == 1 && (strcmp(m->data[curr].key,key)==0)) + return curr; + + curr = (curr + 1) % m->table_size; + } + + return MAP_FULL; +} + +/* + * Doubles the size of the hashmap, and rehashes all the elements + */ +int hashmap_rehash(map_t in){ + int i; + int old_size; + hashmap_element* curr; + + /* Setup the new elements */ + hashmap_map *m = (hashmap_map *) in; + hashmap_element* temp = (hashmap_element *) + calloc(2 * m->table_size, sizeof(hashmap_element)); + if(!temp) return MAP_OMEM; + + /* Update the array */ + curr = m->data; + m->data = temp; + + /* Update the size */ + old_size = m->table_size; + m->table_size = 2 * m->table_size; + m->size = 0; + + /* Rehash the elements */ + for(i = 0; i < old_size; i++){ + int status; + + if (curr[i].in_use == 0) + continue; + + status = hashmap_put(m, curr[i].key, curr[i].data); + if (status != MAP_OK) + return status; + } + + free(curr); + + return MAP_OK; +} + +/* + * Add a pointer to the hashmap with some key + */ +int hashmap_put(map_t in, char* key, any_t value){ + int index; + hashmap_map* m; + + /* Cast the hashmap */ + m = (hashmap_map *) in; + + /* Find a place to put our value */ + index = hashmap_hash(in, key); + while(index == MAP_FULL){ + if (hashmap_rehash(in) == MAP_OMEM) { + return MAP_OMEM; + } + index = hashmap_hash(in, key); + } + + /* Set the data */ + m->data[index].data = value; + m->data[index].key = key; + m->data[index].in_use = 1; + m->size++; + + return MAP_OK; +} + +/* + * Get your pointer out of the hashmap with a key + */ +int hashmap_get(map_t in, char* key, any_t *arg){ + int curr; + int i; + hashmap_map* m; + + /* Cast the hashmap */ + m = (hashmap_map *) in; + + /* Find data location */ + curr = hashmap_hash_int(m, key); + + /* Linear probing, if necessary */ + for(i = 0; idata[curr].in_use; + if (in_use == 1){ + if (strcmp(m->data[curr].key,key)==0){ + *arg = (((m->data)[curr]).data); + + return MAP_OK; + } + } + + + curr = (curr + 1) % m->table_size; + } + + *arg = NULL; + + /* Not found */ + return MAP_MISSING; +} + +/* + * Iterate the function parameter over each element in the hashmap. The + * additional any_t argument is passed to the function as its first + * argument and the hashmap element is the second. + */ +int hashmap_iterate(map_t in, PFany f, any_t item) { + int i; + + /* Cast the hashmap */ + hashmap_map* m = (hashmap_map*) in; + + /* On empty hashmap, return immediately */ + if (hashmap_length(m) <= 0) + return MAP_MISSING; + + /* Linear probing */ + for(i = 0; i< m->table_size; i++) + if(m->data[i].in_use != 0) { + any_t data = (any_t) (m->data[i].data); + int status = f(item, data); + if (status != MAP_OK) { + return status; + } + } + + return MAP_OK; +} + +typedef int(*PFany2)(any_t,any_t,any_t); + +int hashmap_foreach(map_t in, PFany2 f, any_t item) { + int i; + + /* Cast the hashmap */ + hashmap_map* m = (hashmap_map*) in; + + /* On empty hashmap, return immediately */ + if (hashmap_length(m) <= 0) + return MAP_MISSING; + + /* Linear probing */ + for(i = 0; i< m->table_size; i++) { + if(m->data[i].in_use != 0) { + any_t data = (any_t) (m->data[i].data); + int status = f(item, m->data[i].key , data); + if (status != MAP_OK) { + return status; + } + } + } + + return MAP_OK; +} + +/* + * Remove an element with that key from the map + */ +int hashmap_remove(map_t in, char* key){ + int i; + int curr; + hashmap_map* m; + + /* Cast the hashmap */ + m = (hashmap_map *) in; + + /* Find key */ + curr = hashmap_hash_int(m, key); + + /* Linear probing, if necessary */ + for(i = 0; idata[curr].in_use; + if (in_use == 1){ + if (strcmp(m->data[curr].key,key)==0){ + /* Blank out the fields */ + m->data[curr].in_use = 0; + m->data[curr].data = NULL; + m->data[curr].key = NULL; + + /* Reduce the size */ + m->size--; + return MAP_OK; + } + } + curr = (curr + 1) % m->table_size; + } + + /* Data not found */ + return MAP_MISSING; +} + +/* Deallocate the hashmap */ +void hashmap_free(map_t in){ + hashmap_map* m = (hashmap_map*) in; + free(m->data); + free(m); +} + +/* Return the length of the hashmap */ +int hashmap_length(map_t in){ + hashmap_map* m = (hashmap_map *) in; + if(m != NULL) return m->size; + else return 0; +} diff --git a/src/plugins/dowse/ip2mac.c b/src/plugins/dowse/ip2mac.c new file mode 100644 index 0000000..41fe251 --- /dev/null +++ b/src/plugins/dowse/ip2mac.c @@ -0,0 +1,141 @@ +/* Dowse - ip2mac translation function + * + * (c) Copyright 2016-2024 Dyne.org foundation, Amsterdam + * Written by Nicola Rossi + * Denis Roio + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define IP2MAC_ERROR (1) +#define IP2MAC_RESULT_OK (0) + +int convert_from_ipv4(char *ipaddr_value, char *mac_addr); +int convert_from_ipv6(char *ipaddr_value, char *mac_addr); + +void ethernet_mactoa(struct sockaddr *addr, char*buff) { + + unsigned char *ptr = (unsigned char *) addr->sa_data; + + sprintf(buff, "%02x:%02x:%02x:%02x:%02x:%02x", (ptr[0] & 0377), + (ptr[1] & 0377), (ptr[2] & 0377), (ptr[3] & 0377), (ptr[4] & 0377), + (ptr[5] & 0377)); +} + +int ip4_derive_mac(plugin_data_t *data) { + struct arpreq areq; + struct sockaddr_in *sin; + struct sockaddr_in * p; + char buf[256]; + + /* Make the ARP request. */ + memset(&areq, 0, sizeof(areq)); + sin = (struct sockaddr_in *) &areq.arp_pa; + sin->sin_family = AF_INET; + + sin->sin_addr = data->ip4_addr; + sin = (struct sockaddr_in *) &areq.arp_ha; + sin->sin_family = ARPHRD_ETHER; + + strncpy(areq.arp_dev, data->interface, 15); + + if (ioctl(data->sock, SIOCGARP, (caddr_t) &areq) == -1) { + warn("Error requesting ARP for IP [%s] on device [%s]: %s", + data->ip4, data->interface, strerror(errno)); + return 1; + } + + p = (struct sockaddr_in *) &(areq.arp_pa); + + inet_ntop(AF_INET, &(p->sin_addr), buf, sizeof(buf)); + + ethernet_mactoa(&areq.arp_ha, data->mac); + + return 0; +} + +int convert_from_ipv6(char *ipaddr_value, char *mac_addr) { + int socket_fd; + struct arpreq areq; + struct sockaddr_in6 *sin; + struct in6_addr ipaddr; + + /* Get an internet domain socket. */ + if ((socket_fd = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { + err("Sorry but during IP-ARP conversion I cannot open a socket [%s]", + strerror(errno)); + return (1); + } + + /* Make the ARP request. */ + memset(&areq, 0, sizeof(areq)); + sin = (struct sockaddr_in6 *) &areq.arp_pa; + sin->sin6_family = AF_INET6; + + if (inet_pton(AF_INET6, ipaddr_value, &ipaddr) == 0) { + err( + "Sorry but during IP-ARP conversion I cannot execute inet_aton(%s) due to (%s)", + ipaddr_value, strerror(errno)); + + close(socket_fd); + return (1); + } + + sin->sin6_addr = ipaddr; + sin = (struct sockaddr_in6 *) &areq.arp_ha; + sin->sin6_family = ARPHRD_ETHER; + + /* TODO definizione di device su cui e' attestata webui */ + const char *dev = getenv("interface"); + if (dev == NULL) { + dev = "lo"; + } + + strncpy(areq.arp_dev, dev, 15); + + if (ioctl(socket_fd, SIOCGARP, (caddr_t) &areq) == -1) { + err( + "-- Error: unable to make ARP request for IP [%s], error on device [%s] due to [%s]", + ipaddr_value, dev, strerror(errno)); + return (1); + } + char buf[256]; + struct sockaddr_in6 * p; + p = (struct sockaddr_in6 *) &(areq.arp_pa); + inet_ntop(AF_INET6, &(p->sin6_addr), buf, sizeof(buf)); + + ethernet_mactoa(&areq.arp_ha, mac_addr); + + func("Conversion form %s -> %s\n", ipaddr_value, mac_addr); + close(socket_fd); + return 0; +} diff --git a/src/plugins/dowse/log.c b/src/plugins/dowse/log.c new file mode 100644 index 0000000..4696d00 --- /dev/null +++ b/src/plugins/dowse/log.c @@ -0,0 +1,160 @@ +/* Dowse - logging lib + * + * (c) Copyright 2016-2024 Dyne.org foundation, Amsterdam + * Written by Denis Roio aka jaromil + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* #include */ +#include "dowse.h" + +#define ANSI_COLOR_RED "\x1b[31m" +#define ANSI_COLOR_GREEN "\x1b[32m" +#define ANSI_COLOR_YELLOW "\x1b[33m" +#define ANSI_COLOR_BLUE "\x1b[34m" +#define ANSI_COLOR_MAGENTA "\x1b[35m" +#define ANSI_COLOR_CYAN "\x1b[36m" +#define ANSI_COLOR_RESET "\x1b[0m" + +#include + +redisContext *log_redis = NULL;//connect_redis("127.0.0.1", 6379, 0); +int logredis_retry_to_connect=1; +ssize_t nop; + +void func(const char *fmt, ...) { +#if (DEBUG==1) + va_list args; + + char msg[256]; + size_t len; + + va_start(args, fmt); + + vsnprintf(msg, sizeof(msg), fmt, args); + len = strlen(msg); + nop = write(2, ANSI_COLOR_BLUE " [D] " ANSI_COLOR_RESET, 5+5+4); + nop = write(2, msg, len); + nop = write(2, "\n", 1); + fsync(2); + + va_end(args); + + // toredis("DEBUG", msg); +#endif + return; +} + +void _minimal_err(char *msg,int sizeof_msg,const char *fmt, ...) { + size_t len; + + va_list args; + va_start(args, fmt); + + vsnprintf(msg, sizeof_msg, fmt, args); + len = strlen(msg); + nop = write(2, ANSI_COLOR_RED " [!] " ANSI_COLOR_RESET, 5+5+4); + nop = write(2, msg, len); + nop = write(2, "\n", 1); + fsync(2); + + va_end(args); + +} + + +void err(const char *fmt, ...) { + va_list args; + char msg[256]; + va_start(args, fmt); + _minimal_err(msg,sizeof(msg),fmt,args); + va_end(args); + // toredis("ERROR", msg); + +} + + +void notice(const char *fmt, ...) { + va_list args; + + char msg[256]; + size_t len; + + va_start(args, fmt); + + vsnprintf(msg, sizeof(msg), fmt, args); + len = strlen(msg); + nop = write(2, ANSI_COLOR_GREEN " (*) " ANSI_COLOR_RESET, 5+5+4); + nop = write(2, msg, len); + nop = write(2, "\n", 1); + fsync(2); + + va_end(args); + + // toredis("NOTICE", msg); +} + + +void act(const char *fmt, ...) { + va_list args; + + char msg[256]; + size_t len; + + va_start(args, fmt); + + vsnprintf(msg, sizeof(msg), fmt, args); + len = strlen(msg); + nop = write(2, " . ", 5); + nop = write(2, msg, len); + nop = write(2, "\n", 1); + fsync(2); + + va_end(args); + + // toredis("ACT", msg); +} + + + +void warn(const char *fmt, ...) { + va_list args; + + char msg[256]; + size_t len; + + va_start(args, fmt); + + vsnprintf(msg, sizeof(msg), fmt, args); + len = strlen(msg); + nop = write(2, ANSI_COLOR_YELLOW " (*) " ANSI_COLOR_RESET, 5+5+4); + nop = write(2, msg, len); + nop = write(2, "\n", 1); + fsync(2); + + va_end(args); + + // toredis("WARN", msg); +} diff --git a/src/plugins/dowse/redis.c b/src/plugins/dowse/redis.c new file mode 100644 index 0000000..9292bff --- /dev/null +++ b/src/plugins/dowse/redis.c @@ -0,0 +1,82 @@ +/* Dowse - hiredis helpers + * + * (c) Copyright 2016 Dyne.org foundation, Amsterdam + * Written by Denis Roio aka jaromil + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include + +int okredis(redisContext *r, redisReply *res) { + if(!res) { + err("redis error: %s", r->errstr); + return(0); + } else if( res->type == REDIS_REPLY_ERROR ) { + err("redis error: %s", res->str); + return(0); + } else { + return(1); + } +} + +redisReply *cmd_redis(redisContext *redis, const char *format, ...) { + va_list args; + + redisReply *res; + char command[512]; + + va_start(args, format); + vsnprintf(command, 511, format, args); + res = redisCommand(redis, command); + va_end(args); + + if ( okredis(redis, res) ) { + return res; + } else { + return NULL; + } +} + + +redisContext *connect_redis(int db) { + + redisContext *rx = NULL; + int const port = 6379; + struct timeval timeout = { 1, 500 }; + + act ("Connecting to redis on port %u", port); + + rx = redisConnectWithTimeout("127.0.0.1", port, timeout); + + if (!rx) { + err("Connection error: can't allocate redis context"); + return NULL; + } + + + if(rx->err) { + err("Redis connection error: %s", rx->errstr); + redisFree(rx); + return NULL; + } + + return rx; +}