320 lines
9.4 KiB
Go
320 lines
9.4 KiB
Go
package goricochet
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/s-rah/go-ricochet/auth"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
)
|
|
|
|
// Ricochet is a protocol to conducting anonymous IM.
|
|
type Ricochet struct {
|
|
conn net.Conn
|
|
logger *log.Logger
|
|
}
|
|
|
|
// RicochetData is a structure containing the raw data and the channel it the
|
|
// message originated on.
|
|
type RicochetData struct {
|
|
Channel int32
|
|
Data []byte
|
|
}
|
|
|
|
// Init sets up the Ricochet object.
|
|
func (r *Ricochet) Init(debugLog bool) {
|
|
|
|
if debugLog {
|
|
r.logger = log.New(os.Stdout, "[Ricochet]: ", log.Ltime|log.Lmicroseconds)
|
|
} else {
|
|
r.logger = log.New(ioutil.Discard, "[Ricochet]: ", log.Ltime|log.Lmicroseconds)
|
|
}
|
|
}
|
|
|
|
// Connect sets up a ricochet connection between from and to which are
|
|
// both ricochet formated hostnames e.g. qn6uo4cmsrfv4kzq.onion. If this
|
|
// function finished successfully then the connection can be assumed to
|
|
// be open and authenticated.
|
|
// To specify a local port using the format "127.0.0.1:[port]|ricochet-id".
|
|
func (r *Ricochet) Connect(from string, to string) error {
|
|
var err error
|
|
networkResolver := new(NetworkResolver)
|
|
r.conn, to, err = networkResolver.Resolve(to)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return r.negotiateVersion()
|
|
}
|
|
|
|
// Authenticate opens an Authentication Channel and send a client cookie
|
|
func (r *Ricochet) Authenticate(channelID int32, clientCookie [16]byte) error {
|
|
messageBuilder := new(MessageBuilder)
|
|
data, err := messageBuilder.OpenAuthenticationChannel(channelID, clientCookie)
|
|
|
|
if err != nil {
|
|
return errors.New("Cannot Marshal Open Channel Message")
|
|
}
|
|
r.logger.Printf("Sending Open Channel with Auth Request (channel:%d)", channelID)
|
|
r.sendPacket(data, 0)
|
|
return nil
|
|
}
|
|
|
|
// SendProof sends an authentication proof in response to a challenge.
|
|
func (r *Ricochet) SendProof(channelID int32, publickeyBytes []byte, signatureBytes []byte) error {
|
|
// Construct a Proof Message
|
|
proof := &Protocol_Data_AuthHiddenService.Proof{
|
|
PublicKey: publickeyBytes,
|
|
Signature: signatureBytes,
|
|
}
|
|
|
|
ahsPacket := &Protocol_Data_AuthHiddenService.Packet{
|
|
Proof: proof,
|
|
Result: nil,
|
|
}
|
|
|
|
data, err := proto.Marshal(ahsPacket)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r.logger.Printf("Sending Proof Auth Request (channel:%d)", channelID)
|
|
r.sendPacket(data, channelID)
|
|
return nil
|
|
}
|
|
|
|
// OpenChannel opens a new chat channel with the given id
|
|
// Prerequisites:
|
|
// * Must have Previously issued a successful Connect()
|
|
// * If acting as the client, id must be odd, else even
|
|
func (r *Ricochet) OpenChatChannel(id int32) error {
|
|
messageBuilder := new(MessageBuilder)
|
|
data, err := messageBuilder.OpenChatChannel(id)
|
|
|
|
if err != nil {
|
|
return errors.New("error constructing control channel message to open channel")
|
|
}
|
|
|
|
r.logger.Printf("Opening Chat Channel: %d", id)
|
|
r.sendPacket(data, 0)
|
|
return nil
|
|
}
|
|
|
|
// SendContactRequest initiates a contact request to the server.
|
|
// Prerequisites:
|
|
// * Must have Previously issued a successful Connect()
|
|
func (r *Ricochet) SendContactRequest(channel int32, nick string, message string) error {
|
|
messageBuilder := new(MessageBuilder)
|
|
data, err := messageBuilder.OpenContactRequestChannel(channel, nick, message)
|
|
|
|
if err != nil {
|
|
return errors.New("error constructing control channel message to send contact request")
|
|
}
|
|
|
|
r.sendPacket(data, 0)
|
|
return nil
|
|
}
|
|
|
|
// AckOpenChannel acknowledges a previously received open channel message
|
|
// Prerequisites:
|
|
// * Must have Previously issued a successful Connect()
|
|
func (r *Ricochet) AckOpenChannel(channel int32, result bool) error {
|
|
messageBuilder := new(MessageBuilder)
|
|
data, err := messageBuilder.AckOpenChannel(channel, result)
|
|
if err != nil {
|
|
return errors.New("Failed to serialize open channel ack")
|
|
}
|
|
r.sendPacket(data, 0)
|
|
return nil
|
|
}
|
|
|
|
// AckChatMessage acknowledges a previously received chat message.
|
|
// Prerequisites:
|
|
// * Must have Previously issued a successful Connect()
|
|
func (r *Ricochet) AckChatMessage(channel int32, messageID int32) error {
|
|
messageBuilder := new(MessageBuilder)
|
|
data, err := messageBuilder.AckChatMessage(messageID)
|
|
if err != nil {
|
|
return errors.New("Failed to serialize chat message ack")
|
|
}
|
|
r.sendPacket(data, channel)
|
|
return nil
|
|
}
|
|
|
|
// SendMessage sends a Chat Message (message) to a give Channel (channel).
|
|
// Prerequisites:
|
|
// * Must have previously issued a successful Connect()
|
|
// * Must have previously opened channel with OpenChanel
|
|
func (r *Ricochet) SendMessage(channel int32, message string) error {
|
|
messageBuilder := new(MessageBuilder)
|
|
data, err := messageBuilder.ChatMessage(message)
|
|
|
|
if err != nil {
|
|
return errors.New("error constructing control channel message to send chat message")
|
|
}
|
|
|
|
r.logger.Printf("Sending Message on Channel: %d", channel)
|
|
r.sendPacket(data, channel)
|
|
return nil
|
|
}
|
|
|
|
// negotiateVersion Perform version negotiation with the connected host.
|
|
func (r *Ricochet) negotiateVersion() error {
|
|
version := make([]byte, 4)
|
|
version[0] = 0x49
|
|
version[1] = 0x4D
|
|
version[2] = 0x01
|
|
version[3] = 0x01
|
|
|
|
fmt.Fprintf(r.conn, "%s", version)
|
|
r.logger.Print("Negotiating Version ", version)
|
|
res, err := r.recv()
|
|
|
|
if len(res) != 1 || err != nil {
|
|
return errors.New("Failed Version Negotiating")
|
|
}
|
|
|
|
if res[0] != 1 {
|
|
return errors.New("Failed Version Negotiating - Invalid Version ")
|
|
}
|
|
|
|
r.logger.Print("Successfully Negotiated Version ", res[0])
|
|
return nil
|
|
}
|
|
|
|
// sendPacket places the data into a structure needed for the client to
|
|
// decode the packet and writes the packet to the network.
|
|
func (r *Ricochet) sendPacket(data []byte, channel int32) {
|
|
header := make([]byte, 4+len(data))
|
|
header[0] = byte(len(header) >> 8)
|
|
header[1] = byte(len(header) & 0x00FF)
|
|
header[2] = 0x00
|
|
header[3] = byte(channel)
|
|
copy(header[4:], data[:])
|
|
fmt.Fprintf(r.conn, "%s", header)
|
|
}
|
|
|
|
// ListenAndWait is intended to be a background thread listening for all messages
|
|
// a client will send, automaticall responding to some, and making the others available to
|
|
// Listen()
|
|
// Prerequisites:
|
|
// * Must have previously issued a successful Connect()
|
|
func (r *Ricochet) ListenAndWait(serverHostname string, service RicochetService) error {
|
|
for true {
|
|
packets, err := r.getMessages()
|
|
r.handleFatal(err, "Error attempted to get new messages")
|
|
|
|
messageDecoder := new(MessageDecoder)
|
|
|
|
for _, packet := range packets {
|
|
|
|
if len(packet.Data) == 0 {
|
|
r.logger.Printf("Closing Channel %d", packet.Channel)
|
|
service.OnChannelClose(packet.Channel, serverHostname)
|
|
break
|
|
}
|
|
|
|
if packet.Channel == 0 {
|
|
|
|
message, err := messageDecoder.DecodeControlMessage(packet.Data)
|
|
|
|
if err != nil {
|
|
r.logger.Printf("Failed to decode data packet, discarding")
|
|
break
|
|
}
|
|
|
|
if message.Type == "openchannel" && message.Ack == false {
|
|
r.logger.Printf("new open channel request %d %s", message.ChannelID, serverHostname)
|
|
service.OnOpenChannelRequest(message.ChannelID, serverHostname)
|
|
} else if message.Type == "openchannel" && message.Ack == true {
|
|
r.logger.Printf("new open channel request ack %d %s", message.ChannelID, serverHostname)
|
|
service.OnOpenChannelRequestAck(message.ChannelID, serverHostname, message.Accepted)
|
|
} else if message.Type == "openauthchannel" && message.Ack == true {
|
|
r.logger.Printf("new authentication challenge %d %s", message.ChannelID, serverHostname)
|
|
service.OnAuthenticationChallenge(message.ChannelID, serverHostname, message.ServerCookie)
|
|
} else {
|
|
r.logger.Printf("Received Unknown Control Message\n", message)
|
|
}
|
|
} else if packet.Channel == 1 {
|
|
result, _ := messageDecoder.DecodeAuthMessage(packet.Data)
|
|
r.logger.Printf("newreceived auth result %d", packet.Channel)
|
|
service.OnAuthenticationResult(1, serverHostname, result)
|
|
} else {
|
|
|
|
// At this point the only other expected type of message is a Chat Message
|
|
messageDecoder := new(MessageDecoder)
|
|
message, err := messageDecoder.DecodeChatMessage(packet.Data)
|
|
if err != nil {
|
|
r.logger.Printf("Failed to decode data packet, discarding on channel %d", packet.Channel)
|
|
break
|
|
}
|
|
|
|
if message.Ack == true {
|
|
service.OnChatMessageAck(packet.Channel, serverHostname, message.MessageID)
|
|
} else {
|
|
service.OnChatMessage(packet.Channel, serverHostname, message.MessageID, message.Message)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getMessages returns an array of new messages received from the ricochet client
|
|
func (r *Ricochet) getMessages() ([]RicochetData, error) {
|
|
buf, err := r.recv()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to retrieve new messages from the client")
|
|
}
|
|
|
|
pos := 0
|
|
finished := false
|
|
datas := []RicochetData{}
|
|
|
|
for !finished {
|
|
size := int(binary.BigEndian.Uint16(buf[pos+0 : pos+2]))
|
|
channel := int(binary.BigEndian.Uint16(buf[pos+2 : pos+4]))
|
|
|
|
if pos+size > len(buf) {
|
|
return datas, errors.New("Partial data packet received")
|
|
}
|
|
|
|
data := RicochetData{
|
|
Channel: int32(channel),
|
|
Data: buf[pos+4 : pos+size],
|
|
}
|
|
|
|
datas = append(datas, data)
|
|
pos += size
|
|
if pos >= len(buf) {
|
|
finished = true
|
|
}
|
|
}
|
|
r.logger.Printf("Got %d Packets", len(datas))
|
|
return datas, nil
|
|
}
|
|
|
|
// recv reads data from the client, and returns the raw byte array, else error.
|
|
func (r *Ricochet) recv() ([]byte, error) {
|
|
buf := make([]byte, 4096)
|
|
n, err := r.conn.Read(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret := make([]byte, n)
|
|
copy(ret[:], buf[:])
|
|
return ret, nil
|
|
}
|
|
|
|
func (r *Ricochet) handleFatal(err error, message string) {
|
|
if err != nil {
|
|
r.logger.Fatal(message)
|
|
}
|
|
}
|