Download | Plain Text | No Line Numbers


  1. /*
  2.  * Copyright (C) 2006 Manuel Mausz (manuel@mausz.at)
  3.  * Origin code copyright (c) mjd@digitaleveryware.com 2003
  4.  * (http://www.digitaleveryware.com/projects/greylisting/)
  5.  *
  6.  * This program is free software; you can redistribute it and/or
  7.  * modify it under the terms of the GNU General Public License
  8.  * as published by the Free Software Foundation; either
  9.  * version 2 of the License, or (at your option) any later
  10.  * version.
  11.  *
  12.  * This program is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15.  * GNU General Public License for more details.
  16.  *
  17.  * You should have received a copy of the GNU General Public License
  18.  * along with this program; if not, write to the Free Software Foundation,
  19.  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20.  */
  21.  
  22. #include <stdio.h>
  23. #include <stdlib.h>
  24. #include <string.h>
  25. #include <stdarg.h>
  26. #include <errno.h>
  27. #include <mysql.h>
  28.  
  29. #define SQLCMDSIZE 2048
  30. #define RET_NOTFOUND 0
  31. #define RET_ACCEPT 1
  32. #define RET_REJECT 2
  33. #define RET_TEMPREJECT 3
  34. #define CMD_TEMPREJECT "E451 temporary failure (#4.3.0)\n"
  35. #define CMD_REJECT "E553 sorry, your envelope sender has been denied (#5.7.1)\n"
  36. #define LOGLEVEL_FATAL 1
  37. #define LOGLEVEL_ERROR 2
  38. #define LOGLEVEL_WARN 3
  39. #define LOGLEVEL_INFO 4
  40. #define LOGLEVEL_DEBUG 5
  41. #define MAXCONFIGLINESIZE 1024 // maybe change this to dynamic allocation sometime
  42. #if MYSQL_VERSION_ID >= 50003
  43. # define QUERYSIZE 500
  44. #else
  45. # define QUERYSIZE 700
  46. #endif
  47.  
  48. static char *configfile = "control/greylisting";
  49. static char *mysql_host = NULL;
  50. static char *mysql_user = NULL;
  51. static char *mysql_pass = NULL;
  52. static char *mysql_db = NULL;
  53. unsigned int mysql_port = 3306;
  54. unsigned int block_expire = 55;
  55. unsigned int record_expire = 500;
  56. unsigned int record_expire_good = 36;
  57. static char *relay_ip;
  58. static int loglevel = LOGLEVEL_WARN;
  59.  
  60. void gllog(unsigned int level, char* format, ...)
  61. {
  62. va_list args;
  63. if (level > loglevel)
  64. return;
  65. va_start(args, format);
  66. vfprintf(stderr, format, args);
  67. va_end(args);
  68. }
  69.  
  70. int load_config(void)
  71. {
  72. char *tmp;
  73. FILE *config;
  74. char buf[MAXCONFIGLINESIZE];
  75. int i;
  76.  
  77. /* first check for logging var */
  78. tmp = getenv("GLLOGLEVEL");
  79. if (tmp)
  80. loglevel = atoi(tmp);
  81.  
  82. /* check if greylisting is enabled */
  83. if (!getenv("GREYLISTING") || getenv("RELAYCLIENT"))
  84. {
  85. gllog(LOGLEVEL_DEBUG, "greylisting: greylisting is not activated\n");
  86. return 0;
  87. }
  88.  
  89. /* basic environment variables needed */
  90. relay_ip = getenv("TCPREMOTEIP");
  91. if (!relay_ip)
  92. {
  93. gllog(LOGLEVEL_FATAL, "greylisting: one of the following envvars is not set: TCPREMOTEIP\n");
  94. return 0;
  95. }
  96.  
  97. /* avoid buffer overflows (max. query is ~410 chars long) */
  98. if (strlen(relay_ip) > SQLCMDSIZE - QUERYSIZE)
  99. {
  100. gllog(LOGLEVEL_FATAL, "greylisting: buffer overflow protection occurs\n");
  101. return 0;
  102. }
  103.  
  104. /* fetch config file path */
  105. tmp = getenv("GLCONFIGFILE");
  106. if (tmp)
  107. configfile = tmp;
  108.  
  109. /* fetch config file content */
  110. gllog(LOGLEVEL_DEBUG, "greylisting: configfile=%s\n", configfile);
  111. config = fopen(configfile, "r");
  112. if (!config)
  113. gllog(LOGLEVEL_DEBUG, "greylisting: configfile error: %s\n", strerror(errno));
  114. else
  115. {
  116. while((tmp = fgets(buf, sizeof(buf), config)))
  117. {
  118. if (buf[0] == '#' || buf[0] == ';')
  119. continue;
  120. for(i = 0; i < strlen(buf) && buf[i] != '\r' && buf[i] != '\n'; i++);
  121. buf[i] = 0;
  122. if (strstr(tmp, "mysql_host=") == tmp)
  123. {
  124. free(mysql_host);
  125. mysql_host = strdup(tmp + strlen("mysql_host="));
  126. }
  127. else if (strstr(tmp, "mysql_port=") == tmp)
  128. mysql_port = atoi(tmp + strlen("mysql_port="));
  129. else if (strstr(tmp, "mysql_user=") == tmp)
  130. {
  131. free(mysql_user);
  132. mysql_user = strdup(tmp + strlen("mysql_user="));
  133. }
  134. else if (strstr(tmp, "mysql_pass=") == tmp)
  135. {
  136. free(mysql_pass);
  137. mysql_pass = strdup(tmp + strlen("mysql_pass="));
  138. }
  139. else if (strstr(tmp, "mysql_db=") == tmp)
  140. {
  141. free(mysql_db);
  142. mysql_db = strdup(tmp + strlen("mysql_db="));
  143. }
  144. else if (strstr(tmp, "block_expire=") == tmp)
  145. block_expire = atoi(tmp + strlen("block_expire="));
  146. else if (strstr(tmp, "record_expire=") == tmp)
  147. record_expire = atoi(tmp + strlen("record_expire="));
  148. else if (strstr(tmp, "record_expire_good=") == tmp)
  149. record_expire_good = atoi(tmp + strlen("record_expire_good="));
  150. else if (strstr(tmp, "loglevel=") == tmp && !getenv("GLLOGLEVEL"))
  151. loglevel = atoi(tmp + strlen("loglevel="));
  152. }
  153. fclose(config);
  154. }
  155.  
  156. /* environment variables */
  157. tmp = getenv("GLMYSQLHOST");
  158. if (tmp)
  159. {
  160. free(mysql_host);
  161. mysql_host = strdup(tmp);
  162. }
  163.  
  164. tmp = getenv("GLMYSQLPORT");
  165. if (tmp)
  166. mysql_port = atoi(tmp);
  167.  
  168. tmp = getenv("GLMYSQLUSER");
  169. if (tmp)
  170. {
  171. free(mysql_user);
  172. mysql_user = strdup(tmp);
  173. }
  174.  
  175. tmp = getenv("GLMYSQLPASS");
  176. if (tmp)
  177. {
  178. free(mysql_pass);
  179. mysql_pass = strdup(tmp);
  180. }
  181.  
  182. tmp = getenv("GLMYSQLDB");
  183. if (tmp)
  184. {
  185. free(mysql_db);
  186. mysql_db = strdup(tmp);
  187. }
  188.  
  189. tmp = getenv("GLBLOCKEXPIRE");
  190. if (tmp)
  191. block_expire = atoi(tmp);
  192.  
  193. tmp = getenv("GLRECORDEXPIRE");
  194. if (tmp)
  195. record_expire = atoi(tmp);
  196.  
  197. tmp = getenv("GLRECORDEXPIREGOOD");
  198. if (tmp)
  199. record_expire_good = atoi(tmp);
  200.  
  201. /* logging */
  202. gllog(LOGLEVEL_DEBUG, "greylisting: mysql: host=%s, port=%d, user=%s, pass=******\n", mysql_host, mysql_port, mysql_user);
  203. gllog(LOGLEVEL_DEBUG, "greylisting: block_expire=%d, record_expire=%d, record_expire_good=%d\n", block_expire, record_expire, record_expire_good);
  204. return 1;
  205. }
  206.  
  207. void cleanup()
  208. {
  209. free(mysql_host);
  210. free(mysql_user);
  211. free(mysql_pass);
  212. }
  213.  
  214. int mysql_query_wrapper(MYSQL *mysql, char *query)
  215. {
  216. int result;
  217.  
  218. result = mysql_query(mysql, query);
  219. gllog(LOGLEVEL_DEBUG, "greylisting: mysql: %s - ret=%d\n", query, result);
  220. return result;
  221. }
  222.  
  223. /* check if relay_ip is white-/blacklisted */
  224. int check_listed(MYSQL *mysql)
  225. {
  226. MYSQL_RES *res;
  227. MYSQL_ROW row;
  228. char query[SQLCMDSIZE];
  229. int found = RET_NOTFOUND;
  230.  
  231. sprintf(query, "SET @uipaddr = inet_aton('%s'); ", relay_ip);
  232. if (mysql_query_wrapper(mysql, query))
  233. {
  234. gllog(LOGLEVEL_ERROR, "greylisting: mysql: %s\n", mysql_error(mysql));
  235. return 0;
  236. }
  237.  
  238. #if MYSQL_VERSION_ID >= 50003
  239. sprintf(query,
  240. "SELECT `id`, `block_expires` >= UTC_TIMESTAMP(), `block_expires` < UTC_TIMESTAMP() "
  241. "FROM `greylisting_lists` "
  242. "WHERE `record_expires` > UTC_TIMESTAMP() "
  243. "AND `ipaddr_start` <= @uipaddr "
  244. "AND @uipaddr <= `ipaddr_end` "
  245. "ORDER BY (`ipaddr_end` - `ipaddr_start`) ASC "
  246. "LIMIT 1");
  247. #else
  248. sprintf(query,
  249. "SELECT `id`, `block_expires` >= UTC_TIMESTAMP(), `block_expires` < UTC_TIMESTAMP() "
  250. "FROM `greylisting_lists` "
  251. "WHERE `record_expires` > UTC_TIMESTAMP() "
  252. "AND (@base := IF(INSTR(`ipaddr`, '.'), 32, 128)) "
  253. "AND IF( "
  254. "INSTR(`ipaddr`, '/'), "
  255. "(@ipaddr_start := inet_aton(substring_index(`ipaddr`, '/', 1))) "
  256. "AND (@ipaddr_count := POW(2, @base - substring_index(`ipaddr`, '/', -1))) "
  257. "AND (@ipaddr_end := @ipaddr_start + @ipaddr_count - 1), "
  258. "(@ipaddr_start := inet_aton(`ipaddr`)) "
  259. "AND (@ipaddr_end := @ipaddr_start) "
  260. ") "
  261. "AND @ipaddr_start <= @uipaddr "
  262. "AND @uipaddr <= @ipaddr_end "
  263. "ORDER BY (@ipaddr_end - @ipaddr_start) ASC "
  264. "LIMIT 1");
  265. #endif
  266.  
  267. if (mysql_query_wrapper(mysql, query) ||
  268. !(res = mysql_store_result(mysql)))
  269. {
  270. gllog(LOGLEVEL_ERROR, "greylisting: mysql: %s\n", mysql_error(mysql));
  271. return 0;
  272. }
  273.  
  274. if ((row = mysql_fetch_row(res)))
  275. {
  276. if (atoi(row[1]))
  277. {
  278. found = RET_REJECT;
  279. gllog(LOGLEVEL_INFO, "greylisting: %s is blacklisted (id=%s) - rejecting\n", relay_ip, row[0]);
  280. }
  281. else if (atoi(row[2]))
  282. {
  283. found = RET_ACCEPT;
  284. gllog(LOGLEVEL_INFO, "greylisting: %s is whitelisted (id=%s) - accepting\n", relay_ip, row[0]);
  285. }
  286. }
  287.  
  288. mysql_free_result(res);
  289. return found;
  290. }
  291.  
  292. int check_greylisted(MYSQL *mysql)
  293. {
  294. MYSQL_RES *res;
  295. MYSQL_ROW row;
  296. char query[SQLCMDSIZE];
  297. char *relay_ip_sub = NULL;
  298. char *ptr;
  299. char ipdelimeter;
  300. int ret = RET_NOTFOUND;
  301.  
  302. /*
  303.   * 0 ... query matches anything in the same /24 subnet
  304.   * 1 ... query does an exact ip match
  305.   */
  306. if (0)
  307. {
  308. sprintf(query,
  309. "SELECT `id`, `block_expires` < UTC_TIMESTAMP() "
  310. "FROM `greylisting_data` "
  311. "WHERE `record_expires` > UTC_TIMESTAMP() "
  312. "AND `relay_ip` = '%s' "
  313. "LIMIT 1",
  314. relay_ip);
  315. }
  316. else
  317. {
  318. /* strip off the last octet */
  319. ipdelimeter = '.';
  320. if (!strchr(relay_ip, '.'))
  321. ipdelimeter = ':';
  322. relay_ip_sub = strdup(relay_ip);
  323. ptr = strrchr(relay_ip_sub, ipdelimeter);
  324. if (ptr)
  325. *ptr = '\0';
  326.  
  327. sprintf(query,
  328. "SELECT `id`, `block_expires` < UTC_TIMESTAMP() "
  329. "FROM `greylisting_data` "
  330. "WHERE `record_expires` > UTC_TIMESTAMP() "
  331. "AND `relay_ip` LIKE '%s%c%%' "
  332. "LIMIT 1",
  333. relay_ip_sub,
  334. ipdelimeter);
  335. free(relay_ip_sub);
  336. }
  337.  
  338. if (mysql_query_wrapper(mysql, query) ||
  339. !(res = mysql_store_result(mysql)))
  340. {
  341. gllog(LOGLEVEL_ERROR, "greylisting: mysql: %s\n", mysql_error(mysql));
  342. return RET_NOTFOUND;
  343. }
  344.  
  345. if ((row = mysql_fetch_row(res)))
  346. {
  347. if (atoi(row[1]))
  348. {
  349. sprintf(query,
  350. "UPDATE `greylisting_data` "
  351. "SET `record_expires` = UTC_TIMESTAMP() + INTERVAL %u DAY, `passed_count` = `passed_count` + 1 "
  352. "WHERE `id` = '%s'",
  353. record_expire_good, row[0]);
  354. ret = RET_ACCEPT;
  355. gllog(LOGLEVEL_INFO, "greylisting: %s exists (id=%s) - accepting\n", relay_ip, row[0]);
  356. }
  357. else
  358. {
  359. sprintf(query,
  360. "UPDATE `greylisting_data` "
  361. "SET `blocked_count` = `blocked_count` + 1 "
  362. "WHERE `id` = '%s'",
  363. row[0]);
  364. ret = RET_TEMPREJECT;
  365. gllog(LOGLEVEL_INFO, "greylisting: %s is blocked (id=%s) - temp. rejecting\n", relay_ip, row[0]);
  366. }
  367. }
  368. else
  369. {
  370. sprintf(query,
  371. "INSERT INTO `greylisting_data` "
  372. "VALUES (0, '%s', '', '', UTC_TIMESTAMP() + INTERVAL %u MINUTE, UTC_TIMESTAMP() + INTERVAL %u MINUTE, 1, 0, 0, UTC_TIMESTAMP(), UTC_TIMESTAMP())",
  373. relay_ip, block_expire, record_expire);
  374. ret = RET_TEMPREJECT;
  375. gllog(LOGLEVEL_INFO, "greylisting: %s doesn't exist. - temp. rejecting\n", relay_ip);
  376. }
  377. mysql_free_result(res);
  378.  
  379. if (mysql_query_wrapper(mysql, query))
  380. {
  381. gllog(LOGLEVEL_ERROR, "greylisting: mysql: %s\n", mysql_error(mysql));
  382. return RET_NOTFOUND;
  383. }
  384.  
  385. return ret;
  386. }
  387.  
  388. int main()
  389. {
  390. int ret = 1;
  391. int greylisted = 0;
  392. MYSQL *mysql = NULL;
  393.  
  394. /* load config */
  395. if (ret && !load_config())
  396. ret = 0;
  397.  
  398. /* connect to mysql */
  399. if (ret)
  400. {
  401. mysql_library_init(-1, NULL, NULL);
  402. mysql = mysql_init(NULL);
  403. if (!mysql_real_connect(mysql, mysql_host, mysql_user, mysql_pass, mysql_db, mysql_port, NULL, 0))
  404. {
  405. gllog(LOGLEVEL_FATAL, "greylisting: mysql: %s\n", mysql_error(mysql));
  406. ret = 0;
  407. }
  408. }
  409.  
  410. /* greylisting checks */
  411. if (ret && !greylisted)
  412. {
  413. greylisted = check_listed(mysql);
  414. if (greylisted == RET_NOTFOUND)
  415. greylisted = check_greylisted(mysql);
  416. }
  417.  
  418. /* print smtp error code */
  419. if (ret)
  420. {
  421. switch(greylisted)
  422. {
  423. case RET_REJECT:
  424. printf(CMD_REJECT);
  425. break;
  426. case RET_TEMPREJECT:
  427. printf(CMD_TEMPREJECT);
  428. break;
  429. }
  430. }
  431.  
  432. /* cleanup stuff */
  433. gllog(LOGLEVEL_DEBUG, "greylisting: exiting\n");
  434. if (mysql)
  435. mysql_close(mysql);
  436. mysql_library_end();
  437. cleanup();
  438. return !ret;
  439. }
  440.  
  441.