BBS2chProxyKeyManager.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. #include "BBS2chProxyKeyManager.h"
  2. #if defined(USE_YYJSON)
  3. #include "yyjson/yyjson.h"
  4. #elif defined(USE_PICOJSON)
  5. #include <fstream>
  6. #include "picojson/picojson.h"
  7. #else
  8. #include "parson/parson.h"
  9. #endif
  10. #include <stdlib.h>
  11. #include <time.h>
  12. extern double getCurrentTime(void);
  13. extern void log_printf(int level, const char *format ...);
  14. const std::string BBS2chProxyKeyManager::_emptyKey = "00000000-0000-0000-0000-000000000000";
  15. const std::string& BBS2chProxyKeyManager::getKey()
  16. {
  17. if (_lastKey.empty())
  18. return _emptyKey;
  19. return _lastKey;
  20. }
  21. const std::string& BBS2chProxyKeyManager::getKey(const std::string &userAgent)
  22. {
  23. pthread_mutex_lock(&_mutex);
  24. std::map<std::string, std::string>::iterator it = _keys.find(userAgent);
  25. if (it != _keys.end()) {
  26. const std::string &key = it->second;
  27. pthread_mutex_unlock(&_mutex);
  28. return key;
  29. }
  30. pthread_mutex_unlock(&_mutex);
  31. return _emptyKey;
  32. }
  33. void BBS2chProxyKeyManager::setKey(const std::string &key, const std::string &oldKey, const std::string &userAgent, int reason)
  34. {
  35. /* do nothing when all-zero key is given - is this safe for all cases? */
  36. if (key == _emptyKey) {
  37. return;
  38. }
  39. pthread_mutex_lock(&_mutex);
  40. if (key.empty()) {
  41. if ((reason >= 3320 && reason <= 3324) || (reason >= 3390 && reason <= 3392)) {
  42. _expiredKeys.insert(oldKey);
  43. }
  44. _keys.erase(userAgent);
  45. log_printf(1, "Reset MonaKey for %s\n", userAgent.c_str());
  46. }
  47. else {
  48. _keys[userAgent] = key;
  49. _keyIssueTimes[key] = getCurrentTime();
  50. saveKeys();
  51. log_printf(1, "Updated MonaKey for %s: %s\n", userAgent.c_str(), key.c_str());
  52. }
  53. _lastKey = key;
  54. pthread_mutex_unlock(&_mutex);
  55. }
  56. bool BBS2chProxyKeyManager::isExpired(const std::string &key)
  57. {
  58. return _expiredKeys.count(key) != 0;
  59. }
  60. double BBS2chProxyKeyManager::secondsToWaitBeforePosting(const std::string &key)
  61. {
  62. double seconds = 0.0;
  63. pthread_mutex_lock(&_mutex);
  64. std::map<std::string, double>::iterator it = _keyIssueTimes.find(key);
  65. if (it != _keyIssueTimes.end()) {
  66. seconds = 4.0 - (getCurrentTime() - it->second);
  67. }
  68. pthread_mutex_unlock(&_mutex);
  69. return seconds;
  70. }
  71. void BBS2chProxyKeyManager::setStorage(const char *jsonPath)
  72. {
  73. _storagePath = jsonPath;
  74. }
  75. /* should be called once on main thread */
  76. int BBS2chProxyKeyManager::loadKeys()
  77. {
  78. if (_storagePath.empty()) return 0;
  79. #if defined(USE_YYJSON)
  80. yyjson_doc *doc = yyjson_read_file(_storagePath.c_str(), YYJSON_READ_ALLOW_TRAILING_COMMAS, NULL, NULL);
  81. if (!doc) return 0;
  82. yyjson_val *root = yyjson_doc_get_root(doc);
  83. if (!yyjson_is_obj(root)) {
  84. yyjson_doc_free(doc);
  85. return 0;
  86. }
  87. yyjson_val *keyStore = yyjson_obj_get(root, "MonaKeys");
  88. if (!yyjson_is_obj(keyStore)) {
  89. yyjson_doc_free(doc);
  90. return 0;
  91. }
  92. int numKeys = 0;
  93. double latest = -1;
  94. size_t idx, max;
  95. yyjson_val *key, *val;
  96. yyjson_obj_foreach(keyStore, idx, max, key, val) {
  97. if (!yyjson_is_obj(val)) continue;
  98. const char *userAgent = yyjson_get_str(key);
  99. const char *key_ = yyjson_get_str(yyjson_obj_get(val, "key"));
  100. double issued_on = yyjson_get_real(yyjson_obj_get(val, "issued_on"));
  101. if (key_) {
  102. numKeys++;
  103. _keys[userAgent] = key_;
  104. if (issued_on > 0) {
  105. _keyIssueTimes[key_] = issued_on;
  106. }
  107. if (issued_on > latest) {
  108. _lastKey = key_;
  109. }
  110. }
  111. }
  112. yyjson_doc_free(doc);
  113. return numKeys;
  114. #elif defined(USE_PICOJSON)
  115. std::ifstream ifs(_storagePath, std::ios::binary);
  116. if (!ifs) return 0;
  117. ifs.unsetf(std::ios::skipws);
  118. picojson::value root;
  119. std::string err;
  120. picojson::parse(root, std::istream_iterator<char>(ifs), std::istream_iterator<char>(), &err);
  121. if (!err.empty()) return 0;
  122. if (!root.is<picojson::object>()) return 0;
  123. picojson::value& keyStore = root.get<picojson::object>()["MonaKeys"];
  124. if (!keyStore.is<picojson::object>()) return 0;
  125. int numKeys = 0;
  126. double latest = -1;
  127. picojson::value::object& obj = keyStore.get<picojson::object>();
  128. for (picojson::value::object::iterator it = obj.begin(); it != obj.end(); it++) {
  129. if (!it->second.is<picojson::object>()) continue;
  130. const std::string &userAgent = it->first;
  131. const picojson::value& key = it->second.get<picojson::object>()["key"];
  132. const picojson::value& issued_on = it->second.get<picojson::object>()["issued_on"];
  133. if (key.is<std::string>()) {
  134. const std::string &keyValue = key.get<std::string>();
  135. numKeys++;
  136. _keys[userAgent] = keyValue;
  137. if (issued_on.is<double>()) {
  138. double value = issued_on.get<double>();
  139. if (value > 0) {
  140. _keyIssueTimes[keyValue] = value;
  141. }
  142. if (value > latest) {
  143. _lastKey = keyValue;
  144. }
  145. }
  146. }
  147. }
  148. return numKeys;
  149. #else
  150. JSON_Value *json = json_parse_file(_storagePath.c_str());
  151. if (!json) return 0;
  152. if (json_type(json) != JSONObject) {
  153. json_value_free(json);
  154. return 0;
  155. }
  156. JSON_Object *root = json_object(json);
  157. JSON_Object *keyStore = json_object_get_object(root, "MonaKeys");
  158. int numKeys = 0;
  159. if (keyStore) {
  160. double latest = -1;
  161. size_t length = json_object_get_count(keyStore);
  162. for (size_t i=0; i<length; i++) {
  163. const char *userAgent = json_object_get_name(keyStore, i);
  164. JSON_Value *entry = json_object_get_value_at(keyStore, i);
  165. if (json_type(entry) != JSONObject) continue;
  166. const char *key = json_object_get_string(json_object(entry), "key");
  167. double issued_on = json_object_get_number(json_object(entry), "issued_on");
  168. if (key) {
  169. numKeys++;
  170. _keys[userAgent] = key;
  171. if (issued_on > 0) {
  172. _keyIssueTimes[key] = issued_on;
  173. }
  174. if (issued_on > latest) {
  175. _lastKey = key;
  176. }
  177. }
  178. }
  179. }
  180. JSON_Object *cookies = json_object_get_object(root, "Cookies");
  181. if (cookies) {
  182. size_t length = json_object_get_count(cookies);
  183. for (size_t i=0; i<length; i++) {
  184. std::string userAgent = json_object_get_name(cookies, i);
  185. JSON_Value *array = json_object_get_value_at(cookies, i);
  186. if (json_type(array) != JSONArray) continue;
  187. size_t numEntries = json_array_get_count(json_array(array));
  188. for (size_t j=0; j<numEntries; j++) {
  189. JSON_Value *entry = json_array_get_value(json_array(array), j);
  190. if (json_type(entry) != JSONObject) continue;
  191. JSON_Object *entryObj = json_object(entry);
  192. Cookie cookie;
  193. const char *str = json_object_get_string(entryObj, "name");
  194. if (str) cookie.name = str;
  195. str = json_object_get_string(entryObj, "value");
  196. if (str) cookie.value = str;
  197. str = json_object_get_string(entryObj, "domain");
  198. if (str) cookie.domain = str;
  199. cookie.includeSubdomains = json_object_get_boolean(entryObj, "includeSubdomains");
  200. str = json_object_get_string(entryObj, "path");
  201. if (str) cookie.path = str;
  202. cookie.secure = json_object_get_boolean(entryObj, "secure");
  203. str = json_object_get_string(entryObj, "expires");
  204. if (str) cookie.expires = str;
  205. _cookies[userAgent].set(cookie);
  206. }
  207. }
  208. }
  209. json_value_free(json);
  210. return numKeys;
  211. #endif
  212. }
  213. /* this private function is called inside setKey(), and should not be called from other places */
  214. bool BBS2chProxyKeyManager::saveKeys()
  215. {
  216. if (_storagePath.empty()) return false;
  217. #if defined(USE_YYJSON)
  218. yyjson_mut_doc *doc;
  219. yyjson_mut_val *root;
  220. yyjson_doc *tmp = yyjson_read_file(_storagePath.c_str(), YYJSON_READ_ALLOW_TRAILING_COMMAS, NULL, NULL);
  221. if (tmp) {
  222. doc = yyjson_doc_mut_copy(tmp, NULL);
  223. yyjson_doc_free(tmp);
  224. root = yyjson_mut_doc_get_root(doc);
  225. if (!yyjson_mut_is_obj(root)) {
  226. log_printf(0, "The file %s looks like JSON but unusable as a MonaKey storage.\n", _storagePath.c_str());
  227. yyjson_mut_doc_free(doc);
  228. return false;
  229. }
  230. } else {
  231. doc = yyjson_mut_doc_new(NULL);
  232. root = yyjson_mut_obj(doc);
  233. yyjson_mut_doc_set_root(doc, root);
  234. }
  235. yyjson_mut_val *keyStore = yyjson_mut_obj(doc);
  236. yyjson_mut_obj_put(root, yyjson_mut_str(doc, "MonaKeys"), keyStore);
  237. for (std::map<std::string, std::string>::iterator it = _keys.begin(); it != _keys.end(); it++) {
  238. const std::string &userAgent = it->first;
  239. const std::string &key = it->second;
  240. double issued_on = 0;
  241. std::map<std::string, double>::iterator it2 = _keyIssueTimes.find(key);
  242. if (it2 != _keyIssueTimes.end()) {
  243. issued_on = it2->second;
  244. }
  245. yyjson_mut_val *entry = yyjson_mut_obj(doc);
  246. yyjson_mut_obj_add_str(doc, entry, "key", key.c_str());
  247. if (issued_on > 0) yyjson_mut_obj_add_real(doc, entry, "issued_on", issued_on);
  248. yyjson_mut_obj_put(keyStore, yyjson_mut_str(doc, userAgent.c_str()), entry);
  249. }
  250. yyjson_write_err err;
  251. yyjson_mut_write_file(_storagePath.c_str(), doc, YYJSON_WRITE_PRETTY|YYJSON_WRITE_ESCAPE_UNICODE, NULL, &err);
  252. if (err.code) {
  253. log_printf(0, "Error while writing MonaKeys to %s: %s\n", _storagePath.c_str(), err.msg);
  254. }
  255. yyjson_mut_doc_free(doc);
  256. return err.code == 0;
  257. #elif defined(USE_PICOJSON)
  258. picojson::value root;
  259. std::ifstream ifs(_storagePath, std::ios::binary);
  260. if (ifs) {
  261. ifs.unsetf(std::ios::skipws);
  262. std::string err;
  263. picojson::parse(root, std::istream_iterator<char>(ifs), std::istream_iterator<char>(), &err);
  264. ifs.close();
  265. if (!err.empty() && !root.is<picojson::object>()) {
  266. log_printf(0, "The file %s looks like JSON but unusable as a MonaKey storage.\n", _storagePath.c_str());
  267. return false;
  268. }
  269. }
  270. if (root.is<picojson::null>()) {
  271. root = picojson::value(picojson::object());
  272. }
  273. picojson::object keyStore;
  274. for (std::map<std::string, std::string>::iterator it = _keys.begin(); it != _keys.end(); it++) {
  275. const std::string &userAgent = it->first;
  276. const std::string &key = it->second;
  277. double issued_on = 0;
  278. std::map<std::string, double>::iterator it2 = _keyIssueTimes.find(key);
  279. if (it2 != _keyIssueTimes.end()) {
  280. issued_on = it2->second;
  281. }
  282. picojson::object entry;
  283. entry.insert(std::make_pair("key", picojson::value(key)));
  284. if (issued_on > 0) entry.insert(std::make_pair("issued_on", picojson::value(issued_on)));
  285. keyStore.insert(std::make_pair(userAgent, entry));
  286. }
  287. root.get<picojson::object>().insert(std::make_pair("MonaKeys", keyStore));
  288. std::ofstream ofs(_storagePath);
  289. if (!ofs) {
  290. log_printf(0, "Error while writing MonaKeys to %s\n", _storagePath.c_str());
  291. return false;
  292. }
  293. root.serialize(std::ostream_iterator<char>(ofs), true);
  294. return true;
  295. #else
  296. JSON_Value *json = json_parse_file(_storagePath.c_str());
  297. if (!json) {
  298. json = json_value_init_object();
  299. }
  300. else if (json_type(json) != JSONObject) {
  301. log_printf(0, "The file %s looks like JSON but unusable as a MonaKey storage.\n", _storagePath.c_str());
  302. json_value_free(json);
  303. return false;
  304. }
  305. JSON_Object *root = json_object(json);
  306. JSON_Value *object = json_value_init_object();
  307. json_object_set_value(root, "MonaKeys", object);
  308. JSON_Object *keyStore = json_object(object);
  309. for (std::map<std::string, std::string>::iterator it = _keys.begin(); it != _keys.end(); it++) {
  310. const std::string &userAgent = it->first;
  311. const std::string &key = it->second;
  312. double issued_on = 0;
  313. std::map<std::string, double>::iterator it2 = _keyIssueTimes.find(key);
  314. if (it2 != _keyIssueTimes.end()) {
  315. issued_on = it2->second;
  316. }
  317. JSON_Value *entry = json_value_init_object();
  318. json_object_set_string(json_object(entry), "key", key.c_str());
  319. if (issued_on > 0) json_object_set_number(json_object(entry), "issued_on", issued_on);
  320. json_object_set_value(keyStore, userAgent.c_str(), entry);
  321. }
  322. bool ret = json_serialize_to_file_pretty(json, _storagePath.c_str()) == JSONSuccess;
  323. if (!ret) {
  324. log_printf(0, "Error while writing MonaKeys to %s\n", _storagePath.c_str());
  325. }
  326. json_value_free(json);
  327. return ret;
  328. #endif
  329. }
  330. BBS2chProxyKeyManager::CookieJar& BBS2chProxyKeyManager::getCookieJar(const std::string &userAgent)
  331. {
  332. pthread_mutex_lock(&_mutex);
  333. BBS2chProxyKeyManager::CookieJar& jar = _cookies[userAgent];
  334. pthread_mutex_unlock(&_mutex);
  335. return jar;
  336. }
  337. bool BBS2chProxyKeyManager::flushCookies()
  338. {
  339. if (_storagePath.empty()) return false;
  340. #if defined(USE_YYJSON)
  341. return false;
  342. #elif defined(USE_PICOJSON)
  343. return false;
  344. #else
  345. JSON_Value *json = json_parse_file(_storagePath.c_str());
  346. if (!json) {
  347. json = json_value_init_object();
  348. }
  349. else if (json_type(json) != JSONObject) {
  350. log_printf(0, "The file %s looks like JSON but unusable as a cookie storage.\n", _storagePath.c_str());
  351. json_value_free(json);
  352. return false;
  353. }
  354. JSON_Object *root = json_object(json);
  355. JSON_Value *object = json_value_init_object();
  356. json_object_set_value(root, "Cookies", object);
  357. pthread_mutex_lock(&_mutex);
  358. for (std::map<std::string, CookieJar>::iterator it = _cookies.begin(); it != _cookies.end(); ++it) {
  359. it->second.lock();
  360. JSON_Value *array = (JSON_Value *)it->second.jsonValue();
  361. if (array && json_array_get_count(json_array(array))) {
  362. json_object_set_value(json_object(object), it->first.c_str(), array);
  363. }
  364. it->second.unlock();
  365. }
  366. pthread_mutex_unlock(&_mutex);
  367. bool ret = json_serialize_to_file_pretty(json, _storagePath.c_str()) == JSONSuccess;
  368. if (!ret) {
  369. log_printf(0, "Error while writing cookies to %s\n", _storagePath.c_str());
  370. }
  371. json_value_free(json);
  372. return ret;
  373. #endif
  374. }
  375. BBS2chProxyKeyManager::Cookie::Cookie(const std::string &valueInNetscapeFormat)
  376. {
  377. std::vector<std::string> list;
  378. size_t offset = 0;
  379. while (1) {
  380. size_t pos = valueInNetscapeFormat.find('\t', offset);
  381. if (pos == std::string::npos) {
  382. list.push_back(valueInNetscapeFormat.substr(offset));
  383. break;
  384. }
  385. list.push_back(valueInNetscapeFormat.substr(offset, pos - offset));
  386. offset = pos + 1;
  387. }
  388. if (list.size() == 7) {
  389. name = list[5];
  390. value = list[6];
  391. domain = list[0];
  392. includeSubdomains = list[1] == "TRUE" ? true : false;
  393. path = list[2];
  394. secure = list[3] == "TRUE" ? true : false;
  395. expires = list[4];
  396. }
  397. }
  398. std::string BBS2chProxyKeyManager::Cookie::valueInNetscapeFormat()
  399. {
  400. std::string ret = domain;
  401. ret += '\t';
  402. ret += includeSubdomains ? "TRUE" : "FALSE";
  403. ret += '\t';
  404. ret += path;
  405. ret += '\t';
  406. ret += secure ? "TRUE" : "FALSE";
  407. ret += '\t';
  408. ret += expires;
  409. ret += '\t';
  410. ret += name;
  411. ret += '\t';
  412. ret += value;
  413. return ret;
  414. }
  415. bool BBS2chProxyKeyManager::Cookie::isExpired()
  416. {
  417. return expires != "0" && strtoull(expires.c_str(), NULL, 10) < time(NULL);
  418. }
  419. bool BBS2chProxyKeyManager::Cookie::isSameAs(Cookie &cookie)
  420. {
  421. return (name == cookie.name) && (domain == cookie.domain) && (path == cookie.path);
  422. }
  423. void *BBS2chProxyKeyManager::Cookie::jsonValue()
  424. {
  425. if (isExpired() || expires == "0") return NULL;
  426. #if defined(USE_YYJSON)
  427. return NULL;
  428. #elif defined(USE_PICOJSON)
  429. return NULL;
  430. #else
  431. JSON_Value *entry = json_value_init_object();
  432. json_object_set_string(json_object(entry), "name", name.c_str());
  433. json_object_set_string(json_object(entry), "value", value.c_str());
  434. json_object_set_string(json_object(entry), "domain", domain.c_str());
  435. json_object_set_boolean(json_object(entry), "includeSubdomains", includeSubdomains);
  436. json_object_set_string(json_object(entry), "path", path.c_str());
  437. json_object_set_boolean(json_object(entry), "secure", secure);
  438. json_object_set_string(json_object(entry), "expires", expires.c_str());
  439. return entry;
  440. #endif
  441. }
  442. std::vector<BBS2chProxyKeyManager::Cookie>& BBS2chProxyKeyManager::CookieJar::getList()
  443. {
  444. return _cookies;
  445. }
  446. void BBS2chProxyKeyManager::CookieJar::set(Cookie &cookie)
  447. {
  448. if (cookie.name.empty() || cookie.domain.empty() || cookie.path.empty()) return;
  449. for (std::vector<Cookie>::iterator it = _cookies.begin(); it != _cookies.end(); ++it) {
  450. if (it->isSameAs(cookie)) {
  451. if (cookie.isExpired()) _cookies.erase(it);
  452. else *it = cookie;
  453. return;
  454. }
  455. }
  456. if (cookie.isExpired()) return;
  457. _cookies.push_back(cookie);
  458. }
  459. void BBS2chProxyKeyManager::CookieJar::clear()
  460. {
  461. _cookies.clear();
  462. }
  463. void BBS2chProxyKeyManager::CookieJar::lock()
  464. {
  465. pthread_mutex_lock(&_mutex);
  466. }
  467. void BBS2chProxyKeyManager::CookieJar::unlock()
  468. {
  469. pthread_mutex_unlock(&_mutex);
  470. }
  471. void *BBS2chProxyKeyManager::CookieJar::jsonValue()
  472. {
  473. #if defined(USE_YYJSON)
  474. return NULL;
  475. #elif defined(USE_PICOJSON)
  476. return NULL;
  477. #else
  478. JSON_Value *array = json_value_init_array();
  479. for (std::vector<Cookie>::iterator it = _cookies.begin(); it != _cookies.end(); ++it) {
  480. JSON_Value *entry = (JSON_Value *)it->jsonValue();
  481. if (entry) json_array_append_value(json_array(array), entry);
  482. }
  483. return array;
  484. #endif
  485. }