#include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Log.h" #if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) #include #include #elif defined(_WIN32) #include #include #include #include #include #include #include #pragma comment(lib, "IPHLPAPI.lib") #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) int inet_pton(int af, const char *src, void *dst) { /* This function was written by Petar Korponai?. See http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found */ struct sockaddr_storage ss; int size = sizeof (ss); char src_copy[INET6_ADDRSTRLEN + 1]; ZeroMemory (&ss, sizeof (ss)); strncpy_s (src_copy, src, INET6_ADDRSTRLEN + 1); src_copy[INET6_ADDRSTRLEN] = 0; if (WSAStringToAddress (src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) { switch (af) { case AF_INET: *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr; return 1; case AF_INET6: *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr; return 1; } } return 0; } #endif namespace i2p { namespace util { namespace config { std::map mapArgs; std::map > mapMultiArgs; void OptionParser(int argc, const char* const argv[]) { mapArgs.clear(); mapMultiArgs.clear(); for(int i = 1; i < argc; ++i) { std::string strKey (argv[i]); std::string strValue; size_t has_data = strKey.find('='); if(has_data != std::string::npos) { strValue = strKey.substr(has_data+1); strKey = strKey.substr(0, has_data); } #ifdef _WIN32 boost::to_lower(strKey); if(boost::algorithm::starts_with(strKey, "/")) strKey = "-" + strKey.substr(1); #endif if(strKey[0] != '-') break; mapArgs[strKey] = strValue; mapMultiArgs[strKey].push_back(strValue); } for(auto& entry : mapArgs) { std::string name = entry.first; // interpret --foo as -foo (as long as both are not set) if (name.find("--") == 0) { std::string singleDash(name.begin()+1, name.end()); if (mapArgs.count(singleDash) == 0) mapArgs[singleDash] = entry.second; name = singleDash; } } } const char* GetCharArg(const std::string& strArg, const std::string& nDefault) { if(mapArgs.count(strArg)) return mapArgs[strArg].c_str(); return nDefault.c_str(); } std::string GetArg(const std::string& strArg, const std::string& strDefault) { if(mapArgs.count(strArg)) return mapArgs[strArg]; return strDefault; } int GetArg(const std::string& strArg, int nDefault) { if(mapArgs.count(strArg)) return stoi(mapArgs[strArg]); return nDefault; } bool HasArg(const std::string& strArg) { return mapArgs.count(strArg); } } namespace filesystem { std::string appName("i2pd"); void SetAppName(const std::string& name) { appName = name; } std::string GetAppName() { return appName; } const boost::filesystem::path& GetDataDir() { static boost::filesystem::path path; // TODO: datadir parameter is useless because GetDataDir is called before OptionParser // and mapArgs is not initialized yet /*if (i2p::util::config::mapArgs.count("-datadir")) path = boost::filesystem::system_complete(i2p::util::config::mapArgs["-datadir"]); else */ path = GetDefaultDataDir(); if(!boost::filesystem::exists(path)) { // Create data directory if(!boost::filesystem::create_directory(path)) { LogPrint("Failed to create data directory!"); path = ""; return path; } } if(!boost::filesystem::is_directory(path)) path = GetDefaultDataDir(); return path; } std::string GetFullPath(const std::string& filename) { std::string fullPath = GetDataDir().string(); #ifndef _WIN32 fullPath.append("/"); #else fullPath.append("\\"); #endif fullPath.append(filename); return fullPath; } boost::filesystem::path GetConfigFile() { boost::filesystem::path pathConfigFile(i2p::util::config::GetArg("-conf", "i2p.conf")); if(!pathConfigFile.is_complete()) pathConfigFile = GetDataDir() / pathConfigFile; return pathConfigFile; } boost::filesystem::path GetTunnelsConfigFile() { boost::filesystem::path pathTunnelsConfigFile(i2p::util::config::GetArg("-tunnelscfg", "tunnels.cfg")); if(!pathTunnelsConfigFile.is_complete()) pathTunnelsConfigFile = GetDataDir() / pathTunnelsConfigFile; return pathTunnelsConfigFile; } boost::filesystem::path GetWebuiDataDir() { return GetDataDir() / "webui"; } boost::filesystem::path GetDefaultDataDir() { // Custom path, or default path: // Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd // Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd // Mac: ~/Library/Application Support/i2pd // Unix: ~/.i2pd #ifdef I2PD_CUSTOM_DATA_PATH return boost::filesystem::path(std::string(I2PD_CUSTOM_DATA_PATH)); #else #ifdef _WIN32 // Windows char localAppData[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); return boost::filesystem::path(std::string(localAppData) + "\\" + appName); #else boost::filesystem::path pathRet; char* pszHome = getenv("HOME"); if(pszHome == NULL || strlen(pszHome) == 0) pathRet = boost::filesystem::path("/"); else pathRet = boost::filesystem::path(pszHome); #ifdef __APPLE__ // Mac pathRet /= "Library/Application Support"; boost::filesystem::create_directory(pathRet); return pathRet / appName; #else // Unix return pathRet / (std::string (".") + appName); #endif #endif #endif } void ReadConfigFile(std::map& mapSettingsRet, std::map >& mapMultiSettingsRet) { boost::filesystem::ifstream streamConfig(GetConfigFile()); if(!streamConfig.good()) return; // No i2pd.conf file is OK std::set setOptions; setOptions.insert("*"); for(boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) { // Don't overwrite existing settings so command line settings override i2pd.conf std::string strKey = std::string("-") + it->string_key; if(mapSettingsRet.count(strKey) == 0) { mapSettingsRet[strKey] = it->value[0]; } mapMultiSettingsRet[strKey].push_back(it->value[0]); } } boost::filesystem::path GetCertificatesDir() { return GetDataDir () / "certificates"; } void InstallFiles() { namespace bfs = boost::filesystem; boost::system::error_code e; const bfs::path source = bfs::canonical( config::GetArg("-install", "webui"), e ); const bfs::path destination = GetWebuiDataDir(); if(e || !bfs::is_directory(source)) throw std::runtime_error("Given directory is invalid or does not exist"); // TODO: check that destination is not in source try { CopyDir(source, destination); } catch(...) { throw std::runtime_error("Could not copy webui folder to i2pd folder."); } } void CopyDir(const boost::filesystem::path& src, const boost::filesystem::path& dest) { namespace bfs = boost::filesystem; bfs::create_directory(dest); for(bfs::directory_iterator file(src); file != bfs::directory_iterator(); ++file) { const bfs::path current(file->path()); if(bfs::is_directory(current)) CopyDir(current, dest / current.filename()); else bfs::copy_file( current, dest / current.filename(), bfs::copy_option::overwrite_if_exists ); } } } namespace http { std::string httpRequest(const std::string& address) { try { i2p::util::http::url u(address); boost::asio::ip::tcp::iostream site; // please don't uncomment following line because it's not compatible with boost 1.46 // 1.46 is default boost for Ubuntu 12.04 LTS //site.expires_from_now (boost::posix_time::seconds(30)); if(u.port_ == 80) site.connect(u.host_, "http"); else { std::stringstream ss; ss << u.port_; site.connect(u.host_, ss.str()); } if(site) { // User-Agent is needed to get the server list routerInfo files. site << "GET " << u.path_ << " HTTP/1.1\r\nHost: " << u.host_ << "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" << "Connection: close\r\n\r\n"; // read response and extract content return GetHttpContent(site); } else { LogPrint("Can't connect to ", address); return ""; } } catch(const std::exception& ex) { LogPrint("Failed to download ", address, " : ", ex.what()); return ""; } } std::string GetHttpContent (std::istream& response) { std::string version, statusMessage; response >> version; // HTTP version int status; response >> status; // status std::getline (response, statusMessage); if(status == 200) { // OK bool isChunked = false; std::string header; while(!response.eof() && header != "\r") { std::getline(response, header); auto colon = header.find (':'); if(colon != std::string::npos) { std::string field = header.substr (0, colon); if(field == i2p::util::http::TRANSFER_ENCODING) isChunked = (header.find("chunked", colon + 1) != std::string::npos); } } std::stringstream ss; if(isChunked) MergeChunkedResponse(response, ss); else ss << response.rdbuf(); return ss.str(); } else { LogPrint("HTTP response ", status); return ""; } } void MergeChunkedResponse(std::istream& response, std::ostream& merged) { while(!response.eof()) { std::string hexLen; int len; std::getline(response, hexLen); std::istringstream iss(hexLen); iss >> std::hex >> len; if(!len) break; char* buf = new char[len]; response.read(buf, len); merged.write(buf, len); delete[] buf; std::getline(response, hexLen); // read \r\n after chunk } } int httpRequestViaI2pProxy(const std::string& address, std::string &content) { content = ""; try { boost::asio::ip::tcp::iostream site; // please don't uncomment following line because it's not compatible with boost 1.46 // 1.46 is default boost for Ubuntu 12.04 LTS //site.expires_from_now (boost::posix_time::seconds(30)); { std::stringstream ss; ss << i2p::util::config::GetArg("-httpproxyport", 4446); site.connect("127.0.0.1", ss.str()); } if(site) { i2p::util::http::url u(address); std::stringstream ss; ss << "GET " << address << " HTTP/1.0" << std::endl; ss << "Host: " << u.host_ << std::endl; ss << "Accept: */*" << std::endl; ss << "User - Agent: Wget / 1.11.4" << std::endl; ss << "Connection: close" << std::endl; ss << std::endl; site << ss.str(); // read response std::string version, statusMessage; site >> version; // HTTP version int status; site >> status; // status std::getline(site, statusMessage); if(status == 200) { // OK std::string header; while(std::getline(site, header) && header != "\r"){} std::stringstream ss; ss << site.rdbuf(); content = ss.str(); return status; } else { LogPrint("HTTP response ", status); return status; } } else { LogPrint("Can't connect to proxy"); return 408; } } catch (std::exception& ex) { LogPrint("Failed to download ", address, " : ", ex.what()); return 408; } } url::url(const std::string& url_s) { portstr_ = "80"; port_ = 80; user_ = ""; pass_ = ""; parse(url_s); } void url::parse(const std::string& url_s) { const std::string prot_end("://"); std::string::const_iterator prot_i = search( url_s.begin(), url_s.end(), prot_end.begin(), prot_end.end() ); protocol_.reserve(distance(url_s.begin(), prot_i)); // Make portocol lowercase transform( url_s.begin(), prot_i, back_inserter(protocol_), std::ptr_fun(std::tolower) ); if(prot_i == url_s.end()) return; advance(prot_i, prot_end.length()); std::string::const_iterator path_i = find(prot_i, url_s.end(), '/'); host_.reserve(distance(prot_i, path_i)); // Make host lowerase transform(prot_i, path_i, back_inserter(host_), std::ptr_fun(std::tolower)); // parse user/password auto user_pass_i = find(host_.begin(), host_.end(), '@'); if(user_pass_i != host_.end()) { std::string user_pass = std::string(host_.begin(), user_pass_i); auto pass_i = find(user_pass.begin(), user_pass.end(), ':'); if (pass_i != user_pass.end()) { user_ = std::string(user_pass.begin(), pass_i); pass_ = std::string(pass_i + 1, user_pass.end()); } else user_ = user_pass; host_.assign(user_pass_i + 1, host_.end()); } // parse port auto port_i = find(host_.begin(), host_.end(), ':'); if(port_i != host_.end()) { portstr_ = std::string(port_i + 1, host_.end()); host_.assign(host_.begin(), port_i); try { port_ = boost::lexical_cast(portstr_); } catch(const std::exception& e) { port_ = 80; } } std::string::const_iterator query_i = find(path_i, url_s.end(), '?'); path_.assign(path_i, query_i); if( query_i != url_s.end() ) ++query_i; query_.assign(query_i, url_s.end()); } std::string urlDecode(const std::string& data) { std::string res(data); for(size_t pos = res.find('%'); pos != std::string::npos; pos = res.find('%', pos + 1)) { const char c = strtol(res.substr(pos + 1, 2).c_str(), NULL, 16); res.replace(pos, 3, 1, c); } return res; } } namespace net { #if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback) { ifaddrs* ifaddr, *ifa = nullptr; if(getifaddrs(&ifaddr) == -1) { LogPrint(eLogError, "Can't excute getifaddrs"); return fallback; } int family = 0; // look for interface matching local address for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { if(!ifa->ifa_addr) continue; family = ifa->ifa_addr->sa_family; if(family == AF_INET && localAddress.is_v4()) { sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr; if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) break; // address matches } else if(family == AF_INET6 && localAddress.is_v6()) { sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr; if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) break; // address matches } } int mtu = fallback; if(ifa && family) { // interface found? int fd = socket(family, SOCK_DGRAM, 0); if(fd > 0) { ifreq ifr; strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) mtu = ifr.ifr_mtu; // MTU else LogPrint (eLogError, "Failed to run ioctl"); close(fd); } else LogPrint(eLogError, "Failed to create datagram socket"); } else { LogPrint( eLogWarning, "Interface for local address", localAddress.to_string(), " not found" ); } freeifaddrs(ifaddr); return mtu; } #elif defined(_WIN32) int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) { ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; if(GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); } DWORD dwRetVal = GetAdaptersAddresses( AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); if(dwRetVal != NO_ERROR) { LogPrint( eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed" ); FREE(pAddresses); return fallback; } pCurrAddresses = pAddresses; while(pCurrAddresses) { PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; if(pUnicast == nullptr) { LogPrint( eLogError, "GetMTU() has failed: not a unicast ipv4 address, this is not supported" ); } for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr; if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) { auto result = pAddresses->Mtu; FREE(pAddresses); return result; } pUnicast = pUnicast->Next; } pCurrAddresses = pCurrAddresses->Next; } LogPrint(eLogError, "GetMTU() error: no usable unicast ipv4 addresses found"); FREE(pAddresses); return fallback; } int GetMTUWindowsIpv6(sockaddr_in6 inputAddress, int fallback) { ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; if(GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); } DWORD dwRetVal = GetAdaptersAddresses( AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); if(dwRetVal != NO_ERROR) { LogPrint( eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed" ); FREE(pAddresses); return fallback; } bool found_address = false; pCurrAddresses = pAddresses; while(pCurrAddresses) { PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; if(pUnicast == nullptr) { LogPrint( eLogError, "GetMTU() has failed: not a unicast ipv6 address, this is not supported" ); } for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; for (int j = 0; j != 8; ++j) { if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) { break; } else { found_address = true; } } if (found_address) { auto result = pAddresses->Mtu; FREE(pAddresses); pAddresses = nullptr; return result; } pUnicast = pUnicast->Next; } pCurrAddresses = pCurrAddresses->Next; } LogPrint(eLogError, "GetMTU() error: no usable unicast ipv6 addresses found"); FREE(pAddresses); return fallback; } int GetMTUWindows(const boost::asio::ip::address& localAddress, int fallback) { #ifdef UNICODE string localAddress_temporary = localAddress.to_string(); wstring localAddressUniversal(localAddress_temporary.begin(), localAddress_temporary.end()); #else std::string localAddressUniversal = localAddress.to_string(); #endif if(localAddress.is_v4()) { sockaddr_in inputAddress; inet_pton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); return GetMTUWindowsIpv4(inputAddress, fallback); } else if(localAddress.is_v6()) { sockaddr_in6 inputAddress; inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); return GetMTUWindowsIpv6(inputAddress, fallback); } else { LogPrint(eLogError, "GetMTU() has failed: address family is not supported"); return fallback; } } #endif // WIN32 int GetMTU(const boost::asio::ip::address& localAddress) { const int fallback = 576; // fallback MTU #if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) return GetMTUUnix(localAddress, fallback); #elif defined(WIN32) return GetMTUWindows(localAddress, fallback); #endif return fallback; } } } // util } // i2p