/* Copyright (C) 2000-1 drscholl@users.sourceforge.net * This is free software distributed under the terms of the * GNU Public License. See the file COPYING for details. * * $Id: class.c,v 1.21 2001/09/22 05:52:06 drscholl Exp $ * * Based on bans.c in part. * oracle ip database idea taken from ircd * is_address() taken from hybrid ircd and modified to return a complete * ip. * All this mess put together by Colten Edwards (q) */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #ifndef WIN32 #include #include #include #include #endif #include "opennap.h" #include "hashlist.h" #include "debug.h" #define F_SYNC (1<<0) typedef struct { char *name; LIST *list; unsigned int flags; unsigned int oracle[256]; } acl_t; static acl_t ilines; static acl_t dlines; static acl_t elines; static acl_t limits; #define check_oracle(acl,ip) (((acl)->oracle[(ip) & 0xff] & (ip)) == (ip)) int check_class (CONNECTION * con, ip_info_t * info) { LIST *list; CLASS *class; int count, boot_em = 0; count = info->users; if (!count) return count; if (Max_Clones > 0 && count >= Max_Clones) boot_em = count; if (check_oracle (&limits, con->ip)) { for (list = limits.list; list; list = list->next) { class = list->data; if ((con->ip & class->mask) == (class->target & class->mask)) { if (count >= class->limit) return count; else return 0; } } } return boot_em; } /* determine whether a connection from this ip address is allowed by the acls * defined. */ int acl_connection_allowed (unsigned int ip) { if (ilines.list) { /* i:lines exist, don't allow any connections unless the ip * matches */ if (!check_oracle (&ilines, ip)) return 0; } /* check for a d:line */ if (check_oracle (&dlines, ip)) { /* check for an e:line */ if (check_oracle (&elines, ip)) return 1; return 0; } return 1; } static int generic_acl_save (acl_t * acl) { FILE *fp; LIST *list; access_t *b; char path[_POSIX_PATH_MAX]; char maskstr[sizeof ("/xxx.xxx.xxx.xxx")]; snprintf (path, sizeof (path), "%s/%s", Config_Dir, acl->name); if (acl->list == 0) { unlink (path); return 0; } #ifdef WIN32 #define LE "\r\n" #else #define LE "\n" #endif if ((fp = fopen (path, "w")) == 0) return -1; fprintf (fp, "# DO NOT EDIT THIS FILE! - automatically generated by opennap%s", LE); fprintf (fp, "# Hi Mom, We got %s!%s", acl->name, LE); for (list = acl->list; list; list = list->next) { struct in_addr in; b = list->data; maskstr[0] = 0; if (b->mask != 0xffffffff) { in.s_addr = BSWAP32 (b->mask); snprintf (maskstr, sizeof (maskstr), "/%s", inet_ntoa (in)); } in.s_addr = BSWAP32 (b->ip); fprintf (fp, "%s%s %3d%s", inet_ntoa (in), maskstr, b->count, LE); } if (fclose (fp)) { logerr ("generic_acl_save", "fclose"); return -1; } return 0; } void acl_save (void) { generic_acl_save (&limits); generic_acl_save (&ilines); generic_acl_save (&dlines); generic_acl_save (&elines); } static int compare_ipmask (unsigned int a, unsigned int amask, unsigned int b, unsigned int bmask) { int r = -2; /* always use the smallest mask to compare */ unsigned int usemask = (amask > bmask) ? bmask : amask; /* struct in_addr in; in.s_addr = ntohl(a); printf("comparing %s(%u) and", inet_ntoa(in), a); in.s_addr = ntohl(b); printf(" %s(%u) = ",inet_ntoa(in), b); in.s_addr = ntohl(usemask); printf(" using mask=%s ", inet_ntoa(in)); */ if ((a & usemask) == (b & usemask)) { /*printf("masks are equal "); */ /* the larger mask (more specific) should sort first */ if (bmask != amask) r = (amask > bmask) ? 1 : -1; } /* otherwise sort by ip */ if (r == -2) { if (b == a) r = 0; else r = (b > a) ? 1 : -1; } /* printf("%d\n", r); */ return r; } /** acl_insert * @param acl access list to modify * @param ipstr ip/mask string * @param nstr limit to place on class * @returns -1 on error, 0 if new acl was added, 1 if existing acl was changed */ static int acl_insert (acl_t * acl, char *ipstr, int count) { unsigned int ip, mask; LIST **access_list = &acl->list; access_t *acc; LIST *list; int r; if (!is_address (ipstr, &ip, &mask)) return -1; for (; *access_list; access_list = &(*access_list)->next) { acc = (*access_list)->data; r = compare_ipmask (htonl (BSWAP32 (ip)), htonl (BSWAP32 (mask)), htonl (BSWAP32 (acc->ip)), htonl (BSWAP32 (acc->mask))); if (r == 0) { acc->count = count; return 1; } else if (r > 0) break; } acc = CALLOC (1, sizeof (access_t)); if (!acc) { OUTOFMEMORY ("acl_insert"); return -1; } acc->ip = ip; acc->mask = mask; acc->count = count; list = CALLOC (1, sizeof (LIST)); if (!list) { FREE (acc); OUTOFMEMORY ("acl_insert"); return -1; } list->next = *access_list; *access_list = list; list->data = acc; acl->oracle[ip & 0xff] |= (ip | ~mask); return 0; } static int generic_acl_load (acl_t * acl) { FILE *fp; int ac; char *av[2], *ptr, path[_POSIX_PATH_MAX]; int line = 0; snprintf (path, sizeof (path), "%s/%s", Config_Dir, acl->name); if (!(fp = fopen (path, "r"))) { if (errno != ENOENT) logerr ("generic_acl_load", path); return -1; } while (fgets (Buf, sizeof (Buf) - 1, fp)) { line++; ptr = Buf; while (ISSPACE (*ptr)) ptr++; if (*ptr == '#' || *ptr == 0) continue; ac = split_line (av, FIELDS (av), Buf); if (ac < 1) continue; if (acl_insert (acl, av[0], (ac > 1) ? atoi (av[1]) : 0) == -1) { log_message ("generic_acl_load:%s:%d:error parsing line:%s %s", path, line, av[0], (ac > 1) ? av[1] : ""); } } fclose (fp); return 0; } static acl_t * get_acl (int tag) { if (tag == MSG_CLIENT_DLINE_ADD || tag == MSG_CLIENT_DLINE_DEL || tag == MSG_CLIENT_DLINE_LIST) return &dlines; if (tag == MSG_CLIENT_ELINE_ADD || tag == MSG_CLIENT_ELINE_DEL || tag == MSG_CLIENT_ELINE_LIST) return &elines; if (tag == MSG_CLIENT_ILINE_ADD || tag == MSG_CLIENT_ILINE_DEL || tag == MSG_CLIENT_ILINE_LIST) return &ilines; if (tag == MSG_CLIENT_CLASS_ADD || tag == MSG_CLIENT_CLASS_DEL || tag == MSG_CLIENT_CLASS_LIST) return &limits; return 0; } static void generic_acl_init (acl_t * acl, const char *name, int flags) { memset (acl, 0, sizeof (acl)); acl->name = STRDUP (name); acl->flags = flags; } void acl_init (void) { generic_acl_init (&limits, "limit", F_SYNC); generic_acl_load (&limits); generic_acl_init (&ilines, "iline", 0); generic_acl_load (&ilines); generic_acl_init (&dlines, "dline", 0); generic_acl_load (&dlines); generic_acl_init (&elines, "eline", 0); generic_acl_load (&elines); } /* ??? [:sender] [/] [arg] */ HANDLER (generic_acl_add) { int ac; char *av[2]; char *sender_name; USER *sender; acl_t *acl; int count, modified = 0; (void) len; if (pop_user_server (con, tag, &pkt, &sender_name, &sender)) return; if (sender && sender->level < LEVEL_MODERATOR) return; ac = split_line (av, FIELDS (av), pkt); if (ac < 1) return; acl = get_acl (tag); count = (ac > 1) ? atoi (av[1]) : 0; modified = acl_insert (acl, av[0], count); if (modified == -1) { if (ISUSER (con)) send_cmd (con, MSG_SERVER_NOSUCH, "unable to parse ip/mask"); return; } notify_mods (CHANGELOG_MODE, "%s%s %s %s on %s (%d)", sender ? "" : "Server ", sender_name, modified ? "modified" : "added", acl->name, av[0], count); if (acl->flags & F_SYNC) pass_message_args (con, tag, ":%s %s %d", sender_name, av[0], count); generic_acl_save (acl); } /* ??? [:sender] [/] */ HANDLER (generic_acl_del) { char *host; unsigned int ip, mask; LIST **access_list; LIST *tmp; char *sender_name; USER *sender; access_t *acc; acl_t *acl; (void) len; if (pop_user_server (con, tag, &pkt, &sender_name, &sender)) return; if (sender && sender->level < LEVEL_MODERATOR) return; host = next_arg (&pkt); if (!host) return; if (!is_address (host, &ip, &mask)) return; acl = get_acl (tag); access_list = &acl->list; if (!check_oracle (acl, ip)) { if (ISUSER (con)) send_cmd (con, MSG_SERVER_NOSUCH, "no matching ip addresses"); return; } for (; *access_list; access_list = &(*access_list)->next) { acc = (*access_list)->data; if (mask == acc->mask && (ip & mask) == (acc->ip & acc->mask)) { tmp = *access_list; *access_list = (*access_list)->next; FREE (tmp); break; } } /* rebuild the oracle */ memset (acl->oracle, 0, sizeof (int) * 256); for (tmp = acl->list; tmp; tmp = tmp->next) { acc = tmp->data; acl->oracle[acc->ip & 0xff] |= (acc->ip | ~acc->mask); } notify_mods (CHANGELOG_MODE, "%s%s removed %s on %s", sender ? "" : "Server ", sender_name, acl->name, host); if (acl->flags & F_SYNC) pass_message_args (con, tag, ":%s %s", sender_name, host); generic_acl_save (acl); } /* ??? */ HANDLER (generic_acl_list) { access_t *acc; acl_t *acl; char maskstr[sizeof ("/xxx.xxx.xxx.xxx")]; struct in_addr in; LIST *list; (void) len; (void) pkt; CHECK_USER_CLASS ("generic_acl_list"); if (con->user->level < LEVEL_MODERATOR) return; acl = get_acl (tag); for (list = acl->list; list; list = list->next) { acc = list->data; maskstr[0] = 0; if (acc->mask != 0xffffffff) { in.s_addr = BSWAP32 (acc->mask); snprintf (maskstr, sizeof (maskstr), "/%s", inet_ntoa (in)); } in.s_addr = BSWAP32 (acc->ip); send_cmd (con, tag, "%s%s %d", inet_ntoa (in), maskstr, acc->count); } send_cmd (con, tag, ""); } static void generic_acl_sync (CONNECTION * con, int tag, acl_t * acl) { LIST *list; access_t *c; struct in_addr in; char maskstr[sizeof ("/xxx.xxx.xxx.xxx")]; ASSERT (validate_connection (con)); for (list = acl->list; list; list = list->next) { c = list->data; maskstr[0] = 0; if (c->mask != 0xffffffff) { in.s_addr = BSWAP32 (c->mask); snprintf (maskstr, sizeof (maskstr), "/%s", inet_ntoa (in)); } in.s_addr = BSWAP32 (c->ip); send_cmd (con, tag, ":%s %s%s %d", Server_Name, inet_ntoa (in), maskstr, c->count); } } void acl_sync (CONNECTION * con) { generic_acl_sync (con, MSG_CLIENT_CLASS_ADD, &limits); } static void acl_free (acl_t * acl) { list_free (acl->list, free_pointer); FREE (acl->name); } void acl_destroy (void) { acl_free (&limits); acl_free (&dlines); acl_free (&ilines); acl_free (&elines); }