// The main loop. // * The main job is to pass messages between standard input/output, the OTR // library, the TCP socket. // * It starts goroutines that listen on standard input and the TCP socket. // Note: it only starts listening on standard input when an encrypted // connection has been established, to prevent any data being sent in plain // text. // * When an encrypted session has been established, it checks if the contact // is authentication and authorised (according to -remember and -expect). func mainLoop(privateKey otr.PrivateKey, upstream io.ReadWriter) { var conv otr.Conversation var theirFingerprint string = "" conv.PrivateKey = &privateKey netOutChan := make(chan []byte, 100) netInChan := make(chan []byte, 100) stdOutChan := make(chan []byte, 100) stdInChan := make(chan []byte, 100) sigTermChan := make(chan os.Signal) // Delimit ciphertext messages with newlines var nl = []byte("\n") msgSender, msgReceiver := NewDelimitedSender(upstream, nl), NewDelimitedReceiver(upstream, nl) go SendForever(msgSender, netOutChan) go ReceiveForever(msgReceiver, netInChan) // Don't touch secret input or output anything until we are sure everything // is encrypted and authorised. // go bufferedReadLoop(os.Stdin, stdInChan) // go writeLoop(os.Stdout, stdOutChan) go sigLoop(sigTermChan) send := func(toSend [][]byte) { for _, msg := range toSend { netOutChan <- msg } } stdInChan <- []byte(otr.QueryMessage) // Queue a handshake message to be sent authorised := false // conversation ready to send secret data? Loop: for { select { case <-sigTermChan: break Loop case plaintext, alive := <-stdInChan: // fmt.Fprintf(os.Stderr, "Read %d bytes of plaintext.\n", len(plaintext)) if !alive { break Loop } if bytes.Index(plaintext, []byte{0}) != -1 { fmt.Fprintf(os.Stderr, "The OTR protocol only supports UTF8-encoded text.\n"+ "Please use base64 or another suitable encoding for binary data.\n") break Loop } toSend, err := conv.Send(plaintext) if err != nil { exitError(err) } send(toSend) case otrText, alive := <-netInChan: if !alive { if authorised { exitPrintf("Connection dropped! Recent messages might not be deniable.\n") } exitPrintf("Connection dropped!\n") } plaintext, encrypted, state, toSend, err := conv.Receive(otrText) if err != nil { exitError(err) } if state == otr.ConversationEnded { return } send(toSend) if conv.IsEncrypted() { fingerprint := string(conv.TheirPublicKey.Fingerprint()) if authorised && theirFingerprint != fingerprint { exitPrintf("The contact changed mid-conversation.\n") } if !authorised { theirFingerprint = fingerprint authoriseRemember(fingerprint) authorised = true var w io.Writer var r io.Reader r, w = os.Stdin, os.Stdout if execCommand != "" { r, w = StartCommand(fingerprint) } go bufferedReadLoop(r, stdInChan) go writeLoop(w, stdOutChan) } } if len(plaintext) > 0 { if !encrypted || !authorised { exitPrintf("Received unencrypted or unauthenticated text.\n") } // fmt.Fprintf(os.Stderr, "Received %d bytes of plaintext.\n", len(plaintext)) stdOutChan <- plaintext } } } // We want to terminate the conversation. To do this, we send the // termination messages, and wait for the other side to close the // connection. It's important that these messages get through, for // deniability. toSend := conv.End() send(toSend) netOutChan <- nil ShutdownLoop: for { select { case _, alive := <-netInChan: if !alive { break ShutdownLoop } } } }