/* * otr.c -- Off-The-Record Messaging for mcabber * * Copyright (C) 2007 Frank Zschockelt <mcabber_otr@freakysoft.de> * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */#include<config.h>#ifdef HAVE_LIBOTR#include"otr.h"#include"logprint.h"#include"hbuf.h"#include"jab_priv.h"#include"roster.h"#include"utils.h"#include"screen.h"#include"settings.h"staticOtrlUserStateuserstate=NULL;staticchar*account=NULL;staticchar*keyfile=NULL;staticchar*fprfile=NULL;staticOtrlPolicycb_policy(void*opdata,ConnContext*ctx);staticvoidcb_create_privkey(void*opdata,constchar*accountname,constchar*protocol);staticintcb_is_logged_in(void*opdata,constchar*accountname,constchar*protocol,constchar*recipient);staticvoidcb_inject_message(void*opdata,constchar*accountname,constchar*protocol,constchar*recipient,constchar*message);staticvoidcb_notify(void*opdata,OtrlNotifyLevellevel,constchar*accountname,constchar*protocol,constchar*username,constchar*title,constchar*primary,constchar*secondary);staticintcb_display_otr_message(void*opdata,constchar*accountname,constchar*protocol,constchar*username,constchar*msg);staticvoidcb_update_context_list(void*opdata);staticconstchar*cb_protocol_name(void*opdata,constchar*protocol);staticvoidcb_protocol_name_free(void*opdata,constchar*protocol_name);staticvoidcb_new_fingerprint(void*opdata,OtrlUserStateus,constchar*accountname,constchar*protocol,constchar*username,unsignedcharfingerprint[20]);staticvoidcb_write_fingerprints(void*opdata);staticvoidcb_gone_secure(void*opdata,ConnContext*context);staticvoidcb_gone_insecure(void*opdata,ConnContext*context);staticvoidcb_still_secure(void*opdata,ConnContext*context,intis_reply);staticvoidcb_log_message(void*opdata,constchar*message);staticintcb_max_message_size(void*opdata,ConnContext*context);staticOtrlMessageAppOpsops={cb_policy,cb_create_privkey,cb_is_logged_in,cb_inject_message,cb_notify,cb_display_otr_message,cb_update_context_list,cb_protocol_name,cb_protocol_name_free,cb_new_fingerprint,cb_write_fingerprints,cb_gone_secure,cb_gone_insecure,cb_still_secure,cb_log_message,cb_max_message_size,NULL,/*account_name*/NULL/*account_name_free*/};staticvoidotr_message_disconnect(ConnContext*ctx);staticConnContext*otr_get_context(constchar*buddy);staticvoidotr_startstop(constchar*buddy,intstart);staticvoidotr_handle_smp_tlvs(OtrlTLV*tlvs,ConnContext*ctx);staticchar*otr_get_dir(void);voidotr_init(constchar*jid){char*root=otr_get_dir();account=jidtodisp(jid);keyfile=g_strdup_printf("%s%s.key",root,account);fprfile=g_strdup_printf("%s%s.fpr",root,account);g_free(root);OTRL_INIT;userstate=otrl_userstate_create();if(otrl_privkey_read(userstate,keyfile)){scr_LogPrint(LPRINT_LOGNORM,"Could not read OTR key from %s",keyfile);cb_create_privkey(NULL,account,"jabber");}if(otrl_privkey_read_fingerprints(userstate,fprfile,NULL,NULL)){scr_LogPrint(LPRINT_LOGNORM,"Could not read OTR fingerprints from %s",fprfile);}}voidotr_terminate(void){ConnContext*ctx;for(ctx=userstate->context_root;ctx;ctx=ctx->next)if(ctx->msgstate==OTRL_MSGSTATE_ENCRYPTED)otr_message_disconnect(ctx);g_free(account);account=NULL;/* XXX This #ifdef is a quick workaround: when mcabber * is linked to both gnutls and libotr, libgcrypt will * segfault when we call otrl_userstate_free(). * This is reported to be a bug in libgcrypt :-/ * Mikael */#if defined(HAVE_GNUTLS) && !defined(HAVE_OPENSSL)if(!settings_opt_get_int("ssl"))#endifotrl_userstate_free(userstate);userstate=NULL;g_free(keyfile);keyfile=NULL;}staticchar*otr_get_dir(void){char*configured_dir=(char*)settings_opt_get("otr_dir");if(configured_dir)returnexpand_filename(configured_dir);elsereturnexpand_filename("~/.mcabber/otr/");}staticConnContext*otr_get_context(constchar*buddy){intnull=0;returnotrl_context_find(userstate,buddy,account,"jabber",1,&null,NULL,NULL);}staticvoidotr_message_disconnect(ConnContext*ctx){if(ctx->msgstate==OTRL_MSGSTATE_ENCRYPTED)cb_gone_insecure(NULL,ctx);otrl_message_disconnect(userstate,&ops,NULL,ctx->accountname,ctx->protocol,ctx->username);}staticvoidotr_startstop(constchar*buddy,intstart){char*msg=NULL;ConnContext*ctx=otr_get_context(buddy);if(!userstate||!ctx)return;if(start&&ctx->msgstate==OTRL_MSGSTATE_ENCRYPTED)otr_message_disconnect(ctx);if(start){OtrlPolicypolicy=cb_policy(NULL,ctx);if(policy==plain){scr_LogPrint(LPRINT_LOGNORM,"The OTR policy for this user is set to"" plain. You have to change it first.");return;}msg=otrl_proto_default_query_msg(ctx->accountname,policy);cb_inject_message(NULL,ctx->accountname,ctx->protocol,ctx->username,msg);free(msg);}elseotr_message_disconnect(ctx);}voidotr_establish(constchar*buddy){otr_startstop(buddy,1);}voidotr_disconnect(constchar*buddy){otr_startstop(buddy,0);}voidotr_fingerprint(constchar*buddy,constchar*trust){charfpr[45],*tr;ConnContext*ctx=otr_get_context(buddy);if(!userstate||!ctx)return;if(!ctx->active_fingerprint||!ctx->active_fingerprint->fingerprint){scr_LogPrint(LPRINT_LOGNORM,"No active fingerprint - start OTR for this buddy first.");return;}otrl_privkey_hash_to_human(fpr,ctx->active_fingerprint->fingerprint);if(trust){if(strcmp(fpr,trust)==0)otrl_context_set_trust(ctx->active_fingerprint,"trust");elseotrl_context_set_trust(ctx->active_fingerprint,NULL);}tr=ctx->active_fingerprint->trust;scr_LogPrint(LPRINT_LOGNORM,"%s [%44s]: %s",ctx->username,fpr,tr&&*tr?"trusted":"untrusted");cb_write_fingerprints(NULL);}staticvoidotr_handle_smp_tlvs(OtrlTLV*tlvs,ConnContext*ctx){OtrlTLV*tlv=NULL;char*sbuf=NULL;NextExpectedSMPnextMsg=ctx->smstate->nextExpected;tlv=otrl_tlv_find(tlvs,OTRL_TLV_SMP1);if(tlv){if(nextMsg!=OTRL_SMP_EXPECT1)otr_smp_abort(ctx->username);else{sbuf=g_strdup_printf("OTR: Received SMP Initiation. ""Answer with /otr smpr %s $secret",ctx->username);}}tlv=otrl_tlv_find(tlvs,OTRL_TLV_SMP2);if(tlv){if(nextMsg!=OTRL_SMP_EXPECT2)otr_smp_abort(ctx->username);else{sbuf=g_strdup("OTR: Received SMP Response.");/* If we received TLV2, we will send TLV3 and expect TLV4 */ctx->smstate->nextExpected=OTRL_SMP_EXPECT4;}}tlv=otrl_tlv_find(tlvs,OTRL_TLV_SMP3);if(tlv){if(nextMsg!=OTRL_SMP_EXPECT3)otr_smp_abort(ctx->username);else{/* If we received TLV3, we will send TLV4 * We will not expect more messages, so prepare for next SMP */ctx->smstate->nextExpected=OTRL_SMP_EXPECT1;/* Report result to user */if(ctx->active_fingerprint&&ctx->active_fingerprint->trust&&*ctx->active_fingerprint->trust!='\0')sbuf=g_strdup("OTR: SMP succeeded");elsesbuf=g_strdup("OTR: SMP failed");}}tlv=otrl_tlv_find(tlvs,OTRL_TLV_SMP4);if(tlv){if(nextMsg!=OTRL_SMP_EXPECT4)otr_smp_abort(ctx->username);else{/* We will not expect more messages, so prepare for next SMP */ctx->smstate->nextExpected=OTRL_SMP_EXPECT1;/* Report result to user */if(ctx->active_fingerprint&&ctx->active_fingerprint->trust&&*ctx->active_fingerprint->trust!='\0')sbuf=g_strdup("OTR: SMP succeeded");elsesbuf=g_strdup("OTR: SMP failed");}}tlv=otrl_tlv_find(tlvs,OTRL_TLV_SMP_ABORT);if(tlv){/* The message we are waiting for will not arrive, so reset * and prepare for the next SMP */sbuf=g_strdup("OTR: SMP aborted by your buddy");ctx->smstate->nextExpected=OTRL_SMP_EXPECT1;}if(sbuf){scr_WriteIncomingMessage(ctx->username,sbuf,0,HBB_PREFIX_INFO,0);g_free(sbuf);}}/* * returns whether a otr_message was received * sets *otr_data to NULL, when it was an internal otr message */intotr_receive(char**otr_data,constchar*buddy,int*free_msg){intignore_message;char*newmessage=NULL;OtrlTLV*tlvs=NULL;OtrlTLV*tlv=NULL;ConnContext*ctx;*free_msg=0;ignore_message=otrl_message_receiving(userstate,&ops,NULL,account,"jabber",buddy,*otr_data,&newmessage,&tlvs,NULL,NULL);ctx=otr_get_context(buddy);tlv=otrl_tlv_find(tlvs,OTRL_TLV_DISCONNECTED);if(tlv){/* Notify the user that the other side disconnected. */if(ctx){cb_gone_insecure(NULL,ctx);otr_disconnect(buddy);}}otr_handle_smp_tlvs(tlvs,ctx);if(tlvs!=NULL)otrl_tlv_free(tlvs);if(ignore_message)*otr_data=NULL;if(!ignore_message&&newmessage){*free_msg=1;*otr_data=g_strdup(newmessage);otrl_message_free(newmessage);if(ctx->msgstate==OTRL_MSGSTATE_ENCRYPTED)return1;}return0;}intotr_send(char**msg,constchar*buddy){gcry_error_terr;char*newmessage=NULL;ConnContext*ctx=otr_get_context(buddy);err=otrl_message_sending(userstate,&ops,NULL,account,"jabber",buddy,*msg,NULL,&newmessage,NULL,NULL);if(err)*msg=NULL;/*something went wrong, don't send the plain-message! */if(!err&&newmessage){*msg=g_strdup(newmessage);otrl_message_free(newmessage);if(cb_policy(NULL,ctx)&OTRL_POLICY_REQUIRE_ENCRYPTION||ctx->msgstate==OTRL_MSGSTATE_ENCRYPTED)return1;}return0;}/* Prints OTR connection state */voidotr_print_info(constchar*buddy){constchar*state,*auth,*policy;ConnContext*ctx=otr_get_context(buddy);OtrlPolicyp=cb_policy(ctx->app_data,ctx);if(!userstate||!ctx)return;switch(ctx->msgstate){caseOTRL_MSGSTATE_PLAINTEXT:state="plaintext";break;caseOTRL_MSGSTATE_ENCRYPTED:switch(ctx->protocol_version){case1:state="encrypted V1";break;case2:state="encrypted V2";break;default:state="encrypted";};break;caseOTRL_MSGSTATE_FINISHED:state="finished";break;default:state="unknown state";}switch(ctx->auth.authstate){caseOTRL_AUTHSTATE_NONE:switch(ctx->otr_offer){caseOFFER_NOT:auth="no offer sent";break;caseOFFER_SENT:auth="offer sent";break;caseOFFER_ACCEPTED:auth="offer accepted";break;caseOFFER_REJECTED:auth="offer rejected";break;default:auth="unknown auth";}break;caseOTRL_AUTHSTATE_AWAITING_DHKEY:auth="awaiting D-H key";break;caseOTRL_AUTHSTATE_AWAITING_REVEALSIG:auth="awaiting reveal signature";break;caseOTRL_AUTHSTATE_AWAITING_SIG:auth="awaiting signature";break;caseOTRL_AUTHSTATE_V1_SETUP:auth="v1 setup";break;default:auth="unknown auth";}if(p==OTRL_POLICY_NEVER)policy="plain";elseif(p==(OTRL_POLICY_OPPORTUNISTIC&~OTRL_POLICY_ALLOW_V1))policy="opportunistic";elseif(p==(OTRL_POLICY_MANUAL&~OTRL_POLICY_ALLOW_V1))policy="manual";elseif(p==(OTRL_POLICY_ALWAYS&~OTRL_POLICY_ALLOW_V1))policy="always";elsepolicy="unknown";scr_LogPrint(LPRINT_LOGNORM,"%s: %s (%s) [%s]",ctx->username,state,auth,policy);}staticConnContext*otr_context_encrypted(constchar*buddy){ConnContext*ctx=otr_get_context(buddy);if(!userstate||!ctx||ctx->msgstate!=OTRL_MSGSTATE_ENCRYPTED){scr_LogPrint(LPRINT_LOGNORM,"You have to start an OTR channel with %s before you can ""use SMP.",buddy);returnNULL;}returnctx;}voidotr_smp_query(constchar*buddy,constchar*secret){ConnContext*ctx=otr_context_encrypted(buddy);if(!secret){scr_LogPrint(LPRINT_LOGNORM,"Using SMP without a secret isn't a good idea.");return;}if(ctx){otrl_message_initiate_smp(userstate,&ops,NULL,ctx,(constunsignedchar*)secret,strlen(secret));scr_WriteIncomingMessage(ctx->username,"OTR: Socialist Millionaires' Protocol ""initiated.",0,HBB_PREFIX_INFO,0);}}voidotr_smp_respond(constchar*buddy,constchar*secret){ConnContext*ctx=otr_context_encrypted(buddy);if(!secret){scr_LogPrint(LPRINT_LOGNORM,"Using SMP without a secret isn't a good idea.");return;}if(ctx){if(!ctx->smstate->secret){scr_LogPrint(LPRINT_LOGNORM,"Don't call smpr before you haven't received an SMP ""Initiation!");return;}otrl_message_respond_smp(userstate,&ops,NULL,ctx,(constunsignedchar*)secret,strlen(secret));scr_WriteIncomingMessage(ctx->username,"OTR: Socialist Millionaires' Protocol: ""response sent",0,HBB_PREFIX_INFO,0);}}voidotr_smp_abort(constchar*buddy){ConnContext*ctx=otr_context_encrypted(buddy);if(ctx){otrl_message_abort_smp(userstate,&ops,NULL,ctx);scr_WriteIncomingMessage(ctx->username,"OTR: Socialist Millionaires' Protocol aborted.",0,HBB_PREFIX_INFO,0);}}voidotr_key(void){OtrlPrivKey*key;charreadable[45]="";if(!userstate)return;for(key=userstate->privkey_root;key;key=key->next){otrl_privkey_fingerprint(userstate,readable,key->accountname,key->protocol);scr_LogPrint(LPRINT_LOGNORM,"%s: %s",key->accountname,readable);}}/* Return the OTR policy for the given context. */staticOtrlPolicycb_policy(void*opdata,ConnContext*ctx){enumotr_policyp=settings_otr_getpolicy(NULL);if(ctx)if(settings_otr_getpolicy(ctx->username))p=settings_otr_getpolicy(ctx->username);switch(p){caseplain:returnOTRL_POLICY_NEVER;caseopportunistic:returnOTRL_POLICY_OPPORTUNISTIC&~OTRL_POLICY_ALLOW_V1;casemanual:returnOTRL_POLICY_MANUAL&~OTRL_POLICY_ALLOW_V1;casealways:returnOTRL_POLICY_ALWAYS&~OTRL_POLICY_ALLOW_V1;}returnOTRL_POLICY_MANUAL&~OTRL_POLICY_ALLOW_V1;}/* Create a private key for the given accountname/protocol if * desired. */staticvoidcb_create_privkey(void*opdata,constchar*accountname,constchar*protocol){gcry_error_te;char*root;scr_LogPrint(LPRINT_LOGNORM,"Generating new OTR key for %s. This may take a while...",accountname);scr_DoUpdate();e=otrl_privkey_generate(userstate,keyfile,accountname,protocol);if(e){root=otr_get_dir();scr_LogPrint(LPRINT_LOGNORM,"OTR key generation failed! Please mkdir ""%s if you want to use otr encryption.",root);g_free(root);}elsescr_LogPrint(LPRINT_LOGNORM,"OTR key generated.");}/* Report whether you think the given user is online. Return 1 if * you think he is, 0 if you think he isn't, -1 if you're not sure. * If you return 1, messages such as heartbeats or other * notifications may be sent to the user, which could result in "not * logged in" errors if you're wrong. */staticintcb_is_logged_in(void*opdata,constchar*accountname,constchar*protocol,constchar*recipient){intret=(roster_getstatus((char*)recipient,NULL)!=offline);returnret;}/* Send the given IM to the given recipient from the given * accountname/protocol. */staticvoidcb_inject_message(void*opdata,constchar*accountname,constchar*protocol,constchar*recipient,constchar*message){char*id=g_strdup("otrinject");jb_send_msg(recipient,message,ROSTER_TYPE_USER,"",id,NULL,NULL);g_free(id);}/* Display a notification message for a particular * accountname / protocol / username conversation. */staticvoidcb_notify(void*opdata,OtrlNotifyLevellevel,constchar*accountname,constchar*protocol,constchar*username,constchar*title,constchar*primary,constchar*secondary){char*type;char*sbuf=NULL;switch(level){caseOTRL_NOTIFY_ERROR:type="error";break;caseOTRL_NOTIFY_WARNING:type="warning";break;caseOTRL_NOTIFY_INFO:type="info";break;default:type="unknown";}sbuf=g_strdup_printf("OTR %s:%s\n%s\n%s",type,title,primary,secondary);scr_WriteIncomingMessage(username,sbuf,0,HBB_PREFIX_INFO,0);g_free(sbuf);}/* Display an OTR control message for a particular * accountname / protocol / username conversation. Return 0 if you are able * to successfully display it. If you return non-0 (or if this * function is NULL), the control message will be displayed inline, * as a received message, or else by using the above notify() * callback. */staticintcb_display_otr_message(void*opdata,constchar*accountname,constchar*protocol,constchar*username,constchar*msg){scr_WriteIncomingMessage(username,msg,0,HBB_PREFIX_INFO,0);return0;}/* When the list of ConnContexts changes (including a change in * state), this is called so the UI can be updated. */staticvoidcb_update_context_list(void*opdata){/*maybe introduce new status characters for mcabber, * then use this function (?!)*/}/* Return a newly allocated string containing a human-friendly name * for the given protocol id */staticconstchar*cb_protocol_name(void*opdata,constchar*protocol){returnprotocol;}/* Deallocate a string allocated by protocol_name */staticvoidcb_protocol_name_free(void*opdata,constchar*protocol_name){/* We didn't allocated memory, so we don't have to free anything :p */}/* A new fingerprint for the given user has been received. */staticvoidcb_new_fingerprint(void*opdata,OtrlUserStateus,constchar*accountname,constchar*protocol,constchar*username,unsignedcharfingerprint[20]){char*sbuf=NULL;charreadable[45];otrl_privkey_hash_to_human(readable,fingerprint);sbuf=g_strdup_printf("OTR: new fingerprint: %s",readable);scr_WriteIncomingMessage(username,sbuf,0,HBB_PREFIX_INFO,0);g_free(sbuf);}/* The list of known fingerprints has changed. Write them to disk. */staticvoidcb_write_fingerprints(void*opdata){otrl_privkey_write_fingerprints(userstate,fprfile);}/* A ConnContext has entered a secure state. */staticvoidcb_gone_secure(void*opdata,ConnContext*context){scr_WriteIncomingMessage(context->username,"OTR: channel established",0,HBB_PREFIX_INFO,0);}/* A ConnContext has left a secure state. */staticvoidcb_gone_insecure(void*opdata,ConnContext*context){scr_WriteIncomingMessage(context->username,"OTR: channel closed",0,HBB_PREFIX_INFO,0);}/* We have completed an authentication, using the D-H keys we * already knew. is_reply indicates whether we initiated the AKE. */staticvoidcb_still_secure(void*opdata,ConnContext*context,intis_reply){scr_WriteIncomingMessage(context->username,"OTR: channel reestablished",0,HBB_PREFIX_INFO,0);}/* Log a message. The passed message will end in "\n". */staticvoidcb_log_message(void*opdata,constchar*message){scr_LogPrint(LPRINT_DEBUG,"OTR: %s",message);}/* Find the maximum message size supported by this protocol. */staticintcb_max_message_size(void*opdata,ConnContext*context){return8192;}#endif /* HAVE_LIBOTR *//* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */