コード例 #1
0
ファイル: main.go プロジェクト: andres-erbsen/chatterbox
func main() {
	if len(os.Args) == 1 {
		fmt.Fprintf(os.Stderr, `%s: missing operand\n
Usage: %s [-rf] FILE...
		`, os.Args[0], os.Args[0])
		os.Exit(1)
	}
	exitCode := 0
	if os.Args[1] == "-rf" {
		for _, path := range os.Args[2:] {
			if err := shred.RemoveAll(path); err != nil {
				fmt.Fprintf(os.Stderr, "%s: %s\n", path, err)
				exitCode = 1
			}
		}
	} else {
		for i, path := range os.Args[1:] {
			if i == 1 && path == "--" {
				continue
			}
			if err := shred.Remove(path); err != nil {
				fmt.Fprintf(os.Stderr, "%s: %s\n", path, err)
				exitCode = 1
			}
		}
	}
	os.Exit(exitCode)
}
コード例 #2
0
func (p *Paths) ConversationToOutbox(metadata *proto.ConversationMetadata, msgs ...string) error {
	path := filepath.Join(p.OutboxDir(), ConversationName(metadata))
	tmpDir, err := p.MkdirInTemp()
	if err != nil {
		return err
	}
	defer shred.RemoveAll(tmpDir)
	if err := p.MarshalToFile(filepath.Join(tmpDir, MetadataFileName), metadata); err != nil {
		return err
	}
	return os.Rename(filepath.Join(tmpDir), path)
}
コード例 #3
0
ファイル: daemon.go プロジェクト: andres-erbsen/chatterbox
func (p *Daemon) conversationToConversations(metadata *proto.ConversationMetadata) error {
	path := filepath.Join(p.ConversationDir(), persistence.ConversationName(metadata))
	tmpDir, err := p.MkdirInTemp()
	if err != nil {
		return err
	}
	defer shred.RemoveAll(tmpDir)
	if err := p.MarshalToFile(filepath.Join(tmpDir, persistence.MetadataFileName), metadata); err != nil {
		return err
	}
	return os.Rename(filepath.Join(tmpDir), path)
}
コード例 #4
0
ファイル: daemon.go プロジェクト: andres-erbsen/chatterbox
func (d *Daemon) saveMessage(message *proto.Message) error {
	// generate metadata file
	metadata := proto.ConversationMetadata{
		Participants: message.Participants,
		Subject:      message.Subject,
	}
	// generate conversation name
	convName := persistence.ConversationName(&metadata)
	messageName := persistence.MessageName(time.Unix(0, message.Date), string(message.Dename))

	// create conversation directory if it doesn't already exist
	convDir := filepath.Join(d.ConversationDir(), convName)
	outboxDir := filepath.Join(d.OutboxDir(), convName)
	_, err := os.Stat(convDir)
	if err != nil && !os.IsNotExist(err) {
		return err
	} else if err != nil && os.IsNotExist(err) {
		// new message in existing conversation
		if err := d.conversationToConversations(&metadata); err != nil {
			return err
		}
	}

	err = d.AtomicWriteFile(filepath.Join(convDir, messageName), message.Contents, 0600)
	if err != nil {
		return err
	}

	// to outbox
	tdir, err := d.MkdirInTemp()
	if err != nil {
		return err
	}
	defer shred.RemoveAll(tdir)
	err = d.MarshalToFile(filepath.Join(tdir, persistence.MetadataFileName), &metadata)
	if err != nil {
		return err
	}
	if err = os.Rename(filepath.Join(tdir), outboxDir); err != nil && !os.IsExist(err) && !strings.Contains(err.Error(), "directory not empty") {
		return err
	}
	return nil
}
コード例 #5
0
func InitFs(d *Daemon) error {
	// create root directory and immediate sub directories
	os.MkdirAll(d.RootDir, 0700)
	subdirs := []string{
		d.ConversationDir(),
		d.OutboxDir(),
		d.TempDir(),
		d.privDir(),
		d.profilesDir(),
		d.ratchetKeysDir(),
	}
	for _, dir := range subdirs {
		os.MkdirAll(dir, 0700) // FIXME: handle error
	}

	// for each existing conversation, create a folder in the outbox
	copyToOutbox := func(cPath string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if cPath != d.ConversationDir() {
			if f.IsDir() {
				// create the outbox directory in tmp, then (atomically) move it to outbox
				tmpDir, err := d.MkdirInTemp()
				if err != nil {
					return err
				}
				defer shred.RemoveAll(tmpDir)
				conversationInfo, err := os.Stat(cPath)
				if err != nil {
					return err
				}
				var c_perm = conversationInfo.Mode()
				metadataFile := filepath.Join(cPath, persistence.MetadataFileName)
				metadataInfo, err := os.Stat(metadataFile)
				if err != nil {
					return err
				}
				var m_perm = metadataInfo.Mode()
				oldUmask := syscall.Umask(0000)
				defer syscall.Umask(oldUmask)
				os.Mkdir(filepath.Join(tmpDir, path.Base(cPath)), c_perm)
				err = Copy(metadataFile, filepath.Join(tmpDir, path.Base(cPath), persistence.MetadataFileName), m_perm)
				if err != nil {
					return err
				}
				err = os.Rename(filepath.Join(tmpDir, path.Base(cPath)), filepath.Join(d.OutboxDir(), path.Base(cPath)))
				if err != nil {
					// skip this conversation; this probably means it already exists in the outbox
					return nil
				}
			}
		}
		return nil
	}
	err := filepath.Walk(d.ConversationDir(), copyToOutbox)
	if err != nil {
		return err
	}
	return nil
}
コード例 #6
0
func TestEncryptFirstMessage(t *testing.T) {
	alice := "alice"
	bob := "bob"

	denameConfig, denameTeardown := denameTestutil.SingleServer(t)
	defer denameTeardown()

	aliceDnmc, err := denameClient.NewClient(denameConfig, nil, nil)
	bobDnmc, err := denameClient.NewClient(denameConfig, nil, nil)

	if err != nil {
		t.Fatal(err)
	}

	_, serverPubkey, serverAddr, serverTeardown := server.CreateTestServer(t)
	defer serverTeardown()

	aliceDir, err := ioutil.TempDir("", "daemon-alice")
	if err != nil {
		t.Fatal(err)
	}
	defer shred.RemoveAll(aliceDir)
	bobDir, err := ioutil.TempDir("", "daemon-bob")
	if err != nil {
		t.Fatal(err)
	}
	defer shred.RemoveAll(bobDir)

	aliceConf := &Daemon{
		Paths: persistence.Paths{
			RootDir:     aliceDir,
			Application: "daemon",
		},
		Now:                  time.Now,
		foreignDenameClient:  aliceDnmc,
		timelessDenameClient: aliceDnmc,
		inBuf:                make([]byte, proto.SERVER_MESSAGE_SIZE),
		outBuf:               make([]byte, proto.SERVER_MESSAGE_SIZE),
		LocalAccountConfig:   proto.LocalAccountConfig{},
		LocalAccount: proto.LocalAccount{
			Dename: alice,
		},
		cc: util.NewConnectionCache(util.NewAnonDialer("DANGEROUS_NO_TOR")),
	}

	bobConf := &Daemon{
		Paths: persistence.Paths{
			RootDir:     bobDir,
			Application: "daemon",
		},
		Now:                  time.Now,
		foreignDenameClient:  bobDnmc,
		timelessDenameClient: bobDnmc,
		inBuf:                make([]byte, proto.SERVER_MESSAGE_SIZE),
		outBuf:               make([]byte, proto.SERVER_MESSAGE_SIZE),
		LocalAccountConfig:   proto.LocalAccountConfig{},
		LocalAccount: proto.LocalAccount{
			Dename: bob,
		},
		cc: util.NewConnectionCache(util.NewAnonDialer("DANGEROUS_NO_TOR")),
	}

	aliceHomeConn := util.CreateTestAccount(alice, aliceDnmc, &aliceConf.LocalAccountConfig, serverAddr, serverPubkey, t)
	defer aliceHomeConn.Close()
	bobHomeConn := util.CreateTestAccount(bob, bobDnmc, &bobConf.LocalAccountConfig, serverAddr, serverPubkey, t)
	defer bobHomeConn.Close()

	//fmt.Printf("CBob: %v\n", ([32]byte)(bobConf.TransportSecretKeyForServer))
	aliceNotifies := make(chan *util.EnvelopeWithId)
	aliceReplies := make(chan *proto.ServerToClient)

	aliceConnToServer := &util.ConnectionToServer{
		InBuf:        aliceConf.inBuf,
		Conn:         aliceHomeConn,
		ReadReply:    aliceReplies,
		ReadEnvelope: aliceNotifies,
	}

	go aliceConnToServer.ReceiveMessages()

	bobNotifies := make(chan *util.EnvelopeWithId)
	bobReplies := make(chan *proto.ServerToClient)

	bobConnToServer := &util.ConnectionToServer{
		InBuf:        bobConf.inBuf,
		Conn:         bobHomeConn,
		ReadReply:    bobReplies,
		ReadEnvelope: bobNotifies,
	}

	go bobConnToServer.ReceiveMessages()

	if err := InitFs(aliceConf); err != nil {
		t.Fatal(err)
	}

	if err := InitFs(bobConf); err != nil {
		t.Fatal(err)
	}
	//Bob uploads keys
	bobPublicPrekeys, bobSecretPrekeys, err := GeneratePrekeys(maxPrekeys)
	var bobSigningKey [64]byte
	copy(bobSigningKey[:], bobConf.KeySigningSecretKey[:64])
	err = util.UploadKeys(bobConnToServer, util.SignKeys(bobPublicPrekeys, &bobSigningKey))
	if err != nil {
		t.Fatal(err)
	}

	//Bob enables notifications
	if err = util.EnablePush(bobConnToServer); err != nil {
		t.Fatal(err)
	}

	//Alice uploads keys
	alicePublicPrekeys, _, err := GeneratePrekeys(maxPrekeys)
	var aliceSigningKey [64]byte
	copy(aliceSigningKey[:], aliceConf.KeySigningSecretKey[:64])
	err = util.UploadKeys(aliceConnToServer, util.SignKeys(alicePublicPrekeys, &aliceSigningKey))
	if err != nil {
		t.Fatal(err)
	}

	//Alice enables notification
	if err = util.EnablePush(aliceConnToServer); err != nil {
		t.Fatal(err)
	}

	participants := make([]string, 0, 2)
	participants = append(participants, alice)
	participants = append(participants, bob)

	msg1 := []byte("Envelope")

	payload := proto.Message{
		Subject:      "Subject1",
		Participants: participants,
		Dename:       alice,
		Contents:     msg1,
	}
	envelope, err := payload.Marshal()
	if err != nil {
		t.Fatal(err)
	}

	err = aliceConf.sendFirstMessage(envelope, bob)
	if err != nil {
		t.Fatal(err)
	}
	incoming := <-bobConnToServer.ReadEnvelope

	out, bobRatch, _, err := bobConf.decryptFirstMessage(incoming.Envelope, bobPublicPrekeys, bobSecretPrekeys)
	if err != nil {
		t.Fatal(err)
	}

	fmt.Printf("Bob hears: %s\n", out)

	msg2 := []byte("Envelope2")
	payload2 := proto.Message{
		Subject:      "Subject3",
		Participants: participants,
		Dename:       bob,
		Contents:     msg2,
	}
	envelope2, err := payload2.Marshal()
	if err != nil {
		t.Fatal(err)
	}

	err = bobConf.sendMessage(envelope2, alice, bobRatch)
	if err != nil {
		t.Fatal(err)
	}

	incomingAlice := <-aliceConnToServer.ReadEnvelope

	aliceConf.fillAuth = util.FillAuthWith((*[32]byte)(&aliceConf.MessageAuthSecretKey))
	aliceConf.checkAuth = util.CheckAuthWith(aliceConf.ProfileRatchet)
	aliceRatchets, err := AllRatchets(aliceConf, aliceConf.fillAuth, aliceConf.checkAuth)
	outAlice, _, err := decryptMessage(incomingAlice.Envelope, aliceRatchets)
	if err != nil {
		t.Fatal(err)
	}

	ha := sha256.Sum256(incomingAlice.Envelope)
	if err := aliceConf.saveMessage(outAlice); err != nil {
		t.Fatal(err)
	}
	if err := util.DeleteMessages(aliceConnToServer, []*[32]byte{&ha}); err != nil {
		t.Fatal(err)
	}
	fmt.Printf("Alice hears: %s\n", outAlice)

	//TODO: Confirm message is as expected within the test
}
コード例 #7
0
func TestReadFromFiles(t *testing.T) {
	alice := "alice"
	bob := "bob"

	denameConfig, denameTeardown := denameTestutil.SingleServer(t)
	defer denameTeardown()

	_, serverPubkey, serverAddr, serverTeardown := server.CreateTestServer(t)
	defer serverTeardown()

	aliceDir, err := ioutil.TempDir("", "daemon-alice")
	if err != nil {
		t.Fatal(err)
	}
	defer shred.RemoveAll(aliceDir)

	bobDir, err := ioutil.TempDir("", "daemon-bob")
	if err != nil {
		t.Fatal(err)
	}
	defer shred.RemoveAll(bobDir)

	aliceDaemon := PrepareTestAccountDaemon(alice, aliceDir, denameConfig, serverAddr, serverPubkey, t)
	bobDaemon := PrepareTestAccountDaemon(bob, bobDir, denameConfig, serverAddr, serverPubkey, t)

	//activate initialized daemons
	aliceDaemon.Start()
	bobDaemon.Start()
	defer aliceDaemon.Stop()
	defer bobDaemon.Stop()

	//alice creates a new conversation and sends a message
	participants := []string{alice, bob}
	subj := "testConversation"
	conv := &proto.ConversationMetadata{
		Participants: participants,
		Subject:      subj,
	}

	if err := aliceDaemon.ConversationToOutbox(conv); err != nil {
		t.Fatal(err)
	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		t.Fatal(err)
	}

	err = watcher.Add(bobDaemon.ConversationDir())
	if err != nil {
		t.Fatal(err)
	}

	sentMessage := "Bob, can you hear me?"
	if err := aliceDaemon.MessageToOutbox(persistence.ConversationName(conv), sentMessage); err != nil {
		t.Fatal(err)
	}

	var files []string
	for len(files) == 0 {
		watcher.Add((<-watcher.Events).Name)
		if files, err = filepath.Glob(filepath.Join(bobDaemon.ConversationDir(), persistence.ConversationName(conv), "*alice")); err != nil {
			t.Fatal(err)
		}
	}

	receivedMessage, err := ioutil.ReadFile(files[0])
	if err != nil {
		t.Fatal(err)
	}
	if string(receivedMessage) != sentMessage {
		t.Fatal("receivedMessage != sentMessage")
	}
}
コード例 #8
0
ファイル: daemon.go プロジェクト: andres-erbsen/chatterbox
func (d *Daemon) processOutboxDir(dirname string) error {
	// TODO: refactor: separate message assembly and filesystem access?
	// parse metadata
	metadataFile := filepath.Join(dirname, persistence.MetadataFileName)
	if _, err := os.Stat(metadataFile); err != nil {
		return nil // no metadata --> not an outgoing message
	}

	metadata := proto.ConversationMetadata{}
	err := persistence.UnmarshalFromFile(metadataFile, &metadata)
	if err != nil {
		return err
	}

	metadata.Participants = append(metadata.Participants, d.Dename)
	undupStrings(metadata.Participants)
	sort.Strings(metadata.Participants)
	convName := persistence.ConversationName(&metadata)

	// load messages
	potentialMessages, err := ioutil.ReadDir(dirname)
	if err != nil {
		return err
	}
	messages := make([][]byte, 0, len(potentialMessages))
	for _, finfo := range potentialMessages {
		if !finfo.IsDir() && finfo.Name() != persistence.MetadataFileName {
			msg, err := ioutil.ReadFile(filepath.Join(dirname, finfo.Name()))
			if err != nil {
				return err
			}

			// make protobuf for message; append it
			d.ourDenameLookupMu.Lock()
			payload := proto.Message{
				Dename:       d.Dename,
				DenameLookup: d.ourDenameLookup,
				Contents:     msg,
				Subject:      metadata.Subject,
				Participants: metadata.Participants,
				Date:         finfo.ModTime().UnixNano(),
			}
			d.ourDenameLookupMu.Unlock()
			payloadBytes, err := payload.Marshal()
			if err != nil {
				return err
			}
			messages = append(messages, payloadBytes)
		}
	}
	if len(messages) == 0 {
		return nil // no messages to send, just the metadata file
	}

	if err := d.conversationToConversations(&metadata); err != nil && !os.IsExist(err) && !strings.Contains(fmt.Sprint(err), "directory not empty") {
		log.Fatal(err)
	}

	for _, recipient := range metadata.Participants {
		if recipient == d.Dename {
			continue
		}
		for _, msg := range messages {
			if err != nil {
				return err
			}
			if msgRatch, err := LoadRatchet(d, recipient, d.fillAuth, d.checkAuth); err != nil { //First message to this recipien
				if err := d.sendFirstMessage(msg, recipient); err != nil {
					return err
				}
			} else {
				if err := d.sendMessage(msg, recipient, msgRatch); err != nil {
					return err
				}
			}
		}
	}

	// move the sent messages to the conversation folder
	for _, finfo := range potentialMessages {
		if !finfo.IsDir() && finfo.Name() != persistence.MetadataFileName {
			if err = os.Rename(filepath.Join(dirname, finfo.Name()), filepath.Join(d.ConversationDir(), persistence.ConversationName(&metadata), persistence.MessageName(finfo.ModTime(), string(d.Dename)))); err != nil {
				log.Fatal(err)
			}
		}
	}

	// canonicalize the outbox folder name
	if dirname != filepath.Join(d.OutboxDir(), convName) {
		if err := os.Rename(dirname, filepath.Join(d.OutboxDir(), convName)); err != nil {
			shred.RemoveAll(dirname)
		}
	}

	return nil
}