#include "dispatcher"

typedef map < long, std::queue<time_t> > AccessMap;
static AccessMap accesslog;
static time_t accesslog_lastclean = 0;

// Execute an external program upon excess of hard/soft rates
static void run_excess(string const &prog, char const *ip) {
    ostringstream o;
    o << prog << ' ' << ip;
    msg("Max connection rate exceeded, invoking '" << o.str() << '\n');
    int ret = sysrun(o.str());
    if (ret == -1) {
	throw Error(string("Failed to start system call: ") +
		    strerror(errno));
    } else if (ret) {
	warnmsg("Program '" << o.str()  << 
		"' exited with exit status " << ret << '\n');
    } else {
	msg("Program '" << o.str() << "' finished.\n");
    }
}

bool Dispatcher::check_dos() {
    msg ("Verifying DOS protection\n");
    Threadlist::desc("Verifying");

    static int lock;

    // Check 'softmaxconnrate' and 'hardmaxconnrate' now!
    // Descend into this block if connrate_time() is set, AND
    // either hardmaxconnrate() is set,
    // or both softmaxconnrate() and defertime() are set.
    if (config.connrate_time() &&
	(config.hardmaxconnrate() ||
	 (config.softmaxconnrate() && config.defertime()))) {

	// The map lookup/insert key.
	struct sockaddr_in sa = clientfd().clientaddr();
	long key;
	memset(&key, 0, sizeof(key));
	memcpy(&key, &(sa.sin_addr), sizeof(sa.sin_addr));
	       
     	time_t now, min_ts;
     	now = time(0);
     	min_ts = now - config.connrate_time();
     	unsigned max_conns = max(config.hardmaxconnrate(),
     	  			 config.softmaxconnrate());

	mutex_lock (&lock);
	accesslog[key].push(now);
	mutex_unlock (&lock);
	
	if (accesslog_lastclean < min_ts) {
	    // Clean the entire access log, it's been a while...

	    mutex_lock(&lock);
	    accesslog_lastclean = now;
	    mutex_unlock(&lock);

	    bool done = false;
	    while (!done) {
		done = true;
		for (AccessMap::iterator i = accesslog.begin();
		     i != accesslog.end();
		     i++ ) {
		    if (accesslog[i->first].back() < min_ts) {
			// This IP hasn't made ANY connections in a while
			// -- erase! Also restart loop, i++ won't be valid
			// after erase(i)
			accesslog.erase(i);
			done = false;
			break;
		    } else {
			// Keep popping off this IP's oldest connection
			// until we have only "recent" connections left.
			mutex_lock(&lock);
			while ( accesslog[i->first].front() < min_ts
				|| accesslog[i->first].size() > max_conns )
			    accesslog[i->first].pop();
			mutex_unlock(&lock);
		    }
		}
	    }
	} else {
	    // The "big log" doesn't need to be fully cleaned,
	    // but this particular IP should be!
	    mutex_lock(&lock);
	    while ( (accesslog[key].front() < min_ts)
		    ||
		    (accesslog[key].size() > max_conns)
		)  {
	     	accesslog[key].pop();
	    }
	    mutex_unlock(&lock);
	}

	if ( config.hardmaxconnrate() &&
	     (accesslog[key].size() >= config.hardmaxconnrate()) ) {
	    // This IP has violated the "HARD" limit!  Reject the connection
	    ostringstream o;
	    o << "Client " << inet2string(clientfd().clientaddr().sin_addr)
	      << " has hit the HARD maximum number of connections ("
	      << config.hardmaxconnrate() << " conections in "
	      << config.connrate_time() << " seconds; "
	      << accesslog[key].size() 
	      << " connections recorded). Client is refused.\n";
	    warnmsg (o.str());
	    run_excess(config.hardmaxconnexcess(),
		       inet2string(clientfd().clientaddr().sin_addr).c_str());
	    return false;
	} else if ( config.softmaxconnrate() &&
		    (accesslog[key].size() >= config.softmaxconnrate()) ) {
	    // This IP has violated the "SOFT" Limit. Go to sleep for a while.
	    ostringstream o;
	    o << "Client " << inet2string(clientfd().clientaddr().sin_addr)
	      << " has hit the SOFT maximum number of connections ("
	      << config.softmaxconnrate() << " connections in "
	      << config.connrate_time() << " sedonds; "
	      << accesslog[key].size()
	      << " connections recorded). Client is deferred for "
	      << config.defertime() << " microseconds.\n";
	    warnmsg (o.str());
	    run_excess(config.softmaxconnexcess(),
		       inet2string(clientfd().clientaddr().sin_addr).c_str());
	    usleep(config.defertime());
	}
    }

    return true;
}
