/* * Copyright (C) 2009 Julien BLACHE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include "daapd.h" #include "err.h" #include "conffile.h" #include "httpd.h" #define WEBFACE_ROOT "/usr/share/mt-daapd/admin-root/" struct content_type_map { char *ext; char *ctype; }; static struct content_type_map ext2ctype[] = { { ".html", "text/html; charset=utf-8" }, { ".xml", "text/xml; charset=utf-8" }, { ".css", "text/css; charset=utf-8" }, { ".txt", "text/plain; charset=utf-8" }, { ".js", "application/javascript; charset=utf-8" }, { ".gif", "image/gif" }, { ".ico", "image/x-ico" }, { ".png", "image/png" }, { NULL, NULL } }; static int exit_pipe[2]; static int httpd_exit; static struct event_base *evbase_httpd; static struct event exitev; static struct evhttp *evhttpd; static pthread_t tid_httpd; /* Thread: httpd */ static int path_is_legal(char *path) { return strncmp(WEBFACE_ROOT, path, strlen(WEBFACE_ROOT)); } /* Thread: httpd */ static void redirect_to_index(struct evhttp_request *req, char *uri) { char buf[256]; int slashed; int ret; slashed = (uri[strlen(uri) - 1] == '/'); ret = snprintf(buf, sizeof(buf), "%s%sindex.html", uri, (slashed) ? "" : "/"); if ((ret < 0) || (ret >= sizeof(buf))) { DPRINTF(E_LOG, L_HTTPD, "Redirection URL exceeds buffer length\n"); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } evhttp_add_header(req->output_headers, "Location", buf); evhttp_send_reply(req, HTTP_MOVETEMP, "Moved", NULL); } /* Thread: httpd */ static void serve_file(struct evhttp_request *req, char *uri) { char *ext; char path[PATH_MAX]; char *deref; char *ctype; struct evbuffer *evbuf; struct stat sb; int fd; int i; int ret; ret = snprintf(path, sizeof(path), "%s%s", WEBFACE_ROOT, uri + 1); /* skip starting '/' */ if ((ret < 0) || (ret >= sizeof(path))) { DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", uri); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_HTTPD, "Could not lstat() %s: %s\n", path, strerror(errno)); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } if (S_ISDIR(sb.st_mode)) { redirect_to_index(req, uri); return; } else if (S_ISLNK(sb.st_mode)) { deref = realpath(path, NULL); if (!deref) { DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno)); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } if (strlen(deref) + 1 > PATH_MAX) { DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); free(deref); return; } strcpy(path, deref); free(deref); ret = stat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", path, strerror(errno)); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } if (S_ISDIR(sb.st_mode)) { redirect_to_index(req, uri); return; } } if (path_is_legal(path) != 0) { evhttp_send_error(req, 403, "Forbidden"); return; } evbuf = evbuffer_new(); if (!evbuf) { DPRINTF(E_LOG, L_HTTPD, "Could not create evbuffer\n"); evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal error"); return; } fd = open(path, O_RDONLY); if (fd < 0) { DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", path, strerror(errno)); evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); return; } ret = evbuffer_read(evbuf, fd, sb.st_size); close(fd); if (ret < 0) { DPRINTF(E_LOG, L_HTTPD, "Could not read file into evbuffer\n"); evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal error"); return; } ctype = "application/octet-stream"; ext = strrchr(path, '.'); if (ext) { for (i = 0; ext2ctype[i].ext; i++) { if (strcmp(ext, ext2ctype[i].ext) == 0) { ctype = ext2ctype[i].ctype; break; } } } evhttp_add_header(req->output_headers, "Content-Type", ctype); evhttp_send_reply(req, HTTP_OK, "OK", evbuf); evbuffer_free(evbuf); } /* Thread: httpd */ static void webface_cb(struct evhttp_request *req, void *arg) { const char *req_uri; char *uri; char *ptr; req_uri = evhttp_request_uri(req); if (!req_uri) { redirect_to_index(req, "/"); return; } uri = strdup(req_uri); ptr = strchr(uri, '?'); if (ptr) { DPRINTF(E_DBG, L_HTTPD, "Found query string\n"); *ptr = '\0'; } ptr = uri; uri = evhttp_decode_uri(uri); free(ptr); serve_file(req, uri); free(uri); } /* Thread: httpd */ static void * httpd(void *arg) { event_base_dispatch(evbase_httpd); if (!httpd_exit) DPRINTF(E_FATAL, L_HTTPD, "HTTPd event loop terminated ahead of time!\n"); pthread_exit(NULL); } /* Thread: httpd */ static void exit_cb(int fd, short event, void *arg) { event_base_loopbreak(evbase_httpd); httpd_exit = 1; } /* Thread: main */ int httpd_init(void) { unsigned short port; int bindv6; int ret; httpd_exit = 0; ret = pipe2(exit_pipe, O_CLOEXEC); if (ret < 0) { DPRINTF(E_FATAL, L_HTTPD, "Could not create pipe: %s\n", strerror(errno)); return -1; } evbase_httpd = event_base_new(); if (!evbase_httpd) { DPRINTF(E_FATAL, L_HTTPD, "Could not create an event base\n"); goto evbase_fail; } event_set(&exitev, exit_pipe[0], EV_READ, exit_cb, NULL); event_base_set(evbase_httpd, &exitev); event_add(&exitev, NULL); evhttpd = evhttp_new(evbase_httpd); if (!evhttpd) { DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server\n"); goto evhttp_fail; } port = cfg_getint(cfg_getnsec(cfg, "library", 0), "port") /* tmp */ + 1; /* evhttp doesn't support IPv6 yet, so this is expected to fail */ bindv6 = evhttp_bind_socket(evhttpd, "::", port); if (bindv6 < 0) DPRINTF(E_INF, L_HTTPD, "Could not bind IN6ADDR_ANY:%d (that's OK)\n", port); ret = evhttp_bind_socket(evhttpd, "0.0.0.0", port); if (ret < 0) { if (bindv6 < 0) { DPRINTF(E_FATAL, L_HTTPD, "Could not bind INADDR_ANY:%d\n", port); goto bind_fail; } } evhttp_set_gencb(evhttpd, webface_cb, NULL); ret = pthread_create(&tid_httpd, NULL, httpd, NULL); if (ret != 0) { DPRINTF(E_FATAL, L_HTTPD, "Could not spawn HTTPd thread: %s\n", strerror(errno)); goto thread_fail; } return 0; thread_fail: bind_fail: evhttp_free(evhttpd); evhttp_fail: event_base_free(evbase_httpd); evbase_fail: close(exit_pipe[0]); close(exit_pipe[1]); return -1; } /* Thread: main */ void httpd_deinit(void) { int dummy = 42; int ret; ret = write(exit_pipe[1], &dummy, sizeof(dummy)); if (ret != sizeof(dummy)) { DPRINTF(E_FATAL, L_HTTPD, "Could not write to exit fd: %s\n", strerror(errno)); return; } ret = pthread_join(tid_httpd, NULL); if (ret != 0) { DPRINTF(E_FATAL, L_HTTPD, "Could not join HTTPd thread: %s\n", strerror(errno)); return; } close(exit_pipe[0]); close(exit_pipe[1]); evhttp_free(evhttpd); event_base_free(evbase_httpd); }