/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1998-1999,2001 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include "snoop.h"

static void put_method(char *cp, int method);
static void put_socks5_addr(char *cp, const unsigned char *buf, int fraglen);
static void put_socks4_res(char *cp, int code);
static void put_socks5_res(char *cp, int code);

int
interpret_socks_call(flags, line, fraglen)
	int flags;
	char *line;
	int fraglen;
{
	unsigned char	*buf = (unsigned char *)line;
	char		*cp;
	struct in_addr	ipaddr;
	int		i, n;

	if (flags & F_SUM) {
	cp = get_sum_line();
	if (fraglen >= 2) {
		switch (buf[0]) {
		case 4:		/* SOCKS4 */
			n = buf[1];
			switch (n) {
			case 1:
			case 2:
				if (fraglen >= 8) {
					(void) memcpy(&ipaddr, &buf[4],
					    sizeof (ipaddr));
					(void) sprintf(cp,
					    "SOCKS4 %s %s:%u",
					    addrtoname(AF_INET, &ipaddr),
					    (n == 1)? "CONNECT": "BIND",
					    (buf[2] << 8) | buf[3]);
					cp += strlen(cp);
					if (fraglen > 8) {
						(void) sprintf(cp, " User=");
						cp += strlen(cp);
						for (i = 8;
							i < 40 && i < fraglen;
							++i) {
							if (buf[i] == '\0')
								break;
							*cp++ = buf[i];
						}
						if (i == 40) {
							*cp++ = '.';
							*cp++ = '.';
							*cp++ = '.';
						}
						*cp = '\0';
					}
				}
				break;
			default:
				(void) sprintf(cp, "SOCKS4 OP=%u", n);
			}
			break;
		case 5:		/* SOCKS5 */
			n = buf[1];
			if (2 + n == fraglen) {
				(void) sprintf(cp,
					"SOCKS5 CONTACT NMETHODS=%d:", n);
				cp += strlen(cp);
				for (i = 0; i < n && 2 + i < fraglen; ++i) {
					put_method(cp, buf[2 + i]);
					cp += strlen(cp);
				}
			} else if (fraglen >= 6 && buf[2] == 0) {
				const char	*cmd;

				if (n < 1 || n > 3) {
					(void) sprintf(cp,
						"SOCKS (send data): %s",
						show_string(line, fraglen, 20));
				} else {
					switch (n) {
					case 1:
						cmd = "CONNECT";
						break;
					case 2:
						cmd = "BIND";
						break;
					case 3:
						cmd = "ASSOCIATE_UDP";
						break;
					}
					(void) sprintf(cp, "SOCKS5 %s ", cmd);
					cp += strlen(cp);
					put_socks5_addr(cp, &buf[3],
						fraglen - 3);
				}
			} else {
				(void) sprintf(cp, "SOCKS (send data): %s",
					show_string(line, fraglen, 20));
			}
			break;
		default:
			(void) sprintf(cp, "SOCKS (send data): %s",
				show_string(line, fraglen, 20));
		}
	} else {
		(void) sprintf(cp, "SOCKS (send data): %s",
			show_string(line, fraglen, 20));
	}

	} /* if (flags & F_SUM) */

	if (flags & F_DTAIL) {
		show_header("SOCKS: ", "SOCKS Header", fraglen);
		show_space();
		cp = get_line(0, 0);
		if (fraglen >= 2) {
			switch (buf[0]) {
			case 4:
				(void) sprintf(cp, "Version = 4");
				n = buf[1];
				switch (n) {
				case 1:
				case 2:
					(void) sprintf(get_line(0, 0),
					    "Operation = %s",
					    (n == 1)? "CONNECT": "BIND");
					if (fraglen >= 8) {
						(void) memcpy(&ipaddr, &buf[4],
						    sizeof (ipaddr));
						(void) sprintf(get_line(0, 0),
						    "Destination = %s:%u",
						    addrtoname(AF_INET,
						    &ipaddr),
						    (buf[2] << 8) | buf[3]);
						if (fraglen > 8) {
							cp = get_line(0, 0);
							(void) sprintf(cp,
							    "User = ");
							cp += strlen(cp);
							for (i = 8;
								i < 40; ++i) {
								if
								(buf[i] == '\0')
									break;
								*cp++ = buf[i];
							}
							if (i == 40) {
								*cp++ = '.';
								*cp++ = '.';
								*cp++ = '.';
							}
							*cp = '\0';
						}
					}
					break;
				default:
					(void) sprintf(get_line(0, 0),
					    "Operation = %u (unknown)", n);
				}
				break;
			case 5:		/* SOCKS5 */
				(void) sprintf(cp, "Version = 5");
				n = buf[1];
				if (2 + n == fraglen) {
					(void) sprintf(get_line(0, 0),
					    "Number of methods = %u", n);
					for (i = 0;
						i < n && 2 + i < fraglen; ++i) {
						cp = get_line(0, 0);
						(void) sprintf(cp,
							"Method %3u =", i);
						cp += strlen(cp);
						put_method(cp, buf[2 + i]);
					}
				} else if (fraglen >= 6 && buf[2] == 0) {
					const char	*cmd;
					if (n < 1 || n > 3) {
						(void) sprintf(cp,
							"SOCKS (send data): %s",
							show_string(line,
							fraglen, 20));
					} else {
						switch (n) {
						case 1:
							cmd = "CONNECT";
							break;
						case 2:
							cmd = "BIND";
							break;
						case 3:
							cmd = "ASSOCIATE_UDP";
							break;
						}
						(void) sprintf(get_line(0, 0),
						    "Operation = %s ", cmd);
						put_socks5_addr(get_line(0, 0),
						    &buf[3], fraglen - 3);
						break;
					}
				} else
					(void) sprintf(cp,
						" SOCKS (send data): %s",
						show_string(line, fraglen,
						20));
				break;
			default:
				(void) sprintf(cp,
					"SOCKS (send data): %s",
					show_string(line, fraglen, 20));
			}
			show_space();
		} else
			(void) sprintf(cp,
				"SOCKS (send data): %s",
				show_string(line, fraglen, 20));
	}

out:
	return (fraglen);
}

int
interpret_socks_reply(flags, line, fraglen)
	int flags;
	char *line;
	int fraglen;
{
	unsigned char	*buf = (unsigned char *)line;
	char		*cp;
	struct in_addr	ipaddr;

	if (flags & F_SUM) {
		cp = get_sum_line();
		if (fraglen >= 2) {
			switch (buf[0]) {
			case 0:
				(void) sprintf(cp, "SOCKS4 ");
				cp += strlen(cp);
				if (fraglen >= 8) {
					(void) memcpy(&ipaddr, &buf[4],
					    sizeof (ipaddr));
					(void) sprintf(cp, "%s:%u ",
					    addrtoname(AF_INET, &ipaddr),
					    (buf[2] << 8) | buf[3]);
					cp += strlen(cp);
				}
				/* reply version, no SOCKS version in v4 */
				put_socks4_res(cp, buf[1]);
				break;
			case 5:
				(void) sprintf(cp, "SOCKS5 method accepted:");
				cp += strlen(cp);
				put_method(cp, buf[1]);
				break;
			default:
				(void) sprintf(cp, "SOCKS (recv data)");
			}
		} else
			(void) sprintf(cp, "SOCKS (recv data)");
	}

	if (flags & F_DTAIL) {
		show_header("SOCKS: ", "SOCKS Header", fraglen);
		show_space();
		cp = get_line(0, 0);
		if (fraglen >= 2) {
			switch (buf[0]) {
			case 0:
				/* reply version, no SOCKS version in v4 */
				(void) sprintf(cp,
				    "Reply version = 0 (SOCKS version 4)");
				if (fraglen >= 8) {
					(void) memcpy(&ipaddr, &buf[4],
					    sizeof (ipaddr));
					(void) sprintf(get_line(0, 0),
					    "Destination %s:%u ",
					    addrtoname(AF_INET, &ipaddr),
					    (buf[2] << 8) | buf[3]);
				}
				cp = get_line(0, 0);
				(void) sprintf(cp, "Result code = %u ", buf[1]);
				cp += strlen(cp);
				put_socks4_res(cp, buf[1]);
				break;
			case 5:
				(void) sprintf(cp, "Reply version = 5");
				if (fraglen == 2) {
					cp = get_line(0, 0);
					(void) sprintf(cp, "Method accepted =");
					cp += strlen(cp);
					put_method(cp, buf[1]);
				} else if (fraglen >= 6 && buf[2] == 0x00) {
					cp = get_line(0, 0);
					(void) sprintf(cp, "Status = ");
					cp += strlen(cp);
					put_socks5_res(cp, buf[1]);
					put_socks5_addr(get_line(0, 0),
					    &buf[3], fraglen - 3);
				}
				break;
			default:
				(void) sprintf(cp, "(recv data)");
			}
		} else
			(void) sprintf(cp, "(recv data)");
		show_space();
	}

out:
	return (fraglen);
}

static void
put_method(char *cp, int method)
{
	switch (method) {
	case 0:
		(void) sprintf(cp, " NOAUTH");
		break;
	case 1:
		(void) sprintf(cp, " GSSAPI");
		break;
	case 2:
		(void) sprintf(cp, " USERNAME/PASSWD");
		break;
	case 255:
		(void) sprintf(cp, " NONE");
		break;
	default:
		(void) sprintf(cp, " 0x%02x (unknown)", method);
	}
}

static void
put_socks5_addr(char *cp, const unsigned char *buf, int fraglen)
{
	struct in_addr	ipaddr;
	int		i;

	switch (buf[0]) {
	case 1:
		/* IPv4 */
		(void) sprintf(cp, "Address = ");
		cp += strlen(cp);
		if (1 + 4 + 2 <= fraglen) {
			(void) memcpy(&ipaddr, &buf[1], sizeof (ipaddr));
			(void) sprintf(cp, "%s:%u",
			    addrtoname(AF_INET, &ipaddr),
			    (buf[5] << 8) | buf[5 + 1]);
		} else
			(void) strcat(cp, "(IPv4)");
		break;
	case 3:
		/* domain name */
		(void) sprintf(cp, "Domain name = ");
		cp += strlen(cp);
		for (i = 0; i <= buf[1] && 1 + i < fraglen; ++i)
			*cp++ = buf[1 + i];
		if (1 + i + 2 <= fraglen)
			(void) sprintf(cp, ":%u",
			    (buf[1 + i] << 8) | buf[1 + i + 1]);
		else
			*cp = '\0';
		break;
	case 4:
		/* IPv6 */
		(void) sprintf(cp, "Address = ");
		if (1 + 16 <= fraglen) {
			for (i = 0; i < 16; ++i) {
				if (i > 0)
					*cp++ = '.';
				(void) sprintf(cp, "%u", buf[1 + i]);
				cp += strlen(cp);
			}
			if (1 + 16 + 2 <= fraglen) {
				(void) sprintf(cp, ":%u",
				    (buf[1 + 16] << 8) | buf[1 + 16 + 1]);
			}
		} else
			(void) strcat(cp, "(IPv6)");
		break;
	default:
		(void) sprintf(cp, "Address type = 0x%02x (unknown)", buf[0]);
	}
}

static void
put_socks4_res(char *cp, int code)
{
	switch (code) {
	case 90:
		(void) sprintf(cp, "request granted");
		break;
	case 91:
		(void) sprintf(cp, "request rejected or failed");
		break;
	case 92:
		(void) sprintf(cp, "socksd can't connect to client's identd");
		break;
	case 93:
		(void) sprintf(cp, "identity mismatch");
		break;
	default:
		(void) sprintf(cp, "0x%02x (unknown)", code);
	}
}

static void
put_socks5_res(char *cp, int code)
{
	switch (code) {
	case 0:
		(void) strcpy(cp, "succeeded");
		break;
	case 1:
		(void) strcpy(cp, "general SOCKS server failure");
		break;
	case 2:
		(void) strcpy(cp, "connection not allowed by ruleset");
		break;
	case 3:
		(void) strcpy(cp, "network unreachable");
		break;
	case 4:
		(void) strcpy(cp, "host unreachable");
		break;
	case 5:
		(void) strcpy(cp, "connection refused");
		break;
	case 6:
		(void) strcpy(cp, "TTL expired");
		break;
	case 7:
		(void) strcpy(cp, "command not supported");
		break;
	case 8:
		(void) strcpy(cp, "address type not supported");
		break;
	default:
		(void) sprintf(cp, "code 0x%02x", code);
	}
}