// Send a message to the network, fragmenting first if necessary. // All messages to be sent to the network should go through this // method immediately before they are sent, ie after encryption. String fragmentAndSend(String message, int fragPolicy, OTRCallbacks callback) { int mms = callback.maxMessageSize(this); // Don't incur overhead of fragmentation unless necessary if (mms == 0 || message.length() <= mms) { // No fragmentation necessary if (fragPolicy == Policy.FRAGMENT_SEND_ALL) { callback.injectMessage(this.accountName, this.protocol, this.recName, message); return null; } else { // return the entire given message. return message; } } int fragment_count = ((message.length() - 1) / (mms - 19)) + 1; // like ceil(msglen/(mms - 19)) String[] frags = Proto.fragmentCreate(mms, fragment_count, message); String returnFragment = null; // Determine which fragments to send and which to return // based on given Fragment Policy. If the first fragment // should be returned instead of sent, store it. if (fragPolicy == Policy.FRAGMENT_SEND_ALL_BUT_FIRST) { returnFragment = frags[0]; } else { callback.injectMessage(accountName, protocol, recName, frags[0]); } // Prevent the demo receiver to receive all the messages in a single read try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 1; i < fragment_count - 1; i++) { callback.injectMessage(accountName, protocol, recName, frags[i]); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } // If the last fragment should be stored instead of sent, // store it if (fragPolicy == Policy.FRAGMENT_SEND_ALL_BUT_LAST) { returnFragment = frags[fragment_count - 1]; } else { callback.injectMessage(accountName, protocol, recName, frags[fragment_count - 1]); } return returnFragment; }
/** * Put a connection into the PLAINTEXT state, first sending the other side a notice that we're * doing so if we're currently ENCRYPTED, and we think he's logged in. * * @throws OTRException */ public void disconnect(OTRCallbacks callback) throws OTRException { if (this.msgState.getCurState() == MsgState.ST_ENCRYPTED && this.their_keyid > 0 && callback.isLoggedIn(accountName, protocol, recName) == 1) { TLV[] tlvs = new TLV[1]; tlvs[0] = new TLV(TLV.DISCONNECTED, new byte[0]); DataMessage dm = Proto.createData(this, new byte[0], Proto.MSGFLAGS_IGNORE_UNREADABLE, tlvs); callback.injectMessage(accountName, protocol, recName, new String(dm.getContent())); } forceFinished(); this.msgState.curState = MsgState.ST_UNENCRYPTED; callback.updateContextList(); }
/** * Handle a message just received from the network. It is safe to pass all received messages to * this routine. * * <p>If no Exception is thrown, and the return value is not null, replace the received message * with msg in the returned StringTLV, and deliver that to the user instead. * * <p>If the return value is null, then the message you received was an internal protocol message, * and no message should be delivered to the user. */ public StringTLV messageReceiving(String inMessage, OTRCallbacks callback) throws OTRException { OTRMessage message = OTRMessage.parse(inMessage); /* Check the policy */ int policy = callback.getOtrPolicy(this); /* Should we go on at all? */ if ((policy & Policy.VERSION_MASK) == 0) { return null; } // See if we have a fragment switch (Proto.fragmentAccumulate(this, new String(message.getContent()))) { case Proto.FRAGMENT_UNFRAGMENTED: // Do nothing break; case Proto.FRAGMENT_INCOMPLETE: // We've accumulated this fragment, but we don't have a // complete message yet return null; case Proto.FRAGMENT_COMPLETE: // We've got a new complete message, in unfragmessage. message = OTRMessage.parse(this.complete_msg); break; } byte msgtype = message.getType(); /* See if they responded to our OTR offer */ if ((policy & Policy.SEND_WHITESPACE_TAG) != 0) { if (msgtype != OTRMessage.MSG_NOTOTR) { otr_offer = OFFER_ACCEPTED; } else if (otr_offer == OFFER_SENT) { otr_offer = OFFER_REJECTED; } } gone_encrypted = 0; ignore_message = -1; switch (msgtype) { case OTRMessage.MSG_QUERY: /* Start AKE */ try { auth.startAKE(null); this.sendOrErrorAuth(false, callback); } catch (OTRException e) { this.sendOrErrorAuth(true, callback); } if (auth.havemsgp != 0) { this.lastMessage = auth.lastauthmsg; } /* Don't display the Query message to the user. */ if (ignore_message == -1) ignore_message = 1; break; case OTRMessage.MSG_DH_COMMIT: if ((policy & Policy.ALLOW_V2) != 0) { try { auth.handleCommit(message.getContent(), null); this.sendOrErrorAuth(false, callback); } catch (OTRException e) { this.sendOrErrorAuth(true, callback); } } if (ignore_message == -1) ignore_message = 1; break; case OTRMessage.MSG_DH_KEY: if ((policy & Policy.ALLOW_V2) != 0) { /* Get our private key */ PrivKey privkey = us.getPrivKey(new Account(accountName, protocol), true); try { auth.handleKey(message.getContent(), privkey); this.sendOrErrorAuth(false, callback); } catch (OTRException e) { this.sendOrErrorAuth(true, callback); } } if (ignore_message == -1) ignore_message = 1; break; case OTRMessage.MSG_REVEAL_SIGNATURE: if ((policy & Policy.ALLOW_V2) != 0) { /* Get our private key */ PrivKey privkey = us.getPrivKey(new Account(accountName, protocol), true); try { auth.handleRevealsig(message.getContent(), privkey); this.goEncrypted(callback); this.sendOrErrorAuth(false, callback); } catch (OTRException e) { this.sendOrErrorAuth(true, callback); } } if (ignore_message == -1) ignore_message = 1; break; case OTRMessage.MSG_SIGNATURE: if ((policy & Policy.ALLOW_V2) != 0) { /* Get our private key */ try { auth.handleSignature(message.getContent()); this.goEncrypted(callback); this.sendOrErrorAuth(false, callback); } catch (OTRException e) { this.sendOrErrorAuth(true, callback); } } if (ignore_message == -1) ignore_message = 1; break; case OTRMessage.MSG_DATA: switch (msgState.getCurState()) { case MsgState.ST_UNENCRYPTED: case MsgState.ST_FINISHED: callback.handleMsgEvent(OTRCallbacks.OTRL_ERRCODE_MSG_NOT_IN_PRIVATE, this, null); ignore_message = 1; String err_msg = callback.errorMessage(this, OTRCallbacks.OTRL_ERRCODE_MSG_NOT_IN_PRIVATE); callback.injectMessage(accountName, protocol, recName, err_msg); break; case MsgState.ST_ENCRYPTED: byte[] res; StringTLV stlv = new StringTLV(); OTRTLV[] tlvs; try { DataMessage dm = (DataMessage) message; res = Proto.acceptData(this, dm, null); } catch (OTRException otre) { callback.handleMsgEvent(OTRCallbacks.OTRL_MSGEVENT_RCVDMSG_UNREADABLE, this, null); return null; } int end = 0; for (; end < res.length && res[end] != 0; end++) {} stlv.msg = new String(res, 0, end); if (end != res.length && end + 1 != res.length) { end++; tlvs = new TLV().parse(res, end, res.length - end); stlv.tlvs = tlvs; /* If the other side told us he's disconnected his * private connection, make a note of that so we * don't try sending anything else to him. */ OTRTLV tlv = new TLV().find(tlvs, TLV.DISCONNECTED); if (tlv != null) { forceFinished(); } /* If TLVs contain SMP data, process it */ int nextMsg = this.smstate.nextExpected; tlv = new TLV().find(tlvs, TLV.SMP1Q); if (tlv != null && nextMsg == SM.EXPECT1) { /* We can only do the verification half now. * We must wait for the secret to be entered * to continue. */ byte[] question = tlv.getValue(); int qlen = 0; for (; qlen != question.length && question[qlen] != 0; qlen++) {} if (qlen == question.length) qlen = 0; else qlen++; byte[] input = new byte[question.length - qlen]; System.arraycopy(question, qlen, input, 0, question.length - qlen); SM.step2a(this.smstate, input, 1, prov); if (qlen != 0) qlen--; byte[] plainq = new byte[qlen]; System.arraycopy(question, 0, plainq, 0, qlen); if (this.smstate.smProgState != SM.PROG_CHEATED) { callback.handleSmpEvent( OTRCallbacks.OTRL_SMPEVENT_ASK_FOR_ANSWER, this, 25, new String(plainq)); } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_CHEATED, this, 0, null); this.smstate.nextExpected = SM.EXPECT1; this.smstate.smProgState = SM.PROG_OK; } } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_ERROR, this, 0, null); } tlv = new TLV().find(tlvs, TLV.SMP1); if (tlv != null) { if (nextMsg == SM.EXPECT1) { /* We can only do the verification half now. * We must wait for the secret to be entered * to continue. */ SM.step2a(this.smstate, tlv.getValue(), 0, prov); if (this.smstate.smProgState != SM.PROG_CHEATED) { callback.handleSmpEvent( OTRCallbacks.OTRL_SMPEVENT_ASK_FOR_SECRET, this, 25, null); } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_CHEATED, this, 0, null); this.smstate.nextExpected = SM.EXPECT1; this.smstate.smProgState = SM.PROG_OK; } } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_ERROR, this, 0, null); } } tlv = new TLV().find(tlvs, TLV.SMP2); if (tlv != null && nextMsg == SM.EXPECT2) { byte[] nextmsg = SM.step3(this.smstate, tlv.getValue(), prov); if (this.smstate.smProgState != SM.PROG_CHEATED) { /* Send msg with next smp msg content */ OTRTLV sendtlv = new TLV(TLV.SMP3, nextmsg); OTRTLV[] stlvs = new OTRTLV[1]; stlvs[0] = sendtlv; DataMessage dm = Proto.createData(this, new byte[0], Proto.MSGFLAGS_IGNORE_UNREADABLE, stlvs); byte[] senddata = dm.getContent(); this.fragmentAndSend(new String(senddata), Policy.FRAGMENT_SEND_ALL, callback); this.smstate.nextExpected = SM.EXPECT4; } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_CHEATED, this, 0, null); this.smstate.nextExpected = SM.EXPECT1; this.smstate.smProgState = SM.PROG_OK; } } else if (tlv != null) { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_ERROR, this, 0, null); } tlv = new TLV().find(tlvs, TLV.SMP3); if (tlv != null && nextMsg == SM.EXPECT3) { byte[] nextmsg = SM.step4(this.smstate, tlv.getValue(), prov); /* Set trust level based on result */ if (this.smstate.smProgState == SM.PROG_SUCCEEDED) { this.setSmpTrust(callback, true); } else { this.setSmpTrust(callback, false); } if (this.smstate.smProgState != SM.PROG_CHEATED) { /* Send msg with next smp msg content */ OTRTLV[] stlvs = new TLV[1]; stlvs[0] = new TLV(TLV.SMP4, nextmsg); DataMessage dm = Proto.createData(this, new byte[0], Proto.MSGFLAGS_IGNORE_UNREADABLE, stlvs); byte[] senddata = dm.getContent(); this.fragmentAndSend(new String(senddata), Policy.FRAGMENT_SEND_ALL, callback); int succorfail = this.smstate.smProgState == SM.PROG_SUCCEEDED ? OTRCallbacks.OTRL_SMPEVENT_SUCCESS : OTRCallbacks.OTRL_SMPEVENT_FAILURE; callback.handleSmpEvent(succorfail, this, 100, null); this.smstate.nextExpected = SM.EXPECT1; } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_CHEATED, this, 0, null); this.smstate.nextExpected = SM.EXPECT1; this.smstate.smProgState = SM.PROG_OK; } } else if (tlv != null) { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_ERROR, this, 0, null); } tlv = new TLV().find(tlvs, TLV.SMP4); if (tlv != null && nextMsg == SM.EXPECT4) { SM.step5(this.smstate, tlv.getValue(), prov); if (this.smstate.smProgState == SM.PROG_SUCCEEDED) { this.setSmpTrust(callback, true); } else { this.setSmpTrust(callback, false); } if (this.smstate.smProgState != SM.PROG_CHEATED) { int succorfail = this.smstate.smProgState == SM.PROG_SUCCEEDED ? OTRCallbacks.OTRL_SMPEVENT_SUCCESS : OTRCallbacks.OTRL_SMPEVENT_FAILURE; callback.handleSmpEvent(succorfail, this, 100, null); this.smstate.nextExpected = SM.EXPECT1; } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_CHEATED, this, 0, null); this.smstate.nextExpected = SM.EXPECT1; this.smstate.smProgState = SM.PROG_OK; } } else if (tlv != null) { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_ERROR, this, 0, null); } tlv = new TLV().find(tlvs, TLV.SMP_ABORT); if (tlv != null) { this.smstate.nextExpected = SM.EXPECT1; } } return stlv; } break; case OTRMessage.MSG_TAGGED_WHITESPACE: /* Start AKE */ if (msgState.getCurState() == MsgState.ST_UNENCRYPTED) { auth.startAKE(null); if (auth.havemsgp != 0) { this.lastMessage = new String(auth.lastauthmsg); fragmentAndSend(lastMessage, Policy.FRAGMENT_SEND_ALL, callback); StringTLV stlv = new StringTLV(); stlv.msg = ((TaggedPlaintextMessage) message).getStripped(); return stlv; } } else { StringTLV stlv = new StringTLV(); stlv.msg = ((TaggedPlaintextMessage) message).getStripped(); return stlv; } break; case OTRMessage.MSG_NOTOTR: if (this.msgState.getCurState() != MsgState.ST_UNENCRYPTED || (policy & Policy.REQUIRE_ENCRYPTION) != 0) { /* Not fine. Let the user know. */ callback.handleMsgEvent( OTRCallbacks.OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED, this, message.toString()); } ignore_message = 1; break; default: /* We received an OTR message we didn't recognize. Ignore * it, but make a log entry. */ callback.handleMsgEvent(OTRCallbacks.OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED, this, null); ignore_message = 1; break; } return null; }