2 * Copyright (C) 2006 Michael Tharp <gxti@partiallystapled.com>
3 * Copyright (C) 2006 charybdis development team
4 * Copyright (C) 2016 ChatLounge IRC Network Development Team
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
10 * 1.Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * 2.Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3.The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
43 #include "s_newconf.h"
46 static const char sasl_desc
[] = "Provides SASL authentication support";
48 static void m_authenticate(struct MsgBuf
*, struct Client
*, struct Client
*, int, const char **);
49 static void me_sasl(struct MsgBuf
*, struct Client
*, struct Client
*, int, const char **);
50 static void me_mechlist(struct MsgBuf
*, struct Client
*, struct Client
*, int, const char **);
52 static void abort_sasl(struct Client
*);
53 static void abort_sasl_exit(hook_data_client_exit
*);
55 static void advertise_sasl_cap(bool);
56 static void advertise_sasl_new(struct Client
*);
57 static void advertise_sasl_exit(void *);
58 static void advertise_sasl_config(void *);
60 static unsigned int CLICAP_SASL
= 0;
61 static char mechlist_buf
[BUFSIZE
];
62 static bool sasl_agent_present
= false;
64 struct Message authenticate_msgtab
= {
65 "AUTHENTICATE", 0, 0, 0, 0,
66 {{m_authenticate
, 2}, {m_authenticate
, 2}, mg_ignore
, mg_ignore
, mg_ignore
, {m_authenticate
, 2}}
68 struct Message sasl_msgtab
= {
70 {mg_ignore
, mg_ignore
, mg_ignore
, mg_ignore
, {me_sasl
, 5}, mg_ignore
}
72 struct Message mechlist_msgtab
= {
73 "MECHLIST", 0, 0, 0, 0,
74 {mg_ignore
, mg_ignore
, mg_ignore
, mg_ignore
, {me_mechlist
, 2}, mg_ignore
}
77 mapi_clist_av1 sasl_clist
[] = {
78 &authenticate_msgtab
, &sasl_msgtab
, &mechlist_msgtab
, NULL
80 mapi_hfn_list_av1 sasl_hfnlist
[] = {
81 { "new_local_user", (hookfn
) abort_sasl
},
82 { "client_exit", (hookfn
) abort_sasl_exit
},
83 { "new_remote_user", (hookfn
) advertise_sasl_new
},
84 { "after_client_exit", (hookfn
) advertise_sasl_exit
},
85 { "conf_read_end", (hookfn
) advertise_sasl_config
},
90 sasl_visible(struct Client
*ignored
)
92 struct Client
*agent_p
= NULL
;
94 if (ConfigFileEntry
.sasl_service
)
95 agent_p
= find_named_client(ConfigFileEntry
.sasl_service
);
97 return agent_p
!= NULL
&& IsService(agent_p
);
101 sasl_data(struct Client
*client_p
)
103 return *mechlist_buf
!= 0 ? mechlist_buf
: NULL
;
106 static struct ClientCapability capdata_sasl
= {
107 .visible
= sasl_visible
,
109 .flags
= CLICAP_FLAGS_STICKY
,
115 memset(mechlist_buf
, 0, sizeof mechlist_buf
);
116 sasl_agent_present
= false;
118 CLICAP_SASL
= capability_put(cli_capindex
, "sasl", &capdata_sasl
);
119 advertise_sasl_config(NULL
);
126 advertise_sasl_cap(false);
127 capability_orphan(cli_capindex
, "sasl");
130 DECLARE_MODULE_AV2(sasl
, _modinit
, _moddeinit
, sasl_clist
, NULL
, sasl_hfnlist
, NULL
, NULL
, sasl_desc
);
133 m_authenticate(struct MsgBuf
*msgbuf_p
, struct Client
*client_p
, struct Client
*source_p
,
134 int parc
, const char *parv
[])
136 struct Client
*agent_p
= NULL
;
137 struct Client
*saslserv_p
= NULL
;
139 /* They really should use CAP for their own sake. */
140 if(!IsCapable(source_p
, CLICAP_SASL
))
143 if(source_p
->localClient
->sasl_next_retry
> rb_current_time())
145 sendto_one(source_p
, form_str(RPL_LOAD2HI
), me
.name
, EmptyString(source_p
->name
) ? "*" : source_p
->name
, msgbuf_p
->cmd
);
149 if (strlen(client_p
->id
) == 3 || (source_p
->preClient
&& !EmptyString(source_p
->preClient
->id
)))
151 exit_client(client_p
, client_p
, client_p
, "Mixing client and server protocol");
155 if (*parv
[1] == ':' || strchr(parv
[1], ' '))
157 exit_client(client_p
, client_p
, client_p
, "Malformed AUTHENTICATE");
161 saslserv_p
= find_named_client(ConfigFileEntry
.sasl_service
);
162 if(saslserv_p
== NULL
|| !IsService(saslserv_p
))
164 sendto_one(source_p
, form_str(ERR_SASLABORTED
), me
.name
, EmptyString(source_p
->name
) ? "*" : source_p
->name
);
168 if(source_p
->localClient
->sasl_complete
)
170 *source_p
->localClient
->sasl_agent
= '\0';
171 source_p
->localClient
->sasl_complete
= 0;
174 if(strlen(parv
[1]) > 400)
176 sendto_one(source_p
, form_str(ERR_SASLTOOLONG
), me
.name
, EmptyString(source_p
->name
) ? "*" : source_p
->name
);
182 /* Allocate a UID. */
183 rb_strlcpy(source_p
->id
, generate_uid(), sizeof(source_p
->id
));
184 add_to_id_hash(source_p
->id
, source_p
);
187 if(*source_p
->localClient
->sasl_agent
)
188 agent_p
= find_id(source_p
->localClient
->sasl_agent
);
192 if (!strcmp(parv
[1], "*"))
194 sendto_one(source_p
, form_str(ERR_SASLABORTED
), me
.name
, EmptyString(source_p
->name
) ? "*" : source_p
->name
);
195 source_p
->localClient
->sasl_out
= 0;
199 sendto_one(saslserv_p
, ":%s ENCAP %s SASL %s %s H %s %s %c",
200 me
.id
, saslserv_p
->servptr
->name
, source_p
->id
, saslserv_p
->id
,
201 source_p
->host
, source_p
->sockhost
,
202 IsSSL(source_p
) ? 'S' : 'P');
204 if (source_p
->certfp
!= NULL
)
205 sendto_one(saslserv_p
, ":%s ENCAP %s SASL %s %s S %s %s",
206 me
.id
, saslserv_p
->servptr
->name
, source_p
->id
, saslserv_p
->id
,
207 parv
[1], source_p
->certfp
);
209 sendto_one(saslserv_p
, ":%s ENCAP %s SASL %s %s S %s",
210 me
.id
, saslserv_p
->servptr
->name
, source_p
->id
, saslserv_p
->id
,
213 rb_strlcpy(source_p
->localClient
->sasl_agent
, saslserv_p
->id
, IDLEN
);
217 if (!strcmp(parv
[1], "*"))
219 sendto_one(source_p
, form_str(ERR_SASLABORTED
), me
.name
, EmptyString(source_p
->name
) ? "*" : source_p
->name
);
220 sendto_one(agent_p
, ":%s ENCAP %s SASL %s %s D A", me
.id
, agent_p
->servptr
->name
, source_p
->id
, agent_p
->id
);
221 source_p
->localClient
->sasl_out
= 0;
225 sendto_one(agent_p
, ":%s ENCAP %s SASL %s %s C %s",
226 me
.id
, agent_p
->servptr
->name
, source_p
->id
, agent_p
->id
,
230 source_p
->localClient
->sasl_out
++;
234 me_sasl(struct MsgBuf
*msgbuf_p
, struct Client
*client_p
, struct Client
*source_p
,
235 int parc
, const char *parv
[])
237 struct Client
*target_p
, *agent_p
;
240 /* Let propagate if not addressed to us, or if broadcast.
241 * Only SASL agents can answer global requests.
243 if(strncmp(parv
[2], me
.id
, 3))
246 if((target_p
= find_id(parv
[2])) == NULL
)
249 if((agent_p
= find_id(parv
[1])) == NULL
)
252 if(source_p
!= agent_p
->servptr
) /* WTF?! */
255 /* We only accept messages from SASL agents; these must have umode +S
256 * (so the server must be listed in a service{} block).
258 if(!IsService(agent_p
))
261 /* If SASL has been aborted, we only want to track authentication failures. */
262 in_progress
= target_p
->localClient
->sasl_out
!= 0;
264 /* Reject if someone has already answered. */
265 if(*target_p
->localClient
->sasl_agent
&& strncmp(parv
[1], target_p
->localClient
->sasl_agent
, IDLEN
))
267 else if(!*target_p
->localClient
->sasl_agent
&& in_progress
)
268 rb_strlcpy(target_p
->localClient
->sasl_agent
, parv
[1], IDLEN
);
273 sendto_one(target_p
, "AUTHENTICATE %s", parv
[4]);
274 target_p
->localClient
->sasl_messages
++;
277 else if(*parv
[3] == 'D')
282 sendto_one(target_p
, form_str(ERR_SASLFAIL
), me
.name
, EmptyString(target_p
->name
) ? "*" : target_p
->name
);
284 /* Failures with zero messages are just "unknown mechanism" errors; don't count those. */
285 if(target_p
->localClient
->sasl_messages
> 0)
289 /* Allow 2 tries before rate-limiting as some clients try EXTERNAL
290 * then PLAIN right after it if the auth failed, causing the client to be
291 * rate-limited immediately and not being able to login with SASL.
293 if (target_p
->localClient
->sasl_failures
++ > 0)
294 target_p
->localClient
->sasl_next_retry
= rb_current_time() + (1 << MIN(target_p
->localClient
->sasl_failures
+ 1, 8));
296 else if(throttle_add((struct sockaddr
*)&target_p
->localClient
->ip
))
298 exit_client(target_p
, target_p
, &me
, "Too many failed authentication attempts");
303 else if(*parv
[4] == 'S')
306 sendto_one(target_p
, form_str(RPL_SASLSUCCESS
), me
.name
, EmptyString(target_p
->name
) ? "*" : target_p
->name
);
307 target_p
->localClient
->sasl_failures
= 0;
308 target_p
->localClient
->sasl_complete
= 1;
309 ServerStats
.is_ssuc
++;
312 *target_p
->localClient
->sasl_agent
= '\0'; /* Blank the stored agent so someone else can answer */
313 target_p
->localClient
->sasl_messages
= 0;
315 else if(*parv
[3] == 'M')
318 sendto_one(target_p
, form_str(RPL_SASLMECHS
), me
.name
, EmptyString(target_p
->name
) ? "*" : target_p
->name
, parv
[4]);
324 me_mechlist(struct MsgBuf
*msgbuf_p
, struct Client
*client_p
, struct Client
*source_p
,
325 int parc
, const char *parv
[])
327 rb_strlcpy(mechlist_buf
, parv
[1], sizeof mechlist_buf
);
330 /* If the client never finished authenticating but is
331 * registering anyway, abort the exchange.
334 abort_sasl(struct Client
*data
)
336 if(data
->localClient
->sasl_out
== 0 || data
->localClient
->sasl_complete
)
339 data
->localClient
->sasl_out
= data
->localClient
->sasl_complete
= 0;
340 ServerStats
.is_sbad
++;
343 sendto_one(data
, form_str(ERR_SASLABORTED
), me
.name
, EmptyString(data
->name
) ? "*" : data
->name
);
345 if(*data
->localClient
->sasl_agent
)
347 struct Client
*agent_p
= find_id(data
->localClient
->sasl_agent
);
350 sendto_one(agent_p
, ":%s ENCAP %s SASL %s %s D A", me
.id
, agent_p
->servptr
->name
,
351 data
->id
, agent_p
->id
);
356 sendto_server(NULL
, NULL
, CAP_TS6
|CAP_ENCAP
, NOCAPS
, ":%s ENCAP * SASL %s * D A", me
.id
,
361 abort_sasl_exit(hook_data_client_exit
*data
)
363 if (data
->target
->localClient
)
364 abort_sasl(data
->target
);
368 advertise_sasl_cap(bool available
)
370 if (sasl_agent_present
!= available
) {
372 sendto_local_clients_with_capability(CLICAP_CAP_NOTIFY
, ":%s CAP * NEW :sasl", me
.name
);
374 sendto_local_clients_with_capability(CLICAP_CAP_NOTIFY
, ":%s CAP * DEL :sasl", me
.name
);
376 sasl_agent_present
= available
;
381 advertise_sasl_new(struct Client
*client_p
)
383 if (!ConfigFileEntry
.sasl_service
)
386 if (irccmp(client_p
->name
, ConfigFileEntry
.sasl_service
))
389 advertise_sasl_cap(IsService(client_p
));
393 advertise_sasl_exit(void *ignored
)
395 if (!ConfigFileEntry
.sasl_service
)
398 if (sasl_agent_present
) {
399 advertise_sasl_cap(sasl_visible(NULL
));
404 advertise_sasl_config(void *ignored
)
406 advertise_sasl_cap(sasl_visible(NULL
));