main.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <stdarg.h>
  5. #ifdef _WIN32
  6. #include <winsock2.h>
  7. #include <ws2tcpip.h>
  8. #include <mswsock.h>
  9. #else
  10. #include <sys/socket.h>
  11. #include <netinet/in.h>
  12. #include <netdb.h>
  13. #endif
  14. #include <pthread.h>
  15. #include <signal.h>
  16. #include <getopt.h>
  17. #include <curl/curl.h>
  18. #include "BBS2chProxyConnection.h"
  19. #include "BBS2chProxyThreadInfo.h"
  20. #include "BBS2chProxyAuth.h"
  21. #define PORT 9080
  22. #define VERSION "20210519"
  23. #define BACKLOG 32
  24. #define NUM_LOCKS 7
  25. char *proxy_server;
  26. long proxy_port;
  27. long proxy_type;
  28. long timeout = 30;
  29. char *user_agent;
  30. char *appKey;
  31. char *hmacKey;
  32. char *api_ua_auth;
  33. char *api_ua_dat;
  34. char *x_2ch_ua_auth;
  35. char *x_2ch_ua_dat;
  36. int allow_chunked;
  37. int verbosity;
  38. int curl_features;
  39. unsigned int curl_version_number;
  40. bool accept_https;
  41. int force_5chnet = 1;
  42. int force_5chnet_https;
  43. int force_ipv4;
  44. char *bbsmenu_url;
  45. char *api_server;
  46. std::map<std::string, std::string> bbscgi_headers;
  47. int gikofix;
  48. CURLSH *curl_share;
  49. static pthread_mutex_t lockarray[NUM_LOCKS];
  50. void log_printf(int level, const char *format ...)
  51. {
  52. if(level > verbosity) return;
  53. va_list argp;
  54. va_start(argp, format);
  55. vfprintf(stderr, format, argp);
  56. va_end(argp);
  57. fflush(stderr);
  58. }
  59. struct listener {
  60. int port;
  61. int sock_listener;
  62. struct sockaddr_in addr_listener;
  63. };
  64. static void usage(void)
  65. {
  66. fprintf(stderr,"usage: proxy2ch [OPTIONS]\n");
  67. fprintf(stderr,"available options:\n");
  68. fprintf(stderr," -p <port> : Listen on port <port> (default: %d)\n",PORT);
  69. fprintf(stderr," -t <timeout> : Set connection timeout to <timeout> seconds (default: %ld)\n",timeout);
  70. fprintf(stderr," -a <user-agent> : Overwrite user-agent for connection\n");
  71. fprintf(stderr," -g : Accept all incoming connections (default: localhost only)\n");
  72. fprintf(stderr," -c : Accept HTTP CONNECT method (act as an HTTPS proxy)\n");
  73. fprintf(stderr," -4 : Force IPv4 DNS resolution\n");
  74. fprintf(stderr," -b <backlog> : Set backlog value to <backlog> for listen() (default: %d)\n",BACKLOG);
  75. fprintf(stderr," -s : Force https connection for 5ch.net/bbspink.com URLs\n");
  76. fprintf(stderr," --proxy <server:port> : Use proxy <server:port> for connection\n");
  77. fprintf(stderr," --api <AppKey:HmacKey> : Use API instead of read.cgi for dat retrieving\n");
  78. fprintf(stderr," --api-auth-ua <user-agent> : Specify user-agent for API authentication\n");
  79. fprintf(stderr," --api-dat-ua <user-agent> : Specify user-agent for dat retrieving via API\n");
  80. fprintf(stderr," --api-auth-xua <X-2ch-UA> : Specify X-2ch-UA for API authentication\n");
  81. fprintf(stderr," --api-dat-xua <X-2ch-UA> : Specify X-2ch-UA for dat retrieving via API\n");
  82. fprintf(stderr," --api-server <server> : Specify gateway server for API\n");
  83. fprintf(stderr," --bbsmenu <URL> : Replace \"5ch.net\" occurrences in links for URL\n");
  84. fprintf(stderr," --chunked : Preserve \"chunked\" transfer encoding\n");
  85. fprintf(stderr," --bbscgi-header <header: value> : Force replace header when requesting bbs.cgi\n");
  86. fprintf(stderr," --verbose : Print logs in detail\n");
  87. fprintf(stderr," --gikofix : Fix invalid HTTP POST body (for gikoNavi)\n");
  88. }
  89. static void *listen(void *param)
  90. {
  91. struct listener *listener = (struct listener *)param;
  92. log_printf(0,"Listening on port %d...\n",listener->port);
  93. if(listener->addr_listener.sin_addr.s_addr == INADDR_ANY) {
  94. log_printf(0,"WARNING: proxy accepts all incoming connections!\n");
  95. }
  96. fflush(stderr);
  97. int sock_c;
  98. pthread_mutex_t mutex, mutex2;
  99. BBS2chProxyThreadCache *cache = new BBS2chProxyThreadCache();
  100. socklen_t addrlen = sizeof(listener->addr_listener);
  101. pthread_mutex_init(&mutex, NULL);
  102. pthread_mutex_init(&mutex2, NULL);
  103. BBS2chProxyAuth *auth = new BBS2chProxyAuth(&mutex2);
  104. while(1) {
  105. if (-1 == (sock_c = accept(listener->sock_listener, (struct sockaddr *)&listener->addr_listener, &addrlen))) {
  106. perror("accept");
  107. continue;
  108. }
  109. //fprintf(stderr,"accepted\n");
  110. BBS2chProxyConnection *connection = new BBS2chProxyConnection(sock_c, cache, auth, &mutex);
  111. connection->run();
  112. }
  113. pthread_mutex_destroy(&mutex);
  114. pthread_mutex_destroy(&mutex2);
  115. delete cache;
  116. }
  117. static void lock_cb(CURL *handle, curl_lock_data data, curl_lock_access access, void *userptr)
  118. {
  119. pthread_mutex_lock(&lockarray[data]);
  120. }
  121. static void unlock_cb(CURL *handle, curl_lock_data data, void *userptr)
  122. {
  123. pthread_mutex_unlock(&lockarray[data]);
  124. }
  125. static void init_locks(void)
  126. {
  127. int i;
  128. for(i = 0; i< NUM_LOCKS; i++)
  129. pthread_mutex_init(&lockarray[i], NULL);
  130. }
  131. int main(int argc, char *argv[])
  132. {
  133. struct listener listener;
  134. int ch;
  135. extern char *optarg;
  136. extern int optind, opterr;
  137. int option_index;
  138. bool global = false;
  139. int backlog = BACKLOG;
  140. struct option options[] = {
  141. {"proxy", 1, NULL, 0},
  142. {"api", 1, NULL, 0},
  143. {"api-auth-ua", 1, NULL, 0},
  144. {"api-dat-ua", 1, NULL, 0},
  145. {"api-auth-xua", 1, NULL, 0},
  146. {"api-dat-xua", 1, NULL, 0},
  147. {"api-server", 1, NULL, 0},
  148. {"bbsmenu", 1, NULL, 0},
  149. {"chunked", 0, NULL, 0},
  150. {"verbose", 0, NULL, 0},
  151. {"debug", 0, NULL, 0},
  152. {"bbscgi-header", 1, NULL, 0},
  153. {"gikofix", 0, NULL, 0},
  154. {0, 0, 0, 0}
  155. };
  156. curl_global_init(CURL_GLOBAL_DEFAULT);
  157. curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
  158. curl_features = data->features;
  159. curl_version_number = data->version_num;
  160. if(data->version_num >= 0x074400) { /* version 7.68.0 or later */
  161. init_locks();
  162. curl_share = curl_share_init();
  163. curl_share_setopt(curl_share, CURLSHOPT_LOCKFUNC, lock_cb);
  164. curl_share_setopt(curl_share, CURLSHOPT_UNLOCKFUNC, unlock_cb);
  165. curl_share_setopt(curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
  166. #if LIBCURL_VERSION_NUM >= 0x070a03
  167. curl_share_setopt(curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
  168. #endif
  169. /* Shared connection cache is still buggy at the moment!
  170. See https://github.com/curl/curl/issues/4915 */
  171. #if 0 && LIBCURL_VERSION_NUM >= 0x073900
  172. curl_share_setopt(curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
  173. #endif
  174. }
  175. log_printf(0,"proxy2ch version %s with curl %s (TLS/SSL backend: %s)\n",VERSION,data->version,data->ssl_version);
  176. memset(&listener, 0, sizeof(listener));
  177. listener.port = PORT;
  178. api_server = strdup("api.5ch.net");
  179. while ((ch = getopt_long(argc, argv, "p:t:ha:gc4b:s", options, &option_index)) != -1) {
  180. switch (ch) {
  181. case 0:
  182. if(!strcmp(options[option_index].name, "proxy")) {
  183. char *ptr = strchr(optarg, '@');
  184. if(!ptr) {
  185. ptr = strstr(optarg, "://");
  186. if(ptr) ptr = strchr(ptr+3,':');
  187. else ptr = strchr(optarg,':');
  188. }
  189. else ptr = strchr(ptr+1,':');
  190. if(!ptr) {
  191. fprintf(stderr,"Proxy port is not specified, as --proxy=server:port\n");
  192. return -1;
  193. }
  194. proxy_server = (char *)malloc(ptr-optarg+1);
  195. proxy_port = atoi(ptr+1);
  196. memcpy(proxy_server,optarg,ptr-optarg);
  197. proxy_server[ptr-optarg] = 0;
  198. if(!strncasecmp(optarg,"socks4://",9)) proxy_type = CURLPROXY_SOCKS4;
  199. else if(!strncasecmp(optarg,"socks5://",9)) proxy_type = CURLPROXY_SOCKS5;
  200. #if LIBCURL_VERSION_NUM >= 0x071200
  201. else if(!strncasecmp(optarg,"socks4a://",10)) proxy_type = CURLPROXY_SOCKS4A;
  202. else if(!strncasecmp(optarg,"socks5h://",10)) proxy_type = CURLPROXY_SOCKS5_HOSTNAME;
  203. #endif
  204. log_printf(0,"Using proxy %s:%ld for connection\n",proxy_server,proxy_port);
  205. }
  206. else if(!strcmp(options[option_index].name, "api")) {
  207. if((curl_features & CURL_VERSION_SSL) == 0) {
  208. fprintf(stderr,"Your libcurl doesn't support HTTPS; API mode cannot be enabled.\n");
  209. return -1;
  210. }
  211. char *ptr = strchr(optarg, ':');
  212. if(!ptr) {
  213. fprintf(stderr,"API keys should be provided as AppKey:HmacKey\n");
  214. return -1;
  215. }
  216. appKey = (char *)malloc(ptr-optarg+1);
  217. memcpy(appKey,optarg,ptr-optarg);
  218. appKey[ptr-optarg] = 0;
  219. char *start = ptr+1;
  220. ptr = strchr(start, ':');
  221. if(!ptr) ptr = strchr(optarg, 0);
  222. hmacKey = (char *)malloc(ptr-start+1);
  223. memcpy(hmacKey,start,ptr-start);
  224. hmacKey[ptr-start] = 0;
  225. /*if(*ptr) {
  226. x_2ch_ua = (char *)malloc(strlen(ptr+1)+11);
  227. sprintf(x_2ch_ua,"X-2ch-UA: %s",ptr+1);
  228. }*/
  229. //fprintf(stderr,"%s,%s,%s\n",appKey,hmacKey,x_2ch_ua);
  230. //return 0;
  231. }
  232. else if(!strcmp(options[option_index].name, "api-auth-ua")) {
  233. api_ua_auth = (char *)malloc(strlen(optarg)+1);
  234. strcpy(api_ua_auth,optarg);
  235. }
  236. else if(!strcmp(options[option_index].name, "api-dat-ua")) {
  237. api_ua_dat = (char *)malloc(strlen(optarg)+1);
  238. strcpy(api_ua_dat,optarg);
  239. }
  240. else if(!strcmp(options[option_index].name, "api-auth-xua")) {
  241. x_2ch_ua_auth = (char *)malloc(strlen(optarg)+11);
  242. sprintf(x_2ch_ua_auth,"X-2ch-UA: %s",optarg);
  243. }
  244. else if(!strcmp(options[option_index].name, "api-dat-xua")) {
  245. x_2ch_ua_dat = (char *)malloc(strlen(optarg)+11);
  246. sprintf(x_2ch_ua_dat,"X-2ch-UA: %s",optarg);
  247. }
  248. else if(!strcmp(options[option_index].name, "chunked")) {
  249. allow_chunked = 1;
  250. }
  251. else if(!strcmp(options[option_index].name, "verbose")) {
  252. verbosity = 1;
  253. }
  254. else if(!strcmp(options[option_index].name, "debug")) {
  255. verbosity = 5;
  256. }
  257. else if(!strcmp(options[option_index].name, "bbsmenu")) {
  258. bbsmenu_url = (char *)malloc(strlen(optarg)+1);
  259. strcpy(bbsmenu_url, optarg);
  260. }
  261. else if(!strcmp(options[option_index].name, "api-server")) {
  262. if(api_server) free(api_server);
  263. api_server = (char *)malloc(strlen(optarg)+1);
  264. strcpy(api_server, optarg);
  265. }
  266. else if(!strcmp(options[option_index].name, "bbscgi-header")) {
  267. char *ptr = strchr(optarg, ':');
  268. if(!ptr) break;
  269. char *header = (char *)malloc(ptr-optarg+1);
  270. memcpy(header,optarg,ptr-optarg);
  271. header[ptr-optarg] = 0;
  272. char *value = ptr+1;
  273. ptr = header+(ptr-optarg-1);
  274. while(*ptr == ' ') *ptr-- = 0;
  275. while(*value == ' ') value++;
  276. bbscgi_headers[header] = value;
  277. log_printf(1, "Custom header for bbs.cgi: \"%s: %s\"\n", header, value);
  278. free(header);
  279. }
  280. else if(!strcmp(options[option_index].name, "gikofix")) {
  281. gikofix = 1;
  282. }
  283. break;
  284. case 'p':
  285. listener.port = atoi(optarg);
  286. break;
  287. case 't':
  288. timeout = atoi(optarg);
  289. break;
  290. case 'a':
  291. user_agent = (char *)malloc(strlen(optarg)+1);
  292. strcpy(user_agent, optarg);
  293. break;
  294. case 'g':
  295. global = true;
  296. break;
  297. case 'c':
  298. accept_https = true;
  299. break;
  300. case '4':
  301. force_ipv4 = 1;
  302. break;
  303. case 'b':
  304. backlog = atoi(optarg);
  305. break;
  306. case 's':
  307. if((curl_features & CURL_VERSION_SSL) == 0) {
  308. fprintf(stderr,"Your libcurl doesn't support HTTPS; it does not work with -s option.\n");
  309. return -1;
  310. }
  311. if(strstr(data->ssl_version, "OpenSSL/0") || strstr(data->ssl_version, "OpenSSL/1.0") ||
  312. (strstr(data->ssl_version, "LibreSSL/2") && !strstr(data->ssl_version, "LibreSSL/2.9"))) {
  313. fprintf(stderr,
  314. "WARNING: OpenSSL < 1.1.0 and LibreSSL < 2.9.0 aren't thread-safe without setting callbacks for mutex. "
  315. "It may cause unintended crashes when many requests are incoming at the same time.\n");
  316. }
  317. force_5chnet_https = 1;
  318. break;
  319. default:
  320. usage();
  321. return 0;
  322. }
  323. }
  324. log_printf(0, "Global User-Agent: %s\n",user_agent?user_agent:"n/a");
  325. if(appKey) {
  326. log_printf(0, "API gateway server: %s\n",api_server);
  327. log_printf(0, "User-Agent (for API authentication): %s\n",api_ua_auth?api_ua_auth:"");
  328. log_printf(0, "User-Agent (for API dat retrieving): %s\n",api_ua_dat?api_ua_dat:"");
  329. log_printf(0, "X-2ch-UA (for API authentication): %s\n",x_2ch_ua_auth?x_2ch_ua_auth+10:"");
  330. log_printf(0, "X-2ch-UA (for API dat retrieving): %s\n",x_2ch_ua_dat?x_2ch_ua_dat+10:"");
  331. }
  332. #ifdef _WIN32
  333. WSADATA wsaData;
  334. if (WSAStartup(MAKEWORD(2, 0), &wsaData) == SOCKET_ERROR) {
  335. fprintf(stderr, "WSAStartup: error initializing WSA.\n");
  336. return -1;
  337. }
  338. #endif
  339. listener.addr_listener.sin_family = AF_INET;
  340. if(global) listener.addr_listener.sin_addr.s_addr = INADDR_ANY;
  341. else listener.addr_listener.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  342. listener.addr_listener.sin_port = htons(listener.port);
  343. #ifdef _WIN32
  344. if ((listener.sock_listener = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0)) == INVALID_SOCKET) {
  345. fprintf(stderr,"WSASocket: socket initialize error\n");
  346. return -1;
  347. }
  348. #else
  349. if (-1 == (listener.sock_listener = socket(AF_INET, SOCK_STREAM, 0))) {
  350. perror("socket");
  351. return -1;
  352. }
  353. #endif
  354. int optval=1;
  355. setsockopt(listener.sock_listener, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(optval));
  356. #ifdef _WIN32
  357. optval = SO_SYNCHRONOUS_NONALERT;
  358. setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&optval, sizeof(optval));
  359. #endif
  360. socklen_t addrlen = sizeof(listener.addr_listener);
  361. if (-1 == bind(listener.sock_listener, (struct sockaddr *)&listener.addr_listener, addrlen)) {
  362. perror("bind");
  363. return -1;
  364. }
  365. if (-1 == listen(listener.sock_listener, backlog)) {
  366. perror("listen");
  367. return -1;
  368. }
  369. if (-1 == getsockname(listener.sock_listener, (struct sockaddr *)&listener.addr_listener, &addrlen)) {
  370. perror("getsockname");
  371. return -1;
  372. }
  373. #ifndef _WIN32
  374. signal( SIGPIPE , SIG_IGN );
  375. #endif
  376. pthread_t thread_listener;
  377. if(0 != pthread_create(&thread_listener , NULL , listen , &listener))
  378. perror("pthread_create");
  379. pthread_join(thread_listener, NULL);
  380. return 0;
  381. }