#!/usr/bin/python
"""
    Provides usage stats for all cpanel users on a box

    Copyright  Afrihost.com (PTY) Ltd 2004
    Original file by: Ed Pascoe <ed@pascoe.co.za>
"""
__author__  = "Ed Pascoe <ed@pascoe.co.za>"
__credits__ = "Afrihost.com -- The best webhosting. Ever."


import os.path, sys, os,stat, time
import optparse
import pwd
import glob
import smtplib
import StringIO
import fcntl
import tempfile
import random
import socket

if __name__=='__main__': sys.path.append(os.path.dirname(os.path.realpath(sys.argv[0]))+'/../../pylib')

    
class CpanelUsageDetails:
    cpanel_dir="/var/cpanel/users"
    hostname=""
    def __init__(self):
        f=os.popen("hostname","r")
        self.hostname=f.read()
        self.hostname=self.hostname.strip()
        f.close()
        self.output=sys.stdout
    def homedirectory(self,user):
        """Find the users homedirectory"""
        try:
            u=pwd.getpwnam(user)
            uh=u[5]  #Look up the users homedirectory
            return uh
        except:
            if os.path.exists("/home/%s" % (user)): #User exists but not in passwd file
                return "/home/%s" % (user)
            else: return None #NO home directory
    def newestfile(self,dirname):
        """Find the youngest file in the given directory"""
        newestfile=None
        newtime=0
        try:
            for root, dirs, files in os.walk(dirname):
                for fname in files:
                    if fname[0]==".": continue 
                    fpath=os.path.join(root,fname)
                    if fpath.find('/tmp/') > -1: continue  #Skip webalizer stuff
                    if os.path.isfile:
                        mtime=os.stat(fpath)[stat.ST_MTIME]
                        if mtime > newtime:
                            newtime=mtime
                            newestfile=fpath
            if not newestfile: return False
            return time.strftime("%Y-%m-%d",time.localtime(newtime))
        except Exception:
            return time.strftime("%Y-%m-%d",time.localtime(0))
    def homeusage(self,user):
        """Find out how much space the users home directory takes up"""
        sign=""
        try:
            u=pwd.getpwnam(user)
            uh=u[5]  #Look up the users homedirectory
            if not os.path.exists(uh): raise ("no home directory")
        except:
            if os.path.exists("/home/%s" % (user)): #User exists but not in passwd file
                sign="-"
                uh="/home/%s" % (user)
            else: return "0" #NO home directory
        f=os.popen("du -sk '%s' | cut -f 1" % (uh),"r")  # andrew was here, removing -skL because of symbolic link farms in home dirs!
        usage=f.read()
        usage=usage.strip()
        f.close()
        return sign+usage
    def __userread(self,user):
        """Reads in cpanel data for the given user and returns as a dict
           Will append cpanel_dir if user is not a filename
        """
        data={}
        if user.find("/") == -1: user=os.path.join(self.cpanel_dir,user)
        f=open(user,"r");
        for line in f.readlines():
            if line.find("=") < 0 : continue
            line=line.strip()
            k,v=line.split("=")
            data[k]=v
        return data
    def sqlusage(self,user):
        """Find out how much disk space the user mysql databases take up"""
        total=0
        count=0
        if os.path.exists("/var/db/mysql"):
            mysql="/var/db/mysql"
        elif os.path.exists("/var/lib/mysql"):
            mysql="/var/lib/mysql"
        else: return total
        sqllist=glob.glob("%s/%s_*" % (mysql,user))
        sqllist.append(os.path.join(mysql,user))
        for f in  sqllist:
            if not os.path.exists(f): continue
            count+=1
            try:
                f=os.popen("du -skL '%s' | cut -f 1" % (f),"r")
                usage=f.read()
                usage=int(usage.strip())
                f.close()
            except: continue
            total += usage
        return (total,count)
    def sitestatus(self,user):
        """Check to see if user is suspended"""
        if os.path.exists(os.path.join("/var/cpanel/suspended",user)):
            return "suspended"
        else: return "active"
    def linecount(self,fname):
        """Returns number of lines in a file"""
        if not os.path.exists(fname): return 0
        try: f=open(fname,"r")
        except: return -1
        count=0
        for l in f:
            count+=1
        f.close()
        return count
    def emailusage(self,user,dns):
        hd=self.homedirectory(user)
        try: emailcount=self.linecount(os.path.join(hd,"etc",dns,"passwd"))
        except: emailcount=0
        try: aliascount=self.linecount(os.path.join("/etc/valiases",dns))
        except: aliascount=0 
        return (emailcount,aliascount)
    def run(self):
        self.output.write("hostname,dns,username,owner,plan,homeusage,sqlusage,sqlcount,emailcount,aliascount,status,mostrecent\n")
        count=0
        ulist=os.listdir("/var/cpanel/users")
        ulist.sort()
        for f in ulist:
            count+=1
            #if count > 30: return
            row=[]
            data=self.__userread(f)
            try:
                row.append(self.hostname)
                row.append(data['DNS'])
                row.append(f)
                row.append(data['OWNER'])
                row.append(data['PLAN'])
            except:
                #sys.stderr.write("Failure on user '%s', skipping\n" % (f))
                continue
            hu=self.homeusage(f) ; row.append(hu)
            sql,sqlcount=self.sqlusage(f) 
            row.append(sql)
            row.append(sqlcount)
            emailcount,aliascount=self.emailusage(f,data['DNS'])
            row.append(emailcount)
            row.append(aliascount)
            susp=self.sitestatus(f); row.append(susp)
            mostrecent=self.newestfile(self.homedirectory(f))
            row.append(mostrecent)
            for i in range(0,len(row)): row[i]=str(row[i]) #Convert everything to a string
            self.output.write(",".join(row))
            self.output.write("\n")
    def installCron(self,cmdname=None,comment="Generate hosting account statistics",runhour=None):
        """Make sure this program runs daily in the cron
           If cmdname is not specified will use python __name__ as the script to run
           comment is the comment to place in the cron file 
           runhour is the hour to start (random by default)
        """
        #Install into daily cron
        if not cmdname: 
            if __name__=='__main__':n=os.path.realpath(sys.argv[0])
            else: n=__name__
            cmdname="python "+n+ " -n -b 2>/dev/null > /dev/null"

        cron=[]
        hasfound=0
        for line in os.popen("crontab -l -u root","r").readlines():
            line=line.strip()
            cron.append(line)
            if line.find(cmdname) > -1: hasfound=1
        if hasfound: return True #Already installed
        if not runhour: runhour=random.randint(0,23)

        cron.append("")
        cron.append("#"+comment)
        cron.append("#Comment out (DO NOT DELETE) the command below to disable")
        cron.append("%s %s * * * %s" % (random.randint(0,59),runhour,cmdname))

        try: (h,fname)=tempfile.mkstemp()
        except AttributeError: #Doh, old version of python
            fname=tempfile.mktemp()
        f=open(fname,"w")
        f.write("\n".join(cron))
        f.write("\n")
        f.close()
        os.system("crontab -u root %s" % (fname)) #Load the changes into the cron
    def fileOutput(self,fname=None):
        """Redirect output to file call fname. If not specified will redirect to
           /root/Account_Usage/<hostname>-account_usage.csv
        """
        if not fname: fname="/root/Account_Usage/%s-account_usage.csv" % (socket.gethostname())
        #Make sure the parent directory exists. Create if needed 
        #print "Fname:",fname
        #print os.path.realpath(fname)
        #print os.path.dirname(os.path.realpath(fname))
        dir=os.path.dirname(os.path.realpath(fname))
        #print "testing: ",dir
        try:os.makedirs(dir)
        except OSError: pass #ALready exists
        if os.path.exists(fname):self.output=open(fname,"r+")
        else: self.output=open(fname,"w+")
        try:
               fcntl.flock(self.output,fcntl.LOCK_EX | fcntl.LOCK_NB)  #Lock the file while we work with it
        except IOError:
            raise Exception,"Lock failed. Must be in use, aborting"


if __name__=='__main__':
    usage="Usage: %prog [<options>] " +"\n"+__doc__+"\n"
    parser=optparse.OptionParser(usage)
    parser.add_option("--runeventhoughwedontusethis", dest="runeventhoughwedontusethis", action="store_true", default=False, help="runeventhoughwedontusethis")
    parser.add_option("-b","--batch", dest="batch", action="store_true", default=0, help="Run as a batch process with output going to /root/Account_Usage/<hostname>-account_usage.csv")
    parser.add_option("-n","--nocron", dest="nocron", action="store_true", default=0, help="Do not make sure this script is in the cron")
    parser.add_option("-o","--output", dest="outputfile", type="string", help="Redirect output into the given file")
    (options,args) = parser.parse_args()
    if not options.runeventhoughwedontusethis: 
        sys.exit(1)

    d=CpanelUsageDetails()
    if options.batch: d.fileOutput()
    if options.outputfile: d.fileOutput(options.outputfile)
    if not options.nocron: d.installCron(runhour=23)
    try: d.run()
    except KeyboardInterrupt:
        print "\nUser requested abort."
        sys.exit(1)

