Advanced HTTP / ASP.Net Scripting with PowerShell

Mon, Mar 26, 2007 5-minute read

Back in the good ol’ days, my domain used to support a catch-all email address. I could give out any email address (as long as it ended in @leeholmes.com,) and still be guaranteed to receive the mail. When asked to create an account at some random website, I just make one up on the spot tailored specifically to them.

I got an odd look from my dentist when I did this, though. They must have thought — “who in their right mind likes dentists so much that they pick it for their email address?” As with all computer jokes, explaining it didn’t make it any better – I just got a smile and nod.

In any case, my domain soon fell under the wrath of a dictionary attack spammer – which I noticed when I was getting spam as fast as Outlook could receive it. I quickly disabled my catch-all alias, but I immediately missed how easy it was to create a new temporary alias. Creating the aliases manually got old quickly, so I wrote a script to do it instead. It works against “SmarterMail Professional Edition” – the software used at WebHost4Life (the hosting company I use.)

If you use WebHost4Life, feel free to use this script to let you create email aliases with ease. If you don’t, you might still find the code useful for building your own script to automate HTTP / ASP.Net scenarios.

PS >New-MailAlias foobar2

cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Credential
User: [email protected]
Password for user [email protected]: ***********

Logging in.
Logged in.
Creating alias.
Alias created.

(Note: This script uses Send-TcpRequest, which I posted earlier as “Connect-Computer”: http://www.leeholmes.com/blog/ScriptingNetworkTCPConnectionsInPowerShell.aspx)

##############################################################################
##
## New-MailAlias.ps1
##
## Create a new mail alias on a mail host managed by a "SmarterMail 
## Professional Edition" web interface
##
## The alias provided will redirect mail to the user specified by $recipient
## The credential should be the full email address and password of the domain
## email administrator -- usually postmaster@$mailHost.
##
##############################################################################
param(
    [string] $alias = $("Specify an alias"), 
    [System.Management.Automation.PsCredential] $cred = $(Get-Credential))

[void] [Reflection.Assembly]::LoadWithPartialName("System.Web")

$mailHost = "mail.example.com"
$recipient = "[email protected]"

function Main
{
    Write-Host "Logging in."
    $session = LogIn
    Write-Host "Logged in."

    Write-Host "Creating alias."
    $viewstate = GetAliasPage $session

    SubmitNewAlias $viewstate $session
    Write-Host "Alias created."
}

function LogIn
{
    ## The template for the login request
    $loginRequest = 
@"
GET /Login.aspx HTTP/1.1
Host: $mailHost

"@

    ## Request the login page from the server
    $loginPage = Send-TcpRequest $mailHost 80 -Input $loginRequest

    ## Extract the ASP.Net session ID and viewstate for the page
    $session = GetSessionId $loginPage
    Write-Verbose "Got session: $session"
    $viewstate = GetViewState $loginPage

    ## The template for the login page submission
    $login = 
@"
POST /Login.aspx HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: __LENGTH
Host: $mailHost
Cookie: SelectedLanguage=; permcookie=1680x1050; ASP.NET_SessionId=__SESSION; screensize=1680x1050; settings=

__POSTDATA

"@

    ## Add the username, password, and viewstate into the information we want to
    ## post
    $postData = "__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=__REPLACEMENTVIEWSTATE&txtUserName=__USERNAME&txtPassword=__PASSWORD&LanguageList=&btnEnterClick.x=0&btnEnterClick.y=0"
    $postData = $postData.Replace("__REPLACEMENTVIEWSTATE", [System.Web.HttpUtility]::UrlEncode($viewstate))
    $postData = $postData.Replace("__USERNAME", [System.Web.HttpUtility]::UrlEncode($cred.GetNetworkCredential().UserName + '@' + $cred.GetNetworkCredential().Domain))
    $postData = $postData.Replace("__PASSWORD", [System.Web.HttpUtility]::UrlEncode($cred.GetNetworkCredential().Password))

    ## Replace the placeholders for the post data in the login template, as well as
    ## the ASP.Net session cookie
    $login = $login.Replace("__LENGTH", $postData.Length)
    $login = $login.Replace("__POSTDATA", $postData)
    $login = $login.Replace("__SESSION", $session)

    ## Submit the login page, and check for errors
    $output = Send-TcpRequest&nbs
p;$mailHost 80 -Input $login
    if($output -match "frmerror.aspx")
    {
        Write-Error "Failed to log in."
        Write-Error "Sent:"
        Write-Error $login
        Write-Error "Output:"
        Write-Error $output

        exit
    }
    ##############################################################################

    $session
}

function GetAliasPage($session)
{
    ## The template for the alias page request
    $aliasRequest = 
@"
GET /DomainAdmin/frmUserAlias.aspx HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
Host: $mailHost
Cookie: SelectedLanguage=; permcookie=1680x1050; ASP.NET_SessionId=__SESSION; screensize=1680x1050; settings=empty

"@

    ## Replace the placeholder for the ASP.Net session cookie
    $aliasRequest = $aliasRequest.Replace("__SESSION", $session)

    ## Request the alias page from the server
    $aliasPage = Send-TcpRequest $mailHost 80 -Input $aliasRequest

    ## Check for errors
    if($aliasPage -match "frmerror.aspx")
    {
        Write-Error "Failed to get alias page."
        Write-Error "Sent:"
        Write-Error $aliasRequest
        Write-Error "Got:"
        Write-Error $aliasPage

        exit
    }

    ## Extract the viewstate from the page
    GetViewState $aliasPage
}

function SubmitNewAlias($viewstate, $session)
{
    ## The template for the alias page submission
    $addAlias = 
@"
POST /DomainAdmin/frmUserAlias.aspx HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
Content-Length: __LENGTH
Host: $mailHost
Cookie: SelectedLanguage=; permcookie=1680x1050; ASP.NET_SessionId=__SESSION; screensize=1680x1050; settings=empty

__POSTDATA

"@

    ## Add the alias name and viewstate into the information we want to post
    $postData = "__EVENTTARGET=SaveTextImageButton&__EVENTARGUMENT=&__VIEWSTATE=__REPLACEMENTVIEWSTATE&headerinnercontrol_folderlist1_TreeView1_CheckedList=&headerinnercontrol_folderlist1_TreeView1_MultipleSelectedList=&sbsearchtext232=&sbsearchfolder232=&sbsearchfield232=0&headerinnercontrol_pulldownmenu1_M1_ContextData=&headerinnercontrol_pulldownmenu1_M2_ContextData=&txtName=__ALIASNAME&txtEmails=$recipient"
    $postData = $postData.Replace("__REPLACEMENTVIEWSTATE", [System.Web.HttpUtility]::UrlEncode($viewState))
    $postData = $postData.Replace("__ALIASNAME", [System.Web.HttpUtility]::UrlEncode($alias))

    ## Replace the placeholders for the post data in the login template, as well as
    ## the ASP.Net session cookie
    $addAlias = $addAlias.Replace("__LENGTH", $postData.Length)
    $addAlias = $addAlias.Replace("__POSTDATA", $postData)
    $addAlias = $addAlias.Replace("__SESSION", $session)

    ## Submit the alias page, and check for errors
    $output = Send-TcpRequest $mailHost 80 -Input $addAlias

    if($output -match "tiptextfailure")
    {
        Write-Error "Failed to add alias."
        $output = $output -replace "(?s).*<div class='tiptextfailure'>([^<]+)</div>.*",'$1'
        Write-Error $output

        exit
    }
    elseif($output -match "frmerror.aspx")
    {
        Write-Error "Failed to add alias -- protocol error"
        Write-Error "Sent:"
        Write-Error $addAlias
        Write-Error "Got:"
        Write-Error $output

        exit
    }
    elseif($output -notmatch "</html>")
    {
        Write-Error "Failed to add alias -- confirmation not present"
        Write-Error "Sent:"
        Write-Error $addAlias
        Write-Error "Got:"
        Write-Error $output

        exit
    }
}

## Extract the ASP.Net session ID frcom a page
function GetSessionId($page)
{
    $page -replace '(?s).*Set-Cookie: ASP.NET_SessionId=([^;]+);.*','$1'
}

## Extract the viewstate from a page
function GetViewState($page)
{
    $page -replace '(?s).*name="__VIEWSTATE" value="([^"]+)".*','$1'
}

. Main