unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
authprompt.c (14241B)
1 /* 2 * Auth prompt: SASL authentication for clients that don't support SASL 3 * (C) Copyright 2018 Bram Matthys ("Syzop") and the UnrealIRCd team 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 1, or (at your option) 8 * any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 */ 19 20 #include "unrealircd.h" 21 22 ModuleHeader MOD_HEADER 23 = { 24 "authprompt", 25 "1.0", 26 "SASL authentication for clients that don't support SASL", 27 "UnrealIRCd Team", 28 "unrealircd-6", 29 }; 30 31 /** Configuration settings */ 32 struct { 33 int enabled; 34 MultiLine *message; 35 MultiLine *fail_message; 36 MultiLine *unconfirmed_message; 37 } cfg; 38 39 /** User struct */ 40 typedef struct APUser APUser; 41 struct APUser { 42 char *authmsg; 43 char *reason; 44 }; 45 46 /* Global variables */ 47 ModDataInfo *authprompt_md = NULL; 48 49 /* Forward declarations */ 50 static void free_config(void); 51 static void init_config(void); 52 static void config_postdefaults(void); 53 int authprompt_config_test(ConfigFile *, ConfigEntry *, int, int *); 54 int authprompt_config_run(ConfigFile *, ConfigEntry *, int); 55 int authprompt_sasl_continuation(Client *client, const char *buf); 56 int authprompt_sasl_result(Client *client, int success); 57 int authprompt_place_host_ban(Client *client, int action, const char *reason, long duration); 58 int authprompt_find_tkline_match(Client *client, TKL *tk); 59 int authprompt_pre_local_handshake_timeout(Client *client, const char **comment); 60 int authprompt_pre_connect(Client *client); 61 CMD_FUNC(cmd_auth); 62 void authprompt_md_free(ModData *md); 63 64 /* Some macros */ 65 #define SetAPUser(x, y) do { moddata_client(x, authprompt_md).ptr = y; } while(0) 66 #define SEUSER(x) ((APUser *)moddata_client(x, authprompt_md).ptr) 67 #define AGENT_SID(agent_p) (agent_p->user != NULL ? agent_p->user->server : agent_p->name) 68 69 MOD_TEST() 70 { 71 HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, authprompt_config_test); 72 return MOD_SUCCESS; 73 } 74 75 MOD_INIT() 76 { 77 ModDataInfo mreq; 78 79 MARK_AS_OFFICIAL_MODULE(modinfo); 80 81 memset(&mreq, 0, sizeof(mreq)); 82 mreq.name = "authprompt"; 83 mreq.type = MODDATATYPE_CLIENT; 84 mreq.free = authprompt_md_free; 85 authprompt_md = ModDataAdd(modinfo->handle, mreq); 86 if (!authprompt_md) 87 { 88 config_error("could not register authprompt moddata"); 89 return MOD_FAILED; 90 } 91 92 init_config(); 93 HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, authprompt_config_run); 94 HookAdd(modinfo->handle, HOOKTYPE_SASL_CONTINUATION, 0, authprompt_sasl_continuation); 95 HookAdd(modinfo->handle, HOOKTYPE_SASL_RESULT, 0, authprompt_sasl_result); 96 HookAdd(modinfo->handle, HOOKTYPE_PLACE_HOST_BAN, 0, authprompt_place_host_ban); 97 HookAdd(modinfo->handle, HOOKTYPE_FIND_TKLINE_MATCH, 0, authprompt_find_tkline_match); 98 HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_HANDSHAKE_TIMEOUT, 0, authprompt_pre_local_handshake_timeout); 99 /* For HOOKTYPE_PRE_LOCAL_CONNECT we want a low priority, so we are called last. 100 * This gives hooks like the one from the blacklist module (pending softban) 101 * a chance to be handled first. 102 */ 103 HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, -1000000, authprompt_pre_connect); 104 CommandAdd(modinfo->handle, "AUTH", cmd_auth, 1, CMD_UNREGISTERED); 105 return MOD_SUCCESS; 106 } 107 108 MOD_LOAD() 109 { 110 config_postdefaults(); 111 return MOD_SUCCESS; 112 } 113 114 MOD_UNLOAD() 115 { 116 free_config(); 117 return MOD_SUCCESS; 118 } 119 120 static void init_config(void) 121 { 122 /* This sets some default values */ 123 memset(&cfg, 0, sizeof(cfg)); 124 cfg.enabled = 1; 125 } 126 127 static void config_postdefaults(void) 128 { 129 if (!cfg.message) 130 { 131 addmultiline(&cfg.message, "The server requires clients from this IP address to authenticate with a registered nickname and password."); 132 addmultiline(&cfg.message, "Please reconnect using SASL, or authenticate now by typing: /QUOTE AUTH nick:password"); 133 } 134 if (!cfg.fail_message) 135 { 136 addmultiline(&cfg.fail_message, "Authentication failed."); 137 } 138 if (!cfg.unconfirmed_message) 139 { 140 addmultiline(&cfg.unconfirmed_message, "You are trying to use an unconfirmed services account."); 141 addmultiline(&cfg.unconfirmed_message, "This services account can only be used after it has been activated/confirmed."); 142 } 143 } 144 145 static void free_config(void) 146 { 147 freemultiline(cfg.message); 148 freemultiline(cfg.fail_message); 149 freemultiline(cfg.unconfirmed_message); 150 memset(&cfg, 0, sizeof(cfg)); /* needed! */ 151 } 152 153 int authprompt_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) 154 { 155 int errors = 0; 156 ConfigEntry *cep; 157 158 if (type != CONFIG_SET) 159 return 0; 160 161 /* We are only interrested in set::authentication-prompt... */ 162 if (!ce || !ce->name || strcmp(ce->name, "authentication-prompt")) 163 return 0; 164 165 for (cep = ce->items; cep; cep = cep->next) 166 { 167 if (!cep->value) 168 { 169 config_error("%s:%i: set::authentication-prompt::%s with no value", 170 cep->file->filename, cep->line_number, cep->name); 171 errors++; 172 } else 173 if (!strcmp(cep->name, "enabled")) 174 { 175 } else 176 if (!strcmp(cep->name, "message")) 177 { 178 } else 179 if (!strcmp(cep->name, "fail-message")) 180 { 181 } else 182 if (!strcmp(cep->name, "unconfirmed-message")) 183 { 184 } else 185 { 186 config_error("%s:%i: unknown directive set::authentication-prompt::%s", 187 cep->file->filename, cep->line_number, cep->name); 188 errors++; 189 } 190 } 191 *errs = errors; 192 return errors ? -1 : 1; 193 } 194 195 int authprompt_config_run(ConfigFile *cf, ConfigEntry *ce, int type) 196 { 197 ConfigEntry *cep; 198 199 if (type != CONFIG_SET) 200 return 0; 201 202 /* We are only interrested in set::authentication-prompt... */ 203 if (!ce || !ce->name || strcmp(ce->name, "authentication-prompt")) 204 return 0; 205 206 for (cep = ce->items; cep; cep = cep->next) 207 { 208 if (!strcmp(cep->name, "enabled")) 209 { 210 cfg.enabled = config_checkval(cep->value, CFG_YESNO); 211 } else 212 if (!strcmp(cep->name, "message")) 213 { 214 addmultiline(&cfg.message, cep->value); 215 } else 216 if (!strcmp(cep->name, "fail-message")) 217 { 218 addmultiline(&cfg.fail_message, cep->value); 219 } else 220 if (!strcmp(cep->name, "unconfirmed-message")) 221 { 222 addmultiline(&cfg.unconfirmed_message, cep->value); 223 } 224 } 225 return 1; 226 } 227 228 void authprompt_md_free(ModData *md) 229 { 230 APUser *se = md->ptr; 231 232 if (se) 233 { 234 safe_free(se->authmsg); 235 safe_free(se->reason); 236 safe_free(se); 237 md->ptr = se = NULL; 238 } 239 } 240 241 /** Parse an authentication request from the user (form: <user>:<pass>). 242 * @param str The input string with the request. 243 * @param username Pointer to the username string. 244 * @param password Pointer to the password string. 245 * @retval 1 if the format is correct, 0 if not. 246 * @note The returned 'username' and 'password' are valid until next call to parse_nickpass(). 247 */ 248 int parse_nickpass(const char *str, char **username, char **password) 249 { 250 static char buf[250]; 251 char *p; 252 253 strlcpy(buf, str, sizeof(buf)); 254 255 p = strchr(buf, ':'); 256 if (!p) 257 return 0; 258 259 *p++ = '\0'; 260 *username = buf; 261 *password = p; 262 263 if (!*username[0] || !*password[0]) 264 return 0; 265 266 return 1; 267 } 268 269 char *make_authbuf(const char *username, const char *password) 270 { 271 char inbuf[256]; 272 static char outbuf[512]; 273 int size; 274 275 size = strlen(username) + 1 + strlen(username) + 1 + strlen(password); 276 if (size >= sizeof(inbuf)-1) 277 return NULL; /* too long */ 278 279 /* Because size limits are already checked above, we can cut some corners here: */ 280 memset(inbuf, 0, sizeof(inbuf)); 281 strcpy(inbuf, username); 282 strcpy(inbuf+strlen(username)+1, username); 283 strcpy(inbuf+strlen(username)+1+strlen(username)+1, password); 284 /* ^ normal people use stpcpy here ;) */ 285 286 if (b64_encode(inbuf, size, outbuf, sizeof(outbuf)) < 0) 287 return NULL; /* base64 encoding error */ 288 289 return outbuf; 290 } 291 292 /** Send first SASL authentication request (AUTHENTICATE PLAIN). 293 * Among other things, this is used to discover the agent 294 * which will later be used for this session. 295 */ 296 void send_first_auth(Client *client) 297 { 298 Client *sasl_server; 299 char *addr = BadPtr(client->ip) ? "0" : client->ip; 300 const char *certfp = moddata_client_get(client, "certfp"); 301 302 sasl_server = find_client(SASL_SERVER, NULL); 303 if (!sasl_server) 304 { 305 /* Services down. */ 306 return; 307 } 308 309 /* Make them a user, needed for CHGHOST etc that we may receive */ 310 if (!client->user) 311 make_user(client); 312 313 sendto_one(sasl_server, NULL, ":%s SASL %s %s H %s %s", 314 me.id, SASL_SERVER, client->id, addr, addr); 315 316 if (certfp) 317 sendto_one(sasl_server, NULL, ":%s SASL %s %s S %s %s", 318 me.id, SASL_SERVER, client->id, "PLAIN", certfp); 319 else 320 sendto_one(sasl_server, NULL, ":%s SASL %s %s S %s", 321 me.id, SASL_SERVER, client->id, "PLAIN"); 322 323 /* The rest is sent from authprompt_sasl_continuation() */ 324 325 client->local->sasl_out++; 326 } 327 328 CMD_FUNC(cmd_auth) 329 { 330 char *username = NULL; 331 char *password = NULL; 332 char *authbuf; 333 334 if (!SEUSER(client)) 335 { 336 if (HasCapability(client, "sasl")) 337 sendnotice(client, "ERROR: Cannot use /AUTH when your client is doing SASL."); 338 else 339 sendnotice(client, "ERROR: /AUTH authentication request received before authentication prompt (too early!)"); 340 return; 341 } 342 343 if ((parc < 2) || BadPtr(parv[1]) || !parse_nickpass(parv[1], &username, &password)) 344 { 345 sendnotice(client, "ERROR: Syntax is: /AUTH <nickname>:<password>"); 346 sendnotice(client, "Example: /AUTH mynick:secretpass"); 347 return; 348 } 349 350 if (!SASL_SERVER) 351 { 352 sendnotice(client, "ERROR: SASL is not configured on this server, or services are down."); 353 // numeric instead? SERVICESDOWN? 354 return; 355 } 356 357 /* Presumably if the user is really fast, this could happen.. */ 358 if (*client->local->sasl_agent || SEUSER(client)->authmsg) 359 { 360 sendnotice(client, "ERROR: Previous authentication request is still in progress. Please wait."); 361 return; 362 } 363 364 authbuf = make_authbuf(username, password); 365 if (!authbuf) 366 { 367 sendnotice(client, "ERROR: Internal error. Oversized username/password?"); 368 return; 369 } 370 371 safe_strdup(SEUSER(client)->authmsg, authbuf); 372 373 send_first_auth(client); 374 } 375 376 void authprompt_tag_as_auth_required(Client *client, const char *reason) 377 { 378 /* Allocate, and therefore indicate, that we are going to handle SASL for this user */ 379 if (!SEUSER(client)) 380 SetAPUser(client, safe_alloc(sizeof(APUser))); 381 safe_strdup(SEUSER(client)->reason, reason); 382 } 383 384 void authprompt_send_auth_required_message(Client *client) 385 { 386 /* Send the standard-reply ACCOUNT_REQUIRED_TO_CONNECT if the client supports receiving it */ 387 if (HasCapability(client, "standard-replies")) 388 { 389 const char *reason = SEUSER(client) && SEUSER(client)->reason ? SEUSER(client)->reason : NULL; 390 if (reason) 391 sendto_one(client, NULL, "FAIL * ACCOUNT_REQUIRED_TO_CONNECT :An account is required to connect: %s", reason); 392 else 393 sendto_one(client, NULL, "FAIL * ACCOUNT_REQUIRED_TO_CONNECT :An account is required to connect"); 394 } 395 396 /* Display set::authentication-prompt::message */ 397 sendnotice_multiline(client, cfg.message); 398 } 399 400 /* Called upon "place a host ban on this user" (eg: spamfilter, blacklist, ..) */ 401 int authprompt_place_host_ban(Client *client, int action, const char *reason, long duration) 402 { 403 /* If it's a soft-xx action and the user is not logged in 404 * and the user is not yet online, then we will handle this user. 405 */ 406 if (IsSoftBanAction(action) && !IsLoggedIn(client) && !IsUser(client) && cfg.enabled) 407 { 408 /* And tag the user */ 409 authprompt_tag_as_auth_required(client, reason); 410 authprompt_send_auth_required_message(client); 411 return 1; /* pretend user is killed */ 412 } 413 return 99; /* no action taken, proceed normally */ 414 } 415 416 /** Called upon "check for KLINE/GLINE" */ 417 int authprompt_find_tkline_match(Client *client, TKL *tkl) 418 { 419 /* If it's a soft-xx action and the user is not logged in 420 * and the user is not yet online, then we will handle this user. 421 */ 422 if (cfg.enabled && 423 TKLIsServerBan(tkl) && 424 (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) && 425 !IsLoggedIn(client) && 426 !IsUser(client)) 427 { 428 /* And tag the user */ 429 authprompt_tag_as_auth_required(client, tkl->ptr.serverban->reason); 430 authprompt_send_auth_required_message(client); 431 return 1; /* pretend user is killed */ 432 } 433 return 99; /* no action taken, proceed normally */ 434 } 435 436 int authprompt_pre_connect(Client *client) 437 { 438 /* If the user is tagged as auth required and not logged in, then.. */ 439 if (SEUSER(client) && !IsLoggedIn(client) && cfg.enabled) 440 { 441 authprompt_send_auth_required_message(client); 442 return HOOK_DENY; /* do not process register_user() */ 443 } 444 445 return HOOK_CONTINUE; /* no action taken, proceed normally */ 446 } 447 448 int authprompt_sasl_continuation(Client *client, const char *buf) 449 { 450 /* If it's not for us (eg: user is doing real SASL) then return 0. */ 451 if (!SEUSER(client) || !SEUSER(client)->authmsg) 452 return 0; 453 454 if (!strcmp(buf, "+")) 455 { 456 Client *agent = find_client(client->local->sasl_agent, NULL); 457 if (agent) 458 { 459 sendto_one(agent, NULL, ":%s SASL %s %s C %s", 460 me.id, AGENT_SID(agent), client->id, SEUSER(client)->authmsg); 461 } 462 safe_free(SEUSER(client)->authmsg); 463 } 464 return 1; /* inhibit displaying of message */ 465 } 466 467 int authprompt_sasl_result(Client *client, int success) 468 { 469 /* If it's not for us (eg: user is doing real SASL) then return 0. */ 470 if (!SEUSER(client)) 471 return 0; 472 473 if (!success) 474 { 475 sendnotice_multiline(client, cfg.fail_message); 476 return 1; 477 } 478 479 if (client->user && !IsLoggedIn(client)) 480 { 481 sendnotice_multiline(client, cfg.unconfirmed_message); 482 return 1; 483 } 484 485 /* Authentication was a success */ 486 if (*client->name && client->user && *client->user->username && IsNotSpoof(client)) 487 { 488 register_user(client); 489 /* User MAY be killed now. But since we 'return 1' below, it's safe */ 490 } 491 492 return 1; /* inhibit success/failure message */ 493 } 494 495 /** Override the default "Registration timeout" quit reason */ 496 int authprompt_pre_local_handshake_timeout(Client *client, const char **comment) 497 { 498 if (SEUSER(client)) 499 { 500 if (SEUSER(client)->reason) 501 *comment = SEUSER(client)->reason; 502 else 503 *comment = "Account required to connect"; 504 } 505 506 return HOOK_CONTINUE; 507 }