696 lines
13 KiB
C
696 lines
13 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/sendfile.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/select.h>
|
|
#include <pthread.h>
|
|
#ifdef DEBUG
|
|
#include <ctype.h>
|
|
#endif
|
|
|
|
#define VERSION "1.3"
|
|
|
|
#define CRLF "\r\n"
|
|
|
|
#ifdef DEBUG
|
|
#define LISTEN_PORT 80
|
|
#define ROOT "/mnt/hd2/af/bootstrap/files/"
|
|
#else
|
|
#define LISTEN_PORT 80
|
|
#define ROOT "/var/bootstrap/"
|
|
#endif
|
|
|
|
/* How to cast fd_set structures on this OS */
|
|
#define FD_CAST (fd_set *)
|
|
|
|
/* The network socket on which we're listening - only written to by main
|
|
* thread before other threads are created. */
|
|
int listen_sock = -1;
|
|
/* Flag indicating reload required. Only accessed using atomic ops. */
|
|
int reload = 0;
|
|
/* Index page and its lock. */
|
|
char *xindex = "index.html";
|
|
pthread_mutex_t xindex_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
static struct {
|
|
char *ext;
|
|
char *type;
|
|
} mime_types[] = {
|
|
{ ".css", "text/css" },
|
|
{ ".js", "text/javascript" },
|
|
{ ".gif", "image/gif" },
|
|
{ ".png", "image/png" },
|
|
{ ".html", "text/html" },
|
|
{ NULL, "text/html" }
|
|
};
|
|
|
|
/********************************************************************
|
|
* A fatal error has occured, log and exit.
|
|
*/
|
|
void
|
|
fatal_error(char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
|
|
va_start(argp, fmt);
|
|
vfprintf(stderr, fmt, argp);
|
|
va_end(argp);
|
|
fprintf(stderr, "\n");
|
|
exit(1);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
/********************************************************************
|
|
* Log a message.
|
|
*/
|
|
void
|
|
debug(char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
|
|
printf("[%d] ", (int)pthread_self());
|
|
|
|
va_start(argp, fmt);
|
|
vprintf(fmt, argp);
|
|
va_end(argp);
|
|
printf("\n");
|
|
fflush(stdout);
|
|
}
|
|
|
|
void
|
|
hexdump(char *s, unsigned long len)
|
|
{
|
|
unsigned long offset;
|
|
|
|
if (!s)
|
|
{
|
|
debug("Hexdump - empty string.");
|
|
return;
|
|
}
|
|
|
|
if (!len)
|
|
len = strlen(s);
|
|
|
|
printf("----------------------------------------------------------\n");
|
|
printf("Hexdump: (%lu bytes)\n", len);
|
|
printf("----------------------------------------------------------\n");
|
|
|
|
for (offset = 0; offset < len; offset += 16)
|
|
{
|
|
unsigned long i;
|
|
|
|
printf("%08lx ", offset);
|
|
|
|
for (i = offset; i - offset < 16; i++)
|
|
{
|
|
if (i < len)
|
|
printf("%02x", s[i]);
|
|
else
|
|
printf(" ");
|
|
}
|
|
|
|
printf(" ");
|
|
|
|
for (i = offset; i < len && i - offset < 16; i++)
|
|
printf("%c", isprint((int)s[i]) ? s[i] : '.');
|
|
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUGMAX
|
|
#define debugmax(...) debug(__VA_ARGS__)
|
|
#else
|
|
#define debugmax(...)
|
|
#endif
|
|
|
|
#else /* !DEBUG */
|
|
|
|
#define debug(...)
|
|
#define debugmax(...)
|
|
|
|
#endif /* DEBUG */
|
|
|
|
/********************************************************************
|
|
* Network / socket utility functions
|
|
*/
|
|
|
|
int
|
|
write_fd(int fd, char *ptr, int len)
|
|
{
|
|
if (!len)
|
|
len = strlen(ptr);
|
|
|
|
debugmax("write_fd: %s", ptr);
|
|
|
|
while (len > 0)
|
|
{
|
|
struct timeval timeout;
|
|
int nbytes;
|
|
fd_set fs;
|
|
|
|
FD_ZERO(&fs);
|
|
FD_SET(fd, &fs);
|
|
timeout.tv_sec = 10;
|
|
timeout.tv_usec = 0;
|
|
if (select(fd + 1, FD_CAST NULL, FD_CAST&fs, FD_CAST NULL,
|
|
(struct timeval *)&timeout) == 0 ||
|
|
!FD_ISSET(fd, &fs))
|
|
{
|
|
/* Timeout */
|
|
debug("write_fd() Timeout waiting for socket.");
|
|
errno = ETIMEDOUT;
|
|
return -1;
|
|
}
|
|
|
|
nbytes = write(fd, ptr, len);
|
|
|
|
if (nbytes == -1)
|
|
{
|
|
if (errno == EINTR)
|
|
continue;
|
|
|
|
if (errno == EAGAIN)
|
|
{
|
|
sleep(1);
|
|
continue;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
len -= nbytes, ptr += nbytes;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
send_fd(int fd, char *fmt, ...)
|
|
{
|
|
char buf[0x400];
|
|
va_list argp;
|
|
|
|
va_start(argp, fmt);
|
|
vsprintf(buf, fmt, argp);
|
|
va_end(argp);
|
|
|
|
return write_fd(fd, buf, strlen(buf));
|
|
}
|
|
|
|
char *
|
|
read_fd_line(int fd, int timeout, char *buf, int bufl, int strip)
|
|
{
|
|
struct timeval tm;
|
|
fd_set fs;
|
|
char *q;
|
|
int i, l;
|
|
|
|
*buf = '\0';
|
|
l = 0;
|
|
while (!strchr(buf, '\r') && !strchr(buf, '\n') && l < bufl)
|
|
{
|
|
FD_ZERO(&fs);
|
|
FD_SET(fd, &fs);
|
|
|
|
tm.tv_sec = timeout;
|
|
tm.tv_usec = 100;
|
|
|
|
if (select(fd + 1, FD_CAST&fs, FD_CAST NULL,
|
|
FD_CAST NULL, (struct timeval *)&tm) == 0 ||
|
|
!FD_ISSET(fd, &fs))
|
|
{
|
|
debug("read_fd_line() Timeout when reading response.");
|
|
return (char *)NULL;
|
|
}
|
|
|
|
if ((i = read(fd, buf + l, bufl - l)) < 1)
|
|
{
|
|
debugmax("read_fd_line(): %d", i);
|
|
break;
|
|
}
|
|
|
|
l += i;
|
|
buf[l] = '\0';
|
|
|
|
#ifdef DEBUGMAX
|
|
debugmax("read_fd_line() %d bytes", i);
|
|
hexdump(buf, l);
|
|
#endif
|
|
}
|
|
|
|
if (strip && (q = strpbrk(buf, "\n\r")))
|
|
*q = '\0';
|
|
|
|
return buf;
|
|
}
|
|
|
|
void
|
|
headers(int fd, char *type, char *extra, int cache)
|
|
{
|
|
write_fd(fd, "HTTP/1.1 200 OK" CRLF, 0);
|
|
if (extra)
|
|
write_fd(fd, extra, 0);
|
|
if (cache)
|
|
{
|
|
time_t t = time(NULL) + 3600;
|
|
struct tm *my_tm = gmtime(&t);
|
|
char expiry[80];
|
|
strftime(expiry, sizeof(expiry),
|
|
"%a, %d %b %Y %H:%M:%S GMT", my_tm);
|
|
|
|
send_fd(fd,
|
|
"Content-Type: %s" CRLF
|
|
"Cache-Control: public" CRLF
|
|
"Expires: %s" CRLF,
|
|
type, expiry);
|
|
}
|
|
else
|
|
send_fd(fd,
|
|
"Content-Type: %s" CRLF
|
|
"Pragma: no-cache" CRLF
|
|
"Cache-Control: no-cache" CRLF
|
|
"Expires: -1" CRLF,
|
|
type);
|
|
write_fd(fd, CRLF, 2);
|
|
}
|
|
|
|
void
|
|
send_file(int fd, char *file, char *type, int chunked, int size, int cache)
|
|
{
|
|
int ifd;
|
|
off_t off = 0;
|
|
struct stat st;
|
|
|
|
debug("send_file(%s, %s)", file, type ? type : "NULL");
|
|
|
|
if (strstr(file, ".."))
|
|
{
|
|
debug(".. found in file.");
|
|
return;
|
|
}
|
|
|
|
if (type && !strcmp(type, "auto"))
|
|
{
|
|
int i;
|
|
|
|
type = "text/html";
|
|
|
|
for (i = 0; mime_types[i].ext; i++)
|
|
{
|
|
if (strstr(file, mime_types[i].ext))
|
|
{
|
|
type = mime_types[i].type;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((ifd = open(file, O_RDONLY)) < 0)
|
|
{
|
|
/* Don't set caching for the 404 response. */
|
|
cache = 0;
|
|
|
|
if ((ifd = open("404.html", O_RDONLY)) < 0)
|
|
{
|
|
headers(fd, "text/plain", NULL, 0);
|
|
send_fd(fd, "Error opening %s\n", file);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (type)
|
|
headers(fd, type, NULL, cache);
|
|
|
|
fstat(ifd, &st);
|
|
|
|
if (size <= 0 || size > st.st_size)
|
|
size = st.st_size;
|
|
|
|
if (chunked)
|
|
send_fd(fd, "%x" CRLF, size);
|
|
|
|
sendfile(fd, ifd, &off, size);
|
|
|
|
if (chunked)
|
|
write_fd(fd, CRLF, 2);
|
|
|
|
close(ifd);
|
|
}
|
|
|
|
void
|
|
install_webif(int fd)
|
|
{
|
|
char buf[0x111];
|
|
FILE *fp;
|
|
|
|
headers(fd, "text/html", "Transfer-Encoding: chunked" CRLF, 0);
|
|
|
|
/* Send a dummy 257 byte block of space characters to kick the
|
|
* browser into life. */
|
|
memset(buf, ' ', sizeof(buf));
|
|
buf[0x101] = '\0';
|
|
send_fd(fd, "101" CRLF "%s" CRLF, buf);
|
|
|
|
send_file(fd, "install.html", NULL, 1, 0, 0);
|
|
|
|
if ((fp = popen("./bin/install_webif", "r")))
|
|
{
|
|
char buf[0x400];
|
|
|
|
while (!feof(fp) && fgets(buf, sizeof(buf), fp))
|
|
{
|
|
send_fd(fd, "%x" CRLF, strlen(buf) + 5);
|
|
write_fd(fd, buf, 0);
|
|
write_fd(fd, "<br/>", 0);
|
|
write_fd(fd, CRLF, 2);
|
|
}
|
|
pclose(fp);
|
|
}
|
|
|
|
send_file(fd, "installed.html", NULL, 1, 0, 0);
|
|
write_fd(fd, "0" CRLF CRLF, 5);
|
|
}
|
|
|
|
void
|
|
send_image(int fd, char *img)
|
|
{
|
|
char buf[0x100];
|
|
|
|
sprintf(buf, "/opt/share/images/blue/%s", img);
|
|
send_file(fd, buf, "image/png", 0, 0, 1);
|
|
}
|
|
|
|
void *
|
|
handle_request(void *arg)
|
|
{
|
|
char line[0x100];
|
|
char req[80];
|
|
int fd;
|
|
|
|
fd = (int)(unsigned long)arg;
|
|
|
|
debug("New thread starting for fd %d", fd);
|
|
|
|
if (!read_fd_line(fd, 10, line, sizeof(line), 1))
|
|
goto cleanup;
|
|
|
|
debug("Got request line: [%s]", line);
|
|
|
|
*req = '\0';
|
|
if (!sscanf(line, "GET %80s HTTP", req))
|
|
{
|
|
debug("Could not extract req from first line.");
|
|
goto cleanup;
|
|
}
|
|
else
|
|
{
|
|
debug("Extracted req: %s", req);
|
|
}
|
|
|
|
/* Skip additional request headers. */
|
|
do
|
|
{
|
|
if (!read_fd_line(fd, 0, line, sizeof(line), 1))
|
|
break;
|
|
debugmax("Skipping request line: [%s]", line);
|
|
} while (strlen(line));
|
|
|
|
pthread_mutex_lock(&xindex_lock);
|
|
|
|
if (!strncmp(req, "/i/", 3))
|
|
send_image(fd, req + 3);
|
|
else if (!strcmp(xindex, "index.html") && !strncmp(req, "/install", 8))
|
|
install_webif(fd);
|
|
else if (!strcmp(xindex, "safe.html") && !strncmp(req, "/nosafe", 7))
|
|
{
|
|
unlink("/var/lib/humaxtv/mod/safemode");
|
|
send_file(fd, "nosafe.html", "text/html", 0, 0, 0);
|
|
}
|
|
else if (!strcmp(xindex, "maint.html") && !strncmp(req, "/exit", 5))
|
|
{
|
|
send_file(fd, "nomaint.html", "text/html", 0, 0, 0);
|
|
sleep(5);
|
|
system("./bin/reboot");
|
|
}
|
|
else if (strcmp(xindex, "init.html") && !strncmp(req, "/reboot", 7))
|
|
{
|
|
send_file(fd, "rebooting.html", "text/html", 0, 0, 0);
|
|
sleep(5);
|
|
system("./bin/reboot");
|
|
}
|
|
else if (!strcmp(req, "/ping"))
|
|
{
|
|
if (!strcmp(xindex, "init.html"))
|
|
write_fd(fd, "HTTP/1.1 204 OK" CRLF CRLF, 0);
|
|
else
|
|
send_file(fd, "pong.html", "text/html", 0, 0, 0);
|
|
}
|
|
else if (!strcmp(req, "/") || !strncmp(req, "/?", 2))
|
|
send_file(fd, xindex, "text/html", 0, 0, 0);
|
|
else
|
|
send_file(fd, req + 1, "auto", 0, 0, 1);
|
|
|
|
pthread_mutex_unlock(&xindex_lock);
|
|
|
|
cleanup:
|
|
|
|
shutdown(fd, SHUT_RDWR);
|
|
close(fd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/********************************************************************
|
|
* Main program
|
|
*/
|
|
|
|
int
|
|
nonblock(int fd)
|
|
{
|
|
int tmp = 1;
|
|
#if defined(_AIX) || defined(HPUX)
|
|
if (ioctl(fd, FIONBIO, &tmp) == -1)
|
|
return 0;
|
|
#else
|
|
fcntl(fd, F_SETOWN, getpid());
|
|
if ((tmp = fcntl(fd, F_GETFL)) == -1)
|
|
tmp = 0;
|
|
tmp |= O_NDELAY;
|
|
if (fcntl(fd, F_SETFL, tmp) == -1)
|
|
return 0;
|
|
#endif /* _AIX || HPUX */
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
setup_socket(int port_number, int type, int family)
|
|
{
|
|
struct sockaddr_in sin;
|
|
int tmp, sockfd;
|
|
|
|
memset((char *)&sin, '\0', sizeof(sin));
|
|
sin.sin_port = htons((unsigned short)port_number);
|
|
sin.sin_family = family;
|
|
sin.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
if ((sockfd = socket(sin.sin_family, type, 0)) == -1)
|
|
fatal_error("Could not create socket.");
|
|
|
|
tmp = 1;
|
|
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
|
|
(char *)&tmp, sizeof(tmp)) < 0)
|
|
fatal_error("Could not set socket re-useable.");
|
|
|
|
if (bind(sockfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
|
|
fatal_error("Could not bind socket.");
|
|
|
|
if (type == SOCK_STREAM && listen(sockfd, 7) == -1)
|
|
fatal_error("Could not listen on port");
|
|
|
|
nonblock(sockfd);
|
|
return sockfd;
|
|
}
|
|
|
|
void
|
|
sighup_handler(int sig __attribute__((unused)))
|
|
{
|
|
/* Any thread can receive this signal so use atomic or to set
|
|
* the global reload variable. */
|
|
__sync_fetch_and_or(&reload, 1);
|
|
}
|
|
|
|
static void
|
|
setup_signals()
|
|
{
|
|
struct sigaction sa;
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
memset(&sa, '\0', sizeof(sa));
|
|
|
|
sa.sa_handler = sighup_handler;
|
|
/*sa.sa_flags = SA_RESTART; */
|
|
if (sigaction(SIGHUP, &sa, NULL) == -1)
|
|
perror("sigaction");
|
|
}
|
|
|
|
void
|
|
check_reload()
|
|
{
|
|
char buf[0x100];
|
|
int fd, n;
|
|
char *p;
|
|
|
|
if (!__sync_bool_compare_and_swap(&reload, 1, 0))
|
|
return;
|
|
|
|
debug("Reload requested.");
|
|
|
|
if ((fd = open("/tmp/.bootstrap", O_RDONLY)) == -1)
|
|
{
|
|
perror("open");
|
|
return;
|
|
}
|
|
|
|
if ((n = read(fd, buf, sizeof(buf) - 1)) > 0)
|
|
{
|
|
buf[n] = '\0';
|
|
if ((p = strpbrk(buf, "\n\r ")))
|
|
*p = '\0';
|
|
pthread_mutex_lock(&xindex_lock);
|
|
free(xindex);
|
|
xindex = strdup(buf);
|
|
pthread_mutex_unlock(&xindex_lock);
|
|
debug("Landing page set to '%s'", xindex);
|
|
}
|
|
else if (n == -1)
|
|
perror("read");
|
|
close(fd);
|
|
unlink("/tmp/.bootstrap");
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
#ifndef DEBUG
|
|
int fd;
|
|
|
|
switch(fork())
|
|
{
|
|
case -1:
|
|
perror("fork");
|
|
return -1;
|
|
case 0: /* child */
|
|
break;
|
|
default: /* parent */
|
|
return 0;
|
|
}
|
|
#ifdef TIOCNOTTY
|
|
ioctl(fileno(stdin), TIOCNOTTY);
|
|
#endif
|
|
freopen("/dev/null", "r", stdin);
|
|
|
|
fd = open("/dev/null", O_WRONLY, 0666);
|
|
dup2(fd, fileno(stdout));
|
|
dup2(fd, fileno(stderr));
|
|
close(fd);
|
|
|
|
setsid();
|
|
#endif /* DEBUG */
|
|
|
|
if (argc > 1)
|
|
xindex = argv[1];
|
|
|
|
xindex = strdup(xindex);
|
|
|
|
debug("Installing signal handlers...");
|
|
setup_signals();
|
|
|
|
chdir(ROOT);
|
|
listen_sock = setup_socket(LISTEN_PORT, SOCK_STREAM, AF_INET);
|
|
|
|
debug("Listening...");
|
|
|
|
/* Main loop - wait for a connection on the socket then spawn a
|
|
* child thread to perform the health check.
|
|
*/
|
|
for (;;)
|
|
{
|
|
struct timeval delay;
|
|
fd_set readfds;
|
|
fd_set exfds;
|
|
|
|
FD_ZERO(&readfds);
|
|
FD_ZERO(&exfds);
|
|
|
|
FD_SET(listen_sock, &readfds);
|
|
|
|
#ifdef DEBUG
|
|
delay.tv_sec = 30;
|
|
#else
|
|
delay.tv_sec = 1200;
|
|
#endif
|
|
delay.tv_usec = 0;
|
|
|
|
check_reload();
|
|
|
|
if (select(listen_sock + 1, FD_CAST&readfds, FD_CAST NULL,
|
|
FD_CAST&exfds, (struct timeval *)&delay) < 0)
|
|
{
|
|
debug("select() error: %s", strerror(errno));
|
|
}
|
|
else if (FD_ISSET(listen_sock, &exfds))
|
|
{
|
|
debug("Socket excpetion: %s", strerror(errno));
|
|
}
|
|
else if (FD_ISSET(listen_sock, &readfds))
|
|
{
|
|
/* New connection, accept and spawn child. */
|
|
struct sockaddr_in addr;
|
|
int newfd;
|
|
socklen_t length;
|
|
pthread_t tid;
|
|
|
|
length = sizeof(addr);
|
|
if ((newfd = accept(listen_sock,
|
|
(struct sockaddr *)&addr, &length)) < 0)
|
|
{
|
|
debug("Accept error: %s", strerror(errno));
|
|
continue;
|
|
}
|
|
debug("Connection from %s", inet_ntoa(addr.sin_addr));
|
|
|
|
nonblock(newfd);
|
|
|
|
if (pthread_create(&tid, NULL, handle_request,
|
|
(void *)(unsigned long)newfd))
|
|
{
|
|
debug("Async thread creation failed - %s",
|
|
strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
debug("Created thread %d", (int)tid);
|
|
pthread_detach(tid);
|
|
}
|
|
}
|
|
|
|
debug("Main Loop poll.");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|