(pure test commit)
[toast/findwwwritable.git] / findwwwritable.py
index 4ecc4b7..66eca6a 100755 (executable)
@@ -1,21 +1,41 @@
 #!/usr/bin/python
-# python 2.x is used
+# python 2.7+ is used
+
+# Copyright (C) 2011-2012 Philipp Spitzer, gregor herrmann, Stefan Stahl
+#
+# This file is part of findwwwritable.
+#
+# findwwwritable is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 2 of the License, or (at your option)
+# any later version.
+#
+# findwwwritable is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# findwwwritable.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
+import sys
 import stat
 from os.path import join
+import argparse
+import ConfigParser
 
 
-def collect_writable_dirs(rootdir, uids, gids):
-    """Returns a list of directories below rootdir (including rootdir) that are writable 
+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 +43,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)
@@ -56,20 +76,98 @@ def summarize_dirs(writable_dirs):
     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.3'
 
     # 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
+    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='additional configuration file')
+    parser.add_argument('-w', '--whitelist', help='filename of whitelist (default: {})'.format(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='*', help='directories are searched below basedir (default: {})'.format(basedir))
+    args = parser.parse_args()
+
+    # parse config file
+    config = ConfigParser.ConfigParser({'whitelist': whitelist_filename, 'full': '0'})
+    config_files = [config_filename, os.path.expanduser('~/.'+PROGNAME)]
+    if not args.config is None: config_files.append(args.config)
+    config.read(config_files)
+    if args.whitelist is None: args.whitelist = config.get(PROGNAME, 'whitelist')
+    if not args.full: args.full = config.getboolean(PROGNAME, 'full')
+    if args.uids is None:
+        if config.has_option(PROGNAME, 'uids'):
+            uids = config.get(PROGNAME, 'uids')
+            uids = uids.split(' ')
+            args.uids = map(int, uids)
+        else:
+            args.uids = uids
+    if args.gids is None:
+        if config.has_option(PROGNAME, 'gids'):
+            gids = config.get(PROGNAME, 'gids')
+            gids = gids.split(' ')
+            args.gids = map(int, gids)
+        else:
+            args.gids = gids
+    if len(args.basedir) == 0:
+        if config.has_option(PROGNAME, 'basedir'):
+            basedir = config.get(PROGNAME, 'basedir')
+            basedir = basedir.split(' ')
+            args.basedir = map(int, basedir)
+        else:
+            args.basedir = [basedir]
+
+    # read whitelist
+    whitelist = read_whitelist(args.whitelist)
 
     # collect and summarize writable directories
-    writable_dirs = collect_writable_dirs(rootdir, uids, gids)
-    writable_dirs = summarize_dirs(writable_dirs)
+    writable_dirs = []
+    for basedir in args.basedir:
+        writable_dirs.extend(collect_writable_dirs(basedir, args.uids, args.gids))
+    writable_dirs = sorted(set(writable_dirs))
+    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
 
-