// 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 } } } }
func main() { flag.Parse() log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) prv := otr.PrivateKey{} if *genKey { prv.Generate(rand.Reader) fmt.Println(base64.RawStdEncoding.EncodeToString(prv.Serialize(nil))) return } if *prvDataEnc == "" { log.Fatalln("You have to provide OTR key") } prvData, err := base64.RawStdEncoding.DecodeString(*prvDataEnc) if err != nil { log.Fatalln("Invalid private key's Base64 encoding:", err) } if _, ok := prv.Parse(prvData); !ok { log.Fatalln("Unable to parse private key") } log.Println("Our fingerprint:", hex.EncodeToString(prv.Fingerprint())) var wg sync.WaitGroup var ctin *os.File var ctout *os.File var ptin *os.File var ptout *os.File opener := func(fdd **os.File, path string, flag int) { fd, err := os.OpenFile(path, flag, os.FileMode(0600)) if err != nil { log.Fatalln("Unable to open", path, "file:", err) } *fdd = fd wg.Done() } wg.Add(1) go opener(&ctin, *ctInPath, os.O_RDONLY) wg.Add(1) go opener(&ptin, *ptInPath, os.O_RDONLY) wg.Add(1) go opener(&ctout, *ctOutPath, os.O_WRONLY) wg.Add(1) go opener(&ptout, *ptOutPath, os.O_WRONLY) wg.Wait() conv := otr.Conversation{PrivateKey: &prv, FragmentSize: MaxMsgSize} exited := make(chan struct{}) go func() { scanner := bufio.NewScanner(ptin) var err error var msg []byte var msgs [][]byte var t string for scanner.Scan() { t = scanner.Text() if strings.HasPrefix(t, OTRStart) { ctout.Write(append([]byte(otr.QueryMessage), '\n')) continue } else if t == OTRStop { msgs = conv.End() } else if strings.HasPrefix(t, OTRSMP) { ss := strings.SplitN(t, " ", 3) var question string if len(ss) == 1 { log.Println("Invalid SMP usage") continue } else if len(ss) == 3 { question = ss[2] } msgs, err = conv.Authenticate(question, []byte(ss[1])) if err != nil { log.Println("Error during authentication:", err) continue } } else { msgs, err = conv.Send(scanner.Bytes()) if err != nil { log.Println("Error during sending:", err) continue } } for _, msg = range msgs { ctout.Write(append(msg, '\n')) } } exited <- struct{}{} }() go func() { scanner := bufio.NewScanner(ctin) var got []byte var msg []byte var enc bool var change otr.SecurityChange var toSend [][]byte var err error for scanner.Scan() { got, enc, change, toSend, err = conv.Receive(scanner.Bytes()) if err != nil { log.Println("Error during receiving:", err) continue } for _, msg = range toSend { ctout.Write(append(msg, '\n')) } switch change { case otr.NewKeys: log.Println( "OTR established, remote fingerprint:", hex.EncodeToString(conv.TheirPublicKey.Fingerprint()), ) case otr.ConversationEnded: log.Println("OTR terminated") case otr.SMPSecretNeeded: log.Println("SMP requested:", conv.SMPQuestion()) case otr.SMPComplete: log.Println("SMP succeeded") case otr.SMPFailed: log.Println("SMP failed") } if len(got) > 0 { if !enc { got = append([]byte("Unencrypted:"), got...) } ptout.Write(append(got, '\n')) } } exited <- struct{}{} }() killed := make(chan os.Signal) signal.Notify(killed, os.Interrupt, os.Kill) select { case <-exited: case <-killed: } if conv.IsEncrypted() { for _, msg := range conv.End() { ctout.Write(append(msg, '\n')) } } ctout.Close() ctin.Close() ptout.Close() ptin.Close() }