#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::string; using std::endl; using std::clog; /* * An Input Buffer - only one read(2) is used each time InBuff::read() is * called, getline only reads from the preread buffer. This is usefull for * handling select. * Todo: It would be nice if this was a complete istream ... */ class InBuff { public: InBuff(int fd = -1) : read_fd(fd), is_eof(false) {} void read(); // Simplified semantics: return false if there isn't a whole line to read // and in that case, leave the buffer as is untill next time. bool getline(string& line, char sep = '\n'); void attach(int fd) { read_fd = fd; } bool eof() const { return is_eof; } private: int read_fd; bool is_eof; string data; }; void InBuff::read() { char buffer[1024]; ssize_t result = ::read(read_fd, buffer, 1023); if(result == -1) throw incu::c_error("read data"); if(result == 0) { is_eof = true; throw std::underflow_error("end of file"); } DEBUG << "Read " << result << " chars."; data += string(buffer, result); } bool InBuff::getline(string& line, char sep) { const string::size_type pos = data.find(sep); if(pos == string::npos) return false; line = data.substr(0, pos); data.erase(0, pos+1); return true; } int checked_accept(int serv_sock) { struct sockaddr_in remote; socklen_t len = sizeof(remote); int remote_fd = accept(serv_sock, reinterpret_cast(&remote), &len); if(remote_fd == -1) throw incu::c_error("Cannot accept"); incu::Log() << "Got request from " << inet_ntoa(remote.sin_addr) << " on fd " << remote_fd; return remote_fd; } class Connection { public: Connection(const string& target, int serv_sock, bool redir_to_uri, const string& response) : remote_fd(checked_accept(serv_sock)), obuf(new incu::OutFdBuf(remote_fd, true /* , bufsize? */)), con(&(*obuf)), is_done(false), target_base(target), append_uri(redir_to_uri), http_response(response) { buf.attach(remote_fd); } ~Connection() { DEBUG << "Hang up on " << remote_fd; if(buf.eof()) DEBUG << "Connection is already closed"; else { con << "Connection: close" << endl; } } bool handleLine() { buf.read(); string line; while(buf.getline(line)) { if(line.size() > 0 && line[line.size()-1] == '\r') line.erase(line.size()-1); DEBUG << "fd #" << remote_fd << " says \"" << line << '"'; if(line.empty()) { // request completed; give a response if(uri.empty()) { con << "HTTP/1.1 501 Not Implemented" << endl << "Date: " << incu::Time().format() << endl << "Server: webredirect, Rasmus Kaj (Unix)" << endl; is_done = true; return false; } con << http_response << "\nDate: " << incu::Time().format() << "\nServer: webredirect, Rasmus Kaj (Unix)" << "\nLocation: " << target_base; if (append_uri) con << uri; con << "\nContent-Type: text/html\n\n"; con << ""; con << "Please see " << target_base; if (append_uri) con << uri; con << "" << endl; is_done = true; return false; // done ... } string::size_type pos = line.find(' '); const string w = line.substr(0, pos); if(w == "HEAD" || w == "GET" || w == "POST" && pos != string::npos) { ++pos; string::size_type p2 = line.find(' ', pos); if(p2 != string::npos) p2 -= pos; uri = line.substr(pos, p2); DEBUG << "fd #" << remote_fd << " requested " << uri; } } return true; // more to come ... } int fd() const { return remote_fd; } bool done() const { return is_done; } private: int remote_fd; InBuff buf; // The stream is for output only, input is through buf. std::auto_ptr obuf; std::ostream con; string uri; bool is_done; string target_base; bool append_uri; string http_response; }; class FdSet : public fd_set { public: FdSet() { FD_ZERO(this); } void set(int fd) { FD_SET(fd, this); } void clear(int fd) { FD_CLR(fd, this); } bool isset(int fd) { return FD_ISSET(fd, this); } }; int main(int argc, char* argv[]) { using incu::Log; try { Log::addDevice(new incu::SysLog(argv[0], incu::SysLog::DAEMON)); incu::SockaddrIn bind_addr; bind_addr.port(80).addr(INADDR_ANY); string location; string response = "HTTP/1.1 301 Moved Permanently"; bool append_uri = true; for(int i = 1; i < argc; ++i) { if(argv[i][0] == '-') switch(argv[i][1]) { case 'd': Log::setTreshold(incu::l_debug); break; case 'q': Log::setTreshold(incu::l_important); break; case 'a': bind_addr.addr(argv[++i]); break; case 'p': bind_addr.port(atoi(argv[++i])); break; case 't': location = argv[++i]; break; case 'n': append_uri = false; break; case 'm': response = "HTTP/1.1 302 Moved Temporarily"; break; case 'h': clog << "Usage: " << argv[0] << " [ options ]" << endl << "Options include:" << endl << " -d enable debug printouts." << endl << " -q log important stuff only." << endl << " -a addr bind addr only, default INADDR_ANY." << endl << " -p portno Bind the server to portno, default is 80." << endl << " -t target Redirect users to target." << endl << " -n don't append requested path to redirect" << endl << " -m send HTTP 302 (moved temporarily), instead of 301" << endl; return 0; default: clog << "unrecognized option: -" << argv[i][1] << endl; return 1; } } if(location.empty()) throw std::runtime_error("No redirect target specified."); Log() << "Starting webredirect " << bind_addr << " to " << location; incu::Socket server(PF_INET, SOCK_STREAM, "tcp"); server.bind(bind_addr); server.listen(64); // GH says 64 is a kernel limit, but it isn't. FdSet ifds; ifds.set(server.fd()); int maxfd = server.fd(); std::map current; for(;;) try { DEBUG << "Main loop"; incu::TimeVal to(5 * 60); FdSet tifds = ifds; int numfds = select( maxfd+1, &tifds, NULL, NULL, &to); DEBUG << "Select returned " << numfds; if ( numfds == -1 ) throw incu::c_error("Cannot select"); if ( numfds == 0 ) continue; /* Find the first connection with input */ for(int fd = 0; fd <= maxfd ; ++fd) if(tifds.isset(fd)) { if(fd == server.fd()) { // Request for a new connection Connection* con = new Connection(location, server.fd(), append_uri, response); current[con->fd()] = con; ifds.set(con->fd()); maxfd = std::max(maxfd, con->fd()); } else try { // Data from existing connection if(!current[fd]->handleLine() || current[fd]->done()) { Connection *t = current[fd]; current[fd] = 0; delete t; ifds.clear(fd); } } catch(const std::exception& err) { incu::Log() << "Terminating connection " << fd << " on error: " << err.what(); Connection *t = current[fd]; current[fd] = 0; delete t; ifds.clear(fd); } } } catch(const std::exception& err) { incu::Log() << err.what(); sleep(2); // Don't choke cpu / logs on repeting errors } // Never leave the main loop, but we might have got an exception before we // entered it ... } catch(const std::exception& err) { incu::Log(l_fatal) << err.what(); } }