#!/usr/bin/python3
"""
ntlogon.py written by Timothy Grant

Copyright 1999 by Avalon Technology Group, Inc.

This programme is copyright 1999 by Avalon Technology Group, Inc. it
is distributed under the terms of the GNU Public License.

IMPORTANT:

This program is now maintained by Gabriel Orozco (redimido@glo.org.mx)
since Nov/2001
first because I could not find Timothy anywhere, but now because of
an email from Timothy where we agreed I'll continue the development.

------------------------------------------------------------------------

This program was updated in Jun/2006 by Gabriel Orozco.

New additions is ntlogon now works with LDAP too =)

Main additions are the Footer Section which always will be the last one,
along with the ability to check all the groups a user belongs to, and
add to the script all the stuff to check into all those groups.
I also deleted the %G macro, because now it's not needed.

----

In Dec/2001 I fixed a bug that when the user is listed last into the
group in /etc/group, it was not finded. problem is that the last user has
an extra linefeed added. I deleted it in the comparisions, and is fixed.

Also found that sometimes the user name field has some more data into it.
so, when I'm obtaining the user's name, I splitted this and retain only
the user name. so, is fixed.

Added also the option --debugpause, to pause the execucion of the string
if there is some error. this code is under hevy testing now.

------------------------------------------------------------------------

This work is distributed under the terms of the GNU Public License.

The format for the configuration file is as follows:

While there is some room for confusion, we attempt to process things in
order of specificity: Global first, Group second, User third, OS Type
forth. This order can be debated forever, but it seems to make the most
sense.

# Everything in the Global section applies to all users logging on to the
# network
[Global]
@ECHO "Welcome to our network!!!"
NET TIME \\\\servername /SET /YES
NET USE F: \\\\servername\\globalshare /YES

# Map the private user area in the global section so we don't have to
# create individual user entries for each user!
NET USE U: \\\\servername\\%U /YES

# Group entries, User entries and OS entries each start with the
# keyword followed by a dash followed by--appropriately enough the Group
# name, the User name, or the OS name.
[Group-admin]
@ECHO "Welcome administrators!"
NET USE G: \\\\servername\\adminshare1 /YES
NET USE I: \\\\servername\\adminshare2 /YES

[Group-peons]
@ECHO "Be grateful we let you use computers!"
NET USE G: \\\\servername\\peonshare1 /YES

[Group-hackers]
@ECHO "What can I do for you today great one?"
NET USE G: \\\\servername\\hackershare1 /YES
NET USE I: \\\\servername\\adminshare2 /YES

[User-fred]
@ECHO "Hello there Fred!"
NET USE F: \\\\servername\\fredsspecialshare /YES

[OS-WfWg]
@ECHO "Time to upgrade it?"

# End configuration file

usage: ntlogon.py [-u  | --user=username]
                  [-l  | --localuser=username]
                  [-o  | --os=osname]
                  [-m  | --machine=netbiosname]
                  [-f  | --templatefile=filename]
                  [-d  | --dir=netlogon directory]
                  [-v  | --version]
                  [-h  | --help]
                  [--pause]
                  [--debug]

--user = is the username for samba. this maybe is not the local user if you
         are using smbuser, which masquerades users. for that reason, I will
         leave the option here but try not to use it.
""" 
#   


import sys, getopt, re, string, os, subprocess

version = "ntlogon.py v0.11 updated 2006/06/30 by Gabriel Orozco (Redimido)"

def grpScript(localuser, debug):
    """
    grpScript will take all the groups a user belongs to.
    first the primary group, and then all the secondary ones.
    with this, the main script will check if the user is member
    of any available group, and then will process scripts
    from any of thoso metioned groups.
    """
    grps = []
    user_name = ''
    if debug:
       print(" ")
       print("-- get groups and user name function --------------")
       print(" ")
       print("local user = " + localuser)
       print(" ")

    gErr, idoutput = subprocess.getstatusoutput('id ' + localuser)
    if gErr != 0 :
       print("ERROR: this user does not have the command id? error = ", gErr)
       print("Exiting from ntlogon.\n")
       sys.exit(1)
    if debug:
       print("id output = " + idoutput)
    lPrimaryGroup = 1
    p = re.compile('([a-z ^_]+)')
    groups_part = p.findall(idoutput)
    if debug:
       print("groups :", groups_part[5:])
    for group in groups_part[5:]:
       grps.append(group)
       if lPrimaryGroup:
          nErr, user_name = subprocess.getstatusoutput('getent passwd ' + localuser + ' | cut -d: -f5 | cut -d, -f 1')
          lPrimaryGroup = 0
          if debug:
             print("real user name: " + user_name)
    if debug:
       print(" ")
       print("-- end groups and user name function --------------")
       print(" ")
    return grps, user_name
# End grpScript()

def buildScript(buf, sections, groups, user, localuser, username, ostype, machine, debug, pause, debugpause):
    """
    buildScript() Takes the contents of the template file and builds
    a DOS batch file to be executed as an NT logon script. It does this
    by determining which sections of the configuration file should be included
    and creating a list object that contains each line contained in each
    included section.  The list object is then returned to the calling 
    routine.
   
    All comments (#) are removed. A REM is inserted to show
    which section of the configuration file each line comes from.
    We leave blanklines as they are sometimes useful for debugging
   
    We also replace all of the Samba macros (e.g., %U, %G, %a, %m) with their
    expanded versions which have been passed to us by smbd
    """
    hdrstring   = ''
    script      = []

    #
    # These are the Samba macros that we currently know about.
    # any user defined macros will also be added to this dictionary.
    # We do not store the % sign as part of the macro name.
    # The replace routine will prepend the % sign to all possible
    # replacements.
    # 
    macros = {
                'U': user,
                'a': ostype,
                'm': machine,
                'N': username
             }
    
    #
    # Process each section defined in the list sections
    #
    for s in sections:
        # print 'searching for: ' + s

        if s == 'Group':
                iterations = len(groups)
        else:
            iterations = 1
        
        for i in range(iterations):
        
        
            #
            # We need to set up a regex for each possible section we
            # know about. This is slightly complicated due to the fact
            # that section headers contain user defined text.
            #
            if s == 'Global':
                hdrstring = '\[ *' + s + ' *\]'
            elif s == 'Footer':
                hdrstring = '\[ *' + s + ' *\]'
            elif s == 'Group':
                hdrstring = '\[ *' + s + ' *- *' + groups[i]   + ' *\]'
            elif s == 'User':
                hdrstring = '\[ *' + s + ' *- *' + user    + ' *\]'
            elif s == 'OS':
                hdrstring = '\[ *' + s + ' *- *' + ostype  + ' *\]'
            elif s == 'Machine':
                hdrstring = '\[ *' + s + ' *- *' + machine + ' *\]'
                
            idx = 0
            
            while idx < len(buf):
                ln = buf[idx]
                
                #
                # See if we have found a section header
                #
                if re.search(r'(?i)' + hdrstring, ln):
                    idx = idx + 1   # increment the counter to move to the next
                                    # line.
                    
                    x = re.match(r'([^#\r\n]*)', ln)    # Determine the section
                                                        # name and strip out CR/LF
                                                        # and comment information
                    
                    if debug:
                        print('rem ' + x.group(1) + ' commands')
                    else:
                        # create the rem at the beginning of each section of the
                        # logon script.
                        script.append('rem ' + x.group(1) + ' commands') 
                    
                    #
                    # process each line until we have found another section
                    # header
                    #
                    while not re.search(r'.*\[.*\].*', buf[idx]):
                        
                        #
                        # strip comments and line endings
                        #
                        x = re.match(r'([^#\r\n]*)', buf[idx])
                        
                        if string.strip(x.group(1)) != '' :
                            # if there is still content  after stripping comments and
                            # line endings then this is a line to process

                            line = x.group(1)

                            #
                            # Check to see if this is a macro definition line
                            #
                            vardef = re.match(r'(.*)=(.*)', line + ' ')

                            if vardef:
                                varname = string.strip(vardef.group(1))             # Strip leading and
                                varsub  = string.strip(vardef.group(2))             # and trailing spaces
                                if debug:
                                    print("Found a macro! " + line)

                                if varname == '':
                                    print("Error: No substition name specified line: %d" % idx)
                                    sys.exit(1)
                                    
                                if varsub == '':
                                    print("Error: No substitution text provided line: %d" % idx)
                                    sys.exit(1)
                                
                                if varname in macros:
                                    print("Warning: macro %s redefined line: %d" % (varname, idx))

                                macros[varname] = varsub
                                
                                idx = idx + 1
                                if idx < len(buf):
                                    continue
                            
                            #
                            # Replace all the  macros that we currently
                            # know about.
                            #
                            # Iterate over the dictionary that contains all known
                            # macro substitutions.
                            #
                            # We test for a macro name by prepending % to each dictionary
                            # key.
                            #
                            for varname in list(macros.keys()):
                                line = re.sub(r'%' + varname + r'(\W)',
                                              macros[varname] + r'\1', line)
                                
                                
                                
                            #
                            # Check to see if this is an include line, and include
                            # the file requested, if it's there.
                            #
                            if re.search('^Include?', line):
                                incldef = re.match(r'(.*) (.*)', line)
                                if incldef:
                                    inclsub  = string.strip(incldef.group(2))             # and trailing spaces
                                    
                                    if inclsub == '':
                                        print("Error: No substitution text provided line: %d" % idx)
                                        sys.exit(1)
                                    
                                    # check if the include file is there, and loads it
                                    FlagYesThereAreTheIncFile = 1
                                    try:
                                        icFile = open(inclsub, 'r')
                                    except IOError:
                                        # the file is not there of we cannot access it as root
                                        # which is worst.
                                        FlagYesThereAreTheIncFile = 0
                                    
                                    #open the file only if it could be open
                                    if FlagYesThereAreTheIncFile:
                                        tmpInclude = icFile.readlines()
                                        
                                        for inclLine in tmpInclude:
                                            if debug:
                                                print(inclLine)
                                                if pause:
                                                    print('pause')
                                            else:
                                                script.append(inclLine)
                                    else:
                                        if debug:
                                            print('The include file specified does not exist ')
                                            print('or cannot be open : ' + inclsub)
                                            if pause:
                                                print('pause')
                                        else:
                                            script.append('@echo The include file specified do not exist, ')
                                            script.append('@echo or cannot be open : ' + inclsub)
                                        
                                    idx = idx + 1
                                    continue
                            
                            if idx < len(buf):
                                if debug:
                                    print(line)
                                    if pause:
                                        print('pause')
                                    elif debugpause:
                                        print('@if not errorlevel == 0 @pause')
                                else:
                                    script.append(line)
                        
                        idx = idx + 1
                        
                        if idx >= len(buf):
                            break   # if we have reached the end of the file
                                    # stop processing.
                        
                idx = idx + 1   # increment the line counter
            
            if debug:
                print('')
            else:
                script.append('')
        
    return script
    
# End buildScript()
    

def run():
    """
    run() everything starts here. The main routine reads the command line
    arguments, opens and reads the configuration file.
    """
    configfile  = '/etc/ntlogon.conf'   # Default configuration file
    group       = ''                    # Default group
    groups      = []                    # Secondary Groups
    user        = ''                    # Default user
    localuser   = ''                    # Default local user
    user_name   = ''                    # Real User Name taked from /etc/passwd
    ostype      = ''                    # Default os
    machine     = ''                    # Default machine type
    outfile     = 'logon.bat'           # Default batch file name
                                        #   this file name WILL take on the form
                                        #   username.bat if a username is specified
    debug       = 0                     # Default debugging mode
    pause       = 0                     # Default pause mode
    debugpause  = 0                     # Default pause if error.
    sout        = 0                     # Define if use standard out for output (1) or file (0)
    outdir      = '/etc/samba/netlogon/'   # Default netlogon directory

    # Currently supported  configuration file sections:
    sections    = ['Global', 'Machine', 'OS', 'Group', 'User', 'Footer']
   
    options, args = getopt.getopt(sys.argv[1:], 'd:f:g:h:l:o:s:u:m:v', 
                                 ['templatefile=', 
                                  'group=',
                                  'help',
                                  'os=',
                                  'user=',
                                  'localuser=',
                                  'machine=',
                                  'dir=',
                                  'sout',
                                  'version',
                                  'pause',
                                  'debugpause',
                                  'debug'])
                                  
    #
    # Process the command line arguments
    #
    for i in options:
        # template file to process
        if (i[0] == '-f') or (i[0] == '--templatefile'):
            configfile = i[1]
        # define the group to be used
        elif (i[0] == '-g') or (i[0] == '--group'):
            group = i[1]
        # define the os type
        elif (i[0] == '-o') or (i[0] == '--os'):
            ostype = i[1]
        # define the user
        elif (i[0] == '-u') or (i[0] == '--user'):
            user = i[1]
            outfile = user + '.bat' # Setup the output file name
        # define the local user
        elif (i[0] == '-l') or (i[0] == '--localuser'):
            localuser = i[1]
        # define the machine
        elif (i[0] == '-m') or (i[0] == '--machine'):
            machine = i[1]
        # define the netlogon directory
        elif (i[0] == '-d') or (i[0] == '--dir'):
            outdir = i[1]
        # if we are asked to turn on debug info, do so.
        elif (i[0] == '--debug'):
            debug = 1
        # if we are asked to turn on the automatic pause functionality, do so
        elif (i[0] == '--pause'):
            pause = 1
        # if we are asked to turn on the automatic pause functionality, do so
        elif (i[0] == '--debugpause'):
            debugpause = 1
        # if we are asked for the version number, print it.
        elif (i[0] == '-v') or (i[0] == '--version'):
            print(version)
            sys.exit(0)
        # if we are asked for help print the docstring.
        elif (i[0] == '-h') or (i[0] == '--help'):
            print(__doc__)
            sys.exit(0)
        elif (i[0] == '-s') or (i[0] == '--sout'):
            sout = 1
    if debug:
       print(" ")
       print("-- Options: -----------------------------")
       print(" ")
       print('configfile = ' + configfile)
       print('group = ' + group)
       print('os = ' + ostype)
       print('user = ' + user)
       print('local user = ' + localuser)
       print('machine = ' + machine)
       print('outdir = ' + outdir)
       print('debug = ', debug)
       print('pause = ', pause)
       print('debugpause = ', debugpause)
       print('standard out = ', sout)
       print(" ")
       print("-- End Options: --------------------------")
    
    #
    # open the configuration file
    #    
    try:
        iFile = open(configfile, 'r')
    except IOError:
        print('Unable to open configuration file: ' + configfile)
        sys.exit(1)
    buf = iFile.readlines() # read in the entire configuration file
    
    #
    # open the output file
    #    
    if not debug and not sout:
        try:
            oFile = open(outdir + outfile, 'w')
        except IOError:
            print('Unable to open logon script file: ' + outdir + outfile)
            sys.exit(1)
    
    #
    # call the groups filing routine
    #
    groups, user_name = grpScript(localuser, debug)
    
    #
    # call the script building routine
    #
    script = buildScript(buf, sections, groups, user, localuser, user_name, ostype, machine, debug, pause, debugpause)

    #
    # write out the script file
    #
    if not debug:
        for ln in script:
            if sout:
               print(ln)
            else:
               oFile.write(ln + '\r\n')
            if pause:
                if string.strip(ln) != '':        # Because whitespace is a useful tool, we
                    if not sout:                  # don't put pauses after an empty line.
                        oFile.write('pause' + '\r\n')


# End run()

#
# immediate-mode commands, for drag-and-drop or execfile() execution
#
if __name__ == '__main__':
    run()
else:
    print("Module ntlogon.py imported.")
    print("To run, type: ntlogon.run()")
    print("To reload after changes to the source, type: reload(ntlogon)")
   
#
# End NTLogon.py
#
