chore: Add debug_assemble binary
Attempt to reassemble messages produced by the mailbox state debug tool. Unfortunately, most of it will only work if the messages have been fully decrypted. To handle encrypted messages we need to have access to the user's keyring, which is not available.
This commit is contained in:
parent
5823e3a99f
commit
eff7df2136
|
@ -22,6 +22,7 @@ import (
|
|||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
|
@ -37,6 +38,7 @@ import (
|
|||
imapservice "github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/usertypes"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
bmessage "github.com/ProtonMail/proton-bridge/v3/pkg/message"
|
||||
"github.com/bradenaw/juniper/xmaps"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"github.com/emersion/go-message"
|
||||
|
@ -224,6 +226,55 @@ func (user *User) DebugDownloadMessages(
|
|||
return nil
|
||||
}
|
||||
|
||||
func TryBuildDebugMessage(path string) error {
|
||||
meta, err := loadDebugMetadata(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load metadata: %w", err)
|
||||
}
|
||||
|
||||
body, bodyDecrypted, err := loadDebugBody(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load body: %w", err)
|
||||
}
|
||||
|
||||
var da []bmessage.DecryptedAttachment
|
||||
if len(meta.Attachments) != 0 {
|
||||
d, err := loadAttachments(path, &meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
da = d
|
||||
}
|
||||
|
||||
decryptedMessage := bmessage.DecryptedMessage{
|
||||
Msg: proton.Message{
|
||||
MessageMetadata: meta.MessageMetadata,
|
||||
Header: meta.Header,
|
||||
ParsedHeaders: meta.ParsedHeaders,
|
||||
Body: "",
|
||||
MIMEType: meta.MIMEType,
|
||||
Attachments: nil,
|
||||
},
|
||||
Body: bytes.Buffer{},
|
||||
BodyErr: nil,
|
||||
Attachments: da,
|
||||
}
|
||||
|
||||
if bodyDecrypted {
|
||||
decryptedMessage.Body.Write(body)
|
||||
} else {
|
||||
decryptedMessage.Msg.Body = string(body)
|
||||
decryptedMessage.BodyErr = fmt.Errorf("body did not decrypt")
|
||||
}
|
||||
|
||||
var rfc822Message bytes.Buffer
|
||||
if err := bmessage.BuildRFC822Into(nil, &decryptedMessage, defaultMessageJobOpts(), &rfc822Message); err != nil {
|
||||
return fmt.Errorf("failed to build message: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBodyName(path string) string {
|
||||
return filepath.Join(path, "body.txt")
|
||||
}
|
||||
|
@ -297,16 +348,16 @@ func decodeSimpleMessage(outPath string, kr *crypto.KeyRing, msg proton.Message)
|
|||
return nil
|
||||
}
|
||||
|
||||
func writeMetadata(outPath string, msg proton.Message) error {
|
||||
type CustomMetadata struct {
|
||||
proton.MessageMetadata
|
||||
Header string
|
||||
ParsedHeaders proton.Headers
|
||||
MIMEType rfc822.MIMEType
|
||||
Attachments []proton.Attachment
|
||||
}
|
||||
type DebugMetadata struct {
|
||||
proton.MessageMetadata
|
||||
Header string
|
||||
ParsedHeaders proton.Headers
|
||||
MIMEType rfc822.MIMEType
|
||||
Attachments []proton.Attachment
|
||||
}
|
||||
|
||||
metadata := CustomMetadata{
|
||||
func writeMetadata(outPath string, msg proton.Message) error {
|
||||
metadata := DebugMetadata{
|
||||
MessageMetadata: msg.MessageMetadata,
|
||||
Header: msg.Header,
|
||||
ParsedHeaders: msg.ParsedHeaders,
|
||||
|
@ -433,3 +484,78 @@ func writeCustomAttachmentPart(
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadDebugMetadata(dir string) (DebugMetadata, error) {
|
||||
metadataPath := getMetadataPath(dir)
|
||||
b, err := os.ReadFile(metadataPath) //nolint:gosec
|
||||
if err != nil {
|
||||
return DebugMetadata{}, err
|
||||
}
|
||||
|
||||
var m DebugMetadata
|
||||
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return DebugMetadata{}, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func loadDebugBody(dir string) ([]byte, bool, error) {
|
||||
if b, err := os.ReadFile(getBodyName(dir)); err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, false, err
|
||||
}
|
||||
} else {
|
||||
return b, true, nil
|
||||
}
|
||||
|
||||
if b, err := os.ReadFile(getBodyNameFailed(dir)); err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, false, err
|
||||
}
|
||||
} else {
|
||||
return b, false, nil
|
||||
}
|
||||
|
||||
return nil, false, fmt.Errorf("body is either pgp message, which we can't handle or is missing")
|
||||
}
|
||||
|
||||
func loadAttachments(dir string, meta *DebugMetadata) ([]bmessage.DecryptedAttachment, error) {
|
||||
attDecrypted := make([]bmessage.DecryptedAttachment, 0, len(meta.Attachments))
|
||||
|
||||
for _, a := range meta.Attachments {
|
||||
data, err := os.ReadFile(getAttachmentPathSuccess(dir, a.ID, a.Name))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, fmt.Errorf("attachment (%v,%v) must have failed to decrypt, we can't do anything since we need the user's keyring", a.ID, a.Name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to load attachment (%v,%v): %w", a.ID, a.Name, err)
|
||||
}
|
||||
|
||||
da := bmessage.DecryptedAttachment{
|
||||
Packet: nil,
|
||||
Encrypted: nil,
|
||||
Data: bytes.Buffer{},
|
||||
Err: nil,
|
||||
}
|
||||
|
||||
da.Data.Write(data)
|
||||
|
||||
attDecrypted = append(attDecrypted, da)
|
||||
}
|
||||
|
||||
return attDecrypted, nil
|
||||
}
|
||||
|
||||
func defaultMessageJobOpts() bmessage.JobOptions {
|
||||
return bmessage.JobOptions{
|
||||
IgnoreDecryptionErrors: true, // Whether to ignore decryption errors and create a "custom message" instead.
|
||||
SanitizeDate: true, // Whether to replace all dates before 1970 with RFC822's birthdate.
|
||||
AddInternalID: true, // Whether to include MessageID as X-Pm-Internal-Id.
|
||||
AddExternalID: true, // Whether to include ExternalID as X-Pm-External-Id.
|
||||
AddMessageDate: true, // Whether to include message time as X-Pm-Date.
|
||||
AddMessageIDReference: true, // Whether to include the MessageID in References.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Printf("Usage: %v <dump dir>\n", os.Args[0])
|
||||
return
|
||||
}
|
||||
|
||||
if err := user.TryBuildDebugMessage(os.Args[1]); err != nil {
|
||||
fmt.Printf("%v\n", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue