source: otr.c @ 8c2b1c3

Last change on this file since 8c2b1c3 was 8c2b1c3, checked in by Sven Moritz Hallberg <sm@…>, at 2008-02-11T14:36:19Z

honor simulate_netsplit for encrypted/trusted mode changes

  • Property mode set to 100644
File size: 34.3 KB
RevLine 
[764c7d1]1#include "bitlbee.h"
2#ifdef WITH_OTR
3#include "irc.h"
4#include "otr.h"
5#include <sys/types.h>
6#include <unistd.h>
7
8/**
9files used to store OTR data:
10  $configdir/$nick.otr_keys
11  $configdir/$nick.otr_fprints
12 **/
13
14
15/** OTR interface routines for the OtrlMessageAppOps struct: **/
16
17OtrlPolicy op_policy(void *opdata, ConnContext *context);
18
19void op_create_privkey(void *opdata, const char *accountname, const char *protocol);
20
21int op_is_logged_in(void *opdata, const char *accountname, const char *protocol,
22        const char *recipient);
23
24void op_inject_message(void *opdata, const char *accountname, const char *protocol,
25        const char *recipient, const char *message);
26
27int op_display_otr_message(void *opdata, const char *accountname, const char *protocol,
28        const char *username, const char *msg);
29
30void op_new_fingerprint(void *opdata, OtrlUserState us, const char *accountname,
31        const char *protocol, const char *username, unsigned char fingerprint[20]);
32
33void op_write_fingerprints(void *opdata);
34
35void op_gone_secure(void *opdata, ConnContext *context);
36
37void op_gone_insecure(void *opdata, ConnContext *context);
38
39void op_still_secure(void *opdata, ConnContext *context, int is_reply);
40
41void op_log_message(void *opdata, const char *message);
42
[a13855a]43int op_max_message_size(void *opdata, ConnContext *context);
[764c7d1]44
[a13855a]45const char *op_account_name(void *opdata, const char *account, const char *protocol);
[764c7d1]46
47
48/** otr sub-command handlers: **/
49
[8521b02]50void cmd_otr_connect(irc_t *irc, char **args);
51void cmd_otr_disconnect(irc_t *irc, char **args);
[5a71d9c]52void cmd_otr_smp(irc_t *irc, char **args);
53void cmd_otr_trust(irc_t *irc, char **args);
[764c7d1]54void cmd_otr_info(irc_t *irc, char **args);
[94e7eb3]55void cmd_otr_keygen(irc_t *irc, char **args);
[8521b02]56/* void cmd_otr_forget(irc_t *irc, char **args); */
[764c7d1]57
58const command_t otr_commands[] = {
[8521b02]59        { "connect",     1, &cmd_otr_connect,    0 },
60        { "disconnect",  1, &cmd_otr_disconnect, 0 },
61        { "smp",         2, &cmd_otr_smp,        0 },
62        { "trust",       6, &cmd_otr_trust,      0 },
63        { "info",        0, &cmd_otr_info,       0 },
[94e7eb3]64        { "keygen",      1, &cmd_otr_keygen,     0 },
[8521b02]65        /*
66        { "forget",      1, &cmd_otr_forget,     0 },
67        */
[764c7d1]68        { NULL }
69};
70
71
72/** misc. helpers/subroutines: **/
73
74/* start background thread to generate a (new) key for a given account */
75void otr_keygen(irc_t *irc, const char *handle, const char *protocol);
76/* keygen thread main func */
77gpointer otr_keygen_thread_func(gpointer data);
78/* mainloop handler for when keygen thread finishes */
79gboolean keygen_finish_handler(gpointer data, gint fd, b_input_condition cond);
80/* data to be passed to otr_keygen_thread_func */
81struct kgdata {
82        irc_t *irc;            /* access to OTR userstate */
83        char *keyfile;         /* free me! */
84        const char *handle;      /* don't free! */
85        const char *protocol;    /* don't free! */
86        GMutex *mutex;         /* lock for the 'done' flag, free me! */
87        int done;              /* is the thread done? */
88        gcry_error_t result;   /* return value of otrl_privkey_generate */
89};
90
91/* yes/no handlers for "generate key now?" */
92void yes_keygen(gpointer w, void *data);
93void no_keygen(gpointer w, void *data);
94
95/* helper to make sure accountname and protocol match the incoming "opdata" */
96struct im_connection *check_imc(void *opdata, const char *accountname,
97        const char *protocol);
98
[5a71d9c]99/* determine the nick for a given handle/protocol pair
100   returns "handle/protocol" if not found */
[764c7d1]101const char *peernick(irc_t *irc, const char *handle, const char *protocol);
102
[8521b02]103/* turn a hexadecimal digit into its numerical value */
104int hexval(char a);
105
[5a71d9c]106/* determine the user_t for a given handle/protocol pair
107   returns NULL if not found */
108user_t *peeruser(irc_t *irc, const char *handle, const char *protocol);
109
[764c7d1]110/* handle SMP TLVs from a received message */
111void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs);
112
[5a71d9c]113/* update op/voice flag of given user according to encryption state and settings
114   returns 0 if neither op_buddies nor voice_buddies is set to "encrypted",
115   i.e. msgstate should be announced seperately */
116int otr_update_modeflags(irc_t *irc, user_t *u);
117
[8521b02]118/* show general info about the OTR subsystem; called by 'otr info' */
119void show_general_otr_info(irc_t *irc);
120
121/* show info about a given OTR context */
122void show_otr_context_info(irc_t *irc, ConnContext *ctx);
123
[764c7d1]124/* show the list of fingerprints associated with a given context */
125void show_fingerprints(irc_t *irc, ConnContext *ctx);
126
[5bf5edf]127/* to log out accounts during keygen */
128extern void cmd_account(irc_t *irc, char **cmd);
129
[764c7d1]130
131/*** routines declared in otr.h: ***/
132
133void otr_init(void)
134{
135        if(!g_thread_supported()) g_thread_init(NULL);
136        OTRL_INIT;
137       
138        /* fill global OtrlMessageAppOps */
139        global.otr_ops.policy = &op_policy;
140        global.otr_ops.create_privkey = &op_create_privkey;
141        global.otr_ops.is_logged_in = &op_is_logged_in;
142        global.otr_ops.inject_message = &op_inject_message;
143        global.otr_ops.notify = NULL;
144        global.otr_ops.display_otr_message = &op_display_otr_message;
145        global.otr_ops.update_context_list = NULL;
146        global.otr_ops.protocol_name = NULL;
147        global.otr_ops.protocol_name_free = NULL;
148        global.otr_ops.new_fingerprint = &op_new_fingerprint;
149        global.otr_ops.write_fingerprints = &op_write_fingerprints;
150        global.otr_ops.gone_secure = &op_gone_secure;
151        global.otr_ops.gone_insecure = &op_gone_insecure;
152        global.otr_ops.still_secure = &op_still_secure;
153        global.otr_ops.log_message = &op_log_message;
[a13855a]154        global.otr_ops.max_message_size = &op_max_message_size;
155        global.otr_ops.account_name = &op_account_name;
[764c7d1]156        global.otr_ops.account_name_free = NULL;
157}
158
159/* Notice on the otr_mutex:
160
161   The incoming/outgoing message handlers try to lock the otr_mutex. If they succeed,
162   this will prevent a concurrent keygen (possibly spawned by that very command)
163   from messing up the userstate. If the lock fails, that means there already is
164   a keygen in progress. Instead of blocking for an unknown time, they
165   will bail out gracefully, informing the user of this temporary "coma".
166   TODO: Hold back incoming/outgoing messages and process them when keygen completes?
167
168   The other routines do not lock the otr_mutex themselves, it is done as a
169   catch-all in the root command handler. Rationale:
170     a) it's easy to code
171     b) it makes it obvious that no command can get its userstate corrupted
172     c) the "irc" struct is readily available there for feedback to the user
173 */
174
175void otr_load(irc_t *irc)
176{
177        char s[512];
178        account_t *a;
179        gcry_error_t e;
180
181        log_message(LOGLVL_DEBUG, "otr_load '%s'", irc->nick);
182
183        g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, irc->nick);
184        e = otrl_privkey_read(irc->otr_us, s);
185        if(e && e!=ENOENT) {
[3c80a9d]186                log_message(LOGLVL_ERROR, "otr load: %s: %s", s, strerror(e));
[764c7d1]187        }
188        g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->nick);
189        e = otrl_privkey_read_fingerprints(irc->otr_us, s, NULL, NULL);
190        if(e && e!=ENOENT) {
[3c80a9d]191                log_message(LOGLVL_ERROR, "otr load: %s: %s", s, strerror(e));
[764c7d1]192        }
193       
194        /* check for otr keys on all accounts */
195        for(a=irc->accounts; a; a=a->next) {
196                otr_check_for_key(a);
197        }
198}
199
200void otr_save(irc_t *irc)
201{
202        char s[512];
[3c80a9d]203        gcry_error_t e;
[764c7d1]204
205        log_message(LOGLVL_DEBUG, "otr_save '%s'", irc->nick);
206
207        g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->nick);
[3c80a9d]208        e = otrl_privkey_write_fingerprints(irc->otr_us, s);
209        if(e) {
210                log_message(LOGLVL_ERROR, "otr save: %s: %s", s, strerror(e));
211        }
[764c7d1]212}
213
214void otr_remove(const char *nick)
215{
216        char s[512];
217       
218        log_message(LOGLVL_DEBUG, "otr_remove '%s'", nick);
219
220        g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, nick);
221        unlink(s);
222        g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, nick);
223        unlink(s);
224}
225
226void otr_rename(const char *onick, const char *nnick)
227{
228        char s[512], t[512];
229       
230        log_message(LOGLVL_DEBUG, "otr_rename '%s' -> '%s'", onick, nnick);
231
232        g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, onick);
233        g_snprintf(t, 511, "%s%s.otr_keys", global.conf->configdir, nnick);
234        rename(s,t);
235        g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, onick);
236        g_snprintf(t, 511, "%s%s.otr_fprints", global.conf->configdir, nnick);
237        rename(s,t);
238}
239
240void otr_check_for_key(account_t *a)
241{
242        irc_t *irc = a->irc;
[a13855a]243        OtrlPrivKey *k;
[764c7d1]244       
[a13855a]245        k = otrl_privkey_find(irc->otr_us, a->user, a->prpl->name);
246        if(k) {
247                irc_usermsg(irc, "otr: %s/%s ready",
248                        a->user, a->prpl->name);
[764c7d1]249        } else {
250                otr_keygen(irc, a->user, a->prpl->name);
251        }
252}
253
254char *otr_handle_message(struct im_connection *ic, const char *handle, const char *msg)
255{
256        int ignore_msg;
257        char *newmsg = NULL;
258        OtrlTLV *tlvs = NULL;
259        char *colormsg;
260       
261    if(!g_mutex_trylock(ic->irc->otr_mutex)) {
[5d62040]262                irc_usermsg(ic->irc, "otr keygen in progress - msg from %s dropped",
[a13855a]263                        peernick(ic->irc, handle, ic->acc->prpl->name));
[764c7d1]264                return NULL;
265        }
266
267        ignore_msg = otrl_message_receiving(ic->irc->otr_us, &global.otr_ops, ic,
268                ic->acc->user, ic->acc->prpl->name, handle, msg, &newmsg,
269                &tlvs, NULL, NULL);
270
271        otr_handle_smp(ic, handle, tlvs);
272       
273        if(ignore_msg) {
274                /* this was an internal OTR protocol message */
275                g_mutex_unlock(ic->irc->otr_mutex);
276                return NULL;
277        } else if(!newmsg) {
278                /* this was a non-OTR message */
279                g_mutex_unlock(ic->irc->otr_mutex);
280                return g_strdup(msg);
281        } else {
282                /* OTR has processed this message */
283                ConnContext *context = otrl_context_find(ic->irc->otr_us, handle,
284                        ic->acc->user, ic->acc->prpl->name, 0, NULL, NULL, NULL);
285                if(context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
286                        /* color according to f'print trust */
287                        char color;
288                        const char *trust = context->active_fingerprint->trust;
289                        if(trust && trust[0] != '\0')
290                                color='3';   /* green */
291                        else
292                                color='5';   /* red */
293                        colormsg = g_strdup_printf("\x03%c%s\x0F", color, newmsg);
294                } else {
295                        colormsg = g_strdup(newmsg);
296                }
297                otrl_message_free(newmsg);
298                g_mutex_unlock(ic->irc->otr_mutex);
299                return colormsg;
300        }
301}
302
303int otr_send_message(struct im_connection *ic, const char *handle, const char *msg, int flags)
304{       
[5a71d9c]305        int st;
306        char *otrmsg = NULL;
307        ConnContext *ctx = NULL;
308       
[764c7d1]309    if(!g_mutex_trylock(ic->irc->otr_mutex)) {
[5d62040]310                irc_usermsg(ic->irc, "otr keygen in progress - msg to %s not sent",
[5a71d9c]311                        peernick(ic->irc, handle, ic->acc->prpl->name));
312                return 1;
[764c7d1]313    }
314   
315        st = otrl_message_sending(ic->irc->otr_us, &global.otr_ops, ic,
316                ic->acc->user, ic->acc->prpl->name, handle,
317                msg, NULL, &otrmsg, NULL, NULL);
318        if(st) {
319                g_mutex_unlock(ic->irc->otr_mutex);
320                return st;
321        }
322
323        ctx = otrl_context_find(ic->irc->otr_us,
324                        handle, ic->acc->user, ic->acc->prpl->name,
325                        1, NULL, NULL, NULL);
326
327        if(otrmsg) {
328                if(!ctx) {
329                        otrl_message_free(otrmsg);
330                        g_mutex_unlock(ic->irc->otr_mutex);
331                        return 1;
332                }
333                st = otrl_message_fragment_and_send(&global.otr_ops, ic, ctx,
334                        otrmsg, OTRL_FRAGMENT_SEND_ALL, NULL);
335                otrl_message_free(otrmsg);
336        } else {
337                /* yeah, well, some const casts as usual... ;-) */
338                st = ic->acc->prpl->buddy_msg( ic, (char *)handle, (char *)msg, flags );
339        }
340       
341        g_mutex_unlock(ic->irc->otr_mutex);
342        return st;
343}
344
345void cmd_otr(irc_t *irc, char **args)
346{
347        const command_t *cmd;
348       
349        if(!args[0])
350                return;
351       
352        if(!args[1])
353                return;
354       
355        for(cmd=otr_commands; cmd->command; cmd++) {
356                if(strcmp(cmd->command, args[1]) == 0)
357                        break;
358        }
359       
360        if(!cmd->command) {
361                irc_usermsg(irc, "%s %s: unknown subcommand, see \x02help otr\x02",
362                        args[0], args[1]);
363                return;
364        }
365       
366        if(!args[cmd->required_parameters+1]) {
367                irc_usermsg(irc, "%s %s: not enough arguments (%d req.)",
368                        args[0], args[1], cmd->required_parameters);
369                return;
370        }
371       
372        cmd->execute(irc, args+1);
373}
374
375
376/*** OTR "MessageAppOps" callbacks for global.otr_ui: ***/
377
378OtrlPolicy op_policy(void *opdata, ConnContext *context)
379{
380        /* TODO: OTR policy configurable */
381        return OTRL_POLICY_OPPORTUNISTIC;
382}
383
384void op_create_privkey(void *opdata, const char *accountname,
385        const char *protocol)
386{
387        struct im_connection *ic = check_imc(opdata, accountname, protocol);
388        char *s;
389       
390        log_message(LOGLVL_DEBUG, "op_create_privkey '%s' '%s'", accountname, protocol);
391
392        s = g_strdup_printf("oops, no otr privkey for %s/%s - generate one now?",
393                accountname, protocol);
394        query_add(ic->irc, ic, s, yes_keygen, no_keygen, ic->acc);
395}
396
397int op_is_logged_in(void *opdata, const char *accountname,
398        const char *protocol, const char *recipient)
399{
400        struct im_connection *ic = check_imc(opdata, accountname, protocol);
401        user_t *u;
402
403        log_message(LOGLVL_DEBUG, "op_is_logged_in '%s' '%s' '%s'", accountname, protocol, recipient);
404       
405        /* lookup the user_t for the given recipient */
406        u = user_findhandle(ic, recipient);
407        if(u) {
408                if(u->online)
409                        return 1;
410                else
411                        return 0;
412        } else {
413                return -1;
414        }
415}
416
417void op_inject_message(void *opdata, const char *accountname,
418        const char *protocol, const char *recipient, const char *message)
419{
420        struct im_connection *ic = check_imc(opdata, accountname, protocol);
421
422        log_message(LOGLVL_DEBUG, "op_inject_message '%s' '%s' '%s' '%s'", accountname, protocol, recipient, message);
423
424        if (strcmp(accountname, recipient) == 0) {
425                /* huh? injecting messages to myself? */
426                irc_usermsg(ic->irc, "note to self: %s", message);
427        } else {
428                /* need to drop some consts here :-( */
429                /* TODO: get flags into op_inject_message?! */
430                ic->acc->prpl->buddy_msg(ic, (char *)recipient, (char *)message, 0);
431                /* ignoring return value :-/ */
432        }
433}
434
435int op_display_otr_message(void *opdata, const char *accountname,
[5a71d9c]436        const char *protocol, const char *username, const char *message)
[764c7d1]437{
438        struct im_connection *ic = check_imc(opdata, accountname, protocol);
[5a71d9c]439        char *msg = g_strdup(message);
[764c7d1]440
[5a71d9c]441        log_message(LOGLVL_DEBUG, "op_display_otr_message '%s' '%s' '%s' '%s'", accountname, protocol, username, message);
[764c7d1]442
[5a71d9c]443        strip_html(msg);
444        irc_usermsg(ic->irc, "otr: %s", msg);
[764c7d1]445
[5a71d9c]446        g_free(msg);
[764c7d1]447        return 0;
448}
449
450void op_new_fingerprint(void *opdata, OtrlUserState us,
451        const char *accountname, const char *protocol,
452        const char *username, unsigned char fingerprint[20])
453{
454        struct im_connection *ic = check_imc(opdata, accountname, protocol);
455        char hunam[45];         /* anybody looking? ;-) */
456       
457        otrl_privkey_hash_to_human(hunam, fingerprint);
458        log_message(LOGLVL_DEBUG, "op_new_fingerprint '%s' '%s' '%s' '%s'", accountname, protocol, username, hunam);
459
460        irc_usermsg(ic->irc, "new fingerprint for %s: %s",
461                peernick(ic->irc, username, protocol), hunam);
462}
463
464void op_write_fingerprints(void *opdata)
465{
466        struct im_connection *ic = (struct im_connection *)opdata;
467
468        log_message(LOGLVL_DEBUG, "op_write_fingerprints");
469
470        otr_save(ic->irc);
471}
472
473void op_gone_secure(void *opdata, ConnContext *context)
474{
475        struct im_connection *ic =
476                check_imc(opdata, context->accountname, context->protocol);
[5a71d9c]477        user_t *u;
[764c7d1]478
479        log_message(LOGLVL_DEBUG, "op_gone_secure '%s' '%s' '%s'", context->accountname, context->protocol, context->username);
480
[5a71d9c]481        u = peeruser(ic->irc, context->username, context->protocol);
482        if(!u) {
483                log_message(LOGLVL_ERROR,
[8521b02]484                        "BUG: otr.c: op_gone_secure: user_t for %s/%s/%s not found!",
485                        context->username, context->protocol, context->accountname);
[5a71d9c]486                return;
487        }
488        if(context->active_fingerprint->trust[0])
489                u->encrypted = 2;
490        else
491                u->encrypted = 1;
492        if(!otr_update_modeflags(ic->irc, u))
493                irc_usermsg(ic->irc, "conversation with %s is now off the record", u->nick);
[764c7d1]494}
495
496void op_gone_insecure(void *opdata, ConnContext *context)
497{
498        struct im_connection *ic =
499                check_imc(opdata, context->accountname, context->protocol);
[5a71d9c]500        user_t *u;
[764c7d1]501
502        log_message(LOGLVL_DEBUG, "op_gone_insecure '%s' '%s' '%s'", context->accountname, context->protocol, context->username);
503
[5a71d9c]504        u = peeruser(ic->irc, context->username, context->protocol);
505        if(!u) {
506                log_message(LOGLVL_ERROR,
[8521b02]507                        "BUG: otr.c: op_gone_insecure: user_t for %s/%s/%s not found!",
508                        context->username, context->protocol, context->accountname);
[5a71d9c]509                return;
510        }
511        u->encrypted = 0;
512        if(!otr_update_modeflags(ic->irc, u))
513                irc_usermsg(ic->irc, "conversation with %s is now in the clear", u->nick);
[764c7d1]514}
515
516void op_still_secure(void *opdata, ConnContext *context, int is_reply)
517{
518        struct im_connection *ic =
519                check_imc(opdata, context->accountname, context->protocol);
[5a71d9c]520        user_t *u;
[764c7d1]521
522        log_message(LOGLVL_DEBUG, "op_still_secure '%s' '%s' '%s' is_reply=%d",
523                context->accountname, context->protocol, context->username, is_reply);
524
[5a71d9c]525        u = peeruser(ic->irc, context->username, context->protocol);
526        if(!u) {
527                log_message(LOGLVL_ERROR,
[8521b02]528                        "BUG: otr.c: op_still_secure: user_t for %s/%s/%s not found!",
529                        context->username, context->protocol, context->accountname);
[5a71d9c]530                return;
531        }
532        if(context->active_fingerprint->trust[0])
533                u->encrypted = 2;
534        else
535                u->encrypted = 1;
536        if(!otr_update_modeflags(ic->irc, u))
537                irc_usermsg(ic->irc, "otr connection with %s has been refreshed", u->nick);
[764c7d1]538}
539
540void op_log_message(void *opdata, const char *message)
541{
[5a71d9c]542        char *msg = g_strdup(message);
543       
544        strip_html(msg);
545        log_message(LOGLVL_INFO, "otr: %s", msg);
546        g_free(msg);
[764c7d1]547}
548
[a13855a]549int op_max_message_size(void *opdata, ConnContext *context)
550{
[5a71d9c]551        struct im_connection *ic =
552                check_imc(opdata, context->accountname, context->protocol);
553
554        return ic->acc->prpl->mms;
[a13855a]555}
556
557const char *op_account_name(void *opdata, const char *account, const char *protocol)
558{
559        struct im_connection *ic = (struct im_connection *)opdata;
560
561        log_message(LOGLVL_DEBUG, "op_account_name '%s' '%s'", account, protocol);
562       
563        return peernick(ic->irc, account, protocol);
564}
565
[764c7d1]566
567/*** OTR sub-command handlers ***/
568
[8521b02]569void cmd_otr_disconnect(irc_t *irc, char **args)
[764c7d1]570{
571        user_t *u;
572
573        u = user_find(irc, args[1]);
574        if(!u || !u->ic) {
575                irc_usermsg(irc, "%s: unknown user", args[1]);
576                return;
577        }
578       
579        otrl_message_disconnect(irc->otr_us, &global.otr_ops,
580                u->ic, u->ic->acc->user, u->ic->acc->prpl->name, u->handle);
581}
582
[8521b02]583void cmd_otr_connect(irc_t *irc, char **args)
[764c7d1]584{
585        user_t *u;
586
587        u = user_find(irc, args[1]);
588        if(!u || !u->ic) {
589                irc_usermsg(irc, "%s: unknown user", args[1]);
590                return;
591        }
592        if(!u->online) {
593                irc_usermsg(irc, "%s is offline", args[1]);
594                return;
595        }
596       
597        imc_buddy_msg(u->ic, u->handle, "?OTR?", 0);
598}
599
[5a71d9c]600void cmd_otr_smp(irc_t *irc, char **args)
[764c7d1]601{
602        user_t *u;
603        ConnContext *ctx;
604       
605        u = user_find(irc, args[1]);
606        if(!u || !u->ic) {
607                irc_usermsg(irc, "%s: unknown user", args[1]);
608                return;
609        }
610        if(!u->online) {
611                irc_usermsg(irc, "%s is offline", args[1]);
612                return;
613        }
614       
615        ctx = otrl_context_find(irc->otr_us, u->handle,
616                u->ic->acc->user, u->ic->acc->prpl->name, 1, NULL, NULL, NULL);
617        if(!ctx) {
[3c80a9d]618                /* huh? out of memory or what? */
[764c7d1]619                return;
620        }
621
622        if(ctx->smstate->nextExpected != OTRL_SMP_EXPECT1) {
623                log_message(LOGLVL_INFO,
624                        "SMP already in phase %d, sending abort before reinitiating",
625                        ctx->smstate->nextExpected+1);
626                otrl_message_abort_smp(irc->otr_us, &global.otr_ops, u->ic, ctx);
627                otrl_sm_state_free(ctx->smstate);
628        }
629       
630        /* warning: the following assumes that smstates are cleared whenever an SMP
631           is completed or aborted! */ 
632        if(ctx->smstate->secret == NULL) {
633                irc_usermsg(irc, "smp: initiating with %s...", u->nick);
634                otrl_message_initiate_smp(irc->otr_us, &global.otr_ops,
635                        u->ic, ctx, (unsigned char *)args[2], strlen(args[2]));
636                /* smp is now in EXPECT2 */
637        } else {
638                /* if we're still in EXPECT1 but smstate is initialized, we must have
639                   received the SMP1, so let's issue a response */
640                irc_usermsg(irc, "smp: responding to %s...", u->nick);
641                otrl_message_respond_smp(irc->otr_us, &global.otr_ops,
642                        u->ic, ctx, (unsigned char *)args[2], strlen(args[2]));
643                /* smp is now in EXPECT3 */
644        }
645}
646
[5a71d9c]647void cmd_otr_trust(irc_t *irc, char **args)
648{
649        user_t *u;
650        ConnContext *ctx;
651        unsigned char raw[20];
652        Fingerprint *fp;
653        int i,j;
654       
655        u = user_find(irc, args[1]);
656        if(!u || !u->ic) {
657                irc_usermsg(irc, "%s: unknown user", args[1]);
658                return;
659        }
660       
661        ctx = otrl_context_find(irc->otr_us, u->handle,
662                u->ic->acc->user, u->ic->acc->prpl->name, 0, NULL, NULL, NULL);
663        if(!ctx) {
664                irc_usermsg(irc, "%s: no otr context with user", args[1]);
665                return;
666        }
667       
668        /* convert given fingerprint to raw representation */
669        for(i=0; i<5; i++) {
670                for(j=0; j<4; j++) {
671                        char *p = args[2+i]+(2*j);
672                        char *q = p+1;
673                        int x, y;
674                       
675                        if(!*p || !*q) {
676                                irc_usermsg(irc, "failed: truncated fingerprint block %d", i+1);
677                                return;
678                        }
679                       
680                        x = hexval(*p);
681                        y = hexval(*q);
682                        if(x<0) {
683                                irc_usermsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+1, i+1);
684                                return;
685                        }
686                        if(y<0) {
687                                irc_usermsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+2, i+1);
688                                return;
689                        }
690
691                        raw[i*4+j] = x*16 + y;
692                }
693        }
694        fp = otrl_context_find_fingerprint(ctx, raw, 0, NULL);
695        if(!fp) {
696                irc_usermsg(irc, "failed: no such fingerprint for %s", args[1]);
697        } else {
698                char *trust = args[7] ? args[7] : "affirmed";
699                otrl_context_set_trust(fp, trust);
700                irc_usermsg(irc, "fingerprint match, trust set to \"%s\"", trust);
701                if(u->encrypted)
702                        u->encrypted = 2;
703                otr_update_modeflags(irc, u);
704        }
705}
706
[8521b02]707void cmd_otr_info(irc_t *irc, char **args)
[764c7d1]708{
[8521b02]709        if(!args[1]) {
710                show_general_otr_info(irc);
[764c7d1]711        } else {
[8521b02]712                char *arg = g_strdup(args[1]);
713                char *myhandle, *handle, *protocol;
[764c7d1]714                ConnContext *ctx;
[8521b02]715               
716                /* interpret arg as 'user/protocol/account' if possible */
717                protocol = strchr(arg, '/');
718                if(protocol) {
719                        *(protocol++) = '\0';
720                        myhandle = strchr(protocol, '/');
721                        if(!myhandle) {
722                                /* TODO: try to find a unique account for this context */
723                        }
[764c7d1]724                }
[8521b02]725                if(protocol && myhandle) {
726                        *(myhandle++) = '\0';
727                        handle = arg;
728                        ctx = otrl_context_find(irc->otr_us, handle, myhandle, protocol, 0, NULL, NULL, NULL);
729                        if(!ctx) {
730                                irc_usermsg(irc, "no such context (%s %s %s)", handle, protocol, myhandle);
731                                g_free(arg);
732                                return;
733                        }
[764c7d1]734                } else {
[8521b02]735                        user_t *u = user_find(irc, args[1]);
736                        if(!u || !u->ic) {
737                                irc_usermsg(irc, "%s: unknown user", args[1]);
738                                g_free(arg);
739                                return;
[5a71d9c]740                        }
[8521b02]741                        ctx = otrl_context_find(irc->otr_us, u->handle, u->ic->acc->user,
742                                u->ic->acc->prpl->name, 0, NULL, NULL, NULL);
743                        if(!ctx) {
744                                irc_usermsg(irc, "no otr context with %s", args[1]);
745                                g_free(arg);
746                                return;
747                        }
748                }
749       
750                /* show how we resolved the (nick) argument, if we did */
751                if(handle!=arg) {
752                        irc_usermsg(irc, "%s is %s/%s; we are %s/%s to them", args[1],
753                                ctx->username, ctx->protocol, ctx->accountname, ctx->protocol);
[764c7d1]754                }
[8521b02]755                show_otr_context_info(irc, ctx);
756                g_free(arg);
[764c7d1]757        }
758}
759
[94e7eb3]760void cmd_otr_keygen(irc_t *irc, char **args)
761{
762        int i, n;
763        account_t *a;
764       
765        n = atoi(args[1]);
766        if(n<0 || (!n && strcmp(args[1], "0"))) {
767                irc_usermsg(irc, "%s: invalid account number", args[1]);
768                return;
769        }
770       
771        a = irc->accounts;
772        for(i=0; i<n && a; i++, a=a->next);
773        if(!a) {
774                irc_usermsg(irc, "%s: no such account", args[1]);
775                return;
776        }
777       
778        if(otrl_privkey_find(irc->otr_us, a->user, a->prpl->name)) {
779                char *s = g_strdup_printf("account %d already has a key, replace it?", n);
780                query_add(irc, a->ic, s, yes_keygen, no_keygen, a);
781        } else {
782                otr_keygen(irc, a->user, a->prpl->name);
783        }
784}
785
[764c7d1]786
787/*** local helpers / subroutines: ***/
788
789/* Socialist Millionaires' Protocol */
790void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
791{
792        irc_t *irc = ic->irc;
793        OtrlUserState us = irc->otr_us;
794        OtrlMessageAppOps *ops = &global.otr_ops;
795        OtrlTLV *tlv = NULL;
796        ConnContext *context;
797        NextExpectedSMP nextMsg;
798        user_t *u;
799
800        u = user_findhandle(ic, handle);
801        if(!u) return;
802        context = otrl_context_find(us, handle,
803                ic->acc->user, ic->acc->prpl->name, 1, NULL, NULL, NULL);
[3c80a9d]804        if(!context) {
805                /* huh? out of memory or what? */
806                return;
807        }
[764c7d1]808        nextMsg = context->smstate->nextExpected;
809
810        tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1);
811        if (tlv) {
812                if (nextMsg != OTRL_SMP_EXPECT1) {
813                        irc_usermsg(irc, "smp %s: spurious SMP1 received, aborting", u->nick);
814                        otrl_message_abort_smp(us, ops, u->ic, context);
815                        otrl_sm_state_free(context->smstate);
816                } else {
817                        irc_usermsg(irc, "smp: initiated by %s"
818                                " - respond with \x02otr smp %s <secret>\x02",
819                                u->nick, u->nick);
820                        /* smp stays in EXPECT1 until user responds */
821                }
822        }
823        tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2);
824        if (tlv) {
825                if (nextMsg != OTRL_SMP_EXPECT2) {
826                        irc_usermsg(irc, "smp %s: spurious SMP2 received, aborting", u->nick);
827                        otrl_message_abort_smp(us, ops, u->ic, context);
828                        otrl_sm_state_free(context->smstate);
829                } else {
830                        /* SMP2 received, otrl_message_receiving will have sent SMP3 */
831                        context->smstate->nextExpected = OTRL_SMP_EXPECT4;
832                }
833        }
834        tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3);
835        if (tlv) {
836                if (nextMsg != OTRL_SMP_EXPECT3) {
837                        irc_usermsg(irc, "smp %s: spurious SMP3 received, aborting", u->nick);
838                        otrl_message_abort_smp(us, ops, u->ic, context);
839                        otrl_sm_state_free(context->smstate);
840                } else {
841                        /* SMP3 received, otrl_message_receiving will have sent SMP4 and set fp trust */
842                        const char *trust = context->active_fingerprint->trust;
843                        if(!trust || trust[0]=='\0') {
844                                irc_usermsg(irc, "smp %s: secrets did not match, fingerprint not trusted",
845                                        u->nick);
846                        } else {
847                                irc_usermsg(irc, "smp %s: secrets proved equal, fingerprint trusted",
848                                        u->nick);
849                        }
850                        otrl_sm_state_free(context->smstate);
851                        /* smp is in back in EXPECT1 */
852                }
853        }
854        tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4);
855        if (tlv) {
856                if (nextMsg != OTRL_SMP_EXPECT4) {
857                        irc_usermsg(irc, "smp %s: spurious SMP4 received, aborting", u->nick);
858                        otrl_message_abort_smp(us, ops, u->ic, context);
859                        otrl_sm_state_free(context->smstate);
860                } else {
861                        /* SMP4 received, otrl_message_receiving will have set fp trust */
862                        const char *trust = context->active_fingerprint->trust;
863                        if(!trust || trust[0]=='\0') {
864                                irc_usermsg(irc, "smp %s: secrets did not match, fingerprint not trusted",
865                                        u->nick);
866                        } else {
867                                irc_usermsg(irc, "smp %s: secrets proved equal, fingerprint trusted",
868                                        u->nick);
869                        }
870                        otrl_sm_state_free(context->smstate);
871                        /* smp is in back in EXPECT1 */
872                }
873        }
874        tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT);
875        if (tlv) {
876                irc_usermsg(irc, "smp: received abort from %s", u->nick);
877                otrl_sm_state_free(context->smstate);
878                /* smp is in back in EXPECT1 */
879        }
880}
881
882/* helper to assert that account and protocol names given to ops below always
883   match the im_connection passed through as opdata */
884struct im_connection *check_imc(void *opdata, const char *accountname,
885        const char *protocol)
886{
887        struct im_connection *ic = (struct im_connection *)opdata;
888
889        if (strcmp(accountname, ic->acc->user) != 0) {
890                log_message(LOGLVL_WARNING,
891                        "otr: internal account name mismatch: '%s' vs '%s'",
892                        accountname, ic->acc->user);
893        }
894        if (strcmp(protocol, ic->acc->prpl->name) != 0) {
895                log_message(LOGLVL_WARNING,
896                        "otr: internal protocol name mismatch: '%s' vs '%s'",
897                        protocol, ic->acc->prpl->name);
898        }
899       
900        return ic;
901}
902
[5a71d9c]903user_t *peeruser(irc_t *irc, const char *handle, const char *protocol)
[764c7d1]904{
905        user_t *u;
906       
[8521b02]907        log_message(LOGLVL_DEBUG, "peeruser '%s' '%s'", handle, protocol);
908       
[764c7d1]909        for(u=irc->users; u; u=u->next) {
910                struct prpl *prpl;
911                if(!u->ic || !u->handle)
[8521b02]912                        continue;
[764c7d1]913                prpl = u->ic->acc->prpl;
914                if(strcmp(prpl->name, protocol) == 0
915                        && prpl->handle_cmp(u->handle, handle) == 0) {
[5a71d9c]916                        return u;
[764c7d1]917                }
918        }
919       
[5a71d9c]920        return NULL;
921}
922
[8521b02]923int hexval(char a)
924{
925        int x=tolower(a);
926       
927        if(x>='a' && x<='f')
928                x = x - 'a' + 10;
929        else if(x>='0' && x<='9')
930                x = x - '0';
931        else
932                return -1;
933       
934        return x;
935}
936
[5a71d9c]937const char *peernick(irc_t *irc, const char *handle, const char *protocol)
938{
939        static char fallback[512];
940       
941        user_t *u = peeruser(irc, handle, protocol);
942        if(u) {
943                return u->nick;
944        } else {
945                g_snprintf(fallback, 511, "%s/%s", handle, protocol);
946                return fallback;
947        }
948}
949
950int otr_update_modeflags(irc_t *irc, user_t *u)
951{
[52e6e17]952        char *vb = set_getstr(&irc->set, "voice_buddies");
953        char *hb = set_getstr(&irc->set, "halfop_buddies");
954        char *ob = set_getstr(&irc->set, "op_buddies");
955        int encrypted = u->encrypted;
956        int trusted = u->encrypted > 1;
957        char flags[7];
958        int nflags;
959        char *p = flags;
[8c2b1c3]960        char *from;
[52e6e17]961        int i;
[5a71d9c]962       
[52e6e17]963        if(!strcmp(vb, "encrypted")) {
964                *(p++) = encrypted ? '+' : '-';
965                *(p++) = 'v';
966                nflags++;
967        } else if(!strcmp(vb, "trusted")) {
968                *(p++) = trusted ? '+' : '-';
969                *(p++) = 'v';
970                nflags++;
971        }
972        if(!strcmp(hb, "encrypted")) {
973                *(p++) = encrypted ? '+' : '-';
974                *(p++) = 'h';
975                nflags++;
976        } else if(!strcmp(hb, "trusted")) {
977                *(p++) = trusted ? '+' : '-';
978                *(p++) = 'h';
979                nflags++;
980        }
981        if(!strcmp(ob, "encrypted")) {
982                *(p++) = encrypted ? '+' : '-';
983                *(p++) = 'o';
984                nflags++;
985        } else if(!strcmp(ob, "trusted")) {
986                *(p++) = trusted ? '+' : '-';
987                *(p++) = 'o';
988                nflags++;
989        }
990        *p = '\0';
[5a71d9c]991       
[52e6e17]992        p = g_malloc(nflags * (strlen(u->nick)+1) + 1);
993        *p = '\0';
994        if(!p)
[5a71d9c]995                return 0;
[52e6e17]996        for(i=0; i<nflags; i++) {
997                strcat(p, " ");
998                strcat(p, u->nick);
[5a71d9c]999        }
[8c2b1c3]1000        if(set_getbool(&irc->set, "simulate_netsplit"))
1001                from = g_strdup(irc->myhost);
1002        else
1003                from = g_strdup_printf("%s!%s@%s", irc->mynick, irc->mynick, irc->myhost);
1004        irc_write(irc, ":%s MODE %s %s%s", from, irc->channel, flags, p);
1005        g_free(from);
[52e6e17]1006        g_free(p);
[5a71d9c]1007               
1008        return 1;
[764c7d1]1009}
1010
1011void show_fingerprints(irc_t *irc, ConnContext *ctx)
1012{
1013        char human[45];
1014        Fingerprint *fp;
1015        const char *trust;
1016        int count=0;
1017       
1018        for(fp=&ctx->fingerprint_root; fp; fp=fp->next) {
1019                if(!fp->fingerprint)
1020                        continue;
1021                count++;
1022                otrl_privkey_hash_to_human(human, fp->fingerprint);
1023                if(!fp->trust || fp->trust[0] == '\0') {
1024                        trust="untrusted";
1025                } else {
1026                        trust=fp->trust;
1027                }
1028                if(fp == ctx->active_fingerprint) {
[8521b02]1029                        irc_usermsg(irc, \x02%s (%s)\x02", human, trust);
[764c7d1]1030                } else {
[8521b02]1031                        irc_usermsg(irc, "  %s (%s)", human, trust);
[764c7d1]1032                }
1033        }
1034        if(count==0)
[8521b02]1035                irc_usermsg(irc, "  no fingerprints");
1036}
1037
1038void show_general_otr_info(irc_t *irc)
1039{
1040        ConnContext *ctx;
1041        OtrlPrivKey *key;
1042        char human[45];
1043
1044        /* list all privkeys */
1045        irc_usermsg(irc, "\x1fprivate keys:\x1f");
1046        for(key=irc->otr_us->privkey_root; key; key=key->next) {
1047                const char *hash;
1048               
1049                switch(key->pubkey_type) {
1050                case OTRL_PUBKEY_TYPE_DSA:
1051                        irc_usermsg(irc, "  %s/%s - DSA", key->accountname, key->protocol);
1052                        break;
1053                default:
1054                        irc_usermsg(irc, "  %s/%s - type %d", key->accountname, key->protocol,
1055                                key->pubkey_type);
1056                }
1057
1058                /* No, it doesn't make much sense to search for the privkey again by
1059                   account/protocol, but libotr currently doesn't provide a direct routine
1060                   for hashing a given 'OtrlPrivKey'... */
1061                hash = otrl_privkey_fingerprint(irc->otr_us, human, key->accountname, key->protocol);
1062                if(hash) /* should always succeed */
1063                        irc_usermsg(irc, "    %s", human);
1064        }
1065
1066        /* list all contexts */
1067        irc_usermsg(irc, "%s", "");
1068        irc_usermsg(irc, "\x1f" "connection contexts:\x1f (bold=currently encrypted)");
1069        for(ctx=irc->otr_us->context_root; ctx; ctx=ctx->next) {\
1070                user_t *u;
1071                char *userstring;
1072               
1073                u = peeruser(irc, ctx->username, ctx->protocol);
1074                if(u)
1075                        userstring = g_strdup_printf("%s/%s/%s (%s)",
1076                                ctx->username, ctx->protocol, ctx->accountname, u->nick);
1077                else
1078                        userstring = g_strdup_printf("%s/%s/%s",
1079                                ctx->username, ctx->protocol, ctx->accountname);
1080               
1081                if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
1082                        otrl_privkey_hash_to_human(human, ctx->active_fingerprint->fingerprint);
1083                        irc_usermsg(irc, \x02%s\x02", userstring);
1084                        irc_usermsg(irc, "    %s", human);
1085                } else {
1086                        irc_usermsg(irc, "  %s", userstring);
1087                }
1088               
1089                g_free(userstring);
1090        }
1091}
1092
1093void show_otr_context_info(irc_t *irc, ConnContext *ctx)
1094{
1095        switch(ctx->otr_offer) {
1096        case OFFER_NOT:
1097                irc_usermsg(irc, "  otr offer status: none sent");
1098                break;
1099        case OFFER_SENT:
1100                irc_usermsg(irc, "  otr offer status: awaiting reply");
1101                break;
1102        case OFFER_ACCEPTED:
1103                irc_usermsg(irc, "  otr offer status: accepted our offer");
1104                break;
1105        case OFFER_REJECTED:
1106                irc_usermsg(irc, "  otr offer status: ignored our offer");
1107                break;
1108        default:
1109                irc_usermsg(irc, "  otr offer status: %d", ctx->otr_offer);
1110        }
1111
1112        switch(ctx->msgstate) {
1113        case OTRL_MSGSTATE_PLAINTEXT:
1114                irc_usermsg(irc, "  connection state: cleartext");
1115                break;
1116        case OTRL_MSGSTATE_ENCRYPTED:
1117                irc_usermsg(irc, "  connection state: encrypted (v%d)", ctx->protocol_version);
1118                break;
1119        case OTRL_MSGSTATE_FINISHED:
1120                irc_usermsg(irc, "  connection state: shut down");
1121                break;
1122        default:
1123                irc_usermsg(irc, "  connection state: %d", ctx->msgstate);
1124        }
1125
1126    irc_usermsg(irc, "  known fingerprints: (bold=active)");   
1127        show_fingerprints(irc, ctx);
[764c7d1]1128}
1129
1130void otr_keygen(irc_t *irc, const char *handle, const char *protocol)
1131{
[5bf5edf]1132        char *account_off[] = {"account", "off", NULL};
[764c7d1]1133        GError *err;
1134        GThread *thr;
1135        struct kgdata *kg;
1136        gint ev;
1137       
1138        kg = g_new0(struct kgdata, 1);
1139        if(!kg) {
1140                irc_usermsg(irc, "otr keygen failed: out of memory");
1141                return;
1142        }
1143
1144        /* Assemble the job description to be passed to thread and handler */
1145        kg->irc = irc;
1146        kg->keyfile = g_strdup_printf("%s%s.otr_keys", global.conf->configdir, kg->irc->nick);
1147        if(!kg->keyfile) {
1148                irc_usermsg(irc, "otr keygen failed: out of memory");
1149                g_free(kg);
1150                return;
1151        }
1152        kg->handle = handle;
1153        kg->protocol = protocol;
1154        kg->mutex = g_mutex_new();
1155        if(!kg->mutex) {
1156                irc_usermsg(irc, "otr keygen failed: couldn't create mutex");
1157                g_free(kg->keyfile);
1158                g_free(kg);
1159                return;
1160        }
1161        kg->done = FALSE;
1162
1163        /* Poll for completion of the thread periodically. I would have preferred
1164           to just wait on a pipe but this way it's portable to Windows. *sigh*
1165        */
1166        ev = b_timeout_add(1000, &keygen_finish_handler, kg);
1167        if(!ev) {
1168                irc_usermsg(irc, "otr keygen failed: couldn't register timeout");
1169                g_free(kg->keyfile);
1170                g_mutex_free(kg->mutex);
1171                g_free(kg);
1172                return;
1173        }
1174
[5bf5edf]1175        /* tell the user what's happening, go comatose, and start the keygen */
1176        irc_usermsg(irc, "going comatose for otr key generation, this will take a moment");
1177        irc_usermsg(irc, "all accounts logging out, user commands disabled");
1178        cmd_account(irc, account_off);
1179        irc_usermsg(irc, "generating new otr privkey for %s/%s...",
1180                handle, protocol);
1181       
[764c7d1]1182        thr = g_thread_create(&otr_keygen_thread_func, kg, FALSE, &err);
1183        if(!thr) {
1184                irc_usermsg(irc, "otr keygen failed: %s", err->message);
1185                g_free(kg->keyfile);
1186                g_mutex_free(kg->mutex);
1187                g_free(kg);
1188                b_event_remove(ev);
1189        }
1190}
1191
1192gpointer otr_keygen_thread_func(gpointer data)
1193{
1194        struct kgdata *kg = (struct kgdata *)data;
1195       
1196        /* lock OTR subsystem and do the work */
1197        g_mutex_lock(kg->irc->otr_mutex);
1198        kg->result = otrl_privkey_generate(kg->irc->otr_us, kg->keyfile, kg->handle,
1199                kg->protocol);
1200        g_mutex_unlock(kg->irc->otr_mutex);
1201        /* OTR enabled again */
1202       
1203        /* notify mainloop */
1204        g_mutex_lock(kg->mutex);
1205        kg->done = TRUE;
1206        g_mutex_unlock(kg->mutex);
1207       
1208        return NULL;
1209}
1210
1211gboolean keygen_finish_handler(gpointer data, gint fd, b_input_condition cond)
1212{
1213        struct kgdata *kg = (struct kgdata *)data;
1214        int done;
1215       
1216        g_mutex_lock(kg->mutex);
1217        done = kg->done;
1218        g_mutex_unlock(kg->mutex);
1219        if(kg->done) {
1220                if(kg->result) {
[3c80a9d]1221                        irc_usermsg(kg->irc, "otr keygen: %s", strerror(kg->result));
[764c7d1]1222                } else {
1223                        irc_usermsg(kg->irc, "otr keygen for %s/%s complete", kg->handle, kg->protocol);
1224                }
1225                g_free(kg->keyfile);
1226                g_mutex_free(kg->mutex);
1227                g_free(kg);
1228                return FALSE; /* unregister timeout */
1229        }
1230
1231        return TRUE;  /* still working, continue checking */
1232}
1233
1234void yes_keygen(gpointer w, void *data)
1235{
1236        account_t *acc = (account_t *)data;
1237       
1238        otr_keygen(acc->irc, acc->user, acc->prpl->name);
1239}
1240
1241void no_keygen(gpointer w, void *data)
1242{
1243        account_t *acc = (account_t *)data;
1244       
[94e7eb3]1245        irc_usermsg(acc->irc, "keygen cancelled for %s/%s",
[764c7d1]1246                acc->user, acc->prpl->name);
1247}
1248
1249
1250#else /* WITH_OTR undefined */
1251
1252void cmd_otr(irc_t *irc, char **args)
1253{
1254        irc_usermsg(irc, "otr: n/a, compiled without OTR support");
1255}
1256
1257#endif
Note: See TracBrowser for help on using the repository browser.