diff --git a/spf.py b/spf.py
index 13c1eda8e80199e401670ae796aeb5dfda4a01a9..4f57045d2d9e10f09ea84e1a833c7a71809c65dd 100755
--- a/spf.py
+++ b/spf.py
@@ -2,7 +2,8 @@
 """SPF (Sender Policy Framework) implementation.
 
 Copyright (c) 2003, Terence Way
-Portions Copyright (c) 2004,2005 Stuart Gathman <stuart@bmsi.com>
+Portions Copyright (c) 2004,2005,2006 Stuart Gathman <stuart@bmsi.com>
+Portions Copyright (c) 2005,2006 Scott Kitterman <scott@kitterman.com>
 This module is free software, and you may redistribute it and/or modify
 it under the same terms as Python itself, so long as this copyright message
 and disclaimer are retained in their original form.
@@ -19,7 +20,7 @@ AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
 For more information about SPF, a tool against email forgery, see
-	http://openspf.org/
+	http://www.openspf.org/
 
 For news, bugfixes, etc. visit the home page for this implementation at
 	http://www.wayforward.net/spf/
@@ -47,6 +48,9 @@ For news, bugfixes, etc. visit the home page for this implementation at
 # Terrence is not responding to email.
 #
 # $Log$
+# Revision 1.22  2006/06/21 21:13:07  customdesigned
+# initialize perm_error
+#
 # Revision 1.21  2006/05/12 16:15:20  customdesigned
 # a:1.2.3.4 -> ip4:1.2.3.4 'lax' heuristic.
 #
@@ -273,7 +277,7 @@ For news, bugfixes, etc. visit the home page for this implementation at
 
 __author__ = "Terence Way"
 __email__ = "terry@wayforward.net"
-__version__ = "1.6: December 18, 2003"
+__version__ = "1.7: July 26, 2006"
 MODULE = 'spf'
 
 USAGE = """To check an incoming mail request:
@@ -311,6 +315,8 @@ def DNSLookup(name,qtype):
     #resp.show()
     # key k: ('wayforward.net', 'A'), value v
     return [((a['name'], a['typename']), a['data']) for a in resp.answers]
+  except IOError,x:
+    raise TempError,'DNS ' + str(x)
   except DNS.DNSError,x:
     raise TempError,'DNS ' + str(x)
 
@@ -322,7 +328,7 @@ def isSPF(txt):
 MASK = 0xFFFFFFFFL
 
 # Regular expression to look for modifiers
-RE_MODIFIER = re.compile(r'^([a-zA-Z]+)=')
+RE_MODIFIER = re.compile(r'^([a-zA-Z0-9_\-\.]+)=')
 
 # Regular expression to find macro expansions
 RE_CHAR = re.compile(r'%(%|_|-|(\{[a-zA-Z][0-9]*r?[^\}]*\}))')
@@ -330,7 +336,7 @@ RE_CHAR = re.compile(r'%(%|_|-|(\{[a-zA-Z][0-9]*r?[^\}]*\}))')
 # Regular expression to break up a macro expansion
 RE_ARGS = re.compile(r'([0-9]*)(r?)([^0-9a-zA-Z]*)')
 
-RE_CIDR = re.compile(r'/([1-9]|1[0-9]*|2[0-9]*|3[0-2]*)$')
+RE_CIDR = re.compile(r'/([1-9]|1[0-9]|2[0-9]|3[0-2])$')
 
 RE_IP4 = re.compile(r'\.'.join(
         [r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4)+'$')
@@ -370,9 +376,9 @@ except NameError:
 DEFAULT_SPF = 'v=spf1 a/24 mx/24 ptr'
 
 # maximum DNS lookups allowed
-MAX_LOOKUP = 10 #draft-schlitt-spf-classic-02 Para 10.1
-MAX_MX = 10 #draft-schlitt-spf-classic-02 Para 10.1
-MAX_PTR = 10 #draft-schlitt-spf-classic-02 Para 10.1
+MAX_LOOKUP = 10 #RFC 4408 Para 10.1
+MAX_MX = 10 #RFC 4408 Para 10.1
+MAX_PTR = 10 #RFC 4408 Para 10.1
 MAX_CNAME = 10 # analogous interpretation to MAX_PTR
 MAX_RECURSION = 20
 ALL_MECHANISMS = ('a', 'mx', 'ptr', 'exists', 'include', 'ip4', 'ip6', 'all')
@@ -480,7 +486,7 @@ class query(object):
 	('unknown', 550, 'SPF Permanent Error: Unknown mechanism found: moo')
 
 	>>> q.check(spf='v=spf1 =a ?all moo')
-	('unknown', 550, 'SPF Permanent Error: Unknown qualifier, IETF draft para 4.6.1, found in: =a')
+	('unknown', 550, 'SPF Permanent Error: Unknown qualifier, RFC 4408 para 4.6.1, found in: =a')
 
 	>>> q.check(spf='v=spf1 ip4:192.0.0.0/8 ~all')
 	('pass', 250, 'sender SPF verified')
@@ -575,6 +581,11 @@ class query(object):
 	Returns mech,m,arg,cidrlength,result
 
 	Examples:
+	>>> q = query(s='strong-bad@email.example.com.',
+	...           h='mx.example.org', i='192.0.2.3')
+	>>> q.validate_mechanism('A')
+	('A', 'a', 'email.example.com', 32, 'pass')
+
 	>>> q = query(s='strong-bad@email.example.com',
 	...           h='mx.example.org', i='192.0.2.3')
 	>>> q.validate_mechanism('A')
@@ -583,8 +594,24 @@ class query(object):
 	>>> q.validate_mechanism('?mx:%{d}/27')
 	('?mx:%{d}/27', 'mx', 'email.example.com', 27, 'neutral')
 
-	>>> q.validate_mechanism('-mx::%%%_/.Clara.de/27')
-	('-mx::%%%_/.Clara.de/27', 'mx', ':% /.Clara.de', 27, 'fail')
+	>>> try: q.validate_mechanism('ip4:1.2.3.4/247')
+	... except PermError,x: print x
+	Invalid IP4 address: ip4:1.2.3.4/247
+
+	>>> try: q.validate_mechanism('a:example.com:8080')
+	... except PermError,x: print x
+	Too many :. Not allowed in domain name.: a:example.com:8080
+	
+	>>> try: q.validate_mechanism('ip4:1.2.3.444/24')
+	... except PermError,x: print x
+	Invalid IP4 address: ip4:1.2.3.444/24
+	
+	>>> try: q.validate_mechanism('-all:3030')
+	... except PermError,x: print x
+	Invalid all mechanism format - only qualifier allowed with all: -all:3030
+
+	>>> q.validate_mechanism('-mx:%%%_/.Clara.de/27')
+	('-mx:%%%_/.Clara.de/27', 'mx', '% /.Clara.de', 27, 'fail')
 
 	>>> q.validate_mechanism('~exists:%{i}.%{s1}.100/86400.rate.%{d}')
 	('~exists:%{i}.%{s1}.100/86400.rate.%{d}', 'exists', '192.0.2.3.com.100/86400.rate.email.example.com', 32, 'softfail')
@@ -608,7 +635,9 @@ class query(object):
 		  x = self.note_error(
 		    'Use the ip4 mechanism for ip4 addresses',mech)
 		  m = 'ip4'
-		  
+		# Check for : within the arguement
+		if arg.count(':') > 0:
+		  raise PermError('Too many :. Not allowed in domain name.',mech)
 		if m in ('a', 'mx', 'ptr', 'exists', 'include'):
 		  arg = self.expand(arg)
 		  # FQDN must contain at least one '.'
@@ -631,11 +660,18 @@ class query(object):
 		  return mech,m,arg,cidrlength,result
 		if m == 'ip4' and not RE_IP4.match(arg):
 		  raise PermError('Invalid IP4 address',mech)
+		#validate 'all' mechanism per RFC 4408 ABNF
+		if m == 'all' and \
+		    (arg != self.d  or mech.count(':') or mech.count('/')):
+ 		  print '|'+ arg + '|', mech, self.d,
+		  self.note_error(
+	      'Invalid all mechanism format - only qualifier allowed with all'
+		    ,mech)
 		if m in ALL_MECHANISMS:
 		  return mech,m,arg,cidrlength,result
 		if m[1:] in ALL_MECHANISMS:
 		  x = self.note_error(
-		    'Unknown qualifier, IETF draft para 4.6.1, found in', mech)
+		    'Unknown qualifier, RFC 4408 para 4.6.1, found in', mech)
 		else:
 		  x = self.note_error('Unknown mechanism found',mech)
 		return mech,m,arg,cidrlength,x
@@ -770,11 +806,12 @@ class query(object):
 	def get_explanation(self, spec):
 		"""Expand an explanation."""
 		if spec:
-		  return self.expand(''.join(self.dns_txt(self.expand(spec))))
+		  txt = ''.join(self.dns_txt(self.expand(spec)))
+		  return self.expand(txt,stripdot=False)
 		else:
 		  return 'explanation : Required option is missing'
 
-	def expand(self, str):
+	def expand(self, str, stripdot=True): # macros='slodipvh'
 		"""Do SPF RFC macro expansion.
 
 		Examples:
@@ -842,6 +879,9 @@ class query(object):
 		>>> q.expand('%{p2}.trusted-domains.example.net')
 		'example.org.trusted-domains.example.net'
 
+		>>> q.expand('%{p2}.trusted-domains.example.net.')
+		'example.org.trusted-domains.example.net'
+
 		"""
 		end = 0
 		result = ''
@@ -867,7 +907,10 @@ class query(object):
 					        JOINERS.get(letter))
 
 			end = i.end()
-		return result + str[end:]
+		result += str[end:]
+		if stripdot and result.endswith('.'):
+		  return result[:-1]
+		return result
 
 	def dns_spf(self, domain):
 		"""Get the SPF record recorded in DNS for a specific domain
@@ -919,7 +962,7 @@ class query(object):
 		"""Get a list of IP addresses for all MX exchanges for a
 		domain name.
 		"""
-# draft-schlitt-spf-classic-02 section 5.4 "mx"
+# RFC 4408 section 5.4 "mx"
 # To prevent DoS attacks, more than 10 MX names MUST NOT be looked up
 		if self.strict:
 		  max = MAX_MX
@@ -1072,8 +1115,8 @@ def parse_mechanism(str, d):
 	>>> parse_mechanism('-exists:%{i}.%{s1}.100/86400.rate.%{d}','foo.com')
 	('-exists', '%{i}.%{s1}.100/86400.rate.%{d}', 32)
 
-	>>> parse_mechanism('mx::%%%_/.Claranet.de/27','foo.com')
-	('mx', ':%%%_/.Claranet.de', 27)
+	>>> parse_mechanism('mx:%%%_/.Claranet.de/27','foo.com')
+	('mx', '%%%_/.Claranet.de', 27)
 
 	>>> parse_mechanism('mx:%{d}/27','foo.com')
 	('mx', '%{d}', 27)