/* * IMC2 - an inter-mud communications protocol * * iced.c: IMC-channel-extensions (ICE) daemon code * * Copyright (C) 1997 Oliver Jowett <oliver@jowett.manawatu.planet.co.nz> * * 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; either version 2 of the License, or * (at your option) any later version. * * 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 (see the file COPYING); if not, write to the * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include <stdlib.h> #include <stdio.h> #include <stdarg.h> #include <string.h> #include <ctype.h> #include "imc.h" #include "iced.h" ice_channel *iced_channel_list; static ice_channel *next_refresh, *next_list; imc_char_data iced_char = { "ICE", /* name */ 0, /* invis */ -1, /* level */ 0 /* wizi */ }; static void fixactive(ice_channel *c) { char arg[IMC_NAME_LENGTH]; const char *p; char buf[IMC_DATA_LENGTH]; if (c->policy != ICE_PRIVATE) { imc_strfree(c->active); c->active=imc_strdup(""); return; } buf[0]=0; p=imc_getarg(c->invited, arg, IMC_NAME_LENGTH); while (arg[0]) { const char *mud=imc_mudof(arg); if (!imc_hasname(buf, mud)) { if (buf[0]) strcat(buf, " "); strcat(buf, mud); } p=imc_getarg(p, arg, IMC_NAME_LENGTH); } p=imc_getarg(c->operators, arg, IMC_NAME_LENGTH); while (arg[0]) { const char *mud=imc_mudof(arg); if (!imc_hasname(buf, mud)) { if (buf[0]) strcat(buf, " "); strcat(buf, mud); } p=imc_getarg(p, arg, IMC_NAME_LENGTH); } if (!imc_hasname(buf, imc_mudof(c->owner))) { if (buf[0]) strcat(buf, " "); strcat(buf, imc_mudof(c->owner)); } imc_strfree(c->active); c->active=imc_strdup(buf); } void iced_save_channels(void) { ice_channel *c; FILE *fp; char name[IMC_DATA_LENGTH]; strcpy(name, imc_prefix); strcat(name, "iced"); fp=fopen(name, "w"); if (!fp) { imc_logerror("Can't write to %s", name); return; } for (c=iced_channel_list; c; c=c->next) { /* save */ fprintf(fp, "%s %s %d\n" "op %s\n" "invite %s\n" "exclude %s\n", c->name, c->owner, c->policy, c->operators[0] ? c->operators : "none", c->invited[0] ? c->invited : "none", c->excluded[0] ? c->excluded : "none"); } fclose(fp); } void iced_load_channels(void) { FILE *fp; char name[IMC_DATA_LENGTH]; char buf1[IMC_DATA_LENGTH]; char buf2[IMC_DATA_LENGTH]; char buf3[IMC_DATA_LENGTH]; char buf4[IMC_DATA_LENGTH]; char buf5[IMC_DATA_LENGTH]; int p; strcpy(name, imc_prefix); strcat(name, "iced"); fp=fopen(name, "r"); if (!fp) { imc_logerror("Can't open %s", name); return; } while (fscanf(fp, "%s %s %d\n" "op %[^\n]\n" "invite %[^\n]\n" "exclude %[^\n]\n", buf1, buf2, &p, buf3, buf4, buf5) == 6) { ice_channel *c=imc_malloc(sizeof(*c)); if (!strcmp(buf3, "none")) buf3[0]=0; if (!strcmp(buf4, "none")) buf4[0]=0; if (!strcmp(buf5, "none")) buf5[0]=0; c->local=NULL; c->name=imc_strdup(buf1); c->owner=imc_strdup(buf2); c->operators=imc_strdup(buf3); c->invited=imc_strdup(buf4); c->excluded=imc_strdup(buf5); c->active=imc_strdup(""); c->policy=p; c->next=iced_channel_list; iced_channel_list=c; fixactive(c); imc_logstring("ICEd: loaded %s (%s) owned by %s", c->name, c->policy == ICE_OPEN ? "open" : c->policy == ICE_CLOSED ? "closed" : "private", c->owner); } fclose(fp); } ice_channel *iced_findchannel(const char *name) { ice_channel *c; for (c=iced_channel_list; c; c=c->next) if (!strcasecmp(c->name, name)) return c; return NULL; } void iced_gannounce(const char *fmt, ...) { char buf[IMC_DATA_LENGTH]; va_list ap; strcpy(buf, "announces: "); va_start(ap, fmt); vsnprintf(buf+strlen(buf), IMC_DATA_LENGTH, fmt, ap); va_end(ap); imc_send_emote(&iced_char, 15, buf, "*"); } void iced_privmsg(ice_channel *c, imc_packet *out, const char *exclude) { const char *p; char arg[IMC_NAME_LENGTH]; p=imc_getarg(c->active, arg, IMC_NAME_LENGTH); while(arg[0]) { if (!exclude || strcasecmp(arg, exclude)) { sprintf(out->to, "*@%s", arg); imc_send(out); } p=imc_getarg(p, arg, IMC_NAME_LENGTH); } } void iced_announce(ice_channel *c, const char *fmt, ...) { va_list ap; char buf[IMC_DATA_LENGTH]; strcpy(buf, "announces: "); va_start(ap, fmt); vsnprintf(buf+strlen(buf), IMC_DATA_LENGTH-strlen(buf), fmt, ap); va_end(ap); if (c->policy == ICE_PRIVATE) { imc_packet out; strcpy(out.from, "ICE"); strcpy(out.to, "*"); strcpy(out.type, "ice-msg-r"); imc_initdata(&out.data); imc_addkey(&out.data, "realfrom", imc_makename("ICE", imc_name)); imc_addkey(&out.data, "text", buf); imc_addkey(&out.data, "channel", c->name); imc_addkeyi(&out.data, "emote", 1); iced_privmsg(c, &out, NULL); imc_freedata(&out.data); } else { imc_packet out; strcpy(out.from, "ICE"); strcpy(out.to, "*"); strcpy(out.type, "ice-msg-b"); imc_initdata(&out.data); imc_addkey(&out.data, "text", buf); imc_addkey(&out.data, "channel", c->name); imc_addkeyi(&out.data, "emote", 1); imc_send(&out); imc_freedata(&out.data); } } int (*iced_recv_chain)(const imc_packet *, int); /* channel daemon hook */ int iced_recv(const imc_packet *p, int bcast) { /* commands */ if (!strcasecmp(p->type, "ice-cmd")) { iced_recv_command(p->from, imc_getkey(&p->data, "channel", ""), imc_getkey(&p->data, "command", ""), imc_getkey(&p->data, "data", ""), 0); return 1; } else if (!strcasecmp(p->type, "ice-msg-p")) { /* private message to be forwarded */ iced_recv_msg_p(p->from, imc_getkey(&p->data, "channel", ""), imc_getkey(&p->data, "text", ""), imc_getkeyi(&p->data, "emote", 0)); return 1; } else if (!strcasecmp(p->type, "ice-msg-b")) { /* check for misdirection */ iced_recv_msg_b(p->from, imc_getkey(&p->data, "channel", "")); return 1; } else if (!strcasecmp(p->type, "ice-refresh")) { iced_recv_refresh(p->from, imc_getkey(&p->data, "channel", "*")); return 1; } else if (!strcasecmp(p->type, "ice-join-request")) { iced_recv_join(p->from, imc_getkey(&p->data, "channel", "")); return 1; } else if (!strcasecmp(p->type, "ice-leave-request")) { iced_recv_leave(p->from, imc_getkey(&p->data, "channel", "")); return 1; } if (iced_recv_chain) return (*iced_recv_chain)(p, bcast); else return 0; } struct { char *name; int level; /* 0=anyone, 1=op only, 2=owner only */ void (*cmdfn)(ice_channel *c, const char *cname, const char *from, const char *data); int needchan; } iced_cmdtable[] = { { "destroy", 2, iced_destroy, 1 }, { "policy", 2, iced_policy, 1 }, { "addop", 2, iced_addop, 1 }, { "removeop", 2, iced_removeop, 1 }, { "invite", 1, iced_invite, 1 }, { "uninvite", 1, iced_uninvite, 1 }, { "exclude", 1, iced_exclude, 1 }, { "unexclude", 1, iced_unexclude, 1 }, { "create", 0, iced_create, 0 }, { "refresh", 0, iced_refresh, 0 }, { "list", 0, iced_list, 0 }, { NULL, 0, NULL } }; int iced_getaccess(ice_channel *c, const char *from) { if (!c) return 0; else if (!strcasecmp(from, c->owner)) return 2; else if (imc_hasname(c->operators, from)) return 1; else return 0; } void iced_recv_command(const char *from, const char *chan, const char *cmd, const char *data, int override) { ice_channel *c; int i; if (imc_isignored(from)) { imc_sendignore(from); return; } for (i=0; iced_cmdtable[i].name; i++) if (!strcasecmp(iced_cmdtable[i].name, cmd)) break; if (!iced_cmdtable[i].name) { imc_send_tell(&iced_char, from, "Unknown command. Send LIST for a list.", 1); return; } c=iced_findchannel(chan); if (!c && iced_cmdtable[i].needchan) { imc_send_tell(NULL, from, "No such channel. Syntax: icommand <command> <channel> [<data..>]", 1); return; } if (!override && (iced_getaccess(c, from) < iced_cmdtable[i].level)) { imc_send_tell(&iced_char, from, "Insufficient security to do that.", 1); return; } (*iced_cmdtable[i].cmdfn)(c, chan, from, data); iced_save_channels(); } /* list commands */ void iced_list(ice_channel *c, const char *cname, const char *from, const char *data) { char out[IMC_DATA_LENGTH]; int i; int access; strcpy(out, "Available commands:\n" "Lvl Ok? Name\n"); access=iced_getaccess(c, from); for (i=0; iced_cmdtable[i].name; i++) { sprintf(out+strlen(out), " %d %s %s\n", iced_cmdtable[i].level, access >= iced_cmdtable[i].level ? "Yes" : "No ", iced_cmdtable[i].name); } imc_send_tell(&iced_char, from, out, 1); } void iced_send_destroy(const char *cname, const char *to) { imc_packet out; strcpy(out.from, "ICE"); strcpy(out.to, to ? to : "*"); strcpy(out.type, "ice-destroy"); imc_initdata(&out.data); imc_addkey(&out.data, "channel", cname); imc_send(&out); imc_freedata(&out.data); } /* destroy a channel */ void iced_destroy(ice_channel *c, const char *cname, const char *from, const char *data) { ice_channel *p; if (strcasecmp(data, "destroy")) { imc_send_tell(&iced_char, from, "Use 'destroy <channel> destroy' to confirm.", 1); return; } /* remove/free the channel from our list */ if (c==iced_channel_list) iced_channel_list=c->next; else { for (p=iced_channel_list; p; p=p->next) if (p->next == c) break; if (!p) imc_logerror("%s not in channel list?!", c->name); else p->next=c->next; } if (c==next_refresh) next_refresh=c->next; if (c==next_list) next_list=c->next; imc_logstring("%s destroys channel %s", from, c->name); iced_gannounce("Channel %s has been destroyed by %s.", c->name, from); imc_strfree(c->name); imc_strfree(c->owner); imc_strfree(c->operators); imc_strfree(c->invited); imc_strfree(c->excluded); imc_strfree(c->active); imc_free(c, sizeof(*c)); imc_send_tell(&iced_char, from, "Done.", 1); /* send destroy notification */ iced_send_destroy(c->name, NULL); } /* set channel policy */ void iced_policy(ice_channel *c, const char *cname, const char *from, const char *data) { if (!strcasecmp(data, "open")) { c->policy=ICE_OPEN; iced_announce(c, "Channel policy is now: open."); iced_gannounce("%s is now policy: open.", c->name); iced_update(c, NULL); } else if (!strcasecmp(data, "closed")) { c->policy=ICE_CLOSED; iced_announce(c, "Channel policy is now: closed."); iced_gannounce("%s is now policy: closed.", c->name); iced_update(c, NULL); } else if (!strcasecmp(data, "private")) { c->policy=ICE_PRIVATE; iced_announce(c, "Channel policy is now: private."); iced_gannounce("%s is now policy: private.", c->name); iced_update(c, NULL); } else imc_send_tell(&iced_char, from, "Syntax: <channel> policy [open|closed|private]", 1); } /* add operator */ void iced_addop(ice_channel *c, const char *cname, const char *from, const char *data) { char buf[1000]; char arg[IMC_NAME_LENGTH]; if (!data[0]) { imc_send_tell(&iced_char, from, "Syntax: addop <channel> <user@mud>", 1); return; } if (imc_hasname(c->operators, data)) { imc_send_tell(&iced_char, from, "They are already an operator.", 1); return; } imc_getarg(data, arg, IMC_NAME_LENGTH); if (!strchr(arg, '@')) { imc_send_tell(&iced_char, from, "Need a full user@mud name to add.", 1); return; } imc_addname(&c->operators, arg); sprintf(buf, "%s is now an operator of %s.", arg, c->name); imc_send_tell(&iced_char, from, buf, 1); iced_announce(c, "%s is now an operator.", arg); iced_update(c, NULL); } /* remove operator */ void iced_removeop(ice_channel *c, const char *cname, const char *from, const char *data) { char buf[1000]; char arg[IMC_NAME_LENGTH]; imc_getarg(data, arg, IMC_NAME_LENGTH); if (!arg[0]) { imc_send_tell(&iced_char, from, "Syntax: removeop <channel> <user@mud>", 1); return; } if (!imc_hasname(c->operators, arg)) { imc_send_tell(&iced_char, from, "They are not an operator.", 1); return; } imc_removename(&c->operators, arg); sprintf(buf, "%s is no longer an operator of %s.", arg, c->name); imc_send_tell(&iced_char, from, buf, 1); iced_announce(c, "%s is no longer an operator.", arg); iced_update(c, NULL); } /* invite mud or player */ void iced_invite(ice_channel *c, const char *cname, const char *from, const char *data) { char buf[1000]; char arg[IMC_NAME_LENGTH]; imc_getarg(data, arg, IMC_NAME_LENGTH); if (!arg[0]) { imc_send_tell(&iced_char, from, "Syntax: invite <channel> <user@mud>", 1); return; } if (imc_hasname(c->invited, arg)) { imc_send_tell(&iced_char, from, "They are already on the invite list.", 1); return; } imc_addname(&c->invited, arg); sprintf(buf, "%s is now invited to %s.", arg, c->name); imc_send_tell(&iced_char, from, buf, 1); iced_announce(c, "%s invites %s.", from, arg); iced_update(c, NULL); } /* uninvite mud or player */ void iced_uninvite(ice_channel *c, const char *cname, const char *from, const char *data) { char buf[1000]; char arg[IMC_NAME_LENGTH]; imc_getarg(data, arg, IMC_NAME_LENGTH); if (!arg[0]) { imc_send_tell(&iced_char, from, "Syntax: uninvite <channel> <user@mud>", 1); return; } if (!imc_hasname(c->invited, arg)) { imc_send_tell(&iced_char, from, "They are not currently on the invite list.", 1); return; } imc_removename(&c->invited, arg); sprintf(buf, "%s is no longer invited on %s.", arg, c->name); imc_send_tell(&iced_char, from, buf, 1); iced_announce(c, "%s uninvites %s.", from, arg); iced_update(c, NULL); } /* exclude mud or player */ void iced_exclude(ice_channel *c, const char *cname, const char *from, const char *data) { char buf[1000]; char arg[IMC_NAME_LENGTH]; imc_getarg(data, arg, IMC_NAME_LENGTH); if (!arg[0]) { imc_send_tell(&iced_char, from, "Syntax: exclude <channel> <user@mud>", 1); return; } if (imc_hasname(c->excluded, arg)) { imc_send_tell(&iced_char, from, "They are already on the exclude list.", 1); return; } imc_addname(&c->excluded, arg); sprintf(buf, "%s is now excluded from %s.", arg, c->name); imc_send_tell(&iced_char, from, buf, 1); iced_announce(c, "%s excludes %s.", from, arg); iced_update(c, NULL); } /* unexclude mud or player */ void iced_unexclude(ice_channel *c, const char *cname, const char *from, const char *data) { char buf[1000]; char arg[IMC_NAME_LENGTH]; imc_getarg(data, arg, IMC_NAME_LENGTH); if (!arg[0]) { imc_send_tell(&iced_char, from, "Syntax: unexclude <channel> <user@mud>", 1); return; } if (!imc_hasname(c->excluded, arg)) { imc_send_tell(&iced_char, from, "They are not on the exclude list.", 1); return; } imc_removename(&c->excluded, arg); sprintf(buf, "%s is no longer excluded from %s.", arg, c->name); imc_send_tell(&iced_char, from, buf, 1); iced_announce(c, "%s unexcludes %s.", from, arg); iced_update(c, NULL); } /* create a channel */ void iced_create(ice_channel *c, const char *cname, const char *from, const char *data) { ice_channel *p; if (!imc_hasname(ICED_CREATORS, "*") && !imc_hasname(ICED_CREATORS, from) && !imc_hasname(ICED_CREATORS, imc_mudof(from))) { imc_send_tell(&iced_char, from, "You don't have permission to create channels here.", 1); return; } if (!cname[0]) { char buf[IMC_DATA_LENGTH]; sprintf(buf, "Syntax: create %s:<channel name>", imc_name); imc_send_tell(&iced_char, from, buf, 1); return; } if (!strchr(cname, ':') || strcasecmp(ice_mudof(cname), imc_name)) { char buf[IMC_DATA_LENGTH]; sprintf(buf, "Channels created here must begin with %s:", imc_name); imc_send_tell(&iced_char, from, buf, 1); return; } if (c) { imc_send_tell(&iced_char, from, "A channel by that name already exists.", 1); return; } p=imc_malloc(sizeof(*p)); p->name=imc_strdup(cname); p->owner=imc_strdup(from); p->operators=imc_strdup(""); p->invited=imc_strdup(""); p->excluded=imc_strdup(""); p->active=imc_strdup(""); p->policy=ICE_CLOSED; /* start refreshes as needed */ if (!iced_channel_list) imc_add_event(ICED_REFRESH_TIME, ev_iced_refresh, NULL, 1); p->next=iced_channel_list; iced_channel_list=p; iced_update(p, NULL); imc_logstring("%s creates channel %s", from, p->name); iced_gannounce("Channel %s created by %s.", p->name, from); imc_send_tell(&iced_char, from, "Channel created.", 1); } /* refresh a channel */ void iced_refresh(ice_channel *c, const char *cname, const char *from, const char *data) { if (!c) { if (!strcmp(cname, "*")) { if (!next_list) { next_list=iced_channel_list; imc_add_event(5, ev_iced_chanlist, (void *)from, 1); } else { imc_send_tell(&iced_char, from, "Refresh already in progress - try again later.", 1); } } else { iced_send_destroy(cname, imc_mudof(from)); imc_send_tell(&iced_char, from, "No such channel. Destroy message sent.", 1); } return; } iced_update(c, imc_mudof(from)); imc_send_tell(&iced_char, from, "Channel refreshed.", 1); } /* private message - for forwarding */ void iced_recv_msg_p(const char *from, const char *chan, const char *txt, int emote) { ice_channel *c; imc_packet out; char temp[IMC_NAME_LENGTH]; if (imc_isignored(from)) { imc_sendignore(from); return; } c=iced_findchannel(chan); if (!c) { imc_send_tell(&iced_char, from, "You're trying to talk on a nonexistant channel.", 1); iced_send_destroy(chan, imc_mudof(from)); return; } if (!ice_audible(c, from)) { imc_send_tell(&iced_char, from, "You're trying to talk on a channel that you don't have access to.", 1); iced_update(c, imc_mudof(from)); return; } if (c->policy != ICE_PRIVATE) { imc_send_tell(&iced_char, from, "Misconfiguration, sending PtP message on nonprivate channel. Try again.", 1); iced_update(c, imc_mudof(from)); } strcpy(out.from, "ICE"); strcpy(out.type, "ice-msg-r"); /* redirect */ imc_initdata(&out.data); imc_addkey(&out.data, "realfrom", from); imc_addkey(&out.data, "channel", chan); imc_addkey(&out.data, "text", txt); imc_addkeyi(&out.data, "emote", emote); strcpy(temp, imc_mudof(from)); /* since we do several imc_sends */ iced_privmsg(c, &out, temp); imc_freedata(&out.data); } /* broadcast message - complain if its a private channel */ void iced_recv_msg_b(const char *from, const char *chan) { ice_channel *c; if (!strchr(chan, ':') || strcasecmp(ice_mudof(chan), imc_name)) return; c=iced_findchannel(chan); if (!c) { imc_send_tell(&iced_char, from, "You're trying to talk on a nonexistant channel.", 1); iced_send_destroy(chan, imc_mudof(from)); return; } if (!ice_audible(c, from)) { imc_send_tell(&iced_char, from, "You're trying to talk on a channel that you don't have access to.", 1); iced_update(c, imc_mudof(from)); return; } if (c->policy == ICE_PRIVATE) { imc_send_tell(&iced_char, from, "Misconfiguration, sending broadcast message on private channel. Try again.", 1); iced_update(c, imc_mudof(from)); } /* do nothing otherwise */ } /* refresh request */ void iced_recv_refresh(const char *from, const char *chan) { if (!strcmp(chan, "*")) { if (!next_list) { next_list=iced_channel_list; imc_add_event(5, ev_iced_chanlist, (void *)from, 1); } else return; /* try again later */ } else { ice_channel *c=iced_findchannel(chan); if (!c) { iced_send_destroy(chan, imc_mudof(from)); return; /* no such channel */ } iced_update(c, imc_mudof(from)); } } void iced_recv_join(const char *from, const char *chan) { ice_channel *c; const char *mud; c=iced_findchannel(chan); if (!c) return; mud=imc_mudof(from); if (!ice_audible(c, mud)) return; if (imc_hasname(c->active, mud)) return; imc_addname(&c->active, mud); } void iced_recv_leave(const char *from, const char *chan) { ice_channel *c; const char *mud; c=iced_findchannel(chan); if (!c) return; mud=imc_mudof(from); if (!imc_hasname(c->active, mud)) return; imc_removename(&c->active, mud); } /* update a channel */ void iced_update(ice_channel *c, const char *to) { imc_packet out; fixactive(c); strcpy(out.from, "ICE"); strcpy(out.to, to ? to : "*@*"); strcpy(out.type, "ice-update"); imc_initdata(&out.data); imc_addkey(&out.data, "channel", c->name); imc_addkey(&out.data, "owner", c->owner); imc_addkey(&out.data, "operators", c->operators); imc_addkey(&out.data, "policy", c->policy == ICE_OPEN ? "open" : c->policy == ICE_CLOSED ? "closed" : "private"); imc_addkey(&out.data, "invited", c->invited); imc_addkey(&out.data, "excluded", c->excluded); imc_send(&out); imc_freedata(&out.data); } /* spam out all channel updates at once */ void ev_iced_chanlist(void *data) { char *to=data; if (!next_list) return; iced_update(next_list, to); imc_add_event(5, ev_iced_chanlist, data, 1); next_list=next_list->next; } /* generate a channel listing */ void ev_iced_refresh(void *dummy) { imc_add_event(ICED_REFRESH_TIME, ev_iced_refresh, NULL, 1); if (imc_active < IA_UP) return; if (!next_refresh) next_refresh=iced_channel_list; if (!next_refresh) return; iced_update(next_refresh, NULL); next_refresh=next_refresh->next; } /* global init */ void iced_init(void) { imc_logstring("ICE daemon starting."); iced_recv_chain=imc_recv_hook; imc_recv_hook=iced_recv; imc_add_event(ICED_REFRESH_TIME, ev_iced_refresh, NULL, 1); iced_load_channels(); }