package main import ( "bufio" "cwtch.im/cwtch/model" libpeer "cwtch.im/cwtch/peer" "fmt" "github.com/sethvargo/go-diceware/diceware" "math/big" "os" "time" "path" "os/user" "strings" "log" "io/ioutil" "sync" "cwtch.im/cwtch/peer/connections" "math/rand" "encoding/base32" ) func driftoff() { time.Sleep(time.Second * 7) os.Exit(1) } func addContactEntry(peer libpeer.CwtchPeer, name string, onion string) { decodedPub, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion[:56])) if err != nil { fmt.Printf("couldn't extract public key from %v.onion\n", onion) os.Exit(1) } pp := model.PublicProfile{ name, decodedPub[:32], false, false, onion} if peer == nil { fmt.Printf("peer is nil?!?!?\n") } peer.GetProfile().Contacts[onion] = &pp peer.GetProfile().SetCustomAttribute(onion+"_name", name) peer.GetProfile().SetCustomAttribute(name+"_onion", onion) peer.GetProfile().TrustPeer(onion) } func exitOnEnter() { fmt.Println("okay, now have your friend type \"sendafriend seek\" and enter the secret") fmt.Println("or press enter to cancel the game and exit") reader := bufio.NewReader(os.Stdin) reader.ReadString('\n') os.Exit(0) } func printHelp() { // "group" is a secret feature so please don't add it here yet fmt.Println("usage: sendafriend [command] [options]\nCommands: help, setname, info, list, add, send, receive, hide, seek") } func printGroupHelp() { fmt.Println("usage: sendafriend group {list, create, import, export, send, listen}") } func main() { if len(os.Args) < 2 { printHelp() os.Exit(1) } if os.Getenv("SENDAFRIEND_DEBUG") != "YES" { log.SetFlags(0) log.SetOutput(ioutil.Discard) } // this is a cwtch server used only to make the "pair" command work // it is completely untrusted and only blinded information is passed through it // it can point to any ol cwtch group, as long as both people running "pair" are using the same one hidenseekInviteStr := "torv30I0zJgNiUz57gJlxFsp4PUq47WDoKNCL95GEadaLlEE=EsABCiA1MjQzYjNkNzczMjAwNzc1MDExMDI0MjJkM2NkOGNmZBIgsXZKp350ShZ2l3iqOh4ZhGJmj0Etb5QmkL5iLZno9AQaOGZxcWJkNWxrNzRnbG5zZ292aXVuNm92Z3V0MmNqY252Mm12aWdvamNuc21wd3NucTJ5ZGpmM3FkIkD5x/JNhpLVe982dE9+3mp1IEog6O8yGdrIuUs0Tb6F1Fh1J1zuv1nJBFECuzYyF1yhqNvOn9Z6MatXVi+PKQIO" // the set of servers to randomly pick from when using "sendafriend group create" serverList := []string{"fqqbd5lk74glnsgoviun6ovgut2cjcnv2mvigojcnsmpwsnq2ydjf3qd"} var dirname, filename string if os.Getenv("SENDAFRIEND_FOLDER") != "" { dirname = os.Getenv("SENDAFRIEND_FOLDER") filename = path.Join(dirname, "identity.private") } else { usr, err := user.Current() if err != nil { fmt.Printf("\nerror: could not load current user: %v\n", err) os.Exit(1) } dirname = path.Join(usr.HomeDir, ".sendafriend") filename = path.Join(dirname, "identity.private") } os.MkdirAll(dirname, 0700) peer, err := libpeer.LoadCwtchPeer(filename, "be gay do crime") if err != nil { fmt.Println("couldn't load your config file, attempting to create a new one now") names, err := diceware.Generate(1) peer, err = libpeer.NewCwtchPeer(names[0], "be gay do crime", filename) if err != nil { fmt.Println("couldn't create one either :( exiting") os.Exit(1) } } switch os.Args[1] { default: printHelp() case "help": printHelp() case "iam": if len(os.Args) != 3 { fmt.Println("example: sendafriend iam alice") os.Exit(1) } peer.GetProfile().Name = os.Args[2] fmt.Printf("hi, %v!\n", os.Args[2]) case "setname": if len(os.Args) == 3 { fmt.Println("example: sendafriend setname alice xyz") os.Exit(1) } _, exists := peer.GetProfile().GetCustomAttribute(os.Args[3] + "_name") if !exists { fmt.Printf("um you don't have anyone with that onion address in your list") os.Exit(1) } peer.GetProfile().SetCustomAttribute(os.Args[3]+"_name", os.Args[2]) peer.GetProfile().SetCustomAttribute(os.Args[2]+"_onion", os.Args[3]) //todo: unset old one fmt.Printf("okay, i'll remember that %v is named %v\n", os.Args[3], os.Args[2]) case "info": fmt.Printf("your name is currently %v and your address is currently %v\n", peer.GetProfile().Name, peer.GetProfile().Onion) case "list": contacts := peer.GetContacts() for i := range contacts { attr, _ := peer.GetProfile().GetCustomAttribute(contacts[i] + "_name") fmt.Printf("%v <%v> %v\n", attr, contacts[i], peer.GetContact(contacts[i]).Trusted) } case "add": if len(os.Args) != 4 { fmt.Println("example: sendafriend add bob xyz") os.Exit(1) } _, exists := peer.GetProfile().GetCustomAttribute(os.Args[3] + "_name") if exists { fmt.Printf("woah you already have someone named %v\n", os.Args[3]) os.Exit(1) } addContactEntry(peer, os.Args[2], os.Args[3]) fmt.Printf("okay, added %v <%v>\n", os.Args[2], os.Args[3]) case "send": if len(os.Args) != 3 { fmt.Println("example: sendafriend send bob < file.txt") os.Exit(1) } onion, exists := peer.GetProfile().GetCustomAttribute(os.Args[2] + "_onion") if !exists { fmt.Printf("you don't seem to have a contact named %v\n", os.Args[2]) } processData := func(onion string, data []byte) []byte { return nil } peer.SetPeerDataHandler(processData) fmt.Printf("connecting to %v...\n", os.Args[2]) connection := peer.PeerWithOnion(onion) fmt.Printf("sending data...\n") reader := bufio.NewReader(os.Stdin) packet := make([]byte, 65531) br, _ := reader.Read(packet) if br > 65530 { fmt.Printf("sorry but i can't send more than 65530 bytes at once right now :( errorinn is working on it!\n") os.Exit(1) } connection.SendPacket(packet[:br]) case "receive": if len(os.Args) != 3 { fmt.Fprintf(os.Stderr, "example: sendafriend receive bob > file.txt\n") os.Exit(1) } verifyOnion, exists := peer.GetProfile().GetCustomAttribute(os.Args[2] + "_onion") if !exists { fmt.Fprintf(os.Stderr, "hmm you don't have a contact named %v\n", os.Args[2]) os.Exit(1) } processData := func(onion string, data []byte) []byte { if onion != verifyOnion { fmt.Fprintf(os.Stderr, "woah, got some unauthenticated data. discarding it!\n") os.Exit(1) } fmt.Printf("%s", data) os.Exit(0) return nil } peer.SetPeerDataHandler(processData) fmt.Fprintf(os.Stderr, "waiting for %v to send...\n", os.Args[2]) err = peer.Listen() if err != nil { fmt.Printf("error listening for connections: %v\n", err) } case "hide": if len(os.Args) != 3 { fmt.Fprintf(os.Stderr, "example: sendafriend hide bob\n") os.Exit(1) } suggestedSecret, _ := diceware.Generate(2) fmt.Printf("enter a secret that only the two of you would know [%v %v]: ", suggestedSecret[0], suggestedSecret[1]) in := bufio.NewReader(os.Stdin) secret, _ := in.ReadString('\n') secret = secret[:len(secret)-1] if secret == "" { secret = strings.Join(suggestedSecret, " ") } fmt.Println("please wait a moment while some magic happens...") ephemeralPeer, err := libpeer.NewCwtchPeer("Alice", "alicepass", "") if err != nil { fmt.Printf("couldn't create an ephemeral onion address: %v\n", err) os.Exit(1) } groupId, err := ephemeralPeer.ImportGroup(hidenseekInviteStr) if err != nil { fmt.Printf("couldn't configure the bbs settings: %v\n", err) os.Exit(1) } group := ephemeralPeer.GetGroup(groupId) ephemeralPeer.JoinServer(group.GroupServer) mp := ephemeralPeer.GetServers() for ; mp[group.GroupServer] != connections.AUTHENTICATED; mp = ephemeralPeer.GetServers() { time.Sleep(time.Millisecond * 500) } err = ephemeralPeer.SendMessageToGroup(groupId, "be gay do crimes") if err != nil { fmt.Printf("the hide-n-seek server is down or something? try again i guess; %v\n", err) os.Exit(1) } convos := make(map[string]*Conversation) processData := func(onion string, data []byte) []byte { if _, contains := convos[onion]; !contains { convos[onion] = new(Conversation) secretInt := new(big.Int) secretInt.SetBytes([]byte(secret)) convos[onion].smp.secret = secretInt } tlvPacket := arr2tlv(data) if tlvPacket.typ == 42 { fmt.Printf("you've been found! adding %v <%s> to contact list\n", os.Args[2], tlvPacket.data) addContactEntry(peer, os.Args[2], string(tlvPacket.data)) peer.Save() finalPacket := tlv{42, uint16(len(peer.GetProfile().Onion)), []byte(peer.GetProfile().Onion)} go driftoff() return tlv2arr(finalPacket) } out, _, _ := convos[onion].processSMP(tlvPacket) return tlv2arr(out) } ephemeralPeer.SetPeerDataHandler(processData) go ephemeralPeer.Listen() exitOnEnter() case "seek": if len(os.Args) != 3 { fmt.Fprintf(os.Stderr, "example: sendafriend seek alice\n") os.Exit(1) } fmt.Print("enter a secret that only the two of you would know: ") in := bufio.NewReader(os.Stdin) secret, _ := in.ReadString('\n') secret = secret[:len(secret)-1] fmt.Println("okay, now please wait a moment while some magic happens...") ephemeralPeer, err := libpeer.NewCwtchPeer("Alice", "alicepass", "") if err != nil { fmt.Printf("couldn't create an ephemeral onion address: %v\n", err) os.Exit(1) } groupId, err := ephemeralPeer.ImportGroup(hidenseekInviteStr) if err != nil { fmt.Printf("couldn't configure the bbs settings: %v\n", err) os.Exit(1) } group := ephemeralPeer.GetGroup(groupId) ephemeralPeer.JoinServer(group.GroupServer) //sadly we cannot listen for messages normally because we want to go in reverse order messages := group.GetTimeline() for ; len(messages) == 0; messages = group.GetTimeline() { time.Sleep(time.Second * 3) } var waitGroup sync.WaitGroup fmt.Printf("got advertisements. checking them...") for i := len(messages) - 1; i >= 0; i-- { if messages[i].Message == "be gay do crimes" { // this is not as scary as it seems because only one of these goroutines will actually use the peer waitGroup.Add(1) go doSMP(peer, messages[i].PeerID, secret, os.Args[2]) } } waitGroup.Wait() case "group": if len(os.Args) < 3 { printGroupHelp() os.Exit(1) } switch os.Args[2] { default: printGroupHelp() case "help": printGroupHelp() case "list": groups := peer.GetGroups() for i := range groups { g := peer.GetGroup(groups[i]) groupName, _ := peer.GetProfile().GetCustomAttribute(g.GroupID + "_groupname") fmt.Printf("%v <%v@%v> %v\n", groupName, g.GroupID, g.GroupServer, g.Accepted) } case "create": if len(os.Args) != 4 { fmt.Println("example: sendafriend group create [groupname]") os.Exit(1) } groupId, _, err := peer.StartGroup(serverList[rand.Intn(len(serverList))]) if err != nil { fmt.Printf("couldn't create a group: %v\n", err) os.Exit(1) } peer.GetProfile().SetCustomAttribute(os.Args[3] + "_groupid", groupId) peer.GetProfile().SetCustomAttribute(groupId + "_groupname", os.Args[3]) fmt.Printf("created group %v\n", os.Args[3]) case "import": if len(os.Args) != 5 { fmt.Println("example: sendafriend group import [groupname] [groupstr]") os.Exit(1) } groupId, err := peer.ImportGroup(os.Args[4]) if err != nil { fmt.Printf("couldn't import the group: %v\n", err) os.Exit(1) } peer.GetProfile().SetCustomAttribute(os.Args[3] + "_groupid", groupId) peer.GetProfile().SetCustomAttribute(groupId + "_groupname", os.Args[3]) case "export": if len(os.Args) != 4 { fmt.Println("example: sendafriend group export [groupname") os.Exit(1) } groupId, exists := peer.GetProfile().GetCustomAttribute(os.Args[3] + "_groupid") if !exists { fmt.Printf("you don't seem to have a group called %v\n", os.Args[3]) os.Exit(1) } exportStr, err := peer.ExportGroup(groupId) if err != nil { fmt.Printf("couldn't export group: %v\n", err) os.Exit(1) } fmt.Println(exportStr) case "send": if len(os.Args) != 4 { fmt.Println("example: echo \"go!\" | sendafriend group send [groupname]") os.Exit(1) } groupId, exists := peer.GetProfile().GetCustomAttribute(os.Args[3] + "_groupid") if !exists { fmt.Printf("you don't seem to have a group called %v\n", os.Args[3]) os.Exit(1) } g := peer.GetGroup(groupId) reader := bufio.NewReader(os.Stdin) message := make([]byte, 1025) br, err := reader.Read(message) if err != nil { fmt.Println("error reading input") os.Exit(1) } message = message[:br] if br > 1024 { fmt.Println("sorry, this feature uses cwtch groups which currently only support up to 1024 byte messages") os.Exit(1) } fmt.Println("sending...") peer.JoinServer(g.GroupServer) mp := peer.GetServers() for ; mp[g.GroupServer] != connections.AUTHENTICATED; mp = peer.GetServers() { time.Sleep(time.Millisecond * 500) } err = peer.SendMessageToGroup(groupId, string(message)) if err != nil { fmt.Printf("error sending message: %v\n", err) os.Exit(1) } case "listen": if len(os.Args) != 4 { fmt.Println("example: sendafriend group listen [groupname]") os.Exit(1) } groupId, exists := peer.GetProfile().GetCustomAttribute(os.Args[3] + "_groupid") if !exists { fmt.Printf("you don't seem to have a group called %v\n", os.Args[3]) os.Exit(1) } group := peer.GetGroup(groupId) if group == nil { fmt.Printf("storage out of sync. please report this bug as it shouldn't have happened\n") os.Exit(1) } group.NewMessage = make(chan model.Message) peer.JoinServer(group.GroupServer) for { m := <-group.NewMessage fmt.Printf("%v\n", m.Message) } case "invite": if len(os.Args) != 5 { fmt.Println("example: sendafriend group invite [friendname] [groupname]") os.Exit(1) } onion, exists := peer.GetProfile().GetCustomAttribute(os.Args[3] + "_onion") if !exists { fmt.Fprintf(os.Stderr, "hmm you don't have a contact named %v\n", os.Args[3]) os.Exit(1) } groupId, exists := peer.GetProfile().GetCustomAttribute(os.Args[4] + "_groupid") if !exists { fmt.Printf("you don't seem to have a group called %v\n", os.Args[4]) os.Exit(1) } fmt.Printf("attempting to connect to %v...\n", os.Args[3]) peer.PeerWithOnion(onion) mp := peer.GetPeers() for ; mp[onion] != connections.AUTHENTICATED; mp = peer.GetPeers() { time.Sleep(time.Millisecond * 500) } err = peer.InviteOnionToGroup(onion, groupId) if err != nil { fmt.Printf("Trusted: %v\n", peer.GetContact(onion).Trusted) fmt.Printf("failed to invite %v to %v: %v\n", os.Args[3], os.Args[4], err) os.Exit(1) } case "accept": if len(os.Args) != 5 { fmt.Println("example: sendafriend group accept [friendname] [groupname]") os.Exit(1) } onion, exists := peer.GetProfile().GetCustomAttribute(os.Args[3] + "_onion") if !exists { fmt.Fprintf(os.Stderr, "hmm you don't have a contact named %v\n", os.Args[3]) os.Exit(1) } _, exists = peer.GetProfile().GetCustomAttribute(os.Args[4] + "_groupid") if exists { fmt.Printf("oops, you already have a group called %v\n", os.Args[4]) os.Exit(1) } s := len(peer.GetGroups()) go peer.Listen() fmt.Println("okay, have a friend run \"sendafriend group invite [yourname] [groupname]\"") for { n := len(peer.GetGroups()) if n == s { time.Sleep(time.Millisecond * 500) continue } groups := peer.GetGroups() for i := range groups { if peer.GetGroup(groups[i]).Owner == onion && !peer.GetGroup(groups[i]).Accepted { fmt.Printf("adding group %v!\n", os.Args[4]) peer.AcceptInvite(groups[i]) peer.GetProfile().SetCustomAttribute(os.Args[4] + "_groupid", groups[i]) peer.GetProfile().SetCustomAttribute(groups[i] + "_groupname", os.Args[4]) peer.Save() os.Exit(0) } } } } } peer.Save() }