524 lines
11 KiB
C
524 lines
11 KiB
C
#include "sock.h"
|
|
#include "utility.h"
|
|
#include "display.h"
|
|
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
|
|
#include <sys/poll.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip.h> /* 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
|
|
*
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Opens and binds the listen socket and listens for incomming
|
|
* connections.
|
|
*
|
|
* \param addr the TCP/IP port number on which to listen
|
|
* \param command function to process device commands from the socket
|
|
* \param device context argument to device command
|
|
*/
|
|
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 <= 0)
|
|
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);
|
|
--num_fds;
|
|
if (n != num_fds)
|
|
{
|
|
fdv[n] = fdv[num_fds];
|
|
fds[n] = fds[num_fds];
|
|
}
|
|
fdv[num_fds].fd = -1;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case EAGAIN: /* AKA EWOULDBLOCK */
|
|
dbg_printf(0, "EAGAIN:");
|
|
break;
|
|
case EINTR:
|
|
dbg_printf(0, "EINTR:");
|
|
break;
|
|
default:
|
|
perror("recv");
|
|
sock_close(n);
|
|
}
|
|
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
|
|
* \param dev device context to match for this report
|
|
*/
|
|
void sock_report(BUFFER* bp, int match, void* dev)
|
|
{
|
|
int i;
|
|
for (i = 1; i < num_fds; ++i)
|
|
{
|
|
if (fdv[i].match == match && fdv[i].device == dev)
|
|
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;
|
|
}
|