#!/usr/bin/env python

'''
Checks project permissions and php modules.

This script is designed to run AFTER make_project is executed.
'''

import functools, argparse
import grp, pwd
import os, socket, sys
import subprocess
import shlex

#-----------------------------------------------------------
# Functions
def gethostname():
   try:
      return socket.getfqdn().split('.')[0]
   except:
      return 'localhost'
#gethostname

def check_user(user):
   try:
      mypwdb = pwd.getpwnam(user)
   except KeyError as e:
      print("ERROR: ", e)
      print("ERROR: Specified apache user (%s) not found, Aborting"%(user))
      sys.exit(1)
   #try
# check_user

def check_dir(dir):
   if not os.path.isdir(dir):
      print("ERROR: project directory %s does not exist, Aborting"%(dir))
      sys.exit(1)
   #try
   return dir
#check_dir

def determineapacheuser(user):

   '''Attempts to determine the apache Web server user from the password
   database. Checks a few possibilities that are linux defaults. If
   one user matches, it is returned, otherwise an error created.

   Known Issue: if the system has multiple users defined from the list below,
   the script will abort.

   '''
   possibleapacheusers = [ 'apache',
                           'apache2',
                           'http',
                           'httpd',
                           'web-data',
                           'www-data']

   if user=="FINDOUT":
      allusernames = [x.pw_name for x in pwd.getpwall()]
      out1 = set(allusernames).intersection(possibleapacheusers)

      if len(out1)==1:
         return out1.pop()

      print("WARNING: Script could not determine which user runs apache Web server. Aborting")
      print("         You should specify the apache user with the -a/--apache_user flag")
      print("         Try running the following command with apache Web server running:")
      print("           ps -ef | grep -E '(httpd|apache2|apache)' | grep -v `whoami` | grep -v root")
      sys.exit(1)
   else:
      check_user(user)
      return user
   #endif
#determineapacheuser

def check_permissions(user, fpath):

   mypwdb = pwd.getpwnam(user)

   try:
      mystat = os.stat(fpath)
   except OSError as e:
      print("ERROR: ", e)
      return 1
   #try

   owner_permissions = oct(mystat.st_mode)[4:5]
   group_permissions = oct(mystat.st_mode)[5:6]

   file_owner = pwd.getpwuid(mystat.st_uid).pw_name
   group_members = grp.getgrgid(mystat.st_gid).gr_mem

   '''Three checks:
   1) if file owner is the user and owner's permissions allow for writing
   2) if user is in group_members and group's permissions allow for writing
   3) if user's gid is the same as the file's gid, and group's permissions allow for writing
      The last condition is required because group_members (from gr_mem) does not
      contain the primary user of the group.
   '''

   if (file_owner == user and owner_permissions >=5) or (user in group_members and group_permissions >=5) or (mypwdb.pw_gid==mystat.st_gid and group_permissions >=5) :
      print("Info:  User %s can write to file/dir %s"%(user, fpath))
      return 0
   #end if

   print("WARNING: User %s can NOT write to %s"%(user, fpath))
   return 2
#check_permissions


def check_php_modules(modulestocheck):
   '''Uses php -m and checks to see if certain php modules are installed.
   '''

   phpcmd = ['php','-m']
   try:
      phpout = subprocess.check_output(phpcmd)
   except Exception as e:
      print("ERROR: ",e)
      print("ERROR: Can't execute %s"%(" ".join(phpcmd)))
   #try
   phpout = str(phpout)
   phpmodules = phpout.split("\n")

   modulesnotfound = set(modulestocheck).difference(phpmodules)

   if len(modulesnotfound)>0:
      print("WARNING: The following php modules were not found on your system: %s"%(" ".join(modulesnotfound)))
      return 1

   print("Info:  All required/recommended php modules found.")
   return 0

#check_php_modules


# Checks the given command if it is executable
# can also check output (not implemented yet)
def check_tool(command):

    # make sure command is a valid list to pass to subprocess.call()
    if not isinstance(command, list):
        command = shlex.split(command)

    try:
        cmdout = subprocess.check_output(command,stderr=subprocess.STDOUT)
    except OSError as e:
        print("WARNING: The following tool was not found on your system: %s"%(command[0]))
        if e.errno != os.errno.ENOENT:
            print("ERROR: ",e)
            print("ERROR: Can't execute %s"%(" ".join(command)))
        return 1
    except subprocess.CalledProcessError as cpe:
        print("WARNING: The following tool reported a non-zero returncode: %s"%(" ".join(command)))
        return 2

    return 0
#check_tool

#-----------------------------------------------------------
# Main program

parser = argparse.ArgumentParser(description="Checks BOINC project directory permissions and php modules.")
parser.add_argument("-p","--project_dir", action="store", dest="project_dir", required=True, help="Full path to the project root directory.")
parser.add_argument("-a","--apache_user", action="store", dest="apache_user", default="FINDOUT", help="User which apache runs. Typically www-data (Debian based), httpd (Redhat based), or apache. If not specified, the script will automatically try to determine.")

try:
   args = parser.parse_args()
except Exception as e:
   print("ERROR: command-line parser error", e)
   sys.exit(1)
#try

# Define global variables
S_HOSTNAME  = gethostname()
APACHE_USER = determineapacheuser(args.apache_user)
PROJECT_DIR = check_dir(args.project_dir)
PATHS = ['log_'+S_HOSTNAME,
         'html',
         'html/cache',
         'html/inc',
         'html/languages',
         'html/languages/compiled',
         'upload',]
MODULES = ["curl","gd","mysqli","xml"]
TOOLS = ['curl --version']

# Run the check_permissions function on all directories
rcperm = map(functools.partial(check_permissions, APACHE_USER), [os.path.join(PROJECT_DIR,p) for p in PATHS])

# Check for php modules
rcphp = check_php_modules(MODULES)

# Check for tools
rctools = map(check_tool,TOOLS)

if sum(rcperm)+rcphp+sum(rctools) == 0:
   print("---All permissions checks passed, necessary php modules and tools installed. Your BOINC project should be ready to start.---")
else:
   print("WARNING: Some problems were found with your BOINC installation, please check the messages above for details.")

sys.exit(0)
