// Smersh // // Douglas Thrift // // $Id$ #include "Daemon.hpp" string Daemon::crlf("\r\n"); void Daemon::serve(bool fork, Daemon* self) { api::TcpSocket server; server.Create(); server.SetAddress(api::InternetAddress(api::InternetAddress::Any, port)); if (fork) { switch (pid_t pid = ::fork()) { case -1: cerr << program << ": fork()\n"; exit(1); case 0: break; default: cout << pid << '\n'; return; } } server.Listen(50); while (true) { Client* client (new Client); server.Accept(client->socket, &client->ip); api::Thread thread(etl::BindAll(&Daemon::handle, self, client)); } } Daemon::Status Daemon::request(istream& sin, Environment& env, ostream& post, ostream& log) { string line; Matcher request("^([A-Z]+) (.*?)(\\?.+)? HTTP/(\\d+)\\.(\\d+)$"); getline(sin, line); log << '"' << line << "\" "; env.put("HTTP_REFERER=-"); env.put("HTTP_USER_AGENT=-"); if (line == request) { if (lexical_cast(request[4]) > 1) return version; Matcher method("^GET|HEAD|POST$"); if (request[1] != method) return notImplemented; env.set("REQUEST_METHOD", method); env.set("REQUEST_URI", request[2] + request[3]); if (!request[3].empty()) env.set("QUERY_STRING", request[3].substr(1)); headers(sin, env); if (env.get("HTTP_HOST").empty()) return bad; if (method[0] == "POST") return message(sin, env, post); return ok; } return bad; } void Daemon::response(ostream& sout, Status status) { sout << "HTTP/1.1 " << status << ' ' << reason(status) << crlf << "Date: " << date() << crlf << "Server: " << server() << crlf << "Connection: close" << crlf; } string Daemon::reason(Status status) { ostringstream sout; switch (status) { case ok: sout << "OK"; break; case found: sout << "Found"; break; case seeOther: sout << "See Other"; break; case bad: sout << "Bad Request"; break; case notFound: sout << "Not Found"; break; case lengthRequired: sout << "Length Required"; break; case mediaType: sout << "Unsupported Media Type"; break; case serverError: sout << "Internal Server Error"; break; case notImplemented: sout << "Not Implemented"; break; case version: sout << "HTTP Version not supported"; } return sout.str(); } string Daemon::server() { utsname system; uname(&system); return string("Smersh/0.9 (") + system.sysname + ')'; } string Daemon::server(const Environment& env) { ostringstream server; string port(env.get("SERVER_PORT")); server << this->server() << " Server at " << env.get("SERVER_NAME") << " Po" << "rt " << (port.empty() ? lexical_cast(this->port) : port); return server.str(); } streamsize Daemon::error(ostream& sout, Status status, const Environment& env) { string reason(this->reason(status)); ostringstream error; error << "" << status << ' ' << reason << "

" << reason << "

Mistakes were made, deal with t" << "hem.


" << server(env) << "
\r\n"; sout << "Content-Length: " << error.str().length() << crlf << "Content-Type: text/html; charset=UTF-8\r\n\r\n" << error.str(); return error.str().size(); } string Daemon::date(bool log) { time_t now(time(NULL)); tm time; if (log) localtime_r(&now, &time); else gmtime_r(&now, &time); const char* format(log ? "[%m/%d/%Y:%T %z]" : "%a, %d %b %Y %T GMT"); char when[log ? 29 : 30]; strftime(when, log ? 29 : 30, format, &time); return when; } int Daemon::handle(Client* client) { ios::InputOutputStreamBufAdapter adapter(client->socket); iostream sio(&adapter); Environment env; stringstream post; ostringstream log; Status code(request(sio, env, post, log)); if (env.get("REQUEST_URI") == "/favicon.ico") code = notFound; response(sio, code); bool head(env.get("REQUEST_METHOD") == "HEAD"); streamsize sent(0); if (code == ok && env.get("REQUEST_URI") == "/robots.txt") { sio << "Content-Length: 28\r\nContent-Type: text/plain; charset=UTF-8\r" << "\n\r\n"; if (!head) { sio << "User-agent: *\r\nDisallow: /\r\n"; sent = 28; } } else if (code == ok && !head) { ostringstream content; Smersh smersh(post, content, env); sio << "Content-Length: " << content.str().length() << crlf << "Content-Type: text/html; charset=UTF-8\r\n\r\n"; sio.write(content.str().data(), content.str().size()); sent = content.str().size(); } else if (head) sio << "Content-Type: text/html; charset=UTF-8\r\n\r\n"; else sent = error(sio, code, env); ofstream fout(this->log.c_str(), ios_base::app); fout << inet_ntoa(client->ip->sin_addr) << " - - " << date(true) << ' ' << log.str() << code << ' ' << lexical_cast(sent) << " \"" << env.get("HTTP_REFERER") << "\" \"" << env.get("HTTP_USER_AGENT") << "\"\n"; sio << flush; client->socket.ShutdownWrite(); delete client; } void Daemon::headers(istream& sin, Environment& env) { do { string line; getline(sin, line); if (line.empty()) break; istringstream input(line); string name, value; std::getline(input, name, ':'); getline(input, value); for (char next(sin.peek()); next == ' ' || next == '\t'; next = sin.peek()) { getline(sin, line); value += ' ' + line; } Matcher matcher("^\\s*(.+)\\s*$"); if (name == "Content-Length") { if (value == matcher) env.set("CONTENT_LENGTH", matcher[1]); } else if (name == "Content-Type") { if (value == matcher) env.set("CONTENT_TYPE", matcher[1]); } else if (name == "Host") { Matcher matcher("^\\s*(.+?)(:[0-9]+)?\\s*$"); if (value == matcher) { bool port(matcher.size() > 2); env.set("HTTP_HOST", matcher[1] + (port ? matcher[2] : "")); env.set("SERVER_NAME", matcher[1]); if (port) env.set("SERVER_PORT", matcher[2].substr(1)); } } else if (name == "Referer") { if (value == matcher) env.set("HTTP_REFERER", matcher[1]); } else if (name == "User-Agent") { if (value == matcher) env.set("HTTP_USER_AGENT", matcher[1]); } } while (sin.good()); } Daemon::Status Daemon::message(istream& sin, Environment& env, ostream& post) { string contentLength(env.get("CONTENT_LENGTH")); if (env.get("CONTENT_TYPE") != "application/x-www-form-urlencoded") return mediaType; if (contentLength.empty()) return lengthRequired; streamsize length(lexical_cast(contentLength)); char* content(new char[length]); sin.read(content, length); post.write(content, length); delete [] content; return ok; }