# Microsoft Azure Linux Agent
#
# Copyright 2018 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Requires Python 2.6+ and Openssl 1.0+

import socket
import struct
import binascii
import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.utils.shellutil as shellutil
import azurelinuxagent.common.utils.textutil as textutil
import azurelinuxagent.common.logger as logger
from azurelinuxagent.common.exception import OSUtilError
from azurelinuxagent.common.osutil.default import DefaultOSUtil
from azurelinuxagent.common.future import ustr


class FreeBSDOSUtil(DefaultOSUtil):

    def __init__(self):
        super(FreeBSDOSUtil, self).__init__()
        self._scsi_disks_timeout_set = False
        self.jit_enabled = True

    @staticmethod
    def get_agent_bin_path():
        return "/usr/local/sbin"

    def set_hostname(self, hostname):
        rc_file_path = '/etc/rc.conf'
        conf_file = fileutil.read_file(rc_file_path).split("\n")
        textutil.set_ini_config(conf_file, "hostname", hostname)
        fileutil.write_file(rc_file_path, "\n".join(conf_file))
        self._run_command_without_raising(["hostname", hostname], log_error=False)

    def restart_ssh_service(self):
        return shellutil.run('service sshd restart', chk_err=False)

    def useradd(self, username, expiration=None, comment=None):
        """
        Create user account with 'username'
        """
        userentry = self.get_userentry(username)
        if userentry is not None:
            logger.warn("User {0} already exists, skip useradd", username)
            return
        if expiration is not None:
            cmd = ["pw", "useradd", username, "-e", expiration, "-m"]
        else:
            cmd = ["pw", "useradd", username, "-m"]
        if comment is not None:
            cmd.extend(["-c", comment])

        self._run_command_raising_OSUtilError(cmd, err_msg="Failed to create user account:{0}".format(username))

    def del_account(self, username):
        if self.is_sys_user(username):
            logger.error("{0} is a system user. Will not delete it.", username)
        self._run_command_without_raising(['touch', '/var/run/utx.active'])
        self._run_command_without_raising(['rmuser', '-y', username])
        self.conf_sudoer(username, remove=True)

    def chpasswd(self, username, password, crypt_id=6, salt_len=10):
        if self.is_sys_user(username):
            raise OSUtilError(("User {0} is a system user, "
                               "will not set password.").format(username))
        passwd_hash = textutil.gen_password_hash(password, crypt_id, salt_len)
        self._run_command_raising_OSUtilError(['pw', 'usermod', username, '-H', '0'], cmd_input=passwd_hash,
                                              err_msg="Failed to set password for {0}".format(username))

    def del_root_password(self):
        err = shellutil.run('pw usermod root -h -')
        if err:
            raise OSUtilError("Failed to delete root password: Failed to update password database.")

    def get_if_mac(self, ifname):
        data = self._get_net_info()
        if data[0] == ifname:
            return data[2].replace(':', '').upper()
        return None

    def get_first_if(self):
        return self._get_net_info()[:2]

    @staticmethod
    def read_route_table():
        """
        Return a list of strings comprising the route table as in the Linux /proc/net/route format. The input taken is from FreeBSDs
        `netstat -rn -f inet` command. Here is what the function does in detail:

        1. Runs `netstat -rn -f inet` which outputs a column formatted list of ipv4 routes in priority order like so:

            > Routing tables
            > 
            > Internet:
            > Destination        Gateway            Flags    Refs      Use  Netif Expire
            > default            61.221.xx.yy       UGS         0      247    em1
            > 10                 10.10.110.5        UGS         0       50    em0
            > 10.10.110/26       link#1             UC          0        0    em0
            > 10.10.110.5        00:1b:0d:e6:58:40  UHLW        2        0    em0   1145
            > 61.221.xx.yy/29    link#2             UC          0        0    em1
            > 61.221.xx.yy       00:1b:0d:e6:57:c0  UHLW        2        0    em1   1055
            > 61.221.xx/24       link#2             UC          0        0    em1
            > 127.0.0.1          127.0.0.1          UH          0        0    lo0
        
        2. Convert it to an array of lines that resemble an equivalent /proc/net/route content on a Linux system like so:

            > Iface   Destination Gateway     Flags   RefCnt  Use Metric  Mask        MTU Window  IRTT
            > gre828  00000000    00000000    0001    0   0   0   000000F8    0   0   0
            > ens160  00000000    FE04700A    0003    0   0   100 00000000    0   0   0
            > gre828  00000008    00000000    0001    0   0   0   000000FE    0   0   0
            > ens160  0004700A    00000000    0001    0   0   100 00FFFFFF    0   0   0
            > gre828  2504700A    00000000    0005    0   0   0   FFFFFFFF    0   0   0
            > gre828  3704700A    00000000    0005    0   0   0   FFFFFFFF    0   0   0
            > gre828  4104700A    00000000    0005    0   0   0   FFFFFFFF    0   0   0

        :ret
urn: Entries in the ipv4 route priority list from `netstat -rn -f inet` in the linux `/proc/net/route` style
        :rtype: list(str)
        """

        def _get_netstat_rn_ipv4_routes():
  
          """
            Runs `netstat -rn -f inet` and parses its output and returns a list of routes where the key is the column name
            and the value is the value in the column, stripped of leading and trailing whitespace.

            :return: List of dictionaries representing routes in the ipv4 route priority list from `netstat -rn -f inet`
            :rtype: list(dict)
            """
            cmd = ["netstat", "-rn", "-f", "inet"]
            output = shellutil.run_command(cmd, log_error=True)
            output_lines = output.split("\n")
            if len(output_lines) < 3:
                raise OSUtilError("`netstat -rn -f inet` output seems to be empty")
            output_lines = [line.strip() for line in output_lines if line]
            if "Internet:" not in output_lines:
                raise OSUtilError("`netstat -rn -f inet` output seems to contain no ipv4 routes")
            route_header_line = output_lines.index("Internet:") + 1
            # Parse the file structure and left justify the routes
            route_start_line = route_header_line + 1
            route_line_length = max([len(line) for line in output_lines[route_header_line:]])
            netstat_route_list = [line.ljust(route_line_length) for line in output_lines[route_start_line:]]
            # Parse the headers
            _route_headers = output_lines[route_header_line].split()
            n_route_headers = len(_route_headers)
            route_columns = {}
            for i in range(0, n_route_headers - 1):
                route_columns[_route_headers[i]] = (
                    output_lines[route_header_line].index(_route_headers[i]),
                    (output_lines[route_header_line].index(_route_headers[i + 1]) - 1)
                )
            route_columns[_route_headers[n_route_headers - 1]] = (
                output_lines[route_header_line].index(_route_headers[n_route_headers - 1]),
                None
            )
            # Parse the routes
            netstat_routes = []
            n_netstat_routes = len(netstat_route_list)
  