Most people today believe that Netscape and other "internet" companies produce true Internet products. They think that Netscape's programmers actually sit down and write the code that interacts with your modem and ISP. They click on a link and think that it's their browser that's connecting to the server and downloading a file. They use FTP and think that it's their FTP program that's handling the data as it flows across the Internet and tumbles down the wires to their computer.
They couldn't be more wrong !!
Netscape may be a great company with an exceptional product like Navigator 4.0, but they don't dabble in Internet programming. What we're going to demonstrate is that Internet browsers and servers do not hold code of any complexity. Probably less than 5% of all the code which went into Navigator was Internet related code and when I say Internet related code I mean Windows Sockets code.
Windows sockets or WinSock is an abstraction layer applied by the University of California over the TCP/IP protocol (This might be a good time to jump over to our WinSock tutorial. The WinSock makes TCP/IP (read Internet) programming easier by equating file transfers over the Internet with file transfers over the hard disk. So, you have nice tidy little functions like send() and recv() to transfer information over the Internet. Unfortunately, if the abstraction layer is complex and intricate enough, the commands that you give and their translation into actions have no perceivable relation with each other. So, I might spend my whole life using sockets but never learn that TCP packets carry sequence and acknowledgement numbers.
So, sockets programming is not Internet programming. Netscape is not directly talking to the serial port or the network card and unless you do that, you cannot claim to be writing Internet code. All it's doing is calling other peoples' functions and code, which creates the actual bytes which flow across the lines. Infact, on most machines running Windows95, the file wsock32.dll and the VxD wsock.vxd (collectively called the stack) have been written by Microsoft ! It's Microsoft which is the Internet company in this case, not Netscape.
The same goes for Java too. It may be called an Internet programming language but all it does is call WinSock code through Netscape. When you're through with this page, jump over to our Java expose.
What we're going to do is prove to you that all the hype about Netscape being an Internet company is just so much hot air. We're going to show you how to fool Netscape into doing anything you want it to, by creating our own dummy wsock32.dll.
The Elusive .dll
All code under Windows '95 is in .dlls and the .lib files tell you exactly where in which .dll the requested function code is stored. So what we're going to do now is write our own dummy wsock32.dll, but we'll be leaving the .lib file untouched. We've used Visual C++ 4.0 for all these programs. Create a project to make a .dll. Name the project yyz and then add the code given below to a file yyz.c.
Note: These programs only work under Netscape Navigator. The reason Internet Explorer rejects them is because it call some undocumented functions in wsock32.dll which we don't really know much about.
Before creating our fake wsock32.dll, make sure you assign a default page to Netscape. Any page will do, but use an IP address (e.g. 202.54.1.18), not a name like www.neca.com. The reason we don't want to use a domain name is because the process of converting a name into a corresponding IP address is very complex and involves a whole bunch of functions. Besides, we'd also have to emulate the DNS Server and we're just too lazy to do that!
What we're going to be doing here is create a very limited .dll where the only function is DllMain, which is the first function to be called by Netscape. In DllMain we have a function called abc() which simply writes "DllMain", or anything else within it's brackets, to the hard disk in the file z.txt. This is a better way of keeping track of function calls than using messageboxes which continuously halt the program and whose messages cannot be saved.
yyz.c
#include <windows.h> #include <stdio.h> void abc(char * p) { FILE *fp=fopen("c:\\z.txt","a+"); fprintf(fp,"%s\n",p); fclose(fp); } char aa[1000]; BOOL WINAPI DllMain( HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved) { abc("DllMain"); return 1; }
This exercise will prove that Netscape cannot work without the functions in Wsock32.dll.
DllMain is called when Netscape both loads and unloads and if it is absent the .dll will not load. Inside DllMain we have to put the statement return 1;, for the .dll to load successfully. A return 0; implies that an error occurred and the .dll is not loaded. For our dummy .dll to have any effect, it must be placed in the subdirectory, C:\Windows\System and renamed to wsock32.dll.
Before you do this be sure to copy the original wsock32.dll somewhere else !
Tip: When the project is built we get the file yyz.dll in the debug directory. Instead of copying the .dll into C:\Windows\System by hand each time, simple create a batch file copydll.bat with the following commands
Copy c:\yyz\debug\yyz.dll c:\windows\system\wsock32.dll del c:\z.txt
Once the fake DLL is in place, start up Netscape and watch the errors fly. Netscape will react angrily, and spit out an 'Unable to initialize the network layer! Check your winsock for errors' error message. Netscape not finding many of the functions it needs, will hang taking your computer with it. Pay it no attention and remove Netscape from memory. To remedy the situation press Ctrl+Atl+Del once, click on the line which says Netscape and then click the button End Task. Wait for 10 seconds. If there is no change, repeat the procedure and keep your fingers crossed.
What we've demonstrated here is that Netscape will accept our .dll as genuine, even though it hardly contains anything at all, and will attempt to communicate with it.
If you analyse the file shown above, you'll see that DllMain is called when Netscape loads into memory and when it unloads from memory.
z.txt
DllMain DllMain
Create the .def file as given below. It's in the .def file that you "export" your functions so that they may be called by other programs.
yyz.def
LIBRARY yyz DESCRIPTION "wsock function" EXPORTS WSAAsyncGetHostByAddr @102 WSAAsyncGetHostByName @103 WSAAsyncGetProtoByName @105 WSAAsyncGetProtoByNumber @104 WSAAsyncGetServByName @107 WSAAsyncGetServByPort @106 WSAAsyncSelect @101 WSACancelAsyncRequest @108 WSACancelBlockingCall @113 WSAGetLastError @111 WSAIsBlocking @114 WSARecvEx @1107 WSASetBlockingHook @109 WSASetLastError @112 WSAStartup @115 WSAUnhookBlockingHook @110 accept @1 bind @2 closesocket @3 connect @4 gethostbyaddr @51 gethostbyname @52 gethostname @57 getpeername @5 getprotobyname @53 getprotobynumber @54 getservbyname @55 getservbyport @56 getsockname @6 getsockopt @7 htonl @8 htons @9 inet_addr @10 inet_ntoa @11 ioctlsocket @12 listen @13 ntohl @14 ntohs @15 recv @16 recvfrom @17 select @18 send @19 sendto @20 setsockopt @21 shutdown @22 socket @23 WSACleanup @116 __WSAFDIsSet @151 s_perror @1108 TransmitFile @1140 EnumProtocolsA @1111 EnumProtocolsW @1112 GetAddressByNameA @1109 GetAddressByNameW @1110 GetNameByTypeA @1115 GetNameByTypeW @1116 GetServiceA @1119 GetServiceW @1120 GetTypeByNameA @1113 GetTypeByNameW @1114 SetServiceA @1117 SetServiceW @1118 NPLoadNameSpaces @1130 dn_expand @1106 inet_network @1100 rexec @1103 rresvport @1104 WsControl DllMain
Once you're through with the .def file, create a .c file and cut-copy-paste the code given below into it. Name the file yyz.c. This file is going to turn into our dummy WinSock once we're through compiling it. We've added our trademark abc()'s everywhere. This function will keep writing the name of the functions called, to a file on disk.
Code for dummy wsock32.dll
yyz.c
#include <windows.h> #include <stdio.h> void abc(char * p) { FILE *fp=fopen("c:\\z.txt","a+"); fprintf(fp,"%s\n",p); fclose(fp); } char aa[1000]; BOOL WINAPI DllMain( HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved) { abc("DllMain"); return 1; } SOCKET PASCAL FAR accept (SOCKET s, struct sockaddr FAR *addr,int FAR *addrlen) { abc("accept "); return 0; } int PASCAL FAR closesocket (SOCKET s) { abc("closesocket "); return 0; } int PASCAL FAR connect (SOCKET s, const struct sockaddr FAR *name, int namelen) { abc("connect "); return 0; } int PASCAL FAR ioctlsocket (SOCKET s, long cmd, u_long FAR *argp) { abc("ioctlsocket "); return 0; } int PASCAL FAR getpeername (SOCKET s, struct sockaddr FAR *name, int FAR * namelen) { abc("getpeername "); return 0; } int PASCAL FAR getsockname (SOCKET s, struct sockaddr FAR *name, int FAR * namelen) { abc("getsockname "); return 0; } int PASCAL FAR getsockopt (SOCKET s, int level, int optname, char FAR * optval, int FAR *optlen) { abc("getsockopt "); return 0; } u_long PASCAL FAR htonl (u_long hostlong) { abc("htonl "); return 0; } char FAR * PASCAL FAR inet_ntoa (struct in_addr in) { abc("inet_ntoa "); return 0; } int PASCAL FAR listen (SOCKET s, int backlog) { abc("listen "); return 0; } u_long PASCAL FAR ntohl (u_long netlong) { abc("ntohl "); return 0; } u_short PASCAL FAR ntohs (u_short netshort) { abc("ntohs "); return 0; } int PASCAL FAR recvfrom (SOCKET s, char FAR * buf, int len, int flags, struct sockaddr FAR *from, int FAR * fromlen) { abc("recvfrom "); return 0; } int PASCAL FAR select (int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout) { abc("select "); return 0; } int PASCAL FAR sendto (SOCKET s, const char FAR * buf, int len, int flags, const struct sockaddr FAR *to, int tolen) { abc("sendto "); return 0; } int PASCAL FAR shutdown (SOCKET s, int how) { abc("shutdown "); return 0; } struct hostent FAR * PASCAL FAR gethostbyaddr(const char FAR * addr, int len, int type) { abc("gethostbyaddr"); return 0; } struct hostent FAR * PASCAL FAR gethostbyname(const char FAR * name) { abc("gethostbyname"); return 0; } int PASCAL FAR gethostname (char FAR * name, int namelen) { abc("gethostname "); return 0; } struct servent FAR * PASCAL FAR getservbyport(int port, const char FAR * proto) { abc("getservbyport"); return 0; } struct servent FAR * PASCAL FAR getservbyname(const char FAR * name, const char FAR * proto) { abc("getservbyname"); return 0; } struct protoent FAR * PASCAL FAR getprotobynumber(int proto) { abc("getprotobynumber"); return 0; } struct protoent FAR * PASCAL FAR getprotobyname(const char FAR * name) { abc("getprotobyname"); return 0; } int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData) { abc("WSAStartup"); return 0; } int PASCAL FAR WSACleanup(void) { abc("WSACleanup"); return 0; } void PASCAL FAR WSASetLastError(int iError) { abc("WSASetLastError"); } int PASCAL FAR WSAGetLastError(void) { abc("WSAGetLastError"); return 0; } BOOL PASCAL FAR WSAIsBlocking(void) { abc("WSAIsBlocking"); return 0; } int PASCAL FAR WSAUnhookBlockingHook(void) { abc("WSAUnhookBlockingHook"); return 0; } FARPROC PASCAL FAR WSASetBlockingHook(FARPROC lpBlockFunc) { abc("WSASetBlockingHook"); return 0; } int PASCAL FAR WSACancelBlockingCall(void) { abc("WSACancelBlockingCall"); return 0; } HANDLE PASCAL FAR WSAAsyncGetServByName(HWND hWnd, u_int wMsg, const char FAR * name,const char FAR * proto, char FAR * buf, int buflen) { abc("WSAAsyncGetServByName"); return 0; } HANDLE PASCAL FAR WSAAsyncGetServByPort(HWND hWnd, u_int wMsg, int port, const char FAR * proto, char FAR * buf,int buflen) { abc("WSAAsyncGetServByPort"); return 0; } HANDLE PASCAL FAR WSAAsyncGetProtoByName(HWND hWnd, u_int wMsg, const char FAR * name, char FAR * buf,int buflen) { abc("WSAAsyncGetProtoByName"); return 0; } HANDLE PASCAL FAR WSAAsyncGetProtoByNumber(HWND hWnd, u_int wMsg, int number, char FAR * buf,int buflen) { abc("WSAAsyncGetProtoByNumber"); return 0; } HANDLE PASCAL FAR WSAAsyncGetHostByName(HWND hWnd, u_int wMsg, const char FAR * name, char FAR * buf,int buflen) { abc("WSAAsyncGetHostByName"); return 0; } HANDLE PASCAL FAR WSAAsyncGetHostByAddr(HWND hWnd, u_int wMsg, const char FAR * addr, int len, int type,char FAR * buf, int buflen) { abc("WSAAsyncGetHostByAddr"); return 0; } int PASCAL FAR WSACancelAsyncRequest(HANDLE hAsyncTaskHandle) { abc("WSACancelAsyncRequest"); return 0; } int PASCAL FAR WSAAsyncSelect(SOCKET s, HWND hWnd, u_int wMsg, long lEvent) { abc("WSAAsyncSelect"); return 0; } int PASCAL __WSAFDIsSet () { abc("__WSAFDIsSet "); return 0; } int PASCAL s_perror () { abc("s_perror "); return 0; } int PASCAL TransmitFile () { abc("TransmitFile "); return 0; } int PASCAL EnumProtocolsA () { abc("EnumProtocolsA "); return 0; } int PASCAL EnumProtocolsW () { abc("EnumProtocolsW "); return 0; } int PASCAL GetAddressByNameA () { abc("GetAddressByNameA "); return 0; } int PASCAL GetAddressByNameW () { abc("GetAddressByNameW "); return 0; } int PASCAL GetNameByTypeA () { abc("GetNameByTypeA "); return 0; } int PASCAL GetNameByTypeW () { abc("GetNameByTypeW "); return 0; } int PASCAL GetServiceA () { abc("GetServiceA "); return 0; } int PASCAL GetServiceW () { abc("GetServiceW "); return 0; } int PASCAL GetTypeByNameA () { abc("GetTypeByNameA "); return 0; } int PASCAL GetTypeByNameW () { abc("GetTypeByNameW "); return 0; } int PASCAL SetServiceA () { abc("SetServiceA "); return 0; } int PASCAL SetServiceW () { abc("SetServiceW "); return 0; } int PASCAL dn_expand () { abc("dn_expand "); return 0; } int PASCAL inet_network () { abc("inet_network "); return 0; } int PASCAL rexec () { abc("rexec "); return 0; } int PASCAL rresvport () { abc("rresvport "); return 0; } int PASCAL WSARecvEx () { abc("WSARecvEx "); return 0; } unsigned long PASCAL FAR inet_addr (const char FAR * cp) { abc("inet_addr "); return 0; } u_short PASCAL FAR htons (u_short hostshort) { abc("htons "); return 0; } SOCKET PASCAL FAR socket (int af, int type, int protocol) { abc("socket "); return 0; } int PASCAL FAR bind (SOCKET s, const struct sockaddr FAR *addr, int namelen) { abc("bind "); return 0; } int PASCAL FAR setsockopt (SOCKET s, int level, int optname, const char FAR * optval, int optlen) { abc("setsockopt "); return 0; } int PASCAL FAR recv (SOCKET s, char FAR * buf, int len, int flags) { abc("recv "); return 0; } int PASCAL FAR send (SOCKET s, const char FAR * buf, int len, int flags) { abc("send "); return 0; } int PASCAL WsControl(void *i1,void *i2,void *i3,void *i4,void *i5,void *i6) { abc("WsControl"); return 0; } int PASCAL NPLoadNameSpaces (void *i1,void *i2,void *i3) { abc("NPLoadNameSpaces "); return 0; }
All the functions currently return a 0 with one exception, DllMain()retuns a 1.Wait just a little longer and we'll start doing some fun stuff with this! When every thing is back to normal we'll add new code to yyz.c and build the project again. Copy the new .dll to windows\system. Run Netscape again.
Examine the file z.txt.
z.txt
DllMain WSAStartup WSACleanup DllMain
If you've read our tutorial on WinSock programming, or have some experience with it, you'll know that the first function which must be called by any WinSock compliant program is WSAStartup with two parameters. The first parameter is the version number of the socket and the second is the address of a structure which looks like WSAData.
WSAStartup() must succed otherwise all subsequent calls to other functions fail and Netscape will refuse to load.
Since Navigator has been written by conscientious programmes, every time there is a WSAStartup called, the last function to be called is WSACleanup. So we must fake that function too. We don't have to do anything in this function but simply return 0.
Replace the brain dead WSAStartup() we'd given you earlier with this one. Using this we will be able to discover the parameters Navigator passes to WSAStartup().
int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData) { sprintf(aa,"wVersionRequired ...%u....%x",wVersionRequired,wVersionRequired); abc(aa); abc("WSAStartup"); return 0; }
Build the .dll as before and then copy it to the windows\system directory, overwriting the earlier version. Delete the file z.txt from the root. These steps should be repeated after every modification, otherwise the change will not be reflected in our program output.
Now load on Netscape and brace for errors.
Check the z.txt output file.
z.txt
DllMain wVersionRequired ...257....101 WSAStartup WSACleanup DllMain
As you noticed, Netscape throws the same error as before. In fact, unless we correctly fill up the structure WSADATA, Netscape will refuse to work. Netscape needs the information in the structure WSAData to correctly initialize its own variables and structures and for subsequent WinSock (WSOCK32.DLL) function calls to succeed. So we have to do what the original WSAStartup does, that is, initialise all the members in WSAData and return a 0. Most functions we'll be writing return 0 for success and some other predefind number to indicate specific errors, so unless otherwise told assume that you must return a 0.
The members of the structure and the values assigned to them are shown below.
int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData) { sprintf(aa,"wVersionRequired ...%u....%x",wVersionRequired,wVersionRequired); abc(aa); lpWSAData->wVersion=257; lpWSAData->wHighVersion=257; strcpy(lpWSAData->szDescription,"Microsoft Windows Sockets Version 1.1."); strcpy(lpWSAData->szSystemStatus,"Running on Windows 95."); lpWSAData->iMaxSockets=256; lpWSAData->iMaxUdpDg=65467; lpWSAData->lpVendorInfo=0; abc("WSAStartup"); return 0; }
Run Netscape again.
It still hangs
Salvage what's left of your machine and read z.txt.
z.txt
DllMain WSAStartup wVersionRequired ...257....101 htons socket WSAAsyncSelect setsockopt ioctlsocket inet_addr connect send send send
All we've done so far is discover which functions are being called by Netscape and in which order.
Right after WSAStartup, Netscape calls the function htons. This is a function which converts Little Endian numbers to Big Endian numbers. What's all this about little ends and big ends you ask. Well maybe what we need here is an example.
If I store the number 300 in memory using an Intel based machine, that number is stored internally in memory as two bytes of data (1 byte max. = 256). So this number will be stored as 44 and 1 i.e. 44*1 + 1*256 = 300 and this storage scheme is called little endian. On Motorola based machines however, the number 300 is stored upside down as 1 and 44 i.e. 1*256 + 44*1=300. This is little endian. Since each processor is aware of it's own storage scheme there is absolutely no confusion while these numbers are used within the boundaries of your computer. However, when these very same numbers are transferred across the Internet, chaos reigns !
If I transfer the number 300 from a Motorola machine, for example a Mac, to an Intel based machine, the number is stored the opposite way. So 300 will not be stored as 1*256 + 44*1=300, instead it will be stored as 1*1 + 44*256 = 11265 !! A tad off the mark.
Since some standard had to be found the good people decided that the non-Intel standard should be followed and named that standard the Network Byte Order. So all numbers used in the TCP/IP headers must be converted to the Network Byte Order using htons.
Here htons is being used by Netscape to convert the port number 80 (HTTP) to its Big Endian avatar. Ports are explained in great detail in our WinSock tutorial and so I wouldn't repeat the explanation again.
The htons being called is our htons, so we can return a 0. Since Netscape trusts us completely, it doesn't object at all.
To check, the parameters Netscape passed to htons(),we replace the earlier function with the following one.
u_short PASCAL FAR htons (u_short hostshort) { abc("htons "); sprintf(aa,"htons....%u",hostshort); abc(aa); return 0; }
z.txt
DllMain WSAStartup wVersionRequired ...257....101 htons htons....80 socket WSAAsyncSelect setsockopt ioctlsocket inet_addr connect send send send
Right after htons, Netscape calls the function socket and passes it three parameters. A socket is a hypothetical entity created simply to make programming easier. You can think of it as a file handle like fopen, except that in fopen you are creating a file, with a socket you just define it's characteristics. This one function was created so that it could be used over several different types of networks like DecNet, ApleTalk etc. So the first parameter, AF_INET, specifies that this socket will be used over the Internet. The second parameter is SOCK_STREAM and tells the WinSock that the protocol being used is TCP/IP, which is a stream based protocol (more on that later). The last parameter is usually a 0, but we can say 6 which means TCP/IP. Our socket function returns a 0 which stands for the number of the socket created since several sockets can be created at the same time (Netscape's default is 4). From now on, all dealings with this socket will be made using its number. If we had returned a -1, Netscape would have understood that an error had occured.
In our next programme we check the values that are passed to the socket function.The function socket() has been explained in great detail in our WinSOck tutorial so there's no point in me wearing my nails down here.
SOCKET PASCAL FAR socket (int af, int type, int protocol) { abc("socket "); sprintf(aa,"af=%d..type=%d..protocol=%d",af,type,protocol); abc(aa); return 0; }
The value are dumped to z.txt.
z.txt
DllMain WSAStartup wVersionRequired ...257....101 htons htons...80 socket af=2..type=1..protocol=6 WSAAsyncSelect setsockopt ioctlsocket inet_addr connect send send send send send
2 is the actual value of the #define AF_INET, the second number is SOCK_STREAM and the last number is the value of TCP/IP
After Netscape finishes of with the socket function, it calls a function named WSAAsyncSelect. Now the problem with the Internet is that you never know how long something will take to download. It may take an hour or it may appear instantaneously (fat chance !). But we can't have poor ol' Netscape hanging around counting it's toes waiting for the file to arrive, it should be free to cater to the users demands. One way of accomplishing this is to create a thread and let that wait at the COM port for the information. Another way is to use WSAAsyncSelect. The reason Netscape chooses to use WSAAsyncSelect instead of threads is because threads are not possible under Windows 3.1 and Netscape's browser must be portable across operating systems.
WSAAsyncSelect allows you to free up Netscape to perform other tasks by making the various functions non-blocking, i.e. they'll only call upon Netscape when they have something to give it, like parts of the page being downloaded. We'll explain this mechanism is greater detail later when we handle the code, now is not the time to tackle it.
Add this to find the parameters of WSAAsyncSelect().
int PASCAL FAR WSAAsyncSelect(SOCKET s, HWND hWnd, u_int wMsg,long lEvent) { abc("WSAAsyncSelect"); sprintf(aa,"WSAAsyncSelect - wMsg = %u...lEvent=%ld",wMsg,lEvent); abc(aa); return 0; }
z.txt
DllMain WSAStartup wVersionRequired ...257....101 htons htons....80 socket af=2..type=1..protocol=6 WSAAsyncSelect WSAAsyncSelect - wMsg = 53615...lEvent=63 setsockopt ioctlsocket inet_addr connect send send send send
The next two functions called by Netscape are setsockopt and iocltsocket. These functions change the nature of the TCP/IP headers and make them perform certain actions. Since Netscape is a commercial product, it has to make sure that every thing is in place. In real life, programmers have to do a lot more work than we've done to make our WinSock tutorial. setsockopt() is not necessary but it helps fine tune the socket connection. The optname could be SO_LINGER (which changes the sockets closing behaviour), SO_BROADCAST and so on.This stuff is too complicated to get into right now, but once you've read through our Internet programming tutorials these functions will seem easy by comparison.
int PASCAL FAR setsockopt (SOCKET s, int level, int optname,const char FAR * optval, int optlen) { abc("setsockopt "); sprintf(aa,"level=%d..optname=%d..optval=%s.....optlen=%d",level,optname,optval,optlen); abc(aa); return 0; }
z.txt
DllMain WSAStartup wVersionRequired ...257....101 htons htons....80 socket af=2..type=1..protocol=6 WSAAsyncSelect WSAAsyncSelect - wMsg = 53615...lEvent=63 setsockopt level=65535..optname=128..optval=.....optlen=8 ioctlsocket inet_addr connect send send send
int PASCAL FAR ioctlsocket (SOCKET s, long cmd, u_long FAR *argp) { abc("ioctlsocket "); sprintf(aa,"cmd = %lu...*argp=%ld",cmd,*argp); abc(aa); return 0; }
z.txt
DllMain WSAStartup wVersionRequired ...257....101 htons htons....80 socket af=2..type=1..protocol=6 WSAAsyncSelect WSAAsyncSelect - wMsg = 53615...lEvent=63 setsockopt ioctlsocket cmd = 2147772030...*argp=1 inet_addr connect send send send
After these two, Netscape calls the function inet_addr. Inet_addr converts IP address from their dotted decimal format (e.g. 202.54.1.18) to their actual value as a four byte long number. The dotted decimal notation is used simply to make remembering IP address a little easier. In this format, each number shown represents a byte in the long which makes up a IP address. So 202.54.1.18 is actually IP address 202*(2^24) + 54*(2^16) + 1*(2^8) + 18*(2^0) = ********. As usual, instead of returning a real value, we simply tell Netscape that the answer is 0.
Add the code given below to the .dll file. Now we'll be able to see the IP address we typed in as the default page in Netscape. If we'd given a name like www.neca.com, then a couple more functions would have been called to resolve the name to it's IP address.
unsigned long PASCAL FAR inet_addr (const char FAR * cp) { abc("inet_addr "); sprintf(aa,"cp..%s",cp); abc(aa); return 0; }
z.txt
DllMain WSAStartup wVersionRequired ...257....101 htons htons....80 socket af=2..type=1..protocol=6 WSAAsyncSelect WSAAsyncSelect - wMsg = 53615...lEvent=63 setsockopt ioctlsocket inet_addr cp..206.103.13.130 connect send send send send
206.103.13.130 is the IP address which we have given to Netscape as the default page.
Inet_addr is actually called as Netscape fills up three members of a structure called sockaddr_in. This structure holds important data about the connection.
After initialising the members of the structure sockaddr_in, Netscape calls the function connect from within our .dll and passes it three parameters. The first parameter is the handle to the socket that's just been created which in our case is 0 because we returned 0 while in socket. Next is a pointer to the structure sockaddr_in and then its length. If you've read our WinSock tutorial, you'll know what these members mean. We know that connect() is used to connect to a remote site; it's time to discover the parameters passed to it. Add these lines to your project .
int PASCAL FAR connect (SOCKET s, const struct sockaddr FAR *name, int namelen) { abc("connect "); sprintf(aa,"socket..%ld..namelen...%d",s,namelen); abc(aa); sprintf(aa, "sa_family = %d.. sa_data = %s..%d. .namelen=%d..", name->sa_family, name->sa_data , name->sa_data[0], namelen ); abc(aa); return 0; }
The output file reveals the parameters.
z.txt
DllMain WSAStartup wVersionRequired ...257....101 htons htons....80 socket af=2..type=1..protocol=6 WSAAsyncSelect WSAAsyncSelect - wMsg = 53615...lEvent=63 setsockopt ioctlsocket inet_addr cp..206.103.13.130 connect socket..0..namelen...16 sa_family=2..sa_data = P..80..namelen=16.. send send send send
In connect, we return a 0 to Netscape, any other value and Netscape will display an error. If you want, you can place a messagebox here just before the return statement and when you run Netscape you'll see the messagebox and the line at the bottom will read something like 'Connecting to host. ….Waiting for reply'. Only when you click on OK in the MessageBox will the program proceed.
Since we've returned a 0 while in connect, Netscape will assume it's connected to a site on the Internet, even though it hasn't done any such thing. Since it assumes that all's well, it will now call the send function to send a request for a file. The HTTP command that has to be sent is GET / along with some aditional information like HTTP version numbers etc. The forward slash after GET means that the server should return the default file to Netscape. Send takes four parameters. The first is a handle to the socket, the next the name of the array which contains the HTTP command , the third is the length of the array and the last one is the flag which is always 0.
int PASCAL FAR send (SOCKET s, const char FAR * buf, int len, int flags) { abc("send "); sprintf(aa,"buf=%s..len=%d..flags=%d",buf,len,flags); abc(aa); return len; }
z.txt
DllMain WSAStartup wVersionRequired ...257....101 htons htons....80 socket af=2..type=1..protocol=6 WSAAsyncSelect WSAAsyncSelect - wMsg = 53615...lEvent=63 setsockopt ioctlsocket inet_addr cp..206.103.13.130 connect sa_family=2..sa_data = P..80..namelen=16.. send buf=GET / HTTP/1.0 If-Modified-Since: Thursday, 22-May-97 15:11:09 GMT; length=4031 Connection: Keep-Alive User-Agent: Mozilla/3.0 (Win95; I) Host: 206.103.13.130 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* ..len=232..flags=0 recv closesocket WSACleanup DllMain
To receive a page from the 'server', we have to first convince Netscape that it's send() succeeded. We've been returning a zero from send() and this implies that the send failed. So now, we have to return the length of the data send()'s shot across and only then will Netscape realise that it's function call has succeeded.
It was because the send() was 'failing' again and again that Netscape kept calling it repeatedly in the hope that it would succeed. Send is called and it works too. If it hadn't, you wouldn't have seen recv() being called.
As send cannot return a 0, we have returned 232 which is the size of the header that Netscape wants to send and this confirms that all the data has been sent.
Netscape thinks that it really has sent some bytes across the Internet asking for a file, so it calls the recv function to receive this file as it comes. Recv is called with four parameters. The handle to the socket, a pointer to an address in memory and the size of that memory along with a 0 for flags. So what we do in our recv is copy the bytes " Fooled You " with a \r and a \n to the memory location specified and return the number of bytes received i.e. 14. When Netscape sees the 14 it will think that it just got 14 bytes of the Internet and it will store these away for future reference. It will also call the recv function again. It does this because we didn't give a header for the 'file' we returned and so Netscape doesn't know the size of the file. We could have put in a little more effort and done that, but what the hell ! Since the file size is unknown, Netscape calls the recv function a second time to check if there is more to display. Since we don't have anything else to give it, we set up an if statement and the second time round return a 0 which means 'all over'. So Netscape will display Fooled You on the screen and tidy up by calling closesocket and WSCleanup.
int cnt; int PASCAL FAR recv (SOCKET s, char FAR * buf, int len, int flags) { cnt++; abc("recv "); sprintf(aa,"buf=%s..len=%d..flags=%d..cnt=%d",buf,len,flags,cnt); abc(aa); if (cnt == 1) { memcpy(buf," Fooled You \r\n",14); return 14; } else return 0; }
We need a counter here to check if the function recv() is called more than once. So we declare cnt in the beginning of the program.
z.txt
DllMain WSAStartup wVersionRequired ...257....101 htons htons....80 socket af=2..type=1..protocol=6 WSAAsyncSelect WSAAsyncSelect - wMsg = 53332...lEvent=63 setsockopt ioctlsocket inet_addr cp..206.103.13.130 connect sa_family=2..sa_data = P..80..namelen=16.. send buf=GET / HTTP/1.0 Connection: Keep-Alive User-Agent: Mozilla/3.0 (Win95; I) Host: 206.103.13.130 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* ..len=166..flags=0 recv buf=¨..len=260..flags=0..cnt=1 recv buf=..len=31744..flags=0..cnt=2 closesocket WSACleanup DllMain
Let say , we set our default page to the homepage of the site www.mafatlal.co.in. Netscape then sends some information like the date of the homepage it had last downloaded the same site, the name of the browser (Mozilla) etc. The reason Netscape keeps a track of the date stamp on a file is because if the dates of the file in the cache and the file of the server match, then Netscape quickly retrieves that page from cache instead of downloading it.
The line Connection: Keep-Alive tells the server not to disconnect after every file download, the default when using the HTTP protocol. Host is simply the IP address of the server we have connected to and Accepted informs the server about the mime type's we accept.
This is how servers keep statistics about the types of browsers on the market because as shown above, servers reveal their identity each time they each time they ask for a file from the server.
If you've been following our step by step approach properly, the final .cpp file should a lot like this...
yyz.c
#include <windows.h> #include <stdio.h> void abc(char * p) { FILE *fp=fopen("c:\\z.txt","a+"); fprintf(fp,"%s\n",p); fclose(fp); } int cnt=0; char aa[1000]; BOOL WINAPI DllMain( HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved) { abc("DllMain"); return 1; } SOCKET PASCAL FAR accept (SOCKET s, struct sockaddr FAR *addr,int FAR *addrlen) { abc("accept "); return 0; } int PASCAL FAR closesocket (SOCKET s) { abc("closesocket "); return 0; } int PASCAL FAR connect (SOCKET s, const struct sockaddr FAR *name, int namelen) { abc("connect "); sprintf(aa,"sa_family=%d..sa_data = %s..%d..namelen=%d..",name->sa_family,name->sa_data,name->sa_data[0],namelen); abc(aa); return 0; } int PASCAL FAR ioctlsocket (SOCKET s, long cmd, u_long FAR *argp) { abc("ioctlsocket "); return 0; } int PASCAL FAR getpeername (SOCKET s, struct sockaddr FAR *name, int FAR * namelen) { abc("getpeername "); return 0; } int PASCAL FAR getsockname (SOCKET s, struct sockaddr FAR *name, int FAR * namelen) { abc("getsockname "); return 0; } int PASCAL FAR getsockopt (SOCKET s, int level, int optname, char FAR * optval, int FAR *optlen) { abc("getsockopt "); return 0; } u_long PASCAL FAR htonl (u_long hostlong) { abc("htonl "); return 0; } char FAR * PASCAL FAR inet_ntoa (struct in_addr in) { abc("inet_ntoa "); return 0; } int PASCAL FAR listen (SOCKET s, int backlog) { abc("listen "); return 0; } u_long PASCAL FAR ntohl (u_long netlong) { abc("ntohl "); return 0; } u_short PASCAL FAR ntohs (u_short netshort) { abc("ntohs "); return 0; } int PASCAL FAR recvfrom (SOCKET s, char FAR * buf, int len, int flags, struct sockaddr FAR *from, int FAR * fromlen) { abc("recvfrom "); return 0; } int PASCAL FAR select (int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout) { abc("select "); return 0; } int PASCAL FAR sendto (SOCKET s, const char FAR * buf, int len, int flags, const struct sockaddr FAR *to, int tolen) { abc("sendto "); return 0; } int PASCAL FAR shutdown (SOCKET s, int how) { abc("shutdown "); return 0; } struct hostent FAR * PASCAL FAR gethostbyaddr(const char FAR * addr, int len, int type) { abc("gethostbyaddr"); return 0; } struct hostent FAR * PASCAL FAR gethostbyname(const char FAR * name) { abc("gethostbyname"); return 0; } int PASCAL FAR gethostname (char FAR * name, int namelen) { abc("gethostname "); return 0; } struct servent FAR * PASCAL FAR getservbyport(int port, const char FAR * proto) { abc("getservbyport"); return 0; } struct servent FAR * PASCAL FAR getservbyname(const char FAR * name, const char FAR * proto) { abc("getservbyname"); return 0; } struct protoent FAR * PASCAL FAR getprotobynumber(int proto) { abc("getprotobynumber"); return 0; } struct protoent FAR * PASCAL FAR getprotobyname(const char FAR * name) { abc("getprotobyname"); return 0; } int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData) { sprintf(aa,"wVersionRequired ...%u....%x",wVersionRequired,wVersionRequired); abc(aa); abc("WSAStartup"); lpWSAData->wVersion=257; lpWSAData->wHighVersion=257; strcpy(lpWSAData->szDescription,"Microsoft Windows Sockets Version 1.1."); strcpy(lpWSAData->szSystemStatus,"Running on Windows 95."); lpWSAData->iMaxSockets=256; lpWSAData->iMaxUdpDg=65467; lpWSAData->lpVendorInfo=0; return 0; return 0; } int PASCAL FAR WSACleanup(void) { abc("WSACleanup"); return 0; } void PASCAL FAR WSASetLastError(int iError) { abc("WSASetLastError"); } int PASCAL FAR WSAGetLastError(void) { abc("WSAGetLastError"); return 0; } BOOL PASCAL FAR WSAIsBlocking(void) { abc("WSAIsBlocking"); return 0; } int PASCAL FAR WSAUnhookBlockingHook(void) { abc("WSAUnhookBlockingHook"); return 0; } FARPROC PASCAL FAR WSASetBlockingHook(FARPROC lpBlockFunc) { abc("WSASetBlockingHook"); return 0; } int PASCAL FAR WSACancelBlockingCall(void) { abc("WSACancelBlockingCall"); return 0; } HANDLE PASCAL FAR WSAAsyncGetServByName(HWND hWnd, u_int wMsg, const char FAR * name,const char FAR * proto, char FAR * buf, int buflen) { abc("WSAAsyncGetServByName"); return 0; } HANDLE PASCAL FAR WSAAsyncGetServByPort(HWND hWnd, u_int wMsg, int port, const char FAR * proto, char FAR * buf,int buflen) { abc("WSAAsyncGetServByPort"); return 0; } HANDLE PASCAL FAR WSAAsyncGetProtoByName(HWND hWnd, u_int wMsg, const char FAR * name, char FAR * buf,int buflen) { abc("WSAAsyncGetProtoByName"); return 0; } HANDLE PASCAL FAR WSAAsyncGetProtoByNumber(HWND hWnd, u_int wMsg, int number, char FAR * buf,int buflen) { abc("WSAAsyncGetProtoByNumber"); return 0; } HANDLE PASCAL FAR WSAAsyncGetHostByName(HWND hWnd, u_int wMsg, const char FAR * name, char FAR * buf,int buflen) { abc("WSAAsyncGetHostByName"); return 0; } HANDLE PASCAL FAR WSAAsyncGetHostByAddr(HWND hWnd, u_int wMsg, const char FAR * addr, int len, int type,char FAR * buf, int buflen) { abc("WSAAsyncGetHostByAddr"); return 0; } int PASCAL FAR WSACancelAsyncRequest(HANDLE hAsyncTaskHandle) { abc("WSACancelAsyncRequest"); return 0; } int PASCAL FAR WSAAsyncSelect(SOCKET s, HWND hWnd, u_int wMsg, long lEvent) { abc("WSAAsyncSelect"); sprintf(aa,"WSAAsyncSelect - wMsg = %u...lEvent=%ld",wMsg,lEvent); abc(aa); return 0; } int PASCAL __WSAFDIsSet () { abc("__WSAFDIsSet "); return 0; } int PASCAL s_perror () { abc("s_perror "); return 0; } int PASCAL TransmitFile () { abc("TransmitFile "); return 0; } int PASCAL EnumProtocolsA () { abc("EnumProtocolsA "); return 0; } int PASCAL EnumProtocolsW () { abc("EnumProtocolsW "); return 0; } int PASCAL GetAddressByNameA () { abc("GetAddressByNameA "); return 0; } int PASCAL GetAddressByNameW () { abc("GetAddressByNameW "); return 0; } int PASCAL GetNameByTypeA () { abc("GetNameByTypeA "); return 0; } int PASCAL GetNameByTypeW () { abc("GetNameByTypeW "); return 0; } int PASCAL GetServiceA () { abc("GetServiceA "); return 0; } int PASCAL GetServiceW () { abc("GetServiceW "); return 0; } int PASCAL GetTypeByNameA () { abc("GetTypeByNameA "); return 0; } int PASCAL GetTypeByNameW () { abc("GetTypeByNameW "); return 0; } int PASCAL SetServiceA () { abc("SetServiceA "); return 0; } int PASCAL SetServiceW () { abc("SetServiceW "); return 0; } int PASCAL dn_expand () { abc("dn_expand "); return 0; } int PASCAL inet_network () { abc("inet_network "); return 0; } int PASCAL rexec () { abc("rexec "); return 0; } int PASCAL rresvport () { abc("rresvport "); return 0; } int PASCAL WSARecvEx () { abc("WSARecvEx "); return 0; } unsigned long PASCAL FAR inet_addr (const char FAR * cp) { abc("inet_addr "); sprintf(aa,"cp..%s",cp); abc(aa); return 0; } u_short PASCAL FAR htons (u_short hostshort) { abc("htons "); sprintf(aa,"htons....%u",hostshort); abc(aa); return 80; } SOCKET PASCAL FAR socket (int af, int type, int protocol) { abc("socket "); sprintf(aa,"af=%d..type=%d..protocol=%d",af,type,protocol); abc(aa); return 0; } int PASCAL FAR bind (SOCKET s, const struct sockaddr FAR *addr, int namelen) { abc("bind "); return 0; } int PASCAL FAR setsockopt (SOCKET s, int level, int optname, const char FAR * optval, int optlen) { abc("setsockopt "); return 0; } int PASCAL FAR recv (SOCKET s, char FAR * buf, int len, int flags) { cnt++; abc("recv "); sprintf(aa,"buf=%s..len=%d..flags=%d..cnt=%d",buf,len,flags,cnt); abc(aa); if (cnt == 1) { memcpy(buf," Fooled You \r\n",14); return 14; } else return 0; } int PASCAL FAR send (SOCKET s, const char FAR * buf, int len, int flags) { abc("send "); sprintf(aa,"buf=%s..len=%d..flags=%d",buf,len,flags); abc(aa); return len; } int PASCAL WsControl(void *i1,void *i2,void *i3,void *i4,void *i5,void *i6) { abc("WsControl"); return 0; } int PASCAL NPLoadNameSpaces (void *i1,void *i2,void *i3) { abc("NPLoadNameSpaces "); return 0; }
To Block or not to Block...
Seems pretty simple huh ?! Here comes the fun part. How exactly does connect manage to act non-blocking although it is blocking in nature ? Through some tricky programming of course ! Check out connect() function and you'll get what we mean. Connect() is a function which actually establishes a connection between two computers on the Internet. When connect() is called, it establishes a TCP/IP connection through the Three Way Handshake. The actual bytes along with some programs and explainations can be found in our TCP/IP tutorial. Since connect() is a function which interacts with the big bad world of cyberspace, we really don't know how long it'll take to finish it job (netlag and all that jazz). Some way has to be found to make it non-blocking. In our simple connect emulator, we just returned a zero , but in actuallity, the fucntion returns a -1 to Netscape.
Before calling connect, Netscape first calls WSAAsyncselect and passes it two parameters. The first is the socket number which in our case is 0 and the second is the number 63. Now this number is a collection of flags which tell WSAAsyncselect that all the function, recv, send, connect etc., are to be non-blocking.
The parameters that WSAAsyncSelect() is called with includes the handle of the window that is supposed to deal with the message (wMsg) when the even (lEvent) occurs. The value of wMsg should be greater than WM_USER, which is 1024. All values less than and equal to 1024 are reserved by Microsoft. When the events occur, for the socket (s), we want the message to be sent to a window whose handle is given as hWnd. In WinSock programming (take a peek at our WinSock Tutorial which explains this in greater detail), events can be things like FD_ACCEPT,FD_READ and so on. Multiple events can be trapped by ORing then together.
What we told you earlier about setsockopt() applies here as well. ioctlsocket() is a function that allows us to set a socket as blocking or nonblocking. It also lets us check the amount of data waiting in the buffer.
The socket can be blocking or nonblocking. The commands that get affected due to this behaviour of socket are accept(),connect(),send(),recv(), and recvfrom().To understand what is blocking we have to take an example. In cricket, when two bastmans are at the crease, a new bastman cannot come in to bat. Only thing he can do is sit in pavilion and pray to god. Only when one of the bastman gets out,he can go and bat. This waiting is blocking. The flags - 63 - tell us that Netscape wants nonblocking functions.
When Netscape calls the connect function , instead of returning a 0, we return SOCKET_ERROR or -1. This means that an error has occurred during the connect, even though no such thing has happened. On seeing this message, Netscape hurries over to wsock32.dll and calls WSAGetLastError to try and find out what exactly happened. In WSAGetLastError we're supposed to return WSAEWOULDBLOCK. This is our way of telling Netscape that no error has occurred during the connect, we've just made the connect blocking so you can now leave connect alone and do something else. Netscape understands and starts calling the function select approximately every 10 seconds. Select is a function which tells Netscape how many sockets are open and what is the status of those sockets. After around 3 selects, done to simulate net lag, we use SendMessage to send a message to Netscape window with the information FD_CONNECT (Actually this long is 2 ints, one is for the error, in this case there is no error and the second is why are you calling Netscape.) which tells Netscape that it's all right now to call connect. So Netscape rushes over to connect and calls it once more. This time we return a 0 for success telling Netscape that we're connected.
Pretty much the same thing happens for send and recv too but things are much worse for recv. Recv never knows how long it'll take for some piece of information to reach us. It may be that the server at the other end is occupied or that the lines are really busy, so even recv has to be made non-blocking. Last time around we simply copied ' Fooled You ' at a memory address and told Netscape about it. This time we'll do things the right way.int cnt1; int PASCAL FAR connect (SOCKET s, const struct sockaddr FAR *name, int namelen) { cnt1++; abc("connect "); sprintf(aa,"sa_family=%d..sa_data = %s..%d..namelen=%d..",name->sa_family,name->sa_data,name->sa_data[0],namelen); abc(aa); if (cnt1==1) return SOCKET_ERROR; else return 0; } int PASCAL FAR WSAGetLastError(void) { abc("WSAGetLastError"); return WSAEWOULDBLOCK; } int sctr; int PASCAL FAR select (int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout) { sctr++; abc("select "); if (sctr == 3) { SendMessage(ohWnd,owMsg,osock,FD_CONNECT); abc("After SendMessage"); } return 0; }
Netscape first calls recv with a very small number i.e. the 260 bytes which comprise the header. What we're supposed to do is return -1 the first time. As usual Netscape will then call WSAGetLastError which will return WSAEWOULDBLOCK. So Netscape will keep calling select till at last it receives a FD_READ. When it does, it will dash over to recv and give it the address of an array 32k large. Since we've already returned our bogus data, we return a 0 here telling Netscape that it's all over at our end.
A series of functions explaining this stuff are listed below. Browse through them at leisure.
int PASCAL FAR recv (SOCKET s, char FAR * buf, int len, int flags) { cnt++; abc("recv "); sprintf(aa,"buf=%s..len=%d..flags=%d..cnt=%d",buf,len,flags,cnt); abc(aa); if (cnt == 1) { abc("recv ..cnt =1"); return SOCKET_ERROR; } else if(cnt == 2) { abc("recv ..cnt =2"); memcpy(buf,"Hello\r\n",7); return 7; } abc("recv ..cnt =3"); return 0; } int PASCAL FAR WSAGetLastError(void) { abc("WSAGetLastError"); return WSAEWOULDBLOCK; } int sctr; int PASCAL FAR select (int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout) { sctr++; abc("select "); if (sctr == 3) SendMessage(ohWnd,owMsg,osock,FD_READ); return 0; }
As you can see, calling code form the WinSock is much simpler than writing it yourself. There is absolutely nothing wrong with Netscape doing this also, but it doesn't give them the right to call themselves an Internet company.
The jokes on us, pal.
You've now seen how easy it really is to fool Netscape into believing that it received the text 'Fooled You' over the Internet. This is because Netscape makes socket calls instead of using the com port or the Network card . Since it's only window to the world is the WinSock, we can take advantage of this and con Netscape into displaying anything we want. It's absolutely the same with Java or any other programming language or application that uses socket calls.
We're not saying that using sockets is a bad idea. On the contrary using sockets makes Internet programming much simpler. What we are trying to point out is that there is a large difference between programming with sockets and programming with TCP/IP directly. It's only when you've done the latter that you can truly say that you understand what the Internet is all about.
Netscape has no right to call itself an "Internet programming" company. The only ones who can are the ones who write the TCP/IP stacks and WinSocks. Netscape is a great company with a remarkable product and it's done a lot to popularise the Internet, but it's not an Internet company.
Uptil now, you really couldn't say anything as Netscape's marketing men strutted around town, sniggering and whispering to each other "We sure got them fooled !". Now we've given you the chance to use all these .dlls and scream at the Navigator window, "Fooled You !!". The above tutorial is a joint effort of
Mr. Vijay Mukhi
Ms. Sonal Kotecha
Mr. Arsalan Zaidi
Mr. R. D. Parab
Vijay Mukhi's Computer
Institute
VMCI, B-13, Everest Building, Tardeo, Mumbai 400 034, India
Tel : 91-22-496 4335 /6/7/8/9
Fax : 91-22-307 28 59
e-mail : vmukhi@giasbm01.vsnl.net.in
http://www.vijaymukhi.com