mailto: blog -at- heyrick -dot- eu

Navi: Previous entry Display calendar Next entry
Switch to desktop version

FYI! Last read at 05:15 on 2024/12/18.

Before everything else...

Happy
birthday
Mom!

 

Simple RISC OS SSL fetcher

As explained the other day, I needed to set up my Manga reader to handle SSL connections. Thankfully, the R-Comp "SecureSockets" module came to the rescue. It's old and clunky (like pretty much anything to do with networking on RISC OS) but it works.

In this b.log article, I'm going to demonstrate how to use the module to implement your own SSL connections.

First, a simple web fetcher

I present below the code to fetch web pages from the internet. It is hardwired to fetch this page from my site. Build it with the DDE and run it from a TaskWindow...

/* Simple HTTP fetcher

   To demonstrate fetching web pages.

   http://www.heyrick.co.uk/blog/index.php?diary=20180312

   Written by Rick Murray, Monday, 12th March 2018.
   Released under the EUPL.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "kernel.h"

#include "TCPIPLibs:socklib.h"         // Socket functions
#include "TCPIPLibs:sys.errno.h"       // Error defs
#include "TCPIPLibs:sys.socket.h"      // Miscellaneous socket stuff
#include "TCPIPLibs:netinet.in.h"      // sockaddr_in definition
#include "TCPIPLibs:netdb.h"           // Resolver
#include "TCPIPLibs:unixlib.h"         // For bcopy()

_kernel_swi_regs r;

char  domain[] = "www.heyrick.co.uk";
char  url[]    = "/blog/index.php?diary=20180312";

char  workspace[1024];



int main(void)
{
   struct sockaddr_in server;
   struct hostent *rb = 0;
   struct servent *sp = 0;
   int    handle = 0;
   int    returncode = 0;
   int    amount = 0;
   int    zeros = 0;

   // Resolve domain
   rb = gethostbyname(domain);
   if (rb == 0)
   {
      printf("Unable to resolve \"%s\".\n", domain);
      exit(0);
   }

   // Create a handle
   handle = socket(AF_INET, SOCK_STREAM, 0);
   if (handle == -1)
   {
      printf("Unable to create a handle.\n");
      exit(0);
   }

   // Set up the IP address block
   bzero((char *)&server, sizeof(server));
   bcopy(rb->h_addr, (char *)&server.sin_addr, rb->h_length);
   server.sin_family = AF_INET;
   sp = getservbyname("http", "tcp");
   server.sin_port = sp->s_port;

   // Connect to host
   returncode = connect(handle, (struct sockaddr *)&server, sizeof(server));
   if (returncode == -1)
   {
      socketclose(handle);
      printf("Unable to connect to \"%s\".\n", domain);
      exit(0);
   }

   // Mark the socket as non-blocking (&8004667E = FIONBIO)
   returncode = 1;
   socketioctl(handle, 0x8004667E, &returncode);

   // Now we're connected, so say what we want
   sprintf(workspace, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", url, domain);
   returncode = socketwrite(handle, workspace, strlen(workspace));
   if (returncode == -1)
   {
      socketclose(handle);
      printf("Error while sending data to host.\n");
      exit(0);
   }

   // Now receive data
   do
   {
      // Read some data
      amount = recv(handle, workspace, 1024, 0x80);

      // Error?
      if ( (amount < 0) && (errno != EWOULDBLOCK) )
      {
         zeros = 0;
         socketclose(handle);
         printf("Error %d while reading data from host.\n", errno);
         exit(0);
      }

      // No data?
      if ( amount == 0 )
      {
         zeros++;

         if ( zeros > 255 )
         {
            // 255 times we received nothing, connection probably ended.
            socketclose(handle);
            exit(1);
         }
      }

      // Some data
      if ( amount > 0 )
      {
         // We have something to read, so...
         zeros = 0;
         for (int recurse = 0; recurse < amount; recurse++)
         {
            int  byte = workspace[recurse];

            // Filter control characters other than linefeed
            if ( (byte < 32) && (byte != 10 ) )
               byte = 0;

            // Output filtered byte
            if (byte != 0)
               putchar(byte);
         }
      } // end of "if ( amount > 0 )"
   } while ( 1 );

   return 0; // never comes here
}

This is a fairly straightforward fetcher. The only weird bit is the use of a counter to timeout the connection. This is because a non-blocking socket does not close itself when the connection has finished, it reports EWOULDBLOCK. So you're supposed to use select(), right? Only select() kept telling me the connection was still active.
I'm probably doing it wrong.
Here's the code I used. This goes between the do and the call to recv():

      // Wait for some data
      do
      {
         FD_ZERO(&readset);
         FD_SET(handle, &readset);
         returncode = select( (handle + 1), &readset, NULL, NULL, NULL );
         printf("returncode = %d, errno = %d\n", returncode, errno);
      } while ( returncode == -1 );

      // Did the other side close the connection?
      if ( returncode == 0 )
      {
         // Yes!
         socketclose(handle);
         printf("\n** END **\n");
         exit(0);
      }

 

Now enter SSL

The way the SecureSockets module has been implemented leads to a rather simple utilisation. One needs only to establish the socket connection as normal, then direct the SecureSockets module to establish an SSL connection using that socket connection, and finally to send and receive data via the SecureSockets module.
In essence, creating a connection within a connection.

/* Simple HTTPS fetcher

   To demonstrate fetching web pages using an encrypted connection.

   https://www.heyrick.co.uk/blog/index.php?diary=20180312

   Written by Rick Murray, Monday, 12th March 2018.
   Released under the EUPL.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "kernel.h"

#include "TCPIPLibs:socklib.h"         // Socket functions
#include "TCPIPLibs:sys.errno.h"       // Error defs
#include "TCPIPLibs:sys.socket.h"      // Miscellaneous socket stuff
#include "TCPIPLibs:netinet.in.h"      // sockaddr_in definition
#include "TCPIPLibs:netdb.h"           // Resolver
#include "TCPIPLibs:unixlib.h"         // For bcopy()

#define XSecure_Connect     0x74280
#define XSecure_Read        0x74282
#define XSecure_Peek        0x74283
#define XSecure_Write       0x74284
#define XSecure_GetError    0x74285
#define XSecure_Disconnect  0x74286
#define XSecure_State       0x74292
#define XSecure_PollConnect 0x74293

static int  socketwritessl(int, const void *, unsigned int);
static void socketclosessl(int);
static int  recvssl(int, char *, int, int);


_kernel_swi_regs r;

char  domain[] = "www.heyrick.co.uk";
char  url[]    = "/blog/index.php?diary=20180312";

char  workspace[1024];



int main(void)
{
   struct sockaddr_in server;
   struct hostent *rb = 0;
   int    handle = 0;
   int    returncode = 0;
   int    amount = 0;
   int    sslhandle = 0;
   int    sslcode = 0;
   _kernel_oserror *err;

   // Resolve domain
   rb = gethostbyname(domain);
   if (rb == 0)
   {
      printf("Unable to resolve \"%s\".\n", domain);
      exit(0);
   }

   // Create a handle
   handle = socket(AF_INET, SOCK_STREAM, 0);
   if (handle == -1)
   {
      printf("Unable to create a handle.\n");
      exit(0);
   }

   // Set up the IP address block
   bzero((char *)&server, sizeof(server));
   bcopy(rb->h_addr, (char *)&server.sin_addr, rb->h_length);
   server.sin_family = AF_INET;
   server.sin_port = htons(443); // SSL is on port 443

   // Connect to host
   returncode = connect(handle, (struct sockaddr *)&server, sizeof(server));
   if (returncode == -1)
   {
      socketclose(handle);
      printf("Unable to connect to \"%s\".\n", domain);
      exit(0);
   }

   // Mark the socket as non-blocking (&8004667E = FIONBIO)
   returncode = 1;
   socketioctl(handle, 0x8004667E, &returncode);

   // Initiate SSL connection
   r.r[0] = 4; // SSLv3 client, can downgrade to SSLv2
   r.r[1] = handle;
   err = _kernel_swi(XSecure_Connect, &r, &r);
   if ( err )
   {
      socketclose(handle);
      printf("Error establishing SSL connection: %s\n", err->errmess);
      exit(0);
   }
   sslhandle = r.r[0];
   if ( sslhandle == 0 )
   {
      socketclose(handle);
      printf("SSL connection failed.\n");
      exit(0);
   }

   // Poll while awaiting SSL connection
   do
   {
      r.r[0] = sslhandle;
      _kernel_swi(XSecure_PollConnect, &r, &r);
      sslcode = r.r[0];
   } while ( sslcode != 1 );

   // Now we're connected, so say what we want
   sprintf(workspace, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", url, domain);
   returncode = socketwritessl(sslhandle, workspace, strlen(workspace));
   if (returncode == -1)
   {
      socketclosessl(sslhandle);
      socketclose(handle);
      printf("Error while sending data to host.\n");
      exit(0);
   }

   // Now receive data
   while ( sslcode != 6 )
   {
      // Read some data
      amount = recvssl(sslhandle, workspace, 1024, 0x80);

      // Error?
      if ( amount <= 0 )
      {
         // Less than or equal to zero bytes received - possible error?
         r.r[0] = sslhandle;
         r.r[1] = amount;
         _kernel_swi(XSecure_GetError, &r, &r);
         sslcode = r.r[0];
      }
      else
      {
         // More than zero bytes, we have sone data
         for (int recurse = 0; recurse < amount; recurse++)
         {
            int  byte = workspace[recurse];

            // Filter control characters other than linefeed
            if ( (byte < 32) && (byte != 10 ) )
               byte = 0;

            // Output filtered byte
            if (byte != 0)
               putchar(byte);
         }
      } // end of "if ( amount > 0 )"
   }; // end of "while (sslcode != 5)"

   // We come here when all data has been received
   socketclosessl(sslhandle);
   socketclose(handle);

   return 0;
}



static int  socketwritessl(int sslhdl, const void *buf, unsigned int len)
{
   r.r[0] = sslhdl;
   r.r[1] = (int)buf;
   r.r[2] = len;
   _kernel_swi(XSecure_Write, &r, &r);

   return r.r[0];
}



static void socketclosessl(int sslhdl)
{
   r.r[0] = sslhdl;
   _kernel_swi(XSecure_Disconnect, &r, &r);

   return;
}



static int  recvssl(int sslhdl, char *buf, int blen, int flags)
{
   // we ignore flags

   r.r[0] = sslhdl;
   r.r[1] = (int)buf;
   r.r[2] = blen;
   _kernel_swi(XSecure_Read, &r, &r);

   return r.r[0];
}

As can be seen from the above, once the socket is connected and set to non-blocking, a call is made to Secure_Connect to begin the process, and then we just sit on Secure_PollConnect until it is done.

From this point on, we can use Secure_Read and Secure_Write to send and receive data. Sometimes the SSL system will return an error status (perhaps if the data hasn't been fully received for decoding) so we must check by calling Secure_GetError to see what went wrong. If the error response is '6', this means the connection has finished and all data has been received; though I notice that the response is '5' in !Manga - I don't know if polling the Wimp changes the behaviour or what, but it is perhaps worth being aware of this. A simple log to DADebug will indicate what response is being received, should you need to test it.

The three functions at the end basically mimic socketwrite(), socketclose(), and recv() but using the SSL method instead of plain socket actions.

 

As you can see, adding support for SSL is not too complex. Therefore, hopefully, this article has demonstrated how to implement SSL in your own programs, which may be useful now that Google is strongarming everybody to switch to SSL by yesterday...

Finally, here's the code and prebuilt executables:

minifetch.zip (42.3KiB)

 

Linky

Well. It's here now. The man came at half past eleven. The old meter was disconnected in a matter of minutes, and the Linky installed in its place. A special box was plugged in, I presume to set up the firmware with the appropriate billing information. The change took about twenty minutes, and the man was very particular to check that everything was screwed in place extremely tightly - I'm guessing Enerdis (formerly EDF) don't want their smart meter rollout blighted by too many problems.

I asked if I could keep the old meter (a very reliable thing that predates me) and was told that it isn't possible. The poor thing, that kept on going strong after countless lightning strikes, will probably be gutted, stripped, melted down, and turned into part of a bonnet for a bloody Dacia...

Some photos. First up, the original meter sporting a nice selection of cobwebs.

Here's the installer hooking up the Linky:

And, finally, the puke green Linky. Seriously, this photo doesn't do justice to the bizarre colour. The little light at the top blinks at a speed relative to how much energy is being consumed. The screen, by default, is off. Pressing a button will show the current reading and contract type (base meaning there's no weird stuff like special rates or off-peak). The "0 kWh" currently reads "2 kWh" as I write this.
Pressing buttons will show other information such as daily maximal power consumption rated in volt-amps, or the current consumption in volt-amps. Since the meter needs to convert volts and amps to watts, it's a shame it can't display the current consumption in W or kW (I have a plug that can manage at least that much!) as I would imagine people could better equate a wattage rating (given most domestic devices are measured in watts) than some sort of obscure value such as "8VA". Given that converting VA to W requires understanding the power factor and the difference between inductive (pf=0) and resistive (pf=1) loads, it can get pretty hairy pretty quickly...
Here is, apparently, a formula for working out the correct power factor for a three phase system by using a metered reading in kilowatts (P), a line-to-neutral value (VL-N) in volts, and the current in amps (I). I say apparently, because if I just strung together some random Asian characters, it would make about as much sense to me...
PF = |cos φ| = 1000 × P(kW) / (3 × VL-N(V) × I(A))
Yeah... didn't we invent computers so we didn't have to do maths like that?

Here it is then, here's Linky:

 

 

Your comments:

Jeff Doggett, 14th March 2018, 15:24
bzero and bcopy are old non-standard functions. 
You should be using the Ansi standard memset and memcpy.
Jeff Doggett, 16th March 2018, 09:54
Optimisation. 
 
sprintf returns the number of character printed. 
 
len = sprintf (...); 
socketwritessl (a, b, len); 
 
saves a call to strlen.

Add a comment (v0.11) [help?]
Your name:

 
Your email (optional):

 
Validation:
Please type 70724 backwards.

 
Your comment:

 

Navi: Previous entry Display calendar Next entry
Switch to desktop version

Search:

See the rest of HeyRick :-)