#include "config.h"

#ifdef NTLM_ENABLE
#include "fetchmail.h"
#include "i18n.h"
#include "ntlm.h"
#include "socket.h"

#include <ctype.h>
#include <string.h>

int ntlm_helper(int sock, struct query *ctl, const char *proto, bool *dontread)
{
/** returns 0 for success or if SASL authentication was cancelled with a * line
 * NTLM support by Grant Edwards.
 *
 * Handle MS-Exchange NTLM authentication method.  This is the same
 * as the NTLM auth used by Samba for SMB related services. We just
 * encode the packets in base64 instead of sending them out via a
 * network interface.
 *
 * Much source (ntlm.h, smb*.c smb*.h) was borrowed from Samba.
 */
    tSmbNtlmAuthRequest request;
    tSmbNtlmAuthChallenge challenge;
    tSmbNtlmAuthResponse response;

    char msgbuf[2048];
    int result;

    if ((result = gen_recv(sock, msgbuf, sizeof msgbuf)))
	return result;

    if (msgbuf[0] != '+' && strspn(msgbuf+1, " \t") < strlen(msgbuf+1)) {
	if (outlevel >= O_VERBOSE) {
	    report(stdout, GT_("Warning: received malformed challenge to \"AUTH(ENTICATE) NTLM\"!\n"));
	}
	*dontread = true;
	return PS_AUTHFAIL;
	// we do not goto cancelfail because we're not in SASL so let's not send the asterisk
    }

    if (buildSmbNtlmAuthRequest(&request,ctl->remotename,NULL)) {
	    report(stderr, GT_("Error: cannot build NTLM NEGOTIATE_MESSAGE (internal buffer too small)\n"));
	    result = PS_AUTHFAIL;
	    goto cancelfail;
    }

    if (outlevel >= O_DEBUG)
	dumpSmbNtlmAuthRequest(stdout, &request);

    memset(msgbuf,0,sizeof msgbuf);
    to64frombits (msgbuf, &request, SmbLength(&request), sizeof msgbuf);

    if (outlevel >= O_MONITOR)
	report(stdout, "%s> %s\n", proto, msgbuf);

    strcat(msgbuf,"\r\n");
    ssize_t written = SockWrite (sock, msgbuf, strlen (msgbuf));
    if (written < 0 || (size_t)written != strlen(msgbuf))
        return PS_PROTOCOL;

    if ((result = gen_recv(sock, msgbuf, sizeof msgbuf)))
	goto cancelfail;

    /*
     * < 0: decoding error
     * >= 0 < 32: too short to be plausible
     */
    if ((result = from64tobits (&challenge, msgbuf, sizeof(challenge))) < 0
	    || result < 32)
    {
	report (stderr, GT_("could not decode BASE64 challenge\n")); // includes "challenge" too small to hold entire decoded message
	if ('+' == msgbuf[0] && isspace((unsigned char)msgbuf[1])) {
		// in SASL, abort properly
		result = PS_AUTHFAIL;
		goto cancelfail;
	} else {
		/* We do not goto cancelfail; the server has already sent some
		 * tagged or final reply, so the protocol exchange has ended, no need
		 * for us to send the asterisk. */
	}
	return PS_PROTOCOL;
    }

    /* validate challenge:
     * - ident
     * - message type
     * - that offset points into buffer
     * - that offset + length does not wrap
     * - that offset + length is not bigger than buffer */
    if (0 != memcmp("NTLMSSP", challenge.ident, 8)
	    || challenge.msgType != 2
	    || challenge.uDomain.offset > (unsigned)result
	    || (challenge.uDomain.offset + challenge.uDomain.len) < challenge.uDomain.offset
	    || (challenge.uDomain.offset + challenge.uDomain.len) > (unsigned)result)
    {
	report (stderr, GT_("NTLM challenge contains invalid data.\n"));
	result = PS_AUTHFAIL;
	goto cancelfail;
    }

    if (outlevel >= O_DEBUG)
	dumpSmbNtlmAuthChallenge(stdout, &challenge);

    if (buildSmbNtlmAuthResponse(&challenge, &response,ctl->remotename,ctl->password)) {
	    report(stderr, GT_("Error: cannot build NTLM AUTHENTICATE_MESSAGE (internal buffer too small)\n"));
	    result = PS_AUTHFAIL;
	    goto cancelfail;
    }

    if (outlevel >= O_DEBUG)
	dumpSmbNtlmAuthResponse(stdout, &response);

    memset(msgbuf,0,sizeof msgbuf);
    to64frombits (msgbuf, &response, SmbLength(&response), sizeof msgbuf);

    if (outlevel >= O_MONITOR)
	report(stdout, "%s> %s\n", proto, msgbuf);

    strcat(msgbuf,"\r\n");
    written = SockWrite(sock, msgbuf, strlen(msgbuf));
    if (written < 0 || (size_t)written != strlen(msgbuf))
        return PS_PROTOCOL;

    return PS_SUCCESS;

cancelfail: /* cancel authentication */
    {
	if (outlevel >= O_MONITOR)
	    report(stdout, "%s> *\n", proto);
	(void)SockWrite(sock, "*\r\n", 3);
	return NTLM_BUFFER_TOO_SMALL;
    }
}

#else
typedef int make_pedantic_iso_c_compilers_happy;
#endif /* NTLM_ENABLE */
