/*
 * Copyright (c) 1990,1993 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/syslog.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netatalk/endian.h>
#include <netatalk/at.h>
#include <atalk/afp.h>
#include <atalk/paths.h>
#include <atalk/dsi.h>
#include <atalk/atp.h>
#include <atalk/asp.h>
#include <atalk/compat.h>
#include <atalk/util.h>
#include <limits.h>
#include <string.h>
#include <ctype.h>
#include <pwd.h>

#ifdef SOLARIS
#define SHADOWPW
#endif SOLARIS

#ifdef SHADOWPW
#include <shadow.h>
#endif SHADOWPW

#ifndef USE_PAM
extern char *crypt();
#endif

#include "auth.h"
#include "globals.h"
#include "status.h"
#include "switch.h"

#define PASSWDLEN  8

#ifdef USE_PAM
/* Static variables used to communicate between the conversation function
 * and the server_login function
 */
static char *PAM_username;
static char *PAM_password;
static int PAM_error = 0;
/* PAM conversation function
 * Here we assume (for now, at least) that echo on means login name, and
 * echo off means password.
 */
static int PAM_conv (int num_msg,
                     const struct pam_message **msg,
                     struct pam_response **resp,
                     void *appdata_ptr) {
  int count = 0;
  struct pam_response *reply;
  
#define COPY_STRING(s) (s) ? strdup(s) : NULL
  
  if (num_msg < 1)
    return PAM_CONV_ERR;

  reply = (struct pam_response *) 
    calloc(num_msg, sizeof(struct pam_response));

  if (!reply)
    return PAM_CONV_ERR;

  for (count = 0; count < num_msg; count++) {
    char *string = NULL;

    switch (msg[count]->msg_style) {
    case PAM_PROMPT_ECHO_ON:
      if (!(string = COPY_STRING(PAM_username)))
	goto pam_fail_conv;
      break;
    case PAM_PROMPT_ECHO_OFF:
      if (!(string = COPY_STRING(PAM_password)))
	goto pam_fail_conv;
      break;
    case PAM_TEXT_INFO:
      /* ignore it... */
      break;
    case PAM_ERROR_MSG:
    default:
      goto pam_fail_conv;
    }

    if (string) {  
      reply[count].resp_retcode = 0;
      reply[count].resp = string;
      string = NULL;
    }
  }

  *resp = reply;
  return PAM_SUCCESS;

pam_fail_conv:
  for (count = 0; count < num_msg; count++) {
    switch (msg[count]->msg_style) {
    case PAM_PROMPT_ECHO_ON:
    case PAM_PROMPT_ECHO_OFF:
      if (reply[count].resp) {
	memset(reply[count].resp, 0, PASSWDLEN);
	free(reply[count].resp);
      }
      break;      
    }
    reply[count].resp = NULL;
  }
  free(reply);
  return PAM_CONV_ERR;
}

static struct pam_conv PAM_conversation = {
  &PAM_conv,
  NULL
};

pam_handle_t *pamh = NULL; /* We need this global so we can do the pam_end()
			      elsewhere (afp_dsi.c and afp_asp.c) */
#define PAM_BAIL(obj, pam) if (PAM_error != PAM_SUCCESS) { \
      pam_end((pam), PAM_error); (pam) = NULL; \
      return send_reply(obj, AFPERR_NOTAUTH); \
      }

#endif /* USE_PAM */


#if defined( KRB ) || defined( AFS ) || defined( UAM_AFSKRB )
#include <netinet/in.h>
#endif KRB AFS UAM_AFSKRB

#if defined( KRB ) || defined( UAM_AFSKRB ) || defined( UAM_RNDNUM )
#include <des.h>

C_Block			seskey;
Key_schedule		seskeysched;
#endif

#if defined( KRB ) || defined( UAM_AFSKRB )
#include <krb.h>
#include <prot.h>
static char		realm[ REALM_SZ ];
#endif KRB UAM_AFSKRB

#ifdef UAM_AFSKRB
static int		validseskey = 0;
static int		logged = 0;
static char		*tktfile;
static char		instance[ INST_SZ ], name[ ANAME_SZ ];
#endif UAM_AFSKRB

#ifdef AFS
#include <afs/stds.h>
#include <rx/rxkad.h>
#include <afs/afs.h>
#include <afs/venus.h>
#include <afs/afsint.h>

char *ka_LocalCell();

struct ClearToken {
    int32_t AuthHandle;
    char HandShakeKey[8];
    int32_t ViceId;
    int32_t BeginTimestamp;
    int32_t EndTimestamp;
};
#endif AFS

int	afp_version = 11;
uid_t	uuid;
#if defined( __svr4__ ) && !defined( NGROUPS )
#define NGROUPS NGROUPS_MAX
#endif __svr4__ NGROUPS
#if defined( sun ) && !defined( __svr4__ ) || defined( ultrix )
int	groups[ NGROUPS ];
#else sun __svr4__ ultrix
#if defined( __svr4__ ) && !defined( NGROUPS )
#define NGROUPS	NGROUPS_MAX
#endif __svr4__ NGROUPS
gid_t	groups[ NGROUPS ];
#endif sun ultrix
int	ngroups;
char	*username = NULL;
char	*mktemp();

static char	clrtxtname[ MACFILELEN + 1 ];

/*
 * These numbers are scattered throughout the code.
 */
struct afp_versions	afp_versions[] = {
    { "AFPVersion 1.1",	11 },
    { "AFPVersion 2.0",	20 },
    { "AFPVersion 2.1",	21 },
    { "AFP2.2",	22 }
};

/* NOTE: the login process is susceptible to a denial-of-service
 *       attack because the client can potentially not disconnect
 *       on a failed login attempt. to prevent this from happening,
 *       we need to close the connection on errors. */
struct afp_uams		afp_uams[] = {
#ifdef KRB
    { "Kerberos IV", AU_KRBIV, krb4_login, krb4_logincont, 0 },
#endif KRB
    { "Cleartxt Passwrd", AU_CLRTXT, clrtxt_login, NULL, 0 },
#ifdef UAM_AFSKRB
    { "AFS Kerberos", AU_AFSKRB, afskrb_login, afskrb_logincont, 0 },
#endif UAM_AFSKRB
    { "No User Authent", AU_GUEST, noauth_login,	NULL, 0 },
#ifdef UAM_RNDNUM
    { "Randnum exchange", AU_RANDNUM, randnum_login,  randnum_logincont, 0 },
    { "2-Way Randnum exchange", AU_2WAYRANDNUM,  randnum_login,
      rand2num_logincont, 0 },
    /* handle FPChangePW for 2-way randnum authentication. 
     * NOTE: the above Randnum exchange will match against 
     * AU_RANDNUM first. */
    { "Randnum Exchange", AU_2WAYRANDNUM, NULL,  NULL, 0 }
#endif
};
struct afp_uams		*afp_uam = NULL;
#define AFP_UAM_NUM     (sizeof(afp_uams) / sizeof(afp_uams[0]))

void status_versions( data )
    char	*data;
{
    struct afp_status	*status;
    int			len, num, i;

    status = (struct afp_status *)data;
    num = sizeof( afp_versions ) / sizeof( afp_versions[ 0 ] );
    data += ntohs( status->as_versoff );
    *data++ = num;
    for ( i = 0; i < num; i++ ) {
	len = strlen( afp_versions[ i ].av_name );
	*data++ = len;
	bcopy( afp_versions[ i ].av_name , data, len );
	data += len;
    }
    status->as_uamsoff = htons( data - (char *)status );
}

void status_uams(char *data, const int authbits)
{
    struct afp_status	*status;
    int			len, num, i;

    status = (struct afp_status *)data;
    for ( num = i = 0; i < AFP_UAM_NUM; i++ ) {
	if (afp_uams[ i ].au_number & authbits) {
	    num++;
	}
    }
    data += ntohs( status->as_uamsoff );
    *data++ = num;
    for ( i = 0; i < AFP_UAM_NUM; i++ ) {
        if (afp_uams[ i ].au_number & authbits) {
	    len = strlen( afp_uams[ i ].au_name );
	    *data++ = len;
	    bcopy( afp_uams[ i ].au_name, data, len );
	    data += len;
	}
    }

    /* icon offset */
    status->as_iconoff = htons( data - (char *)status );
}

static struct afp_uams *find_uam(const AFPObj *obj, 
				 struct afp_uams *uams,
				 const int num,
				 char *buf, const int len)
{
  int i;

  if (len < 1)
    return NULL;

  for ( i = 0; i < num; i++ ) {
    if ( (uams[ i ].au_number & obj->options.authbits) == 0 ) 
      continue;

    if ( strndiacasecmp( buf, uams[ i ].au_name, len ) == 0 ) 
	    return &uams[ i ];
  }

  return NULL;
}


/* handle errors by closing the connection. this is only needed
 * by the afp_* functions. */
static int send_reply(const AFPObj *obj, const int err)
{
  if ((err == AFP_OK) || (err == AFPERR_AUTHCONT))
    return err;

  obj->reply(obj->handle, err);
  obj->exit(0);
}

/* handle "real" user names */
static struct passwd *getname(char *name, const int len)
{
  struct passwd *pwent;
  char *user, buf[255];
  int i;

  if (pwent = getpwnam(name))
    return pwent;

#ifndef NO_REAL_USER_NAME
  for (i = 0; i < len; i++)
    name[i] = tolower(name[i]);

  setpwent();
  while (pwent = getpwent()) {
    strncpy(buf, pwent->pw_gecos, sizeof(buf));
    if ((user = strtok(buf, ",")) == NULL)
      user = pwent->pw_gecos;

    if (strncasecmp(user, name, len) == 0) {
      strncpy(name, pwent->pw_name, len);
      break;
    }
  }
  endpwent();
#endif

  return pwent;
}

afp_login(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    int		len, i, num;

    *rbuflen = 0;

    if ( nologin & 1) 
	return send_reply(obj,AFPERR_SHUTDOWN );

    ibuf++;
    len = (unsigned char) *ibuf++;
    num = sizeof( afp_versions ) / sizeof( afp_versions[ 0 ]);
    for ( i = 0; i < num; i++ ) {
	if ( strncmp( ibuf, afp_versions[ i ].av_name , len ) == 0 ) {
	    afp_version = afp_versions[ i ].av_number;
	    break;
	}
    }
    if ( i == num ) 				/* An inappropo version */
	return send_reply(obj, AFPERR_BADVERS );
    ibuf += len;

    len = (unsigned char) *ibuf++;
    if ((afp_uam = find_uam(obj, afp_uams, AFP_UAM_NUM, ibuf, len)) == NULL)
      return send_reply(obj, AFPERR_BADUAM);
    ibuf += len;

    return send_reply(obj, afp_uam->au_login(obj, ibuf, ibuflen,
					     rbuf, rbuflen ));
}

afp_logincont(obj, ibuf, ibuflen, rbuf, rbuflen)
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    if ( afp_uam == NULL || afp_uam->au_logincont == NULL ) {
	*rbuflen = 0;
	return send_reply(obj, AFPERR_NOTAUTH );
    }
    return send_reply(obj, afp_uam->au_logincont(obj, ibuf, ibuflen,
						 rbuf, rbuflen));
}

afp_logout(obj, ibuf, ibuflen, rbuf, rbuflen)
     AFPObj     *obj;
     char       *ibuf, *rbuf;
     int        ibuflen, *rbuflen;
{
  syslog(LOG_INFO, "logout");
  obj->exit(0);
}

noauth_login(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct passwd	*pwent;

    *rbuflen = 0;
    syslog( LOG_INFO, "login noauth" );

    /* this shit should be done by login() XXX */
    if (( pwent = getpwnam(obj->options.guest )) == NULL ) {
	syslog( LOG_ERR, "noauth_login: getpwnam( %s ): %m", 
		obj->options.guest );
	return( AFPERR_BADUAM );
    }

    if ( setgid( pwent->pw_gid ) < 0 || setuid( pwent->pw_uid ) < 0 ) {
	syslog( LOG_ERR, "noauth_login: setreugid: %m" );
	return( AFPERR_BADUAM );
    }

    uuid = pwent->pw_uid;
    ngroups = 0;

#ifdef AFS
    if ( setpag() < 0 ) {
	syslog( LOG_ERR, "noauth_login: setpag: %m" );
	return( AFPERR_BADUAM );
    }
#endif AFS
    afp_switch = postauth_switch;
    return( AFP_OK );
}

login( name, uid, gid )
    char	*name;
    uid_t	uid;
    gid_t	gid;
{
    if ( uid == 0 ) {	/* don't allow root login */
	syslog( LOG_ERR, "login: root login denied!" );
	return AFPERR_NOTAUTH;
    }

    syslog( LOG_INFO, "login %s (uid %d, gid %d)", name, uid, gid );
    if (initgroups( name, gid ) < 0) {
#ifdef RUN_AS_USER
      syslog(LOG_INFO, "running with uid %d", geteuid());
#else
      syslog(LOG_ERR, "login: %m");
      return AFPERR_BADUAM;
#endif
    }
    
    if (setgid( gid ) < 0 || seteuid( uid ) < 0) {
	syslog( LOG_ERR, "login: %m" );
	return AFPERR_BADUAM;
    }

    if (( ngroups = getgroups( NGROUPS, groups )) < 0 ) {
	syslog( LOG_ERR, "login: getgroups: %m" );
	return AFPERR_BADUAM;
    }
    uuid = uid;

    afp_switch = postauth_switch;
    return( AFP_OK );
}

#ifdef KRB

lcase( p )
    char	*p;
{
    for (; *p; p++ ) {
	if ( isupper( *p )) {
	    *p = tolower( *p );
	}
    }
    return;
}

ucase( p )
    char	*p;
{
    for (; *p; p++ ) {
	if ( islower( *p )) {
	    *p = toupper( *p );
	}
    }
    return;
}

#define KRB4CMD_HELO	1
#define KRB4RPL_REALM	2
#define KRB4WRT_SESS	3
#define KRB4RPL_DONE	4
#define KRB4RPL_PRINC	5
#define KRB4WRT_TOKEN	6
#define KRB4WRT_SKIP	7
#define KRB4RPL_DONEMUT	8


krb4_login(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    char		*p;
    int			len;

    if ( *ibuf != KRB4CMD_HELO ) {
	*rbuflen = 0;
	syslog( LOG_INFO, "krb4_login: bad command %d", *ibuf );
	return( AFPERR_NOTAUTH );
    }

    p = rbuf;
    if ( krb_get_lrealm( realm, 1 ) != KSUCCESS ) {
	*rbuflen = 0;
	syslog( LOG_ERR, "krb4_login: can't get local realm!" );
	return( AFPERR_NOTAUTH );
    }

    *p++ = KRB4RPL_REALM;
    *p++ = 1;
    len = strlen( realm );
    *p++ = len;
    strcpy( p, realm );
    p += len + 1;

#ifdef AFS
    if ( setpag() < 0 ) {
	*rbuflen = 0;
	syslog( LOG_ERR, "krb_login: setpag: %m" );
	return( AFPERR_BADUAM );
    }
#endif AFS

    *rbuflen = p - rbuf;
    return( AFPERR_AUTHCONT );
}

krb4_logincont(obj, ibuf, ibuflen, rbuf, rbuflen)
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct passwd	*pwd;
    KTEXT_ST		tkt;
    static AUTH_DAT	ad;
    int			rc;
    short		len;
    char		*p;
    CREDENTIALS		cr;
#ifdef AFS
    struct ViceIoctl	vi;
    struct ClearToken	ct;
#endif AFS
    char		buf[ 1024 ];
    int			aint;

    switch (obj->proto) {
    case AFPPROTO_ASP:
      if ( asp_wrtcont(obj->handle, rbuf, rbuflen ) < 0 ) {
	*rbuflen = 0;
	return( AFPERR_PARAM );
      }
      break;
    case AFPPROTO_DSI:
      *rbuflen = dsi_writeinit(obj->handle, rbuf, *rbuflen);
      break;
    }

    p = rbuf;

    switch ( rc = *p++ ) {
    case KRB4WRT_SESS :
	bcopy( p, &len, sizeof( short ));
	tkt.length = ntohs( len );
	p += sizeof( short );

	if ( tkt.length <= 0 || tkt.length > MAX_KTXT_LEN ) {
	    *rbuflen = 0;
	    return( AFPERR_BADUAM );
	}
	bcopy( p, tkt.dat, tkt.length );
	p += tkt.length;

	if (( rc = krb_rd_req( &tkt, "afpserver", obj->Obj, 0, &ad, "" ))
		!= RD_AP_OK ) {
	    syslog( LOG_ERR, "krb4_logincont: krb_rd_req: %s",
		    krb_err_txt[ rc ] );
	    *rbuflen = 0;
	    return( AFPERR_BADUAM );
	}

	syslog( LOG_INFO, "krb4_login: %s.%s@%s", ad.pname, ad.pinst,
		ad.prealm );
	bcopy( ad.session, seskey, sizeof( C_Block ));
	key_sched((C_Block *) seskey, seskeysched );

	username = ad.pname;
	p = rbuf;

#ifndef AFS
	*p = KRB4RPL_DONE;	/* XXX */
	*rbuflen = 1;

	if (( pwd = getpwnam( ad.pname )) == NULL ) {
	    return( AFPERR_NOTAUTH );
	}
	return( login( pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
#else AFS
	/* get principals */
	*p++ = KRB4RPL_PRINC;
	len = strlen( realm );
	*p++ = len + 1;
	*p++ = '@';
	strcpy( p, realm );
	p += len + 1;
	*rbuflen = p - rbuf;
	return( AFPERR_AUTHCONT );

    case KRB4WRT_TOKEN :
	bcopy( p, &len, sizeof( short ));
	len = ntohs( len );
	p += sizeof( short );
	bcopy( p, &cr, len );

	pcbc_encrypt((C_Block *)&cr, (C_Block *)&cr, len, seskeysched,
		seskey, DES_DECRYPT );

	p = buf;
	cr.ticket_st.length = ntohl( cr.ticket_st.length );
	bcopy( &cr.ticket_st.length, p, sizeof( int ));
	p += sizeof( int );
	bcopy( cr.ticket_st.dat, p, cr.ticket_st.length );
	p += cr.ticket_st.length;

	ct.AuthHandle = ntohl( cr.kvno );
	bcopy( cr.session, ct.HandShakeKey, sizeof( cr.session ));
	ct.ViceId = 0;
	ct.BeginTimestamp = ntohl( cr.issue_date );
	ct.EndTimestamp = krb_life_to_time( ntohl( cr.issue_date ),
		ntohl( cr.lifetime ));

	aint = sizeof( struct ClearToken );
	bcopy( &aint, p, sizeof( int ));
	p += sizeof( int );
	bcopy( &ct, p, sizeof( struct ClearToken ));
	p += sizeof( struct ClearToken );

	aint = 0;
	bcopy( &aint, p, sizeof( int ));
	p += sizeof( int );

	lcase( realm );
	strcpy( p, realm );
	p += strlen( realm ) + 1;

	vi.in = buf;
	vi.in_size = p - buf;
	vi.out = buf;
	vi.out_size = sizeof( buf );
	if ( pioctl( 0, VIOCSETTOK, &vi, 0 ) < 0 ) {
	    syslog( LOG_ERR, "krb4_logincont: pioctl: %m" );
	    *rbuflen = 0;
	    return( AFPERR_BADUAM );
	}
	/* FALL THROUGH */

    case KRB4WRT_SKIP :
	p = rbuf;
	*p = KRB4RPL_DONE;	/* XXX */
	*rbuflen = 1;

	if (( pwd = getpwnam( ad.pname )) == NULL ) {
	    return( AFPERR_NOTAUTH );
	}
	return( login( pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
#endif AFS

    default :
	syslog( LOG_INFO, "krb4_logincont: bad command %d", rc );
	*rbuflen = 0;
	return( AFPERR_NOTAUTH );
	break;
    }
}

#endif KRB

#if !defined(USE_PAM) || defined(UAM_RNDNUM)
static int check_user(const struct passwd *pwd, const char *name)
{
  char *p;

  if ( pwd->pw_shell != NULL && pwd->pw_shell[ 0 ] != '\0' ) {
	while (( p = getusershell()) != NULL ) {
	    if ( strcmp( p, pwd->pw_shell ) == 0 ) {
		break;
	    }
	}
	endusershell();
	if ( p == NULL ) {
	  syslog( LOG_INFO, "illegal shell %s for %s",
		  pwd->pw_shell, name);
	  return( AFPERR_NOTAUTH );
	}
    }
  
  return AFP_OK;
}
#endif


clrtxt_login(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct passwd	*pwd;
    int	                len;

#ifdef USE_PAM
    int                 err;
#else
    char                *p;
#ifdef SHADOWPW
    struct spwd		*sp;
#endif SHADOWPW
#endif /* USE_PAM */

    *rbuflen = 0;

    len = (unsigned char) *ibuf++;
    if ( len > MACFILELEN ) {
	return( AFPERR_PARAM );
    }
    bcopy( ibuf, clrtxtname, len );
    ibuf += len;
    clrtxtname[ len ] = '\0';
    username = clrtxtname;
    if ( *ibuf == '\0' ) {
      ++ibuf;
    }
    ibuf[ PASSWDLEN ] = '\0';

    if (( pwd = getname( clrtxtname, sizeof(clrtxtname) )) == NULL ) {
	return AFPERR_NOTAUTH;
    }

#ifndef USE_PAM
    if (check_user(pwd, clrtxtname) < 0)
      return AFPERR_NOTAUTH;

#ifdef SHADOWPW
    if (( sp = getspnam( clrtxtname )) == NULL ) {
	syslog( LOG_INFO, "no shadow passwd entry for %s", clrtxtname );
	return AFPERR_NOTAUTH;
    }
    pwd->pw_passwd = sp->sp_pwdp;
#endif SHADOWPW

    if ( pwd->pw_passwd != NULL ) {
#ifdef AFS
	if ( kcheckuser( pwd, ibuf ) == 0 ) {
	    return( login( pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
	}
#endif AFS
	p = crypt( ibuf, pwd->pw_passwd );
	if ( strcmp( p, pwd->pw_passwd ) == 0 ) {
	    return( login( pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
	}
    }
    return AFPERR_NOTAUTH;
#else /* USE_PAM */
    PAM_username = username;
    PAM_password = ibuf; /* Set these things up for the conv function */

    err = pam_start("netatalk", username, &PAM_conversation,
		    &pamh);
    PAM_BAIL(obj, pamh);
    /* solaris craps out if PAM_TTY isn't set */
    pam_set_item(pamh, PAM_TTY, "afpd");
    PAM_error = pam_authenticate(pamh,0);
    PAM_BAIL(obj, pamh);
    PAM_error = pam_acct_mgmt(pamh, 0);
    PAM_BAIL(obj, pamh);
#ifndef PAM_CRED_ESTABLISH
#define PAM_CRED_ESTABLISH PAM_ESTABLISH_CRED
#endif
    PAM_error = pam_setcred(pamh, PAM_CRED_ESTABLISH);
    PAM_BAIL(obj, pamh);
    PAM_error = pam_open_session(pamh, 0);
    PAM_BAIL(obj, pamh);

    return (login(pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
#endif /* USE_PAM */
}


#ifdef UAM_RNDNUM
static struct passwd	*randpwd;
static u_int8_t         randbuf[8];

/* hash to a 16-bit number. this will generate completely harmless 
 * warnings on 64-bit machines. */
#define randhash(a) ((((u_int32_t) (a) >> 8) ^ (u_int32_t) (a)) & 0xffff)

/* courtesy of shirsch@ibm.net. */
static int randpass(const struct passwd *pwd, const char *file,
		    const char *keyfile, char *passwd, 
		    const int len, const int set) 
{
  char path[MAXPATHLEN + 1];
  struct stat st;
  int fd, i;
#if 0
  int keyfd;
#endif

  /* Build pathname to user's '.passwd' file */
  i = strlen(file);
  if (file[0] == '~') {
    if ( (strlen(pwd->pw_dir) + i - 1) > MAXPATHLEN)
    return AFPERR_PARAM;
  
    strcpy(path,  pwd->pw_dir );
    strcat(path, "/" );
    strcat(path, file + 2);
  } else if (i > MAXPATHLEN)
    return AFPERR_PARAM;
  else
    strcpy(path, file);
  
  if ( (fd = open(path, (set) ? O_WRONLY : O_RDONLY)) < 0 ) {
    syslog( LOG_ERR, "Failed to open %s", path);
    return AFPERR_NOTAUTH;
  }

#if 0
  if ((keyfd = open(keyfile, O_RDONLY)) < 0) 
    syslog(LOG_INFO, "Using unencrypted passwd");
#endif
  
  if ( fstat( fd, &st ) < 0 ) {
    close(fd);
    return AFPERR_NOTAUTH;
  }
  
  /* If any of these are true, disallow login: 
   * - not a regular file
   * - gid or uid don't match user
   * - anyone else has permissions of any sort
   */
  if ( !S_ISREG(st.st_mode) ||
       (pwd->pw_uid != st.st_uid) ||
       (pwd->pw_gid != st.st_gid) ||
       (st.st_mode & ( S_IRWXG | S_IRWXO )) ) {
    syslog( LOG_INFO, "Insecure permissions found for %s", path);
    close(fd);
    return AFPERR_NOTAUTH;
  }

  /* get the password */
  if (set) {
    if (write(fd, passwd, len) < 0) {
      syslog( LOG_ERR, "Failed to write to %s", path );
      close( fd );
      return AFPERR_NOTAUTH;
    }
  } else {
    if (read(fd, passwd, len) < 0) {
      syslog( LOG_ERR, "Failed to read from %s", path );
      close( fd );
      return AFPERR_NOTAUTH;
    }
  close(fd);
  
  /* get rid of pesky characters */
  for (i = 0; i < len; i++)
    if ((passwd[i] != ' ') && isspace(passwd[i]))
      passwd[i] = '\0';
  }

  return AFP_OK;
}

  
/* randnum sends an 8-byte number and uses the user's password to
 * check against the encrypted reply. */
randnum_login(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
  u_int32_t result;
  u_int16_t sessid;
  int fd, len, err;
  
  *rbuflen = 0;
  len = (unsigned char) *ibuf++;
  if ( len > MACFILELEN ) {
	return AFPERR_PARAM;
  }
  memcpy(clrtxtname, ibuf, len );
  ibuf += len;
  clrtxtname[ len ] = '\0';
  username = clrtxtname;
  if ( *ibuf == '\0' ) {
    ++ibuf;
  }
  
  if (( randpwd = getname( clrtxtname, sizeof(clrtxtname) )) == NULL ) {
    return send_reply(obj, AFPERR_NOTAUTH);
  }
  
  syslog( LOG_INFO, "randnum/rand2num login: %s", username);
  if (check_user(randpwd, clrtxtname) < 0)
    return send_reply(obj, AFPERR_NOTAUTH);

  if ((err = randpass(randpwd, obj->options.passwdfile, NULL,
		      seskey, sizeof(seskey), 0)) != AFP_OK)
    return send_reply(obj, err);

  /* construct a random number */
  if ((fd = open("/dev/urandom", O_RDONLY)) < 0) {
    struct timeval tv;
    struct timezone tz;
    
    if (gettimeofday(&tv, &tz) < 0)
      return AFPERR_PARAM;
    srandom(tv.tv_sec + (unsigned int) obj + (unsigned int) obj->handle);
    result = random();
    memcpy(randbuf, &result, sizeof(result));
    result = random();
    memcpy(randbuf + sizeof(result), &result, sizeof(result));
  } else {
    result = read(fd, randbuf, sizeof(randbuf));
    close(fd);
    if (result < 0)
      return send_reply(obj, AFPERR_PARAM);
  }

  /* session id is a hashed version of the obj pointer */
  sessid = randhash(obj);
  memcpy(rbuf, &sessid, sizeof(sessid));
  rbuf += sizeof(sessid);
  *rbuflen = sizeof(sessid);
  
  /* send the random number off */
  memcpy(rbuf, randbuf, sizeof(randbuf));
  *rbuflen += sizeof(randbuf);
  return AFPERR_AUTHCONT;
}


/* check encrypted reply. we actually setup the encryption stuff
 * here as the first part of randnum and rand2num are identical. */
randnum_logincont(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
  u_int16_t sessid;

  *rbuflen = 0;
  ibuf += 2;

  memcpy(&sessid, ibuf, sizeof(sessid));
  if (sessid != randhash(obj))
    return send_reply(obj, AFPERR_PARAM);

  ibuf += 2;

  /* encrypt. this saves a little space by using the fact that
   * des can encrypt in-place without side-effects. */
  key_sched((C_Block *) seskey, seskeysched);
  memset(seskey, 0, sizeof(seskey));
  ecb_encrypt((C_Block *) randbuf, (C_Block *) randbuf,
	       seskeysched, DES_ENCRYPT);
  memset(seskeysched, 0, sizeof(seskeysched));

  /* test against what the client sent */
  if ( memcmp( randbuf, ibuf, sizeof(randbuf) )) { /* != */
    memset(randbuf, 0, sizeof(randbuf));
    syslog(LOG_INFO, "randnum: failed login");
    return send_reply(obj, AFPERR_NOTAUTH);
  }

  memset(randbuf, 0, sizeof(randbuf));
  return send_reply(obj, login( randpwd->pw_name, randpwd->pw_uid, 
			       randpwd->pw_gid ));
}


/* differences from randnum:
 * 1) each byte of the key is shifted left one bit
 * 2) client sends the server a 64-bit number. the server encrypts it
 *    and sends it back as part of the reply.
 */
rand2num_logincont(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
  u_int16_t sessid;
  int i;

  *rbuflen = 0;
  ibuf += 2;

  /* compare session id */
  memcpy(&sessid, ibuf, sizeof(sessid));
  if (sessid != randhash(obj))
    return send_reply(obj, AFPERR_PARAM);

  ibuf += sizeof(sessid);

  /* shift key elements left one bit */
  for (i = 0; i < sizeof(seskey); i++)
    seskey[i] <<= 1;

  /* encrypt randbuf */
  key_sched((C_Block *) seskey, seskeysched);
  memset(seskey, 0, sizeof(seskey));
  ecb_encrypt( (C_Block *) randbuf, (C_Block *) randbuf,
	       seskeysched, DES_ENCRYPT);

  /* test against client's reply */
  if (memcmp(randbuf, ibuf, sizeof(randbuf))) { /* != */
    memset(randbuf, 0, sizeof(randbuf));
    memset(seskeysched, 0, sizeof(seskeysched));
    syslog(LOG_INFO, "rand2num: failed login");
    return send_reply(obj, AFPERR_NOTAUTH);
  }
  ibuf += sizeof(randbuf);
  memset(randbuf, 0, sizeof(randbuf));

  /* encrypt client's challenge and send back */
  ecb_encrypt( (C_Block *) ibuf, (C_Block *) rbuf,
	       seskeysched, DES_ENCRYPT);
  memset(seskeysched, 0, sizeof(seskeysched));
  *rbuflen = sizeof(randbuf);
  
  return send_reply(obj, login( randpwd->pw_name, randpwd->pw_uid,
				randpwd->pw_gid ));
}
#endif /* UAM_RNDNUM */

#if defined(USE_PAM) || defined(UAM_RNDNUM)
/* change password  --
 * NOTE: an FPLogin must already have completed successfully for this
 *       to work. 
 */
afp_changepw(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
#ifdef USE_PAM
  char	pw[ PASSWDLEN + 1 ];
  pam_handle_t *lpamh;
  uid_t uid;
#endif
#ifdef UAM_RNDNUM
  int err;
#endif
  struct passwd *pwd;
  int len;

  *rbuflen = 0;
  ibuf += 2; 

  /* make sure we can deal w/ this uam */
  len = (unsigned char) *ibuf++;
  if ((afp_uam = find_uam(obj, afp_uams, AFP_UAM_NUM, ibuf, len)) == NULL) {
    return AFPERR_BADUAM;
  }
  ibuf += len;
  if ((len + 1) & 1) /* pad byte */
    ibuf++;

  len = (unsigned char) *ibuf++;
  if ( len > MACFILELEN ) {
    return AFPERR_PARAM;
  }
  memcpy(clrtxtname, ibuf, len);
  clrtxtname[ len ] = '\0';
  ibuf += len;
  if ((len + 1) & 1) /* pad byte */
    ibuf++;
  
  syslog(LOG_INFO, "changing password for <%s>.", clrtxtname);

  if (( pwd = getname( clrtxtname, sizeof(clrtxtname) )) == NULL ) {
    return AFPERR_NOTAUTH;
  }

  /* these two cases are the only accepted ones. */
  switch (afp_uam->au_number) {
#ifdef UAM_RNDNUM
  case AU_RANDNUM:
  case AU_2WAYRANDNUM:
    if (check_user(pwd, clrtxtname) < 0)
      return AFPERR_NOTAUTH;

    /* old password is encrypted with new password and new password is
     * encrypted with old. */
    if ((err = randpass(pwd, obj->options.passwdfile, NULL,
			seskey, sizeof(seskey), 0)) != AFP_OK)
      return err;

    /* use old passwd to decrypt new passwd */
    key_sched((C_Block *) seskey, seskeysched);
    ibuf += PASSWDLEN; /* new passwd */
    ecb_encrypt( (C_Block *) ibuf, (C_Block *) ibuf, seskeysched, DES_DECRYPT);

    /* now use new passwd to decrypt old passwd */
    key_sched((C_Block *) ibuf, seskeysched);
    ibuf -= PASSWDLEN; /* old passwd */
    ecb_encrypt((C_Block *) ibuf, (C_Block *) ibuf, seskeysched, DES_DECRYPT);
    err = memcmp(seskey, ibuf, sizeof(seskey));
    if (!err) 
      err = randpass(pwd, obj->options.passwdfile, NULL,
			ibuf + PASSWDLEN, sizeof(seskey), 1);

    /* zero out some fields */
    memset(seskeysched, 0, sizeof(seskeysched));
    memset(seskey, 0, sizeof(seskey));
    memset(ibuf, 0, sizeof(seskey)); /* old passwd */
    memset(ibuf + PASSWDLEN, 0, sizeof(seskey)); /* new passwd */
    if (err)
      return err;
    break;
#endif

#ifdef USE_PAM
  case AU_CLRTXT:
    /* old password */
    memcpy(pw, ibuf, PASSWDLEN);
    pw[PASSWDLEN] = '\0';

    /* Set these things up for the conv function */
    PAM_username = pwd->pw_name;
    PAM_password = pw; 

    PAM_error = pam_start("netatalk", clrtxtname, &PAM_conversation,
			  &lpamh);
    if (PAM_error != PAM_SUCCESS) 
      return AFPERR_NOTAUTH;
    pam_set_item(lpamh, PAM_TTY, "afpd");

    /* we might need to do this as root */
    uid = geteuid();
    seteuid(0);
    PAM_error = pam_authenticate(lpamh,0);
    if (PAM_error != PAM_SUCCESS) {
      pam_end(lpamh, PAM_error);
      seteuid(uid);
      return AFPERR_NOTAUTH; 
    }

    /* new password */
    ibuf += PASSWDLEN;
    PAM_password = ibuf;
    ibuf[PASSWDLEN] = '\0';
    
    /* this really does need to be done as root */
    PAM_error = pam_chauthtok(lpamh, 0);
    seteuid(uid); /* un-root ourselves. */
    memset(pw, 0, PASSWDLEN);
    memset(ibuf, 0, PASSWDLEN);
    if (PAM_error != PAM_SUCCESS) {
      pam_end(lpamh, PAM_error);
      return AFPERR_ACCESS;
    }

    pam_end(lpamh, 0);
    break;
#endif
  default:
    return AFPERR_BADUAM;
  }

  syslog(LOG_INFO, "password changed succeeded" );
  return( AFP_OK );
}
#endif

/* FPGetUserInfo */
afp_getuserinfo(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    u_int8_t  thisuser;
    u_int32_t id;
    u_int16_t bitmap;
    
    *rbuflen = 0;
    ibuf++;
    thisuser = *ibuf++;
    ibuf += sizeof(id); /* userid is not used in AFP 2.0 */
    memcpy(&bitmap, ibuf, sizeof(bitmap));
    bitmap = ntohs(bitmap);
    
    /* deal with error cases. we don't have to worry about 
     * AFPERR_ACCESS or AFPERR_NOITEM as geteuid and getegid always
     * succeed. */
    if (!thisuser) 
      return AFPERR_PARAM;
    if ((bitmap & USERIBIT_ALL) != bitmap)
      return AFPERR_BITMAP;
    
    /* copy the bitmap back to reply buffer */
    memcpy(rbuf, ibuf, sizeof(bitmap));
    rbuf += sizeof(bitmap);
    *rbuflen = sizeof(bitmap);

    /* copy the user/group info */
    if (bitmap & USERIBIT_USER) {
      id = htonl(geteuid());
      memcpy(rbuf, &id, sizeof(id));
      rbuf += sizeof(id);
      *rbuflen += sizeof(id);
    }
    
    if (bitmap & USERIBIT_GROUP) {
      id = htonl(getegid());
      memcpy(rbuf, &id, sizeof(id));
      rbuf += sizeof(id);
      *rbuflen += sizeof(id);
    }
    
    return AFP_OK;
}    


#ifdef AFS
#include <rx/rxkad.h>
#include <afs/afsint.h>

char *ka_LocalCell();

void
addrealm(realm,cells)
    char *realm;
	char ***cells;
{
    char **ptr;
	int temp;

	ptr= *cells;

    for(;*ptr != 0 ;ptr++)
        if(!strcmp(realm,*ptr))
            return;

	temp=ptr- *cells;
	*cells=(char**)realloc(*cells,((2+temp)*sizeof(char*)));
	ptr= *cells+temp;

    *ptr=(char*)malloc(strlen(realm)+1);
    strcpy(*ptr++,realm);
	*ptr=0;
    return;
}

int kcheckuser(pwd,passwd)
	struct passwd *pwd;
	char *passwd;
{
	int32_t code;
	char *instance="";
	char realm[MAXKTCREALMLEN];
	char lorealm[MAXKTCREALMLEN];
	char *cell;
	Date lifetime=MAXKTCTICKETLIFETIME;
	int rval;
	char **cells=(char **)malloc(sizeof(char*));
	char *temp;
	int rc,cellNum;
	struct ktc_principal serviceName;

	*cells=0;

	code = ka_Init(0);

	{
		char *temp,*temp1;
		temp=(char*)malloc(strlen(pwd->pw_dir)+1);
		strcpy(temp,pwd->pw_dir);
		temp1=temp;
		temp=strtok(temp,"/");
		temp=strtok('\0',"/");
		ka_CellToRealm(temp,realm,0);
		addrealm(realm,&cells);
		free(temp1);
	}

	setpag();
	authenticate(cells,pwd->pw_name,passwd);
	cellNum=0;
	rc=ktc_ListTokens(cellNum,&cellNum,&serviceName);
	if(rc)
		rval=1;
	else{
		rval=0;
	}

	return(rval);
}

authenticate(cells,name,passwd)
	char **cells;
	char *name;
	char *passwd;
{
	char **ptr=cells;
	char *errorstring;

	while(*ptr){
	    ka_UserAuthenticate(name,/*instance*/"",/*cell*/*ptr++,
		    passwd,/*setpag*/0,&errorstring);
	}
}
#endif AFS

#if defined( UAM_AFSKRB ) && defined( AFS )
afskrb_login(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    KTEXT_ST	authent, rpkt;
    CREDENTIALS	cr;
    char	*p, *q;
    int		len, rc, whoserealm;
    short	slen;

    len = (unsigned char) *ibuf++;
    ibuf[ len ] = '\0';
    if (( p = strchr( ibuf, '@' )) != NULL ) {
	*p++ = '\0';
	strcpy( realm, p );
	ucase( realm );
	whoserealm = 0;
    } else {
	if ( krb_get_lrealm( realm, 1 ) != KSUCCESS ) {
	    *rbuflen = 0;
	    return AFPERR_BADUAM;
	}
	whoserealm = 1;
    }
    if (( p = strchr( ibuf, '.' )) != NULL ) {
	*p++ = '\0';
	strcpy( instance, p );
    } else {
	*instance = '\0';
    }
    strcpy( name, ibuf );
    /*
     * We don't have the session key, yet. Get one.
     */
    p = rbuf;
    if ( validseskey == 0 ) {
	if ( setpag() < 0 ) {
	    syslog( LOG_ERR, "krb_login: setpag: %m" );
	    *rbuflen = 0;
	    return AFPERR_BADUAM;
	}
	krb_set_tkt_string(( tktfile = mktemp( _PATH_AFPTKT )));
	if (( rc =  krb_get_svc_in_tkt( "afpserver", Obj, realm,
		TICKET_GRANTING_TICKET, realm, 255, KEYFILE )) != INTK_OK ) {
	    *rbuflen = 0;
	    syslog( LOG_ERR, "krb_login: can't get ticket-granting-ticket" );
	    return (( whoserealm ) ? AFPERR_BADUAM : AFPERR_PARAM );
	}
	if ( krb_mk_req( &authent, name, instance, realm, 0 ) != KSUCCESS ) {
	    *rbuflen = 0;
	    return ( AFPERR_PARAM );
	}
	if ( krb_get_cred( name, instance, realm, &cr ) != KSUCCESS ) {
	    *rbuflen = 0;
	    return ( AFPERR_BADUAM );
	}

	if ( unlink( tktfile ) < 0 ) {
	    syslog( LOG_ERR, "krb_login: unlink %s: %m", tktfile );
	    *rbuflen = 0;
	    return ( AFPERR_BADUAM );
	}

	bcopy( cr.session, seskey, sizeof( C_Block ));
	key_sched((C_Block *) seskey, seskeysched );
	validseskey = 1;
	username = name;

	bcopy( authent.dat, p, authent.length );
	p += authent.length;
    }

    if ( kuam_get_in_tkt( name, instance, realm, TICKET_GRANTING_TICKET,
	    realm, 255, &rpkt ) != INTK_OK ) {
	*rbuflen = 0;
	return ( AFPERR_PARAM );
    }


    q = (char *)rpkt.dat;
    *p++ = *q++;
    *p++ = *q++;
    while ( *q++ )
	;
    while ( *q++ )
	;
    while ( *q++ )
	;
    q += 10;

    len = strlen( realm );
    strcpy( p, realm );
    p += len + 1;
    bcopy( q, &slen, sizeof( short ));
    bcopy( &slen, p, sizeof( short ));
    p += sizeof( short );
    q += sizeof( short );
    bcopy( q, p, slen );
    p += slen;

    *rbuflen = p - rbuf;
    return( AFPERR_AUTHCONT );
}

afskrb_logincont(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    CREDENTIALS		cr;
    struct ViceIoctl	vi;
    struct ClearToken	ct;
    struct passwd	*pwd;
    char		buf[ 1024 ], *p;
    int			aint;
    short		clen;

    *rbuflen = 0;
    ibuf += 2;
    bcopy( ibuf, &clen, sizeof( short ));
    clen = ntohs( clen );
    ibuf += sizeof( short );

    pcbc_encrypt((C_Block *)ibuf, (C_Block *)ibuf,
	    clen, seskeysched, seskey, DES_DECRYPT );
    if ( kuam_set_in_tkt( name, instance, realm, TICKET_GRANTING_TICKET,
	    realm, ibuf ) != INTK_OK ) {
	return ( AFPERR_PARAM );
    }

    if ( get_ad_tkt( "afs", "", realm, 255 ) != KSUCCESS ) {
	return ( AFPERR_PARAM );
    }
    if ( krb_get_cred( "afs", "", realm, &cr ) != KSUCCESS ) {
	return ( AFPERR_PARAM );
    }

    p = buf;
    bcopy( &cr.ticket_st.length, p, sizeof( int ));
    p += sizeof( int );
    bcopy( cr.ticket_st.dat, p, cr.ticket_st.length );
    p += cr.ticket_st.length;

    ct.AuthHandle = cr.kvno;
    bcopy( cr.session, ct.HandShakeKey, sizeof( cr.session ));
    ct.ViceId = 0;
    ct.BeginTimestamp = cr.issue_date;
    /* ct.EndTimestamp = cr.issue_date + ( cr.lifetime * 5 * 60 ); */
    ct.EndTimestamp = krb_life_to_time( cr.issue_date, cr.lifetime );

    aint = sizeof( struct ClearToken );
    bcopy( &aint, p, sizeof( int ));
    p += sizeof( int );
    bcopy( &ct, p, sizeof( struct ClearToken ));
    p += sizeof( struct ClearToken );

    aint = 0;
    bcopy( &aint, p, sizeof( int ));
    p += sizeof( int );

    lcase( realm );
    strcpy( p, realm );
    p += strlen( realm ) + 1;

    vi.in = buf;
    vi.in_size = p - buf;
    vi.out = buf;
    vi.out_size = sizeof( buf );
    if ( pioctl( 0, VIOCSETTOK, &vi, 0 ) < 0 ) {
	syslog( LOG_ERR, "krb_logincont: pioctl: %m" );
	return ( AFPERR_BADUAM );
    }

    if ( unlink( tktfile ) < 0 ) {
	syslog( LOG_ERR, "krb_logincont: %s: %m", tktfile );
	return ( AFPERR_BADUAM );
    }

    if (( pwd = getpwnam( username )) == NULL ) {
	return ( AFPERR_NOTAUTH );
    }
    if ( logged == 0 ) {
	logged = 1;
	syslog( LOG_INFO, "authenticated %s.%s@%s", name, instance, realm );
	return ( login( pwd->pw_name,
				      pwd->pw_uid, pwd->pw_gid ));
    }
    syslog( LOG_INFO, "re-authenticated %s.%s@%s", name, instance, realm );
    return( AFP_OK );
}
#endif UAM_AFSKRB AFS
