Monday, 20 August 2007 20:18

Write your own Linux server part two

By
In part one, we presented a real-world story where a Linux server – or daemon – solved a need for an ISP. The code to achieve this was introduced, and annotated. However, we left the best till now – the actual socket handling itself, plus the rc.d script to start the daemon at boot time and kill it at shutdown.

Socket handling


This daemon, like any other, is designed to run in the background, and respond to network connections over TCP/IP. We can achieve this using the best known TCP/IP handling interface, Berkeley sockets. Our main() routine in dwserv.cpp sets it all up.


int main (int argc, char *argv [])
{
  struct sockaddr_in sin, fsin;
  struct protoent *ppe;
  int sock, ssock;
  int alen;
  int port = 5000;
  int qlen = 5;
  int pid;
  int fd;
  char nowtime [26];
  char Remote [80];
  int connections = 0;
  FILE *logfp = NULL;


First, as we are going to be creating accounts, the server must be run as the super-user. However, this need not cause any fear, because our server is both robust and secure. And, of course, it is open source and can thus be inspected and enhanced.

  if (geteuid () != 0)
  {
    printf ("\n%s must be run with super-user privileges.\n", argv [0]);
    return 0;
  }


We then establish a server socket, bound to the port we have chosen to use, as specified by the port variable above.

// Set up the socket.

  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = INADDR_ANY;
  sin.sin_port = htons (port);

  if ((ppe = getprotobyname ("tcp")) == 0)
    errexit ("Can't find tcp: %s\n", strerror (errno));

  if ((sock = socket (PF_INET, SOCK_STREAM, ppe->p_proto)) < 0)
    errexit ("Can't create socket: %s\n", strerror (errno));

  if (bind (sock, (struct sockaddr *) &sin, sizeof (sin)) < 0)
    errexit ("Can't bind to port: %s\n", strerror (errno));

  if (listen (sock, qlen) < 0)
    errexit ("Can't listen on port: %s\n", strerror (errno));


We set the server to run in the background by using the fork system call to create a second process, and by disconnecting the new process from the controlling tty. The original process is terminated.


  if ((pid = fork ()) < 0)
    errexit ("Error setting up server: %s\n", strerror (errno));

  if (pid)  // Non-zero is parent.
  {
    printf ("Server pid is %d.\n", pid);
    if (strlen (FileName) > 0)
      log ("Server pid is %d.\n\n", pid);
    exit (0);
  }

  fd = open ("/dev/tty", O_RDWR);
  ioctl (fd, TIOCNOTTY, 0);
  close (fd);


The server now simply sits passively in the background listening forever for network connections.


If an incoming request is made, then a new, slave, socket is created to process it. This is hived off into a child process. This permits the server socket and process to continue accepting more connections because it has offloaded the processing. This gives the appearance of allowing multiple simultaneous connections.


  while (1)
  {
    alen = sizeof (fsin);
    ssock = accept (sock, (struct sockaddr *) &fsin, &alen);
    if (ssock < 0)
    {
      if (errno == EINTR)
        continue;
      else
        errexit ("accept: %s\n", strerror (errno));
    }

    strcpy (Remote, IPtoAddress (fsin.sin_addr));
    log ("%s: connect from %s\n", CurrentDateTime (nowtime), Remote);

    signal (SIGCHLD, reaper);
    connections++;
    if (fork () == 0)
    {
      process (ssock, Remote, connections);
      exit (0);
    }
    else
      close (ssock);
  }
}


The process method, called above, deciphers the command received from the client and calls the appropriate method to deal with it - such as CreateAccount. Any error messages are returned to the client through the same socket.


Security and debugging


Given this daemon creates user accounts you will appreciate it must be protected from being used by others who do not have genuine authority to do so. We must take some steps to protect it. In this case, the best form of protection is isolation from external networks. However, we do take some steps in the program code to provide protection by obscurity.

Specifically, when the client first connects, the daemon merely echoes the date and time to appear like a harmless service. The connecting client must now pass a secret password to the server to proceed. This is defined in dwserv.h as follows:

#define SECRET_PASSWORD "allyourbasearebelongtous"


Of course, a client program (like a PHP form on an intranet) needs to have the secret password (in order to pass it to the server). So, if you change the password in the server, then you must change it in the client as well.

To provide some further security, the server does not return any messages to the client apart from error messages and output of requests to return all the e-mail aliases or groups for a given login ID. This is unlike more chatty protocols like SMTP which even provides help!

Despite being so mute, to aid in debugging, a log file can be switched on which will track all connections made and commands issued. A command-line flag can turn this on, but the best way is described in the following section.

Launching the daemon at system startup


The server is a 24-hour process, designed to just run and respond to requests as they are received. For this reason, it is best if the server can be set running at system start-up time.

A script, 99dwsvr, is included which can be placed in the /etc/rc2.d directory hierarchy. In this directory one finds shell programs that set server processes running at startup - just like ours. The programs also terminate server processes when the system is shutting down or changing run-level. The scripts all work in a consistent fashion - giving them a parameter of start starts the process; giving a parameter of stop stops the process.

Our shell script does just this. It also lets some important flags be set easily, such as the port number, and whether to turn logging on or not.

DWSERV_DIR=.

# Edit the following to change the port number that dwserv
# runs at. Any client program must specify this port number to connect.
PORT=5000

# Edit the following to increase or decrease the connection queue. This
# probably need not be touched unless dwserv is expected to
# receive many simultaneous connections.
QLEN=5

# Turn on or off logging - use 'on' or 'off'
LOG=on

# Edit the following to specify where the logfile is to be stored - this
# will only take effect if logging is switched on (above).
LOGFILE=/tmp/dwsvr.log

# Edit the following to specify if verbose logging should be performed.
# This will create a larger log file but will give much more information
# about what is happening - use 'yes' or 'no'
VERBOSE=yes


All you need to do with 99dwsvr is copy it into the /etc/rc2.d directory area. Your Linux server will call it, itself, when necessary. You can also call it manually to start and stop the server by executing either ./99dwsvr start or ./99dwsvr stop like so


# ./99dwsvr start
dwserv running
# ./99dwsvr stop
dwserv stopped



Testing from a terminal window


The daemon is intended for use with a front-end client system such as an intranet page. However, it can be called – and thus tested – directly from a command-line based telnet program.

Simply use telnet to connect to the port the daemon is running on, such as with a command like

telnet localhost 5000


The daemon will give no helpful output. You must first enter the secret password before useful tasks can be performed. The date and time will be repeatedly outputted, giving up to three opportunities to enter the password before the calling client is disconnected.


$ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Mon Aug 20 10:38:24 2007
abcde
Mon Aug 20 10:38:30 2001
help
Mon Aug 20 10:38:32 2001
helo
Connection closed by foreign host.


The secret password is found in the dwserv.h file.  Note that you must stop the daemon and re-execute make anytime you change the source code. If a front-end program connects to the daemon then it must be modified to use the correct secret password.


$ grep SECRET *.h
dwserv.h:#define SECRET_PASSWORD "allyourbasearebelongtous"


The following commands are available -


ALIA - return aliases for a given username
GROU - return groups for a given username
MAKE - make a new user account
STRT - start a process
VERS - return the program version
QUIT - disconnect from the server
STAT - inspect the number of connections


Here’s another sample session. This time we enter the correct secret password, and you will see we get meaningful output from the VERS and STAT commands, before choosing to disconnect.


# telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Mon Aug 20 10:38:43 2007
allyourbasearebelongtous
vers
1.2
stat
3 connections.
quit
Connection closed by foreign host.


The log file

If logging is enabled, the log file will report on each connection that is made to the server.  You will find information detailing the computer that connected and the commands that were executed.  This permits you to monitor any accounts that have been created or processes executed.

The log file is only ever appended to, even if the daemon is restarted. It is not overwritten. This means you will not lose information, but it does mean the log file must be rotated periodically to avoid running out of disk space.

Final comments


You now have the complete code to produce your own Linux network service. In this case, it allows you to make accounts and some other useful tasks. This gives power to control your network from an intranet or through some other means.

What’s nice is that the daemon uses TCP/IP sockets and responds in a timely fashion to incoming requests, spawning their processing off to a child worker thread. Also, the rc2.d script provides automatic startup in a manner consistent with other, distro-supplied, daemons.

Check out 99dwsvr on the following page.


99dwsvr

#!/bin/sh
#
# dwsvr  Bring up/down the dwserv process
#
# by David M. Williams

# -----------------------------------------------------------------------------
# Configurable options ...
# Comment out any of the following lines to use dwserv's default values.
# -----------------------------------------------------------------------------

DWSERV_DIR=.

# Edit the following to change the port number that dwserv
# runs at. Any client program must specify this port number to connect.
PORT=4000

# Edit the following to increase or decrease the connection queue. This
# probably need not be touched unless dwserv is expected to
# receive many simultaneous connections.
QLEN=5

# Turn on or off logging - use 'on' or 'off'
LOG=on

# Edit the following to specify where the logfile is to be stored - this
# will only take effect if logging is switched on (above).
LOGFILE=/tmp/dwsvr.log

# Edit the following to specify if verbose logging should be performed.
# This will create a larger log file but will give much more information
# about what is happening - use 'yes' or 'no'
VERBOSE=yes

# -----------------------------------------------------------------------------
# Nothing below here needs modification
# -----------------------------------------------------------------------------

getPID_Linux ()
{
 PID=`ps -e | grep dwserv | grep -v grep | cut -c1-5`
}

getPID_SunOS ()
{
 PID=`ps -ejf | grep dwserv | grep -v grep | cut -c10-14`
}

getPID=getPID_`uname`
$getPID

case "$1" in
 stop)
  if [ "a$PID" = "a" ];
  then
   echo "dwserv is not running"
   exit 1
  fi

  kill -9 $PID > /dev/null 2>&1
  sleep 1

  $getPID
  if [ "a$PID" = "a" ];
  then
   echo "dwserv stopped"
  else
   echo "dwserv could not be stopped"
  fi
  ;;

 start)
  if [ "a$PID" != "a" ];
  then
   echo "dwserv is already running"
   exit 1
  fi

  COMMANDLINE=

  if [ "a$PORT" != "a" ];
  then
   COMMANDLINE="$COMMANDLINE -p$PORT"
  fi

  if [ "a$QLEN" != "a" ];
  then
   COMMANDLINE="$COMMANDLINE -q$QLEN"
  fi

  if [ "$LOG" = "on" ];
  then
   COMMANDLINE="$COMMANDLINE -l"

   if [ "$VERBOSE" = "yes" ];
   then
    COMMANDLINE="$COMMANDLINE -v"
   fi

   if [ "a$LOGFILE" != "a" ];
   then
    COMMANDLINE="$COMMANDLINE -f$LOGFILE"
   fi
  fi

  $DWSERV_DIR/dwserv $COMMANDLINE > /dev/null 2>&1

  $getPID
  if [ "a$PID" = "a" ];
  then
   echo "dwserv could not be started"
  else
   echo "dwserv running"
  fi
  ;;

 *)
  echo "Usage: dwserv {start|stop}"
  if [ "a$PID" != "a" ];
  then
   echo "dwserv is running"
  else
   echo "dwserv is not running"
  fi
  exit 1
esac


Subscribe to Newsletter here

WEBINAR 12 AUGUST - Why is Cyber Security PR different?

This webinar is an introduction for cyber security companies and communication professionals on the nuances of cyber security public relations in the Asia Pacific.

Join Code Red Security PR Network for a virtual conversation with leading cyber security and ICT journalists, Victor Ng and Stuart Corner, on PR best practices and key success factors for effective communication in the Asian Pacific cyber security market.

You will also hear a success story testimonial from Claroty and what Code Red Security PR has achieved for the brand.

Please register here by 11 August 2020 and a confirmation email, along with instructions on how to join the webinar will be sent to you after registration.

Aug 12, 2020 01:00 PM in Canberra, Melbourne, Sydney. We look forward to seeing you there!

REGISTER NOW!

PROMOTE YOUR WEBINAR ON ITWIRE

It's all about Webinars.

These days our customers Advertising & Marketing campaigns are mainly focussed on Webinars.

If you wish to promote a Webinar we recommend at least a 2 week campaign prior to your event.

The iTWire campaign will include extensive adverts on our News Site itwire.com and prominent Newsletter promotion https://www.itwire.com/itwire-update.html and Promotional News & Editorial.

For covid-19 assistance we have extended terms, a Webinar Business Booster Pack and other supportive programs.

We look forward to discussing your campaign goals with you. Please click the button below.

MORE INFO HERE!

BACK TO HOME PAGE
David M Williams

David has been computing since 1984 where he instantly gravitated to the family Commodore 64. He completed a Bachelor of Computer Science degree from 1990 to 1992, commencing full-time employment as a systems analyst at the end of that year. David subsequently worked as a UNIX Systems Manager, Asia-Pacific technical specialist for an international software company, Business Analyst, IT Manager, and other roles. David has been the Chief Information Officer for national public companies since 2007, delivering IT knowledge and business acumen, seeking to transform the industries within which he works. David is also involved in the user group community, the Australian Computer Society technical advisory boards, and education.

BACK TO HOME PAGE

WEBINARS ONLINE & DEMAND

GUEST ARTICLES

VENDOR NEWS

Guest Opinion

Guest Interviews

Guest Reviews

Guest Research & Case Studies

Channel News

Comments