Advertisement
Guest User

mod_evasive24.c

a guest
Jul 7th, 2015
247
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.49 KB | None | 0 0
  1. /*
  2. mod_evasive for Apache 2
  3. Copyright (c) by Jonathan A. Zdziarski
  4.  
  5. LICENSE
  6.  
  7. This program is free software; you can redistribute it and/or
  8. modify it under the terms of the GNU General Public License
  9. as published by the Free Software Foundation; either version 2
  10. of the License, or (at your option) any later 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
  19. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20.  
  21. */
  22.  
  23. #include <sys/types.h>
  24. #include <unistd.h>
  25. #include <sys/socket.h>
  26. #include <sys/stat.h>
  27. #include <netinet/in.h>
  28. #include <arpa/inet.h>
  29. #include <string.h>
  30. #include <stdlib.h>
  31. #include <sys/types.h>
  32. #include <time.h>
  33. #include <syslog.h>
  34. #include <errno.h>
  35.  
  36. #include "httpd.h"
  37. #include "http_core.h"
  38. #include "http_config.h"
  39. #include "http_log.h"
  40. #include "http_request.h"
  41.  
  42. module AP_MODULE_DECLARE_DATA evasive20_module;
  43.  
  44. pid_t getpid(void);
  45. pid_t getppid(void);
  46.  
  47. /* BEGIN DoS Evasive Maneuvers Definitions */
  48.  
  49. #define MAILER "/bin/mail %s"
  50. #define LOG( A, ... ) { openlog("mod_evasive", LOG_PID, LOG_DAEMON); syslog( A, __VA_ARGS__ ); closelog(); }
  51.  
  52. #define DEFAULT_HASH_TBL_SIZE 3097ul // Default hash table size
  53. #define DEFAULT_PAGE_COUNT 2 // Default maximum page hit count per interval
  54. #define DEFAULT_SITE_COUNT 50 // Default maximum site hit count per interval
  55. #define DEFAULT_PAGE_INTERVAL 1 // Default 1 Second page interval
  56. #define DEFAULT_SITE_INTERVAL 1 // Default 1 Second site interval
  57. #define DEFAULT_BLOCKING_PERIOD 10 // Default for Detected IPs; blocked for 10 seconds
  58. #define DEFAULT_LOG_DIR "/tmp" // Default temp directory
  59.  
  60. /* END DoS Evasive Maneuvers Definitions */
  61.  
  62. /* BEGIN NTT (Named Timestamp Tree) Headers */
  63.  
  64. enum { ntt_num_primes = 28 };
  65.  
  66. /* ntt root tree */
  67. struct ntt {
  68. long size;
  69. long items;
  70. struct ntt_node **tbl;
  71. };
  72.  
  73. /* ntt node (entry in the ntt root tree) */
  74. struct ntt_node {
  75. char *key;
  76. time_t timestamp;
  77. long count;
  78. struct ntt_node *next;
  79. };
  80.  
  81. /* ntt cursor */
  82. struct ntt_c {
  83. long iter_index;
  84. struct ntt_node *iter_next;
  85. };
  86.  
  87. struct ntt *ntt_create(long size);
  88. int ntt_destroy(struct ntt *ntt);
  89. struct ntt_node *ntt_find(struct ntt *ntt, const char *key);
  90. struct ntt_node *ntt_insert(struct ntt *ntt, const char *key, time_t timestamp);
  91. int ntt_delete(struct ntt *ntt, const char *key);
  92. long ntt_hashcode(struct ntt *ntt, const char *key);
  93. struct ntt_node *c_ntt_first(struct ntt *ntt, struct ntt_c *c);
  94. struct ntt_node *c_ntt_next(struct ntt *ntt, struct ntt_c *c);
  95.  
  96. /* END NTT (Named Timestamp Tree) Headers */
  97.  
  98.  
  99. /* BEGIN DoS Evasive Maneuvers Globals */
  100.  
  101. struct ntt *hit_list; // Our dynamic hash table
  102.  
  103. static unsigned long hash_table_size = DEFAULT_HASH_TBL_SIZE;
  104. static int page_count = DEFAULT_PAGE_COUNT;
  105. static int page_interval = DEFAULT_PAGE_INTERVAL;
  106. static int site_count = DEFAULT_SITE_COUNT;
  107. static int site_interval = DEFAULT_SITE_INTERVAL;
  108. static int blocking_period = DEFAULT_BLOCKING_PERIOD;
  109. static char *email_notify = NULL;
  110. static char *log_dir = NULL;
  111. static char *system_command = NULL;
  112. static const char *whitelist(cmd_parms *cmd, void *dconfig, const char *ip);
  113. int is_whitelisted(const char *ip);
  114.  
  115. /* END DoS Evasive Maneuvers Globals */
  116.  
  117. static void * create_hit_list(apr_pool_t *p, server_rec *s)
  118. {
  119. /* Create a new hit list for this listener */
  120.  
  121. hit_list = ntt_create(hash_table_size);
  122. return 0;
  123. }
  124.  
  125. static const char *whitelist(cmd_parms *cmd, void *dconfig, const char *ip)
  126. {
  127. char entry[128];
  128. snprintf(entry, sizeof(entry), "WHITELIST_%s", ip);
  129. ntt_insert(hit_list, entry, time(NULL));
  130.  
  131. return NULL;
  132. }
  133.  
  134.  
  135. static int access_checker(request_rec *r)
  136. {
  137. int ret = OK;
  138.  
  139. /* BEGIN DoS Evasive Maneuvers Code */
  140.  
  141. if (r->prev == NULL && r->main == NULL && hit_list != NULL) {
  142. char hash_key[2048];
  143. struct ntt_node *n;
  144. time_t t = time(NULL);
  145.  
  146. /* Check whitelist */
  147. if (is_whitelisted(r->useragent_ip))
  148. return OK;
  149.  
  150. /* First see if the IP itself is on "hold" */
  151. n = ntt_find(hit_list, r->useragent_ip);
  152.  
  153. if (n != NULL && t-n->timestamp<blocking_period) {
  154.  
  155. /* If the IP is on "hold", make it wait longer in 403 land */
  156. ret = HTTP_FORBIDDEN;
  157. n->timestamp = time(NULL);
  158.  
  159. /* Not on hold, check hit stats */
  160. } else {
  161.  
  162. /* Has URI been hit too much? */
  163. snprintf(hash_key, 2048, "%s_%s", r->useragent_ip, r->uri);
  164. n = ntt_find(hit_list, hash_key);
  165. if (n != NULL) {
  166.  
  167. /* If URI is being hit too much, add to "hold" list and 403 */
  168. if (t-n->timestamp<page_interval && n->count>=page_count) {
  169. ret = HTTP_FORBIDDEN;
  170. ntt_insert(hit_list, r->useragent_ip, time(NULL));
  171. } else {
  172.  
  173. /* Reset our hit count list as necessary */
  174. if (t-n->timestamp>=page_interval) {
  175. n->count=0;
  176. }
  177. }
  178. n->timestamp = t;
  179. n->count++;
  180. } else {
  181. ntt_insert(hit_list, hash_key, t);
  182. }
  183.  
  184. /* Has site been hit too much? */
  185. snprintf(hash_key, 2048, "%s_SITE", r->useragent_ip);
  186. n = ntt_find(hit_list, hash_key);
  187. if (n != NULL) {
  188.  
  189. /* If site is being hit too much, add to "hold" list and 403 */
  190. if (t-n->timestamp<site_interval && n->count>=site_count) {
  191. ret = HTTP_FORBIDDEN;
  192. ntt_insert(hit_list, r->useragent_ip, time(NULL));
  193. } else {
  194.  
  195. /* Reset our hit count list as necessary */
  196. if (t-n->timestamp>=site_interval) {
  197. n->count=0;
  198. }
  199. }
  200. n->timestamp = t;
  201. n->count++;
  202. } else {
  203. ntt_insert(hit_list, hash_key, t);
  204. }
  205. }
  206.  
  207. /* Perform email notification and system functions */
  208. if (ret == HTTP_FORBIDDEN) {
  209. char filename[1024];
  210. struct stat s;
  211. FILE *file;
  212.  
  213. snprintf(filename, sizeof(filename), "%s/dos-%s", log_dir != NULL ? log_dir : DEFAULT_LOG_DIR, r->useragent_ip);
  214. if (stat(filename, &s)) {
  215. file = fopen(filename, "w");
  216. if (file != NULL) {
  217. fprintf(file, "%d\n", getpid());
  218. fclose(file);
  219.  
  220. LOG(LOG_ALERT, "Blacklisting address %s: possible DoS attack.", r->useragent_ip);
  221. if (email_notify != NULL) {
  222. snprintf(filename, sizeof(filename), MAILER, email_notify);
  223. file = popen(filename, "w");
  224. if (file != NULL) {
  225. fprintf(file, "To: %s\n", email_notify);
  226. fprintf(file, "Subject: HTTP BLACKLIST %s\n\n", r->useragent_ip);
  227. fprintf(file, "mod_evasive HTTP Blacklisted %s\n", r->useragent_ip);
  228. pclose(file);
  229. }
  230. }
  231.  
  232. if (system_command != NULL) {
  233. snprintf(filename, sizeof(filename), system_command, r->useragent_ip);
  234. system(filename);
  235. }
  236.  
  237. } else {
  238. LOG(LOG_ALERT, "Couldn't open logfile %s: %s",filename, strerror(errno));
  239. }
  240.  
  241. } /* if (temp file does not exist) */
  242.  
  243. } /* if (ret == HTTP_FORBIDDEN) */
  244.  
  245. } /* if (r->prev == NULL && r->main == NULL && hit_list != NULL) */
  246.  
  247. /* END DoS Evasive Maneuvers Code */
  248.  
  249. if (ret == HTTP_FORBIDDEN
  250. && (ap_satisfies(r) != SATISFY_ANY || !ap_some_auth_required(r))) {
  251. ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
  252. "client denied by server configuration: %s",
  253. r->filename);
  254. }
  255.  
  256. return ret;
  257. }
  258.  
  259. int is_whitelisted(const char *ip) {
  260. char hashkey[128];
  261. char octet[4][4];
  262. char *dip;
  263. char *oct;
  264. int i = 0;
  265.  
  266. memset(octet, 0, 16);
  267. dip = strdup(ip);
  268. if (dip == NULL)
  269. return 0;
  270.  
  271. oct = strtok(dip, ".");
  272. while(oct != NULL && i<4) {
  273. if (strlen(oct)<=3)
  274. strcpy(octet[i], oct);
  275. i++;
  276. oct = strtok(NULL, ".");
  277. }
  278. free(dip);
  279.  
  280. /* Exact Match */
  281. snprintf(hashkey, sizeof(hashkey), "WHITELIST_%s", ip);
  282. if (ntt_find(hit_list, hashkey)!=NULL)
  283. return 1;
  284.  
  285. /* IPv4 Wildcards */
  286. snprintf(hashkey, sizeof(hashkey), "WHITELIST_%s.*.*.*", octet[0]);
  287. if (ntt_find(hit_list, hashkey)!=NULL)
  288. return 1;
  289.  
  290. snprintf(hashkey, sizeof(hashkey), "WHITELIST_%s.%s.*.*", octet[0], octet[1]);
  291. if (ntt_find(hit_list, hashkey)!=NULL)
  292. return 1;
  293.  
  294. snprintf(hashkey, sizeof(hashkey), "WHITELIST_%s.%s.%s.*", octet[0], octet[1], octet[2]);
  295. if (ntt_find(hit_list, hashkey)!=NULL)
  296. return 1;
  297.  
  298. /* No match */
  299. return 0;
  300. }
  301.  
  302. static apr_status_t destroy_hit_list(void *not_used) {
  303. ntt_destroy(hit_list);
  304. free(email_notify);
  305. free(system_command);
  306. return 0;
  307. }
  308.  
  309.  
  310. /* BEGIN NTT (Named Timestamp Tree) Functions */
  311.  
  312. static unsigned long ntt_prime_list[ntt_num_primes] =
  313. {
  314. 53ul, 97ul, 193ul, 389ul, 769ul,
  315. 1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
  316. 49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
  317. 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
  318. 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
  319. 1610612741ul, 3221225473ul, 4294967291ul
  320. };
  321.  
  322.  
  323. /* Find the numeric position in the hash table based on key and modulus */
  324.  
  325. long ntt_hashcode(struct ntt *ntt, const char *key) {
  326. unsigned long val = 0;
  327. for (; *key; ++key) val = 5 * val + *key;
  328. return(val % ntt->size);
  329. }
  330.  
  331. /* Creates a single node in the tree */
  332.  
  333. struct ntt_node *ntt_node_create(const char *key) {
  334. char *node_key;
  335. struct ntt_node* node;
  336.  
  337. node = (struct ntt_node *) malloc(sizeof(struct ntt_node));
  338. if (node == NULL) {
  339. return NULL;
  340. }
  341. if ((node_key = strdup(key)) == NULL) {
  342. free(node);
  343. return NULL;
  344. }
  345. node->key = node_key;
  346. node->timestamp = time(NULL);
  347. node->next = NULL;
  348. return(node);
  349. }
  350.  
  351. /* Tree initializer */
  352.  
  353. struct ntt *ntt_create(long size) {
  354. long i = 0;
  355. struct ntt *ntt = (struct ntt *) malloc(sizeof(struct ntt));
  356.  
  357. if (ntt == NULL)
  358. return NULL;
  359. while (ntt_prime_list[i] < size) { i++; }
  360. ntt->size = ntt_prime_list[i];
  361. ntt->items = 0;
  362. ntt->tbl = (struct ntt_node **) calloc(ntt->size, sizeof(struct ntt_node *));
  363. if (ntt->tbl == NULL) {
  364. free(ntt);
  365. return NULL;
  366. }
  367. return(ntt);
  368. }
  369.  
  370. /* Find an object in the tree */
  371.  
  372. struct ntt_node *ntt_find(struct ntt *ntt, const char *key) {
  373. long hash_code;
  374. struct ntt_node *node;
  375.  
  376. if (ntt == NULL) return NULL;
  377.  
  378. hash_code = ntt_hashcode(ntt, key);
  379. node = ntt->tbl[hash_code];
  380.  
  381. while (node) {
  382. if (!strcmp(key, node->key)) {
  383. return(node);
  384. }
  385. node = node->next;
  386. }
  387. return((struct ntt_node *)NULL);
  388. }
  389.  
  390. /* Insert a node into the tree */
  391.  
  392. struct ntt_node *ntt_insert(struct ntt *ntt, const char *key, time_t timestamp) {
  393. long hash_code;
  394. struct ntt_node *parent;
  395. struct ntt_node *node;
  396. struct ntt_node *new_node = NULL;
  397.  
  398. if (ntt == NULL) return NULL;
  399.  
  400. hash_code = ntt_hashcode(ntt, key);
  401. parent = NULL;
  402. node = ntt->tbl[hash_code];
  403.  
  404. while (node != NULL) {
  405. if (strcmp(key, node->key) == 0) {
  406. new_node = node;
  407. node = NULL;
  408. }
  409.  
  410. if (new_node == NULL) {
  411. parent = node;
  412. node = node->next;
  413. }
  414. }
  415.  
  416. if (new_node != NULL) {
  417. new_node->timestamp = timestamp;
  418. new_node->count = 0;
  419. return new_node;
  420. }
  421.  
  422. /* Create a new node */
  423. new_node = ntt_node_create(key);
  424. new_node->timestamp = timestamp;
  425. new_node->timestamp = 0;
  426.  
  427. ntt->items++;
  428.  
  429. /* Insert */
  430. if (parent) { /* Existing parent */
  431. parent->next = new_node;
  432. return new_node; /* Return the locked node */
  433. }
  434.  
  435. /* No existing parent; add directly to hash table */
  436. ntt->tbl[hash_code] = new_node;
  437. return new_node;
  438. }
  439.  
  440. /* Tree destructor */
  441.  
  442. int ntt_destroy(struct ntt *ntt) {
  443. struct ntt_node *node, *next;
  444. struct ntt_c c;
  445.  
  446. if (ntt == NULL) return -1;
  447.  
  448. node = c_ntt_first(ntt, &c);
  449. while(node != NULL) {
  450. next = c_ntt_next(ntt, &c);
  451. ntt_delete(ntt, node->key);
  452. node = next;
  453. }
  454.  
  455. free(ntt->tbl);
  456. free(ntt);
  457. ntt = (struct ntt *) NULL;
  458.  
  459. return 0;
  460. }
  461.  
  462. /* Delete a single node in the tree */
  463.  
  464. int ntt_delete(struct ntt *ntt, const char *key) {
  465. long hash_code;
  466. struct ntt_node *parent = NULL;
  467. struct ntt_node *node;
  468. struct ntt_node *del_node = NULL;
  469.  
  470. if (ntt == NULL) return -1;
  471.  
  472. hash_code = ntt_hashcode(ntt, key);
  473. node = ntt->tbl[hash_code];
  474.  
  475. while (node != NULL) {
  476. if (strcmp(key, node->key) == 0) {
  477. del_node = node;
  478. node = NULL;
  479. }
  480.  
  481. if (del_node == NULL) {
  482. parent = node;
  483. node = node->next;
  484. }
  485. }
  486.  
  487. if (del_node != NULL) {
  488.  
  489. if (parent) {
  490. parent->next = del_node->next;
  491. } else {
  492. ntt->tbl[hash_code] = del_node->next;
  493. }
  494.  
  495. free(del_node->key);
  496. free(del_node);
  497. ntt->items--;
  498.  
  499. return 0;
  500. }
  501.  
  502. return -5;
  503. }
  504.  
  505. /* Point cursor to first item in tree */
  506.  
  507. struct ntt_node *c_ntt_first(struct ntt *ntt, struct ntt_c *c) {
  508.  
  509. c->iter_index = 0;
  510. c->iter_next = (struct ntt_node *)NULL;
  511. return(c_ntt_next(ntt, c));
  512. }
  513.  
  514. /* Point cursor to next iteration in tree */
  515.  
  516. struct ntt_node *c_ntt_next(struct ntt *ntt, struct ntt_c *c) {
  517. long index;
  518. struct ntt_node *node = c->iter_next;
  519.  
  520. if (ntt == NULL) return NULL;
  521.  
  522. if (node) {
  523. if (node != NULL) {
  524. c->iter_next = node->next;
  525. return (node);
  526. }
  527. }
  528.  
  529. if (! node) {
  530. while (c->iter_index < ntt->size) {
  531. index = c->iter_index++;
  532.  
  533. if (ntt->tbl[index]) {
  534. c->iter_next = ntt->tbl[index]->next;
  535. return(ntt->tbl[index]);
  536. }
  537. }
  538. }
  539. return((struct ntt_node *)NULL);
  540. }
  541.  
  542. /* END NTT (Named Pointer Tree) Functions */
  543.  
  544.  
  545. /* BEGIN Configuration Functions */
  546.  
  547. static const char *
  548. get_hash_tbl_size(cmd_parms *cmd, void *dconfig, const char *value) {
  549. long n = strtol(value, NULL, 0);
  550.  
  551. if (n<=0) {
  552. hash_table_size = DEFAULT_HASH_TBL_SIZE;
  553. } else {
  554. hash_table_size = n;
  555. }
  556.  
  557. return NULL;
  558. }
  559.  
  560. static const char *
  561. get_page_count(cmd_parms *cmd, void *dconfig, const char *value) {
  562. long n = strtol(value, NULL, 0);
  563. if (n<=0) {
  564. page_count = DEFAULT_PAGE_COUNT;
  565. } else {
  566. page_count = n;
  567. }
  568.  
  569. return NULL;
  570. }
  571.  
  572. static const char *
  573. get_site_count(cmd_parms *cmd, void *dconfig, const char *value) {
  574. long n = strtol(value, NULL, 0);
  575. if (n<=0) {
  576. site_count = DEFAULT_SITE_COUNT;
  577. } else {
  578. site_count = n;
  579. }
  580.  
  581. return NULL;
  582. }
  583.  
  584. static const char *
  585. get_page_interval(cmd_parms *cmd, void *dconfig, const char *value) {
  586. long n = strtol(value, NULL, 0);
  587. if (n<=0) {
  588. page_interval = DEFAULT_PAGE_INTERVAL;
  589. } else {
  590. page_interval = n;
  591. }
  592.  
  593. return NULL;
  594. }
  595.  
  596. static const char *
  597. get_site_interval(cmd_parms *cmd, void *dconfig, const char *value) {
  598. long n = strtol(value, NULL, 0);
  599. if (n<=0) {
  600. site_interval = DEFAULT_SITE_INTERVAL;
  601. } else {
  602. site_interval = n;
  603. }
  604.  
  605. return NULL;
  606. }
  607.  
  608. static const char *
  609. get_blocking_period(cmd_parms *cmd, void *dconfig, const char *value) {
  610. long n = strtol(value, NULL, 0);
  611. if (n<=0) {
  612. blocking_period = DEFAULT_BLOCKING_PERIOD;
  613. } else {
  614. blocking_period = n;
  615. }
  616.  
  617. return NULL;
  618. }
  619.  
  620. static const char *
  621. get_log_dir(cmd_parms *cmd, void *dconfig, const char *value) {
  622. if (value != NULL && value[0] != 0) {
  623. if (log_dir != NULL)
  624. free(log_dir);
  625. log_dir = strdup(value);
  626. }
  627.  
  628. return NULL;
  629. }
  630.  
  631. static const char *
  632. get_email_notify(cmd_parms *cmd, void *dconfig, const char *value) {
  633. if (value != NULL && value[0] != 0) {
  634. if (email_notify != NULL)
  635. free(email_notify);
  636. email_notify = strdup(value);
  637. }
  638.  
  639. return NULL;
  640. }
  641.  
  642. static const char *
  643. get_system_command(cmd_parms *cmd, void *dconfig, const char *value) {
  644. if (value != NULL && value[0] != 0) {
  645. if (system_command != NULL)
  646. free(system_command);
  647. system_command = strdup(value);
  648. }
  649.  
  650. return NULL;
  651. }
  652.  
  653. /* END Configuration Functions */
  654.  
  655. static const command_rec access_cmds[] =
  656. {
  657. AP_INIT_TAKE1("DOSHashTableSize", get_hash_tbl_size, NULL, RSRC_CONF,
  658. "Set size of hash table"),
  659.  
  660. AP_INIT_TAKE1("DOSPageCount", get_page_count, NULL, RSRC_CONF,
  661. "Set maximum page hit count per interval"),
  662.  
  663. AP_INIT_TAKE1("DOSSiteCount", get_site_count, NULL, RSRC_CONF,
  664. "Set maximum site hit count per interval"),
  665.  
  666. AP_INIT_TAKE1("DOSPageInterval", get_page_interval, NULL, RSRC_CONF,
  667. "Set page interval"),
  668.  
  669. AP_INIT_TAKE1("DOSSiteInterval", get_site_interval, NULL, RSRC_CONF,
  670. "Set site interval"),
  671.  
  672. AP_INIT_TAKE1("DOSBlockingPeriod", get_blocking_period, NULL, RSRC_CONF,
  673. "Set blocking period for detected DoS IPs"),
  674.  
  675. AP_INIT_TAKE1("DOSEmailNotify", get_email_notify, NULL, RSRC_CONF,
  676. "Set email notification"),
  677.  
  678. AP_INIT_TAKE1("DOSLogDir", get_log_dir, NULL, RSRC_CONF,
  679. "Set log dir"),
  680.  
  681. AP_INIT_TAKE1("DOSSystemCommand", get_system_command, NULL, RSRC_CONF,
  682. "Set system command on DoS"),
  683.  
  684. AP_INIT_ITERATE("DOSWhitelist", whitelist, NULL, RSRC_CONF,
  685. "IP-addresses wildcards to whitelist"),
  686.  
  687. { NULL }
  688. };
  689.  
  690. static void register_hooks(apr_pool_t *p) {
  691. ap_hook_access_checker(access_checker, NULL, NULL, APR_HOOK_MIDDLE);
  692. apr_pool_cleanup_register(p, NULL, apr_pool_cleanup_null, destroy_hit_list);
  693. };
  694.  
  695. module AP_MODULE_DECLARE_DATA evasive20_module =
  696. {
  697. STANDARD20_MODULE_STUFF,
  698. NULL,
  699. NULL,
  700. create_hit_list,
  701. NULL,
  702. access_cmds,
  703. register_hooks
  704. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement