# if !defined(_POSIX_SOURCE) # define _POSIX_SOURCE # endif # if !defined(_BSD_SOURCE) # define _BSD_SOURCE # endif # include # include # include # include # include # include #include #include #include #include #include #include "client.h" #include "timer.h" #include "server.h" #include "monolog.h" #include "libtelnet.h" #include "rh.h" static const telnet_telopt_t woe_telnet_opts[] = { { TELNET_TELOPT_COMPRESS2, TELNET_WILL, TELNET_DONT }, { TELNET_TELOPT_ECHO , TELNET_WONT, TELNET_DONT }, { -1, 0, 0 } }; #define WOE_SERVER_BUFFER_SIZE 10000 #define WOE_SERVER_LISTEN_BACKLOG 8 struct woe_server { char buffer[WOE_SERVER_BUFFER_SIZE]; short listen_port; int listen_sock; int busy; struct sockaddr_in addr; socklen_t addrlen; mrb_state * mrb; struct pollfd pfd[WOE_CLIENTS_MAX + 1]; struct woe_client * clients[WOE_CLIENTS_MAX]; struct woe_timer * timers[WOE_TIMERS_MAX]; void (*event_handler) (telnet_t *telnet, telnet_event_t *ev, void *user_data); void (*disconnect_handler) (struct woe_server * srv, struct woe_client * cli, void *user_data); }; int woe_server_disconnect(struct woe_server * srv, struct woe_client * client); int woe_send(int sock, const char *buffer, unsigned int size) { int res; if (sock == -1) return -1; /* send data */ while (size > 0) { if ((res = send(sock, buffer, size, 0)) == -1) { if (errno != EINTR && errno != ECONNRESET) { LOG_ERROR("send() failed: %s\n", strerror(errno)); return -2; } else { return 0; } } else if (res == 0) { LOG_ERROR("send() unexpectedly returned 0\n"); return -3; } /* update pointer and size to see if we've got more to send */ buffer += res; size -= res; } return 0; } static void woe_event_handler(telnet_t *telnet, telnet_event_t *ev, void *user_data) { struct woe_client * client = (struct woe_client *) user_data; switch (ev->type) { /* data received */ case TELNET_EV_DATA: woe_client_input(client, ev->data.buffer, ev->data.size); /* telnet_negotiate(telnet, TELNET_WONT, TELNET_TELOPT_ECHO); telnet_negotiate(telnet, TELNET_WILL, TELNET_TELOPT_ECHO); */ break; /* data must be sent */ case TELNET_EV_SEND: woe_send(client->sock, ev->data.buffer, ev->data.size); break; /* enable compress2 if accepted by client */ case TELNET_EV_DO: if (ev->neg.telopt == TELNET_TELOPT_COMPRESS2) telnet_begin_compress2(telnet); woe_client_negotiate(client, TELNET_DO, ev->neg.telopt); break; case TELNET_EV_DONT: woe_client_negotiate(client, TELNET_DONT, ev->neg.telopt); break; case TELNET_EV_WILL: woe_client_negotiate(client, TELNET_WILL, ev->neg.telopt); break; case TELNET_EV_WONT: woe_client_negotiate(client, TELNET_WONT, ev->neg.telopt); break; case TELNET_EV_IAC: woe_client_iac(client, ev->iac.cmd); break; case TELNET_EV_SUBNEGOTIATION: woe_client_subnegotiate(client, ev->sub.buffer, ev->sub.size, ev->sub.telopt); break; case TELNET_EV_TTYPE: woe_client_ttype(client, ev->ttype.cmd, ev->ttype.name); break; case TELNET_EV_COMPRESS: woe_client_compress(client, ev->compress.state); break; case TELNET_EV_ENVIRON: woe_client_environ(client, ev->environ.cmd, ev->environ.values, ev->environ.size); break; case TELNET_EV_MSSP: woe_client_mssp(client, ev->mssp.values, ev->mssp.size); break; /* warning */ case TELNET_EV_WARNING: LOG_WARNING("Telnet warning for client %d %s.\n", client->index, ev->error.msg); woe_client_warning(client, ev->error.errcode, ev->error.msg); break; /* error */ case TELNET_EV_ERROR: LOG_ERROR("Telnet error for client %d %s.\n", client->index, ev->error.msg); woe_client_error(client, ev->error.errcode, ev->error.msg); woe_server_disconnect(client->server, client); break; case TELNET_EV_ZMP: woe_client_zmp(client, ev->zmp.argc, ev->zmp.argv); break; default: LOG_NOTE("Ignored telnet event %d.\n", ev->type); /* ignore */ break; } } void woe_server_request_shutdown(struct woe_server * srv) { if (srv) srv->busy = 0; } int woe_server_busy(struct woe_server * srv) { if (!srv) return 0; return srv->busy; } struct woe_server * woe_server_free(struct woe_server * srv) { int index; close(srv->listen_sock); for (index = 0; index < WOE_CLIENTS_MAX; ++index) { woe_server_remove_client(srv, index); } for (index = 0; index < WOE_TIMERS_MAX; ++index) { woe_server_remove_timer(srv, index); } free(srv); return NULL; } struct woe_server * woe_server_init(struct woe_server * srv, int port) { int index; if (!srv) return NULL; srv->listen_sock = -1; srv->listen_port = port; srv->busy = !0; srv->mrb = NULL; memset(srv->buffer , '\0' , sizeof(srv->buffer)); memset(srv->pfd , 0 , sizeof(srv->pfd)); memset(&srv->addr , 0 , sizeof(srv->addr)); for (index = 0; index < WOE_CLIENTS_MAX; ++index) { srv->clients[index] = NULL; } for (index = 0; index < WOE_TIMERS_MAX; ++index) { srv->timers[index] = NULL; } srv->event_handler = woe_event_handler; return srv; } struct woe_server * woe_server_new(int port) { struct woe_server * srv = calloc(1, sizeof(struct woe_server)); if (!srv) return NULL; return woe_server_init(srv, port); } static const telnet_telopt_t telopts[] = { { TELNET_TELOPT_COMPRESS2, TELNET_WILL, TELNET_DONT }, { -1, 0, 0 } }; struct woe_server * woe_server_set_mrb(struct woe_server * srv, mrb_state * mrb) { if (!srv) return NULL; srv->mrb = mrb; return srv; } mrb_state * woe_server_get_mrb(struct woe_server * srv) { if (!srv) return NULL; return srv->mrb; } /** Sets up the server to listen at its configured port. */ int woe_server_listen(struct woe_server * srv) { int res; /* create listening socket */ if ((srv->listen_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { LOG_ERROR("socket() failed: %s\n", strerror(errno)); return 1; } /* Reuse address option */ res = 1; if (setsockopt(srv->listen_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&res, sizeof(res)) == -1) { LOG_ERROR("setsockopt() failed: %s\n", strerror(errno)); return 2; } /* Bind to listening addr/port */ srv->addr.sin_family = AF_INET; srv->addr.sin_addr.s_addr = INADDR_ANY; srv->addr.sin_port = htons(srv->listen_port); if (bind(srv->listen_sock, (struct sockaddr *)&srv->addr, sizeof(srv->addr)) == -1) { LOG_ERROR("setsockopt() failed: %s\n", strerror(errno)); return 3; } /* listen for clients */ if (listen(srv->listen_sock, WOE_SERVER_LISTEN_BACKLOG) == -1) { fprintf(stderr, "listen() failed: %s\n", strerror(errno)); return 4; } LOG_NOTE("Listening on port %d\n", srv->listen_port); /* Initialize listening descriptors */ srv->pfd[WOE_CLIENTS_MAX].fd = srv->listen_sock; srv->pfd[WOE_CLIENTS_MAX].events = POLLIN; return 0; } /** Returns one of the clients of the server or NULL if not in use or out of * range */ struct woe_client * woe_server_get_client(struct woe_server * srv, int index) { if (!srv) return NULL; if (index < 0) return NULL; if (index >= WOE_CLIENTS_MAX) return NULL; return srv->clients[index]; } /** Stores a client of the server at the given index*/ struct woe_client * woe_server_put_client(struct woe_server * srv, int index, struct woe_client * cli) { if (!srv) return NULL; if (!cli) return NULL; if (index < 0) return NULL; if (index >= WOE_CLIENTS_MAX) return NULL; if (srv->clients[index]) { woe_client_free(srv->clients[index]); } srv->clients[index] = cli; return cli; } /** Removes a client of the server at the given index*/ struct woe_client * woe_server_remove_client(struct woe_server * srv, int index) { if (!srv) return NULL; if (index < 0) return NULL; if (index >= WOE_CLIENTS_MAX) return NULL; if (srv->clients[index]) { woe_client_free(srv->clients[index]); } srv->clients[index] = NULL; return NULL; } /** Returns one of the timers of the server or NULL if not in use or out of * range */ struct woe_timer * woe_server_get_timer(struct woe_server * srv, int index) { if (!srv) return NULL; if (index < 0) return NULL; if (index >= WOE_CLIENTS_MAX) return NULL; return srv->timers[index]; } /** Stores a timer of the server at the given index*/ struct woe_timer * woe_server_put_timer(struct woe_server * srv, int index, struct woe_timer * tim) { if (!srv) return NULL; if (!tim) return NULL; if (index < 0) return NULL; if (index >= WOE_TIMERS_MAX) return NULL; if (srv->timers[index]) { woe_timer_free(srv->timers[index]); } srv->timers[index] = tim; return tim; } /** Removes a timer of the server at the given index*/ struct woe_timer * woe_server_remove_timer(struct woe_server * srv, int index) { if (!srv) return NULL; if (index < 0) return NULL; if (index >= WOE_TIMERS_MAX) return NULL; if (srv->timers[index]) { woe_timer_free(srv->timers[index]); } srv->timers[index] = NULL; return NULL; } /** Find an index to put a newtimerr and returns a pointer to it. * Returns -1 if no free space is available. **/ int woe_server_get_available_timer_index(struct woe_server * srv) { int i; for (i = 0; i < WOE_TIMERS_MAX; ++i) { struct woe_timer * timer = woe_server_get_timer(srv, i); if (!timer) { return i; } } return -1; } /** Creates a new timer for this server. Return null if no memory or no space * for a new timer. */ struct woe_timer * woe_server_make_new_timer(struct woe_server * srv) { struct woe_timer * new; int index = woe_server_get_available_timer_index(srv); if (index < 0) return NULL; new = woe_timer_new(srv, index); return woe_server_put_timer(srv, index, new); } /** Creates a new timr and returns it's id. */ int woe_server_make_new_timer_id(struct woe_server * srv) { struct woe_timer * new = woe_server_make_new_timer(srv); if (!new) return -1; return new->index; } /** Find an index to put a new user and returns a pointer to it. * Returns -1 if no free space is available. **/ int woe_server_get_available_client_index(struct woe_server * srv) { int i; for (i = 0; i < WOE_CLIENTS_MAX; ++i) { struct woe_client * client = woe_server_get_client(srv, i); if (!client) { return i; } } return -1; } /** Creates a new client for this server. Return null if no memory or no space * for a new client. */ struct woe_client * woe_server_make_new_client(struct woe_server * srv, int socket, struct sockaddr_in *addr, socklen_t addrlen) { struct woe_client * new; int index = woe_server_get_available_client_index(srv); if (index < 0) return NULL; new = woe_client_new(srv, index, socket, addr, addrlen); return woe_server_put_client(srv, index, new); } /** Sets a timer's values by id */ int woe_server_set_timer_value(struct woe_server * srv, int index, double value, double interval) { struct woe_timer * tim; tim = woe_server_get_timer(srv, index); if (!tim) return -1; return woe_timer_set(tim, value, interval); } /** Gets a timer's values by id */ int woe_server_get_timer_value(struct woe_server * srv, int index, double * value, double * interval) { struct woe_timer * tim; tim = woe_server_get_timer(srv, index); if (!tim) return -1; return woe_timer_get(tim, value, interval); } /* Handles a new connection to this server. */ int woe_server_handle_connect(struct woe_server * srv) { struct woe_client * client; int res; struct sockaddr_in addr; socklen_t addrlen; /* accept the connection */ addrlen = sizeof(addr); if ((res = accept(srv->listen_sock, (struct sockaddr *)&addr, &addrlen)) == -1) { LOG_ERROR("accept() failed: %s\n", strerror(errno)); return 1; } LOG_NOTE("Connection received.\n"); client = woe_server_make_new_client(srv, res, &addr, addrlen); /* No space for a new client */ if (!client) { LOG_WARNING("Connection rejected (too many users or OOM)\n"); write(res, "Too many users.\r\n", 14); close(res); return 2; } /* init, welcome */ client->telnet = telnet_init(woe_telnet_opts, srv->event_handler, 0, client); if (!client->telnet) { LOG_ERROR("Could not initialize telnet connection for user."); woe_server_disconnect(srv, client); return 3; } /*telnet_negotiate(client->telnet, TELNET_DO, TELNET_TELOPT_ECHO);*/ telnet_negotiate(client->telnet, TELNET_WILL, TELNET_TELOPT_COMPRESS2); telnet_printf(client->telnet, "Welcome to WOE!\r\n"); /* telnet_negotiate(client->telnet, TELNET_WILL, TELNET_TELOPT_ECHO); */ if (srv->mrb) { rh_run_toplevel(srv->mrb, "woe_on_connect", "i", client->index); } return 0; } /** Sends a telnet command to the numbered client. */ int woe_server_iac(struct woe_server * srv, int client, int command) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_iac(pclient->telnet, command); return 0; } /** Send a telnet negotiation to the numbered client. */ int woe_server_negotiate(struct woe_server * srv, int client, int how, int option) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_negotiate(pclient->telnet, how, option); return 0; } /** Sends a telnet start of subnegotiation to the numbered client. */ int woe_server_begin_sb(struct woe_server * srv, int client, int telopt) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_begin_sb(pclient->telnet, telopt); return 0; } /** Sends a telnet end of subnegotiation to the numbered client. */ int woe_server_finish_sb(struct woe_server * srv, int client) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_finish_sb(pclient->telnet); return 0; } /** Sends a complete telnet subnegotiation buffer to the numbered client. */ int woe_server_subnegotiation(struct woe_server * srv, int client, int telopt, char * buffer, int size) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_subnegotiation(pclient->telnet, telopt, buffer, size); return 0; } /** Begin sending compressed data to the to the numbered client. */ int woe_server_begin_compress2(struct woe_server * srv, int client) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_begin_compress2(pclient->telnet); return 0; } /** Send formated output with newline escaping to the to the numbered client. */ int woe_server_vprintf(struct woe_server * srv, int client, const char *fmt, va_list va) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_vprintf(pclient->telnet, fmt, va); return 0; } /** Send formated output with newline escaping to the to the numbered client. */ int woe_server_printf(struct woe_server * srv, int client, const char *fmt, ...) { va_list va; int res; struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; va_start(va, fmt); telnet_vprintf(pclient->telnet, fmt, va); va_end(va); return 0; } /** Send formated output without newline escaping to the to the numbered client. */ int woe_server_raw_vprintf(struct woe_server * srv, int client, const char *fmt, va_list va) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_raw_vprintf(pclient->telnet, fmt, va); return 0; } /** Send formated output without newline escaping to the to the numbered client. */ int woe_server_raw_printf(struct woe_server * srv, int client, const char *fmt, ...) { va_list va; int res; struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; va_start(va, fmt); res = telnet_raw_vprintf(pclient->telnet, fmt, va); va_end(va); return 0; } /** Begin a NEW-ENVIRON subnegotiation with the numbered client. */ int woe_server_begin_newenviron(struct woe_server * srv, int client, int type) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_begin_newenviron(pclient->telnet, type); return 0; } /** Send a NEW-ENVIRON variable name or value to the numbered client. */ int woe_server_newenviron_value(struct woe_server * srv, int client, int type, char * value) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_newenviron_value(pclient->telnet, type, value); return 0; } /** Finish a NEW-ENVIRON subnegotiation with the numbered client. */ int woe_server_finish_newenviron(struct woe_server * srv, int client) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_finish_newenviron(pclient->telnet); return 0; } /** Send a TERMINAL-TYPE SEND command to the numbered client. */ int woe_server_ttype_send(struct woe_server * srv, int client) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_ttype_send(pclient->telnet); return 0; } /** Send a TERMINAL-TYPE IS command to the numbered client. */ int woe_server_ttype_is(struct woe_server * srv, int client, char * ttype) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_ttype_is(pclient->telnet, ttype); return 0; } /** Send a ZMP command to the numbered client. */ int woe_server_send_zmp(struct woe_server * srv, int client, int argc, const char ** argv) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_send_zmp(pclient->telnet, argc, argv); return 0; } /** Send a ZMP command to the numbered client. */ int woe_server_send_vzmpv(struct woe_server * srv, int client, va_list va) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_send_vzmpv(pclient->telnet, va); return 0; } /** Send a ZMP command to the numbered client. */ int woe_server_send_zmpv(struct woe_server * srv, int client, ...) { va_list va; struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; va_start(va, client); telnet_send_vzmpv(pclient->telnet, va); va_end(va); return 0; } /** Begin sending a ZMP command to the numbered client. */ int woe_server_begin_zmp(struct woe_server * srv, int client, const char * cmd) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_begin_zmp(pclient->telnet, cmd); return 0; } /** Send a ZMP command argument to the numbered client. */ int woe_server_zmp_arg(struct woe_server * srv, int client, const char * arg) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_zmp_arg(pclient->telnet, arg); return 0; } /** Finish sending a ZMP command to the numbered client. */ int woe_server_finish_zmp(struct woe_server * srv, int client, const char * cmd) { struct woe_client * pclient; if (!srv) return -1; pclient = woe_server_get_client(srv, client); if (!pclient) return -2; telnet_finish_zmp(pclient->telnet); return 0; } /** Disconnect a client from the server. */ int woe_server_disconnect(struct woe_server * srv, struct woe_client * client) { int index; if (!srv) return 1; if (!client) return 2; close(client->sock); if (srv->disconnect_handler) { srv->disconnect_handler(srv, client, NULL); } index = client->index; if (srv->mrb) { rh_run_toplevel(srv->mrb, "woe_on_disconnect", "i", index); } /* Get rid of client, will also free memory asociated. */ woe_server_remove_client(srv, index); return 0; } /** Forcfullly disconnect a client from the server by id. * Set a quit flag that woe_server_update will check. */ int woe_server_disconnect_id(struct woe_server * srv, int id) { struct woe_client * client = woe_server_get_client(srv, id); if (!client) return -1; client->busy = 0; return 0; } /** Polls the server once and updates any of the clients if needed. */ int woe_server_update(struct woe_server * srv, int timeout) { int i, res; /* Check timers for readiness. */ for (i = 0; i < WOE_TIMERS_MAX; ++i) { struct woe_timer * timer = woe_server_get_timer(srv, i); if (woe_timer_passed(timer)) { woe_timer_callback(timer); } } /* Disconnect clients that should quit */ for (i = 0; i < WOE_CLIENTS_MAX; ++i) { struct woe_client * client = woe_server_get_client(srv, i); if (!client) continue; if (!client->busy) { woe_server_disconnect(srv, client); } } /* prepare for poll */ memset(srv->pfd , 0 , sizeof(srv->pfd)); for (i = 0; i != WOE_CLIENTS_MAX; ++i) { struct woe_client * client = woe_server_get_client(srv, i); if (client) { srv->pfd[i].fd = client->sock; srv->pfd[i].events = POLLIN; } else { srv->pfd[i].fd = -1; srv->pfd[i].events = 0; } } /* Also listen for connnect events. */ srv->pfd[WOE_CLIENTS_MAX].fd = srv->listen_sock; srv->pfd[WOE_CLIENTS_MAX].events = POLLIN; /* Poll for activity */ res = poll(srv->pfd, WOE_CLIENTS_MAX + 1, timeout); /* Check for time out */ if (res == 0) { /* Time out but that's OK. */ return 0; } /* Log errors. */ if (res == -1 && errno != EINTR) { LOG_ERROR("poll() failed: %s\n", strerror(errno)); return 1; } /* Handle new connection connection */ if (srv->pfd[WOE_CLIENTS_MAX].revents & POLLIN) { woe_server_handle_connect(srv); } /* Read from clients */ for (i = 0; i < WOE_CLIENTS_MAX; ++i) { struct woe_client * client = woe_server_get_client(srv, i); if (!client) continue; /* Input from clients. */ if (srv->pfd[i].revents & POLLIN) { res = recv(client->sock, srv->buffer, sizeof(srv->buffer), 0); if (res < 0) { LOG_ERROR("recv(client) failed: %s\n", strerror(errno)); } else if (res == 0) { /* Disconnect the client. */ woe_server_disconnect(srv, client); } else { /* Let telnet lib process incoming data. */ telnet_recv(client->telnet, srv->buffer, res); // telnet_send(client->telnet, srv->buffer, res); // telnet_send(telnet, ev->data.buffer, ev->data.size); } } } return 0; } int woe_server_send_to_client(struct woe_server * srv, int client, char * data, size_t size) { struct woe_client * pclient = woe_server_get_client(srv, client); if (!pclient) return -1; telnet_send(pclient->telnet, data, size); return size; }