/* * xmpp.c -- Jabber protocol handling * * Copyright (C) 2008-2014 Frank Zschockelt <mcabber@freakysoft.de> * Copyright (C) 2005-2015 Mikael Berthe <mikael@lilotux.net> * Parts come from the centericq project: * Copyright (C) 2002-2005 by Konstantin Klyagin <konst@konst.org.ua> * * 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; if not, see <http://www.gnu.org/licenses/>. */#include<stdlib.h>#include<string.h>#include"xmpp.h"#include"xmpp_helper.h"#include"xmpp_iq.h"#include"xmpp_iqrequest.h"#include"xmpp_muc.h"#include"xmpp_s10n.h"#include"caps.h"#include"events.h"#include"histolog.h"#include"hooks.h"#include"otr.h"#include"roster.h"#include"screen.h"#include"settings.h"#include"utils.h"#include"main.h"#include"carbons.h"#define RECONNECTION_TIMEOUT 60L#ifndef LOUDMOUTH_USES_SHA256#define FINGERPRINT_LENGTH 16 // old loudmouth still uses MD5 :(#endifLmConnection*lconnection=NULL;staticguintAutoConnection;inlinevoidupdate_last_use(void);inlinegbooleanxmpp_reconnect();enumimstatusmystatus=offline;staticenumimstatusmywantedstatus=available;gchar*mystatusmsg;charimstatus2char[imstatus_size+1]={'_','o','f','d','n','a','i','\0'};char*imstatus_showmap[]={"","","chat","dnd","xa","away",""};LmMessageNode*bookmarks=NULL;LmMessageNode*rosternotes=NULL;staticstructIqHandlers{constgchar*xmlns;LmHandleMessageFunctionhandler;}iq_handlers[]={{NS_PING,&handle_iq_ping},{NS_VERSION,&handle_iq_version},{NS_TIME,&handle_iq_time},{NS_ROSTER,&handle_iq_roster},{NS_XMPP_TIME,&handle_iq_time202},{NS_LAST,&handle_iq_last},{NS_DISCO_INFO,&handle_iq_disco_info},{NS_DISCO_ITEMS,&handle_iq_disco_items},{NS_COMMANDS,&handle_iq_commands},{NS_VCARD,&handle_iq_vcard},{NULL,NULL}};voidupdate_last_use(void){iqlast=time(NULL);}gbooleanxmpp_is_online(void){if(lconnection&&lm_connection_is_authenticated(lconnection))returnTRUE;elsereturnFALSE;}// Note: the caller should check the jid is correctvoidxmpp_addbuddy(constchar*bjid,constchar*name,constchar*group){LmMessageNode*query,*y;LmMessage*iq;LmMessageHandler*handler;char*cleanjid;if(!xmpp_is_online())return;cleanjid=jidtodisp(bjid);// Stripping resource, just in case...// We don't check if the jabber user already exists in the roster,// because it allows to re-ask for notification.iq=lm_message_new_with_sub_type(NULL,LM_MESSAGE_TYPE_IQ,LM_MESSAGE_SUB_TYPE_SET);query=lm_message_node_add_child(iq->node,"query",NULL);lm_message_node_set_attribute(query,"xmlns",NS_ROSTER);y=lm_message_node_add_child(query,"item",NULL);lm_message_node_set_attribute(y,"jid",cleanjid);if(name)lm_message_node_set_attribute(y,"name",name);if(group)lm_message_node_add_child(y,"group",group);handler=lm_message_handler_new(handle_iq_dummy,NULL,FALSE);lm_connection_send_with_reply(lconnection,iq,handler,NULL);lm_message_handler_unref(handler);lm_message_unref(iq);xmpp_send_s10n(cleanjid,LM_MESSAGE_SUB_TYPE_SUBSCRIBE);roster_add_user(cleanjid,name,group,ROSTER_TYPE_USER,sub_pending,-1);g_free(cleanjid);buddylist_defer_build();scr_update_roster();}voidxmpp_updatebuddy(constchar*bjid,constchar*name,constchar*group){LmMessage*iq;LmMessageHandler*handler;LmMessageNode*x;char*cleanjid;if(!xmpp_is_online())return;// XXX We should check name's and group's correctnesscleanjid=jidtodisp(bjid);// Stripping resource, just in case...iq=lm_message_new_with_sub_type(NULL,LM_MESSAGE_TYPE_IQ,LM_MESSAGE_SUB_TYPE_SET);x=lm_message_node_add_child(iq->node,"query",NULL);lm_message_node_set_attribute(x,"xmlns",NS_ROSTER);x=lm_message_node_add_child(x,"item",NULL);lm_message_node_set_attribute(x,"jid",cleanjid);if(name)lm_message_node_set_attribute(x,"name",name);if(group)lm_message_node_add_child(x,"group",group);handler=lm_message_handler_new(handle_iq_dummy,NULL,FALSE);lm_connection_send_with_reply(lconnection,iq,handler,NULL);lm_message_handler_unref(handler);lm_message_unref(iq);g_free(cleanjid);}voidxmpp_delbuddy(constchar*bjid){LmMessageNode*y,*z;LmMessage*iq;LmMessageHandler*handler;char*cleanjid;if(!xmpp_is_online())return;cleanjid=jidtodisp(bjid);// Stripping resource, just in case...// If the current buddy is an agent, unsubscribe from itif(roster_gettype(cleanjid)==ROSTER_TYPE_AGENT){scr_LogPrint(LPRINT_LOGNORM,"Unregistering from the %s agent",cleanjid);iq=lm_message_new_with_sub_type(cleanjid,LM_MESSAGE_TYPE_IQ,LM_MESSAGE_SUB_TYPE_SET);y=lm_message_node_add_child(iq->node,"query",NULL);lm_message_node_set_attribute(y,"xmlns",NS_REGISTER);lm_message_node_add_child(y,"remove",NULL);handler=lm_message_handler_new(handle_iq_dummy,NULL,FALSE);lm_connection_send_with_reply(lconnection,iq,handler,NULL);lm_message_handler_unref(handler);lm_message_unref(iq);}// Cancel the subscriptionsxmpp_send_s10n(cleanjid,LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED);// cancel "from"xmpp_send_s10n(cleanjid,LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE);// cancel "to"// Ask for removal from rosteriq=lm_message_new_with_sub_type(NULL,LM_MESSAGE_TYPE_IQ,LM_MESSAGE_SUB_TYPE_SET);y=lm_message_node_add_child(iq->node,"query",NULL);lm_message_node_set_attribute(y,"xmlns",NS_ROSTER);z=lm_message_node_add_child(y,"item",NULL);lm_message_node_set_attributes(z,"jid",cleanjid,"subscription","remove",NULL);handler=lm_message_handler_new(handle_iq_dummy,NULL,FALSE);lm_connection_send_with_reply(lconnection,iq,handler,NULL);lm_message_handler_unref(handler);lm_message_unref(iq);roster_del_user(cleanjid);g_free(cleanjid);buddylist_defer_build();scr_update_roster();}voidxmpp_request(constchar*fjid,enumiqreq_typereqtype){GSList*resources,*p_res;GSList*roster_elt;constchar*strreqtype,*xmlns;gbooleanvcard2user;if(reqtype==iqreq_version){xmlns=NS_VERSION;strreqtype="version";}elseif(reqtype==iqreq_time){xmlns=NS_XMPP_TIME;strreqtype="time";}elseif(reqtype==iqreq_last){xmlns=NS_LAST;strreqtype="last";}elseif(reqtype==iqreq_ping){xmlns=NS_PING;strreqtype="ping";}elseif(reqtype==iqreq_vcard){xmlns=NS_VCARD;strreqtype="vCard";}elsereturn;// Is it a vCard request to a regular user?// (vCard requests are sent to bare JIDs, except in MUC rooms...)vcard2user=(reqtype==iqreq_vcard&&!roster_find(fjid,jidsearch,ROSTER_TYPE_ROOM));if(strchr(fjid,JID_RESOURCE_SEPARATOR)||vcard2user){// This is a full JID or a vCard request to a contactxmpp_iq_request(fjid,xmlns);scr_LogPrint(LPRINT_NORMAL,"Sent %s request to <%s>",strreqtype,fjid);return;}// The resource has not been specifiedroster_elt=roster_find(fjid,jidsearch,ROSTER_TYPE_USER|ROSTER_TYPE_ROOM);if(!roster_elt){scr_LogPrint(LPRINT_NORMAL,"No known resource for <%s>...",fjid);xmpp_iq_request(fjid,xmlns);// Let's send a request anyway...scr_LogPrint(LPRINT_NORMAL,"Sent %s request to <%s>",strreqtype,fjid);return;}// Send a request to each resourceresources=buddy_getresources(roster_elt->data);if(!resources){scr_LogPrint(LPRINT_NORMAL,"No known resource for <%s>...",fjid);xmpp_iq_request(fjid,xmlns);// Let's send a request anyway...scr_LogPrint(LPRINT_NORMAL,"Sent %s request to <%s>",strreqtype,fjid);}for(p_res=resources;p_res;p_res=g_slist_next(p_res)){gchar*fulljid;fulljid=g_strdup_printf("%s/%s",fjid,(char*)p_res->data);xmpp_iq_request(fulljid,xmlns);scr_LogPrint(LPRINT_NORMAL,"Sent %s request to <%s>",strreqtype,fulljid);g_free(fulljid);g_free(p_res->data);}g_slist_free(resources);}// xmpp_send_msg(jid, text, type, subject,// otrinject, *encrypted, type_overwrite, *xep184)// When encrypted is not NULL, the function set *encrypted to 1 if the// message has been PGP (or OTR) -encrypted. If encryption enforcement is set// and encryption fails, *encrypted is set to -1.// otrinject should be set to FALSE (unless the message already has an OTR// payload, i.e. if the function is called from an otr.c routine).voidxmpp_send_msg(constchar*fjid,constchar*text,inttype,constchar*subject,gbooleanotrinject,gint*encrypted,LmMessageSubTypetype_overwrite,gpointer*xep184){LmMessage*x;LmMessageSubTypesubtype;#ifdef HAVE_LIBOTRintotr_msg=0;char*otr_msg_string=NULL;#endifchar*barejid;#if defined HAVE_GPGME || defined XEP0085char*rname;GSList*sl_buddy;#endif#ifdef XEP0085LmMessageNode*event;structxep0085*xep85=NULL;#endifgchar*enc=NULL;if(encrypted)*encrypted=0;if(!xmpp_is_online())return;if(!text&&type==ROSTER_TYPE_USER)return;if(type_overwrite!=LM_MESSAGE_SUB_TYPE_NOT_SET)subtype=type_overwrite;else{if(type==ROSTER_TYPE_ROOM)subtype=LM_MESSAGE_SUB_TYPE_GROUPCHAT;elsesubtype=LM_MESSAGE_SUB_TYPE_CHAT;}barejid=jidtodisp(fjid);#if defined HAVE_GPGME || defined HAVE_LIBOTR || defined XEP0085rname=strchr(fjid,JID_RESOURCE_SEPARATOR);sl_buddy=roster_find(barejid,jidsearch,ROSTER_TYPE_USER);// If we can get a resource name, we use it. Else we use NULL,// which hopefully will give us the most likely resource.if(rname)rname++;#ifdef HAVE_LIBOTRif(otr_enabled()&&!otrinject){if(type==ROSTER_TYPE_USER){otr_msg_string=otr_send(text,barejid,&otr_msg);if(!otr_msg_string){if(encrypted)*encrypted=-1;gotoxmpp_send_msg_return;}text=otr_msg_string;}if(otr_msg&&encrypted)*encrypted=ENCRYPTED_OTR;}#endif#ifdef HAVE_GPGMEif(type==ROSTER_TYPE_USER&&sl_buddy&&gpg_enabled()){if(!settings_pgp_getdisabled(barejid)){// not disabled for this contact?guintforce;structpgp_data*res_pgpdata;force=settings_pgp_getforce(barejid);res_pgpdata=buddy_resource_pgp(sl_buddy->data,rname);if(force||(res_pgpdata&&res_pgpdata->sign_keyid)){/* Remote client has PGP support (we have a signature) * OR encryption is enforced (force = TRUE). * If the contact has a specific KeyId, we'll use it; * if not, we'll use the key used for the signature. * Both keys should match, in theory (cf. XEP-0027). */constchar*key;key=settings_pgp_getkeyid(barejid);if(!key&&res_pgpdata)key=res_pgpdata->sign_keyid;if(key){intnkeys=1;constchar*keys[]={key,0};if(carbons_enabled())keys[nkeys++]=gpg_get_private_key_id();enc=gpg_encrypt(text,keys,nkeys);}if(!enc&&force){if(encrypted)*encrypted=-1;gotoxmpp_send_msg_return;}}}}#endif // HAVE_GPGME#endif // HAVE_GPGME || defined XEP0085x=lm_message_new_with_sub_type(fjid,LM_MESSAGE_TYPE_MESSAGE,subtype);lm_message_node_add_child(x->node,"body",enc?"This message is PGP-encrypted.":text);if(subject)lm_message_node_add_child(x->node,"subject",subject);if(enc){LmMessageNode*y;y=lm_message_node_add_child(x->node,"x",enc);lm_message_node_set_attribute(y,"xmlns",NS_ENCRYPTED);if(encrypted)*encrypted=ENCRYPTED_PGP;g_free(enc);}#ifdef HAVE_LIBOTR// We probably don't want Carbons for encrypted messages, since the other// resources won't be able to decrypt them.if(otr_msg&&carbons_enabled())lm_message_node_add_child(x->node,"private",NS_CARBONS_2);#endif// XEP-0184: Message Receiptsif(sl_buddy&&xep184&&caps_has_feature(buddy_resource_getcaps(sl_buddy->data,rname),NS_RECEIPTS,barejid)){lm_message_node_set_attribute(lm_message_node_add_child(x->node,"request",NULL),"xmlns",NS_RECEIPTS);*xep184=g_strdup(lm_message_get_id(x));}#ifdef XEP0085// If typing notifications are disabled, we can skip all this stuff...if(chatstates_disabled||type==ROSTER_TYPE_ROOM)gotoxmpp_send_msg_no_chatstates;if(sl_buddy)xep85=buddy_resource_xep85(sl_buddy->data,rname);/* XEP-0085 5.1 * "Until receiving a reply to the initial content message (or a standalone * notification) from the Contact, the User MUST NOT send subsequent chat * state notifications to the Contact." * In our implementation support is initially "unknown", then it's "probed" * and can become "ok". */if(xep85&&(xep85->support==CHATSTATES_SUPPORT_OK||xep85->support==CHATSTATES_SUPPORT_UNKNOWN)){event=lm_message_node_add_child(x->node,"active",NULL);lm_message_node_set_attribute(event,"xmlns",NS_CHATSTATES);if(xep85->support==CHATSTATES_SUPPORT_UNKNOWN)xep85->support=CHATSTATES_SUPPORT_PROBED;xep85->last_state_sent=ROSTER_EVENT_ACTIVE;}#endifxmpp_send_msg_no_chatstates:#ifdef WITH_DEPRECATED_STATUS_INVISIBLEif(mystatus!=invisible)#endifupdate_last_use();lm_connection_send(lconnection,x,NULL);lm_message_unref(x);xmpp_send_msg_return:#ifdef HAVE_LIBOTRg_free(otr_msg_string);#endifg_free(barejid);}#ifdef XEP0085// xmpp_send_xep85_chatstate()// Send a XEP-85 chatstate.staticvoidxmpp_send_xep85_chatstate(constchar*bjid,constchar*resname,guintstate){LmMessage*m;LmMessageNode*event;GSList*sl_buddy;constchar*chattag;char*rjid,*fjid=NULL;structxep0085*xep85=NULL;if(!xmpp_is_online())return;sl_buddy=roster_find(bjid,jidsearch,ROSTER_TYPE_USER);// If we have a resource name, we use it. Else we use NULL,// which hopefully will give us the most likely resource.if(sl_buddy)xep85=buddy_resource_xep85(sl_buddy->data,resname);if(!xep85||(xep85->support!=CHATSTATES_SUPPORT_OK))return;if(state==xep85->last_state_sent)return;if(state==ROSTER_EVENT_ACTIVE)chattag="active";elseif(state==ROSTER_EVENT_COMPOSING)chattag="composing";elseif(state==ROSTER_EVENT_PAUSED)chattag="paused";else{scr_LogPrint(LPRINT_LOGNORM,"Error: unsupported XEP-85 state (%d)",state);return;}xep85->last_state_sent=state;if(resname)fjid=g_strdup_printf("%s/%s",bjid,resname);rjid=resname?fjid:(char*)bjid;m=lm_message_new_with_sub_type(rjid,LM_MESSAGE_TYPE_MESSAGE,LM_MESSAGE_SUB_TYPE_CHAT);event=lm_message_node_add_child(m->node,chattag,NULL);lm_message_node_set_attribute(event,"xmlns",NS_CHATSTATES);lm_connection_send(lconnection,m,NULL);lm_message_unref(m);g_free(fjid);}#endif// xmpp_send_chatstate(buddy, state)// Send a chatstate or event (XEP-22/85) according to the buddy's capabilities.// The message is sent to one of the resources with the highest priority.#if defined XEP0085voidxmpp_send_chatstate(gpointerbuddy,guintchatstate){constchar*bjid;constchar*activeres;GSList*resources,*p_res,*p_next;structxep0085*xep85=NULL;bjid=buddy_getjid(buddy);if(!bjid)return;activeres=buddy_getactiveresource(buddy);/* Send the chatstate to the last resource (which should have the highest priority). If chatstate is "active", send an "active" state to all resources which do not curently have this state. */resources=buddy_getresources(buddy);for(p_res=resources;p_res;p_res=p_next){p_next=g_slist_next(p_res);xep85=buddy_resource_xep85(buddy,p_res->data);if(xep85&&xep85->support==CHATSTATES_SUPPORT_OK){// If p_next is NULL, this is the highest (prio) resource, i.e.// the one we are probably writing to - unless there is defined an// active resourceif(!g_strcmp0(p_res->data,activeres)||(!p_next&&!activeres)||(xep85->last_state_sent!=ROSTER_EVENT_ACTIVE&&chatstate==ROSTER_EVENT_ACTIVE))xmpp_send_xep85_chatstate(bjid,p_res->data,chatstate);}g_free(p_res->data);}g_slist_free(resources);// If the last resource had chatstates support when can return now,// we don't want to send a XEP22 event.if(xep85&&xep85->support==CHATSTATES_SUPPORT_OK)return;}#endif// chatstates_reset_probed(fulljid)// If the XEP has been probed for this contact, set it back to unknown so// that we probe it again. The parameter must be a full jid (w/ resource).#if defined XEP0085staticvoidchatstates_reset_probed(constchar*fulljid){char*rname,*barejid;GSList*sl_buddy;structxep0085*xep85;rname=strchr(fulljid,JID_RESOURCE_SEPARATOR);if(!rname++)return;barejid=jidtodisp(fulljid);sl_buddy=roster_find(barejid,jidsearch,ROSTER_TYPE_USER);g_free(barejid);if(!sl_buddy)return;xep85=buddy_resource_xep85(sl_buddy->data,rname);if(xep85&&xep85->support==CHATSTATES_SUPPORT_PROBED)xep85->support=CHATSTATES_SUPPORT_UNKNOWN;}#endif#ifdef HAVE_GPGME// keys_mismatch(key, expectedkey)// Return TRUE if both keys are non-null and "expectedkey" doesn't match// the end of "key".// If one of the keys is null, return FALSE.// If expectedkey is less than 8 bytes long, return TRUE.//// Example: keys_mismatch("C9940A9BB0B92210", "B0B92210") will return FALSE.staticboolkeys_mismatch(constchar*key,constchar*expectedkey){intlk,lek;if(!expectedkey||!key)returnFALSE;lk=strlen(key);lek=strlen(expectedkey);// If the expectedkey is less than 8 bytes long, this is probably a// user mistake so we consider it's a mismatch.if(lek<8)returnTRUE;if(lek<lk)key+=lk-lek;returnstrcasecmp(key,expectedkey);}#endif// check_signature(barejid, resourcename, xmldata, text)// Verify the signature (in xmldata) of "text" for the contact// barejid/resourcename.// xmldata is the 'jabber:x:signed' stanza.// If the key id is found, the contact's PGP data are updated.staticvoidcheck_signature(constchar*barejid,constchar*rname,LmMessageNode*node,constchar*text){#ifdef HAVE_GPGMEconstchar*p,*key;GSList*sl_buddy;structpgp_data*res_pgpdata;gpgme_sigsum_tsigsum;// All parameters must be validif(!(node&&barejid&&rname&&text))return;if(!gpg_enabled())return;// Get the resource PGP data structuresl_buddy=roster_find(barejid,jidsearch,ROSTER_TYPE_USER);if(!sl_buddy)return;res_pgpdata=buddy_resource_pgp(sl_buddy->data,rname);if(!res_pgpdata)return;if(!node->name||strcmp(node->name,"x"))// XXX: probably uselessreturn;// We expect "<x xmlns='jabber:x:signed'>"// Get signaturep=lm_message_node_get_value(node);if(!p)return;key=gpg_verify(p,text,&sigsum);if(key){constchar*expectedkey;char*buf;g_free(res_pgpdata->sign_keyid);res_pgpdata->sign_keyid=(char*)key;res_pgpdata->last_sigsum=sigsum;if(sigsum&GPGME_SIGSUM_RED){buf=g_strdup_printf("Bad signature from <%s/%s>",barejid,rname);scr_WriteIncomingMessage(barejid,buf,0,HBB_PREFIX_INFO,0);scr_LogPrint(LPRINT_LOGNORM,"%s",buf);g_free(buf);}// Verify that the key id is the one we expect.expectedkey=settings_pgp_getkeyid(barejid);if(keys_mismatch(key,expectedkey)){buf=g_strdup_printf("Warning: The KeyId from <%s/%s> doesn't match ""the key you set up",barejid,rname);scr_WriteIncomingMessage(barejid,buf,0,HBB_PREFIX_INFO,0);scr_LogPrint(LPRINT_LOGNORM,"%s",buf);g_free(buf);}}#endif}staticLmSSLResponsessl_cb(LmSSL*ssl,LmSSLStatusstatus,gpointerud){switch(status){caseLM_SSL_STATUS_NO_CERT_FOUND:scr_LogPrint(LPRINT_LOGNORM,"No certificate found!");break;caseLM_SSL_STATUS_UNTRUSTED_CERT:scr_LogPrint(LPRINT_LOGNORM,"Certificate is not trusted!");// The user specified a fingerprint, let's wait for lm to check thatif(settings_opt_get("ssl_fingerprint"))returnLM_SSL_RESPONSE_CONTINUE;break;caseLM_SSL_STATUS_CERT_EXPIRED:scr_LogPrint(LPRINT_LOGNORM,"Certificate has expired!");break;caseLM_SSL_STATUS_CERT_NOT_ACTIVATED:scr_LogPrint(LPRINT_LOGNORM,"Certificate has not been activated!");break;caseLM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:scr_LogPrint(LPRINT_LOGNORM,"Certificate hostname does not match expected hostname!");break;caseLM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:{#ifndef LOUDMOUTH_USES_SHA256charfpr[3*FINGERPRINT_LENGTH]={0};fingerprint_to_hex(lm_ssl_get_fingerprint(ssl),fpr,FINGERPRINT_LENGTH);#endifscr_LogPrint(LPRINT_LOGNORM,"Certificate fingerprint does not match expected fingerprint!");#ifndef LOUDMOUTH_USES_SHA256scr_LogPrint(LPRINT_LOGNORM,"Remote fingerprint: %s",fpr);#elsescr_LogPrint(LPRINT_LOGNORM,"Remote fingerprint: %s",lm_ssl_get_fingerprint(ssl));#endifscr_LogPrint(LPRINT_LOGNORM,"Expect fingerprint: %s",settings_opt_get("ssl_fingerprint"));returnLM_SSL_RESPONSE_STOP;}break;caseLM_SSL_STATUS_GENERIC_ERROR:scr_LogPrint(LPRINT_LOGNORM,"Generic SSL error!");break;default:scr_LogPrint(LPRINT_LOGNORM,"SSL error:%d",status);}if(settings_opt_get_int("ssl_ignore_checks"))returnLM_SSL_RESPONSE_CONTINUE;returnLM_SSL_RESPONSE_STOP;}staticvoidconnection_auth_cb(LmConnection*connection,gbooleansuccess,gpointeruser_data){LmSSL*lssl;if((lssl=lm_connection_get_ssl(connection))!=NULL){#ifndef LOUDMOUTH_USES_SHA256charfpr[3*FINGERPRINT_LENGTH]={0};fingerprint_to_hex(lm_ssl_get_fingerprint(lssl),fpr,FINGERPRINT_LENGTH);scr_LogPrint(LPRINT_LOGNORM,"Connection established.\n""Remote fingerprint: %s",fpr);#elsescr_LogPrint(LPRINT_LOGNORM,"Connection established.\n""Remote fingerprint: %s",lm_ssl_get_fingerprint(lssl));#endif}if(success){xmpp_iq_request(NULL,NS_ROSTER);xmpp_iq_request(NULL,NS_DISCO_INFO);xmpp_request_storage("storage:bookmarks");xmpp_request_storage("storage:rosternotes");/* XXX Not needed before xmpp_setprevstatus() LmMessage *m; m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_AVAILABLE); lm_connection_send(connection, m, NULL); lm_message_unref(m); */xmpp_setprevstatus();AutoConnection=TRUE;}elsescr_LogPrint(LPRINT_LOGNORM,"Authentication failed");}gbooleanxmpp_reconnect(){if(!lconnection)xmpp_connect();returnFALSE;}staticvoid_try_to_reconnect(void){xmpp_disconnect();if(AutoConnection)g_timeout_add_seconds(RECONNECTION_TIMEOUT+(random()%90L),xmpp_reconnect,NULL);}staticvoidconnection_open_cb(LmConnection*connection,gbooleansuccess,gpointeruser_data){GError*error=NULL;if(success){constchar*password,*resource;char*username;username=jid_get_username(settings_opt_get("jid"));password=settings_opt_get("password");resource=strchr(lm_connection_get_jid(connection),JID_RESOURCE_SEPARATOR);if(resource)resource++;if(!lm_connection_authenticate(lconnection,username,password,resource,connection_auth_cb,NULL,FALSE,&error)){scr_LogPrint(LPRINT_LOGNORM,"Failed to authenticate: %s",error->message);g_error_free(error);_try_to_reconnect();}g_free(username);}else{scr_LogPrint(LPRINT_LOGNORM,"There was an error while connecting.");_try_to_reconnect();}}staticvoidconnection_close_cb(LmConnection*connection,LmDisconnectReasonreason,gpointeruser_data){constchar*str;switch(reason){caseLM_DISCONNECT_REASON_OK:str="LM_DISCONNECT_REASON_OK";break;caseLM_DISCONNECT_REASON_PING_TIME_OUT:str="LM_DISCONNECT_REASON_PING_TIME_OUT";break;caseLM_DISCONNECT_REASON_HUP:str="LM_DISCONNECT_REASON_HUP";break;caseLM_DISCONNECT_REASON_ERROR:str="LM_DISCONNECT_REASON_ERROR";break;caseLM_DISCONNECT_REASON_UNKNOWN:default:str="LM_DISCONNECT_REASON_UNKNOWN";break;}if(reason!=LM_DISCONNECT_REASON_OK)_try_to_reconnect();// Free bookmarksif(bookmarks)lm_message_node_unref(bookmarks);bookmarks=NULL;// Free rosterroster_free();if(rosternotes)lm_message_node_unref(rosternotes);rosternotes=NULL;// Reset carbonscarbons_reset();// Update displayscr_update_roster();scr_update_buddy_window();if(!reason)scr_LogPrint(LPRINT_LOGNORM,"Disconnected.");elsescr_LogPrint(LPRINT_NORMAL,"Disconnected, reason: %d->'%s'",reason,str);xmpp_setstatus(offline,NULL,mystatusmsg,TRUE);}staticvoidhandle_state_events(constchar*bjid,constchar*resource,LmMessageNode*node){#if defined XEP0085LmMessageNode*state_ns=NULL;GSList*sl_buddy;structxep0085*xep85=NULL;sl_buddy=roster_find(bjid,jidsearch,ROSTER_TYPE_USER);if(!sl_buddy){return;}/* Let's see whether the contact supports XEP0085 */xep85=buddy_resource_xep85(sl_buddy->data,resource);if(xep85)state_ns=lm_message_node_find_xmlns(node,NS_CHATSTATES);if(!state_ns){/* Sender does not use chat states */return;}xep85->support=CHATSTATES_SUPPORT_OK;if(!strcmp(state_ns->name,"composing")){xep85->last_state_rcvd=ROSTER_EVENT_COMPOSING;}elseif(!strcmp(state_ns->name,"active")){xep85->last_state_rcvd=ROSTER_EVENT_ACTIVE;}elseif(!strcmp(state_ns->name,"paused")){xep85->last_state_rcvd=ROSTER_EVENT_PAUSED;}elseif(!strcmp(state_ns->name,"inactive")){xep85->last_state_rcvd=ROSTER_EVENT_INACTIVE;}elseif(!strcmp(state_ns->name,"gone")){xep85->last_state_rcvd=ROSTER_EVENT_GONE;}buddy_resource_setevents(sl_buddy->data,resource,xep85->last_state_rcvd);scr_update_roster();#endif}staticvoidgotmessage(LmMessageSubTypetype,constchar*from,constchar*body,constchar*enc,constchar*subject,time_ttimestamp,LmMessageNode*node_signed,gbooleancarbon){char*bjid;constchar*rname;char*decrypted_pgp=NULL;char*decrypted_otr=NULL;intotr_msg=0,free_msg=0;bjid=jidtodisp(from);rname=strchr(from,JID_RESOURCE_SEPARATOR);if(rname)rname++;#ifdef HAVE_GPGMEif(gpg_enabled()){if(enc){decrypted_pgp=gpg_decrypt(enc);if(decrypted_pgp)body=decrypted_pgp;}// Check signature of the unencrypted/decrypted messageif(node_signed)check_signature(bjid,rname,node_signed,body);}#endif// Check for unexpected groupchat messages// If we receive a groupchat message from a room we're not a member of,// this is probably a server issue and the best we can do is to send// a type unavailable.if(type==LM_MESSAGE_SUB_TYPE_GROUPCHAT&&!roster_getnickname(bjid)){// It shouldn't happen, probably a server issueGSList*room_elt;char*mbuf;mbuf=g_strdup_printf("Unexpected groupchat packet!");scr_LogPrint(LPRINT_LOGNORM,"%s",mbuf);scr_WriteIncomingMessage(bjid,mbuf,0,HBB_PREFIX_INFO,0);g_free(mbuf);// Send back an unavailable packetxmpp_setstatus(offline,bjid,"",TRUE);// MUC// Make sure this is a room (it can be a conversion user->room)room_elt=roster_find(bjid,jidsearch,0);if(!room_elt){roster_add_user(bjid,NULL,NULL,ROSTER_TYPE_ROOM,sub_none,-1);}else{buddy_settype(room_elt->data,ROSTER_TYPE_ROOM);}buddylist_defer_build();scr_update_roster();gotogotmessage_return;}// We don't call the message_in hook if 'block_unsubscribed' is true and// this is a regular message from an unsubscribed user.// System messages (from our server) are allowed.if(settings_opt_get_int("block_unsubscribed")&&(roster_gettype(bjid)!=ROSTER_TYPE_ROOM)&&!(roster_getsubscription(bjid)&sub_from)&&(type!=LM_MESSAGE_SUB_TYPE_GROUPCHAT)){char*sbjid=jidtodisp(lm_connection_get_jid(lconnection));constchar*server=strchr(sbjid,JID_DOMAIN_SEPARATOR);if(server&&g_strcmp0(server+1,bjid)){scr_LogPrint(LPRINT_LOGNORM,"Blocked a message from <%s>",bjid);g_free(sbjid);gotogotmessage_return;}g_free(sbjid);}#ifdef HAVE_LIBOTRif(otr_enabled()){decrypted_otr=(char*)body;otr_msg=otr_receive(&decrypted_otr,bjid,&free_msg);if(!decrypted_otr){gotogotmessage_return;}body=decrypted_otr;}#endif{// format and pass message for further processinggchar*fullbody=NULL;guintencrypted;if(decrypted_pgp)encrypted=ENCRYPTED_PGP;elseif(otr_msg)encrypted=ENCRYPTED_OTR;elseencrypted=0;if(subject){if(body)fullbody=g_strdup_printf("[%s]\n%s",subject,body);elsefullbody=g_strdup_printf("[%s]\n",subject);body=fullbody;}hk_message_in(bjid,rname,timestamp,body,type,encrypted,carbon);g_free(fullbody);}gotmessage_return:// Clean up and exitg_free(bjid);g_free(decrypted_pgp);if(free_msg)g_free(decrypted_otr);}staticLmHandlerResulthandle_messages(LmMessageHandler*handler,LmConnection*connection,LmMessage*m,gpointeruser_data){constchar*p,*from=lm_message_get_from(m);char*bjid,*res;LmMessageNode*x;constchar*body=NULL;constchar*enc=NULL;constchar*subject=NULL;time_ttimestamp=0L;LmMessageSubTypemstype;gbooleanskip_process=FALSE;LmMessageNode*ns_signed=NULL;mstype=lm_message_get_sub_type(m);body=lm_message_node_get_child_value(m->node,"body");x=lm_message_node_find_xmlns(m->node,NS_ENCRYPTED);if(x&&(p=lm_message_node_get_value(x))!=NULL)enc=p;// Get the bare-JID/room (bjid) and the resource/nickname (res)bjid=g_strdup(lm_message_get_from(m));res=strchr(bjid,JID_RESOURCE_SEPARATOR);if(res)*res++=0;// Timestamp?timestamp=lm_message_node_get_timestamp(m->node);p=lm_message_node_get_child_value(m->node,"subject");if(p!=NULL){if(mstype!=LM_MESSAGE_SUB_TYPE_GROUPCHAT){// Chat messagesubject=p;}else{// Room topicGSList*roombuddy;gchar*mbuf;constgchar*subj=p;// Set the new topicroombuddy=roster_find(bjid,jidsearch,0);if(roombuddy)buddy_settopic(roombuddy->data,subj);// Display inside the room windowif(!res){// No specific resource (this is certainly history)if(*subj)mbuf=g_strdup_printf("The topic has been set to: %s",subj);elsembuf=g_strdup_printf("The topic has been cleared");}else{if(*subj)mbuf=g_strdup_printf("%s has set the topic to: %s",res,subj);elsembuf=g_strdup_printf("%s has cleared the topic",res);}scr_WriteIncomingMessage(bjid,mbuf,timestamp,HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG,0);if(settings_opt_get_int("log_muc_conf"))hlog_write_message(bjid,0,-1,mbuf);g_free(mbuf);// The topic is displayed in the chat status line, so refresh now.scr_update_chat_status(TRUE);}}if(mstype==LM_MESSAGE_SUB_TYPE_ERROR){x=lm_message_node_get_child(m->node,"error");display_server_error(x,from);#ifdef XEP0085// If the XEP85/22 support is probed, set it back to unknown so that// we probe it again.chatstates_reset_probed(from);#endif}else{handle_state_events(bjid,res,m->node);}// Check for carbons!x=lm_message_node_find_xmlns(m->node,NS_CARBONS_2);gbooleancarbons=FALSE;if(x&&(!g_strcmp0(x->name,"received")||!g_strcmp0(x->name,"sent"))){LmMessageNode*xenc;constchar*carbon_name=x->name;carbons=TRUE;// Go 1 level deeper to the forwarded messagex=lm_message_node_find_xmlns(x,NS_FORWARD);if(x)x=lm_message_node_get_child(x,"message");if(!x){scr_LogPrint(LPRINT_LOGNORM,"Could not read carbon message! Please fill a bug.");gotohandle_messages_return;}xenc=lm_message_node_find_xmlns(x,NS_ENCRYPTED);if(xenc&&(p=lm_message_node_get_value(xenc))!=NULL)enc=p;if(body&&*body&&!subject)ns_signed=lm_message_node_find_xmlns(x,NS_SIGNED);elseskip_process=TRUE;// Parse a message that is send to one of our other resourcesif(!g_strcmp0(carbon_name,"received")){from=lm_message_node_get_attribute(x,"from");if(!from){scr_LogPrint(LPRINT_LOGNORM,"Malformed carbon copy!");gotohandle_messages_return;}g_free(bjid);bjid=g_strdup(from);res=strchr(bjid,JID_RESOURCE_SEPARATOR);if(res)*res++=0;// Try to handle forwarded chat state messageshandle_state_events(from,res,x);scr_LogPrint(LPRINT_DEBUG,"Received incoming carbon from <%s>",from);}elseif(!g_strcmp0(carbon_name,"sent")){#ifdef HAVE_GPGMEchar*decrypted_pgp=NULL;#endifguintencrypted=0;constchar*to=lm_message_node_get_attribute(x,"to");if(!to){scr_LogPrint(LPRINT_LOGNORM,"Malformed carbon copy!");gotohandle_messages_return;}g_free(bjid);bjid=jidtodisp(to);#ifdef HAVE_GPGMEif(gpg_enabled()){if(enc){decrypted_pgp=gpg_decrypt(enc);if(decrypted_pgp){body=decrypted_pgp;encrypted=ENCRYPTED_PGP;}}/* // Check messsage signature // This won't work here, since check_signature wasn't intended // to be used to check our own messages. if (ns_signed) check_signature(ME, NULL, ns_signed, body); */}#endifif(body&&*body)hk_message_out(bjid,NULL,timestamp,body,encrypted,TRUE,NULL);scr_LogPrint(LPRINT_DEBUG,"Received outgoing carbon for <%s>",to);#ifdef HAVE_GPGMEg_free(decrypted_pgp);#endifgotohandle_messages_return;}}else{// Not a Carbonns_signed=lm_message_node_find_xmlns(m->node,NS_SIGNED);}// Do not process the message if some fields are missingif(!from||(!body&&!subject))skip_process=TRUE;if(!skip_process){gotmessage(mstype,from,body,enc,subject,timestamp,ns_signed,carbons);}// We're done if it was a Carbon forwarded messageif(carbons)gotohandle_messages_return;// Report received message if message delivery receipt was requestedif(lm_message_node_get_child(m->node,"request")&&(roster_getsubscription(bjid)&sub_from)){constgchar*mid;LmMessageNode*y;LmMessage*rcvd=lm_message_new(from,LM_MESSAGE_TYPE_MESSAGE);mid=lm_message_get_id(m);// For backward compatibility (XEP184 < v.1.1):lm_message_node_set_attribute(rcvd->node,"id",mid);y=lm_message_node_add_child(rcvd->node,"received",NULL);lm_message_node_set_attribute(y,"xmlns",NS_RECEIPTS);lm_message_node_set_attribute(y,"id",mid);lm_connection_send(connection,rcvd,NULL);lm_message_unref(rcvd);}{// xep184 receipt confirmationLmMessageNode*received=lm_message_node_get_child(m->node,"received");if(received&&!g_strcmp0(lm_message_node_get_attribute(received,"xmlns"),NS_RECEIPTS)){char*jid=jidtodisp(from);constchar*id=lm_message_node_get_attribute(received,"id");// This is for backward compatibility; if the remote client didn't add// the id as an attribute of the 'received' tag, we use the message id:if(!id)id=lm_message_get_id(m);scr_remove_receipt_flag(jid,id);g_free(jid);#ifdef MODULES_ENABLE{hk_arg_targs[]={{"jid",from},{NULL,NULL},};hk_run_handlers("hook-mdr-received",args);}#endif}}if(from){x=lm_message_node_find_xmlns(m->node,NS_MUC_USER);if(x&&!strcmp(x->name,"x"))got_muc_message(from,x,timestamp);x=lm_message_node_find_xmlns(m->node,NS_X_CONFERENCE);if(x&&!strcmp(x->name,"x")){constchar*jid=lm_message_node_get_attribute(x,"jid");if(jid){constchar*reason=lm_message_node_get_attribute(x,"reason");constchar*password=lm_message_node_get_attribute(x,"password");// We won't send decline stanzas as it is a Direct Invitationgot_invite(from,jid,reason,password,FALSE);}}}handle_messages_return:g_free(bjid);returnLM_HANDLER_RESULT_REMOVE_MESSAGE;}staticLmHandlerResultcb_caps(LmMessageHandler*h,LmConnection*c,LmMessage*m,gpointeruser_data){char*ver=user_data;char*hash;constchar*from=lm_message_get_from(m);char*bjid=jidtodisp(from);LmMessageSubTypemstype=lm_message_get_sub_type(m);hash=strchr(ver,',');if(hash)*hash++='\0';if(mstype==LM_MESSAGE_SUB_TYPE_RESULT){LmMessageNode*info;LmMessageNode*query=lm_message_node_get_child(m->node,"query");if(caps_has_hash(ver,bjid)||!query)gotocaps_callback_return;caps_add(ver);info=lm_message_node_get_child(query,"identity");while(info){if(!g_strcmp0(info->name,"identity"))caps_add_identity(ver,lm_message_node_get_attribute(info,"category"),lm_message_node_get_attribute(info,"name"),lm_message_node_get_attribute(info,"type"),lm_message_node_get_attribute(info,"xml:lang"));info=info->next;}info=lm_message_node_get_child(query,"feature");while(info){if(!g_strcmp0(info->name,"feature"))caps_add_feature(ver,lm_message_node_get_attribute(info,"var"));info=info->next;}info=lm_message_node_get_child(query,"x");{LmMessageNode*field;LmMessageNode*value;constchar*formtype,*var;while(info){if(!g_strcmp0(info->name,"x")&&!g_strcmp0(lm_message_node_get_attribute(info,"type"),"result")&&!g_strcmp0(lm_message_node_get_attribute(info,"xmlns"),"jabber:x:data")){field=lm_message_node_get_child(info,"field");formtype=NULL;while(field){if(!g_strcmp0(field->name,"field")&&!g_strcmp0(lm_message_node_get_attribute(field,"var"),"FORM_TYPE")&&!g_strcmp0(lm_message_node_get_attribute(field,"type"),"hidden")){value=lm_message_node_get_child(field,"value");if(value)formtype=lm_message_node_get_value(value);}field=field->next;}if(formtype){caps_add_dataform(ver,formtype);field=lm_message_node_get_child(info,"field");while(field){var=lm_message_node_get_attribute(field,"var");if(!g_strcmp0(field->name,"field")&&(g_strcmp0(var,"FORM_TYPE")||g_strcmp0(lm_message_node_get_attribute(field,"type"),"hidden"))){value=lm_message_node_get_child(field,"value");while(value){if(!g_strcmp0(value->name,"value"))caps_add_dataform_field(ver,formtype,var,lm_message_node_get_value(value));value=value->next;}}field=field->next;}}}info=info->next;}}if(caps_verify(ver,hash))caps_copy_to_persistent(ver,lm_message_node_to_string(query));elsecaps_move_to_local(ver,bjid);}caps_callback_return:g_free(bjid);g_free(ver);returnLM_HANDLER_RESULT_REMOVE_MESSAGE;}staticLmHandlerResulthandle_presence(LmMessageHandler*handler,LmConnection*connection,LmMessage*m,gpointeruser_data){char*bjid;constchar*from,*rname,*p=NULL,*ustmsg=NULL;enumimstatusust;charbpprio;time_ttimestamp=0L;LmMessageNode*muc_packet,*caps;LmMessageSubTypemstype=lm_message_get_sub_type(m);// Check for MUC presence packetmuc_packet=lm_message_node_find_xmlns(m->node,NS_MUC_USER);from=lm_message_get_from(m);if(!from){scr_LogPrint(LPRINT_LOGNORM,"Unexpected presence packet!");if(mstype==LM_MESSAGE_SUB_TYPE_ERROR){display_server_error(lm_message_node_get_child(m->node,"error"),lm_message_get_from(m));returnLM_HANDLER_RESULT_REMOVE_MESSAGE;}returnLM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;}rname=strchr(from,JID_RESOURCE_SEPARATOR);if(rname)rname++;if(settings_opt_get_int("ignore_self_presence")){constchar*self_fjid=lm_connection_get_jid(connection);if(self_fjid&&!strcasecmp(self_fjid,from)){returnLM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;// Ignoring self presence}}bjid=jidtodisp(from);if(mstype==LM_MESSAGE_SUB_TYPE_ERROR){LmMessageNode*x;scr_LogPrint(LPRINT_LOGNORM,"Error presence packet from <%s>",bjid);x=lm_message_node_find_child(m->node,"error");display_server_error(x,from);// Let's check it isn't a nickname conflict.// XXX Note: We should handle the <conflict/> string condition.if((p=lm_message_node_get_attribute(x,"code"))!=NULL){if(atoi(p)==409){// 409 = conflict (nickname is in use or registered by another user)// If we are not inside this room, we should reset the nicknameGSList*room_elt=roster_find(bjid,jidsearch,0);if(room_elt&&!buddy_getinsideroom(room_elt->data))buddy_setnickname(room_elt->data,NULL);}}g_free(bjid);returnLM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;}p=lm_message_node_get_child_value(m->node,"priority");if(p&&*p){intrawprio=atoi(p);if(rawprio>127)bpprio=127;elseif(rawprio<-128)bpprio=-128;elsebpprio=rawprio;}else{bpprio=0;}ust=available;p=lm_message_node_get_child_value(m->node,"show");if(p){if(!strcmp(p,"away"))ust=away;elseif(!strcmp(p,"dnd"))ust=dontdisturb;elseif(!strcmp(p,"xa"))ust=notavail;elseif(!strcmp(p,"chat"))ust=freeforchat;}if(mstype==LM_MESSAGE_SUB_TYPE_UNAVAILABLE)ust=offline;ustmsg=lm_message_node_get_child_value(m->node,"status");if(ustmsg&&!*ustmsg)ustmsg=NULL;// Timestamp?timestamp=lm_message_node_get_timestamp(m->node);if(muc_packet){// This is a MUC presence messagehandle_muc_presence(from,muc_packet,bjid,rname,ust,ustmsg,timestamp,bpprio);}else{// Not a MUC message, so this is a regular buddy...// Call hk_statuschange() if status has changed or if the// status message is differentconstchar*msg;msg=roster_getstatusmsg(bjid,rname);if((ust!=roster_getstatus(bjid,rname))||(!ustmsg&&msg&&msg[0])||(ustmsg&&(!msg||strcmp(ustmsg,msg)))||(bpprio!=roster_getprio(bjid,rname)))hk_statuschange(bjid,rname,bpprio,timestamp,ust,ustmsg);// Presence signature processingif(!ustmsg)ustmsg="";// Some clients omit the <status/> element :-(check_signature(bjid,rname,lm_message_node_find_xmlns(m->node,NS_SIGNED),ustmsg);}// XEP-0115 Entity Capabilitiescaps=lm_message_node_find_xmlns(m->node,NS_CAPS);if(caps&&ust!=offline){constchar*ver=lm_message_node_get_attribute(caps,"ver");constchar*hash=lm_message_node_get_attribute(caps,"hash");GSList*sl_buddy=NULL;if(!hash){// No support for legacy formatgotohandle_presence_return;}if(!ver||!g_strcmp0(ver,"")||!g_strcmp0(hash,""))gotohandle_presence_return;if(rname)sl_buddy=roster_find(bjid,jidsearch,ROSTER_TYPE_USER);// Only cache the caps if the user is on the rosterif(sl_buddy&&buddy_getonserverflag(sl_buddy->data)){buddy_resource_setcaps(sl_buddy->data,rname,ver);if(!caps_has_hash(ver,bjid)&&!caps_restore_from_persistent(ver)){char*node;LmMessageHandler*handler;LmMessage*iq=lm_message_new_with_sub_type(from,LM_MESSAGE_TYPE_IQ,LM_MESSAGE_SUB_TYPE_GET);node=g_strdup_printf("%s#%s",lm_message_node_get_attribute(caps,"node"),ver);lm_message_node_set_attributes(lm_message_node_add_child(iq->node,"query",NULL),"xmlns",NS_DISCO_INFO,"node",node,NULL);g_free(node);handler=lm_message_handler_new(cb_caps,g_strdup_printf("%s,%s",ver,hash),NULL);lm_connection_send_with_reply(connection,iq,handler,NULL);lm_message_unref(iq);lm_message_handler_unref(handler);}}}handle_presence_return:g_free(bjid);returnLM_HANDLER_RESULT_REMOVE_MESSAGE;}staticLmHandlerResulthandle_iq(LmMessageHandler*handler,LmConnection*connection,LmMessage*m,gpointeruser_data){inti;constchar*xmlns=NULL;char*nodestr;LmMessageNode*x;LmMessageSubTypemstype=lm_message_get_sub_type(m);if(mstype==LM_MESSAGE_SUB_TYPE_ERROR){display_server_error(lm_message_node_get_child(m->node,"error"),lm_message_get_from(m));returnLM_HANDLER_RESULT_REMOVE_MESSAGE;}if(mstype==LM_MESSAGE_SUB_TYPE_RESULT){nodestr=lm_message_node_to_string(m->node);scr_LogPrint(LPRINT_DEBUG,"Unhandled IQ result? %s",nodestr);g_free(nodestr);returnLM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;}for(x=m->node->children;x;x=x->next){xmlns=lm_message_node_get_attribute(x,"xmlns");if(xmlns)for(i=0;iq_handlers[i].xmlns;++i)if(!strcmp(iq_handlers[i].xmlns,xmlns))returniq_handlers[i].handler(NULL,connection,m,user_data);xmlns=NULL;}if((mstype==LM_MESSAGE_SUB_TYPE_SET)||(mstype==LM_MESSAGE_SUB_TYPE_GET))send_iq_error(connection,m,XMPP_ERROR_NOT_IMPLEMENTED);nodestr=lm_message_node_to_string(m->node);scr_LogPrint(LPRINT_DEBUG,"Unhandled IQ: %s",nodestr);g_free(nodestr);scr_LogPrint(LPRINT_NORMAL,"Received unhandled IQ request from <%s>.",lm_message_get_from(m));returnLM_HANDLER_RESULT_REMOVE_MESSAGE;}staticLmHandlerResulthandle_s10n(LmMessageHandler*handler,LmConnection*connection,LmMessage*m,gpointeruser_data){char*r;char*buf;intnewbuddy;guinthook_result;LmMessageSubTypemstype=lm_message_get_sub_type(m);constchar*from=lm_message_get_from(m);constchar*msg=lm_message_node_get_child_value(m->node,"status");if(mstype==LM_MESSAGE_SUB_TYPE_ERROR){display_server_error(lm_message_node_get_child(m->node,"error"),lm_message_get_from(m));returnLM_HANDLER_RESULT_REMOVE_MESSAGE;}if(!from){scr_LogPrint(LPRINT_DEBUG,"handle_s10n: Unexpected presence packet!");returnLM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;}r=jidtodisp(from);newbuddy=!roster_find(r,jidsearch,0);hook_result=hk_subscription(mstype,r,msg);if(mstype==LM_MESSAGE_SUB_TYPE_SUBSCRIBE){/* The sender wishes to subscribe to our presence */if(hook_result){g_free(r);returnLM_HANDLER_RESULT_REMOVE_MESSAGE;}buf=g_strdup_printf("<%s> wants to subscribe to your presence updates",from);scr_WriteIncomingMessage(r,buf,0,HBB_PREFIX_INFO,0);scr_LogPrint(LPRINT_LOGNORM,"%s",buf);g_free(buf);if(msg){buf=g_strdup_printf("<%s> said: %s",from,msg);scr_WriteIncomingMessage(r,buf,0,HBB_PREFIX_INFO,0);replace_nl_with_dots(buf);scr_LogPrint(LPRINT_LOGNORM,"%s",buf);g_free(buf);}// Create a new event item{constchar*id;char*desc=g_strdup_printf("<%s> wants to subscribe to your ""presence updates",r);id=evs_new(desc,NULL,0,evscallback_subscription,g_strdup(r),(GDestroyNotify)g_free);g_free(desc);if(id)buf=g_strdup_printf("Please use /event %s accept|reject",id);elsebuf=g_strdup_printf("Unable to create a new event!");}scr_WriteIncomingMessage(r,buf,0,HBB_PREFIX_INFO,0);scr_LogPrint(LPRINT_LOGNORM,"%s",buf);g_free(buf);}elseif(mstype==LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE){/* The sender is unsubscribing from our presence */xmpp_send_s10n(from,LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED);buf=g_strdup_printf("<%s> is unsubscribing from your ""presence updates",from);scr_WriteIncomingMessage(r,buf,0,HBB_PREFIX_INFO,0);scr_LogPrint(LPRINT_LOGNORM,"%s",buf);g_free(buf);}elseif(mstype==LM_MESSAGE_SUB_TYPE_SUBSCRIBED){/* The sender has allowed us to receive their presence */buf=g_strdup_printf("<%s> has allowed you to receive their ""presence updates",from);scr_WriteIncomingMessage(r,buf,0,HBB_PREFIX_INFO,0);scr_LogPrint(LPRINT_LOGNORM,"%s",buf);g_free(buf);}elseif(mstype==LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED){/* The subscription request has been denied or a previously-granted subscription has been cancelled */roster_unsubscribed(from);scr_update_roster();buf=g_strdup_printf("<%s> has cancelled your subscription to ""their presence updates",from);scr_WriteIncomingMessage(r,buf,0,HBB_PREFIX_INFO,0);scr_LogPrint(LPRINT_LOGNORM,"%s",buf);g_free(buf);}else{g_free(r);returnLM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;}if(newbuddy)scr_update_roster();g_free(r);returnLM_HANDLER_RESULT_REMOVE_MESSAGE;}// TODO: Use the enum of loudmouth, when it's included in the header...typedefenum{LM_LOG_LEVEL_VERBOSE=1<<(G_LOG_LEVEL_USER_SHIFT),LM_LOG_LEVEL_NET=1<<(G_LOG_LEVEL_USER_SHIFT+1),LM_LOG_LEVEL_PARSER=1<<(G_LOG_LEVEL_USER_SHIFT+2),LM_LOG_LEVEL_SSL=1<<(G_LOG_LEVEL_USER_SHIFT+3),LM_LOG_LEVEL_SASL=1<<(G_LOG_LEVEL_USER_SHIFT+4),LM_LOG_LEVEL_ALL=(LM_LOG_LEVEL_NET|LM_LOG_LEVEL_VERBOSE|LM_LOG_LEVEL_PARSER|LM_LOG_LEVEL_SSL|LM_LOG_LEVEL_SASL)}LmLogLevelFlags;staticvoidlm_debug_handler(constgchar*log_domain,GLogLevelFlagslog_level,constgchar*message,gpointeruser_data){if(message&&*message){char*msg;intmcabber_loglevel=settings_opt_get_int("tracelog_level");if(mcabber_loglevel<2)return;if(message[0]=='\n')msg=g_strdup(&message[1]);elsemsg=g_strdup(message);if(msg[strlen(msg)-1]=='\n')msg[strlen(msg)-1]='\0';if(log_level&LM_LOG_LEVEL_VERBOSE){scr_LogPrint(LPRINT_DEBUG,"LM-VERBOSE: %s",msg);}if(log_level&LM_LOG_LEVEL_NET){if(mcabber_loglevel>2)scr_LogPrint(LPRINT_DEBUG,"LM-NET: %s",msg);}elseif(log_level&LM_LOG_LEVEL_PARSER){if(mcabber_loglevel>3)scr_LogPrint(LPRINT_DEBUG,"LM-PARSER: %s",msg);}elseif(log_level&LM_LOG_LEVEL_SASL){scr_LogPrint(LPRINT_DEBUG,"LM-SASL: %s",msg);}elseif(log_level&LM_LOG_LEVEL_SSL){scr_LogPrint(LPRINT_DEBUG,"LM-SSL: %s",msg);}g_free(msg);}}// xmpp_connect()// Return a non-zero value if there's an obvious problem// (no JID, no password, etc.)gintxmpp_connect(void){constchar*userjid,*password,*resource,*servername,*ssl_fpr;char*dynresource=NULL;#ifndef LOUDMOUTH_USES_SHA256charfpr[FINGERPRINT_LENGTH]={0};#endifconstchar*proxy_host;constchar*resource_prefix=PACKAGE_NAME;char*fjid;intssl,tls;LmSSL*lssl;unsignedintport;unsignedintping=40;LmMessageHandler*handler;GError*error=NULL;xmpp_disconnect();servername=settings_opt_get("server");userjid=settings_opt_get("jid");password=settings_opt_get("password");resource=settings_opt_get("resource");proxy_host=settings_opt_get("proxy_host");ssl_fpr=settings_opt_get("ssl_fingerprint");if(!userjid){scr_LogPrint(LPRINT_LOGNORM,"Your JID has not been specified!");return-1;}if(!password){scr_LogPrint(LPRINT_LOGNORM,"Your password has not been specified!");return-1;}lconnection=lm_connection_new_with_context(NULL,main_context);g_log_set_handler("LM",LM_LOG_LEVEL_ALL,lm_debug_handler,NULL);if(settings_opt_get("pinginterval"))ping=(unsignedint)settings_opt_get_int("pinginterval");lm_connection_set_keep_alive_rate(lconnection,ping);scr_LogPrint(LPRINT_DEBUG,"Ping interval established: %d secs",ping);lm_connection_set_disconnect_function(lconnection,connection_close_cb,NULL,NULL);handler=lm_message_handler_new(handle_messages,NULL,NULL);lm_connection_register_message_handler(lconnection,handler,LM_MESSAGE_TYPE_MESSAGE,LM_HANDLER_PRIORITY_NORMAL);lm_message_handler_unref(handler);handler=lm_message_handler_new(handle_iq,NULL,NULL);lm_connection_register_message_handler(lconnection,handler,LM_MESSAGE_TYPE_IQ,LM_HANDLER_PRIORITY_NORMAL);lm_message_handler_unref(handler);handler=lm_message_handler_new(handle_presence,NULL,NULL);lm_connection_register_message_handler(lconnection,handler,LM_MESSAGE_TYPE_PRESENCE,LM_HANDLER_PRIORITY_LAST);lm_message_handler_unref(handler);handler=lm_message_handler_new(handle_s10n,NULL,NULL);lm_connection_register_message_handler(lconnection,handler,LM_MESSAGE_TYPE_PRESENCE,LM_HANDLER_PRIORITY_NORMAL);lm_message_handler_unref(handler);/* Connect to server */scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG,"Connecting to server%s%s",servername?": ":"",servername?servername:"");if(!resource)resource=resource_prefix;// Initialize pseudo-random seedsrandom(time(NULL));if(!settings_opt_get("disable_random_resource")){#if HAVE_ARC4RANDOMdynresource=g_strdup_printf("%s.%08x",resource,arc4random());#elseunsignedinttab[2];tab[0]=(unsignedint)(0xffff*(random()/(RAND_MAX+1.0)));tab[1]=(unsignedint)(0xffff*(random()/(RAND_MAX+1.0)));dynresource=g_strdup_printf("%s.%04x%04x",resource,tab[0],tab[1]);#endifresource=dynresource;}port=(unsignedint)settings_opt_get_int("port");if(port)scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG," using port %d",port);scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG," with resource %s",resource);if(proxy_host){intproxy_port=settings_opt_get_int("proxy_port");if(proxy_port<=0||proxy_port>65535){scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG,"Invalid proxy port: %d",proxy_port);}else{constchar*proxy_user,*proxy_pass;LmProxy*lproxy;proxy_user=settings_opt_get("proxy_user");proxy_pass=settings_opt_get("proxy_pass");// Proxy initializationlproxy=lm_proxy_new_with_server(LM_PROXY_TYPE_HTTP,proxy_host,proxy_port);lm_proxy_set_username(lproxy,proxy_user);lm_proxy_set_password(lproxy,proxy_pass);lm_connection_set_proxy(lconnection,lproxy);lm_proxy_unref(lproxy);scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG," using proxy %s:%d",proxy_host,proxy_port);}}fjid=compose_jid(userjid,servername,resource);lm_connection_set_jid(lconnection,fjid);if(servername)lm_connection_set_server(lconnection,servername);#if defined(HAVE_LIBOTR)otr_init(fjid);#endifg_free(fjid);g_free(dynresource);ssl=settings_opt_get_int("ssl");tls=settings_opt_get_int("tls");if(!lm_ssl_is_supported()){if(ssl||tls){scr_LogPrint(LPRINT_LOGNORM,"** Error: SSL is NOT available, ""please recompile loudmouth with SSL enabled.");return-1;}}if(ssl&&tls){scr_LogPrint(LPRINT_LOGNORM,"You can only set ssl or tls, not both.");return-1;}if(!port)port=(ssl?LM_CONNECTION_DEFAULT_PORT_SSL:LM_CONNECTION_DEFAULT_PORT);lm_connection_set_port(lconnection,port);#ifndef LOUDMOUTH_USES_SHA256if(ssl_fpr&&(!hex_to_fingerprint(ssl_fpr,fpr,FINGERPRINT_LENGTH))){scr_LogPrint(LPRINT_LOGNORM,"** Please set the fingerprint in the format ""97:5C:00:3F:1D:77:45:25:E2:C5:70:EC:83:C8:87:EE");return-1;}lssl=lm_ssl_new((ssl_fpr?fpr:NULL),ssl_cb,NULL,NULL);#elselssl=lm_ssl_new(ssl_fpr,ssl_cb,NULL,NULL);#endifif(lssl){#ifdef HAVE_LM_SSL_CIPHER_LISTconstchar*ssl_ciphers=settings_opt_get("ssl_ciphers");lm_ssl_set_cipher_list(lssl,ssl_ciphers);#endif#ifdef HAVE_LM_SSL_CAconstchar*ssl_ca=settings_opt_get("ssl_ca");char*ssl_ca_xp;ssl_ca_xp=expand_filename(ssl_ca);lm_ssl_set_ca(lssl,ssl_ca_xp);g_free(ssl_ca_xp);#endiflm_ssl_use_starttls(lssl,!ssl,tls);lm_connection_set_ssl(lconnection,lssl);lm_ssl_unref(lssl);}elseif(ssl||tls){scr_LogPrint(LPRINT_LOGNORM,"** Error: Couldn't create SSL struct.");return-1;}if(!lm_connection_open(lconnection,connection_open_cb,NULL,FALSE,&error)){_try_to_reconnect();scr_LogPrint(LPRINT_LOGNORM,"Failed to open: %s",error->message);g_error_free(error);}return0;}// xmpp_insert_entity_capabilities(presence_stanza)// Entity Capabilities (XEP-0115)voidxmpp_insert_entity_capabilities(LmMessageNode*x,enumimstatusstatus){LmMessageNode*y;constchar*ver=entity_version(status);if(!ver)return;y=lm_message_node_add_child(x,"c",NULL);lm_message_node_set_attribute(y,"xmlns",NS_CAPS);lm_message_node_set_attribute(y,"hash","sha-1");lm_message_node_set_attribute(y,"node",MCABBER_CAPS_NODE);lm_message_node_set_attribute(y,"ver",ver);}voidxmpp_disconnect(void){if(!lconnection)return;if(lm_connection_is_authenticated(lconnection)){// Launch pre-disconnect internal hookhk_predisconnect();// Announce it to everyone elsexmpp_setstatus(offline,NULL,"",FALSE);}if(lm_connection_is_open(lconnection))lm_connection_close(lconnection,NULL);lm_connection_unref(lconnection);lconnection=NULL;}voidxmpp_setstatus(enumimstatusst,constchar*recipient,constchar*msg,intdo_not_sign){LmMessage*m;gbooleanisonline;if(msg){// The status message has been specified. We'll use it, unless it is// "-" which is a special case (option meaning "no status message").if(!strcmp(msg,"-"))msg="";}else{// No status message specified; we'll use:// a) the default status message (if provided by the user);// b) the current status message;// c) no status message (i.e. an empty one).msg=settings_get_status_msg(st);if(!msg){if(mystatusmsg)msg=mystatusmsg;elsemsg="";}}isonline=xmpp_is_online();// Only send the packet if we're online.// (But we want to update internal status even when disconnected,// in order to avoid some problems during network failures)if(isonline){#ifdef WITH_DEPRECATED_STATUS_INVISIBLEconstchar*s_msg=(st!=invisible?msg:NULL);#else// XXX Could be removed if/when we get rid of status invisible// completely.constchar*s_msg=msg;#endifif(!recipient){// This is a global status, send presence to chatrooms#ifdef WITH_DEPRECATED_STATUS_INVISIBLEif(st!=invisible)#endif{structT_presenceroom_presence;room_presence.st=st;room_presence.msg=msg;foreach_buddy(ROSTER_TYPE_ROOM,&roompresence,&room_presence);}}m=lm_message_new_presence(st,recipient,s_msg);xmpp_insert_entity_capabilities(m->node,st);// Entity Caps (XEP-0115)#ifdef HAVE_GPGMEif(!do_not_sign&&gpg_enabled()){char*signature;signature=gpg_sign(s_msg?s_msg:"");if(signature){LmMessageNode*y;y=lm_message_node_add_child(m->node,"x",signature);lm_message_node_set_attribute(y,"xmlns",NS_SIGNED);g_free(signature);}}#endiflm_connection_send(lconnection,m,NULL);lm_message_unref(m);}// If we didn't change our _global_ status, we are doneif(recipient)return;if(isonline||!st){// We'll have to update the roster if we switch to/from offline because// we don't know the presences of buddies when offline...if(mystatus==offline||st==offline)scr_update_roster();if(isonline||mystatus||st)#ifdef WITH_DEPRECATED_STATUS_INVISIBLEhk_mystatuschange(0,mystatus,st,(st!=invisible?msg:""));#elsehk_mystatuschange(0,mystatus,st,msg);#endifmystatus=st;}if(st)mywantedstatus=st;if(msg!=mystatusmsg){g_free(mystatusmsg);if(*msg)mystatusmsg=g_strdup(msg);elsemystatusmsg=NULL;}if(!scr_curses_status())return;// Called from config. fileif(!Autoaway)update_last_use();// Update status linescr_update_main_status(TRUE);}enumimstatusxmpp_getstatus(void){returnmystatus;}constchar*xmpp_getstatusmsg(void){returnmystatusmsg;}// xmpp_setprevstatus()// Set previous status. This wrapper function is used after a disconnection.voidxmpp_setprevstatus(void){xmpp_setstatus(mywantedstatus,NULL,mystatusmsg,FALSE);}// send_storage(store)// Send the node "store" to update the server.// Note: the caller should check we're online.voidsend_storage(LmMessageNode*store){LmMessage*iq;LmMessageHandler*handler;LmMessageNode*query;if(!rosternotes)return;iq=lm_message_new_with_sub_type(NULL,LM_MESSAGE_TYPE_IQ,LM_MESSAGE_SUB_TYPE_SET);query=lm_message_node_add_child(iq->node,"query",NULL);lm_message_node_set_attribute(query,"xmlns",NS_PRIVATE);lm_message_node_insert_childnode(query,store);handler=lm_message_handler_new(handle_iq_dummy,NULL,FALSE);lm_connection_send_with_reply(lconnection,iq,handler,NULL);lm_message_handler_unref(handler);lm_message_unref(iq);}// xmpp_is_bookmarked(roomjid)// Return TRUE if there's a bookmark for the given jid.guintxmpp_is_bookmarked(constchar*bjid){LmMessageNode*x;if(!bookmarks)returnFALSE;// Walk through the storage bookmark tagsfor(x=bookmarks->children;x;x=x->next){// If the node is a conference item, check the jid.if(x->name&&!strcmp(x->name,"conference")){constchar*fjid=lm_message_node_get_attribute(x,"jid");if(fjid&&!strcasecmp(bjid,fjid))returnTRUE;}}returnFALSE;}// xmpp_get_bookmark_nick(roomjid)// Return the room nickname if it is present in a bookmark.constchar*xmpp_get_bookmark_nick(constchar*bjid){LmMessageNode*x;if(!bookmarks||!bjid)returnNULL;// Walk through the storage bookmark tagsfor(x=bookmarks->children;x;x=x->next){// If the node is a conference item, check the jid.if(x->name&&!strcmp(x->name,"conference")){constchar*fjid=lm_message_node_get_attribute(x,"jid");if(fjid&&!strcasecmp(bjid,fjid))returnlm_message_node_get_child_value(x,"nick");}}returnNULL;}// xmpp_get_bookmark_password(roomjid)// Return the room password if it is present in a bookmark.constchar*xmpp_get_bookmark_password(constchar*bjid){LmMessageNode*x;if(!bookmarks||!bjid)returnNULL;// Walk through the storage bookmark tagsfor(x=bookmarks->children;x;x=x->next){// If the node is a conference item, check the jid.if(x->name&&!strcmp(x->name,"conference")){constchar*fjid=lm_message_node_get_attribute(x,"jid");if(fjid&&!strcasecmp(bjid,fjid))returnlm_message_node_get_child_value(x,"password");}}returnNULL;}intxmpp_get_bookmark_autojoin(constchar*bjid){LmMessageNode*x;if(!bookmarks||!bjid)return0;// Walk through the storage bookmark tagsfor(x=bookmarks->children;x;x=x->next){// If the node is a conference item, check the jid.if(x->name&&!strcmp(x->name,"conference")){constchar*fjid=lm_message_node_get_attribute(x,"jid");if(fjid&&!strcasecmp(bjid,fjid)){constchar*autojoin;autojoin=lm_message_node_get_attribute(x,"autojoin");if(autojoin&&(!strcmp(autojoin,"1")||!strcmp(autojoin,"true")))return1;return0;}}}return0;}// xmpp_get_all_storage_bookmarks()// Return a GSList with all storage bookmarks.// The caller should g_free the list (not the MUC jids).GSList*xmpp_get_all_storage_bookmarks(void){LmMessageNode*x;GSList*sl_bookmarks=NULL;// If we have no bookmarks, probably the server doesn't support them.if(!bookmarks)returnNULL;// Walk through the storage bookmark tagsfor(x=bookmarks->children;x;x=x->next){// If the node is a conference item, let's add the note to our list.if(x->name&&!strcmp(x->name,"conference")){structbookmark*bm_elt;constchar*autojoin,*name,*nick,*passwd;constchar*fjid=lm_message_node_get_attribute(x,"jid");if(!fjid)continue;bm_elt=g_new0(structbookmark,1);bm_elt->roomjid=g_strdup(fjid);autojoin=lm_message_node_get_attribute(x,"autojoin");nick=lm_message_node_get_child_value(x,"nick");name=lm_message_node_get_attribute(x,"name");passwd=lm_message_node_get_child_value(x,"password");if(autojoin&&(!strcmp(autojoin,"1")||!strcmp(autojoin,"true")))bm_elt->autojoin=1;if(nick)bm_elt->nick=g_strdup(nick);if(name)bm_elt->name=g_strdup(name);if(passwd)bm_elt->password=g_strdup(passwd);sl_bookmarks=g_slist_append(sl_bookmarks,bm_elt);}}returnsl_bookmarks;}// xmpp_set_storage_bookmark(roomid, name, nick, passwd, autojoin,// printstatus, autowhois, flagjoins, group)// Update the private storage bookmarks: add a conference room.// If name is nil, we remove the bookmark.voidxmpp_set_storage_bookmark(constchar*roomid,constchar*name,constchar*nick,constchar*passwd,intautojoin,enumroom_printstatuspstatus,enumroom_autowhoisawhois,enumroom_flagjoinsfjoins,constchar*group){LmMessageNode*x;boolchanged=FALSE;if(!roomid)return;// If we have no bookmarks, probably the server doesn't support them.if(!bookmarks){scr_LogPrint(LPRINT_NORMAL,"Sorry, your server doesn't seem to support private storage.");return;}// Walk through the storage tagsfor(x=bookmarks->children;x;x=x->next){// If the current node is a conference item, see if we have to replace it.if(x->name&&!strcmp(x->name,"conference")){constchar*fjid=lm_message_node_get_attribute(x,"jid");if(!fjid)continue;if(!strcmp(fjid,roomid)){// We've found a bookmark for this room. Let's hide it and we'll// create a new one.lm_message_node_hide(x);changed=TRUE;if(!name)scr_LogPrint(LPRINT_LOGNORM,"Deleting bookmark...");}}}// Let's create a node/bookmark for this roomid, if the name is not NULL.if(name){x=lm_message_node_add_child(bookmarks,"conference",NULL);lm_message_node_set_attributes(x,"jid",roomid,"name",name,"autojoin",autojoin?"1":"0",NULL);if(nick)lm_message_node_add_child(x,"nick",nick);if(passwd)lm_message_node_add_child(x,"password",passwd);if(pstatus)lm_message_node_add_child(x,"print_status",strprintstatus[pstatus]);if(awhois)lm_message_node_set_attributes(x,"autowhois",(awhois==autowhois_on)?"1":"0",NULL);if(fjoins)lm_message_node_add_child(x,"flag_joins",strflagjoins[fjoins]);if(group&&*group)lm_message_node_add_child(x,"group",group);changed=TRUE;}if(!changed)return;if(xmpp_is_online()){send_storage(bookmarks);scr_LogPrint(LPRINT_LOGNORM,"Bookmarks updated.");}else{scr_LogPrint(LPRINT_LOGNORM,"Warning: you're not connected to the server.");}}staticstructannotation*parse_storage_rosternote(LmMessageNode*notenode){constchar*p;structannotation*note=g_new0(structannotation,1);p=lm_message_node_get_attribute(notenode,"cdate");if(p)note->cdate=from_iso8601(p,1);p=lm_message_node_get_attribute(notenode,"mdate");if(p)note->mdate=from_iso8601(p,1);note->text=g_strdup(lm_message_node_get_value(notenode));note->jid=g_strdup(lm_message_node_get_attribute(notenode,"jid"));returnnote;}// xmpp_get_all_storage_rosternotes()// Return a GSList with all storage annotations.// The caller should g_free the list and its contents.GSList*xmpp_get_all_storage_rosternotes(void){LmMessageNode*x;GSList*sl_notes=NULL;// If we have no rosternotes, probably the server doesn't support them.if(!rosternotes)returnNULL;// Walk through the storage rosternotes tagsfor(x=rosternotes->children;x;x=x->next){structannotation*note;// We want a note itemif(!x->name||strcmp(x->name,"note"))continue;// Just in case, check the jid...if(!lm_message_node_get_attribute(x,"jid"))continue;// Ok, let's add the note to our listnote=parse_storage_rosternote(x);sl_notes=g_slist_append(sl_notes,note);}returnsl_notes;}// xmpp_get_storage_rosternotes(barejid, silent)// Return the annotation associated with this jid.// If silent is TRUE, no warning is displayed when rosternotes is disabled// The caller should g_free the string and structure after use.structannotation*xmpp_get_storage_rosternotes(constchar*barejid,intsilent){LmMessageNode*x;if(!barejid)returnNULL;// If we have no rosternotes, probably the server doesn't support them.if(!rosternotes){if(!silent)scr_LogPrint(LPRINT_NORMAL,"Sorry, ""your server doesn't seem to support private storage.");returnNULL;}// Walk through the storage rosternotes tagsfor(x=rosternotes->children;x;x=x->next){constchar*fjid;// We want a note itemif(!x->name||strcmp(x->name,"note"))continue;// Just in case, check the jid...fjid=lm_message_node_get_attribute(x,"jid");if(fjid&&!strcmp(fjid,barejid))// We've found a note for this contact.returnparse_storage_rosternote(x);}returnNULL;// No note found}// xmpp_set_storage_rosternotes(barejid, note)// Update the private storage rosternotes: add/delete a note.// If note is nil, we remove the existing note.voidxmpp_set_storage_rosternotes(constchar*barejid,constchar*note){LmMessageNode*x;boolchanged=FALSE;constchar*cdate=NULL;if(!barejid)return;// If we have no rosternotes, probably the server doesn't support them.if(!rosternotes){scr_LogPrint(LPRINT_NORMAL,"Sorry, your server doesn't seem to support private storage.");return;}// Walk through the storage tagsfor(x=rosternotes->children;x;x=x->next){// If the current node is a conference item, see if we have to replace it.if(x->name&&!strcmp(x->name,"note")){constchar*fjid=lm_message_node_get_attribute(x,"jid");if(!fjid)continue;if(!strcmp(fjid,barejid)){// We've found a note for this jid. Let's hide it and we'll// create a new one.cdate=lm_message_node_get_attribute(x,"cdate");lm_message_node_hide(x);changed=TRUE;break;}}}// Let's create a node for this jid, if the note is not NULL.if(note){charmdate[20];time_tnow;time(&now);to_iso8601(mdate,now);if(!cdate)cdate=mdate;x=lm_message_node_add_child(rosternotes,"note",note);lm_message_node_set_attributes(x,"jid",barejid,"cdate",cdate,"mdate",mdate,NULL);changed=TRUE;}if(!changed)return;if(xmpp_is_online())send_storage(rosternotes);elsescr_LogPrint(LPRINT_LOGNORM,"Warning: you're not connected to the server.");}/* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2: For Vim users... */