#!/usr/bin/python
#
# Windows Azure Linux Agent
#
# Copyright 2012 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.4+ and Openssl 1.0+
#
# Implements parts of RFC 2131, 1541, 1497 and
# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
#

import array
import base64
import httplib
import os
import os.path
import platform
import pwd
import re
import shutil
import socket
import SocketServer
import struct
import sys
import tempfile
import textwrap
import threading
import time
import traceback
import xml.dom.minidom

GuestAgentName = "WALinuxAgent"
GuestAgentLongName = "Windows Azure Linux Agent"
GuestAgentVersion = "rd_wala.120504-1323"
ProtocolVersion = "2011-12-31"

Config = None
LinuxDistro = "UNKNOWN"
Verbose = False
WaAgent = None
DiskActivated = False
Openssl = "openssl"

PossibleEthernetInterfaces = ["seth0", "seth1", "eth0", "eth1"]
RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
               "/etc/udev/rules.d/70-persistent-net.rules" ]
VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"]
LibDir = "/var/lib/waagent"

# This lets us index into a string or an array of integers transparently.
def Ord(a):
    if type(a) == type("a"):
        a = ord(a)
    return a

def IsWindows():
    return (platform.uname()[0] == "Windows")

def IsLinux():
    return (platform.uname()[0] == "Linux")

def DetectLinuxDistro():
    global LinuxDistro
    if os.path.isfile("/etc/redhat-release"):
        LinuxDistro = "RedHat"
        return True
    if os.path.isfile("/etc/lsb-release") and "Ubuntu" in GetFileContents("/etc/lsb-release"):
        LinuxDistro = "Ubuntu"
        return True
    if os.path.isfile("/etc/debian_version"):
        LinuxDistro = "Debian"
        return True
    if os.path.isfile("/etc/SuSE-release"):
        LinuxDistro = "Suse"
        return True
    return False

def IsRedHat():
    return "RedHat" in LinuxDistro

def IsUbuntu():
    return "Ubuntu" in LinuxDistro

def IsDebian():
    return IsUbuntu() or "Debian" in LinuxDistro

def IsSuse():
    return "Suse" in LinuxDistro

def UsesRpm():
    return IsRedHat() or IsSuse()

def UsesDpkg():
    return IsDebian()

def GetLastPathElement(path):
    return path.rsplit('/', 1)[1]

def GetFileContents(filepath):
    file = None
    try:
        file = open(filepath)
    except:
        return None
    if file == None:
        return None
    try:
        return file.read()
    finally:
        file.close()

def SetFileContents(filepath, contents):
    file = open(filepath, "w")
    try:
        file.write(contents)
    finally:
        file.close()

def AppendFileContents(filepath, contents):
    file = open(filepath, "a")
    try:
        file.write(contents)
    finally:
        file.close()

def ReplaceFileContentsAtomic(filepath, contents):
    handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath))
    try:
        os.write(handle, contents)
    finally:
        os.close(handle)
    try:
        os.rename(temp, filepath)
        return
    except:
        pass
    os.remove(filepath)
    os.rename(temp, filepath)

def GetLineStartingWith(prefix, filepath):
    for line in GetFileContents(filepath).split('\n'):
        if line.startswith(prefix):
            return line
    return None

def Run(a):
    LogIfVerbose(a)
    return os.system(a)

def GetNodeTextData(a):
    for b in a.childNodes:
        if b.nodeType == b.TEXT_NODE:
            return b.data

def GetHome():
    home = None
    try:
        home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip()
    except:
        pass
    if (home == None) or (home.startswith("/") == False):
        home = "/home"
    return home

def ChangeOwner(filepath, user):
    p = None
    try:
        p = pwd.getpwnam(user)
    except:
        pass
    if p != None:
        os.chown(filepath, p[2], p[3])

def CreateDir(dirpath, user, mode):
    try:
        os.makedirs(dirpath, mode)
    except:
        pass
    ChangeOwner(dirpath, user)

def CreateAccount(user, password, expiration, thumbprint):
    if IsWindows():
        Log("Skipping CreateAccount on Windows")
        return None
    userentry = None
    try:
        userentry = pwd.getpwnam(user)
    except:
        pass
    uidmin = None
    try:
        uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
    except:
        pass
    if uidmin == None:
        uidmin = 100
    if userentry != None and userentry[2] < uidmin:
        Error("CreateAccount: " + user + " is a system user. Will not set password.")
        return "Failed to set password for system user: " + user + " (0x06)."
    if userentry == None:
        command = "useradd -m " + user
        if expiration != None:
            command += " -e " + expiration.split('.')[0]
        if Run(command):
            Error("Failed to create user account: " + user)
            return "Failed to create user account: " + user + " (0x07)."
    else:
        Log("CreateAccount: " + user + " already exists. Will update password.")
    if password != None:
        os.popen("chpasswd", "w").write(user + ":" + password + "\n")
    try:
        if password == None:
            SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
        else:
            SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
        os.chmod("/etc/sudoers.d/waagent", 0440)
    except:
        Error("CreateAccount: Failed to configure sudo access for user.")
        return "Failed to configure sudo privileges (0x08)."
    home = GetHome()
    if thumbprint != None:
        dir = home + "/" + user + "/.ssh"
        CreateDir(dir, user, 0700)
        pub = dir + "/id_rsa.pub"
        prv = dir + "/id_rsa"
        Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
        SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
        for f in [pub, prv]:
            os.chmod(f, 0600)
            ChangeOwner(f, user)
        SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
        ChangeOwner(dir + "/authorized_keys", user)
    Log("Created user account: " + user)
    return None

def DeleteAccount(user):
    if IsWindows():
        Log("Skipping DeleteAccount on Windows")
        return
    userentry = None
    try:
        userentry = pwd.getpwnam(user)
    except:
        pass
    if userentry == None:
        Error("DeleteAccount: " + user + " not found.")
        return
    uidmin = None
    try:
        uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
    except:
        pass
    if uidmin == None:
        uidmin = 100
    if userentry[2] < uidmin:
        Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
        return
    Run("userdel -f -r " + user)
    try:
        os.remove("/etc/sudoers.d/waagent")
    except:
        pass
    return

def ReloadSshd():
    name = None
    if IsRedHat() or IsSuse():
        name = "sshd"
    if IsDebian():
        name = "ssh"
    if name == None:
        return
    if not Run("service " + name + " status | grep running"):
        Run("service " + name + " reload")

def IsInRangeInclusive(a, low, high):
    return (a >= low and a <= high)

def IsPrintable(ch):
    return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9'))

def HexDump(buffer, size):
    if size < 0:
        size = len(buffer)
    result = ""
    for i in range(0, size):
        if (i % 16) == 0:
            result += "%06X: " % i
        byte = struct.unpack("B", buffer[i])[0]
        result += "%02X " % byte
        if (i & 15) == 7:
            result += " "
        if ((i + 1) % 16) == 0 or (i + 1) == size:
            j = i
            while ((j + 1) % 16) != 0:
                result += "   "
                if (j & 7) == 7:
                    result += " "
                j += 1
            result += " "
            for j in range(i - (i % 16), i + 1):
                byte = struct.unpack("B", buffer[j])[0]
                k = '.'
                if IsPrintable(byte):
                    k = chr(byte)
                result += k
            if (i + 1) != size:
                result += "\n"
    return result

def ThrottleLog(counter):
    # Log everything up to 10, every 10 up to 100, then every 100.
    return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0)

def Logger():
    class T(object):
        def __init__(self):
            self.File = None

    self = T()

    def LogToFile(message):
        FilePath = ["/var/log/waagent.log", "waagent.log"][IsWindows()]
        if not os.path.isfile(FilePath) and self.File != None:
            self.File.close()
            self.File = None
        if self.File == None:
            self.File = open(FilePath, "a")
        self.File.write(message + "\n")
        self.File.flush()

    def Log(message):
        LogWithPrefix("", message)

    def LogWithPrefix(prefix, message):
        t = time.localtime()
        t = "%04u/%02u/%02u %02u:%02u:%02u " % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
        t += prefix
        for line in message.split('\n'):
            line = t + line
            print(line)
            LogToFile(line)

    return Log, LogWithPrefix

Log, LogWithPrefix = Logger()

def NoLog(message):
    pass

def LogIfVerbose(message):
    if Verbose == True:
        Log(message)

def LogWithPrefixIfVerbose(prefix, message):
    if Verbose == True:
        LogWithPrefix(prefix, message)

def Warn(message):
    LogWithPrefix("WARNING:", message)

def Error(message):
    LogWithPrefix("ERROR:", message)

def ErrorWithPrefix(prefix, message):
    LogWithPrefix("ERROR:" + prefix, message)

def Linux_ioctl_GetIpv4Address(ifname):
    import fcntl
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])

def Linux_ioctl_GetInterfaceMac(ifname):
    import fcntl
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    info = fcntl.ioctl(s.fileno(), 0x8927,  struct.pack('256s', ifname[:15]))
    return ''.join(['%02X' % Ord(char) for char in info[18:24]])

def GetIpv4Address():
    if IsLinux():
        for ifname in PossibleEthernetInterfaces:
            try:
                return Linux_ioctl_GetIpv4Address(ifname)
            except IOError, e:
                pass
    else:
        try:
            return socket.gethostbyname(socket.gethostname())
        except Exception, e:
            ErrorWithPrefix("GetIpv4Address:", str(e))
            ErrorWithPrefix("GetIpv4Address:", traceback.format_exc())

def HexStringToByteArray(a):
    b = ""
    for c in range(0, len(a) / 2):
        b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16))
    return b

def GetMacAddress():
    if IsWindows():
        # Windows:   Physical Address. . . . . . . . . : 00-15-17-79-00-7F\n
        a = "ipconfig /all | findstr /c:\"Physical Address\" | findstr /v \"00-00-00-00-00-00-00\""
        a = os.popen(a).read()
        a = re.sub("\s+$", "", a)
        a = re.sub(".+ ", "", a)
        a = re.sub(":", "", a)
        a = re.sub("-", "", a)
    else:
        for ifname in PossibleEthernetInterfaces:
            try:
                a = Linux_ioctl_GetInterfaceMac(ifname)
                break
            except IOError, e:
                pass
    return HexStringToByteArray(a)

def DeviceForIdePort(n):
    if n > 3:
        return None
    g0 = "00000000"
    if n > 1:
        g0 = "00000001"
        n = n - 2
    device = None
    path="/sys/bus/vmbus/devices/"
    for vmbus in os.listdir(path):
        guid=GetFileContents(path + vmbus + "/device_id").lstrip('{').split('-')
        if guid[0] == g0 and guid[1] == "000" + str(n):
            for root, dirs, files in os.walk(path + vmbus):
                if root.endswith("/block"):
                    device = dirs[0]
                    break
            break
    return device

class Util(object):
    def _HttpGet(self, url, headers):
        LogIfVerbose("HttpGet(" + url + ")")
        maxRetry = 2
        if url.startswith("http://"):
            url = url[7:]
            url = url[url.index("/"):]
        for retry in range(0, maxRetry + 1):
            strRetry = str(retry)
            log = [NoLog, Log][retry > 0]
            log("retry HttpGet(" + url + "),retry=" + strRetry)
            response = None
            strStatus = "None"
            try:
                httpConnection = httplib.HTTPConnection(self.Endpoint)
                if headers == None:
                    request = httpConnection.request("GET", url)
                else:
                    request = httpConnection.request("GET", url, None, headers)
                response = httpConnection.getresponse()
                strStatus = str(response.status)
            except:
                pass
            log("response HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
            if response == None or response.status != httplib.OK:
                Error("HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
                if retry == maxRetry:
                    Log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
                    return None
                else:
                    Log("sleep 10 seconds HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
                    time.sleep(10)
            else:
                log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
                return response.read()

    def HttpGetWithoutHeaders(self, url):
        return self._HttpGet(url, None)

    def HttpGetWithHeaders(self, url):
        return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion})

    def HttpSecureGetWithHeaders(self, url, transportCert):
        return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName,
                                   "x-ms-version": ProtocolVersion,
                                   "x-ms-cipher-name": "DES_EDE3_CBC",
                                   "x-ms-guest-agent-public-x509-cert": transportCert})

    def HttpPost(self, url, data):
        LogIfVerbose("HttpPost(" + url + ")")
        maxRetry = 2
        for retry in range(0, maxRetry + 1):
            strRetry = str(retry)
            log = [NoLog, Log][retry > 0]
            log("retry HttpPost(" + url + "),retry=" + strRetry)
            response = None
            strStatus = "None"
            try:
                httpConnection = httplib.HTTPConnection(self.Endpoint)
                request = httpConnection.request("POST", url, data, {"x-ms-agent-name": GuestAgentName,
                                                                     "Content-Type": "text/xml; charset=utf-8",
                                                                     "x-ms-version": ProtocolVersion})
                response = httpConnection.getresponse()
                strStatus = str(response.status)
            except:
                pass
            log("response HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
            if response == None or (response.status != httplib.OK and response.status != httplib.ACCEPTED):
                Error("HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
                if retry == maxRetry:
                    Log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
                    return None
                else:
                    Log("sleep 10 seconds HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
                    time.sleep(10)
            else:
                log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
                return response

def LoadBalancerProbeServer(port):

    class T(object):
        def __init__(self, port):
            enabled = Config.get("LBProbeResponder")
            if enabled != None and enabled.lower().startswith("n"):
                return
            self.ProbeCounter = 0
            self.server = SocketServer.TCPServer((GetIpv4Address(), port), TCPHandler)
            self.server_thread = threading.Thread(target = self.server.serve_forever)
            self.server_thread.setDaemon(True)
            self.server_thread.start()

        def shutdown(self):
            global EnableLoadBalancerProbes
            if not EnableLoadBalancerProbes:
                return
            self.server.shutdown()

    class TCPHandler(SocketServer.BaseRequestHandler):
        def GetHttpDateTimeNow(self):
            # Date: Fri, 25 Mar 2011 04:53:10 GMT
            return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())

        def handle(self):
            context.ProbeCounter = (context.ProbeCounter + 1) % 1000000
            log = [NoLog, LogIfVerbose][ThrottleLog(context.ProbeCounter)]
            strCounter = str(context.ProbeCounter)
            if context.ProbeCounter == 1:
                Log("Receiving LB probes.")
            log("Received LB probe # " + strCounter)
            self.request.recv(1024)
            self.request.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Type: text/html\r\nDate: " + self.GetHttpDateTimeNow() + "\r\n\r\nOK")

    context = T(port)
    return context

class ConfigurationProvider(object):
    def __init__(self):
        self.values = dict()
        if os.path.isfile("/etc/waagent.conf") == False:
            raise Exception("Missing configuration in /etc/waagent.conf")
        try:
            for line in GetFileContents("/etc/waagent.conf").split('\n'):
                if not line.startswith("#") and "=" in line:
                    parts = line.split()[0].split('=')
                    value = parts[1].strip("\" ")
                    if value != "None":
                        self.values[parts[0]] = value
                    else:
                        self.values[parts[0]] = None
        except:
            Error("Unable to parse /etc/waagent.conf")
            raise
        return

    def get(self, key):
        return self.values.get(key)

class EnvMonitor(object):
    def __init__(self):
        self.shutdown = False
        self.HostName = socket.gethostname()
        self.server_thread = threading.Thread(target = self.monitor)
        self.server_thread.setDaemon(True)
        self.server_thread.start()
        self.published = False

    def monitor(self):
        publish = Config.get("Provisioning.MonitorHostName")
        dhcpcmd = "pidof dhclient"
        if IsSuse():
            dhcpcmd = "pidof dhcpcd"
        if IsDebian():
            dhcpcmd = "pidof dhclient3"
        dhcppid = os.popen(dhcpcmd).read()
        while not self.shutdown:
            for a in RulesFiles:
                if os.path.isfile(a):
                    if os.path.isfile(GetLastPathElement(a)):
                        os.remove(GetLastPathElement(a))
                    shutil.move(a, ".")
                    Log("EnvMonitor: Moved " + a + " -> " + LibDir)
            if publish != None and publish.lower().startswith("y"):
                try:
                    if socket.gethostname() != self.HostName:
                        Log("EnvMonitor: Detected host name change: " + self.HostName + " -> " + socket.gethostname())
                        self.HostName = socket.gethostname()
                        WaAgent.UpdateAndPublishHostName(self.HostName)
                        dhcppid = os.popen(dhcpcmd).read()
                        self.published = True
                except:
                    pass
            else:
                self.published = True
            pid = ""
            if not os.path.isdir("/proc/" + dhcppid.strip()):
                pid = os.popen(dhcpcmd).read()
            if pid != "" and pid != dhcppid:
                Log("EnvMonitor: Detected dhcp client restart. Restoring routing table.")
                WaAgent.RestoreRoutes()
                dhcppid = pid
            time.sleep(5)

    def SetHostName(self, name):
        if socket.gethostname() == name:
            self.published = True
        else:
            Run("hostname " + name)

    def IsNamePublished(self):
        return self.published

    def shutdown(self):
        self.shutdown = True
        self.server_thread.join()

class Certificates(object):
#
# <CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd">
#  <Version>2010-12-15</Version>
#  <Incarnation>2</Incarnation>
#  <Format>Pkcs7BlobWithPfxContents</Format>
#  <Data>MIILTAY...
#  </Data>
# </CertificateFile>
#
    def __init__(self):
        self.reinitialize()

    def reinitialize(self):
        self.Incarnation = None
        self.Role = None

    def Parse(self, xmlText):
        self.reinitialize()
        SetFileContents("Certificates.xml", xmlText)
        dom = xml.dom.minidom.parseString(xmlText)
        for a in [ "CertificateFile", "Version", "Incarnation",
                   "Format", "Data", ]:
            if not dom.getElementsByTagName(a):
                Error("Certificates.Parse: Missing " + a)
                return None
        node = dom.childNodes[0]
        if node.localName != "CertificateFile":
            Error("Certificates.Parse: root not CertificateFile")
            return None
        SetFileContents("Certificates.p7m",
            "MIME-Version: 1.0\n"
            + "Content-Disposition: attachment; filename=\"Certificates.p7m\"\n"
            + "Content-Type: application/x-pkcs7-mime; name=\"Certificates.p7m\"\n"
            + "Content-Transfer-Encoding: base64\n\n"
            + GetNodeTextData(dom.getElementsByTagName("Data")[0]))
        if Run(Openssl + " cms -decrypt -in Certificates.p7m -inkey TransportPrivate.pem -recip TransportCert.pem | " + Openssl + " pkcs12 -nodes -password pass: -out Certificates.pem"):
            Error("Certificates.Parse: Failed to extract certificates from CMS message.")
            return self
        # There may be multiple certificates in this package. Split them.
        file = open("Certificates.pem")
        pindex = 1
        cindex = 1
        output = open("temp.pem", "w")
        for line in file.readlines():
            output.write(line)
            if line.startswith("-----END PRIVATE KEY-----") or line.startswith("-----END CERTIFICATE-----"):
                output.close()
                if line.startswith("-----END PRIVATE KEY-----"):
                    os.rename("temp.pem", str(pindex) + ".prv")
                    pindex += 1
                else:
                    os.rename("temp.pem", str(cindex) + ".crt")
                    cindex += 1
                output = open("temp.pem", "w")
        output.close()
        os.remove("temp.pem")
        keys = dict()
        index = 1
        filename = str(index) + ".crt"
        while os.path.isfile(filename):
            thumbprint = os.popen(Openssl + " x509 -in " + filename + " -fingerprint -noout").read().rstrip().split('=')[1].replace(':', '').upper()
            pubkey=os.popen(Openssl + " x509 -in " + filename + " -pubkey -noout").read()
            keys[pubkey] = thumbprint
            os.rename(filename, thumbprint + ".crt")
            os.chmod(thumbprint + ".crt", 0600)
            if IsRedHat():
                Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + thumbprint + ".crt")
            index += 1
            filename = str(index) + ".crt"
        index = 1
        filename = str(index) + ".prv"
        while os.path.isfile(filename):
            pubkey = os.popen(Openssl + " rsa -in " + filename + " -pubout").read()
            os.rename(filename, keys[pubkey] + ".prv")
            os.chmod(keys[pubkey] + ".prv", 0600)
            if IsRedHat():
                Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + keys[pubkey] + ".prv")
            index += 1
            filename = str(index) + ".prv"
        return self

class SharedConfig(object):
#
# <SharedConfig version="1.0.0.0" goalStateIncarnation="1">
#   <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
#     <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
#     <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
#   </Deployment>
#   <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
#   <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" settleTimeSeconds="10" />
#   <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8">
#     <Probes>
#       <Probe name="MachineRole" />
#       <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" />
#       <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" />
#     </Probes>
#   </LoadBalancerSettings>
#   <OutputEndpoints>
#     <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS">
#       <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
#     </Endpoint>
#   </OutputEndpoints>
#   <Instances>
#     <Instance id="MachineRole_IN_0" address="10.115.153.75">
#       <FaultDomains randomId="0" updateId="0" updateCount="0" />
#       <InputEndpoints>
#         <Endpoint name="a" address="10.115.153.75:80" protocol="http" isPublic="true" loadBalancedPublicAddress="70.37.106.197:80" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
#           <LocalPorts>
#             <LocalPortRange from="80" to="80" />
#           </LocalPorts>
#         </Endpoint>
#         <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
#           <LocalPorts>
#             <LocalPortRange from="3389" to="3389" />
#           </LocalPorts>
#         </Endpoint>
#         <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" address="10.115.153.75:20000" protocol="tcp" isPublic="true" loadBalancedPublicAddress="70.37.106.197:3389" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
#           <LocalPorts>
#             <LocalPortRange from="20000" to="20000" />
#           </LocalPorts>
#         </Endpoint>
#       </InputEndpoints>
#     </Instance>
#   </Instances>
# </SharedConfig>
#
    def __init__(self):
        self.reinitialize()

    def reinitialize(self):
        self.Deployment = None
        self.Incarnation = None
        self.Role = None
        self.LoadBalancerSettings = None
        self.OutputEndpoints = None
        self.Instances = None

    def Parse(self, xmlText):
        self.reinitialize()
        SetFileContents("SharedConfig.xml", xmlText)
        dom = xml.dom.minidom.parseString(xmlText)
        for a in [ "SharedConfig", "Deployment", "Service",
                   "ServiceInstance", "Incarnation", "Role", ]:
            if not dom.getElementsByTagName(a):
                Error("SharedConfig.Parse: Missing " + a)
                return None
        node = dom.childNodes[0]
        if node.localName != "SharedConfig":
            Error("SharedConfig.Parse: root not SharedConfig")
            return None
        program = Config.get("Role.TopologyConsumer")
        if program != None:
            os.spawnl(os.P_NOWAIT, program, program, LibDir + "/SharedConfig.xml")
        return self

class HostingEnvironmentConfig(object):
#
# <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1">
#   <StoredCertificates>
#     <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" />
#   </StoredCertificates>
#   <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
#     <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
#     <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
#   </Deployment>
#   <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
#   <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" hostingEnvironmentVersion="1" software="" softwareType="ApplicationPackage" entryPoint="" parameters="" settleTimeSeconds="10" />
#   <HostingEnvironmentSettings name="full" Runtime="rd_fabric_stable.110217-1402.RuntimePackage_1.0.0.8.zip">
#     <CAS mode="full" />
#     <PrivilegeLevel mode="max" />
#     <AdditionalProperties><CgiHandlers></CgiHandlers></AdditionalProperties>
#   </HostingEnvironmentSettings>
#   <ApplicationSettings>
#     <Setting name="__ModelData" value="&lt;m role=&quot;MachineRole&quot; xmlns=&quot;urn:azure:m:v1&quot;>&lt;r name=&quot;MachineRole&quot;>&lt;e name=&quot;a&quot; />&lt;e name=&quot;b&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput&quot; />&lt;/r>&lt;/m>" />
#     <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=http;AccountName=osimages;AccountKey=DNZQ..." />
#     <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="MIIBnQYJKoZIhvcN..." />
#     <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2022-07-23T23:59:59.0000000-07:00" />
#     <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="test" />
#     <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true" />
#     <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />
#     <Setting name="Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" value="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" />
#   </ApplicationSettings>
#   <ResourceReferences>
#     <Resource name="DiagnosticStore" type="directory" request="Microsoft.Cis.Fabric.Controller.Descriptions.ServiceDescription.Data.Policy" sticky="true" size="1" path="db00a7755a5e4e8a8fe4b19bc3b330c3.MachineRole.DiagnosticStore\" disableQuota="false" />
#   </ResourceReferences>
# </HostingEnvironmentConfig>
#
    def __init__(self):
        self.reinitialize()

    def reinitialize(self):
        self.StoredCertificates = None
        self.Deployment = None
        self.Incarnation = None
        self.Role = None
        self.HostingEnvironmentSettings = None
        self.ApplicationSettings = None
        self.Certificates = None
        self.ResourceReferences = None

    def Parse(self, xmlText):
        self.reinitialize()
        SetFileContents("HostingEnvironmentConfig.xml", xmlText)
        dom = xml.dom.minidom.parseString(xmlText)
        for a in [ "HostingEnvironmentConfig", "Deployment", "Service",
                   "ServiceInstance", "Incarnation", "Role", ]:
            if not dom.getElementsByTagName(a):
                Error("HostingEnvironmentConfig.Parse: Missing " + a)
                return None
        node = dom.childNodes[0]
        if node.localName != "HostingEnvironmentConfig":
            Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig")
            return None
        self.ApplicationSettings = dom.getElementsByTagName("Setting")
        self.Certificates = dom.getElementsByTagName("StoredCertificate")
        return self

    def DecryptPassword(self, e):
        SetFileContents("password.p7m",
            "MIME-Version: 1.0\n"
            + "Content-Disposition: attachment; filename=\"password.p7m\"\n"
            + "Content-Type: application/x-pkcs7-mime; name=\"password.p7m\"\n"
            + "Content-Transfer-Encoding: base64\n\n"
            + textwrap.fill(e, 64))
        return os.popen(Openssl + " cms -decrypt -in password.p7m -inkey Certificates.pem -recip Certificates.pem").read()

    def ActivateResourceDisk(self):
        global DiskActivated
        if IsWindows():
            DiskActivated = True
            Log("Skipping ActivateResourceDisk on Windows")
            return
        format = Config.get("ResourceDisk.Format")
        if format == None or format.lower().startswith("n"):
            DiskActivated = True
            return
        device = DeviceForIdePort(1)
        if device == None:
            Error("ActivateResourceDisk: Unable to detect disk topology.")
            return
        device = "/dev/" + device
        for entry in os.popen("mount").read().split():
            if entry.startswith(device + "1"):
                Log("ActivateResourceDisk: " + device + "1 is already mounted.")
                DiskActivated = True
                return
        mountpoint = Config.get("ResourceDisk.MountPoint")
        if mountpoint == None:
            mountpoint = "/mnt/resource"
        CreateDir(mountpoint, "root", 0755)
        fs = Config.get("ResourceDisk.Filesystem")
        if fs == None:
            fs = "ext3"
        if os.popen("sfdisk -q -c " + device + " 1").read().rstrip() == "7" and fs != "ntfs":
            Run("sfdisk -c " + device + " 1 83")
            Run("mkfs." + fs + " " + device + "1")
        if Run("mount " + device + "1 " + mountpoint):
            Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "1).")
            return
        Log("Resource disk (" + device + "1) is mounted at " + mountpoint + " with fstype " + fs)
        DiskActivated = True
        swap = Config.get("ResourceDisk.EnableSwap")
        if swap == None or swap.lower().startswith("n"):
            return
        sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024
        if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
            os.remove(mountpoint + "/swapfile")
        if not os.path.isfile(mountpoint + "/swapfile"):
            Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
            Run("mkswap " + mountpoint + "/swapfile")
        if not Run("swapon " + mountpoint + "/swapfile"):
            Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
        else:
            Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")

    def Process(self):
        if DiskActivated == False:
            diskThread = threading.Thread(target = self.ActivateResourceDisk)
            diskThread.start()
        User = None
        Pass = None
        Expiration = None
        Thumbprint = None
        for b in self.ApplicationSettings:
            sname = b.getAttribute("name")
            svalue = b.getAttribute("value")
            if sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword":
                Pass = self.DecryptPassword(svalue)
            elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername":
                User = svalue
            elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration":
                Expiration = svalue
            elif sname == "Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption":
                Thumbprint = svalue.split(':')[1].upper()
        if User != None and Pass != None:
            if User != "root" and User != "" and Pass != "":
                CreateAccount(User, Pass, Expiration, Thumbprint)
            else:
                Error("Not creating user account: user=" + User + " pass=" + Pass)
        for c in self.Certificates:
            cname = c.getAttribute("name")
            csha1 = c.getAttribute("certificateId").split(':')[1].upper()
            cpath = c.getAttribute("storeName")
            clevel = c.getAttribute("configurationLevel")
            if os.path.isfile(csha1 + ".prv"):
                Log("Private key with thumbprint: " + csha1 + " was retrieved.")
            if os.path.isfile(csha1 + ".crt"):
                Log("Public cert with thumbprint: " + csha1 + " was retrieved.")
        program = Config.get("Role.ConfigurationConsumer")
        if program != None:
            os.spawnl(os.P_NOWAIT, program, program, LibDir + "/HostingEnvironmentConfig.xml")

class GoalState(Util):
#
# <GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd">
#   <Version>2010-12-15</Version>
#   <Incarnation>1</Incarnation>
#   <Machine>
#     <ExpectedState>Started</ExpectedState>
#     <LBProbePorts>
#       <Port>16001</Port>
#     </LBProbePorts>
#   </Machine>
#   <Container>
#     <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId>
#     <RoleInstanceList>
#       <RoleInstance>
#         <InstanceId>MachineRole_IN_0</InstanceId>
#         <State>Started</State>
#         <Configuration>
#           <HostingEnvironmentConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=hostingEnvironmentConfig&amp;incarnation=1</HostingEnvironmentConfig>
#           <SharedConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=sharedConfig&amp;incarnation=1</SharedConfig>
#           <Certificates>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&amp;incarnation=1</Certificates>
#         </Configuration>
#       </RoleInstance>
#     </RoleInstanceList>
#   </Container>
# </GoalState>
#
# There is only one Role for VM images.
#
# Of primary interest is:
#  Machine/ExpectedState -- this is how shutdown is requested
#  LBProbePorts -- an http server needs to run here
#  We also note Container/ContainerID and RoleInstance/InstanceId to form the health report.
#  And of course, Incarnation
#
    def __init__(self, Agent):
        self.Agent = Agent
        self.Endpoint = Agent.Endpoint
        self.TransportCert = Agent.TransportCert
        self.reinitialize()

    def reinitialize(self):
        self.Incarnation = None # integer
        self.ExpectedState = None # "Started" or "Stopped"
        self.HostingEnvironmentConfigUrl = None
        self.HostingEnvironmentConfigXml = None
        self.HostingEnvironmentConfig = None
        self.SharedConfigUrl = None
        self.SharedConfigXml = None
        self.SharedConfig = None
        self.CertificatesUrl = None
        self.CertificatesXml = None
        self.Certificates = None
        self.RoleInstanceId = None
        self.ContainerId = None
        self.LoadBalancerProbePort = None # integer, ?list of integers

    def Parse(self, xmlText):
        self.reinitialize()
        node = xml.dom.minidom.parseString(xmlText).childNodes[0]
        if node.localName != "GoalState":
            Error("GoalState.Parse: root not GoalState")
            return None
        for a in node.childNodes:
            if a.nodeType == node.ELEMENT_NODE:
                if a.localName == "Incarnation":
                    self.Incarnation = GetNodeTextData(a)
                elif a.localName == "Machine":
                    for b in a.childNodes:
                        if b.nodeType == node.ELEMENT_NODE:
                            if b.localName == "ExpectedState":
                                self.ExpectedState = GetNodeTextData(b)
                                Log("ExpectedState: " + self.ExpectedState)
                            elif b.localName == "LBProbePorts":
                                for c in b.childNodes:
                                    if c.nodeType == node.ELEMENT_NODE and c.localName == "Port":
                                        self.LoadBalancerProbePort = int(GetNodeTextData(c))
                elif a.localName == "Container":
                    for b in a.childNodes:
                        if b.nodeType == node.ELEMENT_NODE:
                            if b.localName == "ContainerId":
                                self.ContainerId = GetNodeTextData(b)
                                Log("ContainerId: " + self.ContainerId)
                            elif b.localName == "RoleInstanceList":
                                for c in b.childNodes:
                                    if c.localName == "RoleInstance":
                                        for d in c.childNodes:
                                            if d.nodeType == node.ELEMENT_NODE:
                                                if d.localName == "InstanceId":
                                                    self.RoleInstanceId = GetNodeTextData(d)
                                                    Log("RoleInstanceId: " + self.RoleInstanceId)
                                                elif d.localName == "State":
                                                    pass
                                                elif d.localName == "Configuration":
                                                    for e in d.childNodes:
                                                        if e.nodeType == node.ELEMENT_NODE:
                                                            if e.localName == "HostingEnvironmentConfig":
                                                                self.HostingEnvironmentConfigUrl = GetNodeTextData(e)
                                                                LogIfVerbose("HostingEnvironmentConfigUrl:" + self.HostingEnvironmentConfigUrl)
                                                                self.HostingEnvironmentConfigXml = self.HttpGetWithHeaders(self.HostingEnvironmentConfigUrl)
                                                                self.HostingEnvironmentConfig = HostingEnvironmentConfig().Parse(self.HostingEnvironmentConfigXml)
                                                            elif e.localName == "SharedConfig":
                                                                self.SharedConfigUrl = GetNodeTextData(e)
                                                                LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl)
                                                                self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl)
                                                                self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml)
                                                            elif e.localName == "Certificates":
                                                                self.CertificatesUrl = GetNodeTextData(e)
                                                                LogIfVerbose("CertificatesUrl:" + self.CertificatesUrl)
                                                                self.CertificatesXml = self.HttpSecureGetWithHeaders(self.CertificatesUrl, self.TransportCert)
                                                                self.Certificates = Certificates().Parse(self.CertificatesXml)
        if self.Incarnation == None:
            Error("GoalState.Parse: Incarnation missing")
            return None
        if self.ExpectedState == None:
            Error("GoalState.Parse: ExpectedState missing")
            return None
        if self.RoleInstanceId == None:
            Error("GoalState.Parse: RoleInstanceId missing")
            return None
        if self.ContainerId == None:
            Error("GoalState.Parse: ContainerId missing")
            return None
        SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText)
        return self

    def Process(self):
        self.HostingEnvironmentConfig.Process()

class OvfEnv(object):
#
# <?xml version="1.0" encoding="utf-8"?>
# <Environment xmlns="http://schemas.dmtf.org/ovf/environment/1" xmlns:oe="http://schemas.dmtf.org/ovf/environment/1" xmlns:wa="http://schemas.microsoft.com/windowsazure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
#    <wa:ProvisioningSection>
#      <wa:Version>1.0</wa:Version>
#      <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
#        <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
#        <HostName>HostName</HostName>
#        <UserName>UserName</UserName>
#        <UserPassword>UserPassword</UserPassword>
#        <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication>
#        <SSH>
#          <PublicKeys>
#            <PublicKey>
#              <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
#              <Path>$HOME/UserName/.ssh/authorized_keys</Path>
#            </PublicKey>
#          </PublicKeys>
#          <KeyPairs>
#            <KeyPair>
#              <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
#              <Path>$HOME/UserName/.ssh/id_rsa</Path>
#            </KeyPair>
#          </KeyPairs>
#        </SSH>
#      </LinuxProvisioningConfigurationSet>
#    </wa:ProvisioningSection>
# </Environment>
#
    def __init__(self):
        self.reinitialize()

    def reinitialize(self):
        self.WaNs = "http://schemas.microsoft.com/windowsazure"
        self.OvfNs = "http://schemas.dmtf.org/ovf/environment/1"
        self.MajorVersion = 1
        self.MinorVersion = 0
        self.ComputerName = None
        self.AdminPassword = None
        self.UserName = None
        self.UserPassword = None
        self.DisableSshPasswordAuthentication = True
        self.SshPublicKeys = []
        self.SshKeyPairs = []

    def Parse(self, xmlText):
        self.reinitialize()
        dom = xml.dom.minidom.parseString(xmlText)
        if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1:
            Error("Unable to parse OVF XML.")
        section = None
        newer = False
        for p in dom.getElementsByTagNameNS(self.WaNs, "ProvisioningSection"):
            for n in p.childNodes:
                if n.localName == "Version":
                    verparts = GetNodeTextData(n).split('.')
                    major = int(verparts[0])
                    minor = int(verparts[1])
                    if major > self.MajorVersion:
                        newer = True
                    if major != self.MajorVersion:
                        break
                    if minor > self.MinorVersion:
                        newer = True
                    section = p
        if newer == True:
            Warn("Newer provisioning configuration detected. Please consider updating waagent.")
        if section == None:
            Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion))
            return None
        self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0])
        self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0])
        try:
            self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0])
        except:
            pass
        disableSshPass = section.getElementsByTagNameNS(self.WaNs, "DisableSshPasswordAuthentication")
        if len(disableSshPass) != 0:
            self.DisableSshPasswordAuthentication = (GetNodeTextData(disableSshPass[0]).lower() == "true")
        for pkey in section.getElementsByTagNameNS(self.WaNs, "PublicKey"):
            fp = None
            path = None
            for c in pkey.childNodes:
                if c.localName == "Fingerprint":
                    fp = GetNodeTextData(c).upper()
                if c.localName == "Path":
                    path = GetNodeTextData(c)
            self.SshPublicKeys += [[fp, path]]
        for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"):
            fp = None
            path = None
            for c in keyp.childNodes:
                if c.localName == "Fingerprint":
                    fp = GetNodeTextData(c).upper()
                if c.localName == "Path":
                    path = GetNodeTextData(c)
            self.SshKeyPairs += [[fp, path]]
        return self

    def PrepareDir(self, filepath):
        home = GetHome()
        # Expand HOME variable if present in path
        path = os.path.normpath(filepath.replace("$HOME", home))
        if (path.startswith("/") == False) or (path.endswith("/") == True):
            return None
        dir = path.rsplit('/', 1)[0]
        if dir != "":
            CreateDir(dir, "root", 0700)
            if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
                ChangeOwner(dir, self.UserName)
        return path

    def NumberToBytes(self, i):
        result = []
        while i:
            result.append(chr(i&0xFF))
            i >>= 8
        result.reverse()
        return ''.join(result)

    def BitsToString(self, a):
        index=7
        s = ""
        c = 0
        for bit in a:
            c = c | (bit << index)
            index = index - 1
            if index == -1:
                s = s + struct.pack('>B', c)
                c = 0
                index = 7
        return s

    def OpensslToSsh(self, file):
        from pyasn1.codec.der import decoder as der_decoder
        try:
            f = open(file).read().replace('\n','').split("KEY-----")[1].split('-')[0]
            k=der_decoder.decode(self.BitsToString(der_decoder.decode(base64.b64decode(f))[0][1]))[0]
            n=k[0]
            e=k[1]
            keydata=""
            keydata += struct.pack('>I',len("ssh-rsa"))
            keydata += "ssh-rsa"
            keydata += struct.pack('>I',len(self.NumberToBytes(e)))
            keydata += self.NumberToBytes(e)
            keydata += struct.pack('>I',len(self.NumberToBytes(n)) + 1)
            keydata += "\0"
            keydata += self.NumberToBytes(n)
        except Exception, e:
            print("OpensslToSsh: Exception " + str(e))
            return None
        return "ssh-rsa " + base64.b64encode(keydata) + "\n"

    def Process(self):
        error = None
        WaAgent.EnvMonitor.SetHostName(self.ComputerName)
        if self.DisableSshPasswordAuthentication:
            filepath = "/etc/ssh/sshd_config"
            # Disable RFC 4252 and RFC 4256 authentication schemes.
            ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
                (a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")),
                GetFileContents(filepath).split('\n'))) + "PasswordAuthentication no\nChallengeResponseAuthentication no\n")
            Log("Disabled SSH password-based authentication methods.")
        if self.AdminPassword != None:
            os.popen("chpasswd", "w").write("root:" + self.AdminPassword + "\n")
        if self.UserName != None:
            error = CreateAccount(self.UserName, self.UserPassword, None, None)
        sel = os.popen("getenforce").read().startswith("Enforcing")
        if sel == True and IsRedHat():
            Run("setenforce 0")
        home = GetHome()
        for pkey in self.SshPublicKeys:
            if not os.path.isfile(pkey[0] + ".crt"):
                Error("PublicKey not found: " + pkey[0])
                error = "Failed to deploy public key (0x09)."
                continue
            path = self.PrepareDir(pkey[1])
            if path == None:
                Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0])
                error = "Invalid path for public key (0x03)."
                continue
            Run(Openssl + " x509 -in " + pkey[0] + ".crt -noout -pubkey > " + pkey[0] + ".pub")
            if IsRedHat():
                Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + pkey[0] + ".pub")
            if IsUbuntu():
                # Only supported in new SSH releases
                Run("ssh-keygen -i -m PKCS8 -f " + pkey[0] + ".pub >> " + path)
            else:
                SshPubKey = self.OpensslToSsh(pkey[0] + ".pub")
                if SshPubKey != None:
                    AppendFileContents(path, SshPubKey)
                else:
                    Error("Failed: " + pkey[0] + ".crt -> " + path)
                    error = "Failed to deploy public key (0x04)."
            if IsRedHat():
                Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path)
            if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
                ChangeOwner(path, self.UserName)
        for keyp in self.SshKeyPairs:
            if not os.path.isfile(keyp[0] + ".prv"):
                Error("KeyPair not found: " + keyp[0])
                error = "Failed to deploy key pair (0x0A)."
                continue
            path = self.PrepareDir(keyp[1])
            if path == None:
                Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0])
                error = "Invalid path for key pair (0x05)."
                continue
            SetFileContents(path, GetFileContents(keyp[0] + ".prv"))
            os.chmod(path, 0600)
            Run("ssh-keygen -y -f " + keyp[0] + ".prv > " + path + ".pub")
            if IsRedHat():
                Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path)
                Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path + ".pub")
            if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
                ChangeOwner(path, self.UserName)
                ChangeOwner(path + ".pub", self.UserName)
        if sel == True and IsRedHat():
            Run("setenforce 1")
        while not WaAgent.EnvMonitor.IsNamePublished():
            time.sleep(1)
        ReloadSshd()
        return error

def UpdateAndPublishHostNameCommon(name):
    # RedHat
    if IsRedHat():
        filepath = "/etc/sysconfig/network"
        if os.path.isfile(filepath):
            ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n"
                + "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n'))))

        for ethernetInterface in PossibleEthernetInterfaces:
            filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface
            if os.path.isfile(filepath):
                ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n"
                    + "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n'))))

    # Debian
    if IsDebian():
        SetFileContents("/etc/hostname", name)

    for filepath in EtcDhcpClientConfFiles:
        if os.path.isfile(filepath):
            ReplaceFileContentsAtomic(filepath, "send host-name \"" + name + "\";\n"
                + "\n".join(filter(lambda a: not a.startswith("send host-name"), GetFileContents(filepath).split('\n'))))

    # Suse
    if IsSuse():
        SetFileContents("/etc/HOSTNAME", name)

class Agent(Util):
    def __init__(self):
        self.GoalState = None
        self.Endpoint = None
        self.LoadBalancerProbeServer = None
        self.HealthReportCounter = 0
        self.TransportCert = ""
        self.EnvMonitor = None
        self.SendData = None
        self.DhcpResponse = None

    def CheckVersions(self):
        #<?xml version="1.0" encoding="utf-8"?>
        #<Versions>
        #  <Preferred>
        #    <Version>2010-12-15</Version>
        #  </Preferred>
        #  <Supported>
        #    <Version>2010-12-15</Version>
        #    <Version>2010-28-10</Version>
        #  </Supported>
        #</Versions>
        global ProtocolVersion
        protocolVersionSeen = False
        node = xml.dom.minidom.parseString(self.HttpGetWithoutHeaders("/?comp=versions")).childNodes[0]
        if node.localName != "Versions":
            Error("CheckVersions: root not Versions")
            return False
        for a in node.childNodes:
            if a.nodeType == node.ELEMENT_NODE and a.localName == "Supported":
                for b in a.childNodes:
                    if b.nodeType == node.ELEMENT_NODE and b.localName == "Version":
                        v = GetNodeTextData(b)
                        LogIfVerbose("Fabric supported wire protocol version: " + v)
                        if v == ProtocolVersion:
                            protocolVersionSeen = True
            if a.nodeType == node.ELEMENT_NODE and a.localName == "Preferred":
                v = GetNodeTextData(a.getElementsByTagName("Version")[0])
                LogIfVerbose("Fabric preferred wire protocol version: " + v)
                if ProtocolVersion < v:
                    Warn("Newer wire protocol version detected. Please consider updating waagent.")
        if not protocolVersionSeen:
            Warn("Agent supported wire protocol version: " + ProtocolVersion + " was not advertised by Fabric.")
            ProtocolVersion = "2011-08-31"
        Log("Negotiated wire protocol version: " + ProtocolVersion)
        return True

    def Unpack(self, buffer, offset, range):
        result = 0
        for i in range:
            result = (result << 8) | Ord(buffer[offset + i])
        return result

    def UnpackLittleEndian(self, buffer, offset, length):
        return self.Unpack(buffer, offset, range(length - 1, -1, -1))

    def UnpackBigEndian(self, buffer, offset, length):
        return self.Unpack(buffer, offset, range(0, length))

    def HexDump3(self, buffer, offset, length):
        return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]])

    def HexDump2(self, buffer):
        return self.HexDump3(buffer, 0, len(buffer))

    def BuildDhcpRequest(self):
        #
        # typedef struct _DHCP {
        #     UINT8   Opcode;                     /* op:     BOOTREQUEST or BOOTREPLY */
        #     UINT8   HardwareAddressType;        /* htype:  ethernet */
        #     UINT8   HardwareAddressLength;      /* hlen:   6 (48 bit mac address) */
        #     UINT8   Hops;                       /* hops:   0 */
        #     UINT8   TransactionID[4];           /* xid:    random */
        #     UINT8   Seconds[2];                 /* secs:   0 */
        #     UINT8   Flags[2];                   /* flags:  0 or 0x8000 for broadcast */
        #     UINT8   ClientIpAddress[4];         /* ciaddr: 0 */
        #     UINT8   YourIpAddress[4];           /* yiaddr: 0 */
        #     UINT8   ServerIpAddress[4];         /* siaddr: 0 */
        #     UINT8   RelayAgentIpAddress[4];     /* giaddr: 0 */
        #     UINT8   ClientHardwareAddress[16];  /* chaddr: 6 byte ethernet MAC address */
        #     UINT8   ServerName[64];             /* sname:  0 */
        #     UINT8   BootFileName[128];          /* file:   0  */
        #     UINT8   MagicCookie[4];             /*   99  130   83   99 */
        #                                         /* 0x63 0x82 0x53 0x63 */
        #     /* options -- hard code ours */
        #
        #     UINT8 MessageTypeCode;              /* 53 */
        #     UINT8 MessageTypeLength;            /* 1 */
        #     UINT8 MessageType;                  /* 1 for DISCOVER */
        #     UINT8 End;                          /* 255 */
        # } DHCP;
        #

        # tuple of 244 zeros
        # (struct.pack_into would be good here, but requires Python 2.5)
        sendData = [0] * 244

        transactionID = os.urandom(4)
        macAddress = GetMacAddress()

        # Opcode = 1
        # HardwareAddressType = 1 (ethernet/MAC)
        # HardwareAddressLength = 6 (ethernet/MAC/48 bits)
        for a in range(0, 3):
            sendData[a] = [1, 1, 6][a]

        # fill in transaction id (random number to ensure response matches request)
        for a in range(0, 4):
            sendData[4 + a] = Ord(transactionID[a])

        LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4)))

        # fill in ClientHardwareAddress
        for a in range(0, 6):
            sendData[0x1C + a] = Ord(macAddress[a])

        # DHCP Magic Cookie: 99, 130, 83, 99
        # MessageTypeCode = 53 DHCP Message Type
        # MessageTypeLength = 1
        # MessageType = DHCPDISCOVER
        # End = 255 DHCP_END
        for a in range(0, 8):
            sendData[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a]
        return array.array("c", map(chr, sendData))

    def IntegerToIpAddressV4String(self, a):
        return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF)

    def RouteAdd(self, net, mask, gateway):
        if IsWindows():
            return
        net = self.IntegerToIpAddressV4String(net)
        mask = self.IntegerToIpAddressV4String(mask)
        gateway = self.IntegerToIpAddressV4String(gateway)
        Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway)

    def HandleDhcpResponse(self, sendData, receiveBuffer):
        LogIfVerbose("HandleDhcpResponse")
        bytesReceived = len(receiveBuffer)
        if bytesReceived < 0xF6:
            Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived))
            return None

        LogIfVerbose("BytesReceived: " + hex(bytesReceived))
        LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived))

        # check transactionId, cookie, MAC address
        # cookie should never mismatch
        # transactionId and MAC address may mismatch if we see a response meant from another machine

        for offsets in [range(4, 4 + 4), range(0x1C, 0x1C + 6), range(0xEC, 0xEC + 4)]:
            for offset in offsets:
                sentByte = Ord(sendData[offset])
                receivedByte = Ord(receiveBuffer[offset])
                if sentByte != receivedByte:
                    LogIfVerbose("HandleDhcpResponse: sent cookie:" + self.HexDump3(sendData, 0xEC, 4))
                    LogIfVerbose("HandleDhcpResponse: rcvd cookie:" + self.HexDump3(receiveBuffer, 0xEC, 4))
                    LogIfVerbose("HandleDhcpResponse: sent transactionID:" + self.HexDump3(sendData, 4, 4))
                    LogIfVerbose("HandleDhcpResponse: rcvd transactionID:" + self.HexDump3(receiveBuffer, 4, 4))
                    LogIfVerbose("HandleDhcpResponse: sent ClientHardwareAddress:" + self.HexDump3(sendData, 0x1C, 6))
                    LogIfVerbose("HandleDhcpResponse: rcvd ClientHardwareAddress:" + self.HexDump3(receiveBuffer, 0x1C, 6))
                    LogIfVerbose("HandleDhcpResponse: transactionId, cookie, or MAC address mismatch")
                    return None
        endpoint = None

        #
        # Walk all the returned options, parsing out what we need, ignoring the others.
        # We need the custom option 245 to find the the endpoint we talk to,
        # as well as, to handle some Linux DHCP client incompatibilities,
        # options 3 for default gateway and 249 for routes. And 255 is end.
        #

        i = 0xF0 # offset to first option
        while i < bytesReceived:
            option = Ord(receiveBuffer[i])
            length = 0
            if (i + 1) < bytesReceived:
                length = Ord(receiveBuffer[i + 1])
            LogIfVerbose("DHCP option " + hex(option) + " at offset:" + hex(i) + " with length:" + hex(length))
            if option == 255:
                LogIfVerbose("DHCP packet ended at offset " + hex(i))
                break
            elif option == 249:
                # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
                LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length))
                if length < 5:
                    Error("Data too small for option " + option)
                j = i + 2
                while j < (i + length + 2):
                    maskLengthBits = Ord(receiveBuffer[j])
                    maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3)
                    mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits))
                    j += 1
                    net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes)
                    net <<= (32 - maskLengthBytes * 8)
                    net &= mask
                    j += maskLengthBytes
                    gateway = self.UnpackBigEndian(receiveBuffer, j, 4)
                    j += 4
                    self.RouteAdd(net, mask, gateway)
                if j != (i + length + 2):
                    Error("HandleDhcpResponse: Unable to parse routes")
            elif option == 3 or option == 245:
                if i + 5 < bytesReceived:
                    if length != 4:
                        Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes")
                        return None
                    gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4)
                    IpAddress = self.IntegerToIpAddressV4String(gateway)
                    if option == 3:
                        self.RouteAdd(0, 0, gateway)
                        name = "DefaultGateway"
                    else:
                        endpoint = IpAddress
                        name = "Windows Azure wire protocol endpoint"
                    LogIfVerbose(name + ": " + IpAddress + " at " + hex(i))
                else:
                    Error("HandleDhcpResponse: Data too small for option " + option)
            else:
                LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length))
            i += length + 2
        return endpoint

    def DoDhcpWork(self):
        #
        # Discover the wire server via DHCP option 245.
        # And workaround incompatibility with Windows Azure DHCP servers.
        #
        ShortSleep = False # Sleep 1 second before retrying DHCP queries.

        if not IsWindows():
            Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT")
            Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT")

        sleepDurations = [0, 5, 10, 30, 60, 60, 60, 60]
        maxRetry = len(sleepDurations)
        lastTry = (maxRetry - 1)
        for retry in range(0, maxRetry):
            try:
                strRetry = str(retry)
                prefix = "DoDhcpWork: try=" + strRetry
                LogIfVerbose(prefix)
                sendData = self.BuildDhcpRequest()
                LogWithPrefixIfVerbose("DHCP request:", HexDump(sendData, len(sendData)))
                sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                if IsSuse():
                    # This is required because sending after binding to 0.0.0.0 fails with
                    # network unreachable when the default gateway is not set up.
                    sock.bind((GetIpv4Address(), 68))
                else:
                    sock.bind(("0.0.0.0", 68))
                sock.sendto(sendData, ("<broadcast>", 67))
                receiveBuffer = sock.recv(1024)
                sock.close()
                endpoint = self.HandleDhcpResponse(sendData, receiveBuffer)
                if endpoint == None:
                    LogIfVerbose("DoDhcpWork: No endpoint found")
                if endpoint != None or retry == lastTry:
                    if endpoint != None:
                        self.SendData = sendData
                        self.DhcpResponse = receiveBuffer
                    if retry == lastTry:
                        LogIfVerbose("DoDhcpWork: try=" + strRetry)
                    return endpoint
                sleepDuration = [sleepDurations[retry % len(sleepDurations)], 1][ShortSleep]
                LogIfVerbose("DoDhcpWork: sleep=" + str(sleepDuration))
                time.sleep(sleepDuration)
            except Exception, e:
                ErrorWithPrefix(prefix, str(e))
                ErrorWithPrefix(prefix, traceback.format_exc())
        return None

    def UpdateAndPublishHostName(self, name):
        # Set hostname locally and publish to iDNS
        Log("Setting host name: " + name)
        UpdateAndPublishHostNameCommon(name)
        for ethernetInterface in PossibleEthernetInterfaces:
            if IsSuse():
                Run("ifrenew " + ethernetInterface)
            else:
                Run("ifdown " + ethernetInterface + " && ifup " + ethernetInterface)
        self.RestoreRoutes()

    def RestoreRoutes(self):
        if self.SendData != None and self.DhcpResponse != None:
            self.HandleDhcpResponse(self.SendData, self.DhcpResponse)

    def UpdateGoalState(self):
        goalStateXml = None
        maxRetry = 9
        log = NoLog
        for retry in range(1, maxRetry + 1):
            strRetry = str(retry)
            log("retry UpdateGoalState,retry=" + strRetry)
            goalStateXml = self.HttpGetWithHeaders("/machine/?comp=goalstate")
            if goalStateXml != None:
                break
            log = Log
            time.sleep(retry)
        if not goalStateXml:
            Error("UpdateGoalState failed.")
            return
        Log("Retrieved GoalState from Windows Azure Fabric.")
        self.GoalState = GoalState(self).Parse(goalStateXml)
        return self.GoalState

    def ReportReady(self):
        counter = (self.HealthReportCounter + 1) % 1000000
        self.HealthReportCounter = counter
        healthReport = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><Health xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><GoalStateIncarnation>"
                        + self.GoalState.Incarnation
                        + "</GoalStateIncarnation><Container><ContainerId>"
                        + self.GoalState.ContainerId
                        + "</ContainerId><RoleInstanceList><Role><InstanceId>"
                        + self.GoalState.RoleInstanceId
                        + "</InstanceId><Health><State>Ready</State></Health></Role></RoleInstanceList></Container></Health>")
        a = self.HttpPost("/machine?comp=health", healthReport)
        if a != None:
            return a.getheader("x-ms-latest-goal-state-incarnation-number")
        return None

    def ReportNotReady(self, status, desc):
        healthReport = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><Health xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><GoalStateIncarnation>"
                        + self.GoalState.Incarnation
                        + "</GoalStateIncarnation><Container><ContainerId>"
                        + self.GoalState.ContainerId
                        + "</ContainerId><RoleInstanceList><Role><InstanceId>"
                        + self.GoalState.RoleInstanceId
                        + "</InstanceId><Health><State>NotReady</State>"
                        + "<Details><SubStatus>" + status + "</SubStatus><Description>" + desc + "</Description></Details>"
                        + "</Health></Role></RoleInstanceList></Container></Health>")
        a = self.HttpPost("/machine?comp=health", healthReport)
        if a != None:
            return a.getheader("x-ms-latest-goal-state-incarnation-number")
        return None

    def ReportRoleProperties(self, thumbprint):
        roleProperties = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><RoleProperties><Container>"
                        + "<ContainerId>" + self.GoalState.ContainerId + "</ContainerId>"
                        + "<RoleInstances><RoleInstance>"
                        + "<Id>" + self.GoalState.RoleInstanceId + "</Id>"
                        + "<Properties><Property name=\"CertificateThumbprint\" value=\"" + thumbprint + "\" /></Properties>"
                        + "</RoleInstance></RoleInstances></Container></RoleProperties>")
        a = self.HttpPost("/machine?comp=roleProperties", roleProperties)
        Log("Posted Role Properties. CertificateThumbprint=" + thumbprint)
        return a

    def LoadBalancerProbeServer_Shutdown(self):
        if self.LoadBalancerProbeServer != None:
            self.LoadBalancerProbeServer.shutdown()
            self.LoadBalancerProbeServer = None

    def GenerateTransportCert(self):
        Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem")
        cert = ""
        for line in GetFileContents("TransportCert.pem").split('\n'):
            if not "CERTIFICATE" in line:
                cert += line.rstrip()
        return cert

    def Provision(self):
        if IsWindows():
            Log("Skipping Provision on Windows")
            return
        enabled = Config.get("Provisioning.Enabled")
        if enabled != None and enabled.lower().startswith("n"):
            return
        Log("Provisioning image started.")
        type = Config.get("Provisioning.SshHostKeyPairType")
        if type == None:
            type = "rsa"
        regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
        if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
            Run("rm -f /etc/ssh/ssh_host_*key*")
            Run("ssh-keygen -N '' -t " + type + " -f /etc/ssh/ssh_host_" + type + "_key")
            ReloadSshd()
        SetFileContents(LibDir + "/provisioned", "")
        dvd = "/dev/hdc"
        if os.path.exists("/dev/sr0"):
            dvd = "/dev/sr0"
        if Run("fdisk -l " + dvd + " | grep Disk"):
            return
        CreateDir("/mnt/cdrom/secure", "root", 0700)
        if Run("mount " + dvd + " /mnt/cdrom/secure"):
            Error("Unable to provision: Failed to mount DVD.")
            return "Failed to retrieve provisioning data (0x01)."
        if not os.path.isfile("/mnt/cdrom/secure/ovf-env.xml"):
            Error("Unable to provision: Missing ovf-env.xml on DVD.")
            return "Failed to retrieve provisioning data (0x02)."
        ovfxml = GetFileContents("/mnt/cdrom/secure/ovf-env.xml")
        SetFileContents("ovf-env.xml", re.sub("<UserPassword>.*?<", "<UserPassword>*<", ovfxml))
        Run("umount /mnt/cdrom/secure")
        error = None
        if ovfxml != None:
            Log("Provisioning image using OVF settings in the DVD.")
            ovfobj = OvfEnv().Parse(ovfxml)
            if ovfobj != None:
                error = ovfobj.Process()
        # This is done here because regenerated SSH host key pairs may be potentially overwritten when processing the ovfxml
        fingerprint = os.popen("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub").read().rstrip().split()[1].replace(':','')
        self.ReportRoleProperties(fingerprint)
        delRootPass = Config.get("Provisioning.DeleteRootPassword")
        if delRootPass != None and delRootPass.lower().startswith("y"):
            DeleteRootPassword()
        Log("Provisioning image completed.")
        return error

    def Run(self):
        if IsLinux():
            SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n")

        if GetIpv4Address() == None:
            Log("Waiting for network.")
            while(GetIpv4Address() == None):
                time.sleep(10)

        Log("IPv4 address: " + GetIpv4Address())
        Log("MAC  address: " + ":".join(["%02X" % Ord(a) for a in GetMacAddress()]))

        # Consume Entropy in ACPI table provided by Hyper-V
        try:
            SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
        except:
            pass

        Log("Probing for Windows Azure environment.")
        self.Endpoint = self.DoDhcpWork()

        if self.Endpoint == None:
            Log("Windows Azure environment not detected.")
            while True:
                time.sleep(60)

        Log("Discovered Windows Azure endpoint: " + self.Endpoint)
        if not self.CheckVersions():
            Error("Agent.CheckVersions failed")
            sys.exit(1)

        self.EnvMonitor = EnvMonitor()

        # Set SCSI timeout on root device
        try:
            timeout = Config.get("OS.RootDeviceScsiTimeout")
            if timeout != None:
                SetFileContents("/sys/block/" + DeviceForIdePort(0) + "/device/timeout", timeout)
        except:
            pass

        global Openssl
        Openssl = Config.get("OS.OpensslPath")
        if Openssl == None:
            Openssl = "openssl"

        self.TransportCert = self.GenerateTransportCert()

        incarnation = None # goalStateIncarnationFromHealthReport
        currentPort = None # loadBalancerProbePort
        goalState = None # self.GoalState, instance of GoalState
        provisioned = os.path.exists(LibDir + "/provisioned")
        program = Config.get("Role.StateConsumer")
        provisionError = None
        while True:
            if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation):
                goalState = self.UpdateGoalState()

                if provisioned == False:
                    self.ReportNotReady("Provisioning", "Starting")

                goalState.Process()

                if provisioned == False:
                    provisionError = self.Provision()
                    provisioned = True

                #
                # only one port supported
                # restart server if new port is different than old port
                # stop server if no longer a port
                #
                goalPort = goalState.LoadBalancerProbePort
                if currentPort != goalPort:
                    self.LoadBalancerProbeServer_Shutdown()
                    currentPort = goalPort
                    if currentPort != None:
                        self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort)

            if program != None and DiskActivated == True:
                os.spawnl(os.P_NOWAIT, program, program, "Ready")
                program = None

            if goalState.ExpectedState == "Stopped":
                program = Config.get("Role.StateConsumer")
                if program != None:
                    Run(program + " Shutdown")
                self.EnvMonitor.shutdown()
                self.LoadBalancerProbeServer_Shutdown()
                command = ["/sbin/shutdown -hP now", "shutdown /s /t 5"][IsWindows()]
                Run(command)
                return

            sleepToReduceAccessDenied = 3
            time.sleep(sleepToReduceAccessDenied)
            i = None
            if provisionError != None:
                i = self.ReportNotReady("ProvisioningFailed", provisionError)
            else:
                i = self.ReportReady()
            if i != None:
                incarnation = i
            time.sleep(25 - sleepToReduceAccessDenied)

Init_Suse = """\
#! /bin/sh

### BEGIN INIT INFO
# Provides: WindowsAzureLinuxAgent
# Required-Start: $network sshd
# Required-Stop: $network sshd
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
# Description: Start the WindowsAzureLinuxAgent
### END INIT INFO

WAZD_BIN=/usr/sbin/waagent
test -x $WAZD_BIN || exit 5

case "$1" in
    start)
        echo "Starting WindowsAzureLinuxAgent"
        ## Start daemon with startproc(8). If this fails
        ## the echo return value is set appropriate.

        startproc -f $WAZD_BIN -daemon
        exit $?
        ;;
    stop)
        echo "Shutting down WindowsAzureLinuxAgent"
        ## Stop daemon with killproc(8) and if this fails
        ## set echo the echo return value.

        killproc -p /var/run/waagent.pid $WAZD_BIN
        exit $?
        ;;
    try-restart)
        ## Stop the service and if this succeeds (i.e. the
        ## service was running before), start it again.
        $0 status >/dev/null &&  $0 restart
        ;;
    restart)
        ## Stop the service and regardless of whether it was
        ## running or not, start it again.
        $0 stop
        $0 start
        ;;
    force-reload|reload)
        ;;
    status)
        echo -n "Checking for service WindowsAzureLinuxAgent "
        ## Check status with checkproc(8), if process is running
        ## checkproc will return with exit status 0.

        checkproc -p $WAZD_PIDFILE $WAZD_BIN
        exit $?
        ;;
    probe)
        ;;
    *)
        echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
        exit 1
        ;;
esac
"""

Init_RedHat = """\
#!/bin/bash
#
# Init file for WindowsAzureLinuxAgent.
#
# chkconfig: 2345 60 80
# description: WindowsAzureLinuxAgent
#

# source function library
. /etc/rc.d/init.d/functions

RETVAL=0
FriendlyName="WindowsAzureLinuxAgent"
WAZD_BIN=/usr/sbin/waagent

start()
{
    echo -n $"Starting $FriendlyName: "
    $WAZD_BIN -daemon &
}

stop()
{
    echo -n $"Stopping $FriendlyName: "
    killproc -p /var/run/waagent.pid $WAZD_BIN
    RETVAL=$?
    echo
    return $RETVAL
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        stop
        start
        ;;
    reload)
        ;;
    report)
        ;;
    status)
        status $WAZD_BIN
        RETVAL=$?
        ;;
    *)
        echo $"Usage: $0 {start|stop|restart|status}"
        RETVAL=1
esac
exit $RETVAL
"""

Init_Debian = """\
#!/bin/sh
### BEGIN INIT INFO
# Provides:          WindowsAzureLinuxAgent
# Required-Start:    $network $syslog
# Required-Stop:     $network $syslog
# Should-Start:      $network $syslog
# Should-Stop:       $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: WindowsAzureLinuxAgent
# Description:       WindowsAzureLinuxAgent
### END INIT INFO

. /lib/lsb/init-functions

OPTIONS="-daemon"
WAZD_BIN=/usr/sbin/waagent
WAZD_PID=/var/run/waagent.pid

case "$1" in
    start)
        log_begin_msg "Starting WindowsAzureLinuxAgent..."
        pid=$( pidofproc $WAZD_BIN )
        if [ -n "$pid" ] ; then
              log_begin_msg "Already running."
              log_end_msg 0
              exit 0
        fi
        start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS
        log_end_msg $?
        ;;

    stop)
        log_begin_msg "Stopping WindowsAzureLinuxAgent..."
        start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID
        ret=$?
        rm -f $WAZD_PID
        log_end_msg $ret
        ;;
    force-reload)
        $0 restart
        ;;
    restart)
        $0 stop
        $0 start
        ;;
    status)
        status_of_proc $WAZD_BIN && exit 0 || exit $?
        ;;
    *)
        log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}"
        exit 1
        ;;
esac

exit 0
"""

WaagentConf = """\
#
# Windows Azure Linux Agent Configuration
#

Role.StateConsumer=None                 # Specified program is invoked with "Ready" or "Shutdown".
                                        # Shutdown will be initiated only after the program returns. Windows Azure will
                                        # power off the VM if shutdown is not completed within ?? minutes.
Role.ConfigurationConsumer=None         # Specified program is invoked with XML file argument specifying role configuration.
Role.TopologyConsumer=None              # Specified program is invoked with XML file argument specifying role topology.

Provisioning.Enabled=y                  #
Provisioning.DeleteRootPassword=y       # Password authentication for root account will be unavailable.
Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
Provisioning.SshHostKeyPairType=rsa     # Supported values are "rsa", "dsa" and "ecdsa".
Provisioning.MonitorHostName=y          # Monitor host name changes and publish changes via DHCP requests.

ResourceDisk.Format=y                   # Format if unformatted. If 'n', resource disk will not be mounted.
ResourceDisk.Filesystem=ext4            #
ResourceDisk.MountPoint=/mnt/resource   #
ResourceDisk.EnableSwap=n               # Create and use swapfile on resource disk.
ResourceDisk.SwapSizeMB=0               # Size of the swapfile.

LBProbeResponder=y                      # Respond to load balancer probes if requested by Windows Azure.

Logs.Verbose=n                          #

OS.RootDeviceScsiTimeout=300            # Root device timeout in seconds.
OS.OpensslPath=None                     # If "None", the system default version is used.
"""

WaagentLogrotate = """\
/var/log/waagent.log {
    monthly
    rotate 6
    notifempty
    missingok
}
"""

def AddToLinuxKernelCmdline(options):
    if os.path.isfile("/boot/grub/menu.lst"):
        Run("sed -i --follow-symlinks '/kernel/s|$| " + options + " |' /boot/grub/menu.lst")
    filepath = "/etc/default/grub"
    if os.path.isfile(filepath):
        filecontents = GetFileContents(filepath).split('\n')
        current = filter(lambda a: a.startswith("GRUB_CMDLINE_LINUX"), filecontents)
        ReplaceFileContentsAtomic(filepath,
            "\n".join(filter(lambda a: not a.startswith("GRUB_CMDLINE_LINUX"), filecontents))
            + current[0][:-1] + " " + options + "\"\n")
        Run("update-grub")

def ApplyVNUMAWorkaround():
    VersionParts = platform.release().replace('-', '.').split('.')
    if int(VersionParts[0]) > 2:
        return
    if int(VersionParts[1]) > 6:
        return
    if int(VersionParts[2]) > 37:
        return
    AddToLinuxKernelCmdline("numa=off")
    # TODO: This is not ideal for offline installation.
    print("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.")

def RevertVNUMAWorkaround():
    print("Automatic reverting of GRUB configuration is not yet supported. Please edit by hand.")

def Install():
    if IsWindows():
        print("ERROR: -install invalid for Windows.")
        return 1
    os.chmod(sys.argv[0], 0755)
    SwitchCwd()
    requiredDeps = [ "/sbin/route", "/sbin/shutdown" ]
    if IsDebian():
        requiredDeps += [ "/usr/sbin/update-rc.d" ]
    if IsSuse():
        requiredDeps += [ "/sbin/insserv" ]
    for a in requiredDeps:
        if not os.path.isfile(a):
            Error("Missing required dependency: " + a)
            return 1
    missing = False
    for a in [ "ssh-keygen", "useradd", "openssl", "sfdisk",
               "fdisk", "mkfs", "chpasswd", "sed", "grep", "sudo" ]:
        if Run("which " + a + " > /dev/null 2>&1"):
            Warn("Missing dependency: " + a)
            missing = True
    if missing == True:
        Warn("Please resolve missing dependencies listed for full functionality.")
    if UsesRpm():
        if not Run("rpm --quiet -q NetworkManager"):
            Error(GuestAgentLongName + " is not compatible with NetworkManager.")
            return 1
        if Run("rpm --quiet -q python-pyasn1"):
            Error(GuestAgentLongName + " requires python-pyasn1.")
            return 1
    if UsesDpkg() and Run("dpkg -l network-manager | grep -q ^un"):
        Error(GuestAgentLongName + " is not compatible with network-manager.")
        return 1
    for a in RulesFiles:
        if os.path.isfile(a):
            if os.path.isfile(GetLastPathElement(a)):
                os.remove(GetLastPathElement(a))
            shutil.move(a, ".")
            Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
    filename = "waagent"
    filepath = "/etc/init.d/" + filename
    distro = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
    if distro == 0:
        Error("Unable to detect Linux Distribution.")
        return 1
    init = [[Init_RedHat, "chkconfig --add " + filename],
            [Init_Debian, "update-rc.d " + filename + " defaults"],
            [Init_Suse, "insserv " + filename]][distro - 1]
    SetFileContents(filepath, init[0])
    os.chmod(filepath, 0755)
    Run(init[1])
    if os.path.isfile("/etc/waagent.conf"):
        try:
            os.remove("/etc/waagent.conf.old")
        except:
            pass
        try:
            os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
            Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
        except:
            pass
    SetFileContents("/etc/waagent.conf", WaagentConf)
    SetFileContents("/etc/logrotate.d/waagent", WaagentLogrotate)
    filepath = "/etc/ssh/sshd_config"
    ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
        a.startswith("ClientAliveInterval"),
        GetFileContents(filepath).split('\n'))) + "ClientAliveInterval 180\n")
    Log("Configured SSH client probing to keep connections alive.")
    ApplyVNUMAWorkaround()
    return 0

def Uninstall():
    if IsWindows():
        print("ERROR: -uninstall invalid for windows, see waagent_service.exe")
        return 1
    SwitchCwd()
    for a in RulesFiles:
        if os.path.isfile(GetLastPathElement(a)):
            try:
                shutil.move(GetLastPathElement(a), a)
                Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
            except:
                pass
    filename = "waagent"
    a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
    if a == 0:
        Error("Unable to detect Linux Distribution.")
        return 1
    Run("service " + filename + " stop")
    cmd = ["chkconfig --del " + filename,
           "update-rc.d -f " + filename + " remove",
           "insserv -r " + filename][a - 1]
    Run(cmd)
    for f in os.listdir(LibDir) + ["/etc/init.d/" + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]:
        try:
            os.remove(f)
        except:
            pass
    RevertVNUMAWorkaround()
    return 0

def DeleteRootPassword():
    SetFileContents("/etc/shadow-temp", "")
    os.chmod("/etc/shadow-temp", 0000)
    Run("(echo root:*LOCK*:14600:::::: && grep -v ^root /etc/shadow ) > /etc/shadow-temp")
    Run("mv -f /etc/shadow-temp /etc/shadow")
    Log("Root password deleted.")

def Deprovision(force, deluser):
    if IsWindows():
        Run(os.environ["windir"] + "\\system32\\sysprep\\sysprep.exe /generalize")
        return 0

    SwitchCwd()
    ovfxml = GetFileContents("ovf-env.xml")
    ovfobj = None
    if ovfxml != None:
        ovfobj = OvfEnv().Parse(ovfxml)

    print("WARNING! The waagent service will be stopped.")
    print("WARNING! All SSH host key pairs will be deleted.")
    print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
    print("WARNING! Cached DHCP leases will be deleted.")

    delRootPass = Config.get("Provisioning.DeleteRootPassword")
    if delRootPass != None and delRootPass.lower().startswith("y"):
        print("WARNING! root password will be disabled. You will not be able to login as root.")

    if ovfobj != None and deluser == True:
        print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.")

    if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'):
        return 1

    Run("service waagent stop")

    if deluser == True:
        DeleteAccount(ovfobj.UserName)

    # Remove SSH host keys
    regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
    if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
        Run("rm -f /etc/ssh/ssh_host_*key*")

    # Remove root password
    if delRootPass != None and delRootPass.lower().startswith("y"):
        DeleteRootPassword()

    # Remove distribution specific networking configuration

    UpdateAndPublishHostNameCommon("localhost.localdomain")

    # RedHat, Suse, Debian
    for a in VarLibDhcpDirectories:
        Run("rm -f " + a + "/*")

    # Clear LibDir, remove nameserver and root bash history
    for f in os.listdir(LibDir) + ["/etc/resolv.conf", "/root/.bash_history", "/var/log/waagent.log"]:
        try:
            os.remove(f)
        except:
            pass

    return 0

def SwitchCwd():
    if not IsWindows():
        CreateDir(LibDir, "root", 0700)
        os.chdir(LibDir)

def Usage():
    print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]")
    return 0

if GuestAgentVersion == "":
    print("WARNING! This is a non-standard agent that does not include a valid version string.")
if IsLinux() and not DetectLinuxDistro():
    print("WARNING! Unable to detect Linux distribution. Some functionality may be broken.")

if len(sys.argv) == 1:
    sys.exit(Usage())

args = []
force = False
for a in sys.argv[1:]:
    if re.match("^([-/]*)(help|usage|\?)", a):
        sys.exit(Usage())
    elif re.match("^([-/]*)verbose", a):
        Verbose = True
    elif re.match("^([-/]*)force", a):
        force = True
    elif re.match("^([-/]*)(setup|install)", a):
        sys.exit(Install())
    elif re.match("^([-/]*)(uninstall)", a):
        sys.exit(Uninstall())
    else:
        args.append(a)

Config = ConfigurationProvider()

verbose = Config.get("Logs.Verbose")
if verbose != None and verbose.lower().startswith("y"):
    Verbose = True

daemon = False
for a in args:
    if re.match("^([-/]*)deprovision\+user", a):
        sys.exit(Deprovision(force, True))
    elif re.match("^([-/]*)deprovision", a):
        sys.exit(Deprovision(force, False))
    elif re.match("^([-/]*)daemon", a):
        daemon = True
    elif re.match("^([-/]*)version", a):
        print(GuestAgentVersion + " running on " + LinuxDistro)
        sys.exit(0)
    elif re.match("^([-/]*)serialconsole", a):
        AddToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0")
        Log("Configured kernel to use ttyS0 as the boot console.")
        sys.exit(0)
    else:
        print("Invalid command line parameter:" + a)
        sys.exit(1)

if daemon == False:
    sys.exit(Usage())

try:
    SwitchCwd()
    Log(GuestAgentLongName + " Version: " + GuestAgentVersion)
    if IsLinux():
        Log("Linux Distribution Detected      : " + LinuxDistro)
    WaAgent = Agent()
    WaAgent.Run()
except Exception, e:
    Error(traceback.format_exc())
    Error("Exception: " + str(e))
    sys.exit(1)
