/* 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 "listen.h" #include "common/protocol.h" #include "common/remoteio.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); size_t distrend_listen_read_handle(struct remoteio *rem, struct distrend_listens *listens, void *buf, size_t len, 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_read(multiio_context_t multiio, int fd, short revent, struct distrend_listens *listens, struct distrend_client *client); 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; /* 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, POLLRDNORM, (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, POLLRDNORM); 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; } //int listen_handle_read(struct distrend_listens *listens, // struct distrend_client *client) /** * 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; 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, newclientsock)) { fprintf(stderr, "error allocating/adding client struct\n"); return 1; } newclient->rem = rem; fprintf(stderr, __FILE__ ":%d: This client SHOULD be registered in the table tennis system here\n", __LINE__); fprintf(stderr, "accepted new connection; fd=%d\n", newclientsock); /** * 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); /* 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; /** * 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; 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 req->len + distrend_listen_read_handle(rem, listens, buf + req->len, len - req->len, client); } return 0; } int distrend_listen_free(struct distrend_listens *listens) { 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; } /** This function shall only be called after the appropriate distrend_listen_poll_deletefd() has been called @deprecated blah @todo kill this func? */ int distrend_client_free(struct distrend_client *client) { fprintf(stderr, " Kaaaaaaa bOOOOOOOOMNMMMM!\n"); abort(); return 0; } 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->len, data->req_data); return TRUE; } /** helper for listen_handle_read() 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; }