#!/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 - 2022 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 'rubygems'
require 'mig'
require 'logger'
include Migration

def get_mapped_object object
  if @opt['k']
    new = simple_context(object, @opt['k'])
  #elsif @opt['matchup-file']
  elsif 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

def map_posix r, users
  #p r
  type = "unused"
  if r['owner'] != nil and users != nil
    users.each do |u|
      if r['owner'].include? "root"
        type = "user"
      elsif r['owner'].casecmp(u['entity'])
        cls = u['objectClass']
        if cls.include? "organizationalPerson"  
          type = "user"
        elsif cls.include? "groupOfNames"
          type = "group"
        elsif cls.include? "Organization"
          type = "group"
        elsif cls.include? "organizationUnit"
          type = "group"
        else
          type = "unused"
        end
      end
    end
  end

  if r['owner']
    file = r['file'].slice(@opt['V'].length, r['file'].length)
    file = file[1, file.length] if file[0, 1] == ':'
    file = file[1, file.length] if file[0, 1] == '/'
       
    dst_path = @file_path.chomp('/') + "/"
    puts "---" if @firstTime == true
    r['file'].gsub!("'", "''")
    puts "- file: '#{r['file']}'"
    @firstTime = false
    @outcnt = @outcnt + 1
    puts "  type: posix"
    puts "  src-file: '#{r['file']}'"
    puts "  dst-file: '#{dst_path}#{file.gsub("'", "''")}'"
    if r['owner'] != nil
      puts "  ldap_owner: '#{r['owner'].gsub("'", "''")}'"
      puts "  owner: '#{linux_name(r['owner'],type).gsub("'", "''")}'"
      owner_type = type 
      puts "  owner-type: '#{owner_type}'"
    
      if r['rights'] != nil
        rights = nw_rights_to_linux_permissions(r['rights'], owner_type) 
        puts "  rights: '#{rights}'"
      end
    end
    if r['attrs'] != nil
      attrs = nw_attributes_to_linux_permission(r['attrs'])
      printf "  attrs: %o\n", attrs
    end
  end
end

def map_ncp r
  unless r['file'] == nil
    file = r['file'].slice(@opt['V'].length, r['file'].length)
    if file != nil and file != ""
      file = file[1, file.length] if file[0,1] == ":" or file[0,1] == "/"
    end
    if not @opt['x']
      if @opt['v'].include?(":")
        dst_path = @opt['v']
      else
        dst_path = @opt['v'] + ":"
      end
      dst_path = dst_path + "/" + file unless file == nil
    else
      if file != nil and file != ""
        dst_path = @file_path.chomp('/') + "/" + file
      else
        dst_path = @file_path.chomp('/')
      end
    end
    puts "---" if @firstTime == true
    
    r['file'].gsub!("'", "''")
    puts "- file: '#{r['file']}'"
    @firstTime = false
    @outcnt = @outcnt + 1
    if @nss
      puts "  type: nss"
    else
      puts "  type: ncp"
    end
    puts "  src-file: '#{r['file']}'"
    puts "  dst-file: '#{dst_path.gsub("'", "''")}'"
  
    puts "  owner: '#{get_mapped_object(r['owner']).gsub("'", "''")}'" if r['owner'] and get_mapped_object(r['owner']) != ""
    puts "  modifier: '#{get_mapped_object(r['modifier']).gsub("'", "''")}'" if r['modifier'] and get_mapped_object(r['modifier']) != ""
    puts "  trustee: '#{get_mapped_object(r['trustee']).gsub("'", "''")}'" if r['trustee'] and get_mapped_object(r['trustee']) != ""

    #rights = convert_number_to_netware_permission(r['rights'])  
    puts "  rights: #{r['rights']}" if r['rights']
    puts "  mask: #{r['mask']}" if r['mask']
    puts "  dir-quota: #{r['dir-quota'].to_i*1024}" if r['dir-quota']
    puts "  user-quota: #{r['user-quota'].to_i*1024}" if r['user-quota']
    puts "  extended attrs: #{r['extended attrs']}" if r['extended attrs']
    puts "  attrs: #{r['attrs']}" if r['attrs']
  end
end

def verifyAndFixDestVol
  vol = nil
  mntpt = nil
  if @opt['v']
    vol = @opt['v']
    @opt['v'].sub!(":\\/", ":")
    @opt['v'].sub!(":\\", ":")
    if @opt['v'].include?(":")
      vol, extraPath = @opt['v'].split(":")
    end
  end

  if not @opt['p']
    # First see whether it is NSS
    @nss = false
    volume, mntpt = get_nss_vol_and_mnt_point_from_vol vol
    if volume == nil or mntpt == nil
      # That means given volume is not NSS. Try for NCP
      vol, mntpt = get_ncp_vol_and_mnt_point_from_vol(vol)
      if vol == nil or mntpt == nil
        print_message(FATAL, "Given volume #{@opt['v']} is neither NSS nor NCP")
        exit 1
      else
        print_message(INFO, "Given destination volume is of type NCP")
      end
    else
      @nss = true
      print_message(INFO, "Given destination volume is of type NSS")
    end
  else
    print_message(FATAL, "Cannot use --destination-volume option with POSIX migration. Use --destination-path instead.")
    printUsage($argdef)
    exit 1
  end
=begin
  if not @opt['p']
    vol, mntpt = get_nss_vol_and_mnt_point_from_vol @opt['v']
    if vol == nil
      print_message(FATAL, "the specified NSS  volume  #{@opt['v']} does not exist on the destination server.")

      exit 1
    end
      return mntpt.chomp("/") + "/"   #if the path ends with / it wont add /, else it will
  else
    vol, mntpt = get_ncp_vol_and_mnt_point_from_vol @opt['v']
    if vol == nil
      print_message(FATAL, "The specified NCP volume  #{@opt['v']} does not exist on the destination server.")
      exit 1
    end
    return mntpt.chomp("/") + "/"   #if the path ends with / it wont add /, else it will
  end
=end
end

def main
  $argdef = [
    ['p', 'posix', "maps  rights to POSIX", ParseArgBool],
    ['V', 'source-path', "path to be migrated from the source server", ParseArgText, true],
    ['v', 'destination-path', "specifies the volume to migrate rights to on the destination server", ParseArgText],
    ['x', 'destination-full-path', "specifies an absolute destination path for rights.Can be used for NSS, NCP, or POSIX data migration", ParseArgText],
    ['k', 'destination-ldap-container', "option to specify ldap container where all users and groups have been migrated", ParseArgText],
    ['m', 'maptrustees-file', "specify the name of the maptrustees file associated with this maprights migration.  Required for POSIX rights mapping", ParseArgText],
    [nil, 'matchup-file', "the object matchup file generated by migmatchup", ParseArgText],
    [nil, "inputfile", "the input file (usually produced by running 'mls').  If not given on the commandline, will read from stdin", ParseArgOrd],
    ['c', "session-file", "stores the migration progress, this file can be used to continue the migration", ParseArgText],
    [nil, 'progress', "display progress in terms of number of objects processed", ParseArgBool],
    [nil, 'progress-interval', "time interval for displaying progress", ParseArgText],
    [nil, "precheck", "checks whether system meets all pre-requisite to start maprights", ParseArgBool],
    [nil, "debug", "generate debug log",ParseArgBool],
    ParseArgHelp,
    ParseArgUsage
  ]
  
  @init = true
  maprightsCommand = "#{$0}"
  debugOpt = false
  $*.each do |arg|
    if arg.strip == "--debug"
      debugOpt = true
    end
    maprightsCommand += " #{arg}"
  end
  initLog(debugOpt, "maprights")
  print_message(DEBUG, "maprights command started at #{`date +%d-%m-%y\\ %H:%M:%S`}")
  print_message(DEBUG, "maprights command executed as: #{maprightsCommand}\n")

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

  @firstTime = true
  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['n'] and @opt['p']
    print_message(FATAL, "Cannot specify --ncp(-n) and --posix(-p)  options together")
    printUsage($argdef)
    exit 1
  end

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

  if @opt['x'] and @opt['v']
    print_message(FATAL, "Cannot specify --destination-volume(-v) and --destination-path(-x) options together")
    printUsage($argdef)
    exit 1
  end

  if not @opt['x'] and not @opt['v']
    print_message(FATAL, "Must specify either of --destination-volume(-v) or --destination-path(-x)")
    printUsage($argdef)
    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['p'] 
    if not @opt['m']
      print_message(FATAL, "Must specify --maptrustees-file(-m) with --posix(-p) option")
      printUsage($argdef)
      exit 1
    end
    if @opt['v']
      print_message(FATAL, "For --posix(-p) you need to use the --destination-path(-x) option")
      printUsage($argdef)
      exit 1
    end
    if not @opt['x']
      print_message(FATAL, "Must specify --destination-path(-x) with --posix(-p) option")
      printUsage($argdef)
      exit 1
    end
    if @opt['matchup-file']
      print_message(FATAL, "Cannot specify --matchup-file with --posix(-p) option")
      printUsage($argdef)
      exit 1
    end
  end

  if @opt['x']
    if @opt['p']
      @file_path = @opt['x']
    else
      @nss, @file_path = convert_linux_path_to_nss_path(@opt['x'])
      if @file_path == nil
        print_message(FATAL, "NSS or NCP migration specified and the --destination-path specified is not of that type")
        exit 1
      end
    end
    dest_path = @opt['x']
  elsif @opt['v']
  
    # check if the destination volume exists on the target server
    verifyAndFixDestVol
    dest_path = @opt['v']
  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 filemap.class == String
    input = File.open filemap,"r"
  else
    input = filemap
  end

  if @opt['m']
    if File.exist?(@opt['m']) and File.size(@opt['m']) > 0
      u = YAML.load(File.open(@opt['m'], "r"))
    else
      print_message(INFO, "Specified maptrustees file '#{@opt['m']}' either does not exist or it is empty")
    end
  end
  if @opt['matchup-file']
    if not File.exist?(@opt['matchup-file']) or not File.size(@opt['matchup-file']) > 0
      print_message(INFO, "Specified matchup file '#{@opt['matchup-file']}' either does not exist or it is empty")
    end
  end
  @matchup = {}
  if @opt['matchup-file'] != nil and 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
      if tempMatchup != nil
        tempMatchup.each { |key, value|
          @matchup[key.downcase] = value
        }
      end
    rescue
      print_message(FATAL, "Unable to parse matchup file '#{@opt['matchup-file']}'. Error: #{$!.message}")
      exit 1
    end
  end

  if input != $stdin
    Psych.load_stream(input) do |doc|
      if doc != nil
        doc.each do |obj|
          if doc.class == FalseClass
            print_message(FATAL, "Input file not in correct format")
            exit 1
          end
          break
        end
        break
      end
    end
    # Set the input pointer to 0
    input.seek(0, IO::SEEK_SET)
  end
  if @opt['precheck']
    print_message(INFO, "Validation for maprights is done successfully")
    exit 0
  end
  
  print_message(DEBUG, "Loaded the input file successfully")
  objNumber = 0
  @jobDone = false
  if @opt['c']
    $session =  "---\n"
    $session << "  started-on: #{`date`}"
    $session << "  src-path: #{@opt['V']}\n"
    $session << "  dest-path: #{@opt['v']}\n" if @opt['v']
    $session << "  dest-path: #{@opt['x']}\n" if @opt['x']
    $session << "  utility: maprights\n"
    # Session file specified, check whether it exists...
    if File.exist?(@opt['c']) and File.size?(@opt['c']) != 0
      sessionInput = File.open(@opt['c'], "r")
      sessionInfo = YAML.load(sessionInput)
      if sessionInfo['utility'] != "maprights" or
         sessionInfo['src-path'] != @opt['V'] or 
         sessionInfo['dest-path'] != dest_path
        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 migtrustees'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 @opt['progress'] and input != $stdin
    print_message(INFO, "precomputing for displaying progress")
    # Find out the total Number of objects has to be processed.
    @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
  @cnt = 0
  @outcnt = 0
  @init = false
  Psych.load_stream(input) do |doc|
    if doc != nil
      doc.each do |obj|
        if @cnt >= objNumber
          if @opt['p']
            map_posix obj, u
          else
            map_ncp obj
          end
          writeToSessionFile(@jobDone, @cnt) if @opt['c'] and @cnt != 0 and @cnt % 20 == 0
          # Print '---', to tell YAML load only NO_OF_OBJECTS_PER_RECORD in YAML.load function
          puts "---" if @outcnt != 0 and @outcnt % NO_OF_OBJECTS_PER_RECORD == 0
        end
        @cnt += 1
      end
    end
  end
  input.close if input.class == File
  if @opt['c']
    @jobDone = true
    writeToSessionFile(@jobDone, @cnt)
  end
  print_message(PROGRESS, "Mapped rights for #{@totObjs} files of #{@totObjs}") if @opt['progress']
  print_message(DEBUG, "maprights completed successfully at #{`date +%d-%m-%y\\ %H:%M:%S`}")
end
                                          
begin
  trap("ALRM"){
    print_message(PROGRESS, "Mapped rights for #{@cnt} files 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.to_s)
  exit e.code 
rescue MigSyntaxError => e
  print_message(FATAL, "MigSyntaxError, " + e.to_s)
  printUsage($argdef)
  exit e.code
rescue Interrupt
  print_message(DEBUG, "Interrupted by user")
  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 
  $log.close if @opt['debug']
end

