/* Copyright 1998 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : Multiuser.h                            */
/*                                                 */
/* Purpose: Functions and definitions to enable    */
/*          the browser to function in an          */
/*          Customer-style multiuser environment, */
/*          if SINGLE_USER is undefined when the   */
/*          whole of the browser source is         */
/*          compiled. Otherwise, some functions    */
/*          available, but only those to do basic  */
/*          single user loading and saving of      */
/*          histories, hotlists etc.               */
/*                                                 */
/* Author : Merlyn Kline for Customer browser     */
/*          This source adapted by A.D.Hodgkinson  */
/*                                                 */
/* History: 23-Jul-97: Created.                    */
/*          16-Mar-98: Bulk of the code imported   */
/*                     from Customer browser.     */
/*                     Fair amount of rewriting /  */
/*                     fixing required.            */
/***************************************************/

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

#include "swis.h"
#include "flex.h"

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "toolbox.h"
#include "iconbar.h"
#include "window.h"
#include "gadgets.h"

#include "svcprint.h"
#include "Global.h"
#include "Utils.h"

#include "Browser.h"
#include "ChoiceDefs.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "History.h"
#include "Hotlist.h"
#include "ImgHistory.h"
#include "Multiuser.h"
#include "URLutils.h"

#include "Multiuser.h"

/* Local statics for all builds */

static ObjectId iconbar = NULL_ObjectId;

#ifndef SINGLE_USER

  /* Local statics for multiuser builds */

  static int          pass_pos;
  static char         current_pass   [Limits_Multi_Pathname];
  static char         login_pb       [Limits_Multi_Pathname];
  static char         trans_file     [9]; /* Lowest common denominator maximum filename length (FileCore, 10 chars) minus 2, plus 1 for terminator */

  static unsigned int login_stamp;
  static time_t       login_time;
  static char         login_password [Limits_Multi_Password];
  static char         login_user     [Limits_Multi_UserName];

  static char         rnd_buf        [Limits_Multi_Encoded];
  static char         rnd_buf1       [Limits_Multi_Encoded];

  static ObjectId     login;

  /* Static function prototypes for multiuser builds */

  static int               multiuser_login_ok                       (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
  static int               multiuser_login_cancel                   (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);

  static int               multiuser_write_line                     (FILE * f, const char * s, int newline);
  static int               multiuser_get_line                       (FILE * f, char * buf, int unknown);
  static unsigned          multiuser_get_stamp                      (const char * f);
  static unsigned          multiuser_get_length                     (const char * f);
  static int               multiuser_file_exists                    (const char * f);

  static void              multiuser_encode_line                    (char * buf, int keepnl, int magic);
//static void              multiuser_decode_data                    (char * data, int len, char * pass, int keepnl, int magic);

  static FILE            * multiuser_open_postbox                   (const char * where, char * name);
  static _kernel_oserror * multiuser_read_user                      (const char * user, const char * password);

  static int               multiuser_watch_flags                    (int eventcode, WimpPollBlock * b, IdBlock * idb, void * handle);
  static int               multiuser_check_login                    (int eventcode, WimpPollBlock * b, IdBlock * idb, void * handle);

#else

  /* Static function prototypes for single user builds */

  static _kernel_oserror * multiuser_read_user                      (const char * name, const char * password);

#endif

/*************************************************/
/* multiuser_set_iconbar_variant()               */
/*                                               */
/* Set either the normal, greyed out or question */
/* mark icon bar icon variants.                  */
/*                                               */
/* Parameters: Pointer to a string holding the   */
/*             suffix to the sprite name (so     */
/*             pointer to "n" or possibly a null */
/*             string for the normal sprite, "g" */
/*             for the greyed out icon, etc.).   */
/*                                               */
/* Assumes:    The pointer may be NULL.          */
/*************************************************/

_kernel_oserror * multiuser_set_iconbar_variant(const char * suffix)
{
  const char * local_suffix = suffix ? suffix : "";

  if (
       iconbar != NULL_ObjectId ||
       !toolbox_create_object(0,
                              "Iconbar",
                              &iconbar)
     )
  {
    *lasttokn = 0;
    lookup_token("_SpriName",1,0);
    strcat(tokens, local_suffix);

    return iconbar_set_sprite(0,
                              iconbar,
                              tokens);
  }

  return NULL;
}

/*************************************************/
/* multiuser_login()                             */
/*                                               */
/* Either read histories etc. based on the       */
/* Choices in single user builds, or open the    */
/* Log In dialogue box.                          */
/*************************************************/

_kernel_oserror * multiuser_login(void)
{
  #ifdef SINGLE_USER

    return multiuser_read_user(NULL, NULL);

  #else

    int        have_default = 0;
    static int tried_once   = 0;

    int        have_user    = 0;
    int        have_pass    = 0;

    re_login = logged_in = 0;

    /* Show the 'greyed out' icon bar sprite */

    show_error_ret(multiuser_set_iconbar_variant("g"));

    /* Create and show the Log In dialogue box */

    RetError(toolbox_create_object(0,
                                   "LogIn",
                                   &login));

    RetError(toolbox_show_object(0,
                                 login,
                                 Toolbox_ShowObject_Centre,
                                 NULL,
                                 NULL_ObjectId,
                                 NULL_ComponentId));

    /* Fill in the user name and password writables */

    have_default = strcmp(lookup_choice("DefaultUser:no",0,0), "no");

    if (!have_default)
    {
      RetError(writablefield_set_value(0, login, LogInPassWrit, ""));
      RetError(writablefield_set_value(0, login, LogInUserWrit, ""));
    }
    else
    {
      RetError(writablefield_set_value(0, login, LogInPassWrit, lookup_choice("DefaultPass:",1,0)));
      if (*tokens) have_pass = 1;

      RetError(writablefield_set_value(0, login, LogInUserWrit, lookup_choice("DefaultName:",1,0)));
      if (*tokens) have_user = 1;
    }

    /* Fill in the message panel */

    button_set_value(0,
                     login,
                     LogInPrompt,
                     lookup_token("LogIn:To use the World Wide Web, please log in by supplying your user name and password.",0,0));

    /* Event handlers */

    RetError(event_register_toolbox_handler(login, ELogInOK,     multiuser_login_ok,     NULL));
    RetError(event_register_toolbox_handler(login, ELogInCancel, multiuser_login_cancel, NULL));

    /* Now the hacky bit; if default_user is set, call the OK handler */
    /* directly... If everything works, the login dialogue will be    */
    /* closed before it is even seen, otherwise it will be opened     */
    /* with the default user name and password entered in it.         */

    if (!tried_once && have_default && have_user && have_pass)
    {
      tried_once = 1;

      multiuser_login_ok(0, NULL, NULL, NULL);
    }

    return NULL;

  #endif
}

/*************************************************/
/* multiuser_logout()                            */
/*                                               */
/*************************************************/

_kernel_oserror * multiuser_logout(void)
{

  #ifdef SINGLE_USER

    #ifndef REMOTE_HOTLIST

      /* Save the hotlist, if required */

      #ifdef TRACE
        if (tl & (1u<<5)) Printf("multiuser_logout: Calling hotlist_save\n");
      #endif

      if (
           choices.save_hotlist != Choices_SaveHotlist_Never
         )
         show_error_ret(hotlist_save(lookup_choice("HotlistSave:Browse:User.Hotlist",0,0)));

    #endif

    /* Same for the global history... */

    if (
         choices.save_history != Choices_SaveHistory_Never
       )
       show_error_ret(history_save(lookup_choice("HistorySave:Browse:User.History",0,0)));

    /* ...and the image history */

    if (
         choices.save_image_history != Choices_SaveImageHistory_Never
       )
       show_error_ret(imghistory_save(lookup_choice("ImageHistorySave:Browse:User.Images",0,0)));

    return NULL;

  #else

    _kernel_oserror * e = NULL;

    if (logged_in)
    {
      /* Save the hotlist, and discard it */

      e = multiuser_save_hotlist();
      hotlist_discard();

      /* Throw away the histories */

      history_limit(0);
      imghistory_limit(0);
    }

    return e;

  #endif
}

#ifndef SINGLE_USER

  /*************************************************/
  /* multiuser_save_hotlist()                      */
  /*                                               */
  /* Save the user's hotlist through the postbox   */
  /* scheme.                                       */
  /*************************************************/

  _kernel_oserror * multiuser_save_hotlist(void)
  {
    FILE * f;
    char   buf [Limits_Multi_Pathname];
    char   name[Limits_Multi_Pathname];

    sprintf(buf, "%s.PostBoxes", choices.post_out);

    f = multiuser_open_postbox(buf, name);

    if (!f)
    {
      erb.errnum = Utils_Error_Custom_Message;

      StrNCpy0(erb.errmess,
               lookup_token("NoPBoxes:No postboxes available at server. Wait a moment and try again.",
                            0,
                            0));

      return &erb;
    }
    else
    {
      sprintf(buf, "X-Mercury-Command:SaveHotlist\n%s\n", user.name);

      /* Yuckery - we hope that the new naming scheme should */
      /* prevent clashes.                                    */

      fclose(f);

      return hotlist_save_hotlist(name, buf, 0);
    }
  }

  /*************************************************/
  /* multiuser_login_ok()                          */
  /*                                               */
  /* Handle ELogInOK events (e.g. clicks on the    */
  /* 'OK' button in the Log In dialogue box).      */
  /*                                               */
  /* Parameters are as standard for a Toolbox      */
  /* event hander.                                 */
  /*************************************************/

  static int multiuser_login_ok(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
  {
    _kernel_oserror * e;

    char              user[Limits_Multi_UserName];
    char              pass[Limits_Multi_Password];

    ChkError(writablefield_get_value(0,
                                     login,
                                     LogInPassWrit,
                                     pass,
                                     sizeof(pass),
                                     NULL));

    ChkError(writablefield_get_value(0,
                                     login,
                                     LogInUserWrit,
                                     user,
                                     sizeof(user),
                                     NULL));

    /* If multiuser_read_user returns an error, keep the login dialogue */
    /* open. Otherwise, install two null handlers. One watches for the  */
    /* server's response to the login request. The other watches the    */
    /* re_login and logged_in flags and takes appropriate action when   */
    /* one or the other becomes set.                                    */

    e = multiuser_read_user(user, pass);

    if (!e)
    {
      register_null_claimant(Wimp_ENull, multiuser_check_login, NULL);
      register_null_claimant(Wimp_ENull, multiuser_watch_flags, NULL);

      /* Close the dialogue, too - don't want the user pressing 'OK' */
      /* lots of times whilst waiting for the first login...!        */

      event_deregister_toolbox_handlers_for_object(login);
      toolbox_delete_object(0, login);

      login = NULL_ObjectId;
    }
    else show_error_ret(e);

    return 1;
  }

  /*************************************************/
  /* multiuser_login_cancel()                      */
  /*                                               */
  /* Handle ELogInCancel events (e.g. clicks on    */
  /* the 'Cancel' button in the Log In dialogue    */
  /* box).                                         */
  /*                                               */
  /* Parameters are as standard for a Toolbox      */
  /* event hander.                                 */
  /*************************************************/

  static int multiuser_login_cancel(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
  {
    /* Didn't log in, so browser quits */

    multiuser_logout();

    exit(EXIT_SUCCESS);

    /* Stop compiler complaining about implicit returns in non-void functions... */

    return 1;
  }

  /*************************************************/
  /* multiuser_write_line()                        */
  /*                                               */
  /*************************************************/

  static int multiuser_write_line(FILE * f, const char * s, int newline)
  {
    fputs(s, f);
    if (newline) fputc('\n', f);

    return !ferror(f);
  }

  /*************************************************/
  /* multiuser_get_line()                          */
  /*                                               */
  /*************************************************/

  static int multiuser_get_line(FILE * f, char * buf, int unknown)
  {
    char * p;

    /* Read up to 256 bytes into the buffer */

    p = fgets(buf, 256, f);
    if (!p) return 0;

    /* Find either a newline or zero terminator, and ensure */
    /* that this is treated as the end of the string by     */
    /* writing a zero over that byte.                       */

    while (*p != '\n' && * p != '\0') p++;
    *p = '\0';

    return 1;
  }

  /*************************************************/
  /* multiuser_get_stamp()                         */
  /*                                               */
  /*************************************************/

  static unsigned multiuser_get_stamp(const char * f)
  {
    unsigned exec_addr;

    _swix(OS_File,
          _INR(0,1) | _OUT(3),

          23,
          f,

          &exec_addr);

    return exec_addr;
  }

  /*************************************************/
  /* multiuser_get_length()                        */
  /*                                               */
  /*************************************************/

  static unsigned multiuser_get_length(const char * f)
  {
    unsigned len;
    int      type;

    _swix(OS_File,
          _INR(0,1) | _OUT(0) | _OUT(4),

          23,
          f,

          &type,
          &len);

    if (!type) return 0;
    else       return len;
  }

  /*************************************************/
  /* multiuser_file_exists()                       */
  /*                                               */
  /*************************************************/

  int multiuser_file_exists(const char * f)
  {
    int type;

    _swix(OS_File,
          _INR(0,1) | _OUT(0),

          23,
          f,

          &type);

    return (type != 0);
  }

  /*************************************************/
  /* multiuser_encode_line()                       */
  /*                                               */
  /*************************************************/

  static void multiuser_encode_line(char * buf, int keepnl, int magic)
  {
    int    i, len;
    char   p;
    char * password = current_pass;

    /* Terminate the line with '\n' and a C string terminator for convenience */

    len = strlen(buf);

    buf[len]     = '\n';
    buf[len + 1] = 0;

    /* Go through character by character */

    for (i = 0; i <= len; i++)
    {
      p = password[pass_pos];

      /* If we are to preserve newline data, don't EOR with the current */
      /* character obtained from the password; similarly, ensure that   */
      /* we don't EOR if this would generate a newline character. If    */
      /* keeping newlines is unimportant, go ahead and EOR it anyway.   */

      if (
           !keepnl ||
           (
             buf[i]       != '\n' &&
             (buf[i] ^ p) != '\n'
           )
         )
      {
        if (
             buf[i] != p &&
             buf[i] != 0
           )
           buf[i] ^= p;
      }

      /* Increment the position in the password */

      pass_pos++;

      /* Have we reached a control character, i.e. the end of */
      /* the password string?                                 */

      if (password[pass_pos] < 32)
      {
        unsigned int r1, r2, tmp;

        /* If the 'magic' flag is set, treat the password as */
        /* a 64 bit binary number (stored as 2 32-bit nos.   */
        /* output by 'printf("%8x%8x")') and rotate it one   */
        /* bit to the right.                                 */

        if (magic)
        {
          sscanf(password, "%8x%8x", &r1, &r2);

          tmp = r2 & 1;
          r2  = r2 >> 1;

          if (r1 & 1) r2 |= (1u<<31);

          r1  = r1 >> 1;

          if (tmp) r1 |= (1u<<31);

          sprintf(password, "%08x%08x", r1, r2);
        }

        /* Point back to the first character of the password */

        pass_pos = 0;
      }
    }
  }

///*************************************************/
///* multiuser_decode_data()                       */
///*                                               */
///*************************************************/
//
//static void multiuser_decode_data(char * data, int len, char * pass, int keepnl, int magic)
//{
//  int    i;
//  char * c;
//  char   p;
//  char   password[Limits_Multi_Pathname];
//
//  StrNCpy0(password, pass);
//
//  c = data;
//  i = 0;
//
//  while (c < data + len)
//  {
//    p = password[i];
//
//    /* Only EOR with the byte from the password if the encoder would have */
//    /* EORed it - see multiuser_encode_line for the 'keep newline' info.  */
//
//    if (
//         !keepnl ||
//         (
//           *c         != '\n' &&
//           ((*c) ^ p) != '\n'
//         )
//       )
//    {
//      if (
//           *c != 0 &&
//           *c != p
//         )
//         *c=(*c) ^ p;
//    }
//
//    i++;
//
//    /* Has the end of the password been reached? */
//
//    if (password[i] < 32)
//    {
//      unsigned int r1, r2, tmp;
//
//      /* If 'magic' is set, rotate the password right one bit as */
//      /* in multiuser_encode_line.                               */
//
//      if (magic)
//      {
//        sscanf(password, "%8x%8x", &r1, &r2);
//
//        tmp = r2 & 1;
//        r2  = r2 >> 1;
//
//        if (r1 & 1) r2 |= (1u<<31);
//
//        r1  = r1 >> 1;
//
//        if (tmp) r1 |= (1u<<31);
//
//        sprintf(password, "%08x%08x", r1, r2);
//      }
//
//      i = 0;
//    }
//
//    c++;
//  }
//}

  /*************************************************/
  /* multiuser_create_unique_postbox_filename()    */
  /*                                               */
  /* Create a unique filename based on the machine */
  /* IP address. This is an 8 character filename   */
  /* prefix stored in 'trans_file'.                */
  /*                                               */
  /* This name MUST NOT depend on the current      */
  /* settings of Post_In / Post_Out, as they may   */
  /* be invalid and/or could change after this     */
  /* function is called without it being called    */
  /* again.                                        */
  /*************************************************/

  void multiuser_create_unique_postbox_filename(void)
  {
    char * tmp;

    /* If Inet$LocalAddr exists use it as the filename */

    tmp = getenv("Inet$LocalAddr");

    if (tmp)
    {
      StrNCpy0(trans_file, tmp);
    }

    /* Otherwise, seed the random number generator with */
    /* the monotonic time and work from that.           */

    else
    {
      unsigned t;

      _swix(OS_ReadMonotonicTime,
            _OUT(0),

            &t);

      srand(t);

      sprintf(trans_file, "%x", rand());
    }
  }

  /*************************************************/
  /* multiuser_open_postbox()                      */
  /*                                               */
  /*************************************************/

  static FILE * multiuser_open_postbox(const char * where, char * name)
  {
    char buf[3];
    int  count = 0, tmp2;

    /* Assume buffer is Limits_Multi_Pathname long... Sigh...   */
    /* '+4' is one for the '.', one for the terminator, and two */
    /* for the two digit suffix on trans_file.                  */

    if (strlen(where) + strlen(trans_file) + 4 > Limits_Multi_Pathname) return NULL;

    /* Compile the full name in 'name' */

    sprintf(name, "%s.%s", where, trans_file);

    tmp2 = strlen(name);

    /* Go through trying to find an unused file as */
    /* 'nameXX', where XX is '00' to 'nn'.         */

    while(multiuser_file_exists(name))
    {
      *(name + tmp2) = '\0';

      sprintf(buf, "%02i", count++);
      strcat(name, buf);

      /* =8*! Ooooooh, yes. Shudder. */

      if (count > 70) return NULL;
    }

    return fopen(name, "w");
  }

  /*************************************************/
  /* multiuser_read_user() (multiuser version)     */
  /*                                               */
  /*************************************************/

  static _kernel_oserror * multiuser_read_user(const char * user, const char * password)
  {
    _kernel_oserror * e = NULL;

    char              pass     [Limits_Multi_Password];
    char              rnd_buf2 [Limits_Multi_Encoded];
    char              rnd_buf3 [Limits_Multi_Encoded];
    char              buf      [Limits_Multi_Encoded];

    unsigned int      rnd, rnd1;

    FILE            * f;
    char            * c;

    /* Seed random number generator with current time */

    srand(time(NULL));

    /* Get two random numbers */

    rnd  = rand();
    rnd1 = rand();

    /* Write them as a string into rnd_buf as two hex numbers back */
    /* to back, and copy this to rnd_buf2.                         */

    sprintf(rnd_buf, "%08x%08x", rnd, rnd1);
    StrNCpy0(rnd_buf2, rnd_buf);

    /* Get another two random numbers, and again write them as two */
    /* back to back hex numbers to rnd_buf1, copying to rnd_buf3.  */

    rnd  = rand();
    rnd1 = rand();

    sprintf(rnd_buf1, "%08x%08x", rnd, rnd1);
    StrNCpy0(rnd_buf3, rnd_buf1);

    /* We must have a non-null user and non-null password string */

    if (!user || !password || !*user || !*password)
    {
      erb.errnum = Utils_Error_Custom_Message;

      StrNCpy0(erb.errmess,
               lookup_token("NoUser:You must enter a user name and password to use the World Wide Web.",
                            0,
                            0));

      return &erb;
    }

    /* Take a local copy of the password (up to 11 chars plus terminator) */

    StrNCpy0(pass, password);

    /* Create a postbox file */

    sprintf(buf, "%s.PostBoxes", choices.post_out);
    f = multiuser_open_postbox(buf, login_pb);

    /* No postboxes available? Complain. */

    if (!f)
    {
      erb.errnum = Utils_Error_Custom_Message;

      StrNCpy0(erb.errmess,
               lookup_token("NoPBoxes:No postboxes available at server. Wait a moment and try again.",
                            0,
                            0));

      return &erb;
    }

    /* Convert password to lower case */

    for (c = pass; *c; c++) *c = tolower(*c);

    /* Set the global pointer into the password to the first */
    /* character, and take a copy of the password in the     */
    /* global password buffer.                               */

    pass_pos = 0;
    StrNCpy0(current_pass, pass);

    /* Encode the random data in rnd_buf2 with the current */
    /* password, remembering not to scramble it by setting */
    /* the 'magic' flag.                                   */

    multiuser_encode_line(rnd_buf2, 1, 0);

    /* Reset the password position, and now use the rnd_buf */
    /* contents (same as rnd_buf2 before encoding) as a     */
    /* password, to encode rnd_buf3 with scrambling.        */

    pass_pos = 0;
    StrNCpy0(current_pass, rnd_buf);
    multiuser_encode_line(rnd_buf3, 1, 1);

    /* Now we have a postbox */

    if (
         multiuser_write_line(f, "X-Mercury-Command:Login250\n", 0) &&
         multiuser_write_line(f, user, 1)                           &&
         multiuser_write_line(f, rnd_buf2, 0)                       &&
         multiuser_write_line(f, rnd_buf3, 0)                       &&
         multiuser_write_line(f, "WWWAccess\n", 0)
       )
    {
      /* For timeout */

      login_time = time(NULL);

      StrNCpy0(login_password, pass);
      StrNCpy0(login_user,     user);
    }
    else
    {
      /* Error writing to postbox */

      erb.errnum = Utils_Error_Custom_Message;

      StrNCpy0(erb.errmess,
               lookup_token("PBoxWErr:Error writing to server postbox. Wait a moment and try again.",
                            0,
                            0));

      e = &erb;
    }

    fclose(f);

    /* Watch for server writing to file by remembering */
    /* the current timestamp before we go into the     */
    /* waiting loop.                                   */

    login_stamp = multiuser_get_stamp(login_pb);

    return e;
  }

  /*************************************************/
  /* multiuser_watch_flags()                       */
  /*                                               */
  /* Watch the re_login and logged_in flags        */
  /* and deal with them if they become set.        */
  /*                                               */
  /* Parameters are as standard for a Wimp event   */
  /* handler (this is called on null events).      */
  /*************************************************/

  static int multiuser_watch_flags(int eventcode, WimpPollBlock * b, IdBlock * idb, void * handle)
  {
    if (re_login || logged_in)
    {
      deregister_null_claimant(Wimp_ENull, multiuser_check_login, NULL);
      deregister_null_claimant(Wimp_ENull, multiuser_watch_flags, NULL);

      if (re_login)
      {
        /* This call clears the re_login and logged_in flags itself */

        ChkError(multiuser_login());
      }
      else
      {
        /* Show the 'normal' icon bar sprite */

        show_error_ret(multiuser_set_iconbar_variant("n"));
      }
    }

    return 0;
  }

  /*************************************************/
  /* multiuser_check_login()                       */
  /*                                               */
  /* Parameters are as standard for a Wimp event   */
  /* handler (this is called on null events).      */
  /*************************************************/

  static int multiuser_check_login(int eventcode, WimpPollBlock * b, IdBlock * idb, void * handle)
  {
    char pass      [Limits_Multi_Pathname];
    char key       [Limits_Multi_Pathname];
    char temp      [Limits_Multi_Pathname];
    char user_file [Limits_Multi_Pathname];

    if (!multiuser_get_length(login_pb))
    {
      /* User name of password wrong */

      erb.errnum = Utils_Error_Custom_Message;

      StrNCpy0(erb.errmess,
               lookup_token("BadUorPW:Incorrect user name or password.",
                            0,
                            0));

      show_error_ret(&erb);

      re_login = 1;

      return 0;
    }

    /* Has the login file been modified? */

    if (multiuser_get_stamp(login_pb) != login_stamp)
    {
      FILE * f;

      f = fopen(login_pb, "r");

      /* Is the server still writing to the file? */

      if (f == NULL)
      {
        time_t t = time(NULL);

        _swix(Hourglass_On, 0);

        /* Wait about 5 seconds... */

        while (difftime(time(NULL), t) < 5);

        _swix(Hourglass_Off, 0);

        /* ...and try again */

        f = fopen(login_pb, "r");
      }

      /* Was the file successfully opened? */

      if (f)
      {
        /* Yes; read the details */

        if (
             !multiuser_get_line(f, user.name, 0) ||
             !multiuser_get_line(f, temp,      0) ||
             !multiuser_get_line(f, user_file, 0) ||
             !multiuser_get_line(f, temp,      0) ||
             !multiuser_get_line(f, pass,      0) ||
             !multiuser_get_line(f, key,       0)
           )
        {
          /* There was a problem reading from the file */

  multiuser_check_login_read_error:

          erb.errnum = Utils_Error_Custom_Message;

          StrNCpy0(erb.errmess,
                   lookup_token("PBoxRErr:Error reading from server postbox. Wait a moment and try again.",
                                0,
                                0));

          show_error_ret(&erb);

          fclose(f);
          remove(login_pb);

          re_login = 1;

          return 0;
        }
        else if (strcmp(pass, rnd_buf))
        {
          /* File contents read OK, but wrong user name or password */

          erb.errnum = Utils_Error_Custom_Message;

          StrNCpy0(erb.errmess,
                   lookup_token("BadUorPW:Incorrect user name or password.",
                                0,
                                0));

          show_error_ret(&erb);

          fclose(f);
          remove(login_pb);

          re_login = 1;

          return 0;
        }
        else
        {
          /* Password is OK, so check we are allowed WWW access. */

          if (
               !multiuser_get_line(f, temp, 0) ||
               !multiuser_get_line(f, temp, 0) ||
               !multiuser_get_line(f, temp, 0)
             )
          {
            /* File read error */

            goto multiuser_check_login_read_error;
          }
          else if (strcmp(temp, "Yes"))
          {
            /* Not allowed WWW access */

            erb.errnum = Utils_Error_Custom_Message;

            StrNCpy0(erb.errmess,
                     lookup_token("NoWWW:Sorry, you are not authorised to access the World Wide Web.",
                                  0,
                                  0));

            show_error_ret(&erb);

            fclose(f);
            remove(login_pb);

            /* Go back to the login screen */

            re_login = 1;

            return 0;
          }

          /* Logged in successfully */

          pass_pos = 0;

          // Given that the browser never does anything with this
          // afterwards, I've removed the field from the userdef structure
          // but left this here in case a good reason to put it back
          // ends up coming to light.
          //
          // multiuser_decode_data(key, strlen(key), rnd_buf1, 1, 1);
          //
          // StrNCpy0(user.password, key);
        }

        /* Close the temporary file and remove it */

        fclose(f);
        remove(login_pb);

        /* Load history and hotlist */

        // Customer didn't allow individual user home pages,
        // so basically this is a waste of space. Left in for
        // future expansion, ahem.
        //
        // StrNCpy0(user.homepage, choices.home_page);

        sprintf(user.hotlist_path,       "%s.Web.%s.HotList", choices.post_in, user_file);
        sprintf(user.history_path,       "%s.Web.%s.History", choices.post_in, user_file);
        sprintf(user.image_history_path, "%s.Web.%s.Images",  choices.post_in, user_file);

        /* Main hotlist (will return error if file not found,  */
        /* so ignore errors)                                   */

        hotlist_load(user.hotlist_path);

        if (!strcmp(lookup_choice("LoadResources:yes",0,0), "yes"))
        {
          /* Resources file (will not return error if file not   */
          /* found, only if out of memory etc., so report those) */

          sprintf(temp, "%s.Web.Resources", choices.post_in);
          show_error_ret(hotlist_load_resources(temp));
        }

        /* Histories */

        show_error_ret(history_load(user.history_path));
        show_error_ret(imghistory_load(user.image_history_path));

        /* Flag that the user is logged in now. */

        logged_in = 1;
      }
    }

    /* File not modified */

    else
    {
      /* Have we timed out? */

      if (difftime(time(NULL), login_time) < choices.log_in_timeout) return 0;

      /* Yes; server isn't responding. */

      remove(login_pb);

      erb.errnum = Utils_Error_Custom_Message;

      StrNCpy0(erb.errmess,
               lookup_token("NoServer:Server not responding.",
                            0,
                            0));

      show_error_ret(&erb);

      re_login = 1;
    }

    return 0;
  }

#else

  /*************************************************/
  /* multiuser_read_user() (single user version)   */
  /*                                               */
  /*************************************************/

  static _kernel_oserror * multiuser_read_user(const char * name, const char * password)
  {
    _kernel_oserror * e1, * e2 = NULL;

    #ifdef TRACE
      if (tl & (1u<<5)) Printf("multiuser_read_user: Loading history\n");
    #endif

    e1 = history_load(choices.history_path);
    if (e1) e2 = e1;

    /* Similarly, load the image history */

    #ifdef TRACE
      if (tl & (1u<<5)) Printf("multiuser_read_user: Loading image history\n");
    #endif

    e1 = imghistory_load(choices.image_history_path);
    if (e1) e2 = e1;

    #ifndef REMOTE_HOTLIST

      /* Load the hotlist */

      e1 = hotlist_load(choices.hotlist_path);
      if (e1) e2 = e1;

    #endif

    return e2;
  }

#endif