Added the argparse module to be able to use command line arguments.
[toast/findwwwritable.git] / findwwwritable.py
index 0408caafdd68820830fff95ef9cad9e016070aaa..efd77ad96826356948c94f47cbe95566a5439f89 100755 (executable)
@@ -1,21 +1,22 @@
 #!/usr/bin/python
-# python 2.x is used
+# python 2.7+ is used
 
 import os
 import stat
 from os.path import join
+import argparse
 
 
-def collect_writable_dirs(rootdir, uids, gids):
-    """Returns a list of directories below rootdir (including rootdir) that are writeable 
-    by the users with the given uids or gids or that are world writeable.
+def collect_writable_dirs(basedir, uids, gids):
+    """Returns a list of directories below basedir (including basedir) that are writable
+    by the users with the given uids or gids or that are world writable.
     Normally, uid(s) is the user id of the apache user (e.g. www-data) and gids is a list
     of group ids this user is member of.
     
-    :param rootdir: string. directory where the search should start at
+    :param basedir: string. directory where the search should start at
     :param uids: list of integer user ids
     :param gids: list of integer group ids"""
-    assert isinstance(rootdir, str)
+    assert isinstance(basedir, str)
     assert isinstance(uids, list)
     for uid in uids: assert isinstance(uid, int)
     assert isinstance(gids, list)
@@ -23,7 +24,7 @@ def collect_writable_dirs(rootdir, uids, gids):
 
     writable_dirs = [] # dirs with write permissions - this list is filled by this function
 
-    for root, dirs, files in os.walk(rootdir):
+    for root, dirs, files in os.walk(basedir):
         for d in dirs:
             dp = join(root, d) # dp is the dir with path
             s = os.lstat(dp)
@@ -41,9 +42,9 @@ def collect_writable_dirs(rootdir, uids, gids):
 
 def summarize_dirs(writable_dirs):
     """Takes a list of directories and omits each subdirectory if its parent directory
-    is also included in the list. This list is modified "in place" (nothing is returned).
+    is also included in the list. The modified list is returned.
 
-    :param writeable_dirs: List of directories (strings)."""
+    :param writable_dirs: List of directories (strings)."""
     writable_dirs = sorted(writable_dirs)
 
     i = 0
@@ -53,20 +54,73 @@ def summarize_dirs(writable_dirs):
         else:
             i += 1
 
+    return writable_dirs
+
+
+def read_whitelist(whitelist_filename):
+    """Reads the given whitelist (one directory name per line) and returns it as list.
+    Empty lines are omitted. Lines beginning with # are omitted as well.
+    If the file does not exist, it returns an empty list."""
+    whitelist = []
+    try: file = open(whitelist_filename, 'r')
+    except IOError: return []
+    for line in file:
+        line = line.strip()
+        if len(line) == 0: continue
+        if line[0] == '#': continue
+        whitelist.append(line)
+    file.close()
+    return sorted(set(whitelist))
+
+
+def apply_whitelist(writable_dirs, whitelist):
+    """Removes all directories that are contained in the list whitelist from the list writable_dirs.
+    It returns the modified writable_dirs.
+
+    :param writable_dirs: List of directories
+    :param whitelist: List of directories that should be removed from writable_dirs.
+    :return: list of writable directories."""
+    return sorted(list(set(writable_dirs).difference(whitelist)))
+
 
 
 if __name__ == '__main__':
+    # constants
+    PROGNAME = 'findwwwritable'
+    VERSION = '0.0.2'
 
     # variables
     uids = [33]                # user ids of the user whos write permissions should be found
     gids = [33, 42, 121, 127]  # group ids of the user whos write permissions should be found
-    rootdir = '/home'          # directory where the seach is started
-
-    # collect and summarize writeable directories
-    writable_dirs = collect_writable_dirs(rootdir, uids, gids)
-    summarize_dirs(writable_dirs)
-
-    # print writeable directories
+    basedir = '/home'          # directory where the seach is started
+    config_filename = '/etc/findwwwritable/config'
+    whitelist_filename = '/etc/findwwwritable/whitelist' # list of directories that are known to be writable
+                                                         # and that should not be reported.
+
+    # parse command line arguments
+    parser = argparse.ArgumentParser(description='find directories that are writeable by www-data (or an other user)')
+    parser.add_argument('-v', '--version', action='version', version='{} {}'.format(PROGNAME, VERSION))
+    parser.add_argument('-c', '--config', help='configuration file (default: {})'.format(config_filename), default=config_filename)
+    parser.add_argument('-w', '--whitelist', help='filename of whitelist (default: {})'.format(whitelist_filename), default=whitelist_filename)
+    parser.add_argument('-f', '--full', help='do not omit subdirs', action='store_true')
+    parser.add_argument('-u', '--uid', dest='uids', help='system uid of the user. may be specified more than once. (default: {})'.format(uids), action='append', type=int)
+    parser.add_argument('-g', '--gid', dest='gids', help='system gid of the user. may be specified more than once. (default: {})'.format(gids), action='append', type=int)
+    parser.add_argument('basedir', nargs='*', default=basedir, help='directories are searched below basedir (default: {})'.format(basedir))
+    args = parser.parse_args()
+    if args.uids is None: args.uids = uids
+    if args.gids is None: args.gids = gids
+
+    # read whitelist
+    whitelist = read_whitelist(args.whitelist)
+
+    # collect and summarize writable directories
+    writable_dirs = []
+    for basedir in args.basedir:
+        writable_dirs.extend(collect_writable_dirs(basedir, args.uids, args.gids))
+    if not args.full: writable_dirs = summarize_dirs(writable_dirs)
+    writable_dirs = apply_whitelist(writable_dirs, whitelist)
+
+    # print writable directories
     for d in writable_dirs:
         print d