#!/usr/bin/python3
# -*- coding: utf-8 -*-

# This file is part of pamac.
#
#    Copyright (C) 2017-2019  Alexander Naumov <alexander_naumov@opensuse.org>
#
#    PAMAC is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    PAMAC is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with PAMAC.  If not, see <http://www.gnu.org/licenses/>.

import subprocess as sp
import os, sys, re, glob, shutil, platform

VERSION = "v0.97 Beta"
PATH_PAM = "/etc/pam.d/"

def printf(clr, string):
  if   clr == "RED":   print('\x1b[0;30;41m' + string + '\x1b[0m')
  elif clr == "red":   print('\x1b[0;31;48m' + string + '\x1b[0m', end='')
  elif clr == "GREEN": print('\x1b[0;30;42m' + string + '\x1b[0m')
  elif clr == "green": print('\x1b[0;32;48m' + string + '\x1b[0m', end='')
  elif clr == "orange":print('\x1b[38;5;9m'  + string + '\x1b[0m', end='')
  else:                print(string, end='')
  print("-------------------------------------------------------")

def usage():
  print ("pamac " + VERSION + "\n")
  print ("Usage: pamac [Option] [Service | File]")
  print ("\nOPTIONS:")
  print ("  show-pam-config")
  print ("  show-pam-info [Service]")
  print ("  pam-list")
  print ("  pam-configure [Service]")
  print ("  make-pam-clean [Service]")
  print ("  check-my-config [File]")
  print ("\nEXAMPLES:")
  print ("  pamac check-my-config /etc/pamac.d/pamac.conf")
  print ("  pamac show-pam-info sshd login")
  print ("  pamac pam-configure sshd")
  print ("\nDocumentation: man pages pamac(8) and pamac.conf(5)")
  sys.exit()


def distribution():
  """
  It checks GNU distribution running on. Addition methods are preferable.

  FIXME: add check for empty returned list

  Input : VOID
  Output: STRING "DEB" or "RPM", depend on distribution
  """
  if platform.linux_distribution()[0] in ['Ubuntu', 'Debian']:
    return 'DEB'
  if platform.linux_distribution()[0] in ['CentOS Linux', 'Fedora', 'openSUSE', 'SuSE']:
    return 'RPM'
  if len(platform.linux_distribution()[0]) == 0:
    if os.path.exists("/etc/SUSE-brand"): return "RPM"
    if os.path.exists("/etc/redhat-release"): return "RPM"
    if os.path.exists("/etc/fedora-release"): return "RPM"
    if os.path.exists("/etc/debian_version"): return "DEB"
  else:
    print ("error: can't get this information...")
    sys.exit(2)  


def show_info(pam):
  """
  This function try to get description from the packaging database via rpm(1)
  or dpkg-query(1).

  Input : LIST of pam services.
  Output: VOID
  """
  for p in pam:
    if p in pam_list("relative"):
      if distribution() == "DEB":
        CMD = "dpkg-query -W -f='${Description}\n' $(dpkg -S /etc/pam.d/" + p + " | cut -d':' -f1)"
      if distribution() == "RPM":
        print("RPM")
        CMD = "rpm -q --queryformat '%{Description}\n' $(rpm -qf /etc/pam.d/" + p + ")"
      print ("-----------------------------------------------------------------------")
      print ("Information about " + PATH_PAM + p + ": ")
      print (sp.getoutput(CMD))


def pam_list(type):
  """
  Simple, but a very important function. This should be used as a check for
  pam files. KISS principle is highly preferable here ;-)

  Input : STRINGs: "realative" for realative PATHs and all others strings for absolute PATH.
  Output: LIST of pam files.
  """
  if type == "relative":
    LIST = []
    for i in glob.glob(PATH_PAM + "*"):
      LIST.append(i[11:])
    return LIST
  else:
    return glob.glob(PATH_PAM + "*")


def cleaning(pam):
  """
  It removes information about pamac configuration from pam files.
  Again - it writes to /etc/pam.d/* files. Be extremely careful here!

  Input : LIST of pam services
  Output: VOID
  """
  for p in pam:    
    if p not in pam_list("relative"):
      print("error: can't find this PAM configuration file")
      sys.exit(2)
    else:
      try:
        with open(PATH_PAM + p,"r") as input:
          with open(PATH_PAM + "." + p,"w") as output: 
            for line in input:
              if not re.search("accesscontrol",line):
                output.write(line)
      except OSError as err:
        print("OS error: {0}".format(err))
        sys.exit(2)

      try:
        shutil.move(PATH_PAM + "." + p, PATH_PAM + p)
      except OSError as err:
        print("OS error: {0}".format(err))
        sys.exit(2)
  print("ok")


def configure(pam):
  """
  It adds pamac configuration to the /etc/pam.d/* files.
  Again - it writes to /etc/pam.d/* files. Be extremely careful here!

  Input : LIST of pam services
  Output: VOID
  """
  for p in pam:
    if p not in pam_list("relative"):
      print("error: can't find this PAM configuration file")
      sys.exit(2)
    else:
      try:
        with open(PATH_PAM + p, "a") as pam_file:
          pam_file.write("\n#PAMAC configuration\n")
          pam_file.write("auth        required     pam_python.so accesscontrol.py\n")
          pam_file.write("session     required     pam_python.so accesscontrol.py\n")
      except OSError as err:
        print("OS error: {0}".format(err))
        sys.exit(2)
  print("ok")


def show_config(show=False):
  """
  It parses pam files and shows pamac configuration in it.

  Input : BOOL. If 'show' sets to True, it uses stdout.
  Output: LIST of correctly configured /etc/pam.d/* services.
  """
  count = 0
  configured = []
  for FILE in pam_list("absolute"):
    if show==True:
      print ("-------------------------------------------------------" +
              "-------------------------------------------------")
    with open(FILE, 'r') as fd:
      count = 0
      for line in fd:
        if re.search("accesscontrol",line):
          if count == 0:
            if show==True: print (FILE, "\t\t\t\t", line, end="")
          else:
            if show==True: print ("\t\t\t\t\t\t", line, end="")
          count = count + 1
          if not re.search('#', line):
            configured.append(FILE)
    if count == 0:
      if show==True: print (FILE)
  return list(dict(zip(configured,configured)).values())


def check_user_config(files):
  """
  It makes a list of checks (syntax) for user's configuraion file.

  Input:  LIST of STRINGs, list of files to check
  Output: VOID
  """
  if len(files) == 0:
    files = ['/etc/pamac.d/pamac.conf']

  for FILE in files:
    if not os.path.exists(FILE):
      print ("Dammit! I can't find this file: "+ FILE)
      sys.exit(2)

    try:
      with open(FILE, 'r') as config_file:
        printf ("GREEN", "\nFILE: " + FILE)
        for rule in config_file.readlines():
          rule = rule.upper()
          if rule[0] == "#" or not rule.strip():
            continue

          if rule[:8] == "DEFAULT:":
            if rule[8:12] == "CLOSE" or rule[8:11] == "OPEN":
              printf ("red", "DEFAULT should be CLOSE or OPEN: \n\n" + rule)
            else:
              printf ("green", rule)
            continue

          if rule[:6] == "DEBUG:":
            if rule[6:10] == "TRUE" or rule[6:11] == "FALSE":
              printf ("green", rule)
            else:
              printf ("red", "DEBUG should be TRUE or FALSE: \n\n" + rule)
            continue

          if len(rule.split(" ")) != 4:
            printf ("red", "Broken rule, wrong options number:\n\n" + rule)

          elif rule.split(" ")[0] not in [pam.upper() for pam in pam_list("relative") + ["sshd-key"]]:
            printf ("red", "Broken rule, unknown PAM service: \n" + rule)

          elif rule.split(" ")[0] not in list(map(lambda x: x[11:].upper(), show_config(False))) + ["SSHD-KEY"]:
            printf ("orange", "Warning: PAM service is in user's config,\n" +
                              "but it's not configured in /etc/pam.d/* file.\n" +
                              "To fix it, use: pamac pam-configure " +
                              str(rule.split(" ")[0].lower()) + "\n\n" + rule)

          elif rule.split(" ")[1] not in ['OPEN', 'CLOSE', 'ASK','NUMBER']:
            printf ("red", "Broken rule, second parameter is wrong: \n\n" + rule)

          elif rule.split(" ")[2] not in ['USER', 'GROUP']:
            printf ("red", "Broken rule, third parameter is wrong : \n\n" + rule)

          else:
            printf ("green", rule)

    except OSError as err:
      print("OS error: {0}".format(err))
      sys.exit(2)

    if os.path.isfile("/etc/sestatus.conf"):
      try:
        if sp.getoutput("sestatus | grep SELinux | grep status").split(" ")[-1] == "enabled":
          printf("orange", "Warning: SELinux is enabled.\n" +
                 "Notification windows (option 'ASK') could be blocked.\n" +
                 "Write acces to the /var/log could be also blocked.\n" +
                 "In this case access will be not possible.\n")
      except OSError as err:
        print("Can't get info about SELinux status...")


def test_window():
    """
    It calls list of window-tests.

    Input: VOID
    Output: VOID
    """
    for test in ['ask','info','xorg']:
      try:
        print(sp.call('/usr/share/pamac/windows.py ' + test + ' HOST USER PAM-SERVICE',
            stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE, shell=True))
      except OSError as err:
        print("Something goes wrong: {0}".format(err))


def print_format_table():
    """
    It prints table of formatted text format options. Just as a demo here.

    Input:  VOID
    Output: VOID
    """
    for style in range(8):
        for fg in range(30, 38):
            s1 = ''
            for bg in range(40,48):
                format = ';'.join([str(style), str(fg), str(bg)])
                s1 += '\x1b[%sm %s \x1b[0m' % (format, format)
            print(s1)
        print('\n')


if __name__ == '__main__':
  if len(sys.argv) == 2 and sys.argv[1] == "pam-list":
    for i in pam_list("relative"):
      print (i)

  elif len(sys.argv) == 2 and sys.argv[1] == "show-pam-config":  show_config(True)
  elif len(sys.argv)  > 2 and sys.argv[1] == "show-pam-info":    show_info(sys.argv[2:])
  elif len(sys.argv)  > 2 and sys.argv[1] == "pam-configure":    configure(sys.argv[2:])
  elif len(sys.argv)  > 2 and sys.argv[1] == "make-pam-clean":   cleaning(sys.argv[2:])
  elif len(sys.argv) >= 2 and sys.argv[1] == "check-my-config":  check_user_config(sys.argv[2:])
  elif len(sys.argv) == 2 and sys.argv[1] == "color-table":      print_format_table()
  elif len(sys.argv) == 2 and sys.argv[1] == "test-window":      test_window()
  else: usage()
  sys.exit()
