#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "patchlevel.h" #include "ppport.h" #define HASHGET(rv, k, l) (SV*)*hv_fetch((HV*)SvRV(rv), k, l, 0) #define ACCOUNT2OTR(rv) HASHGET(rv, "otr", 3) #define CONTACT2ACCOUNT(rv) HASHGET(rv, "act", 3) #define CHANNEL2ACCOUNT(rv) CONTACT2ACCOUNT(HASHGET(rv, "cnt", 3)) #define CHANNEL2CONTACT(rv) HASHGET(rv, "cnt", 3) #define ACCOUNT2CTX(rv) INT2PTR(Protocol__OTR,SvIV((SV*)SvRV(ACCOUNT2OTR(rv)))) #define CHANNEL2CTX(rv) ACCOUNT2CTX(CHANNEL2ACCOUNT(rv)) /* There is a struct name conflict with perl.h */ #define context otr_context #include #undef context /* libotr */ #include #include #include static const struct mmsByProto { char *protocol; int mms; } mmsTable[8] = { {"prpl-msn", 1409}, {"prpl-icq", 2346}, {"prpl-aim", 2343}, {"prpl-yahoo", 799}, {"prpl-gg", 1999}, {"prpl-irc", 417}, {"prpl-oscar", 2343}, {NULL, 0} }; static const char *TrustStates[] = { "Not private", "Unverified", "Private", "Finished" }; typedef enum { TRUST_NOT_PRIVATE, TRUST_UNVERIFIED, TRUST_PRIVATE, TRUST_FINISHED } TrustLevel; typedef struct { OtrlUserState userstate; char * privkeys_file; char * contacts_file; char * instance_tags_file; } OTRctx; typedef OTRctx * Protocol__OTR; static void write_fingerprints(OTRctx *ctx); /* privkey.c - Convert a hex character to a value */ static unsigned int ctoh(unsigned char c) { if (c >= '0' && c <= '9') return c-'0'; if (c >= 'a' && c <= 'f') return c-'a'+10; if (c >= 'A' && c <= 'F') return c-'A'+10; return 0; /* Unknown hex char */ } static OtrlPolicy policy_cb(void *opdata, ConnContext *context) { OtrlPolicy policy = OTRL_POLICY_DEFAULT; if (!context) return policy; SV *channel = (SV *)opdata; policy = SvUV(HASHGET(channel, "policy", 6)); return policy; } static int is_logged_in_cb(void *opdata, const char *accountname, const char *protocol, const char *recipient) { int count; int is_logged_in = 0; dSP; SV *channel = (SV *)opdata; if ( ! hv_exists((HV*)SvRV(channel), "on_is_contact_logged_in", 23) ) return -1; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_is_contact_logged_in", 23 ))); PUTBACK; count = call_method( "_ev", G_SCALAR ); if ( count != 1 ) { croak("on_is_contact_logged_in() callback did not return scalar value"); } SPAGAIN; is_logged_in = POPi; if ( is_logged_in < -1 || is_logged_in > 1 ) { croak("on_is_contact_logged_in() callback must return: -1, 0 or 1"); } PUTBACK; FREETMPS; LEAVE; return is_logged_in; } static void inject_message_cb(void *opdata, const char *accountname, const char *protocol, const char *recipient, const char *message) { dSP; SV *channel = (SV *)opdata; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_write", 8 ))); XPUSHs( sv_2mortal( newSVpv( message, 0 ))); PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } static void send_default_query_msg(SV *channel) { char * init_msg; HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); unsigned int channel_policy = SvUV(HASHGET(channel, "policy", 6)); char * contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); char * account_name = SvPV_nolen(HASHGET(account, "name", 4)); char * account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); init_msg = otrl_proto_default_query_msg( account_name, channel_policy); inject_message_cb( (void*) channel, account_name, account_protocol, contact_name, init_msg ); free(init_msg); } static void write_fingerprints(OTRctx *ctx) { FILE * fs; gcry_error_t err; /* contacts_file */ fs = fopen(ctx->contacts_file,"wb"); if ( ! fs ) return; err = otrl_privkey_write_fingerprints_FILEp(ctx->userstate, fs); if (fs) fclose(fs); if ( err ) { croak("Cannot update contacts files: %s\n", gcry_strerror(err)); } } static void write_fingerprints_cb(void *opdata) { SV *channel = (SV *)opdata; OTRctx * ctx = CHANNEL2CTX(channel); write_fingerprints(ctx); } static void update_context_list_cb(void *opdata) { SV *channel = (SV *)opdata; OTRctx * ctx = CHANNEL2CTX(channel); write_fingerprints(ctx); } static void gone_secure_mark_only_cb(void *opdata, ConnContext *context) { SV *channel = (SV *)opdata; (void)hv_store((HV*)SvRV(channel), "gone_secure", 11, &PL_sv_yes, 0); } static void update_channel_sessions(SV *channel, ConnContext *context) { if ( context->their_instance ) { char itoa[20] = {0}; HV *sessions = (HV*)SvRV(HASHGET(channel, "known_sessions", 14)); snprintf(itoa, 20, "%u", context->their_instance); (void)hv_store(sessions, itoa, strlen(itoa), newSViv(1), 0); } } static void gone_secure_cb(void *opdata, ConnContext *context) { dSP; SV *channel = (SV *)opdata; update_channel_sessions(channel, context); if ( ! hv_exists((HV*)SvRV(channel), "on_gone_secure", 14) ) return; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_gone_secure", 14 ))); PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } static void gone_insecure_cb(void *opdata, ConnContext *context) { dSP; SV *channel = (SV *)opdata; update_channel_sessions(channel, context); if ( ! hv_exists((HV*)SvRV(channel), "on_gone_insecure", 16) ) return; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_gone_insecure", 16 ))); PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } static void still_secure_cb(void *opdata, ConnContext *context, int is_reply) { dSP; SV *channel = (SV *)opdata; if ( is_reply != 0 ) return; update_channel_sessions(channel, context); if ( ! hv_exists((HV*)SvRV(channel), "on_still_secure", 15) ) return; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_still_secure", 15 ))); PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } static void new_fingerprint_cb(void *opdata, OtrlUserState us, const char *accountname, const char *protocol, const char *username, unsigned char fingerprint[20]) { ConnContext *context; int seenbefore = 0; char *hex_fingerprint; dSP; SV *channel = (SV *)opdata; /* just skip */ if ( ! hv_exists((HV*)SvRV(channel), "on_unverified_fingerprint", 25) ) return; /* Figure out if this is the first fingerprint we've seen for this * user. */ context = otrl_context_find(us, username, accountname, protocol, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if (context) { Fingerprint *fp; for ( fp = context->fingerprint_root.next; fp; fp = fp->next ) { if (memcmp(fingerprint, fp->fingerprint, 20)) { /* This is a previously seen fingerprint for this user, * different from the one we were passed. */ seenbefore = 1; break; } } } Newx(hex_fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN, char); otrl_privkey_hash_to_human(hex_fingerprint, fingerprint); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_unverified_fingerprint", 25 ))); XPUSHs( sv_2mortal( newSVpvn( hex_fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN - 1))); Safefree(hex_fingerprint); if (seenbefore) { XPUSHs( sv_2mortal( &PL_sv_yes ) ); } else { XPUSHs( sv_2mortal( &PL_sv_no ) ); } PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } static int max_message_size_cb(void *opdata, ConnContext *context) { SV *channel = (SV *)opdata; int max_message_size = SvIV(HASHGET(channel, "max_message_size", 16)); if ( max_message_size > 0 ) { return max_message_size; } else { int i; HV * account = (HV*)CHANNEL2ACCOUNT(channel); char *account_protocol; account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); for ( i = 0; mmsTable[i].protocol != NULL; i++ ) { if ( strEQ(mmsTable[i].protocol, account_protocol) ) { return mmsTable[i].mms; } } } /* unlimited */ return 0; } static void received_symkey_cb(void *opdata, ConnContext *context, unsigned int use, const unsigned char *usedata, size_t usedatalen, const unsigned char *symkey) { dSP; SV *channel = (SV *)opdata; if ( ! hv_exists((HV*)SvRV(channel), "on_symkey", 9) ) return; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_symkey", 9 ))); XPUSHs( sv_2mortal( newSVpvn( (char*)symkey, OTRL_EXTRAKEY_BYTES ))); XPUSHs( sv_2mortal( newSVuv( use ))); if ( usedata ) { XPUSHs( sv_2mortal( newSVpvn( (char *)usedata, usedatalen ))); } PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } static const char* error_message_cb(void *opdata, ConnContext *context, OtrlErrorCode err_code) { char * err_msg; int count; dSP; SV *channel = (SV *)opdata; if ( ! hv_exists((HV*)SvRV(channel), "on_error", 8) ) return NULL; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_error", 8 ))); XPUSHs( sv_2mortal( newSViv( err_code ))); PUTBACK; count = call_method( "_ev", G_SCALAR ); if ( count != 1 ) { croak("on_error() callback did not return message string"); } SPAGAIN; err_msg = savepv( POPp ); PUTBACK; FREETMPS; LEAVE; return err_msg; } static void error_message_free_cb(void *opdata, const char *err_msg) { if (err_msg) Safefree((char *)err_msg); } static void smp_event_cb(SV *channel, OtrlSMPEvent smp_event, unsigned short progress_percent) { dSP; if ( ! hv_exists((HV*)SvRV(channel), "on_smp_event", 12) ) return; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_smp_event", 12 ))); XPUSHs( sv_2mortal( newSViv( smp_event ))); XPUSHs( sv_2mortal( newSViv( progress_percent ))); PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } static void handle_smp_event_cb(void *, OtrlSMPEvent , ConnContext *, unsigned short , char *); static void handle_msg_event_cb(void *opdata, OtrlMessageEvent msg_event, ConnContext *context, const char* message, gcry_error_t err) { dSP; SV *channel = (SV *)opdata; if ( ! hv_exists((HV*)SvRV(channel), "on_event", 8) ) return; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_event", 8 ))); XPUSHs( sv_2mortal( newSViv( msg_event ))); switch ( msg_event ) { case OTRL_MSGEVENT_SETUP_ERROR: XPUSHs( sv_2mortal( newSVpv( gcry_strerror(err), 0 ))); break; case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR: case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED: XPUSHs( sv_2mortal( newSVpv( message, 0 ))); break; default: break; } PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } static void create_instag_cb(void *opdata, const char *accountname, const char *protocol) { FILE * fs; SV *channel = (SV *)opdata; OTRctx * ctx = CHANNEL2CTX(channel); /* contacts_file */ fs = fopen(ctx->instance_tags_file,"w+b"); if ( ! fs ) return; otrl_instag_generate_FILEp(ctx->userstate, fs, accountname, protocol); fclose(fs); } static void timer_control_cb(void *, unsigned int); static void convert_data_cb(void *opdata, ConnContext *context, OtrlConvertType convert_type, char ** dest, const char *src) { char *cb_name; int count; dSP; SV *channel = (SV *)opdata; if ( convert_type == OTRL_CONVERT_SENDING ) { if ( hv_exists((HV*)SvRV(channel), "on_before_encrypt", 17) ) { cb_name = "on_before_encrypt"; } else { *dest = NULL; return; } } else { /* OTRL_CONVERT_RECEIVING */ if ( hv_exists((HV*)SvRV(channel), "on_after_decrypt", 16) ) { cb_name = "on_after_decrypt"; } else { *dest = NULL; return; } } ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( cb_name, strlen(cb_name) ))); XPUSHs( sv_2mortal( newSVpv( src, 0 ))); PUTBACK; count = call_method( "_ev", G_SCALAR ); if ( count != 1 ) { croak("%s() callback did not return message string", cb_name); } SPAGAIN; *dest = savepv( POPp ); PUTBACK; FREETMPS; LEAVE; } static void convert_data_free_cb(void *opdata, ConnContext *context, char *dest) { if (dest) Safefree(dest); } static OtrlMessageAppOps callbacks = { policy_cb, NULL, /* missing privkey is generated when creating account obj */ is_logged_in_cb, inject_message_cb, update_context_list_cb, new_fingerprint_cb, write_fingerprints_cb, gone_secure_mark_only_cb /* gone_secure_cb */, gone_insecure_cb, still_secure_cb, max_message_size_cb, NULL, /* account_name */ NULL, /* account_name_free */ received_symkey_cb, error_message_cb, error_message_free_cb, NULL /* resent_msg_prefix_cb */, NULL /* resent_msg_prefix_free_cb */, handle_smp_event_cb, handle_msg_event_cb, create_instag_cb, convert_data_cb, convert_data_free_cb, timer_control_cb }; static void smp_event_popup(SV *channel, ConnContext *context, char *question) { int count; OTRctx * ctx = CHANNEL2CTX(channel); dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_smp", 6 ))); if ( question != NULL ) { XPUSHs( sv_2mortal( newSVpv( question, 0 ))); } PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } static void handle_smp_event_cb(void *opdata, OtrlSMPEvent smp_event, ConnContext *context, unsigned short progress_percent, char *question) { SV *channel = (SV *)opdata; OTRctx * ctx; if ( ! context ) return; if ( ! hv_exists((HV*)SvRV(channel), "on_smp", 6) ) return; ctx = CHANNEL2CTX(channel); switch (smp_event) { case OTRL_SMPEVENT_NONE : smp_event_cb(channel, smp_event, progress_percent); break; case OTRL_SMPEVENT_ASK_FOR_SECRET : smp_event_popup( channel, context, NULL); break; case OTRL_SMPEVENT_ASK_FOR_ANSWER : smp_event_popup( channel, context, question); break; case OTRL_SMPEVENT_CHEATED : otrl_message_abort_smp( ctx->userstate, &callbacks, channel, context ); /* FALLTHROUGH */ case OTRL_SMPEVENT_IN_PROGRESS : case OTRL_SMPEVENT_SUCCESS : case OTRL_SMPEVENT_FAILURE : case OTRL_SMPEVENT_ABORT : smp_event_cb(channel, smp_event, progress_percent); break; case OTRL_SMPEVENT_ERROR : otrl_message_abort_smp( ctx->userstate, &callbacks, channel, context ); smp_event_cb(channel, smp_event, progress_percent); break; } } static void timer_control_cb(void *opdata, unsigned int interval) { SV *channel = (SV *)opdata; /* user provided timer function */ if ( hv_exists((HV*)SvRV(channel), "on_timer", 8) ) { dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_timer", 8 ))); XPUSHs( sv_2mortal( newSVuv( interval ))); PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } else if ( interval > 0 ) { OTRctx * ctx = CHANNEL2CTX(channel); otrl_message_poll(ctx->userstate, &callbacks, channel); } } /* (From otr-plugin.c) What level of trust do we have in the privacy of this ConnContext? */ TrustLevel otrp_plugin_context_to_trust(ConnContext *context) { TrustLevel level = TRUST_NOT_PRIVATE; if (context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED) { if (context->active_fingerprint && context->active_fingerprint->trust && context->active_fingerprint->trust[0] != '\0') { level = TRUST_PRIVATE; } else { level = TRUST_UNVERIFIED; } } else if (context && context->msgstate == OTRL_MSGSTATE_FINISHED) { level = TRUST_FINISHED; } return level; } MODULE = Protocol::OTR PACKAGE = Protocol::OTR BOOT: { #if (PATCHLEVEL > 4) || ((PATCHLEVEL == 4) && (SUBVERSION >= 70)) HV *pstash = gv_stashpv("Protocol::OTR", 0); newCONSTSUB(pstash, "POLICY_OPPORTUNISTIC", newSVuv(OTRL_POLICY_OPPORTUNISTIC) ); newCONSTSUB(pstash, "POLICY_ALWAYS", newSVuv(OTRL_POLICY_ALWAYS) ); newCONSTSUB(pstash, "ERRCODE_NONE", newSVuv(OTRL_ERRCODE_NONE) ); newCONSTSUB(pstash, "ERRCODE_ENCRYPTION_ERROR", newSVuv(OTRL_ERRCODE_ENCRYPTION_ERROR) ); newCONSTSUB(pstash, "ERRCODE_MSG_NOT_IN_PRIVATE", newSVuv(OTRL_ERRCODE_MSG_NOT_IN_PRIVATE) ); newCONSTSUB(pstash, "ERRCODE_MSG_UNREADABLE", newSVuv(OTRL_ERRCODE_MSG_UNREADABLE) ); newCONSTSUB(pstash, "ERRCODE_MSG_MALFORMED", newSVuv(OTRL_ERRCODE_MSG_MALFORMED) ); newCONSTSUB(pstash, "MSGEVENT_NONE", newSVuv(OTRL_MSGEVENT_NONE) ); newCONSTSUB(pstash, "MSGEVENT_ENCRYPTION_REQUIRED", newSVuv(OTRL_MSGEVENT_ENCRYPTION_REQUIRED) ); newCONSTSUB(pstash, "MSGEVENT_ENCRYPTION_ERROR", newSVuv(OTRL_MSGEVENT_ENCRYPTION_ERROR) ); newCONSTSUB(pstash, "MSGEVENT_CONNECTION_ENDED", newSVuv(OTRL_MSGEVENT_CONNECTION_ENDED) ); newCONSTSUB(pstash, "MSGEVENT_SETUP_ERROR", newSVuv(OTRL_MSGEVENT_SETUP_ERROR) ); newCONSTSUB(pstash, "MSGEVENT_MSG_REFLECTED", newSVuv(OTRL_MSGEVENT_MSG_REFLECTED) ); newCONSTSUB(pstash, "MSGEVENT_MSG_RESENT", newSVuv(OTRL_MSGEVENT_MSG_RESENT) ); newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_NOT_IN_PRIVATE", newSVuv(OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE) ); newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_UNREADABLE", newSVuv(OTRL_MSGEVENT_RCVDMSG_UNREADABLE) ); newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_MALFORMED", newSVuv(OTRL_MSGEVENT_RCVDMSG_MALFORMED) ); newCONSTSUB(pstash, "MSGEVENT_LOG_HEARTBEAT_RCVD", newSVuv(OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD) ); newCONSTSUB(pstash, "MSGEVENT_LOG_HEARTBEAT_SENT", newSVuv(OTRL_MSGEVENT_LOG_HEARTBEAT_SENT) ); newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_GENERAL_ERR", newSVuv(OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR) ); newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_UNENCRYPTED", newSVuv(OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED) ); newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_UNRECOGNIZED", newSVuv(OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED) ); newCONSTSUB(pstash, "MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE", newSVuv(OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE) ); newCONSTSUB(pstash, "SMPEVENT_NONE", newSVuv(OTRL_SMPEVENT_NONE) ); newCONSTSUB(pstash, "SMPEVENT_CHEATED", newSVuv(OTRL_SMPEVENT_CHEATED) ); newCONSTSUB(pstash, "SMPEVENT_IN_PROGRESS", newSVuv(OTRL_SMPEVENT_IN_PROGRESS) ); newCONSTSUB(pstash, "SMPEVENT_SUCCESS", newSVuv(OTRL_SMPEVENT_SUCCESS) ); newCONSTSUB(pstash, "SMPEVENT_FAILURE", newSVuv(OTRL_SMPEVENT_FAILURE) ); newCONSTSUB(pstash, "SMPEVENT_ABORT", newSVuv(OTRL_SMPEVENT_ABORT) ); newCONSTSUB(pstash, "SMPEVENT_ERROR", newSVuv(OTRL_SMPEVENT_ERROR) ); newCONSTSUB(pstash, "INSTAG_BEST", newSVuv(OTRL_INSTAG_BEST) ); newCONSTSUB(pstash, "INSTAG_RECENT", newSVuv(OTRL_INSTAG_RECENT) ); newCONSTSUB(pstash, "INSTAG_RECENT_RECEIVED", newSVuv(OTRL_INSTAG_RECENT_RECEIVED) ); newCONSTSUB(pstash, "INSTAG_RECENT_SENT", newSVuv(OTRL_INSTAG_RECENT_SENT) ); #endif /* Version check should be the very first call because it makes sure that important subsystems are intialized. */ if (!gcry_check_version (GCRYPT_VERSION)) { croak("libgcrypt version mismatch\n"); } /* We don't want to see any warnings, e.g. because we have not yet parsed program options which might be used to suppress such warnings. */ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); /* Allocate a pool of 16k secure memory. This make the secure memory available and also drops privileges where needed. */ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); /* It is now okay to let Libgcrypt complain when there was/is a problem with the secure memory. */ gcry_control (GCRYCTL_RESUME_SECMEM_WARN); if ( PerlEnv_getenv("PROTOCOL_OTR_ENABLE_QUICK_RANDOM") ) { gcry_control(GCRYCTL_ENABLE_QUICK_RANDOM, 0); } /* Tell Libgcrypt that initialization has completed. */ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); OTRL_INIT; } #if (PATCHLEVEL < 4) || ((PATCHLEVEL == 4) && (SUBVERSION < 70)) PROTOTYPES: ENABLE unsigned int POLICY_OPPORTUNISTIC() CODE: RETVAL = OTRL_POLICY_OPPORTUNISTIC; OUTPUT: RETVAL unsigned int POLICY_ALWAYS() CODE: RETVAL = OTRL_POLICY_ALWAYS; OUTPUT: RETVAL unsigned int ERRCODE_NONE() CODE: RETVAL = OTRL_ERRCODE_NONE; OUTPUT: RETVAL unsigned int ERRCODE_ENCRYPTION_ERROR() CODE: RETVAL = OTRL_ERRCODE_ENCRYPTION_ERROR; OUTPUT: RETVAL unsigned int ERRCODE_MSG_NOT_IN_PRIVATE() CODE: RETVAL = OTRL_ERRCODE_MSG_NOT_IN_PRIVATE; OUTPUT: RETVAL unsigned int ERRCODE_MSG_UNREADABLE() CODE: RETVAL = OTRL_ERRCODE_MSG_UNREADABLE; OUTPUT: RETVAL unsigned int ERRCODE_MSG_MALFORMED() CODE: RETVAL = OTRL_ERRCODE_MSG_MALFORMED; OUTPUT: RETVAL unsigned int MSGEVENT_NONE() CODE: RETVAL = OTRL_MSGEVENT_NONE; OUTPUT: RETVAL unsigned int MSGEVENT_ENCRYPTION_REQUIRED() CODE: RETVAL = OTRL_MSGEVENT_ENCRYPTION_REQUIRED; OUTPUT: RETVAL unsigned int MSGEVENT_ENCRYPTION_ERROR() CODE: RETVAL = OTRL_MSGEVENT_ENCRYPTION_ERROR; OUTPUT: RETVAL unsigned int MSGEVENT_CONNECTION_ENDED() CODE: RETVAL = OTRL_MSGEVENT_CONNECTION_ENDED; OUTPUT: RETVAL unsigned int MSGEVENT_SETUP_ERROR() CODE: RETVAL = OTRL_MSGEVENT_SETUP_ERROR; OUTPUT: RETVAL unsigned int MSGEVENT_MSG_REFLECTED() CODE: RETVAL = OTRL_MSGEVENT_MSG_REFLECTED; OUTPUT: RETVAL unsigned int MSGEVENT_MSG_RESENT() CODE: RETVAL = OTRL_MSGEVENT_MSG_RESENT; OUTPUT: RETVAL unsigned int MSGEVENT_RCVDMSG_NOT_IN_PRIVATE() CODE: RETVAL = OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE; OUTPUT: RETVAL unsigned int MSGEVENT_RCVDMSG_UNREADABLE() CODE: RETVAL = OTRL_MSGEVENT_RCVDMSG_UNREADABLE; OUTPUT: RETVAL unsigned int MSGEVENT_RCVDMSG_MALFORMED() CODE: RETVAL = OTRL_MSGEVENT_RCVDMSG_MALFORMED; OUTPUT: RETVAL unsigned int MSGEVENT_LOG_HEARTBEAT_RCVD() CODE: RETVAL = OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD; OUTPUT: RETVAL unsigned int MSGEVENT_LOG_HEARTBEAT_SENT() CODE: RETVAL = OTRL_MSGEVENT_LOG_HEARTBEAT_SENT; OUTPUT: RETVAL unsigned int MSGEVENT_RCVDMSG_GENERAL_ERR() CODE: RETVAL = OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR; OUTPUT: RETVAL unsigned int MSGEVENT_RCVDMSG_UNENCRYPTED() CODE: RETVAL = OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED; OUTPUT: RETVAL unsigned int MSGEVENT_RCVDMSG_UNRECOGNIZED() CODE: RETVAL = OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED; OUTPUT: RETVAL unsigned int MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE() CODE: RETVAL = OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE; OUTPUT: RETVAL unsigned int SMPEVENT_NONE() CODE: RETVAL = OTRL_SMPEVENT_NONE; OUTPUT: RETVAL unsigned int SMPEVENT_CHEATED() CODE: RETVAL = OTRL_SMPEVENT_CHEATED; OUTPUT: RETVAL unsigned int SMPEVENT_IN_PROGRESS() CODE: RETVAL = OTRL_SMPEVENT_IN_PROGRESS; OUTPUT: RETVAL unsigned int SMPEVENT_SUCCESS() CODE: RETVAL = OTRL_SMPEVENT_SUCCESS; OUTPUT: RETVAL unsigned int SMPEVENT_FAILURE() CODE: RETVAL = OTRL_SMPEVENT_FAILURE; OUTPUT: RETVAL unsigned int SMPEVENT_ABORT() CODE: RETVAL = OTRL_SMPEVENT_ABORT; OUTPUT: RETVAL unsigned int SMPEVENT_ERROR() CODE: RETVAL = OTRL_SMPEVENT_ERROR; OUTPUT: RETVAL unsigned int INSTAG_BEST() CODE: RETVAL = OTRL_INSTAG_BEST; OUTPUT: RETVAL unsigned int INSTAG_RECENT() CODE: RETVAL = OTRL_INSTAG_RECENT; OUTPUT: RETVAL unsigned int INSTAG_RECENT_RECEIVED() CODE: RETVAL = OTRL_INSTAG_RECENT_RECEIVED; OUTPUT: RETVAL unsigned int INSTAG_RECENT_SENT() CODE: RETVAL = OTRL_INSTAG_RECENT_SENT; OUTPUT: RETVAL PROTOTYPES: DISABLE #endif Protocol::OTR _new(privkeys_file, contacts_file, instance_tags_file) char * privkeys_file char * contacts_file char * instance_tags_file PROTOTYPE: $$$ PREINIT: FILE *fs; gcry_error_t err; OTRctx * ctx; SV *obj; CODE: { Newx(ctx, 1, OTRctx); ctx->privkeys_file = savepv(privkeys_file); ctx->contacts_file = savepv(contacts_file); ctx->instance_tags_file = savepv(instance_tags_file); ctx->userstate = otrl_userstate_create(); /* privkeys_file */ fs = fopen(ctx->privkeys_file,"rb"); err = otrl_privkey_read_FILEp(ctx->userstate, fs); if (fs) fclose(fs); if ( err ) { croak("Cannot read/write privkeys_file: %s", gcry_strerror(err)); } /* contacts_file */ fs = fopen(ctx->contacts_file,"rb"); err = otrl_privkey_read_fingerprints_FILEp(ctx->userstate, fs, NULL, NULL); if (fs) fclose(fs); if ( err ) { croak("Cannot read/write contacts_file: %s", gcry_strerror(err)); } /* instance_tags_file */ fs = fopen(ctx->instance_tags_file,"rb"); err = otrl_instag_read_FILEp(ctx->userstate, fs); if (fs) fclose(fs); if ( err ) { croak("Cannot read/write instance_tags_file: %s", gcry_strerror(err)); } RETVAL = ctx; } OUTPUT: RETVAL SV * _accounts(ctx) Protocol::OTR ctx PROTOTYPE: $ PREINIT: OtrlPrivKey *p; AV * list; char *fingerprint; CODE: { list = (AV *)sv_2mortal( (SV *)newAV() ); for(p=ctx->userstate->privkey_root; p; p=p->next) { HV *ac = (HV*)sv_2mortal((SV*)newHV()); (void)hv_store(ac, "name", 4, newSVpv( p->accountname, 0 ), 0); (void)hv_store(ac, "protocol", 8, newSVpv( p->protocol, 0 ), 0); Newx(fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN, char); fingerprint = otrl_privkey_fingerprint(ctx->userstate, fingerprint, p->accountname, p->protocol); (void)hv_store(ac, "fingerprint", 11, newSVpv( fingerprint, 0 ), 0); Safefree(fingerprint); av_push(list, newRV((SV*)ac)); } RETVAL = newRV_inc((SV*) list); } OUTPUT: RETVAL SV * _account(ctx, accountname, protocol) Protocol::OTR ctx char * accountname char * protocol ALIAS: _find_account = 1 PROTOTYPE: $$$ PREINIT: OtrlPrivKey *p; char *fingerprint; CODE: { p = otrl_privkey_find(ctx->userstate, accountname, protocol); if ( ! p ) { if ( ix == 1 ) XSRETURN_UNDEF; FILE * fs; gcry_error_t err; /* privkeys_file */ fs = fopen(ctx->privkeys_file,"w+b"); if ( ! fs ) croak("Could not open %s for updating: %s", ctx->privkeys_file, Strerror(errno)); err = otrl_privkey_generate_FILEp(ctx->userstate, fs, accountname, protocol); if (fs) fclose(fs); if (err) { croak("Cannot generate: %s", gcry_strerror(err)); } } Newx(fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN, char); fingerprint = otrl_privkey_fingerprint(ctx->userstate, fingerprint, accountname, protocol); RETVAL = newSVpvn(fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN - 1); Safefree(fingerprint); } OUTPUT: RETVAL void DESTROY(self) Protocol::OTR self CODE: { if ( self->userstate ) { otrl_userstate_free(self->userstate); self->userstate = NULL; } Safefree(self->privkeys_file); Safefree(self->contacts_file); Safefree(self->instance_tags_file); Safefree(self); } MODULE = Protocol::OTR PACKAGE = Protocol::OTR::Account void _contact(account, username, sv_fprint, verified) SV * account char * username SV * sv_fprint SV * verified PROTOTYPE: $$;$$ PREINIT: OTRctx *ctx; unsigned char fingerprint[20] = {0}; int with_fingerprint = 0; int is_verified = 0; ConnContext *context; Fingerprint *fprint; int context_created = 0; int fprint_added = 0; char * account_name; char * account_protocol; PPCODE: { ctx = ACCOUNT2CTX(account); if ( SvOK(sv_fprint) ) { STRLEN flen; unsigned char *fingerprint_hex = (unsigned char*)SvPV(sv_fprint, flen); if ( ( /* human readable string */ flen == 44 && fingerprint_hex[8] == ' ' && fingerprint_hex[17] == ' ' && fingerprint_hex[26] == ' ' && fingerprint_hex[35] == ' ' ) || ( /* hex encoded string */ flen == 40 && strchr((char*)fingerprint_hex, ' ') == NULL ) ) { int j, i; for(j=0, i=0; j<=20; i+=2) { fingerprint[j++] = (ctoh(fingerprint_hex[i]) << 4) + (ctoh(fingerprint_hex[i+1])); if (flen == 44 && j % 4 == 0 ) { /* skip spaces */ i++; } } with_fingerprint = 1; } else { croak("Fingerprint has invalid format: %s", fingerprint_hex); } if ( SvOK(verified) && SvTRUE(verified) ) { is_verified = 1; } } account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); context = otrl_context_find( ctx->userstate, username, account_name, account_protocol, OTRL_INSTAG_BEST, 1, &context_created, NULL, NULL ); if ( context ) { if ( context_created && ! with_fingerprint ) { //croak("New contact requires fingerprint"); } if ( with_fingerprint ) { fprint = otrl_context_find_fingerprint(context, fingerprint, 1, &fprint_added); otrl_context_set_trust(fprint, is_verified ? "verified" : NULL); } if ( context_created || fprint_added ) { write_fingerprints(ctx); } XSRETURN_YES; } croak("Could not find or create contact"); } SV * _contacts(account) SV * account PROTOTYPE: $ PREINIT: AV * list; ConnContext *context; OTRctx *ctx; char * account_name; char * account_protocol; CODE: { ctx = ACCOUNT2CTX(account); list = (AV *)sv_2mortal( (SV *)newAV() ); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); for(context = ctx->userstate->context_root; context; context = context->next) { /* Fingerprints are only stored in the master contexts */ if (context->their_instance != OTRL_INSTAG_MASTER) continue; /* Found matching account */ if ( ! strEQ(context->accountname, account_name) ) continue; if ( ! strEQ(context->protocol, account_protocol) ) continue; av_push(list, newSVpv( context->username, 0 )); } RETVAL = newRV_inc((SV*) list); } OUTPUT: RETVAL MODULE = Protocol::OTR PACKAGE = Protocol::OTR::Contact SV * _fingerprints(contact) SV * contact PROTOTYPE: \% PREINIT: AV * list; ConnContext *context; Fingerprint *fingerprint; OTRctx *ctx; char *contact_name; char *account_name; char *account_protocol; CODE: { HV *account = (HV*)HASHGET(contact, "act", 3); ctx = ACCOUNT2CTX(account); list = (AV *)sv_2mortal( (SV *)newAV() ); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); for(context = ctx->userstate->context_root; context; context = context->next) { /* Fingerprints are only stored in the master contexts */ if (context->their_instance != OTRL_INSTAG_MASTER) continue; /* Found matching account */ if ( ! strEQ(context->accountname, account_name) ) continue; if ( ! strEQ(context->protocol, account_protocol) ) continue; if ( ! strEQ(context->username, contact_name) ) continue; /* Don't bother with the first (fingerprintless) entry. */ for (fingerprint = context->fingerprint_root.next; fingerprint; fingerprint = fingerprint->next) { ConnContext *context_iter; TrustLevel best_level = TRUST_NOT_PRIVATE; int used = 0; HV *fprint = (HV*)sv_2mortal((SV*)newHV()); for (context_iter = context->m_context; context_iter && context_iter->m_context == context->m_context; context_iter = context_iter->next) { TrustLevel this_level = TRUST_NOT_PRIVATE; if (context_iter->active_fingerprint == fingerprint) { this_level = otrp_plugin_context_to_trust(context_iter); used = 1; if (this_level == TRUST_PRIVATE) { best_level = TRUST_PRIVATE; } else if (this_level == TRUST_UNVERIFIED && best_level != TRUST_PRIVATE) { best_level = TRUST_UNVERIFIED; } else if (this_level == TRUST_FINISHED && best_level == TRUST_NOT_PRIVATE) { best_level = TRUST_FINISHED; } } } (void)hv_store(fprint, "fingerprint", 11, newSVpvn( (char*)fingerprint->fingerprint, 20), 0); (void)hv_store(fprint, "is_verified", 11, otrl_context_is_fingerprint_trusted(fingerprint) ? &PL_sv_yes : &PL_sv_no , 0); (void)hv_store(fprint, "status", 6, newSVpv( used ? TrustStates[best_level] : "Unused", 0 ), 0); av_push(list, newRV((SV*)fprint)); } } RETVAL = newRV_inc((SV*) list); } OUTPUT: RETVAL void _active_fingerprint(contact) SV * contact PROTOTYPE: \% PREINIT: ConnContext *context; Fingerprint *fingerprint; OTRctx *ctx; char *contact_name; char *account_name; char *account_protocol; PPCODE: { HV *account = (HV*)HASHGET(contact, "act", 3); ctx = ACCOUNT2CTX(account); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); context = otrl_context_find( ctx->userstate, contact_name, account_name, account_protocol, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL ); if (context) { /* Don't bother with the first (fingerprintless) entry. */ for (fingerprint = context->fingerprint_root.next; fingerprint; fingerprint = fingerprint->next) { ConnContext *context_iter; for (context_iter = context->m_context; context_iter && context_iter->m_context == context->m_context; context_iter = context_iter->next) { if (context_iter->active_fingerprint == fingerprint) { HV *fprint = (HV*)sv_2mortal((SV*)newHV()); TrustLevel this_level = otrp_plugin_context_to_trust(context_iter); (void)hv_store(fprint, "fingerprint", 11, newSVpvn( (char *)fingerprint->fingerprint, 20), 0); (void)hv_store(fprint, "is_verified", 11, otrl_context_is_fingerprint_trusted(fingerprint) ? &PL_sv_yes : &PL_sv_no , 0); (void)hv_store(fprint, "status", 6, newSVpv( TrustStates[this_level], 0 ), 0); XPUSHs(newRV_inc((SV*) fprint)); XSRETURN(1); } } } } XSRETURN_UNDEF; } MODULE = Protocol::OTR PACKAGE = Protocol::OTR::Fingerprint void hash(fprint) SV *fprint PROTOTYPE: $ PREINIT: char *hex_fingerprint; unsigned char *fprint_bytes; PPCODE: { fprint_bytes = (unsigned char*)SvPVbyte_nolen(HASHGET(fprint, "fingerprint", 11)); Newx(hex_fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN, char); otrl_privkey_hash_to_human(hex_fingerprint, fprint_bytes); XPUSHs( sv_2mortal( newSVpvn( hex_fingerprint, OTRL_PRIVKEY_FPRINT_HUMAN_LEN - 1))); Safefree(hex_fingerprint); XSRETURN(1); } void set_verified(fprint, verified) SV *fprint SV * verified PROTOTYPE: $$ PREINIT: OTRctx * ctx; ConnContext * context; Fingerprint * fingerprint; int is_verified; char *contact_name; char *account_name; char *account_protocol; PPCODE: { HV *contact = (HV*)HASHGET(fprint, "cnt", 3); HV *account = (HV*)HASHGET(contact, "act", 3); ctx = ACCOUNT2CTX(account); is_verified = SvOK(verified) && SvTRUE(verified) ? 1 : 0; contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); context = otrl_context_find(ctx->userstate, contact_name, account_name, account_protocol, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if ( context ) { unsigned char *fprint_bytes = (unsigned char*)SvPV_nolen(HASHGET(fprint, "fingerprint", 11)); fingerprint = otrl_context_find_fingerprint(context, fprint_bytes, 0, NULL); if ( fingerprint ) { otrl_context_set_trust(fingerprint, verified ? "verified" : ""); (void)hv_store((HV*)SvRV(fprint), "is_verified", 11, is_verified ? &PL_sv_yes : &PL_sv_no , 0); write_fingerprints(ctx); is_verified ? XSRETURN_YES : XSRETURN_NO; } } XSRETURN_UNDEF; } MODULE = Protocol::OTR PACKAGE = Protocol::OTR::Channel void init(channel) SV * channel ALIAS: refresh = 1 PROTOTYPE: $ PPCODE: { send_default_query_msg(channel); XSRETURN_YES; } void create_symkey(channel, use, use_for) SV * channel unsigned int use SV * use_for PROTOTYPE: $$$ PREINIT: ConnContext *context; int context_created = 0; OTRctx * ctx; char *contact_name; char * account_name; char * account_protocol; PPCODE: { HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); ctx = CHANNEL2CTX(channel); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); context = otrl_context_find( ctx->userstate, contact_name, account_name, account_protocol, SvUV(HASHGET(channel, "selected_instag", 15)), 0, &context_created, NULL, NULL ); if ( context && ! context_created && context->msgstate == OTRL_MSGSTATE_ENCRYPTED ) { unsigned char *symkey; unsigned char *usedata; STRLEN usedatalen; gcry_error_t err; usedata = (unsigned char*)SvPVbyte(use_for, usedatalen); Newx(symkey, OTRL_EXTRAKEY_BYTES, unsigned char); err = otrl_message_symkey(ctx->userstate, &callbacks, channel /* opdata */, context, use, usedata, (size_t)usedatalen, symkey); XPUSHs( sv_2mortal( newSVpvn( (char*)symkey, OTRL_EXTRAKEY_BYTES ))); Safefree(symkey); XSRETURN( 1 ); } XSRETURN_UNDEF; } void finish(channel) SV * channel PROTOTYPE: $ PREINIT: OTRctx * ctx; char *contact_name; char *account_name; char *account_protocol; PPCODE: { HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); ctx = ACCOUNT2CTX(account); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); (void)hv_store((HV*)SvRV(channel), "gone_secure", 11, &PL_sv_no, 0); otrl_message_disconnect_all_instances( ctx->userstate, &callbacks, channel, account_name, account_protocol, contact_name ); XSRETURN_YES; } void write(channel, plain_text) SV * channel char * plain_text PROTOTYPE: $$ PREINIT: OTRctx * ctx; char *contact_name; char *account_name; char *account_protocol; PPCODE: { ConnContext *context; int context_created = 0; gcry_error_t err; char *newmessage = NULL; unsigned int selected_instag; HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); ctx = ACCOUNT2CTX(account); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); selected_instag = SvUV(HASHGET(channel, "selected_instag", 15)); context = otrl_context_find( ctx->userstate, contact_name, account_name, account_protocol, selected_instag, 0, &context_created, NULL, NULL ); if ( context && context->msgstate == OTRL_MSGSTATE_FINISHED ) { handle_msg_event_cb( (void*) channel, OTRL_MSGEVENT_CONNECTION_ENDED, context, NULL, err ); XSRETURN_NO; } err = otrl_message_sending(ctx->userstate, &callbacks, channel /* opdata */, account_name, account_protocol, contact_name, selected_instag, plain_text, NULL /* tlvs */, &newmessage, OTRL_FRAGMENT_SEND_ALL, &context, NULL /* add_app_info */, NULL /* add_app_info_data */); if ( err ) { otrl_message_free(newmessage); croak("Cannot send: %s", gcry_strerror(err)); } otrl_message_free(newmessage); XSRETURN_YES; } void ping(channel) SV * channel PROTOTYPE: $ PPCODE: { OTRctx * ctx = CHANNEL2CTX(channel); otrl_message_poll(ctx->userstate, &callbacks, channel); XSRETURN_YES; } void smp_verify(channel, answer, ...) SV * channel SV * answer PROTOTYPE: $$;$ PPCODE: { ConnContext *context; int context_created = 0; char * question = NULL; STRLEN slen; unsigned char * secret; OTRctx * ctx; char *contact_name; char * account_name; char * account_protocol; HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); ctx = CHANNEL2CTX(channel); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); context = otrl_context_find( ctx->userstate, contact_name, account_name, account_protocol, SvUV(HASHGET(channel, "selected_instag", 15)), 0, &context_created, NULL, NULL ); if ( ! context ) XSRETURN_NO; secret = (unsigned char*)SvPVbyte(answer, slen); if ( items == 3 && SvOK(ST(2)) ) { STRLEN qlen; question = SvPVbyte(ST(2), qlen); otrl_message_initiate_smp_q( ctx->userstate, &callbacks, channel, context, question, secret, slen ); } else { otrl_message_initiate_smp( ctx->userstate, &callbacks, channel, context, secret, slen ); } XSRETURN_YES; } void smp_respond(channel, response) SV * channel unsigned char *response PROTOTYPE: $$ PPCODE: { ConnContext *context; int context_created = 0; OTRctx * ctx; char *contact_name; char * account_name; char * account_protocol; HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); ctx = CHANNEL2CTX(channel); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); context = otrl_context_find( ctx->userstate, contact_name, account_name, account_protocol, SvUV(HASHGET(channel, "selected_instag", 15)), 0, &context_created, NULL, NULL ); if ( ! context ) XSRETURN_NO; otrl_message_respond_smp(ctx->userstate, &callbacks, channel, context, response, strlen((char*)response)); XSRETURN_YES; } void smp_abort(channel) SV * channel PROTOTYPE: $ PPCODE: { ConnContext *context; int context_created = 0; OTRctx * ctx; char *contact_name; char * account_name; char * account_protocol; HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); ctx = CHANNEL2CTX(channel); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); context = otrl_context_find( ctx->userstate, contact_name, account_name, account_protocol, SvUV(HASHGET(channel, "selected_instag", 15)), 0, &context_created, NULL, NULL ); if ( ! context ) XSRETURN_NO; otrl_message_abort_smp( ctx->userstate, &callbacks, channel, context ); XSRETURN_YES; } void read(channel, input) SV * channel char * input PROTOTYPE: $$ PPCODE: { int res; char *newmessage = NULL; OtrlTLV *tlvs = NULL; OtrlTLV *tlv = NULL; OtrlMessageType msgtype; ConnContext *context; HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); OTRctx *ctx = ACCOUNT2CTX(account); char *contact_name; char *account_name; char *account_protocol; if ( ! input ) { XSRETURN_NO; } contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); msgtype = otrl_proto_message_type(input); res = otrl_message_receiving(ctx->userstate, &callbacks, channel /* opdata */, account_name, account_protocol, contact_name, input, &newmessage, &tlvs, &context, NULL /* add_app_info */, NULL /* add_app_info_data */); if ( context ) { if (newmessage) { ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_read", 7 ))); XPUSHs( sv_2mortal( newSVpv( newmessage, 0 ))); PUTBACK; otrl_message_free(newmessage); call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; } else { otrl_message_free(newmessage); } } tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED); if (tlv) { if ( hv_exists((HV*)SvRV(channel), "on_gone_insecure", 16) ) { ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( channel ); XPUSHs( sv_2mortal( newSVpvn( "on_gone_insecure", 16 ))); PUTBACK; call_method( "_ev", G_DISCARD | G_VOID ); FREETMPS; LEAVE; (void)hv_store((HV*)SvRV(channel), "gone_secure", 11, &PL_sv_no, 0); } } otrl_tlv_free(tlvs); if ( res == 1) { int gone_secure = SvTRUE(HASHGET(channel, "gone_secure", 11)); /* gone_secure */ if ( gone_secure && ( /* our init */ msgtype == OTRL_MSGTYPE_REVEALSIG || /* their init */ msgtype == OTRL_MSGTYPE_SIGNATURE ) && context->msgstate == OTRL_MSGSTATE_ENCRYPTED && context->auth.authstate == OTRL_AUTHSTATE_NONE ) { gone_secure_cb( (void*) channel, context ); } XSRETURN_NO; } XSRETURN_YES; } void sessions(channel) SV * channel PROTOTYPE: $ PREINIT: HV * sessions; HE * entry; I32 count; PPCODE: { sessions = (HV*)SvRV(HASHGET(channel, "known_sessions", 14)); count = hv_iterinit(sessions); EXTEND(SP, count); while ( (entry = hv_iternext(sessions)) ) { I32 klen; char *key = hv_iterkey( entry, &klen ); XPUSHs(sv_2mortal(newSVpvn(key, klen))); } } void current_session(channel) SV * channel PROTOTYPE: $ PREINIT: ConnContext *context; OTRctx * ctx; char *contact_name; char * account_name; char * account_protocol; PPCODE: { HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); ctx = CHANNEL2CTX(channel); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); context = otrl_context_find( ctx->userstate, contact_name, account_name, account_protocol, SvUV(HASHGET(channel, "selected_instag", 15)), 0, NULL, NULL, NULL ); if ( context ) { XPUSHs( sv_2mortal( newSVuv(context->their_instance) ) ); XSRETURN(1); } XSRETURN_UNDEF; } void select_session(channel, session) SV * channel SV * session PROTOTYPE: $$ PREINIT: otrl_instag_t instag; ConnContext *context; OTRctx * ctx; char *contact_name; char *account_name; char *account_protocol; PPCODE: { HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); ctx = ACCOUNT2CTX(account); instag = SvUV(session); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); context = otrl_context_find( ctx->userstate, contact_name, account_name, account_protocol, instag, 0, NULL, NULL, NULL ); if ( context ) { (void)hv_store((HV*)SvRV(channel), "selected_instag", 6, newSVuv(instag), 0); send_default_query_msg(channel); XSRETURN_YES; } XSRETURN_NO; } void status(channel) SV * channel PROTOTYPE: $ PREINIT: ConnContext *context; Fingerprint *fingerprint; OTRctx *ctx; char *contact_name; char *account_name; char *account_protocol; PPCODE: { HV * contact = (HV*)CHANNEL2CONTACT(channel); HV * account = (HV*)CONTACT2ACCOUNT(contact); ctx = ACCOUNT2CTX(account); contact_name = SvPV_nolen(HASHGET(contact, "name", 4)); account_name = SvPV_nolen(HASHGET(account, "name", 4)); account_protocol = SvPV_nolen(HASHGET(account, "protocol", 8)); context = otrl_context_find( ctx->userstate, contact_name, account_name, account_protocol, SvUV(HASHGET(channel, "selected_instag", 15)), 0, NULL, NULL, NULL ); if (context) { TrustLevel this_level = otrp_plugin_context_to_trust(context); XPUSHs( newSVpv( TrustStates[this_level], 0 ) ); XSRETURN(1); } XSRETURN_UNDEF; }