//irc.c: /* * Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2011-2012 * * This file is part of roard a part of RoarAudio, * a cross-platform sound system for both, home and professional use. * See README for details. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 * as published by the Free Software Foundation. * * RoarAudio 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 software; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include #include #define MAX_CHANNELS 8 static int on_nick(int client, const char * cmd, char * args, char * text); static int on_user(int client, const char * cmd, char * args, char * text); static int on_quit(int client, const char * cmd, char * args, char * text); static int on_ping(int client, const char * cmd, char * args, char * text); static int on_lusers(int client, const char * cmd, char * args, char * text); static int on_whois(int client, const char * cmd, char * args, char * text); static int on_privmsg(int client, const char * cmd, char * args, char * text); static int on_list(int client, const char * cmd, char * args, char * text); static int on_join(int client, const char * cmd, char * args, char * text); static int on_part(int client, const char * cmd, char * args, char * text); static int on_names(int client, const char * cmd, char * args, char * text); static int on_topic(int client, const char * cmd, char * args, char * text); static int do_join(int client, const char * channel); static int do_part(int client, const char * channel); static int do_names(int client, const char * channel); static void cb_client_delete(struct roar_notify_core * core, struct roar_event * event, void * userdata); static struct roar_subscriber * subscription_client_delete = NULL; static char * server_name = "irc.roard"; static char * server_fullname = "RoarAudio roard IRC Server plugin"; static char * quit_msg = NULL; static struct channel { char * name; struct { char * text; char * user; time_t ts; } topic; size_t client_count; int clients[ROAR_CLIENTS_MAX]; } g_channels[MAX_CHANNELS]; static struct command { const char * name; int (*func)(int client, const char * cmd, char * args, char * text); } g_commands[] = { {"NICK", on_nick}, {"USER", on_user}, {"QUIT", on_quit}, {"PING", on_ping}, {"LUSERS", on_lusers}, {"WHOIS", on_whois}, {"PRIVMSG", on_privmsg}, {"NOTICE", on_privmsg}, {"LIST", on_list}, {"JOIN", on_join}, {"PART", on_part}, {"NAMES", on_names}, {"TOPIC", on_topic}, {NULL, NULL} }; static void init(void) { struct roar_event event; memset(&event, 0, sizeof(event)); event.event = ROAR_OE_BASICS_DELETE; event.emitter = -1; event.target = -1; event.target_type = ROAR_OT_CLIENT; memset(g_channels, 0, sizeof(g_channels)); subscription_client_delete = roar_notify_core_subscribe(NULL, &event, cb_client_delete, NULL); } static int is_valid_name (const char * name) { register char c; for (; (c = *name) != 0; name++) { if ( !(isalnum(c) || c == '-' || c == '_') ) return 0; } return 1; } static void strip_nl(char * str) { register char c; for (; (c = *str) != 0; str++) { if ( c == '\r' || c == '\n' ) { *str = 0; return; } } } static char * get_nick(int client) { struct roar_client * c; clients_get(client, &c); return c->name; } static const char * get_ident(int client) { static char buf[80]; struct roar_client * c; clients_get(client, &c); if ( c->uid == -1 ) { buf[0] = '~'; buf[1] = 0; } else { snprintf(buf, sizeof(buf)-1, "uid%i~", c->uid); buf[sizeof(buf)-1] = 0; } return buf; } static const char * get_node(int client) { struct roar_client * c; static char buf_nnode[80]; char * nnode; clients_get(client, &c); roar_nnode_to_str(&(c->nnode), buf_nnode, sizeof(buf_nnode)); nnode = strstr(buf_nnode, ": "); if ( nnode == NULL ) { nnode = "unknown~"; } else { nnode += 2; } return nnode; } static const char * get_ufull(int client) { struct roar_client * c; static char buf[80]; const char * ident = get_ident(client); const char * nnode = get_node(client); clients_get(client, &c); snprintf(buf, sizeof(buf)-1, "%s!%s@%s", c->name, ident, nnode); buf[sizeof(buf)-1] = 0; return buf; } static const char * get_realname(int client) { (void)client; return "(no realname)"; } static int get_client_by_nick(const char * nick) { struct roar_client * c; int i; for (i = 0; i < ROAR_CLIENTS_MAX; i++) { if ( clients_get(i, &c) != 0 ) continue; if ( !!strcasecmp(c->name, nick) ) continue; return i; } return -1; } static struct channel * get_channel(const char * name) { struct channel * c; size_t i; for (i = 0; i < MAX_CHANNELS; i++) { c = &(g_channels[i]); if ( !c->client_count ) continue; if ( !!strcasecmp(c->name, name) ) continue; return c; } return NULL; } static size_t get_listener_list(int client, const char * channel, int ** listener) { struct channel * c; static int clients[ROAR_CLIENTS_MAX]; size_t ret = 0; size_t i, k; int j; int found; for (i = 0; i < MAX_CHANNELS; i++) { c = &(g_channels[i]); if ( !c->client_count ) continue; if ( c->clients[client] < 1 ) continue; if ( channel != NULL && !!strcasecmp(c->name, channel) ) continue; for (j = 0; j < ROAR_CLIENTS_MAX; j++) { if ( c->clients[j] > 0 ) { found = 0; for (k = 0; k < ret; k++) { if ( clients[k] == j ) { found = 1; } } if ( !found ) { clients[ret] = j; ret++; } } } } *listener = clients; return ret; } static void put_printf(int client, const char *format, ...) { va_list ap; struct roar_buffer * buf; size_t len = 2048; void * data; int ret; if ( roar_buffer_new_data(&buf, len, &data) == -1 ) return; va_start(ap, format); ret = vsnprintf(data, len-1, format, ap); va_end(ap); if ( roar_buffer_set_len(buf, strlen(data)) == -1 ) { roar_buffer_free(buf); return; } clients_add_output(client, &buf); } static int do_join(int client, const char * channel) { struct channel * c = NULL; size_t i; if ( channel[0] != '#' ) return -1; if ( !is_valid_name(channel+1) ) return -1; for (i = 0; i < MAX_CHANNELS; i++) { if ( !g_channels[i].client_count ) continue; if ( !!strcasecmp(g_channels[i].name, channel) ) continue; c = &(g_channels[i]); break; } if ( c == NULL ) { for (i = 0; i < MAX_CHANNELS; i++) { if ( g_channels[i].client_count ) continue; c = &(g_channels[i]); break; } if ( c == NULL ) return -1; memset(c, 0, sizeof(*c)); c->name = roar_mm_strdup(channel); } if ( c->clients[client] ) return -1; c->clients[client] = 1; c->client_count++; return 0; } static int do_part(int client, const char * channel) { struct channel * c = NULL; size_t i; for (i = 0; i < MAX_CHANNELS; i++) { if ( !g_channels[i].client_count ) continue; if ( !!strcasecmp(g_channels[i].name, channel) ) continue; if ( !g_channels[i].clients[client] ) return -1; c = &(g_channels[i]); break; } c->clients[client] = 0; c->client_count--; if ( !c->client_count ) { roar_mm_free(c->name); if ( c->topic.text != NULL ) roar_mm_free(c->topic.text); if ( c->topic.user != NULL ) roar_mm_free(c->topic.user); } return 0; } static int do_names(int client, const char * channel) { const char * nick = get_nick(client); size_t i; char buf[256]; size_t len, offset; char * c_nick; int j; for (i = 0; i < MAX_CHANNELS; i++) { if ( !g_channels[i].client_count ) continue; if ( !!strcasecmp(g_channels[i].name, channel) ) continue; offset = 0; for (j = 0; j < ROAR_CLIENTS_MAX; j++) { if ( g_channels[i].clients[j] == 0 ) continue; c_nick = get_nick(j); len = strlen(c_nick); if ( (offset + len + 3) > sizeof(buf) ) { buf[offset] = 0; put_printf(client, ":%s 353 %s = %s :%s\n", server_name, nick, channel, buf); offset = 0; } memcpy(buf + offset, c_nick, len); offset += len; buf[offset] = ' '; offset++; buf[offset] = 0; } if ( offset ) { buf[offset] = 0; put_printf(client, ":%s 353 %s = %s :%s\n", server_name, nick, channel, buf); } put_printf(client, ":%s 366 %s %s :End of /NAMES list.\n", server_name, nick, channel); return 0; } return -1; } static void cb_client_delete(struct roar_notify_core * core, struct roar_event * event, void * userdata) { int * listener; size_t count; struct channel * c; char * text = quit_msg; int client = event->target; const char * ufull = get_ufull(client); size_t i; (void)core, (void)userdata; if ( text == NULL ) { text = "Client deleted. Died, kicked or internal error."; } put_printf(client, "ERROR :Closing Link: %s (Quit: %s)\n", ufull, text); count = get_listener_list(client, NULL, &listener); for (; count; count--, listener++) put_printf(*listener, ":%s QUIT :Quit: %s\n", ufull, text); for (i = 0; i < MAX_CHANNELS; i++) { c = &(g_channels[i]); if ( !c->client_count ) continue; if ( !c->clients[client] ) continue; c->clients[client] = 0; c->client_count--; if ( !c->client_count ) { roar_mm_free(c->name); if ( c->topic.text != NULL ) roar_mm_free(c->topic.text); if ( c->topic.user != NULL ) roar_mm_free(c->topic.user); } } clients_flush(client); } static int new_client(int client, struct roar_vio_calls * vio, struct roard_listen * lsock) { struct roar_client_server * cs; char * name; (void)client, (void)vio, (void)lsock; clients_get_server(client, &cs); name = ROAR_CLIENT(cs)->name; snprintf(name, sizeof(ROAR_CLIENT(cs)->name), "Client%i~", client); /* :ph7.ph.sft NOTICE AUTH :*** Looking up your hostname... :ph7.ph.sft NOTICE AUTH :*** Couldn't resolve your hostname; using your IP address instead NICK nick USER A B C D :ph7.ph.sft NOTICE AUTH :*** Couldn't resolve your hostname; using your IP address instead :ph7.ph.sft 002 nick :Your host is ph7.ph.sft, running version Unreal3.2.7 :ph7.ph.sft 003 nick :This server was created Tue Aug 28 2007 at 10:02:00 CEST :ph7.ph.sft 004 nick ph7.ph.sft Unreal3.2.7 iowghraAsORTVSxNCWqBzvdHtGp lvhopsmntikrRcaqOALQbSeIKVfMCuzNTGj :ph7.ph.sft 005 nick NAMESX SAFELIST HCN MAXCHANNELS=10 CHANLIMIT=#:10 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 MAXTARGETS=20 WALLCHOPS :are supported by this server :ph7.ph.sft 005 nick WATCH=128 SILENCE=15 MODES=12 CHANTYPES=# PREFIX=(ohv)@%+ CHANMODES=beIqa,kfL,lj,psmntirRcOAQKVCuzNSMTG NETWORK=PH2 CASEMAPPING=ascii EXTBAN=~,cqnr ELIST=MNUCT STATUSMSG=@%+ EXCEPTS INVEX :are supported by this server :ph7.ph.sft 005 nick CMDS=KNOCK,MAP,DCCALLOW,USERIP :are supported by this server */ put_printf(client, ":%s 001 %s :Welcome to the roard based IRC server.\n" ":%s 375 %s :- %s Message of the Day -\n" ":%s 372 %s :- MotD goes here...\n" ":%s 376 %s :End of /MOTD command.\n", server_name, name, server_name, name, server_name, server_name, name, server_name, name ); on_lusers(client, NULL, NULL, NULL); return 0; } static int check_client(int client, struct roar_vio_calls * vio) { struct roar_vio_calls rvio; char cmd[1024*2]; char * args; char * text; ssize_t len; size_t i; int found = 0; if ( vio == NULL ) { vio = &rvio; roar_vio_open_fh_socket(vio, clients_get_fh(client)); } len = roar_vio_read(vio, cmd, sizeof(cmd)-1); if ( len < 1 ) { clients_delete(client); return -1; } cmd[len] = 0; strip_nl(cmd); ROAR_DBG("check_client(client=%i, vio=?): cmd=\"%s\"", client, cmd); if ( cmd[0] == 0 ) return 0; args = strstr(cmd, " "); if ( args != NULL ) { *args = 0; args++; if ( *args == ':' ) { text = args + 1; args = NULL; } else { text = strstr(args, " :"); if ( text != NULL ) { *text = 0; text += 2; } } } else { text = NULL; } for (i = 0; g_commands[i].name != NULL; i++) { if ( !strcasecmp(g_commands[i].name, cmd) ) { found = 1; g_commands[i].func(client, cmd, args, text); } } if ( !found ) { put_printf(client, ":%s 421 %s %s :Unknown command\n", server_name, get_nick(client), cmd); } return 0; } // commands: static int on_nick(int client, const char * cmd, char * args, char * text) { char * nick = get_nick(client); int * listener; size_t count; const char * ufull; (void)cmd, (void)text; if ( args != NULL && args[0] != 0 && is_valid_name(args) && strlen(args) < ROAR_BUFFER_NAME ) { if ( get_client_by_nick(args) != -1 ) { put_printf(client, ":%s 433 %s %s :Nickname is already in use.\n", server_name, nick, args); } else { ufull = get_ufull(client); put_printf(client, ":%s NICK :%s\n", ufull, args); count = get_listener_list(client, NULL, &listener); for (; count; count--, listener++) if ( *listener != client ) put_printf(*listener, ":%s NICK :%s\n", ufull, args); strcpy(nick, args); } } else { put_printf(client, ":%s 432 %s %s :Erroneous Nickname: Illegal characters\n", server_name, nick, args); } return 0; } static int on_user(int client, const char * cmd, char * args, char * text) { (void)client, (void)cmd, (void)args, (void)text; return 0; } static int on_quit(int client, const char * cmd, char * args, char * text) { int ret; (void)cmd, (void)args; if ( text == NULL ) text = "... have a cuddle ..."; quit_msg = text; ret = clients_delete(client); quit_msg = NULL; return ret; } static int on_ping(int client, const char * cmd, char * args, char * text) { (void)cmd, (void)args; put_printf(client, "PONG :%s\n", text); return 0; } static int on_lusers(int client, const char * cmd, char * args, char * text) { const char * clientnick = get_nick(client); size_t i; size_t numchans = 0; (void)cmd, (void)args, (void)text; /* :v0.fellig.org 251 nick :There are 9 users and 11 invisible on 1 servers :v0.fellig.org 252 nick 1 :operator(s) online :v0.fellig.org 254 nick 16 :channels formed :v0.fellig.org 255 nick :I have 20 clients and 0 servers :v0.fellig.org 265 nick :Current Local Users: 20 Max: 52 :v0.fellig.org 266 nick :Current Global Users: 20 Max: 22 */ for (i = 0; i < MAX_CHANNELS; i++) { if ( !g_channels[i].client_count ) continue; numchans++; } put_printf(client, ":%s 251 %s :There are %zu users and 0 invisible on 1 servers\n", server_name, clientnick, counters_get(cur, clients)); put_printf(client, ":%s 254 %s %zu :channels formed\n", server_name, clientnick, numchans); put_printf(client, ":%s 255 %s :I have %zu clients and 0 servers\n", server_name, clientnick, counters_get(cur, clients)); put_printf(client, ":%s 265 %s :Current Local Users: %zu Max: \n", server_name, clientnick, counters_get(cur, clients)); put_printf(client, ":%s 266 %s :Current Global Users: %zu Max: \n", server_name, clientnick, counters_get(cur, clients)); return 0; } static int on_whois(int client, const char * cmd, char * args, char * text) { const char * clientnick = get_nick(client); const char * tnick = args; int tclient; (void)cmd, (void)text; /* :ph7.ph.sft 317 nick phi 131 1322456381 :seconds idle, signon time */ if ( (tclient = get_client_by_nick(tnick)) == -1 ) { put_printf(client, ":%s 401 %s %s :No such nick/channel\n", server_name, clientnick, tnick); } else { put_printf(client, ":%s 311 %s %s %s %s * :%s\n", server_name, clientnick, tnick, get_ident(tclient), get_node(tclient), get_realname(tclient)); put_printf(client, ":%s 312 %s %s %s :%s\n", server_name, clientnick, tnick, server_name, server_fullname); } put_printf(client, ":%s 318 %s %s :End of /WHOIS list.\n", server_name, clientnick, tnick); return 0; } static int on_privmsg(int client, const char * cmd, char * args, char * text) { const char * ufull = get_ufull(client); int * listener; size_t count; char * next; int tmp; if ( args == NULL || text == NULL ) return -1; if ( text[0] == 0 ) return 0; while (args != NULL) { next = strstr(args, ","); if ( next != NULL ) { *next = 0; next++; } if ( args[0] == '#' ) { count = get_listener_list(client, args, &listener); for (; count; count--, listener++) if ( *listener != client ) put_printf(*listener, ":%s %s %s :%s\n", ufull, cmd, args, text); } else { if ( (tmp = get_client_by_nick(args)) == -1 ) { put_printf(client, ":%s 401 %s %s :No such nick/channel\n", server_name, get_nick(client), args); } else { put_printf(tmp, ":%s %s %s :%s\n", ufull, cmd, args, text); } } args = next; } return 0; } static int on_list(int client, const char * cmd, char * args, char * text) { const char * clientnick = get_nick(client); struct channel * c; size_t i; (void)cmd, (void)args, (void)text; put_printf(client, ":%s 321 %s Channel :Users Name\n", server_name, clientnick); for (i = 0; i < MAX_CHANNELS; i++) { c = &(g_channels[i]); if ( !c->client_count ) continue; put_printf(client, ":%s 322 %s %s %zu :[+] %s\n", server_name, clientnick, c->name, c->client_count, c->topic.text == NULL ? "" : c->topic.text); } put_printf(client, ":%s 323 %s :End of /LIST\n", server_name, clientnick); return 0; } static int on_join(int client, const char * cmd, char * args, char * text) { struct channel * c; const char * ufull = get_ufull(client); int * listener; size_t count; const char * nick; (void)cmd, (void)text; if ( args == NULL ) return -1; if ( do_join(client, args) != 0 ) { return -1; } count = get_listener_list(client, args, &listener); for (; count; count--, listener++) put_printf(*listener, ":%s JOIN :%s\n", ufull, args); c = get_channel(args); if ( c->topic.text != NULL ) { nick = get_nick(client); put_printf(client, ":%s 332 %s %s :%s\n" ":%s 333 %s %s %s %li\n", server_name, nick, c->name, c->topic.text, server_name, nick, c->name, c->topic.user, (long int)c->topic.ts ); } do_names(client, args); return 0; } static int on_part(int client, const char * cmd, char * args, char * text) { const char * ufull = get_ufull(client); int * listener; size_t count; (void)cmd; if ( args == NULL ) return -1; if ( text == NULL ) text = "Dejoined."; count = get_listener_list(client, args, &listener); for (; count; count--, listener++) put_printf(*listener, ":%s PART %s :%s\n", ufull, args, text); do_part(client, args); return 0; } static int on_names(int client, const char * cmd, char * args, char * text) { (void)cmd, (void)text; if ( args == NULL ) return -1; return do_names(client, args); } static int on_topic(int client, const char * cmd, char * args, char * text) { struct channel * c; const char * ufull = get_ufull(client); int * listener; size_t count; char * nick = get_nick(client); (void)cmd; if ( args == NULL ) return -1; c = get_channel(args); if ( c == NULL ) return -1; if ( !c->clients[client] ) { return -1; } if ( c->topic.text != NULL ) roar_mm_free(c->topic.text); if ( c->topic.user != NULL ) roar_mm_free(c->topic.user); c->topic.text = NULL; c->topic.user = roar_mm_strdup(nick); c->topic.ts = time(NULL); if ( text != NULL ) c->topic.text = roar_mm_strdup(text); if ( text == NULL ) text = ""; count = get_listener_list(client, c->name, &listener); for (; count; count--, listener++) put_printf(*listener, ":%s TOPIC %s :%s\n", ufull, c->name, text); return 0; } // plugin handling suff: static int unload(struct roar_dl_librarypara * para, struct roar_dl_libraryinst * lib) { (void)para, (void)lib; roar_notify_core_unsubscribe(NULL, subscription_client_delete); return 0; } static struct roard_proto proto[1] = { {ROAR_PROTO_IRC, ROAR_SUBSYS_NONE, "Internet Relay Chat", NULL, new_client, check_client, NULL, NULL} }; ROARD_DL_REG_PROTO(proto) ROAR_DL_PLUGIN_START(protocol_irc) { ROARD_DL_CHECK_VERSIONS(); ROAR_DL_PLUGIN_META_PRODUCT_NIV("protocol-irc", ROAR_VID_ROARAUDIO, ROAR_VNAME_ROARAUDIO); ROAR_DL_PLUGIN_META_VERSION(ROAR_VERSION_STRING); ROAR_DL_PLUGIN_META_LICENSE_TAG(GPLv3_0); ROAR_DL_PLUGIN_META_CONTACT_FLNE("Philipp", "Schafft", "ph3-der-loewe", "lion@lion.leolix.org"); ROAR_DL_PLUGIN_META_DESC("Implementation of the Internet Relay Chat (IRC)"); ROAR_DL_PLUGIN_REG_UNLOAD(unload); ROARD_DL_REGFN_PROTO(); init(); } ROAR_DL_PLUGIN_END //ll