/*
 * passlog.cpp - basic log formats implementation
 * $Id: passlog.cpp,v 1.4 2004/06/05 15:15:17 rdenisc Exp $
 */

/***********************************************************************
 *  Copyright (C) 2002-2003 Remi Denis-Courmont.                       *
 *  This program is free software; you can redistribute and/or modify  *
 *  it under the terms of the GNU General Public License as published  *
 *  by the Free Software Foundation; version 2 of the license.         *
 *                                                                     *
 *  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, you can get it from:              *
 *  http://www.gnu.org/copyleft/gpl.html                               *
 ***********************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "passlog.h"
#include <ctype.h> // isspace()
#include <stdio.h> // sscanf()
#include <string.h> // strchr()

/*
 * Returns a pointer to the first non 'white' (space or tab) char in a string.
 * CRASHES if str si NULL.
 */
static const char *
eat_white (const char *str)
{
	while (isspace (*str))
		str++;
	return str;
}


/*
 * Performs a case-insensitive comparaison of the first word of two strings.
 * Returns true if different, 0 if equal.
 * CRASHES if any param is NULL.
 */
static int
wordcasecmp (const char *w1, const char *w2)
{
	w1 = eat_white (w1);
	w2 = eat_white (w2);

	do
	{
		char c1 = *w1++, c2 = *w2++;

		if (isspace (c1) || !c1)
			return !(isspace (c2) || !c2);
		if (tolower (c1) != tolower (c2))
			return 1;
	}
	while (1);

	return 1; // dead code
}


/*
 * Performs base64 decoding.
 */
#define BASE64_SKIP -3
#define BASE64_EOF  -2
#define BASE64_ERR  -1

static char
base64_reduce (char c)
{
	if ((c >= 'A') && (c <= 'Z'))
		return c - 'A';
	if ((c >= 'a') && (c <= 'z'))
		return c - 'a' + 26;
	if ((c >= '0') && (c <= '9'))
		return c - '0' + 52;

	switch (c)
	{
		case '+':
			return 62;

		case '/':
			return 63;

		case '=':
			return BASE64_EOF;

		case '\n':
		case '\r':
		case ' ':
			return BASE64_SKIP;
	}
	return BASE64_ERR;
}


/*
 * Decodes Base64-encoded source to target.
 * Returns 0 on success, -1 on I/O error, -2 if source is not Base64-encoded.
 */
static int
unbase64 (unsigned char *dec, const unsigned char *enc, size_t maxlen)
{
	for (size_t len = 0; len < maxlen; len += 3)
	{
		unsigned char in[4];
		unsigned avail = 4;

		for (int i = 0; (i < 4) && (avail == 4); i++)
		{
			// reads one byte
			int buf = *enc;
			if (buf == 0)
				buf = BASE64_EOF; // end of encoded string
			else
				enc++;

			// decodes a 6-bits value from an octet
			buf = base64_reduce (buf);
			switch (buf)
			{
				case BASE64_SKIP: /* skips CR/LF/SP */
					break;

				case BASE64_ERR: /* invalid base64 char */
					return -1;

				case BASE64_EOF: /* end of string */
					avail = i;
					break;

				default:
					in[i] = (unsigned char)buf;
			}
		}

		if (maxlen <= (len + ((avail > 0) ? (avail - 1) : 0)))
			return -1; // output buffer too short

		switch (avail)
		{
			case 4:
				dec[len + 2] = (in[2] << 6) | (in[3] >> 0);
			case 3:
				dec[len + 1] = (in[1] << 4) | (in[2] >> 2);
			case 2:
				dec[len] = (in[0] << 2) | (in[1] >> 4);
		}

		if (avail < 4)
			return 0;
	}

	return 0;
}


/*** PasswordDataLog class implementation ***/
int PasswordDataLog::WriteServerLine (const char *, int length, int)
{
#if 0
	if (!oob)
	{
		/*switch (proto_class)
		{
			case undefined:
			case unknown:
			case HTTP_like:
			case POP_like:
			case FTP_like:
		}*/
	}
#endif
	return length;
}


/*
 * There is of course still a clever way to escape password capture
 * by sending an overly long quantity of spaces between PASS and your actual
 * password, or by getting your password split into two parts by the
 * line-bufferization engine.
 */
int PasswordDataLog::WriteClientLine (const char *data, int length, int oob)
{
	if (!oob)
	{
		// FTP/SMTP/NNTP/POP1/POP3:
		if (!wordcasecmp ("USER", data))
			sscanf (data, "%*s %127s", username);
		else
		if (!wordcasecmp ("PASS", data))
		{
			if ((sscanf (data, "%*s %127s", password) == 1)
			 && *username)
				fprintf (out, "UNKNOWN: %s:%s\n", username,
						password);
		}
		else
		// POP2 (but not SMTP!!):
		if (!wordcasecmp ("HELO", data))
		{
			if (sscanf (data, "%*s %127s %127s", username,
					password) == 2)
				fprintf (out, "UNKNOWN: %s:%s\n", username,
						password);
		}
		else
		// HTTP Basic authentication
		if (!wordcasecmp ("Authorization:", data)
		 || !wordcasecmp ("Proxy-Authorization:", data))
		{
			unsigned char buf_enc[345];
			char buf_dec[256];

			if (sscanf (data, "%*s Basic %511s\n", buf_enc) == 1)
			{
				unbase64 ((unsigned char*)buf_dec, buf_enc,
						sizeof (buf_dec));

				char *lim = strchr (buf_dec, ':');
				if (lim != NULL)
				{
					*lim = 0;
					lim++;
					fprintf (out, "UNKNOWN: %s:%s\n",
							buf_dec, lim);
				}
			}
		}
	}
	return length;
}


DataLog *PasswordLogMaker (void)
{
	return new PasswordDataLog;
}


syntax highlighted by Code2HTML, v. 0.9.1