--- sshd.c.orig+debian 2009-02-17 15:41:42.000000000 +0000 +++ sshd.c 2009-02-17 15:40:05.000000000 +0000 @@ -1266,6 +1266,204 @@ } } +/* exponential backoff hack, to prevent brute-force attacks against sshd */ + +#include +#define backoff_database "/var/lib/ssh/backoff.db" +#define backoff_database_dir "/var/lib/ssh" +#define backoff_initial_lockout 1 +#define backoff_max_sleep 16 + /* backoff_max_sleep = 16 allows "simultaneous" connections */ + +/* needs -ldb-4 */ + +int +exp_backoff_close(DB *db) +{ + int fd; + if (db->fd(db, &fd) != 0) + goto failed; + struct flock fl; + fl.l_type = F_UNLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 1; + fcntl(fd, F_SETLK, &fl); + if (db->close(db, 0) == -1) + return -1; + return 0; +failed: + debug("exp_backoff_close failed: %.200s", strerror(errno)); + db->close(db, 0); + return -1; +} + +void +exp_backoff_db_error_handler(const DB_ENV *dbenv, const char *error_prefix, const char *msg) +{ + if (error_prefix != NULL && msg != NULL) { + debug("%s: %s", error_prefix, msg); + } else if (error_prefix == NULL && msg != NULL) { + debug("db error: %s", msg); + } else if (error_prefix != NULL && msg == NULL) { + debug("%s: error", error_prefix); + } else if (error_prefix == NULL && msg == NULL) { + debug("db error"); + } +} + +DB * +exp_backoff_open_and_lock(const char *remote_ip) +{ + remote_ip = remote_ip; /* ignore */ + DB *db; + int status; + if ((status = db_create(&db, NULL, 0))) { + debug("exp_backoff_open_and_lock: db_create failed: %s", db_strerror(status)); + goto failed; + } + db->set_errcall(db, exp_backoff_db_error_handler); + db->set_errpfx(db, "exp_backoff"); + mkdir(backoff_database_dir, 0700); + if ((status = db->open(db, NULL, backoff_database, NULL, DB_HASH, DB_CREATE, 0600))) { + db->err(db, status, "db->open failed"); + goto failed; + } + int fd; + if ((status = db->fd(db, &fd) != 0)) { + db->err(db, status, "db->fd failed"); + goto failed; + } + struct flock fl; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 1; + if (fcntl(fd, F_SETLKW, &fl) == -1) { + debug("exp_backoff_open_and_lock: fcntl(.. F_SETLKW ..) failed: %.200s", strerror(errno)); + goto failed; + } + return db; +failed: + exp_backoff_close(db); + return NULL; +} + +int +exp_backoff_check_allowed_to_login(const char *remote_ip) +{ + int allowed = 0; + int status; + time_t when; + char when_s[64]; + + debug("exp_backoff_check_allowed_to_login: remote_ip = %s", remote_ip); + + DB *db; + debug("exp_backoff_check_allowed_to_login"); + db = exp_backoff_open_and_lock(remote_ip); + if (db == NULL) + goto failed2; + debug("exp_backoff_check_allowed_to_login: opened okay"); + + DBT key, data; + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + + key.data = (char*)remote_ip; + key.size = strlen(remote_ip)+1; + + data.data = when_s; + data.ulen = sizeof(when_s); + data.flags = DB_DBT_USERMEM; + + status = db->get(db, NULL, &key, &data, 0); + if (status == DB_NOTFOUND) { + debug("exp_backoff_check_allowed_to_login: not found"); + int count = snprintf(when_s, sizeof(when_s), "%d %d", backoff_initial_lockout, (int)time(NULL)); + data.size = count + 1; + status = db->put(db, NULL, &key, &data, 0); + if (status == -1) { + db->err(db, status, "db->put %s:%s failed"); + goto failed; + } + debug("exp_backoff_check_allowed_to_login: put %s:%s succeeded", (char*)(key.data), (char*)(data.data)); + allowed = 1; + } else if (status == 0) { + debug("exp_backoff_check_allowed_to_login: found"); + int lockout_time; + status = sscanf(data.data, "%d %d", &lockout_time, (int *)&when); + if (status != 2) { + debug("exp_backoff_check_allowed_to_login: sscanf failed: %.200s", strerror(errno)); + goto failed; + } + debug("lockout_time %d ; when %d", lockout_time, (int)when); + + allowed = 1; + int now; + while ((now = time(NULL)) < when) { + if (when-now > backoff_max_sleep) { + allowed = 0; + break; + } + sleep(when-now); + debug("exp_backoff_check_allowed_to_login: sleep done"); + } + if (allowed) { + when = now + lockout_time; + debug("when %d ; allowed %d", (int)when, (int)time(NULL)); + lockout_time *= 2; + + int count = snprintf(when_s, sizeof(when_s), "%d %d", lockout_time, (int)when); + data.size = count + 1; + status = db->put(db, NULL, &key, &data, 0); + if (status == -1) { + db->err(db, status, "db->put failed"); + goto failed; + } + debug("exp_backoff_check_allowed_to_login: put succeeded"); + } + } else { + db->err(db, status, "db->get failed"); + goto failed; + } + + if (exp_backoff_close(db) != 0) + goto failed2; + + debug("exp_backoff_check_allowed_to_login: returning %d", allowed); + return allowed; + +failed: + exp_backoff_close(db); +failed2: + return -1; +} + +void +exp_backoff_reset_allowed_to_login(const char *remote_ip) +{ + DB *db; + int status; + debug("exp_backoff_reset_allowed_to_login"); + + db = exp_backoff_open_and_lock(remote_ip); + if (db == NULL) { + return; + } + + DBT key; + memset(&key, 0, sizeof(DBT)); + + key.data = (char*)remote_ip; + key.size = strlen(remote_ip)+1; + + if ((status = db->del(db, NULL, &key, 0)) != 0) { + db->err(db, status, "db->del failed"); + } + + exp_backoff_close(db); +} /* * Main program for the daemon. @@ -1874,6 +2072,15 @@ /* Log the connection. */ verbose("Connection from %.500s port %d", remote_ip, remote_port); + /* + * Exponential backoff - check when that IP address is allowed to log + * in, and exit if not yet. + */ + int allowed = exp_backoff_check_allowed_to_login(remote_ip); + if (!allowed) { + exit(0); + } + #ifdef USE_SECURITY_SESSION_API /* * Create a new security session for use by the new user login if @@ -2012,6 +2219,8 @@ } #endif + exp_backoff_reset_allowed_to_login(remote_ip); + /* * In privilege separation, we fork another child and prepare * file descriptor passing.