Linux Socket Programming by Example - Warren Gay

< BACKCONTINUE >
159231153036212041242100244042145096184016146223183074028121223008110132153212142180204006227

Installing Wrapper and Server Programs

This section will present a simple datagram server and a corresponding TCP wrapper program. The wrapper program implements a very simple security policy.

Examining Server and Wrapper Logging Code

The server and wrapper program share a few functions for logging purposes. These logging functions are presented in Listing 16.1.

Example 16.1. log.c—Logging Functions Used by Server and Wrapper
 <$nopage>
001 1:   /* log.c
002 2:    *
003 3:    * Logging Functions:
004 4:    */
005 5:   #include <stdio.h>
006 6:   #include <unistd.h>
007 7:   #include <stdlib.h>
008 8:   #include <string.h>
009 9:   #include <stdarg.h>
010 10:  #include <errno.h>
011 11:
012 12:  static FILE *logf = NULL;      /* Log File */
013 13:
014 14:  /*
015 15:   * Open log file for append:
016 16:   *
017 17:   * RETURNS:
018 18:   *  0   Success
019 19:   *  -1  Failed.
020 20:   */
021 21:  int
022 22:  log_open(const char *pathname) {
023 23:
024 24:      logf = fopen(pathname,"a");
025 25:      return logf ? 0 : -1;
026 26:  }
027 27:
028 28:  /*
029 29:   * Log information to a file:
030 30:   */
031 31:  void
032 32:  log(const char *format,…) {
033 33:      va_list ap;
034 34:
035 35:      if ( !logf )
036 36:          return;        /* No log file open */
037 37:
038 38:      fprintf(logf,"[PID %ld] ",(long)getpid());
039 39:
040 40:      va_start(ap,format);
041 41:      vfprintf(logf,format,ap);
042 42:      va_end(ap);
043 43:      fflush(logf);
044 44:  }
045 45:
046 46:  /*
047 47:   * Close the log file:
048 48:   */
049 49:  void
050 50:  log_close(void) {
051 51:
052 52:      if ( logf )
053 53:          fclose(logf); <$nopage>
054 54:      logf = NULL;
055 55:  }
056 56:
057 57:  /*
058 58:   * This function reports the error to
059 59:   * the log file and calls exit(1).
060 60:   */
061 61:  void
062 62:  bail(const char *on_what) {
063 63:
064 64:      if ( logf ) {          /* Is log open? */
065 65:          if ( errno )             /* Error? */
066 66:              log("%s: ",strerror(errno));
067 67:          log("%s\n",on_what);    /* Log msg */
068 68:          log_close();
069 69:      }
070 70:      exit(1);
071 71:  }
072  <$nopage>

The major components present in Listing 16.1 are the following:

  • A log_open() function to open the log file (lines 21 to 26).

  • A printf(3) styled logging function log() (lines 31 to 44). This function provides the convenience of printf(3), while ensuring that the process ID is always part of the log message (see line 38).

  • A log_close() function to close the log file (lines 49 to 55).

  • A modified version of the bail() function that writes its output to the log file instead of stderr (lines 61 to 71).

The include file that is used by the referencing programs is shown in Listing 16.2.

Example 16.2. log.h—The log.h Header File
 <$nopage>
001 1:   /* log.h
002 2:    *
003 3:    * log.c externs:
004 4:    */
005 5:   extern int log_open(const char *pathname);
006 6:   extern void log(const char *format,…);
007 7:   extern void log_close(void);
008 8:   extern void bail(const char *on_what);
009  <$nopage>

Listing 16.2 simply defines the function prototypes for the logging functions shown previously in Listing 16.1.

Examining the Datagram Server Code

This section illustrates a datagram server that processes the first datagram and then loops back for more. If no further datagrams arrive within eight seconds, the server times out and exits. The inetd daemon will not start another server until it is notified of this server's termination (the /etc/inetd.conf entry must use the wait flag word).

Listing 16.3 shows the code used for the datagram server program.

Example 16.3. dgramisrvr.c—The inetd Datagram Server
 <$nopage>
001 1:   /* dgramisrvr.c:
002 2:    *
003 3:    * Example inetd datagram server:
004 4:    */
005 5:   #include <stdio.h>
006 6:   #include <unistd.h>
007 7:   #include <stdlib.h>
008 8:   #include <stdarg.h>
009 9:   #include <errno.h>
010 10:  #include <string.h>
011 11:  #include <sys/types.h>
012 12:  #include <sys/time.h>
013 13:  #include <sys/socket.h>
014 14:  #include <netinet/in.h>
015 15:  #include <arpa/inet.h>
016 16:
017 17:  #include "log.h"
018 18:
019 19:  #define LOGPATH "/tmp/dgramisrvr.log"
020 20:
021 21:  int
022 22:  main(int argc,char **argv) {
023 23:      int z;
024 24:      int s;                       /* Socket */
025 25:      int alen;         /* Length of address */
026 26:      struct sockaddr_in adr_clnt; /* Client */
027 27:      char dgram[512];     /* Receive buffer */
028 28:      char dtfmt[512];   /* Date/Time Result */
029 29:      time_t td;    /* Current Time and Date */
030 30:      struct tm dtv;     /* Date time values */
031 31:      fd_set rx_set;    /* Incoming req. set */
032 32:      struct timeval tmout; /* Timeout value */
033 33:
034 34:      /*
035 35:       * Open a log file for append:
036 36:       */
037 37:      if ( log_open(LOGPATH) == -1 )
038 38:          exit(1);           /* No log file! */
039 39:
040 40:      log("dgramisrvr started.\n");
041 41:
042 42:      /*
043 43:       * Other initialization:
044 44:       */
045 45:      s = 0;   /* Our socket is on std input */
046 46:      FD_ZERO(&rx_set);        /* Initialize */ <$nopage>
047 47:      FD_SET(s,&rx_set);      /* Notice fd=0 */
048 48:
049 49:      /*
050 50:       * Now wait for incoming datagrams:
051 51:       */
052 52:      for (;;) {
053 53:          /*
054 54:           * Block until a datagram arrives:
055 55:           */
056 56:          alen = sizeof adr_clnt;
057 57:
058 58:          z = recvfrom(s,          /* Socket */
059 59:              dgram,     /* Receiving buffer */
060 60:              sizeof dgram, /* Max recv size */
061 61:              0,        /* Flags: no options */
062 62:              (struct sockaddr *)&adr_clnt,
063 63:              &alen);  /* Addr len, in & out */
064 64:
065 65:          if ( z < 0 )
066 66:              bail("recvfrom(2)");
067 67:
068 68:          dgram[z] = 0; /* NULL terminate dgram */
069 69:
070 70:          /*
071 71:           * Log the request:
072 72:           */
073 73:          log("Got request '%s' from %s port %d\n",
074 74:              dgram,
075 75:              inet_ntoa(adr_clnt.sin_addr),
076 76:              ntohs(adr_clnt.sin_port));
077 77:
078 78:          /*
079 79:           * Get the current date and time:
080 80:           */
081 81:          time(&td);  /* current time & date */
082 82:          dtv = *localtime(&td);
083 83:
084 84:          /*
085 85:           * Format a new date and time string,
086 86:           * based upon the input format string:
087 87:           */
088 88:          strftime(dtfmt, /* Formatted result */
089 89:              sizeof dtfmt,       /* Max size */
090 90:              dgram,      /* date/time format */
091 91:              &dtv);          /* Input values */
092 92:
093 93:          /*
094 94:           * Send the formatted result back to the
095 95:           * client program:
096 96:           */
097 97:          z = sendto(s,            /* Socket */
098 98:              dtfmt,      /* datagram result */
099 99:              strlen(dtfmt),       /* length */
100 100:             0,        /* Flags: no options */
101 101:             (struct sockaddr *)&adr_clnt,
102 102:             alen);
103 103:
104 104:         if ( z < 0 )
105 105:             bail("sendto(2)");
106 106:
107 107:         /* <$nopage>
108 108:          * Wait for next packet or timeout:
109 109:          *
110 110:          * This is easily accomplished with the
111 111:          * use of select(2).
112 112:          */
113 113:         do  {
114 114:             /* Establish Timeout = 8.0 secs */
115 115:             tmout.tv_sec = 8;   /* 8 seconds */
116 116:             tmout.tv_usec = 0;  /* + 0 usec */
117 117:
118 118:             /* Wait for read event or timeout */
119 119:             z = select(s+1,&rx_set,NULL,NULL,&tmout);
120 120:
121 121:         } while ( z == -1 && errno == EINTR );
122 122:
123 123:         /*
124 124:          * Exit if select(2) returns an error
125 125:          * or if it indicates a timeout:
126 126:          */
127 127:         if ( z <= 0 )
128 128:             break;
129 129:     }
130 130:
131 131:     /*
132 132:      * Close the socket and exit:
133 133:      */
134 134:     if ( z == -1 )
135 135:         log("%s: select(2)\n",strerror(errno));
136 136:     else
137 137:         log("Timed out: server exiting.\n");
138 138:
139 139:     close(s);
140 140:     log_close();
141 141:     return 0;
142 142: } <$nopage>
143  <$nopage>

The server code is organized simply as one main() program. The basic steps used by this server can be described as follows:

  1. The log file is opened, and the process ID and the startup message is logged to the log file (lines 37 to 40).

  2. The socket given to this server from inetd is on file units 0, 1, and 2. The server will use file unit 0 for this purpose (line 45).

  3. The variable rx_set is initialized for use with select(2) by the macro calls in lines 46 and 47.

  4. The server loop starts in line 52. This server loops, and so the flag word wait must be specified in the /etc/inetd.conf file.

  5. The server reads a datagram from the socket (s=0) in lines 56 to 66.

  6. The received datagram has a terminating null byte stuffed into the buffer dgram[] (line 68).

  7. The server processes the request by formatting a date/time string according to the received format string (lines 81 to 91).

  8. The server responds back to the client with the formatted result (lines 97 to 105).

  9. The server waits for another datagram in the select(2) call (lines 113 to 121). This will be described in more detail later.

  10. If an error has occurred, or a timeout has occurred, the control exits the for loop at line 128 (the break statement).

  11. Finally, the server logs the error message (line 135) or it logs the fact that it timed out (line 137).

Now, examine the segment of code that implements the timeout code:

  1. A do while loop is entered starting in line 113. This is necessary because you should always allow for the possibility of an interrupted system call. This is indicated after a signal handler returns from handling a signal, by returning an error indication and setting errno to the value EINTR. Consequently, the while statement repeats this call if -1 is returned from select(2) and the error value is EINTR.

  2. The timeout value is established in lines 115 and 116. Here, the timeout is established at eight seconds.

  3. The variable rx_set is initialized in lines 46 and 47 so that the select(2) call will report when datagrams arrive on file unit 0 (its socket). Normally, this should be established prior to each entry to select(2) because this function updates the contents of rx_set. However, in this case, this is unnecessary because the for loop is repeated only if select(2) returns with the rx_set bit set for unit 0 set (indicating that a datagram has arrived).

  4. The select(2) call returns with 1 if a datagram has arrived, 0 if a timeout occurred instead, or -1 if some other error has occurred.

  5. If a timeout or error occurs, the for loop is exited (line 128).

  6. Otherwise, rx_set still has bit 0 set, and the for loop is repeated (line 52).

This server program shows how a UDP server can loop back, and read more datagrams until a timeout occurs. There are other ways to accomplish a timeout, but this is perhaps one of the simplest ways it can be done.

Examining the Simple TCP Wrapper Program

Now, it is time to introduce the source code for the simple TCP wrapper program that will be used. This program is illustrated in Listing 16.4.

Example 16.4. wrapper.c—The Simple TCP Wrapper Program
 <$nopage>
001 1:   /* wrapper.c:
002 2:    *
003 3:    * Simple wrapper example:
004 4:    */
005 5:   #include <stdio.h>
006 6:   #include <unistd.h>
007 7:   #include <stdlib.h>
008 8:   #include <errno.h>
009 9:   #include <string.h>
010 10:  #include <sys/types.h>
011 11:  #include <sys/socket.h>
012 12:  #include <netinet/in.h>
013 13:  #include <arpa/inet.h>
014 14:
015 15:  #include "log.h"
016 16:
017 17:  #define LOGPATH "/tmp/wrapper.log"
018 18:
019 19:  int
020 20:  main(int argc,char **argv,char **envp) {
021 21:      int z;
022 22:      struct sockaddr_in adr_clnt; /* Client */
023 23:      int alen;             /* Address length */
024 24:      char dgram[512];      /* Receive buffer */
025 25:      char *str_addr;  /* String form of addr */
026 26:
027 27:      /*
028 28:       * We must log denied attempts:
029 29:       */
030 30:      if ( log_open(LOGPATH) == -1 )
031 31:          exit(1);   /* Can't open log file! */
032 32:
033 33:      log("wrapper started.\n");
034 34:
035 35:      /*
036 36:       * Peek at datagram using MSG_PEEK:
037 37:       */
038 38:      alen = sizeof adr_clnt;      /* length */
039 39:
040 40:      z = recvfrom(0, /* Socket on std input */
041 41:          dgram,          /* Receiving buffer */
042 42:          sizeof dgram,      /* Max recv size */
043 43:          MSG_PEEK,      /* Flags: Peek!!!!!! */
044 44:          (struct sockaddr *)&adr_clnt,
045 45:          &alen);       /* Addr len, in & out */
046 46:
047 47:      if ( z < 0 )
048 48:          bail("recvfrom(2), peeking at client"
049 49:              " address.");
050 50:
051 51:      /*
052 52:       * Convert IP address to string form:
053 53:       */
054 54:      str_addr = inet_ntoa(adr_clnt.sin_addr); <$nopage>
055 55:
056 56:      if ( strcmp(str_addr,"127.7.7.7") != 0 ) {
057 57:          /*
058 58:           * Not our special 127.7.7.7 address:
059 59:           */
060 60:          log("Address %s port %d rejected.\n",
061 61:              str_addr, ntohs(adr_clnt.sin_port));
062 62:
063 63:          /*
064 64:           * We must read this packet now without
065 65:           * the MSG_PEEK option to discard dgram:
066 66:           */
067 67:          z = recvfrom(0,          /* Socket */
068 68:              dgram,     /* Receiving buffer */
069 69:              sizeof dgram,  /* Max rcv size */
070 70:              0,                /* No flags!! */
071 71:              (struct sockaddr *)&adr_clnt,
072 72:              &alen);
073 73:
074 74:          if ( z < 0 )
075 75:              bail("recvfrom(2), eating dgram");
076 76:          exit(1);
077 77:      }
078 78:
079 79:      /*
080 80:       * Accept this dgram request, and
081 81:       * launch the server:
082 82:       */
083 83:      log("Address %s port %d accepted.\n",
084 84:          str_addr, ntohs(adr_clnt.sin_port));
085 85:
086 86:      /*
087 87:       * inetd has provided argv[0] from the
088 88:       * config file /etc/inetd.conf: we have
089 89:       * used this to indicate the server's
090 90:       * full pathname for this example. We
091 91:       * simply pass any other arguments and
092 92:       * environment as is.
093 93:       */
094 94:      log("Starting '%s'\n",argv[0]);
095 95:      log_close();    /* No longer need this */
096 96:
097 97:      z = execve(argv[0],argv,envp);
098 98:
099 99:      /*
100 100:      * If control returns, then execve(2)
101 101:      * failed for some reason:
102 102:      */
103 103:     log_open(LOGPATH);      /* Re-open log */
104 104:     bail("execve(2), starting server");
105 105:     return 1;
106 106: } <$nopage>
107  <$nopage>

The wrapper program shown in Listing 16.4 implements the following very simple security policy:

  • If the client's address is 127.7.7.7, his request is allowed to go to the datagram server. No restriction upon client's port number is applied.

  • If the client's address is any other IP address, the request is logged and denied.

NOTE

The policy address of 127.7.7.7 was chosen as an example that all readers should be able to test (even those readers without an actual network established). The reader is encouraged to modify the program if he does have a network, in order to try out additional policy rules.


The TCP wrapper program presented uses the following basic steps:

  1. To log denied and granted attempts, a separate log file /tmp/wrapper.log is opened in lines 30 to 31. The wrapper program's process ID and start banner are also logged for demonstration purposes in line 33.

  2. A datagram wrapper program cannot use getpeername(2) function to determine the datagram address. Instead, it must call upon recvfrom(2) using the MSG_PEEK flag bit (lines 40 to 49). The MSG_PEEK flag allows the client address to be returned into the address structure adr_clnt (line 44) without actually removing the datagram from the input queue for this socket.

  3. For demonstration purposes, this simple program converts the client's address into a string for easier comparison (line 54). This is probably not recommended for a secure wrapper program.

  4. The client's address is tested in line 56. If the client's IP address is not the magic 127.7.7.7 IP number that the wrapper program insists upon, the code in lines 60 to 77 is executed (the request is denied).

  5. If the request is accepted, the code in lines 83 to 97 is executed.

Now, examine what happens when an incoming request is rejected by this wrapper program:

  1. First, the rejection of the request is logged in lines 60 to 61.

  2. The datagram must be discarded (or "eaten") by the wrapper program. This is done by receiving it in lines 67 to 72 (note that no MSG_PEEK flag is used here). If this is not done, the datagram remains waiting to be processed for this socket.

  3. Finally, errors are reported, if required, and the wrapper program terminates (lines 74 to 76).

When the datagram request is accepted by the wrapper program, the following steps are carried out:

  1. The log() function is called to log this request (lines 83 to 84).

  2. For demonstration reasons, the executable being started (the server) is logged in line 94. This can aid debugging if you decide to modify this code.

  3. The log file is closed (line 95). This should be done because the log file unit will remain open after the call to execve(2) if this is not done.

  4. The wrapper program is replaced with the server by calling execve(2) (line 97). The current wrapper program is abandoned as the server program is brought into memory to replace it. For this reason, the wrapper and the server programs keep the same process ID (this is important to inetd, the parent process).

If all goes well, the server begins to execute. If for some reason the server does not execute, then the execve(2) function will return control. The following error recovery steps are then carried out:

  1. The log file is reopened (line 103) because it was closed prior to calling execve(2) in line 95.

  2. The error is reported to the log file (line 104).

< BACKCONTINUE >

Index terms contained in this section

accepting requests
      TCP wrappers
datagram servers
      inetd datagram server code example 2nd 3rd 4th
      inetd datagram server code process analysis 2nd
      inetd datagram server timeout code analysis 2nd
      log.c logging functions code example 2nd 3rd
      log.h header files code example 2nd
errors
     TCP wrappers
            error recovery
functions
     logging
            log.c logging functions code example 2nd 3rd
            log.h header files example 2nd
inetd
     servers
            inetd datagram server code example 2nd 3rd 4th
            inetd datagram server code process analysis 2nd
            inetd datagram server timeout code analysis 2nd
            log.c logging functions code example 2nd 3rd
            log.h header files code example 2nd
listings
      inetd datagram server code example 2nd 3rd 4th
      log.c logging functions code example 2nd 3rd
      log.h header files code example 2nd
      TCP wrapper program code example 2nd 3rd
logging functions
      log.c logging functions code example 2nd 3rd
      log.h header files code example 2nd
rejecting requests
      TCP wrappers
requests
     accepting
            TCP wrappers
     rejecting
            TCP wrappers
security
     TCP wrappers
            accepting requests
            error recovery
            inetd datagram server code example 2nd 3rd 4th
            inetd datagram server code process analysis 2nd
            inetd datagram server timeout code analysis 2nd
            log.c logging functions code example 2nd 3rd
            log.h header files code example 2nd
            rejecting requests
            TCP wrapper program code analysis 2nd 3rd
            TCP wrapper program code example 2nd 3rd
servers
     datagram
            inetd datagram server code example 2nd 3rd 4th
            inetd datagram server code process analysis 2nd
            inetd datagram server timeout code analysis 2nd
            log.c logging functions code example 2nd 3rd
            log.h header files code example 2nd
TCP wrappers
      inetd datagram server code example 2nd 3rd 4th
      inetd datagram server code process analysis 2nd
      inetd datagram server timeout code analysis 2nd
      log.c logging functions code example 2nd 3rd
      log.h header files code example 2nd
     TCP wrapper program
            accepting requests
            code analysis 2nd 3rd
            code example 2nd 3rd
            error recovery
            rejecting requests
timeouts
      inetd datagram server timeout code analysis 2nd
wrappers, TCP
      inetd datagram server code example 2nd 3rd 4th
      inetd datagram server code process analysis 2nd
      inetd datagram server timeout code analysis 2nd
      log.c logging functions code example 2nd 3rd
      log.h header files code example 2nd
     TCP wrapper program
            accepting requests
            code analysis 2nd 3rd
            code example 2nd 3rd
            error recovery
            rejecting requests