diff --git a/include/client.h b/include/client.h index a97b67bd0..cfc55ceee 100644 --- a/include/client.h +++ b/include/client.h @@ -285,6 +285,10 @@ struct LocalUser char sasl_agent[IDLEN]; unsigned char sasl_out; unsigned char sasl_complete; + + unsigned int sasl_messages; + unsigned int sasl_failures; + time_t sasl_next_retry; }; struct AuthClient diff --git a/modules/m_sasl.c b/modules/m_sasl.c index 23a5e313e..470832cb8 100644 --- a/modules/m_sasl.c +++ b/modules/m_sasl.c @@ -35,6 +35,7 @@ #include "msg.h" #include "modules.h" #include "numeric.h" +#include "reject.h" #include "s_serv.h" #include "s_stats.h" #include "string.h" @@ -130,14 +131,20 @@ m_authenticate(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client * if(!IsCapable(source_p, CLICAP_SASL)) return; - if (strlen(client_p->id) == 3) + if(source_p->localClient->sasl_next_retry > rb_current_time()) + { + sendto_one(source_p, form_str(RPL_LOAD2HI), me.name, EmptyString(source_p->name) ? "*" : source_p->name, msgbuf_p->cmd); + return; + } + + if(strlen(client_p->id) == 3) { exit_client(client_p, client_p, client_p, "Mixing client and server protocol"); return; } saslserv_p = find_named_client(ConfigFileEntry.sasl_service); - if (saslserv_p == NULL || !IsService(saslserv_p)) + if(saslserv_p == NULL || !IsService(saslserv_p)) { sendto_one(source_p, form_str(ERR_SASLABORTED), me.name, EmptyString(source_p->name) ? "*" : source_p->name); return; @@ -223,17 +230,39 @@ me_sasl(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_ rb_strlcpy(target_p->localClient->sasl_agent, parv[1], IDLEN); if(*parv[3] == 'C') + { sendto_one(target_p, "AUTHENTICATE %s", parv[4]); + target_p->localClient->sasl_messages++; + } else if(*parv[3] == 'D') { if(*parv[4] == 'F') + { sendto_one(target_p, form_str(ERR_SASLFAIL), me.name, EmptyString(target_p->name) ? "*" : target_p->name); - else if(*parv[4] == 'S') { + /* Failures with zero messages are just "unknown mechanism" errors; don't count those. */ + if(target_p->localClient->sasl_messages > 0) + { + if(*target_p->name) + { + target_p->localClient->sasl_failures++; + target_p->localClient->sasl_next_retry = rb_current_time() + (1 << MIN(target_p->localClient->sasl_failures + 5, 13)); + } + else if(throttle_add((struct sockaddr*)&target_p->localClient->ip)) + { + exit_client(target_p, target_p, &me, "Too many failed authentication attempts"); + return; + } + } + } + else if(*parv[4] == 'S') + { sendto_one(target_p, form_str(RPL_SASLSUCCESS), me.name, EmptyString(target_p->name) ? "*" : target_p->name); + target_p->localClient->sasl_failures = 0; target_p->localClient->sasl_complete = 1; ServerStats.is_ssuc++; } *target_p->localClient->sasl_agent = '\0'; /* Blank the stored agent so someone else can answer */ + target_p->localClient->sasl_messages = 0; } else if(*parv[3] == 'M') sendto_one(target_p, form_str(RPL_SASLMECHS), me.name, EmptyString(target_p->name) ? "*" : target_p->name, parv[4]);