/* * Spdylay - SPDY Library * * Copyright (c) 2012 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spdylay_ssl.h" #include "uri.h" #include "util.h" namespace spdylay { struct Config { std::string htdocs; bool verbose; uint16_t port; std::string private_key_file; std::string cert_file; Config(): verbose(false), port(0) {} }; extern bool ssl_debug; namespace { Config config; const std::string STATUS_200 = "200 OK"; const std::string STATUS_304 = "304 Not Modified"; const std::string STATUS_400 = "400 Bad Request"; const std::string STATUS_404 = "404 Not Found"; const std::string DEFAULT_HTML = "index.html"; const std::string SPDYD_SERVER = "spdyd spdylay/"SPDYLAY_VERSION; } // namespace struct Request { int32_t stream_id; std::vector > headers; int file; Request(int32_t stream_id) : stream_id(stream_id), file(-1) {} ~Request() { if(file != -1) { close(file); } } }; class Sessions; class EventHandler { public: virtual ~EventHandler() {} virtual int execute(Sessions *sessions) = 0; virtual bool want_read() = 0; virtual bool want_write() = 0; virtual int fd() const = 0; virtual bool finish() = 0; }; class Sessions { public: Sessions(int epfd, SSL_CTX *ssl_ctx) : epfd_(epfd), ssl_ctx_(ssl_ctx) {} ~Sessions() { SSL_CTX_free(ssl_ctx_); } void add_handler(EventHandler *handler) { handlers_.insert(handler); } void remove_handler(EventHandler *handler) { handlers_.erase(handler); } SSL* ssl_session_new(int fd) { SSL *ssl = SSL_new(ssl_ctx_); if(SSL_set_fd(ssl, fd) == 0) { SSL_free(ssl); return 0; } return ssl; } int add_poll(EventHandler *handler) { return update_poll_internal(handler, EPOLL_CTL_ADD); } int mod_poll(EventHandler *handler) { return update_poll_internal(handler, EPOLL_CTL_MOD); } private: int update_poll_internal(EventHandler *handler, int op) { epoll_event ev; ev.events = 0; if(handler->want_read()) { ev.events |= EPOLLIN; } if(handler->want_write()) { ev.events |= EPOLLOUT; } ev.data.ptr = handler; int r = epoll_ctl(epfd_, op, handler->fd(), &ev); return r; } std::set handlers_; SSL_CTX *ssl_ctx_; int epfd_; }; namespace { void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; } } // namespace namespace { void on_session_closed(int64_t session_id) { if(config.verbose) { print_session_id(session_id); print_timer(); std::cout << " closed" << std::endl; } } } // namespace class SpdyEventHandler : public EventHandler { public: SpdyEventHandler(int fd, SSL *ssl, const spdylay_session_callbacks *callbacks, int64_t session_id) : fd_(fd), ssl_(ssl), session_id_(session_id), want_write_(false) { spdylay_session_server_new(&session_, callbacks, this); } virtual ~SpdyEventHandler() { on_session_closed(session_id_); spdylay_session_del(session_); for(std::map::iterator i = id2req_.begin(), eoi = id2req_.end(); i != eoi; ++i) { delete (*i).second; } SSL_shutdown(ssl_); SSL_free(ssl_); shutdown(fd_, SHUT_WR); close(fd_); } virtual int execute(Sessions *sessions) { int r; r = spdylay_session_recv(session_); if(r == 0) { r = spdylay_session_send(session_); } return r; } virtual bool want_read() { return spdylay_session_want_read(session_); } virtual bool want_write() { return spdylay_session_want_write(session_) || want_write_; } virtual int fd() const { return fd_; } virtual bool finish() { return !want_read() && !want_write(); } ssize_t send_data(const uint8_t *data, size_t len, int flags) { ssize_t r; r = SSL_write(ssl_, data, len); return r; } ssize_t recv_data(uint8_t *data, size_t len, int flags) { ssize_t r; want_write_ = false; r = SSL_read(ssl_, data, len); if(r < 0) { if(SSL_get_error(ssl_, r) == SSL_ERROR_WANT_WRITE) { want_write_ = true; } } return r; } bool would_block(int r) { int e = SSL_get_error(ssl_, r); return e == SSL_ERROR_WANT_WRITE || e == SSL_ERROR_WANT_READ; } int submit_file_response(const std::string& status, int32_t stream_id, time_t last_modified, off_t file_length, spdylay_data_provider *data_prd) { const char *nv[] = { "status", status.c_str(), "version", "HTTP/1.1", "server", SPDYD_SERVER.c_str(), "content-length", util::to_str(file_length).c_str(), "cache-control", "max-age=3600", "date", util::http_date(time(0)).c_str(), 0, 0, 0 }; if(last_modified != 0) { nv[12] = "last-modified"; nv[13] = util::http_date(last_modified).c_str(); } return spdylay_submit_response(session_, stream_id, nv, data_prd); } int submit_response(const std::string& status, int32_t stream_id, spdylay_data_provider *data_prd) { const char *nv[] = { "status", status.c_str(), "version", "HTTP/1.1", "server", SPDYD_SERVER.c_str(), 0 }; return spdylay_submit_response(session_, stream_id, nv, data_prd); } void add_stream(int32_t stream_id, Request *req) { id2req_[stream_id] = req; } void remove_stream(int32_t stream_id) { Request *req = id2req_[stream_id]; id2req_.erase(stream_id); delete req; } Request* get_stream(int32_t stream_id) { return id2req_[stream_id]; } int64_t session_id() const { return session_id_; } private: spdylay_session *session_; int fd_; SSL* ssl_; int64_t session_id_; bool want_write_; std::map id2req_; }; namespace { ssize_t hd_send_callback(spdylay_session *session, const uint8_t *data, size_t len, int flags, void *user_data) { SpdyEventHandler *hd = (SpdyEventHandler*)user_data; ssize_t r = hd->send_data(data, len, flags); if(r < 0) { if(hd->would_block(r)) { r = SPDYLAY_ERR_WOULDBLOCK; } else { r = SPDYLAY_ERR_CALLBACK_FAILURE; } } return r; } } // namespace namespace { ssize_t hd_recv_callback(spdylay_session *session, uint8_t *data, size_t len, int flags, void *user_data) { SpdyEventHandler *hd = (SpdyEventHandler*)user_data; ssize_t r = hd->recv_data(data, len, flags); if(r < 0) { if(hd->would_block(r)) { r = SPDYLAY_ERR_WOULDBLOCK; } else { r = SPDYLAY_ERR_CALLBACK_FAILURE; } } else if(r == 0) { r = SPDYLAY_ERR_CALLBACK_FAILURE; } return r; } } // namespace namespace { ssize_t file_read_callback (spdylay_session *session, uint8_t *buf, size_t length, int *eof, spdylay_data_source *source, void *user_data) { int fd = source->fd; ssize_t r; while((r = read(fd, buf, length)) == -1 && errno == EINTR); if(r == -1) { return SPDYLAY_ERR_CALLBACK_FAILURE; } else { if(r == 0) { *eof = 1; } return r; } } } // namespace namespace { bool check_url(const std::string& url) { // We don't like '\' in url. return !url.empty() && url[0] == '/' && url.find('\\') == std::string::npos && url.find("/../") == std::string::npos && url.find("/./") == std::string::npos && !util::endsWith(url, "/..") && !util::endsWith(url, "/."); } } // namespace namespace { void prepare_status_response(Request *req, SpdyEventHandler *hd, const std::string& status) { int pipefd[2]; if(pipe(pipefd) == -1) { hd->submit_response(status, req->stream_id, 0); } else { std::stringstream ss; ss << "" << status << "" << "

" << status << "

" << "
" << "
" << SPDYD_SERVER << " at port " << config.port << "
" << ""; std::string body = ss.str(); write(pipefd[1], body.c_str(), body.size()); close(pipefd[1]); req->file = pipefd[0]; spdylay_data_provider data_prd; data_prd.source.fd = pipefd[0]; data_prd.read_callback = file_read_callback; hd->submit_file_response(status, req->stream_id, 0, body.size(), &data_prd); } } } // namespace namespace { void prepare_response(Request *req, SpdyEventHandler *hd) { std::string url; bool url_found = false; bool method_found = false; bool scheme_found = false; bool version_found = false; time_t last_mod; bool last_mod_found = false; for(int i = 0; i < (int)req->headers.size(); ++i) { const std::string &field = req->headers[i].first; const std::string &value = req->headers[i].second; if(!url_found && field == "url") { url_found = true; url = value; } else if(field == "method") { method_found = true; } else if(field == "scheme") { scheme_found = true; } else if(field == "version") { version_found = true; } else if(!last_mod_found && field == "if-modified-since") { last_mod_found = true; last_mod = util::parse_http_date(value); } } if(!url_found || !method_found || !scheme_found || !version_found) { prepare_status_response(req, hd, STATUS_400); return; } std::string::size_type query_pos = url.find("?"); if(query_pos != std::string::npos) { url = url.substr(0, query_pos); } url = util::percentDecode(url.begin(), url.end()); if(!check_url(url)) { prepare_status_response(req, hd, STATUS_404); return; } std::string path = config.htdocs+url; if(path[path.size()-1] == '/') { path += DEFAULT_HTML; } int file = open(path.c_str(), O_RDONLY); if(file == -1) { prepare_status_response(req, hd, STATUS_404); } else { struct stat buf; if(fstat(file, &buf) == -1) { close(file); prepare_status_response(req, hd, STATUS_404); } else { req->file = file; spdylay_data_provider data_prd; data_prd.source.fd = file; data_prd.read_callback = file_read_callback; if(last_mod_found && buf.st_mtime <= last_mod) { prepare_status_response(req, hd, STATUS_304); } else { hd->submit_file_response(STATUS_200, req->stream_id, buf.st_mtime, buf.st_size, &data_prd); } } } } } // namespace namespace { void append_nv(Request *req, char **nv) { for(int i = 0; nv[i]; i += 2) { req->headers.push_back(std::make_pair(nv[i], nv[i+1])); } } } // namespace namespace { void hd_on_ctrl_recv_callback (spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, void *user_data) { SpdyEventHandler *hd = (SpdyEventHandler*)user_data; if(config.verbose) { print_session_id(hd->session_id()); on_ctrl_recv_callback(session, type, frame, user_data); } switch(type) { case SPDYLAY_SYN_STREAM: { int32_t stream_id = frame->syn_stream.stream_id; Request *req = new Request(stream_id); append_nv(req, frame->syn_stream.nv); hd->add_stream(stream_id, req); break; } case SPDYLAY_HEADERS: { int32_t stream_id = frame->headers.stream_id; Request *req = hd->get_stream(stream_id); append_nv(req, frame->headers.nv); break; } default: break; } } } // namespace namespace { void hd_on_request_recv_callback (spdylay_session *session, int32_t stream_id, void *user_data) { SpdyEventHandler *hd = (SpdyEventHandler*)user_data; prepare_response(hd->get_stream(stream_id), hd); } } // namespace namespace { void hd_on_ctrl_send_callback (spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, void *user_data) { SpdyEventHandler *hd = (SpdyEventHandler*)user_data; if(config.verbose) { print_session_id(hd->session_id()); on_ctrl_send_callback(session, type, frame, user_data); } } } // namespace namespace { void on_data_chunk_recv_callback (spdylay_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) { // TODO Handle POST } } // namespace namespace { void hd_on_data_recv_callback (spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, void *user_data) { // TODO Handle POST SpdyEventHandler *hd = (SpdyEventHandler*)user_data; if(config.verbose) { print_session_id(hd->session_id()); on_data_recv_callback(session, flags, stream_id, length, user_data); } } } // namespace namespace { void hd_on_data_send_callback (spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length, void *user_data) { SpdyEventHandler *hd = (SpdyEventHandler*)user_data; if(config.verbose) { print_session_id(hd->session_id()); on_data_send_callback(session, flags, stream_id, length, user_data); } } } // namespace namespace { void on_stream_close_callback (spdylay_session *session, int32_t stream_id, spdylay_status_code status_code, void *user_data) { SpdyEventHandler *hd = (SpdyEventHandler*)user_data; hd->remove_stream(stream_id); if(config.verbose) { print_session_id(hd->session_id()); print_timer(); printf(" stream_id=%d closed\n", stream_id); fflush(stdout); } } } // namespace class SSLAcceptEventHandler : public EventHandler { public: SSLAcceptEventHandler(int fd, SSL *ssl, int64_t session_id) : fd_(fd), ssl_(ssl), fail_(false), want_read_(true), want_write_(true), finish_(false), session_id_(session_id) {} virtual ~SSLAcceptEventHandler() { if(fail_) { on_session_closed(session_id_); SSL_shutdown(ssl_); SSL_free(ssl_); shutdown(fd_, SHUT_WR); close(fd_); } } virtual int execute(Sessions *sessions) { want_read_ = want_write_ = false; int r = SSL_accept(ssl_); if(r == 1) { finish_ = true; const unsigned char *next_proto = 0; unsigned int next_proto_len; SSL_get0_next_proto_negotiated(ssl_, &next_proto, &next_proto_len); if(next_proto) { std::string proto(next_proto, next_proto+next_proto_len); if(config.verbose) { std::cout << "The negotiated next protocol: " << proto << std::endl; } if(proto == "spdy/2") { add_next_handler(sessions); } else { fail_ = true; } } else { fail_ = true; } } else if(r == 0) { int e = SSL_get_error(ssl_, r); if(e == SSL_ERROR_SSL) { if(config.verbose) { std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; } } finish_ = true; fail_ = true; } else { int d = SSL_get_error(ssl_, r); if(d == SSL_ERROR_WANT_READ) { want_read_ = true; } else if(d == SSL_ERROR_WANT_WRITE) { want_write_ = true; } else { finish_ = true; fail_ = true; } } return 0; } virtual bool want_read() { return want_read_; } virtual bool want_write() { return want_write_; } virtual int fd() const { return fd_; } virtual bool finish() { return finish_; } private: void add_next_handler(Sessions *sessions) { spdylay_session_callbacks callbacks; memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); callbacks.send_callback = hd_send_callback; callbacks.recv_callback = hd_recv_callback; callbacks.on_stream_close_callback = on_stream_close_callback; callbacks.on_ctrl_recv_callback = hd_on_ctrl_recv_callback; callbacks.on_ctrl_send_callback = hd_on_ctrl_send_callback; callbacks.on_data_recv_callback = hd_on_data_recv_callback; callbacks.on_data_send_callback = hd_on_data_send_callback; callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; callbacks.on_request_recv_callback = hd_on_request_recv_callback; SpdyEventHandler *hd = new SpdyEventHandler(fd_, ssl_, &callbacks, session_id_); if(sessions->mod_poll(hd) == -1) { // fd_, ssl_ are freed by ~SpdyEventHandler() delete hd; } else { sessions->add_handler(hd); } } int fd_; SSL *ssl_; bool fail_, finish_; bool want_read_, want_write_; int64_t session_id_; }; class ListenEventHandler : public EventHandler { public: ListenEventHandler(int fd) : fd_(fd), session_id_seed_(0) {} virtual ~ListenEventHandler() { close(fd_); } virtual int execute(Sessions *sessions) { int cfd; while((cfd = accept(fd_, 0, 0)) == -1 && errno == EINTR); if(cfd != -1) { if(make_non_block(cfd) == -1 || set_tcp_nodelay(cfd) == -1) { close(cfd); } else { add_next_handler(sessions, cfd); } } return 0; } virtual bool want_read() { return true; } virtual bool want_write() { return false; } virtual int fd() const { return fd_; } virtual bool finish() { return false; } private: void add_next_handler(Sessions *sessions, int cfd) { SSL *ssl = sessions->ssl_session_new(cfd); if(ssl == 0) { close(cfd); return; } SSLAcceptEventHandler *hd = new SSLAcceptEventHandler(cfd, ssl, ++session_id_seed_); if(sessions->add_poll(hd) == -1) { delete hd; SSL_free(ssl); close(cfd); } else { sessions->add_handler(hd); } } int fd_; int64_t session_id_seed_; }; namespace { void on_close(Sessions &sessions, EventHandler *hd) { sessions.remove_handler(hd); delete hd; } } // namespace namespace { unsigned char *proto_list; unsigned int proto_list_len; } // namespace namespace { int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len, void *arg) { *data = proto_list; *len = proto_list_len; return SSL_TLSEXT_ERR_OK; } } // namespace namespace { int reactor() { SSL_CTX *ssl_ctx; ssl_ctx = SSL_CTX_new(SSLv23_server_method()); if(!ssl_ctx) { std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl; return -1; } SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); if(SSL_CTX_use_PrivateKey_file(ssl_ctx, config.private_key_file.c_str(), SSL_FILETYPE_PEM) != 1) { std::cerr << "SSL_CTX_use_PrivateKey_file failed." << std::endl; return -1; } if(SSL_CTX_use_certificate_file(ssl_ctx, config.cert_file.c_str(), SSL_FILETYPE_PEM) != 1) { std::cerr << "SSL_CTX_use_certificate_file failed." << std::endl; return -1; } if(SSL_CTX_check_private_key(ssl_ctx) != 1) { std::cerr << "SSL_CTX_check_private_key failed." << std::endl; return -1; } SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, 0); int sfd = make_listen_socket(config.port); if(sfd == -1) { std::cerr << "Could not listen on port " << config.port << std::endl; return -1; } make_non_block(sfd); int epfd = epoll_create(1); if(epfd == -1) { perror("epoll_create"); return -1; } Sessions sessions(epfd, ssl_ctx); ListenEventHandler *listen_hd = new ListenEventHandler(sfd); if(sessions.add_poll(listen_hd) == -1) { std::cerr << "Adding listening socket to poll failed." << std::endl; return -1; } sessions.add_handler(listen_hd); const int MAX_EVENTS = 256; epoll_event events[MAX_EVENTS]; while(1) { int n = epoll_wait(epfd, events, MAX_EVENTS, -1); if(n == -1) { perror("epoll_wait"); } else { for(int i = 0; i < n; ++i) { EventHandler *hd = reinterpret_cast(events[i].data.ptr); int r = 0; if((events[i].events & EPOLLIN) || (events[i].events & EPOLLOUT)) { r = hd->execute(&sessions); } else if(events[i].events & (EPOLLERR | EPOLLHUP)) { on_close(sessions, hd); } if(r != 0) { on_close(sessions, hd); } else { if(hd->finish()) { on_close(sessions, hd); } else { sessions.mod_poll(hd); } } } } } on_close(sessions, listen_hd); close(epfd); return 0; } } // namespace namespace { void print_usage(std::ostream& out) { out << "Usage: spdyd [-hv] PORT PRIVATE_KEY CERT" << std::endl; } } // namespace namespace { void print_help(std::ostream& out) { print_usage(out); out << "\n" << "OPTIONS:\n" << " -d, --htdocs=PATH Specify document root. If this option is not\n" << " specified, the document root is the current\n" << " working directory.\n" << "\n" << " -v, --verbose Print debug information such as reception/\n" << " transmission of frames and name/value pairs.\n" << std::endl; } } // namespace int main(int argc, char **argv) { config.htdocs = "./"; while(1) { static option long_options[] = { {"verbose", no_argument, 0, 'v' }, {"htdocs", required_argument, 0, 'd' }, {"help", no_argument, 0, 'h' }, {0, 0, 0, 0 } }; int option_index = 0; int c = getopt_long(argc, argv, "dhv", long_options, &option_index); if(c == -1) { break; } switch(c) { case 'd': config.htdocs = optarg; break; case 'h': print_help(std::cout); exit(EXIT_SUCCESS); case 'v': config.verbose = true; break; case '?': exit(EXIT_FAILURE); default: break; } } if(argc-optind < 3) { print_usage(std::cerr); std::cerr << "Too few arguments" << std::endl; exit(EXIT_FAILURE); } struct sigaction act; memset(&act, 0, sizeof(struct sigaction)); act.sa_handler = SIG_IGN; sigaction(SIGPIPE, &act, 0); OpenSSL_add_all_algorithms(); SSL_load_error_strings(); SSL_library_init(); reset_timer(); config.port = strtol(argv[optind++], 0, 10); config.private_key_file = argv[optind++]; config.cert_file = argv[optind++]; ssl_debug = config.verbose; // We only speak "spdy/2". proto_list_len = 7; proto_list = new unsigned char[proto_list_len]; proto_list[0] = 6; memcpy(&proto_list[1], "spdy/2", 6); reactor(); return 0; } } // namespace spdylay int main(int argc, char **argv) { return spdylay::main(argc, argv); }