/* * MU-Conference - Multi-User Conference Service * Copyright (c) 2002 David Sutton * Portions (c) Copyright 2005 Apple Computer, Inc. * * * This program is free software; you can redistribute it and/or drvify * 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA */ #include "conference.h" extern int deliver__flag; /* Handles logging for each room, simply returning if logfile is not defined */ void con_room_log(cnr room, char *nick, char *message) { time_t t; xmlnode xml; jid user; char *output; char timestr[50]; size_t timelen = 49; FILE *logfile; pool p; if(message == NULL || room == NULL) { log_warn(NAME, "[%s] ERR: Aborting - NULL reference found - [%s][%s]", FZONE, message, room); return; } logfile = room->logfile; if(logfile == NULL) { log_debug(NAME, "[%s] Logging not enabled for this room", FZONE); return; } p = pool_heap(1024); /* nicked from mod_time */ t = time(NULL); strftime(timestr, timelen, "[%H:%M:%S]", localtime(&t)); if(room->logformat == LOG_XML) { xml = jutil_msgnew("groupchat", jid_full(room->id) , NULL, strescape(p, message)); user = jid_new(xmlnode_pool(xml), jid_full(room->id)); jid_set(user, nick, JID_RESOURCE); xmlnode_put_attrib(xml, "from", jid_full(user)); jutil_delay(xml, NULL); fprintf(logfile, "%s\n", xmlnode2str(xml)); xmlnode_free(xml); } else if(room->logformat == LOG_XHTML) { if(nick) { if(j_strncmp(message, "/me", 3) == 0) { output = extractAction(strescape(p, message), p); fprintf(logfile, "%s * %s%s
\n", timestr, nick, output); } else { fprintf(logfile, "%s <%s> %s
\n", timestr, nick, strescape(p, message)); } } else { fprintf(logfile, "%s --- %s
\n", timestr, message); } } else { if(nick) { if(j_strncmp(message, "/me", 3) == 0) { output = extractAction(message, p); fprintf(logfile, "%s * %s%s\n", timestr, nick, output); } else { fprintf(logfile, "%s <%s> %s\n", timestr, nick, message); } } else { fprintf(logfile, "%s --- %s\n", timestr, message); } } fflush(logfile); pool_free(p); return; } void con_room_log_new(cnr room) { char *filename; char *curdate; char *dirname; struct stat fileinfo; time_t now = time(NULL); int type; pool p; spool sp; if(room == NULL) { log_warn(NAME, "[%s] Aborting - NULL room", FZONE); return; } p = pool_heap(1024); type = room->logformat; dirname = jid_full(room->id); sp = spool_new(p); if(room->master->logdir) { spooler(sp, room->master->logdir, "/", dirname, sp); } else { spooler(sp, "./", dirname, sp); } filename = spool_print(sp); if(stat(filename,&fileinfo) < 0 && mkdir(filename, S_IRWXU) < 0) { log_warn(NAME, "[%s] ERR: unable to open log directory >%s<", FZONE, filename); return; } curdate = dateget(now); if(type == LOG_XML) spooler(sp, "/", curdate, ".xml", sp); else if(type == LOG_XHTML) spooler(sp, "/", curdate, ".html", sp); else spooler(sp, "/", curdate, ".txt", sp); filename = spool_print(sp); if(stat(filename,&fileinfo) < 0) { log_debug(NAME, "[%s] New logfile >%s<", FZONE, filename); room->logfile = fopen(filename, "a"); if(type == LOG_XHTML && room->logfile != NULL) { fprintf(room->logfile, "\n\nLogs for %s, %s\n\n\n", jid_full(room->id), curdate); fflush(room->logfile); } } else { room->logfile = fopen(filename, "a"); } if(room->logfile == NULL) log_warn(NAME, "[%s] ERR: unable to open log file >%s<", FZONE, filename); else log_debug(NAME, "[%s] Opened logfile >%s<", FZONE, filename); pool_free(p); free(curdate); return; } void con_room_log_close(cnr room) { int type; FILE *logfile; if(room == NULL) { log_warn(NAME, "[%s] Aborting - NULL room", FZONE); return; } type = room->logformat; logfile = room->logfile; if(logfile == NULL) { log_warn(NAME, "[%s] Aborting - NULL logfile", FZONE); return; } log_debug(NAME, "[%s] Closing logfile for room >%s<", FZONE, jid_full(room->id)); if(type == LOG_XHTML) { fprintf(logfile, "\n\n"); fflush(logfile); } fclose(room->logfile); room->logfile = NULL; } void con_room_send_invite(cnu sender, xmlnode node) { xmlnode result; xmlnode element; xmlnode invite; char *body, *user, *reason, *inviter; cnr room; jid from; pool p; if(sender == NULL || node == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return; } log_debug(NAME, "[%s] Sending room invite", FZONE); room = sender->room; from = sender->realid; invite = xmlnode_get_tag(node, "invite"); user = xmlnode_get_attrib(invite, "to"); reason = xmlnode_get_tag_data(invite, "reason"); if(room->public == 1) { inviter = jid_full(jid_user(jid_fix(from))); } else { inviter = xmlnode_get_data(sender->nick); } xmlnode_put_attrib(invite, "from", inviter); xmlnode_hide_attrib(invite, "to"); p = xmlnode_pool(node); if(reason == NULL) { reason = spools(p, "None given", p); } // Apple Radar 3998957: change invitation to be more readable in iChat. // body = spools(p, "You have been invited to the ", jid_full(jid_fix(room->id)), " room by ", inviter, "\nReason: ", reason, p); body = spools(p, "You have been invited to a chat room by ", inviter, "\nReason: ", reason, p); result = jutil_msgnew("normal", user , "Invitation", body); xmlnode_put_attrib(result, "from", jid_full(jid_fix(room->id))); if(room->secret != NULL) { xmlnode_insert_cdata(xmlnode_insert_tag(invite, "password"), room->secret, -1); } xmlnode_insert_node(result, node); element = xmlnode_insert_tag(result, "x"); xmlnode_put_attrib(element, "jid", jid_full(jid_fix(room->id))); xmlnode_put_attrib(element, "xmlns", NS_X_CONFERENCE); xmlnode_insert_cdata(element, reason, -1); log_debug(NAME, "[%s] >>>%s<<<", FZONE, xmlnode2str(result)); deliver(dpacket_new(result), NULL); xmlnode_free(node); return; } void con_room_leaveall(gpointer key, gpointer data, gpointer arg) { cnu user = (cnu)data; xmlnode info = (xmlnode)arg; char *alt, *reason; xmlnode presence; xmlnode tag; xmlnode element; xmlnode node; xmlnode destroy; if(user == NULL) { log_warn(NAME, "[%s] Aborting - NULL user attribute found", FZONE); return; } presence = jutil_presnew(JPACKET__UNAVAILABLE, NULL, NULL); tag = xmlnode_insert_tag(presence,"x"); xmlnode_put_attrib(tag, "xmlns", NS_MUC_USER); element = xmlnode_insert_tag(tag, "item"); xmlnode_put_attrib(element, "role", "none"); xmlnode_put_attrib(element, "affiliation", "none"); if (info != NULL) { destroy = xmlnode_insert_tag(tag, "destroy"); reason = xmlnode_get_tag_data(info, "reason"); node = xmlnode_insert_tag(destroy, "reason"); if(reason != NULL) { xmlnode_insert_cdata(node, reason, -1); } alt = xmlnode_get_attrib(info, "jid"); if (alt != NULL) { xmlnode_put_attrib(destroy, "jid", alt); } } con_user_send(user, user, presence); } void _con_room_usernick(gpointer key, gpointer data, gpointer arg) { cnu user = (cnu)data; xmlnode x = (xmlnode)arg; if(user == NULL || x == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return; } if(j_strcmp(xmlnode_get_data(x),xmlnode_get_data(user->nick)) == 0) xmlnode_put_vattrib(x, "u", (void*)user); } cnu con_room_usernick(cnr room, char *nick) { cnu user; xmlnode node = xmlnode_new_tag("nick"); if(room == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return NULL; } log_debug(NAME, "[%s] searching for nick %s in room %s", FZONE, nick, jid_full(jid_fix(room->id))); xmlnode_insert_cdata(node, nick, -1); g_hash_table_foreach(room->local, _con_room_usernick, (void *)node); user = (cnu)xmlnode_get_vattrib(node,"u"); xmlnode_free(node); return user; } /* returns a valid nick from the list, x is the first foo xmlnode, checks siblings */ char *con_room_nick(cnr room, cnu user, xmlnode x) { char *nick = NULL; xmlnode cur; int count = 1; if(room == NULL || user == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return NULL; } log_debug(NAME, "[%s] looking for valid nick in room %s from starter %s", FZONE, jid_full(jid_fix(room->id)), xmlnode2str(x)); /* make-up-a-nick phase */ if(x == NULL) { nick = pmalloco(user->p, j_strlen(user->realid->user) + 10); log_debug(NAME, "[%s] Malloc: Nick = %d", FZONE, j_strlen(user->realid->user) + 10); sprintf(nick, "%s", user->realid->user); while(con_room_usernick(room, nick) != NULL) sprintf(nick, "%s%d", user->realid->user,count++); return nick; } /* scan the list */ for(cur = x; cur != NULL; cur = xmlnode_get_nextsibling(cur)) { if(j_strcmp(xmlnode_get_name(cur),"nick") == 0 && (nick = xmlnode_get_data(cur)) != NULL) if(con_room_usernick(room, nick) == NULL) break; } if(is_registered(room->master, jid_full(jid_user(jid_fix(user->realid))), nick) == -1) nick = NULL; return nick; } void con_room_sendwalk(gpointer key, gpointer data, gpointer arg) { xmlnode x = (xmlnode)arg; cnu to = (cnu)data; cnu from; xmlnode output; if(x == NULL || to == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return; } from = (cnu)xmlnode_get_vattrib(x,"cnu"); if(j_strncmp(xmlnode_get_name(x),"presence",8) == 0) { output = add_extended_presence(from, to, x, NULL, NULL, NULL); con_user_send(to, from, output); } else { con_user_send(to, from, xmlnode_dup(x)); /* Need to send duplicate */ } } /* Browse for members of a room */ void con_room_browsewalk(gpointer key, gpointer data, gpointer arg) { jid userjid; cnu user = (cnu)data; xmlnode q = (xmlnode)arg; xmlnode xml; if(user == NULL || q == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return; } xml = xmlnode_insert_tag(q, "item"); userjid = jid_new(xmlnode_pool(xml), jid_full(user->room->id)); jid_set(userjid, xmlnode_get_data(user->nick), JID_RESOURCE); xmlnode_put_attrib(xml, "category", "user"); xmlnode_put_attrib(xml, "type", "client"); xmlnode_put_attrib(xml, "name", xmlnode_get_data(user->nick)); xmlnode_put_attrib(xml, "jid", jid_full(userjid)); } void _con_room_discoinfo(cnr room, jpacket jp) { xmlnode result; if(room == NULL) { log_warn(NAME, "[%s] Aborting - NULL room attribute found", FZONE); return; } jutil_iqresult(jp->x); xmlnode_put_attrib(xmlnode_insert_tag(jp->x,"query"), "xmlns", NS_DISCO_INFO); jpacket_reset(jp); result = xmlnode_insert_tag(jp->iq,"identity"); xmlnode_put_attrib(result, "category", "conference"); xmlnode_put_attrib(result, "type", "text"); xmlnode_put_attrib(result, "name", room->name); xmlnode_put_attrib(xmlnode_insert_tag(jp->iq, "feature"), "var", NS_MUC); xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", NS_DISCO); xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", NS_BROWSE); xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", NS_VERSION); xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", NS_LAST); xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", NS_TIME); xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", NS_VCARD); if(j_strlen(room->secret) > 0) xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_password"); else xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_unsecure"); if(room->public == 1) xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_public"); else xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_hidden"); if(room->persistent == 1) xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_persistent"); else xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_temporary"); if(room->invitation == 1) xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_membersonly"); else xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_open"); if(room->moderated == 1) xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_moderated"); else xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_unmoderated"); if(room->visible == 1) xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_nonanonymous"); else xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc_semianonymous"); if(room->legacy == 1) xmlnode_put_attrib(xmlnode_insert_tag(jp->iq,"feature"), "var", "muc-legacy"); deliver(dpacket_new(jp->x), NULL); return; } void _con_room_discoitem(gpointer key, gpointer data, gpointer arg) { jid userjid; cnu user = (cnu)data; xmlnode query = (xmlnode)arg; xmlnode xml; if(user == NULL || query == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return; } xml = xmlnode_insert_tag(query, "item"); userjid = jid_new(xmlnode_pool(xml), jid_full(user->room->id)); jid_set(userjid, xmlnode_get_data(user->nick), JID_RESOURCE); xmlnode_put_attrib(xml, "jid", jid_full(userjid)); } void con_room_outsider(cnr room, cnu from, jpacket jp) { xmlnode q; int start; char nstr[10]; time_t t; char *str; if(room == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found -%s- -%s-", FZONE, room); return; } log_debug(NAME, "[%s] handling request from outsider %s to room %s", FZONE, jid_full(jp->from), jid_full(room->id)); /* any presence here is just fluff */ if(jp->type == JPACKET_PRESENCE) { log_debug(NAME, "[%s] Dropping presence from outsider", FZONE); xmlnode_free(jp->x); return; } if(jp->type == JPACKET_MESSAGE) { log_debug(NAME, "[%s] Bouncing message from outsider", FZONE); jutil_error(jp->x, TERROR_FORBIDDEN); deliver(dpacket_new(jp->x), NULL); return; } /* public iq requests */ if(jpacket_subtype(jp) == JPACKET__SET) { if(NSCHECK(jp->iq, NS_MUC_OWNER)) { log_debug(NAME, "[%s] IQ Set for owner function", FZONE); if(from && is_owner(room, jp->from)) { xdata_room_config(room, from, room->locked, jp->x); jutil_iqresult(jp->x); deliver(dpacket_new(jp->x), NULL); return; } else { log_debug(NAME, "[%s] IQ Set for owner disallowed", FZONE); jutil_error(jp->x, TERROR_NOTALLOWED); deliver(dpacket_new(jp->x), NULL); return; } } else if(NSCHECK(jp->iq, NS_REGISTER)) { log_debug(NAME, "[%s] IQ Set for Registration function", FZONE); jutil_error(jp->x, TERROR_NOTALLOWED); deliver(dpacket_new(jp->x), NULL); return; } } if(jpacket_subtype(jp) == JPACKET__GET) { if(NSCHECK(jp->iq,NS_VERSION)) { jutil_iqresult(jp->x); xmlnode_put_attrib(xmlnode_insert_tag(jp->x,"query"),"xmlns",NS_VERSION); jpacket_reset(jp); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq,"name"),NAME,-1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq,"version"),VERSION,-1); deliver(dpacket_new(jp->x),NULL); return; } else if(NSCHECK(jp->iq, NS_BROWSE)) { jutil_iqresult(jp->x); q = xmlnode_insert_tag(jp->x,"item"); xmlnode_put_attrib(q,"category","conference"); if(room->public && room->invitation == 0) { xmlnode_put_attrib(q,"type","public"); g_hash_table_foreach(room->local, con_room_browsewalk, (void*)q); } else if(room->public && is_member(room, jp->from)) { xmlnode_put_attrib(q,"type","public"); g_hash_table_foreach(room->local, con_room_browsewalk, (void*)q); } else { xmlnode_put_attrib(q,"type","private"); } xmlnode_put_attrib(q,"xmlns",NS_BROWSE); xmlnode_put_attrib(q,"name",room->name); xmlnode_put_attrib(q,"version",VERSION); xmlnode_insert_cdata(xmlnode_insert_tag(q,"ns"),NS_MUC,-1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_DISCO, -1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_BROWSE, -1); /* xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_REGISTER, -1); */ xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_VERSION, -1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_LAST, -1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_TIME, -1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_VCARD, -1); deliver(dpacket_new(jp->x), NULL); return; } else if(NSCHECK(jp->iq, NS_DISCO_INFO)) { log_debug(NAME, "[%s] Outside room packet - Disco Info Request", FZONE); _con_room_discoinfo(room, jp); return; } else if(NSCHECK(jp->iq, NS_DISCO_ITEMS)) { log_debug(NAME, "[%s] Outside room packet - Disco Items Request", FZONE); jutil_iqresult(jp->x); xmlnode_put_attrib(xmlnode_insert_tag(jp->x,"query"), "xmlns", NS_DISCO_ITEMS); jpacket_reset(jp); deliver(dpacket_new(jp->x), NULL); return; } else if(NSCHECK(jp->iq, NS_LAST)) { log_debug(NAME, "[%s] Outside room packet - Last Request", FZONE); jutil_iqresult(jp->x); xmlnode_put_attrib(xmlnode_insert_tag(jp->x,"query"),"xmlns",NS_LAST); jpacket_reset(jp); start = time(NULL) - room->start; sprintf(nstr,"%d",start); xmlnode_put_attrib(jp->iq,"seconds", pstrdup(jp->p, nstr)); deliver(dpacket_new(jp->x),NULL); return; } else if(NSCHECK(jp->iq,NS_TIME)) { /* Compliant with JEP-0090 */ log_debug(NAME, "[%s] Server packet - Time Request", FZONE); jutil_iqresult(jp->x); xmlnode_put_attrib(xmlnode_insert_tag(jp->x, "query"), "xmlns", NS_TIME); jpacket_reset(jp); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "utc"), jutil_timestamp(), -1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "tz"), tzname[0], -1); /* create nice display time */ t = time(NULL); str = ctime(&t); str[strlen(str) - 1] = '\0'; /* cut off newline */ xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "display"), pstrdup(jp->p, str), -1); free(str); deliver(dpacket_new(jp->x),NULL); return; } else if(NSCHECK(jp->iq, NS_MUC_OWNER)) { if(j_strcmp(xmlnode_get_name(jp->iq),"query") == 0) { log_debug(NAME, "[%s] IQ Get for owner: configuration", FZONE); if(!is_owner(room, from->realid)) { jutil_error(jp->x, TERROR_BAD); deliver(dpacket_new(jp->x), NULL); return; } xdata_room_config(room, from, 0, jp->x); xmlnode_free(jp->x); return; } } else if(NSCHECK(jp->iq, NS_REGISTER)) { log_debug(NAME, "[%s] IQ Get for Registration function", FZONE); jutil_error(jp->x, TERROR_NOTALLOWED); deliver(dpacket_new(jp->x), NULL); return; } else if(NSCHECK(jp->iq,NS_VCARD)) { log_debug(NAME, "[%s] Outside room packet - VCard Request", FZONE); jutil_iqresult(jp->x); xmlnode_put_attrib(xmlnode_insert_tag(jp->x,"vCard"),"xmlns",NS_VCARD); jpacket_reset(jp); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "DESC"), room->description, -1); deliver(dpacket_new(jp->x),NULL); return; } } log_debug(NAME, "[%s] Sending Not Implemented", FZONE); jutil_error(jp->x, TERROR_NOTIMPL); deliver(dpacket_new(jp->x), NULL); return; } void con_room_process(cnr room, cnu from, jpacket jp) { char *nick = NULL; char *key; char *str; time_t t; int start; char nstr[10]; xmlnode result, item, x, node; jid id; if(room == NULL || from == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return; } log_debug(NAME, "[%s] handling request from participant %s(%s/%s) to room %s", FZONE, jid_full(from->realid), from->localid->resource, xmlnode_get_data(from->nick), jid_full(room->id)); /* presence is just stored and forwarded */ if(jp->type == JPACKET_PRESENCE) { xmlnode_free(from->presence); from->presence = xmlnode_dup(jp->x); jutil_delay(from->presence, NULL); xmlnode_put_vattrib(jp->x, "cnu", (void*)from); g_hash_table_foreach(room->local, con_room_sendwalk, (void*)jp->x); xmlnode_free(jp->x); return; } if(jp->type == JPACKET_MESSAGE) { if(NSCHECK(xmlnode_get_tag(jp->x,"x"),NS_MUC_USER)) { /* Invite */ log_debug(NAME, "[%s] Found invite request", FZONE); if(room->invitation == 1 && room->invites == 0 && !is_admin(room, from->realid)) { log_debug(NAME, "[%s] Forbidden invitation request, returning error", FZONE); jutil_error(jp->x,TERROR_FORBIDDEN); deliver(dpacket_new(jp->x),NULL); return; } item = xmlnode_dup(xmlnode_get_tag(jp->x,"x")); nick = xmlnode_get_attrib(xmlnode_get_tag(item, "invite"), "to"); if( nick == NULL) { log_debug(NAME, "[%s] No receipient, returning error", FZONE); jutil_error(jp->x,TERROR_BAD); deliver(dpacket_new(jp->x),NULL); xmlnode_free(item); return; } if(room->invitation == 1) { id = jid_new(xmlnode_pool(item), nick); key = j_strdup(jid_full(jid_user(jid_fix(id)))); g_hash_table_insert(room->member, key, (void*)item); } else { xmlnode_free(item); } con_room_send_invite(from, xmlnode_get_tag(jp->x,"x")); return; } /* if topic, store it */ if((x = xmlnode_get_tag(jp->x,"subject")) != NULL ) { /* catch the invite hack */ if((nick = xmlnode_get_data(x)) != NULL && j_strncasecmp(nick,"invite:",7) == 0) { nick += 7; if((jp->to = jid_new(jp->p, nick)) == NULL) { jutil_error(jp->x,TERROR_BAD); }else{ xmlnode_put_attrib(jp->x, "to", jid_full(jp->to)); jp->from = jid_new(jp->p, jid_full(jid_user(from->localid))); jid_set(jp->from, xmlnode_get_data(from->nick), JID_RESOURCE); xmlnode_put_attrib(jp->x, "from", jid_full(jp->from)); } deliver(dpacket_new(jp->x), NULL); return; } /* Disallow subject change if user does not have high enough level */ if((!is_admin(room, from->realid) && room->subjectlock == 0) || is_visitor(room, from->realid) ) { jutil_error(jp->x,TERROR_FORBIDDEN); deliver(dpacket_new(jp->x),NULL); return; } /* Save room topic for new users */ xmlnode_free(room->topic); room->topic = xmlnode_new_tag("topic"); xmlnode_put_attrib(room->topic, "subject", xmlnode_get_data(x)); xmlnode_insert_cdata(room->topic, xmlnode_get_data(from->nick), -1); xmlnode_insert_cdata(room->topic, " has set the topic to: ", -1); xmlnode_insert_cdata(room->topic, xmlnode_get_data(x), -1); /* Save the room topic if room is persistent */ if(room->persistent == 1) { xdb_room_set(room); } } /* Check if allowed to talk */ if(room->moderated == 1 && !is_participant(room, from->realid)) { /* Attempting to talk in a moderated room without voice intrinsic */ jutil_error(jp->x,TERROR_MUC_VOICE); deliver(dpacket_new(jp->x),NULL); return; } if(jp->subtype != JPACKET__GROUPCHAT) { jutil_error(jp->x, TERROR_BAD); deliver(dpacket_new(jp->x), NULL); return; } /* ensure type="groupchat" */ xmlnode_put_attrib(jp->x,"type","groupchat"); /* Save copy of packet for history */ node = xmlnode_dup(jp->x); /* broadcast */ xmlnode_put_vattrib(jp->x,"cnu",(void*)from); g_hash_table_foreach(room->local, con_room_sendwalk, (void*)jp->x); /* log */ con_room_log(room, xmlnode_get_data(from->nick), xmlnode_get_tag_data(jp->x, "body")); /* Save from address */ id = jid_new(xmlnode_pool(node), jid_full(from->localid)); jid_set(id, xmlnode_get_data(from->nick), JID_RESOURCE); xmlnode_put_attrib(node, "from", jid_full(id)); /* store in history */ jutil_delay(node, jid_full(room->id)); if(room->master->history > 0) { if(++room->hlast == room->master->history) room->hlast = 0; xmlnode_free(room->history[room->hlast]); room->history[room->hlast] = node; } else { xmlnode_free(node); } xmlnode_free(jp->x); return; } /* public iq requests */ if(jpacket_subtype(jp) == JPACKET__SET) { if(NSCHECK(jp->iq, NS_MUC_ADMIN)) { log_debug(NAME, "[%s] IQ Set for admin function: >%s<", FZONE, xmlnode_get_name(jp->iq)); if(!is_moderator(room, from->realid)) { jutil_error(jp->x, TERROR_FORBIDDEN); deliver(dpacket_new(jp->x), NULL); return; } if(j_strcmp(xmlnode_get_name(jp->iq), "query") == 0) { log_debug(NAME, "[%s] List set requested by admin...", FZONE); result = xmlnode_get_tag(jp->x, "query"); if(NSCHECK(xmlnode_get_tag(result,"x"),NS_DATA)) { log_debug(NAME, "[%s] Received x:data", FZONE); jutil_error(jp->x, TERROR_BAD); deliver(dpacket_new(jp->x), NULL); return; } else { con_parse_item(from, jp); return; } } } else if(NSCHECK(jp->iq, NS_MUC_OWNER)) { if(!is_owner(room, from->realid)) { jutil_error(jp->x, TERROR_NOTALLOWED); deliver(dpacket_new(jp->x), NULL); return; } if(j_strcmp(xmlnode_get_name(jp->iq),"query") == 0) { result = xmlnode_get_tag(jp->x, "query"); node = xmlnode_get_tag(result, "destroy"); if(node) { log_debug(NAME, "[%s] IQ Set for owner: destroy requested", FZONE); /* Remove old xdb registration */ if(room->persistent == 1) { xdb_room_clear(room); } /* inform everyone they have left the room */ g_hash_table_foreach(room->remote, con_room_leaveall, node); con_room_zap(room); jutil_iqresult(jp->x); deliver(dpacket_new(jp->x), NULL); return; } else if(NSCHECK(xmlnode_get_tag(result,"x"),NS_DATA)) { log_debug(NAME, "[%s] Received x:data", FZONE); xdata_handler(room, from, jp); jutil_iqresult(jp->x); deliver(dpacket_new(jp->x), NULL); return; } else { log_debug(NAME, "[%s] IQ Set for owner: configuration set", FZONE); con_parse_item(from, jp); return; } } else { jutil_error(jp->x, TERROR_BAD); deliver(dpacket_new(jp->x), NULL); return; } } else if(NSCHECK(jp->iq, NS_REGISTER)) { log_debug(NAME, "[%s] IQ Set for Registration function", FZONE); jutil_error(jp->x, TERROR_NOTALLOWED); deliver(dpacket_new(jp->x), NULL); return; } jutil_error(jp->x, TERROR_BAD); deliver(dpacket_new(jp->x), NULL); return; } if(jpacket_subtype(jp) == JPACKET__GET) { if(NSCHECK(jp->iq,NS_VERSION)) { jutil_iqresult(jp->x); xmlnode_put_attrib(xmlnode_insert_tag(jp->x,"query"),"xmlns",NS_VERSION); jpacket_reset(jp); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq,"name"),NAME,-1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq,"version"),VERSION,-1); deliver(dpacket_new(jp->x),NULL); return; } else if(NSCHECK(jp->iq, NS_BROWSE)) { jutil_iqresult(jp->x); result = xmlnode_insert_tag(jp->x,"item"); xmlnode_put_attrib(result,"category", "conference"); xmlnode_put_attrib(result,"xmlns", NS_BROWSE); xmlnode_put_attrib(result,"name", room->name); if(room->public) { xmlnode_put_attrib(result,"type", "public"); } else { xmlnode_put_attrib(result,"type", "private"); } xmlnode_insert_cdata(xmlnode_insert_tag(result,"ns"),NS_MUC,-1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_DISCO, -1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_BROWSE, -1); /* xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_REGISTER, -1); */ xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_VERSION, -1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_LAST, -1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_TIME, -1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "ns"), NS_VCARD, -1); g_hash_table_foreach(room->local, con_room_browsewalk, (void*)result); deliver(dpacket_new(jp->x), NULL); return; } else if(NSCHECK(jp->iq, NS_DISCO_INFO)) { log_debug(NAME, "[%s] room packet - Disco Info Request", FZONE); _con_room_discoinfo(room, jp); return; } else if(NSCHECK(jp->iq, NS_DISCO_ITEMS)) { log_debug(NAME, "[%s] room packet - Disco Items Request", FZONE); jutil_iqresult(jp->x); result = xmlnode_insert_tag(jp->x, "query"); xmlnode_put_attrib(result, "xmlns", NS_DISCO_ITEMS); g_hash_table_foreach(room->local, _con_room_discoitem, (void*)result); deliver(dpacket_new(jp->x), NULL); return; } else if(NSCHECK(jp->iq, NS_LAST)) { log_debug(NAME, "[%s] room packet - Last Request", FZONE); jutil_iqresult(jp->x); xmlnode_put_attrib(xmlnode_insert_tag(jp->x,"query"),"xmlns", NS_LAST); jpacket_reset(jp); start = time(NULL) - room->start; sprintf(nstr,"%d",start); xmlnode_put_attrib(jp->iq,"seconds", pstrdup(jp->p, nstr)); deliver(dpacket_new(jp->x),NULL); return; } else if(NSCHECK(jp->iq,NS_TIME)) { /* Compliant with JEP-0090 */ log_debug(NAME, "[%s] Server packet - Time Request", FZONE); jutil_iqresult(jp->x); xmlnode_put_attrib(xmlnode_insert_tag(jp->x, "query"), "xmlns", NS_TIME); jpacket_reset(jp); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "utc"), jutil_timestamp(), -1); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "tz"), tzname[0], -1); /* create nice display time */ t = time(NULL); str = ctime(&t); str[strlen(str) - 1] = '\0'; /* cut off newline */ xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "display"), pstrdup(jp->p, str), -1); deliver(dpacket_new(jp->x),NULL); return; } else if(NSCHECK(jp->iq, NS_MUC_ADMIN)) { log_debug(NAME, "[%s] IQ Get for admin function, %s", FZONE, xmlnode_get_name(jp->iq)); if(!is_moderator(room, from->realid)) { jutil_error(jp->x, TERROR_FORBIDDEN); deliver(dpacket_new(jp->x), NULL); return; } if(j_strcmp(xmlnode_get_name(jp->iq),"query") == 0) { con_parse_item(from, jp); return; } } else if(NSCHECK(jp->iq, NS_MUC_OWNER)) { log_debug(NAME, "[%s] IQ Get for owner function, %s", FZONE, xmlnode_get_name(jp->iq)); if(!is_owner(room, from->realid)) { jutil_error(jp->x, TERROR_FORBIDDEN); deliver(dpacket_new(jp->x), NULL); return; } if(j_strcmp(xmlnode_get_name(jp->iq),"query") == 0) { con_parse_item(from, jp); return; } } else if(NSCHECK(jp->iq, NS_REGISTER)) { log_debug(NAME, "[%s] IQ Get for Registration function", FZONE); jutil_error(jp->x, TERROR_NOTALLOWED); deliver(dpacket_new(jp->x), NULL); return; } else if(NSCHECK(jp->iq,NS_VCARD)) { log_debug(NAME, "[%s] room packet - VCard Request", FZONE); jutil_iqresult(jp->x); xmlnode_put_attrib(xmlnode_insert_tag(jp->x, "vCard"), "xmlns", NS_VCARD); jpacket_reset(jp); xmlnode_insert_cdata(xmlnode_insert_tag(jp->iq, "DESC"), room->description, -1); deliver(dpacket_new(jp->x),NULL); return; } } log_debug(NAME, "[%s] Sending Bad Request", FZONE); jutil_error(jp->x, TERROR_BAD); deliver(dpacket_new(jp->x), NULL); return; } cnr con_room_new(cni master, jid roomid, jid owner, char *name, char *secret, int private, int xdata, int persist) { cnr room; pool p; cnu admin; char *key; time_t now = time(NULL); /* Create pool for room struct */ p = pool_new(); room = pmalloco(p, sizeof(_cnr)); log_debug(NAME, "[%s] Malloc: _cnr = %d", FZONE, sizeof(_cnr)); room->p = p; room->master = master; /* room jid */ room->id = jid_new(p, jid_full(jid_fix(roomid))); /* room natural language name for browse/disco */ if(name) room->name = j_strdup(name); else room->name = j_strdup(room->id->user); /* room password */ room->secret = j_strdup(secret); room->private = private; /* Initialise room history */ room->history = pmalloco(p, sizeof(xmlnode) * master->history); /* make array of xmlnodes */ log_debug(NAME, "[%s] Malloc: history = %d", FZONE, sizeof(xmlnode) * master->history); /* Room time */ room->start = now; room->created = now; /* user hashes */ room->remote = g_hash_table_new_full(g_str_hash,g_str_equal, ght_remove_key, ght_remove_cnu); room->local = g_hash_table_new_full(g_str_hash,g_str_equal, ght_remove_key, NULL); room->roster = g_hash_table_new_full(g_str_hash,g_str_equal, ght_remove_key, ght_remove_xmlnode); /* Affiliated hashes */ room->owner = g_hash_table_new_full(g_str_hash,g_str_equal, ght_remove_key, ght_remove_xmlnode); room->admin = g_hash_table_new_full(g_str_hash,g_str_equal, ght_remove_key, ght_remove_xmlnode); room->member = g_hash_table_new_full(g_str_hash,g_str_equal, ght_remove_key, ght_remove_xmlnode); room->outcast = g_hash_table_new_full(g_str_hash,g_str_equal, ght_remove_key, ght_remove_xmlnode); /* Role hashes */ room->moderator = g_hash_table_new_full(g_str_hash,g_str_equal, ght_remove_key, NULL); room->participant = g_hash_table_new_full(g_str_hash,g_str_equal, ght_remove_key, NULL); /* Room messages */ room->note_leave = j_strdup(xmlnode_get_tag_data(master->config,"notice/leave")); room->note_join = j_strdup(xmlnode_get_tag_data(master->config,"notice/join")); room->note_rename = j_strdup(xmlnode_get_tag_data(master->config,"notice/rename")); /* Room Defaults */ room->public = master->public; room->subjectlock = 0; room->maxusers = 0; room->persistent = persist; room->moderated = 0; room->defaulttype = 0; room->privmsg = 0; room->invitation = 0; room->invites = 0; room->legacy = 0; room->visible = 1; room->logfile = NULL; room->logformat = LOG_TEXT; room->description = j_strdup(room->name); /* Assign owner to room */ if(owner != NULL) { admin = (void*)con_user_new(room, owner); add_roster(room, admin->realid); room->creator = jid_new(room->p, jid_full(jid_user(admin->realid))); add_affiliate(room->owner, admin->realid, NULL); if(xdata > 0 ) xdata_room_config(room,admin,1,NULL); log_debug(NAME, "[%s] Added new admin: %s to room %s", FZONE, jid_full(jid_fix(owner)), jid_full(room->id)); } key = j_strdup(jid_full(room->id)); g_hash_table_insert(master->rooms, key, (void*)room); log_debug(NAME,"[%s] new room %s (%s/%s/%d)", FZONE, jid_full(room->id),name,secret,private); /* Save room configuration if persistent */ if(room->persistent == 1) { xdb_room_set(room); } return room; } void _con_room_send(gpointer key, gpointer data, gpointer arg) { cnu user = (cnu)data; xmlnode x = (xmlnode)arg; xmlnode output; if(user == NULL || x == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return; } output = xmlnode_dup((xmlnode)x); xmlnode_put_attrib(output, "to", jid_full(user->realid)); deliver(dpacket_new(output), NULL); return; } void _con_room_send_legacy(gpointer key, gpointer data, gpointer arg) { cnu user = (cnu)data; xmlnode x = (xmlnode)arg; xmlnode output; if(user == NULL || x == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return; } output = xmlnode_dup((xmlnode)x); if(!is_legacy(user)) { xmlnode_free(output); return; } xmlnode_put_attrib(output, "to", jid_full(user->realid)); deliver(dpacket_new(output), NULL); return; } void con_room_send(cnr room, xmlnode x, int legacy) { if(room == NULL || x == NULL) { log_warn(NAME, "[%s] Aborting - NULL attribute found", FZONE); return; } log_debug(NAME,"[%s] Sending packet from room %s: %s", FZONE, jid_full(room->id), xmlnode2str(x)); /* log */ con_room_log(room, NULL, xmlnode_get_tag_data(x, "body")); xmlnode_put_attrib(x, "from", jid_full(room->id)); deliver__flag = 0; if(legacy) g_hash_table_foreach(room->local, _con_room_send_legacy, (void*)x); else g_hash_table_foreach(room->local, _con_room_send, (void*)x); deliver__flag = 1; deliver(NULL, NULL); xmlnode_free(x); return; } /* Clear up room hashes */ void con_room_cleanup(cnr room) { char *roomid; if(room == NULL) { log_warn(NAME, "[%s] Aborting - NULL room attribute found", FZONE); return; } roomid = j_strdup(jid_full(room->id)); log_debug(NAME, "[%s] cleaning room %s", FZONE, roomid); /* Clear old hashes */ log_debug(NAME, "[%s] zapping list remote in room %s", FZONE, roomid); g_hash_table_destroy(room->remote); log_debug(NAME, "[%s] zapping list local in room %s", FZONE, roomid); g_hash_table_destroy(room->local); log_debug(NAME, "[%s] zapping list roster in room %s", FZONE, roomid); g_hash_table_destroy(room->roster); log_debug(NAME, "[%s] zapping list owner in room %s", FZONE, roomid); g_hash_table_destroy(room->owner); log_debug(NAME, "[%s] zapping list admin in room %s", FZONE, roomid); g_hash_table_destroy(room->admin); log_debug(NAME, "[%s] zapping list member in room %s", FZONE, roomid); g_hash_table_destroy(room->member); log_debug(NAME, "[%s] zapping list outcast in room %s", FZONE, roomid); g_hash_table_destroy(room->outcast); log_debug(NAME, "[%s] zapping list moderator in room %s", FZONE, roomid); g_hash_table_destroy(room->moderator); log_debug(NAME, "[%s] zapping list participant in room %s", FZONE, roomid); g_hash_table_destroy(room->participant); log_debug(NAME, "[%s] closing room log in room %s", FZONE, roomid); if(room->logfile && room->logfile != NULL) fclose(room->logfile); log_debug(NAME, "[%s] Clearing any history in room %s", FZONE, roomid); con_room_history_clear(room); log_debug(NAME, "[%s] Clearing topic in room %s", FZONE, roomid); xmlnode_free(room->topic); log_debug(NAME, "[%s] Clearing strings and legacy messages in room %s", FZONE, roomid); free(room->name); free(room->description); free(room->secret); free(room->note_join); free(room->note_rename); free(room->note_leave); free(roomid); return; } /* Zap room entry */ void con_room_zap(cnr room) { if(room == NULL) { log_warn(NAME, "[%s] Aborting - NULL room attribute found", FZONE); return; } log_debug(NAME, "[%s] cleaning up room %s", FZONE, jid_full(room->id)); con_room_cleanup(room); log_debug(NAME, "[%s] zapping room %s from list", FZONE, jid_full(room->id)); g_hash_table_remove(room->master->rooms, jid_full(room->id)); return; } void con_room_history_clear(cnr room) { int h; if(room->master->history > 0) { h = room->hlast; while(1) { h++; if(h == room->master->history) h = 0; xmlnode_free(room->history[h]); if(h == room->hlast) break; } } }