Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/python
- '''queue_repair.py - qmail tools.
- '''
- __version__ = '0.9.0'
- __author__ = 'xXx'
- import sys
- import os
- from stat import *
- import string
- import pwd
- import grp
- import getopt
- #######################################
- # globals
- #######################################
- confqmail = '/var/qmail'
- wd = None
- testmode = 1
- checked_dir = {}
- checked_owner = {}
- checked_mode = {}
- #######################################
- # data
- #######################################
- users = {
- 'alias' : None,
- 'qmaild' : None,
- 'qmaill' : None,
- 'qmailp' : None,
- 'qmailq' : None,
- 'qmailr' : None,
- 'qmails' : None,
- }
- groups = {
- 'qmail' : None,
- 'nofiles' : None,
- }
- dirs = {
- # Directories to check; format is:
- # key: pathname - all paths are relative to conf-qmail
- # data: (user, group, mode, split)
- # split is: 0 : no, 1 : yes, -1 : only with big-todo
- 'queue' : ('qmailq', 'qmail', 0750, 0),
- 'queue/bounce' : ('qmails', 'qmail', 0700, 0),
- 'queue/info' : ('qmails', 'qmail', 0700, 1),
- 'queue/intd' : ('qmailq', 'qmail', 0700, -1),
- 'queue/local' : ('qmails', 'qmail', 0700, 1),
- 'queue/lock' : ('qmailq', 'qmail', 0750, 0),
- 'queue/mess' : ('qmailq', 'qmail', 0750, 1),
- 'queue/pid' : ('qmailq', 'qmail', 0700, 0),
- 'queue/remote' : ('qmails', 'qmail', 0700, 1),
- 'queue/todo' : ('qmailq', 'qmail', 0750, -1),
- }
- nondirs = {
- # Files to check; format is:
- # key: pathname - all paths are relative to conf-qmail
- # data: (user, group, mode)
- 'queue/lock/sendmutex' : ('qmails', 'qmail', 0600),
- 'queue/lock/tcpto' : ('qmailr', 'qmail', 0644),
- }
- #######################################
- # functions
- #######################################
- #######################################
- def primes(min, max):
- '''primes(min, max)
- Return a list of primes between min and max inclusive.
- '''
- result = []
- primelist = [2]
- if min <= 2:
- result.append(2)
- i = 3
- while i <= max:
- for p in primelist:
- if (i % p == 0) or (p * p > i): break
- if (i % p <> 0):
- primelist.append(i)
- if i >= min:
- result.append(i)
- i = i + 2
- return result
- #######################################
- def err(s, showhelp=0):
- '''err(s, showhelp=0)
- Write s + '\n' to stderr, optionally call show_help(), and exit.
- '''
- sys.stderr.write('%s\n' % s)
- if showhelp:
- show_help()
- if wd:
- os.chdir(wd)
- sys.exit(1)
- #######################################
- def msg(s):
- '''msg(s)
- Write s + '\n' to stdout.
- '''
- sys.stdout.write('%s\n' % s)
- #######################################
- def is_splitdir(is_split, bigtodo):
- '''is_splitdir(is_split, bigtodo)
- Return 0 if directory should not contain split subdirectories, 1 if it
- should.
- '''
- return (is_split == 1) or (is_split == -1 and bigtodo)
- #######################################
- def determine_users():
- '''determine_users()
- Look up UIDs and GIDs for all keys in globals users and groups which are
- not already set.
- '''
- global users, groups
- msg('finding qmail UIDs/GIDs...')
- us = users.keys()
- gs = groups.keys()
- for u in us:
- if users[u]:
- # Handle case of someone else determining UIDs for us
- msg(' %-7s preset as UID %i' % (u, users[u]))
- continue
- try:
- users[u] = pwd.getpwnam(u)[2]
- except KeyError:
- err('no uid for %s' % u)
- msg(' %-7s : UID %i' % (u, users[u]))
- for g in gs:
- if groups[g]:
- # Handle case of someone else determining GIDs for us
- msg(' %-7s preset as GID %i' % (g, groups[g]))
- continue
- try:
- groups[g] = grp.getgrnam(g)[2]
- except KeyError:
- err('no gid for %s' % g)
- msg(' %-7s : GID %i' % (g, groups[g]))
- #######################################
- def check_dir(path, user, group, mode):
- '''check_dir(path, user, group, mode)
- Verify path is an existing directory, that it owned by user:group, and
- that it has octal mode mode. If testmode is set, create path if it
- doesn't exist.
- '''
- if checked_dir.has_key(path):
- return
- msg(' checking directory %s...' % path)
- if not os.path.exists(path):
- msg(' directory %s does not exist' % path)
- if not testmode:
- os.makedirs(path, mode)
- else:
- if os.path.islink(path):
- msg(' %s is a symlink instead of directory' % path)
- if not testmode:
- os.unlink(path)
- if not os.path.isdir(path):
- msg(' %s is not a directory' % path)
- if not testmode:
- os.unlink(path)
- chown(path, user, group)
- chmod(path, mode)
- checked_dir[path] = None
- #######################################
- def chown(path, user, group):
- '''chown(path, user, group)
- Verify path is owned by user:group, and make it so if testmode is not set.
- '''
- if checked_owner.has_key(path):
- return
- uid = users[user]
- gid = groups[group]
- try:
- s = os.stat(path)
- if s[ST_UID] != uid or s[ST_GID] != gid:
- msg(' %s ownership %i:%i, should be %s:%s' % (path,
- s[ST_UID], s[ST_GID], user, group))
- if not testmode:
- os.chown(path, uid, gid)
- s = os.stat(path)
- msg(' fixed, %s ownership %i:%i' % (path, s[ST_UID], s[ST_GID]))
- else:
- msg(' testmode, not fixing')
- except OSError, o:
- err(o or '[no error message]')
- checked_owner[path] = None
- #######################################
- def chmod(path, mode):
- '''chmod(path, mode)
- Verify path has mode mode, and make it so if testmode is not set.
- '''
- if checked_mode.has_key(path):
- return
- try:
- s = os.stat(path)
- curmode = S_IMODE(s[ST_MODE])
- if curmode != mode:
- msg(' %s is mode %o, should be %o' % (path, curmode, mode))
- if not testmode:
- os.chmod(path, mode)
- s = os.stat(path)
- newmode = S_IMODE(s[ST_MODE])
- msg(' changed %s mode to %o' % (path, newmode))
- else:
- msg(' testmode, not fixing')
- except OSError, o:
- err(o or '[no error message]')
- checked_mode[path] = None
- #######################################
- def determine_split():
- '''determine_split()
- Return probable conf-split value of queue based on contents.
- '''
- splits = []
- msg('determining conf-split...')
- for (path, (user, group, mode, is_split)) in dirs.items():
- if is_split != 1:
- continue
- highest = 0
- contents = os.listdir(path)
- for item in contents:
- p = os.path.join(path, item)
- if os.path.islink(p):
- msg(' found unexpected symlink %s' % p)
- continue
- if not os.path.isdir(p):
- msg(' found unexpected non-directory %s' % p)
- continue
- try:
- i = int(item)
- except ValueError:
- msg(' found unexpected directory %s' % p)
- continue
- if i > highest:
- highest = i
- splits.append(highest)
- split = splits[0]
- for i in splits[1:]:
- if i != split:
- err(' not all subdirectories split the same; use --split N to force')
- # First split directory is '0'
- split = split + 1
- msg(' conf-split appears to be %i' % split)
- return split
- #######################################
- def determine_bigtodo(split):
- '''determine_bigtodo(split)
- Return 1 if big-todo appears to be in use based on contents of queue,
- 0 otherwise.
- '''
- splits = []
- bigtodo = 0
- msg('determining big-todo...')
- for i in range(split):
- p = os.path.join('queue/todo', str(i))
- if os.path.islink(p):
- msg(' found unexpected symlink %s' % p)
- elif os.path.isdir(p):
- splits.append(i)
- elif not os.path.exists(p):
- # big-todo probably not in use
- pass
- else:
- msg(' found unexpected direntry %s' % p)
- if splits == range(split):
- # big-todo apparently in use
- bigtodo = 1
- msg(' big-todo found')
- elif splits:
- # big-todo in use, but doesn't match split
- err(' todo split != split; if using --split N, use --bigtodo to force')
- else:
- msg(' big-todo not found')
- return bigtodo
- #######################################
- def check_dirs(paths, split, bigtodo):
- '''check_dirs(paths, split, bigtodo)
- Verify ownership, mode, and contents of each queue directory in paths.
- '''
- msg('checking main queue directories...')
- _dirs = paths.keys()
- _dirs.sort()
- for path in _dirs:
- (user, group, mode, is_split) = paths[path]
- check_dir(path, user, group, mode)
- msg('checking split sub-directories...')
- for (path, (user, group, mode, is_split)) in paths.items():
- if path in ('queue', 'queue/lock'):
- # Nothing in these directories to check at this point
- continue
- this_split = is_splitdir(is_split, bigtodo)
- if not this_split:
- splits = []
- else:
- splits = range(split)
- for i in splits:
- splitpath = os.path.join(path, str(i))
- check_dir(splitpath, user, group, mode)
- try:
- contents = os.listdir(path)
- except OSError:
- # Directory missing
- if testmode:
- continue
- err('bug -- directory %s missing, should exist by now' % path)
- for item in contents:
- p = os.path.join(path, item)
- if this_split:
- if (is_split == -1) and os.path.isfile(p):
- # Found possible file in path while converting queue to
- # big-todo
- try:
- i = int(item)
- if not testmode:
- # Move to '0' split subdirectory; will be
- # fixed later by check_hash_and_ownership
- new_p = os.path.join(path, '0', item)
- msg(' moving %s to %s' % (p, new_p))
- os.rename(p, new_p)
- except ValueError:
- # Not a message file
- msg(' found unexpected file %s' % p)
- continue
- # This directory should contain only split subdirectories
- if not os.path.isdir(p):
- msg(' found unexpected direntry %s' % p)
- continue
- try:
- i = int(item)
- if i not in splits:
- msg(' found unexpected split subdirectory %s' % p)
- if not testmode:
- files = os.listdir(p)
- for f in files:
- # Move any files in this to-be-remove split subdir
- # into the 0 splitdir. Will be moved into the
- # proper split subdir later by
- # check_hash_and_ownership().
- filep = os.path.join(p, f)
- msg(' preserving file %s' % filep)
- os.rename(filep, os.path.join(path, '0', f))
- os.removedirs(p)
- except ValueError:
- msg(' found unexpected direntry %s' % p)
- else:
- # This directory should contain only files
- if os.path.isdir(p):
- msg(' found unexpected directory %s' % p)
- try:
- i = int(item)
- except ValueError:
- msg(' %s not a split subdirectory; ignoring' % p)
- continue
- msg(' %s is a split subdirectory; %s should not be split' % (p, path))
- if not testmode:
- savefiles = os.listdir(p)
- if savefiles:
- msg(' moving files from %s to %s' % (p, path))
- for f in savefiles:
- os.rename(os.path.join(p, f), os.path.join(path, f))
- os.rmdir(p)
- elif not os.path.isfile(p):
- msg(' found unexpected direntry %s; ignoring' % p)
- continue
- else:
- # Found file
- pass
- #######################################
- def check_files(paths):
- '''check_files(paths)
- Verify ownership and mode of each queue file in paths.
- '''
- msg('checking files...')
- for (path, (user, group, mode)) in paths.items():
- if os.path.exists(path):
- if not os.path.isfile(path):
- msg(' %s is not a file' % path)
- if not testmode:
- os.unlink(path)
- else:
- msg(' file %s does not exist' % path)
- if not os.path.exists(path) and not testmode:
- open(path, 'w')
- chown(path, user, group)
- chmod(path, mode)
- #######################################
- def check_trigger():
- '''check_trigger()
- Verify ownership, mode, and inode type of trigger fifo.
- '''
- path = 'queue/lock/trigger'
- user = 'qmails'
- group = 'qmail'
- if not os.path.exists(path):
- msg(' %s missing' % path)
- else:
- if os.path.islink(path):
- msg(' %s is a symlink instead of fifo' % path)
- if not testmode:
- os.unlink(path)
- else:
- mode = os.stat(path)[ST_MODE]
- if not S_ISFIFO(mode):
- msg(' %s not a fifo' % path)
- if not testmode:
- os.unlink(path)
- if not os.path.exists(path) and not testmode:
- os.mkfifo(path)
- chown(path, user, group)
- chmod(path, 0622)
- #######################################
- def check_messages(path, split):
- '''check_messages(path, split)
- Return list of files found under path which are not named after their
- inode number.
- '''
- misnamed = []
- msg('checking queue/mess files...')
- for i in range(split):
- messdir = os.path.join(path, str(i))
- try:
- contents = os.listdir(messdir)
- except OSError:
- continue
- for f in contents:
- p = os.path.join(messdir, f)
- if os.path.islink(p):
- msg(' found unexpected symlink %s' % p)
- continue
- elif not os.path.isfile(p):
- msg(' found unexpected non-file %s' % p)
- continue
- try:
- filenum = int(f)
- except ValueError:
- msg(' found unexpected file %s' % p)
- continue
- s = os.stat(p)
- inode = s[ST_INO]
- if filenum == inode:
- continue
- # Found mess file not named after inode
- msg(' %s is inode %i' % (p, inode))
- # Will be fixed by fix_inode_names()
- misnamed.append((i, filenum, inode))
- return misnamed
- #######################################
- def fix_inode_names(paths, split, bigtodo, misnamed):
- '''fix_inode_names(paths, split, bigtodo, misnamed)
- For each path in paths, correct file names based on results of
- check_messages(). Correct split sub-directory location as well.
- '''
- msg('fixing misnamed messages...')
- for (path, (user, junk, junk, is_split)) in paths.items():
- for (oldhash, oldno, newno) in misnamed:
- if not is_splitdir(is_split, bigtodo):
- old_p = os.path.join(path, str(oldno))
- new_p = os.path.join(path, str(newno))
- else:
- old_p = os.path.join(path, str(oldhash), str(oldno))
- new_p = os.path.join(path, str(newno % split), str(newno))
- if os.path.exists(old_p):
- if os.path.islink(old_p):
- msg(' found unexpected symlink %s' % old_p)
- continue
- if not os.path.isfile(old_p):
- msg(' found unexpected direntry %s' % old_p)
- continue
- msg(' %s should be %s' % (old_p, new_p))
- if not testmode:
- os.rename(old_p, new_p)
- msg(' fixed')
- #######################################
- def check_hash_and_ownership(paths, split, bigtodo):
- '''check_hash_and_ownership(paths, split, bigtodo)
- For each path in paths, correct file ownership, mode, and split subdirectory
- of all files found.
- '''
- msg('checking split locations...')
- for (path, (user, group, junk, is_split)) in paths.items():
- if path in ('queue', 'queue/lock'):
- # Nothing in these directories to check at this point
- continue
- elif path in ('queue/mess', 'queue/todo'):
- mode = 0644
- else:
- mode = 0600
- this_split = is_splitdir(is_split, bigtodo)
- if this_split:
- splits = range(split)
- else:
- splits = ['']
- for splitval in splits:
- _dir = os.path.join(path, str(splitval))
- try:
- contents = os.listdir(_dir)
- except OSError:
- if not testmode:
- err('bug -- directory %s missing, should exist by now' % _dir)
- continue
- for f in contents:
- old_p = os.path.join(_dir, f)
- try:
- if not os.path.isfile(old_p):
- raise ValueError
- j = int(f)
- except ValueError:
- msg(' found unexpected direntry %s; ignoring' % old_p)
- continue
- # Check ownership and mode
- chown(old_p, user, group)
- chmod(old_p, mode)
- if not this_split:
- continue
- # Check whether file is in correct split sub-directory
- hashv = j % split
- if hashv != splitval:
- # message in wrong split dir
- new_p = os.path.join(path, str(hashv), f)
- msg(' %s should be %s' % (old_p, new_p))
- if not testmode:
- os.rename(old_p, new_p)
- # Ensure ownership and mode
- chown(new_p, user, group)
- chmod(new_p, mode)
- msg(' fixed')
- #######################################
- def get_current_messages(split):
- '''get_current_messages(split)
- Return list of all message files under queue/mess.
- '''
- messages = []
- msg('finding current messages...')
- for i in range(split):
- path = os.path.join('queue/mess', str(i))
- try:
- contents = os.listdir(path)
- except OSError:
- continue
- for item in contents:
- try:
- messages.append(int(item))
- except ValueError:
- if testmode:
- pass
- else:
- msg(' found unexpected direntry %s' % os.path.join(path, item))
- messages.sort()
- msg(' found %i messages' % len(messages))
- return messages
- #######################################
- def check_queue(qmaildir=confqmail, test=1, force_split=None, force_bigtodo=None, force_create=0, mathishard=0):
- '''check_queue(qmaildir=confqmail, test=1, force_split=None, force_bigtodo=None, force_create=0, mathishard=0)
- Verify (and correct if test is not set) queue structure rooted at
- qmaildir/queue.
- Determine conf-split automatically if force_split is not set.
- Determine if big-todo is in use automatically if force_bigtodo is not set.
- '''
- global wd
- global testmode
- testmode = test
- split = None
- wd = os.getcwd()
- try:
- os.chdir(qmaildir)
- except StandardError:
- err('failed to chdir to %s' % qmaildir)
- if testmode:
- msg('running in test-only mode')
- else:
- msg('running in repair mode')
- determine_users()
- if not force_split:
- try:
- split = determine_split()
- except OSError:
- msg('basic queue directories not found at %s' % qmaildir)
- if not split:
- if not force_create:
- err(' use --create to force creation of queue at %s' % qmaildir)
- # --create implies --repair
- testmode = 0
- if not force_split:
- err('if creating a new queue, you must supply a conf-split value with --split')
- split = int(force_split)
- if split < 1:
- err('split must be >= 1')
- if not force_bigtodo:
- err('if creating a new queue, you must supply either --bigtodo or --no-bigtodo')
- msg('using forced conf-split of %i' % split)
- msg('creating new queue at %s' % qmaildir)
- l = int(split * 0.8)
- h = int(split * 1.2)
- suggested_splits = primes(l, h)
- if not split in suggested_splits:
- msg('split should be prime, not %i: suggestions %s' % (split, suggested_splits))
- if not mathishard and not testmode:
- err(' use --i-want-a-broken-conf-split to force non-prime split')
- if force_bigtodo == 1:
- bigtodo = 1
- msg('using forced big-todo')
- elif force_bigtodo == -1:
- bigtodo = 0
- msg('using forced non-big-todo')
- else:
- bigtodo = determine_bigtodo(split)
- check_dirs(dirs, split, bigtodo)
- check_files(nondirs)
- check_trigger()
- misnamed = check_messages('queue/mess', split)
- # Handle misnamed files in directories
- if misnamed:
- fix_inode_names(dirs, split, bigtodo, misnamed)
- # Handle mis-hashed files and bad owner/group/mode
- check_hash_and_ownership(dirs, split, bigtodo)
- #######################################
- def show_help():
- '''show_help()
- Display usage information.
- '''
- msg('\n'
- 'Usage: queue_repair.py [options] [conf-qmail]\n')
- msg('Options:\n'
- ' conf-qmail (default: %s)' % confqmail)
- msg(
- ' -t or --test Test only; do not modify the filesystem\n'
- ' -r or --repair Repair errors found (default: test)\n'
- ' -b or --bigtodo Force use of big-todo (default: auto)\n'
- ' -n or --no-bigtodo Force non-use of big-todo (default: auto)\n'
- ' -s N or --split N Force conf-split of N (default: auto)\n'
- ' -c or --create Force creation of queue (default: no)\n'
- ' --i-want-a-broken-conf-split Force non-prime conf-split (default: no)\n'
- ' -h or --help This text\n'
- )
- #######################################
- def main():
- '''main()
- Parse options and call check_queue().
- '''
- msg('queue_repair.py v. %s\n'
- 'Copyright (C) 2001 %s' % (__version__, __author__))
- msg('Licensed under the GNU General Public License version 2\n')
- optionlist = 's:bnrthc'
- longoptionlist = ('split=', 'bigtodo', 'no-bigtodo', 'repair', 'test',
- 'i-want-a-broken-conf-split', 'help', 'create')
- force_split = None
- force_bigtodo = None
- test = 1
- qmaildir = confqmail
- mathishard = 0
- create = 0
- try:
- options, args = getopt.getopt(sys.argv[1:], optionlist, longoptionlist)
- for (option, value) in options:
- if option in ('-s', '--split'):
- try:
- force_split = int(value)
- if force_split < 1:
- raise ValueError
- except ValueError:
- raise getopt.error, 'split value must be a positive integer (%s)' % value
- elif option in ('-n', '--no-bigtodo'):
- force_bigtodo = -1
- elif option in ('-b', '--bigtodo'):
- force_bigtodo = 1
- elif option in ('-r', '--repair'):
- test = 0
- elif option in ('-t', '--test'):
- test = 1
- elif option == '--i-want-a-broken-conf-split':
- mathishard = 1
- elif option in ('-h', '--help'):
- show_help()
- sys.exit(0)
- elif option in ('-c', '--create'):
- create = 1
- if args:
- if len(args) > 1:
- raise getopt.error, 'conf-qmail must be a single argument (%s)' % string.join(args)
- qmaildir = args[0]
- except getopt.error, o:
- err('Error: %s' % o, showhelp=1)
- check_queue(qmaildir, test, force_split, force_bigtodo, create, mathishard)
- #######################################
- if __name__ == '__main__':
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement