예제 #1
0
파일: handler.go 프로젝트: pgpst/pgpst
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)
	}
}
예제 #2
0
파일: routes_keys.go 프로젝트: pgpst/pgpst
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)
}