2 ctrlproxy: A modular IRC proxy
3 (c) 2005-2006 Jelmer Vernooij <jelmer@nl.linux.org>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #include "internals.h"
32 #ifdef HAVE_SYS_SOCKET_H
33 #include <sys/socket.h>
37 void default_listener_log_fn(enum log_level l, const struct irc_listener *listener, const char *ret)
39 if (listener->network != NULL)
40 network_log(l, listener->network, "%s", ret);
42 log_global(l, "%s", ret);
46 gboolean default_socks_gssapi(struct pending_client *cl, gss_name_t authn_name)
48 /* FIXME: Check if principal matches own user name */
53 static gboolean listener_check_username_password(struct irc_listener *listener,
54 const char *username, const char *password)
58 if (listener->config != NULL) {
59 for (gl = listener->config->allow_rules; gl; gl = gl->next)
61 struct allow_rule *r = gl->data;
63 if (r->password == NULL || r->username == NULL)
66 if (strcmp(r->username, username))
69 if (strcmp(r->password, password))
75 if (listener->config->password != NULL) {
76 return (strcmp(listener->config->password, password) == 0);
80 if (listener->global->config->password != NULL) {
81 return (strcmp(listener->global->config->password, password) == 0);
84 listener_log(LOG_WARNING, listener, "No password set, allowing client _without_ authentication!");
89 gboolean default_socks_auth_simple(struct pending_client *cl, const char *username, const char *password,
90 gboolean (*on_known)(struct pending_client *, gboolean))
92 if (listener_check_username_password(cl->listener, username, password))
93 return on_known(cl, TRUE);
95 return on_known(cl, FALSE);
98 gboolean default_socks_connect_fqdn (struct pending_client *cl, const char *hostname, uint16_t port)
101 struct irc_network *result;
102 struct network_config *nc;
104 listener_log(LOG_INFO, cl->listener, "Request to connect to %s:%d", hostname, port);
106 result = find_network_by_hostname(cl->listener->global, hostname, port,
107 cl->listener->global->config->create_implicit, NULL);
109 if (result == NULL) {
110 listener_log(LOG_WARNING, cl->listener, "Unable to return network matching %s:%d", hostname, port);
111 return listener_socks_error(cl, REP_NET_UNREACHABLE);
114 if (result->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED &&
115 !connect_network(result)) {
116 network_log(LOG_ERROR, result, "Unable to connect");
117 return listener_socks_error(cl, REP_NET_UNREACHABLE);
120 nc = result->private_data;
122 if (nc->type == NETWORK_TCP) {
123 struct sockaddr *name;
127 name = (struct sockaddr *)result->connection.data.tcp.local_name;
129 if (name != NULL && name->sa_family == AF_INET) {
130 struct sockaddr_in *name4 = (struct sockaddr_in *)name;
132 data = (gchar *)&name4->sin_addr;
134 port = name4->sin_port;
135 } else if (name != NULL && name->sa_family == AF_INET6) {
136 struct sockaddr_in6 *name6 = (struct sockaddr_in6 *)name;
138 data = (gchar *)&name6->sin6_addr;
140 port = name6->sin6_port;
142 network_log(LOG_ERROR, result, "Unable to obtain local address for connection to server");
143 return listener_socks_error(cl, REP_NET_UNREACHABLE);
146 listener_socks_reply(cl, REP_OK, atyp, len, data, port);
149 gchar *data = g_strdup("xlocalhost");
150 data[0] = strlen(data+1);
152 listener_socks_reply(cl, REP_OK, ATYP_FQDN, data[0]+1, data, 1025);
155 desc = g_io_channel_ip_get_description(cl->connection);
157 if (cl->listener == cl->listener->global->unix_domain_socket_listener)
158 desc = g_strdup("Unix domain socket socks client");
160 desc = g_strdup("socks client");
162 client_init_iochannel(result, cl->connection, desc);
168 void listener_log(enum log_level l, const struct irc_listener *listener,
169 const char *fmt, ...)
174 if (listener->log_fn == NULL)
181 ret = g_strdup_vprintf(fmt, ap);
184 listener->log_fn(l, listener, ret);
189 static gboolean handle_client_line(struct pending_client *pc, const struct irc_line *l)
191 struct irc_listener *listener = pc->listener;
193 if (l == NULL || l->args[0] == NULL) {
197 if (!g_strcasecmp(l->args[0], "PASS")) {
198 const char *networkname = NULL;
199 struct irc_network *n = listener->network;
200 gboolean authenticated = FALSE;
201 char *actual_password;
203 if (strchr(l->args[1], ':') != NULL) {
204 char *p = strchr(l->args[1], ':');
205 actual_password = g_strndup(l->args[1], p-l->args[1]);
208 actual_password = g_strdup(l->args[1]);
212 authenticated = listener_check_username_password(listener, NULL, actual_password);
214 g_free(actual_password);
217 listener_log(LOG_INFO, listener, "Client successfully authenticated");
219 if (networkname != NULL) {
220 n = find_network_by_hostname(listener->global,
221 networkname, 6667, listener->global->config->create_implicit,
224 irc_sendf(pc->connection, listener->iconv, NULL,
225 ":%s %d %s :Password error: unable to find network",
226 get_my_hostname(), ERR_PASSWDMISMATCH, "*");
227 g_io_channel_flush(pc->connection, NULL);
231 if (n->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED &&
232 !connect_network(n)) {
233 irc_sendf(pc->connection, listener->iconv, NULL,
234 ":%s %d %s :Password error: unable to connect",
235 get_my_hostname(), ERR_PASSWDMISMATCH, "*");
236 g_io_channel_flush(pc->connection, NULL);
241 irc_sendf(pc->connection, listener->iconv, NULL,
242 "NOTICE AUTH :PASS OK");
243 g_io_channel_flush(pc->connection, NULL);
247 char *desc = g_io_channel_ip_get_description(pc->connection);
248 if (pc->listener == pc->listener->global->unix_domain_socket_listener)
249 desc = g_strdup("Unix domain socket client");
250 else if (desc == NULL)
252 client_init_iochannel(n, pc->connection, desc);
259 listener_log(LOG_WARNING, listener,
260 "User tried to log in with incorrect password!");
262 status = irc_sendf(pc->connection, listener->iconv, NULL,
263 ":%s %d %s :Password mismatch",
264 get_my_hostname(), ERR_PASSWDMISMATCH, "*");
265 g_io_channel_flush(pc->connection, NULL);
267 if (status != G_IO_STATUS_NORMAL) {
274 irc_sendf(pc->connection, listener->iconv, NULL, ":%s %d %s :You are not registered. Did you specify a password?", get_my_hostname(), ERR_NOTREGISTERED, "*");
275 g_io_channel_flush(pc->connection, NULL);
282 static int next_autoport(struct global *global)
284 return ++global->config->listener_autoport;
287 void free_listener(struct irc_listener *l)
289 l->global->listeners = g_list_remove(l->global->listeners, l);
291 irc_network_unref(l->network);
296 static struct irc_listener_ops default_listener_ops = {
297 .handle_client_line = handle_client_line,
298 .socks_auth_simple = default_socks_auth_simple,
300 .socks_gssapi = default_socks_gssapi,
302 .socks_connect_fqdn = default_socks_connect_fqdn,
305 struct irc_listener *listener_init(struct global *global, struct listener_config *cfg)
307 struct irc_listener *l = g_new0(struct irc_listener, 1);
311 l->ssl_credentials = cfg->ssl_credentials;
312 g_assert(!l->ssl || l->ssl_credentials != NULL);
314 l->ops = &default_listener_ops;
315 l->iconv = (GIConv)-1;
316 l->log_fn = default_listener_log_fn;
318 if (l->config->network != NULL) {
319 l->network = network_ref(find_network(global->networks, l->config->network));
320 if (l->network == NULL) {
321 listener_log(LOG_WARNING, l, "Network `%s' for listener not found", l->config->network);
325 l->global->listeners = g_list_append(l->global->listeners, l);
330 static void auto_add_listener(struct irc_network *n, void *private_data)
333 struct irc_listener *l;
334 struct listener_config *cfg;
335 struct network_config *nc = n->private_data;
337 /* See if there is already a listener for n */
338 for (gl = n->global->listeners; gl; gl = gl->next) {
341 if (l->network == n || l->network == NULL)
345 cfg = g_new0(struct listener_config, 1);
346 cfg->network = g_strdup(nc->name);
347 cfg->port = g_strdup_printf("%d", next_autoport(n->global));
348 l = listener_init(n->global, cfg);
349 listener_start_tcp(l, NULL, cfg->port);
352 gboolean init_listeners(struct global *global)
357 if (global->config->auto_listener)
358 register_new_network_notify(global, auto_add_listener, NULL);
360 for (gl = global->config->listeners; gl; gl = gl->next) {
361 struct listener_config *cfg = gl->data;
362 struct irc_listener *l = listener_init(global, cfg);
365 ret &= listener_start_tcp(l, cfg->address, cfg->port);
371 void fini_listeners(struct global *global)
374 for(gl = global->listeners; gl; gl = gl->next) {
375 struct irc_listener *l = gl->data;
382 gboolean start_unix_domain_socket_listener(struct global *global)
385 struct sockaddr_un un;
386 struct irc_listener *l = g_new0(struct irc_listener, 1);
389 sock = socket(PF_UNIX, SOCK_STREAM, 0);
391 log_global(LOG_ERROR, "error creating unix socket: %s", strerror(errno));
396 un.sun_family = AF_UNIX;
397 strncpy(un.sun_path, global->config->network_socket, sizeof(un.sun_path));
400 if (bind(sock, (struct sockaddr *)&un, sizeof(un)) < 0) {
401 log_global(LOG_ERROR, "unable to bind to %s: %s", un.sun_path, strerror(errno));
406 if (listen(sock, 5) < 0) {
407 log_global(LOG_ERROR, "error listening on socket: %s", strerror(errno));
412 l->ops = &default_listener_ops;
414 l->iconv = (GIConv)-1;
416 l->ssl_credentials = NULL;
418 l->log_fn = default_listener_log_fn;
420 ioc = g_io_channel_unix_new(sock);
423 log_global(LOG_ERROR, "Unable to create GIOChannel for unix server socket");
427 listener_add_iochannel(l, ioc, NULL, NULL);
429 global->unix_domain_socket_listener = l;
434 gboolean stop_unix_domain_socket_listener(struct global *global)
436 if (global->unix_domain_socket_listener != NULL)
437 listener_stop(global->unix_domain_socket_listener);
438 unlink(global->config->network_socket);