#include "cf/network.h" #include "cf/task.h" #include "cf/codec.h" #include "cf/file.h" #include "crypto.h" #include "msg.hpp" #include #include #include #include "sqlite3.h" // -------------------------------------------------------------- std::map gOnlineUsers; std::map > gSessionMap; // -------------------------------------------------------------- int cb_getFriendList(void * userArg, int argc, char ** argv, char ** colName) { std::string * friendList = (std::string *) userArg; *friendList = argv[1]; return 0; } int cb_getSMS(void * userArg, int argc, char ** argv, char ** colName) { std::string * sms = (std::string *) userArg; for (int i = 0; i < argc; i++) { if (!stricmp(colName[i], "sms")) { *sms = argv[i]; break; } } return 0; } int cb_exist(void * userArg, int argc, char ** argv, char ** colName) { // È£ÃâµÇ¸é Àִ°Šbool * result = (bool *)userArg; *result = true; return 0; } //=============================================================== // -------------------------------------------------------------- class DBManager { private: sqlite3 * db; public: DBManager() throw (cf::exception) : db(NULL) { Init(); } void Init() throw (cf::exception) { int result = 0; bool isExist = false; isExist = cf::file("account.db").exists(); result = sqlite3_open("account.db", &db); if (result) fprintf(stderr, "sqlite_open failed\n"); if (!isExist) setup(); } void setup() throw (cf::exception) { std::string sqlCreateAccountTable; std::string sqlCreateAuthTable; std::string sqlCreateFriendsTable; // declaration // query sqlCreateAccountTable = "CREATE TABLE T_ACCOUNT" "(" " id TEXT PRIMARY KEY," " pw TEXT" ")"; sqlCreateAuthTable = "CREATE TABLE T_AUTH" "(" " id TEXT NOT NULL," " ip TEXT NOT NULL," " sms TEXT NOT NULL" ")"; sqlCreateFriendsTable = "CREATE TABLE T_FRIENDS" "(" " id TEXT NOT NULL," " friend TEXT NOT NULL" ")"; exec(sqlCreateAccountTable, NULL, NULL); exec(sqlCreateAuthTable, NULL, NULL); exec(sqlCreateFriendsTable, NULL, NULL); } void exec(std::string & query, sqlite3_callback cb, void * userArg) throw(cf::exception) { int result = 0; char * errMsg = NULL; result = sqlite3_exec(db, query.c_str(), cb, userArg, &errMsg); LOG("[SQL] " + query); if (result != SQLITE_OK) THROW_EXCEPTION("[DBERROR][" << result << "] " << errMsg); } bool login(const std::string & id, const std::string & pw) throw (cf::exception) { try { bool result = false; std::string query = "select * from T_ACCOUNT where id ='" + id + "' and pw = '" + pw + "'"; this->exec(query, cb_exist, &result); return result; } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } } bool join(const std::string & id, const std::string & pw, const std::string & sms, const std::string & ip) throw (cf::exception) { try { // account bool isExistAccount = false; std::string existAccount = "select * from T_ACCOUNT where id = '" + id + "'"; std::string insertAccount = "insert into T_ACCOUNT(id, pw) values('" + id + "', '" + pw + "')"; // auth bool isExistAuth = false; std::string existAuth = "select * from T_AUTH where id = '" + id + "' and sms = '" + sms + "' and ip = '" + ip + "'"; std::string insertAuth = "insert into T_AUTH(id, sms, ip) values( '" + id + "', '" + sms + "' , '" + ip + "')"; this->exec(existAccount, cb_exist, &isExistAccount); if (!isExistAccount) { this->exec(insertAccount, NULL, NULL); this->exec(insertAuth, NULL, NULL); } else { this->exec(existAuth, cb_exist, &isExistAuth); if (!isExistAuth) this->exec(insertAuth, NULL, NULL); } } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } return true; } std::vector getFriendList(std::string & id) throw(cf::exception) { int result = 0; std::string friendList; try { std::string query = "select * from T_FRIENDS where id = '" + id + "'"; this->exec(query, cb_getFriendList, &friendList); } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } } std::string getSMS(std::string & id, std::string & ip) { try { std::string sms; std::string query = "select * from T_AUTH where id = '" + id + "' and ip = '" + ip + "'"; this->exec(query, cb_getSMS, &sms); if (sms.length() == 0) THROW_EXCEPTION("sms was not found for '" + id + "' from '" + ip + "'"); return sms; } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } } bool addFriend(std::string & id, std::string & friendID) { try { std::string query = "insert into T_FRIENDS values('" + id + "', '" + friendID + "')"; this->exec(query, NULL, NULL); return true; } catch (cf::exception & e) { FORWARD_EXCEPTION(e); return false; } } }; DBManager dbmgr; class Runner { public: cf::network::tcp * mSock; cf::task::thread * mThread; Runner(cf::network::tcp & sock, int (*worker)(void *)) : mSock(NULL) , mThread(NULL) { mSock = new(std::nothrow) cf::network::tcp(sock.detach()); mThread = new(std::nothrow) cf::task::thread(worker); if (!mSock || !mThread) { dispose(); THROW_EXCEPTION("cannot allocate thread arguments"); } mThread->start(this); } ~Runner() { dispose(); } void dispose() { if (mSock) delete mSock; if (mThread) delete mThread; } }; static bool isOnline(const std::string & id) { return gOnlineUsers.find(id) != gOnlineUsers.end(); } static void logout(const std::string & id) { gOnlineUsers[id].close(); gOnlineUsers.erase(id); LOG(STR(id << " was logged out")); } static unsigned int generateSeed() { unsigned int ret = 0; int t = (int)time(NULL); cf::bin b; b.resize(sizeof(int)); b.set((cf::uint8_t*)&t, sizeof(int)); cf::bin s = crypto().sha256(b); memcpy(&ret, s.buffer(), sizeof(unsigned int)); return ret; } static std::string generateSMSCode() { return "545454"; char random[8] = {0x00,}; sprintf(random, "%06d", generateSeed() % 1000000); return random; } static std::string httpSMS(const Protocol::Message & parser) { std::string phone = parser.get(ProtocolType::PHONE); #define CRLF "\r\n" std::string code = generateSMSCode(); std::string url = "unsigned.kr"; std::string uri = "/~cheese/sms/req.php?to=" + phone + "&code=" + code; std::string http = "GET " + uri + " HTTP/1.1" CRLF "Host: " + url + CRLF "Accept: text/plain" CRLF CRLF; cf::network::tcp smsSock; cf::network::host smsServer(url, 80); smsSock.connect(smsServer); smsSock.send(http); smsSock.receive(); smsSock.close(); return code; } static Protocol::Message getSecret(const Protocol::Message & parser, const std::string & sms) { std::string secret = parser.get(ProtocolType::SECRET); std::string random = parser.get(ProtocolType::RANDOM); crypto crypt; crypt.setKey(cf::bin(sms + DELIMITER + random)); std::string auth = crypt.decrypt(cf::codec::hex::getInstance()->decode(secret)).toString(); if (auth[0] != '{') THROW_EXCEPTION("invalid secret data (check cipher and its key)"); for (size_t iter = 0; iter < auth.length(); iter++) { if (auth[iter] < ' ' || '~' < auth[iter]) THROW_EXCEPTION("invalid secret data (check cipher and its key)"); } Protocol::Message authParser; authParser.parse(auth); return authParser; } static bool join(const Protocol::Message & parser, const std::string & sms, const std::string & address) throw (cf::exception) { Protocol::Message & authParser = getSecret(parser, sms); if (sms != authParser.get(ProtocolType::SMS)) THROW_EXCEPTION("SMS is not same"); std::string id = authParser.get(ProtocolType::ID); std::string pw = authParser.get(ProtocolType::PW); std::string rcvdSMS = authParser.get(ProtocolType::SMS); if (sms != rcvdSMS) THROW_EXCEPTION("invalid sms code"); return dbmgr.join(id, pw, sms, address); } static bool login(const Protocol::Message & parser, cf::network::tcp & sock, std::string & ip, std::string sms) throw (cf::exception) { Protocol::Message & authParser = getSecret(parser, sms); std::string id = authParser.get(ProtocolType::ID); std::string pw = authParser.get(ProtocolType::PW); bool result = dbmgr.login(id, pw); if (result) gOnlineUsers[id] = SecureSocket(&sock, ip, sms); return result; } static bool chat(const Protocol::Message & message) { bool result = false; Protocol::Message parser = message; std::string sessid = parser.get(ProtocolType::SESSION_ID); std::vector idList = gSessionMap[sessid]; std::string sender = parser.get(ProtocolType::ID); parser.mObject[ProtocolType::TYPE] = ProtocolType::LISTEN; std::string serialized = parser.serialize(); for (size_t iter = 0; iter < idList.size(); iter++) { std::string id = idList[iter]; if (sender != id && isOnline(id)) gOnlineUsers[id].send(serialized); result = true; } return result; } static std::string createSessionID(std::string & idList) { cf::bin sessid; sessid = crypto().sha256(cf::bin(idList)); return cf::codec::hex::getInstance()->encode(sessid); } static bool openSession(const Protocol::Message & message) { Protocol::Message parser = message; bool result = false; std::string sessid; std::vector idList = parser.getList(ProtocolType::ID_LIST); std::string concat = joinStrings(idList); sessid = createSessionID(concat); parser.mObject[ProtocolType::SESSION_ID] = sessid; std::string serialized = parser.serialize(); for (size_t iter = 0; iter < idList.size(); iter++) { std::string id = idList[iter]; if (isOnline(id)) gOnlineUsers[id].send(serialized); result = true; } gSessionMap[sessid] = idList; return result; } static bool addFriend(const Protocol::Message & message) { Protocol::Message parser = message; std::string result = ""; std::string id = parser.get(ProtocolType::ID); std::string friendID = parser.get(ProtocolType::FRIEND_ID); return dbmgr.addFriend(id, friendID); } static std::string authenticator(cf::network::tcp & sock) { Protocol::Message parser; try { std::string sms; std::string id; bool loggedIn = false; bool result = false; while (true) { result = false; parser.parse(sock.receive().toString()); if (parser.type() == ProtocolType::SMS) { sms = httpSMS(parser); result = true; } else if (parser.type() == ProtocolType::JOIN) { if (!join(parser, sms, sock.peer().address())) THROW_EXCEPTION("user(" << parser.get(ProtocolType::ID) << ") cannot join"); result = true; } else if (parser.type() == ProtocolType::LOGIN) { id = parser.get(ProtocolType::ID); std::string ip = sock.peer().address(); sms = dbmgr.getSMS(id, ip); // ½ÇÆÐÇÏ¸é ¾Õ¿¡¼­ exceptionÀ¸·Î ´Ù ó¸®µÊ // ¿©±ä ¼º°øÇÒ ¶§¿¡¸¸ ¿È if (login(parser, sock, ip, sms)) { loggedIn = true; result = true; } } // success sock.send(Protocol::Response().result(parser.type(), result)); if (loggedIn) return id; } } catch (cf::exception & e) { sock.send(Protocol::Response().result(parser.type(), false)); FORWARD_EXCEPTION(e); } } static int deliverer(void * arg) { Runner * runner = reinterpret_cast(arg); cf::network::tcp * sock = runner->mSock; std::string id; try { id = authenticator(*sock); Protocol::Response response; bool result = true; while (result) { std::string message = gOnlineUsers[id].receive(); Protocol::Message parser; parser.parse(message); LOG(message); if (parser.type() == ProtocolType::TELL) { result = chat(parser); } else if (parser.type() == ProtocolType::OPEN_SESSION) { result = openSession(parser); } else if (parser.type() == ProtocolType::ADD_FRIEND) { result = addFriend(parser); } gOnlineUsers[id].send(response.result(parser.type(), result)); } } catch (cf::exception & e) { LOG(e.stackTrace()); } logout(id); delete runner; return 0; } static int server(unsigned short port) { cf::network::tcp sock; try { sock.bind(port); sock.listen(); } catch(cf::exception & e) { LOG(e.what()); return -1; } while (true) { try { cf::network::tcp client = sock.accept(); Runner * runner = new(std::nothrow) Runner(client, deliverer); if (!runner) THROW_EXCEPTION("cannot create thread argument"); } catch(cf::exception & e) { LOG(e.what()); } } return 0; } int main(int argc, char ** argv) { if (argc != 2) { std::cerr << "-_-^" << std::endl; return -1; } return server((unsigned short)atoi(argv[1])); }