mailto: blog -at- heyrick -dot- eu

DumbServer - writing a simple server for RISC OS

Following a discussion on the RISC OS Open forums, I thought I'd have a crack at writing a complete simple server for RISC OS.

Presented below is a program that opens port 123. If you connect to it, you will see a menu giving various options, and upon pressing a key, will give humorous responses. Only one connection can be established at a time, other connections will be told the server is busy.

This program compiles with the ROOL DDE. It makes use of the TCPIP libraries, and the standard C libraries. It is a proper multitasking program, though for simplicity it does not place an icon on the iconbar or anything like that. You can quit it from the TaskManager.

The code ought to be fairly self-documenting with the comments. If you think anything needs to be explained better, leave a comment. The exception is the FD_SET/FD_ISSET nonsense in check_listener(). This is because the socket code has its origins in Unix, and Unix is good at doing complicated things, but less good at simple things. You can easily check dozens of sockets at the same time, but you need to do all of this junk to check just one socket. Ho hum. Just copy it into your code, it works... ☺

/* DumbServer v0.01

   by Rick Murray, 2016/01/04
   to demonstrate writing a simple Internet accessible server.
   http://www.heyrick.co.uk/blog/index.php?diary=20160104

   Run this program, then connect to the machine on port 123 using any telnet client.


   © 2016 Richard Murray
   Licensed under the I-Don't-Give-A-Crap licence version 0.
   If you can't handle sarcasm, assume bsd. And don't read the rest of this file.
*/


#include "kernel.h"
#include "swis.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define COMPAT_INET4 1
#include "sys/types.h"       // shorthand types (like u_int and u_long)
#include "sys/socket.h"      // address families (etc) and socket functions
#include "socklib.h"         // more socket functions (and dupes of prior line)
#include "inetlib.h"         // various support functions
#include "netinet/in.h"      // IP/IP definitions
#include "sys/ioctl.h"       // for ioctl function
#include "sys/errno.h"       // error definitions
#include "netdb.h"           // hostent definition

#define TRUE  1
#define FALSE 0


static unsigned int sock = 0;    // listening socket
static unsigned int mysock = -1; // connected socket (is -1 if NOT connected)
static fd_set isready;
static struct timeval timeout;

static unsigned int taskhandle;
union polldatadef
{
   char bytes[256];
   int  words[64];
} data;

static char buffer[256] = "";

static void check_listener(void);
static void check_socket(void);



int main(void)
{
   // Set up a socket for receiving incoming connections
   int  args[2];
   int  event = 0;
   struct sockaddr_in saddr;
   union polldatadef polldata;


   // ## PART ONE
   // ## Setting up the socket

   // Create the socket
   sock = socket(AF_INET, SOCK_STREAM, PF_UNSPEC);

   // Allow address reuse - *very* *important*. If you don't do this, then you'll need to
   // wait for the socket to "expire" in between running this program, and running it again.
   args[0] = 1; // non-zero = enable
   setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &args, sizeof(int));

   // Set linger time to 10 seconds maximum
   args[0] = 1;  // wait for data to drain
   args[1] = 10; // 10 seconds manimum
   setsockopt(sock, SOL_SOCKET, SO_LINGER, &args, (sizeof(int) * 2));

   // Bind to port 123
   memset(&saddr, 0, sizeof(saddr));   // zero everything before use
   saddr.sin_family = AF_INET;         // accept internet connection...
   saddr.sin_port = htons(123);        // ...on port 123...
   saddr.sin_addr.s_addr = INADDR_ANY; // ...from anybody.
   if ( bind(sock, (struct sockaddr *)&saddr, sizeof(saddr)) != 0 )
   {
      socketclose(sock);
      printf("Failed to bind socket - is DumbServer already running?\n");
      exit(EXIT_FAILURE);
   }

   // Make it non-blocking; so the machine won't hang up waiting for something to happen.
   args[0] = 1;
   socketioctl(sock, FIONBIO, &args[0]);

   // Now place an ear against the wire
   if ( listen(sock, 1) != 0 )
   {
      socketclose(sock);
      printf("Unable to listen for incoming connections.\n");
      exit(EXIT_FAILURE);
   }


   // ## PART TWO
   // ## Initialising as a Wimp task and polling

   // Makes extensive use of embedded assembler here. Sad pathetic people will hate me for
   // this (see if I care...look, this is me caring, see?), however the idea is to paste in
   // some boilerplate Wimp code without specifying any particular library and all that that
   // may involve...
   //
   // The SWI instruction:
   //   SWI  <swinum>, {<input>}, {<output>}, {<corrupted>}
   // in/out/corr is a register list. Can also specify LR (in module code) and PSR if flags
   // are expected to be corrupted (they usually are).
   // The register lists control how cc generates code, so do NOT forget anything, and
   // specify ALL used or corrupted registers.

   // Basic minimal Wimp initialise
   __asm
   {
      MOV   R0, 200
      MOV   R1, 0x4B534154   // "TASK"
      MOV   R2, "DumbServer"
      MOV   R3, 0
      SWI   (Wimp_Initialise + XOS_Bit), {R0-R3}, {R1}, {R0, PSR}
      MOV   taskhandle, R1
   }

   // Polling loop
   while ( TRUE )
   {
      __asm
      {
         // Get the current ticker value
         SWI   OS_ReadMonotonicTime, {}, {R0}, {PSR}

         // Put it into R2 with 50 (half a second) added
         ADD   R2, R0, 50

         // The poll block buffer
         MOV   R1, &polldata

         // And finally the poll mask
         MOV   R0, #0  // lazy - just return everything...

         // Yield!
         SWI   (Wimp_PollIdle + XOS_Bit), {R0-R2}, {R0, PSR}, {R1, R2}

         // Remember the event code
         MOVVC event, R0

         // else fake enough upon an error to get the program to shut down
         MOVVS event, 17             // User_Message
         MOVVS polldata.words[4], 0  // Message_Quit
      }

      // Now deal with the poll events. Will likely be either NULL or USER_MESSAGE.
      // We'll ignore anything else.

      switch (event)
      {
         case  0: // Null_Event - anything happen?
                  check_listener();
                  if ( mysock != -1 )
                     check_socket();
                  break;

         case 17: // User_Message, check +16 for message code
                  switch (polldata.words[4])
                  {
                     case 0x00000 : // Message_Quit
                                    // Close task
                                    __asm
                                    {
                                       MOV   R0, taskhandle
                                       MOV   R1, 0x4B534154   // "TASK"
                                       SWI   (Wimp_CloseDown + XOS_Bit), {R0-R1}, {}, {R0, PSR}
                                    }
                                    // Close socket(s)
                                    if ( mysock != -1 )
                                       socketclose(mysock);
                                    socketclose(sock);
                                    // Bye.
                                    exit(EXIT_SUCCESS);
                                    break;
                  }
                  break;

      } // end of event switch
   }; // end of "while (TRUE)" polling loop

   return 0; // shouldn't ever be executed
}



static void check_listener(void)
{
   // Check to see if there's an incoming connection
   struct sockaddr_in saddr;
   int namelen = 0;

   FD_ZERO(&isready);
   FD_SET(sock, &isready);
   timeout.tv_sec = 0;
   timeout.tv_usec = 0;
   select((sock+1), &isready, 0, 0, &timeout);
   if (FD_ISSET(sock, &isready))
   {
      // There is a connection pending.
      if (mysock == -1)
      {
         // We are not connected, so accept this connection.
         mysock = accept(sock, (struct sockaddr *)&saddr, &namelen);
         if (mysock == -1)
         {
            // Something went wrong. Just get out of here, the listener is still active...
            return;
         }
      }
      else
      {
         // We are ALREADY connected, so accept and DISCARD this connection.
         int  pickupsock;

         pickupsock = accept(sock, (struct sockaddr *)0, (int *)0);
         if (pickupsock != -1)
         {
            strcpy(buffer, "Sorry, the server is busy.\r\n");
            socketwrite(pickupsock, &buffer, strlen(buffer));
            shutdown(pickupsock, 0);
            socketclose(pickupsock);
         }
      }

      // The IP address of the remote user is saddr.sin_addr.s_addr, if you need it.

      // At this point, "mysock" will be != -1 so it will be treated as a live socket and any
      // other connections will result in busy message followed by a disconnect.

      // Right-o, let's welcome the user.
      strcpy(buffer, "** ACCESS GRANTED **\r\n\r\n\
Please select:\r\n\
   [A]ccess All Files\r\n\
   [B]reak into system\r\n\
   [C]orrupt system\r\n\
   [Q]uit.\r\n\
>");
      // Can anybody say CHEESY! (^_^)
      socketwrite(mysock, &buffer, strlen(buffer));
   }

   return;
}



static void check_socket(void)
{
   // Check to see if connected socket has done something.
   int  howmuch = 0;

   // Peek a byte to see if connection is still active
   howmuch = recv(mysock, &buffer, 1, MSG_PEEK);
   if (howmuch == 0)
   {
      // Does NOT return zero bytes as a result. As we're non-blocking, it would return -1
      // with errno set to 35 (EWOULDBLOCK).
      // Therefore, a result of zero means the remote end has thrown in the towel.
      // Dunno why we need to do this rubbish instead of socketread() returning -1 with the
      // error EBUGGEREDOFF or somesuch...
      shutdown(mysock, 0);
      socketclose(mysock);
      mysock = -1; // so we know nothing is connected now
      return;
   }

   // Read up to 250 bytes.
   howmuch = socketread(mysock, &buffer, 250);
   if (howmuch > 0)
   {
      // Our menu provides single keypress options, so we'll only look at  the first character
      // that is received. If the user types more, well, that's their problem isn't it?
      switch( buffer[0] )
      {
         case 'A' : // falls through
         case 'a' : strcpy(buffer, "\r\nWhat the hell? This isn't a movie!\r\n\
No self-respecting system would EVER have an option like that.\r\n>");
                    break;

         case 'B' : // falls through
         case 'b' : strcpy(buffer, "\r\nYeah... and I suppose you want inch tall \
characters as well, right?\r\n>");
                    break;

         case 'C' : // falls through
         case 'c' : strcpy(buffer, "\r\nThis one is easy, just install Windows 10.\r\n>");
                    // And with Win10 IoT for the Pi, this even makes sense on a machine
                    // running RISC OS.......god help us...
                    break;

         case 'Q' : // falls through
         case 'q' : strcpy(buffer, "\r\nBye!\r\n");
                    socketwrite(mysock, &buffer, strlen(buffer));
                    shutdown(mysock, 0);
                    socketclose(mysock);
                    mysock = -1; // so we know nothing is connected now
                    return;
                    break; // never executed, but looks weird without it

         default  : strcpy(buffer, "\r\nCan't you read? Enter A, B, C, or Q.\r\n>");
                    break;
      }

      socketwrite(mysock, &buffer, strlen(buffer));
   }

   return;
}

 

Ideas for you, now:

  • There is no verification of WHO is connecting. You might want to ask for a username/password or somesuch.
  • It is fairly easy to extend this to cater for multiple concurrent connections. Turn mysock into an array, then you can support up to n connections.
  • If you want a proper telnet server, change the port to 23 and you may like to do some telnet/terminal negotiation. I believe Nettle (maybe others) will local echo at the moment. This can be disabled by the server by proper negotiation.
  • Alternatively, you can make a cheap'n'nasty web server by hanging on to port 80 (or 8080, usually) and waiting quietly until you see GET /filename (followed by some other rubbish, followed by a blank line). There is actually a whole load of things a browser will send you, and a bunch of responses it expects in return. Google will educate you.
  • Likewise, hang on to port 110 and you can fake being a POP3 server. Asides from encrypted connections, the underpinnings of many Internet protocols is to open a connection and push data back and forth in this sort of manner. Until recently when practically all decent mail servers expect SSL, it was possible to log in and check your mail using a telnet program and typing the commands in manually.
  • Rip out the assembler stuff and drop in some proper library code. I recommend DeskLib (well, I would...search this blog for why!). You can then add an iconbar icon, a menu, all the usual stuff.
  • The icon has been ripped from OmniClient and had "Duh" superimposed. This is because my ability to draw utterly crap icons is legendary. Well, maybe not legendary, but it should be. My drawing sucks. A blind three year old girl could make more convincing drawings of things she's never seen...using crayons...those really big ones.

 

And now, the bit you were waiting for. Click the picture for it...

 

 

Your comments:

Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺ ADDING COMMENTS DOES NOT WORK IF READING TRANSLATED VERSIONS.
 
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.

Colin, 5th January 2016, 16:32
A recv return value of 0 means the the remote end of the socket wont send any more data ie that it has shutdown the 'write' side of its socket - it isn't an error condition. The socket may still be open for you to write to it. It will return zero whether the socket is nonblocking or not. 
 
Love the lengths you go to to avoid using _swix 
 
Could simplify checklistener to just accept instead of select and accept. nonblocking accept returns ewouldblock if there are no queued connections

Add a comment (v0.11) [help?] . . . try the comment feed!
Your name
Your email (optional)
Validation Are you real? Please type 58668 backwards.
Your comment
French flagSpanish flagJapanese flag
Calendar
«   January 2016   »
MonTueWedThuFriSatSun
    12
578910
11121415
181921222324
27282931

(Felicity? Marte? Find out!)

Last 5 entries

List all b.log entries

Return to the site index

Geekery
 
Alphabetical:

Search

Search Rick's b.log!

PS: Don't try to be clever.
It's a simple substring match.

Etc...

Last read at 22:02 on 2024/11/21.

QR code


Valid HTML 4.01 Transitional
Valid CSS
Valid RSS 2.0

 

© 2016 Rick Murray
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted.
RIPA notice: No consent is given for interception of page transmission.

 

Have you noticed the watermarks on pictures?
Next entry - 2016/01/06
Return to top of page