maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19package address
 20
 21import (
 22	"strings"
 23
 24	"golang.org/x/net/idna"
 25)
 26
 27/*
 28Rules for validation are subset of rules listed here:
 29https://emailregex.com/email-validation-summary/
 30*/
 31
 32// Valid checks whether ths string is valid as a email address as defined by
 33// RFC 5321.
 34func Valid(addr string) bool {
 35	if len(addr) > 320 { // RFC 3696 says it's 320, not 255.
 36		return false
 37	}
 38
 39	mbox, domain, err := Split(addr)
 40	if err != nil {
 41		return false
 42	}
 43
 44	// The only case where this can be true is "postmaster".
 45	// So allow it.
 46	if domain == "" {
 47		return true
 48	}
 49
 50	return ValidMailboxName(mbox) && ValidDomain(domain)
 51}
 52
 53var 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}
 65
 66// ValidMailboxName checks whether the specified string is a valid mailbox-name
 67// 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 false
 73		}
 74
 75		// 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 false
 81			}
 82		}
 83		return true
 84	}
 85
 86	// Without quotes, limited set of ASCII graphics is allowed + ASCII
 87	// alphanumeric characters.
 88	// RFC 6531 extends that to allow any Unicode (UTF-8).
 89	for _, ch := range mbox {
 90		if validGraphic[ch] {
 91			continue
 92		}
 93		if ch >= '0' && ch <= '9' {
 94			continue
 95		}
 96		if ch >= 'A' && ch <= 'Z' {
 97			continue
 98		}
 99		if ch >= 'a' && ch <= 'z' {
100			continue
101		}
102		if ch > 0x7F { // Unicode
103			continue
104		}
105
106		return false
107	}
108
109	return true
110}
111
112// 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 false
116	}
117	if strings.HasPrefix(domain, ".") {
118		return false
119	}
120	if strings.Contains(domain, "..") {
121		return false
122	}
123
124	// 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 false
129	}
130	labels := strings.Split(domainASCII, ".")
131	for _, label := range labels {
132		if len(label) > 64 {
133			return false
134		}
135	}
136
137	return true
138}