1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package address2021import (22 "strings"2324 "golang.org/x/net/idna"25)2627/*28Rules for validation are subset of rules listed here:29https://emailregex.com/email-validation-summary/30*/3132// Valid checks whether ths string is valid as a email address as defined by33// RFC 5321.34func Valid(addr string) bool {35 if len(addr) > 320 { // RFC 3696 says it's 320, not 255.36 return false37 }3839 mbox, domain, err := Split(addr)40 if err != nil {41 return false42 }4344 // The only case where this can be true is "postmaster".45 // So allow it.46 if domain == "" {47 return true48 }4950 return ValidMailboxName(mbox) && ValidDomain(domain)51}5253var validGraphic = map[rune]bool{54 '!': true, '#': true,55 '$': true, '%': true,56 '&': true, '\'': true,57 '*': true, '+': true,58 '-': true, '/': true,59 '=': true, '?': true,60 '^': true, '_': true,61 '`': true, '{': true,62 '|': true, '}': true,63 '~': true, '.': true,64}6566// ValidMailboxName checks whether the specified string is a valid mailbox-name67// element of e-mail address (left part of it, before at-sign).68func ValidMailboxName(mbox string) bool {69 if strings.HasPrefix(mbox, `"`) {70 raw, err := UnquoteMbox(mbox)71 if err != nil {72 return false73 }7475 // Inside quotes, any ASCII graphic and space is allowed.76 // Additionally, RFC 6531 extends that to allow any Unicode (UTF-8).77 for _, ch := range raw {78 if ch < ' ' || ch == 0x7F /* DEL */ {79 // ASCII control characters.80 return false81 }82 }83 return true84 }8586 // Without quotes, limited set of ASCII graphics is allowed + ASCII87 // alphanumeric characters.88 // RFC 6531 extends that to allow any Unicode (UTF-8).89 for _, ch := range mbox {90 if validGraphic[ch] {91 continue92 }93 if ch >= '0' && ch <= '9' {94 continue95 }96 if ch >= 'A' && ch <= 'Z' {97 continue98 }99 if ch >= 'a' && ch <= 'z' {100 continue101 }102 if ch > 0x7F { // Unicode103 continue104 }105106 return false107 }108109 return true110}111112// ValidDomain checks whether the specified string is a valid DNS domain.113func ValidDomain(domain string) bool {114 if len(domain) > 255 || len(domain) == 0 {115 return false116 }117 if strings.HasPrefix(domain, ".") {118 return false119 }120 if strings.Contains(domain, "..") {121 return false122 }123124 // Length checks are to be applied to A-labels form.125 // maddy uses U-labels representation across the code (for lookups, etc).126 domainASCII, err := idna.ToASCII(domain)127 if err != nil {128 return false129 }130 labels := strings.Split(domainASCII, ".")131 for _, label := range labels {132 if len(label) > 64 {133 return false134 }135 }136137 return true138}