/*
 * Copyright (c) 1997,1998 Stanford University
 *
 * The use of this software for revenue-generating purposes may require a
 * license from the owners of the underlying intellectual property.
 *
 * Within that constraint, permission to use, copy, modify, and distribute
 * this software and its documentation for any purpose is hereby granted
 * without fee, provided that the above copyright notices and this permission
 * notice appear in all copies of the software and related documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
 * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifdef SRP
#include <sys/types.h>
#include <arpa/telnet.h>
#include <stdio.h>
#include <pwd.h>

#ifdef	__STDC__
#include <stdlib.h>
#endif
#ifdef	NO_STRING_H
#include <strings.h>
#else
#include <string.h>
#endif

#include "encrypt.h"
#include "auth.h"
#include "misc.h"

#include "t_pwd.h"
#include "t_server.h"
#include "t_client.h"

extern auth_debug_mode;

static unsigned char str_data[1024] = { IAC, SB, TELOPT_AUTHENTICATION, 0,
			  		AUTHTYPE_SRP, };
static unsigned char str_name[1024] = { IAC, SB, TELOPT_AUTHENTICATION,
					TELQUAL_NAME, };

static struct t_server * ts = NULL;
static struct t_client * tc = NULL;
/*
static struct t_pw * tpw = NULL;
static struct t_conf * tconf = NULL;
*/

static int waitresponse = 0;	/* Flag to indicate readiness for response */
static struct t_num * B;	/* Holder for B */

#define PWD_SZ 128

static char user_passwd[PWD_SZ];

#define	SRP_AUTH	0		/* Authentication data follows */
#define	SRP_REJECT	1		/* Rejected (reason might follow) */
#define	SRP_ACCEPT	2		/* Accepted */
#define SRP_CHALLENGE	3
#define SRP_RESPONSE	4

#define SRP_EXP		8
#define SRP_PARAMS	9

static int
Data(ap, type, d, c)
	Authenticator *ap;
	int type;
	void *d;
	int c;
{
	unsigned char *p = str_data + 4;
	unsigned char *cd = (unsigned char *)d;

	if (c == -1)
		c = strlen((char *)cd);

	if (auth_debug_mode) {
		printf("%s:%d: [%d] (%d)",
			str_data[3] == TELQUAL_IS ? ">>>IS" : ">>>REPLY",
			str_data[3],
			type, c);
		printd(d, c);
		printf("\r\n");
	}
	*p++ = ap->type;
	*p++ = ap->way;
	*p++ = type;
	while (c-- > 0) {
		if ((*p++ = *cd++) == IAC)
			*p++ = IAC;
	}
	*p++ = IAC;
	*p++ = SE;
	if (str_data[3] == TELQUAL_IS)
		printsub('>', &str_data[2], p - (&str_data[2]));
	return(net_write(str_data, p - str_data));
}

int
srp_init(ap, server)
	Authenticator *ap;
	int server;
{
  if (server) {
    str_data[3] = TELQUAL_REPLY;
/*
    if((tpw = t_openpw(NULL)) == NULL)
      return 0;
*/
  }
  else {
    str_data[3] = TELQUAL_IS;
  }
  waitresponse = 0;
  return 1;
}

int
srp_send(ap)
     Authenticator *ap;
{
  printf("[ Trying SRP ... ]\n");
  if (!UserNameRequested) {
    if (auth_debug_mode) {
      printf("SRP: no user name supplied\r\n");
    }
    return(0);
  }
  if (!auth_sendname(UserNameRequested, strlen(UserNameRequested))) {
    if (auth_debug_mode)
      printf("Not enough room for user name\r\n");
    return(0);
  }
  if (!Data(ap, SRP_AUTH, (void *)NULL, 0)) {
    return(0);
  }
  return(1);
}

void
encode_length(data, num)
     unsigned char * data;
     int num;
{
  *data = (num >> 8) & 0xff;
  *++data = num & 0xff;
}

int
decode_length(data)
     unsigned char * data;
{
  return (((int) *data & 0xff) << 8) | (*(data + 1) & 0xff);
}

void
srp_is(ap, data, cnt)
	Authenticator *ap;
	unsigned char *data;
	int cnt;
{
  char pbuf[2 * MAXPARAMLEN + 5];
  char * ptr;
  struct t_num A;
  char hexbuf[MAXHEXPARAMLEN];
  struct passwd * pass;
/*
  FILE * passfp = NULL;
  FILE * conffp = NULL;
*/
#ifdef	ENCRYPTION
  Session_Key skey;
#endif

  if(cnt-- < 1)
    return;
  switch(*data++) {
  case SRP_AUTH:
    /* Send parameters back to client */
    if(ts != NULL) {
      t_serverclose(ts);
      ts = NULL;
    }
    if(!UserNameRequested) {
      if (auth_debug_mode)
	printf("No username available\r\n");
      Data(ap, SRP_REJECT, (void *) "No username supplied", -1);
      break;
    }
/*
    if(tpw == NULL) {
      if((tpw = t_openpw(NULL)) == NULL) {
	if (auth_debug_mode)
	  printf("Unable to open password file\r\n");
	Data(ap, SRP_REJECT, (void *) "No password file", -1);
	break;
      }
    }
    if(tconf == NULL) {
      if((tconf = t_openconf(NULL)) == NULL) {
	if (auth_debug_mode)
	  printf("Unable to open configuration file\r\n");
	Data(ap, SRP_REJECT, (void *) "No configuration file", -1);
	break;
      }
    }
    ts = t_serveropen(UserNameRequested, tpw, tconf);
*/
    ts = t_serveropen(UserNameRequested);
/*
    t_closepw(tpw);
    if(passfp)
      fclose(passfp);
    tpw = NULL;
    t_closeconf(tconf);
    if(conffp)
      fclose(conffp);
    tconf = NULL;
*/

    if(ts == NULL) {
      if (auth_debug_mode)
	printf("User %s not found\r\n", UserNameRequested);
      Data(ap, SRP_REJECT, (void *) "Password not set", -1);
      break;
    }
    ptr = pbuf;

    encode_length(ptr, ts->n.len);
    ptr += 2;
    memcpy(ptr, ts->n.data, ts->n.len);
    ptr += ts->n.len;

    encode_length(ptr, ts->g.len);
    ptr += 2;
    memcpy(ptr, ts->g.data, ts->g.len);
    ptr += ts->g.len;

    encode_length(ptr, ts->s.len);
    ptr += 2;
    memcpy(ptr, ts->s.data, ts->s.len);
    ptr += ts->s.len;

    Data(ap, SRP_PARAMS, pbuf, ptr - pbuf);

    B = t_servergenexp(ts);

    break;

  case SRP_EXP:
    /* Client is sending A to us.  Compute challenge and expected response. */
    if(ts == NULL || B == NULL) {
      if (auth_debug_mode)
	printf("Protocol error: SRP_EXP unexpected\r\n");
      Data(ap, SRP_REJECT, (void *) "Protocol error: unexpected EXP", -1);
      break;
    }

    /* Wait until now to send B, since it contains the key to "u" */
    Data(ap, SRP_CHALLENGE, B->data, B->len);
    B = NULL;
 
    A.data = data;
    A.len = cnt;
    ptr = t_servergetkey(ts, &A);

    if(ptr == NULL) {
      if (auth_debug_mode)
	printf("Security alert: Trivial session key attempted\r\n");
      Data(ap, SRP_REJECT, (void *) "Trivial session key detected", -1);
      break;
    }

    waitresponse = 1;
    break;

  case SRP_RESPONSE:
    /* Got the response; see if it's correct */
    if(ts == NULL || !waitresponse) {
      if (auth_debug_mode)
	printf("Protocol error: SRP_RESPONSE unexpected\r\n");
      Data(ap, SRP_REJECT, (void *) "Protocol error: unexpected RESPONSE", -1);
      break;
    }

    if(cnt < RESPONSE_LEN) {
      if (auth_debug_mode)
	printf("Protocol error: malformed response\r\n");
      Data(ap, SRP_REJECT, (void *) "Protocol error: malformed response", -1);
      break;
    }

    if(t_serververify(ts, data) == 0) {
      Data(ap, SRP_ACCEPT, t_serverresponse(ts), RESPONSE_LEN);

#ifdef	ENCRYPTION
      skey.type = SK_GENERIC;
      skey.length = 40;
      skey.data = ts->session_key;
      encrypt_session_key(&skey, 1);
#endif

      auth_finished(ap, AUTH_VALID);
    }
    else {
      Data(ap, SRP_REJECT, (void *) "Login incorrect", -1);
      auth_finished(ap, AUTH_REJECT);
    }

    break;

  default:
    if (auth_debug_mode)
      printf("Unknown SRP option %d\r\n", data[-1]);
    Data(ap, SRP_REJECT, (void *) "Unknown option received", -1);
    break;
  }
}

void
srp_reply(ap, data, cnt)
	Authenticator *ap;
	unsigned char *data;
	int cnt;
{
  struct t_num n;
  struct t_num g;
  struct t_num s;

  struct t_num B;
  struct t_num * A;

  char hexbuf[MAXHEXPARAMLEN];
  int pflag;
#ifdef	ENCRYPTION
  Session_Key skey;
#endif

  if(cnt-- < 1)
    return;
  switch(*data++) {
  case SRP_REJECT:
    if (cnt > 0) {
      printf("[ SRP refuses authentication for '%s' (%.*s) ]\r\n",
	     UserNameRequested, cnt, data);
    } else
      printf("[ SRP refuses authentication for '%s' ]\r\n", UserNameRequested);
    auth_send_retry();
    break;
  case SRP_ACCEPT:
    if(tc == NULL || cnt < RESPONSE_LEN || !waitresponse) {
      if (auth_debug_mode)
	printf("Protocol error\r\n");
      break;
    }

    if(t_clientverify(tc, data) == 0) {
      printf("[ SRP authentication successful ]\n");

#ifdef	ENCRYPTION
      skey.type = SK_GENERIC;
      skey.length = SESSION_KEY_LEN;
      skey.data = tc->session_key;
      encrypt_session_key(&skey, 0);
#endif
    }
    else
      printf("[ Warning: SRP server authentication failed ]\n");
    break;
  case SRP_PARAMS:
    if(!UserNameRequested) {
      if (auth_debug_mode)
	printf("No username available\r\n");
      break;
    }

    n.len = decode_length(data);
    data += 2;
    cnt -= 2;
    if(n.len > cnt) {
      if (auth_debug_mode)
	printf("n too long\r\n");
      break;
    }
    n.data = data;
    data += n.len;
    cnt -= n.len;

    printf("[ Using %d-bit modulus for '%s' ]\n", 8 * n.len, UserNameRequested);

    g.len = decode_length(data);
    data += 2;
    cnt -= 2;
    if(g.len > cnt) {
      if (auth_debug_mode)
	printf("g too long\r\n");
      break;
    }
    g.data = data;
    data += g.len;
    cnt -= g.len;

    s.len = decode_length(data);
    data += 2;
    cnt -= 2;
    if(s.len > cnt) {
      if (auth_debug_mode)
	printf("salt too long\r\n");
      break;
    }
    s.data = data;
    data += s.len;
    cnt -= s.len;

    tc = t_clientopen(UserNameRequested, &n, &g, &s);

    A = t_clientgenexp(tc);

    Data(ap, SRP_EXP, A->data, A->len);

    local_des_read_pw_string(user_passwd, sizeof(user_passwd) - 1, "SRP Password: ", 0);
    t_clientpasswd(tc, user_passwd);
    memset(user_passwd, 0, sizeof(user_passwd));

    break;

  case SRP_CHALLENGE:
    if(tc == NULL) {
      if (auth_debug_mode)
	printf("Protocol error\r\n");
      break;
    }
      
    B.data = data;
    B.len = cnt;
    t_clientgetkey(tc, &B);

    Data(ap, SRP_RESPONSE, t_clientresponse(tc), RESPONSE_LEN);
    waitresponse = 1;

    break;

  default:
    if(auth_debug_mode)
      printf("Unknown reply option\n");
    break;
  }
}

int
srp_status(ap, name, level)
	Authenticator *ap;
	char *name;
	int level;
{
	if (level < AUTH_USER)
	  return(level);

	if (UserNameRequested) {
	  strcpy(name, UserNameRequested);
	  return AUTH_VALID;
	}
}

#define	BUMP(buf, len)		while (*(buf)) {++(buf), --(len);}
#define	ADDC(buf, len, c)	if ((len) > 0) {*(buf)++ = (c); --(len);}

	void
srp_printsub(data, cnt, buf, buflen)
	unsigned char *data, *buf;
	int cnt, buflen;
{
	char lbuf[32];
	register int i;

	buf[buflen-1] = '\0';		/* make sure its NULL terminated */
	buflen -= 1;

	switch(data[3]) {
	case SRP_REJECT:		/* Rejected (reason might follow) */
		strncpy((char *)buf, " REJECT ", buflen);

	common:
		BUMP(buf, buflen);
		if (cnt <= 4)
			break;
		ADDC(buf, buflen, '"');
		for (i = 4; i < cnt; i++)
			ADDC(buf, buflen, data[i]);
		ADDC(buf, buflen, '"');
		ADDC(buf, buflen, '\0');
		break;

	case SRP_ACCEPT:		/* Accepted (data might follow) */
		strncpy((char *)buf, " ACCEPT", buflen);
		goto common2;

	case SRP_AUTH:			/* Authentication data follows */
		strncpy((char *)buf, " AUTH", buflen);
		goto common2;

	case SRP_CHALLENGE:
		strncpy((char *)buf, " CHALLENGE", buflen);
		goto common2;

	case SRP_RESPONSE:
		strncpy((char *)buf, " RESPONSE", buflen);
		goto common2;

	case SRP_PARAMS:
		strncpy((char *)buf, " PARAMS", buflen);
		goto common2;

	case SRP_EXP:
		strncpy((char *)buf, " EXP", buflen);
		goto common2;

	default:
		sprintf(lbuf, " %d (unknown)", data[3]);
		strncpy((char *)buf, lbuf, buflen);
	common2:
		BUMP(buf, buflen);
		for (i = 4; i < cnt; i++) {
			sprintf(lbuf, " %d", data[i]);
			strncpy((char *)buf, lbuf, buflen);
			BUMP(buf, buflen);
		}
		break;
	}
}

#endif
