[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Commit configurability work for 0.0.5. (needs testing)
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv19199/lib/mixminion
Modified Files:
ClientMain.py Common.py Config.py Main.py test.py
Log Message:
Commit configurability work for 0.0.5. (needs testing)
- Add ability to disable directory paranoia (disabled by default on
cygwin, win32).
- Make directory paranoia messages more helpful.
- Change min,default,max public key overlap from (0,2,6) hours to (6,24,72)
hours.
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.92
retrieving revision 1.93
diff -u -d -r1.92 -r1.93
--- ClientMain.py 26 Jun 2003 03:23:53 -0000 1.92
+++ ClientMain.py 26 Jun 2003 17:43:27 -0000 1.93
@@ -1095,6 +1095,8 @@
#ShredCommand: rm -f
## Use this option to specify a nonstandard entropy source.
#EntropySource: /dev/urandom
+## Set this option to 'no' to disable permission checking
+#FileParanoia: yes
[DirectoryServers]
# Not yet implemented
Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.92
retrieving revision 1.93
diff -u -d -r1.92 -r1.93
--- Common.py 26 Jun 2003 03:23:53 -0000 1.92
+++ Common.py 26 Jun 2003 17:43:27 -0000 1.93
@@ -30,6 +30,7 @@
import signal
import stat
import statvfs
+import string
import sys
import threading
import time
@@ -42,6 +43,11 @@
del Queue
del Empty
+try:
+ import pwd, grp
+except ImportError:
+ pwd = grp = None
+
from types import StringType
class MixError(Exception):
@@ -64,6 +70,10 @@
"""Exception class for failed authentication to a server."""
pass
+class MixFilePermissionError(MixFatalError):
+ """Exception raised when a file has the wrong owner or permissions."""
+ pass
+
class UIError(MixError):
"""Exception raised for an error that should be reported to the user,
not dumped as a stack trace."""
@@ -161,6 +171,31 @@
else:
return "".join(pieces)
+def englishSequence(lst, empty="none"):
+ """Given a sequence of items, return the sequence formatted
+ according to ordinary English conventions of punctuation.
+
+ If the list is empty, the value of 'empty' will be returned."""
+
+ if len(lst) == 0:
+ return empty
+ elif len(lst) == 1:
+ return lst[0]
+
+ punc = ", "
+ for item in lst:
+ if "," in item or stringContains(item, " and "):
+ punc = "; "
+ break
+
+ if len(lst) == 2:
+ if punc == ", ":
+ return "%s and %s" % tuple(lst)
+ else:
+ return "%s; and %s" % tuple(lst)
+ else:
+ return "%s%sand %s" % (punc.join(lst[0:-1]), punc, lst[-1])
+
#----------------------------------------------------------------------
# Functions to generate and parse OpenPGP-style ASCII armor
@@ -266,6 +301,46 @@
raise MixFatalError("Unreachable code somehow reached.")
#----------------------------------------------------------------------
+
+#----------------------------------------------------------------------
+
+# A set of directories we've issued warnings about -- we won't check
+# them again.
+_WARNED_DIRECTORIES = {}
+# A set of directories that have checked out -- we won't check them again.
+_VALID_DIRECTORIES = {}
+# A list of user IDs
+_TRUSTED_UIDS = [ 0 ]
+
+# Flags: what standard Unix access controls should we check for?
+_CHECK_UID = 1
+_CHECK_GID = 1
+_CHECK_MODE = 1
+
+if sys.platform in ('cygwin', 'win32'):
+ # Under windows, we can't do anything sensible with permissions AFAICT.
+ _CHECK_UID = _CHECK_GID = _CHECK_MODE = 0
+elif os.environ.get("MM_NO_FILE_PARANOIA"):
+ _CHECK_UID = _CHECK_GID = _CHECK_MODE = 0
+
+def _uidToName(uid):
+ """Helper function: given a uid, return a username or descriptive
+ string."""
+ try:
+ return pwd.getpwuid(uid)[0]
+ except (KeyError, AttributeError):
+ # KeyError: no such pwent. AttributeError: pwd module not loaded.
+ return "user %s"%uid
+
+def _gidToName(gid):
+ """Helper function: given a gid, return a groupname or descriptive string
+ """
+ try:
+ return grp.getgrgid(gid)[0]
+ except (KeyError, AttributeError):
+ # KeyError: no such grpent. AttributeError: grp module not loaded.
+ return "group %s"%gid
+
def checkPrivateFile(fn, fix=1):
"""Checks whether f is a file owned by this uid, set to mode 0600 or
0700, and all its parents pass checkPrivateDir. Raises MixFatalError
@@ -285,12 +360,17 @@
if not os.path.isfile(fn):
raise MixFatalError("%s is not a regular file" % fn)
me = os.getuid()
- if st[stat.ST_UID] != me:
- raise MixFatalError("File %s must have owner %s" % (fn, me))
+ if _CHECK_UID and st[stat.ST_UID] != me:
+ ownerName = _uidToName( st[stat.ST_UID])
+ myName = _uidToName(me)
+ raise MixFilePermissionError(
+ "File %s is owned by %s, but Mixminion is running as %s"
+ % (fn, ownrerName, myName))
mode = st[stat.ST_MODE] & 0777
- if mode not in (0700, 0600):
+ if _CHECK_MODE and mode not in (0700, 0600):
if not fix:
- raise MixFatalError("Bad mode %o on file %s" % (mode & 0777, fn))
+ raise MixFilePermissionError("Bad mode %o on file %s"
+ % (mode & 0777, fn))
newmode = {0:0600,0100:0700}[(mode & 0100)]
LOG.warn("Repairing permissions on file %s" % fn)
os.chmod(fn, newmode)
@@ -304,14 +384,11 @@
try:
os.makedirs(d, 0700)
except OSError, e:
- raise MixFatalError("Unable to create directory %s: %s" % (d, e))
+ raise MixFatalError(
+ "Unable to create directory %s: %s" % (d, e))
checkPrivateDir(d)
-_WARNED_DIRECTORIES = {}
-_VALID_DIRECTORIES = {}
-_TRUSTED_UIDS = [ 0 ]
-
def checkPrivateDir(d, recurse=1):
"""Check whether d is a directory owned by this uid, set to mode
0700. All of d's parents must not be writable or owned by anybody but
@@ -330,11 +407,15 @@
st = os.stat(d)
# check permissions
- if st[stat.ST_MODE] & 0777 != 0700:
- raise MixFatalError("Directory %s must be mode 0700" % d)
+ if _CHECK_MODE and st[stat.ST_MODE] & 0777 != 0700:
+ raise MixFilePermissionError("Directory %s must be mode 0700" % d)
- if st[stat.ST_UID] != me:
- raise MixFatalError("Directory %s has must have owner %s" %(d, me))
+ if _CHECK_UID and st[stat.ST_UID] != me:
+ ownerName = _uidToName( st[stat.ST_UID])
+ myName = _uidToName(me)
+ raise MixFilePermissionError(
+ "Directory %s is owned by %s, but Mixminion is running as %s"
+ %(d,ownerName,myName))
if not recurse:
return
@@ -351,35 +432,34 @@
st = os.stat(d)
mode = st[stat.ST_MODE]
owner = st[stat.ST_UID]
- if owner not in trusted_uids:
- raise MixFatalError("Bad owner (uid=%s) on directory %s"
- % (owner, d))
- if (mode & 02) and not (mode & stat.S_ISVTX):
- raise MixFatalError("Bad mode (%o) on directory %s" %
- (mode&0777, d))
+ if _CHECK_UID and owner not in trusted_uids:
+ ownerName = _uidToName(owner)
+ trustedNames = map(_uidToName,trusted_uids)
+ raise MixFilePermissionError(
+ "Directory %s is owned by %s, but I only trust %s"
+ % (d, ownerName, englishSequence(trustedNames, "(nobody)")))
+ if _CHECK_MODE and (mode & 02) and not (mode & stat.S_ISVTX):
+ raise MixFilePermissionError(
+ "Bad permissions (mode %o) on directory %s" %
+ (mode&0777, d))
- if (mode & 020) and not (mode & stat.S_ISVTX):
+ if _CHECK_MODE and (mode & 020) and not (mode & stat.S_ISVTX):
# FFFF We may want to give an even stronger error here.
- if not _WARNED_DIRECTORIES.has_key(d):
+ if _CHECK_GID and not _WARNED_DIRECTORIES.has_key(d):
group = grp.getgrgid(st[stat.ST_GID])[0]
+ groupName = _gidToName(group)
LOG.warn("Directory %s is writable by group %s (mode %o)",
- d, group, mode&0777)
- _WARNED_DIRECTORIES[d] = 1
+ d, groupName, mode&0777)
+ _WARNED_DIRECTORIES[d] = 1
def configureTrustedUsers(config):
users = config['Host']['TrustedUser']
if not users:
return
+ if not _CHECK_UID:
+ return
- for u in users:
- u = u.strip()
- try:
- ent = pwd.getpwnam(u)
- except KeyError:
- LOG.warn("TrustedUser: No such user as %s", u)
- continue
-
- uid = ent[2]
+ for uid in users:
_TRUSTED_UIDS.append(uid)
#----------------------------------------------------------------------
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.49
retrieving revision 1.50
diff -u -d -r1.49 -r1.50
--- Config.py 25 Jun 2003 17:03:11 -0000 1.49
+++ Config.py 26 Jun 2003 17:43:27 -0000 1.50
@@ -56,6 +56,11 @@
import os
import re
import socket # for inet_aton and error
+try:
+ import pwd
+except ImportError:
+ pwd = None
+
from cStringIO import StringIO
import mixminion.Common
@@ -369,6 +374,17 @@
return os.path.expanduser(s)
+def _parseUser(s):
+ """Validation function. Matches a username or UID. Returns a UID."""
+ s = s.strip()
+ try:
+ return pwd.getpwnam(s)[2]
+ except (KeyError,AttributeError):
+ try:
+ return _parseInt(s)
+ except ConfigError:
+ raise ConfigError("Expected a user name or UID, but got %r",s)
+
#----------------------------------------------------------------------
# Regular expression to match a section header.
@@ -724,7 +740,8 @@
'Host' : { '__SECTION__' : ('ALLOW', None, None),
'ShredCommand': ('ALLOW', _parseCommand, None),
'EntropySource': ('ALLOW', _parseFilename, "/dev/urandom"),
- 'TrustedUser': ('ALLOW*', None, None),
+ 'TrustedUser': ('ALLOW*', _parseUser, None),
+ 'FileParanoia': ('Allow', _parseBoolean, "yes"),
},
'DirectoryServers' :
{ '__SECTION__' : ('REQUIRE', None, None),
Index: Main.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Main.py,v
retrieving revision 1.51
retrieving revision 1.52
diff -u -d -r1.51 -r1.52
--- Main.py 26 Jun 2003 03:23:53 -0000 1.51
+++ Main.py 26 Jun 2003 17:43:27 -0000 1.52
@@ -225,6 +225,7 @@
# command implementation code, we catch all UIError exceptions here.
commonModule = __import__('mixminion.Common', {}, {}, ['UIError'])
uiErrorClass = getattr(commonModule, 'UIError')
+ filePermissionErrorClass = getattr(commonModule, 'MixFilePermissionError')
# Read the module and function.
command_module, command_fn = _COMMANDS[args[1]]
@@ -242,6 +243,12 @@
func(commandStr, ["--help"])
except uiErrorClass, e:
e.dumpAndExit()
+ except filePermissionErrorClass, e:
+ print str(e)
+ print "(You can disable file permission checking by setting",
+ print "the MM_NO_FILE_PARANOIA"
+ print "environment variable."
+ sys.exit(1)
except KeyboardInterrupt:
print "Interrupted."
except ImportError, e:
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.124
retrieving revision 1.125
diff -u -d -r1.124 -r1.125
--- test.py 26 Jun 2003 03:19:29 -0000 1.124
+++ test.py 26 Jun 2003 17:43:27 -0000 1.125
@@ -4971,8 +4971,10 @@
self.assertEquals(vu, keyring.getNextKeyRotation())
# Check the second key we created.
- key2time = vu+3600
- va, vu, curKey = keyring._getLiveKeys(key2time)[0]
+ key2time = vu+3600*48
+ live = keyring._getLiveKeys(key2time)
+ self.assertEquals(len(live),1)
+ va, vu, curKey = live[0]
self.assertEquals(va, finish)
self.assertEquals(vu, mixminion.Common.previousMidnight(
finish+10*24*60*60+60))
@@ -5015,7 +5017,7 @@
# In case we started very close to midnight, remove keys as if it
# were a little in the future; otherwise, we won't remove the
# just-expired keys.
- keyring.removeDeadKeys(now+360)
+ keyring.removeDeadKeys(now+24*60*60)
self.assertEquals(3, len(keyring.keySets))
if USE_SLOW_MODE:
@@ -5875,7 +5877,7 @@
tc = loader.loadTestsFromTestCase
if 0:
- suite.addTest(tc(MMTPTests))
+ suite.addTest(tc(ServerKeysTests))
return suite
suite.addTest(tc(MiscTests))