#!/usr/bin/env ruby.ruby3.4

# Copyright 2025 Open Text
#
# ------------------------------------------------------------------------------
# The only warranties for products and services of Open Text and its
# affiliates and licensors (“Open Text”) are as may be set forth in the express
# warranty statements accompanying such products and services.  Nothing herein
# should be construed as constituting an additional warranty. Open Text shall not
# be liable for technical or editorial errors or omissions contained herein. The
# information contained herein is subject to change without notice.
#
# Except as specifically indicated otherwise, this document contains
# confidential information and a valid license is required for possession, use or
# copying. If this work is provided to the U.S. Government, consistent with FAR
# 12.211 and 12.212, Commercial Computer Software, Computer Software
# Documentation, and Technical Data for Commercial Items are licensed to the U.S.
# Government under vendor's standard commercial license.
# ------------------------------------------------------------------------------

# (C) Copyright 1995 - 2021 Micro Focus or one of its affiliates.
 
# The only warranties for products and services of Micro Focus and its affiliates and licensors
# (“Micro Focus”) are as may be set forth in the express warranty statements accompanying such
# products and services. Nothing herein should be construed as constituting an additional
# warranty. Micro Focus shall not be liable for technical or editorial errors or omissions contained
# herein. The information contained herein is subject to change without notice.
 
# Except as specifically indicated otherwise, this document contains confidential information
# and a valid license is required for possession, use or copying. If this work is provided to the
# U.S. Government, consistent with FAR 12.211 and 12.212, Commercial Computer Software,
# Computer Software Documentation, and Technical Data for Commercial Items  are licensed
# to the U.S. Government under vendor's standard commercial license.

# Copyright (C) [2007-2009] Novell, Inc.  All Rights Reserved.

# THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES.
# IT MAY NOT BE USED, COPIED, DISTRIBUTED, DISCLOSED, ADAPTED, PERFORMED,
# DISPLAYED, COLLECTED, COMPILED, OR LINKED WITHOUT NOVELL'S PRIOR WRITTEN
# CONSENT.  USE OR EXPLOITATION OF THIS WORK WITHOUT AUTHORIZATION COULD
# SUBJECT THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
 
# NOVELL PROVIDES THE WORK "AS IS," WITHOUT ANY EXPRESS OR IMPLIED WARRANTY,
# INCLUDING WITHOUT THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE, AND NON-INFRINGEMENT. NOVELL, THE AUTHORS OF THE WORK,
# AND THE OWNERS OF COPYRIGHT IN THE WORK ARE NOT LIABLE FOR ANY CLAIM, DAMAGES,
# OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,
# ARISING FROM, OUT OF, OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER
# DEALINGS IN THE WORK.
require 'yaml'
require 'rubygems'
require 'mig'
require 'monitor'
require 'logger'
require 'psych'

include Migration

SupportedAttrs =  " CN description mail facsimileTelephoneNumber" +
     " fullName generationQualifier givenName initials" +
     " Language l loginAllowedTimeMap loginDisabled" +
     " loginExpirationTime loginGraceLimit loginGraceRemaining" +
     " loginMaximumSimultaneous loginScript networkAddressRestriction" +
     " o ou passwordAllowChange passwordExpirationInterval" +
     " passwordExpirationTime passwordMinimumLength passwordRequired" +
     " passwordUniqueRequired physicalDeliveryOfficeName postOfficeBox" +
     " postalAddress postalCode st street sn telephoneNumber title" +
     " uid ndsHomeDirectory objectClass groupMembership securityEquals" +
     " member equivalentToMe l s c"


WinSupportedAttrs = "description mail facsimileTelephoneNumber fullName givenName initials Language l physicalDeliveryOfficeName postOfficeBox postalCode st street telephoneNumber title"

def map_ntdomain i
  if i.class == Migration::Windows::Domain::WindowsUser
    result = nil  
    if @defaultSearchBase != nil
      if @defaultSearchBase.length >0
        first_name, base_name = parse_name @full_username
        first_name = "sAMAccountName=#{i.user_name}"
        result = Migration.winldapsearch @source, @full_username, @defaultSearchBase, first_name, @passwd, false, WinSupportedAttrs,false
        if result == nil
          print_message(ERROR, $globalLdapErrorMsg)
          # Error occurred in ldapsearch ... return
          return
        end
      end
    end
    #$stderr.puts "result is #{result)"
    dn = "cn=#{i.user_name},#{@opt['k']}"
    puts "- entity: \"#{dn}\"" 
    puts "  dn: \"#{dn}\""
    puts "  cn: \"#{i.user_name}\""
    puts "  sn: \"#{i.user_name}\""
    puts "  fullName: \"#{i.full_name}\"" if not i.full_name.empty?
    #puts "  homeDirectory: #{i.dir_drive}"

    puts "  objectClass:"
    puts "  - inetOrgPerson"
    puts "  - organizationalPerson"
    puts "  - Person"
    puts "  - ndsLoginProperties"
    puts "  - Top"
    puts "  primaryGroup: #{@primary_group}" if @primary_group
=begin
    newpass = ""
    if @opt['r']
      chars = ("a".."z").to_a + ("1".."9").to_a 
      newpass = Array.new(8, '').collect{chars[rand(chars.size)]}.join
    elsif @opt['specific-password']
      newpass = @opt['specific-password']
    end
=end
    #puts "  changetype: add" if @opt['r'] or @opt['S'] 
   # puts "  userpassword: #{newpass}" if @opt['r'] or @opt['specific-password'] 
    #puts "  posix-user: #{linux_name(dn,'user')}" if @opt['p'] 
    if @opt['p']
      name = i.user_name
      name.downcase!
      if name.include? " "
        name.gsub!(/ /,"_")
      end  
      puts "  posix-user: #{name}" 
    end
    usergroups = Array.new
    @records.each do |group|
      if group.class == Migration::Windows::Domain::WindowsGroup 
        group.members.each do |mem|
          usergroups << group.group_name if mem == i.user_name
        end
      end
    end
    puts "  groupMembership: " if not usergroups.empty?
    usergroups.each do |grp|
      puts "  - \"cn=#{grp},#{@opt['k']}\""
    end
    puts "  securityEquals: " if not usergroups.empty? 
    usergroups.each do |grp|
      puts "  - \"cn=#{grp},#{@opt['k']}\""
    end
    # result will be populaed only if Active Directory is available on source
    result.each do |attrval|
      if attrval != nil and attrval != ""
        name, value = attrval.split(':',2)
        puts "  #{attrval}" if WinSupportedAttrs.include? name 
      end
    end if result

  else
    dn = "cn=#{i.group_name},#{@opt['k']}"
    puts "- entity: \"#{dn}\"" 
    puts "  dn: \"#{dn}\""
    puts "  cn: \"#{i.group_name}\""
    puts "  objectClass:"
    puts "  - groupOfNames"
    puts "  - Top"
    puts "  member:" if not i.members.empty?
    i.members.each do |m|
      if @opt['p']
        #name = "cn=#{m},#{@opt['k']}"
        name = m
        name.downcase!
        if name.include? " "
          name.gsub!(/ /,"_")
        end  
        puts "  - \"#{name}\""
      else
        puts "  - \"cn=#{m},#{@opt['k']}\""
      end
    end 
    puts "  equivalentToMe:" if not i.members.empty?
    i.members.each do |m|
      if @opt['p']
        #name = "cn=#{m},#{@opt['k']}"
        name = m
        name.downcase!
        if name.include? " "
          name.gsub!(/ /,"_")
        end  
        puts "  - \"#{name}\""
      else
        puts "  - \"cn=#{m},#{@opt['k']}\""
      end
    end 
    puts "  lum-group: enable" if @opt['G']
    if @opt['p']
      name = i.group_name
      name.downcase!
      if name.include? " "
        name.gsub!(/ /,"_")
      end  
      puts "  posix-group: \"#{name}\"" 
    end
  end
end

def delete_obj_classes objclasses
  if @obj_class_list.nil?
    @obj_class_list = []
    file = "/etc/opt/novell/migration/objclass-exclude-list.conf"
    File.open(file).each { |line|
      @obj_class_list << line.strip if line.strip != "" and line.strip[0,1] != '#'
    }
  end
  @obj_class_list.each { |elem|
    objclasses.delete_if { |objclass| objclass.casecmp(elem) == 0 }
  }
  return objclasses
end

def should_migrate_obj? obj
  if @obj_list.nil?
    @obj_list = []
    file = ""
    if @opt['E']
      file = @opt['E']
    else
      file = "/etc/opt/novell/migration/obj-exclude-list.conf"
    end
    
    File.open(file).each { |line|
      @obj_list << line.strip if line.strip != "" and line.strip[0,1] != '#'
    }
  end
  @obj_list.each { |elem| 
    return false if (obj =~ /#{elem}/i) == 0
  }
  if @opt['matchup-file'] != nil and not @matchup.empty? and @matchup[obj.downcase] != nil and 
        @matchup[obj.downcase].casecmp("ignore") == 0
    return false
  end
  return true
end

def should_migrate_group? grp
  return false if not should_migrate_obj?(grp)
  result = Migration.ldapsearch(@source, @full_username, grp, "", @passwd, false, "", @useSecure, @port)
  if result == nil
    # Error occurred in ldapsearch... return
    return false
  end
  if result.include?("rbsContext") or result.include?("rbsScope") or result.include?("rbsCollection")
    return false
  else
    return true
  end
end

def get_mapped_object object
  if @opt['k']
    new = simple_context object, @opt['k']
  elsif @opt['matchup-file'] and not @matchup.empty?
    new = @matchup[object.downcase]
    new = object if new.nil?
    new = "" if new.casecmp("ignore") == 0
  else
    new = object
  end
  print_message(DEBUG, "mapped #{object} to #{new}")
  return new
end

# Here we delete the objects which we do not want to migrate and also map it.
def processGenericAtt(attrib, hattrs)
  hattrs[attrib].each_index do |i|
    decoded = false
    objNameToMap = hattrs[attrib][i]
    if objNameToMap =~ /^_decoded_/
      objNameToMap.sub!(/_decoded_/, "")
      decoded = true
    end
    if should_migrate_group?(objNameToMap.strip) == false
      hattrs[attrib].delete(hattrs[attrib][i])
      next
    end
    if get_mapped_object(objNameToMap) != ""
      hattrs[attrib][i] = get_mapped_object(objNameToMap)
      hattrs[attrib][i] = "_decoded_#{hattrs[attrib][i]}" if decoded
    end
  end
  hattrs[attrib].uniq!
  return hattrs
end

#Method to replace Non English character with empty string in group name
def replaceNonEngChar(owner)
    arr = owner.bytes
    len = owner.length
    
    if arr.length > len
      owner = owner.gsub(/[^0-9a-zA-Z$&+,:;=?@#|'<>.-^*()%!]+/,'')
    end
    
    return owner
end

def map_ldap owner
  #work around to handle s to st conversion
  if owner.include?(",s=")
    owner.sub!(",s=", ",st=")
  elsif owner.include?(",S=")
    owner.sub!(",S=", ",ST=")
  end
  lowner = owner.downcase
  owner = owner.gsub("\[", "\\[") if owner.include? "\["
  owner = owner.gsub("\]", "\\]") if owner.include? "\]"
  if not @hash.has_key?(lowner) and should_migrate_obj?(owner)
    @hash[lowner] = true
    first_name, base_name = parse_name owner
    unless @hash.has_key?(base_name)
      map_ldap(base_name) if not @opt['k'] and not @opt['matchup-file']
    end
    
    if @opt['map-homedir-only']
      result = Migration.ldapsearch @source, @full_username, base_name, first_name, @passwd, false, "dn ndsHomeDirectory", @useSecure, @port
    else
      result = Migration.ldapsearch @source, @full_username, base_name, first_name, @passwd, false, SupportedAttrs, @useSecure, @port
    end
    if result == nil
      print_message(ERROR, $globalLdapErrorMsg)
      return 
    end
    #newpass = nil
    if @opt['L']
      result.each {|r| puts r } unless result == nil
    else
      owner = replaceNonEngChar(owner)
      n_v = get_mapped_object(owner)
      o_v = Regexp.new(owner, Regexp::EXTENDED | Regexp::IGNORECASE)
      person_object = false
      group_object = false
      
      hattrs = Hash.new
      
      i = 0
      while i < result.length
        attrib = result[i]
        encoded = false
# Time and network restrictions are stored as raw data, the interpretation is very difficult.
# Hence we spoof time and network attribute values as non-encoded data
#        if attrib.include?("::")
        if attrib.include?("networkAddressRestriction") or attrib.include?("loginAllowedTimeMap")
          a, v = attrib.split(":: ", 2)
          a = "#{a}:"
        elsif attrib.include?("::")
          a, v = attrib.split(":: ", 2)
          encoded = true
        else
          a, v = attrib.split(": ",2)
        end
        # Do not strip, value can have space at beginning and ending.
        #v.strip!
        j = i + 1
        while (result[j] != nil and result[j].index(":") == nil)
          v << result[j][1, result[j].length].rstrip
          i = i + 1
          j = j + 1
        end
        if encoded
          val = Base64.decode64(v)
        else
          val = v
        end

        a.rstrip!
        if (val != nil and val != "")
          val.gsub!(o_v, n_v)
        else
          # For some attributes, especially loginScript we may get nil value. It is because if user deletes 
          # the attribute from iManager and did not delete the "/n" character. Then ldapsearch returns nil
          # for that attribute. Hence, val.gsub! throws exception.
          i = i + 1
          next
        end
        #val.gsub!(/"/,"\\\"")
        #older eDir had homeDirectory instead of ndsHomeDirectory,
        #replace it with ndsHomeDirectory, so we don't have to change 
        #anything else
        if a.include? "homeDirectory"
          a = "ndsHomeDirectory"
        end

        if a.include?("ndsHomeDirectory")
          if @opt['H'] != nil
            serv_context = ""
            dest_server = ""
            # Following code is added to take care of condition when specified home directory
            # path is a cluster resource.
            # TODO: If specified home directory path is a NCP mounted cluster resource, then 
            # in this case we need to write the code to take care of this condition.
            dest_server, serv_context = getDNOfVolume(@vol_name) if not @opt['p'] and @isNSS
            if dest_server == nil or dest_server == ""
              # For some reason NSS's _ADMIN could get us dn of this volume (if it is NSS).
              # Use default server dn
              dest_server = server_name
              dest_server.chomp!
            end
            if serv_context == nil or serv_context == ""
              # For some reason NSS's _ADMIN could get us dn of this volume (if it is NSS).
              # Use default server context
              serv_context = server_context()
              serv_context.chomp!
            end
            if (val.include?("\\") or val.include?("\/"))
              n1 = val.rindex("\\")
              if n1 == nil
                n1 = val.rindex("\/")
              end
            else
              n1 = val.rindex("#")
            end
            if (n1 == nil)
              print_message(ERROR, "Failed to parse NDS home directory for user #{owner}")
            else
              if (@opt['p'])
                val = @opt['H'].chomp("/") + "/" + val.slice(n1+1, val.size)
              else
                @opt['H'] = @opt['H'].chomp("/") + "/"
                home_dir = @opt['H'].slice(@mntpt.size, @opt['H'].size) + val.slice(n1+1, val.size)
                home_dir.sub!('/','#') # Substitute the first occurance of '/' with #
                # Here #0# works as a separator
                val = dest_server + "_" + @vol_name + "," + serv_context + "#0" + home_dir
              end
            end
          else
            print_message(WARN, "User #{owner} has home directory attribute and option --homedir(-H) is not given")
          end
        end

        if a.include?("loginScript")
          val.gsub!(/\r\n/,"#rn\n")
        end
        
        arr = nil
        if hattrs[a] == nil
          arr = Array.new
        else
          arr = hattrs[a]
        end
        if encoded
          val = "_decoded_#{val}"
        end
        hattrs[a] = arr << val

        person_object = true if v.include? "inetOrgPerson"
        group_object = true if v.include? "groupOfNames" or
                               v.include? "Organization" or
                               v.include? "organizationalUnit"

        i = i + 1
      end unless result == nil

      if hattrs.has_key? "member" and @opt['progress']
        @totObjs += hattrs['member'].size
      end
      if person_object
        if hattrs.has_key? "groupMembership"
          processGenericAtt("groupMembership", hattrs)
        end
        val = Array.new
        services = Array.new
        if @opt['l'] or @opt['p']
          hattrs["primaryGroup"] = val << @primary_group 
          @opt['l'].each do |s|
            hattrs["lum-services"] = services << s
          end if @opt['l']
        end
        val = Array.new
        if @opt['p']
          val = Array.new
          hattrs["posix-user"] = val << linux_name(owner,"user") 
          groupMembership = Array.new
          hattrs['groupMembership'].each do |m|
            groupMembership << linux_name(m, "group")
          end if hattrs.has_key? "groupMembership"

          hattrs['groupMembership'] = groupMembership if hattrs.has_key? "groupMembership" 
        end
      end
      
      if hattrs.has_key? "securityEquals"
        processGenericAtt("securityEquals", hattrs)
      end
      if hattrs.has_key? "equivalentToMe"
        processGenericAtt("equivalentToMe", hattrs)
      end
      
      if group_object 
        val = Array.new
        hattrs["lum-group"] = val << "enable" if @opt['G']

        oldMembers = []
        if hattrs.has_key? "member"
          actualMembers =  hattrs["member"].clone
          hattrs = processGenericAtt("member", hattrs)
        end
=begin
        actualEquivalentToMe = []
        if hattrs.has_key? "equivalentToMe"
          hattrs["equivalentToMe"].delete_if { |x| should_migrate_obj?(x.strip) == false }
          actualEquivalentToMe = hattrs["equivalentToMe"].clone
          hattrs["equivalentToMe"].each_index do |i|
            if get_mapped_object(hattrs["equivalentToMe"][i]) != ""
              hattrs["equivalentToMe"][i] = get_mapped_object(hattrs["equivalentToMe"][i])
            end
          end
        end 
=end

        if @opt['p']
          val = Array.new
          hattrs["posix-group"] = val << linux_name(owner,"group") 
          members = Array.new
          hattrs['member'].each do |m|
            members << linux_name(m, "user")
          end if hattrs['members']
          hattrs['member'] = members if hattrs['member']
          name = nil
          if hattrs['o'] or hattrs['ou']
            name = hattrs['o']
          elsif hattrs['ou']
            name = hattrs['ou']
          end

          if name
            result = Migration.ldapsearch @source, @full_username, base_name, first_name, @passwd, false, "", @useSecure, @port
            if result != nil
              result.each do |l|
                if l.include? "dn"
                  members << l.slice(4,l.length-5)  
                end
              end
              hattrs['member'] = members 
            end
          end
        end
      end

      if not hattrs.empty?
        puts "---" if @firstTime == true
        puts "- entity: " + owner.gsub("'", "''")
        @firstTime = false
        @outcnt = @outcnt + 1
      else
        print_message(WARN, "Could not get information about entity #{owner}")
        return
      end
     
      #delete the unwanted objectClasses
      hattrs['objectClass'] = delete_obj_classes(hattrs['objectClass']) if not hattrs['objectClass'].nil?
 
      hattrs.each do |a, v|
        unless v == nil or v.length == 0
          if v.length == 1
            v[0].gsub!("'","''")
            puts "  #{a}: '#{v.join}'"
          else
            puts "  #{a}:"
            v.each {|val| 
              val.gsub!("'","''")
              puts "  - '#{val}'"}
          end
        end
      end
      actualMembers.each do |member|
        unless @hash.has_key?(member.downcase)
          map_ldap(member)
        end
      end if not actualMembers.nil?
=begin
      actualEquivalentToMe.each do |equiv|
        unless @hash.has_key?(equiv.downcase)
          map_ldap(equiv)
        end
      end if not actualEquivalentToMe.nil?
=end
    end
  end
end

def main
  $argdef = [
    ['s', 'source-server', "indicates the source LDAP server's IP address", ParseArgText, true],
#    ['W', 'windows', "indicates that a Windows file server will be the migration source", ParseArgBool],
    ['H', 'homedir', "full path to the directory under which user's Home Directories are migrated", ParseArgText],
    [nil, 'map-homedir-only', "use this option if you are migrating the volumes between servers in the same tree so that only the home directories are changed", ParseArgBool],
    ['p', 'posix', "maps users and groups to /etc/passwd and /etc/group in Linux. The default is ldap if no mapping flag is specified", ParseArgBool],
#    ['l', 'lum', "Maps users and groups to lum.  The default is ldap if no mapping flag is specified", ParseArgMult],
    ['k', 'destination-ldap-container', "option to specify LDAP container where all users and groups will be migrated", ParseArgText],
    [nil, 'matchup-file', "the object matchup file generated by migmatchup", ParseArgText],
#    ['i', 'verbose-information', "Prints verbose user/group information", ParseArgBool],
#    ['g', 'primary-group', "If not specified the default group is 'cn=<server-name>-group,<server-container>' for lum and 'users' for posix", ParseArgText],
    ['g', 'primary-group', "primary POSIX group for migrated users. If not specified the default primary group is 'users'. The specified group should be created before running migtrustees.", ParseArgText],
#    ['G', 'lum-enable-groups', "If --lum is specified, this will lum-enable all groups", ParseArgBool],
#    ['L', 'output-ldif', "Normal output is yaml format, this option generates ldif output", ParseArgBool],
    ['E', 'obj-exclude-file', "Exclude the objects listed in this file from migration", ParseArgText],
#    ['C', 'user-context', "Active Directory User context, default is CN=Users", ParseArgText],
    ['c', 'session-file', "stores the migration progress, this file can be used to continue the migration", ParseArgText],
    [nil, 'source-unsecure-ldap', "use unsecure LDAP for all LDAP commands", ParseArgBool],
    [nil, 'source-ldap-port', "port where the LDAP server is listening", ParseArgText],
    [nil, 'progress', "display the progress in terms of number of objects processed", ParseArgBool],
    [nil, 'progress-interval', "time interval for displaying progress", ParseArgText],
    [nil, 'use-casa', "use CASA to store/retrieve username and password information", ParseArgBool],
    [nil, 'precheck', "checks whether system meets all pre-requisite to start maptrustees", ParseArgBool],
    [nil, 'debug', "generate debug log", ParseArgBool],
    [nil, "inputfile", "the input file (usually produced by running 'mls'or 'ntuserls').  If not given on the commandline, will read from stdin", ParseArgOrd],
    ParseArgHelp,
    ParseArgUsage
  ]

  @init = true
  maptrusteesCommand = "#{$0}"
  debugOpt = false
  $*.each do |arg|
    if arg.strip == "--debug"
      debugOpt = true
    end
    maptrusteesCommand += " #{arg}"
  end
  initLog(debugOpt, "maptrustees")
  print_message(DEBUG, "maptrustees command started at #{`date +%d-%m-%y\\ %H:%M:%S`}")
  print_message(DEBUG, "maptrustees command executed as: #{maptrusteesCommand}")

  @opt = {}
  @isNSS = false
  (@opt, @extraargs) = parseargs(ARGV, $argdef)

  @opt['use-casa'] = true if ENV["MIG_USE_CASA"] != nil  

  @obj_list = nil
  filemap = @opt['inputfile']
  if filemap == nil
    filemap = $stdin
    if filemap.isatty
      print_message(FATAL, "Must specify input file or provide input to stdin")
      printUsage($argdef)
      exit 1
    end
  end
  if @opt['inputfile']
    if not File.exist?(@opt['inputfile']) or not File.size?(@opt['inputfile'])
      print_message(FATAL, "Specified input file #{@opt['inputfile']} does not exist or it is empty")
      exit 1
    end
  end

  if @opt['k'] and @opt['p']
    print_message(FATAL, "Cannot specify both --posix(-p) and --destination-ldap-container(-k) options together")
    printUsage($argdef)
    exit 1
  end

  if @opt['k'] and @opt['matchup-file']
    print_message(FATAL, "Cannot specify both --destination-ldap-container(-k) and --matchup-file options together")
    printUsage($argdef)
    exit 1
  end

  if @opt['matchup-file'] and @opt['p']
    print_message(FATAL, "Cannot specify both --posix(-p) and --matchup-file options together")
    printUsage($argdef)
    exit 1
  end

  if @opt['k']
    unless  is_valid_ldap_container? @opt['k']
      print_message(FATAL, "Param --destination-ldap-container was not a valid ldap container")
      exit 1
    end
  end

  if @opt['C']
    unless  is_valid_activeDir_context? @opt['C']
      print_message(FATAL, "Param --user-context is not a valid user context")
      exit 1
    end
  end

  if @opt['E']
    unless File.exist?(@opt['E'])
      print_message(FATAL, "File specified in --obj-exclude-file(-E) does not exits")
      exit 1
    end
  end
  if @opt['g'] and not @opt['p']
    print_message(FATAL, "Cannot specify --primary-group (-g) without option --posix (-p)")
    exit 1
  end
  if @opt['progress-interval'] and not @opt['progress']
    print_message(FATAL, "Cannot specify --progress-interval without --progress")
    exit 1
  end
  if @opt['progress-interval'] and @opt['progress-interval'].to_i <= 0
    print_message(FATAL, "value for --progress-interval should be greater than 0")
    exit 1
  end
  if @opt['map-homedir-only'] and not @opt['H']
    print_message(FATAL, "Need to specify option --homedir(-H) along with option --map-homedir-only")
    exit 1
  end

  @firstTime = true
  @primary_group = @opt['g']
  if @primary_group == nil
    if @opt['p']
      @primary_group = "users"
    elsif @opt['l']
      available_services = Dir.entries("/etc/pam.d")
      lum_services = @opt['l']
      lum_services.each do |s|
        unless available_services.include? s
          print_message(FATAL, "#{s} is not an available service for lum enabling")
          exit 1
        end
      end
      @primary_group = ldap_primary_group_name
    end
  end

  if filemap.class == String
    input = File.open filemap,"r" 
  else
    input = filemap
  end
  if filemap == $stdin and (@opt['progress'] or @opt['progress-interval'])
    print_message(FATAL, "Cannot specify --progress or --progress-interval when input is stdin")
    exit 1
  end
  if @opt['H']
    @vol_name, @mntpt = get_nss_vol_and_mnt_point_from_path(@opt['H'])
    if @vol_name != nil and @vol_name != ""
      @isNSS = true
    end
    if @vol_name == nil
      @vol_name, @mntpt = get_ncp_vol_and_mnt_point_from_path(@opt['H'])
      if @vol_name == nil and not @opt['p']
        print_message(FATAL, "NSS/NCP migration specified but path specified with --homedir(-H) is neither a NSS or NCP path")
        printUsage($argdef)
        exit 1
      end
    end
    if @vol_name != nil and @opt['p']
      print_message(WARN, "You have specified NSS/NCP path along with POSIX option")
    end
  end
  
  if @opt['matchup-file'] != nil
    if File.exist?(@opt['matchup-file']) and File.size(@opt['matchup-file']) > 0
      matchupFile = File.open(@opt['matchup-file'])
      if matchupFile == nil
        print_message(FATAL, "Cannot open matchup file #{@opt['matcup-file']}")
        exit 1
      end
      begin
        tempMatchup = YAML.load(matchupFile)
        if tempMatchup.class == FalseClass
          print_message(FATAL, "Matchup file '#{@opt['matchup-file']}' not in correct format")
          exit 1
        end
        @matchup = {}
        if tempMatchup != nil
          tempMatchup.each { |key, value|
            @matchup[key.downcase] = value
          }
        else
          print_message(INFO, "Given migmatchup input file is empty")
        end
      rescue
        print_message(FATAL, "Unable to parse matchup file '#{@opt['matchup-file']}'. Error: #{$!.message}")
        exit 1
      end
    else
      print_message(WARN, "Given migmatchup input file '#{@opt['matchup-file']}' is empty or does not exist")
    end
  end

  @hash = Hash.new
  @hash['""'] = true
  #don't migrate the special user like [public]
  @hash['[public]'] = true
  @hash['[root]'] = true
  @hash['[adminstrator]'] = true
  @hash['[supervisor]'] = true

  # TO-DO: Should work for windows also... Once it works
  # need to remove second part of if condition
  if input != $stdin and not @opt['W']
    Psych.load_stream(input) do |doc|
      if doc != nil
        doc.each do |obj|
          if obj.class == FalseClass
            print_message(FATAL, "Input file not in correct format")
            exit 1
          end
          break
        end
        break
      else
        print_message(INFO, "Given mls input file is empty")
        exit 0
      end
    end
    input.seek(0, IO::SEEK_SET)
  end

  if not @opt['W']  
    if not @opt['s']
      print_message(FATAL, "must specify --source-server IP address")
      printUsage($argdef)
      exit 1
    end
    print_message(DEBUG, "Input file loaded successfully")

    credentials = Migration::KeyStore.credential(@opt['s'], :ldap_dn, @opt['use-casa'])
    @init = false

    @full_username = "\"#{credentials[:username]}\""
    @source = @opt['s']
    @passwd = credentials[:password]
    # Find out whether the LDAP is SSL enabled.
    @useSecure = true
    @useSecure = false if @opt['source-unsecure-ldap']
    @port = nil
    @port = @opt['source-ldap-port'] if @opt['source-ldap-port']
    createLdapConfFile()

    credentials = nil

    @username = Migration.just_username(@full_username)
    if @opt['precheck']
      print_message(INFO, "Validation for maptrustees is done successfully")
      exit 0
    end
    objNumber = 0
    @cnt = 0
    @outcnt = 0
    @jobDone = false
    if @opt['c']
      $session = "---\n"
      $session << "  started-on: #{`date`}"
      $session << "  src-server: #{@opt['s']}\n"
      $session << "  utility: maptrustees\n"
      if File.exist?(@opt['c']) and File.size?(@opt['c']) != 0
        sessionInput = File.open(@opt['c'], "r")
        sessionInfo = YAML.load(sessionInput)
        if sessionInfo['utility'] != "maptrustees" or
          sessionInfo['src-server'] != @opt['s']
          print_message(FATAL, "Invalid session file #{@opt['c']} specified. Please check the file")
          exit 1
        end
         if sessionInfo['status'] =~ /Completed/
          print_message(WARN, "Session file #{@opt['c']} shows maptrustees's status as 'Completed'")
          exit 0
        elsif sessionInfo['status'] =~ /Stopped/
          objNumber = sessionInfo['stopped-at'].to_i
        else
          print_message(FATAL, "Session file is corrupted")
          exit 1
        end
        sessionInput.close
      end
    end
    if  input != $stdin and @opt['progress']
      print_message(INFO, "precomputing for displaying progress")
      @totObjs = getTotalObjs(@opt['inputfile'])
      print_message(INFO, "Total number of objects to be processed is #{@totObjs}")
      if @opt['progress-interval']
        @progressInterval = @opt['progress-interval'].to_i
      else
        @progressInterval = 30
      end
      startTimer(@progressInterval)
    end
    if input == $stdin
      # We need to ignore first line ---
      input.gets
    end

    Psych.load_stream(input) do |doc|
      if doc != nil
         doc.each do |obj|
           if @cnt >= objNumber
              map_ldap(obj['owner']) if obj.include? ('owner')
              map_ldap(obj['trustee']) if obj.include? ('trustee')
              map_ldap(obj['modifier']) if obj.include? ('modifier')
             writeToSessionFile(@jobDone, @cnt) if @opt['c'] and @cnt != 0 and @cnt % 20 == 0
             puts "---" if @outcnt != 0 and @outcnt % NO_OF_OBJECTS_PER_RECORD == 0
           end
         @cnt = @cnt + 1
         end
      end  
    end
  else
    if not @opt['k'] and not @opt['p']
      print_message(FATAL, "Must specify param --destination-ldap-container when mapping users from NT Domains")
      printUsage($argdef)
      exit 1
    end
    input.close if input.class == File

    credentials = Migration::KeyStore.credential( @opt['s'], :cn ,@opt['use-casa'])
    if filemap.class == String
      input = File.open filemap,"r" 
    else
      input = filemap
    end

    @source = @opt['s']
    @passwd = credentials[:password]
    @defaultSearchBase = Migration.getDefaultLDAPContext(@opt['s'], false)
    if @defaultSearchBase != nil
      @defaultSearchBase.strip!
      if @defaultSearchBase.length > 0
        # Need to form the AD FDN for LDAP search "CN=Administrator,CN=Users,DC=Microsoft,DC=first,DC=com"
        if not @opt['C']
          #Bydefault Active Directory Administrator is in the Organizational Unit CN=Users.
          @full_username = "CN=#{credentials[:username]},CN=Users,#{@defaultSearchBase}" 
        else
          @full_username = "CN=#{credentials[:username]},#{@opt['C']}" 
        end
      end
    end
    @records = YAML.load_windows_yaml(input)
    if @opt['precheck']
      print_message(INFO, "Validation for maptrustees is done successfully")
      credentials.clear if credentials != nil
      exit 0
    end
    print_message(DEBUG, "Input file loaded successfully")
    @records.each do |r|
      map_ntdomain r
    end
  end
  input.close if input.class == File
  if @opt['c']
    @jobDone = true
    writeToSessionFile(@jobDone, @cnt)
  end  
  deleteLdapConfFile()
  print_message(PROGRESS, "Processed #{@totObjs} trustees of #{@totObjs}") if @opt['progress']
  print_message(DEBUG, "maptrustees completed successfully at #{`date +%d-%m-%y\\ %H:%M:%S`}")
end

begin
  trap("ALRM"){
    print_message(PROGRESS, "Processed #{@outcnt} trustees of #{@totObjs}")
    writeToSessionFile(@jobDone, @cnt) if @opt['c']
    startTimer(@progressInterval) if @opt['progress']
  }

  main #if __FILE__ == $0
rescue SystemCallError => failed  
  print_message(FATAL,"SystemCallError, #{failed.message}")
  exit failed.errno
rescue MigrationException => e 
  print_message(FATAL,"MigrationException, #{e.message}")
  exit e.code 
rescue MigSyntaxError => e
  print_message(FATAL, "MigSyntaxError, " + e.to_s)
  printUsage($argdef)
  exit e.code
rescue LdapAuthError => e 
  print_message(FATAL,"LdapAuthError, #{e.message}")
  exit e.code 
rescue Interrupt
  print_message(DEBUG, "Interrupted by user")
  deleteLdapConfFile()
  writeToSessionFile(@jobDone, @cnt) if @opt['c'] and @init == false
  exit 0
rescue 
  print_message(FATAL, "Caught exception: #{$!.message}\n Backtrace:\n #{$!.backtrace.join("\n")}")
  exit 1
ensure
  deleteLdapConfFile()
  $log.close if @opt['debug']
end
