Monday, November 4, 2013

Updated: Script(s) to Backup AudioCodes Gateways

Backup of an AudioCodes gateway with a script has been on my list to do for a long time. Not sure why I didn't have the brain storm earlier, but I used Wireshark to take apart the backup process and see what is being done.

When I saw the HTTP GET in Wireshark for the file, I initially thought I could download just the BOARD.ini file, but then I found that GWs with 6.4 firmware or later have the Serial Number as part of the BOARD file name (BOARD_SN1234567.ini).

So back to the drawing board... I then had to step back and look at the page that was downloaded to give the interface to perform backups and pull out the filename used in the URL to request the file.

Anyway... I'm quite proud of hacking this together. Enjoy.

If you are running on a Windows 7 or other workstation you'll need to set your PowerShell Execution Policy.



Version 6.4 and older

# Script to Backup AudioCodes Gateways
# Created By Jonathan McKinney (blog.lyncdialog.com)
# Disclaimer: You running this script means you won't blame Jonathan McKinney or his employer if this breaks your stuff. This script is provided AS IS without warranty of any kind.

$username = "Admin"
$password = "Admin"
$regex = "BOARD.*\.ini"
$address = "10.10.1.10"
$path = "c:\scripts\"


$webclient = new-object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($username, $password)

Write-Host `r`n 'Detecting .ini name' `r`n
$configurl = "http://" + $address + "/ConfigurationFile"
$webpage = $webclient.DownloadString($configurl)
$pattern = $webpage -split "`n" | Select-String -pattern $regex -Allmatches | % { $_.Matches | % { $_.Value } }
Write-Host `r`n 'Detected Board Filename ' $pattern


Write-Host  `r`n 'Backing up ' $address `r`n
$backupurl = "http://" + $address + "/FS/" + $pattern
$backupfilename = $path + $pattern
$webclient.DownloadFile($backupurl,$backupfilename)
Write-Host  `r




Version 6.6 and newer

So I have to admit that this was a huge pain to figure out. So I am even more proud I got this working than the above script. Having that been said... I consider this beta until more people test this and have success. Please give me feedback if you run into problems.

Audiocodes implemented Forms Based Authentication starting on version 6.6. Since I didn't understand much about this, I had to do a lot of research and found there are 1000 different variations on this, which made it hard to find examples of what I needed to do. So once again I went back to my trusty friend Wireshark and picked apart what was being sent in the HTTP POST versus what Audiocodes was doing in javascript.

There are some pre-requisites in order for this script to run successfully.To start with you will need to set the HTTP Authentication Mode on the Audiocodes to "Web Based Authentication", as illustrated below (note you will need to switch to "Advanced" on the left). This is necessary because there is a bunch of HASH algorithms that Audiocodes has running in javascript otherwise, that I can't deal with from a command line script (Note: this is a change from the original script I wrote that didn't handle the more complicated Hash algorithm used with Web Based Authentication).



Next you will need to install a package called cURL that is a command line tool for interacting with web pages. It is required to work with the Forms Based Auth that audiocodes uses. I used a new tool to install it called Chocolatey, which is the equivalent of RPM packages for Windows. Here is the page for cURL (http://chocolatey.org/packages/curl) and here is the main page for Chocolatey (http://chocolatey.org/)

This script now supports MediaPacks and Mediants on version 6.6 and newer.

# Script to Backup AudioCodes Gateways
# Created By Jonathan McKinney (blog.lyncdialog.com)
# Disclaimer: You running this script means you won't blame Jonathan McKinney or his employer if this breaks your stuff. This script is provided AS IS without warranty of any kind.


$username = "Admin"
$password = "Admin"
$login1sregex = "\<s\>(\d*)\<\/s\>"
$login1rregex = "\<r\>(.*)\<\/r\>"
$filenameregex = "BOARD.*\.ini"
$address = "10.10.1.10"
$path = "c:\scripts\"

$a1 = $null
$loginusername = $null
$loginurl = $null
$configurl = $null
$backupurl = $null
$logoffurl = $null
$logoffwebpage = $null
$login1webpage = $null
$login1spattern = $null
$login1rpattern = $null
$hashByteArray1 = $null
$hashByteArray2 = $null
$passwordhash = $null
$resultwebpage = $null
$configwebpage = $null
$filenamepattern = $null
$backupfilename = $null



# Authentication Section

Write-Host `r`n 'Authenticating' `r`n
$login1username = "c1=" + $username
$loginurl = "http://" + $address + "/UE/Login"
$login1webpage = curl $loginurl --data-urlencode "t=1" `
                                --data-urlencode "c0=0" `
                                --data-urlencode $login1username `
                                --header "X-Requested-With: XMLHttpRequest" `
                                -c cookiejar.txt `
                                --header "Cookie: aclogname=; C2=ct" `
                                --user-agent "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"

$login1spattern = $login1webpage -split "`n" | Select-String -pattern $login1sregex -Allmatches | % { $_.Matches | % { $_.groups[1].Value } }

$login1spattern = "s=" + $login1spattern

$login1rpattern = $login1webpage -split "`n" | Select-String -pattern $login1rregex -Allmatches | % { $_.Matches | % { $_.groups[1].Value } }

$cryptoServiceProvider = [System.Security.Cryptography.SHA256CryptoServiceProvider]
$hashAlgorithm = New-Object $cryptoServiceProvider
$hashByteArray1 = $hashAlgorithm.ComputeHash($([Char[]]$password))

foreach ($byte in $hashByteArray1)
{     $a1 += "{0:x2}" -f $byte
}$a2 = $username + ":" + $login1rpattern + ":" + $a1
$hashByteArray2 = $hashAlgorithm.ComputeHash($([Char[]]$a2))

foreach ($byte in $hashByteArray2)
{     $passwordhash += "{0:x2}" -f $byte
}$passwordhash = "c1=" + $passwordhash
$resultwebpage = curl $loginurl --data-urlencode "t=1" `
                                --data-urlencode $login1spattern `
                                --data-urlencode "c0=1" `
                                --data-urlencode $passwordhash `
                                --header "X-Requested-With: XMLHttpRequest" `
                                -c cookiejar.txt `
                                --header "Cookie: aclogname=; C2=ct" `
                                --user-agent "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"


# Detect filename for backup section

Write-Host `r`n 'Detecting .ini name' `r`n
$configurl = "http://" + $address + "/ConfigurationFile"
$configwebpage = curl $configurl -b cookiejar.txt

$filenamepattern = $configwebpage -split "`n" | Select-String -pattern $filenameregex -Allmatches | % { $_.Matches | % { $_.Value } }

Write-Host `r`n 'Detected Board Filename ' $filenamepattern


# Backup section

Write-Host `r`n 'Backing up ' $address `r`n
$backupurl = "http://" + $address + "/FS/" + $filenamepattern
$backupfilename = $path + $filenamepattern
curl -o $backupfilename $backupurl -b cookiejar.txt
Write-Host `r


# Logoff section

$logoffurl = "http://" + $address + "/PressLogOff"
$logoffwebpage = curl $logoffurl -b cookiejar.txt





Authentication type Auto Detect

And now... the pièce de résistance... I combined both scripts... decided to make it more Function based and now it Auto Detects the authentication type. It has the same prerequisites as the Scripts above. So please read all of the article before you try to use this script. Also if you are wondering why I didn't use cURL for all of the work in the new script... it kept truncating the authentication header when doing the basic (Digest) authentication and failing. Couldn't find a parameter to increase the header size allowed.

Enjoy... as usual... feedback is always welcome.

# Script to Backup AudioCodes Gateways# Created By Jonathan McKinney (blog.lyncdialog.com)# Disclaimer: You running this script means you won't blame Jonathan McKinney or his employer if this breaks your stuff. This script is provided AS IS without warranty of any kind.

# User Modifiable Variables

$username = "Admin"
$password = "Admin"
$address = "m2600.t2mdev.com"
$backuppath = "c:\jonmck\script\"


# End User Modifiable Variables
$filename = $null
$statuscodeeval = $null


#Function to detect if Forms Auth is present

function DetectAuthType ($ad)
{
     $loginurl = $null
     $loginurl = "http://" + $ad + "/"
     $statuscode = curl -o null.txt $loginurl -w '%{http_code}'

     If ($statuscode -eq "401")
     {
          $statuscodeeval = "basic"
     }

     ElseIf ($statuscode -eq "203")

     {
          $statuscodeeval = "forms"
     }     
     Else
     {
          $statuscodeeval = "neither"
     }     
     Remove-Item null.txt
     Return $statuscodeeval
}


# Function to Authenticate with Forms Auth

function AuthenticateForms ($un, $pw, $ad)
{
     $login1sregex = "\<s\>(\d*)\<\/s\>"
     $login1rregex = "\<r\>(.*)\<\/r\>"
     $a1 = $null
     $loginusername = $null
     $loginurl = $null
     $login1webpage = $null
     $login1spattern = $null
     $login1rpattern = $null
     $hashByteArray1 = $null
     $hashByteArray2 = $null
     $passwordhash = $null
     $resultwebpage = $null
     $formsbasedauth = $null
     
     Write-Host `r`n 'Forms Authentication' `r`n
     $login1username = "c1=" + $un
     $loginurl = "http://" + $ad + "/UE/Login"
     $login1webpage = curl $loginurl --data-urlencode "t=1" `
                                     --data-urlencode "c0=0" `
                                     --data-urlencode $login1username `
                                     --header "X-Requested-With: XMLHttpRequest" `
                                     -c cookiejar.txt `
                                     --header "Cookie: aclogname=; C2=ct" `
                                     --user-agent "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"

     $login1spattern = $login1webpage -split "`n" | Select-String -pattern $login1sregex -Allmatches | % { $_.Matches | % { $_.groups[1].Value } }

     $login1spattern = "s=" + $login1spattern
     $login1rpattern = $login1webpage -split "`n" | Select-String -pattern $login1rregex -Allmatches | % { $_.Matches | % { $_.groups[1].Value } }

     $cryptoServiceProvider = [System.Security.Cryptography.SHA256CryptoServiceProvider]
     $hashAlgorithm = New-Object $cryptoServiceProvider
     $hashByteArray1 = $hashAlgorithm.ComputeHash($([Char[]]$password))
     foreach ($byte in $hashByteArray1)
     {
          $a1 += "{0:x2}" -f $byte
     }
     
     $a2 = $username + ":" + $login1rpattern + ":" + $a1
     $hashByteArray2 = $hashAlgorithm.ComputeHash($([Char[]]$a2))

     foreach ($byte in $hashByteArray2)
     {
          $passwordhash += "{0:x2}" -f $byte
     }
     
     $passwordhash = "c1=" + $passwordhash
     $resultwebpage = curl $loginurl --data-urlencode "t=1" `
                                     --data-urlencode $login1spattern `
                                     --data-urlencode "c0=1" `
                                     --data-urlencode $passwordhash `
                                     --header "X-Requested-With: XMLHttpRequest" `
                                     -c cookiejar.txt `
                                     --header "Cookie: aclogname=; C2=ct" `
                                     --user-agent "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
}


# Function to Backup Gateway WITHOUT Forms Auth

function BackupBasic ($un, $pw, $ad, $path)
{
     $regex = "BOARD.*\.ini"
     $webclient = $null
     $configurl = $null
     $backupurl = $null
     $webpage = $null
     $pattern = $null
     $backupfilename = $null

     $webclient = new-object System.Net.WebClient
     $webclient.Credentials = New-Object System.Net.NetworkCredential($un, $pw)

     Write-Host `r`n 'Detecting .ini name' `r`n

     $configurl = "http://" + $ad + "/ConfigurationFile"
     $webpage = $webclient.DownloadString($configurl)
     $pattern = $webpage -split "`n" | Select-String -pattern $regex -Allmatches | % { $_.Matches | % { $_.Value } }
     Write-Host `r`n 'Detected Board Filename ' $pattern

     Write-Host `r`n 'Backing up ' $ad `r`n
     $backupurl = "http://" + $ad + "/FS/" + $pattern
     $backupfilename = $path + $pattern
     $webclient.DownloadFile($backupurl,$backupfilename)
     Write-Host `r
}


# Function to Detect the Backup Filename with Forms Auth for the Audiocodes Configuration File

function DetectFilenameForms ($ad)
{
     $fnregex = "BOARD.*\.ini"
     $configurl = $null
     $fnpat = $null
     $configwebpage = $null

     Write-Host `r`n 'Detecting .ini name' `r`n
     $configurl = "http://" + $ad + "/ConfigurationFile"
     $configwebpage = curl $configurl -b cookiejar.txt
     $fnpat = $configwebpage -split "`n" | Select-String -pattern $fnregex -Allmatches | % { $_.Matches | % { $_.Value } }

     Write-Host `r`n 'Detected Board Filename ' $fnpat
     return $fnpat
}


# Function to Backup Gateway with Forms Auth

function BackupGatewayForms ($ad, $path, $fn)
{
     $backupurl = $null
     $backupfilename = $null
     
     Write-Host `r`n 'Backing up ' $ad `r`n
     $backupurl = "http://" + $ad + "/FS/" + $fn
     $backupfilename = $path + $fn
     curl -o $backupfilename $backupurl -b cookiejar.txt
     Write-Host `r
}

# Function to Logoff Audiocodes with Forms Auth after finishing work

function LogoffForms ($ad)
{
     $logoffurl = $null
     $logoffwebpage = $null
     
     $logoffurl = "http://" + $ad + "/PressLogOff"
     $logoffwebpage = curl $logoffurl -b cookiejar.txt
     Remove-Item cookiejar.txt
}


# Main Script


$formsbasedauth = DetectAuthType $address

if ($formsbasedauth -eq "forms")
{
     AuthenticateForms $username $password $address
     $filename = DetectFilenameForms $address
     BackupGatewayForms $address $backuppath $filename
     LogoffForms $address
}
ElseIf ($formsbasedauth -eq "basic")
{
     BackupBasic $username $password $address $backuppath
}
Else
{
     Write-Host "Something went really wrong... couldn't detect Authentication type... Might I suggest Wireshark"
}

31 comments:

  1. Nice dude, thanks for posting! Hope you're doing well.

    ReplyDelete
    Replies
    1. I am doing well... and staying busy like everyone else is with Lync :-)

      Delete
  2. It seems to work on 6.4, but not on 6.6 (File is blank).

    ReplyDelete
    Replies
    1. I'll take a look... thanks for the feedback

      Delete
    2. I bet it has to do with the forms based auth on 6.6....hmmm

      Delete
  3. Anyone had any luck with the 6.6 firmware form based login? Seems quite a painful process and I'd hate to reinvent the wheel... Thanks

    ReplyDelete
    Replies
    1. I had a buddy working on a technique using PowerShell to drive IE... but it prompts for file download instead of just downloading... haven't found a way around that. I haven't had a chance to dig back in to this yet.

      Delete
    2. One thing I haven't checked... is if there is a way to turn off the forms based authentication... if you can get it back to the old way of authenticating... the other script should work

      Delete
  4. you can use SSH or telnet to

    ReplyDelete
    Replies
    1. Yea this might be the way we have to go... but would prefer to do it through web interface.

      Delete
  5. Hi johnathan, I need to copy the configuration of one Audio Gateway 1000 to another...

    Do you know how I can do that.

    ReplyDelete
    Replies
    1. Just do a backup of the configuration. In the .ini file you can modify the interfaces section to match the IP address of the target gateway. You can also do a search and replace for any routes that have the old IP in case you were doing tel to ip and then ip to tel routing for analogs as an example

      Delete
    2. Hi johnathan,
      I tried to run the third script after installing curl, but power shell always responds with this error: Invoke-WebRequest:

      Unable to process the parameter. The parameter name 'or' is ambiguous. Possible matches include:-OutFile-OutVariable OutBuffer.
      In C: \ Scripts \ Backup Audiocodes.ps1: 21 car: 25
      + $ Statuscode = curl-o $ null.txt loginUrl-w '% {} HTTP_CODE'
      + ~ ~
      + CategoryInfo: InvalidArgument: (:) [Invoke-WebRequest], ParameterBindingException
      + FullyQualifiedErrorId: AmbiguousParameter, Microsoft.PowerShell.Commands.InvokeWebRequestCommand

      Remove-Item: Can not find path 'C: \ null.txt' because it does not exist.
      In C: \ Scripts \ Backup Audiocodes.ps1: 36 car: 6
      + Remove-Item null.txt
      + ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
      + CategoryInfo: ObjectNotFound: (C: \ null.txt: String) [Remove-Item], ItemNotFoundException
      + FullyQualifiedErrorId: PathNotFound, Microsoft.PowerShell.Commands.RemoveItemCommand

      Do you know how I can fix this?
      Thank you.

      Delete
    3. You might not have the right version of powershell installed. Can you verify by running on a Lync Server,,, people have had better luck there. In general Powershell 3 or higher should work

      Delete
    4. Hi, thank you for this script I added to your hard work as I found certain versions of mediants require a different hashing sequence during FBA. HTTP://www.mklync.com Thanks guys hope it helps out

      Delete
    5. I ran into this myself Luciano... it has to do with a curl alias in PowerShell. https://github.com/lukesampson/scoop/issues/56

      Delete
  6. Getting the configuration from a 6.6 gateway involves two HTTP POSTs to log in, as well as cookie information and REFER information in each subsequent request. It's complicated. I've done it using Debian, Bash and curl and it's working perfectly so far. I could send it to you if you like.

    ReplyDelete
  7. First of all, a brilliant script :-)
    I have found some issues with this script tho, basically the AudioCodes Web Engine requires a different hashing sequence based on the "m" version (I believe this is the Mediant Version). Any ways I have updated the script to detect the M version of the AudioCodes Gateways then performs the required hashing sequence, I have briefly explained the issue and script is found here also:
    http://www.mklync.com/?p=296
    I'm sure this should help out people tryin to use this script to back up a Mediant 1000 :-)

    ReplyDelete
    Replies
    1. I believe that the m version is more related to other security features being enabled rather than the defaults. I have vague memory of running into it when trying to disable forms based auth.

      I'd have to poke around to find it again... but the script I have above works for all the audiocodes up to 6.8 out of the box. But I think having the modified version you did is important too. Thanks for figuring that out!

      Delete
  8. Im using load 7.00A.004.503 with the latest version of cURL and get the below. Any ideas?

    PS C:\Scripts> .\ACBackup.ps1

    Authenticating

    Invoke-WebRequest : Parameter cannot be processed because the parameter name 'c' is ambiguous. Possible matches
    include: -Credential -CertificateThumbprint -Certificate -ContentType.
    At C:\Scripts\ACBackup.ps1:41 char:1
    + -c cookiejar.txt `
    + ~~
    + CategoryInfo : InvalidArgument: (:) [Invoke-WebRequest], ParameterBindingException
    + FullyQualifiedErrorId : AmbiguousParameter,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

    Invoke-WebRequest : Parameter cannot be processed because the parameter name 'c' is ambiguous. Possible matches
    include: -Credential -CertificateThumbprint -Certificate -ContentType.
    At C:\Scripts\ACBackup.ps1:68 char:1
    + -c cookiejar.txt `
    + ~~
    + CategoryInfo : InvalidArgument: (:) [Invoke-WebRequest], ParameterBindingException
    + FullyQualifiedErrorId : AmbiguousParameter,Microsoft.PowerShell.Commands.InvokeWebRequestCommand


    Detecting .ini name

    curl : Cannot send a content-body with this verb-type.
    At C:\Scripts\ACBackup.ps1:76 char:18
    + $configwebpage = curl $configurl -b cookiejar.txt
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Invoke-WebRequest], ProtocolViolationException
    + FullyQualifiedErrorId : System.Net.ProtocolViolationException,Microsoft.PowerShell.Commands.InvokeWebRequestComm
    and


    Detected Board Filename

    Backing up 1.1.1.1

    Invoke-WebRequest : Parameter cannot be processed because the parameter name 'o' is ambiguous. Possible matches
    include: -OutFile -OutVariable -OutBuffer.
    At C:\Scripts\ACBackup.ps1:86 char:6
    + curl -o $backupfilename $backupurl -b cookiejar.txt
    + ~~
    + CategoryInfo : InvalidArgument: (:) [Invoke-WebRequest], ParameterBindingException
    + FullyQualifiedErrorId : AmbiguousParameter,Microsoft.PowerShell.Commands.InvokeWebRequestCommand


    curl : Cannot send a content-body with this verb-type.
    At C:\Scripts\ACBackup.ps1:91 char:18
    + $logoffwebpage = curl $logoffurl -b cookiejar.txt
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Invoke-WebRequest], ProtocolViolationException
    + FullyQualifiedErrorId : System.Net.ProtocolViolationException,Microsoft.PowerShell.Commands.InvokeWebRequestComm
    and

    PS C:\Scripts>

    ReplyDelete
  9. It looks like the cURL alias is pointed to Invoke-WebRequest rather than cURL. I've had this problem on some machines... you can kill the alias using this method http://superuser.com/questions/883914/how-do-i-permanently-remove-a-default-powershell-alias

    ReplyDelete
  10. Hi Jonathan, Nice Work! Thank you!
    Why don't you use Invoke-WebRequest instead of curl? curl is a powershell Alias of Invoke-WebRequest because this is the implementation of curl. So you don't need to install curl before using the script.

    ReplyDelete
    Replies
    1. Oh I definitely tried... But it could t handle the more complicated auth of the newer firmware. The biggest feature I needed was the ability to store a cookie once authorized so that subsequent requests didn't go through the auth process again. If you look at the script the branch for older firmware does use invoke-webrequest.

      Delete
  11. Hi Jonathan,

    I get the following warning after running the script.

    Backing up 172.30.40.4

    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
    Warning: Failed to create the file d:\CURL\: No such file or directory
    curl: (23) Failed writing received data to disk/application

    Thanks a lot for your help.

    ReplyDelete
    Replies
    1. haven't seen that before... make sure you are running the PoSH console as "Administrator" and that the user you are logged into Windows with has the permissions to create that directory. You might also try to uninstall CURL and reinstall again and make sure there are no errors.

      Delete
  12. Hi Jonathan,
    I test the script but only works whith "basic authentication", in Forms Based authentication i get the following "xml version="1.0" encoding="UTF-8" fail no access level"

    GW:
    Mediant 1000; Version ID:6.80A.248.003

    Thanks for your help.

    ReplyDelete
    Replies
    1. Did you change the HTTPS cipher string? You might also take a look at this modified script http://www.mklync.com/?p=296

      Delete
  13. Hi Dear Sir,
    Thanks for your amazing script, I don't have any idea how audiocodes devices work, but need to have a script which can backup mediant 500 to mediant 2000 devices, do you have kind of script can work on these vesrions?
    thanks for your help.
    Mediant500-600-800-1000 and 2000

    ReplyDelete
    Replies
    1. have you tried this script... I suspect it will work for all that hardware even though I haven't tested

      Delete
  14. Hi Jonathan,
    Tried running your script against a Mediant 2600 hundred with the latest Firmware (7.20A.000.042) installed.
    Couldn't get it to work and on investigation the $login1webpage variable is getting populated with: "user not found"
    Have also tried the script from mklync.com with no success.
    The user I'm authenticating with definitely exists. Any suggestions?
    Thanks

    ReplyDelete