Category: Software

Software reviews and commentary

  • Fun with Garmin Forensics

    Fun with Garmin Forensics

    As you’ll recall from my Christmas post the company I work for is replacing their fleet management equipment, and in part that means upgrading some of our older Garmin GPS screens. As a result a steady stream of old equipment has been trickling into my office.

    old_garmins

    Even though these units no longer fit our needs, there is still plenty of life left to them.

    Science!

    Wikipedia states that “forensic science is the scientific method of gathering and examining information about the past“, our methods might not be too scientific, but these devices are chock-full of information about the past.

    In preparation for finding them new homes, I thought it might be wise to see what kind of private data these things have stored. I came across a nice overview presentation on GPS forensics that covers a variety of models and brands. That presentation listed some files of interest:

    Current.gpx
    Archive.gpx
    Position.gpx
    GarminDevice.xml

    After connecting the Garmin to a computer and waiting for the Garmin’s operating system to start I was able to see the Garmin as a storage device.

    garmin_usb

    Navigating to the \Garmin\GPX directory I was able to locate Current.gpx.
    garmin_nuvi_5000_mass_storage

    garmin_nuvi_5000_root_folder

    garmin_nuvi_5000_garmin_folder

    garmin_nuvi_5000_gpx_folder

    GPX Format

    The GPX in the GPX file format stands for GPS eXchange Format. It’s a fairly simple XML format with plenty of human readable items of interest. Here are some (slightly sanitized) snippets from the GPX file I recovered to give you an idea of the format. The file I was working with was devoid of line breaks, but I’ve added some here for clarity.

    After a quick header we get into some more interesting stuff.

    <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
    <gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" creator="nüvi 5000" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd">

    The “metadata” section seems pretty boring. I think the time listed here is the last time the unit was used.

    <metadata>
     <link href="http://www.garmin.com">
      <text>Garmin International</text>
      </link>
      <time>2014-01-16T21:44:20Z</time>
    </metadata>

    Now on to some good stuff, Waypoints.

    <wpt lat="23.137160" lon="-81.687469">
      <ele>241.17</ele>
      <name>001</name>
      <sym>Waypoint</sym>
    </wpt>

    Some Waypoint entries contain “extensions”, these might include Address Book entries

    <extensions>
     <gpxx:WaypointExtension>
      <gpxx:Categories>
        <gpxx:Category>Address Book</gpxx:Category>
      </gpxx:Categories>

    Or just addresses

    <extensions>
     <gpxx:WaypointExtension>
      <gpxx:Address>
        <gpxx:StreetAddress>101 W. Flagler St</gpxx:StreetAddress>
        <gpxx:City>Miami</gpxx:City>
        <gpxx:State>FL</gpxx:State>
        <gpxx:Country>USA</gpxx:Country>
        <gpxx:PostalCode>33130</gpxx:PostalCode>
      </gpxx:Address>
     </gpxx:WaypointExtension>
    </extensions>

    Far more interesting than the Waypoints however is the Tracks log. This data is a series of positions at a given time which leaves a trail of breadcrumbs which we can use to reconstruct the journey.

    Here is part of one particular journey

    <trk>
      <name>ACTIVE LOG: 08 JAN 2014 12:06</name>
       <trkseg>

    After the “trkseg” element begins a series of coordinates, elevations, and times follow

    <trkpt lat="23.884920" lon="-81.686757">
      <ele>196.25</ele>
      <time>2014-01-08T17:06:41Z</time>
    </trkpt>
    <trkpt lat="23.884317" lon="-81.686580">
      <ele>196.73</ele>
      <time>2014-01-08T17:06:43Z</time>
    </trkpt>
    <trkpt lat="23.884317" lon="-81.686580">
      <ele>196.73</ele>
      <time>2014-01-08T17:06:44Z</time>
    </trkpt>
    <trkpt lat="23.884317" lon="-81.686580">
      <ele>196.73</ele>
      <time>2014-01-08T17:06:45Z</time>
    </trkpt>

    …And so on

    Google Earth

    As you can see this information is easy to work with to suit your needs. If your needs are just to watch what happened and when, Google Earth is great tool for this. It turns out Google Earth already understands the GPX format, so we don’t need to extract any data manually.

    Just click on Tools then GPS
    google_earth_import_1

    Select the Import from file option followed by the Import button
    google_earth_import_2

    Navigate to your Current.gpx file (in my case \Garmin\GPX), select it and click Open
    google_earth_import_3

    Google Earth will tell you what data it was able to find. Click OK
    google_earth_import_4

    On the left panel you can move between waypoints to see them on the map.
    google_earth_view_imported_2

    Fun

    For even more fun, select one of the “Tracks” logs, and click the play button with breadcrumb icon (I assume that’s what that is anyhow).
    google_earth_view_imported_3

    Here is some sample video I captured from the playback of some track log data. The data isn’t real-time of course (that would be boring), but there is still some relevant timing preserved. Notice how you can even tell which stop lights were red by the time elapsed between movements. Very interesting and potentially revealing stuff.



  • Import Nextel iDEN Contacts to Google

    Import Nextel iDEN Contacts to Google

    Sprint is decommissioning it’s Nextel iDEN network in 2013. The Nextel network has been very popular with businesses for it’s push-to-talk (PTT) “walkie-talkie“-like service. Several rugged (and bulky) mobile phones like the Motorola i355 and i365 are well adapted to the sometimes harsh environments of field work and clumsy service technicians. Unfortunately it seems that many of these phones will outlive the network that supports them, leaving just SouthernLINC and a handful of small regional providers to offer any iDEN network service in the U.S. market.

    It seems that everyone is switching to smartphones these days, and my office is no exception. One of the nicest features from an information technology standpoint about this new generation of phone is cloud integration. Frequently setting up one Exchange server or Google Apps account on the phone will synchronize calendars, contacts, and email. It soon became apparent while migrating the staff off their beastly “dumb”-phones that people were going to be spending a lot of time manually transferring their contacts from one phone to the other. To speed up the process I created a VBScript that works with the Motorola iDEN Phonebook Manager to export the phone book Jet database into a CSV file compatible with Google Contacts.

    EDIT: Updated to add a kludgy work-around for 64-bit Windows

    Download iden2googlecontacts.zip

    CSVname = "export.csv"
    DBname = "import.mdb"

    ‘Optional Usage: iden2googlecontacts.vbs SourceDB TargetCSV

    If WScript.Arguments.Count => 1 Then
        DBname = WScript.Arguments(0)
    End If
    If WScript.Arguments.Count => 2 Then
        CSVname = WScript.Arguments(1)
    End If


    ‘So I guess this doesn’t work with the default WSH interpreter on 64-bit versions of Windows
    If WScript.Arguments.Count < 3 Then
        Set objShell = CreateObject("WScript.Shell")
        If objShell.RegRead("HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PROCESSOR_ARCHITECTURE") = "AMD64" Then
            WINDIR=objShell.ExpandEnvironmentStrings("%WINDIR%")

            Set fs = CreateObject("Scripting.FileSystemObject")
            If Not fs.FileExists(WINDIR & "\SysWOW64\wscript.exe") Then
                WScript.Echo WINDIR & "\SysWOW64\wscript.exe"
                WScript.Echo "Warning: Can’t find 64-bit Wscript.exe, this probably won’t work."
                Set colSystemEnvVars = Nothing
                Set objShell = Nothing
                Set fs = Nothing
            Else
                ‘The script won’t fork if there are 3 or more arguments on the command line
                Command = WINDIR & "\SysWOW64\wscript.exe "
                Command = Command & Chr(34) & WScript.ScriptFullName & Chr(34)
                Command = Command & " "
                Command = Command & Chr(34) & DBname & Chr(34)
                Command = Command & " "
                Command = COmmand & Chr(34) & CSVname & Chr(34)
                Command = Command & " "
                Command = Command & "NO_FORK"
                objShell.Run Command
                WScript.Quit
            End If
        End If
    End If
    ‘OK Things should be back to normal at this point

    If InStr(CSVname, "") = 0 Then
        Set objShell = CreateObject("WScript.shell")
        CSVname = objShell.CurrentDirectory & "" & CSVname
        Set objShell = Nothing
    End If

    If InStr(DBname, "") = 0 Then
        Set objShell = CreateObject("WScript.shell")
        DBname = objShell.CurrentDirectory & "" & DBname
        Set objShell = Nothing
    End If

    Set fs = CreateObject("Scripting.FileSystemObject")
    If Not fs.FileExists(DBname) Then
        WScript.Echo "Error: Database " & DBname & " was not found!"
        WScript.Quit
    End If
    Set fs = Nothing

    WScript.Echo "Exporting " & DBname & " to CSV file " & CSVname

    Set objFSO = CreateObject("Scripting.FileSystemObject")
    Set objTextFile = objFSO.CreateTextFile(CSVname, True)

    i=0

    objTextFile.Write("Name,Given Name,Additional Name,Family Name,Yomi Name,Given Name Yomi,Additional Name Yomi,Family Name Yomi,Name Prefix,Name Suffix,Initials,Nickname,Short Name,Maiden Name,Birthday,Gender,Location,Billing Information,Directory Server,Mileage,Occupation,Hobby,Sensitivity,Priority,Subject,Notes,Group Membership,E-mail 1 – Type,E-mail 1 – Value,E-mail 2 – Type,E-mail 2 – Value,Phone 1 – Type,Phone 1 – Value,Organization 1 – Type,Organization 1 – Name,Organization 1 – Yomi Name,Organization 1 – Title,Organization 1 – Department,Organization 1 – Symbol,Organization 1 – Location,Organization 1 – Job Description" & vbCRLF)

    SQL = "SELECT ContactName, Number, NumberType FROM tblContactList WHERE NumberType<>1 AND NumberType <= 6;"

    set oConn=CreateObject("ADODB.Connection")
    oConn.Open "Driver={Microsoft Access Driver (*.mdb)}; DBQ=" & DBname & ";"
    Set oRS=oConn.Execute(SQL)

    Do While Not oRS.EOF
            ‘Name
        objTextFile.Write(oRS.Fields("ContactName"))
        objTextFile.Write(",")
        ‘Given Name
        objTextFile.Write(",")
        ‘Additional Name
        objTextFile.Write(",")
        ‘Family Name
        objTextFile.Write(",")
        ‘Yomi Name
        objTextFile.Write(",")
        ‘Given Name Yomi
        objTextFile.Write(",")
        ‘Additional Name Yomi
        objTextFile.Write(",")
        ‘Family Name Yomi
        objTextFile.Write(",")
        ‘Name Prefix
        objTextFile.Write(",")
        ‘Name Suffix
        objTextFile.Write(",")
        ‘Initials
        objTextFile.Write(",")
        ‘Nickname
        objTextFile.Write(",")
        ‘Short Name
        objTextFile.Write(",")
        ‘Maiden Name
        objTextFile.Write(",")
        ‘Birthday
        objTextFile.Write(",")
        ‘Gender
        objTextFile.Write(",")
        ‘Location
        objTextFile.Write(",")
        ‘Billing Information
        objTextFile.Write(",")
        ‘Directory Server
        objTextFile.Write(",")
        ‘Mileage
        objTextFile.Write(",")
        ‘Occupation
        objTextFile.Write(",")
        ‘Hobby
        objTextFile.Write(",")
        ‘Sensitivity
        objTextFile.Write(",")
        ‘Priority
        objTextFile.Write(",")
        ‘Subject
        objTextFile.Write(",")
        ‘Notes
        objTextFile.Write(",")
        ‘Group Membership
        objTextFile.Write(",")
        ‘E-mail 1 – Type
        objTextFile.Write(",")
        ‘E-mail 1 – Value
        objTextFile.Write(",")
        ‘E-mail 2 – Type
        objTextFile.Write(",")
        ‘E-mail 2 – Value
        objTextFile.Write(",")
        ‘Phone 1 – Type
        Select Case oRS.Fields("NumberType")
            Case "0"
                objTextFile.Write("Mobile")
            Case "2"
                objTextFile.Write("Home")
            Case "3"
                objTextFile.Write("Work")
            Case "4"
                objTextFile.Write("Mobile")
            Case "5"
                objTextFile.Write("Work Fax")
            Case "6"
                objTextFile.Write("Pager")
            Case else
                objTextFile.Write("Mobile")
        End Select
        objTextFile.Write(",")
        ‘Phone 1 – Value
        objTextFile.Write(oRS.Fields("Number"))
        ‘Organization 1 – Type
        objTextFile.Write(",")
        ‘Organization 1 – Name
        objTextFile.Write(",")
        ‘Organization 1 – Yomi Name
        objTextFile.Write(",")
        ‘Organization 1 – Title
        objTextFile.Write(",")
        ‘Organization 1 – Department
        objTextFile.Write(",")
        ‘Organization 1 – Symbol
        objTextFile.Write(",")
        ‘Organization 1 – Location
        objTextFile.Write(",")
        ‘Organization 1 – Job Description
        objTextFile.Write("," & vbCRLF)
        i = i + 1
        oRS.MoveNext
    Loop

    objTextFile.Close
    Set objFSO = Nothing

    If i > 1 Then
        WScript.Echo "Exported " & i & " record"
    Else
        WScript.Echo "Exported " & i & " records"
    End If

    If oRS.State = 1 or oRS.State = True Then
        oRS.Close
            oConn.Close
            set oRS = Nothing
            set oConn = Nothing
    End If

    The Motorola iDEN Phonebook Manager is a free download, but you’ll need a cheap cable (available on eBay for under $5 for many phones) to dump the contacts off the iDEN SIM card.

    Dock the phone to the USB cable and connect it to the computer you’ll be working with. You may be prompted to install additional drivers for your phone, but most of what you need will probably be included with the Motorola iDEN software download.

    Open up Motorola iDEN Phonebook Manager and select “New/Edit Phonebook“.

    Next select “Load From Phone“.

    The software will prompt you to make sure the cable is connected between the phone and computer. Press “OK“.

    An activity screen will open showing communication in progress between the computer and the phone.

    The phone’s display may also show some indication of something unusual happening (as seen on an i355) or may just go blank (i365).

    Once all the contacts are imported click the “Continue” button on the bottom of the window.

    Select “Save To File“.

    Pick a name for your file. I chose “import.mdb“, if you choose something else, you’ll have to edit the VBScript accordingly. Remember where you save the file.

    Download iden2googlecontacts.zip and extract the iden2googlecontacts.vbs script into the same directory as your phone book export (“import.mdb” in this example).

    Edit the script to change the input or output file names if required. When you are ready to run the script, double-click on iden2googlecontacts.vbs. The script will output the file names for verification, or warn you if it is unable to find the database to import contacts from.

    When the script is complete it will tell you how many contacts it exported into the new CSV file (named export.csv by default).

    Armed with the new CSV file export you just created, open up Google Contacts. under the “More” button choose “Import…“.

    Click the “Choose File” button.

    Navigate to the location where your export file was created and select the file (export.csv in this example). Click “Open” to proceed.

    Click the “Import” button.

    If everything worked OK, you should now see the new contacts. Click “Find & merge duplicates” to reconcile the import with any contacts the user may have already had in Google Contacts.

    To be useful on the smartphone you may have to move these contacts to the “My Contacts” group label. Click the square selection box as shown below and then choose “All” to select all contacts.

    Click the group button (the thing with three heads on it) and remove the check mark from the auto-generated label (shown here as “Import 9/28/12”). Place a check mark next to “My Contacts” and click “Apply

    The contacts should all be removed from the auto-generated group label. You may optionally remove this group label now. Click the “More” icon again and choose “Delete group“.

    Google will prompt you to confirm the deletion.

    That’s it. Depending on the model of your mobile phone additional steps may be required to resynchronise your contacts with the server, but many phones handle this automatically.

    If you are processing many phone books, consider the following command for batch processing

    for %f in (*.mdb) do cscript iden2googlecontacts.vbs %~nf.mdb %~nf.csv

    If you use this script and find it helpful (or run into problems), please let me know in the comments.

  • Symantec Endpoint Recovery Tool (SERT) Pin Number (All of Them)

    Symantec Endpoint Recovery Tool (SERT) Pin Number (All of Them)

    The Symantec Endpoint Recovery Tool (SERT) is a great concept, it puts the power of Symantec’s Endpoint Protection antivirus scanning onto a bootable CD. If you are familiar with rootkits, you can probably see where this is useful. To scan a file for viruses an antivirus scanner makes a request to the operating system for the contents of the file. Sophisticated forms of malicious software (malware) can intercept this request. Instead of the system returning the contents of the virus to the scanner, benign data is returned instead and thus detection by the antivirus software is avoided. A rootkit can only be effective at intercepting requests while it is running in the system. By booting from a “Live CD” a rootkit-free operating system can be used to perform the antivirus inspection. A similar idea utilizing the open source ClamAV antivirus scanner can be found in the OpenDiagnostics Live CD. EDIT: Readers might also find the AVG Rescue CD of interest.

    Now that I’ve said some nice things about Symantec, let us get to the crux of this article.

    “The expiry is by design.”

    The Symantec Endpoint Recovery Tool has developed a nasty trait of asking for a “PIN” before it will begin a scan.

    According to Symantec this issue occurs whenever the SERT software is used post-April 30th, 2012. Instead of acknowledging this as a bug, Symantec asserts this annoyance is “by design”.

    http://www.symantec.com/business/support/index?page=content&id=TECH159200

    I have a hard time believing this claim. There is no reason given as to why this “feature” would possibly be intentional. According to the KB article, “No serial number, license number, or PIN exists for this tool”. If the intent was simply to expire the software I would have expected an error to that effect not a prompt for an imaginary pin number. Why ask for something that should “by design” never exist? If the expiry date was built-in because of licensing issues, or to get people to upgrade for some other reason shouldn’t there be a replacement release of this software ready to go prior to the April deadline? This might make a little bit of sense if the idea was to kill off SERT unceremoniously, but the KB goes on to say “A new version of the SERT tool will be made available shortly”.

    A quick check of File Connect did not show me any suitable replacement candidate

    The latest SERT ISO available to me continues to be the (apparently) deprecated Symantec_Endpoint_Recovery_Tool_2.0.24_AllWin_EN.iso with the SHA-1 sum matching my existing copy (ded1b82350ecfe315896630feb04938aa48e22ee).

    Bug or not, someone goofed up. The alternative is that Symantec knew the software would quit working, chose to do nothing about it, and decided to be needlessly vague about the details.

    Continued use

    There are two ways to continue using the software, neither of which seem to be documented in the KB.

    The first method is the most obvious. If the software quits working after April 30th 2012, just set an earlier date on the system.

    During the normal flow of using SERT you are given an option to “Launch Command Prompt” before going into Endpoint Recovery Tool proper.

    Click “Launch Command Prompt”

    At the command prompt type “date 4-29-2012” and hit enter. Then type “exit” and hit enter.

    You should be back at the menu, choose “Continue loading Endpoint Recovery Tool”.

    You should now see the License Agreement as you normally would have seen it pre-April 30th, 2012.

    Method 2

    If by “No serial number, license number, or PIN exists for this tool” Symantec actually meant “No single serial number […]”, they would be correct, there are in fact very many of them. Continuing to make the “by design” argument more perplexing, the PIN code that should not exist does in fact exist, and is extremely easy to guess. I can’t help, but wonder what possible purpose this “design” had in mind.

    I started out spamming “1”‘s into the PIN field, which didn’t work, then moved on to “2”, which did work. I started making a list of the codes that worked for me.

    2222222222222
    3333333333333
    4444444444444
    6666666666666
    7777777777777
    8888888888888
    9999999999999

    Then I started playing with some variations. It didn’t take long before I realized what was going on.

    2222222222223
    3222222222222
    2346789234678

    Next, I started looking for characters other than numbers

    222222222222B
    222222222222C
    222222222222D
    222222222222F
    222222222222G
    222222222222H
    222222222222J
    222222222222K
    222222222222M
    222222222222P
    222222222222Q
    222222222222R
    222222222222T
    222222222222V
    222222222222W
    222222222222X
    222222222222Y

    So in the end, the PIN code is any combination of 13 of the following 24 alphanumeric characters
    {2, 3, 4, 6, 7, 8, 9, B, C, D, F, G, H, J, K, M, P, Q, R, T, V, W, X, Y}

    Interesting “design” Symantec.

  • Access an APC AP5456 IP Gateway for Analog KVM in Linux

    Access an APC AP5456 IP Gateway for Analog KVM in Linux

    I recently wrote about running an ActiveX component without Internet Explorer. I used that technique to come up with a shell script front-end for downloading, unpacking and running an executable in Wine for accessing an APC IP KVM (model AP5456). Here is the results of that effort.

    At a minimum the script requires Wine and curl, but to do everything without manually downloading and extracting the cabinet file yourself, you’ll also need cabextract. Some less exotic stuff from coreutils: basename and dirname are needed, but you probably have those already.

    You can download the script here.

    #!/bin/bash

    # A script to access an APC brand model AP5456 IP Gateway for Analog KVM
    # In Linux, using Wine

    USERNAME="apc"
    PASSWORD=""
    KVM_SERVER=""

    VIEWER_PATH=""

    while [ -z "$KVM_SERVER" ]
    do
        echo -n "FQDN of KVM Server (not including https://): "
        read KVM_SERVER
    done

    while [ -z "$USERNAME" ]
    do
            echo -n "Username: "
            read USERNAME
    done

    while [ -z "$PASSWORD" ]
    do
            echo -n "Password: "
            tput invis
            read PASSWORD
            tput sgr0
    done

    get_cab_name () {
        while read line
        do
            if [ "${line/’OBJECT CODEBASE’/}" != "$line" ]; then
                cab_name="${line##*'<OBJECT CODEBASE="’}"
                cab_name="${cab_name%%’"’*}"
                echo "$cab_name"
            fi
        done
    }

    get_cmd_args () {
        while read line
        do
            if [ "${line/PARAM/}" != "$line" ]; then
                ipaddr="${line##*'<PARAM NAME="ipaddr" VALUE="’}"
                ipaddr="${ipaddr%%’"’*}"

                sessionkey="${line##*'<PARAM NAME="sessionkey" VALUE="’}"
                sessionkey="${sessionkey%%’"’*}"

                encryptionkey="${line##*'<PARAM NAME="encryptionkey" VALUE="’}"
                encryptionkey="${encryptionkey%%’"’*}"
            fi
        done
        if [ -n "$sessionkey" ] && [ -n "$encryptionkey" ] && [ -n "$ipaddr" ]; then
            echo "-k $sessionkey -e $encryptionkey $ipaddr"
        fi

    }

    wine="`which wine`"
    if [ -z "$wine" ]; then
        echo "Wine was not found, but is required.  Can not continue without it." >&2
        exit 1
    fi

    cd "`dirname $0`"
    #If a path to vpclient.exe wasn’t specified, and we can’t find it, use /tmp
    #as a place to download it to
    if [ -z "$VIEWER_PATH" ] && ! [ -f "vpclient.exe" ]; then
        mkdir -p "/tmp/vpclient"
        VIEWER_PATH="/tmp/vpclient"
    fi

    #If a full path wasn’t specified, prepend current working directory
    if [ "${VIEWER_PATH:0:1}" != "/" ]; then
        VIEWER_PATH="`pwd`/$VIEWER_PATH"
    fi

    if ! [ -f "$VIEWER_PATH/vpclient.exe" ]; then

        cab_url="`curl -s –insecure –user "$USERNAME:$PASSWORD" "https://$KVM_SERVER/launch.asp" |get_cab_name`"
        cab_url="https://$KVM_SERVER/$cab_url"

        cabextract="`which cabextract`"
        if [ -z "$cabextract" ]; then
            echo "vpclient.exe was not found in $VIEWER_PATH" >&2
            echo "cabextract was not found either, so we can’t download">&2
            echo -e "and extract vpclient.exe from it’s CAB archive.\n" >&2
            echo "Error: user intervention required." >&2
            echo "———————————-" >&2
            echo "Download $cab_url" >&2
            echo "Unpack `basename $cab_url` into $VIEWER_PATH" >&2
            echo " — OR — " >&2
            echo "Install cabextract" >&2
            exit 1
        fi

        if ! [ -d "$VIEWER_PATH" ]; then
            echo "WARNING: Viewer Path specified is not valid." >&2
            echo "Path: $VIEWER_PATH" >&2
            echo -e "——————————————-\n" >&2
            echo -n "Would you like me to create it? (y/N)?" >&2
            read answer
            shopt -s nocasematch
            if [ "$answer" = "y" ]; then
                mkdir -p "$VIEWER_PATH"
                if ! [ -d "$VIEWER_PATH" ]; then
                    echo "Couldn’t create: $VIEWER_PATH" >&2
                    exit 1
                fi
            else
                exit
            fi
            shopt -u nocasematch
           
        fi

        cd "$VIEWER_PATH"
            curl -s –insecure –user "$USERNAME:$PASSWORD" "$cab_url" >"`basename $cab_url`"
            $cabextract -q "`basename $cab_url`" 2>/dev/null
    fi


    cmd_args="`curl -s –insecure –user "$USERNAME:$PASSWORD" "https://$KVM_SERVER/launch.asp" |get_cmd_args`"

    cd "$VIEWER_PATH"

    #Sanity check
    if [ -f "./vpclient.exe" ]; then
        $wine "./vpclient.exe" $cmd_args
    else
        echo "Failed, do it yourself: $wine vpclient.exe $cmd_args"
    fi