OBJEXT = @OBJEXT@
CFLAGS = @CFLAGS@ @COMMON_CFLAGS@
MODS_SHARED = log_irssi auto_away log_custom \
- socks @MODS_SHARED@
+ @MODS_SHARED@
LDFLAGS = @LDFLAGS@
BINS = @BINS@
scriptdir = $(cdatadir)/scripts
* Integrate listeners into the core.
+ * Integrate socks support into listener code. Socks connections
+ can now be accepted at the same port as other connections.
+
+ * Allow configuring a single listener in ~/.ctrlproxy/config.
+
Ctrlproxy 3.0.3 2007-07-22
# Please adapt to your needs!
[global]
-
# Replication mechanism to use (some other IRC proxies call this backlog)
# Possible values: none, simple, highlight, lastdisconnect
# Meanings:
# containing 'matches' (see below)
replication = none
+# Prepend all lines in replication with the time a line was received when replicating
+report-time = false
+
+# What words to look for when remembering lines
+# (in case "replication = highlight")
+# Seperate using semicolons
+# matches=ctrlproxy;foobar
+
+# Port at which CtrlProxy should listen for new connections
+# Connections can be plain IRC connections or using the SOCKS protocol.
+port=6680
+
+# Password for logging in to ctrlproxy
+password=
+
+# Set "bind" to make ctrlproxy only listen on a specific IP address:
+# Example: bind=192.168.4.3
+
# Override motd-file location
#motd-file = /tmp/my-motd
-# Prepend all lines with the time a line was received when replicating
-report-time = false
-
+# Save state to configuration file on exit
autosave = true
# Networks to connect to on startup. Seperate by semicolons
# autoconnect = admin;irc.oftc.net;irc.freenode.net;
# Support for interfacing to ctrlproxy
-# using /MSG ctrlproxy or /CTRLPROXY -->
-[admin]
+# using /MSG ctrlproxy
+admin-user = ctrlproxy
+
+# Send ctrlproxy log messages to the admin channel
+admin-log = true
# Irssi-style logging
[log-irssi]
# Work as a socks proxy
#[socks]
-#port = 8800
#allow = jelmer:secret, foo:bar
-# Make sure messages are not sent too fast after each other
-# (prevents being kicked by the server for 'Excess flooding'
-#[antiflood]
-
# Automatically set AWAY after a certain period of time
#[auto-away]
#message = I'm currently away, sorry!
#time = 300 # in seconds
-
-#[nickserv]
-# Learn new nickserv user/password combinations by interpreting traffic to server
-#learn = true
-
-#[listen]
-# Listen for network connections start port 6667
-# auto = true
-# autoport = 6667
# Please adapt to your needs!
[global]
-
-# Replication mechanism to use
+# Replication mechanism to use (some other IRC proxies call this backlog)
# Possible values: none, simple, highlight, lastdisconnect
+# Meanings:
+# none: No backlog
+# simple: Send backlog since the user last said something
+# lastdisconnect: Send backlog since the users' last disconnect
+# highlight: Send backlog since last connect, but only lines
+# containing 'matches' (see below)
replication = none
-# Override motd-file location
-#motd-file = /tmp/my-motd
+# Prepend all lines in replication with the time a line was received when replicating
+report-time = false
-#Disable autosave on exit
-#autosave = False
-#
# What words to look for when remembering lines
# (in case "replication = highlight")
# Seperate using semicolons
-matches=ctrlproxy;foobar
+# matches=ctrlproxy;foobar
+
+# Port at which CtrlProxy should listen for new connections
+# Connections can be plain IRC connections or using the SOCKS protocol.
+port=6680
+
+# Password for logging in to ctrlproxy
+password=
+
+# Set "bind" to make ctrlproxy only listen on a specific IP address:
+# Example: bind=192.168.4.3
-# Prepend all lines with the time a line was received when replicating
-report-time=False
+# Override motd-file location
+#motd-file = /tmp/my-motd
+
+# Save state to configuration file on exit
+autosave = true
-# List of networks/servers to autoconnect to
-autoconnect=irc.oftc.net;irc.freenode.net;
+# Networks to connect to on startup. Seperate by semicolons
+autoconnect = admin
+# autoconnect = admin;irc.oftc.net;irc.freenode.net;
# Support for interfacing to ctrlproxy
# using /MSG ctrlproxy or /CTRLPROXY -->
# Irssi-style logging
[log-irssi]
-logfile = /home/jelmer/tmp/ctrlproxy
+# logfile = /home/jelmer/tmp/ctrlproxy
# Work as a socks proxy
#[socks]
-#port = 8800
#allow = jelmer:secret, foo:bar
-# Make sure messages are not sent too fast after each other
-# (prevents being kicked by the server for 'Excess flooding'
-#[antiflood]
-
# Automatically set AWAY after a certain period of time
#[auto-away]
#message = I'm currently away, sorry!
#time = 300 # in seconds
-
-#[nickserv]
-# Learn new nickserv user/password combinations by interpreting traffic to server
-#learn = true
-
-[listener]
-# Listen for network connections start port 6667
-# auto = true
-# autoport = 6667
-password = secret
+++ /dev/null
-/*
- ctrlproxy: A modular IRC proxy
- (c) 2005 Jelmer Vernooij <jelmer@nl.linux.org>
- SOCKS server (see RFC 1928, 1929 and 1961)
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- 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, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-*/
-
-/* TODO:
- * - ipv6 support (listen)
- * - support for ipv4 and ipv6 atyp's
- * - support for gssapi method
- * - support for ident method
- */
-
-#include "ctrlproxy.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <glib.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <netdb.h>
-
-#ifdef HAVE_SYS_SOCKET_H
-#include <sys/socket.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "socks"
-
-#define SOCKS_VERSION 0x05
-#define DEFAULT_SOCKS_PORT 1080
-
-#define SOCKS_METHOD_NOAUTH 0x00
-#define SOCKS_METHOD_GSSAPI 0x01
-#define SOCKS_METHOD_USERNAME_PW 0x02
-#define SOCKS_METHOD_NOACCEPTABLE 0xFF
-
-#define ATYP_IPV4 0x01
-#define ATYP_FQDN 0x03
-#define ATYP_IPV6 0x04
-
-#define CMD_CONNECT 0x01
-#define CMD_BIND 0x02
-#define CMD_UDP_ASSOCIATE 0x03
-
-#define REP_OK 0x00
-#define REP_GENERAL_FAILURE 0x01
-#define REP_NOT_ALLOWED 0x02
-#define REP_NET_UNREACHABLE 0x03
-#define REP_HOST_UNREACHABLE 0x04
-#define REP_CONN_REFUSED 0x05
-#define REP_TTL_EXPIRED 0x06
-#define REP_CMD_NOT_SUPPORTED 0x07
-#define REP_ATYP_NOT_SUPPORTED 0x08
-
-static GIOChannel *server_channel = NULL;
-static int server_channel_in = -1;
-enum socks_state { STATE_NEW = 0, STATE_AUTH, STATE_NORMAL };
-
-struct allow_rule {
- const char *username;
- const char *password;
-};
-
-static GList *allow_rules = NULL;
-static GList *pending_clients = NULL;
-
-struct socks_method;
-struct socks_client
-{
- GIOChannel *connection;
- const char *user;
- const char *password;
- gint watch_id;
- struct socks_method *method;
- enum socks_state state;
- void *method_data;
- struct sockaddr *clientname;
- socklen_t clientname_len;
- struct global *global;
-};
-
-static gboolean socks_reply(GIOChannel *ioc, guint8 err, guint8 atyp, guint8 data_len, gchar *data, guint16 port)
-{
- gchar *header = g_new0(gchar, 7 + data_len);
- GIOStatus status;
- gsize read;
-
- header[0] = SOCKS_VERSION;
- header[1] = err;
- header[2] = 0x0; /* Reserved */
- header[3] = atyp;
- memcpy(header+4, data, data_len);
- *((guint16 *)(header+4+data_len)) = htons(port);
-
- status = g_io_channel_write_chars(ioc, header, 6 + data_len, &read, NULL);
-
- g_free(header);
-
- g_io_channel_flush(ioc, NULL);
-
- return (err == REP_OK);
-}
-
-static gboolean socks_error(GIOChannel *ioc, guint8 err)
-{
- guint8 data = 0x0;
- return socks_reply(ioc, err, ATYP_FQDN, 1, (gchar *)&data, 0);
-}
-
-static gboolean anon_acceptable(struct socks_client *cl)
-{
- return FALSE; /* Don't allow anonymous connects */
-}
-
-static gboolean pass_acceptable(struct socks_client *cl)
-{
- return TRUE; /* FIXME: Check whether there is a password specified */
-}
-
-static gboolean pass_handle_data(struct socks_client *cl)
-{
- GList *gl;
- gchar header[2];
- gsize read;
- GIOStatus status;
- gchar uname[0x100], pass[0x100];
-
- status = g_io_channel_read_chars(cl->connection, header, 2, &read, NULL);
- if (status != G_IO_STATUS_NORMAL) {
- return FALSE;
- }
-
- if (header[0] != SOCKS_VERSION && header[0] != 0x1) {
- log_global(LOG_WARNING, "Client suddenly changed socks uname/pwd version to %x", header[0]);
- return socks_error(cl->connection, REP_GENERAL_FAILURE);
- }
-
- status = g_io_channel_read_chars(cl->connection, uname, header[1], &read, NULL);
- if (status != G_IO_STATUS_NORMAL) {
- return FALSE;
- }
-
- uname[(guint8)header[1]] = '\0';
-
- status = g_io_channel_read_chars(cl->connection, header, 1, &read, NULL);
- if (status != G_IO_STATUS_NORMAL) {
- return FALSE;
- }
-
- status = g_io_channel_read_chars(cl->connection, pass, header[0], &read, NULL);
- if (status != G_IO_STATUS_NORMAL) {
- return FALSE;
- }
-
- pass[(guint8)header[0]] = '\0';
-
- header[0] = 0x1;
- header[1] = 0x0; /* set to non-zero if invalid */
-
- for (gl = allow_rules; gl; gl = gl->next)
- {
- struct allow_rule *r = gl->data;
-
- if (r->password == NULL || r->username == NULL)
- continue;
-
- if (strcmp(r->username, uname))
- continue;
-
- if (strcmp(r->password, pass))
- continue;
-
- break;
- }
-
- header[1] = (gl == NULL);
-
- status = g_io_channel_write_chars(cl->connection, header, 2, &read, NULL);
- if (status != G_IO_STATUS_NORMAL) {
- return FALSE;
- }
-
- g_io_channel_flush(cl->connection, NULL);
-
- if (header[1] == 0x0) {
- cl->state = STATE_NORMAL;
- return TRUE;
- } else {
- log_global(LOG_WARNING, "Password mismatch for user %s", uname);
- return FALSE;
- }
-}
-
-static struct network *socks_map_network_fqdn(struct global *global, const char *hostname, guint16 port)
-{
- return find_network_by_hostname(global, hostname, port, TRUE);
-}
-
-struct socks_method {
- gint id;
- const char *name;
- gboolean (*acceptable) (struct socks_client *cl);
- gboolean (*handle_data) (struct socks_client *cl);
-} socks_methods[] = {
- { SOCKS_METHOD_NOAUTH, "none", anon_acceptable, NULL },
- { SOCKS_METHOD_GSSAPI, "gssapi", NULL, NULL },
- { SOCKS_METHOD_USERNAME_PW, "username/password", pass_acceptable, pass_handle_data },
- { -1, NULL, NULL }
-};
-
-static gboolean handle_client_data (GIOChannel *ioc, GIOCondition o, gpointer data)
-{
- struct socks_client *cl = data;
- GIOStatus status;
- int i;
-
- if (o == G_IO_HUP) {
- pending_clients = g_list_remove(pending_clients, cl);
- g_free(cl->clientname);
- g_free(cl);
- return FALSE;
- }
-
- if (cl->state == STATE_NEW) {
- gchar header[2];
- gchar methods[0x100];
- gsize read;
- status = g_io_channel_read_chars(ioc, header, 2, &read, NULL);
- if (status != G_IO_STATUS_NORMAL) {
- return FALSE;
- }
-
- if (header[0] != SOCKS_VERSION)
- {
- log_global(LOG_WARNING, "Ignoring client with socks version %d", header[0]);
- return FALSE;
- }
-
- /* None by default */
- cl->method = NULL;
-
- status = g_io_channel_read_chars(ioc, methods, header[1], &read, NULL);
- if (status != G_IO_STATUS_NORMAL) {
- return FALSE;
- }
- for (i = 0; i < header[1]; i++) {
- int j;
- for (j = 0; socks_methods[j].id != -1; j++)
- {
- if (socks_methods[j].id == methods[i] &&
- socks_methods[j].acceptable &&
- socks_methods[j].acceptable(cl)) {
- cl->method = &socks_methods[j];
- break;
- }
- }
- }
-
- header[0] = SOCKS_VERSION;
- header[1] = cl->method?cl->method->id:SOCKS_METHOD_NOACCEPTABLE;
-
- status = g_io_channel_write_chars(ioc, header, 2, &read, NULL);
- if (status != G_IO_STATUS_NORMAL) {
- return FALSE;
- }
-
- g_io_channel_flush(ioc, NULL);
-
- if (!cl->method) {
- log_global(LOG_WARNING, "Refused client because no valid method was available");
- return FALSE;
- }
-
- log_global(LOG_INFO, "Accepted socks client authenticating using %s", cl->method->name);
-
- if (!cl->method->handle_data) {
- cl->state = STATE_NORMAL;
- } else {
- cl->state = STATE_AUTH;
- }
- } else if (cl->state == STATE_AUTH) {
- return cl->method->handle_data(cl);
- } else if (cl->state == STATE_NORMAL) {
- gchar header[4];
- gsize read;
-
- status = g_io_channel_read_chars(ioc, header, 4, &read, NULL);
- if (status != G_IO_STATUS_NORMAL) {
- return FALSE;
- }
-
- if (header[0] != SOCKS_VERSION) {
- log_global(LOG_WARNING, "Client suddenly changed socks version to %x", header[0]);
- return socks_error(ioc, REP_GENERAL_FAILURE);
- }
-
- if (header[1] != CMD_CONNECT) {
- log_global(LOG_WARNING, "Client used unknown command %x", header[1]);
- return socks_error(ioc, REP_CMD_NOT_SUPPORTED);
- }
-
- /* header[2] is reserved */
-
- switch (header[3]) {
- case ATYP_IPV4:
- return socks_error(ioc, REP_ATYP_NOT_SUPPORTED);
-
- case ATYP_IPV6:
- return socks_error(ioc, REP_ATYP_NOT_SUPPORTED);
-
- case ATYP_FQDN:
- {
- char hostname[0x100];
- guint16 port;
- char *desc;
- struct network *result;
-
- status = g_io_channel_read_chars(ioc, header, 1, &read, NULL);
- status = g_io_channel_read_chars(ioc, hostname, header[0], &read, NULL);
- hostname[(guint8)header[0]] = '\0';
-
- status = g_io_channel_read_chars(ioc, header, 2, &read, NULL);
- port = ntohs(*(guint16 *)header);
-
- log_global(LOG_INFO, "Request to connect to %s:%d", hostname, port);
-
- result = socks_map_network_fqdn(cl->global, hostname, port);
-
- if (!result) {
- log_global(LOG_WARNING, "Unable to return network matching %s:%d", hostname, port);
- return socks_error(ioc, REP_NET_UNREACHABLE);
- }
-
- if (result->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED &&
- !connect_network(result)) {
- log_network(LOG_ERROR, result, "Unable to connect");
- return socks_error(ioc, REP_NET_UNREACHABLE);
- }
-
- if (result->config->type == NETWORK_TCP) {
-#ifdef HAVE_IPV6
- struct sockaddr_in6 *name6;
-#endif
- struct sockaddr_in *name4;
- int atyp, len, port;
- gchar *data;
-
-#ifdef HAVE_IPV6
- name6 = (struct sockaddr_in6 *)result->connection.data.tcp.local_name;
-#endif
- name4 = (struct sockaddr_in *)result->connection.data.tcp.local_name;
-
- if (name4->sin_family == AF_INET) {
- atyp = ATYP_IPV4;
- data = (gchar *)&name4->sin_addr;
- len = 4;
- port = name4->sin_port;
-#ifdef HAVE_IPV6
- } else if (name6->sin6_family == AF_INET6) {
- atyp = ATYP_IPV6;
- data = (gchar *)&name6->sin6_addr;
- len = 16;
- port = name6->sin6_port;
-#endif
- } else {
- log_network(LOG_ERROR, result, "Unable to obtain local address for connection to server");
- return socks_error(ioc, REP_NET_UNREACHABLE);
- }
-
- socks_reply(ioc, REP_OK, atyp, len, data, port);
-
- } else {
- gchar *data = g_strdup("xlocalhost");
- data[0] = strlen(data+1);
-
- socks_reply(ioc, REP_OK, ATYP_FQDN, data[0]+1, data, 1025);
- }
-
- desc = g_io_channel_ip_get_description(ioc);
- client_init(result, ioc, desc);
- g_free(desc);
-
- pending_clients = g_list_remove(pending_clients, cl);
-
- g_free(cl->clientname);
- g_free(cl);
-
- return FALSE;
- }
- default:
- return socks_error(ioc, REP_ATYP_NOT_SUPPORTED);
-
- }
- }
-
- return TRUE;
-}
-
-static gboolean handle_new_client (GIOChannel *ioc, GIOCondition o, gpointer data)
-{
- /* Spawn off new client */
- struct socks_client *cl;
- int ns;
-
- cl = g_new0(struct socks_client, 1);
- cl->global = data;
-#ifdef HAVE_IPV6
- cl->clientname_len = sizeof(struct sockaddr_in6);
-#else
- cl->clientname_len = sizeof(struct sockaddr_in);
-#endif
- cl->clientname = g_malloc(cl->clientname_len);
-
- ns = accept(g_io_channel_unix_get_fd(ioc), cl->clientname, &cl->clientname_len);
- if (!ns) {
- g_free(cl->clientname);
- g_free(cl);
- log_global(LOG_ERROR, "Unable to accept connection");
- return TRUE;
- }
-
- cl->connection = g_io_channel_unix_new(ns);
- g_io_channel_set_close_on_unref(cl->connection, TRUE);
- cl->state = STATE_NEW;
- g_io_channel_set_encoding(cl->connection, NULL, NULL);
- g_io_channel_set_flags(cl->connection, G_IO_FLAG_NONBLOCK, NULL);
- cl->watch_id = g_io_add_watch(cl->connection, G_IO_IN | G_IO_HUP, handle_client_data, cl);
- g_io_channel_unref(cl->connection);
-
- pending_clients = g_list_append(pending_clients, cl);
-
- return TRUE;
-}
-
-void kill_pending_client(struct socks_client *sc)
-{
- g_source_remove(sc->watch_id);
-
- g_free(sc->clientname);
- g_free(sc);
-
- pending_clients = g_list_remove(pending_clients, sc);
-}
-
-static void fini_plugin(void)
-{
- while (pending_clients) {
- struct socks_client *sc = pending_clients->data;
- kill_pending_client(sc);
- }
-
- /* Close port */
- if (server_channel_in != -1)
- g_source_remove(server_channel_in);
-}
-
-static void load_config(struct global *global)
-{
- int sock;
- const int on = 1;
- struct sockaddr_in addr;
- guint16 port;
- GKeyFile *kf = global->config->keyfile;
- gsize size, i;
- char **allows;
-
- allows = g_key_file_get_string_list(kf, "socks", "allow", &size, NULL);
-
- if (allows == NULL)
- return;
-
- for (i = 0; i < size; i++) {
- struct allow_rule *r = g_new0(struct allow_rule, 1);
- char **parts = g_strsplit(allows[i], ":", 2);
-
- r->username = parts[0];
- r->password = parts[1];
-
- g_free(parts);
- allow_rules = g_list_append(allow_rules, r);
- }
-
- g_strfreev(allows);
-
- if (g_key_file_has_key(kf, "socks", "port", NULL))
- port = g_key_file_get_integer(kf, "socks", "port", NULL);
- else
- port = DEFAULT_SOCKS_PORT;
-
- sock = socket(PF_INET, SOCK_STREAM, 0);
- if (sock < 0) {
- log_global(LOG_ERROR, "error creating socket: %s", strerror(errno));
- return;
- }
-
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
-
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
-
- if (bind (sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
- log_global(LOG_ERROR, "Unable to bind to port %d: %s", port, strerror(errno));
- return;
- }
-
- if (listen(sock, 5) < 0) {
- log_global(LOG_ERROR, "error listening on socket: %s", strerror(errno));
- return;
- }
-
- server_channel = g_io_channel_unix_new(sock);
- g_io_channel_set_close_on_unref(server_channel, TRUE);
-
- if (server_channel == NULL) {
- log_global(LOG_ERROR, "Unable to create GIOChannel for server socket");
- return;
- }
-
- server_channel_in = g_io_add_watch(server_channel, G_IO_IN, handle_new_client, global);
- g_io_channel_unref(server_channel);
-
- log_global(LOG_INFO, "Listening for SOCKS connections on port %d", port);
-}
-
-static gboolean init_plugin(void)
-{
- register_load_config_notify(load_config);
- atexit(fini_plugin);
- return TRUE;
-}
-
-struct plugin_ops plugin = {
- .name = "socks",
- .version = 0,
- .init = init_plugin,
-};
"Please register only once per session", NULL);
} else if (!g_strcasecmp(l->args[0], "CTRLPROXY")) {
admin_process_command(c, l, 1);
- } else if (!c->network->global->config->admin_noprivmsg &&
+ } else if (c->network->global->config->admin_user != NULL &&
!g_strcasecmp(l->args[0], "PRIVMSG") &&
- !g_strcasecmp(l->args[1], "CTRLPROXY")) {
+ !g_strcasecmp(l->args[1], c->network->global->config->admin_user)) {
admin_process_command(c, l, 2);
} else if (!g_strcasecmp(l->args[0], "PRIVMSG") && l->argc > 2 &&
l->args[2][0] == '\001' &&
#include "irc.h"
#include "listener.h"
#include "ssl.h"
+#include "socks.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
static GIConv iconv = (GIConv)-1;
+static gboolean kill_pending_client(struct pending_client *pc)
+{
+ pc->listener->pending = g_list_remove(pc->listener->pending, pc);
+
+ g_source_remove(pc->watch_id);
+
+ g_free(pc->clientname);
+
+ g_free(pc);
+
+ return TRUE;
+}
+
static gboolean handle_client_receive(GIOChannel *c, GIOCondition condition, gpointer data)
{
struct line *l;
- struct listener *listener = data;
+ struct pending_client *pc = data;
GError *error = NULL;
GIOStatus status;
+ if (condition == G_IO_HUP) {
+ kill_pending_client(pc);
+ return FALSE;
+ }
+
g_assert(c != NULL);
while ((status = irc_recv_line(c, iconv, &error, &l)) == G_IO_STATUS_NORMAL) {
continue;
}
- if (listener->config->password == NULL) {
- log_network(LOG_WARNING, listener->network, "No password set, allowing client _without_ authentication!");
+ if (pc->listener->config->password == NULL) {
+ log_network(LOG_WARNING, pc->listener->network, "No password set, allowing client _without_ authentication!");
}
if (!g_strcasecmp(l->args[0], "PASS")) {
char *desc;
- struct network *n = listener->network;
+ struct network *n = pc->listener->network;
gboolean authenticated = FALSE;
- if (listener->config->password == NULL) {
+ if (pc->listener->config->password == NULL) {
authenticated = TRUE;
- } else if (strcmp(l->args[1], listener->config->password) == 0) {
+ } else if (strcmp(l->args[1], pc->listener->config->password) == 0) {
authenticated = TRUE;
- } else if (strncmp(l->args[1], listener->config->password, strlen(listener->config->password)) == 0 &&
- l->args[1][strlen(listener->config->password)+1] == ':') {
+ } else if (strncmp(l->args[1], pc->listener->config->password, strlen(pc->listener->config->password)) == 0 &&
+ l->args[1][strlen(pc->listener->config->password)+1] == ':') {
authenticated = TRUE;
- n = find_network(listener->global, l->args[1]+strlen(listener->config->password)+1);
+ n = find_network(pc->listener->global, l->args[1]+strlen(pc->listener->config->password)+1);
if (n == NULL) {
- log_network(LOG_WARNING, listener->network, "User tried to log in with incorrect password!");
+ log_network(LOG_WARNING, pc->listener->network, "User tried to log in with incorrect password!");
irc_sendf(c, iconv, NULL, ":%s %d %s :Password mismatch", get_my_hostname(), ERR_PASSWDMISMATCH, "*");
free_line(l);
desc = g_io_channel_ip_get_description(c);
client_init(n, c, desc);
+ kill_pending_client(pc);
+
free_line(l);
return FALSE;
} else {
- log_network(LOG_WARNING, listener->network, "User tried to log in with incorrect password!");
+ log_network(LOG_WARNING, pc->listener->network, "User tried to log in with incorrect password!");
irc_sendf(c, iconv, NULL, ":%s %d %s :Password mismatch", get_my_hostname(), ERR_PASSWDMISMATCH, "*");
free_line(l);
static gboolean handle_new_client(GIOChannel *c_server, GIOCondition condition, void *_listener)
{
struct listener *listener = _listener;
+ struct pending_client *pc;
GIOChannel *c;
int sock = accept(g_io_channel_unix_get_fd(c_server), NULL, 0);
g_io_channel_set_close_on_unref(c, TRUE);
g_io_channel_set_encoding(c, NULL, NULL);
g_io_channel_set_flags(c, G_IO_FLAG_NONBLOCK, NULL);
- g_io_add_watch(c, G_IO_IN, handle_client_receive, listener);
+ pc = g_new0(struct pending_client, 1);
+ pc->connection = c;
+ pc->watch_id = g_io_add_watch(c, G_IO_IN | G_IO_HUP, handle_client_receive, pc);
+ pc->listener = listener;
g_io_channel_unref(c);
- listener->pending = g_list_append(listener->pending, c);
+ listener->pending = g_list_append(listener->pending, pc);
return TRUE;
}
stop_listener(l);
}
}
+
+
+/* TODO:
+ * - support for ipv4 and ipv6 atyp's
+ * - support for gssapi method
+ * - support for ident method
+ */
+
+static gboolean socks_reply(GIOChannel *ioc, guint8 err, guint8 atyp, guint8 data_len, gchar *data, guint16 port)
+{
+ gchar *header = g_new0(gchar, 7 + data_len);
+ GIOStatus status;
+ gsize read;
+
+ header[0] = SOCKS_VERSION;
+ header[1] = err;
+ header[2] = 0x0; /* Reserved */
+ header[3] = atyp;
+ memcpy(header+4, data, data_len);
+ *((guint16 *)(header+4+data_len)) = htons(port);
+
+ status = g_io_channel_write_chars(ioc, header, 6 + data_len, &read, NULL);
+
+ g_free(header);
+
+ g_io_channel_flush(ioc, NULL);
+
+ return (err == REP_OK);
+}
+
+static gboolean socks_error(GIOChannel *ioc, guint8 err)
+{
+ guint8 data = 0x0;
+ return socks_reply(ioc, err, ATYP_FQDN, 1, (gchar *)&data, 0);
+}
+
+static gboolean anon_acceptable(struct pending_client *cl)
+{
+ return FALSE; /* Don't allow anonymous connects */
+}
+
+static gboolean pass_acceptable(struct pending_client *cl)
+{
+ return TRUE; /* FIXME: Check whether there is a password specified */
+}
+
+static gboolean pass_handle_data(struct pending_client *cl)
+{
+ GList *gl;
+ gchar header[2];
+ gsize read;
+ GIOStatus status;
+ gchar uname[0x100], pass[0x100];
+
+ status = g_io_channel_read_chars(cl->connection, header, 2, &read, NULL);
+ if (status != G_IO_STATUS_NORMAL) {
+ return FALSE;
+ }
+
+ if (header[0] != SOCKS_VERSION && header[0] != 0x1) {
+ log_global(LOG_WARNING, "Client suddenly changed socks uname/pwd version to %x", header[0]);
+ return socks_error(cl->connection, REP_GENERAL_FAILURE);
+ }
+
+ status = g_io_channel_read_chars(cl->connection, uname, header[1], &read, NULL);
+ if (status != G_IO_STATUS_NORMAL) {
+ return FALSE;
+ }
+
+ uname[(guint8)header[1]] = '\0';
+
+ status = g_io_channel_read_chars(cl->connection, header, 1, &read, NULL);
+ if (status != G_IO_STATUS_NORMAL) {
+ return FALSE;
+ }
+
+ status = g_io_channel_read_chars(cl->connection, pass, header[0], &read, NULL);
+ if (status != G_IO_STATUS_NORMAL) {
+ return FALSE;
+ }
+
+ pass[(guint8)header[0]] = '\0';
+
+ header[0] = 0x1;
+ header[1] = 0x0; /* set to non-zero if invalid */
+
+ for (gl = cl->listener->config->allow_rules; gl; gl = gl->next)
+ {
+ struct allow_rule *r = gl->data;
+
+ if (r->password == NULL || r->username == NULL)
+ continue;
+
+ if (strcmp(r->username, uname))
+ continue;
+
+ if (strcmp(r->password, pass))
+ continue;
+
+ break;
+ }
+
+ header[1] = (gl == NULL);
+
+ status = g_io_channel_write_chars(cl->connection, header, 2, &read, NULL);
+ if (status != G_IO_STATUS_NORMAL) {
+ return FALSE;
+ }
+
+ g_io_channel_flush(cl->connection, NULL);
+
+ if (header[1] == 0x0) {
+ cl->socks.state = SOCKS_STATE_NORMAL;
+ return TRUE;
+ } else {
+ log_global(LOG_WARNING, "Password mismatch for user %s", uname);
+ return FALSE;
+ }
+}
+
+static struct socks_method {
+ gint id;
+ const char *name;
+ gboolean (*acceptable) (struct pending_client *cl);
+ gboolean (*handle_data) (struct pending_client *cl);
+} socks_methods[] = {
+ { SOCKS_METHOD_NOAUTH, "none", anon_acceptable, NULL },
+ { SOCKS_METHOD_GSSAPI, "gssapi", NULL, NULL },
+ { SOCKS_METHOD_USERNAME_PW, "username/password", pass_acceptable, pass_handle_data },
+ { -1, NULL, NULL }
+};
+
+static gboolean handle_client_data (GIOChannel *ioc, GIOCondition o, gpointer data)
+{
+ struct pending_client *cl = data;
+ GIOStatus status;
+ int i;
+
+ if (cl->socks.state == SOCKS_STATE_NEW) {
+ gchar header[2];
+ gchar methods[0x100];
+ gsize read;
+ status = g_io_channel_read_chars(ioc, header, 2, &read, NULL);
+ if (status != G_IO_STATUS_NORMAL) {
+ kill_pending_client(cl);
+ return FALSE;
+ }
+
+ if (header[0] != SOCKS_VERSION)
+ {
+ log_global(LOG_WARNING, "Ignoring client with socks version %d", header[0]);
+ kill_pending_client(cl);
+ return FALSE;
+ }
+
+ /* None by default */
+ cl->socks.method = NULL;
+
+ status = g_io_channel_read_chars(ioc, methods, header[1], &read, NULL);
+ if (status != G_IO_STATUS_NORMAL) {
+ kill_pending_client(cl);
+ return FALSE;
+ }
+ for (i = 0; i < header[1]; i++) {
+ int j;
+ for (j = 0; socks_methods[j].id != -1; j++)
+ {
+ if (socks_methods[j].id == methods[i] &&
+ socks_methods[j].acceptable &&
+ socks_methods[j].acceptable(cl)) {
+ cl->socks.method = &socks_methods[j];
+ break;
+ }
+ }
+ }
+
+ header[0] = SOCKS_VERSION;
+ header[1] = cl->socks.method?cl->socks.method->id:SOCKS_METHOD_NOACCEPTABLE;
+
+ status = g_io_channel_write_chars(ioc, header, 2, &read, NULL);
+ if (status != G_IO_STATUS_NORMAL) {
+ kill_pending_client(cl);
+ return FALSE;
+ }
+
+ g_io_channel_flush(ioc, NULL);
+
+ if (!cl->socks.method) {
+ log_global(LOG_WARNING, "Refused client because no valid method was available");
+ kill_pending_client(cl);
+ return FALSE;
+ }
+
+ log_global(LOG_INFO, "Accepted socks client authenticating using %s", cl->socks.method->name);
+
+ if (!cl->socks.method->handle_data) {
+ cl->socks.state = SOCKS_STATE_NORMAL;
+ } else {
+ cl->socks.state = SOCKS_STATE_AUTH;
+ }
+ } else if (cl->socks.state == SOCKS_STATE_AUTH) {
+ gboolean ret;
+ ret = cl->socks.method->handle_data(cl);
+ if (!ret)
+ kill_pending_client(cl);
+ return ret;
+ } else if (cl->socks.state == SOCKS_STATE_NORMAL) {
+ gchar header[4];
+ gsize read;
+
+ status = g_io_channel_read_chars(ioc, header, 4, &read, NULL);
+ if (status != G_IO_STATUS_NORMAL) {
+ kill_pending_client(cl);
+ return FALSE;
+ }
+
+ if (header[0] != SOCKS_VERSION) {
+ log_global(LOG_WARNING, "Client suddenly changed socks version to %x", header[0]);
+ kill_pending_client(cl);
+ return socks_error(ioc, REP_GENERAL_FAILURE);
+ }
+
+ if (header[1] != CMD_CONNECT) {
+ log_global(LOG_WARNING, "Client used unknown command %x", header[1]);
+ kill_pending_client(cl);
+ return socks_error(ioc, REP_CMD_NOT_SUPPORTED);
+ }
+
+ /* header[2] is reserved */
+
+ switch (header[3]) {
+ case ATYP_IPV4:
+ kill_pending_client(cl);
+ return socks_error(ioc, REP_ATYP_NOT_SUPPORTED);
+
+ case ATYP_IPV6:
+ kill_pending_client(cl);
+ return socks_error(ioc, REP_ATYP_NOT_SUPPORTED);
+
+ case ATYP_FQDN:
+ {
+ char hostname[0x100];
+ guint16 port;
+ char *desc;
+ struct network *result;
+
+ status = g_io_channel_read_chars(ioc, header, 1, &read, NULL);
+ status = g_io_channel_read_chars(ioc, hostname, header[0], &read, NULL);
+ hostname[(guint8)header[0]] = '\0';
+
+ status = g_io_channel_read_chars(ioc, header, 2, &read, NULL);
+ port = ntohs(*(guint16 *)header);
+
+ log_global(LOG_INFO, "Request to connect to %s:%d", hostname, port);
+
+ result = find_network_by_hostname(cl->listener->global, hostname, port, TRUE);
+
+ if (!result) {
+ log_global(LOG_WARNING, "Unable to return network matching %s:%d", hostname, port);
+ kill_pending_client(cl);
+ return socks_error(ioc, REP_NET_UNREACHABLE);
+ }
+
+ if (result->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED &&
+ !connect_network(result)) {
+ log_network(LOG_ERROR, result, "Unable to connect");
+ kill_pending_client(cl);
+ return socks_error(ioc, REP_NET_UNREACHABLE);
+ }
+
+ if (result->config->type == NETWORK_TCP) {
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 *name6;
+#endif
+ struct sockaddr_in *name4;
+ int atyp, len, port;
+ gchar *data;
+
+#ifdef HAVE_IPV6
+ name6 = (struct sockaddr_in6 *)result->connection.data.tcp.local_name;
+#endif
+ name4 = (struct sockaddr_in *)result->connection.data.tcp.local_name;
+
+ if (name4->sin_family == AF_INET) {
+ atyp = ATYP_IPV4;
+ data = (gchar *)&name4->sin_addr;
+ len = 4;
+ port = name4->sin_port;
+#ifdef HAVE_IPV6
+ } else if (name6->sin6_family == AF_INET6) {
+ atyp = ATYP_IPV6;
+ data = (gchar *)&name6->sin6_addr;
+ len = 16;
+ port = name6->sin6_port;
+#endif
+ } else {
+ log_network(LOG_ERROR, result, "Unable to obtain local address for connection to server");
+ kill_pending_client(cl);
+ return socks_error(ioc, REP_NET_UNREACHABLE);
+ }
+
+ socks_reply(ioc, REP_OK, atyp, len, data, port);
+
+ } else {
+ gchar *data = g_strdup("xlocalhost");
+ data[0] = strlen(data+1);
+
+ socks_reply(ioc, REP_OK, ATYP_FQDN, data[0]+1, data, 1025);
+ }
+
+ desc = g_io_channel_ip_get_description(ioc);
+ client_init(result, ioc, desc);
+ g_free(desc);
+
+ kill_pending_client(cl);
+
+ return FALSE;
+ }
+ default:
+ kill_pending_client(cl);
+ return socks_error(ioc, REP_ATYP_NOT_SUPPORTED);
+
+ }
+ }
+
+ return TRUE;
+}
#define G_MODULE_EXPORT
#endif
+
/**
* A listener.
*/
gint watch_id;
};
+struct socks_method;
+
+struct pending_client {
+ GIOChannel *connection;
+ const char *user;
+ const char *password;
+ gint watch_id;
+ struct sockaddr *clientname;
+ socklen_t clientname_len;
+ struct listener *listener;
+ struct {
+ struct socks_method *method;
+ enum state {
+ SOCKS_UNUSED = 0,
+ SOCKS_STATE_NEW = -1,
+ SOCKS_STATE_AUTH,
+ SOCKS_STATE_NORMAL
+ } state;
+ void *method_data;
+ } socks;
+};
+
G_MODULE_EXPORT struct listener *listener_init(struct global *global, struct listener_config *);
G_MODULE_EXPORT gboolean start_listener(struct listener *);
G_MODULE_EXPORT gboolean stop_listener(struct listener *);
#include <glib/gstdio.h>
#define DEFAULT_ADMIN_PORT 6680
+#define DEFAULT_SOCKS_PORT 1080
gboolean g_key_file_save_to_file(GKeyFile *kf, const gchar *file, GError **error)
{
default_password = g_key_file_get_string(cfg->keyfile, "listener", "password", NULL);
- g_key_file_set_boolean(cfg->keyfile, "listener", "auto", cfg->auto_listener);
-
- g_key_file_set_integer(cfg->keyfile, "listener", "autoport", cfg->listener_autoport);
+ if (cfg->auto_listener) {
+ g_key_file_set_boolean(cfg->keyfile, "listener", "auto", cfg->auto_listener);
+ g_key_file_set_integer(cfg->keyfile, "listener", "autoport", cfg->listener_autoport);
+ }
filename = g_build_filename(path, "listener", NULL);
for (gl = cfg->listeners; gl; gl = gl->next) {
struct listener_config *l = gl->data;
- char *tmp;
- if (!l->address)
- tmp = g_strdup(l->port);
- else
- tmp = g_strdup_printf("%s:%s", l->address, l->port);
+ if (l->is_default) {
+ g_key_file_set_string(cfg->keyfile, "global", "port", l->port);
+ if (l->address != NULL)
+ g_key_file_set_string(cfg->keyfile, "global", "bind", l->address);
+ if (l->password != NULL)
+ g_key_file_set_string(cfg->keyfile, "global", "password", l->password);
- if (l->password != NULL &&
- !(default_password != NULL && strcmp(l->password, default_password) == 0))
- g_key_file_set_string(kf, tmp, "password", l->password);
+ g_key_file_set_boolean(cfg->keyfile, "global", "ssl", l->ssl);
+ } else {
+ char *tmp;
+ if (!l->address)
+ tmp = g_strdup(l->port);
+ else
+ tmp = g_strdup_printf("%s:%s", l->address, l->port);
- if (l->network != NULL) {
- g_key_file_set_string(kf, tmp, "network", l->network);
- }
+ if (l->password != NULL &&
+ !(default_password != NULL && strcmp(l->password, default_password) == 0))
+ g_key_file_set_string(kf, tmp, "password", l->password);
- g_key_file_set_boolean(kf, tmp, "ssl", l->ssl);
+ if (l->network != NULL) {
+ g_key_file_set_string(kf, tmp, "network", l->network);
+ }
- g_free(tmp);
+ g_key_file_set_boolean(kf, tmp, "ssl", l->ssl);
+
+ g_free(tmp);
+ }
}
if (!g_key_file_save_to_file(kf, filename, &error)) {
cfg->keyfile = g_key_file_new();
g_key_file_set_boolean(cfg->keyfile, "global", "autosave", cfg->autosave);
- g_key_file_set_boolean(cfg->keyfile, "admin", "without_privmsg", cfg->admin_noprivmsg);
- g_key_file_set_boolean(cfg->keyfile, "admin", "log", cfg->admin_log);
+ if (cfg->admin_user != NULL)
+ g_key_file_set_string(cfg->keyfile, "global", "admin-user", cfg->admin_user);
+ g_key_file_set_boolean(cfg->keyfile, "global", "admin-log", cfg->admin_log);
g_key_file_set_integer(cfg->keyfile, "global", "max_who_age", cfg->max_who_age);
if (cfg->client_charset != NULL)
return nc;
}
+static void config_load_listeners_socks(struct ctrlproxy_config *cfg)
+{
+ char **allows;
+ gsize size, i;
+ GKeyFile *kf = cfg->keyfile;
+ struct listener_config *l;
+
+ allows = g_key_file_get_string_list(kf, "socks", "allow", &size, NULL);
+
+ if (allows == NULL)
+ return;
+
+ l = g_new0(struct listener_config, 1);
+
+ if (g_key_file_has_key(kf, "socks", "port", NULL))
+ l->port = g_key_file_get_string(kf, "socks", "port", NULL);
+ else
+ l->port = g_strdup_printf("%d", DEFAULT_SOCKS_PORT);
+
+ /* We can use the socks listener as default listener, if there was
+ * no default listener specified */
+ if (cfg->listeners == NULL ||
+ !((struct listener_config *)cfg->listeners->data)->is_default)
+ l->is_default = TRUE;
+
+ g_key_file_remove_key(kf, "socks", "port", NULL);
+
+ for (i = 0; i < size; i++) {
+ struct allow_rule *r = g_new0(struct allow_rule, 1);
+ char **parts = g_strsplit(allows[i], ":", 2);
+
+ r->username = parts[0];
+ r->password = parts[1];
+
+ g_free(parts);
+ l->allow_rules = g_list_append(l->allow_rules, r);
+ }
+
+ g_strfreev(allows);
+
+ cfg->listeners = g_list_append(cfg->listeners, l);
+}
+
static void config_load_listeners(struct ctrlproxy_config *cfg)
{
char *filename = g_build_filename(cfg->config_dir, "listener", NULL);
if (g_key_file_has_key(cfg->keyfile, "listener", "autoport", NULL))
cfg->listener_autoport = g_key_file_get_integer(cfg->keyfile, "listener", "autoport", NULL);
+ if (g_key_file_has_key(cfg->keyfile, "global", "port", NULL)) {
+ struct listener_config *l = g_new0(struct listener_config, 1);
+ l->port = g_key_file_get_string(cfg->keyfile, "global", "port", NULL);
+ l->password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
+ l->address = g_key_file_get_string(cfg->keyfile, "global", "bind", NULL);
+ l->ssl = g_key_file_has_key(cfg->keyfile, "global", "ssl", NULL) &&
+ g_key_file_get_boolean(cfg->keyfile, "global", "ssl", NULL);
+ l->is_default = TRUE;
+
+ cfg->listeners = g_list_append(cfg->listeners, l);
+ }
+
kf = g_key_file_new();
if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {
g_free(filename);
}
-
-
static void config_load_networks(struct ctrlproxy_config *cfg)
{
char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
if (!g_file_test(cfg->motd_file, G_FILE_TEST_EXISTS))
log_global(LOG_ERROR, "Can't open MOTD file '%s' for reading", cfg->motd_file);
- if (g_key_file_has_key(kf, "admin", "without_privmsg", NULL))
- cfg->admin_noprivmsg = g_key_file_get_boolean(kf, "admin", "without_privmsg", NULL);
+ if (g_key_file_has_key(kf, "admin", "without_privmsg", NULL)) {
+ if (g_key_file_get_boolean(kf, "admin", "without_privmsg", NULL)) {
+ cfg->admin_user = NULL;
+ } else {
+ cfg->admin_user = g_strdup("ctrlproxy");
+ }
+ g_key_file_remove_key(kf, "admin", "without_privmsg", NULL);
+ }
+
+ if (g_key_file_has_key(kf, "global", "admin-user", NULL)) {
+ cfg->admin_user = g_key_file_get_string(kf, "global", "admin-user", NULL);
+ }
cfg->admin_log = TRUE;
if (g_key_file_has_key(kf, "admin", "log", NULL) && !g_key_file_get_boolean(kf, "admin", "log", NULL))
cfg->admin_log = FALSE;
-
+ g_key_file_remove_key(kf, "admin", "log", NULL);
+ if (g_key_file_has_key(kf, "global", "admin-log", NULL) && !g_key_file_get_boolean(kf, "global", "admin-log", NULL))
+ cfg->admin_log = FALSE;
for (gl = cfg->networks; gl; gl = gl->next) {
struct network_config *nc = gl->data;
config_load_networks(cfg);
config_load_listeners(cfg);
+ config_load_listeners_socks(cfg);
size = 0;
autoconnect_list = g_key_file_get_string_list(kf, "global", "autoconnect", &size, NULL);
gboolean create_configuration(const char *config_dir)
{
- GKeyFile *kf;
struct global *global;
char port[250];
- char *pass, *listenerfile;
- GError *error = NULL;
+ struct listener_config *l;
+ char *pass;
if (g_file_test(config_dir, G_FILE_TEST_IS_DIR)) {
fprintf(stderr, "%s already exists\n", config_dir);
global->config->config_dir = g_strdup(config_dir);
save_configuration(global->config, config_dir);
- kf = g_key_file_new();
-
snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
printf("Please specify port the administration interface should listen on.\n"
"Prepend with a colon to listen on a specific address.\n"
if (strlen(port) == 0)
snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
+ l = g_new0(struct listener_config, 1);
pass = getpass("Please specify a password for the administration interface: ");
- g_key_file_set_string(kf, port, "network", "admin");
+ l->port = port;
if (!strcmp(pass, "")) {
fprintf(stderr, "Warning: no password specified. Authentication disabled!\n");
} else {
- g_key_file_set_string(kf, port, "password", pass);
+ l->password = pass;
}
- listenerfile = g_build_filename(config_dir, "listener", NULL);
-
- if (!g_key_file_save_to_file(kf, listenerfile, &error)) {
- fprintf(stderr, "Error saving %s: %s\n", listenerfile, error->message);
- return FALSE;
- }
+ global->config->listeners = g_list_append(global->config->listeners, l);
return TRUE;
}
} type_settings;
};
+struct allow_rule {
+ const char *username;
+ const char *password;
+};
+
struct listener_config {
gboolean ssl;
gpointer ssl_credentials;
char *address;
char *port;
char *network;
+ GList *allow_rules;
+ gboolean is_default; /* Whether this is the "default" listener, stored in ~/.ctrlproxy/config */
};
/**
char *linestack_backend;
char *client_charset;
gboolean admin_log;
- gboolean admin_noprivmsg;
+ char *admin_user;
gboolean report_time;
int max_who_age;
GKeyFile *keyfile;
--- /dev/null
+/*
+ ctrlproxy: A modular IRC proxy
+ (c) 2005-2007 Jelmer Vernooij <jelmer@nl.linux.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef __SOCKS_H__
+#define __SOCKS_H__
+
+#define SOCKS_VERSION 0x05
+
+#define SOCKS_METHOD_NOAUTH 0x00
+#define SOCKS_METHOD_GSSAPI 0x01
+#define SOCKS_METHOD_USERNAME_PW 0x02
+#define SOCKS_METHOD_NOACCEPTABLE 0xFF
+
+#define ATYP_IPV4 0x01
+#define ATYP_FQDN 0x03
+#define ATYP_IPV6 0x04
+
+#define CMD_CONNECT 0x01
+#define CMD_BIND 0x02
+#define CMD_UDP_ASSOCIATE 0x03
+
+#define REP_OK 0x00
+#define REP_GENERAL_FAILURE 0x01
+#define REP_NOT_ALLOWED 0x02
+#define REP_NET_UNREACHABLE 0x03
+#define REP_HOST_UNREACHABLE 0x04
+#define REP_CONN_REFUSED 0x05
+#define REP_TTL_EXPIRED 0x06
+#define REP_CMD_NOT_SUPPORTED 0x07
+#define REP_ATYP_NOT_SUPPORTED 0x08
+
+#endif /* __SOCKS_H__ */