Your Linux
host is using inetd, which has been configured to listen on port 23 for telnet requests.
It accepts the connection request from step 1.

The /etc/inetd.conf
configuration file directs your inetd server to fork(2) a new process. The
parent process goes back to listening for more connects.

The child
process from step 3 now calls exec(2) to execute the /usr/sbin/tcpd TCP wrapper
program.

The tcpd program
determines whether the client should be given access or not. This is determined
by the combination of the socket addresses involved and the configuration files /etc/hosts.deny
and /etc/hosts.allow.

If access is
to be denied, tcpd simply terminates (this causes file units 0, 1, and 2 to be
closed, which are the socket file descriptors).

If access is
to be granted, the executable that is to be started is determined by tcpd's
argv [0] value. In this example, the name is in.telnetd. This specifies the
executable pathname /usr/sbin/in.telnetd, which is passed to the exec(2) function
to load and execute.

The server now
runs in place of tcpd with the same process ID that tcpd formerly had. The server
now performs input and output on the sockets (file units 0, 1, and 2).

Step 7 is
important— it is where the server process is started by the exec(2) function
call from within tcpd. This maintains the important parent/child relationship
between inetd and the (child)

server process.
When the wait flag word is used, the inetd daemon can start the next server
only when it detects that the current child process has ended. This works
correctly only when the server process is a
direct child process of the parent inetd. Numbers might help make this easier
to digest:

The inetd daemon
has process ID 124 for this example.

The inetd daemon
calls fork(2) to start a child process. This child process ID is now 1243 for
this example.

Note that tcpd
is now running as PID 1243 (recall that exec(2) uses the same process resources
to start a new program, while discarding the original program that called exec(2)).

The tcpd eventually
calls exec(2) again, when access is to be granted. This starts the new server,
which is /usr/sbin/in.telnetd in this example.

Note that the
server /usr/sbin/in.telnetd still is PID 1243 because exec(2) does not create a
new process (see notes in step 4).

Server in.telnetd
eventually exits (PID 1243 terminates).

Parent process
inetd (PID 124) receives a SIGCHLD signal to indicate that its child process ID
1243 has terminated. This will cause inetd to call upon wait (2) to determine
which child process has
terminated.

From this list of
steps, you can see how cleverly inserted the tcpd wrapper program is. This program
never actually performs I/O on the sockets— this would disturb the protocol
being used (telnet or otherwise).

Determining Access

You might still have two questions at this point:

How does the TCP wrapper program determine what service it is securing (telnet, ftp, andso on)?

How does it determine who the client is?

Now, let's briefly state each solution in the following sections.

Determining the
Service

The tcpd program
can determine the service it is protecting by calling upon the getsockname (2) function.
Remember that function? It not only returns the socket address that the client
was connecting to,
but it indicates the port number of the service. In the previous examples, the
port number was 23 (the telnet service).

Determining the
Client Identity

Because the tcpd program
was not the one that executed the accept(2) function call (this was done by inetd),
it must determine who the client is. As you've probably guessed, this is done
with the getpeername(2) function. You will recall that this function retrieves
the address and port number of the remote client, in the same manner as getsockname(2).

Determining the
Datagram Client Identity

Determining the
identity of a datagram client is a bit trickier. The astute reader might have wondered
about this in the previous section, because datagrams do not use the accept(2) function
call. It is also not possible to use getpeername(2) on datagram sockets because
each datagram can potentially come from different clients. The client's address
is returned by the recvfrom(2) function call. How, then, can tcpd determine the
client's identity without actually reading the
server's datagram?

It turns out that
tcpd is able to cheat. The client's address and port number can be determined
by calling recvfrom(2) using the flag option MSG_PEEK. Example code is shown as
follows:

Example

int z;

struct
sockaddr_in adr_clnt;/* AF_INET */

int len_inet; /* length */

int s; /* Socket */

char dgram[512];
/* Recv buffer */

len_inet = sizeof
adr_clnt;

z = recvfrom(s, /* Socket */

dgram, /* Receiving buffer */

sizeof dgram, /* Max recv buf size */

MSG_PEEK, /* Flags: Peek at data */

(struct sockaddr *)&adr_clnt,/*
Addr */

&len_inet); /* Addr len, in & out */

Notice the flag
option MSG_PEEK. This option directs the kernel to carry out the recvfrom(2) call
as normal except that the datagram is not to be removed from the queue as
"read." This allows the tcpd program to "peek" at the
datagram that the server will subsequently read, if access is granted.

Notice that the
data itself is not important here. What this MSG_PEEK operation accomplishes is
that it returns the client's IP address (in the example, this is placed into adr_clnt).
The wrapper program can
determine from the variable adr_clnt whether this datagram should be processed
by the server or not.