/** * hermes antispam proxy * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * @author Juan José Gutiérrez de Quevedo */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_SSL #include #endif //HAVE_SSL #ifndef WIN32 #include #endif //WIN32 #include "Proxy.h" #include "Socket.h" #include "ServerSocket.h" #include "Configfile.h" #include "Utils.h" #include "Logger.h" using namespace std; void *thread_main(void *); void *cleaner_thread_run(void *); void exit_requested(int); //global var to know when we have to exit bool quit=false; //mutexes pthread_mutex_t childrenlist_mutex=PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t info_stack_mutex=PTHREAD_MUTEX_INITIALIZER; //our config Configfile cfg; //our logger LOGGER_CLASS hermes_log; //this variable is thread-local to allow having a unique id per-thread which we can //print at the start of log messages __thread unsigned long connection_id; list children; #if defined(HAVE_SSL) && OPENSSL_VERSION_NUMBER < 0x10100000L pthread_mutex_t ssl_locks[CRYPTO_NUM_LOCKS]={PTHREAD_MUTEX_INITIALIZER}; void ssl_locking_function(int mode,int n,const char *file,int line) { if(n>CRYPTO_NUM_LOCKS) throw Exception(_("Error, "+Utils::inttostr(n)+" is bigger than CRYPTO_NUM_LOCKS()("+Utils::inttostr(CRYPTO_NUM_LOCKS)+")"),__FILE__,__LINE__); if(mode&CRYPTO_LOCK) pthread_mutex_lock(&ssl_locks[n]); else pthread_mutex_unlock(&ssl_locks[n]); } #endif //HAVE_SSL int #ifdef WIN32_SERVICE hermes_main #else main #endif //WIN32_SERVICE (int argc,char *argv[]) { /* TODO:think of this again if(argc>2) { for(unsigned i=1;i0) exit(0); //succesful fork if(retval<0) { LERR(_("Error forking into the background") + Utils::errnotostrerror(errno)); return -1; } } if(cfg.getPidFile()!="") { try { Utils::write_pid(cfg.getPidFile(),getpid()); } catch(Exception &e) { LERR(e); } } if(cfg.getChroot()!="") { //this is needed to get hermes to load the dns resolver BEFORE chrooting (void)gethostbyname("hermes-project.com"); if(-1 == chdir(cfg.getChroot().c_str())) { LERR(_("Couldn't chdir into ") + cfg.getChroot() + " " + Utils::errnotostrerror(errno) ); return -1; } if(-1==chroot(cfg.getChroot().c_str())) { LERR(_("Couldn't chroot ") + Utils::errnotostrerror(errno)); return -1; } if(-1 == chdir("/")) { LERR(_("Couldn't chdir into /, this shouldn't happen: " + Utils::errnotostrerror(errno)) ); return -1; } } #endif //WIN32 LINF("Starting hermes with pid "+Utils::inttostr(getpid())); try { server.init(); server.setPort(cfg.getListeningPort()); server.listen(cfg.getListeningPort(),cfg.getBindTo()); } catch(Exception &e) { LERR(e); return -1; //couldn't bind, exit } #ifndef WIN32 if(cfg.getDropPrivileges()) { //drop privileges once we have opened the listening port if(-1 == setgroups(0,NULL)) { LERR(_("Error dropping priviledges " + Utils::errnotostrerror(errno)) ); return -1; } if(-1 == setgid(cfg.getGid())) { LERR(_("Error setting gid " + Utils::inttostr(cfg.getGid()) + " " + Utils::errnotostrerror(errno)) ); return -1; } if(-1 == setuid(cfg.getUid())) { LERR(_("Error setting uid " + Utils::inttostr(cfg.getUid()) + " " + Utils::errnotostrerror(errno)) ); return -1; } if(-1 == setuid(cfg.getUid())) { LERR(_("Error setting uid " + Utils::inttostr(cfg.getUid()) + " " + Utils::errnotostrerror(errno)) ); return -1; } } #endif //WIN32 /* start our cleaner thread */ if(cfg.getCleanDb()) pthread_create(&cleaner_thread,NULL,cleaner_thread_run,NULL); new_conn_info info; stack info_stack; while(!quit) { if(server.canRead(1)) //wait one second for incoming connections, if none then loop again(allows us to check for SIGTERM and SIGINT) { pthread_t thread; pthread_attr_t thread_attr; pthread_attr_init(&thread_attr); pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); int retval; int fd=server.accept(&peer_address); info.new_fd=fd; info.peer_address=peer_address; info.connection_id=++nconns; pthread_mutex_lock(&info_stack_mutex); info_stack.push(info); pthread_mutex_unlock(&info_stack_mutex); retval=pthread_create(&thread,&thread_attr,thread_main,(void *)&info_stack); if(retval) { LERR(_("Error creating thread: ") + Utils::errnotostrerror(retval) + _(". Sleeping 5 seconds before continuing...")); sleep(5); } else { #ifdef WIN32 LDEB("New thread created [" + Utils::ulongtostr(nconns) + "] thread_id: " + Utils::ulongtostr((unsigned long)thread.p) + ":" + Utils::ulongtostr(thread.x)); #else LDEB("New thread created [" + Utils::ulongtostr(nconns) + "] thread_id: " + Utils::ulongtostr(thread)); #endif //WIN32 pthread_mutex_lock(&childrenlist_mutex); children.push_back(nconns); pthread_mutex_unlock(&childrenlist_mutex); } } } //close connection so that the port is no longer usable server.close(); // wait for all threads to finish LINF("Waiting for threads to finish"); #ifndef WIN32 while(children.size()) { if(false==cfg.getBackground()) { cout << "Threads active:" << children.size() << (char)13; fflush(stdout); } sleep(1); } #endif //WIN32 if(cfg.getCleanDb()) pthread_join(cleaner_thread,NULL); #ifndef WIN32 if(false==cfg.getBackground()) cout << endl; #endif //WIN32 #ifdef HAVE_SPF Spf::deinitialize(); #endif //HAVE_SPF return 0; } /** * this threads cleans the database once each hour, deleting * the records on the database that have an expire time < now * */ void *cleaner_thread_run(void *) { try { Database db; time_t next_run=time(NULL)+3600; db.setDatabaseFile(cfg.getDatabaseFile()); db.open(); while(!quit) { time_t now=time(NULL); sched_yield(); if(now>next_run) { unsigned long spamcount=0; next_run=now+3600; //if we just add 3600 like before, then if //time changes during execution of hermes this will run //every few seconds instead of every hour try { spamcount=db.cleanDB(); LDEB("Cleaning database, cleaning "+Utils::inttostr(spamcount)+" blocked spams."); } catch(Exception &e) { LERR("Error cleaning the database: " + string(e)); } if(spamcount>0&&cfg.getSubmitStats()) { try { Socket s; string server_response; s.init(); s.connect("stats.hermes-project.com",11125); #ifdef HAVE_SSL if(cfg.getSubmitStatsSsl()) { s.writeLine("ssl"); s.prepareSSL(false); s.startSSL(false); } else #endif //HAVE_SSL s.writeLine("non-ssl"); s.writeLine(cfg.getSubmitStatsUsername()); s.writeLine(cfg.getSubmitStatsPassword()); s.writeLine(Utils::inttostr(spamcount)); server_response=s.readLine(); s.close(); if("OK"!=server_response) throw Exception(server_response,__FILE__,__LINE__); } catch(Exception &e) { LDEB("Exception sending stats: "+string(e)); } } } #ifndef WIN32 if(false==cfg.getBackground()) { if(!(now%10)) //echo info each 10 seconds { stringstream ss; pthread_mutex_lock(&childrenlist_mutex); ss << children.size() << " threads running: "; for(list::iterator i=children.begin();i!=children.end();i++) ss << "[ " << *i << " ] "; pthread_mutex_unlock(&childrenlist_mutex); ss << endl; cout << ss.str(); } } #endif //WIN32 sleep(1); } db.close(); } catch(Exception &e) { LERR(e); } return NULL; } void remove_child_from_childlist(unsigned long child_id) { pthread_mutex_lock(&childrenlist_mutex); children.remove(child_id); pthread_mutex_unlock(&childrenlist_mutex); } void *thread_main(void *info_stack) { try { Socket client; //for the input connection from the client Proxy p; new_conn_info peerinfo; //read a new peerinfo from the stack pthread_mutex_lock(&info_stack_mutex); peerinfo=((stack*)info_stack)->top(); ((stack*)info_stack)->pop(); pthread_mutex_unlock(&info_stack_mutex); connection_id=peerinfo.connection_id; client.setFD(peerinfo.new_fd); p.setOutside(client); p.run(peerinfo.peer_address); remove_child_from_childlist(connection_id); } catch(Exception &e) { LDEB(e); } return NULL; } void exit_requested(int) { if(!quit) { quit=true; #ifndef WIN32 if(false==cfg.getBackground()) cout << "Hit control+c again to force-quit" << endl; #endif //WIN32 } else exit(-1); } #ifdef WIN32 //pthreads on win32 doesn't provide an operator== for pthread_t //and it's also an struct, not an int, so supply one operator== here bool operator==(pthread_t t1,pthread_t t2) { return t1.p==t2.p&&t1.x==t2.x; } #endif //WIN32