Blob Blame History Raw
Patch by Robert Scheck <robert@fedoraproject.org> for bitlbee >= 3.2.2 which just
re-adds the support for libotr < 4.0.0 again - but this time conditionally (based
on the previous implementation in bitlbee). This patch has been also proposed to
upstream, see http://bugs.bitlbee.org/bitlbee/ticket/1163 for details.

--- bitlbee-3.2.2/otr.c				2014-07-05 23:40:53.000000000 +0200
+++ bitlbee-3.2.2/otr.c.libotr-3-4		2014-07-06 16:50:44.000000000 +0200
@@ -58,6 +58,11 @@
 int op_is_logged_in(void *opdata, const char *accountname, const char *protocol,
 	const char *recipient);
 
+#if OTRL_VERSION_MAJOR < 4
+int op_display_otr_message(void *opdata, const char *accountname, const char *protocol,
+	const char *username, const char *msg);
+#endif
+
 void op_inject_message(void *opdata, const char *accountname, const char *protocol,
 	const char *recipient, const char *message);
 
@@ -78,6 +83,7 @@
 
 const char *op_account_name(void *opdata, const char *account, const char *protocol);
 
+#if OTRL_VERSION_MAJOR >= 4
 void op_create_instag(void *opdata, const char *account, const char *protocol);
 
 void op_convert_msg(void *opdata, ConnContext *ctx, OtrlConvertType typ,
@@ -92,6 +98,7 @@
 
 const char *op_otr_error_message(void *opdata, ConnContext *ctx,
 	OtrlErrorCode err_code);
+#endif
 
 /** otr sub-command handlers: **/
 
@@ -153,8 +160,10 @@
 void yes_forget_context(void *data);
 void yes_forget_key(void *data);
 
+#if OTRL_VERSION_MAJOR >= 4
 /* timeout handler that calls otrl_message_poll */
 gboolean ev_message_poll(gpointer data, gint fd, b_input_condition cond);
+#endif
 
 /* helper to make sure accountname and protocol match the incoming "opdata" */
 struct im_connection *check_imc(void *opdata, const char *accountname,
@@ -171,11 +180,16 @@
    returns NULL if not found */
 irc_user_t *peeruser(irc_t *irc, const char *handle, const char *protocol);
 
+#if OTRL_VERSION_MAJOR >= 4
 /* show an otr-related message to the user */
 void display_otr_message(void *opdata, ConnContext *ctx, const char *fmt, ...);
 
 /* write an otr-related message to the system log */
 void log_otr_message(void *opdata, const char *fmt, ...);
+#else
+/* handle SMP TLVs from a received message */
+void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs);
+#endif
 
 /* combined handler for the 'otr smp' and 'otr smpq' commands */
 void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question,
@@ -207,11 +221,13 @@
 /* check whether a string is safe to use in a path component */
 int strsane(const char *s);
 
+#if OTRL_VERSION_MAJOR >= 4
 /* close the OTR connection with the given buddy */
 gboolean otr_disconnect_user(irc_t *irc, irc_user_t *u);
 
 /* close all active OTR connections */
 void otr_disconnect_all(irc_t *irc);
+#endif
 
 /* functions to be called for certain events */
 static const struct irc_plugin otr_plugin;
@@ -241,6 +257,7 @@
 	otr_ops.account_name = &op_account_name;
 	otr_ops.account_name_free = NULL;
 
+#if OTRL_VERSION_MAJOR >= 4
 	/* stuff added with libotr 4.0.0 */
 	otr_ops.received_symkey = NULL;         /* we don't use the extra key */
 	otr_ops.otr_error_message = &op_otr_error_message;
@@ -253,6 +270,13 @@
 	otr_ops.convert_msg = &op_convert_msg;
 	otr_ops.convert_free = &op_convert_free;
 	otr_ops.timer_control = NULL;    	/* we just poll */
+#else
+	otr_ops.notify = NULL;
+	otr_ops.display_otr_message = &op_display_otr_message;
+	otr_ops.protocol_name = NULL;
+	otr_ops.protocol_name_free = NULL;
+	otr_ops.log_message = &op_log_message;
+#endif
 		
 	root_command_add( "otr", 1, cmd_otr, 0 );
 	register_irc_plugin( &otr_plugin );
@@ -277,9 +301,12 @@
 
 	s = set_add( &irc->b->set, "otr_does_html", "true", set_eval_bool, irc );
 	
+
+#if OTRL_VERSION_MAJOR >= 4
 	/* regularly call otrl_message_poll */
 	gint definterval = otrl_message_poll_get_default_interval(irc->otr->us);
 	irc->otr->timer = b_timeout_add(definterval, ev_message_poll, irc->otr);
+#endif
 
 	return TRUE;
 }
@@ -287,8 +314,10 @@
 void otr_irc_free(irc_t *irc)
 {
 	otr_t *otr = irc->otr;
+#if OTRL_VERSION_MAJOR >= 4
 	otr_disconnect_all(irc);
 	b_event_remove(otr->timer);
+#endif
 	otrl_userstate_free(otr->us);
 	if(otr->keygen) {
 		kill(otr->keygen, SIGTERM);
@@ -326,11 +355,13 @@
 		if(e && e!=enoent) {
 			irc_rootmsg(irc, "otr load: %s: %s", s, gcry_strerror(e));
 		}
+#if OTRL_VERSION_MAJOR >= 4
 		g_snprintf(s, 511, "%s%s.otr_instags", global.conf->configdir, irc->user->nick);
 		e = otrl_instag_read(irc->otr->us, s);
 		if(e && e!=enoent) {
 			irc_rootmsg(irc, "otr load: %s: %s", s, gcry_strerror(e));
 		}
+#endif
 	}
 	
 	/* check for otr keys on all accounts */
@@ -428,7 +459,13 @@
 	
 	ignore_msg = otrl_message_receiving(irc->otr->us, &otr_ops, ic,
 		ic->acc->user, ic->acc->prpl->name, iu->bu->handle, msg, &newmsg,
+#if OTRL_VERSION_MAJOR >= 4
 		&tlvs, NULL, NULL, NULL);
+#else
+		&tlvs, NULL, NULL);
+
+	otr_handle_smp(ic, iu->bu->handle, tlvs);
+#endif
 
 	if(ignore_msg) {
 		/* this was an internal OTR protocol message */
@@ -437,8 +474,62 @@
 		/* this was a non-OTR message */
 		return msg;
 	} else {
+#if OTRL_VERSION_MAJOR >= 4
 		/* we're done with the original msg, which will be caller-freed. */
 		return newmsg;
+#else
+		/* OTR has processed this message */
+		ConnContext *context = otrl_context_find(irc->otr->us, iu->bu->handle,
+			ic->acc->user, ic->acc->prpl->name, 0, NULL, NULL, NULL);
+
+		/* we're done with the original msg, which will be caller-freed. */
+		/* NB: must not change the newmsg pointer, since we free it. */
+		msg = newmsg;
+
+		if(context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+			/* HTML decoding */
+			/* perform any necessary stripping that the top level would miss */
+			if(set_getbool(&ic->bee->set, "otr_does_html") &&
+			   !(ic->flags & OPT_DOES_HTML) &&
+			   set_getbool(&ic->bee->set, "strip_html")) {
+				strip_html(msg);
+			}
+
+			/* coloring */
+			if(set_getbool(&ic->bee->set, "otr_color_encrypted")) {
+				int color;                /* color according to f'print trust */
+				char *pre="", *sep="";    /* optional parts */
+				const char *trust = context->active_fingerprint->trust;
+
+				if(trust && trust[0] != '\0')
+					color=3;   /* green */
+				else
+					color=5;   /* red */
+
+				/* in a query window, keep "/me " uncolored at the beginning */
+				if(g_strncasecmp(msg, "/me ", 4) == 0
+				   && irc_user_msgdest(iu) == irc->user->nick) {
+					msg += 4;  /* skip */
+					pre = "/me ";
+				}
+
+				/* comma in first place could mess with the color code */
+				if(msg[0] == ',') {
+				    /* insert a space between color spec and message */
+				    sep = " ";
+				}
+
+				msg = g_strdup_printf("%s\x03%.2d%s%s\x0F", pre,
+					color, sep, msg);
+			}
+		}
+
+		if(msg == newmsg) {
+			msg = g_strdup(newmsg);
+		}
+		otrl_message_free(newmsg);
+		return msg;
+#endif
 	}
 }
 
@@ -446,19 +537,25 @@
 {	
 	int st;
 	char *otrmsg = NULL;
+#if OTRL_VERSION_MAJOR < 4
+	char *emsg = msg;           /* the message as we hand it to libotr */
+#endif
 	ConnContext *ctx = NULL;
 	irc_t *irc = iu->irc;
 	struct im_connection *ic = iu->bu->ic;
+#if OTRL_VERSION_MAJOR >= 4
 	otrl_instag_t instag = OTRL_INSTAG_BEST; // XXX?
 	/* NB: in libotr 4.0.0 OTRL_INSTAG_RECENT will cause a null-pointer deref
 	 * in otrl_message_sending with newly-added OTR contexts.
 	 */
+#endif
 
 	/* don't do OTR on certain (not classic IM) protocols, e.g. twitter */
 	if(ic->acc->prpl->options & OPT_NOOTR) {
 		return msg;
 	}
 
+#if OTRL_VERSION_MAJOR >= 4
 	st = otrl_message_sending(irc->otr->us, &otr_ops, ic,
 		ic->acc->user, ic->acc->prpl->name, iu->bu->handle, instag,
 		msg, NULL, &otrmsg, OTRL_FRAGMENT_SEND_ALL_BUT_LAST, &ctx, NULL, NULL);
@@ -476,6 +573,47 @@
 	}
 
 	return msg;
+#else
+	ctx = otrl_context_find(irc->otr->us,
+			iu->bu->handle, ic->acc->user, ic->acc->prpl->name,
+			1, NULL, NULL, NULL);
+
+	/* HTML encoding */
+	/* consider OTR plaintext to be HTML if otr_does_html is set */
+	if(ctx && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
+	   set_getbool(&ic->bee->set, "otr_does_html") &&
+	   (g_strncasecmp(msg, "<html>", 6) != 0)) {
+		emsg = escape_html(msg);
+	}
+	
+	st = otrl_message_sending(irc->otr->us, &otr_ops, ic,
+		ic->acc->user, ic->acc->prpl->name, iu->bu->handle,
+		emsg, NULL, &otrmsg, NULL, NULL);
+	if(emsg != msg) {
+		g_free(emsg);   /* we're done with this one */
+	}
+	if(st) {
+		return NULL;
+	}
+
+	if(otrmsg) {
+		if(!ctx) {
+			otrl_message_free(otrmsg);
+			return NULL;
+		}
+		st = otrl_message_fragment_and_send(&otr_ops, ic, ctx,
+			otrmsg, OTRL_FRAGMENT_SEND_ALL, NULL);
+		otrl_message_free(otrmsg);
+	} else {
+		/* note: otrl_message_sending handles policy, so that if REQUIRE_ENCRYPTION is set,
+		   this case does not occur */
+		return msg;
+	}
+	
+	/* TODO: Error reporting should be done here now (if st!=0), probably. */
+	
+	return NULL;
+#endif
 }
 
 static const struct irc_plugin otr_plugin =
@@ -592,6 +730,28 @@
 	}
 }
 
+#if OTRL_VERSION_MAJOR < 4
+int op_display_otr_message(void *opdata, const char *accountname,
+	const char *protocol, const char *username, const char *message)
+{
+	struct im_connection *ic = check_imc(opdata, accountname, protocol);
+	char *msg = g_strdup(message);
+	irc_t *irc = ic->bee->ui_data;
+	irc_user_t *u = peeruser(irc, username, protocol);
+
+	strip_html(msg);
+	if(u) {
+		/* display as a notice from this particular user */
+		irc_usernotice(u, "%s", msg);
+	} else {
+		irc_rootmsg(irc, "[otr] %s", msg);
+	}
+
+	g_free(msg);
+	return 0;
+}
+#endif
+
 void op_new_fingerprint(void *opdata, OtrlUserState us,
 	const char *accountname, const char *protocol,
 	const char *username, unsigned char fingerprint[20])
@@ -682,6 +842,17 @@
 	}
 }
 
+#if OTRL_VERSION_MAJOR < 4
+void op_log_message(void *opdata, const char *message)
+{
+	char *msg = g_strdup(message);
+	    
+	strip_html(msg);
+	log_message(LOGLVL_INFO, "otr: %s", msg);
+	g_free(msg);
+}
+#endif
+
 int op_max_message_size(void *opdata, ConnContext *context)
 {
 	struct im_connection *ic =
@@ -698,6 +869,7 @@
 	return peernick(irc, account, protocol);
 }
 
+#if OTRL_VERSION_MAJOR >= 4
 void op_create_instag(void *opdata, const char *account, const char *protocol)
 {
 	struct im_connection *ic =
@@ -937,6 +1109,7 @@
 		return "i suffered an unexpected OTR error";
 	}
 }
+#endif
 
 
 
@@ -952,6 +1125,7 @@
 {
 	irc_user_t *u;
 
+#if OTRL_VERSION_MAJOR >= 4
 	if(!strcmp("*", args[1])) {
 		otr_disconnect_all(irc);
 		irc_rootmsg(irc, "all conversations are now in cleartext");
@@ -962,12 +1136,35 @@
 		else
 			irc_rootmsg(irc, "%s: unknown user", args[1]);
 	}
+#else
+	u = irc_user_by_name(irc, args[1]);
+	if(!u || !u->bu || !u->bu->ic) {
+		irc_rootmsg(irc, "%s: unknown user", args[1]);
+		return;
+	}
+	
+	otrl_message_disconnect(irc->otr->us, &otr_ops,
+		u->bu->ic, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, u->bu->handle);
+	
+	/* for some reason, libotr (3.1.0) doesn't do this itself: */
+	if(u->flags & IRC_USER_OTR_ENCRYPTED) {
+		ConnContext *ctx;
+		ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user,
+			u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
+		if(ctx)
+			op_gone_insecure(u->bu->ic, ctx);
+		else /* huh? */
+			u->flags &= ( IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED );
+	}
+#endif
 }
 
 void cmd_otr_connect(irc_t *irc, char **args)
 {
 	irc_user_t *u;
+#if OTRL_VERSION_MAJOR >= 4
 	char *msg, *query = "?OTR?";
+#endif
 
 	u = irc_user_by_name(irc, args[1]);
 	if(!u || !u->bu || !u->bu->ic) {
@@ -979,6 +1176,7 @@
 		return;
 	}
 	
+#if OTRL_VERSION_MAJOR >= 4
 	/* passing this through the filter so it goes through libotr which
 	 * will replace the simple query string with a proper one */
 	msg = otr_filter_msg_out(u, query, 0);
@@ -991,6 +1189,9 @@
 		if(msg != query)
 			g_free(msg);
 	}
+#else
+	bee_user_msg(irc->b, u->bu, "?OTR?v2?", 0);
+#endif
 }
 
 void cmd_otr_smp(irc_t *irc, char **args)
@@ -1018,7 +1219,11 @@
 	}
 	
 	ctx = otrl_context_find(irc->otr->us, u->bu->handle,
+#if OTRL_VERSION_MAJOR >= 4
 		u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
+#else
+		u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
+#endif
 	if(!ctx) {
 		irc_rootmsg(irc, "%s: no otr context with user", args[1]);
 		return;
@@ -1082,7 +1287,11 @@
 		if(protocol && myhandle) {
 			*(myhandle++) = '\0';
 			handle = arg;
+#if OTRL_VERSION_MAJOR >= 4
 			ctx = otrl_context_find(irc->otr->us, handle, myhandle, protocol, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
+#else
+			ctx = otrl_context_find(irc->otr->us, handle, myhandle, protocol, 0, NULL, NULL, NULL);
+#endif
 			if(!ctx) {
 				irc_rootmsg(irc, "no such context");
 				g_free(arg);
@@ -1096,7 +1305,11 @@
 				return;
 			}
 			ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user,
+#if OTRL_VERSION_MAJOR >= 4
 				u->bu->ic->acc->prpl->name, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
+#else
+				u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
+#endif
 			if(!ctx) {
 				irc_rootmsg(irc, "no otr context with %s", args[1]);
 				g_free(arg);
@@ -1217,7 +1430,11 @@
 		}
 		
 		ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user,
+#if OTRL_VERSION_MAJOR >= 4
 			u->bu->ic->acc->prpl->name, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
+#else
+			u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
+#endif
 		if(!ctx) {
 			irc_rootmsg(irc, "no otr context with %s", args[2]);
 			return;
@@ -1260,7 +1477,11 @@
 		}
 		
 		ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user,
+#if OTRL_VERSION_MAJOR >= 4
 			u->bu->ic->acc->prpl->name, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
+#else
+			u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
+#endif
 		if(!ctx) {
 			irc_rootmsg(irc, "no otr context with %s", args[2]);
 			return;
@@ -1308,6 +1529,136 @@
 
 /*** local helpers / subroutines: ***/
 
+#if OTRL_VERSION_MAJOR < 4
+/* Socialist Millionaires' Protocol */
+void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
+{
+	irc_t *irc = ic->bee->ui_data;
+	OtrlUserState us = irc->otr->us;
+	OtrlMessageAppOps *ops = &otr_ops;
+	OtrlTLV *tlv = NULL;
+	ConnContext *context;
+	NextExpectedSMP nextMsg;
+	irc_user_t *u;
+	bee_user_t *bu;
+
+	bu = bee_user_by_handle(ic->bee, ic, handle);
+	if(!bu || !(u = bu->ui_data)) return;
+	context = otrl_context_find(us, handle,
+		ic->acc->user, ic->acc->prpl->name, 1, NULL, NULL, NULL);
+	if(!context) {
+		/* huh? out of memory or what? */
+		irc_rootmsg(irc, "smp: failed to get otr context for %s", u->nick);
+		otrl_message_abort_smp(us, ops, u->bu->ic, context);
+		otrl_sm_state_free(context->smstate);
+		return;
+	}
+	nextMsg = context->smstate->nextExpected;
+
+	if (context->smstate->sm_prog_state == OTRL_SMP_PROG_CHEATED) {
+		irc_rootmsg(irc, "smp %s: opponent violated protocol, aborting",
+			u->nick);
+		otrl_message_abort_smp(us, ops, u->bu->ic, context);
+		otrl_sm_state_free(context->smstate);
+		return;
+	}
+
+	tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1Q);
+	if (tlv) {
+		if (nextMsg != OTRL_SMP_EXPECT1) {
+			irc_rootmsg(irc, "smp %s: spurious SMP1Q received, aborting", u->nick);
+			otrl_message_abort_smp(us, ops, u->bu->ic, context);
+			otrl_sm_state_free(context->smstate);
+		} else {
+			char *question = g_strndup((char *)tlv->data, tlv->len);
+			irc_rootmsg(irc, "smp: initiated by %s with question: \x02\"%s\"\x02", u->nick,
+				question);
+			irc_rootmsg(irc, "smp: respond with \x02otr smp %s <answer>\x02",
+				u->nick);
+			g_free(question);
+			/* smp stays in EXPECT1 until user responds */
+		}
+	}
+	tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1);
+	if (tlv) {
+		if (nextMsg != OTRL_SMP_EXPECT1) {
+			irc_rootmsg(irc, "smp %s: spurious SMP1 received, aborting", u->nick);
+			otrl_message_abort_smp(us, ops, u->bu->ic, context);
+			otrl_sm_state_free(context->smstate);
+		} else {
+			irc_rootmsg(irc, "smp: initiated by %s"
+				" - respond with \x02otr smp %s <secret>\x02",
+				u->nick, u->nick);
+			/* smp stays in EXPECT1 until user responds */
+		}
+	}
+	tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2);
+	if (tlv) {
+		if (nextMsg != OTRL_SMP_EXPECT2) {
+			irc_rootmsg(irc, "smp %s: spurious SMP2 received, aborting", u->nick);
+			otrl_message_abort_smp(us, ops, u->bu->ic, context);
+			otrl_sm_state_free(context->smstate);
+		} else {
+			/* SMP2 received, otrl_message_receiving will have sent SMP3 */
+			context->smstate->nextExpected = OTRL_SMP_EXPECT4;
+		}
+	}
+	tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3);
+	if (tlv) {
+		if (nextMsg != OTRL_SMP_EXPECT3) {
+			irc_rootmsg(irc, "smp %s: spurious SMP3 received, aborting", u->nick);
+			otrl_message_abort_smp(us, ops, u->bu->ic, context);
+			otrl_sm_state_free(context->smstate);
+		} else {
+			/* SMP3 received, otrl_message_receiving will have sent SMP4 */
+			if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) {
+				if(context->smstate->received_question) {
+					irc_rootmsg(irc, "smp %s: correct answer, you are trusted",
+						u->nick);
+				} else {
+					irc_rootmsg(irc, "smp %s: secrets proved equal, fingerprint trusted",
+						u->nick);
+				}
+			} else {
+				if(context->smstate->received_question) {
+					irc_rootmsg(irc, "smp %s: wrong answer, you are not trusted",
+						u->nick);
+				} else {
+					irc_rootmsg(irc, "smp %s: secrets did not match, fingerprint not trusted",
+						u->nick);
+				}
+			}
+			otrl_sm_state_free(context->smstate);
+			/* smp is in back in EXPECT1 */
+		}
+	}
+	tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4);
+	if (tlv) {
+		if (nextMsg != OTRL_SMP_EXPECT4) {
+			irc_rootmsg(irc, "smp %s: spurious SMP4 received, aborting", u->nick);
+			otrl_message_abort_smp(us, ops, u->bu->ic, context);
+			otrl_sm_state_free(context->smstate);
+		} else {
+			/* SMP4 received, otrl_message_receiving will have set fp trust */
+			if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) {
+				irc_rootmsg(irc, "smp %s: secrets proved equal, fingerprint trusted",
+					u->nick);
+			} else {
+				irc_rootmsg(irc, "smp %s: secrets did not match, fingerprint not trusted",
+					u->nick);
+			}
+			otrl_sm_state_free(context->smstate);
+			/* smp is in back in EXPECT1 */
+		}
+	}
+	tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT);
+	if (tlv) {
+		irc_rootmsg(irc, "smp: received abort from %s", u->nick);
+		otrl_sm_state_free(context->smstate);
+		/* smp is in back in EXPECT1 */
+	}
+}
+#else
 void log_otr_message(void *opdata, const char *fmt, ...)
 {
 	va_list va;
@@ -1340,6 +1691,7 @@
 
 	g_free(msg);
 }
+#endif
 
 /* combined handler for the 'otr smp' and 'otr smpq' commands */
 void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question,
@@ -1347,7 +1699,9 @@
 {
 	irc_user_t *u;
 	ConnContext *ctx;
+#if OTRL_VERSION_MAJOR >= 4
 	otrl_instag_t instag = OTRL_INSTAG_BEST;  // XXX
+#endif
 
 	u = irc_user_by_name(irc, nick);
 	if(!u || !u->bu || !u->bu->ic) {
@@ -1360,7 +1714,11 @@
 	}
 	
 	ctx = otrl_context_find(irc->otr->us, u->bu->handle,
+#if OTRL_VERSION_MAJOR >= 4
 		u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, instag, 0, NULL, NULL, NULL);
+#else
+		u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
+#endif
 	if(!ctx || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED) {
 		irc_rootmsg(irc, "smp: otr inactive with %s, try \x02otr connect"
 				" %s\x02", nick, nick);
@@ -1401,6 +1759,7 @@
 	}
 }
 
+#if OTRL_VERSION_MAJOR >= 4
 /* timeout handler that calls otrl_message_poll */
 gboolean ev_message_poll(gpointer data, gint fd, b_input_condition cond)
 {
@@ -1411,6 +1770,7 @@
 
 	return TRUE;	/* cycle timer */
 }
+#endif
 
 /* helper to assert that account and protocol names given to ops below always
    match the im_connection passed through as opdata */
@@ -1419,6 +1779,7 @@
 {
 	struct im_connection *ic = (struct im_connection *)opdata;
 
+#if OTRL_VERSION_MAJOR >= 4
 	/* libotr 4.0.0 has a bug where it doesn't set opdata, so we catch
 	 * that and try to find the desired connection in the global list. */
 	if(!ic) {
@@ -1433,6 +1794,7 @@
 		if(!l)
 			return NULL;
 	}
+#endif
 
 	if (strcmp(accountname, ic->acc->user) != 0) {
 		log_message(LOGLVL_WARNING,
@@ -2005,6 +2367,7 @@
 	return strpbrk(s, "/\\") == NULL;
 }
 
+#if OTRL_VERSION_MAJOR >= 4
 /* close the OTR connection with the given buddy */
 gboolean otr_disconnect_user(irc_t *irc, irc_user_t *u)
 {
@@ -2035,5 +2398,6 @@
 		}
 	}
 }
+#endif
 
 /* vim: set noet ts=4 sw=4: */
--- bitlbee-3.2.2/otr.h				2014-07-05 23:40:53.000000000 +0200
+++ bitlbee-3.2.2/otr.h.libotr-3-4		2014-07-06 16:49:21.000000000 +0200
@@ -66,8 +66,10 @@
 	/* keygen jobs waiting to be sent to slave */
 	kg_t *todo;
 
+#if OTRL_VERSION_MAJOR >= 4
 	/* event timer for otrl_message_poll */
 	gint timer;
+#endif
 } otr_t;
 
 /* called from main() */
--- bitlbee-3.2.2/configure			2014-07-05 23:40:53.000000000 +0200
+++ bitlbee-3.2.2/configure.libotr-3-4		2014-07-06 17:09:54.000000000 +0200
@@ -517,12 +517,6 @@
 	echo "CFLAGS+=-I${otrprefix}/include" >> Makefile.settings
 	echo 'OTR_PI=otr.so' >> Makefile.settings
 fi
-if [ "$otr" != 0 ] && ! pkg-config libotr --atleast-version=4.0; then
-	echo
-	echo 'WARNING: Your libotr seems to be old. BitlBee now needs at least libotr 4.0.'
-	# Not hard-failing because the code above doesn't use pkg-config, so who knows
-	# what's true at this point...
-fi
 
 if [ "$skype" = "1" -o "$skype" = "plugin" ]; then
 	if [ "$arch" = "Darwin" ]; then