/*
Copyright 2010 Nathan Phillip Brink
This file is a part of DistRen.
DistRen is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DistRen 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with DistRen. If not, see .
*/
#include "common/config.h"
#include "listen.h"
#include "common/protocol.h"
#include "common/remoteio.h"
#include "common/request.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* local */
struct distrend_request_handler_info
{
enum distren_request_type request_type;
distrend_handle_request_func_t handler;
};
struct distrend_client *distrend_client_new(struct distrend_listens *listens,
enum distrend_client_state state,
struct remoteio *rem);
int distrend_client_free(struct distrend_client *client);
int distrend_dispatch_request(struct distrend_listens *listens, struct remoteio *rem, struct distrend_client *client, struct distren_request *req, void *reqdata);
struct distrend_polled_sock *distrend_polled_sock_get_by_offset(struct distrend_listens *listens, size_t pollfds_offset);
static size_t distrend_listen_read_handle(struct remoteio *rem, struct distrend_listens *listens, void *buf, size_t len, struct distrend_client *client);
static void distrend_listen_remoteio_handle_close(struct distrend_listens *listens, struct distrend_client *client);
int listen_handle_accept(multiio_context_t multiio,
int fd,
short revent,
struct distrend_listens *listens,
int *port);
int listen_handle_error(multiio_context_t multiio,
int fd,
short revent,
struct distrend_listens *listens,
int *port);
/*** TO BE MOVED TO REMOTEIO */
int listen_handle_existence(multiio_context_t multiio,
int fd,
short revent,
struct distrend_listens *listens,
struct distrend_client *client);
struct distrend_listens *distrend_listens_new(multiio_context_t multiio,
struct general_info *geninfo,
struct options_common *opts)
{
struct distrend_listens *listens;
listens = malloc(sizeof(struct distrend_listens));
if(!listens)
return NULL;
listens->request_handlers = list_init();
if(!listens->request_handlers)
{
free(listens);
return NULL;
}
listens->options = opts;
listens->geninfo = geninfo;
/* multiio */
listens->multiio = multiio;
/* tabletennis */
listens->tabletennis = tabletennis_new(listens, 32, 16);
/* This type is used for accepting connections with accept() */
multiio_socket_type_register(multiio, &listens->socket_type);
multiio_event_handler_register(multiio,
listens->socket_type,
POLLERR | POLLHUP | POLLNVAL,
(multiio_event_handler_func_t)&listen_handle_error,
listens);
multiio_event_handler_register(multiio,
listens->socket_type,
POLLIN,
(multiio_event_handler_func_t)&listen_handle_accept,
listens);
return listens;
}
int distrend_listen_add(struct distrend_listens *listens, int port)
{
int tmp;
int fd;
int *saved_port;
struct sockaddr_in6 sockaddr =
{
.sin6_family = AF_INET6,
.sin6_port = 0,
.sin6_flowinfo = 0,
.sin6_addr = IN6ADDR_ANY_INIT,
.sin6_scope_id = 0
};
sockaddr.sin6_port = htons(port);
fd = socket(AF_INET6, SOCK_STREAM, 0);
tmp = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
if(tmp == -1)
{
perror("bind");
close(fd);
return 1;
}
tmp = listen(fd, 1);
if(tmp == -1)
{
perror("listen");
close(fd);
return 1;
}
saved_port = malloc(sizeof(int));
if(!saved_port)
{
perror("malloc");
close(fd);
return 1;
}
*saved_port = port;
tmp = multiio_socket_add(listens->multiio, fd, listens->socket_type, saved_port, POLLIN);
if(tmp)
{
close(fd);
return 1;
}
/**
* @todo perhaps we'll someday want the ability to unlisten()? :-p
* Then we have to store the fd somewheres so that we can call
* multiio_socket_del() on it. So far, however, that functionality
* isn't needed.
*/
return 0;
}
int listen_handle_error(multiio_context_t multiio,
int fd,
short revent,
struct distrend_listens *listens,
int *port)
{
fprintf(stderr, "Port %d experienced an error or is closed. Closing it.\n", *port);
multiio_socket_del(listens->multiio, fd);
close(fd);
free(port);
return 0;
}
/**
* an important thing to handle
*
* @deprecated to be replaced with table_tennis
*/
int listen_handle_existence(multiio_context_t multiio,
int fd,
short revent,
struct distrend_listens *listens,
struct distrend_client *client)
{
/**
* handle dead/closed sockets here!
*/
fprintf(stderr, __FILE__ ":%d: handle dead/closed sockets here!\n", __LINE__);
/**
* Manage timing-out clients.
*/
if(time(NULL) > client->cleanup_time)
switch(client->state)
{
case DISTREND_CLIENT_PREVERSION:
distrend_send_disconnect(client, "You have failed to present version information in a timely manner. Cya :-p");
break;
case DISTREND_CLIENT_PREAUTH:
distrend_send_disconnect(client, "You have failed to present authentication information in a timely manner. Cya ;-)");
break;
case DISTREND_CLIENT_GOOD:
/*
* pings should be managed with two queues sharing this struct:
* struct pingpong_queue { struct distrend_client *client; time_t ping_time };
*
* - queue to_ping: contains queue of clients to send pings to
* with the client whose ping time is earliest at the front
*
* - queue to_be_ponged: contains a queue of clients to clean up
* if they haven't recieved pongs. client with earliest cleanup
* time is at the front. If a PONG packet is received in time, the
* cleanup_time is bumped but the queue is left alone. When the
* queue's element is encountered, cleanup_time is checked and then
* the client is readded to the to_ping queue.
*
* Each queue shall be eaten as time passes and continue forever
* in circularity.
*
* data structures.
* fun.
* impossible to explain in text.
* easy to think up.
* impossible to determine the workings of existing ones.
* and... when you need them, screenshot utilities just aren't available :-/.
*/
distrend_send_disconnect(client, "Ping timeout :-p");
break;
case DISTREND_CLIENT_BAD:
fprintf(stderr, __FILE__ ":%d: aaarrrrgh!\n :-D\n", __LINE__);
break;
default:
break;
}
return 0;
}
struct distrend_accept_client_proc_data
{
struct distrend_listens *listens;
time_t current_time;
};
/**
* Handle new connections.
*/
int listen_handle_accept(multiio_context_t multiio,
int fd,
short revent,
struct distrend_listens *listens,
int *port)
//int distrend_accept(struct distrend_listens *listens)//, struct distrend_clientset *clients, distrend_handle_request_t handlereq, void *handlereqdata)
{
struct distrend_client *newclient;
int newclientsock;
struct remoteio *rem;
struct distren_request *req;
void *data;
newclientsock = accept(fd, (struct sockaddr *)NULL, (socklen_t *)NULL);
/* used to call int distrend_client_add(struct distrend_listens *listens, int sock, DISTREND_CLIENT_PREVERSION)*/
newclient = distrend_client_new(listens, DISTREND_CLIENT_PREVERSION, NULL);
if(remoteio_open_socket(&rem, listens->options->remoteio, (remoteio_read_handle_func_t)&distrend_listen_read_handle, newclient, (remoteio_close_handle_func_t)&distrend_listen_remoteio_handle_close, newclientsock))
{
fprintf(stderr, "error allocating/adding client struct\n");
return 1;
}
newclient->rem = rem;
fprintf(stderr, "accepted new connection; fd=%d\n", newclientsock);
/* tabletennis */
tabletennis_add_client(listens->tabletennis, newclient);
/**
* For those using netcat/telnet to debug their internets.
*/
#ifndef PACKAGE_URL
#define PACKAGE_URL "http://ohnopub.net/distren/"
#endif
#define DISTREN_GREETING PACKAGE_STRING " " PACKAGE_URL " : Nathan Phillip Brink && Ethan Michael Zonca\n"
/* using sizeof() - 1 because the sizeof() includes a NULL byte we want to ignore. */
remoteio_write(newclient->rem, DISTREN_GREETING, sizeof(DISTREN_GREETING) - 1);
/* send a DISTREN_REQUEST_VERSION immediately as per protocol */
distren_request_version(&req, &data,
DISTREN_SERVERTYPE_SUBMIT
| DISTREN_SERVERTYPE_DISTRIBUTE
| DISTREN_SERVERTYPE_RENDER,
PACKAGE_STRING);
distren_request_send(rem, req, data);
distren_request_free_with_data(req, data);
/*
list_mvfront(listens->clients);
newclient = list_curr(listens->clients);
while(newclient)
{
if(newclient->state == DISTREND_CLIENT_DEAD)
{
distrend_listen_poll_deletefd(listens, &newclient->sock);
distrend_client_free(newclient);
list_remove_curr(listens->clients);
fprintf(stderr, "removed dead connection\n");
}
list_mvnext(listens->clients);
*//* provide for termination of this loop *//*
if(newclient == list_curr(listens->clients))
newclient = NULL;
else
newclient = list_curr(listens->clients);
}
*/
return 0;
}
/**
* Handle read events from remoteio, remoteio_read_handle_func_t.
*
* This func requires that someone called remoteio_generic_data_set(remoteio_opts, listens);
*
* @param client the client associated with this remoteio instance.
*/
size_t distrend_listen_read_handle(struct remoteio *rem, struct distrend_listens *listens, void *buf, size_t len, struct distrend_client *client)
{
struct distren_request *req;
void *reqdata;
size_t used_len;
used_len = 0;
/**
* Manage input, etc.
*/
if(client->expectlen == 0)
{
/* search out header from input so far */
if(len > sizeof(struct distren_request))
{
if(distren_request_new_fromdata(&req, buf, len))
{
fprintf(stderr, "Error handling data from client (magic likely did not match), closing connection\n");
/*
* yes, this is safe and legal... because of hackishness
* in remoteio_close() ;-)
*/
remoteio_close(rem);
return 1;
}
client->expectlen = req->len + sizeof(struct distren_request);
distren_request_free(req);
}
}
if(client->expectlen
&& len >= client->expectlen)
{
if(distren_request_new_fromdata(&req, buf, len))
{
if(client->state == DISTREND_CLIENT_PREAUTH)
remoteio_close(rem);
else
distrend_send_disconnect(client, "Protocol error.");
return 1;
}
/*
* this really shouldn't happen... reparsing the same data with
* distren_request_new_fromdata() a second time shouldn't yeild
* a different req->len than it did before.
*/
if(len - sizeof(struct distren_request) < req->len)
{
fprintf(stderr, "Unexpected error handling some data from client\n");
distren_request_free(req);
/* but we should pay homage to W3C if the impossible happens */
distrend_send_disconnect(client, "HTTP/1.1 503 Internal Server Error");
return 1;
}
reqdata = malloc(req->len);
if(!reqdata)
{
fprintf(stderr, "OOM\n");
distren_request_free(req);
return 1;
}
memcpy(reqdata, ((void *)buf) + sizeof(struct distren_request), req->len);
client->expectlen = 0;
used_len = sizeof(struct distren_request) + req->len;
distrend_dispatch_request(listens, rem, client, req, reqdata);
free(reqdata);
distren_request_free(req);
/* I actually just used recursion in non-LISP code! :-D */
return used_len + distrend_listen_read_handle(rem, listens, buf + used_len, len - used_len, client);
}
return used_len;
}
/**
* Handle cleaning up after remoteio_close() has been called. This includes cleaning up the struct distrend_client and stuffs
*/
void distrend_listen_remoteio_handle_close(struct distrend_listens *listens, struct distrend_client *client)
{
/*
* remoteio handles removing itself from multiio for us. We just
* have to clean up tabletennis and the struct itself.
*/
tabletennis_del_client(listens->tabletennis, client);
free(client);
}
int distrend_listen_free(struct distrend_listens *listens)
{
tabletennis_free(listens->tabletennis);
fprintf(stderr, "%s:%d: I am a stub that needn't be implemented 'til later\n", __FILE__, __LINE__);
return 1;
}
/**
This is probably just NOT a placeholder for remotio
*/
void remotio_send_to_client(struct distrend_client *client, const char *msg, size_t len)
{
fprintf(stderr, "%s:%d: STUB I should queue data for writing to a client.... or should I? :-p\n", __FILE__, __LINE__);
}
/**
* Allocates and initializes a struct distrend_client.
*
*
*/
struct distrend_client *distrend_client_new(struct distrend_listens *listens, enum distrend_client_state state, struct remoteio *rem)
{
struct distrend_client *client;
client = malloc(sizeof(struct distrend_client));
if(!client)
{
fprintf(stderr, "OOM\n");
return NULL;
}
client->state = state;
client->cleanup_time = time(NULL) + DISTREND_LISTEN_AUTHTIME;
client->inlen = 0;
client->expectlen = 0;
client->rem = rem;
return client;
}
int distrend_client_write_request(struct distrend_client *client, const struct distren_request *req, const void *data)
{
char *towrite;
int ret;
size_t msglen;
msglen = sizeof(struct distren_request) + req->len;
towrite = malloc(msglen);
if(!towrite)
{
fprintf(stderr, "OOM\n");
return 1;
}
memcpy(towrite, req, sizeof(struct distren_request));
memcpy(towrite + sizeof(struct distren_request), data, req->len);
ret = remoteio_write(client->rem, towrite, msglen);
free(towrite);
return ret;
}
/*
int distrend_client_read(struct distrend_client *client, char **toread, size_t *lenread)
{
struct distrend_packet *packet;
*lenread = 0;
*toread = NULL;
if(q_empty(client->inmsgs))
return 1;
packet = q_dequeue(client->inmsgs);
*lenread = packet->len;
*toread = packet->data;
free(packet);
return 0;
}
*/
int distrend_send_disconnect(struct distrend_client *client, const char *quit_msg)
{
struct distren_request *req;
distren_request_new(&req, strlen(quit_msg), DISTREN_REQUEST_DISCONNECT);
distrend_client_write_request(client, req, quit_msg);
distren_request_free(req);
client->state = DISTREND_CLIENT_BAD;
client->cleanup_time = time(NULL) + DISTREND_LISTEN_DISCONNECT_GRACE;
return 0;
}
int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler)
{
struct distrend_request_handler_info *handler_info;
handler_info = malloc(sizeof(struct distrend_request_handler_info));
if(!handler_info)
return 1;
handler_info->request_type = type;
handler_info->handler = handler;
list_insert_after(listens->request_handlers, handler_info, 0);
return 0;
}
struct distrend_dispatch_request_data
{
struct general_info *geninfo;
struct distrend_client *client;
struct distren_request *req;
void *req_data;
};
/**
traversal function for distrend_dispatch_request().
*/
int _distrend_dispatch_request_trav(struct distrend_dispatch_request_data *data, struct distrend_request_handler_info *handler_info)
{
if(handler_info->request_type == data->req->type)
(*handler_info->handler)(data->geninfo, data->client, data->req, data->req_data);
return TRUE;
}
/**
helper for distrend_listen_read_handle() which looks up the correct
request handler and handles handing the the request to the
handler. :-p
*/
int distrend_dispatch_request(struct distrend_listens *listens, struct remoteio *rem, struct distrend_client *client, struct distren_request *req, void *reqdata)
{
struct distrend_dispatch_request_data data;
data.geninfo = listens->geninfo;
data.client = client;
data.req = req;
data.req_data = reqdata;
list_traverse(listens->request_handlers, &data, (list_traverse_func_t)&_distrend_dispatch_request_trav, LIST_FRNT | LIST_SAVE);
return 0;
}