/* * Off-the-Record communication support. * * mICQ OTR extension Copyright (C) © 2007 Robert Bartel * * This extension 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; version 2 dated June, 1991. * * This extension 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. * * In addition, as a special exception permission is granted to link the * code of this release of mICQ with the OpenSSL project's "OpenSSL" * library, and distribute the linked executables. You must obey the GNU * General Public License in all respects for all of the code used other * than "OpenSSL". If you modify this file, you may extend this exception * to your version of the file, but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your version * of this file. * * You should have received a copy of the GNU General Public License * along with this package; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * $Id$ */ #include "micq.h" #include "util_otr.h" #ifdef ENABLE_OTR #include #include #include #include #include #include #include #include "contact.h" #include "connection.h" #include "preferences.h" #include "im_request.h" #include "util_str.h" #include "util_opts.h" /* log OTR messages? */ #define OTR_ENABLE_LOG (1) /* Holds private keys + known fingerprints + context */ static OtrlUserState userstate = NULL; /* filename for private keys / fingerprints / logfile */ #define KEYFILENAME ("otr.key") #define FPRFILENAME ("otr.fpr") #define LOGFILENAME ("otr.log") static str_s keyfile = {0, 0, 0}; static str_s fprfile = {0, 0, 0}; static FILE *log_file = NULL; /* callback prototypes */ static OtrlPolicy cb_policy (void *opdata, ConnContext *context); static void cb_create_privkey (void *opdata, const char *accountname, const char *protocol); static int cb_is_logged_in (void *opdata, const char *accountname, const char *protocol, const char *recipient); static void cb_inject_message (void *opdata, const char *accountname, const char *protocol, const char *recipient, const char *message); static void cb_notify (void *opdata, OtrlNotifyLevel level, const char *accountname, const char *protocol, const char *username, const char *title, const char *primary, const char *secondary); static int cb_display_otr_message (void *opdata, const char *accountname, const char *protocol, const char *username, const char *msg); static void cb_update_context_list (void *opdata); static const char *cb_protocol_name (void *opdata, const char *protocol); static void cb_protocol_name_free (void *opdata, const char *protocol_name); static void cb_new_fingerprint (void *opdata, OtrlUserState us, const char *accountname, const char *protocol, const char *username, unsigned char fingerprint[20]); static void cb_write_fingerprints (void *opdata); static void cb_gone_secure (void *opdata, ConnContext *context); static void cb_gone_insecure (void *opdata, ConnContext *context); static void cb_still_secure (void *opdata, ConnContext *context, int is_reply); static void cb_log_message (void *opdata, const char *message); /* Callback structure */ static OtrlMessageAppOps ops = { policy: cb_policy, create_privkey: cb_create_privkey, is_logged_in: cb_is_logged_in, inject_message: cb_inject_message, notify: cb_notify, display_otr_message: cb_display_otr_message, update_context_list: cb_update_context_list, protocol_name: cb_protocol_name, protocol_name_free: cb_protocol_name_free, new_fingerprint: cb_new_fingerprint, write_fingerprints: cb_write_fingerprints, gone_secure: cb_gone_secure, gone_insecure: cb_gone_insecure, still_secure: cb_still_secure, log_message: cb_log_message }; /* connection type to protocol name mapping */ static const char *proto_name (UWORD proto_type) { const char *proto; proto = ConnectionServerType (proto_type); /* make icq8, icq5 and peer the same */ if (!strncmp (proto, "icq", 3) || !strcmp (proto, "peer")) proto = "icq"; return proto; } /* protocol name to connection type mapping */ static UWORD proto_type (const char *proto_name) { UWORD type; if (!strcmp (proto_name, "icq")) type = TYPEF_HAVEUIN | TYPEF_ANY_SERVER; else type = ConnectionServerNType (proto_name, 0); return type; } /* otr context to contact */ static Contact *find_contact (const char *account, const char *proto, const char *contact) { Connection *conn; Contact *cont; /* may there be more than 1 TYPEF_HAVEUIN connection for a screen name? */ conn = ConnectionFindScreen (proto_type (proto), account); if (!conn) { rl_print (COLDEBUG); rl_printf (i18n (2642, "util_otr: no connection found for account %s and protocol %s"), account, proto); rl_printf ("%s\n", COLNONE); return NULL; } cont = ContactScreen (conn, contact); if (!cont) { rl_print (COLDEBUG); rl_printf (i18n (2643, "util_otr: no contact %s found"), contact); rl_printf ("%s\n", COLNONE); return NULL; } return cont; } static void add_app_data_find (void *data, ConnContext *context) { if (context->app_data) return; if (data) context->app_data = data; data = find_contact (context->accountname, context->protocol, context->username); assert (data); context->app_data = data; } /* contact to otr context */ static ConnContext *find_context (Contact *cont, int create) { ConnContext *ctx; int created = 0; if (!cont || !userstate || !uiG.conn) return NULL; ctx = otrl_context_find (userstate, cont->screen, cont->serv->screen, proto_name (cont->serv->type), create, &created, add_app_data_find, cont); if (ctx) assert (ctx->app_data == cont); return ctx; } /* returns a newly allocated session id string */ static char *readable_sid (unsigned char *data, size_t len, OtrlSessionIdHalf half) { unsigned int i; char *sid, *tmp; if (!data || !len) return NULL; sid = malloc (len * 3 + 2); if (!sid) return NULL; if (half == OTRL_SESSIONID_FIRST_HALF_BOLD) { sid[0] = '['; tmp = sid + 1; } else tmp = sid; for (i = 0; i < len; i++) { if (i == len/2) { tmp[3*i-1] = (half == OTRL_SESSIONID_FIRST_HALF_BOLD)? ']' : ' '; tmp[3*i] = (half == OTRL_SESSIONID_FIRST_HALF_BOLD)? ' ' : '['; tmp++; } snprintf (tmp + 3*i, 3*(len - i), "%02hhX:", data[i]); } if (half != OTRL_SESSIONID_FIRST_HALF_BOLD) sid[len * 3] = ']'; sid[len * 3 + 2 - 1] = 0; return sid; } /* Prints OTR connection state */ static void print_context (ConnContext *ctx) { const char *state, *auth, *policy; OtrlPolicy p = cb_policy (ctx->app_data, ctx); if (!userstate || !ctx) return; switch (ctx->msgstate) { case OTRL_MSGSTATE_PLAINTEXT: state = i18n (2665, "plaintext"); break; case OTRL_MSGSTATE_ENCRYPTED: state = i18n (2666, "encrypted"); break; case OTRL_MSGSTATE_FINISHED: state = i18n (2667, "finished"); break; default: state = i18n (2668, "unknown state"); } switch (ctx->auth.authstate) { case OTRL_AUTHSTATE_NONE: switch (ctx->otr_offer) { case OFFER_NOT: auth = i18n (2669, "no offer sent"); break; case OFFER_SENT: auth = i18n (2670, "offer sent"); break; case OFFER_ACCEPTED: auth = i18n (2671, "offer accepted"); break; case OFFER_REJECTED: auth = i18n (2672, "offer rejected"); break; default: auth = i18n (2673, "unknown auth"); } break; case OTRL_AUTHSTATE_AWAITING_DHKEY: auth = i18n (2674, "awaiting D-H key"); break; case OTRL_AUTHSTATE_AWAITING_REVEALSIG: auth = i18n (2675, "awaiting reveal signature"); break; case OTRL_AUTHSTATE_AWAITING_SIG: auth = i18n (2676, "awaiting signature"); break; case OTRL_AUTHSTATE_V1_SETUP: auth = i18n (2677, "v1 setup"); break; default: auth = i18n (2673, "unknown auth"); } if (p == OTRL_POLICY_NEVER) policy = i18n (2678, "never"); else if (p == OTRL_POLICY_OPPORTUNISTIC) policy = i18n (2679, "try"); else if (p == OTRL_POLICY_MANUAL) policy = i18n (2680, "manual"); else if (p == (OTRL_POLICY_ALWAYS & ~OTRL_POLICY_ALLOW_V1)) policy = i18n (2681, "always"); else policy = i18n (2682, "unknown"); rl_printf ("%s/%s/%s: %s%s%s (%s) [%s]\n", ctx->accountname, ctx->protocol, ctx->username, COLQUOTE, state, COLNONE, auth, policy); if (ctx->active_fingerprint && ctx->active_fingerprint->fingerprint) { char fpr[45], *tr; otrl_privkey_hash_to_human (fpr, ctx->active_fingerprint->fingerprint); tr = ctx->active_fingerprint->trust; rl_printf (" %45s %s(%s)%s\n", fpr, COLQUOTE, tr && *tr ? i18n (2683, "trusted") : i18n (2684, "untrusted"), COLNONE); } if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { char *sid = readable_sid (ctx->sessionid, ctx->sessionid_len, ctx->sessionid_half); rl_printf (" V%u/%u: %s%s%s\n", ctx->protocol_version, ctx->generation, COLQUOTE, sid, COLNONE); free (sid); } } /* Prints all context states */ static void print_all_context () { ConnContext *ctx; if (!userstate) return; for (ctx = userstate->context_root; ctx; ctx = ctx->next) print_context (ctx); } /* Prints private key information */ static void print_keys () { OtrlPrivKey *key; if (!userstate) return; for (key = userstate->privkey_root; key; key = key->next) { char fpr[45] = "?"; const char *type = "?"; if (key->pubkey_type == OTRL_PUBKEY_TYPE_DSA) type = "DSA"; otrl_privkey_fingerprint (userstate, fpr, key->accountname, key->protocol); rl_printf ("%s/%s: %45s (%s)\n", key->accountname, key->protocol, fpr, type); } } /* Initialize OTR library, create user state * and read private key/fingerprint lists */ void OTRInit () { gcry_error_t ret; Connection *conn; int i; if (!libotr_is_present) { rl_printf (i18n (2634, "Install libOTR 3.0.0 or newer and enjoy off-the-record encrypted messages!\n")); return; } OTRL_INIT; assert (!userstate); userstate = otrl_userstate_create (); /* build filename strings */ s_init (&keyfile, PrefUserDirReal (prG), strlen (KEYFILENAME)); s_cat (&keyfile, KEYFILENAME); s_init (&fprfile, PrefUserDirReal (prG), strlen (FPRFILENAME)); s_cat (&fprfile, FPRFILENAME); /* get privat keys */ ret = otrl_privkey_read (userstate, keyfile.txt); if (ret) rl_printf (i18n (2645, "Could not read OTR private key from %s.\n"), keyfile.txt); for (i = 0; (conn = ConnectionNr (i)); i++) if (conn->type & TYPEF_ANY_SERVER && !otrl_privkey_find (userstate, conn->screen, proto_name (conn->type))) { cb_create_privkey (NULL, conn->screen, proto_name (conn->type)); } /* get fingerprints */ otrl_privkey_read_fingerprints (userstate, fprfile.txt, add_app_data_find, NULL); } /* Close encrypted sessions, free userstate and write fingerprints */ void OTREnd () { ConnContext *ctx; gcry_error_t ret; if (!userstate) return; /* really needed? */ ret = otrl_privkey_write_fingerprints (userstate, fprfile.txt); if (ret) { rl_print (COLERROR); rl_print (i18n (2646, "Writing OTR fingerprints failed!")); rl_printf ("%s\n", COLNONE); } /* close all encrypted sessions first */ for (ctx = userstate->context_root; ctx; ctx = ctx->next) if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { otrl_message_disconnect (userstate, &ops, ctx->app_data, ctx->accountname, ctx->protocol, ctx->username); } otrl_userstate_free (userstate); userstate = NULL; s_done (&fprfile); s_done (&keyfile); if (log_file) { fclose (log_file); log_file = NULL; } } /* Free message allocated by libotr */ void OTRFree (char *msg) { otrl_message_free (msg); } /* Process incoming message * returns != 0 when message was internal for OTR protocol * (like key exchange...) */ int OTRMsgIn (Contact *cont, fat_srv_msg_t *msg) { Contact *pcont = cont; int ret; assert (userstate); char *otr_text = NULL; #if ENABLE_CONT_HIER while (pcont->parent && pcont->parent->serv == pcont->serv) pcont = pcont->parent; #endif ret = otrl_message_receiving (userstate, &ops, pcont, pcont->serv->screen, proto_name (pcont->serv->type), pcont->screen, msg->msgtext, &otr_text, NULL, add_app_data_find, pcont); if (ret) { if (otr_text) OTRFree (otr_text); return 1; } if (otr_text) { /* replace text with decrypted version */ ConnContext *ctx; free (msg->msgtext); msg->msgtext = strdup (otr_text); OTRFree (otr_text); ctx = find_context (pcont, 0); assert (ctx); if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { msg->otrencrypted = 1; if (ctx->active_fingerprint && ctx->active_fingerprint->trust && *ctx->active_fingerprint->trust) msg->otrencrypted = 2; } } return 0; } /* Process outgoing message * returns != 0 when message could not be encrypted * (do not send cleartext in this case) */ Message *OTRMsgOut (Message *msg) { char *otr_text = NULL; Contact *cont = msg->cont; gcry_error_t ret; assert (userstate); #if ENABLE_CONT_HIER while (cont->parent && cont->parent->serv == cont->serv) cont = cont->parent; #endif ret = otrl_message_sending (userstate, &ops, cont, cont->serv->screen, proto_name (cont->serv->type), cont->screen, msg->send_message, NULL, &otr_text, add_app_data_find, cont); if (ret) { rl_print (COLERROR); rl_printf (i18n (2641, "Message for %s could not be encrypted and was NOT sent!"), msg->cont->nick); rl_printf ("%s\n", COLNONE); MsgD (msg); if (otr_text) OTRFree (otr_text); return NULL; } /* replace text if OTR changed it */ if (otr_text && strcmp (msg->send_message, otr_text)) { ConnContext *ctx; assert (!msg->plain_message); ctx = find_context (cont, 0); assert (ctx); if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { msg->otrencrypted = 1; if (ctx->active_fingerprint && ctx->active_fingerprint->trust && *ctx->active_fingerprint->trust) msg->otrencrypted = 2; } msg->plain_message = msg->send_message; msg->send_message = strdup (otr_text); OTRFree (otr_text); } return msg; } ConnContext *Cont2Ctx (Contact *cont, int create) { ConnContext *ctx; if (!cont) return NULL; ctx = find_context (cont, create); if (!ctx) { rl_printf (i18n (2685, "Could not find context for %s.\n"), s_cquote (cont->screen, COLQUOTE)); return NULL; } return ctx; } void OTRContext (Contact *cont) { ConnContext *ctx = Cont2Ctx (cont, 0); if (!userstate || (cont && !ctx)) return; if (ctx) print_context (ctx); else print_all_context (); } void OTRStart (Contact *cont, UBYTE start) { ConnContext *ctx = Cont2Ctx (cont, start); if (!userstate || !ctx) return; if (start && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) otrl_message_disconnect (userstate, &ops, cont, ctx->accountname, ctx->protocol, ctx->username); if (start) { OtrlPolicy policy = cb_policy (cont, ctx); char *msg; if (policy == OTRL_POLICY_NEVER) { rl_printf (i18n (2648, "No OTR is allowed as long as policy is set to %s.\n"), s_qquote ("never")); rl_printf (i18n (2686, "Use %s to change.\n"), s_qquote (s_sprintf ("opt %s otrpolicy", cont->nick))); return; } msg = otrl_proto_default_query_msg (ctx->accountname, policy); IMCliMsg (cont, MSG_NORM, msg, OptSetVals (NULL, CO_OTRINJECT, TRUE, 0)); free (msg); } else otrl_message_disconnect (userstate, &ops, cont, ctx->accountname, ctx->protocol, ctx->username); } void OTRSetTrust (Contact *cont, UBYTE trust) { char fpr[45], *tr; ConnContext *ctx = Cont2Ctx (cont, 0); if (!userstate || !ctx) return; if (!ctx->active_fingerprint || !ctx->active_fingerprint->fingerprint) { rl_print (i18n (2651, "No active fingerprint set - start OTR session first.\n")); return; } otrl_privkey_hash_to_human (fpr, ctx->active_fingerprint->fingerprint); if (trust) otrl_context_set_trust (ctx->active_fingerprint, trust == 2 ? "." : NULL); tr = ctx->active_fingerprint->trust; printf ("%s [%44s]: %s\n", ctx->username, fpr, tr && *tr ? i18n (2683, "trusted") : i18n (2684, "untrusted")); } void OTRGenKey (void) { if (!userstate) return; if (uiG.conn && !otrl_privkey_find (userstate, uiG.conn->screen, proto_name (uiG.conn->type))) { cb_create_privkey (NULL, uiG.conn->screen, proto_name (uiG.conn->type)); } else rl_print (i18n (2652, "Delete existing key first!\n")); } void OTRListKeys (void) { if (!userstate) return; print_keys (); } /* Return the OTR policy for the given context. * maybe combination of: * OTRL_POLICY_ALLOW_V1, OTRL_POLICY_ALLOW_V2, OTRL_POLICY_REQUIRE_ENCRYPTION * OTRL_POLICY_SEND_WHITESPACE_TAG, OTRL_POLICY_WHITESPACE_START_AKE, * OTRL_POLICY_ERROR_START_AKE * or shorter: * OTRL_POLICY_NEVER, OTRL_POLICY_OPPORTUNISTIC, OTRL_POLICY_MANUAL, * OTRL_POLICY_ALWAYS, OTRL_POLICY_DEFAULT * */ static OtrlPolicy cb_policy (void *opdata, ConnContext *context) { Contact *cont = opdata; const char *policy = NULL; assert (cont); assert (cont == context->app_data); if (cont) policy = ContactPrefStr (cont, CO_OTRPOLICY); if (policy) { if (!strcmp (policy, "never")) return OTRL_POLICY_NEVER; else if (!strcmp (policy, "try")) return OTRL_POLICY_OPPORTUNISTIC; else if (!strcmp (policy, "manual")) return OTRL_POLICY_MANUAL; else if (!strcmp (policy, "always")) return OTRL_POLICY_ALWAYS & ~OTRL_POLICY_ALLOW_V1; else if (*policy) { rl_print (COLDEBUG); rl_printf (i18n (2653, "util_otr: \"%s\" not a policy (use never|try|manual|always)"), policy); rl_printf ("%s\n", COLNONE); } } return cont ? OTRL_POLICY_DEFAULT : OTRL_POLICY_ALLOW_V1; } /* Create a private key for the given accountname/protocol if * desired. */ static void cb_create_privkey (void *opdata, const char *accountname, const char *protocol) { gcry_error_t ret; char *prot = strdup (s_qquote (protocol)); rl_printf (i18n (2654, "Generating new OTR key for %s account %s. This may take a while..."), prot, s_qquote (accountname)); s_free (prot); ret = otrl_privkey_generate (userstate, keyfile.txt, accountname, protocol); if (ret) rl_print (i18n (2655, "failed.\n")); else rl_print (i18n (2656, "successfull.\n")); } /* 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. */ static int cb_is_logged_in (void *opdata, const char *accountname, const char *protocol, const char *recipient) { Contact *cont = opdata; assert (cont); assert (cont == find_contact (accountname, protocol, recipient)); if (!cont->group) return -1; else if (cont->status == ims_offline) return 0; else return 1; } /* Send the given IM to the given recipient from the given * accountname/protocol. */ static void cb_inject_message (void *opdata, const char *accountname, const char *protocol, const char *recipient, const char *message) { Contact *cont = opdata; Message *msg; UBYTE ret; assert (cont); assert (cont == find_contact (accountname, protocol, recipient)); msg = MsgC (); msg->cont = cont; msg->type = MSG_NORM; msg->send_message = strdup (message); msg->otrinjected = 1; msg->trans = CV_MSGTRANS_ANY; ret = IMCliReMsg (cont, msg); if (ret == RET_DEFER && msg->maxsize) { str_s str = { NULL, 0, 0 }; UDWORD fragments, frag; fragments = strlen (message) / msg->maxsize + 1; frag = 1; assert (fragments < 65536); while (frag <= fragments) { s_init (&str, "", msg->maxsize); s_catf (&str, "?OTR,%hu,%hu,", (unsigned short)frag, (unsigned short)fragments); s_catn (&str, message, strlen (message) / (fragments + 1 - frag)); s_catc (&str, ','); message += strlen (message) / (fragments + 1 - frag); cb_inject_message (opdata, accountname, protocol, recipient, str.txt); } s_done (&str); MsgD (msg); return; } if (!RET_IS_OK (ret)) { rl_print (COLDEBUG); rl_printf (i18n (2659, "otr_inject: message injection for contact %s failed"), recipient); rl_printf ("%s\n", COLNONE); } } /* Display a notification message for a particular accountname / * protocol / username conversation. */ static void cb_notify (void *opdata, OtrlNotifyLevel level, const char *accountname, const char *protocol, const char *username, const char *title, const char *primary, const char *secondary) { Contact *cont = (Contact *)opdata; const char *type; assert (cont); assert (cont == find_contact (accountname, protocol, username)); switch (level) { case OTRL_NOTIFY_ERROR: type = "Error"; break; case OTRL_NOTIFY_WARNING: type = "Warning"; break; case OTRL_NOTIFY_INFO: type = "Info"; break; default: type = "Notify"; } rl_log_for (cont->nick, COLCONTACT); rl_printf ("%sOTR %s:%s %s\n %s\n %s\n", COLERROR, type, COLNONE, title, primary, secondary); } /* 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. */ static int cb_display_otr_message (void *opdata, const char *accountname, const char *protocol, const char *username, const char *msg) { Contact *cont = (Contact *)opdata; assert (cont); assert (cont == find_contact (accountname, protocol, username)); rl_log_for (cont->nick, COLCONTACT); rl_printf ("%sOTR%s: %s\n", COLERROR, COLNONE, msg); return 0; } /* When the list of ConnContexts changes (including a change in * state), this is called so the UI can be updated. */ static void cb_update_context_list (void *opdata) { /* anything useful here? */ /* print_all_context (); */ } /* Return a newly-allocated string containing a human-friendly name * for the given protocol id */ static const char *cb_protocol_name (void *opdata, const char *protocol) { /* is this function used anywhere? */ return strdup (protocol); } /* Deallocate a string allocated by protocol_name */ static void cb_protocol_name_free (void *opdata, const char *protocol_name) { free ((char *) protocol_name); } /* A new fingerprint for the given user has been received. */ static void cb_new_fingerprint (void *opdata, OtrlUserState us, const char *accountname, const char *protocol, const char *username, unsigned char fingerprint[20]) { Contact *cont = (Contact *)opdata; char fpr[45]; assert (cont); assert (cont == find_contact (accountname, protocol, username)); otrl_privkey_hash_to_human (fpr, fingerprint); rl_log_for (cont->nick, COLCONTACT); rl_printf (i18n (2660, "new OTR fingerprint: %s.\n"), fpr); } /* The list of known fingerprints has changed. Write them to disk. */ static void cb_write_fingerprints (void *opdata) { otrl_privkey_write_fingerprints (userstate, fprfile.txt); } /* A ConnContext has entered a secure state. */ static void cb_gone_secure (void *opdata, ConnContext *context) { Contact *cont = opdata; assert (cont); assert (cont == context->app_data); rl_log_for (cont->nick, COLCONTACT); rl_printf (i18n (2661, "secure OTR channel established.\n")); } /* A ConnContext has left a secure state. */ static void cb_gone_insecure (void *opdata, ConnContext *context) { Contact *cont = opdata; assert (cont); assert (cont == context->app_data); rl_log_for (cont->nick, COLCONTACT); rl_printf (i18n (2662, "secure OTR channel closed.\n")); } /* We have completed an authentication, using the D-H keys we * already knew. is_reply indicates whether we initiated the AKE. */ static void cb_still_secure (void *opdata, ConnContext *context, int is_reply) { Contact *cont = opdata; assert (cont); assert (cont == context->app_data); rl_log_for (cont->nick, COLCONTACT); if (is_reply) rl_printf (i18n (2663, "secure OTR channel reestablished.\n")); else rl_printf (i18n (2664, "reestablished secure OTR channel.\n")); } /* Log a message. The passed message will end in "\n". */ static void cb_log_message (void *opdata, const char *message) { if (!OTR_ENABLE_LOG) return; if (log_file == NULL) { str_s logname = {0, 0, 0}; s_init (&logname, PrefUserDirReal (prG), strlen (LOGFILENAME)); s_cat (&logname, LOGFILENAME); log_file = fopen (logname.txt, "a"); s_done (&logname); } if (log_file != NULL) fputs (message, log_file); } #endif /* ENABLE_OTR */