X-Git-Url: https://git.stg.codes/stg.git/blobdiff_plain/75dfb67fd068cc291e22f4f813b407812ebd09d2..ede5927555814a1da45d4755a760fc07243ad9ae:/projects/rlm_stg/stg_client.cpp diff --git a/projects/rlm_stg/stg_client.cpp b/projects/rlm_stg/stg_client.cpp index 113e71c9..399b971d 100644 --- a/projects/rlm_stg/stg_client.cpp +++ b/projects/rlm_stg/stg_client.cpp @@ -18,278 +18,583 @@ * Author : Maxim Mamontov */ -/* - * Realization of data access via Stargazer for RADIUS - * - * $Revision: 1.8 $ - * $Date: 2010/04/16 12:30:02 $ - * - */ +#include "stg_client.h" -#include -#include -#include // close +#include "stg/json_parser.h" +#include "stg/json_generator.h" +#include "stg/common.h" +#include +#include #include #include -#include -#include -#include +#include +#include +#include // UNIX +#include // IP +#include // TCP +#include -#include "stg_client.h" +using STG::JSON::Parser; +using STG::JSON::PairsParser; +using STG::JSON::EnumParser; +using STG::JSON::NodeParser; +using STG::JSON::Gen; +using STG::JSON::MapGen; +using STG::JSON::StringGen; -typedef std::vector > PAIRS; +namespace { -//----------------------------------------------------------------------------- +double CONN_TIMEOUT = 5; +double PING_TIMEOUT = 1; -STG_CLIENT::STG_CLIENT(const std::string & host, uint16_t port, uint16_t lp, const std::string & pass) - : password(pass), - framedIP(0) +STG_CLIENT* stgClient = NULL; + +std::string toStage(STG_CLIENT::TYPE type) { -/*sock = socket(AF_INET, SOCK_DGRAM, 0); -if (sock == -1) + switch (type) { - std::string message = strerror(errno); - message = "Socket create error: '" + message + "'"; - throw std::runtime_error(message); + case STG_CLIENT::AUTHORIZE: return "authorize"; + case STG_CLIENT::AUTHENTICATE: return "authenticate"; + case STG_CLIENT::POST_AUTH: return "postauth"; + case STG_CLIENT::PRE_ACCT: return "preacct"; + case STG_CLIENT::ACCOUNT: return "accounting"; } + return ""; +} -struct hostent * he = NULL; -he = gethostbyname(host.c_str()); -if (he == NULL) - { - throw std::runtime_error("gethostbyname error"); - } +enum Packet +{ + PING, + PONG, + DATA +}; -outerAddr.sin_family = AF_INET; -outerAddr.sin_port = htons(port); -outerAddr.sin_addr.s_addr = *(uint32_t *)he->h_addr; +std::map packetCodes; +std::map resultCodes; -InitEncrypt(&ctx, password); +class PacketParser : public EnumParser +{ + public: + PacketParser(NodeParser* next, Packet& packet, std::string& packetStr) + : EnumParser(next, packet, packetStr, packetCodes) + { + if (!packetCodes.empty()) + return; + packetCodes["ping"] = PING; + packetCodes["pong"] = PONG; + packetCodes["data"] = DATA; + } +}; + +class ResultParser : public EnumParser +{ + public: + ResultParser(NodeParser* next, bool& result, std::string& resultStr) + : EnumParser(next, result, resultStr, resultCodes) + { + if (!resultCodes.empty()) + return; + resultCodes["no"] = false; + resultCodes["ok"] = true; + } +}; + +class TopParser : public NodeParser +{ + public: + TopParser() + : m_packetParser(this, m_packet, m_packetStr), + m_resultParser(this, m_result, m_resultStr), + m_replyParser(this, m_reply), + m_modifyParser(this, m_modify) + {} + + virtual NodeParser* parseStartMap() { return this; } + virtual NodeParser* parseMapKey(const std::string& value) + { + std::string key = ToLower(value); + + if (key == "packet") + return &m_packetParser; + else if (key == "result") + return &m_resultParser; + else if (key == "reply") + return &m_replyParser; + else if (key == "modify") + return &m_modifyParser; + + return this; + } + virtual NodeParser* parseEndMap() { return this; } + + const std::string& packetStr() const { return m_packetStr; } + Packet packet() const { return m_packet; } + const std::string& resultStr() const { return m_resultStr; } + bool result() const { return m_result; } + const PairsParser::Pairs& reply() const { return m_reply; } + const PairsParser::Pairs& modify() const { return m_modify; } + + private: + std::string m_packetStr; + Packet m_packet; + std::string m_resultStr; + bool m_result; + PairsParser::Pairs m_reply; + PairsParser::Pairs m_modify; + + PacketParser m_packetParser; + ResultParser m_resultParser; + PairsParser m_replyParser; + PairsParser m_modifyParser; +}; + +class ProtoParser : public Parser +{ + public: + ProtoParser() : Parser( &m_topParser ) {} -PrepareNet();*/ -} + const std::string& packetStr() const { return m_topParser.packetStr(); } + Packet packet() const { return m_topParser.packet(); } + const std::string& resultStr() const { return m_topParser.resultStr(); } + bool result() const { return m_topParser.result(); } + const PairsParser::Pairs& reply() const { return m_topParser.reply(); } + const PairsParser::Pairs& modify() const { return m_topParser.modify(); } -STG_CLIENT::~STG_CLIENT() -{ -/*close(sock);*/ -} + private: + TopParser m_topParser; +}; -int STG_CLIENT::PrepareNet() +class PacketGen : public Gen { -return 0; + public: + PacketGen(const std::string& type) + : m_type(type) + { + m_gen.add("packet", m_type); + } + void run(yajl_gen_t* handle) const + { + m_gen.run(handle); + } + PacketGen& add(const std::string& key, const std::string& value) + { + m_gen.add(key, new StringGen(value)); + return *this; + } + PacketGen& add(const std::string& key, MapGen* map) + { + m_gen.add(key, map); + return *this; + } + private: + MapGen m_gen; + StringGen m_type; +}; + } -int STG_CLIENT::Send(const RAD_PACKET & packet) +class STG_CLIENT::Impl { -/*char buf[RAD_MAX_PACKET_LEN]; - -Encrypt(&ctx, buf, (char *)&packet, sizeof(RAD_PACKET) / 8); +public: + Impl(const std::string& address, Callback callback, void* data); + ~Impl(); -int res = sendto(sock, buf, sizeof(RAD_PACKET), 0, (struct sockaddr *)&outerAddr, sizeof(outerAddr)); + bool stop(); -if (res == -1) - errorStr = "Error sending data"; + bool request(TYPE type, const std::string& userName, const std::string& password, const PAIRS& pairs); -return res;*/ -} +private: + ChannelConfig m_config; -int STG_CLIENT::RecvData(RAD_PACKET * packet) -{ -/*char buf[RAD_MAX_PACKET_LEN]; -int res; + int m_sock; -struct sockaddr_in addr; -socklen_t len = sizeof(struct sockaddr_in); + bool m_running; + bool m_stopped; -res = recvfrom(sock, buf, RAD_MAX_PACKET_LEN, 0, reinterpret_cast(&addr), &len); -if (res == -1) - { - errorStr = "Error receiving data"; - return -1; - } + time_t m_lastPing; + time_t m_lastActivity; -Decrypt(&ctx, (char *)packet, buf, res / 8); + pthread_t m_thread; + pthread_mutex_t m_mutex; -return 0;*/ -} + Callback m_callback; + void* m_data; -int STG_CLIENT::Request(RAD_PACKET * packet, const std::string & login, const std::string & svc, uint8_t packetType) -{ -/*int res; + ProtoParser m_parser; + + void m_writeHeader(TYPE type, const std::string& userName, const std::string& password); + void m_writePairBlock(const PAIRS& source); + PAIRS m_readPairBlock(); + + static void* run(void* ); + + void runImpl(); + + int connect(); + int connectTCP(); + int connectUNIX(); -memcpy((void *)&packet->magic, (void *)RAD_ID, RAD_MAGIC_LEN); -packet->protoVer[0] = '0'; -packet->protoVer[1] = '1'; -packet->packetType = packetType; -packet->ip = 0; -strncpy((char *)packet->login, login.c_str(), RAD_LOGIN_LEN); -strncpy((char *)packet->service, svc.c_str(), RAD_SERVICE_LEN); + bool read(); + bool tick(); -res = Send(*packet); -if (res == -1) - return -1; + bool process(); + bool processPing(); + bool processPong(); + bool processData(); + bool sendPing(); + bool sendPong(); -res = RecvData(packet); -if (res == -1) - return -1; + static bool write(void* data, const char* buf, size_t size); +}; -if (strncmp((char *)packet->magic, RAD_ID, RAD_MAGIC_LEN)) +ChannelConfig::ChannelConfig(std::string addr) +{ + // unix:pass@/var/run/stg.sock + // tcp:secret@192.168.0.1:12345 + // udp:key@isp.com.ua:54321 + + size_t pos = addr.find_first_of(':'); + if (pos == std::string::npos) + throw Error("Missing transport name."); + transport = ToLower(addr.substr(0, pos)); + addr = addr.substr(pos + 1); + if (addr.empty()) + throw Error("Missing address to connect to."); + pos = addr.find_first_of('@'); + if (pos != std::string::npos) { + key = addr.substr(0, pos); + addr = addr.substr(pos + 1); + if (addr.empty()) + throw Error("Missing address to connect to."); + } + if (transport == "unix") { - errorStr = "Magic invalid. Wanted: '"; - errorStr += RAD_ID; - errorStr += "', got: '"; - errorStr += (char *)packet->magic; - errorStr += "'"; - return -1; + address = addr; + return; } - -return 0;*/ + pos = addr.find_first_of(':'); + if (pos == std::string::npos) + throw Error("Missing port."); + address = addr.substr(0, pos); + portStr = addr.substr(pos + 1); + if (str2x(portStr, port)) + throw Error("Invalid port value."); } -//----------------------------------------------------------------------------- - -const STG_PAIRS * STG_CLIENT::Authorize(const std::string & login, const std::string & svc) +STG_CLIENT::STG_CLIENT(const std::string& address, Callback callback, void* data) + : m_impl(new Impl(address, callback, data)) { -/*RAD_PACKET packet; +} -userPassword = ""; +STG_CLIENT::~STG_CLIENT() +{ +} -if (Request(&packet, login, svc, RAD_AUTZ_PACKET)) - return -1; +bool STG_CLIENT::stop() +{ + return m_impl->stop(); +} -if (packet.packetType != RAD_ACCEPT_PACKET) - return -1; +bool STG_CLIENT::request(TYPE type, const std::string& userName, const std::string& password, const PAIRS& pairs) +{ + return m_impl->request(type, userName, password, pairs); +} -userPassword = (char *)packet.password;*/ +STG_CLIENT* STG_CLIENT::get() +{ + return stgClient; +} -PAIRS pairs; -pairs.push_back(std::make_pair("Cleartext-Password", userPassword)); +bool STG_CLIENT::configure(const std::string& address, Callback callback, void* data) +{ + if ( stgClient != NULL && stgClient->stop() ) + delete stgClient; + try { + stgClient = new STG_CLIENT(address, callback, data); + return true; + } catch (const ChannelConfig::Error& ex) { + // TODO: Log it + } + return false; +} -return ToSTGPairs(pairs); +STG_CLIENT::Impl::Impl(const std::string& address, Callback callback, void* data) + : m_config(address), + m_sock(connect()), + m_running(false), + m_stopped(true), + m_lastPing(time(NULL)), + m_lastActivity(m_lastPing), + m_callback(callback), + m_data(data) +{ + int res = pthread_create(&m_thread, NULL, run, this); + if (res != 0) + throw Error("Failed to create thread: " + std::string(strerror(errno))); } -const STG_PAIRS * STG_CLIENT::Authenticate(const std::string & login, const std::string & svc) +STG_CLIENT::Impl::~Impl() { -/*RAD_PACKET packet; + stop(); + shutdown(m_sock, SHUT_RDWR); + close(m_sock); +} -userPassword = ""; +bool STG_CLIENT::Impl::stop() +{ + if (m_stopped) + return true; -if (Request(&packet, login, svc, RAD_AUTH_PACKET)) - return -1; + m_running = false; -if (packet.packetType != RAD_ACCEPT_PACKET) - return -1;*/ + for (size_t i = 0; i < 25 && !m_stopped; i++) { + struct timespec ts = {0, 200000000}; + nanosleep(&ts, NULL); + } -PAIRS pairs; + if (m_stopped) { + pthread_join(m_thread, NULL); + return true; + } -return ToSTGPairs(pairs); + return false; } -const STG_PAIRS * STG_CLIENT::PostAuth(const std::string & login, const std::string & svc) +bool STG_CLIENT::Impl::request(TYPE type, const std::string& userName, const std::string& password, const PAIRS& pairs) { -/*RAD_PACKET packet; + boost::scoped_ptr map(new MapGen); + for (PAIRS::const_iterator it = pairs.begin(); it != pairs.end(); ++it) + map->add(it->first, new StringGen(it->second)); + map->add("Radius-Username", new StringGen(userName)); + map->add("Radius-Userpass", new StringGen(password)); -userPassword = ""; + PacketGen gen("data"); + gen.add("stage", toStage(type)) + .add("pairs", map.get()); -if (Request(&packet, login, svc, RAD_POST_AUTH_PACKET)) - return -1; + m_lastPing = time(NULL); -if (packet.packetType != RAD_ACCEPT_PACKET) - return -1; - -if (svc == "Framed-User") - framedIP = packet.ip; -else - framedIP = 0;*/ + return generate(gen, &STG_CLIENT::Impl::write, this); +} -PAIRS pairs; -pairs.push_back(std::make_pair("Framed-IP-Address", inet_ntostring(framedIP))); +void STG_CLIENT::Impl::runImpl() +{ + m_running = true; + + while (m_running) { + fd_set fds; + + FD_ZERO(&fds); + FD_SET(m_sock, &fds); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 500000; + + int res = select(m_sock + 1, &fds, NULL, NULL, &tv); + if (res < 0) + { + //m_error = std::string("'select' is failed: '") + strerror(errno) + "'."; + //m_logger(m_error); + break; + } + + if (!m_running) + break; + + if (res > 0) + { + if (FD_ISSET(m_sock, &fds)) + m_running = read(); + } + else + m_running = tick(); + } -return ToSTGPairs(pairs); + m_stopped = true; } -const STG_PAIRS * STG_CLIENT::PreAcct(const std::string & login, const std::String & service) +int STG_CLIENT::Impl::connect() { -PAIRS pairs; - -return ToSTGPairs(pairs); + if (m_config.transport == "tcp") + return connectTCP(); + else if (m_config.transport == "unix") + return connectUNIX(); + throw Error("Invalid transport type: '" + m_config.transport + "'. Should be 'tcp' or 'unix'."); } -const STG_PAIRS * STG_CLIENT::Account(const std::string & type, const std::string & login, const std::string & svc, const std::string & sessid) +int STG_CLIENT::Impl::connectTCP() { -/*RAD_PACKET packet; + addrinfo hints; + memset(&hints, 0, sizeof(addrinfo)); + + hints.ai_family = AF_INET; /* Allow IPv4 */ + hints.ai_socktype = SOCK_STREAM; /* Stream socket */ + hints.ai_flags = 0; /* For wildcard IP address */ + hints.ai_protocol = 0; /* Any protocol */ + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + addrinfo* ais = NULL; + int res = getaddrinfo(m_config.address.c_str(), m_config.portStr.c_str(), &hints, &ais); + if (res != 0) + throw Error("Error resolvin address '" + m_config.address + "': " + gai_strerror(res)); + + for (addrinfo* ai = ais; ai != NULL; ai = ai->ai_next) + { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + { + Error error(std::string("Error creating TCP socket: ") + strerror(errno)); + freeaddrinfo(ais); + throw error; + } + if (::connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) + { + shutdown(fd, SHUT_RDWR); + close(fd); + // TODO: log it. + continue; + } + freeaddrinfo(ais); + return fd; + } + + freeaddrinfo(ais); -userPassword = ""; -strncpy((char *)packet.sessid, sessid.c_str(), RAD_SESSID_LEN); + throw Error("Failed to resolve '" + m_config.address); +}; -if (type == "Start") +int STG_CLIENT::Impl::connectUNIX() +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + throw Error(std::string("Error creating UNIX socket: ") + strerror(errno)); + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, m_config.address.c_str(), m_config.address.length()); + if (::connect(fd, reinterpret_cast(&addr), sizeof(addr)) == -1) { - if (Request(&packet, login, svc, RAD_ACCT_START_PACKET)) - return -1; + Error error(std::string("Error binding UNIX socket: ") + strerror(errno)); + shutdown(fd, SHUT_RDWR); + close(fd); + throw error; } -else if (type == "Stop") + return fd; +} + +bool STG_CLIENT::Impl::read() +{ + static std::vector buffer(1024); + ssize_t res = ::read(m_sock, buffer.data(), buffer.size()); + if (res < 0) { - if (Request(&packet, login, svc, RAD_ACCT_STOP_PACKET)) - return -1; + //m_logger("Failed to read data from '" + m_remote + "': " + strerror(errno)); + return false; } -else if (type == "Interim-Update") + m_lastActivity = time(NULL); + if (res == 0) { - if (Request(&packet, login, svc, RAD_ACCT_UPDATE_PACKET)) - return -1; + if (!m_parser.done()) + { + //m_logger("Failed to read data from '" + m_remote + "': " + strerror(errno)); + return false; + } + return process(); } -else + return m_parser.append(buffer.data(), res); +} + +bool STG_CLIENT::Impl::tick() +{ + time_t now = time(NULL); + if (difftime(now, m_lastActivity) > CONN_TIMEOUT) { - if (Request(&packet, login, svc, RAD_ACCT_OTHER_PACKET)) - return -1; + //m_logger("Connection to " + m_remote + " timed out."); + return false; } + if (difftime(now, m_lastPing) > PING_TIMEOUT) + sendPing(); + return true; +} -if (packet.packetType != RAD_ACCEPT_PACKET) - return -1;*/ +bool STG_CLIENT::Impl::process() +{ + switch (m_parser.packet()) + { + case PING: + return processPing(); + case PONG: + return processPong(); + case DATA: + return processData(); + } + //m_logger("Received invalid packet type: " + m_parser.packetStr()); + return false; +} -PAIRS pairs; +bool STG_CLIENT::Impl::processPing() +{ + return sendPong(); +} -return ToSTGPairs(pairs); +bool STG_CLIENT::Impl::processPong() +{ + m_lastActivity = time(NULL); + return true; } -//----------------------------------------------------------------------------- +bool STG_CLIENT::Impl::processData() +{ + RESULT result; + for (PairsParser::Pairs::const_iterator it = m_parser.reply().begin(); it != m_parser.reply().end(); ++it) + result.reply.push_back(std::make_pair(it->first, it->second)); + for (PairsParser::Pairs::const_iterator it = m_parser.modify().begin(); it != m_parser.modify().end(); ++it) + result.modify.push_back(std::make_pair(it->first, it->second)); + return m_callback(m_data, result); +} -std::string STG_CLIENT_ST::m_host; -uint16_t STG_CLIENT_ST::m_port(6666); -std::string STG_CLIENT_ST::m_password; +bool STG_CLIENT::Impl::sendPing() +{ + PacketGen gen("ping"); -//----------------------------------------------------------------------------- + m_lastPing = time(NULL); -STG_CLIENT * STG_CLIENT_ST::Get() -{ - static STG_CLIENT * stgClient = NULL; - if ( stgClient == NULL ) - stgClient = new STG_CLIENT(m_host, m_port, m_password); - return stgClient; + return generate(gen, &STG_CLIENT::Impl::write, this); } -void STG_CLIENT_ST::Configure(const std::string & host, uint16_t port, const std::string & password) +bool STG_CLIENT::Impl::sendPong() { - m_host = host; - m_port = port; - m_password = password; -} + PacketGen gen("pong"); -//----------------------------------------------------------------------------- + m_lastPing = time(NULL); -const STG_PAIR * ToSTGPairs(const PAIRS & source) + return generate(gen, &STG_CLIENT::Impl::write, this); +} + +bool STG_CLIENT::Impl::write(void* data, const char* buf, size_t size) { - STG_PAIR * pairs = new STG_PAIR[source.size() + 1]; - for (size_t pos = 0; pos < source.size(); ++pos) { - bzero(pairs[pos].key, sizeof(STG_PAIR::key)); - bzero(pairs[pos].value, sizeof(STG_PAIR::value)); - strncpy(pairs[pos].key, source[pos].first.c_str(), sizeof(STG_PAIR::key)); - strncpy(pairs[pos].value, source[pos].second.c_str(), sizeof(STG_PAIR::value)); - ++pos; + STG_CLIENT::Impl& impl = *static_cast(data); + while (size > 0) + { + ssize_t res = ::write(impl.m_sock, buf, size); + if (res < 0) + { + //conn.m_logger("Failed to write pong to '" + conn.m_remote + "': " + strerror(errno)); + return false; + } + size -= res; } - bzero(pairs[sources.size()].key, sizeof(STG_PAIR::key)); - bzero(pairs[sources.size()].value, sizeof(STG_PAIR::value)); + return true; +} - return pairs; +void* STG_CLIENT::Impl::run(void* data) +{ + Impl& impl = *static_cast(data); + impl.runImpl(); + return NULL; }