forked from andrewclausen/otrcat
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mainloop.go
284 lines (255 loc) · 7.08 KB
/
mainloop.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
// Copyright (C) 2014 Andrew Clausen
// This program may be distributed under the BSD-style licence that Go is
// released under; see https://golang.org/LICENSE.
//
// This file contains mainLoop(), which forwards, encrypts and decrypts
// messages. All authentication and authorisation logic is in here.
package main
import (
"bytes"
"golang.org/x/crypto/otr"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"time"
)
// Checks if the contact is authorised, and remembers the contact if
// appropriate. This is the only place these tasks are done, with the
// following exceptions:
// * "me" is included in the contact list
// * the main loop checks if the contact changed mid-conversation (which we
// forbid)
func authoriseRemember(fingerprint string) {
name, known := contactsReverse[fingerprint]
if expect != "" {
if !known {
exitPrintf("Expected contact '%s', but the contact is unknown.\n",
expect)
}
if name != expect {
exitPrintf("Expected contact '%s', but the contact is '%s'.\n",
expect, name)
}
return // authorised
}
if !anyone && !known {
exitPrintf("The contact is unknown. " +
"Use -anyone or -remember to talk to unknown contacts.\n")
}
if remember != "" && known {
fmt.Fprintf(os.Stderr,
"Warning: Expected an unknown contact, but the contact is known "+
"as '%s'.\n",
name)
}
if remember != "" && !known {
fmt.Fprintf(os.Stderr, "Remembering contact '%s'.\n", remember)
contacts[remember] = fingerprint
contactsReverse[fingerprint] = remember
saveContacts(contactsPath)
}
if remember == "" && known {
fmt.Fprintf(os.Stderr, "The contact is '%s'.\n", name)
}
}
// Implements the -exec option, which runs a given command using /bin/sh, and
// connects the processes stdin/stdout to this side of the conversation
func StartCommand(theirFingerprint string) (io.Reader, io.Writer) {
cmd := exec.Command("/bin/sh", "-c", execCommand, "--",
contactsReverse[theirFingerprint])
stdIn, err := cmd.StdinPipe()
if err != nil {
exitError(err)
}
stdOut, err := cmd.StdoutPipe()
if err != nil {
exitError(err)
}
if err := cmd.Start(); err != nil {
exitError(err)
}
return stdOut, stdIn
}
// Turns a Reader into a channel of buffers.
func readLoop(r io.Reader, ch chan []byte) {
for {
buf := make([]byte, 4096) // TODO: what's a good buffer size?
n, err := r.Read(buf)
if err == io.EOF {
close(ch)
return
}
if err != nil {
exitError(err)
}
ch <- buf[:n]
}
}
// This read loop reads as much as it can every 0.1s (TODO: make this tunable).
// This is a good compromise for latency, defeating timing-based traffic
// analysis, and amortising overheads with sending messages.
func bufferedReadLoop(r io.Reader, ch chan []byte) {
upstream := make(chan []byte, 100)
go readLoop(r, upstream)
ticker := time.Tick(time.Second / 10)
queue := []byte(nil)
Loop:
for {
select {
case buf, open := <-upstream:
if !open {
break Loop
}
queue = append(queue, buf...)
case <-ticker:
// TODO: send cover traffic when there is nothing to send?
if queue != nil {
ch <- queue
queue = nil
}
}
}
ch <- queue
close(ch)
}
func writeLoop(w io.Writer, ch chan []byte) {
for {
buf, open := <-ch
if !open {
return
}
_, err := w.Write(buf)
if err != nil {
exitError(err)
}
}
}
// Listen for SIGTERM signals
func sigLoop(ch chan os.Signal) {
listener := make(chan os.Signal)
signal.Notify(listener, os.Interrupt)
for {
select {
case sig := <-listener:
ch <- sig
}
}
}
// 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
}
}
}
}