#include "sock.h" #include "utility.h" #include "display.h" #include #include #include #include #include #include #include #include #include #include #include /* superset of previous */ #define LINE_LEN 1024 #define MAX_SOCK 200 /** * Mode of the socket */ typedef enum terminal_mode_t { /** unknown or uninitialised */ term_idle = 0, /** telnet socket */ term_tty, /** web page GET */ term_page, /** web form POST */ term_form, /** SOAP request */ term_soap } TERM_MODE; /** * Terminal Control Structure * * Maintains the state of the connection */ typedef struct terminal_t { /** file descriptor for the socket */ int fd; /** mode of the connection */ TERM_MODE mode; /** current state of the connection */ int state; /** value to match for reports */ int match; /** TOD socket connected */ struct timeval connect_time; /** address of peer */ struct sockaddr_in addr; /** function to handle input ready */ void (*input)(int idx); /** input line buffer */ char line[LINE_LEN]; /** length of text in line */ int line_len; /** URL for GET/POST */ char url[LINE_LEN]; /** value from Content-Length header */ int content_length; /** associated device */ int(*command)(void* device, const char* cmd); void* device; } TERMINAL, *pTERMINAL; /** * Array of terminal control structures */ static TERMINAL fdv[MAX_SOCK]; /** * This structure parallels the terminal control structure */ static struct pollfd fds[MAX_SOCK]; /** Number of active connections */ static int num_fds = 0; /** descriptor of the listen socket */ static int sock_l; /** * Initialise the socket interface * * Opens and binds the listen socket and listens for incomming * connections. * * \param addr the TCP/IP port number on which to listen */ void sock_init(void) { int i; dbg_printf(0, "sock_init\n"); memset(fdv, 0, sizeof(fdv)); for (i = 0; i < MAX_SOCK; ++i) { fdv[i].fd = -1; } memset(fds, 0, sizeof(fds)); num_fds = 0; } void sock_listen(int addr, int (*command)(void* device, const char* cmd), void* device) { int status; long flags; int one = 1; struct sockaddr_in my_addr; status = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (status < 0) { perror("socket"); exit(EXIT_FAILURE); } sock_l = status; flags = fcntl(sock_l, F_GETFL); flags |= O_NONBLOCK; fcntl(sock_l, F_SETFL, flags); setsockopt(sock_l, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); memset(&my_addr, 0, sizeof(my_addr)); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(addr); my_addr.sin_addr.s_addr = INADDR_ANY; status = bind(sock_l, (struct sockaddr*) &my_addr, sizeof(my_addr)); if (status < 0) { perror("bind"); exit(EXIT_FAILURE); } status = listen(sock_l, 5); if (status < 0) { perror("listen"); exit(EXIT_FAILURE); } fds[num_fds].fd = sock_l; fds[num_fds].events = POLLIN | POLLOUT; fdv[num_fds].fd = sock_l; fdv[num_fds].input = sock_accept; fdv[num_fds].command = command; fdv[num_fds].device = device; ++num_fds; } /** * Check for socket activity * * Polls active sockets for activity: connections on the listen socket or * received data on the active sockets. * * \param timeout wait time in milliseconds */ void sock_check(int timeout) { int i; int ready; ready = poll(fds, num_fds, timeout); if (ready < 0) { perror("poll"); return; } if (ready == 0) return; dbg_printf(0, "sock_check, ready=%d\n", ready); for (i = 0; i < MAX_SOCK; ++i) { if (fds[i].revents) { if (fds[i].revents & POLLIN) fdv[i].input(i); if (--ready) break; } } } /** * Close and remove an active or disconnected socket * * \param n index of socket */ void sock_close(int n) { dbg_printf(0, "sock_close\n"); shutdown(fdv[n].fd, SHUT_RDWR); close(fdv[n].fd); if (n != num_fds) { fdv[n] = fdv[num_fds]; fds[n] = fds[num_fds]; } fdv[num_fds].fd = -1; --num_fds; } /** * Handle a line of input from a socket * * \param n index of socket */ void sock_line(int n) { char *cp = &fdv[n].line[0]; char *up; dbg_printf(0, "%3d: %s", fdv[n].state, fdv[n].line); if (fdv[n].line[fdv[n].line_len - 1] != '\n') dbg_printf(0, "\n"); switch(fdv[n].state) { case 0: /* * we are looking for HTTP GET or POST or a SICS command */ if (toupper(cp[0]) == 'G' && toupper(cp[1]) == 'E' && toupper(cp[2]) == 'T' && isspace(cp[3]) && strstr(cp, "HTTP/")) { cp+= 4; while (isspace(*cp)) ++cp; up = &fdv[n].url[0]; while (*cp && !isspace(*cp)) *up++ = *cp++; *up = '\0'; fdv[n].state = 1; } else if (toupper(cp[0]) == 'P' && toupper(cp[1]) == 'O' && toupper(cp[2]) == 'S' && toupper(cp[3]) == 'T' && isspace(cp[4]) && strstr(cp, "HTTP/")) { cp+= 4; while (isspace(*cp)) ++cp; up = &fdv[n].url[0]; while (*cp && !isspace(*cp)) *up++ = *cp++; *up = '\0'; fdv[n].content_length = 0; fdv[n].state = 3; } else if (toupper(cp[0]) == 'S' && toupper(cp[1]) == 'I' && toupper(cp[2]) == 'C' && toupper(cp[3]) == 'S' && isspace(cp[4])) { BUFFER buf; memcpy(buf.body, fdv[n].line, fdv[n].line_len); buf.length = fdv[n].line_len; buf.body[buf.length] = '\0'; process_command(n, &buf); fdv[n].content_length = 0; fdv[n].state = 0; } else { BUFFER buf; memcpy(buf.body, fdv[n].line, fdv[n].line_len); buf.length = fdv[n].line_len; buf.body[buf.length] = '\0'; process_command(n, &buf); fdv[n].content_length = 0; fdv[n].state = 0; } break; case 1: /* * We are scanning the header of a GET */ while (*cp == '\r' || *cp == '\n') ++cp; if (!*cp) { if (strncasecmp(fdv[n].url, "/form", 5) == 0) put_form(n); else if (strncasecmp(fdv[n].url, "/cmd=", 5) == 0) { fdv[n].command(fdv[n].device, &fdv[n].url[5]); put_page_refresh(n); } else put_page(n); fdv[n].state = 0; } break; case 3: /* * we are scanning the header of a POST */ while (*cp == '\r' || *cp == '\n') ++cp; if (!*cp) { if (fdv[n].content_length == 0) fdv[n].state = 5; else fdv[n].state = 4; break; } if (strncasecmp("Content-Length", cp, 14) == 0 && (cp = strchr(&cp[14], ':'))) { fdv[n].content_length = atoi(&cp[1]); dbg_printf(0, "Content Length = %d\n", fdv[n].content_length); } break; case 4: /* * we are scanning the body of a POST */ dbg_printf(0, "Content-Length: %d, Line-Length: %d\n", fdv[n].content_length, fdv[n].line_len); fdv[n].content_length -= fdv[n].line_len; if (fdv[n].content_length <= 0) { BUFFER buf; memcpy(buf.body, fdv[n].line, fdv[n].line_len); buf.length = fdv[n].line_len; process_form(n, &buf); fdv[n].state = 5; } break; default: break; } /* * If we have reached the end of a POST, send the new form */ if (fdv[n].state == 5) { put_form_refresh(n); fdv[n].state = 0; } } /** * Handle data received on a socket * * \param n index of socket */ void sock_input(int n) { dbg_printf(0, "sock_input(%d)\n", n); ssize_t sz; char buffer[1024]; sz = recv(fdv[n].fd, &buffer, sizeof(buffer), 0); if (sz == 0) { sock_close(n); return; } if (sz < 0) { if (errno == EAGAIN) /* AKA EWOULDBLOCK */ dbg_printf(0, "EAGAIN:"); else if (errno == ESPIPE) /* Illegal seek (on pipe or socket) */ dbg_printf(0, "ESPIPE:"); else perror("recv"); return; } int i; for (i = 0; i < sz; ++i) { if (fdv[n].line_len < LINE_LEN - 1) fdv[n].line[fdv[n].line_len++] = buffer[i]; if (buffer[i] == '\n' || (fdv[n].state == 4 && fdv[n].line_len == fdv[n].content_length)) { fdv[n].line[fdv[n].line_len] = '\0'; sock_line(n); fdv[n].line_len = 0; } } return; } /** * Handle connection arrived on listen socket * * \param n index of socket */ void sock_accept(int n) { dbg_printf(0, "sock_accept(%d)\n", n); int sock_n; struct sockaddr_in my_addr; socklen_t my_len = sizeof(my_addr); sock_n = accept(fdv[n].fd, (struct sockaddr*) &my_addr, &my_len); if (sock_n < 0) { perror("accept"); return; } if (num_fds < MAX_SOCK) { fdv[num_fds].fd = sock_n; fdv[num_fds].addr = my_addr; fdv[num_fds].input = sock_input; fdv[num_fds].command = fdv[n].command; fdv[num_fds].device = fdv[n].device; fdv[num_fds].line_len = 0; fdv[num_fds].state = 0; fds[num_fds].fd = sock_n; fds[num_fds].events = POLLIN; ++num_fds; } else { /* maximum connections active */ /* TODO log error */ close(sock_n); } } /** * Send data to active connection * * The data in the buffer is sent to the socket * * \param n index of socket * \param bp pointer to buffer to send */ void sock_send(int n, BUFFER* bp) { int status; status = send(fdv[n].fd, bp->body, bp->length, MSG_NOSIGNAL); if (status < 0) { if (errno == EAGAIN) /* flooded */ ; else if (errno == EPIPE) { perror("send EPIPE"); sock_close(n); } else { perror("send"); sock_close(n); } } } /** * The report in the buffer is sent to all active sockets which match * * \param bp pointer to buffer containing report * \param match value to match for this report */ void sock_report(BUFFER* bp, int match) { int i; for (i = 1; i < num_fds; ++i) { if (fdv[i].match == match) sock_send(i, bp); } } /** * Set the match parameter for the socket * * \param n index of socket * \param match value to match for this socket */ void sock_set_match(int n, int match) { fdv[n].match = match; } /** * Send an OK to the socket * * \param n index of socket */ void sock_ok(int n) { BUFFER buffer; strcpy(buffer.body, "OK\r\n"); buffer.length = strlen(buffer.body); sock_send(n, &buffer); } /** * Send an ERR to the socket * * \param n index of socket */ void sock_err(int n) { BUFFER buffer; strcpy(buffer.body, "ERR\r\n"); buffer.length = strlen(buffer.body); sock_send(n, &buffer); } void* sock_device(int n) { return fdv[n].device; }