unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
sasl.c (10888B)
1 /* 2 * IRC - Internet Relay Chat, src/modules/sasl.c 3 * (C) 2012 The UnrealIRCd Team 4 * 5 * See file AUTHORS in IRC package for additional names of 6 * the programmers. 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 1, or (at your option) 11 * any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 */ 22 23 #include "unrealircd.h" 24 25 ModuleHeader MOD_HEADER 26 = { 27 "sasl", 28 "5.2.1", 29 "SASL", 30 "UnrealIRCd Team", 31 "unrealircd-6", 32 }; 33 34 /* Forward declarations */ 35 void saslmechlist_free(ModData *m); 36 const char *saslmechlist_serialize(ModData *m); 37 void saslmechlist_unserialize(const char *str, ModData *m); 38 const char *sasl_capability_parameter(Client *client); 39 int sasl_server_synced(Client *client); 40 int sasl_account_login(Client *client, MessageTag *mtags); 41 EVENT(sasl_timeout); 42 43 /* Macros */ 44 #define MSG_AUTHENTICATE "AUTHENTICATE" 45 #define MSG_SASL "SASL" 46 #define AGENT_SID(agent_p) (agent_p->user != NULL ? agent_p->user->server : agent_p->name) 47 48 /* Variables */ 49 long CAP_SASL = 0L; 50 51 /* 52 * The following people were involved in making the previous iteration of SASL over 53 * IRC which allowed psuedo-identifiers: 54 * 55 * danieldg, Daniel de Graff <danieldg@inspircd.org> 56 * jilles, Jilles Tjoelker <jilles@stack.nl> 57 * Jobe, Matthew Beeching <jobe@mdbnet.co.uk> 58 * gxti, Michael Tharp <gxti@partiallystapled.com> 59 * nenolod, William Pitcock <nenolod@dereferenced.org> 60 * 61 * Thanks also to all of the client authors which have implemented SASL in their 62 * clients. With the backwards-compatibility layer allowing "lightweight" SASL 63 * implementations, we now truly have a universal authentication mechanism for 64 * IRC. 65 */ 66 67 int sasl_account_login(Client *client, MessageTag *mtags) 68 { 69 if (!MyConnect(client)) 70 return 0; 71 72 /* Notify user */ 73 if (IsLoggedIn(client)) 74 { 75 sendnumeric(client, RPL_LOGGEDIN, 76 BadPtr(client->name) ? "*" : client->name, 77 BadPtr(client->user->username) ? "*" : client->user->username, 78 BadPtr(client->user->realhost) ? "*" : client->user->realhost, 79 client->user->account, client->user->account); 80 } 81 else 82 { 83 sendnumeric(client, RPL_LOGGEDOUT, 84 BadPtr(client->name) ? "*" : client->name, 85 BadPtr(client->user->username) ? "*" : client->user->username, 86 BadPtr(client->user->realhost) ? "*" : client->user->realhost); 87 } 88 return 0; 89 } 90 91 92 /* 93 * SASL message 94 * 95 * parv[1]: distribution mask 96 * parv[2]: target 97 * parv[3]: mode/state 98 * parv[4]: data 99 * parv[5]: out-of-bound data 100 */ 101 CMD_FUNC(cmd_sasl) 102 { 103 if (!SASL_SERVER || MyUser(client) || (parc < 4) || !parv[4]) 104 return; 105 106 if (!strcasecmp(parv[1], me.name) || !strncmp(parv[1], me.id, 3)) 107 { 108 Client *target; 109 110 target = find_client(parv[2], NULL); 111 if (!target || !MyConnect(target)) 112 return; 113 114 if (target->user == NULL) 115 make_user(target); 116 117 /* reject if another SASL agent is answering */ 118 if (*target->local->sasl_agent && strcasecmp(client->name, target->local->sasl_agent)) 119 return; 120 else 121 strlcpy(target->local->sasl_agent, client->name, sizeof(target->local->sasl_agent)); 122 123 if (*parv[3] == 'C') 124 { 125 RunHookReturn(HOOKTYPE_SASL_CONTINUATION, !=0, target, parv[4]); 126 sendto_one(target, NULL, "AUTHENTICATE %s", parv[4]); 127 } 128 else if (*parv[3] == 'D') 129 { 130 *target->local->sasl_agent = '\0'; 131 if (*parv[4] == 'F') 132 { 133 target->local->sasl_sent_time = 0; 134 add_fake_lag(target, 7000); /* bump fakelag due to failed authentication attempt */ 135 RunHookReturn(HOOKTYPE_SASL_RESULT, !=0, target, 0); 136 sendnumeric(target, ERR_SASLFAIL); 137 } 138 else if (*parv[4] == 'S') 139 { 140 target->local->sasl_sent_time = 0; 141 target->local->sasl_complete++; 142 RunHookReturn(HOOKTYPE_SASL_RESULT, !=0, target, 1); 143 sendnumeric(target, RPL_SASLSUCCESS); 144 } 145 } 146 else if (*parv[3] == 'M') 147 sendnumeric(target, RPL_SASLMECHS, parv[4]); 148 149 return; 150 } 151 152 /* not for us; propagate. */ 153 sendto_server(client, 0, 0, NULL, ":%s SASL %s %s %c %s %s", 154 client->name, parv[1], parv[2], *parv[3], parv[4], parc > 5 ? parv[5] : ""); 155 } 156 157 /* 158 * AUTHENTICATE message 159 * 160 * parv[1]: data 161 */ 162 CMD_FUNC(cmd_authenticate) 163 { 164 Client *agent_p = NULL; 165 166 /* Failing to use CAP REQ for sasl is a protocol violation. */ 167 if (!SASL_SERVER || !MyConnect(client) || BadPtr(parv[1]) || !HasCapability(client, "sasl")) 168 return; 169 170 if ((parv[1][0] == ':') || strchr(parv[1], ' ')) 171 { 172 sendnumeric(client, ERR_CANNOTDOCOMMAND, "AUTHENTICATE", "Invalid parameter"); 173 return; 174 } 175 176 if (strlen(parv[1]) > 400) 177 { 178 sendnumeric(client, ERR_SASLTOOLONG); 179 return; 180 } 181 182 if (client->user == NULL) 183 make_user(client); 184 185 if (*client->local->sasl_agent) 186 agent_p = find_client(client->local->sasl_agent, NULL); 187 188 if (agent_p == NULL) 189 { 190 char *addr = BadPtr(client->ip) ? "0" : client->ip; 191 const char *certfp = moddata_client_get(client, "certfp"); 192 193 sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s H %s %s", 194 me.name, SASL_SERVER, client->id, addr, addr); 195 196 if (certfp) 197 sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s S %s %s", 198 me.name, SASL_SERVER, client->id, parv[1], certfp); 199 else 200 sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s S %s", 201 me.name, SASL_SERVER, client->id, parv[1]); 202 } 203 else 204 sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s C %s", 205 me.name, AGENT_SID(agent_p), client->id, parv[1]); 206 207 client->local->sasl_out++; 208 client->local->sasl_sent_time = TStime(); 209 } 210 211 static int abort_sasl(Client *client) 212 { 213 client->local->sasl_sent_time = 0; 214 215 if (client->local->sasl_out == 0 || client->local->sasl_complete) 216 return 0; 217 218 client->local->sasl_out = client->local->sasl_complete = 0; 219 sendnumeric(client, ERR_SASLABORTED); 220 221 if (*client->local->sasl_agent) 222 { 223 Client *agent_p = find_client(client->local->sasl_agent, NULL); 224 225 if (agent_p != NULL) 226 { 227 sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s D A", 228 me.name, AGENT_SID(agent_p), client->id); 229 return 0; 230 } 231 } 232 233 sendto_server(NULL, 0, 0, NULL, ":%s SASL * %s D A", me.name, client->id); 234 return 0; 235 } 236 237 /** Is this capability visible? 238 * Note that 'client' may be NULL when queried from CAP DEL / CAP NEW 239 */ 240 int sasl_capability_visible(Client *client) 241 { 242 if (!SASL_SERVER || !find_server(SASL_SERVER, NULL)) 243 return 0; 244 245 /* Don't advertise 'sasl' capability if we are going to reject the 246 * user anyway due to set::plaintext-policy. This way the client 247 * won't attempt SASL authentication and thus it prevents the client 248 * from sending the password unencrypted (in case of method PLAIN). 249 */ 250 if (client && !IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_DENY)) 251 return 0; 252 253 /* Similarly, don't advertise when we are going to reject the user 254 * due to set::outdated-tls-policy. 255 */ 256 if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_DENY) && outdated_tls_client(client)) 257 return 0; 258 259 return 1; 260 } 261 262 int sasl_connect(Client *client) 263 { 264 return abort_sasl(client); 265 } 266 267 int sasl_quit(Client *client, MessageTag *mtags, const char *comment) 268 { 269 return abort_sasl(client); 270 } 271 272 int sasl_server_quit(Client *client, MessageTag *mtags) 273 { 274 if (!SASL_SERVER) 275 return 0; 276 277 /* If the set::sasl-server is gone, let everyone know 'sasl' is no longer available */ 278 if (!strcasecmp(client->name, SASL_SERVER)) 279 send_cap_notify(0, "sasl"); 280 281 return 0; 282 } 283 284 void auto_discover_sasl_server(int justlinked) 285 { 286 if (!SASL_SERVER && SERVICES_NAME) 287 { 288 Client *client = find_server(SERVICES_NAME, NULL); 289 if (client && moddata_client_get(client, "saslmechlist")) 290 { 291 /* SASL server found */ 292 if (justlinked) 293 { 294 unreal_log(ULOG_INFO, "config", "SASL_SERVER_AUTODETECT", client, 295 "Services server $client provides SASL authentication, good! " 296 "I'm setting set::sasl-server to \"$client\" internally."); 297 } 298 safe_strdup(SASL_SERVER, SERVICES_NAME); 299 if (justlinked) 300 sasl_server_synced(client); 301 } 302 } 303 } 304 305 int sasl_server_synced(Client *client) 306 { 307 if (!SASL_SERVER) 308 { 309 auto_discover_sasl_server(1); 310 return 0; 311 } 312 313 /* If the set::sasl-server is gone, let everyone know 'sasl' is no longer available */ 314 if (!strcasecmp(client->name, SASL_SERVER)) 315 send_cap_notify(1, "sasl"); 316 317 return 0; 318 } 319 320 MOD_INIT() 321 { 322 ClientCapabilityInfo cap; 323 ModDataInfo mreq; 324 325 MARK_AS_OFFICIAL_MODULE(modinfo); 326 327 CommandAdd(modinfo->handle, MSG_SASL, cmd_sasl, MAXPARA, CMD_USER|CMD_SERVER); 328 CommandAdd(modinfo->handle, MSG_AUTHENTICATE, cmd_authenticate, MAXPARA, CMD_UNREGISTERED|CMD_USER); 329 330 HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, sasl_connect); 331 HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, sasl_quit); 332 HookAdd(modinfo->handle, HOOKTYPE_SERVER_QUIT, 0, sasl_server_quit); 333 HookAdd(modinfo->handle, HOOKTYPE_SERVER_SYNCED, 0, sasl_server_synced); 334 HookAdd(modinfo->handle, HOOKTYPE_ACCOUNT_LOGIN, 0, sasl_account_login); 335 336 memset(&cap, 0, sizeof(cap)); 337 cap.name = "sasl"; 338 cap.visible = sasl_capability_visible; 339 cap.parameter = sasl_capability_parameter; 340 ClientCapabilityAdd(modinfo->handle, &cap, &CAP_SASL); 341 342 memset(&mreq, 0, sizeof(mreq)); 343 mreq.name = "saslmechlist"; 344 mreq.free = saslmechlist_free; 345 mreq.serialize = saslmechlist_serialize; 346 mreq.unserialize = saslmechlist_unserialize; 347 mreq.sync = MODDATA_SYNC_EARLY; 348 mreq.self_write = 1; 349 mreq.type = MODDATATYPE_CLIENT; 350 ModDataAdd(modinfo->handle, mreq); 351 352 EventAdd(modinfo->handle, "sasl_timeout", sasl_timeout, NULL, 2000, 0); 353 354 return MOD_SUCCESS; 355 } 356 357 MOD_LOAD() 358 { 359 auto_discover_sasl_server(0); 360 return MOD_SUCCESS; 361 } 362 363 MOD_UNLOAD() 364 { 365 return MOD_SUCCESS; 366 } 367 368 void saslmechlist_free(ModData *m) 369 { 370 safe_free(m->str); 371 } 372 373 const char *saslmechlist_serialize(ModData *m) 374 { 375 if (!m->str) 376 return NULL; 377 return m->str; 378 } 379 380 void saslmechlist_unserialize(const char *str, ModData *m) 381 { 382 safe_strdup(m->str, str); 383 } 384 385 const char *sasl_capability_parameter(Client *client) 386 { 387 Client *server; 388 389 if (SASL_SERVER) 390 { 391 server = find_server(SASL_SERVER, NULL); 392 if (server) 393 return moddata_client_get(server, "saslmechlist"); /* NOTE: could still return NULL */ 394 } 395 396 return NULL; 397 } 398 399 EVENT(sasl_timeout) 400 { 401 Client *client; 402 403 list_for_each_entry(client, &unknown_list, lclient_node) 404 { 405 if (client->local->sasl_sent_time && 406 (TStime() - client->local->sasl_sent_time > iConf.sasl_timeout)) 407 { 408 sendnotice(client, "SASL request timed out (server or client misbehaving) -- aborting SASL and continuing connection..."); 409 abort_sasl(client); 410 } 411 } 412 }