/* * Copyright (c) 2009, Rambler media * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY Rambler media ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL Rambler BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Ipmark is custom plugin for marking ip with some weight, it understand several commands: * - add value * - delete * - check * * This plugin is a sample of custom filters system in rspamd */ #include "config.h" #include "cfg_file.h" #include "radix.h" #define ADD_COMMAND "add" #define DELETE_COMMAND "delete" #define CHECK_COMMAND "check" enum ipmark_command { COMMAND_ADD, COMMAND_DELETE, COMMAND_CHECK }; /* Exported functions */ void module_init (struct config_file *cfg); void* before_connect (void); gboolean parse_line (const char *line, size_t len, char **output, void *user_data); void after_connect (char **output, char **log_line, void *user_data); void module_fin (void); /* Internal variables */ static char *filename = NULL; static radix_tree_t *radix = NULL; /* Implementation */ char * get_module_opt (struct config_file *cfg, char *module_name, char *opt_name) { GList *cur_opt; struct module_opt *cur; cur_opt = g_hash_table_lookup (cfg->modules_opts, module_name); if (cur_opt == NULL) { return NULL; } while (cur_opt) { cur = cur_opt->data; if (strcmp (cur->param, opt_name) == 0) { return cur->value; } cur_opt = g_list_next (cur_opt); } return NULL; } static gboolean parse_ipmask (const char *begin, struct in_addr *ina, int *mask, int *value) { const char *pos; char ip_buf[sizeof ("255.255.255.255")], mask_buf[3] = { '\0', '\0', '\0' }, *p; int state = 1, dots = 0; bzero (ip_buf, sizeof (ip_buf)); bzero (mask_buf, sizeof (mask_buf)); pos = begin; while (*pos && state < 5) { switch (state) { case 1: /* Begin parse ip */ if (g_ascii_isspace (*p)) { state = 3; } else if (p - ip_buf >= sizeof (ip_buf) || dots > 3) { return FALSE; } if (g_ascii_isdigit (*pos)) { *p ++ = *pos ++; } else if (*pos == '.') { *p ++ = *pos ++; dots ++; } else if (*pos == '/') { pos ++; p = mask_buf; state = 2; } else { /* Invalid character */ return FALSE; } break; case 2: /* Parse mask */ if (g_ascii_isspace (*p)) { state = 3; } else if (p - mask_buf > 2) { return FALSE; } if (g_ascii_isdigit (*pos)) { *p ++ = *pos ++; } else { return FALSE; } break; case 3: if (!g_ascii_isspace (*p)) { state = 4; } else { p ++; } break; case 4: *value = strtol (p, NULL, 10); state = 99; break; } } if (!inet_aton (ip_buf, ina)) { return FALSE; } if (mask_buf[0] != '\0') { /* Also parse mask */ *mask = (mask_buf[0] - '0') * 10 + mask_buf[1] - '0'; if (*mask > 32) { return FALSE; } } else { *mask = 32; } *mask = 0xFFFFFFFF << (32 - *mask); return TRUE; } static void read_radix_file (void) { FILE *f; char buf[BUFSIZ]; struct in_addr ina; int mask = 0, value = 0; f = fopen (filename, "r"); if (f != NULL) { while (fgets (buf, sizeof (buf), f)) { if (parse_ipmask (buf, &ina, &mask, &value)) { (void)radix32tree_add (radix, ntohl (ina.s_addr), mask, (uintptr_t)value); } } fclose (f); } } static gboolean write_cb_func (uint32_t key, uint32_t level, uintptr_t value, void *user_data) { FILE *f = user_data; struct in_addr ina; ina.s_addr = htonl (value); fprintf (f, "%s/%d %d\n", inet_ntoa (ina), level, (int)value); return FALSE; } static void write_radix_file (void) { FILE *f; /* Traverse throught radix tree */ f = fopen (filename, "w"); if (f != NULL) { radix32tree_traverse (radix, write_cb_func, f); fclose (f); } } void module_init (struct config_file *cfg) { char *value; if (cfg && (value = get_module_opt (cfg, "ipmark", "file")) != NULL) { filename = g_strdup (value); } radix = radix_tree_create (); if (filename) { read_radix_file (); } } void * before_connect (void) { /* In fact we do not need any session data, so just return NULL */ return NULL; } void module_fin (void) { if (filename) { write_radix_file (); g_free (filename); filename = NULL; } if (radix) { radix_tree_free (radix); radix = NULL; } } gboolean parse_line (const char *line, size_t len, char **output, void *user_data) { char ip_buf[sizeof ("255.255.255.255")], mask_buf[3] = {'\0', '\0', '\0'}; const char *p; char *c = ip_buf, *err_str; struct in_addr ina; int state = 0, next_state = 0, dots = 0; int16_t value = 0; uint32_t mask; enum ipmark_command cmd = COMMAND_ADD; /* Parse input line */ p = line; while (p - line < len && state < 100) { switch (state) { case 0: /* Expect command */ if (g_ascii_strncasecmp (line, ADD_COMMAND, sizeof (ADD_COMMAND) - 1) == 0) { state = 99; next_state = 1; cmd = COMMAND_ADD; p += sizeof (ADD_COMMAND); } else if (g_ascii_strncasecmp (line, DELETE_COMMAND, sizeof (DELETE_COMMAND) - 1) == 0) { state = 99; next_state = 1; cmd = COMMAND_DELETE; p += sizeof (DELETE_COMMAND); } else if (g_ascii_strncasecmp (line, CHECK_COMMAND, sizeof (CHECK_COMMAND) - 1) == 0) { state = 99; next_state = 1; cmd = COMMAND_CHECK; p += sizeof (CHECK_COMMAND); } else { state = 100; } break; case 1: /* Expect ip or ipmask */ if (c - ip_buf >= sizeof (ip_buf) || dots > 3) { state = 100; } if (g_ascii_isdigit (*p)) { *c ++ = *p ++; } else if (*p == '.') { *c ++ = *p ++; dots ++; } else if (*p == '/') { p ++; c = mask_buf; state = 2; } else if (g_ascii_isspace (*p)) { if (cmd == COMMAND_ADD) { next_state = 3; } else { next_state = 100; } state = 99; } else { /* Invalid character */ state = 100; } break; case 2: /* Parse mask */ if (c - mask_buf > 2) { state = 100; } if (g_ascii_isdigit (*p)) { *c ++ = *p ++; } else if (g_ascii_isspace (*p)) { if (cmd == COMMAND_ADD) { next_state = 3; } else { next_state = 100; } state = 99; } else { state = 100; } break; case 3: errno = 0; value = strtol (p, &err_str, 10); if (errno != 0) { state = 100; } else { state = 101; } break; case 99: /* Skip spaces */ if (g_ascii_isspace (*p)) { p ++; } else { state = next_state; } break; } } if (state == 100 || !inet_aton (ip_buf, &ina)) { /* Error occured */ *output = g_strdup ("ERR: invalid command"); return FALSE; } /* Process mask */ if (mask_buf[0] == '\0') { /* Assume /32 mask */ mask = 0xFFFFFFFF; } else { mask = (mask_buf[0] - '0') * 10 + mask_buf[1] - '0'; if (mask > 32) { mask = 32; } mask = 0xFFFFFFFF << (32 - mask); } /* Process command */ switch (cmd) { case COMMAND_ADD: state = radix32tree_add (radix, ntohl (ina.s_addr), mask, (uintptr_t)value); if (state == 0) { *output = g_strdup_printf ("OK: new value %d", (int)value); } else if (state == -1) { *output = g_strdup ("ERR: cannot insert value"); } else { *output = g_strdup_printf ("OK: new value %d", state); } break; case COMMAND_DELETE: if (radix32tree_delete (radix, ntohl (ina.s_addr), mask) == 0) { *output = g_strdup ("OK: address deleted"); } else { *output = g_strdup ("ERR: address not found"); } break; case COMMAND_CHECK: if ((value = radix32tree_find (radix, ntohl (ina.s_addr))) != RADIX_NO_VALUE) { *output = g_strdup_printf ("OK: %d", (int)value); } else { *output = g_strdup ("ERR: address not found"); } break; } return TRUE; } void after_connect (char **output, char **log_line, void *user_data) { /* Placeholder */ return; }