source: chevmsgr/trunk/msgsrv.cpp@ 33

Last change on this file since 33 was 33, checked in by cheese, 8 years ago
  1. 클라이언트 UI ID/PW 입력 안하면 죽는 문제 수정
  2. UI 종료 함수 exit로 수정
  3. 그룹챗 인덱스 변수 형 때문에 나오는 warning 수정
  4. toOpenSession 함수 가끔 죽는 문제 수정
  5. generateRandom()에서 time()하던거 rand()로 수정
  6. sms 전송 url 및 sms 코드 생성 로직 대폭 수정 (주석 참고)
File size: 14.6 KB
Line 
1
2#include "cf/network.h"
3#include "cf/task.h"
4#include "cf/codec.h"
5#include "cf/file.h"
6#include "crypto.h"
7
8#include "msg.hpp"
9
10#include <map>
11#include <stdlib.h>
12#include <time.h>
13
14#include "sqlite3.h"
15
16// --------------------------------------------------------------
17
18std::map<std::string, SecureSocket> gOnlineUsers;
19std::map<std::string, std::vector<std::string> > gSessionMap;
20
21// --------------------------------------------------------------
22
23int cb_getFriendList(void * userArg, int argc, char ** argv, char ** colName)
24{
25 std::vector<std::string> * friendList = (std::vector<std::string> *) userArg;
26
27 friendList->push_back(argv[1]);
28
29 return 0;
30}
31
32int cb_getSMS(void * userArg, int argc, char ** argv, char ** colName)
33{
34 std::string * sms = (std::string *) userArg;
35
36 for (int i = 0; i < argc; i++)
37 {
38 if (!stricmp(colName[i], "sms"))
39 {
40 *sms = argv[i];
41 break;
42 }
43 }
44
45 return 0;
46}
47
48int cb_exist(void * userArg, int argc, char ** argv, char ** colName)
49{
50 // È£ÃâµÇ¸é Àִ°Å
51 bool * result = (bool *)userArg;
52
53 *result = true;
54
55 return 0;
56}
57
58
59//===============================================================
60
61// --------------------------------------------------------------
62class DBManager
63{
64private:
65 sqlite3 * db;
66
67public:
68 DBManager()
69 throw (cf::exception)
70 : db(NULL)
71 {
72 Init();
73 }
74
75 void Init()
76 throw (cf::exception)
77 {
78 int result = 0;
79 bool isExist = false;
80
81 isExist = cf::file("account.db").exists();
82
83 result = sqlite3_open("account.db", &db);
84 if (result)
85 fprintf(stderr, "sqlite_open failed\n");
86
87 if (!isExist)
88 setup();
89 }
90
91 void setup()
92 throw (cf::exception)
93 {
94 std::string sqlCreateAccountTable;
95 std::string sqlCreateAuthTable;
96 std::string sqlCreateFriendsTable;
97 // declaration
98
99 // query
100 sqlCreateAccountTable =
101 "CREATE TABLE T_ACCOUNT"
102 "("
103 " id TEXT PRIMARY KEY,"
104 " pw TEXT"
105 ")";
106 sqlCreateAuthTable =
107 "CREATE TABLE T_AUTH"
108 "("
109 " id TEXT NOT NULL,"
110 " ip TEXT NOT NULL,"
111 " sms TEXT NOT NULL"
112 ")";
113 sqlCreateFriendsTable =
114 "CREATE TABLE T_FRIENDS"
115 "("
116 " id TEXT NOT NULL,"
117 " friend TEXT NOT NULL"
118 ")";
119
120 exec(sqlCreateAccountTable, NULL, NULL);
121 exec(sqlCreateAuthTable, NULL, NULL);
122 exec(sqlCreateFriendsTable, NULL, NULL);
123 }
124
125 void exec(std::string & query, sqlite3_callback cb, void * userArg)
126 throw(cf::exception)
127 {
128 int result = 0;
129 char * errMsg = NULL;
130
131 result = sqlite3_exec(db, query.c_str(), cb, userArg, &errMsg);
132 LOG("[SQL] " + query);
133
134 if (result != SQLITE_OK)
135 THROW_EXCEPTION("[DBERROR][" << result << "] " << errMsg);
136 }
137
138 bool login(const std::string & id, const std::string & pw)
139 throw (cf::exception)
140 {
141 try
142 {
143 bool result = false;
144 std::string query = "select * from T_ACCOUNT where id ='" + id + "' and pw = '" + pw + "'";
145
146 this->exec(query, cb_exist, &result);
147
148 return result;
149 }
150 catch (cf::exception & e)
151 {
152 FORWARD_EXCEPTION(e);
153 }
154 }
155
156 bool join(const std::string & id, const std::string & pw, const std::string & sms, const std::string & ip)
157 throw (cf::exception)
158 {
159 try
160 {
161 // account
162 bool isExistAccount = false;
163 std::string existAccount = "select * from T_ACCOUNT where id = '" + id + "'";
164 std::string insertAccount = "insert into T_ACCOUNT(id, pw) values('" + id + "', '" + pw + "')";
165
166 // auth
167 bool isExistAuth = false;
168 std::string existAuth = "select * from T_AUTH where id = '" + id + "' and sms = '" + sms + "' and ip = '" + ip + "'";
169 std::string insertAuth = "insert into T_AUTH(id, sms, ip) values( '" + id + "', '" + sms + "' , '" + ip + "')";
170
171 this->exec(existAccount, cb_exist, &isExistAccount);
172
173 if (!isExistAccount)
174 {
175 this->exec(insertAccount, NULL, NULL);
176 this->exec(insertAuth, NULL, NULL);
177 }
178 else
179 {
180 this->exec(existAuth, cb_exist, &isExistAuth);
181 if (!isExistAuth)
182 this->exec(insertAuth, NULL, NULL);
183 }
184 }
185 catch (cf::exception & e)
186 {
187 FORWARD_EXCEPTION(e);
188 }
189
190 return true;
191 }
192
193 std::vector<std::string> getFriendList(std::string & id)
194 throw(cf::exception)
195 {
196 int result = 0;
197 std::vector<std::string> friendList;
198
199 try
200 {
201 std::string query = "select * from T_FRIENDS where id = '" + id + "'";
202
203 this->exec(query, cb_getFriendList, &friendList);
204
205 return friendList;
206 }
207 catch (cf::exception & e)
208 {
209 FORWARD_EXCEPTION(e);
210 }
211 }
212
213 std::string getSMS(std::string & id, std::string & ip)
214 {
215 try
216 {
217 std::string sms;
218 std::string query = "select * from T_AUTH where id = '" + id + "' and ip = '" + ip + "'";
219
220 this->exec(query, cb_getSMS, &sms);
221 if (sms.length() == 0)
222 THROW_EXCEPTION("sms was not found for '" + id + "' from '" + ip + "'");
223
224 return sms;
225 }
226 catch (cf::exception & e)
227 {
228 FORWARD_EXCEPTION(e);
229 }
230 }
231
232 bool addFriend(std::string & id, std::string & friendID)
233 {
234 try
235 {
236 std::string query = "insert into T_FRIENDS values('" + id + "', '" + friendID + "')";
237
238 this->exec(query, NULL, NULL);
239
240 return true;
241 }
242 catch (cf::exception & e)
243 {
244 FORWARD_EXCEPTION(e);
245 return false;
246 }
247 }
248};
249DBManager dbmgr;
250
251class Runner
252{
253public:
254 cf::network::tcp * mSock;
255 cf::task::thread * mThread;
256
257 Runner(cf::network::tcp & sock, int (*worker)(void *))
258 : mSock(NULL)
259 , mThread(NULL)
260 {
261 mSock = new(std::nothrow) cf::network::tcp(sock.detach());
262 mThread = new(std::nothrow) cf::task::thread(worker);
263 if (!mSock || !mThread)
264 {
265 dispose();
266 THROW_EXCEPTION("cannot allocate thread arguments");
267 }
268
269 mThread->start(this);
270 }
271
272 ~Runner()
273 {
274 dispose();
275 }
276
277 void dispose()
278 {
279 if (mSock) delete mSock;
280 if (mThread) delete mThread;
281 }
282};
283
284static bool isOnline(const std::string & id)
285{
286 return gOnlineUsers.find(id) != gOnlineUsers.end();
287}
288
289static void logout(const std::string & id)
290{
291 gOnlineUsers[id].close();
292 gOnlineUsers.erase(id);
293 LOG(STR(id << " was logged out"));
294}
295
296static std::string generateSMSCode()
297{
298 cf::bin random = generateRandom(); // ·£´ý °ª °¡Á®¿È
299 cf::bin hash = crypto().sha256(random); // ·£´ý °ª Çؽ¬
300 std::string enc = cf::codec::hex::getInstance()->encode(hash); // Çؽ¬ÇÑ °á°ú hex encode
301
302 std::string sms; // ÃÖÁ¾ sms¹ÞÀ» º¯¼ö
303
304 // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
305 for (int i = 0; i < 8 ; i++)
306 {
307 char buf[128] = {0,};
308
309 // [0] ºÎÅÍ 4±ÛÀÚ, [4] ºÎÅÍ 4±ÛÀÚ, ...
310 size_t start = i * 4;
311
312 // 1. enc º¯¼ö¿¡¼­ 4ÀÚ¸®¾¿ ¼ø¼­´ë·Î ¶¼¾î³»¼­ xxxx- ·Î ¸¸µë
313 // std::string::substr(½ÃÀÛ À妽º, °³¼ö)
314 // 2. std::string::substr() ´Â std::stringÀ» return ÇϹǷÎ,
315 // std::string::c_str() ÇÏ¿© const char * ÇüÀ¸·Î ¹Þ¾Æ¿È
316 snprintf(buf, sizeof(buf), "%s-", enc.substr(start, 4).c_str());
317
318 // xxxx- ·Î ¸¸µç°Å sms °á°ú¿¡ Ãß°¡
319 sms += buf;
320 }
321
322 // ·çÇÁ¿¡¼­ xxxx- ¸¦ °è¼Ó ºÙ¿©¼­ ´Ù ¸¸µé°í ³ª¸é ¸¶Áö¸·ÀÌ '-' ·Î ³¡³ª°Ô µÇ¹Ç·Î ¸¶Áö¸· ±ÛÀÚ ¶¼¾î³¿
323 sms[sms.length() - 1] = 0;
324
325 return sms;
326}
327
328static std::string httpSMS(const Protocol::Message & parser)
329{
330 std::string phone = parser.get<std::string>(ProtocolType::PHONE);
331
332#define CRLF "\r\n"
333 std::string code = generateSMSCode();
334 std::string url = "chev.unsigned.kr";
335 std::string uri = "/sms/req.php?to=" + phone + "&code=" + code;
336 std::string http =
337 "GET " + uri + " HTTP/1.1" CRLF
338 "Host: " + url + CRLF
339 "Accept: text/plain" CRLF
340 CRLF;
341
342 cf::network::tcp smsSock;
343 cf::network::host smsServer(url, 80);
344
345 smsSock.connect(smsServer);
346 smsSock.send(http);
347 smsSock.receive();
348 smsSock.close();
349
350 return code;
351}
352
353static Protocol::Message getSecret(const Protocol::Message & parser, const std::string & sms)
354{
355 std::string secret = parser.get<std::string>(ProtocolType::SECRET);
356 std::string random = parser.get<std::string>(ProtocolType::RANDOM);
357
358 crypto crypt;
359 crypt.setKey(cf::bin(sms + DELIMITER + random));
360
361 std::string auth = crypt.decrypt(cf::codec::hex::getInstance()->decode(secret)).toString();
362
363 if (auth[0] != '{')
364 THROW_EXCEPTION("invalid secret data (check cipher and its key)");
365
366 for (size_t iter = 0; iter < auth.length(); iter++)
367 {
368 if (auth[iter] < ' ' || '~' < auth[iter])
369 THROW_EXCEPTION("invalid secret data (check cipher and its key)");
370 }
371
372 Protocol::Message authParser;
373
374 authParser.parse(auth);
375
376 return authParser;
377}
378
379static bool join(const Protocol::Message & parser, const std::string & sms, const std::string & address)
380 throw (cf::exception)
381{
382 Protocol::Message & authParser = getSecret(parser, sms);
383
384 if (sms != authParser.get<std::string>(ProtocolType::SMS))
385 THROW_EXCEPTION("SMS is not same");
386
387 std::string id = authParser.get<std::string>(ProtocolType::ID);
388 std::string pw = authParser.get<std::string>(ProtocolType::PW);
389 std::string rcvdSMS = authParser.get<std::string>(ProtocolType::SMS);
390
391 if (sms != rcvdSMS)
392 THROW_EXCEPTION("invalid sms code");
393
394 return dbmgr.join(id, pw, sms, address);
395}
396
397static bool login(const Protocol::Message & parser, cf::network::tcp & sock, std::string & ip, std::string sms)
398 throw (cf::exception)
399{
400 Protocol::Message & authParser = getSecret(parser, sms);
401
402 std::string id = authParser.get<std::string>(ProtocolType::ID);
403 std::string pw = authParser.get<std::string>(ProtocolType::PW);
404
405 bool result = dbmgr.login(id, pw);
406 if (result)
407 gOnlineUsers[id] = SecureSocket(&sock, ip, sms);
408
409 return result;
410}
411
412static bool chat(const Protocol::Message & message)
413{
414 bool result = false;
415 Protocol::Message parser = message;
416 std::string sessid = parser.get<std::string>(ProtocolType::SESSION_ID);
417 std::vector<std::string> idList = gSessionMap[sessid];
418 std::string sender = parser.get<std::string>(ProtocolType::ID);
419 parser.mObject[ProtocolType::TYPE] = ProtocolType::LISTEN;
420 std::string serialized = parser.serialize();
421
422 for (size_t iter = 0; iter < idList.size(); iter++)
423 {
424 std::string id = idList[iter];
425
426 if (sender != id && isOnline(id))
427 gOnlineUsers[id].send(serialized);
428
429 result = true;
430 }
431
432 return result;
433}
434
435static std::vector<SFriend> getFriendList(const Protocol::Message & parser)
436 throw (cf::exception)
437{
438 std::string id = parser.get<std::string>(ProtocolType::ID);
439 std::vector<std::string> fl = dbmgr.getFriendList(id);
440 std::vector<SFriend> fv;
441
442 for (size_t iter = 0; iter < fl.size(); iter++)
443 {
444 SFriend s;
445 s.id = fl[iter];
446 s.name = fl[iter]; // username?
447 fv.push_back(s);
448 }
449
450 return fv;
451}
452
453static std::string createSessionID(std::string & idList)
454{
455 cf::bin sessid;
456
457 sessid = crypto().sha256(cf::bin(idList));
458
459 return cf::codec::hex::getInstance()->encode(sessid);
460}
461
462static bool openSession(const Protocol::Message & message)
463{
464 const static cf::bin delim(DELIMITER);
465
466 Protocol::Message parser = message;
467 bool result = true;
468 std::string sessid;
469 std::vector<std::string> idList = parser.getList<std::string>(ProtocolType::ID_LIST);
470 std::string concat = joinStrings(idList);
471
472 sessid = createSessionID(concat);
473
474 parser.mObject[ProtocolType::SESSION_ID] = sessid;
475
476 cf::bin allKeys;
477
478 for (size_t iter = 0; iter < idList.size(); iter++)
479 {
480 std::string id = idList[iter];
481 if (isOnline(id))
482 {
483 SecureSocket & secsock = gOnlineUsers[id];
484 std::string ip = secsock.getTcpSocket().local().address();
485 std::string sms = dbmgr.getSMS(id, ip);
486
487 cf::bin seed = sms + DELIMITER + ip;
488 cf::bin key = crypto().sha256(seed);
489
490 allKeys += key;
491 }
492 }
493
494 allKeys += generateRandom();
495 std::string hashedKey = cf::codec::hex::getInstance()->encode(crypto().sha256(allKeys));
496 parser.mObject[ProtocolType::SESSION_KEY] = hashedKey;
497
498 std::string serialized = parser.serialize();
499
500 for (size_t iter = 0; iter < idList.size(); iter++)
501 {
502 std::string id = idList[iter];
503 if (isOnline(id))
504 gOnlineUsers[id].send(serialized);
505 }
506
507 gSessionMap[sessid] = idList;
508
509 return result;
510}
511
512static bool addFriend(const Protocol::Message & message)
513{
514 Protocol::Message parser = message;
515 std::string result = "";
516 std::string id = parser.get<std::string>(ProtocolType::ID);
517 std::string friendID = parser.get<std::string>(ProtocolType::FRIEND_ID);
518
519 return dbmgr.addFriend(id, friendID);
520}
521
522static std::string authenticator(cf::network::tcp & sock)
523{
524 Protocol::Message parser;
525
526 try
527 {
528 std::string sms;
529 std::string id;
530 bool loggedIn = false;
531 bool result = false;
532
533 while (true)
534 {
535 result = false;
536 std::string message = sock.receive().toString();
537 if (message[0] != '{')
538 THROW_EXCEPTION("unknown message");
539
540 parser.parse(message);
541
542 LOG(message);
543
544 if (parser.type() == ProtocolType::SMS)
545 {
546 sms = httpSMS(parser);
547 result = true;
548 }
549 else if (parser.type() == ProtocolType::JOIN)
550 {
551 if (!join(parser, sms, sock.peer().address()))
552 THROW_EXCEPTION("user(" << parser.get<std::string>(ProtocolType::ID) << ") cannot join");
553 result = true;
554 }
555 else if (parser.type() == ProtocolType::LOGIN)
556 {
557 id = parser.get<std::string>(ProtocolType::ID);
558 std::string ip = sock.peer().address();
559 sms = dbmgr.getSMS(id, ip);
560
561 // ½ÇÆÐÇÏ¸é ¾Õ¿¡¼­ exceptionÀ¸·Î ´Ù 󸮵Ê
562 // ¿©±ä ¼º°øÇÒ ¶§¿¡¸¸ ¿È
563 if (login(parser, sock, ip, sms))
564 {
565 loggedIn = true;
566 result = true;
567 }
568 }
569
570 // success
571 sock.send(Protocol::Response().result(parser.type(), result));
572 if (loggedIn)
573 return id;
574 }
575 }
576 catch (cf::exception & e)
577 {
578 sock.send(Protocol::Response().result(parser.type(), false));
579 FORWARD_EXCEPTION(e);
580 }
581}
582
583static int deliverer(void * arg)
584{
585 Runner * runner = reinterpret_cast<Runner *>(arg);
586 cf::network::tcp * sock = runner->mSock;
587
588 std::string id;
589
590 try
591 {
592 id = authenticator(*sock);
593 SecureSocket & secsock = gOnlineUsers[id];
594
595 while (true)
596 {
597 bool result = true;
598 Protocol::Response response;
599 std::string message = secsock.receive();
600 Protocol::Message parser;
601 parser.parse(message);
602
603 LOG(message);
604
605 if (parser.type() == ProtocolType::TELL)
606 {
607 result = chat(parser);
608 }
609 else if (parser.type() == ProtocolType::OPEN_SESSION)
610 {
611 result = openSession(parser);
612 }
613 else if (parser.type() == ProtocolType::ADD_FRIEND)
614 {
615 result = addFriend(parser);
616 }
617 else if (parser.type() == ProtocolType::GET_FRIEND_LIST)
618 {
619 try
620 {
621 std::vector<SFriend> fl = getFriendList(parser);
622 secsock.send(response.friendList(fl));
623 } catch (cf::exception & e)
624 {
625 LOG(e.stackTrace());
626 result = false;
627 }
628 }
629
630 secsock.send(response.result(parser.type(), result));
631 }
632 }
633 catch (cf::exception & e)
634 {
635 LOG(e.stackTrace());
636 }
637
638 logout(id);
639 delete runner;
640 return 0;
641}
642
643static int server(unsigned short port)
644{
645 cf::network::tcp sock;
646
647 try
648 {
649 sock.bind(port);
650 sock.listen();
651 }
652 catch(cf::exception & e)
653 {
654 LOG(e.what());
655 return -1;
656 }
657
658 while (true)
659 {
660 try
661 {
662 cf::network::tcp client = sock.accept();
663 Runner * runner = new(std::nothrow) Runner(client, deliverer);
664 if (!runner)
665 THROW_EXCEPTION("cannot create thread argument");
666 }
667 catch(cf::exception & e)
668 {
669 LOG(e.what());
670 }
671 }
672
673 return 0;
674}
675
676int main(int argc, char ** argv)
677{
678 if (argc != 2)
679 {
680 std::cerr << "-_-^" << std::endl;
681 return -1;
682 }
683
684 return server((unsigned short)atoi(argv[1]));
685}
Note: See TracBrowser for help on using the repository browser.