/** * 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 "Database.h" #include extern LOGGER_CLASS hermes_log; void Database::setDatabaseFile(string p_dbfile) { dbfile=p_dbfile; } /** * * this function executes a query and checks for error * it doesn't return any value, as it is not needed(the queries don't return data) * */ void Database::doQuery(string p_sql) { int retval; bool was_busy=false; do { retval=sqlite3_exec(dbh,p_sql.c_str(),NULL,NULL,NULL); if(SQLITE_OK!=retval&&SQLITE_BUSY!=retval) throw SQLException("SQL: "+p_sql+" sqlite3_errmsg: "+sqlite3_errmsg(dbh),__FILE__,__LINE__); if(SQLITE_BUSY==retval) { sleep(1+rand()%2); LERR("doquery() sql failed with busy state, retrying"); was_busy=true; } } while(SQLITE_BUSY==retval); if(was_busy) LERR("doquery() executed correctly after failing initially"); } string Database::cleanString(string s) { string result=""; for(unsigned int i=0;i31&&s[i]<127) switch(s[i]) { case ' ': case '<': case '>': case '(': case ')': case '[': case ']': case '\\': case ',': case ';': case ':': case '"': case '%': case '\'': break; default: result+=s[i]; } return result; } bool Database::greylisted(string ip,string from,string to,int initial_expiry,int initial_blacklist,int whitelist_expiry) { char **result; int nrow=0; int ncolumn=0; bool retval=true; bool was_busy=false; int sqlite_retval; int now=time(NULL); string strnow=Utils::inttostr(now); string sql="SELECT id,blocked_until FROM greylist WHERE ip=\""+ip+"\" AND emailfrom=\""+from+"\" AND emailto=\""+to+"\" AND "+strnow+" < expires LIMIT 1;"; do { sqlite_retval=sqlite3_get_table(dbh,sql.c_str(),&result,&nrow,&ncolumn,NULL); if(sqlite_retval!=SQLITE_OK&&sqlite_retval!=SQLITE_BUSY) { if(NULL!=result) sqlite3_free_table(result); throw SQLException("SQL: "+sql+" sqlite3_errmsg: "+sqlite3_errmsg(dbh),__FILE__,__LINE__); } if(SQLITE_BUSY==sqlite_retval) { sleep(1+rand()%2); LERR("greylisted() sql failed with busy state, retrying"); was_busy=true; } } while(sqlite_retval==SQLITE_BUSY); if(was_busy) LERR("greylisted() executed correctly after failing initially"); sql=""; if(nrow>0) { string id=result[2]; //we have seen this triplet before if(now0) return true; else return false; } bool Database::whitelistedTO(string p_email) { string sql=""; sql="SELECT email FROM whitelisted_tos WHERE email=\""+p_email+"\" LIMIT 1;"; if(countRows(sql)>0) return true; else return false; } bool Database::whitelistedDomain(string p_domain) { string sql=""; sql="SELECT domain FROM whitelisted_domains WHERE domain=\""+p_domain+"\" LIMIT 1;"; if(countRows(sql)>0) return true; else return false; } bool Database::blacklistedTO(string p_email) { string sql=""; sql="SELECT email FROM blacklisted_tos WHERE email=\""+p_email+"\" LIMIT 1;"; if(countRows(sql)>0) return true; else return false; } bool Database::blacklistedToDomain(string p_domain) { string sql=""; sql="SELECT domain FROM blacklisted_todomains WHERE domain=\""+p_domain+"\" LIMIT 1;"; if(countRows(sql)>0) return true; else return false; } bool Database::blacklistedIP(string p_ip) { string sql=""; sql="SELECT ip FROM blacklisted_ips WHERE ip=\""+p_ip+"\" LIMIT 1;"; if(countRows(sql)>0) return true; else return false; } bool Database::blacklistedFROM(string p_email) { string sql=""; sql="SELECT email FROM blacklisted_froms WHERE email=\""+p_email+"\" LIMIT 1;"; if(countRows(sql)>0) return true; else return false; } bool Database::whitelistedHostname(string p_hostname) { string sql=""; sql="SELECT hostname FROM whitelisted_hostnames WHERE hostname=SUBSTR(\""+p_hostname+"\",-LENGTH(hostname),LENGTH(hostname)) LIMIT 1;"; if(countRows(sql)>0) return true; else return false; } bool Database::allowedDomainPerIP(string p_domain,string p_ip) { string sql="",sql_domain=""; sql="SELECT ip FROM allowed_domains_per_ip WHERE domain=\""+p_domain+"\" AND ip=\""+p_ip+"\" LIMIT 1;"; sql_domain="SELECT ip FROM allowed_domains_per_ip WHERE domain=\""+p_domain+"\" LIMIT 1;"; if(countRows(sql_domain)>0&&0==countRows(sql)) return false; else return true; } /** * this function returns an integer value from a sql * it is useful to calculate things with sql * * i.e.: SELECT SUM(intfield) FROM table * * @param p_sql SQL query to perform * * @return the first value of the first column, rest of data is ignored */ unsigned long Database::getIntValue(string& p_sql) { char **result; int nrow=0; int ncolumn=0; int sqlite_retval; bool was_busy=false; unsigned long value; do { sqlite_retval=sqlite3_get_table(dbh,p_sql.c_str(),&result,&nrow,&ncolumn,NULL); if(SQLITE_OK!=sqlite_retval&&SQLITE_BUSY!=sqlite_retval) { if(NULL!=result) sqlite3_free_table(result); throw SQLException("SQL: "+p_sql+" sqlite3_errmsg: "+sqlite3_errmsg(dbh),__FILE__,__LINE__); } if(SQLITE_BUSY==sqlite_retval) { sleep(1+rand()%2); LERR("getIntValue() sql failed with busy state, retrying"); was_busy=true; } } while(SQLITE_BUSY==sqlite_retval); if(was_busy) LERR("getIntValue() executed correctly after failing initially"); if(NULL==result) throw SQLException("SQL: "+p_sql+" didn't return any data, SQL query may be wrong",__FILE__,__LINE__); if('\0'==result[ncolumn][0]) value=0; //why sqlite doesn't return 0 when there are no rows? else value=strtoul(result[ncolumn],NULL,10); sqlite3_free_table(result); return value; } /** * clean the spam database and return the number of spam messages * * @return number of spam messages deleted */ unsigned long Database::cleanDB() { unsigned long spamcount=0; //shut compiler up string sql; try { //block database until we have finished cleaning it doQuery("BEGIN EXCLUSIVE TRANSACTION"); //now count how many blocked emails we have to submit to stats //we do it always because if we don't submit stats it stills appears on the logs sql="SELECT SUM(blocked) FROM greylist WHERE expires