source: otr.c @ 94e7eb3

Last change on this file since 94e7eb3 was 94e7eb3, checked in by Sven Moritz Hallberg <sm@…>, at 2008-02-10T17:56:59Z

add 'otr keygen' command

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