/* * * (C) 2013-22 - ntop.org * * * 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 * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include "ntop_includes.h" #ifdef __linux__ #include #define EVENT_SIZE ( sizeof (struct inotify_event) ) #define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) #endif #ifdef WIN32 #include /* SHGetFolderPath() */ #else #include #include #endif Ntop *ntop; static const char* dirs[] = { NULL, /* Populated at runtime */ NULL, /* Populated at runtime for WIN32 builds */ CONST_ALT_INSTALL_DIR, CONST_ALT2_INSTALL_DIR, #ifndef WIN32 CONST_DEFAULT_INSTALL_DIR, /* Last is the specified with ./configure --prefix , defaulting to /usr/local */ #endif NULL }; extern struct keyval string_to_replace[]; /* LuaEngine.cpp */ /* ******************************************* */ Ntop::Ntop(const char *appName) { // WTF: it's weird why do you want a global instance of ntop. ntop = this; globals = new (std::nothrow) NtopGlobals(); extract = new (std::nothrow) TimelineExtract(); address = new (std::nothrow) AddressResolution(); offline = false; pa = NULL; #ifdef WIN32 myTZname = strdup(_tzname[0] ? _tzname[0] : "CET"); #else myTZname = strdup(tzname[0] ? tzname[0] : "CET"); #endif custom_ndpi_protos = NULL; prefs = NULL, redis = NULL; #ifndef HAVE_NEDGE export_interface = NULL; zmqPublisher = NULL; #endif trackers_automa = NULL; num_cpus = -1; num_defined_interfaces = 0; num_dump_interfaces = 0; iface = NULL; start_time = last_modified_static_file_epoch = 0, epoch_buf[0] = '\0'; /* It will be initialized by start() */ last_stats_reset = 0; old_iface_to_purge = NULL; setZoneInfo(); /* Checks loader */ flowChecksReloadInProgress = true; /* Lazy, will be reloaded the first time this condition is evaluated */ hostChecksReloadInProgress = true; flow_checks_loader = NULL; host_checks_loader = NULL; /* Flow alerts exclusions */ #ifdef NTOPNG_PRO alertExclusionsReloadInProgress = true; alert_exclusions = alert_exclusions_shadow = NULL; #endif /* Host Pools reload - Interfaces initialize their pools inside the constructor */ hostPoolsReloadInProgress = false; httpd = NULL, geo = NULL, mac_manufacturers = NULL; memset(&cpu_stats, 0, sizeof(cpu_stats)); cpu_load = 0; system_interface = NULL; purgeLoop_started = false; interfacesShuttedDown = false; #ifndef WIN32 cping = NULL, default_ping = NULL; #endif privileges_dropped = false; can_send_icmp = Utils::isPingSupported(); for(int i = 0; i < CONST_MAX_NUM_NETWORKS; i++) local_network_names[i] = local_network_aliases[i] = NULL; #ifndef WIN32 if(can_send_icmp) { cping = new (std::nothrow) ContinuousPing(); default_ping = new (std::nothrow)Ping(NULL /* System interface */); } #endif internal_alerts_queue = new (std::nothrow) FifoSerializerQueue(INTERNAL_ALERTS_QUEUE_SIZE); resolvedHostsBloom = new (std::nothrow) Bloom(NUM_HOSTS_RESOLVED_BITS); #ifdef WIN32 if(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, working_dir) != S_OK) { strncpy(working_dir, "C:\\Windows\\Temp\\ntopng", sizeof(working_dir)); // Fallback: it should never happen working_dir[sizeof(working_dir) - 1] = '\0'; } else { int l = strlen(working_dir); snprintf(&working_dir[l], sizeof(working_dir), "%s", "\\ntopng"); } // Get the full path and filename of this program if(GetModuleFileName(NULL, startup_dir, sizeof(startup_dir)) == 0) { startup_dir[0] = '\0'; } else { for(int i=(int)strlen(startup_dir)-1; i>0; i--) { if(startup_dir[i] == '\\') { startup_dir[i] = '\0'; break; } } } snprintf(install_dir, sizeof(install_dir), "%s", startup_dir); dirs[0] = startup_dir; dirs[1] = install_dir; #else /* Note: working_dir folder will be created lazily, avoid creating it now */ if (Utils::dir_exists(CONST_OLD_DEFAULT_DATA_DIR)) /* keep using the old dir */ snprintf(working_dir, sizeof(working_dir), CONST_OLD_DEFAULT_DATA_DIR); else snprintf(working_dir, sizeof(working_dir), CONST_DEFAULT_DATA_DIR); //umask(0); if(getcwd(startup_dir, sizeof(startup_dir)) == NULL) ntop->getTrace()->traceEvent(TRACE_ERROR, "Occurred while checking the current directory (errno=%d)", errno); dirs[0] = startup_dir; install_dir[0] = '\0'; for(int i = 0; i < (int)COUNT_OF(dirs); i++) { if(dirs[i]) { char path[MAX_PATH+32]; struct stat statbuf; snprintf(path, sizeof(path), "%s/scripts/lua/index.lua", dirs[i]); fixPath(path); if(stat(path, &statbuf) == 0) { snprintf(install_dir, sizeof(install_dir), "%s", dirs[i]); break; } } } #endif setScriptsDir(); #ifdef NTOPNG_PRO pro = new (std::nothrow) NtopPro(); #else pro = NULL; #endif #ifdef __linux__ inotify_fd = -1; #endif #ifndef HAVE_NEDGE refresh_ips_rules = false; #endif // printf("--> %s [%s]\n", startup_dir, appName); initTimezone(); ntop->getTrace()->traceEvent(TRACE_INFO, "System Timezone offset: %+ld", time_offset); initAllowedProtocolPresets(); udp_socket = Utils::openSocket(AF_INET, SOCK_DGRAM, 0, "Ntop UDP"); #ifndef WIN32 setservent(1); startupLockFile = -1; #endif } /* ******************************************* */ #ifndef WIN32 void Ntop::lockNtopInstance() { char lockPath[MAX_PATH+8]; struct flock lock; struct stat st; if((stat(working_dir, &st) != 0) || !S_ISDIR(st.st_mode)) { ntop->getTrace()->traceEvent(TRACE_DEBUG, "Working dir does not exist yet"); return; } snprintf(lockPath, sizeof(lockPath), "%s/.lock", working_dir); lock.l_type = F_WRLCK; /* read/write (exclusive versus shared) lock */ lock.l_whence = SEEK_SET; /* base for seek offsets */ lock.l_start = 0; /* 1st byte in file */ lock.l_len = 0; /* 0 here means 'until EOF' */ lock.l_pid = getpid(); /* process id */ if((startupLockFile = open(lockPath, O_RDWR | O_CREAT, 0666)) < 0) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Unable to open lock file %s [%s]", lockPath, strerror(errno)); exit(EXIT_FAILURE); } if(fcntl(startupLockFile, F_SETLK, &lock) < 0) { /** F_SETLK doesn't block, F_SETLKW does **/ ntop->getTrace()->traceEvent(TRACE_ERROR, "Another ntopng instance is running..."); exit(EXIT_FAILURE); } } #endif /* ******************************************* */ /* Setup timezone differences We call it all the time as daylight can change during the night and thus we need to have it "fresh" */ void Ntop::initTimezone() { #ifdef WIN32 time_offset = -_timezone; #else time_t t = time(NULL); struct tm *l = localtime(&t); time_offset = l->tm_gmtoff; #endif } /* ******************************************* */ Ntop::~Ntop() { int num_local_networks = local_network_tree.getNumAddresses(); for(int i = 0; i < num_local_networks; i++) { if (local_network_names[i] != NULL) free(local_network_names[i]); if (local_network_aliases[i] != NULL) free(local_network_aliases[i]); } if(httpd) delete httpd; /* Stop the http server before tearing down network interfaces */ if(purgeLoop_started) pthread_join(purgeLoop, NULL); for(int i = 0; i < num_defined_interfaces; i++) { if(iface[i]) { delete iface[i]; iface[i] = NULL; } } if(zoneinfo) free(zoneinfo); delete []iface; if(system_interface) delete system_interface; if(extract) delete extract; #ifndef WIN32 if(cping) delete cping; if(default_ping) delete default_ping; for(std::map::iterator it = ping.begin(); it != ping.end(); ++it) delete it->second; #endif Utils::closeSocket(udp_socket); if(trackers_automa) ndpi_free_automa(trackers_automa); if(custom_ndpi_protos) free(custom_ndpi_protos); if(old_iface_to_purge) delete old_iface_to_purge; delete address; if(pa) delete pa; if(geo) delete geo; if(mac_manufacturers) delete mac_manufacturers; #ifndef HAVE_NEDGE if(zmqPublisher) delete zmqPublisher; #endif #ifdef NTOPNG_PRO if(pro) delete pro; if(alert_exclusions) delete alert_exclusions; if(alert_exclusions_shadow) delete alert_exclusions_shadow; #endif #if defined(HAVE_CLICKHOUSE) && defined(HAVE_MYSQL) if(clickhouseImport) delete clickhouseImport; #endif if(resolvedHostsBloom) delete resolvedHostsBloom; delete internal_alerts_queue; if(redis) { delete redis; redis = NULL; } if(prefs) { delete prefs; prefs = NULL; } if(globals) { delete globals; globals = NULL; } if(flow_checks_loader) delete flow_checks_loader; if(host_checks_loader) delete host_checks_loader; if(myTZname) free(myTZname); #ifdef __linux__ if(inotify_fd > 0) close(inotify_fd); #endif } /* ******************************************* */ void Ntop::registerPrefs(Prefs *_prefs, bool quick_registration) { char value[32]; struct stat buf; prefs = _prefs; if(!quick_registration) { if(stat(prefs->get_data_dir(), &buf) || (!(buf.st_mode & S_IFDIR)) /* It's not a directory */ // || (!(buf.st_mode & S_IWRITE)) /* It's not writable */ ) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Invalid directory %s specified", prefs->get_data_dir()); exit(-1); } if(stat(prefs->get_callbacks_dir(), &buf) || (!(buf.st_mode & S_IFDIR)) /* It's not a directory */ || (!(buf.st_mode & S_IREAD)) /* It's not readable */) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Invalid directory %s specified", prefs->get_callbacks_dir()); exit(-1); } if(prefs->get_local_networks()) { setLocalNetworks(prefs->get_local_networks()); } else { /* Add defaults */ /* http://www.networksorcery.com/enp/protocol/ip/multicast.htm */ setLocalNetworks((char*)CONST_DEFAULT_LOCAL_NETS); } } /* Initialize redis and populate some default values */ Utils::initRedis(&redis, prefs->get_redis_host(), prefs->get_redis_password(), prefs->get_redis_port(), prefs->get_redis_db_id(), quick_registration); if(redis) redis->setDefaults(); if(!quick_registration) { /* Initialize another redis instance for the trace of events */ ntop->getTrace()->initRedis(prefs->get_redis_host(), prefs->get_redis_password(), prefs->get_redis_port(), prefs->get_redis_db_id()); if(ntop->getRedis() == NULL) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Unable to initialize redis. Quitting..."); exit(-1); } } #ifdef NTOPNG_PRO pro->init_license(); if(!ntop->getPro()->has_unlimited_enterprise_l_license()) prefs->toggle_dump_flows_direct(false); #endif if(quick_registration) return; checkReloadAlertExclusions(); system_interface = new (std::nothrow) NetworkInterface(SYSTEM_INTERFACE_NAME, SYSTEM_INTERFACE_NAME); /* License check could have increased the number of interfaces available */ resetNetworkInterfaces(); /* Read the old last_stats_reset */ if(ntop->getRedis()->get((char*)LAST_RESET_TIME, value, sizeof(value)) >= 0) last_stats_reset = atol(value); /* Now we can enable the periodic activities */ pa = new (std::nothrow) PeriodicActivities(); #if defined(HAVE_CLICKHOUSE) && defined(HAVE_MYSQL) if(prefs->do_dump_flows_on_clickhouse()) clickhouseImport = new (std::nothrow) ClickHouseImport(); else clickhouseImport = NULL; #endif redis->setInitializationComplete(); } /* ******************************************* */ void Ntop::resetNetworkInterfaces() { if(iface) delete []iface; if((iface = new (std::nothrow) NetworkInterface*[MAX_NUM_DEFINED_INTERFACES]()) == NULL) throw "Not enough memory"; ntop->getTrace()->traceEvent(TRACE_INFO, "Interfaces Available: %u", MAX_NUM_DEFINED_INTERFACES); } /* ******************************************* */ void Ntop::createExportInterface() { #ifndef HAVE_NEDGE if(prefs->get_export_endpoint()) export_interface = new (std::nothrow) ExportInterface(prefs->get_export_endpoint()); else export_interface = NULL; #endif } /* ******************************************* */ void Ntop::start() { struct timeval begin, end; u_long usec_diff; char daybuf[64], buf[128]; time_t when = time(NULL); int i = 0; getTrace()->traceEvent(TRACE_NORMAL, "Welcome to %s %s v.%s (%s) - (C) 1998-22 ntop.org", #ifdef HAVE_NEDGE "ntopng edge", #else "ntopng", #endif PACKAGE_MACHINE, PACKAGE_VERSION, NTOPNG_GIT_RELEASE); if(PACKAGE_OS[0] != '\0') getTrace()->traceEvent(TRACE_NORMAL, "Built on %s", PACKAGE_OS); last_modified_static_file_epoch = start_time = time(NULL); snprintf(epoch_buf, sizeof(epoch_buf), "%u", (u_int32_t)start_time); string_to_replace[i].key = CONST_HTTP_PREFIX_STRING, string_to_replace[i].val = ntop->getPrefs()->get_http_prefix(); i++; string_to_replace[i].key = CONST_NTOP_STARTUP_EPOCH, string_to_replace[i].val = ntop->getStartTimeString(); i++; string_to_replace[i].key = CONST_NTOP_PRODUCT_NAME, string_to_replace[i].val = #ifdef HAVE_NEDGE ntop->getPro()->get_product_name() #else (char*)"ntopng" #endif ; i++; string_to_replace[i].key = NULL, string_to_replace[i].val = NULL; strftime(daybuf, sizeof(daybuf), CONST_DB_DAY_FORMAT, localtime(&when)); snprintf(buf, sizeof(buf), "ntopng.%s.hostkeys", daybuf); #ifdef NTOPNG_PRO if(!pro->forced_community_edition()) pro->printLicenseInfo(); am.init(); #endif FlowRiskAlerts::checkUndefinedRisks(); prefs->loadInstanceNameDefaults(); loadLocalInterfaceAddress(); address->startResolveAddressLoop(); system_interface->allocateStructures(); for(int i=0; iallocateStructures(); #ifdef __linux__ inotify_fd = inotify_init(); if(inotify_fd < 0) ntop->getTrace()->traceEvent(TRACE_ERROR, "inotify_init failed[%d]: %s", errno, strerror(errno)); else { uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY; char path[MAX_PATH]; /* Watch some directories. TODO: recursive watch */ snprintf(path, sizeof(path), "%s/system", ntop->get_callbacks_dir()); inotify_add_watch(inotify_fd, path, mask); snprintf(path, sizeof(path), "%s/interface", ntop->get_callbacks_dir()); inotify_add_watch(inotify_fd, path, mask); snprintf(path, sizeof(path), "%s/lua/modules", prefs->get_scripts_dir()); inotify_add_watch(inotify_fd, path, mask); #ifdef NTOPNG_PRO snprintf(path, sizeof(path), "%s/lua/pro/modules", prefs->get_scripts_dir()); inotify_add_watch(inotify_fd, path, mask); #endif } #endif /* Note: must start periodic activities loop only *after* interfaces have been * completely initialized. * * Note: this will also run the startup.lua script sequentially. * After this call, startup.lua has completed. */ pa->startPeriodicActivitiesLoop(); if(get_HTTPserver()) get_HTTPserver()->start_accepting_requests(); #ifdef HAVE_NEDGE /* TODO: enable start/stop of the captive portal webserver directly from Lua */ if(get_HTTPserver() && prefs->isCaptivePortalEnabled()) get_HTTPserver()->startCaptiveServer(); #endif checkReloadHostPools(); checkReloadFlowChecks(); checkReloadHostChecks(); for(int i=0; istartPacketPolling(); startPurgeLoop(); // sleep(2); for(int i=0; icheckPointCounters(true); /* Reset drop counters */ /* Align to the next 5-th second of the clock to make sure housekeeping starts alinged (and remains aligned when the housekeeping frequency is a multiple of 5 seconds) */ gettimeofday(&begin, NULL); _usleep((5 - begin.tv_sec % 5) * 1e6 - begin.tv_usec); Utils::setThreadName("ntopng-main"); while((!globals->isShutdown()) && (!globals->isShutdownRequested())) { const u_int32_t nap_usec = ntop->getPrefs()->get_housekeeping_frequency() * 1e6; gettimeofday(&begin, NULL); runHousekeepingTasks(); /* Check if it is time to signal the shutdown, depending on the configuration. NOTE: Shutdown when done is only meaningful for pcap-dump interfaces when the file has been read. */ checkShutdownWhenDone(); gettimeofday(&end, NULL); usec_diff = Utils::usecTimevalDiff(&end, &begin); if(usec_diff >= nap_usec) ntop->getTrace()->traceEvent(TRACE_NORMAL, "Houkeeping activities (main loop) took %.3fs", (float)usec_diff/1e6); #ifdef __linux__ if (inotify_fd > 0) { while (usec_diff < nap_usec) { int maxfd = 0; fd_set rset; struct timeval tv; int rc; //ntop->getTrace()->traceEvent(TRACE_NORMAL, "Sleeping %i microsecods", (nap_usec - usec_diff)); FD_ZERO(&rset); FD_SET(inotify_fd, &rset); maxfd = inotify_fd; tv.tv_sec = 0, tv.tv_usec = (nap_usec - usec_diff); rc = select(maxfd + 1, &rset, NULL, NULL, &tv); if(rc > 0) { if(FD_ISSET(inotify_fd, &rset)) { char buffer[EVENT_BUF_LEN]; /* Consume the event */ rc = read(inotify_fd, buffer, sizeof(buffer)); if(rc < 0) ntop->getTrace()->traceEvent(TRACE_DEBUG, "read() returned %d", rc); ntop->getTrace()->traceEvent(TRACE_DEBUG, "Directory changed"); } } gettimeofday(&end, NULL); usec_diff = Utils::usecTimevalDiff(&end, &begin); if (rc < 0) break; /* fall back to sleeping to avoid spinning on a failing select */ } } #endif if(usec_diff < nap_usec) _usleep(nap_usec-usec_diff); } } /* ******************************************* */ bool Ntop::isLocalAddress(int family, void *addr, int16_t *network_id, u_int8_t *network_mask_bits) { u_int8_t nmask_bits; *network_id = this->localNetworkLookup(family, addr, &nmask_bits); if(*network_id != -1 && network_mask_bits) *network_mask_bits = nmask_bits; return(((*network_id) == -1) ? false : true); }; /* ******************************************* */ void Ntop::getLocalNetworkIp(int16_t local_network_id, IpAddress **network_ip, u_int8_t *network_prefix) { char *network_address, *slash; *network_ip = new (std::nothrow) IpAddress(); *network_prefix = 0; if (local_network_id >= 0) network_address = strdup(getLocalNetworkName(local_network_id)); else network_address = strdup((char*)"0.0.0.0/0"); /* Remote networks */ if((slash = strchr(network_address, '/'))) { *network_prefix = atoi(slash + 1); *slash = '\0'; } if(*network_ip) (*network_ip)->set(network_address); if(network_address) free(network_address); }; /* ******************************************* */ #ifdef WIN32 #include #include #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) /* Note: could also use malloc() and free() */ char* Ntop::getIfName(int if_id, char *name, u_int name_len) { // Declare and initialize variables PIP_INTERFACE_INFO pInfo = NULL; ULONG ulOutBufLen = 0; DWORD dwRetVal = 0; int iReturn = 1; int i; name[0] = '\0'; // Make an initial call to GetInterfaceInfo to get // the necessary size in the ulOutBufLen variable dwRetVal = GetInterfaceInfo(NULL, &ulOutBufLen); if(dwRetVal == ERROR_INSUFFICIENT_BUFFER) { pInfo = (IP_INTERFACE_INFO *)MALLOC(ulOutBufLen); if(pInfo == NULL) { return(name); } } // Make a second call to GetInterfaceInfo to get // the actual data we need dwRetVal = GetInterfaceInfo(pInfo, &ulOutBufLen); if(dwRetVal == NO_ERROR) { for(i = 0; i < pInfo->NumAdapters; i++) { if(pInfo->Adapter[i].Index == if_id) { int j, k, begin = 0; for(j = 0, k = 0; (k < name_len) && (pInfo->Adapter[i].Name[j] != '\0'); j++) { if(begin) { if((char)pInfo->Adapter[i].Name[j] == '}') break; name[k++] = (char)pInfo->Adapter[i].Name[j]; } else if((char)pInfo->Adapter[i].Name[j] == '{') begin = 1; } name[k] = '\0'; } break; } } FREE(pInfo); return(name); } #endif /* ******************************************* */ void Ntop::loadLocalInterfaceAddress() { const int bufsize = 128; char buf[bufsize]; #ifdef WIN32 PMIB_IPADDRTABLE pIPAddrTable; DWORD dwSize = 0; DWORD dwRetVal = 0; IN_ADDR IPAddr; char buf2[bufsize]; /* Variables used to return error message */ LPVOID lpMsgBuf; // Before calling AddIPAddress we use GetIpAddrTable to get // an adapter to which we can add the IP. pIPAddrTable = (MIB_IPADDRTABLE *)MALLOC(sizeof(MIB_IPADDRTABLE)); if(pIPAddrTable) { // Make an initial call to GetIpAddrTable to get the // necessary size into the dwSize variable if(GetIpAddrTable(pIPAddrTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) { FREE(pIPAddrTable); pIPAddrTable = (MIB_IPADDRTABLE *)MALLOC(dwSize); } if(pIPAddrTable == NULL) { return; } } // Make a second call to GetIpAddrTable to get the // actual data we want if((dwRetVal = GetIpAddrTable(pIPAddrTable, &dwSize, 0)) != NO_ERROR) { if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwRetVal, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR)& lpMsgBuf, 0, NULL)) { LocalFree(lpMsgBuf); } return; } for(int ifIdx = 0; ifIdx < (int)pIPAddrTable->dwNumEntries; ifIdx++) { char name[256]; getIfName(pIPAddrTable->table[ifIdx].dwIndex, name, sizeof(name)); for(int id = 0; id < num_defined_interfaces; id++) { if((name[0] != '\0') && (strstr(iface[id]->get_name(), name) != NULL)) { u_int32_t bits = Utils::numberOfSetBits((u_int32_t)pIPAddrTable->table[ifIdx].dwMask); IPAddr.S_un.S_addr = (u_long)pIPAddrTable->table[ifIdx].dwAddr; snprintf(buf, bufsize, "%s/32", inet_ntoa(IPAddr)); local_interface_addresses.addAddress(buf); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Adding %s as IPv4 NIC addr. [%s]", buf, iface[id]->get_description()); iface[id]->addInterfaceAddress(buf); IPAddr.S_un.S_addr = (u_long)(pIPAddrTable->table[ifIdx].dwAddr & pIPAddrTable->table[ifIdx].dwMask); snprintf(buf2, bufsize, "%s/%u", inet_ntoa(IPAddr), bits); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Adding %s as IPv4 local nw [%s]", buf2, iface[id]->get_description()); addLocalNetworkList(buf2); iface[id]->addInterfaceNetwork(buf2, buf); } } } /* TODO: add IPv6 support */ if(pIPAddrTable) { FREE(pIPAddrTable); pIPAddrTable = NULL; } #else struct ifaddrs *local_addresses, *ifa; /* buf must be big enough for an IPv6 address(e.g. 3ffe:2fa0:1010:ca22:020a:95ff:fe8a:1cf8) */ char buf_orig[bufsize+32]; char net_buf[bufsize+32]; int sock = Utils::openSocket(AF_INET, SOCK_STREAM, 0, "loadLocalInterfaceAddress"); if(getifaddrs(&local_addresses) != 0) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Unable to read interface addresses"); Utils::closeSocket(sock); return; } for(ifa = local_addresses; ifa != NULL; ifa = ifa->ifa_next) { struct ifreq ifr; u_int32_t netmask; int cidr, ifId = -1; if((ifa->ifa_addr == NULL) || ((ifa->ifa_addr->sa_family != AF_INET) && (ifa->ifa_addr->sa_family != AF_INET6)) || ((ifa->ifa_flags & IFF_UP) == 0)) continue; for(int i = 0; i < num_defined_interfaces; i++) { if(strstr(iface[i]->get_name(), ifa->ifa_name)) { ifId = i; break; } } if(ifId == -1) continue; if(ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in* s4 =(struct sockaddr_in *)(ifa->ifa_addr); u_int32_t nm; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_addr.sa_family = AF_INET; strncpy(ifr.ifr_name, ifa->ifa_name, sizeof(ifr.ifr_name)-1); ioctl(sock, SIOCGIFNETMASK, &ifr); netmask = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; cidr = 0, nm = netmask; while(nm) { cidr += (nm & 0x01); nm >>= 1; } if(inet_ntop(ifa->ifa_addr->sa_family, (void *)&(s4->sin_addr), buf, sizeof(buf)) != NULL) { char buf_orig2[bufsize+32]; snprintf(buf_orig2, sizeof(buf_orig2), "%s/%d", buf, 32); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Adding %s as IPv4 interface address for %s", buf_orig2, iface[ifId]->get_name()); local_interface_addresses.addAddress(buf_orig2); iface[ifId]->addInterfaceAddress(buf_orig2); /* Set to zero non network bits */ s4->sin_addr.s_addr = htonl(ntohl(s4->sin_addr.s_addr) & ntohl(netmask)); inet_ntop(ifa->ifa_addr->sa_family, (void *)&(s4->sin_addr), buf, sizeof(buf)); snprintf(net_buf, sizeof(net_buf), "%s/%d", buf, cidr); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Adding %s as IPv4 local network for %s", net_buf, iface[ifId]->get_name()); iface[ifId]->addInterfaceNetwork(net_buf, buf_orig2); addLocalNetworkList(net_buf); } } else if(ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *s6 =(struct sockaddr_in6 *)(ifa->ifa_netmask); u_int8_t *b = (u_int8_t *)&(s6->sin6_addr); cidr = 0; for(int i=0; i<16; i++) { u_int8_t num_bits = __builtin_popcount(b[i]); if(num_bits == 0) break; cidr += num_bits; } s6 = (struct sockaddr_in6 *)(ifa->ifa_addr); if(inet_ntop(ifa->ifa_addr->sa_family,(void *)&(s6->sin6_addr), buf, sizeof(buf)) != NULL) { snprintf(buf_orig, sizeof(buf_orig), "%s/%d", buf, 128); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Adding %s as IPv6 interface address for %s", buf_orig, iface[ifId]->get_name()); local_interface_addresses.addAddresses(buf_orig); iface[ifId]->addInterfaceAddress(buf_orig); for(int i = cidr, j = 0; i > 0; i -= 8, ++j) s6->sin6_addr.s6_addr[j] &= i >= 8 ? 0xff : (u_int32_t)(( 0xffU << ( 8 - i ) ) & 0xffU ); inet_ntop(ifa->ifa_addr->sa_family,(void *)&(s6->sin6_addr), buf, sizeof(buf)); snprintf(net_buf, sizeof(net_buf), "%s/%d", buf, cidr); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Adding %s as IPv6 local network for %s", net_buf, iface[ifId]->get_name()); iface[ifId]->addInterfaceNetwork(net_buf, buf_orig); addLocalNetworkList(net_buf); } } } freeifaddrs(local_addresses); Utils::closeSocket(sock); #endif ntop->getTrace()->traceEvent(TRACE_INFO, "Local Interface Addresses (System Host)"); ntop->getTrace()->traceEvent(TRACE_INFO, "Local Networks"); } /* ******************************************* */ void Ntop::loadGeolocation() { if(geo != NULL) delete geo; geo = new (std::nothrow) Geolocation(); } /* ******************************************* */ void Ntop::loadMacManufacturers(char *dir) { if(mac_manufacturers != NULL) delete mac_manufacturers; if((mac_manufacturers = new (std::nothrow) MacManufacturers(dir)) == NULL) throw "Not enough memory"; } /* ******************************************* */ void Ntop::setWorkingDir(char *dir) { snprintf(working_dir, sizeof(working_dir), "%s", dir); removeTrailingSlash(working_dir); setScriptsDir(); }; /* ******************************************* */ void Ntop::removeTrailingSlash(char *str) { int len = (int)strlen(str)-1; if((len > 0) && ((str[len] == '/') || (str[len] == '\\'))) str[len] = '\0'; } /* ******************************************* */ void Ntop::setCustomnDPIProtos(char *path) { if(path != NULL) { if(custom_ndpi_protos != NULL) free(custom_ndpi_protos); custom_ndpi_protos = strdup(path); } } /* ******************************************* */ void Ntop::lua_periodic_activities_stats(NetworkInterface *iface, lua_State* vm) { if(pa) pa->lua(iface, vm); } /* ******************************************* */ void Ntop::lua_alert_queues_stats(lua_State* vm) { lua_newtable(vm); if(getInternalAlertsQueue()) getInternalAlertsQueue()->lua(vm, "internal_alerts_queue"); lua_pushstring(vm, "alert_queues"); lua_insert(vm, -2); lua_settable(vm, -3); } /* ******************************************* */ bool Ntop::recipients_are_empty() { return recipients.empty(); } /* ******************************************* */ bool Ntop::recipients_enqueue(AlertFifoItem *notification, AlertEntity alert_entity) { return recipients.enqueue(notification, alert_entity); } /* ******************************************* */ bool Ntop::recipient_enqueue(u_int16_t recipient_id, const AlertFifoItem* const notification) { return recipients.enqueue(recipient_id, notification); } /* ******************************************* */ bool Ntop::recipient_dequeue(u_int16_t recipient_id, AlertFifoItem *notification) { return recipients.dequeue(recipient_id, notification); } /* ******************************************* */ void Ntop::recipient_stats(u_int16_t recipient_id, lua_State* vm) { recipients.lua(recipient_id, vm); } /* ******************************************* */ time_t Ntop::recipient_last_use(u_int16_t recipient_id) { return recipients.last_use(recipient_id); } /* ******************************************* */ void Ntop::recipient_delete(u_int16_t recipient_id) { recipients.delete_recipient(recipient_id); } /* ******************************************* */ void Ntop::recipient_register(u_int16_t recipient_id, AlertLevel minimum_severity, Bitmap128 enabled_categories, Bitmap128 enabled_host_pools, Bitmap128 enabled_entities) { recipients.register_recipient(recipient_id, minimum_severity, enabled_categories, enabled_host_pools, enabled_entities); } /* ******************************************* */ void Ntop::getUsers(lua_State* vm) { char **usernames; char *username; char *key, *val; int rc, i; size_t len; lua_newtable(vm); if((rc = ntop->getRedis()->keys("ntopng.user.*.password", &usernames)) <= 0) return; if((key = (char*)malloc(CONST_MAX_LEN_REDIS_VALUE)) == NULL) return; else if((val = (char*)malloc(CONST_MAX_LEN_REDIS_VALUE)) == NULL) { free(key); return; } for(i = 0; i < rc; i++) { if(usernames[i] == NULL) goto next_username; /* safety check */ if((username = strchr(usernames[i], '.')) == NULL) goto next_username; if((username = strchr(username+1, '.')) == NULL) goto next_username; len = strlen(++username); if(len < sizeof(".password")) goto next_username; username[len - sizeof(".password") + 1] = '\0'; lua_newtable(vm); snprintf(key, CONST_MAX_LEN_REDIS_VALUE, CONST_STR_USER_FULL_NAME, username); if(ntop->getRedis()->get(key, val, CONST_MAX_LEN_REDIS_VALUE) >= 0) lua_push_str_table_entry(vm, "full_name", val); else lua_push_str_table_entry(vm, "full_name", (char*) "unknown"); snprintf(key, CONST_MAX_LEN_REDIS_VALUE, CONST_STR_USER_PASSWORD, username); if(ntop->getRedis()->get(key, val, CONST_MAX_LEN_REDIS_VALUE) >= 0) lua_push_str_table_entry(vm, "password", val); else lua_push_str_table_entry(vm, "password", (char*) "unknown"); snprintf(key, CONST_MAX_LEN_REDIS_VALUE, CONST_STR_USER_GROUP, username); if(ntop->getRedis()->get(key, val, CONST_MAX_LEN_REDIS_VALUE) >= 0) lua_push_str_table_entry(vm, "group", val); else lua_push_str_table_entry(vm, "group", (char*)NTOP_UNKNOWN_GROUP); snprintf(key, CONST_MAX_LEN_REDIS_VALUE, CONST_STR_USER_LANGUAGE, username); if(ntop->getRedis()->get(key, val, CONST_MAX_LEN_REDIS_VALUE) >= 0) lua_push_str_table_entry(vm, "language", val); else lua_push_str_table_entry(vm, "language", (char*)""); snprintf(key, CONST_MAX_LEN_REDIS_VALUE, CONST_STR_USER_ALLOW_PCAP, username); if(ntop->getRedis()->get(key, val, CONST_MAX_LEN_REDIS_VALUE) >= 0) lua_push_bool_table_entry(vm, "allow_pcap_download", true); snprintf(key, CONST_MAX_LEN_REDIS_VALUE, CONST_STR_USER_ALLOW_HISTORICAL_FLOW, username); if(ntop->getRedis()->get(key, val, CONST_MAX_LEN_REDIS_VALUE) >= 0) lua_push_bool_table_entry(vm, "allow_historical_flow", true); snprintf(key, CONST_MAX_LEN_REDIS_VALUE, CONST_STR_USER_NETS, username); if(ntop->getRedis()->get(key, val, CONST_MAX_LEN_REDIS_VALUE) >= 0) lua_push_str_table_entry(vm, CONST_ALLOWED_NETS, val); else lua_push_str_table_entry(vm, CONST_ALLOWED_NETS, (char*)""); snprintf(key, CONST_MAX_LEN_REDIS_VALUE, CONST_STR_USER_ALLOWED_IFNAME, username); if((ntop->getRedis()->get(key, val, CONST_MAX_LEN_REDIS_VALUE) >= 0) && val[0] != '\0') lua_push_str_table_entry(vm, CONST_ALLOWED_IFNAME, val); else lua_push_str_table_entry(vm, CONST_ALLOWED_IFNAME, (char*)""); snprintf(key, CONST_MAX_LEN_REDIS_VALUE, CONST_STR_USER_HOST_POOL_ID, username); if(ntop->getRedis()->get(key, val, CONST_MAX_LEN_REDIS_VALUE) >= 0) lua_push_uint64_table_entry(vm, "host_pool_id", atoi(val)); lua_pushstring(vm, username); lua_insert(vm, -2); lua_settable(vm, -3); next_username: if(usernames[i]) free(usernames[i]); } free(usernames); free(key), free(val); } /* ******************************************* */ /** * @brief Check if the current user is an administrator * * @param vm The lua state. * @return true if the current user is an administrator, false otherwise. */ bool Ntop::isUserAdministrator(lua_State* vm) { struct mg_connection *conn; char *username, *group; if(!ntop->getPrefs()->is_users_login_enabled()) return(true); /* login disabled for all users, everyone's an admin */ if((conn = getLuaVMUservalue(vm,conn)) == NULL) { /* this is an internal script (e.g. periodic script), admin check should pass */ return(true); } else if(HTTPserver::authorized_localhost_user_login(conn)) return(true); /* login disabled from localhost, everyone's connecting from localhost is an admin */ if((username = getLuaVMUserdata(vm, user)) == NULL) { // ntop->getTrace()->traceEvent(TRACE_WARNING, "%s(%s): NO", __FUNCTION__, "???"); return(false); /* Unknown */ } if(!strncmp(username, NTOP_NOLOGIN_USER, strlen(username))) return(true); if((group = getLuaVMUserdata(vm,group)) != NULL) { return(!strcmp(group, NTOP_NOLOGIN_USER) || !strcmp(group, CONST_ADMINISTRATOR_USER)); } else { // ntop->getTrace()->traceEvent(TRACE_WARNING, "%s(%s): NO", __FUNCTION__, username); return(false); /* Unknown */ } } /* ******************************************* */ void Ntop::getAllowedInterface(lua_State* vm) { char *allowed_ifname; allowed_ifname = getLuaVMUserdata(vm, allowed_ifname); lua_pushstring(vm, allowed_ifname != NULL ? allowed_ifname : (char*)""); } /* ******************************************* */ void Ntop::getAllowedNetworks(lua_State* vm) { char key[64], val[64]; const char *username = getLuaVMUservalue(vm, user); snprintf(key, sizeof(key), CONST_STR_USER_NETS, username ? username : ""); lua_pushstring(vm, (ntop->getRedis()->get(key, val, sizeof(val)) >= 0) ? val : CONST_DEFAULT_ALL_NETS); } /* ******************************************* */ // NOTE: ifname must be of size MAX_INTERFACE_NAME_LEN bool Ntop::getInterfaceAllowed(lua_State* vm, char *ifname) const { char *allowed_ifname; allowed_ifname = getLuaVMUserdata(vm, allowed_ifname); if(ifname == NULL) return false; if((allowed_ifname == NULL) || (allowed_ifname[0] == '\0')) { ifname = NULL; return false; } strncpy(ifname, allowed_ifname, MAX_INTERFACE_NAME_LEN-1); ifname[MAX_INTERFACE_NAME_LEN - 1] = '\0'; return true; } /* ******************************************* */ bool Ntop::isInterfaceAllowed(lua_State* vm, const char *ifname) const { char *allowed_ifname; bool ret; if(vm == NULL || ifname == NULL) return true; /* Always return true when no lua state is passed */ allowed_ifname = getLuaVMUserdata(vm, allowed_ifname); if((allowed_ifname == NULL) || (allowed_ifname[0] == '\0')) { ntop->getTrace()->traceEvent(TRACE_DEBUG, "No allowed interface found for %s", ifname); // this is a lua script called within ntopng (no HTTP UI and user interaction, e.g. startup.lua) ret = true; } else { ntop->getTrace()->traceEvent(TRACE_DEBUG, "Allowed interface %s, requested %s", allowed_ifname, ifname); ret = !strncmp(allowed_ifname, ifname, strlen(allowed_ifname)); } return ret; } /* ******************************************* */ bool Ntop::isLocalUser(lua_State* vm) { struct mg_connection *conn; if((conn = getLuaVMUservalue(vm,conn)) == NULL) { /* this is an internal script (e.g. periodic script), admin check should pass */ return(true); } return getLuaVMUservalue(vm,localuser); } /* ******************************************* */ bool Ntop::isInterfaceAllowed(lua_State* vm, int ifid) const { return isInterfaceAllowed(vm, prefs->get_if_name(ifid)); } /* ******************************************* */ bool Ntop::isPcapDownloadAllowed(lua_State* vm, const char *ifname) { bool allow_pcap_download = false; if(isUserAdministrator(vm)) return true; if(isInterfaceAllowed(vm, ifname)) { char *username = getLuaVMUserdata(vm,user); bool allow_historical_flow; ntop->getUserCapabilities(username, &allow_pcap_download, &allow_historical_flow); } return(allow_pcap_download); } /* ******************************************* */ char *Ntop::preparePcapDownloadFilter(lua_State* vm, char *filter) { char *username; char *restricted_filter = NULL; char key[64], val[MAX_USER_NETS_VAL_LEN], val_cpy[MAX_USER_NETS_VAL_LEN]; char *tmp, *net; int filter_len, len = 0, off = 0, num_nets = 0; if(isUserAdministrator(vm)) /* keep the original filter */ goto no_restriction; username = getLuaVMUserdata(vm,user); if (username == NULL || username[0] == '\0') return(NULL); snprintf(key, sizeof(key), CONST_STR_USER_NETS, username); if(ntop->getRedis()->get(key, val, sizeof(val)) == -1) goto no_restriction; /* no subnet configured for this user */ if (strlen(val) == 0) goto no_restriction; /* no subnet configured for this user */ /* compute final filter length */ if (filter != NULL) filter_len = strlen(filter); else filter_len = 0; if (filter_len > 0) len = filter_len + strlen("() and ()"); tmp = NULL; strcpy(val_cpy, val); net = strtok_r(val_cpy, ",", &tmp); while(net != NULL) { len += strlen(" or net ") + strlen(net); net = strtok_r(NULL, ",", &tmp); } /* build final/restricted filter */ restricted_filter = (char*)malloc(len+1); if (restricted_filter == NULL) return(NULL); if (filter_len > 0) off += snprintf(&restricted_filter[off], len-off, "("); tmp = NULL; net = strtok_r(val, ",", &tmp); while(net != NULL) { if (strcmp(net, "0.0.0.0/0") != 0 && strcmp(net, "::/0") != 0) { if (num_nets > 0) off += snprintf(&restricted_filter[off], len-off, " or "); off += snprintf(&restricted_filter[off], len-off, "net %s", net); num_nets++; } net = strtok_r(NULL, ",", &tmp); } if (filter_len > 0) off += snprintf(&restricted_filter[off], len-off, ") and (%s)", filter); return(restricted_filter); no_restriction: return(strdup(filter == NULL ? "" : filter)); } /* ******************************************* */ bool Ntop::checkUserInterfaces(const char * user) const { char ifbuf[MAX_INTERFACE_NAME_LEN]; /* Check if the user has an allowed interface and that interface has not yet been instantiated in ntopng (e.g, this can happen with dynamic interfaces after ntopng has been restarted.) */ getUserAllowedIfname(user, ifbuf, sizeof(ifbuf)); if(ifbuf[0] != '\0' && !isExistingInterface(ifbuf)) return false; return true; } /* ******************************************* */ bool Ntop::getUserPasswordHashLocal(const char * user, char *password_hash) const { char key[64], val[64]; snprintf(key, sizeof(key), CONST_STR_USER_PASSWORD, user); if(ntop->getRedis()->get(key, val, sizeof(val)) < 0) { return(false); } sprintf(password_hash, "%s", val); return(true); } /* ******************************************* */ void Ntop::getUserGroupLocal(const char * user, char *group) const { char key[64], val[64]; snprintf(key, sizeof(key), CONST_STR_USER_GROUP, user); strncpy(group, ((ntop->getRedis()->get(key, val, sizeof(val)) >= 0) ? val : NTOP_UNKNOWN_GROUP), NTOP_GROUP_MAXLEN); group[NTOP_GROUP_MAXLEN - 1] = '\0'; } /* ******************************************* */ bool Ntop::isLocalAuthEnabled() const { char val[64]; if((ntop->getRedis()->get((char*)PREF_NTOP_LOCAL_AUTH, val, sizeof(val)) >= 0) && val[0] == '0') return(false); return(true); } /* ******************************************* */ bool Ntop::checkUserPasswordLocal(const char * user, const char * password, char *group) const { char val[64], password_hash[33]; if(!isLocalAuthEnabled()) return(false); ntop->getTrace()->traceEvent(TRACE_INFO, "Checking Local auth"); if((!strcmp(user, "admin")) && (ntop->getRedis()->get((char*)TEMP_ADMIN_PASSWORD, val, sizeof(val)) >= 0) && (val[0] != '\0') && (!strcmp(val, password))) goto valid_local_user; if (!getUserPasswordHashLocal(user, val)) { return(false); } else { mg_md5(password_hash, password, NULL); if(strcmp(password_hash, val) != 0) { return(false); } } valid_local_user: getUserGroupLocal(user, group); return(true); } /* ******************************************* */ // Return 1 if username/password is allowed, 0 otherwise. bool Ntop::checkUserPassword(const char * user, const char * password, char *group, bool *localuser) const { char val[64]; *localuser = false; if(!user || user[0] == '\0' || !password || password[0] == '\0') return(false); #if defined(NTOPNG_PRO) && defined(HAVE_LDAP) if(ntop->getPro()->has_valid_license()) { if(ntop->getRedis()->get((char*)PREF_NTOP_LDAP_AUTH, val, sizeof(val)) >= 0 && val[0] == '1') { ntop->getTrace()->traceEvent(TRACE_INFO, "Checking LDAP auth"); bool ldap_ret = false; bool is_admin; char *ldapServer = NULL, *ldapAccountType = NULL, *ldapAnonymousBind = NULL, *bind_dn = NULL, *bind_pwd = NULL, *user_group = NULL, *search_path = NULL, *admin_group = NULL; if( !(ldapServer = (char*)calloc(sizeof(char), MAX_LDAP_LEN)) || !(ldapAccountType = (char*)calloc(sizeof(char), MAX_LDAP_LEN)) /* either 'posix' or 'samaccount' */ || !(ldapAnonymousBind = (char*)calloc(sizeof(char), MAX_LDAP_LEN)) /* either '1' or '0' */ || !(bind_dn = (char*)calloc(sizeof(char), MAX_LDAP_LEN)) || !(bind_pwd = (char*)calloc(sizeof(char), MAX_LDAP_LEN)) || !(user_group = (char*)calloc(sizeof(char), MAX_LDAP_LEN)) || !(search_path = (char*)calloc(sizeof(char), MAX_LDAP_LEN)) || !(admin_group = (char*)calloc(sizeof(char), MAX_LDAP_LEN))) { static bool ldap_nomem = false; if(!ldap_nomem) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Unable to allocate memory for the LDAP authentication"); ldap_nomem = true; } goto ldap_auth_out; } ntop->getRedis()->get((char*)PREF_LDAP_SERVER, ldapServer, MAX_LDAP_LEN); ntop->getRedis()->get((char*)PREF_LDAP_ACCOUNT_TYPE, ldapAccountType, MAX_LDAP_LEN); ntop->getRedis()->get((char*)PREF_LDAP_BIND_ANONYMOUS, ldapAnonymousBind, MAX_LDAP_LEN); ntop->getRedis()->get((char*)PREF_LDAP_BIND_DN, bind_dn, MAX_LDAP_LEN); ntop->getRedis()->get((char*)PREF_LDAP_BIND_PWD, bind_pwd, MAX_LDAP_LEN); ntop->getRedis()->get((char*)PREF_LDAP_SEARCH_PATH, search_path, MAX_LDAP_LEN); ntop->getRedis()->get((char*)PREF_LDAP_USER_GROUP, user_group, MAX_LDAP_LEN); ntop->getRedis()->get((char*)PREF_LDAP_ADMIN_GROUP, admin_group, MAX_LDAP_LEN); if(ldapServer[0]) { ldap_ret = LdapAuthenticator::validUserLogin(ldapServer, ldapAccountType, (atoi(ldapAnonymousBind) == 0) ? false : true, bind_dn[0] != '\0' ? bind_dn : NULL, bind_pwd[0] != '\0' ? bind_pwd : NULL, search_path[0] != '\0' ? search_path : NULL, user, password[0] != '\0' ? password : NULL, user_group[0] != '\0' ? user_group : NULL, admin_group[0] != '\0' ? admin_group : NULL, &is_admin); if(ldap_ret) { strncpy(group, is_admin ? CONST_USER_GROUP_ADMIN : CONST_USER_GROUP_UNPRIVILEGED, NTOP_GROUP_MAXLEN); group[NTOP_GROUP_MAXLEN - 1] = '\0'; } } ldap_auth_out: if(ldapServer) free(ldapServer); if(ldapAnonymousBind) free(ldapAnonymousBind); if(bind_dn) free(bind_dn); if(bind_pwd) free(bind_pwd); if(user_group) free(user_group); if(search_path) free(search_path); if(admin_group) free(admin_group); if(ldap_ret) return(true); } } #endif #ifdef HAVE_RADIUS /* NOTE Use https://idblender.com/tools/public-radius for testing the implementation with a public server */ if(ntop->getRedis()->get((char*)PREF_NTOP_RADIUS_AUTH, val, sizeof(val)) >= 0 && val[0] == '1') { ntop->getTrace()->traceEvent(TRACE_INFO, "Checking RADIUS auth"); int result; bool radius_ret = false; char dict_path[MAX_RADIUS_LEN]; char *radiusServer = NULL, *radiusSecret = NULL, *authServer = NULL, *radiusAdminGroup = NULL, *radiusUnprivCapabilitiesGroup = NULL; rc_handle *rh = NULL; VALUE_PAIR *send = NULL, *received = NULL; bool has_unprivileged_capabilities = false; if(!password || !password[0]) return false; if(!(radiusServer = (char*)calloc(sizeof(char), MAX_RADIUS_LEN)) || !(radiusSecret = (char*)calloc(sizeof(char), MAX_SECRET_LENGTH + 1)) || !(radiusAdminGroup = (char*)calloc(sizeof(char), MAX_RADIUS_LEN)) || !(radiusUnprivCapabilitiesGroup = (char*)calloc(sizeof(char), MAX_RADIUS_LEN)) || !(authServer = (char*)calloc(sizeof(char), MAX_RADIUS_LEN))) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: unable to allocate memory"); goto radius_auth_out; } ntop->getRedis()->get((char*)PREF_RADIUS_SERVER, radiusServer, MAX_RADIUS_LEN); ntop->getRedis()->get((char*)PREF_RADIUS_SECRET, radiusSecret, MAX_SECRET_LENGTH + 1); ntop->getRedis()->get((char*)PREF_RADIUS_ADMIN_GROUP, radiusAdminGroup, MAX_RADIUS_LEN); ntop->getRedis()->get((char*)PREF_RADIUS_UNPRIV_CAP_GROUP, radiusUnprivCapabilitiesGroup, MAX_RADIUS_LEN); if(!radiusServer[0] || !radiusSecret[0]) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: no radius server or secret set !"); goto radius_auth_out; } snprintf(authServer, MAX_RADIUS_LEN - 1, "%s:%s", radiusServer, radiusSecret); /* NOTE: this is an handle to the radius lib. It will be passed to multiple functions and cleaned up at the end. * https://github.com/FreeRADIUS/freeradius-client/blob/master/src/radembedded.c */ rh = rc_new(); if(rh == NULL) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: unable to allocate memory"); goto radius_auth_out; } /* ********* */ rh = rc_config_init(rh); if(rh == NULL) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: failed to init configuration"); goto radius_auth_out; } /* RADIUS only auth */ if(rc_add_config(rh, "auth_order", "radius", "config", 0) != 0) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: Unable to set auth_order"); goto radius_auth_out; } if(rc_add_config(rh, "radius_retries", "3", "config", 0) != 0) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: Unable to set retries config"); goto radius_auth_out; } if(rc_add_config(rh, "radius_timeout", "5", "config", 0) != 0) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: Unable to set timeout config"); goto radius_auth_out; } snprintf(dict_path, sizeof(dict_path), "%s/other/radcli_dictionary.txt", ntop->getPrefs()->get_docs_dir()); if(rc_add_config(rh, "dictionary", dict_path, "config", 0) != 0) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: Unable to set dictionary config"); goto radius_auth_out; } if(rc_add_config(rh, "authserver", authServer, "config", 0) != 0) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: Unable to set authserver config: \"%s\"", authServer); goto radius_auth_out; } #ifdef HAVE_RC_TEST_CONFIG /* Necessary since radcli release 1.2.10 */ if(rc_test_config(rh, "ntopng") != 0) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: rc_test_config failed"); goto radius_auth_out; } #endif /* ********* */ if(rc_read_dictionary(rh, rc_conf_str(rh, "dictionary")) != 0) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: unable to read dictionary"); goto radius_auth_out; } if(rc_avpair_add(rh, &send, PW_USER_NAME, user, -1, 0) == NULL) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: unable to set username"); goto radius_auth_out; } if(rc_avpair_add(rh, &send, PW_USER_PASSWORD, password, -1, 0) == NULL) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Radius: unable to set password"); goto radius_auth_out; } ntop->getTrace()->traceEvent(TRACE_INFO, "Radius: performing auth for user %s", user); result = rc_auth(rh, 0, send, &received, NULL); if(result == OK_RC) { bool is_admin = false; if((radiusAdminGroup[0] != '\0') || (radiusUnprivCapabilitiesGroup[0] != '\0')) { VALUE_PAIR *vp = received; char name[sizeof(vp->name)]; char value[sizeof(vp->strvalue)]; while(vp != NULL) { if(rc_avpair_tostr(rh, vp, name, sizeof(name), value, sizeof(value)) == 0) { /* The "Filter-Id" attribute is used to set user privileges */ if(strcmp(name, "Filter-Id") == 0) { if(strcmp(value, radiusAdminGroup) == 0) is_admin = true; else if(strcmp(value, radiusUnprivCapabilitiesGroup) == 0) has_unprivileged_capabilities = true; break; /* We care only about "Filter-Id" */ } } vp = vp->next; } } if(has_unprivileged_capabilities) { changeUserPermission(user, true, 86400 /* 1 day */); changeUserHistoricalFlowPermission(user, true, 86400 /* 1 day */); } else { char key[64]; snprintf(key, sizeof(key), CONST_STR_USER_ALLOW_PCAP, user); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_ALLOW_HISTORICAL_FLOW, user); ntop->getRedis()->del(key); } strncpy(group, is_admin ? CONST_USER_GROUP_ADMIN : CONST_USER_GROUP_UNPRIVILEGED, NTOP_GROUP_MAXLEN); group[NTOP_GROUP_MAXLEN - 1] = '\0'; radius_ret = true; } else { /* Do not display messages for user 'admin' */ if(strcmp(user, "admin")) { switch(result) { case TIMEOUT_RC: ntop->getTrace()->traceEvent(TRACE_WARNING, "Radius Authentication timeout for user \"%s\"", user); break; case REJECT_RC: ntop->getTrace()->traceEvent(TRACE_WARNING, "Radius Authentication rejected for user \"%s\"", user); break; default: ntop->getTrace()->traceEvent(TRACE_WARNING, "Radius Authentication failure[%d]: user \"%s\"", result, user); } } } radius_auth_out: if(send) rc_avpair_free(send); if(received) rc_avpair_free(received); if(rh) rc_destroy(rh); if(radiusAdminGroup) free(radiusAdminGroup); if(radiusUnprivCapabilitiesGroup) free(radiusUnprivCapabilitiesGroup); if(radiusServer) free(radiusServer); if(radiusSecret) free(radiusSecret); if(authServer) free(authServer); if(radius_ret) return(true); } #endif if(ntop->getRedis()->get((char*)PREF_NTOP_HTTP_AUTH, val, sizeof(val)) >= 0) { ntop->getTrace()->traceEvent(TRACE_INFO, "Checking HTTP auth"); if(val[0] == '1') { int postLen; char *httpUrl = NULL, *postData = NULL, *returnData = NULL; bool http_ret = false; int rc = 0; HTTPTranferStats stats; HTTPAuthenticator auth; memset(&auth, 0, sizeof(auth)); if(!password || !password[0]) return false; postLen = 100 + strlen(user) + strlen(password); if(!(httpUrl = (char*)calloc(sizeof(char), MAX_HTTP_AUTHENTICATOR_LEN)) || !(postData = (char*)calloc(sizeof(char), postLen + 1)) || !(returnData = (char*)calloc(sizeof(char), MAX_HTTP_AUTHENTICATOR_RETURN_DATA_LEN + 1))) { ntop->getTrace()->traceEvent(TRACE_ERROR, "HTTP: unable to allocate memory"); goto http_auth_out; } ntop->getRedis()->get((char*)PREF_HTTP_AUTHENTICATOR_URL, httpUrl, MAX_HTTP_AUTHENTICATOR_LEN); if(!httpUrl[0]) { ntop->getTrace()->traceEvent(TRACE_ERROR, "HTTP: no http url set !"); goto http_auth_out; } snprintf(postData, postLen, "{\"user\": \"%s\", \"password\": \"%s\"}", user, password); if(Utils::postHTTPJsonData(NULL, // no digest user NULL, // no digest password httpUrl, postData, 0, &stats, returnData, MAX_HTTP_AUTHENTICATOR_RETURN_DATA_LEN, &rc)) { if(rc == 200) { // parse JSON if(!Utils::parseAuthenticatorJson(&auth, returnData)) { ntop->getTrace()->traceEvent(TRACE_ERROR, "HTTP: unable to parse json answer data !"); goto http_auth_out; } strncpy(group, auth.admin ? CONST_USER_GROUP_ADMIN : CONST_USER_GROUP_UNPRIVILEGED, NTOP_GROUP_MAXLEN); group[NTOP_GROUP_MAXLEN - 1] = '\0'; if(auth.allowedNets != NULL) { if(!Ntop::changeAllowedNets((char*)user, auth.allowedNets)) { ntop->getTrace()->traceEvent(TRACE_ERROR, "HTTP: unable to set allowed nets for user %s", user); goto http_auth_out; } } if(auth.allowedIfname != NULL) { if(!Ntop::changeAllowedIfname((char*)user, auth.allowedIfname)) { ntop->getTrace()->traceEvent(TRACE_ERROR, "HTTP: unable to set allowed ifname for user %s", user); goto http_auth_out; } } if(auth.language != NULL) { if(!Ntop::changeUserLanguage((char*)user, auth.language)) { ntop->getTrace()->traceEvent(TRACE_ERROR, "HTTP: unable to set language for user %s", user); goto http_auth_out; } } http_ret = true; } else ntop->getTrace()->traceEvent(TRACE_WARNING, "HTTP: authentication rejected [code=%d]", rc); } else ntop->getTrace()->traceEvent(TRACE_WARNING, "HTTP: could not contact the HTTP authenticator"); http_auth_out: Utils::freeAuthenticator(&auth); if(httpUrl) free(httpUrl); if(postData) free(postData); if(returnData) free(returnData); if(http_ret) return(true); } } /* Check local auth */ if (checkUserPasswordLocal(user, password, group)) { /* mark the user as local */ *localuser = true; return(true); } return(false); } /* ******************************************* */ static int getLoginAttempts(struct mg_connection *conn) { char ipbuf[32], key[128], val[16]; int cur_attempts = 0; IpAddress client_addr; client_addr.set(mg_get_client_address(conn)); snprintf(key, sizeof(key), CONST_STR_FAILED_LOGIN_KEY, client_addr.print(ipbuf, sizeof(ipbuf))); if((ntop->getRedis()->get(key, val, sizeof(val)) >= 0) && val[0]) cur_attempts = atoi(val); return(cur_attempts); } /* ******************************************* */ bool Ntop::isBlacklistedLogin(struct mg_connection *conn) const { return(getLoginAttempts(conn) >= MAX_FAILED_LOGIN_ATTEMPTS); } /* ******************************************* */ bool Ntop::checkGuiUserPassword(struct mg_connection *conn, const char * user, const char * password, char *group, bool *localuser) const { char *remote_ip, ipbuf[64], key[128], val[16]; int cur_attempts = 0; bool rv; IpAddress client_addr; client_addr.set(mg_get_client_address(conn)); if(ntop->isCaptivePortalUser(user)) { ntop->getTrace()->traceEvent(TRACE_WARNING, "User %s is not a gui user. Login is denied.", user); return false; } remote_ip = client_addr.print(ipbuf, sizeof(ipbuf)); if((cur_attempts = getLoginAttempts(conn)) >= MAX_FAILED_LOGIN_ATTEMPTS) { ntop->getTrace()->traceEvent(TRACE_INFO, "Login denied for '%s' from blacklisted IP %s", user, remote_ip); return false; } rv = checkUserPassword(user, password, group, localuser); snprintf(key, sizeof(key), CONST_STR_FAILED_LOGIN_KEY, remote_ip); if(!rv) { cur_attempts++; snprintf(val, sizeof(val), "%d", cur_attempts); ntop->getRedis()->set(key, val, FAILED_LOGIN_ATTEMPTS_INTERVAL); if(cur_attempts >= MAX_FAILED_LOGIN_ATTEMPTS) ntop->getTrace()->traceEvent(TRACE_INFO, "IP %s is now blacklisted from login for %d seconds", remote_ip, FAILED_LOGIN_ATTEMPTS_INTERVAL); HTTPserver::traceLogin(user, false); } else ntop->getRedis()->del(key); return(rv); } /* ******************************************* */ bool Ntop::checkCaptiveUserPassword(const char * user, const char * password, char *group) const { bool localuser = false; bool rv; if(!ntop->isCaptivePortalUser(user)) { ntop->getTrace()->traceEvent(TRACE_WARNING, "User %s is not a captive portal user. Login is denied.", user); return false; } rv = checkUserPassword(user, password, group, &localuser); return(rv); } /* ******************************************* */ bool Ntop::mustChangePassword(const char *user) { char val[8]; if ((strcmp(user, "admin") == 0) && (ntop->getRedis()->get((char *)CONST_DEFAULT_PASSWORD_CHANGED, val, sizeof(val)) < 0 || val[0] == '0')) return true; return false; } /* ******************************************* */ /* NOTE: the admin vs local user checks must be performed by the caller */ bool Ntop::resetUserPassword(char *username, char *old_password, char *new_password) { char key[64]; char password_hash[33]; char group[NTOP_GROUP_MAXLEN]; if((old_password != NULL) && (old_password[0] != '\0')) { bool localuser = false; if(!checkUserPassword(username, old_password, group, &localuser)) return(false); if(!localuser) return(false); } snprintf(key, sizeof(key), CONST_STR_USER_PASSWORD, username); mg_md5(password_hash, new_password, NULL); if(ntop->getRedis()->set(key, password_hash, 0) < 0) return(false); return(true); } /* ******************************************* */ bool Ntop::changeUserFullName(const char * username, const char * full_name) const { char key[64]; if (username == NULL || username[0] == '\0' || full_name == NULL || !existsUser(username)) return false; ntop->getTrace()->traceEvent(TRACE_DEBUG, "Changing full name to %s for %s", full_name, username); snprintf(key, sizeof(key), CONST_STR_USER_FULL_NAME, username); ntop->getRedis()->set(key, full_name, 0); return (ntop->getRedis()->set(key, (char*) full_name, 0) >= 0); } /* ******************************************* */ bool Ntop::changeUserRole(char *username, char *usertype) const { if(usertype != NULL) { char key[64]; snprintf(key, sizeof(key), CONST_STR_USER_GROUP, username); if(ntop->getRedis()->set(key, usertype, 0) < 0) return(false); } return(true); } /* ******************************************* */ bool Ntop::changeAllowedNets(char *username, char *allowed_nets) const { if(allowed_nets != NULL) { char key[64]; snprintf(key, sizeof(key), CONST_STR_USER_NETS, username); if(ntop->getRedis()->set(key, allowed_nets, 0) < 0) return(false); } return(true); } /* ******************************************* */ bool Ntop::changeAllowedIfname(char *username, char *allowed_ifname) const { /* Add as exception :// */ char *column_slash = strstr(allowed_ifname, ":__"); if (username == NULL || username[0] == '\0') return false; if(column_slash) column_slash[1] = column_slash[2] = '/'; ntop->getTrace()->traceEvent(TRACE_DEBUG, "Changing allowed ifname to %s for %s", allowed_ifname, username); char key[64]; snprintf(key, sizeof(key), CONST_STR_USER_ALLOWED_IFNAME, username); if(allowed_ifname != NULL && allowed_ifname[0] != '\0') { return (ntop->getRedis()->set(key, allowed_ifname, 0) >= 0); } else { ntop->getRedis()->del(key); } return(true); } /* ******************************************* */ bool Ntop::changeUserHostPool(const char * username, const char * host_pool_id) const { if (username == NULL || username[0] == '\0') return false; ntop->getTrace()->traceEvent(TRACE_DEBUG, "Changing host pool id to %s for %s", host_pool_id, username); char key[64]; snprintf(key, sizeof(key), CONST_STR_USER_HOST_POOL_ID, username); if(host_pool_id != NULL && host_pool_id[0] != '\0') { return (ntop->getRedis()->set(key, (char*)host_pool_id, 0) >= 0); } else { ntop->getRedis()->del(key); } return(true); } /* ******************************************* */ bool Ntop::changeUserLanguage(const char * username, const char * language) const { if (username == NULL || username[0] == '\0') return false; ntop->getTrace()->traceEvent(TRACE_DEBUG, "Changing user language %s for %s", language, username); char key[64]; snprintf(key, sizeof(key), CONST_STR_USER_LANGUAGE, username); if(language != NULL && language[0] != '\0') return (ntop->getRedis()->set(key, (char*)language, 0) >= 0); else ntop->getRedis()->del(key); return(true); } /* ******************************************* */ bool Ntop::changeUserPermission(const char * username, bool allow_pcap_download, u_int32_t ttl) const { char key[64]; if (username == NULL || username[0] == '\0') return false; ntop->getTrace()->traceEvent(TRACE_DEBUG, "Changing user permission [allow-pcap-download: %s] for %s", allow_pcap_download ? "true" : "false", username); snprintf(key, sizeof(key), CONST_STR_USER_ALLOW_PCAP, username); if(allow_pcap_download) return (ntop->getRedis()->set(key, "1", ttl) >= 0); else ntop->getRedis()->del(key); return(true); } /* ******************************************* */ bool Ntop::changeUserHistoricalFlowPermission(const char * username, bool allow_historical_flow, u_int32_t ttl) const { char key[64]; if (username == NULL || username[0] == '\0') return false; ntop->getTrace()->traceEvent(TRACE_DEBUG, "Changing user permission [allow-historical-flow: %s] for %s", allow_historical_flow ? "true" : "false", username); snprintf(key, sizeof(key), CONST_STR_USER_ALLOW_HISTORICAL_FLOW, username); if(allow_historical_flow) return (ntop->getRedis()->set(key, "1", ttl) >= 0); else ntop->getRedis()->del(key); return(true); } /* ******************************************* */ bool Ntop::getUserCapabilities(const char * username, bool *allow_pcap_download, bool *allow_historical_flow) const { char key[64], val[2]; *allow_pcap_download = *allow_historical_flow = false; if(username == NULL || username[0] == '\0') return(false); /* ************** */ snprintf(key, sizeof(key), CONST_STR_USER_ALLOW_PCAP, username); if(ntop->getRedis()->get(key, val, sizeof(val)) >= 0) if(strcmp(val, "1") == 0) *allow_pcap_download = true; /* ************** */ snprintf(key, sizeof(key), CONST_STR_USER_ALLOW_HISTORICAL_FLOW, username); if(ntop->getRedis()->get(key, val, sizeof(val)) >= 0) if(strcmp(val, "1") == 0) *allow_historical_flow = true; return(true); } /* ******************************************* */ /* Assigns a unique user id to be assigned to a new ntopng user. Callers must lock. Assigned user id, if assignment is successful, is returned in the first param. */ bool Ntop::assignUserId(u_int8_t *new_user_id) { char cur_id_buf[8]; int cur_id; for(cur_id = 0; cur_id < NTOP_MAX_NUM_USERS; cur_id++) { snprintf(cur_id_buf, sizeof(cur_id_buf), "%d", cur_id); if(!ntop->getRedis()->sismember(PREF_NTOP_USER_IDS, cur_id_buf)) break; } if(cur_id == NTOP_MAX_NUM_USERS) return false; /* No more ids available */ if(ntop->getRedis()->sadd(PREF_NTOP_USER_IDS, cur_id_buf) < 0) return false; /* Unable to add the newly assigned user id to the set of all user ids */ *new_user_id = cur_id; ntop->getTrace()->traceEvent(TRACE_DEBUG, "Assigned user id %u", *new_user_id); return true; } /* ******************************************* */ bool Ntop::existsUser(const char * username) const { char key[CONST_MAX_LEN_REDIS_KEY], val[2] /* Don't care about the content */; snprintf(key, sizeof(key), CONST_STR_USER_GROUP, username); if(ntop->getRedis()->get(key, val, sizeof(val)) >= 0) return(true); // user already exists return(false); } /* ******************************************* */ bool Ntop::addUser(char *username, char *full_name, char *password, char *host_role, char *allowed_networks, char *allowed_ifname, char *host_pool_id, char *language, bool allow_pcap_download, bool allow_historical_flow) { char key[CONST_MAX_LEN_REDIS_KEY]; char password_hash[33]; char new_user_id_buf[8]; u_int8_t new_user_id = 0; users_m.lock(__FILE__, __LINE__); if(existsUser(username) /* User already existing */ || !assignUserId(&new_user_id) /* Unable to assign a user id */) { users_m.unlock(__FILE__, __LINE__); return(false); } snprintf(key, sizeof(key), CONST_STR_USER_FULL_NAME, username); ntop->getRedis()->set(key, full_name, 0); snprintf(key, sizeof(key), CONST_STR_USER_GROUP, username); ntop->getRedis()->set(key, (char*) host_role, 0); snprintf(key, sizeof(key), CONST_STR_USER_ID, username); snprintf(new_user_id_buf, sizeof(new_user_id_buf), "%d", new_user_id); ntop->getRedis()->set(key, new_user_id_buf, 0); snprintf(key, sizeof(key), CONST_STR_USER_PASSWORD, username); mg_md5(password_hash, password, NULL); ntop->getRedis()->set(key, password_hash, 0); snprintf(key, sizeof(key), CONST_STR_USER_NETS, username); ntop->getRedis()->set(key, allowed_networks, 0); snprintf(key, sizeof(key), CONST_STR_USER_THEME, username); ntop->getRedis()->set(key, "", 0); snprintf(key, sizeof(key), CONST_STR_USER_DATE_FORMAT, username); ntop->getRedis()->set(key, "", 0); if(language && language[0] != '\0') { snprintf(key, sizeof(key), CONST_STR_USER_LANGUAGE, username); ntop->getRedis()->set(key, language, 0); } if(allow_pcap_download) { snprintf(key, sizeof(key), CONST_STR_USER_ALLOW_PCAP, username); ntop->getRedis()->set(key, "1", 0); } if(allow_historical_flow) { snprintf(key, sizeof(key), CONST_STR_USER_ALLOW_HISTORICAL_FLOW, username); ntop->getRedis()->set(key, "1", 0); } if(allowed_ifname && allowed_ifname[0] != '\0'){ ntop->getTrace()->traceEvent(TRACE_DEBUG, "Setting allowed ifname: %s", allowed_ifname); snprintf(key, sizeof(key), CONST_STR_USER_ALLOWED_IFNAME, username); ntop->getRedis()->set(key, allowed_ifname, 0); } if(host_pool_id && host_pool_id[0] != '\0') { snprintf(key, sizeof(key), CONST_STR_USER_HOST_POOL_ID, username); ntop->getRedis()->set(key, host_pool_id, 0); } users_m.unlock(__FILE__, __LINE__); return(true); } /* ******************************************* */ bool Ntop::addUserAPIToken(const char * username, const char *api_token) { char key[CONST_MAX_LEN_REDIS_KEY]; snprintf(key, sizeof(key), CONST_STR_USER_API_TOKEN, username); ntop->getRedis()->set(key, api_token); return(true); } /* ******************************************* */ bool Ntop::isCaptivePortalUser(const char * username) { char key[64], val[64]; snprintf(key, sizeof(key), CONST_STR_USER_GROUP, username); if((ntop->getRedis()->get(key, val, sizeof(val)) >= 0) && (!strcmp(val, CONST_USER_GROUP_CAPTIVE_PORTAL))) { return(true); } return(false); } /* ******************************************* */ bool Ntop::deleteUser(char *username) { char user_id_buf[8]; char key[64]; users_m.lock(__FILE__, __LINE__); if(!existsUser(username)) { users_m.unlock(__FILE__, __LINE__); return(false); } snprintf(key, sizeof(key), CONST_STR_USER_ID, username); /* Dispose the currently assigned user id so that it can be recycled */ if((ntop->getRedis()->get(key, user_id_buf, sizeof(user_id_buf)) >= 0)) { ntop->getRedis()->srem(PREF_NTOP_USER_IDS, user_id_buf); } snprintf(key, sizeof(key), CONST_STR_USER_FULL_NAME, username); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_GROUP, username); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_ID, username); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_PASSWORD, username); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_NETS, username); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_ALLOWED_IFNAME, username); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_LANGUAGE, username); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_ALLOW_PCAP, username); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_HOST_POOL_ID, username); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_THEME, username); ntop->getRedis()->del(key); snprintf(key, sizeof(key), CONST_STR_USER_DATE_FORMAT, username); ntop->getRedis()->del(key); /* Delete the API Token, first from the hash of all tokens, then from the user */ char api_token[NTOP_SESSION_ID_LENGTH]; if(getUserAPIToken(username, api_token, sizeof(api_token))) { ntop->getRedis()->hashDel(NTOPNG_API_TOKEN_PREFIX, api_token); } snprintf(key, sizeof(key), CONST_STR_USER_API_TOKEN, username); ntop->getRedis()->del(key); users_m.unlock(__FILE__, __LINE__); return true; } /* ******************************************* */ bool Ntop::getUserHostPool(char *username, u_int16_t *host_pool_id) { char key[64], val[64]; snprintf(key, sizeof(key), CONST_STR_USER_HOST_POOL_ID, username ? username : ""); if(ntop->getRedis()->get(key, val, sizeof(val)) >= 0) { if(host_pool_id) *host_pool_id = atoi(val); return true; } if(host_pool_id) *host_pool_id = NO_HOST_POOL_ID; return false; } /* ******************************************* */ bool Ntop::getUserAllowedIfname(const char * username, char *buf, size_t buflen) const { char key[64]; snprintf(key, sizeof(key), CONST_STR_USER_ALLOWED_IFNAME, username ? username : ""); if(ntop->getRedis()->get(key, buf, buflen) >= 0) return true; return false; } /* ******************************************* */ bool Ntop::getUserAPIToken(const char * username, char *buf, size_t buflen) const { char key[CONST_MAX_LEN_REDIS_KEY]; snprintf(key, sizeof(key), CONST_STR_USER_API_TOKEN, username); if(ntop->getRedis()->get(key, buf, buflen) >= 0) return true; return false; } /* ******************************************* */ void Ntop::fixPath(char *str, bool replaceDots) { for(int i=0; str[i] != '\0'; i++) { #ifdef WIN32 /* Allowed windows path and file characters: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#win32_file_namespaces */ if (str[i] == '/') str[i] = '\\'; else if (str[i] == '\\') continue; else if ((i == 1) && (str[i] == ':')) // c:\\... continue; else if (str[i] == ':' || str[i] == '"' || str[i] == '|' || str[i] == '?' || str[i] == '*') str[i] = '_'; #endif if(replaceDots) { if((i > 0) && (str[i] == '.') && (str[i-1] == '.')) { // ntop->getTrace()->traceEvent(TRACE_WARNING, "Invalid path detected %s", str); str[i-1] = '_', str[i] = '_'; /* Invalidate the path */ } } } } /* ******************************************* */ char* Ntop::getValidPath(char *__path) { char _path[MAX_PATH+8]; struct stat buf; #ifdef WIN32 const char *install_dir = (const char *)get_install_dir(); #endif bool has_drive_colon = 0; if(strncmp(__path, "./", 2) == 0) { snprintf(_path, sizeof(_path), "%s/%s", startup_dir, &__path[2]); fixPath(_path); if(stat(_path, &buf) == 0) { free(__path); return(strdup(_path)); } } #ifdef WIN32 has_drive_colon = (isalpha((int)__path[0]) && (__path[1] == ':' && (__path[2] == '\\' || __path[2] == '/'))); #endif if((__path[0] == '/') || (__path[0] == '\\') || has_drive_colon) { /* Absolute paths */ if(stat(__path, &buf) == 0) { return(__path); } } else snprintf(_path, MAX_PATH, "%s", __path); /* relative paths */ for(int i = 0; i < (int)COUNT_OF(dirs); i++) { if(dirs[i] /* Ignore / as when you start ntopng as a service you might have /scripts or /httpdocs on your filesystem fooling ntopng initialization and thus breaking averything */ && strcmp(dirs[i], "/") ) { char path[2*MAX_PATH]; snprintf(path, sizeof(path), "%s/%s", dirs[i], _path); fixPath(path); if(stat(path, &buf) == 0) { free(__path); return(strdup(path)); } } } free(__path); return(strdup("")); } /* ******************************************* */ void Ntop::daemonize() { #ifndef WIN32 int childpid; ntop->getTrace()->traceEvent(TRACE_NORMAL, "Parent process is exiting (this is normal)"); signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); /* IMPORTANT SIGCHLD should NOT be masked as otherwise with popen()/pclose() we receive an error when closing the pipe on FreeBSD signal(SIGCHLD, SIG_IGN); */ signal(SIGQUIT, SIG_IGN); if((childpid = fork()) < 0) ntop->getTrace()->traceEvent(TRACE_ERROR, "Occurred while daemonizing (errno=%d)", errno); else { if(!childpid) { /* child */ int rc; //ntop->getTrace()->traceEvent(TRACE_NORMAL, "Bye bye: I'm becoming a daemon..."); rc = chdir("/"); if(rc != 0) ntop->getTrace()->traceEvent(TRACE_ERROR, "Error while moving to / directory"); setsid(); /* detach from the terminal */ fclose(stdin); fclose(stdout); /* fclose(stderr); */ /* * clear any inherited file mode creation mask */ //umask(0); /* * Use line buffered stdout */ /* setlinebuf (stdout); */ setvbuf(stdout, (char *)NULL, _IOLBF, 0); } else /* father */ exit(0); } #endif } /* ******************************************* */ void Ntop::setLocalNetworks(char *_nets) { char *nets; u_int len; if(_nets == NULL) return; len = (u_int)strlen(_nets); if((len > 2) && (_nets[0] == '"') && (_nets[len-1] == '"')) { nets = strdup(&_nets[1]); nets[len-2] = '\0'; } else nets = strdup(_nets); addLocalNetworkList(nets); free(nets); }; /* ******************************************* */ NetworkInterface* Ntop::getInterfaceById(int if_id) { if(if_id == SYSTEM_INTERFACE_ID) return(system_interface); for(int i=0; iget_id() == if_id) return(iface[i]); } return(NULL); } /* ******************************************* */ bool Ntop::isExistingInterface(const char * name) const { if(name == NULL) return(false); for(int i=0; iget_name(), name)) return(true); } if(!strcmp(name, getSystemInterface()->get_name())) return(true); return(false); } /* ******************************************* */ NetworkInterface* Ntop::getNetworkInterface(const char *name, lua_State* vm) { char allowed_ifname[MAX_INTERFACE_NAME_LEN] = {0}; char *bad_num = NULL; int if_id; if(vm && getInterfaceAllowed(vm, allowed_ifname)) { ntop->getTrace()->traceEvent(TRACE_DEBUG, "Forcing allowed interface. [requested: %s][selected: %s]", name, allowed_ifname); return getNetworkInterface(allowed_ifname); } if(name == NULL) return(NULL); /* This method accepts both interface names or Ids. * Due to bad Lua number formatting, a float number may be received. */ if_id = strtof(name, &bad_num); if((if_id == SYSTEM_INTERFACE_ID) || !strcmp(name, SYSTEM_INTERFACE_NAME)) return(getSystemInterface()); if((bad_num == NULL) || (*bad_num == '\0')) { /* name is a number */ return(getInterfaceById(if_id)); } /* if here, name is a string */ for(int i = 0; iget_name())) { NetworkInterface *ret_iface = isInterfaceAllowed(vm, iface[i]->get_name()) ? iface[i] : NULL; if(ret_iface) return(ret_iface); } } return(NULL); }; /* ******************************************* */ int Ntop::getInterfaceIdByName(lua_State *vm, const char * name) { NetworkInterface * res = getNetworkInterface(name, vm); if(res) return res->get_id(); return(-1); } /* ****************************************** */ /* NOTE: the interface is deleted when this method returns false */ bool Ntop::registerInterface(NetworkInterface *_if) { bool rv = true; /* Needed as can be called concurrently by NetworkInterface::registerSubInterface */ m.lock(__FILE__, __LINE__); for(int i = 0; i < num_defined_interfaces; i++) { if(strcmp(iface[i]->get_name(), _if->get_name()) == 0) { ntop->getTrace()->traceEvent(TRACE_WARNING, "Skipping duplicated interface %s", _if->get_description()); rv = false; goto out; } } if(num_defined_interfaces < MAX_NUM_DEFINED_INTERFACES) { ntop->getTrace()->traceEvent(TRACE_NORMAL, "Registered interface %s [id: %d]", _if->get_description(), _if->get_id()); iface[num_defined_interfaces++] = _if; rv = true; goto out; } else { static bool too_many_interfaces_error = false; if(!too_many_interfaces_error) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Too many interfaces defined"); too_many_interfaces_error = true; } rv = false; goto out; } out: if(!rv) delete _if; m.unlock(__FILE__, __LINE__); return(rv); }; /* ******************************************* */ void Ntop::initInterface(NetworkInterface *_if) { /* Initialization related to flow-dump */ if(ntop->getPrefs()->do_dump_flows()) { if(_if->initFlowDump(num_dump_interfaces)) num_dump_interfaces++; _if->startDBLoop(); } /* Other initialization activities */ _if->initFlowChecksLoop(); _if->initHostChecksLoop(); _if->checkDisaggregationMode(); } /* *************************************** */ void Ntop::addToPool(char *host_or_mac, u_int16_t user_pool_id) { char key[128], pool_buf[16]; #ifdef HOST_POOLS_DEBUG ntop->getTrace()->traceEvent(TRACE_INFO, "Adding %s as host pool member [pool id: %i]", host_or_mac, user_pool_id); #endif snprintf(pool_buf, sizeof(pool_buf), "%u", user_pool_id); snprintf(key, sizeof(key), HOST_POOL_MEMBERS_KEY, pool_buf); ntop->getRedis()->sadd(key, host_or_mac); /* New member added */ reloadHostPools(); } /* ******************************************* */ void Ntop::checkReloadHostPools() { if(hostPoolsReloadInProgress /* Check if a reload has been requested */) { /* Leave this BEFORE the actual swap and new allocation to guarantee changes are always seen */ hostPoolsReloadInProgress = false; for(int i = 0; i < get_num_interfaces(); i++) { NetworkInterface *iface; if((iface = ntop->getInterface(i)) != NULL) iface->reloadHostPools(); } } } /* ******************************************* */ void Ntop::checkReloadAlertExclusions() { #ifdef NTOPNG_PRO if(alert_exclusions_shadow) { /* Dispose old memory if necessary */ delete alert_exclusions_shadow; alert_exclusions_shadow = NULL; } if(alertExclusionsReloadInProgress /* Check if a reload has been requested */ || !alert_exclusions /* Control groups are not allocated */) { alertExclusionsReloadInProgress = false; /* Leave this BEFORE the actual swap and new allocation to guarantee changes are always seen */ alert_exclusions_shadow = alert_exclusions; /* Save the existing instance */ alert_exclusions = new (std::nothrow) AlertExclusions(); /* Allocate a new instance */ if(!alert_exclusions) ntop->getTrace()->traceEvent(TRACE_ERROR, "Unable to allocate memory for control groups."); } #endif } /* ******************************************* */ void Ntop::checkReloadFlowChecks() { if (!ntop->getPrefs()->is_pro_edition() /* Community mode */ && flow_checks_loader && flow_checks_loader->getChecksEdition() != ntopng_edition_community) { /* Force a reload when switching to community (demo mode) */ reloadFlowChecks(); } if(flowChecksReloadInProgress /* Reload requested from the UI upon configuration changes */) { FlowChecksLoader *old, *tmp_flow_checks_loader = new (std::nothrow) FlowChecksLoader(); if(!tmp_flow_checks_loader) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Unable to allocate memory for flow checks."); return; } tmp_flow_checks_loader->initialize(); old = flow_checks_loader; /* Pass the newly allocated loader to all interfaces so they will update their checks */ for(int i = 0; i < get_num_interfaces(); i++) iface[i]->reloadFlowChecks(tmp_flow_checks_loader); flow_checks_loader = tmp_flow_checks_loader; if(old) { sleep(2); /* Make sure nobody is using the old one */ delete old; } flowChecksReloadInProgress = false; } } /* ******************************************* */ void Ntop::checkReloadHostChecks() { if (!ntop->getPrefs()->is_pro_edition() /* Community mode */ && host_checks_loader && host_checks_loader->getChecksEdition() != ntopng_edition_community) { /* Force a reload when switching to community (demo mode) */ reloadHostChecks(); } if(hostChecksReloadInProgress /* Reload requested from the UI upon configuration changes */) { HostChecksLoader *old, *tmp_host_checks_loader = new (std::nothrow) HostChecksLoader(); if(!tmp_host_checks_loader) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Unable to allocate memory for host checks."); return; } tmp_host_checks_loader->initialize(); old = host_checks_loader; /* Pass the newly allocated loader to all interfaces so they will update their checks */ for(int i = 0; i < get_num_interfaces(); i++) iface[i]->reloadHostChecks(tmp_host_checks_loader); host_checks_loader = tmp_host_checks_loader; if(old) { sleep(2); /* Make sure nobody is using the old one */ delete old; } hostChecksReloadInProgress = false; } } /* ******************************************* */ /* NOTE: the multiple isShutdown checks below are necessary to reduce the shutdown time */ void Ntop::runHousekeepingTasks() { checkReloadHostPools(); checkReloadAlertExclusions(); checkReloadFlowChecks(); checkReloadHostChecks(); for(int i = 0; i < get_num_interfaces(); i++) { if (!iface[i]->isStartingUp()) iface[i]->runHousekeepingTasks(); } #ifdef NTOPNG_PRO pro->runHousekeepingTasks(); #endif } /* ******************************************* */ void Ntop::runShutdownTasks() { /* Final shut down tasks for all interfaces */ for(int i=0; iisView()) iface[i]->runShutdownTasks(); } for(int i=0; iisView()) iface[i]->runShutdownTasks(); } } /* ******************************************* */ /* Checks if all the activities are completed (e.g., all packets processed, notifications sent) and possibly sends a shutdown signal to terminate. NOTE: Only effective when ntopng is started with --shutdown-when-done. Without that options ntopng keeps running and doesn't terminate. */ void Ntop::checkShutdownWhenDone() { if(ntop->getPrefs()->shutdownWhenDone()) { for(int i = 0; i < get_num_interfaces(); i++) { NetworkInterface *iface = getInterface(i); /* Check all the interfaces reading from pcap files if they are done with their activities. */ if(iface->read_from_pcap_dump() && !iface->read_from_pcap_dump_done()) /* iface isn't done yet */ return; } /* Here all interface reading from pcap files are done. */ if(!recipients_are_empty()) { /* Recipients are still processing notifications, wait until they're done. */ ntop->getTrace()->traceEvent(TRACE_NORMAL, "Waiting for pending notifications.."); return; } /* When they are done, signal ntopng to shutdown */ /* One extra housekeeping before executing tests (this assumes all flows have been walked) */ runHousekeepingTasks(); runShutdownTasks(); /* Test Script (Runtime Analysis) */ if(ntop->getPrefs()->get_test_runtime_script_path()) { const char *test_runtime_script_path = ntop->getPrefs()->get_test_runtime_script_path(); /* Execute as Bash script */ ntop->getTrace()->traceEvent(TRACE_NORMAL, "> Running Runtime Script '%s'", test_runtime_script_path); Utils::exec(test_runtime_script_path); } /* Perform shutdown operations on all active interfaces - this also flushes all active flows */ ntop->shutdownInterfaces(); /* Make sure all flushed flows are also dumped to the database for post analysis (e.g. historical data) */ #if defined(HAVE_CLICKHOUSE) && defined(HAVE_MYSQL) if(clickhouseImport) importClickHouseDumps(true); #endif /* Test Script (Post Analysis) */ if(ntop->getPrefs()->get_test_post_script_path()) { const char *test_post_script_path = ntop->getPrefs()->get_test_post_script_path(); /* Execute as Bash script */ ntop->getTrace()->traceEvent(TRACE_NORMAL, "> Running Post Script '%s'", test_post_script_path); Utils::exec(test_post_script_path); } ntop->getGlobals()->shutdown(); } } /* ******************************************* */ void Ntop::shutdownPeriodicActivities() { if(pa) { while(pa->isRunning()) sleep(1); delete pa; pa = NULL; } } /* ******************************************* */ void Ntop::shutdownInterfaces() { /* First, shutdown all view interfaces so they can release counters from the viewed interfaces */ if(interfacesShuttedDown) return; for(int i=0; iisView()) { EthStats *stats = iface[i]->getStats(); stats->print(); iface[i]->shutdown(); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Polling shut down [interface: %s]", iface[i]->get_description()); } } /* Now, shutdown all other non-view interfaces */ for(int i=0; iisView()) { EthStats *stats = iface[i]->getStats(); stats->print(); iface[i]->shutdown(); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Polling shut down [interface: %s]", iface[i]->get_description()); } } interfacesShuttedDown = true; } /* ******************************************* */ void Ntop::shutdownAll() { ThreadedActivity *shutdown_activity; /* Wait until currently executing periodic activities are completed, * Periodic activites should not run during interfaces shutdown */ ntop->shutdownPeriodicActivities(); /* Perform shutdown operations on all active interfaces, * including purging all active flows, hosts, etc */ ntop->shutdownInterfaces(); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Executing shutdown script [%s]", SHUTDOWN_SCRIPT_PATH); /* Exec shutdown script before shutting down ntopng */ if((shutdown_activity = new (std::nothrow) ThreadedActivity(SHUTDOWN_SCRIPT_PATH))) { /* Don't call run() as by the time the script will be run the delete below will free the memory */ shutdown_activity->runSystemScript(time(NULL)); delete shutdown_activity; } #if defined(HAVE_CLICKHOUSE) && defined(HAVE_MYSQL) /* Dump flows flushed during shutdown */ /* Commented out: this is done on restart to speed up the shutdown if(clickhouseImport) importClickHouseDumps(true); */ #endif /* Complete the shutdown */ ntop->getGlobals()->shutdown(); #ifndef WIN32 /* PID file cannot be deleted as it is under `/var/run` which, in turn, is a symlink to `/run`, which is not writable by user `ntopng`. As user `ntopng` has no write privileges on `/run`, the PID file cannot be deleted from inside this process. Deletion is performed as part of the ExecStopPost in the systemd ntopng.service file */ #if 0 if(ntop->getPrefs()->get_pid_path() != NULL) { int rc = unlink(ntop->getPrefs()->get_pid_path()); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Deleted PID %s: [rc: %d][%s]", ntop->getPrefs()->get_pid_path(), rc, strerror(errno)); } #endif #endif } /* **************************************************** */ void Ntop::purgeLoopBody() { while(!globals->isShutdown()) { for(u_int i = 0; i < get_num_interfaces(); i++) { NetworkInterface *cur_iface = getInterface(i); if(cur_iface) cur_iface->purgeQueuedIdleEntries(); } /* Safe to sleep 100ms as entries are marked as idle by interfaces purgeIdle which runs every second on 1/24th of the hash table. This is run faster that 1 second as there may be idle entries not deleted (number of uses greater than 0) */ _usleep(100000); } } /* **************************************************** */ static void* purgeLoop(void *arg) { ntop->purgeLoopBody(); return NULL; } /* **************************************************** */ /* Thread which iterates on all available interfaces and perform the delete operations on idle hash table entries */ bool Ntop::startPurgeLoop() { if(!purgeLoop_started) { pthread_create(&purgeLoop, NULL, ::purgeLoop, NULL); purgeLoop_started = true; } return purgeLoop_started; } /* ******************************************* */ void Ntop::loadTrackers() { FILE *fd; char line[MAX_PATH]; snprintf(line, sizeof(line), "%s/other/trackers.txt", prefs->get_docs_dir()); if((fd = fopen(line, "r")) != NULL) { if((trackers_automa = ndpi_init_automa()) == NULL) { ntop->getTrace()->traceEvent(TRACE_WARNING, "Unable to initialize trackers"); fclose(fd); return; } while(fgets(line, MAX_PATH, fd) != NULL) { char *str = strdup(line); if (str) ndpi_add_string_to_automa(trackers_automa, str); } fclose(fd); ndpi_finalize_automa(trackers_automa); } else ntop->getTrace()->traceEvent(TRACE_WARNING, "Unable to load trackers file %s", line); } /* ******************************************* */ bool Ntop::isATrackerHost(char *host) { return trackers_automa && ndpi_match_string(trackers_automa, host) > 0; } /* ******************************************* */ void Ntop::initAllowedProtocolPresets() { for(u_int i=0; igetDeviceAllowedProtocols((DeviceType) i); NDPI_BITMASK_SET_ALL(b->clientAllowed); NDPI_BITMASK_SET_ALL(b->serverAllowed); } } /* ******************************************* */ void Ntop::refreshAllowedProtocolPresets(DeviceType device_type, bool client, lua_State *L, int index) { DeviceProtocolBitmask *b = ntop->getDeviceAllowedProtocols(device_type); lua_pushnil(L); if (b == NULL) return; if (client) NDPI_BITMASK_RESET(b->clientAllowed); else NDPI_BITMASK_RESET(b->serverAllowed); while(lua_next(L, index) != 0) { u_int key_proto = lua_tointeger(L, -2); int t = lua_type(L, -1); if((int)key_proto < 0) continue; switch (t) { case LUA_TNUMBER: { u_int value_action = lua_tointeger(L, -1); if (value_action) { if (client) NDPI_BITMASK_ADD(b->clientAllowed, key_proto); else NDPI_BITMASK_ADD(b->serverAllowed, key_proto); } } break; default: ntop->getTrace()->traceEvent(TRACE_ERROR, "Internal error: type %d not handled", t); break; } lua_pop(L, 1); } } /* ******************************************* */ #ifdef NTOPNG_PRO bool Ntop::addIPToLRUMatches(u_int32_t client_ip, u_int16_t user_pool_id, char *label, char *ifname) { for(int i=0; iis_bridge_interface() && (strcmp(iface[i]->get_name(), ifname) == 0)) { iface[i]->addIPToLRUMatches(client_ip, user_pool_id, label); return true; } } return false; } /* ******************************************* */ bool Ntop::addToNotifiedInformativeCaptivePortal(u_int32_t client_ip) { for(int i = 0; i < num_defined_interfaces; i++) { if(iface[i]->is_bridge_interface()) /* TODO: handle multiple interfaces separately */ iface[i]->addToNotifiedInformativeCaptivePortal(client_ip); } return true; } #endif /* ******************************************* */ DeviceProtoStatus Ntop::getDeviceAllowedProtocolStatus(DeviceType dev_type, ndpi_protocol proto, u_int16_t pool_id, bool as_client) { /* Check if this application protocol is allowd for the specified device type */ DeviceProtocolBitmask *bitmask = getDeviceAllowedProtocols(dev_type); NDPI_PROTOCOL_BITMASK *direction_bitmask = as_client ? (&bitmask->clientAllowed) : (&bitmask->serverAllowed); #ifdef HAVE_NEDGE /* On nEdge the concept of device protocol policies is only applied to unassigned devices on LAN */ if(pool_id != NO_HOST_POOL_ID) return device_proto_allowed; #endif /* Always allow network critical protocols */ if(Utils::isCriticalNetworkProtocol(proto.master_protocol) || Utils::isCriticalNetworkProtocol(proto.app_protocol)) return device_proto_allowed; if((proto.master_protocol != NDPI_PROTOCOL_UNKNOWN) && (!NDPI_ISSET(direction_bitmask, proto.master_protocol))) { return device_proto_forbidden_master; } else if((!NDPI_ISSET(direction_bitmask, proto.app_protocol))) { /* We consider NDPI_PROTOCOL_UNKNOWN as a protocol to be allowed */ return device_proto_forbidden_app; } return device_proto_allowed; } /* ******************************************* */ void Ntop::resetStats() { char buf[32]; last_stats_reset = time(NULL); snprintf(buf, sizeof(buf), "%ld", last_stats_reset); /* Saving this is essential to reset inactive hosts across ntopng restarts */ getRedis()->set(LAST_RESET_TIME, buf); } /* ******************************************* */ void Ntop::refreshCPULoad() { if(Utils::getCPULoad(&cpu_stats)) cpu_load = cpu_stats.load; else cpu_load = -1; } /* ******************************************* */ bool Ntop::getCPULoad(float *out) { bool rv; if(cpu_load >= 0) { *out = cpu_load; rv = true; } else rv = false; return(rv); } /* ******************************************* */ bool Ntop::initnDPIReload() { bool rc = false; for(u_int i = 0; iinitnDPIReload(); return(rc); } /* ******************************************* */ bool Ntop::isnDPIReloadInProgress() { bool rc = false; for(u_int i = 0; iisnDPIReloadInProgress(); return(rc); } /* ******************************************* */ void Ntop::finalizenDPIReload() { for(u_int i = 0; ifinalizenDPIReload(); } /* ******************************************* */ bool Ntop::nDPILoadIPCategory(char *what, ndpi_protocol_category_t id, char *list_name) { char *persistent_name = getPersistentCustomListName(list_name); bool success = true; for(u_int i = 0; inDPILoadIPCategory(what, id, persistent_name)) success = false; } } return success; } /* ******************************************* */ bool Ntop::nDPILoadHostnameCategory(char *what, ndpi_protocol_category_t id, char *list_name) { char *persistent_name = getPersistentCustomListName(list_name); bool success = true; for(u_int i = 0; inDPILoadHostnameCategory(what, id, persistent_name)) success = false; } } return success; } /* ******************************************* */ int Ntop::nDPILoadMaliciousJA3Signatures(const char *file_path) { int rc = 0; for(u_int i = 0; inDPILoadMaliciousJA3Signatures(file_path); return(rc /* last one returned */); } /* ******************************************* */ ndpi_protocol_category_t Ntop::get_ndpi_proto_category(u_int protoid) { for(u_int i = 0; iget_ndpi_proto_category(protoid)); return(NDPI_PROTOCOL_CATEGORY_UNSPECIFIED); } /* ******************************************* */ void Ntop::setnDPIProtocolCategory(u_int16_t protoId, ndpi_protocol_category_t protoCategory) { for(u_int i = 0; isetnDPIProtocolCategory(protoId, protoCategory); } /* *************************************** */ void Ntop::setLastInterfacenDPIReload(time_t now) { for(u_int i = 0; isetLastInterfacenDPIReload(now); } /* *************************************** */ bool Ntop::needsnDPICleanup() { bool rc = false; for(u_int i = 0; ineedsnDPICleanup(); return(rc); } /* *************************************** */ u_int16_t Ntop::getnDPIProtoByName(const char *name) { NetworkInterface *iface = getFirstInterface(); if(iface) return iface->getnDPIProtoByName(name); return(NDPI_PROTOCOL_UNKNOWN); } /* *************************************** */ void Ntop::setnDPICleanupNeeded(bool needed) { for(u_int i = 0; isetnDPICleanupNeeded(needed); } /* *************************************** */ void Ntop::setScriptsDir() { #ifdef WIN32 snprintf(scripts_dir, sizeof(scripts_dir), "%s\\scripts", get_working_dir()); #else snprintf(scripts_dir, sizeof(scripts_dir), "%s/scripts", get_working_dir()); #endif } /* ******************************************* */ inline int16_t Ntop::localNetworkLookup(int family, void *addr, u_int8_t *network_mask_bits) { return(local_network_tree.findAddress(family, addr, network_mask_bits)); } /* **************************************** */ u_int16_t Ntop::getLocalNetworkId(const char *address_str) { u_int16_t i; for(i = 0; i< local_network_tree.getNumAddresses(); i++) { if(!strcmp(address_str, local_network_names[i])) return(i); } return((u_int16_t)-1); } /* ******************************************* */ bool Ntop::addLocalNetwork(char *_net) { char *net, *position_ptr; char alias[64] = ""; int id = local_network_tree.getNumAddresses(), pos = 0; if(id >= CONST_MAX_NUM_NETWORKS) { ntop->getTrace()->traceEvent(TRACE_ERROR, "Too many networks defined (%d): ignored %s", id, _net); return(false); } // Getting the pointer and the position to the "=" indicator position_ptr = strstr(_net, "="); pos = (position_ptr == NULL ? 0 : position_ptr - _net); if(pos) { // "=" indicator is present inside the string // Separating the alias from the network net = strndup(_net, pos); memcpy(alias, position_ptr + 1, strlen(_net) - pos - 1); } else { net = strdup(_net); } if(net == NULL) { ntop->getTrace()->traceEvent(TRACE_WARNING, "Not enough memory"); return(false); } // Adding the Network to the local Networks if (!local_network_tree.addAddresses(net)) { ntop->getTrace()->traceEvent(TRACE_WARNING, "Failure adding address"); free(net); return(false); } local_network_names[id] = net; // Adding, if available, the alias if(pos) { char out[128] = { '\0' }; u_int len = ndpi_min(strlen(alias), sizeof(out)-2); for(u_int i=0, j=0; igetTrace()->traceEvent(TRACE_INFO, "Added Local Network %s", net); return(true); } /* ******************************************* */ bool Ntop::getLocalNetworkAlias(lua_State *vm, u_int16_t network_id) { char *alias = local_network_aliases[network_id]; // Checking if the network has an alias if(!alias) return false; lua_pushstring(vm, alias); return true; } /* ******************************************* */ /* Format: 131.114.21.0/24,10.0.0.0/255.0.0.0 */ void Ntop::addLocalNetworkList(const char *rule) { char *tmp, *net = strtok_r((char *) rule, ",", &tmp); while(net != NULL) { if(!addLocalNetwork(net)) return; net = strtok_r(NULL, ",", &tmp); } } /* ******************************************* */ bool Ntop::luaFlowCheckInfo(lua_State *vm, std::string check_name) const { FlowChecksLoader *fcl = flow_checks_loader; if(fcl) return fcl->luaCheckInfo(vm, check_name); return false; } /* ******************************************* */ void Ntop::luaClickHouseStats(lua_State *vm) const { #if defined(HAVE_CLICKHOUSE) && defined(HAVE_MYSQL) if(clickhouseImport) { clickhouseImport->lua(vm); return; } #endif lua_pushnil(vm); } /* ******************************************* */ bool Ntop::luaHostCheckInfo(lua_State *vm, std::string check_name) const { HostChecksLoader *hcl = host_checks_loader; if(hcl) return hcl->luaCheckInfo(vm, check_name); return false; } /* ******************************************* */ bool Ntop::isDbCreated() { for(int i = 0; i < MAX_NUM_INTERFACE_IDS; i++) { NetworkInterface *iface = ntop->getInterface(i); if(iface && (!iface->isDbCreated())) return(false); } return(true); } /* ******************************************* */ #ifndef HAVE_NEDGE bool Ntop::broadcastIPSMessage(char *msg) { bool rc = false; if(prefs->getZMQPublishEventsURL() == NULL) return(false); /* Jeopardized users_m lock :-) */ users_m.lock(__FILE__, __LINE__); if(zmqPublisher == NULL) { try { zmqPublisher = new ZMQPublisher(prefs->getZMQPublishEventsURL()); } catch(...) { zmqPublisher = NULL; } } if(!zmqPublisher) { ntop->getTrace()->traceEvent(TRACE_WARNING, "Unable to create ZMQ publisher"); users_m.unlock(__FILE__, __LINE__); return(false); } if(msg) rc = zmqPublisher->sendIPSMessage(msg); users_m.unlock(__FILE__, __LINE__); return(rc); } #endif /* ******************************************* */ #ifndef WIN32 /* ******************************************* */ Ping* Ntop::getPing(char *ifname) { if(!can_send_icmp) return(NULL); if((ifname == NULL) || (ifname[0] == '\0')) return(default_ping); else { std::map::iterator it = ping.find(ifname); if(it == ping.end()) { ntop->getTrace()->traceEvent(TRACE_WARNING, "Unable to find ping for interface %s", ifname); return(default_ping); } else return(it->second); } } /* ******************************************* */ void Ntop::initPing() { if(!can_send_icmp) return; for(int i=0; igetIfType()) { case interface_type_PF_RING: case interface_type_PCAP: { char *name = iface[i]->get_name(); Ping *p = new (std::nothrow)Ping(name); if(p) { ping[std::string(name)] = p; ntop->getTrace()->traceEvent(TRACE_NORMAL, "Created pinger for %s", name); } else ntop->getTrace()->traceEvent(TRACE_WARNING, "Unable to create ping for interface %s", name); } break; default: ntop->getTrace()->traceEvent(TRACE_NORMAL, "Skipping pinger for %s [ifType: %u]", iface[i]->get_name(), iface[i]->getIfType()); /* Nothing to do for other interface types */ break; } } } /* ******************************************* */ void Ntop::collectResponses(lua_State* vm) { lua_newtable(vm); default_ping->collectResponses(vm, false /* IPv4 */); default_ping->collectResponses(vm, true /* IPv6 */); for(std::map::iterator it = ping.begin(); it != ping.end(); ++it) { it->second->collectResponses(vm, false /* IPv4 */); it->second->collectResponses(vm, true /* IPv6 */); } } /* ******************************************* */ void Ntop::collectContinuousResponses(lua_State* vm) { lua_newtable(vm); cping->collectResponses(vm, false /* IPv4 */); cping->collectResponses(vm, true /* IPv6 */); } #endif /* ******************************************* */ /* This method is needed to have a string that is not deallocated after a call, but that is persistent inside nDPI */ char* Ntop::getPersistentCustomListName(char *list_name) { std::string key(list_name); std::map::iterator it = cachedCustomLists.find(key); if(it == cachedCustomLists.end()) { /* Not found */ cachedCustomLists[key] = true; it = cachedCustomLists.find(key); } return((char*)it->first.c_str()); } /* ******************************************* */ void Ntop::setZoneInfo() { #ifndef WIN32 #ifdef __FreeBSD__ FILE *fd = fopen("/var/db/zoneinfo", "r"); zoneinfo = NULL; if(fd != NULL) { char timezone[64]; if(fgets(timezone, sizeof(timezone), fd)) { int len = strlen(timezone); if(len > 0) timezone[len-1] = '\0'; zoneinfo = strdup(timezone); } fclose(fd); } else { /* Last resort */ const char *command_buf = "find /usr/share/zoneinfo -type f | xargs md5sum | grep `md5sum -q /etc/localtime` | tail -1 | cut -d '/' -f 5-"; FILE *fp; if((fp = popen(command_buf, "r")) != NULL) { char line[256]; if(fgets(line, sizeof(line), fp) != NULL) zoneinfo = strdup(line); pclose(fp); } } #else char buf[64]; ssize_t rc = readlink("/etc/localtime", buf, sizeof(buf)); u_int num_slash = 0; zoneinfo = NULL; if(rc > 0) { buf[rc] = '\0'; rc--; while(rc > 0) { if(buf[rc] == '/') { if(++num_slash == 2) break; } rc--; } if(num_slash == 2) { rc++; zoneinfo = strdup(&buf[rc]); } } #endif #else zoneinfo = getWindowsTimezone(); #endif /* WIN32 */ if(zoneinfo == NULL) { ntop->getTrace()->traceEvent(TRACE_WARNING, "Unable to find timezone: using UTC"); zoneinfo = strdup("Europe/London"); /* UTC */ } if(zoneinfo) ntop->getTrace()->traceEvent(TRACE_INFO, "ntopng timezone set to %s", zoneinfo); } /* ******************************************* */ // #define DEBUG_SPEEDTEST #include "../third-party/speedtest.c" void Ntop::speedtest(lua_State *vm) { json_object *rc; /* We need to make sure that only one caller at time calls speedtest as - the speedtest code is not reentrant - running multiple tests concurrently reports wrong results as clients compete for the same bandwidth */ speedtest_m.lock(__FILE__, __LINE__); rc = ::speedtest(); if(rc) { lua_pushstring(vm, json_object_to_json_string(rc)); json_object_put(rc); /* Free memory */ } else lua_pushnil(vm); speedtest_m.unlock(__FILE__, __LINE__); } /* ******************************************* */ bool Ntop::createPcapInterface(const char *path, int *iface_id) { #ifndef HAVE_NEDGE NetworkInterface *new_iface, *old_iface = NULL; #endif bool ret; #ifndef HAVE_NEDGE u_int slot_id; if(old_iface_to_purge != NULL) { delete old_iface_to_purge; old_iface_to_purge = NULL; } if(*iface_id != -1) { for(int i=0; iget_id() == *iface_id) { old_iface = iface[i], slot_id = i; break; } } } try { errno = 0; new_iface = new PcapInterface((const char*)path, (u_int8_t)ntop->get_num_interfaces(), true /* delete pcap when done */); if(old_iface == NULL) { /* Allocate new interface */ if(registerInterface(new_iface)) { initInterface(new_iface); new_iface->allocateStructures(); new_iface->startPacketPolling(); *iface_id = new_iface->get_id(); } } else { NetworkInterface *old = iface[slot_id]; initInterface(new_iface); new_iface->allocateStructures(); new_iface->startPacketPolling(); m.lock(__FILE__, __LINE__); iface[slot_id] = new_iface; /* Swap interfaces */ old->shutdown(); /* Wait until the interface is shutdown */ while(old->isRunning()) sleep(1); /* Trick to avoid crashing while using the same interface we want to free */ old_iface_to_purge = old; *iface_id = new_iface->get_id(); m.unlock(__FILE__, __LINE__); } ret = true; } catch(int err) { getTrace()->traceEvent(TRACE_ERROR, "Unable to open interface %s with pcap [%d]: %s", path, err, strerror(err)); ret = false; } #else ret = false; #endif return(ret); } /* ******************************************* */ void Ntop::incBlacklisHits(std::string listname) { blStats.incHits(listname); }