/*
ctrlproxy: A modular IRC proxy
(c) 2005-2006 Jelmer Vernooij <jelmer@nl.linux.org>
Manual listen on ports
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.
*/
#include "internals.h"
#include "irc.h"
#include "listener.h"
#include "ssl.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>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <netdb.h>
static GIConv iconv = (GIConv)-1;
static gboolean handle_client_receive(GIOChannel *c, GIOCondition condition, gpointer data)
{
struct line *l;
struct listener *listener = data;
GError *error = NULL;
GIOStatus status;
g_assert(c);
while ((status = irc_recv_line(c, iconv, &error, &l)) == G_IO_STATUS_NORMAL) {
g_assert(l);
if (!l->args[0]){
free_line(l);
continue;
}
if(!listener->password) {
log_network(LOG_WARNING, listener->network, "No password set, allowing client _without_ authentication!");
}
if(!g_strcasecmp(l->args[0], "PASS")) {
char *desc;
if (listener->password && strcmp(l->args[1], listener->password)) {
log_network(LOG_WARNING, 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);
return TRUE;
}
log_network (LOG_INFO, listener->network, "Client successfully authenticated");
desc = g_io_channel_ip_get_description(c);
client_init(listener->network, c, desc);
free_line(l);
return FALSE;
} else {
irc_sendf(c, iconv, NULL, ":%s %d %s :You are not registered", get_my_hostname(), ERR_NOTREGISTERED, "*");
}
free_line(l);
}
if (status != G_IO_STATUS_AGAIN)
return FALSE;
return TRUE;
}
static gboolean handle_new_client(GIOChannel *c_server, GIOCondition condition, void *_listener)
{
struct listener *listener = _listener;
GIOChannel *c;
int sock = accept(g_io_channel_unix_get_fd(c_server), NULL, 0);
if (sock < 0) {
log_global(LOG_WARNING, "Error accepting new connection: %s", strerror(errno));
return TRUE;
}
c = g_io_channel_unix_new(sock);
if (listener->ssl) {
#ifdef HAVE_GNUTLS
c = ssl_wrap_iochannel(c, SSL_TYPE_SERVER,
NULL, listener->ssl_credentials);
g_assert(c != NULL);
#else
log_global(LOG_WARNING, "SSL support not available, not listening for SSL connection");
#endif
}
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);
g_io_channel_unref(c);
listener->pending = g_list_append(listener->pending, c);
return TRUE;
}
/* FIXME: Store in global struct somehow */
static GList *listeners = NULL;
static int autoport = 6667;
static gboolean auto_listener = FALSE;
static GKeyFile *keyfile = NULL;
static int next_autoport()
{
return ++autoport;
}
gboolean start_listener(struct listener *l)
{
int sock = -1;
const int on = 1;
struct addrinfo *res, *all_res;
int error;
struct addrinfo hints;
struct listener_iochannel *lio;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
g_assert(!l->active);
error = getaddrinfo(l->address, l->port, &hints, &all_res);
if (error) {
log_global(LOG_ERROR, "Can't get address for %s:%s", l->address?l->address:"", l->port);
return FALSE;
}
for (res = all_res; res; res = res->ai_next) {
GIOChannel *ioc;
lio = g_new0(struct listener_iochannel, 1);
if (getnameinfo(res->ai_addr, res->ai_addrlen,
lio->address, NI_MAXHOST, lio->port, NI_MAXSERV,
NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
strcpy(lio->address, "");
strcpy(lio->port, "");
}
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock < 0) {
log_global(LOG_ERROR, "error creating socket: %s", strerror(errno));
close(sock);
g_free(lio);
continue;
}
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) {
/* Don't warn when binding to the same address using IPv4
* /and/ ipv6. */
if (!l->active || errno != EADDRINUSE) {
log_global(LOG_ERROR, "bind to %s:%s failed: %s", l->address,
l->port, strerror(errno));
}
close(sock);
g_free(lio);
continue;
}
if (listen(sock, 5) < 0) {
log_global(LOG_ERROR, "error listening on socket: %s",
strerror(errno));
close(sock);
g_free(lio);
continue;
}
ioc = g_io_channel_unix_new(sock);
if (ioc == NULL) {
log_global(LOG_ERROR,
"Unable to create GIOChannel for server socket");
close(sock);
g_free(lio);
continue;
}
g_io_channel_set_close_on_unref(ioc, TRUE);
g_io_channel_set_encoding(ioc, NULL, NULL);
lio->watch_id = g_io_add_watch(ioc, G_IO_IN, handle_new_client, l);
g_io_channel_unref(ioc);
l->incoming = g_list_append(l->incoming, lio);
log_network( LOG_INFO, l->network, "Listening on %s:%s",
lio->address, lio->port);
l->active = TRUE;
}
freeaddrinfo(all_res);
return l->active;
}
gboolean stop_listener(struct listener *l)
{
while (l->incoming != NULL) {
struct listener_iochannel *lio = l->incoming->data;
g_source_remove(lio->watch_id);
log_global(LOG_INFO, "Stopped listening at %s:%s", lio->address,
lio->port);
g_free(lio);
l->incoming = g_list_remove(l->incoming, lio);
}
return TRUE;
}
void free_listener(struct listener *l)
{
g_free(l->password);
g_free(l->port);
g_free(l->address);
listeners = g_list_remove(listeners, l);
g_free(l);
}
struct listener *listener_init(const char *address, const char *port)
{
struct listener *l = g_new0(struct listener, 1);
l->address = address?g_strdup(address):NULL;
l->port = g_strdup(port);
if (l->port == NULL)
l->port = g_strdup("6667");
listeners = g_list_append(listeners, l);
return l;
}
static void update_config(struct global *global, const char *path)
{
GList *gl;
char *filename;
GKeyFile *kf;
GError *error = NULL;
char *default_password;
default_password = g_key_file_get_string(global->config->keyfile, "listener", "password", NULL);
g_key_file_set_boolean(global->config->keyfile, "listener", "auto", auto_listener);
g_key_file_set_integer(global->config->keyfile, "listener", "autoport", autoport);
filename = g_build_filename(path, "listener", NULL);
if (keyfile)
keyfile = g_key_file_new();
kf = keyfile;
for (gl = listeners; gl; gl = gl->next) {
struct listener *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->password && !(default_password && strcmp(l->password, default_password) == 0))
g_key_file_set_string(kf, tmp, "password", l->password);
if (l->network) {
g_assert(l->network->info.name != NULL);
g_key_file_set_string(kf, tmp, "network", l->network->info.name);
}
g_key_file_set_boolean(kf, tmp, "ssl", l->ssl);
g_free(tmp);
}
if (!g_key_file_save_to_file(kf, filename, &error)) {
log_global(LOG_WARNING, "Unable to save to \"%s\": %s", filename, error->message);
}
g_free(filename);
}
static void auto_add_listener(struct network *n, void *private_data)
{
GList *gl;
char *port;
struct listener *l;
/* See if there is already a listener for n */
for (gl = listeners; gl; gl = gl->next) {
l = gl->data;
if (l->network == n || l->network == NULL)
return;
}
port = g_strdup_printf("%d", next_autoport());
l = listener_init(NULL, port);
l->network = n;
start_listener(l);
}
static void load_config(struct global *global)
{
char *filename = g_build_filename(global->config->config_dir, "listener", NULL);
int i;
char **groups;
gsize size;
GKeyFile *kf;
char *default_password;
default_password = g_key_file_get_string(global->config->keyfile, "listener", "password", NULL);
if (g_key_file_has_key(global->config->keyfile, "listener", "auto", NULL))
auto_listener = g_key_file_get_boolean(global->config->keyfile, "listener", "auto", NULL);
if (g_key_file_has_key(global->config->keyfile, "listener", "autoport", NULL))
autoport = g_key_file_get_integer(global->config->keyfile, "listener", "autoport", NULL);
if (auto_listener)
register_new_network_notify(global, auto_add_listener, NULL);
keyfile = kf = g_key_file_new();
if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, NULL)) {
g_free(filename);
return;
}
groups = g_key_file_get_groups(kf, &size);
for (i = 0; i < size; i++)
{
struct listener *l;
char *address, *port, *tmp;
tmp = g_strdup(groups[i]);
port = strrchr(tmp, ':');
if (port) {
address = tmp;
*port = '\0';
port++;
} else {
port = tmp;
address = NULL;
}
l = listener_init(address, port);
g_free(tmp);
l->password = g_key_file_get_string(kf, groups[i], "password", NULL);
if (!l->password)
l->password = default_password;
if (g_key_file_has_key(kf, groups[i], "ssl", NULL))
l->ssl = g_key_file_get_boolean(kf, groups[i], "ssl", NULL);
#ifdef HAVE_GNUTLS
if (l->ssl)
l->ssl_credentials = ssl_create_server_credentials(global, kf, groups[i]);
#endif
if (g_key_file_has_key(kf, groups[i], "network", NULL)) {
tmp = g_key_file_get_string(kf, groups[i], "network", NULL);
l->network = find_network(global, tmp);
if (!l->network) {
log_global(LOG_ERROR, "Unable to find network named \"%s\"", tmp);
}
g_free(tmp);
}
start_listener(l);
}
g_strfreev(groups);
g_free(filename);
}
static void fini_plugin(void)
{
GList *gl;
for(gl = listeners; gl; gl = gl->next) {
struct listener *l = gl->data;
if (l->active)
stop_listener(l);
}
}
void cmd_start_listener(admin_handle h, char **args, void *userdata)
{
char *b, *p;
struct listener *l;
if (!args[1]) {
admin_out(h, "No port specified");
return;
}
if (!args[2]) {
admin_out(h, "No password specified");
return;
}
b = g_strdup(args[1]);
if ((p = strchr(b, ':'))) {
*p = '\0';
p++;
} else {
p = b;
b = NULL;
}
l = listener_init(b, p);
if (b) g_free(b); else g_free(p);
l->password = g_strdup(args[2]);
if (args[3]) {
l->network = find_network(admin_get_global(h), args[3]);
if (l->network == NULL) {
admin_out(h, "No such network `%s'", args[3]);
free_listener(l);
return;
}
}
start_listener(l);
}
void cmd_stop_listener(admin_handle h, char **args, void *userdata)
{
GList *gl;
char *b, *p;
int i = 0;
if (!args[0]) {
admin_out(h, "No port specified");
return;
}
b = g_strdup(args[1]);
if ((p = strchr(b, ':'))) {
*p = '\0';
p++;
} else {
p = b;
b = NULL;
}
for (gl = listeners; gl; gl = gl->next) {
struct listener *l = gl->data;
if (b && !l->address)
continue;
if (b && l->address && strcmp(b, l->address) != 0)
continue;
if (strcmp(p, l->port) != 0)
continue;
stop_listener(l);
free_listener(l);
i++;
}
if (b) g_free(b); else g_free(p);
admin_out(h, "%d listeners stopped", i);
}
void cmd_list_listener(admin_handle h, char **args, void *userdata)
{
GList *gl;
for (gl = listeners; gl; gl = gl->next) {
struct listener *l = gl->data;
admin_out(h, "%s:%s%s%s%s", l->address?l->address:"", l->port, l->network?" (":"", l->network?l->network->info.name:"", l->network?")":"");
}
}
const static struct admin_command listener_commands[] = {
{ "STARTLISTENER", cmd_start_listener },
{ "STOPLISTENER", cmd_stop_listener },
{ "LISTLISTENER", cmd_list_listener },
{ NULL }
};
static gboolean init_plugin(void)
{
int i;
register_load_config_notify(load_config);
register_save_config_notify(update_config);
for (i = 0; listener_commands[i].name; i++)
register_admin_command(&listener_commands[i]);
atexit(fini_plugin);
return TRUE;
}
struct plugin_ops plugin = {
.name = "listener",
.version = 0,
.init = init_plugin,
};
syntax highlighted by Code2HTML, v. 0.9.1