Python Script to dial a VPN connection, automate VPN routing table updates.

Dale Nagata dnagata at creo.com
Fri Mar 23 22:52:40 EST 2001


Warren Postma wrote:
> 
> Sorry for the nearly-off-topic nature of this post, but ....
> 
> I work from home now, and there's something in Windows 2000 that's
> annoyingly manual:
> 
> I have a permanent internet connection via @home cable modem, and I "dial"
> through that to create a VPN connection to the office.  The problems are
> two:
> 
> (1) Windows won't accept or save my userid, password and the domain name, so
> I have to enter it manually.
> 
> (2) After connecting, I have two default gateways in my routing tables
> (ROUTE PRINT) and I need to update it so only the traffic that is destined
> to the office goes through that interface.  The IP addresses I get given are
> different each time, making a batch file insufficient, so voila, a perfect
> use for python.
> 
> So after solving #2 above in Python, I wondered, does anyone know how, in
> Python win32 extensions, one might dial an RAS connection, and if it's
> possible to do so  through the win32 API somewhere or through COM? Then I
> could solve #1 above as well.
> 
> Anyone else think this might make a nice little Python applet if I build in
> some more VPN/RAS stuff and maybe even Gui-fy it a bit in wxPython or
> tkInter.
> 

I have a similar home setup.

The win32ras module didn't have all the functions that I needed,
so I used a combination of shelling out to the GUI dialer and
monitoring the RAS connection state via some calls to RASAPI32.DLL
using the windll module, then I just shell out to the route command
to set up the routing table.

The attached script should work if you have set up a dialup networking
entry
named 'Office VPN', but you can change PHONEBOOK_ENTRY at the top of the
script to whatever you want (heck, you can change the whole script
to whatever you want :-)

Rename the script to vpnadmin.cmd or .bat - our mail system screens out
attachments that look like executable content

works for me


--
Dale Nagata         |  tel : +1 604.451.2700 ext. 2254 (UTC-0800)
Software Developer  |  fax : +1 604.437.9891 
Creo Products Inc.  |  pgr : +1 604.691.8279
Burnaby BC Canada   |  http://www.creo.com/
-------------- next part --------------
@python -x %~f0 %* && pause && goto :EOF
#
# vpnadmin.py
#
# 1. Dial RAS phonebook entry PHONEBOOK_ENTRY. (skip if already connected)
# 2. figure out what IP address was assigned to connection.
# 3. Modify routing table so that office network goes through this gateway address.
#
# This works with my ADSL connection.
#
# W2K: disable default prompting for username/password
# or wait in script while in this state???
#
# also remember to disable default gateway for PPTP connection entry
#

# ----------------------------------------------------------------------------

# standard Python modules
import os
import sys
import time
import string
import struct
import msvcrt

# Win32 Extension modules
import win32api
import win32con
import win32ras
import winerror

# DLL pseudo-modules, for directly callng raw Win32 APIs that aren't wrapped
import windll
user32 = windll.module( 'user32' )
rasapi32 = windll.module( 'rasapi32' )

# ----------------------------------------------------------------------------

# name of phonebook entry to dial, in default system phonebook
PHONEBOOK_ENTRY = 'Office VPN'

# ----------------------------------------------------------------------------

# other constants...
RESERVED = 0

# <ras.h>
RASCS_PAUSED = 0x1000
RASCS_DONE   = 0x2000

RASCS_OpenPort = 0
RASCS_PortOpened = 1
RASCS_ConnectDevice = 2
RASCS_DeviceConnected = 3
RASCS_AllDevicesConnected = 4
RASCS_Authenticate = 5
RASCS_AuthNotify = 6 # sending username and password...
RASCS_AuthRetry = 7
RASCS_AuthCallback = 8
RASCS_AuthChangePassword = 9
RASCS_AuthProject = 10
RASCS_AuthLinkSpeed = 11
RASCS_AuthAck = 12
RASCS_ReAuthenticate = 13
RASCS_Authenticated = 14
RASCS_PrepareForCallback = 15
RASCS_WaitForModemReset = 16
RASCS_WaitForCallback = 17
RASCS_Projected = 18

#if (WINVER >= 0x400)
RASCS_StartAuthentication = 19
RASCS_CallbackComplete = 20
RASCS_LogonNetwork = 21
#endif

RASCS_SubEntryConnected = 22
RASCS_SubEntryDisconnected = 23

RASCS_Interactive = RASCS_PAUSED
RASCS_RetryAuthentication = RASCS_PAUSED + 1
RASCS_CallbackSetByCaller = RASCS_PAUSED + 2
RASCS_PasswordExpired = RASCS_PAUSED + 3

RASCS_Connected = RASCS_DONE
RASCS_Disconnected = RASCS_DONE + 1

RASP_PppIp=0x8021

# ----------------------------------------------------------------------------

# return string formatted like default exception traceback
def format_traceback():
    import sys
    x = sys.exc_info()
    buf = 'Traceback (innermost last):\n'
    import traceback
    tblist = traceback.extract_tb( x[2] )
    for tb in tblist:
        buf = buf + '  File "%s", line %d, in %s\n    %s\n' % tb
    buf = buf + '%s: %s\n' % ( x[0].__name__, str( x[1] ) )
    return buf

# print traceback to stdout
def print_traceback( buf=None ):
    if not buf:
        buf = format_traceback()
    import sys
    sys.stderr.write( buf )

# ----------------------------------------------------------------------------

# get HWND of window matching given class name and title
def FindWindow( wndclassname, windowname ):
    hwnd = user32.FindWindow( windll.cstring( wndclassname ), windll.cstring( windowname ) )
    if hwnd == 0:
        errcode = win32api.GetLastError()
        errmsg = win32api.FormatMessage( errcode )
        raise win32api.error( errcode, 'FindWindow', errmsg )
    return hwnd

# ----------------------------------------------------------------------------

# dial a phonebook entry if it isn't already connected
# run the GUI tool so it looks nicer
# wait for the dialog to close
# check connection status afterwards
def DialPhoneBookEntry( phonebook_entry ):
    isconnected = 0
    conns = win32ras.EnumConnections()
    for conn in conns:
        #print conn
        if conn[1] == phonebook_entry:
            isconnected = 1

    if isconnected:
        print 'Connected to', phonebook_entry
    else:
        print 'Dialing %s . . .' % phonebook_entry
        win32api.WinExec( 'rasphone -d \"%s\"' % phonebook_entry )
        # TODO: handle Cancel within rasphone
        status = RASCS_Disconnected
        while not isconnected:
            win32api.Sleep( 1000 )
            conns = win32ras.EnumConnections()
            for conn in conns:
                if conn[1] == phonebook_entry:
                    hConn = conn[0]
                    status = win32ras.GetConnectStatus( hConn )
                    # intermediate states 5 = RASCS_Authenticate, 14=RASCS_Authenticated
                    if status[0] == RASCS_Authenticate:
                        if status != status[0]:
                            status = status[0]
                            print 'Authenticating...'
                    elif status[0] == RASCS_Authenticated:
                        if status != status[0]:
                            status = status[0]
                            print 'Authenticated.'
                    elif status[0] == RASCS_Connected:
                        print 'Connected.'
                        isconnected = 1
                        break
                    else:
                        print 'status:', status
            else:
                # *** this only works in NT4
                # *** need to figure out equiv for W2K
                winver = win32api.LOWORD( win32api.GetVersion() )
                if winver < 5:
                    try:
                        hwnd = FindWindow( '#32770', 'Connecting to %s...' % phonebook_entry )
                    except win32api.error, err:
                        if err[0] == winerror.ERROR_PROC_NOT_FOUND:
                            print 'Connection cancelled.'
                            time.sleep( 1 )
                            return
        #while not connected
    #else not connected

    return isconnected
#DialPhoneBookEntry

# ----------------------------------------------------------------------------

# return dotted decimal IP address for RAS connection
def GetRasIpAddress( phonebook_entry ):
    connlist = win32ras.EnumConnections()
    #[(655360, 'Office PPTP', 'VPN', 'RASPPTPM')]
    for conninfo in connlist:
        if conninfo[1] == phonebook_entry:
            break
    else:
        print "RAS connection '%s' not found" % phonebook_entry
        sys.exit(1)

    hrasconn = conninfo[0]
    structsize = struct.calcsize( 'L L 16s' )

    dw = windll.membuf( struct.calcsize( 'i' ) )
    dw.mb.write( struct.pack( 'i', structsize ) )

    buf = windll.membuf( structsize )
    buf.mb.write( struct.pack( 'L', structsize ) ) # MUST set struct member

    # on W2K/laptop, need a bit of a delay???
    maxretries = 10
    count = 0
    while count < maxretries:
        err = rasapi32.RasGetProjectionInfo( hrasconn, RASP_PppIp, buf, dw )
        if err == 0:
            break
        else:
            count = count + 1
            if count < maxretries:
                win32api.Sleep( 1000 )
            else:
                print 'RasGetProjectionInfo error %d' % err
                print 'go look up the error code in <ras.h>'
                sys.exit( 1 )

    dwSize, err, rawipaddr = struct.unpack( 'L L 16s', buf.mb.read() )
    ipaddr, junk = string.split( rawipaddr,'\0', 1 )
    #'192.168.91.4\000_ma'

    print 'IP address:', ipaddr
    return ipaddr
#GetRasIpAddress

# ----------------------------------------------------------------------------

# update routing table for office subnets given IP address to use as gateway
def SetupOfficeRouting( gateway ):
    os.system( 'route add 192.197.216.0 mask 255.255.252.0 %s' % gateway )
    os.system( 'route add 192.168.0.0 mask 255.255.128.0 %s' % gateway )
    os.system( 'route add 204.174.12.32 mask 255.255.255.240 %s' % gateway )
    os.system( 'route add 204.174.12.48 mask 255.255.255.240 %s' % gateway )
    os.system( 'route add 204.174.12.112 mask 255.255.255.240 %s' % gateway )
    print 'IP routing established for office network using PPTP connection.'

# ----------------------------------------------------------------------------

# hangup RAS connection, if connected
def HangUpConnection( phonebook_entry ):
    connlist = win32ras.EnumConnections()
    #[(655360, 'Office PPTP', 'VPN', 'RASPPTPM')]
    for conninfo in connlist:
        if conninfo[1] == phonebook_entry:
            hrasconn = conninfo[0]
            win32ras.HangUp( hrasconn )
            break
    else:
        print 'Not connected.'
        #print "RAS connection '%s' not found" % phonebook_entry
        sys.exit(1)

# ----------------------------------------------------------------------------

# manage Office PPTP connection
def vpnadmin():
    USAGE = 'Usage: vpnadmin { dial | hangup | print }'

    argc = len( sys.argv )
    if argc != 2:
        print USAGE
        sys.exit( 1 )

    cmd = sys.argv[1]
    if cmd == 'dial':
        try:
            isp_entry = os.environ['ISP_PHONEBOOK_ENTRY']
            DialPhoneBookEntry( isp_entry )
            pass
        except KeyError:
            pass
        DialPhoneBookEntry( PHONEBOOK_ENTRY )
        ipaddr = GetRasIpAddress( PHONEBOOK_ENTRY )
        SetupOfficeRouting( ipaddr )
    elif cmd == 'hangup':
        HangUpConnection( PHONEBOOK_ENTRY )
    elif cmd == 'print':
        os.system( 'ipconfig /all' )
        os.system( 'route print' )
    else:
        print 'Unrecognized command:', cmd
        print USAGE
        sys.exit( 1 )

def main():
    try:
        vpnadmin()
    except SystemExit:
        pass
    except:
        print_traceback()
        raw_input( 'Press ENTER to finish . . . ' )

if __name__ == '__main__':
    main()


More information about the Python-list mailing list