PPP Client Support for Microsoft's CHAP-80
==========================================

Eric Rosenquist          rosenqui@strataware.com


INTRODUCTION

Microsoft has introduced an extension to the Challenge/Handshake
Authentication Protocol (CHAP) which eliminates the need for them to have
access to cleartext passwords.  The details of the Microsoft extensions can
be found in the document:

    <ftp://ftp.microsoft.com/developr/rfc/chapexts.txt>

In short, MS-CHAP is identified as <auth chap 80> since the hex value of 80
is used to designate Microsoft's scheme.  Standard PPP CHAP uses a value of
5.  If you enable PPP debugging with the "debug" option and see something
like the following in your logs, the remote server is requesting MS-CHAP:

  rcvd [LCP ConfReq id=0x2 <asyncmap 0x0> <auth chap 80> <magic 0x46a3>]
                                           ^^^^^^^^^^^^

The standard PPP implementation will indicate its lack of support for
MS-CHAP by NAKing it:

  lcp_reqci: rcvd AUTHTYPE
   (c223)
    (NAK)

Windows NT Server systems are often configured to "Accept only Microsoft
Authentication" to enhance security.  Up until now, that meant that you
couldn't use this version of PPPD to connect to such a system.  I've
managed to get a client-only implementation of MS-CHAP working; it will
authenticate itself to another system using MS-CHAP, but if you're using
PPPD as a dial-in server, you won't be able to use MS-CHAP to authenticate
the clients.  This would not be a lot of extra work given that the
framework is in place, but I didn't need it myself so I didn't implement
it.


BUILDING THE PPPD

MS-CHAP uses a combination of MD4 hashing and DES encryption for
authentication.  You'll need to get Eric Young's DES library in order to
use my MS-CHAP extensions.  You can find it in:

    <ftp://ftp.psy.uq.oz.au/pub/Crypto/DES/>

I used libdes-3.06, but hopefully anything newer than that will work also.
Get the library, build and test it on your system, and install it somewhere
(typically /usr/local/lib and /usr/local/include).

You should now be ready to (re)compile the PPPD.  Go to the ppp-X.Y/pppd
directory and make sure the Makefile contains "-DUSE_MSCHAP" in the
COMPILE_FLAGS macro, and that the LIBS macro contains "-ldes".  Depending
on your system and where the DES library was installed, you may also need
to alter the include and library paths used by your compiler.

Do a "make clean" and then a "make" to rebuild the PPPD.  Assuming all goes
well, install the new PPPD and move on to the CONFIGURATION section.


CONFIGURATION

If you've never used PPPD with CHAP before, read the man page (type "man
pppd") and read the description in there.  Basically, you need to edit the
"chap-secrets" file typically named /etc/ppp/chap-secrets.  This should
contain the following two lines for each system with which you use CHAP
(with no leading blanks):

    RemoteHost  Account     Secret
    Account     RemoteHost  Secret

Note that you need both lines and that item 1 and 2 are swapped in the
second line.  I'm not sure why you need it twice, but it works and I didn't
have time to look into it further.  The "RemoteHost" is a somewhat
arbitrary name for the remote Windows NT system you're dialing.  It doesn't
have to match the NT system's name, but it *does* have to match what you
use with the "remotename" parameter.  The "Account" is the Windows NT
account name you have been told to use when dialing, and the "Secret" is
the password for that account.  For example, if your service provider calls
their machine "DialupNT" and tells you your account and password are
"customer47" and "foobar", add the following to your chap-secrets file:

    DialupNT    customer47  foobar
    customer47  DialupNT    foobar

The only other thing you need to do for MS-CHAP (compared to normal CHAP)
is to always use the "remotename" option, either on the command line or in
your "options" file (see the pppd man page for details).  In the case of
the above example, you would need to use the following command line:

    pppd name customer47 remotename DialupNT <other options>

or add:

    name customer47
    remotename DialupNT

to your PPPD "options" file.

The "remotename" option is required for MS-CHAP since Microsoft PPP servers
don't send their system name in the CHAP challenge packet.


TROUBLESHOOTING

Assuming that everything else has been configured correctly for PPP and
CHAP, the MS-CHAP-specific problems you're likely to encounter are mostly
related to your Windows NT account and its settings.  A Microsoft server
returns error codes in its CHAP response.  The following are extracted from
Microsoft's "chapexts.txt" file referenced above:

 646 ERROR_RESTRICTED_LOGON_HOURS
 647 ERROR_ACCT_DISABLED
 648 ERROR_PASSWD_EXPIRED
 649 ERROR_NO_DIALIN_PERMISSION
 691 ERROR_AUTHENTICATION_FAILURE
 709 ERROR_CHANGING_PASSWORD

You'll see these in your pppd log as a line similar to:

   Remote message: E=649 R=0

The "E=" is the error number from the table above, and the "R=" flag
indicates whether the error is transient and the client should retry.  If
you consistently get error 691, then either you're using the wrong account
name/password, or the DES library or MD4 hashing (in md4.c) aren't working
properly.  Verify your account name and password (use a Windows NT or
Windows 95 system to dial-in if you have one available).  If that checks
out, test the DES library with the "destest" program included with the DES
library.  If DES checks out, the md4.c routines are probably failing
(system byte ordering may be a problem) or my code is screwing up.  I've
only got access to a Linux system, so you're on your own for anything else.
I can generate an MD4 test program if necessary in order to verify proper
MD4 operation.


STILL TO DO

A site using only MS-CHAP to authenticate has no need to store cleartext
passwords in the "chap-secrets" file.  A utility that spits out the ASCII
hex MD4 hash of a given password would be nice, and would allow that hash
to be used in chap-secrets in place of the password.  The code to do this
could quite easily be lifted from chap_ms.c (you have to convert the
password to Unicode before hashing it).  The chap_ms.c file would also have
to be changed to recognize a password hash (16 binary bytes == 32 ASCII hex
characters) and skip the hashing stage.

A server implementation would allow MS-CHAP to be used with Windows NT and
Windows 95 clients for enhanced security.  Some new command-line options
would be required, as would code to generate the Challenge packet and
verify the response.  Most of the helper functions are in place, so this
shouldn't be too hard for someone to add.




These are the differences to the origial 2.2.0d distribution package. You
must use the patch utility to apply these patches before the code may be
built.

diff --unified=10 --new-file --recursive ppp-2.2.orig/pppd/chap.c ppp-2.2/pppd/chap.c
--- ppp-2.2.orig/pppd/chap.c	Tue Jul 11 21:50:34 1995
+++ ppp-2.2/pppd/chap.c	Mon Dec  4 21:30:53 1995
@@ -27,20 +27,23 @@
  */
 
 #include <stdio.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/time.h>
 #include <syslog.h>
 
 #include "pppd.h"
 #include "chap.h"
+#ifdef USE_MSCHAP
+#include "chap_ms.h"
+#endif /* USE_MSCHAP */
 #include "md5.h"
 
 chap_state chap[NUM_PPP];		/* CHAP state; one for each unit */
 
 static void ChapChallengeTimeout __P((caddr_t));
 static void ChapResponseTimeout __P((caddr_t));
 static void ChapReceiveChallenge __P((chap_state *, u_char *, int, int));
 static void ChapReceiveResponse __P((chap_state *, u_char *, int, int));
 static void ChapReceiveSuccess __P((chap_state *, u_char *, int, int));
 static void ChapReceiveFailure __P((chap_state *, u_char *, int, int));
@@ -363,50 +366,68 @@
 	return;
     }
     rchallenge = inp;
     INCPTR(rchallenge_len, inp);
 
     if (len >= sizeof(rhostname))
 	len = sizeof(rhostname) - 1;
     BCOPY(inp, rhostname, len);
     rhostname[len] = '\000';
 
+#ifndef USE_MSCHAP
     CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: received name field: %s",
 	       rhostname));
+#else
+    CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: received name field: '%s'",
+	      rhostname));
+
+    /* Microsoft doesn't send their name back in the PPP packet */
+    if (!rhostname[0] && cstate->resp_type == CHAP_MICROSOFT) {
+        extern char remote_name[];
+	strcpy(rhostname, remote_name);
+	CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: using '%s' as remote name", rhostname));
+    }
+#endif /* USE_MSCHAP */
 
     /* get secret for authenticating ourselves with the specified host */
     if (!get_secret(cstate->unit, cstate->resp_name, rhostname,
 		    secret, &secret_len, 0)) {
 	secret_len = 0;		/* assume null secret if can't find one */
 	syslog(LOG_WARNING, "No CHAP secret found for authenticating us to %s",
 	       rhostname);
     }
 
     /* cancel response send timeout if necessary */
     if (cstate->clientstate == CHAPCS_RESPONSE)
 	UNTIMEOUT(ChapResponseTimeout, (caddr_t) cstate);
 
     cstate->resp_id = id;
     cstate->resp_transmits = 0;
 
     /*  generate MD based on negotiated type */
     switch (cstate->resp_type) { 
 
-    case CHAP_DIGEST_MD5:		/* only MD5 is defined for now */
+    case CHAP_DIGEST_MD5:
 	MD5Init(&mdContext);
 	MD5Update(&mdContext, &cstate->resp_id, 1);
 	MD5Update(&mdContext, secret, secret_len);
 	MD5Update(&mdContext, rchallenge, rchallenge_len);
 	MD5Final(&mdContext);
 	BCOPY(mdContext.digest, cstate->response, MD5_SIGNATURE_SIZE);
 	cstate->resp_length = MD5_SIGNATURE_SIZE;
 	break;
+
+#ifdef USE_MSCHAP
+    case CHAP_MICROSOFT:
+	ChapMS(cstate, rchallenge, rchallenge_len, secret, secret_len);
+	break;
+#endif /* USE_MSCHAP */
 
     default:
 	CHAPDEBUG((LOG_INFO, "unknown digest type %d", cstate->resp_type));
 	return;
     }
 
     ChapSendResponse(cstate);
 }
 
--- pppd/chap.c.orig	Tue Jul 11 21:50:34 1995
+++ pppd/chap.c	Wed Dec 13 20:07:32 1995
@@ -27,20 +27,23 @@
  */
 
 #include <stdio.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/time.h>
 #include <syslog.h>
 
 #include "pppd.h"
 #include "chap.h"
+#ifdef USE_MSCHAP
+#include "chap_ms.h"
+#endif /* USE_MSCHAP */
 #include "md5.h"
 
 chap_state chap[NUM_PPP];		/* CHAP state; one for each unit */
 
 static void ChapChallengeTimeout __P((caddr_t));
 static void ChapResponseTimeout __P((caddr_t));
 static void ChapReceiveChallenge __P((chap_state *, u_char *, int, int));
 static void ChapReceiveResponse __P((chap_state *, u_char *, int, int));
 static void ChapReceiveSuccess __P((chap_state *, u_char *, int, int));
 static void ChapReceiveFailure __P((chap_state *, u_char *, int, int));
@@ -363,50 +366,68 @@
 	return;
     }
     rchallenge = inp;
     INCPTR(rchallenge_len, inp);
 
     if (len >= sizeof(rhostname))
 	len = sizeof(rhostname) - 1;
     BCOPY(inp, rhostname, len);
     rhostname[len] = '\000';
 
+#ifndef USE_MSCHAP
     CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: received name field: %s",
 	       rhostname));
+#else
+    CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: received name field: '%s'",
+	      rhostname));
+
+    /* Microsoft doesn't send their name back in the PPP packet */
+    if (!rhostname[0] && cstate->resp_type == CHAP_MICROSOFT) {
+        extern char remote_name[];
+	strcpy(rhostname, remote_name);
+	CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: using '%s' as remote name", rhostname));
+    }
+#endif /* USE_MSCHAP */
 
     /* get secret for authenticating ourselves with the specified host */
     if (!get_secret(cstate->unit, cstate->resp_name, rhostname,
 		    secret, &secret_len, 0)) {
 	secret_len = 0;		/* assume null secret if can't find one */
 	syslog(LOG_WARNING, "No CHAP secret found for authenticating us to %s",
 	       rhostname);
     }
 
     /* cancel response send timeout if necessary */
     if (cstate->clientstate == CHAPCS_RESPONSE)
 	UNTIMEOUT(ChapResponseTimeout, (caddr_t) cstate);
 
     cstate->resp_id = id;
     cstate->resp_transmits = 0;
 
     /*  generate MD based on negotiated type */
     switch (cstate->resp_type) { 
 
-    case CHAP_DIGEST_MD5:		/* only MD5 is defined for now */
+    case CHAP_DIGEST_MD5:
 	MD5Init(&mdContext);
 	MD5Update(&mdContext, &cstate->resp_id, 1);
 	MD5Update(&mdContext, secret, secret_len);
 	MD5Update(&mdContext, rchallenge, rchallenge_len);
 	MD5Final(&mdContext);
 	BCOPY(mdContext.digest, cstate->response, MD5_SIGNATURE_SIZE);
 	cstate->resp_length = MD5_SIGNATURE_SIZE;
 	break;
+
+#ifdef USE_MSCHAP
+    case CHAP_MICROSOFT:
+	ChapMS(cstate, rchallenge, rchallenge_len, secret, secret_len);
+	break;
+#endif /* USE_MSCHAP */
 
     default:
 	CHAPDEBUG((LOG_INFO, "unknown digest type %d", cstate->resp_type));
 	return;
     }
 
     ChapSendResponse(cstate);
 }
 
 
--- pppd/chap.h.orig	Sun Dec 10 16:18:22 1995
+++ pppd/chap.h	Wed Dec 13 20:08:43 1995
@@ -32,23 +32,24 @@
 #define MD5_SIGNATURE_SIZE	16	/* 16 bytes in a MD5 message digest */
 
 #define CHAP_CHALLENGE		1
 #define CHAP_RESPONSE		2
 #define CHAP_SUCCESS		3
 #define CHAP_FAILURE    	4
 
 /*
  *  Challenge lengths (for challenges we send) and other limits.
  */
+
 #define MIN_CHALLENGE_LENGTH	32
 #define MAX_CHALLENGE_LENGTH	64
-#define MAX_RESPONSE_LENGTH	16	/* sufficient for MD5 */
+#define MAX_RESPONSE_LENGTH	64	/* sufficient for MD5 and MSCHAP */
 
 /*
  * Each interface is described by a chap structure.
  */
 
 typedef struct chap_state {
     int unit;			/* Interface unit number */
     int clientstate;		/* Client state */
     int serverstate;		/* Server state */
     u_char challenge[MAX_CHALLENGE_LENGTH]; /* last challenge string sent */
--- pppd/chap_ms.c.orig	Wed Dec 13 20:07:32 1995
+++ pppd/chap_ms.c	Wed Dec 13 20:07:32 1995
@@ -0,0 +1,179 @@
+/*
+ * chap_ms.c - Microsoft MS-CHAP compatible implementation.
+ *
+ * Copyright (c) 1995 Eric Rosenquist, Strata Software Limited.
+ * http://www.strataware.com/
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Eric Rosenquist.  The name of the author may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#ifdef USE_MSCHAP
+#ifndef lint
+static char rcsid[] = "$Id$";
+#endif
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <syslog.h>
+
+#include "pppd.h"
+#include "chap.h"
+#include "chap_ms.h"
+#include "md4.h"
+
+#include <des.h>
+
+typedef struct {
+    u_char LANManResp[24];
+    u_char NTResp[24];
+    u_char UseNT;			/* If 1, ignore the LANMan response field */
+} MS_ChapResponse;
+#define MS_CHAP_RESPONSE_LEN	49	/* Don't rely on sizeof(MS_ChapResponse) in case of struct padding */
+
+
+static void	DesEncrypt __P((u_char *, u_char *, u_char *));
+static void	MakeKey __P((u_char *, u_char *));
+
+static void
+ChallengeResponse(challenge, pwHash, response)
+    u_char *challenge;	/* IN   8 octets */
+    u_char *pwHash;	/* IN  16 octets */
+    u_char *response;	/* OUT 24 octets */
+{
+    char    ZPasswordHash[21];
+
+    BZERO(ZPasswordHash, sizeof(ZPasswordHash));
+    BCOPY(pwHash, ZPasswordHash, 16);
+
+#if 0
+    log_packet(ZPasswordHash, sizeof(ZPasswordHash), "ChallengeResponse - ZPasswordHash");
+#endif
+
+    DesEncrypt(challenge, ZPasswordHash +  0, response + 0);
+    DesEncrypt(challenge, ZPasswordHash +  7, response + 8);
+    DesEncrypt(challenge, ZPasswordHash + 14, response + 16);
+
+#if 0
+    log_packet(response, 24, "ChallengeResponse - response");
+#endif
+}
+
+
+static void
+DesEncrypt(clear, key, cipher)
+    u_char *clear;	/* IN  8 octets */
+    u_char *key;	/* IN  7 octets */
+    u_char *cipher;	/* OUT 8 octets */
+{
+    des_cblock		des_key;
+    des_key_schedule	key_schedule;
+
+    MakeKey(key, des_key);
+
+    des_set_key(&des_key, key_schedule);
+
+#if 0
+    CHAPDEBUG((LOG_INFO, "DesEncrypt: 8 octet input : %02X%02X%02X%02X%02X%02X%02X%02X",
+	       clear[0], clear[1], clear[2], clear[3], clear[4], clear[5], clear[6], clear[7]));
+#endif
+
+    des_ecb_encrypt((des_cblock *)clear, (des_cblock *)cipher, key_schedule, 1);
+
+#if 0
+    CHAPDEBUG((LOG_INFO, "DesEncrypt: 8 octet output: %02X%02X%02X%02X%02X%02X%02X%02X",
+	       cipher[0], cipher[1], cipher[2], cipher[3], cipher[4], cipher[5], cipher[6], cipher[7]));
+#endif
+}
+
+
+static u_char Get7Bits(input, startBit)
+    u_char *input;
+    int startBit;
+{
+    register unsigned int	word;
+
+    word  = (unsigned)input[startBit / 8] << 8;
+    word |= (unsigned)input[startBit / 8 + 1];
+
+    word >>= 15 - (startBit % 8 + 7);
+
+    return word & 0xFE;
+}
+
+
+static void MakeKey(key, des_key)
+    u_char *key;	/* IN  56 bit DES key missing parity bits */
+    u_char *des_key;	/* OUT 64 bit DES key with parity bits added */
+{
+    des_key[0] = Get7Bits(key,  0);
+    des_key[1] = Get7Bits(key,  7);
+    des_key[2] = Get7Bits(key, 14);
+    des_key[3] = Get7Bits(key, 21);
+    des_key[4] = Get7Bits(key, 28);
+    des_key[5] = Get7Bits(key, 35);
+    des_key[6] = Get7Bits(key, 42);
+    des_key[7] = Get7Bits(key, 49);
+
+    des_set_odd_parity((des_cblock *)des_key);
+
+#if 0
+    CHAPDEBUG((LOG_INFO, "MakeKey: 56-bit input : %02X%02X%02X%02X%02X%02X%02X",
+	       key[0], key[1], key[2], key[3], key[4], key[5], key[6]));
+    CHAPDEBUG((LOG_INFO, "MakeKey: 64-bit output: %02X%02X%02X%02X%02X%02X%02X%02X",
+	       des_key[0], des_key[1], des_key[2], des_key[3], des_key[4], des_key[5], des_key[6], des_key[7]));
+#endif
+}
+#endif /* USE_MSCHAP */
+
+void
+ChapMS(cstate, rchallenge, rchallenge_len, secret, secret_len)
+    chap_state *cstate;
+    char *rchallenge;
+    int rchallenge_len;
+    char *secret;
+    int secret_len;
+{
+#ifdef USE_MSCHAP
+    int			i;
+    MDstruct		md4Context;
+    MS_ChapResponse	response;
+    u_char		unicodePassword[MAX_NT_PASSWORD * 2];
+
+#if 0
+    CHAPDEBUG((LOG_INFO, "ChapMS: secret is '%.*s'", secret_len, secret));
+#endif
+
+    BZERO(&response, sizeof(response));
+
+    /* Initialize the Unicode version of the secret (== password). */
+    /* This implicitly supports 8-bit ISO8859/1 characters. */
+    BZERO(unicodePassword, sizeof(unicodePassword));
+    for (i = 0; i < secret_len; i++)
+	unicodePassword[i * 2] = (u_char)secret[i];
+
+    MDbegin(&md4Context);
+    MDupdate(&md4Context, unicodePassword, secret_len * 2 * 8);	/* Unicode is 2 bytes/char, *8 for bit count */
+    MDupdate(&md4Context, NULL, 0);	/* Tell MD4 we're done */
+
+    ChallengeResponse(rchallenge, (char *)md4Context.buffer, response.NTResp);
+
+    response.UseNT = 1;
+
+    BCOPY(&response, cstate->response, MS_CHAP_RESPONSE_LEN);
+    cstate->resp_length = MS_CHAP_RESPONSE_LEN;
+#endif /* USE_MSCHAP */
+}
--- pppd/chap_ms.h.orig	Wed Dec 13 20:07:32 1995
+++ pppd/chap_ms.h	Wed Dec 13 20:07:32 1995
@@ -0,0 +1,32 @@
+/*
+ * chap.h - Cryptographic Handshake Authentication Protocol definitions.
+ *
+ * Copyright (c) 1995 Eric Rosenquist, Strata Software Limited.
+ * http://www.strataware.com/
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Eric Rosenquist.  The name of the author may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $Id$
+ */
+
+#ifndef __CHAPMS_INCLUDE__
+
+#define MAX_NT_PASSWORD	256	/* Maximum number of (Unicode) chars in an NT password */
+
+void ChapMS __P((chap_state *, char *, int, char *, int));
+
+#define __CHAPMS_INCLUDE__
+#endif /* __CHAPMS_INCLUDE__ */
--- pppd/lcp.c.orig	Mon Dec  4 22:19:20 1995
+++ pppd/lcp.c	Wed Dec 13 20:07:32 1995
@@ -1255,21 +1255,25 @@
 		    break;
 		}
 		if (!ao->neg_chap) {	/* we don't want to do CHAP */
 		    orc = CONFNAK;	/* NAK it and suggest PAP */
 		    PUTCHAR(CI_AUTHTYPE, nakp);
 		    PUTCHAR(CILEN_SHORT, nakp);
 		    PUTSHORT(PPP_PAP, nakp);
 		    break;
 		}
 		GETCHAR(cichar, p);	/* get digest type*/
+#ifndef USE_MSCHAP
 		if (cichar != ao->chap_mdtype) {
+#else
+		if (cichar != ao->chap_mdtype && cichar != CHAP_MICROSOFT) {
+#endif /* USE_MSCHAP */
 		    orc = CONFNAK;
 		    PUTCHAR(CI_AUTHTYPE, nakp);
 		    PUTCHAR(CILEN_CHAP, nakp);
 		    PUTSHORT(PPP_CHAP, nakp);
 		    PUTCHAR(ao->chap_mdtype, nakp);
 		    break;
 		}
 		ho->chap_mdtype = cichar; /* save md type */
 		ho->neg_chap = 1;
 		break;
--- pppd/md4.c.orig	Wed Dec 13 20:07:32 1995
+++ pppd/md4.c	Wed Dec 13 20:07:32 1995
@@ -0,0 +1,295 @@
+/*
+** ********************************************************************
+** md4.c -- Implementation of MD4 Message Digest Algorithm           **
+** Updated: 2/16/90 by Ronald L. Rivest                              **
+** (C) 1990 RSA Data Security, Inc.                                  **
+** ********************************************************************
+*/
+
+/*
+** To use MD4:
+**   -- Include md4.h in your program
+**   -- Declare an MDstruct MD to hold the state of the digest
+**          computation.
+**   -- Initialize MD using MDbegin(&MD)
+**   -- For each full block (64 bytes) X you wish to process, call
+**          MDupdate(&MD,X,512)
+**      (512 is the number of bits in a full block.)
+**   -- For the last block (less than 64 bytes) you wish to process,
+**          MDupdate(&MD,X,n)
+**      where n is the number of bits in the partial block. A partial
+**      block terminates the computation, so every MD computation
+**      should terminate by processing a partial block, even if it
+**      has n = 0.
+**   -- The message digest is available in MD.buffer[0] ...
+**      MD.buffer[3].  (Least-significant byte of each word
+**      should be output first.)
+**   -- You can print out the digest using MDprint(&MD)
+*/
+
+/* Implementation notes:
+** This implementation assumes that ints are 32-bit quantities.
+** If the machine stores the least-significant byte of an int in the
+** least-addressed byte (e.g., VAX and 8086), then LOWBYTEFIRST
+** should be set to TRUE.  Otherwise (e.g., SUNS), LOWBYTEFIRST
+** should be set to FALSE.  Note that on machines with LOWBYTEFIRST
+** FALSE the routine MDupdate modifies has a side-effect on its input
+** array (the order of bytes in each word are reversed).  If this is
+** undesired a call to MDreverse(X) can reverse the bytes of X back
+** into order after each call to MDupdate.
+**
+** NOTE: LOWBYTEFIRST removed by Eric Rosenquist in favour of run-time
+** detection to simplify build process.
+*/
+
+#define TRUE  1
+#define FALSE 0
+
+/* Compile-time includes
+*/
+#include <stdio.h>
+#include "md4.h"
+#include "pppd.h"
+
+/* Compile-time declarations of MD4 "magic constants".
+*/
+#define I0  0x67452301       /* Initial values for MD buffer */
+#define I1  0xefcdab89
+#define I2  0x98badcfe
+#define I3  0x10325476
+#define C2  013240474631     /* round 2 constant = sqrt(2) in octal */
+#define C3  015666365641     /* round 3 constant = sqrt(3) in octal */
+/* C2 and C3 are from Knuth, The Art of Programming, Volume 2
+** (Seminumerical Algorithms), Second Edition (1981), Addison-Wesley.
+** Table 2, page 660.
+*/
+
+#define fs1  3               /* round 1 shift amounts */
+#define fs2  7
+#define fs3 11
+#define fs4 19
+#define gs1  3               /* round 2 shift amounts */
+#define gs2  5
+#define gs3  9
+#define gs4 13
+#define hs1  3               /* round 3 shift amounts */
+#define hs2  9
+#define hs3 11
+#define hs4 15
+
+/* Compile-time macro declarations for MD4.
+** Note: The "rot" operator uses the variable "tmp".
+** It assumes tmp is declared as unsigned int, so that the >>
+** operator will shift in zeros rather than extending the sign bit.
+*/
+#define f(X,Y,Z)             ((X&Y) | ((~X)&Z))
+#define g(X,Y,Z)             ((X&Y) | (X&Z) | (Y&Z))
+#define h(X,Y,Z)             (X^Y^Z)
+#define rot(X,S)             (tmp=X,(tmp<<S) | (tmp>>(32-S)))
+#define ff(A,B,C,D,i,s)      A = rot((A + f(B,C,D) + X[i]),s)
+#define gg(A,B,C,D,i,s)      A = rot((A + g(B,C,D) + X[i] + C2),s)
+#define hh(A,B,C,D,i,s)      A = rot((A + h(B,C,D) + X[i] + C3),s)
+
+/* MDprint(MDp)
+** Print message digest buffer MDp as 32 hexadecimal digits.
+** Order is from low-order byte of buffer[0] to high-order byte of
+** buffer[3].
+** Each byte is printed with high-order hexadecimal digit first.
+** This is a user-callable routine.
+*/
+void
+MDprint(MDp)
+MDptr MDp;
+{ int i,j;
+for (i=0;i<4;i++)
+ for (j=0;j<32;j=j+8)
+   printf("%02x",(MDp->buffer[i]>>j) & 0xFF);
+}
+
+/* MDbegin(MDp)
+** Initialize message digest buffer MDp.
+** This is a user-callable routine.
+*/
+void
+MDbegin(MDp)
+MDptr MDp;
+{ int i;
+MDp->buffer[0] = I0;
+MDp->buffer[1] = I1;
+MDp->buffer[2] = I2;
+MDp->buffer[3] = I3;
+for (i=0;i<8;i++) MDp->count[i] = 0;
+MDp->done = 0;
+}
+
+/* MDreverse(X)
+** Reverse the byte-ordering of every int in X.
+** Assumes X is an array of 16 ints.
+** The macro revx reverses the byte-ordering of the next word of X.
+*/
+#define revx { t = (*X << 16) | (*X >> 16); \
+  *X++ = ((t & 0xFF00FF00) >> 8) | ((t & 0x00FF00FF) << 8); }
+MDreverse(X)
+unsigned int *X;
+{ register unsigned int t;
+revx; revx; revx; revx; revx; revx; revx; revx;
+revx; revx; revx; revx; revx; revx; revx; revx;
+}
+
+/* MDblock(MDp,X)
+** Update message digest buffer MDp->buffer using 16-word data block X.
+** Assumes all 16 words of X are full of data.
+** Does not update MDp->count.
+** This routine is not user-callable.
+*/
+static void
+MDblock(MDp,X)
+MDptr MDp;
+unsigned int *X;
+{
+register unsigned int tmp, A, B, C, D;
+static int low_byte_first = -1;
+
+if (low_byte_first == -1) {
+    low_byte_first = (htons((unsigned short int)1) != 1);
+}
+
+if (low_byte_first == 0) {
+    MDreverse(X);
+}
+
+A = MDp->buffer[0];
+B = MDp->buffer[1];
+C = MDp->buffer[2];
+D = MDp->buffer[3];
+/* Update the message digest buffer */
+ff(A , B , C , D ,  0 , fs1); /* Round 1 */
+ff(D , A , B , C ,  1 , fs2);
+ff(C , D , A , B ,  2 , fs3);
+ff(B , C , D , A ,  3 , fs4);
+ff(A , B , C , D ,  4 , fs1);
+ff(D , A , B , C ,  5 , fs2);
+ff(C , D , A , B ,  6 , fs3);
+ff(B , C , D , A ,  7 , fs4);
+ff(A , B , C , D ,  8 , fs1);
+ff(D , A , B , C ,  9 , fs2);
+ff(C , D , A , B , 10 , fs3);
+ff(B , C , D , A , 11 , fs4);
+ff(A , B , C , D , 12 , fs1);
+ff(D , A , B , C , 13 , fs2);
+ff(C , D , A , B , 14 , fs3);
+ff(B , C , D , A , 15 , fs4);
+gg(A , B , C , D ,  0 , gs1); /* Round 2 */
+gg(D , A , B , C ,  4 , gs2);
+gg(C , D , A , B ,  8 , gs3);
+gg(B , C , D , A , 12 , gs4);
+gg(A , B , C , D ,  1 , gs1);
+gg(D , A , B , C ,  5 , gs2);
+gg(C , D , A , B ,  9 , gs3);
+gg(B , C , D , A , 13 , gs4);
+gg(A , B , C , D ,  2 , gs1);
+gg(D , A , B , C ,  6 , gs2);
+gg(C , D , A , B , 10 , gs3);
+gg(B , C , D , A , 14 , gs4);
+gg(A , B , C , D ,  3 , gs1);
+gg(D , A , B , C ,  7 , gs2);
+gg(C , D , A , B , 11 , gs3);
+gg(B , C , D , A , 15 , gs4);
+hh(A , B , C , D ,  0 , hs1); /* Round 3 */
+hh(D , A , B , C ,  8 , hs2);
+hh(C , D , A , B ,  4 , hs3);
+hh(B , C , D , A , 12 , hs4);
+hh(A , B , C , D ,  2 , hs1);
+hh(D , A , B , C , 10 , hs2);
+hh(C , D , A , B ,  6 , hs3);
+hh(B , C , D , A , 14 , hs4);
+hh(A , B , C , D ,  1 , hs1);
+hh(D , A , B , C ,  9 , hs2);
+hh(C , D , A , B ,  5 , hs3);
+hh(B , C , D , A , 13 , hs4);
+hh(A , B , C , D ,  3 , hs1);
+hh(D , A , B , C , 11 , hs2);
+hh(C , D , A , B ,  7 , hs3);
+hh(B , C , D , A , 15 , hs4);
+MDp->buffer[0] += A;
+MDp->buffer[1] += B;
+MDp->buffer[2] += C;
+MDp->buffer[3] += D;
+}
+
+/* MDupdate(MDp,X,count)
+** Input: MDp -- an MDptr
+**        X -- a pointer to an array of unsigned characters.
+**        count -- the number of bits of X to use.
+**          (if not a multiple of 8, uses high bits of last byte.)
+** Update MDp using the number of bits of X given by count.
+** This is the basic input routine for an MD4 user.
+** The routine completes the MD computation when count < 512, so
+** every MD computation should end with one call to MDupdate with a
+** count less than 512.  A call with count 0 will be ignored if the
+** MD has already been terminated (done != 0), so an extra call with
+** count 0 can be given as a "courtesy close" to force termination
+** if desired.
+*/
+void
+MDupdate(MDp,X,count)
+MDptr MDp;
+unsigned char *X;
+unsigned int count;
+{ unsigned int i, tmp, bit, byte, mask;
+unsigned char XX[64];
+unsigned char *p;
+/* return with no error if this is a courtesy close with count
+** zero and MDp->done is true.
+*/
+if (count == 0 && MDp->done) return;
+/* check to see if MD is already done and report error */
+if (MDp->done)
+	  { printf("\nError: MDupdate MD already done."); return; }
+/* Add count to MDp->count */
+tmp = count;
+p = MDp->count;
+while (tmp)
+ { tmp += *p;
+   *p++ = tmp;
+   tmp = tmp >> 8;
+ }
+/* Process data */
+if (count == 512)
+ { /* Full block of data to handle */
+   MDblock(MDp,(unsigned int *)X);
+ }
+else if (count > 512) /* Check for count too large */
+ { printf("\nError: MDupdate called with illegal count value %d."
+		  ,count);
+   return;
+ }
+else /* partial block -- must be last block so finish up */
+ { /* Find out how many bytes and residual bits there are */
+   byte = count >> 3;
+   bit =  count & 7;
+   /* Copy X into XX since we need to modify it */
+   for (i=0;i<=byte;i++)   XX[i] = X[i];
+   for (i=byte+1;i<64;i++) XX[i] = 0;
+   /* Add padding '1' bit and low-order zeros in last byte */
+   mask = 1 << (7 - bit);
+   XX[byte] = (XX[byte] | mask) & ~( mask - 1);
+   /* If room for bit count, finish up with this block */
+   if (byte <= 55)
+	 { for (i=0;i<8;i++) XX[56+i] = MDp->count[i];
+	   MDblock(MDp,(unsigned int *)XX);
+	 }
+   else /* need to do two blocks to finish up */
+	 { MDblock(MDp,(unsigned int *)XX);
+	   for (i=0;i<56;i++) XX[i] = 0;
+	   for (i=0;i<8;i++)  XX[56+i] = MDp->count[i];
+	   MDblock(MDp,(unsigned int *)XX);
+	 }
+   /* Set flag saying we're done with MD computation */
+   MDp->done = 1;
+ }
+}
+
+/*
+** End of md4.c
+****************************(cut)***********************************/
--- pppd/md4.h.orig	Wed Dec 13 20:07:32 1995
+++ pppd/md4.h	Wed Dec 13 20:07:32 1995
@@ -0,0 +1,51 @@
+/*
+** ********************************************************************
+** md4.h -- Header file for implementation of                        **
+** MD4 Message Digest Algorithm                                      **
+** Updated: 2/13/90 by Ronald L. Rivest                              **
+** (C) 1990 RSA Data Security, Inc.                                  **
+** ********************************************************************
+*/
+
+/* MDstruct is the data structure for a message digest computation.
+*/
+typedef struct {
+unsigned int buffer[4]; /* Holds 4-word result of MD computation */
+unsigned char count[8]; /* Number of bits processed so far */
+unsigned int done;      /* Nonzero means MD computation finished */
+} MDstruct, *MDptr;
+
+/* MDbegin(MD)
+** Input: MD -- an MDptr
+** Initialize the MDstruct prepatory to doing a message digest
+** computation.
+*/
+extern void MDbegin();
+
+/* MDupdate(MD,X,count)
+** Input: MD -- an MDptr
+**        X -- a pointer to an array of unsigned characters.
+**        count -- the number of bits of X to use (an unsigned int).
+** Updates MD using the first "count" bits of X.
+** The array pointed to by X is not modified.
+** If count is not a multiple of 8, MDupdate uses high bits of
+** last byte.
+** This is the basic input routine for a user.
+** The routine terminates the MD computation when count < 512, so
+** every MD computation should end with one call to MDupdate with a
+** count less than 512.  Zero is OK for a count.
+*/
+extern void MDupdate();
+
+/* MDprint(MD)
+** Input: MD -- an MDptr
+** Prints message digest buffer MD as 32 hexadecimal digits.
+** Order is from low-order byte of buffer[0] to high-order byte
+** of buffer[3].
+** Each byte is printed with high-order hexadecimal digit first.
+*/
+extern void MDprint();
+
+/*
+** End of md4.h
+****************************(cut)***********************************/
--- pppd/Makefile.linux.orig	Wed Dec 13 20:18:18 1995
+++ pppd/Makefile.linux	Mon Dec  4 22:05:09 1995
@@ -30,6 +30,13 @@
 CFLAGS= $(COPTS) $(DEBUG_FLAGS) $(COMPILE_FLAGS)
 SOURCE= RELNOTES Makefile.linux $(PPPDSRCS) $(HEADERS) $(MANPAGES)
 
+ifdef USE_MSCHAP
+PPPDSRCS += md4.c chap_ms.c
+PPPDOBJS += md4.o chap_ms.o
+CFLAGS   += -DUSE_MSCHAP
+LIBS     += -ldes
+endif
+
 ifdef USE_MS_DNS
 CFLAGS   += -DUSE_MS_DNS=1
 endif
