Logo Search packages:      
Sourcecode: gaim-librvp version File versions  Download package

getntlm.c

/*
 *  File: getntlm.c - Gaim RVP plug-in implementation.
 *  Copyright (C) 2003  Peter Fales (psfales@lucent.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * This file includes copies of base64.c from fetchmail by
 * Eric Raymond and md4.c from the Samba package by Andrew
 * Tridgell.  Further information below.
 */

/* crypt & co. */
#define _XOPEN_SOURCE
#include <unistd.h>

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

/*******************************************************************
base64.c from fetchmail - by Eric Raymond
*******************************************************************/

/*
 * base64.c -- base-64 conversion routines.
 *
 * For license terms, see the file COPYING in this directory.
 *
 * This base 64 encoding is defined in RFC2045 section 6.8,
 * "Base64 Content-Transfer-Encoding", but lines must not be broken in the
 * scheme used here.

*/
#include <ctype.h>

static const char base64digits[] =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

#define BAD -1
static const char base64val[] = {
  BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD,
  BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD,
  BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD, 62, BAD,BAD,BAD, 63,
  52, 53, 54, 55,  56, 57, 58, 59,  60, 61,BAD,BAD, BAD,BAD,BAD,BAD,
  BAD,  0,  1,  2,   3,  4,  5,  6,   7,  8,  9, 10,  11, 12, 13, 14,
  15, 16, 17, 18,  19, 20, 21, 22,  23, 24, 25,BAD, BAD,BAD,BAD,BAD,
  BAD, 26, 27, 28,  29, 30, 31, 32,  33, 34, 35, 36,  37, 38, 39, 40,
  41, 42, 43, 44,  45, 46, 47, 48,  49, 50, 51,BAD, BAD,BAD,BAD,BAD
};
#define DECODE64(c)  (isascii(c) ? base64val[c] : BAD)

/* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */
static void to64frombits(unsigned char *out, const unsigned char *in, int inlen) {
  for (; inlen >= 3; inlen -= 3) {
    *out++ = base64digits[in[0] >> 2];
    *out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)];
    *out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
    *out++ = base64digits[in[2] & 0x3f];
    in += 3;
  }
  if (inlen > 0) {
    unsigned char fragment;

    *out++ = base64digits[in[0] >> 2];
    fragment = (in[0] << 4) & 0x30;
    if (inlen > 1)
      fragment |= in[1] >> 4;
    *out++ = base64digits[fragment];
    *out++ = (inlen < 2) ? '=' : base64digits[(in[1] << 2) & 0x3c];
    *out++ = '=';
  }
  *out = '\0';
}

/* base 64 to raw bytes in quasi-big-endian order, returning count of bytes */
/* maxlen limits output buffer size, set to zero to ignore */
static int from64tobits(unsigned char *out, const unsigned char *in, int maxlen) {
  int len = 0;
  register unsigned char digit1, digit2, digit3, digit4;

  if (in[0] == '+' && in[1] == ' ')
    in += 2;
  if (*in == '\r')
    return(0);

  do {
    digit1 = in[0];
    if (DECODE64(digit1) == BAD)
      return(-1);
    digit2 = in[1];
    if (DECODE64(digit2) == BAD)
      return(-1);
    digit3 = in[2];
    if (digit3 != '=' && DECODE64(digit3) == BAD)
      return(-1);
    digit4 = in[3];
    if (digit4 != '=' && DECODE64(digit4) == BAD)
      return(-1);
    in += 4;
    ++len;
    if (maxlen && len > maxlen)
      return(-1);
    *out++ = (DECODE64(digit1) << 2) | (DECODE64(digit2) >> 4);
    if (digit3 != '=')
      {
        ++len;
        if (maxlen && len > maxlen)
          return(-1);
        *out++ = ((DECODE64(digit2) << 4) & 0xf0) | (DECODE64(digit3) >> 2);
        if (digit4 != '=')
          {
            ++len;
            if (maxlen && len > maxlen)
              return(-1);
            *out++ = ((DECODE64(digit3) << 6) & 0xc0) | DECODE64(digit4);
          }
      }
  } while
    (*in && *in != '\r' && digit4 != '=');

  return (len);
}

/* base64.c ends here */

/******************************************************************
md4.c - from the Samba package
******************************************************************/

/*
  Unix SMB/Netbios implementation.
  Version 1.9.
  a implementation of MD4 designed for use in the SMB authentication protocol
  Copyright (C) Andrew Tridgell 1997

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/


/* NOTE: This code makes no attempt to be fast!

It assumes that a int is at least 32 bits long
*/

typedef unsigned int uint32;

static uint32 A, B, C, D;

static uint32 F(uint32 X, uint32 Y, uint32 Z)
{
  return (X&Y) | ((~X)&Z);
}

static uint32 G(uint32 X, uint32 Y, uint32 Z)
{
  return (X&Y) | (X&Z) | (Y&Z);
}

static uint32 H(uint32 X, uint32 Y, uint32 Z)
{
  return X^Y^Z;
}

static uint32 lshift(uint32 x, int s)
{
  x &= 0xFFFFFFFF;
  return ((x<<s)&0xFFFFFFFF) | (x>>(32-s));
}

#define ROUND1(a,b,c,d,k,s) a = lshift(a + F(b,c,d) + X[k], s)
#define ROUND2(a,b,c,d,k,s) a = lshift(a + G(b,c,d) + X[k] + (uint32)0x5A827999,s)
#define ROUND3(a,b,c,d,k,s) a = lshift(a + H(b,c,d) + X[k] + (uint32)0x6ED9EBA1,s)

/* this applies md4 to 64 byte chunks */
static void mdfour64(uint32 *M)
{
  int j;
  uint32 AA, BB, CC, DD;
  uint32 X[16];

  for (j=0;j<16;j++)
    X[j] = M[j];

  AA = A; BB = B; CC = C; DD = D;

  ROUND1(A,B,C,D,  0,  3);  ROUND1(D,A,B,C,  1,  7);
  ROUND1(C,D,A,B,  2, 11);  ROUND1(B,C,D,A,  3, 19);
  ROUND1(A,B,C,D,  4,  3);  ROUND1(D,A,B,C,  5,  7);
  ROUND1(C,D,A,B,  6, 11);  ROUND1(B,C,D,A,  7, 19);
  ROUND1(A,B,C,D,  8,  3);  ROUND1(D,A,B,C,  9,  7);
  ROUND1(C,D,A,B, 10, 11);  ROUND1(B,C,D,A, 11, 19);
  ROUND1(A,B,C,D, 12,  3);  ROUND1(D,A,B,C, 13,  7);
  ROUND1(C,D,A,B, 14, 11);  ROUND1(B,C,D,A, 15, 19);

  ROUND2(A,B,C,D,  0,  3);  ROUND2(D,A,B,C,  4,  5);
  ROUND2(C,D,A,B,  8,  9);  ROUND2(B,C,D,A, 12, 13);
  ROUND2(A,B,C,D,  1,  3);  ROUND2(D,A,B,C,  5,  5);
  ROUND2(C,D,A,B,  9,  9);  ROUND2(B,C,D,A, 13, 13);
  ROUND2(A,B,C,D,  2,  3);  ROUND2(D,A,B,C,  6,  5);
  ROUND2(C,D,A,B, 10,  9);  ROUND2(B,C,D,A, 14, 13);
  ROUND2(A,B,C,D,  3,  3);  ROUND2(D,A,B,C,  7,  5);
  ROUND2(C,D,A,B, 11,  9);  ROUND2(B,C,D,A, 15, 13);

  ROUND3(A,B,C,D,  0,  3);  ROUND3(D,A,B,C,  8,  9);
  ROUND3(C,D,A,B,  4, 11);  ROUND3(B,C,D,A, 12, 15);
  ROUND3(A,B,C,D,  2,  3);  ROUND3(D,A,B,C, 10,  9);
  ROUND3(C,D,A,B,  6, 11);  ROUND3(B,C,D,A, 14, 15);
  ROUND3(A,B,C,D,  1,  3);  ROUND3(D,A,B,C,  9,  9);
  ROUND3(C,D,A,B,  5, 11);  ROUND3(B,C,D,A, 13, 15);
  ROUND3(A,B,C,D,  3,  3);  ROUND3(D,A,B,C, 11,  9);
  ROUND3(C,D,A,B,  7, 11);  ROUND3(B,C,D,A, 15, 15);

  A += AA; B += BB; C += CC; D += DD;

  A &= 0xFFFFFFFF; B &= 0xFFFFFFFF;
  C &= 0xFFFFFFFF; D &= 0xFFFFFFFF;

  for (j=0;j<16;j++)
    X[j] = 0;
}

static void copy64(uint32 *M, unsigned char *in)
{
  int i;

  for (i=0;i<16;i++)
    M[i] = (in[i*4+3]<<24) | (in[i*4+2]<<16) |
      (in[i*4+1]<<8) | (in[i*4+0]<<0);
}

static void copy4(unsigned char *out,uint32 x)
{
  out[0] = x&0xFF;
  out[1] = (x>>8)&0xFF;
  out[2] = (x>>16)&0xFF;
  out[3] = (x>>24)&0xFF;
}

/* produce a md4 message digest from data of length n bytes */
static void mdfour(unsigned char *out, unsigned char *in, int n) {
  unsigned char buf[128];
  uint32 M[16];
  uint32 b = n * 8;
  int i;

  A = 0x67452301;
  B = 0xefcdab89;
  C = 0x98badcfe;
  D = 0x10325476;

  while (n > 64) {
    copy64(M, in);
    mdfour64(M);
    in += 64;
    n -= 64;
  }

  for (i=0;i<128;i++)
    buf[i] = 0;
  memcpy(buf, in, n);
  buf[n] = 0x80;

  if (n <= 55) {
    copy4(buf+56, b);
    copy64(M, buf);
    mdfour64(M);
  } else {
    copy4(buf+120, b);
    copy64(M, buf);
    mdfour64(M);
    copy64(M, buf+64);
    mdfour64(M);
  }

  for (i=0;i<128;i++)
    buf[i] = 0;
  copy64(M, buf);

  copy4(out, A);
  copy4(out+4, B);
  copy4(out+8, C);
  copy4(out+12, D);

  A = B = C = D = 0;
}



/******************************************************************/


/*
 * des_enc
 *  encode an 8-byte data block using the 8-byte key using DES.
 *  It converts the data and key to 64 byte arrays of zeroes and
 *  ones, the format required by setkey() and encrypt()
 */

static void des_enc(char *key, unsigned char *data) {
  char key_bits[64];
  char data_bits[64];
  int byte, bit;
  int i;

  i=0;
  for ( byte=0 ; byte<8 ; byte++ ) {
    for ( bit = 7 ; bit >= 0 ; bit-- ) {
      key_bits[i] = (key[byte] >> bit)&1;
      data_bits[i] = (data[byte] >> bit)&1;
      i++;
    }
  }
  setkey(key_bits);
  encrypt(data_bits,0);

  i=0;
  for ( byte=0 ; byte<8 ; byte++ ) {
    data[byte]=0;
    for ( bit = 7 ; bit >= 0 ; bit-- ) {
      data[byte] |= (data_bits[i]<<bit);
      i++;
    }
  }
}

/*
 * set_odd_parity
 *
 *  DES requires that every 8th bit is a parity bit.  By
 *  convention, bit 1 is the MSB of the first byte, so bit
 *  8 is the LSB of the first byte.  This routine counts
 *  the bits in the input byte and sets the LSB as appropriate
 *  for odd parity
 */

static char
set_odd_parity(char in)
{
  int i,count;
  count=0;
  for ( i=0 ; i< 8 ; i++ )
    if ( (in>>i)&1 )
      count++;
  if ( (count % 2) == 0 )
    in ^= 0x01;
  return in;
}

/*
 *  convert_key
 *
 *  DES requires a 56 bit key with parity bits every 8 bits
 *  for a total of 64 bits.  This converts a 7 byte (56 bit) key to
 *  64 bits with appropriate parity insertion.
 */

static void convert_key(unsigned char *in_key, char *out_key)
{
  out_key[0] = set_odd_parity( ((in_key[0])) );
  out_key[1] = set_odd_parity( ((in_key[0]<<7)&0xff) | (in_key[1]>>1 )   );
  out_key[2] = set_odd_parity( ((in_key[1]<<6)&0xff) | (in_key[2]>>2 )   );
  out_key[3] = set_odd_parity( ((in_key[2]<<5)&0xff) | (in_key[3]>>3 )   );
  out_key[4] = set_odd_parity( ((in_key[3]<<4)&0xff) | (in_key[4]>>4 )   );
  out_key[5] = set_odd_parity( ((in_key[4]<<3)&0xff) | (in_key[5]>>5 )   );
  out_key[6] = set_odd_parity( ((in_key[5]<<2)&0xff) | (in_key[6]>>6 )   );
  out_key[7] = set_odd_parity( ((in_key[6]<<1)&0xff) );

}

/*
 * calc_resp
 *
 *  Based on calc_resp from Authen::Perl::NTLM
 */

static unsigned char * calc_resp(unsigned char *key, char* challenge, unsigned char *resp) {
  char out_key[8];

  convert_key(key, out_key);
  memcpy(resp,challenge,8);
  des_enc(out_key, resp);

  convert_key(key+7, out_key);
  memcpy(resp+8,challenge,8);
  des_enc(out_key, resp+8);

  convert_key(key+14, out_key);
  memcpy(resp+16,challenge,8);
  des_enc(out_key, resp+16);

  resp[24]=0;  /* Just in case we want to try printing it */

  return resp;
}

char lm_magic[] = { 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };
unsigned char buffer[1024];

/*
 * get_auth_ntlm - calculates the response to an NTLM authentication
 *  challenge using the supplied username, password, host name,
 *  and domain.   It is derived from the Perl code in the
 *  Auth:NTLM module.
 */
unsigned char *get_auth_ntlm (unsigned char *www_authenticate, char *username,
                              char *passwd, char *hostname, char *domain,
                              char *authid) {
  char challenge[8];
  unsigned char auth[1024];
  unsigned char nt_pw[128];
  unsigned char nt_hpw[22];
  unsigned char lm_hpw[22];
  int i;
  char lm_key[8];
  unsigned char nt_resp[25];
  unsigned char lm_resp[25];
  unsigned char lm_pw[17];
  char dom[64];
  char user[64];
  char host[64];

  int lm_resp_len, lm_resp_off, nt_resp_len, nt_resp_off;
  int dom_len, user_len, user_off, host_len, host_off;
  int msg_len;

  memset( buffer, 0, 1024 );

  /* Challenge must begin with "NTLM" */
  if ( memcmp(www_authenticate,"NTLM ",5) != 0 ) {
    printf("Invalid NTLM string\n");
    return NULL;
  }

  from64tobits(buffer, www_authenticate+5, 0);
  memcpy(challenge,&buffer[24],8);

  /* Hack to Convert passwd to unicode in nt_pw */
  for (i=0 ; i<strlen(passwd) ; i++ )
    {
      nt_pw[i*2]=passwd[i];
      nt_pw[i*2+1]=0;
    }

  mdfour(nt_hpw, nt_pw, strlen(passwd)*2);
  /* Append 5 null bytes */
  memset( nt_hpw + 16, 0, 5 );

  calc_resp(nt_hpw,challenge,nt_resp);

  /* Pad passwd out to 14 chars, and convert to upper case */
  memset( lm_pw, 0, 14) ;
  for ( i=0 ; i<14 && i < strlen(passwd); i++ )
    {
      lm_pw[i] = toupper(passwd[i]);
    }

  convert_key(lm_pw, lm_key);
  memcpy(lm_hpw,lm_magic,8);
  des_enc(lm_key, lm_hpw);

  convert_key(lm_pw+7, lm_key);
  memcpy(lm_hpw+8,lm_magic,8);

  des_enc(lm_key, lm_hpw+8);

  /* Append 5 null bytes */
  memset( lm_hpw + 16, 0, 5 );

  calc_resp(lm_hpw,challenge,lm_resp);

  /* Convert domain to upper case and unicode */
  for ( i=0 ; i < strlen(domain) ; i++ ) {
    dom[i*2] = toupper(domain[i]);
    dom[i*2+1] = 0;
  }
  dom_len = i*2;

  /* Convert authid to unicode */
  /* If an authid was passed in use that instead of username */
  if (authid  && authid[0] )
    {
      for ( i=0 ; i < strlen(authid) ; i++ ) {
        user[i*2] = authid[i];
        user[i*2+1] = 0;
      }
      user_len = i*2;
    } else {
    /* Convert username to unicode */
    for ( i=0 ; i < strlen(username) ; i++ ) {
      if ( username[i] == '@' ) {
        break;
      }
      user[i*2] = username[i];
      user[i*2+1] = 0;
    }
    user_len = i*2;
  }


  /* Convert hostname to upper case and unicode */
  for ( i=0 ; i < strlen(hostname) ; i++ ) {
    host[i*2] = toupper(hostname[i]);
    host[i*2+1] = 0;
  }
  host_len = i*2;


  memcpy( auth,"NTLMSSP\0", 8 ); /* protocol */
  i=8;
  auth[i++] = 0x03;   /* type */
  auth[i++] = 0;
  auth[i++] = 0;
  auth[i++] = 0;
  lm_resp_len = 24;
  auth[i++] = lm_resp_len;
  auth[i++] = 0;
  auth[i++] = lm_resp_len;
  auth[i++] = 0;
  lm_resp_off = 64+dom_len+user_len+host_len;
  auth[i++] = lm_resp_off&0xff;
  auth[i++] = lm_resp_off >> 8;
  auth[i++] = 0;
  auth[i++] = 0;
  nt_resp_off = 64+dom_len+user_len+host_len+lm_resp_len;
  nt_resp_len = 24;
  auth[i++] = nt_resp_len;
  auth[i++] = 0;
  auth[i++] = nt_resp_len;
  auth[i++] = 0;
  auth[i++] = nt_resp_off&0xff;
  auth[i++] = nt_resp_off >> 8;
  auth[i++] = 0;
  auth[i++] = 0;

  auth[i++] = dom_len;
  auth[i++] = 0;
  auth[i++] = dom_len;
  auth[i++] = 0;
  auth[i++] = 64; /* dom_off */
  auth[i++] = 0;
  auth[i++] = 0;
  auth[i++] = 0;

  auth[i++] = user_len;
  auth[i++] = 0;
  auth[i++] = user_len;
  auth[i++] = 0;
  user_off = 64+dom_len;
  auth[i++] = user_off & 0xff;
  auth[i++] = user_off >> 8;
  auth[i++] = 0;
  auth[i++] = 0;

  auth[i++] = host_len;
  auth[i++] = 0;
  auth[i++] = host_len;
  auth[i++] = 0;
  host_off = 64+dom_len+user_len;
  auth[i++] = host_off & 0xff;
  auth[i++] = host_off >> 8;
  auth[i++] = 0;
  auth[i++] = 0;

  auth[i++] = 0;
  auth[i++] = 0;
  auth[i++] = 0;
  auth[i++] = 0;

  msg_len = 64+dom_len+user_len+host_len+lm_resp_len+nt_resp_len;
  auth[i++] = msg_len & 0xff;
  auth[i++] = msg_len >> 8;
  auth[i++] = 0;
  auth[i++] = 0;

  /* This is supposed to be "flags".  It was originally coded as
   *
   * auth[i++] = 0x82;
   * auth[i++] = 0x01;
   *
   * Corresponding to a value of 0x0182.  0x8205 would make
   * more sense, as NTLM.pm uses
   *
   *     $flags = Authen::NTLM::NTLMSSP_NEGOTIATE_ALWAYS_SIGN
   *    | Authen::NTLM::NTLMSSP_NEGOTIATE_NTLM
   *    | Authen::NTLM::NTLMSSP_NEGOTIATE_UNICODE
   *    | Authen::NTLM::NTLMSSP_REQUEST_TARGET;
   *
   *
   * which is:
   *
   *        0x8000 | 0x0200 | 0x0001 | 0x0004
   *
   * but, it turns out that the value seems to be ignored and
   * even zero works.  What gives??
   *
   * I guess we'll stick with zero for now...
   */
  auth[i++] = 0x00;
  auth[i++] = 0x00;
  auth[i++] = 0;
  auth[i++] = 0;

  memcpy(&auth[i],dom,dom_len);
  i += dom_len;
  memcpy(&auth[i],user,user_len);
  i += user_len;
  memcpy(&auth[i],host,host_len);
  i += host_len;
  memcpy(&auth[i],lm_resp,24);
  i += 24;
  memcpy(&auth[i],nt_resp,24);
  i += 24;

  memcpy(buffer,"NTLM ", 5);

  to64frombits(buffer+5, auth, i);

  return buffer;
}

struct _msg1 {
  gchar signature[8];
  guint32 type;
  guint32 flags;
  guint16 domlen1;
  guint16 domlen2;
  guint32 domptr;
  guint16 hostlen1;
  guint16 hostlen2;
  guint32 hostptr;

  guint32 bits;
  guint32 bits2;
} msg1;

unsigned char *get_ntlm_msg1( char *domain, char *host ) {
  struct _msg1 msg;
  gchar *blob;
  gint hostlen = strlen( host );
  gint domlen = strlen( domain );

  memcpy( msg.signature, "NTLMSSP\0", 8 );
  msg.type = GUINT32_TO_LE( 1 );
  msg.flags = GUINT32_TO_LE( 0x0200 /* ntlm */ |
                             0x0004 /* return realm */|
                             0x0002 /* oem */ |
                             0x0001 /* unicode */ );
  msg.domlen1 = GUINT16_TO_LE( domlen );
  msg.domlen2 = GUINT16_TO_LE( domlen );
  msg.domptr = GUINT32_TO_LE( 0x28 + hostlen );
  msg.hostlen1 = GUINT16_TO_LE( hostlen );
  msg.hostlen2 = GUINT16_TO_LE( hostlen );
  msg.hostptr = GUINT32_TO_LE( 0x28 );

  /* I have no idea what this data is. Neither, apparently, does
     Ethereal... */
  msg.bits =  GUINT32_TO_LE( 0x050009308 );
  msg.bits2 = GUINT32_TO_LE( 0x00000000f );

  blob = g_malloc0( sizeof( struct _msg1 ) + hostlen + domlen );
  memcpy( blob, (unsigned char *)&msg, sizeof( struct _msg1 ));
  memcpy( &blob[sizeof( struct _msg1 )], host, hostlen );
  memcpy( &blob[sizeof( struct _msg1 ) + hostlen], domain, domlen );

  memcpy( buffer, "NTLM ", 5 );
  to64frombits( buffer + 5, (unsigned char *)blob, sizeof( struct _msg1 ) +
                hostlen + domlen );
  g_free( blob );

  return buffer;
}

Generated by  Doxygen 1.6.0   Back to index