diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index a7218a082c..cf257d83f6 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1707,6 +1707,9 @@ LEVEL = Info ;; ;; convert links of attached images to inline images. Only for images hosted in this gitea instance. ;BASE64_EMBED_IMAGES = false +;; +;; The maximum size of an image attachment to be embedded in the email. +;BASE64_EMBED_IMAGES_MAX_SIZE = 5242880 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index 638c442884..48a8e965a6 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -13,22 +13,23 @@ import ( "code.gitea.io/gitea/modules/log" - shellquote "github.com/kballard/go-shellquote" + "github.com/kballard/go-shellquote" ) // Mailer represents mail service. type Mailer struct { // Mailer - Name string `ini:"NAME"` - From string `ini:"FROM"` - EnvelopeFrom string `ini:"ENVELOPE_FROM"` - OverrideEnvelopeFrom bool `ini:"-"` - FromName string `ini:"-"` - FromEmail string `ini:"-"` - SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"` - SubjectPrefix string `ini:"SUBJECT_PREFIX"` - OverrideHeader map[string][]string `ini:"-"` - Base64EmbedImages bool `ini:"BASE64_EMBED_IMAGES"` + Name string `ini:"NAME"` + From string `ini:"FROM"` + EnvelopeFrom string `ini:"ENVELOPE_FROM"` + OverrideEnvelopeFrom bool `ini:"-"` + FromName string `ini:"-"` + FromEmail string `ini:"-"` + SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"` + SubjectPrefix string `ini:"SUBJECT_PREFIX"` + OverrideHeader map[string][]string `ini:"-"` + Base64EmbedImages bool `ini:"BASE64_EMBED_IMAGES"` + Base64EmbedImagesMaxSizePerAttachment int64 `ini:"BASE64_EMBED_IMAGES_MAX_SIZE_PER_ATTACHMENT"` // SMTP sender Protocol string `ini:"PROTOCOL"` @@ -152,6 +153,7 @@ func loadMailerFrom(rootCfg ConfigProvider) { sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true) sec.Key("FROM").MustString(sec.Key("USER").String()) sec.Key("BASE64_EMBED_IMAGES").MustBool(false) + sec.Key("BASE64_EMBED_IMAGES_MAX_SIZE_PER_ATTACHMENT").MustInt64(5 * 1024 * 1024) // Now map the values on to the MailService MailService = &Mailer{} diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 8d231e08fa..d8227fc6e1 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -21,7 +21,9 @@ import ( activities_model "code.gitea.io/gitea/models/activities" issues_model "code.gitea.io/gitea/models/issues" + access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/emoji" @@ -421,6 +423,7 @@ func Base64InlineImages(body string, ctx *MailCommentContext) (string, error) { } func AttachmentSrcToBase64DataURI(attachmentPath string, ctx *MailCommentContext) (string, error) { + maxSizePerImageAttachment := setting.MailService.Base64EmbedImagesMaxSizePerAttachment if !strings.HasPrefix(attachmentPath, setting.AppURL) { // external image return "", fmt.Errorf("external image") } @@ -435,6 +438,16 @@ func AttachmentSrcToBase64DataURI(attachmentPath string, ctx *MailCommentContext return "", err } + // "Doer" is theoretically not the correct permission check (as Doer created the action on which to send), but as this is batch processed the receipants can't be accessed. + // Therefore we check the Doer, with which we counter leaking information as a Doer brute force attack on attachments would be possible. + perm, err := access_model.GetUserRepoPermission(ctx, ctx.Issue.Repo, ctx.Doer) + if err != nil { + return "", err + } + if !perm.CanRead(unit.TypeIssues) { + return "", fmt.Errorf("no permission") + } + fr, err := storage.Attachments.Open(attachment.RelativePath()) if err != nil { return "", err @@ -446,7 +459,16 @@ func AttachmentSrcToBase64DataURI(attachmentPath string, ctx *MailCommentContext return "", err } + if len(content) > int(maxSizePerImageAttachment) { + return "", fmt.Errorf("image too large (%d bytes) of max %d bytes", len(content), maxSizePerImageAttachment) + } + mimeType := http.DetectContentType(content) + + if !strings.HasPrefix(mimeType, "image/") { + return "", fmt.Errorf("not an image") + } + encoded := base64.StdEncoding.EncodeToString(content) dataURI := fmt.Sprintf("data:%s;base64,%s", mimeType, encoded)