finger tutorial
Finger is basic protocol user to get information about a user from a server. Being text based makes this a very easy thing to write and I thought a nice introduction to using sockets. This tutorial is aimed at windows, but with small modifications should work on unix/linux.
Finger works by opening a TCP connection to a finger server on port 79, and sending a username followed by a carriage return and a line feed. The result received is the finger information.
To use sockets on windows we have to use winsock, to use this we have to include winsock2.h in our application and link with ws2_32.lib. I have written this tutorial using Microsoft Visual C++ 6.0 and a zip file of the project and source is available for download on the downloads page. Alternatively you can view the source online.
Below is the basic setup needed and the function prototypes for the 2 functions that do the actual work.
#include <stdlib.h> // for exit()
#include <string.h> // for strchr(), strcpy()
#include <winsock2.h>
// fingers the user at the host
int finger(const char *user, const char *host);
// splits the string userhost into user and host
int parsehost(char *userhost, char **user, char **host);
int main(int argc, char* argv[])
{
Being a console application we need to check that we have the correct number of command line arguments, which should be 2, the first is the name of the program, the second is the one passed in.
if (argc != 2)
{
printf("\nUsage: finger user@host\n");
return 0;
}
To use winsock it needs to be started to let it load the appropriate dlls and allocate any resources it needs, this is done through a call to WSAStartup.
WSADATA wsData;
if (WSAStartup(WINSOCK_VERSION, &wsData) != 0)
{
printf("\nError starting winsock, exiting...\n");
exit(1);
}
We need to separate the user from the hostname in the string passed into the program, we copy the string passed on the command line into userhost then call parsehost() on it, this returns 2 pointers, one to the username and one to the hostname.
// separate the user from the hostname.
char userhost[128] = { 0 };
char *user = NULL;
char *host = NULL;
strcpy(userhost, argv[1]);
if (parsehost(userhost, &user, &host) == 0)
{
finger(user, host); // finger it
}
Now we're finished we need to shut down winsock to allow it to release any resources it's using and unload it's dlls, this is done with a WSACleanup() call.
if (WSACleanup() == SOCKET_ERROR)
{
printf("\nError shutting down winsock, exiting...\n");
exit(1);
}
return 0;
}
This is the parsehost() function, it takes the string userhost and effectively splits it into two strings by inserting a NULL (\0) terminator in the middle, keeping a pointer to the "second" string.
{
*user = userhost;
*host = strchr(userhost, '@'); // search for separator of name/host
if (*host == NULL)
{
printf("\nAdddress to finger must be in the form of user@host\n");
return 1;
}
**host = '\0'; // replace @ with end of string null
*host++; // advance pointer past new null to start of hostname
printf("User: %s\n", *user);
printf("Host: %s\n\n", *host);
return 0;
}
Finally we get to the meat of the application, the finger function, this
is the function responsible for creating the connection, sending the username
and receiving the result.
The first thing that needs to be done is to try and resolve the hostname
from the human readable form into an IP address that the computer can
use. This is done using gethostbyname(), it returns a pointer to a hostent
structure containing information about the host, or NULL on failure.
{
// resolve hostname to address
hostent *phe;
phe = gethostbyname(host);
if (phe == NULL)
{
printf("Unable to resolve host\n\n");
return 1;
}
printf("Hostname: %s\n", phe->h_name);
printf("IP address: %s\n", inet_ntoa(*(in_addr*)phe->h_addrlist[0]));
Now we create the socket that we will use for the connection. Sockets are created using the socket() function, the arguments it takes are the socket family we are using, in this case AF_INET for a network socket, and a socket type SOCK_STREAM for a stream socket, network family sockets user TCP for stream sockets.
SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
printf("Failed to create TCP/IP socket\n\n");
return 1;
}
We're getting there, next thing we need to do is to fill in a sockaddr_in structure with the address information about the host we want to connect to.
One thing I haven't mentioned before now but is shown in the code below is the possible differences in byte ordering of variables over the network and on a host machine. These may be the same or they may be different depending on the platform you are using, to be on the safe side, always convert them.
Conversion is done using the family of functions, htons(), htonl(), ntohs(), ntohl(), being host to network short, host to network long, network to host short and network to host long.
In the structure below the short value sin_port needs to be in network byte order, so we use the htons() function to convert it. The address returned from gethostbyname() is already in network byte order, so this doesn't need to be converted.
memset(&sin, 0, sizeof(sockaddr_in));
sin.sin_family = AF_INET; // socket family
sin.sin_port = htons(79); // 79 == finger port
sin.sin_addr = *(in_addr *)phe->h_addr_list[0]; //first address returned from gethostbyname()
To connect to the host we use the connect() function, this takes the socket we want to connect with, the address structure we just filled in, and the size of that structure. Due to the fact that connect can be used with more than just network sockets, it takes a generic sockaddr structure which it acts on differently depending on the address family, sockaddr_in is the address structure for AF_INET sockets, so to be passed in it needs to be cast to sockaddr.
if (status == SOCKET_ERROR)
{
closesocket(s);
printf("Error connecting socket\n\n");
return 1;
}
We now have our socket connected to the remote server, sitting there waiting for us to do something with it. Lets do something then, we need to send the username we have got followed by a carriage return and a line feed (\r\n). We send data over a socket using the send() function. It takes the socket we want send data on, a pointer to the buffer of data to send, the length of the buffer in bytes, and flags, we don't need any flags so just set this as zero.
printf("Sending username: %s\n\n", user);
status = send(s, user, strlen(user), 0);
if (status == SOCKET_ERROR)
{
closesocket(s);
printf("Failure to send username\n\n");
return 1;
}
status = send(s, "\r\n", 2, 0);
if (status == SOCKET_ERROR)
{
closesocket(s);
printf("Failure to send username\n\n");
return 1;
}
Now if all has gone well our username should have flown off to the server, the server retrieved the appropriate information and is now sending the information we asked for back to use. To receive data we use the recv() function. Just like send this takes the socket we want to receive data on, a pointer to a buffer to put the data in, the length of the buffer, and any flags. recv() returns the number of bytes it has received if it received data, or 0 if the other end closed it's socket, indicating we received all the data and are finished.
int totalbytes = 0;
char recvbuf[513] = { 0 };
while ((recv_result = recv(s, recvbuf, 512, 0)) > 0)
{
totalbytes += recv_result;
recvbuf[recv_result] = 0;
printf("%s", recvbuf); // print out data
}
// other end of connection closed socket
if (recv_result == 0)
{
printf("\nAll done, received %d bytes\n\n", totalbytes);
}
else
{
closesocket(s);
printf("Failure receiving data\n\n");
return 1;
}
closesocket(s);
return 0;
}
At last we're finished, I hope this helped at least someone on a quest to learn sockets. If you have any questions or comments please send me an email and I'll at least try to answer them.