So what would you do with RAS? Take as an example the application I mentioned above ”getting information from several remote sites to a central location. Listing 6-1 contains the code for a simple console mode application named RasDial.exe that allows a file to be copied from a local drive to a remote drive after the establishment of a RAS session. The complete project is included on the companion CD-ROM.
Listing 6-1
RasDial.cpp //RasDial.cpp:SimpleRASdialingexample... // #include"stdafx.h" //Prototypeforcallbackfunction... DWORDWINAPIRasDialFunc2(DWORDdwCallbackId,//User-definedvaluespecifiedin //RasDialcall DWORDdwSubEntry,//Subentryindexinmultilinkconnection HRASCONNhrasconn,//HandletoRASconnection UINTunMsg,//Typeofeventthathasoccurred RASCONNSTATErascs,//Connectionstateabouttobeentered DWORDdwError,//Errorthatmayhaveoccurred DWORDdwExtendedError//Extendederrorinformationfor //someerrors); HANDLEmuNotConnected; //-------------------------------------------------------------------- //Mainroutineforconsolemodeapplication... intmain(intargc,char*argv[]) { RASDIALPARAMSrdp; HRASCONNhrasconn=NULL; DWORDret; FILE*in; FILE*out; charbuffer[4096]; memset((void*)&rdp,'// RasDial.cpp: Simple RAS dialing example... // #include "stdafx.h" // Prototype for callback function... DWORD WINAPI RasDialFunc2(DWORD dwCallbackId, // User -defined value specified in // RasDial call DWORD dwSubEntry, // Subentry index in multilink connection HRASCONN hrasconn, // Handle to RAS connection UINT unMsg, // Type of event that has occurred RASCONNSTATE rascs, // Connection state about to be entered DWORD dwError, // Error that may have occurred DWORD dwExtendedError // Extended error information for // some errors); HANDLE muNotConnected; //-------------------------------------------------------------------- // Main routine for console mode application... int main(int argc, char* argv[]) { RASDIALPARAMS rdp; HRASCONN hrasconn=NULL; DWORD ret; FILE *in; FILE *out; char buffer[4096]; memset ((void *)&rdp, '\0', sizeof(RASDIALPARAMS)); rdp.dwSize = sizeof(RASDIALPARAMS); strcpy(rdp.szEntryName, "TEST"); strcpy(rdp.szPhoneNumber, ""); strcpy(rdp.szCallbackNumber, ""); strcpy(rdp.szUserName, "Dougr"); strcpy(rdp.szPassword, "SECRET"); strcpy (rdp.szDomain, "ACCESS"); // Create the mutex; don't worry about security (first NULL), // false (initially DO NOT own the mutex). // Second NULL means object not named, since it will not // be shared among processes... muNotConnected = CreateMutex(NULL, false, NULL); ret = RasDial(NULL, NULL, &rdp, 2, RasDialFunc2, &hrasconn); if (ret) { char buffer[255]; RasGetErrorString(ret, buffer, 255); printf("\nGetLastError=%d %s", GetLastError(), buffer); return(0); } // This is to give the first call to RasDialFunc2 // time to get mutex... Sleep(1000); // Wait a long time, 100 seconds. This is likely longer // than needed for the file in question but allows // extra time for negotiation. switch (WaitForSingleObject(muNotConnected, 100000)) { case WAIT_ABANDONED: printf("\nWait abandoned ...Thread terminated without giving" " up control!"); break; case WAIT_TIMEOUT: printf("\n100 seconds elapsed...Bailing."); break; case WAIT_OBJECT_0: printf("\nObject acquired ...All seems well"); in = fopen("c:\\service.log", "r+t"); // Note that the 'c' included in the mode parameter // is used to force the contents to be written // directly to disk when fflush is called. // Obviously, the path should be modified for // your server. out = fopen("\\\\dougr\\shared\\service.log", "w+tc"); if (in == NULL out==NULL) { printf("\nCannot open file..."); } else { size_t bytes; while ((bytes = fread((void *)buffer, 1, 4096, in))) { fwrite((void *)buffer, 1, bytes, out); } fclose(in); fflush (out); fclose(out); printf("\nData Copied!"); } break; } RasHangUp(hrasconn); CloseHandle(muNotConnected); return 0; } //-------------------------------------------------------------------- // Callback function... DWORD WINAPI RasDialFunc2(DWORD dwCallbackId, // User-defined value specified in // RasDial call DWORD dwSubEntry, // Subentry index in multilink // connection HRASCONN hrasconn, // Handle to RAS connection UINT unMsg, // Type of event that has occurred RASCONNSTATE rasconnstate, // Connection state about to be // entered DWORD dwError, // Error that may have occurred DWORD dwExtendedError // Extended error information for // some errors) { switch(rasconnstate) { case RASCS_OpenPort: if (WaitForSingleObject(muNotConnected, 0) != WAIT_OBJECT_0) { printf("\nShould not be here! Can't get mutex!"); } else { printf("\nrasconnstate=RASCS_OpenPort"); } break; case RASCS_PortOpened: printf("\nrasconnstate=RASCS_PortOpened"); break; case RASCS_ConnectDevice: printf("\nrasconnstate=RASCS_ConnectDevice"); break; case RASCS_DeviceConnected: printf("\nrasconnstate=RASCS_DeviceConnected"); break; case RASCS_AllDevicesConnected: printf("\nrasconnstate=RASCS_AllDevicesConnected"); break; case RASCS_Authenticate: printf("\nrasconnstate=RASCS_Authenticate"); break; case RASCS_AuthNotify: printf("\nrasconnstate=RASCS_AuthNotify"); break; case RASCS_AuthRetry: printf("\nrasconnstate=RASCS_AuthRetry"); break; case RASCS_AuthCallback: printf("\nrasconnstate=RASCS_AuthCallback"); break; case RASCS_AuthChangePassword: printf("\nrasconnstate=RASCS_AuthChangePassword"); break; case RASCS_AuthProject: printf("\nrasconnstate=RASCS_AuthProject"); break; case RASCS_AuthLinkSpeed: printf("\nrasconnstate=RASCS_AuthLinkSpeed"); break; case RASCS_AuthAck: printf("\nrasconnstate=RASCS_AuthAck"); break; case RASCS_ReAuthenticate: printf("\nrasconnstate=RASCS_ReAuthenticate"); break; case RASCS_Authenticated: printf("\nrasconnstate=RASCS_Authenticated"); break; case RASCS_PrepareForCallback: printf("\nrasconnstate=RASCS_PrepareForCallback"); break; case RASCS_WaitForModemReset: printf("\nrasconnstate=RASCS_WaitForModemReset"); break; case RASCS_WaitForCallback: printf("\nrasconnstate=RASCS_WaitForCallback"); break; case RASCS_Projected: printf("\nrasconnstate=RASCS_Projected"); break; case RASCS_SubEntryConnected: printf("\nrasconnstate=RASCS_SubEntryConnected"); break; case RASCS_SubEntryDisconnected: printf("\nrasconnstate=RASCS_SubEntryDisconnected"); break; case RASCS_Interactive: printf("\nrasconnstate=RASCS_Interactive"); break; case RASCS_RetryAuthentication: printf("\nrasconnstate=RASCS_RetryAuthentication"); break; case RASCS_CallbackSetByCaller: printf("\nrasconnstate=RASCS_CallbackSetByCaller"); break; case RASCS_PasswordExpired: printf("\nrasconnstate=RASCS_PasswordExpired"); break; #if (WINVER >= 0x500) case RASCS_InvokeEapUI: printf("\nrasconnstate=RASCS_InvokeEapU"); break; #endif case RASCS_Connected: printf("\nrasconnstate=RASCS_Connected - Copy the data..."); ReleaseMutex(muNotConnected); break; case RASCS_Disconnected: printf("\nrasconnstate=RASCS_Disconnected"); break; } if (dwError) { printf("\nGot here error=%d rasconnstate=%d", dwError,rasconnstate); } return 1; // Continue getting notifications... }',sizeof(RASDIALPARAMS)); rdp.dwSize=sizeof(RASDIALPARAMS); strcpy(rdp.szEntryName,"TEST"); strcpy(rdp.szPhoneNumber,""); strcpy(rdp.szCallbackNumber,""); strcpy(rdp.szUserName,"Dougr"); strcpy(rdp.szPassword,"SECRET"); strcpy(rdp.szDomain,"ACCESS"); //Createthemutex;don'tworryaboutsecurity(firstNULL), //false(initiallyDONOTownthemutex). //SecondNULLmeansobjectnotnamed,sinceitwillnot //besharedamongprocesses... muNotConnected=CreateMutex(NULL,false,NULL); ret=RasDial(NULL,NULL,&rdp,2,RasDialFunc2,&hrasconn); if(ret) { charbuffer[255]; RasGetErrorString(ret,buffer,255); printf("\nGetLastError=%d%s",GetLastError(),buffer); return(0); } //ThisistogivethefirstcalltoRasDialFunc2 //timetogetmutex... Sleep(1000); //Waitalongtime,100seconds.Thisislikelylonger //thanneededforthefileinquestionbutallows //extratimefornegotiation. switch(WaitForSingleObject(muNotConnected,100000)) { caseWAIT_ABANDONED: printf("\nWaitabandoned...Threadterminatedwithoutgiving" "upcontrol!"); break; caseWAIT_TIMEOUT: printf("\n100secondselapsed...Bailing."); break; caseWAIT_OBJECT_0: printf("\nObjectacquired...Allseemswell"); in=fopen("c:\service.log","r+t"); //Notethatthe'c'includedinthemodeparameter //isusedtoforcethecontentstobewritten //directlytodiskwhenfflushiscalled. //Obviously,thepathshouldbemodifiedfor //yourserver. out=fopen("\\dougr\shared\service.log","w+tc"); if(in==NULLout==NULL) { printf("\nCannotopenfile..."); } else { size_tbytes; while((bytes=fread((void*)buffer,1,4096,in))) { fwrite((void*)buffer,1,bytes,out); } fclose(in); fflush(out); fclose(out); printf("\nDataCopied!"); } break; } RasHangUp(hrasconn); CloseHandle(muNotConnected); return0; } //-------------------------------------------------------------------- //Callbackfunction... DWORDWINAPIRasDialFunc2(DWORDdwCallbackId,//User-definedvaluespecifiedin //RasDialcall DWORDdwSubEntry,//Subentryindexinmultilink //connection HRASCONNhrasconn,//HandletoRASconnection UINTunMsg,//Typeofeventthathasoccurred RASCONNSTATErasconnstate,//Connectionstateabouttobe //entered DWORDdwError,//Errorthatmayhaveoccurred DWORDdwExtendedError//Extendederrorinformationfor //someerrors) { switch(rasconnstate) { caseRASCS_OpenPort: if(WaitForSingleObject(muNotConnected,0)!= WAIT_OBJECT_0) { printf("\nShouldnotbehere!Can'tgetmutex!"); } else { printf("\nrasconnstate=RASCS_OpenPort"); } break; caseRASCS_PortOpened: printf("\nrasconnstate=RASCS_PortOpened"); break; caseRASCS_ConnectDevice: printf("\nrasconnstate=RASCS_ConnectDevice"); break; caseRASCS_DeviceConnected: printf("\nrasconnstate=RASCS_DeviceConnected"); break; caseRASCS_AllDevicesConnected: printf("\nrasconnstate=RASCS_AllDevicesConnected"); break; caseRASCS_Authenticate: printf("\nrasconnstate=RASCS_Authenticate"); break; caseRASCS_AuthNotify: printf("\nrasconnstate=RASCS_AuthNotify"); break; caseRASCS_AuthRetry: printf("\nrasconnstate=RASCS_AuthRetry"); break; caseRASCS_AuthCallback: printf("\nrasconnstate=RASCS_AuthCallback"); break; caseRASCS_AuthChangePassword: printf("\nrasconnstate=RASCS_AuthChangePassword"); break; caseRASCS_AuthProject: printf("\nrasconnstate=RASCS_AuthProject"); break; caseRASCS_AuthLinkSpeed: printf("\nrasconnstate=RASCS_AuthLinkSpeed"); break; caseRASCS_AuthAck: printf("\nrasconnstate=RASCS_AuthAck"); break; caseRASCS_ReAuthenticate: printf("\nrasconnstate=RASCS_ReAuthenticate"); break; caseRASCS_Authenticated: printf("\nrasconnstate=RASCS_Authenticated"); break; caseRASCS_PrepareForCallback: printf("\nrasconnstate=RASCS_PrepareForCallback"); break; caseRASCS_WaitForModemReset: printf("\nrasconnstate=RASCS_WaitForModemReset"); break; caseRASCS_WaitForCallback: printf("\nrasconnstate=RASCS_WaitForCallback"); break; caseRASCS_Projected: printf("\nrasconnstate=RASCS_Projected"); break; caseRASCS_SubEntryConnected: printf("\nrasconnstate=RASCS_SubEntryConnected"); break; caseRASCS_SubEntryDisconnected: printf("\nrasconnstate=RASCS_SubEntryDisconnected"); break; caseRASCS_Interactive: printf("\nrasconnstate=RASCS_Interactive"); break; caseRASCS_RetryAuthentication: printf("\nrasconnstate=RASCS_RetryAuthentication"); break; caseRASCS_CallbackSetByCaller: printf("\nrasconnstate=RASCS_CallbackSetByCaller"); break; caseRASCS_PasswordExpired: printf("\nrasconnstate=RASCS_PasswordExpired"); break; #if(WINVER>=0x500) caseRASCS_InvokeEapUI: printf("\nrasconnstate=RASCS_InvokeEapU"); break; #endif caseRASCS_Connected: printf("\nrasconnstate=RASCS_Connected-Copythedata..."); ReleaseMutex(muNotConnected); break; caseRASCS_Disconnected: printf("\nrasconnstate=RASCS_Disconnected"); break; } if(dwError) { printf("\nGothereerror=%drasconnstate=%d", dwError,rasconnstate); } return1;//Continuegettingnotifications... } |
Take a look at the top of the listing. The main routine first declares several variables used for the RAS calls and for the actual file copying that is the objective of this exercise. The RASDIALPARAMS structure, rdp , is initialized next . First, dwSize is set to the size of the structure. This entry is used by RAS to determine the version of the structure being sent. The next entry, szEntryName , is the name of the dial-up connection as it appears on the Settings/Network and Dial-Up Connections menu. The szPhoneNumber entry is the phone number to dial if it differs from the phone number stored in the connection specified by szEntryName . The szCallbackNumber entry is used to specify a number for the remote site to call back to the originator of the call.
NOTE
In some previous Windows versions, szCallbackNumber could be set to an asterisk (*) to indicate that the number stored in the registry as a callback should be used. This is not a valid option in Windows 2000 or Windows NT version 4 or later. This callback number is ignored unless the user has "Set By Caller" permission for callbacks on the remote RAS server.
The next three entries are used for authenticating the RAS connection. The user name and password are self-explanatory, but the connection might not work exactly as you think it will. For example, the user name and password are used for access to the RAS server on the other end of the connection, but this does not mean you are logged on as that user on the remote network. If the user name and password on the local machine and the remote machine are the same, this gives an appearance of simultaneously logging on to the RAS server and some of the resources on the other end of the connection. This is only an appearance. If you need to access resources under an authority different from the user name and password on the local machine, the application might need to call LogonUser , as described in Chapter 4.
You might or might not need to set the szDomain entry, depending on the identity of the server being called. If you're connecting to a Windows 2000 server, you are better off specifying a domain. Connecting to Internet servers running other operating systems, such as Unix, generally does not require a domain name to be specified. Setting an asterisk for the domain name specifies that the domain set in the phonebook entry should be used.
The mutex muNotConnected is created with a call to CreateMutex . (See Chapter 2 for details.) The mutex is created with a NULL passed as the security descriptor to get the default security descriptor, a false indicating that the calling thread does not want ownership of the object, and a final NULL indicating that no name is to be used. The name is not required because this mutex will not be used across process boundaries.
The next step is to actually dial the phone, using the RasDial Win32 API function. The prototype for this function is as follows :
DWORDRasDial(LPRASDIALEXTENSIONSlpRasDialExtensions, //Pointertofunctionextensionsdata LPCTSTRlpszPhonebook,//Pointertofullpathandfilenameof //phonebookfile LPRASDIALPARAMSlpRasDialParams, //Pointertocallingparametersdata DWORDdwNotifierType,//SpecifiestypeofRasDialeventhandler LPVOIDlpvNotifier,//SpecifiesahandlerforRasDialevents LPHRASCONNlphRasConn//Pointertovariabletoreceive //connectionhandle); |
The first parameter, lpRasDialExtensions , is used to set some advanced options or can be set to NULL if no options need to be set. Features you can set using this structure include the use of a dialing prefix and suffix, the modem speaker, software compression, the use of scripting options, and Windows 2000 Extensible Authentication Protocol (EAP). In the RasDial.exe sample program, lpRasDialExtensions is set to NULL.
The second parameter, lpszPhonebook , is a pointer to the name of a phonebook file if the default phonebook is not to be used. Phonebook files have a .pbk extension. Next, the address of the RASDIALPARAMS structure filled in earlier is passed as the lpRasDialParams parameter. The fourth parameter, dwNotifierType , specifies the way in which asynchronous events generated by dialing are handled. Interestingly, there are no defined constants for these values. 0xFFFFFFFF specifies that a window procedure should receive notifications and that the fifth parameter, lpvNotifier , should contain a window handle. This parameter is not commonly used in server-based application development. A value of 0, 1, or 2 specifies that, respectively, lpvNotifier should contain a pointer to a RasDialFunc , RasDialFunc1 , or RasDialFunc2 function. The RasDial.exe example uses a dwNotifierType of 2 and therefore specifies a callback function, RasDialFunc2 , whose prototype is declared earlier. The name of the function could be anything; I use RasDialFunc2 here for clarity. Alternatively, lpvNotified could be NULL, in which case RasDial operates synchronously. This is seldom an ideal choice in a server application. RasDialFunc , RasDialFunc1 , and RasDialFunc2 behave similarly, with RasDialFunc only accepting uMsg , rasconnstate , and dwError and returning no value. RasDialFunc1 adds hrasconn and dwExtendedError . RasDialFunc2 has the prototype above, and the value returned allows the callback to indicate if it wishes to receive further notifications. Returning 0 indicates no further notifications should be sent; a nonzero value indicates notifications should continue.
The final parameter is lphRasConn , a handle that is used for other RAS operations, including RasHangUp . If an error occurs in the call to RasDial , a message is displayed using another RAS API function, RasGetErrorString. The prototype for this function is as follows:
DWORDRasGetErrorString(UINTuErrorValue,//Errortogetstringfor LPTSTRlpszErrorString,//Buffertoholderrorstring DWORDcBufSize//Size,incharacters,ofbuffer); |
The uErrorValue parameter is the value returned by one of the RAS functions. The lpszErrorString parameter is the buffer to receive the error string, and cBufSize is the size, in characters, of the buffer.
Once RasDial returns with a value of 0, the function waits briefly to allow the callback function to acquire the mutex muNotConnected. One second is allowed for this, and the mutex will be acquired on the first call to RasDialFunc2 , when the port is opened.
NOTE
One unusual thing about copying files over a RAS connection became clear to me during testing of this application. Notice in main the unusual file open mode passed in the call to fopen to open the file on the remote server. The "w" indicates the file should be opened for writing, the "+" indicates the file should be created if it does not exist, and the "t" indicates this is a text mode file, not unreasonable for the data being copied. But what about that "c"? I have programmed in C and C++ since 1984, and I've never seen this. The Microsoft Visual C++ documentation indicates the "c" is used to force the waiting data to be flushed to the disk when a call to fflush is made. On some occasions, without this additional character in the open mode parameter, I received an error upon the termination of the RAS connection indicating that not all data had been written to the disk before the connection was terminated. So while RAS is almost like being attached via a traditional network, it is not exactly like it.
The main function next calls WaitForSingleObject on the muNotConnected mutex with a timeout of 100,000 to allow 100 seconds for the connection to take place. If WaitForSingleObject returns with a return value of WAIT_ABANDONED, the thread that owned the mutex (in this case the thread calling RasDialFunc2 ) has terminated without giving up ownership of the mutex. This indicates some catastrophic failure in the RAS system. If WaitForSingleObject returns WAIT_TIMEOUT, 100 seconds have elapsed and the application assumes that the operation has failed.
If the object is acquired, which WaitForSingleObject indicates by returning WAIT_OBJECT_0, the connection should be present and the file copy takes place. Finally, RasHangUp is called with the handle obtained during the call to RasDial , and the handle to the mutex object is freed.
RasDialFunc2 is one of three different callback functions that can be used to make RasDial operate asynchronously. In the example program, RasDialFunc2 is little more than a switch statement that displays messages as the dial-up process takes place. One element that is important in creating a callback such as this is that any operation must take place and quickly return to the caller so that actual work can be done. In the case of communications in which modem negotiations are taking place, any delay could cause RAS to think an error has occurred. Of course, in the event of an error, it is allowable to take some time in the function, even putting up a message box if needed. In server-based applications, however, this is not usually advisable.
The only case blocks that are anything beyond debugging aids are the handlers for opening the port and connecting. When the port is opened, the following code section is executed:
caseRASCS_OpenPort: if(WaitForSingleObject(muNotConnected,0)!=WAIT_OBJECT_0) { printf("\nShouldnotbehere!Can'tgetmutex!"); } else { printf("\nrasconnstate=RASCS_OpenPort"); } break; |
This function waits for the mutex with a timeout of 0, meaning that the function should immediately return, even if the object has not been acquired. If the return is not a return value indicating the object has been acquired, a message is displayed indicating the mutex cannot be obtained. This should never occur. When the connection is made, the following code is executed.
caseRASCS_Connected: printf("\nrasconnstate=RASCS_Connected-Copythedata..."); ReleaseMutex(muNotConnected); break; |
This releases the mutex, which lets the main routine know that the connection has been made and the copy can proceed. This is how the callback lets the rest of the program know that a connection has been established.
When the program is run, notifications are received as the dialing takes place. The specific order of the notifications cannot be counted on, though using the notification associated with opening the port and the establishment of connections seems reliable in practice.