main.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. #include <string>
  2. #include <vector>
  3. #include <map>
  4. #include <stack>
  5. #include <set>
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <stdarg.h>
  10. #include <sys/stat.h>
  11. #ifdef _WIN32
  12. #include <winsock2.h>
  13. #include <ws2tcpip.h>
  14. #include <mswsock.h>
  15. #define in_addr_t UINT32
  16. #else
  17. #include <sys/socket.h>
  18. #include <netinet/in.h>
  19. #include <netdb.h>
  20. #include <poll.h>
  21. #endif
  22. #include <pthread.h>
  23. #include <signal.h>
  24. #include <getopt.h>
  25. #include <curl/curl.h>
  26. #ifdef USE_LUA
  27. #include <lua.hpp>
  28. #endif
  29. #include "BBS2chProxyConnection.h"
  30. #include "BBS2chProxyThreadInfo.h"
  31. #include "BBS2chProxyAuth.h"
  32. #include "BBS2chProxyHttpHeaders.h"
  33. #ifdef USE_MITM
  34. #include "BBS2chProxySecureSocket.h"
  35. #endif
  36. #define PORT 9080
  37. #define VERSION "20230713"
  38. #define BACKLOG 32
  39. #define NUM_LOCKS 7
  40. #ifndef NO_THREAD_POOL
  41. #ifndef NUM_THREADS_DEFAULT
  42. #define NUM_THREADS_DEFAULT 8
  43. #endif
  44. #include "BBS2chProxyThreadPool.h"
  45. static std::stack<void *>curl_handles;
  46. static int num_threads = NUM_THREADS_DEFAULT;
  47. #endif
  48. char *proxy_server;
  49. long proxy_port;
  50. long proxy_type;
  51. long timeout = 30;
  52. char *user_agent;
  53. char *appKey;
  54. char *hmacKey;
  55. BBS2chProxyHttpHeaders api_auth_headers;
  56. BBS2chProxyHttpHeaders api_dat_headers;
  57. int allow_chunked;
  58. int verbosity;
  59. int curl_features;
  60. unsigned int curl_version_number;
  61. bool accept_https;
  62. int force_5chnet = 1;
  63. int force_5chnet_https;
  64. int force_ipv4;
  65. char *bbsmenu_url;
  66. char *api_server;
  67. BBS2chProxyHttpHeaders bbscgi_headers;
  68. int gikofix;
  69. CURLSH *curl_share;
  70. char *lua_script;
  71. unsigned int api_mode = 3;
  72. unsigned int mitm_mode = 0;
  73. std::vector<std::string> bbscgi_postorder;
  74. unsigned int bbscgi_utf8 = 1;
  75. int api_override;
  76. int direct_dat = 0;
  77. int fool_janestyle = 0;
  78. int talk_to_5ch = 0;
  79. int subject_to_lastmodify = 0;
  80. std::set<std::string> subject_to_lastmodify_hosts;
  81. int manage_bbscgi_cookies;
  82. static pthread_mutex_t lockarray[NUM_LOCKS];
  83. void log_printf(int level, const char *format ...)
  84. {
  85. if(level > verbosity) return;
  86. va_list argp;
  87. va_start(argp, format);
  88. vfprintf(stderr, format, argp);
  89. va_end(argp);
  90. fflush(stderr);
  91. }
  92. struct listener {
  93. int port;
  94. int sock;
  95. int backlog;
  96. struct sockaddr_in addr;
  97. listener(in_addr_t _addr, int _port, int _backlog) : port(_port), backlog(_backlog)
  98. {
  99. memset(&addr, 0, sizeof(addr));
  100. addr.sin_family = AF_INET;
  101. addr.sin_addr.s_addr = htonl(_addr);
  102. addr.sin_port = htons(_port);
  103. };
  104. bool initializeSocket() {
  105. #ifdef _WIN32
  106. if ((sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0)) == INVALID_SOCKET) {
  107. fprintf(stderr,"WSASocket: socket initialize error\n");
  108. return false;
  109. }
  110. #else
  111. if (-1 == (sock = socket(AF_INET, SOCK_STREAM, 0))) {
  112. perror("socket");
  113. return false;
  114. }
  115. #endif
  116. int optval=1;
  117. setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(optval));
  118. socklen_t addrlen = sizeof(addr);
  119. if (-1 == bind(sock, (struct sockaddr *)&addr, addrlen)) {
  120. perror("bind");
  121. return false;
  122. }
  123. if (-1 == listen(sock, backlog)) {
  124. perror("listen");
  125. return false;
  126. }
  127. addrlen = sizeof(addr);
  128. if (-1 == getsockname(sock, (struct sockaddr *)&addr, &addrlen)) {
  129. perror("getsockname");
  130. return false;
  131. }
  132. return true;
  133. };
  134. };
  135. static void usage(void)
  136. {
  137. fprintf(stderr,"usage: proxy2ch [OPTIONS]\n");
  138. fprintf(stderr,"available options:\n");
  139. fprintf(stderr," -p <port[,port2,...]> : Listen on port <port> (default: %d)\n",PORT);
  140. fprintf(stderr," -t <timeout> : Set connection timeout to <timeout> seconds (default: %ld)\n",timeout);
  141. fprintf(stderr," -a <user-agent> : Overwrite user-agent for connection\n");
  142. fprintf(stderr," -g : Accept all incoming connections (default: localhost only)\n");
  143. fprintf(stderr," -c : Accept HTTP CONNECT method (act as an HTTPS proxy)\n");
  144. fprintf(stderr," -4 : Force IPv4 DNS resolution\n");
  145. fprintf(stderr," -b <backlog> : Set backlog value to <backlog> for listen() (default: %d)\n",BACKLOG);
  146. fprintf(stderr," -s : Force https connection for 5ch.net/bbspink.com URLs\n");
  147. fprintf(stderr," --proxy <server:port> : Use proxy <server:port> for connection\n");
  148. fprintf(stderr," --api <AppKey:HmacKey> : Use API for reading/posting\n");
  149. fprintf(stderr," --api-usage <read|post|all|talk> : Specify operations where API is used (default: all)\n");
  150. fprintf(stderr," --api-auth-ua <user-agent> : Specify user-agent for API authentication\n");
  151. fprintf(stderr," --api-dat-ua <user-agent> : Specify user-agent for dat retrieving via API\n");
  152. fprintf(stderr," --api-auth-xua <X-2ch-UA> : Specify X-2ch-UA for API authentication\n");
  153. fprintf(stderr," --api-dat-xua <X-2ch-UA> : Specify X-2ch-UA for dat retrieving via API\n");
  154. fprintf(stderr," --api-server <server> : Specify gateway server for API\n");
  155. fprintf(stderr," --api-override : Add support for overriding requests which already use API for dat retrieving\n");
  156. fprintf(stderr," --direct-dat : Retrieve dat without using API or read.cgi\n");
  157. fprintf(stderr," --bbsmenu <URL> : Replace \"5ch.net\" occurrences in links for URL\n");
  158. fprintf(stderr," --chunked : Preserve \"chunked\" transfer encoding\n");
  159. fprintf(stderr," --bbscgi-header <header: value> : Force replace header when sending request to bbs.cgi\n");
  160. fprintf(stderr," --bbscgi-postorder <field1,field2,...> : Specify a field order in request body being sent to bbs.cgi\n");
  161. fprintf(stderr," --bbscgi-utf8 <none|api|all> : Specify whether a request body being sent to bbs.cgi should be converted to UTF-8\n");
  162. #ifdef USE_LUA
  163. fprintf(stderr," --bbscgi-lua <path> : Process request header/body being sent to bbs.cgi with a Lua script at <path>\n");
  164. #endif
  165. fprintf(stderr," --verbose : Print logs in detail\n");
  166. fprintf(stderr," --gikofix : Fix invalid HTTP POST body (for gikoNavi)\n");
  167. fprintf(stderr," --keystore <path> : Use a file at <path> as a persistent storage for MonaKey\n");
  168. #ifndef NO_THREAD_POOL
  169. fprintf(stderr," --num-threads <num> : Specify number of threads in a thread pool\n");
  170. #endif
  171. #ifdef USE_MITM
  172. fprintf(stderr," --mitm <minimal|all> : Act as MITM proxy when -c option is given (experimental)\n");
  173. fprintf(stderr," --mitm-ca-cert <certpath> : Specify CA certificate in PEM format for MITM proxy\n");
  174. fprintf(stderr," --mitm-ca-key <keypath> : Specify CA private key in PEM format for MITM proxy\n");
  175. fprintf(stderr," --mitm-certgen : Generate self-signed CA certificate and private key, print them in PEM format, and exit\n");
  176. #endif
  177. }
  178. static void *threadMainLoop(void *param)
  179. {
  180. #ifndef _WIN32
  181. sigset_t signalsToIgnore;
  182. sigemptyset(&signalsToIgnore);
  183. sigaddset(&signalsToIgnore, SIGPIPE);
  184. if (-1 == pthread_sigmask(SIG_BLOCK, &signalsToIgnore, NULL)) {
  185. perror("pthread_sigmask");
  186. return NULL;
  187. }
  188. #endif
  189. CURL *curl = curl_easy_init();
  190. #ifndef NO_THREAD_POOL
  191. BBS2chProxyThreadPool<PBBS2chProxyConnection> *pool = reinterpret_cast<BBS2chProxyThreadPool<PBBS2chProxyConnection> *>(param);
  192. #ifndef NO_CURL_REUSE_HTTPS
  193. bool canReuseHttps = curl_version_number >= 0x75000; /* 7.80.0 and later */
  194. #else
  195. bool canReuseHttps = false;
  196. #endif
  197. pool->lock();
  198. curl_handles.push(curl);
  199. pool->unlock();
  200. while (1) {
  201. PBBS2chProxyConnection connection;
  202. if (pool->getAndLock(&connection) != 0) {
  203. curl = curl_handles.top();
  204. curl_handles.pop();
  205. pool->unlock();
  206. break;
  207. }
  208. connection->curl = curl_handles.top();
  209. curl_handles.pop();
  210. pool->unlock();
  211. connection->connect();
  212. pool->lock();
  213. /* curl_easy_reset does not necessaryly release unused TLS contexts, and
  214. it results in waste of memory. This issue seems to be fixed in curl
  215. 7.80.0 and later, but in the earlier versions the logic below might
  216. be useful to reduce memory consumption.
  217. Ref: https://github.com/curl/curl/issues/7683 */
  218. /*if (curl_handles.size() + 2 < num_threads && pool->countInQueue() == 0) {
  219. curl_easy_cleanup(connection->curl);
  220. connection->curl = curl_easy_init();
  221. }*/
  222. if (!canReuseHttps && connection->isHttps) {
  223. curl_easy_cleanup(connection->curl);
  224. connection->curl = curl_easy_init();
  225. }
  226. curl_handles.push(connection->curl);
  227. pool->unlock();
  228. }
  229. #else
  230. BBS2chProxyConnection *connection = reinterpret_cast<BBS2chProxyConnection *>(param);
  231. connection->curl = curl;
  232. connection->connect();
  233. delete connection;
  234. #endif
  235. curl_easy_cleanup(curl);
  236. return NULL;
  237. }
  238. static void *listen(void *param)
  239. {
  240. std::vector<listener> *listeners = (std::vector<listener> *)param;
  241. std::vector<listener>::iterator it;
  242. for (it = listeners->begin(); it != listeners->end(); it++) {
  243. log_printf(0,"Listening on port %d...\n",it->port);
  244. if(it->addr.sin_addr.s_addr == INADDR_ANY) {
  245. log_printf(0,"WARNING: proxy accepts all incoming connections!\n");
  246. }
  247. }
  248. fflush(stderr);
  249. int sock_c;
  250. BBS2chProxyThreadCache cache;
  251. #ifndef NO_THREAD_POOL
  252. BBS2chProxyThreadPool<PBBS2chProxyConnection> pool(num_threads);
  253. pool.run(threadMainLoop);
  254. #endif
  255. #ifdef _WIN32
  256. fd_set fds;
  257. int nfds = 0;
  258. for (it = listeners->begin(); it != listeners->end(); it++) {
  259. if (nfds < it->sock) nfds = it->sock;
  260. }
  261. nfds = nfds + 1;
  262. #else
  263. std::vector<struct pollfd> fds;
  264. for (it = listeners->begin(); it != listeners->end(); it++) {
  265. struct pollfd fd;
  266. fd.fd = it->sock;
  267. fd.events = POLLIN;
  268. fd.revents = 0;
  269. fds.push_back(fd);
  270. }
  271. #endif
  272. while(1) {
  273. #ifdef _WIN32
  274. FD_ZERO(&fds);
  275. for (it = listeners->begin(); it != listeners->end(); it++) {
  276. FD_SET(it->sock, &fds);
  277. }
  278. if (select(nfds, &fds, NULL, NULL, NULL) < 0) {
  279. perror("select");
  280. continue;
  281. }
  282. for (it = listeners->begin(); it != listeners->end(); it++) {
  283. if (FD_ISSET(it->sock, &fds)) {
  284. int socket = it->sock;
  285. int port = it->port;
  286. #else
  287. if (poll(&fds.front(), fds.size(), -1) < 0) {
  288. perror("poll");
  289. continue;
  290. }
  291. for (std::vector<struct pollfd>::iterator it = fds.begin(); it != fds.end(); it++) {
  292. if (it->revents & POLLIN) {
  293. int socket = it->fd;
  294. int port = listeners->at(it - fds.begin()).port;
  295. #endif
  296. struct sockaddr_in tmp_addr;
  297. socklen_t addrlen = sizeof(tmp_addr);
  298. if (-1 == (sock_c = accept(socket, (struct sockaddr *)&tmp_addr, &addrlen))) {
  299. perror("accept");
  300. continue;
  301. }
  302. #ifndef NO_THREAD_POOL
  303. PBBS2chProxyConnection connection(new BBS2chProxyConnection(sock_c, port, &cache));
  304. pool.add(connection);
  305. #else
  306. BBS2chProxyConnection *connection = new BBS2chProxyConnection(sock_c, port, &cache);
  307. connection->run(threadMainLoop);
  308. #endif
  309. //fprintf(stderr,"accepted on %d\n", port);
  310. }
  311. }
  312. }
  313. }
  314. static void lock_cb(CURL *handle, curl_lock_data data, curl_lock_access access, void *userptr)
  315. {
  316. pthread_mutex_lock(&lockarray[data]);
  317. }
  318. static void unlock_cb(CURL *handle, curl_lock_data data, void *userptr)
  319. {
  320. pthread_mutex_unlock(&lockarray[data]);
  321. }
  322. static void init_locks(void)
  323. {
  324. int i;
  325. for(i = 0; i< NUM_LOCKS; i++)
  326. pthread_mutex_init(&lockarray[i], NULL);
  327. }
  328. int main(int argc, char *argv[])
  329. {
  330. std::vector<listener> listeners;
  331. std::vector<int> ports;
  332. int ch;
  333. extern char *optarg;
  334. extern int optind, opterr;
  335. int option_index;
  336. bool global = false;
  337. int backlog = BACKLOG;
  338. const char *certpath = NULL, *keypath = NULL;
  339. const char *keyStorage = NULL;
  340. struct option options[] = {
  341. {"proxy", 1, NULL, 0},
  342. {"api", 1, NULL, 0},
  343. {"api-auth-ua", 1, NULL, 0},
  344. {"api-dat-ua", 1, NULL, 0},
  345. {"api-auth-xua", 1, NULL, 0},
  346. {"api-dat-xua", 1, NULL, 0},
  347. {"api-auth-header", 1, NULL, 0},
  348. {"api-dat-header", 1, NULL, 0},
  349. {"api-server", 1, NULL, 0},
  350. {"api-usage", 1, NULL, 0},
  351. {"api-override", 0, NULL, 0},
  352. {"direct-dat", 0, NULL, 0},
  353. {"bbsmenu", 1, NULL, 0},
  354. {"chunked", 0, NULL, 0},
  355. {"verbose", 0, NULL, 0},
  356. {"debug", 0, NULL, 0},
  357. {"bbscgi-header", 1, NULL, 0},
  358. {"bbscgi-postorder", 1, NULL, 0},
  359. {"bbscgi-utf8", 1, NULL, 0},
  360. #ifdef USE_LUA
  361. {"bbscgi-lua", 1, NULL, 0},
  362. #endif
  363. {"gikofix", 0, NULL, 0},
  364. {"fool-janestyle", 0, NULL, 0},
  365. {"keystore", 1, NULL, 0},
  366. {"talk-to-5ch", 0, NULL, 0},
  367. {"subject-to-lastmodify", 2, NULL, 0},
  368. {"manage-bbscgi-cookies", 0, NULL, 0},
  369. #ifdef USE_MITM
  370. {"mitm", 1, NULL, 0},
  371. {"mitm-ca-cert", 1, NULL, 0},
  372. {"mitm-ca-key", 1, NULL, 0},
  373. {"mitm-certgen", 0, NULL, 0},
  374. #endif
  375. #ifndef NO_THREAD_POOL
  376. {"num-threads", 1, NULL, 0},
  377. #endif
  378. {0, 0, 0, 0}
  379. };
  380. curl_global_init(CURL_GLOBAL_DEFAULT);
  381. curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
  382. curl_features = data->features;
  383. curl_version_number = data->version_num;
  384. if(data->version_num >= 0x074400) { /* version 7.68.0 or later */
  385. init_locks();
  386. curl_share = curl_share_init();
  387. curl_share_setopt(curl_share, CURLSHOPT_LOCKFUNC, lock_cb);
  388. curl_share_setopt(curl_share, CURLSHOPT_UNLOCKFUNC, unlock_cb);
  389. curl_share_setopt(curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
  390. #if LIBCURL_VERSION_NUM >= 0x070a03
  391. curl_share_setopt(curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
  392. #endif
  393. /* Shared connection cache is still buggy at the moment!
  394. See https://github.com/curl/curl/issues/4915 */
  395. #if 0 && LIBCURL_VERSION_NUM >= 0x073900
  396. curl_share_setopt(curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
  397. #endif
  398. }
  399. log_printf(0,"proxy2ch version %s with curl %s (TLS/SSL backend: %s)\n",VERSION,data->version,data->ssl_version);
  400. #ifdef USE_LUA
  401. log_printf(0,"Scripting enabled with " LUA_RELEASE "\n");
  402. #endif
  403. while ((ch = getopt_long(argc, argv, "p:t:ha:gc4b:s", options, &option_index)) != -1) {
  404. switch (ch) {
  405. case 0:
  406. if(!strcmp(options[option_index].name, "proxy")) {
  407. char *ptr = strchr(optarg, '@');
  408. if(!ptr) {
  409. ptr = strstr(optarg, "://");
  410. if(ptr) ptr = strchr(ptr+3,':');
  411. else ptr = strchr(optarg,':');
  412. }
  413. else ptr = strchr(ptr+1,':');
  414. if(!ptr) {
  415. fprintf(stderr,"Proxy port is not specified, as --proxy=server:port\n");
  416. return -1;
  417. }
  418. proxy_server = (char *)malloc(ptr-optarg+1);
  419. proxy_port = atoi(ptr+1);
  420. memcpy(proxy_server,optarg,ptr-optarg);
  421. proxy_server[ptr-optarg] = 0;
  422. if(!strncasecmp(optarg,"socks4://",9)) proxy_type = CURLPROXY_SOCKS4;
  423. else if(!strncasecmp(optarg,"socks5://",9)) proxy_type = CURLPROXY_SOCKS5;
  424. #if LIBCURL_VERSION_NUM >= 0x071200
  425. else if(!strncasecmp(optarg,"socks4a://",10)) proxy_type = CURLPROXY_SOCKS4A;
  426. else if(!strncasecmp(optarg,"socks5h://",10)) proxy_type = CURLPROXY_SOCKS5_HOSTNAME;
  427. #endif
  428. }
  429. else if(!strcmp(options[option_index].name, "api")) {
  430. if((curl_features & CURL_VERSION_SSL) == 0) {
  431. fprintf(stderr,"Your libcurl doesn't support HTTPS; API mode cannot be enabled.\n");
  432. return -1;
  433. }
  434. char *ptr = strchr(optarg, ':');
  435. if(!ptr) {
  436. fprintf(stderr,"API keys should be provided as AppKey:HmacKey\n");
  437. return -1;
  438. }
  439. appKey = (char *)malloc(ptr-optarg+1);
  440. memcpy(appKey,optarg,ptr-optarg);
  441. appKey[ptr-optarg] = 0;
  442. char *start = ptr+1;
  443. ptr = strchr(start, ':');
  444. if(!ptr) ptr = strchr(optarg, 0);
  445. hmacKey = (char *)malloc(ptr-start+1);
  446. memcpy(hmacKey,start,ptr-start);
  447. hmacKey[ptr-start] = 0;
  448. /*if(*ptr) {
  449. x_2ch_ua = (char *)malloc(strlen(ptr+1)+11);
  450. sprintf(x_2ch_ua,"X-2ch-UA: %s",ptr+1);
  451. }*/
  452. //fprintf(stderr,"%s,%s,%s\n",appKey,hmacKey,x_2ch_ua);
  453. //return 0;
  454. }
  455. else if(!strcmp(options[option_index].name, "api-auth-ua")) {
  456. api_auth_headers.set("User-Agent", optarg);
  457. }
  458. else if(!strcmp(options[option_index].name, "api-dat-ua")) {
  459. api_dat_headers.set("User-Agent", optarg);
  460. }
  461. else if(!strcmp(options[option_index].name, "api-auth-xua")) {
  462. api_auth_headers.set("X-2ch-UA", optarg);
  463. }
  464. else if(!strcmp(options[option_index].name, "api-dat-xua")) {
  465. api_dat_headers.set("X-2ch-UA", optarg);
  466. }
  467. else if(!strcmp(options[option_index].name, "api-auth-header")) {
  468. PBBS2chProxyHttpHeaderEntry parsedHeader = BBS2chProxyHttpHeaders::parse(optarg, strlen(optarg));
  469. if (parsedHeader) {
  470. api_auth_headers.add(parsedHeader->getName(), parsedHeader->getValue());
  471. }
  472. }
  473. else if(!strcmp(options[option_index].name, "api-dat-header")) {
  474. PBBS2chProxyHttpHeaderEntry parsedHeader = BBS2chProxyHttpHeaders::parse(optarg, strlen(optarg));
  475. if (parsedHeader) {
  476. api_dat_headers.add(parsedHeader->getName(), parsedHeader->getValue());
  477. }
  478. }
  479. else if(!strcmp(options[option_index].name, "chunked")) {
  480. allow_chunked = 1;
  481. }
  482. else if(!strcmp(options[option_index].name, "verbose")) {
  483. verbosity = 1;
  484. }
  485. else if(!strcmp(options[option_index].name, "debug")) {
  486. verbosity = 5;
  487. }
  488. else if(!strcmp(options[option_index].name, "bbsmenu")) {
  489. bbsmenu_url = (char *)malloc(strlen(optarg)+1);
  490. strcpy(bbsmenu_url, optarg);
  491. }
  492. else if(!strcmp(options[option_index].name, "api-server")) {
  493. if(api_server) free(api_server);
  494. api_server = (char *)malloc(strlen(optarg)+1);
  495. strcpy(api_server, optarg);
  496. }
  497. else if(!strcmp(options[option_index].name, "bbscgi-header")) {
  498. PBBS2chProxyHttpHeaderEntry parsedHeader = BBS2chProxyHttpHeaders::parse(optarg, strlen(optarg));
  499. if (parsedHeader) {
  500. bbscgi_headers.add(parsedHeader->getName(), parsedHeader->getValue());
  501. }
  502. }
  503. else if(!strcmp(options[option_index].name, "bbscgi-postorder")) {
  504. const char *ptr = optarg;
  505. while(*ptr == ' ') ptr++;
  506. while(*ptr) {
  507. const char *end = strchr(ptr, ',');
  508. if(end) {
  509. const char *next = end + 1;
  510. if(end > ptr) {
  511. end--;
  512. while(*end == ' ' && end > ptr) end--;
  513. bbscgi_postorder.push_back(std::string(ptr, end-ptr+1));
  514. }
  515. ptr = next;
  516. while(*ptr == ' ') ptr++;
  517. continue;
  518. }
  519. end = strchr(ptr, 0);
  520. while(*end == ' ' && end > ptr) end--;
  521. if(end > ptr) bbscgi_postorder.push_back(std::string(ptr, end-ptr));
  522. break;
  523. }
  524. }
  525. else if(!strcmp(options[option_index].name, "bbscgi-utf8")) {
  526. if(!strcmp(optarg, "none")) bbscgi_utf8 = 0;
  527. else if(!strcmp(optarg, "api")) bbscgi_utf8 = 1;
  528. else if(!strcmp(optarg, "all")) bbscgi_utf8 = 2;
  529. else {
  530. fprintf(stderr, "A value for --bbscgi-utf8 must be one of [none, api, all]\n");
  531. return -1;
  532. }
  533. }
  534. #ifdef USE_LUA
  535. else if(!strcmp(options[option_index].name, "bbscgi-lua")) {
  536. lua_script = (char *)malloc(strlen(optarg)+1);
  537. strcpy(lua_script, optarg);
  538. }
  539. #endif
  540. else if(!strcmp(options[option_index].name, "gikofix")) {
  541. gikofix = 1;
  542. }
  543. else if(!strcmp(options[option_index].name, "api-usage")) {
  544. if(!strcmp(optarg, "read")) api_mode = 1;
  545. else if(!strcmp(optarg, "post")) api_mode = 2;
  546. else if(!strcmp(optarg, "postinclpink")) api_mode = 4;
  547. else if(!strcmp(optarg, "all")) api_mode = 3;
  548. else if(!strcmp(optarg, "allinclpink")) api_mode = 5;
  549. else if(!strcmp(optarg, "talk")) api_mode = 8;
  550. else {
  551. fprintf(stderr, "A value for --api-usage must be one of [read, post, postinclpink, all, allinclpink, talk]\n");
  552. return -1;
  553. }
  554. }
  555. else if(!strcmp(options[option_index].name, "api-override")) {
  556. api_override = 1;
  557. }
  558. #ifdef USE_MITM
  559. else if(!strcmp(options[option_index].name, "mitm")) {
  560. if(!strcmp(optarg, "minimal")) mitm_mode = 1;
  561. else if(!strcmp(optarg, "all")) mitm_mode = 2;
  562. else {
  563. fprintf(stderr, "A value for --mitm must be one of [minimal, all]\n");
  564. return -1;
  565. }
  566. }
  567. else if(!strcmp(options[option_index].name, "mitm-ca-cert")) {
  568. certpath = optarg;
  569. }
  570. else if(!strcmp(options[option_index].name, "mitm-ca-key")) {
  571. keypath = optarg;
  572. }
  573. else if(!strcmp(options[option_index].name, "mitm-certgen")) {
  574. BBS2chProxySecureSocket::generateAndPrintSelfSignedCertificate();
  575. return 0;
  576. }
  577. #endif
  578. #ifndef NO_THREAD_POOL
  579. else if(!strcmp(options[option_index].name, "num-threads")) {
  580. int num = atoi(optarg);
  581. if (num < 1) {
  582. fprintf(stderr, "Number of threads must be greater than 0\n");
  583. return -1;
  584. }
  585. if (num > 64) {
  586. fprintf(stderr, "Number of threads must be less than or equal to 64\n");
  587. return -1;
  588. }
  589. num_threads = num;
  590. }
  591. #endif
  592. else if(!strcmp(options[option_index].name, "keystore")) {
  593. struct stat st;
  594. if (stat(optarg, &st) != -1) {
  595. if (S_ISDIR(st.st_mode)) {
  596. fprintf(stderr, "The path \"%s\" should be a file, not a directory\n", optarg);
  597. return -1;
  598. }
  599. }
  600. keyStorage = optarg;
  601. }
  602. else if(!strcmp(options[option_index].name, "direct-dat")) {
  603. direct_dat = 1;
  604. }
  605. else if(!strcmp(options[option_index].name, "fool-janestyle")) {
  606. fool_janestyle = 1;
  607. }
  608. else if(!strcmp(options[option_index].name, "talk-to-5ch")) {
  609. talk_to_5ch = 1;
  610. }
  611. else if(!strcmp(options[option_index].name, "subject-to-lastmodify")) {
  612. if (optarg) {
  613. for (char *ptr = optarg; *ptr;) {
  614. char *next = strchr(ptr, ',');
  615. if (!next) {
  616. subject_to_lastmodify_hosts.insert(std::string(ptr));
  617. break;
  618. } else {
  619. if (next > ptr) subject_to_lastmodify_hosts.insert(std::string(ptr, next-ptr));
  620. ptr = next + 1;
  621. }
  622. }
  623. subject_to_lastmodify = 2;
  624. }
  625. else subject_to_lastmodify = 1;
  626. }
  627. else if (!strcmp(options[option_index].name, "manage-bbscgi-cookies")) {
  628. if (data->version_num >= 0x074d00) { // 7.77.0 or later
  629. manage_bbscgi_cookies = 1;
  630. } else {
  631. fprintf(stderr, "--manage-bbscgi-cookies option only works on 7.77.0 or later.\n");
  632. return -1;
  633. }
  634. }
  635. break;
  636. case 'p':
  637. for (char *ptr = optarg;;) {
  638. int port = strtoul(ptr, &ptr, 10);
  639. if (port < 65536) ports.push_back(port);
  640. if (*ptr != ',') break;
  641. ptr++;
  642. }
  643. break;
  644. case 't':
  645. timeout = atoi(optarg);
  646. break;
  647. case 'a':
  648. user_agent = (char *)malloc(strlen(optarg)+1);
  649. strcpy(user_agent, optarg);
  650. break;
  651. case 'g':
  652. global = true;
  653. break;
  654. case 'c':
  655. accept_https = true;
  656. break;
  657. case '4':
  658. force_ipv4 = 1;
  659. break;
  660. case 'b':
  661. backlog = atoi(optarg);
  662. break;
  663. case 's':
  664. if((curl_features & CURL_VERSION_SSL) == 0) {
  665. fprintf(stderr,"Your libcurl doesn't support HTTPS; it does not work with -s option.\n");
  666. return -1;
  667. }
  668. if(strstr(data->ssl_version, "OpenSSL/0") || strstr(data->ssl_version, "OpenSSL/1.0") ||
  669. (strstr(data->ssl_version, "LibreSSL/2") && !strstr(data->ssl_version, "LibreSSL/2.9"))) {
  670. fprintf(stderr,
  671. "WARNING: OpenSSL < 1.1.0 and LibreSSL < 2.9.0 aren't thread-safe without setting callbacks for mutex. "
  672. "It may cause unintended crashes when many requests are incoming at the same time.\n");
  673. }
  674. force_5chnet_https = 1;
  675. break;
  676. default:
  677. usage();
  678. return 0;
  679. }
  680. }
  681. if (!api_server) {
  682. if (api_mode & 8) api_server = strdup("api.talk-platform.com");
  683. else api_server = strdup("api.5ch.net");
  684. }
  685. if (direct_dat && (appKey && (api_mode & 1))) {
  686. fprintf(stderr, "WARNING: API will never be used for .dat retrieving when --direct-dat is given.\n");
  687. }
  688. #ifdef USE_MITM
  689. if (mitm_mode) {
  690. if (!certpath || !keypath) {
  691. fprintf(stderr, "MITM is enabled but certificate and/or key is not given.\n");
  692. return -1;
  693. }
  694. if (BBS2chProxySecureSocket::initializeCerts(certpath, keypath) != 0) {
  695. fprintf(stderr, "MITM is enabled but given certificate and/or key is invalid.\n");
  696. return -1;
  697. }
  698. if (!accept_https) {
  699. fprintf(stderr, "WARNING: --mitm option is given but -c is not given. MITM mode is disabled.\n");
  700. }
  701. }
  702. #endif
  703. log_printf(0, "Global User-Agent: %s\n",user_agent?user_agent:"n/a");
  704. if(appKey) {
  705. log_printf(0, "Use API for:");
  706. if (api_mode & 1) log_printf(0, " reading");
  707. if (api_mode & 2) log_printf(0, " posting");
  708. if (api_mode & 4) log_printf(0, " posting (including bbspink)");
  709. if (api_mode & 8) log_printf(0, " talk boards");
  710. log_printf(0, "\n");
  711. if ((api_mode & 1) || (api_mode & 8)) {
  712. if (user_agent && !strncmp(user_agent, "Monazilla/", strlen("Monazilla/"))) {
  713. if (!api_auth_headers.has("User-Agent")) api_auth_headers.set("User-Agent", user_agent);
  714. if (!api_dat_headers.has("User-Agent")) api_dat_headers.set("User-Agent", user_agent);
  715. }
  716. log_printf(0, "API gateway server: %s\n",api_server);
  717. log_printf(0, "User-Agent (for API authentication): %s\n", api_auth_headers.get("User-Agent").c_str());
  718. log_printf(0, "User-Agent (for API dat retrieving): %s\n", api_dat_headers.get("User-Agent").c_str());
  719. log_printf(0, "X-2ch-UA (for API authentication): %s\n", api_auth_headers.get("X-2ch-UA").c_str());
  720. log_printf(0, "X-2ch-UA (for API dat retrieving): %s\n", api_dat_headers.get("X-2ch-UA").c_str());
  721. }
  722. }
  723. if(!bbscgi_headers.empty()) {
  724. log_printf(0, "Custom headers for bbs.cgi:\n");
  725. for (BBS2chProxyHttpHeaders::iterator it = bbscgi_headers.begin(); it != bbscgi_headers.end(); ++it) {
  726. log_printf(0, " %s\n", it->second.c_str());
  727. }
  728. }
  729. if(lua_script) {
  730. log_printf(0, "Use Lua script %s for bbs.cgi request modification\n", lua_script);
  731. }
  732. if(proxy_server) {
  733. log_printf(0,"Use proxy %s:%ld for connection\n",proxy_server,proxy_port);
  734. }
  735. if (keyStorage) {
  736. BBS2chProxyConnection::keyManager.setStorage(keyStorage);
  737. int loaded = BBS2chProxyConnection::keyManager.loadKeys();
  738. if (loaded) {
  739. log_printf(0, "Loaded %d keys from %s\n", loaded, keyStorage);
  740. } else {
  741. log_printf(0, "New keys will be saved to %s\n", keyStorage);
  742. }
  743. }
  744. BBS2chProxyConnection::compileRegex();
  745. #ifdef _WIN32
  746. WSADATA wsaData;
  747. if (WSAStartup(MAKEWORD(2, 0), &wsaData) == SOCKET_ERROR) {
  748. fprintf(stderr, "WSAStartup: error initializing WSA.\n");
  749. return -1;
  750. }
  751. int optval = SO_SYNCHRONOUS_NONALERT;
  752. setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&optval, sizeof(optval));
  753. #endif
  754. if (ports.empty()) ports.push_back(PORT);
  755. for (std::vector<int>::iterator it = ports.begin(); it != ports.end(); it++) {
  756. listeners.push_back(listener(global ? INADDR_ANY : INADDR_LOOPBACK, *it, backlog));
  757. if (!listeners.back().initializeSocket()) return -1;
  758. }
  759. #ifndef _WIN32
  760. struct sigaction sa;
  761. memset(&sa, 0, sizeof(sa));
  762. sa.sa_handler = SIG_IGN;
  763. sigemptyset(&sa.sa_mask);
  764. if (-1 == sigaction(SIGPIPE, &sa, NULL)) {
  765. perror("sigaction");
  766. return -1;
  767. }
  768. #endif
  769. #if 0
  770. pthread_t thread_listener;
  771. if(0 != pthread_create(&thread_listener , NULL , listen , &listeners))
  772. perror("pthread_create");
  773. pthread_join(thread_listener, NULL);
  774. #else
  775. listen(&listeners);
  776. #endif
  777. return 0;
  778. }