--- a/otr.c +++ b/otr.c @@ -1,17 +1,19 @@ /* OTR support (cf. http://www.cypherpunks.ca/otr/) - (c) 2008-2011 Sven Moritz Hallberg - (c) 2008 funded by stonedcoder.org + (c) 2008-2011,2013 Sven Moritz Hallberg + funded by stonedcoder.org files used to store OTR data: /.otr_keys /.otr_fprints + /.otr_instags <- don't copy this one between hosts top-level todos: (search for TODO for more ;-)) integrate otr_load/otr_save with existing storage backends per-account policy settings per-user policy settings + add a way to select recipient instance */ /* @@ -59,9 +61,6 @@ int op_is_logged_in(void *opdata, const void op_inject_message(void *opdata, const char *accountname, const char *protocol, const char *recipient, const char *message); -int op_display_otr_message(void *opdata, const char *accountname, const char *protocol, - const char *username, const char *msg); - void op_new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, const char *protocol, const char *username, unsigned char fingerprint[20]); @@ -79,6 +78,20 @@ int op_max_message_size(void *opdata, Co const char *op_account_name(void *opdata, const char *account, const char *protocol); +void op_create_instag(void *opdata, const char *account, const char *protocol); + +void op_convert_msg(void *opdata, ConnContext *ctx, OtrlConvertType typ, + char **dst, const char *src); +void op_convert_free(void *opdata, ConnContext *ctx, char *msg); + +void op_handle_smp_event(void *opdata, OtrlSMPEvent ev, ConnContext *ctx, + unsigned short percent, char *question); + +void op_handle_msg_event(void *opdata, OtrlMessageEvent ev, ConnContext *ctx, + const char *message, gcry_error_t err); + +const char *op_otr_error_message(void *opdata, ConnContext *ctx, + OtrlErrorCode err_code); /** otr sub-command handlers: **/ @@ -140,6 +153,9 @@ void yes_forget_fingerprint(void *data); void yes_forget_context(void *data); void yes_forget_key(void *data); +/* timeout handler that calls otrl_message_poll */ +gboolean ev_message_poll(gpointer data, gint fd, b_input_condition cond); + /* helper to make sure accountname and protocol match the incoming "opdata" */ struct im_connection *check_imc(void *opdata, const char *accountname, const char *protocol); @@ -155,8 +171,11 @@ int hexval(char a); returns NULL if not found */ irc_user_t *peeruser(irc_t *irc, const char *handle, const char *protocol); -/* handle SMP TLVs from a received message */ -void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs); +/* show an otr-related message to the user */ +void display_otr_message(void *opdata, ConnContext *ctx, const char *fmt, ...); + +/* write an otr-related message to the system log */ +void log_otr_message(void *opdata, const char *fmt, ...); /* combined handler for the 'otr smp' and 'otr smpq' commands */ void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question, @@ -207,21 +226,29 @@ void init_plugin(void) otr_ops.create_privkey = &op_create_privkey; otr_ops.is_logged_in = &op_is_logged_in; otr_ops.inject_message = &op_inject_message; - otr_ops.notify = NULL; - otr_ops.display_otr_message = &op_display_otr_message; otr_ops.update_context_list = NULL; - otr_ops.protocol_name = NULL; - otr_ops.protocol_name_free = NULL; otr_ops.new_fingerprint = &op_new_fingerprint; otr_ops.write_fingerprints = &op_write_fingerprints; otr_ops.gone_secure = &op_gone_secure; otr_ops.gone_insecure = &op_gone_insecure; otr_ops.still_secure = &op_still_secure; - otr_ops.log_message = &op_log_message; otr_ops.max_message_size = &op_max_message_size; otr_ops.account_name = &op_account_name; otr_ops.account_name_free = NULL; - + + /* stuff added with libotr 4.0.0 */ + otr_ops.received_symkey = NULL; /* we don't use the extra key */ + otr_ops.otr_error_message = &op_otr_error_message; + otr_ops.otr_error_message_free = NULL; + otr_ops.resent_msg_prefix = NULL; /* default: [resent] */ + otr_ops.resent_msg_prefix_free = NULL; + otr_ops.handle_smp_event = &op_handle_smp_event; + otr_ops.handle_msg_event = &op_handle_msg_event; + otr_ops.create_instag = &op_create_instag; + otr_ops.convert_msg = &op_convert_msg; + otr_ops.convert_free = &op_convert_free; + otr_ops.timer_control = NULL; /* we just poll */ + root_command_add( "otr", 1, cmd_otr, 0 ); register_irc_plugin( &otr_plugin ); } @@ -245,12 +272,17 @@ gboolean otr_irc_new(irc_t *irc) s = set_add( &irc->b->set, "otr_does_html", "true", set_eval_bool, irc ); + /* regularly call otrl_message_poll */ + gint definterval = otrl_message_poll_get_default_interval(irc->otr->us); + irc->otr->timer = b_timeout_add(definterval, ev_message_poll, irc->otr); + return TRUE; } void otr_irc_free(irc_t *irc) { otr_t *otr = irc->otr; + b_event_remove(otr->timer); otrl_userstate_free(otr->us); if(otr->keygen) { kill(otr->keygen, SIGTERM); @@ -288,6 +320,11 @@ void otr_load(irc_t *irc) if(e && e!=enoent) { irc_rootmsg(irc, "otr load: %s: %s", s, gcry_strerror(e)); } + g_snprintf(s, 511, "%s%s.otr_instags", global.conf->configdir, irc->user->nick); + e = otrl_instag_read(irc->otr->us, s); + if(e && e!=enoent) { + irc_rootmsg(irc, "otr load: %s: %s", s, gcry_strerror(e)); + } } /* check for otr keys on all accounts */ @@ -385,10 +422,8 @@ char *otr_filter_msg_in(irc_user_t *iu, ignore_msg = otrl_message_receiving(irc->otr->us, &otr_ops, ic, ic->acc->user, ic->acc->prpl->name, iu->bu->handle, msg, &newmsg, - &tlvs, NULL, NULL); + &tlvs, NULL, NULL, NULL); - otr_handle_smp(ic, iu->bu->handle, tlvs); - if(ignore_msg) { /* this was an internal OTR protocol message */ return NULL; @@ -396,57 +431,8 @@ char *otr_filter_msg_in(irc_user_t *iu, /* this was a non-OTR message */ return msg; } else { - /* OTR has processed this message */ - ConnContext *context = otrl_context_find(irc->otr->us, iu->bu->handle, - ic->acc->user, ic->acc->prpl->name, 0, NULL, NULL, NULL); - /* we're done with the original msg, which will be caller-freed. */ - /* NB: must not change the newmsg pointer, since we free it. */ - msg = newmsg; - - if(context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED) { - /* HTML decoding */ - /* perform any necessary stripping that the top level would miss */ - if(set_getbool(&ic->bee->set, "otr_does_html") && - !(ic->flags & OPT_DOES_HTML) && - set_getbool(&ic->bee->set, "strip_html")) { - strip_html(msg); - } - - /* coloring */ - if(set_getbool(&ic->bee->set, "otr_color_encrypted")) { - int color; /* color according to f'print trust */ - char *pre="", *sep=""; /* optional parts */ - const char *trust = context->active_fingerprint->trust; - - if(trust && trust[0] != '\0') - color=3; /* green */ - else - color=5; /* red */ - - /* in a query window, keep "/me " uncolored at the beginning */ - if(g_strncasecmp(msg, "/me ", 4) == 0 - && irc_user_msgdest(iu) == irc->user->nick) { - msg += 4; /* skip */ - pre = "/me "; - } - - /* comma in first place could mess with the color code */ - if(msg[0] == ',') { - /* insert a space between color spec and message */ - sep = " "; - } - - msg = g_strdup_printf("%s\x03%.2d%s%s\x0F", pre, - color, sep, msg); - } - } - - if(msg == newmsg) { - msg = g_strdup(newmsg); - } - otrl_message_free(newmsg); - return msg; + return newmsg; } } @@ -458,49 +444,26 @@ char *otr_filter_msg_out(irc_user_t *iu, ConnContext *ctx = NULL; irc_t *irc = iu->irc; struct im_connection *ic = iu->bu->ic; + otrl_instag_t instag = OTRL_INSTAG_RECENT; // XXX? /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ if(ic->acc->prpl->options & OPT_NOOTR) { return msg; } - ctx = otrl_context_find(irc->otr->us, - iu->bu->handle, ic->acc->user, ic->acc->prpl->name, - 1, NULL, NULL, NULL); - - /* HTML encoding */ - /* consider OTR plaintext to be HTML if otr_does_html is set */ - if(ctx && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED && - set_getbool(&ic->bee->set, "otr_does_html") && - (g_strncasecmp(msg, "", 6) != 0)) { - emsg = escape_html(msg); - } - st = otrl_message_sending(irc->otr->us, &otr_ops, ic, - ic->acc->user, ic->acc->prpl->name, iu->bu->handle, - emsg, NULL, &otrmsg, NULL, NULL); + ic->acc->user, ic->acc->prpl->name, iu->bu->handle, instag, + emsg, NULL, &otrmsg, OTRL_FRAGMENT_SEND_ALL, &ctx, NULL, NULL); + /* in libotr 4.0.0 with OTRL_FRAGMENT_SEND_ALL, otrmsg must be passed + * but the value it gets carries no meaning. it can be set even though + * the message has been injected. */ + if(emsg != msg) { g_free(emsg); /* we're done with this one */ } if(st) { - return NULL; + /* TODO: Error reporting? */ } - - if(otrmsg) { - if(!ctx) { - otrl_message_free(otrmsg); - return NULL; - } - st = otrl_message_fragment_and_send(&otr_ops, ic, ctx, - otrmsg, OTRL_FRAGMENT_SEND_ALL, NULL); - otrl_message_free(otrmsg); - } else { - /* note: otrl_message_sending handles policy, so that if REQUIRE_ENCRYPTION is set, - this case does not occur */ - return msg; - } - - /* TODO: Error reporting should be done here now (if st!=0), probably. */ return NULL; } @@ -619,26 +582,6 @@ void op_inject_message(void *opdata, con } } -int op_display_otr_message(void *opdata, const char *accountname, - const char *protocol, const char *username, const char *message) -{ - struct im_connection *ic = check_imc(opdata, accountname, protocol); - char *msg = g_strdup(message); - irc_t *irc = ic->bee->ui_data; - irc_user_t *u = peeruser(irc, username, protocol); - - strip_html(msg); - if(u) { - /* display as a notice from this particular user */ - irc_usernotice(u, "%s", msg); - } else { - irc_rootmsg(irc, "[otr] %s", msg); - } - - g_free(msg); - return 0; -} - void op_new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, const char *protocol, const char *username, unsigned char fingerprint[20]) @@ -690,6 +633,9 @@ void op_gone_secure(void *opdata, ConnCo void op_gone_insecure(void *opdata, ConnContext *context) { + /* XXX on 'otr disconnect', this gets called for every instance and we + * get the message multiple times... */ + struct im_connection *ic = check_imc(opdata, context->accountname, context->protocol); irc_t *irc = ic->bee->ui_data; @@ -729,20 +675,11 @@ void op_still_secure(void *opdata, ConnC } } -void op_log_message(void *opdata, const char *message) -{ - char *msg = g_strdup(message); - - strip_html(msg); - log_message(LOGLVL_INFO, "otr: %s", msg); - g_free(msg); -} - int op_max_message_size(void *opdata, ConnContext *context) { struct im_connection *ic = check_imc(opdata, context->accountname, context->protocol); - + return ic->acc->prpl->mms; } @@ -754,6 +691,246 @@ const char *op_account_name(void *opdata return peernick(irc, account, protocol); } +void op_create_instag(void *opdata, const char *account, const char *protocol) +{ + struct im_connection *ic = + check_imc(opdata, account, protocol); + irc_t *irc = ic->bee->ui_data; + gcry_error_t e; + char s[512]; + + g_snprintf(s, 511, "%s%s.otr_instags", global.conf->configdir, + irc->user->nick); + e = otrl_instag_generate(irc->otr->us, s, account, protocol); + if(e) { + irc_rootmsg(irc, "otr: %s/%s: otrl_instag_generate failed: %s", + account, protocol, gcry_strerror(e)); + } +} + +void op_convert_msg(void *opdata, ConnContext *ctx, OtrlConvertType typ, + char **dst, const char *src) +{ + struct im_connection *ic = + check_imc(opdata, ctx->accountname, ctx->protocol); + irc_t *irc = ic->bee->ui_data; + irc_user_t *iu = peeruser(irc, ctx->username, ctx->protocol); + + if(typ == OTRL_CONVERT_RECEIVING) { + char *msg = g_strdup(src); + + /* HTML decoding */ + if(set_getbool(&ic->bee->set, "otr_does_html") && + !(ic->flags & OPT_DOES_HTML) && + set_getbool(&ic->bee->set, "strip_html")) { + strip_html(msg); + *dst = msg; + } + + /* coloring */ + if(set_getbool(&ic->bee->set, "otr_color_encrypted")) { + int color; /* color according to f'print trust */ + char *pre="", *sep=""; /* optional parts */ + const char *trust = ctx->active_fingerprint->trust; + + if(trust && trust[0] != '\0') + color=3; /* green */ + else + color=5; /* red */ + + /* in a query window, keep "/me " uncolored at the beginning */ + if(g_strncasecmp(msg, "/me ", 4) == 0 + && irc_user_msgdest(iu) == irc->user->nick) { + msg += 4; /* skip */ + pre = "/me "; + } + + /* comma in first place could mess with the color code */ + if(msg[0] == ',') { + /* insert a space between color spec and message */ + sep = " "; + } + + *dst = g_strdup_printf("%s\x03%.2d%s%s\x0F", pre, + color, sep, msg); + g_free(msg); + } + } else { + /* HTML encoding */ + /* consider OTR plaintext to be HTML if otr_does_html is set */ + if(ctx && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED && + set_getbool(&ic->bee->set, "otr_does_html") && + (g_strncasecmp(src, "", 6) != 0)) { + *dst = escape_html(src); + } + } +} + +void op_convert_free(void *opdata, ConnContext *ctx, char *msg) +{ + g_free(msg); +} + +/* Socialist Millionaires' Protocol */ +void op_handle_smp_event(void *opdata, OtrlSMPEvent ev, ConnContext *ctx, + unsigned short percent, char *question) +{ + struct im_connection *ic = + check_imc(opdata, ctx->accountname, ctx->protocol); + irc_t *irc = ic->bee->ui_data; + OtrlUserState us = irc->otr->us; + irc_user_t *u = peeruser(irc, ctx->username, ctx->protocol); + + if(!u) return; + + switch(ev) { + case OTRL_SMPEVENT_ASK_FOR_SECRET: + irc_rootmsg(irc, "smp: initiated by %s" + " - respond with \x02otr smp %s \x02", + u->nick, u->nick); + break; + case OTRL_SMPEVENT_ASK_FOR_ANSWER: + irc_rootmsg(irc, "smp: initiated by %s with question: \x02\"%s\"\x02", u->nick, + question); + irc_rootmsg(irc, "smp: respond with \x02otr smp %s \x02", + u->nick); + break; + case OTRL_SMPEVENT_CHEATED: + irc_rootmsg(irc, "smp %s: opponent violated protocol, aborting", + u->nick); + otrl_message_abort_smp(us, &otr_ops, u->bu->ic, ctx); + otrl_sm_state_free(ctx->smstate); + break; + case OTRL_SMPEVENT_NONE: + break; + case OTRL_SMPEVENT_IN_PROGRESS: + break; + case OTRL_SMPEVENT_SUCCESS: + if(ctx->smstate->received_question) { + irc_rootmsg(irc, "smp %s: correct answer, you are trusted", + u->nick); + } else { + irc_rootmsg(irc, "smp %s: secrets proved equal, fingerprint trusted", + u->nick); + } + otrl_sm_state_free(ctx->smstate); + break; + case OTRL_SMPEVENT_FAILURE: + if(ctx->smstate->received_question) { + irc_rootmsg(irc, "smp %s: wrong answer, you are not trusted", + u->nick); + } else { + irc_rootmsg(irc, "smp %s: secrets did not match, fingerprint not trusted", + u->nick); + } + otrl_sm_state_free(ctx->smstate); + break; + case OTRL_SMPEVENT_ABORT: + irc_rootmsg(irc, "smp: received abort from %s", u->nick); + otrl_sm_state_free(ctx->smstate); + break; + case OTRL_SMPEVENT_ERROR: + irc_rootmsg(irc, "smp %s: protocol error, aborting", + u->nick); + otrl_message_abort_smp(us, &otr_ops, u->bu->ic, ctx); + otrl_sm_state_free(ctx->smstate); + break; + } +} + +void op_handle_msg_event(void *opdata, OtrlMessageEvent ev, ConnContext *ctx, + const char *message, gcry_error_t err) +{ + switch(ev) { + case OTRL_MSGEVENT_ENCRYPTION_REQUIRED: + display_otr_message(opdata, ctx, + "policy requires encryption - message not sent"); + break; + case OTRL_MSGEVENT_ENCRYPTION_ERROR: + display_otr_message(opdata, ctx, + "error during encryption - message not sent"); + break; + case OTRL_MSGEVENT_CONNECTION_ENDED: + display_otr_message(opdata, ctx, + "other end has disconnected OTR - " + "close connection or reconnect!"); + break; + case OTRL_MSGEVENT_SETUP_ERROR: + display_otr_message(opdata, ctx, + "OTR connection failed: %s", gcry_strerror(err)); + break; + case OTRL_MSGEVENT_MSG_REFLECTED: + display_otr_message(opdata, ctx, + "received our own OTR message (!?)"); + break; + case OTRL_MSGEVENT_MSG_RESENT: + display_otr_message(opdata, ctx, + "the previous message was resent"); + break; + case OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE: + display_otr_message(opdata, ctx, + "unexpected encrypted message received"); + break; + case OTRL_MSGEVENT_RCVDMSG_UNREADABLE: + display_otr_message(opdata, ctx, + "unreadable encrypted message received"); + break; + case OTRL_MSGEVENT_RCVDMSG_MALFORMED: + display_otr_message(opdata, ctx, + "malformed OTR message received"); + break; + case OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD: + if(global.conf->verbose) { + log_otr_message(opdata, "%s/%s: heartbeat received", + ctx->accountname, ctx->protocol); + } + break; + case OTRL_MSGEVENT_LOG_HEARTBEAT_SENT: + if(global.conf->verbose) { + log_otr_message(opdata, "%s/%s: heartbeat sent", + ctx->accountname, ctx->protocol); + } + break; + case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR: + display_otr_message(opdata, ctx, + "OTR error message received: %s", message); + break; + case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED: + display_otr_message(opdata, ctx, + "unencrypted message received: %s", message); + break; + case OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED: + display_otr_message(opdata, ctx, + "unrecognized OTR message received"); + break; + case OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE: + display_otr_message(opdata, ctx, + "OTR message for a different instance received"); + break; + default: + /* shouldn't happen */ + break; + } +} + +const char *op_otr_error_message(void *opdata, ConnContext *ctx, + OtrlErrorCode err_code) +{ + switch(err_code) { + case OTRL_ERRCODE_ENCRYPTION_ERROR: + return "i failed to encrypt a message"; + case OTRL_ERRCODE_MSG_NOT_IN_PRIVATE: + return "you sent an encrypted message i didn't expect"; + case OTRL_ERRCODE_MSG_UNREADABLE: + return "could not read encrypted message"; + case OTRL_ERRCODE_MSG_MALFORMED: + return "you sent a malformed OTR message"; + default: + return "i suffered an unexpected OTR error"; + } +} + + /*** OTR sub-command handlers ***/ @@ -773,19 +950,24 @@ void cmd_otr_disconnect(irc_t *irc, char return; } - otrl_message_disconnect(irc->otr->us, &otr_ops, + /* XXX we disconnect all instances; is that what we want? */ + otrl_message_disconnect_all_instances(irc->otr->us, &otr_ops, u->bu->ic, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, u->bu->handle); - /* for some reason, libotr (3.1.0) doesn't do this itself: */ - if(u->flags & IRC_USER_OTR_ENCRYPTED) { - ConnContext *ctx; - ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, - u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); - if(ctx) - op_gone_insecure(u->bu->ic, ctx); - else /* huh? */ - u->flags &= ( IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED ); + /* for some reason, libotr (4.0.0) doesn't do this itself: */ + if(!(u->flags & IRC_USER_OTR_ENCRYPTED)) + return; + + ConnContext *ctx, *p; + ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, + u->bu->ic->acc->prpl->name, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); + if(!ctx) { /* huh? */ + u->flags &= ( IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED ); + return; } + + for(p=ctx; p && p->m_context == ctx->m_context; p=p->next) + op_gone_insecure(u->bu->ic, p); } void cmd_otr_connect(irc_t *irc, char **args) @@ -802,7 +984,9 @@ void cmd_otr_connect(irc_t *irc, char ** return; } - bee_user_msg(irc->b, u->bu, "?OTR?v2?", 0); + /* passing this through the filter so it goes through libotr which + * will replace the simple query string with a proper one */ + otr_filter_msg_out(u, "?OTR?", 0); } void cmd_otr_smp(irc_t *irc, char **args) @@ -830,7 +1014,7 @@ void cmd_otr_trust(irc_t *irc, char **ar } ctx = otrl_context_find(irc->otr->us, u->bu->handle, - u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if(!ctx) { irc_rootmsg(irc, "%s: no otr context with user", args[1]); return; @@ -894,7 +1078,7 @@ void cmd_otr_info(irc_t *irc, char **arg if(protocol && myhandle) { *(myhandle++) = '\0'; handle = arg; - ctx = otrl_context_find(irc->otr->us, handle, myhandle, protocol, 0, NULL, NULL, NULL); + ctx = otrl_context_find(irc->otr->us, handle, myhandle, protocol, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if(!ctx) { irc_rootmsg(irc, "no such context"); g_free(arg); @@ -908,7 +1092,7 @@ void cmd_otr_info(irc_t *irc, char **arg return; } ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, - u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + u->bu->ic->acc->prpl->name, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if(!ctx) { irc_rootmsg(irc, "no otr context with %s", args[1]); g_free(arg); @@ -981,6 +1165,8 @@ void yes_forget_context(void *data) ConnContext *ctx = (ConnContext *)p->snd; g_free(p); + + // XXX forget all contexts if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { irc_rootmsg(irc, "active otr connection with %s, terminate it first", @@ -1027,7 +1213,7 @@ void cmd_otr_forget(irc_t *irc, char **a } ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, - u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + u->bu->ic->acc->prpl->name, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if(!ctx) { irc_rootmsg(irc, "no otr context with %s", args[2]); return; @@ -1070,7 +1256,7 @@ void cmd_otr_forget(irc_t *irc, char **a } ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, - u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + u->bu->ic->acc->prpl->name, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if(!ctx) { irc_rootmsg(irc, "no otr context with %s", args[2]); return; @@ -1118,133 +1304,37 @@ void cmd_otr_forget(irc_t *irc, char **a /*** local helpers / subroutines: ***/ -/* Socialist Millionaires' Protocol */ -void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs) +void log_otr_message(void *opdata, const char *fmt, ...) { + va_list va; + + va_start(va, fmt); + char *msg = g_strdup_vprintf(fmt, va); + va_end(va); + + log_message(LOGLVL_INFO, "otr: %s", msg); +} + +void display_otr_message(void *opdata, ConnContext *ctx, const char *fmt, ...) +{ + struct im_connection *ic = + check_imc(opdata, ctx->accountname, ctx->protocol); irc_t *irc = ic->bee->ui_data; - OtrlUserState us = irc->otr->us; - OtrlMessageAppOps *ops = &otr_ops; - OtrlTLV *tlv = NULL; - ConnContext *context; - NextExpectedSMP nextMsg; - irc_user_t *u; - bee_user_t *bu; + irc_user_t *u = peeruser(irc, ctx->username, ctx->protocol); + va_list va; - bu = bee_user_by_handle(ic->bee, ic, handle); - if(!bu || !(u = bu->ui_data)) return; - context = otrl_context_find(us, handle, - ic->acc->user, ic->acc->prpl->name, 1, NULL, NULL, NULL); - if(!context) { - /* huh? out of memory or what? */ - irc_rootmsg(irc, "smp: failed to get otr context for %s", u->nick); - otrl_message_abort_smp(us, ops, u->bu->ic, context); - otrl_sm_state_free(context->smstate); - return; - } - nextMsg = context->smstate->nextExpected; + va_start(va, fmt); + char *msg = g_strdup_vprintf(fmt, va); + va_end(va); - if (context->smstate->sm_prog_state == OTRL_SMP_PROG_CHEATED) { - irc_rootmsg(irc, "smp %s: opponent violated protocol, aborting", - u->nick); - otrl_message_abort_smp(us, ops, u->bu->ic, context); - otrl_sm_state_free(context->smstate); - return; + if(u) { + /* display as a notice from this particular user */ + irc_usernotice(u, "%s", msg); + } else { + irc_rootmsg(irc, "[otr] %s", msg); } - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1Q); - if (tlv) { - if (nextMsg != OTRL_SMP_EXPECT1) { - irc_rootmsg(irc, "smp %s: spurious SMP1Q received, aborting", u->nick); - otrl_message_abort_smp(us, ops, u->bu->ic, context); - otrl_sm_state_free(context->smstate); - } else { - char *question = g_strndup((char *)tlv->data, tlv->len); - irc_rootmsg(irc, "smp: initiated by %s with question: \x02\"%s\"\x02", u->nick, - question); - irc_rootmsg(irc, "smp: respond with \x02otr smp %s \x02", - u->nick); - g_free(question); - /* smp stays in EXPECT1 until user responds */ - } - } - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1); - if (tlv) { - if (nextMsg != OTRL_SMP_EXPECT1) { - irc_rootmsg(irc, "smp %s: spurious SMP1 received, aborting", u->nick); - otrl_message_abort_smp(us, ops, u->bu->ic, context); - otrl_sm_state_free(context->smstate); - } else { - irc_rootmsg(irc, "smp: initiated by %s" - " - respond with \x02otr smp %s \x02", - u->nick, u->nick); - /* smp stays in EXPECT1 until user responds */ - } - } - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2); - if (tlv) { - if (nextMsg != OTRL_SMP_EXPECT2) { - irc_rootmsg(irc, "smp %s: spurious SMP2 received, aborting", u->nick); - otrl_message_abort_smp(us, ops, u->bu->ic, context); - otrl_sm_state_free(context->smstate); - } else { - /* SMP2 received, otrl_message_receiving will have sent SMP3 */ - context->smstate->nextExpected = OTRL_SMP_EXPECT4; - } - } - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3); - if (tlv) { - if (nextMsg != OTRL_SMP_EXPECT3) { - irc_rootmsg(irc, "smp %s: spurious SMP3 received, aborting", u->nick); - otrl_message_abort_smp(us, ops, u->bu->ic, context); - otrl_sm_state_free(context->smstate); - } else { - /* SMP3 received, otrl_message_receiving will have sent SMP4 */ - if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) { - if(context->smstate->received_question) { - irc_rootmsg(irc, "smp %s: correct answer, you are trusted", - u->nick); - } else { - irc_rootmsg(irc, "smp %s: secrets proved equal, fingerprint trusted", - u->nick); - } - } else { - if(context->smstate->received_question) { - irc_rootmsg(irc, "smp %s: wrong answer, you are not trusted", - u->nick); - } else { - irc_rootmsg(irc, "smp %s: secrets did not match, fingerprint not trusted", - u->nick); - } - } - otrl_sm_state_free(context->smstate); - /* smp is in back in EXPECT1 */ - } - } - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4); - if (tlv) { - if (nextMsg != OTRL_SMP_EXPECT4) { - irc_rootmsg(irc, "smp %s: spurious SMP4 received, aborting", u->nick); - otrl_message_abort_smp(us, ops, u->bu->ic, context); - otrl_sm_state_free(context->smstate); - } else { - /* SMP4 received, otrl_message_receiving will have set fp trust */ - if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) { - irc_rootmsg(irc, "smp %s: secrets proved equal, fingerprint trusted", - u->nick); - } else { - irc_rootmsg(irc, "smp %s: secrets did not match, fingerprint not trusted", - u->nick); - } - otrl_sm_state_free(context->smstate); - /* smp is in back in EXPECT1 */ - } - } - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT); - if (tlv) { - irc_rootmsg(irc, "smp: received abort from %s", u->nick); - otrl_sm_state_free(context->smstate); - /* smp is in back in EXPECT1 */ - } + g_free(msg); } /* combined handler for the 'otr smp' and 'otr smpq' commands */ @@ -1253,6 +1343,7 @@ void otr_smp_or_smpq(irc_t *irc, const c { irc_user_t *u; ConnContext *ctx; + otrl_instag_t instag = OTRL_INSTAG_RECENT; // XXX u = irc_user_by_name(irc, nick); if(!u || !u->bu || !u->bu->ic) { @@ -1265,7 +1356,7 @@ void otr_smp_or_smpq(irc_t *irc, const c } ctx = otrl_context_find(irc->otr->us, u->bu->handle, - u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, instag, 0, NULL, NULL, NULL); if(!ctx || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED) { irc_rootmsg(irc, "smp: otr inactive with %s, try \x02otr connect" " %s\x02", nick, nick); @@ -1306,6 +1397,17 @@ void otr_smp_or_smpq(irc_t *irc, const c } } +/* timeout handler that calls otrl_message_poll */ +gboolean ev_message_poll(gpointer data, gint fd, b_input_condition cond) +{ + otr_t *otr = data; + + if(otr && otr->us) + otrl_message_poll(otr->us, &otr_ops, NULL); + + return TRUE; /* cycle timer */ +} + /* helper to assert that account and protocol names given to ops below always match the im_connection passed through as opdata */ struct im_connection *check_imc(void *opdata, const char *accountname, @@ -1313,6 +1415,21 @@ struct im_connection *check_imc(void *op { struct im_connection *ic = (struct im_connection *)opdata; + /* libotr 4.0.0 has a bug where it doesn't set opdata, so we catch + * that and try to find the desired connection in the global list. */ + if(!ic) { + GSList *l; + for(l=get_connections(); l; l=l->next) { + ic = l->data; + if(strcmp(accountname, ic->acc->user) == 0 && + strcmp(protocol, ic->acc->prpl->name) == 0) + break; + } + assert(l != NULL); /* a match should always be found */ + if(!l) + return NULL; + } + if (strcmp(accountname, ic->acc->user) != 0) { log_message(LOGLVL_WARNING, "otr: internal account name mismatch: '%s' vs '%s'", @@ -1593,6 +1710,8 @@ void show_general_otr_info(irc_t *irc) irc_rootmsg(irc, " (none)"); /* list all contexts */ + /* XXX remove this, or split off as its own command */ + /* XXX show instags? */ irc_rootmsg(irc, "%s", ""); irc_rootmsg(irc, "\x1f" "connection contexts:\x1f (bold=currently encrypted)"); for(ctx=irc->otr->us->context_root; ctx; ctx=ctx->next) {\ @@ -1621,6 +1740,8 @@ void show_general_otr_info(irc_t *irc) void show_otr_context_info(irc_t *irc, ConnContext *ctx) { + // XXX show all instags/subcontexts + switch(ctx->otr_offer) { case OFFER_NOT: irc_rootmsg(irc, " otr offer status: none sent"); --- a/otr.h +++ b/otr.h @@ -7,8 +7,8 @@ /* OTR support (cf. http://www.cypherpunks.ca/otr/) - 2008, Sven Moritz Hallberg - (c) and funded by stonedcoder.org + (c) 2008,2013 Sven Moritz Hallberg + funded by stonedcoder.org */ /* @@ -65,6 +65,9 @@ typedef struct otr { /* keygen jobs waiting to be sent to slave */ kg_t *todo; + + /* event timer for otrl_message_poll */ + gint timer; } otr_t; /* called from main() */