source: otr.c @ 5bf5edf

Last change on this file since 5bf5edf was 5bf5edf, checked in by Sven Moritz Hallberg <sm@…>, at 2008-02-10T21:54:28Z

log out all accounts when going into keygen

  • Property mode set to 100644
File size: 34.1 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;
960        int i;
[5a71d9c]961       
[52e6e17]962        if(!strcmp(vb, "encrypted")) {
963                *(p++) = encrypted ? '+' : '-';
964                *(p++) = 'v';
965                nflags++;
966        } else if(!strcmp(vb, "trusted")) {
967                *(p++) = trusted ? '+' : '-';
968                *(p++) = 'v';
969                nflags++;
970        }
971        if(!strcmp(hb, "encrypted")) {
972                *(p++) = encrypted ? '+' : '-';
973                *(p++) = 'h';
974                nflags++;
975        } else if(!strcmp(hb, "trusted")) {
976                *(p++) = trusted ? '+' : '-';
977                *(p++) = 'h';
978                nflags++;
979        }
980        if(!strcmp(ob, "encrypted")) {
981                *(p++) = encrypted ? '+' : '-';
982                *(p++) = 'o';
983                nflags++;
984        } else if(!strcmp(ob, "trusted")) {
985                *(p++) = trusted ? '+' : '-';
986                *(p++) = 'o';
987                nflags++;
988        }
989        *p = '\0';
[5a71d9c]990       
[52e6e17]991        p = g_malloc(nflags * (strlen(u->nick)+1) + 1);
992        *p = '\0';
993        if(!p)
[5a71d9c]994                return 0;
[52e6e17]995        for(i=0; i<nflags; i++) {
996                strcat(p, " ");
997                strcat(p, u->nick);
[5a71d9c]998        }
[52e6e17]999        irc_write( irc, ":%s!%s@%s MODE %s %s%s", irc->mynick, irc->mynick, irc->myhost,
1000                irc->channel, flags, p );
1001        g_free(p);
[5a71d9c]1002               
1003        return 1;
[764c7d1]1004}
1005
1006void show_fingerprints(irc_t *irc, ConnContext *ctx)
1007{
1008        char human[45];
1009        Fingerprint *fp;
1010        const char *trust;
1011        int count=0;
1012       
1013        for(fp=&ctx->fingerprint_root; fp; fp=fp->next) {
1014                if(!fp->fingerprint)
1015                        continue;
1016                count++;
1017                otrl_privkey_hash_to_human(human, fp->fingerprint);
1018                if(!fp->trust || fp->trust[0] == '\0') {
1019                        trust="untrusted";
1020                } else {
1021                        trust=fp->trust;
1022                }
1023                if(fp == ctx->active_fingerprint) {
[8521b02]1024                        irc_usermsg(irc, \x02%s (%s)\x02", human, trust);
[764c7d1]1025                } else {
[8521b02]1026                        irc_usermsg(irc, "  %s (%s)", human, trust);
[764c7d1]1027                }
1028        }
1029        if(count==0)
[8521b02]1030                irc_usermsg(irc, "  no fingerprints");
1031}
1032
1033void show_general_otr_info(irc_t *irc)
1034{
1035        ConnContext *ctx;
1036        OtrlPrivKey *key;
1037        char human[45];
1038
1039        /* list all privkeys */
1040        irc_usermsg(irc, "\x1fprivate keys:\x1f");
1041        for(key=irc->otr_us->privkey_root; key; key=key->next) {
1042                const char *hash;
1043               
1044                switch(key->pubkey_type) {
1045                case OTRL_PUBKEY_TYPE_DSA:
1046                        irc_usermsg(irc, "  %s/%s - DSA", key->accountname, key->protocol);
1047                        break;
1048                default:
1049                        irc_usermsg(irc, "  %s/%s - type %d", key->accountname, key->protocol,
1050                                key->pubkey_type);
1051                }
1052
1053                /* No, it doesn't make much sense to search for the privkey again by
1054                   account/protocol, but libotr currently doesn't provide a direct routine
1055                   for hashing a given 'OtrlPrivKey'... */
1056                hash = otrl_privkey_fingerprint(irc->otr_us, human, key->accountname, key->protocol);
1057                if(hash) /* should always succeed */
1058                        irc_usermsg(irc, "    %s", human);
1059        }
1060
1061        /* list all contexts */
1062        irc_usermsg(irc, "%s", "");
1063        irc_usermsg(irc, "\x1f" "connection contexts:\x1f (bold=currently encrypted)");
1064        for(ctx=irc->otr_us->context_root; ctx; ctx=ctx->next) {\
1065                user_t *u;
1066                char *userstring;
1067               
1068                u = peeruser(irc, ctx->username, ctx->protocol);
1069                if(u)
1070                        userstring = g_strdup_printf("%s/%s/%s (%s)",
1071                                ctx->username, ctx->protocol, ctx->accountname, u->nick);
1072                else
1073                        userstring = g_strdup_printf("%s/%s/%s",
1074                                ctx->username, ctx->protocol, ctx->accountname);
1075               
1076                if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
1077                        otrl_privkey_hash_to_human(human, ctx->active_fingerprint->fingerprint);
1078                        irc_usermsg(irc, \x02%s\x02", userstring);
1079                        irc_usermsg(irc, "    %s", human);
1080                } else {
1081                        irc_usermsg(irc, "  %s", userstring);
1082                }
1083               
1084                g_free(userstring);
1085        }
1086}
1087
1088void show_otr_context_info(irc_t *irc, ConnContext *ctx)
1089{
1090        switch(ctx->otr_offer) {
1091        case OFFER_NOT:
1092                irc_usermsg(irc, "  otr offer status: none sent");
1093                break;
1094        case OFFER_SENT:
1095                irc_usermsg(irc, "  otr offer status: awaiting reply");
1096                break;
1097        case OFFER_ACCEPTED:
1098                irc_usermsg(irc, "  otr offer status: accepted our offer");
1099                break;
1100        case OFFER_REJECTED:
1101                irc_usermsg(irc, "  otr offer status: ignored our offer");
1102                break;
1103        default:
1104                irc_usermsg(irc, "  otr offer status: %d", ctx->otr_offer);
1105        }
1106
1107        switch(ctx->msgstate) {
1108        case OTRL_MSGSTATE_PLAINTEXT:
1109                irc_usermsg(irc, "  connection state: cleartext");
1110                break;
1111        case OTRL_MSGSTATE_ENCRYPTED:
1112                irc_usermsg(irc, "  connection state: encrypted (v%d)", ctx->protocol_version);
1113                break;
1114        case OTRL_MSGSTATE_FINISHED:
1115                irc_usermsg(irc, "  connection state: shut down");
1116                break;
1117        default:
1118                irc_usermsg(irc, "  connection state: %d", ctx->msgstate);
1119        }
1120
1121    irc_usermsg(irc, "  known fingerprints: (bold=active)");   
1122        show_fingerprints(irc, ctx);
[764c7d1]1123}
1124
1125void otr_keygen(irc_t *irc, const char *handle, const char *protocol)
1126{
[5bf5edf]1127        char *account_off[] = {"account", "off", NULL};
[764c7d1]1128        GError *err;
1129        GThread *thr;
1130        struct kgdata *kg;
1131        gint ev;
1132       
1133        kg = g_new0(struct kgdata, 1);
1134        if(!kg) {
1135                irc_usermsg(irc, "otr keygen failed: out of memory");
1136                return;
1137        }
1138
1139        /* Assemble the job description to be passed to thread and handler */
1140        kg->irc = irc;
1141        kg->keyfile = g_strdup_printf("%s%s.otr_keys", global.conf->configdir, kg->irc->nick);
1142        if(!kg->keyfile) {
1143                irc_usermsg(irc, "otr keygen failed: out of memory");
1144                g_free(kg);
1145                return;
1146        }
1147        kg->handle = handle;
1148        kg->protocol = protocol;
1149        kg->mutex = g_mutex_new();
1150        if(!kg->mutex) {
1151                irc_usermsg(irc, "otr keygen failed: couldn't create mutex");
1152                g_free(kg->keyfile);
1153                g_free(kg);
1154                return;
1155        }
1156        kg->done = FALSE;
1157
1158        /* Poll for completion of the thread periodically. I would have preferred
1159           to just wait on a pipe but this way it's portable to Windows. *sigh*
1160        */
1161        ev = b_timeout_add(1000, &keygen_finish_handler, kg);
1162        if(!ev) {
1163                irc_usermsg(irc, "otr keygen failed: couldn't register timeout");
1164                g_free(kg->keyfile);
1165                g_mutex_free(kg->mutex);
1166                g_free(kg);
1167                return;
1168        }
1169
[5bf5edf]1170        /* tell the user what's happening, go comatose, and start the keygen */
1171        irc_usermsg(irc, "going comatose for otr key generation, this will take a moment");
1172        irc_usermsg(irc, "all accounts logging out, user commands disabled");
1173        cmd_account(irc, account_off);
1174        irc_usermsg(irc, "generating new otr privkey for %s/%s...",
1175                handle, protocol);
1176       
[764c7d1]1177        thr = g_thread_create(&otr_keygen_thread_func, kg, FALSE, &err);
1178        if(!thr) {
1179                irc_usermsg(irc, "otr keygen failed: %s", err->message);
1180                g_free(kg->keyfile);
1181                g_mutex_free(kg->mutex);
1182                g_free(kg);
1183                b_event_remove(ev);
1184        }
1185}
1186
1187gpointer otr_keygen_thread_func(gpointer data)
1188{
1189        struct kgdata *kg = (struct kgdata *)data;
1190       
1191        /* lock OTR subsystem and do the work */
1192        g_mutex_lock(kg->irc->otr_mutex);
1193        kg->result = otrl_privkey_generate(kg->irc->otr_us, kg->keyfile, kg->handle,
1194                kg->protocol);
1195        g_mutex_unlock(kg->irc->otr_mutex);
1196        /* OTR enabled again */
1197       
1198        /* notify mainloop */
1199        g_mutex_lock(kg->mutex);
1200        kg->done = TRUE;
1201        g_mutex_unlock(kg->mutex);
1202       
1203        return NULL;
1204}
1205
1206gboolean keygen_finish_handler(gpointer data, gint fd, b_input_condition cond)
1207{
1208        struct kgdata *kg = (struct kgdata *)data;
1209        int done;
1210       
1211        g_mutex_lock(kg->mutex);
1212        done = kg->done;
1213        g_mutex_unlock(kg->mutex);
1214        if(kg->done) {
1215                if(kg->result) {
[3c80a9d]1216                        irc_usermsg(kg->irc, "otr keygen: %s", strerror(kg->result));
[764c7d1]1217                } else {
1218                        irc_usermsg(kg->irc, "otr keygen for %s/%s complete", kg->handle, kg->protocol);
1219                }
1220                g_free(kg->keyfile);
1221                g_mutex_free(kg->mutex);
1222                g_free(kg);
1223                return FALSE; /* unregister timeout */
1224        }
1225
1226        return TRUE;  /* still working, continue checking */
1227}
1228
1229void yes_keygen(gpointer w, void *data)
1230{
1231        account_t *acc = (account_t *)data;
1232       
1233        otr_keygen(acc->irc, acc->user, acc->prpl->name);
1234}
1235
1236void no_keygen(gpointer w, void *data)
1237{
1238        account_t *acc = (account_t *)data;
1239       
[94e7eb3]1240        irc_usermsg(acc->irc, "keygen cancelled for %s/%s",
[764c7d1]1241                acc->user, acc->prpl->name);
1242}
1243
1244
1245#else /* WITH_OTR undefined */
1246
1247void cmd_otr(irc_t *irc, char **args)
1248{
1249        irc_usermsg(irc, "otr: n/a, compiled without OTR support");
1250}
1251
1252#endif
Note: See TracBrowser for help on using the repository browser.