hermes.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /**
  2. * hermes antispam proxy
  3. * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; version 2 of the License
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License along
  15. * with this program; if not, write to the Free Software Foundation, Inc.,
  16. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  17. *
  18. * @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
  19. */
  20. #include "config.h"
  21. #include <iostream>
  22. #include <list>
  23. #include <stack>
  24. #include <pthread.h>
  25. #include <signal.h>
  26. #include <time.h>
  27. #ifdef HAVE_SSL
  28. #include <openssl/crypto.h>
  29. #endif //HAVE_SSL
  30. #ifndef WIN32
  31. #include <grp.h>
  32. #endif //WIN32
  33. #include "Proxy.h"
  34. #include "Socket.h"
  35. #include "ServerSocket.h"
  36. #include "Configfile.h"
  37. #include "Utils.h"
  38. #include "Logger.h"
  39. using namespace std;
  40. void *thread_main(void *);
  41. void *cleaner_thread_run(void *);
  42. void exit_requested(int);
  43. //global var to know when we have to exit
  44. bool quit=false;
  45. //mutexes
  46. pthread_mutex_t childrenlist_mutex=PTHREAD_MUTEX_INITIALIZER;
  47. pthread_mutex_t info_stack_mutex=PTHREAD_MUTEX_INITIALIZER;
  48. //our config
  49. Configfile cfg;
  50. //our logger
  51. LOGGER_CLASS hermes_log;
  52. //this variable is thread-local to allow having a unique id per-thread which we can
  53. //print at the start of log messages
  54. __thread unsigned long connection_id;
  55. list<unsigned long> children;
  56. #if defined(HAVE_SSL) && OPENSSL_VERSION_NUMBER < 0x10100000L
  57. pthread_mutex_t ssl_locks[CRYPTO_NUM_LOCKS]={PTHREAD_MUTEX_INITIALIZER};
  58. void ssl_locking_function(int mode,int n,const char *file,int line)
  59. {
  60. if(n>CRYPTO_NUM_LOCKS)
  61. throw Exception(_("Error, "+Utils::inttostr(n)+" is bigger than CRYPTO_NUM_LOCKS()("+Utils::inttostr(CRYPTO_NUM_LOCKS)+")"),__FILE__,__LINE__);
  62. if(mode&CRYPTO_LOCK)
  63. pthread_mutex_lock(&ssl_locks[n]);
  64. else
  65. pthread_mutex_unlock(&ssl_locks[n]);
  66. }
  67. #endif //HAVE_SSL
  68. int
  69. #ifdef WIN32_SERVICE
  70. hermes_main
  71. #else
  72. main
  73. #endif //WIN32_SERVICE
  74. (int argc,char *argv[])
  75. {
  76. /* TODO:think of this again
  77. if(argc>2)
  78. {
  79. for(unsigned i=1;i<argc;i++)
  80. {
  81. argv++
  82. }
  83. */
  84. #if defined(HAVE_SSL) && OPENSSL_VERSION_NUMBER < 0x10100000L
  85. CRYPTO_set_locking_callback(ssl_locking_function);
  86. #ifndef WIN32 //getpid() returns different values for threads on windows, therefor this is not needed
  87. CRYPTO_set_id_callback(pthread_self);
  88. #endif //WIN32
  89. #endif //HAVE_SSL
  90. try
  91. {
  92. if(2==argc)
  93. {
  94. if(!Utils::file_exists(argv[1]))
  95. throw Exception(string(_("Config file "))+argv[1]+_(" doesn't exist or is not readable."),__FILE__,__LINE__);
  96. cfg.parse(argv[1]);
  97. }
  98. else
  99. throw Exception(_("Config file not specified"), __FILE__, __LINE__);
  100. cfg.validateConfig();
  101. }
  102. catch(Exception &e)
  103. {
  104. LERR(e);
  105. return -1;
  106. }
  107. unsigned long nconns=0;
  108. signal(SIGTERM,exit_requested);
  109. signal(SIGINT,exit_requested);
  110. #ifndef WIN32
  111. signal(SIGCHLD,SIG_IGN);
  112. signal(SIGPIPE,SIG_IGN);
  113. #endif //WIN32
  114. //we have to create the server socket BEFORE chrooting, because if we don't,
  115. //SSL cannot initialize because it's missing libz
  116. ServerSocket server;
  117. pthread_t cleaner_thread;
  118. string peer_address;
  119. #ifndef WIN32
  120. if(cfg.getBackground())
  121. {
  122. int retval;
  123. retval=fork();
  124. if(retval>0)
  125. exit(0); //succesful fork
  126. if(retval<0)
  127. {
  128. LERR(_("Error forking into the background") + Utils::errnotostrerror(errno));
  129. return -1;
  130. }
  131. }
  132. if(cfg.getPidFile()!="")
  133. {
  134. try
  135. {
  136. Utils::write_pid(cfg.getPidFile(),getpid());
  137. }
  138. catch(Exception &e)
  139. {
  140. LERR(e);
  141. }
  142. }
  143. if(cfg.getChroot()!="")
  144. {
  145. //this is needed to get hermes to load the dns resolver BEFORE chrooting
  146. (void)gethostbyname("hermes-project.com");
  147. if(-1 == chdir(cfg.getChroot().c_str()))
  148. {
  149. LERR(_("Couldn't chdir into ") + cfg.getChroot() + " " + Utils::errnotostrerror(errno) );
  150. return -1;
  151. }
  152. if(-1==chroot(cfg.getChroot().c_str()))
  153. {
  154. LERR(_("Couldn't chroot ") + Utils::errnotostrerror(errno));
  155. return -1;
  156. }
  157. if(-1 == chdir("/"))
  158. {
  159. LERR(_("Couldn't chdir into /, this shouldn't happen: " + Utils::errnotostrerror(errno)) );
  160. return -1;
  161. }
  162. }
  163. #endif //WIN32
  164. LINF("Starting hermes with pid "+Utils::inttostr(getpid()));
  165. try
  166. {
  167. server.init();
  168. server.setPort(cfg.getListeningPort());
  169. server.listen(cfg.getListeningPort(),cfg.getBindTo());
  170. }
  171. catch(Exception &e)
  172. {
  173. LERR(e);
  174. return -1; //couldn't bind, exit
  175. }
  176. #ifndef WIN32
  177. if(cfg.getDropPrivileges())
  178. {
  179. //drop privileges once we have opened the listening port
  180. if(-1 == setgroups(0,NULL))
  181. {
  182. LERR(_("Error dropping priviledges " + Utils::errnotostrerror(errno)) );
  183. return -1;
  184. }
  185. if(-1 == setgid(cfg.getGid()))
  186. {
  187. LERR(_("Error setting gid " + Utils::inttostr(cfg.getGid()) + " " + Utils::errnotostrerror(errno)) );
  188. return -1;
  189. }
  190. if(-1 == setuid(cfg.getUid()))
  191. {
  192. LERR(_("Error setting uid " + Utils::inttostr(cfg.getUid()) + " " + Utils::errnotostrerror(errno)) );
  193. return -1;
  194. }
  195. if(-1 == setuid(cfg.getUid()))
  196. {
  197. LERR(_("Error setting uid " + Utils::inttostr(cfg.getUid()) + " " + Utils::errnotostrerror(errno)) );
  198. return -1;
  199. }
  200. }
  201. #endif //WIN32
  202. /* start our cleaner thread */
  203. if(cfg.getCleanDb())
  204. pthread_create(&cleaner_thread,NULL,cleaner_thread_run,NULL);
  205. new_conn_info info;
  206. stack<new_conn_info> info_stack;
  207. while(!quit)
  208. {
  209. if(server.canRead(1)) //wait one second for incoming connections, if none then loop again(allows us to check for SIGTERM and SIGINT)
  210. {
  211. pthread_t thread;
  212. pthread_attr_t thread_attr;
  213. pthread_attr_init(&thread_attr);
  214. pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
  215. int retval;
  216. int fd=server.accept(&peer_address);
  217. info.new_fd=fd;
  218. info.peer_address=peer_address;
  219. info.connection_id=++nconns;
  220. pthread_mutex_lock(&info_stack_mutex);
  221. info_stack.push(info);
  222. pthread_mutex_unlock(&info_stack_mutex);
  223. retval=pthread_create(&thread,&thread_attr,thread_main,(void *)&info_stack);
  224. if(retval)
  225. {
  226. LERR(_("Error creating thread: ") + Utils::errnotostrerror(retval) + _(". Sleeping 5 seconds before continuing..."));
  227. sleep(5);
  228. }
  229. else
  230. {
  231. #ifdef WIN32
  232. LDEB("New thread created [" + Utils::ulongtostr(nconns) + "] thread_id: " + Utils::ulongtostr((unsigned long)thread.p) + ":" + Utils::ulongtostr(thread.x));
  233. #else
  234. LDEB("New thread created [" + Utils::ulongtostr(nconns) + "] thread_id: " + Utils::ulongtostr(thread));
  235. #endif //WIN32
  236. pthread_mutex_lock(&childrenlist_mutex);
  237. children.push_back(nconns);
  238. pthread_mutex_unlock(&childrenlist_mutex);
  239. }
  240. }
  241. }
  242. //close connection so that the port is no longer usable
  243. server.close();
  244. // wait for all threads to finish
  245. LINF("Waiting for threads to finish");
  246. #ifndef WIN32
  247. while(children.size())
  248. {
  249. if(false==cfg.getBackground())
  250. {
  251. cout << "Threads active:" << children.size() << (char)13;
  252. fflush(stdout);
  253. }
  254. sleep(1);
  255. }
  256. #endif //WIN32
  257. if(cfg.getCleanDb())
  258. pthread_join(cleaner_thread,NULL);
  259. #ifndef WIN32
  260. if(false==cfg.getBackground())
  261. cout << endl;
  262. #endif //WIN32
  263. #ifdef HAVE_SPF
  264. Spf::deinitialize();
  265. #endif //HAVE_SPF
  266. return 0;
  267. }
  268. /**
  269. * this threads cleans the database once each hour, deleting
  270. * the records on the database that have an expire time < now
  271. *
  272. */
  273. void *cleaner_thread_run(void *)
  274. {
  275. try
  276. {
  277. Database db;
  278. time_t next_run=time(NULL)+3600;
  279. db.setDatabaseFile(cfg.getDatabaseFile());
  280. db.open();
  281. while(!quit)
  282. {
  283. time_t now=time(NULL);
  284. sched_yield();
  285. if(now>next_run)
  286. {
  287. unsigned long spamcount=0;
  288. next_run=now+3600; //if we just add 3600 like before, then if
  289. //time changes during execution of hermes this will run
  290. //every few seconds instead of every hour
  291. try
  292. {
  293. spamcount=db.cleanDB();
  294. LDEB("Cleaning database, cleaning "+Utils::inttostr(spamcount)+" blocked spams.");
  295. }
  296. catch(Exception &e)
  297. {
  298. LERR("Error cleaning the database: " + string(e));
  299. }
  300. if(spamcount>0&&cfg.getSubmitStats())
  301. {
  302. try
  303. {
  304. Socket s;
  305. string server_response;
  306. s.init();
  307. s.connect("stats.hermes-project.com",11125);
  308. #ifdef HAVE_SSL
  309. if(cfg.getSubmitStatsSsl())
  310. {
  311. s.writeLine("ssl");
  312. s.prepareSSL(false);
  313. s.startSSL(false);
  314. }
  315. else
  316. #endif //HAVE_SSL
  317. s.writeLine("non-ssl");
  318. s.writeLine(cfg.getSubmitStatsUsername());
  319. s.writeLine(cfg.getSubmitStatsPassword());
  320. s.writeLine(Utils::inttostr(spamcount));
  321. server_response=s.readLine();
  322. s.close();
  323. if("OK"!=server_response)
  324. throw Exception(server_response,__FILE__,__LINE__);
  325. }
  326. catch(Exception &e)
  327. {
  328. LDEB("Exception sending stats: "+string(e));
  329. }
  330. }
  331. }
  332. #ifndef WIN32
  333. if(false==cfg.getBackground())
  334. {
  335. if(!(now%10)) //echo info each 10 seconds
  336. {
  337. stringstream ss;
  338. pthread_mutex_lock(&childrenlist_mutex);
  339. ss << children.size() << " threads running: ";
  340. for(list<unsigned long>::iterator i=children.begin();i!=children.end();i++)
  341. ss << "[ " << *i << " ] ";
  342. pthread_mutex_unlock(&childrenlist_mutex);
  343. ss << endl;
  344. cout << ss.str();
  345. }
  346. }
  347. #endif //WIN32
  348. sleep(1);
  349. }
  350. db.close();
  351. }
  352. catch(Exception &e)
  353. {
  354. LERR(e);
  355. }
  356. return NULL;
  357. }
  358. void remove_child_from_childlist(unsigned long child_id)
  359. {
  360. pthread_mutex_lock(&childrenlist_mutex);
  361. children.remove(child_id);
  362. pthread_mutex_unlock(&childrenlist_mutex);
  363. }
  364. void *thread_main(void *info_stack)
  365. {
  366. try
  367. {
  368. Socket client; //for the input connection from the client
  369. Proxy p;
  370. new_conn_info peerinfo;
  371. //read a new peerinfo from the stack
  372. pthread_mutex_lock(&info_stack_mutex);
  373. peerinfo=((stack<new_conn_info>*)info_stack)->top();
  374. ((stack<new_conn_info>*)info_stack)->pop();
  375. pthread_mutex_unlock(&info_stack_mutex);
  376. connection_id=peerinfo.connection_id;
  377. client.setFD(peerinfo.new_fd);
  378. p.setOutside(client);
  379. p.run(peerinfo.peer_address);
  380. remove_child_from_childlist(connection_id);
  381. }
  382. catch(Exception &e)
  383. {
  384. LDEB(e);
  385. }
  386. return NULL;
  387. }
  388. void exit_requested(int)
  389. {
  390. if(!quit)
  391. {
  392. quit=true;
  393. #ifndef WIN32
  394. if(false==cfg.getBackground())
  395. cout << "Hit control+c again to force-quit" << endl;
  396. #endif //WIN32
  397. }
  398. else
  399. exit(-1);
  400. }
  401. #ifdef WIN32
  402. //pthreads on win32 doesn't provide an operator== for pthread_t
  403. //and it's also an struct, not an int, so supply one operator== here
  404. bool operator==(pthread_t t1,pthread_t t2)
  405. {
  406. return t1.p==t2.p&&t1.x==t2.x;
  407. }
  408. #endif //WIN32