func (m *Mailer) HandleDelivery(next func(conn *smtpd.Connection)) func(conn *smtpd.Connection) { return func(conn *smtpd.Connection) { // Context variables var ( isSpam = false ctxID = uniuri.NewLen(uniuri.UUIDLen) ) // Check for spam spamReply, err := m.Spam.Report(string(conn.Envelope.Data)) if err != nil { m.Log.WithFields(logrus.Fields{ "ctx_id": ctxID, "err": err, }).Error("Unable to check an email in spamd") } if spamReply != nil && spamReply.Code == spamc.EX_OK { if spam, ok := spamReply.Vars["isSpam"]; ok && spam.(bool) { isSpam = true } } // Trim the spaces from the input conn.Envelope.Data = bytes.TrimSpace(conn.Envelope.Data) // First run the analysis algorithm to generate an email description node := &models.EmailNode{} if err := analyzeEmail(node, conn.Envelope.Data); err != nil { m.Error(conn, err) return } // Calculate message ID messageID := node.Headers.Get("Message-ID") x1i := strings.Index(messageID, "<") if x1i != -1 { x2i := strings.Index(messageID[x1i+1:], ">") if x2i != -1 { messageID = messageID[x1i+1 : x1i+x2i+1] } } if messageID == "" { messageID = uniuri.NewLen(uniuri.UUIDLen) + "@invalid-incoming.pgp.st" } // Generate the members field fromHeader, err := mail.ParseAddress(node.Headers.Get("From")) if err != nil { m.Error(conn, err) return } members := []string{fromHeader.Address} toHeader, err := node.Headers.AddressList("From") if err != nil { m.Error(conn, err) return } for _, to := range toHeader { members = append(members, to.Address) } if x := node.Headers.Get("CC"); x != "" { ccHeader, err := node.Headers.AddressList("CC") if err != nil { m.Error(conn, err) return } for _, cc := range ccHeader { members = append(members, cc.Address) } } // Get the recipients list from the scope recipients := conn.Environment["recipients"].([]recipient) for _, recipient := range recipients { // Parse the found key keyring, err := openpgp.ReadKeyRing(bytes.NewReader(recipient.Key.Body)) if err != nil { m.Error(conn, err) return } // Prepare a new email object email := &models.Email{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: recipient.Account.ID, MessageID: messageID, Status: "received", } // Generate a new key key := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, key); err != nil { m.Error(conn, err) return } akey := [32]byte{} copy(akey[:], key) // Generate a new nonce nonce := make([]byte, 8) if _, err = rand.Read(nonce); err != nil { m.Error(conn, err) return } // Set up a new stream stream, err := chacha20.New(key, nonce) if err != nil { m.Error(conn, err) return } // Prepare output ciphertext := make( []byte, len(conn.Envelope.Data)+ chacha20.NonceSize+ (len(conn.Envelope.Data)/1024+1)*16, ) // First write the nonce copy(ciphertext[:chacha20.NonceSize], nonce) // Then write the authenticated encrypted stream oi := chacha20.NonceSize for i := 0; i < len(conn.Envelope.Data); i += 1024 { // Get the chunk max := i + 1024 if max > len(conn.Envelope.Data) { max = len(conn.Envelope.Data) } chunk := conn.Envelope.Data[i:max] // Encrypt the block stream.XORKeyStream(ciphertext[oi:oi+len(chunk)], chunk) // Authenticate it var out [16]byte poly1305.Sum(&out, ciphertext[oi:oi+len(chunk)], &akey) // Write it into the result copy(ciphertext[oi+len(chunk):oi+len(chunk)+poly1305.TagSize], out[:]) // Increase the counter oi += 1024 + poly1305.TagSize } // Save it to the body email.Body = ciphertext // Create a manifest and encrypt it manifest, err := json.Marshal(models.Manifest{ Key: key, Nonce: nonce, Description: node, }) if err != nil { m.Error(conn, err) return } encryptedManifest, err := utils.PGPEncrypt(manifest, keyring) if err != nil { m.Error(conn, err) return } email.Manifest = encryptedManifest // Match the thread var thread *models.Thread // Get the References header var references string if x := node.Headers.Get("In-Reply-To"); x != "" { references = x } else if x := node.Headers.Get("References"); x != "" { references = x } // Match by Message-ID if references != "" { // As specified in http://www.jwz.org/doc/threading.html, first thing <> is the msg id // We support both <message-id> and message-id format. x1i := strings.Index(references, "<") if x1i != -1 { x2i := strings.Index(references[x1i+1:], ">") if x2i != -1 { references = references[x1i+1 : x1i+x2i+1] } } // Look up the message ID in the database cursor, err := r.Table("emails").GetAllByIndex("messageIDOwner", []interface{}{ references, recipient.Account.ID, }).CoerceTo("array").Do(func(emails r.Term) r.Term { return r.Branch( emails.Count().Eq(1), r.Table("threads").Get(emails.Nth(0).Field("thread")).Default(map[string]interface{}{}), map[string]interface{}{}, ) }).Run(m.Rethink) if err != nil { m.Error(conn, err) return } defer cursor.Close() if err := cursor.One(&thread); err != nil { m.Error(conn, err) return } // Check if we've found it, clear it if it's invalid if thread.ID == "" { thread = nil } } // We can't match it by subject, so proceed to create a new thread if thread == nil { var secure string if findEncrypted(node) { secure = "all" } else { secure = "none" } labels := []string{recipient.Labels.Inbox} if isSpam { labels = append(labels, recipient.Labels.Spam) } thread = &models.Thread{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: recipient.Account.ID, Labels: labels, Members: members, Secure: secure, } if err := r.Table("threads").Insert(thread).Exec(m.Rethink); err != nil { m.Error(conn, err) return } } else { // Modify the existing thread foundInbox := false foundSpam := false for _, label := range thread.Labels { if label == recipient.Labels.Inbox { foundInbox = true } if isSpam { if label == recipient.Labels.Spam { foundSpam = true } if foundInbox && foundSpam { break } } else { if foundInbox { break } } } // Append to thread.Labels if !foundInbox { thread.Labels = append(thread.Labels, recipient.Labels.Inbox) } if !foundSpam && isSpam { thread.Labels = append(thread.Labels, recipient.Labels.Spam) } // Members update membersHash := map[string]struct{}{} for _, member := range thread.Members { membersHash[member] = struct{}{} } for _, member := range members { if _, ok := membersHash[member]; !ok { membersHash[member] = struct{}{} } } thread.Members = []string{} for member := range membersHash { thread.Members = append(thread.Members, member) } update := map[string]interface{}{ "date_modified": time.Now(), "is_read": false, "labels": thread.Labels, "members": thread.Members, } secure := findEncrypted(node) if (thread.Secure == "all" && !secure) || (thread.Secure == "none" && secure) { thread.Secure = "some" } if err := r.Table("threads").Get(thread.ID).Update(update).Exec(m.Rethink); err != nil { m.Error(conn, err) return } } email.Thread = thread.ID if err := r.Table("emails").Insert(email).Exec(m.Rethink); err != nil { m.Error(conn, err) return } m.Log.WithFields(logrus.Fields{ "address": recipient.Address.ID, "account": recipient.Account.MainAddress, }).Info("Email received") } next(conn) } }
func (a *API) createKey(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Check the scope if !models.InScope(token.Scope, []string{"keys"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Decode the input var input struct { Body []byte `json:"body"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": 0, "message": err.Error(), }) return } // Validate the input if input.Body == nil || len(input.Body) == 0 { c.JSON(422, &gin.H{ "code": 0, "message": "No key body in the input", }) return } // Parse the key keyring, err := openpgp.ReadKeyRing(bytes.NewReader(input.Body)) if err != nil { c.JSON(422, &gin.H{ "code": 0, "message": "Invalid key format", }) return } publicKey := keyring[0].PrimaryKey // Parse the identities identities := []*models.Identity{} for _, identity := range keyring[0].Identities { id := &models.Identity{ Name: identity.Name, } if identity.SelfSignature != nil { sig := identity.SelfSignature id.SelfSignature = &models.Signature{ Type: uint8(sig.SigType), Algorithm: uint8(sig.PubKeyAlgo), Hash: uint(sig.Hash), CreationTime: sig.CreationTime, SigLifetimeSecs: sig.SigLifetimeSecs, KeyLifetimeSecs: sig.KeyLifetimeSecs, IssuerKeyID: sig.IssuerKeyId, IsPrimaryID: sig.IsPrimaryId, RevocationReason: sig.RevocationReason, RevocationReasonText: sig.RevocationReasonText, } } if identity.Signatures != nil { id.Signatures = []*models.Signature{} for _, sig := range identity.Signatures { id.Signatures = append(id.Signatures, &models.Signature{ Type: uint8(sig.SigType), Algorithm: uint8(sig.PubKeyAlgo), Hash: uint(sig.Hash), CreationTime: sig.CreationTime, SigLifetimeSecs: sig.SigLifetimeSecs, KeyLifetimeSecs: sig.KeyLifetimeSecs, IssuerKeyID: sig.IssuerKeyId, IsPrimaryID: sig.IsPrimaryId, RevocationReason: sig.RevocationReason, RevocationReasonText: sig.RevocationReasonText, }) } } identities = append(identities, id) } // Acquire key's length length, err := publicKey.BitLength() if err != nil { c.JSON(422, &gin.H{ "code": 0, "message": "Couldn't calculate bit length", }) return } // Generate a new key struct key := &models.Key{ ID: hex.EncodeToString(publicKey.Fingerprint[:]), DateCreated: time.Now(), DateModified: time.Now(), Owner: account.ID, Algorithm: uint8(publicKey.PubKeyAlgo), Length: length, Body: input.Body, KeyID: publicKey.KeyId, KeyIDString: publicKey.KeyIdString(), KeyIDShortString: publicKey.KeyIdShortString(), Identities: identities, } // Insert it into database if err := r.Table("keys").Insert(key).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } c.JSON(201, key) }