////////////////////////////////////////////////////////////////////// // Traffic Montor Control HTTP server // Written by Jonathan A. Foster // Started August 13th, 2021 // Copyright JF Possibilities, Inc. All rights reserved. // // Provide a control panel to manage which domains we want to watch, // ignore and block. ////////////////////////////////////////////////////////////////////// // TODO: put the note fields to use (dns,dns_wild) // TODO: Any purpose in wild-card selection "accepted" host names? #include #include #include #include /// C++CMS/DB /// #include #include #include #include #include #include #include #include /// Our app /// #include "../strutil.h" #include "data.h" /// Build flags /// // TODO: better application of C++CMS booster logging //#define DEBUGGIN ////////////////////////////////////////////////////////////////////// // DNS wild card matcher ////////////////////////////////////////////////////////////////////// bool dns_wild_match(const StringList &wilds, const std::string &host) { for(StringList::const_iterator i=wilds.begin(); i!=wilds.end(); i++) { if(host.length()length()) continue; if(host==*i) return true; if(host.substr(host.length()-i->length()+1)=="."+*i) return true; } return false; } ////////////////////////////////////////////////////////////////////// // C++CMS App to manage the state of all known domain names. ////////////////////////////////////////////////////////////////////// const std::string filter_titles[] = { "Undecided", "Accepted", "Blocked" }; const std::string actions[] = { "Reset", "Accept", "Block" }; std::string root_uri = ""; struct app: public cppcms::application { std::auto_ptr sql; int items_per_page; std::string numlist(const std::string &field) { std::string r; const cppcms::http::request::form_type &POST = request().post(); for( cppcms::http::request::form_type::const_iterator it = POST.find(field); it!=POST.end() && it->first==field; it++ ) { if(it->second!="") { if(r!="") r+=","; r+="'"+sql->escape(it->second)+"'"; } } return r; } /// This looks at new log entries and creates missing DNS entries. /// This should be run periodically. Right now we run just prior /// displaying a "list" page. This could be used in a CRON job... void catchup() { std::string s; StringList list; StringList wild; /// Read list of wild-card blocks /// cppdb::result r = *sql << "SELECT name FROM dns_wild WHERE status=?" << Domain::blocked; while(r.next()) { r >> s; wild.push_back(s); } /// Auto-add unknown domains to DNS list /// // Find all connections not recorded in the DNS table r = *sql << "SELECT c.them_name " "FROM connections c LEFT OUTER JOIN dns ON c.them_name=dns.name " "WHERE c.them_name<>'' AND dns.name IS NULL " "GROUP BY c.them_name"; while(r.next()) { r >> s; list.push_back(s); } // add them if(!list.empty()) { cppdb::statement q = *sql << "INSERT INTO dns (name, status) VALUES (?,?)"; for(StringList::iterator i=list.begin(); i!=list.end(); i++) { q.reset(); // If blocked by wild card add it to the blocked list otherwise its // undecided. q << *i << (Domain::blocked*dns_wild_match(wild, *i)) << cppdb::exec; } } } void list(Domain::STATUS sid) { int i; std::string s; DomainList c; cppdb::result r; // TODO: put this someplace else? /// Form processing /// if(request().request_method()=="POST") { std::string op = request().post("op"); // nothing to do without a valid "op" if(op=="0" || op=="1" || op=="2") { // eliminate NOP busywork if((s=numlist("id"))!="" && sid!=atoi(op.c_str())) { *sql << "UPDATE dns SET status="+op+" WHERE name IN ("+s+")" << cppdb::exec; } if(s=="*" || s=="*.") { c.error = "'*' and '*.' are not acceptable."; } else if((s=request().post("domain"))!="") { // wild card block handling if(s.substr(0,2)=="*.") { s = s.substr(2); if(op=="2") *sql << "INSERT INTO dns_wild (name,status) VALUES (?,?)" << s << op << cppdb::exec; else c.error = "Wild cards can only be used to block domains. " "This has been treated as regular domain prefix " "search."; } // regardless move all existing matches to the specified status. *sql << "UPDATE dns SET status=? WHERE name=? OR name LIKE ?" << op << s << ("%."+s) << cppdb::exec; } } } /// Update DB with new log data /// catchup(); /// Produce list of names of the desired STATUS /// c.filter = filter_titles[sid]; s = request().get("pg"); if(s=="") c.page = 1; else c.page = atoi(s.c_str()); if(c.page < 1 || c.page > 999999) c.page = 1; r = *sql << "SELECT name, decided, status FROM dns WHERE status=? " "LIMIT "+str((c.page-1)*items_per_page)+","+str(items_per_page) << sid; for(i = c.list.size(); r.next(); i++) { c.list.resize(i+1); r >> c.list[i].name >> c.list[i].decided >> c.list[i].status; } r = *sql << "SELECT count(*) FROM dns WHERE status=?" << sid << cppdb::row; r >> c.count; c.page_size = items_per_page; c.pages = (c.count+items_per_page-1)/items_per_page; render("mainskin", "domain_list", c); } void undecided() { list(Domain::undecided); } void accepted() { list(Domain::accepted ); } void blocked() { list(Domain::blocked ); } app(cppcms::service &s): cppcms::application(s), items_per_page(50) { #ifdef DEBUGGIN std::cerr << "spawning app object" << std::endl; #endif sql.reset(new cppdb::session()); sql->open(settings().get("trafficctrl.db")); mapper().root(root_uri); mapper().assign("blocked", "/blocked"); dispatcher().assign("/blocked/?", &app::blocked, this); mapper().assign("accepted", "/accepted"); dispatcher().assign("/accepted/?", &app::accepted, this); mapper().assign(""); dispatcher().assign("/?", &app::undecided, this); } // logging void main(const std::string url) { #ifdef DEBUGGIN std::cerr << "request: " << url << '\n' << " INFO: " << request().path_info() << '\n' << " SCRIPT: " << request().script_name() << '\n' << std::endl; #endif cppcms::application::main(url); } }; ////////////////////////////////////////////////////////////////////// // main() - Launch C++CMS with my test app on "/" ////////////////////////////////////////////////////////////////////// int main(int argc, char **args) { // create server object cppcms::service srv(argc, args); // Get root URI from configuration file. root_uri = srv.settings().get("trafficctrl.root_uri", ""); // Mount our app in the server srv.applications_pool().mount( cppcms::create_pool(), cppcms::mount_point(root_uri) ); // Serve it! // TODO: log crashes. #ifdef DEBUGGIN std::cerr << "Launching" << std::endl; #endif srv.run(); return 0; }